aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore14
-rw-r--r--.travis.yml26
-rw-r--r--CONTRIBUTING.md1
-rw-r--r--HOWTO/INSTALL.md2
-rw-r--r--Makefile.in4
-rw-r--r--OTP_VERSION2
-rw-r--r--bootstrap/bin/no_dot_erlang.bootbin6486 -> 6544 bytes
-rw-r--r--bootstrap/bin/start.bootbin6486 -> 6544 bytes
-rw-r--r--bootstrap/bin/start_clean.bootbin6486 -> 6544 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_a.beambin2712 -> 3200 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_asm.beambin11328 -> 10988 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_block.beambin12080 -> 3444 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_bs.beambin5464 -> 0 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_bsm.beambin11936 -> 0 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_clean.beambin6564 -> 3516 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_dead.beambin13892 -> 0 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_dict.beambin5060 -> 4644 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_disasm.beambin21728 -> 20860 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_except.beambin3216 -> 4204 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_flatten.beambin2988 -> 1928 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_jump.beambin9276 -> 10012 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_kernel_to_ssa.beambin0 -> 28692 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_listing.beambin1332 -> 1580 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_opcodes.beambin7304 -> 7548 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_peep.beambin3036 -> 3588 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_receive.beambin6112 -> 0 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_record.beambin2236 -> 0 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_reorder.beambin1904 -> 0 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_split.beambin2168 -> 0 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa.beambin0 -> 12168 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_bsm.beambin0 -> 17888 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_codegen.beambin0 -> 37604 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_dead.beambin0 -> 12072 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_funs.beambin0 -> 2556 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_lint.beambin0 -> 7536 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_opt.beambin0 -> 39916 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_pp.beambin0 -> 5500 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_pre_codegen.beambin0 -> 45300 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_recv.beambin0 -> 3884 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_share.beambin0 -> 5348 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_type.beambin0 -> 28324 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_trim.beambin7540 -> 8676 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_type.beambin18728 -> 0 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_utils.beambin22904 -> 3548 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_validator.beambin32176 -> 46224 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_z.beambin3284 -> 3604 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/cerl.beambin30144 -> 28236 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/cerl_clauses.beambin2932 -> 2808 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/cerl_inline.beambin37800 -> 34712 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/cerl_sets.beambin2860 -> 2728 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/cerl_trees.beambin22408 -> 20408 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/compile.beambin41552 -> 41208 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/compiler.app25
-rw-r--r--bootstrap/lib/compiler/ebin/compiler.appup2
-rw-r--r--bootstrap/lib/compiler/ebin/core_lib.beambin4292 -> 3720 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/core_lint.beambin12880 -> 12472 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/core_parse.beambin63104 -> 63064 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/core_pp.beambin11964 -> 11472 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/core_scan.beambin6672 -> 6248 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/erl_bifs.beambin2204 -> 2080 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/rec_env.beambin4588 -> 4468 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/sys_core_alias.beambin5984 -> 5548 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/sys_core_bsm.beambin5584 -> 1672 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/sys_core_dsetel.beambin6708 -> 0 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/sys_core_fold.beambin49380 -> 45480 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/sys_core_fold_lists.beambin4408 -> 4020 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/sys_core_inline.beambin3992 -> 3908 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/sys_pre_attributes.beambin2716 -> 2468 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/v3_codegen.beambin65252 -> 0 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/v3_core.beambin58180 -> 50672 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/v3_kernel.beambin55832 -> 50576 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/v3_kernel_pp.beambin12744 -> 12052 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/application.beambin3772 -> 3684 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/application_controller.beambin31400 -> 29612 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/application_master.beambin6344 -> 6108 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/application_starter.beambin1188 -> 1180 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/auth.beambin6332 -> 6148 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/code.beambin13136 -> 12688 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/code_server.beambin24136 -> 22552 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/disk_log.beambin32124 -> 29556 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/disk_log_1.beambin23896 -> 22392 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/disk_log_server.beambin6360 -> 6136 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/dist_ac.beambin24844 -> 23356 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/dist_util.beambin13044 -> 12312 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erl_boot_server.beambin5744 -> 5580 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erl_ddll.beambin2864 -> 2744 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erl_distribution.beambin1620 -> 1596 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erl_epmd.beambin7248 -> 7036 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erl_reply.beambin900 -> 888 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erl_signal_handler.beambin1112 -> 1100 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/error_handler.beambin1596 -> 1576 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/error_logger.beambin6432 -> 6112 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erts_debug.beambin8220 -> 9176 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/file.beambin14052 -> 13356 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/file_io_server.beambin15708 -> 15292 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/file_server.beambin5096 -> 4912 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/gen_sctp.beambin3188 -> 3212 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/gen_tcp.beambin2076 -> 2096 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/gen_udp.beambin1312 -> 1324 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/global.beambin31236 -> 29188 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/global_group.beambin16916 -> 15708 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/global_search.beambin3076 -> 2920 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/group.beambin14948 -> 14200 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/group_history.beambin5720 -> 5496 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/heart.beambin5352 -> 5140 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/hipe_unified_loader.beambin12500 -> 12348 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet.beambin24196 -> 23232 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet6_sctp.beambin1464 -> 1440 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet6_tcp.beambin3120 -> 3016 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet6_udp.beambin1752 -> 1720 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_config.beambin7532 -> 7236 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_db.beambin26440 -> 25300 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_dns.beambin19268 -> 18564 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_gethost_native.beambin10084 -> 9708 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_hosts.beambin1976 -> 1904 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_parse.beambin13828 -> 13396 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_res.beambin13784 -> 13192 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_sctp.beambin2180 -> 2148 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_tcp.beambin2820 -> 2784 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_tcp_dist.beambin7708 -> 7476 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_udp.beambin1924 -> 1908 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/kernel.app7
-rw-r--r--bootstrap/lib/kernel/ebin/kernel.appup49
-rw-r--r--bootstrap/lib/kernel/ebin/kernel.beambin3596 -> 3532 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/kernel_config.beambin2748 -> 2604 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/kernel_refc.beambin2464 -> 2288 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/local_tcp.beambin2248 -> 2180 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/local_udp.beambin1412 -> 1372 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger.beambin11976 -> 15012 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_backend.beambin2632 -> 2544 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_config.beambin3032 -> 3176 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_disk_log_h.beambin3576 -> 3348 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_filters.beambin1800 -> 1748 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_formatter.beambin9060 -> 8552 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_h_common.beambin11800 -> 7644 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_handler_watcher.beambin1372 -> 1344 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_olp.beambin0 -> 8308 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_proxy.beambin0 -> 2884 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_server.beambin11612 -> 11352 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_simple_h.beambin4476 -> 4236 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_std_h.beambin5336 -> 5168 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/logger_sup.beambin576 -> 636 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/net.beambin608 -> 0 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/net_adm.beambin2920 -> 2824 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/net_kernel.beambin25304 -> 24164 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/os.beambin5148 -> 5112 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/pg2.beambin7856 -> 7568 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/ram_file.beambin6248 -> 6008 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/raw_file_io.beambin1724 -> 1660 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/raw_file_io_compressed.beambin2364 -> 2308 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/raw_file_io_deflate.beambin2704 -> 2592 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/raw_file_io_delayed.beambin5428 -> 5200 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/raw_file_io_inflate.beambin4240 -> 4140 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/raw_file_io_list.beambin2608 -> 2460 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/rpc.beambin7940 -> 7704 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/seq_trace.beambin1600 -> 1600 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/standard_error.beambin3828 -> 3724 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/user.beambin11488 -> 10980 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/user_drv.beambin11184 -> 10844 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/user_sup.beambin1736 -> 1704 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/wrap_log_reader.beambin3124 -> 2940 bytes
-rw-r--r--bootstrap/lib/kernel/include/dist.hrl3
-rw-r--r--bootstrap/lib/stdlib/ebin/array.beambin11844 -> 11640 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/base64.beambin6304 -> 6496 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/beam_lib.beambin19612 -> 18660 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/binary.beambin2952 -> 2844 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/c.beambin17408 -> 16968 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/calendar.beambin7756 -> 7632 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/dets.beambin48944 -> 45128 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/dets_server.beambin6732 -> 6444 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/dets_utils.beambin27380 -> 25712 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/dets_v9.beambin47832 -> 45228 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/dict.beambin9516 -> 8836 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/digraph.beambin7848 -> 7500 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/digraph_utils.beambin6824 -> 6732 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/edlin.beambin10952 -> 10472 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/edlin_expand.beambin3976 -> 3748 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/epp.beambin29748 -> 28268 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_abstract_code.beambin1020 -> 968 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_anno.beambin3628 -> 3484 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_bits.beambin2452 -> 2348 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_compile.beambin7168 -> 6676 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_error.beambin8368 -> 8256 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_eval.beambin36256 -> 35048 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_expand_records.beambin21760 -> 19356 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_internal.beambin7796 -> 6732 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_lint.beambin91808 -> 84964 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_parse.beambin97468 -> 96136 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_posix_msg.beambin5160 -> 5168 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_pp.beambin26528 -> 25552 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_scan.beambin28204 -> 25696 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_tar.beambin33388 -> 30896 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/error_logger_file_h.beambin4056 -> 3944 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/error_logger_tty_h.beambin4936 -> 4776 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/escript.beambin16832 -> 15636 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/ets.beambin22460 -> 21460 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/eval_bits.beambin8144 -> 7660 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/file_sorter.beambin29136 -> 27276 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/filelib.beambin10684 -> 10188 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/filename.beambin15144 -> 14788 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/gb_sets.beambin8376 -> 7712 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/gb_trees.beambin5572 -> 5084 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/gen.beambin5068 -> 4896 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/gen_event.beambin13972 -> 13012 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/gen_fsm.beambin11476 -> 12424 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/gen_server.beambin15012 -> 15328 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/gen_statem.beambin20676 -> 20408 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/io.beambin6044 -> 5792 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/io_lib.beambin13828 -> 13532 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/io_lib_format.beambin14876 -> 13892 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/io_lib_fread.beambin7156 -> 6520 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/io_lib_pretty.beambin22016 -> 20996 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/lists.beambin30004 -> 29272 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/log_mf_h.beambin2468 -> 2352 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/maps.beambin3520 -> 3188 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/ms_transform.beambin19544 -> 18452 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/orddict.beambin2928 -> 2900 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/ordsets.beambin1952 -> 1940 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/otp_internal.beambin10596 -> 10396 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/pool.beambin3744 -> 3664 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/proc_lib.beambin13036 -> 12624 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/proplists.beambin4708 -> 4596 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/qlc.beambin68896 -> 65060 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/qlc_pt.beambin74944 -> 70536 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/queue.beambin6124 -> 5904 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/rand.beambin22352 -> 29052 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/random.beambin1728 -> 1768 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/re.beambin13240 -> 12312 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/sets.beambin6608 -> 6152 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/shell.beambin29848 -> 28396 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/shell_default.beambin4064 -> 4064 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/slave.beambin4816 -> 4740 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/sofs.beambin37312 -> 35276 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/stdlib.app4
-rw-r--r--bootstrap/lib/stdlib/ebin/stdlib.appup47
-rw-r--r--bootstrap/lib/stdlib/ebin/string.beambin37096 -> 35032 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/supervisor.beambin23352 -> 21588 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/supervisor_bridge.beambin2400 -> 2332 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/sys.beambin8728 -> 9084 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/timer.beambin5424 -> 5272 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/unicode.beambin13456 -> 14076 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/unicode_util.beambin195372 -> 198636 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/uri_string.beambin26396 -> 24988 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/win32reg.beambin5356 -> 5120 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/zip.beambin26156 -> 24188 bytes
-rw-r--r--bootstrap/lib/stdlib/include/assert.hrl6
-rw-r--r--configure.src435
-rw-r--r--erts/aclocal.m4250
-rwxr-xr-xerts/autoconf/configure.vxworks2
-rw-r--r--erts/configure.in950
-rw-r--r--erts/doc/src/Makefile7
-rw-r--r--erts/doc/src/atomics.xml26
-rw-r--r--erts/doc/src/counters.xml26
-rw-r--r--erts/doc/src/erl.xml2
-rw-r--r--erts/doc/src/erl_dist_protocol.xml116
-rw-r--r--erts/doc/src/erl_driver.xml206
-rw-r--r--erts/doc/src/erl_ext_dist.xml18
-rw-r--r--erts/doc/src/erl_nif.xml518
-rw-r--r--erts/doc/src/erl_prim_loader.xml14
-rw-r--r--erts/doc/src/erl_tracer.xml40
-rw-r--r--erts/doc/src/erlang.xml884
-rw-r--r--erts/doc/src/erts_alloc.xml11
-rw-r--r--erts/doc/src/init.xml22
-rw-r--r--erts/doc/src/net.xml126
-rw-r--r--erts/doc/src/notes.xml129
-rw-r--r--erts/doc/src/part.xml1
-rw-r--r--erts/doc/src/persistent_term.xml28
-rw-r--r--erts/doc/src/ref_man.xml34
-rw-r--r--erts/doc/src/socket.xml645
-rw-r--r--erts/doc/src/socket_usage.xml773
-rw-r--r--erts/doc/src/specs.xml2
-rw-r--r--erts/doc/src/zlib.xml78
-rw-r--r--erts/emulator/Makefile.in45
-rw-r--r--erts/emulator/beam/arith_instrs.tab105
-rw-r--r--erts/emulator/beam/atom.names3
-rw-r--r--erts/emulator/beam/beam_debug.c21
-rw-r--r--erts/emulator/beam/beam_emu.c73
-rw-r--r--erts/emulator/beam/beam_load.c258
-rw-r--r--erts/emulator/beam/bif.c545
-rw-r--r--erts/emulator/beam/bif.tab35
-rw-r--r--erts/emulator/beam/bif_instrs.tab249
-rw-r--r--erts/emulator/beam/big.c134
-rw-r--r--erts/emulator/beam/big.h16
-rw-r--r--erts/emulator/beam/binary.c112
-rw-r--r--erts/emulator/beam/break.c48
-rw-r--r--erts/emulator/beam/bs_instrs.tab309
-rw-r--r--erts/emulator/beam/copy.c6
-rw-r--r--erts/emulator/beam/dist.c1598
-rw-r--r--erts/emulator/beam/dist.h273
-rw-r--r--erts/emulator/beam/erl_afit_alloc.c2
-rw-r--r--erts/emulator/beam/erl_alloc.c248
-rw-r--r--erts/emulator/beam/erl_alloc.h33
-rw-r--r--erts/emulator/beam/erl_alloc.types29
-rw-r--r--erts/emulator/beam/erl_alloc_util.c962
-rw-r--r--erts/emulator/beam/erl_alloc_util.h85
-rw-r--r--erts/emulator/beam/erl_ao_firstfit_alloc.c214
-rw-r--r--erts/emulator/beam/erl_ao_firstfit_alloc.h7
-rw-r--r--erts/emulator/beam/erl_arith.c970
-rw-r--r--erts/emulator/beam/erl_bestfit_alloc.c47
-rw-r--r--erts/emulator/beam/erl_bif_binary.c419
-rw-r--r--erts/emulator/beam/erl_bif_ddll.c2
-rw-r--r--erts/emulator/beam/erl_bif_guard.c540
-rw-r--r--erts/emulator/beam/erl_bif_info.c56
-rw-r--r--erts/emulator/beam/erl_bif_lists.c315
-rw-r--r--erts/emulator/beam/erl_bif_persistent.c17
-rw-r--r--erts/emulator/beam/erl_bif_port.c16
-rw-r--r--erts/emulator/beam/erl_bif_re.c64
-rw-r--r--erts/emulator/beam/erl_binary.h1
-rw-r--r--erts/emulator/beam/erl_bits.c52
-rw-r--r--erts/emulator/beam/erl_bits.h13
-rw-r--r--erts/emulator/beam/erl_db.c103
-rw-r--r--erts/emulator/beam/erl_db.h31
-rw-r--r--erts/emulator/beam/erl_db_catree.c2250
-rw-r--r--erts/emulator/beam/erl_db_catree.h133
-rw-r--r--erts/emulator/beam/erl_db_hash.c14
-rw-r--r--erts/emulator/beam/erl_db_tree.c1711
-rw-r--r--erts/emulator/beam/erl_db_tree_util.h158
-rw-r--r--erts/emulator/beam/erl_db_util.c36
-rw-r--r--erts/emulator/beam/erl_db_util.h40
-rw-r--r--erts/emulator/beam/erl_dirty_bif.tab3
-rw-r--r--erts/emulator/beam/erl_drv_nif.h4
-rw-r--r--erts/emulator/beam/erl_gc.c50
-rw-r--r--erts/emulator/beam/erl_goodfit_alloc.c2
-rw-r--r--erts/emulator/beam/erl_hl_timer.c432
-rw-r--r--erts/emulator/beam/erl_hl_timer.h2
-rw-r--r--erts/emulator/beam/erl_init.c71
-rw-r--r--erts/emulator/beam/erl_lock_check.c103
-rw-r--r--erts/emulator/beam/erl_lock_check.h4
-rw-r--r--erts/emulator/beam/erl_lock_count.h2
-rw-r--r--erts/emulator/beam/erl_lock_flags.h6
-rw-r--r--erts/emulator/beam/erl_map.c76
-rw-r--r--erts/emulator/beam/erl_message.c201
-rw-r--r--erts/emulator/beam/erl_message.h29
-rw-r--r--erts/emulator/beam/erl_monitor_link.c131
-rw-r--r--erts/emulator/beam/erl_monitor_link.h230
-rw-r--r--erts/emulator/beam/erl_nif.c75
-rw-r--r--erts/emulator/beam/erl_nif.h12
-rw-r--r--erts/emulator/beam/erl_nif_api_funcs.h16
-rw-r--r--erts/emulator/beam/erl_node_tables.c240
-rw-r--r--erts/emulator/beam/erl_node_tables.h86
-rw-r--r--erts/emulator/beam/erl_port.h2
-rw-r--r--erts/emulator/beam/erl_port_task.c2
-rw-r--r--erts/emulator/beam/erl_printf_term.c8
-rw-r--r--erts/emulator/beam/erl_proc_sig_queue.c374
-rw-r--r--erts/emulator/beam/erl_proc_sig_queue.h91
-rw-r--r--erts/emulator/beam/erl_process.c1073
-rw-r--r--erts/emulator/beam/erl_process.h14
-rw-r--r--erts/emulator/beam/erl_process_dump.c37
-rw-r--r--erts/emulator/beam/erl_ptab.h5
-rw-r--r--erts/emulator/beam/erl_rbtree.h280
-rw-r--r--erts/emulator/beam/erl_time_sup.c6
-rw-r--r--erts/emulator/beam/erl_trace.c44
-rw-r--r--erts/emulator/beam/erl_trace.h2
-rw-r--r--erts/emulator/beam/erl_unicode.c4
-rw-r--r--erts/emulator/beam/erl_vm.h19
-rw-r--r--erts/emulator/beam/external.c361
-rw-r--r--erts/emulator/beam/external.h76
-rw-r--r--erts/emulator/beam/global.h23
-rw-r--r--erts/emulator/beam/instrs.tab82
-rw-r--r--erts/emulator/beam/io.c19
-rw-r--r--erts/emulator/beam/msg_instrs.tab4
-rw-r--r--erts/emulator/beam/ops.tab394
-rw-r--r--erts/emulator/beam/sys.h9
-rw-r--r--erts/emulator/beam/utils.c119
-rw-r--r--erts/emulator/drivers/common/inet_drv.c10
-rw-r--r--erts/emulator/drivers/unix/ttsl_drv.c6
-rw-r--r--erts/emulator/drivers/win32/ttsl_drv.c4
-rw-r--r--erts/emulator/hipe/hipe_native_bif.c2
-rw-r--r--erts/emulator/internal_doc/CarrierMigration.md19
-rw-r--r--erts/emulator/internal_doc/CountingInstructions.md53
-rw-r--r--erts/emulator/nifs/common/net_nif.c1702
-rw-r--r--erts/emulator/nifs/common/prim_file_nif.c112
-rw-r--r--erts/emulator/nifs/common/socket_dbg.c138
-rw-r--r--erts/emulator/nifs/common/socket_dbg.h55
-rw-r--r--erts/emulator/nifs/common/socket_int.h384
-rw-r--r--erts/emulator/nifs/common/socket_nif.c18649
-rw-r--r--erts/emulator/nifs/common/socket_tarray.c143
-rw-r--r--erts/emulator/nifs/common/socket_tarray.h47
-rw-r--r--erts/emulator/nifs/common/socket_util.c1658
-rw-r--r--erts/emulator/nifs/common/socket_util.h205
-rw-r--r--erts/emulator/sys/common/erl_check_io.c288
-rw-r--r--erts/emulator/sys/common/erl_check_io.h3
-rw-r--r--erts/emulator/sys/common/erl_mmap.h57
-rw-r--r--erts/emulator/sys/common/erl_osenv.c12
-rw-r--r--erts/emulator/sys/common/erl_poll.c4
-rw-r--r--erts/emulator/sys/unix/sys_drivers.c16
-rw-r--r--erts/emulator/sys/win32/sys.c4
-rw-r--r--erts/emulator/test/Makefile33
-rw-r--r--erts/emulator/test/alloc_SUITE.erl3
-rw-r--r--erts/emulator/test/bif_SUITE.erl54
-rw-r--r--erts/emulator/test/big_SUITE.erl18
-rw-r--r--erts/emulator/test/binary_SUITE.erl178
-rw-r--r--erts/emulator/test/bs_construct_SUITE.erl14
-rw-r--r--erts/emulator/test/call_trace_SUITE.erl2
-rw-r--r--erts/emulator/test/code_SUITE.erl26
-rw-r--r--erts/emulator/test/code_SUITE_data/literals.erl8
-rw-r--r--erts/emulator/test/distribution_SUITE.erl336
-rw-r--r--erts/emulator/test/driver_SUITE.erl19
-rw-r--r--erts/emulator/test/emulator_bench.spec1
-rw-r--r--erts/emulator/test/erts_debug_SUITE.erl31
-rw-r--r--erts/emulator/test/erts_test_utils.erl271
-rw-r--r--erts/emulator/test/esock_misc/socket_client.erl538
-rw-r--r--erts/emulator/test/esock_misc/socket_lib.erl133
-rw-r--r--erts/emulator/test/esock_misc/socket_server.erl954
-rw-r--r--erts/emulator/test/esock_ttest/.gitignore (renamed from lib/os_mon/mibs/v1/.gitignore)0
-rwxr-xr-xerts/emulator/test/esock_ttest/esock-ttest352
-rwxr-xr-xerts/emulator/test/esock_ttest/esock-ttest-client72
-rwxr-xr-xerts/emulator/test/esock_ttest/esock-ttest-server-gen32
-rwxr-xr-xerts/emulator/test/esock_ttest/esock-ttest-server-sock32
-rw-r--r--erts/emulator/test/exception_SUITE.erl5
-rw-r--r--erts/emulator/test/fun_SUITE.erl26
-rw-r--r--erts/emulator/test/net_SUITE.erl481
-rw-r--r--erts/emulator/test/nif_SUITE.erl165
-rw-r--r--erts/emulator/test/nif_SUITE_data/nif_SUITE.c115
-rw-r--r--erts/emulator/test/node_container_SUITE.erl259
-rw-r--r--erts/emulator/test/num_bif_SUITE.erl14
-rw-r--r--erts/emulator/test/persistent_term_SUITE.erl85
-rw-r--r--erts/emulator/test/process_SUITE.erl22
-rw-r--r--erts/emulator/test/smoke_test_SUITE.erl7
-rw-r--r--erts/emulator/test/socket_SUITE.erl17990
-rw-r--r--erts/emulator/test/socket_test_evaluator.erl524
-rw-r--r--erts/emulator/test/socket_test_evaluator.hrl68
-rw-r--r--erts/emulator/test/socket_test_lib.erl98
-rw-r--r--erts/emulator/test/socket_test_logger.erl118
-rw-r--r--erts/emulator/test/socket_test_ttest.hrl (renamed from lib/otp_mibs/src/otp_mibs.appup.src)22
-rw-r--r--erts/emulator/test/socket_test_ttest_client.hrl141
-rw-r--r--erts/emulator/test/socket_test_ttest_lib.erl127
-rw-r--r--erts/emulator/test/socket_test_ttest_tcp_client.erl678
-rw-r--r--erts/emulator/test/socket_test_ttest_tcp_client_gen.erl49
-rw-r--r--erts/emulator/test/socket_test_ttest_tcp_client_socket.erl51
-rw-r--r--erts/emulator/test/socket_test_ttest_tcp_gen.erl138
-rw-r--r--erts/emulator/test/socket_test_ttest_tcp_server.erl642
-rw-r--r--erts/emulator/test/socket_test_ttest_tcp_server_gen.erl (renamed from lib/kernel/src/net.erl)35
-rw-r--r--erts/emulator/test/socket_test_ttest_tcp_server_socket.erl43
-rw-r--r--erts/emulator/test/socket_test_ttest_tcp_socket.erl414
-rw-r--r--erts/emulator/test/system_info_SUITE.erl97
-rw-r--r--erts/emulator/test/timer_bif_SUITE.erl4
-rwxr-xr-xerts/emulator/utils/beam_makeops57
-rw-r--r--erts/emulator/zlib/adler32.c24
-rw-r--r--erts/emulator/zlib/compress.c45
-rw-r--r--erts/emulator/zlib/crc32.c45
-rw-r--r--erts/emulator/zlib/deflate.c805
-rw-r--r--erts/emulator/zlib/deflate.h35
-rw-r--r--erts/emulator/zlib/gzguts.h23
-rw-r--r--erts/emulator/zlib/inffast.c88
-rw-r--r--erts/emulator/zlib/inflate.c126
-rw-r--r--erts/emulator/zlib/inflate.h11
-rw-r--r--erts/emulator/zlib/inftrees.c29
-rw-r--r--erts/emulator/zlib/trees.c102
-rw-r--r--erts/emulator/zlib/uncompr.c101
-rw-r--r--erts/emulator/zlib/zconf.h41
-rw-r--r--erts/emulator/zlib/zlib.h452
-rw-r--r--erts/emulator/zlib/zlib.mk5
-rw-r--r--erts/emulator/zlib/zutil.c52
-rw-r--r--erts/emulator/zlib/zutil.h52
-rw-r--r--erts/epmd/src/epmd.c10
-rw-r--r--erts/etc/common/erlexec.c2
-rw-r--r--erts/etc/common/heart.c38
-rw-r--r--erts/etc/unix/Makefile3
-rw-r--r--erts/etc/unix/cerl.src29
-rw-r--r--erts/etc/unix/etp-commands.in122
-rw-r--r--erts/etc/unix/etp-rr-run-until-beam.py45
-rw-r--r--erts/etc/unix/to_erl.c1
-rw-r--r--erts/etc/win32/msys_tools/vc/ld.sh5
-rw-r--r--erts/include/internal/gcc/ethr_dw_atomic.h2
-rw-r--r--erts/include/internal/gcc/ethr_membar.h2
-rw-r--r--erts/include/internal/i386/ethr_dw_atomic.h2
-rw-r--r--erts/include/internal/win/ethr_dw_atomic.h2
-rw-r--r--erts/lib_src/common/erl_printf.c5
-rw-r--r--erts/preloaded/ebin/atomics.beambin3304 -> 3292 bytes
-rw-r--r--erts/preloaded/ebin/counters.beambin3148 -> 3092 bytes
-rw-r--r--erts/preloaded/ebin/erl_init.beambin0 -> 1820 bytes
-rw-r--r--erts/preloaded/ebin/erl_prim_loader.beambin54480 -> 52636 bytes
-rw-r--r--erts/preloaded/ebin/erl_tracer.beambin2208 -> 2200 bytes
-rw-r--r--erts/preloaded/ebin/erlang.beambin103388 -> 99948 bytes
-rw-r--r--erts/preloaded/ebin/erts_code_purger.beambin11396 -> 11204 bytes
-rw-r--r--erts/preloaded/ebin/erts_dirty_process_signal_handler.beambin2780 -> 2764 bytes
-rw-r--r--erts/preloaded/ebin/erts_internal.beambin17884 -> 17788 bytes
-rw-r--r--erts/preloaded/ebin/erts_literal_area_collector.beambin3308 -> 3268 bytes
-rw-r--r--erts/preloaded/ebin/init.beambin51528 -> 50304 bytes
-rw-r--r--erts/preloaded/ebin/net.beambin0 -> 5940 bytes
-rw-r--r--erts/preloaded/ebin/otp_ring0.beambin1444 -> 1424 bytes
-rw-r--r--erts/preloaded/ebin/persistent_term.beambin1696 -> 1836 bytes
-rw-r--r--erts/preloaded/ebin/prim_buffer.beambin3612 -> 3596 bytes
-rw-r--r--erts/preloaded/ebin/prim_eval.beambin1496 -> 1496 bytes
-rw-r--r--erts/preloaded/ebin/prim_file.beambin28380 -> 28084 bytes
-rw-r--r--erts/preloaded/ebin/prim_inet.beambin82228 -> 81012 bytes
-rw-r--r--erts/preloaded/ebin/prim_zip.beambin22916 -> 22644 bytes
-rw-r--r--erts/preloaded/ebin/socket.beambin0 -> 70312 bytes
-rw-r--r--erts/preloaded/ebin/zlib.beambin19788 -> 19716 bytes
-rw-r--r--erts/preloaded/src/Makefile33
-rw-r--r--erts/preloaded/src/erl_init.erl (renamed from erts/preloaded/src/otp_ring0.erl)23
-rw-r--r--erts/preloaded/src/erl_prim_loader.erl7
-rw-r--r--erts/preloaded/src/erlang.erl306
-rw-r--r--erts/preloaded/src/erts.app.src8
-rw-r--r--erts/preloaded/src/init.erl13
-rw-r--r--erts/preloaded/src/net.erl336
-rw-r--r--erts/preloaded/src/persistent_term.erl9
-rw-r--r--erts/preloaded/src/prim_eval.S4
-rw-r--r--erts/preloaded/src/prim_file.erl7
-rw-r--r--erts/preloaded/src/prim_inet.erl23
-rw-r--r--erts/preloaded/src/socket.erl3680
-rw-r--r--erts/test/upgrade_SUITE.erl2
-rw-r--r--erts/vsn.mk2
-rw-r--r--lib/Makefile2
-rw-r--r--lib/asn1/doc/src/asn1ct.xml14
-rw-r--r--lib/asn1/src/asn1ct_check.erl2
-rw-r--r--lib/asn1/test/asn1_SUITE_data/IN-CS-1-Datatypes.asn2
-rw-r--r--lib/common_test/doc/src/basics_chapter.xml2
-rw-r--r--lib/common_test/doc/src/common_test_app.xml26
-rw-r--r--lib/common_test/doc/src/ct.xml142
-rw-r--r--lib/common_test/doc/src/ct_cover.xml8
-rw-r--r--lib/common_test/doc/src/ct_ftp.xml26
-rw-r--r--lib/common_test/doc/src/ct_hooks.xml38
-rw-r--r--lib/common_test/doc/src/ct_master.xml24
-rw-r--r--lib/common_test/doc/src/ct_netconfc.xml110
-rw-r--r--lib/common_test/doc/src/ct_property_test.xml6
-rw-r--r--lib/common_test/doc/src/ct_rpc.xml18
-rw-r--r--lib/common_test/doc/src/ct_slave.xml12
-rw-r--r--lib/common_test/doc/src/ct_snmp.xml38
-rw-r--r--lib/common_test/doc/src/ct_ssh.xml152
-rw-r--r--lib/common_test/doc/src/ct_telnet.xml63
-rw-r--r--lib/common_test/doc/src/ct_testspec.xml4
-rw-r--r--lib/common_test/doc/src/notes.xml53
-rw-r--r--lib/common_test/doc/src/unix_telnet.xml6
-rw-r--r--lib/common_test/src/ct.erl6
-rw-r--r--lib/common_test/src/ct_config.erl8
-rw-r--r--lib/common_test/src/ct_cover.erl5
-rw-r--r--lib/common_test/src/ct_framework.erl34
-rw-r--r--lib/common_test/src/ct_logs.erl10
-rw-r--r--lib/common_test/src/ct_master.erl4
-rw-r--r--lib/common_test/src/ct_netconfc.erl10
-rw-r--r--lib/common_test/src/ct_release_test.erl20
-rw-r--r--lib/common_test/src/ct_repeat.erl2
-rw-r--r--lib/common_test/src/ct_run.erl8
-rw-r--r--lib/common_test/src/ct_telnet.erl146
-rw-r--r--lib/common_test/src/ct_telnet_client.erl6
-rw-r--r--lib/common_test/src/ct_util.erl5
-rw-r--r--lib/common_test/src/ct_util.hrl1
-rw-r--r--lib/common_test/src/cth_log_redirect.erl27
-rw-r--r--lib/common_test/src/cth_surefire.erl2
-rw-r--r--lib/common_test/src/test_server.erl47
-rw-r--r--lib/common_test/src/test_server_ctrl.erl46
-rw-r--r--lib/common_test/test/ct_auto_compile_SUITE.erl4
-rw-r--r--lib/common_test/test/ct_config_SUITE.erl12
-rw-r--r--lib/common_test/test/ct_hooks_SUITE.erl106
-rw-r--r--lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_cth_update_result_post_end_tc_SUITE.erl101
-rw-r--r--lib/common_test/test/ct_hooks_SUITE_data/cth/tests/update_result_post_end_tc_cth.erl98
-rw-r--r--lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl10
-rw-r--r--lib/common_test/test/ct_telnet_SUITE.erl39
-rw-r--r--lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_faulty_regexp_SUITE.erl79
-rw-r--r--lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl11
-rw-r--r--lib/common_test/test/test_server_SUITE.erl4
-rw-r--r--lib/common_test/test/test_server_test_lib.erl2
-rw-r--r--lib/common_test/test_server/configure.in6
-rw-r--r--lib/common_test/test_server/ts_benchmark.erl2
-rw-r--r--lib/common_test/test_server/ts_erl_config.erl32
-rw-r--r--lib/common_test/test_server/ts_install.erl26
-rw-r--r--lib/compiler/doc/src/compile.xml46
-rw-r--r--lib/compiler/scripts/.gitignore1
-rwxr-xr-xlib/compiler/scripts/smoke122
-rw-r--r--lib/compiler/scripts/smoke-mix.exs95
-rw-r--r--lib/compiler/src/Makefile39
-rw-r--r--lib/compiler/src/beam_a.erl41
-rw-r--r--lib/compiler/src/beam_asm.erl4
-rw-r--r--lib/compiler/src/beam_block.erl636
-rw-r--r--lib/compiler/src/beam_bs.erl280
-rw-r--r--lib/compiler/src/beam_bsm.erl708
-rw-r--r--lib/compiler/src/beam_clean.erl178
-rw-r--r--lib/compiler/src/beam_dead.erl971
-rw-r--r--lib/compiler/src/beam_dict.erl15
-rw-r--r--lib/compiler/src/beam_disasm.erl27
-rw-r--r--lib/compiler/src/beam_except.erl168
-rw-r--r--lib/compiler/src/beam_flatten.erl73
-rw-r--r--lib/compiler/src/beam_jump.erl423
-rw-r--r--lib/compiler/src/beam_kernel_to_ssa.erl1312
-rw-r--r--lib/compiler/src/beam_listing.erl9
-rw-r--r--lib/compiler/src/beam_peep.erl83
-rw-r--r--lib/compiler/src/beam_receive.erl416
-rw-r--r--lib/compiler/src/beam_record.erl131
-rw-r--r--lib/compiler/src/beam_reorder.erl150
-rw-r--r--lib/compiler/src/beam_split.erl94
-rw-r--r--lib/compiler/src/beam_ssa.erl890
-rw-r--r--lib/compiler/src/beam_ssa.hrl66
-rw-r--r--lib/compiler/src/beam_ssa_bsm.erl1046
-rw-r--r--lib/compiler/src/beam_ssa_codegen.erl2077
-rw-r--r--lib/compiler/src/beam_ssa_dead.erl1076
-rw-r--r--lib/compiler/src/beam_ssa_funs.erl149
-rw-r--r--lib/compiler/src/beam_ssa_lint.erl349
-rw-r--r--lib/compiler/src/beam_ssa_opt.erl2273
-rw-r--r--lib/compiler/src/beam_ssa_opt.hrl53
-rw-r--r--lib/compiler/src/beam_ssa_pp.erl238
-rw-r--r--lib/compiler/src/beam_ssa_pre_codegen.erl2831
-rw-r--r--lib/compiler/src/beam_ssa_recv.erl278
-rw-r--r--lib/compiler/src/beam_ssa_share.erl370
-rw-r--r--lib/compiler/src/beam_ssa_type.erl1937
-rw-r--r--lib/compiler/src/beam_trim.erl326
-rw-r--r--lib/compiler/src/beam_type.erl1117
-rw-r--r--lib/compiler/src/beam_utils.erl1184
-rw-r--r--lib/compiler/src/beam_validator.erl2787
-rw-r--r--lib/compiler/src/beam_z.erl31
-rw-r--r--lib/compiler/src/cerl_sets.erl2
-rw-r--r--lib/compiler/src/compile.erl215
-rw-r--r--lib/compiler/src/compiler.app.src23
-rw-r--r--lib/compiler/src/erl_bifs.erl19
-rwxr-xr-xlib/compiler/src/genop.tab26
-rw-r--r--lib/compiler/src/sys_core_bsm.erl312
-rw-r--r--lib/compiler/src/sys_core_dsetel.erl360
-rw-r--r--lib/compiler/src/sys_core_fold.erl191
-rw-r--r--lib/compiler/src/sys_core_fold_lists.erl101
-rw-r--r--lib/compiler/src/sys_core_inline.erl3
-rw-r--r--lib/compiler/src/v3_codegen.erl2791
-rw-r--r--lib/compiler/src/v3_core.erl30
-rw-r--r--lib/compiler/src/v3_kernel.erl126
-rw-r--r--lib/compiler/src/v3_kernel.hrl2
-rw-r--r--lib/compiler/test/Makefile45
-rw-r--r--lib/compiler/test/apply_SUITE.erl8
-rw-r--r--lib/compiler/test/beam_except_SUITE.erl36
-rw-r--r--lib/compiler/test/beam_jump_SUITE.erl125
-rw-r--r--lib/compiler/test/beam_ssa_SUITE.erl497
-rw-r--r--lib/compiler/test/beam_type_SUITE.erl168
-rw-r--r--lib/compiler/test/beam_utils_SUITE.erl6
-rw-r--r--lib/compiler/test/beam_validator_SUITE.erl112
-rw-r--r--lib/compiler/test/beam_validator_SUITE_data/bad_bin_match.S2
-rw-r--r--lib/compiler/test/beam_validator_SUITE_data/receive_stacked.S6
-rw-r--r--lib/compiler/test/bif_SUITE.erl7
-rw-r--r--lib/compiler/test/bs_construct_SUITE.erl5
-rw-r--r--lib/compiler/test/bs_match_SUITE.erl219
-rw-r--r--lib/compiler/test/compilation_SUITE.erl6
-rw-r--r--lib/compiler/test/compile_SUITE.erl289
-rw-r--r--lib/compiler/test/compiler.cover5
-rw-r--r--lib/compiler/test/core_fold_SUITE.erl32
-rw-r--r--lib/compiler/test/float_SUITE.erl15
-rw-r--r--lib/compiler/test/fun_SUITE.erl7
-rw-r--r--lib/compiler/test/guard_SUITE.erl107
-rw-r--r--lib/compiler/test/guard_SUITE_data/guard_SUITE_tuple_size.S30
-rw-r--r--lib/compiler/test/inline_SUITE.erl40
-rw-r--r--lib/compiler/test/inline_SUITE_data/barnes2.erl2
-rw-r--r--lib/compiler/test/map_SUITE.erl95
-rw-r--r--lib/compiler/test/match_SUITE.erl161
-rw-r--r--lib/compiler/test/misc_SUITE.erl140
-rw-r--r--lib/compiler/test/receive_SUITE.erl53
-rw-r--r--lib/compiler/test/receive_SUITE_data/ref_opt/no_5.erl38
-rw-r--r--lib/compiler/test/receive_SUITE_data/ref_opt/yes_14.S71
-rw-r--r--lib/compiler/test/receive_SUITE_data/ref_opt/yes_14.erl27
-rw-r--r--lib/compiler/test/regressions_SUITE.erl2
-rw-r--r--lib/compiler/test/test_lib.erl65
-rw-r--r--lib/compiler/test/warnings_SUITE.erl84
-rw-r--r--lib/configure.in.src62
-rw-r--r--lib/crypto/c_src/Makefile.in110
-rw-r--r--lib/crypto/c_src/aead.c243
-rw-r--r--lib/crypto/c_src/aead.h29
-rw-r--r--lib/crypto/c_src/aes.c451
-rw-r--r--lib/crypto/c_src/aes.h41
-rw-r--r--lib/crypto/c_src/algorithms.c273
-rw-r--r--lib/crypto/c_src/algorithms.h30
-rw-r--r--lib/crypto/c_src/api_ng.c223
-rw-r--r--lib/crypto/c_src/api_ng.h29
-rw-r--r--lib/crypto/c_src/atoms.c212
-rw-r--r--lib/crypto/c_src/atoms.h114
-rw-r--r--lib/crypto/c_src/block.c149
-rw-r--r--lib/crypto/c_src/block.h28
-rw-r--r--lib/crypto/c_src/bn.c186
-rw-r--r--lib/crypto/c_src/bn.h36
-rw-r--r--lib/crypto/c_src/chacha20.c124
-rw-r--r--lib/crypto/c_src/chacha20.h29
-rw-r--r--lib/crypto/c_src/check_erlang.cocci196
-rw-r--r--lib/crypto/c_src/check_openssl.cocci281
-rw-r--r--lib/crypto/c_src/cipher.c229
-rw-r--r--lib/crypto/c_src/cipher.h73
-rw-r--r--lib/crypto/c_src/cmac.c88
-rw-r--r--lib/crypto/c_src/cmac.h28
-rw-r--r--lib/crypto/c_src/common.h38
-rw-r--r--lib/crypto/c_src/crypto.c6118
-rw-r--r--lib/crypto/c_src/crypto_callback.c46
-rw-r--r--lib/crypto/c_src/dh.c294
-rw-r--r--lib/crypto/c_src/dh.h29
-rw-r--r--lib/crypto/c_src/digest.c125
-rw-r--r--lib/crypto/c_src/digest.h40
-rw-r--r--lib/crypto/c_src/dss.c144
-rw-r--r--lib/crypto/c_src/dss.h29
-rw-r--r--lib/crypto/c_src/ec.c414
-rw-r--r--lib/crypto/c_src/ec.h35
-rw-r--r--lib/crypto/c_src/ecdh.c94
-rw-r--r--lib/crypto/c_src/ecdh.h28
-rw-r--r--lib/crypto/c_src/eddsa.c63
-rw-r--r--lib/crypto/c_src/eddsa.h30
-rw-r--r--lib/crypto/c_src/engine.c842
-rw-r--r--lib/crypto/c_src/engine.h49
-rw-r--r--lib/crypto/c_src/evp.c164
-rw-r--r--lib/crypto/c_src/evp.h29
-rw-r--r--lib/crypto/c_src/evp_compat.h210
-rw-r--r--lib/crypto/c_src/fips.c52
-rw-r--r--lib/crypto/c_src/fips.h29
-rw-r--r--lib/crypto/c_src/hash.c499
-rw-r--r--lib/crypto/c_src/hash.h33
-rw-r--r--lib/crypto/c_src/hmac.c270
-rw-r--r--lib/crypto/c_src/hmac.h33
-rw-r--r--lib/crypto/c_src/info.c107
-rw-r--r--lib/crypto/c_src/info.h35
-rw-r--r--lib/crypto/c_src/math.c53
-rw-r--r--lib/crypto/c_src/math.h28
-rw-r--r--lib/crypto/c_src/openssl_config.h354
-rw-r--r--lib/crypto/c_src/otp_test_engine.c186
-rw-r--r--lib/crypto/c_src/pkey.c1456
-rw-r--r--lib/crypto/c_src/pkey.h31
-rw-r--r--lib/crypto/c_src/poly1305.c90
-rw-r--r--lib/crypto/c_src/poly1305.h28
-rw-r--r--lib/crypto/c_src/rand.c149
-rw-r--r--lib/crypto/c_src/rand.h31
-rw-r--r--lib/crypto/c_src/rc4.c92
-rw-r--r--lib/crypto/c_src/rc4.h29
-rw-r--r--lib/crypto/c_src/rsa.c282
-rw-r--r--lib/crypto/c_src/rsa.h31
-rw-r--r--lib/crypto/c_src/srp.c307
-rw-r--r--lib/crypto/c_src/srp.h30
-rw-r--r--lib/crypto/configure.in775
-rw-r--r--lib/crypto/doc/src/crypto.xml283
-rw-r--r--lib/crypto/doc/src/engine_keys.xml4
-rw-r--r--lib/crypto/src/crypto.erl561
-rw-r--r--lib/crypto/test/Makefile4
-rw-r--r--lib/crypto/test/blowfish_SUITE.erl300
-rw-r--r--lib/crypto/test/crypto.spec5
-rw-r--r--lib/crypto/test/crypto_SUITE.erl172
-rw-r--r--lib/crypto/test/crypto_bench.spec3
-rw-r--r--lib/crypto/test/crypto_bench_SUITE.erl400
-rw-r--r--lib/crypto/test/engine_SUITE.erl4
-rw-r--r--lib/debugger/doc/src/debugger.xml12
-rw-r--r--lib/debugger/doc/src/i.xml64
-rw-r--r--lib/debugger/doc/src/int.xml64
-rw-r--r--lib/debugger/src/dbg_icmd.erl2
-rw-r--r--lib/debugger/test/int_eval_SUITE.erl5
-rw-r--r--lib/dialyzer/doc/src/dialyzer.xml12
-rw-r--r--lib/dialyzer/src/dialyzer_codeserver.erl8
-rw-r--r--lib/dialyzer/src/dialyzer_contracts.erl130
-rw-r--r--lib/dialyzer/src/dialyzer_utils.erl39
-rw-r--r--lib/dialyzer/src/typer.erl2
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/results/para1
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/para/para4.erl11
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/para/para4_adt.erl12
-rw-r--r--lib/dialyzer/test/options1_SUITE_data/src/compiler/beam_validator.erl2
-rw-r--r--lib/dialyzer/test/r9c_SUITE_data/src/asn1/asn1ct_gen_ber.erl4
-rw-r--r--lib/dialyzer/test/r9c_SUITE_data/src/asn1/asn1ct_gen_ber_bin_v2.erl4
-rw-r--r--lib/dialyzer/test/r9c_SUITE_data/src/mnesia/mnesia_lib.erl2
-rw-r--r--lib/dialyzer/test/r9c_SUITE_data/src/mnesia/mnesia_tm.erl2
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/maps_sum2
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/spec_other_module2
-rw-r--r--lib/dialyzer/test/small_SUITE_data/src/lists_key_bug.erl75
-rw-r--r--lib/dialyzer/test/small_SUITE_data/src/spec_other_module.erl7
-rw-r--r--lib/diameter/doc/src/diameter.xml28
-rw-r--r--lib/diameter/doc/src/diameter_app.xml18
-rw-r--r--lib/diameter/doc/src/diameter_codec.xml6
-rw-r--r--lib/diameter/doc/src/diameter_make.xml10
-rw-r--r--lib/diameter/doc/src/diameter_sctp.xml4
-rw-r--r--lib/diameter/doc/src/diameter_tcp.xml4
-rw-r--r--lib/diameter/doc/src/diameter_transport.xml4
-rw-r--r--lib/diameter/doc/src/notes.xml18
-rw-r--r--lib/diameter/src/base/diameter_gen.erl2
-rw-r--r--lib/diameter/src/base/diameter_traffic.erl2
-rw-r--r--lib/diameter/src/diameter.appup.src4
-rw-r--r--lib/diameter/src/transport/diameter_tcp.erl8
-rw-r--r--lib/edoc/src/edoc.erl17
-rw-r--r--lib/eldap/README2
-rw-r--r--lib/eldap/doc/src/eldap.xml68
-rw-r--r--lib/eldap/test/make_certs.erl4
-rw-r--r--lib/erl_docgen/doc/src/doc-build.xml2
-rw-r--r--lib/erl_docgen/doc/src/docgen_xml_check.xml4
-rw-r--r--lib/erl_docgen/priv/css/otp_doc.css39
-rw-r--r--lib/erl_docgen/priv/dtd/cref.dtd2
-rw-r--r--lib/erl_docgen/priv/dtd/erlref.dtd2
-rw-r--r--lib/erl_docgen/priv/xsl/db_html.xsl185
-rw-r--r--lib/erl_docgen/src/docgen_edoc_xml_cb.erl2
-rw-r--r--lib/erl_interface/configure.in36
-rw-r--r--lib/erl_interface/doc/src/ei.xml194
-rw-r--r--lib/erl_interface/doc/src/ei_connect.xml417
-rw-r--r--lib/erl_interface/doc/src/ei_users_guide.xml30
-rw-r--r--lib/erl_interface/doc/src/erl_connect.xml59
-rw-r--r--lib/erl_interface/doc/src/erl_error.xml10
-rw-r--r--lib/erl_interface/doc/src/erl_eterm.xml67
-rw-r--r--lib/erl_interface/doc/src/erl_format.xml4
-rw-r--r--lib/erl_interface/doc/src/erl_global.xml17
-rw-r--r--lib/erl_interface/doc/src/erl_interface.xml12
-rw-r--r--lib/erl_interface/doc/src/erl_malloc.xml24
-rw-r--r--lib/erl_interface/doc/src/erl_marshal.xml26
-rw-r--r--lib/erl_interface/doc/src/ref_man.xml8
-rw-r--r--lib/erl_interface/doc/src/ref_man_ei.xml10
-rw-r--r--lib/erl_interface/doc/src/ref_man_erl_interface.xml8
-rw-r--r--lib/erl_interface/doc/src/registry.xml40
-rw-r--r--lib/erl_interface/include/ei.h61
-rw-r--r--lib/erl_interface/include/ei_config.h.in3
-rw-r--r--lib/erl_interface/include/erl_interface.h213
-rw-r--r--lib/erl_interface/src/INSTALL4
-rw-r--r--lib/erl_interface/src/Makefile.in13
-rw-r--r--lib/erl_interface/src/README.internal8
-rw-r--r--lib/erl_interface/src/connect/ei_connect.c1136
-rw-r--r--lib/erl_interface/src/connect/ei_resolve.c22
-rw-r--r--lib/erl_interface/src/connect/ei_resolve.h2
-rw-r--r--lib/erl_interface/src/connect/eirecv.c62
-rw-r--r--lib/erl_interface/src/connect/send.c74
-rw-r--r--lib/erl_interface/src/connect/send_exit.c25
-rw-r--r--lib/erl_interface/src/connect/send_reg.c64
-rw-r--r--lib/erl_interface/src/epmd/epmd_port.c85
-rw-r--r--lib/erl_interface/src/epmd/epmd_publish.c36
-rw-r--r--lib/erl_interface/src/epmd/epmd_unpublish.c33
-rw-r--r--lib/erl_interface/src/legacy/erl_connect.c9
-rw-r--r--lib/erl_interface/src/legacy/erl_eterm.c2
-rw-r--r--lib/erl_interface/src/misc/ei_init.c32
-rw-r--r--lib/erl_interface/src/misc/ei_internal.h20
-rw-r--r--lib/erl_interface/src/misc/ei_portio.c865
-rw-r--r--lib/erl_interface/src/misc/ei_portio.h95
-rw-r--r--lib/erl_interface/src/misc/ei_pthreads.c2
-rw-r--r--lib/erl_interface/src/misc/eidef.h1
-rw-r--r--lib/erl_interface/src/not_used/send_link.c3
-rw-r--r--lib/erl_interface/src/prog/erl_start.c2
-rw-r--r--lib/erl_interface/test/all_SUITE_data/Makefile.src4
-rw-r--r--lib/erl_interface/test/ei_accept_SUITE.erl11
-rw-r--r--lib/erl_interface/test/ei_accept_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/ei_accept_SUITE_data/ei_accept_test.c41
-rw-r--r--lib/erl_interface/test/ei_accept_SUITE_data/eiaccnode.c88
-rw-r--r--lib/erl_interface/test/ei_connect_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/ei_connect_SUITE_data/ei_connect_test.c2
-rw-r--r--lib/erl_interface/test/ei_decode_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c15
-rw-r--r--lib/erl_interface/test/ei_decode_encode_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/ei_decode_encode_SUITE_data/ei_decode_encode_test.c2
-rw-r--r--lib/erl_interface/test/ei_encode_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/ei_encode_SUITE_data/ei_encode_test.c14
-rw-r--r--lib/erl_interface/test/ei_format_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/ei_format_SUITE_data/ei_format_test.c8
-rw-r--r--lib/erl_interface/test/ei_print_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/ei_print_SUITE_data/ei_print_test.c8
-rw-r--r--lib/erl_interface/test/ei_tmo_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/ei_tmo_SUITE_data/ei_tmo_test.c8
-rw-r--r--lib/erl_interface/test/erl_connect_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/erl_eterm_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/erl_eterm_SUITE_data/cnode.c42
-rw-r--r--lib/erl_interface/test/erl_ext_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/erl_format_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/erl_global_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/erl_match_SUITE_data/Makefile.src2
-rw-r--r--lib/erl_interface/test/port_call_SUITE_data/Makefile.src2
-rw-r--r--lib/et/doc/src/et.xml14
-rw-r--r--lib/et/doc/src/et_collector.xml46
-rw-r--r--lib/et/doc/src/et_selector.xml8
-rw-r--r--lib/et/doc/src/et_viewer.xml14
-rw-r--r--lib/ftp/doc/src/ftp.xml84
-rw-r--r--lib/hipe/cerl/erl_bif_types.erl5
-rw-r--r--lib/hipe/cerl/erl_types.erl94
-rw-r--r--lib/hipe/doc/src/hipe_app.xml11
-rw-r--r--lib/hipe/icode/hipe_beam_to_icode.erl38
-rw-r--r--lib/hipe/icode/hipe_icode_range.erl2
-rw-r--r--lib/hipe/llvm/hipe_llvm_main.erl12
-rw-r--r--lib/hipe/main/hipe.erl153
-rw-r--r--lib/hipe/rtl/hipe_icode2rtl.erl2
-rw-r--r--lib/hipe/rtl/hipe_rtl_arith.inc4
-rw-r--r--lib/hipe/rtl/hipe_rtl_ssa_const_prop.erl6
-rw-r--r--lib/hipe/test/Makefile1
-rw-r--r--lib/hipe/test/basic_SUITE_data/basic_inline_function.erl73
-rw-r--r--lib/hipe/test/hipe_testsuite_driver.erl2
-rw-r--r--lib/inets/doc/src/http_uri.xml12
-rw-r--r--lib/inets/doc/src/httpc.xml50
-rw-r--r--lib/inets/doc/src/httpd.xml28
-rw-r--r--lib/inets/doc/src/httpd_custom_api.xml8
-rw-r--r--lib/inets/doc/src/httpd_socket.xml8
-rw-r--r--lib/inets/doc/src/httpd_util.xml55
-rw-r--r--lib/inets/doc/src/inets.xml20
-rw-r--r--lib/inets/doc/src/mod_alias.xml10
-rw-r--r--lib/inets/doc/src/mod_auth.xml58
-rw-r--r--lib/inets/doc/src/mod_esi.xml8
-rw-r--r--lib/inets/doc/src/mod_security.xml34
-rw-r--r--lib/inets/doc/src/notes.xml2
-rw-r--r--lib/inets/doc/src/notes_history.xml4
-rw-r--r--lib/inets/examples/httpd_load_test/hdlt_slave.erl11
-rw-r--r--lib/inets/src/http_client/httpc_cookie.erl2
-rw-r--r--lib/inets/src/http_client/httpc_manager.erl2
-rw-r--r--lib/inets/src/http_client/httpc_response.erl10
-rw-r--r--lib/inets/test/http_format_SUITE.erl2
-rw-r--r--lib/inets/test/httpc_proxy_SUITE.erl2
-rw-r--r--lib/inets/test/httpd_bench_SUITE.erl11
-rw-r--r--lib/jinterface/doc/src/notes.xml2
-rw-r--r--lib/jinterface/java_src/com/ericsson/otp/erlang/OtpEpmd.java2
-rw-r--r--lib/jinterface/java_src/com/ericsson/otp/erlang/OtpInputStream.java12
-rw-r--r--lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMbox.java4
-rw-r--r--lib/kernel/doc/src/application.xml109
-rw-r--r--lib/kernel/doc/src/auth.xml12
-rw-r--r--lib/kernel/doc/src/code.xml88
-rw-r--r--lib/kernel/doc/src/disk_log.xml74
-rw-r--r--lib/kernel/doc/src/erl_boot_server.xml12
-rw-r--r--lib/kernel/doc/src/erl_ddll.xml32
-rw-r--r--lib/kernel/doc/src/erl_epmd.xml16
-rw-r--r--lib/kernel/doc/src/error_handler.xml8
-rw-r--r--lib/kernel/doc/src/error_logger.xml46
-rw-r--r--lib/kernel/doc/src/file.xml122
-rw-r--r--lib/kernel/doc/src/gen_sctp.xml42
-rw-r--r--lib/kernel/doc/src/gen_tcp.xml24
-rw-r--r--lib/kernel/doc/src/gen_udp.xml16
-rw-r--r--lib/kernel/doc/src/global.xml42
-rw-r--r--lib/kernel/doc/src/global_group.xml22
-rw-r--r--lib/kernel/doc/src/heart.xml18
-rw-r--r--lib/kernel/doc/src/inet.xml101
-rw-r--r--lib/kernel/doc/src/inet_res.xml38
-rw-r--r--lib/kernel/doc/src/logger.xml260
-rw-r--r--lib/kernel/doc/src/logger_chapter.xml69
-rw-r--r--lib/kernel/doc/src/logger_disk_log_h.xml4
-rw-r--r--lib/kernel/doc/src/logger_filters.xml10
-rw-r--r--lib/kernel/doc/src/logger_formatter.xml6
-rw-r--r--lib/kernel/doc/src/logger_std_h.xml4
-rw-r--r--lib/kernel/doc/src/net_adm.xml22
-rw-r--r--lib/kernel/doc/src/net_kernel.xml28
-rw-r--r--lib/kernel/doc/src/os.xml38
-rw-r--r--lib/kernel/doc/src/pg2.xml22
-rw-r--r--lib/kernel/doc/src/rpc.xml56
-rw-r--r--lib/kernel/doc/src/seq_trace.xml20
-rw-r--r--lib/kernel/doc/src/wrap_log_reader.xml12
-rw-r--r--lib/kernel/include/dist.hrl3
-rw-r--r--lib/kernel/src/Makefile7
-rw-r--r--lib/kernel/src/application.erl22
-rw-r--r--lib/kernel/src/application_controller.erl104
-rw-r--r--lib/kernel/src/code_server.erl10
-rw-r--r--lib/kernel/src/dist_util.erl4
-rw-r--r--lib/kernel/src/erl_epmd.erl8
-rw-r--r--lib/kernel/src/erts_debug.erl58
-rw-r--r--lib/kernel/src/gen_tcp.erl6
-rw-r--r--lib/kernel/src/gen_udp.erl6
-rw-r--r--lib/kernel/src/inet_config.erl2
-rw-r--r--lib/kernel/src/inet_db.erl7
-rw-r--r--lib/kernel/src/inet_dns.erl2
-rw-r--r--lib/kernel/src/inet_tcp_dist.erl1
-rw-r--r--lib/kernel/src/kernel.app.src5
-rw-r--r--lib/kernel/src/logger.erl224
-rw-r--r--lib/kernel/src/logger_config.erl8
-rw-r--r--lib/kernel/src/logger_disk_log_h.erl21
-rw-r--r--lib/kernel/src/logger_h_common.erl670
-rw-r--r--lib/kernel/src/logger_h_common.hrl174
-rw-r--r--lib/kernel/src/logger_internal.hrl9
-rw-r--r--lib/kernel/src/logger_olp.erl626
-rw-r--r--lib/kernel/src/logger_olp.hrl180
-rw-r--r--lib/kernel/src/logger_proxy.erl165
-rw-r--r--lib/kernel/src/logger_server.erl34
-rw-r--r--lib/kernel/src/logger_simple_h.erl4
-rw-r--r--lib/kernel/src/logger_std_h.erl98
-rw-r--r--lib/kernel/src/logger_sup.erl4
-rw-r--r--lib/kernel/src/net_kernel.erl18
-rw-r--r--lib/kernel/src/seq_trace.erl2
-rw-r--r--lib/kernel/src/standard_error.erl3
-rw-r--r--lib/kernel/src/user.erl3
-rw-r--r--lib/kernel/src/user_drv.erl7
-rw-r--r--lib/kernel/test/Makefile9
-rw-r--r--lib/kernel/test/application_SUITE.erl97
-rw-r--r--lib/kernel/test/code_SUITE.erl12
-rw-r--r--lib/kernel/test/inet_SUITE.erl33
-rw-r--r--lib/kernel/test/inet_res_SUITE.erl2
-rw-r--r--lib/kernel/test/init_SUITE.erl2
-rw-r--r--lib/kernel/test/kernel_bench.spec1
-rw-r--r--lib/kernel/test/logger.cover5
-rw-r--r--lib/kernel/test/logger.spec2
-rw-r--r--lib/kernel/test/logger_SUITE.erl62
-rw-r--r--lib/kernel/test/logger_disk_log_h_SUITE.erl171
-rw-r--r--lib/kernel/test/logger_env_var_SUITE.erl16
-rw-r--r--lib/kernel/test/logger_formatter_SUITE.erl2
-rw-r--r--lib/kernel/test/logger_olp_SUITE.erl90
-rw-r--r--lib/kernel/test/logger_proxy_SUITE.erl274
-rw-r--r--lib/kernel/test/logger_std_h_SUITE.erl166
-rw-r--r--lib/kernel/test/logger_stress_SUITE.erl456
-rw-r--r--lib/kernel/test/logger_test_lib.erl10
-rw-r--r--lib/kernel/test/seq_trace_SUITE.erl7
-rw-r--r--lib/megaco/configure.in115
-rw-r--r--lib/megaco/doc/src/megaco.xml102
-rw-r--r--lib/megaco/doc/src/megaco_codec_meas.xml6
-rw-r--r--lib/megaco/doc/src/megaco_codec_mstone1.xml26
-rw-r--r--lib/megaco/doc/src/megaco_codec_mstone2.xml6
-rw-r--r--lib/megaco/doc/src/megaco_codec_transform.xml6
-rw-r--r--lib/megaco/doc/src/megaco_edist_compress.xml6
-rw-r--r--lib/megaco/doc/src/megaco_encoder.xml14
-rw-r--r--lib/megaco/doc/src/megaco_flex_scanner.xml12
-rw-r--r--lib/megaco/doc/src/megaco_tcp.xml30
-rw-r--r--lib/megaco/doc/src/megaco_transport.xml8
-rw-r--r--lib/megaco/doc/src/megaco_udp.xml30
-rw-r--r--lib/megaco/doc/src/megaco_user.xml48
-rw-r--r--lib/megaco/src/flex/Makefile.in40
-rw-r--r--lib/mnesia/doc/src/mnesia.xml243
-rw-r--r--lib/mnesia/doc/src/mnesia_frag_hash.xml12
-rw-r--r--lib/mnesia/doc/src/mnesia_registry.xml6
-rw-r--r--lib/mnesia/doc/src/notes.xml2
-rw-r--r--lib/mnesia/examples/bench/README6
-rw-r--r--lib/mnesia/src/mnesia.erl40
-rw-r--r--lib/mnesia/src/mnesia_dumper.erl37
-rw-r--r--lib/mnesia/src/mnesia_lib.erl2
-rw-r--r--lib/mnesia/src/mnesia_tm.erl2
-rw-r--r--lib/mnesia/test/README6
-rw-r--r--lib/observer/doc/src/crashdump.xml8
-rw-r--r--lib/observer/doc/src/etop.xml14
-rw-r--r--lib/observer/doc/src/observer.xml4
-rw-r--r--lib/observer/doc/src/ttb.xml47
-rw-r--r--lib/observer/src/cdv_bin_cb.erl4
-rw-r--r--lib/observer/src/cdv_detail_wx.erl3
-rw-r--r--lib/observer/src/cdv_html_wx.erl11
-rw-r--r--lib/observer/src/cdv_table_wx.erl3
-rw-r--r--lib/observer/src/cdv_term_cb.erl2
-rw-r--r--lib/observer/src/cdv_virtual_list_wx.erl3
-rw-r--r--lib/observer/src/cdv_wx.erl8
-rw-r--r--lib/observer/src/observer_alloc_wx.erl3
-rw-r--r--lib/observer/src/observer_app_wx.erl11
-rw-r--r--lib/observer/src/observer_perf_wx.erl15
-rw-r--r--lib/observer/src/observer_port_wx.erl14
-rw-r--r--lib/observer/src/observer_pro_wx.erl15
-rw-r--r--lib/observer/src/observer_procinfo.erl8
-rw-r--r--lib/observer/src/observer_trace_wx.erl17
-rw-r--r--lib/observer/src/observer_traceoptions_wx.erl9
-rw-r--r--lib/observer/src/observer_tv_table.erl3
-rw-r--r--lib/observer/src/observer_tv_wx.erl17
-rw-r--r--lib/observer/src/observer_wx.erl22
-rw-r--r--lib/observer/test/crashdump_helper.erl5
-rw-r--r--lib/observer/test/crashdump_viewer_SUITE.erl5
-rw-r--r--lib/observer/test/crashdump_viewer_SUITE_data/old_format.dump24
-rw-r--r--lib/observer/test/observer_SUITE.erl2
-rw-r--r--lib/odbc/c_src/Makefile.in2
-rw-r--r--lib/odbc/c_src/odbcserver.c5
-rw-r--r--lib/odbc/configure.in6
-rw-r--r--lib/odbc/doc/src/databases.xml2
-rw-r--r--lib/odbc/doc/src/error_handling.xml2
-rw-r--r--lib/odbc/doc/src/getting_started.xml2
-rw-r--r--lib/odbc/doc/src/notes_history.xml4
-rw-r--r--lib/odbc/doc/src/odbc.xml54
-rw-r--r--lib/odbc/test/odbc_connect_SUITE.erl2
-rw-r--r--lib/os_mon/Makefile6
-rw-r--r--lib/os_mon/doc/src/Makefile1
-rw-r--r--lib/os_mon/doc/src/cpu_sup.xml14
-rw-r--r--lib/os_mon/doc/src/disksup.xml12
-rw-r--r--lib/os_mon/doc/src/memsup.xml24
-rw-r--r--lib/os_mon/doc/src/nteventlog.xml8
-rw-r--r--lib/os_mon/doc/src/os_mon_app.xml27
-rw-r--r--lib/os_mon/doc/src/os_mon_mib.xml74
-rw-r--r--lib/os_mon/doc/src/os_sup.xml10
-rw-r--r--lib/os_mon/doc/src/ref_man.xml1
-rw-r--r--lib/os_mon/mibs/Makefile101
-rw-r--r--lib/os_mon/mibs/OTP-OS-MON-MIB.funcs5
-rw-r--r--lib/os_mon/mibs/OTP-OS-MON-MIB.mib423
-rw-r--r--lib/os_mon/src/Makefile3
-rw-r--r--lib/os_mon/src/memsup.erl1
-rw-r--r--lib/os_mon/src/os_mon.app.src6
-rw-r--r--lib/os_mon/src/os_mon_mib.erl251
-rw-r--r--lib/os_mon/test/Makefile2
-rw-r--r--lib/os_mon/test/os_mon.spec1
-rw-r--r--lib/os_mon/test/os_mon_mib_SUITE.cfg8
-rw-r--r--lib/os_mon/test/os_mon_mib_SUITE.erl578
-rw-r--r--lib/otp_mibs/AUTHORS8
-rw-r--r--lib/otp_mibs/doc/src/Makefile116
-rw-r--r--lib/otp_mibs/doc/src/introduction.xml47
-rw-r--r--lib/otp_mibs/doc/src/mibs.xml71
-rw-r--r--lib/otp_mibs/doc/src/notes.xml327
-rw-r--r--lib/otp_mibs/doc/src/otp_mib.xml73
-rw-r--r--lib/otp_mibs/doc/src/ref_man.xml38
-rw-r--r--lib/otp_mibs/ebin/.gitignore0
-rw-r--r--lib/otp_mibs/include/.gitignore0
-rw-r--r--lib/otp_mibs/info2
-rw-r--r--lib/otp_mibs/mibs/OTP-EVA-MIB.mib569
-rw-r--r--lib/otp_mibs/mibs/OTP-MIB.funcs2
-rw-r--r--lib/otp_mibs/mibs/OTP-MIB.mib318
-rw-r--r--lib/otp_mibs/mibs/v1/.gitignore0
-rw-r--r--lib/otp_mibs/priv/bin/.gitignore0
-rw-r--r--lib/otp_mibs/priv/mibs/.gitignore0
-rw-r--r--lib/otp_mibs/priv/obj/.gitignore0
-rw-r--r--lib/otp_mibs/src/Makefile106
-rw-r--r--lib/otp_mibs/src/otp_mib.erl219
-rw-r--r--lib/otp_mibs/src/otp_mibs.app.src30
-rw-r--r--lib/otp_mibs/test/Makefile85
-rw-r--r--lib/otp_mibs/test/otp_mibs_SUITE.cfg15
-rw-r--r--lib/otp_mibs/test/otp_mibs_SUITE.erl255
-rw-r--r--lib/otp_mibs/vsn.mk5
-rw-r--r--lib/parsetools/doc/src/leex.xml19
-rw-r--r--lib/parsetools/doc/src/yecc.xml6
-rw-r--r--lib/public_key/doc/src/public_key.xml114
-rw-r--r--lib/public_key/src/public_key.erl2
-rw-r--r--lib/reltool/doc/src/reltool.xml34
-rw-r--r--lib/reltool/doc/src/reltool_examples.xml13
-rw-r--r--lib/reltool/src/reltool.hrl3
-rw-r--r--lib/reltool/src/reltool_server.erl14
-rw-r--r--lib/reltool/src/reltool_target.erl27
-rw-r--r--lib/reltool/test/reltool_server_SUITE.erl183
-rw-r--r--lib/reltool/test/reltool_test_lib.erl3
-rw-r--r--lib/reltool/test/reltool_wx_SUITE.erl7
-rw-r--r--lib/runtime_tools/c_src/Makefile.in8
-rw-r--r--lib/runtime_tools/doc/src/dbg.xml118
-rw-r--r--lib/runtime_tools/doc/src/dyntrace.xml32
-rw-r--r--lib/runtime_tools/doc/src/erts_alloc_config.xml10
-rw-r--r--lib/runtime_tools/doc/src/msacc.xml32
-rw-r--r--lib/runtime_tools/doc/src/scheduler.xml12
-rw-r--r--lib/runtime_tools/doc/src/system_information.xml6
-rw-r--r--lib/runtime_tools/examples/dist.systemtap26
-rw-r--r--lib/runtime_tools/examples/driver1.systemtap44
-rw-r--r--lib/runtime_tools/examples/function-calls.systemtap22
-rw-r--r--lib/runtime_tools/examples/garbage-collection.systemtap16
-rw-r--r--lib/runtime_tools/examples/memory1.systemtap16
-rw-r--r--lib/runtime_tools/examples/messages.systemtap16
-rw-r--r--lib/runtime_tools/examples/port1.systemtap28
-rw-r--r--lib/runtime_tools/examples/process-scheduling.systemtap14
-rw-r--r--lib/runtime_tools/examples/spawn-exit.systemtap16
-rw-r--r--lib/runtime_tools/examples/user-probe-n.systemtap13
-rw-r--r--lib/runtime_tools/examples/user-probe.systemtap12
-rw-r--r--lib/runtime_tools/src/system_information.erl1
-rw-r--r--lib/runtime_tools/test/system_information_SUITE_data/information_test_report.dat2
-rw-r--r--lib/sasl/doc/src/alarm_handler.xml8
-rw-r--r--lib/sasl/doc/src/rb.xml38
-rw-r--r--lib/sasl/doc/src/release_handler.xml42
-rw-r--r--lib/sasl/doc/src/systools.xml16
-rw-r--r--lib/sasl/src/rb.erl2
-rw-r--r--lib/sasl/src/release_handler.erl4
-rw-r--r--lib/sasl/src/release_handler_1.erl2
-rw-r--r--lib/sasl/src/systools.erl6
-rw-r--r--lib/sasl/src/systools_make.erl8
-rw-r--r--lib/sasl/src/systools_relup.erl2
-rw-r--r--lib/sasl/test/installer.erl2
-rw-r--r--lib/sasl/test/rb_SUITE.erl2
-rw-r--r--lib/sasl/test/systools_SUITE.erl2
-rw-r--r--lib/sasl/test/test_lib.hrl4
-rw-r--r--lib/snmp/doc/src/snmp.xml82
-rw-r--r--lib/snmp/doc/src/snmp_community_mib.xml12
-rw-r--r--lib/snmp/doc/src/snmp_framework_mib.xml10
-rw-r--r--lib/snmp/doc/src/snmp_generic.xml30
-rw-r--r--lib/snmp/doc/src/snmp_index.xml18
-rw-r--r--lib/snmp/doc/src/snmp_notification_mib.xml10
-rw-r--r--lib/snmp/doc/src/snmp_pdus.xml26
-rw-r--r--lib/snmp/doc/src/snmp_standard_mib.xml14
-rw-r--r--lib/snmp/doc/src/snmp_target_mib.xml16
-rw-r--r--lib/snmp/doc/src/snmp_user_based_sm_mib.xml10
-rw-r--r--lib/snmp/doc/src/snmp_view_based_acm_mib.xml18
-rw-r--r--lib/snmp/doc/src/snmpa.xml252
-rw-r--r--lib/snmp/doc/src/snmpa_conf.xml112
-rw-r--r--lib/snmp/doc/src/snmpa_discovery_handler.xml4
-rw-r--r--lib/snmp/doc/src/snmpa_error.xml6
-rw-r--r--lib/snmp/doc/src/snmpa_error_io.xml6
-rw-r--r--lib/snmp/doc/src/snmpa_error_logger.xml6
-rw-r--r--lib/snmp/doc/src/snmpa_error_report.xml6
-rw-r--r--lib/snmp/doc/src/snmpa_local_db.xml24
-rw-r--r--lib/snmp/doc/src/snmpa_mib_data.xml34
-rw-r--r--lib/snmp/doc/src/snmpa_mib_storage.xml26
-rw-r--r--lib/snmp/doc/src/snmpa_mpd.xml20
-rw-r--r--lib/snmp/doc/src/snmpa_network_interface.xml12
-rw-r--r--lib/snmp/doc/src/snmpa_network_interface_filter.xml10
-rw-r--r--lib/snmp/doc/src/snmpa_notification_delivery_info_receiver.xml6
-rw-r--r--lib/snmp/doc/src/snmpa_notification_filter.xml4
-rw-r--r--lib/snmp/doc/src/snmpa_supervisor.xml10
-rw-r--r--lib/snmp/doc/src/snmpc.xml10
-rw-r--r--lib/snmp/doc/src/snmpm.xml224
-rw-r--r--lib/snmp/doc/src/snmpm_conf.xml48
-rw-r--r--lib/snmp/doc/src/snmpm_mpd.xml10
-rw-r--r--lib/snmp/doc/src/snmpm_network_interface.xml20
-rw-r--r--lib/snmp/doc/src/snmpm_network_interface_filter.xml10
-rw-r--r--lib/snmp/doc/src/snmpm_user.xml16
-rw-r--r--lib/snmp/mibs/Makefile.in36
-rw-r--r--lib/snmp/mibs/OTP-REG.mib (renamed from lib/otp_mibs/mibs/OTP-REG.mib)0
-rw-r--r--lib/snmp/mibs/OTP-TC.mib (renamed from lib/otp_mibs/mibs/OTP-TC.mib)0
-rw-r--r--lib/snmp/src/agent/snmpa_set_lib.erl6
-rw-r--r--lib/snmp/src/agent/snmpa_trap.erl4
-rw-r--r--lib/snmp/test/snmp_compiler_test.erl6
-rw-r--r--lib/snmp/test/test-mibs/ALARM-MIB.mib2
-rw-r--r--lib/snmp/test/test-mibs/SNMPv2-TC.mib4
-rw-r--r--lib/snmp/vsn.mk2
-rw-r--r--lib/ssh/doc/src/notes.xml17
-rw-r--r--lib/ssh/doc/src/ssh.xml50
-rw-r--r--lib/ssh/doc/src/ssh_client_channel.xml32
-rw-r--r--lib/ssh/doc/src/ssh_client_key_api.xml8
-rw-r--r--lib/ssh/doc/src/ssh_connection.xml36
-rw-r--r--lib/ssh/doc/src/ssh_file.xml12
-rw-r--r--lib/ssh/doc/src/ssh_server_channel.xml10
-rw-r--r--lib/ssh/doc/src/ssh_server_key_api.xml6
-rw-r--r--lib/ssh/doc/src/ssh_sftp.xml108
-rw-r--r--lib/ssh/doc/src/ssh_sftpd.xml4
-rw-r--r--lib/ssh/src/ssh_sftpd.erl7
-rw-r--r--lib/ssh/test/ssh_bench_SUITE.erl48
-rw-r--r--lib/ssh/test/ssh_compat_SUITE.erl18
-rw-r--r--lib/ssh/test/ssh_connection_SUITE.erl4
-rw-r--r--lib/ssh/test/ssh_trpt_test_lib.erl99
-rw-r--r--lib/ssl/doc/specs/.gitignore1
-rw-r--r--lib/ssl/doc/src/Makefile8
-rw-r--r--lib/ssl/doc/src/notes.xml4
-rw-r--r--lib/ssl/doc/src/specs.xml9
-rw-r--r--lib/ssl/doc/src/ssl.xml1831
-rw-r--r--lib/ssl/doc/src/ssl_crl_cache.xml33
-rw-r--r--lib/ssl/doc/src/ssl_crl_cache_api.xml71
-rw-r--r--lib/ssl/doc/src/ssl_distribution.xml2
-rw-r--r--lib/ssl/doc/src/ssl_session_cache_api.xml109
-rw-r--r--lib/ssl/src/Makefile136
-rw-r--r--lib/ssl/src/dtls_connection.erl340
-rw-r--r--lib/ssl/src/dtls_handshake.erl67
-rw-r--r--lib/ssl/src/dtls_handshake.hrl20
-rw-r--r--lib/ssl/src/dtls_packet_demux.erl12
-rw-r--r--lib/ssl/src/dtls_record.erl89
-rw-r--r--lib/ssl/src/dtls_socket.erl49
-rw-r--r--lib/ssl/src/inet_tls_dist.erl48
-rw-r--r--lib/ssl/src/ssl.app.src6
-rw-r--r--lib/ssl/src/ssl.erl672
-rw-r--r--lib/ssl/src/ssl_alert.erl81
-rw-r--r--lib/ssl/src/ssl_alert.hrl5
-rw-r--r--lib/ssl/src/ssl_api.hrl49
-rw-r--r--lib/ssl/src/ssl_app.erl17
-rw-r--r--lib/ssl/src/ssl_certificate.erl2
-rw-r--r--lib/ssl/src/ssl_cipher.erl294
-rw-r--r--lib/ssl/src/ssl_cipher.hrl18
-rw-r--r--lib/ssl/src/ssl_cipher_format.erl124
-rw-r--r--lib/ssl/src/ssl_connection.erl1536
-rw-r--r--lib/ssl/src/ssl_connection.hrl160
-rw-r--r--lib/ssl/src/ssl_crl_cache.erl4
-rw-r--r--lib/ssl/src/ssl_crl_cache_api.erl15
-rw-r--r--lib/ssl/src/ssl_crl_hash_dir.erl5
-rw-r--r--lib/ssl/src/ssl_dh_groups.erl467
-rw-r--r--lib/ssl/src/ssl_handshake.erl1086
-rw-r--r--lib/ssl/src/ssl_handshake.hrl86
-rw-r--r--lib/ssl/src/ssl_internal.hrl56
-rw-r--r--lib/ssl/src/ssl_logger.erl433
-rw-r--r--lib/ssl/src/ssl_manager.erl54
-rw-r--r--lib/ssl/src/ssl_pkix_db.erl3
-rw-r--r--lib/ssl/src/ssl_record.erl95
-rw-r--r--lib/ssl/src/ssl_record.hrl7
-rw-r--r--lib/ssl/src/ssl_session.erl17
-rw-r--r--lib/ssl/src/ssl_session_cache_api.erl24
-rw-r--r--lib/ssl/src/tls_connection.erl617
-rw-r--r--lib/ssl/src/tls_connection.hrl1
-rw-r--r--lib/ssl/src/tls_connection_1_3.erl168
-rw-r--r--lib/ssl/src/tls_handshake.erl201
-rw-r--r--lib/ssl/src/tls_handshake.hrl1
-rw-r--r--lib/ssl/src/tls_handshake_1_3.erl1048
-rw-r--r--lib/ssl/src/tls_handshake_1_3.hrl238
-rw-r--r--lib/ssl/src/tls_record.erl457
-rw-r--r--lib/ssl/src/tls_record_1_3.erl298
-rw-r--r--lib/ssl/src/tls_record_1_3.hrl58
-rw-r--r--lib/ssl/src/tls_sender.erl296
-rw-r--r--lib/ssl/src/tls_socket.erl56
-rw-r--r--lib/ssl/src/tls_v1.erl441
-rw-r--r--lib/ssl/test/Makefile6
-rw-r--r--lib/ssl/test/make_certs.erl18
-rw-r--r--lib/ssl/test/property_test/ssl_eqc_handshake.erl812
-rw-r--r--lib/ssl/test/ssl.spec14
-rw-r--r--lib/ssl/test/ssl_ECC_SUITE.erl61
-rw-r--r--lib/ssl/test/ssl_alpn_handshake_SUITE.erl79
-rw-r--r--lib/ssl/test/ssl_basic_SUITE.erl1604
-rw-r--r--lib/ssl/test/ssl_bench.spec1
-rw-r--r--lib/ssl/test/ssl_bench_SUITE.erl65
-rw-r--r--lib/ssl/test/ssl_certificate_verify_SUITE.erl113
-rw-r--r--lib/ssl/test/ssl_crl_SUITE.erl30
-rw-r--r--lib/ssl/test/ssl_dist_bench_SUITE.erl162
-rw-r--r--lib/ssl/test/ssl_dist_test_lib.erl5
-rw-r--r--lib/ssl/test/ssl_eqc_SUITE.erl58
-rw-r--r--lib/ssl/test/ssl_handshake_SUITE.erl132
-rw-r--r--lib/ssl/test/ssl_npn_handshake_SUITE.erl62
-rw-r--r--lib/ssl/test/ssl_npn_hello_SUITE.erl42
-rw-r--r--lib/ssl/test/ssl_payload_SUITE.erl144
-rw-r--r--lib/ssl/test/ssl_pem_cache_SUITE.erl15
-rw-r--r--lib/ssl/test/ssl_rfc_5869_SUITE.erl316
-rw-r--r--lib/ssl/test/ssl_session_cache_SUITE.erl126
-rw-r--r--lib/ssl/test/ssl_sni_SUITE.erl8
-rw-r--r--lib/ssl/test/ssl_test_lib.erl308
-rw-r--r--lib/ssl/test/ssl_to_openssl_SUITE.erl49
-rw-r--r--lib/stdlib/doc/src/array.xml60
-rw-r--r--lib/stdlib/doc/src/base64.xml14
-rw-r--r--lib/stdlib/doc/src/beam_lib.xml34
-rw-r--r--lib/stdlib/doc/src/binary.xml56
-rw-r--r--lib/stdlib/doc/src/c.xml70
-rw-r--r--lib/stdlib/doc/src/calendar.xml64
-rw-r--r--lib/stdlib/doc/src/dets.xml90
-rw-r--r--lib/stdlib/doc/src/dict.xml44
-rw-r--r--lib/stdlib/doc/src/digraph.xml66
-rw-r--r--lib/stdlib/doc/src/digraph_utils.xml38
-rw-r--r--lib/stdlib/doc/src/epp.xml34
-rw-r--r--lib/stdlib/doc/src/erl_anno.xml36
-rw-r--r--lib/stdlib/doc/src/erl_eval.xml32
-rw-r--r--lib/stdlib/doc/src/erl_expand_records.xml4
-rw-r--r--lib/stdlib/doc/src/erl_id_trans.xml4
-rw-r--r--lib/stdlib/doc/src/erl_internal.xml22
-rw-r--r--lib/stdlib/doc/src/erl_lint.xml12
-rw-r--r--lib/stdlib/doc/src/erl_parse.xml32
-rw-r--r--lib/stdlib/doc/src/erl_pp.xml32
-rw-r--r--lib/stdlib/doc/src/erl_scan.xml30
-rw-r--r--lib/stdlib/doc/src/erl_tar.xml30
-rw-r--r--lib/stdlib/doc/src/ets.xml327
-rw-r--r--lib/stdlib/doc/src/file_sorter.xml30
-rw-r--r--lib/stdlib/doc/src/filelib.xml30
-rw-r--r--lib/stdlib/doc/src/filename.xml48
-rw-r--r--lib/stdlib/doc/src/gb_sets.xml72
-rw-r--r--lib/stdlib/doc/src/gb_trees.xml54
-rw-r--r--lib/stdlib/doc/src/gen_event.xml52
-rw-r--r--lib/stdlib/doc/src/gen_server.xml56
-rw-r--r--lib/stdlib/doc/src/gen_statem.xml371
-rw-r--r--lib/stdlib/doc/src/io.xml96
-rw-r--r--lib/stdlib/doc/src/io_lib.xml64
-rw-r--r--lib/stdlib/doc/src/lists.xml172
-rw-r--r--lib/stdlib/doc/src/log_mf_h.xml6
-rw-r--r--lib/stdlib/doc/src/maps.xml114
-rw-r--r--lib/stdlib/doc/src/math.xml52
-rw-r--r--lib/stdlib/doc/src/ms_transform.xml8
-rw-r--r--lib/stdlib/doc/src/notes.xml21
-rw-r--r--lib/stdlib/doc/src/orddict.xml44
-rw-r--r--lib/stdlib/doc/src/ordsets.xml38
-rw-r--r--lib/stdlib/doc/src/pool.xml18
-rw-r--r--lib/stdlib/doc/src/proc_lib.xml58
-rw-r--r--lib/stdlib/doc/src/proplists.xml45
-rw-r--r--lib/stdlib/doc/src/qlc.xml54
-rw-r--r--lib/stdlib/doc/src/queue.xml62
-rw-r--r--lib/stdlib/doc/src/rand.xml148
-rw-r--r--lib/stdlib/doc/src/random.xml18
-rw-r--r--lib/stdlib/doc/src/re.xml22
-rw-r--r--lib/stdlib/doc/src/sets.xml38
-rw-r--r--lib/stdlib/doc/src/shell.xml16
-rw-r--r--lib/stdlib/doc/src/slave.xml41
-rw-r--r--lib/stdlib/doc/src/sofs.xml168
-rw-r--r--lib/stdlib/doc/src/string.xml142
-rw-r--r--lib/stdlib/doc/src/supervisor.xml24
-rw-r--r--lib/stdlib/doc/src/supervisor_bridge.xml10
-rw-r--r--lib/stdlib/doc/src/sys.xml249
-rw-r--r--lib/stdlib/doc/src/timer.xml44
-rw-r--r--lib/stdlib/doc/src/unicode.xml32
-rw-r--r--lib/stdlib/doc/src/uri_string.xml18
-rw-r--r--lib/stdlib/doc/src/win32reg.xml28
-rw-r--r--lib/stdlib/doc/src/zip.xml44
-rw-r--r--lib/stdlib/include/assert.hrl6
-rw-r--r--lib/stdlib/src/calendar.erl94
-rw-r--r--lib/stdlib/src/erl_lint.erl6
-rw-r--r--lib/stdlib/src/erl_parse.yrl9
-rw-r--r--lib/stdlib/src/erl_posix_msg.erl4
-rw-r--r--lib/stdlib/src/erl_pp.erl4
-rw-r--r--lib/stdlib/src/gen_fsm.erl154
-rw-r--r--lib/stdlib/src/gen_server.erl53
-rw-r--r--lib/stdlib/src/gen_statem.erl2331
-rw-r--r--lib/stdlib/src/io_lib.erl18
-rw-r--r--lib/stdlib/src/io_lib_format.erl27
-rw-r--r--lib/stdlib/src/io_lib_pretty.erl20
-rw-r--r--lib/stdlib/src/maps.erl157
-rw-r--r--lib/stdlib/src/ms_transform.erl4
-rw-r--r--lib/stdlib/src/otp_internal.erl29
-rw-r--r--lib/stdlib/src/rand.erl563
-rw-r--r--lib/stdlib/src/slave.erl17
-rw-r--r--lib/stdlib/src/stdlib.app.src2
-rw-r--r--lib/stdlib/src/stdlib.appup.src8
-rw-r--r--lib/stdlib/src/sys.erl178
-rw-r--r--lib/stdlib/src/uri_string.erl23
-rw-r--r--lib/stdlib/test/Makefile8
-rw-r--r--lib/stdlib/test/binary_module_SUITE.erl20
-rw-r--r--lib/stdlib/test/calendar_SUITE.erl16
-rw-r--r--lib/stdlib/test/epp_SUITE.erl2
-rw-r--r--lib/stdlib/test/erl_eval_SUITE.erl12
-rw-r--r--lib/stdlib/test/erl_lint_SUITE.erl62
-rw-r--r--lib/stdlib/test/erl_pp_SUITE.erl11
-rw-r--r--lib/stdlib/test/ets_SUITE.erl1833
-rw-r--r--lib/stdlib/test/ets_SUITE_data/visualize_throughput.html253
-rw-r--r--lib/stdlib/test/gen_fsm_SUITE.erl12
-rw-r--r--lib/stdlib/test/gen_statem_SUITE.erl114
-rw-r--r--lib/stdlib/test/lists_SUITE.erl16
-rw-r--r--lib/stdlib/test/qlc_SUITE.erl10
-rw-r--r--lib/stdlib/test/rand_SUITE.erl436
-rw-r--r--lib/stdlib/test/rand_Xoroshiro928ss_dev.txt343
-rw-r--r--lib/stdlib/test/stdlib.spec2
-rw-r--r--lib/stdlib/test/stdlib_bench_SUITE.erl130
-rw-r--r--lib/stdlib/test/stdlib_bench_SUITE_data/generic_server.erl4
-rw-r--r--lib/stdlib/test/stdlib_bench_SUITE_data/generic_statem.erl10
-rw-r--r--lib/stdlib/test/unicode_util_SUITE_data/GraphemeBreakTest.txt1148
-rw-r--r--lib/stdlib/test/unicode_util_SUITE_data/LineBreakTest.txt246
-rw-r--r--lib/stdlib/test/unicode_util_SUITE_data/NormalizationTest.txt52
-rw-r--r--lib/stdlib/test/uri_string_SUITE.erl10
-rw-r--r--lib/stdlib/uc_spec/CaseFolding.txt85
-rw-r--r--lib/stdlib/uc_spec/CompositionExclusions.txt4
-rw-r--r--lib/stdlib/uc_spec/GraphemeBreakProperty.txt117
-rw-r--r--lib/stdlib/uc_spec/PropList.txt77
-rw-r--r--lib/stdlib/uc_spec/README-UPDATE.txt8
-rw-r--r--lib/stdlib/uc_spec/SpecialCasing.txt8
-rw-r--r--lib/stdlib/uc_spec/UnicodeData.txt778
-rw-r--r--lib/stdlib/uc_spec/emoji-data.txt714
-rwxr-xr-xlib/stdlib/uc_spec/gen_unicode_mod.escript149
-rw-r--r--lib/stdlib/vsn.mk2
-rw-r--r--lib/syntax_tools/src/erl_prettypr.erl5
-rw-r--r--lib/syntax_tools/src/igor.erl2
-rw-r--r--lib/syntax_tools/test/syntax_tools_SUITE.erl36
-rw-r--r--lib/syntax_tools/test/syntax_tools_SUITE_data/specs_and_funs.erl18
-rw-r--r--lib/tftp/doc/src/tftp.xml36
-rw-r--r--lib/tools/doc/src/cover.xml104
-rw-r--r--lib/tools/doc/src/cprof.xml42
-rw-r--r--lib/tools/doc/src/eprof.xml38
-rw-r--r--lib/tools/doc/src/fprof.xml50
-rw-r--r--lib/tools/doc/src/instrument.xml23
-rw-r--r--lib/tools/doc/src/lcnt.xml68
-rw-r--r--lib/tools/doc/src/make.xml10
-rw-r--r--lib/tools/doc/src/tags.xml16
-rw-r--r--lib/tools/doc/src/xref.xml64
-rw-r--r--lib/tools/priv/styles.css5
-rw-r--r--lib/tools/src/cover.erl494
-rw-r--r--lib/tools/src/tools.app.src2
-rw-r--r--lib/tools/test/cover_SUITE.erl40
-rw-r--r--lib/tools/test/instrument_SUITE.erl35
-rw-r--r--lib/wx/api_gen/wx_extra/added_func.h6
-rw-r--r--lib/wx/api_gen/wx_gen.erl9
-rw-r--r--lib/wx/api_gen/wx_gen_cpp.erl8
-rw-r--r--lib/wx/api_gen/wxapi.conf36
-rw-r--r--lib/wx/c_src/Makefile.in1
-rw-r--r--lib/wx/c_src/gen/wxe_derived_dest.h10
-rw-r--r--lib/wx/c_src/gen/wxe_events.cpp4
-rw-r--r--lib/wx/c_src/gen/wxe_funcs.cpp115
-rw-r--r--lib/wx/c_src/gen/wxe_init.cpp66
-rw-r--r--lib/wx/c_src/gen/wxe_macros.h20
-rw-r--r--lib/wx/c_src/wxe_impl.cpp16
-rw-r--r--lib/wx/c_src/wxe_ps_init.c14
-rw-r--r--lib/wx/doc/overview.edoc2
-rw-r--r--lib/wx/examples/demo/ex_graphicsContext.erl2
-rw-r--r--lib/wx/include/wx.hrl16
-rw-r--r--lib/wx/src/gen/wxDisplay.erl131
-rw-r--r--lib/wx/src/gen/wxGCDC.erl287
-rw-r--r--lib/wx/src/gen/wxe_debug.hrl18
-rw-r--r--lib/wx/src/gen/wxe_funcs.hrl18
-rw-r--r--lib/wx/test/wx_event_SUITE.erl4
-rw-r--r--lib/xmerl/doc/src/notes.xml16
-rw-r--r--lib/xmerl/doc/src/xmerl_sax_parser.xml10
-rw-r--r--lib/xmerl/src/xmerl_sax_parser_base.erlsrc2
-rw-r--r--lib/xmerl/test/xmerl_SUITE_data/eventp/CMOM.xml26
-rw-r--r--lib/xmerl/test/xmerl_SUITE_data/eventp/CelloMOM.xml26
-rw-r--r--lib/xmerl/test/xmerl_xsd_lib.erl7
-rw-r--r--make/configure.in (renamed from configure.in)83
-rw-r--r--make/lazy_configure.mk82
-rwxr-xr-xmake/make_emakefile.in2
-rw-r--r--make/otp_ded.mk.in25
-rw-r--r--make/otp_patch_solve_forward_merge_version2
-rw-r--r--make/otp_version_tickets2
-rw-r--r--make/otp_version_tickets_in_merge (renamed from lib/otp_mibs/doc/html/.gitignore)0
-rwxr-xr-xotp_build209
-rw-r--r--otp_versions.table9
-rwxr-xr-xscripts/diffable317
-rw-r--r--system/doc/Makefile1
-rw-r--r--system/doc/design_principles/applications.xml2
-rw-r--r--system/doc/design_principles/statem.xml438
-rw-r--r--system/doc/efficiency_guide/binaryhandling.xml8
-rw-r--r--system/doc/embedded/starting.xml2
-rw-r--r--system/doc/general_info/Makefile (renamed from lib/otp_mibs/mibs/Makefile)88
-rw-r--r--system/doc/general_info/book.xml (renamed from lib/otp_mibs/doc/src/book.xml)21
-rw-r--r--system/doc/general_info/deprecations.xml77
-rw-r--r--system/doc/general_info/part.xml (renamed from lib/otp_mibs/doc/src/part.xml)21
-rw-r--r--system/doc/general_info/scheduled_for_removal.xml61
-rw-r--r--system/doc/general_info/xmlfiles.mk (renamed from lib/otp_mibs/Makefile)23
-rw-r--r--system/doc/html/general_info/.gitignore (renamed from lib/otp_mibs/doc/man3/.gitignore)0
-rw-r--r--system/doc/oam/oam_intro.xml55
-rw-r--r--system/doc/reference_manual/expressions.xml1
-rw-r--r--system/doc/system_principles/misc.xml8
-rw-r--r--system/doc/top/Makefile5
-rw-r--r--system/doc/top/book.xml1
-rw-r--r--system/doc/top/templates/index.html.src2
-rw-r--r--system/doc/xml/general_info/.gitignore (renamed from lib/otp_mibs/doc/pdf/.gitignore)0
1443 files changed, 135938 insertions, 47793 deletions
diff --git a/.gitignore b/.gitignore
index b90bda1763..9497169cde 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,9 +34,12 @@ i686-pc-linux-gnu
x86_64-unknown-linux-gnu
i386-apple-darwin[0-9]*.[0-9]*.[0-9]*
x86_64-apple-darwin[0-9]*.[0-9]*.[0-9]*
+x86_64-unknown-freebsd[0-9]*.[0-9]*
sparc-sun-solaris[0-9]*.[0-9]*
i386-pc-solaris[0-9]*.[0-9]*
i386-unknown-freebsd[0-9]*.[0-9]*
+x86_64-unknown-freebsd[0-9]*.[0-9]*
+x86_64-unknown-openbsd[0-9]*.[0-9]*
tile-tilera-linux-gnu
powerpc-unknown-linux-gnu
aarch64-unknown-linux-gnu
@@ -172,9 +175,10 @@ JAVADOC-GENERATED
/lib/*/doc/pdf/*.pdf
/lib/*/doc/xml/*.xml
-/lib/configure
/lib/config.log
/lib/config.status
+/make/config.log
+/make/config.status
#
# Files generated by configure.
@@ -187,10 +191,12 @@ JAVADOC-GENERATED
# Files generated by "./otp_build autoconf"
#
+/lib/configure
/lib/*/configure
/lib/common_test/test_server/configure
/lib/configure.in
-/aclocal.m4
+/make/aclocal.m4
+/make/configure
/lib/common_test/priv/auxdir/config.guess
/lib/common_test/priv/auxdir/config.sub
/lib/common_test/priv/auxdir/install-sh
@@ -208,6 +214,7 @@ JAVADOC-GENERATED
/lib/wx/autoconf/config.guess
/lib/wx/autoconf/config.sub
/lib/wx/autoconf/install-sh
+/lib/crypto/aclocal.m4
#
# Files generated when building/running tests (especially if
@@ -240,8 +247,11 @@ JAVADOC-GENERATED
/lib/compiler/src/core_parse.erl
/lib/compiler/test/*_no_opt_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/*_r21_SUITE.erl
+/lib/compiler/test/*_no_module_opt_SUITE.erl
# crypto
/lib/crypto/test/crypto_SUITE_data/*.rsp
diff --git a/.travis.yml b/.travis.yml
index ee724f8947..00fe85fc04 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -36,16 +36,42 @@ matrix:
script:
- ./scripts/build-otp
- ./scripts/run-dialyzer
+
- env: Linux32
services:
- docker
script:
- ./scripts/build-docker-otp 32 sh -c "scripts/build-otp release && ./otp_build tests && scripts/run-smoke-tests && bin/dialyzer --build_plt --apps erts kernel stdlib"
+
- env: Linux64SmokeTest
script:
+ - ERL_COMPILER_OPTIONS=ssalint ./scripts/build-otp
+ - ERL_COMPILER_OPTIONS=ssalint ./otp_build tests
+ - ./scripts/run-smoke-tests
+
+ - env: Linux-ppc64le-SmokeTest
+ os: linux-ppc64le
+ script:
- ./scripts/build-otp
- ./otp_build tests
- ./scripts/run-smoke-tests
+ addons:
+ apt:
+ update: true
+ packages:
+ - autoconf
+ - libncurses-dev
+ - build-essential
+ - libssl-dev
+ - libwxgtk3.0-dev
+ - libgl1-mesa-dev
+ - libglu1-mesa-dev
+ - libpng3
+ - default-jdk
+ - g++
+ - xsltproc
+ - libxml2-utils
+
- env: Linux64Docbuild
script:
- ./scripts/build-otp docs
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 96db464b52..8814e506f9 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -3,6 +3,7 @@
## License
By making a contribution to this project, I certify that:
+
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
diff --git a/HOWTO/INSTALL.md b/HOWTO/INSTALL.md
index 456dafeba5..674454bc8e 100644
--- a/HOWTO/INSTALL.md
+++ b/HOWTO/INSTALL.md
@@ -338,6 +338,8 @@ use the `--prefix` argument like this: `./configure --prefix=<Dir>`.
Some of the available `configure` options are:
* `--prefix=PATH` - Specify installation prefix.
+* `--disable-parallel-configure` - Disable parallel execution of
+ `configure` scripts (parallel execution is enabled by default)
* `--{enable,disable}-kernel-poll` - Kernel poll support (enabled by
default if possible)
* `--{enable,disable}-hipe` - HiPE support (enabled by default on supported
diff --git a/Makefile.in b/Makefile.in
index 749cd27f9b..30755c2e4c 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -769,7 +769,7 @@ tertiary_bootstrap_copy:
true; \
done
# copy erl_interface includes
- $(V_at)for x in lib/erl_interface/include/*; do \
+ $(V_at)for x in lib/erl_interface/include/* lib/erl_interface/include/$(TARGET)/*; do \
BN=`basename $$x`; \
TF=$(BOOTSTRAP_ROOT)/bootstrap/lib/erl_interface/include/$$BN; \
test -f $$TF && \
@@ -990,7 +990,7 @@ primary_bootstrap_copy:
# To remove modules left by the bootstrap building, but leave (restore)
# the modules in kernel which are needed for an emulator build
-KERNEL_PRELOAD = otp_ring0 init erl_prim_loader prim_inet prim_file zlib prim_zip erlang erts_code_purger
+KERNEL_PRELOAD = erl_init init erl_prim_loader prim_inet prim_file zlib prim_zip erlang erts_code_purger
KERNEL_PRELOAD_BEAMS=$(KERNEL_PRELOAD:%=$(BOOTSTRAP_TOP)/lib/kernel/ebin/%.beam)
start_scripts:
diff --git a/OTP_VERSION b/OTP_VERSION
index ba92843422..da0a41680b 100644
--- a/OTP_VERSION
+++ b/OTP_VERSION
@@ -1 +1 @@
-21.2.5
+22.0-rc1
diff --git a/bootstrap/bin/no_dot_erlang.boot b/bootstrap/bin/no_dot_erlang.boot
index db76df8e3c..b48d22fe41 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 db76df8e3c..b48d22fe41 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 db76df8e3c..b48d22fe41 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 248c6dc0e4..ceb83ba48b 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 03eb4986e4..5c91f09b30 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 dc941da6ae..1db56cb5f1 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_bs.beam b/bootstrap/lib/compiler/ebin/beam_bs.beam
deleted file mode 100644
index 1fc68940ac..0000000000
--- a/bootstrap/lib/compiler/ebin/beam_bs.beam
+++ /dev/null
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_bsm.beam b/bootstrap/lib/compiler/ebin/beam_bsm.beam
deleted file mode 100644
index 6bf3b576b2..0000000000
--- a/bootstrap/lib/compiler/ebin/beam_bsm.beam
+++ /dev/null
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_clean.beam b/bootstrap/lib/compiler/ebin/beam_clean.beam
index bc49326e74..4d567eca88 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_dead.beam b/bootstrap/lib/compiler/ebin/beam_dead.beam
deleted file mode 100644
index 534eb65372..0000000000
--- a/bootstrap/lib/compiler/ebin/beam_dead.beam
+++ /dev/null
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_dict.beam b/bootstrap/lib/compiler/ebin/beam_dict.beam
index 1e2068b5cb..b9317a55ee 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_disasm.beam b/bootstrap/lib/compiler/ebin/beam_disasm.beam
index 2d9bc49992..6b6b6d0f3f 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_except.beam b/bootstrap/lib/compiler/ebin/beam_except.beam
index 6eed82587a..0fb15b1422 100644
--- a/bootstrap/lib/compiler/ebin/beam_except.beam
+++ b/bootstrap/lib/compiler/ebin/beam_except.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_flatten.beam b/bootstrap/lib/compiler/ebin/beam_flatten.beam
index e039f3c12b..d7780861c2 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 a77c5caf36..289c08630a 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
new file mode 100644
index 0000000000..a6b54314f6
--- /dev/null
+++ 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 87a8791bc4..097ba5e7ff 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 98b2c19b48..52a23f7f6c 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_peep.beam b/bootstrap/lib/compiler/ebin/beam_peep.beam
index 5f33584fe4..9246bd1c27 100644
--- a/bootstrap/lib/compiler/ebin/beam_peep.beam
+++ b/bootstrap/lib/compiler/ebin/beam_peep.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_receive.beam b/bootstrap/lib/compiler/ebin/beam_receive.beam
deleted file mode 100644
index 909e5403d1..0000000000
--- a/bootstrap/lib/compiler/ebin/beam_receive.beam
+++ /dev/null
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_record.beam b/bootstrap/lib/compiler/ebin/beam_record.beam
deleted file mode 100644
index 188bd82412..0000000000
--- a/bootstrap/lib/compiler/ebin/beam_record.beam
+++ /dev/null
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_reorder.beam b/bootstrap/lib/compiler/ebin/beam_reorder.beam
deleted file mode 100644
index 05562e19ea..0000000000
--- a/bootstrap/lib/compiler/ebin/beam_reorder.beam
+++ /dev/null
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_split.beam b/bootstrap/lib/compiler/ebin/beam_split.beam
deleted file mode 100644
index 476dd53ee6..0000000000
--- a/bootstrap/lib/compiler/ebin/beam_split.beam
+++ /dev/null
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa.beam b/bootstrap/lib/compiler/ebin/beam_ssa.beam
new file mode 100644
index 0000000000..9ba124a92d
--- /dev/null
+++ b/bootstrap/lib/compiler/ebin/beam_ssa.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_bsm.beam b/bootstrap/lib/compiler/ebin/beam_ssa_bsm.beam
new file mode 100644
index 0000000000..50412bc8de
--- /dev/null
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_bsm.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_codegen.beam b/bootstrap/lib/compiler/ebin/beam_ssa_codegen.beam
new file mode 100644
index 0000000000..50737c66b1
--- /dev/null
+++ 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
new file mode 100644
index 0000000000..f3dbfc42ff
--- /dev/null
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_dead.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_funs.beam b/bootstrap/lib/compiler/ebin/beam_ssa_funs.beam
new file mode 100644
index 0000000000..6370e1eb78
--- /dev/null
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_funs.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_lint.beam b/bootstrap/lib/compiler/ebin/beam_ssa_lint.beam
new file mode 100644
index 0000000000..322653dfc7
--- /dev/null
+++ 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
new file mode 100644
index 0000000000..7b34cc5906
--- /dev/null
+++ 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
new file mode 100644
index 0000000000..f76f15aa70
--- /dev/null
+++ 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
new file mode 100644
index 0000000000..8481429a30
--- /dev/null
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_pre_codegen.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_recv.beam b/bootstrap/lib/compiler/ebin/beam_ssa_recv.beam
new file mode 100644
index 0000000000..578e8db0eb
--- /dev/null
+++ 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
new file mode 100644
index 0000000000..ea8e83d919
--- /dev/null
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_share.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_type.beam b/bootstrap/lib/compiler/ebin/beam_ssa_type.beam
new file mode 100644
index 0000000000..52bdbf6937
--- /dev/null
+++ 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 32cc6efe88..00f3224106 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_type.beam b/bootstrap/lib/compiler/ebin/beam_type.beam
deleted file mode 100644
index fe2824b20e..0000000000
--- a/bootstrap/lib/compiler/ebin/beam_type.beam
+++ /dev/null
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_utils.beam b/bootstrap/lib/compiler/ebin/beam_utils.beam
index 7c65d7b6e4..996525efc9 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 6d2c9eb69d..a5db96bdaf 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 57698dbb3b..21ca7bcf46 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 4f8ae7ca43..948c4759b0 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 fdd67aebde..a20a4ca43b 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 f0d6063f19..7bf92a0dd7 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_sets.beam b/bootstrap/lib/compiler/ebin/cerl_sets.beam
index 6905c648c2..bda70130a6 100644
--- a/bootstrap/lib/compiler/ebin/cerl_sets.beam
+++ b/bootstrap/lib/compiler/ebin/cerl_sets.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/cerl_trees.beam b/bootstrap/lib/compiler/ebin/cerl_trees.beam
index 4304437799..831eddc405 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 3f52b9463d..5829ab4867 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 f53cc32c12..0a5e6de039 100644
--- a/bootstrap/lib/compiler/ebin/compiler.app
+++ b/bootstrap/lib/compiler/ebin/compiler.app
@@ -19,29 +19,34 @@
{application, compiler,
[{description, "ERTS CXC 138 10"},
- {vsn, "7.2.7"},
+ {vsn, "7.3.1"},
{modules, [
beam_a,
beam_asm,
beam_block,
- beam_bs,
- beam_bsm,
beam_clean,
- beam_dead,
beam_dict,
beam_disasm,
beam_except,
beam_flatten,
beam_jump,
+ beam_kernel_to_ssa,
beam_listing,
beam_opcodes,
beam_peep,
- beam_receive,
- beam_reorder,
- beam_record,
- beam_split,
+ beam_ssa,
+ beam_ssa_bsm,
+ beam_ssa_codegen,
+ beam_ssa_dead,
+ beam_ssa_funs,
+ beam_ssa_lint,
+ beam_ssa_opt,
+ beam_ssa_pp,
+ beam_ssa_pre_codegen,
+ beam_ssa_recv,
+ beam_ssa_share,
+ beam_ssa_type,
beam_trim,
- beam_type,
beam_utils,
beam_validator,
beam_z,
@@ -60,12 +65,10 @@
rec_env,
sys_core_alias,
sys_core_bsm,
- sys_core_dsetel,
sys_core_fold,
sys_core_fold_lists,
sys_core_inline,
sys_pre_attributes,
- v3_codegen,
v3_core,
v3_kernel,
v3_kernel_pp
diff --git a/bootstrap/lib/compiler/ebin/compiler.appup b/bootstrap/lib/compiler/ebin/compiler.appup
index 350a3924e7..8370664167 100644
--- a/bootstrap/lib/compiler/ebin/compiler.appup
+++ b/bootstrap/lib/compiler/ebin/compiler.appup
@@ -16,7 +16,7 @@
%% limitations under the License.
%%
%% %CopyrightEnd%
-{"7.1.5",
+{"7.3.1",
[{<<".*">>,[{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 f3737ecc26..19b0c3dfcb 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 567e381ee8..ecad4f33e0 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 618668e92e..f14357cf02 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 6518f3a4ce..2423c9b2ea 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 bc0190fdb1..8e44464cd7 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 37bd743796..e626e91f4f 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 b0ef249e15..6d0c5baa04 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 52e46da7ad..622221a8b0 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 0bc7588ab3..6f4204e0a1 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_dsetel.beam b/bootstrap/lib/compiler/ebin/sys_core_dsetel.beam
deleted file mode 100644
index c189549b16..0000000000
--- a/bootstrap/lib/compiler/ebin/sys_core_dsetel.beam
+++ /dev/null
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/sys_core_fold.beam b/bootstrap/lib/compiler/ebin/sys_core_fold.beam
index 9def7c8d2c..dd2874bbe0 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 0ad7ab7f9c..f4aac9ac13 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 ebe35883b5..5647c8fcb5 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_pre_attributes.beam b/bootstrap/lib/compiler/ebin/sys_pre_attributes.beam
index d96bc1913a..002b4f2914 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_codegen.beam b/bootstrap/lib/compiler/ebin/v3_codegen.beam
deleted file mode 100644
index 4700b80b93..0000000000
--- a/bootstrap/lib/compiler/ebin/v3_codegen.beam
+++ /dev/null
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/v3_core.beam b/bootstrap/lib/compiler/ebin/v3_core.beam
index 6a76fba69a..06a5b9973a 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 7a155ec05c..54b2f1f0ad 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 4aef1389be..5177b65198 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 58f1681fba..9db02f55fd 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 4f52e506c2..32612f1bf6 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 55adf9849c..77bb9544e2 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 ca7b77abcb..0daab463ab 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 200a996f96..c77178bf7c 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 669efdc50f..41e4bd4ab2 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 93c354bf40..6add687cf2 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 4f0eae4984..25142ee575 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 10c7275240..350b66d1fb 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 0f0418a911..3b961dcefa 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/dist_ac.beam b/bootstrap/lib/kernel/ebin/dist_ac.beam
index 517fa7dfdb..e82fc7dc75 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 3956e595f8..fd21dd47f5 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 6559fc04c1..81f6779c20 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_ddll.beam b/bootstrap/lib/kernel/ebin/erl_ddll.beam
index de9b1cb638..c04bc43872 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 8c8cba9068..6a096155d6 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 449fd8dff1..6ed9862a0e 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_reply.beam b/bootstrap/lib/kernel/ebin/erl_reply.beam
index 25b4b6c55e..7658a35642 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 1a1d9d28ee..da03769e2e 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/error_handler.beam b/bootstrap/lib/kernel/ebin/error_handler.beam
index 1ce96ca432..19739b0043 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 08ec3e0e1a..10b13c35b6 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 82641f173a..5336f4ae6c 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 07a4f410e1..9c2759adc9 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 d9ca62a82b..b8f1272be4 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 4f1d53cb39..cc2face79c 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 dcd81b1c6f..9fc5800f0c 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 0e492978d5..736b28c68f 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_udp.beam b/bootstrap/lib/kernel/ebin/gen_udp.beam
index 323b6cd4bb..f6022dca19 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/global.beam b/bootstrap/lib/kernel/ebin/global.beam
index e138217d25..f57cf03afd 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 b072f96e5d..83885cdeb5 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 653d2a8699..a94d9da454 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 03abd10d20..396f1166f1 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 97f98ebe2f..b9df7cfc14 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 0c3caaf969..1b63142893 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/hipe_unified_loader.beam b/bootstrap/lib/kernel/ebin/hipe_unified_loader.beam
index 7f205de75f..fc80f2d1bd 100644
--- a/bootstrap/lib/kernel/ebin/hipe_unified_loader.beam
+++ b/bootstrap/lib/kernel/ebin/hipe_unified_loader.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet.beam b/bootstrap/lib/kernel/ebin/inet.beam
index cece8e6e85..b0c67afe90 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 bea47a3591..a6220a9d69 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 b2dc48d794..7cacde3399 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_udp.beam b/bootstrap/lib/kernel/ebin/inet6_udp.beam
index abc4b650ae..c12c6f4afc 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 5b50b553d7..c44b389b75 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 513a7dc03d..76d7ac2adb 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 f483acd96c..d88fa2185d 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_gethost_native.beam b/bootstrap/lib/kernel/ebin/inet_gethost_native.beam
index 610d9a2205..afc4999c42 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 63fadee640..8fb47e84eb 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 175649cd81..84c0169d4b 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 463f90fae8..c964152d79 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 5107eb63b8..0e659bd738 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 5e3d9b307b..c273d47012 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 fdfe042445..d09aa250c8 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 d358ee15dc..62156a3284 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 898ed0a048..ab106113c2 100644
--- a/bootstrap/lib/kernel/ebin/kernel.app
+++ b/bootstrap/lib/kernel/ebin/kernel.app
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All 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 @@
{application, kernel,
[
{description, "ERTS CXC 138 10"},
- {vsn, "6.1"},
+ {vsn, "6.2"},
{modules, [application,
application_controller,
application_master,
@@ -68,11 +68,12 @@
logger_formatter,
logger_h_common,
logger_handler_watcher,
+ logger_olp,
+ logger_proxy,
logger_server,
logger_simple_h,
logger_std_h,
logger_sup,
- net,
net_adm,
net_kernel,
os,
diff --git a/bootstrap/lib/kernel/ebin/kernel.appup b/bootstrap/lib/kernel/ebin/kernel.appup
index ac114f0354..59e716b2d0 100644
--- a/bootstrap/lib/kernel/ebin/kernel.appup
+++ b/bootstrap/lib/kernel/ebin/kernel.appup
@@ -16,13 +16,42 @@
%% limitations under the License.
%%
%% %CopyrightEnd%
-{"5.4.3",
- %% Up from - max one major revision back
- [{<<"5\\.3(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.0
- {<<"5\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.1+
- {<<"6\\.0(\\.[0-9]+)*">>,[restart_new_emulator]}], % OTP-21
- %% Down to - max one major revision back
- [{<<"5\\.3(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.0
- {<<"5\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.1+
- {<<"6\\.0(\\.[0-9]+)*">>,[restart_new_emulator]}] % OTP-21
-}.
+%%
+%% We allow upgrade from, and downgrade to all previous
+%% versions from the following OTP releases:
+%% - OTP 20
+%% - OTP 21
+%%
+%% We also allow upgrade from, and downgrade to all
+%% versions that have branched off from the above
+%% stated previous versions.
+%%
+{"6.2",
+ [{<<"^5\\.3$">>,[restart_new_emulator]},
+ {<<"^5\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^5\\.3\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^5\\.4$">>,[restart_new_emulator]},
+ {<<"^5\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^5\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^5\\.4\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^5\\.4\\.3(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^6\\.0$">>,[restart_new_emulator]},
+ {<<"^6\\.0\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^6\\.0\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^6\\.1$">>,[restart_new_emulator]},
+ {<<"^6\\.1\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^6\\.1\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}],
+ [{<<"^5\\.3$">>,[restart_new_emulator]},
+ {<<"^5\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^5\\.3\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^5\\.4$">>,[restart_new_emulator]},
+ {<<"^5\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^5\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^5\\.4\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^5\\.4\\.3(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^6\\.0$">>,[restart_new_emulator]},
+ {<<"^6\\.0\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^6\\.0\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^6\\.1$">>,[restart_new_emulator]},
+ {<<"^6\\.1\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^6\\.1\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}.
diff --git a/bootstrap/lib/kernel/ebin/kernel.beam b/bootstrap/lib/kernel/ebin/kernel.beam
index b3f0783d25..8f75029c0f 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 85b517fe0f..e749cc6c96 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 2fca33154c..04306c6e2c 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 02c5848f11..2958d19524 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 400c9ca23b..cc5f46f746 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 26e0d3b0f1..bd17885e22 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 b2e7854689..40a3c4d80a 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 7eb426852b..51c7f78237 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 7105ce9003..dd0ca5f204 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 57197e137c..2d8035278d 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 3e99f7e1fa..83df11e66f 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 c09eaaba65..5e14216b5d 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 44bc104ff9..fb20fd1818 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
new file mode 100644
index 0000000000..ed06b6ae5e
--- /dev/null
+++ 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
new file mode 100644
index 0000000000..ed0ed0f56b
--- /dev/null
+++ 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 c7c9b11962..dcff9beac7 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 fca659598d..954bbdc071 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 60618d56c9..3b5d3cd9f8 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 32eb526a09..60192ef4a1 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
deleted file mode 100644
index 29c8bc5dde..0000000000
--- a/bootstrap/lib/kernel/ebin/net.beam
+++ /dev/null
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/net_adm.beam b/bootstrap/lib/kernel/ebin/net_adm.beam
index 05aa46dcb8..5810577c6a 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 a62690e27a..4c57f5f6c8 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 f2a003d44f..f05007041b 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/pg2.beam b/bootstrap/lib/kernel/ebin/pg2.beam
index a106700e44..d293ea18f3 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/ram_file.beam b/bootstrap/lib/kernel/ebin/ram_file.beam
index c2f265ede4..02c4c6454a 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 734a051882..16dc314ba5 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 6f07a3c5c3..3c41f8528e 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 222817d912..11b8f4a6db 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 2c20a06fd1..b3381cc397 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 228a8f28a5..bd0064ebf2 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 4c69c890cd..b544fd394e 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 7a1c9e0b31..b53525e2ca 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 1d1e277bf3..f81a39ddc0 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/standard_error.beam b/bootstrap/lib/kernel/ebin/standard_error.beam
index c01106ef11..18582d22f2 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
index 4cbf2174a2..f3573d95af 100644
--- a/bootstrap/lib/kernel/ebin/user.beam
+++ b/bootstrap/lib/kernel/ebin/user.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/user_drv.beam b/bootstrap/lib/kernel/ebin/user_drv.beam
index c5a3330ee1..ce4d4328de 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 6aa34138c8..c82fca70da 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 a0f24625b4..ceb8dd9a78 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 003852f1b0..f06fc328d7 100644
--- a/bootstrap/lib/kernel/include/dist.hrl
+++ b/bootstrap/lib/kernel/include/dist.hrl
@@ -42,6 +42,9 @@
-define(DFLAG_BIG_CREATION, 16#40000).
-define(DFLAG_SEND_SENDER, 16#80000).
-define(DFLAG_BIG_SEQTRACE_LABELS, 16#100000).
+%% -define(DFLAG_NO_MAGIC, 16#200000). %% Used internally only
+-define(DFLAG_EXIT_PAYLOAD, 16#400000).
+-define(DFLAG_FRAGMENTS, 16#800000).
%% 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 e352b65951..ac3193515c 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 902d8edc6c..5872970da7 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 ee20aced6b..c13a82a074 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 e712319947..0cedd64883 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 6c8d14bac1..c61132b2de 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 1dc653109d..d0abc17e48 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 a7500207db..a92506125d 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 31f0f2701a..8cd247e3be 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_utils.beam b/bootstrap/lib/stdlib/ebin/dets_utils.beam
index 2b56a46eb1..16c6f1aba5 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 2fda3ba53f..2f82423bac 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 345df4c87f..dc390024a4 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 1faf374588..60abe020fd 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 fa41a7af26..772332cd08 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 c8d4cac351..b6a7ecc9a5 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_expand.beam b/bootstrap/lib/stdlib/ebin/edlin_expand.beam
index 62e209dcc7..865066c3ac 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/epp.beam b/bootstrap/lib/stdlib/ebin/epp.beam
index dba73e824b..2618efb7d2 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 e6042b9715..973ad9fb80 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 7a0a4c08d8..a40fcb3f92 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 b9b75ecaef..1beb0d5ebb 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 a317ae28a1..fa223657bf 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 dc9d0a8d39..db9d45a0d2 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 4d96a82267..90b3858207 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 9a3daef51e..3a631e24bc 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_internal.beam b/bootstrap/lib/stdlib/ebin/erl_internal.beam
index 0b56862c57..9015ad8936 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 ce99e04342..35e02b9595 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 86876cda96..41369ca147 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 a5d30afed2..5f6c03bcae 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 f645aea910..64e558e272 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 a7a2910e1b..3b30d3e8c7 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_tar.beam b/bootstrap/lib/stdlib/ebin/erl_tar.beam
index b72e4ebf6a..97ddc7d703 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 2f2e54de1a..64de26f7a4 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 a6e46e72b4..e7ee41cc2e 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 781484fe0b..7685b4a49a 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 ab4996ef4e..cb7c957719 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 0a3c0b47fb..08f86f86ff 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 9fb87f4a68..f24f6cfbe8 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 d1ec5357fc..62b8c3588e 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 85d568a8e1..9981a33a4e 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 43ba8674b7..8d1e2c11e0 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 4476889671..0aae509cb6 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 b0e38024c5..85a9c25618 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 ef8d0cd929..f3376d2ee2 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 3a4df3499c..5c4a0aa3d5 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 1752276cc8..06c3d6de19 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 1c1b93a036..0aab776d07 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 197d9b7f9c..631863b587 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 05894640cb..c52efe1b44 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 9b8d7d8a9e..5556dc733b 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 b33a6bbd72..b56a9e3eb7 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 bd65ecfc30..7ffe569c60 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 a711637e0c..5a330bece0 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 b27db50f62..b812621c0a 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 ec0ebce58a..193a06241d 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/ms_transform.beam b/bootstrap/lib/stdlib/ebin/ms_transform.beam
index ae3550872f..bb98ae56b3 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 57a23fbe98..d6f267ad8f 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 eac57f960a..9e49566404 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 c42b18f6cf..a975a43c00 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/pool.beam b/bootstrap/lib/stdlib/ebin/pool.beam
index b056cf44ec..408ee3a0c9 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 45f25d7820..b51898901d 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 cb2e3ddb11..a8ddeb2d57 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 c1cd50f143..4b7a3b5278 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 4153598cc7..22c0e2ef95 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 8c6b611247..571d4a13a5 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 55f8db7445..9636f4fbbf 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 37f12f8a36..a4bc2b6128 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 4334fa8dc4..d6920d54ce 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 c1b7414741..e3609ea527 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 65ffc775ad..319bb9aebf 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 b2b955de04..afd0c65921 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/slave.beam b/bootstrap/lib/stdlib/ebin/slave.beam
index e832637c7c..259d300155 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 0acc6b92ba..f64faafa52 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 d660046c77..2bb0b06f12 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, "3.6"},
+ {vsn, "3.7.1"},
{modules, [array,
base64,
beam_lib,
@@ -108,7 +108,7 @@
dets]},
{applications, [kernel]},
{env, []},
- {runtime_dependencies, ["sasl-3.0","kernel-6.0","erts-10.0","crypto-3.3",
+ {runtime_dependencies, ["sasl-3.0","kernel-6.0","erts-@OTP-15128@","crypto-3.3",
"compiler-5.0"]}
]}.
diff --git a/bootstrap/lib/stdlib/ebin/stdlib.appup b/bootstrap/lib/stdlib/ebin/stdlib.appup
index 381b33c833..d22e917574 100644
--- a/bootstrap/lib/stdlib/ebin/stdlib.appup
+++ b/bootstrap/lib/stdlib/ebin/stdlib.appup
@@ -16,11 +16,42 @@
%% limitations under the License.
%%
%% %CopyrightEnd%
-{"3.4.5",
- %% Up from - max one major revision back
- [{<<"3\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.*
- {<<"3\\.5(\\.[0-9]+)*">>,[restart_new_emulator]}],% OTP-21.*
- %% Down to - max one major revision back
- [{<<"3\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.*
- {<<"3\\.5(\\.[0-9]+)*">>,[restart_new_emulator]}] % OTP-20.*
-}.
+%%
+%% We allow upgrade from, and downgrade to all previous
+%% versions from the following OTP releases:
+%% - OTP 20
+%% - OTP 21
+%%
+%% We also allow upgrade from, and downgrade to all
+%% versions that have branched off from the above
+%% stated previous versions.
+%%
+{"3.7.1",
+ [{<<"^3\\.4$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.3(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.4(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.5(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.5$">>,[restart_new_emulator]},
+ {<<"^3\\.5\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.6$">>,[restart_new_emulator]},
+ {<<"^3\\.6\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.7$">>,[restart_new_emulator]},
+ {<<"^3\\.7\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}],
+ [{<<"^3\\.4$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.3(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.4(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.5(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.5$">>,[restart_new_emulator]},
+ {<<"^3\\.5\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.6$">>,[restart_new_emulator]},
+ {<<"^3\\.6\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.7$">>,[restart_new_emulator]},
+ {<<"^3\\.7\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}]}.
diff --git a/bootstrap/lib/stdlib/ebin/string.beam b/bootstrap/lib/stdlib/ebin/string.beam
index 57142f539a..da647da3ca 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 939f385c93..1816fe6c41 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 6cfaa984df..761907b40c 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 599cc20df4..2b4906508a 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 7baece7eb2..2953299e8b 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 bdd4374aef..d61ed589aa 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 fe2f9c24dc..8ddf73ca26 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 36aec511d8..001118f0b9 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 3194d09463..136b65f9fb 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 ef7ea86791..cd6274efe5 100644
--- a/bootstrap/lib/stdlib/ebin/zip.beam
+++ b/bootstrap/lib/stdlib/ebin/zip.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/include/assert.hrl b/bootstrap/lib/stdlib/include/assert.hrl
index 2ec89e7d8a..28d25c6589 100644
--- a/bootstrap/lib/stdlib/include/assert.hrl
+++ b/bootstrap/lib/stdlib/include/assert.hrl
@@ -140,7 +140,7 @@
-endif.
%% This is mostly a convenience which gives more detailed reports.
-%% Note: Guard is a guarded pattern, and can not be used for value.
+%% Note: Guard is a guarded pattern, and cannot be used for value.
-ifdef(NOASSERT).
-define(assertMatch(Guard, Expr), ok).
-define(assertMatch(Guard, Expr, Comment), ok).
@@ -289,7 +289,7 @@
end).
-endif.
-%% Note: Class and Term are patterns, and can not be used for value.
+%% Note: Class and Term are patterns, and cannot be used for value.
%% Term can be a guarded pattern, but Class cannot.
-ifdef(NOASSERT).
-define(assertException(Class, Term, Expr), ok).
@@ -364,7 +364,7 @@
?assertException(throw, Term, Expr, Comment)).
%% This is the inverse case of assertException, for convenience.
-%% Note: Class and Term are patterns, and can not be used for value.
+%% Note: Class and Term are patterns, and cannot be used for value.
%% Both Class and Term can be guarded patterns.
-ifdef(NOASSERT).
-define(assertNotException(Class, Term, Expr), ok).
diff --git a/configure.src b/configure.src
new file mode 100644
index 0000000000..889902eb96
--- /dev/null
+++ b/configure.src
@@ -0,0 +1,435 @@
+#!/bin/sh
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2018. All 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 is a handcrafted wrapper script which runs the actual (autoconf)
+# 'configure' scripts in different parts of OTP.
+#
+# The true (autoconf) configure scripts are run in parallel unless
+# --disable-parallel-configure is passed as argument to this script.
+#
+# The config cache feature is disabled since it mostly causes problems
+# and especially when executing multiple configure scripts in parallel.
+# On windows a static cache for each configure is used since it
+# otherwise takes forever to run configure...
+#
+
+OTP_VERSION=@OTP_VERSION@
+
+unset CDPATH
+
+default_cflags="-g -O2"
+
+mXY_build=
+static_cache=
+
+bootstrap_only=no
+parallel_otp_configure=yes
+help=no
+user_srcdir=
+config_arguments=
+skip_applications=
+while test $# != 0; do
+ case $1 in
+ -srcdir=* | --srcdir=*)
+ user_srcdir=`expr "$1" : '[^=]*=\(.*\)'`
+ if test "$ERL_TOP" != ""; then
+ echo "WARNING: Overriding ERL_TOP with $user_srcdir" 1>&2
+ echo "" 1>&2
+ fi
+ ERL_TOP="$user_srcdir"
+ ;;
+ -srcdir | --srcdir)
+ shift
+ test $# != 0 || {
+ echo "ERROR: Missing source dir" 1>&2
+ exit 1
+ }
+ user_srcdir="$1"
+ if test "$ERL_TOP" != ""; then
+ echo "WARNING: Overriding ERL_TOP with $user_srcdir" 1>&2
+ echo "" 1>&2
+ fi
+ ERL_TOP="$user_srcdir"
+ ;;
+ --enable-bootstrap-only)
+ bootstrap_only=yes;;
+ --disable-bootstrap-only)
+ bootstrap_only=no;;
+ --enable-option-checking)
+ echo "ERROR: Cannot enable option checking" 1>&2
+ exit 1;;
+ --disable-option-checking)
+ # Got it...
+ ;;
+ --disable-parallel-configure)
+ parallel_otp_configure=no
+ ;;
+ --config-cache | -C)
+ echo "WARNING: Ignoring config cache file since it will mess up the configuration" 1>&2
+ echo "" 1>&2
+ ;;
+ -cache-file=* | --cache-file=* )
+ static_cache=`expr "$1" : '[^=]*=\(.*\)'`
+ if test "$static_cache" != "/dev/null"; then
+ echo "WARNING: Only using config cache file '$static_cache' as static cache" 1>&2
+ echo "" 1>&2
+ else
+ static_cache=
+ fi
+ ;;
+ -cache-file | --cache-file)
+ shift
+ test $# != 0 || {
+ echo "ERROR: Missing cache file" 1>&2
+ exit 1
+ }
+ static_cache=$1
+ if test "$static_cache" != "/dev/null"; then
+ echo "WARNING: Only using config cache file '$static_cache' as static cache" 1>&2
+ echo "" 1>&2
+ else
+ static_cache=
+ fi
+ ;;
+ --enable-m64-build)
+ mXY_build="-m64"
+ ;;
+ --enable-m32-build)
+ mXY_build="-m32"
+ ;;
+ --disable-m64-build)
+ if test "$mXY_build" = "-m64"; then
+ mXY_build=
+ fi;;
+ --disable-m32-build)
+ if test "$mXY_build" = "-m32"; then
+ mXY_build=
+ fi;;
+ CFLAGS=* | LDFLAGS=*)
+ flgs_var=`expr "$1" : '\([^=]*\)=.*'`
+ flgs_val=`expr "$1" : '[^=]*=\(.*\)'`
+ eval $flgs_var=\$flgs_val
+ ;;
+ --help=r* | -help=r*)
+ help=all;;
+ --help* | -help* | -h)
+ help=$1;;
+ *)
+ case $1 in
+ --without-*)
+ skip_app=`expr "$1" : '--without-\(.*\)'`
+ if test -d "lib/$skip_app"; then
+ skip_applications="$skip_applications $skip_app"
+ fi;;
+ *)
+ ;;
+ esac
+ case $1 in
+ *\'*)
+ 1=`echo "$1" | sed "s/'/'\\\\\\\\''/g"` ;;
+ *)
+ ;;
+ esac
+ config_arguments="$config_arguments '$1'";;
+ esac
+ shift
+done
+
+if test $parallel_otp_configure = yes; then
+ case `uname -s` in
+ MSYS* | msys* | CYGWIN* | cygwin )
+ ;;
+ *)
+ trap 'kill -KILL -$$' 1 2 3 6 15
+ ;;
+ esac
+fi
+
+#
+# Figure ERL_TOP out...
+#
+
+root_dir=
+dir_chk_data="$OTP_VERSION"
+
+if root_dir=`dirname "$0" 2>/dev/null` \
+ && test "$root_dir" != "" \
+ && cd "$root_dir" 2>/dev/null; then
+ root_dir=`pwd 2>/dev/null`
+ if test $? -ne 0; then
+ root_dir=
+ else
+ case "$root_dir" in
+ /*)
+ echo $dir_chk_data > "$root_dir"/config.dir.check.$$ 2>/dev/null
+ ;;
+ *)
+ root_dir=
+ esac
+ fi
+else
+ root_dir=
+fi
+
+if test "$root_dir" = ""; then
+ case X"$ERL_TOP" in
+ X)
+ echo "ERROR: Cannot figure out the root directory of" 1>&2
+ echo " the OTP source. Please set the ERL_TOP" 1>&2
+ echo " environment variable." 1>&2
+ exit 1
+ ;;
+ X/*)
+ cd "$ERL_TOP" 2>/dev/null || {
+ echo "ERROR: Cannot change directory to ERL_TOP " 1>&2
+ echo " '$ERL_TOP'" 1>&2
+ exit 1
+ }
+ ;;
+ X*)
+ echo "ERROR: ERL_TOP '$ERL_TOP' is not an absolute path" 1>&2
+ exit 1
+ ;;
+ esac
+else
+ case X"$ERL_TOP" in
+ X)
+ ERL_TOP="$root_dir"
+ rm -f "$root_dir"/config.dir.check.$$
+ ;;
+ X/*)
+ cd "$ERL_TOP" 2>/dev/null || {
+ rm -f "$root_dir"/config.dir.check.$$
+ echo "ERROR: Cannot change directory into ERL_TOP " 1>&2
+ echo " '$ERL_TOP'" 1>&2
+ exit 1
+ }
+ dir_chk=`cat ./config.dir.check.$$ 2>/dev/null` || dir_chk=error
+ rm -f "$root_dir"/config.dir.check.$$
+ test "$dir_chk" = "$dir_chk_data" || {
+ echo "ERROR: ERL_TOP '$ERL_TOP' is not the same" 1>&2
+ echo " as 'configure' location '$root_dir'" 1>&2
+ exit 1
+ }
+ ;;
+ X*)
+ rm -f "$root_dir"/config.dir.check.$$
+ echo "ERROR: ERL_TOP '$ERL_TOP' is not an absolute path" 1>&2
+ exit 1
+ ;;
+ esac
+fi
+
+export ERL_TOP
+
+rm -f "$ERL_TOP/lib/SKIP-APPLICATIONS"
+
+case "$help" in
+ no)
+ ;;
+ all)
+ (cd "$ERL_TOP/make" && ./configure --help)
+ app_dirs=
+ for app_dir in erts lib/*; do
+ if test -f "$app_dir/configure"; then
+ echo ""
+ echo "=== $app_dir ==="
+ (cd "$ERL_TOP/$app_dir" && ./configure --help=short)
+ fi
+ done
+ exit 0;;
+ *)
+ (cd "$ERL_TOP/make" && ./configure "$help")
+ exit 0;;
+esac
+
+if test "$mXY_build" = ""; then
+ if test "$CFLAGS" != ""; then
+ config_arguments="$config_arguments CFLAGS='$CFLAGS'"
+ unset CFLAGS
+ fi
+
+ if test "$LDFLAGS" != ""; then
+ config_arguments="$config_arguments LDFLAGS='$LDFLAGS'"
+ unset LDFLAGS
+ fi
+else
+ if test "$CFLAGS" = ""; then
+ CFLAGS=$default_cflags
+ fi
+ config_arguments="$config_arguments CFLAGS='$mXY_build $CFLAGS'"
+ unset CFLAGS
+ config_arguments="$config_arguments LDFLAGS='$mXY_build $LDFLAGS'"
+ unset LDFLAGS
+ case $mXY_build in
+ -m32)
+ config_arguments="$config_arguments --enable-m32-build"
+ ;;
+ -m64)
+ config_arguments="$config_arguments --enable-m64-build"
+ ;;
+ esac
+fi
+
+config_arguments="$config_arguments --disable-option-checking"
+
+if test $bootstrap_only = no; then
+ check_conf_dirs=`echo lib/*`
+else
+ check_conf_dirs="@BOOTSTRAP_LIB_APP_DIRS@"
+fi
+
+# 'erts' *needs* to be last in app_dirs! Any apps
+# after it wont be able to run in parallel with
+# erts...
+
+app_dirs=
+for app_dir in make $check_conf_dirs erts; do
+ if test -f "$app_dir/configure"; then
+ if test "$static_cache" != ""; then
+ cp -f "$static_cache" "$ERL_TOP/$app_dir/local.static.config.cache"
+ fi
+ rm -f $app_dir/configure.result.failed
+ rm -f $app_dir/configure.result.command
+ rm -f $app_dir/configure.result.stdout
+ rm -f $app_dir/configure.result.stderr
+ app_dirs="$app_dirs $app_dir"
+ fi
+done
+
+if test "$static_cache" = ""; then
+ local_cache_file=/dev/null
+else
+ local_cache_file=./local.static.config.cache
+fi
+
+for app_dir in $app_dirs; do
+ cd "$ERL_TOP/$app_dir"
+ cmd_str="./configure $config_arguments --cache-file=$local_cache_file --srcdir=\"$ERL_TOP/$app_dir\""
+ if test $parallel_otp_configure = no; then
+ echo "=== Running configure in $ERL_TOP/$app_dir ==="
+ eval $cmd_str
+ echo ""
+ elif test $app_dir != erts; then
+ echo $cmd_str >./configure.result.command
+ eval $cmd_str 1>./configure.result.stdout 2>./configure.result.stderr || echo "fatal" > "./configure.result.failed" &
+ else
+ # ERTS configure gets to use stdout/stderr; the others are printed when done...
+ echo "=== Running configure in $ERL_TOP/erts ==="
+ echo $cmd_str
+ eval $cmd_str || {
+ rm -f erts/configure.result.* lib/*/configure.result.*
+ echo "ERROR: $ERL_TOP/$app_dir/configure failed!" 1>&2
+ kill -KILL -$$
+ exit 1
+ }
+ fi
+done
+
+cd "$ERL_TOP"
+
+if test $parallel_otp_configure = yes; then
+ # Wait for all sub-configures to finish...
+ wait
+
+ for app_dir in $app_dirs; do
+ if test -f "$app_dir/configure.result.command"; then
+ echo ""
+ echo "=== Running configure in $ERL_TOP/$app_dir ==="
+ cat "$app_dir/configure.result.command"
+ rm -f "$app_dir/configure.result.command"
+ fi
+ if test -f "$app_dir/configure.result.stdout"; then
+ cat "$app_dir/configure.result.stdout"
+ rm -f "$app_dir/configure.result.stdout"
+ fi
+ if test -f "$app_dir/configure.result.stderr"; then
+ cat "$app_dir/configure.result.stderr" 1>&2
+ rm -f "$app_dir/configure.result.stderr"
+ fi
+ if test -f "$app_dir/configure.result.failed"; then
+ rm -f erts/configure.result.* lib/*/configure.result.*
+ echo "ERROR: $ERL_TOP/$app_dir/configure failed!" 1>&2
+ exit 1
+ fi
+ done
+fi
+
+
+echo ""
+
+pattern="lib/*/SKIP"
+files=`echo $pattern`
+if test "$files" != "$pattern" || test "$skip_applications" != ""; then
+ echo '*********************************************************************'
+ echo '********************** APPLICATIONS DISABLED **********************'
+ echo '*********************************************************************'
+ echo
+ if test "$files" != "$pattern"; then
+ for skipfile in $files; do
+ app=`dirname $skipfile`; app=`basename $app`
+ printf "%-15s: " $app; cat $skipfile
+ done
+ fi
+ for skipapp in $skip_applications; do
+ printf "%-15s: User gave --without-%s option\n" $skipapp $skipapp
+ echo "$skipapp" >> "$ERL_TOP/lib/SKIP-APPLICATIONS"
+ done
+ echo
+ echo '*********************************************************************'
+fi
+pattern="lib/*/CONF_INFO"
+files=`echo $pattern`
+if test "$files" != "$pattern" || test -f erts/CONF_INFO; then
+ echo '*********************************************************************'
+ echo '********************** APPLICATIONS INFORMATION *******************'
+ echo '*********************************************************************'
+ echo
+ test "$files" != "$pattern" || files=""
+ test ! -f erts/CONF_INFO || files="$files erts/CONF_INFO"
+ for infofile in $files; do
+ app=`dirname $infofile`; app=`basename $app`
+ printf "%-15s: " $app; cat $infofile
+ done
+ echo
+ echo '*********************************************************************'
+fi
+if test -f "erts/doc/CONF_INFO"; then
+ echo '*********************************************************************'
+ echo '********************** DOCUMENTATION INFORMATION ******************'
+ echo '*********************************************************************'
+ echo
+ printf "%-15s: \n" documentation;
+ havexsltproc="yes"
+ for cmd in `cat erts/doc/CONF_INFO`; do
+ echo " $cmd is missing."
+ if test $cmd = "xsltproc"; then
+ havexsltproc="no"
+ fi
+ done
+ if test $havexsltproc = "no"; then
+ echo ' The documentation cannot be built.'
+ else
+ echo ' Using fakefop to generate placeholder PDF files.'
+ fi
+ echo
+ echo '*********************************************************************'
+fi
diff --git a/erts/aclocal.m4 b/erts/aclocal.m4
index 3d227e462c..0ca2755802 100644
--- a/erts/aclocal.m4
+++ b/erts/aclocal.m4
@@ -122,6 +122,9 @@ dnl
AC_DEFUN(LM_WINDOWS_ENVIRONMENT,
[
+
+if test "X$windows_environment_" != "Xchecked"; then
+windows_environment_=checked
MIXED_CYGWIN=no
MIXED_MSYS=no
@@ -197,6 +200,8 @@ else
fi
AC_SUBST(MIXED_MSYS)
+
+fi
])
dnl ----------------------------------------------------------------------
@@ -2856,3 +2861,248 @@ AC_DEFUN([LM_HARDWARE_ARCH], [
AC_SUBST(ARCH)
])
+
+dnl
+dnl--------------------------------------------------------------------
+dnl Dynamic Erlang Drivers
+dnl
+dnl Linking to produce dynamic Erlang drivers to be loaded by Erlang's
+dnl Dynamic Driver Loader and Linker (DDLL). Below the prefix DED is an
+dnl abbreviation for `Dynamic Erlang Driver'.
+dnl
+dnl For DED we need something quite sloppy, which allows undefined references
+dnl (notably driver functions) in the resulting shared library.
+dnl Example of Makefile rule (and settings of macros):
+dnl
+dnl LIBS = @LIBS@
+dnl LD = @DED_LD@
+dnl LDFLAGS = @DED_LDFLAGS@
+dnl soname = @ldsoname@
+dnl
+dnl my_drv.so: my_drv.o my_utils.o
+dnl $(LD) $(LDFLAGS) $(soname) $@ -o $@ $^ -lc $(LIBS)
+dnl
+dnl--------------------------------------------------------------------
+dnl
+
+AC_DEFUN(ERL_DED,
+ [
+
+USER_LD=$LD
+USER_LDFLAGS="$LDFLAGS"
+
+LM_CHECK_THR_LIB
+
+DED_CC=$CC
+DED_GCC=$GCC
+
+DED_CFLAGS=
+DED_OSTYPE=unix
+case $host_os in
+ linux*)
+ DED_CFLAGS="-D_GNU_SOURCE" ;;
+ win32)
+ DED_CFLAGS="-D_WIN32_WINNT=0x0600 -DWINVER=0x0600"
+ DED_OSTYPE=win32 ;;
+ *)
+ ;;
+esac
+
+
+DED_WARN_FLAGS="-Wall -Wstrict-prototypes"
+case "$host_cpu" in
+ tile*)
+ # tile-gcc is a bit stricter with -Wmissing-prototypes than other gccs,
+ # and too strict for our taste.
+ ;;
+ *)
+ DED_WARN_FLAGS="$DED_WARN_FLAGS -Wmissing-prototypes";;
+esac
+
+LM_TRY_ENABLE_CFLAG([-Wdeclaration-after-statement], [DED_WARN_FLAGS])
+
+LM_TRY_ENABLE_CFLAG([-Werror=return-type], [DED_WERRORFLAGS])
+LM_TRY_ENABLE_CFLAG([-Werror=implicit], [DED_WERRORFLAGS])
+LM_TRY_ENABLE_CFLAG([-Werror=undef], [DED_WERRORFLAGS])
+
+DED_SYS_INCLUDE="-I${ERL_TOP}/erts/emulator/beam -I${ERL_TOP}/erts/include -I${ERL_TOP}/erts/include/$host -I${ERL_TOP}/erts/include/internal -I${ERL_TOP}/erts/include/internal/$host -I${ERL_TOP}/erts/emulator/sys/$DED_OSTYPE -I${ERL_TOP}/erts/emulator/sys/common"
+DED_INCLUDE=$DED_SYS_INCLUDE
+
+if test "$THR_DEFS" = ""; then
+ DED_THR_DEFS="-D_THREAD_SAFE -D_REENTRANT"
+else
+ DED_THR_DEFS="$THR_DEFS"
+fi
+# DED_EMU_THR_DEFS=$EMU_THR_DEFS
+DED_CFLAGS="$CFLAGS $CPPFLAGS $DED_CFLAGS"
+if test "x$GCC" = xyes; then
+ DED_STATIC_CFLAGS="$DED_CFLAGS"
+ DED_CFLAGS="$DED_CFLAGS -fPIC"
+fi
+
+DED_EXT=so
+case $host_os in
+ win32) DED_EXT=dll;;
+ darwin*)
+ DED_CFLAGS="$DED_CFLAGS -fno-common"
+ DED_STATIC_CFLAGS="$DED_STATIC_CFLAGS -fno-common";;
+ *)
+ ;;
+esac
+
+DED_STATIC_CFLAGS="$DED_STATIC_CFLAGS -DSTATIC_ERLANG_NIF -DSTATIC_ERLANG_DRIVER"
+
+if test "$CFLAG_RUNTIME_LIBRARY_PATH" = ""; then
+
+ CFLAG_RUNTIME_LIBRARY_PATH="-Wl,-R"
+ case $host_os in
+ darwin*)
+ CFLAG_RUNTIME_LIBRARY_PATH=
+ ;;
+ win32)
+ CFLAG_RUNTIME_LIBRARY_PATH=
+ ;;
+ osf*)
+ CFLAG_RUNTIME_LIBRARY_PATH="-Wl,-rpath,"
+ ;;
+ *)
+ ;;
+ esac
+
+fi
+
+# If DED_LD is set in environment, we expect all DED_LD* variables
+# to be specified (cross compiling)
+if test "x$DED_LD" = "x"; then
+
+DED_LD_FLAG_RUNTIME_LIBRARY_PATH="-R"
+case $host_os in
+ win32)
+ DED_LD="ld.sh"
+ DED_LDFLAGS="-dll"
+ DED_LD_FLAG_RUNTIME_LIBRARY_PATH=
+ ;;
+ solaris2*|sysv4*)
+ DED_LDFLAGS="-G"
+ if test X${enable_m64_build} = Xyes; then
+ DED_LDFLAGS="-64 $DED_LDFLAGS"
+ fi
+ ;;
+ aix4*)
+ DED_LDFLAGS="-G -bnoentry -bexpall"
+ ;;
+ freebsd2*)
+ # Non-ELF GNU linker
+ DED_LDFLAGS="-Bshareable"
+ ;;
+ darwin*)
+ # Mach-O linker: a shared lib and a loadable
+ # object file is not the same thing.
+ DED_LDFLAGS="-bundle -bundle_loader ${ERL_TOP}/bin/$host/beam.smp"
+ if test X${enable_m64_build} = Xyes; then
+ DED_LDFLAGS="-m64 $DED_LDFLAGS"
+ else
+ if test X${enable_m32_build} = Xyes; then
+ DED_LDFLAGS="-m32 $DED_LDFLAGS"
+ else
+ AC_CHECK_SIZEOF(void *)
+ case "$ac_cv_sizeof_void_p" in
+ 8)
+ DED_LDFLAGS="-m64 $DED_LDFLAGS";;
+ *)
+ ;;
+ esac
+ fi
+ fi
+ DED_LD="$CC"
+ DED_LD_FLAG_RUNTIME_LIBRARY_PATH="$CFLAG_RUNTIME_LIBRARY_PATH"
+ ;;
+ linux*)
+ DED_LD="$CC"
+ DED_LD_FLAG_RUNTIME_LIBRARY_PATH="$CFLAG_RUNTIME_LIBRARY_PATH"
+ DED_LDFLAGS="-shared -Wl,-Bsymbolic"
+ if test X${enable_m64_build} = Xyes; then
+ DED_LDFLAGS="-m64 $DED_LDFLAGS"
+ fi;
+ if test X${enable_m32_build} = Xyes; then
+ DED_LDFLAGS="-m32 $DED_LDFLAGS"
+ fi
+ ;;
+ freebsd*)
+ DED_LD="$CC"
+ DED_LD_FLAG_RUNTIME_LIBRARY_PATH="$CFLAG_RUNTIME_LIBRARY_PATH"
+ DED_LDFLAGS="-shared"
+ if test X${enable_m64_build} = Xyes; then
+ DED_LDFLAGS="-m64 $DED_LDFLAGS"
+ fi;
+ if test X${enable_m32_build} = Xyes; then
+ DED_LDFLAGS="-m32 $DED_LDFLAGS"
+ fi
+ ;;
+ openbsd*)
+ DED_LD="$CC"
+ DED_LD_FLAG_RUNTIME_LIBRARY_PATH="$CFLAG_RUNTIME_LIBRARY_PATH"
+ DED_LDFLAGS="-shared"
+ ;;
+ osf*)
+ # NOTE! Whitespace after -rpath is important.
+ DED_LD_FLAG_RUNTIME_LIBRARY_PATH="-rpath "
+ DED_LDFLAGS="-shared -expect_unresolved '*'"
+ ;;
+ *)
+ # assume GNU linker and ELF
+ DED_LDFLAGS="-shared"
+ # GNU linker has no option for 64bit build, should not propagate -m64
+ ;;
+esac
+
+if test "$DED_LD" = "" && test "$USER_LD" != ""; then
+ DED_LD="$USER_LD"
+ DED_LDFLAGS="$USER_LDFLAGS $DED_LDFLAGS"
+fi
+
+DED_LIBS=$LIBS
+
+fi # "x$DED_LD" = "x"
+
+AC_CHECK_TOOL(DED_LD, ld, false)
+test "$DED_LD" != "false" || AC_MSG_ERROR([No linker found])
+
+AC_MSG_CHECKING(for static compiler flags)
+DED_STATIC_CFLAGS="$DED_WERRORFLAGS $DED_WFLAGS $DED_THR_DEFS $DED_STATIC_CFLAGS"
+AC_MSG_RESULT([$DED_STATIC_CFLAGS])
+AC_MSG_CHECKING(for basic compiler flags for loadable drivers)
+DED_BASIC_CFLAGS=$DED_CFLAGS
+AC_MSG_RESULT([$DED_CFLAGS])
+AC_MSG_CHECKING(for compiler flags for loadable drivers)
+DED_CFLAGS="$DED_WERRORFLAGS $DED_WARN_FLAGS $DED_THR_DEFS $DED_CFLAGS"
+AC_MSG_RESULT([$DED_CFLAGS])
+AC_MSG_CHECKING(for linker for loadable drivers)
+AC_MSG_RESULT([$DED_LD])
+AC_MSG_CHECKING(for linker flags for loadable drivers)
+AC_MSG_RESULT([$DED_LDFLAGS])
+AC_MSG_CHECKING(for 'runtime library path' linker flag)
+if test "x$DED_LD_FLAG_RUNTIME_LIBRARY_PATH" != "x"; then
+ AC_MSG_RESULT([$DED_LD_FLAG_RUNTIME_LIBRARY_PATH])
+else
+ AC_MSG_RESULT([not found])
+fi
+
+AC_SUBST(DED_CC)
+AC_SUBST(DED_GCC)
+AC_SUBST(DED_EXT)
+AC_SUBST(DED_SYS_INCLUDE)
+AC_SUBST(DED_INCLUDE)
+AC_SUBST(DED_BASIC_CFLAGS)
+AC_SUBST(DED_CFLAGS)
+AC_SUBST(DED_STATIC_CFLAGS)
+AC_SUBST(DED_WARN_FLAGS)
+AC_SUBST(DED_WERRORFLAGS)
+AC_SUBST(DED_LD)
+AC_SUBST(DED_LDFLAGS)
+AC_SUBST(DED_LD_FLAG_RUNTIME_LIBRARY_PATH)
+AC_SUBST(DED_LIBS)
+AC_SUBST(DED_THR_DEFS)
+AC_SUBST(DED_OSTYPE)
+
+])
diff --git a/erts/autoconf/configure.vxworks b/erts/autoconf/configure.vxworks
index a253848403..1893f3f7e0 100755
--- a/erts/autoconf/configure.vxworks
+++ b/erts/autoconf/configure.vxworks
@@ -93,6 +93,7 @@ erts_lib_src=${ERL_TOP}/erts/lib_src
erts_incl=${ERL_TOP}/erts/include
erts_incl_intrnl=${ERL_TOP}/erts/include/internal
etcdir=${ERL_TOP}/erts/etc/common
+erlint_incl_dir=${ERL_TOP}/lib/erl_interface/include
erlint_dir=${ERL_TOP}/lib/erl_interface/src
epmd_dir=${ERL_TOP}/erts/epmd/src
os_mon_dir=${ERL_TOP}/lib/os_mon/c_src
@@ -109,6 +110,7 @@ CONFIG_FILES="${ERL_TOP}/erts/emulator/$host/Makefile
$erts_incl_intrnl/$host/ethread.mk
$erts_incl_intrnl/$host/ethread_header_config.h
$etcdir/$host/Makefile
+ $erlint_incl_dir/$host/ei_config.h
$erlint_dir/$host/Makefile
$erlint_dir/$host/eidefs.mk
$epmd_dir/$host/Makefile
diff --git a/erts/configure.in b/erts/configure.in
index 3ba8216a19..b070ad0649 100644
--- a/erts/configure.in
+++ b/erts/configure.in
@@ -26,11 +26,6 @@ AC_PREREQ(2.59)
LM_PRECIOUS_VARS
-if test "x$no_recursion" != "xyes" -a "x$OVERRIDE_CONFIG_CACHE" = "x"; then
- # We do not want to use a common cache!
- cache_file=/dev/null
-fi
-
dnl How to set srcdir absolute is taken from the GNU Emacs distribution
#### Make srcdir absolute, if it isn't already. It's important to
#### avoid running the path through pwd unnecessary, since pwd can
@@ -470,7 +465,10 @@ dnl
dnl Make sure we find config.h
dnl
-extra_flags="-I${ERL_TOP}/erts/$host $OTP_EXTRA_FLAGS"
+ERTS_CONFIG_H_IDIR="-I${ERL_TOP}/erts/$host"
+AC_SUBST(ERTS_CONFIG_H_IDIR)
+
+extra_flags="$ERTS_CONFIG_H_IDIR $OTP_EXTRA_FLAGS"
CFLAGS="$CFLAGS $extra_flags"
DEBUG_CFLAGS="-g $CPPFLAGS $extra_flags $DEBUG_CFLAGS"
DEBUG_FLAGS=-g
@@ -502,23 +500,6 @@ case $CFLAGS in
;;
esac
-
-
-CFLAG_RUNTIME_LIBRARY_PATH="-Wl,-R"
-case $host_os in
- darwin*)
- CFLAG_RUNTIME_LIBRARY_PATH=
- ;;
- win32)
- CFLAG_RUNTIME_LIBRARY_PATH=
- ;;
- osf*)
- CFLAG_RUNTIME_LIBRARY_PATH="-Wl,-rpath,"
- ;;
- *)
- ;;
-esac
-
lfs_conf=ok
lfs_source=none
if test "${LFS_CFLAGS+set}" = "set" || \
@@ -605,7 +586,6 @@ AC_SUBST(DEBUG_FLAGS)
AC_SUBST(DEBUG_CFLAGS)
AC_SUBST(WFLAGS)
AC_SUBST(WERRORFLAGS)
-AC_SUBST(CFLAG_RUNTIME_LIBRARY_PATH)
## Check if we can do profile guided optimization of beam_emu
LM_CHECK_ENABLE_CFLAG([-fprofile-generate -Werror],[PROFILE_GENERATE])
@@ -944,9 +924,6 @@ esac
AC_SUBST(LD)
-LDFLAG_RUNTIME_LIBRARY_PATH="$CFLAG_RUNTIME_LIBRARY_PATH"
-AC_SUBST(LDFLAG_RUNTIME_LIBRARY_PATH)
-
dnl Check for cygwin and object/exe files extension
dnl AC_CYGWIN is deprecated
AC_EXEEXT
@@ -1518,7 +1495,7 @@ dnl Some Linuxes needs <sys/socketio.h> instead of <sys/sockio.h>
dnl
AC_CHECK_HEADERS(fcntl.h limits.h unistd.h syslog.h dlfcn.h ieeefp.h \
sys/types.h sys/stropts.h sys/sysctl.h \
- sys/ioctl.h sys/time.h sys/uio.h \
+ sys/ioctl.h sys/time.h sys/uio.h sys/mman.h \
sys/socket.h sys/sockio.h sys/socketio.h \
net/errno.h malloc.h arpa/nameser.h libdlpi.h \
pty.h util.h libutil.h utmp.h langinfo.h poll.h sdkddkver.h)
@@ -1673,7 +1650,9 @@ if test x"$ac_cv_header_netinet_sctp_h" = x"yes"; then
struct sctp_paddrparams.spp_sackdelay,
struct sctp_paddrparams.spp_flags,
struct sctp_remote_error.sre_data,
- struct sctp_send_failed.ssf_data], [], [],
+ struct sctp_send_failed.ssf_data,
+ struct sctp_event_subscribe.sctp_authentication_event,
+ struct sctp_event_subscribe.sctp_sender_dry_event], [], [],
[#if HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
@@ -2069,29 +2048,6 @@ esac
AC_CHECK_DECLS([posix2time, time2posix],,,[#include <time.h>])
-disable_vfork=false
-if test "x$EMU_THR_LIB_NAME" != "x"; then
- AC_MSG_CHECKING([if vfork is known to hang multithreaded applications])
- case $host_os in
- osf*)
- AC_MSG_RESULT(yes)
- disable_vfork=true;;
- *)
- AC_MSG_RESULT(no);;
- esac
-fi
-
-if test $disable_vfork = false; then
- AC_FUNC_VFORK
- if test $ac_cv_func_vfork_works = no; then
- disable_vfork=true
- fi
-fi
-
-if test $disable_vfork = true; then
- AC_DEFINE(DISABLE_VFORK, 1, [Define if you want to disable vfork.])
-fi
-
AC_FUNC_VPRINTF
dnl The AC_DEFINEs are necessary for autoheader to work. :-(
@@ -3028,165 +2984,6 @@ dnl ----------------------------------------------------------------------
dnl Stuff that should be moved into their respective application
dnl ----------------------------------------------------------------------
-dnl crypto
-#--------------------------------------------------------------------
-# Dynamic Erlang Drivers
-#
-# Linking to produce dynamic Erlang drivers to be loaded by Erlang's
-# Dynamic Driver Loader and Linker (DDLL). Below the prefix DED is an
-# abbreviation for `Dynamic Erlang Driver'.
-#
-# For DED we need something quite sloppy, which allows undefined references
-# (notably driver functions) in the resulting shared library.
-# Example of Makefile rule (and settings of macros):
-#
-# LIBS = @LIBS@
-# LD = @DED_LD@
-# LDFLAGS = @DED_LDFLAGS@
-# soname = @ldsoname@
-#
-# my_drv.so: my_drv.o my_utils.o
-# $(LD) $(LDFLAGS) $(soname) $@ -o $@ $^ -lc $(LIBS)
-#
-#--------------------------------------------------------------------
-
-DED_SYS_INCLUDE="-I${ERL_TOP}/erts/emulator/beam -I${ERL_TOP}/erts/include -I${ERL_TOP}/erts/include/$host -I${ERL_TOP}/erts/include/internal -I${ERL_TOP}/erts/include/internal/$host -I${ERL_TOP}/erts/emulator/sys/$ERLANG_OSTYPE -I${ERL_TOP}/erts/emulator/sys/common"
-
-if test "X$ETHR_DEFS" = "X"; then
- DED_THR_DEFS="-D_THREAD_SAFE -D_REENTRANT"
-else
- DED_THR_DEFS="$ETHR_DEFS"
-fi
-DED_EMU_THR_DEFS=$EMU_THR_DEFS
-DED_CFLAGS="$CFLAGS $CPPFLAGS"
-if test "x$GCC" = xyes; then
- DED_STATIC_CFLAGS="$DED_CFLAGS"
- DED_CFLAGS="$DED_CFLAGS -fPIC"
-fi
-
-DED_EXT=so
-case $host_os in
- win32) DED_EXT=dll;;
- darwin*)
- DED_CFLAGS="$DED_CFLAGS -fno-common"
- DED_STATIC_CFLAGS="$DED_STATIC_CFLAGS -fno-common";;
- *)
- ;;
-esac
-
-DED_STATIC_CFLAGS="$DED_STATIC_CFLAGS -DSTATIC_ERLANG_NIF -DSTATIC_ERLANG_DRIVER"
-
-# If DED_LD is set in environment, we expect all DED_LD* variables
-# to be specified (cross compiling)
-if test "x$DED_LD" = "x"; then
-
-DED_LD_FLAG_RUNTIME_LIBRARY_PATH="-R"
-case $host_os in
- win32)
- DED_LD="ld.sh"
- DED_LDFLAGS="-dll"
- DED_LD_FLAG_RUNTIME_LIBRARY_PATH=
- ;;
- solaris2*|sysv4*)
- DED_LDFLAGS="-G"
- if test X${enable_m64_build} = Xyes; then
- DED_LDFLAGS="-64 $DED_LDFLAGS"
- fi
- ;;
- aix4*)
- DED_LDFLAGS="-G -bnoentry -bexpall"
- ;;
- freebsd2*)
- # Non-ELF GNU linker
- DED_LDFLAGS="-Bshareable"
- ;;
- darwin*)
- # Mach-O linker: a shared lib and a loadable
- # object file is not the same thing.
- DED_LDFLAGS="-bundle -bundle_loader ${ERL_TOP}/bin/$host/beam.smp"
- case $ARCH in
- amd64)
- DED_LDFLAGS="-m64 $DED_LDFLAGS"
- ;;
- *)
- ;;
- esac
- DED_LD="$CC"
- DED_LD_FLAG_RUNTIME_LIBRARY_PATH="$CFLAG_RUNTIME_LIBRARY_PATH"
- ;;
- linux*)
- DED_LD="$CC"
- DED_LD_FLAG_RUNTIME_LIBRARY_PATH="$CFLAG_RUNTIME_LIBRARY_PATH"
- DED_LDFLAGS="-shared -Wl,-Bsymbolic"
- if test X${enable_m64_build} = Xyes; then
- DED_LDFLAGS="-m64 $DED_LDFLAGS"
- fi;
- if test X${enable_m32_build} = Xyes; then
- DED_LDFLAGS="-m32 $DED_LDFLAGS"
- fi
- ;;
- freebsd*)
- DED_LD="$CC"
- DED_LD_FLAG_RUNTIME_LIBRARY_PATH="$CFLAG_RUNTIME_LIBRARY_PATH"
- DED_LDFLAGS="-shared"
- if test X${enable_m64_build} = Xyes; then
- DED_LDFLAGS="-m64 $DED_LDFLAGS"
- fi;
- if test X${enable_m32_build} = Xyes; then
- DED_LDFLAGS="-m32 $DED_LDFLAGS"
- fi
- ;;
- openbsd*)
- DED_LD="$CC"
- DED_LD_FLAG_RUNTIME_LIBRARY_PATH="$CFLAG_RUNTIME_LIBRARY_PATH"
- DED_LDFLAGS="-shared"
- ;;
- osf*)
- # NOTE! Whitespace after -rpath is important.
- DED_LD_FLAG_RUNTIME_LIBRARY_PATH="-rpath "
- DED_LDFLAGS="-shared -expect_unresolved '*'"
- ;;
- *)
- # assume GNU linker and ELF
- DED_LDFLAGS="-shared"
- # GNU linker has no option for 64bit build, should not propagate -m64
- ;;
-esac
-
-if test "$DED_LD" = "" && test "$USER_LD" != ""; then
- DED_LD="$USER_LD"
- DED_LDFLAGS="$USER_LDFLAGS $DED_LDFLAGS"
-fi
-
-fi # "x$DED_LD" = "x"
-
-AC_CHECK_TOOL(DED_LD, ld, false)
-test "$DED_LD" != "false" || AC_MSG_ERROR([No linker found])
-
-AC_MSG_CHECKING(for compiler flags for loadable drivers)
-AC_MSG_RESULT([$DED_CFLAGS])
-AC_MSG_CHECKING(for linker for loadable drivers)
-AC_MSG_RESULT([$DED_LD])
-AC_MSG_CHECKING(for linker flags for loadable drivers)
-AC_MSG_RESULT([$DED_LDFLAGS])
-AC_MSG_CHECKING(for 'runtime library path' linker flag)
-if test "x$DED_LD_FLAG_RUNTIME_LIBRARY_PATH" != "x"; then
- AC_MSG_RESULT([$DED_LD_FLAG_RUNTIME_LIBRARY_PATH])
-else
- AC_MSG_RESULT([not found])
-fi
-
-AC_SUBST(DED_EXT)
-AC_SUBST(DED_SYS_INCLUDE)
-AC_SUBST(DED_CFLAGS)
-AC_SUBST(DED_STATIC_CFLAGS)
-AC_SUBST(DED_LD)
-AC_SUBST(DED_LDFLAGS)
-AC_SUBST(DED_LD_FLAG_RUNTIME_LIBRARY_PATH)
-AC_SUBST(DED_THR_DEFS)
-AC_SUBST(DED_EMU_THR_DEFS)
-AC_SUBST(STATIC_CFLAGS)
-
dnl
dnl We should look for a compiler that handles jump tables, for beam_emu
dnl to be optimized
@@ -3343,733 +3140,6 @@ if test "$enable_lttng_test" = "yes" ; then
fi
-dnl
-dnl SSL, SSH and CRYPTO need the OpenSSL libraries
-dnl
-dnl Check flags --with-ssl, --without-ssl --with-ssl=PATH.
-dnl If no option is given or --with-ssl is set without a path then we
-dnl search for OpenSSL libraries and header files in the standard locations.
-dnl If set to --without-ssl we disable the use of SSL, SSH and CRYPTO.
-dnl If set to --with-ssl=PATH we use that path as the prefix, i.e. we
-dnl use "PATH/include" and "PATH/lib".
-
-AC_SUBST(SSL_INCLUDE)
-AC_SUBST(SSL_INCDIR)
-AC_SUBST(SSL_LIBDIR)
-AC_SUBST(SSL_FLAGS)
-AC_SUBST(SSL_CRYPTO_LIBNAME)
-AC_SUBST(SSL_SSL_LIBNAME)
-AC_SUBST(SSL_CC_RUNTIME_LIBRARY_PATH)
-AC_SUBST(SSL_LD_RUNTIME_LIBRARY_PATH)
-AC_SUBST(SSL_DED_LD_RUNTIME_LIBRARY_PATH)
-AC_SUBST(SSL_DYNAMIC_ONLY)
-AC_SUBST(SSL_LINK_WITH_KERBEROS)
-AC_SUBST(STATIC_KERBEROS_LIBS)
-AC_SUBST(SSL_LINK_WITH_ZLIB)
-AC_SUBST(STATIC_ZLIB_LIBS)
-
-std_ssl_locations="/usr/local /usr/sfw /usr /opt/local /usr/pkg /usr/local/openssl /usr/lib/openssl /usr/openssl /usr/local/ssl /usr/lib/ssl /usr/ssl /"
-
-AC_ARG_WITH(ssl-zlib,
-AS_HELP_STRING([--with-ssl-zlib=PATH],
- [specify location of ZLib to be used by OpenSSL])
-AS_HELP_STRING([--with-ssl-zlib],
- [link SSL with Zlib (default if found)])
-AS_HELP_STRING([--without-ssl-zlib],
- [don't link SSL with ZLib]))
-
-
-if test "x$with_ssl_zlib" = "xno"; then
- SSL_LINK_WITH_ZLIB=no
- STATIC_ZLIB_LIBS=
-elif test "x$with_ssl_zlib" = "xyes" || test "x$with_ssl_zlib" = "x"; then
- if test $erl_xcomp_without_sysroot = yes; then
- AC_MSG_WARN([Cannot search for zlib; missing cross system root (erl_xcomp_sysroot).])
- SSL_LINK_WITH_ZLIB=no
- STATIC_ZLIB_LIBS=
- elif test "x$MIXED_CYGWIN" = "xyes" -o "x$MIXED_MSYS" = "xyes"; then
- SSL_LINK_WITH_ZLIB=no
- STATIC_ZLIB_LIBS=
- else
- SSL_LINK_WITH_ZLIB=no
- STATIC_ZLIB_LIBS=
- AC_MSG_CHECKING(for static ZLib to be used by SSL in standard locations)
- for rdir in $std_ssl_locations; do
- dir="$erl_xcomp_sysroot$rdir"
- if test "x$ac_cv_sizeof_void_p" = "x8"; then
- if test -f "$dir/lib64/libz.a"; then
- SSL_LINK_WITH_ZLIB=yes
- STATIC_ZLIB_LIBS="$dir/lib64/libz.a"
- break
- elif test -f "$dir/lib/64/libz.a"; then
- SSL_LINK_WITH_ZLIB=yes
- STATIC_ZLIB_LIBS="$dir/lib/64/libz.a"
- break
- fi
- fi
- if test -f "$dir/lib/libz.a"; then
- SSL_LINK_WITH_ZLIB=yes
- STATIC_ZLIB_LIBS="$dir/lib/libz.a"
- break
- fi
- done
- if test "x$SSL_LINK_WITH_ZLIB" = "xno"; then
- AC_MSG_RESULT([no])
- else
- AC_MSG_RESULT([$STATIC_ZLIB_LIBS])
- fi
- fi
-else
- SSL_LINK_WITH_ZLIB=no
- STATIC_ZLIB_LIBS=
- if test -f "$with_ssl_zlib/libz.a"; then
- SSL_LINK_WITH_ZLIB=yes
- STATIC_ZLIB_LIBS=$with_ssl_zlib/libz.a
- elif test -f "$with_ssl_zlib/lib/libz.a"; then
- SSL_LINK_WITH_ZLIB=yes
- STATIC_ZLIB_LIBS=$with_ssl_zlib/lib/libz.a
- fi
- if test "x$ac_cv_sizeof_void_p" = "x8"; then
- if test -f "$with_ssl_zlib/lib64/libz.a"; then
- SSL_LINK_WITH_ZLIB=yes
- STATIC_ZLIB_LIBS=$with_ssl_zlib/lib64/libz.a
- elif test -f "$with_ssl_zlib/lib/64/libz.a"; then
- SSL_LINK_WITH_ZLIB=yes
- STATIC_ZLIB_LIBS=$with_ssl_zlib/lib/64/libz.a
- fi
- fi
- if test "x$SSL_LINK_WITH_ZLIB" = "xno"; then
- AC_MSG_ERROR(Invalid path to option --with-ssl-zlib=PATH)
- fi
-fi
-
-
-AC_ARG_WITH(ssl,
-AS_HELP_STRING([--with-ssl=PATH], [specify location of OpenSSL include and lib])
-AS_HELP_STRING([--with-ssl], [use SSL (default)])
-AS_HELP_STRING([--without-ssl], [don't use SSL]))
-
-AC_ARG_WITH(ssl-incl,
-AS_HELP_STRING([--with-ssl-incl=PATH], [location of OpenSSL include dir, if different than specified by --with-ssl=PATH]),
-[
-case X$with_ssl in
- X | Xyes | Xno) AC_MSG_ERROR([--with-ssl-incl=PATH set without --with-ssl=PATH]);;
-esac
-],
-[with_ssl_incl=$with_ssl]) #default
-
-AC_ARG_WITH(ssl-rpath,
-AS_HELP_STRING([--with-ssl-rpath=yes|no|PATHS],
- [runtime library path for OpenSSL. Default is "yes", which equates to a
- number of standard locations. If "no", then no runtime
- library paths will be used. Anything else should be a
- comma separated list of paths.]),
-[
-case X$with_ssl in
- Xno) AC_MSG_ERROR([--with-ssl-rpath set without --with-ssl]);;
-esac
-],
-[with_ssl_rpath=yes]) #default
-
-
-AC_ARG_ENABLE(dynamic-ssl-lib,
-AS_HELP_STRING([--disable-dynamic-ssl-lib],
- [disable using dynamic openssl libraries]),
-[ case "$enableval" in
- no) enable_dynamic_ssl=no ;;
- *) enable_dynamic_ssl=yes ;;
- esac ], enable_dynamic_ssl=yes)
-
-#----------------------------------------------------------------------
-# We actually might do the SSL tests twice due to late discovery of
-# kerberos problems with static linking, in case we redo it all trying
-# dynamic SSL libraries instead.
-#----------------------------------------------------------------------
-
-ssl_done=no
-
-while test "x$ssl_done" != "xyes"; do
-
-ssl_done=yes # Default only one run
-
-# Remove all SKIP files from previous runs
-for a in ssl crypto ssh; do
- rm -f $ERL_TOP/lib/$a/SKIP
-done
-
-SSL_DYNAMIC_ONLY=$enable_dynamic_ssl
-SSL_STATIC_ONLY=no
-
-case "$erl_xcomp_without_sysroot-$with_ssl" in
- yes-* | no-no)
- SSL_APP=
- CRYPTO_APP=
- SSH_APP=
- if test "$with_ssl" = "no"; then
- skip="User gave --without-ssl option"
- else
- skip="Cannot search for ssl; missing cross system root (erl_xcomp_sysroot)."
- fi
- for a in ssl crypto ssh; do
- echo "$skip" > $ERL_TOP/lib/$a/SKIP
- done
- ;;
- no-yes | no- )
- # On windows, we could try to find the installation
- # of Shining Light OpenSSL, which can be found by poking in
- # the uninstall section in the registry, it's worth a try...
- extra_dir=""
- if test "x$MIXED_CYGWIN" = "xyes"; then
- AC_CHECK_PROG(REGTOOL, regtool, regtool, false)
- if test "$ac_cv_prog_REGTOOL" != false; then
- wrp="/machine/software/microsoft/windows/currentversion/"
- if test "x$ARCH" = "xamd64"; then
- urp="uninstall/openssl (64-bit)_is1/inno setup: app path"
- regtool_subsystem=-w
- else
- urp="uninstall/openssl (32-bit)_is1/inno setup: app path"
- regtool_subsystem=-W
- fi
- rp="$wrp$urp"
- if regtool -q $regtool_subsystem get "$rp" > /dev/null; then
- true
- else
- # Fallback to unspecified wordlength
- urp="uninstall/openssl_is1/inno setup: app path"
- rp="$wrp$urp"
- fi
- if regtool -q $regtool_subsystem get "$rp" > /dev/null; then
- ssl_install_dir=`regtool -q $regtool_subsystem get "$rp"`
- # Try hard to get rid of spaces...
- if cygpath -d "$ssl_install_dir" > /dev/null 2>&1; then
- ssl_install_dir=`cygpath -d "$ssl_install_dir"`
- fi
- extra_dir=`cygpath $ssl_install_dir`
- fi
- fi
- elif test "x$MIXED_MSYS" = "xyes"; then
- AC_CHECK_PROG(REGTOOL, reg_query.sh, reg_query.sh, false)
- if test "$ac_cv_prog_REGTOOL" != false; then
- if test "x$ARCH" = "xamd64"; then
- rp="HKLM/SOFTWARE/Microsoft/Windows/CurrentVersion/Uninstall/OpenSSL (64-bit)_is1"
- else
- rp="HKLM/SOFTWARE/Microsoft/Windows/CurrentVersion/Uninstall/OpenSSL_is1"
- fi
- key="Inno Setup: App Path"
- if "$ac_cv_prog_REGTOOL" "$rp" "$key" > /dev/null; then
- ssl_install_dir=`"$ac_cv_prog_REGTOOL" "$rp" "$key"`
- extra_dir=`win2msys_path.sh "$ssl_install_dir"`
- fi
- fi
- fi
- # We search for OpenSSL in the common OS standard locations.
- SSL_APP=ssl
- CRYPTO_APP=crypto
- SSH_APP=ssh
-
- SSL_CRYPTO_LIBNAME=crypto
- SSL_SSL_LIBNAME=ssl
-
- if test "x$MIXED_CYGWIN" = "xyes" -o "x$MIXED_MSYS" = "xyes"; then
- if test "x$ARCH" = "xamd64"; then
- std_win_ssl_locations="/cygdrive/c/OpenSSL-Win64 /c/OpenSSL-Win64 /opt/local64/pgm/OpenSSL"
- else
- std_win_ssl_locations="/cygdrive/c/OpenSSL-Win32 /c/OpenSSL-Win32 /cygdrive/c/OpenSSL /c/OpenSSL /opt/local/pgm/OpenSSL"
- fi
- else
- std_win_ssl_locations=""
- fi
-
-
- AC_MSG_CHECKING(for OpenSSL >= 0.9.8c in standard locations)
- for rdir in $extra_dir $std_win_ssl_locations $std_ssl_locations; do
- dir="$erl_xcomp_sysroot$rdir"
- if test -f "$erl_xcomp_isysroot$rdir/include/openssl/opensslv.h"; then
- is_real_ssl=yes
- SSL_INCDIR="$dir"
- if test "x$MIXED_CYGWIN" = "xyes" -o "x$MIXED_MSYS" = "xyes"; then
- if test -f "$dir/lib/VC/libeay32.lib"; then
- SSL_RUNTIME_LIBDIR="$rdir/lib/VC"
- SSL_LIBDIR="$dir/lib/VC"
- SSL_CRYPTO_LIBNAME=libeay32
- SSL_SSL_LIBNAME=ssleay32
- elif test -f "$dir/lib/VC/openssl.lib"; then
- SSL_RUNTIME_LIBDIR="$rdir/lib/VC"
- SSL_LIBDIR="$dir/lib/VC"
- elif test -f $dir/lib/VC/libeay32MD.lib; then
- SSL_CRYPTO_LIBNAME=libeay32MD
- SSL_SSL_LIBNAME=ssleay32MD
- if test "x$enable_dynamic_ssl" = "xno" && \
- test -f $dir/lib/VC/static/libeay32MD.lib; then
- SSL_RUNTIME_LIBDIR="$rdir/lib/VC/static"
- SSL_LIBDIR="$dir/lib/VC/static"
- else
- SSL_RUNTIME_LIBDIR="$rdir/lib/VC"
- SSL_LIBDIR="$dir/lib/VC"
- fi
- elif test -f "$dir/lib/libeay32.lib"; then
- SSL_RUNTIME_LIBDIR="$rdir/lib"
- SSL_LIBDIR="$dir/lib"
- SSL_CRYPTO_LIBNAME=libeay32
- SSL_SSL_LIBNAME=ssleay32
- elif test -f "$dir/lib/openssl.lib"; then
- SSL_RUNTIME_LIBDIR="$rdir/lib"
- SSL_LIBDIR="$dir/lib"
- else
- is_real_ssl=no
- fi
- elif test -f "$dir/lib/powerpc/libsslcrypto.a"; then
- SSL_CRYPTO_LIBNAME=sslcrypto
- SSL_LIBDIR="$dir/lib/powerpc/"
- SSL_RUNTIME_LIBDIR="$rdir/lib/powerpc/"
- else
- if test "x$ac_cv_sizeof_void_p" = "x8"; then
- if test -f "$dir/lib64/libcrypto.a"; then
- SSL_RUNTIME_LIBDIR="$rdir/lib64"
- SSL_LIBDIR="$dir/lib64"
- elif test -f "$dir/lib/64/libcrypto.a"; then
- SSL_RUNTIME_LIBDIR="$rdir/lib/64"
- SSL_LIBDIR="$dir/lib/64"
- elif test -f "$dir/lib64/libcrypto.so"; then
- SSL_RUNTIME_LIBDIR="$rdir/lib64"
- SSL_LIBDIR="$dir/lib64"
- elif test -f "$dir/lib/64/libcrypto.so"; then
- SSL_RUNTIME_LIBDIR="$rdir/lib/64"
- SSL_LIBDIR="$dir/lib/64"
- else
- SSL_RUNTIME_LIBDIR="$rdir/lib"
- SSL_LIBDIR="$dir/lib"
- fi
- else
- SSL_RUNTIME_LIBDIR="$rdir/lib"
- SSL_LIBDIR="$dir/lib"
- fi
- fi
- if test '!' -f "$SSL_LIBDIR/lib${SSL_CRYPTO_LIBNAME}.a"; then
- SSL_DYNAMIC_ONLY=yes
- elif test '!' -f "$SSL_LIBDIR/lib${SSL_CRYPTO_LIBNAME}.so" -a '!' -f "$SSL_LIBDIR/lib${SSL_CRYPTO_LIBNAME}.dylib"; then
- SSL_STATIC_ONLY=yes
- fi
- SSL_BINDIR="$rdir/bin"
- if test "x$is_real_ssl" = "xyes" ; then
- SSL_INCLUDE="-I$dir/include"
- old_CPPFLAGS=$CPPFLAGS
- CPPFLAGS=$SSL_INCLUDE
- AC_EGREP_CPP(^yes$,[
-#include <openssl/opensslv.h>
-#if OPENSSL_VERSION_NUMBER >= 0x0090803fL
-yes
-#endif
- ],[
- ssl_found=yes
- ],[
- SSL_APP=
- ssl_found=no
- ])
- CPPFLAGS=$old_CPPFLAGS
- if test "x$ssl_found" = "xyes"; then
- if test "x$MIXED_CYGWIN" = "xyes" -o "x$MIXED_MSYS" = "xyes"; then
- ssl_linkable=yes
- elif test "x${SSL_CRYPTO_LIBNAME}" = "xsslcrypto"; then
- # This should only be triggered seen OSE
- ssl_linkable=yes
- else
- saveCFLAGS="$CFLAGS"
- saveLDFLAGS="$LDFLAGS"
- saveLIBS="$LIBS"
- CFLAGS="$CFLAGS $SSL_INCLUDE"
- if test "x$SSL_STATIC_ONLY" = "xyes"; then
- LIBS="${SSL_LIBDIR}/lib${SSL_CRYPTO_LIBNAME}.a"
- else
- LDFLAGS="$LDFLAGS -L$SSL_LIBDIR"
- LIBS="$LIBS -l${SSL_CRYPTO_LIBNAME}"
- fi
- AC_TRY_LINK([
- #include <stdio.h>
- #include <openssl/hmac.h>],
- [
- HMAC(0, 0, 0, 0, 0, 0, 0);
- ],
- [ssl_linkable=yes],
- [ssl_linkable=no])
- CFLAGS="$saveCFLAGS"
- LDFLAGS="$saveLDFLAGS"
- LIBS="$saveLIBS"
- fi
- fi
- if test "x$ssl_found" = "xyes" && test "x$ssl_linkable" = "xyes"; then
- AC_MSG_RESULT([$dir])
- break;
- fi
- fi
- fi
- done
-
- if test "x$ssl_found" != "xyes" ; then
- dnl
- dnl If no SSL found above, check whether we are running on OpenBSD.
- dnl
- case $host_os in
- openbsd*)
- if test -f "$erl_xcomp_isysroot/usr/include/openssl/opensslv.h"; then
- # Trust OpenBSD to have everything the in the correct locations.
- ssl_found=yes
- ssl_linkable=yes
- SSL_INCDIR="$erl_xcomp_sysroot/usr"
- AC_MSG_RESULT([$SSL_INCDIR])
- SSL_RUNTIME_LIB="/usr/lib"
- SSL_LIB="$erl_xcomp_sysroot/usr/lib"
- SSL_BINDIR="/usr/sbin"
- dnl OpenBSD requires us to link with -L and -l
- SSL_DYNAMIC_ONLY="yes"
- fi
- ;;
- esac
- fi
-dnl Now, certain linuxes have a 64bit libcrypto
-dnl that cannot build shared libraries (i.e. not PIC)
-dnl One could argue that this is wrong, but
-dnl so it is - be adoptable
- if test "$ssl_found" = "yes" && test "$ssl_linkable" = "yes" && test "$SSL_DYNAMIC_ONLY" != "yes"; then
- case $host_os in
- linux*)
- saveCFLAGS="$CFLAGS"
- saveLDFLAGS="$LDFLAGS"
- saveLIBS="$LIBS"
- CFLAGS="$DED_CFLAGS $SSL_INCLUDE"
- LDFLAGS="$DED_LDFLAGS"
- LIBS="$SSL_LIBDIR/libcrypto.a $STATIC_ZLIB_LIBS"
- AC_TRY_LINK([
- #include <stdio.h>
- #include <openssl/hmac.h>],
- [
- HMAC(0, 0, 0, 0, 0, 0, 0);
- ],
- [ssl_dyn_linkable=yes],
- [ssl_dyn_linkable=no])
- CFLAGS="$saveCFLAGS"
- LDFLAGS="$saveLDFLAGS"
- LIBS="$saveLIBS"
- if test "x$ssl_dyn_linkable" != "xyes"; then
- SSL_DYNAMIC_ONLY=yes
- AC_MSG_WARN([SSL will be linked against dynamic lib as static lib is not purely relocatable])
- fi
- ;;
- esac
- fi
-
-
-
-
- if test "x$ssl_found" != "xyes" || test "x$ssl_linkable" != "xyes"; then
- if test "x$ssl_found" = "xyes"; then
- AC_MSG_RESULT([found; but not usable])
- else
- AC_MSG_RESULT([no])
- fi
- SSL_APP=
- CRYPTO_APP=
- SSH_APP=
- AC_MSG_WARN([No (usable) OpenSSL found, skipping ssl, ssh and crypto applications])
-
- for a in ssl crypto ssh; do
- echo "No usable OpenSSL found" > $ERL_TOP/lib/$a/SKIP
- done
- fi
- ;;
- *)
- # Option given with PATH to package
- if test ! -d "$with_ssl" ; then
- AC_MSG_ERROR(Invalid path to option --with-ssl=PATH)
- fi
- if test ! -d "$with_ssl_incl" ; then
- AC_MSG_ERROR(Invalid path to option --with-ssl-incl=PATH)
- fi
- SSL_INCDIR="$with_ssl_incl"
- SSL_CRYPTO_LIBNAME=crypto
- SSL_SSL_LIBNAME=ssl
- if test "x$MIXED_CYGWIN" = "xyes" -o "x$MIXED_MSYS" = "xyes" && test -d "$with_ssl/lib/VC"; then
- if test -f "$with_ssl/lib/VC/libeay32.lib"; then
- SSL_LIBDIR="$with_ssl/lib/VC"
- SSL_CRYPTO_LIBNAME=libeay32
- SSL_SSL_LIBNAME=ssleay32
- elif test -f "$with_ssl/lib/VC/openssl.lib"; then
- SSL_LIBDIR="$with_ssl/lib/VC"
- elif test -f $with_ssl/lib/VC/libeay32MD.lib; then
- SSL_CRYPTO_LIBNAME=libeay32MD
- SSL_SSL_LIBNAME=ssleay32MD
- if test "x$enable_dynamic_ssl" = "xno" && \
- test -f $with_ssl/lib/VC/static/libeay32MD.lib; then
- SSL_LIBDIR="$with_ssl/lib/VC/static"
- else
- SSL_LIBDIR="$with_ssl/lib/VC"
- fi
- elif test -f "$with_ssl/lib/libeay32.lib"; then
- SSL_LIBDIR="$with_ssl/lib"
- SSL_CRYPTO_LIBNAME=libeay32
- SSL_SSL_LIBNAME=ssleay32
- else
- # This probably wont work, but that's what the user said, so...
- SSL_LIBDIR="$with_ssl/lib"
- fi
- elif test -f "$dir/lib/powerpc/libsslcrypto.a"; then
- SSL_CRYPTO_LIBNAME=sslcrypto
- SSL_LIBDIR="$with_ssl/lib/powerpc/"
- elif test "x$ac_cv_sizeof_void_p" = "x8"; then
- if test -f "$with_ssl/lib64/libcrypto.a"; then
- SSL_LIBDIR="$with_ssl/lib64"
- elif test -f "$with_ssl/lib/64/libcrypto.a"; then
- SSL_LIBDIR="$with_ssl/lib/64"
- elif test -f "$with_ssl/lib64/libcrypto.so"; then
- SSL_LIBDIR="$with_ssl/lib64"
- elif test -f "$with_ssl/lib/64/libcrypto.so"; then
- SSL_LIBDIR="$with_ssl/lib/64"
- else
- SSL_LIBDIR="$with_ssl/lib"
- fi
- else
- SSL_LIBDIR="$with_ssl/lib"
- fi
- if test '!' -f "${SSL_LIBDIR}/lib${SSL_CRYPTO_LIBNAME}.a"; then
- SSL_DYNAMIC_ONLY=yes
- elif test '!' -f ${SSL_LIBDIR}/lib${SSL_CRYPTO_LIBNAME}.so -a '!' -f "$SSL_LIBDIR/lib${SSL_CRYPTO_LIBNAME}.dylib"; then
- SSL_STATIC_ONLY=yes
- fi
- SSL_INCLUDE="-I$with_ssl_incl/include"
- SSL_APP=ssl
- CRYPTO_APP=crypto
- SSH_APP=ssh
- if test "$cross_compiling" = "yes"; then
- SSL_RUNTIME_LIBDIR=`echo "$SSL_LIBDIR" | sed -n "s|^$erl_xcomp_sysroot\(/*\)\(.*\)\$|/\2|p"`
- else
- SSL_RUNTIME_LIBDIR="$SSL_LIBDIR"
- fi
-esac
-
-if test "x$SSL_APP" != "x" ; then
- dnl We found openssl, now check if we use kerberos 5 support
- dnl FIXME: Do we still support platforms that have Kerberos?
- AC_MSG_CHECKING(for OpenSSL kerberos 5 support)
- old_CPPFLAGS=$CPPFLAGS
- CPPFLAGS=$SSL_INCLUDE
- AC_EGREP_CPP(^yes$,[
-#include <openssl/opensslv.h>
-#include <openssl/opensslconf.h>
-#if OPENSSL_VERSION_NUMBER < 0x1010000fL && !defined(OPENSSL_NO_KRB5)
-yes
-#endif
- ],[
- AC_MSG_RESULT([yes])
- ssl_krb5_enabled=yes
- if test "x$SSL_DYNAMIC_ONLY" != "xyes"; then
- if test -f "$SSL_LIBDIR/libkrb5.a"; then
- SSL_LINK_WITH_KERBEROS=yes
- STATIC_KERBEROS_LIBS="$SSL_LIBDIR/libkrb5.a"
- if test -f "$SSL_LIBDIR/libkrb5support.a"; then
- STATIC_KERBEROS_LIBS="$STATIC_KERBEROS_LIBS $SSL_LIBDIR/libkrb5support.a"
- fi
- if test -f "$SSL_LIBDIR/libk5crypto.a"; then
- STATIC_KERBEROS_LIBS="$STATIC_KERBEROS_LIBS $SSL_LIBDIR/libk5crypto.a"
- fi
- if test -f "$SSL_LIBDIR/libresolv.a"; then
- STATIC_KERBEROS_LIBS="$STATIC_KERBEROS_LIBS $SSL_LIBDIR/libresolv.a"
- fi
- if test -f "$SSL_LIBDIR/libcom_err.a"; then
- STATIC_KERBEROS_LIBS="$STATIC_KERBEROS_LIBS $SSL_LIBDIR/libcom_err.a"
- fi
- else
- AC_MSG_WARN([Kerberos needed but no kerberos static libraries found])
- AC_MSG_WARN([Rescanning for dynamic SSL libraries])
- enable_dynamic_ssl=yes
- ssl_done=no
- SSL_LINK_WITH_KERBEROS=no
- STATIC_KERBEROS_LIBS=""
- ssl_krb5_enabled=no
- SSL_WITH_KERBEROS=no
- fi
- else
- SSL_LINK_WITH_KERBEROS=no
- STATIC_KERBEROS_LIBS=""
- fi
- ],[
- AC_MSG_RESULT([no])
- ssl_krb5_enabled=no
- SSL_WITH_KERBEROS=no
- ])
- CPPFLAGS=$old_CPPFLAGS
- SSL_KRB5_INCLUDE=
- if test "x$ssl_krb5_enabled" = "xyes" ; then
- AC_MSG_CHECKING(for krb5.h in standard locations)
- for dir in $extra_dir "$SSL_INCDIR/include" "$SSL_INCDIR/include/openssl" \
- "$SSL_INCDIR/include/kerberos" \
- "$erl_xcomp_isysroot/cygdrive/c/kerberos/include" \
- "$erl_xcomp_isysroot/usr/local/kerberos/include" \
- "$erl_xcomp_isysroot/usr/kerberos/include" \
- "$erl_xcomp_isysroot/usr/include"
- do
- if test -f "$dir/krb5.h" ; then
- SSL_KRB5_INCLUDE="$dir"
- break
- fi
- done
- if test "x$SSL_KRB5_INCLUDE" = "x" ; then
- AC_MSG_RESULT([not found])
- SSL_APP=
- CRYPTO_APP=
- SSH_APP=
- AC_MSG_WARN([OpenSSL is configured for kerberos but no krb5.h found])
- for a in ssl crypto ssh ; do
- echo "OpenSSL is configured for kerberos but no krb5.h found" > $ERL_TOP/lib/$a/SKIP
- done
- else
- AC_MSG_RESULT([found in $SSL_KRB5_INCLUDE])
- SSL_INCLUDE="$SSL_INCLUDE -I$SSL_KRB5_INCLUDE"
- fi
- fi
-fi
-
-done # while test ssl_done != yes
-
-SSL_CC_RUNTIME_LIBRARY_PATH=
-SSL_LD_RUNTIME_LIBRARY_PATH=
-SSL_DED_LD_RUNTIME_LIBRARY_PATH=
-cc_rflg="$CFLAG_RUNTIME_LIBRARY_PATH"
-ld_rflg="$LDFLAG_RUNTIME_LIBRARY_PATH"
-ded_ld_rflg="$DED_LD_FLAG_RUNTIME_LIBRARY_PATH"
-
-
-case "$with_ssl_rpath" in
-
-yes) # Use standard lib locations for ssl runtime library path
-
- if test "$SSL_APP" != "" && test "$SSL_DYNAMIC_ONLY" = "yes" && \
- { test "$cc_rflg" != "" || test "$ld_rflg" != "" || test "$ded_ld_rflg" != ""; } ; then
-
- AC_MSG_CHECKING(for ssl runtime library path to use)
-
- libdirs="/lib"
-
- if test "$ac_cv_sizeof_void_p" = "8"; then
- dir_lib64=no
- dir_lib_64=no
-
- case "$SSL_RUNTIME_LIBDIR" in
- */lib/64 | */lib/64/ ) dir_lib_64=yes;;
- */lib64 | */lib64/ ) dir_lib64=yes;;
- *) ;;
- esac
-
- for dir in $std_ssl_locations; do
- test $dir_lib_64 = no &&
- test -d "$erl_xcomp_sysroot$dir/lib/64" &&
- dir_lib_64=yes
- test $dir_lib64 = no &&
- test -d "$erl_xcomp_sysroot$dir/lib64" &&
- dir_lib64=yes
- done
-
- test $dir_lib_64 = yes && libdirs="/lib/64 $libdirs"
- test $dir_lib64 = yes && libdirs="/lib64 $libdirs"
- fi
-
- for type in std x_std curr; do
-
- cc_rpath="$cc_rflg$SSL_RUNTIME_LIBDIR"
- ld_rpath="$ld_rflg$SSL_RUNTIME_LIBDIR"
- ded_ld_rpath="$ded_ld_rflg$SSL_RUNTIME_LIBDIR"
- rpath="$SSL_RUNTIME_LIBDIR"
-
- if test $type != curr; then
- for ldir in $libdirs; do
- for dir in $std_ssl_locations; do
- test "$SSL_LIBDIR" != "$dir$ldir" || continue
- test $type != x_std || test -d "$dir$ldir" || continue
- test "$cc_rflg" = "" ||
- cc_rpath="$cc_rpath $cc_rflg$dir$ldir"
- test "$ld_rflg" = "" ||
- ld_rpath="$ld_rpath $ld_rflg$dir$ldir"
- test "$ded_ld_rflg" = "" ||
- ded_ld_rpath="$ded_ld_rpath $ded_ld_rflg$dir$ldir"
- rpath="$rpath:$dir$ldir"
- done
- done
- fi
-
- saveCFLAGS="$CFLAGS"
- saveLDFLAGS="$LDFLAGS"
- saveLIBS="$LIBS"
- CFLAGS="$CFLAGS $SSL_INCLUDE"
- LDFLAGS="$LDFLAGS $ld_rpath -L$SSL_LIBDIR"
- LIBS="-lcrypto"
- AC_TRY_LINK([
- #include <stdio.h>
- #include <openssl/hmac.h>
- ],
- [
- HMAC(0, 0, 0, 0, 0, 0, 0);
- ],
- [rpath_success=yes],
- [rpath_success=no])
- CFLAGS="$saveCFLAGS"
- LDFLAGS="$saveLDFLAGS"
- LIBS="$saveLIBS"
-
- test "$rpath_success" = "yes" && break
- done
-
- test "$rpath_success" = "yes" || { cc_rpath=; ld_rpath=; ded_ld_rpath=; rpath=; }
-
- SSL_CC_RUNTIME_LIBRARY_PATH="$cc_rpath"
- SSL_LD_RUNTIME_LIBRARY_PATH="$ld_rpath"
- SSL_DED_LD_RUNTIME_LIBRARY_PATH="$ded_ld_rpath"
-
- AC_MSG_RESULT([$rpath])
- test "$rpath" != "" || AC_MSG_WARN([Cannot set run path during linking])
- fi
- ;;
-
-no) # Use no ssl runtime library path
- SSL_DED_LD_RUNTIME_LIBRARY_PATH=
- ;;
-
-*) # Use ssl runtime library paths set by --with-ssl-rpath (without any check)
- ded_ld_rpath=
- delimit=
- for dir in `echo $with_ssl_rpath | sed "s/,/ /g"`; do
- ded_ld_rpath="$ded_ld_rpath$delimit$ded_ld_rflg$dir"
- delimit=" "
- done
- SSL_DED_LD_RUNTIME_LIBRARY_PATH="$ded_ld_rpath"
- ;;
-
-esac
-
-
-AC_ARG_ENABLE(fips,
-AS_HELP_STRING([--enable-fips], [enable OpenSSL FIPS mode support])
-AS_HELP_STRING([--disable-fips], [disable OpenSSL FIPS mode support (default)]),
-[ case "$enableval" in
- yes) enable_fips_support=yes ;;
- *) enable_fips_support=no ;;
- esac ], enable_fips_support=no)
-
-if test "x$enable_fips_support" = "xyes" && test "$CRYPTO_APP" != ""; then
- saveCFLAGS="$CFLAGS"
- saveLDFLAGS="$LDFLAGS"
- saveLIBS="$LIBS"
- CFLAGS="$CFLAGS $SSL_INCLUDE"
- LDFLAGS="$LDFLAGS $SSL_LD_RUNTIME_LIBRARY_PATH -L$SSL_LIBDIR"
- LIBS="-lcrypto"
- AC_CHECK_FUNC([FIPS_mode_set],
- [SSL_FLAGS="-DFIPS_SUPPORT"],
- [SSL_FLAGS=])
- CFLAGS="$saveCFLAGS"
- LDFLAGS="$saveLDFLAGS"
- LIBS="$saveLIBS"
-else
- SSL_FLAGS=
-fi
-
#--------------------------------------------------------------------
# Os mon stuff.
#--------------------------------------------------------------------
@@ -4210,6 +3280,8 @@ AC_DEFINE_UNQUOTED(ERTS_EMU_CMDLINE_FLAGS,
"$STATIC_CFLAGS $CFLAGS $DEBUG_CFLAGS $EMU_THR_DEFS $DEFS $WERRORFLAGS $WFLAGS",
[The only reason ERTS_EMU_CMDLINE_FLAGS exists is to force modification of config.h when the emulator command line flags are modified by configure])
+AC_SUBST(STATIC_CFLAGS)
+
dnl ----------------------------------------------------------------------
dnl Directories needed for the build
dnl ----------------------------------------------------------------------
@@ -4312,7 +3384,6 @@ AC_CONFIG_FILES([
include/internal/$host/erts_internal.mk:include/internal/erts_internal.mk.in
lib_src/$host/Makefile:lib_src/Makefile.in
../make/$host/otp.mk:../make/otp.mk.in
- ../make/$host/otp_ded.mk:../make/otp_ded.mk.in
])
AC_CONFIG_FILES([../make/make_emakefile:../make/make_emakefile.in],
@@ -4323,7 +3394,6 @@ dnl The ones below should be moved to their respective lib
dnl
AC_CONFIG_FILES([
../lib/os_mon/c_src/$host/Makefile:../lib/os_mon/c_src/Makefile.in
- ../lib/crypto/c_src/$host/Makefile:../lib/crypto/c_src/Makefile.in
../lib/runtime_tools/c_src/$host/Makefile:../lib/runtime_tools/c_src/Makefile.in
../lib/tools/c_src/$host/Makefile:../lib/tools/c_src/Makefile.in
])
diff --git a/erts/doc/src/Makefile b/erts/doc/src/Makefile
index 40f74b78ff..06a8691c0e 100644
--- a/erts/doc/src/Makefile
+++ b/erts/doc/src/Makefile
@@ -55,7 +55,9 @@ XML_REF3_EFILES = \
persistent_term.xml \
atomics.xml \
counters.xml \
- zlib.xml
+ zlib.xml \
+ socket.xml \
+ net.xml
XML_REF3_FILES = \
$(XML_REF3_EFILES) \
@@ -77,6 +79,7 @@ XML_CHAPTER_FILES = \
driver.xml \
absform.xml \
inet_cfg.xml \
+ socket_usage.xml \
erl_ext_dist.xml \
erl_dist_protocol.xml \
communication.xml \
@@ -146,6 +149,8 @@ $(INFO_FILE): $(INFO_FILE_SRC) $(ERL_TOP)/make/$(TARGET)/otp.mk
debug opt:
+ldocs: xmllint local_docs
+
clean:
rm -rf $(HTMLDIR)/*
rm -rf $(XMLDIR)
diff --git a/erts/doc/src/atomics.xml b/erts/doc/src/atomics.xml
index 3fca92fb97..455973f011 100644
--- a/erts/doc/src/atomics.xml
+++ b/erts/doc/src/atomics.xml
@@ -23,14 +23,14 @@
<title>atomics</title>
</header>
- <module>atomics</module>
+ <module since="OTP 21.2">atomics</module>
<modulesummary>Atomic Functions</modulesummary>
<description>
<p>This module provides a set of functions to do atomic operations towards
mutable atomic variables. The implementation utilizes only
atomic hardware instructions without any software level locking, which makes
it very efficient for concurrent access. The atomics are organized into
- arrays with the follwing semantics:</p>
+ arrays with the following semantics:</p>
<list type="bulleted">
<item>
<p>Atomics are 64 bit integers.</p>
@@ -70,7 +70,7 @@
<funcs>
<func>
- <name name="new" arity="2"/>
+ <name name="new" arity="2" since="OTP 21.2"/>
<fsummary>Create atomic array</fsummary>
<desc>
<p>Create a new atomic array of <c><anno>Arity</anno></c> atomics.</p>
@@ -85,11 +85,13 @@
bsl 64)-1</c>.</p>
</item>
</taglist>
+ <p>Atomics are not tied to the current process and are automatically
+ garbage collected when they are no longer referenced.</p>
</desc>
</func>
<func>
- <name name="put" arity="3"/>
+ <name name="put" arity="3" since="OTP 21.2"/>
<fsummary>Set atomic value</fsummary>
<desc>
<p>Set atomic to <c><anno>Value</anno></c>.</p>
@@ -97,7 +99,7 @@
</func>
<func>
- <name name="get" arity="2"/>
+ <name name="get" arity="2" since="OTP 21.2"/>
<fsummary>Read atomic value</fsummary>
<desc>
<p>Read atomic value.</p>
@@ -105,7 +107,7 @@
</func>
<func>
- <name name="add" arity="3"/>
+ <name name="add" arity="3" since="OTP 21.2"/>
<fsummary>Add to atomic</fsummary>
<desc>
<p>Add <c><anno>Incr</anno></c> to atomic.</p>
@@ -113,7 +115,7 @@
</func>
<func>
- <name name="add_get" arity="3"/>
+ <name name="add_get" arity="3" since="OTP 21.2"/>
<fsummary>Atomic add and get</fsummary>
<desc>
<p>Atomic addition and return of the result.</p>
@@ -121,7 +123,7 @@
</func>
<func>
- <name name="sub" arity="3"/>
+ <name name="sub" arity="3" since="OTP 21.2"/>
<fsummary>Subtract from atomic</fsummary>
<desc>
<p>Subtract <c><anno>Decr</anno></c> from atomic.</p>
@@ -129,7 +131,7 @@
</func>
<func>
- <name name="sub_get" arity="3"/>
+ <name name="sub_get" arity="3" since="OTP 21.2"/>
<fsummary>Atomic sub and get</fsummary>
<desc>
<p>Atomic subtraction and return of the result.</p>
@@ -137,7 +139,7 @@
</func>
<func>
- <name name="exchange" arity="3"/>
+ <name name="exchange" arity="3" since="OTP 21.2"/>
<fsummary>Atomic exchange.</fsummary>
<desc>
<p>Atomically replaces the value of the atomic with
@@ -147,7 +149,7 @@
</func>
<func>
- <name name="compare_exchange" arity="4"/>
+ <name name="compare_exchange" arity="4" since="OTP 21.2"/>
<fsummary>Atomic compare and exchange.</fsummary>
<desc>
<p>Atomically compares the atomic with <c><anno>Expected</anno></c>,
@@ -158,7 +160,7 @@
</func>
<func>
- <name name="info" arity="1"/>
+ <name name="info" arity="1" since="OTP 21.2"/>
<fsummary>Get information about atomic array.</fsummary>
<desc>
<p>Return information about an atomic array in a map. The map
diff --git a/erts/doc/src/counters.xml b/erts/doc/src/counters.xml
index ba4a22759f..36816bd68d 100644
--- a/erts/doc/src/counters.xml
+++ b/erts/doc/src/counters.xml
@@ -23,13 +23,13 @@
<title>counters</title>
</header>
- <module>counters</module>
+ <module since="OTP 21.2">counters</module>
<modulesummary>Counter Functions</modulesummary>
<description>
<p>This module provides a set of functions to do operations towards
shared mutable counter variables. The implementation does not utilize any
software level locking, which makes it very efficient for concurrent
- access. The counters are organized into arrays with the follwing
+ access. The counters are organized into arrays with the following
semantics:</p>
<list type="bulleted">
<item>
@@ -71,7 +71,7 @@
<funcs>
<func>
- <name name="new" arity="2"/>
+ <name name="new" arity="2" since="OTP 21.2"/>
<fsummary>Create counter array</fsummary>
<desc>
<p>Create a new counter array of <c><anno>Size</anno></c> counters.</p>
@@ -80,7 +80,7 @@
<taglist>
<tag><c>atomics</c> (Default)</tag>
<item><p>Counters will be sequentially consistent. If write
- operation A is done sequencially before write operation B, then a concurrent reader
+ operation A is done sequentially before write operation B, then a concurrent reader
may see none of them, only A, or both A and B. It cannot see only B.</p>
</item>
<tag><c>write_concurrency</c></tag>
@@ -90,7 +90,7 @@
inconsistency and memory consumption per counter.</p>
<p>Read operations may see sequentially inconsistent results with
regard to concurrent write operations. Even if write operation A is done
- sequencially before write operation B, a concurrent reader may see any
+ sequentially before write operation B, a concurrent reader may see any
combination of A and B, including only B. A read operation is only
guaranteed to see all writes done sequentially before the read. No writes
are ever lost, but will eventually all be seen.</p>
@@ -103,11 +103,13 @@
acceptable.</p>
</item>
</taglist>
+ <p>Counters are not tied to the current process and are automatically
+ garbage collected when they are no longer referenced.</p>
</desc>
</func>
<func>
- <name name="get" arity="2"/>
+ <name name="get" arity="2" since="OTP 21.2"/>
<fsummary>Read counter value</fsummary>
<desc>
<p>Read counter value.</p>
@@ -115,7 +117,7 @@
</func>
<func>
- <name name="add" arity="3"/>
+ <name name="add" arity="3" since="OTP 21.2"/>
<fsummary>Add to counter</fsummary>
<desc>
<p>Add <c><anno>Incr</anno></c> to counter at index
@@ -124,7 +126,7 @@
</func>
<func>
- <name name="sub" arity="3"/>
+ <name name="sub" arity="3" since="OTP 21.2"/>
<fsummary>Subtract from counter</fsummary>
<desc>
<p>Subtract <c><anno>Decr</anno></c> from counter at index
@@ -133,25 +135,25 @@
</func>
<func>
- <name name="put" arity="3"/>
+ <name name="put" arity="3" since="OTP 21.2"/>
<fsummary>Set counter to value</fsummary>
<desc>
<p>Write <c><anno>Value</anno></c> to counter at index
<c><anno>Ix</anno></c>.</p>
<note>
<p>Despite its name, the <c>write_concurrency</c> optimization does not
- improve <c>put</c>. A call to <c>put</c> is a relative heavy
+ improve <c>put</c>. A call to <c>put</c> is a relatively heavy
operation compared to the very lightweight and scalable <seealso
marker="#add/3"><c>add</c></seealso> and <seealso marker="#sub/3">
<c>sub</c></seealso>. The cost for a <c>put</c> with
- <c>write_concurrency</c> is lika a <seealso marker="#get/2"><c>get</c>
+ <c>write_concurrency</c> is like a <seealso marker="#get/2"><c>get</c>
</seealso> plus a <c>put</c> without <c>write_concurrency</c>.</p>
</note>
</desc>
</func>
<func>
- <name name="info" arity="1"/>
+ <name name="info" arity="1" since="OTP 21.2"/>
<fsummary>Get information about counter array.</fsummary>
<desc>
<p>Return information about a counter array in a map. The map
diff --git a/erts/doc/src/erl.xml b/erts/doc/src/erl.xml
index 05a9895687..88ddb03e97 100644
--- a/erts/doc/src/erl.xml
+++ b/erts/doc/src/erl.xml
@@ -464,7 +464,7 @@
</item>
<tag><c><![CDATA[-rsh Program]]></c></tag>
<item>
- <p>Specifies an alternative to <c><![CDATA[rsh]]></c> for starting a
+ <p>Specifies an alternative to <c><![CDATA[ssh]]></c> for starting a
slave node on a remote host; see
<seealso marker="stdlib:slave"><c>slave(3)</c></seealso>.</p>
</item>
diff --git a/erts/doc/src/erl_dist_protocol.xml b/erts/doc/src/erl_dist_protocol.xml
index c90c8f9521..185c75fe84 100644
--- a/erts/doc/src/erl_dist_protocol.xml
+++ b/erts/doc/src/erl_dist_protocol.xml
@@ -532,11 +532,7 @@ io:format("old/unused name ~ts at port ~p, fd = ~p ~n",
<marker id="distribution_handshake"/>
<title>Distribution Handshake</title>
<p>This section describes the distribution handshake protocol introduced
- in Erlang/OTP R6. This description was previously located in
- <c>$ERL_TOP/lib/kernel/internal_doc/distribution_handshake.txt</c> and
- has more or less been copied and "formatted" here. It has been almost
- unchanged since 1999, but the handshake has not changed much since then
- either.</p>
+ in Erlang/OTP R6. The handshake has remained almost the same since then.</p>
<section>
<title>General</title>
@@ -847,6 +843,18 @@ DiB == gen_digest(ChA, ICA)?
of the <c>SEND_TT</c> control message.
</p>
</item>
+ <tag><c>-define(DFLAG_BIG_SEQTRACE_LABELS, 16#100000).</c></tag>
+ <item>
+ <p>The node understands any term as the seqtrace label.</p>
+ </item>
+ <tag><c>-define(DFLAG_EXIT_PAYLOAD, 16#400000).</c></tag>
+ <item>
+ <p>Use the <c>PAYLOAD_EXIT</c>, <c>PAYLOAD_EXIT_TT</c>,
+ <c>PAYLOAD_EXIT2</c>, <c>PAYLOAD_EXIT2_TT</c>
+ and <c>PAYLOAD_MONITOR_P_EXIT</c>
+ <seealso marker="#control_message">control message</seealso>s
+ instead of the non-PAYLOAD variants.</p>
+ </item>
</taglist>
<p>
There is also function <c>dist_util:strict_order_flags/0</c>
@@ -859,7 +867,7 @@ DiB == gen_digest(ChA, ICA)?
<section>
<marker id="connected_nodes"/>
<title>Protocol between Connected Nodes</title>
- <p>As from ERTS 5.7.2 the runtime system passes a distribution flag
+ <p>Since ERTS 5.7.2 (OTP R13B) the runtime system passes a distribution flag
in the handshake stage that enables the use of a
<seealso marker="erl_ext_dist#distribution_header">distribution header
</seealso> on all messages passed. Messages passed between nodes have in
@@ -878,7 +886,7 @@ DiB == gen_digest(ChA, ICA)?
<cell align="center"><c>ControlMessage</c></cell>
<cell align="center"><c>Message</c></cell>
</row>
- <tcaption>Format of Messages Passed between Nodes (as from ERTS 5.7.2)
+ <tcaption>Format of Messages Passed between Nodes (as from ERTS 5.7.2 (OTP R13B))
</tcaption>
</table>
@@ -887,15 +895,23 @@ DiB == gen_digest(ChA, ICA)?
<item>
<p>Equal to d + n + m.</p>
</item>
+ <tag><c>DistributionHeader</c></tag>
+ <item>
+ <p>
+ <seealso marker="erl_ext_dist#distribution_header">Distribution header
+ describing the atom cache and fragmented distribution messages.
+ </seealso>
+ </p>
+ </item>
<tag><c>ControlMessage</c></tag>
<item>
<p>A tuple passed using the external format of Erlang.</p>
</item>
<tag><c>Message</c></tag>
<item>
- <p>The message sent to another node using the '!' (in external format).
- Notice that <c>Message</c> is only passed in combination with a
- <c>ControlMessage</c> encoding a send ('!').</p>
+ <p>The message sent to another node using the '!'
+ or the reason for a EXIT, EXIT2 or DOWN signal using
+ the external term format.</p>
</item>
</taglist>
@@ -903,7 +919,7 @@ DiB == gen_digest(ChA, ICA)?
number is omitted from the terms that follow a distribution header
</seealso>.</p>
- <p>Nodes with an ERTS version earlier than 5.7.2 does not pass the
+ <p>Nodes with an ERTS version earlier than 5.7.2 (OTP R13B) does not pass the
distribution flag that enables the distribution header. Messages passed
between nodes have in this case the following format:</p>
@@ -920,7 +936,7 @@ DiB == gen_digest(ChA, ICA)?
<cell align="center"><c>ControlMessage</c></cell>
<cell align="center"><c>Message</c></cell>
</row>
- <tcaption>Format of Messages Passed between Nodes (before ERTS 5.7.2)
+ <tcaption>Format of Messages Passed between Nodes (before ERTS 5.7.2 (OTP R13B))
</tcaption>
</table>
@@ -963,6 +979,7 @@ DiB == gen_digest(ChA, ICA)?
<tag><c>EXIT</c></tag>
<item>
<p><c>{3, FromPid, ToPid, Reason}</c></p>
+ <p>This signal is sent when a link has been broken</p>
</item>
<tag><c>UNLINK</c></tag>
<item>
@@ -985,6 +1002,7 @@ DiB == gen_digest(ChA, ICA)?
<tag><c>EXIT2</c></tag>
<item>
<p><c>{8, FromPid, ToPid, Reason}</c></p>
+ <p>This signal is sent by a call to the erlang:exit/2 bif</p>
</item>
</taglist>
</section>
@@ -1007,7 +1025,7 @@ DiB == gen_digest(ChA, ICA)?
<p><c>{16, FromPid, Unused, ToName, TraceToken}</c></p>
<p>Followed by <c>Message</c>.</p>
<p><c>Unused</c> is kept for backward compatibility.</p>
- </item>
+ </item>
<tag><c>EXIT2_TT</c></tag>
<item>
<p><c>{18, FromPid, ToPid, TraceToken, Reason}</c></p>
@@ -1061,7 +1079,7 @@ DiB == gen_digest(ChA, ICA)?
<p><c>{22, FromPid, ToPid}</c></p>
<p>Followed by <c>Message</c>.</p>
<p>
- This control messages replace the <c>SEND</c> control
+ This control message replaces the <c>SEND</c> control
message and will be sent when the distribution flag
<seealso marker="erl_dist_protocol#dflags"><c>DFLAG_SEND_SENDER</c></seealso>
has been negotiated in the connection setup handshake.
@@ -1080,7 +1098,7 @@ DiB == gen_digest(ChA, ICA)?
<p><c>{23, FromPid, ToPid, TraceToken}</c></p>
<p>Followed by <c>Message</c>.</p>
<p>
- This control messages replace the <c>SEND_TT</c> control
+ This control message replaces the <c>SEND_TT</c> control
message and will be sent when the distribution flag
<seealso marker="erl_dist_protocol#dflags"><c>DFLAG_SEND_SENDER</c></seealso>
has been negotiated in the connection setup handshake.
@@ -1097,4 +1115,72 @@ DiB == gen_digest(ChA, ICA)?
</taglist>
</section>
+ <section>
+ <title>New Ctrlmessages for Erlang/OTP 22</title>
+ <note><p>
+ Messages encoded before the connection has
+ been set up may still use the non-PAYLOAD variant.
+ However, once a PAYLOAD control message has been sent,
+ no more non-PAYLOAD control messages will be sent in
+ the same direction on the connection.
+ </p></note>
+ <taglist>
+ <tag><c>PAYLOAD_EXIT</c></tag>
+ <item>
+ <p><c>{24, FromPid, ToPid}</c></p>
+ <p>Followed by <c>Reason</c>.</p>
+ <p>
+ This control message replaces the <c>EXIT</c> control
+ message and will be sent when the distribution flag
+ <seealso marker="erl_dist_protocol#dflags"><c>DFLAG_EXIT_PAYLOAD</c></seealso>
+ has been negotiated in the connection setup handshake.
+ </p>
+ </item>
+ <tag><c>PAYLOAD_EXIT_TT</c></tag>
+ <item>
+ <p><c>{25, FromPid, ToPid}</c></p>
+ <p>Followed by <c>Reason</c>.</p>
+ <p>
+ This control message replaces the <c>EXIT_TT</c> control
+ message and will be sent when the distribution flag
+ <seealso marker="erl_dist_protocol#dflags"><c>DFLAG_EXIT_PAYLOAD</c></seealso>
+ has been negotiated in the connection setup handshake.
+ </p>
+ </item>
+ <tag><c>PAYLOAD_EXIT2</c></tag>
+ <item>
+ <p><c>{26, FromPid, ToPid}</c></p>
+ <p>Followed by <c>Reason</c>.</p>
+ <p>
+ This control message replaces the <c>EXIT2</c> control
+ message and will be sent when the distribution flag
+ <seealso marker="erl_dist_protocol#dflags"><c>DFLAG_EXIT_PAYLOAD</c></seealso>
+ has been negotiated in the connection setup handshake.
+ </p>
+ </item>
+ <tag><c>PAYLOAD_EXIT2_TT</c></tag>
+ <item>
+ <p><c>{27, FromPid, ToPid}</c></p>
+ <p>Followed by <c>Reason</c>.</p>
+ <p>
+ This control message replaces the <c>EXIT2_TT</c> control
+ message and will be sent when the distribution flag
+ <seealso marker="erl_dist_protocol#dflags"><c>DFLAG_EXIT_PAYLOAD</c></seealso>
+ has been negotiated in the connection setup handshake.
+ </p>
+ </item>
+ <tag><c>PAYLOAD_MONITOR_P_EXIT</c></tag>
+ <item>
+ <p><c>{28, FromPid, ToPid, Ref}</c></p>
+ <p>Followed by <c>Reason</c>.</p>
+ <p>
+ This control message replaces the <c>MONITOR_P_EXIT</c> control
+ message and will be sent when the distribution flag
+ <seealso marker="erl_dist_protocol#dflags"><c>DFLAG_EXIT_PAYLOAD</c></seealso>
+ has been negotiated in the connection setup handshake.
+ </p>
+ </item>
+ </taglist>
+ </section>
+
</chapter>
diff --git a/erts/doc/src/erl_driver.xml b/erts/doc/src/erl_driver.xml
index 7055889e4a..58678f2393 100644
--- a/erts/doc/src/erl_driver.xml
+++ b/erts/doc/src/erl_driver.xml
@@ -944,7 +944,7 @@ int suggested_stack_size;</code>
<funcs>
<func>
- <name><ret>void</ret><nametext>add_driver_entry(ErlDrvEntry
+ <name since=""><ret>void</ret><nametext>add_driver_entry(ErlDrvEntry
*de)</nametext></name>
<fsummary>Add a driver entry.</fsummary>
<desc>
@@ -968,7 +968,7 @@ int suggested_stack_size;</code>
</func>
<func>
- <name><ret>void *</ret>
+ <name since=""><ret>void *</ret>
<nametext>driver_alloc(ErlDrvSizeT size)</nametext></name>
<fsummary>Allocate memory.</fsummary>
<desc>
@@ -985,7 +985,7 @@ int suggested_stack_size;</code>
</func>
<func>
- <name><ret>ErlDrvBinary *</ret>
+ <name since=""><ret>ErlDrvBinary *</ret>
<nametext>driver_alloc_binary(ErlDrvSizeT size)</nametext></name>
<fsummary>Allocate a driver binary.</fsummary>
<desc>
@@ -1008,7 +1008,7 @@ int suggested_stack_size;</code>
</func>
<func>
- <name><ret>long</ret><nametext>driver_async(ErlDrvPort port, unsigned
+ <name since=""><ret>long</ret><nametext>driver_async(ErlDrvPort port, unsigned
int* key, void (*async_invoke)(void*), void* async_data, void
(*async_free)(void*))</nametext></name>
<fsummary>Perform an asynchronous call within a driver.</fsummary>
@@ -1076,7 +1076,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>unsigned int</ret><nametext>driver_async_port_key(ErlDrvPort
+ <name since="OTP R16B02"><ret>unsigned int</ret><nametext>driver_async_port_key(ErlDrvPort
port)</nametext></name>
<fsummary>Calculate an async key from an ErlDrvPort.</fsummary>
<desc>
@@ -1096,7 +1096,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>long</ret>
+ <name since=""><ret>long</ret>
<nametext>driver_binary_dec_refc(ErlDrvBinary *bin)</nametext></name>
<fsummary>Decrement the reference count of a driver binary.</fsummary>
<desc>
@@ -1117,7 +1117,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>long</ret>
+ <name since=""><ret>long</ret>
<nametext>driver_binary_get_refc(ErlDrvBinary *bin)</nametext></name>
<fsummary>Get the reference count of a driver binary.</fsummary>
<desc>
@@ -1128,7 +1128,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>long</ret>
+ <name since=""><ret>long</ret>
<nametext>driver_binary_inc_refc(ErlDrvBinary *bin)</nametext></name>
<fsummary>Increment the reference count of a driver binary.</fsummary>
<desc>
@@ -1140,7 +1140,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>ErlDrvTermData</ret><nametext>driver_caller(ErlDrvPort
+ <name since=""><ret>ErlDrvTermData</ret><nametext>driver_caller(ErlDrvPort
port)</nametext></name>
<fsummary>Return the process making the driver call.</fsummary>
<desc>
@@ -1183,7 +1183,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret>
+ <name since=""><ret>int</ret>
<nametext>driver_cancel_timer(ErlDrvPort port)</nametext></name>
<fsummary>Cancel a previously set timer.</fsummary>
<desc>
@@ -1196,7 +1196,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>driver_compare_monitors(const ErlDrvMonitor
+ <name since=""><ret>int</ret><nametext>driver_compare_monitors(const ErlDrvMonitor
*monitor1, const ErlDrvMonitor *monitor2)</nametext></name>
<fsummary>Compare two monitors.</fsummary>
<desc>
@@ -1211,7 +1211,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>ErlDrvTermData</ret><nametext>driver_connected(ErlDrvPort
+ <name since=""><ret>ErlDrvTermData</ret><nametext>driver_connected(ErlDrvPort
port)</nametext></name>
<fsummary>Return the port owner process.</fsummary>
<desc>
@@ -1223,7 +1223,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>ErlDrvPort</ret><nametext>driver_create_port(ErlDrvPort port,
+ <name since=""><ret>ErlDrvPort</ret><nametext>driver_create_port(ErlDrvPort port,
ErlDrvTermData owner_pid, char* name,
ErlDrvData drv_data)</nametext></name>
<fsummary>Create a new port (driver instance).</fsummary>
@@ -1269,7 +1269,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>driver_demonitor_process(ErlDrvPort port,
+ <name since=""><ret>int</ret><nametext>driver_demonitor_process(ErlDrvPort port,
const ErlDrvMonitor *monitor)</nametext></name>
<fsummary>Stop monitoring a process from a driver.</fsummary>
<desc>
@@ -1281,7 +1281,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>ErlDrvSizeT</ret><nametext>driver_deq(ErlDrvPort port,
+ <name since=""><ret>ErlDrvSizeT</ret><nametext>driver_deq(ErlDrvPort port,
ErlDrvSizeT size)</nametext></name>
<fsummary>Dequeue data from the head of the driver queue.</fsummary>
<desc>
@@ -1299,7 +1299,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>driver_enq(ErlDrvPort port, char* buf,
+ <name since=""><ret>int</ret><nametext>driver_enq(ErlDrvPort port, char* buf,
ErlDrvSizeT len)</nametext></name>
<fsummary>Enqueue data in the driver queue.</fsummary>
<desc>
@@ -1325,7 +1325,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>driver_enq_bin(ErlDrvPort port,
+ <name since=""><ret>int</ret><nametext>driver_enq_bin(ErlDrvPort port,
ErlDrvBinary *bin, ErlDrvSizeT offset, ErlDrvSizeT len)</nametext>
</name>
<fsummary>Enqueue binary in the driver queue.</fsummary>
@@ -1346,7 +1346,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>driver_enqv(ErlDrvPort port, ErlIOVec *ev,
+ <name since=""><ret>int</ret><nametext>driver_enqv(ErlDrvPort port, ErlIOVec *ev,
ErlDrvSizeT skip)</nametext></name>
<fsummary>Enqueue vector in the driver queue.</fsummary>
<desc>
@@ -1365,11 +1365,11 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>driver_failure(ErlDrvPort port, int
+ <name since=""><ret>int</ret><nametext>driver_failure(ErlDrvPort port, int
error)</nametext></name>
- <name><ret>int</ret><nametext>driver_failure_atom(ErlDrvPort port, char
+ <name since=""><ret>int</ret><nametext>driver_failure_atom(ErlDrvPort port, char
*string)</nametext></name>
- <name><ret>int</ret><nametext>driver_failure_posix(ErlDrvPort port, int
+ <name since=""><ret>int</ret><nametext>driver_failure_posix(ErlDrvPort port, int
error)</nametext></name>
<fsummary>Fail with error.</fsummary>
<desc>
@@ -1393,7 +1393,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>driver_failure_eof(ErlDrvPort
+ <name since=""><ret>int</ret><nametext>driver_failure_eof(ErlDrvPort
port)</nametext></name>
<fsummary>Fail with EOF.</fsummary>
<desc>
@@ -1408,7 +1408,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>void</ret><nametext>driver_free(void *ptr)</nametext></name>
+ <name since=""><ret>void</ret><nametext>driver_free(void *ptr)</nametext></name>
<fsummary>Free an allocated memory block.</fsummary>
<desc>
<marker id="driver_free"></marker>
@@ -1422,7 +1422,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>void</ret>
+ <name since=""><ret>void</ret>
<nametext>driver_free_binary(ErlDrvBinary *bin)</nametext></name>
<fsummary>Free a driver binary.</fsummary>
<desc>
@@ -1436,7 +1436,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>ErlDrvTermData</ret>
+ <name since=""><ret>ErlDrvTermData</ret>
<nametext>driver_get_monitored_process(ErlDrvPort port, const
ErlDrvMonitor *monitor)</nametext></name>
<fsummary>Retrieve the process ID from a monitor.</fsummary>
@@ -1452,7 +1452,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret>
+ <name since=""><ret>int</ret>
<nametext>driver_get_now(ErlDrvNowData *now)</nametext></name>
<fsummary>Read a system time stamp.</fsummary>
<desc>
@@ -1473,7 +1473,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>driver_lock_driver(ErlDrvPort
+ <name since=""><ret>int</ret><nametext>driver_lock_driver(ErlDrvPort
port)</nametext></name>
<fsummary>Ensure the driver is never unloaded.</fsummary>
<desc>
@@ -1486,7 +1486,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>ErlDrvTermData</ret><nametext>driver_mk_atom(char*
+ <name since=""><ret>ErlDrvTermData</ret><nametext>driver_mk_atom(char*
string)</nametext></name>
<fsummary>Make an atom from a name.</fsummary>
<desc>
@@ -1501,7 +1501,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>ErlDrvTermData</ret><nametext>driver_mk_port(ErlDrvPort
+ <name since=""><ret>ErlDrvTermData</ret><nametext>driver_mk_port(ErlDrvPort
port)</nametext></name>
<fsummary>Make an Erlang term port from a port.</fsummary>
<desc>
@@ -1517,7 +1517,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>driver_monitor_process(ErlDrvPort port,
+ <name since=""><ret>int</ret><nametext>driver_monitor_process(ErlDrvPort port,
ErlDrvTermData process, ErlDrvMonitor *monitor)</nametext></name>
<fsummary>Monitor a process from a driver.</fsummary>
<desc>
@@ -1540,7 +1540,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>driver_output(ErlDrvPort port, char *buf,
+ <name since=""><ret>int</ret><nametext>driver_output(ErlDrvPort port, char *buf,
ErlDrvSizeT len)</nametext></name>
<fsummary>Send data from driver to port owner.</fsummary>
<desc>
@@ -1560,7 +1560,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>driver_output_binary(ErlDrvPort port, char
+ <name since=""><ret>int</ret><nametext>driver_output_binary(ErlDrvPort port, char
*hbuf, ErlDrvSizeT hlen, ErlDrvBinary* bin, ErlDrvSizeT offset,
ErlDrvSizeT len)</nametext></name>
<fsummary>Send data from a driver binary to port owner.</fsummary>
@@ -1589,7 +1589,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>driver_output_term(ErlDrvPort port,
+ <name since=""><ret>int</ret><nametext>driver_output_term(ErlDrvPort port,
ErlDrvTermData* term, int n)</nametext></name>
<fsummary>Send term data from driver to port owner.</fsummary>
<desc>
@@ -1608,7 +1608,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>driver_output2(ErlDrvPort port, char *hbuf,
+ <name since=""><ret>int</ret><nametext>driver_output2(ErlDrvPort port, char *hbuf,
ErlDrvSizeT hlen, char *buf, ErlDrvSizeT len)</nametext></name>
<fsummary>Send data and binary data to port owner.</fsummary>
<desc>
@@ -1625,7 +1625,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>driver_outputv(ErlDrvPort port, char* hbuf,
+ <name since=""><ret>int</ret><nametext>driver_outputv(ErlDrvPort port, char* hbuf,
ErlDrvSizeT hlen, ErlIOVec *ev, ErlDrvSizeT skip)</nametext></name>
<fsummary>Send vectorized data to port owner.</fsummary>
<desc>
@@ -1654,7 +1654,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>ErlDrvPDL</ret>
+ <name since=""><ret>ErlDrvPDL</ret>
<nametext>driver_pdl_create(ErlDrvPort port)</nametext></name>
<fsummary>Create a port data lock.</fsummary>
<desc>
@@ -1672,7 +1672,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>long</ret><nametext>driver_pdl_dec_refc(ErlDrvPDL
+ <name since=""><ret>long</ret><nametext>driver_pdl_dec_refc(ErlDrvPDL
pdl)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -1686,7 +1686,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>long</ret>
+ <name since=""><ret>long</ret>
<nametext>driver_pdl_get_refc(ErlDrvPDL pdl)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -1698,7 +1698,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>long</ret>
+ <name since=""><ret>long</ret>
<nametext>driver_pdl_inc_refc(ErlDrvPDL pdl)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -1712,7 +1712,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>void</ret>
+ <name since=""><ret>void</ret>
<nametext>driver_pdl_lock(ErlDrvPDL pdl)</nametext></name>
<fsummary>Lock port data lock.</fsummary>
<desc>
@@ -1723,7 +1723,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>void</ret>
+ <name since=""><ret>void</ret>
<nametext>driver_pdl_unlock(ErlDrvPDL pdl)</nametext></name>
<fsummary>Unlock port data lock.</fsummary>
<desc>
@@ -1734,7 +1734,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>SysIOVec *</ret><nametext>driver_peekq(ErlDrvPort port, int
+ <name since=""><ret>SysIOVec *</ret><nametext>driver_peekq(ErlDrvPort port, int
*vlen)</nametext></name>
<fsummary>Get the driver queue as a vector.</fsummary>
<desc>
@@ -1755,7 +1755,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>ErlDrvSizeT</ret><nametext>driver_peekqv(ErlDrvPort port,
+ <name since="OTP R15B"><ret>ErlDrvSizeT</ret><nametext>driver_peekqv(ErlDrvPort port,
ErlIOVec *ev)</nametext></name>
<fsummary>Get the driver queue as an I/O vector.</fsummary>
<desc>
@@ -1775,7 +1775,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>driver_pushq(ErlDrvPort port, char* buf,
+ <name since=""><ret>int</ret><nametext>driver_pushq(ErlDrvPort port, char* buf,
ErlDrvSizeT len)</nametext></name>
<fsummary>Push data at the head of the driver queue.</fsummary>
<desc>
@@ -1792,7 +1792,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>driver_pushq_bin(ErlDrvPort port,
+ <name since=""><ret>int</ret><nametext>driver_pushq_bin(ErlDrvPort port,
ErlDrvBinary *bin, ErlDrvSizeT offset, ErlDrvSizeT len)</nametext>
</name>
<fsummary>Push binary at the head of the driver queue.</fsummary>
@@ -1812,7 +1812,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>driver_pushqv(ErlDrvPort port, ErlIOVec
+ <name since=""><ret>int</ret><nametext>driver_pushqv(ErlDrvPort port, ErlIOVec
*ev, ErlDrvSizeT skip)</nametext></name>
<fsummary>Push vector at the head of the driver queue.</fsummary>
<desc>
@@ -1831,7 +1831,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>driver_read_timer(ErlDrvPort port, unsigned
+ <name since=""><ret>int</ret><nametext>driver_read_timer(ErlDrvPort port, unsigned
long *time_left)</nametext></name>
<fsummary>Read the time left before time-out.</fsummary>
<desc>
@@ -1844,7 +1844,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>void *</ret>
+ <name since=""><ret>void *</ret>
<nametext>driver_realloc(void *ptr, ErlDrvSizeT size)</nametext></name>
<fsummary>Resize an allocated memory block.</fsummary>
<desc>
@@ -1859,7 +1859,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>ErlDrvBinary *</ret>
+ <name since=""><ret>ErlDrvBinary *</ret>
<nametext>driver_realloc_binary(ErlDrvBinary *bin, ErlDrvSizeT size)
</nametext></name>
<fsummary>Resize a driver binary.</fsummary>
@@ -1873,7 +1873,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>driver_select(ErlDrvPort port, ErlDrvEvent
+ <name since=""><ret>int</ret><nametext>driver_select(ErlDrvPort port, ErlDrvEvent
event, int mode, int on)</nametext></name>
<fsummary>Provides an event for having the emulator call the driver.
</fsummary>
@@ -1932,7 +1932,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>driver_send_term(ErlDrvPort port,
+ <name since=""><ret>int</ret><nametext>driver_send_term(ErlDrvPort port,
ErlDrvTermData receiver, ErlDrvTermData* term, int n)</nametext></name>
<fsummary>Send term data to other process than port owner process.
</fsummary>
@@ -1958,7 +1958,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>driver_set_timer(ErlDrvPort port, unsigned
+ <name since=""><ret>int</ret><nametext>driver_set_timer(ErlDrvPort port, unsigned
long time)</nametext></name>
<fsummary>Set a timer to call the driver.</fsummary>
<desc>
@@ -1977,7 +1977,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>ErlDrvSizeT</ret>
+ <name since=""><ret>ErlDrvSizeT</ret>
<nametext>driver_sizeq(ErlDrvPort port)</nametext></name>
<fsummary>Return the size of the driver queue.</fsummary>
<desc>
@@ -1991,7 +1991,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>void</ret><nametext>driver_system_info(ErlDrvSysInfo
+ <name since=""><ret>void</ret><nametext>driver_system_info(ErlDrvSysInfo
*sys_info_ptr, size_t size)</nametext></name>
<fsummary>Get information about the Erlang runtime system.</fsummary>
<desc>
@@ -2008,7 +2008,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>ErlDrvSizeT</ret><nametext>driver_vec_to_buf(ErlIOVec *ev,
+ <name since=""><ret>ErlDrvSizeT</ret><nametext>driver_vec_to_buf(ErlIOVec *ev,
char *buf, ErlDrvSizeT len)</nametext></name>
<fsummary>Collect data segments into a buffer.</fsummary>
<desc>
@@ -2029,7 +2029,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>void</ret><nametext>erl_drv_busy_msgq_limits(ErlDrvPort port,
+ <name since="OTP R16B"><ret>void</ret><nametext>erl_drv_busy_msgq_limits(ErlDrvPort port,
ErlDrvSizeT *low, ErlDrvSizeT *high)</nametext></name>
<fsummary>Set and get limits for busy port message queue.</fsummary>
<desc>
@@ -2083,7 +2083,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>void</ret><nametext>erl_drv_cond_broadcast(ErlDrvCond
+ <name since=""><ret>void</ret><nametext>erl_drv_cond_broadcast(ErlDrvCond
*cnd)</nametext></name>
<fsummary>Broadcast on a condition variable.</fsummary>
<desc>
@@ -2097,7 +2097,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>ErlDrvCond *</ret><nametext>erl_drv_cond_create(char
+ <name since=""><ret>ErlDrvCond *</ret><nametext>erl_drv_cond_create(char
*name)</nametext></name>
<fsummary>Create a condition variable.</fsummary>
<desc>
@@ -2114,7 +2114,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>void</ret><nametext>erl_drv_cond_destroy(ErlDrvCond
+ <name since=""><ret>void</ret><nametext>erl_drv_cond_destroy(ErlDrvCond
*cnd)</nametext></name>
<fsummary>Destroy a condition variable.</fsummary>
<desc>
@@ -2128,7 +2128,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>char *</ret><nametext>erl_drv_cond_name(ErlDrvCond
+ <name since="OTP R16B02"><ret>char *</ret><nametext>erl_drv_cond_name(ErlDrvCond
*cnd)</nametext></name>
<fsummary>Get name of driver mutex.</fsummary>
<desc>
@@ -2142,7 +2142,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>void</ret><nametext>erl_drv_cond_signal(ErlDrvCond
+ <name since=""><ret>void</ret><nametext>erl_drv_cond_signal(ErlDrvCond
*cnd)</nametext></name>
<fsummary>Signal on a condition variable.</fsummary>
<desc>
@@ -2156,7 +2156,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>void</ret><nametext>erl_drv_cond_wait(ErlDrvCond *cnd,
+ <name since=""><ret>void</ret><nametext>erl_drv_cond_wait(ErlDrvCond *cnd,
ErlDrvMutex *mtx)</nametext></name>
<fsummary>Wait on a condition variable.</fsummary>
<desc>
@@ -2185,7 +2185,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>erl_drv_consume_timeslice(ErlDrvPort port,
+ <name since="OTP R16B"><ret>int</ret><nametext>erl_drv_consume_timeslice(ErlDrvPort port,
int percent)</nametext></name>
<fsummary>Give the runtime system a hint about how much CPU time the
current driver callback call has consumed.</fsummary>
@@ -2228,7 +2228,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>ErlDrvTime</ret><nametext>erl_drv_convert_time_unit(ErlDrvTime
+ <name since="OTP 18.3"><ret>ErlDrvTime</ret><nametext>erl_drv_convert_time_unit(ErlDrvTime
val, ErlDrvTimeUnit from, ErlDrvTimeUnit to)</nametext></name>
<fsummary>Convert time unit of a time value.</fsummary>
<desc>
@@ -2254,7 +2254,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>erl_drv_equal_tids(ErlDrvTid tid1,
+ <name since=""><ret>int</ret><nametext>erl_drv_equal_tids(ErlDrvTid tid1,
ErlDrvTid tid2)</nametext></name>
<fsummary>Compare thread identifiers for equality.</fsummary>
<desc>
@@ -2276,7 +2276,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>erl_drv_getenv(const char *key, char
+ <name since=""><ret>int</ret><nametext>erl_drv_getenv(const char *key, char
*value, size_t *value_size)</nametext></name>
<fsummary>Get the value of an environment variable.</fsummary>
<desc>
@@ -2317,7 +2317,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>void</ret><nametext>erl_drv_init_ack(ErlDrvPort port,
+ <name since="OTP 19.0"><ret>void</ret><nametext>erl_drv_init_ack(ErlDrvPort port,
ErlDrvData res)</nametext></name>
<fsummary>Acknowledge the start of the port.</fsummary>
<desc>
@@ -2345,7 +2345,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>ErlDrvTime</ret>
+ <name since="OTP 18.3"><ret>ErlDrvTime</ret>
<nametext>erl_drv_monotonic_time(ErlDrvTimeUnit time_unit)</nametext>
</name>
<fsummary>Get Erlang monotonic time.</fsummary>
@@ -2365,7 +2365,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>ErlDrvMutex *</ret><nametext>erl_drv_mutex_create(char
+ <name since=""><ret>ErlDrvMutex *</ret><nametext>erl_drv_mutex_create(char
*name)</nametext></name>
<fsummary>Create a mutex.</fsummary>
<desc>
@@ -2380,7 +2380,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>void</ret><nametext>erl_drv_mutex_destroy(ErlDrvMutex
+ <name since=""><ret>void</ret><nametext>erl_drv_mutex_destroy(ErlDrvMutex
*mtx)</nametext></name>
<fsummary>Destroy a mutex.</fsummary>
<desc>
@@ -2395,7 +2395,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>void</ret><nametext>erl_drv_mutex_lock(ErlDrvMutex
+ <name since=""><ret>void</ret><nametext>erl_drv_mutex_lock(ErlDrvMutex
*mtx)</nametext></name>
<fsummary>Lock a mutex.</fsummary>
<desc>
@@ -2414,7 +2414,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>char *</ret><nametext>erl_drv_mutex_name(ErlDrvMutex
+ <name since="OTP R16B02"><ret>char *</ret><nametext>erl_drv_mutex_name(ErlDrvMutex
*mtx)</nametext></name>
<fsummary>Get name of driver mutex.</fsummary>
<desc>
@@ -2428,7 +2428,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>erl_drv_mutex_trylock(ErlDrvMutex
+ <name since=""><ret>int</ret><nametext>erl_drv_mutex_trylock(ErlDrvMutex
*mtx)</nametext></name>
<fsummary>Try lock a mutex.</fsummary>
<desc>
@@ -2447,7 +2447,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>void</ret><nametext>erl_drv_mutex_unlock(ErlDrvMutex
+ <name since=""><ret>void</ret><nametext>erl_drv_mutex_unlock(ErlDrvMutex
*mtx)</nametext></name>
<fsummary>Unlock a mutex.</fsummary>
<desc>
@@ -2460,7 +2460,7 @@ r = driver_async(myPort, &myKey, myData, myFunc); ]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>erl_drv_output_term(ErlDrvTermData port,
+ <name since="OTP R16B"><ret>int</ret><nametext>erl_drv_output_term(ErlDrvTermData port,
ErlDrvTermData* term, int n)</nametext></name>
<fsummary>Send term data from driver to port owner.</fsummary>
<desc>
@@ -2637,7 +2637,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>int</ret><nametext>erl_drv_putenv(const char *key, char
+ <name since=""><ret>int</ret><nametext>erl_drv_putenv(const char *key, char
*value)</nametext></name>
<fsummary>Set the value of an environment variable.</fsummary>
<desc>
@@ -2668,7 +2668,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>ErlDrvRWLock *</ret><nametext>erl_drv_rwlock_create(char
+ <name since=""><ret>ErlDrvRWLock *</ret><nametext>erl_drv_rwlock_create(char
*name)</nametext></name>
<fsummary>Create an rwlock.</fsummary>
<desc>
@@ -2684,7 +2684,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>void</ret><nametext>erl_drv_rwlock_destroy(ErlDrvRWLock
+ <name since=""><ret>void</ret><nametext>erl_drv_rwlock_destroy(ErlDrvRWLock
*rwlck)</nametext></name>
<fsummary>Destroy an rwlock.</fsummary>
<desc>
@@ -2699,7 +2699,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>char *</ret><nametext>erl_drv_rwlock_name(ErlDrvRWLock
+ <name since="OTP R16B02"><ret>char *</ret><nametext>erl_drv_rwlock_name(ErlDrvRWLock
*rwlck)</nametext></name>
<fsummary>Get name of driver mutex.</fsummary>
<desc>
@@ -2713,7 +2713,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>void</ret><nametext>erl_drv_rwlock_rlock(ErlDrvRWLock
+ <name since=""><ret>void</ret><nametext>erl_drv_rwlock_rlock(ErlDrvRWLock
*rwlck)</nametext></name>
<fsummary>Read lock an rwlock.</fsummary>
<desc>
@@ -2733,7 +2733,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>void</ret><nametext>erl_drv_rwlock_runlock(ErlDrvRWLock
+ <name since=""><ret>void</ret><nametext>erl_drv_rwlock_runlock(ErlDrvRWLock
*rwlck)</nametext></name>
<fsummary>Read unlock an rwlock.</fsummary>
<desc>
@@ -2746,7 +2746,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>void</ret><nametext>erl_drv_rwlock_rwlock(ErlDrvRWLock
+ <name since=""><ret>void</ret><nametext>erl_drv_rwlock_rwlock(ErlDrvRWLock
*rwlck)</nametext></name>
<fsummary>Read/write lock an rwlock.</fsummary>
<desc>
@@ -2766,7 +2766,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>void</ret><nametext>erl_drv_rwlock_rwunlock(ErlDrvRWLock
+ <name since=""><ret>void</ret><nametext>erl_drv_rwlock_rwunlock(ErlDrvRWLock
*rwlck)</nametext></name>
<fsummary>Read/write unlock an rwlock.</fsummary>
<desc>
@@ -2779,7 +2779,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>int</ret><nametext>erl_drv_rwlock_tryrlock(ErlDrvRWLock
+ <name since=""><ret>int</ret><nametext>erl_drv_rwlock_tryrlock(ErlDrvRWLock
*rwlck)</nametext></name>
<fsummary>Try to read lock an rwlock.</fsummary>
<desc>
@@ -2799,7 +2799,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>int</ret><nametext>erl_drv_rwlock_tryrwlock(ErlDrvRWLock
+ <name since=""><ret>int</ret><nametext>erl_drv_rwlock_tryrwlock(ErlDrvRWLock
*rwlck)</nametext></name>
<fsummary>Try to read/write lock an rwlock.</fsummary>
<desc>
@@ -2819,7 +2819,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>int</ret><nametext>erl_drv_send_term(ErlDrvTermData port,
+ <name since="OTP R16B"><ret>int</ret><nametext>erl_drv_send_term(ErlDrvTermData port,
ErlDrvTermData receiver, ErlDrvTermData* term, int n)</nametext></name>
<fsummary>Send term data to other process than port owner process.
</fsummary>
@@ -2843,7 +2843,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>void</ret><nametext>erl_drv_set_os_pid(ErlDrvPort port,
+ <name since="OTP 19.0"><ret>void</ret><nametext>erl_drv_set_os_pid(ErlDrvPort port,
ErlDrvSInt pid)</nametext></name>
<fsummary>Set the os_pid for the port.</fsummary>
<desc>
@@ -2857,7 +2857,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>int</ret><nametext>erl_drv_thread_create(char *name, ErlDrvTid
+ <name since=""><ret>int</ret><nametext>erl_drv_thread_create(char *name, ErlDrvTid
*tid, void * (*func)(void *), void *arg, ErlDrvThreadOpts
*opts)</nametext></name>
<fsummary>Create a thread.</fsummary>
@@ -2920,7 +2920,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>void</ret><nametext>erl_drv_thread_exit(void
+ <name since=""><ret>void</ret><nametext>erl_drv_thread_exit(void
*exit_value)</nametext></name>
<fsummary>Terminate calling thread.</fsummary>
<desc>
@@ -2939,7 +2939,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>int</ret><nametext>erl_drv_thread_join(ErlDrvTid tid, void
+ <name since=""><ret>int</ret><nametext>erl_drv_thread_join(ErlDrvTid tid, void
**exit_value)</nametext></name>
<fsummary>Join with another thread.</fsummary>
<desc>
@@ -2962,7 +2962,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>char *</ret><nametext>erl_drv_thread_name(ErlDrvTid
+ <name since="OTP R16B02"><ret>char *</ret><nametext>erl_drv_thread_name(ErlDrvTid
tid)</nametext></name>
<fsummary>Get name of driver mutex.</fsummary>
<desc>
@@ -2976,7 +2976,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>ErlDrvThreadOpts *</ret>
+ <name since=""><ret>ErlDrvThreadOpts *</ret>
<nametext>erl_drv_thread_opts_create(char *name)</nametext></name>
<fsummary>Create thread options.</fsummary>
<desc>
@@ -3005,7 +3005,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>void</ret>
+ <name since=""><ret>void</ret>
<nametext>erl_drv_thread_opts_destroy(ErlDrvThreadOpts *opts)</nametext>
</name>
<fsummary>Destroy thread options.</fsummary>
@@ -3020,7 +3020,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>ErlDrvTid</ret>
+ <name since=""><ret>ErlDrvTid</ret>
<nametext>erl_drv_thread_self(void)</nametext></name>
<fsummary>Get the thread identifier of the current thread.</fsummary>
<desc>
@@ -3031,7 +3031,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>ErlDrvTime</ret><nametext>erl_drv_time_offset(ErlDrvTimeUnit
+ <name since="OTP 18.3"><ret>ErlDrvTime</ret><nametext>erl_drv_time_offset(ErlDrvTimeUnit
time_unit)</nametext></name>
<fsummary>Get current time offset.</fsummary>
<desc>
@@ -3054,7 +3054,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>void *</ret><nametext>erl_drv_tsd_get(ErlDrvTSDKey
+ <name since=""><ret>void *</ret><nametext>erl_drv_tsd_get(ErlDrvTSDKey
key)</nametext></name>
<fsummary>Get thread-specific data.</fsummary>
<desc>
@@ -3069,7 +3069,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>int</ret><nametext>erl_drv_tsd_key_create(char *name,
+ <name since=""><ret>int</ret><nametext>erl_drv_tsd_key_create(char *name,
ErlDrvTSDKey *key)</nametext></name>
<fsummary>Create a thread-specific data key.</fsummary>
<desc>
@@ -3086,7 +3086,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>void</ret><nametext>erl_drv_tsd_key_destroy(ErlDrvTSDKey
+ <name since=""><ret>void</ret><nametext>erl_drv_tsd_key_destroy(ErlDrvTSDKey
key)</nametext></name>
<fsummary>Destroy a thread-specific data key.</fsummary>
<desc>
@@ -3111,7 +3111,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>void</ret><nametext>erl_drv_tsd_set(ErlDrvTSDKey key, void
+ <name since=""><ret>void</ret><nametext>erl_drv_tsd_set(ErlDrvTSDKey key, void
*data)</nametext></name>
<fsummary>Set thread-specific data.</fsummary>
<desc>
@@ -3138,7 +3138,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>char *</ret><nametext>erl_errno_id(int error)</nametext></name>
+ <name since=""><ret>char *</ret><nametext>erl_errno_id(int error)</nametext></name>
<fsummary>Get Erlang error atom name from error number.</fsummary>
<desc>
<marker id="erl_errno_id"></marker>
@@ -3150,7 +3150,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>int</ret><nametext>remove_driver_entry(ErlDrvEntry
+ <name since=""><ret>int</ret><nametext>remove_driver_entry(ErlDrvEntry
*de)</nametext></name>
<fsummary>Remove a driver entry.</fsummary>
<desc>
@@ -3164,7 +3164,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>void</ret><nametext>set_busy_port(ErlDrvPort port, int
+ <name since=""><ret>void</ret><nametext>set_busy_port(ErlDrvPort port, int
on)</nametext></name>
<fsummary>Signal or unsignal port as busy.</fsummary>
<desc>
@@ -3195,7 +3195,7 @@ erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]
</func>
<func>
- <name><ret>void</ret><nametext>set_port_control_flags(ErlDrvPort port,
+ <name since=""><ret>void</ret><nametext>set_port_control_flags(ErlDrvPort port,
int flags)</nametext></name>
<fsummary>Set flags on how to handle control entry function.</fsummary>
<desc>
diff --git a/erts/doc/src/erl_ext_dist.xml b/erts/doc/src/erl_ext_dist.xml
index b7090d0472..4721747097 100644
--- a/erts/doc/src/erl_ext_dist.xml
+++ b/erts/doc/src/erl_ext_dist.xml
@@ -136,16 +136,10 @@
</note>
</section>
- <section>
+ <section>
+ <marker id="distribution_header"/>
<title>Distribution Header</title>
<p>
- <marker id="distribution_header"/>
- As from ERTS 5.7.2 the old atom cache protocol was
- dropped and a new one was introduced. This protocol
- introduced the distribution header. Nodes with an ERTS version
- earlier than 5.7.2 can still communicate with new nodes,
- but no distribution header and no atom cache are used.</p>
- <p>
The distribution header only contains an atom cache
reference section, but can in the future contain more
information. The distribution header precedes one or more Erlang
@@ -373,8 +367,9 @@
</row>
<tcaption>FLOAT_EXT</tcaption></table>
<p>
- A float is stored in string format. The format used in sprintf to
- format the float is "%.20e"
+ A finite float (i.e. not inf, -inf or NaN) is stored in
+ string format. The format used in sprintf to format the
+ float is "%.20e"
(there are more bytes allocated than necessary).
To unpack the float, use sscanf with format "%lf".
</p>
@@ -989,7 +984,8 @@
</row>
<tcaption>NEW_FLOAT_EXT</tcaption></table>
<p>
- A float is stored as 8 bytes in big-endian IEEE format.
+ A finite float (i.e. not inf, -inf or NaN) is stored as 8 bytes
+ in big-endian IEEE format.
</p>
<p>
This term is used in minor version 1 of the external format.
diff --git a/erts/doc/src/erl_nif.xml b/erts/doc/src/erl_nif.xml
index bbc12b0a56..cc7452bab5 100644
--- a/erts/doc/src/erl_nif.xml
+++ b/erts/doc/src/erl_nif.xml
@@ -783,14 +783,16 @@ typedef struct {
<p>A process identifier (pid). In contrast to pid terms (instances of
<c>ERL_NIF_TERM</c>), <c>ErlNifPid</c>s are self-contained and not
bound to any <seealso marker="#ErlNifEnv">environment</seealso>.
- <c>ErlNifPid</c> is an opaque type.</p>
+ <c>ErlNifPid</c> is an opaque type. It can be copied, moved
+ in memory, forgotten, and so on.</p>
</item>
<tag><marker id="ErlNifPort"/><c>ErlNifPort</c></tag>
<item>
<p>A port identifier. In contrast to port ID terms (instances of
<c>ERL_NIF_TERM</c>), <c>ErlNifPort</c>s are self-contained and not
bound to any <seealso marker="#ErlNifEnv">environment</seealso>.
- <c>ErlNifPort</c> is an opaque type.</p>
+ <c>ErlNifPort</c> is an opaque type. It can be copied, moved
+ in memory, forgotten, and so on.</p>
</item>
<tag><marker id="ErlNifResourceType"/><c>ErlNifResourceType</c></tag>
<item>
@@ -963,7 +965,7 @@ typedef struct {
<funcs>
<func>
- <name><ret>void *</ret><nametext>enif_alloc(size_t size)</nametext></name>
+ <name since=""><ret>void *</ret><nametext>enif_alloc(size_t size)</nametext></name>
<fsummary>Allocate dynamic memory.</fsummary>
<desc>
<p>Allocates memory of <c>size</c> bytes.</p>
@@ -974,7 +976,7 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret>
+ <name since=""><ret>int</ret>
<nametext>enif_alloc_binary(size_t size, ErlNifBinary* bin)</nametext>
</name>
<fsummary>Create a new binary.</fsummary>
@@ -998,7 +1000,7 @@ typedef struct {
</func>
<func>
- <name><ret>ErlNifEnv *</ret><nametext>enif_alloc_env()</nametext></name>
+ <name since="OTP R14B"><ret>ErlNifEnv *</ret><nametext>enif_alloc_env()</nametext></name>
<fsummary>Create a new environment.</fsummary>
<desc>
<p>Allocates a new process independent environment. The environment can
@@ -1012,7 +1014,7 @@ typedef struct {
</func>
<func>
- <name><ret>void *</ret><nametext>enif_alloc_resource(ErlNifResourceType*
+ <name since="OTP R13B04"><ret>void *</ret><nametext>enif_alloc_resource(ErlNifResourceType*
type, unsigned size)</nametext></name>
<fsummary>Allocate a memory-managed resource object.</fsummary>
<desc>
@@ -1022,7 +1024,7 @@ typedef struct {
</func>
<func>
- <name><ret>size_t</ret><nametext>enif_binary_to_term(ErlNifEnv *env,
+ <name since="OTP 19.0"><ret>size_t</ret><nametext>enif_binary_to_term(ErlNifEnv *env,
const unsigned char* data, size_t size, ERL_NIF_TERM *term,
ErlNifBinaryToTerm opts)</nametext></name>
<fsummary>Create a term from the external format.</fsummary>
@@ -1047,7 +1049,7 @@ typedef struct {
</func>
<func>
- <name><ret>void</ret><nametext>enif_clear_env(ErlNifEnv* env)</nametext>
+ <name since="OTP R14B"><ret>void</ret><nametext>enif_clear_env(ErlNifEnv* env)</nametext>
</name>
<fsummary>Clear an environment for reuse.</fsummary>
<desc>
@@ -1058,7 +1060,7 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP R13B04"><ret>int</ret>
<nametext>enif_compare(ERL_NIF_TERM lhs, ERL_NIF_TERM rhs)</nametext>
</name>
<fsummary>Compare two terms.</fsummary>
@@ -1073,7 +1075,7 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret><nametext>enif_compare_monitors(const ErlNifMonitor
+ <name since="OTP 20.0"><ret>int</ret><nametext>enif_compare_monitors(const ErlNifMonitor
*monitor1, const ErlNifMonitor *monitor2)</nametext></name>
<fsummary>Compare two monitors.</fsummary>
<desc>
@@ -1088,7 +1090,21 @@ typedef struct {
</func>
<func>
- <name><ret>void</ret>
+ <name since="OTP @OTP-15011@"><ret>int</ret>
+ <nametext>enif_compare_pids(const ErlNifPid *pid1, const ErlNifPid *pid2)
+ </nametext></name>
+ <fsummary>Compare two pids.</fsummary>
+ <desc>
+ <p>Compares two <seealso marker="#ErlNifPid"><c>ErlNifPid</c>
+ </seealso>s according to term order.</p>
+ <p>Returns <c>0</c> if <c>pid1</c> and <c>pid2</c> are equal,
+ &lt; <c>0</c> if <c>pid1</c> &lt; <c>pid2</c>, and
+ &gt; <c>0</c> if <c>pid1</c> &gt; <c>pid2</c>.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="OTP R13B04"><ret>void</ret>
<nametext>enif_cond_broadcast(ErlNifCond *cnd)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -1098,7 +1114,7 @@ typedef struct {
</func>
<func>
- <name><ret>ErlNifCond *</ret>
+ <name since="OTP R13B04"><ret>ErlNifCond *</ret>
<nametext>enif_cond_create(char *name)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -1108,7 +1124,7 @@ typedef struct {
</func>
<func>
- <name><ret>void</ret>
+ <name since="OTP R13B04"><ret>void</ret>
<nametext>enif_cond_destroy(ErlNifCond *cnd)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -1118,7 +1134,7 @@ typedef struct {
</func>
<func>
- <name><ret>char*</ret>
+ <name since="OTP 21.0"><ret>char*</ret>
<nametext>enif_cond_name(ErlNifCond* cnd)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -1128,7 +1144,7 @@ typedef struct {
</func>
<func>
- <name><ret>void</ret>
+ <name since="OTP R13B04"><ret>void</ret>
<nametext>enif_cond_signal(ErlNifCond *cnd)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -1138,7 +1154,7 @@ typedef struct {
</func>
<func>
- <name><ret>void</ret>
+ <name since="OTP R13B04"><ret>void</ret>
<nametext>enif_cond_wait(ErlNifCond *cnd, ErlNifMutex *mtx)</nametext>
</name>
<fsummary></fsummary>
@@ -1149,7 +1165,7 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP R16B"><ret>int</ret>
<nametext>enif_consume_timeslice(ErlNifEnv *env, int percent)</nametext>
</name>
<fsummary></fsummary>
@@ -1184,7 +1200,7 @@ typedef struct {
</func>
<func>
- <name><ret>ErlNifTime</ret><nametext>enif_convert_time_unit(ErlNifTime
+ <name since="OTP 18.3"><ret>ErlNifTime</ret><nametext>enif_convert_time_unit(ErlNifTime
val, ErlNifTimeUnit from, ErlNifTimeUnit to)</nametext></name>
<fsummary>Convert time unit of a time value.</fsummary>
<desc>
@@ -1209,7 +1225,7 @@ typedef struct {
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret>
+ <name since="OTP 19.0"><ret>ERL_NIF_TERM</ret>
<nametext>enif_cpu_time(ErlNifEnv *)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -1225,8 +1241,8 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret><nametext>enif_demonitor_process(ErlNifEnv* caller_env, void* obj,
- const ErlNifMonitor* mon)</nametext></name>
+ <name since="OTP 20.0"><ret>int</ret><nametext>enif_demonitor_process(ErlNifEnv* caller_env,
+ void* obj, const ErlNifMonitor* mon)</nametext></name>
<fsummary>Cancel a process monitor.</fsummary>
<desc>
<marker id="enif_demonitor_process"></marker>
@@ -1252,7 +1268,7 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP R13B04"><ret>int</ret>
<nametext>enif_equal_tids(ErlNifTid tid1, ErlNifTid tid2)</nametext>
</name>
<fsummary></fsummary>
@@ -1263,7 +1279,7 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret><nametext>enif_fprintf(FILE *stream, const char *format, ...)</nametext></name>
+ <name since="OTP 21.0"><ret>int</ret><nametext>enif_fprintf(FILE *stream, const char *format, ...)</nametext></name>
<fsummary>Format strings and Erlang terms.</fsummary>
<desc>
<p>Similar to <c>fprintf</c> but this format string also accepts
@@ -1276,7 +1292,7 @@ typedef struct {
</func>
<func>
- <name><ret>void</ret><nametext>enif_free(void* ptr)</nametext></name>
+ <name since=""><ret>void</ret><nametext>enif_free(void* ptr)</nametext></name>
<fsummary>Free dynamic memory.</fsummary>
<desc>
<p>Frees memory allocated by
@@ -1285,7 +1301,7 @@ typedef struct {
</func>
<func>
- <name><ret>void</ret>
+ <name since="OTP R14B"><ret>void</ret>
<nametext>enif_free_env(ErlNifEnv* env)</nametext></name>
<fsummary>Free an environment allocated with enif_alloc_env.</fsummary>
<desc>
@@ -1296,7 +1312,7 @@ typedef struct {
</func>
<func>
- <name><ret>void</ret>
+ <name since="OTP 20.1"><ret>void</ret>
<nametext>enif_free_iovec(ErlNifIOvec* iov)</nametext></name>
<fsummary>Free an ErlIOVec</fsummary>
<desc>
@@ -1321,7 +1337,7 @@ enif_free_iovec(iovec);]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_get_atom(ErlNifEnv* env, ERL_NIF_TERM
+ <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>
<fsummary>Get the text representation of an atom term.</fsummary>
@@ -1337,7 +1353,7 @@ enif_free_iovec(iovec);]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_get_atom_length(ErlNifEnv* env,
+ <name since="OTP R14B"><ret>int</ret><nametext>enif_get_atom_length(ErlNifEnv* env,
ERL_NIF_TERM term, unsigned* len, ErlNifCharEncoding encode)</nametext>
</name>
<fsummary>Get the length of atom <c>term</c>.</fsummary>
@@ -1351,7 +1367,7 @@ enif_free_iovec(iovec);]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_get_double(ErlNifEnv* env,
+ <name since="OTP R13B04"><ret>int</ret><nametext>enif_get_double(ErlNifEnv* env,
ERL_NIF_TERM term, double* dp)</nametext></name>
<fsummary>Read a floating-point number term.</fsummary>
<desc>
@@ -1362,7 +1378,7 @@ enif_free_iovec(iovec);]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_get_int(ErlNifEnv* env, ERL_NIF_TERM
+ <name since=""><ret>int</ret><nametext>enif_get_int(ErlNifEnv* env, ERL_NIF_TERM
term, int* ip)</nametext></name>
<fsummary>Read an integer term.</fsummary>
<desc>
@@ -1373,7 +1389,7 @@ enif_free_iovec(iovec);]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_get_int64(ErlNifEnv* env, ERL_NIF_TERM
+ <name since="OTP R14B"><ret>int</ret><nametext>enif_get_int64(ErlNifEnv* env, ERL_NIF_TERM
term, ErlNifSInt64* ip)</nametext></name>
<fsummary>Read a 64-bit integer term.</fsummary>
<desc>
@@ -1384,7 +1400,7 @@ enif_free_iovec(iovec);]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_get_local_pid(ErlNifEnv* env,
+ <name since="OTP R14B"><ret>int</ret><nametext>enif_get_local_pid(ErlNifEnv* env,
ERL_NIF_TERM term, ErlNifPid* pid)</nametext></name>
<fsummary>Read a local pid term.</fsummary>
<desc>
@@ -1392,11 +1408,14 @@ enif_free_iovec(iovec);]]></code>
initializes the pid variable <c>*pid</c> from it and returns
<c>true</c>. Otherwise returns <c>false</c>. No check is done to see
if the process is alive.</p>
+ <note><p><c>enif_get_local_pid</c> will return false if argument
+ <c>term</c> is the atom <seealso marker="#enif_make_pid">
+ <c>undefined</c></seealso>.</p></note>
</desc>
</func>
<func>
- <name><ret>int</ret><nametext>enif_get_local_port(ErlNifEnv* env,
+ <name since="OTP 19.0"><ret>int</ret><nametext>enif_get_local_port(ErlNifEnv* env,
ERL_NIF_TERM term, ErlNifPort* port_id)</nametext></name>
<fsummary>Read a local port term.</fsummary>
<desc>
@@ -1408,7 +1427,7 @@ enif_free_iovec(iovec);]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_get_list_cell(ErlNifEnv* env,
+ <name since=""><ret>int</ret><nametext>enif_get_list_cell(ErlNifEnv* env,
ERL_NIF_TERM list, ERL_NIF_TERM* head, ERL_NIF_TERM* tail)</nametext>
</name>
<fsummary>Get head and tail from a list.</fsummary>
@@ -1420,7 +1439,7 @@ enif_free_iovec(iovec);]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_get_list_length(ErlNifEnv* env,
+ <name since="OTP R14B"><ret>int</ret><nametext>enif_get_list_length(ErlNifEnv* env,
ERL_NIF_TERM term, unsigned* len)</nametext></name>
<fsummary>Get the length of list <c>term</c>.</fsummary>
<desc>
@@ -1431,7 +1450,7 @@ enif_free_iovec(iovec);]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_get_long(ErlNifEnv* env, ERL_NIF_TERM
+ <name since="OTP R13B04"><ret>int</ret><nametext>enif_get_long(ErlNifEnv* env, ERL_NIF_TERM
term, long int* ip)</nametext></name>
<fsummary>Read a long integer term.</fsummary>
<desc>
@@ -1442,7 +1461,7 @@ enif_free_iovec(iovec);]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_get_map_size(ErlNifEnv* env,
+ <name since="OTP 18.0"><ret>int</ret><nametext>enif_get_map_size(ErlNifEnv* env,
ERL_NIF_TERM term, size_t *size)</nametext></name>
<fsummary>Read the size of a map term.</fsummary>
<desc>
@@ -1454,7 +1473,7 @@ enif_free_iovec(iovec);]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_get_map_value(ErlNifEnv* env,
+ <name since="OTP 18.0"><ret>int</ret><nametext>enif_get_map_value(ErlNifEnv* env,
ERL_NIF_TERM map, ERL_NIF_TERM key, ERL_NIF_TERM* value)</nametext>
</name>
<fsummary>Get the value of a key in a map.</fsummary>
@@ -1467,7 +1486,7 @@ enif_free_iovec(iovec);]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_get_resource(ErlNifEnv* env,
+ <name since="OTP R13B04"><ret>int</ret><nametext>enif_get_resource(ErlNifEnv* env,
ERL_NIF_TERM term, ErlNifResourceType* type, void** objp)</nametext>
</name>
<fsummary>Get the pointer to a resource object.</fsummary>
@@ -1480,7 +1499,7 @@ enif_free_iovec(iovec);]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_get_string(ErlNifEnv* env,
+ <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>
<fsummary>Get a C-string from a list.</fsummary>
@@ -1504,7 +1523,7 @@ enif_free_iovec(iovec);]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_get_tuple(ErlNifEnv* env, ERL_NIF_TERM
+ <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>
<fsummary>Inspect the elements of a tuple.</fsummary>
<desc>
@@ -1520,7 +1539,7 @@ enif_free_iovec(iovec);]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_get_uint(ErlNifEnv* env, ERL_NIF_TERM
+ <name since="OTP R13B04"><ret>int</ret><nametext>enif_get_uint(ErlNifEnv* env, ERL_NIF_TERM
term, unsigned int* ip)</nametext></name>
<fsummary>Read an unsigned integer term.</fsummary>
<desc>
@@ -1532,7 +1551,7 @@ enif_free_iovec(iovec);]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_get_uint64(ErlNifEnv* env,
+ <name since="OTP R14B"><ret>int</ret><nametext>enif_get_uint64(ErlNifEnv* env,
ERL_NIF_TERM term, ErlNifUInt64* ip)</nametext></name>
<fsummary>Read an unsigned 64-bit integer term.</fsummary>
<desc>
@@ -1544,7 +1563,7 @@ enif_free_iovec(iovec);]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_get_ulong(ErlNifEnv* env, ERL_NIF_TERM
+ <name since=""><ret>int</ret><nametext>enif_get_ulong(ErlNifEnv* env, ERL_NIF_TERM
term, unsigned long* ip)</nametext></name>
<fsummary>Read an unsigned integer term.</fsummary>
<desc>
@@ -1557,7 +1576,7 @@ enif_free_iovec(iovec);]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_getenv(const char* key, char* value,
+ <name since="OTP 18.2"><ret>int</ret><nametext>enif_getenv(const char* key, char* value,
size_t *value_size)</nametext></name>
<fsummary>Get the value of an environment variable.</fsummary>
<desc>
@@ -1567,7 +1586,7 @@ enif_free_iovec(iovec);]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_has_pending_exception(ErlNifEnv* env,
+ <name since="OTP 18.0"><ret>int</ret><nametext>enif_has_pending_exception(ErlNifEnv* env,
ERL_NIF_TERM* reason)</nametext></name>
<fsummary>Check if an exception has been raised.</fsummary>
<desc>
@@ -1588,7 +1607,7 @@ enif_free_iovec(iovec);]]></code>
</func>
<func>
- <name>
+ <name since="OTP 20.0">
<ret>ErlNifUInt64</ret>
<nametext>enif_hash(ErlNifHash type, ERL_NIF_TERM term, ErlNifUInt64 salt)</nametext>
</name>
@@ -1601,7 +1620,7 @@ enif_free_iovec(iovec);]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_inspect_binary(ErlNifEnv* env,
+ <name since=""><ret>int</ret><nametext>enif_inspect_binary(ErlNifEnv* env,
ERL_NIF_TERM bin_term, ErlNifBinary* bin)</nametext></name>
<fsummary>Inspect the content of a binary.</fsummary>
<desc>
@@ -1613,7 +1632,7 @@ enif_free_iovec(iovec);]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_inspect_iolist_as_binary(ErlNifEnv*
+ <name since="OTP R13B04"><ret>int</ret><nametext>enif_inspect_iolist_as_binary(ErlNifEnv*
env, ERL_NIF_TERM term, ErlNifBinary* bin)</nametext></name>
<fsummary>Inspect the content of an iolist.</fsummary>
<desc>
@@ -1627,7 +1646,7 @@ enif_free_iovec(iovec);]]></code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_inspect_iovec(ErlNifEnv*
+ <name since="OTP 20.1"><ret>int</ret><nametext>enif_inspect_iovec(ErlNifEnv*
env, size_t max_elements, ERL_NIF_TERM iovec_term, ERL_NIF_TERM* tail,
ErlNifIOVec** iovec)</nametext></name>
<fsummary>Inspect a list of binaries as an ErlNifIOVec.</fsummary>
@@ -1667,7 +1686,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>ErlNifIOQueue *</ret>
+ <name since="OTP 20.1"><ret>ErlNifIOQueue *</ret>
<nametext>enif_ioq_create(ErlNifIOQueueOpts opts)</nametext></name>
<fsummary>Create a new IO Queue</fsummary>
<desc>
@@ -1678,7 +1697,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>void</ret>
+ <name since="OTP 20.1"><ret>void</ret>
<nametext>enif_ioq_destroy(ErlNifIOQueue *q)</nametext></name>
<fsummary>Destroy an IO Queue and free it's content</fsummary>
<desc>
@@ -1687,7 +1706,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP 20.1"><ret>int</ret>
<nametext>enif_ioq_deq(ErlNifIOQueue *q, size_t count, size_t *size)</nametext></name>
<fsummary>Dequeue count bytes from the IO Queue</fsummary>
<desc>
@@ -1700,7 +1719,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP 20.1"><ret>int</ret>
<nametext>enif_ioq_enq_binary(ErlNifIOQueue *q, ErlNifBinary *bin, size_t skip)</nametext></name>
<fsummary>Enqueue the binary into the IO Queue</fsummary>
<desc>
@@ -1713,7 +1732,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP 20.1"><ret>int</ret>
<nametext>enif_ioq_enqv(ErlNifIOQueue *q, ErlNifIOVec *iovec, size_t skip)</nametext></name>
<fsummary>Enqueue the iovec into the IO Queue</fsummary>
<desc>
@@ -1724,7 +1743,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>SysIOVec *</ret>
+ <name since="OTP 20.1"><ret>SysIOVec *</ret>
<nametext>enif_ioq_peek(ErlNifIOQueue *q, int *iovlen)</nametext></name>
<fsummary>Peek inside the IO Queue</fsummary>
<desc>
@@ -1738,7 +1757,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP 21.0"><ret>int</ret>
<nametext>enif_ioq_peek_head(ErlNifEnv *env, ErlNifIOQueue *q, size_t *size, ERL_NIF_TERM *bin_term)</nametext></name>
<fsummary>Peek the head of the IO Queue.</fsummary>
<desc>
@@ -1753,7 +1772,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>size_t</ret>
+ <name since="OTP 20.1"><ret>size_t</ret>
<nametext>enif_ioq_size(ErlNifIOQueue *q)</nametext></name>
<fsummary>Get the current size of the IO Queue</fsummary>
<desc>
@@ -1762,7 +1781,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP R13B04"><ret>int</ret>
<nametext>enif_is_atom(ErlNifEnv* env, ERL_NIF_TERM term)</nametext>
</name>
<fsummary>Determine if a term is an atom.</fsummary>
@@ -1772,7 +1791,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret>
+ <name since=""><ret>int</ret>
<nametext>enif_is_binary(ErlNifEnv* env, ERL_NIF_TERM term)</nametext>
</name>
<fsummary>Determine if a term is a binary.</fsummary>
@@ -1782,7 +1801,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP 19.0"><ret>int</ret>
<nametext>enif_is_current_process_alive(ErlNifEnv* env)</nametext>
</name>
<fsummary>Determine if currently executing process is alive.</fsummary>
@@ -1795,7 +1814,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret><nametext>enif_is_empty_list(ErlNifEnv* env,
+ <name since="OTP R13B04"><ret>int</ret><nametext>enif_is_empty_list(ErlNifEnv* env,
ERL_NIF_TERM term)</nametext></name>
<fsummary>Determine if a term is an empty list.</fsummary>
<desc>
@@ -1804,7 +1823,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret><nametext>enif_is_exception(ErlNifEnv* env,
+ <name since="OTP R14B03"><ret>int</ret><nametext>enif_is_exception(ErlNifEnv* env,
ERL_NIF_TERM term)</nametext></name>
<fsummary>Determine if a term is an exception.</fsummary>
<desc><marker id="enif_is_exception"/>
@@ -1813,7 +1832,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret><nametext>enif_is_fun(ErlNifEnv* env, ERL_NIF_TERM
+ <name since="OTP R13B04"><ret>int</ret><nametext>enif_is_fun(ErlNifEnv* env, ERL_NIF_TERM
term)</nametext></name>
<fsummary>Determine if a term is a fun.</fsummary>
<desc>
@@ -1822,7 +1841,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret><nametext>enif_is_identical(ERL_NIF_TERM lhs,
+ <name since="OTP R13B04"><ret>int</ret><nametext>enif_is_identical(ERL_NIF_TERM lhs,
ERL_NIF_TERM rhs)</nametext></name>
<fsummary>Erlang operator =:=.</fsummary>
<desc>
@@ -1832,7 +1851,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP R14B"><ret>int</ret>
<nametext>enif_is_list(ErlNifEnv* env, ERL_NIF_TERM term)</nametext>
</name>
<fsummary>Determine if a term is a list.</fsummary>
@@ -1842,7 +1861,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret><nametext>enif_is_map(ErlNifEnv* env, ERL_NIF_TERM
+ <name since="OTP 18.0"><ret>int</ret><nametext>enif_is_map(ErlNifEnv* env, ERL_NIF_TERM
term)</nametext></name>
<fsummary>Determine if a term is a map.</fsummary>
<desc>
@@ -1852,7 +1871,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret><nametext>enif_is_number(ErlNifEnv* env, ERL_NIF_TERM
+ <name since="OTP R15B"><ret>int</ret><nametext>enif_is_number(ErlNifEnv* env, ERL_NIF_TERM
term)</nametext></name>
<fsummary>Determine if a term is a number (integer or float).</fsummary>
<desc>
@@ -1861,7 +1880,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP R13B04"><ret>int</ret>
<nametext>enif_is_pid(ErlNifEnv* env, ERL_NIF_TERM term)</nametext>
</name>
<fsummary>Determine if a term is a pid.</fsummary>
@@ -1871,7 +1890,18 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP @OTP-15011@"><ret>int</ret>
+ <nametext>enif_is_pid_undefined(const ErlNifPid* pid)</nametext></name>
+ <fsummary>Determine if pid is undefined.</fsummary>
+ <desc>
+ <p>Returns <c>true</c> if <c>pid</c> has been set as undefined by
+ <seealso marker="#enif_set_pid_undefined"><c>enif_set_pid_undefined</c>
+ </seealso>.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="OTP R13B04"><ret>int</ret>
<nametext>enif_is_port(ErlNifEnv* env, ERL_NIF_TERM term)</nametext>
</name>
<fsummary>Determine if a term is a port.</fsummary>
@@ -1881,7 +1911,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret><nametext>enif_is_port_alive(ErlNifEnv* env,
+ <name since="OTP 19.0"><ret>int</ret><nametext>enif_is_port_alive(ErlNifEnv* env,
ErlNifPort *port_id)</nametext></name>
<fsummary>Determine if a local port is alive.</fsummary>
<desc>
@@ -1893,7 +1923,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret><nametext>enif_is_process_alive(ErlNifEnv* env,
+ <name since="OTP 19.0"><ret>int</ret><nametext>enif_is_process_alive(ErlNifEnv* env,
ErlNifPid *pid)</nametext></name>
<fsummary>Determine if a local process is alive.</fsummary>
<desc>
@@ -1905,7 +1935,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP R13B04"><ret>int</ret>
<nametext>enif_is_ref(ErlNifEnv* env, ERL_NIF_TERM term)</nametext>
</name>
<fsummary>Determine if a term is a reference.</fsummary>
@@ -1915,7 +1945,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP R14B"><ret>int</ret>
<nametext>enif_is_tuple(ErlNifEnv* env, ERL_NIF_TERM term)</nametext>
</name>
<fsummary>Determine if a term is a tuple.</fsummary>
@@ -1925,7 +1955,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP R14B"><ret>int</ret>
<nametext>enif_keep_resource(void* obj)</nametext>
</name>
<fsummary>Add a reference to a resource object.</fsummary>
@@ -1941,7 +1971,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret>
+ <name since=""><ret>ERL_NIF_TERM</ret>
<nametext>enif_make_atom(ErlNifEnv* env, const char* name)</nametext>
</name>
<fsummary>Create an atom term.</fsummary>
@@ -1955,7 +1985,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret><nametext>enif_make_atom_len(ErlNifEnv* env,
+ <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>
@@ -1969,7 +1999,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret>
+ <name since=""><ret>ERL_NIF_TERM</ret>
<nametext>enif_make_badarg(ErlNifEnv* env)</nametext></name>
<fsummary>Make a badarg exception.</fsummary>
<desc>
@@ -1997,7 +2027,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret>
+ <name since=""><ret>ERL_NIF_TERM</ret>
<nametext>enif_make_binary(ErlNifEnv* env, ErlNifBinary* bin)</nametext>
</name>
<fsummary>Make a binary term.</fsummary>
@@ -2010,7 +2040,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret><nametext>enif_make_copy(ErlNifEnv* dst_env,
+ <name since="OTP R14B"><ret>ERL_NIF_TERM</ret><nametext>enif_make_copy(ErlNifEnv* dst_env,
ERL_NIF_TERM src_term)</nametext></name>
<fsummary>Make a copy of a term.</fsummary>
<desc>
@@ -2021,7 +2051,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret>
+ <name since="OTP R13B04"><ret>ERL_NIF_TERM</ret>
<nametext>enif_make_double(ErlNifEnv* env, double d)</nametext></name>
<fsummary>Create a floating-point term.</fsummary>
<desc>
@@ -2033,7 +2063,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret><nametext>enif_make_existing_atom(ErlNifEnv* env,
+ <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>
<fsummary>Create an existing atom term.</fsummary>
@@ -2049,7 +2079,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret><nametext>enif_make_existing_atom_len(ErlNifEnv* env,
+ <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>
@@ -2065,7 +2095,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</desc>
</func>
- <func><name><ret>ERL_NIF_TERM</ret>
+ <func><name since=""><ret>ERL_NIF_TERM</ret>
<nametext>enif_make_int(ErlNifEnv* env, int i)</nametext></name>
<fsummary>Create an integer term.</fsummary>
<desc>
@@ -2074,7 +2104,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret>
+ <name since="OTP R14B"><ret>ERL_NIF_TERM</ret>
<nametext>enif_make_int64(ErlNifEnv* env, ErlNifSInt64 i)</nametext>
</name>
<fsummary>Create an integer term.</fsummary>
@@ -2084,7 +2114,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret>
+ <name since=""><ret>ERL_NIF_TERM</ret>
<nametext>enif_make_list(ErlNifEnv* env, unsigned cnt, ...)</nametext>
</name>
<fsummary>Create a list term.</fsummary>
@@ -2097,24 +2127,24 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret>
+ <name since="OTP R13B04"><ret>ERL_NIF_TERM</ret>
<nametext>enif_make_list1(ErlNifEnv* env, ERL_NIF_TERM e1)</nametext>
</name>
- <name><ret>ERL_NIF_TERM</ret><nametext>enif_make_list2(ErlNifEnv* env,
+ <name since="OTP R13B04"><ret>ERL_NIF_TERM</ret><nametext>enif_make_list2(ErlNifEnv* env,
ERL_NIF_TERM e1, ERL_NIF_TERM e2)</nametext></name>
- <name><ret>ERL_NIF_TERM</ret><nametext>enif_make_list3(ErlNifEnv* env,
+ <name since="OTP R13B04"><ret>ERL_NIF_TERM</ret><nametext>enif_make_list3(ErlNifEnv* env,
ERL_NIF_TERM e1, ERL_NIF_TERM e2, ERL_NIF_TERM e3)</nametext></name>
- <name><ret>ERL_NIF_TERM</ret><nametext>enif_make_list4(ErlNifEnv* env,
+ <name since="OTP R13B04"><ret>ERL_NIF_TERM</ret><nametext>enif_make_list4(ErlNifEnv* env,
ERL_NIF_TERM e1, ..., ERL_NIF_TERM e4)</nametext></name>
- <name><ret>ERL_NIF_TERM</ret><nametext>enif_make_list5(ErlNifEnv* env,
+ <name since="OTP R13B04"><ret>ERL_NIF_TERM</ret><nametext>enif_make_list5(ErlNifEnv* env,
ERL_NIF_TERM e1, ..., ERL_NIF_TERM e5)</nametext></name>
- <name><ret>ERL_NIF_TERM</ret><nametext>enif_make_list6(ErlNifEnv* env,
+ <name since="OTP R13B04"><ret>ERL_NIF_TERM</ret><nametext>enif_make_list6(ErlNifEnv* env,
ERL_NIF_TERM e1, ..., ERL_NIF_TERM e6)</nametext></name>
- <name><ret>ERL_NIF_TERM</ret><nametext>enif_make_list7(ErlNifEnv* env,
+ <name since="OTP R13B04"><ret>ERL_NIF_TERM</ret><nametext>enif_make_list7(ErlNifEnv* env,
ERL_NIF_TERM e1, ..., ERL_NIF_TERM e7)</nametext></name>
- <name><ret>ERL_NIF_TERM</ret><nametext>enif_make_list8(ErlNifEnv* env,
+ <name since="OTP R13B04"><ret>ERL_NIF_TERM</ret><nametext>enif_make_list8(ErlNifEnv* env,
ERL_NIF_TERM e1, ..., ERL_NIF_TERM e8)</nametext></name>
- <name><ret>ERL_NIF_TERM</ret><nametext>enif_make_list9(ErlNifEnv* env,
+ <name since="OTP R13B04"><ret>ERL_NIF_TERM</ret><nametext>enif_make_list9(ErlNifEnv* env,
ERL_NIF_TERM e1, ..., ERL_NIF_TERM e9)</nametext></name>
<fsummary>Create a list term.</fsummary>
<desc>
@@ -2126,7 +2156,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret><nametext>enif_make_list_cell(ErlNifEnv*
+ <name since=""><ret>ERL_NIF_TERM</ret><nametext>enif_make_list_cell(ErlNifEnv*
env, ERL_NIF_TERM head, ERL_NIF_TERM tail)</nametext></name>
<fsummary>Create a list cell.</fsummary>
<desc>
@@ -2135,7 +2165,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret>
+ <name since="OTP R13B04"><ret>ERL_NIF_TERM</ret>
<nametext>enif_make_list_from_array(ErlNifEnv* env, const ERL_NIF_TERM
arr[], unsigned cnt)</nametext></name>
<fsummary>Create a list term from an array.</fsummary>
@@ -2147,7 +2177,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret>
+ <name since="OTP R13B04"><ret>ERL_NIF_TERM</ret>
<nametext>enif_make_long(ErlNifEnv* env, long int i)</nametext></name>
<fsummary>Create an integer term from a long int.</fsummary>
<desc>
@@ -2156,7 +2186,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret><nametext>enif_make_map_put(ErlNifEnv* env,
+ <name since="OTP 18.0"><ret>int</ret><nametext>enif_make_map_put(ErlNifEnv* env,
ERL_NIF_TERM map_in, ERL_NIF_TERM key, ERL_NIF_TERM value,
ERL_NIF_TERM* map_out)</nametext></name>
<fsummary>Insert key-value pair in map.</fsummary>
@@ -2172,7 +2202,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret><nametext>enif_make_map_remove(ErlNifEnv* env,
+ <name since="OTP 18.0"><ret>int</ret><nametext>enif_make_map_remove(ErlNifEnv* env,
ERL_NIF_TERM map_in, ERL_NIF_TERM key, ERL_NIF_TERM* map_out)</nametext>
</name>
<fsummary>Remove key from map.</fsummary>
@@ -2188,7 +2218,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret><nametext>enif_make_map_update(ErlNifEnv* env,
+ <name since="OTP 18.0"><ret>int</ret><nametext>enif_make_map_update(ErlNifEnv* env,
ERL_NIF_TERM map_in, ERL_NIF_TERM key, ERL_NIF_TERM new_value,
ERL_NIF_TERM* map_out)</nametext></name>
<fsummary>Replace value for key in map.</fsummary>
@@ -2203,7 +2233,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP 21.0"><ret>int</ret>
<nametext>enif_make_map_from_arrays(ErlNifEnv* env, ERL_NIF_TERM keys[],
ERL_NIF_TERM values[], size_t cnt, ERL_NIF_TERM *map_out)</nametext></name>
<fsummary>Make map term from the given keys and values.</fsummary>
@@ -2217,7 +2247,19 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>unsigned char *</ret><nametext>enif_make_new_binary(ErlNifEnv*
+ <name since="OTP @OTP-15362@"><ret>ERL_NIF_TERM</ret>
+ <nametext>enif_make_monitor_term(ErlNifEnv* env, const ErlNifMonitor* mon)</nametext></name>
+ <fsummary>Make monitor term from the given monitor identifier.</fsummary>
+ <desc>
+ <p>Creates a term identifying the given monitor received from
+ <seealso marker="#enif_monitor_process"><c>enif_monitor_process</c>
+ </seealso>.</p>
+ <p>This function is primarily intended for debugging purpose.</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>
<desc>
@@ -2233,7 +2275,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret>
+ <name since="OTP 18.0"><ret>ERL_NIF_TERM</ret>
<nametext>enif_make_new_map(ErlNifEnv* env)</nametext></name>
<fsummary>Make an empty map term.</fsummary>
<desc>
@@ -2242,17 +2284,18 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret>
+ <name since="OTP R14B"><ret>ERL_NIF_TERM</ret>
<nametext>enif_make_pid(ErlNifEnv* env, const ErlNifPid* pid)</nametext>
</name>
<fsummary>Make a pid term.</fsummary>
<desc>
- <p>Makes a pid term from <c>*pid</c>.</p>
+ <p>Makes a pid term or the atom <seealso marker="#enif_set_pid_undefined">
+ <c>undefined</c></seealso> from <c>*pid</c>.</p>
</desc>
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret>
+ <name since="OTP R13B04"><ret>ERL_NIF_TERM</ret>
<nametext>enif_make_ref(ErlNifEnv* env)</nametext></name>
<fsummary>Create a reference.</fsummary>
<desc>
@@ -2262,7 +2305,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret>
+ <name since="OTP R13B04"><ret>ERL_NIF_TERM</ret>
<nametext>enif_make_resource(ErlNifEnv* env, void* obj)</nametext>
</name>
<fsummary>Create an opaque handle to a resource object.</fsummary>
@@ -2310,7 +2353,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret>
+ <name since="OTP R14B"><ret>ERL_NIF_TERM</ret>
<nametext>enif_make_resource_binary(ErlNifEnv* env, void* obj, const
void* data, size_t size)</nametext></name>
<fsummary>Create a custom binary term.</fsummary>
@@ -2336,7 +2379,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP R15B"><ret>int</ret>
<nametext>enif_make_reverse_list(ErlNifEnv* env, ERL_NIF_TERM list_in,
ERL_NIF_TERM *list_out)</nametext></name>
<fsummary>Create the reverse of a list.</fsummary>
@@ -2352,7 +2395,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret><nametext>enif_make_string(ErlNifEnv* env,
+ <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>
@@ -2363,7 +2406,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret><nametext>enif_make_string_len(ErlNifEnv*
+ <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>
@@ -2376,7 +2419,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret><nametext>enif_make_sub_binary(ErlNifEnv*
+ <name since="OTP R13B04"><ret>ERL_NIF_TERM</ret><nametext>enif_make_sub_binary(ErlNifEnv*
env, ERL_NIF_TERM bin_term, size_t pos, size_t size)</nametext></name>
<fsummary>Make a subbinary term.</fsummary>
<desc>
@@ -2388,7 +2431,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret><nametext>enif_make_tuple(ErlNifEnv* env,
+ <name since=""><ret>ERL_NIF_TERM</ret><nametext>enif_make_tuple(ErlNifEnv* env,
unsigned cnt, ...)</nametext></name>
<fsummary>Creates a tuple term.</fsummary>
<desc>
@@ -2399,23 +2442,23 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret><nametext>enif_make_tuple1(ErlNifEnv* env,
+ <name since="OTP R13B04"><ret>ERL_NIF_TERM</ret><nametext>enif_make_tuple1(ErlNifEnv* env,
ERL_NIF_TERM e1)</nametext></name>
- <name><ret>ERL_NIF_TERM</ret><nametext>enif_make_tuple2(ErlNifEnv* env,
+ <name since="OTP R13B04"><ret>ERL_NIF_TERM</ret><nametext>enif_make_tuple2(ErlNifEnv* env,
ERL_NIF_TERM e1, ERL_NIF_TERM e2)</nametext></name>
- <name><ret>ERL_NIF_TERM</ret><nametext>enif_make_tuple3(ErlNifEnv* env,
+ <name since="OTP R13B04"><ret>ERL_NIF_TERM</ret><nametext>enif_make_tuple3(ErlNifEnv* env,
ERL_NIF_TERM e1, ERL_NIF_TERM e2, ERL_NIF_TERM e3)</nametext></name>
- <name><ret>ERL_NIF_TERM</ret><nametext>enif_make_tuple4(ErlNifEnv* env,
+ <name since="OTP R13B04"><ret>ERL_NIF_TERM</ret><nametext>enif_make_tuple4(ErlNifEnv* env,
ERL_NIF_TERM e1, ..., ERL_NIF_TERM e4)</nametext></name>
- <name><ret>ERL_NIF_TERM</ret><nametext>enif_make_tuple5(ErlNifEnv* env,
+ <name since="OTP R13B04"><ret>ERL_NIF_TERM</ret><nametext>enif_make_tuple5(ErlNifEnv* env,
ERL_NIF_TERM e1, ..., ERL_NIF_TERM e5)</nametext></name>
- <name><ret>ERL_NIF_TERM</ret><nametext>enif_make_tuple6(ErlNifEnv* env,
+ <name since="OTP R13B04"><ret>ERL_NIF_TERM</ret><nametext>enif_make_tuple6(ErlNifEnv* env,
ERL_NIF_TERM e1, ..., ERL_NIF_TERM e6)</nametext></name>
- <name><ret>ERL_NIF_TERM</ret><nametext>enif_make_tuple7(ErlNifEnv* env,
+ <name since="OTP R13B04"><ret>ERL_NIF_TERM</ret><nametext>enif_make_tuple7(ErlNifEnv* env,
ERL_NIF_TERM e1, ..., ERL_NIF_TERM e7)</nametext></name>
- <name><ret>ERL_NIF_TERM</ret><nametext>enif_make_tuple8(ErlNifEnv* env,
+ <name since="OTP R13B04"><ret>ERL_NIF_TERM</ret><nametext>enif_make_tuple8(ErlNifEnv* env,
ERL_NIF_TERM e1, ..., ERL_NIF_TERM e8)</nametext></name>
- <name><ret>ERL_NIF_TERM</ret><nametext>enif_make_tuple9(ErlNifEnv* env,
+ <name since="OTP R13B04"><ret>ERL_NIF_TERM</ret><nametext>enif_make_tuple9(ErlNifEnv* env,
ERL_NIF_TERM e1, ..., ERL_NIF_TERM e9)</nametext></name>
<fsummary>Create a tuple term.</fsummary>
<desc>
@@ -2427,7 +2470,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret>
+ <name since="OTP R13B04"><ret>ERL_NIF_TERM</ret>
<nametext>enif_make_tuple_from_array(ErlNifEnv* env, const ERL_NIF_TERM
arr[], unsigned cnt)</nametext></name>
<fsummary>Create a tuple term from an array.</fsummary>
@@ -2438,7 +2481,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret>
+ <name since="OTP R13B04"><ret>ERL_NIF_TERM</ret>
<nametext>enif_make_uint(ErlNifEnv* env, unsigned int i)</nametext>
</name>
<fsummary>Create an unsigned integer term.</fsummary>
@@ -2448,7 +2491,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret>
+ <name since="OTP R14B"><ret>ERL_NIF_TERM</ret>
<nametext>enif_make_uint64(ErlNifEnv* env, ErlNifUInt64 i)</nametext>
</name>
<fsummary>Create an unsigned integer term.</fsummary>
@@ -2458,7 +2501,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret>
+ <name since=""><ret>ERL_NIF_TERM</ret>
<nametext>enif_make_ulong(ErlNifEnv* env, unsigned long i)</nametext>
</name>
<fsummary>Create an integer term from an unsigned long int.</fsummary>
@@ -2468,7 +2511,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret><nametext>enif_make_unique_integer(ErlNifEnv
+ <name since="OTP 19.0"><ret>ERL_NIF_TERM</ret><nametext>enif_make_unique_integer(ErlNifEnv
*env, ErlNifUniqueInteger properties)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -2486,7 +2529,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name><ret>int</ret><nametext>enif_map_iterator_create(ErlNifEnv *env,
+ <name since="OTP 18.0"><ret>int</ret><nametext>enif_map_iterator_create(ErlNifEnv *env,
ERL_NIF_TERM map, ErlNifMapIterator *iter, ErlNifMapIteratorEntry
entry)</nametext></name>
<fsummary>Create a map iterator.</fsummary>
@@ -2521,7 +2564,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>void</ret><nametext>enif_map_iterator_destroy(ErlNifEnv *env,
+ <name since="OTP 18.0"><ret>void</ret><nametext>enif_map_iterator_destroy(ErlNifEnv *env,
ErlNifMapIterator *iter)</nametext></name>
<fsummary>Destroy a map iterator.</fsummary>
<desc>
@@ -2532,7 +2575,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_map_iterator_get_pair(ErlNifEnv *env,
+ <name since="OTP 18.0"><ret>int</ret><nametext>enif_map_iterator_get_pair(ErlNifEnv *env,
ErlNifMapIterator *iter, ERL_NIF_TERM *key, ERL_NIF_TERM
*value)</nametext></name>
<fsummary>Get key and value at current map iterator position.</fsummary>
@@ -2545,7 +2588,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_map_iterator_is_head(ErlNifEnv *env,
+ <name since="OTP 18.0"><ret>int</ret><nametext>enif_map_iterator_is_head(ErlNifEnv *env,
ErlNifMapIterator *iter)</nametext></name>
<fsummary>Check if map iterator is positioned before first.</fsummary>
<desc>
@@ -2555,7 +2598,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_map_iterator_is_tail(ErlNifEnv *env,
+ <name since="OTP 18.0"><ret>int</ret><nametext>enif_map_iterator_is_tail(ErlNifEnv *env,
ErlNifMapIterator *iter)</nametext></name>
<fsummary>Check if map iterator is positioned after last.</fsummary>
<desc>
@@ -2565,7 +2608,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_map_iterator_next(ErlNifEnv *env,
+ <name since="OTP 18.0"><ret>int</ret><nametext>enif_map_iterator_next(ErlNifEnv *env,
ErlNifMapIterator *iter)</nametext></name>
<fsummary>Increment map iterator to point to next entry.</fsummary>
<desc>
@@ -2577,7 +2620,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_map_iterator_prev(ErlNifEnv *env,
+ <name since="OTP 18.0"><ret>int</ret><nametext>enif_map_iterator_prev(ErlNifEnv *env,
ErlNifMapIterator *iter)</nametext></name>
<fsummary>Decrement map iterator to point to previous entry.</fsummary>
<desc>
@@ -2589,8 +2632,8 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_monitor_process(ErlNifEnv* caller_env, void* obj,
- const ErlNifPid* target_pid, ErlNifMonitor* mon)</nametext></name>
+ <name since="OTP 20.0"><ret>int</ret><nametext>enif_monitor_process(ErlNifEnv* caller_env,
+ void* obj, const ErlNifPid* target_pid, ErlNifMonitor* mon)</nametext></name>
<fsummary>Monitor a process from a resource.</fsummary>
<desc>
<marker id="enif_monitor_process"></marker>
@@ -2613,7 +2656,9 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<p>Argument <c>caller_env</c> is the environment of the calling process
or callback. Must only be NULL if calling from a custom thread.</p>
<p>Returns <c>0</c> on success, &lt; 0 if no <c>down</c> callback is
- provided, and &gt; 0 if the process is no longer alive.</p>
+ provided, and &gt; 0 if the process is no longer alive or if
+ <c>target_pid</c> is <seealso marker="#enif_set_pid_undefined">
+ undefined</seealso>.</p>
<p>This function is only thread-safe when the emulator with SMP support
is used. It can only be used in a non-SMP emulator from a NIF-calling
thread.</p>
@@ -2621,7 +2666,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>ErlNifTime</ret>
+ <name since="OTP 18.3"><ret>ErlNifTime</ret>
<nametext>enif_monotonic_time(ErlNifTimeUnit time_unit)</nametext>
</name>
<fsummary>Get Erlang monotonic time.</fsummary>
@@ -2642,7 +2687,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>ErlNifMutex *</ret>
+ <name since="OTP R13B04"><ret>ErlNifMutex *</ret>
<nametext>enif_mutex_create(char *name)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -2652,7 +2697,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>void</ret>
+ <name since="OTP R13B04"><ret>void</ret>
<nametext>enif_mutex_destroy(ErlNifMutex *mtx)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -2662,7 +2707,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>void</ret>
+ <name since="OTP R13B04"><ret>void</ret>
<nametext>enif_mutex_lock(ErlNifMutex *mtx)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -2672,7 +2717,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>char*</ret>
+ <name since="OTP 21.0"><ret>char*</ret>
<nametext>enif_mutex_name(ErlNifMutex* mtx)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -2682,7 +2727,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP R13B04"><ret>int</ret>
<nametext>enif_mutex_trylock(ErlNifMutex *mtx)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -2692,7 +2737,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>void</ret>
+ <name since="OTP R13B04"><ret>void</ret>
<nametext>enif_mutex_unlock(ErlNifMutex *mtx)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -2702,7 +2747,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret>
+ <name since="OTP 19.0"><ret>ERL_NIF_TERM</ret>
<nametext>enif_now_time(ErlNifEnv *env)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -2713,7 +2758,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>ErlNifResourceType *</ret>
+ <name since="OTP R13B04"><ret>ErlNifResourceType *</ret>
<nametext>enif_open_resource_type(ErlNifEnv* env, const char*
module_str, const char* name, ErlNifResourceDtor* dtor,
ErlNifResourceFlags flags, ErlNifResourceFlags* tried)</nametext>
@@ -2752,7 +2797,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>ErlNifResourceType *</ret>
+ <name since="OTP 20.0"><ret>ErlNifResourceType *</ret>
<nametext>enif_open_resource_type_x(ErlNifEnv* env, const char* name,
const ErlNifResourceTypeInit* init,
ErlNifResourceFlags flags, ErlNifResourceFlags* tried)</nametext>
@@ -2771,7 +2816,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>int</ret><nametext>enif_port_command(ErlNifEnv* env, const
+ <name since="OTP 19.0"><ret>int</ret><nametext>enif_port_command(ErlNifEnv* env, const
ErlNifPort* to_port, ErlNifEnv *msg_env, ERL_NIF_TERM msg)</nametext>
</name>
<fsummary>Send a port_command to to_port.</fsummary>
@@ -2814,7 +2859,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>void *</ret>
+ <name since="OTP R13B04"><ret>void *</ret>
<nametext>enif_priv_data(ErlNifEnv* env)</nametext></name>
<fsummary>Get the private data of a NIF library.</fsummary>
<desc>
@@ -2825,7 +2870,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret><nametext>enif_raise_exception(ErlNifEnv*
+ <name since="OTP 18.0"><ret>ERL_NIF_TERM</ret><nametext>enif_raise_exception(ErlNifEnv*
env, ERL_NIF_TERM reason)</nametext></name>
<fsummary>Raise a NIF error exception.</fsummary>
<desc>
@@ -2848,7 +2893,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>void *</ret>
+ <name since="OTP 20.2"><ret>void *</ret>
<nametext>enif_realloc(void* ptr, size_t size)</nametext></name>
<fsummary>Reallocate dynamic memory.</fsummary>
<desc>
@@ -2862,7 +2907,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP R13B04"><ret>int</ret>
<nametext>enif_realloc_binary(ErlNifBinary* bin, size_t size)</nametext>
</name>
<fsummary>Change the size of a binary.</fsummary>
@@ -2876,7 +2921,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>void</ret>
+ <name since=""><ret>void</ret>
<nametext>enif_release_binary(ErlNifBinary* bin)</nametext></name>
<fsummary>Release a binary.</fsummary>
<desc>
@@ -2887,7 +2932,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>void</ret>
+ <name since="OTP R13B04"><ret>void</ret>
<nametext>enif_release_resource(void* obj)</nametext></name>
<fsummary>Release a resource object.</fsummary>
<desc>
@@ -2906,7 +2951,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>ErlNifRWLock *</ret>
+ <name since="OTP R13B04"><ret>ErlNifRWLock *</ret>
<nametext>enif_rwlock_create(char *name)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -2916,7 +2961,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>void</ret>
+ <name since="OTP R13B04"><ret>void</ret>
<nametext>enif_rwlock_destroy(ErlNifRWLock *rwlck)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -2926,7 +2971,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>char*</ret>
+ <name since="OTP 21.0"><ret>char*</ret>
<nametext>enif_rwlock_name(ErlNifRWLock* rwlck)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -2936,7 +2981,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>void</ret>
+ <name since="OTP R13B04"><ret>void</ret>
<nametext>enif_rwlock_rlock(ErlNifRWLock *rwlck)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -2946,7 +2991,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>void</ret>
+ <name since="OTP R13B04"><ret>void</ret>
<nametext>enif_rwlock_runlock(ErlNifRWLock *rwlck)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -2956,7 +3001,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>void</ret>
+ <name since="OTP R13B04"><ret>void</ret>
<nametext>enif_rwlock_rwlock(ErlNifRWLock *rwlck)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -2966,7 +3011,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>void</ret>
+ <name since="OTP R13B04"><ret>void</ret>
<nametext>enif_rwlock_rwunlock(ErlNifRWLock *rwlck)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -2976,7 +3021,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP R13B04"><ret>int</ret>
<nametext>enif_rwlock_tryrlock(ErlNifRWLock *rwlck)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -2986,7 +3031,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP R13B04"><ret>int</ret>
<nametext>enif_rwlock_tryrwlock(ErlNifRWLock *rwlck)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -2996,7 +3041,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>ERL_NIF_TERM</ret><nametext>enif_schedule_nif(ErlNifEnv* env,
+ <name since="OTP 17.3"><ret>ERL_NIF_TERM</ret><nametext>enif_schedule_nif(ErlNifEnv* env,
const char* fun_name, int flags, ERL_NIF_TERM (*fp)(ErlNifEnv* env, int
argc, const ERL_NIF_TERM argv[]), int argc, const ERL_NIF_TERM
argv[])</nametext></name>
@@ -3041,7 +3086,7 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP 20.0"><ret>int</ret>
<nametext>enif_select(ErlNifEnv* env, ErlNifEvent event, enum ErlNifSelectFlags mode,
void* obj, const ErlNifPid* pid, ERL_NIF_TERM ref)</nametext>
</name>
@@ -3056,13 +3101,21 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<p>Argument <c>mode</c> describes the type of events to wait for. It can be
<c>ERL_NIF_SELECT_READ</c>, <c>ERL_NIF_SELECT_WRITE</c> or a bitwise
OR combination to wait for both. It can also be <c>ERL_NIF_SELECT_STOP</c>
- which is described further below. When a read or write event is triggered,
+ or <c>ERL_NIF_SELECT_CANCEL</c> which are described further
+ below. When a read or write event is triggered,
a notification message like this is sent to the process identified by
<c>pid</c>:</p>
<code type="none">{select, Obj, Ref, ready_input | ready_output}</code>
<p><c>ready_input</c> or <c>ready_output</c> indicates if the event object
is ready for reading or writing.</p>
- <p>Argument <c>pid</c> may be <c>NULL</c> to indicate the calling process.</p>
+ <note><p>For complete control over the message format use the newer functions
+ <seealso marker="#enif_select_read"><c>enif_select_read</c></seealso> or
+ <seealso marker="#enif_select_write"><c>enif_select_write</c></seealso>
+ introduced in erts-11.0 (OTP-22.0).</p>
+ </note>
+ <p>Argument <c>pid</c> may be <c>NULL</c> to indicate the calling
+ process. It must not be set as <seealso marker="#enif_set_pid_undefined">
+ undefined</seealso>.</p>
<p>Argument <c>obj</c> is a resource object obtained from
<seealso marker="#enif_alloc_resource"><c>enif_alloc_resource</c></seealso>.
The purpose of the resource objects is as a container of the event object
@@ -3073,17 +3126,29 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
or the atom <c>undefined</c>. It will be passed as <c>Ref</c> in the notifications.
If a selective <c>receive</c> statement is used to wait for the notification
then a reference created just before the <c>receive</c> will exploit a runtime
- optimization that bypasses all earlier received messages in the queue.</p>
+ optimization that bypasses all earlier received messages in the
+ queue.</p>
<p>The notifications are one-shot only. To receive further notifications of the same
type (read or write), repeated calls to <c>enif_select</c> must be made
after receiving each notification.</p>
+ <p><c>ERL_NIF_SELECT_CANCEL</c> can be used to cancel previously
+ selected events. It must be used in a bitwise OR combination with
+ <c>ERL_NIF_SELECT_READ</c> and/or <c>ERL_NIF_SELECT_WRITE</c> to
+ indicate which type of event to cancel. Arguments <c>pid</c> and
+ <c>ref</c> are ignored when <c>ERL_NIF_SELECT_CANCEL</c> is specified.
+ The return value will tell if the event was actualy cancelled or if a
+ notification may already have been sent.</p>
<p>Use <c>ERL_NIF_SELECT_STOP</c> as <c>mode</c> in order to safely
close an event object that has been passed to <c>enif_select</c>. The
<seealso marker="#ErlNifResourceStop"><c>stop</c></seealso> callback
of the resource <c>obj</c> will be called when it is safe to close
the event object. This safe way of closing event objects must be used
- even if all notifications have been received and no further calls to
- <c>enif_select</c> have been made.</p>
+ even if all notifications have been received (or cancelled) and no
+ further calls to <c>enif_select</c> have been made.
+ <c>ERL_NIF_SELECT_STOP</c> will first cancel any selected events
+ before it calls or schedules the <c>stop</c> callback. Arguments
+ <c>pid</c> and <c>ref</c> are ignored when <c>ERL_NIF_SELECT_STOP</c>
+ is specified.</p>
<p>The first call to <c>enif_select</c> for a specific OS <c>event</c> will establish
a relation between the event object and the containing resource. All subsequent calls
for an <c>event</c> must pass its containing resource as argument
@@ -3105,7 +3170,15 @@ enif_map_iterator_destroy(env, &amp;iter);</code>
<item>The stop callback was called directly by <c>enif_select</c>.</item>
<tag><c>ERL_NIF_SELECT_STOP_SCHEDULED</c></tag>
<item>The stop callback was scheduled to run on some other thread
- or later by this thread.</item>
+ or later by this thread.</item>
+ <tag><c>ERL_NIF_SELECT_READ_CANCELLED</c></tag>
+ <item>A read event was cancelled by <c>ERL_NIF_SELECT_CANCEL</c> or
+ <c>ERL_NIF_SELECT_STOP</c> and is guaranteed not to generate a
+ <c>ready_input</c> notification message.</item>
+ <tag><c>ERL_NIF_SELECT_WRITE_CANCELLED</c></tag>
+ <item>A write event was cancelled by <c>ERL_NIF_SELECT_CANCEL</c> or
+ <c>ERL_NIF_SELECT_STOP</c> and is guaranteed not to generate a
+ <c>ready_output</c> notification message.</item>
</taglist>
<p>Returns a negative value if the call failed where the following bits can be set:</p>
<taglist>
@@ -3131,11 +3204,48 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
}
</code>
</note>
+ <note><p>The mode flag <c>ERL_NIF_SELECT_CANCEL</c> and the return flags
+ <c>ERL_NIF_SELECT_READ_CANCELLED</c> and
+ <c>ERL_NIF_SELECT_WRITE_CANCELLED</c> were introduced in erts-11.0
+ (OTP-22.0).</p>
+ </note>
</desc>
</func>
<func>
- <name><ret>ErlNifPid *</ret>
+ <name since="OTP 22.0"><ret>int</ret>
+ <nametext>enif_select_read(ErlNifEnv* env, ErlNifEvent event, void* obj,
+ const ErlNifPid* pid, ERL_NIF_TERM msg, ErlNifEnv* msg_env)</nametext>
+ </name>
+ <name since="OTP 22.0"><ret>int</ret>
+ <nametext>enif_select_write(ErlNifEnv* env, ErlNifEvent event, void* obj,
+ const ErlNifPid* pid, ERL_NIF_TERM msg, ErlNifEnv* msg_env)</nametext>
+ </name>
+ <fsummary>Manage subscription on IO event.</fsummary>
+ <desc>
+ <p>These are variants of <seealso marker="#enif_select">enif_select</seealso>
+ where you can supply your own message term <c>msg</c> that will be sent to
+ the process instead of the predefined tuple <c>{select,_,_,_}.</c></p>
+ <p>Argument <c>msg_env</c> must either be <c>NULL</c> or the environment of
+ <c>msg</c> allocated with <seealso marker="#enif_alloc_env">
+ <c>enif_alloc_env</c></seealso>. If argument <c>msg_env</c> is
+ <c>NULL</c> the term <c>msg</c> will be copied, otherwise both
+ <c>msg</c> and <c>msg_env</c> will be invalidated by a successful call
+ to <c>enif_select_read</c> or <c>enif_select_write</c>. The environment
+ is then to either be freed with <seealso marker="#enif_free_env">
+ <c>enif_free_env</c></seealso> or cleared for reuse with
+ <seealso marker="#enif_clear_env"><c>enif_clear_env</c></seealso>. An
+ unsuccessful call will leave <c>msg</c> and <c>msg_env</c> still valid.</p>
+ <p>Apart from the message format <c>enif_select_read</c> and
+ <c>enif_select_write</c> behaves exactly the same as <seealso
+ marker="#enif_select">enif_select</seealso> with argument <c>mode</c> as
+ either <c>ERL_NIF_SELECT_READ</c> or <c>ERL_NIF_SELECT_WRITE</c>. To
+ cancel or close events use <seealso marker="#enif_select">enif_select</seealso>.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="OTP R14B"><ret>ErlNifPid *</ret>
<nametext>enif_self(ErlNifEnv* caller_env, ErlNifPid* pid)</nametext>
</name>
<fsummary>Get the pid of the calling process.</fsummary>
@@ -3148,8 +3258,8 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</func>
<func>
- <name><ret>int</ret><nametext>enif_send(ErlNifEnv* caller_env, ErlNifPid* to_pid,
- ErlNifEnv* msg_env, ERL_NIF_TERM msg)</nametext></name>
+ <name since="OTP R14B"><ret>int</ret><nametext>enif_send(ErlNifEnv* caller_env,
+ ErlNifPid* to_pid, ErlNifEnv* msg_env, ERL_NIF_TERM msg)</nametext></name>
<fsummary>Send a message to a process.</fsummary>
<desc>
<p>Sends a message to a process.</p>
@@ -3179,8 +3289,9 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
<c>msg</c>) is invalidated by a successful call to <c>enif_send</c>.
The environment is to either be freed with
<seealso marker="#enif_free_env">
- <c>enif_free_env</c></seealso> of cleared for reuse with
- <seealso marker="#enif_clear_env"><c>enif_clear_env</c></seealso>.</p>
+ <c>enif_free_env</c></seealso> or cleared for reuse with
+ <seealso marker="#enif_clear_env"><c>enif_clear_env</c></seealso>. An
+ unsuccessful call will leave <c>msg</c> and <c>msg_env</c> still valid.</p>
<p>If <c>msg_env</c> is set to <c>NULL</c>, the <c>msg</c> term is
copied and the original term and its environment is still valid after
the call.</p>
@@ -3195,7 +3306,18 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</func>
<func>
- <name><ret>unsigned</ret>
+ <name since="OTP @OTP-15011@"><ret>void</ret>
+ <nametext>enif_set_pid_undefined(ErlNifPid* pid)</nametext></name>
+ <fsummary>Set pid as undefined.</fsummary>
+ <desc>
+ <p>Sets an <seealso marker="#ErlNifPid"><c>ErlNifPid</c></seealso>
+ variable as undefined. See <seealso marker="#enif_is_pid_undefined">
+ <c>enif_is_pid_undefined</c></seealso>.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="OTP R13B04"><ret>unsigned</ret>
<nametext>enif_sizeof_resource(void* obj)</nametext></name>
<fsummary>Get the byte size of a resource object.</fsummary>
<desc>
@@ -3206,7 +3328,7 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</func>
<func>
- <name><ret>int</ret><nametext>enif_snprintf(char *str, size_t size, const
+ <name since="OTP 19.0"><ret>int</ret><nametext>enif_snprintf(char *str, size_t size, const
char *format, ...)</nametext></name>
<fsummary>Format strings and Erlang terms.</fsummary>
<desc>
@@ -3220,7 +3342,7 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</func>
<func>
- <name><ret>void</ret><nametext>enif_system_info(ErlNifSysInfo
+ <name since="OTP R13B04"><ret>void</ret><nametext>enif_system_info(ErlNifSysInfo
*sys_info_ptr, size_t size)</nametext></name>
<fsummary>Get information about the Erlang runtime system.</fsummary>
<desc>
@@ -3230,7 +3352,7 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</func>
<func>
- <name><ret>int</ret><nametext>enif_term_to_binary(ErlNifEnv *env,
+ <name since="OTP 19.0"><ret>int</ret><nametext>enif_term_to_binary(ErlNifEnv *env,
ERL_NIF_TERM term, ErlNifBinary *bin)</nametext></name>
<fsummary>Convert a term to the external format.</fsummary>
<desc>
@@ -3247,7 +3369,7 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP R13B04"><ret>int</ret>
<nametext>enif_thread_create(char *name,ErlNifTid
*tid,void * (*func)(void *),void *args,ErlNifThreadOpts
*opts)</nametext></name>
@@ -3259,7 +3381,7 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</func>
<func>
- <name><ret>void</ret>
+ <name since="OTP R13B04"><ret>void</ret>
<nametext>enif_thread_exit(void *resp)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -3269,7 +3391,7 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP R13B04"><ret>int</ret>
<nametext>enif_thread_join(ErlNifTid, void **respp)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -3279,7 +3401,7 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</func>
<func>
- <name><ret>char*</ret>
+ <name since="OTP 21.0"><ret>char*</ret>
<nametext>enif_thread_name(ErlNifTid tid)</nametext></name>
<fsummary>Thread name</fsummary>
<desc>
@@ -3289,7 +3411,7 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</func>
<func>
- <name><ret>ErlNifThreadOpts *</ret>
+ <name since="OTP R13B04"><ret>ErlNifThreadOpts *</ret>
<nametext>enif_thread_opts_create(char *name)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -3299,7 +3421,7 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</func>
<func>
- <name><ret>void</ret>
+ <name since="OTP R13B04"><ret>void</ret>
<nametext>enif_thread_opts_destroy(ErlNifThreadOpts *opts)</nametext>
</name>
<fsummary></fsummary>
@@ -3310,7 +3432,7 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</func>
<func>
- <name><ret>ErlNifTid</ret>
+ <name since="OTP R13B04"><ret>ErlNifTid</ret>
<nametext>enif_thread_self(void)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -3320,7 +3442,7 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP 19.0"><ret>int</ret>
<nametext>enif_thread_type(void)</nametext></name>
<fsummary>Determine type of current thread</fsummary>
<desc>
@@ -3342,7 +3464,7 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</func>
<func>
- <name><ret>ErlNifTime</ret>
+ <name since="OTP 18.3"><ret>ErlNifTime</ret>
<nametext>enif_time_offset(ErlNifTimeUnit time_unit)</nametext></name>
<fsummary>Get current time offset.</fsummary>
<desc>
@@ -3364,7 +3486,7 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</func>
<func>
- <name><ret>void *</ret>
+ <name since="OTP R13B04"><ret>void *</ret>
<nametext>enif_tsd_get(ErlNifTSDKey key)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -3374,7 +3496,7 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP R13B04"><ret>int</ret>
<nametext>enif_tsd_key_create(char *name, ErlNifTSDKey *key)</nametext>
</name>
<fsummary></fsummary>
@@ -3385,7 +3507,7 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</func>
<func>
- <name><ret>void</ret>
+ <name since="OTP R13B04"><ret>void</ret>
<nametext>enif_tsd_key_destroy(ErlNifTSDKey key)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -3395,7 +3517,7 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</func>
<func>
- <name><ret>void</ret>
+ <name since="OTP R13B04"><ret>void</ret>
<nametext>enif_tsd_set(ErlNifTSDKey key, void *data)</nametext></name>
<fsummary></fsummary>
<desc>
@@ -3405,7 +3527,7 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP 21.0"><ret>int</ret>
<nametext>enif_vfprintf(FILE *stream, const char *format, va_list ap)
</nametext></name>
<fsummary>Format strings and Erlang terms.</fsummary>
@@ -3417,7 +3539,7 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP 21.0"><ret>int</ret>
<nametext>enif_vsnprintf(char *str, size_t size, const char *format, va_list ap)
</nametext></name>
<fsummary>Format strings and Erlang terms.</fsummary>
@@ -3429,7 +3551,7 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP 20.0"><ret>int</ret>
<nametext>enif_whereis_pid(ErlNifEnv *env,
ERL_NIF_TERM name, ErlNifPid *pid)</nametext></name>
<fsummary>Looks up a process by its registered name.</fsummary>
@@ -3457,7 +3579,7 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</func>
<func>
- <name><ret>int</ret>
+ <name since="OTP 20.0"><ret>int</ret>
<nametext>enif_whereis_port(ErlNifEnv *env,
ERL_NIF_TERM name, ErlNifPort *port)</nametext></name>
<fsummary>Looks up a port by its registered name.</fsummary>
diff --git a/erts/doc/src/erl_prim_loader.xml b/erts/doc/src/erl_prim_loader.xml
index 286bac6c93..043d11b7b4 100644
--- a/erts/doc/src/erl_prim_loader.xml
+++ b/erts/doc/src/erl_prim_loader.xml
@@ -29,7 +29,7 @@
<rev></rev>
<file>erl_prim_loader.xml</file>
</header>
- <module>erl_prim_loader</module>
+ <module since="">erl_prim_loader</module>
<modulesummary>Low-level Erlang loader.</modulesummary>
<description>
<p>This module is used to load all Erlang modules into
@@ -47,7 +47,7 @@
<funcs>
<func>
- <name name="get_file" arity="1"/>
+ <name name="get_file" arity="1" since=""/>
<fsummary>Get a file.</fsummary>
<desc>
<p>Fetches a file using the low-level loader.
@@ -65,7 +65,7 @@
</func>
<func>
- <name name="get_path" arity="0"/>
+ <name name="get_path" arity="0" since=""/>
<fsummary>Get the path set in the loader.</fsummary>
<desc>
<p>Gets the path set in the loader. The path is
@@ -75,7 +75,7 @@
</func>
<func>
- <name name="list_dir" arity="1"/>
+ <name name="list_dir" arity="1" since=""/>
<fsummary>List files in a directory.</fsummary>
<desc>
<p>Lists all the files in a directory. Returns
@@ -92,7 +92,7 @@
</func>
<func>
- <name name="read_file_info" arity="1"/>
+ <name name="read_file_info" arity="1" since=""/>
<fsummary>Get information about a file.</fsummary>
<desc>
<p>Retrieves information about a file. Returns
@@ -114,7 +114,7 @@
</func>
<func>
- <name name="read_link_info" arity="1"/>
+ <name name="read_link_info" arity="1" since="OTP 17.1.2"/>
<fsummary>Get information about a link or file.</fsummary>
<desc>
<p>Works like
@@ -131,7 +131,7 @@
</func>
<func>
- <name name="set_path" arity="1"/>
+ <name name="set_path" arity="1" since=""/>
<fsummary>Set the path of the loader.</fsummary>
<desc>
<p>Sets the path of the loader if
diff --git a/erts/doc/src/erl_tracer.xml b/erts/doc/src/erl_tracer.xml
index fd3c17f337..fa4717bc2f 100644
--- a/erts/doc/src/erl_tracer.xml
+++ b/erts/doc/src/erl_tracer.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>erl_tracer</module>
+ <module since="OTP 19.0">erl_tracer</module>
<modulesummary>Erlang tracer behavior.</modulesummary>
<description>
<p>This behavior module implements the back end of the Erlang
@@ -195,7 +195,7 @@
<funcs>
<func>
- <name>Module:enabled(TraceTag, TracerState, Tracee) -> Result</name>
+ <name since="OTP 19.0">Module:enabled(TraceTag, TracerState, Tracee) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
<v>TraceTag = <seealso marker="#type-trace_tag">
@@ -224,7 +224,7 @@
</func>
<func>
- <name>Module:enabled_call(TraceTag, TracerState, Tracee) -> Result</name>
+ <name since="OTP 19.0">Module:enabled_call(TraceTag, TracerState, Tracee) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
<v>TraceTag = <seealso marker="#type-trace_tag_call">
@@ -244,7 +244,7 @@
</func>
<func>
- <name>Module:enabled_garbage_collection(TraceTag, TracerState, Tracee) -> Result</name>
+ <name since="OTP 19.0">Module:enabled_garbage_collection(TraceTag, TracerState, Tracee) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
<v>TraceTag = <seealso marker="#type-trace_tag_gc">
@@ -264,7 +264,7 @@
</func>
<func>
- <name>Module:enabled_ports(TraceTag, TracerState, Tracee) -> Result</name>
+ <name since="OTP 19.0">Module:enabled_ports(TraceTag, TracerState, Tracee) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
<v>TraceTag = <seealso marker="#type-trace_tag_ports">
@@ -284,7 +284,7 @@
</func>
<func>
- <name>Module:enabled_procs(TraceTag, TracerState, Tracee) -> Result</name>
+ <name since="OTP 19.0">Module:enabled_procs(TraceTag, TracerState, Tracee) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
<v>TraceTag = <seealso marker="#type-trace_tag_procs">
@@ -304,7 +304,7 @@
</func>
<func>
- <name>Module:enabled_receive(TraceTag, TracerState, Tracee) -> Result
+ <name since="OTP 19.0">Module:enabled_receive(TraceTag, TracerState, Tracee) -> Result
</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
@@ -325,7 +325,7 @@
</func>
<func>
- <name>Module:enabled_running_ports(TraceTag, TracerState, Tracee) ->
+ <name since="OTP 19.0">Module:enabled_running_ports(TraceTag, TracerState, Tracee) ->
Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
@@ -346,7 +346,7 @@
</func>
<func>
- <name>Module:enabled_running_procs(TraceTag, TracerState, Tracee) ->
+ <name since="OTP 19.0">Module:enabled_running_procs(TraceTag, TracerState, Tracee) ->
Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
@@ -368,7 +368,7 @@
</func>
<func>
- <name>Module:enabled_send(TraceTag, TracerState, Tracee) -> Result</name>
+ <name since="OTP 19.0">Module:enabled_send(TraceTag, TracerState, Tracee) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
<v>TraceTag = <seealso marker="#type-trace_tag_send">
@@ -388,7 +388,7 @@
</func>
<func>
- <name>Module:trace(TraceTag, TracerState, Tracee, TraceTerm,
+ <name since="OTP 19.0">Module:trace(TraceTag, TracerState, Tracee, TraceTerm,
Opts) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
@@ -417,7 +417,7 @@
</func>
<func>
- <name name="trace">Module:trace(seq_trace, TracerState, Label,
+ <name name="trace" since="OTP 19.0">Module:trace(seq_trace, TracerState, Label,
SeqTraceInfo, Opts) -> Result</name>
<fsummary>Check if a sequence trace event is to be generated.</fsummary>
<type>
@@ -439,7 +439,7 @@
</func>
<func>
- <name>Module:trace_call(TraceTag, TracerState, Tracee, TraceTerm,
+ <name since="OTP 19.0">Module:trace_call(TraceTag, TracerState, Tracee, TraceTerm,
Opts) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
@@ -463,7 +463,7 @@
</func>
<func>
- <name>Module:trace_garbage_collection(TraceTag, TracerState, Tracee,
+ <name since="OTP 19.0">Module:trace_garbage_collection(TraceTag, TracerState, Tracee,
TraceTerm, Opts) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
@@ -487,7 +487,7 @@
</func>
<func>
- <name>Module:trace_ports(TraceTag, TracerState, Tracee, TraceTerm,
+ <name since="OTP 19.0">Module:trace_ports(TraceTag, TracerState, Tracee, TraceTerm,
Opts) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
@@ -511,7 +511,7 @@
</func>
<func>
- <name>Module:trace_procs(TraceTag, TracerState, Tracee, TraceTerm,
+ <name since="OTP 19.0">Module:trace_procs(TraceTag, TracerState, Tracee, TraceTerm,
Opts) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
@@ -535,7 +535,7 @@
</func>
<func>
- <name>Module:trace_receive(TraceTag, TracerState, Tracee, TraceTerm,
+ <name since="OTP 19.0">Module:trace_receive(TraceTag, TracerState, Tracee, TraceTerm,
Opts) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
@@ -559,7 +559,7 @@
</func>
<func>
- <name>Module:trace_running_ports(TraceTag, TracerState, Tracee,
+ <name since="OTP 19.0">Module:trace_running_ports(TraceTag, TracerState, Tracee,
TraceTerm, Opts) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
@@ -583,7 +583,7 @@
</func>
<func>
- <name>Module:trace_running_procs(TraceTag, TracerState, Tracee,
+ <name since="OTP 19.0">Module:trace_running_procs(TraceTag, TracerState, Tracee,
TraceTerm, Opts) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
@@ -607,7 +607,7 @@
</func>
<func>
- <name>Module:trace_send(TraceTag, TracerState, Tracee, TraceTerm,
+ <name since="OTP 19.0">Module:trace_send(TraceTag, TracerState, Tracee, TraceTerm,
Opts) -> Result</name>
<fsummary>Check if a trace event is to be generated.</fsummary>
<type>
diff --git a/erts/doc/src/erlang.xml b/erts/doc/src/erlang.xml
index 8e014c3010..e78ded4ae1 100644
--- a/erts/doc/src/erlang.xml
+++ b/erts/doc/src/erlang.xml
@@ -29,7 +29,7 @@
<rev></rev>
<file>erlang.xml</file>
</header>
- <module>erlang</module>
+ <module since="">erlang</module>
<modulesummary>The Erlang BIFs.</modulesummary>
<description>
<p>By convention, most Built-In Functions (BIFs) are included
@@ -210,8 +210,8 @@
<funcs>
<func>
- <name name="abs" arity="1" clause_i="1"/>
- <name name="abs" arity="1" clause_i="2"/>
+ <name name="abs" arity="1" clause_i="1" since=""/>
+ <name name="abs" arity="1" clause_i="2" since=""/>
<fsummary>Arithmetical absolute value.</fsummary>
<desc>
<p>Returns an integer or float that is the arithmetical
@@ -227,7 +227,7 @@
</func>
<func>
- <name name="adler32" arity="1"/>
+ <name name="adler32" arity="1" since=""/>
<fsummary>Compute adler32 checksum.</fsummary>
<desc>
<p>Computes and returns the adler32 checksum for
@@ -236,7 +236,7 @@
</func>
<func>
- <name name="adler32" arity="2"/>
+ <name name="adler32" arity="2" since=""/>
<fsummary>Compute adler32 checksum.</fsummary>
<desc>
<p>Continues computing the adler32 checksum by combining
@@ -253,7 +253,7 @@ Y = erlang:adler32([Data1,Data2]).</code>
</func>
<func>
- <name name="adler32_combine" arity="3"/>
+ <name name="adler32_combine" arity="3" since=""/>
<fsummary>Combine two adler32 checksums.</fsummary>
<desc>
<p>Combines two previously computed adler32 checksums.
@@ -272,7 +272,7 @@ Z = erlang:adler32_combine(X,Y,iolist_size(Data2)).</code>
</func>
<func>
- <name name="append_element" arity="2"/>
+ <name name="append_element" arity="2" since=""/>
<fsummary>Append an extra element to a tuple.</fsummary>
<desc>
<p>Returns a new tuple that has one element more than
@@ -289,7 +289,7 @@ Z = erlang:adler32_combine(X,Y,iolist_size(Data2)).</code>
</func>
<func>
- <name name="apply" arity="2"/>
+ <name name="apply" arity="2" since=""/>
<fsummary>Apply a function to an argument list.</fsummary>
<desc>
<p>Calls a fun, passing the elements in <c><anno>Args</anno></c>
@@ -307,7 +307,7 @@ Z = erlang:adler32_combine(X,Y,iolist_size(Data2)).</code>
</func>
<func>
- <name name="apply" arity="3"/>
+ <name name="apply" arity="3" since=""/>
<fsummary>Apply a function to an argument list.</fsummary>
<desc>
<p>Returns the result of applying <c>Function</c> in
@@ -337,7 +337,7 @@ Z = erlang:adler32_combine(X,Y,iolist_size(Data2)).</code>
</func>
<func>
- <name name="atom_to_binary" arity="2"/>
+ <name name="atom_to_binary" arity="2" since=""/>
<fsummary>Return the binary representation of an atom.</fsummary>
<desc>
<p>Returns a binary corresponding to the text
@@ -362,7 +362,7 @@ Z = erlang:adler32_combine(X,Y,iolist_size(Data2)).</code>
</func>
<func>
- <name name="atom_to_list" arity="1"/>
+ <name name="atom_to_list" arity="1" since=""/>
<fsummary>Text representation of an atom.</fsummary>
<desc>
<p>Returns a string corresponding to the text
@@ -374,7 +374,7 @@ Z = erlang:adler32_combine(X,Y,iolist_size(Data2)).</code>
</func>
<func>
- <name name="binary_part" arity="2"/>
+ <name name="binary_part" arity="2" since="OTP R14B"/>
<fsummary>Extract a part of a binary.</fsummary>
<desc>
<p>Extracts the part of the binary described by
@@ -399,7 +399,7 @@ Z = erlang:adler32_combine(X,Y,iolist_size(Data2)).</code>
</func>
<func>
- <name name="binary_part" arity="3"/>
+ <name name="binary_part" arity="3" since="OTP R14B"/>
<fsummary>Extract a part of a binary.</fsummary>
<desc>
<p>The same as <c>binary_part(<anno>Subject</anno>,
@@ -409,7 +409,7 @@ Z = erlang:adler32_combine(X,Y,iolist_size(Data2)).</code>
</func>
<func>
- <name name="binary_to_atom" arity="2"/>
+ <name name="binary_to_atom" arity="2" since=""/>
<fsummary>Convert from text representation to an atom.</fsummary>
<desc>
<p>Returns the atom whose text representation is
@@ -438,7 +438,7 @@ Z = erlang:adler32_combine(X,Y,iolist_size(Data2)).</code>
</func>
<func>
- <name name="binary_to_existing_atom" arity="2"/>
+ <name name="binary_to_existing_atom" arity="2" since=""/>
<fsummary>Convert from text representation to an atom.</fsummary>
<desc>
<p>As
@@ -459,7 +459,7 @@ Z = erlang:adler32_combine(X,Y,iolist_size(Data2)).</code>
</func>
<func>
- <name name="binary_to_float" arity="1"/>
+ <name name="binary_to_float" arity="1" since="OTP R16B"/>
<fsummary>Convert from text representation to a float.</fsummary>
<desc>
<p>Returns the float whose text representation is
@@ -473,7 +473,7 @@ Z = erlang:adler32_combine(X,Y,iolist_size(Data2)).</code>
</func>
<func>
- <name name="binary_to_integer" arity="1"/>
+ <name name="binary_to_integer" arity="1" since="OTP R16B"/>
<fsummary>Convert from text representation to an integer.</fsummary>
<desc>
<p>Returns an integer whose text representation is
@@ -487,7 +487,7 @@ Z = erlang:adler32_combine(X,Y,iolist_size(Data2)).</code>
</func>
<func>
- <name name="binary_to_integer" arity="2"/>
+ <name name="binary_to_integer" arity="2" since="OTP R16B"/>
<fsummary>Convert from text representation to an integer.</fsummary>
<desc>
<p>Returns an integer whose text representation in base
@@ -502,7 +502,7 @@ Z = erlang:adler32_combine(X,Y,iolist_size(Data2)).</code>
</func>
<func>
- <name name="binary_to_list" arity="1"/>
+ <name name="binary_to_list" arity="1" since=""/>
<fsummary>Convert a binary to a list.</fsummary>
<desc>
<p>Returns a list of integers corresponding to the bytes of
@@ -511,7 +511,7 @@ Z = erlang:adler32_combine(X,Y,iolist_size(Data2)).</code>
</func>
<func>
- <name name="binary_to_list" arity="3"/>
+ <name name="binary_to_list" arity="3" since=""/>
<fsummary>Convert part of a binary to a list.</fsummary>
<type_desc variable="Start">1..byte_size(<c><anno>Binary</anno></c>)
</type_desc>
@@ -533,7 +533,7 @@ Z = erlang:adler32_combine(X,Y,iolist_size(Data2)).</code>
</func>
<func>
- <name name="binary_to_term" arity="1"/>
+ <name name="binary_to_term" arity="1" since=""/>
<fsummary>Decode an Erlang external term format binary.</fsummary>
<desc>
<p>Returns an Erlang term that is the result of decoding
@@ -559,7 +559,7 @@ hello
</func>
<func>
- <name name="binary_to_term" arity="2"/>
+ <name name="binary_to_term" arity="2" since="OTP R13B04"/>
<fsummary>Decode an Erlang external term format binary.</fsummary>
<desc>
<p>As <c>binary_to_term/1</c>, but takes these options:</p>
@@ -613,7 +613,7 @@ hello
</func>
<func>
- <name name="bit_size" arity="1"/>
+ <name name="bit_size" arity="1" since=""/>
<fsummary>Return the size of a bitstring.</fsummary>
<desc>
<p>Returns an integer that is the size in bits of
@@ -628,7 +628,7 @@ hello
</func>
<func>
- <name name="bitstring_to_list" arity="1"/>
+ <name name="bitstring_to_list" arity="1" since=""/>
<fsummary>Convert a bitstring to a list.</fsummary>
<desc>
<p>Returns a list of integers corresponding to the bytes of
@@ -639,7 +639,7 @@ hello
</func>
<func>
- <name name="bump_reductions" arity="1"/>
+ <name name="bump_reductions" arity="1" since=""/>
<fsummary>Increment the reduction counter.</fsummary>
<desc>
<p>This implementation-dependent function increments
@@ -657,7 +657,7 @@ hello
</func>
<func>
- <name name="byte_size" arity="1"/>
+ <name name="byte_size" arity="1" since=""/>
<fsummary>Return the size of a bitstring (or binary).</fsummary>
<desc>
<p>Returns an integer that is the number of bytes needed to
@@ -674,7 +674,7 @@ hello
</func>
<func>
- <name name="cancel_timer" arity="1"/>
+ <name name="cancel_timer" arity="1" since=""/>
<fsummary>Cancel a timer.</fsummary>
<desc>
<p>Cancels a timer. The same as calling
@@ -684,7 +684,7 @@ hello
</func>
<func>
- <name name="cancel_timer" arity="2"/>
+ <name name="cancel_timer" arity="2" since="OTP 18.0"/>
<fsummary>Cancel a timer.</fsummary>
<desc>
<p>Cancels a timer that has been created by
@@ -766,7 +766,7 @@ hello
</func>
<func>
- <name name="ceil" arity="1"/>
+ <name name="ceil" arity="1" since="OTP 20.0"/>
<fsummary>Returns the smallest integer not less than the argument</fsummary>
<desc>
<p>Returns the smallest integer not less than
@@ -779,7 +779,7 @@ hello
</desc>
</func>
<func>
- <name name="check_old_code" arity="1"/>
+ <name name="check_old_code" arity="1" since="OTP R14B04"/>
<fsummary>Check if a module has old code.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Module</anno></c> has old code,
@@ -790,7 +790,7 @@ hello
</func>
<func>
- <name name="check_process_code" arity="2"/>
+ <name name="check_process_code" arity="2" since=""/>
<fsummary>Check if a process executes old code for a module.</fsummary>
<desc>
<p>The same as
@@ -801,7 +801,7 @@ hello
</func>
<func>
- <name name="check_process_code" arity="3"/>
+ <name name="check_process_code" arity="3" since="OTP 17.0"/>
<fsummary>Check if a process executes old code for a module.</fsummary>
<desc>
<p>Checks if the node local process identified by
@@ -904,7 +904,7 @@ hello
</func>
<func>
- <name name="convert_time_unit" arity="3"/>
+ <name name="convert_time_unit" arity="3" since="OTP 18.0"/>
<fsummary>Convert time unit of a time value.</fsummary>
<desc>
<p>Converts the <c><anno>Time</anno></c> value of time unit
@@ -922,7 +922,7 @@ hello
</func>
<func>
- <name name="crc32" arity="1"/>
+ <name name="crc32" arity="1" since=""/>
<fsummary>Compute crc32 (IEEE 802.3) checksum.</fsummary>
<desc>
<p>Computes and returns the crc32 (IEEE 802.3 style) checksum
@@ -931,7 +931,7 @@ hello
</func>
<func>
- <name name="crc32" arity="2"/>
+ <name name="crc32" arity="2" since=""/>
<fsummary>Compute crc32 (IEEE 802.3) checksum.</fsummary>
<desc>
<p>Continues computing the crc32 checksum by combining
@@ -948,7 +948,7 @@ Y = erlang:crc32([Data1,Data2]).</code>
</func>
<func>
- <name name="crc32_combine" arity="3"/>
+ <name name="crc32_combine" arity="3" since=""/>
<fsummary>Combine two crc32 (IEEE 802.3) checksums.</fsummary>
<desc>
<p>Combines two previously computed crc32 checksums.
@@ -967,7 +967,7 @@ Z = erlang:crc32_combine(X,Y,iolist_size(Data2)).</code>
</func>
<func>
- <name name="date" arity="0"/>
+ <name name="date" arity="0" since=""/>
<fsummary>Current date.</fsummary>
<desc>
<p>Returns the current date as <c>{Year, Month, Day}</c>.</p>
@@ -980,7 +980,7 @@ Z = erlang:crc32_combine(X,Y,iolist_size(Data2)).</code>
</func>
<func>
- <name name="decode_packet" arity="3"/>
+ <name name="decode_packet" arity="3" since=""/>
<fsummary>Extract a protocol packet from a binary.</fsummary>
<desc>
<p>Decodes the binary <c><anno>Bin</anno></c> according to the packet
@@ -1090,7 +1090,7 @@ Z = erlang:crc32_combine(X,Y,iolist_size(Data2)).</code>
</func>
<func>
- <name name="delete_element" arity="2"/>
+ <name name="delete_element" arity="2" since="OTP R16B"/>
<fsummary>Delete element at index in a tuple.</fsummary>
<type_desc variable="Index">1..tuple_size(<anno>Tuple1</anno>)</type_desc>
<desc>
@@ -1103,7 +1103,7 @@ Z = erlang:crc32_combine(X,Y,iolist_size(Data2)).</code>
</func>
<func>
- <name name="delete_module" arity="1"/>
+ <name name="delete_module" arity="1" since=""/>
<fsummary>Make the current code for a module old.</fsummary>
<desc>
<p>Makes the current code for <c><anno>Module</anno></c> become old
@@ -1121,7 +1121,7 @@ Z = erlang:crc32_combine(X,Y,iolist_size(Data2)).</code>
</func>
<func>
- <name name="demonitor" arity="1"/>
+ <name name="demonitor" arity="1" since=""/>
<fsummary>Stop monitoring.</fsummary>
<desc>
<p>If <c><anno>MonitorRef</anno></c> is a reference that the
@@ -1163,7 +1163,7 @@ Z = erlang:crc32_combine(X,Y,iolist_size(Data2)).</code>
</func>
<func>
- <name name="demonitor" arity="2"/>
+ <name name="demonitor" arity="2" since=""/>
<fsummary>Stop monitoring.</fsummary>
<desc>
<p>The returned value is <c>true</c> unless <c>info</c> is part
@@ -1231,7 +1231,7 @@ end</code>
</func>
<func>
- <name name="disconnect_node" arity="1"/>
+ <name name="disconnect_node" arity="1" since=""/>
<fsummary>Force the disconnection of a node.</fsummary>
<desc>
<p>Forces the disconnection of a node. This appears to
@@ -1245,7 +1245,7 @@ end</code>
</func>
<func>
- <name name="display" arity="1"/>
+ <name name="display" arity="1" since=""/>
<fsummary>Print a term on standard output.</fsummary>
<desc>
<p>Prints a text representation of <c><anno>Term</anno></c> on the
@@ -1257,7 +1257,7 @@ end</code>
</func>
<func>
- <name name="dist_ctrl_get_data" arity="1"/>
+ <name name="dist_ctrl_get_data" arity="1" since="OTP 21.0"/>
<fsummary>Get distribution channel data to pass to another node.</fsummary>
<desc>
<p>
@@ -1290,7 +1290,7 @@ end</code>
</func>
<func>
- <name name="dist_ctrl_get_data_notification" arity="1"/>
+ <name name="dist_ctrl_get_data_notification" arity="1" since="OTP 21.0"/>
<fsummary>Request notification about available outgoing distribution channel data.</fsummary>
<desc>
<p>
@@ -1326,7 +1326,7 @@ end</code>
</func>
<func>
- <name name="dist_ctrl_input_handler" arity="2"/>
+ <name name="dist_ctrl_input_handler" arity="2" since="OTP 21.0"/>
<fsummary>Register distribution channel input handler process.</fsummary>
<desc>
<p>
@@ -1359,7 +1359,7 @@ end</code>
</func>
<func>
- <name name="dist_ctrl_put_data" arity="2"/>
+ <name name="dist_ctrl_put_data" arity="2" since="OTP 21.0"/>
<fsummary>Pass data into the VM from a distribution channel.</fsummary>
<desc>
<p>
@@ -1392,7 +1392,7 @@ end</code>
</func>
<func>
- <name name="element" arity="2"/>
+ <name name="element" arity="2" since=""/>
<fsummary>Return the Nth element of a tuple.</fsummary>
<type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc>
<desc>
@@ -1406,7 +1406,7 @@ b</pre>
</func>
<func>
- <name name="erase" arity="0"/>
+ <name name="erase" arity="0" since=""/>
<fsummary>Return and delete the process dictionary.</fsummary>
<desc>
<p>Returns the process dictionary and deletes it, for
@@ -1420,7 +1420,7 @@ b</pre>
</func>
<func>
- <name name="erase" arity="1"/>
+ <name name="erase" arity="1" since=""/>
<fsummary>Return and delete a value from the process dictionary.
</fsummary>
<desc>
@@ -1437,7 +1437,7 @@ b</pre>
</func>
<func>
- <name name="error" arity="1"/>
+ <name name="error" arity="1" since=""/>
<fsummary>Stop execution with a specified reason.</fsummary>
<desc>
<p>Stops the execution of the calling process with the reason
@@ -1461,7 +1461,7 @@ b</pre>
</func>
<func>
- <name name="error" arity="2"/>
+ <name name="error" arity="2" since=""/>
<fsummary>Stop execution with a specified reason.</fsummary>
<desc>
<p>Stops the execution of the calling process with the reason
@@ -1478,7 +1478,7 @@ b</pre>
</func>
<func>
- <name name="exit" arity="1"/>
+ <name name="exit" arity="1" since=""/>
<fsummary>Stop execution with a specified reason.</fsummary>
<desc>
<p>Stops the execution of the calling process with exit reason
@@ -1495,7 +1495,7 @@ b</pre>
</func>
<func>
- <name name="exit" arity="2"/>
+ <name name="exit" arity="2" since=""/>
<fsummary>Send an exit signal to a process or a port.</fsummary>
<desc>
<p>Sends an exit signal with exit reason <c><anno>Reason</anno></c> to
@@ -1531,7 +1531,7 @@ b</pre>
</func>
<func>
- <name name="external_size" arity="1"/>
+ <name name="external_size" arity="1" since="OTP R14B04"/>
<fsummary>Calculate the maximum size for a term encoded in the Erlang
external term format.</fsummary>
<desc>
@@ -1550,7 +1550,7 @@ erlang:external_size(<anno>Term</anno>, [])</code>
</func>
<func>
- <name name="external_size" arity="2"/>
+ <name name="external_size" arity="2" since="OTP R14B04"/>
<fsummary>Calculate the maximum size for a term encoded in the Erlang
external term format.</fsummary>
<desc>
@@ -1570,7 +1570,7 @@ true</pre>
</func>
<func>
- <name name="float" arity="1"/>
+ <name name="float" arity="1" since=""/>
<fsummary>Convert a number to a float.</fsummary>
<desc>
<p>Returns a float by converting <c><anno>Number</anno></c> to a float,
@@ -1592,7 +1592,7 @@ true</pre>
</func>
<func>
- <name name="float_to_binary" arity="1"/>
+ <name name="float_to_binary" arity="1" since="OTP R16B"/>
<fsummary>Text representation of a float.</fsummary>
<desc>
<p>The same as
@@ -1601,7 +1601,7 @@ true</pre>
</func>
<func>
- <name name="float_to_binary" arity="2"/>
+ <name name="float_to_binary" arity="2" since="OTP R16B"/>
<fsummary>Text representation of a float formatted using specified
options.</fsummary>
<desc>
@@ -1619,7 +1619,7 @@ true</pre>
</func>
<func>
- <name name="float_to_list" arity="1"/>
+ <name name="float_to_list" arity="1" since=""/>
<fsummary>Text representation of a float.</fsummary>
<desc>
<p>The same as
@@ -1628,7 +1628,7 @@ true</pre>
</func>
<func>
- <name name="float_to_list" arity="2"/>
+ <name name="float_to_list" arity="2" since="OTP R16B"/>
<fsummary>Text representation of a float formatted using specified
options.</fsummary>
<desc>
@@ -1664,7 +1664,7 @@ true</pre>
</func>
<func>
- <name name="floor" arity="1"/>
+ <name name="floor" arity="1" since="OTP 20.0"/>
<fsummary>Returns the largest integer not greater than the argument</fsummary>
<desc>
<p>Returns the largest integer not greater than
@@ -1678,7 +1678,7 @@ true</pre>
</func>
<func>
- <name name="fun_info" arity="1"/>
+ <name name="fun_info" arity="1" since=""/>
<fsummary>Information about a fun.</fsummary>
<desc>
<p>Returns a list with information about the fun
@@ -1751,6 +1751,10 @@ true</pre>
<item>
<p><c>Pid</c> is the process identifier of the process
that originally created the fun.</p>
+ <p>It might point to the <c>init</c> process if the
+ <c>Fun</c> was statically allocated when module was
+ loaded (this optimisation is performed for local
+ functions that do not capture the enviornment).</p>
</item>
<tag><c>{index, Index}</c></tag>
<item>
@@ -1779,7 +1783,7 @@ true</pre>
</func>
<func>
- <name name="fun_info" arity="2"/>
+ <name name="fun_info" arity="2" since=""/>
<fsummary>Information about a fun.</fsummary>
<type name="fun_info_item"/>
<desc>
@@ -1799,7 +1803,7 @@ true</pre>
</func>
<func>
- <name name="fun_to_list" arity="1"/>
+ <name name="fun_to_list" arity="1" since=""/>
<fsummary>Text representation of a fun.</fsummary>
<desc>
<p>Returns a string corresponding to the text
@@ -1808,7 +1812,7 @@ true</pre>
</func>
<func>
- <name name="function_exported" arity="3"/>
+ <name name="function_exported" arity="3" since=""/>
<fsummary>Check if a function is exported and loaded.</fsummary>
<desc>
<p>Returns <c>true</c> if the module <c><anno>Module</anno></c> is
@@ -1824,7 +1828,7 @@ true</pre>
</func>
<func>
- <name name="garbage_collect" arity="0"/>
+ <name name="garbage_collect" arity="0" since=""/>
<fsummary>Force an immediate garbage collection of the calling process.
</fsummary>
<desc>
@@ -1840,7 +1844,7 @@ true</pre>
</func>
<func>
- <name name="garbage_collect" arity="1"/>
+ <name name="garbage_collect" arity="1" since=""/>
<fsummary>Garbage collect a process.</fsummary>
<desc>
<p>The same as
@@ -1850,7 +1854,7 @@ true</pre>
</func>
<func>
- <name name="garbage_collect" arity="2"/>
+ <name name="garbage_collect" arity="2" since="OTP 17.0"/>
<fsummary>Garbage collect a process.</fsummary>
<desc>
<p>Garbage collects the node local process identified by
@@ -1915,7 +1919,7 @@ true</pre>
</func>
<func>
- <name name="get" arity="0"/>
+ <name name="get" arity="0" since=""/>
<fsummary>Return the process dictionary.</fsummary>
<desc>
<p>Returns the process dictionary as a list of
@@ -1930,7 +1934,7 @@ true</pre>
</func>
<func>
- <name name="get" arity="1"/>
+ <name name="get" arity="1" since=""/>
<fsummary>Return a value from the process dictionary.</fsummary>
<desc>
<p>Returns the value <c><anno>Val</anno></c> associated with
@@ -1946,7 +1950,7 @@ true</pre>
</func>
<func>
- <name name="get_cookie" arity="0"/>
+ <name name="get_cookie" arity="0" since=""/>
<fsummary>Get the magic cookie of the local node.</fsummary>
<desc>
<p>Returns the magic cookie of the local node if the node is
@@ -1955,7 +1959,7 @@ true</pre>
</func>
<func>
- <name name="get_keys" arity="0"/>
+ <name name="get_keys" arity="0" since="OTP 18.0"/>
<fsummary>Return a list of all keys from the process dictionary.
</fsummary>
<desc>
@@ -1971,7 +1975,7 @@ true</pre>
</func>
<func>
- <name name="get_keys" arity="1"/>
+ <name name="get_keys" arity="1" since=""/>
<fsummary>Return a list of keys from the process dictionary.</fsummary>
<desc>
<p>Returns a list of keys that are associated with the value
@@ -1989,7 +1993,7 @@ true</pre>
</func>
<func>
- <name name="get_stacktrace" arity="0"/>
+ <name name="get_stacktrace" arity="0" since=""/>
<fsummary>Get the call stack back-trace of the last exception.</fsummary>
<type name="stack_item"/>
<desc>
@@ -2054,7 +2058,7 @@ end</pre>
</func>
<func>
- <name name="group_leader" arity="0"/>
+ <name name="group_leader" arity="0" since=""/>
<fsummary>Get the group leader for the calling process.</fsummary>
<desc>
<p>Returns the process identifier of the group leader for the
@@ -2069,7 +2073,7 @@ end</pre>
</func>
<func>
- <name name="group_leader" arity="2"/>
+ <name name="group_leader" arity="2" since=""/>
<fsummary>Set the group leader for a process.</fsummary>
<desc>
<p>Sets the group leader of <c><anno>Pid</anno></c>
@@ -2090,7 +2094,7 @@ end</pre>
</func>
<func>
- <name name="halt" arity="0"/>
+ <name name="halt" arity="0" since=""/>
<fsummary>Halt the Erlang runtime system and indicate normal exit to
the calling environment.</fsummary>
<desc>
@@ -2103,7 +2107,7 @@ os_prompt%</pre>
</func>
<func>
- <name name="halt" arity="1"/>
+ <name name="halt" arity="1" since=""/>
<fsummary>Halt the Erlang runtime system.</fsummary>
<desc>
<p>The same as <seealso marker="#halt/2">
@@ -2117,7 +2121,7 @@ os_prompt%</pre>
</func>
<func>
- <name name="halt" arity="2"/>
+ <name name="halt" arity="2" since="OTP R15B01"/>
<fsummary>Halt the Erlang runtime system.</fsummary>
<desc>
<p><c><anno>Status</anno></c> must be a non-negative integer, a string,
@@ -2159,7 +2163,7 @@ os_prompt%</pre>
</func>
<func>
- <name name="hd" arity="1"/>
+ <name name="hd" arity="1" since=""/>
<fsummary>Head of a list.</fsummary>
<desc>
<p>Returns the head of <c><anno>List</anno></c>, that is,
@@ -2174,7 +2178,7 @@ os_prompt%</pre>
</func>
<func>
- <name name="hibernate" arity="3"/>
+ <name name="hibernate" arity="3" since=""/>
<fsummary>Hibernate a process until a message is sent to it.</fsummary>
<desc>
<p>Puts the calling process into a wait state where its memory
@@ -2215,7 +2219,7 @@ os_prompt%</pre>
</func>
<func>
- <name name="insert_element" arity="3"/>
+ <name name="insert_element" arity="3" since="OTP R16B"/>
<fsummary>Insert an element at index in a tuple.</fsummary>
<type_desc variable="Index">1..tuple_size(<anno>Tuple1</anno>)
+ 1</type_desc>
@@ -2233,7 +2237,7 @@ os_prompt%</pre>
</func>
<func>
- <name name="integer_to_binary" arity="1"/>
+ <name name="integer_to_binary" arity="1" since="OTP R16B"/>
<fsummary>Text representation of an integer.</fsummary>
<desc>
<p>Returns a binary corresponding to the text
@@ -2245,7 +2249,7 @@ os_prompt%</pre>
</func>
<func>
- <name name="integer_to_binary" arity="2"/>
+ <name name="integer_to_binary" arity="2" since="OTP R16B"/>
<fsummary>Text representation of an integer.</fsummary>
<desc>
<p>Returns a binary corresponding to the text
@@ -2258,7 +2262,7 @@ os_prompt%</pre>
</func>
<func>
- <name name="integer_to_list" arity="1"/>
+ <name name="integer_to_list" arity="1" since=""/>
<fsummary>Text representation of an integer.</fsummary>
<desc>
<p>Returns a string corresponding to the text
@@ -2270,7 +2274,7 @@ os_prompt%</pre>
</func>
<func>
- <name name="integer_to_list" arity="2"/>
+ <name name="integer_to_list" arity="2" since=""/>
<fsummary>Text representation of an integer.</fsummary>
<desc>
<p>Returns a string corresponding to the text
@@ -2283,7 +2287,7 @@ os_prompt%</pre>
</func>
<func>
- <name name="iolist_size" arity="1"/>
+ <name name="iolist_size" arity="1" since=""/>
<fsummary>Size of an iolist.</fsummary>
<desc>
<p>Returns an integer, that is the size in bytes,
@@ -2296,7 +2300,7 @@ os_prompt%</pre>
</func>
<func>
- <name name="iolist_to_binary" arity="1"/>
+ <name name="iolist_to_binary" arity="1" since=""/>
<fsummary>Convert an iolist to a binary.</fsummary>
<desc>
<p>Returns a binary that is made from the integers and
@@ -2314,7 +2318,7 @@ os_prompt%</pre>
</func>
<func>
- <name name="iolist_to_iovec" arity="1"/>
+ <name name="iolist_to_iovec" arity="1" since="OTP 20.1"/>
<fsummary>Converts an iolist to a iovec.</fsummary>
<desc>
<p>Returns an iovec that is made from the integers and binaries in
@@ -2323,7 +2327,7 @@ os_prompt%</pre>
</func>
<func>
- <name name="is_alive" arity="0"/>
+ <name name="is_alive" arity="0" since=""/>
<fsummary>Check whether the local node is alive.</fsummary>
<desc>
<p>Returns <c>true</c> if the local node is alive (that is, if
@@ -2333,7 +2337,7 @@ os_prompt%</pre>
</func>
<func>
- <name name="is_atom" arity="1"/>
+ <name name="is_atom" arity="1" since=""/>
<fsummary>Check whether a term is an atom.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is an atom,
@@ -2343,7 +2347,7 @@ os_prompt%</pre>
</func>
<func>
- <name name="is_binary" arity="1"/>
+ <name name="is_binary" arity="1" since=""/>
<fsummary>Check whether a term is a binary.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is a binary,
@@ -2354,7 +2358,7 @@ os_prompt%</pre>
</func>
<func>
- <name name="is_bitstring" arity="1"/>
+ <name name="is_bitstring" arity="1" since=""/>
<fsummary>Check whether a term is a bitstring.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is a
@@ -2364,7 +2368,7 @@ os_prompt%</pre>
</func>
<func>
- <name name="is_boolean" arity="1"/>
+ <name name="is_boolean" arity="1" since=""/>
<fsummary>Check whether a term is a boolean.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is the
@@ -2375,7 +2379,7 @@ os_prompt%</pre>
</func>
<func>
- <name name="is_builtin" arity="3"/>
+ <name name="is_builtin" arity="3" since=""/>
<fsummary>Check if a function is a BIF implemented in C.</fsummary>
<desc>
<p>This BIF is useful for builders of cross-reference tools.</p>
@@ -2386,7 +2390,7 @@ os_prompt%</pre>
</func>
<func>
- <name name="is_float" arity="1"/>
+ <name name="is_float" arity="1" since=""/>
<fsummary>Check whether a term is a float.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is a floating point
@@ -2396,7 +2400,7 @@ os_prompt%</pre>
</func>
<func>
- <name name="is_function" arity="1"/>
+ <name name="is_function" arity="1" since=""/>
<fsummary>Check whether a term is a fun.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is a fun, otherwise
@@ -2406,7 +2410,7 @@ os_prompt%</pre>
</func>
<func>
- <name name="is_function" arity="2"/>
+ <name name="is_function" arity="2" since=""/>
<fsummary>Check whether a term is a fun with a specified given arity.
</fsummary>
<desc>
@@ -2418,7 +2422,7 @@ os_prompt%</pre>
</func>
<func>
- <name name="is_integer" arity="1"/>
+ <name name="is_integer" arity="1" since=""/>
<fsummary>Check whether a term is an integer.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is an integer,
@@ -2428,7 +2432,7 @@ os_prompt%</pre>
</func>
<func>
- <name name="is_list" arity="1"/>
+ <name name="is_list" arity="1" since=""/>
<fsummary>Check whether a term is a list.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is a list with
@@ -2438,7 +2442,7 @@ os_prompt%</pre>
</func>
<func>
- <name name="is_map" arity="1"/>
+ <name name="is_map" arity="1" since="OTP 17.0"/>
<fsummary>Check whether a term is a map.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is a map,
@@ -2448,7 +2452,7 @@ os_prompt%</pre>
</func>
<func>
- <name name="is_map_key" arity="2"/>
+ <name name="is_map_key" arity="2" since="OTP 21.0"/>
<fsummary></fsummary>
<desc>
<p>Returns <c>true</c> if map <c><anno>Map</anno></c> contains
@@ -2468,7 +2472,7 @@ false</code>
</func>
<func>
- <name name="is_number" arity="1"/>
+ <name name="is_number" arity="1" since=""/>
<fsummary>Check whether a term is a number.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is an integer or a
@@ -2478,7 +2482,7 @@ false</code>
</func>
<func>
- <name name="is_pid" arity="1"/>
+ <name name="is_pid" arity="1" since=""/>
<fsummary>Check whether a term is a process identifier.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is a process
@@ -2488,7 +2492,7 @@ false</code>
</func>
<func>
- <name name="is_port" arity="1"/>
+ <name name="is_port" arity="1" since=""/>
<fsummary>Check whether a term is a port.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is a port identifier,
@@ -2498,7 +2502,7 @@ false</code>
</func>
<func>
- <name name="is_process_alive" arity="1"/>
+ <name name="is_process_alive" arity="1" since=""/>
<fsummary>Check whether a process is alive.</fsummary>
<desc>
<p><c><anno>Pid</anno></c> must refer to a process at the local
@@ -2510,7 +2514,7 @@ false</code>
</func>
<func>
- <name name="is_record" arity="2"/>
+ <name name="is_record" arity="2" since=""/>
<fsummary>Check whether a term appears to be a record.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is a tuple and its
@@ -2531,7 +2535,7 @@ false</code>
</func>
<func>
- <name name="is_record" arity="3"/>
+ <name name="is_record" arity="3" since=""/>
<fsummary>Check whether a term appears to be a record.</fsummary>
<desc>
<p><c><anno>RecordTag</anno></c> must be an atom.</p>
@@ -2550,7 +2554,7 @@ false</code>
</func>
<func>
- <name name="is_reference" arity="1"/>
+ <name name="is_reference" arity="1" since=""/>
<fsummary>Check whether a term is a reference.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is a reference,
@@ -2560,7 +2564,7 @@ false</code>
</func>
<func>
- <name name="is_tuple" arity="1"/>
+ <name name="is_tuple" arity="1" since=""/>
<fsummary>Check whether a term is a tuple.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is a tuple,
@@ -2570,7 +2574,7 @@ false</code>
</func>
<func>
- <name name="length" arity="1"/>
+ <name name="length" arity="1" since=""/>
<fsummary>Length of a list.</fsummary>
<desc>
<p>Returns the length of <c><anno>List</anno></c>, for example:</p>
@@ -2582,7 +2586,7 @@ false</code>
</func>
<func>
- <name name="link" arity="1"/>
+ <name name="link" arity="1" since=""/>
<fsummary>Create a link to another process (or port).</fsummary>
<desc>
<p>Creates a link between the calling process and another
@@ -2609,7 +2613,7 @@ false</code>
</func>
<func>
- <name name="list_to_atom" arity="1"/>
+ <name name="list_to_atom" arity="1" since=""/>
<fsummary>Convert from text representation to an atom.</fsummary>
<desc>
<p>Returns the atom whose text representation is
@@ -2629,7 +2633,7 @@ false</code>
</func>
<func>
- <name name="list_to_binary" arity="1"/>
+ <name name="list_to_binary" arity="1" since=""/>
<fsummary>Convert a list to a binary.</fsummary>
<desc>
<p>Returns a binary that is made from the integers and
@@ -2647,7 +2651,7 @@ false</code>
</func>
<func>
- <name name="list_to_bitstring" arity="1"/>
+ <name name="list_to_bitstring" arity="1" since=""/>
<fsummary>Convert a list to a bitstring.</fsummary>
<type name="bitstring_list"/>
<desc>
@@ -2668,7 +2672,7 @@ false</code>
</func>
<func>
- <name name="list_to_existing_atom" arity="1"/>
+ <name name="list_to_existing_atom" arity="1" since=""/>
<fsummary>Convert from text representation to an atom.</fsummary>
<desc>
<p>Returns the atom whose text representation is
@@ -2689,7 +2693,7 @@ false</code>
</func>
<func>
- <name name="list_to_float" arity="1"/>
+ <name name="list_to_float" arity="1" since=""/>
<fsummary>Convert from text representation to a float.</fsummary>
<desc>
<p>Returns the float whose text representation is
@@ -2703,7 +2707,7 @@ false</code>
</func>
<func>
- <name name="list_to_integer" arity="1"/>
+ <name name="list_to_integer" arity="1" since=""/>
<fsummary>Convert from text representation to an integer.</fsummary>
<desc>
<p>Returns an integer whose text representation is
@@ -2717,7 +2721,7 @@ false</code>
</func>
<func>
- <name name="list_to_integer" arity="2"/>
+ <name name="list_to_integer" arity="2" since=""/>
<fsummary>Convert from text representation to an integer.</fsummary>
<desc>
<p>Returns an integer whose text representation in base
@@ -2732,7 +2736,7 @@ false</code>
</func>
<func>
- <name name="list_to_pid" arity="1"/>
+ <name name="list_to_pid" arity="1" since=""/>
<fsummary>Convert from text representation to a pid.</fsummary>
<desc>
<p>Returns a process identifier whose text representation is a
@@ -2750,7 +2754,7 @@ false</code>
</func>
<func>
- <name name="list_to_port" arity="1"/>
+ <name name="list_to_port" arity="1" since="OTP 20.0"/>
<fsummary>Convert from text representation to a port.</fsummary>
<desc>
<p>Returns a port identifier whose text representation is a
@@ -2768,7 +2772,7 @@ false</code>
</func>
<func>
- <name name="list_to_ref" arity="1"/>
+ <name name="list_to_ref" arity="1" since="OTP 20.0"/>
<fsummary>Convert from text representation to a ref.</fsummary>
<desc>
<p>Returns a reference whose text representation is a
@@ -2786,7 +2790,7 @@ false</code>
</func>
<func>
- <name name="list_to_tuple" arity="1"/>
+ <name name="list_to_tuple" arity="1" since=""/>
<fsummary>Convert a list to a tuple.</fsummary>
<desc>
<p>Returns a tuple corresponding to <c><anno>List</anno></c>,
@@ -2799,7 +2803,7 @@ false</code>
</func>
<func>
- <name name="load_module" arity="2"/>
+ <name name="load_module" arity="2" since=""/>
<fsummary>Load object code for a module.</fsummary>
<desc>
<p>If <c><anno>Binary</anno></c> contains the object code for module
@@ -2832,7 +2836,7 @@ false</code>
</func>
<func>
- <name name="load_nif" arity="2"/>
+ <name name="load_nif" arity="2" since=""/>
<fsummary>Load NIF library.</fsummary>
<desc>
<p>Loads and links a dynamic library containing native
@@ -2885,7 +2889,7 @@ false</code>
</func>
<func>
- <name name="loaded" arity="0"/>
+ <name name="loaded" arity="0" since=""/>
<fsummary>List all loaded modules.</fsummary>
<desc>
<p>Returns a list of all loaded Erlang modules (current and
@@ -2896,7 +2900,7 @@ false</code>
</func>
<func>
- <name name="localtime" arity="0"/>
+ <name name="localtime" arity="0" since=""/>
<fsummary>Current local date and time.</fsummary>
<desc>
<p>Returns the current local date and time,
@@ -2911,7 +2915,7 @@ false</code>
</func>
<func>
- <name name="localtime_to_universaltime" arity="1"/>
+ <name name="localtime_to_universaltime" arity="1" since=""/>
<fsummary>Convert from local to Universal Time Coordinated (UTC) date
and time.</fsummary>
<desc>
@@ -2928,7 +2932,7 @@ false</code>
</func>
<func>
- <name name="localtime_to_universaltime" arity="2"/>
+ <name name="localtime_to_universaltime" arity="2" since=""/>
<fsummary>Convert from local to Universal Time Coordinated (UTC) date
and time.</fsummary>
<desc>
@@ -2954,7 +2958,7 @@ false</code>
</func>
<func>
- <name name="make_ref" arity="0"/>
+ <name name="make_ref" arity="0" since=""/>
<fsummary>Return a unique reference.</fsummary>
<desc>
<p>Returns a
@@ -2971,7 +2975,7 @@ false</code>
</func>
<func>
- <name name="make_tuple" arity="2"/>
+ <name name="make_tuple" arity="2" since=""/>
<fsummary>Create a new tuple of a specified arity.</fsummary>
<desc>
<p>Creates a new tuple of the specified <c><anno>Arity</anno></c>, where
@@ -2983,7 +2987,7 @@ false</code>
</func>
<func>
- <name name="make_tuple" arity="3"/>
+ <name name="make_tuple" arity="3" since=""/>
<fsummary>Create a new tuple with specifed arity and contents.</fsummary>
<desc>
<p>Creates a tuple of size <c><anno>Arity</anno></c>, where each element
@@ -3001,7 +3005,7 @@ false</code>
</func>
<func>
- <name name="map_get" arity="2" />
+ <name name="map_get" arity="2" since="OTP 21.0"/>
<fsummary>Extract a value from a map</fsummary>
<desc>
<p>Returns value <c><anno>Value</anno></c> associated with
@@ -3020,7 +3024,7 @@ false</code>
</func>
<func>
- <name name="map_size" arity="1"/>
+ <name name="map_size" arity="1" since="OTP 17.0"/>
<fsummary>Return the size of a map.</fsummary>
<desc>
<p>Returns an integer, which is the number of key-value pairs
@@ -3033,7 +3037,7 @@ false</code>
</func>
<func>
- <name name="match_spec_test" arity="3"/>
+ <name name="match_spec_test" arity="3" since="OTP 19.0"/>
<fsummary>Test that a match specification works.</fsummary>
<desc>
<p>Tests a match specification used in calls to
@@ -3071,7 +3075,7 @@ false</code>
</func>
<func>
- <name name="max" arity="2"/>
+ <name name="max" arity="2" since=""/>
<fsummary>Return the largest of two terms.</fsummary>
<desc>
<p>Returns the largest of <c><anno>Term1</anno></c> and
@@ -3081,7 +3085,7 @@ false</code>
</func>
<func>
- <name name="md5" arity="1"/>
+ <name name="md5" arity="1" since=""/>
<fsummary>Compute an MD5 message digest.</fsummary>
<desc>
<p>Computes an MD5 message digest from <c><anno>Data</anno></c>, where
@@ -3099,7 +3103,7 @@ false</code>
</func>
<func>
- <name name="md5_final" arity="1"/>
+ <name name="md5_final" arity="1" since=""/>
<fsummary>Finish the update of an MD5 context and return the computed
MD5 message digest.</fsummary>
<desc>
@@ -3109,7 +3113,7 @@ false</code>
</func>
<func>
- <name name="md5_init" arity="0"/>
+ <name name="md5_init" arity="0" since=""/>
<fsummary>Create an MD5 context.</fsummary>
<desc>
<p>Creates an MD5 context, to be used in the following calls to
@@ -3118,7 +3122,7 @@ false</code>
</func>
<func>
- <name name="md5_update" arity="2"/>
+ <name name="md5_update" arity="2" since=""/>
<fsummary>Update an MD5 context with data and return a new context.
</fsummary>
<desc>
@@ -3129,7 +3133,7 @@ false</code>
</func>
<func>
- <name name="memory" arity="0"/>
+ <name name="memory" arity="0" since=""/>
<fsummary>Information about dynamically allocated memory.</fsummary>
<type name="memory_type"/>
<desc>
@@ -3273,8 +3277,8 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="memory" arity="1" clause_i="1"/>
- <name name="memory" arity="1" clause_i="2"/>
+ <name name="memory" arity="1" clause_i="1" since=""/>
+ <name name="memory" arity="1" clause_i="2" since=""/>
<fsummary>Information about dynamically allocated memory.</fsummary>
<type name="memory_type"/>
<desc>
@@ -3313,7 +3317,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="min" arity="2"/>
+ <name name="min" arity="2" since=""/>
<fsummary>Return the smallest of two terms.</fsummary>
<desc>
<p>Returns the smallest of <c><anno>Term1</anno></c> and
@@ -3323,7 +3327,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="module_loaded" arity="1"/>
+ <name name="module_loaded" arity="1" since=""/>
<fsummary>Check if a module is loaded.</fsummary>
<desc>
<p>Returns <c>true</c> if the module <c><anno>Module</anno></c>
@@ -3338,9 +3342,9 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="monitor" arity="2" clause_i="1"/>
- <name name="monitor" arity="2" clause_i="2"/>
- <name name="monitor" arity="2" clause_i="3"/>
+ <name name="monitor" arity="2" clause_i="1" since=""/>
+ <name name="monitor" arity="2" clause_i="2" since="OTP 19.0"/>
+ <name name="monitor" arity="2" clause_i="3" since="OTP 18.0"/>
<fsummary>Start monitoring.</fsummary>
<type name="registered_name"/>
<type name="registered_process_identifier"/>
@@ -3512,7 +3516,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="monitor_node" arity="2"/>
+ <name name="monitor_node" arity="2" since=""/>
<fsummary>Monitor the status of a node.</fsummary>
<desc>
<p>Monitor the status of the node <c><anno>Node</anno></c>.
@@ -3536,7 +3540,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="monitor_node" arity="3"/>
+ <name name="monitor_node" arity="3" since=""/>
<fsummary>Monitor the status of a node.</fsummary>
<desc>
<p>Behaves as
@@ -3562,7 +3566,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="monotonic_time" arity="0"/>
+ <name name="monotonic_time" arity="0" since="OTP 18.0"/>
<fsummary>Current Erlang monotonic time.</fsummary>
<desc>
<p>Returns the current
@@ -3596,7 +3600,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="monotonic_time" arity="1"/>
+ <name name="monotonic_time" arity="1" since="OTP 18.0"/>
<fsummary>Current Erlang monotonic time.</fsummary>
<desc>
<p>Returns the current
@@ -3614,7 +3618,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="nif_error" arity="1"/>
+ <name name="nif_error" arity="1" since="OTP R14B"/>
<fsummary>Stop execution with a specified reason.</fsummary>
<desc>
<p>Works exactly like
@@ -3627,7 +3631,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="nif_error" arity="2"/>
+ <name name="nif_error" arity="2" since="OTP R14B"/>
<fsummary>Stop execution with a specified reason.</fsummary>
<desc>
<p>Works exactly like
@@ -3640,7 +3644,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="node" arity="0"/>
+ <name name="node" arity="0" since=""/>
<fsummary>Name of the local node.</fsummary>
<desc>
<p>Returns the name of the local node. If the node is not alive,
@@ -3650,7 +3654,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="node" arity="1"/>
+ <name name="node" arity="1" since=""/>
<fsummary>At which node a pid, port, or reference originates.</fsummary>
<desc>
<p>Returns the node where <c><anno>Arg</anno></c> originates.
@@ -3663,7 +3667,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="nodes" arity="0"/>
+ <name name="nodes" arity="0" since=""/>
<fsummary>All visible nodes in the system.</fsummary>
<desc>
<p>Returns a list of all visible nodes in the system, except
@@ -3672,7 +3676,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="nodes" arity="1"/>
+ <name name="nodes" arity="1" since=""/>
<fsummary>All nodes of a certain type in the system.</fsummary>
<desc>
<p>Returns a list of nodes according to the argument specified.
@@ -3715,7 +3719,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="now" arity="0"/>
+ <name name="now" arity="0" since=""/>
<fsummary>Elapsed time since 00:00 GMT.</fsummary>
<type name="timestamp"/>
<desc>
@@ -3744,7 +3748,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="open_port" arity="2"/>
+ <name name="open_port" arity="2" since=""/>
<fsummary>Open a port.</fsummary>
<desc>
<p>Returns a port identifier as the result of opening a
@@ -4085,7 +4089,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="phash" arity="2"/>
+ <name name="phash" arity="2" since=""/>
<fsummary>Portable hash function.</fsummary>
<type_desc variable="Range">Range = 1..2^32, Hash = 1..Range</type_desc>
<desc>
@@ -4100,8 +4104,8 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="phash2" arity="1"/>
- <name name="phash2" arity="2"/>
+ <name name="phash2" arity="1" since=""/>
+ <name name="phash2" arity="2" since=""/>
<fsummary>Portable hash function.</fsummary>
<type_desc variable="Range">1..2^32</type_desc>
<type_desc variable="Hash">0..Range-1</type_desc>
@@ -4125,7 +4129,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="pid_to_list" arity="1"/>
+ <name name="pid_to_list" arity="1" since=""/>
<fsummary>Text representation of a pid.</fsummary>
<desc>
<p>Returns a string corresponding to the text
@@ -4134,7 +4138,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="port_call" arity="3"/>
+ <name name="port_call" arity="3" since=""/>
<fsummary>Perform a synchronous call to a port with term data.</fsummary>
<desc>
<p>Performs a synchronous call to a port. The meaning of
@@ -4170,16 +4174,23 @@ RealSystem = system + MissedSystem</code>
</item>
<tag><c>badarg</c></tag>
<item>
- If the port driver so decides for any reason (probably
+ <p>If the port driver so decides for any reason (probably
something wrong with <c><anno>Operation</anno></c>
- or <c><anno>Data</anno></c>).
+ or <c><anno>Data</anno></c>).</p>
+ <warning>
+ <p>Do not call <c>port_call</c> with an unknown
+ <c><anno>Port</anno></c> identifier and expect <c>badarg</c>
+ exception. Any undefined behavior is possible (including node
+ crash) depending on how the port driver interprets the supplied
+ arguments.</p>
+ </warning>
</item>
</taglist>
</desc>
</func>
<func>
- <name name="port_close" arity="1"/>
+ <name name="port_close" arity="1" since=""/>
<fsummary>Close an open port.</fsummary>
<desc>
<p>Closes an open port. Roughly the same as <c><anno>Port</anno> !
@@ -4219,7 +4230,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="port_command" arity="2"/>
+ <name name="port_command" arity="2" since=""/>
<fsummary>Send data to a port.</fsummary>
<desc>
<p>Sends data to a port. Same as
@@ -4262,11 +4273,16 @@ RealSystem = system + MissedSystem</code>
<p>If <c><anno>Data</anno></c> is an invalid I/O list.</p>
</item>
</taglist>
+ <warning>
+ <p>Do not send data to an unknown port. Any undefined behavior is
+ possible (including node crash) depending on how the port driver
+ interprets the data.</p>
+ </warning>
</desc>
</func>
<func>
- <name name="port_command" arity="3"/>
+ <name name="port_command" arity="3" since=""/>
<fsummary>Send data to a port.</fsummary>
<desc>
<p>Sends data to a port. <c>port_command(Port, Data, [])</c>
@@ -4321,11 +4337,16 @@ RealSystem = system + MissedSystem</code>
a busy port.
</item>
</taglist>
+ <warning>
+ <p>Do not send data to an unknown port. Any undefined behavior is
+ possible (including node crash) depending on how the port driver
+ interprets the data.</p>
+ </warning>
</desc>
</func>
<func>
- <name name="port_connect" arity="2"/>
+ <name name="port_connect" arity="2" since=""/>
<fsummary>Set the owner of a port.</fsummary>
<desc>
<p>Sets the port owner (the connected port) to <c><anno>Pid</anno></c>.
@@ -4394,7 +4415,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="port_control" arity="3"/>
+ <name name="port_control" arity="3" since=""/>
<fsummary>Perform a synchronous control operation on a port.</fsummary>
<desc>
<p>Performs a synchronous control operation on a port.
@@ -4425,13 +4446,20 @@ RealSystem = system + MissedSystem</code>
If the port driver so decides for any reason (probably
something wrong with <c><anno>Operation</anno></c> or
<c><anno>Data</anno></c>).
+ <warning>
+ <p>Do not call <c>port_control/3</c> with an unknown
+ <c><anno>Port</anno></c> identifier and expect <c>badarg</c>
+ exception. Any undefined behavior is possible (including node
+ crash) depending on how the port driver interprets the supplied
+ arguments.</p>
+ </warning>
</item>
</taglist>
</desc>
</func>
<func>
- <name name="port_info" arity="1"/>
+ <name name="port_info" arity="1" since=""/>
<fsummary>Information about a port.</fsummary>
<desc>
<p>Returns a list containing tuples with information about
@@ -4462,7 +4490,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="port_info" arity="2" clause_i="1"/>
+ <name name="port_info" arity="2" clause_i="1" since=""/>
<fsummary>Information about the connected process of a port.</fsummary>
<desc>
<p><c><anno>Pid</anno></c> is the process identifier of the process
@@ -4478,7 +4506,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="port_info" arity="2" clause_i="2"/>
+ <name name="port_info" arity="2" clause_i="2" since=""/>
<fsummary>Information about the internal index of a port.</fsummary>
<desc>
<p><c><anno>Index</anno></c> is the internal index of the port. This
@@ -4494,7 +4522,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="port_info" arity="2" clause_i="3"/>
+ <name name="port_info" arity="2" clause_i="3" since=""/>
<fsummary>Information about the input of a port.</fsummary>
<desc>
<p><c><anno>Bytes</anno></c> is the total number of bytes
@@ -4510,7 +4538,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="port_info" arity="2" clause_i="4"/>
+ <name name="port_info" arity="2" clause_i="4" since=""/>
<fsummary>Information about the links of a port.</fsummary>
<desc>
<p><c><anno>Pids</anno></c> is a list of the process identifiers
@@ -4526,7 +4554,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="port_info" arity="2" clause_i="5"/>
+ <name name="port_info" arity="2" clause_i="5" since="OTP R16B"/>
<fsummary>Information about the locking of a port.</fsummary>
<desc>
<p><c><anno>Locking</anno></c> is one of the following:</p>
@@ -4547,7 +4575,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="port_info" arity="2" clause_i="6"/>
+ <name name="port_info" arity="2" clause_i="6" since="OTP R16B"/>
<fsummary>Information about the memory size of a port.</fsummary>
<desc>
<p><c><anno>Bytes</anno></c> is the total number of
@@ -4565,7 +4593,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="port_info" arity="2" clause_i="7"/>
+ <name name="port_info" arity="2" clause_i="7" since="OTP R16B"/>
<fsummary>Information about the monitors of a port.</fsummary>
<desc>
<p><c><anno>Monitors</anno></c> represent processes monitored by
@@ -4581,7 +4609,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="port_info" arity="2" clause_i="8"/>
+ <name name="port_info" arity="2" clause_i="8" since="OTP 19.0"/>
<fsummary>Which processes are monitoring this port.</fsummary>
<desc>
<p>Returns list of pids that are monitoring given port at the
@@ -4597,7 +4625,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="port_info" arity="2" clause_i="9"/>
+ <name name="port_info" arity="2" clause_i="9" since=""/>
<fsummary>Information about the name of a port.</fsummary>
<desc>
<p><c><anno>Name</anno></c> is the command name set by
@@ -4613,7 +4641,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="port_info" arity="2" clause_i="10"/>
+ <name name="port_info" arity="2" clause_i="10" since="OTP R16B"/>
<fsummary>Information about the OS pid of a port.</fsummary>
<desc>
<p><c><anno>OsPid</anno></c> is the process identifier (or equivalent)
@@ -4632,7 +4660,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="port_info" arity="2" clause_i="11"/>
+ <name name="port_info" arity="2" clause_i="11" since=""/>
<fsummary>Information about the output of a port.</fsummary>
<desc>
<p><c><anno>Bytes</anno></c> is the total number of bytes written
@@ -4651,7 +4679,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="port_info" arity="2" clause_i="12"/>
+ <name name="port_info" arity="2" clause_i="12" since="OTP R16B"/>
<fsummary>Information about the parallelism hint of a port.</fsummary>
<desc>
<p><c><anno>Boolean</anno></c> corresponds to the port parallelism
@@ -4662,7 +4690,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="port_info" arity="2" clause_i="13"/>
+ <name name="port_info" arity="2" clause_i="13" since="OTP R16B"/>
<fsummary>Information about the queue size of a port.</fsummary>
<desc>
<p><c><anno>Bytes</anno></c> is the total number
@@ -4679,7 +4707,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="port_info" arity="2" clause_i="14"/>
+ <name name="port_info" arity="2" clause_i="14" since=""/>
<fsummary>Information about the registered name of a port.</fsummary>
<desc>
<p><c><anno>RegisteredName</anno></c> is the registered name of
@@ -4696,7 +4724,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="port_to_list" arity="1"/>
+ <name name="port_to_list" arity="1" since=""/>
<fsummary>Text representation of a port identifier.</fsummary>
<desc>
<p>Returns a string corresponding to the text
@@ -4705,7 +4733,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="ports" arity="0"/>
+ <name name="ports" arity="0" since=""/>
<fsummary>List all existing ports.</fsummary>
<desc>
<p>Returns a list of port identifiers corresponding to all the
@@ -4715,7 +4743,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="pre_loaded" arity="0"/>
+ <name name="pre_loaded" arity="0" since=""/>
<fsummary>List all preloaded modules.</fsummary>
<desc>
<p>Returns a list of Erlang modules that are preloaded in
@@ -4726,7 +4754,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="process_display" arity="2"/>
+ <name name="process_display" arity="2" since=""/>
<fsummary>Write information about a local process on standard error.
</fsummary>
<desc>
@@ -4740,7 +4768,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="process_flag" arity="2" clause_i="1"/>
+ <name name="process_flag" arity="2" clause_i="1" since=""/>
<fsummary>Set process flag trap_exit for the calling process.</fsummary>
<desc>
<p>When <c>trap_exit</c> is set to <c>true</c>, exit signals
@@ -4757,7 +4785,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="process_flag" arity="2" clause_i="2"/>
+ <name name="process_flag" arity="2" clause_i="2" since=""/>
<fsummary>Set process flag error_handler for the calling process.
</fsummary>
<desc>
@@ -4772,7 +4800,7 @@ RealSystem = system + MissedSystem</code>
<func>
<name name="process_flag" arity="2" clause_i="3"
- anchor="process_flag_min_heap_size"/>
+ anchor="process_flag_min_heap_size" since=""/>
<fsummary>Set process flag min_heap_size for the calling process.
</fsummary>
<desc>
@@ -4782,7 +4810,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="process_flag" arity="2" clause_i="4"/>
+ <name name="process_flag" arity="2" clause_i="4" since="OTP R13B04"/>
<fsummary>Set process flag min_bin_vheap_size for the calling process.
</fsummary>
<desc>
@@ -4794,7 +4822,7 @@ RealSystem = system + MissedSystem</code>
<func>
<name name="process_flag" arity="2" clause_i="5"
- anchor="process_flag_max_heap_size"/>
+ anchor="process_flag_max_heap_size" since="OTP 19.0"/>
<fsummary>Set process flag max_heap_size for the calling process.
</fsummary>
<type name="max_heap_size"/>
@@ -4868,7 +4896,7 @@ RealSystem = system + MissedSystem</code>
<func>
<name name="process_flag" arity="2" clause_i="6"
- anchor="process_flag_message_queue_data"/>
+ anchor="process_flag_message_queue_data" since="OTP 19.0"/>
<fsummary>Set process flag message_queue_data for the calling process.
</fsummary>
<type name="message_queue_data"/>
@@ -4910,7 +4938,7 @@ RealSystem = system + MissedSystem</code>
<func>
<name name="process_flag" arity="2" clause_i="7"
- anchor="process_flag_priority"/>
+ anchor="process_flag_priority" since=""/>
<fsummary>Set process flag priority for the calling process.</fsummary>
<type name="priority_level"/>
<desc>
@@ -4982,7 +5010,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="process_flag" arity="2" clause_i="8"/>
+ <name name="process_flag" arity="2" clause_i="8" since=""/>
<fsummary>Set process flag save_calls for the calling process.</fsummary>
<desc>
<p><c><anno>N</anno></c> must be an integer in the interval 0..10000.
@@ -5013,7 +5041,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="process_flag" arity="2" clause_i="9"/>
+ <name name="process_flag" arity="2" clause_i="9" since=""/>
<fsummary>Set process flag sensitive for the calling process.</fsummary>
<desc>
<p>Sets or clears flag <c>sensitive</c> for the current process.
@@ -5047,7 +5075,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="process_flag" arity="3"/>
+ <name name="process_flag" arity="3" since=""/>
<fsummary>Set process flags for a process.</fsummary>
<desc>
<p>Sets certain flags for the process <c><anno>Pid</anno></c>,
@@ -5062,7 +5090,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="process_info" arity="1"/>
+ <name name="process_info" arity="1" since=""/>
<fsummary>Information about a process.</fsummary>
<type name="process_info_result_item"/>
<type name="priority_level"/>
@@ -5113,8 +5141,8 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="process_info" arity="2" clause_i="1"/>
- <name name="process_info" arity="2" clause_i="2"/>
+ <name name="process_info" arity="2" clause_i="1" since=""/>
+ <name name="process_info" arity="2" clause_i="2" since=""/>
<fsummary>Information about a process.</fsummary>
<type name="process_info_item"/>
<type name="process_info_result_item"/>
@@ -5182,11 +5210,13 @@ RealSystem = system + MissedSystem</code>
changed or removed without prior notice.</p>
</item>
<tag><c>{current_function, {<anno>Module</anno>,
- <anno>Function</anno>, Arity}}</c></tag>
+ <anno>Function</anno>, Arity} | undefined}</c></tag>
<item>
<p><c><anno>Module</anno></c>, <c><anno>Function</anno></c>,
<c><anno>Arity</anno></c> is
- the current function call of the process.</p>
+ the current function call of the process. The value
+ <c>undefined</c> can be returned if the process is
+ currently executing native compiled code.</p>
</item>
<tag><c>{current_location, {<anno>Module</anno>,
<anno>Function</anno>, <anno>Arity</anno>,
@@ -5447,7 +5477,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="processes" arity="0"/>
+ <name name="processes" arity="0" since=""/>
<fsummary>All processes.</fsummary>
<desc>
<p>Returns a list of process identifiers corresponding to
@@ -5464,7 +5494,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="purge_module" arity="1"/>
+ <name name="purge_module" arity="1" since=""/>
<fsummary>Remove old code for a module.</fsummary>
<desc>
<p>Removes old code for <c><anno>Module</anno></c>.
@@ -5489,7 +5519,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="put" arity="2"/>
+ <name name="put" arity="2" since=""/>
<fsummary>Add a new value to the process dictionary.</fsummary>
<desc>
<p>Adds a new <c><anno>Key</anno></c> to the process dictionary,
@@ -5511,7 +5541,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="raise" arity="3"/>
+ <name name="raise" arity="3" since=""/>
<fsummary>Stop execution with an exception of specified class, reason,
and call stack backtrace.</fsummary>
<type name="raise_stacktrace"/>
@@ -5550,7 +5580,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="read_timer" arity="1"/>
+ <name name="read_timer" arity="1" since=""/>
<fsummary>Read the state of a timer.</fsummary>
<desc>
<p>Reads the state of a timer. The same as calling
@@ -5560,7 +5590,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="read_timer" arity="2"/>
+ <name name="read_timer" arity="2" since="OTP 18.0"/>
<fsummary>Read the state of a timer.</fsummary>
<desc>
<p>Reads the state of a timer that has been created by either
@@ -5616,7 +5646,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="ref_to_list" arity="1"/>
+ <name name="ref_to_list" arity="1" since=""/>
<fsummary>Text representation of a reference.</fsummary>
<desc>
<p>Returns a string corresponding to the text
@@ -5629,7 +5659,7 @@ RealSystem = system + MissedSystem</code>
</func>
<func>
- <name name="register" arity="2"/>
+ <name name="register" arity="2" since=""/>
<fsummary>Register a name for a pid (or port).</fsummary>
<desc>
<p>Associates the name <c><anno>RegName</anno></c> with a process
@@ -5658,7 +5688,7 @@ true</pre>
</func>
<func>
- <name name="registered" arity="0"/>
+ <name name="registered" arity="0" since=""/>
<fsummary>All registered names.</fsummary>
<desc>
<p>Returns a list of names that have been registered using
@@ -5671,7 +5701,7 @@ true</pre>
</func>
<func>
- <name name="resume_process" arity="1"/>
+ <name name="resume_process" arity="1" since=""/>
<fsummary>Resume a suspended process.</fsummary>
<desc>
<p>Decreases the suspend count on the process identified by
@@ -5712,7 +5742,7 @@ true</pre>
</func>
<func>
- <name name="round" arity="1"/>
+ <name name="round" arity="1" since=""/>
<fsummary>Return an integer by rounding a number.</fsummary>
<desc>
<p>Returns an integer by rounding <c><anno>Number</anno></c>,
@@ -5725,7 +5755,7 @@ true</pre>
</func>
<func>
- <name name="self" arity="0"/>
+ <name name="self" arity="0" since=""/>
<fsummary>Return pid of the calling process.</fsummary>
<desc>
<p>Returns the process identifier of the calling process, for
@@ -5738,7 +5768,7 @@ true</pre>
</func>
<func>
- <name name="send" arity="2"/>
+ <name name="send" arity="2" since=""/>
<fsummary>Send a message.</fsummary>
<type name="dst"/>
<desc>
@@ -5758,7 +5788,7 @@ true</pre>
</func>
<func>
- <name name="send" arity="3"/>
+ <name name="send" arity="3" since=""/>
<fsummary>Send a message conditionally.</fsummary>
<type name="dst"/>
<desc>
@@ -5790,7 +5820,7 @@ true</pre>
</func>
<func>
- <name name="send_after" arity="3"/>
+ <name name="send_after" arity="3" since=""/>
<fsummary>Start a timer.</fsummary>
<desc>
<p>Starts a timer. The same as calling
@@ -5801,7 +5831,7 @@ true</pre>
</func>
<func>
- <name name="send_after" arity="4"/>
+ <name name="send_after" arity="4" since="OTP 18.0"/>
<fsummary>Start a timer.</fsummary>
<desc>
<p>Starts a timer. When the timer expires, the message
@@ -5814,7 +5844,7 @@ true</pre>
</func>
<func>
- <name name="send_nosuspend" arity="2"/>
+ <name name="send_nosuspend" arity="2" since=""/>
<fsummary>Try to send a message without ever blocking.</fsummary>
<type name="dst"/>
<desc>
@@ -5864,7 +5894,7 @@ true</pre>
</func>
<func>
- <name name="send_nosuspend" arity="3"/>
+ <name name="send_nosuspend" arity="3" since=""/>
<fsummary>Try to send a message without ever blocking.</fsummary>
<type name="dst"/>
<desc>
@@ -5897,7 +5927,7 @@ true</pre>
</func>
<func>
- <name name="set_cookie" arity="2"/>
+ <name name="set_cookie" arity="2" since=""/>
<fsummary>Set the magic cookie of a node.</fsummary>
<desc>
<p>Sets the magic cookie of <c><anno>Node</anno></c> to the atom
@@ -5914,7 +5944,7 @@ true</pre>
</func>
<func>
- <name name="setelement" arity="3"/>
+ <name name="setelement" arity="3" since=""/>
<fsummary>Set the Nth element of a tuple.</fsummary>
<type_desc variable="Index">1..tuple_size(<anno>Tuple1</anno></type_desc>
<desc>
@@ -5931,7 +5961,7 @@ true</pre>
</func>
<func>
- <name name="size" arity="1"/>
+ <name name="size" arity="1" since=""/>
<fsummary>Size of a tuple or binary.</fsummary>
<desc>
<p>Returns the number of elements in a tuple or the number of
@@ -5954,7 +5984,7 @@ true</pre>
</func>
<func>
- <name name="spawn" arity="1"/>
+ <name name="spawn" arity="1" since=""/>
<fsummary>Create a new process with a fun as entry point.</fsummary>
<desc>
<p>Returns the process identifier of a new process started by the
@@ -5965,7 +5995,7 @@ true</pre>
</func>
<func>
- <name name="spawn" arity="2"/>
+ <name name="spawn" arity="2" since=""/>
<fsummary>Create a new process with a fun as entry point on a specified
node.</fsummary>
<desc>
@@ -5979,7 +6009,7 @@ true</pre>
</func>
<func>
- <name name="spawn" arity="3"/>
+ <name name="spawn" arity="3" since=""/>
<fsummary>Create a new process with a function as entry point.</fsummary>
<desc>
<p>Returns the process identifier of a new process started by
@@ -6004,7 +6034,7 @@ true</pre>
</func>
<func>
- <name name="spawn" arity="4"/>
+ <name name="spawn" arity="4" since=""/>
<fsummary>Create a new process with a function as entry point on a
specified node.</fsummary>
<desc>
@@ -6019,7 +6049,7 @@ true</pre>
</func>
<func>
- <name name="spawn_link" arity="1"/>
+ <name name="spawn_link" arity="1" since=""/>
<fsummary>Create and link to a new process with a fun as entry point.
</fsummary>
<desc>
@@ -6033,7 +6063,7 @@ true</pre>
</func>
<func>
- <name name="spawn_link" arity="2"/>
+ <name name="spawn_link" arity="2" since=""/>
<fsummary>Create and link to a new process with a fun as entry point on
a specified node.</fsummary>
<desc>
@@ -6050,7 +6080,7 @@ true</pre>
</func>
<func>
- <name name="spawn_link" arity="3"/>
+ <name name="spawn_link" arity="3" since=""/>
<fsummary>Create and link to a new process with a function as entry point.
</fsummary>
<desc>
@@ -6064,7 +6094,7 @@ true</pre>
</func>
<func>
- <name name="spawn_link" arity="4"/>
+ <name name="spawn_link" arity="4" since=""/>
<fsummary>Create and link to a new process with a function as entry point
on a specified node.</fsummary>
<desc>
@@ -6082,7 +6112,7 @@ true</pre>
</func>
<func>
- <name name="spawn_monitor" arity="1"/>
+ <name name="spawn_monitor" arity="1" since=""/>
<fsummary>Create and monitor a new process with a fun as entry point.
</fsummary>
<desc>
@@ -6096,7 +6126,7 @@ true</pre>
</func>
<func>
- <name name="spawn_monitor" arity="3"/>
+ <name name="spawn_monitor" arity="3" since=""/>
<fsummary>Create and monitor a new process with a function as entry point.
</fsummary>
<desc>
@@ -6110,7 +6140,7 @@ true</pre>
</func>
<func>
- <name name="spawn_opt" arity="2"/>
+ <name name="spawn_opt" arity="2" since=""/>
<fsummary>Create a new process with a fun as entry point.</fsummary>
<type name="priority_level"/>
<type name="max_heap_size"/>
@@ -6128,7 +6158,7 @@ true</pre>
</func>
<func>
- <name name="spawn_opt" arity="3"/>
+ <name name="spawn_opt" arity="3" since=""/>
<fsummary>Create a new process with a fun as entry point on a specified
node.</fsummary>
<type name="priority_level"/>
@@ -6146,7 +6176,7 @@ true</pre>
</func>
<func>
- <name name="spawn_opt" arity="4"/>
+ <name name="spawn_opt" arity="4" since=""/>
<fsummary>Create a new process with a function as entry point.</fsummary>
<type name="priority_level"/>
<type name="max_heap_size"/>
@@ -6282,7 +6312,7 @@ true</pre>
</func>
<func>
- <name name="spawn_opt" arity="5"/>
+ <name name="spawn_opt" arity="5" since=""/>
<fsummary>Create a new process with a function as entry point on a
specified node.</fsummary>
<type name="priority_level"/>
@@ -6305,7 +6335,7 @@ true</pre>
</func>
<func>
- <name name="split_binary" arity="2"/>
+ <name name="split_binary" arity="2" since=""/>
<fsummary>Split a binary into two.</fsummary>
<type_desc variable="Pos">0..byte_size(Bin)</type_desc>
<desc>
@@ -6329,7 +6359,7 @@ true</pre>
</func>
<func>
- <name name="start_timer" arity="3"/>
+ <name name="start_timer" arity="3" since=""/>
<fsummary>Start a timer.</fsummary>
<desc>
<p>Starts a timer. The same as calling
@@ -6340,7 +6370,7 @@ true</pre>
</func>
<func>
- <name name="start_timer" arity="4"/>
+ <name name="start_timer" arity="4" since="OTP 18.0"/>
<fsummary>Start a timer.</fsummary>
<desc>
<p>Starts a timer. When the timer expires, the message
@@ -6399,7 +6429,7 @@ true</pre>
<func>
<name name="statistics" arity="1" clause_i="1"
- anchor="statistics_active_tasks"/>
+ anchor="statistics_active_tasks" since="OTP 18.3"/>
<fsummary>Information about active processes and ports.</fsummary>
<desc>
<p>Returns the same as
@@ -6414,7 +6444,7 @@ true</pre>
<func>
<name name="statistics" arity="1" clause_i="2"
- anchor="statistics_active_tasks_all"/>
+ anchor="statistics_active_tasks_all" since="OTP 20.0"/>
<fsummary>Information about active processes and ports.</fsummary>
<desc>
<p>Returns a list where each element represents the amount
@@ -6455,7 +6485,7 @@ true</pre>
</func>
<func>
- <name name="statistics" arity="1" clause_i="3"/>
+ <name name="statistics" arity="1" clause_i="3" since=""/>
<fsummary>Information about context switches.</fsummary>
<desc>
<p>Returns the total number of context switches since the
@@ -6465,7 +6495,7 @@ true</pre>
<func>
<name name="statistics" arity="1" clause_i="4"
- anchor="statistics_exact_reductions"/>
+ anchor="statistics_exact_reductions" since=""/>
<fsummary>Information about exact reductions.</fsummary>
<desc>
<p>Returns the number of exact reductions.</p>
@@ -6479,7 +6509,7 @@ true</pre>
</func>
<func>
- <name name="statistics" arity="1" clause_i="5"/>
+ <name name="statistics" arity="1" clause_i="5" since=""/>
<fsummary>Information about garbage collection.</fsummary>
<desc>
<p>Returns information about garbage collection, for example:</p>
@@ -6491,7 +6521,7 @@ true</pre>
</func>
<func>
- <name name="statistics" arity="1" clause_i="6"/>
+ <name name="statistics" arity="1" clause_i="6" since=""/>
<fsummary>Information about I/O.</fsummary>
<desc>
<p>Returns <c><anno>Input</anno></c>,
@@ -6503,7 +6533,7 @@ true</pre>
<func>
<name name="statistics" arity="1" clause_i="7"
- anchor="statistics_microstate_accounting"/>
+ anchor="statistics_microstate_accounting" since="OTP 19.0"/>
<fsummary>Information about microstate accounting.</fsummary>
<desc>
<p>Microstate accounting can be used to measure how much time the Erlang
@@ -6646,7 +6676,7 @@ lists:map(
<func>
<name name="statistics" arity="1" clause_i="8"
- anchor="statistics_reductions"/>
+ anchor="statistics_reductions" since=""/>
<fsummary>Information about reductions.</fsummary>
<desc>
<p>Returns information about reductions, for example:</p>
@@ -6665,7 +6695,7 @@ lists:map(
<func>
<name name="statistics" arity="1" clause_i="9"
- anchor="statistics_run_queue"/>
+ anchor="statistics_run_queue" since=""/>
<fsummary>Information about the run-queues.</fsummary>
<desc>
<p>Returns the total length of all normal run-queues. That is, the number
@@ -6682,7 +6712,7 @@ lists:map(
<func>
<name name="statistics" arity="1" clause_i="10"
- anchor="statistics_run_queue_lengths"/>
+ anchor="statistics_run_queue_lengths" since="OTP 18.3"/>
<fsummary>Information about the run-queue lengths.</fsummary>
<desc>
<p>Returns the same as
@@ -6697,7 +6727,7 @@ lists:map(
<func>
<name name="statistics" arity="1" clause_i="11"
- anchor="statistics_run_queue_lengths_all"/>
+ anchor="statistics_run_queue_lengths_all" since="OTP 20.0"/>
<fsummary>Information about the run-queue lengths.</fsummary>
<desc>
<p>Returns a list where each element represents the amount
@@ -6739,7 +6769,7 @@ lists:map(
</func>
<func>
- <name name="statistics" arity="1" clause_i="12"/>
+ <name name="statistics" arity="1" clause_i="12" since=""/>
<fsummary>Information about runtime.</fsummary>
<desc>
<p>Returns information about runtime, in milliseconds.</p>
@@ -6758,7 +6788,7 @@ lists:map(
<func>
<name name="statistics" arity="1" clause_i="13"
- anchor="statistics_scheduler_wall_time"/>
+ anchor="statistics_scheduler_wall_time" since="OTP R15B01"/>
<fsummary>Information about each schedulers work time.</fsummary>
<desc>
<p>Returns a list of tuples with
@@ -6882,7 +6912,7 @@ ok
<func>
<name name="statistics" arity="1" clause_i="14"
- anchor="statistics_scheduler_wall_time_all"/>
+ anchor="statistics_scheduler_wall_time_all" since="OTP 20.0"/>
<fsummary>Information about each schedulers work time.</fsummary>
<desc>
<p>The same as
@@ -6910,7 +6940,7 @@ ok
</func>
<func>
<name name="statistics" arity="1" clause_i="15"
- anchor="statistics_total_active_tasks"/>
+ anchor="statistics_total_active_tasks" since="OTP 18.3"/>
<fsummary>Information about active processes and ports.</fsummary>
<desc>
<p>The same as calling
@@ -6921,7 +6951,7 @@ ok
<func>
<name name="statistics" arity="1" clause_i="16"
- anchor="statistics_total_active_tasks_all"/>
+ anchor="statistics_total_active_tasks_all" since="OTP 20.0"/>
<fsummary>Information about active processes and ports.</fsummary>
<desc>
<p>The same as calling
@@ -6932,7 +6962,7 @@ ok
<func>
<name name="statistics" arity="1" clause_i="17"
- anchor="statistics_total_run_queue_lengths"/>
+ anchor="statistics_total_run_queue_lengths" since="OTP 18.3"/>
<fsummary>Information about the run-queue lengths.</fsummary>
<desc>
<p>The same as calling
@@ -6943,7 +6973,7 @@ ok
<func>
<name name="statistics" arity="1" clause_i="18"
- anchor="statistics_total_run_queue_lengths_all"/>
+ anchor="statistics_total_run_queue_lengths_all" since="OTP 20.0"/>
<fsummary>Information about the run-queue lengths.</fsummary>
<desc>
<p>The same as calling
@@ -6953,7 +6983,7 @@ ok
</func>
<func>
- <name name="statistics" arity="1" clause_i="19"/>
+ <name name="statistics" arity="1" clause_i="19" since=""/>
<fsummary>Information about wall clock.</fsummary>
<desc>
<p>Returns information about wall clock. <c>wall_clock</c> can
@@ -6964,7 +6994,7 @@ ok
</func>
<func>
- <name name="suspend_process" arity="1"/>
+ <name name="suspend_process" arity="1" since=""/>
<fsummary>Suspend a process.</fsummary>
<desc>
<p>Suspends the process identified by
@@ -6979,7 +7009,7 @@ ok
</func>
<func>
- <name name="suspend_process" arity="2"/>
+ <name name="suspend_process" arity="2" since=""/>
<fsummary>Suspend a process.</fsummary>
<desc>
<p>Increases the suspend count on the process identified by
@@ -7125,7 +7155,7 @@ ok
</func>
<func>
- <name name="system_flag" arity="2" clause_i="1"/>
+ <name name="system_flag" arity="2" clause_i="1" since=""/>
<fsummary>Set system flag <c>backtrace_depth</c>.</fsummary>
<desc>
<p>Sets the maximum depth of call stack back-traces in the
@@ -7138,7 +7168,7 @@ ok
<func>
<name name="system_flag" arity="2" clause_i="2"
- anchor="system_flag_cpu_topology"/>
+ anchor="system_flag_cpu_topology" since=""/>
<fsummary>Set system flag <c>cpu_topology</c>.</fsummary>
<type name="cpu_topology"/>
<type name="level_entry"/>
@@ -7186,7 +7216,7 @@ ok
<func>
<name name="system_flag" arity="2" clause_i="3"
- anchor="system_flag_dirty_cpu_schedulers_online"/>
+ anchor="system_flag_dirty_cpu_schedulers_online" since="OTP 17.0"/>
<fsummary>Set system_flag_dirty_cpu_schedulers_online.</fsummary>
<desc>
<p>
@@ -7214,7 +7244,7 @@ ok
</func>
<func>
- <name name="system_flag" arity="2" clause_i="4"/>
+ <name name="system_flag" arity="2" clause_i="4" since="OTP 20.2.3"/>
<fsummary>Set system flag for erts_alloc.</fsummary>
<desc>
<p>Sets system flags for
@@ -7231,7 +7261,7 @@ ok
</func>
<func>
- <name name="system_flag" arity="2" clause_i="5"/>
+ <name name="system_flag" arity="2" clause_i="5" since=""/>
<fsummary>Set system flag fullsweep_after.</fsummary>
<desc>
<p>Sets system flag <c>fullsweep_after</c>.
@@ -7251,7 +7281,7 @@ ok
<func>
<name name="system_flag" arity="2" clause_i="6"
- anchor="system_flag_microstate_accounting"/>
+ anchor="system_flag_microstate_accounting" since="OTP 19.0"/>
<fsummary>Set system flag microstate_accounting.</fsummary>
<desc>
<p>
@@ -7264,7 +7294,7 @@ ok
</func>
<func>
- <name name="system_flag" arity="2" clause_i="7"/>
+ <name name="system_flag" arity="2" clause_i="7" since=""/>
<fsummary>Set system flag min_heap_size.</fsummary>
<desc>
<p>Sets the default minimum heap size for processes. The size
@@ -7279,7 +7309,7 @@ ok
</func>
<func>
- <name name="system_flag" arity="2" clause_i="8"/>
+ <name name="system_flag" arity="2" clause_i="8" since="OTP R13B04"/>
<fsummary>Set system flag min_bin_vheap_size.</fsummary>
<desc>
<p>Sets the default minimum binary virtual heap size for
@@ -7297,7 +7327,7 @@ ok
<func>
<name name="system_flag" arity="2" clause_i="9"
- anchor="system_flag_max_heap_size"/>
+ anchor="system_flag_max_heap_size" since="OTP 19.0"/>
<fsummary>Set system flag max_heap_size.</fsummary>
<type name="max_heap_size"/>
<desc>
@@ -7315,7 +7345,7 @@ ok
<func>
<name name="system_flag" arity="2" clause_i="10"
- anchor="system_flag_multi_scheduling"/>
+ anchor="system_flag_multi_scheduling" since=""/>
<fsummary>Set system flag multi_scheduling.</fsummary>
<desc>
<p>
@@ -7371,7 +7401,7 @@ ok
<func>
<name name="system_flag" arity="2" clause_i="11"
- anchor="system_flag_scheduler_bind_type"/>
+ anchor="system_flag_scheduler_bind_type" since=""/>
<fsummary>Set system flag scheduler_bind_type.</fsummary>
<type name="scheduler_bind_type"/>
<desc>
@@ -7498,7 +7528,7 @@ ok
<func>
<name name="system_flag" arity="2" clause_i="12"
- anchor="system_flag_scheduler_wall_time"/>
+ anchor="system_flag_scheduler_wall_time" since="OTP R15B01"/>
<fsummary>Set system flag scheduler_wall_time.</fsummary>
<desc>
<p>
@@ -7511,7 +7541,7 @@ ok
<func>
<name name="system_flag" arity="2" clause_i="13"
- anchor="system_flag_schedulers_online"/>
+ anchor="system_flag_schedulers_online" since=""/>
<fsummary>Set system flag schedulers_online.</fsummary>
<desc>
<p>
@@ -7539,7 +7569,39 @@ ok
</func>
<func>
- <name name="system_flag" arity="2" clause_i="14"/>
+ <name name="system_flag" arity="2" clause_i="14" since="OTP 21.3"/>
+ <fsummary>Set system logger process.</fsummary>
+ <desc>
+ <p>Sets the process that will receive the logging
+ messages generated by ERTS. If set to <c>undefined</c>,
+ all logging messages generated by ERTS will be dropped.
+ The messages will be in the format:</p>
+ <code>
+{log,Level,Format,ArgList,Metadata} where
+
+Level = atom(),
+Format = string(),
+ArgList = list(term()),
+Metadata = #{ pid => pid(),
+ group_leader => pid(),
+ time := logger:timestamp(),
+ error_logger := #{ emulator := true, tag := atom() }
+ </code>
+ <p>If the <c>system_logger</c> process dies,
+ this flag will be reset to <c>logger</c>.</p>
+ <p>The default is the process named <c>logger</c>.</p>
+ <p>Returns the old value of the flag.</p>
+ <note><p>This function is designed to be used by the
+ KERNEL <seealso marker="kernel:logger"><c>logger</c></seealso>.
+ Be careful if you change it to something else as
+ log messages may be lost. If you want to intercept
+ emulator log messages, do it by adding a specialized handler
+ to the KERNEL logger.</p></note>
+ </desc>
+ </func>
+
+ <func>
+ <name name="system_flag" arity="2" clause_i="15" since=""/>
<fsummary>Set system flag trace_control_word.</fsummary>
<desc>
<p>Sets the value of the node trace control word to
@@ -7553,8 +7615,8 @@ ok
</func>
<func>
- <name name="system_flag" arity="2" clause_i="15"
- anchor="system_flag_time_offset"/>
+ <name name="system_flag" arity="2" clause_i="16"
+ anchor="system_flag_time_offset" since="OTP 18.0"/>
<fsummary>Finalize the time offset.</fsummary>
<desc>
<p>
@@ -7583,7 +7645,7 @@ ok
</func>
<func>
- <name name="system_info" arity="1" clause_i="76"/>
+ <name name="system_info" arity="1" clause_i="76" since=""/>
<fsummary>System info overview.</fsummary>
<desc>
<p>Returns information about the current system.
@@ -7703,8 +7765,9 @@ ok
<seealso marker="#system_info_nif_version"><c>nif_version</c></seealso>,
<seealso marker="#system_info_otp_release"><c>otp_release</c></seealso>,
<seealso marker="#system_info_port_parallelism"><c>port_parallelism</c></seealso>,
- <seealso marker="#system_info_system_version"><c>system_version</c></seealso>,
<seealso marker="#system_info_system_architecture"><c>system_architecture</c></seealso>,
+ <seealso marker="#system_info_system_logger"><c>system_logger</c></seealso>,
+ <seealso marker="#system_info_system_version"><c>system_version</c></seealso>,
<seealso marker="#system_info_trace_control_word"><c>trace_control_word</c></seealso>,
<seealso marker="#system_info_version"><c>version</c></seealso>,
<seealso marker="#system_info_wordsize"><c>wordsize</c></seealso>
@@ -7716,12 +7779,12 @@ ok
<func>
<name name="system_info" arity="1" clause_i="1"
- anchor="system_info_allocator"/> <!-- allocated_areas -->
- <name name="system_info" arity="1" clause_i="2"/> <!-- allocator -->
- <name name="system_info" arity="1" clause_i="3"/> <!-- {allocator, _} -->
- <name name="system_info" arity="1" clause_i="4"/> <!-- alloc_util_allocators -->
- <name name="system_info" arity="1" clause_i="5"/> <!-- {allocator_sizes, _} -->
- <name name="system_info" arity="1" clause_i="27"/> <!-- elib_malloc -->
+ anchor="system_info_allocator" since=""/> <!-- allocated_areas -->
+ <name name="system_info" arity="1" clause_i="2" since=""/> <!-- allocator -->
+ <name name="system_info" arity="1" clause_i="3" since=""/> <!-- {allocator, _} -->
+ <name name="system_info" arity="1" clause_i="4" since=""/> <!-- alloc_util_allocators -->
+ <name name="system_info" arity="1" clause_i="5" since=""/> <!-- {allocator_sizes, _} -->
+ <name name="system_info" arity="1" clause_i="27" since=""/> <!-- elib_malloc -->
<fsummary>Information about the system allocators.</fsummary>
<type variable="Allocator" name_i="2"/>
<type variable="Version" name_i="2"/>
@@ -7873,10 +7936,10 @@ ok
<func>
<name name="system_info" arity="1" clause_i="12"
- anchor="system_info_cpu_topology"/> <!-- cpu_topology -->
- <name name="system_info" arity="1" clause_i="13"/> <!-- {cpu_topology, _} -->
- <name name="system_info" arity="1" clause_i="38"/> <!-- logical_processors -->
- <name name="system_info" arity="1" clause_i="73"/> <!-- update_cpu_info -->
+ anchor="system_info_cpu_topology" since=""/> <!-- cpu_topology -->
+ <name name="system_info" arity="1" clause_i="13" since=""/> <!-- {cpu_topology, _} -->
+ <name name="system_info" arity="1" clause_i="38" since=""/> <!-- logical_processors -->
+ <name name="system_info" arity="1" clause_i="74" since="OTP R14B"/> <!-- update_cpu_info -->
<fsummary>Information about the CPU topology of the system.</fsummary>
<type name="cpu_topology"/>
<type name="level_entry"/>
@@ -8028,15 +8091,15 @@ ok
<func>
<name name="system_info" arity="1" clause_i="31"
- anchor="system_info_process"/> <!-- fullsweep_after -->
- <name name="system_info" arity="1" clause_i="32"/> <!-- garbage_collection -->
- <name name="system_info" arity="1" clause_i="33"/> <!-- heap_sizes -->
- <name name="system_info" arity="1" clause_i="34"/> <!-- heap_type -->
- <name name="system_info" arity="1" clause_i="40"/> <!-- max_heap_size -->
- <name name="system_info" arity="1" clause_i="41"/> <!-- message_queue_data -->
- <name name="system_info" arity="1" clause_i="42"/> <!-- min_heap_size -->
- <name name="system_info" arity="1" clause_i="43"/> <!-- min_bin_vheap_size -->
- <name name="system_info" arity="1" clause_i="57"/> <!-- procs -->
+ anchor="system_info_process" since=""/> <!-- fullsweep_after -->
+ <name name="system_info" arity="1" clause_i="32" since=""/> <!-- garbage_collection -->
+ <name name="system_info" arity="1" clause_i="33" since=""/> <!-- heap_sizes -->
+ <name name="system_info" arity="1" clause_i="34" since=""/> <!-- heap_type -->
+ <name name="system_info" arity="1" clause_i="40" since="OTP 19.0"/> <!-- max_heap_size -->
+ <name name="system_info" arity="1" clause_i="41" since="OTP 19.0"/> <!-- message_queue_data -->
+ <name name="system_info" arity="1" clause_i="42" since="OTP R13B04"/> <!-- min_heap_size -->
+ <name name="system_info" arity="1" clause_i="43" since="OTP R13B04"/> <!-- min_bin_vheap_size -->
+ <name name="system_info" arity="1" clause_i="57" since=""/> <!-- procs -->
<fsummary>Information about the default process heap settings.</fsummary>
<type name="message_queue_data"/>
<type name="max_heap_size"/>
@@ -8146,14 +8209,14 @@ ok
</func>
<func>
- <name name="system_info" arity="1" clause_i="6" anchor="system_info_limits"/> <!-- atom_count -->
- <name name="system_info" arity="1" clause_i="7"/> <!-- atom_limit -->
- <name name="system_info" arity="1" clause_i="29"/> <!-- ets_count -->
- <name name="system_info" arity="1" clause_i="30"/> <!-- ets_limit -->
- <name name="system_info" arity="1" clause_i="53"/> <!-- port_count -->
- <name name="system_info" arity="1" clause_i="54"/> <!-- port_limit -->
- <name name="system_info" arity="1" clause_i="55"/> <!-- process_count -->
- <name name="system_info" arity="1" clause_i="56"/> <!-- process_limit -->
+ <name name="system_info" arity="1" clause_i="6" anchor="system_info_limits" since="OTP 20.0"/> <!-- atom_count -->
+ <name name="system_info" arity="1" clause_i="7" since="OTP 20.0"/> <!-- atom_limit -->
+ <name name="system_info" arity="1" clause_i="29" since="OTP 21.1"/> <!-- ets_count -->
+ <name name="system_info" arity="1" clause_i="30" since="OTP R16B03"/> <!-- ets_limit -->
+ <name name="system_info" arity="1" clause_i="53" since="OTP R16B"/> <!-- port_count -->
+ <name name="system_info" arity="1" clause_i="54" since="OTP R16B"/> <!-- port_limit -->
+ <name name="system_info" arity="1" clause_i="55" since=""/> <!-- process_count -->
+ <name name="system_info" arity="1" clause_i="56" since=""/> <!-- process_limit -->
<fsummary>Information about various system limits.</fsummary>
<desc>
<marker id="system_info_limits"/>
@@ -8227,14 +8290,14 @@ ok
<func>
<name name="system_info" arity="1" clause_i="26"
- anchor="system_info_time"/> <!-- end_time -->
- <name name="system_info" arity="1" clause_i="50"/> <!-- os_monotonic_time_source -->
- <name name="system_info" arity="1" clause_i="51"/> <!-- os_system_time_source -->
- <name name="system_info" arity="1" clause_i="63"/> <!-- start_time -->
- <name name="system_info" arity="1" clause_i="68"/> <!-- time_correction -->
- <name name="system_info" arity="1" clause_i="69"/> <!-- time_offset -->
- <name name="system_info" arity="1" clause_i="70"/> <!-- time_warp_mode -->
- <name name="system_info" arity="1" clause_i="71"/> <!-- tolerant_timeofday -->
+ anchor="system_info_time" since="OTP 18.0"/> <!-- end_time -->
+ <name name="system_info" arity="1" clause_i="50" since="OTP 18.0"/> <!-- os_monotonic_time_source -->
+ <name name="system_info" arity="1" clause_i="51" since="OTP 18.0"/> <!-- os_system_time_source -->
+ <name name="system_info" arity="1" clause_i="63" since="OTP 18.0"/> <!-- start_time -->
+ <name name="system_info" arity="1" clause_i="69" since="OTP 18.0"/> <!-- time_correction -->
+ <name name="system_info" arity="1" clause_i="70" since="OTP 18.0"/> <!-- time_offset -->
+ <name name="system_info" arity="1" clause_i="71" since="OTP 18.0"/> <!-- time_warp_mode -->
+ <name name="system_info" arity="1" clause_i="72" since="OTP 17.1"/> <!-- tolerant_timeofday -->
<fsummary>Information about system time.</fsummary>
<desc>
<marker id="system_info_time_tags"/>
@@ -8324,7 +8387,7 @@ ok
system time</seealso> that is used by the runtime system.</p>
<p>The list contains two-tuples with <c>Key</c>s
as first element, and <c>Value</c>s as second element. The
- order if these tuples is undefined. The following
+ order of these tuples is undefined. The following
tuples can be part of the list, but more tuples can be
introduced in the future:</p>
<taglist>
@@ -8455,19 +8518,19 @@ ok
<func>
<name name="system_info" arity="1" clause_i="17"
- anchor="system_info_scheduler"/> <!-- dirty_cpu_schedulers -->
- <name name="system_info" arity="1" clause_i="18"/> <!-- dirty_cpu_schedulers_online -->
- <name name="system_info" arity="1" clause_i="19"/> <!-- dirty_io_schedulers -->
- <name name="system_info" arity="1" clause_i="45"/> <!-- multi_scheduling -->
- <name name="system_info" arity="1" clause_i="46"/> <!-- multi_scheduling_blockers -->
- <name name="system_info" arity="1" clause_i="49"/> <!-- normal_multi_scheduling_blockers -->
- <name name="system_info" arity="1" clause_i="58"/> <!-- scheduler_bind_type -->
- <name name="system_info" arity="1" clause_i="59"/> <!-- scheduler_bindings -->
- <name name="system_info" arity="1" clause_i="60"/> <!-- scheduler_id -->
- <name name="system_info" arity="1" clause_i="61"/> <!-- schedulers -->
- <name name="system_info" arity="1" clause_i="62"/> <!-- smp_support -->
- <name name="system_info" arity="1" clause_i="66"/> <!-- threads -->
- <name name="system_info" arity="1" clause_i="67"/> <!-- thread_pool_size -->
+ anchor="system_info_scheduler" since="OTP 17.0"/> <!-- dirty_cpu_schedulers -->
+ <name name="system_info" arity="1" clause_i="18" since="OTP 17.0"/> <!-- dirty_cpu_schedulers_online -->
+ <name name="system_info" arity="1" clause_i="19" since="OTP 17.0"/> <!-- dirty_io_schedulers -->
+ <name name="system_info" arity="1" clause_i="45" since=""/> <!-- multi_scheduling -->
+ <name name="system_info" arity="1" clause_i="46" since=""/> <!-- multi_scheduling_blockers -->
+ <name name="system_info" arity="1" clause_i="49" since="OTP 19.0"/> <!-- normal_multi_scheduling_blockers -->
+ <name name="system_info" arity="1" clause_i="58" since=""/> <!-- scheduler_bind_type -->
+ <name name="system_info" arity="1" clause_i="59" since=""/> <!-- scheduler_bindings -->
+ <name name="system_info" arity="1" clause_i="60" since=""/> <!-- scheduler_id -->
+ <name name="system_info" arity="1" clause_i="61" since=""/> <!-- schedulers -->
+ <name name="system_info" arity="1" clause_i="62" since=""/> <!-- smp_support -->
+ <name name="system_info" arity="1" clause_i="67" since=""/> <!-- threads -->
+ <name name="system_info" arity="1" clause_i="68" since=""/> <!-- thread_pool_size -->
<fsummary>Information about system schedulers.</fsummary>
<desc>
<marker id="system_info_scheduler_tags"/>
@@ -8755,11 +8818,11 @@ ok
<func>
<name name="system_info" arity="1" clause_i="14"
- anchor="system_info_dist"/> <!-- creation -->
- <name name="system_info" arity="1" clause_i="16"/> <!-- delayed_node_table_gc -->
- <name name="system_info" arity="1" clause_i="20"/> <!-- dist -->
- <name name="system_info" arity="1" clause_i="21"/> <!-- dist_buf_busy_limit -->
- <name name="system_info" arity="1" clause_i="22"/> <!-- dist_ctrl -->
+ anchor="system_info_dist" since=""/> <!-- creation -->
+ <name name="system_info" arity="1" clause_i="16" since="OTP 18.0"/> <!-- delayed_node_table_gc -->
+ <name name="system_info" arity="1" clause_i="20" since=""/> <!-- dist -->
+ <name name="system_info" arity="1" clause_i="21" since="OTP R14B01"/> <!-- dist_buf_busy_limit -->
+ <name name="system_info" arity="1" clause_i="22" since=""/> <!-- dist_ctrl -->
<fsummary>Information about erlang distribution.</fsummary>
<desc>
<marker id="system_info_dist_tags"/>
@@ -8833,14 +8896,14 @@ ok
<!-- <name name="system_info" arity="1" clause_i="6"/> atom_count -->
<!-- <name name="system_info" arity="1" clause_i="7"/> atom_limit -->
<name name="system_info" arity="1" clause_i="8"
- anchor="system_info_misc"/> <!-- build_type -->
- <name name="system_info" arity="1" clause_i="9"/> <!-- c_compiler_used -->
- <name name="system_info" arity="1" clause_i="10"/> <!-- check_io -->
- <name name="system_info" arity="1" clause_i="11"/> <!-- compat_rel -->
+ anchor="system_info_misc" since="OTP R14B"/> <!-- build_type -->
+ <name name="system_info" arity="1" clause_i="9" since=""/> <!-- c_compiler_used -->
+ <name name="system_info" arity="1" clause_i="10" since=""/> <!-- check_io -->
+ <name name="system_info" arity="1" clause_i="11" since=""/> <!-- compat_rel -->
<!-- <name name="system_info" arity="1" clause_i="12"/> cpu_topology -->
<!-- <name name="system_info" arity="1" clause_i="13"/> {cpu_topology, _} -->
<!-- <name name="system_info" arity="1" clause_i="14"/> creation -->
- <name name="system_info" arity="1" clause_i="15"/> <!-- debug_compiled -->
+ <name name="system_info" arity="1" clause_i="15" since=""/> <!-- debug_compiled -->
<!-- <name name="system_info" arity="1" clause_i="16"/> delayed_node_table_gc -->
<!-- <name name="system_info" arity="1" clause_i="17"/> dirty_cpu_schedulers -->
<!-- <name name="system_info" arity="1" clause_i="18"/> dirty_cpu_schedulers_online -->
@@ -8848,9 +8911,9 @@ ok
<!-- <name name="system_info" arity="1" clause_i="20"/> dist -->
<!-- <name name="system_info" arity="1" clause_i="21"/> dist_buf_busy_limit -->
<!-- <name name="system_info" arity="1" clause_i="22"/> dist_ctrl -->
- <name name="system_info" arity="1" clause_i="23"/> <!-- driver_version -->
- <name name="system_info" arity="1" clause_i="24"/> <!-- dynamic_trace -->
- <name name="system_info" arity="1" clause_i="25"/> <!-- dynamic_trace_probes -->
+ <name name="system_info" arity="1" clause_i="23" since=""/> <!-- driver_version -->
+ <name name="system_info" arity="1" clause_i="24" since="OTP R15B01"/> <!-- dynamic_trace -->
+ <name name="system_info" arity="1" clause_i="25" since="OTP R15B01"/> <!-- dynamic_trace_probes -->
<!-- <name name="system_info" arity="1" clause_i="26"/> end_time -->
<!-- <name name="system_info" arity="1" clause_i="27"/> elib_malloc -->
<!-- <name name="system_info" arity="1" clause_i="28"/> eager_check_io, removed -->
@@ -8860,24 +8923,24 @@ ok
<!-- <name name="system_info" arity="1" clause_i="32"/> garbage_collection -->
<!-- <name name="system_info" arity="1" clause_i="33"/> heap_sizes -->
<!-- <name name="system_info" arity="1" clause_i="34"/> heap_type -->
- <name name="system_info" arity="1" clause_i="35"/> <!-- info -->
- <name name="system_info" arity="1" clause_i="36"/> <!-- kernel_poll -->
- <name name="system_info" arity="1" clause_i="37"/> <!-- loaded -->
+ <name name="system_info" arity="1" clause_i="35" since=""/> <!-- info -->
+ <name name="system_info" arity="1" clause_i="36" since=""/> <!-- kernel_poll -->
+ <name name="system_info" arity="1" clause_i="37" since=""/> <!-- loaded -->
<!-- <name name="system_info" arity="1" clause_i="38"/> logical_processors -->
- <name name="system_info" arity="1" clause_i="39"/> <!-- machine -->
+ <name name="system_info" arity="1" clause_i="39" since=""/> <!-- machine -->
<!-- <name name="system_info" arity="1" clause_i="40"/> max_heap_size -->
<!-- <name name="system_info" arity="1" clause_i="41"/> message_queue_data -->
<!-- <name name="system_info" arity="1" clause_i="42"/> min_heap_size -->
<!-- <name name="system_info" arity="1" clause_i="43"/> min_bin_vheap_size -->
- <name name="system_info" arity="1" clause_i="44"/> <!-- modified_timing_level -->
+ <name name="system_info" arity="1" clause_i="44" since=""/> <!-- modified_timing_level -->
<!-- <name name="system_info" arity="1" clause_i="45"/> multi_scheduling -->
<!-- <name name="system_info" arity="1" clause_i="46"/> multi_scheduling_blockers -->
- <name name="system_info" arity="1" clause_i="47"/> <!-- nif_version -->
+ <name name="system_info" arity="1" clause_i="47" since="OTP 17.4"/> <!-- nif_version -->
<!-- n<name name="system_info" arity="1" clause_i="48"/> ormal_multi_scheduling_blockers -->
- <name name="system_info" arity="1" clause_i="49"/> <!-- otp_release -->
+ <name name="system_info" arity="1" clause_i="49" since=""/> <!-- otp_release -->
<!-- <name name="system_info" arity="1" clause_i="50"/> os_monotonic_time_source -->
<!-- <name name="system_info" arity="1" clause_i="51"/> os_system_time_source -->
- <name name="system_info" arity="1" clause_i="52"/> <!-- port_parallelism -->
+ <name name="system_info" arity="1" clause_i="52" since="OTP R16B"/> <!-- port_parallelism -->
<!-- <name name="system_info" arity="1" clause_i="53"/> port_count -->
<!-- <name name="system_info" arity="1" clause_i="54"/> port_limit -->
<!-- <name name="system_info" arity="1" clause_i="55"/> process_count -->
@@ -8889,19 +8952,20 @@ ok
<!-- <name name="system_info" arity="1" clause_i="61"/> schedulers -->
<!-- <name name="system_info" arity="1" clause_i="62"/> smp_support -->
<!-- <name name="system_info" arity="1" clause_i="63"/> start_time -->
- <name name="system_info" arity="1" clause_i="64"/> <!-- system_version -->
- <name name="system_info" arity="1" clause_i="65"/> <!-- system_architecture -->
- <!-- <name name="system_info" arity="1" clause_i="66"/> threads -->
- <!-- <name name="system_info" arity="1" clause_i="67"/> thread_pool_size -->
- <!-- <name name="system_info" arity="1" clause_i="68"/> time_correction -->
- <!-- <name name="system_info" arity="1" clause_i="69"/> time_offset -->
- <!-- <name name="system_info" arity="1" clause_i="70"/> time_warp_mode -->
- <!-- <name name="system_info" arity="1" clause_i="71"/> tolerant_timeofday -->
- <name name="system_info" arity="1" clause_i="72"/> <!-- trace_control_word -->
- <!-- <name name="system_info" arity="1" clause_i="73"/> update_cpu_info -->
- <name name="system_info" arity="1" clause_i="74"/> <!-- version -->
- <name name="system_info" arity="1" clause_i="75"/> <!-- wordsize -->
- <!-- <name name="system_info" arity="1" clause_i="76"/> overview -->
+ <name name="system_info" arity="1" clause_i="64" since=""/> <!-- system_architecture -->
+ <name name="system_info" arity="1" clause_i="65" since="OTP 21.3"/> <!-- system_logger -->
+ <name name="system_info" arity="1" clause_i="66" since=""/> <!-- system_version -->
+ <!-- <name name="system_info" arity="1" clause_i="67"/> threads -->
+ <!-- <name name="system_info" arity="1" clause_i="68"/> thread_pool_size -->
+ <!-- <name name="system_info" arity="1" clause_i="69"/> time_correction -->
+ <!-- <name name="system_info" arity="1" clause_i="70"/> time_offset -->
+ <!-- <name name="system_info" arity="1" clause_i="71"/> time_warp_mode -->
+ <!-- <name name="system_info" arity="1" clause_i="72"/> tolerant_timeofday -->
+ <name name="system_info" arity="1" clause_i="73" since=""/> <!-- trace_control_word -->
+ <!-- <name name="system_info" arity="1" clause_i="74"/> update_cpu_info -->
+ <name name="system_info" arity="1" clause_i="75" since=""/> <!-- version -->
+ <name name="system_info" arity="1" clause_i="76" since=""/> <!-- wordsize -->
+ <!-- <name name="system_info" arity="1" clause_i="77"/> overview -->
<fsummary>Information about the system.</fsummary>
<desc>
<marker id="system_info_misc_tags"/>
@@ -9057,18 +9121,24 @@ ok
<seealso marker="erl#+spp"><c>+spp</c></seealso>
in <c>erl(1)</c>.</p>
</item>
- <tag><marker id="system_info_system_version"/>
- <c>system_version</c></tag>
- <item>
- <p>Returns a string containing version number and
- some important properties, such as the number of schedulers.</p>
- </item>
<tag><marker id="system_info_system_architecture"/>
<c>system_architecture</c></tag>
<item>
<p>Returns a string containing the processor and OS
architecture the emulator is built for.</p>
</item>
+ <tag><marker id="system_info_system_logger"/>
+ <c>system_logger</c></tag>
+ <item>
+ <p>Returns the current <c>system_logger</c> as set by
+ <seealso marker="#system_flag/2"><c>erlang:system_flag(system_logger, _)</c></seealso>.</p>
+ </item>
+ <tag><marker id="system_info_system_version"/>
+ <c>system_version</c></tag>
+ <item>
+ <p>Returns a string containing version number and
+ some important properties, such as the number of schedulers.</p>
+ </item>
<tag><marker id="system_info_trace_control_word"/>
<c>trace_control_word</c></tag>
<item>
@@ -9110,7 +9180,7 @@ ok
</func>
<func>
- <name name="system_monitor" arity="0"/>
+ <name name="system_monitor" arity="0" since=""/>
<fsummary>Current system performance monitoring settings.</fsummary>
<type name="system_monitor_option"/>
<desc>
@@ -9124,7 +9194,7 @@ ok
</func>
<func>
- <name name="system_monitor" arity="1"/>
+ <name name="system_monitor" arity="1" since=""/>
<fsummary>Set or clear system performance monitoring options.</fsummary>
<type name="system_monitor_option"/>
<desc>
@@ -9142,7 +9212,7 @@ ok
</func>
<func>
- <name name="system_monitor" arity="2"/>
+ <name name="system_monitor" arity="2" since=""/>
<fsummary>Set system performance monitoring options.</fsummary>
<type name="system_monitor_option"/>
<desc>
@@ -9274,7 +9344,7 @@ ok
</func>
<func>
- <name name="system_profile" arity="0"/>
+ <name name="system_profile" arity="0" since=""/>
<fsummary>Current system profiling settings.</fsummary>
<type name="system_profile_option"/>
<desc>
@@ -9289,7 +9359,7 @@ ok
</func>
<func>
- <name name="system_profile" arity="2"/>
+ <name name="system_profile" arity="2" since=""/>
<fsummary>Current system profiling settings.</fsummary>
<type name="system_profile_option"/>
<desc>
@@ -9363,7 +9433,7 @@ ok
</func>
<func>
- <name name="system_time" arity="0"/>
+ <name name="system_time" arity="0" since="OTP 18.0"/>
<fsummary>Current Erlang system time.</fsummary>
<desc>
<p>Returns current
@@ -9385,7 +9455,7 @@ ok
</func>
<func>
- <name name="system_time" arity="1"/>
+ <name name="system_time" arity="1" since="OTP 18.0"/>
<fsummary>Current Erlang system time.</fsummary>
<desc>
<p>Returns current
@@ -9407,7 +9477,7 @@ ok
</func>
<func>
- <name name="term_to_binary" arity="1"/>
+ <name name="term_to_binary" arity="1" since=""/>
<fsummary>Encode a term to an Erlang external term format binary.
</fsummary>
<desc>
@@ -9435,7 +9505,7 @@ hello
</func>
<func>
- <name name="term_to_binary" arity="2"/>
+ <name name="term_to_binary" arity="2" since=""/>
<fsummary>Encode a term to en Erlang external term format binary.
</fsummary>
<desc>
@@ -9500,7 +9570,7 @@ hello
</func>
<func>
- <name name="throw" arity="1"/>
+ <name name="throw" arity="1" since=""/>
<fsummary>Throw an exception.</fsummary>
<desc>
<p>A non-local return from a function. If evaluated within a
@@ -9514,7 +9584,7 @@ hello
</func>
<func>
- <name name="time" arity="0"/>
+ <name name="time" arity="0" since=""/>
<fsummary>Current time.</fsummary>
<desc>
<p>Returns the current time as <c>{Hour, Minute, Second}</c>.</p>
@@ -9527,7 +9597,7 @@ hello
</func>
<func>
- <name name="time_offset" arity="0"/>
+ <name name="time_offset" arity="0" since="OTP 18.0"/>
<fsummary>Current time offset.</fsummary>
<desc>
<p>Returns the current time offset between
@@ -9559,7 +9629,7 @@ hello
</func>
<func>
- <name name="time_offset" arity="1"/>
+ <name name="time_offset" arity="1" since="OTP 18.0"/>
<fsummary>Current time offset.</fsummary>
<desc>
<p>Returns the current time offset between
@@ -9578,7 +9648,7 @@ hello
</func>
<func>
- <name name="timestamp" arity="0"/>
+ <name name="timestamp" arity="0" since="OTP 18.0"/>
<fsummary>Current Erlang System time.</fsummary>
<type name="timestamp"/>
<desc>
@@ -9617,7 +9687,7 @@ timestamp() ->
</func>
<func>
- <name name="tl" arity="1"/>
+ <name name="tl" arity="1" since=""/>
<fsummary>Tail of a list.</fsummary>
<desc>
<p>Returns the tail of <c><anno>List</anno></c>, that is,
@@ -9632,7 +9702,7 @@ timestamp() ->
</func>
<func>
- <name name="trace" arity="3"/>
+ <name name="trace" arity="3" since=""/>
<fsummary>Set trace flags for a process or processes.</fsummary>
<type name="trace_flag"/>
<desc>
@@ -10288,7 +10358,7 @@ timestamp() ->
</func>
<func>
- <name name="trace_delivered" arity="1"/>
+ <name name="trace_delivered" arity="1" since=""/>
<fsummary>Notification when trace has been delivered.</fsummary>
<desc>
<p>The delivery of trace messages (generated by
@@ -10343,7 +10413,7 @@ timestamp() ->
</func>
<func>
- <name name="trace_info" arity="2"/>
+ <name name="trace_info" arity="2" since=""/>
<fsummary>Trace information about a process or function.</fsummary>
<type name="trace_info_return"/>
<type name="trace_info_item_result"/>
@@ -10479,7 +10549,7 @@ timestamp() ->
</func>
<func>
- <name name="trace_pattern" arity="2" clause_i="1"/>
+ <name name="trace_pattern" arity="2" clause_i="1" since=""/>
<fsummary>Set trace patterns for call, send, or 'receive' tracing.
</fsummary>
<type name="trace_pattern_mfa"/>
@@ -10497,7 +10567,7 @@ timestamp() ->
</func>
<func>
- <name name="trace_pattern" arity="3" clause_i="1"/>
+ <name name="trace_pattern" arity="3" clause_i="1" since="OTP 19.0"/>
<fsummary>Set trace pattern for message sending.</fsummary>
<type name="trace_match_spec"/>
<type name="match_variable"/>
@@ -10568,7 +10638,7 @@ timestamp() ->
</func>
<func>
- <name name="trace_pattern" arity="3" clause_i="2"/>
+ <name name="trace_pattern" arity="3" clause_i="2" since="OTP 19.0"/>
<fsummary>Set trace pattern for tracing of message receiving.</fsummary>
<type name="trace_match_spec"/>
<type name="match_variable"/>
@@ -10640,7 +10710,7 @@ timestamp() ->
</func>
<func>
- <name name="trace_pattern" arity="3" clause_i="3"/>
+ <name name="trace_pattern" arity="3" clause_i="3" since=""/>
<fsummary>Set trace patterns for tracing of function calls.</fsummary>
<type name="trace_pattern_mfa"/>
<type name="trace_match_spec"/>
@@ -10831,7 +10901,7 @@ timestamp() ->
</func>
<func>
- <name name="trunc" arity="1"/>
+ <name name="trunc" arity="1" since=""/>
<fsummary>Return an integer by truncating a number.</fsummary>
<desc>
<p>Returns an integer by truncating <c><anno>Number</anno></c>,
@@ -10844,7 +10914,7 @@ timestamp() ->
</func>
<func>
- <name name="tuple_size" arity="1"/>
+ <name name="tuple_size" arity="1" since=""/>
<fsummary>Return the size of a tuple.</fsummary>
<desc>
<p>Returns an integer that is the number of elements in
@@ -10857,7 +10927,7 @@ timestamp() ->
</func>
<func>
- <name name="tuple_to_list" arity="1"/>
+ <name name="tuple_to_list" arity="1" since=""/>
<fsummary>Convert a tuple to a list.</fsummary>
<desc>
<p>Returns a list corresponding to <c><anno>Tuple</anno></c>.
@@ -10870,7 +10940,7 @@ timestamp() ->
</func>
<func>
- <name name="unique_integer" arity="0"/>
+ <name name="unique_integer" arity="0" since="OTP 18.0"/>
<fsummary>Get a unique integer value.</fsummary>
<desc>
<p>Generates and returns an
@@ -10883,7 +10953,7 @@ timestamp() ->
</func>
<func>
- <name name="unique_integer" arity="1"/>
+ <name name="unique_integer" arity="1" since="OTP 18.0"/>
<fsummary>Get a unique integer value.</fsummary>
<desc>
<p>Generates and returns an
@@ -10965,7 +11035,7 @@ timestamp() ->
</func>
<func>
- <name name="universaltime" arity="0"/>
+ <name name="universaltime" arity="0" since=""/>
<fsummary>Current date and time according to Universal Time Coordinated
(UTC).</fsummary>
<desc>
@@ -10982,7 +11052,7 @@ timestamp() ->
</func>
<func>
- <name name="universaltime_to_localtime" arity="1"/>
+ <name name="universaltime_to_localtime" arity="1" since=""/>
<fsummary>Convert from Universal Time Coordinated (UTC) to local date
and time.</fsummary>
<desc>
@@ -11001,7 +11071,7 @@ timestamp() ->
</func>
<func>
- <name name="unlink" arity="1"/>
+ <name name="unlink" arity="1" since=""/>
<fsummary>Remove a link to another process or port.</fsummary>
<desc>
<p>Removes the link, if there is one, between the calling
@@ -11047,7 +11117,7 @@ end</code>
</func>
<func>
- <name name="unregister" arity="1"/>
+ <name name="unregister" arity="1" since=""/>
<fsummary>Remove the registered name for a process (or port).</fsummary>
<desc>
<p>Removes the registered name <c><anno>RegName</anno></c>
@@ -11063,7 +11133,7 @@ true</pre>
</func>
<func>
- <name name="whereis" arity="1"/>
+ <name name="whereis" arity="1" since=""/>
<fsummary>Get the pid (or port) with a specified registered name.
</fsummary>
<desc>
@@ -11077,7 +11147,7 @@ true</pre>
</func>
<func>
- <name name="yield" arity="0"/>
+ <name name="yield" arity="0" since=""/>
<fsummary>Let other processes get a chance to execute.</fsummary>
<desc>
<p>Voluntarily lets other processes (if any) get a chance to
diff --git a/erts/doc/src/erts_alloc.xml b/erts/doc/src/erts_alloc.xml
index a094217959..962bc9a244 100644
--- a/erts/doc/src/erts_alloc.xml
+++ b/erts/doc/src/erts_alloc.xml
@@ -487,11 +487,10 @@
utilization value used. Once a carrier is abandoned, no new
allocations are made in it. When an allocator instance gets an
increased multiblock carrier need, it first tries to fetch an
- abandoned carrier from an allocator instance of the same
- allocator type. If no abandoned carrier can be fetched, it
- creates a new empty carrier. When an abandoned carrier has been
- fetched, it will function as an ordinary carrier. This feature has
- special requirements on the
+ abandoned carrier from another allocator instance. If no abandoned
+ carrier can be fetched, it creates a new empty carrier. When an
+ abandoned carrier has been fetched, it will function as an ordinary
+ carrier. This feature has special requirements on the
<seealso marker="#M_as">allocation strategy</seealso> used. Only
the strategies <c>aoff</c>, <c>aoffcbf</c>, <c>aoffcaobf</c>,
<c>ageffcaoff</c>m, <c>ageffcbf</c> and <c>ageffcaobf</c>
@@ -584,7 +583,7 @@
carriers are decided in section
<seealso marker="#mseg_mbc_sizes">
The alloc_util Framework</seealso>. On
- 32-bit Unix style OS this limit cannot be set &gt; 128 MB.</p>
+ 32-bit Unix style OS this limit cannot be set &gt; 64 MB.</p>
</item>
<tag><marker id="M_mbcgs"/><c><![CDATA[+M<S>mbcgs <ratio>]]></c></tag>
<item>
diff --git a/erts/doc/src/init.xml b/erts/doc/src/init.xml
index c14f0a558d..c824e37976 100644
--- a/erts/doc/src/init.xml
+++ b/erts/doc/src/init.xml
@@ -29,7 +29,7 @@
<rev></rev>
<file>init.xml</file>
</header>
- <module>init</module>
+ <module since="">init</module>
<modulesummary>Coordination of system startup.</modulesummary>
<description>
<p>This module is preloaded and contains the code for
@@ -50,7 +50,7 @@
<funcs>
<func>
- <name name="boot" arity="1"/>
+ <name name="boot" arity="1" since=""/>
<fsummary>Start the Erlang runtime system.</fsummary>
<desc>
<p>Starts the Erlang runtime system. This function is called
@@ -69,7 +69,7 @@
</func>
<func>
- <name name="get_argument" arity="1"/>
+ <name name="get_argument" arity="1" since=""/>
<fsummary>Get the values associated with a command-line user flag.
</fsummary>
<desc>
@@ -112,7 +112,7 @@
</func>
<func>
- <name name="get_arguments" arity="0"/>
+ <name name="get_arguments" arity="0" since=""/>
<fsummary>Get all command-line user flags.</fsummary>
<desc>
<p>Returns all command-line flags and the system-defined flags, see
@@ -121,7 +121,7 @@
</func>
<func>
- <name name="get_plain_arguments" arity="0"/>
+ <name name="get_plain_arguments" arity="0" since=""/>
<fsummary>Get all non-flag command-line arguments.</fsummary>
<desc>
<p>Returns any plain command-line arguments as a list of strings
@@ -130,7 +130,7 @@
</func>
<func>
- <name name="get_status" arity="0"/>
+ <name name="get_status" arity="0" since=""/>
<fsummary>Get system status information.</fsummary>
<type name="internal_status"/>
<desc>
@@ -146,7 +146,7 @@
</func>
<func>
- <name name="reboot" arity="0"/>
+ <name name="reboot" arity="0" since=""/>
<fsummary>Take down and restart an Erlang node smoothly.</fsummary>
<desc>
<p>All applications are taken down smoothly, all code is
@@ -162,7 +162,7 @@
</func>
<func>
- <name name="restart" arity="0"/>
+ <name name="restart" arity="0" since=""/>
<fsummary>Restart the running Erlang node.</fsummary>
<desc>
<p>The system is restarted <em>inside</em> the running Erlang
@@ -178,7 +178,7 @@
</func>
<func>
- <name name="script_id" arity="0"/>
+ <name name="script_id" arity="0" since=""/>
<fsummary>Get the identity of the used boot script.</fsummary>
<desc>
<p>Gets the identity of the boot script used to boot the system.
@@ -189,7 +189,7 @@
</func>
<func>
- <name name="stop" arity="0"/>
+ <name name="stop" arity="0" since=""/>
<fsummary>Take down an Erlang node smoothly.</fsummary>
<desc>
<p>The same as
@@ -198,7 +198,7 @@
</func>
<func>
- <name name="stop" arity="1"/>
+ <name name="stop" arity="1" since=""/>
<fsummary>Take down an Erlang node smoothly.</fsummary>
<desc>
<p>All applications are taken down smoothly, all code is
diff --git a/erts/doc/src/net.xml b/erts/doc/src/net.xml
new file mode 100644
index 0000000000..bd85594c98
--- /dev/null
+++ b/erts/doc/src/net.xml
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2018</year><year>2018</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>net</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ <file>net.xml</file>
+ </header>
+ <module since="OTP @OTP-14831@">net</module>
+ <modulesummary>Network interface.</modulesummary>
+ <description>
+ <p>This module provides an API for the network interface.</p>
+ </description>
+
+ <datatypes>
+ <datatype>
+ <name name="address_info"/>
+ </datatype>
+ <datatype>
+ <name name="name_info"/>
+ </datatype>
+ <datatype>
+ <name name="name_info_flags"/>
+ </datatype>
+ <datatype>
+ <name name="name_info_flag"/>
+ </datatype>
+ <datatype>
+ <name name="name_info_flag_ext"/>
+ </datatype>
+ <datatype>
+ <name name="network_interface_name"/>
+ </datatype>
+ <datatype>
+ <name name="network_interface_index"/>
+ </datatype>
+ </datatypes>
+
+ <funcs>
+ <func>
+ <name name="gethostname" arity="0"/>
+ <fsummary>Get hostname.</fsummary>
+ <desc>
+ <p>Returns the name of the current host.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="getnameinfo" arity="1" since="OTP @OTP-14831@"/>
+ <name name="getnameinfo" arity="2" since="OTP @OTP-14831@"/>
+ <fsummary>Address-to-name transaltion.</fsummary>
+ <desc>
+ <p>Address-to-name translation in a protocol-independant manner.</p>
+ <p>This function is the inverse of
+ <seealso marker="#getaddrinfo/1"><c>getaddrinfo</c></seealso>.
+ It converts a socket address to a corresponding host and service.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="getaddrinfo" arity="1" since="OTP @OTP-14831@"/>
+ <name name="getaddrinfo" arity="2" clause_i="1" since="OTP @OTP-14831@"/>
+ <name name="getaddrinfo" arity="2" clause_i="2" since="OTP @OTP-14831@"/>
+ <name name="getaddrinfo" arity="2" clause_i="3" since="OTP @OTP-14831@"/>
+ <fsummary>Network address and service transation.</fsummary>
+ <desc>
+ <p>Network address and service translation.</p>
+ <p>This function is the inverse of
+ <seealso marker="#getnameinfo/1"><c>getnameinfo</c></seealso>.
+ It converts host and service to a corresponding socket address.</p>
+ <p>One of the <c>Host</c> and <c>Service</c> may be <c>undefined</c>
+ but <em>not</em> both.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="if_name2index" arity="1" since="OTP @OTP-14831@"/>
+ <fsummary>Mappings between network interface names and indexes.</fsummary>
+ <desc>
+ <p>Mappings between network interface names and indexes.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="if_index2name" arity="1" since="OTP @OTP-14831@"/>
+ <fsummary>Mappings between network interface index and names.</fsummary>
+ <desc>
+ <p>Mappings between network interface index and names.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="if_names" arity="0" since="OTP @OTP-14831@"/>
+ <fsummary>Get network interface names and indexes.</fsummary>
+ <desc>
+ <p>Get network interface names and indexes.</p>
+ </desc>
+ </func>
+
+ </funcs>
+
+</erlref>
+
diff --git a/erts/doc/src/notes.xml b/erts/doc/src/notes.xml
index fca1bdd8cc..3473a12526 100644
--- a/erts/doc/src/notes.xml
+++ b/erts/doc/src/notes.xml
@@ -31,6 +31,24 @@
</header>
<p>This document describes the changes made to the ERTS application.</p>
+<section><title>Erts 10.2.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ When using the <c>{linger,{true,T}}</c> option;
+ <c>gen_tcp:listen/2</c> used the full linger time before
+ returning for example <c>eaddrinuse</c>. This bug has now
+ been corrected.</p>
+ <p>
+ Own Id: OTP-14728 Aux Id: ERIERL-303 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Erts 10.2.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -1694,6 +1712,62 @@
</section>
+<section><title>Erts 9.3.3.9</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>Added an optional <c>./configure</c> flag to compile
+ the emulator with spectre mitigation:
+ <c>--with-spectre-mitigation</c></p>
+ <p>Note that this requires a recent version of GCC with
+ support for spectre mitigation and the
+ <c>--mindirect-branch=thunk</c> flag, such as
+ <c>8.1</c>.</p>
+ <p>
+ Own Id: OTP-15430 Aux Id: ERIERL-237 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 9.3.3.8</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ A bug that could cause dirty schedulers to become
+ unresponsive has been fixed.</p>
+ <p>
+ Own Id: OTP-15509 Aux Id: PR-2027, PR-2093 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 9.3.3.7</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed bug in operator <c>band</c> of two negative
+ operands causing erroneous result if the absolute value
+ of one of the operands have the lowest <c>N*W</c> bits as
+ zero and the other absolute value is not larger than
+ <c>N*W</c> bits. <c>N</c> is an integer of 1 or larger
+ and <c>W</c> is 32 or 64 depending on word size.</p>
+ <p>
+ Own Id: OTP-15487 Aux Id: ERL-804 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Erts 9.3.3.6</title>
<section><title>Improvements and New Features</title>
@@ -2055,6 +2129,22 @@
</section>
+<section><title>Erts 9.2.0.1</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Extra internal consistency checks wrt communication with
+ erl_child_setup process.</p>
+ <p>
+ Own Id: OTP-15488 Aux Id: ERIERL-231 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Erts 9.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -3253,6 +3343,43 @@
</section>
+<section><title>Erts 8.3.5.7</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed bug in operator <c>band</c> of two negative
+ operands causing erroneous result if the absolute value
+ of one of the operands have the lowest <c>N*W</c> bits as
+ zero and the other absolute value is not larger than
+ <c>N*W</c> bits. <c>N</c> is an integer of 1 or larger
+ and <c>W</c> is 32 or 64 depending on word size.</p>
+ <p>
+ Own Id: OTP-15487 Aux Id: ERL-804 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>Added an optional <c>./configure</c> flag to compile
+ the emulator with spectre mitigation:
+ <c>--with-spectre-mitigation</c></p>
+ <p>Note that this requires a recent version of GCC with
+ support for spectre mitigation and the
+ <c>--mindirect-branch=thunk</c> flag, such as
+ <c>8.1</c>.</p>
+ <p>
+ Own Id: OTP-15430 Aux Id: ERIERL-237 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Erts 8.3.5.6</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -10864,7 +10991,7 @@
you use erlang:halt/2 with an integer first argument and
an option list containing {flush,false} as the second
argument. Note that now is flushing not dependant of the
- exit code, and you can not only flush async threads
+ exit code, and you cannot only flush async threads
operations which we deemed as a strange behaviour anyway.
</p>
<p>Also, erlang:halt/1,2 has gotten a new feature: If the
diff --git a/erts/doc/src/part.xml b/erts/doc/src/part.xml
index 05e9a24af8..f0b8a00b90 100644
--- a/erts/doc/src/part.xml
+++ b/erts/doc/src/part.xml
@@ -42,6 +42,7 @@
<xi:include href="tty.xml"/>
<xi:include href="driver.xml"/>
<xi:include href="inet_cfg.xml"/>
+ <xi:include href="socket_usage.xml"/>
<xi:include href="erl_ext_dist.xml"/>
<xi:include href="erl_dist_protocol.xml"/>
</part>
diff --git a/erts/doc/src/persistent_term.xml b/erts/doc/src/persistent_term.xml
index 29a6c67051..9d3c9afd80 100644
--- a/erts/doc/src/persistent_term.xml
+++ b/erts/doc/src/persistent_term.xml
@@ -29,7 +29,7 @@
<rev></rev>
<file>persistent_term.xml</file>
</header>
- <module>persistent_term</module>
+ <module since="OTP 21.2">persistent_term</module>
<modulesummary>Persistent terms.</modulesummary>
<description>
<p>This module is similar to <seealso
@@ -213,7 +213,7 @@ will be slower as the number of persistent terms increases.</pre>
<funcs>
<func>
- <name name="erase" arity="1"/>
+ <name name="erase" arity="1" since="OTP 21.2"/>
<fsummary>Erase the name for a persistent term.</fsummary>
<desc>
<p>Erase the name for the persistent term with key
@@ -229,7 +229,7 @@ will be slower as the number of persistent terms increases.</pre>
</func>
<func>
- <name name="get" arity="0"/>
+ <name name="get" arity="0" since="OTP 21.2"/>
<fsummary>Get all persistent terms.</fsummary>
<desc>
<p>Retrieve the keys and values for all persistent terms.
@@ -239,7 +239,7 @@ will be slower as the number of persistent terms increases.</pre>
</func>
<func>
- <name name="get" arity="1"/>
+ <name name="get" arity="1" since="OTP 21.2"/>
<fsummary>Get the value for a persistent term.</fsummary>
<desc>
<p>Retrieve the value for the persistent term associated with
@@ -256,7 +256,23 @@ will be slower as the number of persistent terms increases.</pre>
</func>
<func>
- <name name="info" arity="0"/>
+ <name name="get" arity="2" since="OTP 21.3"/>
+ <fsummary>Get the value for a persistent term.</fsummary>
+ <desc>
+ <p>Retrieve the value for the persistent term associated with
+ the key <c><anno>Key</anno></c>. The lookup will be made in
+ constant time and the value will not be copied to the heap
+ of the calling process.</p>
+ <p>This function returns <c><anno>Default</anno></c> if no
+ term has been stored with the key <c><anno>Key</anno></c>.</p>
+ <p>If the calling process holds on to the value of the
+ persistent term and the persistent term is deleted in the future,
+ the term will be copied to the process.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="info" arity="0" since="OTP 21.2"/>
<fsummary>Get information about persistent terms.</fsummary>
<desc>
<p>Return information about persistent terms in a map. The map
@@ -272,7 +288,7 @@ will be slower as the number of persistent terms increases.</pre>
</func>
<func>
- <name name="put" arity="2"/>
+ <name name="put" arity="2" since="OTP 21.2"/>
<fsummary>Store a term.</fsummary>
<desc>
<p>Store the value <c><anno>Value</anno></c> as a persistent term and
diff --git a/erts/doc/src/ref_man.xml b/erts/doc/src/ref_man.xml
index a78aaa449e..80cdcf9145 100644
--- a/erts/doc/src/ref_man.xml
+++ b/erts/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>2018</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -31,26 +31,28 @@
</header>
<description>
</description>
- <xi:include href="erl_prim_loader.xml"/>
- <xi:include href="erlang.xml"/>
- <xi:include href="init.xml"/>
- <xi:include href="persistent_term.xml"/>
- <xi:include href="zlib.xml"/>
+ <xi:include href="atomics.xml"/>
+ <xi:include href="counters.xml"/>
+ <xi:include href="driver_entry.xml"/>
<xi:include href="epmd.xml"/>
<xi:include href="erl.xml"/>
+ <xi:include href="erlang.xml"/>
<xi:include href="erlc.xml"/>
- <xi:include href="werl.xml"/>
- <xi:include href="escript.xml"/>
- <xi:include href="erlsrv.xml"/>
- <xi:include href="start_erl.xml"/>
- <xi:include href="run_erl.xml"/>
- <xi:include href="start.xml"/>
<xi:include href="erl_driver.xml"/>
- <xi:include href="driver_entry.xml"/>
- <xi:include href="erts_alloc.xml"/>
<xi:include href="erl_nif.xml"/>
+ <xi:include href="erl_prim_loader.xml"/>
+ <xi:include href="erlsrv.xml"/>
<xi:include href="erl_tracer.xml"/>
- <xi:include href="atomics.xml"/>
- <xi:include href="counters.xml"/>
+ <xi:include href="erts_alloc.xml"/>
+ <xi:include href="escript.xml"/>
+ <xi:include href="init.xml"/>
+ <xi:include href="net.xml"/>
+ <xi:include href="persistent_term.xml"/>
+ <xi:include href="run_erl.xml"/>
+ <xi:include href="socket.xml"/>
+ <xi:include href="start.xml"/>
+ <xi:include href="start_erl.xml"/>
+ <xi:include href="werl.xml"/>
+ <xi:include href="zlib.xml"/>
</application>
diff --git a/erts/doc/src/socket.xml b/erts/doc/src/socket.xml
new file mode 100644
index 0000000000..caf7058b34
--- /dev/null
+++ b/erts/doc/src/socket.xml
@@ -0,0 +1,645 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2018</year><year>2019</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>socket</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ <file>socket.xml</file>
+ </header>
+ <module since="OTP @OTP-14831@">socket</module>
+ <modulesummary>Socket interface.</modulesummary>
+ <description>
+ <p>This module provides an API for the socket interface.
+ It is used to create, delete and manipulate sockets,
+ send and receive data.</p>
+ <p>The idea is that it shall be as "close as possible" to the OS
+ level socket interface. The only significant addition is that some of
+ the functions,
+ e.g. <seealso marker="#recv/3"><c>recv/3</c></seealso>,
+ has a timeout argument. </p>
+ </description>
+
+ <datatypes>
+ <datatype>
+ <name name="domain"/>
+ </datatype>
+ <datatype>
+ <name name="type"/>
+ </datatype>
+ <datatype>
+ <name name="protocol"/>
+ </datatype>
+ <datatype>
+ <name>socket()</name>
+ <desc><p>As returned by
+ <seealso marker="#open/2"><c>open/2,3,4</c></seealso> and
+ <seealso marker="#accept/1"><c>accept/1,2</c></seealso>.</p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="ip4_address"/>
+ </datatype>
+ <datatype>
+ <name name="ip6_address"/>
+ </datatype>
+ <datatype>
+ <name name="ip_address"/>
+ </datatype>
+ <datatype>
+ <name name="sockaddr"/>
+ </datatype>
+ <datatype>
+ <name name="sockaddr_in4"/>
+ </datatype>
+ <datatype>
+ <name name="sockaddr_in6"/>
+ </datatype>
+ <datatype>
+ <name name="sockaddr_un"/>
+ </datatype>
+ <datatype>
+ <name name="port_number"/>
+ </datatype>
+ <datatype>
+ <name name="in6_flow_info"/>
+ </datatype>
+ <datatype>
+ <name name="in6_scope_id"/>
+ </datatype>
+ <datatype>
+ <name name="accept_flags"/>
+ </datatype>
+ <datatype>
+ <name name="accept_flag"/>
+ </datatype>
+ <datatype>
+ <name name="send_flags"/>
+ </datatype>
+ <datatype>
+ <name name="send_flag"/>
+ </datatype>
+ <datatype>
+ <name name="recv_flags"/>
+ </datatype>
+ <datatype>
+ <name name="recv_flag"/>
+ </datatype>
+ <datatype>
+ <name name="shutdown_how"/>
+ </datatype>
+ <datatype>
+ <name name="sockopt_level"/>
+ </datatype>
+ <datatype>
+ <name name="otp_socket_option"/>
+ </datatype>
+ <datatype>
+ <name name="socket_option"/>
+ </datatype>
+ <datatype>
+ <name name="ip_socket_option"/>
+ </datatype>
+ <datatype>
+ <name name="ipv6_socket_option"/>
+ </datatype>
+ <datatype>
+ <name name="tcp_socket_option"/>
+ </datatype>
+ <datatype>
+ <name name="udp_socket_option"/>
+ </datatype>
+ <datatype>
+ <name name="sctp_socket_option"/>
+ </datatype>
+ <datatype>
+ <name name="timeval"/>
+ </datatype>
+ <datatype>
+ <name name="ip_tos"/>
+ </datatype>
+ <datatype>
+ <name name="ip_mreq"/>
+ </datatype>
+ <datatype>
+ <name name="ip_mreq_source"/>
+ </datatype>
+ <datatype>
+ <name name="ip_pmtudisc"/>
+ </datatype>
+ <datatype>
+ <name name="ip_msfilter_mode"/>
+ </datatype>
+ <datatype>
+ <name name="ip_msfilter"/>
+ </datatype>
+ <datatype>
+ <name name="ip_pktinfo"/>
+ </datatype>
+ <datatype>
+ <name name="ipv6_mreq"/>
+ </datatype>
+ <datatype>
+ <name name="ipv6_pmtudisc"/>
+ </datatype>
+ <datatype>
+ <name name="ipv6_pktinfo"/>
+ </datatype>
+ <datatype>
+ <name name="sctp_assoc_id"/>
+ </datatype>
+ <datatype>
+ <name name="sctp_sndrcvinfo"/>
+ </datatype>
+ <datatype>
+ <name name="sctp_event_subscribe"/>
+ </datatype>
+ <datatype>
+ <name name="sctp_assocparams"/>
+ </datatype>
+ <datatype>
+ <name name="sctp_initmsg"/>
+ </datatype>
+ <datatype>
+ <name name="sctp_rtoinfo"/>
+ </datatype>
+ <datatype>
+ <name name="msghdr_flag"/>
+ </datatype>
+ <datatype>
+ <name name="msghdr_flags"/>
+ </datatype>
+ <datatype>
+ <name name="msghdr"/>
+ </datatype>
+ <datatype>
+ <name name="cmsghdr_level"/>
+ </datatype>
+ <datatype>
+ <name name="cmsghdr_type"/>
+ </datatype>
+ <!--
+ <datatype>
+ <name name="cmsghdr_data"/>
+ </datatype>
+ -->
+ <datatype>
+ <name name="cmsghdr_recv"/>
+ </datatype>
+ <datatype>
+ <name name="cmsghdr_send"/>
+ </datatype>
+ <datatype>
+ <name name="uint8"/>
+ </datatype>
+ <datatype>
+ <name name="uint16"/>
+ </datatype>
+ <datatype>
+ <name name="uint20"/>
+ </datatype>
+ <datatype>
+ <name name="uint32"/>
+ </datatype>
+ <datatype>
+ <name name="int32"/>
+ </datatype>
+ <datatype>
+ <name name="supports_options_socket"/>
+ </datatype>
+ <datatype>
+ <name name="supports_options_ip"/>
+ </datatype>
+ <datatype>
+ <name name="supports_options_ipv6"/>
+ </datatype>
+ <datatype>
+ <name name="supports_options_tcp"/>
+ </datatype>
+ <datatype>
+ <name name="supports_options_udp"/>
+ </datatype>
+ <datatype>
+ <name name="supports_options_sctp"/>
+ </datatype>
+ <datatype>
+ <name name="supports_options"/>
+ </datatype>
+ </datatypes>
+
+ <funcs>
+ <func>
+ <name name="accept" arity="1" since="OTP @OTP-14831@"/>
+ <name name="accept" arity="2" since="OTP @OTP-14831@"/>
+ <fsummary>Accept a connection on a socket.</fsummary>
+ <desc>
+ <p>Accept a connection on a socket.</p>
+ <p>This call is used with connection-based socket types
+ (<c>stream</c> or <c>seqpacket</c>). It extracs the first pending
+ connection request for the listen socket and returns the (newly)
+ connected socket.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="bind" arity="2" since="OTP @OTP-14831@"/>
+ <fsummary>Bind a name to a socket.</fsummary>
+ <desc>
+ <p>Bind a name to a socket.</p>
+ <p>When a socket is created
+ (with <seealso marker="#open/2"><c>open</c></seealso>),
+ it has no address assigned to it. <c>bind</c> assigns the
+ address specified by the <c>Addr</c> argument.</p>
+ <p>The rules used for name binding vary between domains.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="close" arity="1" since="OTP @OTP-14831@"/>
+ <fsummary>Close a socket.</fsummary>
+ <desc>
+ <p>Closes the socket.</p>
+
+ <note>
+ <p>Note that for e.g. <c>protocol</c> = <c>tcp</c>, most implementations
+ doing a close does not guarantee that any data sent is delivered to
+ the recipient before the close is detected at the remote side. </p>
+ <p>One way to handle this is to use the
+ <seealso marker="#shutdown/2"><c>shutdown</c></seealso>
+ function
+ (<c>socket:shutdown(Socket, write)</c>) to signal that no more data is
+ to be sent and then wait for the read side of the socket to be closed.</p>
+ </note>
+ </desc>
+ </func>
+
+ <func>
+ <name name="connect" arity="2" since="OTP @OTP-14831@"/>
+ <name name="connect" arity="3" since="OTP @OTP-14831@"/>
+ <fsummary>Initiate a connection on a socket.</fsummary>
+ <desc>
+ <p>This function connects the socket to the address
+ specied by the <c>SockAddr</c> argument.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="getopt" arity="3" clause_i="1" since="OTP @OTP-14831@"/>
+ <name name="getopt" arity="3" clause_i="2" since="OTP @OTP-14831@"/>
+ <name name="getopt" arity="3" clause_i="3" since="OTP @OTP-14831@"/>
+ <name name="getopt" arity="3" clause_i="4" since="OTP @OTP-14831@"/>
+ <name name="getopt" arity="3" clause_i="5" since="OTP @OTP-14831@"/>
+ <name name="getopt" arity="3" clause_i="6" since="OTP @OTP-14831@"/>
+ <name name="getopt" arity="3" clause_i="7" since="OTP @OTP-14831@"/>
+ <fsummary>Get an option on a socket.</fsummary>
+ <desc>
+ <p>Get an option on a socket.</p>
+ <p>What properties are valid depend both on <c>Level</c> and
+ on what kind of socket it is (<c>domain</c>, <c>type</c> and
+ <c>protocol</c>).</p>
+
+ <p>See the
+ <seealso marker="socket_usage#socket_options">socket options</seealso>
+ chapter of the users guide for more info. </p>
+
+ <note><p>Not all options are valid on all platforms. That is,
+ even if "we" support an option, that does not mean that the
+ underlying OS does.</p></note>
+
+ </desc>
+ </func>
+
+ <func>
+ <name name="getopt" arity="3" clause_i="8" since="OTP @OTP-14831@"/>
+ <fsummary>Get an option on a socket.</fsummary>
+ <desc>
+ <p>Get an option on a socket.</p>
+ <p>What properties are valid depend both on <c>Level</c> and
+ on what kind of socket it is (<c>domain</c>, <c>type</c> and
+ <c>protocol</c>).</p>
+ <p>When specifying <c>Level</c> as an integer, and therefor
+ using "native mode", it is *currently* up to the caller to
+ know how to interpret the result.</p>
+
+ <p>See the
+ <seealso marker="socket_usage#socket_options">socket options</seealso>
+ chapter of the users guide for more info. </p>
+
+ <note><p>Not all options are valid on all platforms. That is,
+ even if "we" support an option, that does not mean that the
+ underlying OS does.</p></note>
+ </desc>
+ </func>
+
+ <func>
+ <name name="listen" arity="1" since="OTP @OTP-14831@"/>
+ <name name="listen" arity="2" since="OTP @OTP-14831@"/>
+ <fsummary>Listen for connections on a socket.</fsummary>
+ <desc>
+ <p>Listen for connections on a socket.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="open" arity="2" since="OTP @OTP-14831@"/>
+ <name name="open" arity="3" since="OTP @OTP-14831@"/>
+ <name name="open" arity="4" since="OTP @OTP-14831@"/>
+ <fsummary>Create an endpoint for communication.</fsummary>
+ <desc>
+ <p>Creates an endpoint (socket) for communication.</p>
+ <p>For some <c>types</c> there is a default protocol, which will
+ be used if no protocol is specified: </p>
+
+ <list>
+ <item><p><c>stream</c>: <c>tcp</c></p></item>
+ <item><p><c>dgram</c>: <c>udp</c></p></item>
+ <item><p><c>seqpacket</c>: <c>sctp</c></p></item>
+ </list>
+
+ <p>The <c>Extra</c> argument is intended for "obscure" options.
+ Currently the only supported option is <c>netns</c>, which
+ is only supported on the linux platform.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="peername" arity="1" since="OTP @OTP-14831@"/>
+ <fsummary>Get name of connected socket peer.</fsummary>
+ <desc>
+ <p>Returns the address of the peer connected to the socket.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="recv" arity="1" since="OTP @OTP-14831@"/>
+ <name name="recv" arity="2" since="OTP @OTP-14831@"/>
+ <name name="recv" arity="3" clause_i="1" since="OTP @OTP-14831@"/>
+ <name name="recv" arity="3" clause_i="2" since="OTP @OTP-14831@"/>
+ <name name="recv" arity="4" since="OTP @OTP-14831@"/>
+ <fsummary>Receive a message from a socket.</fsummary>
+ <desc>
+ <p>Receive a message from a socket.</p>
+ <p>There is a special case for the argument <c>Length</c>.
+ If it is set to zero (0), it means "give me everything you
+ currently have".</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="recvfrom" arity="1" since="OTP @OTP-14831@"/>
+ <name name="recvfrom" arity="2" since="OTP @OTP-14831@"/>
+ <name name="recvfrom" arity="3" clause_i="1" since="OTP @OTP-14831@"/>
+ <name name="recvfrom" arity="3" clause_i="2" since="OTP @OTP-14831@"/>
+ <name name="recvfrom" arity="3" clause_i="3" since="OTP @OTP-14831@"/>
+ <name name="recvfrom" arity="4" since="OTP @OTP-14831@"/>
+ <fsummary>Receive a message from a socket.</fsummary>
+ <desc>
+ <p>Receive a message from a socket.</p>
+ <p>This function reads "messages", which means that regardless of
+ how much we want to read, it returns when we get a message.</p>
+ <p>The <c>BufSz</c> argument basically defines the size of the
+ receive buffer. By setting the value to zero (0), the configured
+ size (setopt with <c>Level</c> = <c>otp</c> and <c>Key</c> = <c>rcvbuf</c>)
+ is used.</p>
+ <p>It may be impossible to know what (buffer) size is appropriate
+ "in advance", and in those cases it may be convenient to use the
+ (recv) 'peek' flag. When this flag is provided, the message is *not*
+ "consumed" from the underlying buffers, so another recvfrom call
+ is needed, possibly with a then adjusted buffer size.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="recvmsg" arity="1" since="OTP @OTP-14831@"/>
+ <name name="recvmsg" arity="2" clause_i="1" since="OTP @OTP-14831@"/>
+ <name name="recvmsg" arity="2" clause_i="2" since="OTP @OTP-14831@"/>
+ <name name="recvmsg" arity="3" clause_i="1" since="OTP @OTP-14831@"/>
+ <name name="recvmsg" arity="3" clause_i="2" since="OTP @OTP-14831@"/>
+ <name name="recvmsg" arity="5" since="OTP @OTP-14831@"/>
+ <fsummary>Receive a message from a socket.</fsummary>
+ <desc>
+ <p>Receive a message from a socket.</p>
+ <p>This function reads "messages", which means that regardless of
+ how much we want to read, it returns when we get a message.</p>
+ <p>The message will be delivered in the form of a <c>msghdr()</c>,
+ which may contain the source address (if socket not connected),
+ a list of <c>cmsghdr_recv()</c> (depends on what socket options have
+ been set and what the protocol and platform supports) and
+ also a set of flags, providing further info about the read. </p>
+
+ <p>The <c>BufSz</c> argument basically defines the size of the
+ receive buffer. By setting the value to zero (0), the configured
+ size (setopt with <c>Level</c> = <c>otp</c> and <c>Key</c> = <c>rcvbuf</c>)
+ is used.</p>
+
+ <p>The <c>CtrlSz</c> argument basically defines the size of the
+ receive buffer for the control messages.
+ By setting the value to zero (0), the configured size (setopt
+ with <c>Level</c> = <c>otp</c>) is used.</p>
+
+ <p>It may be impossible to know what (buffer) size is appropriate
+ "in advance", and in those cases it may be convenient to use the
+ (recv) 'peek' flag. When this flag is provided, the message is *not*
+ "consumed" from the underlying buffers, so another recvmsg call
+ is needed, possibly with a then adjusted buffer size.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="send" arity="2" since="OTP @OTP-14831@"/>
+ <name name="send" arity="3" clause_i="1" since="OTP @OTP-14831@"/>
+ <name name="send" arity="3" clause_i="2" since="OTP @OTP-14831@"/>
+ <name name="send" arity="4" since="OTP @OTP-14831@"/>
+ <fsummary>Send a message on a socket.</fsummary>
+ <desc>
+ <p>Send a message on a connected socket.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="sendmsg" arity="2" since="OTP @OTP-14831@"/>
+ <name name="sendmsg" arity="3" clause_i="1" since="OTP @OTP-14831@"/>
+ <name name="sendmsg" arity="3" clause_i="2" since="OTP @OTP-14831@"/>
+ <name name="sendmsg" arity="4" since="OTP @OTP-14831@"/>
+ <fsummary>Send a message on a socket.</fsummary>
+ <desc>
+ <p>Send a message on a socket. The destination, if needed
+ (socket <em>not</em> connected) is provided in the <c>MsgHdr</c>,
+ which also contains the message to send,
+ The <c>MsgHdr</c> may also contain an list of optional <c>cmsghdr_send()</c>
+ (depends on what the protocol and platform supports).</p>
+
+ <p>Unlike the <seealso marker="#send/2"><c>send</c></seealso> function,
+ this one sends <em>one message</em>.
+ This means that if, for whatever reason, its not possible to send the
+ message in one go, the function will instead return with the
+ <em>remaining</em> data (<c>{ok, Remaining}</c>). Thereby leaving it
+ up to the caller to decide what to do (retry with the remaining data
+ of give up). </p>
+
+ </desc>
+ </func>
+
+ <func>
+ <name name="sendto" arity="3" since="OTP @OTP-14831@"/>
+ <name name="sendto" arity="4" clause_i="1" since="OTP @OTP-14831@"/>
+ <name name="sendto" arity="4" clause_i="2" since="OTP @OTP-14831@"/>
+ <name name="sendto" arity="5" since="OTP @OTP-14831@"/>
+ <fsummary>Send a message on a socket.</fsummary>
+ <desc>
+ <p>Send a message on a socket, to the specified destination.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="setopt" arity="4" clause_i="1" since="OTP @OTP-14831@"/>
+ <name name="setopt" arity="4" clause_i="2" since="OTP @OTP-14831@"/>
+ <name name="setopt" arity="4" clause_i="3" since="OTP @OTP-14831@"/>
+ <name name="setopt" arity="4" clause_i="4" since="OTP @OTP-14831@"/>
+ <name name="setopt" arity="4" clause_i="5" since="OTP @OTP-14831@"/>
+ <name name="setopt" arity="4" clause_i="6" since="OTP @OTP-14831@"/>
+ <name name="setopt" arity="4" clause_i="7" since="OTP @OTP-14831@"/>
+ <fsummary>Set options on a socket.</fsummary>
+ <desc>
+ <p>Set options on a socket.</p>
+ <p>What properties are valid depend both on <c>Level</c> and on
+ what kind of socket it is (<c>domain</c>, <c>type</c> and
+ <c>protocol</c>).</p>
+
+ <p>See the
+ <seealso marker="socket_usage#socket_options">socket options</seealso>
+ chapter of the users guide for more info. </p>
+
+ <note><p>Not all options are valid on all platforms. That is,
+ even if "we" support an option, that does not mean that the
+ underlying OS does.</p></note>
+
+ <note><p>Sockets are set 'non-blocking' when created, so this option
+ is *not* available (as it would adversely effect the Erlang VM
+ to set a socket 'blocking').</p></note>
+ </desc>
+ </func>
+
+ <func>
+ <name name="setopt" arity="4" clause_i="8" since="OTP @OTP-14831@"/>
+ <fsummary>Set options on a socket.</fsummary>
+ <desc>
+ <p>Set options on a socket.</p>
+ <p>What properties are valid depend both on <c>Level</c> and on
+ what kind of socket it is (<c>domain</c>, <c>type</c> and
+ <c>protocol</c>).</p>
+
+ <p>See the
+ <seealso marker="socket_usage#socket_options">socket options</seealso>
+ chapter of the users guide for more info. </p>
+
+ <note><p>Not all options are valid on all platforms. That is,
+ even if "we" support an option, that does not mean that the
+ underlying OS does.</p></note>
+
+ <note><p>Sockets are set 'non-blocking' when created, so this option
+ is *not* available (as it would adversely effect the Erlang VM
+ to set a socket 'blocking').</p></note>
+ </desc>
+ </func>
+
+ <func>
+ <name name="shutdown" arity="2" since="OTP @OTP-14831@"/>
+ <fsummary>Shut down part of a full-duplex connection.</fsummary>
+ <desc>
+ <p>Shut down all or part of a full-duplex connection.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="sockname" arity="1" since="OTP @OTP-14831@"/>
+ <fsummary>Get socket name.</fsummary>
+ <desc>
+ <p>Returns the current address to which the socket is bound.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="supports" arity="0" since="OTP @OTP-14831@"/>
+ <name name="supports" arity="1" clause_i="1" since="OTP @OTP-14831@"/>
+ <name name="supports" arity="1" clause_i="2" since="OTP @OTP-14831@"/>
+ <name name="supports" arity="1" clause_i="3" since="OTP @OTP-14831@"/>
+ <name name="supports" arity="1" clause_i="4" since="OTP @OTP-14831@"/>
+ <name name="supports" arity="2" clause_i="1" since="OTP @OTP-14831@"/>
+ <name name="supports" arity="2" clause_i="2" since="OTP @OTP-14831@"/>
+ <name name="supports" arity="2" clause_i="3" since="OTP @OTP-14831@"/>
+ <name name="supports" arity="2" clause_i="4" since="OTP @OTP-14831@"/>
+ <name name="supports" arity="2" clause_i="5" since="OTP @OTP-14831@"/>
+ <name name="supports" arity="2" clause_i="6" since="OTP @OTP-14831@"/>
+ <name name="supports" arity="2" clause_i="7" since="OTP @OTP-14831@"/>
+ <name name="supports" arity="3" clause_i="1" since="OTP @OTP-14831@"/>
+ <name name="supports" arity="3" clause_i="2" since="OTP @OTP-14831@"/>
+ <name name="supports" arity="3" clause_i="3" since="OTP @OTP-14831@"/>
+ <name name="supports" arity="3" clause_i="4" since="OTP @OTP-14831@"/>
+ <name name="supports" arity="3" clause_i="5" since="OTP @OTP-14831@"/>
+ <name name="supports" arity="3" clause_i="6" since="OTP @OTP-14831@"/>
+ <name name="supports" arity="3" clause_i="7" since="OTP @OTP-14831@"/>
+ <fsummary>Report info about what the platform supports.</fsummary>
+ <desc>
+ <p>This function intends to retreive information about what the
+ platform supports. Such as if SCTP is supported. Or which socket
+ options are supported. </p>
+ </desc>
+ </func>
+
+ </funcs>
+ <section>
+ <title>Examples</title>
+ <marker id="examples"></marker>
+ <code type="none">
+client(Addr, SAddr, SPort) ->
+ {ok, Sock} = socket:open(inet, stream, tcp),
+ {ok, _} = socket:bind(Sock, #{family => inet,
+ addr => Addr}),
+ ok = socket:connect(Sock, #{family => inet,
+ addr => SAddr,
+ port => SPort}),
+ Msg = list_to_binary("hello"),
+ ok = socket:send(Sock, Msg),
+ ok = socket:shutdown(Sock, write),
+ {ok, Msg} = socket:recv(Sock),
+ ok = socket:close(Sock).
+
+server(Addr, Port) ->
+ {ok, LSock} = socket:open(inet, stream, tcp),
+ {ok, _} = socket:bind(LSock, #{family => inet,
+ port => Port,
+ addr => Addr}),
+ ok = socket:listen(LSock),
+ {ok, Sock} = socket:accept(LSock),
+ {ok, Msg} = socket:recv(Sock),
+ ok = socket:send(Sock, Msg),
+ ok = socket:shutdown(Sock, write),
+ ok = socket:close(Sock),
+ ok = socket:close(LSock).
+ </code>
+ </section>
+</erlref>
+
diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml
new file mode 100644
index 0000000000..e0f006e618
--- /dev/null
+++ b/erts/doc/src/socket_usage.xml
@@ -0,0 +1,773 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>2018</year><year>2019</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>Socket Usage</title>
+ <prepared>Micael Karlberg</prepared>
+ <docno></docno>
+ <date>2018-07-17</date>
+ <rev>PA1</rev>
+ <file>socket_usage.xml</file>
+ </header>
+
+ <section>
+ <title>Introduction</title>
+ <p>The socket interface (module) is basically an "thin" layer on top of
+ the OS socket interface. It is assumed that, unless you have special needs,
+ gen_[tcp|udp|sctp] should be sufficent. </p>
+ <p>Note that just because we have a documented and described option,
+ it does <em>not</em> mean that the OS supports it. So its recommended
+ that the user reads the platform specific documentation for the
+ option used. </p>
+ </section>
+
+ <section>
+ <marker id="socket_options"></marker>
+ <title>Socket Options</title>
+
+ <p>Options for level <c>otp</c>: </p>
+ <table>
+ <row>
+ <cell><em>Option Name</em></cell>
+ <cell><em>Value Type</em></cell>
+ <cell><em>Set</em></cell>
+ <cell><em>Get</em></cell>
+ <cell><em>Other Requirements and comments</em></cell>
+ </row>
+ <row>
+ <cell>assoc_id</cell>
+ <cell>integer()</cell>
+ <cell>no</cell>
+ <cell>yes</cell>
+ <cell>type = seqpacket, protocol = sctp, is an association</cell>
+ </row>
+ <row>
+ <cell>debug</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>iow</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>controlling_process</cell>
+ <cell>pid()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>rcvbuf</cell>
+ <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>
+ </row>
+ <row>
+ <cell>rcvctrlbuf</cell>
+ <cell>default | pos_integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>default only valid for set</cell>
+ </row>
+ <row>
+ <cell>sndctrlbuf</cell>
+ <cell>default | pos_integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>default only valid for set</cell>
+ </row>
+ <row>
+ <cell>fd</cell>
+ <cell>integer()</cell>
+ <cell>no</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <tcaption>option levels</tcaption>
+ </table>
+
+ <p>Options for level <c>socket</c>: </p>
+ <table>
+ <row>
+ <cell><em>Option Name</em></cell>
+ <cell><em>Value Type</em></cell>
+ <cell><em>Set</em></cell>
+ <cell><em>Get</em></cell>
+ <cell><em>Other Requirements and comments</em></cell>
+ </row>
+ <row>
+ <cell>acceptcon</cell>
+ <cell>boolean()</cell>
+ <cell>no</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>bindtodevice</cell>
+ <cell>string()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>broadcast</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = dgram</cell>
+ </row>
+ <row>
+ <cell>debug</cell>
+ <cell>integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>requires admin capability</cell>
+ </row>
+ <row>
+ <cell>domain</cell>
+ <cell>domain()</cell>
+ <cell>no</cell>
+ <cell>yes</cell>
+ <cell><em>Not</em> on FreeBSD (for instance)</cell>
+ </row>
+ <row>
+ <cell>dontroute</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>keepalive</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>linger</cell>
+ <cell>abort | linger()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>oobinline</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>peek_off</cell>
+ <cell>integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>domain = local (unix)</cell>
+ </row>
+ <row>
+ <cell>priority</cell>
+ <cell>integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>protocol</cell>
+ <cell>protocol()</cell>
+ <cell>no</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>rcvbuf</cell>
+ <cell>non_neg_integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>rcvlowat</cell>
+ <cell>non_neg_integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>rcvtimeo</cell>
+ <cell>timeval()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>reuseaddr</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>reuseport</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>domain = inet | inet6</cell>
+ </row>
+ <row>
+ <cell>sndbuf</cell>
+ <cell>non_neg_integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>sndlowat</cell>
+ <cell>non_neg_integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>not changeable on Linux</cell>
+ </row>
+ <row>
+ <cell>sndtimeo</cell>
+ <cell>timeval()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>timestamp</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>type</cell>
+ <cell>type()</cell>
+ <cell>no</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <tcaption>socket options</tcaption>
+ </table>
+
+ <p>Options for level <c>ip</c>: </p>
+ <table>
+ <row>
+ <cell><em>Option Name</em></cell>
+ <cell><em>Value Type</em></cell>
+ <cell><em>Set</em></cell>
+ <cell><em>Get</em></cell>
+ <cell><em>Other Requirements and comments</em></cell>
+ </row>
+ <row>
+ <cell>add_membership</cell>
+ <cell>ip_mreq()</cell>
+ <cell>yes</cell>
+ <cell>no</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>add_source_membership</cell>
+ <cell>ip_mreq_source()</cell>
+ <cell>yes</cell>
+ <cell>no</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>block_source</cell>
+ <cell>ip_mreq_source()</cell>
+ <cell>yes</cell>
+ <cell>no</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>drop_membership</cell>
+ <cell>ip_mreq()</cell>
+ <cell>yes</cell>
+ <cell>no</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>drop_source_membership</cell>
+ <cell>ip_mreq_source()</cell>
+ <cell>yes</cell>
+ <cell>no</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>freebind</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>hdrincl</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = raw</cell>
+ </row>
+ <row>
+ <cell>minttl</cell>
+ <cell>integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = raw</cell>
+ </row>
+ <row>
+ <cell>msfilter</cell>
+ <cell>null | ip_msfilter()</cell>
+ <cell>yes</cell>
+ <cell>no</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>mtu</cell>
+ <cell>integer()</cell>
+ <cell>no</cell>
+ <cell>yes</cell>
+ <cell>type = raw</cell>
+ </row>
+ <row>
+ <cell>mtu_discover</cell>
+ <cell>ip_pmtudisc()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>multicast_all</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>multicast_if</cell>
+ <cell>any | ip4_address()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>multicast_loop</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>multicast_ttl</cell>
+ <cell>uint8()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>nodefrag</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = raw</cell>
+ </row>
+ <row>
+ <cell>pktinfo</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = dgram</cell>
+ </row>
+ <row>
+ <cell>recvdstaddr</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = dgram</cell>
+ </row>
+ <row>
+ <cell>recverr</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>recvif</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = dgram | raw</cell>
+ </row>
+ <row>
+ <cell>recvopts</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type =/= stream</cell>
+ </row>
+ <row>
+ <cell>recvorigdstaddr</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>recvttl</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type =/= stream</cell>
+ </row>
+ <row>
+ <cell>retopts</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type =/= stream</cell>
+ </row>
+ <row>
+ <cell>router_alert</cell>
+ <cell>integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = raw</cell>
+ </row>
+ <row>
+ <cell>sendsrcaddr</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>tos</cell>
+ <cell>ip_tos()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>some high-priority levels may require superuser capability</cell>
+ </row>
+ <row>
+ <cell>transparent</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>requires admin capability</cell>
+ </row>
+ <row>
+ <cell>ttl</cell>
+ <cell>integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>unblock_source</cell>
+ <cell>ip_mreq_source()</cell>
+ <cell>yes</cell>
+ <cell>no</cell>
+ <cell>none</cell>
+ </row>
+ <tcaption>ip options</tcaption>
+ </table>
+
+ <p>Options for level <c>ipv6</c>: </p>
+ <table>
+ <row>
+ <cell><em>Option Name</em></cell>
+ <cell><em>Value Type</em></cell>
+ <cell><em>Set</em></cell>
+ <cell><em>Get</em></cell>
+ <cell><em>Other Requirements and comments</em></cell>
+ </row>
+ <row>
+ <cell>addrform</cell>
+ <cell>inet</cell>
+ <cell>yes</cell>
+ <cell>no</cell>
+ <cell>allowed only for IPv6 sockets that are connected and bound to a
+ v4-mapped-on-v6 address</cell>
+ </row>
+ <row>
+ <cell>add_membership</cell>
+ <cell>ipv6_mreq()</cell>
+ <cell>yes</cell>
+ <cell>no</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>authhdr</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = dgram | raw, obsolete?</cell>
+ </row>
+ <row>
+ <cell>drop_membership</cell>
+ <cell>ipv6_mreq()</cell>
+ <cell>yes</cell>
+ <cell>no</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>dstopts</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = dgram | raw, requires superuser privileges to update</cell>
+ </row>
+ <row>
+ <cell>flowinfo</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = dgram | raw, requires superuser privileges to update</cell>
+ </row>
+ <row>
+ <cell>hoplimit</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = dgram | raw, requires superuser privileges to update</cell>
+ </row>
+ <row>
+ <cell>hopopts</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = dgram | raw, requires superuser privileges to update</cell>
+ </row>
+ <row>
+ <cell>mtu</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>Get: Only after the socket has been connected</cell>
+ </row>
+ <row>
+ <cell>mtu_discover</cell>
+ <cell>ipv6_pmtudisc()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>multicast_hops</cell>
+ <cell>default | uint8()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>multicast_if</cell>
+ <cell>integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = dgram | raw</cell>
+ </row>
+ <row>
+ <cell>multicast_loop</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>recverr</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>recvpktinfo | pktinfo</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = dgram | raw</cell>
+ </row>
+ <row>
+ <cell>router_alert</cell>
+ <cell>integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = raw</cell>
+ </row>
+ <row>
+ <cell>rthdr</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>type = dgram | raw, requires superuser privileges to update</cell>
+ </row>
+ <row>
+ <cell>unicast_hops</cell>
+ <cell>default | uint8()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>v6only</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>no</cell>
+ <cell>none</cell>
+ </row>
+ <tcaption>ipv6 options</tcaption>
+ </table>
+
+ <p>Options for level <c>tcp</c>: </p>
+ <table>
+ <row>
+ <cell><em>Option Name</em></cell>
+ <cell><em>Value Type</em></cell>
+ <cell><em>Set</em></cell>
+ <cell><em>Get</em></cell>
+ <cell><em>Other Requirements and comments</em></cell>
+ </row>
+ <row>
+ <cell>congestion</cell>
+ <cell>string()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>maxseg</cell>
+ <cell>integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>nodelay</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <tcaption>tcp options</tcaption>
+ </table>
+
+ <p>Options for level <c>udp</c>: </p>
+ <table>
+ <row>
+ <cell><em>Option Name</em></cell>
+ <cell><em>Value Type</em></cell>
+ <cell><em>Set</em></cell>
+ <cell><em>Get</em></cell>
+ <cell><em>Other Requirements and comments</em></cell>
+ </row>
+ <row>
+ <cell>cork</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <tcaption>udp options</tcaption>
+ </table>
+
+ <p>Options for level <c>sctp</c>: </p>
+ <table>
+ <row>
+ <cell><em>Option Name</em></cell>
+ <cell><em>Value Type</em></cell>
+ <cell><em>Set</em></cell>
+ <cell><em>Get</em></cell>
+ <cell><em>Other Requirements and comments</em></cell>
+ </row>
+ <row>
+ <cell>associnfo</cell>
+ <cell>sctp_assocparams()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>autoclose</cell>
+ <cell>non_neg_integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>disable_fragments</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>events</cell>
+ <cell>sctp_event_subscribe()</cell>
+ <cell>yes</cell>
+ <cell>no</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>initmsg</cell>
+ <cell>sctp_initmsg()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>maxseg</cell>
+ <cell>non_neg_integer()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>nodelay</cell>
+ <cell>boolean()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <row>
+ <cell>rtoinfo</cell>
+ <cell>sctp_rtoinfo()</cell>
+ <cell>yes</cell>
+ <cell>yes</cell>
+ <cell>none</cell>
+ </row>
+ <tcaption>sctp options</tcaption>
+ </table>
+
+ </section>
+</chapter>
+
diff --git a/erts/doc/src/specs.xml b/erts/doc/src/specs.xml
index 0b943e6295..68fab5edf1 100644
--- a/erts/doc/src/specs.xml
+++ b/erts/doc/src/specs.xml
@@ -5,6 +5,8 @@
<xi:include href="../specs/specs_erl_tracer.xml"/>
<xi:include href="../specs/specs_init.xml"/>
<xi:include href="../specs/specs_persistent_term.xml"/>
+ <xi:include href="../specs/specs_socket.xml"/>
+ <xi:include href="../specs/specs_net.xml"/>
<xi:include href="../specs/specs_zlib.xml"/>
<xi:include href="../specs/specs_atomics.xml"/>
<xi:include href="../specs/specs_counters.xml"/>
diff --git a/erts/doc/src/zlib.xml b/erts/doc/src/zlib.xml
index 6f4c42da27..38229456c9 100644
--- a/erts/doc/src/zlib.xml
+++ b/erts/doc/src/zlib.xml
@@ -29,7 +29,7 @@
<rev></rev>
<file>zlib.xml</file>
</header>
- <module>zlib</module>
+ <module since="">zlib</module>
<modulesummary>zlib compression interface.</modulesummary>
<description>
<p>This module provides an API for the zlib library
@@ -120,7 +120,7 @@ list_to_binary([Compressed|Last])</pre>
<funcs>
<func>
- <name name="adler32" arity="2"/>
+ <name name="adler32" arity="2" since=""/>
<fsummary>Calculate the Adler checksum.</fsummary>
<desc>
<p>Calculates the Adler-32 checksum for <c><anno>Data</anno></c>.</p>
@@ -133,7 +133,7 @@ list_to_binary([Compressed|Last])</pre>
</func>
<func>
- <name name="adler32" arity="3"/>
+ <name name="adler32" arity="3" since=""/>
<fsummary>Calculate the Adler checksum.</fsummary>
<desc>
<p>Updates a running Adler-32 checksum for <c><anno>Data</anno></c>.
@@ -153,7 +153,7 @@ Crc = lists:foldl(fun(Data,Crc0) ->
</func>
<func>
- <name name="adler32_combine" arity="4"/>
+ <name name="adler32_combine" arity="4" since=""/>
<fsummary>Combine two Adler-32 checksums.</fsummary>
<desc>
<p>Combines two Adler-32 checksums into one. For two binaries or
@@ -172,7 +172,7 @@ Crc = lists:foldl(fun(Data,Crc0) ->
</func>
<func>
- <name name="close" arity="1"/>
+ <name name="close" arity="1" since=""/>
<fsummary>Close a stream.</fsummary>
<desc>
<p>Closes the stream referenced by <c><anno>Z</anno></c>.</p>
@@ -180,7 +180,7 @@ Crc = lists:foldl(fun(Data,Crc0) ->
</func>
<func>
- <name name="compress" arity="1"/>
+ <name name="compress" arity="1" since=""/>
<fsummary>Compress data with standard zlib functionality.</fsummary>
<desc>
<p>Compresses data with zlib headers and checksum.</p>
@@ -188,7 +188,7 @@ Crc = lists:foldl(fun(Data,Crc0) ->
</func>
<func>
- <name name="crc32" arity="1"/>
+ <name name="crc32" arity="1" since=""/>
<fsummary>Get current CRC.</fsummary>
<desc>
<p>Gets the current calculated CRC checksum.</p>
@@ -202,7 +202,7 @@ Crc = lists:foldl(fun(Data,Crc0) ->
</func>
<func>
- <name name="crc32" arity="2"/>
+ <name name="crc32" arity="2" since=""/>
<fsummary>Calculate CRC.</fsummary>
<desc>
<p>Calculates the CRC checksum for <c><anno>Data</anno></c>.</p>
@@ -215,7 +215,7 @@ Crc = lists:foldl(fun(Data,Crc0) ->
</func>
<func>
- <name name="crc32" arity="3"/>
+ <name name="crc32" arity="3" since=""/>
<fsummary>Calculate CRC.</fsummary>
<desc>
<p>Updates a running CRC checksum for <c><anno>Data</anno></c>.
@@ -235,7 +235,7 @@ Crc = lists:foldl(fun(Data,Crc0) ->
</func>
<func>
- <name name="crc32_combine" arity="4"/>
+ <name name="crc32_combine" arity="4" since=""/>
<fsummary>Combine two CRCs.</fsummary>
<desc>
<p>Combines two CRC checksums into one. For two binaries or iolists,
@@ -254,7 +254,7 @@ Crc = lists:foldl(fun(Data,Crc0) ->
</func>
<func>
- <name name="deflate" arity="2"/>
+ <name name="deflate" arity="2" since=""/>
<fsummary>Compress data.</fsummary>
<desc>
<p>Same as <c>deflate(<anno>Z</anno>, <anno>Data</anno>, none)</c>.</p>
@@ -262,7 +262,7 @@ Crc = lists:foldl(fun(Data,Crc0) ->
</func>
<func>
- <name name="deflate" arity="3"/>
+ <name name="deflate" arity="3" since=""/>
<fsummary>Compress data.</fsummary>
<desc>
<p>Compresses as much data as possible, and
@@ -300,7 +300,7 @@ list_to_binary([B1,B2])</pre>
</func>
<func>
- <name name="deflateEnd" arity="1"/>
+ <name name="deflateEnd" arity="1" since=""/>
<fsummary>End deflate session.</fsummary>
<desc>
<p>Ends the deflate session and cleans all data used. Notice that this
@@ -311,7 +311,7 @@ list_to_binary([B1,B2])</pre>
</func>
<func>
- <name name="deflateInit" arity="1"/>
+ <name name="deflateInit" arity="1" since=""/>
<fsummary>Initialize a session for compression.</fsummary>
<desc>
<p>Same as <c>zlib:deflateInit(<anno>Z</anno>, default)</c>.</p>
@@ -319,7 +319,7 @@ list_to_binary([B1,B2])</pre>
</func>
<func>
- <name name="deflateInit" arity="2"/>
+ <name name="deflateInit" arity="2" since=""/>
<fsummary>Initialize a session for compression.</fsummary>
<desc>
<p>Initializes a zlib stream for compression.</p>
@@ -334,7 +334,7 @@ list_to_binary([B1,B2])</pre>
</func>
<func>
- <name name="deflateInit" arity="6"/>
+ <name name="deflateInit" arity="6" since=""/>
<fsummary>Initialize a session for compression.</fsummary>
<desc>
<p>Initiates a zlib stream for compression.</p>
@@ -410,7 +410,7 @@ list_to_binary([B1,B2])</pre>
</func>
<func>
- <name name="deflateParams" arity="3"/>
+ <name name="deflateParams" arity="3" since=""/>
<fsummary>Dynamicly update deflate parameters.</fsummary>
<desc>
<p>Dynamically updates the compression level and compression
@@ -432,7 +432,7 @@ list_to_binary([B1,B2])</pre>
</func>
<func>
- <name name="deflateReset" arity="1"/>
+ <name name="deflateReset" arity="1" since=""/>
<fsummary>Reset the deflate session.</fsummary>
<desc>
<p>Equivalent to
@@ -446,7 +446,7 @@ list_to_binary([B1,B2])</pre>
</func>
<func>
- <name name="deflateSetDictionary" arity="2"/>
+ <name name="deflateSetDictionary" arity="2" since=""/>
<fsummary>Initialize the compression dictionary.</fsummary>
<desc>
<p>Initializes the compression dictionary from the specified byte
@@ -464,7 +464,7 @@ list_to_binary([B1,B2])</pre>
</func>
<func>
- <name name="getBufSize" arity="1"/>
+ <name name="getBufSize" arity="1" since=""/>
<fsummary>Get buffer size.</fsummary>
<desc>
<p>Gets the size of the intermediate buffer.</p>
@@ -476,7 +476,7 @@ list_to_binary([B1,B2])</pre>
</func>
<func>
- <name name="gunzip" arity="1"/>
+ <name name="gunzip" arity="1" since=""/>
<fsummary>Uncompress data with gz header.</fsummary>
<desc>
<p>Uncompresses data with gz headers and checksum.</p>
@@ -484,7 +484,7 @@ list_to_binary([B1,B2])</pre>
</func>
<func>
- <name name="gzip" arity="1"/>
+ <name name="gzip" arity="1" since=""/>
<fsummary>Compress data with gz header.</fsummary>
<desc>
<p>Compresses data with gz headers and checksum.</p>
@@ -492,7 +492,7 @@ list_to_binary([B1,B2])</pre>
</func>
<func>
- <name name="inflate" arity="2"/>
+ <name name="inflate" arity="2" since=""/>
<fsummary>Decompress data.</fsummary>
<desc>
<p>Equivalent to
@@ -502,7 +502,7 @@ list_to_binary([B1,B2])</pre>
</func>
<func>
- <name name="inflate" arity="3"/>
+ <name name="inflate" arity="3" since="OTP 20.1"/>
<fsummary>Decompress data.</fsummary>
<desc>
<p>Decompresses as much data as possible. It can introduce some output
@@ -524,7 +524,7 @@ list_to_binary([B1,B2])</pre>
</func>
<func>
- <name name="inflateChunk" arity="1"/>
+ <name name="inflateChunk" arity="1" since="OTP 18.0"/>
<fsummary>Read next uncompressed chunk.</fsummary>
<desc>
<warning>
@@ -540,7 +540,7 @@ list_to_binary([B1,B2])</pre>
</func>
<func>
- <name name="inflateChunk" arity="2"/>
+ <name name="inflateChunk" arity="2" since="OTP 18.0"/>
<fsummary>Decompress data with limited output size.</fsummary>
<desc>
<warning>
@@ -584,7 +584,7 @@ loop(Z, Handler, Uncompressed) ->
</func>
<func>
- <name name="inflateEnd" arity="1"/>
+ <name name="inflateEnd" arity="1" since=""/>
<fsummary>End inflate session.</fsummary>
<desc>
<p>Ends the inflate session and cleans all data used. Notice
@@ -595,7 +595,7 @@ loop(Z, Handler, Uncompressed) ->
</func>
<func>
- <name name="inflateGetDictionary" arity="1"/>
+ <name name="inflateGetDictionary" arity="1" since="OTP 20.0"/>
<fsummary>Return the decompression dictionary.</fsummary>
<desc>
<p>Returns the decompression dictionary currently in use
@@ -607,7 +607,7 @@ loop(Z, Handler, Uncompressed) ->
</func>
<func>
- <name name="inflateInit" arity="1"/>
+ <name name="inflateInit" arity="1" since=""/>
<fsummary>Initialize a session for decompression.</fsummary>
<desc>
<p>Initializes a zlib stream for decompression.</p>
@@ -615,7 +615,7 @@ loop(Z, Handler, Uncompressed) ->
</func>
<func>
- <name name="inflateInit" arity="2"/>
+ <name name="inflateInit" arity="2" since=""/>
<fsummary>Initialize a session for decompression.</fsummary>
<desc>
<p>Initializes a decompression session on zlib stream.</p>
@@ -634,7 +634,7 @@ loop(Z, Handler, Uncompressed) ->
</func>
<func>
- <name name="inflateReset" arity="1"/>
+ <name name="inflateReset" arity="1" since=""/>
<fsummary>>Reset the inflate session.</fsummary>
<desc>
<p>Equivalent to
@@ -648,7 +648,7 @@ loop(Z, Handler, Uncompressed) ->
</func>
<func>
- <name name="inflateSetDictionary" arity="2"/>
+ <name name="inflateSetDictionary" arity="2" since=""/>
<fsummary>Initialize the decompression dictionary.</fsummary>
<desc>
<p>Initializes the decompression dictionary from the specified
@@ -688,7 +688,7 @@ new_unpack(Z, Compressed, Dict) ->
</func>
<func>
- <name name="open" arity="0"/>
+ <name name="open" arity="0" since=""/>
<fsummary>Open a stream and return a stream reference.</fsummary>
<desc>
<p>Opens a zlib stream.</p>
@@ -696,7 +696,7 @@ new_unpack(Z, Compressed, Dict) ->
</func>
<func>
- <name name="safeInflate" arity="2"/>
+ <name name="safeInflate" arity="2" since="OTP 20.1"/>
<fsummary>Decompress data with limited output size.</fsummary>
<desc>
<p>Like <seealso marker="#inflate/2"><c>inflate/2</c></seealso>,
@@ -733,7 +733,7 @@ loop(Z, Handler, {finished, Output}) ->
</func>
<func>
- <name name="setBufSize" arity="2"/>
+ <name name="setBufSize" arity="2" since=""/>
<fsummary>Set buffer size.</fsummary>
<desc>
<p>Sets the intermediate buffer size.</p>
@@ -745,7 +745,7 @@ loop(Z, Handler, {finished, Output}) ->
</func>
<func>
- <name name="set_controlling_process" arity="2"/>
+ <name name="set_controlling_process" arity="2" since="OTP 20.1.3"/>
<fsummary>Transfers ownership of a zlib stream.</fsummary>
<desc>
<p>Changes the controlling process of <c><anno>Z</anno></c> to
@@ -754,7 +754,7 @@ loop(Z, Handler, {finished, Output}) ->
</func>
<func>
- <name name="uncompress" arity="1"/>
+ <name name="uncompress" arity="1" since=""/>
<fsummary>Uncompress data with standard zlib functionality.</fsummary>
<desc>
<p>Uncompresses data with zlib headers and checksum.</p>
@@ -762,7 +762,7 @@ loop(Z, Handler, {finished, Output}) ->
</func>
<func>
- <name name="unzip" arity="1"/>
+ <name name="unzip" arity="1" since=""/>
<fsummary>Uncompress data without the zlib headers.</fsummary>
<desc>
<p>Uncompresses data without zlib headers and checksum.</p>
@@ -770,7 +770,7 @@ loop(Z, Handler, {finished, Output}) ->
</func>
<func>
- <name name="zip" arity="1"/>
+ <name name="zip" arity="1" since=""/>
<fsummary>Compress data without the zlib headers.</fsummary>
<desc>
<p>Compresses data without zlib headers and checksum.</p>
diff --git a/erts/emulator/Makefile.in b/erts/emulator/Makefile.in
index e7648f2396..21351df656 100644
--- a/erts/emulator/Makefile.in
+++ b/erts/emulator/Makefile.in
@@ -1,7 +1,8 @@
+
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1996-2018. All Rights Reserved.
+# Copyright Ericsson AB 1996-2019. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -632,14 +633,16 @@ GENERATE += $(TTF_DIR)/driver_tab.c
# This list must be consistent with PRE_LOADED_MODULES in
# erts/preloaded/src/Makefile.
-PRELOAD_BEAM = $(ERL_TOP)/erts/preloaded/ebin/otp_ring0.beam \
- $(ERL_TOP)/erts/preloaded/ebin/erts_code_purger.beam \
+PRELOAD_BEAM = $(ERL_TOP)/erts/preloaded/ebin/erts_code_purger.beam \
+ $(ERL_TOP)/erts/preloaded/ebin/erl_init.beam \
$(ERL_TOP)/erts/preloaded/ebin/init.beam \
$(ERL_TOP)/erts/preloaded/ebin/prim_buffer.beam \
$(ERL_TOP)/erts/preloaded/ebin/prim_eval.beam \
$(ERL_TOP)/erts/preloaded/ebin/prim_inet.beam \
$(ERL_TOP)/erts/preloaded/ebin/prim_file.beam \
$(ERL_TOP)/erts/preloaded/ebin/zlib.beam \
+ $(ERL_TOP)/erts/preloaded/ebin/socket.beam \
+ $(ERL_TOP)/erts/preloaded/ebin/net.beam \
$(ERL_TOP)/erts/preloaded/ebin/prim_zip.beam \
$(ERL_TOP)/erts/preloaded/ebin/erl_prim_loader.beam \
$(ERL_TOP)/erts/preloaded/ebin/erlang.beam \
@@ -832,6 +835,18 @@ EMU_OBJS = \
$(OBJDIR)/beam_catches.o $(OBJDIR)/code_ix.o \
$(OBJDIR)/beam_ranges.o
+ifneq ($(TARGET), win32)
+# These are *currently* only needed for non-win32,
+# since the nif-functions for socket and net are basically
+# stubbed with notsup in the win32 case.
+ESOCK_RUN_OBJS = \
+ $(OBJDIR)/socket_dbg.o \
+ $(OBJDIR)/socket_tarray.o \
+ $(OBJDIR)/socket_util.o
+else
+ESOCK_RUN_OBJS =
+endif
+
RUN_OBJS += \
$(OBJDIR)/erl_alloc.o $(OBJDIR)/erl_mtrace.o \
$(OBJDIR)/erl_alloc_util.o $(OBJDIR)/erl_goodfit_alloc.o \
@@ -864,7 +879,7 @@ RUN_OBJS += \
$(OBJDIR)/register.o $(OBJDIR)/break.o \
$(OBJDIR)/erl_async.o $(OBJDIR)/erl_lock_check.o \
$(OBJDIR)/erl_gc.o $(OBJDIR)/erl_lock_count.o \
- $(OBJDIR)/erl_posix_str.o \
+ $(OBJDIR)/erl_posix_str.o \
$(OBJDIR)/erl_bits.o $(OBJDIR)/erl_math.o \
$(OBJDIR)/erl_fun.o $(OBJDIR)/erl_bif_port.o \
$(OBJDIR)/erl_term.o $(OBJDIR)/erl_node_tables.o \
@@ -878,14 +893,18 @@ RUN_OBJS += \
$(OBJDIR)/erl_thr_queue.o $(OBJDIR)/erl_sched_spec_pre_alloc.o \
$(OBJDIR)/erl_ptab.o $(OBJDIR)/erl_map.o \
$(OBJDIR)/erl_msacc.o $(OBJDIR)/erl_lock_flags.o \
- $(OBJDIR)/erl_io_queue.o
+ $(OBJDIR)/erl_io_queue.o $(OBJDIR)/erl_db_catree.o \
+ $(ESOCK_RUN_OBJS)
LTTNG_OBJS = $(OBJDIR)/erlang_lttng.o
+
NIF_OBJS = \
- $(OBJDIR)/erl_tracer_nif.o \
- $(OBJDIR)/prim_buffer_nif.o \
- $(OBJDIR)/prim_file_nif.o \
- $(OBJDIR)/zlib_nif.o
+ $(OBJDIR)/erl_tracer_nif.o \
+ $(OBJDIR)/prim_buffer_nif.o \
+ $(OBJDIR)/prim_file_nif.o \
+ $(OBJDIR)/zlib_nif.o \
+ $(OBJDIR)/socket_nif.o \
+ $(OBJDIR)/net_nif.o
ifeq ($(TARGET),win32)
DRV_OBJS = \
@@ -1146,7 +1165,15 @@ endif
BEAM_SRC=$(wildcard beam/*.c)
DRV_COMMON_SRC=$(wildcard drivers/common/*.c)
DRV_OSTYPE_SRC=$(wildcard drivers/$(ERLANG_OSTYPE)/*.c)
+ifeq ($(TARGET), win32)
+# These are *currently* only needed for non-win32,
+# since the nif-functions for socket and net are basically
+# stubbed with badarg in the win32 case.
+NIF_SOCKET_UTILS_SRC=$(filter-out nifs/common/socket_nif.c, $(wildcard nifs/common/socket_*.c))
+NIF_COMMON_SRC=$(filter-out $(NIF_SOCKET_UTILS_SRC), $(wildcard nifs/common/*.c))
+else
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
diff --git a/erts/emulator/beam/arith_instrs.tab b/erts/emulator/beam/arith_instrs.tab
index b828e86788..574fceec5b 100644
--- a/erts/emulator/beam/arith_instrs.tab
+++ b/erts/emulator/beam/arith_instrs.tab
@@ -19,21 +19,22 @@
// %CopyrightEnd%
//
-OUTLINED_ARITH_2(Fail, Live, Name, BIF, Op1, Op2, Dst) {
+OUTLINED_ARITH_2(Fail, Name, BIF, Op1, Op2, Dst) {
Eterm result;
- Uint live = $Live;
- HEAVY_SWAPOUT;
- reg[live] = $Op1;
- reg[live+1] = $Op2;
- result = erts_gc_$Name (c_p, reg, live);
- HEAVY_SWAPIN;
+#ifdef DEBUG
+ Eterm* orig_htop = HTOP;
+ Eterm* orig_stop = E;
+#endif
+ DEBUG_SWAPOUT;
+ result = erts_$Name (c_p, $Op1, $Op2);
+ DEBUG_SWAPIN;
+ ASSERT(orig_htop == HTOP && orig_stop == E);
ERTS_HOLE_CHECK(c_p);
if (ERTS_LIKELY(is_value(result))) {
- $REFRESH_GEN_DEST();
$Dst = result;
$NEXT0();
}
- $BIF_ERROR_ARITY_2($Fail, $BIF, reg[live], reg[live+1]);
+ $BIF_ERROR_ARITY_2($Fail, $BIF, $Op1, $Op2);
}
@@ -48,7 +49,7 @@ plus.fetch(Op1, Op2) {
PlusOp2 = $Op2;
}
-plus.execute(Fail, Live, Dst) {
+plus.execute(Fail, Dst) {
if (ERTS_LIKELY(is_both_small(PlusOp1, PlusOp2))) {
Sint i = signed_val(PlusOp1) + signed_val(PlusOp2);
if (ERTS_LIKELY(IS_SSMALL(i))) {
@@ -56,7 +57,7 @@ plus.execute(Fail, Live, Dst) {
$NEXT0();
}
}
- $OUTLINED_ARITH_2($Fail, $Live, mixed_plus, BIF_splus_2, PlusOp1, PlusOp2, $Dst);
+ $OUTLINED_ARITH_2($Fail, mixed_plus, BIF_splus_2, PlusOp1, PlusOp2, $Dst);
}
i_minus := minus.fetch.execute;
@@ -70,7 +71,7 @@ minus.fetch(Op1, Op2) {
MinusOp2 = $Op2;
}
-minus.execute(Fail, Live, Dst) {
+minus.execute(Fail, Dst) {
if (ERTS_LIKELY(is_both_small(MinusOp1, MinusOp2))) {
Sint i = signed_val(MinusOp1) - signed_val(MinusOp2);
if (ERTS_LIKELY(IS_SSMALL(i))) {
@@ -78,7 +79,7 @@ minus.execute(Fail, Live, Dst) {
$NEXT0();
}
}
- $OUTLINED_ARITH_2($Fail, $Live, mixed_minus, BIF_sminus_2, MinusOp1, MinusOp2, $Dst);
+ $OUTLINED_ARITH_2($Fail, mixed_minus, BIF_sminus_2, MinusOp1, MinusOp2, $Dst);
}
i_increment := increment.fetch.execute;
@@ -91,9 +92,8 @@ increment.fetch(Src) {
increment_reg_val = $Src;
}
-increment.execute(IncrementVal, Live, Dst) {
+increment.execute(IncrementVal, Dst) {
Eterm increment_val = $IncrementVal;
- Uint live;
Eterm result;
if (ERTS_LIKELY(is_small(increment_reg_val))) {
@@ -103,15 +103,9 @@ increment.execute(IncrementVal, Live, Dst) {
$NEXT0();
}
}
- live = $Live;
- HEAVY_SWAPOUT;
- reg[live] = increment_reg_val;
- reg[live+1] = make_small(increment_val);
- result = erts_gc_mixed_plus(c_p, reg, live);
- HEAVY_SWAPIN;
+ result = erts_mixed_plus(c_p, increment_reg_val, make_small(increment_val));
ERTS_HOLE_CHECK(c_p);
if (ERTS_LIKELY(is_value(result))) {
- $REFRESH_GEN_DEST();
$Dst = result;
$NEXT0();
}
@@ -119,19 +113,19 @@ increment.execute(IncrementVal, Live, Dst) {
goto find_func_info;
}
-i_times(Fail, Live, Op1, Op2, Dst) {
+i_times(Fail, Op1, Op2, Dst) {
Eterm op1 = $Op1;
Eterm op2 = $Op2;
- $OUTLINED_ARITH_2($Fail, $Live, mixed_times, BIF_stimes_2, op1, op2, $Dst);
+ $OUTLINED_ARITH_2($Fail, mixed_times, BIF_stimes_2, op1, op2, $Dst);
}
-i_m_div(Fail, Live, Op1, Op2, Dst) {
+i_m_div(Fail, Op1, Op2, Dst) {
Eterm op1 = $Op1;
Eterm op2 = $Op2;
- $OUTLINED_ARITH_2($Fail, $Live, mixed_div, BIF_div_2, op1, op2, $Dst);
+ $OUTLINED_ARITH_2($Fail, mixed_div, BIF_div_2, op1, op2, $Dst);
}
-i_int_div(Fail, Live, Op1, Op2, Dst) {
+i_int_div(Fail, Op1, Op2, Dst) {
Eterm op1 = $Op1;
Eterm op2 = $Op2;
if (ERTS_UNLIKELY(op2 == SMALL_ZERO)) {
@@ -144,7 +138,7 @@ i_int_div(Fail, Live, Op1, Op2, Dst) {
$NEXT0();
}
}
- $OUTLINED_ARITH_2($Fail, $Live, int_div, BIF_intdiv_2, op1, op2, $Dst);
+ $OUTLINED_ARITH_2($Fail, int_div, BIF_intdiv_2, op1, op2, $Dst);
}
i_rem := rem.fetch.execute;
@@ -158,7 +152,7 @@ rem.fetch(Src1, Src2) {
RemOp2 = $Src2;
}
-rem.execute(Fail, Live, Dst) {
+rem.execute(Fail, Dst) {
if (ERTS_UNLIKELY(RemOp2 == SMALL_ZERO)) {
c_p->freason = BADARITH;
$BIF_ERROR_ARITY_2($Fail, BIF_rem_2, RemOp1, RemOp2);
@@ -166,7 +160,7 @@ rem.execute(Fail, Live, Dst) {
$Dst = make_small(signed_val(RemOp1) % signed_val(RemOp2));
$NEXT0();
} else {
- $OUTLINED_ARITH_2($Fail, $Live, int_rem, BIF_rem_2, RemOp1, RemOp2, $Dst);
+ $OUTLINED_ARITH_2($Fail, int_rem, BIF_rem_2, RemOp1, RemOp2, $Dst);
}
}
@@ -181,7 +175,7 @@ band.fetch(Src1, Src2) {
BandOp2 = $Src2;
}
-band.execute(Fail, Live, Dst) {
+band.execute(Fail, Dst) {
if (ERTS_LIKELY(is_both_small(BandOp1, BandOp2))) {
/*
* No need to untag -- TAG & TAG == TAG.
@@ -189,10 +183,10 @@ band.execute(Fail, Live, Dst) {
$Dst = BandOp1 & BandOp2;
$NEXT0();
}
- $OUTLINED_ARITH_2($Fail, $Live, band, BIF_band_2, BandOp1, BandOp2, $Dst);
+ $OUTLINED_ARITH_2($Fail, band, BIF_band_2, BandOp1, BandOp2, $Dst);
}
-i_bor(Fail, Live, Src1, Src2, Dst) {
+i_bor(Fail, Src1, Src2, Dst) {
if (ERTS_LIKELY(is_both_small($Src1, $Src2))) {
/*
* No need to untag -- TAG | TAG == TAG.
@@ -200,10 +194,10 @@ i_bor(Fail, Live, Src1, Src2, Dst) {
$Dst = $Src1 | $Src2;
$NEXT0();
}
- $OUTLINED_ARITH_2($Fail, $Live, bor, BIF_bor_2, $Src1, $Src2, $Dst);
+ $OUTLINED_ARITH_2($Fail, bor, BIF_bor_2, $Src1, $Src2, $Dst);
}
-i_bxor(Fail, Live, Src1, Src2, Dst) {
+i_bxor(Fail, Src1, Src2, Dst) {
if (ERTS_LIKELY(is_both_small($Src1, $Src2))) {
/*
* TAG ^ TAG == 0.
@@ -214,7 +208,7 @@ i_bxor(Fail, Live, Src1, Src2, Dst) {
$Dst = ($Src1 ^ $Src2) | make_small(0);
$NEXT0();
}
- $OUTLINED_ARITH_2($Fail, $Live, bxor, BIF_bxor_2, $Src1, $Src2, $Dst);
+ $OUTLINED_ARITH_2($Fail, bxor, BIF_bxor_2, $Src1, $Src2, $Dst);
}
i_bsl := shift.setup_bsl.execute;
@@ -265,7 +259,7 @@ shift.setup_bsl(Src1, Src2) {
}
}
-shift.execute(Fail, Live, Dst) {
+shift.execute(Fail, Dst) {
Uint big_words_needed;
if (ERTS_LIKELY(is_small(Op1))) {
@@ -320,7 +314,9 @@ shift.execute(Fail, Live, Dst) {
}
{
Eterm tmp_big[2];
- Sint big_need_size = BIG_NEED_SIZE(big_words_needed+1);
+ Sint big_need_size = 1 + BIG_NEED_SIZE(big_words_needed+1);
+ Eterm* hp;
+ Eterm* hp_end;
/*
* Slightly conservative check the size to avoid
@@ -331,15 +327,16 @@ shift.execute(Fail, Live, Dst) {
if (big_need_size-8 > BIG_ARITY_MAX) {
$SYSTEM_LIMIT($Fail);
}
- $GC_TEST_PRESERVE(big_need_size+1, $Live, Op1);
+ hp = HeapFragOnlyAlloc(c_p, big_need_size);
if (is_small(Op1)) {
Op1 = small_to_big(signed_val(Op1), tmp_big);
}
- Op1 = big_lshift(Op1, shift_left_count, HTOP);
+ Op1 = big_lshift(Op1, shift_left_count, hp);
+ hp_end = hp + big_need_size;
if (is_big(Op1)) {
- HTOP += bignum_header_arity(*HTOP) + 1;
+ hp += bignum_header_arity(*hp) + 1;
}
- HEAP_SPACE_VERIFIED(0);
+ HRelease(c_p, hp_end, hp);
if (ERTS_UNLIKELY(is_nil(Op1))) {
/*
* This result must have been only slighty larger
@@ -349,7 +346,6 @@ shift.execute(Fail, Live, Dst) {
$SYSTEM_LIMIT($Fail);
}
ERTS_HOLE_CHECK(c_p);
- $REFRESH_GEN_DEST();
$Dst = Op1;
$NEXT0();
}
@@ -366,31 +362,28 @@ shift.execute(Fail, Live, Dst) {
reg[0] = Op1;
reg[1] = Op2;
SWAPOUT;
- if (IsOpCode(I[0], i_bsl_ssjtd)) {
+ if (IsOpCode(I[0], i_bsl_ssjd)) {
I = handle_error(c_p, I, reg, &bif_export[BIF_bsl_2]->info.mfa);
} else {
- ASSERT(IsOpCode(I[0], i_bsr_ssjtd));
+ ASSERT(IsOpCode(I[0], i_bsr_ssjd));
I = handle_error(c_p, I, reg, &bif_export[BIF_bsr_2]->info.mfa);
}
goto post_error_handling;
}
}
-i_int_bnot(Fail, Src, Live, Dst) {
+i_int_bnot(Fail, Src, Dst) {
Eterm bnot_val = $Src;
+ Eterm result;
+
if (ERTS_LIKELY(is_small(bnot_val))) {
- bnot_val = make_small(~signed_val(bnot_val));
+ result = make_small(~signed_val(bnot_val));
} else {
- Uint live = $Live;
- HEAVY_SWAPOUT;
- reg[live] = bnot_val;
- bnot_val = erts_gc_bnot(c_p, reg, live);
- HEAVY_SWAPIN;
+ result = erts_bnot(c_p, bnot_val);
ERTS_HOLE_CHECK(c_p);
- if (ERTS_UNLIKELY(is_nil(bnot_val))) {
- $BIF_ERROR_ARITY_1($Fail, BIF_bnot_1, reg[live]);
+ if (ERTS_UNLIKELY(is_nil(result))) {
+ $BIF_ERROR_ARITY_1($Fail, BIF_bnot_1, bnot_val);
}
- $REFRESH_GEN_DEST();
}
- $Dst = bnot_val;
+ $Dst = result;
}
diff --git a/erts/emulator/beam/atom.names b/erts/emulator/beam/atom.names
index a14f22b19e..f81082a698 100644
--- a/erts/emulator/beam/atom.names
+++ b/erts/emulator/beam/atom.names
@@ -209,7 +209,6 @@ atom dirty_nif_finalizer
atom disable_trace
atom disabled
atom discard
-atom display_items
atom dist
atom dist_cmd
atom dist_ctrl_put_data
@@ -238,6 +237,7 @@ atom eof
atom eol
atom Eq='=:='
atom Eqeq='=='
+atom erl_init
atom erl_tracer
atom erlang
atom erl_signal_server
@@ -546,6 +546,7 @@ atom reload
atom rem
atom report_errors
atom reset
+atom reset_seq_trace
atom restart
atom return_from
atom return_to
diff --git a/erts/emulator/beam/beam_debug.c b/erts/emulator/beam/beam_debug.c
index 9633de2021..f71efd708f 100644
--- a/erts/emulator/beam/beam_debug.c
+++ b/erts/emulator/beam/beam_debug.c
@@ -558,23 +558,6 @@ print_op(fmtfn_t to, void *to_arg, int op, int size, BeamInstr* addr)
case 'I':
case 'W':
switch (op) {
- case op_i_gc_bif1_jWstd:
- case op_i_gc_bif2_jWtssd:
- case op_i_gc_bif3_jWtssd:
- {
- const ErtsGcBif* p;
- BifFunction gcf = (BifFunction) *ap;
- for (p = erts_gc_bifs; p->bif != 0; p++) {
- if (p->gc_bif == gcf) {
- print_bif_name(to, to_arg, p->bif);
- break;
- }
- }
- if (p->bif == 0) {
- erts_print(to, to_arg, "%d", (Uint)gcf);
- }
- break;
- }
case op_i_make_fun_Wt:
if (*sign == 'W') {
ErlFunEntry* fe = (ErlFunEntry *) *ap;
@@ -786,8 +769,8 @@ print_op(fmtfn_t to, void *to_arg, int op, int size, BeamInstr* addr)
}
}
break;
- case op_i_put_tuple_xI:
- case op_i_put_tuple_yI:
+ case op_put_tuple2_xI:
+ case op_put_tuple2_yI:
case op_new_map_dtI:
case op_update_map_assoc_sdtI:
case op_update_map_exact_jsdtI:
diff --git a/erts/emulator/beam/beam_emu.c b/erts/emulator/beam/beam_emu.c
index e909a0b4da..90162a6543 100644
--- a/erts/emulator/beam/beam_emu.c
+++ b/erts/emulator/beam/beam_emu.c
@@ -206,8 +206,12 @@ void** beam_ops;
#ifdef DEBUG
# /* The stack pointer is used in an assertion. */
# define LIGHT_SWAPOUT SWAPOUT
+# define DEBUG_SWAPOUT SWAPOUT
+# define DEBUG_SWAPIN SWAPIN
#else
# define LIGHT_SWAPOUT HEAP_TOP(c_p) = HTOP
+# define DEBUG_SWAPOUT
+# define DEBUG_SWAPIN
#endif
/*
@@ -386,7 +390,6 @@ do { \
*/
static void init_emulator_finish(void) NOINLINE;
static ErtsCodeMFA *ubif2mfa(void* uf) NOINLINE;
-static ErtsCodeMFA *gcbif2mfa(void* gcf) NOINLINE;
static BeamInstr* handle_error(Process* c_p, BeamInstr* pc,
Eterm* reg, ErtsCodeMFA* bif_mfa) NOINLINE;
static BeamInstr* call_error_handler(Process* p, ErtsCodeMFA* mfa,
@@ -401,6 +404,7 @@ static BeamInstr* apply_fun(Process* p, Eterm fun,
Eterm args, Eterm* reg) NOINLINE;
static Eterm new_fun(Process* p, Eterm* reg,
ErlFunEntry* fe, int num_free) NOINLINE;
+static int is_function2(Eterm Term, Uint arity);
static Eterm erts_gc_new_map(Process* p, Eterm* reg, Uint live,
Uint n, BeamInstr* ptr) NOINLINE;
static Eterm erts_gc_new_small_map_lit(Process* p, Eterm* reg, Eterm keys_literal,
@@ -881,19 +885,22 @@ void process_main(Eterm * x_reg_array, FloatDef* f_reg_array)
#include "beam_warm.h"
OpCase(normal_exit): {
- SWAPOUT;
+ HEAVY_SWAPOUT;
c_p->freason = EXC_NORMAL;
c_p->arity = 0; /* In case this process will ever be garbed again. */
ERTS_UNREQ_PROC_MAIN_LOCK(c_p);
erts_do_exit_process(c_p, am_normal);
ERTS_REQ_PROC_MAIN_LOCK(c_p);
+ HEAVY_SWAPIN;
goto do_schedule;
}
OpCase(continue_exit): {
+ HEAVY_SWAPOUT;
ERTS_UNREQ_PROC_MAIN_LOCK(c_p);
erts_continue_exit_process(c_p);
ERTS_REQ_PROC_MAIN_LOCK(c_p);
+ HEAVY_SWAPIN;
goto do_schedule;
}
@@ -1301,18 +1308,6 @@ void erts_dirty_process_main(ErtsSchedulerData *esdp)
}
static ErtsCodeMFA *
-gcbif2mfa(void* gcf)
-{
- int i;
- for (i = 0; erts_gc_bifs[i].bif; i++) {
- if (erts_gc_bifs[i].gc_bif == gcf)
- return &bif_export[erts_gc_bifs[i].exp_ix]->info.mfa;
- }
- erts_exit(ERTS_ERROR_EXIT, "bad gc bif");
- return NULL;
-}
-
-static ErtsCodeMFA *
ubif2mfa(void* uf)
{
int i;
@@ -1320,7 +1315,7 @@ ubif2mfa(void* uf)
if (erts_u_bifs[i].bif == uf)
return &bif_export[erts_u_bifs[i].exp_ix]->info.mfa;
}
- erts_exit(ERTS_ERROR_EXIT, "bad u bif");
+ erts_exit(ERTS_ERROR_EXIT, "bad u bif: %p\n", uf);
return NULL;
}
@@ -2671,6 +2666,19 @@ new_fun(Process* p, Eterm* reg, ErlFunEntry* fe, int num_free)
return make_fun(funp);
}
+static int
+is_function2(Eterm Term, Uint arity)
+{
+ if (is_fun(Term)) {
+ ErlFunThing* funp = (ErlFunThing *) fun_val(Term);
+ return funp->arity == arity;
+ } else if (is_export(Term)) {
+ Export* exp = (Export *) (export_val(Term)[1]);
+ return exp->info.mfa.arity == arity;
+ }
+ return 0;
+}
+
static Eterm get_map_element(Eterm map, Eterm key)
{
Uint32 hx;
@@ -3062,12 +3070,14 @@ erts_gc_update_map_exact(Process* p, Eterm* reg, Uint live, Uint n, Eterm* new_p
Uint need;
flatmap_t *old_mp, *mp;
Eterm res;
+ Eterm* old_hp;
Eterm* hp;
Eterm* E;
Eterm* old_keys;
Eterm* old_vals;
Eterm new_key;
Eterm map;
+ int changed = 0;
n /= 2; /* Number of values to be updated */
ASSERT(n > 0);
@@ -3134,6 +3144,7 @@ erts_gc_update_map_exact(Process* p, Eterm* reg, Uint live, Uint n, Eterm* new_p
* Update map, keeping the old key tuple.
*/
+ old_hp = p->htop;
hp = p->htop;
E = p->stop;
@@ -3156,20 +3167,26 @@ erts_gc_update_map_exact(Process* p, Eterm* reg, Uint live, Uint n, Eterm* new_p
/* Not same keys */
*hp++ = *old_vals;
} else {
- GET_TERM(new_p[1], *hp);
- hp++;
- n--;
+ GET_TERM(new_p[1], *hp);
+ if(*hp != *old_vals) changed = 1;
+ hp++;
+ n--;
if (n == 0) {
- /*
- * All updates done. Copy remaining values
- * and return the result.
- */
- for (i++, old_vals++; i < num_old; i++) {
- *hp++ = *old_vals++;
- }
- ASSERT(hp == p->htop + need);
- p->htop = hp;
- return res;
+ /*
+ * All updates done. Copy remaining values
+ * if any changed or return the original one.
+ */
+ if(changed) {
+ for (i++, old_vals++; i < num_old; i++) {
+ *hp++ = *old_vals++;
+ }
+ ASSERT(hp == p->htop + need);
+ p->htop = hp;
+ return res;
+ } else {
+ p->htop = old_hp;
+ return map;
+ }
} else {
new_p += 2;
GET_TERM(*new_p, new_key);
diff --git a/erts/emulator/beam/beam_load.c b/erts/emulator/beam/beam_load.c
index e61199a8fd..f4eeb54a1b 100644
--- a/erts/emulator/beam/beam_load.c
+++ b/erts/emulator/beam/beam_load.c
@@ -2868,6 +2868,7 @@ load_code(LoaderState* stp)
break;
case op_bs_put_string_WW:
case op_i_bs_match_string_xfWW:
+ case op_i_bs_match_string_yfWW:
new_string_patch(stp, ci-1);
break;
@@ -2969,6 +2970,8 @@ load_code(LoaderState* stp)
#define succ(St, X, Y) ((X).type == (Y).type && (X).val + 1 == (Y).val)
#define succ2(St, X, Y) ((X).type == (Y).type && (X).val + 2 == (Y).val)
#define succ3(St, X, Y) ((X).type == (Y).type && (X).val + 3 == (Y).val)
+#define succ4(St, X, Y) ((X).type == (Y).type && (X).val + 4 == (Y).val)
+
#ifdef NO_FPE_SIGNALS
#define no_fpe_signals(St) 1
@@ -2985,6 +2988,35 @@ compiled_with_otp_20_or_higher(LoaderState* stp)
}
/*
+ * Predicate that tests whether the following two moves are independent:
+ *
+ * move Src1 Dst1
+ * move Src2 Dst2
+ *
+ */
+static int
+independent_moves(LoaderState* stp, GenOpArg Src1, GenOpArg Dst1,
+ GenOpArg Src2, GenOpArg Dst2)
+{
+ return (Src1.type != Dst2.type || Src1.val != Dst2.val) &&
+ (Src2.type != Dst1.type || Src2.val != Dst1.val) &&
+ (Dst1.type != Dst2.type ||Dst1.val != Dst2.val);
+}
+
+/*
+ * Predicate that tests that two registers are distinct.
+ *
+ * move Src1 Dst1
+ * move Src2 Dst2
+ *
+ */
+static int
+distinct(LoaderState* stp, GenOpArg Reg1, GenOpArg Reg2)
+{
+ return Reg1.type != Reg2.type || Reg1.val != Reg2.val;
+}
+
+/*
* Predicate that tests whether a jump table can be used.
*/
@@ -3579,38 +3611,36 @@ gen_skip_bits2(LoaderState* stp, GenOpArg Fail, GenOpArg Ms,
}
static GenOp*
-gen_increment(LoaderState* stp, GenOpArg Reg, GenOpArg Integer,
- GenOpArg Live, GenOpArg Dst)
+gen_increment(LoaderState* stp, GenOpArg Reg,
+ GenOpArg Integer, GenOpArg Dst)
{
GenOp* op;
NEW_GENOP(stp, op);
- op->op = genop_i_increment_4;
- op->arity = 4;
+ op->op = genop_i_increment_3;
+ op->arity = 3;
op->next = NULL;
op->a[0] = Reg;
op->a[1].type = TAG_u;
op->a[1].val = Integer.val;
- op->a[2] = Live;
- op->a[3] = Dst;
+ op->a[2] = Dst;
return op;
}
static GenOp*
-gen_increment_from_minus(LoaderState* stp, GenOpArg Reg, GenOpArg Integer,
- GenOpArg Live, GenOpArg Dst)
+gen_increment_from_minus(LoaderState* stp, GenOpArg Reg,
+ GenOpArg Integer, GenOpArg Dst)
{
GenOp* op;
NEW_GENOP(stp, op);
- op->op = genop_i_increment_4;
- op->arity = 4;
+ op->op = genop_i_increment_3;
+ op->arity = 3;
op->next = NULL;
op->a[0] = Reg;
op->a[1].type = TAG_u;
op->a[1].val = -Integer.val;
- op->a[2] = Live;
- op->a[3] = Dst;
+ op->a[2] = Dst;
return op;
}
@@ -4245,115 +4275,123 @@ gen_make_fun2(LoaderState* stp, GenOpArg idx)
{
ErlFunEntry* fe;
GenOp* op;
+ Uint arity, num_free;
if (idx.val >= stp->num_lambdas) {
- stp->lambda_error = "missing or short chunk 'FunT'";
- fe = 0;
+ stp->lambda_error = "missing or short chunk 'FunT'";
+ fe = 0;
+ num_free = 0;
+ arity = 0;
} else {
- fe = stp->lambdas[idx.val].fe;
+ fe = stp->lambdas[idx.val].fe;
+ num_free = stp->lambdas[idx.val].num_free;
+ arity = fe->arity;
}
NEW_GENOP(stp, op);
- op->op = genop_i_make_fun_2;
- op->arity = 2;
- op->a[0].type = TAG_u;
- op->a[0].val = (BeamInstr) fe;
- op->a[1].type = TAG_u;
- op->a[1].val = stp->lambdas[idx.val].num_free;
- op->next = NULL;
- return op;
-}
-static GenOp*
-translate_gc_bif(LoaderState* stp, GenOp* op, GenOpArg Bif)
-{
- const ErtsGcBif* p;
- BifFunction bf;
-
- bf = stp->import[Bif.val].bf;
- for (p = erts_gc_bifs; p->bif != 0; p++) {
- if (p->bif == bf) {
- op->a[1].type = TAG_u;
- op->a[1].val = (BeamInstr) p->gc_bif;
- return op;
- }
+ /*
+ * It's possible this is called before init process is started,
+ * skip the optimisation in such case.
+ */
+ if (num_free == 0 && erts_init_process_id != ERTS_INVALID_PID) {
+ Uint lit;
+ Eterm* hp;
+ ErlFunThing* funp;
+
+ lit = new_literal(stp, &hp, ERL_FUN_SIZE);
+ funp = (ErlFunThing *) hp;
+ erts_refc_inc(&fe->refc, 2);
+ funp->thing_word = HEADER_FUN;
+ funp->next = NULL;
+ funp->fe = fe;
+ funp->num_free = 0;
+ funp->creator = erts_init_process_id;
+ funp->arity = arity;
+
+ op->op = genop_move_2;
+ op->arity = 2;
+ op->a[0].type = TAG_q;
+ op->a[0].val = lit;
+ op->a[1].type = TAG_x;
+ op->a[1].val = 0;
+ } else {
+ op->op = genop_i_make_fun_2;
+ op->arity = 2;
+ op->a[0].type = TAG_u;
+ op->a[0].val = (BeamInstr) fe;
+ op->a[1].type = TAG_u;
+ op->a[1].val = num_free;
}
- op->op = genop_unsupported_guard_bif_3;
- op->arity = 3;
- op->a[0].type = TAG_a;
- op->a[0].val = stp->import[Bif.val].module;
- op->a[1].type = TAG_a;
- op->a[1].val = stp->import[Bif.val].function;
- op->a[2].type = TAG_u;
- op->a[2].val = stp->import[Bif.val].arity;
- return op;
-}
-
-/*
- * Rewrite gc_bifs with one parameter (the common case).
- */
-static GenOp*
-gen_guard_bif1(LoaderState* stp, GenOpArg Fail, GenOpArg Live, GenOpArg Bif,
- GenOpArg Src, GenOpArg Dst)
-{
- GenOp* op;
-
- NEW_GENOP(stp, op);
op->next = NULL;
- op->op = genop_i_gc_bif1_5;
- op->arity = 5;
- op->a[0] = Fail;
- /* op->a[1] is set by translate_gc_bif() */
- op->a[2] = Src;
- op->a[3] = Live;
- op->a[4] = Dst;
- return translate_gc_bif(stp, op, Bif);
+ return op;
}
-/*
- * This is used by the ops.tab rule that rewrites gc_bifs with two parameters.
- */
static GenOp*
-gen_guard_bif2(LoaderState* stp, GenOpArg Fail, GenOpArg Live, GenOpArg Bif,
- GenOpArg S1, GenOpArg S2, GenOpArg Dst)
+gen_is_function2(LoaderState* stp, GenOpArg Fail, GenOpArg Fun, GenOpArg Arity)
{
GenOp* op;
+ int literal_arity = Arity.type == TAG_i;
+ int fun_is_reg = Fun.type == TAG_x || Fun.type == TAG_y;
NEW_GENOP(stp, op);
op->next = NULL;
- op->op = genop_i_gc_bif2_6;
- op->arity = 6;
- op->a[0] = Fail;
- /* op->a[1] is set by translate_gc_bif() */
- op->a[2] = Live;
- op->a[3] = S1;
- op->a[4] = S2;
- op->a[5] = Dst;
- return translate_gc_bif(stp, op, Bif);
-}
-/*
- * This is used by the ops.tab rule that rewrites gc_bifs with three parameters.
- */
-static GenOp*
-gen_guard_bif3(LoaderState* stp, GenOpArg Fail, GenOpArg Live, GenOpArg Bif,
- GenOpArg S1, GenOpArg S2, GenOpArg S3, GenOpArg Dst)
-{
- GenOp* op;
-
- NEW_GENOP(stp, op);
- op->next = NULL;
- op->op = genop_ii_gc_bif3_7;
- op->arity = 7;
- op->a[0] = Fail;
- /* op->a[1] is set by translate_gc_bif() */
- op->a[2] = Live;
- op->a[3] = S1;
- op->a[4] = S2;
- op->a[5] = S3;
- op->a[6] = Dst;
- return translate_gc_bif(stp, op, Bif);
+ if (fun_is_reg &&literal_arity) {
+ /*
+ * Most common case. Fun in a register and arity
+ * is an integer literal.
+ */
+ if (Arity.val > MAX_ARG) {
+ /* Arity is negative or too big. */
+ op->op = genop_jump_1;
+ op->arity = 1;
+ op->a[0] = Fail;
+ return op;
+ } else {
+ op->op = genop_hot_is_function2_3;
+ op->arity = 3;
+ op->a[0] = Fail;
+ op->a[1] = Fun;
+ op->a[2].type = TAG_u;
+ op->a[2].val = Arity.val;
+ return op;
+ }
+ } else {
+ /*
+ * Handle extremely uncommon cases by a slower sequence.
+ */
+ GenOp* move_fun;
+ GenOp* move_arity;
+
+ NEW_GENOP(stp, move_fun);
+ NEW_GENOP(stp, move_arity);
+
+ move_fun->next = move_arity;
+ move_arity->next = op;
+
+ move_fun->arity = 2;
+ move_fun->op = genop_move_2;
+ move_fun->a[0] = Fun;
+ move_fun->a[1].type = TAG_x;
+ move_fun->a[1].val = 1022;
+
+ move_arity->arity = 2;
+ move_arity->op = genop_move_2;
+ move_arity->a[0] = Arity;
+ move_arity->a[1].type = TAG_x;
+ move_arity->a[1].val = 1023;
+
+ op->op = genop_cold_is_function2_3;
+ op->arity = 3;
+ op->a[0] = Fail;
+ op->a[1].type = TAG_x;
+ op->a[1].val = 1022;
+ op->a[2].type = TAG_x;
+ op->a[2].val = 1023;
+ return move_fun;
+ }
}
static GenOp*
@@ -4522,19 +4560,6 @@ is_empty_map(LoaderState* stp, GenOpArg Lit)
}
/*
- * Predicate to test whether the given literal is an export.
- */
-static int
-literal_is_export(LoaderState* stp, GenOpArg Lit)
-{
- Eterm term;
-
- ASSERT(Lit.type == TAG_q);
- term = stp->literals[Lit.val].term;
- return is_export(term);
-}
-
-/*
* Pseudo predicate map_key_sort that will sort the Rest operand for
* map instructions as a side effect.
*/
@@ -6114,7 +6139,8 @@ erts_release_literal_area(ErtsLiteralArea* literal_area)
}
default:
ASSERT(is_external_header(oh->thing_word));
- erts_deref_node_entry(((ExternalThing*)oh)->node);
+ erts_deref_node_entry(((ExternalThing*)oh)->node,
+ make_boxed(&oh->thing_word));
}
oh = oh->next;
}
diff --git a/erts/emulator/beam/bif.c b/erts/emulator/beam/bif.c
index 015c051cc1..7faba35e1c 100644
--- a/erts/emulator/beam/bif.c
+++ b/erts/emulator/beam/bif.c
@@ -184,7 +184,7 @@ BIF_RETTYPE link_1(BIF_ALIST_1)
DistEntry *dep;
ErtsLink *lnk;
int code;
- ErtsDSigData dsd;
+ ErtsDSigSendContext ctx;
dep = external_pid_dist_entry(BIF_ARG_1);
if (dep == erts_this_dist_entry)
@@ -201,9 +201,9 @@ BIF_RETTYPE link_1(BIF_ALIST_1)
ldp = erts_link_to_data(lnk);
- code = erts_dsig_prepare(&dsd, dep, BIF_P,
+ code = erts_dsig_prepare(&ctx, dep, BIF_P,
ERTS_PROC_LOCK_MAIN,
- ERTS_DSP_RLOCK, 0, 1);
+ ERTS_DSP_RLOCK, 0, 1, 1);
switch (code) {
case ERTS_DSIG_PREP_NOT_ALIVE:
case ERTS_DSIG_PREP_NOT_CONNECTED:
@@ -218,16 +218,14 @@ BIF_RETTYPE link_1(BIF_ALIST_1)
* We have (pending) connection.
* Setup link and enqueue link signal.
*/
-#ifdef DEBUG
- int inserted =
-#endif
- erts_link_dist_insert(&ldp->b, dep->mld);
- ASSERT(inserted);
+ int inserted = erts_link_dist_insert(&ldp->b, dep->mld);
+ ASSERT(inserted); (void)inserted;
erts_de_runlock(dep);
- code = erts_dsig_send_link(&dsd, BIF_P->common.id, BIF_ARG_1);
+ code = erts_dsig_send_link(&ctx, BIF_P->common.id, BIF_ARG_1);
if (code == ERTS_DSIG_SEND_YIELD)
ERTS_BIF_YIELD_RETURN(BIF_P, am_true);
+ ASSERT(code == ERTS_DSIG_SEND_OK);
BIF_RET(am_true);
break;
}
@@ -309,7 +307,7 @@ demonitor(Process *c_p, Eterm ref, Eterm *multip)
DistEntry *dep;
int code = ERTS_DSIG_SEND_OK;
int deleted;
- ErtsDSigData dsd;
+ ErtsDSigSendContext ctx;
ASSERT(is_external_pid(to) || is_node_name_atom(to));
@@ -325,8 +323,8 @@ demonitor(Process *c_p, Eterm ref, Eterm *multip)
}
}
- code = erts_dsig_prepare(&dsd, dep, c_p, ERTS_PROC_LOCK_MAIN,
- ERTS_DSP_RLOCK, 0, 0);
+ code = erts_dsig_prepare(&ctx, dep, c_p, ERTS_PROC_LOCK_MAIN,
+ ERTS_DSP_RLOCK, 0, 1, 0);
deleted = erts_monitor_dist_delete(&mdp->target);
@@ -355,8 +353,8 @@ demonitor(Process *c_p, Eterm ref, Eterm *multip)
* monitor list since in case of monitor name
* the atom is stored there. Yield if necessary.
*/
- code = erts_dsig_send_demonitor(&dsd, c_p->common.id,
- watched, mdp->ref, 0);
+ code = erts_dsig_send_demonitor(&ctx, c_p->common.id,
+ watched, mdp->ref);
break;
}
@@ -536,7 +534,7 @@ BIF_RETTYPE monitor_2(BIF_ALIST_2)
}
if (is_external_pid(target)) {
- ErtsDSigData dsd;
+ ErtsDSigSendContext ctx;
int code;
dep = external_pid_dist_entry(target);
@@ -554,9 +552,9 @@ BIF_RETTYPE monitor_2(BIF_ALIST_2)
BIF_P->common.id, id, name);
erts_monitor_tree_insert(&ERTS_P_MONITORS(BIF_P), &mdp->origin);
- code = erts_dsig_prepare(&dsd, dep,
+ code = erts_dsig_prepare(&ctx, dep,
BIF_P, ERTS_PROC_LOCK_MAIN,
- ERTS_DSP_RLOCK, 0, 1);
+ ERTS_DSP_RLOCK, 0, 1, 1);
switch (code) {
case ERTS_DSIG_PREP_NOT_ALIVE:
case ERTS_DSIG_PREP_NOT_CONNECTED:
@@ -567,15 +565,11 @@ BIF_RETTYPE monitor_2(BIF_ALIST_2)
case ERTS_DSIG_PREP_PENDING:
case ERTS_DSIG_PREP_CONNECTED: {
-#ifdef DEBUG
- int inserted =
-#endif
-
- erts_monitor_dist_insert(&mdp->target, dep->mld);
- ASSERT(inserted);
+ int inserted = erts_monitor_dist_insert(&mdp->target, dep->mld);
+ ASSERT(inserted); (void)inserted;
erts_de_runlock(dep);
- code = erts_dsig_send_monitor(&dsd, BIF_P->common.id, target, ref);
+ code = erts_dsig_send_monitor(&ctx, BIF_P->common.id, target, ref);
break;
}
@@ -921,7 +915,7 @@ BIF_RETTYPE unlink_1(BIF_ALIST_1)
ErtsLinkData *ldp;
DistEntry *dep;
int code;
- ErtsDSigData dsd;
+ ErtsDSigSendContext ctx;
dep = external_pid_dist_entry(BIF_ARG_1);
if (dep == erts_this_dist_entry)
@@ -939,15 +933,15 @@ BIF_RETTYPE unlink_1(BIF_ALIST_1)
else
erts_link_release(lnk);
- code = erts_dsig_prepare(&dsd, dep, BIF_P, ERTS_PROC_LOCK_MAIN,
- ERTS_DSP_NO_LOCK, 0, 0);
+ code = erts_dsig_prepare(&ctx, dep, BIF_P, ERTS_PROC_LOCK_MAIN,
+ ERTS_DSP_NO_LOCK, 0, 1, 0);
switch (code) {
case ERTS_DSIG_PREP_NOT_ALIVE:
case ERTS_DSIG_PREP_NOT_CONNECTED:
BIF_RET(am_true);
case ERTS_DSIG_PREP_PENDING:
case ERTS_DSIG_PREP_CONNECTED:
- code = erts_dsig_send_unlink(&dsd, BIF_P->common.id, BIF_ARG_1);
+ code = erts_dsig_send_unlink(&ctx, BIF_P->common.id, BIF_ARG_1);
if (code == ERTS_DSIG_SEND_YIELD)
ERTS_BIF_YIELD_RETURN(BIF_P, am_true);
break;
@@ -1308,10 +1302,11 @@ static BIF_RETTYPE send_exit_signal_bif(Process *c_p, Eterm id, Eterm reason, in
ERTS_BIF_PREP_RET(ret_val, am_true); /* Old incarnation of this node... */
else {
int code;
- ErtsDSigData dsd;
+ ErtsDSigSendContext ctx;
+
+ code = erts_dsig_prepare(&ctx, dep, c_p, ERTS_PROC_LOCK_MAIN,
+ ERTS_DSP_NO_LOCK, 0, 0, 1);
- code = erts_dsig_prepare(&dsd, dep, c_p, ERTS_PROC_LOCK_MAIN,
- ERTS_DSP_NO_LOCK, 0, 1);
switch (code) {
case ERTS_DSIG_PREP_NOT_ALIVE:
case ERTS_DSIG_PREP_NOT_CONNECTED:
@@ -1319,11 +1314,29 @@ static BIF_RETTYPE send_exit_signal_bif(Process *c_p, Eterm id, Eterm reason, in
break;
case ERTS_DSIG_PREP_PENDING:
case ERTS_DSIG_PREP_CONNECTED:
- code = erts_dsig_send_exit2(&dsd, c_p->common.id, id, reason);
- if (code == ERTS_DSIG_SEND_YIELD)
+ code = erts_dsig_send_exit2(&ctx, c_p->common.id, id, reason);
+ switch (code) {
+ case ERTS_DSIG_SEND_YIELD:
ERTS_BIF_PREP_YIELD_RETURN(ret_val, c_p, am_true);
- else
+ break;
+ case ERTS_DSIG_SEND_CONTINUE:
+ BUMP_ALL_REDS(c_p);
+ erts_set_gc_state(c_p, 0);
+ ERTS_BIF_PREP_TRAP1(ret_val, &dsend_continue_trap_export, c_p,
+ erts_dsend_export_trap_context(c_p, &ctx));
+ break;
+ case ERTS_DSIG_SEND_OK:
ERTS_BIF_PREP_RET(ret_val, am_true);
+ break;
+ case ERTS_DSIG_SEND_TOO_LRG:
+ erts_set_gc_state(c_p, 1);
+ ERTS_BIF_PREP_ERROR(ret_val, c_p, SYSTEM_LIMIT);
+ break;
+ default:
+ ASSERT(! "Invalid dsig send exit2 result");
+ ERTS_BIF_PREP_ERROR(ret_val, c_p, EXC_INTERNAL_ERROR);
+ break;
+ }
break;
default:
ASSERT(! "Invalid dsig prepare result");
@@ -1803,36 +1816,40 @@ ebif_bang_2(BIF_ALIST_2)
#define SEND_INTERNAL_ERROR (-6)
#define SEND_AWAIT_RESULT (-7)
#define SEND_YIELD_CONTINUE (-8)
+#define SEND_SYSTEM_LIMIT (-9)
static Sint remote_send(Process *p, DistEntry *dep,
- Eterm to, Eterm full_to, Eterm msg,
- ErtsSendContext* ctx)
+ Eterm to, Eterm node, Eterm full_to, Eterm msg,
+ Eterm return_term, Eterm *ctxpp,
+ int connect, int suspend)
{
Sint res;
int code;
+ ErtsDSigSendContext ctx;
ASSERT(is_atom(to) || is_external_pid(to));
- ctx->dep = dep;
- code = erts_dsig_prepare(&ctx->dsd, dep, p, ERTS_PROC_LOCK_MAIN,
+ code = erts_dsig_prepare(&ctx, dep, p, ERTS_PROC_LOCK_MAIN,
ERTS_DSP_NO_LOCK,
- !ctx->suspend, ctx->connect);
+ !suspend, 0, connect);
+ ctx.return_term = return_term;
+ ctx.node = node;
switch (code) {
case ERTS_DSIG_PREP_NOT_ALIVE:
case ERTS_DSIG_PREP_NOT_CONNECTED:
res = SEND_NOCONNECT;
break;
case ERTS_DSIG_PREP_WOULD_SUSPEND:
- ASSERT(!ctx->suspend);
+ ASSERT(!suspend);
res = SEND_YIELD;
break;
case ERTS_DSIG_PREP_PENDING:
case ERTS_DSIG_PREP_CONNECTED: {
if (is_atom(to))
- code = erts_dsig_send_reg_msg(to, msg, ctx);
+ code = erts_dsig_send_reg_msg(&ctx, to, msg);
else
- code = erts_dsig_send_msg(to, msg, ctx);
+ code = erts_dsig_send_msg(&ctx, to, msg);
/*
* Note that reductions have been bumped on calling
* process by erts_dsig_send_reg_msg() or
@@ -1840,8 +1857,20 @@ static Sint remote_send(Process *p, DistEntry *dep,
*/
if (code == ERTS_DSIG_SEND_YIELD)
res = SEND_YIELD_RETURN;
- else if (code == ERTS_DSIG_SEND_CONTINUE)
+ else if (code == ERTS_DSIG_SEND_CONTINUE) {
+ erts_set_gc_state(p, 0);
+
+ /* Keep a reference to the dist entry if the
+ name is an not a pid. */
+ if (is_atom(to)) {
+ erts_ref_dist_entry(ctx.dep);
+ ctx.deref_dep = 1;
+ }
+
+ *ctxpp = erts_dsend_export_trap_context(p, &ctx);
res = SEND_YIELD_CONTINUE;
+ } else if (code == ERTS_DSIG_SEND_TOO_LRG)
+ res = SEND_SYSTEM_LIMIT;
else
res = 0;
break;
@@ -1862,7 +1891,8 @@ static Sint remote_send(Process *p, DistEntry *dep,
}
static Sint
-do_send(Process *p, Eterm to, Eterm msg, Eterm *refp, ErtsSendContext *ctx)
+do_send(Process *p, Eterm to, Eterm msg, Eterm return_term, Eterm *refp,
+ Eterm *dist_ctx, int connect, int suspend)
{
Eterm portid;
Port *pt;
@@ -1894,7 +1924,8 @@ do_send(Process *p, Eterm to, Eterm msg, Eterm *refp, ErtsSendContext *ctx)
erts_send_error_to_logger(p->group_leader, dsbufp);
return 0;
}
- return remote_send(p, dep, to, to, msg, ctx);
+ return remote_send(p, dep, to, dep->sysname, to, msg, return_term,
+ dist_ctx, connect, suspend);
} else if (is_atom(to)) {
Eterm id = erts_whereis_name_to_id(p, to);
@@ -1949,7 +1980,7 @@ do_send(Process *p, Eterm to, Eterm msg, Eterm *refp, ErtsSendContext *ctx)
ret_val = 0;
if (pt) {
- int ps_flags = ctx->suspend ? 0 : ERTS_PORT_SIG_FLG_NOSUSPEND;
+ int ps_flags = suspend ? 0 : ERTS_PORT_SIG_FLG_NOSUSPEND;
*refp = NIL;
if (IS_TRACED_FL(p, F_TRACE_SEND)) /* trace once only !! */
@@ -1964,12 +1995,12 @@ do_send(Process *p, Eterm to, Eterm msg, Eterm *refp, ErtsSendContext *ctx)
switch (erts_port_command(p, ps_flags, pt, msg, refp)) {
case ERTS_PORT_OP_BUSY:
/* Nothing has been sent */
- if (ctx->suspend)
+ if (suspend)
erts_suspend(p, ERTS_PROC_LOCK_MAIN, pt);
return SEND_YIELD;
case ERTS_PORT_OP_BUSY_SCHEDULED:
/* Message was sent */
- if (ctx->suspend) {
+ if (suspend) {
erts_suspend(p, ERTS_PROC_LOCK_MAIN, pt);
ret_val = SEND_YIELD_RETURN;
break;
@@ -2040,13 +2071,10 @@ do_send(Process *p, Eterm to, Eterm msg, Eterm *refp, ErtsSendContext *ctx)
ASSERT(dep != erts_this_dist_entry);
deref_dep = 1;
}
- ctx->dsd.node = tp[2];
- ret = remote_send(p, dep, tp[1], to, msg, ctx);
- if (ret == SEND_YIELD_CONTINUE) {
- erts_ref_dist_entry(ctx->dep);
- ctx->deref_dep = 1;
- }
+ ret = remote_send(p, dep, tp[1], tp[2], to, msg, return_term,
+ dist_ctx, connect, suspend);
+
if (deref_dep)
erts_deref_dist_entry(dep);
return ret;
@@ -2085,25 +2113,16 @@ BIF_RETTYPE send_3(BIF_ALIST_3)
Eterm l = opts;
Sint result;
-
- DeclareTypedTmpHeap(ErtsSendContext, ctx, BIF_P);
+ int connect = 1, suspend = 1;
+ Eterm ctx;
ERTS_MSACC_PUSH_STATE_M_X();
- UseTmpHeap(sizeof(ErtsSendContext)/sizeof(Eterm), BIF_P);
-
- ctx->suspend = !0;
- ctx->connect = !0;
- ctx->deref_dep = 0;
- ctx->return_term = am_ok;
- ctx->dss.reds = (Sint) (ERTS_BIF_REDS_LEFT(p) * TERM_TO_BINARY_LOOP_FACTOR);
- ctx->dss.phase = ERTS_DSIG_SEND_PHASE_INIT;
-
while (is_list(l)) {
if (CAR(list_val(l)) == am_noconnect) {
- ctx->connect = 0;
+ connect = 0;
} else if (CAR(list_val(l)) == am_nosuspend) {
- ctx->suspend = 0;
+ suspend = 0;
} else {
ERTS_BIF_PREP_ERROR(retval, p, BADARG);
goto done;
@@ -2120,7 +2139,7 @@ BIF_RETTYPE send_3(BIF_ALIST_3)
#endif
ERTS_MSACC_SET_STATE_CACHED_M_X(ERTS_MSACC_STATE_SEND);
- result = do_send(p, to, msg, &ref, ctx);
+ result = do_send(p, to, msg, am_ok, &ref, &ctx, connect, suspend);
ERTS_MSACC_POP_STATE_M_X();
if (result >= 0) {
@@ -2133,22 +2152,21 @@ BIF_RETTYPE send_3(BIF_ALIST_3)
switch (result) {
case SEND_NOCONNECT:
- if (ctx->connect) {
+ if (connect) {
ERTS_BIF_PREP_RET(retval, am_ok);
} else {
ERTS_BIF_PREP_RET(retval, am_noconnect);
}
break;
case SEND_YIELD:
- if (ctx->suspend) {
- ERTS_BIF_PREP_YIELD3(retval,
- bif_export[BIF_send_3], p, to, msg, opts);
+ if (suspend) {
+ ERTS_BIF_PREP_YIELD3(retval, bif_export[BIF_send_3], p, to, msg, opts);
} else {
ERTS_BIF_PREP_RET(retval, am_nosuspend);
}
break;
case SEND_YIELD_RETURN:
- if (!ctx->suspend) {
+ if (!suspend) {
ERTS_BIF_PREP_RET(retval, am_nosuspend);
break;
}
@@ -2162,6 +2180,9 @@ BIF_RETTYPE send_3(BIF_ALIST_3)
case SEND_BADARG:
ERTS_BIF_PREP_ERROR(retval, p, BADARG);
break;
+ case SEND_SYSTEM_LIMIT:
+ ERTS_BIF_PREP_ERROR(retval, p, SYSTEM_LIMIT);
+ break;
case SEND_USER_ERROR:
ERTS_BIF_PREP_ERROR(retval, p, EXC_ERROR);
break;
@@ -2170,9 +2191,7 @@ BIF_RETTYPE send_3(BIF_ALIST_3)
break;
case SEND_YIELD_CONTINUE:
BUMP_ALL_REDS(p);
- erts_set_gc_state(p, 0);
- ERTS_BIF_PREP_TRAP1(retval, &dsend_continue_trap_export, p,
- erts_dsend_export_trap_context(p, ctx));
+ ERTS_BIF_PREP_TRAP1(retval, &dsend_continue_trap_export, p, ctx);
break;
default:
erts_exit(ERTS_ABORT_EXIT, "send_3 invalid result %d\n", (int)result);
@@ -2180,7 +2199,6 @@ BIF_RETTYPE send_3(BIF_ALIST_3)
}
done:
- UnUseTmpHeap(sizeof(ErtsSendContext)/sizeof(Eterm), BIF_P);
return retval;
}
@@ -2194,14 +2212,14 @@ BIF_RETTYPE send_2(BIF_ALIST_2)
static BIF_RETTYPE dsend_continue_trap_1(BIF_ALIST_1)
{
Binary* bin = erts_magic_ref2bin(BIF_ARG_1);
- ErtsSendContext* ctx = (ErtsSendContext*) ERTS_MAGIC_BIN_DATA(bin);
+ ErtsDSigSendContext *ctx = (ErtsDSigSendContext*) ERTS_MAGIC_BIN_DATA(bin);
Sint initial_reds = (Sint) (ERTS_BIF_REDS_LEFT(BIF_P) * TERM_TO_BINARY_LOOP_FACTOR);
int result;
ASSERT(ERTS_MAGIC_BIN_DESTRUCTOR(bin) == erts_dsend_context_dtor);
- ctx->dss.reds = initial_reds;
- result = erts_dsig_send(&ctx->dsd, &ctx->dss);
+ ctx->reds = initial_reds;
+ result = erts_dsig_send(ctx);
switch (result) {
case ERTS_DSIG_SEND_OK:
@@ -2210,7 +2228,7 @@ static BIF_RETTYPE dsend_continue_trap_1(BIF_ALIST_1)
break;
case ERTS_DSIG_SEND_YIELD: /*SEND_YIELD_RETURN*/
erts_set_gc_state(BIF_P, 1);
- if (!ctx->suspend)
+ if (ctx->no_suspend)
BIF_RET(am_nosuspend);
ERTS_BIF_YIELD_RETURN(BIF_P, ctx->return_term);
@@ -2218,6 +2236,10 @@ static BIF_RETTYPE dsend_continue_trap_1(BIF_ALIST_1)
BUMP_ALL_REDS(BIF_P);
BIF_TRAP1(&dsend_continue_trap_export, BIF_P, BIF_ARG_1);
}
+ case ERTS_DSIG_SEND_TOO_LRG: { /*SEND_SYSTEM_LIMIT*/
+ erts_set_gc_state(BIF_P, 1);
+ BIF_ERROR(BIF_P, SYSTEM_LIMIT);
+ }
default:
erts_exit(ERTS_ABORT_EXIT, "dsend_continue_trap invalid result %d\n", (int)result);
break;
@@ -2231,20 +2253,14 @@ Eterm erl_send(Process *p, Eterm to, Eterm msg)
Eterm retval;
Eterm ref;
Sint result;
- DeclareTypedTmpHeap(ErtsSendContext, ctx, p);
+ Eterm ctx;
ERTS_MSACC_PUSH_AND_SET_STATE_M_X(ERTS_MSACC_STATE_SEND);
- UseTmpHeap(sizeof(ErtsSendContext)/sizeof(Eterm), p);
+
#ifdef DEBUG
ref = NIL;
#endif
- ctx->suspend = !0;
- ctx->connect = !0;
- ctx->deref_dep = 0;
- ctx->return_term = msg;
- ctx->dss.reds = (Sint) (ERTS_BIF_REDS_LEFT(p) * TERM_TO_BINARY_LOOP_FACTOR);
- ctx->dss.phase = ERTS_DSIG_SEND_PHASE_INIT;
- result = do_send(p, to, msg, &ref, ctx);
+ result = do_send(p, to, msg, msg, &ref, &ctx, 1, 1);
ERTS_MSACC_POP_STATE_M_X();
@@ -2275,6 +2291,9 @@ Eterm erl_send(Process *p, Eterm to, Eterm msg)
case SEND_BADARG:
ERTS_BIF_PREP_ERROR(retval, p, BADARG);
break;
+ case SEND_SYSTEM_LIMIT:
+ ERTS_BIF_PREP_ERROR(retval, p, SYSTEM_LIMIT);
+ break;
case SEND_USER_ERROR:
ERTS_BIF_PREP_ERROR(retval, p, EXC_ERROR);
break;
@@ -2283,9 +2302,7 @@ Eterm erl_send(Process *p, Eterm to, Eterm msg)
break;
case SEND_YIELD_CONTINUE:
BUMP_ALL_REDS(p);
- erts_set_gc_state(p, 0);
- ERTS_BIF_PREP_TRAP1(retval, &dsend_continue_trap_export, p,
- erts_dsend_export_trap_context(p, ctx));
+ ERTS_BIF_PREP_TRAP1(retval, &dsend_continue_trap_export, p, ctx);
break;
default:
erts_exit(ERTS_ABORT_EXIT, "invalid send result %d\n", (int)result);
@@ -2293,7 +2310,6 @@ Eterm erl_send(Process *p, Eterm to, Eterm msg)
}
done:
- UnUseTmpHeap(sizeof(ErtsSendContext)/sizeof(Eterm), p);
return retval;
}
@@ -2359,7 +2375,7 @@ accumulate(Eterm acc, Uint size)
* bignum buffer with one extra word to be used if
* the bignum grows in the future.
*/
- Eterm* hp = (Eterm *) erts_alloc(ERTS_ALC_T_TEMP_TERM,
+ Eterm* hp = (Eterm *) erts_alloc(ERTS_ALC_T_SHORT_LIVED_TERM,
(BIG_UINT_HEAP_SIZE+1) *
sizeof(Eterm));
return uint_to_big(size, hp);
@@ -2379,7 +2395,7 @@ accumulate(Eterm acc, Uint size)
* The extra word has been consumed. Grow the
* allocation by one word.
*/
- big = (Eterm *) erts_realloc(ERTS_ALC_T_TEMP_TERM,
+ big = (Eterm *) erts_realloc(ERTS_ALC_T_SHORT_LIVED_TERM,
big_val(acc),
(need_heap+1) * sizeof(Eterm));
acc = make_big(big);
@@ -2408,29 +2424,85 @@ consolidate(Process* p, Eterm acc, Uint size)
while (sz--) {
*hp++ = *big++;
}
- erts_free(ERTS_ALC_T_TEMP_TERM, (void *) big_val(acc));
+ erts_free(ERTS_ALC_T_SHORT_LIVED_TERM, (void *) big_val(acc));
return res;
}
}
+typedef struct {
+ Eterm obj;
+ Uint size;
+ Eterm acc;
+ Eterm input_list;
+ ErtsEStack stack;
+ int is_trap_at_L_iter_list;
+} ErtsIOListSizeContext;
+
+static int iolist_size_ctx_bin_dtor(Binary *context_bin) {
+ ErtsIOListSizeContext* context = ERTS_MAGIC_BIN_DATA(context_bin);
+ DESTROY_SAVED_ESTACK(&context->stack);
+ if (context->acc != THE_NON_VALUE) {
+ erts_free(ERTS_ALC_T_SHORT_LIVED_TERM, (void *) big_val(context->acc));
+ }
+ return 1;
+}
+
BIF_RETTYPE iolist_size_1(BIF_ALIST_1)
{
- Eterm obj, hd;
+ static const Uint ITERATIONS_PER_RED = 64;
+ Eterm input_list, obj, hd;
Eterm* objp;
Uint size = 0;
Uint cur_size;
Uint new_size;
Eterm acc = THE_NON_VALUE;
DECLARE_ESTACK(s);
-
- obj = BIF_ARG_1;
+ Uint max_iterations;
+ Uint iterations_until_trap = max_iterations =
+ ITERATIONS_PER_RED * ERTS_BIF_REDS_LEFT(BIF_P);
+ ErtsIOListSizeContext* context = NULL;
+ Eterm state_mref;
+ int is_trap_at_L_iter_list;
+ ESTACK_CHANGE_ALLOCATOR(s, ERTS_ALC_T_SAVED_ESTACK);
+#ifdef DEBUG
+ iterations_until_trap = iterations_until_trap / 10;
+#endif
+ input_list = obj = BIF_ARG_1;
+ if (is_internal_magic_ref(obj)) {
+ /* Restore state after a trap */
+ Binary* state_bin;
+ state_mref = obj;
+ state_bin = erts_magic_ref2bin(state_mref);
+ if (ERTS_MAGIC_BIN_DESTRUCTOR(state_bin) != iolist_size_ctx_bin_dtor) {
+ BIF_ERROR(BIF_P, BADARG);
+ }
+ context = ERTS_MAGIC_BIN_DATA(state_bin);
+ obj = context->obj;
+ size = context->size;
+ acc = context->acc;
+ input_list = context->input_list;
+ ESTACK_RESTORE(s, &context->stack);
+ ASSERT(BIF_P->flags & F_DISABLE_GC);
+ erts_set_gc_state(BIF_P, 1);
+ if (context->is_trap_at_L_iter_list) {
+ goto L_iter_list;
+ }
+ }
goto L_again;
while (!ESTACK_ISEMPTY(s)) {
obj = ESTACK_POP(s);
+ if (iterations_until_trap == 0) {
+ is_trap_at_L_iter_list = 0;
+ goto L_save_state_and_trap;
+ }
L_again:
if (is_list(obj)) {
L_iter_list:
+ if (iterations_until_trap == 0) {
+ is_trap_at_L_iter_list = 1;
+ goto L_save_state_and_trap;
+ }
objp = list_val(obj);
hd = CAR(objp);
obj = CDR(objp);
@@ -2452,12 +2524,14 @@ BIF_RETTYPE iolist_size_1(BIF_ALIST_1)
} else if (is_list(hd)) {
ESTACK_PUSH(s, obj);
obj = hd;
+ iterations_until_trap--;
goto L_iter_list;
} else if (is_not_nil(hd)) {
goto L_type_error;
}
/* Tail */
if (is_list(obj)) {
+ iterations_until_trap--;
goto L_iter_list;
} else if (is_binary(obj) && binary_bitsize(obj) == 0) {
cur_size = binary_size(obj);
@@ -2481,14 +2555,55 @@ BIF_RETTYPE iolist_size_1(BIF_ALIST_1)
} else if (is_not_nil(obj)) {
goto L_type_error;
}
+ iterations_until_trap--;
}
DESTROY_ESTACK(s);
+ BUMP_REDS(BIF_P, (max_iterations - iterations_until_trap) / ITERATIONS_PER_RED);
+ ASSERT(!(BIF_P->flags & F_DISABLE_GC));
+ if (context != NULL) {
+ /* context->acc needs to be reset so that
+ iolist_size_ctx_bin_dtor does not deallocate twice */
+ context->acc = THE_NON_VALUE;
+ }
BIF_RET(consolidate(BIF_P, acc, size));
L_type_error:
DESTROY_ESTACK(s);
- BIF_ERROR(BIF_P, BADARG);
+ if (acc != THE_NON_VALUE) {
+ erts_free(ERTS_ALC_T_SHORT_LIVED_TERM, (void *) big_val(acc));
+ if (context != NULL) {
+ context->acc = THE_NON_VALUE;
+ }
+ }
+ BUMP_REDS(BIF_P, (max_iterations - iterations_until_trap) / ITERATIONS_PER_RED);
+ ASSERT(!(BIF_P->flags & F_DISABLE_GC));
+ if (context == NULL) {
+ BIF_ERROR(BIF_P, BADARG);
+ } else {
+ ERTS_BIF_ERROR_TRAPPED1(BIF_P,
+ BADARG,
+ bif_export[BIF_iolist_size_1],
+ input_list);
+ }
+
+ L_save_state_and_trap:
+ if (context == NULL) {
+ Binary *state_bin = erts_create_magic_binary(sizeof(ErtsIOListSizeContext),
+ iolist_size_ctx_bin_dtor);
+ Eterm* hp = HAlloc(BIF_P, ERTS_MAGIC_REF_THING_SIZE);
+ state_mref = erts_mk_magic_ref(&hp, &MSO(BIF_P), state_bin);
+ context = ERTS_MAGIC_BIN_DATA(state_bin);
+ }
+ context->obj = obj;
+ context->size = size;
+ context->acc = acc;
+ context->is_trap_at_L_iter_list = is_trap_at_L_iter_list;
+ context->input_list = input_list;
+ ESTACK_SAVE(s, &context->stack);
+ erts_set_gc_state(BIF_P, 0);
+ BUMP_ALL_REDS(BIF_P);
+ BIF_TRAP1(bif_export[BIF_iolist_size_1], BIF_P, state_mref);
}
/**********************************************************************/
@@ -2732,9 +2847,7 @@ BIF_RETTYPE atom_to_list_1(BIF_ALIST_1)
Uint num_chars, num_built, num_eaten;
byte* err_pos;
Eterm res;
-#ifdef DEBUG
int ares;
-#endif
if (is_not_atom(BIF_ARG_1))
BIF_ERROR(BIF_P, BADARG);
@@ -2744,11 +2857,9 @@ BIF_RETTYPE atom_to_list_1(BIF_ALIST_1)
if (ap->len == 0)
BIF_RET(NIL); /* the empty atom */
-#ifdef DEBUG
ares =
-#endif
erts_analyze_utf8(ap->name, ap->len, &err_pos, &num_chars, NULL);
- ASSERT(ares == ERTS_UTF8_OK);
+ ASSERT(ares == ERTS_UTF8_OK); (void)ares;
res = erts_utf8_to_list(BIF_P, num_chars, ap->name, ap->len, ap->len,
&num_built, &num_eaten, NIL);
@@ -2810,38 +2921,110 @@ BIF_RETTYPE list_to_existing_atom_1(BIF_ALIST_1)
/* convert an integer to a list of ascii integers */
-BIF_RETTYPE integer_to_list_1(BIF_ALIST_1)
+static Eterm integer_to_list(Process *c_p, Eterm num, int base)
{
- Eterm* hp;
+ Eterm *hp;
+ Eterm res;
Uint need;
+ if (is_small(num)) {
+ char s[128];
+ char *c = s;
+ Uint digits;
+
+ digits = Sint_to_buf(signed_val(num), base, &c, sizeof(s));
+ need = 2 * digits;
+
+ hp = HAlloc(c_p, need);
+ res = buf_to_intlist(&hp, c, digits, NIL);
+ } else {
+ const int DIGITS_PER_RED = 16;
+ Eterm *hp_end;
+ Uint digits;
+
+ digits = big_integer_estimate(num, base);
+
+ if ((digits / DIGITS_PER_RED) > ERTS_BIF_REDS_LEFT(c_p)) {
+ ErtsSchedulerData *esdp = erts_get_scheduler_data();
+
+ /* This could take a very long time, tell the caller to reschedule
+ * us to a dirty CPU scheduler if we aren't already on one. */
+ if (esdp->type == ERTS_SCHED_NORMAL) {
+ return THE_NON_VALUE;
+ }
+ } else {
+ BUMP_REDS(c_p, digits / DIGITS_PER_RED);
+ }
+
+ need = 2 * digits;
+
+ hp = HAlloc(c_p, need);
+ hp_end = hp + need;
+
+ res = erts_big_to_list(num, base, &hp);
+ HRelease(c_p, hp_end, hp);
+ }
+
+ return res;
+}
+
+BIF_RETTYPE integer_to_list_1(BIF_ALIST_1)
+{
+ Eterm res;
+
if (is_not_integer(BIF_ARG_1)) {
- BIF_ERROR(BIF_P, BADARG);
+ BIF_ERROR(BIF_P, BADARG);
}
- if (is_small(BIF_ARG_1)) {
- char *c;
- int n;
- struct Sint_buf ibuf;
+ res = integer_to_list(BIF_P, BIF_ARG_1, 10);
- c = Sint_to_buf(signed_val(BIF_ARG_1), &ibuf);
- n = sys_strlen(c);
- need = 2*n;
- hp = HAlloc(BIF_P, need);
- BIF_RET(buf_to_intlist(&hp, c, n, NIL));
+ if (is_non_value(res)) {
+ Eterm args[1];
+ args[0] = BIF_ARG_1;
+ return erts_schedule_bif(BIF_P,
+ args,
+ BIF_I,
+ integer_to_list_1,
+ ERTS_SCHED_DIRTY_CPU,
+ am_erlang,
+ am_integer_to_list,
+ 1);
}
- else {
- int n = big_decimal_estimate(BIF_ARG_1);
- Eterm res;
- Eterm* hp_end;
- need = 2*n;
- hp = HAlloc(BIF_P, need);
- hp_end = hp + need;
- res = erts_big_to_list(BIF_ARG_1, &hp);
- HRelease(BIF_P,hp_end,hp);
- BIF_RET(res);
+ return res;
+}
+
+BIF_RETTYPE integer_to_list_2(BIF_ALIST_2)
+{
+ Eterm res;
+ SWord base;
+
+ if (is_not_integer(BIF_ARG_1) || is_not_small(BIF_ARG_2)) {
+ BIF_ERROR(BIF_P, BADARG);
}
+
+ base = signed_val(BIF_ARG_2);
+ if (base < 2 || base > 36) {
+ BIF_ERROR(BIF_P, BADARG);
+ }
+
+ res = integer_to_list(BIF_P, BIF_ARG_1, base);
+
+ if (is_non_value(res)) {
+ Eterm args[2];
+ args[0] = BIF_ARG_1;
+ args[1] = BIF_ARG_2;
+ return erts_schedule_bif(BIF_P,
+ args,
+ BIF_I,
+ integer_to_list_2,
+ ERTS_SCHED_DIRTY_CPU,
+ am_erlang,
+ am_integer_to_list,
+ 2);
+ }
+
+ return res;
}
/**********************************************************************/
@@ -3979,10 +4162,12 @@ BIF_RETTYPE list_to_pid_1(BIF_ALIST_1)
if (is_nil(dep->cid))
goto bad;
- enp = erts_find_or_insert_node(dep->sysname, dep->creation);
+ etp = (ExternalThing *) HAlloc(BIF_P, EXTERNAL_THING_HEAD_SIZE + 1);
+
+ enp = erts_find_or_insert_node(dep->sysname, dep->creation,
+ make_boxed(&etp->header));
ASSERT(enp != erts_this_node);
- etp = (ExternalThing *) HAlloc(BIF_P, EXTERNAL_THING_HEAD_SIZE + 1);
etp->header = make_external_pid_header(1);
etp->next = MSO(BIF_P).first;
etp->node = enp;
@@ -4046,10 +4231,11 @@ BIF_RETTYPE list_to_port_1(BIF_ALIST_1)
if (is_nil(dep->cid))
goto bad;
- enp = erts_find_or_insert_node(dep->sysname, dep->creation);
+ etp = (ExternalThing *) HAlloc(BIF_P, EXTERNAL_THING_HEAD_SIZE + 1);
+ enp = erts_find_or_insert_node(dep->sysname, dep->creation,
+ make_boxed(&etp->header));
ASSERT(enp != erts_this_node);
- etp = (ExternalThing *) HAlloc(BIF_P, EXTERNAL_THING_HEAD_SIZE + 1);
etp->header = make_external_port_header(1);
etp->next = MSO(BIF_P).first;
etp->node = enp;
@@ -4152,9 +4338,6 @@ BIF_RETTYPE list_to_ref_1(BIF_ALIST_1)
if (is_nil(dep->cid))
goto bad;
- enp = erts_find_or_insert_node(dep->sysname, dep->creation);
- ASSERT(enp != erts_this_node);
-
hsz = EXTERNAL_THING_HEAD_SIZE;
#if defined(ARCH_64)
hsz += n/2 + 1;
@@ -4163,6 +4346,11 @@ BIF_RETTYPE list_to_ref_1(BIF_ALIST_1)
#endif
etp = (ExternalThing *) HAlloc(BIF_P, hsz);
+
+ enp = erts_find_or_insert_node(dep->sysname, dep->creation,
+ make_boxed(&etp->header));
+ ASSERT(enp != erts_this_node);
+
etp->header = make_external_ref_header(n/2);
etp->next = BIF_P->off_heap.first;
etp->node = enp;
@@ -4282,21 +4470,21 @@ BIF_RETTYPE erts_internal_group_leader_2(BIF_ALIST_2)
if (is_external_pid(BIF_ARG_2)) {
DistEntry *dep;
int code;
- ErtsDSigData dsd;
+ ErtsDSigSendContext ctx;
dep = external_pid_dist_entry(BIF_ARG_2);
ERTS_ASSERT(dep);
if(dep == erts_this_dist_entry)
BIF_ERROR(BIF_P, BADARG);
- code = erts_dsig_prepare(&dsd, dep, BIF_P, ERTS_PROC_LOCK_MAIN,
- ERTS_DSP_NO_LOCK, 0, 1);
+ code = erts_dsig_prepare(&ctx, dep, BIF_P, ERTS_PROC_LOCK_MAIN,
+ ERTS_DSP_NO_LOCK, 0, 1, 1);
switch (code) {
case ERTS_DSIG_PREP_NOT_ALIVE:
case ERTS_DSIG_PREP_NOT_CONNECTED:
BIF_RET(am_true);
case ERTS_DSIG_PREP_PENDING:
case ERTS_DSIG_PREP_CONNECTED:
- code = erts_dsig_send_group_leader(&dsd, BIF_ARG_1, BIF_ARG_2);
+ code = erts_dsig_send_group_leader(&ctx, BIF_ARG_1, BIF_ARG_2);
if (code == ERTS_DSIG_SEND_YIELD)
ERTS_BIF_YIELD_RETURN(BIF_P, am_true);
BIF_RET(am_true);
@@ -4442,13 +4630,6 @@ BIF_RETTYPE system_flag_2(BIF_ALIST_2)
erts_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN);
BIF_RET(old_value);
- } else if (BIF_ARG_1 == am_display_items) {
- int oval = display_items;
- if (!is_small(BIF_ARG_2) || (n = signed_val(BIF_ARG_2)) < 0) {
- goto error;
- }
- display_items = n < 32 ? 32 : n;
- BIF_RET(make_small(oval));
} else if (BIF_ARG_1 == am_debug_flags) {
BIF_RET(am_true);
} else if (BIF_ARG_1 == am_backtrace_depth) {
@@ -4490,11 +4671,12 @@ BIF_RETTYPE system_flag_2(BIF_ALIST_2)
ERTS_TRACER_CLEAR(&old_seq_tracer);
BIF_RET(ret);
- } else if (BIF_ARG_1 == make_small(1)) {
+ } else if (BIF_ARG_1 == am_reset_seq_trace) {
int i, max;
- erts_proc_unlock(BIF_P, ERTS_PROC_LOCK_MAIN);
- erts_thr_progress_block();
+ erts_proc_unlock(BIF_P, ERTS_PROC_LOCK_MAIN);
+ erts_thr_progress_block();
+
max = erts_ptab_max(&erts_proc);
for (i = 0; i < max; i++) {
Process *p = erts_pix2proc(i);
@@ -4506,13 +4688,14 @@ BIF_RETTYPE system_flag_2(BIF_ALIST_2)
#endif
p->seq_trace_clock = 0;
p->seq_trace_lastcnt = 0;
-
+ erts_proc_lock(p, ERTS_PROC_LOCK_MAIN|ERTS_PROC_LOCK_MSGQ);
erts_proc_sig_clear_seq_trace_tokens(p);
+ erts_proc_unlock(p, ERTS_PROC_LOCK_MAIN|ERTS_PROC_LOCK_MSGQ);
}
}
- erts_thr_progress_unblock();
- erts_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN);
+ erts_thr_progress_unblock();
+ erts_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN);
BIF_RET(am_true);
} else if (BIF_ARG_1 == am_scheduler_wall_time) {
@@ -4627,6 +4810,9 @@ BIF_RETTYPE system_flag_2(BIF_ALIST_2)
return erts_bind_schedulers(BIF_P, BIF_ARG_2);
} else if (ERTS_IS_ATOM_STR("erts_alloc", BIF_ARG_1)) {
return erts_alloc_set_dyn_param(BIF_P, BIF_ARG_2);
+ } else if (ERTS_IS_ATOM_STR("system_logger", BIF_ARG_1)) {
+ Eterm res = erts_set_system_logger(BIF_ARG_2);
+ if (is_value(res)) BIF_RET(res);
}
error:
BIF_ERROR(BIF_P, BADARG);
@@ -5129,61 +5315,6 @@ erts_call_dirty_bif(ErtsSchedulerData *esdp, Process *c_p, BeamInstr *I, Eterm *
return exiting;
}
-
-
-#ifdef HARDDEBUG
-/*
-You'll need this line in bif.tab to be able to use this debug bif
-
-bif erlang:send_to_logger/2
-
-*/
-BIF_RETTYPE send_to_logger_2(BIF_ALIST_2)
-{
- byte *buf;
- ErlDrvSizeT len;
- if (!is_atom(BIF_ARG_1) || !(is_list(BIF_ARG_2) ||
- is_nil(BIF_ARG_1))) {
- BIF_ERROR(BIF_P,BADARG);
- }
- if (erts_iolist_size(BIF_ARG_2, &len) != 0)
- BIF_ERROR(BIF_P,BADARG);
- else if (len == 0)
- buf = "";
- else {
-#ifdef DEBUG
- ErlDrvSizeT len2;
-#endif
- buf = (byte *) erts_alloc(ERTS_ALC_T_TMP, len+1);
-#ifdef DEBUG
- len2 =
-#else
- (void)
-#endif
- erts_iolist_to_buf(BIF_ARG_2, buf, len);
- ASSERT(len2 == len);
- buf[len] = '\0';
- switch (BIF_ARG_1) {
- case am_info:
- erts_send_info_to_logger(BIF_P->group_leader, buf, len);
- break;
- case am_warning:
- erts_send_warning_to_logger(BIF_P->group_leader, buf, len);
- break;
- case am_error:
- erts_send_error_to_logger(BIF_P->group_leader, buf, len);
- break;
- default:
- {
- BIF_ERROR(BIF_P,BADARG);
- }
- }
- erts_free(ERTS_ALC_T_TMP, (void *) buf);
- }
- BIF_RET(am_true);
-}
-#endif /* HARDDEBUG */
-
BIF_RETTYPE get_module_info_1(BIF_ALIST_1)
{
Eterm ret = erts_module_info_0(BIF_P, BIF_ARG_1);
diff --git a/erts/emulator/beam/bif.tab b/erts/emulator/beam/bif.tab
index 0e218811d8..11941db8cd 100644
--- a/erts/emulator/beam/bif.tab
+++ b/erts/emulator/beam/bif.tab
@@ -41,7 +41,7 @@
#
-gcbif erlang:abs/1
+ubif erlang:abs/1
bif erlang:adler32/1
bif erlang:adler32/2
bif erlang:adler32_combine/3
@@ -66,7 +66,7 @@ bif erlang:exit/2
bif erlang:exit_signal/2
bif erlang:external_size/1
bif erlang:external_size/2
-gcbif erlang:float/1
+ubif erlang:float/1
bif erlang:float_to_list/1
bif erlang:float_to_list/2
bif erlang:fun_info/2
@@ -84,7 +84,7 @@ bif erlang:phash2/2
ubif erlang:hd/1
bif erlang:integer_to_list/1
bif erlang:is_alive/0
-gcbif erlang:length/1
+ubif erlang:length/1
bif erlang:link/1
bif erlang:list_to_atom/1
bif erlang:list_to_binary/1
@@ -133,10 +133,10 @@ bif erlang:processes/0
bif erlang:put/2
bif erlang:register/2
bif erlang:registered/0
-gcbif erlang:round/1
+ubif erlang:round/1
ubif erlang:self/0
bif erlang:setelement/3
-gcbif erlang:size/1
+ubif erlang:size/1
bif erlang:spawn/3
bif erlang:spawn_link/3
bif erlang:split_binary/2
@@ -146,7 +146,7 @@ bif erlang:term_to_binary/2
bif erlang:throw/1
bif erlang:time/0
ubif erlang:tl/1
-gcbif erlang:trunc/1
+ubif erlang:trunc/1
bif erlang:tuple_to_list/1
bif erlang:universaltime/0
bif erlang:universaltime_to_localtime/1
@@ -481,8 +481,8 @@ bif erlang:list_to_existing_atom/1
#
ubif erlang:is_bitstring/1
ubif erlang:tuple_size/1
-gcbif erlang:byte_size/1
-gcbif erlang:bit_size/1
+ubif erlang:byte_size/1
+ubif erlang:bit_size/1
bif erlang:list_to_bitstring/1
bif erlang:bitstring_to_list/1
@@ -534,8 +534,8 @@ bif erlang:binary_to_term/2
#
# The searching/splitting/substituting thingies
#
-gcbif erlang:binary_part/2
-gcbif erlang:binary_part/3
+ubif erlang:binary_part/2
+ubif erlang:binary_part/3
bif binary:compile_pattern/1
bif binary:match/2
@@ -623,14 +623,13 @@ bif io:printable_range/0
bif re:inspect/2
ubif erlang:is_map/1
-gcbif erlang:map_size/1
+ubif erlang:map_size/1
bif maps:find/2
bif maps:get/2
bif maps:from_list/1
bif maps:is_key/2
bif maps:keys/1
bif maps:merge/2
-bif maps:new/0
bif maps:put/3
bif maps:remove/2
bif maps:update/3
@@ -672,8 +671,8 @@ bif maps:take/2
# New in 20.0
#
-gcbif erlang:floor/1
-gcbif erlang:ceil/1
+ubif erlang:floor/1
+ubif erlang:ceil/1
bif math:floor/1
bif math:ceil/1
bif math:fmod/2
@@ -731,3 +730,11 @@ bif erts_internal:counters_info/1
#
bif erts_internal:spawn_system_process/3
+
+#
+# New in 21.3
+#
+
+bif erlang:integer_to_list/2
+bif erlang:integer_to_binary/2
+bif persistent_term:get/2
diff --git a/erts/emulator/beam/bif_instrs.tab b/erts/emulator/beam/bif_instrs.tab
index 00854471a9..418bbe2b23 100644
--- a/erts/emulator/beam/bif_instrs.tab
+++ b/erts/emulator/beam/bif_instrs.tab
@@ -31,13 +31,20 @@
CALL_GUARD_BIF(BF, TmpReg, Dst) {
Eterm result;
+#ifdef DEBUG
+ Eterm* orig_htop = HTOP;
+ Eterm* orig_stop = E;
+#endif
ERTS_DBG_CHK_REDS(c_p, FCALLS);
c_p->fcalls = FCALLS;
PROCESS_MAIN_CHK_LOCKS(c_p);
ASSERT(!ERTS_PROC_IS_EXITING(c_p));
ERTS_CHK_MBUF_SZ(c_p);
+ DEBUG_SWAPOUT;
result = (*$BF)(c_p, $TmpReg, I);
+ DEBUG_SWAPIN;
+ ASSERT(orig_htop == HTOP && orig_stop == E);
ERTS_CHK_MBUF_SZ(c_p);
ASSERT(!ERTS_PROC_IS_EXITING(c_p) || is_non_value(result));
ERTS_VERIFY_UNUSED_TEMP_ALLOC(c_p);
@@ -51,222 +58,154 @@ CALL_GUARD_BIF(BF, TmpReg, Dst) {
}
}
-// Guard BIF in head. On failure, ignore the error and jump
-// to the code for the next clause. We don't support tracing
+// Guard BIF in head. On failure, ignore the error and jump
+// to the code for the next clause. We don't support tracing
// of guard BIFs.
-bif1(Fail, Bif, Src, Dst) {
+i_bif1 := i_bif.fetch0.call;
+i_bif2 := i_bif.fetch1.fetch0.call;
+i_bif3 := i_bif.fetch2.fetch1.fetch0.call;
+
+i_bif.head() {
ErtsBifFunc bf;
- Eterm tmp_reg[1];
+ Eterm tmp_reg[3];
+}
+i_bif.fetch0(Src) {
tmp_reg[0] = $Src;
- bf = (BifFunction) $Bif;
- $CALL_GUARD_BIF(bf, tmp_reg, $Dst);
-
- $FAIL($Fail);
}
-//
-// Guard BIF in body. It can fail like any BIF. No trace support.
-//
+i_bif.fetch1(Src) {
+ tmp_reg[1] = $Src;
+}
-bif1_body(Bif, Src, Dst) {
- ErtsBifFunc bf;
- Eterm tmp_reg[1];
+i_bif.fetch2(Src) {
+ tmp_reg[2] = $Src;
+}
- tmp_reg[0] = $Src;
+i_bif.call(Fail, Bif, Dst) {
bf = (BifFunction) $Bif;
$CALL_GUARD_BIF(bf, tmp_reg, $Dst);
- reg[0] = tmp_reg[0];
- SWAPOUT;
- I = handle_error(c_p, I, reg, ubif2mfa((void *) bf));
- goto post_error_handling;
+ $FAIL($Fail);
}
//
-// Guard bif in guard with two arguments ('and'/2, 'or'/2, 'xor'/2).
+// Guard BIF in body. It can fail like any BIF. No trace support.
//
-i_bif2(Fail, Bif, Src1, Src2, Dst) {
- Eterm tmp_reg[2];
+i_bif1_body := i_bif_body.fetch0.call;
+i_bif2_body := i_bif_body.fetch1.fetch0.call;
+i_bif3_body := i_bif_body.fetch2.fetch1.fetch0.call;
+
+i_bif_body.head() {
ErtsBifFunc bf;
+ Eterm tmp_reg[3];
+}
- tmp_reg[0] = $Src1;
- tmp_reg[1] = $Src2;
- bf = (ErtsBifFunc) $Bif;
- $CALL_GUARD_BIF(bf, tmp_reg, $Dst);
- $FAIL($Fail);
+i_bif_body.fetch0(Src) {
+ tmp_reg[0] = $Src;
}
-//
-// Guard bif in body with two arguments ('and'/2, 'or'/2, 'xor'/2).
-//
+i_bif_body.fetch1(Src) {
+ tmp_reg[1] = $Src;
+}
-i_bif2_body(Bif, Src1, Src2, Dst) {
- Eterm tmp_reg[2];
- ErtsBifFunc bf;
+i_bif_body.fetch2(Src) {
+ tmp_reg[2] = $Src;
+}
- tmp_reg[0] = $Src1;
- tmp_reg[1] = $Src2;
- bf = (ErtsBifFunc) $Bif;
+i_bif_body.call(Bif, Dst) {
+ bf = (BifFunction) $Bif;
$CALL_GUARD_BIF(bf, tmp_reg, $Dst);
+
reg[0] = tmp_reg[0];
reg[1] = tmp_reg[1];
+ reg[2] = tmp_reg[2];
SWAPOUT;
I = handle_error(c_p, I, reg, ubif2mfa((void *) bf));
goto post_error_handling;
}
//
-// Garbage-collecting BIF with one argument in either guard or body.
+// length/1 is the only guard BIF that does not execute in constant
+// time. Here follows special instructions to allow the calculation of
+// the list length to be broken in several chunks to avoid hogging
+// the scheduler for a long time.
//
-i_gc_bif1(Fail, Bif, Src, Live, Dst) {
- typedef Eterm (*GcBifFunction)(Process*, Eterm*, Uint);
- GcBifFunction bf;
- Eterm result;
- Uint live = (Uint) $Live;
+i_length_setup(Live, Src) {
+ Uint live = $Live;
+ Eterm src = $Src;
- x(live) = $Src;
- bf = (GcBifFunction) $Bif;
- ERTS_DBG_CHK_REDS(c_p, FCALLS);
- c_p->fcalls = FCALLS;
- SWAPOUT;
- PROCESS_MAIN_CHK_LOCKS(c_p);
- ERTS_UNREQ_PROC_MAIN_LOCK(c_p);
- ERTS_CHK_MBUF_SZ(c_p);
- result = (*bf)(c_p, reg, live);
- ERTS_CHK_MBUF_SZ(c_p);
- ERTS_VERIFY_UNUSED_TEMP_ALLOC(c_p);
- ERTS_REQ_PROC_MAIN_LOCK(c_p);
- PROCESS_MAIN_CHK_LOCKS(c_p);
- SWAPIN;
- ERTS_HOLE_CHECK(c_p);
- FCALLS = c_p->fcalls;
- ERTS_DBG_CHK_REDS(c_p, FCALLS);
- if (ERTS_LIKELY(is_value(result))) {
- $REFRESH_GEN_DEST();
- $Dst = result;
- $NEXT0();
- }
- if (ERTS_LIKELY($Fail != 0)) { /* Handle error in guard. */
- $JUMP($Fail);
- }
+ reg[live] = src;
+ reg[live+1] = make_small(0);
+ reg[live+2] = src;
- /* Handle error in body. */
- x(0) = x(live);
- I = handle_error(c_p, I, reg, gcbif2mfa((void *) bf));
- goto post_error_handling;
+ /* This instruction is always followed by i_length */
+ SET_I($NEXT_INSTRUCTION);
+ goto i_length_start__;
+ //| -no_next
}
//
-// Garbage-collecting BIF with two arguments in either guard or body.
+// This instruction can be executed one or more times. When entering
+// this instruction, the X registers have the following contents:
+//
+// reg[live+0] The remainder of the list.
+// reg[live+1] The length so far (tagged integer).
+// reg[live+2] The original list. Only used if an error is generated
+// (if the final tail of the list is not []).
//
-i_gc_bif2(Fail, Bif, Live, Src1, Src2, Dst) {
- typedef Eterm (*GcBifFunction)(Process*, Eterm*, Uint);
- GcBifFunction bf;
- Eterm result;
- Uint live = (Uint) $Live;
-
- /*
- * XXX This calling convention does not make sense. 'live'
- * should point out the first argument, not the second
- * (i.e. 'live' should not be incremented below).
- */
- x(live) = $Src1;
- x(live+1) = $Src2;
- live++;
-
- bf = (GcBifFunction) $Bif;
- ERTS_DBG_CHK_REDS(c_p, FCALLS);
- c_p->fcalls = FCALLS;
- SWAPOUT;
- PROCESS_MAIN_CHK_LOCKS(c_p);
- ERTS_UNREQ_PROC_MAIN_LOCK(c_p);
- ERTS_CHK_MBUF_SZ(c_p);
- result = (*bf)(c_p, reg, live);
- ERTS_CHK_MBUF_SZ(c_p);
- ERTS_VERIFY_UNUSED_TEMP_ALLOC(c_p);
- ERTS_REQ_PROC_MAIN_LOCK(c_p);
- PROCESS_MAIN_CHK_LOCKS(c_p);
- SWAPIN;
- ERTS_HOLE_CHECK(c_p);
- FCALLS = c_p->fcalls;
- ERTS_DBG_CHK_REDS(c_p, FCALLS);
- if (ERTS_LIKELY(is_value(result))) {
- $REFRESH_GEN_DEST();
- $Dst = result;
- $NEXT0();
- }
-
- if (ERTS_LIKELY($Fail != 0)) { /* Handle error in guard. */
- $JUMP($Fail);
- }
+i_length := i_length.start.execute;
- /* Handle error in body. */
- live--;
- x(0) = x(live);
- x(1) = x(live+1);
- I = handle_error(c_p, I, reg, gcbif2mfa((void *) bf));
- goto post_error_handling;
+i_length.start() {
+ i_length_start__:
+ ;
}
-//
-// Garbage-collecting BIF with three arguments in either guard or body.
-//
-
-i_gc_bif3(Fail, Bif, Live, Src2, Src3, Dst) {
- typedef Eterm (*GcBifFunction)(Process*, Eterm*, Uint);
- GcBifFunction bf;
+i_length.execute(Fail, Live, Dst) {
Eterm result;
- Uint live = (Uint) $Live;
+ Uint live;
- /*
- * XXX This calling convention does not make sense. 'live'
- * should point out the first argument, not the third
- * (i.e. 'live' should not be incremented below).
- */
- x(live) = x(SCRATCH_X_REG);
- x(live+1) = $Src2;
- x(live+2) = $Src3;
- live += 2;
-
- bf = (GcBifFunction) $Bif;
ERTS_DBG_CHK_REDS(c_p, FCALLS);
c_p->fcalls = FCALLS;
- SWAPOUT;
PROCESS_MAIN_CHK_LOCKS(c_p);
- ERTS_UNREQ_PROC_MAIN_LOCK(c_p);
+ ASSERT(!ERTS_PROC_IS_EXITING(c_p));
ERTS_CHK_MBUF_SZ(c_p);
- result = (*bf)(c_p, reg, live);
+ DEBUG_SWAPOUT;
+
+ live = $Live;
+ result = erts_trapping_length_1(c_p, reg+live);
+
+ DEBUG_SWAPIN;
ERTS_CHK_MBUF_SZ(c_p);
+ ASSERT(!ERTS_PROC_IS_EXITING(c_p) || is_non_value(result));
ERTS_VERIFY_UNUSED_TEMP_ALLOC(c_p);
- ERTS_REQ_PROC_MAIN_LOCK(c_p);
PROCESS_MAIN_CHK_LOCKS(c_p);
- SWAPIN;
ERTS_HOLE_CHECK(c_p);
FCALLS = c_p->fcalls;
ERTS_DBG_CHK_REDS(c_p, FCALLS);
if (ERTS_LIKELY(is_value(result))) {
+ /* Successful calculation of the list length. */
$REFRESH_GEN_DEST();
$Dst = result;
$NEXT0();
+ } else if (c_p->freason == TRAP) {
+ /*
+ * Good so far, but there is more work to do. Yield.
+ */
+ $SET_CP_I_ABS(I);
+ SWAPOUT;
+ c_p->arity = live + 3;
+ c_p->current = NULL;
+ goto context_switch3;
+ } else {
+ /* Error. */
+ $BIF_ERROR_ARITY_1($Fail, BIF_length_1, reg[live+2]);
}
-
- /* Handle error in guard. */
- if (ERTS_LIKELY($Fail != 0)) {
- $JUMP($Fail);
- }
-
- /* Handle error in body. */
- live -= 2;
- x(0) = x(live);
- x(1) = x(live+1);
- x(2) = x(live+2);
- I = handle_error(c_p, I, reg, gcbif2mfa((void *) bf));
- goto post_error_handling;
+ //| -no_next
}
//
diff --git a/erts/emulator/beam/big.c b/erts/emulator/beam/big.c
index b373d3a577..522f50287a 100644
--- a/erts/emulator/beam/big.c
+++ b/erts/emulator/beam/big.c
@@ -429,6 +429,7 @@
static const byte digits_per_sint_lookup[36-1];
static const byte digits_per_small_lookup[36-1];
static const Sint largest_power_of_base_lookup[36-1];
+static const double lg2_lookup[36-1];
static ERTS_INLINE byte get_digits_per_signed_int(Uint base) {
return digits_per_sint_lookup[base-2];
@@ -442,6 +443,10 @@ static ERTS_INLINE Sint get_largest_power_of_base(Uint base) {
return largest_power_of_base_lookup[base-2];
}
+static ERTS_INLINE double lookup_log2(Uint base) {
+ return lg2_lookup[base - 2];
+}
+
/*
** compare two number vectors
*/
@@ -668,27 +673,25 @@ static dsize_t I_mul(ErtsDigit* x, dsize_t xl, ErtsDigit* y, dsize_t yl, ErtsDig
static dsize_t I_sqr(ErtsDigit* x, dsize_t xl, ErtsDigit* r)
{
- ErtsDigit d_next = *x;
ErtsDigit d;
ErtsDigit* r0 = r;
ErtsDigit* s = r;
if ((r + xl) == x) /* "Inline" operation */
*x = 0;
- x++;
while(xl--) {
- ErtsDigit* y = x;
+ ErtsDigit* y;
ErtsDigit y_0 = 0, y_1 = 0, y_2 = 0, y_3 = 0;
ErtsDigit b0, b1;
ErtsDigit z0, z1, z2;
ErtsDigit t;
dsize_t y_l = xl;
-
+
+ d = *x;
+ x++;
+ y = x;
s = r;
- d = d_next;
- d_next = *x;
- x++;
DMUL(d, d, b1, b0);
DSUMc(*s, b0, y_3, t);
@@ -1722,23 +1725,23 @@ double_to_big(double x, Eterm *heap, Uint hsz)
/*
- ** Estimate the number of decimal digits (include sign)
+ ** Estimate the number of digits in given base (include sign)
*/
-int big_decimal_estimate(Wterm x)
+int big_integer_estimate(Wterm x, Uint base)
{
Eterm* xp = big_val(x);
int lg = I_lg(BIG_V(xp), BIG_SIZE(xp));
- int lg10 = ((lg+1)*28/93)+1;
+ int lgBase = ((lg + 1) / lookup_log2(base)) + 1;
- if (BIG_SIGN(xp)) lg10++; /* add sign */
- return lg10+1; /* add null */
+ if (BIG_SIGN(xp)) lgBase++; /* add sign */
+ return lgBase + 1; /* add null */
}
/*
-** Convert a bignum into a string of decimal numbers
+** Convert a bignum into a string of numbers in given base
*/
-
-static Uint write_big(Wterm x, void (*write_func)(void *, char), void *arg)
+static Uint write_big(Wterm x, int base, void (*write_func)(void *, char),
+ void *arg)
{
Eterm* xp = big_val(x);
ErtsDigit* dx = BIG_V(xp);
@@ -1746,48 +1749,72 @@ static Uint write_big(Wterm x, void (*write_func)(void *, char), void *arg)
short sign = BIG_SIGN(xp);
ErtsDigit rem;
Uint n = 0;
- const Uint digits_per_Sint = get_digits_per_signed_int(10);
- const Sint largest_pow_of_base = get_largest_power_of_base(10);
+ const Uint digits_per_Sint = get_digits_per_signed_int(base);
+ const Sint largest_pow_of_base = get_largest_power_of_base(base);
if (xl == 1 && *dx < largest_pow_of_base) {
- rem = *dx;
- if (rem == 0) {
- (*write_func)(arg, '0'); n++;
- } else {
- while(rem) {
- (*write_func)(arg, (rem % 10) + '0'); n++;
- rem /= 10;
- }
- }
+ rem = *dx;
+ if (rem == 0) {
+ (*write_func)(arg, '0'); n++;
+ } else {
+ while(rem) {
+ int digit = rem % base;
+
+ if (digit < 10) {
+ (*write_func)(arg, digit + '0'); n++;
+ } else {
+ (*write_func)(arg, 'A' + (digit - 10)); n++;
+ }
+
+ rem /= base;
+ }
+ }
} else {
- ErtsDigit* tmp = (ErtsDigit*) erts_alloc(ERTS_ALC_T_TMP,
- sizeof(ErtsDigit)*xl);
- dsize_t tmpl = xl;
+ ErtsDigit* tmp = (ErtsDigit*) erts_alloc(ERTS_ALC_T_TMP,
+ sizeof(ErtsDigit) * xl);
+ dsize_t tmpl = xl;
- MOVE_DIGITS(tmp, dx, xl);
+ MOVE_DIGITS(tmp, dx, xl);
- while(1) {
+ while(1) {
tmpl = D_div(tmp, tmpl, largest_pow_of_base, tmp, &rem);
- if (tmpl == 1 && *tmp == 0) {
- while(rem) {
- (*write_func)(arg, (rem % 10)+'0'); n++;
- rem /= 10;
- }
- break;
- } else {
+
+ if (tmpl == 1 && *tmp == 0) {
+ while(rem) {
+ int digit = rem % base;
+
+ if (digit < 10) {
+ (*write_func)(arg, digit + '0'); n++;
+ } else {
+ (*write_func)(arg, 'A' + (digit - 10)); n++;
+ }
+
+ rem /= base;
+ }
+ break;
+ } else {
Uint i = digits_per_Sint;
- while(i--) {
- (*write_func)(arg, (rem % 10)+'0'); n++;
- rem /= 10;
- }
- }
- }
- erts_free(ERTS_ALC_T_TMP, (void *) tmp);
+
+ while(i--) {
+ int digit = rem % base;
+
+ if (digit < 10) {
+ (*write_func)(arg, digit + '0'); n++;
+ } else {
+ (*write_func)(arg, 'A' + (digit - 10)); n++;
+ }
+
+ rem /= base;
+ }
+ }
+ }
+ erts_free(ERTS_ALC_T_TMP, (void *) tmp);
}
if (sign) {
- (*write_func)(arg, '-'); n++;
+ (*write_func)(arg, '-'); n++;
}
+
return n;
}
@@ -1804,12 +1831,12 @@ write_list(void *arg, char c)
blp->hp += 2;
}
-Eterm erts_big_to_list(Eterm x, Eterm **hpp)
+Eterm erts_big_to_list(Eterm x, int base, Eterm **hpp)
{
struct big_list__ bl;
bl.hp = *hpp;
bl.res = NIL;
- write_big(x, write_list, (void *) &bl);
+ write_big(x, base, write_list, (void *) &bl);
*hpp = bl.hp;
return bl.res;
}
@@ -1820,11 +1847,11 @@ write_string(void *arg, char c)
*(--(*((char **) arg))) = c;
}
-char *erts_big_to_string(Wterm x, char *buf, Uint buf_sz)
+char *erts_big_to_string(Wterm x, int base, char *buf, Uint buf_sz)
{
char *big_str = buf + buf_sz - 1;
*big_str = '\0';
- write_big(x, write_string, (void *) &big_str);
+ write_big(x, base, write_string, (void*)&big_str);
ASSERT(buf <= big_str && big_str <= buf + buf_sz - 1);
return big_str;
}
@@ -1833,11 +1860,11 @@ char *erts_big_to_string(Wterm x, char *buf, Uint buf_sz)
* e.g. 1 bsl 64 -> "18446744073709551616"
*/
-Uint erts_big_to_binary_bytes(Eterm x, char *buf, Uint buf_sz)
+Uint erts_big_to_binary_bytes(Eterm x, int base, char *buf, Uint buf_sz)
{
char *big_str = buf + buf_sz;
Uint n;
- n = write_big(x, write_string, (void *) &big_str);
+ n = write_big(x, base, write_string, (void *) &big_str);
ASSERT(buf <= big_str && big_str <= buf + buf_sz);
return n;
}
@@ -2580,9 +2607,6 @@ static const double lg2_lookup[36-1] = {
4.32193, 4.39232, 4.45943, 4.52356, 4.58496, 4.64386, 4.70044, 4.75489,
4.80735, 4.85798, 4.90689, 4.9542, 5.0, 5.04439, 5.08746, 5.12928, 5.16993
};
-static ERTS_INLINE double lookup_log2(Uint base) {
- return lg2_lookup[base - 2];
-}
/*
* How many digits can fit into a signed int (Sint) for given base, we take
diff --git a/erts/emulator/beam/big.h b/erts/emulator/beam/big.h
index 7556205063..ad19cce395 100644
--- a/erts/emulator/beam/big.h
+++ b/erts/emulator/beam/big.h
@@ -42,7 +42,7 @@ typedef Uint16 ErtsHalfDigit;
#undef BIG_HAVE_DOUBLE_DIGIT
typedef Uint32 ErtsHalfDigit;
#else
-#error "can not determine machine size"
+#error "cannot determine machine size"
#endif
typedef Uint dsize_t; /* Vector size type */
@@ -81,7 +81,11 @@ typedef Uint dsize_t; /* Vector size type */
* a Uint64 argument. Therefore, we must test the size of the argument
* to ensure that the cast does not discard the high-order 32 bits.
*/
-#define _IS_SSMALL32(x) (((Uint32) ((((x)) >> (SMALL_BITS-1)) + 1)) < 2)
+#if defined(ARCH_32)
+# define _IS_SSMALL32(x) (((Uint32) ((((x)) >> (SMALL_BITS-1)) + 1)) < 2)
+#else
+# define _IS_SSMALL32(x) (1)
+#endif
#define _IS_SSMALL64(x) (((Uint64) ((((x)) >> (SMALL_BITS-1)) + 1)) < 2)
#define IS_SSMALL(x) (sizeof(x) == sizeof(Uint32) ? _IS_SSMALL32(x) : _IS_SSMALL64(x))
@@ -119,10 +123,10 @@ typedef Uint dsize_t; /* Vector size type */
#endif
-int big_decimal_estimate(Wterm);
-Eterm erts_big_to_list(Eterm, Eterm**);
-char *erts_big_to_string(Wterm x, char *buf, Uint buf_sz);
-Uint erts_big_to_binary_bytes(Eterm x, char *buf, Uint buf_sz);
+int big_integer_estimate(Wterm, Uint base);
+Eterm erts_big_to_list(Eterm, int base, Eterm**);
+char *erts_big_to_string(Wterm x, int base, char *buf, Uint buf_sz);
+Uint erts_big_to_binary_bytes(Eterm x, int base, char *buf, Uint buf_sz);
Eterm small_times(Sint, Sint, Eterm*);
diff --git a/erts/emulator/beam/binary.c b/erts/emulator/beam/binary.c
index 6a349764b2..a18228b84a 100644
--- a/erts/emulator/beam/binary.c
+++ b/erts/emulator/beam/binary.c
@@ -322,40 +322,102 @@ BIF_RETTYPE binary_to_integer_2(BIF_ALIST_2)
}
+static Eterm integer_to_binary(Process *c_p, Eterm num, int base)
+{
+ Eterm res;
+
+ if (is_small(num)) {
+ char s[128];
+ char *c = s;
+ Uint digits;
+
+ digits = Sint_to_buf(signed_val(num), base, &c, sizeof(s));
+ res = new_binary(c_p, (byte*)c, digits);
+ } else {
+ const int DIGITS_PER_RED = 16;
+ Uint digits, n;
+ byte *bytes;
+
+ digits = big_integer_estimate(num, base);
+
+ if ((digits / DIGITS_PER_RED) > ERTS_BIF_REDS_LEFT(c_p)) {
+ ErtsSchedulerData *esdp = erts_get_scheduler_data();
+
+ /* This could take a very long time, tell the caller to reschedule
+ * us to a dirty CPU scheduler if we aren't already on one. */
+ if (esdp->type == ERTS_SCHED_NORMAL) {
+ return THE_NON_VALUE;
+ }
+ } else {
+ BUMP_REDS(c_p, digits / DIGITS_PER_RED);
+ }
+
+ bytes = (byte*)erts_alloc(ERTS_ALC_T_TMP, sizeof(byte) * digits);
+ n = erts_big_to_binary_bytes(num, base, (char*)bytes, digits);
+ res = new_binary(c_p, bytes + digits - n, n);
+ erts_free(ERTS_ALC_T_TMP, (void*)bytes);
+ }
+
+ return res;
+}
+
BIF_RETTYPE integer_to_binary_1(BIF_ALIST_1)
-{
- Uint size;
+{
Eterm res;
if (is_not_integer(BIF_ARG_1)) {
- BIF_ERROR(BIF_P, BADARG);
+ BIF_ERROR(BIF_P, BADARG);
}
- if (is_small(BIF_ARG_1)) {
- char *c;
- struct Sint_buf ibuf;
+ res = integer_to_binary(BIF_P, BIF_ARG_1, 10);
+
+ if (is_non_value(res)) {
+ Eterm args[1];
+ args[0] = BIF_ARG_1;
+ return erts_schedule_bif(BIF_P,
+ args,
+ BIF_I,
+ integer_to_binary_1,
+ ERTS_SCHED_DIRTY_CPU,
+ am_erlang,
+ am_integer_to_binary,
+ 1);
+ }
- /* Enhancement: If we can calculate the buffer size exactly
- * we could avoid an unnecessary copy of buffers.
- * Useful if size determination is faster than a copy.
- */
- c = Sint_to_buf(signed_val(BIF_ARG_1), &ibuf);
- size = sys_strlen(c);
- res = new_binary(BIF_P, (byte *)c, size);
- } else {
- byte* bytes;
- Uint n = 0;
+ return res;
+}
- /* Here we also have multiple copies of buffers
- * due to new_binary interface
- */
- size = big_decimal_estimate(BIF_ARG_1) - 1; /* remove null */
- bytes = (byte*) erts_alloc(ERTS_ALC_T_TMP, sizeof(byte)*size);
- n = erts_big_to_binary_bytes(BIF_ARG_1, (char *)bytes, size);
- res = new_binary(BIF_P, bytes + size - n, n);
- erts_free(ERTS_ALC_T_TMP, (void *) bytes);
+BIF_RETTYPE integer_to_binary_2(BIF_ALIST_2)
+{
+ Eterm res;
+ SWord base;
+
+ if (is_not_integer(BIF_ARG_1) || is_not_small(BIF_ARG_2)) {
+ BIF_ERROR(BIF_P, BADARG);
+ }
+
+ base = signed_val(BIF_ARG_2);
+ if (base < 2 || base > 36) {
+ BIF_ERROR(BIF_P, BADARG);
}
- BIF_RET(res);
+
+ res = integer_to_binary(BIF_P, BIF_ARG_1, base);
+
+ if (is_non_value(res)) {
+ Eterm args[2];
+ args[0] = BIF_ARG_1;
+ args[1] = BIF_ARG_2;
+ return erts_schedule_bif(BIF_P,
+ args,
+ BIF_I,
+ integer_to_binary_2,
+ ERTS_SCHED_DIRTY_CPU,
+ am_erlang,
+ am_integer_to_binary,
+ 2);
+ }
+
+ return res;
}
#define ERTS_B2L_BYTES_PER_REDUCTION 256
diff --git a/erts/emulator/beam/break.c b/erts/emulator/beam/break.c
index 9ff52c92b8..27bf2187c2 100644
--- a/erts/emulator/beam/break.c
+++ b/erts/emulator/beam/break.c
@@ -82,7 +82,7 @@ process_info(fmtfn_t to, void *to_arg)
* they are most likely just created and has invalid data
*/
if (!ERTS_PROC_IS_EXITING(p) && p->heap != NULL)
- print_process_info(to, to_arg, p);
+ print_process_info(to, to_arg, p, 0);
}
}
@@ -101,13 +101,14 @@ process_killer(void)
rp = erts_pix2proc(i);
if (rp && rp->i != ENULL) {
int br;
- print_process_info(ERTS_PRINT_STDOUT, NULL, rp);
+ print_process_info(ERTS_PRINT_STDOUT, NULL, rp, 0);
erts_printf("(k)ill (n)ext (r)eturn:\n");
while(1) {
if ((j = sys_get_key(0)) <= 0)
erts_exit(0, "");
switch(j) {
case 'k':
+ ASSERT(erts_init_process_id != ERTS_INVALID_PID);
/* Send a 'kill' exit signal from init process */
erts_proc_sig_send_exit(NULL, erts_init_process_id,
rp->common.id, am_kill, NIL,
@@ -128,7 +129,7 @@ typedef struct {
void *to_arg;
} PrintMonitorContext;
-static void doit_print_link(ErtsLink *lnk, void *vpcontext)
+static int doit_print_link(ErtsLink *lnk, void *vpcontext, Sint reds)
{
PrintMonitorContext *pcontext = vpcontext;
fmtfn_t to = pcontext->to;
@@ -140,10 +141,11 @@ static void doit_print_link(ErtsLink *lnk, void *vpcontext)
} else {
erts_print(to, to_arg, ", %T", lnk->other.item);
}
+ return 1;
}
-static void doit_print_monitor(ErtsMonitor *mon, void *vpcontext)
+static int doit_print_monitor(ErtsMonitor *mon, void *vpcontext, Sint reds)
{
ErtsMonitorData *mdp;
PrintMonitorContext *pcontext = vpcontext;
@@ -195,17 +197,19 @@ static void doit_print_monitor(ErtsMonitor *mon, void *vpcontext)
/* ignore other monitors... */
break;
}
+ return 1;
}
/* Display info about an individual Erlang process */
void
-print_process_info(fmtfn_t to, void *to_arg, Process *p)
+print_process_info(fmtfn_t to, void *to_arg, Process *p, ErtsProcLocks orig_locks)
{
int garbing = 0;
int running = 0;
Sint len;
struct saved_calls *scb;
erts_aint32_t state;
+ ErtsProcLocks locks = orig_locks;
/* display the PID */
erts_print(to, to_arg, "=proc:%T\n", p->common.id);
@@ -222,6 +226,22 @@ print_process_info(fmtfn_t to, void *to_arg, Process *p)
| ERTS_PSFLG_DIRTY_RUNNING))
running = 1;
+ if (!(locks & ERTS_PROC_LOCK_MAIN)) {
+ locks |= ERTS_PROC_LOCK_MAIN;
+ if (ERTS_IS_CRASH_DUMPING && running) {
+ if (erts_proc_trylock(p, locks)) {
+ /* crash dumping and main lock taken, this probably means that
+ the process is doing a GC on a dirty-scheduler... so we cannot
+ do erts_proc_sig_fetch as that would potentially cause a segfault */
+ locks = 0;
+ }
+ } else {
+ erts_proc_lock(p, locks);
+ }
+ } else {
+ ERTS_ASSERT(locks == ERTS_PROC_LOCK_MAIN && "Only main lock should be held");
+ }
+
/*
* If the process is registered as a global process, display the
* registered name
@@ -251,13 +271,19 @@ print_process_info(fmtfn_t to, void *to_arg, Process *p)
erts_print(to, to_arg, "Spawned by: %T\n", p->parent);
- erts_proc_lock(p, ERTS_PROC_LOCK_MSGQ);
- len = erts_proc_sig_fetch(p);
- erts_proc_unlock(p, ERTS_PROC_LOCK_MSGQ);
+ if (locks & ERTS_PROC_LOCK_MAIN) {
+ erts_proc_lock(p, ERTS_PROC_LOCK_MSGQ);
+ len = erts_proc_sig_fetch(p);
+ erts_proc_unlock(p, ERTS_PROC_LOCK_MSGQ);
+ } else {
+ len = p->sig_qs.len;
+ }
erts_print(to, to_arg, "Message queue length: %d\n", len);
- /* display the message queue only if there is anything in it */
- if (!ERTS_IS_CRASH_DUMPING && p->sig_qs.first != NULL && !garbing) {
+ /* display the message queue only if there is anything in it
+ and we can do it safely */
+ if (!ERTS_IS_CRASH_DUMPING && p->sig_qs.first != NULL && !garbing
+ && (locks & ERTS_PROC_LOCK_MAIN)) {
erts_print(to, to_arg, "Message queue: [");
ERTS_FOREACH_SIG_PRIVQS(
p, mp,
@@ -357,6 +383,8 @@ print_process_info(fmtfn_t to, void *to_arg, Process *p)
/* Display all states */
erts_print(to, to_arg, "Internal State: ");
erts_dump_extended_process_state(to, to_arg, state);
+
+ erts_proc_unlock(p, locks & ~orig_locks);
}
static void
diff --git a/erts/emulator/beam/bs_instrs.tab b/erts/emulator/beam/bs_instrs.tab
index 61eb02a7a2..2dde70c2e1 100644
--- a/erts/emulator/beam/bs_instrs.tab
+++ b/erts/emulator/beam/bs_instrs.tab
@@ -102,6 +102,7 @@ i_bs_get_binary_all2(Fail, Ms, Live, Unit, Dst) {
LIGHT_SWAPIN;
HEAP_SPACE_VERIFIED(0);
ASSERT(is_value(_result));
+ $REFRESH_GEN_DEST();
$Dst = _result;
} else {
HEAP_SPACE_VERIFIED(0);
@@ -123,6 +124,7 @@ i_bs_get_binary2(Fail, Ms, Live, Sz, Flags, Dst) {
if (is_non_value(_result)) {
$FAIL($Fail);
} else {
+ $REFRESH_GEN_DEST();
$Dst = _result;
}
}
@@ -139,6 +141,7 @@ i_bs_get_binary_imm2(Fail, Ms, Live, Sz, Flags, Dst) {
if (is_non_value(_result)) {
$FAIL($Fail);
} else {
+ $REFRESH_GEN_DEST();
$Dst = _result;
}
}
@@ -161,6 +164,7 @@ i_bs_get_float2(Fail, Ms, Live, Sz, Flags, Dst) {
if (is_non_value(_result)) {
$FAIL($Fail);
} else {
+ $REFRESH_GEN_DEST();
$Dst = _result;
}
}
@@ -724,26 +728,34 @@ bs_start_match.execute(Fail, Live, Slots, Dst) {
$FAIL($Fail);
}
header = *boxed_val(context);
- slots = $Slots;
+
+ /* Reserve a slot for the start position. */
+ slots = $Slots + 1;
live = $Live;
+
if (header_is_bin_matchstate(header)) {
ErlBinMatchState* ms = (ErlBinMatchState *) boxed_val(context);
Uint actual_slots = HEADER_NUM_SLOTS(header);
+
+ /* We're not compatible with contexts created by bs_start_match3. */
+ ASSERT(actual_slots >= 1);
+
ms->save_offset[0] = ms->mb.offset;
- if (actual_slots < slots) {
- ErlBinMatchState* dst;
+ if (ERTS_UNLIKELY(actual_slots < slots)) {
+ ErlBinMatchState* expanded;
Uint live = $Live;
Uint wordsneeded = ERL_BIN_MATCHSTATE_SIZE(slots);
-
$GC_TEST_PRESERVE(wordsneeded, live, context);
ms = (ErlBinMatchState *) boxed_val(context);
- dst = (ErlBinMatchState *) HTOP;
- *dst = *ms;
+ expanded = (ErlBinMatchState *) HTOP;
+ *expanded = *ms;
*HTOP = HEADER_BIN_MATCHSTATE(slots);
HTOP += wordsneeded;
HEAP_SPACE_VERIFIED(0);
- $Dst = make_matchstate(dst);
+ context = make_matchstate(expanded);
+ $REFRESH_GEN_DEST();
}
+ $Dst = context;
} else if (is_binary_header(header)) {
Eterm result;
Uint wordsneeded = ERL_BIN_MATCHSTATE_SIZE(slots);
@@ -758,6 +770,7 @@ bs_start_match.execute(Fail, Live, Slots, Dst) {
if (is_non_value(result)) {
$FAIL($Fail);
}
+ $REFRESH_GEN_DEST();
$Dst = result;
} else {
$FAIL($Fail);
@@ -906,6 +919,7 @@ i_bs_get_integer(Fail, Live, FlagsAndUnit, Ms, Sz, Dst) {
}
wordsneeded = 1+WSIZE(NBYTES((Uint) size));
$GC_TEST_PRESERVE(wordsneeded, $Live, ms);
+ $REFRESH_GEN_DEST();
}
mb = ms_matchbuffer(ms);
LIGHT_SWAPOUT;
@@ -939,6 +953,7 @@ i_bs_get_utf8(Ctx, Fail, Dst) {
if (is_non_value(result)) {
$FAIL($Fail);
}
+ $REFRESH_GEN_DEST();
$Dst = result;
}
@@ -949,6 +964,7 @@ i_bs_get_utf16(Ctx, Fail, Flags, Dst) {
if (is_non_value(result)) {
$FAIL($Fail);
}
+ $REFRESH_GEN_DEST();
$Dst = result;
}
@@ -1029,10 +1045,289 @@ i_bs_match_string(Ctx, Fail, Bits, Ptr) {
i_bs_save2(Src, Slot) {
ErlBinMatchState* _ms = (ErlBinMatchState*) boxed_val((Eterm) $Src);
+ ASSERT(HEADER_NUM_SLOTS(_ms->thing_word) > $Slot);
_ms->save_offset[$Slot] = _ms->mb.offset;
}
i_bs_restore2(Src, Slot) {
ErlBinMatchState* _ms = (ErlBinMatchState*) boxed_val((Eterm) $Src);
+ ASSERT(HEADER_NUM_SLOTS(_ms->thing_word) > $Slot);
_ms->mb.offset = _ms->save_offset[$Slot];
}
+
+bs_get_tail(Src, Dst, Live) {
+ ErlBinMatchBuffer* mb;
+ Uint size, offs;
+ ErlSubBin* sb;
+ Eterm context;
+
+ context = $Src;
+
+ ASSERT(header_is_bin_matchstate(*boxed_val(context)));
+
+ $GC_TEST_PRESERVE(ERL_SUB_BIN_SIZE, $Live, context);
+
+ mb = ms_matchbuffer(context);
+
+ offs = mb->offset;
+ size = mb->size - offs;
+
+ sb = (ErlSubBin *) HTOP;
+ HTOP += ERL_SUB_BIN_SIZE;
+
+ sb->thing_word = HEADER_SUB_BIN;
+ sb->size = BYTE_OFFSET(size);
+ sb->bitsize = BIT_OFFSET(size);
+ sb->offs = BYTE_OFFSET(offs);
+ sb->bitoffs = BIT_OFFSET(offs);
+ sb->is_writable = 0;
+ sb->orig = mb->orig;
+
+ $REFRESH_GEN_DEST();
+ $Dst = make_binary(sb);
+}
+
+
+%if ARCH_64
+
+i_bs_start_match3_gp(Src, Live, Fail, Dst, Pos) {
+ Eterm context, header;
+ Uint position, live;
+
+ context = $Src;
+ live = $Live;
+
+ if (!is_boxed(context)) {
+ $FAIL($Fail);
+ }
+
+ header = *boxed_val(context);
+
+ if (header_is_bin_matchstate(header)) {
+ ErlBinMatchBuffer *mb;
+
+ ASSERT(HEADER_NUM_SLOTS(header) == 0);
+
+ mb = ms_matchbuffer(context);
+ position = mb->offset;
+
+ $Dst = context;
+ } else if (is_binary_header(header)) {
+ ErlBinMatchState *ms;
+
+ $GC_TEST_PRESERVE(ERL_BIN_MATCHSTATE_SIZE(0), live, context);
+ HEAP_TOP(c_p) = HTOP;
+#ifdef DEBUG
+ c_p->stop = E; /* Needed for checking in HeapOnlyAlloc(). */
+#endif
+ ms = erts_bs_start_match_3(c_p, context);
+ HTOP = HEAP_TOP(c_p);
+ HEAP_SPACE_VERIFIED(0);
+
+ if (ms == NULL) {
+ $FAIL($Fail);
+ }
+
+ $REFRESH_GEN_DEST();
+ $Dst = make_matchstate(ms);
+ position = ms->mb.offset;
+ } else {
+ $FAIL($Fail);
+ }
+
+ ASSERT(IS_USMALL(0, position));
+ $Pos = make_small(position);
+}
+
+i_bs_start_match3(Src, Live, Fail, Dst) {
+ Eterm context, header;
+ Uint live;
+
+ context = $Src;
+ live = $Live;
+
+ if (!is_boxed(context)) {
+ $FAIL($Fail);
+ }
+
+ header = *boxed_val(context);
+
+ if (header_is_bin_matchstate(header)) {
+ ASSERT(HEADER_NUM_SLOTS(header) == 0);
+ $Dst = context;
+ } else if (is_binary_header(header)) {
+ ErlBinMatchState *ms;
+
+ $GC_TEST_PRESERVE(ERL_BIN_MATCHSTATE_SIZE(0), live, context);
+ HEAP_TOP(c_p) = HTOP;
+#ifdef DEBUG
+ c_p->stop = E; /* Needed for checking in HeapOnlyAlloc(). */
+#endif
+ ms = erts_bs_start_match_3(c_p, context);
+ HTOP = HEAP_TOP(c_p);
+ HEAP_SPACE_VERIFIED(0);
+
+ if (ms == NULL) {
+ $FAIL($Fail);
+ }
+
+ $REFRESH_GEN_DEST();
+ $Dst = make_matchstate(ms);
+ } else {
+ $FAIL($Fail);
+ }
+}
+
+bs_set_position(Ctx, Pos) {
+ ErlBinMatchBuffer* mb;
+ Eterm context;
+
+ context = $Ctx;
+ ASSERT(header_is_bin_matchstate(*boxed_val(context)));
+
+ mb = ms_matchbuffer(context);
+ mb->offset = unsigned_val($Pos);
+}
+
+i_bs_get_position(Ctx, Dst) {
+ ErlBinMatchBuffer* mb;
+ Eterm context;
+
+ context = $Ctx;
+ ASSERT(header_is_bin_matchstate(*boxed_val(context)));
+
+ mb = ms_matchbuffer(context);
+ $Dst = make_small(mb->offset);
+}
+
+%else
+
+#
+# Unlike their 64-bit counterparts, the 32-bit position instructions operate on
+# an offset from the "base position" of the context because storing raw
+# positions would lead to the creation of far too many bigints.
+#
+# When a match context is reused we check whether its position fits into an
+# immediate, and create a new match context if it does not. This means we only
+# have to allocate stuff roughly once every 16MB rather than every time we
+# match at a position beyond 16MB.
+#
+
+bs_set_position(Ctx, Pos) {
+ Eterm context, position;
+ ErlBinMatchState *ms;
+
+ context = $Ctx;
+ position = $Pos;
+
+ ASSERT(header_is_bin_matchstate(*boxed_val(context)));
+ ms = (ErlBinMatchState*)boxed_val(context);
+
+ if (ERTS_LIKELY(is_small(position))) {
+ ms->mb.offset = ms->save_offset[0] + unsigned_val(position);
+ } else {
+ ASSERT(is_big(position));
+ ms->mb.offset = ms->save_offset[0] + *BIG_V(big_val(position));
+ }
+}
+
+bs_get_position(Ctx, Dst, Live) {
+ ErlBinMatchState *ms;
+ Eterm context;
+ Uint position;
+
+ context = $Ctx;
+
+ ASSERT(header_is_bin_matchstate(*boxed_val(context)));
+ ms = (ErlBinMatchState*)boxed_val(context);
+
+ position = ms->mb.offset - ms->save_offset[0];
+
+ if (ERTS_LIKELY(IS_USMALL(0, position))) {
+ $Dst = make_small(position);
+ } else {
+ Eterm *hp;
+
+ $GC_TEST_PRESERVE(BIG_UINT_HEAP_SIZE, $Live, context);
+
+ hp = HTOP;
+ HTOP += BIG_UINT_HEAP_SIZE;
+
+ *hp = make_pos_bignum_header(1);
+ BIG_DIGIT(hp, 0) = position;
+
+ $REFRESH_GEN_DEST();
+ $Dst = make_big(hp);
+ }
+}
+
+i_bs_start_match3(Src, Live, Fail, Dst) {
+ Eterm context, header;
+ Uint live;
+
+ context = $Src;
+ live = $Live;
+
+ if (!is_boxed(context)) {
+ $FAIL($Fail);
+ }
+
+ header = *boxed_val(context);
+
+ if (header_is_bin_matchstate(header)) {
+ ErlBinMatchState *current_ms;
+ Uint position;
+
+ ASSERT(HEADER_NUM_SLOTS(header) == 1);
+
+ current_ms = (ErlBinMatchState*)boxed_val(context);
+ position = current_ms->mb.offset - current_ms->save_offset[0];
+
+ if (ERTS_LIKELY(IS_USMALL(0, position))) {
+ $Dst = context;
+ } else {
+ ErlBinMatchState *new_ms;
+
+ $GC_TEST_PRESERVE(ERL_BIN_MATCHSTATE_SIZE(1), live, context);
+ current_ms = (ErlBinMatchState*)boxed_val(context);
+
+ new_ms = (ErlBinMatchState*)HTOP;
+ HTOP += ERL_BIN_MATCHSTATE_SIZE(1);
+
+ new_ms->thing_word = HEADER_BIN_MATCHSTATE(1);
+ new_ms->save_offset[0] = current_ms->mb.offset;
+ new_ms->mb = current_ms->mb;
+
+ $REFRESH_GEN_DEST();
+ $Dst = make_matchstate(new_ms);
+ }
+ } else if (is_binary_header(header)) {
+ Eterm result;
+
+ $GC_TEST_PRESERVE(ERL_BIN_MATCHSTATE_SIZE(1), live, context);
+ HEAP_TOP(c_p) = HTOP;
+
+#ifdef DEBUG
+ c_p->stop = E; /* Needed for checking in HeapOnlyAlloc(). */
+#endif
+
+ /* We intentionally use erts_bs_start_match_2 so that we can use
+ * save_offset as a base for all saved positions on this context,
+ * allowing us to avoid bigints for much longer. */
+ result = erts_bs_start_match_2(c_p, context, 1);
+
+ HTOP = HEAP_TOP(c_p);
+ HEAP_SPACE_VERIFIED(0);
+
+ if (is_non_value(result)) {
+ $FAIL($Fail);
+ }
+
+ $REFRESH_GEN_DEST();
+ $Dst = result;
+ } else {
+ $FAIL($Fail);
+ }
+}
+
+%endif
diff --git a/erts/emulator/beam/copy.c b/erts/emulator/beam/copy.c
index e7bd046e18..db74b06cc5 100644
--- a/erts/emulator/beam/copy.c
+++ b/erts/emulator/beam/copy.c
@@ -854,7 +854,7 @@ Eterm copy_struct_x(Eterm obj, Uint sz, Eterm** hpp, ErlOffHeap* off_heap, Uint
case EXTERNAL_REF_SUBTAG:
{
ExternalThing *etp = (ExternalThing *) objp;
- erts_refc_inc(&etp->node->refc, 2);
+ erts_ref_node_entry(etp->node, 2, make_boxed(htop));
}
L_off_heap_node_container_common:
{
@@ -1660,7 +1660,7 @@ Uint copy_shared_perform(Eterm obj, Uint size, erts_shcopy_t *info,
case EXTERNAL_REF_SUBTAG:
{
ExternalThing *etp = (ExternalThing *) ptr;
- erts_refc_inc(&etp->node->refc, 2);
+ erts_ref_node_entry(etp->node, 2, make_boxed(hp));
}
off_heap_node_container_common:
{
@@ -1866,7 +1866,7 @@ Eterm copy_shallow(Eterm* ERTS_RESTRICT ptr, Uint sz, Eterm** hpp,
case EXTERNAL_REF_SUBTAG:
{
ExternalThing* etp = (ExternalThing *) (tp-1);
- erts_refc_inc(&etp->node->refc, 2);
+ erts_ref_node_entry(etp->node, 2, make_boxed(hp-1));
}
off_heap_common:
{
diff --git a/erts/emulator/beam/dist.c b/erts/emulator/beam/dist.c
index 0633bff3c2..b50c8273b1 100644
--- a/erts/emulator/beam/dist.c
+++ b/erts/emulator/beam/dist.c
@@ -50,19 +50,26 @@
#define DIST_CTL_DEFAULT_SIZE 64
/* Turn this on to get printouts of all distribution messages
- * which go on the line
+ * which go on the line. Enabling this may make some testcases
+ * fail. Especially the broken dist testcases in distribution_SUITE.
*/
#if 0
#define ERTS_DIST_MSG_DBG
+FILE *dbg_file;
#endif
#if 0
+/* Enable this to print the dist debug messages to a file instead */
+#define ERTS_DIST_MSG_DBG_FILE "/tmp/dist_dbg.%d"
+#endif
+#if 0
+/* Enable this to print the raw bytes sent and received */
#define ERTS_RAW_DIST_MSG_DBG
#endif
#if defined(ERTS_DIST_MSG_DBG) || defined(ERTS_RAW_DIST_MSG_DBG)
static void bw(byte *buf, ErlDrvSizeT sz)
{
- bin_write(ERTS_PRINT_STDERR, NULL, buf, sz);
+ bin_write(ERTS_PRINT_FILE, dbg_file, buf, sz);
}
#endif
@@ -70,39 +77,93 @@ static void bw(byte *buf, ErlDrvSizeT sz)
static void
dist_msg_dbg(ErtsDistExternal *edep, char *what, byte *buf, int sz)
{
- ErtsHeapFactory factory;
- DeclareTmpHeapNoproc(ctl_default,DIST_CTL_DEFAULT_SIZE);
- Eterm* ctl = ctl_default;
- byte *extp = edep->extp;
+ byte *extp = edep->data->extp;
Eterm msg;
Sint ctl_len;
- Sint size = ctl_len = erts_decode_dist_ext_size(edep);
+ Sint size = ctl_len = erts_decode_dist_ext_size(edep, 0);
if (size < 0) {
- erts_fprintf(stderr,
+ erts_fprintf(dbg_file,
"DIST MSG DEBUG: erts_decode_dist_ext_size(%s) failed:\n",
what);
bw(buf, sz);
}
else {
- ErlHeapFragment *mbuf = new_message_buffer(size);
- erts_factory_static_init(&factory, ctl, ctl_len, &mbuf->off_heap);
- msg = erts_decode_dist_ext(&factory, edep);
+ ErtsHeapFactory factory;
+ ErtsMessage *mbuf = erts_factory_message_create(&factory, NULL, 0, ctl_len);
+ /* Set mbuf msg to NIL as erts_factory_undo will fail otherwise */
+ ERL_MESSAGE_TERM(mbuf) = NIL;
+ msg = erts_decode_dist_ext(&factory, edep, 0);
if (is_value(msg))
- erts_fprintf(stderr, " %s: %T\n", what, msg);
+ erts_fprintf(dbg_file, " %s: %.80T\n", what, msg);
else {
- erts_fprintf(stderr,
+ erts_fprintf(dbg_file,
"DIST MSG DEBUG: erts_decode_dist_ext(%s) failed:\n",
what);
bw(buf, sz);
}
- free_message_buffer(mbuf);
- edep->extp = extp;
+ erts_factory_undo(&factory);
+ edep->data->extp = extp;
}
}
+static char *erts_dop_to_string(enum dop dop) {
+ if (dop == DOP_LINK)
+ return "LINK";
+ if (dop == DOP_SEND)
+ return "SEND";
+ if (dop == DOP_EXIT)
+ return "EXIT";
+ if (dop == DOP_UNLINK)
+ return "UNLINK";
+ if (dop == DOP_REG_SEND)
+ return "REG_SEND";
+ if (dop == DOP_GROUP_LEADER)
+ return "GROUP_LEADER";
+ if (dop == DOP_EXIT2)
+ return "EXIT2";
+ if (dop == DOP_SEND_TT)
+ return "SEND_TT";
+ if (dop == DOP_EXIT_TT)
+ return "EXIT_TT";
+ if (dop == DOP_REG_SEND_TT)
+ return "REG_SEND_TT";
+ if (dop == DOP_EXIT2_TT)
+ return "EXIT2_TT";
+ if (dop == DOP_MONITOR_P)
+ return "MONITOR_P";
+ if (dop == DOP_DEMONITOR_P)
+ return "DEMONITOR_P";
+ if (dop == DOP_MONITOR_P_EXIT)
+ return "MONITOR_P_EXIT";
+ if (dop == DOP_SEND_SENDER)
+ return "SEND_SENDER";
+ if (dop == DOP_SEND_SENDER_TT)
+ return "SEND_SENDER_TT";
+ if (dop == DOP_PAYLOAD_EXIT)
+ return "PAYLOAD_EXIT";
+ if (dop == DOP_PAYLOAD_EXIT_TT)
+ return "PAYLOAD_EXIT_TT";
+ if (dop == DOP_PAYLOAD_EXIT2)
+ return "PAYLOAD_EXIT2";
+ if (dop == DOP_PAYLOAD_EXIT2_TT)
+ return "PAYLOAD_EXIT2_TT";
+ if (dop == DOP_PAYLOAD_MONITOR_P_EXIT)
+ return "PAYLOAD_MONITOR_P_EXIT";
+ ASSERT(0);
+ return "UNKNOWN";
+}
+
#endif
+#if defined(VALGRIND)
+#include <valgrind/valgrind.h>
+#include <valgrind/memcheck.h>
+# define PURIFY_MSG(msg) \
+ VALGRIND_PRINTF("%s, line %d: %s", __FILE__, __LINE__, msg)
+#else
+# define PURIFY_MSG(msg)
+#endif
int erts_is_alive; /* System must be blocked on change */
int erts_dist_buf_busy_limit;
@@ -116,12 +177,17 @@ static Export *dist_ctrl_put_data_trap;
/* forward declarations */
-static int dsig_send_ctl(ErtsDSigData* dsdp, Eterm ctl, int force_busy);
+static void erts_schedule_dist_command(Port *, DistEntry *);
+static int dsig_send_exit(ErtsDSigSendContext *ctx, Eterm ctl, Eterm msg);
+static int dsig_send_ctl(ErtsDSigSendContext *ctx, Eterm ctl);
static void send_nodes_mon_msgs(Process *, Eterm, Eterm, Eterm, Eterm);
static void init_nodes_monitors(void);
static Sint abort_connection(DistEntry* dep, Uint32 conn_id);
static ErtsDistOutputBuf* clear_de_out_queues(DistEntry*);
static void free_de_out_queues(DistEntry*, ErtsDistOutputBuf*);
+int erts_dist_seq_tree_foreach_delete_yielding(DistSeqNode **root,
+ void **vyspp,
+ Sint limit);
static erts_atomic_t no_caches;
static erts_atomic_t no_nodes;
@@ -142,7 +208,6 @@ delete_cache(ErtsAtomCache *cache)
}
}
-
static void
create_cache(DistEntry *dep)
{
@@ -185,32 +250,36 @@ get_suspended_on_de(DistEntry *dep, erts_aint32_t unset_qflgs)
}
}
-#define ERTS_MON_LNK_FIRE_LIMIT 100
+#define ERTS_MON_LNK_FIRE_REDS 40
-static void monitor_connection_down(ErtsMonitor *mon, void *unused)
+static int monitor_connection_down(ErtsMonitor *mon, void *unused, Sint reds)
{
if (erts_monitor_is_origin(mon))
erts_proc_sig_send_demonitor(mon);
else
erts_proc_sig_send_monitor_down(mon, am_noconnection);
+ return ERTS_MON_LNK_FIRE_REDS;
}
-static void link_connection_down(ErtsLink *lnk, void *vdist)
+static int link_connection_down(ErtsLink *lnk, void *vdist, Sint reds)
{
erts_proc_sig_send_link_exit(NULL, THE_NON_VALUE, lnk,
am_noconnection, NIL);
+ return ERTS_MON_LNK_FIRE_REDS;
}
typedef enum {
ERTS_CML_CLEANUP_STATE_LINKS,
ERTS_CML_CLEANUP_STATE_MONITORS,
ERTS_CML_CLEANUP_STATE_ONAME_MONITORS,
+ ERTS_CML_CLEANUP_STATE_SEQUENCES,
ERTS_CML_CLEANUP_STATE_NODE_MONITORS
-} ErtsConMonLnkCleaupState;
+} ErtsConMonLnkSeqCleanupState;
typedef struct {
- ErtsConMonLnkCleaupState state;
+ ErtsConMonLnkSeqCleanupState state;
ErtsMonLnkDist *dist;
+ DistSeqNode *seq;
void *yield_state;
int trigger_node_monitors;
Eterm nodename;
@@ -218,49 +287,58 @@ typedef struct {
Eterm reason;
ErlOffHeap oh;
Eterm heap[1];
-} ErtsConMonLnkCleanup;
+} ErtsConMonLnkSeqCleanup;
static void
-con_monitor_link_cleanup(void *vcmlcp)
+con_monitor_link_seq_cleanup(void *vcmlcp)
{
- ErtsConMonLnkCleanup *cmlcp = vcmlcp;
+ ErtsConMonLnkSeqCleanup *cmlcp = vcmlcp;
ErtsMonLnkDist *dist = cmlcp->dist;
ErtsSchedulerData *esdp;
- int yield;
+ int reds = CONTEXT_REDS;
switch (cmlcp->state) {
case ERTS_CML_CLEANUP_STATE_LINKS:
- yield = erts_link_list_foreach_delete_yielding(&dist->links,
- link_connection_down,
- NULL, &cmlcp->yield_state,
- ERTS_MON_LNK_FIRE_LIMIT);
- if (yield)
+ reds = erts_link_list_foreach_delete_yielding(&dist->links,
+ link_connection_down,
+ NULL, &cmlcp->yield_state,
+ reds);
+ if (reds <= 0)
break;
ASSERT(!cmlcp->yield_state);
cmlcp->state = ERTS_CML_CLEANUP_STATE_MONITORS;
case ERTS_CML_CLEANUP_STATE_MONITORS:
- yield = erts_monitor_list_foreach_delete_yielding(&dist->monitors,
- monitor_connection_down,
- NULL, &cmlcp->yield_state,
- ERTS_MON_LNK_FIRE_LIMIT);
- if (yield)
+ reds = erts_monitor_list_foreach_delete_yielding(&dist->monitors,
+ monitor_connection_down,
+ NULL, &cmlcp->yield_state,
+ reds);
+ if (reds <= 0)
break;
ASSERT(!cmlcp->yield_state);
cmlcp->state = ERTS_CML_CLEANUP_STATE_ONAME_MONITORS;
case ERTS_CML_CLEANUP_STATE_ONAME_MONITORS:
- yield = erts_monitor_tree_foreach_delete_yielding(&dist->orig_name_monitors,
- monitor_connection_down,
- NULL, &cmlcp->yield_state,
- ERTS_MON_LNK_FIRE_LIMIT/2);
- if (yield)
+ reds = erts_monitor_tree_foreach_delete_yielding(&dist->orig_name_monitors,
+ monitor_connection_down,
+ NULL, &cmlcp->yield_state,
+ reds);
+ if (reds <= 0)
break;
cmlcp->dist = NULL;
erts_mon_link_dist_dec_refc(dist);
ASSERT(!cmlcp->yield_state);
+ cmlcp->state = ERTS_CML_CLEANUP_STATE_SEQUENCES;
+ case ERTS_CML_CLEANUP_STATE_SEQUENCES:
+ reds = erts_dist_seq_tree_foreach_delete_yielding(&cmlcp->seq,
+ &cmlcp->yield_state,
+ reds);
+ if (reds <= 0)
+ break;
+
+ ASSERT(!cmlcp->yield_state);
cmlcp->state = ERTS_CML_CLEANUP_STATE_NODE_MONITORS;
case ERTS_CML_CLEANUP_STATE_NODE_MONITORS:
if (cmlcp->trigger_node_monitors) {
@@ -280,22 +358,23 @@ con_monitor_link_cleanup(void *vcmlcp)
esdp = erts_get_scheduler_data();
ASSERT(esdp && esdp->type == ERTS_SCHED_NORMAL);
erts_schedule_misc_aux_work((int) esdp->no,
- con_monitor_link_cleanup,
+ con_monitor_link_seq_cleanup,
(void *) cmlcp);
}
static void
-schedule_con_monitor_link_cleanup(ErtsMonLnkDist *dist,
- Eterm nodename,
- Eterm visability,
- Eterm reason)
+schedule_con_monitor_link_seq_cleanup(ErtsMonLnkDist *dist,
+ DistSeqNode *seq,
+ Eterm nodename,
+ Eterm visability,
+ Eterm reason)
{
- if (dist || is_value(nodename)) {
+ if (dist || is_value(nodename) || seq) {
ErtsSchedulerData *esdp;
- ErtsConMonLnkCleanup *cmlcp;
+ ErtsConMonLnkSeqCleanup *cmlcp;
Uint rsz, size;
- size = sizeof(ErtsConMonLnkCleanup);
+ size = sizeof(ErtsConMonLnkSeqCleanup);
if (is_non_value(reason) || is_immed(reason)) {
rsz = 0;
@@ -322,6 +401,8 @@ schedule_con_monitor_link_cleanup(ErtsMonLnkDist *dist,
erts_mtx_unlock(&dist->mtx);
}
+ cmlcp->seq = seq;
+
cmlcp->trigger_node_monitors = is_value(nodename);
cmlcp->nodename = nodename;
cmlcp->visability = visability;
@@ -335,13 +416,13 @@ schedule_con_monitor_link_cleanup(ErtsMonLnkDist *dist,
esdp = erts_get_scheduler_data();
ASSERT(esdp && esdp->type == ERTS_SCHED_NORMAL);
erts_schedule_misc_aux_work((int) esdp->no,
- con_monitor_link_cleanup,
+ con_monitor_link_seq_cleanup,
(void *) cmlcp);
}
}
/*
-** A full node name constists of a "n@h"
+** A full node name consists of a "n@h"
**
** n must be a valid node name: string of ([a-z][A-Z][0-9]_-)+
**
@@ -558,6 +639,7 @@ int erts_do_net_exits(DistEntry *dep, Eterm reason)
}
else { /* Call from distribution controller (port/process) */
ErtsMonLnkDist *mld;
+ DistSeqNode *sequences;
ErtsAtomCache *cache;
ErtsProcList *suspendees;
ErtsDistOutputBuf *obuf;
@@ -574,9 +656,7 @@ int erts_do_net_exits(DistEntry *dep, Eterm reason)
}
if (dep->state == ERTS_DE_STATE_EXITING) {
-#ifdef DEBUG
ASSERT(erts_atomic32_read_nob(&dep->qflgs) & ERTS_DE_QFLG_EXIT);
-#endif
}
else {
dep->state = ERTS_DE_STATE_EXITING;
@@ -589,6 +669,9 @@ int erts_do_net_exits(DistEntry *dep, Eterm reason)
mld = dep->mld;
dep->mld = NULL;
+ sequences = dep->sequences;
+ dep->sequences = NULL;
+
nodename = dep->sysname;
flags = dep->flags;
@@ -612,14 +695,15 @@ int erts_do_net_exits(DistEntry *dep, Eterm reason)
erts_de_rwunlock(dep);
- schedule_con_monitor_link_cleanup(mld,
- nodename,
- (flags & DFLAG_PUBLISHED
- ? am_visible
- : am_hidden),
- (reason == am_normal
- ? am_connection_closed
- : reason));
+ schedule_con_monitor_link_seq_cleanup(mld,
+ sequences,
+ nodename,
+ (flags & DFLAG_PUBLISHED
+ ? am_visible
+ : am_hidden),
+ (reason == am_normal
+ ? am_connection_closed
+ : reason));
erts_resume_processes(suspendees);
@@ -653,6 +737,16 @@ void init_dist(void)
{
init_nodes_monitors();
+#ifdef ERTS_DIST_MSG_DBG_FILE
+ {
+ char buff[255];
+ sprintf(buff, ERTS_DIST_MSG_DBG_FILE, getpid());
+ dbg_file = fopen(buff,"w+");
+ }
+#elif defined (ERTS_DIST_MSG_DBG)
+ dbg_file = stderr;
+#endif
+
nodedown.reason = NIL;
nodedown.bp = NULL;
@@ -676,21 +770,79 @@ void init_dist(void)
}
}
-#define ErtsDistOutputBuf2Binary(OB) \
- ((Binary *) (((char *) (OB)) - offsetof(Binary, orig_bytes)))
+#define ErtsDistOutputBuf2Binary(OB) OB->bin
+
+#ifdef DEBUG
+
+struct obuf_list;
+struct obuf_list {
+ erts_refc_t refc;
+ struct obuf_list *next;
+ struct obuf_list *prev;
+};
+#define obuf_list_size sizeof(struct obuf_list)
+static struct obuf_list *erts_obuf_list = NULL;
+static erts_mtx_t erts_obuf_list_mtx;
+
+static void
+insert_obuf(struct obuf_list *obuf, erts_aint_t initial) {
+ erts_mtx_lock(&erts_obuf_list_mtx);
+ obuf->next = erts_obuf_list;
+ obuf->prev = NULL;
+ erts_refc_init(&obuf->refc, initial);
+ if (erts_obuf_list)
+ erts_obuf_list->prev = obuf;
+ erts_obuf_list = obuf;
+ erts_mtx_unlock(&erts_obuf_list_mtx);
+}
+
+static void
+remove_obuf(struct obuf_list *obuf) {
+ if (erts_refc_dectest(&obuf->refc, 0) == 0) {
+ erts_mtx_lock(&erts_obuf_list_mtx);
+ if (obuf->prev) {
+ obuf->prev->next = obuf->next;
+ } else {
+ erts_obuf_list = obuf->next;
+ }
+ if (obuf->next) obuf->next->prev = obuf->prev;
+ erts_mtx_unlock(&erts_obuf_list_mtx);
+ }
+}
+
+void check_obuf(void);
+void check_obuf(void) {
+ erts_mtx_lock(&erts_obuf_list_mtx);
+ ERTS_ASSERT(erts_obuf_list == NULL);
+ erts_mtx_unlock(&erts_obuf_list_mtx);
+}
+#else
+#define insert_obuf(...)
+#define remove_obuf(...)
+#define obuf_list_size 0
+#endif
static ERTS_INLINE ErtsDistOutputBuf *
-alloc_dist_obuf(Uint size)
+alloc_dist_obuf(Uint size, Uint headers)
{
+ int i;
ErtsDistOutputBuf *obuf;
- Uint obuf_size = sizeof(ErtsDistOutputBuf)+sizeof(byte)*(size-1);
+ Uint obuf_size = sizeof(ErtsDistOutputBuf)*(headers) +
+ sizeof(byte)*size + obuf_list_size;
Binary *bin = erts_bin_drv_alloc(obuf_size);
- obuf = (ErtsDistOutputBuf *) &bin->orig_bytes[0];
+ size += obuf_list_size;
+ obuf = (ErtsDistOutputBuf *) &bin->orig_bytes[size];
+ erts_refc_add(&bin->intern.refc, headers - 1, 1);
+ for (i = 0; i < headers; i++) {
+ obuf[i].bin = bin;
+ obuf[i].extp = (byte *)&bin->orig_bytes[0] + obuf_list_size;
#ifdef DEBUG
- obuf->dbg_pattern = ERTS_DIST_OUTPUT_BUF_DBG_PATTERN;
- obuf->alloc_endp = obuf->data + size;
- ASSERT(bin == ErtsDistOutputBuf2Binary(obuf));
+ obuf[i].dbg_pattern = ERTS_DIST_OUTPUT_BUF_DBG_PATTERN;
+ obuf[i].alloc_endp = obuf->extp + size;
+ ASSERT(bin == ErtsDistOutputBuf2Binary(obuf));
#endif
+ }
+ insert_obuf((struct obuf_list*)&bin->orig_bytes[0], headers);
return obuf;
}
@@ -699,14 +851,17 @@ free_dist_obuf(ErtsDistOutputBuf *obuf)
{
Binary *bin = ErtsDistOutputBuf2Binary(obuf);
ASSERT(obuf->dbg_pattern == ERTS_DIST_OUTPUT_BUF_DBG_PATTERN);
- erts_bin_release(bin);
+ remove_obuf((struct obuf_list*)&bin->orig_bytes[0]);
+ if (erts_refc_dectest(&bin->intern.refc, 0) == 0) {
+ erts_bin_free(bin);
+ }
}
static ERTS_INLINE Sint
size_obuf(ErtsDistOutputBuf *obuf)
{
- Binary *bin = ErtsDistOutputBuf2Binary(obuf);
- return bin->orig_size;
+ return sizeof(ErtsDistOutputBuf) + (obuf->ext_endp - obuf->ext_start)
+ + (obuf->hdr_endp - obuf->hdrp);
}
static ErtsDistOutputBuf* clear_de_out_queues(DistEntry* dep)
@@ -760,18 +915,20 @@ static void free_de_out_queues(DistEntry* dep, ErtsDistOutputBuf *obuf)
int erts_dsend_context_dtor(Binary* ctx_bin)
{
- ErtsSendContext* ctx = ERTS_MAGIC_BIN_DATA(ctx_bin);
- switch (ctx->dss.phase) {
+ ErtsDSigSendContext* ctx = ERTS_MAGIC_BIN_DATA(ctx_bin);
+ switch (ctx->phase) {
case ERTS_DSIG_SEND_PHASE_MSG_SIZE:
- DESTROY_SAVED_WSTACK(&ctx->dss.u.sc.wstack);
+ DESTROY_SAVED_WSTACK(&ctx->u.sc.wstack);
break;
case ERTS_DSIG_SEND_PHASE_MSG_ENCODE:
- DESTROY_SAVED_WSTACK(&ctx->dss.u.ec.wstack);
+ DESTROY_SAVED_WSTACK(&ctx->u.ec.wstack);
break;
default:;
}
- if (ctx->dss.phase >= ERTS_DSIG_SEND_PHASE_ALLOC && ctx->dss.obuf) {
- free_dist_obuf(ctx->dss.obuf);
+ if (ctx->phase >= ERTS_DSIG_SEND_PHASE_ALLOC && ctx->obuf) {
+ int i;
+ for (i = 0; i < ctx->fragments; i++)
+ free_dist_obuf(&ctx->obuf[i]);
}
if (ctx->deref_dep)
erts_deref_dist_entry(ctx->dep);
@@ -779,10 +936,10 @@ int erts_dsend_context_dtor(Binary* ctx_bin)
return 1;
}
-Eterm erts_dsend_export_trap_context(Process* p, ErtsSendContext* ctx)
+Eterm erts_dsend_export_trap_context(Process* p, ErtsDSigSendContext* ctx)
{
struct exported_ctx {
- ErtsSendContext ctx;
+ ErtsDSigSendContext ctx;
ErtsAtomCacheMap acm;
};
Binary* ctx_bin = erts_create_magic_binary(sizeof(struct exported_ctx),
@@ -790,12 +947,12 @@ Eterm erts_dsend_export_trap_context(Process* p, ErtsSendContext* ctx)
struct exported_ctx* dst = ERTS_MAGIC_BIN_DATA(ctx_bin);
Eterm* hp = HAlloc(p, ERTS_MAGIC_REF_THING_SIZE);
- sys_memcpy(&dst->ctx, ctx, sizeof(ErtsSendContext));
- ASSERT(ctx->dss.ctl == make_tuple(ctx->ctl_heap));
- dst->ctx.dss.ctl = make_tuple(dst->ctx.ctl_heap);
- if (ctx->dss.acmp) {
- sys_memcpy(&dst->acm, ctx->dss.acmp, sizeof(ErtsAtomCacheMap));
- dst->ctx.dss.acmp = &dst->acm;
+ sys_memcpy(&dst->ctx, ctx, sizeof(ErtsDSigSendContext));
+ ASSERT(ctx->ctl == make_tuple(ctx->ctl_heap));
+ dst->ctx.ctl = make_tuple(dst->ctx.ctl_heap);
+ if (ctx->acmp) {
+ sys_memcpy(&dst->acm, ctx->acmp, sizeof(ErtsAtomCacheMap));
+ dst->ctx.acmp = &dst->acm;
}
return erts_mk_magic_ref(&hp, &MSO(p), ctx_bin);
}
@@ -816,71 +973,58 @@ Eterm erts_dsend_export_trap_context(Process* p, ErtsSendContext* ctx)
** Send a DOP_LINK link message
*/
int
-erts_dsig_send_link(ErtsDSigData *dsdp, Eterm local, Eterm remote)
+erts_dsig_send_link(ErtsDSigSendContext *ctx, Eterm local, Eterm remote)
{
- DeclareTmpHeapNoproc(ctl_heap,4);
- Eterm ctl = TUPLE3(&ctl_heap[0], make_small(DOP_LINK), local, remote);
- int res;
- UseTmpHeapNoproc(4);
-
- res = dsig_send_ctl(dsdp, ctl, 0);
- UnUseTmpHeapNoproc(4);
- return res;
+ Eterm ctl = TUPLE3(&ctx->ctl_heap[0], make_small(DOP_LINK), local, remote);
+ return dsig_send_ctl(ctx, ctl);
}
int
-erts_dsig_send_unlink(ErtsDSigData *dsdp, Eterm local, Eterm remote)
+erts_dsig_send_unlink(ErtsDSigSendContext *ctx, Eterm local, Eterm remote)
{
- DeclareTmpHeapNoproc(ctl_heap,4);
- Eterm ctl = TUPLE3(&ctl_heap[0], make_small(DOP_UNLINK), local, remote);
- int res;
-
- UseTmpHeapNoproc(4);
- res = dsig_send_ctl(dsdp, ctl, 0);
- UnUseTmpHeapNoproc(4);
- return res;
+ Eterm ctl = TUPLE3(&ctx->ctl_heap[0], make_small(DOP_UNLINK), local, remote);
+ return dsig_send_ctl(ctx, ctl);
}
/* A local process that's being monitored by a remote one exits. We send:
{DOP_MONITOR_P_EXIT, Local pid or name, Remote pid, ref, reason} */
int
-erts_dsig_send_m_exit(ErtsDSigData *dsdp, Eterm watcher, Eterm watched,
- Eterm ref, Eterm reason)
+erts_dsig_send_m_exit(ErtsDSigSendContext *ctx, Eterm watcher, Eterm watched,
+ Eterm ref, Eterm reason)
{
- Eterm ctl;
- DeclareTmpHeapNoproc(ctl_heap,6);
- int res;
+ Eterm ctl, msg;
- if (~dsdp->flags & (DFLAG_DIST_MONITOR | DFLAG_DIST_MONITOR_NAME)) {
+ if (~ctx->flags & (DFLAG_DIST_MONITOR | DFLAG_DIST_MONITOR_NAME)) {
/*
* Receiver does not support DOP_MONITOR_P_EXIT (see dsig_send_monitor)
*/
return ERTS_DSIG_SEND_OK;
}
- UseTmpHeapNoproc(6);
-
- ctl = TUPLE5(&ctl_heap[0], make_small(DOP_MONITOR_P_EXIT),
- watched, watcher, ref, reason);
+ if (ctx->dep->flags & DFLAG_EXIT_PAYLOAD) {
+ ctl = TUPLE4(&ctx->ctl_heap[0], make_small(DOP_PAYLOAD_MONITOR_P_EXIT),
+ watched, watcher, ref);
+ msg = reason;
+ } else {
+ ctl = TUPLE5(&ctx->ctl_heap[0], make_small(DOP_MONITOR_P_EXIT),
+ watched, watcher, ref, reason);
+ msg = THE_NON_VALUE;
+ }
- res = dsig_send_ctl(dsdp, ctl, 1);
- UnUseTmpHeapNoproc(6);
- return res;
+ return dsig_send_exit(ctx, ctl, msg);
}
/* We want to monitor a process (named or unnamed) on another node, we send:
{DOP_MONITOR_P, Local pid, Remote pid or name, Ref}, which is exactly what's
needed on the other side... */
int
-erts_dsig_send_monitor(ErtsDSigData *dsdp, Eterm watcher, Eterm watched,
+erts_dsig_send_monitor(ErtsDSigSendContext *ctx, Eterm watcher, Eterm watched,
Eterm ref)
{
Eterm ctl;
- DeclareTmpHeapNoproc(ctl_heap,5);
- int res;
- if (~dsdp->flags & (DFLAG_DIST_MONITOR | DFLAG_DIST_MONITOR_NAME)) {
+ if (~ctx->flags & (DFLAG_DIST_MONITOR | DFLAG_DIST_MONITOR_NAME)) {
/*
* Receiver does not support DOP_MONITOR_P.
* Just avoid sending it and by doing that reduce this monitor
@@ -890,48 +1034,40 @@ erts_dsig_send_monitor(ErtsDSigData *dsdp, Eterm watcher, Eterm watched,
return ERTS_DSIG_SEND_OK;
}
- UseTmpHeapNoproc(5);
- ctl = TUPLE4(&ctl_heap[0],
+ ctl = TUPLE4(&ctx->ctl_heap[0],
make_small(DOP_MONITOR_P),
watcher, watched, ref);
- res = dsig_send_ctl(dsdp, ctl, 0);
- UnUseTmpHeapNoproc(5);
- return res;
+ return dsig_send_ctl(ctx, ctl);
}
/* A local process monitoring a remote one wants to stop monitoring, either
because of a demonitor bif call or because the local process died. We send
{DOP_DEMONITOR_P, Local pid, Remote pid or name, ref} */
int
-erts_dsig_send_demonitor(ErtsDSigData *dsdp, Eterm watcher,
- Eterm watched, Eterm ref, int force)
+erts_dsig_send_demonitor(ErtsDSigSendContext *ctx, Eterm watcher,
+ Eterm watched, Eterm ref)
{
Eterm ctl;
- DeclareTmpHeapNoproc(ctl_heap,5);
- int res;
- if (~dsdp->flags & (DFLAG_DIST_MONITOR | DFLAG_DIST_MONITOR_NAME)) {
+ if (~ctx->flags & (DFLAG_DIST_MONITOR | DFLAG_DIST_MONITOR_NAME)) {
/*
* Receiver does not support DOP_DEMONITOR_P (see dsig_send_monitor)
*/
return ERTS_DSIG_SEND_OK;
}
- UseTmpHeapNoproc(5);
- ctl = TUPLE4(&ctl_heap[0],
+ ctl = TUPLE4(&ctx->ctl_heap[0],
make_small(DOP_DEMONITOR_P),
watcher, watched, ref);
- res = dsig_send_ctl(dsdp, ctl, force);
- UnUseTmpHeapNoproc(5);
- return res;
+ return dsig_send_ctl(ctx, ctl);
}
-static int can_send_seqtrace_token(ErtsSendContext* ctx, Eterm token) {
+static int can_send_seqtrace_token(ErtsDSigSendContext* ctx, Eterm token) {
Eterm label;
- if (ctx->dsd.flags & DFLAG_BIG_SEQTRACE_LABELS) {
+ if (ctx->flags & DFLAG_BIG_SEQTRACE_LABELS) {
/* The other end is capable of handling arbitrary seq_trace labels. */
return 1;
}
@@ -947,11 +1083,11 @@ static int can_send_seqtrace_token(ErtsSendContext* ctx, Eterm token) {
}
int
-erts_dsig_send_msg(Eterm remote, Eterm message, ErtsSendContext* ctx)
+erts_dsig_send_msg(ErtsDSigSendContext* ctx, Eterm remote, Eterm message)
{
Eterm ctl;
Eterm token = NIL;
- Process *sender = ctx->dsd.proc;
+ Process *sender = ctx->c_p;
int res;
#ifdef USE_VM_PROBES
Sint tok_label = 0;
@@ -972,7 +1108,7 @@ erts_dsig_send_msg(Eterm remote, Eterm message, ErtsSendContext* ctx)
*node_name = *sender_name = *receiver_name = '\0';
if (DTRACE_ENABLED(message_send) || DTRACE_ENABLED(message_send_remote)) {
erts_snprintf(node_name, sizeof(DTRACE_CHARBUF_NAME(node_name)),
- "%T", ctx->dsd.dep->sysname);
+ "%T", ctx->dep->sysname);
erts_snprintf(sender_name, sizeof(DTRACE_CHARBUF_NAME(sender_name)),
"%T", sender->common.id);
erts_snprintf(receiver_name, sizeof(DTRACE_CHARBUF_NAME(receiver_name)),
@@ -992,7 +1128,7 @@ erts_dsig_send_msg(Eterm remote, Eterm message, ErtsSendContext* ctx)
send_token = (token != NIL && can_send_seqtrace_token(ctx, token));
- if (ctx->dsd.flags & DFLAG_SEND_SENDER) {
+ if (ctx->flags & DFLAG_SEND_SENDER) {
dist_op = make_small(send_token ?
DOP_SEND_SENDER_TT :
DOP_SEND_SENDER);
@@ -1015,21 +1151,18 @@ erts_dsig_send_msg(Eterm remote, Eterm message, ErtsSendContext* ctx)
msize, tok_label, tok_lastcnt, tok_serial);
DTRACE7(message_send_remote, sender_name, node_name, receiver_name,
msize, tok_label, tok_lastcnt, tok_serial);
- ctx->dss.ctl = ctl;
- ctx->dss.msg = message;
- ctx->dss.force_busy = 0;
- res = erts_dsig_send(&ctx->dsd, &ctx->dss);
+ ctx->ctl = ctl;
+ ctx->msg = message;
+ res = erts_dsig_send(ctx);
return res;
}
int
-erts_dsig_send_reg_msg(Eterm remote_name, Eterm message,
- ErtsSendContext* ctx)
+erts_dsig_send_reg_msg(ErtsDSigSendContext* ctx, Eterm remote_name, Eterm message)
{
Eterm ctl;
Eterm token = NIL;
- Process *sender = ctx->dsd.proc;
- int res;
+ Process *sender = ctx->c_p;
#ifdef USE_VM_PROBES
Sint tok_label = 0;
Sint tok_lastcnt = 0;
@@ -1049,7 +1182,7 @@ erts_dsig_send_reg_msg(Eterm remote_name, Eterm message,
*node_name = *sender_name = *receiver_name = '\0';
if (DTRACE_ENABLED(message_send) || DTRACE_ENABLED(message_send_remote)) {
erts_snprintf(node_name, sizeof(DTRACE_CHARBUF_NAME(node_name)),
- "%T", ctx->dsd.dep->sysname);
+ "%T", ctx->dep->sysname);
erts_snprintf(sender_name, sizeof(DTRACE_CHARBUF_NAME(sender_name)),
"%T", sender->common.id);
erts_snprintf(receiver_name, sizeof(DTRACE_CHARBUF_NAME(receiver_name)),
@@ -1074,23 +1207,19 @@ erts_dsig_send_reg_msg(Eterm remote_name, Eterm message,
msize, tok_label, tok_lastcnt, tok_serial);
DTRACE7(message_send_remote, sender_name, node_name, receiver_name,
msize, tok_label, tok_lastcnt, tok_serial);
- ctx->dss.ctl = ctl;
- ctx->dss.msg = message;
- ctx->dss.force_busy = 0;
- res = erts_dsig_send(&ctx->dsd, &ctx->dss);
- return res;
+ ctx->ctl = ctl;
+ ctx->msg = message;
+ return erts_dsig_send(ctx);
}
/* local has died, deliver the exit signal to remote */
int
-erts_dsig_send_exit_tt(ErtsDSigData *dsdp, Eterm local, Eterm remote,
+erts_dsig_send_exit_tt(ErtsDSigSendContext *ctx, Eterm local, Eterm remote,
Eterm reason, Eterm token)
{
- Eterm ctl;
- DeclareTmpHeapNoproc(ctl_heap,6);
- int res;
+ Eterm ctl, msg = THE_NON_VALUE;
#ifdef USE_VM_PROBES
- Process *sender = dsdp->proc;
+ Process *sender = ctx->c_p;
Sint tok_label = 0;
Sint tok_lastcnt = 0;
Sint tok_serial = 0;
@@ -1100,20 +1229,29 @@ erts_dsig_send_exit_tt(ErtsDSigData *dsdp, Eterm local, Eterm remote,
DTRACE_CHARBUF(reason_str, 128);
#endif
- UseTmpHeapNoproc(6);
+ if (ctx->dep->flags & DFLAG_EXIT_PAYLOAD)
+ msg = reason;
+
if (have_seqtrace(token)) {
- seq_trace_update_send(dsdp->proc);
+ seq_trace_update_send(ctx->c_p);
seq_trace_output_exit(token, reason, SEQ_TRACE_SEND, remote, local);
- ctl = TUPLE5(&ctl_heap[0],
- make_small(DOP_EXIT_TT), local, remote, token, reason);
+ if (ctx->dep->flags & DFLAG_EXIT_PAYLOAD) {
+ ctl = TUPLE4(&ctx->ctl_heap[0],
+ make_small(DOP_PAYLOAD_EXIT_TT), local, remote, token);
+ } else
+ ctl = TUPLE5(&ctx->ctl_heap[0],
+ make_small(DOP_EXIT_TT), local, remote, token, reason);
} else {
- ctl = TUPLE4(&ctl_heap[0], make_small(DOP_EXIT), local, remote, reason);
+ if (ctx->dep->flags & DFLAG_EXIT_PAYLOAD)
+ ctl = TUPLE3(&ctx->ctl_heap[0], make_small(DOP_PAYLOAD_EXIT), local, remote);
+ else
+ ctl = TUPLE4(&ctx->ctl_heap[0], make_small(DOP_EXIT), local, remote, reason);
}
#ifdef USE_VM_PROBES
*node_name = *sender_name = *remote_name = '\0';
if (DTRACE_ENABLED(process_exit_signal_remote)) {
erts_snprintf(node_name, sizeof(DTRACE_CHARBUF_NAME(node_name)),
- "%T", dsdp->dep->sysname);
+ "%T", ctx->dep->sysname);
erts_snprintf(sender_name, sizeof(DTRACE_CHARBUF_NAME(sender_name)),
"%T", sender->common.id);
erts_snprintf(remote_name, sizeof(DTRACE_CHARBUF_NAME(remote_name)),
@@ -1129,73 +1267,171 @@ erts_dsig_send_exit_tt(ErtsDSigData *dsdp, Eterm local, Eterm remote,
#endif
DTRACE7(process_exit_signal_remote, sender_name, node_name,
remote_name, reason_str, tok_label, tok_lastcnt, tok_serial);
- /* forced, i.e ignore busy */
- res = dsig_send_ctl(dsdp, ctl, 1);
- UnUseTmpHeapNoproc(6);
- return res;
+ return dsig_send_exit(ctx, ctl, msg);
}
int
-erts_dsig_send_exit(ErtsDSigData *dsdp, Eterm local, Eterm remote, Eterm reason)
+erts_dsig_send_exit(ErtsDSigSendContext *ctx, Eterm local, Eterm remote, Eterm reason)
{
- DeclareTmpHeapNoproc(ctl_heap,5);
- int res;
- Eterm ctl;
+ Eterm ctl, msg = ctx->dep->flags & DFLAG_EXIT_PAYLOAD ? reason : THE_NON_VALUE;
- UseTmpHeapNoproc(5);
- ctl = TUPLE4(&ctl_heap[0],
- make_small(DOP_EXIT), local, remote, reason);
- /* forced, i.e ignore busy */
- res = dsig_send_ctl(dsdp, ctl, 1);
- UnUseTmpHeapNoproc(5);
- return res;
+ if (ctx->dep->flags & DFLAG_EXIT_PAYLOAD) {
+ ctl = TUPLE3(&ctx->ctl_heap[0], make_small(DOP_PAYLOAD_EXIT), local, remote);
+ msg = reason;
+ } else {
+ ctl = TUPLE4(&ctx->ctl_heap[0], make_small(DOP_EXIT), local, remote, reason);
+ msg = THE_NON_VALUE;
+ }
+ return dsig_send_exit(ctx, ctl, msg);
}
int
-erts_dsig_send_exit2(ErtsDSigData *dsdp, Eterm local, Eterm remote, Eterm reason)
+erts_dsig_send_exit2(ErtsDSigSendContext *ctx, Eterm local, Eterm remote, Eterm reason)
{
- DeclareTmpHeapNoproc(ctl_heap,5);
- int res;
- Eterm ctl;
+ Eterm ctl, msg;
- UseTmpHeapNoproc(5);
- ctl = TUPLE4(&ctl_heap[0],
- make_small(DOP_EXIT2), local, remote, reason);
+ if (ctx->dep->flags & DFLAG_EXIT_PAYLOAD) {
+ ctl = TUPLE3(&ctx->ctl_heap[0],
+ make_small(DOP_PAYLOAD_EXIT2), local, remote);
+ msg = reason;
+ } else {
+ ctl = TUPLE4(&ctx->ctl_heap[0],
+ make_small(DOP_EXIT2), local, remote, reason);
+ msg = THE_NON_VALUE;
+ }
- res = dsig_send_ctl(dsdp, ctl, 0);
- UnUseTmpHeapNoproc(5);
- return res;
+ return dsig_send_exit(ctx, ctl, msg);
}
int
-erts_dsig_send_group_leader(ErtsDSigData *dsdp, Eterm leader, Eterm remote)
+erts_dsig_send_group_leader(ErtsDSigSendContext *ctx, Eterm leader, Eterm remote)
{
- DeclareTmpHeapNoproc(ctl_heap,4);
- int res;
Eterm ctl;
- UseTmpHeapNoproc(4);
- ctl = TUPLE3(&ctl_heap[0],
+ ctl = TUPLE3(&ctx->ctl_heap[0],
make_small(DOP_GROUP_LEADER), leader, remote);
- res = dsig_send_ctl(dsdp, ctl, 0);
- UnUseTmpHeapNoproc(4);
- return res;
+ return dsig_send_ctl(ctx, ctl);
}
-#if defined(PURIFY)
-# define PURIFY_MSG(msg) \
- purify_printf("%s, line %d: %s", __FILE__, __LINE__, msg)
-#elif defined(VALGRIND)
-#include <valgrind/valgrind.h>
-#include <valgrind/memcheck.h>
+struct dist_sequences {
+ ErlHeapFragment hfrag;
+ struct dist_sequences *parent;
+ struct dist_sequences *left;
+ struct dist_sequences *right;
+ char is_red;
-# define PURIFY_MSG(msg) \
- VALGRIND_PRINTF("%s, line %d: %s", __FILE__, __LINE__, msg)
-#else
-# define PURIFY_MSG(msg)
-#endif
+ Uint64 seq_id;
+ int cnt;
+ Sint ctl_len;
+};
+
+#define ERTS_RBT_PREFIX dist_seq
+#define ERTS_RBT_T DistSeqNode
+#define ERTS_RBT_KEY_T Uint
+#define ERTS_RBT_FLAGS_T int
+#define ERTS_RBT_INIT_EMPTY_TNODE(T) \
+ do { \
+ (T)->parent = NULL; \
+ (T)->left = NULL; \
+ (T)->right = NULL; \
+ (T)->is_red = 0; \
+ } while(0)
+#define ERTS_RBT_IS_RED(T) ((T)->is_red)
+#define ERTS_RBT_SET_RED(T) ((T)->is_red = 1)
+#define ERTS_RBT_IS_BLACK(T) (!ERTS_RBT_IS_RED(T))
+#define ERTS_RBT_SET_BLACK(T) ((T)->is_red = 0)
+#define ERTS_RBT_GET_FLAGS(T) ((T)->is_red)
+#define ERTS_RBT_SET_FLAGS(T, F) ((T)->is_red = F)
+#define ERTS_RBT_GET_PARENT(T) ((T)->parent)
+#define ERTS_RBT_SET_PARENT(T, P) ((T)->parent = P)
+#define ERTS_RBT_GET_RIGHT(T) ((T)->right)
+#define ERTS_RBT_SET_RIGHT(T, R) ((T)->right = (R))
+#define ERTS_RBT_GET_LEFT(T) ((T)->left)
+#define ERTS_RBT_SET_LEFT(T, L) ((T)->left = (L))
+#define ERTS_RBT_GET_KEY(T) ((T)->seq_id)
+#define ERTS_RBT_IS_LT(KX, KY) (KX < KY)
+#define ERTS_RBT_IS_EQ(KX, KY) (KX == KY)
+#define ERTS_RBT_WANT_DELETE
+#define ERTS_RBT_WANT_LOOKUP_INSERT
+#define ERTS_RBT_WANT_LOOKUP
+#define ERTS_RBT_WANT_FOREACH
+#define ERTS_RBT_WANT_FOREACH_DESTROY_YIELDING
+
+#include "erl_rbtree.h"
+
+struct erts_dist_seq_tree_foreach_iter_arg {
+ int (*func)(ErtsDistExternal *, void *, Sint);
+ void *arg;
+};
+
+static int
+erts_dist_seq_tree_foreach_iter(DistSeqNode *seq, void *arg, Sint reds)
+{
+ struct erts_dist_seq_tree_foreach_iter_arg *state = arg;
+ return state->func(erts_get_dist_ext(&seq->hfrag), state->arg, reds);
+}
+
+void
+erts_dist_seq_tree_foreach(DistEntry *dep, int (*func)(ErtsDistExternal *, void *, Sint), void *arg)
+{
+ struct erts_dist_seq_tree_foreach_iter_arg state;
+ state.func = func;
+ state.arg = arg;
+ dist_seq_rbt_foreach(dep->sequences, erts_dist_seq_tree_foreach_iter, &state);
+}
+
+static int dist_seq_cleanup(DistSeqNode *seq, void *unused, Sint reds)
+{
+ erts_free_dist_ext_copy(erts_get_dist_ext(&seq->hfrag));
+ free_message_buffer(&seq->hfrag);
+ return 1;
+}
+
+typedef struct {
+ DistSeqNode *root;
+ dist_seq_rbt_yield_state_t rbt_ystate;
+} DistSeqNodeYieldState;
+
+int
+erts_dist_seq_tree_foreach_delete_yielding(DistSeqNode **root,
+ void **vyspp,
+ Sint limit)
+{
+ DistSeqNodeYieldState ys = {*root, ERTS_RBT_YIELD_STAT_INITER};
+ DistSeqNodeYieldState *ysp;
+ int res;
+
+ ysp = (DistSeqNodeYieldState *) *vyspp;
+ if (!ysp) {
+ *root = NULL;
+ ysp = &ys;
+ }
+ res = dist_seq_rbt_foreach_destroy_yielding(&ysp->root,
+ dist_seq_cleanup,
+ NULL,
+ &ysp->rbt_ystate,
+ limit);
+ if (res > 0) {
+ if (ysp != &ys)
+ erts_free(ERTS_ALC_T_ML_YIELD_STATE, ysp);
+ *vyspp = NULL;
+ }
+ else {
+
+ if (ysp == &ys) {
+ ysp = erts_alloc(ERTS_ALC_T_SEQ_YIELD_STATE,
+ sizeof(DistSeqNodeYieldState));
+ sys_memcpy((void *) ysp, (void *) &ys,
+ sizeof(DistSeqNodeYieldState));
+ }
+
+ *vyspp = (void *) ysp;
+ }
+
+ return res;
+}
/*
** Input from distribution port.
@@ -1212,10 +1448,13 @@ int erts_net_message(Port *prt,
Uint32 conn_id,
byte *hbuf,
ErlDrvSizeT hlen,
+ Binary *bin,
byte *buf,
ErlDrvSizeT len)
{
- ErtsDistExternal ede;
+ ErtsDistExternal ede, *edep = &ede;
+ ErtsDistExternalData ede_data;
+ ErlHeapFragment *ede_hfrag = NULL;
Sint ctl_len;
Eterm arg;
Eterm from, to;
@@ -1227,10 +1466,8 @@ int erts_net_message(Port *prt,
DeclareTmpHeapNoproc(ctl_default,DIST_CTL_DEFAULT_SIZE);
Eterm* ctl = ctl_default;
ErtsHeapFactory factory;
- Eterm* hp;
Sint type;
Eterm token;
- Eterm token_size;
Uint tuple_arity;
int res;
#ifdef ERTS_DIST_MSG_DBG
@@ -1256,69 +1493,182 @@ int erts_net_message(Port *prt,
}
#ifdef ERTS_RAW_DIST_MSG_DBG
- erts_fprintf(stderr, "<< ");
+ erts_fprintf(dbg_file, "RECV: ");
bw(buf, len);
#endif
- res = erts_prepare_dist_ext(&ede, buf, len, dep, conn_id, dep->cache);
+ ede.data = &ede_data;
+
+ res = erts_prepare_dist_ext(&ede, buf, len, bin, dep, conn_id, dep->cache);
switch (res) {
case ERTS_PREP_DIST_EXT_CLOSED:
return 0; /* Connection not alive; ignore signal... */
case ERTS_PREP_DIST_EXT_FAILED:
#ifdef ERTS_DIST_MSG_DBG
- erts_fprintf(stderr, "DIST MSG DEBUG: erts_prepare_dist_ext() failed:\n");
+ erts_fprintf(dbg_file, "DIST MSG DEBUG: erts_prepare_dist_ext() failed:\n");
bw(buf, orig_len);
#endif
goto data_error;
case ERTS_PREP_DIST_EXT_SUCCESS:
- ctl_len = erts_decode_dist_ext_size(&ede);
+ ctl_len = erts_decode_dist_ext_size(&ede, 1);
if (ctl_len < 0) {
#ifdef ERTS_DIST_MSG_DBG
- erts_fprintf(stderr, "DIST MSG DEBUG: erts_decode_dist_ext_size(CTL) failed:\n");
+ erts_fprintf(dbg_file, "DIST MSG DEBUG: erts_decode_dist_ext_size(CTL) failed:\n");
bw(buf, orig_len);
#endif
PURIFY_MSG("data error");
goto data_error;
}
+
+ /* A non-fragmented message */
+ if (!ede.data->seq_id) {
+ if (ctl_len > DIST_CTL_DEFAULT_SIZE) {
+ ctl = erts_alloc(ERTS_ALC_T_DCTRL_BUF, ctl_len * sizeof(Eterm));
+ }
+
+ erts_factory_tmp_init(&factory, ctl, ctl_len, ERTS_ALC_T_DCTRL_BUF);
+ break;
+ } else {
+ DistSeqNode *seq;
+ Uint sz = erts_dist_ext_size(&ede);
+ Uint used_sz = ctl_len * sizeof(Eterm);
+
+ /* We calculate the size of the heap fragment to be allocated.
+ The used_size part has to be larger that the ctl data and the
+ DistSeqNode. */
+ if (used_sz + (sizeof(ErlHeapFragment) - sizeof(Eterm)) < sizeof(DistSeqNode))
+ used_sz = sizeof(DistSeqNode) - (sizeof(ErlHeapFragment) - sizeof(Eterm));
+
+ seq = (DistSeqNode *)new_message_buffer((sz + used_sz) / sizeof(Eterm));
+ seq->hfrag.used_size = used_sz / sizeof(Eterm);
+
+ seq->ctl_len = ctl_len;
+ seq->seq_id = ede.data->seq_id;
+ seq->cnt = ede.data->frag_id;
+ if (dist_seq_rbt_lookup_insert(&dep->sequences, seq) != NULL) {
+ free_message_buffer(&seq->hfrag);
+ goto data_error;
+ }
+
+ erts_make_dist_ext_copy(&ede, erts_get_dist_ext(&seq->hfrag));
+
+ if (ede.data->frag_id > 1) {
+ seq->cnt--;
+ return 0;
+ }
+ }
+
+ /* fall through, the first fragment in the sequence was the last fragment */
+ case ERTS_PREP_DIST_EXT_FRAG_CONT: {
+ DistSeqNode *seq = dist_seq_rbt_lookup(dep->sequences, ede.data->seq_id);
+
+ if (!seq)
+ goto data_error;
+
+ /* If we did a fall-though we already did this */
+ if (res == ERTS_PREP_DIST_EXT_FRAG_CONT)
+ erts_dist_ext_frag(&ede_data, erts_get_dist_ext(&seq->hfrag));
+
+ /* Verify that the fragments have arrived in the correct order */
+ if (seq->cnt != ede.data->frag_id)
+ goto data_error;
+
+ seq->cnt--;
+
+ /* Check if this was the last fragment */
+ if (ede.data->frag_id > 1)
+ return 0;
+
+ /* Last fragment arrived, time to dispatch the signal */
+ dist_seq_rbt_delete(&dep->sequences, seq);
+ ctl_len = seq->ctl_len;
+
+ /* Now that we no longer need the DistSeqNode we re-use the heapfragment
+ to decode the ctl msg into. We don't need the ctl message to be in
+ the heapfragment, but we decode into the heapfragment speculatively
+ in case there is a trace token that we need. */
+ erts_factory_heap_frag_init(&factory, &seq->hfrag);
+ edep = erts_get_dist_ext(&seq->hfrag);
+ ede_hfrag = &seq->hfrag;
+
+ /* If the sequence consisted of more than 1 fragment we create one large
+ binary out of all of the fragments. This because erts_decode_ext
+ cannot handle a segmented buffer.
+ TODO: Move this copy to as late as possible, preferably in in the
+ erts_decode_dist_ext in the receiving process.
+ */
+ if (edep->data->frag_id > 1) {
+ Uint sz = 0;
+ Binary *bin;
+ int i;
+ byte *ep;
+
+ for (i = 0; i < edep->data->frag_id; i++)
+ sz += edep->data[i].ext_endp - edep->data[i].extp;
+
+ bin = erts_bin_nrml_alloc(sz);
+ ep = (byte*)bin->orig_bytes;
+
+ for (i = 0; i < edep->data->frag_id; i++) {
+ sys_memcpy(ep, edep->data[i].extp, edep->data[i].ext_endp - edep->data[i].extp);
+ ep += edep->data[i].ext_endp - edep->data[i].extp;
+ erts_bin_release(edep->data[i].binp);
+ edep->data[i].binp = NULL;
+ edep->data[i].extp = NULL;
+ edep->data[i].ext_endp = NULL;
+ }
+
+ edep->data->frag_id = 1;
+ edep->data->extp = (byte*)bin->orig_bytes;
+ edep->data->ext_endp = ep;
+ edep->data->binp = bin;
+ }
+
break;
+ }
default:
ERTS_INTERNAL_ERROR("Unexpected result from erts_prepare_dist_ext()");
break;
}
- if (ctl_len > DIST_CTL_DEFAULT_SIZE) {
- ctl = erts_alloc(ERTS_ALC_T_DCTRL_BUF, ctl_len * sizeof(Eterm));
- }
- hp = ctl;
-
- erts_factory_tmp_init(&factory, ctl, ctl_len, ERTS_ALC_T_DCTRL_BUF);
- arg = erts_decode_dist_ext(&factory, &ede);
+ arg = erts_decode_dist_ext(&factory, edep, 1);
if (is_non_value(arg)) {
#ifdef ERTS_DIST_MSG_DBG
- erts_fprintf(stderr, "DIST MSG DEBUG: erts_decode_dist_ext(CTL) failed:\n");
+ erts_fprintf(dbg_file, "DIST MSG DEBUG: erts_decode_dist_ext(CTL) failed:\n");
bw(buf, orig_len);
#endif
PURIFY_MSG("data error");
goto decode_error;
}
-#ifdef ERTS_DIST_MSG_DBG
- erts_fprintf(stderr, "<< CTL: %T\n", arg);
-#endif
+ /* Fill the unused part of the hfrag with a bignum header */
+ if (ede_hfrag && ede_hfrag->mem + ede_hfrag->used_size > factory.hp) {
+ Uint slot = factory.hp - ede_hfrag->mem;
+ ede_hfrag->mem[slot] = make_pos_bignum_header(ede_hfrag->used_size - slot - 1);
+ }
if (is_not_tuple(arg) ||
(tuple = tuple_val(arg), (tuple_arity = arityval(*tuple)) < 1) ||
is_not_small(tuple[1])) {
+#ifdef ERTS_DIST_MSG_DBG
+ if (is_tuple(arg) && arityval(*tuple) > 1)
+ erts_fprintf(dbg_file, "RECV: CTL: %s: %.80T\n",
+ erts_dop_to_string(unsigned_val(tuple[1])), arg);
+#endif
goto invalid_message;
}
- token_size = 0;
+#ifdef ERTS_DIST_MSG_DBG
+ erts_fprintf(dbg_file, "RECV: CTL: %s: %.80T\n",
+ erts_dop_to_string(unsigned_val(tuple[1])), arg);
+#endif
+
token = NIL;
switch (type = unsigned_val(tuple[1])) {
case DOP_LINK: {
- ErtsDSigData dsd;
+ ErtsDSigSendContext ctx;
int code;
if (tuple_arity != 3) {
@@ -1343,10 +1693,7 @@ int erts_net_message(Port *prt,
from, to);
ASSERT(ldp->a.other.item == to);
ASSERT(eq(ldp->b.other.item, from));
-#ifdef DEBUG
- code =
-#endif
- erts_link_dist_insert(&ldp->a, dep->mld);
+ code = erts_link_dist_insert(&ldp->a, dep->mld);
ASSERT(code);
if (erts_proc_sig_send_link(NULL, to, &ldp->b))
@@ -1354,17 +1701,14 @@ int erts_net_message(Port *prt,
/* Failed to send signal; cleanup and reply noproc... */
-#ifdef DEBUG
- code =
-#endif
- erts_link_dist_delete(&ldp->a);
+ code = erts_link_dist_delete(&ldp->a);
ASSERT(code);
erts_link_release_both(ldp);
}
- code = erts_dsig_prepare(&dsd, dep, NULL, 0, ERTS_DSP_NO_LOCK, 0, 0);
+ code = erts_dsig_prepare(&ctx, dep, NULL, 0, ERTS_DSP_NO_LOCK, 1, 1, 0);
if (code == ERTS_DSIG_PREP_CONNECTED) {
- code = erts_dsig_send_exit(&dsd, to, from, am_noproc);
+ code = erts_dsig_send_exit(&ctx, to, from, am_noproc);
ASSERT(code == ERTS_DSIG_SEND_OK);
}
@@ -1397,7 +1741,7 @@ int erts_net_message(Port *prt,
/* A remote process wants to monitor us, we get:
{DOP_MONITOR_P, Remote pid, local pid or name, ref} */
Eterm pid, name;
- ErtsDSigData dsd;
+ ErtsDSigSendContext ctx;
int code;
if (tuple_arity != 4) {
@@ -1452,10 +1796,9 @@ int erts_net_message(Port *prt,
}
- code = erts_dsig_prepare(&dsd, dep, NULL, 0, ERTS_DSP_NO_LOCK, 0, 0);
+ code = erts_dsig_prepare(&ctx, dep, NULL, 0, ERTS_DSP_NO_LOCK, 1, 1, 0);
if (code == ERTS_DSIG_PREP_CONNECTED) {
- code = erts_dsig_send_m_exit(&dsd, watcher, watched, ref,
- am_noproc);
+ code = erts_dsig_send_m_exit(&ctx, watcher, watched, ref, am_noproc);
ASSERT(code == ERTS_DSIG_SEND_OK);
}
@@ -1514,7 +1857,6 @@ int erts_net_message(Port *prt,
goto invalid_message;
}
- token_size = size_object(tuple[5]);
/* Fall through ... */
case DOP_REG_SEND:
/* {DOP_REG_SEND, From, Cookie, ToName} -- Message */
@@ -1529,7 +1871,7 @@ int erts_net_message(Port *prt,
}
#ifdef ERTS_DIST_MSG_DBG
- dist_msg_dbg(&ede, "MSG", buf, orig_len);
+ dist_msg_dbg(edep, "MSG", buf, orig_len);
#endif
from = tuple[2];
@@ -1539,35 +1881,25 @@ int erts_net_message(Port *prt,
}
rp = erts_whereis_process(NULL, 0, to, 0, 0);
if (rp) {
- Uint xsize = (type == DOP_REG_SEND
- ? 0
- : ERTS_HEAP_FRAG_SIZE(token_size));
ErtsProcLocks locks = 0;
- ErtsDistExternal *ede_copy;
- ede_copy = erts_make_dist_ext_copy(&ede, xsize);
if (type == DOP_REG_SEND) {
token = NIL;
} else {
- ErlHeapFragment *heap_frag;
- ErlOffHeap *ohp;
- ASSERT(xsize);
- heap_frag = erts_dist_ext_trailer(ede_copy);
- ERTS_INIT_HEAP_FRAG(heap_frag, token_size, token_size);
- hp = heap_frag->mem;
- ohp = &heap_frag->off_heap;
token = tuple[5];
- token = copy_struct(token, token_size, &hp, ohp);
}
- erts_queue_dist_message(rp, locks, ede_copy, token, from);
+ erts_queue_dist_message(rp, locks, edep, ede_hfrag, token, from);
+
if (locks)
erts_proc_unlock(rp, locks);
- }
+ } else if (ede_hfrag) {
+ erts_free_dist_ext_copy(erts_get_dist_ext(ede_hfrag));
+ free_message_buffer(ede_hfrag);
+ }
break;
case DOP_SEND_SENDER_TT: {
- Uint xsize;
case DOP_SEND_TT:
if (tuple_arity != 4) {
@@ -1575,15 +1907,12 @@ int erts_net_message(Port *prt,
}
token = tuple[4];
- token_size = size_object(token);
- xsize = ERTS_HEAP_FRAG_SIZE(token_size);
goto send_common;
case DOP_SEND_SENDER:
case DOP_SEND:
token = NIL;
- xsize = 0;
if (tuple_arity != 3)
goto invalid_message;
@@ -1599,7 +1928,7 @@ int erts_net_message(Port *prt,
: tuple[2] == am_Empty);
#ifdef ERTS_DIST_MSG_DBG
- dist_msg_dbg(&ede, "MSG", buf, orig_len);
+ dist_msg_dbg(edep, "MSG", buf, orig_len);
#endif
to = tuple[3];
if (is_not_pid(to)) {
@@ -1608,39 +1937,38 @@ int erts_net_message(Port *prt,
rp = erts_proc_lookup(to);
if (rp) {
ErtsProcLocks locks = 0;
- ErtsDistExternal *ede_copy;
-
- ede_copy = erts_make_dist_ext_copy(&ede, xsize);
- if (is_not_nil(token)) {
- ErlHeapFragment *heap_frag;
- ErlOffHeap *ohp;
- ASSERT(xsize);
- heap_frag = erts_dist_ext_trailer(ede_copy);
- ERTS_INIT_HEAP_FRAG(heap_frag, token_size, token_size);
- hp = heap_frag->mem;
- ohp = &heap_frag->off_heap;
- token = copy_struct(token, token_size, &hp, ohp);
- }
- erts_queue_dist_message(rp, locks, ede_copy, token, am_Empty);
+ erts_queue_dist_message(rp, locks, edep, ede_hfrag, token, am_Empty);
if (locks)
erts_proc_unlock(rp, locks);
- }
+ } else if (ede_hfrag) {
+ erts_free_dist_ext_copy(erts_get_dist_ext(ede_hfrag));
+ free_message_buffer(ede_hfrag);
+ }
break;
}
+ case DOP_PAYLOAD_MONITOR_P_EXIT:
case DOP_MONITOR_P_EXIT: {
+
/* We are monitoring a process on the remote node which dies, we get
{DOP_MONITOR_P_EXIT, Remote pid or name, Local pid, ref, reason} */
-
- if (tuple_arity != 5) {
- goto invalid_message;
- }
- watched = tuple[2]; /* remote proc or name which died */
- watcher = tuple[3];
+ watched = tuple[2]; /* remote proc or name which died */
+ watcher = tuple[3];
ref = tuple[4];
- reason = tuple[5];
+
+ if (type == DOP_PAYLOAD_MONITOR_P_EXIT) {
+ if (tuple_arity != 4) {
+ goto invalid_message;
+ }
+ reason = THE_NON_VALUE;
+ } else {
+ if (tuple_arity != 5) {
+ goto invalid_message;
+ }
+ reason = tuple[5];
+ }
if (is_not_ref(ref))
goto invalid_message;
@@ -1656,70 +1984,125 @@ int erts_net_message(Port *prt,
goto invalid_message;
}
- erts_proc_sig_send_dist_monitor_down(dep, ref, watched,
- watcher, reason);
+ if (!erts_proc_lookup(watcher)) break; /* Process not alive */
+
+ if (reason == THE_NON_VALUE) {
+
+#ifdef ERTS_DIST_MSG_DBG
+ dist_msg_dbg(edep, "MSG", buf, orig_len);
+#endif
+
+ }
+
+ erts_proc_sig_send_dist_monitor_down(
+ dep, ref, watched, watcher, edep, ede_hfrag, reason);
break;
}
+ case DOP_PAYLOAD_EXIT:
+ case DOP_PAYLOAD_EXIT_TT:
case DOP_EXIT_TT:
case DOP_EXIT: {
+
/* 'from', which 'to' is linked to, died */
+ from = tuple[2];
+ to = tuple[3];
+
if (type == DOP_EXIT) {
if (tuple_arity != 4) {
goto invalid_message;
}
-
- from = tuple[2];
- to = tuple[3];
- reason = tuple[4];
token = NIL;
- } else {
+ reason = tuple[4];
+ } else if (type == DOP_EXIT_TT){
if (tuple_arity != 5) {
goto invalid_message;
}
- from = tuple[2];
- to = tuple[3];
token = tuple[4];
reason = tuple[5];
- }
+ } else if (type == DOP_PAYLOAD_EXIT) {
+ if (tuple_arity != 3) {
+ goto invalid_message;
+ }
+ token = NIL;
+ reason = THE_NON_VALUE;
+ } else {
+ if (tuple_arity != 4) {
+ goto invalid_message;
+ }
+ token = tuple[4];
+ reason = THE_NON_VALUE;
+ }
if (is_not_external_pid(from)
|| dep != external_pid_dist_entry(from)
|| is_not_internal_pid(to)) {
goto invalid_message;
}
+ if (!erts_proc_lookup(to)) break; /* Process not alive */
+
+ if (reason == THE_NON_VALUE) {
+#ifdef ERTS_DIST_MSG_DBG
+ dist_msg_dbg(edep, "MSG", buf, orig_len);
+#endif
+ }
+
erts_proc_sig_send_dist_link_exit(dep,
- from, to,
+ from, to, edep, ede_hfrag,
reason, token);
break;
}
+ case DOP_PAYLOAD_EXIT2_TT:
+ case DOP_PAYLOAD_EXIT2:
case DOP_EXIT2_TT:
- case DOP_EXIT2:
+ case DOP_EXIT2: {
+
/* 'from' is send an exit signal to 'to' */
+ from = tuple[2];
+ to = tuple[3];
+
if (type == DOP_EXIT2) {
if (tuple_arity != 4) {
goto invalid_message;
}
- from = tuple[2];
- to = tuple[3];
reason = tuple[4];
token = NIL;
- } else {
+ } else if (type == DOP_EXIT2_TT) {
if (tuple_arity != 5) {
goto invalid_message;
}
- from = tuple[2];
- to = tuple[3];
token = tuple[4];
reason = tuple[5];
- }
- if (is_not_pid(from) || is_not_internal_pid(to)) {
+ } else if (type == DOP_PAYLOAD_EXIT2) {
+ if (tuple_arity != 3) {
+ goto invalid_message;
+ }
+ reason = THE_NON_VALUE;
+ token = NIL;
+ } else {
+ if (tuple_arity != 4) {
+ goto invalid_message;
+ }
+ reason = THE_NON_VALUE;
+ token = tuple[4];
+ }
+ if (is_not_pid(from)
+ || dep != external_pid_dist_entry(from)
+ || is_not_internal_pid(to)) {
goto invalid_message;
}
- erts_proc_sig_send_exit(NULL, from, to, reason, token, 0);
- break;
+ if (!erts_proc_lookup(to)) break; /* Process not alive */
+
+ if (reason == THE_NON_VALUE) {
+#ifdef ERTS_DIST_MSG_DBG
+ dist_msg_dbg(edep, "MSG", buf, orig_len);
+#endif
+ }
+ erts_proc_sig_send_dist_exit(dep, from, to, edep, ede_hfrag, reason, token);
+ break;
+ }
case DOP_GROUP_LEADER:
if (tuple_arity != 3) {
goto invalid_message;
@@ -1733,13 +2116,15 @@ int erts_net_message(Port *prt,
(void) erts_proc_sig_send_group_leader(NULL, to, from, NIL);
break;
- default:
+ default:
goto invalid_message;
}
- erts_factory_close(&factory);
- if (ctl != ctl_default) {
- erts_free(ERTS_ALC_T_DCTRL_BUF, (void *) ctl);
+ if (ede_hfrag == NULL) {
+ erts_factory_close(&factory);
+ if (ctl != ctl_default) {
+ erts_free(ERTS_ALC_T_DCTRL_BUF, (void *) ctl);
+ }
}
UnUseTmpHeapNoproc(DIST_CTL_DEFAULT_SIZE);
ERTS_CHK_NO_PROC_LOCKS;
@@ -1752,9 +2137,14 @@ int erts_net_message(Port *prt,
}
decode_error:
PURIFY_MSG("data error");
- erts_factory_close(&factory);
- if (ctl != ctl_default) {
- erts_free(ERTS_ALC_T_DCTRL_BUF, (void *) ctl);
+ if (ede_hfrag == NULL) {
+ erts_factory_close(&factory);
+ if (ctl != ctl_default) {
+ erts_free(ERTS_ALC_T_DCTRL_BUF, (void *) ctl);
+ }
+ } else {
+ erts_free_dist_ext_copy(erts_get_dist_ext(ede_hfrag));
+ free_message_buffer(ede_hfrag);
}
data_error:
UnUseTmpHeapNoproc(DIST_CTL_DEFAULT_SIZE);
@@ -1763,18 +2153,21 @@ data_error:
return -1;
}
-static int dsig_send_ctl(ErtsDSigData* dsdp, Eterm ctl, int force_busy)
+static int dsig_send_exit(ErtsDSigSendContext *ctx, Eterm ctl, Eterm msg)
+{
+ ctx->ctl = ctl;
+ ctx->msg = msg;
+ return erts_dsig_send(ctx);
+}
+
+static int dsig_send_ctl(ErtsDSigSendContext *ctx, Eterm ctl)
{
- struct erts_dsig_send_context ctx;
int ret;
- ctx.ctl = ctl;
- ctx.msg = THE_NON_VALUE;
- ctx.force_busy = force_busy;
- ctx.phase = ERTS_DSIG_SEND_PHASE_INIT;
-#ifdef DEBUG
- ctx.reds = 1; /* provoke assert below (no reduction count without msg) */
-#endif
- ret = erts_dsig_send(dsdp, &ctx);
+ ctx->ctl = ctl;
+ ctx->msg = THE_NON_VALUE;
+ ctx->from = THE_NON_VALUE;
+ ctx->reds = 1; /* provoke assert below (no reduction count without msg) */
+ ret = erts_dsig_send(ctx);
ASSERT(ret != ERTS_DSIG_SEND_CONTINUE);
return ret;
}
@@ -1805,7 +2198,117 @@ notify_dist_data(Process *c_p, Eterm pid)
}
int
-erts_dsig_send(ErtsDSigData *dsdp, struct erts_dsig_send_context* ctx)
+erts_dsig_prepare(ErtsDSigSendContext *ctx,
+ DistEntry *dep,
+ Process *proc,
+ ErtsProcLocks proc_locks,
+ ErtsDSigPrepLock dspl,
+ int no_suspend,
+ int no_trap,
+ int connect)
+{
+ int res;
+
+ if (!erts_is_alive)
+ return ERTS_DSIG_PREP_NOT_ALIVE;
+ if (!dep) {
+ ASSERT(!connect);
+ return ERTS_DSIG_PREP_NOT_CONNECTED;
+ }
+
+#ifdef ERTS_ENABLE_LOCK_CHECK
+ if (connect) {
+ erts_proc_lc_might_unlock(proc, proc_locks);
+ }
+#endif
+
+retry:
+ erts_de_rlock(dep);
+
+ if (dep->state == ERTS_DE_STATE_CONNECTED) {
+ res = ERTS_DSIG_PREP_CONNECTED;
+ }
+ else if (dep->state == ERTS_DE_STATE_PENDING) {
+ res = ERTS_DSIG_PREP_PENDING;
+ }
+ else if (dep->state == ERTS_DE_STATE_EXITING) {
+ res = ERTS_DSIG_PREP_NOT_CONNECTED;
+ goto fail;
+ }
+ else if (connect) {
+ ASSERT(dep->state == ERTS_DE_STATE_IDLE);
+ erts_de_runlock(dep);
+ if (!erts_auto_connect(dep, proc, proc_locks)) {
+ return ERTS_DSIG_PREP_NOT_ALIVE;
+ }
+ goto retry;
+ }
+ else {
+ ASSERT(dep->state == ERTS_DE_STATE_IDLE);
+ res = ERTS_DSIG_PREP_NOT_CONNECTED;
+ goto fail;
+ }
+
+ if (no_suspend) {
+ if (erts_atomic32_read_acqb(&dep->qflgs) & ERTS_DE_QFLG_BUSY) {
+ res = ERTS_DSIG_PREP_WOULD_SUSPEND;
+ goto fail;
+ }
+ }
+
+ ctx->c_p = proc;
+ ctx->dep = dep;
+ ctx->deref_dep = 0;
+ ctx->cid = dep->cid;
+ ctx->connection_id = dep->connection_id;
+ ctx->no_suspend = no_suspend;
+ ctx->no_trap = no_trap;
+ ctx->flags = dep->flags;
+ ctx->return_term = am_true;
+ ctx->phase = ERTS_DSIG_SEND_PHASE_INIT;
+ ctx->from = proc ? proc->common.id : am_undefined;
+ ctx->reds = no_trap ? 1 : (Sint) (ERTS_BIF_REDS_LEFT(proc) * TERM_TO_BINARY_LOOP_FACTOR);
+ if (dspl == ERTS_DSP_NO_LOCK)
+ erts_de_runlock(dep);
+ return res;
+
+ fail:
+ erts_de_runlock(dep);
+ return res;
+}
+
+static
+void erts_schedule_dist_command(Port *prt, DistEntry *dist_entry)
+{
+ DistEntry *dep;
+ Eterm id;
+
+ if (prt) {
+ ERTS_LC_ASSERT(erts_lc_is_port_locked(prt));
+ ASSERT((erts_atomic32_read_nob(&prt->state)
+ & ERTS_PORT_SFLGS_DEAD) == 0);
+
+ dep = (DistEntry*) erts_prtsd_get(prt, ERTS_PRTSD_DIST_ENTRY);
+ ASSERT(dep);
+ id = prt->common.id;
+ }
+ else {
+ ASSERT(dist_entry);
+ ERTS_LC_ASSERT(erts_lc_rwmtx_is_rlocked(&dist_entry->rwmtx)
+ || erts_lc_rwmtx_is_rwlocked(&dist_entry->rwmtx));
+ ASSERT(is_internal_port(dist_entry->cid));
+
+ dep = dist_entry;
+ id = dep->cid;
+ }
+
+ if (!erts_atomic_xchg_mb(&dep->dist_cmd_scheduled, 1))
+ erts_port_task_schedule(id, &dep->dist_cmd, ERTS_PORT_TASK_DIST_CMD);
+}
+
+
+int
+erts_dsig_send(ErtsDSigSendContext *ctx)
{
int retval;
Sint initial_reds = ctx->reds;
@@ -1814,11 +2317,13 @@ erts_dsig_send(ErtsDSigData *dsdp, struct erts_dsig_send_context* ctx)
while (1) {
switch (ctx->phase) {
case ERTS_DSIG_SEND_PHASE_INIT:
- ctx->flags = dsdp->flags;
- ctx->c_p = dsdp->proc;
+ ctx->flags = ctx->flags;
+ ctx->c_p = ctx->c_p;
- if (!ctx->c_p || dsdp->no_suspend)
- ctx->force_busy = 1;
+ if (!ctx->c_p) {
+ ctx->no_trap = 1;
+ ctx->no_suspend = 1;
+ }
ERTS_LC_ASSERT(!ctx->c_p
|| (ERTS_PROC_LOCK_MAIN
@@ -1837,9 +2342,11 @@ erts_dsig_send(ErtsDSigData *dsdp, struct erts_dsig_send_context* ctx)
}
#ifdef ERTS_DIST_MSG_DBG
- erts_fprintf(stderr, ">> CTL: %T\n", ctx->ctl);
+ erts_fprintf(dbg_file, "SEND: CTL: %s: %.80T\n",
+ erts_dop_to_string(unsigned_val(tuple_val(ctx->ctl)[1])),
+ ctx->ctl);
if (is_value(ctx->msg))
- erts_fprintf(stderr, " MSG: %T\n", ctx->msg);
+ erts_fprintf(dbg_file, " MSG: %.160T\n", ctx->msg);
#endif
ctx->data_size = ctx->max_finalize_prepend;
@@ -1856,25 +2363,45 @@ erts_dsig_send(ErtsDSigData *dsdp, struct erts_dsig_send_context* ctx)
ctx->phase = ERTS_DSIG_SEND_PHASE_MSG_SIZE;
case ERTS_DSIG_SEND_PHASE_MSG_SIZE:
- if (erts_encode_dist_ext_size_int(ctx->msg, ctx, &ctx->data_size)) {
- retval = ERTS_DSIG_SEND_CONTINUE;
- goto done;
- }
+ if (!ctx->no_trap) {
+ if (erts_encode_dist_ext_size_int(ctx->msg, ctx, &ctx->data_size)) {
+ retval = ERTS_DSIG_SEND_CONTINUE;
+ goto done;
+ }
+ } else {
+ erts_encode_dist_ext_size(ctx->msg, ctx->flags, ctx->acmp, &ctx->data_size);
+ }
ctx->phase = ERTS_DSIG_SEND_PHASE_ALLOC;
case ERTS_DSIG_SEND_PHASE_ALLOC:
erts_finalize_atom_cache_map(ctx->acmp, ctx->flags);
- ctx->dhdr_ext_size = erts_encode_ext_dist_header_size(ctx->acmp);
- ctx->data_size += ctx->dhdr_ext_size;
+ if (ctx->flags & DFLAG_FRAGMENTS && is_value(ctx->msg) && is_not_immed(ctx->msg)) {
+ /* Calculate the max number of fragments that are needed */
+ ASSERT(is_pid(ctx->from) &&
+ "from has to be a pid because it is used as sequence id");
+ ctx->fragments = ctx->data_size / ERTS_DIST_FRAGMENT_SIZE + 1;
+ } else
+ ctx->fragments = 1;
- ctx->obuf = alloc_dist_obuf(ctx->data_size);
- ctx->obuf->ext_endp = &ctx->obuf->data[0] + ctx->max_finalize_prepend + ctx->dhdr_ext_size;
+ ctx->dhdr_ext_size = erts_encode_ext_dist_header_size(ctx->acmp, ctx->fragments);
+
+ ctx->obuf = alloc_dist_obuf(
+ ctx->dhdr_ext_size + ctx->data_size +
+ (ctx->fragments-1) * ERTS_DIST_FRAGMENT_HEADER_SIZE,
+ ctx->fragments);
+ ctx->obuf->ext_start = &ctx->obuf->extp[0];
+ ctx->obuf->ext_endp = &ctx->obuf->extp[0] + ctx->max_finalize_prepend + ctx->dhdr_ext_size;
/* Encode internal version of dist header */
- ctx->obuf->extp = erts_encode_ext_dist_header_setup(ctx->obuf->ext_endp, ctx->acmp);
+ ctx->obuf->extp = erts_encode_ext_dist_header_setup(
+ ctx->obuf->ext_endp, ctx->acmp, ctx->fragments, ctx->from);
/* Encode control message */
erts_encode_dist_ext(ctx->ctl, &ctx->obuf->ext_endp, ctx->flags, ctx->acmp, NULL, NULL);
+
+ ctx->obuf->hdrp = NULL;
+ ctx->obuf->hdr_endp = NULL;
+
if (is_non_value(ctx->msg)) {
ctx->obuf->msg_start = NULL;
ctx->phase = ERTS_DSIG_SEND_PHASE_FIN;
@@ -1888,44 +2415,137 @@ erts_dsig_send(ErtsDSigData *dsdp, struct erts_dsig_send_context* ctx)
ctx->phase = ERTS_DSIG_SEND_PHASE_MSG_ENCODE;
case ERTS_DSIG_SEND_PHASE_MSG_ENCODE:
- if (erts_encode_dist_ext(ctx->msg, &ctx->obuf->ext_endp, ctx->flags, ctx->acmp, &ctx->u.ec, &ctx->reds)) {
- retval = ERTS_DSIG_SEND_CONTINUE;
- goto done;
- }
+ if (!ctx->no_trap) {
+ if (erts_encode_dist_ext(ctx->msg, &ctx->obuf->ext_endp, ctx->flags,
+ ctx->acmp, &ctx->u.ec, &ctx->reds)) {
+ retval = ERTS_DSIG_SEND_CONTINUE;
+ goto done;
+ }
+ } else {
+ erts_encode_dist_ext(ctx->msg, &ctx->obuf->ext_endp, ctx->flags,
+ ctx->acmp, NULL, NULL);
+ }
- ctx->phase = ERTS_DSIG_SEND_PHASE_FIN;
+ ctx->phase = ERTS_DSIG_SEND_PHASE_FIN;
case ERTS_DSIG_SEND_PHASE_FIN: {
- DistEntry *dep = dsdp->dep;
- int suspended = 0;
- int resume = 0;
ASSERT(ctx->obuf->extp < ctx->obuf->ext_endp);
- ASSERT(&ctx->obuf->data[0] <= ctx->obuf->extp - ctx->max_finalize_prepend);
- ASSERT(ctx->obuf->ext_endp <= &ctx->obuf->data[0] + ctx->data_size);
+ ASSERT(((byte*)&ctx->obuf->bin->orig_bytes[0]+obuf_list_size) <= ctx->obuf->extp - ctx->max_finalize_prepend);
+ ASSERT(ctx->obuf->ext_endp <= ((byte*)ctx->obuf->bin->orig_bytes+obuf_list_size) + ctx->data_size + ctx->dhdr_ext_size);
ctx->data_size = ctx->obuf->ext_endp - ctx->obuf->extp;
ctx->obuf->hopefull_flags = ctx->u.ec.hopefull_flags;
- /*
+
+ if (ctx->fragments > 1) {
+ int fin_fragments;
+ int i;
+ byte *msg = ctx->obuf->msg_start,
+ *msg_end = ctx->obuf->ext_endp,
+ *hdrp = msg_end;
+
+ ASSERT((ctx->obuf->hopefull_flags & ctx->flags) == ctx->obuf->hopefull_flags);
+ ASSERT(get_int64(ctx->obuf->extp + 1 + 1 + 8) == ctx->fragments);
+
+ /* Now that encoding is done we know how large the term will
+ be so we adjust the number of fragments to send. Note that
+ this can mean that only 1 fragment is sent. */
+ fin_fragments = (ctx->obuf->ext_endp - ctx->obuf->msg_start + ERTS_DIST_FRAGMENT_SIZE-1) /
+ ERTS_DIST_FRAGMENT_SIZE - 1;
+
+ /* Update the frag_id in the DIST_FRAG_HEADER */
+ put_int64(fin_fragments+1, ctx->obuf->extp + 1 + 1 + 8);
+
+ if (fin_fragments > 0)
+ msg += ERTS_DIST_FRAGMENT_SIZE;
+ else
+ msg = msg_end;
+ ctx->obuf->next = &ctx->obuf[1];
+ ctx->obuf->ext_endp = msg;
+
+ /* Loop through all fragments, updating the output buffers
+ to be correct and also writing the DIST_FRAG_CONT header. */
+ for (i = 1; i < fin_fragments + 1; i++) {
+ ctx->obuf[i].hopefull_flags = 0;
+ ctx->obuf[i].extp = msg;
+ ctx->obuf[i].ext_start = msg;
+ if (msg + ERTS_DIST_FRAGMENT_SIZE > msg_end)
+ ctx->obuf[i].ext_endp = msg_end;
+ else {
+ msg += ERTS_DIST_FRAGMENT_SIZE;
+ ctx->obuf[i].ext_endp = msg;
+ }
+ ASSERT(ctx->obuf[i].ext_endp > ctx->obuf[i].extp);
+ ctx->obuf[i].hdrp = erts_encode_ext_dist_header_fragment(
+ &hdrp, fin_fragments - i + 1, ctx->from);
+ ctx->obuf[i].hdr_endp = hdrp;
+ ctx->obuf[i].next = &ctx->obuf[i+1];
+ }
+ /* If the initial fragment calculation was incorrect we free the
+ remaining output buffers. */
+ for (; i < ctx->fragments; i++) {
+ free_dist_obuf(&ctx->obuf[i]);
+ }
+ if (!ctx->no_trap && !ctx->no_suspend)
+ ctx->reds -= ctx->fragments;
+ ctx->fragments = fin_fragments + 1;
+ }
+
+ ctx->phase = ERTS_DSIG_SEND_PHASE_SEND;
+
+ if (ctx->reds <= 0) {
+ retval = ERTS_DSIG_SEND_CONTINUE;
+ goto done;
+ }
+ }
+ case ERTS_DSIG_SEND_PHASE_SEND: {
+ /*
* Signal encoded; now verify that the connection still exists,
* and if so enqueue the signal and schedule it for send.
*/
- ctx->obuf->next = NULL;
+ DistEntry *dep = ctx->dep;
+ int suspended = 0;
+ int resume = 0;
+ int i;
erts_de_rlock(dep);
cid = dep->cid;
if (dep->state == ERTS_DE_STATE_EXITING
|| dep->state == ERTS_DE_STATE_IDLE
- || dep->connection_id != dsdp->connection_id) {
+ || dep->connection_id != ctx->connection_id) {
/* Not the same connection as when we started; drop message... */
erts_de_runlock(dep);
- free_dist_obuf(ctx->obuf);
+ for (i = 0; i < ctx->fragments; i++)
+ free_dist_obuf(&ctx->obuf[i]);
+ ctx->fragments = 0;
}
else {
- Sint qsize;
+ Sint qsize = erts_atomic_read_nob(&dep->qsize);
erts_aint32_t qflgs;
ErtsProcList *plp = NULL;
Eterm notify_proc = NIL;
- Sint obsz = size_obuf(ctx->obuf);
+ Sint obsz;
+ int fragments;
+
+ /* Calculate how many fragments to send. This depends on
+ the available space in the distr queue and the amount
+ of remaining reductions. */
+ for (fragments = 0, obsz = 0;
+ fragments < ctx->fragments &&
+ ((ctx->reds > 0 && (qsize + obsz) < erts_dist_buf_busy_limit) ||
+ ctx->no_trap || ctx->no_suspend);
+ fragments++) {
+#ifdef DEBUG
+ int reds = 100;
+#else
+ int reds = 10;
+#endif
+ if (!ctx->no_trap && !ctx->no_suspend)
+ ctx->reds -= reds;
+ obsz += size_obuf(&ctx->obuf[fragments]);
+ }
+
+ ASSERT(fragments == ctx->fragments ||
+ (!ctx->no_trap && !ctx->no_suspend));
erts_mtx_lock(&dep->qlock);
qsize = erts_atomic_add_read_nob(&dep->qsize, (erts_aint_t) obsz);
@@ -1946,7 +2566,7 @@ erts_dsig_send(ErtsDSigData *dsdp, struct erts_dsig_send_context* ctx)
/* else: requester will send itself the message... */
qflgs &= ~ERTS_DE_QFLG_REQ_INFO;
}
- if (!ctx->force_busy && (qflgs & ERTS_DE_QFLG_BUSY)) {
+ if (!ctx->no_suspend && (qflgs & ERTS_DE_QFLG_BUSY)) {
erts_mtx_unlock(&dep->qlock);
plp = erts_proclist_create(ctx->c_p);
@@ -1955,14 +2575,27 @@ erts_dsig_send(ErtsDSigData *dsdp, struct erts_dsig_send_context* ctx)
erts_mtx_lock(&dep->qlock);
}
- /* Enqueue obuf on dist entry */
- if (dep->out_queue.last)
- dep->out_queue.last->next = ctx->obuf;
- else
- dep->out_queue.first = ctx->obuf;
- dep->out_queue.last = ctx->obuf;
+ if (fragments > 1) {
+ if (!ctx->obuf->hdrp) {
+ ASSERT(get_int64(ctx->obuf->extp + 10) == ctx->fragments);
+ } else {
+ ASSERT(get_int64(ctx->obuf->hdrp + 10) == ctx->fragments);
+ }
+ }
+
+ if (fragments) {
+ ctx->obuf[fragments-1].next = NULL;
+ if (dep->out_queue.last)
+ dep->out_queue.last->next = ctx->obuf;
+ else
+ dep->out_queue.first = ctx->obuf;
+ dep->out_queue.last = &ctx->obuf[fragments-1];
+
+ ctx->fragments -= fragments;
+ ctx->obuf = &ctx->obuf[fragments];
+ }
- if (!ctx->force_busy) {
+ if (!ctx->no_suspend) {
qflgs = erts_atomic32_read_nob(&dep->qflgs);
if (!(qflgs & ERTS_DE_QFLG_BUSY)) {
if (suspended)
@@ -2009,6 +2642,13 @@ erts_dsig_send(ErtsDSigData *dsdp, struct erts_dsig_send_context* ctx)
* erroneously scheduled when it shouldn't be.
*/
}
+ /* More fragments left to be sent, yield and re-schedule */
+ if (ctx->fragments) {
+ retval = ERTS_DSIG_SEND_CONTINUE;
+ if (!resume && erts_system_monitor_flags.busy_dist_port)
+ monitor_generic(ctx->c_p, am_busy_dist_port, cid);
+ goto done;
+ }
}
ctx->obuf = NULL;
@@ -2093,9 +2733,9 @@ static Uint
dist_port_commandv(Port *prt, ErtsDistOutputBuf *obuf)
{
int fpe_was_unmasked;
- ErlDrvSizeT size;
- SysIOVec iov[2];
- ErlDrvBinary* bv[2];
+ ErlDrvSizeT size = 0;
+ SysIOVec iov[3];
+ ErlDrvBinary* bv[3];
ErlIOVec eiov;
ERTS_CHK_NO_PROC_LOCKS;
@@ -2110,12 +2750,31 @@ dist_port_commandv(Port *prt, ErtsDistOutputBuf *obuf)
eiov.vsize = 1;
}
else {
- size = obuf->ext_endp - obuf->extp;
+ int i = 1;
eiov.vsize = 2;
- iov[1].iov_base = obuf->extp;
- iov[1].iov_len = size;
- bv[1] = Binary2ErlDrvBinary(ErtsDistOutputBuf2Binary(obuf));
+ if (obuf->hdrp) {
+ eiov.vsize = 3;
+ iov[i].iov_base = obuf->hdrp;
+ iov[i].iov_len = obuf->hdr_endp - obuf->hdrp;
+ size += iov[i].iov_len;
+ bv[i] = Binary2ErlDrvBinary(ErtsDistOutputBuf2Binary(obuf));
+#ifdef ERTS_RAW_DIST_MSG_DBG
+ erts_fprintf(dbg_file, "SEND: ");
+ bw(iov[i].iov_base, iov[i].iov_len);
+#endif
+ i++;
+
+ }
+
+ iov[i].iov_base = obuf->extp;
+ iov[i].iov_len = obuf->ext_endp - obuf->extp;
+#ifdef ERTS_RAW_DIST_MSG_DBG
+ erts_fprintf(dbg_file, "SEND: ");
+ bw(iov[i].iov_base, iov[i].iov_len);
+#endif
+ size += iov[i].iov_len;
+ bv[i] = Binary2ErlDrvBinary(ErtsDistOutputBuf2Binary(obuf));
}
eiov.size = size;
@@ -2222,6 +2881,23 @@ erts_dist_command(Port *prt, int initial_reds)
dep->finalized_out_queue.first = NULL;
dep->finalized_out_queue.last = NULL;
+#ifdef DEBUG
+ {
+ Uint sz = 0;
+ ErtsDistOutputBuf *curr = oq.first;
+ while (curr) {
+ sz += size_obuf(curr);
+ curr = curr->next;
+ }
+ curr = foq.first;
+ while (curr) {
+ sz += size_obuf(curr);
+ curr = curr->next;
+ }
+ ASSERT(sz <= erts_atomic_read_nob(&dep->qsize));
+ }
+#endif
+
sched_flags = erts_atomic32_read_nob(&prt->sched.flags);
if (reds < 0)
@@ -2235,10 +2911,6 @@ erts_dist_command(Port *prt, int initial_reds)
size = (*send)(prt, foq.first);
erts_atomic64_inc_nob(&dep->out);
esdp->io.out += (Uint64) size;
-#ifdef ERTS_RAW_DIST_MSG_DBG
- erts_fprintf(stderr, ">> ");
- bw(foq.first->extp, size);
-#endif
reds -= ERTS_PORT_REDS_DIST_CMD_DATA(size);
fob = foq.first;
obufsize += size_obuf(fob);
@@ -2307,15 +2979,11 @@ erts_dist_command(Port *prt, int initial_reds)
preempt = 1;
break;
}
- ASSERT(&oq.first->data[0] <= oq.first->extp
- && oq.first->extp <= oq.first->ext_endp);
+ ASSERT(oq.first->bin->orig_bytes <= (char*)oq.first->extp
+ && oq.first->extp <= oq.first->ext_endp);
size = (*send)(prt, oq.first);
erts_atomic64_inc_nob(&dep->out);
esdp->io.out += (Uint64) size;
-#ifdef ERTS_RAW_DIST_MSG_DBG
- erts_fprintf(stderr, ">> ");
- bw(oq.first->extp, size);
-#endif
reds -= ERTS_PORT_REDS_DIST_CMD_DATA(size);
fob = oq.first;
obufsize += size_obuf(fob);
@@ -2452,100 +3120,6 @@ erts_dist_command(Port *prt, int initial_reds)
goto done;
}
-#if 0
-
-int
-dist_data_finalize(Process *c_p, int reds_limit)
-{
- int reds = 5;
- DistEntry *dep = ;
- ErtsDistOutputQueue oq, foq;
- ErtsDistOutputBuf *ob;
- int preempt;
-
-
- erts_mtx_lock(&dep->qlock);
- flags = dep->flags;
- oq.first = dep->out_queue.first;
- oq.last = dep->out_queue.last;
- dep->out_queue.first = NULL;
- dep->out_queue.last = NULL;
- erts_mtx_unlock(&dep->qlock);
-
- if (!oq.first) {
- ASSERT(!oq.last);
- oq.first = dep->tmp_out_queue.first;
- oq.last = dep->tmp_out_queue.last;
- }
- else {
- ErtsDistOutputBuf *f, *l;
- ASSERT(oq.last);
- if (dep->tmp_out_queue.last) {
- dep->tmp_out_queue.last->next = oq.first;
- oq.first = dep->tmp_out_queue.first;
- }
- }
-
- if (!oq.first) {
- /* Nothing to do... */
- ASSERT(!oq.last);
- return reds;
- }
-
- foq.first = dep->finalized_out_queue.first;
- foq.last = dep->finalized_out_queue.last;
-
- preempt = 0;
- ob = oq.first;
- ASSERT(ob);
-
- do {
- ob->extp = erts_encode_ext_dist_header_finalize(ob->extp,
- dep->cache,
- flags);
- if (!(flags & DFLAG_DIST_HDR_ATOM_CACHE))
- *--ob->extp = PASS_THROUGH; /* Old node; 'pass through'
- needed */
- ASSERT(&ob->data[0] <= ob->extp && ob->extp < ob->ext_endp);
- reds += ERTS_PORT_REDS_DIST_CMD_FINALIZE;
- preempt = reds > reds_limit;
- if (preempt)
- break;
- ob = ob->next;
- } while (ob);
- /*
- * At least one buffer was finalized; if we got preempted,
- * ob points to the last buffer that we finalized.
- */
- if (foq.last)
- foq.last->next = oq.first;
- else
- foq.first = oq.first;
- if (!preempt) {
- /* All buffers finalized */
- foq.last = oq.last;
- oq.first = oq.last = NULL;
- }
- else {
- /* Not all buffers finalized; split oq. */
- foq.last = ob;
- oq.first = ob->next;
- if (oq.first)
- ob->next = NULL;
- else
- oq.last = NULL;
- }
-
- dep->finalized_out_queue.first = foq.first;
- dep->finalized_out_queue.last = foq.last;
- dep->tmp_out_queue.first = oq.first;
- dep->tmp_out_queue.last = oq.last;
-
- return reds;
-}
-
-#endif
-
BIF_RETTYPE
dist_ctrl_get_data_notification_1(BIF_ALIST_1)
{
@@ -2614,6 +3188,7 @@ dist_ctrl_put_data_2(BIF_ALIST_2)
ErlDrvSizeT size;
Eterm input_handler;
Uint32 conn_id;
+ Binary *bin = NULL;
if (is_binary(BIF_ARG_2))
size = binary_size(BIF_ARG_2);
@@ -2639,13 +3214,27 @@ dist_ctrl_put_data_2(BIF_ALIST_2)
if (size != 0) {
byte *data, *temp_alloc = NULL;
- data = (byte *) erts_get_aligned_binary_bytes(BIF_ARG_2, &temp_alloc);
+ if (binary_bitoffset(BIF_ARG_2))
+ data = (byte *) erts_get_aligned_binary_bytes(BIF_ARG_2, &temp_alloc);
+ else {
+ Eterm real_bin;
+ ProcBin *proc_bin;
+ Uint offset, bitoffs, bitsize;
+
+ ERTS_GET_REAL_BIN(BIF_ARG_2, real_bin, offset, bitoffs, bitsize);
+ ASSERT(bitoffs == 0);
+ data = binary_bytes(real_bin) + offset;
+ proc_bin = (ProcBin *)binary_val(real_bin);
+ if (proc_bin->thing_word == HEADER_PROC_BIN)
+ bin = proc_bin->val;
+ }
+
if (!data)
BIF_ERROR(BIF_P, BADARG);
erts_proc_unlock(BIF_P, ERTS_PROC_LOCK_MAIN);
- (void) erts_net_message(NULL, dep, conn_id, NULL, 0, data, size);
+ (void) erts_net_message(NULL, dep, conn_id, NULL, 0, bin, data, size);
/*
* We ignore any decode failures. On fatal failures the
* connection will be taken down by killing the
@@ -2879,6 +3468,10 @@ static void kill_connection(DistEntry *dep)
void
erts_kill_dist_connection(DistEntry *dep, Uint32 conn_id)
{
+#ifdef ERTS_DIST_MSG_DBG
+ erts_fprintf(dbg_file, "INTR: kill dist conn to %T:%u\n",
+ dep->sysname, conn_id);
+#endif
erts_de_rwlock(dep);
if (conn_id == dep->connection_id
&& dep->state == ERTS_DE_STATE_CONNECTED) {
@@ -2893,7 +3486,7 @@ struct print_to_data {
void *arg;
};
-static void doit_print_monitor_info(ErtsMonitor *mon, void *vptdp)
+static int doit_print_monitor_info(ErtsMonitor *mon, void *vptdp, Sint reds)
{
fmtfn_t to = ((struct print_to_data *) vptdp)->to;
void *arg = ((struct print_to_data *) vptdp)->arg;
@@ -2916,6 +3509,7 @@ static void doit_print_monitor_info(ErtsMonitor *mon, void *vptdp)
else
erts_print(to, arg, "%T\n", mdep->md.origin.other.item);
}
+ return 1;
}
static void print_monitor_info(fmtfn_t to, void *arg, DistEntry *dep)
@@ -2931,12 +3525,13 @@ static void print_monitor_info(fmtfn_t to, void *arg, DistEntry *dep)
}
}
-static void doit_print_link_info(ErtsLink *lnk, void *vptdp)
+static int doit_print_link_info(ErtsLink *lnk, void *vptdp, Sint reds)
{
struct print_to_data *ptdp = vptdp;
ErtsLink *lnk2 = erts_link_to_other(lnk, NULL);
erts_print(ptdp->to, ptdp->arg, "Remote link: %T %T\n",
lnk2->other.item, lnk->other.item);
+ return 1;
}
static void print_link_info(fmtfn_t to, void *arg, DistEntry *dep)
@@ -3538,8 +4133,9 @@ Sint erts_abort_connection_rwunlock(DistEntry* dep)
erts_set_dist_entry_not_connected(dep);
erts_de_rwunlock(dep);
- schedule_con_monitor_link_cleanup(mld, THE_NON_VALUE,
- THE_NON_VALUE, THE_NON_VALUE);
+ schedule_con_monitor_link_seq_cleanup(
+ mld, NULL, THE_NON_VALUE,
+ THE_NON_VALUE, THE_NON_VALUE);
if (resume_procs) {
int resumed = erts_resume_processes(resume_procs);
@@ -3845,16 +4441,16 @@ monitor_node(Process* p, Eterm Node, Eterm Bool, Eterm Options)
}
case am_true: {
- ErtsDSigData dsd;
- dsd.node = Node;
+ ErtsDSigSendContext ctx;
+ ctx.node = Node;
dep = erts_find_or_insert_dist_entry(Node);
if (dep == erts_this_dist_entry)
break;
- switch (erts_dsig_prepare(&dsd, dep, p,
+ switch (erts_dsig_prepare(&ctx, dep, p,
ERTS_PROC_LOCK_MAIN,
- ERTS_DSP_RLOCK, 0, async_connect)) {
+ ERTS_DSP_RLOCK, 0, 0, async_connect)) {
case ERTS_DSIG_PREP_NOT_ALIVE:
case ERTS_DSIG_PREP_NOT_CONNECTED:
/* Trap to either send 'nodedown' or do passive connection attempt */
@@ -3881,28 +4477,22 @@ monitor_node(Process* p, Eterm Node, Eterm Bool, Eterm Options)
Node);
mdep = (ErtsMonitorDataExtended *) erts_monitor_to_data(mon);
if (created) {
-#ifdef DEBUG
int inserted =
-#endif
erts_monitor_dist_insert(&mdep->md.target, dep->mld);
- ASSERT(inserted);
+ ASSERT(inserted); (void)inserted;
ASSERT(mdep->dist->connection_id == dep->connection_id);
}
else if (mdep->dist->connection_id != dep->connection_id) {
ErtsMonitorDataExtended *mdep2;
ErtsMonitor *mon2;
-#ifdef DEBUG
int inserted;
-#endif
mdep2 = ((ErtsMonitorDataExtended *)
erts_monitor_create(ERTS_MON_TYPE_NODE, NIL,
p->common.id, Node, NIL));
mon2 = &mdep2->md.origin;
-#ifdef DEBUG
inserted =
-#endif
erts_monitor_dist_insert(&mdep->md.target, dep->mld);
- ASSERT(inserted);
+ ASSERT(inserted); (void)inserted;
ASSERT(mdep2->dist->connection_id == dep->connection_id);
mdep2->uptr.node_monitors = mdep->uptr.node_monitors;
@@ -4017,6 +4607,10 @@ init_nodes_monitors(void)
{
erts_mtx_init(&nodes_monitors_mtx, "nodes_monitors", NIL,
ERTS_LOCK_FLAGS_PROPERTY_STATIC | ERTS_LOCK_FLAGS_CATEGORY_DISTRIBUTION);
+#ifdef DEBUG
+ erts_mtx_init(&erts_obuf_list_mtx, "sad", NIL,
+ ERTS_LOCK_FLAGS_PROPERTY_STATIC | ERTS_LOCK_FLAGS_CATEGORY_DISTRIBUTION);
+#endif
nodes_monitors = NULL;
no_nodes_monitors = 0;
}
@@ -4158,8 +4752,8 @@ typedef struct {
Uint i;
} ErtsNodesMonitorContext;
-static void
-save_nodes_monitor(ErtsMonitor *mon, void *vctxt)
+static int
+save_nodes_monitor(ErtsMonitor *mon, void *vctxt, Sint reds)
{
ErtsNodesMonitorContext *ctxt = vctxt;
ErtsMonitorData *mdp = erts_monitor_to_data(mon);
@@ -4171,6 +4765,7 @@ save_nodes_monitor(ErtsMonitor *mon, void *vctxt)
ctxt->nmdp[ctxt->i].options = mdp->origin.other.item;
ctxt->i++;
+ return 1;
}
static void
@@ -4303,8 +4898,8 @@ typedef struct {
} ErtsNodesMonitorInfoContext;
-static void
-nodes_monitor_info(ErtsMonitor *mon, void *vctxt)
+static int
+nodes_monitor_info(ErtsMonitor *mon, void *vctxt, Sint reds)
{
ErtsMonitorDataExtended *mdep;
ErtsNodesMonitorInfoContext *ctxt = vctxt;
@@ -4351,6 +4946,7 @@ nodes_monitor_info(ErtsMonitor *mon, void *vctxt)
ctxt->hpp = hpp;
ctxt->szp = szp;
ctxt->res = res;
+ return 1;
}
Eterm
diff --git a/erts/emulator/beam/dist.h b/erts/emulator/beam/dist.h
index d4d7874a70..c4bb967592 100644
--- a/erts/emulator/beam/dist.h
+++ b/erts/emulator/beam/dist.h
@@ -47,6 +47,8 @@
#define DFLAG_SEND_SENDER 0x80000
#define DFLAG_BIG_SEQTRACE_LABELS 0x100000
#define DFLAG_NO_MAGIC 0x200000 /* internal for pending connection */
+#define DFLAG_EXIT_PAYLOAD 0x400000
+#define DFLAG_FRAGMENTS 0x800000
/* Mandatory flags for distribution */
#define DFLAG_DIST_MANDATORY (DFLAG_EXTENDED_REFERENCES \
@@ -75,7 +77,9 @@
| DFLAG_MAP_TAG \
| DFLAG_BIG_CREATION \
| DFLAG_SEND_SENDER \
- | DFLAG_BIG_SEQTRACE_LABELS)
+ | DFLAG_BIG_SEQTRACE_LABELS \
+ | DFLAG_EXIT_PAYLOAD \
+ | DFLAG_FRAGMENTS)
/* Flags addable by local distr implementations */
#define DFLAG_DIST_ADDABLE DFLAG_DIST_DEFAULT
@@ -99,26 +103,35 @@
| DFLAG_BIG_CREATION)
/* opcodes used in distribution messages */
-#define DOP_LINK 1
-#define DOP_SEND 2
-#define DOP_EXIT 3
-#define DOP_UNLINK 4
+enum dop {
+ DOP_LINK = 1,
+ DOP_SEND = 2,
+ DOP_EXIT = 3,
+ DOP_UNLINK = 4,
/* Ancient DOP_NODE_LINK (5) was here, can be reused */
-#define DOP_REG_SEND 6
-#define DOP_GROUP_LEADER 7
-#define DOP_EXIT2 8
-
-#define DOP_SEND_TT 12
-#define DOP_EXIT_TT 13
-#define DOP_REG_SEND_TT 16
-#define DOP_EXIT2_TT 18
-
-#define DOP_MONITOR_P 19
-#define DOP_DEMONITOR_P 20
-#define DOP_MONITOR_P_EXIT 21
-
-#define DOP_SEND_SENDER 22
-#define DOP_SEND_SENDER_TT 23
+ DOP_REG_SEND = 6,
+ DOP_GROUP_LEADER = 7,
+ DOP_EXIT2 = 8,
+
+ DOP_SEND_TT = 12,
+ DOP_EXIT_TT = 13,
+ DOP_REG_SEND_TT = 16,
+ DOP_EXIT2_TT = 18,
+
+ DOP_MONITOR_P = 19,
+ DOP_DEMONITOR_P = 20,
+ DOP_MONITOR_P_EXIT = 21,
+
+ DOP_SEND_SENDER = 22,
+ DOP_SEND_SENDER_TT = 23,
+
+ /* These are used when DFLAG_EXIT_PAYLOAD is detected */
+ DOP_PAYLOAD_EXIT = 24,
+ DOP_PAYLOAD_EXIT_TT = 25,
+ DOP_PAYLOAD_EXIT2 = 26,
+ DOP_PAYLOAD_EXIT2_TT = 27,
+ DOP_PAYLOAD_MONITOR_P_EXIT = 28
+};
/* distribution trap functions */
extern Export* dmonitor_node_trap;
@@ -129,15 +142,15 @@ typedef enum {
} ErtsDSigPrepLock;
-typedef struct {
- Process *proc;
- DistEntry *dep;
- Eterm node; /* used if dep == NULL */
- Eterm cid;
- Eterm connection_id;
- int no_suspend;
- Uint32 flags;
-} ErtsDSigData;
+/* Must be larger or equal to 16 */
+#ifdef DEBUG
+#define ERTS_DIST_FRAGMENT_SIZE 16
+#else
+/* This should be made configurable */
+#define ERTS_DIST_FRAGMENT_SIZE (64 * 1024)
+#endif
+
+#define ERTS_DIST_FRAGMENT_HEADER_SIZE (1 + 1 + 8 + 8) /* magic, header, seq id, frag id*/
#define ERTS_DE_BUSY_LIMIT (1024*1024)
extern int erts_dist_buf_busy_limit;
@@ -159,124 +172,6 @@ extern int erts_is_alive;
/* Pending connection; signals can be enqueued */
#define ERTS_DSIG_PREP_PENDING 4
-ERTS_GLB_INLINE int erts_dsig_prepare(ErtsDSigData *,
- DistEntry*,
- Process *,
- ErtsProcLocks,
- ErtsDSigPrepLock,
- int,
- int);
-
-ERTS_GLB_INLINE
-void erts_schedule_dist_command(Port *, DistEntry *);
-
-int erts_auto_connect(DistEntry* dep, Process *proc, ErtsProcLocks proc_locks);
-
-#if ERTS_GLB_INLINE_INCL_FUNC_DEF
-
-ERTS_GLB_INLINE int
-erts_dsig_prepare(ErtsDSigData *dsdp,
- DistEntry *dep,
- Process *proc,
- ErtsProcLocks proc_locks,
- ErtsDSigPrepLock dspl,
- int no_suspend,
- int connect)
-{
- int res;
-
- if (!erts_is_alive)
- return ERTS_DSIG_PREP_NOT_ALIVE;
- if (!dep) {
- ASSERT(!connect);
- return ERTS_DSIG_PREP_NOT_CONNECTED;
- }
-
-#ifdef ERTS_ENABLE_LOCK_CHECK
- if (connect) {
- erts_proc_lc_might_unlock(proc, proc_locks);
- }
-#endif
-
-retry:
- erts_de_rlock(dep);
-
- if (dep->state == ERTS_DE_STATE_CONNECTED) {
- res = ERTS_DSIG_PREP_CONNECTED;
- }
- else if (dep->state == ERTS_DE_STATE_PENDING) {
- res = ERTS_DSIG_PREP_PENDING;
- }
- else if (dep->state == ERTS_DE_STATE_EXITING) {
- res = ERTS_DSIG_PREP_NOT_CONNECTED;
- goto fail;
- }
- else if (connect) {
- ASSERT(dep->state == ERTS_DE_STATE_IDLE);
- erts_de_runlock(dep);
- if (!erts_auto_connect(dep, proc, proc_locks)) {
- return ERTS_DSIG_PREP_NOT_ALIVE;
- }
- goto retry;
- }
- else {
- ASSERT(dep->state == ERTS_DE_STATE_IDLE);
- res = ERTS_DSIG_PREP_NOT_CONNECTED;
- goto fail;
- }
-
- if (no_suspend) {
- if (erts_atomic32_read_acqb(&dep->qflgs) & ERTS_DE_QFLG_BUSY) {
- res = ERTS_DSIG_PREP_WOULD_SUSPEND;
- goto fail;
- }
- }
- dsdp->proc = proc;
- dsdp->dep = dep;
- dsdp->cid = dep->cid;
- dsdp->connection_id = dep->connection_id;
- dsdp->no_suspend = no_suspend;
- dsdp->flags = dep->flags;
- if (dspl == ERTS_DSP_NO_LOCK)
- erts_de_runlock(dep);
- return res;
-
- fail:
- erts_de_runlock(dep);
- return res;
-}
-
-ERTS_GLB_INLINE
-void erts_schedule_dist_command(Port *prt, DistEntry *dist_entry)
-{
- DistEntry *dep;
- Eterm id;
-
- if (prt) {
- ERTS_LC_ASSERT(erts_lc_is_port_locked(prt));
- ASSERT((erts_atomic32_read_nob(&prt->state)
- & ERTS_PORT_SFLGS_DEAD) == 0);
-
- dep = (DistEntry*) erts_prtsd_get(prt, ERTS_PRTSD_DIST_ENTRY);
- ASSERT(dep);
- id = prt->common.id;
- }
- else {
- ASSERT(dist_entry);
- ERTS_LC_ASSERT(erts_lc_rwmtx_is_rlocked(&dist_entry->rwmtx)
- || erts_lc_rwmtx_is_rwlocked(&dist_entry->rwmtx));
- ASSERT(is_internal_port(dist_entry->cid));
-
- dep = dist_entry;
- id = dep->cid;
- }
-
- if (!erts_atomic_xchg_mb(&dep->dist_cmd_scheduled, 1))
- erts_port_task_schedule(id, &dep->dist_cmd, ERTS_PORT_TASK_DIST_CMD);
-}
-
-#endif
-
#ifdef DEBUG
#define ERTS_DBG_CHK_NO_DIST_LNK(D, R, L) \
erts_dbg_chk_no_dist_proc_link((D), (R), (L))
@@ -336,41 +231,45 @@ enum erts_dsig_send_phase {
ERTS_DSIG_SEND_PHASE_MSG_SIZE,
ERTS_DSIG_SEND_PHASE_ALLOC,
ERTS_DSIG_SEND_PHASE_MSG_ENCODE,
- ERTS_DSIG_SEND_PHASE_FIN
+ ERTS_DSIG_SEND_PHASE_FIN,
+ ERTS_DSIG_SEND_PHASE_SEND
};
-struct erts_dsig_send_context {
- enum erts_dsig_send_phase phase;
- Sint reds;
+typedef struct erts_dsig_send_context {
+ int connect;
+ int no_suspend;
+ int no_trap;
Eterm ctl;
Eterm msg;
- int force_busy;
+ Eterm from;
+ Eterm ctl_heap[6];
+ Eterm return_term;
+
+ DistEntry *dep;
+ Eterm node; /* used if dep == NULL */
+ Eterm cid;
+ Eterm connection_id;
+ int deref_dep;
+
+ enum erts_dsig_send_phase phase;
+ Sint reds;
+
Uint32 max_finalize_prepend;
Uint data_size, dhdr_ext_size;
ErtsAtomCacheMap *acmp;
ErtsDistOutputBuf *obuf;
+ Uint fragments;
Uint32 flags;
Process *c_p;
union {
TTBSizeContext sc;
TTBEncodeContext ec;
}u;
-};
-typedef struct {
- int suspend;
- int connect;
-
- Eterm ctl_heap[6];
- ErtsDSigData dsd;
- DistEntry *dep;
- int deref_dep;
- struct erts_dsig_send_context dss;
-
- Eterm return_term;
-}ErtsSendContext;
+} ErtsDSigSendContext;
+typedef struct dist_sequences DistSeqNode;
/*
* erts_dsig_send_* return values.
@@ -378,22 +277,23 @@ typedef struct {
#define ERTS_DSIG_SEND_OK 0
#define ERTS_DSIG_SEND_YIELD 1
#define ERTS_DSIG_SEND_CONTINUE 2
-
-extern int erts_dsig_send_link(ErtsDSigData *, Eterm, Eterm);
-extern int erts_dsig_send_msg(Eterm, Eterm, ErtsSendContext*);
-extern int erts_dsig_send_exit_tt(ErtsDSigData *, Eterm, Eterm, Eterm, Eterm);
-extern int erts_dsig_send_unlink(ErtsDSigData *, Eterm, Eterm);
-extern int erts_dsig_send_reg_msg(Eterm, Eterm, ErtsSendContext*);
-extern int erts_dsig_send_group_leader(ErtsDSigData *, Eterm, Eterm);
-extern int erts_dsig_send_exit(ErtsDSigData *, Eterm, Eterm, Eterm);
-extern int erts_dsig_send_exit2(ErtsDSigData *, Eterm, Eterm, Eterm);
-extern int erts_dsig_send_demonitor(ErtsDSigData *, Eterm, Eterm, Eterm, int);
-extern int erts_dsig_send_monitor(ErtsDSigData *, Eterm, Eterm, Eterm);
-extern int erts_dsig_send_m_exit(ErtsDSigData *, Eterm, Eterm, Eterm, Eterm);
-
-extern int erts_dsig_send(ErtsDSigData *dsdp, struct erts_dsig_send_context* ctx);
+#define ERTS_DSIG_SEND_TOO_LRG 3
+
+extern int erts_dsig_send_msg(ErtsDSigSendContext*, Eterm, Eterm);
+extern int erts_dsig_send_reg_msg(ErtsDSigSendContext*, Eterm, Eterm);
+extern int erts_dsig_send_link(ErtsDSigSendContext *, Eterm, Eterm);
+extern int erts_dsig_send_exit_tt(ErtsDSigSendContext *, Eterm, Eterm, Eterm, Eterm);
+extern int erts_dsig_send_unlink(ErtsDSigSendContext *, Eterm, Eterm);
+extern int erts_dsig_send_group_leader(ErtsDSigSendContext *, Eterm, Eterm);
+extern int erts_dsig_send_exit(ErtsDSigSendContext *, Eterm, Eterm, Eterm);
+extern int erts_dsig_send_exit2(ErtsDSigSendContext *, Eterm, Eterm, Eterm);
+extern int erts_dsig_send_demonitor(ErtsDSigSendContext *, Eterm, Eterm, Eterm);
+extern int erts_dsig_send_monitor(ErtsDSigSendContext *, Eterm, Eterm, Eterm);
+extern int erts_dsig_send_m_exit(ErtsDSigSendContext *, Eterm, Eterm, Eterm, Eterm);
+
+extern int erts_dsig_send(ErtsDSigSendContext *dsdp);
extern int erts_dsend_context_dtor(Binary*);
-extern Eterm erts_dsend_export_trap_context(Process* p, ErtsSendContext* ctx);
+extern Eterm erts_dsend_export_trap_context(Process* p, ErtsDSigSendContext* ctx);
extern int erts_dist_command(Port *prt, int reds);
extern void erts_dist_port_not_busy(Port *prt);
@@ -403,5 +303,18 @@ extern Uint erts_dist_cache_size(void);
extern Sint erts_abort_connection_rwunlock(DistEntry *dep);
+extern void erts_dist_seq_tree_foreach(
+ DistEntry *dep,
+ int (*func)(ErtsDistExternal *, void*, Sint), void *args);
+
+extern int erts_dsig_prepare(ErtsDSigSendContext *,
+ DistEntry*,
+ Process *,
+ ErtsProcLocks,
+ ErtsDSigPrepLock,
+ int,
+ int,
+ int);
+int erts_auto_connect(DistEntry* dep, Process *proc, ErtsProcLocks proc_locks);
#endif
diff --git a/erts/emulator/beam/erl_afit_alloc.c b/erts/emulator/beam/erl_afit_alloc.c
index 38289ea78a..f07137c883 100644
--- a/erts/emulator/beam/erl_afit_alloc.c
+++ b/erts/emulator/beam/erl_afit_alloc.c
@@ -102,6 +102,8 @@ erts_afalc_start(AFAllctr_t *afallctr,
allctr->add_mbc = NULL;
allctr->remove_mbc = NULL;
allctr->largest_fblk_in_mbc = NULL;
+ allctr->first_fblk_in_mbc = NULL;
+ allctr->next_fblk_in_mbc = NULL;
allctr->init_atoms = init_atoms;
#ifdef ERTS_ALLOC_UTIL_HARD_DEBUG
diff --git a/erts/emulator/beam/erl_alloc.c b/erts/emulator/beam/erl_alloc.c
index 9e36d5e0d1..e6169ebeaa 100644
--- a/erts/emulator/beam/erl_alloc.c
+++ b/erts/emulator/beam/erl_alloc.c
@@ -64,9 +64,6 @@
# error "Too many schedulers; cannot create that many pref alloc instances"
#endif
-#define ERTS_ALC_FIX_TYPE_IX(T) \
- (ERTS_ALC_T2N((T)) - ERTS_ALC_N_MIN_A_FIXED_SIZE)
-
#define ERTS_ALC_DEFAULT_MAX_THR_PREF ERTS_MAX_NO_OF_SCHEDULERS
#if defined(SMALL_MEMORY) || defined(PURIFY) || defined(VALGRIND)
@@ -156,20 +153,13 @@ ERTS_SCHED_PREF_QUICK_ALLOC_IMPL(aireq,
ErtsAlcType_t erts_fix_core_allocator_ix;
-enum allctr_type {
- GOODFIT,
- BESTFIT,
- AFIT,
- FIRSTFIT
-};
-
struct au_init {
int enable;
int thr_spec;
int disable_allowed;
int thr_spec_allowed;
int carrier_migration_allowed;
- enum allctr_type atype;
+ ErtsAlcStrat_t astrat;
struct {
AllctrInit_t util;
GFAllctrInit_t gf;
@@ -219,7 +209,9 @@ typedef struct {
struct au_init test_alloc;
} erts_alc_hndl_args_init_t;
-#define ERTS_AU_INIT__ {0, 0, 1, 1, 1, GOODFIT, DEFAULT_ALLCTR_INIT, {1,1,1,1}}
+#define ERTS_AU_INIT__ {0, 0, 1, 1, 1, \
+ ERTS_ALC_S_GOODFIT, DEFAULT_ALLCTR_INIT, \
+ {1,1,1,1}}
#define SET_DEFAULT_ALLOC_OPTS(IP) \
do { \
@@ -233,7 +225,7 @@ set_default_sl_alloc_opts(struct au_init *ip)
SET_DEFAULT_ALLOC_OPTS(ip);
ip->enable = AU_ALLOC_DEFAULT_ENABLE(1);
ip->thr_spec = 1;
- ip->atype = GOODFIT;
+ ip->astrat = ERTS_ALC_S_GOODFIT;
ip->init.util.name_prefix = "sl_";
ip->init.util.alloc_no = ERTS_ALC_A_SHORT_LIVED;
#ifndef SMALL_MEMORY
@@ -252,7 +244,7 @@ set_default_std_alloc_opts(struct au_init *ip)
SET_DEFAULT_ALLOC_OPTS(ip);
ip->enable = AU_ALLOC_DEFAULT_ENABLE(1);
ip->thr_spec = 1;
- ip->atype = BESTFIT;
+ ip->astrat = ERTS_ALC_S_BESTFIT;
ip->init.util.name_prefix = "std_";
ip->init.util.alloc_no = ERTS_ALC_A_STANDARD;
#ifndef SMALL_MEMORY
@@ -270,7 +262,7 @@ set_default_ll_alloc_opts(struct au_init *ip)
SET_DEFAULT_ALLOC_OPTS(ip);
ip->enable = AU_ALLOC_DEFAULT_ENABLE(1);
ip->thr_spec = 0;
- ip->atype = BESTFIT;
+ ip->astrat = ERTS_ALC_S_BESTFIT;
ip->init.bf.ao = 1;
ip->init.util.ramv = 0;
ip->init.util.mmsbc = 0;
@@ -299,7 +291,7 @@ set_default_literal_alloc_opts(struct au_init *ip)
ip->disable_allowed = 0;
ip->thr_spec_allowed = 0;
ip->carrier_migration_allowed = 0;
- ip->atype = BESTFIT;
+ ip->astrat = ERTS_ALC_S_BESTFIT;
ip->init.bf.ao = 1;
ip->init.util.ramv = 0;
ip->init.util.mmsbc = 0;
@@ -349,7 +341,7 @@ set_default_exec_alloc_opts(struct au_init *ip)
ip->disable_allowed = 0;
ip->thr_spec_allowed = 0;
ip->carrier_migration_allowed = 0;
- ip->atype = BESTFIT;
+ ip->astrat = ERTS_ALC_S_BESTFIT;
ip->init.bf.ao = 1;
ip->init.util.ramv = 0;
ip->init.util.mmsbc = 0;
@@ -378,7 +370,7 @@ set_default_temp_alloc_opts(struct au_init *ip)
ip->thr_spec = 1;
ip->disable_allowed = 0;
ip->carrier_migration_allowed = 0;
- ip->atype = AFIT;
+ ip->astrat = ERTS_ALC_S_AFIT;
ip->init.util.name_prefix = "temp_";
ip->init.util.alloc_no = ERTS_ALC_A_TEMPORARY;
#ifndef SMALL_MEMORY
@@ -397,7 +389,7 @@ set_default_eheap_alloc_opts(struct au_init *ip)
SET_DEFAULT_ALLOC_OPTS(ip);
ip->enable = AU_ALLOC_DEFAULT_ENABLE(1);
ip->thr_spec = 1;
- ip->atype = GOODFIT;
+ ip->astrat = ERTS_ALC_S_GOODFIT;
ip->init.util.name_prefix = "eheap_";
ip->init.util.alloc_no = ERTS_ALC_A_EHEAP;
#ifndef SMALL_MEMORY
@@ -416,7 +408,7 @@ set_default_binary_alloc_opts(struct au_init *ip)
SET_DEFAULT_ALLOC_OPTS(ip);
ip->enable = AU_ALLOC_DEFAULT_ENABLE(1);
ip->thr_spec = 1;
- ip->atype = BESTFIT;
+ ip->astrat = ERTS_ALC_S_BESTFIT;
ip->init.util.name_prefix = "binary_";
ip->init.util.alloc_no = ERTS_ALC_A_BINARY;
#ifndef SMALL_MEMORY
@@ -435,7 +427,7 @@ set_default_ets_alloc_opts(struct au_init *ip)
SET_DEFAULT_ALLOC_OPTS(ip);
ip->enable = AU_ALLOC_DEFAULT_ENABLE(1);
ip->thr_spec = 1;
- ip->atype = BESTFIT;
+ ip->astrat = ERTS_ALC_S_BESTFIT;
ip->init.util.name_prefix = "ets_";
ip->init.util.alloc_no = ERTS_ALC_A_ETS;
#ifndef SMALL_MEMORY
@@ -453,7 +445,7 @@ set_default_driver_alloc_opts(struct au_init *ip)
SET_DEFAULT_ALLOC_OPTS(ip);
ip->enable = AU_ALLOC_DEFAULT_ENABLE(1);
ip->thr_spec = 1;
- ip->atype = BESTFIT;
+ ip->astrat = ERTS_ALC_S_BESTFIT;
ip->init.util.name_prefix = "driver_";
ip->init.util.alloc_no = ERTS_ALC_A_DRIVER;
#ifndef SMALL_MEMORY
@@ -473,7 +465,7 @@ set_default_fix_alloc_opts(struct au_init *ip,
SET_DEFAULT_ALLOC_OPTS(ip);
ip->enable = AU_ALLOC_DEFAULT_ENABLE(1);
ip->thr_spec = 1;
- ip->atype = BESTFIT;
+ ip->astrat = ERTS_ALC_S_BESTFIT;
ip->init.bf.ao = 1;
ip->init.util.name_prefix = "fix_";
ip->init.util.fix_type_size = fix_type_sizes;
@@ -493,7 +485,7 @@ set_default_test_alloc_opts(struct au_init *ip)
SET_DEFAULT_ALLOC_OPTS(ip);
ip->enable = 0; /* Disabled by default */
ip->thr_spec = -1 * erts_no_schedulers;
- ip->atype = FIRSTFIT;
+ ip->astrat = ERTS_ALC_S_FIRSTFIT;
ip->init.aoff.crr_order = FF_AOFF;
ip->init.aoff.blk_order = FF_BF;
ip->init.util.name_prefix = "test_";
@@ -552,8 +544,8 @@ start_au_allocator(ErtsAlcType_t alctr_n,
static void
refuse_af_strategy(struct au_init *init)
{
- if (init->atype == AFIT)
- init->atype = GOODFIT;
+ if (init->astrat == ERTS_ALC_S_AFIT)
+ init->astrat = ERTS_ALC_S_GOODFIT;
}
#ifdef HARD_DEBUG
@@ -576,7 +568,10 @@ static void adjust_fix_alloc_sizes(UWord extra_block_size)
for (i=0; i < tspec->size; i++) {
Allctr_t* allctr = tspec->allctr[i];
for (j=0; j < ERTS_ALC_NO_FIXED_SIZES; ++j) {
- allctr->fix[j].type_size += extra_block_size;
+ size_t size = allctr->fix[j].type_size;
+ size = MAX(size + extra_block_size,
+ sizeof(ErtsAllctrDDBlock_t));
+ allctr->fix[j].type_size = size;
}
}
}
@@ -584,8 +579,11 @@ static void adjust_fix_alloc_sizes(UWord extra_block_size)
{
Allctr_t* allctr = erts_allctrs_info[ERTS_ALC_A_FIXED_SIZE].extra;
for (j=0; j < ERTS_ALC_NO_FIXED_SIZES; ++j) {
- allctr->fix[j].type_size += extra_block_size;
- }
+ size_t size = allctr->fix[j].type_size;
+ size = MAX(size + extra_block_size,
+ sizeof(ErtsAllctrDDBlock_t));
+ allctr->fix[j].type_size = size;
+ }
}
}
}
@@ -597,7 +595,7 @@ strategy_support_carrier_migration(struct au_init *auip)
* Currently only aoff* and ageff* support carrier
* migration, i.e, type AOFIRSTFIT.
*/
- return auip->atype == FIRSTFIT;
+ return auip->astrat == ERTS_ALC_S_FIRSTFIT;
}
static ERTS_INLINE void
@@ -612,7 +610,7 @@ adjust_carrier_migration_support(struct au_init *auip)
*/
if (!strategy_support_carrier_migration(auip)) {
/* Default to aoffcbf */
- auip->atype = FIRSTFIT;
+ auip->astrat = ERTS_ALC_S_FIRSTFIT;
auip->init.aoff.crr_order = FF_AOFF;
auip->init.aoff.blk_order = FF_BF;
}
@@ -1018,7 +1016,7 @@ start_au_allocator(ErtsAlcType_t alctr_n,
int i;
int size = 1;
void *as0;
- enum allctr_type atype;
+ ErtsAlcStrat_t astrat;
ErtsAllocatorFunctions_t *af = &erts_allctrs[alctr_n];
ErtsAllocatorInfo_t *ai = &erts_allctrs_info[alctr_n];
ErtsAllocatorThrSpec_t *tspec = &erts_allctr_thr_spec[alctr_n];
@@ -1077,7 +1075,7 @@ start_au_allocator(ErtsAlcType_t alctr_n,
for (i = 0; i < size; i++) {
Allctr_t *as;
- atype = init->atype;
+ astrat = init->astrat;
if (!init->thr_spec)
as0 = state;
@@ -1094,8 +1092,8 @@ start_au_allocator(ErtsAlcType_t alctr_n,
if (i != 0)
init->init.util.ts = 0;
else {
- if (atype == AFIT)
- atype = GOODFIT;
+ if (astrat == ERTS_ALC_S_AFIT)
+ astrat = ERTS_ALC_S_GOODFIT;
init->init.util.ts = 1;
}
init->init.util.tspec = init->thr_spec + 1;
@@ -1109,25 +1107,26 @@ start_au_allocator(ErtsAlcType_t alctr_n,
(((char *) fix_lists) + fix_list_size));
}
+ init->init.util.alloc_strat = astrat;
init->init.util.ix = i;
- switch (atype) {
- case GOODFIT:
+ switch (astrat) {
+ case ERTS_ALC_S_GOODFIT:
as = erts_gfalc_start((GFAllctr_t *) as0,
&init->init.gf,
&init->init.util);
break;
- case BESTFIT:
+ case ERTS_ALC_S_BESTFIT:
as = erts_bfalc_start((BFAllctr_t *) as0,
&init->init.bf,
&init->init.util);
break;
- case AFIT:
+ case ERTS_ALC_S_AFIT:
as = erts_afalc_start((AFAllctr_t *) as0,
&init->init.af,
&init->init.util);
break;
- case FIRSTFIT:
+ case ERTS_ALC_S_FIRSTFIT:
as = erts_aoffalc_start((AOFFAllctr_t *) as0,
&init->init.aoff,
&init->init.util);
@@ -1363,51 +1362,59 @@ handle_au_arg(struct au_init *auip,
else if(has_prefix("as", sub_param)) {
char *alg = get_value(sub_param + 2, argv, ip);
if (sys_strcmp("bf", alg) == 0) {
- auip->atype = BESTFIT;
+ auip->astrat = ERTS_ALC_S_BESTFIT;
auip->init.bf.ao = 0;
}
else if (sys_strcmp("aobf", alg) == 0) {
- auip->atype = BESTFIT;
+ auip->astrat = ERTS_ALC_S_BESTFIT;
auip->init.bf.ao = 1;
}
else if (sys_strcmp("gf", alg) == 0) {
- auip->atype = GOODFIT;
+ auip->astrat = ERTS_ALC_S_GOODFIT;
}
else if (sys_strcmp("af", alg) == 0) {
- auip->atype = AFIT;
+ auip->astrat = ERTS_ALC_S_AFIT;
}
else if (sys_strcmp("aoff", alg) == 0) {
- auip->atype = FIRSTFIT;
+ auip->astrat = ERTS_ALC_S_FIRSTFIT;
auip->init.aoff.crr_order = FF_AOFF;
auip->init.aoff.blk_order = FF_AOFF;
}
else if (sys_strcmp("aoffcbf", alg) == 0) {
- auip->atype = FIRSTFIT;
+ auip->astrat = ERTS_ALC_S_FIRSTFIT;
auip->init.aoff.crr_order = FF_AOFF;
auip->init.aoff.blk_order = FF_BF;
}
else if (sys_strcmp("aoffcaobf", alg) == 0) {
- auip->atype = FIRSTFIT;
+ auip->astrat = ERTS_ALC_S_FIRSTFIT;
auip->init.aoff.crr_order = FF_AOFF;
auip->init.aoff.blk_order = FF_AOBF;
}
else if (sys_strcmp("ageffcaoff", alg) == 0) {
- auip->atype = FIRSTFIT;
+ auip->astrat = ERTS_ALC_S_FIRSTFIT;
auip->init.aoff.crr_order = FF_AGEFF;
auip->init.aoff.blk_order = FF_AOFF;
}
else if (sys_strcmp("ageffcbf", alg) == 0) {
- auip->atype = FIRSTFIT;
+ auip->astrat = ERTS_ALC_S_FIRSTFIT;
auip->init.aoff.crr_order = FF_AGEFF;
auip->init.aoff.blk_order = FF_BF;
}
else if (sys_strcmp("ageffcaobf", alg) == 0) {
- auip->atype = FIRSTFIT;
+ auip->astrat = ERTS_ALC_S_FIRSTFIT;
auip->init.aoff.crr_order = FF_AGEFF;
auip->init.aoff.blk_order = FF_AOBF;
}
else {
- bad_value(param, sub_param + 1, alg);
+ if (auip->init.util.alloc_no == ERTS_ALC_A_TEST
+ && sys_strcmp("chaosff", alg) == 0) {
+ auip->astrat = ERTS_ALC_S_FIRSTFIT;
+ auip->init.aoff.crr_order = FF_CHAOS;
+ auip->init.aoff.blk_order = FF_CHAOS;
+ }
+ else {
+ bad_value(param, sub_param + 1, alg);
+ }
}
if (!strategy_support_carrier_migration(auip))
auip->init.util.acul = 0;
@@ -2030,33 +2037,55 @@ erts_realloc_n_enomem(ErtsAlcType_t n, void *ptr, Uint size)
}
static ERTS_INLINE UWord
-alcu_size(ErtsAlcType_t ai, ErtsAlcUFixInfo_t *fi, int fisz)
+alcu_size(ErtsAlcType_t alloc_no, ErtsAlcUFixInfo_t *fi, int fisz)
{
- UWord res = 0;
+ UWord res;
+ int ai;
- ASSERT(erts_allctrs_info[ai].enabled);
- ASSERT(erts_allctrs_info[ai].alloc_util);
+ if (!erts_allctrs_info[alloc_no].thr_spec) {
+ AllctrSize_t size;
+ Allctr_t *allctr;
- if (!erts_allctrs_info[ai].thr_spec) {
- Allctr_t *allctr = erts_allctrs_info[ai].extra;
- AllctrSize_t asize;
- erts_alcu_current_size(allctr, &asize, fi, fisz);
- res += asize.blocks;
+ allctr = erts_allctrs_info[alloc_no].extra;
+ erts_alcu_current_size(allctr, &size, fi, fisz);
+
+ return size.blocks;
}
- else {
- ErtsAllocatorThrSpec_t *tspec = &erts_allctr_thr_spec[ai];
- int i;
- ASSERT(tspec->enabled);
+ res = 0;
- for (i = tspec->size - 1; i >= 0; i--) {
- Allctr_t *allctr = tspec->allctr[i];
- AllctrSize_t asize;
- if (allctr) {
- erts_alcu_current_size(allctr, &asize, fi, fisz);
- res += asize.blocks;
- }
- }
+ /* Thread-specific allocators can migrate carriers across types, so we have
+ * to visit every allocator type to gather information on blocks that were
+ * allocated by us. */
+ for (ai = ERTS_ALC_A_MIN; ai < ERTS_ALC_A_MAX; ai++) {
+ ErtsAllocatorThrSpec_t *tspec;
+ Allctr_t *allctr;
+ int i;
+
+ if (!erts_allctrs_info[ai].thr_spec) {
+ continue;
+ }
+
+ tspec = &erts_allctr_thr_spec[ai];
+ ASSERT(tspec->enabled);
+
+ for (i = tspec->size - 1; i >= 0; i--) {
+ allctr = tspec->allctr[i];
+
+ if (allctr) {
+ AllctrSize_t size;
+
+ if (ai == alloc_no) {
+ erts_alcu_current_size(allctr, &size, fi, fisz);
+ } else {
+ erts_alcu_foreign_size(allctr, alloc_no, &size);
+ }
+
+ ASSERT(((SWord)size.blocks) >= 0);
+
+ res += size.blocks;
+ }
+ }
}
return res;
@@ -2400,6 +2429,7 @@ erts_memory(fmtfn_t *print_to_p, void *print_to_arg, void *proc, Eterm earg)
}
if (want_tot_or_sys) {
+ ASSERT(size.total >= size.processes);
size.system = size.total - size.processes;
}
@@ -3459,29 +3489,29 @@ UWord erts_alc_test(UWord op, UWord a1, UWord a2, UWord a3)
switch (op) {
case 0xf00:
if (((Allctr_t *) a1)->thread_safe)
- return (UWord) erts_alcu_alloc_ts(ERTS_ALC_T_UNDEF,
+ return (UWord) erts_alcu_alloc_ts(ERTS_ALC_T_TEST,
(void *) a1,
(Uint) a2);
else
- return (UWord) erts_alcu_alloc(ERTS_ALC_T_UNDEF,
+ return (UWord) erts_alcu_alloc(ERTS_ALC_T_TEST,
(void *) a1,
(Uint) a2);
case 0xf01:
if (((Allctr_t *) a1)->thread_safe)
- return (UWord) erts_alcu_realloc_ts(ERTS_ALC_T_UNDEF,
+ return (UWord) erts_alcu_realloc_ts(ERTS_ALC_T_TEST,
(void *) a1,
(void *) a2,
(Uint) a3);
else
- return (UWord) erts_alcu_realloc(ERTS_ALC_T_UNDEF,
+ return (UWord) erts_alcu_realloc(ERTS_ALC_T_TEST,
(void *) a1,
(void *) a2,
(Uint) a3);
case 0xf02:
if (((Allctr_t *) a1)->thread_safe)
- erts_alcu_free_ts(ERTS_ALC_T_UNDEF, (void *) a1, (void *) a2);
+ erts_alcu_free_ts(ERTS_ALC_T_TEST, (void *) a1, (void *) a2);
else
- erts_alcu_free(ERTS_ALC_T_UNDEF, (void *) a1, (void *) a2);
+ erts_alcu_free(ERTS_ALC_T_TEST, (void *) a1, (void *) a2);
return 0;
case 0xf03: {
Allctr_t *allctr;
@@ -3489,8 +3519,10 @@ UWord erts_alc_test(UWord op, UWord a1, UWord a2, UWord a3)
SET_DEFAULT_ALLOC_OPTS(&init);
init.enable = 1;
- init.atype = GOODFIT;
+ init.astrat = ERTS_ALC_S_GOODFIT;
init.init.util.name_prefix = (char *) a1;
+ init.init.util.alloc_no = ERTS_ALC_A_TEST;
+ init.init.util.alloc_strat = init.astrat;
init.init.util.ts = 1;
if ((char **) a3) {
char **argv = (char **) a3;
@@ -3504,31 +3536,31 @@ UWord erts_alc_test(UWord op, UWord a1, UWord a2, UWord a3)
}
}
- switch (init.atype) {
- case GOODFIT:
+ switch (init.astrat) {
+ case ERTS_ALC_S_GOODFIT:
allctr = erts_gfalc_start((GFAllctr_t *)
- erts_alloc(ERTS_ALC_T_UNDEF,
+ erts_alloc(ERTS_ALC_T_TEST,
sizeof(GFAllctr_t)),
&init.init.gf,
&init.init.util);
break;
- case BESTFIT:
+ case ERTS_ALC_S_BESTFIT:
allctr = erts_bfalc_start((BFAllctr_t *)
- erts_alloc(ERTS_ALC_T_UNDEF,
+ erts_alloc(ERTS_ALC_T_TEST,
sizeof(BFAllctr_t)),
&init.init.bf,
&init.init.util);
break;
- case AFIT:
+ case ERTS_ALC_S_AFIT:
allctr = erts_afalc_start((AFAllctr_t *)
- erts_alloc(ERTS_ALC_T_UNDEF,
+ erts_alloc(ERTS_ALC_T_TEST,
sizeof(AFAllctr_t)),
&init.init.af,
&init.init.util);
break;
- case FIRSTFIT:
+ case ERTS_ALC_S_FIRSTFIT:
allctr = erts_aoffalc_start((AOFFAllctr_t *)
- erts_alloc(ERTS_ALC_T_UNDEF,
+ erts_alloc(ERTS_ALC_T_TEST,
sizeof(AOFFAllctr_t)),
&init.init.aoff,
&init.init.util);
@@ -3544,7 +3576,7 @@ UWord erts_alc_test(UWord op, UWord a1, UWord a2, UWord a3)
}
case 0xf04:
erts_alcu_stop((Allctr_t *) a1);
- erts_free(ERTS_ALC_T_UNDEF, (void *) a1);
+ erts_free(ERTS_ALC_T_TEST, (void *) a1);
break;
case 0xf05: return (UWord) 1;
case 0xf06: return (UWord) ((Allctr_t *) a1)->thread_safe;
@@ -3554,7 +3586,7 @@ UWord erts_alc_test(UWord op, UWord a1, UWord a2, UWord a3)
case 0xf07: return (UWord) ((Allctr_t *) a1)->thread_safe;
#endif
case 0xf08: {
- ethr_mutex *mtx = erts_alloc(ERTS_ALC_T_UNDEF, sizeof(ethr_mutex));
+ ethr_mutex *mtx = erts_alloc(ERTS_ALC_T_TEST, sizeof(ethr_mutex));
if (ethr_mutex_init(mtx) != 0)
ERTS_ALC_TEST_ABORT;
return (UWord) mtx;
@@ -3563,7 +3595,7 @@ UWord erts_alc_test(UWord op, UWord a1, UWord a2, UWord a3)
ethr_mutex *mtx = (ethr_mutex *) a1;
if (ethr_mutex_destroy(mtx) != 0)
ERTS_ALC_TEST_ABORT;
- erts_free(ERTS_ALC_T_UNDEF, (void *) mtx);
+ erts_free(ERTS_ALC_T_TEST, (void *) mtx);
break;
}
case 0xf0a:
@@ -3573,7 +3605,7 @@ UWord erts_alc_test(UWord op, UWord a1, UWord a2, UWord a3)
ethr_mutex_unlock((ethr_mutex *) a1);
break;
case 0xf0c: {
- ethr_cond *cnd = erts_alloc(ERTS_ALC_T_UNDEF, sizeof(ethr_cond));
+ ethr_cond *cnd = erts_alloc(ERTS_ALC_T_TEST, sizeof(ethr_cond));
if (ethr_cond_init(cnd) != 0)
ERTS_ALC_TEST_ABORT;
return (UWord) cnd;
@@ -3582,7 +3614,7 @@ UWord erts_alc_test(UWord op, UWord a1, UWord a2, UWord a3)
ethr_cond *cnd = (ethr_cond *) a1;
if (ethr_cond_destroy(cnd) != 0)
ERTS_ALC_TEST_ABORT;
- erts_free(ERTS_ALC_T_UNDEF, (void *) cnd);
+ erts_free(ERTS_ALC_T_TEST, (void *) cnd);
break;
}
case 0xf0e:
@@ -3596,7 +3628,7 @@ UWord erts_alc_test(UWord op, UWord a1, UWord a2, UWord a3)
break;
}
case 0xf10: {
- ethr_tid *tid = erts_alloc(ERTS_ALC_T_UNDEF, sizeof(ethr_tid));
+ ethr_tid *tid = erts_alloc(ERTS_ALC_T_TEST, sizeof(ethr_tid));
if (ethr_thr_create(tid,
(void * (*)(void *)) a1,
(void *) a2,
@@ -3608,7 +3640,7 @@ UWord erts_alc_test(UWord op, UWord a1, UWord a2, UWord a3)
ethr_tid *tid = (ethr_tid *) a1;
if (ethr_thr_join(*tid, NULL) != 0)
ERTS_ALC_TEST_ABORT;
- erts_free(ERTS_ALC_T_UNDEF, (void *) tid);
+ erts_free(ERTS_ALC_T_TEST, (void *) tid);
break;
}
case 0xf12:
@@ -3960,9 +3992,10 @@ check_memory_fence(void *ptr, Uint *size, ErtsAlcType_t n, int func)
static ErtsAllocatorFunctions_t real_allctrs[ERTS_ALC_A_MAX+1];
static void *
-debug_alloc(ErtsAlcType_t n, void *extra, Uint size)
+debug_alloc(ErtsAlcType_t type, void *extra, Uint size)
{
ErtsAllocatorFunctions_t *real_af = (ErtsAllocatorFunctions_t *) extra;
+ ErtsAlcType_t n;
Uint dsize;
void *res;
@@ -3970,9 +4003,11 @@ debug_alloc(ErtsAlcType_t n, void *extra, Uint size)
erts_hdbg_chk_blks();
#endif
+ n = ERTS_ALC_T2N(type);
+
ASSERT(ERTS_ALC_N_MIN <= n && n <= ERTS_ALC_N_MAX);
dsize = size + FENCE_SZ;
- res = (*real_af->alloc)(n, real_af->extra, dsize);
+ res = (*real_af->alloc)(type, real_af->extra, dsize);
res = set_memory_fence(res, size, n);
@@ -3986,14 +4021,17 @@ debug_alloc(ErtsAlcType_t n, void *extra, Uint size)
static void *
-debug_realloc(ErtsAlcType_t n, void *extra, void *ptr, Uint size)
+debug_realloc(ErtsAlcType_t type, void *extra, void *ptr, Uint size)
{
ErtsAllocatorFunctions_t *real_af = (ErtsAllocatorFunctions_t *) extra;
+ ErtsAlcType_t n;
Uint dsize;
Uint old_size;
void *dptr;
void *res;
+ n = ERTS_ALC_T2N(type);
+
ASSERT(ERTS_ALC_N_MIN <= n && n <= ERTS_ALC_N_MAX);
dsize = size + FENCE_SZ;
@@ -4003,12 +4041,12 @@ debug_realloc(ErtsAlcType_t n, void *extra, void *ptr, Uint size)
erts_hdbg_chk_blks();
#endif
- if (old_size > size)
+ if (ptr && old_size > size)
sys_memset((void *) (((char *) ptr) + size),
0xf,
sizeof(Uint) + old_size - size);
- res = (*real_af->realloc)(n, real_af->extra, dptr, dsize);
+ res = (*real_af->realloc)(type, real_af->extra, dptr, dsize);
res = set_memory_fence(res, size, n);
@@ -4021,12 +4059,16 @@ debug_realloc(ErtsAlcType_t n, void *extra, void *ptr, Uint size)
}
static void
-debug_free(ErtsAlcType_t n, void *extra, void *ptr)
+debug_free(ErtsAlcType_t type, void *extra, void *ptr)
{
ErtsAllocatorFunctions_t *real_af = (ErtsAllocatorFunctions_t *) extra;
+ ErtsAlcType_t n;
void *dptr;
Uint size;
- int free_pattern = n;
+ int free_pattern;
+
+ n = ERTS_ALC_T2N(type);
+ free_pattern = n;
ASSERT(ERTS_ALC_N_MIN <= n && n <= ERTS_ALC_N_MAX);
@@ -4044,7 +4086,7 @@ debug_free(ErtsAlcType_t n, void *extra, void *ptr)
#endif
sys_memset((void *) dptr, free_pattern, size + FENCE_SZ);
- (*real_af->free)(n, real_af->extra, dptr);
+ (*real_af->free)(type, real_af->extra, dptr);
#ifdef PRINT_OPS
fprintf(stderr, "free(%s, 0x%lx)\r\n", ERTS_ALC_N2TD(n), (Uint) ptr);
diff --git a/erts/emulator/beam/erl_alloc.h b/erts/emulator/beam/erl_alloc.h
index fcb58ff58a..c13cf3f5b0 100644
--- a/erts/emulator/beam/erl_alloc.h
+++ b/erts/emulator/beam/erl_alloc.h
@@ -26,10 +26,23 @@
#define ERL_THR_PROGRESS_TSD_TYPE_ONLY
#include "erl_thr_progress.h"
#undef ERL_THR_PROGRESS_TSD_TYPE_ONLY
-#include "erl_alloc_util.h"
#include "erl_threads.h"
#include "erl_mmap.h"
+typedef enum {
+ ERTS_ALC_S_INVALID = 0,
+
+ ERTS_ALC_S_GOODFIT,
+ ERTS_ALC_S_BESTFIT,
+ ERTS_ALC_S_AFIT,
+ ERTS_ALC_S_FIRSTFIT,
+
+ ERTS_ALC_S_MIN = ERTS_ALC_S_GOODFIT,
+ ERTS_ALC_S_MAX = ERTS_ALC_S_FIRSTFIT
+} ErtsAlcStrat_t;
+
+#include "erl_alloc_util.h"
+
#ifdef DEBUG
# undef ERTS_ALC_WANT_INLINE
# define ERTS_ALC_WANT_INLINE 0
@@ -52,6 +65,14 @@
#define ERTS_ALC_NO_FIXED_SIZES \
(ERTS_ALC_N_MAX_A_FIXED_SIZE - ERTS_ALC_N_MIN_A_FIXED_SIZE + 1)
+#define ERTS_ALC_IS_FIX_TYPE(T) \
+ (ERTS_ALC_T2N(T) >= ERTS_ALC_N_MIN_A_FIXED_SIZE && \
+ ERTS_ALC_T2N(T) <= ERTS_ALC_N_MAX_A_FIXED_SIZE)
+
+#define ERTS_ALC_FIX_TYPE_IX(T) \
+ (ASSERT(ERTS_ALC_IS_FIX_TYPE(T)), \
+ ERTS_ALC_T2N((T)) - ERTS_ALC_N_MIN_A_FIXED_SIZE)
+
void erts_sys_alloc_init(void);
void *erts_sys_alloc(ErtsAlcType_t, void *, Uint);
void *erts_sys_realloc(ErtsAlcType_t, void *, void *, Uint);
@@ -228,7 +249,7 @@ void *erts_alloc(ErtsAlcType_t type, Uint size)
void *res;
ERTS_MSACC_PUSH_AND_SET_STATE_X(ERTS_MSACC_STATE_ALLOC);
res = (*erts_allctrs[ERTS_ALC_T2A(type)].alloc)(
- ERTS_ALC_T2N(type),
+ type,
erts_allctrs[ERTS_ALC_T2A(type)].extra,
size);
if (!res)
@@ -243,7 +264,7 @@ void *erts_realloc(ErtsAlcType_t type, void *ptr, Uint size)
void *res;
ERTS_MSACC_PUSH_AND_SET_STATE_X(ERTS_MSACC_STATE_ALLOC);
res = (*erts_allctrs[ERTS_ALC_T2A(type)].realloc)(
- ERTS_ALC_T2N(type),
+ type,
erts_allctrs[ERTS_ALC_T2A(type)].extra,
ptr,
size);
@@ -258,7 +279,7 @@ void erts_free(ErtsAlcType_t type, void *ptr)
{
ERTS_MSACC_PUSH_AND_SET_STATE_X(ERTS_MSACC_STATE_ALLOC);
(*erts_allctrs[ERTS_ALC_T2A(type)].free)(
- ERTS_ALC_T2N(type),
+ type,
erts_allctrs[ERTS_ALC_T2A(type)].extra,
ptr);
ERTS_MSACC_POP_STATE_X();
@@ -271,7 +292,7 @@ void *erts_alloc_fnf(ErtsAlcType_t type, Uint size)
void *res;
ERTS_MSACC_PUSH_AND_SET_STATE_X(ERTS_MSACC_STATE_ALLOC);
res = (*erts_allctrs[ERTS_ALC_T2A(type)].alloc)(
- ERTS_ALC_T2N(type),
+ type,
erts_allctrs[ERTS_ALC_T2A(type)].extra,
size);
ERTS_MSACC_POP_STATE_X();
@@ -285,7 +306,7 @@ void *erts_realloc_fnf(ErtsAlcType_t type, void *ptr, Uint size)
void *res;
ERTS_MSACC_PUSH_AND_SET_STATE_X(ERTS_MSACC_STATE_ALLOC);
res = (*erts_allctrs[ERTS_ALC_T2A(type)].realloc)(
- ERTS_ALC_T2N(type),
+ type,
erts_allctrs[ERTS_ALC_T2A(type)].extra,
ptr,
size);
diff --git a/erts/emulator/beam/erl_alloc.types b/erts/emulator/beam/erl_alloc.types
index 4f03a34390..e7329daa2d 100644
--- a/erts/emulator/beam/erl_alloc.types
+++ b/erts/emulator/beam/erl_alloc.types
@@ -30,10 +30,10 @@
# name space).
# * Types, allocators, classes, and descriptions have different name
# spaces.
-# * The type, allocator, and class names INVALID are reserved and can
-# not be used.
+# * The type, allocator, and class names INVALID are reserved and
+# cannot be used.
# * The descriptions invalid_allocator, invalid_class, and invalid_type
-# are reserved and can not be used.
+# are reserved and cannot be used.
# * Declarations can be done conditionally by use of a
# +if <boolean_variable>
#
@@ -275,6 +275,8 @@ type ML_DIST STANDARD SYSTEM monitor_link_dist
type PF3_ARGS SHORT_LIVED PROCESSES process_flag_3_arguments
type SETUP_CONN_ARG SHORT_LIVED PROCESSES setup_connection_argument
type LIST_TRAP SHORT_LIVED PROCESSES list_bif_trap_state
+type CONT_EXIT_TRAP SHORT_LIVED PROCESSES continue_exit_trap_state
+type SEQ_YIELD_STATE SHORT_LIVED SYSTEM dist_seq_yield_state
type ENVIRONMENT SYSTEM SYSTEM environment
@@ -345,16 +347,17 @@ type COUNTERS STANDARD SYSTEM erl_bif_counters
# Types used by system specific code
#
-type TEMP_TERM TEMPORARY SYSTEM temp_term
-type DRV_TAB LONG_LIVED SYSTEM drv_tab
-type DRV_EV_STATE LONG_LIVED SYSTEM driver_event_state
-type DRV_SEL_D_STATE FIXED_SIZE SYSTEM driver_select_data_state
-type NIF_SEL_D_STATE FIXED_SIZE SYSTEM enif_select_data_state
-type POLLSET LONG_LIVED SYSTEM pollset
-type POLLSET_UPDREQ SHORT_LIVED SYSTEM pollset_update_req
-type POLL_FDS LONG_LIVED SYSTEM poll_fds
-type FD_STATUS LONG_LIVED SYSTEM fd_status
-type SELECT_FDS LONG_LIVED SYSTEM select_fds
+type TEMP_TERM TEMPORARY SYSTEM temp_term
+type SHORT_LIVED_TERM SHORT_LIVED SYSTEM short_lived_term
+type DRV_TAB LONG_LIVED SYSTEM drv_tab
+type DRV_EV_STATE LONG_LIVED SYSTEM driver_event_state
+type DRV_SEL_D_STATE FIXED_SIZE SYSTEM driver_select_data_state
+type NIF_SEL_D_STATE FIXED_SIZE SYSTEM enif_select_data_state
+type POLLSET LONG_LIVED SYSTEM pollset
+type POLLSET_UPDREQ SHORT_LIVED SYSTEM pollset_update_req
+type POLL_FDS LONG_LIVED SYSTEM poll_fds
+type FD_STATUS LONG_LIVED SYSTEM fd_status
+type SELECT_FDS LONG_LIVED SYSTEM select_fds
+if unix
diff --git a/erts/emulator/beam/erl_alloc_util.c b/erts/emulator/beam/erl_alloc_util.c
index 0be4562785..8d4464969a 100644
--- a/erts/emulator/beam/erl_alloc_util.c
+++ b/erts/emulator/beam/erl_alloc_util.c
@@ -42,6 +42,7 @@
#include "global.h"
#include "big.h"
+#include "erl_mmap.h"
#include "erl_mtrace.h"
#define GET_ERL_ALLOC_UTIL_IMPL
#include "erl_alloc_util.h"
@@ -90,15 +91,18 @@ static int initialized = 0;
#define SYS_ALLOC_CARRIER_FLOOR(X) ((X) & SYS_ALLOC_CARRIER_MASK)
#define SYS_ALLOC_CARRIER_CEILING(X) \
SYS_ALLOC_CARRIER_FLOOR((X) + INV_SYS_ALLOC_CARRIER_MASK)
+#define SYS_PAGE_SIZE (sys_page_size)
+#define SYS_PAGE_SZ_MASK ((UWord)(SYS_PAGE_SIZE - 1))
#if 0
/* Can be useful for debugging */
#define MBC_REALLOC_ALWAYS_MOVES
#endif
-
/* alloc_util global parameters */
static Uint sys_alloc_carrier_size;
+static Uint sys_page_size;
+
#if HAVE_ERTS_MSEG
static Uint max_mseg_carriers;
#endif
@@ -113,32 +117,38 @@ static int allow_sys_alloc_carriers;
#define DEC_CC(CC) ((CC)--)
-/* Multi block carrier (MBC) memory layout in R16:
+/* Multi block carrier (MBC) memory layout in OTP 22:
Empty MBC:
-[Carrier_t|pad|Block_t L0T|fhdr| free... ]
+[Carrier_t|pad|Block_t L0T0|fhdr| free... ]
MBC after allocating first block:
-[Carrier_t|pad|Block_t 000| udata |pad|Block_t L0T|fhdr| free... ]
+[Carrier_t|pad|Block_t 0000| udata |pad|Block_t L0T0|fhdr| free... ]
MBC after allocating second block:
-[Carrier_t|pad|Block_t 000| udata |pad|Block_t 000| udata |pad|Block_t L0T|fhdr| free... ]
+[Carrier_t|pad|Block_t 0000| udata |pad|Block_t 0000| udata |pad|Block_t L0T0|fhdr| free... ]
MBC after deallocating first block:
-[Carrier_t|pad|Block_t 00T|fhdr| free |FreeBlkFtr_t|Block_t 0P0| udata |pad|Block_t L0T|fhdr| free... ]
+[Carrier_t|pad|Block_t 00T0|fhdr| free |FreeBlkFtr_t|Block_t 0P00| udata |pad|Block_t L0T0|fhdr| free... ]
+MBC after allocating first block, with allocation tagging enabled:
+[Carrier_t|pad|Block_t 000A| udata |atag|pad|Block_t L0T0|fhdr| free... ]
udata = Allocated user data
+ atag = A tag with basic metadata about this allocation
pad = Padding to ensure correct alignment for user data
fhdr = Allocator specific header to keep track of free block
free = Unused free memory
T = This block is free (THIS_FREE_BLK_HDR_FLG)
P = Previous block is free (PREV_FREE_BLK_HDR_FLG)
L = Last block in carrier (LAST_BLK_HDR_FLG)
+ A = Block has an allocation tag footer, only valid for allocated blocks
+ (ATAG_BLK_HDR_FLG)
*/
/* Single block carrier (SBC):
-[Carrier_t|pad|Block_t 111| udata... ]
+[Carrier_t|pad|Block_t 1110| udata... ]
+[Carrier_t|pad|Block_t 111A| udata | atag]
*/
/* Allocation tags ...
@@ -154,20 +164,20 @@ MBC after deallocating first block:
typedef UWord alcu_atag_t;
-#define MAKE_ATAG(IdAtom, Type) \
- (ASSERT((Type) >= ERTS_ALC_N_MIN && (Type) <= ERTS_ALC_N_MAX), \
+#define MAKE_ATAG(IdAtom, TypeNum) \
+ (ASSERT((TypeNum) >= ERTS_ALC_N_MIN && (TypeNum) <= ERTS_ALC_N_MAX), \
ASSERT(atom_val(IdAtom) <= MAX_ATAG_ATOM_ID), \
- (atom_val(IdAtom) << ERTS_ALC_N_BITS) | (Type))
+ (atom_val(IdAtom) << ERTS_ALC_N_BITS) | (TypeNum))
#define ATAG_ID(AT) (make_atom((AT) >> ERTS_ALC_N_BITS))
#define ATAG_TYPE(AT) ((AT) & ERTS_ALC_N_MASK)
#define MAX_ATAG_ATOM_ID (ERTS_UWORD_MAX >> ERTS_ALC_N_BITS)
-#define DBG_IS_VALID_ATAG(Allocator, AT) \
+#define DBG_IS_VALID_ATAG(AT) \
(ATAG_TYPE(AT) >= ERTS_ALC_N_MIN && \
ATAG_TYPE(AT) <= ERTS_ALC_N_MAX && \
- (Allocator)->alloc_no == ERTS_ALC_T2A(ERTS_ALC_N2T(ATAG_TYPE(AT))))
+ ATAG_ID(AT) <= MAX_ATAG_ATOM_ID)
/* Blocks ... */
@@ -182,10 +192,15 @@ typedef UWord alcu_atag_t;
#endif
#define FBLK_FTR_SZ (sizeof(FreeBlkFtr_t))
+#define BLK_HAS_ATAG(B) \
+ (!!((B)->bhdr & ATAG_BLK_HDR_FLG))
+
#define GET_BLK_ATAG(B) \
- (((alcu_atag_t *) (((char *) (B)) + (BLK_SZ(B))))[-1])
+ (ASSERT(BLK_HAS_ATAG(B)), \
+ ((alcu_atag_t *) (((char *) (B)) + (BLK_SZ(B))))[-1])
#define SET_BLK_ATAG(B, T) \
- (((alcu_atag_t *) (((char *) (B)) + (BLK_SZ(B))))[-1] = (T))
+ ((B)->bhdr |= ATAG_BLK_HDR_FLG, \
+ ((alcu_atag_t *) (((char *) (B)) + (BLK_SZ(B))))[-1] = (T))
#define BLK_ATAG_SZ(AP) ((AP)->atags ? sizeof(alcu_atag_t) : 0)
@@ -203,13 +218,13 @@ typedef UWord alcu_atag_t;
(((FreeBlkFtr_t *) (((char *) (B)) + (SZ)))[-1] = (SZ))
#define SET_MBC_ABLK_SZ(B, SZ) \
- (ASSERT(((SZ) & FLG_MASK) == 0), \
+ (ASSERT(((SZ) & BLK_FLG_MASK) == 0), \
(B)->bhdr = (((B)->bhdr) & ~MBC_ABLK_SZ_MASK) | (SZ))
#define SET_MBC_FBLK_SZ(B, SZ) \
- (ASSERT(((SZ) & FLG_MASK) == 0), \
+ (ASSERT(((SZ) & BLK_FLG_MASK) == 0), \
(B)->bhdr = (((B)->bhdr) & ~MBC_FBLK_SZ_MASK) | (SZ))
#define SET_SBC_BLK_SZ(B, SZ) \
- (ASSERT(((SZ) & FLG_MASK) == 0), \
+ (ASSERT(((SZ) & BLK_FLG_MASK) == 0), \
(B)->bhdr = (((B)->bhdr) & ~SBC_BLK_SZ_MASK) | (SZ))
#define SET_PREV_BLK_FREE(AP,B) \
(ASSERT(!IS_MBC_FIRST_BLK(AP,B)), \
@@ -235,12 +250,12 @@ typedef UWord alcu_atag_t;
# define SET_MBC_ABLK_HDR(B, Sz, F, C) \
(ASSERT(((Sz) & ~MBC_ABLK_SZ_MASK) == 0), \
- ASSERT(!((UWord)(F) & (~FLG_MASK|THIS_FREE_BLK_HDR_FLG))), \
+ ASSERT(!((UWord)(F) & (~BLK_FLG_MASK|THIS_FREE_BLK_HDR_FLG))), \
(B)->bhdr = ((Sz) | (F) | (BLK_CARRIER_OFFSET(B,C) << MBC_ABLK_OFFSET_SHIFT)))
# define SET_MBC_FBLK_HDR(B, Sz, F, C) \
(ASSERT(((Sz) & ~MBC_FBLK_SZ_MASK) == 0), \
- ASSERT(((UWord)(F) & (~FLG_MASK|THIS_FREE_BLK_HDR_FLG|PREV_FREE_BLK_HDR_FLG)) == THIS_FREE_BLK_HDR_FLG), \
+ ASSERT(((UWord)(F) & (~BLK_FLG_MASK|THIS_FREE_BLK_HDR_FLG|PREV_FREE_BLK_HDR_FLG)) == THIS_FREE_BLK_HDR_FLG), \
(B)->bhdr = ((Sz) | (F)), \
(B)->u.carrier = (C))
@@ -257,8 +272,8 @@ typedef UWord alcu_atag_t;
# define SET_BLK_FREE(B) \
(ASSERT(!IS_PREV_BLK_FREE(B)), \
(B)->u.carrier = ABLK_TO_MBC(B), \
- (B)->bhdr |= THIS_FREE_BLK_HDR_FLG, \
- (B)->bhdr &= (MBC_ABLK_SZ_MASK|FLG_MASK))
+ (B)->bhdr &= (MBC_ABLK_SZ_MASK|LAST_BLK_HDR_FLG), \
+ (B)->bhdr |= THIS_FREE_BLK_HDR_FLG)
# define SET_BLK_ALLOCED(B) \
(ASSERT(((B)->bhdr & (MBC_ABLK_OFFSET_MASK|THIS_FREE_BLK_HDR_FLG)) == THIS_FREE_BLK_HDR_FLG), \
@@ -270,15 +285,16 @@ typedef UWord alcu_atag_t;
# define MBC_SZ_MAX_LIMIT ((UWord)~0)
# define SET_MBC_ABLK_HDR(B, Sz, F, C) \
- (ASSERT(((Sz) & FLG_MASK) == 0), \
- ASSERT(!((UWord)(F) & (~FLG_MASK|THIS_FREE_BLK_HDR_FLG))), \
- ASSERT((UWord)(F) < SBC_BLK_HDR_FLG), \
+ (ASSERT(((Sz) & BLK_FLG_MASK) == 0), \
+ ASSERT(((F) & ~BLK_FLG_MASK) == 0), \
+ ASSERT(!((UWord)(F) & (~BLK_FLG_MASK|THIS_FREE_BLK_HDR_FLG))), \
(B)->bhdr = ((Sz) | (F)), \
(B)->carrier = (C))
# define SET_MBC_FBLK_HDR(B, Sz, F, C) \
- (ASSERT(((Sz) & FLG_MASK) == 0), \
- ASSERT(((UWord)(F) & (~FLG_MASK|THIS_FREE_BLK_HDR_FLG|PREV_FREE_BLK_HDR_FLG)) == THIS_FREE_BLK_HDR_FLG), \
+ (ASSERT(((Sz) & BLK_FLG_MASK) == 0), \
+ ASSERT(((F) & ~BLK_FLG_MASK) == 0), \
+ ASSERT(((UWord)(F) & (~BLK_FLG_MASK|THIS_FREE_BLK_HDR_FLG|PREV_FREE_BLK_HDR_FLG)) == THIS_FREE_BLK_HDR_FLG), \
(B)->bhdr = ((Sz) | (F)), \
(B)->carrier = (C))
@@ -297,7 +313,7 @@ typedef UWord alcu_atag_t;
#endif /* !MBC_ABLK_OFFSET_BITS */
#define SET_SBC_BLK_HDR(B, Sz) \
- (ASSERT(((Sz) & FLG_MASK) == 0), (B)->bhdr = ((Sz) | (SBC_BLK_HDR_FLG)))
+ (ASSERT(((Sz) & BLK_FLG_MASK) == 0), (B)->bhdr = ((Sz) | (SBC_BLK_HDR_FLG)))
#define BLK_UMEM_SZ(B) \
@@ -320,7 +336,7 @@ typedef UWord alcu_atag_t;
#define GET_PREV_FREE_BLK_HDR_FLG(B) \
((B)->bhdr & PREV_FREE_BLK_HDR_FLG)
#define GET_BLK_HDR_FLGS(B) \
- ((B)->bhdr & FLG_MASK)
+ ((B)->bhdr & BLK_FLG_MASK)
#define NXT_BLK(B) \
(ASSERT(IS_MBC_BLK(B)), \
@@ -419,7 +435,7 @@ do { \
#define SCH_SBC SBC_CARRIER_HDR_FLAG
#define SET_CARRIER_HDR(C, Sz, F, AP) \
- (ASSERT(((Sz) & FLG_MASK) == 0), (C)->chdr = ((Sz) | (F)), \
+ (ASSERT(((Sz) & CRR_FLG_MASK) == 0), (C)->chdr = ((Sz) | (F)), \
erts_atomic_init_nob(&(C)->allctr, (erts_aint_t) (AP)))
#define BLK_TO_SBC(B) \
@@ -444,8 +460,8 @@ do { \
(!IS_SB_CARRIER((C)))
#define SET_CARRIER_SZ(C, SZ) \
- (ASSERT(((SZ) & FLG_MASK) == 0), \
- ((C)->chdr = ((C)->chdr & FLG_MASK) | (SZ)))
+ (ASSERT(((SZ) & CRR_FLG_MASK) == 0), \
+ ((C)->chdr = ((C)->chdr & CRR_FLG_MASK) | (SZ)))
#define CFLG_SBC (1 << 0)
#define CFLG_MBC (1 << 1)
@@ -575,10 +591,12 @@ do { \
STAT_MSEG_MBC_ALLOC((AP), csz__); \
else \
STAT_SYS_ALLOC_MBC_ALLOC((AP), csz__); \
- (AP)->mbcs.blocks.curr.no += (CRR)->cpool.blocks; \
+ set_new_allctr_abandon_limit(AP); \
+ (AP)->mbcs.blocks.curr.no += (CRR)->cpool.blocks[(AP)->alloc_no]; \
if ((AP)->mbcs.blocks.max.no < (AP)->mbcs.blocks.curr.no) \
(AP)->mbcs.blocks.max.no = (AP)->mbcs.blocks.curr.no; \
- (AP)->mbcs.blocks.curr.size += (CRR)->cpool.blocks_size; \
+ (AP)->mbcs.blocks.curr.size += \
+ (CRR)->cpool.blocks_size[(AP)->alloc_no]; \
if ((AP)->mbcs.blocks.max.size < (AP)->mbcs.blocks.curr.size) \
(AP)->mbcs.blocks.max.size = (AP)->mbcs.blocks.curr.size; \
} while (0)
@@ -601,25 +619,33 @@ do { \
DEBUG_CHECK_CARRIER_NO_SZ((AP)); \
} while (0)
-#define STAT_MBC_ABANDON(AP, CRR) \
-do { \
- UWord csz__ = CARRIER_SZ((CRR)); \
- if (IS_MSEG_CARRIER((CRR))) \
- STAT_MSEG_MBC_FREE((AP), csz__); \
- else \
- STAT_SYS_ALLOC_MBC_FREE((AP), csz__); \
- ERTS_ALC_CPOOL_ASSERT((AP)->mbcs.blocks.curr.no \
- >= (CRR)->cpool.blocks); \
- (AP)->mbcs.blocks.curr.no -= (CRR)->cpool.blocks; \
- ERTS_ALC_CPOOL_ASSERT((AP)->mbcs.blocks.curr.size \
- >= (CRR)->cpool.blocks_size); \
- (AP)->mbcs.blocks.curr.size -= (CRR)->cpool.blocks_size; \
+#define STAT_MBC_FREE(AP, CRR) \
+do { \
+ UWord csz__ = CARRIER_SZ((CRR)); \
+ if (IS_MSEG_CARRIER((CRR))) { \
+ STAT_MSEG_MBC_FREE((AP), csz__); \
+ } else { \
+ STAT_SYS_ALLOC_MBC_FREE((AP), csz__); \
+ } \
+ set_new_allctr_abandon_limit(AP); \
} while (0)
-#define STAT_MBC_BLK_ALLOC_CRR(CRR, BSZ) \
+#define STAT_MBC_ABANDON(AP, CRR) \
+do { \
+ STAT_MBC_FREE(AP, CRR); \
+ ERTS_ALC_CPOOL_ASSERT((AP)->mbcs.blocks.curr.no \
+ >= (CRR)->cpool.blocks[(AP)->alloc_no]); \
+ (AP)->mbcs.blocks.curr.no -= (CRR)->cpool.blocks[(AP)->alloc_no]; \
+ ERTS_ALC_CPOOL_ASSERT((AP)->mbcs.blocks.curr.size \
+ >= (CRR)->cpool.blocks_size[(AP)->alloc_no]); \
+ (AP)->mbcs.blocks.curr.size -= (CRR)->cpool.blocks_size[(AP)->alloc_no]; \
+} while (0)
+
+#define STAT_MBC_BLK_ALLOC_CRR(AP, CRR, BSZ) \
do { \
- (CRR)->cpool.blocks++; \
- (CRR)->cpool.blocks_size += (BSZ); \
+ (CRR)->cpool.blocks[(AP)->alloc_no]++; \
+ (CRR)->cpool.blocks_size[(AP)->alloc_no] += (BSZ); \
+ (CRR)->cpool.total_blocks_size += (BSZ); \
} while (0)
#define STAT_MBC_BLK_ALLOC(AP, CRR, BSZ, FLGS) \
@@ -631,50 +657,67 @@ do { \
cstats__->blocks.curr.size += (BSZ); \
if (cstats__->blocks.max.size < cstats__->blocks.curr.size) \
cstats__->blocks.max.size = cstats__->blocks.curr.size; \
- STAT_MBC_BLK_ALLOC_CRR((CRR), (BSZ)); \
+ STAT_MBC_BLK_ALLOC_CRR((AP), (CRR), (BSZ)); \
} while (0)
static ERTS_INLINE int
stat_cpool_mbc_blk_free(Allctr_t *allctr,
+ ErtsAlcType_t type,
Carrier_t *crr,
Carrier_t **busy_pcrr_pp,
UWord blksz)
{
+ Allctr_t *orig_allctr;
+ int alloc_no;
- ERTS_ALC_CPOOL_ASSERT(crr->cpool.blocks > 0);
- crr->cpool.blocks--;
- ERTS_ALC_CPOOL_ASSERT(crr->cpool.blocks_size >= blksz);
- crr->cpool.blocks_size -= blksz;
+ alloc_no = ERTS_ALC_T2A(type);
- if (!busy_pcrr_pp || !*busy_pcrr_pp)
- return 0;
+ ERTS_ALC_CPOOL_ASSERT(crr->cpool.blocks[alloc_no] > 0);
+ crr->cpool.blocks[alloc_no]--;
+ ERTS_ALC_CPOOL_ASSERT(crr->cpool.blocks_size[alloc_no] >= blksz);
+ crr->cpool.blocks_size[alloc_no] -= blksz;
+ ERTS_ALC_CPOOL_ASSERT(crr->cpool.total_blocks_size >= blksz);
+ crr->cpool.total_blocks_size -= blksz;
+
+ if (allctr->alloc_no == alloc_no && (!busy_pcrr_pp || !*busy_pcrr_pp)) {
+ /* This is a local block, so we should not update the pool
+ * statistics. */
+ return 0;
+ }
- ERTS_ALC_CPOOL_ASSERT(crr == *busy_pcrr_pp);
+ /* This is either a foreign block that's been fetched from the pool, or any
+ * block that's in the pool. The carrier's owner keeps the statistics for
+ * both pooled and foreign blocks. */
+
+ orig_allctr = crr->cpool.orig_allctr;
+
+ ERTS_ALC_CPOOL_ASSERT(alloc_no != allctr->alloc_no ||
+ (crr == *busy_pcrr_pp && allctr == orig_allctr));
#ifdef ERTS_ALC_CPOOL_DEBUG
ERTS_ALC_CPOOL_ASSERT(
- erts_atomic_dec_read_nob(&allctr->cpool.stat.no_blocks) >= 0);
+ erts_atomic_dec_read_nob(&orig_allctr->cpool.stat.no_blocks[alloc_no]) >= 0);
ERTS_ALC_CPOOL_ASSERT(
- erts_atomic_add_read_nob(&allctr->cpool.stat.blocks_size,
+ erts_atomic_add_read_nob(&orig_allctr->cpool.stat.blocks_size[alloc_no],
-((erts_aint_t) blksz)) >= 0);
#else
- erts_atomic_dec_nob(&allctr->cpool.stat.no_blocks);
- erts_atomic_add_nob(&allctr->cpool.stat.blocks_size,
+ erts_atomic_dec_nob(&orig_allctr->cpool.stat.no_blocks[alloc_no]);
+ erts_atomic_add_nob(&orig_allctr->cpool.stat.blocks_size[alloc_no],
-((erts_aint_t) blksz));
#endif
return 1;
}
-#define STAT_MBC_BLK_FREE(AP, CRR, BPCRRPP, BSZ, FLGS) \
-do { \
- if (!stat_cpool_mbc_blk_free((AP), (CRR), (BPCRRPP), (BSZ))) { \
- CarriersStats_t *cstats__ = &(AP)->mbcs; \
- ASSERT(cstats__->blocks.curr.no > 0); \
- cstats__->blocks.curr.no--; \
- ASSERT(cstats__->blocks.curr.size >= (BSZ)); \
- cstats__->blocks.curr.size -= (BSZ); \
- } \
+#define STAT_MBC_BLK_FREE(AP, TYPE, CRR, BPCRRPP, BSZ, FLGS) \
+do { \
+ if (!stat_cpool_mbc_blk_free((AP), (TYPE), (CRR), (BPCRRPP), (BSZ))) { \
+ CarriersStats_t *cstats__ = &(AP)->mbcs; \
+ ASSERT(cstats__->blocks.curr.no > 0); \
+ cstats__->blocks.curr.no--; \
+ ASSERT(cstats__->blocks.curr.size >= (BSZ)); \
+ cstats__->blocks.curr.size -= (BSZ); \
+ } \
} while (0)
/* Debug stuff... */
@@ -721,8 +764,8 @@ static void make_name_atoms(Allctr_t *allctr);
static Block_t *create_carrier(Allctr_t *, Uint, UWord);
static void destroy_carrier(Allctr_t *, Block_t *, Carrier_t **);
-static void mbc_free(Allctr_t *allctr, void *p, Carrier_t **busy_pcrr_pp);
-static void dealloc_block(Allctr_t *, void *, ErtsAlcFixList_t *, int);
+static void mbc_free(Allctr_t *allctr, ErtsAlcType_t type, void *p, Carrier_t **busy_pcrr_pp);
+static void dealloc_block(Allctr_t *, ErtsAlcType_t, Uint32, void *, ErtsAlcFixList_t *);
static alcu_atag_t determine_alloc_tag(Allctr_t *allocator, ErtsAlcType_t type)
{
@@ -764,14 +807,14 @@ static alcu_atag_t determine_alloc_tag(Allctr_t *allocator, ErtsAlcType_t type)
}
}
- return MAKE_ATAG(id, type);
+ return MAKE_ATAG(id, ERTS_ALC_T2N(type));
}
static void set_alloc_tag(Allctr_t *allocator, void *p, alcu_atag_t tag)
{
Block_t *block;
- ASSERT(DBG_IS_VALID_ATAG(allocator, tag));
+ ASSERT(DBG_IS_VALID_ATAG(tag));
ASSERT(allocator->atags && p);
(void)allocator;
@@ -1312,28 +1355,9 @@ chk_fix_list(Allctr_t *allctr, ErtsAlcFixList_t *fix, int ix, int before)
#define ERTS_DBG_CHK_FIX_LIST(A, FIX, IX, B)
#endif
+static ERTS_INLINE Allctr_t *get_pref_allctr(void *extra);
static void *mbc_alloc(Allctr_t *allctr, Uint size);
-typedef struct {
- ErtsAllctrDDBlock_t ddblock__; /* must be first */
- ErtsAlcType_t fix_type;
-} ErtsAllctrFixDDBlock_t;
-
-#define ERTS_ALC_FIX_NO_UNUSE (((ErtsAlcType_t) 1) << ERTS_ALC_N_BITS)
-
-static ERTS_INLINE void
-dealloc_fix_block(Allctr_t *allctr,
- ErtsAlcType_t type,
- void *ptr,
- ErtsAlcFixList_t *fix,
- int dec_cc_on_redirect)
-{
- /* May be redirected... */
- ASSERT((type & ERTS_ALC_FIX_NO_UNUSE) == 0);
- ((ErtsAllctrFixDDBlock_t *) ptr)->fix_type = type | ERTS_ALC_FIX_NO_UNUSE;
- dealloc_block(allctr, ptr, fix, dec_cc_on_redirect);
-}
-
static ERTS_INLINE void
sched_fix_shrink(Allctr_t *allctr, int on)
{
@@ -1375,7 +1399,7 @@ fix_cpool_check_shrink(Allctr_t *allctr,
if (fix->u.cpool.min_list_size > fix->list_size)
fix->u.cpool.min_list_size = fix->list_size;
- dealloc_fix_block(allctr, type, p, fix, 0);
+ dealloc_block(allctr, type, DEALLOC_FLG_FIX_SHRINK, p, fix);
}
}
}
@@ -1386,11 +1410,9 @@ fix_cpool_alloc(Allctr_t *allctr, ErtsAlcType_t type, Uint size)
void *res;
ErtsAlcFixList_t *fix;
- ASSERT(ERTS_ALC_N_MIN_A_FIXED_SIZE <= type
- && type <= ERTS_ALC_N_MAX_A_FIXED_SIZE);
-
- fix = &allctr->fix[type - ERTS_ALC_N_MIN_A_FIXED_SIZE];
- ASSERT(size == fix->type_size);
+ fix = &allctr->fix[ERTS_ALC_FIX_TYPE_IX(type)];
+ ASSERT(type == fix->type && size == fix->type_size);
+ ASSERT(size >= sizeof(ErtsAllctrDDBlock_t));
res = fix->list;
if (res) {
@@ -1419,21 +1441,39 @@ fix_cpool_alloc(Allctr_t *allctr, ErtsAlcType_t type, Uint size)
static ERTS_INLINE void
fix_cpool_free(Allctr_t *allctr,
ErtsAlcType_t type,
+ Uint32 flags,
void *p,
- Carrier_t **busy_pcrr_pp,
- int unuse)
+ Carrier_t **busy_pcrr_pp)
{
ErtsAlcFixList_t *fix;
+ Allctr_t *fix_allctr;
- ASSERT(ERTS_ALC_N_MIN_A_FIXED_SIZE <= type
- && type <= ERTS_ALC_N_MAX_A_FIXED_SIZE);
+ /* If this isn't a fix allocator we need to update the fix list of our
+ * neighboring fix_alloc to keep the statistics consistent. */
+ if (!allctr->fix) {
+ ErtsAllocatorThrSpec_t *tspec = &erts_allctr_thr_spec[ERTS_ALC_A_FIXED_SIZE];
+ fix_allctr = get_pref_allctr(tspec);
+ ASSERT(!fix_allctr->thread_safe);
+ ASSERT(allctr != fix_allctr);
+ }
+ else {
+ fix_allctr = allctr;
+ }
- fix = &allctr->fix[type - ERTS_ALC_N_MIN_A_FIXED_SIZE];
+ ASSERT(ERTS_ALC_IS_CPOOL_ENABLED(fix_allctr));
+ ASSERT(ERTS_ALC_IS_CPOOL_ENABLED(allctr));
- if (unuse)
- fix->u.cpool.used--;
+ fix = &fix_allctr->fix[ERTS_ALC_FIX_TYPE_IX(type)];
+ ASSERT(type == fix->type);
+
+ if (!(flags & DEALLOC_FLG_FIX_SHRINK)) {
+ fix->u.cpool.used--;
+ }
- if ((!busy_pcrr_pp || !*busy_pcrr_pp)
+ /* We don't want foreign blocks to be long-lived, so we skip recycling if
+ * allctr != fix_allctr. */
+ if (allctr == fix_allctr
+ && (!busy_pcrr_pp || !*busy_pcrr_pp)
&& !fix->u.cpool.shrink_list
&& fix->list_size < ERTS_ALCU_FIX_MAX_LIST_SZ) {
*((void **) p) = fix->list;
@@ -1446,7 +1486,7 @@ fix_cpool_free(Allctr_t *allctr,
if (IS_SBC_BLK(blk))
destroy_carrier(allctr, blk, NULL);
else
- mbc_free(allctr, p, busy_pcrr_pp);
+ mbc_free(allctr, type, p, busy_pcrr_pp);
fix->u.cpool.allocated--;
fix_cpool_check_shrink(allctr, type, fix, busy_pcrr_pp);
}
@@ -1473,7 +1513,7 @@ fix_cpool_alloc_shrink(Allctr_t *allctr, erts_aint32_t flgs)
fix->u.cpool.shrink_list = fix->u.cpool.min_list_size;
fix->u.cpool.min_list_size = fix->list_size;
}
- type = (ErtsAlcType_t) (ix + ERTS_ALC_N_MIN_A_FIXED_SIZE);
+ type = ERTS_ALC_N2T((ErtsAlcType_t) (ix + ERTS_ALC_N_MIN_A_FIXED_SIZE));
for (o = 0; o < ERTS_ALC_FIX_MAX_SHRINK_OPS || flush; o++) {
void *ptr;
@@ -1487,7 +1527,7 @@ fix_cpool_alloc_shrink(Allctr_t *allctr, erts_aint32_t flgs)
fix->list = *((void **) ptr);
fix->list_size--;
fix->u.cpool.shrink_list--;
- dealloc_fix_block(allctr, type, ptr, fix, 0);
+ dealloc_block(allctr, type, DEALLOC_FLG_FIX_SHRINK, ptr, fix);
}
if (fix->u.cpool.min_list_size > fix->list_size)
fix->u.cpool.min_list_size = fix->list_size;
@@ -1513,11 +1553,9 @@ fix_nocpool_alloc(Allctr_t *allctr, ErtsAlcType_t type, Uint size)
ErtsAlcFixList_t *fix;
void *res;
- ASSERT(ERTS_ALC_N_MIN_A_FIXED_SIZE <= type
- && type <= ERTS_ALC_N_MAX_A_FIXED_SIZE);
-
- fix = &allctr->fix[type - ERTS_ALC_N_MIN_A_FIXED_SIZE];
- ASSERT(size == fix->type_size);
+ fix = &allctr->fix[ERTS_ALC_FIX_TYPE_IX(type)];
+ ASSERT(type == fix->type && size == fix->type_size);
+ ASSERT(size >= sizeof(ErtsAllctrDDBlock_t));
ERTS_DBG_CHK_FIX_LIST(allctr, fix, ix, 1);
fix->u.nocpool.used++;
@@ -1534,7 +1572,7 @@ fix_nocpool_alloc(Allctr_t *allctr, ErtsAlcType_t type, Uint size)
if (IS_SBC_BLK(blk))
destroy_carrier(allctr, blk, NULL);
else
- mbc_free(allctr, p, NULL);
+ mbc_free(allctr, type, p, NULL);
fix->u.nocpool.allocated--;
}
ERTS_DBG_CHK_FIX_LIST(allctr, fix, ix, 0);
@@ -1569,10 +1607,8 @@ fix_nocpool_free(Allctr_t *allctr,
Block_t *blk;
ErtsAlcFixList_t *fix;
- ASSERT(ERTS_ALC_N_MIN_A_FIXED_SIZE <= type
- && type <= ERTS_ALC_N_MAX_A_FIXED_SIZE);
-
- fix = &allctr->fix[type - ERTS_ALC_N_MIN_A_FIXED_SIZE];
+ fix = &allctr->fix[ERTS_ALC_T2N(type) - ERTS_ALC_N_MIN_A_FIXED_SIZE];
+ ASSERT(fix->type == type);
ERTS_DBG_CHK_FIX_LIST(allctr, fix, ix, 1);
fix->u.nocpool.used--;
@@ -1591,7 +1627,7 @@ fix_nocpool_free(Allctr_t *allctr,
if (IS_SBC_BLK(blk))
destroy_carrier(allctr, blk, NULL);
else
- mbc_free(allctr, p, NULL);
+ mbc_free(allctr, type, p, NULL);
p = fix->list;
fix->list = *((void **) p);
fix->list_size--;
@@ -1602,7 +1638,7 @@ fix_nocpool_free(Allctr_t *allctr,
if (IS_SBC_BLK(blk))
destroy_carrier(allctr, blk, NULL);
else
- mbc_free(allctr, p, NULL);
+ mbc_free(allctr, type, p, NULL);
ERTS_DBG_CHK_FIX_LIST(allctr, fix, ix, 0);
}
@@ -1643,7 +1679,7 @@ fix_nocpool_alloc_shrink(Allctr_t *allctr, erts_aint32_t flgs)
ptr = fix->list;
fix->list = *((void **) ptr);
fix->list_size--;
- dealloc_block(allctr, ptr, NULL, 0);
+ dealloc_block(allctr, fix->type, 0, ptr, NULL);
fix->u.nocpool.allocated--;
}
if (fix->list_size != 0) {
@@ -1685,6 +1721,7 @@ dealloc_mbc(Allctr_t *allctr, Carrier_t *crr)
}
+static UWord allctr_abandon_limit(Allctr_t *allctr);
static void set_new_allctr_abandon_limit(Allctr_t*);
static void abandon_carrier(Allctr_t*, Carrier_t*);
static void poolify_my_carrier(Allctr_t*, Carrier_t*);
@@ -1806,7 +1843,7 @@ get_used_allctr(Allctr_t *pref_allctr, int pref_lock, void *p, UWord *sizep,
static void
init_dd_queue(ErtsAllctrDDQueue_t *ddq)
{
- erts_atomic_init_nob(&ddq->tail.data.marker.atmc_next, ERTS_AINT_NULL);
+ erts_atomic_init_nob(&ddq->tail.data.marker.u.atmc_next, ERTS_AINT_NULL);
erts_atomic_init_nob(&ddq->tail.data.last,
(erts_aint_t) &ddq->tail.data.marker);
erts_atomic_init_nob(&ddq->tail.data.um_refc[0], 0);
@@ -1827,17 +1864,17 @@ ddq_managed_thread_enqueue(ErtsAllctrDDQueue_t *ddq, void *ptr, int cinit)
erts_aint_t itmp;
ErtsAllctrDDBlock_t *enq, *this = ptr;
- erts_atomic_init_nob(&this->atmc_next, ERTS_AINT_NULL);
+ erts_atomic_init_nob(&this->u.atmc_next, ERTS_AINT_NULL);
/* Enqueue at end of list... */
enq = (ErtsAllctrDDBlock_t *) erts_atomic_read_nob(&ddq->tail.data.last);
- itmp = erts_atomic_cmpxchg_relb(&enq->atmc_next,
+ itmp = erts_atomic_cmpxchg_relb(&enq->u.atmc_next,
(erts_aint_t) this,
ERTS_AINT_NULL);
if (itmp == ERTS_AINT_NULL) {
/* We are required to move last pointer */
#ifdef DEBUG
- ASSERT(ERTS_AINT_NULL == erts_atomic_read_nob(&this->atmc_next));
+ ASSERT(ERTS_AINT_NULL == erts_atomic_read_nob(&this->u.atmc_next));
ASSERT(((erts_aint_t) enq)
== erts_atomic_xchg_relb(&ddq->tail.data.last,
(erts_aint_t) this));
@@ -1855,8 +1892,8 @@ ddq_managed_thread_enqueue(ErtsAllctrDDQueue_t *ddq, void *ptr, int cinit)
while (1) {
erts_aint_t itmp2;
- erts_atomic_set_nob(&this->atmc_next, itmp);
- itmp2 = erts_atomic_cmpxchg_relb(&enq->atmc_next,
+ erts_atomic_set_nob(&this->u.atmc_next, itmp);
+ itmp2 = erts_atomic_cmpxchg_relb(&enq->u.atmc_next,
(erts_aint_t) this,
itmp);
if (itmp == itmp2)
@@ -1865,7 +1902,7 @@ ddq_managed_thread_enqueue(ErtsAllctrDDQueue_t *ddq, void *ptr, int cinit)
itmp = itmp2;
else {
enq = (ErtsAllctrDDBlock_t *) itmp2;
- itmp = erts_atomic_read_acqb(&enq->atmc_next);
+ itmp = erts_atomic_read_acqb(&enq->u.atmc_next);
ASSERT(itmp != ERTS_AINT_NULL);
}
i++;
@@ -1881,8 +1918,8 @@ check_insert_marker(ErtsAllctrDDQueue_t *ddq, erts_aint_t ilast)
erts_aint_t itmp;
ErtsAllctrDDBlock_t *last = (ErtsAllctrDDBlock_t *) ilast;
- erts_atomic_init_nob(&ddq->tail.data.marker.atmc_next, ERTS_AINT_NULL);
- itmp = erts_atomic_cmpxchg_relb(&last->atmc_next,
+ erts_atomic_init_nob(&ddq->tail.data.marker.u.atmc_next, ERTS_AINT_NULL);
+ itmp = erts_atomic_cmpxchg_relb(&last->u.atmc_next,
(erts_aint_t) &ddq->tail.data.marker,
ERTS_AINT_NULL);
if (itmp == ERTS_AINT_NULL) {
@@ -1933,7 +1970,7 @@ ddq_dequeue(ErtsAllctrDDQueue_t *ddq)
ASSERT(ddq->head.used_marker);
ddq->head.used_marker = 0;
blk = ((ErtsAllctrDDBlock_t *)
- erts_atomic_read_nob(&blk->atmc_next));
+ erts_atomic_read_nob(&blk->u.atmc_next));
if (blk == ddq->head.unref_end) {
ddq->head.first = blk;
return NULL;
@@ -1941,7 +1978,7 @@ ddq_dequeue(ErtsAllctrDDQueue_t *ddq)
}
ddq->head.first = ((ErtsAllctrDDBlock_t *)
- erts_atomic_read_nob(&blk->atmc_next));
+ erts_atomic_read_nob(&blk->u.atmc_next));
ASSERT(ddq->head.first);
@@ -2003,19 +2040,13 @@ check_pending_dealloc_carrier(Allctr_t *allctr,
int *need_more_work);
static void
-handle_delayed_fix_dealloc(Allctr_t *allctr, void *ptr)
+handle_delayed_fix_dealloc(Allctr_t *allctr, ErtsAlcType_t type, Uint32 flags,
+ void *ptr)
{
- ErtsAlcType_t type;
-
- type = ((ErtsAllctrFixDDBlock_t *) ptr)->fix_type;
-
- ASSERT(ERTS_ALC_N_MIN_A_FIXED_SIZE
- <= (type & ~ERTS_ALC_FIX_NO_UNUSE));
- ASSERT((type & ~ERTS_ALC_FIX_NO_UNUSE)
- <= ERTS_ALC_N_MAX_A_FIXED_SIZE);
+ ASSERT(ERTS_ALC_IS_FIX_TYPE(type));
if (!ERTS_ALC_IS_CPOOL_ENABLED(allctr))
- fix_nocpool_free(allctr, (type & ~ERTS_ALC_FIX_NO_UNUSE), ptr);
+ fix_nocpool_free(allctr, type, ptr);
else {
Block_t *blk = UMEM2BLK(ptr);
Carrier_t *busy_pcrr_p;
@@ -2030,20 +2061,24 @@ handle_delayed_fix_dealloc(Allctr_t *allctr, void *ptr)
NULL, &busy_pcrr_p);
if (used_allctr == allctr) {
doit:
- fix_cpool_free(allctr, (type & ~ERTS_ALC_FIX_NO_UNUSE),
- ptr, &busy_pcrr_p,
- !(type & ERTS_ALC_FIX_NO_UNUSE));
+ fix_cpool_free(allctr, type, flags, ptr, &busy_pcrr_p);
clear_busy_pool_carrier(allctr, busy_pcrr_p);
}
else {
/* Carrier migrated; need to redirect block to new owner... */
- int cinit = used_allctr->dd.ix - allctr->dd.ix;
+ ErtsAllctrDDBlock_t *dd_block;
+ int cinit;
+
+ dd_block = (ErtsAllctrDDBlock_t*)ptr;
+ dd_block->flags = flags;
+ dd_block->type = type;
ERTS_ALC_CPOOL_ASSERT(!busy_pcrr_p);
DEC_CC(allctr->calls.this_free);
- ((ErtsAllctrFixDDBlock_t *) ptr)->fix_type = type;
+ cinit = used_allctr->dd.ix - allctr->dd.ix;
+
if (ddq_enqueue(&used_allctr->dd.q, ptr, cinit))
erts_alloc_notify_delayed_dealloc(used_allctr->ix);
}
@@ -2067,7 +2102,6 @@ handle_delayed_dealloc(Allctr_t *allctr,
int need_mr_wrk = 0;
int have_checked_incoming = 0;
int ops = 0;
- ErtsAlcFixList_t *fix;
int res;
ErtsAllctrDDQueue_t *ddq;
@@ -2076,8 +2110,6 @@ handle_delayed_dealloc(Allctr_t *allctr,
ERTS_ALCU_DBG_CHK_THR_ACCESS(allctr);
- fix = allctr->fix;
-
ddq = &allctr->dd.q;
res = 0;
@@ -2166,16 +2198,27 @@ handle_delayed_dealloc(Allctr_t *allctr,
}
}
else {
+ ErtsAllctrDDBlock_t *dd_block;
+ ErtsAlcType_t type;
+ Uint32 flags;
+
+ dd_block = (ErtsAllctrDDBlock_t*)ptr;
+ flags = dd_block->flags;
+ type = dd_block->type;
+
+ flags |= DEALLOC_FLG_REDIRECTED;
+
ASSERT(IS_SBC_BLK(blk) || (ABLK_TO_MBC(blk) !=
ErtsContainerStruct(blk, Carrier_t,
cpool.homecoming_dd.blk)));
INC_CC(allctr->calls.this_free);
- if (fix)
- handle_delayed_fix_dealloc(allctr, ptr);
- else
- dealloc_block(allctr, ptr, NULL, 1);
+ if (ERTS_ALC_IS_FIX_TYPE(type)) {
+ handle_delayed_fix_dealloc(allctr, type, flags, ptr);
+ } else {
+ dealloc_block(allctr, type, flags, ptr, NULL);
+ }
}
}
@@ -2203,8 +2246,10 @@ enqueue_dealloc_other_instance(ErtsAlcType_t type,
void *ptr,
int cinit)
{
- if (allctr->fix)
- ((ErtsAllctrFixDDBlock_t*) ptr)->fix_type = type;
+ ErtsAllctrDDBlock_t *dd_block = ((ErtsAllctrDDBlock_t*)ptr);
+
+ dd_block->type = type;
+ dd_block->flags = 0;
if (ddq_enqueue(&allctr->dd.q, ptr, cinit))
erts_alloc_notify_delayed_dealloc(allctr->ix);
@@ -2234,10 +2279,7 @@ check_abandon_carrier(Allctr_t *allctr, Block_t *fblk, Carrier_t **busy_pcrr_pp)
if (!ERTS_ALC_IS_CPOOL_ENABLED(allctr))
return;
- allctr->cpool.check_limit_count--;
- if (--allctr->cpool.check_limit_count <= 0)
- set_new_allctr_abandon_limit(allctr);
-
+ ASSERT(allctr->cpool.abandon_limit == allctr_abandon_limit(allctr));
ASSERT(erts_thr_progress_is_managed_thread());
if (allctr->cpool.disable_abandon)
@@ -2255,7 +2297,7 @@ check_abandon_carrier(Allctr_t *allctr, Block_t *fblk, Carrier_t **busy_pcrr_pp)
if (allctr->main_carrier == crr)
return;
- if (crr->cpool.blocks_size > crr->cpool.abandon_limit)
+ if (crr->cpool.total_blocks_size > crr->cpool.abandon_limit)
return;
if (crr->cpool.thr_prgr != ERTS_THR_PRGR_INVALID
@@ -2291,24 +2333,26 @@ erts_alcu_check_delayed_dealloc(Allctr_t *allctr,
ERTS_ALCU_DD_OPS_LIM_LOW, NULL, NULL, NULL)
static void
-dealloc_block(Allctr_t *allctr, void *ptr, ErtsAlcFixList_t *fix, int dec_cc_on_redirect)
+dealloc_block(Allctr_t *allctr, ErtsAlcType_t type, Uint32 flags, void *ptr,
+ ErtsAlcFixList_t *fix)
{
Block_t *blk = UMEM2BLK(ptr);
+ ASSERT(!fix || type == fix->type);
+
ERTS_LC_ASSERT(!allctr->thread_safe
|| erts_lc_mtx_is_locked(&allctr->mutex));
if (IS_SBC_BLK(blk)) {
destroy_carrier(allctr, blk, NULL);
if (fix && ERTS_ALC_IS_CPOOL_ENABLED(allctr)) {
- ErtsAlcType_t type = ((ErtsAllctrFixDDBlock_t *) ptr)->fix_type;
- if (!(type & ERTS_ALC_FIX_NO_UNUSE))
+ if (!(flags & DEALLOC_FLG_FIX_SHRINK))
fix->u.cpool.used--;
fix->u.cpool.allocated--;
}
}
else if (!ERTS_ALC_IS_CPOOL_ENABLED(allctr))
- mbc_free(allctr, ptr, NULL);
+ mbc_free(allctr, type, ptr, NULL);
else {
Carrier_t *busy_pcrr_p;
Allctr_t *used_allctr;
@@ -2317,22 +2361,29 @@ dealloc_block(Allctr_t *allctr, void *ptr, ErtsAlcFixList_t *fix, int dec_cc_on_
NULL, &busy_pcrr_p);
if (used_allctr == allctr) {
if (fix) {
- ErtsAlcType_t type = ((ErtsAllctrFixDDBlock_t *) ptr)->fix_type;
- if (!(type & ERTS_ALC_FIX_NO_UNUSE))
+ if (!(flags & DEALLOC_FLG_FIX_SHRINK))
fix->u.cpool.used--;
fix->u.cpool.allocated--;
}
- mbc_free(allctr, ptr, &busy_pcrr_p);
+ mbc_free(allctr, type, ptr, &busy_pcrr_p);
clear_busy_pool_carrier(allctr, busy_pcrr_p);
}
else {
/* Carrier migrated; need to redirect block to new owner... */
- int cinit = used_allctr->dd.ix - allctr->dd.ix;
+ ErtsAllctrDDBlock_t *dd_block;
+ int cinit;
+
+ dd_block = (ErtsAllctrDDBlock_t*)ptr;
+ dd_block->flags = flags;
+ dd_block->type = type;
ERTS_ALC_CPOOL_ASSERT(!busy_pcrr_p);
- if (dec_cc_on_redirect)
+ if (flags & DEALLOC_FLG_REDIRECTED)
DEC_CC(allctr->calls.this_free);
+
+ cinit = used_allctr->dd.ix - allctr->dd.ix;
+
if (ddq_enqueue(&used_allctr->dd.q, ptr, cinit))
erts_alloc_notify_delayed_dealloc(used_allctr->ix);
}
@@ -2498,9 +2549,155 @@ mbc_alloc(Allctr_t *allctr, Uint size)
return BLK2UMEM(blk);
}
+typedef struct {
+ char *ptr;
+ UWord size;
+} ErtsMemDiscardRegion;
+
+/* Construct a discard region for the user memory of a free block, letting the
+ * OS reclaim its physical memory when required.
+ *
+ * Note that we're ignoring both the footer and everything that comes before
+ * the minimum block size as the allocator uses those areas to manage the
+ * block. */
+static void ERTS_INLINE
+mem_discard_start(Allctr_t *allocator, Block_t *block,
+ ErtsMemDiscardRegion *out)
+{
+ UWord size = BLK_SZ(block);
+
+ ASSERT(size >= allocator->min_block_size);
+
+ if (size > (allocator->min_block_size + FBLK_FTR_SZ)) {
+ out->size = size - allocator->min_block_size - FBLK_FTR_SZ;
+ } else {
+ out->size = 0;
+ }
+
+ out->ptr = (char*)block + allocator->min_block_size;
+}
+
+/* Expands a discard region into a neighboring free block, allowing us to
+ * discard the block header and first page.
+ *
+ * This is very important in small-allocation scenarios where no single block
+ * is large enough to be discarded on its own. */
+static void ERTS_INLINE
+mem_discard_coalesce(Allctr_t *allocator, Block_t *neighbor,
+ ErtsMemDiscardRegion *region)
+{
+ char *neighbor_start;
+
+ ASSERT(IS_FREE_BLK(neighbor));
+
+ neighbor_start = (char*)neighbor;
+
+ if (region->ptr >= neighbor_start) {
+ char *region_start_page;
+
+ region_start_page = region->ptr - SYS_PAGE_SIZE;
+ region_start_page = (char*)((UWord)region_start_page & ~SYS_PAGE_SZ_MASK);
+
+ /* Expand if our first page begins within the previous free block's
+ * unused data. */
+ if (region_start_page >= (neighbor_start + allocator->min_block_size)) {
+ region->size += (region->ptr - region_start_page) - FBLK_FTR_SZ;
+ region->ptr = region_start_page;
+ }
+ } else {
+ char *region_end_page;
+ UWord neighbor_size;
+
+ ASSERT(region->ptr <= neighbor_start);
+
+ region_end_page = region->ptr + region->size + SYS_PAGE_SIZE;
+ region_end_page = (char*)((UWord)region_end_page & ~SYS_PAGE_SZ_MASK);
+
+ neighbor_size = BLK_SZ(neighbor) - FBLK_FTR_SZ;
+
+ /* Expand if our last page ends anywhere within the next free block,
+ * sans the footer we'll inherit. */
+ if (region_end_page < neighbor_start + neighbor_size) {
+ region->size += region_end_page - (region->ptr + region->size);
+ }
+ }
+}
+
+static void ERTS_INLINE
+mem_discard_finish(Allctr_t *allocator, Block_t *block,
+ ErtsMemDiscardRegion *region)
+{
+#ifdef DEBUG
+ char *block_start, *block_end;
+ UWord block_size;
+
+ block_size = BLK_SZ(block);
+
+ /* Ensure that the region is completely covered by the legal area of the
+ * free block. This must hold even when the region is too small to be
+ * discarded. */
+ if (region->size > 0) {
+ ASSERT(block_size > allocator->min_block_size + FBLK_FTR_SZ);
+
+ block_start = (char*)block + allocator->min_block_size;
+ block_end = (char*)block + block_size - FBLK_FTR_SZ;
+
+ ASSERT(region->size == 0 ||
+ (region->ptr + region->size <= block_end &&
+ region->ptr >= block_start &&
+ region->size <= block_size));
+ }
+#else
+ (void)allocator;
+ (void)block;
+#endif
+
+ if (region->size > SYS_PAGE_SIZE) {
+ UWord align_offset, size;
+ char *ptr;
+
+ align_offset = SYS_PAGE_SIZE - ((UWord)region->ptr & SYS_PAGE_SZ_MASK);
+
+ size = (region->size - align_offset) & ~SYS_PAGE_SZ_MASK;
+ ptr = region->ptr + align_offset;
+
+ if (size > 0) {
+ ASSERT(!((UWord)ptr & SYS_PAGE_SZ_MASK));
+ ASSERT(!(size & SYS_PAGE_SZ_MASK));
+
+ erts_mem_discard(ptr, size);
+ }
+ }
+}
+
+static void
+carrier_mem_discard_free_blocks(Allctr_t *allocator, Carrier_t *carrier)
+{
+ static const int MAX_BLOCKS_TO_DISCARD = 100;
+ Block_t *block;
+ int i;
+
+ block = allocator->first_fblk_in_mbc(allocator, carrier);
+ i = 0;
+
+ while (block != NULL && i < MAX_BLOCKS_TO_DISCARD) {
+ ErtsMemDiscardRegion region;
+
+ ASSERT(IS_FREE_BLK(block));
+
+ mem_discard_start(allocator, block, &region);
+ mem_discard_finish(allocator, block, &region);
+
+ block = allocator->next_fblk_in_mbc(allocator, carrier, block);
+ i++;
+ }
+}
+
static void
-mbc_free(Allctr_t *allctr, void *p, Carrier_t **busy_pcrr_pp)
+mbc_free(Allctr_t *allctr, ErtsAlcType_t type, void *p, Carrier_t **busy_pcrr_pp)
{
+ ErtsMemDiscardRegion discard_region = {0};
+ int discard;
Uint is_first_blk;
Uint is_last_blk;
Uint blk_sz;
@@ -2516,12 +2713,28 @@ mbc_free(Allctr_t *allctr, void *p, Carrier_t **busy_pcrr_pp)
ASSERT(IS_MBC_BLK(blk));
ASSERT(blk_sz >= allctr->min_block_size);
+#ifndef DEBUG
+ /* We want to mark freed blocks as reclaimable to the OS, but it's a fairly
+ * expensive operation which doesn't do much good if we use it again soon
+ * after, so we limit it to deallocations on pooled carriers. */
+ discard = busy_pcrr_pp && *busy_pcrr_pp;
+#else
+ /* Always discard in debug mode, regardless of whether we're in the pool or
+ * not. */
+ discard = 1;
+#endif
+
+ if (discard) {
+ mem_discard_start(allctr, blk, &discard_region);
+ }
+
HARD_CHECK_BLK_CARRIER(allctr, blk);
crr = ABLK_TO_MBC(blk);
ERTS_ALC_CPOOL_FREE_OP(allctr);
- STAT_MBC_BLK_FREE(allctr, crr, busy_pcrr_pp, blk_sz, alcu_flgs);
+
+ STAT_MBC_BLK_FREE(allctr, type, crr, busy_pcrr_pp, blk_sz, alcu_flgs);
is_first_blk = IS_MBC_FIRST_ABLK(allctr, blk);
is_last_blk = IS_LAST_BLK(blk);
@@ -2532,6 +2745,10 @@ mbc_free(Allctr_t *allctr, void *p, Carrier_t **busy_pcrr_pp)
blk = PREV_BLK(blk);
(*allctr->unlink_free_block)(allctr, blk);
+ if (discard) {
+ mem_discard_coalesce(allctr, blk, &discard_region);
+ }
+
blk_sz += MBC_FBLK_SZ(blk);
is_first_blk = IS_MBC_FIRST_FBLK(allctr, blk);
SET_MBC_FBLK_SZ(blk, blk_sz);
@@ -2547,6 +2764,11 @@ mbc_free(Allctr_t *allctr, void *p, Carrier_t **busy_pcrr_pp)
if (IS_FREE_BLK(nxt_blk)) {
/* Coalesce with next block... */
(*allctr->unlink_free_block)(allctr, nxt_blk);
+
+ if (discard) {
+ mem_discard_coalesce(allctr, nxt_blk, &discard_region);
+ }
+
blk_sz += MBC_FBLK_SZ(nxt_blk);
SET_MBC_FBLK_SZ(blk, blk_sz);
@@ -2582,16 +2804,22 @@ mbc_free(Allctr_t *allctr, void *p, Carrier_t **busy_pcrr_pp)
else {
(*allctr->link_free_block)(allctr, blk);
HARD_CHECK_BLK_CARRIER(allctr, blk);
- if (busy_pcrr_pp && *busy_pcrr_pp)
+
+ if (discard) {
+ mem_discard_finish(allctr, blk, &discard_region);
+ }
+
+ if (busy_pcrr_pp && *busy_pcrr_pp) {
update_pooled_tree(allctr, crr, blk_sz);
- else
+ } else {
check_abandon_carrier(allctr, blk, busy_pcrr_pp);
+ }
}
}
static void *
-mbc_realloc(Allctr_t *allctr, void *p, Uint size, Uint32 alcu_flgs,
- Carrier_t **busy_pcrr_pp)
+mbc_realloc(Allctr_t *allctr, ErtsAlcType_t type, void *p, Uint size,
+ Uint32 alcu_flgs, Carrier_t **busy_pcrr_pp)
{
void *new_p;
Uint old_blk_sz;
@@ -2629,7 +2857,7 @@ mbc_realloc(Allctr_t *allctr, void *p, Uint size, Uint32 alcu_flgs,
new_blk = UMEM2BLK(new_p);
ASSERT(!(IS_MBC_BLK(new_blk) && ABLK_TO_MBC(new_blk) == *busy_pcrr_pp));
sys_memcpy(new_p, p, MIN(size, old_blk_sz - ABLK_HDR_SZ));
- mbc_free(allctr, p, busy_pcrr_pp);
+ mbc_free(allctr, type, p, busy_pcrr_pp);
return new_p;
}
@@ -2706,7 +2934,7 @@ mbc_realloc(Allctr_t *allctr, void *p, Uint size, Uint32 alcu_flgs,
crr = ABLK_TO_MBC(blk);
ERTS_ALC_CPOOL_REALLOC_OP(allctr);
- STAT_MBC_BLK_FREE(allctr, crr, NULL, old_blk_sz, alcu_flgs);
+ STAT_MBC_BLK_FREE(allctr, type, crr, NULL, old_blk_sz, alcu_flgs);
STAT_MBC_BLK_ALLOC(allctr, crr, blk_sz, alcu_flgs);
ASSERT(MBC_BLK_SZ(blk) >= allctr->min_block_size);
@@ -2810,7 +3038,7 @@ mbc_realloc(Allctr_t *allctr, void *p, Uint size, Uint32 alcu_flgs,
}
ERTS_ALC_CPOOL_REALLOC_OP(allctr);
- STAT_MBC_BLK_FREE(allctr, crr, NULL, old_blk_sz, alcu_flgs);
+ STAT_MBC_BLK_FREE(allctr, type, crr, NULL, old_blk_sz, alcu_flgs);
STAT_MBC_BLK_ALLOC(allctr, crr, blk_sz, alcu_flgs);
ASSERT(IS_ALLOCED_BLK(blk));
@@ -2871,7 +3099,7 @@ mbc_realloc(Allctr_t *allctr, void *p, Uint size, Uint32 alcu_flgs,
if (!new_p)
return NULL;
sys_memcpy(new_p, p, MIN(size, old_blk_sz - ABLK_HDR_SZ));
- mbc_free(allctr, p, busy_pcrr_pp);
+ mbc_free(allctr, type, p, busy_pcrr_pp);
return new_p;
@@ -2901,7 +3129,7 @@ mbc_realloc(Allctr_t *allctr, void *p, Uint size, Uint32 alcu_flgs,
1);
new_p = BLK2UMEM(new_blk);
sys_memcpy(new_p, p, MIN(size, old_blk_sz - ABLK_HDR_SZ));
- mbc_free(allctr, p, NULL);
+ mbc_free(allctr, type, p, NULL);
return new_p;
}
else {
@@ -2958,7 +3186,7 @@ mbc_realloc(Allctr_t *allctr, void *p, Uint size, Uint32 alcu_flgs,
0);
ERTS_ALC_CPOOL_FREE_OP(allctr);
- STAT_MBC_BLK_FREE(allctr, crr, NULL, old_blk_sz, alcu_flgs);
+ STAT_MBC_BLK_FREE(allctr, type, crr, NULL, old_blk_sz, alcu_flgs);
return new_p;
}
@@ -2969,7 +3197,6 @@ mbc_realloc(Allctr_t *allctr, void *p, Uint size, Uint32 alcu_flgs,
#define ERTS_ALC_MAX_DEALLOC_CARRIER 10
#define ERTS_ALC_CPOOL_MAX_FETCH_INSPECT 100
-#define ERTS_ALC_CPOOL_CHECK_LIMIT_COUNT 100
#define ERTS_ALC_CPOOL_MAX_FAILED_STAT_READS 3
#define ERTS_ALC_CPOOL_PTR_MOD_MRK (((erts_aint_t) 1) << 0)
@@ -2996,14 +3223,11 @@ typedef union {
# error "Carrier pool implementation assumes ERTS_ALC_A_MIN > ERTS_ALC_A_INVALID"
#endif
-/*
- * The pool is only allowed to be manipulated by managed
- * threads except in the alloc_SUITE:cpool case. In this
- * test case carrier_pool[ERTS_ALC_A_INVALID] will be
- * used.
- */
+/* The pools are only allowed to be manipulated by managed threads except in
+ * the alloc_SUITE:cpool test, where only test_carrier_pool is used. */
-static ErtsAlcCrrPool_t carrier_pool[ERTS_ALC_A_MAX+1] erts_align_attribute(ERTS_CACHE_LINE_SIZE);
+static ErtsAlcCrrPool_t firstfit_carrier_pool;
+static ErtsAlcCrrPool_t test_carrier_pool;
#define ERTS_ALC_CPOOL_MAX_BACKOFF (1 << 8)
@@ -3024,12 +3248,12 @@ backoff(int n)
static int
cpool_dbg_is_in_pool(Allctr_t *allctr, Carrier_t *crr)
{
- ErtsAlcCPoolData_t *sentinel = &carrier_pool[allctr->alloc_no].sentinel;
+ ErtsAlcCPoolData_t *sentinel = allctr->cpool.sentinel;
ErtsAlcCPoolData_t *cpdp = sentinel;
Carrier_t *tmp_crr;
while (1) {
- cpdp = (ErtsAlcCPoolData_t *) (erts_atomic_read_ddrb(&cpdp->next) & ~FLG_MASK);
+ cpdp = (ErtsAlcCPoolData_t *) (erts_atomic_read_ddrb(&cpdp->next) & ~CRR_FLG_MASK);
if (cpdp == sentinel)
return 0;
tmp_crr = (Carrier_t *) (((char *) cpdp) - offsetof(Carrier_t, cpool));
@@ -3041,7 +3265,7 @@ cpool_dbg_is_in_pool(Allctr_t *allctr, Carrier_t *crr)
static int
cpool_is_empty(Allctr_t *allctr)
{
- ErtsAlcCPoolData_t *sentinel = &carrier_pool[allctr->alloc_no].sentinel;
+ ErtsAlcCPoolData_t *sentinel = allctr->cpool.sentinel;
return ((erts_atomic_read_rb(&sentinel->next) == (erts_aint_t) sentinel)
&& (erts_atomic_read_rb(&sentinel->prev) == (erts_aint_t) sentinel));
}
@@ -3131,16 +3355,31 @@ cpool_insert(Allctr_t *allctr, Carrier_t *crr)
{
ErtsAlcCPoolData_t *cpd1p, *cpd2p;
erts_aint_t val;
- ErtsAlcCPoolData_t *sentinel = &carrier_pool[allctr->alloc_no].sentinel;
+ ErtsAlcCPoolData_t *sentinel = allctr->cpool.sentinel;
Allctr_t *orig_allctr = crr->cpool.orig_allctr;
- ERTS_ALC_CPOOL_ASSERT(allctr->alloc_no == ERTS_ALC_A_INVALID /* testcase */
+ ERTS_ALC_CPOOL_ASSERT(allctr->alloc_no == ERTS_ALC_A_TEST /* testcase */
|| erts_thr_progress_is_managed_thread());
- erts_atomic_add_nob(&orig_allctr->cpool.stat.blocks_size,
- (erts_aint_t) crr->cpool.blocks_size);
- erts_atomic_add_nob(&orig_allctr->cpool.stat.no_blocks,
- (erts_aint_t) crr->cpool.blocks);
+ {
+ int alloc_no = allctr->alloc_no;
+
+ ERTS_ALC_CPOOL_ASSERT(
+ erts_atomic_read_nob(&orig_allctr->cpool.stat.blocks_size[alloc_no]) >= 0 &&
+ crr->cpool.blocks_size[alloc_no] >= 0);
+
+ ERTS_ALC_CPOOL_ASSERT(
+ erts_atomic_read_nob(&orig_allctr->cpool.stat.no_blocks[alloc_no]) >= 0 &&
+ crr->cpool.blocks[alloc_no] >= 0);
+
+ /* We only modify the counter for our current type since the others are
+ * conceptually still in the pool. */
+ erts_atomic_add_nob(&orig_allctr->cpool.stat.blocks_size[alloc_no],
+ ((erts_aint_t) crr->cpool.blocks_size[alloc_no]));
+ erts_atomic_add_nob(&orig_allctr->cpool.stat.no_blocks[alloc_no],
+ ((erts_aint_t) crr->cpool.blocks[alloc_no]));
+ }
+
erts_atomic_add_nob(&orig_allctr->cpool.stat.carriers_size,
(erts_aint_t) CARRIER_SZ(crr));
erts_atomic_inc_nob(&orig_allctr->cpool.stat.no_carriers);
@@ -3213,10 +3452,10 @@ cpool_delete(Allctr_t *allctr, Allctr_t *prev_allctr, Carrier_t *crr)
ErtsAlcCPoolData_t *cpd1p, *cpd2p;
erts_aint_t val;
#ifdef ERTS_ALC_CPOOL_DEBUG
- ErtsAlcCPoolData_t *sentinel = &carrier_pool[allctr->alloc_no].sentinel;
+ ErtsAlcCPoolData_t *sentinel = allctr->cpool.sentinel;
#endif
- ERTS_ALC_CPOOL_ASSERT(allctr->alloc_no == ERTS_ALC_A_INVALID /* testcase */
+ ERTS_ALC_CPOOL_ASSERT(allctr->alloc_no == ERTS_ALC_A_TEST /* testcase */
|| erts_thr_progress_is_managed_thread());
ERTS_ALC_CPOOL_ASSERT(sentinel != &crr->cpool);
@@ -3292,28 +3531,43 @@ cpool_delete(Allctr_t *allctr, Allctr_t *prev_allctr, Carrier_t *crr)
crr->cpool.thr_prgr = erts_thr_progress_later(NULL);
- erts_atomic_add_nob(&prev_allctr->cpool.stat.blocks_size,
- -((erts_aint_t) crr->cpool.blocks_size));
- erts_atomic_add_nob(&prev_allctr->cpool.stat.no_blocks,
- -((erts_aint_t) crr->cpool.blocks));
- erts_atomic_add_nob(&prev_allctr->cpool.stat.carriers_size,
+ {
+ Allctr_t *orig_allctr = crr->cpool.orig_allctr;
+ int alloc_no = allctr->alloc_no;
+
+ ERTS_ALC_CPOOL_ASSERT(orig_allctr == prev_allctr);
+
+ ERTS_ALC_CPOOL_ASSERT(crr->cpool.blocks_size[alloc_no] <=
+ erts_atomic_read_nob(&orig_allctr->cpool.stat.blocks_size[alloc_no]));
+
+ ERTS_ALC_CPOOL_ASSERT(crr->cpool.blocks[alloc_no] <=
+ erts_atomic_read_nob(&orig_allctr->cpool.stat.no_blocks[alloc_no]));
+
+ /* We only modify the counters for our current type since the others
+ * were, conceptually, never taken out of the pool. */
+ erts_atomic_add_nob(&orig_allctr->cpool.stat.blocks_size[alloc_no],
+ -((erts_aint_t) crr->cpool.blocks_size[alloc_no]));
+ erts_atomic_add_nob(&orig_allctr->cpool.stat.no_blocks[alloc_no],
+ -((erts_aint_t) crr->cpool.blocks[alloc_no]));
+
+ erts_atomic_add_nob(&orig_allctr->cpool.stat.carriers_size,
-((erts_aint_t) CARRIER_SZ(crr)));
- erts_atomic_dec_wb(&prev_allctr->cpool.stat.no_carriers);
+ erts_atomic_dec_wb(&orig_allctr->cpool.stat.no_carriers);
+ }
}
static Carrier_t *
cpool_fetch(Allctr_t *allctr, UWord size)
{
- enum { IGNORANT, HAS_SEEN_SENTINEL, THE_LAST_ONE } loop_state;
- int i;
+ int i, seen_sentinel;
Carrier_t *crr;
Carrier_t *reinsert_crr = NULL;
ErtsAlcCPoolData_t *cpdp;
ErtsAlcCPoolData_t *cpool_entrance = NULL;
ErtsAlcCPoolData_t *sentinel;
- ERTS_ALC_CPOOL_ASSERT(allctr->alloc_no == ERTS_ALC_A_INVALID /* testcase */
+ ERTS_ALC_CPOOL_ASSERT(allctr->alloc_no == ERTS_ALC_A_TEST /* testcase */
|| erts_thr_progress_is_managed_thread());
i = ERTS_ALC_CPOOL_MAX_FETCH_INSPECT;
@@ -3415,48 +3669,39 @@ cpool_fetch(Allctr_t *allctr, UWord size)
/*
* Finally search the shared pool and try employ foreign carriers
*/
- sentinel = &carrier_pool[allctr->alloc_no].sentinel;
+ sentinel = allctr->cpool.sentinel;
if (cpool_entrance) {
/*
* We saw a pooled carried above, use it as entrance into the pool
*/
- cpdp = cpool_entrance;
}
else {
/*
- * No pooled carried seen above. Start search at cpool sentinel,
+ * No pooled carrier seen above. Start search at cpool sentinel,
* but begin by passing one element before trying to fetch.
* This in order to avoid contention with threads inserting elements.
*/
- cpool_entrance = sentinel;
- cpdp = cpool_aint2cpd(cpool_read(&cpool_entrance->prev));
- if (cpdp == sentinel)
+ cpool_entrance = cpool_aint2cpd(cpool_read(&sentinel->prev));
+ if (cpool_entrance == sentinel)
goto check_dc_list;
}
- loop_state = IGNORANT;
+ cpdp = cpool_entrance;
+ seen_sentinel = 0;
do {
erts_aint_t exp;
cpdp = cpool_aint2cpd(cpool_read(&cpdp->prev));
- if (cpdp == cpool_entrance) {
- if (cpool_entrance == sentinel) {
- cpdp = cpool_aint2cpd(cpool_read(&cpdp->prev));
- if (cpdp == sentinel)
- break;
- }
- loop_state = THE_LAST_ONE;
- }
- else if (cpdp == sentinel) {
- if (loop_state == HAS_SEEN_SENTINEL) {
+ if (cpdp == sentinel) {
+ if (seen_sentinel) {
/* We been here before. cpool_entrance must have been removed */
INC_CC(allctr->cpool.stat.entrance_removed);
break;
}
- cpdp = cpool_aint2cpd(cpool_read(&cpdp->prev));
- if (cpdp == sentinel)
- break;
- loop_state = HAS_SEEN_SENTINEL;
+ seen_sentinel = 1;
+ continue;
}
+ ASSERT(cpdp != cpool_entrance || seen_sentinel);
+
crr = ErtsContainerStruct(cpdp, Carrier_t, cpool);
exp = erts_atomic_read_rb(&crr->allctr);
@@ -3489,7 +3734,7 @@ cpool_fetch(Allctr_t *allctr, UWord size)
INC_CC(allctr->cpool.stat.fail_shared);
return NULL;
}
- }while (loop_state != THE_LAST_ONE);
+ }while (cpdp != cpool_entrance);
check_dc_list:
/* Last; check our own pending dealloc carrier list... */
@@ -3668,8 +3913,9 @@ cpool_init_carrier_data(Allctr_t *allctr, Carrier_t *crr)
crr->cpool.orig_allctr = allctr;
crr->cpool.thr_prgr = ERTS_THR_PRGR_INVALID;
erts_atomic_init_nob(&crr->cpool.max_size, 0);
- crr->cpool.blocks = 0;
- crr->cpool.blocks_size = 0;
+ sys_memset(&crr->cpool.blocks_size, 0, sizeof(crr->cpool.blocks_size));
+ sys_memset(&crr->cpool.blocks, 0, sizeof(crr->cpool.blocks));
+ crr->cpool.total_blocks_size = 0;
if (!ERTS_ALC_IS_CPOOL_ENABLED(allctr))
crr->cpool.abandon_limit = 0;
else {
@@ -3684,14 +3930,14 @@ cpool_init_carrier_data(Allctr_t *allctr, Carrier_t *crr)
crr->cpool.state = ERTS_MBC_IS_HOME;
}
-static void
-set_new_allctr_abandon_limit(Allctr_t *allctr)
+
+
+static UWord
+allctr_abandon_limit(Allctr_t *allctr)
{
UWord limit;
UWord csz;
- allctr->cpool.check_limit_count = ERTS_ALC_CPOOL_CHECK_LIMIT_COUNT;
-
csz = allctr->mbcs.curr.norm.mseg.size;
csz += allctr->mbcs.curr.norm.sys_alloc.size;
@@ -3701,7 +3947,13 @@ set_new_allctr_abandon_limit(Allctr_t *allctr)
else
limit = (csz/100)*allctr->cpool.util_limit;
- allctr->cpool.abandon_limit = limit;
+ return limit;
+}
+
+static void ERTS_INLINE
+set_new_allctr_abandon_limit(Allctr_t *allctr)
+{
+ allctr->cpool.abandon_limit = allctr_abandon_limit(allctr);
}
static void
@@ -3713,7 +3965,9 @@ abandon_carrier(Allctr_t *allctr, Carrier_t *crr)
unlink_carrier(&allctr->mbc_list, crr);
allctr->remove_mbc(allctr, crr);
- set_new_allctr_abandon_limit(allctr);
+
+ /* Mark our free blocks as unused and reclaimable to the OS. */
+ carrier_mem_discard_free_blocks(allctr, crr);
cpool_insert(allctr, crr);
@@ -3766,7 +4020,8 @@ poolify_my_carrier(Allctr_t *allctr, Carrier_t *crr)
}
static void
-cpool_read_stat(Allctr_t *allctr, UWord *nocp, UWord *cszp, UWord *nobp, UWord *bszp)
+cpool_read_stat(Allctr_t *allctr, int alloc_no,
+ UWord *nocp, UWord *cszp, UWord *nobp, UWord *bszp)
{
int i;
UWord noc = 0, csz = 0, nob = 0, bsz = 0;
@@ -3786,10 +4041,10 @@ cpool_read_stat(Allctr_t *allctr, UWord *nocp, UWord *cszp, UWord *nobp, UWord *
? erts_atomic_read_nob(&allctr->cpool.stat.carriers_size)
: 0);
tnob = (UWord) (nobp
- ? erts_atomic_read_nob(&allctr->cpool.stat.no_blocks)
+ ? erts_atomic_read_nob(&allctr->cpool.stat.no_blocks[alloc_no])
: 0);
tbsz = (UWord) (bszp
- ? erts_atomic_read_nob(&allctr->cpool.stat.blocks_size)
+ ? erts_atomic_read_nob(&allctr->cpool.stat.blocks_size[alloc_no])
: 0);
if (tnoc == noc && tcsz == csz && tnob == nob && tbsz == bsz)
break;
@@ -4044,6 +4299,7 @@ create_carrier(Allctr_t *allctr, Uint umem_sz, UWord flags)
#if HAVE_ERTS_MSEG
mbc_final_touch:
#endif
+ set_new_allctr_abandon_limit(allctr);
blk = MBC_TO_FIRST_BLK(allctr, crr);
@@ -4262,7 +4518,6 @@ destroy_carrier(Allctr_t *allctr, Block_t *blk, Carrier_t **busy_pcrr_pp)
else {
ASSERT(IS_MBC_FIRST_FBLK(allctr, blk));
crr = FIRST_BLK_TO_MBC(allctr, blk);
- crr_sz = CARRIER_SZ(crr);
#ifdef DEBUG
if (!allctr->stopped) {
@@ -4294,15 +4549,7 @@ destroy_carrier(Allctr_t *allctr, Block_t *blk, Carrier_t **busy_pcrr_pp)
else
{
unlink_carrier(&allctr->mbc_list, crr);
-#if HAVE_ERTS_MSEG
- if (IS_MSEG_CARRIER(crr)) {
- ASSERT(crr_sz % ERTS_SACRR_UNIT_SZ == 0);
- STAT_MSEG_MBC_FREE(allctr, crr_sz);
- }
- else
-#endif
- STAT_SYS_ALLOC_MBC_FREE(allctr, crr_sz);
-
+ STAT_MBC_FREE(allctr, crr);
if (allctr->remove_mbc)
allctr->remove_mbc(allctr, crr);
}
@@ -4316,7 +4563,7 @@ destroy_carrier(Allctr_t *allctr, Block_t *blk, Carrier_t **busy_pcrr_pp)
LTTNG5(carrier_destroy,
ERTS_ALC_A2AD(allctr->alloc_no),
allctr->ix,
- crr_sz,
+ CARRIER_SZ(crr),
mbc_stats,
sbc_stats);
}
@@ -4394,6 +4641,8 @@ static struct {
Eterm blocks_size;
Eterm blocks;
+ Eterm foreign_blocks;
+
Eterm calls;
Eterm sys_alloc;
Eterm sys_free;
@@ -4494,6 +4743,7 @@ init_atoms(Allctr_t *allctr)
AM_INIT(carriers);
AM_INIT(blocks_size);
AM_INIT(blocks);
+ AM_INIT(foreign_blocks);
AM_INIT(calls);
AM_INIT(sys_alloc);
@@ -4629,7 +4879,6 @@ sz_info_fix(Allctr_t *allctr,
ErtsAlcFixList_t *fix = &allctr->fix[ix];
UWord alloced = fix->type_size * fix->u.cpool.allocated;
UWord used = fix->type_size * fix->u.cpool.used;
- ErtsAlcType_t n = ERTS_ALC_N_MIN_A_FIXED_SIZE + ix;
if (print_to_p) {
fmtfn_t to = *print_to_p;
@@ -4637,14 +4886,14 @@ sz_info_fix(Allctr_t *allctr,
erts_print(to,
arg,
"fix type internal: %s %bpu %bpu\n",
- (char *) ERTS_ALC_N2TD(n),
+ (char *) ERTS_ALC_T2TD(fix->type),
alloced,
used);
}
if (hpp || szp) {
add_3tup(hpp, szp, &res,
- alloc_type_atoms[n],
+ alloc_type_atoms[ERTS_ALC_T2N(fix->type)],
bld_unstable_uint(hpp, szp, alloced),
bld_unstable_uint(hpp, szp, used));
}
@@ -4657,7 +4906,6 @@ sz_info_fix(Allctr_t *allctr,
ErtsAlcFixList_t *fix = &allctr->fix[ix];
UWord alloced = fix->type_size * fix->u.nocpool.allocated;
UWord used = fix->type_size*fix->u.nocpool.used;
- ErtsAlcType_t n = ERTS_ALC_N_MIN_A_FIXED_SIZE + ix;
if (print_to_p) {
fmtfn_t to = *print_to_p;
@@ -4665,14 +4913,14 @@ sz_info_fix(Allctr_t *allctr,
erts_print(to,
arg,
"fix type: %s %bpu %bpu\n",
- (char *) ERTS_ALC_N2TD(n),
+ (char *) ERTS_ALC_T2TD(fix->type),
alloced,
used);
}
if (hpp || szp) {
add_3tup(hpp, szp, &res,
- alloc_type_atoms[n],
+ alloc_type_atoms[ERTS_ALC_T2N(fix->type)],
bld_unstable_uint(hpp, szp, alloced),
bld_unstable_uint(hpp, szp, used));
}
@@ -4745,9 +4993,9 @@ info_cpool(Allctr_t *allctr,
noc = csz = nob = bsz = ~0;
if (print_to_p || hpp) {
if (sz_only)
- cpool_read_stat(allctr, NULL, &csz, NULL, &bsz);
+ cpool_read_stat(allctr, allctr->alloc_no, NULL, &csz, NULL, &bsz);
else
- cpool_read_stat(allctr, &noc, &csz, &nob, &bsz);
+ cpool_read_stat(allctr, allctr->alloc_no, &noc, &csz, &nob, &bsz);
}
if (print_to_p) {
@@ -4762,6 +5010,10 @@ info_cpool(Allctr_t *allctr,
}
if (hpp || szp) {
+ Eterm foreign_blocks;
+ int i;
+
+ foreign_blocks = NIL;
res = NIL;
if (!sz_only) {
@@ -4808,22 +5060,61 @@ info_cpool(Allctr_t *allctr,
add_3tup(hpp, szp, &res, am.entrance_removed,
bld_unstable_uint(hpp, szp, ERTS_ALC_CC_GIGA_VAL(allctr->cpool.stat.entrance_removed)),
bld_unstable_uint(hpp, szp, ERTS_ALC_CC_VAL(allctr->cpool.stat.entrance_removed)));
+ }
add_2tup(hpp, szp, &res,
am.carriers_size,
bld_unstable_uint(hpp, szp, csz));
- }
- if (!sz_only)
- add_2tup(hpp, szp, &res,
- am.carriers,
- bld_unstable_uint(hpp, szp, noc));
+
+ if (!sz_only) {
+ add_2tup(hpp, szp, &res,
+ am.carriers,
+ bld_unstable_uint(hpp, szp, noc));
+ }
+
add_2tup(hpp, szp, &res,
am.blocks_size,
bld_unstable_uint(hpp, szp, bsz));
- if (!sz_only)
+
+ if (!sz_only) {
add_2tup(hpp, szp, &res,
am.blocks,
bld_unstable_uint(hpp, szp, nob));
+ }
+
+ for (i = ERTS_ALC_A_MIN; i <= ERTS_ALC_A_MAX; i++) {
+ const char *name_str;
+ Eterm name, info;
+
+ if (i == allctr->alloc_no) {
+ continue;
+ }
+
+ cpool_read_stat(allctr, i, NULL, NULL, &nob, &bsz);
+
+ if (bsz == 0 && (nob == 0 || sz_only)) {
+ continue;
+ }
+
+ name_str = ERTS_ALC_A2AD(i);
+ info = NIL;
+
+ add_2tup(hpp, szp, &info,
+ am.blocks_size,
+ bld_unstable_uint(hpp, szp, bsz));
+
+ if (!sz_only) {
+ add_2tup(hpp, szp, &info,
+ am.blocks,
+ bld_unstable_uint(hpp, szp, nob));
+ }
+
+ name = am_atom_put(name_str, sys_strlen(name_str));
+
+ add_2tup(hpp, szp, &foreign_blocks, name, info);
+ }
+
+ add_2tup(hpp, szp, &res, am.foreign_blocks, foreign_blocks);
}
return res;
@@ -5459,6 +5750,19 @@ erts_alcu_info(Allctr_t *allctr,
return res;
}
+void
+erts_alcu_foreign_size(Allctr_t *allctr, ErtsAlcType_t alloc_no, AllctrSize_t *size)
+{
+ if (ERTS_ALC_IS_CPOOL_ENABLED(allctr)) {
+ UWord csz, bsz;
+ cpool_read_stat(allctr, alloc_no, NULL, &csz, NULL, &bsz);
+ size->carriers = csz;
+ size->blocks = bsz;
+ } else {
+ size->carriers = 0;
+ size->blocks = 0;
+ }
+}
void
erts_alcu_current_size(Allctr_t *allctr, AllctrSize_t *size, ErtsAlcUFixInfo_t *fi, int fisz)
@@ -5477,7 +5781,7 @@ erts_alcu_current_size(Allctr_t *allctr, AllctrSize_t *size, ErtsAlcUFixInfo_t *
if (ERTS_ALC_IS_CPOOL_ENABLED(allctr)) {
UWord csz, bsz;
- cpool_read_stat(allctr, NULL, &csz, NULL, &bsz);
+ cpool_read_stat(allctr, allctr->alloc_no, NULL, &csz, NULL, &bsz);
size->blocks += bsz;
size->carriers += csz;
}
@@ -5522,6 +5826,11 @@ do_erts_alcu_alloc(ErtsAlcType_t type, Allctr_t *allctr, Uint size)
ERTS_ALCU_DBG_CHK_THR_ACCESS(allctr);
+ /* Reject sizes that can't fit into the header word. */
+ if (size > ~BLK_FLG_MASK) {
+ return NULL;
+ }
+
#if ALLOC_ZERO_EQ_NULL
if (!size)
return NULL;
@@ -5688,12 +5997,11 @@ do_erts_alcu_free(ErtsAlcType_t type, Allctr_t *allctr, void *p,
ERTS_ALCU_DBG_CHK_THR_ACCESS(allctr);
if (p) {
-
INC_CC(allctr->calls.this_free);
- if (allctr->fix) {
+ if (ERTS_ALC_IS_FIX_TYPE(type)) {
if (ERTS_ALC_IS_CPOOL_ENABLED(allctr))
- fix_cpool_free(allctr, type, p, busy_pcrr_pp, 1);
+ fix_cpool_free(allctr, type, 0, p, busy_pcrr_pp);
else
fix_nocpool_free(allctr, type, p);
}
@@ -5702,7 +6010,7 @@ do_erts_alcu_free(ErtsAlcType_t type, Allctr_t *allctr, void *p,
if (IS_SBC_BLK(blk))
destroy_carrier(allctr, blk, NULL);
else
- mbc_free(allctr, p, busy_pcrr_pp);
+ mbc_free(allctr, type, p, busy_pcrr_pp);
}
}
}
@@ -5804,6 +6112,11 @@ do_erts_alcu_realloc(ErtsAlcType_t type,
return res;
}
+ /* Reject sizes that can't fit into the header word. */
+ if (size > ~BLK_FLG_MASK) {
+ return NULL;
+ }
+
#if ALLOC_ZERO_EQ_NULL
if (!size) {
ASSERT(p);
@@ -5820,7 +6133,7 @@ do_erts_alcu_realloc(ErtsAlcType_t type,
if (size < allctr->sbc_threshold) {
if (IS_MBC_BLK(blk))
- res = mbc_realloc(allctr, p, size, alcu_flgs, busy_pcrr_pp);
+ res = mbc_realloc(allctr, type, p, size, alcu_flgs, busy_pcrr_pp);
else {
Uint used_sz = SBC_HEADER_SIZE + ABLK_HDR_SZ + size;
Uint crr_sz;
@@ -5879,7 +6192,7 @@ do_erts_alcu_realloc(ErtsAlcType_t type,
sys_memcpy((void *) res,
(void *) p,
MIN(MBC_ABLK_SZ(blk) - ABLK_HDR_SZ, size));
- mbc_free(allctr, p, busy_pcrr_pp);
+ mbc_free(allctr, type, p, busy_pcrr_pp);
}
else
res = NULL;
@@ -6247,6 +6560,7 @@ int
erts_alcu_start(Allctr_t *allctr, AllctrInit_t *init)
{
/* erts_alcu_start assumes that allctr has been zeroed */
+ int i;
if (((UWord)allctr & ERTS_CRR_ALCTR_FLG_MASK) != 0) {
erts_exit(ERTS_ABORT_EXIT, "%s:%d:erts_alcu_start: Alignment error\n",
@@ -6270,6 +6584,11 @@ erts_alcu_start(Allctr_t *allctr, AllctrInit_t *init)
allctr->ix = init->ix;
allctr->alloc_no = init->alloc_no;
+ allctr->alloc_strat = init->alloc_strat;
+
+ ASSERT(allctr->alloc_no >= ERTS_ALC_A_MIN &&
+ allctr->alloc_no <= ERTS_ALC_A_MAX);
+
if (allctr->alloc_no < ERTS_ALC_A_MIN
|| ERTS_ALC_A_MAX < allctr->alloc_no)
allctr->alloc_no = ERTS_ALC_A_INVALID;
@@ -6322,8 +6641,7 @@ erts_alcu_start(Allctr_t *allctr, AllctrInit_t *init)
+ sizeof(FreeBlkFtr_t));
if (init->tpref) {
Uint sz = ABLK_HDR_SZ;
- sz += (init->fix ?
- sizeof(ErtsAllctrFixDDBlock_t) : sizeof(ErtsAllctrDDBlock_t));
+ sz += sizeof(ErtsAllctrDDBlock_t);
sz = UNIT_CEILING(sz);
if (sz > allctr->min_block_size)
allctr->min_block_size = sz;
@@ -6334,15 +6652,29 @@ erts_alcu_start(Allctr_t *allctr, AllctrInit_t *init)
allctr->cpool.dc_list.last = NULL;
allctr->cpool.abandon_limit = 0;
allctr->cpool.disable_abandon = 0;
- erts_atomic_init_nob(&allctr->cpool.stat.blocks_size, 0);
- erts_atomic_init_nob(&allctr->cpool.stat.no_blocks, 0);
+ for (i = ERTS_ALC_A_MIN; i <= ERTS_ALC_A_MAX; i++) {
+ erts_atomic_init_nob(&allctr->cpool.stat.blocks_size[i], 0);
+ erts_atomic_init_nob(&allctr->cpool.stat.no_blocks[i], 0);
+ }
erts_atomic_init_nob(&allctr->cpool.stat.carriers_size, 0);
erts_atomic_init_nob(&allctr->cpool.stat.no_carriers, 0);
- allctr->cpool.check_limit_count = ERTS_ALC_CPOOL_CHECK_LIMIT_COUNT;
if (!init->ts && init->acul && init->acnl) {
+ ASSERT(allctr->add_mbc);
+ ASSERT(allctr->remove_mbc);
+ ASSERT(allctr->largest_fblk_in_mbc);
+ ASSERT(allctr->first_fblk_in_mbc);
+ ASSERT(allctr->next_fblk_in_mbc);
+
allctr->cpool.util_limit = init->acul;
allctr->cpool.in_pool_limit = init->acnl;
allctr->cpool.fblk_min_limit = init->acfml;
+
+ if (allctr->alloc_strat == ERTS_ALC_S_FIRSTFIT) {
+ allctr->cpool.sentinel = &firstfit_carrier_pool.sentinel;
+ }
+ else if (allctr->alloc_no != ERTS_ALC_A_TEST) {
+ ERTS_INTERNAL_ERROR("Impossible carrier migration config.");
+ }
}
else {
allctr->cpool.util_limit = 0;
@@ -6350,6 +6682,12 @@ erts_alcu_start(Allctr_t *allctr, AllctrInit_t *init)
allctr->cpool.fblk_min_limit = 0;
}
+ /* The invasive tests don't really care whether the pool is enabled or not,
+ * so we need to set this unconditionally for this allocator type. */
+ if (allctr->alloc_no == ERTS_ALC_A_TEST) {
+ allctr->cpool.sentinel = &test_carrier_pool.sentinel;
+ }
+
allctr->sbc_threshold = adjust_sbct(allctr, init->sbct);
#if HAVE_ERTS_MSEG
@@ -6461,9 +6799,9 @@ erts_alcu_start(Allctr_t *allctr, AllctrInit_t *init)
allctr->fix_shrink_scheduled = 0;
for (i = 0; i < ERTS_ALC_NO_FIXED_SIZES; i++) {
allctr->fix[i].type_size = init->fix_type_size[i];
+ allctr->fix[i].type = ERTS_ALC_N2T(i + ERTS_ALC_N_MIN_A_FIXED_SIZE);
allctr->fix[i].list_size = 0;
allctr->fix[i].list = NULL;
- ASSERT(allctr->fix[i].type_size >= sizeof(ErtsAllctrFixDDBlock_t));
if (ERTS_ALC_IS_CPOOL_ENABLED(allctr)) {
allctr->fix[i].u.cpool.min_list_size = 0;
allctr->fix[i].u.cpool.shrink_list = 0;
@@ -6512,12 +6850,16 @@ erts_alcu_stop(Allctr_t *allctr)
void
erts_alcu_init(AlcUInit_t *init)
{
- int i;
- for (i = 0; i <= ERTS_ALC_A_MAX; i++) {
- ErtsAlcCPoolData_t *sentinel = &carrier_pool[i].sentinel;
- erts_atomic_init_nob(&sentinel->next, (erts_aint_t) sentinel);
- erts_atomic_init_nob(&sentinel->prev, (erts_aint_t) sentinel);
- }
+ ErtsAlcCPoolData_t *sentinel;
+
+ sentinel = &firstfit_carrier_pool.sentinel;
+ erts_atomic_init_nob(&sentinel->next, (erts_aint_t) sentinel);
+ erts_atomic_init_nob(&sentinel->prev, (erts_aint_t) sentinel);
+
+ sentinel = &test_carrier_pool.sentinel;
+ erts_atomic_init_nob(&sentinel->next, (erts_aint_t) sentinel);
+ erts_atomic_init_nob(&sentinel->prev, (erts_aint_t) sentinel);
+
ERTS_CT_ASSERT(SBC_BLK_SZ_MASK == MBC_FBLK_SZ_MASK); /* see BLK_SZ */
#if HAVE_ERTS_MSEG
ASSERT(erts_mseg_unit_size() == ERTS_SACRR_UNIT_SZ);
@@ -6528,6 +6870,8 @@ erts_alcu_init(AlcUInit_t *init)
#endif
allow_sys_alloc_carriers = init->sac;
+ sys_page_size = erts_sys_get_page_size();
+
#ifdef DEBUG
carrier_alignment = sizeof(Unit_t);
#endif
@@ -6699,7 +7043,7 @@ static int blockscan_cpool_yielding(blockscan_t *state)
{
ErtsAlcCPoolData_t *sentinel, *cursor;
- sentinel = &carrier_pool[(state->allocator)->alloc_no].sentinel;
+ sentinel = (state->allocator)->cpool.sentinel;
cursor = blockscan_restore_cpool_cursor(state);
if (ERTS_PROC_IS_EXITING(state->process)) {
@@ -6831,11 +7175,8 @@ static int blockscan_sweep_mbcs(blockscan_t *state)
static int blockscan_sweep_cpool(blockscan_t *state)
{
if (state->current_op != blockscan_sweep_cpool) {
- ErtsAlcCPoolData_t *sentinel;
-
SET_CARRIER_HDR(&state->dummy_carrier, 0, SCH_MBC, state->allocator);
- sentinel = &carrier_pool[(state->allocator)->alloc_no].sentinel;
- state->cpool_cursor = sentinel;
+ state->cpool_cursor = (state->allocator)->cpool.sentinel;
}
state->current_op = blockscan_sweep_cpool;
@@ -7119,11 +7460,14 @@ static int gather_ahist_scan(Allctr_t *allocator,
alcu_atag_t tag;
block = SBC2BLK(allocator, carrier);
- tag = GET_BLK_ATAG(block);
- ASSERT(DBG_IS_VALID_ATAG(allocator, tag));
+ if (BLK_HAS_ATAG(block)) {
+ tag = GET_BLK_ATAG(block);
+
+ ASSERT(DBG_IS_VALID_ATAG(tag));
- gather_ahist_update(state, tag, SBC_BLK_SZ(block));
+ gather_ahist_update(state, tag, SBC_BLK_SZ(block));
+ }
} else {
UWord scanned_bytes = MBC_HEADER_SIZE(allocator);
@@ -7134,10 +7478,10 @@ static int gather_ahist_scan(Allctr_t *allocator,
while (1) {
UWord block_size = MBC_BLK_SZ(block);
- if (IS_ALLOCED_BLK(block)) {
+ if (IS_ALLOCED_BLK(block) && BLK_HAS_ATAG(block)) {
alcu_atag_t tag = GET_BLK_ATAG(block);
- ASSERT(DBG_IS_VALID_ATAG(allocator, tag));
+ ASSERT(DBG_IS_VALID_ATAG(tag));
gather_ahist_update(state, tag, block_size);
}
@@ -7159,7 +7503,7 @@ static int gather_ahist_scan(Allctr_t *allocator,
return blocks_scanned;
}
-static void gather_ahist_append_result(hist_tree_t *node, void *arg)
+static int gather_ahist_append_result(hist_tree_t *node, void *arg, Sint reds)
{
gather_ahist_t *state = (gather_ahist_t*)arg;
@@ -7193,6 +7537,7 @@ static void gather_ahist_append_result(hist_tree_t *node, void *arg)
/* Plain free is intentional. */
free(node);
+ return 1;
}
static void gather_ahist_send(gather_ahist_t *state)
@@ -7251,11 +7596,11 @@ static int gather_ahist_finish(void *arg)
state->building_result = 1;
}
- if (hist_tree_rbt_foreach_destroy_yielding(&state->hist_tree,
- &gather_ahist_append_result,
- state,
- &state->hist_tree_yield,
- BLOCKSCAN_REDUCTIONS)) {
+ if (!hist_tree_rbt_foreach_destroy_yielding(&state->hist_tree,
+ &gather_ahist_append_result,
+ state,
+ &state->hist_tree_yield,
+ BLOCKSCAN_REDUCTIONS)) {
return 1;
}
@@ -7264,10 +7609,11 @@ static int gather_ahist_finish(void *arg)
return 0;
}
-static void gather_ahist_destroy_result(hist_tree_t *node, void *arg)
+static int gather_ahist_destroy_result(hist_tree_t *node, void *arg, Sint reds)
{
(void)arg;
free(node);
+ return 1;
}
static void gather_ahist_abort(void *arg)
@@ -7297,8 +7643,6 @@ int erts_alcu_gather_alloc_histograms(Process *p, int allocator_num,
sched_id,
&allocator)) {
return 0;
- } else if (!allocator->atags) {
- return 0;
}
ensure_atoms_initialized(allocator);
diff --git a/erts/emulator/beam/erl_alloc_util.h b/erts/emulator/beam/erl_alloc_util.h
index f26ace1534..ea1afe8f58 100644
--- a/erts/emulator/beam/erl_alloc_util.h
+++ b/erts/emulator/beam/erl_alloc_util.h
@@ -24,6 +24,7 @@
#define ERTS_ALCU_VSN_STR "3.0"
#include "erl_alloc_types.h"
+#include "erl_alloc.h"
#define ERL_THREADS_EMU_INTERNAL__
#include "erl_threads.h"
@@ -44,6 +45,7 @@ typedef struct {
typedef struct {
char *name_prefix;
ErtsAlcType_t alloc_no;
+ ErtsAlcStrat_t alloc_strat;
int force;
int ix;
int ts;
@@ -101,6 +103,7 @@ typedef struct {
#define ERTS_DEFAULT_ALLCTR_INIT { \
NULL, \
ERTS_ALC_A_INVALID, /* (number) alloc_no: allocator number */\
+ ERTS_ALC_S_INVALID, /* (number) alloc_strat: allocator strategy */\
0, /* (bool) force: force enabled */\
0, /* (number) ix: instance index */\
1, /* (bool) ts: thread safe */\
@@ -138,6 +141,7 @@ typedef struct {
#define ERTS_DEFAULT_ALLCTR_INIT { \
NULL, \
ERTS_ALC_A_INVALID, /* (number) alloc_no: allocator number */\
+ ERTS_ALC_S_INVALID, /* (number) alloc_strat: allocator strategy */\
0, /* (bool) force: force enabled */\
0, /* (number) ix: instance index */\
1, /* (bool) ts: thread safe */\
@@ -188,6 +192,7 @@ Eterm erts_alcu_info(Allctr_t *, int, int, fmtfn_t *, void *, Uint **, Uint *);
void erts_alcu_init(AlcUInit_t *);
void erts_alcu_current_size(Allctr_t *, AllctrSize_t *,
ErtsAlcUFixInfo_t *, int);
+void erts_alcu_foreign_size(Allctr_t *, ErtsAlcType_t, AllctrSize_t *);
void erts_alcu_check_delayed_dealloc(Allctr_t *, int, int *, ErtsThrPrgrVal *, int *);
erts_aint32_t erts_alcu_fix_alloc_shrink(Allctr_t *, erts_aint32_t);
@@ -286,10 +291,18 @@ void erts_alcu_sched_spec_data_init(struct ErtsSchedulerData_ *esdp);
#define UNIT_FLOOR(X) ((X) & UNIT_MASK)
#define UNIT_CEILING(X) UNIT_FLOOR((X) + INV_UNIT_MASK)
-#define FLG_MASK INV_UNIT_MASK
-#define SBC_BLK_SZ_MASK UNIT_MASK
-#define MBC_FBLK_SZ_MASK UNIT_MASK
-#define CARRIER_SZ_MASK UNIT_MASK
+/* We store flags in the bits that no one will ever use. Generally these are
+ * the bits below the alignment size, but for blocks we also steal the highest
+ * bit since the header's a size and no one can expect to be able to allocate
+ * objects that large. */
+#define HIGHEST_WORD_BIT (((UWord) 1) << (sizeof(UWord) * CHAR_BIT - 1))
+
+#define BLK_FLG_MASK (INV_UNIT_MASK | HIGHEST_WORD_BIT)
+#define SBC_BLK_SZ_MASK (~BLK_FLG_MASK)
+#define MBC_FBLK_SZ_MASK (~BLK_FLG_MASK)
+
+#define CRR_FLG_MASK INV_UNIT_MASK
+#define CRR_SZ_MASK UNIT_MASK
#if ERTS_HAVE_MSEG_SUPER_ALIGNED \
|| (!HAVE_ERTS_MSEG && ERTS_HAVE_ERTS_SYS_ALIGNED_ALLOC)
@@ -299,9 +312,9 @@ void erts_alcu_sched_spec_data_init(struct ErtsSchedulerData_ *esdp);
# define ERTS_SUPER_ALIGN_BITS 18
# endif
# ifdef ARCH_64
-# define MBC_ABLK_OFFSET_BITS 24
+# define MBC_ABLK_OFFSET_BITS 23
# else
-# define MBC_ABLK_OFFSET_BITS 9
+# define MBC_ABLK_OFFSET_BITS 8
/* Affects hard limits for sbct and lmbcs documented in erts_alloc.xml */
# endif
# define ERTS_SACRR_UNIT_SHIFT ERTS_SUPER_ALIGN_BITS
@@ -322,18 +335,17 @@ void erts_alcu_sched_spec_data_init(struct ErtsSchedulerData_ *esdp);
#if MBC_ABLK_OFFSET_BITS
# define MBC_ABLK_OFFSET_SHIFT (sizeof(UWord)*8 - MBC_ABLK_OFFSET_BITS)
-# define MBC_ABLK_OFFSET_MASK (~((UWord)0) << MBC_ABLK_OFFSET_SHIFT)
-# define MBC_ABLK_SZ_MASK (~MBC_ABLK_OFFSET_MASK & ~FLG_MASK)
+# define MBC_ABLK_OFFSET_MASK ((~((UWord)0) << MBC_ABLK_OFFSET_SHIFT) & ~BLK_FLG_MASK)
+# define MBC_ABLK_SZ_MASK (~MBC_ABLK_OFFSET_MASK & ~BLK_FLG_MASK)
#else
-# define MBC_ABLK_SZ_MASK (~FLG_MASK)
+# define MBC_ABLK_SZ_MASK (~BLK_FLG_MASK)
#endif
#define MBC_ABLK_SZ(B) (ASSERT(!is_sbc_blk(B)), (B)->bhdr & MBC_ABLK_SZ_MASK)
#define MBC_FBLK_SZ(B) (ASSERT(!is_sbc_blk(B)), (B)->bhdr & MBC_FBLK_SZ_MASK)
#define SBC_BLK_SZ(B) (ASSERT(is_sbc_blk(B)), (B)->bhdr & SBC_BLK_SZ_MASK)
-#define CARRIER_SZ(C) \
- ((C)->chdr & CARRIER_SZ_MASK)
+#define CARRIER_SZ(C) ((C)->chdr & CRR_SZ_MASK)
typedef union {char c[ERTS_ALLOC_ALIGN_BYTES]; long l; double d;} Unit_t;
@@ -351,12 +363,20 @@ typedef struct {
#endif
} Block_t;
-typedef union ErtsAllctrDDBlock_t_ ErtsAllctrDDBlock_t;
+typedef struct ErtsAllctrDDBlock__ {
+ union {
+ struct ErtsAllctrDDBlock__ *ptr_next;
+ erts_atomic_t atmc_next;
+ } u;
+ ErtsAlcType_t type;
+ Uint32 flags;
+} ErtsAllctrDDBlock_t;
-union ErtsAllctrDDBlock_t_ {
- erts_atomic_t atmc_next;
- ErtsAllctrDDBlock_t *ptr_next;
-};
+/* Deallocation was caused by shrinking a fix-list, so usage statistics has
+ * already been updated. */
+#define DEALLOC_FLG_FIX_SHRINK (1 << 0)
+/* Deallocation was redirected to another instance. */
+#define DEALLOC_FLG_REDIRECTED (1 << 1)
typedef struct {
Block_t blk;
@@ -365,11 +385,10 @@ typedef struct {
#endif
} ErtsFakeDDBlock_t;
-
-
#define THIS_FREE_BLK_HDR_FLG (((UWord) 1) << 0)
#define PREV_FREE_BLK_HDR_FLG (((UWord) 1) << 1)
#define LAST_BLK_HDR_FLG (((UWord) 1) << 2)
+#define ATAG_BLK_HDR_FLG HIGHEST_WORD_BIT
#define SBC_BLK_HDR_FLG /* Special flag combo for (allocated) SBC blocks */\
(THIS_FREE_BLK_HDR_FLG | PREV_FREE_BLK_HDR_FLG | LAST_BLK_HDR_FLG)
@@ -381,9 +400,9 @@ typedef struct {
#define HOMECOMING_MBC_BLK_HDR (THIS_FREE_BLK_HDR_FLG | LAST_BLK_HDR_FLG)
#define IS_FREE_LAST_MBC_BLK(B) \
- (((B)->bhdr & FLG_MASK) == (THIS_FREE_BLK_HDR_FLG | LAST_BLK_HDR_FLG))
+ (((B)->bhdr & BLK_FLG_MASK) == (THIS_FREE_BLK_HDR_FLG | LAST_BLK_HDR_FLG))
-#define IS_SBC_BLK(B) (((B)->bhdr & FLG_MASK) == SBC_BLK_HDR_FLG)
+#define IS_SBC_BLK(B) (((B)->bhdr & SBC_BLK_HDR_FLG) == SBC_BLK_HDR_FLG)
#define IS_MBC_BLK(B) (!IS_SBC_BLK((B)))
#define IS_FREE_BLK(B) (ASSERT(IS_MBC_BLK(B)), \
(B)->bhdr & THIS_FREE_BLK_HDR_FLG)
@@ -394,7 +413,8 @@ typedef struct {
# define ABLK_TO_MBC(B) \
(ASSERT(IS_MBC_BLK(B) && !IS_FREE_BLK(B)), \
(Carrier_t*)((ERTS_SACRR_UNIT_FLOOR((UWord)(B)) - \
- (((B)->bhdr >> MBC_ABLK_OFFSET_SHIFT) << ERTS_SACRR_UNIT_SHIFT))))
+ ((((B)->bhdr & ~BLK_FLG_MASK) >> MBC_ABLK_OFFSET_SHIFT) \
+ << ERTS_SACRR_UNIT_SHIFT))))
# define BLK_TO_MBC(B) (IS_FREE_BLK(B) ? FBLK_TO_MBC(B) : ABLK_TO_MBC(B))
#else
# define FBLK_TO_MBC(B) ((B)->carrier)
@@ -433,8 +453,9 @@ typedef struct {
ErtsThrPrgrVal thr_prgr;
erts_atomic_t max_size;
UWord abandon_limit;
- UWord blocks;
- UWord blocks_size;
+ UWord blocks[ERTS_ALC_A_MAX + 1];
+ UWord blocks_size[ERTS_ALC_A_MAX + 1];
+ UWord total_blocks_size;
enum {
ERTS_MBC_IS_HOME,
ERTS_MBC_WAS_POOLED,
@@ -452,7 +473,7 @@ struct Carrier_t_ {
};
#define ERTS_ALC_CARRIER_TO_ALLCTR(C) \
- ((Allctr_t *) (erts_atomic_read_nob(&(C)->allctr) & ~FLG_MASK))
+ ((Allctr_t *) (erts_atomic_read_nob(&(C)->allctr) & ~CRR_FLG_MASK))
typedef struct {
Carrier_t *first;
@@ -530,7 +551,6 @@ typedef struct {
} head;
} ErtsAllctrDDQueue_t;
-
typedef struct {
size_t type_size;
SWord list_size;
@@ -549,6 +569,7 @@ typedef struct {
UWord used;
} cpool;
} u;
+ ErtsAlcType_t type;
} ErtsAlcFixList_t;
struct Allctr_t_ {
@@ -569,6 +590,9 @@ struct Allctr_t_ {
/* Allocator number */
ErtsAlcType_t alloc_no;
+ /* Allocator strategy */
+ ErtsAlcStrat_t alloc_strat;
+
/* Instance index */
int ix;
@@ -617,6 +641,9 @@ struct Allctr_t_ {
AOFF_RBTree_t* pooled_tree;
CarrierList_t dc_list;
+ /* the sentinel of the cpool we're attached to */
+ ErtsAlcCPoolData_t *sentinel;
+
UWord abandon_limit;
int disable_abandon;
int check_limit_count;
@@ -624,8 +651,8 @@ struct Allctr_t_ {
UWord in_pool_limit; /* acnl */
UWord fblk_min_limit; /* acmfl */
struct {
- erts_atomic_t blocks_size;
- erts_atomic_t no_blocks;
+ erts_atomic_t blocks_size[ERTS_ALC_A_MAX + 1];
+ erts_atomic_t no_blocks[ERTS_ALC_A_MAX + 1];
erts_atomic_t carriers_size;
erts_atomic_t no_carriers;
CallCounter_t fail_pooled;
@@ -657,10 +684,12 @@ struct Allctr_t_ {
void (*creating_mbc) (Allctr_t *, Carrier_t *);
void (*destroying_mbc) (Allctr_t *, Carrier_t *);
- /* The three callbacks below are needed to support carrier migration */
+ /* The five callbacks below are needed to support carrier migration. */
void (*add_mbc) (Allctr_t *, Carrier_t *);
void (*remove_mbc) (Allctr_t *, Carrier_t *);
UWord (*largest_fblk_in_mbc) (Allctr_t *, Carrier_t *);
+ Block_t * (*first_fblk_in_mbc) (Allctr_t *, Carrier_t *);
+ Block_t * (*next_fblk_in_mbc) (Allctr_t *, Carrier_t *, Block_t *);
#if HAVE_ERTS_MSEG
void* (*mseg_alloc)(Allctr_t*, Uint *size_p, Uint flags);
diff --git a/erts/emulator/beam/erl_ao_firstfit_alloc.c b/erts/emulator/beam/erl_ao_firstfit_alloc.c
index 3f0ab33597..c19d6d1b1e 100644
--- a/erts/emulator/beam/erl_ao_firstfit_alloc.c
+++ b/erts/emulator/beam/erl_ao_firstfit_alloc.c
@@ -100,16 +100,18 @@
#define AOFF_BLK_SZ(B) MBC_FBLK_SZ(&(B)->hdr)
-#define LIST_NEXT(N) (((AOFF_RBTree_t*)(N))->u.next)
-#define LIST_PREV(N) (((AOFF_RBTree_t*)(N))->parent)
+#define AOFF_LIST_NEXT(N) (((AOFF_RBTree_t*)(N))->u.next)
+#define AOFF_LIST_PREV(N) (((AOFF_RBTree_t*)(N))->parent)
typedef struct AOFF_Carrier_t_ AOFF_Carrier_t;
struct AOFF_Carrier_t_ {
Carrier_t crr;
- AOFF_RBTree_t rbt_node; /* My node in the carrier tree */
- AOFF_RBTree_t* root; /* Root of my block tree */
+ AOFF_RBTree_t rbt_node; /* My node in the carrier tree */
+ AOFF_RBTree_t* root; /* Root of my block tree */
+ enum AOFFSortOrder blk_order;
};
+
#define RBT_NODE_TO_MBC(PTR) ErtsContainerStruct((PTR), AOFF_Carrier_t, rbt_node)
/*
@@ -152,13 +154,13 @@ static ERTS_INLINE Uint node_max_size(AOFF_RBTree_t *x)
static ERTS_INLINE void lower_max_size(AOFF_RBTree_t *node,
AOFF_RBTree_t* stop_at)
{
- AOFF_RBTree_t* x = node;
+ AOFF_RBTree_t* x = node;
Uint old_max = x->max_sz;
Uint new_max = node_max_size(x);
if (new_max < old_max) {
x->max_sz = new_max;
- while ((x=x->parent) != stop_at && x->max_sz == old_max) {
+ while ((x=x->parent) != stop_at && x->max_sz == old_max) {
x->max_sz = node_max_size(x);
}
ASSERT(x == stop_at || x->max_sz > old_max);
@@ -239,6 +241,9 @@ static void aoff_add_mbc(Allctr_t*, Carrier_t*);
static void aoff_remove_mbc(Allctr_t*, Carrier_t*);
static UWord aoff_largest_fblk_in_mbc(Allctr_t*, Carrier_t*);
+static Block_t *aoff_first_fblk_in_mbc(Allctr_t *, Carrier_t *);
+static Block_t *aoff_next_fblk_in_mbc(Allctr_t *, Carrier_t *, Block_t *);
+
/* Generic tree functions used by both carrier and block trees. */
static void rbt_delete(AOFF_RBTree_t** root, AOFF_RBTree_t* del);
static void rbt_insert(enum AOFFSortOrder, AOFF_RBTree_t** root, AOFF_RBTree_t* blk);
@@ -281,15 +286,28 @@ erts_aoffalc_start(AOFFAllctr_t *alc,
sys_memcpy((void *) alc, (void *) &zero.allctr, sizeof(AOFFAllctr_t));
+ if (aoffinit->blk_order == FF_CHAOS) {
+ const enum AOFFSortOrder orders[3] = {FF_AOFF, FF_AOBF, FF_BF};
+ int index = init->ix % (sizeof(orders) / sizeof(orders[0]));
+
+ ASSERT(init->alloc_no == ERTS_ALC_A_TEST);
+ aoffinit->blk_order = orders[index];
+ }
+
+ if (aoffinit->crr_order == FF_CHAOS) {
+ const enum AOFFSortOrder orders[2] = {FF_AGEFF, FF_AOFF};
+ int index = init->ix % (sizeof(orders) / sizeof(orders[0]));
+
+ ASSERT(init->alloc_no == ERTS_ALC_A_TEST);
+ aoffinit->crr_order = orders[index];
+ }
+
alc->blk_order = aoffinit->blk_order;
alc->crr_order = aoffinit->crr_order;
allctr->mbc_header_size = sizeof(AOFF_Carrier_t);
allctr->min_mbc_size = MIN_MBC_SZ;
allctr->min_mbc_first_free_size = MIN_MBC_FIRST_FREE_SZ;
- allctr->min_block_size = (aoffinit->blk_order == FF_BF
- ? (offsetof(AOFF_RBTree_t, u.next)
- + ErtsSizeofMember(AOFF_RBTree_t, u.next))
- : offsetof(AOFF_RBTree_t, u));
+ allctr->min_block_size = sizeof(AOFF_RBTree_t);
allctr->vsn_str = ERTS_ALC_AOFF_ALLOC_VSN_STR;
@@ -311,6 +329,8 @@ erts_aoffalc_start(AOFFAllctr_t *alc,
allctr->add_mbc = aoff_add_mbc;
allctr->remove_mbc = aoff_remove_mbc;
allctr->largest_fblk_in_mbc = aoff_largest_fblk_in_mbc;
+ allctr->first_fblk_in_mbc = aoff_first_fblk_in_mbc;
+ allctr->next_fblk_in_mbc = aoff_next_fblk_in_mbc;
allctr->init_atoms = init_atoms;
#ifdef ERTS_ALLOC_UTIL_HARD_DEBUG
@@ -352,7 +372,7 @@ left_rotate(AOFF_RBTree_t **root, AOFF_RBTree_t *x)
x->parent = y;
y->max_sz = x->max_sz;
- x->max_sz = node_max_size(x);
+ x->max_sz = node_max_size(x);
ASSERT(y->max_sz >= x->max_sz);
}
@@ -377,7 +397,7 @@ right_rotate(AOFF_RBTree_t **root, AOFF_RBTree_t *x)
y->right = x;
x->parent = y;
y->max_sz = x->max_sz;
- x->max_sz = node_max_size(x);
+ x->max_sz = node_max_size(x);
ASSERT(y->max_sz >= x->max_sz);
}
@@ -512,51 +532,52 @@ tree_insert_fixup(AOFF_RBTree_t** root, AOFF_RBTree_t *blk)
static void
aoff_unlink_free_block(Allctr_t *allctr, Block_t *blk)
{
- AOFFAllctr_t* alc = (AOFFAllctr_t*)allctr;
AOFF_RBTree_t* del = (AOFF_RBTree_t*)blk;
AOFF_Carrier_t *crr = (AOFF_Carrier_t*) FBLK_TO_MBC(&del->hdr);
+ (void)allctr;
+
ASSERT(crr->rbt_node.hdr.bhdr == crr->root->max_sz);
- HARD_CHECK_TREE(&crr->crr, alc->blk_order, crr->root, 0);
+ HARD_CHECK_TREE(&crr->crr, crr->blk_order, crr->root, 0);
- if (alc->blk_order == FF_BF) {
+ if (crr->blk_order == FF_BF) {
ASSERT(del->flags & IS_BF_FLG);
if (IS_LIST_ELEM(del)) {
/* Remove from list */
- ASSERT(LIST_PREV(del));
- ASSERT(LIST_PREV(del)->flags & IS_BF_FLG);
- LIST_NEXT(LIST_PREV(del)) = LIST_NEXT(del);
- if (LIST_NEXT(del)) {
- ASSERT(LIST_NEXT(del)->flags & IS_BF_FLG);
- LIST_PREV(LIST_NEXT(del)) = LIST_PREV(del);
+ ASSERT(AOFF_LIST_PREV(del));
+ ASSERT(AOFF_LIST_PREV(del)->flags & IS_BF_FLG);
+ AOFF_LIST_NEXT(AOFF_LIST_PREV(del)) = AOFF_LIST_NEXT(del);
+ if (AOFF_LIST_NEXT(del)) {
+ ASSERT(AOFF_LIST_NEXT(del)->flags & IS_BF_FLG);
+ AOFF_LIST_PREV(AOFF_LIST_NEXT(del)) = AOFF_LIST_PREV(del);
}
return;
}
- else if (LIST_NEXT(del)) {
+ else if (AOFF_LIST_NEXT(del)) {
/* Replace tree node by next element in list... */
- ASSERT(AOFF_BLK_SZ(LIST_NEXT(del)) == AOFF_BLK_SZ(del));
- ASSERT(IS_LIST_ELEM(LIST_NEXT(del)));
-
- replace(&crr->root, (AOFF_RBTree_t*)del, LIST_NEXT(del));
-
- HARD_CHECK_TREE(&crr->crr, alc->blk_order, crr->root, 0);
+ ASSERT(AOFF_BLK_SZ(AOFF_LIST_NEXT(del)) == AOFF_BLK_SZ(del));
+ ASSERT(IS_LIST_ELEM(AOFF_LIST_NEXT(del)));
+
+ replace(&crr->root, (AOFF_RBTree_t*)del, AOFF_LIST_NEXT(del));
+
+ HARD_CHECK_TREE(&crr->crr, crr->blk_order, crr->root, 0);
return;
}
}
rbt_delete(&crr->root, (AOFF_RBTree_t*)del);
- HARD_CHECK_TREE(&crr->crr, alc->blk_order, crr->root, 0);
+ HARD_CHECK_TREE(&crr->crr, crr->blk_order, crr->root, 0);
/* Update the carrier tree with a potentially new (lower) max_sz
- */
+ */
if (crr->root) {
if (crr->rbt_node.hdr.bhdr == crr->root->max_sz) {
return;
}
ASSERT(crr->rbt_node.hdr.bhdr > crr->root->max_sz);
- crr->rbt_node.hdr.bhdr = crr->root->max_sz;
+ crr->rbt_node.hdr.bhdr = crr->root->max_sz;
}
else {
crr->rbt_node.hdr.bhdr = 0;
@@ -737,17 +758,18 @@ rbt_delete(AOFF_RBTree_t** root, AOFF_RBTree_t* del)
static void
aoff_link_free_block(Allctr_t *allctr, Block_t *block)
{
- AOFFAllctr_t* alc = (AOFFAllctr_t*) allctr;
AOFF_RBTree_t *blk = (AOFF_RBTree_t *) block;
AOFF_RBTree_t *crr_node;
AOFF_Carrier_t *blk_crr = (AOFF_Carrier_t*) FBLK_TO_MBC(block);
Uint blk_sz = AOFF_BLK_SZ(blk);
+ (void)allctr;
+
ASSERT(allctr == ERTS_ALC_CARRIER_TO_ALLCTR(&blk_crr->crr));
ASSERT(blk_crr->rbt_node.hdr.bhdr == (blk_crr->root ? blk_crr->root->max_sz : 0));
- HARD_CHECK_TREE(&blk_crr->crr, alc->blk_order, blk_crr->root, 0);
+ HARD_CHECK_TREE(&blk_crr->crr, blk_crr->blk_order, blk_crr->root, 0);
- rbt_insert(alc->blk_order, &blk_crr->root, blk);
+ rbt_insert(blk_crr->blk_order, &blk_crr->root, blk);
/*
* Update carrier tree with a potentially new (larger) max_sz
@@ -773,7 +795,7 @@ rbt_insert(enum AOFFSortOrder order, AOFF_RBTree_t** root, AOFF_RBTree_t* blk)
#ifdef DEBUG
blk->flags = (order == FF_BF) ? IS_BF_FLG : 0;
#else
- blk->flags = 0;
+ blk->flags = 0;
#endif
blk->left = NULL;
blk->right = NULL;
@@ -787,7 +809,7 @@ rbt_insert(enum AOFFSortOrder order, AOFF_RBTree_t** root, AOFF_RBTree_t* blk)
else {
AOFF_RBTree_t *x = *root;
while (1) {
- SWord diff;
+ SWord diff;
if (x->max_sz < blk_sz) {
x->max_sz = blk_sz;
}
@@ -810,14 +832,14 @@ rbt_insert(enum AOFFSortOrder order, AOFF_RBTree_t** root, AOFF_RBTree_t* blk)
}
else {
ASSERT(order == FF_BF);
- ASSERT(blk->flags & IS_BF_FLG);
- ASSERT(x->flags & IS_BF_FLG);
+ ASSERT(blk->flags & IS_BF_FLG);
+ ASSERT(x->flags & IS_BF_FLG);
SET_LIST_ELEM(blk);
- LIST_NEXT(blk) = LIST_NEXT(x);
- LIST_PREV(blk) = x;
- if (LIST_NEXT(x))
- LIST_PREV(LIST_NEXT(x)) = blk;
- LIST_NEXT(x) = blk;
+ AOFF_LIST_NEXT(blk) = AOFF_LIST_NEXT(x);
+ AOFF_LIST_PREV(blk) = x;
+ if (AOFF_LIST_NEXT(x))
+ AOFF_LIST_PREV(AOFF_LIST_NEXT(x)) = blk;
+ AOFF_LIST_NEXT(x) = blk;
return;
}
}
@@ -831,7 +853,7 @@ rbt_insert(enum AOFFSortOrder order, AOFF_RBTree_t** root, AOFF_RBTree_t* blk)
}
if (order == FF_BF) {
SET_TREE_NODE(blk);
- LIST_NEXT(blk) = NULL;
+ AOFF_LIST_NEXT(blk) = NULL;
}
}
@@ -878,7 +900,7 @@ aoff_get_free_block(Allctr_t *allctr, Uint size,
#ifdef HARD_DEBUG
AOFF_RBTree_t* dbg_blk;
#endif
-
+
ASSERT(!cand_blk || cand_size >= size);
/* Get first-fit carrier
@@ -891,7 +913,7 @@ aoff_get_free_block(Allctr_t *allctr, Uint size,
/* Get block within carrier tree
*/
#ifdef HARD_DEBUG
- dbg_blk = HARD_CHECK_TREE(&crr->crr, alc->blk_order, crr->root, size);
+ dbg_blk = HARD_CHECK_TREE(&crr->crr, crr->blk_order, crr->root, size);
#endif
blk = rbt_search(crr->root, size);
@@ -904,7 +926,7 @@ aoff_get_free_block(Allctr_t *allctr, Uint size,
if (!blk)
return NULL;
- if (cand_blk && cmp_cand_blk(alc->blk_order, cand_blk, blk) < 0) {
+ if (cand_blk && cmp_cand_blk(crr->blk_order, cand_blk, blk) < 0) {
return NULL; /* cand_blk was better */
}
@@ -927,21 +949,28 @@ static void aoff_creating_mbc(Allctr_t *allctr, Carrier_t *carrier)
AOFFAllctr_t *alc = (AOFFAllctr_t *) allctr;
AOFF_Carrier_t *crr = (AOFF_Carrier_t*) carrier;
AOFF_RBTree_t **root = &alc->mbc_root;
+ Sint64 bt = get_birth_time();
HARD_CHECK_TREE(NULL, alc->crr_order, *root, 0);
crr->rbt_node.hdr.bhdr = 0;
- if (alc->crr_order == FF_AGEFF || IS_DEBUG) {
- Sint64 bt = get_birth_time();
- crr->rbt_node.u.birth_time = bt;
- crr->crr.cpool.pooled.u.birth_time = bt;
- }
+
+ /* While birth time is only used for FF_AGEFF, we have to set it for all
+ * types as we can be migrated to an instance that uses it and we don't
+ * want to mess its order up. */
+ crr->rbt_node.u.birth_time = bt;
+ crr->crr.cpool.pooled.u.birth_time = bt;
+
rbt_insert(alc->crr_order, root, &crr->rbt_node);
/* aoff_link_free_block will add free block later */
crr->root = NULL;
HARD_CHECK_TREE(NULL, alc->crr_order, *root, 0);
+
+ /* When a carrier has been migrated, its block order may differ from that
+ * of the allocator it's been migrated to. */
+ crr->blk_order = alc->blk_order;
}
#define IS_CRR_IN_TREE(CRR,ROOT) \
@@ -964,7 +993,7 @@ static void aoff_add_mbc(Allctr_t *allctr, Carrier_t *carrier)
AOFF_RBTree_t **root = &alc->mbc_root;
ASSERT(!IS_CRR_IN_TREE(crr, *root));
- HARD_CHECK_TREE(NULL, alc->crr_order, *root, 0);
+ HARD_CHECK_TREE(NULL, alc->crr_order, *root, 0);
rbt_insert(alc->crr_order, root, &crr->rbt_node);
@@ -1034,6 +1063,62 @@ static UWord aoff_largest_fblk_in_mbc(Allctr_t* allctr, Carrier_t* carrier)
return crr->rbt_node.hdr.bhdr;
}
+static Block_t *aoff_first_fblk_in_mbc(Allctr_t *allctr, Carrier_t *carrier)
+{
+ AOFF_Carrier_t *crr = (AOFF_Carrier_t*)carrier;
+
+ (void)allctr;
+
+ if (crr->root) {
+ AOFF_RBTree_t *blk;
+
+ /* Descend to the rightmost block of the tree. */
+ for (blk = crr->root; blk->right; blk = blk->right);
+
+ return (Block_t*)blk;
+ }
+
+ return NULL;
+}
+
+static Block_t *aoff_next_fblk_in_mbc(Allctr_t *allctr, Carrier_t *carrier,
+ Block_t *block)
+{
+ AOFF_RBTree_t *parent, *blk;
+
+ (void)allctr;
+ (void)carrier;
+
+ blk = (AOFF_RBTree_t*)block;
+
+ if (blk->left) {
+ /* Descend to the rightmost block of the left subtree. */
+ for (blk = blk->left; blk->right; blk = blk->right);
+
+ return (Block_t*)blk;
+ }
+
+ while (blk->parent) {
+ parent = blk->parent;
+
+ /* If we ascend from the right we know we haven't visited our parent
+ * yet, because we always descend as far as we can to the right when
+ * entering a subtree. */
+ if (parent->right == blk) {
+ ASSERT(parent->left != blk);
+ return (Block_t*)parent;
+ }
+
+ /* If we ascend from the left we know we've already visited our
+ * parent, and will need to keep ascending until we do so from the
+ * right or reach the end of the tree. */
+ ASSERT(parent->left == blk);
+ blk = parent;
+ }
+
+ return NULL;
+}
+
/*
* info_options()
*/
@@ -1105,7 +1190,7 @@ info_options(Allctr_t *allctr,
}
if (hpp || szp) {
-
+
if (!atoms_initialized)
erts_exit(ERTS_ERROR_EXIT, "%s:%d: Internal error: Atoms not initialized",
__FILE__, __LINE__);;
@@ -1132,7 +1217,7 @@ erts_aoffalc_test(UWord op, UWord a1, UWord a2)
switch (op) {
case 0x500: return (UWord) ((AOFFAllctr_t *) a1)->blk_order == FF_AOBF;
case 0x501: {
- AOFF_RBTree_t *node = ((AOFFAllctr_t *) a1)->mbc_root;
+ AOFF_RBTree_t *node = ((AOFFAllctr_t *) a1)->mbc_root;
Uint size = (Uint) a2;
node = node ? rbt_search(node, size) : NULL;
return (UWord) (node ? RBT_NODE_TO_MBC(node)->root : NULL);
@@ -1140,13 +1225,13 @@ erts_aoffalc_test(UWord op, UWord a1, UWord a2)
case 0x502: return (UWord) ((AOFF_RBTree_t *) a1)->parent;
case 0x503: return (UWord) ((AOFF_RBTree_t *) a1)->left;
case 0x504: return (UWord) ((AOFF_RBTree_t *) a1)->right;
- case 0x505: return (UWord) LIST_NEXT(a1);
+ case 0x505: return (UWord) AOFF_LIST_NEXT(a1);
case 0x506: return (UWord) IS_BLACK((AOFF_RBTree_t *) a1);
case 0x507: return (UWord) IS_TREE_NODE((AOFF_RBTree_t *) a1);
case 0x508: return (UWord) 0; /* IS_BF_ALGO */
case 0x509: return (UWord) ((AOFF_RBTree_t *) a1)->max_sz;
case 0x50a: return (UWord) ((AOFFAllctr_t *) a1)->blk_order == FF_BF;
- case 0x50b: return (UWord) LIST_PREV(a1);
+ case 0x50b: return (UWord) AOFF_LIST_PREV(a1);
default: ASSERT(0); return ~((UWord) 0);
}
}
@@ -1166,7 +1251,7 @@ static int rbt_is_member(AOFF_RBTree_t* root, AOFF_RBTree_t* node)
return 0;
}
node = node->parent;
- }
+ }
return 1;
}
@@ -1279,15 +1364,15 @@ check_tree(Carrier_t* within_crr, enum AOFFSortOrder order, AOFF_RBTree_t* root,
}
if (order == FF_BF) {
AOFF_RBTree_t* y = x;
- AOFF_RBTree_t* nxt = LIST_NEXT(y);
+ AOFF_RBTree_t* nxt = AOFF_LIST_NEXT(y);
ASSERT(IS_TREE_NODE(x));
while (nxt) {
ASSERT(IS_LIST_ELEM(nxt));
ASSERT(AOFF_BLK_SZ(nxt) == AOFF_BLK_SZ(x));
ASSERT(FBLK_TO_MBC(&nxt->hdr) == within_crr);
- ASSERT(LIST_PREV(nxt) == y);
+ ASSERT(AOFF_LIST_PREV(nxt) == y);
y = nxt;
- nxt = LIST_NEXT(nxt);
+ nxt = AOFF_LIST_NEXT(nxt);
}
}
@@ -1301,13 +1386,13 @@ check_tree(Carrier_t* within_crr, enum AOFFSortOrder order, AOFF_RBTree_t* root,
if (x->left) {
ASSERT(x->left->parent == x);
ASSERT(cmp_blocks(order, x->left, x) < 0);
- ASSERT(x->left->max_sz <= x->max_sz);
+ ASSERT(x->left->max_sz <= x->max_sz);
}
if (x->right) {
ASSERT(x->right->parent == x);
ASSERT(cmp_blocks(order, x->right, x) > 0);
- ASSERT(x->right->max_sz <= x->max_sz);
+ ASSERT(x->right->max_sz <= x->max_sz);
}
ASSERT(x->max_sz >= AOFF_BLK_SZ(x));
ASSERT(x->max_sz == AOFF_BLK_SZ(x)
@@ -1327,7 +1412,7 @@ check_tree(Carrier_t* within_crr, enum AOFFSortOrder order, AOFF_RBTree_t* root,
x = x->parent;
--depth;
}
- ASSERT(depth == 0 || (!root && depth==1));
+ ASSERT(depth == 0 || (!root && depth==1));
ASSERT(curr_blacks == 0);
ASSERT((1 << (max_depth/2)) <= node_cnt);
@@ -1373,4 +1458,3 @@ print_tree(AOFF_RBTree_t* root)
#endif /* PRINT_TREE */
#endif /* HARD_DEBUG */
-
diff --git a/erts/emulator/beam/erl_ao_firstfit_alloc.h b/erts/emulator/beam/erl_ao_firstfit_alloc.h
index 68df9e0a49..9c9b98da86 100644
--- a/erts/emulator/beam/erl_ao_firstfit_alloc.h
+++ b/erts/emulator/beam/erl_ao_firstfit_alloc.h
@@ -32,7 +32,12 @@ enum AOFFSortOrder {
FF_AGEFF = 0, /* carrier trees only */
FF_AOFF = 1,
FF_AOBF = 2, /* block trees only */
- FF_BF = 3 /* block trees only */
+ FF_BF = 3, /* block trees only */
+
+ FF_CHAOS = -1 /* A test-specific sort order that picks any of the above
+ * after instance id. Used to test that carriers created
+ * under one order will work fine after being migrated
+ * to another. */
};
typedef struct {
diff --git a/erts/emulator/beam/erl_arith.c b/erts/emulator/beam/erl_arith.c
index 144fb56ea5..68d1cd989e 100644
--- a/erts/emulator/beam/erl_arith.c
+++ b/erts/emulator/beam/erl_arith.c
@@ -52,19 +52,11 @@ static ERTS_INLINE void maybe_shrink(Process* p, Eterm* hp, Eterm res, Uint allo
Uint actual;
if (is_immed(res)) {
- if (p->heap <= hp && hp < p->htop) {
- p->htop = hp;
- }
- else {
- erts_heap_frag_shrink(p, hp);
- }
+ ASSERT(!(p->heap <= hp && hp < p->htop));
+ erts_heap_frag_shrink(p, hp);
} else if ((actual = bignum_header_arity(*hp)+1) < alloc) {
- if (p->heap <= hp && hp < p->htop) {
- p->htop = hp+actual;
- }
- else {
- erts_heap_frag_shrink(p, hp+actual);
- }
+ ASSERT(!(p->heap <= hp && hp < p->htop));
+ erts_heap_frag_shrink(p, hp+actual);
}
}
@@ -246,7 +238,7 @@ shift(Process* p, Eterm arg1, Eterm arg2, int right)
BIF_ERROR(p, SYSTEM_LIMIT);
}
need = BIG_NEED_SIZE(ires+1);
- bigp = HAlloc(p, need);
+ bigp = HeapFragOnlyAlloc(p, need);
arg1 = big_lshift(arg1, i, bigp);
maybe_shrink(p, bigp, arg1, need);
if (is_nil(arg1)) {
@@ -298,7 +290,7 @@ BIF_RETTYPE bnot_1(BIF_ALIST_1)
ret = make_small(~signed_val(BIF_ARG_1));
} else if (is_big(BIF_ARG_1)) {
Uint need = BIG_NEED_SIZE(big_size(BIF_ARG_1)+1);
- Eterm* bigp = HAlloc(BIF_P, need);
+ Eterm* bigp = HeapFragOnlyAlloc(BIF_P, need);
ret = big_bnot(BIF_ARG_1, bigp);
maybe_shrink(BIF_P, bigp, ret, need);
@@ -343,7 +335,7 @@ erts_mixed_plus(Process* p, Eterm arg1, Eterm arg2)
if (IS_SSMALL(ires)) {
return make_small(ires);
} else {
- hp = HAlloc(p, 2);
+ hp = HeapFragOnlyAlloc(p, 2);
res = small_to_big(ires, hp);
return res;
}
@@ -400,7 +392,7 @@ erts_mixed_plus(Process* p, Eterm arg1, Eterm arg2)
sz2 = big_size(arg2);
sz = MAX(sz1, sz2)+1;
need_heap = BIG_NEED_SIZE(sz);
- hp = HAlloc(p, need_heap);
+ hp = HeapFragOnlyAlloc(p, need_heap);
res = big_plus(arg1, arg2, hp);
maybe_shrink(p, hp, res, need_heap);
if (is_nil(res)) {
@@ -446,7 +438,7 @@ erts_mixed_plus(Process* p, Eterm arg1, Eterm arg2)
do_float:
f1.fd = f1.fd + f2.fd;
ERTS_FP_ERROR(p, f1.fd, goto badarith);
- hp = HAlloc(p, FLOAT_SIZE_OBJECT);
+ hp = HeapFragOnlyAlloc(p, FLOAT_SIZE_OBJECT);
res = make_float(hp);
PUT_DOUBLE(f1, hp);
return res;
@@ -488,7 +480,7 @@ erts_mixed_minus(Process* p, Eterm arg1, Eterm arg2)
if (IS_SSMALL(ires)) {
return make_small(ires);
} else {
- hp = HAlloc(p, 2);
+ hp = HeapFragOnlyAlloc(p, 2);
res = small_to_big(ires, hp);
return res;
}
@@ -534,7 +526,7 @@ erts_mixed_minus(Process* p, Eterm arg1, Eterm arg2)
sz2 = big_size(arg2);
sz = MAX(sz1, sz2)+1;
need_heap = BIG_NEED_SIZE(sz);
- hp = HAlloc(p, need_heap);
+ hp = HeapFragOnlyAlloc(p, need_heap);
res = big_minus(arg1, arg2, hp);
maybe_shrink(p, hp, res, need_heap);
if (is_nil(res)) {
@@ -589,7 +581,7 @@ erts_mixed_minus(Process* p, Eterm arg1, Eterm arg2)
do_float:
f1.fd = f1.fd - f2.fd;
ERTS_FP_ERROR(p, f1.fd, goto badarith);
- hp = HAlloc(p, FLOAT_SIZE_OBJECT);
+ hp = HeapFragOnlyAlloc(p, FLOAT_SIZE_OBJECT);
res = make_float(hp);
PUT_DOUBLE(f1, hp);
return res;
@@ -657,7 +649,7 @@ erts_mixed_times(Process* p, Eterm arg1, Eterm arg2)
hdr = big_res[0];
arity = bignum_header_arity(hdr);
ASSERT(arity == 1 || arity == 2);
- hp = HAlloc(p, arity+1);
+ hp = HeapFragOnlyAlloc(p, arity+1);
res = make_big(hp);
*hp++ = hdr;
*hp++ = big_res[1];
@@ -726,7 +718,7 @@ erts_mixed_times(Process* p, Eterm arg1, Eterm arg2)
do_big:
need_heap = BIG_NEED_SIZE(sz);
- hp = HAlloc(p, need_heap);
+ hp = HeapFragOnlyAlloc(p, need_heap);
res = big_times(arg1, arg2, hp);
/*
@@ -779,7 +771,7 @@ erts_mixed_times(Process* p, Eterm arg1, Eterm arg2)
do_float:
f1.fd = f1.fd * f2.fd;
ERTS_FP_ERROR(p, f1.fd, goto badarith);
- hp = HAlloc(p, FLOAT_SIZE_OBJECT);
+ hp = HeapFragOnlyAlloc(p, FLOAT_SIZE_OBJECT);
res = make_float(hp);
PUT_DOUBLE(f1, hp);
return res;
@@ -905,7 +897,7 @@ erts_mixed_div(Process* p, Eterm arg1, Eterm arg2)
do_float:
f1.fd = f1.fd / f2.fd;
ERTS_FP_ERROR(p, f1.fd, goto badarith);
- hp = HAlloc(p, FLOAT_SIZE_OBJECT);
+ hp = HeapFragOnlyAlloc(p, FLOAT_SIZE_OBJECT);
PUT_DOUBLE(f1, hp);
return make_float(hp);
default:
@@ -957,7 +949,7 @@ erts_int_div(Process* p, Eterm arg1, Eterm arg2)
ires = big_size(arg2);
need = BIG_NEED_SIZE(i-ires+1) + BIG_NEED_SIZE(i);
- hp = HAlloc(p, need);
+ hp = HeapFragOnlyAlloc(p, need);
arg1 = big_div(arg1, arg2, hp);
maybe_shrink(p, hp, arg1, need);
if (is_nil(arg1)) {
@@ -1004,7 +996,7 @@ erts_int_rem(Process* p, Eterm arg1, Eterm arg2)
arg1 = SMALL_ZERO;
} else if (ires > 0) {
Uint need = BIG_NEED_SIZE(big_size(arg1));
- Eterm* hp = HAlloc(p, need);
+ Eterm* hp = HeapFragOnlyAlloc(p, need);
arg1 = big_rem(arg1, arg2, hp);
maybe_shrink(p, hp, arg1, need);
@@ -1041,7 +1033,7 @@ Eterm erts_band(Process* p, Eterm arg1, Eterm arg2)
return THE_NON_VALUE;
}
need = BIG_NEED_SIZE(MAX(big_size(arg1), big_size(arg2)) + 1);
- hp = HAlloc(p, need);
+ hp = HeapFragOnlyAlloc(p, need);
arg1 = big_band(arg1, arg2, hp);
ASSERT(is_not_nil(arg1));
maybe_shrink(p, hp, arg1, need);
@@ -1069,7 +1061,7 @@ Eterm erts_bor(Process* p, Eterm arg1, Eterm arg2)
return THE_NON_VALUE;
}
need = BIG_NEED_SIZE(MAX(big_size(arg1), big_size(arg2)) + 1);
- hp = HAlloc(p, need);
+ hp = HeapFragOnlyAlloc(p, need);
arg1 = big_bor(arg1, arg2, hp);
ASSERT(is_not_nil(arg1));
maybe_shrink(p, hp, arg1, need);
@@ -1097,7 +1089,7 @@ Eterm erts_bxor(Process* p, Eterm arg1, Eterm arg2)
return THE_NON_VALUE;
}
need = BIG_NEED_SIZE(MAX(big_size(arg1), big_size(arg2)) + 1);
- hp = HAlloc(p, need);
+ hp = HeapFragOnlyAlloc(p, need);
arg1 = big_bxor(arg1, arg2, hp);
ASSERT(is_not_nil(arg1));
maybe_shrink(p, hp, arg1, need);
@@ -1110,7 +1102,7 @@ Eterm erts_bnot(Process* p, Eterm arg)
if (is_big(arg)) {
Uint need = BIG_NEED_SIZE(big_size(arg)+1);
- Eterm* bigp = HAlloc(p, need);
+ Eterm* bigp = HeapFragOnlyAlloc(p, need);
ret = big_bnot(arg, bigp);
maybe_shrink(p, bigp, ret, need);
@@ -1125,924 +1117,6 @@ Eterm erts_bnot(Process* p, Eterm arg)
return ret;
}
-#define ERTS_NEED_GC(p, need) ((HEAP_LIMIT((p)) - HEAP_TOP((p))) <= (need))
-
-static ERTS_INLINE void
-trim_heap(Process* p, Eterm* hp, Eterm res)
-{
- if (is_immed(res)) {
- ASSERT(p->heap <= hp && hp <= p->htop);
- p->htop = hp;
- } else {
- Eterm* new_htop;
- ASSERT(is_big(res));
- new_htop = hp + bignum_header_arity(*hp) + 1;
- ASSERT(p->heap <= new_htop && new_htop <= p->htop);
- p->htop = new_htop;
- }
- ASSERT(p->heap <= p->htop && p->htop <= p->stop);
-}
-
-/*
- * The functions that follow are called from the emulator loop.
- * They are not allowed to allocate heap fragments, but must do
- * a garbage collection if there is insufficient heap space.
- */
-
-#define erts_heap_frag_shrink horrible error
-#define maybe_shrink horrible error
-
-Eterm
-erts_gc_mixed_plus(Process* p, Eterm* reg, Uint live)
-{
- Eterm arg1;
- Eterm arg2;
- DECLARE_TMP(tmp_big1,0,p);
- DECLARE_TMP(tmp_big2,1,p);
- Eterm res;
- Eterm hdr;
- FloatDef f1, f2;
- dsize_t sz1, sz2, sz;
- int need_heap;
- Eterm* hp;
- Sint ires;
-
- arg1 = reg[live];
- arg2 = reg[live+1];
- ERTS_FP_CHECK_INIT(p);
- switch (arg1 & _TAG_PRIMARY_MASK) {
- case TAG_PRIMARY_IMMED1:
- switch ((arg1 & _TAG_IMMED1_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_IMMED1_SMALL >> _TAG_PRIMARY_SIZE):
- switch (arg2 & _TAG_PRIMARY_MASK) {
- case TAG_PRIMARY_IMMED1:
- switch ((arg2 & _TAG_IMMED1_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_IMMED1_SMALL >> _TAG_PRIMARY_SIZE):
- ires = signed_val(arg1) + signed_val(arg2);
- if (IS_SSMALL(ires)) {
- return make_small(ires);
- } else {
- if (ERTS_NEED_GC(p, 2)) {
- erts_garbage_collect(p, 2, reg, live);
- }
- hp = p->htop;
- p->htop += 2;
- res = small_to_big(ires, hp);
- return res;
- }
- default:
- badarith:
- p->freason = BADARITH;
- return THE_NON_VALUE;
- }
- case TAG_PRIMARY_BOXED:
- hdr = *boxed_val(arg2);
- switch ((hdr & _TAG_HEADER_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_HEADER_POS_BIG >> _TAG_PRIMARY_SIZE):
- case (_TAG_HEADER_NEG_BIG >> _TAG_PRIMARY_SIZE):
- if (arg1 == SMALL_ZERO) {
- return arg2;
- }
- arg1 = small_to_big(signed_val(arg1), tmp_big1);
- goto do_big;
- case (_TAG_HEADER_FLOAT >> _TAG_PRIMARY_SIZE):
- f1.fd = signed_val(arg1);
- GET_DOUBLE(arg2, f2);
- goto do_float;
- default:
- goto badarith;
- }
- }
- default:
- goto badarith;
- }
- case TAG_PRIMARY_BOXED:
- hdr = *boxed_val(arg1);
- switch ((hdr & _TAG_HEADER_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_HEADER_POS_BIG >> _TAG_PRIMARY_SIZE):
- case (_TAG_HEADER_NEG_BIG >> _TAG_PRIMARY_SIZE):
- switch (arg2 & _TAG_PRIMARY_MASK) {
- case (_TAG_IMMED1_SMALL >> _TAG_PRIMARY_SIZE):
- switch ((arg2 & _TAG_IMMED1_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_IMMED1_SMALL >> _TAG_PRIMARY_SIZE):
- if (arg2 == SMALL_ZERO) {
- return arg1;
- }
- arg2 = small_to_big(signed_val(arg2), tmp_big2);
- goto do_big;
- default:
- goto badarith;
- }
- case TAG_PRIMARY_BOXED:
- hdr = *boxed_val(arg2);
- switch ((hdr & _TAG_HEADER_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_HEADER_POS_BIG >> _TAG_PRIMARY_SIZE):
- case (_TAG_HEADER_NEG_BIG >> _TAG_PRIMARY_SIZE):
- do_big:
- sz1 = big_size(arg1);
- sz2 = big_size(arg2);
- sz = MAX(sz1, sz2)+1;
- need_heap = BIG_NEED_SIZE(sz);
- if (ERTS_NEED_GC(p, need_heap)) {
- erts_garbage_collect(p, need_heap, reg, live+2);
- if (ARG_IS_NOT_TMP(arg1,tmp_big1)) {
- arg1 = reg[live];
- }
- if (ARG_IS_NOT_TMP(arg2,tmp_big2)) {
- arg2 = reg[live+1];
- }
- }
- hp = p->htop;
- p->htop += need_heap;
- res = big_plus(arg1, arg2, hp);
- trim_heap(p, hp, res);
- if (is_nil(res)) {
- p->freason = SYSTEM_LIMIT;
- return THE_NON_VALUE;
- }
- return res;
- case (_TAG_HEADER_FLOAT >> _TAG_PRIMARY_SIZE):
- if (big_to_double(arg1, &f1.fd) < 0) {
- goto badarith;
- }
- GET_DOUBLE(arg2, f2);
- goto do_float;
- default:
- goto badarith;
- }
- }
- case (_TAG_HEADER_FLOAT >> _TAG_PRIMARY_SIZE):
- switch (arg2 & _TAG_PRIMARY_MASK) {
- case TAG_PRIMARY_IMMED1:
- switch ((arg2 & _TAG_IMMED1_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_IMMED1_SMALL >> _TAG_PRIMARY_SIZE):
- GET_DOUBLE(arg1, f1);
- f2.fd = signed_val(arg2);
- goto do_float;
- default:
- goto badarith;
- }
- case TAG_PRIMARY_BOXED:
- hdr = *boxed_val(arg2);
- switch ((hdr & _TAG_HEADER_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_HEADER_POS_BIG >> _TAG_PRIMARY_SIZE):
- case (_TAG_HEADER_NEG_BIG >> _TAG_PRIMARY_SIZE):
- GET_DOUBLE(arg1, f1);
- if (big_to_double(arg2, &f2.fd) < 0) {
- goto badarith;
- }
- goto do_float;
- case (_TAG_HEADER_FLOAT >> _TAG_PRIMARY_SIZE):
- GET_DOUBLE(arg1, f1);
- GET_DOUBLE(arg2, f2);
-
- do_float:
- f1.fd = f1.fd + f2.fd;
- ERTS_FP_ERROR(p, f1.fd, goto badarith);
- if (ERTS_NEED_GC(p, FLOAT_SIZE_OBJECT)) {
- erts_garbage_collect(p, FLOAT_SIZE_OBJECT, reg, live);
- }
- hp = p->htop;
- p->htop += FLOAT_SIZE_OBJECT;
- res = make_float(hp);
- PUT_DOUBLE(f1, hp);
- return res;
- default:
- goto badarith;
- }
- default:
- goto badarith;
- }
- }
- default:
- goto badarith;
- }
-}
-
-Eterm
-erts_gc_mixed_minus(Process* p, Eterm* reg, Uint live)
-{
- Eterm arg1;
- Eterm arg2;
- DECLARE_TMP(tmp_big1,0,p);
- DECLARE_TMP(tmp_big2,1,p);
- Eterm hdr;
- Eterm res;
- FloatDef f1, f2;
- dsize_t sz1, sz2, sz;
- int need_heap;
- Eterm* hp;
- Sint ires;
-
- arg1 = reg[live];
- arg2 = reg[live+1];
- ERTS_FP_CHECK_INIT(p);
- switch (arg1 & _TAG_PRIMARY_MASK) {
- case TAG_PRIMARY_IMMED1:
- switch ((arg1 & _TAG_IMMED1_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_IMMED1_SMALL >> _TAG_PRIMARY_SIZE):
- switch (arg2 & _TAG_PRIMARY_MASK) {
- case TAG_PRIMARY_IMMED1:
- switch ((arg2 & _TAG_IMMED1_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_IMMED1_SMALL >> _TAG_PRIMARY_SIZE):
- ires = signed_val(arg1) - signed_val(arg2);
- if (IS_SSMALL(ires)) {
- return make_small(ires);
- } else {
- if (ERTS_NEED_GC(p, 2)) {
- erts_garbage_collect(p, 2, reg, live);
- }
- hp = p->htop;
- p->htop += 2;
- res = small_to_big(ires, hp);
- return res;
- }
- default:
- badarith:
- p->freason = BADARITH;
- return THE_NON_VALUE;
- }
- case TAG_PRIMARY_BOXED:
- hdr = *boxed_val(arg2);
- switch ((hdr & _TAG_HEADER_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_HEADER_POS_BIG >> _TAG_PRIMARY_SIZE):
- case (_TAG_HEADER_NEG_BIG >> _TAG_PRIMARY_SIZE):
- arg1 = small_to_big(signed_val(arg1), tmp_big1);
- goto do_big;
- case (_TAG_HEADER_FLOAT >> _TAG_PRIMARY_SIZE):
- f1.fd = signed_val(arg1);
- GET_DOUBLE(arg2, f2);
- goto do_float;
- default:
- goto badarith;
- }
- }
- default:
- goto badarith;
- }
- case TAG_PRIMARY_BOXED:
- hdr = *boxed_val(arg1);
- switch ((hdr & _TAG_HEADER_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_HEADER_POS_BIG >> _TAG_PRIMARY_SIZE):
- case (_TAG_HEADER_NEG_BIG >> _TAG_PRIMARY_SIZE):
- switch (arg2 & _TAG_PRIMARY_MASK) {
- case (_TAG_IMMED1_SMALL >> _TAG_PRIMARY_SIZE):
- switch ((arg2 & _TAG_IMMED1_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_IMMED1_SMALL >> _TAG_PRIMARY_SIZE):
- if (arg2 == SMALL_ZERO) {
- return arg1;
- }
- arg2 = small_to_big(signed_val(arg2), tmp_big2);
-
- do_big:
- sz1 = big_size(arg1);
- sz2 = big_size(arg2);
- sz = MAX(sz1, sz2)+1;
- need_heap = BIG_NEED_SIZE(sz);
- if (ERTS_NEED_GC(p, need_heap)) {
- erts_garbage_collect(p, need_heap, reg, live+2);
- if (ARG_IS_NOT_TMP(arg1,tmp_big1)) {
- arg1 = reg[live];
- }
- if (ARG_IS_NOT_TMP(arg2,tmp_big2)) {
- arg2 = reg[live+1];
- }
- }
- hp = p->htop;
- p->htop += need_heap;
- res = big_minus(arg1, arg2, hp);
- trim_heap(p, hp, res);
- if (is_nil(res)) {
- p->freason = SYSTEM_LIMIT;
- return THE_NON_VALUE;
- }
- return res;
- default:
- goto badarith;
- }
- case TAG_PRIMARY_BOXED:
- hdr = *boxed_val(arg2);
- switch ((hdr & _TAG_HEADER_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_HEADER_POS_BIG >> _TAG_PRIMARY_SIZE):
- case (_TAG_HEADER_NEG_BIG >> _TAG_PRIMARY_SIZE):
- goto do_big;
- case (_TAG_HEADER_FLOAT >> _TAG_PRIMARY_SIZE):
- if (big_to_double(arg1, &f1.fd) < 0) {
- goto badarith;
- }
- GET_DOUBLE(arg2, f2);
- goto do_float;
- default:
- goto badarith;
- }
- }
- case (_TAG_HEADER_FLOAT >> _TAG_PRIMARY_SIZE):
- switch (arg2 & _TAG_PRIMARY_MASK) {
- case TAG_PRIMARY_IMMED1:
- switch ((arg2 & _TAG_IMMED1_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_IMMED1_SMALL >> _TAG_PRIMARY_SIZE):
- GET_DOUBLE(arg1, f1);
- f2.fd = signed_val(arg2);
- goto do_float;
- default:
- goto badarith;
- }
- case TAG_PRIMARY_BOXED:
- hdr = *boxed_val(arg2);
- switch ((hdr & _TAG_HEADER_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_HEADER_POS_BIG >> _TAG_PRIMARY_SIZE):
- case (_TAG_HEADER_NEG_BIG >> _TAG_PRIMARY_SIZE):
- GET_DOUBLE(arg1, f1);
- if (big_to_double(arg2, &f2.fd) < 0) {
- goto badarith;
- }
- goto do_float;
- case (_TAG_HEADER_FLOAT >> _TAG_PRIMARY_SIZE):
- GET_DOUBLE(arg1, f1);
- GET_DOUBLE(arg2, f2);
-
- do_float:
- f1.fd = f1.fd - f2.fd;
- ERTS_FP_ERROR(p, f1.fd, goto badarith);
- if (ERTS_NEED_GC(p, FLOAT_SIZE_OBJECT)) {
- erts_garbage_collect(p, FLOAT_SIZE_OBJECT, reg, live);
- }
- hp = p->htop;
- p->htop += FLOAT_SIZE_OBJECT;
- res = make_float(hp);
- PUT_DOUBLE(f1, hp);
- return res;
- default:
- goto badarith;
- }
- default:
- goto badarith;
- }
- }
- default:
- goto badarith;
- }
-}
-
-Eterm
-erts_gc_mixed_times(Process* p, Eterm* reg, Uint live)
-{
- Eterm arg1;
- Eterm arg2;
- DECLARE_TMP(tmp_big1,0,p);
- DECLARE_TMP(tmp_big2,1,p);
- Eterm hdr;
- Eterm res;
- FloatDef f1, f2;
- dsize_t sz1, sz2, sz;
- int need_heap;
- Eterm* hp;
-
- arg1 = reg[live];
- arg2 = reg[live+1];
- ERTS_FP_CHECK_INIT(p);
- switch (arg1 & _TAG_PRIMARY_MASK) {
- case TAG_PRIMARY_IMMED1:
- switch ((arg1 & _TAG_IMMED1_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_IMMED1_SMALL >> _TAG_PRIMARY_SIZE):
- switch (arg2 & _TAG_PRIMARY_MASK) {
- case TAG_PRIMARY_IMMED1:
- switch ((arg2 & _TAG_IMMED1_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_IMMED1_SMALL >> _TAG_PRIMARY_SIZE):
- if ((arg1 == SMALL_ZERO) || (arg2 == SMALL_ZERO)) {
- return(SMALL_ZERO);
- } else if (arg1 == SMALL_ONE) {
- return(arg2);
- } else if (arg2 == SMALL_ONE) {
- return(arg1);
- } else {
- DeclareTmpHeap(big_res,3,p);
- UseTmpHeap(3,p);
-
- /*
- * The following code is optimized for the case that
- * result is small (which should be the most common case
- * in practice).
- */
- res = small_times(signed_val(arg1), signed_val(arg2),
- big_res);
- if (is_small(res)) {
- UnUseTmpHeap(3,p);
- return res;
- } else {
- /*
- * The result is a a big number.
- * Allocate a heap fragment and copy the result.
- * Be careful to allocate exactly what we need
- * to not leave any holes.
- */
- Uint arity;
- Uint need;
-
- ASSERT(is_big(res));
- hdr = big_res[0];
- arity = bignum_header_arity(hdr);
- ASSERT(arity == 1 || arity == 2);
- need = arity + 1;
- if (ERTS_NEED_GC(p, need)) {
- erts_garbage_collect(p, need, reg, live);
- }
- hp = p->htop;
- p->htop += need;
- res = make_big(hp);
- *hp++ = hdr;
- *hp++ = big_res[1];
- if (arity > 1) {
- *hp = big_res[2];
- }
- UnUseTmpHeap(3,p);
- return res;
- }
- }
- default:
- badarith:
- p->freason = BADARITH;
- return THE_NON_VALUE;
- }
- case TAG_PRIMARY_BOXED:
- hdr = *boxed_val(arg2);
- switch ((hdr & _TAG_HEADER_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_HEADER_POS_BIG >> _TAG_PRIMARY_SIZE):
- case (_TAG_HEADER_NEG_BIG >> _TAG_PRIMARY_SIZE):
- if (arg1 == SMALL_ZERO)
- return(SMALL_ZERO);
- if (arg1 == SMALL_ONE)
- return(arg2);
- arg1 = small_to_big(signed_val(arg1), tmp_big1);
- sz = 2 + big_size(arg2);
- goto do_big;
- case (_TAG_HEADER_FLOAT >> _TAG_PRIMARY_SIZE):
- f1.fd = signed_val(arg1);
- GET_DOUBLE(arg2, f2);
- goto do_float;
- default:
- goto badarith;
- }
- }
- default:
- goto badarith;
- }
- case TAG_PRIMARY_BOXED:
- hdr = *boxed_val(arg1);
- switch ((hdr & _TAG_HEADER_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_HEADER_POS_BIG >> _TAG_PRIMARY_SIZE):
- case (_TAG_HEADER_NEG_BIG >> _TAG_PRIMARY_SIZE):
- switch (arg2 & _TAG_PRIMARY_MASK) {
- case (_TAG_IMMED1_SMALL >> _TAG_PRIMARY_SIZE):
- switch ((arg2 & _TAG_IMMED1_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_IMMED1_SMALL >> _TAG_PRIMARY_SIZE):
- if (arg2 == SMALL_ZERO)
- return(SMALL_ZERO);
- if (arg2 == SMALL_ONE)
- return(arg1);
- arg2 = small_to_big(signed_val(arg2), tmp_big2);
- sz = 2 + big_size(arg1);
- goto do_big;
- default:
- goto badarith;
- }
- case TAG_PRIMARY_BOXED:
- hdr = *boxed_val(arg2);
- switch ((hdr & _TAG_HEADER_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_HEADER_POS_BIG >> _TAG_PRIMARY_SIZE):
- case (_TAG_HEADER_NEG_BIG >> _TAG_PRIMARY_SIZE):
- sz1 = big_size(arg1);
- sz2 = big_size(arg2);
- sz = sz1 + sz2;
-
- do_big:
- need_heap = BIG_NEED_SIZE(sz);
- if (ERTS_NEED_GC(p, need_heap)) {
- erts_garbage_collect(p, need_heap, reg, live+2);
- if (ARG_IS_NOT_TMP(arg1,tmp_big1)) {
- arg1 = reg[live];
- }
- if (ARG_IS_NOT_TMP(arg2,tmp_big2)) {
- arg2 = reg[live+1];
- }
- }
- hp = p->htop;
- p->htop += need_heap;
- res = big_times(arg1, arg2, hp);
- trim_heap(p, hp, res);
-
- /*
- * Note that the result must be big in this case, since
- * at least one operand was big to begin with, and
- * the absolute value of the other is > 1.
- */
-
- if (is_nil(res)) {
- p->freason = SYSTEM_LIMIT;
- return THE_NON_VALUE;
- }
- return res;
- case (_TAG_HEADER_FLOAT >> _TAG_PRIMARY_SIZE):
- if (big_to_double(arg1, &f1.fd) < 0) {
- goto badarith;
- }
- GET_DOUBLE(arg2, f2);
- goto do_float;
- default:
- goto badarith;
- }
- }
- case (_TAG_HEADER_FLOAT >> _TAG_PRIMARY_SIZE):
- switch (arg2 & _TAG_PRIMARY_MASK) {
- case TAG_PRIMARY_IMMED1:
- switch ((arg2 & _TAG_IMMED1_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_IMMED1_SMALL >> _TAG_PRIMARY_SIZE):
- GET_DOUBLE(arg1, f1);
- f2.fd = signed_val(arg2);
- goto do_float;
- default:
- goto badarith;
- }
- case TAG_PRIMARY_BOXED:
- hdr = *boxed_val(arg2);
- switch ((hdr & _TAG_HEADER_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_HEADER_POS_BIG >> _TAG_PRIMARY_SIZE):
- case (_TAG_HEADER_NEG_BIG >> _TAG_PRIMARY_SIZE):
- GET_DOUBLE(arg1, f1);
- if (big_to_double(arg2, &f2.fd) < 0) {
- goto badarith;
- }
- goto do_float;
- case (_TAG_HEADER_FLOAT >> _TAG_PRIMARY_SIZE):
- GET_DOUBLE(arg1, f1);
- GET_DOUBLE(arg2, f2);
-
- do_float:
- f1.fd = f1.fd * f2.fd;
- ERTS_FP_ERROR(p, f1.fd, goto badarith);
- if (ERTS_NEED_GC(p, FLOAT_SIZE_OBJECT)) {
- erts_garbage_collect(p, FLOAT_SIZE_OBJECT, reg, live);
- }
- hp = p->htop;
- p->htop += FLOAT_SIZE_OBJECT;
- res = make_float(hp);
- PUT_DOUBLE(f1, hp);
- return res;
- default:
- goto badarith;
- }
- default:
- goto badarith;
- }
- }
- default:
- goto badarith;
- }
-}
-
-Eterm
-erts_gc_mixed_div(Process* p, Eterm* reg, Uint live)
-{
- Eterm arg1;
- Eterm arg2;
- FloatDef f1, f2;
- Eterm* hp;
- Eterm hdr;
-
- arg1 = reg[live];
- arg2 = reg[live+1];
- ERTS_FP_CHECK_INIT(p);
- switch (arg1 & _TAG_PRIMARY_MASK) {
- case TAG_PRIMARY_IMMED1:
- switch ((arg1 & _TAG_IMMED1_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_IMMED1_SMALL >> _TAG_PRIMARY_SIZE):
- switch (arg2 & _TAG_PRIMARY_MASK) {
- case TAG_PRIMARY_IMMED1:
- switch ((arg2 & _TAG_IMMED1_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_IMMED1_SMALL >> _TAG_PRIMARY_SIZE):
- f1.fd = signed_val(arg1);
- f2.fd = signed_val(arg2);
- goto do_float;
- default:
- badarith:
- p->freason = BADARITH;
- return THE_NON_VALUE;
- }
- case TAG_PRIMARY_BOXED:
- hdr = *boxed_val(arg2);
- switch ((hdr & _TAG_HEADER_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_HEADER_POS_BIG >> _TAG_PRIMARY_SIZE):
- case (_TAG_HEADER_NEG_BIG >> _TAG_PRIMARY_SIZE):
- f1.fd = signed_val(arg1);
- if (big_to_double(arg2, &f2.fd) < 0) {
- goto badarith;
- }
- goto do_float;
- case (_TAG_HEADER_FLOAT >> _TAG_PRIMARY_SIZE):
- f1.fd = signed_val(arg1);
- GET_DOUBLE(arg2, f2);
- goto do_float;
- default:
- goto badarith;
- }
- }
- default:
- goto badarith;
- }
- case TAG_PRIMARY_BOXED:
- hdr = *boxed_val(arg1);
- switch ((hdr & _TAG_HEADER_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_HEADER_POS_BIG >> _TAG_PRIMARY_SIZE):
- case (_TAG_HEADER_NEG_BIG >> _TAG_PRIMARY_SIZE):
- switch (arg2 & _TAG_PRIMARY_MASK) {
- case (_TAG_IMMED1_SMALL >> _TAG_PRIMARY_SIZE):
- switch ((arg2 & _TAG_IMMED1_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_IMMED1_SMALL >> _TAG_PRIMARY_SIZE):
- if (big_to_double(arg1, &f1.fd) < 0) {
- goto badarith;
- }
- f2.fd = signed_val(arg2);
- goto do_float;
- default:
- goto badarith;
- }
- case TAG_PRIMARY_BOXED:
- hdr = *boxed_val(arg2);
- switch ((hdr & _TAG_HEADER_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_HEADER_POS_BIG >> _TAG_PRIMARY_SIZE):
- case (_TAG_HEADER_NEG_BIG >> _TAG_PRIMARY_SIZE):
- if (big_to_double(arg1, &f1.fd) < 0 ||
- big_to_double(arg2, &f2.fd) < 0) {
- goto badarith;
- }
- goto do_float;
- case (_TAG_HEADER_FLOAT >> _TAG_PRIMARY_SIZE):
- if (big_to_double(arg1, &f1.fd) < 0) {
- goto badarith;
- }
- GET_DOUBLE(arg2, f2);
- goto do_float;
- default:
- goto badarith;
- }
- }
- case (_TAG_HEADER_FLOAT >> _TAG_PRIMARY_SIZE):
- switch (arg2 & _TAG_PRIMARY_MASK) {
- case TAG_PRIMARY_IMMED1:
- switch ((arg2 & _TAG_IMMED1_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_IMMED1_SMALL >> _TAG_PRIMARY_SIZE):
- GET_DOUBLE(arg1, f1);
- f2.fd = signed_val(arg2);
- goto do_float;
- default:
- goto badarith;
- }
- case TAG_PRIMARY_BOXED:
- hdr = *boxed_val(arg2);
- switch ((hdr & _TAG_HEADER_MASK) >> _TAG_PRIMARY_SIZE) {
- case (_TAG_HEADER_POS_BIG >> _TAG_PRIMARY_SIZE):
- case (_TAG_HEADER_NEG_BIG >> _TAG_PRIMARY_SIZE):
- GET_DOUBLE(arg1, f1);
- if (big_to_double(arg2, &f2.fd) < 0) {
- goto badarith;
- }
- goto do_float;
- case (_TAG_HEADER_FLOAT >> _TAG_PRIMARY_SIZE):
- GET_DOUBLE(arg1, f1);
- GET_DOUBLE(arg2, f2);
-
- do_float:
- f1.fd = f1.fd / f2.fd;
- ERTS_FP_ERROR(p, f1.fd, goto badarith);
- if (ERTS_NEED_GC(p, FLOAT_SIZE_OBJECT)) {
- erts_garbage_collect(p, FLOAT_SIZE_OBJECT, reg, live);
- }
- hp = p->htop;
- p->htop += FLOAT_SIZE_OBJECT;
- PUT_DOUBLE(f1, hp);
- return make_float(hp);
- default:
- goto badarith;
- }
- default:
- goto badarith;
- }
- }
- default:
- goto badarith;
- }
-}
-
-Eterm
-erts_gc_int_div(Process* p, Eterm* reg, Uint live)
-{
- Eterm arg1;
- Eterm arg2;
- DECLARE_TMP(tmp_big1,0,p);
- DECLARE_TMP(tmp_big2,1,p);
- int ires;
-
- arg1 = reg[live];
- arg2 = reg[live+1];
- switch (NUMBER_CODE(arg1, arg2)) {
- case SMALL_SMALL:
- /* This case occurs if the most negative fixnum is divided by -1. */
- ASSERT(arg2 == make_small(-1));
- arg1 = small_to_big(signed_val(arg1), tmp_big1);
- /*FALLTHROUGH*/
- case BIG_SMALL:
- arg2 = small_to_big(signed_val(arg2), tmp_big2);
- goto L_big_div;
- case SMALL_BIG:
- if (arg1 != make_small(MIN_SMALL)) {
- return SMALL_ZERO;
- }
- arg1 = small_to_big(signed_val(arg1), tmp_big1);
- /*FALLTHROUGH*/
- case BIG_BIG:
- L_big_div:
- ires = big_ucomp(arg1, arg2);
- if (ires < 0) {
- arg1 = SMALL_ZERO;
- } else if (ires == 0) {
- arg1 = (big_sign(arg1) == big_sign(arg2)) ?
- SMALL_ONE : SMALL_MINUS_ONE;
- } else {
- Eterm* hp;
- int i = big_size(arg1);
- Uint need;
-
- ires = big_size(arg2);
- need = BIG_NEED_SIZE(i-ires+1) + BIG_NEED_SIZE(i);
- if (ERTS_NEED_GC(p, need)) {
- erts_garbage_collect(p, need, reg, live+2);
- if (ARG_IS_NOT_TMP(arg1,tmp_big1)) {
- arg1 = reg[live];
- }
- if (ARG_IS_NOT_TMP(arg2,tmp_big2)) {
- arg2 = reg[live+1];
- }
- }
- hp = p->htop;
- p->htop += need;
- arg1 = big_div(arg1, arg2, hp);
- trim_heap(p, hp, arg1);
- if (is_nil(arg1)) {
- p->freason = SYSTEM_LIMIT;
- return THE_NON_VALUE;
- }
- }
- return arg1;
- default:
- p->freason = BADARITH;
- return THE_NON_VALUE;
- }
-}
-
-Eterm
-erts_gc_int_rem(Process* p, Eterm* reg, Uint live)
-{
- Eterm arg1;
- Eterm arg2;
- DECLARE_TMP(tmp_big1,0,p);
- DECLARE_TMP(tmp_big2,1,p);
- int ires;
-
- arg1 = reg[live];
- arg2 = reg[live+1];
- switch (NUMBER_CODE(arg1, arg2)) {
- case BIG_SMALL:
- arg2 = small_to_big(signed_val(arg2), tmp_big2);
- goto L_big_rem;
- case SMALL_BIG:
- if (arg1 != make_small(MIN_SMALL)) {
- return arg1;
- } else {
- Eterm tmp;
- tmp = small_to_big(signed_val(arg1), tmp_big1);
- if ((ires = big_ucomp(tmp, arg2)) == 0) {
- return SMALL_ZERO;
- } else {
- ASSERT(ires < 0);
- return arg1;
- }
- }
- /* All paths returned */
- case BIG_BIG:
- L_big_rem:
- ires = big_ucomp(arg1, arg2);
- if (ires == 0) {
- arg1 = SMALL_ZERO;
- } else if (ires > 0) {
- Eterm* hp;
- Uint need = BIG_NEED_SIZE(big_size(arg1));
-
- if (ERTS_NEED_GC(p, need)) {
- erts_garbage_collect(p, need, reg, live+2);
- if (ARG_IS_NOT_TMP(arg1,tmp_big1)) {
- arg1 = reg[live];
- }
- if (ARG_IS_NOT_TMP(arg2,tmp_big2)) {
- arg2 = reg[live+1];
- }
- }
- hp = p->htop;
- p->htop += need;
- arg1 = big_rem(arg1, arg2, hp);
- trim_heap(p, hp, arg1);
- if (is_nil(arg1)) {
- p->freason = SYSTEM_LIMIT;
- return THE_NON_VALUE;
- }
- }
- return arg1;
- default:
- p->freason = BADARITH;
- return THE_NON_VALUE;
- }
-}
-
-#define DEFINE_GC_LOGIC_FUNC(func) \
-Eterm erts_gc_##func(Process* p, Eterm* reg, Uint live) \
-{ \
- Eterm arg1; \
- Eterm arg2; \
- DECLARE_TMP(tmp_big1,0,p); \
- DECLARE_TMP(tmp_big2,1,p); \
- Eterm* hp; \
- int need; \
- \
- arg1 = reg[live]; \
- arg2 = reg[live+1]; \
- switch (NUMBER_CODE(arg1, arg2)) { \
- case SMALL_BIG: \
- arg1 = small_to_big(signed_val(arg1), tmp_big1); \
- need = BIG_NEED_SIZE(big_size(arg2) + 1); \
- if (ERTS_NEED_GC(p, need)) { \
- erts_garbage_collect(p, need, reg, live+2); \
- arg2 = reg[live+1]; \
- } \
- break; \
- case BIG_SMALL: \
- arg2 = small_to_big(signed_val(arg2), tmp_big2); \
- need = BIG_NEED_SIZE(big_size(arg1) + 1); \
- if (ERTS_NEED_GC(p, need)) { \
- erts_garbage_collect(p, need, reg, live+2); \
- arg1 = reg[live]; \
- } \
- break; \
- case BIG_BIG: \
- need = BIG_NEED_SIZE(MAX(big_size(arg1), big_size(arg2)) + 1); \
- if (ERTS_NEED_GC(p, need)) { \
- erts_garbage_collect(p, need, reg, live+2); \
- arg1 = reg[live]; \
- arg2 = reg[live+1]; \
- } \
- break; \
- default: \
- p->freason = BADARITH; \
- return THE_NON_VALUE; \
- } \
- hp = p->htop; \
- p->htop += need; \
- arg1 = big_##func(arg1, arg2, hp); \
- trim_heap(p, hp, arg1); \
- return arg1; \
-}
-
-DEFINE_GC_LOGIC_FUNC(band)
-DEFINE_GC_LOGIC_FUNC(bor)
-DEFINE_GC_LOGIC_FUNC(bxor)
-
-Eterm erts_gc_bnot(Process* p, Eterm* reg, Uint live)
-{
- Eterm result;
- Eterm arg;
- Uint need;
- Eterm* bigp;
-
- arg = reg[live];
- if (is_not_big(arg)) {
- p->freason = BADARITH;
- return NIL;
- } else {
- need = BIG_NEED_SIZE(big_size(arg)+1);
- if (ERTS_NEED_GC(p, need)) {
- erts_garbage_collect(p, need, reg, live+1);
- arg = reg[live];
- }
- bigp = p->htop;
- p->htop += need;
- result = big_bnot(arg, bigp);
- trim_heap(p, bigp, result);
- if (is_nil(result)) {
- p->freason = SYSTEM_LIMIT;
- return NIL;
- }
- }
- return result;
-}
-
/* Needed to remove compiler optimization */
double erts_get_positive_zero_float() {
return 0.0f;
diff --git a/erts/emulator/beam/erl_bestfit_alloc.c b/erts/emulator/beam/erl_bestfit_alloc.c
index 9cb1199c2a..0e7be99801 100644
--- a/erts/emulator/beam/erl_bestfit_alloc.c
+++ b/erts/emulator/beam/erl_bestfit_alloc.c
@@ -122,8 +122,8 @@ typedef struct {
RBTree_t *next;
} RBTreeList_t;
-#define LIST_NEXT(N) (((RBTreeList_t *) (N))->next)
-#define LIST_PREV(N) (((RBTreeList_t *) (N))->t.parent)
+#define BF_LIST_NEXT(N) (((RBTreeList_t *) (N))->next)
+#define BF_LIST_PREV(N) (((RBTreeList_t *) (N))->t.parent)
#ifdef DEBUG
@@ -209,6 +209,8 @@ erts_bfalc_start(BFAllctr_t *bfallctr,
allctr->add_mbc = NULL;
allctr->remove_mbc = NULL;
allctr->largest_fblk_in_mbc = NULL;
+ allctr->first_fblk_in_mbc = NULL;
+ allctr->next_fblk_in_mbc = NULL;
allctr->init_atoms = init_atoms;
#ifdef ERTS_ALLOC_UTIL_HARD_DEBUG
@@ -593,7 +595,6 @@ aobf_link_free_block(Allctr_t *allctr, Block_t *block)
RBTree_t *blk = (RBTree_t *) block;
Uint blk_sz = BF_BLK_SZ(blk);
-
blk->flags = 0;
blk->left = NULL;
@@ -673,7 +674,7 @@ aobf_get_free_block(Allctr_t *allctr, Uint size,
x = x->left;
}
}
-
+
if (!blk)
return NULL;
@@ -729,11 +730,11 @@ bf_link_free_block(Allctr_t *allctr, Block_t *block)
if (blk_sz == size) {
SET_LIST_ELEM(blk);
- LIST_NEXT(blk) = LIST_NEXT(x);
- LIST_PREV(blk) = x;
- if (LIST_NEXT(x))
- LIST_PREV(LIST_NEXT(x)) = blk;
- LIST_NEXT(x) = blk;
+ BF_LIST_NEXT(blk) = BF_LIST_NEXT(x);
+ BF_LIST_PREV(blk) = x;
+ if (BF_LIST_NEXT(x))
+ BF_LIST_PREV(BF_LIST_NEXT(x)) = blk;
+ BF_LIST_NEXT(x) = blk;
return; /* Finnished */
}
@@ -764,7 +765,7 @@ bf_link_free_block(Allctr_t *allctr, Block_t *block)
}
SET_TREE_NODE(blk);
- LIST_NEXT(blk) = NULL;
+ BF_LIST_NEXT(blk) = NULL;
#ifdef HARD_DEBUG
check_tree(root, 0, 0);
@@ -780,22 +781,22 @@ bf_unlink_free_block(Allctr_t *allctr, Block_t *block)
if (IS_LIST_ELEM(x)) {
/* Remove from list */
- ASSERT(LIST_PREV(x));
- LIST_NEXT(LIST_PREV(x)) = LIST_NEXT(x);
- if (LIST_NEXT(x))
- LIST_PREV(LIST_NEXT(x)) = LIST_PREV(x);
+ ASSERT(BF_LIST_PREV(x));
+ BF_LIST_NEXT(BF_LIST_PREV(x)) = BF_LIST_NEXT(x);
+ if (BF_LIST_NEXT(x))
+ BF_LIST_PREV(BF_LIST_NEXT(x)) = BF_LIST_PREV(x);
}
- else if (LIST_NEXT(x)) {
+ else if (BF_LIST_NEXT(x)) {
/* Replace tree node by next element in list... */
- ASSERT(BF_BLK_SZ(LIST_NEXT(x)) == BF_BLK_SZ(x));
+ ASSERT(BF_BLK_SZ(BF_LIST_NEXT(x)) == BF_BLK_SZ(x));
ASSERT(IS_TREE_NODE(x));
- ASSERT(IS_LIST_ELEM(LIST_NEXT(x)));
+ ASSERT(IS_LIST_ELEM(BF_LIST_NEXT(x)));
#ifdef HARD_DEBUG
check_tree(root, 0, 0);
#endif
- replace(root, x, LIST_NEXT(x));
+ replace(root, x, BF_LIST_NEXT(x));
#ifdef HARD_DEBUG
check_tree(bfallctr, 0);
@@ -834,7 +835,7 @@ bf_get_free_block(Allctr_t *allctr, Uint size,
x = x->left;
}
}
-
+
if (!blk)
return NULL;
@@ -853,7 +854,7 @@ bf_get_free_block(Allctr_t *allctr, Uint size,
/* Use next block if it exist in order to avoid replacing
the tree node */
- blk = LIST_NEXT(blk) ? LIST_NEXT(blk) : blk;
+ blk = BF_LIST_NEXT(blk) ? BF_LIST_NEXT(blk) : blk;
bf_unlink_free_block(allctr, (Block_t *) blk);
return (Block_t *) blk;
@@ -938,7 +939,7 @@ info_options(Allctr_t *allctr,
}
if (hpp || szp) {
-
+
if (!atoms_initialized)
erts_exit(ERTS_ERROR_EXIT, "%s:%d: Internal error: Atoms not initialized",
__FILE__, __LINE__);;
@@ -969,12 +970,12 @@ erts_bfalc_test(UWord op, UWord a1, UWord a2)
case 0x202: return (UWord) ((RBTree_t *) a1)->parent;
case 0x203: return (UWord) ((RBTree_t *) a1)->left;
case 0x204: return (UWord) ((RBTree_t *) a1)->right;
- case 0x205: return (UWord) LIST_NEXT(a1);
+ case 0x205: return (UWord) BF_LIST_NEXT(a1);
case 0x206: return (UWord) IS_BLACK((RBTree_t *) a1);
case 0x207: return (UWord) IS_TREE_NODE((RBTree_t *) a1);
case 0x208: return (UWord) 1; /* IS_BF_ALGO */
case 0x20a: return (UWord) !((BFAllctr_t *) a1)->address_order; /* IS_BF */
- case 0x20b: return (UWord) LIST_PREV(a1);
+ case 0x20b: return (UWord) BF_LIST_PREV(a1);
default: ASSERT(0); return ~((UWord) 0);
}
}
diff --git a/erts/emulator/beam/erl_bif_binary.c b/erts/emulator/beam/erl_bif_binary.c
index a2610bf2e1..4d6d31cd76 100644
--- a/erts/emulator/beam/erl_bif_binary.c
+++ b/erts/emulator/beam/erl_bif_binary.c
@@ -208,8 +208,8 @@ typedef struct _ac_trie {
typedef struct _bm_data {
byte *x;
Sint len;
+ Sint *badshift;
Sint *goodshift;
- Sint badshift[ALPHABET_SIZE];
} BMData;
typedef struct _ac_find_all_state {
@@ -319,16 +319,104 @@ static void dump_ac_node(ACNode *node, int indent, int ch);
* The needed size of binary data for a search structure - given the
* accumulated string lengths.
*/
-#define BM_SIZE(StrLen) /* StrLen: length of searchstring */ \
-((MYALIGN(sizeof(Sint) * (StrLen))) + /* goodshift array */ \
- MYALIGN(StrLen) + /* searchstring saved */ \
- (MYALIGN(sizeof(BMData)))) /* Structure */
+#define BM_SIZE_SINGLE() /* Single byte search string */ \
+(MYALIGN(1) + /* searchstring saved */ \
+ (MYALIGN(sizeof(BMData)))) /* Structure */
+
+#define BM_SIZE_MULTI(StrLen) /* StrLen: length of searchstring */ \
+((MYALIGN(sizeof(Uint) * (StrLen))) + /* goodshift array */ \
+ (MYALIGN(sizeof(Uint) * ALPHABET_SIZE)) + /* badshift array */ \
+ MYALIGN(StrLen) + /* searchstring saved */ \
+ (MYALIGN(sizeof(BMData)))) /* Structure */
#define AC_SIZE(StrLens) /* StrLens: sum of all searchstring lengths */ \
((MYALIGN(sizeof(ACNode)) * \
((StrLens)+1)) + /* The actual nodes (including rootnode) */ \
MYALIGN(sizeof(ACTrie))) /* Structure */
+/*
+ * Boyer Moore - most obviously implemented more or less exactly as
+ * Christian Charras and Thierry Lecroq describe it in "Handbook of
+ * Exact String-Matching Algorithms"
+ * http://www-igm.univ-mlv.fr/~lecroq/string/
+ */
+
+/*
+ * Call this to compute badshifts array
+ */
+static void compute_badshifts(BMData *bmd)
+{
+ Sint i;
+ Sint m = bmd->len;
+
+ for (i = 0; i < ALPHABET_SIZE; ++i) {
+ bmd->badshift[i] = m;
+ }
+ for (i = 0; i < m - 1; ++i) {
+ bmd->badshift[bmd->x[i]] = m - i - 1;
+ }
+}
+
+/* Helper for "compute_goodshifts" */
+static void compute_suffixes(byte *x, Sint m, Sint *suffixes)
+{
+ int f,g,i;
+
+ suffixes[m - 1] = m;
+
+ f = 0; /* To avoid use before set warning */
+
+ g = m - 1;
+
+ for (i = m - 2; i >= 0; --i) {
+ if (i > g && suffixes[i + m - 1 - f] < i - g) {
+ suffixes[i] = suffixes[i + m - 1 - f];
+ } else {
+ if (i < g) {
+ g = i;
+ }
+ f = i;
+ while ( g >= 0 && x[g] == x[g + m - 1 - f] ) {
+ --g;
+ }
+ suffixes[i] = f - g;
+ }
+ }
+}
+
+/*
+ * Call this to compute goodshift array
+ */
+static void compute_goodshifts(BMData *bmd)
+{
+ Sint m = bmd->len;
+ byte *x = bmd->x;
+ Sint i, j;
+ Sint *suffixes = erts_alloc(ERTS_ALC_T_TMP, m * sizeof(Sint));
+
+ compute_suffixes(x, m, suffixes);
+
+ for (i = 0; i < m; ++i) {
+ bmd->goodshift[i] = m;
+ }
+
+ j = 0;
+
+ for (i = m - 1; i >= -1; --i) {
+ if (i == -1 || suffixes[i] == i + 1) {
+ while (j < m - 1 - i) {
+ if (bmd->goodshift[j] == m) {
+ bmd->goodshift[j] = m - 1 - i;
+ }
+ ++j;
+ }
+ }
+ }
+ for (i = 0; i <= m - 2; ++i) {
+ bmd->goodshift[m - 1 - suffixes[i]] = m - 1 - i;
+ }
+ erts_free(ERTS_ALC_T_TMP, suffixes);
+}
/*
* Callback for the magic binary
@@ -377,20 +465,37 @@ static ACTrie *create_acdata(MyAllocator *my, Uint len,
/*
* The same initialization of allocator and basic data for Boyer-Moore.
+ * For single byte, we don't use goodshift and badshift, only memchr.
*/
static BMData *create_bmdata(MyAllocator *my, byte *x, Uint len,
Binary **the_bin /* out */)
{
- Uint datasize = BM_SIZE(len);
+ Uint datasize;
BMData *bmd;
- Binary *mb = erts_create_magic_binary(datasize,cleanup_my_data_bm);
- byte *data = ERTS_MAGIC_BIN_DATA(mb);
+ Binary *mb;
+ byte *data;
+
+ if(len > 1) {
+ datasize = BM_SIZE_MULTI(len);
+ } else {
+ datasize = BM_SIZE_SINGLE();
+ }
+
+ mb = erts_create_magic_binary(datasize,cleanup_my_data_bm);
+ data = ERTS_MAGIC_BIN_DATA(mb);
init_my_allocator(my, datasize, data);
bmd = my_alloc(my, sizeof(BMData));
bmd->x = my_alloc(my,len);
sys_memcpy(bmd->x,x,len);
bmd->len = len;
- bmd->goodshift = my_alloc(my,sizeof(Uint) * len);
+
+ if(len > 1) {
+ bmd->goodshift = my_alloc(my, sizeof(Uint) * len);
+ bmd->badshift = my_alloc(my, sizeof(Uint) * ALPHABET_SIZE);
+ compute_badshifts(bmd);
+ compute_goodshifts(bmd);
+ }
+
*the_bin = mb;
return bmd;
}
@@ -711,91 +816,8 @@ static BFReturn ac_find_all_non_overlapping(BinaryFindContext *ctx, byte *haysta
return (m == 0) ? BF_NOT_FOUND : BF_OK;
}
-/*
- * Boyer Moore - most obviously implemented more or less exactly as
- * Christian Charras and Thierry Lecroq describe it in "Handbook of
- * Exact String-Matching Algorithms"
- * http://www-igm.univ-mlv.fr/~lecroq/string/
- */
-
-/*
- * Call this to compute badshifts array
- */
-static void compute_badshifts(BMData *bmd)
-{
- Sint i;
- Sint m = bmd->len;
-
- for (i = 0; i < ALPHABET_SIZE; ++i) {
- bmd->badshift[i] = m;
- }
- for (i = 0; i < m - 1; ++i) {
- bmd->badshift[bmd->x[i]] = m - i - 1;
- }
-}
-
-/* Helper for "compute_goodshifts" */
-static void compute_suffixes(byte *x, Sint m, Sint *suffixes)
-{
- int f,g,i;
-
- suffixes[m - 1] = m;
-
- f = 0; /* To avoid use before set warning */
-
- g = m - 1;
-
- for (i = m - 2; i >= 0; --i) {
- if (i > g && suffixes[i + m - 1 - f] < i - g) {
- suffixes[i] = suffixes[i + m - 1 - f];
- } else {
- if (i < g) {
- g = i;
- }
- f = i;
- while ( g >= 0 && x[g] == x[g + m - 1 - f] ) {
- --g;
- }
- suffixes[i] = f - g;
- }
- }
-}
-
-/*
- * Call this to compute goodshift array
- */
-static void compute_goodshifts(BMData *bmd)
-{
- Sint m = bmd->len;
- byte *x = bmd->x;
- Sint i, j;
- Sint *suffixes = erts_alloc(ERTS_ALC_T_TMP, m * sizeof(Sint));
-
- compute_suffixes(x, m, suffixes);
-
- for (i = 0; i < m; ++i) {
- bmd->goodshift[i] = m;
- }
-
- j = 0;
-
- for (i = m - 1; i >= -1; --i) {
- if (i == -1 || suffixes[i] == i + 1) {
- while (j < m - 1 - i) {
- if (bmd->goodshift[j] == m) {
- bmd->goodshift[j] = m - 1 - i;
- }
- ++j;
- }
- }
- }
- for (i = 0; i <= m - 2; ++i) {
- bmd->goodshift[m - 1 - suffixes[i]] = m - 1 - i;
- }
- erts_free(ERTS_ALC_T_TMP, suffixes);
-}
-
#define BM_LOOP_FACTOR 10 /* Should we have a higher value? */
+#define MC_LOOP_FACTOR 8
static void bm_init_find_first_match(BinaryFindContext *ctx)
{
@@ -819,13 +841,38 @@ static BFReturn bm_find_first_match(BinaryFindContext *ctx, byte *haystack)
Sint i;
Sint j = state->pos;
register Uint reds = *reductions;
+ byte *pos_pointer;
+ Sint needle_last = blen - 1;
+ Sint mem_read = len - needle_last - j;
+
+ if (mem_read <= 0) {
+ return BF_NOT_FOUND;
+ }
+ mem_read = MIN(mem_read, reds * MC_LOOP_FACTOR);
+ ASSERT(mem_read > 0);
+
+ pos_pointer = memchr(&haystack[j + needle_last], needle[needle_last], mem_read);
+ if (pos_pointer == NULL) {
+ reds -= mem_read / MC_LOOP_FACTOR;
+ j += mem_read;
+ } else {
+ reds -= (pos_pointer - &haystack[j]) / MC_LOOP_FACTOR;
+ j = pos_pointer - haystack - needle_last;
+ }
+
+ // Ensure we have at least one reduction before entering the loop
+ ++reds;
- while (j <= len - blen) {
+ for(;;) {
+ if (j > len - blen) {
+ *reductions = reds;
+ return BF_NOT_FOUND;
+ }
if (--reds == 0) {
state->pos = j;
return BF_RESTART;
}
- for (i = blen - 1; i >= 0 && needle[i] == haystack[i + j]; --i)
+ for (i = needle_last; i >= 0 && needle[i] == haystack[i + j]; --i)
;
if (i < 0) { /* found */
*reductions = reds;
@@ -835,8 +882,6 @@ static BFReturn bm_find_first_match(BinaryFindContext *ctx, byte *haystack)
}
j += MAX(gs[i],bs[haystack[i+j]] - blen + 1 + i);
}
- *reductions = reds;
- return BF_NOT_FOUND;
}
static void bm_init_find_all(BinaryFindContext *ctx)
@@ -875,14 +920,38 @@ static BFReturn bm_find_all_non_overlapping(BinaryFindContext *ctx, byte *haysta
Sint *gs = bmd->goodshift;
Sint *bs = bmd->badshift;
byte *needle = bmd->x;
- Sint i;
+ Sint i = -1; /* Use memchr on start and on every match */
Sint j = state->pos;
Uint m = state->m;
Uint allocated = state->allocated;
FindallData *out = state->out;
register Uint reds = *reductions;
+ byte *pos_pointer;
+ Sint needle_last = blen - 1;
+ Sint mem_read;
- while (j <= len - blen) {
+ for(;;) {
+ if (i < 0) {
+ mem_read = len - needle_last - j;
+ if(mem_read <= 0) {
+ goto done;
+ }
+ mem_read = MIN(mem_read, reds * MC_LOOP_FACTOR);
+ ASSERT(mem_read > 0);
+ pos_pointer = memchr(&haystack[j + needle_last], needle[needle_last], mem_read);
+ if (pos_pointer == NULL) {
+ reds -= mem_read / MC_LOOP_FACTOR;
+ j += mem_read;
+ } else {
+ reds -= (pos_pointer - &haystack[j]) / MC_LOOP_FACTOR;
+ j = pos_pointer - haystack - needle_last;
+ }
+ // Ensure we have at least one reduction when resuming the loop
+ ++reds;
+ }
+ if (j > len - blen) {
+ goto done;
+ }
if (--reds == 0) {
state->pos = j;
state->m = m;
@@ -890,7 +959,7 @@ static BFReturn bm_find_all_non_overlapping(BinaryFindContext *ctx, byte *haysta
state->out = out;
return BF_RESTART;
}
- for (i = blen - 1; i >= 0 && needle[i] == haystack[i + j]; --i)
+ for (i = needle_last; i >= 0 && needle[i] == haystack[i + j]; --i)
;
if (i < 0) { /* found */
if (m >= allocated) {
@@ -912,6 +981,7 @@ static BFReturn bm_find_all_non_overlapping(BinaryFindContext *ctx, byte *haysta
j += MAX(gs[i],bs[haystack[i+j]] - blen + 1 + i);
}
}
+ done:
state->m = m;
state->out = out;
*reductions = reds;
@@ -931,6 +1001,7 @@ static int do_binary_match_compile(Eterm argument, Eterm *tag, Binary **binp)
Eterm t, b, comp_term = NIL;
Uint characters;
Uint words;
+ Uint size;
characters = 0;
words = 0;
@@ -946,11 +1017,12 @@ static int do_binary_match_compile(Eterm argument, Eterm *tag, Binary **binp)
if (binary_bitsize(b) != 0) {
goto badarg;
}
- if (binary_size(b) == 0) {
+ size = binary_size(b);
+ if (size == 0) {
goto badarg;
}
++words;
- characters += binary_size(b);
+ characters += size;
}
if (is_not_nil(t)) {
goto badarg;
@@ -979,16 +1051,13 @@ static int do_binary_match_compile(Eterm argument, Eterm *tag, Binary **binp)
Uint bitoffs, bitsize;
byte *temp_alloc = NULL;
MyAllocator my;
- BMData *bmd;
Binary *bin;
ERTS_GET_BINARY_BYTES(comp_term, bytes, bitoffs, bitsize);
if (bitoffs != 0) {
bytes = erts_get_aligned_binary_bytes(comp_term, &temp_alloc);
}
- bmd = create_bmdata(&my, bytes, characters, &bin);
- compute_badshifts(bmd);
- compute_goodshifts(bmd);
+ create_bmdata(&my, bytes, characters, &bin);
erts_free_aligned_binary_bytes(temp_alloc);
CHECK_ALLOCATOR(my);
*tag = am_bm;
@@ -1901,9 +1970,7 @@ BIF_RETTYPE erts_binary_part(Process *p, Eterm binary, Eterm epos, Eterm elen)
goto badarg;
}
-
-
- hp = HAlloc(p, ERL_SUB_BIN_SIZE);
+ hp = HeapFragOnlyAlloc(p, ERL_SUB_BIN_SIZE);
ERTS_GET_REAL_BIN(binary, orig, offset, bit_offset, bit_size);
sb = (ErlSubBin *) hp;
@@ -1921,100 +1988,6 @@ BIF_RETTYPE erts_binary_part(Process *p, Eterm binary, Eterm epos, Eterm elen)
BIF_ERROR(p, BADARG);
}
-#define ERTS_NEED_GC(p, need) ((HEAP_LIMIT((p)) - HEAP_TOP((p))) <= (need))
-
-BIF_RETTYPE erts_gc_binary_part(Process *p, Eterm *reg, Eterm live, int range_is_tuple)
-{
- Uint pos;
- Sint len;
- size_t orig_size;
- Eterm orig;
- Uint offset;
- Uint bit_offset;
- Uint bit_size;
- Eterm* hp;
- ErlSubBin* sb;
- Eterm binary;
- Eterm *tp;
- Eterm epos, elen;
- int extra_args;
-
-
- if (range_is_tuple) {
- Eterm tpl = reg[live];
- extra_args = 1;
- if (is_not_tuple(tpl)) {
- goto badarg;
- }
- tp = tuple_val(tpl);
- if (arityval(*tp) != 2) {
- goto badarg;
- }
-
- epos = tp[1];
- elen = tp[2];
- } else {
- extra_args = 2;
- epos = reg[live-1];
- elen = reg[live];
- }
- binary = reg[live-extra_args];
-
- if (is_not_binary(binary)) {
- goto badarg;
- }
- if (!term_to_Uint(epos, &pos)) {
- goto badarg;
- }
- if (!term_to_Sint(elen, &len)) {
- goto badarg;
- }
- if (len < 0) {
- Uint lentmp = -(Uint)len;
- /* overflow */
- if ((Sint)lentmp < 0) {
- goto badarg;
- }
- len = lentmp;
- if (len > pos) {
- goto badarg;
- }
- pos -= len;
- }
- /* overflow */
- if ((pos + len) < pos || (len > 0 && (pos + len) == pos)) {
- goto badarg;
- }
- if ((orig_size = binary_size(binary)) < pos ||
- orig_size < (pos + len)) {
- goto badarg;
- }
-
- if (ERTS_NEED_GC(p, ERL_SUB_BIN_SIZE)) {
- erts_garbage_collect(p, ERL_SUB_BIN_SIZE, reg, live+1-extra_args); /* I don't need the tuple
- or indices any more */
- binary = reg[live-extra_args];
- }
-
- hp = p->htop;
- p->htop += ERL_SUB_BIN_SIZE;
-
- ERTS_GET_REAL_BIN(binary, orig, offset, bit_offset, bit_size);
-
- sb = (ErlSubBin *) hp;
- sb->thing_word = HEADER_SUB_BIN;
- sb->size = len;
- sb->offs = offset + pos;
- sb->orig = orig;
- sb->bitoffs = bit_offset;
- sb->bitsize = 0;
- sb->is_writable = 0;
-
- BIF_RET(make_binary(sb));
-
- badarg:
- BIF_ERROR(p, BADARG);
-}
/*************************************************************
* The actual guard BIFs are in erl_bif_guard.c
* but the implementation of both the non-gc and the gc
@@ -2762,7 +2735,7 @@ static BIF_RETTYPE do_encode_unsigned(Process *p, Eterm uns, Eterm endianess)
dsize_t num_parts = BIG_SIZE(bigp);
Eterm res;
byte *b;
- ErtsDigit d;
+ ErtsDigit d = 0;
if(BIG_SIGN(bigp)) {
goto badarg;
@@ -2778,26 +2751,22 @@ static BIF_RETTYPE do_encode_unsigned(Process *p, Eterm uns, Eterm endianess)
if (endianess == am_big) {
Sint i,j;
j = 0;
- d = BIG_DIGIT(bigp,0);
for (i=n-1;i>=0;--i) {
- b[i] = d & 0xFF;
- if (!((++j) % sizeof(ErtsDigit))) {
+ if (!((j++) % sizeof(ErtsDigit))) {
d = BIG_DIGIT(bigp,j / sizeof(ErtsDigit));
- } else {
- d >>= 8;
}
+ b[i] = d & 0xFF;
+ d >>= 8;
}
} else {
Sint i,j;
j = 0;
- d = BIG_DIGIT(bigp,0);
for (i=0;i<n;++i) {
- b[i] = d & 0xFF;
- if (!((++j) % sizeof(ErtsDigit))) {
+ if (!((j++) % sizeof(ErtsDigit))) {
d = BIG_DIGIT(bigp,j / sizeof(ErtsDigit));
- } else {
- d >>= 8;
}
+ b[i] = d & 0xFF;
+ d >>= 8;
}
}
@@ -3012,17 +2981,19 @@ static void dump_bm_data(BMData *bm)
}
}
erts_printf(">>\n");
- erts_printf("GoodShift array:\n");
- for (i = 0; i < bm->len; ++i) {
- erts_printf("GoodShift[%d]: %ld\n", i, bm->goodshift[i]);
- }
- erts_printf("BadShift array:\n");
- j = 0;
- for (i = 0; i < ALPHABET_SIZE; i += j) {
- for (j = 0; i + j < ALPHABET_SIZE && j < 6; ++j) {
- erts_printf("BS[%03d]:%02ld, ", i+j, bm->badshift[i+j]);
+ if(bm->len > 1) {
+ erts_printf("GoodShift array:\n");
+ for (i = 0; i < bm->len; ++i) {
+ erts_printf("GoodShift[%d]: %ld\n", i, bm->goodshift[i]);
+ }
+ erts_printf("BadShift array:\n");
+ j = 0;
+ for (i = 0; i < ALPHABET_SIZE; i += j) {
+ for (j = 0; i + j < ALPHABET_SIZE && j < 6; ++j) {
+ erts_printf("BS[%03d]:%02ld, ", i+j, bm->badshift[i+j]);
+ }
+ erts_printf("\n");
}
- erts_printf("\n");
}
}
diff --git a/erts/emulator/beam/erl_bif_ddll.c b/erts/emulator/beam/erl_bif_ddll.c
index 4cda0948a0..639aee29dc 100644
--- a/erts/emulator/beam/erl_bif_ddll.c
+++ b/erts/emulator/beam/erl_bif_ddll.c
@@ -829,7 +829,7 @@ BIF_RETTYPE erl_ddll_format_error_int_1(BIF_ALIST_1)
"cannot be loaded/unloaded";
break;
case am_permanent:
- errstring = "DDLL driver is permanent an can not be unloaded/loaded";
+ errstring = "DDLL driver is permanent an cannot be unloaded/loaded";
break;
case am_not_loaded:
errstring = "DDLL driver is not loaded";
diff --git a/erts/emulator/beam/erl_bif_guard.c b/erts/emulator/beam/erl_bif_guard.c
index 8a5c6ada6c..c921b66a7e 100644
--- a/erts/emulator/beam/erl_bif_guard.c
+++ b/erts/emulator/beam/erl_bif_guard.c
@@ -19,7 +19,12 @@
*/
/*
- * Numeric guard BIFs.
+ * This file implements the former GC BIFs. They used to do a GC when
+ * they needed heap space. Because of changes to the implementation of
+ * literals, those BIFs are now allowed to allocate heap fragments
+ * (using HeapFragOnlyAlloc()). Note that they must NOT call HAlloc(),
+ * because the caller does not do any SWAPIN / SWAPOUT (that is,
+ * HEAP_TOP(p) and HEAP_LIMIT(p) contain stale values).
*/
#ifdef HAVE_CONFIG_H
@@ -36,14 +41,16 @@
#include "erl_binary.h"
#include "erl_map.h"
-static Eterm gc_double_to_integer(Process* p, double x, Eterm* reg, Uint live);
-
static Eterm double_to_integer(Process* p, double x);
+static BIF_RETTYPE erlang_length_trap(BIF_ALIST_3);
+static Export erlang_length_export;
-/*
- * Guard BIFs called using apply/3 and guard BIFs that never build
- * anything on the heap.
- */
+void erts_init_bif_guard(void)
+{
+ erts_init_trap_export(&erlang_length_export,
+ am_erlang, am_length, 3,
+ &erlang_length_trap);
+}
BIF_RETTYPE abs_1(BIF_ALIST_1)
{
@@ -56,7 +63,7 @@ BIF_RETTYPE abs_1(BIF_ALIST_1)
i0 = signed_val(BIF_ARG_1);
i = ERTS_SMALL_ABS(i0);
if (i0 == MIN_SMALL) {
- hp = HAlloc(BIF_P, BIG_UINT_HEAP_SIZE);
+ hp = HeapFragOnlyAlloc(BIF_P, BIG_UINT_HEAP_SIZE);
BIF_RET(uint_to_big(i, hp));
} else {
BIF_RET(make_small(i));
@@ -68,7 +75,7 @@ BIF_RETTYPE abs_1(BIF_ALIST_1)
int sz = big_arity(BIF_ARG_1) + 1;
Uint* x;
- hp = HAlloc(BIF_P, sz); /* See note at beginning of file */
+ hp = HeapFragOnlyAlloc(BIF_P, sz); /* See note at beginning of file */
sz--;
res = make_big(hp);
x = big_val(BIF_ARG_1);
@@ -83,7 +90,7 @@ BIF_RETTYPE abs_1(BIF_ALIST_1)
GET_DOUBLE(BIF_ARG_1, f);
if (f.fd < 0.0) {
- hp = HAlloc(BIF_P, FLOAT_SIZE_OBJECT);
+ hp = HeapFragOnlyAlloc(BIF_P, FLOAT_SIZE_OBJECT);
f.fd = fabs(f.fd);
res = make_float(hp);
PUT_DOUBLE(f, hp);
@@ -116,7 +123,7 @@ BIF_RETTYPE float_1(BIF_ALIST_1)
} else if (big_to_double(BIF_ARG_1, &f.fd) < 0) {
goto badarg;
}
- hp = HAlloc(BIF_P, FLOAT_SIZE_OBJECT);
+ hp = HeapFragOnlyAlloc(BIF_P, FLOAT_SIZE_OBJECT);
res = make_float(hp);
PUT_DOUBLE(f, hp);
BIF_RET(res);
@@ -194,26 +201,113 @@ BIF_RETTYPE round_1(BIF_ALIST_1)
BIF_RET(res);
}
+/*
+ * This version of length/1 is called from native code and apply/3.
+ */
+
BIF_RETTYPE length_1(BIF_ALIST_1)
{
+ Eterm args[3];
+
+ /*
+ * Arrange argument registers the way expected by
+ * erts_trapping_length_1(). We save the original argument in
+ * args[2] in case an error should signaled.
+ */
+
+ args[0] = BIF_ARG_1;
+ args[1] = make_small(0);
+ args[2] = BIF_ARG_1;
+ return erlang_length_trap(BIF_P, args, A__I);
+}
+
+static BIF_RETTYPE erlang_length_trap(BIF_ALIST_3)
+{
+ Eterm res;
+
+ res = erts_trapping_length_1(BIF_P, BIF__ARGS);
+ if (is_value(res)) { /* Success. */
+ BIF_RET(res);
+ } else { /* Trap or error. */
+ if (BIF_P->freason == TRAP) {
+ /*
+ * The available reductions were exceeded. Trap.
+ */
+ BIF_TRAP3(&erlang_length_export, BIF_P, BIF_ARG_1, BIF_ARG_2, BIF_ARG_3);
+ } else {
+ /*
+ * Signal an error. The original argument was tucked away in BIF_ARG_3.
+ */
+ ERTS_BIF_ERROR_TRAPPED1(BIF_P, BIF_P->freason,
+ bif_export[BIF_length_1], BIF_ARG_3);
+ }
+ }
+}
+
+/*
+ * Trappable helper function for calculating length/1.
+ *
+ * When calling this function, entries in args[] should be set up as
+ * follows:
+ *
+ * args[0] = List to calculate length for.
+ * args[1] = Length accumulator (tagged integer).
+ *
+ * If the return value is a tagged integer, the length was calculated
+ * successfully.
+ *
+ * Otherwise, if return value is THE_NON_VALUE and p->freason is TRAP,
+ * the available reductions were exceeded and this function must be called
+ * again after rescheduling. args[0] and args[1] have been updated to
+ * contain the next part of the list and length so far, respectively.
+ *
+ * Otherwise, if return value is THE_NON_VALUE, the list did not end
+ * in an empty list (and p->freason is BADARG).
+ */
+
+Eterm erts_trapping_length_1(Process* p, Eterm* args)
+{
Eterm list;
Uint i;
-
- if (is_nil(BIF_ARG_1))
- BIF_RET(SMALL_ZERO);
- if (is_not_list(BIF_ARG_1)) {
- BIF_ERROR(BIF_P, BADARG);
- }
- list = BIF_ARG_1;
- i = 0;
- while (is_list(list)) {
- i++;
+ Uint max_iter;
+ Uint saved_max_iter;
+
+#if defined(DEBUG) || defined(VALGRIND)
+ max_iter = 50;
+#else
+ max_iter = ERTS_BIF_REDS_LEFT(p) * 16;
+#endif
+ saved_max_iter = max_iter;
+ ASSERT(max_iter > 0);
+
+ list = args[0];
+ i = unsigned_val(args[1]);
+ while (is_list(list) && max_iter != 0) {
list = CDR(list_val(list));
+ i++, max_iter--;
+ }
+
+ if (is_list(list)) {
+ /*
+ * We have exceeded the alloted number of iterations.
+ * Save the result so far and signal a trap.
+ */
+ args[0] = list;
+ args[1] = make_small(i);
+ p->freason = TRAP;
+ BUMP_ALL_REDS(p);
+ return THE_NON_VALUE;
+ } else if (is_not_nil(list)) {
+ /* Error. Should be NIL. */
+ BIF_ERROR(p, BADARG);
}
- if (is_not_nil(list)) {
- BIF_ERROR(BIF_P, BADARG);
- }
- BIF_RET(make_small(i));
+
+ /*
+ * We reached the end of the list successfully. Bump reductions
+ * and return result.
+ */
+ BUMP_REDS(p, saved_max_iter / 16);
+ return make_small(i);
}
/* returns the size of a tuple or a binary */
@@ -229,7 +323,7 @@ BIF_RETTYPE size_1(BIF_ALIST_1)
if (IS_USMALL(0, sz)) {
return make_small(sz);
} else {
- Eterm* hp = HAlloc(BIF_P, BIG_UINT_HEAP_SIZE);
+ Eterm* hp = HeapFragOnlyAlloc(BIF_P, BIG_UINT_HEAP_SIZE);
BIF_RET(uint_to_big(sz, hp));
}
}
@@ -252,12 +346,12 @@ BIF_RETTYPE bit_size_1(BIF_ALIST_1)
if (IS_USMALL(0,low_bits)) {
BIF_RET(make_small(low_bits));
} else {
- Eterm* hp = HAlloc(BIF_P, BIG_UINT_HEAP_SIZE);
+ Eterm* hp = HeapFragOnlyAlloc(BIF_P, BIG_UINT_HEAP_SIZE);
BIF_RET(uint_to_big(low_bits, hp));
}
} else {
Uint sz = BIG_UINT_HEAP_SIZE+1;
- Eterm* hp = HAlloc(BIF_P, sz);
+ Eterm* hp = HeapFragOnlyAlloc(BIF_P, sz);
hp[0] = make_pos_bignum_header(sz-1);
BIG_DIGIT(hp,0) = low_bits;
BIG_DIGIT(hp,1) = high_bits;
@@ -281,7 +375,7 @@ BIF_RETTYPE byte_size_1(BIF_ALIST_1)
if (IS_USMALL(0, bytesize)) {
BIF_RET(make_small(bytesize));
} else {
- Eterm* hp = HAlloc(BIF_P, BIG_UINT_HEAP_SIZE);
+ Eterm* hp = HeapFragOnlyAlloc(BIF_P, BIG_UINT_HEAP_SIZE);
BIF_RET(uint_to_big(bytesize, hp));
}
} else {
@@ -325,7 +419,7 @@ double_to_integer(Process* p, double x)
}
sz = BIG_NEED_SIZE(ds); /* number of words including arity */
- hp = HAlloc(p, sz);
+ hp = HeapFragOnlyAlloc(p, sz);
res = make_big(hp);
xp = (ErtsDigit*) (hp + 1);
@@ -371,389 +465,3 @@ BIF_RETTYPE binary_part_2(BIF_ALIST_2)
badarg:
BIF_ERROR(BIF_P,BADARG);
}
-
-
-/*
- * The following code is used when a guard that may build on the
- * heap is called directly. They must not use HAlloc(), but must
- * do a garbage collection if there is insufficient heap space.
- *
- * Important note: All error checking MUST be done before doing
- * a garbage collection. The compiler assumes that all registers
- * are still valid if a guard BIF generates an exception.
- */
-
-#define ERTS_NEED_GC(p, need) ((HEAP_LIMIT((p)) - HEAP_TOP((p))) <= (need))
-
-Eterm erts_gc_length_1(Process* p, Eterm* reg, Uint live)
-{
- Eterm list = reg[live];
- int i;
-
- if (is_nil(list))
- return SMALL_ZERO;
- i = 0;
- while (is_list(list)) {
- i++;
- list = CDR(list_val(list));
- }
- if (is_not_nil(list)) {
- BIF_ERROR(p, BADARG);
- }
- return make_small(i);
-}
-
-Eterm erts_gc_size_1(Process* p, Eterm* reg, Uint live)
-{
- Eterm arg = reg[live];
- if (is_tuple(arg)) {
- Eterm* tupleptr = tuple_val(arg);
- return make_small(arityval(*tupleptr));
- } else if (is_binary(arg)) {
- Uint sz = binary_size(arg);
- if (IS_USMALL(0, sz)) {
- return make_small(sz);
- } else {
- Eterm* hp;
- if (ERTS_NEED_GC(p, BIG_UINT_HEAP_SIZE)) {
- erts_garbage_collect(p, BIG_UINT_HEAP_SIZE, reg, live);
- }
- hp = p->htop;
- p->htop += BIG_UINT_HEAP_SIZE;
- return uint_to_big(sz, hp);
- }
- }
- BIF_ERROR(p, BADARG);
-}
-
-Eterm erts_gc_bit_size_1(Process* p, Eterm* reg, Uint live)
-{
- Eterm arg = reg[live];
- if (is_binary(arg)) {
- Uint low_bits;
- Uint bytesize;
- Uint high_bits;
- bytesize = binary_size(arg);
- high_bits = bytesize >> ((sizeof(Uint) * 8)-3);
- low_bits = (bytesize << 3) + binary_bitsize(arg);
- if (high_bits == 0) {
- if (IS_USMALL(0,low_bits)) {
- return make_small(low_bits);
- } else {
- Eterm* hp;
- if (ERTS_NEED_GC(p, BIG_UINT_HEAP_SIZE)) {
- erts_garbage_collect(p, BIG_UINT_HEAP_SIZE, reg, live);
- }
- hp = p->htop;
- p->htop += BIG_UINT_HEAP_SIZE;
- return uint_to_big(low_bits, hp);
- }
- } else {
- Uint sz = BIG_UINT_HEAP_SIZE+1;
- Eterm* hp;
- if (ERTS_NEED_GC(p, sz)) {
- erts_garbage_collect(p, sz, reg, live);
- }
- hp = p->htop;
- p->htop += sz;
- hp[0] = make_pos_bignum_header(sz-1);
- BIG_DIGIT(hp,0) = low_bits;
- BIG_DIGIT(hp,1) = high_bits;
- return make_big(hp);
- }
- } else {
- BIF_ERROR(p, BADARG);
- }
-}
-
-Eterm erts_gc_byte_size_1(Process* p, Eterm* reg, Uint live)
-{
- Eterm arg = reg[live];
- if (is_binary(arg)) {
- Uint bytesize = binary_size(arg);
- if (binary_bitsize(arg) > 0) {
- bytesize++;
- }
- if (IS_USMALL(0, bytesize)) {
- return make_small(bytesize);
- } else {
- Eterm* hp;
- if (ERTS_NEED_GC(p, BIG_UINT_HEAP_SIZE)) {
- erts_garbage_collect(p, BIG_UINT_HEAP_SIZE, reg, live);
- }
- hp = p->htop;
- p->htop += BIG_UINT_HEAP_SIZE;
- return uint_to_big(bytesize, hp);
- }
- } else {
- BIF_ERROR(p, BADARG);
- }
-}
-
-Eterm erts_gc_map_size_1(Process* p, Eterm* reg, Uint live)
-{
- Eterm arg = reg[live];
- if (is_flatmap(arg)) {
- flatmap_t *mp = (flatmap_t*)flatmap_val(arg);
- return make_small(flatmap_get_size(mp));
- } else if (is_hashmap(arg)) {
- Eterm* hp;
- Uint size;
- size = hashmap_size(arg);
- if (IS_USMALL(0, size)) {
- return make_small(size);
- }
- if (ERTS_NEED_GC(p, BIG_UINT_HEAP_SIZE)) {
- erts_garbage_collect(p, BIG_UINT_HEAP_SIZE, reg, live);
- }
- hp = p->htop;
- p->htop += BIG_UINT_HEAP_SIZE;
- return uint_to_big(size, hp);
- }
- p->fvalue = arg;
- BIF_ERROR(p, BADMAP);
-}
-
-Eterm erts_gc_abs_1(Process* p, Eterm* reg, Uint live)
-{
- Eterm arg;
- Eterm res;
- Sint i0, i;
- Eterm* hp;
-
- arg = reg[live];
-
- /* integer arguments */
- if (is_small(arg)) {
- i0 = signed_val(arg);
- i = ERTS_SMALL_ABS(i0);
- if (i0 == MIN_SMALL) {
- if (ERTS_NEED_GC(p, BIG_UINT_HEAP_SIZE)) {
- erts_garbage_collect(p, BIG_UINT_HEAP_SIZE, reg, live+1);
- arg = reg[live];
- }
- hp = p->htop;
- p->htop += BIG_UINT_HEAP_SIZE;
- return uint_to_big(i, hp);
- } else {
- return make_small(i);
- }
- } else if (is_big(arg)) {
- if (!big_sign(arg)) {
- return arg;
- } else {
- int sz = big_arity(arg) + 1;
- Uint* x;
-
- if (ERTS_NEED_GC(p, sz)) {
- erts_garbage_collect(p, sz, reg, live+1);
- arg = reg[live];
- }
- hp = p->htop;
- p->htop += sz;
- sz--;
- res = make_big(hp);
- x = big_val(arg);
- *hp++ = make_pos_bignum_header(sz);
- x++; /* skip thing */
- while(sz--)
- *hp++ = *x++;
- return res;
- }
- } else if (is_float(arg)) {
- FloatDef f;
-
- GET_DOUBLE(arg, f);
- if (f.fd < 0.0) {
- if (ERTS_NEED_GC(p, FLOAT_SIZE_OBJECT)) {
- erts_garbage_collect(p, FLOAT_SIZE_OBJECT, reg, live+1);
- arg = reg[live];
- }
- hp = p->htop;
- p->htop += FLOAT_SIZE_OBJECT;
- f.fd = fabs(f.fd);
- res = make_float(hp);
- PUT_DOUBLE(f, hp);
- return res;
- }
- else
- return arg;
- }
- BIF_ERROR(p, BADARG);
-}
-
-Eterm erts_gc_float_1(Process* p, Eterm* reg, Uint live)
-{
- Eterm arg;
- Eterm res;
- Eterm* hp;
- FloatDef f;
-
- /* check args */
- arg = reg[live];
- if (is_not_integer(arg)) {
- if (is_float(arg)) {
- return arg;
- } else {
- badarg:
- BIF_ERROR(p, BADARG);
- }
- }
- if (is_small(arg)) {
- Sint i = signed_val(arg);
- f.fd = i; /* use "C"'s auto casting */
- } else if (big_to_double(arg, &f.fd) < 0) {
- goto badarg;
- }
- if (ERTS_NEED_GC(p, FLOAT_SIZE_OBJECT)) {
- erts_garbage_collect(p, FLOAT_SIZE_OBJECT, reg, live+1);
- arg = reg[live];
- }
- hp = p->htop;
- p->htop += FLOAT_SIZE_OBJECT;
- res = make_float(hp);
- PUT_DOUBLE(f, hp);
- return res;
-}
-
-Eterm erts_gc_round_1(Process* p, Eterm* reg, Uint live)
-{
- Eterm arg;
- FloatDef f;
-
- arg = reg[live];
- if (is_not_float(arg)) {
- if (is_integer(arg)) {
- return arg;
- }
- BIF_ERROR(p, BADARG);
- }
- GET_DOUBLE(arg, f);
-
- return gc_double_to_integer(p, round(f.fd), reg, live);
-}
-
-Eterm erts_gc_trunc_1(Process* p, Eterm* reg, Uint live)
-{
- Eterm arg;
- FloatDef f;
-
- arg = reg[live];
- if (is_not_float(arg)) {
- if (is_integer(arg)) {
- return arg;
- }
- BIF_ERROR(p, BADARG);
- }
- /* get the float */
- GET_DOUBLE(arg, f);
-
- /* truncate it and return the resultant integer */
- return gc_double_to_integer(p, (f.fd >= 0.0) ? floor(f.fd) : ceil(f.fd),
- reg, live);
-}
-
-Eterm erts_gc_floor_1(Process* p, Eterm* reg, Uint live)
-{
- Eterm arg;
- FloatDef f;
-
- arg = reg[live];
- if (is_not_float(arg)) {
- if (is_integer(arg)) {
- return arg;
- }
- BIF_ERROR(p, BADARG);
- }
- GET_DOUBLE(arg, f);
- return gc_double_to_integer(p, floor(f.fd), reg, live);
-}
-
-Eterm erts_gc_ceil_1(Process* p, Eterm* reg, Uint live)
-{
- Eterm arg;
- FloatDef f;
-
- arg = reg[live];
- if (is_not_float(arg)) {
- if (is_integer(arg)) {
- return arg;
- }
- BIF_ERROR(p, BADARG);
- }
- GET_DOUBLE(arg, f);
- return gc_double_to_integer(p, ceil(f.fd), reg, live);
-}
-
-static Eterm
-gc_double_to_integer(Process* p, double x, Eterm* reg, Uint live)
-{
- int is_negative;
- int ds;
- ErtsDigit* xp;
- int i;
- Eterm res;
- size_t sz;
- Eterm* hp;
- double dbase;
-
- if ((x < (double) (MAX_SMALL+1)) && (x > (double) (MIN_SMALL-1))) {
- Sint xi = x;
- return make_small(xi);
- }
-
- if (x >= 0) {
- is_negative = 0;
- } else {
- is_negative = 1;
- x = -x;
- }
-
- /* Unscale & (calculate exponent) */
- ds = 0;
- dbase = ((double)(D_MASK)+1);
- while(x >= 1.0) {
- x /= dbase; /* "shift" right */
- ds++;
- }
- sz = BIG_NEED_SIZE(ds); /* number of words including arity */
- if (ERTS_NEED_GC(p, sz)) {
- erts_garbage_collect(p, sz, reg, live);
- }
- hp = p->htop;
- p->htop += sz;
- res = make_big(hp);
- xp = (ErtsDigit*) (hp + 1);
-
- for (i = ds-1; i >= 0; i--) {
- ErtsDigit d;
-
- x *= dbase; /* "shift" left */
- d = x; /* trunc */
- xp[i] = d; /* store digit */
- x -= d; /* remove integer part */
- }
- while ((ds & (BIG_DIGITS_PER_WORD-1)) != 0) {
- xp[ds++] = 0;
- }
-
- if (is_negative) {
- *hp = make_neg_bignum_header(sz-1);
- } else {
- *hp = make_pos_bignum_header(sz-1);
- }
- return res;
-}
-
-/********************************************************************************
- * binary_part guards. The actual implementation is in erl_bif_binary.c
- ********************************************************************************/
-Eterm erts_gc_binary_part_3(Process* p, Eterm* reg, Uint live)
-{
- return erts_gc_binary_part(p,reg,live,0);
-}
-
-Eterm erts_gc_binary_part_2(Process* p, Eterm* reg, Uint live)
-{
- return erts_gc_binary_part(p,reg,live,1);
-}
diff --git a/erts/emulator/beam/erl_bif_info.c b/erts/emulator/beam/erl_bif_info.c
index 7fada0d548..74708b2caa 100644
--- a/erts/emulator/beam/erl_bif_info.c
+++ b/erts/emulator/beam/erl_bif_info.c
@@ -227,7 +227,7 @@ bld_magic_ref_bin_list(Uint **hpp, Uint *szp, ErlOffHeap* oh)
}).
*/
-static void do_calc_mon_size(ErtsMonitor *mon, void *vpsz)
+static int do_calc_mon_size(ErtsMonitor *mon, void *vpsz, Sint reds)
{
ErtsMonitorData *mdp = erts_monitor_to_data(mon);
Uint *psz = vpsz;
@@ -238,7 +238,8 @@ static void do_calc_mon_size(ErtsMonitor *mon, void *vpsz)
else
*psz += is_immed(mon->other.item) ? 0 : NC_HEAP_SIZE(mon->other.item);
- *psz += 9; /* CONS + 6-tuple */
+ *psz += 9; /* CONS + 6-tuple */
+ return 1;
}
typedef struct {
@@ -248,7 +249,7 @@ typedef struct {
Eterm tag;
} MonListContext;
-static void do_make_one_mon_element(ErtsMonitor *mon, void * vpmlc)
+static int do_make_one_mon_element(ErtsMonitor *mon, void * vpmlc, Sint reds)
{
ErtsMonitorData *mdp = erts_monitor_to_data(mon);
MonListContext *pmlc = vpmlc;
@@ -319,6 +320,7 @@ static void do_make_one_mon_element(ErtsMonitor *mon, void * vpmlc)
pmlc->hp += 7;
pmlc->res = CONS(pmlc->hp, tup, pmlc->res);
pmlc->hp += 2;
+ return 1;
}
static Eterm
@@ -328,7 +330,7 @@ make_monitor_list(Process *p, int tree, ErtsMonitor *root, Eterm tail)
Uint sz = 0;
MonListContext mlc;
void (*foreach)(ErtsMonitor *,
- void (*)(ErtsMonitor *, void *),
+ ErtsMonitorFunc,
void *);
foreach = tree ? erts_monitor_tree_foreach : erts_monitor_list_foreach;
@@ -354,7 +356,7 @@ make_monitor_list(Process *p, int tree, ErtsMonitor *root, Eterm tail)
}).
*/
-static void calc_lnk_size(ErtsLink *lnk, void *vpsz)
+static int calc_lnk_size(ErtsLink *lnk, void *vpsz, Sint reds)
{
Uint *psz = vpsz;
Uint sz = 0;
@@ -364,7 +366,8 @@ static void calc_lnk_size(ErtsLink *lnk, void *vpsz)
*psz += sz;
*psz += is_immed(lnk->other.item) ? 0 : size_object(lnk->other.item);
- *psz += 7; /* CONS + 4-tuple */
+ *psz += 7; /* CONS + 4-tuple */
+ return 1;
}
typedef struct {
@@ -374,7 +377,7 @@ typedef struct {
Eterm tag;
} LnkListContext;
-static void make_one_lnk_element(ErtsLink *lnk, void * vpllc)
+static int make_one_lnk_element(ErtsLink *lnk, void * vpllc, Sint reds)
{
LnkListContext *pllc = vpllc;
Eterm tup, t, pid, id;
@@ -411,6 +414,7 @@ static void make_one_lnk_element(ErtsLink *lnk, void * vpllc)
pllc->hp += 5;
pllc->res = CONS(pllc->hp, tup, pllc->res);
pllc->hp += 2;
+ return 1;
}
static Eterm
@@ -420,7 +424,7 @@ make_link_list(Process *p, int tree, ErtsLink *root, Eterm tail)
Uint sz = 0;
LnkListContext llc;
void (*foreach)(ErtsLink *,
- void (*)(ErtsLink *, void *),
+ ErtsLinkFunc,
void *);
foreach = tree ? erts_link_tree_foreach : erts_link_list_foreach;
@@ -519,16 +523,17 @@ do { \
} \
} while (0)
-static void collect_one_link(ErtsLink *lnk, void *vmicp)
+static int collect_one_link(ErtsLink *lnk, void *vmicp, Sint reds)
{
MonitorInfoCollection *micp = vmicp;
EXTEND_MONITOR_INFOS(micp);
micp->mi[micp->mi_i].entity.term = lnk->other.item;
micp->sz += 2 + NC_HEAP_SIZE(lnk->other.item);
micp->mi_i++;
+ return 1;
}
-static void collect_one_origin_monitor(ErtsMonitor *mon, void *vmicp)
+static int collect_one_origin_monitor(ErtsMonitor *mon, void *vmicp, Sint reds)
{
if (erts_monitor_is_origin(mon)) {
MonitorInfoCollection *micp = vmicp;
@@ -573,9 +578,10 @@ static void collect_one_origin_monitor(ErtsMonitor *mon, void *vmicp)
break;
}
}
+ return 1;
}
-static void collect_one_target_monitor(ErtsMonitor *mon, void *vmicp)
+static int collect_one_target_monitor(ErtsMonitor *mon, void *vmicp, Sint reds)
{
MonitorInfoCollection *micp = vmicp;
@@ -612,8 +618,8 @@ static void collect_one_target_monitor(ErtsMonitor *mon, void *vmicp)
default:
break;
}
-
}
+ return 1;
}
typedef struct {
@@ -653,8 +659,8 @@ do { \
} \
} while (0)
-static void
-collect_one_suspend_monitor(ErtsMonitor *mon, void *vsmicp)
+static int
+collect_one_suspend_monitor(ErtsMonitor *mon, void *vsmicp, Sint reds)
{
if (mon->type == ERTS_MON_TYPE_SUSPEND) {
Sint count;
@@ -678,6 +684,7 @@ collect_one_suspend_monitor(ErtsMonitor *mon, void *vsmicp)
smicp->smi_i++;
}
+ return 1;
}
/*
@@ -2705,9 +2712,7 @@ BIF_RETTYPE system_info_1(BIF_ALIST_1)
goto bld_instruction_counts;
}
-#ifdef DEBUG
ASSERT(endp == hp);
-#endif
BIF_RET(res);
#endif /* #ifndef ERTS_OPCODE_COUNTER_SUPPORT */
@@ -2967,7 +2972,7 @@ BIF_RETTYPE system_info_1(BIF_ALIST_1)
} else if (ERTS_IS_ATOM_STR("context_reductions", BIF_ARG_1)) {
BIF_RET(make_small(CONTEXT_REDS));
} else if (ERTS_IS_ATOM_STR("kernel_poll", BIF_ARG_1)) {
-#ifdef ERTS_ENABLE_KERNEL_POLL
+#if ERTS_ENABLE_KERNEL_POLL
BIF_RET(am_true);
#else
BIF_RET(am_false);
@@ -3139,19 +3144,23 @@ BIF_RETTYPE system_info_1(BIF_ALIST_1)
DECL_AM(tag);
BIF_RET(AM_tag);
#endif
+ } else if (ERTS_IS_ATOM_STR("system_logger", BIF_ARG_1)) {
+ BIF_RET(erts_get_system_logger());
}
BIF_ERROR(BIF_P, BADARG);
}
-static void monitor_size(ErtsMonitor *mon, void *vsz)
+static int monitor_size(ErtsMonitor *mon, void *vsz, Sint reds)
{
*((Uint *) vsz) = erts_monitor_size(mon);
+ return 1;
}
-static void link_size(ErtsMonitor *lnk, void *vsz)
+static int link_size(ErtsMonitor *lnk, void *vsz, Sint reds)
{
*((Uint *) vsz) = erts_link_size(lnk);
+ return 1;
}
/**********************************************************************/
@@ -4608,6 +4617,7 @@ BIF_RETTYPE erts_debug_set_internal_state_2(BIF_ALIST_2)
}
}
else if (ERTS_IS_ATOM_STR("broken_halt", BIF_ARG_1)) {
+ erts_proc_unlock(BIF_P, ERTS_PROC_LOCK_MAIN);
broken_halt_test(BIF_ARG_2);
}
else if (ERTS_IS_ATOM_STR("unique_monotonic_integer_state", BIF_ARG_1)) {
@@ -4671,6 +4681,14 @@ BIF_RETTYPE erts_debug_set_internal_state_2(BIF_ALIST_2)
BIF_RET(am_notsup);
#endif
}
+ else if (ERTS_IS_ATOM_STR("ets_force_split", BIF_ARG_1)) {
+ if (is_tuple(BIF_ARG_2)) {
+ Eterm* tpl = tuple_val(BIF_ARG_2);
+
+ if (erts_ets_force_split(tpl[1], tpl[2] == am_true))
+ BIF_RET(am_ok);
+ }
+ }
}
BIF_ERROR(BIF_P, BADARG);
diff --git a/erts/emulator/beam/erl_bif_lists.c b/erts/emulator/beam/erl_bif_lists.c
index aaf262780f..b23fa77f5f 100644
--- a/erts/emulator/beam/erl_bif_lists.c
+++ b/erts/emulator/beam/erl_bif_lists.c
@@ -35,101 +35,270 @@
static Eterm keyfind(int Bif, Process* p, Eterm Key, Eterm Pos, Eterm List);
+/* erlang:'++'/2
+ *
+ * Adds a list to another (LHS ++ RHS). For historical reasons this is
+ * implemented by copying LHS and setting its tail to RHS without checking
+ * that RHS is a proper list. [] ++ 'not_a_list' will therefore result in
+ * 'not_a_list', and [1,2] ++ 3 will result in [1,2|3], and this is a bug that
+ * we have to live with. */
-static BIF_RETTYPE append(Process* p, Eterm A, Eterm B)
-{
- Eterm list;
- Eterm copy;
- Eterm last;
- Eterm* hp = NULL;
- Sint i;
+typedef struct {
+ Eterm lhs_original;
+ Eterm rhs_original;
- list = A;
+ Eterm iterator;
- if (is_nil(list)) {
- BIF_RET(B);
- }
+ Eterm result;
+ Eterm *result_cdr;
+} ErtsAppendContext;
+
+static int append_ctx_bin_dtor(Binary *context_bin) {
+ return 1;
+}
+
+static Eterm append_create_trap_state(Process *p,
+ ErtsAppendContext *from_context) {
+ ErtsAppendContext *to_context;
+ Binary *state_bin;
+ Eterm *hp;
+
+ state_bin = erts_create_magic_binary(sizeof(ErtsAppendContext),
+ append_ctx_bin_dtor);
+
+ to_context = ERTS_MAGIC_BIN_DATA(state_bin);
+ *to_context = *from_context;
- if (is_not_list(list)) {
- BIF_ERROR(p, BADARG);
+ if (from_context->result_cdr == &from_context->result) {
+ to_context->result_cdr = &to_context->result;
}
- /* optimistic append on heap first */
+ hp = HAlloc(p, ERTS_MAGIC_REF_THING_SIZE);
+ return erts_mk_magic_ref(&hp, &MSO(p), state_bin);
+}
+
+static BIF_RETTYPE lists_append_alloc(Process *p, ErtsAppendContext *context) {
+ static const Uint CELLS_PER_RED = 40;
+
+ Eterm *alloc_top, *alloc_end;
+ Uint cells_left, max_cells;
+ Eterm lookahead;
+
+ cells_left = max_cells = CELLS_PER_RED * ERTS_BIF_REDS_LEFT(p);
+ lookahead = context->iterator;
- if ((i = HeapWordsLeft(p) / 2) < 4) {
- goto list_tail;
+#ifdef DEBUG
+ cells_left = max_cells = max_cells / 10 + 1;
+#endif
+
+ while (cells_left != 0 && is_list(lookahead)) {
+ lookahead = CDR(list_val(lookahead));
+ cells_left--;
}
- hp = HEAP_TOP(p);
- copy = last = CONS(hp, CAR(list_val(list)), make_list(hp+2));
- list = CDR(list_val(list));
- hp += 2;
- i -= 2; /* don't use the last 2 words (extra i--;) */
-
- while(i-- && is_list(list)) {
- Eterm* listp = list_val(list);
- last = CONS(hp, CAR(listp), make_list(hp+2));
- list = CDR(listp);
- hp += 2;
+ BUMP_REDS(p, (max_cells - cells_left) / CELLS_PER_RED);
+
+ if (is_not_list(lookahead) && is_not_nil(lookahead)) {
+ /* It's possible that we're erroring out with an incomplete list, so it
+ * must be terminated or we'll leave a hole in the heap. */
+ *context->result_cdr = NIL;
+ return -1;
}
- /* A is proper and B is NIL return A as-is, don't update HTOP */
+ alloc_top = HAlloc(p, 2 * (max_cells - cells_left));
+ alloc_end = alloc_top + 2 * (max_cells - cells_left);
+
+ while (alloc_top < alloc_end) {
+ Eterm *cell = list_val(context->iterator);
+
+ ASSERT(context->iterator != lookahead);
+
+ *context->result_cdr = make_list(alloc_top);
+ context->result_cdr = &CDR(alloc_top);
+ CAR(alloc_top) = CAR(cell);
- if (is_nil(list) && is_nil(B)) {
- BIF_RET(A);
+ context->iterator = CDR(cell);
+ alloc_top += 2;
}
- if (is_nil(list)) {
- HEAP_TOP(p) = hp;
- CDR(list_val(last)) = B;
- BIF_RET(copy);
+ if (is_list(context->iterator)) {
+ /* The result only has to be terminated when returning it to the user,
+ * but we're doing it when trapping as well to prevent headaches when
+ * debugging. */
+ *context->result_cdr = NIL;
+ ASSERT(cells_left == 0);
+ return 0;
}
-list_tail:
+ *context->result_cdr = context->rhs_original;
+ ASSERT(is_nil(context->iterator));
+
+ if (is_nil(context->rhs_original)) {
+ /* The list we created was equal to the original, so we'll return that
+ * in the hopes that the garbage we created can be removed soon. */
+ context->result = context->lhs_original;
+ }
+
+ return 1;
+}
+
+static BIF_RETTYPE lists_append_onheap(Process *p, ErtsAppendContext *context) {
+ static const Uint CELLS_PER_RED = 60;
+
+ Eterm *alloc_start, *alloc_top, *alloc_end;
+ Uint cells_left, max_cells;
+
+ cells_left = max_cells = CELLS_PER_RED * ERTS_BIF_REDS_LEFT(p);
+
+#ifdef DEBUG
+ cells_left = max_cells = max_cells / 10 + 1;
+#endif
+
+ ASSERT(HEAP_LIMIT(p) >= HEAP_TOP(p) + 2);
+ alloc_start = HEAP_TOP(p);
+ alloc_end = HEAP_LIMIT(p) - 2;
+ alloc_top = alloc_start;
+
+ /* Don't process more cells than we have reductions for. */
+ alloc_end = MIN(alloc_top + (cells_left * 2), alloc_end);
+
+ while (alloc_top < alloc_end && is_list(context->iterator)) {
+ Eterm *cell = list_val(context->iterator);
- if ((i = erts_list_length(list)) < 0) {
- BIF_ERROR(p, BADARG);
+ *context->result_cdr = make_list(alloc_top);
+ context->result_cdr = &CDR(alloc_top);
+ CAR(alloc_top) = CAR(cell);
+
+ context->iterator = CDR(cell);
+ alloc_top += 2;
}
- /* remaining list was proper and B is NIL */
- if (is_nil(B)) {
- BIF_RET(A);
+ cells_left -= (alloc_top - alloc_start) / 2;
+ HEAP_TOP(p) = alloc_top;
+
+ ASSERT(cells_left >= 0 && cells_left <= max_cells);
+ BUMP_REDS(p, (max_cells - cells_left) / CELLS_PER_RED);
+
+ if (is_not_list(context->iterator) && is_not_nil(context->iterator)) {
+ *context->result_cdr = NIL;
+ return -1;
}
- if (hp) {
- /* Note: fall through case, already written
- * on the heap.
- * The last 2 words of the heap is not written yet
- */
- Eterm *hp_save = hp;
- ASSERT(i != 0);
- HEAP_TOP(p) = hp + 2;
- if (i == 1) {
- hp[0] = CAR(list_val(list));
- hp[1] = B;
- BIF_RET(copy);
+ if (is_list(context->iterator)) {
+ if (cells_left > CELLS_PER_RED) {
+ return lists_append_alloc(p, context);
}
- hp = HAlloc(p, 2*(i - 1));
- last = CONS(hp_save, CAR(list_val(list)), make_list(hp));
- } else {
- hp = HAlloc(p, 2*i);
- copy = last = CONS(hp, CAR(list_val(list)), make_list(hp+2));
- hp += 2;
+
+ *context->result_cdr = NIL;
+ return 0;
+ }
+
+ *context->result_cdr = context->rhs_original;
+ ASSERT(is_nil(context->iterator));
+
+ if (is_nil(context->rhs_original)) {
+ context->result = context->lhs_original;
}
- list = CDR(list_val(list));
- i--;
+ return 1;
+}
+
+static int append_continue(Process *p, ErtsAppendContext *context) {
+ /* We build the result on the unused part of the heap if possible to save
+ * us the trouble of having to figure out the list size. We fall back to
+ * lists_append_alloc when we run out of space. */
+ if (HeapWordsLeft(p) > 8) {
+ return lists_append_onheap(p, context);
+ }
+
+ return lists_append_alloc(p, context);
+}
+
+static int append_start(Process *p, Eterm lhs, Eterm rhs,
+ ErtsAppendContext *context) {
+ context->lhs_original = lhs;
+ context->rhs_original = rhs;
+
+ context->result_cdr = &context->result;
+ context->result = NIL;
+
+ context->iterator = lhs;
+
+ return append_continue(p, context);
+}
+
+/* erlang:'++'/2 */
+static Eterm append(Export *bif_entry, BIF_ALIST_2) {
+ Eterm lhs = BIF_ARG_1, rhs = BIF_ARG_2;
+
+ if (is_nil(lhs)) {
+ /* This is buggy but expected, `[] ++ 'not_a_list'` has always resulted
+ * in 'not_a_list'. */
+ return rhs;
+ } else if (is_list(lhs)) {
+ /* We start with the context on the stack in the hopes that we won't
+ * have to trap. */
+ ErtsAppendContext context;
+ int res;
+
+ res = append_start(BIF_P, lhs, rhs, &context);
+
+ if (res == 0) {
+ Eterm state_mref;
+
+ state_mref = append_create_trap_state(BIF_P, &context);
+ erts_set_gc_state(BIF_P, 0);
+
+ BIF_TRAP2(bif_entry, BIF_P, state_mref, NIL);
+ }
+
+ if (res < 0) {
+ ASSERT(is_nil(*context.result_cdr));
+ BIF_ERROR(BIF_P, BADARG);
+ }
+
+ ASSERT(*context.result_cdr == context.rhs_original);
+ BIF_RET(context.result);
+ } else if (is_internal_magic_ref(lhs)) {
+ ErtsAppendContext *context;
+ int (*dtor)(Binary*);
+ Binary *magic_bin;
+
+ int res;
+
+ magic_bin = erts_magic_ref2bin(lhs);
+ dtor = ERTS_MAGIC_BIN_DESTRUCTOR(magic_bin);
+
+ if (dtor != append_ctx_bin_dtor) {
+ BIF_ERROR(BIF_P, BADARG);
+ }
+
+ ASSERT(BIF_P->flags & F_DISABLE_GC);
+ ASSERT(rhs == NIL);
- ASSERT(i > -1);
- while(i--) {
- Eterm* listp = list_val(list);
- last = CONS(hp, CAR(listp), make_list(hp+2));
- list = CDR(listp);
- hp += 2;
+ context = ERTS_MAGIC_BIN_DATA(magic_bin);
+ res = append_continue(BIF_P, context);
+
+ if (res == 0) {
+ BIF_TRAP2(bif_entry, BIF_P, lhs, NIL);
+ }
+
+ erts_set_gc_state(BIF_P, 1);
+
+ if (res < 0) {
+ ASSERT(is_nil(*context->result_cdr));
+ ERTS_BIF_ERROR_TRAPPED2(BIF_P, BADARG, bif_entry,
+ context->lhs_original,
+ context->rhs_original);
+ }
+
+ ASSERT(*context->result_cdr == context->rhs_original);
+ BIF_RET(context->result);
}
- CDR(list_val(last)) = B;
- BIF_RET(copy);
+ ASSERT(!(BIF_P->flags & F_DISABLE_GC));
+
+ BIF_ERROR(BIF_P, BADARG);
}
/*
@@ -139,12 +308,12 @@ list_tail:
Eterm
ebif_plusplus_2(BIF_ALIST_2)
{
- return append(BIF_P, BIF_ARG_1, BIF_ARG_2);
+ return append(bif_export[BIF_ebif_plusplus_2], BIF_CALL_ARGS);
}
BIF_RETTYPE append_2(BIF_ALIST_2)
{
- return append(BIF_P, BIF_ARG_1, BIF_ARG_2);
+ return append(bif_export[BIF_append_2], BIF_CALL_ARGS);
}
/* erlang:'--'/2
@@ -915,7 +1084,7 @@ static BIF_RETTYPE lists_reverse_alloc(Process *c_p,
list = list_in;
tail = tail_in;
- cells_left = max_cells = CELLS_PER_RED * (1 + ERTS_BIF_REDS_LEFT(c_p));
+ cells_left = max_cells = CELLS_PER_RED * ERTS_BIF_REDS_LEFT(c_p);
lookahead = list;
while (cells_left != 0 && is_list(lookahead)) {
@@ -964,7 +1133,7 @@ static BIF_RETTYPE lists_reverse_onheap(Process *c_p,
list = list_in;
tail = tail_in;
- cells_left = max_cells = CELLS_PER_RED * (1 + ERTS_BIF_REDS_LEFT(c_p));
+ cells_left = max_cells = CELLS_PER_RED * ERTS_BIF_REDS_LEFT(c_p);
ASSERT(HEAP_LIMIT(c_p) >= HEAP_TOP(c_p) + 2);
alloc_start = HEAP_TOP(c_p);
@@ -992,8 +1161,6 @@ static BIF_RETTYPE lists_reverse_onheap(Process *c_p,
if (is_nil(list)) {
BIF_RET(tail);
} else if (is_list(list)) {
- ASSERT(is_list(tail));
-
if (cells_left > CELLS_PER_RED) {
return lists_reverse_alloc(c_p, list, tail);
}
diff --git a/erts/emulator/beam/erl_bif_persistent.c b/erts/emulator/beam/erl_bif_persistent.c
index 9dca768a18..5a78a043ce 100644
--- a/erts/emulator/beam/erl_bif_persistent.c
+++ b/erts/emulator/beam/erl_bif_persistent.c
@@ -332,6 +332,23 @@ BIF_RETTYPE persistent_term_get_1(BIF_ALIST_1)
BIF_ERROR(BIF_P, BADARG);
}
+BIF_RETTYPE persistent_term_get_2(BIF_ALIST_2)
+{
+ Eterm key = BIF_ARG_1;
+ Eterm result = BIF_ARG_2;
+ HashTable* hash_table = (HashTable *) erts_atomic_read_nob(&the_hash_table);
+ Uint entry_index;
+ Eterm term;
+
+ entry_index = lookup(hash_table, key);
+ term = hash_table->term[entry_index];
+ if (is_boxed(term)) {
+ ASSERT(is_tuple_arity(term, 2));
+ result = tuple_val(term)[2];
+ }
+ BIF_RET(result);
+}
+
BIF_RETTYPE persistent_term_erase_1(BIF_ALIST_1)
{
Eterm key = BIF_ARG_1;
diff --git a/erts/emulator/beam/erl_bif_port.c b/erts/emulator/beam/erl_bif_port.c
index 7fe4e02782..ed825d3dda 100644
--- a/erts/emulator/beam/erl_bif_port.c
+++ b/erts/emulator/beam/erl_bif_port.c
@@ -891,10 +891,6 @@ open_port(Process* p, Eterm name, Eterm settings, int *err_typep, int *err_nump)
driver = &spawn_driver;
} else if (*tp == am_fd) { /* An fd port */
- int n;
- struct Sint_buf sbuf;
- char* p;
-
if (arity != make_arityval(3)) {
goto badarg;
}
@@ -904,15 +900,9 @@ open_port(Process* p, Eterm name, Eterm settings, int *err_typep, int *err_nump)
opts.ifd = unsigned_val(tp[1]);
opts.ofd = unsigned_val(tp[2]);
- /* Syntesize name from input and output descriptor. */
- name_buf = erts_alloc(ERTS_ALC_T_TMP,
- 2*sizeof(struct Sint_buf) + 2);
- p = Sint_to_buf(opts.ifd, &sbuf);
- n = sys_strlen(p);
- sys_strncpy(name_buf, p, n);
- name_buf[n] = '/';
- p = Sint_to_buf(opts.ofd, &sbuf);
- sys_strcpy(name_buf+n+1, p);
+ /* Syntesize name from input and output descriptor. */
+ name_buf = erts_alloc(ERTS_ALC_T_TMP, 256);
+ erts_snprintf(name_buf, 256, "%i/%i", opts.ifd, opts.ofd);
driver = &fd_driver;
} else {
diff --git a/erts/emulator/beam/erl_bif_re.c b/erts/emulator/beam/erl_bif_re.c
index bbc64eb9aa..e0b9202fe7 100644
--- a/erts/emulator/beam/erl_bif_re.c
+++ b/erts/emulator/beam/erl_bif_re.c
@@ -532,10 +532,7 @@ re_compile(Process* p, Eterm arg1, Eterm arg2)
int options = 0;
int pflags = 0;
int unicode = 0;
-#ifdef DEBUG
int buffres;
-#endif
-
if (parse_options(arg2,&options,NULL,&pflags,NULL,NULL,NULL,NULL)
< 0) {
@@ -556,12 +553,8 @@ re_compile(Process* p, Eterm arg1, Eterm arg2)
BIF_ERROR(p,BADARG);
}
expr = erts_alloc(ERTS_ALC_T_RE_TMP_BUF, slen + 1);
-#ifdef DEBUG
- buffres =
-#endif
- erts_iolist_to_buf(arg1, expr, slen);
-
- ASSERT(buffres >= 0);
+ buffres = erts_iolist_to_buf(arg1, expr, slen);
+ ASSERT(buffres >= 0); (void)buffres;
expr[slen]='\0';
result = erts_pcre_compile2(expr, options, &errcode,
@@ -1052,9 +1045,7 @@ build_capture(Eterm capture_spec[CAPSPEC_SIZE], const pcre *code)
tmpb[ap->len] = '\0';
} else {
ErlDrvSizeT slen;
-#ifdef DEBUG
int buffres;
-#endif
if (erts_iolist_size(val, &slen)) {
goto error;
@@ -1068,11 +1059,8 @@ build_capture(Eterm capture_spec[CAPSPEC_SIZE], const pcre *code)
}
}
-#ifdef DEBUG
- buffres =
-#endif
- erts_iolist_to_buf(val, tmpb, slen);
- ASSERT(buffres >= 0);
+ buffres = erts_iolist_to_buf(val, tmpb, slen);
+ ASSERT(buffres >= 0); (void)buffres;
tmpb[slen] = '\0';
}
build_one_capture(code,&ri,&sallocated,has_dupnames,tmpb);
@@ -1145,9 +1133,7 @@ re_run(Process *p, Eterm arg1, Eterm arg2, Eterm arg3)
const char *errstr = "";
int errofset = 0;
int capture_count;
-#ifdef DEBUG
int buffres;
-#endif
if (pflags & PARSE_FLAG_UNICODE &&
(!is_binary(arg2) || !is_binary(arg1) ||
@@ -1161,12 +1147,8 @@ re_run(Process *p, Eterm arg1, Eterm arg2, Eterm arg3)
expr = erts_alloc(ERTS_ALC_T_RE_TMP_BUF, slen + 1);
-#ifdef DEBUG
- buffres =
-#endif
- erts_iolist_to_buf(arg2, expr, slen);
-
- ASSERT(buffres >= 0);
+ buffres = erts_iolist_to_buf(arg2, expr, slen);
+ ASSERT(buffres >= 0); (void)buffres;
expr[slen]='\0';
result = erts_pcre_compile2(expr, comp_options, &errcode,
@@ -1317,9 +1299,7 @@ re_run(Process *p, Eterm arg1, Eterm arg2, Eterm arg3)
restart.subject = (char *) (pb->bytes+offset);
restart.flags |= RESTART_FLAG_SUBJECT_IN_BINARY;
} else {
-#ifdef DEBUG
int buffres;
-#endif
handle_iolist:
if (erts_iolist_size(arg1, &slength)) {
erts_free(ERTS_ALC_T_RE_SUBJECT, restart.ovector);
@@ -1331,11 +1311,8 @@ handle_iolist:
}
restart.subject = erts_alloc(ERTS_ALC_T_RE_SUBJECT, slength);
-#ifdef DEBUG
- buffres =
-#endif
- erts_iolist_to_buf(arg1, restart.subject, slength);
- ASSERT(buffres >= 0);
+ buffres = erts_iolist_to_buf(arg1, restart.subject, slength);
+ ASSERT(buffres >= 0); (void)buffres;
}
if (pflags & PARSE_FLAG_REPORT_ERRORS) {
@@ -1457,10 +1434,7 @@ re_inspect_2(BIF_ALIST_2)
Eterm res;
const pcre *code;
byte *temp_alloc = NULL;
-#ifdef DEBUG
- int infores;
-#endif
-
+ int infores;
if (is_not_tuple(BIF_ARG_1) || (arityval(*tuple_val(BIF_ARG_1)) != 5)) {
goto error;
@@ -1484,12 +1458,8 @@ re_inspect_2(BIF_ALIST_2)
if (erts_pcre_fullinfo(code, NULL, PCRE_INFO_OPTIONS, &options) != 0)
goto error;
-#ifdef DEBUG
- infores =
-#endif
- erts_pcre_fullinfo(code, NULL, PCRE_INFO_NAMECOUNT, &top);
-
- ASSERT(infores == 0);
+ infores = erts_pcre_fullinfo(code, NULL, PCRE_INFO_NAMECOUNT, &top);
+ ASSERT(infores == 0); (void)infores;
if (top <= 0) {
hp = HAlloc(BIF_P, 3);
@@ -1497,18 +1467,10 @@ re_inspect_2(BIF_ALIST_2)
erts_free_aligned_binary_bytes(temp_alloc);
BIF_RET(res);
}
-#ifdef DEBUG
- infores =
-#endif
- erts_pcre_fullinfo(code, NULL, PCRE_INFO_NAMEENTRYSIZE, &entrysize);
-
+ infores = erts_pcre_fullinfo(code, NULL, PCRE_INFO_NAMEENTRYSIZE, &entrysize);
ASSERT(infores == 0);
-#ifdef DEBUG
- infores =
-#endif
- erts_pcre_fullinfo(code, NULL, PCRE_INFO_NAMETABLE, &nametable);
-
+ infores = erts_pcre_fullinfo(code, NULL, PCRE_INFO_NAMETABLE, &nametable);
ASSERT(infores == 0);
has_dupnames = ((options & PCRE_DUPNAMES) != 0);
diff --git a/erts/emulator/beam/erl_binary.h b/erts/emulator/beam/erl_binary.h
index 08edb43c49..4bf77988f7 100644
--- a/erts/emulator/beam/erl_binary.h
+++ b/erts/emulator/beam/erl_binary.h
@@ -278,7 +278,6 @@ Eterm erts_bin_bytes_to_list(Eterm previous, Eterm* hp, byte* bytes, Uint size,
*/
BIF_RETTYPE erts_list_to_binary_bif(Process *p, Eterm arg, Export *bif);
-BIF_RETTYPE erts_gc_binary_part(Process *p, Eterm *reg, Eterm live, int range_is_tuple);
BIF_RETTYPE erts_binary_part(Process *p, Eterm binary, Eterm epos, Eterm elen);
diff --git a/erts/emulator/beam/erl_bits.c b/erts/emulator/beam/erl_bits.c
index 3a16913473..f5807d25d7 100644
--- a/erts/emulator/beam/erl_bits.c
+++ b/erts/emulator/beam/erl_bits.c
@@ -144,6 +144,42 @@ erts_bs_start_match_2(Process *p, Eterm Binary, Uint Max)
return make_matchstate(ms);
}
+ErlBinMatchState *erts_bs_start_match_3(Process *p, Eterm Binary)
+{
+ Eterm Orig;
+ Uint offs;
+ Uint* hp;
+ Uint NeededSize;
+ ErlBinMatchState *ms;
+ Uint bitoffs;
+ Uint bitsize;
+ Uint total_bin_size;
+ ProcBin* pb;
+
+ ASSERT(is_binary(Binary));
+ total_bin_size = binary_size(Binary);
+ if ((total_bin_size >> (8*sizeof(Uint)-3)) != 0) {
+ return NULL;
+ }
+
+ NeededSize = ERL_BIN_MATCHSTATE_SIZE(0);
+ hp = HeapOnlyAlloc(p, NeededSize);
+ ms = (ErlBinMatchState *) hp;
+ ERTS_GET_REAL_BIN(Binary, Orig, offs, bitoffs, bitsize);
+ pb = (ProcBin *) boxed_val(Orig);
+ if (pb->thing_word == HEADER_PROC_BIN && pb->flags != 0) {
+ erts_emasculate_writable_binary(pb);
+ }
+
+ ms->thing_word = HEADER_BIN_MATCHSTATE(0);
+ (ms->mb).orig = Orig;
+ (ms->mb).base = binary_bytes(Orig);
+ (ms->mb).offset = 8 * offs + bitoffs;
+ (ms->mb).size = total_bin_size * 8 + (ms->mb).offset + bitsize;
+
+ return ms;
+}
+
#ifdef DEBUG
# define CHECK_MATCH_BUFFER(MB) check_match_buffer(MB)
@@ -1295,6 +1331,14 @@ erts_bs_append(Process* c_p, Eterm* reg, Uint live, Eterm build_size_term,
}
}
+ if (build_size_in_bits == 0) {
+ if (c_p->stop - c_p->htop < extra_words) {
+ (void) erts_garbage_collect(c_p, extra_words, reg, live+1);
+ bin = reg[live];
+ }
+ return bin;
+ }
+
if((ERTS_UINT_MAX - build_size_in_bits) < erts_bin_offset) {
c_p->freason = SYSTEM_LIMIT;
return THE_NON_VALUE;
@@ -1352,13 +1396,13 @@ erts_bs_append(Process* c_p, Eterm* reg, Uint live, Eterm build_size_term,
Uint bitsize;
Eterm* hp;
- /*
+ /*
* Allocate heap space.
*/
heap_need = PROC_BIN_SIZE + ERL_SUB_BIN_SIZE + extra_words;
if (c_p->stop - c_p->htop < heap_need) {
(void) erts_garbage_collect(c_p, heap_need, reg, live+1);
- bin = reg[live];
+ bin = reg[live];
}
hp = c_p->htop;
@@ -1376,6 +1420,10 @@ erts_bs_append(Process* c_p, Eterm* reg, Uint live, Eterm build_size_term,
}
}
+ if (build_size_in_bits == 0) {
+ return bin;
+ }
+
if((ERTS_UINT_MAX - build_size_in_bits) < erts_bin_offset) {
c_p->freason = SYSTEM_LIMIT;
return THE_NON_VALUE;
diff --git a/erts/emulator/beam/erl_bits.h b/erts/emulator/beam/erl_bits.h
index 7beef5cfda..50d353e1fa 100644
--- a/erts/emulator/beam/erl_bits.h
+++ b/erts/emulator/beam/erl_bits.h
@@ -73,12 +73,16 @@ struct erl_bits_state {
typedef struct erl_bin_match_struct{
Eterm thing_word;
ErlBinMatchBuffer mb; /* Present match buffer */
- Eterm save_offset[1]; /* Saved offsets */
+ Eterm save_offset[1]; /* Saved offsets, only valid for contexts
+ * created through bs_start_match2. */
} ErlBinMatchState;
-#define ERL_BIN_MATCHSTATE_SIZE(_Max) ((sizeof(ErlBinMatchState) + (_Max)*sizeof(Eterm))/sizeof(Eterm))
-#define HEADER_BIN_MATCHSTATE(_Max) _make_header(ERL_BIN_MATCHSTATE_SIZE((_Max))-1, _TAG_HEADER_BIN_MATCHSTATE)
-#define HEADER_NUM_SLOTS(hdr) (header_arity(hdr)-sizeof(ErlBinMatchState)/sizeof(Eterm)+1)
+#define ERL_BIN_MATCHSTATE_SIZE(_Max) \
+ ((offsetof(ErlBinMatchState, save_offset) + (_Max)*sizeof(Eterm))/sizeof(Eterm))
+#define HEADER_BIN_MATCHSTATE(_Max) \
+ _make_header(ERL_BIN_MATCHSTATE_SIZE((_Max)) - 1, _TAG_HEADER_BIN_MATCHSTATE)
+#define HEADER_NUM_SLOTS(hdr) \
+ (header_arity(hdr) - (offsetof(ErlBinMatchState, save_offset) / sizeof(Eterm)) + 1)
#define make_matchstate(_Ms) make_boxed((Eterm*)(_Ms))
#define ms_matchbuffer(_Ms) &(((ErlBinMatchState*) boxed_val(_Ms))->mb)
@@ -144,6 +148,7 @@ void erts_bits_destroy_state(ERL_BITS_PROTO_0);
*/
Eterm erts_bs_start_match_2(Process *p, Eterm Bin, Uint Max);
+ErlBinMatchState *erts_bs_start_match_3(Process *p, Eterm Bin);
Eterm erts_bs_get_integer_2(Process *p, Uint num_bits, unsigned flags, ErlBinMatchBuffer* mb);
Eterm erts_bs_get_binary_2(Process *p, Uint num_bits, unsigned flags, ErlBinMatchBuffer* mb);
Eterm erts_bs_get_float_2(Process *p, Uint num_bits, unsigned flags, ErlBinMatchBuffer* mb);
diff --git a/erts/emulator/beam/erl_db.c b/erts/emulator/beam/erl_db.c
index c009a3bde8..4132a54934 100644
--- a/erts/emulator/beam/erl_db.c
+++ b/erts/emulator/beam/erl_db.c
@@ -90,7 +90,8 @@ enum DbIterSafety {
ITER_SAFE /* No need to fixate at all */
};
# define ITERATION_SAFETY(Proc,Tab) \
- ((IS_TREE_TABLE((Tab)->common.status) || ONLY_WRITER(Proc,Tab)) ? ITER_SAFE \
+ ((IS_TREE_TABLE((Tab)->common.status) || IS_CATREE_TABLE((Tab)->common.status) \
+ || ONLY_WRITER(Proc,Tab)) ? ITER_SAFE \
: (((Tab)->common.status & DB_FINE_LOCKED) ? ITER_UNSAFE : ITER_SAFE_LOCKED))
#define DID_TRAP(P,Ret) (!is_value(Ret) && ((P)->freason == TRAP))
@@ -359,6 +360,7 @@ typedef enum {
extern DbTableMethod db_hash;
extern DbTableMethod db_tree;
+extern DbTableMethod db_catree;
int user_requested_db_max_tabs;
int erts_ets_realloc_always_moves;
@@ -407,21 +409,17 @@ static void
free_dbtable(void *vtb)
{
DbTable *tb = (DbTable *) vtb;
-#ifdef HARDDEBUG
- if (erts_atomic_read_nob(&tb->common.memory_size) != sizeof(DbTable)) {
- erts_fprintf(stderr, "ets: free_dbtable memory remain=%ld fix=%x\n",
- erts_atomic_read_nob(&tb->common.memory_size)-sizeof(DbTable),
- tb->common.fixations);
- }
-#endif
- erts_rwmtx_destroy(&tb->common.rwlock);
- erts_mtx_destroy(&tb->common.fixlock);
- ASSERT(is_immed(tb->common.heir_data));
- if (tb->common.btid)
- erts_bin_release(tb->common.btid);
+ ASSERT(erts_atomic_read_nob(&tb->common.memory_size) == sizeof(DbTable));
- erts_db_free(ERTS_ALC_T_DB_TABLE, tb, (void *) tb, sizeof(DbTable));
+ erts_rwmtx_destroy(&tb->common.rwlock);
+ erts_mtx_destroy(&tb->common.fixlock);
+ ASSERT(is_immed(tb->common.heir_data));
+
+ if (tb->common.btid)
+ erts_bin_release(tb->common.btid);
+
+ erts_db_free(ERTS_ALC_T_DB_TABLE, tb, (void *) tb, sizeof(DbTable));
}
static void schedule_free_dbtable(DbTable* tb)
@@ -1076,7 +1074,7 @@ BIF_RETTYPE ets_update_element_3(BIF_ALIST_3)
DB_BIF_GET_TABLE(tb, DB_WRITE, LCK_WRITE_REC, BIF_ets_update_element_3);
UseTmpHeap(2,BIF_P);
- if (!(tb->common.status & (DB_SET | DB_ORDERED_SET))) {
+ if (!(tb->common.status & (DB_SET | DB_ORDERED_SET | DB_CA_ORDERED_SET))) {
goto bail_out;
}
if (is_tuple(BIF_ARG_3)) {
@@ -1165,7 +1163,7 @@ do_update_counter(Process *p, DbTable* tb,
UseTmpHeap(5, p);
- if (!(tb->common.status & (DB_SET | DB_ORDERED_SET))) {
+ if (!(tb->common.status & (DB_SET | DB_ORDERED_SET | DB_CA_ORDERED_SET))) {
goto bail_out;
}
if (is_integer(arg3)) { /* Incr */
@@ -1647,15 +1645,15 @@ BIF_RETTYPE ets_new_2(BIF_ALIST_2)
val = CAR(list_val(list));
if (val == am_bag) {
status |= DB_BAG;
- status &= ~(DB_SET | DB_DUPLICATE_BAG | DB_ORDERED_SET);
+ status &= ~(DB_SET | DB_DUPLICATE_BAG | DB_ORDERED_SET | DB_CA_ORDERED_SET);
}
else if (val == am_duplicate_bag) {
status |= DB_DUPLICATE_BAG;
- status &= ~(DB_SET | DB_BAG | DB_ORDERED_SET);
+ status &= ~(DB_SET | DB_BAG | DB_ORDERED_SET | DB_CA_ORDERED_SET);
}
else if (val == am_ordered_set) {
status |= DB_ORDERED_SET;
- status &= ~(DB_SET | DB_BAG | DB_DUPLICATE_BAG);
+ status &= ~(DB_SET | DB_BAG | DB_DUPLICATE_BAG | DB_CA_ORDERED_SET);
}
else if (is_tuple(val)) {
Eterm *tp = tuple_val(val);
@@ -1716,7 +1714,13 @@ BIF_RETTYPE ets_new_2(BIF_ALIST_2)
if (is_not_nil(list)) { /* bad opt or not a well formed list */
BIF_ERROR(BIF_P, BADARG);
}
- if (IS_HASH_TABLE(status)) {
+ if (IS_TREE_TABLE(status) && is_fine_locked && !(status & DB_PRIVATE)) {
+ meth = &db_catree;
+ status |= DB_CA_ORDERED_SET;
+ status &= ~(DB_SET | DB_BAG | DB_DUPLICATE_BAG | DB_ORDERED_SET);
+ status |= DB_FINE_LOCKED;
+ }
+ else if (IS_HASH_TABLE(status)) {
meth = &db_hash;
if (is_fine_locked && !(status & DB_PRIVATE)) {
status |= DB_FINE_LOCKED;
@@ -3506,6 +3510,7 @@ void init_db(ErtsDbSpinCount db_spin_count)
db_initialize_hash();
db_initialize_tree();
+ db_initialize_catree();
/* Non visual BIF to trap to. */
erts_init_trap_export(&ets_select_delete_continue_exp,
@@ -3696,7 +3701,7 @@ static SWord proc_cleanup_fixed_table(Process* p, DbFixation* fix)
/*
* erts_db_process_exiting() is called when a process terminates.
* It returns 0 when completely done, and !0 when it wants to
- * yield. c_p->u.terminate can hold a pointer to a state while
+ * yield. *yield_state can hold a pointer to a state while
* yielding.
*/
#define ERTS_DB_INTERNAL_ERROR(LSTR) \
@@ -3704,7 +3709,7 @@ static SWord proc_cleanup_fixed_table(Process* p, DbFixation* fix)
__FILE__, __LINE__)
int
-erts_db_process_exiting(Process *c_p, ErtsProcLocks c_p_locks)
+erts_db_process_exiting(Process *c_p, ErtsProcLocks c_p_locks, void **yield_state)
{
typedef struct {
enum {
@@ -3714,7 +3719,7 @@ erts_db_process_exiting(Process *c_p, ErtsProcLocks c_p_locks)
}op;
DbTable *tb;
} CleanupState;
- CleanupState *state = (CleanupState *) c_p->u.terminate;
+ CleanupState *state = (CleanupState *) *yield_state;
Eterm pid = c_p->common.id;
CleanupState default_state;
SWord initial_reds = ERTS_BIF_REDS_LEFT(c_p);
@@ -3787,7 +3792,7 @@ erts_db_process_exiting(Process *c_p, ErtsProcLocks c_p_locks)
if (state != &default_state)
erts_free(ERTS_ALC_T_DB_PROC_CLEANUP, state);
- c_p->u.terminate = NULL;
+ *yield_state = NULL;
BUMP_REDS(c_p, (initial_reds - reds));
return 0;
@@ -3807,12 +3812,12 @@ erts_db_process_exiting(Process *c_p, ErtsProcLocks c_p_locks)
yield:
if (state == &default_state) {
- c_p->u.terminate = erts_alloc(ERTS_ALC_T_DB_PROC_CLEANUP,
- sizeof(CleanupState));
- sys_memcpy(c_p->u.terminate, (void*) state, sizeof(CleanupState));
+ *yield_state = erts_alloc(ERTS_ALC_T_DB_PROC_CLEANUP,
+ sizeof(CleanupState));
+ sys_memcpy(*yield_state, (void*) state, sizeof(CleanupState));
}
else
- ASSERT(state == c_p->u.terminate);
+ ASSERT(state == *yield_state);
return !0;
}
@@ -3907,7 +3912,7 @@ struct free_fixations_ctx
SWord cnt;
};
-static void free_fixations_op(DbFixation* fix, void* vctx)
+static int free_fixations_op(DbFixation* fix, void* vctx, Sint reds)
{
struct free_fixations_ctx* ctx = (struct free_fixations_ctx*) vctx;
erts_aint_t diff;
@@ -3944,6 +3949,7 @@ static void free_fixations_op(DbFixation* fix, void* vctx)
ERTS_ETS_MISC_MEM_ADD(-sizeof(DbFixation));
}
ctx->cnt++;
+ return 1;
}
int erts_db_execute_free_fixation(Process* p, DbFixation* fix)
@@ -4088,7 +4094,7 @@ struct fixing_procs_info_ctx
Eterm list;
};
-static void fixing_procs_info_op(DbFixation* fix, void* vctx)
+static int fixing_procs_info_op(DbFixation* fix, void* vctx, Sint reds)
{
struct fixing_procs_info_ctx* ctx = (struct fixing_procs_info_ctx*) vctx;
Eterm* hp;
@@ -4098,6 +4104,7 @@ static void fixing_procs_info_op(DbFixation* fix, void* vctx)
tpl = TUPLE2(hp, fix->procs.p->common.id, make_small(fix->counter));
hp += 3;
ctx->list = CONS(hp, tpl, ctx->list);
+ return 1;
}
static Eterm table_info(Process* p, DbTable* tb, Eterm What)
@@ -4114,6 +4121,8 @@ static Eterm table_info(Process* p, DbTable* tb, Eterm What)
ret = am_duplicate_bag;
} else if (tb->common.status & DB_ORDERED_SET) {
ret = am_ordered_set;
+ } else if (tb->common.status & DB_CA_ORDERED_SET) {
+ ret = am_ordered_set;
} else { /*TT*/
ASSERT(tb->common.status & DB_BAG);
ret = am_bag;
@@ -4240,9 +4249,20 @@ static Eterm table_info(Process* p, DbTable* tb, Eterm What)
make_small(stats.max_chain_len),
make_small(stats.kept_items));
}
- else {
+ else if (IS_CATREE_TABLE(tb->common.status)) {
+ DbCATreeStats stats;
+ Eterm* hp;
+
+ db_calc_stats_catree(&tb->catree, &stats);
+ hp = HAlloc(p, 4);
+ ret = TUPLE3(hp,
+ make_small(stats.route_nodes),
+ make_small(stats.base_nodes),
+ make_small(stats.max_depth));
+
+ }
+ else
ret = am_false;
- }
}
return ret;
}
@@ -4409,6 +4429,12 @@ void erts_lcnt_enable_db_lock_count(DbTable *tb, int enable) {
if(IS_HASH_TABLE(tb->common.status)) {
erts_lcnt_enable_db_hash_lock_count(&tb->hash, enable);
+ } else if(IS_CATREE_TABLE(tb->common.status)) {
+ /* erts_lcnt_enable_db_catree_lock_count is not thread safe so
+ the table needs to get locked */
+ db_lock(tb, LCK_WRITE);
+ erts_lcnt_enable_db_catree_lock_count(&tb->catree, enable);
+ db_unlock(tb, LCK_WRITE);
}
}
@@ -4441,3 +4467,16 @@ void erts_lcnt_update_db_locks(int enable) {
#ifdef ETS_DBG_FORCE_TRAP
erts_aint_t erts_ets_dbg_force_trap = 0;
#endif
+
+int erts_ets_force_split(Eterm tid, int on)
+{
+ DbTable* tb = tid2tab(tid);
+ if (!tb || !IS_CATREE_TABLE(tb->common.type))
+ return 0;
+
+ db_lock(tb, LCK_WRITE);
+ if (!(tb->common.status & DB_DELETE))
+ db_catree_force_split(&tb->catree, on);
+ db_unlock(tb, LCK_WRITE);
+ return 1;
+}
diff --git a/erts/emulator/beam/erl_db.h b/erts/emulator/beam/erl_db.h
index 23975d208f..dc77fbb60c 100644
--- a/erts/emulator/beam/erl_db.h
+++ b/erts/emulator/beam/erl_db.h
@@ -66,6 +66,7 @@ typedef struct {
#include "erl_db_util.h" /* Flags */
#include "erl_db_hash.h" /* DbTableHash */
#include "erl_db_tree.h" /* DbTableTree */
+#include "erl_db_catree.h" /* DbTableCATree */
/*TT*/
Uint erts_get_ets_misc_mem_size(void);
@@ -90,6 +91,7 @@ union db_table {
DbTableCommon common; /* Any type of db table */
DbTableHash hash; /* Linear hash array specific data */
DbTableTree tree; /* AVL tree specific data */
+ DbTableCATree catree; /* CA tree specific data */
DbTableRelease release;
/*TT*/
};
@@ -109,7 +111,7 @@ typedef enum {
} ErtsDbSpinCount;
void init_db(ErtsDbSpinCount);
-int erts_db_process_exiting(Process *, ErtsProcLocks);
+int erts_db_process_exiting(Process *, ErtsProcLocks, void **);
int erts_db_execute_free_fixation(Process*, DbFixation*);
void db_info(fmtfn_t, void *, int);
void erts_db_foreach_table(void (*)(DbTable *, void *), void *);
@@ -128,6 +130,7 @@ extern Export ets_select_continue_exp;
extern erts_atomic_t erts_ets_misc_mem_size;
Eterm erts_ets_colliding_names(Process*, Eterm name, Uint cnt);
+int erts_ets_force_split(Eterm tid, int on);
Uint erts_db_get_max_tabs(void);
Eterm erts_db_make_tid(Process *c_p, DbTableCommon *tb);
@@ -284,6 +287,12 @@ ERTS_GLB_INLINE void erts_db_free(ErtsAlcType_t type,
void *ptr,
Uint size);
+ERTS_GLB_INLINE void erts_schedule_db_free(DbTableCommon* tab,
+ void (*free_func)(void *),
+ void *ptr,
+ ErtsThrPrgrLaterOp *lop,
+ Uint size);
+
ERTS_GLB_INLINE void erts_db_free_nt(ErtsAlcType_t type,
void *ptr,
Uint size);
@@ -304,6 +313,26 @@ erts_db_free(ErtsAlcType_t type, DbTable *tab, void *ptr, Uint size)
}
ERTS_GLB_INLINE void
+erts_schedule_db_free(DbTableCommon* tab,
+ void (*free_func)(void *),
+ void *ptr,
+ ErtsThrPrgrLaterOp *lop,
+ Uint size)
+{
+ ASSERT(ptr != 0);
+ ASSERT(((void *) tab) != ptr);
+ ASSERT(size == ERTS_ALC_DBG_BLK_SZ(ptr));
+
+ /*
+ * We update table memory stats here as table may already be gone
+ * when 'free_func' is finally called.
+ */
+ ERTS_DB_ALC_MEM_UPDATE_((DbTable*)tab, size, 0);
+
+ erts_schedule_thr_prgr_later_cleanup_op(free_func, ptr, lop, size);
+}
+
+ERTS_GLB_INLINE void
erts_db_free_nt(ErtsAlcType_t type, void *ptr, Uint size)
{
ASSERT(ptr != 0);
diff --git a/erts/emulator/beam/erl_db_catree.c b/erts/emulator/beam/erl_db_catree.c
new file mode 100644
index 0000000000..75ac1c4a93
--- /dev/null
+++ b/erts/emulator/beam/erl_db_catree.c
@@ -0,0 +1,2250 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB and Kjell Winblad 1998-2018. All 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%
+ */
+
+/*
+ * Description: Implementation of ETS ordered_set table type with
+ * fine-grained synchronization.
+ *
+ * Author: Kjell Winblad
+ *
+ * This implementation is based on the contention adapting search tree
+ * (CA tree). The CA tree is a concurrent data structure that
+ * dynamically adapts its synchronization granularity based on how
+ * much contention is detected in locks. The following publication
+ * contains a detailed description of CA trees:
+ *
+ * A Contention Adapting Approach to Concurrent Ordered Sets
+ * Journal of Parallel and Distributed Computing, 2018
+ * Kjell Winblad and Konstantinos Sagonas
+ * https://doi.org/10.1016/j.jpdc.2017.11.007
+ *
+ * The following publication may also be interesting as it discusses
+ * how the CA tree can be used as an ETS ordered_set table type
+ * backend:
+ *
+ * More Scalable Ordered Set for ETS Using Adaptation
+ * In Thirteenth ACM SIGPLAN workshop on Erlang (2014)
+ * Kjell Winblad and Konstantinos Sagonas
+ * https://doi.org/10.1145/2633448.2633455
+ *
+ * This implementation of the ordered_set ETS table type is only
+ * activated when the options {write_concurrency, true}, public and
+ * ordered_set are passed to the ets:new/2 function. This
+ * implementation is expected to scale better than the default
+ * implementation located in "erl_db_tree.c".
+ *
+ * The default implementation has a static stack optimization (see
+ * get_static_stack in erl_db_tree.c). This implementation does not
+ * have such an optimization as it induces bad scalability when
+ * concurrent read operations are frequent (they all try to get hold
+ * of the same stack). The default implementation may thus perform
+ * better compared to this implementation in scenarios where the
+ * static stack optimization is useful. One such scenario is when only
+ * one process is accessing the table and this process is traversing
+ * the table with a sequence of next/2 calls.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "sys.h"
+#include "erl_vm.h"
+#include "global.h"
+#include "erl_process.h"
+#include "error.h"
+#define ERTS_WANT_DB_INTERNAL__
+#include "erl_db.h"
+#include "bif.h"
+#include "big.h"
+#include "erl_binary.h"
+
+#include "erl_db_catree.h"
+#include "erl_db_tree.h"
+#include "erl_db_tree_util.h"
+
+#ifdef DEBUG
+# define IF_DEBUG(X) X
+#else
+# define IF_DEBUG(X)
+#endif
+
+/*
+** Forward declarations
+*/
+
+static SWord do_free_base_node_cont(DbTableCATree *tb, SWord num_left);
+static SWord do_free_routing_nodes_catree_cont(DbTableCATree *tb, SWord num_left);
+static DbTableCATreeNode *catree_first_base_node_from_free_list(DbTableCATree *tb);
+
+/* Method interface functions */
+static int db_first_catree(Process *p, DbTable *tbl,
+ Eterm *ret);
+static int db_next_catree(Process *p, DbTable *tbl,
+ Eterm key, Eterm *ret);
+static int db_last_catree(Process *p, DbTable *tbl,
+ Eterm *ret);
+static int db_prev_catree(Process *p, DbTable *tbl,
+ Eterm key,
+ Eterm *ret);
+static int db_put_catree(DbTable *tbl, Eterm obj, int key_clash_fail);
+static int db_get_catree(Process *p, DbTable *tbl,
+ Eterm key, Eterm *ret);
+static int db_member_catree(DbTable *tbl, Eterm key, Eterm *ret);
+static int db_get_element_catree(Process *p, DbTable *tbl,
+ Eterm key,int ndex,
+ Eterm *ret);
+static int db_erase_catree(DbTable *tbl, Eterm key, Eterm *ret);
+static int db_erase_object_catree(DbTable *tbl, Eterm object,Eterm *ret);
+static int db_slot_catree(Process *p, DbTable *tbl,
+ Eterm slot_term, Eterm *ret);
+static int db_select_catree(Process *p, DbTable *tbl, Eterm tid,
+ Eterm pattern, int reversed, Eterm *ret);
+static int db_select_count_catree(Process *p, DbTable *tbl, Eterm tid,
+ Eterm pattern, Eterm *ret);
+static int db_select_chunk_catree(Process *p, DbTable *tbl, Eterm tid,
+ Eterm pattern, Sint chunk_size,
+ int reversed, Eterm *ret);
+static int db_select_continue_catree(Process *p, DbTable *tbl,
+ Eterm continuation, Eterm *ret);
+static int db_select_count_continue_catree(Process *p, DbTable *tbl,
+ Eterm continuation, Eterm *ret);
+static int db_select_delete_catree(Process *p, DbTable *tbl, Eterm tid,
+ Eterm pattern, Eterm *ret);
+static int db_select_delete_continue_catree(Process *p, DbTable *tbl,
+ Eterm continuation, Eterm *ret);
+static int db_select_replace_catree(Process *p, DbTable *tbl, Eterm tid,
+ Eterm pattern, Eterm *ret);
+static int db_select_replace_continue_catree(Process *p, DbTable *tbl,
+ Eterm continuation, Eterm *ret);
+static int db_take_catree(Process *, DbTable *, Eterm, Eterm *);
+static void db_print_catree(fmtfn_t to, void *to_arg,
+ int show, DbTable *tbl);
+static int db_free_table_catree(DbTable *tbl);
+static SWord db_free_table_continue_catree(DbTable *tbl, SWord);
+static void db_foreach_offheap_catree(DbTable *,
+ void (*)(ErlOffHeap *, void *),
+ void *);
+static SWord db_delete_all_objects_catree(Process* p, DbTable* tbl, SWord reds);
+static int
+db_lookup_dbterm_catree(Process *, DbTable *, Eterm key, Eterm obj,
+ DbUpdateHandle*);
+static void db_finalize_dbterm_catree(int cret, DbUpdateHandle *);
+
+static void split_catree(DbTableCATree *tb,
+ DbTableCATreeNode* ERTS_RESTRICT base,
+ DbTableCATreeNode* ERTS_RESTRICT parent);
+static void join_catree(DbTableCATree *tb,
+ DbTableCATreeNode *thiz,
+ DbTableCATreeNode *parent);
+
+
+/*
+** External interface
+*/
+DbTableMethod db_catree =
+{
+ db_create_catree,
+ db_first_catree,
+ db_next_catree,
+ db_last_catree,
+ db_prev_catree,
+ db_put_catree,
+ db_get_catree,
+ db_get_element_catree,
+ db_member_catree,
+ db_erase_catree,
+ db_erase_object_catree,
+ db_slot_catree,
+ db_select_chunk_catree,
+ db_select_catree,
+ db_select_delete_catree,
+ db_select_continue_catree,
+ db_select_delete_continue_catree,
+ db_select_count_catree,
+ db_select_count_continue_catree,
+ db_select_replace_catree,
+ db_select_replace_continue_catree,
+ db_take_catree,
+ db_delete_all_objects_catree,
+ db_free_table_catree,
+ db_free_table_continue_catree,
+ db_print_catree,
+ db_foreach_offheap_catree,
+ db_lookup_dbterm_catree,
+ db_finalize_dbterm_catree
+
+};
+
+/*
+ * Constants
+ */
+
+#define ERL_DB_CATREE_LOCK_FAILURE_CONTRIBUTION 200
+#define ERL_DB_CATREE_LOCK_SUCCESS_CONTRIBUTION (-1)
+#define ERL_DB_CATREE_LOCK_MORE_THAN_ONE_CONTRIBUTION (-10)
+#define ERL_DB_CATREE_HIGH_CONTENTION_LIMIT 1000
+#define ERL_DB_CATREE_LOW_CONTENTION_LIMIT (-1000)
+#define ERL_DB_CATREE_MAX_ROUTE_NODE_LAYER_HEIGHT 14
+
+/*
+ * Internal CA tree related helper functions and macros
+ */
+
+#define GET_ROUTE_NODE_KEY(node) (node->u.route.key.term)
+#define GET_BASE_NODE_LOCK(node) (&(node->u.base.lock))
+#define GET_ROUTE_NODE_LOCK(node) (&(node->u.route.lock))
+
+
+/* Helpers for reading and writing shared atomic variables */
+
+/* No memory barrier */
+#define GET_ROOT(tb) ((DbTableCATreeNode*)erts_atomic_read_nob(&((tb)->root)))
+#define GET_LEFT(ca_tree_route_node) ((DbTableCATreeNode*)erts_atomic_read_nob(&(ca_tree_route_node->u.route.left)))
+#define GET_RIGHT(ca_tree_route_node) ((DbTableCATreeNode*)erts_atomic_read_nob(&(ca_tree_route_node->u.route.right)))
+#define SET_ROOT(tb, v) erts_atomic_set_nob(&((tb)->root), (erts_aint_t)(v))
+#define SET_LEFT(ca_tree_route_node, v) erts_atomic_set_nob(&(ca_tree_route_node->u.route.left), (erts_aint_t)(v));
+#define SET_RIGHT(ca_tree_route_node, v) erts_atomic_set_nob(&(ca_tree_route_node->u.route.right), (erts_aint_t)(v));
+
+
+/* Release or acquire barriers */
+#define GET_ROOT_ACQB(tb) ((DbTableCATreeNode*)erts_atomic_read_acqb(&((tb)->root)))
+#define GET_LEFT_ACQB(ca_tree_route_node) ((DbTableCATreeNode*)erts_atomic_read_acqb(&(ca_tree_route_node->u.route.left)))
+#define GET_RIGHT_ACQB(ca_tree_route_node) ((DbTableCATreeNode*)erts_atomic_read_acqb(&(ca_tree_route_node->u.route.right)))
+#define SET_ROOT_RELB(tb, v) erts_atomic_set_relb(&((tb)->root), (erts_aint_t)(v))
+#define SET_LEFT_RELB(ca_tree_route_node, v) erts_atomic_set_relb(&(ca_tree_route_node->u.route.left), (erts_aint_t)(v));
+#define SET_RIGHT_RELB(ca_tree_route_node, v) erts_atomic_set_relb(&(ca_tree_route_node->u.route.right), (erts_aint_t)(v));
+
+/* Compares a key to the key in a route node */
+static ERTS_INLINE Sint cmp_key_route(Eterm key,
+ DbTableCATreeNode *obj)
+{
+ return CMP(key, GET_ROUTE_NODE_KEY(obj));
+}
+
+/*
+ * Used by the split_tree function
+ */
+static ERTS_INLINE
+int less_than_two_elements(TreeDbTerm *root)
+{
+ return root == NULL || (root->left == NULL && root->right == NULL);
+}
+
+/*
+ * Inserts a TreeDbTerm into a tree. Returns the new root.
+ */
+static ERTS_INLINE
+TreeDbTerm* insert_TreeDbTerm(DbTableCATree *tb,
+ TreeDbTerm *insert_to_root,
+ TreeDbTerm *value_to_insert) {
+ /* Non recursive insertion in AVL tree, building our own stack */
+ TreeDbTerm **tstack[STACK_NEED];
+ int tpos = 0;
+ int dstack[STACK_NEED+1];
+ int dpos = 0;
+ int state = 0;
+ TreeDbTerm * base = insert_to_root;
+ TreeDbTerm **this = &base;
+ Sint c;
+ Eterm key;
+ int dir;
+ TreeDbTerm *p1, *p2, *p;
+
+ key = GETKEY(tb, value_to_insert->dbterm.tpl);
+
+ dstack[dpos++] = DIR_END;
+ for (;;)
+ if (!*this) { /* Found our place */
+ state = 1;
+ *this = value_to_insert;
+ (*this)->balance = 0;
+ (*this)->left = (*this)->right = NULL;
+ break;
+ } else if ((c = cmp_key(&tb->common, key, *this)) < 0) {
+ /* go lefts */
+ dstack[dpos++] = DIR_LEFT;
+ tstack[tpos++] = this;
+ this = &((*this)->left);
+ } else { /* go right */
+ dstack[dpos++] = DIR_RIGHT;
+ tstack[tpos++] = this;
+ this = &((*this)->right);
+ }
+
+ while (state && ( dir = dstack[--dpos] ) != DIR_END) {
+ this = tstack[--tpos];
+ p = *this;
+ if (dir == DIR_LEFT) {
+ switch (p->balance) {
+ case 1:
+ p->balance = 0;
+ state = 0;
+ break;
+ case 0:
+ p->balance = -1;
+ break;
+ case -1: /* The icky case */
+ p1 = p->left;
+ if (p1->balance == -1) { /* Single LL rotation */
+ p->left = p1->right;
+ p1->right = p;
+ p->balance = 0;
+ (*this) = p1;
+ } else { /* Double RR rotation */
+ p2 = p1->right;
+ p1->right = p2->left;
+ p2->left = p1;
+ p->left = p2->right;
+ p2->right = p;
+ p->balance = (p2->balance == -1) ? +1 : 0;
+ p1->balance = (p2->balance == 1) ? -1 : 0;
+ (*this) = p2;
+ }
+ (*this)->balance = 0;
+ state = 0;
+ break;
+ }
+ } else { /* dir == DIR_RIGHT */
+ switch (p->balance) {
+ case -1:
+ p->balance = 0;
+ state = 0;
+ break;
+ case 0:
+ p->balance = 1;
+ break;
+ case 1:
+ p1 = p->right;
+ if (p1->balance == 1) { /* Single RR rotation */
+ p->right = p1->left;
+ p1->left = p;
+ p->balance = 0;
+ (*this) = p1;
+ } else { /* Double RL rotation */
+ p2 = p1->left;
+ p1->left = p2->right;
+ p2->right = p1;
+ p->right = p2->left;
+ p2->left = p;
+ p->balance = (p2->balance == 1) ? -1 : 0;
+ p1->balance = (p2->balance == -1) ? 1 : 0;
+ (*this) = p2;
+ }
+ (*this)->balance = 0;
+ state = 0;
+ break;
+ }
+ }
+ }
+ return base;
+}
+
+/*
+ * Split an AVL tree into two trees. The function stores the node
+ * containing the "split key" in the write back parameter
+ * split_key_wb. The function stores the left tree containing the keys
+ * that are smaller than the "split key" in the write back parameter
+ * left_wb and the tree containing the rest of the keys in the write
+ * back parameter right_wb.
+ */
+static void split_tree(DbTableCATree *tb,
+ TreeDbTerm *root,
+ TreeDbTerm **split_key_node_wb,
+ TreeDbTerm **left_wb,
+ TreeDbTerm **right_wb) {
+ TreeDbTerm * split_node = NULL;
+ TreeDbTerm * left_root;
+ TreeDbTerm * right_root;
+ if (root->left == NULL) { /* To get non empty split */
+ *right_wb = root->right;
+ *split_key_node_wb = root->right;
+ root->right = NULL;
+ root->balance = 0;
+ *left_wb = root;
+ return;
+ }
+ split_node = root;
+ left_root = split_node->left;
+ split_node->left = NULL;
+ right_root = split_node->right;
+ split_node->right = NULL;
+ right_root = insert_TreeDbTerm(tb, right_root, split_node);
+ *split_key_node_wb = split_node;
+ *left_wb = left_root;
+ *right_wb = right_root;
+}
+
+/*
+ * Used by the join_trees function
+ */
+static ERTS_INLINE int compute_tree_hight(TreeDbTerm * root)
+{
+ if(root == NULL) {
+ return 0;
+ } else {
+ TreeDbTerm * current_node = root;
+ int hight_so_far = 1;
+ while (current_node->left != NULL || current_node->right != NULL) {
+ if (current_node->balance == -1) {
+ current_node = current_node->left;
+ } else {
+ current_node = current_node->right;
+ }
+ hight_so_far = hight_so_far + 1;
+ }
+ return hight_so_far;
+ }
+}
+
+/*
+ * Used by the join_trees function
+ */
+static ERTS_INLINE
+TreeDbTerm* linkout_min_or_max_tree_node(TreeDbTerm **root, int is_min)
+{
+ TreeDbTerm **tstack[STACK_NEED];
+ int tpos = 0;
+ int dstack[STACK_NEED+1];
+ int dpos = 0;
+ int state = 0;
+ TreeDbTerm **this = root;
+ int dir;
+ TreeDbTerm *q = NULL;
+
+ dstack[dpos++] = DIR_END;
+ for (;;) {
+ if (!*this) { /* Failure */
+ return NULL;
+ } else if (is_min && (*this)->left != NULL) {
+ dstack[dpos++] = DIR_LEFT;
+ tstack[tpos++] = this;
+ this = &((*this)->left);
+ } else if (!is_min && (*this)->right != NULL) {
+ dstack[dpos++] = DIR_RIGHT;
+ tstack[tpos++] = this;
+ this = &((*this)->right);
+ } else { /* Min value, found the one to splice out */
+ q = (*this);
+ if (q->right == NULL) {
+ (*this) = q->left;
+ state = 1;
+ } else if (q->left == NULL) {
+ (*this) = q->right;
+ state = 1;
+ }
+ break;
+ }
+ }
+ while (state && ( dir = dstack[--dpos] ) != DIR_END) {
+ this = tstack[--tpos];
+ if (dir == DIR_LEFT) {
+ state = tree_balance_left(this);
+ } else {
+ state = tree_balance_right(this);
+ }
+ }
+ return q;
+}
+
+#define LINKOUT_MIN_TREE_NODE(root) linkout_min_or_max_tree_node(root, 1)
+#define LINKOUT_MAX_TREE_NODE(root) linkout_min_or_max_tree_node(root, 0)
+
+/*
+ * Joins two AVL trees where all the keys in the left one are smaller
+ * then the keys in the right one and returns the resulting tree.
+ *
+ * The algorithm is described on page 474 in D. E. Knuth. The Art of
+ * Computer Programming: Sorting and Searching,
+ * vol. 3. Addison-Wesley, 2nd edition, 1998.
+ */
+static TreeDbTerm* join_trees(TreeDbTerm *left_root_param,
+ TreeDbTerm *right_root_param)
+{
+ TreeDbTerm **tstack[STACK_NEED];
+ int tpos = 0;
+ int dstack[STACK_NEED+1];
+ int dpos = 0;
+ int state = 1;
+ TreeDbTerm **this;
+ int dir;
+ TreeDbTerm *p1, *p2, *p;
+ TreeDbTerm *left_root = left_root_param;
+ TreeDbTerm *right_root = right_root_param;
+ int left_height;
+ int right_height;
+ int current_height;
+ dstack[dpos++] = DIR_END;
+ if (left_root == NULL) {
+ return right_root;
+ } else if (right_root == NULL) {
+ return left_root;
+ }
+
+ left_height = compute_tree_hight(left_root);
+ right_height = compute_tree_hight(right_root);
+ if (left_height >= right_height) {
+ TreeDbTerm * new_root =
+ LINKOUT_MIN_TREE_NODE(&right_root);
+ int new_right_height = compute_tree_hight(right_root);
+ TreeDbTerm * current_node = left_root;
+ this = &left_root;
+ current_height = left_height;
+ while(current_height > new_right_height + 1) {
+ if (current_node->balance == -1) {
+ current_height = current_height - 2;
+ } else {
+ current_height = current_height - 1;
+ }
+ dstack[dpos++] = DIR_RIGHT;
+ tstack[tpos++] = this;
+ this = &((*this)->right);
+ current_node = current_node->right;
+ }
+ new_root->left = current_node;
+ new_root->right = right_root;
+ new_root->balance = new_right_height - current_height;
+ *this = new_root;
+ } else {
+ /* This case is symmetric to the previous case */
+ TreeDbTerm * new_root =
+ LINKOUT_MAX_TREE_NODE(&left_root);
+ int new_left_height = compute_tree_hight(left_root);
+ TreeDbTerm * current_node = right_root;
+ this = &right_root;
+ current_height = right_height;
+ while (current_height > new_left_height + 1) {
+ if (current_node->balance == 1) {
+ current_height = current_height - 2;
+ } else {
+ current_height = current_height - 1;
+ }
+ dstack[dpos++] = DIR_LEFT;
+ tstack[tpos++] = this;
+ this = &((*this)->left);
+ current_node = current_node->left;
+ }
+ new_root->right = current_node;
+ new_root->left = left_root;
+ new_root->balance = current_height - new_left_height;
+ *this = new_root;
+ }
+ /* Now we need to continue as if this was during the insert */
+ while (state && ( dir = dstack[--dpos] ) != DIR_END) {
+ this = tstack[--tpos];
+ p = *this;
+ if (dir == DIR_LEFT) {
+ switch (p->balance) {
+ case 1:
+ p->balance = 0;
+ state = 0;
+ break;
+ case 0:
+ p->balance = -1;
+ break;
+ case -1: /* The icky case */
+ p1 = p->left;
+ if (p1->balance == -1) { /* Single LL rotation */
+ p->left = p1->right;
+ p1->right = p;
+ p->balance = 0;
+ (*this) = p1;
+ } else { /* Double RR rotation */
+ p2 = p1->right;
+ p1->right = p2->left;
+ p2->left = p1;
+ p->left = p2->right;
+ p2->right = p;
+ p->balance = (p2->balance == -1) ? +1 : 0;
+ p1->balance = (p2->balance == 1) ? -1 : 0;
+ (*this) = p2;
+ }
+ (*this)->balance = 0;
+ state = 0;
+ break;
+ }
+ } else { /* dir == DIR_RIGHT */
+ switch (p->balance) {
+ case -1:
+ p->balance = 0;
+ state = 0;
+ break;
+ case 0:
+ p->balance = 1;
+ break;
+ case 1:
+ p1 = p->right;
+ if (p1->balance == 1) { /* Single RR rotation */
+ p->right = p1->left;
+ p1->left = p;
+ p->balance = 0;
+ (*this) = p1;
+ } else { /* Double RL rotation */
+ p2 = p1->left;
+ p1->left = p2->right;
+ p2->right = p1;
+ p->right = p2->left;
+ p2->left = p;
+ p->balance = (p2->balance == 1) ? -1 : 0;
+ p1->balance = (p2->balance == -1) ? 1 : 0;
+ (*this) = p2;
+ }
+ (*this)->balance = 0;
+ state = 0;
+ break;
+ }
+ }
+ }
+ /* Return the joined tree */
+ if (left_height >= right_height) {
+ return left_root;
+ } else {
+ return right_root;
+ }
+}
+
+#ifdef DEBUG
+# define PROVOKE_RANDOM_SPLIT_JOIN
+#endif
+#ifdef PROVOKE_RANDOM_SPLIT_JOIN
+static int dbg_fastrand(void)
+{
+ static int g_seed = 648835;
+ g_seed = (214013*g_seed+2531011);
+ return (g_seed>>16)&0x7FFF;
+}
+
+static void dbg_provoke_random_splitjoin(DbTableCATree* tb,
+ DbTableCATreeNode* base_node)
+{
+ if (tb->common.status & DB_CATREE_FORCE_SPLIT)
+ return;
+
+ switch (dbg_fastrand() % 8) {
+ case 1:
+ base_node->u.base.lock_statistics = 1+ERL_DB_CATREE_HIGH_CONTENTION_LIMIT;
+ break;
+ case 2:
+ base_node->u.base.lock_statistics = -1+ERL_DB_CATREE_LOW_CONTENTION_LIMIT;
+ break;
+ }
+}
+#else
+# define dbg_provoke_random_splitjoin(T,N)
+#endif /* PROVOKE_RANDOM_SPLIT_JOIN */
+
+static ERTS_INLINE
+int try_wlock_base_node(DbTableCATreeBaseNode *base_node)
+{
+ return EBUSY == erts_rwmtx_tryrwlock(&base_node->lock);
+}
+
+/*
+ * Locks a base node without adjusting the lock statistics
+ */
+static ERTS_INLINE
+void wlock_base_node_no_stats(DbTableCATreeNode *base_node)
+{
+ ASSERT(base_node->is_base_node);
+ erts_rwmtx_rwlock(&base_node->u.base.lock);
+}
+
+/*
+ * Locks a base node and adjusts the lock statistics according to if
+ * the lock was contended or not
+ */
+static ERTS_INLINE
+void wlock_base_node(DbTableCATreeNode *base_node)
+{
+ ASSERT(base_node->is_base_node);
+ if (try_wlock_base_node(&base_node->u.base)) {
+ /* The lock is contended */
+ wlock_base_node_no_stats(base_node);
+ base_node->u.base.lock_statistics += ERL_DB_CATREE_LOCK_FAILURE_CONTRIBUTION;
+ } else {
+ base_node->u.base.lock_statistics += ERL_DB_CATREE_LOCK_SUCCESS_CONTRIBUTION;
+ }
+}
+
+static ERTS_INLINE
+void wunlock_base_node(DbTableCATreeNode *base_node)
+{
+ erts_rwmtx_rwunlock(&base_node->u.base.lock);
+}
+
+static ERTS_INLINE
+void wunlock_adapt_base_node(DbTableCATree* tb,
+ DbTableCATreeNode* node,
+ DbTableCATreeNode* parent,
+ int current_level)
+{
+ dbg_provoke_random_splitjoin(tb,node);
+ if ((!node->u.base.root && parent && !(tb->common.status
+ & DB_CATREE_FORCE_SPLIT))
+ || node->u.base.lock_statistics < ERL_DB_CATREE_LOW_CONTENTION_LIMIT) {
+ join_catree(tb, node, parent);
+ }
+ else if (node->u.base.lock_statistics > ERL_DB_CATREE_HIGH_CONTENTION_LIMIT
+ && current_level < ERL_DB_CATREE_MAX_ROUTE_NODE_LAYER_HEIGHT) {
+ split_catree(tb, node, parent);
+ }
+ else {
+ wunlock_base_node(node);
+ }
+}
+
+static ERTS_INLINE
+void rlock_base_node(DbTableCATreeNode *base_node)
+{
+ ASSERT(base_node->is_base_node);
+ erts_rwmtx_rlock(&base_node->u.base.lock);
+}
+
+static ERTS_INLINE
+void runlock_base_node(DbTableCATreeNode *base_node)
+{
+ ASSERT(base_node->is_base_node);
+ erts_rwmtx_runlock(&base_node->u.base.lock);
+}
+
+static ERTS_INLINE
+void lock_route_node(DbTableCATreeNode *route_node)
+{
+ ASSERT(!route_node->is_base_node);
+ erts_mtx_lock(&route_node->u.route.lock);
+}
+
+static ERTS_INLINE
+void unlock_route_node(DbTableCATreeNode *route_node)
+{
+ ASSERT(!route_node->is_base_node);
+ erts_mtx_unlock(&route_node->u.route.lock);
+}
+
+static ERTS_INLINE
+Eterm copy_route_key(DbRouteKey* dst, Eterm key, Uint key_size)
+{
+ dst->size = key_size;
+ if (key_size != 0) {
+ Eterm* hp = &dst->heap[0];
+ ErlOffHeap tmp_offheap;
+ tmp_offheap.first = NULL;
+ dst->term = copy_struct(key, key_size, &hp, &tmp_offheap);
+ dst->oh = tmp_offheap.first;
+ }
+ else {
+ ASSERT(is_immed(key));
+ dst->term = key;
+ dst->oh = NULL;
+ }
+ return dst->term;
+}
+
+static ERTS_INLINE
+void destroy_route_key(DbRouteKey* key)
+{
+ if (key->oh) {
+ ErlOffHeap oh;
+ oh.first = key->oh;
+ erts_cleanup_offheap(&oh);
+ }
+}
+
+static ERTS_INLINE
+void init_root_iterator(DbTableCATree* tb, CATreeRootIterator* iter,
+ int read_only)
+{
+ iter->tb = tb;
+ iter->read_only = read_only;
+ iter->locked_bnode = NULL;
+ iter->next_route_key = THE_NON_VALUE;
+ iter->search_key = NULL;
+}
+
+static ERTS_INLINE
+void lock_iter_base_node(CATreeRootIterator* iter,
+ DbTableCATreeNode *base_node,
+ DbTableCATreeNode *parent,
+ int current_level)
+{
+ ASSERT(!iter->locked_bnode);
+ if (iter->read_only)
+ rlock_base_node(base_node);
+ else {
+ wlock_base_node(base_node);
+ iter->bnode_parent = parent;
+ iter->bnode_level = current_level;
+ }
+ iter->locked_bnode = base_node;
+}
+
+static ERTS_INLINE
+void unlock_iter_base_node(CATreeRootIterator* iter)
+{
+ ASSERT(iter->locked_bnode);
+ if (iter->read_only)
+ runlock_base_node(iter->locked_bnode);
+ else if (iter->locked_bnode->u.base.is_valid) {
+ wunlock_adapt_base_node(iter->tb, iter->locked_bnode,
+ iter->bnode_parent, iter->bnode_level);
+ }
+ else
+ wunlock_base_node(iter->locked_bnode);
+ iter->locked_bnode = NULL;
+}
+
+static ERTS_INLINE
+void destroy_root_iterator(CATreeRootIterator* iter)
+{
+ if (iter->locked_bnode)
+ unlock_iter_base_node(iter);
+ if (iter->search_key) {
+ destroy_route_key(iter->search_key);
+ erts_free(ERTS_ALC_T_DB_TMP, iter->search_key);
+ }
+}
+
+typedef struct
+{
+ DbTableCATreeNode *parent;
+ int current_level;
+} FindBaseNode;
+
+static ERTS_INLINE
+DbTableCATreeNode* find_base_node(DbTableCATree* tb, Eterm key,
+ FindBaseNode* fbn)
+{
+ DbTableCATreeNode* ERTS_RESTRICT node = GET_ROOT_ACQB(tb);
+ if (fbn) {
+ fbn->parent = NULL;
+ fbn->current_level = 0;
+ }
+ while (!node->is_base_node) {
+ if (fbn) {
+ fbn->current_level++;
+ fbn->parent = node;
+ }
+ if (cmp_key_route(key, node) < 0) {
+ node = GET_LEFT_ACQB(node);
+ } else {
+ node = GET_RIGHT_ACQB(node);
+ }
+ }
+ return node;
+}
+
+static ERTS_INLINE
+DbTableCATreeNode* find_rlock_valid_base_node(DbTableCATree* tb, Eterm key)
+{
+ DbTableCATreeNode* base_node;
+
+ while (1) {
+ base_node = find_base_node(tb, key, NULL);
+ rlock_base_node(base_node);
+ if (base_node->u.base.is_valid)
+ break;
+ runlock_base_node(base_node);
+ }
+ return base_node;
+}
+
+static ERTS_INLINE
+DbTableCATreeNode* find_wlock_valid_base_node(DbTableCATree* tb, Eterm key,
+ FindBaseNode* fbn)
+{
+ DbTableCATreeNode* base_node;
+
+ while (1) {
+ base_node = find_base_node(tb, key, fbn);
+ wlock_base_node(base_node);
+ if (base_node->u.base.is_valid)
+ break;
+ wunlock_base_node(base_node);
+ }
+ return base_node;
+}
+
+#ifdef ERTS_ENABLE_LOCK_CHECK
+# define LC_ORDER(ORDER) ORDER
+#else
+# define LC_ORDER(ORDER) NIL
+#endif
+
+#define sizeof_base_node() \
+ offsetof(DbTableCATreeNode, u.base.end_of_struct__)
+
+static DbTableCATreeNode *create_base_node(DbTableCATree *tb,
+ TreeDbTerm* root)
+{
+ DbTableCATreeNode *p;
+ erts_rwmtx_opt_t rwmtx_opt = ERTS_RWMTX_OPT_DEFAULT_INITER;
+ p = erts_db_alloc(ERTS_ALC_T_DB_TABLE, (DbTable *) tb,
+ sizeof_base_node());
+
+ p->is_base_node = 1;
+ p->u.base.root = root;
+ if (tb->common.type & DB_FREQ_READ)
+ rwmtx_opt.type = ERTS_RWMTX_TYPE_FREQUENT_READ;
+ if (erts_ets_rwmtx_spin_count >= 0)
+ rwmtx_opt.main_spincount = erts_ets_rwmtx_spin_count;
+
+ erts_rwmtx_init_opt(&p->u.base.lock, &rwmtx_opt,
+ "erl_db_catree_base_node",
+ NIL,
+ ERTS_LOCK_FLAGS_CATEGORY_DB);
+ p->u.base.lock_statistics = ((tb->common.status & DB_CATREE_FORCE_SPLIT)
+ ? INT_MAX : 0);
+ p->u.base.is_valid = 1;
+ return p;
+}
+
+static ERTS_INLINE Uint sizeof_route_node(Uint key_size)
+{
+ return (offsetof(DbTableCATreeNode, u.route.key.heap)
+ + key_size*sizeof(Eterm));
+}
+
+static DbTableCATreeNode*
+create_route_node(DbTableCATree *tb,
+ DbTableCATreeNode *left,
+ DbTableCATreeNode *right,
+ DbTerm * keyTerm,
+ DbTableCATreeNode* lc_parent)
+{
+ Eterm key = GETKEY(tb,keyTerm->tpl);
+ int key_size = size_object(key);
+ DbTableCATreeNode* p = erts_db_alloc(ERTS_ALC_T_DB_TABLE,
+ (DbTable *) tb,
+ sizeof_route_node(key_size));
+
+ copy_route_key(&p->u.route.key, key, key_size);
+ p->is_base_node = 0;
+ p->u.route.is_valid = 1;
+ erts_atomic_init_nob(&p->u.route.left, (erts_aint_t)left);
+ erts_atomic_init_nob(&p->u.route.right, (erts_aint_t)right);
+#ifdef ERTS_ENABLE_LOCK_CHECK
+ /* Route node lock order is inverse tree depth (from leafs toward root) */
+ p->u.route.lc_order = (lc_parent == NULL ? MAX_SMALL :
+ lc_parent->u.route.lc_order - 1);
+ /*
+ * This assert may eventually fail as we don't increase 'lc_order' in join
+ * operations when route nodes move up in the tree.
+ * Tough luck if you run a lock-checking VM for such a long time on 32-bit.
+ */
+ ERTS_LC_ASSERT(p->u.route.lc_order >= 0);
+#endif
+ erts_mtx_init(&p->u.route.lock, "erl_db_catree_route_node",
+ LC_ORDER(make_small(p->u.route.lc_order)),
+ ERTS_LOCK_FLAGS_CATEGORY_DB);
+ return p;
+}
+
+static void do_free_base_node(void* vptr)
+{
+ DbTableCATreeNode *p = (DbTableCATreeNode *)vptr;
+ ASSERT(p->is_base_node);
+ erts_rwmtx_destroy(&p->u.base.lock);
+ erts_free(ERTS_ALC_T_DB_TABLE, p);
+}
+
+static void free_catree_base_node(DbTableCATree* tb, DbTableCATreeNode* p)
+{
+ ASSERT(p->is_base_node);
+ ERTS_DB_ALC_MEM_UPDATE_(tb, sizeof_base_node(), 0);
+ do_free_base_node(p);
+}
+
+static void do_free_route_node(void *vptr)
+{
+ DbTableCATreeNode *p = (DbTableCATreeNode *)vptr;
+ ASSERT(!p->is_base_node);
+ erts_mtx_destroy(&p->u.route.lock);
+ destroy_route_key(&p->u.route.key);
+ erts_free(ERTS_ALC_T_DB_TABLE, p);
+}
+
+static void free_catree_route_node(DbTableCATree* tb, DbTableCATreeNode* p)
+{
+ ASSERT(!p->is_base_node);
+ ERTS_DB_ALC_MEM_UPDATE_(tb, sizeof_route_node(p->u.route.key.size), 0);
+ do_free_route_node(p);
+}
+
+
+/*
+ * Returns the parent routing node of the specified
+ * route node 'child' if such a parent exists
+ * or NULL if 'child' is attached to the root.
+ */
+static ERTS_INLINE DbTableCATreeNode *
+parent_of(DbTableCATree *tb,
+ DbTableCATreeNode *child)
+{
+ Eterm key = GET_ROUTE_NODE_KEY(child);
+ DbTableCATreeNode *current = GET_ROOT_ACQB(tb);
+ DbTableCATreeNode *prev = NULL;
+
+ while (current != child) {
+ prev = current;
+ if (cmp_key_route(key, current) < 0) {
+ current = GET_LEFT_ACQB(current);
+ } else {
+ current = GET_RIGHT_ACQB(current);
+ }
+ }
+ return prev;
+}
+
+
+static ERTS_INLINE DbTableCATreeNode *
+leftmost_base_node(DbTableCATreeNode *root)
+{
+ DbTableCATreeNode *node = root;
+ while (!node->is_base_node) {
+ node = GET_LEFT_ACQB(node);
+ }
+ return node;
+}
+
+
+static ERTS_INLINE DbTableCATreeNode *
+rightmost_base_node(DbTableCATreeNode *root)
+{
+ DbTableCATreeNode *node = root;
+ while (!node->is_base_node) {
+ node = GET_RIGHT_ACQB(node);
+ }
+ return node;
+}
+
+
+static ERTS_INLINE DbTableCATreeNode *
+leftmost_route_node(DbTableCATreeNode *root)
+{
+ DbTableCATreeNode *node = root;
+ DbTableCATreeNode *prev_node = NULL;
+ while (!node->is_base_node) {
+ prev_node = node;
+ node = GET_LEFT_ACQB(node);
+ }
+ return prev_node;
+}
+
+static ERTS_INLINE DbTableCATreeNode*
+rightmost_route_node(DbTableCATreeNode *root)
+{
+ DbTableCATreeNode * node = root;
+ DbTableCATreeNode * prev_node = NULL;
+ while (!node->is_base_node) {
+ prev_node = node;
+ node = GET_RIGHT_ACQB(node);
+ }
+ return prev_node;
+}
+
+static ERTS_INLINE
+void init_tree_stack(DbTreeStack *stack,
+ TreeDbTerm **stack_array,
+ Uint init_slot)
+{
+ stack->array = stack_array;
+ stack->pos = 0;
+ stack->slot = init_slot;
+}
+
+static void join_catree(DbTableCATree *tb,
+ DbTableCATreeNode *thiz,
+ DbTableCATreeNode *parent)
+{
+ DbTableCATreeNode *gparent;
+ DbTableCATreeNode *neighbor;
+ DbTableCATreeNode *new_neighbor;
+ DbTableCATreeNode *neighbor_parent;
+
+ ASSERT(thiz->is_base_node);
+ if (parent == NULL) {
+ thiz->u.base.lock_statistics = 0;
+ wunlock_base_node(thiz);
+ return;
+ }
+ ASSERT(!parent->is_base_node);
+ if (GET_LEFT(parent) == thiz) {
+ neighbor = leftmost_base_node(GET_RIGHT_ACQB(parent));
+ if (try_wlock_base_node(&neighbor->u.base)) {
+ /* Failed to acquire lock */
+ thiz->u.base.lock_statistics = 0;
+ wunlock_base_node(thiz);
+ return;
+ } else if (!neighbor->u.base.is_valid) {
+ thiz->u.base.lock_statistics = 0;
+ wunlock_base_node(thiz);
+ wunlock_base_node(neighbor);
+ return;
+ } else {
+ lock_route_node(parent);
+ parent->u.route.is_valid = 0;
+ neighbor->u.base.is_valid = 0;
+ thiz->u.base.is_valid = 0;
+ gparent = NULL;
+ do {
+ if (gparent != NULL) {
+ unlock_route_node(gparent);
+ }
+ gparent = parent_of(tb, parent);
+ if (gparent != NULL)
+ lock_route_node(gparent);
+ } while (gparent != NULL && !gparent->u.route.is_valid);
+
+ if (gparent == NULL) {
+ SET_ROOT_RELB(tb, GET_RIGHT(parent));
+ } else if (GET_LEFT(gparent) == parent) {
+ SET_LEFT_RELB(gparent, GET_RIGHT(parent));
+ } else {
+ SET_RIGHT_RELB(gparent, GET_RIGHT(parent));
+ }
+ unlock_route_node(parent);
+ if (gparent != NULL) {
+ unlock_route_node(gparent);
+ }
+ {
+ TreeDbTerm* new_root = join_trees(thiz->u.base.root,
+ neighbor->u.base.root);
+ new_neighbor = create_base_node(tb, new_root);
+ }
+ if (GET_RIGHT(parent) == neighbor) {
+ neighbor_parent = gparent;
+ } else {
+ neighbor_parent = leftmost_route_node(GET_RIGHT(parent));
+ }
+ }
+ } else { /* Symetric case */
+ ASSERT(GET_RIGHT(parent) == thiz);
+ neighbor = rightmost_base_node(GET_LEFT_ACQB(parent));
+ if (try_wlock_base_node(&neighbor->u.base)) {
+ /* Failed to acquire lock */
+ thiz->u.base.lock_statistics = 0;
+ wunlock_base_node(thiz);
+ return;
+ } else if (!neighbor->u.base.is_valid) {
+ thiz->u.base.lock_statistics = 0;
+ wunlock_base_node(thiz);
+ wunlock_base_node(neighbor);
+ return;
+ } else {
+ lock_route_node(parent);
+ parent->u.route.is_valid = 0;
+ neighbor->u.base.is_valid = 0;
+ thiz->u.base.is_valid = 0;
+ gparent = NULL;
+ do {
+ if (gparent != NULL) {
+ unlock_route_node(gparent);
+ }
+ gparent = parent_of(tb, parent);
+ if (gparent != NULL) {
+ lock_route_node(gparent);
+ } else {
+ gparent = NULL;
+ }
+ } while (gparent != NULL && !gparent->u.route.is_valid);
+ if (gparent == NULL) {
+ SET_ROOT_RELB(tb, GET_LEFT(parent));
+ } else if (GET_RIGHT(gparent) == parent) {
+ SET_RIGHT_RELB(gparent, GET_LEFT(parent));
+ } else {
+ SET_LEFT_RELB(gparent, GET_LEFT(parent));
+ }
+ unlock_route_node(parent);
+ if (gparent != NULL) {
+ unlock_route_node(gparent);
+ }
+ {
+ TreeDbTerm* new_root = join_trees(neighbor->u.base.root,
+ thiz->u.base.root);
+ new_neighbor = create_base_node(tb, new_root);
+ }
+ if (GET_LEFT(parent) == neighbor) {
+ neighbor_parent = gparent;
+ } else {
+ neighbor_parent =
+ rightmost_route_node(GET_LEFT(parent));
+ }
+ }
+ }
+ /* Link in new neighbor and free nodes that are no longer in the tree */
+ if (neighbor_parent == NULL) {
+ SET_ROOT_RELB(tb, new_neighbor);
+ } else if (GET_LEFT(neighbor_parent) == neighbor) {
+ SET_LEFT_RELB(neighbor_parent, new_neighbor);
+ } else {
+ SET_RIGHT_RELB(neighbor_parent, new_neighbor);
+ }
+ wunlock_base_node(thiz);
+ wunlock_base_node(neighbor);
+ /* Free the parent and base */
+ erts_schedule_db_free(&tb->common,
+ do_free_route_node,
+ parent,
+ &parent->u.route.free_item,
+ sizeof_route_node(parent->u.route.key.size));
+ erts_schedule_db_free(&tb->common,
+ do_free_base_node,
+ thiz,
+ &thiz->u.base.free_item,
+ sizeof_base_node());
+ erts_schedule_db_free(&tb->common,
+ do_free_base_node,
+ neighbor,
+ &neighbor->u.base.free_item,
+ sizeof_base_node());
+}
+
+static void split_catree(DbTableCATree *tb,
+ DbTableCATreeNode* ERTS_RESTRICT base,
+ DbTableCATreeNode* ERTS_RESTRICT parent)
+{
+ TreeDbTerm *splitOutWriteBack;
+ DbTableCATreeNode* ERTS_RESTRICT new_left;
+ DbTableCATreeNode* ERTS_RESTRICT new_right;
+ DbTableCATreeNode* ERTS_RESTRICT new_route;
+
+ if (less_than_two_elements(base->u.base.root)) {
+ if (!(tb->common.status & DB_CATREE_FORCE_SPLIT))
+ base->u.base.lock_statistics = 0;
+ wunlock_base_node(base);
+ return;
+ } else {
+ TreeDbTerm *left_tree;
+ TreeDbTerm *right_tree;
+
+ split_tree(tb, base->u.base.root, &splitOutWriteBack,
+ &left_tree, &right_tree);
+
+ new_left = create_base_node(tb, left_tree);
+ new_right = create_base_node(tb, right_tree);
+ new_route = create_route_node(tb,
+ new_left,
+ new_right,
+ &splitOutWriteBack->dbterm,
+ parent);
+ if (parent == NULL) {
+ SET_ROOT_RELB(tb, new_route);
+ } else if(GET_LEFT(parent) == base) {
+ SET_LEFT_RELB(parent, new_route);
+ } else {
+ SET_RIGHT_RELB(parent, new_route);
+ }
+ base->u.base.is_valid = 0;
+ wunlock_base_node(base);
+ erts_schedule_db_free(&tb->common,
+ do_free_base_node,
+ base,
+ &base->u.base.free_item,
+ sizeof_base_node());
+ }
+}
+
+/*
+ * Helper functions for removing the table
+ */
+
+static void catree_add_base_node_to_free_list(
+ DbTableCATree *tb,
+ DbTableCATreeNode *base_node_container)
+{
+ base_node_container->u.base.next =
+ tb->base_nodes_to_free_list;
+ tb->base_nodes_to_free_list = base_node_container;
+}
+
+static void catree_deque_base_node_from_free_list(DbTableCATree *tb)
+{
+ if (tb->base_nodes_to_free_list == NULL) {
+ return; /* List empty */
+ } else {
+ DbTableCATreeNode *first = tb->base_nodes_to_free_list;
+ tb->base_nodes_to_free_list = first->u.base.next;
+ }
+}
+
+static DbTableCATreeNode *catree_first_base_node_from_free_list(
+ DbTableCATree *tb)
+{
+ return tb->base_nodes_to_free_list;
+}
+
+static SWord do_free_routing_nodes_catree_cont(DbTableCATree *tb, SWord num_left)
+{
+ DbTableCATreeNode *root;
+ DbTableCATreeNode *p;
+ for (;;) {
+ root = POP_NODE(&tb->free_stack_rnodes);
+ if (root == NULL) break;
+ else if(root->is_base_node) {
+ catree_add_base_node_to_free_list(tb, root);
+ break;
+ }
+ for (;;) {
+ if ((GET_LEFT(root) != NULL) &&
+ (p = GET_LEFT(root))->is_base_node) {
+ SET_LEFT(root, NULL);
+ catree_add_base_node_to_free_list(tb, p);
+ } else if ((GET_RIGHT(root) != NULL) &&
+ (p = GET_RIGHT(root))->is_base_node) {
+ SET_RIGHT(root, NULL);
+ catree_add_base_node_to_free_list(tb, p);
+ } else if ((p = GET_LEFT(root)) != NULL) {
+ SET_LEFT(root, NULL);
+ PUSH_NODE(&tb->free_stack_rnodes, root);
+ root = p;
+ } else if ((p = GET_RIGHT(root)) != NULL) {
+ SET_RIGHT(root, NULL);
+ PUSH_NODE(&tb->free_stack_rnodes, root);
+ root = p;
+ } else {
+ free_catree_route_node(tb, root);
+ if (--num_left >= 0) {
+ break;
+ } else {
+ return num_left; /* Done enough for now */
+ }
+ }
+ }
+ }
+ return num_left;
+}
+
+static SWord do_free_base_node_cont(DbTableCATree *tb, SWord num_left)
+{
+ TreeDbTerm *root;
+ TreeDbTerm *p;
+ DbTableCATreeNode *base_node_container =
+ catree_first_base_node_from_free_list(tb);
+ for (;;) {
+ root = POP_NODE(&tb->free_stack_elems);
+ if (root == NULL) break;
+ for (;;) {
+ if ((p = root->left) != NULL) {
+ root->left = NULL;
+ PUSH_NODE(&tb->free_stack_elems, root);
+ root = p;
+ } else if ((p = root->right) != NULL) {
+ root->right = NULL;
+ PUSH_NODE(&tb->free_stack_elems, root);
+ root = p;
+ } else {
+ free_term((DbTable*)tb, root);
+ if (--num_left >= 0) {
+ break;
+ } else {
+ return num_left; /* Done enough for now */
+ }
+ }
+ }
+ }
+ catree_deque_base_node_from_free_list(tb);
+ free_catree_base_node(tb, base_node_container);
+ base_node_container = catree_first_base_node_from_free_list(tb);
+ if (base_node_container != NULL) {
+ PUSH_NODE(&tb->free_stack_elems, base_node_container->u.base.root);
+ }
+ return num_left;
+}
+
+
+/*
+** Initialization function
+*/
+
+void db_initialize_catree(void)
+{
+ return;
+};
+
+/*
+** Table interface routines (i.e., what's called by the bif's)
+*/
+
+int db_create_catree(Process *p, DbTable *tbl)
+{
+ DbTableCATree *tb = &tbl->catree;
+ DbTableCATreeNode *root;
+
+ root = create_base_node(tb, NULL);
+ tb->deletion = 0;
+ tb->base_nodes_to_free_list = NULL;
+ erts_atomic_init_relb(&(tb->root), (erts_aint_t)root);
+ return DB_ERROR_NONE;
+}
+
+static int db_first_catree(Process *p, DbTable *tbl, Eterm *ret)
+{
+ TreeDbTerm *root;
+ CATreeRootIterator iter;
+ int result;
+
+ init_root_iterator(&tbl->catree, &iter, 1);
+ root = *catree_find_first_root(&iter);
+ if (!root) {
+ TreeDbTerm **pp = catree_find_next_root(&iter, NULL);
+ root = pp ? *pp : NULL;
+ }
+
+ result = db_first_tree_common(p, tbl, root, ret, NULL);
+
+ destroy_root_iterator(&iter);
+ return result;
+}
+
+static int db_next_catree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
+{
+ DbTreeStack stack;
+ TreeDbTerm * stack_array[STACK_NEED];
+ TreeDbTerm **rootp;
+ CATreeRootIterator iter;
+ int result;
+
+ init_root_iterator(&tbl->catree, &iter, 1);
+ iter.next_route_key = key;
+ rootp = catree_find_next_root(&iter, NULL);
+
+ do {
+ init_tree_stack(&stack, stack_array, 0);
+ result = db_next_tree_common(p, tbl, (rootp ? *rootp : NULL), key, ret, &stack);
+ if (result != DB_ERROR_NONE || *ret != am_EOT)
+ break;
+
+ rootp = catree_find_next_root(&iter, NULL);
+ } while (rootp);
+
+ destroy_root_iterator(&iter);
+ return result;
+}
+
+static int db_last_catree(Process *p, DbTable *tbl, Eterm *ret)
+{
+ TreeDbTerm *root;
+ CATreeRootIterator iter;
+ int result;
+
+ init_root_iterator(&tbl->catree, &iter, 1);
+ root = *catree_find_last_root(&iter);
+ if (!root) {
+ TreeDbTerm **pp = catree_find_prev_root(&iter, NULL);
+ root = pp ? *pp : NULL;
+ }
+
+ result = db_last_tree_common(p, tbl, root, ret, NULL);
+
+ destroy_root_iterator(&iter);
+ return result;
+}
+
+static int db_prev_catree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
+{
+ DbTreeStack stack;
+ TreeDbTerm * stack_array[STACK_NEED];
+ TreeDbTerm **rootp;
+ CATreeRootIterator iter;
+ int result;
+
+ init_root_iterator(&tbl->catree, &iter, 1);
+ iter.next_route_key = key;
+ rootp = catree_find_prev_root(&iter, NULL);
+
+ do {
+ init_tree_stack(&stack, stack_array, 0);
+ result = db_prev_tree_common(p, tbl, (rootp ? *rootp : NULL), key, ret,
+ &stack);
+ if (result != DB_ERROR_NONE || *ret != am_EOT)
+ break;
+ rootp = catree_find_prev_root(&iter, NULL);
+ } while (rootp);
+
+ destroy_root_iterator(&iter);
+ return result;
+}
+
+static int db_put_catree(DbTable *tbl, Eterm obj, int key_clash_fail)
+{
+ DbTableCATree *tb = &tbl->catree;
+ Eterm key = GETKEY(&tb->common, tuple_val(obj));
+ FindBaseNode fbn;
+ DbTableCATreeNode* node = find_wlock_valid_base_node(tb, key, &fbn);
+ int result = db_put_tree_common(&tb->common, &node->u.base.root, obj,
+ key_clash_fail, NULL);
+ wunlock_adapt_base_node(tb, node, fbn.parent, fbn.current_level);
+ return result;
+}
+
+static int db_get_catree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
+{
+ DbTableCATree *tb = &tbl->catree;
+ DbTableCATreeNode* node = find_rlock_valid_base_node(tb, key);
+ int result = db_get_tree_common(p, &tb->common,
+ node->u.base.root,
+ key, ret, NULL);
+ runlock_base_node(node);
+ return result;
+}
+
+TreeDbTerm** catree_find_root(Eterm key, CATreeRootIterator* iter)
+{
+ FindBaseNode fbn;
+ DbTableCATreeNode* base_node;
+
+ while (1) {
+ base_node = find_base_node(iter->tb, key, &fbn);
+ lock_iter_base_node(iter, base_node, fbn.parent, fbn.current_level);
+ if (base_node->u.base.is_valid)
+ break;
+ unlock_iter_base_node(iter);
+ }
+ return &base_node->u.base.root;
+}
+
+static Eterm save_iter_search_key(CATreeRootIterator* iter, Eterm key)
+{
+ Uint key_size;
+
+ if (is_immed(key))
+ return key;
+
+ if (iter->search_key) {
+ if (key == iter->search_key->term)
+ return key; /* already saved */
+ destroy_route_key(iter->search_key);
+ }
+ key_size = size_object(key);
+ if (!iter->search_key || key_size > iter->search_key->size) {
+ iter->search_key = erts_realloc(ERTS_ALC_T_DB_TMP,
+ iter->search_key,
+ (offsetof(DbRouteKey, heap)
+ + key_size*sizeof(Eterm)));
+ }
+ return copy_route_key(iter->search_key, key, key_size);
+}
+
+TreeDbTerm** catree_find_nextprev_root(CATreeRootIterator *iter,
+ int forward,
+ Eterm *search_keyp)
+{
+#ifdef DEBUG
+ DbTableCATreeNode *rejected_invalid = NULL;
+ DbTableCATreeNode *rejected_empty = NULL;
+#endif
+ DbTableCATreeNode *node;
+ DbTableCATreeNode *parent;
+ DbTableCATreeNode* next_route_node;
+ Eterm route_key = iter->next_route_key;
+ int current_level;
+
+ if (iter->locked_bnode) {
+ if (search_keyp)
+ *search_keyp = save_iter_search_key(iter, *search_keyp);
+ unlock_iter_base_node(iter);
+ }
+
+ if (is_non_value(route_key))
+ return NULL;
+
+ while (1) {
+ node = GET_ROOT_ACQB(iter->tb);
+ current_level = 0;
+ parent = NULL;
+ next_route_node = NULL;
+ while (!node->is_base_node) {
+ current_level++;
+ parent = node;
+ if (forward) {
+ if (cmp_key_route(route_key,node) < 0) {
+ next_route_node = node;
+ node = GET_LEFT_ACQB(node);
+ } else {
+ node = GET_RIGHT_ACQB(node);
+ }
+ }
+ else {
+ if (cmp_key_route(route_key,node) > 0) {
+ next_route_node = node;
+ node = GET_RIGHT_ACQB(node);
+ } else {
+ node = GET_LEFT_ACQB(node);
+ }
+ }
+ }
+ ASSERT(node != rejected_invalid);
+ lock_iter_base_node(iter, node, parent, current_level);
+ if (node->u.base.is_valid) {
+ ASSERT(node != rejected_empty);
+ if (node->u.base.root) {
+ iter->next_route_key = (next_route_node ?
+ next_route_node->u.route.key.term :
+ THE_NON_VALUE);
+ iter->locked_bnode = node;
+ return &node->u.base.root;
+ }
+ if (!next_route_node) {
+ unlock_iter_base_node(iter);
+ return NULL;
+ }
+ route_key = next_route_node->u.route.key.term;
+ IF_DEBUG(rejected_empty = node);
+ }
+ else
+ IF_DEBUG(rejected_invalid = node);
+
+ /* Retry */
+ unlock_iter_base_node(iter);
+ }
+}
+
+TreeDbTerm** catree_find_next_root(CATreeRootIterator *iter, Eterm* keyp)
+{
+ return catree_find_nextprev_root(iter, 1, keyp);
+}
+
+TreeDbTerm** catree_find_prev_root(CATreeRootIterator *iter, Eterm* keyp)
+{
+ return catree_find_nextprev_root(iter, 0, keyp);
+}
+
+/* @brief Find root of tree where object with smallest key of all larger than
+ * partially bound key may reside. Can be used as a starting point for
+ * a reverse iteration with pb_key.
+ *
+ * @param pb_key The partially bound key. Example {42, '$1'}
+ * @param iter An initialized root iterator.
+ *
+ * @return Pointer to found root pointer. May not be NULL.
+ */
+TreeDbTerm** catree_find_next_from_pb_key_root(Eterm pb_key,
+ CATreeRootIterator* iter)
+{
+#ifdef DEBUG
+ DbTableCATreeNode *rejected_base = NULL;
+#endif
+ DbTableCATreeNode *node;
+ DbTableCATreeNode *parent;
+ DbTableCATreeNode* next_route_node;
+ int current_level;
+
+ ASSERT(!iter->locked_bnode);
+
+ while (1) {
+ node = GET_ROOT_ACQB(iter->tb);
+ current_level = 0;
+ parent = NULL;
+ next_route_node = NULL;
+ while (!node->is_base_node) {
+ current_level++;
+ parent = node;
+ if (cmp_partly_bound(pb_key, GET_ROUTE_NODE_KEY(node)) >= 0) {
+ next_route_node = node;
+ node = GET_RIGHT_ACQB(node);
+ } else {
+ node = GET_LEFT_ACQB(node);
+ }
+ }
+ ASSERT(node != rejected_base);
+ lock_iter_base_node(iter, node, parent, current_level);
+ if (node->u.base.is_valid) {
+ iter->next_route_key = (next_route_node ?
+ next_route_node->u.route.key.term :
+ THE_NON_VALUE);
+ return &node->u.base.root;
+ }
+ /* Retry */
+ unlock_iter_base_node(iter);
+#ifdef DEBUG
+ rejected_base = node;
+#endif
+ }
+}
+
+/* @brief Find root of tree where object with largest key of all smaller than
+ * partially bound key may reside. Can be used as a starting point for
+ * a forward iteration with pb_key.
+ *
+ * @param pb_key The partially bound key. Example {42, '$1'}
+ * @param iter An initialized root iterator.
+ *
+ * @return Pointer to found root pointer. May not be NULL.
+ */
+TreeDbTerm** catree_find_prev_from_pb_key_root(Eterm key,
+ CATreeRootIterator* iter)
+{
+#ifdef DEBUG
+ DbTableCATreeNode *rejected_base = NULL;
+#endif
+ DbTableCATreeNode *node;
+ DbTableCATreeNode *parent;
+ DbTableCATreeNode* next_route_node;
+ int current_level;
+
+ ASSERT(!iter->locked_bnode);
+
+ while (1) {
+ node = GET_ROOT_ACQB(iter->tb);
+ current_level = 0;
+ parent = NULL;
+ next_route_node = NULL;
+ while (!node->is_base_node) {
+ current_level++;
+ parent = node;
+ if (cmp_partly_bound(key, GET_ROUTE_NODE_KEY(node)) <= 0) {
+ next_route_node = node;
+ node = GET_LEFT_ACQB(node);
+ } else {
+ node = GET_RIGHT_ACQB(node);
+ }
+ }
+ ASSERT(node != rejected_base);
+ lock_iter_base_node(iter, node, parent, current_level);
+ if (node->u.base.is_valid) {
+ iter->next_route_key = (next_route_node ?
+ next_route_node->u.route.key.term :
+ THE_NON_VALUE);
+ return &node->u.base.root;
+ }
+ /* Retry */
+ unlock_iter_base_node(iter);
+#ifdef DEBUG
+ rejected_base = node;
+#endif
+ }
+}
+
+static TreeDbTerm** catree_find_firstlast_root(CATreeRootIterator* iter,
+ int first)
+{
+#ifdef DEBUG
+ DbTableCATreeNode *rejected_base = NULL;
+#endif
+ DbTableCATreeNode *node;
+ DbTableCATreeNode* next_route_node;
+ int current_level;
+
+ while (1) {
+ node = GET_ROOT_ACQB(iter->tb);
+ current_level = 0;
+ next_route_node = NULL;
+ while (!node->is_base_node) {
+ current_level++;
+ next_route_node = node;
+ node = first ? GET_LEFT_ACQB(node) : GET_RIGHT_ACQB(node);
+ }
+ ASSERT(node != rejected_base);
+ lock_iter_base_node(iter, node, next_route_node, current_level);
+ if (node->u.base.is_valid) {
+ iter->next_route_key = (next_route_node ?
+ next_route_node->u.route.key.term :
+ THE_NON_VALUE);
+ return &node->u.base.root;
+ }
+ /* Retry */
+ unlock_iter_base_node(iter);
+#ifdef DEBUG
+ rejected_base = node;
+#endif
+ }
+}
+
+TreeDbTerm** catree_find_first_root(CATreeRootIterator* iter)
+{
+ return catree_find_firstlast_root(iter, 1);
+}
+
+TreeDbTerm** catree_find_last_root(CATreeRootIterator* iter)
+{
+ return catree_find_firstlast_root(iter, 0);
+}
+
+static int db_member_catree(DbTable *tbl, Eterm key, Eterm *ret)
+{
+ DbTableCATree *tb = &tbl->catree;
+ DbTableCATreeNode* node = find_rlock_valid_base_node(tb, key);
+ int result = db_member_tree_common(&tb->common,
+ node->u.base.root,
+ key, ret, NULL);
+ runlock_base_node(node);
+ return result;
+}
+
+static int db_get_element_catree(Process *p, DbTable *tbl,
+ Eterm key, int ndex, Eterm *ret)
+{
+ DbTableCATree *tb = &tbl->catree;
+ DbTableCATreeNode* node = find_rlock_valid_base_node(tb, key);
+ int result = db_get_element_tree_common(p, &tb->common,
+ node->u.base.root,
+ key, ndex, ret, NULL);
+ runlock_base_node(node);
+ return result;
+}
+
+static int db_erase_catree(DbTable *tbl, Eterm key, Eterm *ret)
+{
+ DbTableCATree *tb = &tbl->catree;
+ FindBaseNode fbn;
+ DbTableCATreeNode* node = find_wlock_valid_base_node(tb, key, &fbn);
+ int result = db_erase_tree_common(tbl, &node->u.base.root, key,
+ ret, NULL);
+ wunlock_adapt_base_node(tb, node, fbn.parent, fbn.current_level);
+ return result;
+}
+
+static int db_erase_object_catree(DbTable *tbl, Eterm object, Eterm *ret)
+{
+ DbTableCATree *tb = &tbl->catree;
+ Eterm key = GETKEY(&tb->common, tuple_val(object));
+ FindBaseNode fbn;
+ DbTableCATreeNode* node = find_wlock_valid_base_node(tb, key, &fbn);
+ int result = db_erase_object_tree_common(tbl,
+ &node->u.base.root,
+ object,
+ ret,
+ NULL);
+ wunlock_adapt_base_node(tb, node, fbn.parent, fbn.current_level);
+ return result;
+}
+
+
+static int db_slot_catree(Process *p, DbTable *tbl,
+ Eterm slot_term, Eterm *ret)
+{
+ int result;
+ CATreeRootIterator iter;
+
+ init_root_iterator(&tbl->catree, &iter, 1);
+ result = db_slot_tree_common(p, tbl, *catree_find_first_root(&iter),
+ slot_term, ret, NULL, &iter);
+ destroy_root_iterator(&iter);
+ return result;
+}
+
+static int db_select_continue_catree(Process *p,
+ DbTable *tbl,
+ Eterm continuation,
+ Eterm *ret)
+{
+ int result;
+ CATreeRootIterator iter;
+
+ init_root_iterator(&tbl->catree, &iter, 1);
+ result = db_select_continue_tree_common(p, &tbl->common,
+ continuation, ret, NULL, &iter);
+ destroy_root_iterator(&iter);
+ return result;
+}
+
+static int db_select_catree(Process *p, DbTable *tbl, Eterm tid,
+ Eterm pattern, int reverse, Eterm *ret)
+{
+ int result;
+ CATreeRootIterator iter;
+
+ init_root_iterator(&tbl->catree, &iter, 1);
+ result = db_select_tree_common(p, tbl, tid, pattern, reverse, ret,
+ NULL, &iter);
+ destroy_root_iterator(&iter);
+ return result;
+}
+
+static int db_select_count_continue_catree(Process *p,
+ DbTable *tbl,
+ Eterm continuation,
+ Eterm *ret)
+{
+ int result;
+ CATreeRootIterator iter;
+
+ init_root_iterator(&tbl->catree, &iter, 1);
+ result = db_select_count_continue_tree_common(p, tbl,
+ continuation, ret, NULL,
+ &iter);
+ destroy_root_iterator(&iter);
+ return result;
+}
+
+static int db_select_count_catree(Process *p, DbTable *tbl, Eterm tid,
+ Eterm pattern, Eterm *ret)
+{
+ int result;
+ CATreeRootIterator iter;
+
+ init_root_iterator(&tbl->catree, &iter, 1);
+ result = db_select_count_tree_common(p, tbl,
+ tid, pattern, ret, NULL, &iter);
+ destroy_root_iterator(&iter);
+ return result;
+}
+
+static int db_select_chunk_catree(Process *p, DbTable *tbl, Eterm tid,
+ Eterm pattern, Sint chunk_size,
+ int reversed, Eterm *ret)
+{
+ int result;
+ CATreeRootIterator iter;
+
+ init_root_iterator(&tbl->catree, &iter, 1);
+ result = db_select_chunk_tree_common(p, tbl,
+ tid, pattern, chunk_size, reversed, ret,
+ NULL, &iter);
+ destroy_root_iterator(&iter);
+ return result;
+}
+
+static int db_select_delete_continue_catree(Process *p,
+ DbTable *tbl,
+ Eterm continuation,
+ Eterm *ret)
+{
+ DbTreeStack stack;
+ TreeDbTerm * stack_array[STACK_NEED];
+ int result;
+ CATreeRootIterator iter;
+
+ init_root_iterator(&tbl->catree, &iter, 0);
+ init_tree_stack(&stack, stack_array, 0);
+ result = db_select_delete_continue_tree_common(p, tbl, continuation, ret,
+ &stack, &iter);
+ destroy_root_iterator(&iter);
+ return result;
+}
+
+static int db_select_delete_catree(Process *p, DbTable *tbl, Eterm tid,
+ Eterm pattern, Eterm *ret)
+{
+ DbTreeStack stack;
+ TreeDbTerm * stack_array[STACK_NEED];
+ int result;
+ CATreeRootIterator iter;
+
+ init_root_iterator(&tbl->catree, &iter, 0);
+ init_tree_stack(&stack, stack_array, 0);
+ result = db_select_delete_tree_common(p, tbl,
+ tid, pattern, ret, &stack,
+ &iter);
+ destroy_root_iterator(&iter);
+ return result;
+}
+
+static int db_select_replace_catree(Process *p, DbTable *tbl, Eterm tid,
+ Eterm pattern, Eterm *ret)
+{
+ int result;
+ CATreeRootIterator iter;
+
+ init_root_iterator(&tbl->catree, &iter, 0);
+ result = db_select_replace_tree_common(p, tbl,
+ tid, pattern, ret, NULL, &iter);
+ destroy_root_iterator(&iter);
+ return result;
+}
+
+static int db_select_replace_continue_catree(Process *p, DbTable *tbl,
+ Eterm continuation, Eterm *ret)
+{
+ int result;
+ CATreeRootIterator iter;
+
+ init_root_iterator(&tbl->catree, &iter, 0);
+ result = db_select_replace_continue_tree_common(p, tbl, continuation, ret,
+ NULL, &iter);
+ destroy_root_iterator(&iter);
+ return result;
+}
+
+static int db_take_catree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
+{
+ DbTableCATree *tb = &tbl->catree;
+ FindBaseNode fbn;
+ DbTableCATreeNode* node = find_wlock_valid_base_node(tb, key, &fbn);
+ int result = db_take_tree_common(p, tbl, &node->u.base.root, key,
+ ret, NULL);
+ wunlock_adapt_base_node(tb, node, fbn.parent, fbn.current_level);
+ return result;
+}
+
+/*
+** Other interface routines (not directly coupled to one bif)
+*/
+
+
+/* Display tree contents (for dump) */
+static void db_print_catree(fmtfn_t to, void *to_arg,
+ int show, DbTable *tbl)
+{
+ CATreeRootIterator iter;
+ TreeDbTerm** root;
+
+ init_root_iterator(&tbl->catree, &iter, 1);
+ root = catree_find_first_root(&iter);
+ do {
+ db_print_tree_common(to, to_arg, show, *root, tbl);
+ root = catree_find_next_root(&iter, NULL);
+ } while (root);
+ destroy_root_iterator(&iter);
+}
+
+/* Release all memory occupied by a single table */
+static int db_free_table_catree(DbTable *tbl)
+{
+ while (db_free_table_continue_catree(tbl, ERTS_SWORD_MAX) < 0)
+ ;
+ return 1;
+}
+
+static SWord db_free_table_continue_catree(DbTable *tbl, SWord reds)
+{
+ DbTableCATreeNode *first_base_node;
+ DbTableCATree *tb = &tbl->catree;
+ if (!tb->deletion) {
+ tb->deletion = 1;
+ tb->free_stack_elems.array =
+ erts_db_alloc(ERTS_ALC_T_DB_STK,
+ (DbTable *) tb,
+ sizeof(TreeDbTerm *) * STACK_NEED);
+ tb->free_stack_elems.pos = 0;
+ tb->free_stack_elems.slot = 0;
+ tb->free_stack_rnodes.array =
+ erts_db_alloc(ERTS_ALC_T_DB_STK,
+ (DbTable *) tb,
+ sizeof(DbTableCATreeNode *) * STACK_NEED);
+ tb->free_stack_rnodes.pos = 0;
+ tb->free_stack_rnodes.size = STACK_NEED;
+ PUSH_NODE(&tb->free_stack_rnodes, GET_ROOT(tb));
+ tb->is_routing_nodes_freed = 0;
+ tb->base_nodes_to_free_list = NULL;
+ }
+ if ( ! tb->is_routing_nodes_freed ) {
+ reds = do_free_routing_nodes_catree_cont(tb, reds);
+ if (reds < 0) {
+ return reds; /* Not finished */
+ } else {
+ tb->is_routing_nodes_freed = 1; /* Ready with the routing nodes */
+ first_base_node = catree_first_base_node_from_free_list(tb);
+ PUSH_NODE(&tb->free_stack_elems, first_base_node->u.base.root);
+ }
+ }
+ while (catree_first_base_node_from_free_list(tb) != NULL) {
+ reds = do_free_base_node_cont(tb, reds);
+ if (reds < 0) {
+ return reds; /* Continue later */
+ }
+ }
+ /* Time to free the main structure*/
+ erts_db_free(ERTS_ALC_T_DB_STK,
+ (DbTable *) tb,
+ (void *) tb->free_stack_elems.array,
+ sizeof(TreeDbTerm *) * STACK_NEED);
+ erts_db_free(ERTS_ALC_T_DB_STK,
+ (DbTable *) tb,
+ (void *) tb->free_stack_rnodes.array,
+ sizeof(DbTableCATreeNode *) * STACK_NEED);
+ return 1;
+}
+
+static SWord db_delete_all_objects_catree(Process* p, DbTable* tbl, SWord reds)
+{
+ reds = db_free_table_continue_catree(tbl, reds);
+ if (reds < 0)
+ return reds;
+ db_create_catree(p, tbl);
+ erts_atomic_set_nob(&tbl->catree.common.nitems, 0);
+ return reds;
+}
+
+
+static void do_for_route_nodes(DbTableCATreeNode* node,
+ void (*func)(ErlOffHeap *, void *),
+ void *arg)
+{
+ ErlOffHeap tmp_offheap;
+
+ if (!GET_LEFT(node)->is_base_node)
+ do_for_route_nodes(GET_LEFT(node), func, arg);
+
+ tmp_offheap.first = node->u.route.key.oh;
+ tmp_offheap.overhead = 0;
+ (*func)(&tmp_offheap, arg);
+
+ if (!GET_RIGHT(node)->is_base_node)
+ do_for_route_nodes(GET_RIGHT(node), func, arg);
+}
+
+static void db_foreach_offheap_catree(DbTable *tbl,
+ void (*func)(ErlOffHeap *, void *),
+ void *arg)
+{
+ CATreeRootIterator iter;
+ TreeDbTerm** root;
+
+ init_root_iterator(&tbl->catree, &iter, 1);
+ root = catree_find_first_root(&iter);
+ do {
+ db_foreach_offheap_tree_common(*root, func, arg);
+ root = catree_find_next_root(&iter, NULL);
+ } while (root);
+ destroy_root_iterator(&iter);
+
+ do_for_route_nodes(GET_ROOT(&tbl->catree), func, arg);
+}
+
+static int db_lookup_dbterm_catree(Process *p, DbTable *tbl, Eterm key, Eterm obj,
+ DbUpdateHandle *handle)
+{
+ DbTableCATree *tb = &tbl->catree;
+ FindBaseNode fbn;
+ DbTableCATreeNode* node = find_wlock_valid_base_node(tb, key, &fbn);
+ int res = db_lookup_dbterm_tree_common(p, tbl, &node->u.base.root, key,
+ obj, handle, NULL);
+ if (res == 0) {
+ wunlock_adapt_base_node(tb, node, fbn.parent, fbn.current_level);
+ } else {
+ /* db_finalize_dbterm_catree will unlock */
+ handle->u.catree.base_node = node;
+ handle->u.catree.parent = fbn.parent;
+ handle->u.catree.current_level = fbn.current_level;
+ }
+ return res;
+}
+
+static void db_finalize_dbterm_catree(int cret, DbUpdateHandle *handle)
+{
+ DbTableCATree *tb = &(handle->tb->catree);
+ db_finalize_dbterm_tree_common(cret, handle, NULL);
+ wunlock_adapt_base_node(tb, handle->u.catree.base_node,
+ handle->u.catree.parent,
+ handle->u.catree.current_level);
+ return;
+}
+
+#ifdef ERTS_ENABLE_LOCK_COUNT
+static void erts_lcnt_enable_db_catree_lock_count_helper(DbTableCATree *tb,
+ DbTableCATreeNode *node,
+ int enable)
+{
+ erts_lcnt_ref_t *lcnt_ref;
+ erts_lock_flags_t lock_type;
+ if (node->is_base_node) {
+ lcnt_ref = &GET_BASE_NODE_LOCK(node)->lcnt;
+ lock_type = ERTS_LOCK_TYPE_RWMUTEX;
+ } else {
+ erts_lcnt_enable_db_catree_lock_count_helper(tb, GET_LEFT(node), enable);
+ erts_lcnt_enable_db_catree_lock_count_helper(tb, GET_RIGHT(node), enable);
+ lcnt_ref = &GET_ROUTE_NODE_LOCK(node)->lcnt;
+ lock_type = ERTS_LOCK_TYPE_MUTEX;
+ }
+ if (enable) {
+ erts_lcnt_install_new_lock_info(lcnt_ref, "db_hash_slot", tb->common.the_name,
+ lock_type | ERTS_LOCK_FLAGS_CATEGORY_DB);
+ } else {
+ erts_lcnt_uninstall(lcnt_ref);
+ }
+}
+
+void erts_lcnt_enable_db_catree_lock_count(DbTableCATree *tb, int enable)
+{
+ erts_lcnt_enable_db_catree_lock_count_helper(tb, GET_ROOT(tb), enable);
+}
+#endif /* ERTS_ENABLE_LOCK_COUNT */
+
+void db_catree_force_split(DbTableCATree* tb, int on)
+{
+ CATreeRootIterator iter;
+ TreeDbTerm** root;
+
+ init_root_iterator(tb, &iter, 1);
+ root = catree_find_first_root(&iter);
+ do {
+ iter.locked_bnode->u.base.lock_statistics = (on ? INT_MAX : 0);
+ root = catree_find_next_root(&iter, NULL);
+ } while (root);
+ destroy_root_iterator(&iter);
+
+ if (on)
+ tb->common.status |= DB_CATREE_FORCE_SPLIT;
+ else
+ tb->common.status &= ~DB_CATREE_FORCE_SPLIT;
+}
+
+void db_calc_stats_catree(DbTableCATree* tb, DbCATreeStats* stats)
+{
+ DbTableCATreeNode* stack[ERL_DB_CATREE_MAX_ROUTE_NODE_LAYER_HEIGHT];
+ DbTableCATreeNode* node;
+ Uint depth = 0;
+
+ stats->route_nodes = 0;
+ stats->base_nodes = 0;
+ stats->max_depth = 0;
+
+ node = GET_ROOT(tb);
+ do {
+ while (!node->is_base_node) {
+ stats->route_nodes++;
+ ASSERT(depth < sizeof(stack)/sizeof(*stack));
+ stack[depth++] = node; /* PUSH parent */
+ if (stats->max_depth < depth)
+ stats->max_depth = depth;
+ node = GET_LEFT(node);
+ }
+ stats->base_nodes++;
+
+ while (depth > 0) {
+ DbTableCATreeNode* parent = stack[depth-1];
+ if (node == GET_LEFT(parent)) {
+ node = GET_RIGHT(parent);
+ break;
+ }
+ else {
+ ASSERT(node == GET_RIGHT(parent));
+ node = parent;
+ depth--; /* POP parent */
+ }
+ }
+ } while (depth > 0);
+}
+
+#ifdef HARDDEBUG
+
+/*
+ * Not called, but kept as it might come to use
+ */
+static inline int my_check_table_tree(TreeDbTerm *t)
+{
+ int lh, rh;
+ if (t == NULL)
+ return 0;
+ lh = my_check_table_tree(t->left);
+ rh = my_check_table_tree(t->right);
+ if ((rh - lh) != t->balance) {
+ erts_fprintf(stderr, "Invalid tree balance for this node:\n");
+ erts_fprintf(stderr,"balance = %d, left = 0x%08X, right = 0x%08X\n",
+ t->balance, t->left, t->right);
+ erts_fprintf(stderr,"\nDump:\n---------------------------------\n");
+ erts_fprintf(stderr,"\n---------------------------------\n");
+ abort();
+ }
+ return ((rh > lh) ? rh : lh) + 1;
+}
+
+#endif
diff --git a/erts/emulator/beam/erl_db_catree.h b/erts/emulator/beam/erl_db_catree.h
new file mode 100644
index 0000000000..418837be8e
--- /dev/null
+++ b/erts/emulator/beam/erl_db_catree.h
@@ -0,0 +1,133 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 1998-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%
+ */
+
+/*
+ * Description: Implementation of ETS ordered_set table type with
+ * fine-grained synchronization.
+ *
+ * Author: Kjell Winblad
+ *
+ * "erl_db_catree.c" contains more details about the implementation.
+ *
+ */
+
+#ifndef _DB_CATREE_H
+#define _DB_CATREE_H
+
+struct DbTableCATreeNode;
+
+typedef struct {
+ Eterm term;
+ struct erl_off_heap_header* oh;
+ Uint size;
+ Eterm heap[1];
+} DbRouteKey;
+
+typedef struct {
+ erts_rwmtx_t lock; /* The lock for this base node */
+ Sint lock_statistics;
+ int is_valid; /* If this base node is still valid */
+ TreeDbTerm *root; /* The root of the sequential tree */
+ ErtsThrPrgrLaterOp free_item; /* Used when freeing using thread progress */
+ struct DbTableCATreeNode * next; /* Used when gradually deleting */
+
+ char end_of_struct__;
+} DbTableCATreeBaseNode;
+
+typedef struct {
+#ifdef ERTS_ENABLE_LOCK_CHECK
+ Sint lc_order;
+#endif
+ ErtsThrPrgrLaterOp free_item; /* Used when freeing using thread progress */
+ erts_mtx_t lock; /* Used when joining route nodes */
+ int is_valid; /* If this route node is still valid */
+ erts_atomic_t left;
+ erts_atomic_t right;
+ DbRouteKey key;
+} DbTableCATreeRouteNode;
+
+typedef struct DbTableCATreeNode {
+ int is_base_node;
+ union {
+ DbTableCATreeRouteNode route;
+ DbTableCATreeBaseNode base;
+ } u;
+} DbTableCATreeNode;
+
+typedef struct {
+ Uint pos; /* Current position on stack */
+ Uint size; /* The size of the stack array */
+ DbTableCATreeNode** array; /* The stack */
+} CATreeNodeStack;
+
+typedef struct db_table_catree {
+ DbTableCommon common;
+
+ /* CA Tree-specific fields */
+ erts_atomic_t root; /* The tree root (DbTableCATreeNode*) */
+ Uint deletion; /* Being deleted */
+ DbTreeStack free_stack_elems;/* Used for deletion ...*/
+ CATreeNodeStack free_stack_rnodes;
+ DbTableCATreeNode *base_nodes_to_free_list;
+ int is_routing_nodes_freed;
+} DbTableCATree;
+
+typedef struct {
+ DbTableCATree* tb;
+ Eterm next_route_key;
+ DbTableCATreeNode* locked_bnode;
+ DbTableCATreeNode* bnode_parent;
+ int bnode_level;
+ int read_only;
+ DbRouteKey* search_key;
+} CATreeRootIterator;
+
+
+void db_initialize_catree(void);
+
+int db_create_catree(Process *p, DbTable *tbl);
+
+
+TreeDbTerm** catree_find_root(Eterm key, CATreeRootIterator*);
+
+TreeDbTerm** catree_find_next_from_pb_key_root(Eterm key, CATreeRootIterator*);
+TreeDbTerm** catree_find_prev_from_pb_key_root(Eterm key, CATreeRootIterator*);
+TreeDbTerm** catree_find_nextprev_root(CATreeRootIterator*, int next, Eterm* keyp);
+TreeDbTerm** catree_find_next_root(CATreeRootIterator*, Eterm* keyp);
+TreeDbTerm** catree_find_prev_root(CATreeRootIterator*, Eterm* keyp);
+TreeDbTerm** catree_find_first_root(CATreeRootIterator*);
+TreeDbTerm** catree_find_last_root(CATreeRootIterator*);
+
+
+#ifdef ERTS_ENABLE_LOCK_COUNT
+void erts_lcnt_enable_db_catree_lock_count(DbTableCATree *tb, int enable);
+#endif
+
+void db_catree_force_split(DbTableCATree*, int on);
+
+typedef struct {
+ Uint route_nodes;
+ Uint base_nodes;
+ Uint max_depth;
+} DbCATreeStats;
+void db_calc_stats_catree(DbTableCATree*, DbCATreeStats*);
+
+
+#endif /* _DB_CATREE_H */
diff --git a/erts/emulator/beam/erl_db_hash.c b/erts/emulator/beam/erl_db_hash.c
index 752d3ae3a8..f05a3b51c9 100644
--- a/erts/emulator/beam/erl_db_hash.c
+++ b/erts/emulator/beam/erl_db_hash.c
@@ -2731,13 +2731,9 @@ static int free_seg(DbTableHash *tb, int free_records)
* sure no lingering threads are still hanging in BUCKET macro
* with an old segtab pointer.
*/
- Uint sz = SIZEOF_EXT_SEGTAB(est->nsegs);
- ASSERT(sz == ERTS_ALC_DBG_BLK_SZ(est));
- ERTS_DB_ALC_MEM_UPDATE_(tb, sz, 0);
- erts_schedule_thr_prgr_later_cleanup_op(dealloc_ext_segtab,
- est,
- &est->lop,
- sz);
+ erts_schedule_db_free(&tb->common, dealloc_ext_segtab,
+ est, &est->lop,
+ SIZEOF_EXT_SEGTAB(est->nsegs));
}
else
erts_db_free(ERTS_ALC_T_DB_SEG, (DbTable*)tb, est,
@@ -3107,7 +3103,7 @@ Ldone:
handle->dbterm = &b->dbterm;
handle->flags = flags;
handle->new_size = b->dbterm.size;
- handle->lck = lck;
+ handle->u.hash.lck = lck;
return 1;
}
@@ -3120,7 +3116,7 @@ db_finalize_dbterm_hash(int cret, DbUpdateHandle* handle)
DbTableHash *tb = &tbl->hash;
HashDbTerm **bp = (HashDbTerm **) handle->bp;
HashDbTerm *b = *bp;
- erts_rwmtx_t* lck = (erts_rwmtx_t*) handle->lck;
+ erts_rwmtx_t* lck = handle->u.hash.lck;
HashDbTerm* free_me = NULL;
ERTS_LC_ASSERT(IS_HASH_WLOCKED(tb, lck)); /* locked by db_lookup_dbterm_hash */
diff --git a/erts/emulator/beam/erl_db_tree.c b/erts/emulator/beam/erl_db_tree.c
index 45e4be2426..02a5934a6e 100644
--- a/erts/emulator/beam/erl_db_tree.c
+++ b/erts/emulator/beam/erl_db_tree.c
@@ -48,34 +48,13 @@
#include "erl_binary.h"
#include "erl_db_tree.h"
+#include "erl_db_tree_util.h"
#define GETKEY_WITH_POS(Keypos, Tplp) (*((Tplp) + Keypos))
#define NITEMS(tb) ((int)erts_atomic_read_nob(&(tb)->common.nitems))
-/*
-** A stack of this size is enough for an AVL tree with more than
-** 0xFFFFFFFF elements. May be subject to change if
-** the datatype of the element counter is changed to a 64 bit integer.
-** The Maximal height of an AVL tree is calculated as:
-** h(n) <= 1.4404 * log(n + 2) - 0.328
-** Where n denotes the number of nodes, h(n) the height of the tree
-** with n nodes and log is the binary logarithm.
-*/
-
-#define STACK_NEED 50
#define TREE_MAX_ELEMENTS 0xFFFFFFFFUL
-#define PUSH_NODE(Dtt, Tdt) \
- ((Dtt)->array[(Dtt)->pos++] = Tdt)
-
-#define POP_NODE(Dtt) \
- (((Dtt)->pos) ? \
- (Dtt)->array[--((Dtt)->pos)] : NULL)
-
-#define TOP_NODE(Dtt) \
- ((Dtt->pos) ? \
- (Dtt)->array[(Dtt)->pos - 1] : NULL)
-
#define TOPN_NODE(Dtt, Pos) \
(((Pos) < Dtt->pos) ? \
(Dtt)->array[(Dtt)->pos - ((Pos) + 1)] : NULL)
@@ -89,10 +68,12 @@
/* Obtain table static stack if available. NULL if not.
** Must be released with release_stack()
*/
-static DbTreeStack* get_static_stack(DbTableTree* tb)
+ERTS_INLINE static DbTreeStack* get_static_stack(DbTableTree* tb)
{
- if (!erts_atomic_xchg_acqb(&tb->is_stack_busy, 1)) {
- return &tb->static_stack;
+ if (tb != NULL) {
+ ASSERT(IS_TREE_TABLE(tb->common.type));
+ if (!erts_atomic_xchg_acqb(&tb->is_stack_busy, 1))
+ return &tb->static_stack;
}
return NULL;
}
@@ -100,13 +81,15 @@ static DbTreeStack* get_static_stack(DbTableTree* tb)
/* Obtain static stack if available, otherwise empty dynamic stack.
** Must be released with release_stack()
*/
-static DbTreeStack* get_any_stack(DbTableTree* tb)
+static DbTreeStack* get_any_stack(DbTable* tb, DbTableTree* stack_container)
{
DbTreeStack* stack;
- if (!erts_atomic_xchg_acqb(&tb->is_stack_busy, 1)) {
- return &tb->static_stack;
+ if (stack_container != NULL) {
+ ASSERT(IS_TREE_TABLE(stack_container->common.type));
+ if (!erts_atomic_xchg_acqb(&stack_container->is_stack_busy, 1))
+ return &stack_container->static_stack;
}
- stack = erts_db_alloc(ERTS_ALC_T_DB_STK, (DbTable *) tb,
+ stack = erts_db_alloc(ERTS_ALC_T_DB_STK, tb,
sizeof(DbTreeStack) + sizeof(TreeDbTerm*) * STACK_NEED);
stack->pos = 0;
stack->slot = 0;
@@ -114,62 +97,62 @@ static DbTreeStack* get_any_stack(DbTableTree* tb)
return stack;
}
-static void release_stack(DbTableTree* tb, DbTreeStack* stack)
+static void release_stack(DbTable* tb, DbTableTree* stack_container, DbTreeStack* stack)
{
- if (stack == &tb->static_stack) {
- ASSERT(erts_atomic_read_nob(&tb->is_stack_busy) == 1);
- erts_atomic_set_relb(&tb->is_stack_busy, 0);
- }
- else {
- erts_db_free(ERTS_ALC_T_DB_STK, (DbTable *) tb,
- (void *) stack, sizeof(DbTreeStack) + sizeof(TreeDbTerm*) * STACK_NEED);
+ if (stack_container != NULL) {
+ ASSERT(IS_TREE_TABLE(stack_container->common.type));
+ if (stack == &stack_container->static_stack) {
+ ASSERT(erts_atomic_read_nob(&stack_container->is_stack_busy) == 1);
+ erts_atomic_set_relb(&stack_container->is_stack_busy, 0);
+ return;
+ }
}
+ erts_db_free(ERTS_ALC_T_DB_STK, tb,
+ (void *) stack, sizeof(DbTreeStack) + sizeof(TreeDbTerm*) * STACK_NEED);
}
-static ERTS_INLINE void reset_static_stack(DbTableTree* tb)
+static ERTS_INLINE void reset_stack(DbTreeStack* stack)
{
- tb->static_stack.pos = 0;
- tb->static_stack.slot = 0;
+ if (stack != NULL) {
+ stack->pos = 0;
+ stack->slot = 0;
+ }
}
-static ERTS_INLINE void free_term(DbTableTree *tb, TreeDbTerm* p)
+static ERTS_INLINE void reset_static_stack(DbTableTree* tb)
{
- db_free_term((DbTable*)tb, p, offsetof(TreeDbTerm, dbterm));
+ if (tb != NULL) {
+ ASSERT(IS_TREE_TABLE(tb->common.type));
+ reset_stack(&tb->static_stack);
+ }
}
-static ERTS_INLINE TreeDbTerm* new_dbterm(DbTableTree *tb, Eterm obj)
+static ERTS_INLINE TreeDbTerm* new_dbterm(DbTableCommon *tb, Eterm obj)
{
TreeDbTerm* p;
- if (tb->common.compress) {
- p = db_store_term_comp(&tb->common, NULL, offsetof(TreeDbTerm,dbterm), obj);
+ if (tb->compress) {
+ p = db_store_term_comp(tb, NULL, offsetof(TreeDbTerm,dbterm), obj);
}
else {
- p = db_store_term(&tb->common, NULL, offsetof(TreeDbTerm,dbterm), obj);
+ p = db_store_term(tb, NULL, offsetof(TreeDbTerm,dbterm), obj);
}
return p;
}
-static ERTS_INLINE TreeDbTerm* replace_dbterm(DbTableTree *tb, TreeDbTerm* old,
+static ERTS_INLINE TreeDbTerm* replace_dbterm(DbTableCommon *tb, TreeDbTerm* old,
Eterm obj)
{
TreeDbTerm* p;
ASSERT(old != NULL);
- if (tb->common.compress) {
- p = db_store_term_comp(&tb->common, &(old->dbterm), offsetof(TreeDbTerm,dbterm), obj);
+ if (tb->compress) {
+ p = db_store_term_comp(tb, &(old->dbterm), offsetof(TreeDbTerm,dbterm), obj);
}
else {
- p = db_store_term(&tb->common, &(old->dbterm), offsetof(TreeDbTerm,dbterm), obj);
+ p = db_store_term(tb, &(old->dbterm), offsetof(TreeDbTerm,dbterm), obj);
}
return p;
}
/*
-** Some macros for "direction stacks"
-*/
-#define DIR_LEFT 0
-#define DIR_RIGHT 1
-#define DIR_END 2
-
-/*
* Number of records to delete before trapping.
*/
#define DELETE_RECORD_LIMIT 12000
@@ -208,31 +191,37 @@ static void do_dump_tree2(DbTableTree*, int to, void *to_arg, int show,
** Datatypes
*/
+enum ms_key_boundness {
+ /* Order significant, larger means more "boundness" => less iteration */
+ MS_KEY_UNBOUND = 0,
+ MS_KEY_PARTIALLY_BOUND = 1,
+ MS_KEY_BOUND = 2,
+ MS_KEY_IMPOSSIBLE = 3
+};
+
/*
* This structure is filled in by analyze_pattern() for the select
* functions.
*/
struct mp_info {
- int something_can_match; /* The match_spec is not "impossible" */
- int some_limitation; /* There is some limitation on the search
- * area, i. e. least and/or most is set.*/
- int got_partial; /* The limitation has a partially bound
- * key */
+ enum ms_key_boundness key_boundness;
Eterm least; /* The lowest matching key (possibly
* partially bound expression) */
Eterm most; /* The highest matching key (possibly
* partially bound expression) */
-
- TreeDbTerm **save_term; /* If the key is completely bound, this
- * will be the Tree node we're searching
- * for, otherwise it will be useless */
Binary *mp; /* The compiled match program */
};
+struct select_common {
+ TreeDbTerm **root;
+};
+
+
/*
* Used by doit_select(_chunk)
*/
struct select_context {
+ struct select_common common;
Process *p;
Eterm accum;
Binary *mp;
@@ -248,6 +237,7 @@ struct select_context {
* Used by doit_select_count
*/
struct select_count_context {
+ struct select_common common;
Process *p;
Binary *mp;
Eterm end_condition;
@@ -261,8 +251,10 @@ struct select_count_context {
* Used by doit_select_delete
*/
struct select_delete_context {
+ struct select_common common;
Process *p;
- DbTableTree *tb;
+ DbTableCommon *tb;
+ DbTreeStack *stack;
Uint accum;
Binary *mp;
Eterm end_condition;
@@ -276,8 +268,9 @@ struct select_delete_context {
* Used by doit_select_replace
*/
struct select_replace_context {
+ struct select_common common;
Process *p;
- DbTableTree *tb;
+ DbTableCommon *tb;
Binary *mp;
Eterm end_condition;
Eterm *lastobj;
@@ -292,75 +285,83 @@ typedef int (*extra_match_validator_t)(int keypos, Eterm match, Eterm guard, Ete
/*
** Forward declarations
*/
-static TreeDbTerm *linkout_tree(DbTableTree *tb, Eterm key);
-static TreeDbTerm *linkout_object_tree(DbTableTree *tb,
- Eterm object);
+static TreeDbTerm *linkout_tree(DbTableCommon *tb, TreeDbTerm **root,
+ Eterm key, DbTreeStack *stack);
+static TreeDbTerm *linkout_object_tree(DbTableCommon *tb, TreeDbTerm **root,
+ Eterm object, DbTableTree *stack);
static SWord do_free_tree_continue(DbTableTree *tb, SWord reds);
-static void free_term(DbTableTree *tb, TreeDbTerm* p);
-static int balance_left(TreeDbTerm **this);
-static int balance_right(TreeDbTerm **this);
+static void free_term(DbTable *tb, TreeDbTerm* p);
+int tree_balance_left(TreeDbTerm **this);
+int tree_balance_right(TreeDbTerm **this);
static int delsub(TreeDbTerm **this);
-static TreeDbTerm *slot_search(Process *p, DbTableTree *tb, Sint slot);
-static TreeDbTerm *find_node(DbTableTree *tb, Eterm key);
-static TreeDbTerm **find_node2(DbTableTree *tb, Eterm key);
-static TreeDbTerm **find_ptr(DbTableTree *tb, DbTreeStack*, TreeDbTerm *this);
-static TreeDbTerm *find_next(DbTableTree *tb, DbTreeStack*, Eterm key);
-static TreeDbTerm *find_prev(DbTableTree *tb, DbTreeStack*, Eterm key);
-static TreeDbTerm *find_next_from_pb_key(DbTableTree *tb, DbTreeStack*,
- Eterm key);
-static TreeDbTerm *find_prev_from_pb_key(DbTableTree *tb, DbTreeStack*,
- Eterm key);
-static void traverse_backwards(DbTableTree *tb,
+static TreeDbTerm *slot_search(Process *p, TreeDbTerm *root, Sint slot,
+ DbTable *tb, DbTableTree *stack_container,
+ CATreeRootIterator *iter);
+static TreeDbTerm *find_node(DbTableCommon *tb, TreeDbTerm *root,
+ Eterm key, DbTableTree *stack_container);
+static TreeDbTerm **find_node2(DbTableCommon *tb, TreeDbTerm **root, Eterm key);
+static TreeDbTerm **find_ptr(DbTableCommon *tb, TreeDbTerm **root,
+ DbTreeStack *stack, TreeDbTerm *this);
+static TreeDbTerm *find_next(DbTableCommon *tb, TreeDbTerm *root,
+ DbTreeStack* stack, Eterm key);
+static TreeDbTerm *find_prev(DbTableCommon *tb, TreeDbTerm *root,
+ DbTreeStack* stack, Eterm key);
+static TreeDbTerm *find_next_from_pb_key(DbTable*, TreeDbTerm*** rootpp,
+ DbTreeStack* stack, Eterm key,
+ CATreeRootIterator*);
+static TreeDbTerm *find_prev_from_pb_key(DbTable*, TreeDbTerm*** rootpp,
+ DbTreeStack* stack, Eterm key,
+ CATreeRootIterator*);
+typedef int traverse_doit_funcT(DbTableCommon*, TreeDbTerm*,
+ struct select_common*, int forward);
+
+static void traverse_backwards(DbTableCommon *tb,
DbTreeStack*,
Eterm lastkey,
- int (*doit)(DbTableTree *tb,
- TreeDbTerm *,
- void *,
- int),
- void *context);
-static void traverse_forward(DbTableTree *tb,
+ traverse_doit_funcT*,
+ struct select_common *context,
+ CATreeRootIterator*);
+static void traverse_forward(DbTableCommon *tb,
DbTreeStack*,
Eterm lastkey,
- int (*doit)(DbTableTree *tb,
- TreeDbTerm *,
- void *,
- int),
- void *context);
-static void traverse_update_backwards(DbTableTree *tb,
+ traverse_doit_funcT*,
+ struct select_common *context,
+ CATreeRootIterator*);
+static void traverse_update_backwards(DbTableCommon *tb,
DbTreeStack*,
Eterm lastkey,
- int (*doit)(DbTableTree *tb,
+ int (*doit)(DbTableCommon *tb,
TreeDbTerm **, // out
- void *,
+ struct select_common*,
int),
- void *context);
-static int key_given(DbTableTree *tb, Eterm pattern, TreeDbTerm ***ret,
- Eterm *partly_bound_key);
-static Sint cmp_partly_bound(Eterm partly_bound_key, Eterm bound_key);
+ struct select_common*,
+ CATreeRootIterator*);
+static enum ms_key_boundness key_boundness(DbTableCommon *tb,
+ Eterm pattern, Eterm *keyp);
static Sint do_cmp_partly_bound(Eterm a, Eterm b, int *done);
-static int analyze_pattern(DbTableTree *tb, Eterm pattern,
+static int analyze_pattern(DbTableCommon *tb, Eterm pattern,
extra_match_validator_t extra_validator, /* Optional callback */
struct mp_info *mpi);
-static int doit_select(DbTableTree *tb,
- TreeDbTerm *this,
- void *ptr,
+static int doit_select(DbTableCommon *tb,
+ TreeDbTerm *this,
+ struct select_common* ptr,
int forward);
-static int doit_select_count(DbTableTree *tb,
+static int doit_select_count(DbTableCommon *tb,
TreeDbTerm *this,
- void *ptr,
+ struct select_common*,
int forward);
-static int doit_select_chunk(DbTableTree *tb,
+static int doit_select_chunk(DbTableCommon *tb,
TreeDbTerm *this,
- void *ptr,
+ struct select_common*,
int forward);
-static int doit_select_delete(DbTableTree *tb,
+static int doit_select_delete(DbTableCommon *tb,
TreeDbTerm *this,
- void *ptr,
+ struct select_common*,
int forward);
-static int doit_select_replace(DbTableTree *tb,
+static int doit_select_replace(DbTableCommon *tb,
TreeDbTerm **this_ptr,
- void *ptr,
+ struct select_common*,
int forward);
static int partly_bound_can_match_lesser(Eterm partly_bound_1,
@@ -508,18 +509,18 @@ int db_create_tree(Process *p, DbTable *tbl)
return DB_ERROR_NONE;
}
-static int db_first_tree(Process *p, DbTable *tbl, Eterm *ret)
+int db_first_tree_common(Process *p, DbTable *tbl, TreeDbTerm *root,
+ Eterm *ret, DbTableTree *stack_container)
{
- DbTableTree *tb = &tbl->tree;
DbTreeStack* stack;
TreeDbTerm *this;
- if (( this = tb->root ) == NULL) {
+ if (( this = root ) == NULL) {
*ret = am_EOT;
return DB_ERROR_NONE;
}
/* Walk down the tree to the left */
- if ((stack = get_static_stack(tb)) != NULL) {
+ if ((stack = get_static_stack(stack_container)) != NULL) {
stack->pos = stack->slot = 0;
}
while (this->left != NULL) {
@@ -529,23 +530,27 @@ static int db_first_tree(Process *p, DbTable *tbl, Eterm *ret)
if (stack) {
PUSH_NODE(stack, this);
stack->slot = 1;
- release_stack(tb,stack);
+ release_stack(tbl,stack_container,stack);
}
*ret = db_copy_key(p, tbl, &this->dbterm);
return DB_ERROR_NONE;
}
-static int db_next_tree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
+static int db_first_tree(Process *p, DbTable *tbl, Eterm *ret)
{
DbTableTree *tb = &tbl->tree;
- DbTreeStack* stack;
+ return db_first_tree_common(p, tbl, tb->root, ret, tb);
+}
+
+int db_next_tree_common(Process *p, DbTable *tbl,
+ TreeDbTerm *root, Eterm key,
+ Eterm *ret, DbTreeStack* stack)
+{
TreeDbTerm *this;
- if (is_atom(key) && key == am_EOT)
+ if (key == am_EOT)
return DB_ERROR_BADKEY;
- stack = get_any_stack(tb);
- this = find_next(tb, stack, key);
- release_stack(tb,stack);
+ this = find_next(&tbl->common, root, stack, key);
if (this == NULL) {
*ret = am_EOT;
return DB_ERROR_NONE;
@@ -554,18 +559,27 @@ static int db_next_tree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
return DB_ERROR_NONE;
}
-static int db_last_tree(Process *p, DbTable *tbl, Eterm *ret)
+static int db_next_tree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
{
DbTableTree *tb = &tbl->tree;
+ DbTreeStack* stack = get_any_stack(tbl, tb);
+ int ret_val = db_next_tree_common(p, tbl, tb->root, key, ret, stack);
+ release_stack(tbl,tb,stack);
+ return ret_val;
+}
+
+int db_last_tree_common(Process *p, DbTable *tbl, TreeDbTerm *root,
+ Eterm *ret, DbTableTree *stack_container)
+{
TreeDbTerm *this;
DbTreeStack* stack;
- if (( this = tb->root ) == NULL) {
+ if (( this = root ) == NULL) {
*ret = am_EOT;
return DB_ERROR_NONE;
}
/* Walk down the tree to the right */
- if ((stack = get_static_stack(tb)) != NULL) {
+ if ((stack = get_static_stack(stack_container)) != NULL) {
stack->pos = stack->slot = 0;
}
while (this->right != NULL) {
@@ -574,24 +588,27 @@ static int db_last_tree(Process *p, DbTable *tbl, Eterm *ret)
}
if (stack) {
PUSH_NODE(stack, this);
- stack->slot = NITEMS(tb);
- release_stack(tb,stack);
+ stack->slot = NITEMS(tbl);
+ release_stack(tbl,stack_container,stack);
}
*ret = db_copy_key(p, tbl, &this->dbterm);
return DB_ERROR_NONE;
}
-static int db_prev_tree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
+static int db_last_tree(Process *p, DbTable *tbl, Eterm *ret)
{
DbTableTree *tb = &tbl->tree;
+ return db_last_tree_common(p, tbl, tb->root, ret, tb);
+}
+
+int db_prev_tree_common(Process *p, DbTable *tbl, TreeDbTerm *root, Eterm key,
+ Eterm *ret, DbTreeStack* stack)
+{
TreeDbTerm *this;
- DbTreeStack* stack;
- if (is_atom(key) && key == am_EOT)
+ if (key == am_EOT)
return DB_ERROR_BADKEY;
- stack = get_any_stack(tb);
- this = find_prev(tb, stack, key);
- release_stack(tb,stack);
+ this = find_prev(&tbl->common, root, stack, key);
if (this == NULL) {
*ret = am_EOT;
return DB_ERROR_NONE;
@@ -600,25 +617,30 @@ static int db_prev_tree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
return DB_ERROR_NONE;
}
-static ERTS_INLINE Sint cmp_key(DbTableTree* tb, Eterm key, TreeDbTerm* obj) {
- return CMP(key, GETKEY(tb,obj->dbterm.tpl));
+static int db_prev_tree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
+{
+ DbTableTree *tb = &tbl->tree;
+ DbTreeStack* stack = get_any_stack(tbl, tb);
+ int res = db_prev_tree_common(p, tbl, tb->root, key, ret, stack);
+ release_stack(tbl,tb,stack);
+ return res;
}
-static ERTS_INLINE int cmp_key_eq(DbTableTree* tb, Eterm key, TreeDbTerm* obj) {
+static ERTS_INLINE int cmp_key_eq(DbTableCommon* tb, Eterm key, TreeDbTerm* obj) {
Eterm obj_key = GETKEY(tb,obj->dbterm.tpl);
return is_same(key, obj_key) || CMP(key, obj_key) == 0;
}
-static int db_put_tree(DbTable *tbl, Eterm obj, int key_clash_fail)
+int db_put_tree_common(DbTableCommon *tb, TreeDbTerm **root, Eterm obj,
+ int key_clash_fail, DbTableTree *stack_container)
{
- DbTableTree *tb = &tbl->tree;
/* Non recursive insertion in AVL tree, building our own stack */
TreeDbTerm **tstack[STACK_NEED];
int tpos = 0;
int dstack[STACK_NEED+1];
int dpos = 0;
int state = 0;
- TreeDbTerm **this = &tb->root;
+ TreeDbTerm **this = root;
Sint c;
Eterm key;
int dir;
@@ -626,14 +648,14 @@ static int db_put_tree(DbTable *tbl, Eterm obj, int key_clash_fail)
key = GETKEY(tb, tuple_val(obj));
- reset_static_stack(tb);
+ reset_static_stack(stack_container);
dstack[dpos++] = DIR_END;
for (;;)
if (!*this) { /* Found our place */
state = 1;
- if (erts_atomic_inc_read_nob(&tb->common.nitems) >= TREE_MAX_ELEMENTS) {
- erts_atomic_dec_nob(&tb->common.nitems);
+ if (erts_atomic_inc_read_nob(&tb->nitems) >= TREE_MAX_ELEMENTS) {
+ erts_atomic_dec_nob(&tb->nitems);
return DB_ERROR_SYSRES;
}
*this = new_dbterm(tb, obj);
@@ -724,9 +746,15 @@ static int db_put_tree(DbTable *tbl, Eterm obj, int key_clash_fail)
return DB_ERROR_NONE;
}
-static int db_get_tree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
+static int db_put_tree(DbTable *tbl, Eterm obj, int key_clash_fail)
{
DbTableTree *tb = &tbl->tree;
+ return db_put_tree_common(&tb->common, &tb->root, obj, key_clash_fail, tb);
+}
+
+int db_get_tree_common(Process *p, DbTableCommon *tb, TreeDbTerm *root, Eterm key,
+ Eterm *ret, DbTableTree *stack_container)
+{
Eterm copy;
Eterm *hp, *hend;
TreeDbTerm *this;
@@ -737,13 +765,13 @@ static int db_get_tree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
* The list created around it is purely for interface conformance.
*/
- this = find_node(tb,key);
+ this = find_node(tb,root,key,stack_container);
if (this == NULL) {
*ret = NIL;
} else {
hp = HAlloc(p, this->dbterm.size + 2);
hend = hp + this->dbterm.size + 2;
- copy = db_copy_object_from_ets(&tb->common, &this->dbterm, &hp, &MSO(p));
+ copy = db_copy_object_from_ets(tb, &this->dbterm, &hp, &MSO(p));
*ret = CONS(hp, copy, NIL);
hp += 2;
HRelease(p,hend,hp);
@@ -751,18 +779,28 @@ static int db_get_tree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
return DB_ERROR_NONE;
}
-static int db_member_tree(DbTable *tbl, Eterm key, Eterm *ret)
+static int db_get_tree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
{
DbTableTree *tb = &tbl->tree;
+ return db_get_tree_common(p, &tb->common, tb->root, key, ret, tb);
+}
- *ret = (find_node(tb,key) == NULL) ? am_false : am_true;
+int db_member_tree_common(DbTableCommon *tb, TreeDbTerm *root, Eterm key, Eterm *ret,
+ DbTableTree *stack_container)
+{
+ *ret = (find_node(tb,root,key,stack_container) == NULL) ? am_false : am_true;
return DB_ERROR_NONE;
}
-static int db_get_element_tree(Process *p, DbTable *tbl,
- Eterm key, int ndex, Eterm *ret)
+static int db_member_tree(DbTable *tbl, Eterm key, Eterm *ret)
{
DbTableTree *tb = &tbl->tree;
+ return db_member_tree_common(&tb->common, tb->root, key, ret, tb);
+}
+
+int db_get_element_tree_common(Process *p, DbTableCommon *tb, TreeDbTerm *root, Eterm key,
+ int ndex, Eterm *ret, DbTableTree *stack_container)
+{
/*
* Look the node up:
*/
@@ -776,49 +814,69 @@ static int db_get_element_tree(Process *p, DbTable *tbl,
* around the element here either.
*/
- this = find_node(tb,key);
+ this = find_node(tb,root,key,stack_container);
if (this == NULL) {
return DB_ERROR_BADKEY;
} else {
if (ndex > arityval(this->dbterm.tpl[0])) {
return DB_ERROR_BADPARAM;
}
- *ret = db_copy_element_from_ets(&tb->common, p, &this->dbterm, ndex, &hp, 0);
+ *ret = db_copy_element_from_ets(tb, p, &this->dbterm, ndex, &hp, 0);
}
return DB_ERROR_NONE;
}
-static int db_erase_tree(DbTable *tbl, Eterm key, Eterm *ret)
+static int db_get_element_tree(Process *p, DbTable *tbl,
+ Eterm key, int ndex, Eterm *ret)
{
DbTableTree *tb = &tbl->tree;
+ return db_get_element_tree_common(p, &tb->common, tb->root, key,
+ ndex, ret, tb);
+}
+
+int db_erase_tree_common(DbTable *tbl, TreeDbTerm **root, Eterm key, Eterm *ret,
+ DbTreeStack *stack /* NULL if no static stack */)
+{
TreeDbTerm *res;
*ret = am_true;
- if ((res = linkout_tree(tb, key)) != NULL) {
- free_term(tb, res);
+ if ((res = linkout_tree(&tbl->common, root,key, stack)) != NULL) {
+ free_term(tbl, res);
}
return DB_ERROR_NONE;
}
-static int db_erase_object_tree(DbTable *tbl, Eterm object, Eterm *ret)
+static int db_erase_tree(DbTable *tbl, Eterm key, Eterm *ret)
{
DbTableTree *tb = &tbl->tree;
+ return db_erase_tree_common(tbl, &tb->root, key, ret, &tb->static_stack);
+}
+
+int db_erase_object_tree_common(DbTable *tbl, TreeDbTerm **root, Eterm object,
+ Eterm *ret, DbTableTree *stack_container)
+{
TreeDbTerm *res;
*ret = am_true;
- if ((res = linkout_object_tree(tb, object)) != NULL) {
- free_term(tb, res);
+ if ((res = linkout_object_tree(&tbl->common, root, object, stack_container)) != NULL) {
+ free_term(tbl, res);
}
return DB_ERROR_NONE;
}
-
-static int db_slot_tree(Process *p, DbTable *tbl,
- Eterm slot_term, Eterm *ret)
+static int db_erase_object_tree(DbTable *tbl, Eterm object, Eterm *ret)
{
DbTableTree *tb = &tbl->tree;
+ return db_erase_object_tree_common(tbl, &tb->root, object, ret, tb);
+}
+
+int db_slot_tree_common(Process *p, DbTable *tbl, TreeDbTerm *root,
+ Eterm slot_term, Eterm *ret,
+ DbTableTree *stack_container,
+ CATreeRootIterator *iter)
+{
Sint slot;
TreeDbTerm *st;
Eterm *hp, *hend;
@@ -834,10 +892,10 @@ static int db_slot_tree(Process *p, DbTable *tbl,
if (is_not_small(slot_term) ||
((slot = signed_val(slot_term)) < 0) ||
- (slot > NITEMS(tb)))
+ (slot > NITEMS(tbl)))
return DB_ERROR_BADPARAM;
- if (slot == NITEMS(tb)) {
+ if (slot == NITEMS(tbl)) {
*ret = am_EOT;
return DB_ERROR_NONE;
}
@@ -847,20 +905,27 @@ static int db_slot_tree(Process *p, DbTable *tbl,
* are counted from 1 and up.
*/
++slot;
- st = slot_search(p, tb, slot);
+ st = slot_search(p, root, slot, tbl, stack_container, iter);
if (st == NULL) {
*ret = am_false;
return DB_ERROR_UNSPEC;
}
hp = HAlloc(p, st->dbterm.size + 2);
hend = hp + st->dbterm.size + 2;
- copy = db_copy_object_from_ets(&tb->common, &st->dbterm, &hp, &MSO(p));
+ copy = db_copy_object_from_ets(&tbl->common, &st->dbterm, &hp, &MSO(p));
*ret = CONS(hp, copy, NIL);
hp += 2;
HRelease(p,hend,hp);
return DB_ERROR_NONE;
}
+static int db_slot_tree(Process *p, DbTable *tbl,
+ Eterm slot_term, Eterm *ret)
+{
+ DbTableTree *tb = &tbl->tree;
+ return db_slot_tree_common(p, tbl, tb->root, slot_term, ret, tb, NULL);
+}
+
static BIF_RETTYPE ets_select_reverse(BIF_ALIST_3)
@@ -926,19 +991,14 @@ static BIF_RETTYPE bif_trap3(Export *bif,
{
BIF_TRAP3(bif, p, p1, p2, p3);
}
-
-/*
-** This is called either when the select bif traps or when ets:select/1
-** is called. It does mostly the same as db_select_tree and may in either case
-** trap to itself again (via the ets:select/1 bif).
-** Note that this is common for db_select_tree and db_select_chunk_tree.
-*/
-static int db_select_continue_tree(Process *p,
- DbTable *tbl,
- Eterm continuation,
- Eterm *ret)
+
+int db_select_continue_tree_common(Process *p,
+ DbTableCommon *tb,
+ Eterm continuation,
+ Eterm *ret,
+ DbTableTree *stack_container,
+ CATreeRootIterator* iter)
{
- DbTableTree *tb = &tbl->tree;
DbTreeStack* stack;
struct select_context sc;
unsigned sz;
@@ -951,7 +1011,6 @@ static int db_select_continue_tree(Process *p,
Sint chunk_size;
Sint reverse;
-
#define RET_TO_BIF(Term, State) do { *ret = (Term); return State; } while(0);
/* Decode continuation. We know it's a tuple but not the arity or
@@ -980,28 +1039,37 @@ static int db_select_continue_tree(Process *p,
sc.end_condition = NIL;
sc.lastobj = NULL;
sc.max = 1000;
- sc.keypos = tb->common.keypos;
+ sc.keypos = tb->keypos;
sc.chunk_size = chunk_size;
reverse = unsigned_val(tptr[7]);
sc.got = signed_val(tptr[8]);
- stack = get_any_stack(tb);
- if (chunk_size) {
- if (reverse) {
- traverse_backwards(tb, stack, lastkey, &doit_select_chunk, &sc);
- } else {
- traverse_forward(tb, stack, lastkey, &doit_select_chunk, &sc);
- }
- } else {
- if (reverse) {
- traverse_forward(tb, stack, lastkey, &doit_select, &sc);
- } else {
- traverse_backwards(tb, stack, lastkey, &doit_select, &sc);
- }
+ if (iter) {
+ iter->next_route_key = lastkey;
+ sc.common.root = catree_find_nextprev_root(iter, !!reverse != !!chunk_size, NULL);
}
- release_stack(tb,stack);
+ else
+ sc.common.root = &((DbTableTree*)tb)->root;
+
+ if (sc.common.root) {
+ stack = get_any_stack((DbTable*)tb, stack_container);
+ if (chunk_size) {
+ if (reverse) {
+ traverse_backwards(tb, stack, lastkey, &doit_select_chunk, &sc.common, iter);
+ } else {
+ traverse_forward(tb, stack, lastkey, &doit_select_chunk, &sc.common, iter);
+ }
+ } else {
+ if (reverse) {
+ traverse_forward(tb, stack, lastkey, &doit_select, &sc.common, iter);
+ } else {
+ traverse_backwards(tb, stack, lastkey, &doit_select, &sc.common, iter);
+ }
+ }
+ release_stack((DbTable*)tb,stack_container,stack);
- BUMP_REDS(p, 1000 - sc.max);
+ BUMP_REDS(p, 1000 - sc.max);
+ }
if (sc.max > 0 || (chunk_size && sc.got == chunk_size)) {
if (chunk_size) {
@@ -1082,13 +1150,29 @@ static int db_select_continue_tree(Process *p,
#undef RET_TO_BIF
}
+
+/*
+** This is called either when the select bif traps or when ets:select/1
+** is called. It does mostly the same as db_select_tree and may in either case
+** trap to itself again (via the ets:select/1 bif).
+** Note that this is common for db_select_tree and db_select_chunk_tree.
+*/
+static int db_select_continue_tree(Process *p,
+ DbTable *tbl,
+ Eterm continuation,
+ Eterm *ret)
+{
+ DbTableTree *tb = &tbl->tree;
+ return db_select_continue_tree_common(p, &tb->common,
+ continuation, ret, tb, NULL);
+}
-
-static int db_select_tree(Process *p, DbTable *tbl, Eterm tid,
- Eterm pattern, int reverse, Eterm *ret)
+int db_select_tree_common(Process *p, DbTable *tb,
+ Eterm tid, Eterm pattern, int reverse, Eterm *ret,
+ DbTableTree *stack_container,
+ CATreeRootIterator* iter)
{
/* Strategy: Traverse backwards to build resulting list from tail to head */
- DbTableTree *tb = &tbl->tree;
DbTreeStack* stack;
struct select_context sc;
struct mp_info mpi;
@@ -1121,42 +1205,62 @@ static int db_select_tree(Process *p, DbTable *tbl, Eterm tid,
sc.got = 0;
sc.chunk_size = 0;
- if ((errcode = analyze_pattern(tb, pattern, NULL, &mpi)) != DB_ERROR_NONE) {
+ if ((errcode = analyze_pattern(&tb->common, pattern, NULL, &mpi)) != DB_ERROR_NONE) {
RET_TO_BIF(NIL,errcode);
}
- if (!mpi.something_can_match) {
+ if (mpi.key_boundness == MS_KEY_IMPOSSIBLE) {
RET_TO_BIF(NIL,DB_ERROR_NONE);
/* can't possibly match anything */
}
sc.mp = mpi.mp;
- if (!mpi.got_partial && mpi.some_limitation &&
- CMP_EQ(mpi.least,mpi.most)) {
- doit_select(tb,*(mpi.save_term),&sc,0 /* direction doesn't matter */);
+ if (mpi.key_boundness == MS_KEY_BOUND) {
+ ASSERT(CMP_EQ(mpi.least, mpi.most));
+ if (iter)
+ sc.common.root = catree_find_root(mpi.least, iter);
+ else
+ sc.common.root = &tb->tree.root;
+ this = find_node(&tb->common, *sc.common.root, mpi.least, NULL);
+ if (this)
+ doit_select(&tb->common, this, &sc.common, 0 /* direction doesn't matter */);
RET_TO_BIF(sc.accum,DB_ERROR_NONE);
}
- stack = get_any_stack(tb);
+ stack = get_any_stack((DbTable*)tb,stack_container);
if (reverse) {
- if (mpi.some_limitation) {
- if ((this = find_prev_from_pb_key(tb, stack, mpi.least)) != NULL) {
+ if (mpi.key_boundness == MS_KEY_PARTIALLY_BOUND) {
+ this = find_prev_from_pb_key(tb, &sc.common.root, stack, mpi.least, iter);
+ if (this)
lastkey = GETKEY(tb, this->dbterm.tpl);
- }
sc.end_condition = mpi.most;
}
- traverse_forward(tb, stack, lastkey, &doit_select, &sc);
+ else {
+ ASSERT(mpi.key_boundness == MS_KEY_UNBOUND);
+ if (iter)
+ sc.common.root = catree_find_first_root(iter);
+ else
+ sc.common.root = &tb->tree.root;
+ }
+ traverse_forward(&tb->common, stack, lastkey, &doit_select, &sc.common, iter);
} else {
- if (mpi.some_limitation) {
- if ((this = find_next_from_pb_key(tb, stack, mpi.most)) != NULL) {
- lastkey = GETKEY(tb, this->dbterm.tpl);
- }
+ if (mpi.key_boundness == MS_KEY_PARTIALLY_BOUND) {
+ this = find_next_from_pb_key(tb, &sc.common.root, stack, mpi.most, iter);
+ if (this)
+ lastkey = GETKEY(tb, this->dbterm.tpl);
sc.end_condition = mpi.least;
}
- traverse_backwards(tb, stack, lastkey, &doit_select, &sc);
+ else {
+ ASSERT(mpi.key_boundness == MS_KEY_UNBOUND);
+ if (iter)
+ sc.common.root = catree_find_last_root(iter);
+ else
+ sc.common.root = &tb->tree.root;
+ }
+ traverse_backwards(&tb->common, stack, lastkey, &doit_select, &sc.common, iter);
}
- release_stack(tb,stack);
+ release_stack((DbTable*)tb,stack_container,stack);
#ifdef HARDDEBUG
erts_fprintf(stderr,"Least: %T\n", mpi.least);
erts_fprintf(stderr,"Most: %T\n", mpi.most);
@@ -1192,16 +1296,20 @@ static int db_select_tree(Process *p, DbTable *tbl, Eterm tid,
}
-
-/*
-** This is called either when the select_count bif traps.
-*/
-static int db_select_count_continue_tree(Process *p,
- DbTable *tbl,
- Eterm continuation,
- Eterm *ret)
+static int db_select_tree(Process *p, DbTable *tbl, Eterm tid,
+ Eterm pattern, int reverse, Eterm *ret)
+{
+ return db_select_tree_common(p, tbl, tid,
+ pattern, reverse, ret, &tbl->tree, NULL);
+}
+
+int db_select_count_continue_tree_common(Process *p,
+ DbTable *tb,
+ Eterm continuation,
+ Eterm *ret,
+ DbTableTree *stack_container,
+ CATreeRootIterator* iter)
{
- DbTableTree *tb = &tbl->tree;
DbTreeStack* stack;
struct select_count_context sc;
unsigned sz;
@@ -1213,7 +1321,6 @@ static int db_select_count_continue_tree(Process *p,
Eterm *tptr;
Eterm egot;
-
#define RET_TO_BIF(Term, State) do { *ret = (Term); return State; } while(0);
/* Decode continuation. We know it's a tuple and everything else as
@@ -1245,11 +1352,21 @@ static int db_select_count_continue_tree(Process *p,
sc.got = unsigned_val(tptr[5]);
}
- stack = get_any_stack(tb);
- traverse_backwards(tb, stack, lastkey, &doit_select_count, &sc);
- release_stack(tb,stack);
+ if (iter) {
+ iter->next_route_key = lastkey;
+ sc.common.root = catree_find_prev_root(iter, NULL);
+ }
+ else {
+ sc.common.root = &tb->tree.root;
+ }
- BUMP_REDS(p, 1000 - sc.max);
+ if (sc.common.root) {
+ stack = get_any_stack(tb, stack_container);
+ traverse_backwards(&tb->common, stack, lastkey, &doit_select_count, &sc.common, iter);
+ release_stack(tb,stack_container,stack);
+
+ BUMP_REDS(p, 1000 - sc.max);
+ }
if (sc.max > 0) {
RET_TO_BIF(erts_make_integer(sc.got,p), DB_ERROR_NONE);
@@ -1285,11 +1402,25 @@ static int db_select_count_continue_tree(Process *p,
#undef RET_TO_BIF
}
-
-static int db_select_count_tree(Process *p, DbTable *tbl, Eterm tid,
- Eterm pattern, Eterm *ret)
+/*
+** This is called either when the select_count bif traps.
+*/
+static int db_select_count_continue_tree(Process *p,
+ DbTable *tbl,
+ Eterm continuation,
+ Eterm *ret)
{
DbTableTree *tb = &tbl->tree;
+ return db_select_count_continue_tree_common(p, tbl,
+ continuation, ret, tb, NULL);
+}
+
+
+int db_select_count_tree_common(Process *p, DbTable *tb,
+ Eterm tid, Eterm pattern, Eterm *ret,
+ DbTableTree *stack_container,
+ CATreeRootIterator* iter)
+{
DbTreeStack* stack;
struct select_count_context sc;
struct mp_info mpi;
@@ -1303,7 +1434,6 @@ static int db_select_count_tree(Process *p, DbTable *tbl, Eterm tid,
Eterm egot;
Eterm mpb;
-
#define RET_TO_BIF(Term,RetVal) do { \
if (mpi.mp != NULL) { \
erts_bin_free(mpi.mp); \
@@ -1321,33 +1451,46 @@ static int db_select_count_tree(Process *p, DbTable *tbl, Eterm tid,
sc.keypos = tb->common.keypos;
sc.got = 0;
- if ((errcode = analyze_pattern(tb, pattern, NULL, &mpi)) != DB_ERROR_NONE) {
+ if ((errcode = analyze_pattern(&tb->common, pattern, NULL, &mpi)) != DB_ERROR_NONE) {
RET_TO_BIF(NIL,errcode);
}
- if (!mpi.something_can_match) {
+ if (mpi.key_boundness == MS_KEY_IMPOSSIBLE) {
RET_TO_BIF(make_small(0),DB_ERROR_NONE);
/* can't possibly match anything */
}
sc.mp = mpi.mp;
- if (!mpi.got_partial && mpi.some_limitation &&
- CMP_EQ(mpi.least,mpi.most)) {
- doit_select_count(tb,*(mpi.save_term),&sc,0 /* dummy */);
+ if (mpi.key_boundness == MS_KEY_BOUND) {
+ ASSERT(CMP_EQ(mpi.least, mpi.most));
+ if (iter)
+ sc.common.root = catree_find_root(mpi.least, iter);
+ else
+ sc.common.root = &((DbTable*)tb)->tree.root;
+ this = find_node(&tb->common, *sc.common.root, mpi.least, NULL);
+ if (this)
+ doit_select_count(&tb->common, this, &sc.common, 0 /* dummy */);
RET_TO_BIF(erts_make_integer(sc.got,p),DB_ERROR_NONE);
}
- stack = get_any_stack(tb);
- if (mpi.some_limitation) {
- if ((this = find_next_from_pb_key(tb, stack, mpi.most)) != NULL) {
- lastkey = GETKEY(tb, this->dbterm.tpl);
- }
+ stack = get_any_stack((DbTable*)tb, stack_container);
+ if (mpi.key_boundness == MS_KEY_PARTIALLY_BOUND) {
+ this = find_next_from_pb_key(tb, &sc.common.root, stack, mpi.most, iter);
+ if (this)
+ lastkey = GETKEY(tb, this->dbterm.tpl);
sc.end_condition = mpi.least;
}
+ else {
+ ASSERT(mpi.key_boundness == MS_KEY_UNBOUND);
+ if (iter)
+ sc.common.root = catree_find_last_root(iter);
+ else
+ sc.common.root = &tb->tree.root;
+ }
- traverse_backwards(tb, stack, lastkey, &doit_select_count, &sc);
- release_stack(tb,stack);
+ traverse_backwards(&tb->common, stack, lastkey, &doit_select_count, &sc.common, iter);
+ release_stack((DbTable*)tb,stack_container,stack);
BUMP_REDS(p, 1000 - sc.max);
if (sc.max > 0) {
RET_TO_BIF(erts_make_integer(sc.got,p),DB_ERROR_NONE);
@@ -1383,12 +1526,21 @@ static int db_select_count_tree(Process *p, DbTable *tbl, Eterm tid,
}
-static int db_select_chunk_tree(Process *p, DbTable *tbl, Eterm tid,
- Eterm pattern, Sint chunk_size,
- int reverse,
- Eterm *ret)
+static int db_select_count_tree(Process *p, DbTable *tbl, Eterm tid,
+ Eterm pattern, Eterm *ret)
{
DbTableTree *tb = &tbl->tree;
+ return db_select_count_tree_common(p, tbl,
+ tid, pattern, ret, tb, NULL);
+}
+
+
+int db_select_chunk_tree_common(Process *p, DbTable *tb,
+ Eterm tid, Eterm pattern, Sint chunk_size,
+ int reverse, Eterm *ret,
+ DbTableTree *stack_container,
+ CATreeRootIterator* iter)
+{
DbTreeStack* stack;
struct select_context sc;
struct mp_info mpi;
@@ -1401,7 +1553,6 @@ static int db_select_chunk_tree(Process *p, DbTable *tbl, Eterm tid,
int errcode;
Eterm mpb;
-
#define RET_TO_BIF(Term,RetVal) do { \
if (mpi.mp != NULL) { \
erts_bin_free(mpi.mp); \
@@ -1421,20 +1572,26 @@ static int db_select_chunk_tree(Process *p, DbTable *tbl, Eterm tid,
sc.got = 0;
sc.chunk_size = chunk_size;
- if ((errcode = analyze_pattern(tb, pattern, NULL, &mpi)) != DB_ERROR_NONE) {
+ if ((errcode = analyze_pattern(&tb->common, pattern, NULL, &mpi)) != DB_ERROR_NONE) {
RET_TO_BIF(NIL,errcode);
}
- if (!mpi.something_can_match) {
+ if (mpi.key_boundness == MS_KEY_IMPOSSIBLE) {
RET_TO_BIF(am_EOT,DB_ERROR_NONE);
/* can't possibly match anything */
}
sc.mp = mpi.mp;
- if (!mpi.got_partial && mpi.some_limitation &&
- CMP_EQ(mpi.least,mpi.most)) {
- doit_select(tb,*(mpi.save_term),&sc, 0 /* direction doesn't matter */);
+ if (mpi.key_boundness == MS_KEY_BOUND) {
+ ASSERT(CMP_EQ(mpi.least, mpi.most));
+ if (iter)
+ sc.common.root = catree_find_root(mpi.least, iter);
+ else
+ sc.common.root = &tb->tree.root;
+ this = find_node(&tb->common, *sc.common.root, mpi.least, NULL);
+ if (this)
+ doit_select(&tb->common, this, &sc.common, 0 /* direction doesn't matter */);
if (sc.accum != NIL) {
hp=HAlloc(p, 3);
RET_TO_BIF(TUPLE2(hp,sc.accum,am_EOT),DB_ERROR_NONE);
@@ -1443,25 +1600,39 @@ static int db_select_chunk_tree(Process *p, DbTable *tbl, Eterm tid,
}
}
- stack = get_any_stack(tb);
+ stack = get_any_stack((DbTable*)tb,stack_container);
if (reverse) {
- if (mpi.some_limitation) {
- if ((this = find_next_from_pb_key(tb, stack, mpi.most)) != NULL) {
- lastkey = GETKEY(tb, this->dbterm.tpl);
- }
+ if (mpi.key_boundness == MS_KEY_PARTIALLY_BOUND) {
+ this = find_next_from_pb_key(tb, &sc.common.root, stack, mpi.most, iter);
+ if (this)
+ lastkey = GETKEY(tb, this->dbterm.tpl);
sc.end_condition = mpi.least;
}
- traverse_backwards(tb, stack, lastkey, &doit_select_chunk, &sc);
+ else {
+ ASSERT(mpi.key_boundness == MS_KEY_UNBOUND);
+ if (iter)
+ sc.common.root = catree_find_last_root(iter);
+ else
+ sc.common.root = &tb->tree.root;
+ }
+ traverse_backwards(&tb->common, stack, lastkey, &doit_select_chunk, &sc.common, iter);
} else {
- if (mpi.some_limitation) {
- if ((this = find_prev_from_pb_key(tb, stack, mpi.least)) != NULL) {
- lastkey = GETKEY(tb, this->dbterm.tpl);
- }
+ if (mpi.key_boundness == MS_KEY_PARTIALLY_BOUND) {
+ this = find_prev_from_pb_key(tb, &sc.common.root, stack, mpi.least, iter);
+ if (this)
+ lastkey = GETKEY(tb, this->dbterm.tpl);
sc.end_condition = mpi.most;
}
- traverse_forward(tb, stack, lastkey, &doit_select_chunk, &sc);
+ else {
+ ASSERT(mpi.key_boundness == MS_KEY_UNBOUND);
+ if (iter)
+ sc.common.root = catree_find_first_root(iter);
+ else
+ sc.common.root = &tb->tree.root;
+ }
+ traverse_forward(&tb->common, stack, lastkey, &doit_select_chunk, &sc.common, iter);
}
- release_stack(tb,stack);
+ release_stack((DbTable*)tb,stack_container,stack);
BUMP_REDS(p, 1000 - sc.max);
if (sc.max > 0 || sc.got == chunk_size) {
@@ -1530,15 +1701,25 @@ static int db_select_chunk_tree(Process *p, DbTable *tbl, Eterm tid,
}
-/*
-** This is called when select_delete traps
-*/
-static int db_select_delete_continue_tree(Process *p,
- DbTable *tbl,
- Eterm continuation,
- Eterm *ret)
+static int db_select_chunk_tree(Process *p, DbTable *tbl, Eterm tid,
+ Eterm pattern, Sint chunk_size,
+ int reverse,
+ Eterm *ret)
{
DbTableTree *tb = &tbl->tree;
+ return db_select_chunk_tree_common(p, tbl,
+ tid, pattern, chunk_size,
+ reverse, ret, tb, NULL);
+}
+
+
+int db_select_delete_continue_tree_common(Process *p,
+ DbTable *tbl,
+ Eterm continuation,
+ Eterm *ret,
+ DbTreeStack* stack,
+ CATreeRootIterator* iter)
+{
struct select_delete_context sc;
unsigned sz;
Eterm *hp;
@@ -1549,10 +1730,9 @@ static int db_select_delete_continue_tree(Process *p,
Eterm *tptr;
Eterm eaccsum;
-
#define RET_TO_BIF(Term, State) do { \
if (sc.erase_lastterm) { \
- free_term(tb, sc.lastterm); \
+ free_term(tbl, sc.lastterm); \
} \
*ret = (Term); \
return State; \
@@ -1571,7 +1751,8 @@ static int db_select_delete_continue_tree(Process *p,
mp = erts_db_get_match_prog_binary_unchecked(tptr[4]);
sc.p = p;
- sc.tb = tb;
+ sc.tb = &tbl->common;
+ sc.stack = stack;
if (is_big(tptr[5])) {
sc.accum = big_to_uint32(tptr[5]);
} else {
@@ -1580,17 +1761,26 @@ static int db_select_delete_continue_tree(Process *p,
sc.mp = mp;
sc.end_condition = NIL;
sc.max = 1000;
- sc.keypos = tb->common.keypos;
+ sc.keypos = tbl->common.keypos;
- ASSERT(!erts_atomic_read_nob(&tb->is_stack_busy));
- traverse_backwards(tb, &tb->static_stack, lastkey, &doit_select_delete, &sc);
+ if (iter) {
+ iter->next_route_key = lastkey;
+ sc.common.root = catree_find_prev_root(iter, NULL);
+ }
+ else {
+ sc.common.root = &tbl->tree.root;
+ }
- BUMP_REDS(p, 1000 - sc.max);
+ if (sc.common.root) {
+ traverse_backwards(&tbl->common, stack, lastkey, &doit_select_delete, &sc.common, iter);
+
+ BUMP_REDS(p, 1000 - sc.max);
+ }
if (sc.max > 0) {
RET_TO_BIF(erts_make_integer(sc.accum, p), DB_ERROR_NONE);
}
- key = GETKEY(tb, (sc.lastterm)->dbterm.tpl);
+ key = GETKEY(&tbl->common, (sc.lastterm)->dbterm.tpl);
if (end_condition != NIL &&
cmp_partly_bound(end_condition,key) > 0) { /* done anyway */
RET_TO_BIF(erts_make_integer(sc.accum,p),DB_ERROR_NONE);
@@ -1620,10 +1810,23 @@ static int db_select_delete_continue_tree(Process *p,
#undef RET_TO_BIF
}
-static int db_select_delete_tree(Process *p, DbTable *tbl, Eterm tid,
- Eterm pattern, Eterm *ret)
+static int db_select_delete_continue_tree(Process *p,
+ DbTable *tbl,
+ Eterm continuation,
+ Eterm *ret)
{
DbTableTree *tb = &tbl->tree;
+ ASSERT(!erts_atomic_read_nob(&tb->is_stack_busy));
+ return db_select_delete_continue_tree_common(p, tbl, continuation, ret,
+ &tb->static_stack, NULL);
+}
+
+int db_select_delete_tree_common(Process *p, DbTable *tbl,
+ Eterm tid, Eterm pattern,
+ Eterm *ret,
+ DbTreeStack* stack,
+ CATreeRootIterator* iter)
+{
struct select_delete_context sc;
struct mp_info mpi;
Eterm lastkey = THE_NON_VALUE;
@@ -1641,7 +1844,7 @@ static int db_select_delete_tree(Process *p, DbTable *tbl, Eterm tid,
erts_bin_free(mpi.mp); \
} \
if (sc.erase_lastterm) { \
- free_term(tb, sc.lastterm); \
+ free_term(tbl, sc.lastterm); \
} \
*ret = (Term); \
return RetVal; \
@@ -1655,42 +1858,57 @@ static int db_select_delete_tree(Process *p, DbTable *tbl, Eterm tid,
sc.p = p;
sc.max = 1000;
sc.end_condition = NIL;
- sc.keypos = tb->common.keypos;
- sc.tb = tb;
+ sc.keypos = tbl->common.keypos;
+ sc.tb = &tbl->common;
+ sc.stack = stack;
- if ((errcode = analyze_pattern(tb, pattern, NULL, &mpi)) != DB_ERROR_NONE) {
+ if ((errcode = analyze_pattern(&tbl->common, pattern, NULL, &mpi)) != DB_ERROR_NONE) {
RET_TO_BIF(0,errcode);
}
- if (!mpi.something_can_match) {
+ if (mpi.key_boundness == MS_KEY_IMPOSSIBLE) {
RET_TO_BIF(make_small(0),DB_ERROR_NONE);
/* can't possibly match anything */
}
sc.mp = mpi.mp;
- if (!mpi.got_partial && mpi.some_limitation &&
- CMP_EQ(mpi.least,mpi.most)) {
- doit_select_delete(tb,*(mpi.save_term),&sc, 0 /* direction doesn't
+ if (mpi.key_boundness == MS_KEY_BOUND) {
+ ASSERT(CMP_EQ(mpi.least, mpi.most));
+ if (iter)
+ sc.common.root = catree_find_root(mpi.least, iter);
+ else
+ sc.common.root = &tbl->tree.root;
+ this = find_node(&tbl->common, *sc.common.root, mpi.least, NULL);
+ if (this)
+ doit_select_delete(&tbl->common, this, &sc.common, 0 /* direction doesn't
matter */);
RET_TO_BIF(erts_make_integer(sc.accum,p),DB_ERROR_NONE);
}
- if (mpi.some_limitation) {
- if ((this = find_next_from_pb_key(tb, &tb->static_stack, mpi.most)) != NULL) {
- lastkey = GETKEY(tb, this->dbterm.tpl);
- }
+ if (mpi.key_boundness == MS_KEY_PARTIALLY_BOUND) {
+ this = find_next_from_pb_key(tbl, &sc.common.root, stack, mpi.most, iter);
+ if (this)
+ lastkey = GETKEY(&tbl->common, this->dbterm.tpl);
sc.end_condition = mpi.least;
}
+ else {
+ ASSERT(mpi.key_boundness == MS_KEY_UNBOUND);
+ if (iter)
+ sc.common.root = catree_find_last_root(iter);
+ else
+ sc.common.root = &tbl->tree.root;
+ }
- traverse_backwards(tb, &tb->static_stack, lastkey, &doit_select_delete, &sc);
+ traverse_backwards(&tbl->common, stack, lastkey,
+ &doit_select_delete, &sc.common, iter);
BUMP_REDS(p, 1000 - sc.max);
if (sc.max > 0) {
RET_TO_BIF(erts_make_integer(sc.accum,p), DB_ERROR_NONE);
}
- key = GETKEY(tb, (sc.lastterm)->dbterm.tpl);
+ key = GETKEY(&tbl->common, (sc.lastterm)->dbterm.tpl);
sz = size_object(key);
if (IS_USMALL(0, sc.accum)) {
hp = HAlloc(p, sz + ERTS_MAGIC_REF_THING_SIZE + 6);
@@ -1714,7 +1932,7 @@ static int db_select_delete_tree(Process *p, DbTable *tbl, Eterm tid,
/* Don't free mpi.mp, so don't use macro */
if (sc.erase_lastterm) {
- free_term(tb, sc.lastterm);
+ free_term(tbl, sc.lastterm);
}
*ret = bif_trap1(&ets_select_delete_continue_exp, p, continuation);
return DB_ERROR_NONE;
@@ -1723,12 +1941,21 @@ static int db_select_delete_tree(Process *p, DbTable *tbl, Eterm tid,
}
-static int db_select_replace_continue_tree(Process *p,
+static int db_select_delete_tree(Process *p, DbTable *tbl, Eterm tid,
+ Eterm pattern, Eterm *ret)
+{
+ DbTableTree *tb = &tbl->tree;
+ return db_select_delete_tree_common(p, tbl, tid, pattern, ret,
+ &tb->static_stack, NULL);
+}
+
+int db_select_replace_continue_tree_common(Process *p,
DbTable *tbl,
Eterm continuation,
- Eterm *ret)
+ Eterm *ret,
+ DbTableTree *stack_container,
+ CATreeRootIterator* iter)
{
- DbTableTree *tb = &tbl->tree;
DbTreeStack* stack;
struct select_replace_context sc;
unsigned sz;
@@ -1764,7 +1991,7 @@ static int db_select_replace_continue_tree(Process *p,
sc.end_condition = NIL;
sc.lastobj = NULL;
sc.max = 1000;
- sc.keypos = tb->common.keypos;
+ sc.keypos = tbl->common.keypos;
if (is_big(tptr[5])) {
sc.replaced = big_to_uint32(tptr[5]);
} else {
@@ -1772,9 +1999,18 @@ static int db_select_replace_continue_tree(Process *p,
}
prev_replaced = sc.replaced;
- stack = get_any_stack(tb);
- traverse_update_backwards(tb, stack, lastkey, &doit_select_replace, &sc);
- release_stack(tb,stack);
+ if (iter) {
+ iter->next_route_key = lastkey;
+ sc.common.root = catree_find_prev_root(iter, NULL);
+ }
+ else {
+ sc.common.root = &tbl->tree.root;
+ }
+
+ stack = get_any_stack(tbl, stack_container);
+ traverse_update_backwards(&tbl->common, stack, lastkey, &doit_select_replace,
+ &sc.common, iter);
+ release_stack(tbl, stack_container,stack);
// the more objects we've replaced, the more reductions we've consumed
BUMP_REDS(p, MIN(2000, (1000 - sc.max) + (sc.replaced - prev_replaced)));
@@ -1782,7 +2018,7 @@ static int db_select_replace_continue_tree(Process *p,
if (sc.max > 0) {
RET_TO_BIF(erts_make_integer(sc.replaced,p), DB_ERROR_NONE);
}
- key = GETKEY(tb, sc.lastobj);
+ key = GETKEY(tbl, sc.lastobj);
if (end_condition != NIL &&
(cmp_partly_bound(end_condition,key) > 0)) {
/* done anyway */
@@ -1813,10 +2049,20 @@ static int db_select_replace_continue_tree(Process *p,
#undef RET_TO_BIF
}
-static int db_select_replace_tree(Process *p, DbTable *tbl, Eterm tid,
- Eterm pattern, Eterm *ret)
+static int db_select_replace_continue_tree(Process *p,
+ DbTable *tbl,
+ Eterm continuation,
+ Eterm *ret)
+{
+ return db_select_replace_continue_tree_common(p, tbl, continuation, ret,
+ &tbl->tree, NULL);
+}
+
+int db_select_replace_tree_common(Process *p, DbTable *tbl,
+ Eterm tid, Eterm pattern, Eterm *ret,
+ DbTableTree *stack_container,
+ CATreeRootIterator* iter)
{
- DbTableTree *tb = &tbl->tree;
DbTreeStack* stack;
struct select_replace_context sc;
struct mp_info mpi;
@@ -1843,48 +2089,64 @@ static int db_select_replace_tree(Process *p, DbTable *tbl, Eterm tid,
sc.lastobj = NULL;
sc.p = p;
- sc.tb = tb;
+ sc.tb = &tbl->common;
sc.max = 1000;
sc.end_condition = NIL;
- sc.keypos = tb->common.keypos;
+ sc.keypos = tbl->common.keypos;
sc.replaced = 0;
- if ((errcode = analyze_pattern(tb, pattern, db_match_keeps_key, &mpi)) != DB_ERROR_NONE) {
+ if ((errcode = analyze_pattern(&tbl->common, pattern, db_match_keeps_key, &mpi)) != DB_ERROR_NONE) {
RET_TO_BIF(NIL,errcode);
}
- if (!mpi.something_can_match) {
+ if (mpi.key_boundness == MS_KEY_IMPOSSIBLE) {
RET_TO_BIF(make_small(0),DB_ERROR_NONE);
/* can't possibly match anything */
}
sc.mp = mpi.mp;
- if (!mpi.got_partial && mpi.some_limitation &&
- CMP_EQ(mpi.least,mpi.most)) {
- doit_select_replace(tb,mpi.save_term,&sc,0 /* dummy */);
- reset_static_stack(tb); /* may refer replaced term */
+ if (mpi.key_boundness == MS_KEY_BOUND) {
+ TreeDbTerm** pp;
+ ASSERT(CMP_EQ(mpi.least, mpi.most));
+ if (iter)
+ sc.common.root = catree_find_root(mpi.least, iter);
+ else
+ sc.common.root = &tbl->tree.root;
+ pp = find_node2(&tbl->common, sc.common.root, mpi.least);
+ if (pp) {
+ doit_select_replace(&tbl->common, pp, &sc.common, 0 /* dummy */);
+ reset_static_stack(stack_container); /* may refer replaced term */
+ }
RET_TO_BIF(erts_make_integer(sc.replaced,p),DB_ERROR_NONE);
}
- stack = get_any_stack(tb);
+ stack = get_any_stack(tbl,stack_container);
- if (mpi.some_limitation) {
- if ((this = find_next_from_pb_key(tb, stack, mpi.most)) != NULL) {
- lastkey = GETKEY(tb, this->dbterm.tpl);
- }
+ if (mpi.key_boundness == MS_KEY_PARTIALLY_BOUND) {
+ this = find_next_from_pb_key(tbl, &sc.common.root, stack, mpi.most, iter);
+ if (this)
+ lastkey = GETKEY(tbl, this->dbterm.tpl);
sc.end_condition = mpi.least;
}
+ else {
+ ASSERT(mpi.key_boundness == MS_KEY_UNBOUND);
+ if (iter)
+ sc.common.root = catree_find_last_root(iter);
+ else
+ sc.common.root = &tbl->tree.root;
+ }
- traverse_update_backwards(tb, stack, lastkey, &doit_select_replace, &sc);
- release_stack(tb,stack);
+ traverse_update_backwards(&tbl->common, stack, lastkey, &doit_select_replace,
+ &sc.common, iter);
+ release_stack(tbl,stack_container,stack);
// the more objects we've replaced, the more reductions we've consumed
BUMP_REDS(p, MIN(2000, (1000 - sc.max) + sc.replaced));
if (sc.max > 0) {
RET_TO_BIF(erts_make_integer(sc.replaced,p),DB_ERROR_NONE);
}
- key = GETKEY(tb, sc.lastobj);
+ key = GETKEY(tbl, sc.lastobj);
sz = size_object(key);
if (IS_USMALL(0, sc.replaced)) {
hp = HAlloc(p, sz + ERTS_MAGIC_REF_THING_SIZE + 6);
@@ -1914,52 +2176,72 @@ static int db_select_replace_tree(Process *p, DbTable *tbl, Eterm tid,
}
-static int db_take_tree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
+static int db_select_replace_tree(Process *p, DbTable *tbl, Eterm tid,
+ Eterm pattern, Eterm *ret)
+{
+ return db_select_replace_tree_common(p, tbl, tid, pattern, ret,
+ &tbl->tree, NULL);
+}
+
+int db_take_tree_common(Process *p, DbTable *tbl, TreeDbTerm **root,
+ Eterm key, Eterm *ret,
+ DbTreeStack *stack /* NULL if no static stack */)
{
- DbTableTree *tb = &tbl->tree;
TreeDbTerm *this;
*ret = NIL;
- this = linkout_tree(tb, key);
+ this = linkout_tree(&tbl->common, root, key, stack);
if (this) {
Eterm copy, *hp, *hend;
hp = HAlloc(p, this->dbterm.size + 2);
hend = hp + this->dbterm.size + 2;
- copy = db_copy_object_from_ets(&tb->common,
+ copy = db_copy_object_from_ets(&tbl->common,
&this->dbterm, &hp, &MSO(p));
*ret = CONS(hp, copy, NIL);
hp += 2;
HRelease(p, hend, hp);
- free_term(tb, this);
+ free_term(tbl, this);
}
return DB_ERROR_NONE;
}
+static int db_take_tree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
+{
+ DbTableTree *tb = &tbl->tree;
+ return db_take_tree_common(p, tbl, &tb->root,
+ key, ret, &tb->static_stack);
+}
+
/*
** Other interface routines (not directly coupled to one bif)
*/
-
-/* Display tree contents (for dump) */
-static void db_print_tree(fmtfn_t to, void *to_arg,
- int show,
- DbTable *tbl)
+void db_print_tree_common(fmtfn_t to, void *to_arg,
+ int show, TreeDbTerm *root, DbTable *tbl)
{
- DbTableTree *tb = &tbl->tree;
#ifdef TREE_DEBUG
if (show)
erts_print(to, to_arg, "\nTree data dump:\n"
"------------------------------------------------\n");
- do_dump_tree2(&tbl->tree, to, to_arg, show, tb->root, 0);
+ do_dump_tree2(&tbl->common, to, to_arg, show, root, 0);
if (show)
erts_print(to, to_arg, "\n"
"------------------------------------------------\n");
#else
- erts_print(to, to_arg, "Ordered set (AVL tree), Elements: %d\n", NITEMS(tb));
+ erts_print(to, to_arg, "Ordered set (AVL tree), Elements: %d\n", NITEMS(tbl));
#endif
}
+/* Display tree contents (for dump) */
+static void db_print_tree(fmtfn_t to, void *to_arg,
+ int show,
+ DbTable *tbl)
+{
+ DbTableTree *tb = &tbl->tree;
+ db_print_tree_common(to, to_arg, show, tb->root, tbl);
+}
+
/* release all memory occupied by a single table */
static int db_free_empty_table_tree(DbTable *tbl)
{
@@ -1984,8 +2266,10 @@ static SWord db_free_table_continue_tree(DbTable *tbl, SWord reds)
(DbTable *) tb,
(void *) tb->static_stack.array,
sizeof(TreeDbTerm *) * STACK_NEED);
- ASSERT(erts_atomic_read_nob(&tb->common.memory_size)
- == sizeof(DbTable));
+ ASSERT((erts_atomic_read_nob(&tb->common.memory_size)
+ == sizeof(DbTable)) ||
+ (erts_atomic_read_nob(&tb->common.memory_size)
+ == (sizeof(DbTable) + sizeof(DbFixation))));
}
return reds;
}
@@ -2004,11 +2288,18 @@ static void do_db_tree_foreach_offheap(TreeDbTerm *,
void (*)(ErlOffHeap *, void *),
void *);
+void db_foreach_offheap_tree_common(TreeDbTerm *root,
+ void (*func)(ErlOffHeap *, void *),
+ void * arg)
+{
+ do_db_tree_foreach_offheap(root, func, arg);
+}
+
static void db_foreach_offheap_tree(DbTable *tbl,
void (*func)(ErlOffHeap *, void *),
void * arg)
{
- do_db_tree_foreach_offheap(tbl->tree.root, func, arg);
+ db_foreach_offheap_tree_common(tbl->tree.root, func, arg);
}
@@ -2033,13 +2324,14 @@ do_db_tree_foreach_offheap(TreeDbTerm *tdbt,
do_db_tree_foreach_offheap(tdbt->right, func, arg);
}
-static TreeDbTerm *linkout_tree(DbTableTree *tb, Eterm key) {
+static TreeDbTerm *linkout_tree(DbTableCommon *tb, TreeDbTerm **root,
+ Eterm key, DbTreeStack *stack) {
TreeDbTerm **tstack[STACK_NEED];
int tpos = 0;
int dstack[STACK_NEED+1];
int dpos = 0;
int state = 0;
- TreeDbTerm **this = &tb->root;
+ TreeDbTerm **this = root;
Sint c;
int dir;
TreeDbTerm *q = NULL;
@@ -2050,7 +2342,7 @@ static TreeDbTerm *linkout_tree(DbTableTree *tb, Eterm key) {
* keep the balance. As in insert, we do the stacking ourselves.
*/
- reset_static_stack(tb);
+ reset_stack(stack);
dstack[dpos++] = DIR_END;
for (;;) {
if (!*this) { /* Failure */
@@ -2076,30 +2368,30 @@ static TreeDbTerm *linkout_tree(DbTableTree *tb, Eterm key) {
tstack[tpos++] = this;
state = delsub(this);
}
- erts_atomic_dec_nob(&tb->common.nitems);
+ erts_atomic_dec_nob(&tb->nitems);
break;
}
}
while (state && ( dir = dstack[--dpos] ) != DIR_END) {
this = tstack[--tpos];
if (dir == DIR_LEFT) {
- state = balance_left(this);
+ state = tree_balance_left(this);
} else {
- state = balance_right(this);
+ state = tree_balance_right(this);
}
}
return q;
}
-static TreeDbTerm *linkout_object_tree(DbTableTree *tb,
- Eterm object)
+static TreeDbTerm *linkout_object_tree(DbTableCommon *tb, TreeDbTerm **root,
+ Eterm object, DbTableTree *stack)
{
TreeDbTerm **tstack[STACK_NEED];
int tpos = 0;
int dstack[STACK_NEED+1];
int dpos = 0;
int state = 0;
- TreeDbTerm **this = &tb->root;
+ TreeDbTerm **this = root;
Sint c;
int dir;
TreeDbTerm *q = NULL;
@@ -2114,7 +2406,7 @@ static TreeDbTerm *linkout_object_tree(DbTableTree *tb,
key = GETKEY(tb, tuple_val(object));
- reset_static_stack(tb);
+ reset_static_stack(stack);
dstack[dpos++] = DIR_END;
for (;;) {
if (!*this) { /* Failure */
@@ -2128,7 +2420,7 @@ static TreeDbTerm *linkout_object_tree(DbTableTree *tb,
tstack[tpos++] = this;
this = &((*this)->right);
} else { /* Equal key, found the only possible matching object*/
- if (!db_eq(&tb->common,object,&(*this)->dbterm)) {
+ if (!db_eq(tb,object,&(*this)->dbterm)) {
return NULL;
}
q = (*this);
@@ -2143,16 +2435,16 @@ static TreeDbTerm *linkout_object_tree(DbTableTree *tb,
tstack[tpos++] = this;
state = delsub(this);
}
- erts_atomic_dec_nob(&tb->common.nitems);
+ erts_atomic_dec_nob(&tb->nitems);
break;
}
}
while (state && ( dir = dstack[--dpos] ) != DIR_END) {
this = tstack[--tpos];
if (dir == DIR_LEFT) {
- state = balance_left(this);
+ state = tree_balance_left(this);
} else {
- state = balance_right(this);
+ state = tree_balance_right(this);
}
}
return q;
@@ -2162,7 +2454,7 @@ static TreeDbTerm *linkout_object_tree(DbTableTree *tb,
** For the select functions, analyzes the pattern and determines which
** part of the tree should be searched. Also compiles the match program
*/
-static int analyze_pattern(DbTableTree *tb, Eterm pattern,
+static int analyze_pattern(DbTableCommon *tb, Eterm pattern,
extra_match_validator_t extra_validator, /* Optional callback */
struct mp_info *mpi)
{
@@ -2173,17 +2465,12 @@ static int analyze_pattern(DbTableTree *tb, Eterm pattern,
Eterm *ptpl;
int i;
int num_heads = 0;
- Eterm key;
- Eterm partly_bound;
- int res;
- Eterm least = 0;
- Eterm most = 0;
+ Eterm least = THE_NON_VALUE;
+ Eterm most = THE_NON_VALUE;
+ enum ms_key_boundness boundness;
- mpi->some_limitation = 1;
- mpi->got_partial = 0;
- mpi->something_can_match = 0;
+ mpi->key_boundness = MS_KEY_IMPOSSIBLE;
mpi->mp = NULL;
- mpi->save_term = NULL;
for (lst = pattern; is_list(lst); lst = CDR(list_val(lst)))
++num_heads;
@@ -2204,6 +2491,7 @@ static int analyze_pattern(DbTableTree *tb, Eterm pattern,
Eterm match;
Eterm guard;
Eterm body;
+ Eterm key;
ttpl = CAR(list_val(lst));
if (!is_tuple(ttpl)) {
@@ -2223,7 +2511,7 @@ static int analyze_pattern(DbTableTree *tb, Eterm pattern,
guards[i] = guard = ptpl[2];
bodies[i] = body = ptpl[3];
- if(extra_validator != NULL && !extra_validator(tb->common.keypos, match, guard, body)) {
+ if(extra_validator != NULL && !extra_validator(tb->keypos, match, guard, body)) {
if (buff != sbuff) {
erts_free(ERTS_ALC_T_DB_TMP, buff);
}
@@ -2235,30 +2523,29 @@ static int analyze_pattern(DbTableTree *tb, Eterm pattern,
}
++i;
- partly_bound = NIL;
- res = key_given(tb, tpl, &(mpi->save_term), &partly_bound);
- if ( res >= 0 ) { /* Can match something */
- key = 0;
- mpi->something_can_match = 1;
- if (res > 0) {
- key = GETKEY(tb,tuple_val(tpl));
- } else if (partly_bound != NIL) {
- mpi->got_partial = 1;
- key = partly_bound;
- } else {
- mpi->some_limitation = 0;
- }
- if (key != 0) {
- if (least == 0 ||
- partly_bound_can_match_lesser(key,least)) {
- least = key;
- }
- if (most == 0 ||
- partly_bound_can_match_greater(key,most)) {
- most = key;
- }
- }
- }
+ boundness = key_boundness(tb, tpl, &key);
+ switch (boundness)
+ {
+ case MS_KEY_BOUND:
+ case MS_KEY_PARTIALLY_BOUND:
+ if (is_non_value(least) || partly_bound_can_match_lesser(key,least)) {
+ least = key;
+ }
+ if (is_non_value(most) || partly_bound_can_match_greater(key,most)) {
+ most = key;
+ }
+ break;
+ case MS_KEY_IMPOSSIBLE:
+ case MS_KEY_UNBOUND:
+ break;
+ }
+ if (mpi->key_boundness > boundness)
+ mpi->key_boundness = boundness;
+ }
+
+ if (mpi->key_boundness == MS_KEY_BOUND && !CMP_EQ(least, most)) {
+ /* Several different bound keys */
+ mpi->key_boundness = MS_KEY_PARTIALLY_BOUND;
}
mpi->least = least;
mpi->most = most;
@@ -2300,7 +2587,7 @@ static SWord do_free_tree_continue(DbTableTree *tb, SWord reds)
PUSH_NODE(&tb->static_stack, root);
root = p;
} else {
- free_term(tb, root);
+ free_term((DbTable*)tb, root);
if (--reds < 0) {
return reds; /* Done enough for now */
}
@@ -2314,7 +2601,7 @@ static SWord do_free_tree_continue(DbTableTree *tb, SWord reds)
/*
* Deletion helpers
*/
-static int balance_left(TreeDbTerm **this)
+int tree_balance_left(TreeDbTerm **this)
{
TreeDbTerm *p, *p1, *p2;
int b1, b2, h = 1;
@@ -2359,7 +2646,7 @@ static int balance_left(TreeDbTerm **this)
return h;
}
-static int balance_right(TreeDbTerm **this)
+int tree_balance_right(TreeDbTerm **this)
{
TreeDbTerm *p, *p1, *p2;
int b1, b2, h = 1;
@@ -2431,7 +2718,7 @@ static int delsub(TreeDbTerm **this)
h = 1;
while (tpos && h) {
r = tstack[--tpos];
- h = balance_right(r);
+ h = tree_balance_right(r);
}
return h;
}
@@ -2440,11 +2727,29 @@ static int delsub(TreeDbTerm **this)
* Helper for db_slot
*/
-static TreeDbTerm *slot_search(Process *p, DbTableTree *tb, Sint slot)
+static TreeDbTerm *slot_search(Process *p, TreeDbTerm *root,
+ Sint slot, DbTable *tb,
+ DbTableTree *stack_container,
+ CATreeRootIterator *iter)
{
TreeDbTerm *this;
TreeDbTerm *tmp;
- DbTreeStack* stack = get_any_stack(tb);
+ TreeDbTerm *lastobj;
+ Eterm lastkey;
+ TreeDbTerm **pp;
+ DbTreeStack* stack;
+
+ if (iter) {
+ /* Find first non-empty tree */
+ while (!root) {
+ TreeDbTerm** pp = catree_find_next_root(iter, NULL);
+ if (!pp)
+ return NULL;
+ root = *pp;
+ }
+ }
+
+ stack = get_any_stack(tb,stack_container);
ASSERT(stack != NULL);
if (slot == 1) { /* Don't search from where we are if we are
@@ -2456,57 +2761,84 @@ static TreeDbTerm *slot_search(Process *p, DbTableTree *tb, Sint slot)
are not recorded */
stack->pos = 0;
}
- if (EMPTY_NODE(stack)) {
- this = tb->root;
- if (this == NULL)
- goto done;
- while (this->left != NULL){
- PUSH_NODE(stack, this);
- this = this->left;
- }
- PUSH_NODE(stack, this);
- stack->slot = 1;
- }
- this = TOP_NODE(stack);
- while (stack->slot != slot && this != NULL) {
- if (slot > stack->slot) {
- if (this->right != NULL) {
- this = this->right;
- while (this->left != NULL) {
- PUSH_NODE(stack, this);
- this = this->left;
- }
- PUSH_NODE(stack, this);
- } else {
- for (;;) {
- tmp = POP_NODE(stack);
- this = TOP_NODE(stack);
- if (this == NULL || this->left == tmp)
- break;
- }
- }
- ++(stack->slot);
- } else {
- if (this->left != NULL) {
- this = this->left;
- while (this->right != NULL) {
- PUSH_NODE(stack, this);
- this = this->right;
- }
- PUSH_NODE(stack, this);
- } else {
- for (;;) {
- tmp = POP_NODE(stack);
- this = TOP_NODE(stack);
- if (this == NULL || this->right == tmp)
- break;
- }
- }
- --(stack->slot);
- }
+ while (1) {
+ if (EMPTY_NODE(stack)) {
+ this = root;
+ if (this == NULL)
+ goto next_root;
+ while (this->left != NULL){
+ PUSH_NODE(stack, this);
+ this = this->left;
+ }
+ PUSH_NODE(stack, this);
+ stack->slot++;
+ }
+ this = TOP_NODE(stack);
+ while (stack->slot != slot) {
+ ASSERT(this);
+ lastobj = this;
+ if (slot > stack->slot) {
+ if (this->right != NULL) {
+ this = this->right;
+ while (this->left != NULL) {
+ PUSH_NODE(stack, this);
+ this = this->left;
+ }
+ PUSH_NODE(stack, this);
+ } else {
+ for (;;) {
+ tmp = POP_NODE(stack);
+ this = TOP_NODE(stack);
+ if (!this)
+ goto next_root;
+ if (this->left == tmp)
+ break;
+ }
+ }
+ ++(stack->slot);
+ } else {
+ if (this->left != NULL) {
+ this = this->left;
+ while (this->right != NULL) {
+ PUSH_NODE(stack, this);
+ this = this->right;
+ }
+ PUSH_NODE(stack, this);
+ } else {
+ for (;;) {
+ tmp = POP_NODE(stack);
+ this = TOP_NODE(stack);
+ if (!this)
+ goto next_root;
+ if (this->right == tmp)
+ break;
+ }
+ }
+ --(stack->slot);
+ }
+ }
+ /* Found slot */
+ ASSERT(this);
+ break;
+
+next_root:
+ if (!iter)
+ break; /* EOT */
+
+ ASSERT(slot > stack->slot);
+ if (lastobj) {
+ lastkey = GETKEY(tb, lastobj->dbterm.tpl);
+ lastobj = NULL;
+ }
+ pp = catree_find_next_root(iter, &lastkey);
+ if (!pp)
+ break; /* EOT */
+ root = *pp;
+ stack->pos = 0;
+ find_next(&tb->common, root, stack, lastkey);
}
-done:
- release_stack(tb,stack);
+
+ release_stack(tb,stack_container,stack);
return this;
}
@@ -2514,7 +2846,8 @@ done:
* Find next and previous in sort order
*/
-static TreeDbTerm *find_next(DbTableTree *tb, DbTreeStack* stack, Eterm key) {
+static TreeDbTerm *find_next(DbTableCommon *tb, TreeDbTerm *root,
+ DbTreeStack* stack, Eterm key) {
TreeDbTerm *this;
TreeDbTerm *tmp;
Sint c;
@@ -2526,7 +2859,7 @@ static TreeDbTerm *find_next(DbTableTree *tb, DbTreeStack* stack, Eterm key) {
}
}
if (EMPTY_NODE(stack)) { /* Have to rebuild the stack */
- if (( this = tb->root ) == NULL)
+ if (( this = root ) == NULL)
return NULL;
for (;;) {
PUSH_NODE(stack, this);
@@ -2539,7 +2872,7 @@ static TreeDbTerm *find_next(DbTableTree *tb, DbTreeStack* stack, Eterm key) {
this = this->right;
} else if (c < 0) {
if (this->left == NULL) /* Done */
- return this;
+ goto found_next;
else
this = this->left;
} else
@@ -2554,8 +2887,6 @@ static TreeDbTerm *find_next(DbTableTree *tb, DbTreeStack* stack, Eterm key) {
this = this->left;
PUSH_NODE(stack, this);
}
- if (stack->slot > 0)
- ++(stack->slot);
} else {
do {
tmp = POP_NODE(stack);
@@ -2564,13 +2895,17 @@ static TreeDbTerm *find_next(DbTableTree *tb, DbTreeStack* stack, Eterm key) {
return NULL;
}
} while (this->right == tmp);
- if (stack->slot > 0)
- ++(stack->slot);
}
+
+found_next:
+ if (stack->slot > 0)
+ ++(stack->slot);
+
return this;
}
-static TreeDbTerm *find_prev(DbTableTree *tb, DbTreeStack* stack, Eterm key) {
+static TreeDbTerm *find_prev(DbTableCommon *tb, TreeDbTerm *root,
+ DbTreeStack* stack, Eterm key) {
TreeDbTerm *this;
TreeDbTerm *tmp;
Sint c;
@@ -2582,7 +2917,7 @@ static TreeDbTerm *find_prev(DbTableTree *tb, DbTreeStack* stack, Eterm key) {
}
}
if (EMPTY_NODE(stack)) { /* Have to rebuild the stack */
- if (( this = tb->root ) == NULL)
+ if (( this = root ) == NULL)
return NULL;
for (;;) {
PUSH_NODE(stack, this);
@@ -2595,7 +2930,7 @@ static TreeDbTerm *find_prev(DbTableTree *tb, DbTreeStack* stack, Eterm key) {
this = this->left;
} else if (c > 0) {
if (this->right == NULL) /* Done */
- return this;
+ goto found_prev;
else
this = this->right;
} else
@@ -2610,8 +2945,6 @@ static TreeDbTerm *find_prev(DbTableTree *tb, DbTreeStack* stack, Eterm key) {
this = this->right;
PUSH_NODE(stack, this);
}
- if (stack->slot > 0)
- --(stack->slot);
} else {
do {
tmp = POP_NODE(stack);
@@ -2620,74 +2953,112 @@ static TreeDbTerm *find_prev(DbTableTree *tb, DbTreeStack* stack, Eterm key) {
return NULL;
}
} while (this->left == tmp);
- if (stack->slot > 0)
- --(stack->slot);
}
+
+found_prev:
+ if (stack->slot > 0)
+ --(stack->slot);
+
return this;
}
-static TreeDbTerm *find_next_from_pb_key(DbTableTree *tb, DbTreeStack* stack,
- Eterm key)
+
+/* @brief Find object with smallest key of all larger than partially bound key.
+ * Can be used as a starting point for a reverse iteration with pb_key.
+ *
+ * @param pb_key The partially bound key. Example {42, '$1'}
+ * @param *rootpp Will return pointer to root pointer of tree with found object.
+ * @param iter Root iterator or NULL for plain DbTableTree.
+ * @param stack A stack to use. Will be cleared.
+ *
+ * @return found object or NULL if no such key exists.
+ */
+static TreeDbTerm *find_next_from_pb_key(DbTable *tbl, TreeDbTerm*** rootpp,
+ DbTreeStack* stack, Eterm pb_key,
+ CATreeRootIterator* iter)
{
+ TreeDbTerm* root;
TreeDbTerm *this;
- TreeDbTerm *tmp;
+ Uint candidate = 0;
Sint c;
+ if (iter) {
+ *rootpp = catree_find_next_from_pb_key_root(pb_key, iter);
+ ASSERT(*rootpp);
+ root = **rootpp;
+ }
+ else {
+ *rootpp = &tbl->tree.root;
+ root = tbl->tree.root;
+ }
+
/* spool the stack, we have to "re-search" */
stack->pos = stack->slot = 0;
- if (( this = tb->root ) == NULL)
+ if (( this = root ) == NULL)
return NULL;
for (;;) {
PUSH_NODE(stack, this);
- if (( c = cmp_partly_bound(key,GETKEY(tb, this->dbterm.tpl))) >= 0) {
+ if (( c = cmp_partly_bound(pb_key,GETKEY(tbl, this->dbterm.tpl))) >= 0) {
if (this->right == NULL) {
- do {
- tmp = POP_NODE(stack);
- if (( this = TOP_NODE(stack)) == NULL) {
- return NULL;
- }
- } while (this->right == tmp);
- return this;
- } else
- this = this->right;
+ stack->pos = candidate;
+ return TOP_NODE(stack);
+ }
+ this = this->right;
} else /*if (c < 0)*/ {
if (this->left == NULL) /* Done */
return this;
- else
- this = this->left;
+ candidate = stack->pos;
+ this = this->left;
}
}
}
-static TreeDbTerm *find_prev_from_pb_key(DbTableTree *tb, DbTreeStack* stack,
- Eterm key)
+/* @brief Find object with largest key of all smaller than partially bound key.
+ * Can be used as a starting point for a forward iteration with pb_key.
+ *
+ * @param pb_key The partially bound key. Example {42, '$1'}
+ * @param *rootpp Will return pointer to root pointer of found object.
+ * @param iter Root iterator or NULL for plain DbTableTree.
+ * @param stack A stack to use. Will be cleared.
+ *
+ * @return found object or NULL if no such key exists.
+ */
+static TreeDbTerm *find_prev_from_pb_key(DbTable *tbl, TreeDbTerm*** rootpp,
+ DbTreeStack* stack, Eterm pb_key,
+ CATreeRootIterator* iter)
{
+ TreeDbTerm* root;
TreeDbTerm *this;
- TreeDbTerm *tmp;
+ Uint candidate = 0;
Sint c;
+ if (iter) {
+ *rootpp = catree_find_prev_from_pb_key_root(pb_key, iter);
+ ASSERT(*rootpp);
+ root = **rootpp;
+ }
+ else {
+ *rootpp = &tbl->tree.root;
+ root = tbl->tree.root;
+ }
+
/* spool the stack, we have to "re-search" */
stack->pos = stack->slot = 0;
- if (( this = tb->root ) == NULL)
+ if (( this = root ) == NULL)
return NULL;
for (;;) {
PUSH_NODE(stack, this);
- if (( c = cmp_partly_bound(key,GETKEY(tb, this->dbterm.tpl))) <= 0) {
+ if (( c = cmp_partly_bound(pb_key,GETKEY(tbl, this->dbterm.tpl))) <= 0) {
if (this->left == NULL) {
- do {
- tmp = POP_NODE(stack);
- if (( this = TOP_NODE(stack)) == NULL) {
- return NULL;
- }
- } while (this->left == tmp);
- return this;
- } else
- this = this->left;
- } else /*if (c < 0)*/ {
+ stack->pos = candidate;
+ return TOP_NODE(stack);
+ }
+ this = this->left;
+ } else /*if (c > 0)*/ {
if (this->right == NULL) /* Done */
return this;
- else
- this = this->right;
+ candidate = stack->pos;
+ this = this->right;
}
}
}
@@ -2696,16 +3067,17 @@ static TreeDbTerm *find_prev_from_pb_key(DbTableTree *tb, DbTreeStack* stack,
/*
* Just lookup a node
*/
-static TreeDbTerm *find_node(DbTableTree *tb, Eterm key)
+static TreeDbTerm *find_node(DbTableCommon *tb, TreeDbTerm *root,
+ Eterm key, DbTableTree *stack_container)
{
TreeDbTerm *this;
Sint res;
- DbTreeStack* stack = get_static_stack(tb);
+ DbTreeStack* stack = get_static_stack(stack_container);
if(!stack || EMPTY_NODE(stack)
|| !cmp_key_eq(tb, key, (this=TOP_NODE(stack)))) {
- this = tb->root;
+ this = root;
while (this != NULL && (res = cmp_key(tb,key,this)) != 0) {
if (res < 0)
this = this->left;
@@ -2714,7 +3086,7 @@ static TreeDbTerm *find_node(DbTableTree *tb, Eterm key)
}
}
if (stack) {
- release_stack(tb,stack);
+ release_stack((DbTable*)tb,stack_container,stack);
}
return this;
}
@@ -2722,12 +3094,12 @@ static TreeDbTerm *find_node(DbTableTree *tb, Eterm key)
/*
* Lookup a node and return the address of the node pointer in the tree
*/
-static TreeDbTerm **find_node2(DbTableTree *tb, Eterm key)
+static TreeDbTerm **find_node2(DbTableCommon *tb, TreeDbTerm **root, Eterm key)
{
TreeDbTerm **this;
Sint res;
- this = &tb->root;
+ this = root;
while ((*this) != NULL && (res = cmp_key(tb, key, *this)) != 0) {
if (res < 0)
this = &((*this)->left);
@@ -2744,7 +3116,8 @@ static TreeDbTerm **find_node2(DbTableTree *tb, Eterm key)
* Tries to reuse the existing stack for performance.
*/
-static TreeDbTerm **find_ptr(DbTableTree *tb, DbTreeStack *stack, TreeDbTerm *this) {
+static TreeDbTerm **find_ptr(DbTableCommon *tb, TreeDbTerm **root,
+ DbTreeStack *stack, TreeDbTerm *this) {
Eterm key = GETKEY(tb, this->dbterm.tpl);
TreeDbTerm *tmp;
TreeDbTerm *parent;
@@ -2757,7 +3130,7 @@ static TreeDbTerm **find_ptr(DbTableTree *tb, DbTreeStack *stack, TreeDbTerm *th
}
}
if (EMPTY_NODE(stack)) { /* Have to rebuild the stack */
- if (( tmp = tb->root ) == NULL)
+ if (( tmp = *root ) == NULL)
return NULL;
for (;;) {
PUSH_NODE(stack, tmp);
@@ -2783,7 +3156,7 @@ static TreeDbTerm **find_ptr(DbTableTree *tb, DbTreeStack *stack, TreeDbTerm *th
parent = TOPN_NODE(stack, 1);
if (parent == NULL)
- return ((this != tb->root) ? NULL : &(tb->root));
+ return ((this != *root) ? NULL : root);
if (parent->left == this)
return &(parent->left);
if (parent->right == this)
@@ -2791,12 +3164,11 @@ static TreeDbTerm **find_ptr(DbTableTree *tb, DbTreeStack *stack, TreeDbTerm *th
return NULL;
}
-static int
-db_lookup_dbterm_tree(Process *p, DbTable *tbl, Eterm key, Eterm obj,
- DbUpdateHandle* handle)
+int db_lookup_dbterm_tree_common(Process *p, DbTable *tbl, TreeDbTerm **root,
+ Eterm key, Eterm obj, DbUpdateHandle* handle,
+ DbTableTree *stack_container)
{
- DbTableTree *tb = &tbl->tree;
- TreeDbTerm **pp = find_node2(tb, key);
+ TreeDbTerm **pp = find_node2(&tbl->common, root, key);
int flags = 0;
if (pp == NULL) {
@@ -2807,18 +3179,19 @@ db_lookup_dbterm_tree(Process *p, DbTable *tbl, Eterm key, Eterm obj,
int arity = arityval(*objp);
Eterm *htop, *hend;
- ASSERT(arity >= tb->common.keypos);
+ ASSERT(arity >= tbl->common.keypos);
htop = HAlloc(p, arity + 1);
hend = htop + arity + 1;
sys_memcpy(htop, objp, sizeof(Eterm) * (arity + 1));
- htop[tb->common.keypos] = key;
+ htop[tbl->common.keypos] = key;
obj = make_tuple(htop);
- if (db_put_tree(tbl, obj, 1) != DB_ERROR_NONE) {
+ if (db_put_tree_common(&tbl->common, root,
+ obj, 1, stack_container) != DB_ERROR_NONE) {
return 0;
}
- pp = find_node2(tb, key);
+ pp = find_node2(&tbl->common, root, key);
ASSERT(pp != NULL);
HRelease(p, hend, htop);
flags |= DB_NEW_OBJECT;
@@ -2833,21 +3206,28 @@ db_lookup_dbterm_tree(Process *p, DbTable *tbl, Eterm key, Eterm obj,
return 1;
}
-static void
-db_finalize_dbterm_tree(int cret, DbUpdateHandle *handle)
+static int
+db_lookup_dbterm_tree(Process *p, DbTable *tbl, Eterm key, Eterm obj,
+ DbUpdateHandle* handle)
{
- DbTable *tbl = handle->tb;
DbTableTree *tb = &tbl->tree;
+ return db_lookup_dbterm_tree_common(p, tbl, &tb->root, key, obj, handle, tb);
+}
+
+void db_finalize_dbterm_tree_common(int cret, DbUpdateHandle *handle,
+ DbTableTree *stack_container)
+{
+ DbTable *tbl = handle->tb;
TreeDbTerm *bp = (TreeDbTerm *) *handle->bp;
if (handle->flags & DB_NEW_OBJECT && cret != DB_ERROR_NONE) {
Eterm ret;
- db_erase_tree(tbl, GETKEY(tb, bp->dbterm.tpl), &ret);
+ db_erase_tree(tbl, GETKEY(&tbl->common, bp->dbterm.tpl), &ret);
} else if (handle->flags & DB_MUST_RESIZE) {
db_finalize_resize(handle, offsetof(TreeDbTerm,dbterm));
- reset_static_stack(tb);
+ reset_static_stack(stack_container);
- free_term(tb, bp);
+ free_term(tbl, bp);
}
#ifdef DEBUG
handle->dbterm = 0;
@@ -2855,156 +3235,207 @@ db_finalize_dbterm_tree(int cret, DbUpdateHandle *handle)
return;
}
+static void
+db_finalize_dbterm_tree(int cret, DbUpdateHandle *handle)
+{
+ DbTable *tbl = handle->tb;
+ DbTableTree *tb = &tbl->tree;
+ db_finalize_dbterm_tree_common(cret, handle, tb);
+}
+
/*
* Traverse the tree with a callback function, used by db_match_xxx
*/
-static void traverse_backwards(DbTableTree *tb,
+static void traverse_backwards(DbTableCommon *tb,
DbTreeStack* stack,
Eterm lastkey,
- int (*doit)(DbTableTree *,
- TreeDbTerm *,
- void *,
- int),
- void *context)
+ traverse_doit_funcT* doit,
+ struct select_common *context,
+ CATreeRootIterator* iter)
{
TreeDbTerm *this, *next;
+ TreeDbTerm** root = context->root;
if (lastkey == THE_NON_VALUE) {
- stack->pos = stack->slot = 0;
- if (( this = tb->root ) == NULL) {
- return;
- }
- while (this != NULL) {
- PUSH_NODE(stack, this);
- this = this->right;
- }
- this = TOP_NODE(stack);
- next = find_prev(tb, stack, GETKEY(tb, this->dbterm.tpl));
- if (!((*doit)(tb, this, context, 0)))
- return;
+ if (iter) {
+ while (*root == NULL) {
+ root = catree_find_prev_root(iter, NULL);
+ if (!root)
+ return;
+ }
+ context->root = root;
+ }
+ stack->pos = stack->slot = 0;
+ next = *root;
+ while (next != NULL) {
+ PUSH_NODE(stack, next);
+ next = next->right;
+ }
+ next = TOP_NODE(stack);
} else {
- next = find_prev(tb, stack, lastkey);
+ next = find_prev(tb, *root, stack, lastkey);
}
- while ((this = next) != NULL) {
- next = find_prev(tb, stack, GETKEY(tb, this->dbterm.tpl));
- if (!((*doit)(tb, this, context, 0)))
- return;
+ while (1) {
+ while (next) {
+ this = next;
+ lastkey = GETKEY(tb, this->dbterm.tpl);
+ next = find_prev(tb, *root, stack, lastkey);
+ if (!((*doit)(tb, this, context, 0)))
+ return;
+ }
+
+ if (!iter)
+ return;
+ ASSERT(is_value(lastkey));
+ root = catree_find_prev_root(iter, &lastkey);
+ if (!root)
+ return;
+ context->root = root;
+ stack->pos = stack->slot = 0;
+ next = find_prev(tb, *root, stack, lastkey);
}
}
/*
* Traverse the tree with a callback function, used by db_match_xxx
*/
-static void traverse_forward(DbTableTree *tb,
+static void traverse_forward(DbTableCommon *tb,
DbTreeStack* stack,
Eterm lastkey,
- int (*doit)(DbTableTree *,
- TreeDbTerm *,
- void *,
- int),
- void *context)
+ traverse_doit_funcT* doit,
+ struct select_common *context,
+ CATreeRootIterator* iter)
{
TreeDbTerm *this, *next;
+ TreeDbTerm **root = context->root;
if (lastkey == THE_NON_VALUE) {
- stack->pos = stack->slot = 0;
- if (( this = tb->root ) == NULL) {
- return;
- }
- while (this != NULL) {
- PUSH_NODE(stack, this);
- this = this->left;
- }
- this = TOP_NODE(stack);
- next = find_next(tb, stack, GETKEY(tb, this->dbterm.tpl));
- if (!((*doit)(tb, this, context, 1)))
- return;
+ if (iter) {
+ while (*root == NULL) {
+ root = catree_find_next_root(iter, NULL);
+ if (!root)
+ return;
+ }
+ context->root = root;
+ }
+ stack->pos = stack->slot = 0;
+ next = *root;
+ while (next != NULL) {
+ PUSH_NODE(stack, next);
+ next = next->left;
+ }
+ next = TOP_NODE(stack);
} else {
- next = find_next(tb, stack, lastkey);
+ next = find_next(tb, *root, stack, lastkey);
}
- while ((this = next) != NULL) {
- next = find_next(tb, stack, GETKEY(tb, this->dbterm.tpl));
- if (!((*doit)(tb, this, context, 1)))
- return;
+ while (1) {
+ while (next) {
+ this = next;
+ lastkey = GETKEY(tb, this->dbterm.tpl);
+ next = find_next(tb, *root, stack, lastkey);
+ if (!((*doit)(tb, this, context, 1)))
+ return;
+ }
+
+ if (!iter)
+ return;
+ ASSERT(is_value(lastkey));
+ root = catree_find_next_root(iter, &lastkey);
+ if (!root)
+ return;
+ context->root = root;
+ stack->pos = stack->slot = 0;
+ next = find_next(tb, *root, stack, lastkey);
}
}
/*
* Traverse the tree with an update callback function, used by db_select_replace
*/
-static void traverse_update_backwards(DbTableTree *tb,
+static void traverse_update_backwards(DbTableCommon *tb,
DbTreeStack* stack,
Eterm lastkey,
- int (*doit)(DbTableTree*,
+ int (*doit)(DbTableCommon*,
TreeDbTerm**,
- void*,
+ struct select_common*,
int),
- void* context)
+ struct select_common* context,
+ CATreeRootIterator* iter)
{
int res;
TreeDbTerm *this, *next, **this_ptr;
+ TreeDbTerm** root = context->root;
if (lastkey == THE_NON_VALUE) {
- stack->pos = stack->slot = 0;
- if (( this = tb->root ) == NULL) {
- return;
+ if (iter) {
+ while (*root == NULL) {
+ root = catree_find_prev_root(iter, NULL);
+ if (!root)
+ return;
+ context->root = root;
+ }
}
- while (this != NULL) {
- PUSH_NODE(stack, this);
- this = this->right;
+ stack->pos = stack->slot = 0;
+ next = *root;
+ while (next) {
+ PUSH_NODE(stack, next);
+ next = next->right;
}
- this = TOP_NODE(stack);
- this_ptr = find_ptr(tb, stack, this);
- ASSERT(this_ptr != NULL);
- res = (*doit)(tb, this_ptr, context, 0);
- REPLACE_TOP_NODE(stack, *this_ptr);
- next = find_prev(tb, stack, GETKEY(tb, (*this_ptr)->dbterm.tpl));
- if (!res)
- return;
- } else {
- next = find_prev(tb, stack, lastkey);
+ next = TOP_NODE(stack);
}
+ else
+ next = find_prev(tb, *root, stack, lastkey);
+
+
+ while (1) {
+ while (next) {
+ this = next;
+ this_ptr = find_ptr(tb, root, stack, this);
+ ASSERT(this_ptr != NULL);
+ res = (*doit)(tb, this_ptr, context, 0);
+ this = *this_ptr;
+ REPLACE_TOP_NODE(stack, this);
+ if (!res)
+ return;
+ lastkey = GETKEY(tb, this->dbterm.tpl);
+ next = find_prev(tb, *root, stack, lastkey);
+ }
- while ((this = next) != NULL) {
- this_ptr = find_ptr(tb, stack, this);
- ASSERT(this_ptr != NULL);
- res = (*doit)(tb, this_ptr, context, 0);
- REPLACE_TOP_NODE(stack, *this_ptr);
- next = find_prev(tb, stack, GETKEY(tb, (*this_ptr)->dbterm.tpl));
- if (!res)
+ if (!iter)
+ return;
+ ASSERT(is_value(lastkey));
+ root = catree_find_prev_root(iter, &lastkey);
+ if (!root)
return;
+ context->root = root;
+ stack->pos = stack->slot = 0;
+ next = find_prev(tb, *root, stack, lastkey);
}
}
-/*
- * Returns 0 if not given 1 if given and -1 on no possible match
- * if key is given; *ret is set to point to the object concerned.
- */
-static int key_given(DbTableTree *tb, Eterm pattern, TreeDbTerm ***ret,
- Eterm *partly_bound)
+static enum ms_key_boundness key_boundness(DbTableCommon *tb,
+ Eterm pattern, Eterm *keyp)
{
- TreeDbTerm **this;
Eterm key;
- ASSERT(ret != NULL);
if (pattern == am_Underscore || db_is_variable(pattern) != -1)
- return 0;
- key = db_getkey(tb->common.keypos, pattern);
+ return MS_KEY_UNBOUND;
+ key = db_getkey(tb->keypos, pattern);
if (is_non_value(key))
- return -1; /* can't possibly match anything */
+ return MS_KEY_IMPOSSIBLE; /* can't possibly match anything */
if (!db_has_variable(key)) { /* Bound key */
- if (( this = find_node2(tb, key) ) == NULL) {
- return -1;
- }
- *ret = this;
- return 1;
- } else if (partly_bound != NULL && key != am_Underscore &&
- db_is_variable(key) < 0 && !db_has_map(key))
- *partly_bound = key;
+ *keyp = key;
+ return MS_KEY_BOUND;
+ } else if (key != am_Underscore &&
+ db_is_variable(key) < 0 && !db_has_map(key)) {
+
+ *keyp = key;
+ return MS_KEY_PARTIALLY_BOUND;
+ }
- return 0;
+ return MS_KEY_UNBOUND;
}
@@ -3072,7 +3503,8 @@ static Sint do_cmp_partly_bound(Eterm a, Eterm b, int *done)
}
}
-static Sint cmp_partly_bound(Eterm partly_bound_key, Eterm bound_key) {
+Sint cmp_partly_bound(Eterm partly_bound_key, Eterm bound_key)
+{
int done = 0;
Sint ret = do_cmp_partly_bound(partly_bound_key, bound_key, &done);
#ifdef HARDDEBUG
@@ -3118,7 +3550,7 @@ static int partly_bound_can_match_lesser(Eterm partly_bound_1,
if (ret)
erts_fprintf(stderr," can match lesser than ");
else
- erts_fprintf(stderr," can not match lesser than ");
+ erts_fprintf(stderr," cannot match lesser than ");
erts_fprintf(stderr,"%T\n",partly_bound_2);
#endif
return ret;
@@ -3136,7 +3568,7 @@ static int partly_bound_can_match_greater(Eterm partly_bound_1,
if (ret)
erts_fprintf(stderr," can match greater than ");
else
- erts_fprintf(stderr," can not match greater than ");
+ erts_fprintf(stderr," cannot match greater than ");
erts_fprintf(stderr,"%T\n",partly_bound_2);
#endif
return ret;
@@ -3288,7 +3720,8 @@ static int do_partly_bound_can_match_greater(Eterm a, Eterm b,
* Callback functions for the different match functions
*/
-static int doit_select(DbTableTree *tb, TreeDbTerm *this, void *ptr,
+static int doit_select(DbTableCommon *tb, TreeDbTerm *this,
+ struct select_common* ptr,
int forward)
{
struct select_context *sc = (struct select_context *) ptr;
@@ -3306,7 +3739,7 @@ static int doit_select(DbTableTree *tb, TreeDbTerm *this, void *ptr,
GETKEY_WITH_POS(sc->keypos, this->dbterm.tpl)) > 0))) {
return 0;
}
- ret = db_match_dbterm(&tb->common, sc->p, sc->mp, &this->dbterm, &hp, 2);
+ ret = db_match_dbterm(tb, sc->p,sc->mp, &this->dbterm, &hp, 2);
if (is_value(ret)) {
sc->accum = CONS(hp, ret, sc->accum);
}
@@ -3323,7 +3756,8 @@ static int doit_select(DbTableTree *tb, TreeDbTerm *this, void *ptr,
return 1;
}
-static int doit_select_count(DbTableTree *tb, TreeDbTerm *this, void *ptr,
+static int doit_select_count(DbTableCommon *tb, TreeDbTerm *this,
+ struct select_common* ptr,
int forward)
{
struct select_count_context *sc = (struct select_count_context *) ptr;
@@ -3337,7 +3771,7 @@ static int doit_select_count(DbTableTree *tb, TreeDbTerm *this, void *ptr,
GETKEY_WITH_POS(sc->keypos, this->dbterm.tpl)) > 0)) {
return 0;
}
- ret = db_match_dbterm(&tb->common, sc->p, sc->mp, &this->dbterm, NULL, 0);
+ ret = db_match_dbterm(tb, sc->p, sc->mp, &this->dbterm, NULL, 0);
if (ret == am_true) {
++(sc->got);
}
@@ -3347,7 +3781,8 @@ static int doit_select_count(DbTableTree *tb, TreeDbTerm *this, void *ptr,
return 1;
}
-static int doit_select_chunk(DbTableTree *tb, TreeDbTerm *this, void *ptr,
+static int doit_select_chunk(DbTableCommon *tb, TreeDbTerm *this,
+ struct select_common* ptr,
int forward)
{
struct select_context *sc = (struct select_context *) ptr;
@@ -3366,7 +3801,7 @@ static int doit_select_chunk(DbTableTree *tb, TreeDbTerm *this, void *ptr,
return 0;
}
- ret = db_match_dbterm(&tb->common, sc->p, sc->mp, &this->dbterm, &hp, 2);
+ ret = db_match_dbterm(tb, sc->p, sc->mp, &this->dbterm, &hp, 2);
if (is_value(ret)) {
++(sc->got);
sc->accum = CONS(hp, ret, sc->accum);
@@ -3385,7 +3820,8 @@ static int doit_select_chunk(DbTableTree *tb, TreeDbTerm *this, void *ptr,
}
-static int doit_select_delete(DbTableTree *tb, TreeDbTerm *this, void *ptr,
+static int doit_select_delete(DbTableCommon *tb, TreeDbTerm *this,
+ struct select_common *ptr,
int forward)
{
struct select_delete_context *sc = (struct select_delete_context *) ptr;
@@ -3393,7 +3829,7 @@ static int doit_select_delete(DbTableTree *tb, TreeDbTerm *this, void *ptr,
Eterm key;
if (sc->erase_lastterm)
- free_term(tb, sc->lastterm);
+ free_term((DbTable*)tb, sc->lastterm);
sc->erase_lastterm = 0;
sc->lastterm = this;
@@ -3401,10 +3837,10 @@ static int doit_select_delete(DbTableTree *tb, TreeDbTerm *this, void *ptr,
cmp_partly_bound(sc->end_condition,
GETKEY_WITH_POS(sc->keypos, this->dbterm.tpl)) > 0)
return 0;
- ret = db_match_dbterm(&tb->common, sc->p, sc->mp, &this->dbterm, NULL, 0);
+ ret = db_match_dbterm(tb, sc->p, sc->mp, &this->dbterm, NULL, 0);
if (ret == am_true) {
key = GETKEY(sc->tb, this->dbterm.tpl);
- linkout_tree(sc->tb, key);
+ linkout_tree(sc->tb, sc->common.root, key, sc->stack);
sc->erase_lastterm = 1;
++sc->accum;
}
@@ -3414,7 +3850,8 @@ static int doit_select_delete(DbTableTree *tb, TreeDbTerm *this, void *ptr,
return 1;
}
-static int doit_select_replace(DbTableTree *tb, TreeDbTerm **this, void *ptr,
+static int doit_select_replace(DbTableCommon *tb, TreeDbTerm **this,
+ struct select_common* ptr,
int forward)
{
struct select_replace_context *sc = (struct select_replace_context *) ptr;
@@ -3428,13 +3865,13 @@ static int doit_select_replace(DbTableTree *tb, TreeDbTerm **this, void *ptr,
GETKEY_WITH_POS(sc->keypos, (*this)->dbterm.tpl)) > 0)) {
return 0;
}
- ret = db_match_dbterm(&tb->common, sc->p, sc->mp, &(*this)->dbterm, NULL, 0);
+ ret = db_match_dbterm(tb, sc->p, sc->mp, &(*this)->dbterm, NULL, 0);
if (is_value(ret)) {
TreeDbTerm* new;
TreeDbTerm* old = *this;
#ifdef DEBUG
- Eterm key = db_getkey(tb->common.keypos, ret);
+ Eterm key = db_getkey(tb->keypos, ret);
ASSERT(is_value(key));
ASSERT(cmp_key(tb, key, old) == 0);
#endif
@@ -3444,7 +3881,7 @@ static int doit_select_replace(DbTableTree *tb, TreeDbTerm **this, void *ptr,
new->balance = old->balance;
sc->lastobj = new->dbterm.tpl;
*this = new;
- free_term(tb, old);
+ free_term((DbTable*)tb, old);
++(sc->replaced);
}
if (--(sc->max) <= 0) {
@@ -3454,7 +3891,7 @@ static int doit_select_replace(DbTableTree *tb, TreeDbTerm **this, void *ptr,
}
#ifdef TREE_DEBUG
-static void do_dump_tree2(DbTableTree* tb, int to, void *to_arg, int show,
+static void do_dump_tree2(DbTableCommon* tb, int to, void *to_arg, int show,
TreeDbTerm *t, int offset)
{
if (t == NULL)
@@ -3463,7 +3900,7 @@ static void do_dump_tree2(DbTableTree* tb, int to, void *to_arg, int show,
if (show) {
const char* prefix;
Eterm term;
- if (tb->common.compress) {
+ if (tb->compress) {
prefix = "key=";
term = GETKEY(tb, t->dbterm.tpl);
}
@@ -3518,7 +3955,7 @@ static void check_slot_pos(DbTableTree *tb)
"element position %d is really 0x%08X, when stack says "
"it's 0x%08X\n", tb->stack.slot, t,
tb->stack.array[tb->stack.pos - 1]);
- do_dump_tree2(tb, ERTS_PRINT_STDERR, NULL, 1, tb->root, 0);
+ do_dump_tree2(&tb->common, ERTS_PRINT_STDERR, NULL, 1, tb->root, 0);
}
}
@@ -3533,14 +3970,14 @@ static void check_saved_stack(DbTableTree *tb)
if (t != stack->array[0]) {
erts_fprintf(stderr,"tb->stack[0] is 0x%08X, should be 0x%08X\n",
stack->array[0], t);
- do_dump_tree2(tb, ERTS_PRINT_STDERR, NULL, 1, tb->root, 0);
+ do_dump_tree2(&tb->common, ERTS_PRINT_STDERR, NULL, 1, tb->root, 0);
return;
}
while (n < stack->pos) {
if (t == NULL) {
erts_fprintf(stderr, "NULL pointer in tree when stack not empty,"
" stack depth is %d\n", n);
- do_dump_tree2(tb, ERTS_PRINT_STDERR, NULL, 1, tb->root, 0);
+ do_dump_tree2(&tb->common, ERTS_PRINT_STDERR, NULL, 1, tb->root, 0);
return;
}
n++;
@@ -3554,7 +3991,7 @@ static void check_saved_stack(DbTableTree *tb)
"represent child pointer in tree!"
"(left == 0x%08X, right == 0x%08X\n",
n, tb->stack[n], t->left, t->right);
- do_dump_tree2(tb, ERTS_PRINT_STDERR, NULL, 1, tb->root, 0);
+ do_dump_tree2(&tb->common, ERTS_PRINT_STDERR, NULL, 1, tb->root, 0);
return;
}
}
@@ -3573,7 +4010,7 @@ static int check_table_tree(DbTableTree* tb, TreeDbTerm *t)
erts_fprintf(stderr,"balance = %d, left = 0x%08X, right = 0x%08X\n",
t->balance, t->left, t->right);
erts_fprintf(stderr,"\nDump:\n---------------------------------\n");
- do_dump_tree2(tb, ERTS_PRINT_STDERR, NULL, 1, t, 0);
+ do_dump_tree2(&tb->common, ERTS_PRINT_STDERR, NULL, 1, t, 0);
erts_fprintf(stderr,"\n---------------------------------\n");
}
return ((rh > lh) ? rh : lh) + 1;
diff --git a/erts/emulator/beam/erl_db_tree_util.h b/erts/emulator/beam/erl_db_tree_util.h
new file mode 100644
index 0000000000..02df74678d
--- /dev/null
+++ b/erts/emulator/beam/erl_db_tree_util.h
@@ -0,0 +1,158 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 1998-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%
+ */
+
+#ifndef _DB_TREE_UTIL_H
+#define _DB_TREE_UTIL_H
+
+/*
+** Internal functions and macros used by both the CA tree and the AVL tree
+*/
+
+/*
+** A stack of this size is enough for an AVL tree with more than
+** 0xFFFFFFFF elements. May be subject to change if
+** the datatype of the element counter is changed to a 64 bit integer.
+** The Maximal height of an AVL tree is calculated as:
+** h(n) <= 1.4404 * log(n + 2) - 0.328
+** Where n denotes the number of nodes, h(n) the height of the tree
+** with n nodes and log is the binary logarithm.
+*/
+
+#define STACK_NEED 50
+
+#define PUSH_NODE(Dtt, Tdt) \
+ ((Dtt)->array[(Dtt)->pos++] = Tdt)
+
+#define POP_NODE(Dtt) \
+ (((Dtt)->pos) ? \
+ (Dtt)->array[--((Dtt)->pos)] : NULL)
+
+#define TOP_NODE(Dtt) \
+ ((Dtt->pos) ? \
+ (Dtt)->array[(Dtt)->pos - 1] : NULL)
+
+#define EMPTY_NODE(Dtt) (TOP_NODE(Dtt) == NULL)
+
+static ERTS_INLINE void free_term(DbTable *tb, TreeDbTerm* p)
+{
+ db_free_term(tb, p, offsetof(TreeDbTerm, dbterm));
+}
+
+/*
+** Some macros for "direction stacks"
+*/
+#define DIR_LEFT 0
+#define DIR_RIGHT 1
+#define DIR_END 2
+
+static ERTS_INLINE Sint cmp_key(DbTableCommon* tb, Eterm key, TreeDbTerm* obj) {
+ return CMP(key, GETKEY(tb,obj->dbterm.tpl));
+}
+
+int tree_balance_left(TreeDbTerm **this);
+int tree_balance_right(TreeDbTerm **this);
+
+int db_first_tree_common(Process *p, DbTable *tbl, TreeDbTerm *root,
+ Eterm *ret, DbTableTree *stack_container);
+int db_next_tree_common(Process *p, DbTable *tbl,
+ TreeDbTerm *root, Eterm key,
+ Eterm *ret, DbTreeStack* stack);
+int db_last_tree_common(Process *p, DbTable *tbl, TreeDbTerm *root,
+ Eterm *ret, DbTableTree *stack_container);
+int db_prev_tree_common(Process *p, DbTable *tbl, TreeDbTerm *root, Eterm key,
+ Eterm *ret, DbTreeStack* stack);
+int db_put_tree_common(DbTableCommon *tb, TreeDbTerm **root, Eterm obj,
+ int key_clash_fail, DbTableTree *stack_container);
+int db_get_tree_common(Process *p, DbTableCommon *tb, TreeDbTerm *root, Eterm key,
+ Eterm *ret, DbTableTree *stack_container);
+int db_get_element_tree_common(Process *p, DbTableCommon *tb, TreeDbTerm *root, Eterm key,
+ int ndex, Eterm *ret, DbTableTree *stack_container);
+int db_member_tree_common(DbTableCommon *tb, TreeDbTerm *root, Eterm key, Eterm *ret,
+ DbTableTree *stack_container);
+int db_erase_tree_common(DbTable *tbl, TreeDbTerm **root, Eterm key, Eterm *ret,
+ DbTreeStack *stack /* NULL if no static stack */);
+int db_erase_object_tree_common(DbTable *tbl, TreeDbTerm **root, Eterm object,
+ Eterm *ret, DbTableTree *stack_container);
+int db_slot_tree_common(Process *p, DbTable *tbl, TreeDbTerm *root,
+ Eterm slot_term, Eterm *ret,
+ DbTableTree *stack_container,
+ CATreeRootIterator*);
+int db_select_chunk_tree_common(Process *p, DbTable *tb,
+ Eterm tid, Eterm pattern, Sint chunk_size,
+ int reverse, Eterm *ret,
+ DbTableTree *stack_container,
+ CATreeRootIterator*);
+int db_select_tree_common(Process *p, DbTable *tb,
+ Eterm tid, Eterm pattern, int reverse, Eterm *ret,
+ DbTableTree *stack_container,
+ CATreeRootIterator*);
+int db_select_delete_tree_common(Process *p, DbTable *tbl,
+ Eterm tid, Eterm pattern,
+ Eterm *ret,
+ DbTreeStack* stack,
+ CATreeRootIterator* iter);
+int db_select_continue_tree_common(Process *p,
+ DbTableCommon *tb,
+ Eterm continuation,
+ Eterm *ret,
+ DbTableTree *stack_container,
+ CATreeRootIterator* iter);
+int db_select_delete_continue_tree_common(Process *p,
+ DbTable *tbl,
+ Eterm continuation,
+ Eterm *ret,
+ DbTreeStack* stack,
+ CATreeRootIterator* iter);
+int db_select_count_tree_common(Process *p, DbTable *tb,
+ Eterm tid, Eterm pattern, Eterm *ret,
+ DbTableTree *stack_container,
+ CATreeRootIterator* iter);
+int db_select_count_continue_tree_common(Process *p,
+ DbTable *tb,
+ Eterm continuation,
+ Eterm *ret,
+ DbTableTree *stack_container,
+ CATreeRootIterator* iter);
+int db_select_replace_tree_common(Process *p, DbTable*,
+ Eterm tid, Eterm pattern, Eterm *ret,
+ DbTableTree *stack_container,
+ CATreeRootIterator* iter);
+int db_select_replace_continue_tree_common(Process *p,
+ DbTable*,
+ Eterm continuation,
+ Eterm *ret,
+ DbTableTree *stack_container,
+ CATreeRootIterator* iter);
+int db_take_tree_common(Process *p, DbTable *tbl, TreeDbTerm **root,
+ Eterm key, Eterm *ret,
+ DbTreeStack *stack /* NULL if no static stack */);
+void db_print_tree_common(fmtfn_t to, void *to_arg,
+ int show, TreeDbTerm *root, DbTable *tbl);
+void db_foreach_offheap_tree_common(TreeDbTerm *root,
+ void (*func)(ErlOffHeap *, void *),
+ void * arg);
+int db_lookup_dbterm_tree_common(Process *p, DbTable *tbl, TreeDbTerm **root,
+ Eterm key, Eterm obj, DbUpdateHandle* handle,
+ DbTableTree *stack_container);
+void db_finalize_dbterm_tree_common(int cret, DbUpdateHandle *handle,
+ DbTableTree *stack_container);
+Sint cmp_partly_bound(Eterm partly_bound_key, Eterm bound_key);
+
+#endif /* _DB_TREE_UTIL_H */
diff --git a/erts/emulator/beam/erl_db_util.c b/erts/emulator/beam/erl_db_util.c
index f1d47326b4..1ea7074d21 100644
--- a/erts/emulator/beam/erl_db_util.c
+++ b/erts/emulator/beam/erl_db_util.c
@@ -497,6 +497,7 @@ static erts_atomic32_t trace_control_word;
/* This needs to be here, before the bif table... */
static Eterm db_set_trace_control_word_fake_1(BIF_ALIST_1);
+static Eterm db_length_1(BIF_ALIST_1);
/*
** The table of callable bif's, i e guard bif's and
@@ -603,7 +604,7 @@ static DMCGuardBif guard_tab[] =
},
{
am_length,
- &length_1,
+ &db_length_1,
1,
DBIF_ALL
},
@@ -971,6 +972,26 @@ BIF_RETTYPE db_set_trace_control_word_1(BIF_ALIST_1)
BIF_RET(db_set_trace_control_word(BIF_P, BIF_ARG_1));
}
+/*
+ * Implementation of length/1 for match specs (non-trapping).
+ */
+static Eterm db_length_1(BIF_ALIST_1)
+{
+ Eterm list;
+ Uint i;
+
+ list = BIF_ARG_1;
+ i = 0;
+ while (is_list(list)) {
+ i++;
+ list = CDR(list_val(list));
+ }
+ if (is_not_nil(list)) {
+ BIF_ERROR(BIF_P, BADARG);
+ }
+ BIF_RET(make_small(i));
+}
+
static Eterm db_set_trace_control_word_fake_1(BIF_ALIST_1)
{
Process *p = BIF_P;
@@ -2470,7 +2491,7 @@ restart:
case matchProcessDump: {
erts_dsprintf_buf_t *dsbufp = erts_create_tmp_dsbuf(0);
ASSERT(c_p == self);
- print_process_info(ERTS_PRINT_DSBUF, (void *) dsbufp, c_p);
+ print_process_info(ERTS_PRINT_DSBUF, (void *) dsbufp, c_p, ERTS_PROC_LOCK_MAIN);
*esp++ = new_binary(build_proc, (byte *)dsbufp->str,
dsbufp->str_len);
erts_destroy_tmp_dsbuf(dsbufp);
@@ -3118,9 +3139,7 @@ void* db_store_term_comp(DbTableCommon *tb, DbTerm* old, Uint offset, Eterm obj)
Uint new_sz = offset + db_size_dbterm_comp(tb, obj);
byte* basep;
DbTerm* newp;
-#ifdef DEBUG
byte* top;
-#endif
ASSERT(tb->compress);
if (old != 0) {
@@ -3142,11 +3161,8 @@ void* db_store_term_comp(DbTableCommon *tb, DbTerm* old, Uint offset, Eterm obj)
}
newp->size = size_object(obj);
-#ifdef DEBUG
- top =
-#endif
- copy_to_comp(tb, obj, newp, new_sz);
- ASSERT(top <= basep + new_sz);
+ top = copy_to_comp(tb, obj, newp, new_sz);
+ ASSERT(top <= basep + new_sz); (void)top;
/* ToDo: Maybe realloc if ((basep+new_sz) - top) > WASTED_SPACE_LIMIT */
@@ -3296,7 +3312,7 @@ void db_cleanup_offheap_comp(DbTerm* obj)
default:
ASSERT(is_external_header(u.hdr->thing_word));
ASSERT(u.pb != &tmp);
- erts_deref_node_entry(u.ext->node);
+ erts_deref_node_entry(u.ext->node, make_boxed(u.ep));
break;
}
}
diff --git a/erts/emulator/beam/erl_db_util.h b/erts/emulator/beam/erl_db_util.h
index 6ec3b4f98f..e1af9210ea 100644
--- a/erts/emulator/beam/erl_db_util.h
+++ b/erts/emulator/beam/erl_db_util.h
@@ -89,7 +89,16 @@ typedef struct {
void** bp; /* {Hash|Tree}DbTerm** */
Uint new_size;
int flags;
- void* lck;
+ union {
+ struct {
+ erts_rwmtx_t* lck;
+ } hash;
+ struct {
+ struct DbTableCATreeNode* base_node;
+ struct DbTableCATreeNode* parent;
+ int current_level;
+ } catree;
+ } u;
} DbUpdateHandle;
@@ -274,23 +283,28 @@ typedef struct db_table_common {
} DbTableCommon;
/* These are status bit patterns */
-#define DB_PRIVATE (1 << 0)
-#define DB_PROTECTED (1 << 1)
-#define DB_PUBLIC (1 << 2)
-#define DB_DELETE (1 << 3) /* table is being deleted */
-#define DB_SET (1 << 4)
-#define DB_BAG (1 << 5)
-#define DB_DUPLICATE_BAG (1 << 6)
-#define DB_ORDERED_SET (1 << 7)
-#define DB_FINE_LOCKED (1 << 8) /* write_concurrency */
-#define DB_FREQ_READ (1 << 9) /* read_concurrency */
-#define DB_NAMED_TABLE (1 << 10)
-#define DB_BUSY (1 << 11)
+#define DB_PRIVATE (1 << 0)
+#define DB_PROTECTED (1 << 1)
+#define DB_PUBLIC (1 << 2)
+#define DB_DELETE (1 << 3) /* table is being deleted */
+#define DB_SET (1 << 4)
+#define DB_BAG (1 << 5)
+#define DB_DUPLICATE_BAG (1 << 6)
+#define DB_ORDERED_SET (1 << 7)
+#define DB_CA_ORDERED_SET (1 << 8)
+#define DB_FINE_LOCKED (1 << 9) /* write_concurrency */
+#define DB_FREQ_READ (1 << 10) /* read_concurrency */
+#define DB_NAMED_TABLE (1 << 11)
+#define DB_BUSY (1 << 12)
+
+#define DB_CATREE_FORCE_SPLIT (1 << 31) /* erts_debug */
#define IS_HASH_TABLE(Status) (!!((Status) & \
(DB_BAG | DB_SET | DB_DUPLICATE_BAG)))
#define IS_TREE_TABLE(Status) (!!((Status) & \
DB_ORDERED_SET))
+#define IS_CATREE_TABLE(Status) (!!((Status) & \
+ DB_CA_ORDERED_SET))
#define NFIXED(T) (erts_refc_read(&(T)->common.fix_count,0))
#define IS_FIXED(T) (NFIXED(T) != 0)
diff --git a/erts/emulator/beam/erl_dirty_bif.tab b/erts/emulator/beam/erl_dirty_bif.tab
index 20299ff604..656acfebdb 100644
--- a/erts/emulator/beam/erl_dirty_bif.tab
+++ b/erts/emulator/beam/erl_dirty_bif.tab
@@ -57,9 +57,6 @@ dirty-cpu erts_debug:lcnt_clear/0
# and debug purposes only. We really do *not* want to execute these
# on dirty schedulers on a real system.
-dirty-cpu-test erlang:'++'/2
-dirty-cpu-test erlang:append/2
-dirty-cpu-test erlang:iolist_size/1
dirty-cpu-test erlang:make_tuple/2
dirty-cpu-test erlang:make_tuple/3
dirty-cpu-test erlang:append_element/2
diff --git a/erts/emulator/beam/erl_drv_nif.h b/erts/emulator/beam/erl_drv_nif.h
index 31b4817fb1..a5ecbfff06 100644
--- a/erts/emulator/beam/erl_drv_nif.h
+++ b/erts/emulator/beam/erl_drv_nif.h
@@ -53,7 +53,9 @@ typedef enum {
enum ErlNifSelectFlags {
ERL_NIF_SELECT_READ = (1 << 0),
ERL_NIF_SELECT_WRITE = (1 << 1),
- ERL_NIF_SELECT_STOP = (1 << 2)
+ ERL_NIF_SELECT_STOP = (1 << 2),
+ ERL_NIF_SELECT_CANCEL = (1 << 3),
+ ERL_NIF_SELECT_CUSTOM_MSG= (1 << 4)
};
/*
diff --git a/erts/emulator/beam/erl_gc.c b/erts/emulator/beam/erl_gc.c
index d5dfb096b1..9317850d96 100644
--- a/erts/emulator/beam/erl_gc.c
+++ b/erts/emulator/beam/erl_gc.c
@@ -1303,7 +1303,8 @@ erts_garbage_collect_literals(Process* p, Eterm* literals,
ExternalThing *etp;
ASSERT(is_external_header(ptr->thing_word));
etp = (ExternalThing *) ptr;
- erts_refc_inc(&etp->node->refc, 1);
+ erts_ref_node_entry(etp->node, 1,
+ make_boxed(&oh->thing_word));
break;
}
}
@@ -2438,27 +2439,9 @@ erts_copy_one_frag(Eterm** hpp, ErlOffHeap* off_heap,
cpy_words:
ASSERT(sz >= cpy_sz);
sz -= cpy_sz;
- while (cpy_sz >= 8) {
- cpy_sz -= 8;
- *hp++ = *fhp++;
- *hp++ = *fhp++;
- *hp++ = *fhp++;
- *hp++ = *fhp++;
- *hp++ = *fhp++;
- *hp++ = *fhp++;
- *hp++ = *fhp++;
- *hp++ = *fhp++;
- }
- switch (cpy_sz) {
- case 7: *hp++ = *fhp++;
- case 6: *hp++ = *fhp++;
- case 5: *hp++ = *fhp++;
- case 4: *hp++ = *fhp++;
- case 3: *hp++ = *fhp++;
- case 2: *hp++ = *fhp++;
- case 1: *hp++ = *fhp++;
- default: break;
- }
+ sys_memcpy(hp, fhp, cpy_sz * sizeof(Eterm));
+ hp += cpy_sz;
+ fhp += cpy_sz;
if (oh) {
/* Add to offheap list */
oh->next = off_heap->first;
@@ -2477,7 +2460,7 @@ erts_copy_one_frag(Eterm** hpp, ErlOffHeap* off_heap,
*hpp = hp;
for (i = 0; i < nrefs; i++) {
- if (is_not_immed(refs[i]) && !erts_is_literal(refs[i],boxed_val(refs[i])))
+ if (is_not_immed(refs[i]) && !erts_is_literal(refs[i],ptr_val(refs[i])))
refs[i] = offset_ptr(refs[i], offs);
}
bp->off_heap.first = NULL;
@@ -2854,7 +2837,11 @@ sweep_off_heap(Process *p, int fullsweep)
while (ptr) {
if (IS_MOVED_BOXED(ptr->thing_word)) {
ASSERT(!ErtsInArea(ptr, oheap, oheap_sz));
- *prev = ptr = (struct erl_off_heap_header*) boxed_val(ptr->thing_word);
+ if (is_external_header(((struct erl_off_heap_header*) boxed_val(ptr->thing_word))->thing_word))
+ erts_node_bookkeep(((ExternalThing*)ptr)->node,
+ make_boxed(&ptr->thing_word),
+ ERL_NODE_DEC);
+ *prev = ptr = (struct erl_off_heap_header*) boxed_val(ptr->thing_word);
ASSERT(!IS_MOVED_BOXED(ptr->thing_word));
switch (ptr->thing_word) {
case HEADER_PROC_BIN: {
@@ -2881,6 +2868,11 @@ sweep_off_heap(Process *p, int fullsweep)
/* fall through... */
}
default:
+ if (is_external_header(ptr->thing_word)) {
+ erts_node_bookkeep(((ExternalThing*)ptr)->node,
+ make_boxed(&ptr->thing_word),
+ ERL_NODE_INC);
+ }
prev = &ptr->next;
ptr = ptr->next;
}
@@ -2914,7 +2906,8 @@ sweep_off_heap(Process *p, int fullsweep)
}
default:
ASSERT(is_external_header(ptr->thing_word));
- erts_deref_node_entry(((ExternalThing*)ptr)->node);
+ erts_deref_node_entry(((ExternalThing*)ptr)->node,
+ make_boxed(&ptr->thing_word));
}
*prev = ptr = ptr->next;
}
@@ -3047,6 +3040,13 @@ offset_heap(Eterm* hp, Uint sz, Sint offs, char* area, Uint area_size)
{
struct erl_off_heap_header* oh = (struct erl_off_heap_header*) hp;
+ if (is_external_header(oh->thing_word)) {
+ erts_node_bookkeep(((ExternalThing*)oh)->node,
+ make_boxed(((Eterm*)oh)-offs), ERL_NODE_DEC);
+ erts_node_bookkeep(((ExternalThing*)oh)->node,
+ make_boxed((Eterm*)oh), ERL_NODE_INC);
+ }
+
if (ErtsInArea(oh->next, area, area_size)) {
Eterm** uptr = (Eterm **) (void *) &oh->next;
*uptr += offs; /* Patch the mso chain */
diff --git a/erts/emulator/beam/erl_goodfit_alloc.c b/erts/emulator/beam/erl_goodfit_alloc.c
index 01d4aa54ff..68b9579433 100644
--- a/erts/emulator/beam/erl_goodfit_alloc.c
+++ b/erts/emulator/beam/erl_goodfit_alloc.c
@@ -226,6 +226,8 @@ erts_gfalc_start(GFAllctr_t *gfallctr,
allctr->add_mbc = NULL;
allctr->remove_mbc = NULL;
allctr->largest_fblk_in_mbc = NULL;
+ allctr->first_fblk_in_mbc = NULL;
+ allctr->next_fblk_in_mbc = NULL;
allctr->init_atoms = init_atoms;
#ifdef ERTS_ALLOC_UTIL_HARD_DEBUG
diff --git a/erts/emulator/beam/erl_hl_timer.c b/erts/emulator/beam/erl_hl_timer.c
index ef7a55fa38..b0eb0e85c0 100644
--- a/erts/emulator/beam/erl_hl_timer.c
+++ b/erts/emulator/beam/erl_hl_timer.c
@@ -29,8 +29,6 @@
# include "config.h"
#endif
-/* #define ERTS_MAGIC_REF_BIF_TIMERS */
-
#include "sys.h"
#include "global.h"
#include "bif.h"
@@ -39,9 +37,6 @@
#include "erl_time.h"
#include "erl_hl_timer.h"
#include "erl_proc_sig_queue.h"
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
-#include "erl_binary.h"
-#endif
#define ERTS_TMR_CHECK_CANCEL_ON_CREATE 0
@@ -195,14 +190,9 @@ struct ErtsBifTimer_ {
} type;
struct {
erts_atomic32_t state;
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
- ErtsMagicBinary *mbin;
- ErtsHLTimerList proc_list;
-#else
Uint32 refn[ERTS_REF_NUMBERS];
ErtsBifTimerTree proc_tree;
ErtsBifTimerTree tree;
-#endif
Eterm message;
ErlHeapFragment *bp;
} btm;
@@ -220,11 +210,7 @@ typedef ErtsTimer *(*ErtsCreateTimerFunc)(ErtsSchedulerData *esdp,
int short_time, ErtsTmrType type,
void *rcvrp, Eterm rcvr,
Eterm msg,
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
- ErtsMagicBinary *mbin,
-#else
Uint32 *refn,
-#endif
void (*callback)(void *), void *arg);
#ifdef SMALL_MEMORY
@@ -303,16 +289,12 @@ typedef struct {
struct ErtsHLTimerService_ {
ErtsHLTCncldTmrQ canceled_queue;
ErtsHLTimer *time_tree;
-#ifndef ERTS_MAGIC_REF_BIF_TIMERS
ErtsBifTimer *btm_tree;
-#endif
ErtsHLTimer *next_timeout;
ErtsYieldingTimeoutState yield;
ErtsTWheelTimer service_timer;
};
-#ifndef ERTS_MAGIC_REF_BIF_TIMERS
-
static ERTS_INLINE int
refn_is_lt(Uint32 *x, Uint32 *y)
{
@@ -334,8 +316,6 @@ refn_is_eq(Uint32 *x, Uint32 *y)
return (x[0] == y[0]) & (x[1] == y[1]) & (x[2] == y[2]);
}
-#endif
-
#define ERTS_RBT_PREFIX time
#define ERTS_RBT_T ErtsHLTimer
#define ERTS_RBT_KEY_T ErtsMonotonicTime
@@ -525,13 +505,7 @@ same_time_list_lookup(ErtsHLTimer *root, ErtsHLTimer *x)
#endif /* ERTS_HLT_HARD_DEBUG */
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
-#define ERTS_BTM_HLT2REFN(T) ((T)->btm.mbin->refn)
-#else
#define ERTS_BTM_HLT2REFN(T) ((T)->btm.refn)
-#endif
-
-#ifndef ERTS_MAGIC_REF_BIF_TIMERS
#define ERTS_RBT_PREFIX btm
#define ERTS_RBT_T ErtsBifTimer
@@ -576,87 +550,12 @@ same_time_list_lookup(ErtsHLTimer *root, ErtsHLTimer *x)
#define ERTS_RBT_IS_EQ(KX, KY) refn_is_eq((KX), (KY))
#define ERTS_RBT_WANT_DELETE
#define ERTS_RBT_WANT_INSERT
-#ifndef ERTS_MAGIC_REF_BIF_TIMERS
#define ERTS_RBT_WANT_LOOKUP
-#endif
#define ERTS_RBT_WANT_FOREACH
#define ERTS_RBT_UNDEF
#include "erl_rbtree.h"
-#endif /* !ERTS_MAGIC_REF_BIF_TIMERS */
-
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
-
-static ERTS_INLINE void
-proc_btm_list_insert(ErtsBifTimer **list, ErtsBifTimer *x)
-{
- ErtsBifTimer *y = *list;
- if (!y) {
- x->btm.proc_list.next = x;
- x->btm.proc_list.prev = x;
- *list = x;
- }
- else {
- ERTS_HLT_ASSERT(y->btm.proc_list.prev->btm.proc_list.next == y);
- x->btm.proc_list.next = y;
- x->btm.proc_list.prev = y->btm.proc_list.prev;
- y->btm.proc_list.prev->btm.proc_list.next = x;
- y->btm.proc_list.prev = x;
- }
-}
-
-static ERTS_INLINE void
-proc_btm_list_delete(ErtsBifTimer **list, ErtsBifTimer *x)
-{
- ErtsBifTimer *y = *list;
- if (y == x && x->btm.proc_list.next == x) {
- ERTS_HLT_ASSERT(x->btm.proc_list.prev == x);
- *list = NULL;
- }
- else {
- if (y == x)
- *list = x->btm.proc_list.next;
- ERTS_HLT_ASSERT(x->btm.proc_list.prev->btm.proc_list.next == x);
- ERTS_HLT_ASSERT(x->btm.proc_list.next->btm.proc_list.prev == x);
- x->btm.proc_list.prev->btm.proc_list.next = x->btm.proc_list.next;
- x->btm.proc_list.next->btm.proc_list.prev = x->btm.proc_list.prev;
- }
- x->btm.proc_list.next = NULL;
-}
-
-static ERTS_INLINE int
-proc_btm_list_foreach_destroy_yielding(ErtsBifTimer **list,
- void (*destroy)(ErtsBifTimer *, void *),
- void *arg,
- int limit)
-{
- int i;
- ErtsBifTimer *first, *last;
-
- first = *list;
- if (!first)
- return 0;
-
- last = first->btm.proc_list.prev;
- for (i = 0; i < limit; i++) {
- ErtsBifTimer *x = last;
- last = last->btm.proc_list.prev;
- (*destroy)(x, arg);
- x->btm.proc_list.next = NULL;
- if (x == first) {
- *list = NULL;
- return 0;
- }
- }
-
- last->btm.proc_list.next = first;
- first->btm.proc_list.prev = last;
- return 1;
-}
-
-#else /* !ERTS_MAGIC_REF_BIF_TIMERS */
-
#define ERTS_RBT_PREFIX proc_btm
#define ERTS_RBT_T ErtsBifTimer
#define ERTS_RBT_KEY_T Uint32 *
@@ -700,16 +599,12 @@ proc_btm_list_foreach_destroy_yielding(ErtsBifTimer **list,
#define ERTS_RBT_IS_EQ(KX, KY) refn_is_eq((KX), (KY))
#define ERTS_RBT_WANT_DELETE
#define ERTS_RBT_WANT_INSERT
-#ifndef ERTS_MAGIC_REF_BIF_TIMERS
#define ERTS_RBT_WANT_LOOKUP
-#endif
#define ERTS_RBT_WANT_FOREACH_DESTROY_YIELDING
#define ERTS_RBT_UNDEF
#include "erl_rbtree.h"
-#endif /* !ERTS_MAGIC_REF_BIF_TIMERS */
-
static void init_canceled_queue(ErtsHLTCncldTmrQ *cq);
void
@@ -728,9 +623,7 @@ erts_create_timer_service(void)
srv = erts_alloc_permanent_cache_aligned(ERTS_ALC_T_TIMER_SERVICE,
sizeof(ErtsHLTimerService));
srv->time_tree = NULL;
-#ifndef ERTS_MAGIC_REF_BIF_TIMERS
srv->btm_tree = NULL;
-#endif
srv->next_timeout = NULL;
srv->yield = init_yield;
erts_twheel_init_timer(&srv->service_timer);
@@ -805,40 +698,10 @@ port_timeout_common(Port *port, void *tmr)
return 0;
}
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
-
-static erts_atomic_t *
-mbin_to_btmref__(ErtsMagicBinary *mbin)
-{
- return erts_binary_to_magic_indirection((Binary *) mbin);
-}
-
-static ERTS_INLINE void
-magic_binary_init(ErtsMagicBinary *mbin, ErtsBifTimer *tmr)
-{
- erts_atomic_t *aptr = mbin_to_btmref__(mbin);
- erts_atomic_init_nob(aptr, (erts_aint_t) tmr);
-}
-
-static ERTS_INLINE ErtsBifTimer *
-magic_binary_to_btm(ErtsMagicBinary *mbin)
-{
- erts_atomic_t *aptr = mbin_to_btmref__(mbin);
- ErtsBifTimer *tmr = (ErtsBifTimer *) erts_atomic_read_nob(aptr);
- ERTS_HLT_ASSERT(!tmr || tmr->btm.mbin == mbin);
- return tmr;
-}
-
-#endif /* ERTS_MAGIC_REF_BIF_TIMERS */
-
static ERTS_INLINE erts_aint_t
init_btm_specifics(ErtsSchedulerData *esdp,
ErtsBifTimer *tmr, Eterm msg,
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
- ErtsMagicBinary *mbin
-#else
Uint32 *refn
-#endif
)
{
Uint hsz = is_immed(msg) ? ((Uint) 0) : size_object(msg);
@@ -853,13 +716,6 @@ init_btm_specifics(ErtsSchedulerData *esdp,
tmr->btm.message = copy_struct(msg, hsz, &hp, &bp->off_heap);
tmr->btm.bp = bp;
}
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
- refc = 1;
- tmr->btm.mbin = mbin;
- erts_refc_inc(&mbin->refc, 1);
- magic_binary_init(mbin, tmr);
- tmr->btm.proc_list.next = NULL;
-#else
refc = 0;
tmr->btm.refn[0] = refn[0];
tmr->btm.refn[1] = refn[1];
@@ -868,7 +724,6 @@ init_btm_specifics(ErtsSchedulerData *esdp,
tmr->btm.proc_tree.parent = ERTS_HLT_PFIELD_NOT_IN_TABLE;
btm_rbt_insert(&esdp->timer_service->btm_tree, tmr);
-#endif
erts_atomic32_init_nob(&tmr->btm.state, ERTS_TMR_STATE_ACTIVE);
return refc; /* refc from magic binary... */
@@ -886,11 +741,6 @@ timer_destroy(ErtsTimer *tmr, int twt, int btm)
erts_free(ERTS_ALC_T_HL_PTIMER, tmr);
}
else {
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
- Binary *bp = (Binary *) tmr->btm.btm.mbin;
- if (erts_refc_dectest(&bp->refc, 0) == 0)
- erts_bin_free(bp);
-#endif
if (tmr->head.roflgs & ERTS_TMR_ROFLG_PRE_ALC)
bif_timer_pre_free(&tmr->btm);
else
@@ -940,9 +790,6 @@ schedule_tw_timer_destroy(ErtsTWTimer *tmr)
else {
/* Message buffer already dropped... */
size = sizeof(ErtsBifTimer);
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
- size += sizeof(ErtsMagicIndirectionWord);
-#endif
}
erts_schedule_thr_prgr_later_cleanup_op(
@@ -1006,11 +853,7 @@ create_tw_timer(ErtsSchedulerData *esdp,
int short_time, ErtsTmrType type,
void *rcvrp, Eterm rcvr,
Eterm msg,
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
- ErtsMagicBinary *mbin,
-#else
Uint32 *refn,
-#endif
void (*callback)(void *), void *arg)
{
ErtsTWTimer *tmr;
@@ -1087,11 +930,7 @@ create_tw_timer(ErtsSchedulerData *esdp,
refc += init_btm_specifics(esdp,
(ErtsBifTimer *) tmr,
msg,
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
- mbin
-#else
refn
-#endif
);
break;
@@ -1152,9 +991,6 @@ schedule_hl_timer_destroy(ErtsHLTimer *tmr, Uint32 roflgs)
else {
/* Message buffer already dropped... */
size = sizeof(ErtsBifTimer);
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
- size += sizeof(ErtsMagicIndirectionWord);
-#endif
}
erts_schedule_thr_prgr_later_cleanup_op(
@@ -1192,34 +1028,6 @@ check_canceled_queue(ErtsSchedulerData *esdp, ErtsHLTimerService *srv)
#endif
}
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
-
-static int
-bif_timer_ref_destructor(Binary *unused)
-{
- return 1;
-}
-
-static ERTS_INLINE void
-btm_clear_magic_binary(ErtsBifTimer *tmr)
-{
- erts_atomic_t *aptr = mbin_to_btmref__(tmr->btm.mbin);
- Uint32 roflgs = tmr->type.head.roflgs;
-#ifdef ERTS_HLT_DEBUG
- erts_aint_t tval = erts_atomic_xchg_nob(aptr,
- (erts_aint_t) NULL);
- ERTS_HLT_ASSERT(tval == (erts_aint_t) tmr);
-#else
- erts_atomic_set_nob(aptr, (erts_aint_t) NULL);
-#endif
- if (roflgs & ERTS_TMR_ROFLG_HLT)
- hl_timer_dec_refc(&tmr->type.hlt, roflgs);
- else
- tw_timer_dec_refc(&tmr->type.twt);
-}
-
-#endif /* ERTS_MAGIC_REF_BIF_TIMERS */
-
static ERTS_INLINE void
bif_timer_timeout(ErtsHLTimerService *srv,
ErtsBifTimer *tmr,
@@ -1240,10 +1048,6 @@ bif_timer_timeout(ErtsHLTimerService *srv,
if (state == ERTS_TMR_STATE_ACTIVE) {
Process *proc;
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
- btm_clear_magic_binary(tmr);
-#endif
-
if (roflgs & ERTS_TMR_ROFLG_REG_NAME) {
Eterm term;
term = tmr->type.head.receiver.name;
@@ -1266,18 +1070,11 @@ bif_timer_timeout(ErtsHLTimerService *srv,
erts_proc_lock(proc, ERTS_PROC_LOCK_BTM);
/* If the process is exiting do not disturb the cleanup... */
if (!ERTS_PROC_IS_EXITING(proc)) {
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
- if (tmr->btm.proc_list.next) {
- proc_btm_list_delete(&proc->bif_timers, tmr);
- dec_refc = 1;
- }
-#else
if (tmr->btm.proc_tree.parent != ERTS_HLT_PFIELD_NOT_IN_TABLE) {
proc_btm_rbt_delete(&proc->bif_timers, tmr);
tmr->btm.proc_tree.parent = ERTS_HLT_PFIELD_NOT_IN_TABLE;
dec_refc = 1;
}
-#endif
}
erts_proc_unlock(proc, ERTS_PROC_LOCK_BTM);
if (dec_refc)
@@ -1287,25 +1084,18 @@ bif_timer_timeout(ErtsHLTimerService *srv,
free_message_buffer(tmr->btm.bp);
}
-#ifndef ERTS_MAGIC_REF_BIF_TIMERS
if (tmr->btm.tree.parent != ERTS_HLT_PFIELD_NOT_IN_TABLE) {
btm_rbt_delete(&srv->btm_tree, tmr);
tmr->btm.tree.parent = ERTS_HLT_PFIELD_NOT_IN_TABLE;
}
-#endif
-
}
static void
tw_bif_timer_timeout(void *vbtmp)
{
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
- ErtsHLTimerService *srv = NULL;
-#else
ErtsSchedulerData *esdp = erts_get_scheduler_data();
ErtsHLTimerService *srv = esdp->timer_service;
-#endif
ErtsBifTimer *btmp = (ErtsBifTimer *) vbtmp;
bif_timer_timeout(srv, btmp, btmp->type.head.roflgs);
tw_timer_dec_refc(&btmp->type.twt);
@@ -1317,11 +1107,7 @@ create_hl_timer(ErtsSchedulerData *esdp,
int short_time, ErtsTmrType type,
void *rcvrp, Eterm rcvr,
Eterm msg,
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
- ErtsMagicBinary *mbin,
-#else
Uint32 *refn,
-#endif
void (*callback)(void *), void *arg)
{
ErtsHLTimerService *srv = esdp->timer_service;
@@ -1407,11 +1193,7 @@ create_hl_timer(ErtsSchedulerData *esdp,
refc += init_btm_specifics(esdp,
(ErtsBifTimer *) tmr,
msg,
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
- mbin
-#else
refn
-#endif
);
}
@@ -1628,7 +1410,6 @@ cleanup_sched_local_canceled_timer(ErtsSchedulerData *esdp,
ERTS_HLT_ASSERT((tmr->head.roflgs & ERTS_TMR_ROFLG_SID_MASK)
== (Uint32) esdp->no);
-#ifndef ERTS_MAGIC_REF_BIF_TIMERS
if (roflgs & ERTS_TMR_ROFLG_BIF_TMR) {
ErtsBifTimer *btm = (ErtsBifTimer *) tmr;
if (btm->btm.tree.parent != ERTS_HLT_PFIELD_NOT_IN_TABLE) {
@@ -1636,7 +1417,6 @@ cleanup_sched_local_canceled_timer(ErtsSchedulerData *esdp,
btm->btm.tree.parent = ERTS_HLT_PFIELD_NOT_IN_TABLE;
}
}
-#endif
if (roflgs & ERTS_TMR_ROFLG_HLT) {
hlt_delete_timer(esdp, &tmr->hlt);
@@ -1909,9 +1689,6 @@ setup_bif_timer(Process *c_p, int twheel, ErtsMonotonicTime timeout_pos,
Eterm ref, tmo_msg, *hp;
ErtsBifTimer *tmr;
ErtsSchedulerData *esdp;
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
- Binary *mbin;
-#endif
Eterm tmp_hp[4];
ErtsCreateTimerFunc create_timer;
@@ -1920,18 +1697,10 @@ setup_bif_timer(Process *c_p, int twheel, ErtsMonotonicTime timeout_pos,
esdp = erts_proc_sched_data(c_p);
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
- mbin = erts_create_magic_indirection(bif_timer_ref_destructor);
- hp = HAlloc(c_p, ERTS_MAGIC_REF_THING_SIZE);
- ref = erts_mk_magic_ref(&hp, &c_p->off_heap, mbin);
- ASSERT(erts_get_ref_numbers_thr_id(((ErtsMagicBinary *)mbin)->refn)
- == (Uint32) esdp->no);
-#else
hp = HAlloc(c_p, ERTS_REF_THING_SIZE);
ref = erts_sched_make_ref_in_buffer(esdp, hp);
ASSERT(erts_get_ref_numbers_thr_id(internal_ordinary_ref_numbers(ref))
== (Uint32) esdp->no);
-#endif
tmo_msg = wrap ? TUPLE3(tmp_hp, am_timeout, ref, msg) : msg;
@@ -1939,11 +1708,7 @@ setup_bif_timer(Process *c_p, int twheel, ErtsMonotonicTime timeout_pos,
tmr = (ErtsBifTimer *) create_timer(esdp, timeout_pos,
short_time, ERTS_TMR_BIF,
NULL, rcvr, tmo_msg,
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
- (ErtsMagicBinary *) mbin,
-#else
internal_ordinary_ref_numbers(ref),
-#endif
NULL, NULL);
if (is_internal_pid(rcvr)) {
@@ -1951,14 +1716,10 @@ setup_bif_timer(Process *c_p, int twheel, ErtsMonotonicTime timeout_pos,
rcvr, ERTS_PROC_LOCK_BTM,
ERTS_P2P_FLG_INC_REFC);
if (!proc) {
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
- btm_clear_magic_binary(tmr);
-#else
if (tmr->btm.tree.parent != ERTS_HLT_PFIELD_NOT_IN_TABLE) {
btm_rbt_delete(&esdp->timer_service->btm_tree, tmr);
tmr->btm.tree.parent = ERTS_HLT_PFIELD_NOT_IN_TABLE;
}
-#endif
if (tmr->btm.bp)
free_message_buffer(tmr->btm.bp);
if (twheel)
@@ -1968,11 +1729,7 @@ setup_bif_timer(Process *c_p, int twheel, ErtsMonotonicTime timeout_pos,
timer_destroy((ErtsTimer *) tmr, twheel, 1);
}
else {
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
- proc_btm_list_insert(&proc->bif_timers, tmr);
-#else
proc_btm_rbt_insert(&proc->bif_timers, tmr);
-#endif
erts_proc_unlock(proc, ERTS_PROC_LOCK_BTM);
tmr->type.head.receiver.proc = proc;
}
@@ -2000,10 +1757,6 @@ cancel_bif_timer(ErtsBifTimer *tmr)
if (state != ERTS_TMR_STATE_ACTIVE)
return 0;
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
- btm_clear_magic_binary(tmr);
-#endif
-
if (tmr->btm.bp)
free_message_buffer(tmr->btm.bp);
@@ -2022,19 +1775,12 @@ cancel_bif_timer(ErtsBifTimer *tmr)
* the btm tree by itself (it may be in
* the middle of tree destruction).
*/
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
- if (!ERTS_PROC_IS_EXITING(proc) && tmr->btm.proc_list.next) {
- proc_btm_list_delete(&proc->bif_timers, tmr);
- res = 1;
- }
-#else
if (!ERTS_PROC_IS_EXITING(proc)
&& tmr->btm.proc_tree.parent != ERTS_HLT_PFIELD_NOT_IN_TABLE) {
proc_btm_rbt_delete(&proc->bif_timers, tmr);
tmr->btm.proc_tree.parent = ERTS_HLT_PFIELD_NOT_IN_TABLE;
res = 1;
}
-#endif
erts_proc_unlock(proc, ERTS_PROC_LOCK_BTM);
}
@@ -2075,12 +1821,10 @@ access_btm(ErtsBifTimer *tmr, Uint32 sid, ErtsSchedulerData *esdp, int cancel)
queue_canceled_timer(esdp, sid, (ErtsTimer *) tmr);
}
else {
-#ifndef ERTS_MAGIC_REF_BIF_TIMERS
if (tmr->btm.tree.parent != ERTS_HLT_PFIELD_NOT_IN_TABLE) {
btm_rbt_delete(&esdp->timer_service->btm_tree, tmr);
tmr->btm.tree.parent = ERTS_HLT_PFIELD_NOT_IN_TABLE;
}
-#endif
if (is_hlt) {
if (cncl_res > 0)
hl_timer_dec_refc(&tmr->type.hlt, tmr->type.hlt.head.roflgs);
@@ -2157,52 +1901,6 @@ send_async_info(Process *proc, ErtsProcLocks initial_locks,
return am_ok;
}
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
-
-static BIF_RETTYPE
-access_bif_timer(Process *c_p, Eterm tref, int cancel, int async, int info)
-{
- BIF_RETTYPE ret;
- Eterm res;
- Sint64 time_left;
-
- if (!is_internal_magic_ref(tref)) {
- if (is_not_ref(tref)) {
- ERTS_BIF_PREP_ERROR(ret, c_p, BADARG);
- return ret;
- }
- time_left = -1;
- }
- else {
- ErtsMagicBinary *mbin;
- mbin = (ErtsMagicBinary *) erts_magic_ref2bin(tref);
- if (mbin->destructor != bif_timer_ref_destructor)
- time_left = -1;
- else {
- ErtsBifTimer *tmr;
- Uint32 sid;
- tmr = magic_binary_to_btm(mbin);
- sid = erts_get_ref_numbers_thr_id(internal_magic_ref_numbers(tref));
- ASSERT(1 <= sid && sid <= erts_no_schedulers);
- time_left = access_btm(tmr, sid, erts_proc_sched_data(c_p), cancel);
- }
- }
-
- if (!info)
- res = am_ok;
- else if (!async)
- res = return_info(c_p, time_left);
- else
- res = send_async_info(c_p, ERTS_PROC_LOCK_MAIN,
- tref, cancel, time_left);
-
- ERTS_BIF_PREP_RET(ret, res);
-
- return ret;
-}
-
-#else /* !ERTS_MAGIC_REF_BIF_TIMERS */
-
static ERTS_INLINE Eterm
send_sync_info(Process *proc, ErtsProcLocks initial_locks,
Uint32 *refn, int cancel, Sint64 time_left)
@@ -2505,8 +2203,6 @@ no_timer:
return no_timer_result(c_p, tref, cancel, async, info);
}
-#endif /* !ERTS_MAGIC_REF_BIF_TIMERS */
-
static ERTS_INLINE int
bool_arg(Eterm val, int *argp)
{
@@ -2567,9 +2263,10 @@ parse_bif_timer_options(Eterm option_list, int *async,
return 1;
}
-static void
-exit_cancel_bif_timer(ErtsBifTimer *tmr, void *vesdp)
+static int
+exit_cancel_bif_timer(ErtsBifTimer *tmr, void *vesdp, Sint reds)
{
+#define ERTS_BTM_CANCEL_REDS 80
ErtsSchedulerData *esdp = (ErtsSchedulerData *) vesdp;
Uint32 sid, roflgs;
erts_aint_t state;
@@ -2584,32 +2281,23 @@ exit_cancel_bif_timer(ErtsBifTimer *tmr, void *vesdp)
is_hlt = !!(roflgs & ERTS_TMR_ROFLG_HLT);
ERTS_HLT_ASSERT(sid == erts_get_ref_numbers_thr_id(ERTS_BTM_HLT2REFN(tmr)));
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
- ERTS_HLT_ASSERT(tmr->btm.proc_list.next);
-#else
ERTS_HLT_ASSERT(tmr->btm.proc_tree.parent
!= ERTS_HLT_PFIELD_NOT_IN_TABLE);
tmr->btm.proc_tree.parent = ERTS_HLT_PFIELD_NOT_IN_TABLE;
-#endif
if (state == ERTS_TMR_STATE_ACTIVE) {
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
- btm_clear_magic_binary(tmr);
-#endif
if (tmr->btm.bp)
free_message_buffer(tmr->btm.bp);
if (sid != (Uint32) esdp->no) {
queue_canceled_timer(esdp, sid, (ErtsTimer *) tmr);
- return;
+ return ERTS_BTM_CANCEL_REDS;
}
-#ifndef ERTS_MAGIC_REF_BIF_TIMERS
if (tmr->btm.tree.parent != ERTS_HLT_PFIELD_NOT_IN_TABLE) {
btm_rbt_delete(&esdp->timer_service->btm_tree, tmr);
tmr->btm.tree.parent = ERTS_HLT_PFIELD_NOT_IN_TABLE;
}
-#endif
if (is_hlt)
hlt_delete_timer(esdp, &tmr->type.hlt);
else
@@ -2619,36 +2307,19 @@ exit_cancel_bif_timer(ErtsBifTimer *tmr, void *vesdp)
hl_timer_dec_refc(&tmr->type.hlt, roflgs);
else
tw_timer_dec_refc(&tmr->type.twt);
+ return ERTS_BTM_CANCEL_REDS;
}
-#ifdef ERTS_HLT_DEBUG
-# define ERTS_BTM_MAX_DESTROY_LIMIT 2
-#else
-# define ERTS_BTM_MAX_DESTROY_LIMIT 50
-#endif
-
-#ifndef ERTS_MAGIC_REF_BIF_TIMERS
typedef struct {
ErtsBifTimers *bif_timers;
union {
proc_btm_rbt_yield_state_t proc_btm_yield_state;
} u;
} ErtsBifTimerYieldState;
-#endif
-int erts_cancel_bif_timers(Process *p, ErtsBifTimers **btm, void **vyspp)
+int erts_cancel_bif_timers(Process *p, ErtsBifTimers **btm, void **vyspp, int reds)
{
ErtsSchedulerData *esdp = erts_proc_sched_data(p);
-
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
-
- return proc_btm_list_foreach_destroy_yielding(btm,
- exit_cancel_bif_timer,
- (void *) esdp,
- ERTS_BTM_MAX_DESTROY_LIMIT);
-
-#else /* !ERTS_MAGIC_REF_BIF_TIMERS */
-
ErtsBifTimerYieldState ys = {*btm, {ERTS_RBT_YIELD_STAT_INITER}};
ErtsBifTimerYieldState *ysp;
int res;
@@ -2661,9 +2332,9 @@ int erts_cancel_bif_timers(Process *p, ErtsBifTimers **btm, void **vyspp)
exit_cancel_bif_timer,
(void *) esdp,
&ysp->u.proc_btm_yield_state,
- ERTS_BTM_MAX_DESTROY_LIMIT);
+ reds);
- if (res == 0) {
+ if (res > 0) {
if (ysp != &ys)
erts_free(ERTS_ALC_T_BTM_YIELD_STATE, ysp);
*vyspp = NULL;
@@ -2682,7 +2353,6 @@ int erts_cancel_bif_timers(Process *p, ErtsBifTimers **btm, void **vyspp)
return res;
-#endif /* !ERTS_MAGIC_REF_BIF_TIMERS */
}
static ERTS_INLINE int
@@ -3116,11 +2786,6 @@ btm_print(ErtsBifTimer *tmr, void *vbtmp, ErtsMonotonicTime tpos, int is_hlt)
ErtsMonotonicTime left;
Eterm receiver;
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
- if (!(tmr->type.head.roflgs & ERTS_TMR_ROFLG_BIF_TMR))
- return;
-#endif
-
if (is_hlt) {
ERTS_HLT_ASSERT(tmr->type.head.roflgs & ERTS_TMR_ROFLG_HLT);
if (tmr->type.hlt.timeout <= btmp->now)
@@ -3149,24 +2814,8 @@ btm_print(ErtsBifTimer *tmr, void *vbtmp, ErtsMonotonicTime tpos, int is_hlt)
(Sint64) left);
}
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
-
-static void
-hlt_btm_print(ErtsHLTimer *tmr, void *vbtmp)
-{
- btm_print((ErtsBifTimer *) tmr, vbtmp, 0, 1);
-}
-
-static void
-twt_btm_print(void *vbtmp, ErtsMonotonicTime tpos, void *vtwtp)
-{
- btm_print((ErtsBifTimer *) vtwtp, vbtmp, tpos, 0);
-}
-
-#else
-
-static void
-btm_tree_print(ErtsBifTimer *tmr, void *vbtmp)
+static int
+btm_tree_print(ErtsBifTimer *tmr, void *vbtmp, Sint reds)
{
int is_hlt = !!(tmr->type.head.roflgs & ERTS_TMR_ROFLG_HLT);
ErtsMonotonicTime tpos;
@@ -3175,10 +2824,9 @@ btm_tree_print(ErtsBifTimer *tmr, void *vbtmp)
else
tpos = erts_tweel_read_timeout(&tmr->type.twt.u.tw_tmr);
btm_print(tmr, vbtmp, tpos, is_hlt);
+ return 1;
}
-#endif
-
void
erts_print_bif_timer_info(fmtfn_t to, void *to_arg)
{
@@ -3196,15 +2844,7 @@ erts_print_bif_timer_info(fmtfn_t to, void *to_arg)
for (six = 0; six < erts_no_schedulers; six++) {
ErtsHLTimerService *srv =
erts_aligned_scheduler_data[six].esd.timer_service;
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
- ErtsTimerWheel *twheel =
- erts_aligned_scheduler_data[six].esd.timer_wheel;
- erts_twheel_debug_foreach(twheel, tw_bif_timer_timeout,
- twt_btm_print, (void *) &btmp);
- time_rbt_foreach(srv->time_tree, hlt_btm_print, (void *) &btmp);
-#else
btm_rbt_foreach(srv->btm_tree, btm_tree_print, (void *) &btmp);
-#endif
}
}
@@ -3216,13 +2856,9 @@ typedef struct {
void *arg;
} ErtsBTMForeachDebug;
-static void
-debug_btm_foreach(ErtsBifTimer *tmr, void *vbtmfd)
+static int
+debug_btm_foreach(ErtsBifTimer *tmr, void *vbtmfd, Sint reds)
{
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
- if (!(tmr->type.head.roflgs & ERTS_TMR_ROFLG_BIF_TMR))
- return;
-#endif
if (erts_atomic32_read_nob(&tmr->btm.state) == ERTS_TMR_STATE_ACTIVE) {
ErtsBTMForeachDebug *btmfd = (ErtsBTMForeachDebug *) vbtmfd;
Eterm id = ((tmr->type.head.roflgs & ERTS_TMR_ROFLG_REG_NAME)
@@ -3230,24 +2866,9 @@ debug_btm_foreach(ErtsBifTimer *tmr, void *vbtmfd)
: tmr->type.head.receiver.proc->common.id);
(*btmfd->func)(id, tmr->btm.message, tmr->btm.bp, btmfd->arg);
}
+ return 1;
}
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
-
-static void
-hlt_debug_btm_foreach(ErtsHLTimer *tmr, void *vbtmfd)
-{
- debug_btm_foreach((ErtsBifTimer *) tmr, vbtmfd);
-}
-
-static void
-twt_debug_btm_foreach(void *vbtmfd, ErtsMonotonicTime tpos, void *vtwtp)
-{
- debug_btm_foreach((ErtsBifTimer *) vtwtp, vbtmfd);
-}
-
-#endif
-
void
erts_debug_bif_timer_foreach(void (*func)(Eterm,
Eterm,
@@ -3267,20 +2888,9 @@ erts_debug_bif_timer_foreach(void (*func)(Eterm,
for (six = 0; six < erts_no_schedulers; six++) {
ErtsHLTimerService *srv =
erts_aligned_scheduler_data[six].esd.timer_service;
-#ifdef ERTS_MAGIC_REF_BIF_TIMERS
- ErtsTimerWheel *twheel =
- erts_aligned_scheduler_data[six].esd.timer_wheel;
- erts_twheel_debug_foreach(twheel, tw_bif_timer_timeout,
- twt_debug_btm_foreach,
- (void *) &btmfd);
- time_rbt_foreach(srv->time_tree,
- hlt_debug_btm_foreach,
- (void *) &btmfd);
-#else
btm_rbt_foreach(srv->btm_tree,
debug_btm_foreach,
(void *) &btmfd);
-#endif
}
}
@@ -3305,8 +2915,8 @@ debug_callback_timer_foreach_list(ErtsHLTimer *tmr, void *vdfct)
tmr->head.u.arg);
}
-static void
-debug_callback_timer_foreach(ErtsHLTimer *tmr, void *vdfct)
+static int
+debug_callback_timer_foreach(ErtsHLTimer *tmr, void *vdfct, Sint reds)
{
ErtsDebugForeachCallbackTimer *dfct
= (ErtsDebugForeachCallbackTimer *) vdfct;
@@ -3321,6 +2931,7 @@ debug_callback_timer_foreach(ErtsHLTimer *tmr, void *vdfct)
(*dfct->func)(dfct->arg,
tmr->timeout,
tmr->head.u.arg);
+ return 1;
}
static void
@@ -3368,7 +2979,8 @@ erts_debug_callback_timer_foreach(void (*tclbk)(void *),
if (srv->yield.root)
debug_callback_timer_foreach(srv->yield.root,
- (void *) &dfct);
+ (void *) &dfct,
+ -1);
time_rbt_foreach(srv->time_tree,
debug_callback_timer_foreach,
@@ -3403,9 +3015,7 @@ st_hdbg_func(ErtsHLTimer *tmr, void *vhdbg)
}
ERTS_HLT_ASSERT(tmr->time.tree.u.l.next->time.tree.u.l.prev == tmr);
ERTS_HLT_ASSERT(tmr->time.tree.u.l.prev->time.tree.u.l.next == tmr);
-#ifndef ERTS_MAGIC_REF_BIF_TIMERS
ERTS_HLT_ASSERT(btm_rbt_lookup(hdbg->srv->btm_tree, ERTS_BTM_HLT2REFN(tmr)) == tmr);
-#endif
}
static void
@@ -3434,10 +3044,8 @@ tt_hdbg_func(ErtsHLTimer *tmr, void *vhdbg)
& ~ERTS_HLT_PFLGS_MASK);
ERTS_HLT_ASSERT(tmr == prnt);
}
-#ifndef ERTS_MAGIC_REF_BIF_TIMERS
if (tmr->head.roflgs & ERTS_TMR_ROFLG_BIF_TMR)
ERTS_HLT_ASSERT(btm_rbt_lookup(hdbg->srv->btm_tree, ERTS_BTM_HLT2REFN(tmr)) == tmr);
-#endif
if (tmr->time.tree.same_time) {
ErtsHdbgHLT st_hdbg;
st_hdbg.srv = hdbg->srv;
@@ -3503,7 +3111,6 @@ hdbg_chk_srv(ErtsHLTimerService *srv)
time_rbt_foreach(srv->time_tree, tt_hdbg_func, (void *) &hdbg);
ERTS_HLT_ASSERT(hdbg.found_root);
}
-#ifndef ERTS_MAGIC_REF_BIF_TIMERS
if (srv->btm_tree) {
ErtsHdbgHLT hdbg;
hdbg.srv = srv;
@@ -3512,7 +3119,6 @@ hdbg_chk_srv(ErtsHLTimerService *srv)
btm_rbt_foreach(srv->btm_tree, bt_hdbg_func, (void *) &hdbg);
ERTS_HLT_ASSERT(hdbg.found_root);
}
-#endif
}
#endif /* ERTS_HLT_HARD_DEBUG */
diff --git a/erts/emulator/beam/erl_hl_timer.h b/erts/emulator/beam/erl_hl_timer.h
index e6f5e8b67d..29c873868b 100644
--- a/erts/emulator/beam/erl_hl_timer.h
+++ b/erts/emulator/beam/erl_hl_timer.h
@@ -56,7 +56,7 @@ void erts_cancel_proc_timer(Process *);
void erts_set_port_timer(Port *, Sint64);
void erts_cancel_port_timer(Port *);
Sint64 erts_read_port_timer(Port *);
-int erts_cancel_bif_timers(Process *, ErtsBifTimers **, void **);
+int erts_cancel_bif_timers(Process *, ErtsBifTimers **, void **, int);
int erts_detach_accessor_bif_timers(Process *, ErtsBifTimers *, void **);
ErtsHLTimerService *erts_create_timer_service(void);
void erts_hl_timer_init(void);
diff --git a/erts/emulator/beam/erl_init.c b/erts/emulator/beam/erl_init.c
index 41bec17dfa..82d5140d1c 100644
--- a/erts/emulator/beam/erl_init.c
+++ b/erts/emulator/beam/erl_init.c
@@ -78,7 +78,7 @@ const char etp_erts_version[] = ERLANG_VERSION;
const char etp_otp_release[] = ERLANG_OTP_RELEASE;
const char etp_compile_date[] = ERLANG_COMPILE_DATE;
const char etp_arch[] = ERLANG_ARCHITECTURE;
-#ifdef ERTS_ENABLE_KERNEL_POLL
+#if ERTS_ENABLE_KERNEL_POLL
const int erts_use_kernel_poll = 1;
const int etp_kernel_poll_support = 1;
#else
@@ -128,7 +128,7 @@ const Eterm etp_hole_marker = 0;
static int modified_sched_thread_suggested_stack_size = 0;
-Eterm erts_init_process_id;
+Eterm erts_init_process_id = ERTS_INVALID_PID;
/*
* Note about VxWorks: All variables must be initialized by executable code,
@@ -163,7 +163,6 @@ int erts_initialized = 0;
* Configurable parameters.
*/
-Uint display_items; /* no of items to display in traces etc */
int H_MIN_SIZE; /* The minimum heap grain */
int BIN_VH_MIN_SIZE; /* The minimum binary virtual*/
int H_MAX_SIZE; /* The maximum heap size */
@@ -354,6 +353,7 @@ erl_init(int ncpu,
erts_init_bif();
erts_init_bif_chksum();
erts_init_bif_binary();
+ erts_init_bif_guard();
erts_init_bif_persistent_term();
erts_init_bif_re();
erts_init_unicode(); /* after RE to get access to PCRE unicode */
@@ -376,6 +376,7 @@ erl_init(int ncpu,
}
static Eterm
+
erl_spawn_system_process(Process* parent, Eterm mod, Eterm func, Eterm args,
ErlSpawnOpts *so)
{
@@ -397,16 +398,15 @@ erl_spawn_system_process(Process* parent, Eterm mod, Eterm func, Eterm args,
}
static Eterm
-erl_first_process_otp(char* modname, void* code, unsigned size, int argc, char** argv)
+erl_first_process_otp(char* mod_name, int argc, char** argv)
{
int i;
- Eterm start_mod;
Eterm args;
Eterm res;
Eterm* hp;
Process parent;
ErlSpawnOpts so;
- Eterm env;
+ Eterm boot_mod;
/*
* We need a dummy parent process to be able to call erl_create_process().
@@ -422,16 +422,14 @@ erl_first_process_otp(char* modname, void* code, unsigned size, int argc, char**
args = CONS(hp, new_binary(&parent, (byte*)argv[i], len), args);
hp += 2;
}
- env = new_binary(&parent, code, size);
+ boot_mod = erts_atom_put((byte *) mod_name, sys_strlen(mod_name),
+ ERTS_ATOM_ENC_LATIN1, 1);
args = CONS(hp, args, NIL);
hp += 2;
- args = CONS(hp, env, args);
-
- start_mod = erts_atom_put((byte *) modname, sys_strlen(modname),
- ERTS_ATOM_ENC_LATIN1, 1);
+ args = CONS(hp, boot_mod, args);
so.flags = erts_default_spo_flags;
- res = erl_spawn_system_process(&parent, start_mod, am_start, args, &so);
+ res = erl_spawn_system_process(&parent, am_erl_init, am_start, args, &so);
ASSERT(is_internal_pid(res));
erts_proc_unlock(&parent, ERTS_PROC_LOCK_MAIN);
@@ -527,7 +525,6 @@ erts_preloaded(Process* p)
/* static variables that must not change (use same values at restart) */
static char* program;
static char* init = "init";
-static char* boot = "boot";
static int boot_argc;
static char** boot_argv;
@@ -581,9 +578,6 @@ void erts_usage(void)
int this_rel = this_rel_num();
erts_fprintf(stderr, "Usage: %s [flags] [ -- [init_args] ]\n", progname(program));
erts_fprintf(stderr, "The flags are:\n\n");
-
- /* erts_fprintf(stderr, "-# number set the number of items to be used in traces etc\n"); */
-
erts_fprintf(stderr, "-a size suggested stack size in kilo words for threads\n");
erts_fprintf(stderr, " in the async-thread pool, valid range is [%d-%d]\n",
ERTS_ASYNC_THREAD_MIN_STACK_SIZE,
@@ -591,13 +585,9 @@ void erts_usage(void)
erts_fprintf(stderr, "-A number set number of threads in async thread pool,\n");
erts_fprintf(stderr, " valid range is [0-%d]\n",
ERTS_MAX_NO_OF_ASYNC_THREADS);
-
erts_fprintf(stderr, "-B[c|d|i] c to have Ctrl-c interrupt the Erlang shell,\n");
erts_fprintf(stderr, " d (or no extra option) to disable the break\n");
erts_fprintf(stderr, " handler, i to ignore break signals\n");
-
- /* erts_fprintf(stderr, "-b func set the boot function (default boot)\n"); */
-
erts_fprintf(stderr, "-c bool enable or disable time correction\n");
erts_fprintf(stderr, "-C mode set time warp mode; valid modes are:\n");
erts_fprintf(stderr, " no_time_warp|single_time_warp|multi_time_warp\n");
@@ -616,7 +606,6 @@ void erts_usage(void)
erts_pd_initial_size);
erts_fprintf(stderr, "-hmqd val set default message queue data flag for processes,\n");
erts_fprintf(stderr, " valid values are: off_heap | on_heap\n");
-
erts_fprintf(stderr, "-IOp number set number of pollsets to be used to poll for I/O,\n");
erts_fprintf(stderr, " This value has to be equal or smaller than the\n");
erts_fprintf(stderr, " number of poll threads. If the current platform\n");
@@ -627,9 +616,7 @@ void erts_usage(void)
erts_fprintf(stderr, " number of poll threads.");
erts_fprintf(stderr, "-IOPt number set number of threads to be used to poll for I/O\n");
erts_fprintf(stderr, " as a percentage of the number of schedulers.");
-
- /* erts_fprintf(stderr, "-i module set the boot module (default init)\n"); */
-
+ erts_fprintf(stderr, "-i module set the boot module (default init)\n");
erts_fprintf(stderr, "-n[s|a|d] Control behavior of signals to ports\n");
erts_fprintf(stderr, " Note that this flag is deprecated!\n");
erts_fprintf(stderr, "-M<X> <Y> memory allocator switches,\n");
@@ -644,7 +631,6 @@ void erts_usage(void)
erts_fprintf(stderr, "-R number set compatibility release number,\n");
erts_fprintf(stderr, " valid range [%d-%d]\n",
this_rel-2, this_rel);
-
erts_fprintf(stderr, "-r force ets memory block to be moved on realloc\n");
erts_fprintf(stderr, "-rg amount set reader groups limit\n");
erts_fprintf(stderr, "-sbt type set scheduler bind type, valid types are:\n");
@@ -715,9 +701,7 @@ void erts_usage(void)
erts_fprintf(stderr, "-T number set modified timing level, valid range is [0-%d]\n",
ERTS_MODIFIED_TIMING_LEVELS-1);
erts_fprintf(stderr, "-V print Erlang version\n");
-
erts_fprintf(stderr, "-v turn on chatty mode (GCs will be reported etc)\n");
-
erts_fprintf(stderr, "-W<i|w|e> set error logger warnings mapping,\n");
erts_fprintf(stderr, " see error_logger documentation for details\n");
erts_fprintf(stderr, "-zdbbl size set the distribution buffer busy limit in kilobytes\n");
@@ -808,7 +792,6 @@ early_init(int *argc, char **argv) /*
erts_sched_compact_load = 1;
erts_printf_eterm_func = erts_printf_term;
- display_items = 200;
erts_backtrace_depth = DEFAULT_BACKTRACE_SIZE;
erts_async_max_threads = ERTS_DEFAULT_NO_ASYNC_THREADS;
erts_async_thread_suggested_stack_size = ERTS_ASYNC_THREAD_MIN_STACK_SIZE;
@@ -1315,25 +1298,9 @@ erl_start(int argc, char **argv)
/*
* NOTE: -M flags are handled (and removed from argv) by
- * erts_alloc_init().
- *
- * The -d, -m, -S, -t, and -T flags was removed in
- * Erlang 5.3/OTP R9C.
- *
- * -S, and -T has been reused in Erlang 5.5/OTP R11B.
- *
- * -d has been reused in a patch R12B-4.
+ * erts_alloc_init().
*/
- case '#' :
- arg = get_arg(argv[i]+2, argv[i+1], &i);
- if ((display_items = atoi(arg)) == 0) {
- erts_fprintf(stderr, "bad display items%s\n", arg);
- erts_usage();
- }
- VERBOSE(DEBUG_SYSTEM,
- ("using display items %d\n",display_items));
- break;
case 'p':
if (!sys_strncmp(argv[i],"-pc",3)) {
int printable_chars = ERL_PRINTABLE_CHARACTERS_LATIN1;
@@ -1612,11 +1579,6 @@ erl_start(int argc, char **argv)
init = get_arg(argv[i]+2, argv[i+1], &i);
break;
- case 'b':
- /* define name of initial function */
- boot = get_arg(argv[i]+2, argv[i+1], &i);
- break;
-
case 'B':
if (argv[i][2] == 'i') /* +Bi */
ignore_break = 1;
@@ -2302,8 +2264,8 @@ erl_start(int argc, char **argv)
erts_initialized = 1;
- erts_init_process_id = erl_first_process_otp("otp_ring0", NULL, 0,
- boot_argc, boot_argv);
+ erts_init_process_id = erl_first_process_otp(init, boot_argc, boot_argv);
+ ASSERT(erts_init_process_id != ERTS_INVALID_PID);
{
/*
@@ -2455,12 +2417,17 @@ erts_exit_vv(int n, int flush_async, char *fmt, va_list args1, va_list args2)
erts_exit_epilogue();
}
+void check_obuf(void);
__decl_noreturn void __noreturn erts_exit_epilogue(void)
{
int n = erts_exit_code;
sys_tty_reset(n);
+#ifdef DEBUG
+ check_obuf();
+#endif
+
if (n == ERTS_INTR_EXIT)
exit(0);
else if (n == ERTS_DUMP_EXIT)
diff --git a/erts/emulator/beam/erl_lock_check.c b/erts/emulator/beam/erl_lock_check.c
index 1416c5f96c..39eabb6710 100644
--- a/erts/emulator/beam/erl_lock_check.c
+++ b/erts/emulator/beam/erl_lock_check.c
@@ -42,6 +42,7 @@
#include "erl_term.h"
#include "erl_threads.h"
#include "erl_atom_table.h"
+#include "erl_utils.h"
typedef struct {
char *name;
@@ -91,6 +92,8 @@ static erts_lc_lock_order_t erts_lock_order[] = {
{ "db_tab", "address" },
{ "db_tab_fix", "address" },
{ "db_hash_slot", "address" },
+ { "erl_db_catree_base_node", NULL },
+ { "erl_db_catree_route_node", "index" },
{ "resource_monitors", "address" },
{ "driver_list", NULL },
{ "proc_msgq", "pid" },
@@ -161,7 +164,8 @@ static erts_lc_lock_order_t erts_lock_order[] = {
{ "os_monotonic_time", NULL },
{ "erts_alloc_hard_debug", NULL },
{ "hard_dbg_mseg", NULL },
- { "erts_mmap", NULL }
+ { "erts_mmap", NULL },
+ { "sad", NULL}
};
#define ERTS_LOCK_ORDER_SIZE \
@@ -193,6 +197,12 @@ struct lc_locked_lock_t_ {
unsigned int line;
erts_lock_flags_t flags;
erts_lock_options_t taken_options;
+ /*
+ * Pointer back to the lock instance if it exists or NULL for proc locks.
+ * If set, we use it to allow trylock of other lock instance
+ * but with identical lock order as an already locked lock.
+ */
+ erts_lc_lock_t *lck;
};
typedef struct {
@@ -406,6 +416,10 @@ new_locked_lock(lc_thread_t* thr,
ll->line = line;
ll->flags = lck->flags;
ll->taken_options = options;
+ if ((lck->flags & ERTS_LOCK_FLAGS_MASK_TYPE) == ERTS_LOCK_FLAGS_TYPE_PROCLOCK)
+ ll->lck = NULL;
+ else
+ ll->lck = lck;
return ll;
}
@@ -709,6 +723,14 @@ erts_lc_get_lock_order_id(char *name)
return (Sint16) -1;
}
+static int
+lc_is_term_order(Sint16 id)
+{
+ return erts_lock_order[id].internal_order != NULL
+ && sys_strcmp(erts_lock_order[id].internal_order, "term") == 0;
+}
+
+
static int compare_locked_by_id(lc_locked_lock_t *locked_lock, erts_lc_lock_t *comparand)
{
if(locked_lock->id < comparand->id) {
@@ -720,18 +742,23 @@ static int compare_locked_by_id(lc_locked_lock_t *locked_lock, erts_lc_lock_t *c
return 0;
}
-static int compare_locked_by_id_extra(lc_locked_lock_t *locked_lock, erts_lc_lock_t *comparand)
+static int compare_locked_by_id_extra(lc_locked_lock_t *ll, erts_lc_lock_t *comparand)
{
- int order = compare_locked_by_id(locked_lock, comparand);
+ int order = compare_locked_by_id(ll, comparand);
if(order) {
return order;
- } else if(locked_lock->extra < comparand->extra) {
+ }
+ if (ll->flags & ERTS_LOCK_FLAGS_PROPERTY_TERM_ORDER) {
+ ASSERT(!is_header(ll->extra) && !is_header(comparand->extra));
+ return CMP(ll->extra, comparand->extra);
+ }
+
+ if(ll->extra < comparand->extra) {
return -1;
- } else if(locked_lock->extra > comparand->extra) {
+ } else if(ll->extra > comparand->extra) {
return 1;
}
-
return 0;
}
@@ -970,7 +997,8 @@ erts_lc_trylock_force_busy_flg(erts_lc_lock_t *lck, erts_lock_options_t options)
return 0;
}
else {
- lc_locked_lock_t *tl_lck;
+ lc_locked_lock_t *ll;
+ int order;
ASSERT(thr->locked.last);
@@ -979,25 +1007,25 @@ erts_lc_trylock_force_busy_flg(erts_lc_lock_t *lck, erts_lock_options_t options)
type_order_violation("trylocking ", thr, lck);
#endif
- if (thr->locked.last->id < lck->id
- || (thr->locked.last->id == lck->id
- && thr->locked.last->extra < lck->extra))
- return 0;
+ ll = thr->locked.last;
+ order = compare_locked_by_id_extra(ll, lck);
+
+ if (order < 0)
+ return 0;
/*
- * Lock order violation
+ * TryLock order violation
*/
-
- /* Check that we are not trying to lock this lock twice */
- for (tl_lck = thr->locked.last; tl_lck; tl_lck = tl_lck->prev) {
- if (tl_lck->id < lck->id
- || (tl_lck->id == lck->id && tl_lck->extra <= lck->extra)) {
- if (tl_lck->id == lck->id && tl_lck->extra == lck->extra)
- lock_twice("Trylocking", thr, lck, options);
- break;
- }
- }
+ /* Check that we are not trying to lock this lock twice */
+ do {
+ if (order == 0 && (ll->lck == lck || !ll->lck))
+ lock_twice("Trylocking", thr, lck, options);
+ ll = ll->prev;
+ if (!ll)
+ break;
+ order = compare_locked_by_id_extra(ll, lck);
+ } while (order >= 0);
#ifndef ERTS_LC_ALLWAYS_FORCE_BUSY_TRYLOCK_ON_LOCK_ORDER_VIOLATION
/* We only force busy if a lock order violation would occur
@@ -1044,10 +1072,10 @@ void erts_lc_trylock_flg_x(int locked, erts_lc_lock_t *lck, erts_lock_options_t
#endif
for (tl_lck = thr->locked.last; tl_lck; tl_lck = tl_lck->prev) {
- if (tl_lck->id < lck->id
- || (tl_lck->id == lck->id && tl_lck->extra <= lck->extra)) {
- if (tl_lck->id == lck->id && tl_lck->extra == lck->extra)
- lock_twice("Trylocking", thr, lck, options);
+ int order = compare_locked_by_id_extra(tl_lck, lck);
+ if (order <= 0) {
+ if (order == 0 && (tl_lck->lck == lck || !tl_lck->lck))
+ lock_twice("Trylocking", thr, lck, options);
if (locked) {
ll->next = tl_lck->next;
ll->prev = tl_lck;
@@ -1089,10 +1117,10 @@ void erts_lc_require_lock_flg(erts_lc_lock_t *lck, erts_lock_options_t options,
for (l_lck2 = thr->required.last;
l_lck2;
l_lck2 = l_lck2->prev) {
- if (l_lck2->id < lck->id
- || (l_lck2->id == lck->id && l_lck2->extra < lck->extra))
+ int order = compare_locked_by_id_extra(l_lck2, lck);
+ if (order < 0)
break;
- else if (l_lck2->id == lck->id && l_lck2->extra == lck->extra)
+ if (order == 0)
require_twice(thr, lck);
}
if (!l_lck2) {
@@ -1150,6 +1178,7 @@ void erts_lc_lock_flg_x(erts_lc_lock_t *lck, erts_lock_options_t options,
{
lc_thread_t *thr;
lc_locked_lock_t *new_ll;
+ int order;
if (lck->inited != ERTS_LC_INITITALIZED)
uninitialized_lock();
@@ -1165,10 +1194,10 @@ void erts_lc_lock_flg_x(erts_lc_lock_t *lck, erts_lock_options_t options,
thr->locked.last = thr->locked.first = new_ll;
ASSERT(0 < lck->id && lck->id < ERTS_LOCK_ORDER_SIZE);
thr->matrix.m[lck->id][0] = 1;
+ return;
}
- else if (thr->locked.last->id < lck->id
- || (thr->locked.last->id == lck->id
- && thr->locked.last->extra < lck->extra)) {
+ order = compare_locked_by_id_extra(thr->locked.last, lck);
+ if (order < 0) {
lc_locked_lock_t* ll;
if (LOCK_IS_TYPE_ORDER_VIOLATION(lck->flags, thr->locked.last->flags)) {
type_order_violation("locking ", thr, lck);
@@ -1186,7 +1215,7 @@ void erts_lc_lock_flg_x(erts_lc_lock_t *lck, erts_lock_options_t options,
thr->locked.last->next = new_ll;
thr->locked.last = new_ll;
}
- else if (thr->locked.last->id == lck->id && thr->locked.last->extra == lck->extra)
+ else if (order == 0)
lock_twice("Locking", thr, lck, options);
else
lock_order_violation(thr, lck);
@@ -1298,7 +1327,6 @@ void
erts_lc_init_lock(erts_lc_lock_t *lck, char *name, erts_lock_flags_t flags)
{
lck->id = erts_lc_get_lock_order_id(name);
-
lck->extra = (UWord) &lck->extra;
ASSERT(is_not_immed(lck->extra));
lck->flags = flags;
@@ -1311,8 +1339,13 @@ erts_lc_init_lock_x(erts_lc_lock_t *lck, char *name, erts_lock_flags_t flags, Et
{
lck->id = erts_lc_get_lock_order_id(name);
lck->extra = extra;
- ASSERT(is_immed(lck->extra));
lck->flags = flags;
+ if (lc_is_term_order(lck->id)) {
+ lck->flags |= ERTS_LOCK_FLAGS_PROPERTY_TERM_ORDER;
+ ASSERT(!is_header(lck->extra));
+ }
+ else
+ ASSERT(is_immed(lck->extra));
lck->taken_options = 0;
lck->inited = ERTS_LC_INITITALIZED;
}
diff --git a/erts/emulator/beam/erl_lock_check.h b/erts/emulator/beam/erl_lock_check.h
index d10e32985a..b32f27d9f9 100644
--- a/erts/emulator/beam/erl_lock_check.h
+++ b/erts/emulator/beam/erl_lock_check.h
@@ -104,7 +104,7 @@ Eterm erts_lc_dump_graph(void);
#define erts_lc_lock(lck) erts_lc_lock_x(lck,__FILE__,__LINE__)
#define erts_lc_trylock(res,lck) erts_lc_trylock_x(res,lck,__FILE__,__LINE__)
-#define erts_lc_lock_flg(lck) erts_lc_lock_flg_x(lck,__FILE__,__LINE__)
-#define erts_lc_trylock_flg(res,lck) erts_lc_trylock_flg_x(res,lck,__FILE__,__LINE__)
+#define erts_lc_lock_flg(lck,flg) erts_lc_lock_flg_x(lck,flg,__FILE__,__LINE__)
+#define erts_lc_trylock_flg(res,lck,flg) erts_lc_trylock_flg_x(res,lck,flg,__FILE__,__LINE__)
#endif /* #ifndef ERTS_LOCK_CHECK_H__ */
diff --git a/erts/emulator/beam/erl_lock_count.h b/erts/emulator/beam/erl_lock_count.h
index 89d95a73cf..0d47b16e0b 100644
--- a/erts/emulator/beam/erl_lock_count.h
+++ b/erts/emulator/beam/erl_lock_count.h
@@ -532,7 +532,7 @@ ERTS_GLB_INLINE
void lcnt_dec_lock_state__(ethr_atomic_t *l_state) {
ethr_sint_t state = ethr_atomic_dec_read_acqb(l_state);
- /* We can not assume that state is >= -1 here; unlock and unacquire might
+ /* We cannot assume that state is >= -1 here; unlock and unacquire might
* bring it below -1 and race to increment it back. */
if(state < 0) {
diff --git a/erts/emulator/beam/erl_lock_flags.h b/erts/emulator/beam/erl_lock_flags.h
index d711f69456..2db133b598 100644
--- a/erts/emulator/beam/erl_lock_flags.h
+++ b/erts/emulator/beam/erl_lock_flags.h
@@ -28,15 +28,17 @@
/* Property/category are bitfields to simplify their use in masks. */
#define ERTS_LOCK_FLAGS_MASK_CATEGORY (0xFFC0)
-#define ERTS_LOCK_FLAGS_MASK_PROPERTY (0x0030)
+#define ERTS_LOCK_FLAGS_MASK_PROPERTY (0x0038)
/* Type is a plain number. */
-#define ERTS_LOCK_FLAGS_MASK_TYPE (0x000F)
+#define ERTS_LOCK_FLAGS_MASK_TYPE (0x0007)
#define ERTS_LOCK_FLAGS_TYPE_SPINLOCK (1)
#define ERTS_LOCK_FLAGS_TYPE_MUTEX (2)
#define ERTS_LOCK_FLAGS_TYPE_PROCLOCK (3)
+/* Lock checker use real term order instead of raw word compare */
+#define ERTS_LOCK_FLAGS_PROPERTY_TERM_ORDER (1 << 3)
/* "Static" guarantees that the lock will never be destroyed once created. */
#define ERTS_LOCK_FLAGS_PROPERTY_STATIC (1 << 4)
#define ERTS_LOCK_FLAGS_PROPERTY_READ_WRITE (1 << 5)
diff --git a/erts/emulator/beam/erl_map.c b/erts/emulator/beam/erl_map.c
index cba17d3e6a..62dd85e425 100644
--- a/erts/emulator/beam/erl_map.c
+++ b/erts/emulator/beam/erl_map.c
@@ -125,15 +125,20 @@ BIF_RETTYPE map_size_1(BIF_ALIST_1) {
flatmap_t *mp = (flatmap_t*)flatmap_val(BIF_ARG_1);
BIF_RET(make_small(flatmap_get_size(mp)));
} else if (is_hashmap(BIF_ARG_1)) {
- Eterm *head, *hp, res;
- Uint size, hsz=0;
+ Eterm *head;
+ Uint size;
head = hashmap_val(BIF_ARG_1);
size = head[1];
- (void) erts_bld_uint(NULL, &hsz, size);
- hp = HAlloc(BIF_P, hsz);
- res = erts_bld_uint(&hp, NULL, size);
- BIF_RET(res);
+
+ /*
+ * As long as a small has 28 bits (on a 32-bit machine) for
+ * the integer itself, it is impossible to build a map whose
+ * size would not fit in a small. Add an assertion in case we
+ * ever decreases the number of bits in a small.
+ */
+ ASSERT(IS_USMALL(0, size));
+ BIF_RET(make_small(size));
}
BIF_P->fvalue = BIF_ARG_1;
@@ -475,7 +480,7 @@ Eterm erts_hashmap_from_array(ErtsHeapFactory* factory, Eterm *leafs, Uint n,
Eterm erts_map_from_ks_and_vs(ErtsHeapFactory *factory, Eterm *ks0, Eterm *vs0, Uint n)
{
- if (n < MAP_SMALL_MAP_LIMIT) {
+ if (n <= MAP_SMALL_MAP_LIMIT) {
Eterm *ks, *vs, *hp;
flatmap_t *mp;
Eterm keys;
@@ -1505,25 +1510,6 @@ int hashmap_key_hash_cmp(Eterm* ap, Eterm* bp)
return ap ? -1 : 1;
}
-/* maps:new/0 */
-
-BIF_RETTYPE maps_new_0(BIF_ALIST_0) {
- Eterm* hp;
- Eterm tup;
- flatmap_t *mp;
-
- hp = HAlloc(BIF_P, (MAP_HEADER_FLATMAP_SZ + 1));
- tup = make_tuple(hp);
- *hp++ = make_arityval(0);
-
- mp = (flatmap_t*)hp;
- mp->thing_word = MAP_HEADER_FLATMAP;
- mp->size = 0;
- mp->keys = tup;
-
- BIF_RET(make_flatmap(mp));
-}
-
/* maps:put/3 */
BIF_RETTYPE maps_put_3(BIF_ALIST_3) {
@@ -1707,11 +1693,16 @@ int erts_maps_update(Process *p, Eterm key, Eterm value, Eterm map, Eterm *res)
return 0;
found_key:
- *hp++ = value;
- vs++;
- if (++i < n)
- sys_memcpy(hp, vs, (n - i)*sizeof(Eterm));
- *res = make_flatmap(shp);
+ if(*vs == value) {
+ HRelease(p, shp + MAP_HEADER_FLATMAP_SZ + n, shp);
+ *res = map;
+ } else {
+ *hp++ = value;
+ vs++;
+ if (++i < n)
+ sys_memcpy(hp, vs, (n - i)*sizeof(Eterm));
+ *res = make_flatmap(shp);
+ }
return 1;
}
@@ -1767,9 +1758,7 @@ Eterm erts_maps_put(Process *p, Eterm key, Eterm value, Eterm map) {
if (is_immed(key)) {
for( i = 0; i < n; i ++) {
if (ks[i] == key) {
- *hp++ = value;
- vs++;
- c = 1;
+ goto found_key;
} else {
*hp++ = *vs++;
}
@@ -1777,18 +1766,13 @@ Eterm erts_maps_put(Process *p, Eterm key, Eterm value, Eterm map) {
} else {
for( i = 0; i < n; i ++) {
if (EQ(ks[i], key)) {
- *hp++ = value;
- vs++;
- c = 1;
+ goto found_key;
} else {
*hp++ = *vs++;
}
}
}
- if (c)
- return res;
-
/* the map will grow */
if (n >= MAP_SMALL_MAP_LIMIT) {
@@ -1843,6 +1827,18 @@ Eterm erts_maps_put(Process *p, Eterm key, Eterm value, Eterm map) {
*/
*shp = make_pos_bignum_header(0);
return res;
+
+found_key:
+ if(*vs == value) {
+ HRelease(p, shp + MAP_HEADER_FLATMAP_SZ + n, shp);
+ return map;
+ } else {
+ *hp++ = value;
+ vs++;
+ if (++i < n)
+ sys_memcpy(hp, vs, (n - i)*sizeof(Eterm));
+ return res;
+ }
}
ASSERT(is_hashmap(map));
diff --git a/erts/emulator/beam/erl_message.c b/erts/emulator/beam/erl_message.c
index a3274d7443..9d40754d2d 100644
--- a/erts/emulator/beam/erl_message.c
+++ b/erts/emulator/beam/erl_message.c
@@ -181,7 +181,7 @@ erts_cleanup_offheap(ErlOffHeap *offheap)
break;
default:
ASSERT(is_external_header(u.hdr->thing_word));
- erts_deref_node_entry(u.ext->node);
+ erts_deref_node_entry(u.ext->node, make_boxed(u.ep));
break;
}
}
@@ -201,34 +201,44 @@ free_message_buffer(ErlHeapFragment* bp)
}while (bp != NULL);
}
+static void
+erts_cleanup_message(ErtsMessage *mp)
+{
+ ErlHeapFragment *bp;
+ if (ERTS_SIG_IS_EXTERNAL_MSG(mp) || ERTS_SIG_IS_NON_MSG(mp)) {
+ ErtsDistExternal *edep = erts_proc_sig_get_external(mp);
+ if (edep) {
+ erts_free_dist_ext_copy(edep);
+ if (mp->data.heap_frag == &mp->hfrag) {
+ ASSERT(ERTS_SIG_IS_EXTERNAL_MSG(mp));
+ mp->data.heap_frag = ERTS_MSG_COMBINED_HFRAG;
+ }
+ }
+ }
+
+ if (ERTS_SIG_IS_MSG(mp) && mp->data.attached != ERTS_MSG_COMBINED_HFRAG) {
+ bp = mp->data.heap_frag;
+ } else {
+ /* All non msg signals are combined HFRAG messages,
+ but we overwrite the mp->data field with the
+ nm_signal queue ptr so have to fix that here
+ before freeing it. */
+ mp->data.attached = ERTS_MSG_COMBINED_HFRAG;
+ bp = mp->hfrag.next;
+ erts_cleanup_offheap(&mp->hfrag.off_heap);
+ }
+
+ if (bp)
+ free_message_buffer(bp);
+}
+
void
erts_cleanup_messages(ErtsMessage *msgp)
{
ErtsMessage *mp = msgp;
while (mp) {
ErtsMessage *fmp;
- ErlHeapFragment *bp;
- if (ERTS_SIG_IS_EXTERNAL_MSG(mp)) {
- if (is_not_immed(ERL_MESSAGE_TOKEN(mp))) {
- bp = (ErlHeapFragment *) mp->data.dist_ext->ext_endp;
- erts_cleanup_offheap(&bp->off_heap);
- }
- if (mp->data.dist_ext)
- erts_free_dist_ext_copy(mp->data.dist_ext);
- }
- else {
- if (ERTS_SIG_IS_INTERNAL_MSG(mp)
- && mp->data.attached != ERTS_MSG_COMBINED_HFRAG) {
- bp = mp->data.heap_frag;
- }
- else {
- mp->data.attached = ERTS_MSG_COMBINED_HFRAG;
- bp = mp->hfrag.next;
- erts_cleanup_offheap(&mp->hfrag.off_heap);
- }
- if (bp)
- free_message_buffer(bp);
- }
+ erts_cleanup_message(mp);
fmp = mp;
mp = mp->next;
erts_free_message(fmp);
@@ -260,6 +270,7 @@ void
erts_queue_dist_message(Process *rcvr,
ErtsProcLocks rcvr_locks,
ErtsDistExternal *dist_ext,
+ ErlHeapFragment *hfrag,
Eterm token,
Eterm from)
{
@@ -268,8 +279,26 @@ erts_queue_dist_message(Process *rcvr,
ERTS_LC_ASSERT(rcvr_locks == erts_proc_lc_my_proc_locks(rcvr));
- mp = erts_alloc_message(0, NULL);
- mp->data.dist_ext = dist_ext;
+ if (hfrag) {
+ /* Fragmented message, allocate a message reference */
+ mp = erts_alloc_message(0, NULL);
+ mp->data.heap_frag = hfrag;
+ } else {
+ /* Un-fragmented message, allocate space for
+ token and dist_ext in message. */
+ Uint dist_ext_sz = erts_dist_ext_size(dist_ext) / sizeof(Eterm);
+ Uint token_sz = size_object(token);
+ Uint sz = token_sz + dist_ext_sz;
+ Eterm *hp;
+
+ mp = erts_alloc_message(sz, &hp);
+ mp->data.heap_frag = &mp->hfrag;
+ mp->hfrag.used_size = token_sz;
+
+ erts_make_dist_ext_copy(dist_ext, erts_get_dist_ext(mp->data.heap_frag));
+
+ token = copy_struct(token, token_sz, &hp, &mp->data.heap_frag->off_heap);
+ }
ERL_MESSAGE_FROM(mp) = dist_ext->dep->sysname;
ERL_MESSAGE_TERM(mp) = THE_NON_VALUE;
@@ -493,25 +522,27 @@ Uint
erts_msg_attached_data_size_aux(ErtsMessage *msg)
{
Sint sz;
- ASSERT(is_non_value(ERL_MESSAGE_TERM(msg)));
- ASSERT(msg->data.dist_ext);
- ASSERT(msg->data.dist_ext->heap_size < 0);
-
- sz = erts_decode_dist_ext_size(msg->data.dist_ext);
- if (sz < 0) {
- /* Bad external
- * We leave the message intact in this case as it's not worth the trouble
- * to make all callers remove it from queue. It will be detected again
- * and removed from message queue later anyway.
- */
- return 0;
- }
+ ErtsDistExternal *edep = erts_get_dist_ext(msg->data.heap_frag);
+ ASSERT(ERTS_SIG_IS_EXTERNAL_MSG(msg));
- msg->data.dist_ext->heap_size = sz;
- if (is_not_nil(msg->m[1])) {
- ErlHeapFragment *heap_frag;
- heap_frag = erts_dist_ext_trailer(msg->data.dist_ext);
- sz += heap_frag->used_size;
+ if (edep->heap_size < 0) {
+
+ sz = erts_decode_dist_ext_size(edep, 1);
+ if (sz < 0) {
+ /* Bad external
+ * We leave the message intact in this case as it's not worth the trouble
+ * to make all callers remove it from queue. It will be detected again
+ * and removed from message queue later anyway.
+ */
+ return 0;
+ }
+
+ edep->heap_size = sz;
+ } else {
+ sz = edep->heap_size;
+ }
+ if (is_not_nil(ERL_MESSAGE_TOKEN(msg))) {
+ sz += msg->data.heap_frag->used_size;
}
return sz;
}
@@ -532,9 +563,7 @@ erts_try_alloc_message_on_heap(Process *pp,
if ((*psp) & ERTS_PSFLGS_VOLATILE_HEAP)
goto in_message_fragment;
- else if (
- *plp & ERTS_PROC_LOCK_MAIN
- ) {
+ else if (*plp & ERTS_PROC_LOCK_MAIN) {
try_on_heap:
if (((*psp) & ERTS_PSFLGS_VOLATILE_HEAP)
|| (pp->flags & F_DISABLE_GC)
@@ -1101,80 +1130,6 @@ change_to_off_heap:
return res;
}
-int
-erts_decode_dist_message(Process *proc, ErtsProcLocks proc_locks,
- ErtsMessage *msgp, int force_off_heap)
-{
- ErtsHeapFactory factory;
- Eterm msg;
- ErlHeapFragment *bp;
- Sint need;
- int decode_in_heap_frag;
-
- decode_in_heap_frag = (force_off_heap
- || !(proc_locks & ERTS_PROC_LOCK_MAIN)
- || (proc->flags & F_OFF_HEAP_MSGQ));
-
- if (msgp->data.dist_ext->heap_size >= 0)
- need = msgp->data.dist_ext->heap_size;
- else {
- need = erts_decode_dist_ext_size(msgp->data.dist_ext);
- if (need < 0) {
- /* bad msg; remove it... */
- if (is_not_immed(ERL_MESSAGE_TOKEN(msgp))) {
- bp = erts_dist_ext_trailer(msgp->data.dist_ext);
- erts_cleanup_offheap(&bp->off_heap);
- }
- erts_free_dist_ext_copy(msgp->data.dist_ext);
- msgp->data.dist_ext = NULL;
- return 0;
- }
-
- msgp->data.dist_ext->heap_size = need;
- }
-
- if (is_not_immed(ERL_MESSAGE_TOKEN(msgp))) {
- bp = erts_dist_ext_trailer(msgp->data.dist_ext);
- need += bp->used_size;
- }
-
- if (decode_in_heap_frag)
- erts_factory_heap_frag_init(&factory, new_message_buffer(need));
- else
- erts_factory_proc_prealloc_init(&factory, proc, need);
-
- ASSERT(msgp->data.dist_ext->heap_size >= 0);
- if (is_not_immed(ERL_MESSAGE_TOKEN(msgp))) {
- ErlHeapFragment *heap_frag;
- heap_frag = erts_dist_ext_trailer(msgp->data.dist_ext);
- ERL_MESSAGE_TOKEN(msgp) = copy_struct(ERL_MESSAGE_TOKEN(msgp),
- heap_frag->used_size,
- &factory.hp,
- factory.off_heap);
- erts_cleanup_offheap(&heap_frag->off_heap);
- }
-
- msg = erts_decode_dist_ext(&factory, msgp->data.dist_ext);
- ERL_MESSAGE_TERM(msgp) = msg;
- erts_free_dist_ext_copy(msgp->data.dist_ext);
- msgp->data.attached = NULL;
-
- if (is_non_value(msg)) {
- erts_factory_undo(&factory);
- return 0;
- }
-
- erts_factory_trim_and_close(&factory, msgp->m,
- ERL_MESSAGE_REF_ARRAY_SZ);
-
- ASSERT(!msgp->data.heap_frag);
-
- if (decode_in_heap_frag)
- msgp->data.heap_frag = factory.heap_frags;
-
- return 1;
-}
-
void erts_factory_proc_init(ErtsHeapFactory* factory,
Process* p)
{
@@ -1235,7 +1190,7 @@ erts_factory_message_create(ErtsHeapFactory* factory,
int on_heap;
erts_aint32_t state;
- state = proc ? erts_atomic32_read_nob(&proc->state) : 0;
+ state = proc ? erts_atomic32_read_nob(&proc->state) : ERTS_PSFLG_OFF_HEAP_MSGQ;
if (state & ERTS_PSFLG_OFF_HEAP_MSGQ) {
msgp = erts_alloc_message(sz, &hp);
@@ -1468,8 +1423,8 @@ void erts_factory_close(ErtsHeapFactory* factory)
else
factory->message->data.heap_frag = factory->heap_frags;
- /* Fall through */
- case FACTORY_HEAP_FRAGS:
+ /* Fall through */
+ case FACTORY_HEAP_FRAGS:
bp = factory->heap_frags;
}
diff --git a/erts/emulator/beam/erl_message.h b/erts/emulator/beam/erl_message.h
index b2550814fd..4c2674394e 100644
--- a/erts/emulator/beam/erl_message.h
+++ b/erts/emulator/beam/erl_message.h
@@ -138,7 +138,7 @@ typedef struct erl_heap_fragment ErlHeapFragment;
struct erl_heap_fragment {
ErlHeapFragment* next; /* Next heap fragment */
ErlOffHeap off_heap; /* Offset heap data. */
- Uint alloc_size; /* Size in (half)words of mem */
+ Uint alloc_size; /* Size in words of mem */
Uint used_size; /* With terms to be moved to heap by GC */
Eterm mem[1]; /* Data */
};
@@ -167,7 +167,6 @@ struct erl_heap_fragment {
#define ERL_MESSAGE_REF_FIELDS__ \
ErtsMessage *next; /* Next message */ \
union { \
- ErtsDistExternal *dist_ext; \
ErlHeapFragment *heap_frag; \
void *attached; \
} data; \
@@ -438,7 +437,8 @@ ErlHeapFragment* new_message_buffer(Uint);
ErlHeapFragment* erts_resize_message_buffer(ErlHeapFragment *, Uint,
Eterm *, Uint);
void free_message_buffer(ErlHeapFragment *);
-void erts_queue_dist_message(Process*, ErtsProcLocks, ErtsDistExternal *, Eterm, Eterm);
+void erts_queue_dist_message(Process*, ErtsProcLocks, ErtsDistExternal *,
+ ErlHeapFragment *, Eterm, Eterm);
void erts_queue_message(Process*, ErtsProcLocks,ErtsMessage*, Eterm, Eterm);
void erts_queue_proc_message(Process* from,Process* to, ErtsProcLocks,ErtsMessage*, Eterm);
void erts_queue_proc_messages(Process* from, Process* to, ErtsProcLocks,
@@ -455,8 +455,6 @@ Sint erts_move_messages_off_heap(Process *c_p);
Sint erts_complete_off_heap_message_queue_change(Process *c_p);
Eterm erts_change_message_queue_management(Process *c_p, Eterm new_state);
-int erts_decode_dist_message(Process *, ErtsProcLocks, ErtsMessage *, int);
-
void erts_cleanup_messages(ErtsMessage *mp);
void *erts_alloc_message_ref(void);
@@ -585,22 +583,11 @@ ERTS_GLB_INLINE Uint erts_used_frag_sz(const ErlHeapFragment* bp)
ERTS_GLB_INLINE Uint erts_msg_attached_data_size(ErtsMessage *msg)
{
ASSERT(msg->data.attached);
- if (is_value(ERL_MESSAGE_TERM(msg))) {
- ErlHeapFragment *bp;
- bp = erts_message_to_heap_frag(msg);
- return erts_used_frag_sz(bp);
- }
- else if (msg->data.dist_ext->heap_size < 0)
- return erts_msg_attached_data_size_aux(msg);
- else {
- Uint sz = msg->data.dist_ext->heap_size;
- if (is_not_nil(ERL_MESSAGE_TOKEN(msg))) {
- ErlHeapFragment *heap_frag;
- heap_frag = erts_dist_ext_trailer(msg->data.dist_ext);
- sz += heap_frag->used_size;
- }
- return sz;
- }
+
+ if (ERTS_SIG_IS_INTERNAL_MSG(msg))
+ return erts_used_frag_sz(erts_message_to_heap_frag(msg));
+
+ return erts_msg_attached_data_size_aux(msg);
}
#endif
diff --git a/erts/emulator/beam/erl_monitor_link.c b/erts/emulator/beam/erl_monitor_link.c
index 48d9bd4ca5..1c6b4afaa3 100644
--- a/erts/emulator/beam/erl_monitor_link.c
+++ b/erts/emulator/beam/erl_monitor_link.c
@@ -191,7 +191,8 @@ ml_cmp_keys(Eterm key1, Eterm key2)
if (n1->sysname != n2->sysname)
return n1->sysname < n2->sysname ? -1 : 1;
ASSERT(n1->creation != n2->creation);
- return n1->creation < n2->creation ? -1 : 1;
+ if (n1->creation != 0 && n2->creation != 0)
+ return n1->creation < n2->creation ? -1 : 1;
}
ndw1 = external_thing_data_words(et1);
@@ -335,7 +336,7 @@ ml_rbt_delete(ErtsMonLnkNode **root, ErtsMonLnkNode *ml)
static void
ml_rbt_foreach(ErtsMonLnkNode *root,
- void (*func)(ErtsMonLnkNode *, void *),
+ ErtsMonLnkNodeFunc func,
void *arg)
{
mon_lnk_rbt_foreach(root, func, arg);
@@ -348,7 +349,7 @@ typedef struct {
static int
ml_rbt_foreach_yielding(ErtsMonLnkNode *root,
- void (*func)(ErtsMonLnkNode *, void *),
+ ErtsMonLnkNodeFunc func,
void *arg,
void **vyspp,
Sint limit)
@@ -362,7 +363,7 @@ ml_rbt_foreach_yielding(ErtsMonLnkNode *root,
ysp = &ys;
res = mon_lnk_rbt_foreach_yielding(ysp->root, func, arg,
&ysp->rbt_ystate, limit);
- if (res == 0) {
+ if (res > 0) {
if (ysp != &ys)
erts_free(ERTS_ALC_T_ML_YIELD_STATE, ysp);
*vyspp = NULL;
@@ -383,22 +384,22 @@ ml_rbt_foreach_yielding(ErtsMonLnkNode *root,
}
typedef struct {
- void (*func)(ErtsMonLnkNode *, void *);
+ ErtsMonLnkNodeFunc func;
void *arg;
} ErtsMonLnkForeachDeleteContext;
-static void
-rbt_wrap_foreach_delete(ErtsMonLnkNode *ml, void *vctxt)
+static int
+rbt_wrap_foreach_delete(ErtsMonLnkNode *ml, void *vctxt, Sint reds)
{
ErtsMonLnkForeachDeleteContext *ctxt = vctxt;
ERTS_ML_ASSERT(ml->flags & ERTS_ML_FLG_IN_TABLE);
ml->flags &= ~ERTS_ML_FLG_IN_TABLE;
- ctxt->func(ml, ctxt->arg);
+ return ctxt->func(ml, ctxt->arg, reds);
}
static void
ml_rbt_foreach_delete(ErtsMonLnkNode **root,
- void (*func)(ErtsMonLnkNode *, void *),
+ ErtsMonLnkNodeFunc func,
void *arg)
{
ErtsMonLnkForeachDeleteContext ctxt;
@@ -411,7 +412,7 @@ ml_rbt_foreach_delete(ErtsMonLnkNode **root,
static int
ml_rbt_foreach_delete_yielding(ErtsMonLnkNode **root,
- void (*func)(ErtsMonLnkNode *, void *),
+ ErtsMonLnkNodeFunc func,
void *arg,
void **vyspp,
Sint limit)
@@ -433,7 +434,7 @@ ml_rbt_foreach_delete_yielding(ErtsMonLnkNode **root,
(void *) &ctxt,
&ysp->rbt_ystate,
limit);
- if (res == 0) {
+ if (res > 0) {
if (ysp != &ys)
erts_free(ERTS_ALC_T_ML_YIELD_STATE, ysp);
*vyspp = NULL;
@@ -459,12 +460,11 @@ ml_rbt_foreach_delete_yielding(ErtsMonLnkNode **root,
static int
ml_dl_list_foreach_yielding(ErtsMonLnkNode *list,
- void (*func)(ErtsMonLnkNode *, void *),
+ ErtsMonLnkNodeFunc func,
void *arg,
void **vyspp,
- Sint limit)
+ Sint reds)
{
- Sint cnt = 0;
ErtsMonLnkNode *ml = (ErtsMonLnkNode *) *vyspp;
ERTS_ML_ASSERT(!ml || list);
@@ -475,28 +475,26 @@ ml_dl_list_foreach_yielding(ErtsMonLnkNode *list,
if (ml) {
do {
ERTS_ML_ASSERT(ml->flags & ERTS_ML_FLG_IN_TABLE);
- func(ml, arg);
+ reds -= func(ml, arg, reds);
ml = ml->node.list.next;
- cnt++;
- } while (ml != list && cnt < limit);
+ } while (ml != list && reds > 0);
if (ml != list) {
*vyspp = (void *) ml;
- return 1; /* yield */
+ return 0; /* yield */
}
}
*vyspp = NULL;
- return 0; /* done */
+ return reds <= 0 ? 1 : reds; /* done */
}
static int
ml_dl_list_foreach_delete_yielding(ErtsMonLnkNode **list,
- void (*func)(ErtsMonLnkNode *, void *),
+ ErtsMonLnkNodeFunc func,
void *arg,
void **vyspp,
- Sint limit)
+ Sint reds)
{
- Sint cnt = 0;
ErtsMonLnkNode *first = *list;
ErtsMonLnkNode *ml = (ErtsMonLnkNode *) *vyspp;
@@ -510,19 +508,18 @@ ml_dl_list_foreach_delete_yielding(ErtsMonLnkNode **list,
ErtsMonLnkNode *next = ml->node.list.next;
ERTS_ML_ASSERT(ml->flags & ERTS_ML_FLG_IN_TABLE);
ml->flags &= ~ERTS_ML_FLG_IN_TABLE;
- func(ml, arg);
+ reds -= func(ml, arg, reds);
ml = next;
- cnt++;
- } while (ml != first && cnt < limit);
+ } while (ml != first && reds > 0);
if (ml != first) {
*vyspp = (void *) ml;
- return 1; /* yield */
+ return 0; /* yield */
}
}
*vyspp = NULL;
*list = NULL;
- return 0; /* done */
+ return reds <= 0 ? 1 : reds; /* done */
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
@@ -666,91 +663,91 @@ erts_monitor_tree_delete(ErtsMonitor **root, ErtsMonitor *mon)
void
erts_monitor_tree_foreach(ErtsMonitor *root,
- void (*func)(ErtsMonitor *, void *),
+ ErtsMonitorFunc func,
void *arg)
{
ml_rbt_foreach((ErtsMonLnkNode *) root,
- (void (*)(ErtsMonLnkNode*, void*)) func,
+ (ErtsMonLnkNodeFunc) func,
arg);
}
int
erts_monitor_tree_foreach_yielding(ErtsMonitor *root,
- void (*func)(ErtsMonitor *, void *),
+ ErtsMonitorFunc func,
void *arg,
void **vyspp,
Sint limit)
{
return ml_rbt_foreach_yielding((ErtsMonLnkNode *) root,
- (void (*)(ErtsMonLnkNode*, void*)) func,
+ (int (*)(ErtsMonLnkNode*, void*, Sint)) func,
arg, vyspp, limit);
}
void
erts_monitor_tree_foreach_delete(ErtsMonitor **root,
- void (*func)(ErtsMonitor *, void *),
+ ErtsMonitorFunc func,
void *arg)
{
ml_rbt_foreach_delete((ErtsMonLnkNode **) root,
- (void (*)(ErtsMonLnkNode*, void*)) func,
+ (int (*)(ErtsMonLnkNode*, void*, Sint)) func,
arg);
}
int
erts_monitor_tree_foreach_delete_yielding(ErtsMonitor **root,
- void (*func)(ErtsMonitor *, void *),
+ ErtsMonitorFunc func,
void *arg,
void **vyspp,
Sint limit)
{
return ml_rbt_foreach_delete_yielding((ErtsMonLnkNode **) root,
- (void (*)(ErtsMonLnkNode*, void*)) func,
+ (int (*)(ErtsMonLnkNode*, void*, Sint)) func,
arg, vyspp, limit);
}
void
erts_monitor_list_foreach(ErtsMonitor *list,
- void (*func)(ErtsMonitor *, void *),
+ ErtsMonitorFunc func,
void *arg)
{
void *ystate = NULL;
- while (ml_dl_list_foreach_yielding((ErtsMonLnkNode *) list,
- (void (*)(ErtsMonLnkNode *, void *)) func,
- arg, &ystate, (Sint) INT_MAX));
+ while (!ml_dl_list_foreach_yielding((ErtsMonLnkNode *) list,
+ (int (*)(ErtsMonLnkNode *, void *, Sint)) func,
+ arg, &ystate, (Sint) INT_MAX));
}
int
erts_monitor_list_foreach_yielding(ErtsMonitor *list,
- void (*func)(ErtsMonitor *, void *),
+ ErtsMonitorFunc func,
void *arg,
void **vyspp,
Sint limit)
{
return ml_dl_list_foreach_yielding((ErtsMonLnkNode *) list,
- (void (*)(ErtsMonLnkNode *, void *)) func,
+ (int (*)(ErtsMonLnkNode *, void *, Sint)) func,
arg, vyspp, limit);
}
void
erts_monitor_list_foreach_delete(ErtsMonitor **list,
- void (*func)(ErtsMonitor *, void *),
+ ErtsMonitorFunc func,
void *arg)
{
void *ystate = NULL;
- while (ml_dl_list_foreach_delete_yielding((ErtsMonLnkNode **) list,
- (void (*)(ErtsMonLnkNode*, void*)) func,
- arg, &ystate, (Sint) INT_MAX));
+ while (!ml_dl_list_foreach_delete_yielding((ErtsMonLnkNode **) list,
+ (int (*)(ErtsMonLnkNode*, void*, Sint)) func,
+ arg, &ystate, (Sint) INT_MAX));
}
int
erts_monitor_list_foreach_delete_yielding(ErtsMonitor **list,
- void (*func)(ErtsMonitor *, void *),
+ ErtsMonitorFunc func,
void *arg,
void **vyspp,
Sint limit)
{
return ml_dl_list_foreach_delete_yielding((ErtsMonLnkNode **) list,
- (void (*)(ErtsMonLnkNode*, void*)) func,
+ (int (*)(ErtsMonLnkNode*, void*, Sint)) func,
arg, vyspp, limit);
}
@@ -1074,92 +1071,92 @@ erts_link_tree_delete(ErtsLink **root, ErtsLink *lnk)
void
erts_link_tree_foreach(ErtsLink *root,
- void (*func)(ErtsLink *, void *),
+ ErtsLinkFunc func,
void *arg)
{
ml_rbt_foreach((ErtsMonLnkNode *) root,
- (void (*)(ErtsMonLnkNode*, void*)) func,
+ (ErtsMonLnkNodeFunc) func,
arg);
}
int
erts_link_tree_foreach_yielding(ErtsLink *root,
- void (*func)(ErtsLink *, void *),
+ ErtsLinkFunc func,
void *arg,
void **vyspp,
Sint limit)
{
return ml_rbt_foreach_yielding((ErtsMonLnkNode *) root,
- (void (*)(ErtsMonLnkNode*, void*)) func,
+ (ErtsMonLnkNodeFunc) func,
arg, vyspp, limit);
}
void
erts_link_tree_foreach_delete(ErtsLink **root,
- void (*func)(ErtsLink *, void *),
+ ErtsLinkFunc func,
void *arg)
{
ml_rbt_foreach_delete((ErtsMonLnkNode **) root,
- (void (*)(ErtsMonLnkNode*, void*)) func,
+ (ErtsMonLnkNodeFunc) func,
arg);
}
int
erts_link_tree_foreach_delete_yielding(ErtsLink **root,
- void (*func)(ErtsLink *, void *),
+ ErtsLinkFunc func,
void *arg,
void **vyspp,
Sint limit)
{
return ml_rbt_foreach_delete_yielding((ErtsMonLnkNode **) root,
- (void (*)(ErtsMonLnkNode*, void*)) func,
+ (ErtsMonLnkNodeFunc) func,
arg, vyspp, limit);
}
void
erts_link_list_foreach(ErtsLink *list,
- void (*func)(ErtsLink *, void *),
+ ErtsLinkFunc func,
void *arg)
{
void *ystate = NULL;
- while (ml_dl_list_foreach_yielding((ErtsMonLnkNode *) list,
- (void (*)(ErtsMonLnkNode *, void *)) func,
- arg, &ystate, (Sint) INT_MAX));
+ while (!ml_dl_list_foreach_yielding((ErtsMonLnkNode *) list,
+ (ErtsMonLnkNodeFunc) func,
+ arg, &ystate, (Sint) INT_MAX));
}
int
erts_link_list_foreach_yielding(ErtsLink *list,
- void (*func)(ErtsLink *, void *),
+ ErtsLinkFunc func,
void *arg,
void **vyspp,
Sint limit)
{
return ml_dl_list_foreach_yielding((ErtsMonLnkNode *) list,
- (void (*)(ErtsMonLnkNode *, void *)) func,
+ (ErtsMonLnkNodeFunc) func,
arg, vyspp, limit);
}
void
erts_link_list_foreach_delete(ErtsLink **list,
- void (*func)(ErtsLink *, void *),
+ ErtsLinkFunc func,
void *arg)
{
void *ystate = NULL;
- while (ml_dl_list_foreach_delete_yielding((ErtsMonLnkNode **) list,
- (void (*)(ErtsMonLnkNode*, void*)) func,
- arg, &ystate, (Sint) INT_MAX));
+ while (!ml_dl_list_foreach_delete_yielding((ErtsMonLnkNode **) list,
+ (ErtsMonLnkNodeFunc) func,
+ arg, &ystate, (Sint) INT_MAX));
}
int
erts_link_list_foreach_delete_yielding(ErtsLink **list,
- void (*func)(ErtsLink *, void *),
+ int (*func)(ErtsLink *, void *, Sint),
void *arg,
void **vyspp,
Sint limit)
{
return ml_dl_list_foreach_delete_yielding((ErtsMonLnkNode **) list,
- (void (*)(ErtsMonLnkNode*, void*)) func,
+ (ErtsMonLnkNodeFunc) func,
arg, vyspp, limit);
}
diff --git a/erts/emulator/beam/erl_monitor_link.h b/erts/emulator/beam/erl_monitor_link.h
index ed7bf7d54a..eff861fce8 100644
--- a/erts/emulator/beam/erl_monitor_link.h
+++ b/erts/emulator/beam/erl_monitor_link.h
@@ -439,6 +439,7 @@
(ERTS_ML_FLG_EXTENDED|ERTS_ML_FLG_NAME)
typedef struct ErtsMonLnkNode__ ErtsMonLnkNode;
+typedef int (*ErtsMonLnkNodeFunc)(ErtsMonLnkNode *, void *, Sint);
typedef struct {
UWord parent; /* Parent ptr and flags... */
@@ -622,6 +623,7 @@ erts_ml_dl_list_last__(ErtsMonLnkNode *list)
typedef struct ErtsMonLnkNode__ ErtsMonitor;
+typedef int (*ErtsMonitorFunc)(ErtsMonitor *, void *, Sint);
typedef struct {
ErtsMonitor origin;
@@ -653,6 +655,7 @@ struct ErtsMonitorDataExtended__ {
typedef struct ErtsMonitorSuspend__ ErtsMonitorSuspend;
+
struct ErtsMonitorSuspend__ {
ErtsMonitorData md; /* origin = suspender; target = suspendee */
ErtsMonitorSuspend *next;
@@ -685,7 +688,7 @@ ErtsMonitor *erts_monitor_tree_lookup(ErtsMonitor *root, Eterm key);
*
* @brief Lookup or insert a monitor in a monitor tree
*
- * When the funcion is called it is assumed that:
+ * When the function is called it is assumed that:
* - 'mon' monitor is not part of any tree or list
* If the above is not true, bad things will happen.
*
@@ -711,7 +714,7 @@ ErtsMonitor *erts_monotor_tree_lookup_insert(ErtsMonitor **root,
* If it is not found, creates a monitor and returns a pointer to the
* origin monitor.
*
- * When the funcion is called it is assumed that:
+ * When the function is called it is assumed that:
* - no target monitors with the key 'target' exists in the tree.
* If the above is not true, bad things will happen.
*
@@ -738,7 +741,7 @@ ErtsMonitor *erts_monitor_tree_lookup_create(ErtsMonitor **root, int *created,
*
* @brief Insert a monitor in a monitor tree
*
- * When the funcion is called it is assumed that:
+ * When the function is called it is assumed that:
* - no monitors with the same key that 'mon' exist in the tree
* - 'mon' is not part of any list of tree
* If the above are not true, bad things will happen.
@@ -754,7 +757,7 @@ void erts_monitor_tree_insert(ErtsMonitor **root, ErtsMonitor *mon);
*
* @brief Replace a monitor in a monitor tree
*
- * When the funcion is called it is assumed that:
+ * When the function is called it is assumed that:
* - 'old' monitor and 'new' monitor have exactly the same key
* - 'old' monitor is part of the tree
* - 'new' monitor is not part of any tree or list
@@ -774,7 +777,7 @@ void erts_monitor_tree_replace(ErtsMonitor **root, ErtsMonitor *old,
*
* @brief Delete a monitor from a monitor tree
*
- * When the funcion is called it is assumed that:
+ * When the function is called it is assumed that:
* - 'mon' monitor is part of the tree
* If the above is not true, bad things will happen.
*
@@ -789,7 +792,7 @@ void erts_monitor_tree_delete(ErtsMonitor **root, ErtsMonitor *mon);
*
* @brief Call a function for each monitor in a monitor tree
*
- * The funcion 'func' will be called with a pointer to a monitor
+ * The function 'func' will be called with a pointer to a monitor
* as first argument and 'arg' as second argument for each monitor
* in the tree referred to by 'root'.
*
@@ -802,7 +805,7 @@ void erts_monitor_tree_delete(ErtsMonitor **root, ErtsMonitor *mon);
*
*/
void erts_monitor_tree_foreach(ErtsMonitor *root,
- void (*func)(ErtsMonitor *, void *),
+ ErtsMonitorFunc func,
void *arg);
/**
@@ -810,9 +813,10 @@ void erts_monitor_tree_foreach(ErtsMonitor *root,
* @brief Call a function for each monitor in a monitor tree. Yield
* if lots of monitors exist.
*
- * The funcion 'func' will be called with a pointer to a monitor
+ * The function 'func' will be called with a pointer to a monitor
* as first argument and 'arg' as second argument for each monitor
- * in the tree referred to by 'root'.
+ * in the tree referred to by 'root'. It should return the number of
+ * reductions the operator took to perform.
*
* It is assumed that:
* - *yspp equals NULL on first call
@@ -835,27 +839,28 @@ void erts_monitor_tree_foreach(ErtsMonitor *root,
* *yspp should be NULL. When done *yspp
* will be NULL.
*
- * @param[in] limit Maximum amount of monitors to process
- * before yielding.
+ * @param[in] reds Reductions available to execute before yielding.
*
- * @returns A non-zero value when all monitors has been
- * processed, and zero when more work is needed.
+ * @returns The unconsumed reductions when all monitors
+ * have been processed, and zero when more work
+ * is needed.
*
*/
int erts_monitor_tree_foreach_yielding(ErtsMonitor *root,
- void (*func)(ErtsMonitor *, void *),
+ ErtsMonitorFunc func,
void *arg,
void **vyspp,
- Sint limit);
+ Sint reds);
/**
*
* @brief Delete all monitors from a monitor tree and call a function for
* each monitor
*
- * The funcion 'func' will be called with a pointer to a monitor
+ * The function 'func' will be called with a pointer to a monitor
* as first argument and 'arg' as second argument for each monitor
- * in the tree referred to by 'root'.
+ * in the tree referred to by 'root'. It should return the number of
+ * reductions the operator took to perform.
*
* @param[in,out] root Pointer to pointer to root of monitor tree
*
@@ -866,7 +871,7 @@ int erts_monitor_tree_foreach_yielding(ErtsMonitor *root,
*
*/
void erts_monitor_tree_foreach_delete(ErtsMonitor **root,
- void (*func)(ErtsMonitor *, void *),
+ ErtsMonitorFunc func,
void *arg);
/**
@@ -874,9 +879,10 @@ void erts_monitor_tree_foreach_delete(ErtsMonitor **root,
* @brief Delete all monitors from a monitor tree and call a function for
* each monitor
*
- * The funcion 'func' will be called with a pointer to a monitor
+ * The function 'func' will be called with a pointer to a monitor
* as first argument and 'arg' as second argument for each monitor
- * in the tree referred to by 'root'.
+ * in the tree referred to by 'root'. It should return the number of
+ * reductions the operator took to perform.
*
* It is assumed that:
* - *yspp equals NULL on first call
@@ -899,18 +905,18 @@ void erts_monitor_tree_foreach_delete(ErtsMonitor **root,
* *yspp should be NULL. When done *yspp
* will be NULL.
*
- * @param[in] limit Maximum amount of monitors to process
- * before yielding.
+ * @param[in] reds Reductions available to execute before yielding.
*
- * @returns A non-zero value when all monitors has been
- * processed, and zero when more work is needed.
+ * @returns The unconsumed reductions when all monitors
+ * have been processed, and zero when more work
+ * is needed.
*
*/
int erts_monitor_tree_foreach_delete_yielding(ErtsMonitor **root,
- void (*func)(ErtsMonitor *, void *),
+ ErtsMonitorFunc func,
void *arg,
void **vyspp,
- Sint limit);
+ Sint reds);
/*
* --- Monitor list operations --
@@ -920,7 +926,7 @@ int erts_monitor_tree_foreach_delete_yielding(ErtsMonitor **root,
*
* @brief Insert a monitor in a monitor list
*
- * When the funcion is called it is assumed that:
+ * When the function is called it is assumed that:
* - 'mon' monitor is not part of any list or tree
* If the above is not true, bad things will happen.
*
@@ -935,7 +941,7 @@ ERTS_GLB_INLINE void erts_monitor_list_insert(ErtsMonitor **list, ErtsMonitor *m
*
* @brief Delete a monitor from a monitor list
*
- * When the funcion is called it is assumed that:
+ * When the function is called it is assumed that:
* - 'mon' monitor is part of the list
* If the above is not true, bad things will happen.
*
@@ -980,7 +986,7 @@ ERTS_GLB_INLINE ErtsMonitor *erts_monitor_list_last(ErtsMonitor *list);
*
* @brief Call a function for each monitor in a monitor list
*
- * The funcion 'func' will be called with a pointer to a monitor
+ * The function 'func' will be called with a pointer to a monitor
* as first argument and 'arg' as second argument for each monitor
* in the tree referred to by 'list'.
*
@@ -993,7 +999,7 @@ ERTS_GLB_INLINE ErtsMonitor *erts_monitor_list_last(ErtsMonitor *list);
*
*/
void erts_monitor_list_foreach(ErtsMonitor *list,
- void (*func)(ErtsMonitor *, void *),
+ ErtsMonitorFunc func,
void *arg);
/**
@@ -1001,9 +1007,10 @@ void erts_monitor_list_foreach(ErtsMonitor *list,
* @brief Call a function for each monitor in a monitor list. Yield
* if lots of monitors exist.
*
- * The funcion 'func' will be called with a pointer to a monitor
+ * The function 'func' will be called with a pointer to a monitor
* as first argument and 'arg' as second argument for each monitor
- * in the tree referred to by 'root'.
+ * in the tree referred to by 'root'. It should return the number of
+ * reductions the operator took to perform.
*
* It is assumed that:
* - *yspp equals NULL on first call
@@ -1026,25 +1033,25 @@ void erts_monitor_list_foreach(ErtsMonitor *list,
* *yspp should be NULL. When done *yspp
* will be NULL.
*
- * @param[in] limit Maximum amount of monitors to process
- * before yielding.
+ * @param[in] reds Reductions available to execute before yielding.
*
- * @returns A non-zero value when all monitors has been
- * processed, and zero when more work is needed.
+ * @returns The unconsumed reductions when all monitors
+ * have been processed, and zero when more work
+ * is needed.
*
*/
int erts_monitor_list_foreach_yielding(ErtsMonitor *list,
- void (*func)(ErtsMonitor *, void *),
+ ErtsMonitorFunc func,
void *arg,
void **vyspp,
- Sint limit);
+ Sint reds);
/**
*
* @brief Delete all monitors from a monitor list and call a function for
* each monitor
*
- * The funcion 'func' will be called with a pointer to a monitor
+ * The function 'func' will be called with a pointer to a monitor
* as first argument and 'arg' as second argument for each monitor
* in the tree referred to by 'root'.
*
@@ -1057,7 +1064,7 @@ int erts_monitor_list_foreach_yielding(ErtsMonitor *list,
*
*/
void erts_monitor_list_foreach_delete(ErtsMonitor **list,
- void (*func)(ErtsMonitor *, void *),
+ ErtsMonitorFunc func,
void *arg);
/**
@@ -1065,9 +1072,10 @@ void erts_monitor_list_foreach_delete(ErtsMonitor **list,
* @brief Delete all monitors from a monitor list and call a function for
* each monitor
*
- * The funcion 'func' will be called with a pointer to a monitor
+ * The function 'func' will be called with a pointer to a monitor
* as first argument and 'arg' as second argument for each monitor
- * in the tree referred to by 'root'.
+ * in the tree referred to by 'root'. It should return the number of
+ * reductions the operator took to perform.
*
* It is assumed that:
* - *yspp equals NULL on first call
@@ -1090,18 +1098,18 @@ void erts_monitor_list_foreach_delete(ErtsMonitor **list,
* *yspp should be NULL. When done *yspp
* will be NULL.
*
- * @param[in] limit Maximum amount of monitors to process
- * before yielding.
+ * @param[in] reds Reductions available to execute before yielding.
*
- * @returns A non-zero value when all monitors has been
- * processed, and zero when more work is needed.
+ * @returns The unconsumed reductions when all monitors
+ * have been processed, and zero when more work
+ * is needed.
*
*/
int erts_monitor_list_foreach_delete_yielding(ErtsMonitor **list,
- void (*func)(ErtsMonitor *, void *),
+ ErtsMonitorFunc func,
void *arg,
void **vyspp,
- Sint limit);
+ Sint reds);
/*
* --- Misc monitor operations ---
@@ -1113,7 +1121,7 @@ int erts_monitor_list_foreach_delete_yielding(ErtsMonitor **list,
*
* Can create all types of monitors
*
- * When the funcion is called it is assumed that:
+ * When the function is called it is assumed that:
* - 'ref' is an internal ordinary reference if type is ERTS_MON_TYPE_PROC,
* ERTS_MON_TYPE_PORT, ERTS_MON_TYPE_TIME_OFFSET, or ERTS_MON_TYPE_RESOURCE
* - 'ref' is NIL if type is ERTS_MON_TYPE_NODE, ERTS_MON_TYPE_NODES, or
@@ -1199,7 +1207,7 @@ ERTS_GLB_INLINE int erts_monitor_is_in_table(ErtsMonitor *mon);
* When both the origin and the target part of the monitor have
* been released the monitor structure will be deallocated.
*
- * When the funcion is called it is assumed that:
+ * When the function is called it is assumed that:
* - 'mon' monitor is not part of any list or tree
* - 'mon' is not referred to by any other structures
* If the above are not true, bad things will happen.
@@ -1216,7 +1224,7 @@ ERTS_GLB_INLINE void erts_monitor_release(ErtsMonitor *mon);
* Release both the origin and target parts of the monitor
* simultaneously and deallocate the structure.
*
- * When the funcion is called it is assumed that:
+ * When the function is called it is assumed that:
* - Neither the origin part nor the target part of the monitor
* are not part of any list or tree
* - Neither the origin part nor the target part of the monitor
@@ -1232,7 +1240,7 @@ ERTS_GLB_INLINE void erts_monitor_release_both(ErtsMonitorData *mdp);
*
* @brief Insert monitor in dist monitor tree or list
*
- * When the funcion is called it is assumed that:
+ * When the function is called it is assumed that:
* - 'mon' monitor is not part of any list or tree
* If the above is not true, bad things will happen.
*
@@ -1253,7 +1261,7 @@ ERTS_GLB_INLINE int erts_monitor_dist_insert(ErtsMonitor *mon, ErtsMonLnkDist *d
*
* @brief Delete monitor from dist monitor tree or list
*
- * When the funcion is called it is assumed that:
+ * When the function is called it is assumed that:
* - 'mon' monitor earler has been inserted into 'dist'
* If the above is not true, bad things will happen.
*
@@ -1291,7 +1299,7 @@ erts_monitor_set_dead_dist(ErtsMonitor *mon, Eterm nodename);
* whole size of the monitor data structure is returned; otherwise,
* half of the size is returned.
*
- * When the funcion is called it is assumed that:
+ * When the function is called it is assumed that:
* - 'mon' has not been released
* If the above is not true, bad things will happen.
*
@@ -1507,6 +1515,8 @@ ERTS_GLB_INLINE ErtsMonitorSuspend *erts_monitor_suspend(ErtsMonitor *mon)
typedef struct ErtsMonLnkNode__ ErtsLink;
+typedef int (*ErtsLinkFunc)(ErtsLink *, void *, Sint);
+
typedef struct {
ErtsLink a;
ErtsLink b;
@@ -1544,7 +1554,7 @@ ErtsLink *erts_link_tree_lookup(ErtsLink *root, Eterm item);
*
* @brief Lookup or insert a link in a link tree
*
- * When the funcion is called it is assumed that:
+ * When the function is called it is assumed that:
* - 'lnk' link is not part of any tree or list
* If the above is not true, bad things will happen.
*
@@ -1590,7 +1600,7 @@ ErtsLink *erts_link_tree_lookup_create(ErtsLink **root, int *created,
*
* @brief Insert a link in a link tree
*
- * When the funcion is called it is assumed that:
+ * When the function is called it is assumed that:
* - no links with the same key that 'lnk' exist in the tree
* - 'lnk' is not part of any list of tree
* If the above are not true, bad things will happen.
@@ -1606,7 +1616,7 @@ void erts_link_tree_insert(ErtsLink **root, ErtsLink *lnk);
*
* @brief Replace a link in a link tree
*
- * When the funcion is called it is assumed that:
+ * When the function is called it is assumed that:
* - 'old' link and 'new' link have exactly the same key
* - 'old' link is part of the tree
* - 'new' link is not part of any tree or list
@@ -1630,7 +1640,7 @@ void erts_link_tree_replace(ErtsLink **root, ErtsLink *old, ErtsLink *new);
* the tree and 'lnk' has a lower address than the link in the
* tree, the existing link in the tree is replaced by 'lnk'.
*
- * When the funcion is called it is assumed that:
+ * When the function is called it is assumed that:
* - 'lnk' link is not part of any tree or list
* If the above are not true, bad things will happen.
*
@@ -1649,7 +1659,7 @@ ERTS_GLB_INLINE ErtsLink *erts_link_tree_insert_addr_replace(ErtsLink **root,
*
* @brief Delete a link from a link tree
*
- * When the funcion is called it is assumed that:
+ * When the function is called it is assumed that:
* - 'lnk' link is part of the tree
* If the above is not true, bad things will happen.
*
@@ -1668,7 +1678,7 @@ void erts_link_tree_delete(ErtsLink **root, ErtsLink *lnk);
* If link 'lnk' is not in the tree, another link with the same
* key as 'lnk' is deleted from the tree if such a link exist.
*
- * When the funcion is called it is assumed that:
+ * When the function is called it is assumed that:
* - if 'lnk' link is part of a tree or list, it is part of this tree
* If the above is not true, bad things will happen.
*
@@ -1687,7 +1697,7 @@ ERTS_GLB_INLINE ErtsLink *erts_link_tree_key_delete(ErtsLink **root, ErtsLink *l
*
* @brief Call a function for each link in a link tree
*
- * The funcion 'func' will be called with a pointer to a link
+ * The function 'func' will be called with a pointer to a link
* as first argument and 'arg' as second argument for each link
* in the tree referred to by 'root'.
*
@@ -1700,7 +1710,7 @@ ERTS_GLB_INLINE ErtsLink *erts_link_tree_key_delete(ErtsLink **root, ErtsLink *l
*
*/
void erts_link_tree_foreach(ErtsLink *root,
- void (*func)(ErtsLink *, void *),
+ ErtsLinkFunc,
void *arg);
/**
@@ -1708,9 +1718,10 @@ void erts_link_tree_foreach(ErtsLink *root,
* @brief Call a function for each link in a link tree. Yield if lots
* of links exist.
*
- * The funcion 'func' will be called with a pointer to a link
+ * The function 'func' will be called with a pointer to a link
* as first argument and 'arg' as second argument for each link
- * in the tree referred to by 'root'.
+ * in the tree referred to by 'root'. It should return the number of
+ * reductions the operator took to perform.
*
* It is assumed that:
* - *yspp equals NULL on first call
@@ -1733,25 +1744,25 @@ void erts_link_tree_foreach(ErtsLink *root,
* *yspp should be NULL. When done *yspp
* will be NULL.
*
- * @param[in] limit Maximum amount of links to process
- * before yielding.
+ * @param[in] reds Reductions available to execute before yielding.
*
- * @returns A non-zero value when all links has been
- * processed, and zero when more work is needed.
+ * @returns The unconsumed reductions when all links
+ * have been processed, and zero when more work
+ * is needed.
*
*/
int erts_link_tree_foreach_yielding(ErtsLink *root,
- void (*func)(ErtsLink *, void *),
+ ErtsLinkFunc func,
void *arg,
void **vyspp,
- Sint limit);
+ Sint reds);
/**
*
* @brief Delete all links from a link tree and call a function for
* each link
*
- * The funcion 'func' will be called with a pointer to a link
+ * The function 'func' will be called with a pointer to a link
* as first argument and 'arg' as second argument for each link
* in the tree referred to by 'root'.
*
@@ -1764,7 +1775,7 @@ int erts_link_tree_foreach_yielding(ErtsLink *root,
*
*/
void erts_link_tree_foreach_delete(ErtsLink **root,
- void (*func)(ErtsLink *, void *),
+ ErtsLinkFunc func,
void *arg);
/**
@@ -1772,9 +1783,10 @@ void erts_link_tree_foreach_delete(ErtsLink **root,
* @brief Delete all links from a link tree and call a function for
* each link
*
- * The funcion 'func' will be called with a pointer to a link
+ * The function 'func' will be called with a pointer to a link
* as first argument and 'arg' as second argument for each link
- * in the tree referred to by 'root'.
+ * in the tree referred to by 'root'. It should return the number of
+ * reductions the operator took to perform.
*
* It is assumed that:
* - *yspp equals NULL on first call
@@ -1797,18 +1809,18 @@ void erts_link_tree_foreach_delete(ErtsLink **root,
* *yspp should be NULL. When done *yspp
* will be NULL.
*
- * @param[in] limit Maximum amount of links to process
- * before yielding.
+ * @param[in] reds Reductions available to execute before yielding.
*
- * @returns A non-zero value when all links has been
- * processed, and zero when more work is needed.
+ * @returns The unconsumed reductions when all links
+ * have been processed, and zero when more work
+ * is needed.
*
*/
int erts_link_tree_foreach_delete_yielding(ErtsLink **root,
- void (*func)(ErtsLink *, void *),
+ ErtsLinkFunc func,
void *arg,
void **vyspp,
- Sint limit);
+ Sint reds);
/*
* --- Link list operations ---
@@ -1818,7 +1830,7 @@ int erts_link_tree_foreach_delete_yielding(ErtsLink **root,
*
* @brief Insert a link in a link list
*
- * When the funcion is called it is assumed that:
+ * When the function is called it is assumed that:
* - 'lnk' link is not part of any list or tree
* If the above is not true, bad things will happen.
*
@@ -1833,7 +1845,7 @@ ERTS_GLB_INLINE void erts_link_list_insert(ErtsLink **list, ErtsLink *lnk);
*
* @brief Delete a link from a link list
*
- * When the funcion is called it is assumed that:
+ * When the function is called it is assumed that:
* - 'lnk' link is part of the list
* If the above is not true, bad things will happen.
*
@@ -1878,7 +1890,7 @@ ERTS_GLB_INLINE ErtsLink *erts_link_list_last(ErtsLink *list);
*
* @brief Call a function for each link in a link list
*
- * The funcion 'func' will be called with a pointer to a link
+ * The function 'func' will be called with a pointer to a link
* as first argument and 'arg' as second argument for each link
* in the tree referred to by 'list'.
*
@@ -1891,7 +1903,7 @@ ERTS_GLB_INLINE ErtsLink *erts_link_list_last(ErtsLink *list);
*
*/
void erts_link_list_foreach(ErtsLink *list,
- void (*func)(ErtsLink *, void *),
+ ErtsLinkFunc func,
void *arg);
/**
@@ -1899,9 +1911,10 @@ void erts_link_list_foreach(ErtsLink *list,
* @brief Call a function for each link in a link list. Yield
* if lots of links exist.
*
- * The funcion 'func' will be called with a pointer to a link
+ * The function 'func' will be called with a pointer to a link
* as first argument and 'arg' as second argument for each link
- * in the tree referred to by 'root'.
+ * in the tree referred to by 'root'. It should return the number of
+ * reductions the operator took to perform.
*
* It is assumed that:
* - *yspp equals NULL on first call
@@ -1924,25 +1937,25 @@ void erts_link_list_foreach(ErtsLink *list,
* *yspp should be NULL. When done *yspp
* will be NULL.
*
- * @param[in] limit Maximum amount of links to process
- * before yielding.
+ * @param[in] reds Reductions available to execute before yielding.
*
- * @returns A non-zero value when all links has been
- * processed, and zero when more work is needed.
+ * @returns The unconsumed reductions when all links
+ * have been processed, and zero when more work
+ * is needed.
*
*/
int erts_link_list_foreach_yielding(ErtsLink *list,
- void (*func)(ErtsLink *, void *),
+ ErtsLinkFunc func,
void *arg,
void **vyspp,
- Sint limit);
+ Sint reds);
/**
*
* @brief Delete all links from a link list and call a function for
* each link
*
- * The funcion 'func' will be called with a pointer to a link
+ * The function 'func' will be called with a pointer to a link
* as first argument and 'arg' as second argument for each link
* in the tree referred to by 'root'.
*
@@ -1955,7 +1968,7 @@ int erts_link_list_foreach_yielding(ErtsLink *list,
*
*/
void erts_link_list_foreach_delete(ErtsLink **list,
- void (*func)(ErtsLink *, void *),
+ ErtsLinkFunc func,
void *arg);
/**
@@ -1963,9 +1976,10 @@ void erts_link_list_foreach_delete(ErtsLink **list,
* @brief Delete all links from a link list and call a function for
* each link
*
- * The funcion 'func' will be called with a pointer to a link
+ * The function 'func' will be called with a pointer to a link
* as first argument and 'arg' as second argument for each link
- * in the tree referred to by 'root'.
+ * in the tree referred to by 'root'. It should return the number of
+ * reductions the operator took to perform.
*
* It is assumed that:
* - *yspp equals NULL on first call
@@ -1988,18 +2002,18 @@ void erts_link_list_foreach_delete(ErtsLink **list,
* *yspp should be NULL. When done *yspp
* will be NULL.
*
- * @param[in] limit Maximum amount of links to process
- * before yielding.
+ * @param[in] reds Reductions available to execute before yielding.
*
- * @returns A non-zero value when all links has been
- * processed, and zero when more work is needed.
+ * @returns The unconsumed reductions when all links
+ * have been processed, and zero when more work
+ * is needed.
*
*/
int erts_link_list_foreach_delete_yielding(ErtsLink **list,
- void (*func)(ErtsLink *, void *),
+ ErtsLinkFunc func,
void *arg,
void **vyspp,
- Sint limit);
+ Sint reds);
/*
* --- Misc link operations ---
@@ -2011,7 +2025,7 @@ int erts_link_list_foreach_delete_yielding(ErtsLink **list,
*
* Can create all types of links
*
- * When the funcion is called it is assumed that:
+ * When the function is called it is assumed that:
* - 'ref' is an internal ordinary reference if type is ERTS_MON_TYPE_PROC,
* ERTS_MON_TYPE_PORT, ERTS_MON_TYPE_TIME_OFFSET, or ERTS_MON_TYPE_RESOURCE
* - 'ref' is NIL if type is ERTS_MON_TYPE_NODE or ERTS_MON_TYPE_NODES
@@ -2081,7 +2095,7 @@ ERTS_GLB_INLINE int erts_link_is_in_table(ErtsLink *lnk);
* When both link halves part of the link have been released the link
* structure will be deallocated.
*
- * When the funcion is called it is assumed that:
+ * When the function is called it is assumed that:
* - 'lnk' link is not part of any list or tree
* - 'lnk' is not referred to by any other structures
* If the above are not true, bad things will happen.
@@ -2098,7 +2112,7 @@ ERTS_GLB_INLINE void erts_link_release(ErtsLink *lnk);
* Release both halves of a link simultaneously and deallocate
* the structure.
*
- * When the funcion is called it is assumed that:
+ * When the function is called it is assumed that:
* - Neither of the parts of the link are part of any list or tree
* - Neither of the parts of the link or the link data structure
* are referred to by any other structures
@@ -2113,7 +2127,7 @@ ERTS_GLB_INLINE void erts_link_release_both(ErtsLinkData *ldp);
*
* @brief Insert link in dist link list
*
- * When the funcion is called it is assumed that:
+ * When the function is called it is assumed that:
* - 'lnk' link is not part of any list or tree
* If the above is not true, bad things will happen.
*
@@ -2134,7 +2148,7 @@ ERTS_GLB_INLINE int erts_link_dist_insert(ErtsLink *lnk, ErtsMonLnkDist *dist);
*
* @brief Delete link from dist link list
*
- * When the funcion is called it is assumed that:
+ * When the function is called it is assumed that:
* - 'lnk' link earler has been inserted into 'dist'
* If the above is not true, bad things will happen.
*
@@ -2172,7 +2186,7 @@ erts_link_set_dead_dist(ErtsLink *lnk, Eterm nodename);
* whole size of the link data structure is returned; otherwise,
* half of the size is returned.
*
- * When the funcion is called it is assumed that:
+ * When the function is called it is assumed that:
* - 'lnk' has not been released
* If the above is not true, bad things will happen.
*
diff --git a/erts/emulator/beam/erl_nif.c b/erts/emulator/beam/erl_nif.c
index ee6e6085b6..349d9bf13a 100644
--- a/erts/emulator/beam/erl_nif.c
+++ b/erts/emulator/beam/erl_nif.c
@@ -707,6 +707,29 @@ error:
return reds;
}
+/** @brief Create a message with the content of process independent \c msg_env.
+ * Invalidates \c msg_env.
+ */
+ErtsMessage* erts_create_message_from_nif_env(ErlNifEnv* msg_env)
+{
+ struct enif_msg_environment_t* menv = (struct enif_msg_environment_t*)msg_env;
+ ErtsMessage* mp;
+
+ flush_env(msg_env);
+ mp = erts_alloc_message(0, NULL);
+ mp->data.heap_frag = menv->env.heap_frag;
+ ASSERT(mp->data.heap_frag == MBUF(&menv->phony_proc));
+ if (mp->data.heap_frag != NULL) {
+ /* Move all offheap's from phony proc to the first fragment.
+ Quick and dirty... */
+ ASSERT(!is_offheap(&mp->data.heap_frag->off_heap));
+ mp->data.heap_frag->off_heap = MSO(&menv->phony_proc);
+ clear_offheap(&MSO(&menv->phony_proc));
+ menv->env.heap_frag = NULL;
+ MBUF(&menv->phony_proc) = NULL;
+ }
+ return mp;
+}
int enif_send(ErlNifEnv* env, const ErlNifPid* to_pid,
ErlNifEnv* msg_env, ERL_NIF_TERM msg)
@@ -803,20 +826,8 @@ int enif_send(ErlNifEnv* env, const ErlNifPid* to_pid,
}
#endif
}
- flush_env(msg_env);
- mp = erts_alloc_message(0, NULL);
+ mp = erts_create_message_from_nif_env(msg_env);
ERL_MESSAGE_TOKEN(mp) = token;
- mp->data.heap_frag = menv->env.heap_frag;
- ASSERT(mp->data.heap_frag == MBUF(&menv->phony_proc));
- if (mp->data.heap_frag != NULL) {
- /* Move all offheap's from phony proc to the first fragment.
- Quick and dirty... */
- ASSERT(!is_offheap(&mp->data.heap_frag->off_heap));
- mp->data.heap_frag->off_heap = MSO(&menv->phony_proc);
- clear_offheap(&MSO(&menv->phony_proc));
- menv->env.heap_frag = NULL;
- MBUF(&menv->phony_proc) = NULL;
- }
} else {
erts_literal_area_t litarea;
ErlOffHeap *ohp;
@@ -1040,7 +1051,7 @@ ERL_NIF_TERM enif_make_copy(ErlNifEnv* dst_env, ERL_NIF_TERM src_term)
Eterm* hp;
/*
* No preserved sharing allowed as long as literals are also preserved.
- * Process independent environment can not be reached by purge.
+ * Process independent environment cannot be reached by purge.
*/
sz = size_object(src_term);
hp = alloc_heap(dst_env, sz);
@@ -1072,6 +1083,17 @@ int enif_get_local_pid(ErlNifEnv* env, ERL_NIF_TERM term, ErlNifPid* pid)
return 0;
}
+void enif_set_pid_undefined(ErlNifPid* pid)
+{
+ pid->pid = am_undefined;
+}
+
+int enif_is_pid_undefined(const ErlNifPid* pid)
+{
+ ASSERT(pid->pid == am_undefined || is_internal_pid(pid->pid));
+ return pid->pid == am_undefined;
+}
+
int enif_get_local_port(ErlNifEnv* env, ERL_NIF_TERM term, ErlNifPort* port)
{
if (is_internal_port(term)) {
@@ -2346,14 +2368,22 @@ rmon_refc_read(ErtsResourceMonitors *rms)
return rms->refc & ERTS_RESOURCE_REFC_MASK;
}
-static void dtor_demonitor(ErtsMonitor* mon, void* context)
+static int dtor_demonitor(ErtsMonitor* mon, void* context, Sint reds)
{
ASSERT(erts_monitor_is_origin(mon));
ASSERT(is_internal_pid(mon->other.item));
erts_proc_sig_send_demonitor(mon);
+ return 1;
}
+#ifdef DEBUG
+int erts_dbg_is_resource_dying(ErtsResource* resource)
+{
+ return resource->monitors && rmon_is_dying(resource->monitors);
+}
+#endif
+
# define NIF_RESOURCE_DTOR &nif_resource_dtor
static int nif_resource_dtor(Binary* bin)
@@ -2694,8 +2724,12 @@ int enif_consume_timeslice(ErlNifEnv* env, int percent)
{
Process *proc;
Sint reds;
+ int sched;
- execution_state(env, &proc, NULL);
+ execution_state(env, &proc, &sched);
+
+ if (sched < 0)
+ return 0; /* no-op on dirty scheduler */
ASSERT(is_proc_bound(env) && percent >= 1 && percent <= 100);
if (percent < 1) percent = 1;
@@ -3318,6 +3352,9 @@ int enif_monitor_process(ErlNifEnv* env, void* obj, const ErlNifPid* target_pid,
}
ASSERT(rsrc->type->down);
+ if (target_pid->pid == am_undefined)
+ return 1;
+
ref = erts_make_ref_in_buffer(tmp);
mdp = erts_monitor_create(ERTS_MON_TYPE_RESOURCE, ref,
@@ -3351,6 +3388,12 @@ int enif_monitor_process(ErlNifEnv* env, void* obj, const ErlNifPid* target_pid,
return 0;
}
+ERL_NIF_TERM enif_make_monitor_term(ErlNifEnv* env, const ErlNifMonitor* monitor)
+{
+ Eterm* hp = alloc_heap(env, ERTS_REF_THING_SIZE);
+ return erts_driver_monitor_to_ref(hp, monitor);
+}
+
int enif_demonitor_process(ErlNifEnv* env, void* obj, const ErlNifMonitor* monitor)
{
ErtsResource* rsrc = DATA_TO_RESOURCE(obj);
diff --git a/erts/emulator/beam/erl_nif.h b/erts/emulator/beam/erl_nif.h
index 4c09496ef1..3fd1a8fd4c 100644
--- a/erts/emulator/beam/erl_nif.h
+++ b/erts/emulator/beam/erl_nif.h
@@ -54,10 +54,16 @@
** 2.13: 20.1 add enif_ioq
** 2.14: 21.0 add enif_ioq_peek_head, enif_(mutex|cond|rwlock|thread)_name
** enif_vfprintf, enif_vsnprintf, enif_make_map_from_arrays
+** 2.15: 22.0 ERL_NIF_SELECT_CANCEL, enif_select_(read|write)
*/
#define ERL_NIF_MAJOR_VERSION 2
-#define ERL_NIF_MINOR_VERSION 14
-#define ERL_NIF_MIN_ERTS_VERSION "erts-10.0 (OTP-21)"
+#define ERL_NIF_MINOR_VERSION 15
+/*
+ * WHEN CHANGING INTERFACE VERSION, also replace erts version below
+ * with ticket syntax like "erts-@OTP-12345@", or a temporary placeholder
+ * between two @ like "erts-@MyName@", if you don't know what a ticket is.
+ */
+#define ERL_NIF_MIN_ERTS_VERSION "erts-@OTP-15095@ (OTP-22)"
/*
* The emulator will refuse to load a nif-lib with a major version
@@ -160,6 +166,8 @@ typedef int ErlNifEvent;
#define ERL_NIF_SELECT_STOP_SCHEDULED (1 << 1)
#define ERL_NIF_SELECT_INVALID_EVENT (1 << 2)
#define ERL_NIF_SELECT_FAILED (1 << 3)
+#define ERL_NIF_SELECT_READ_CANCELLED (1 << 4)
+#define ERL_NIF_SELECT_WRITE_CANCELLED (1 << 5)
typedef enum
{
diff --git a/erts/emulator/beam/erl_nif_api_funcs.h b/erts/emulator/beam/erl_nif_api_funcs.h
index 81f64f2390..8ab454c8dd 100644
--- a/erts/emulator/beam/erl_nif_api_funcs.h
+++ b/erts/emulator/beam/erl_nif_api_funcs.h
@@ -210,6 +210,11 @@ ERL_NIF_API_FUNC_DECL(int,enif_vsnprintf,(char*, size_t, const char *fmt, va_lis
ERL_NIF_API_FUNC_DECL(int,enif_make_map_from_arrays,(ErlNifEnv *env, ERL_NIF_TERM keys[], ERL_NIF_TERM values[], size_t cnt, ERL_NIF_TERM *map_out));
+ERL_NIF_API_FUNC_DECL(int,enif_select_x,(ErlNifEnv* env, ErlNifEvent e, enum ErlNifSelectFlags flags, void* obj, const ErlNifPid* pid, ERL_NIF_TERM msg, ErlNifEnv* msg_env));
+ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_make_monitor_term,(ErlNifEnv* env, const ErlNifMonitor*));
+ERL_NIF_API_FUNC_DECL(void,enif_set_pid_undefined,(ErlNifPid* pid));
+ERL_NIF_API_FUNC_DECL(int,enif_is_pid_undefined,(const ErlNifPid* pid));
+
/*
** ADD NEW ENTRIES HERE (before this comment) !!!
*/
@@ -392,6 +397,10 @@ ERL_NIF_API_FUNC_DECL(int,enif_make_map_from_arrays,(ErlNifEnv *env, ERL_NIF_TER
# define enif_vfprintf ERL_NIF_API_FUNC_MACRO(enif_vfprintf)
# define enif_vsnprintf ERL_NIF_API_FUNC_MACRO(enif_vsnprintf)
# define enif_make_map_from_arrays ERL_NIF_API_FUNC_MACRO(enif_make_map_from_arrays)
+# define enif_select_x ERL_NIF_API_FUNC_MACRO(enif_select_x)
+# define enif_make_monitor_term ERL_NIF_API_FUNC_MACRO(enif_make_monitor_term)
+# define enif_set_pid_undefined ERL_NIF_API_FUNC_MACRO(enif_set_pid_undefined)
+# define enif_is_pid_undefined ERL_NIF_API_FUNC_MACRO(enif_is_pid_undefined)
/*
** ADD NEW ENTRIES HERE (before this comment)
@@ -623,6 +632,13 @@ static ERL_NIF_INLINE ERL_NIF_TERM enif_make_list9(ErlNifEnv* env,
#ifndef enif_make_pid
# define enif_make_pid(ENV, PID) ((void)(ENV),(const ERL_NIF_TERM)((PID)->pid))
+# define enif_compare_pids(A, B) (enif_compare((A)->pid,(B)->pid))
+# define enif_select_read(ENV, E, OBJ, PID, MSG, MSG_ENV) \
+ enif_select_x(ENV, E, ERL_NIF_SELECT_READ | ERL_NIF_SELECT_CUSTOM_MSG, \
+ OBJ, PID, MSG, MSG_ENV)
+# define enif_select_write(ENV, E, OBJ, PID, MSG, MSG_ENV) \
+ enif_select_x(ENV, E, ERL_NIF_SELECT_WRITE | ERL_NIF_SELECT_CUSTOM_MSG, \
+ OBJ, PID, MSG, MSG_ENV)
#if SIZEOF_LONG == 8
# define enif_get_int64 enif_get_long
diff --git a/erts/emulator/beam/erl_node_tables.c b/erts/emulator/beam/erl_node_tables.c
index 18ed782ae3..afafaf48dc 100644
--- a/erts/emulator/beam/erl_node_tables.c
+++ b/erts/emulator/beam/erl_node_tables.c
@@ -201,6 +201,7 @@ dist_table_alloc(void *dep_tmpl)
dep->send = NULL;
dep->cache = NULL;
dep->transcode_ctx = NULL;
+ dep->sequences = NULL;
/* Link in */
@@ -801,8 +802,9 @@ node_table_hash(void *venp)
static int
node_table_cmp(void *venp1, void *venp2)
{
- return ((((ErlNode *) venp1)->sysname == ((ErlNode *) venp2)->sysname
- && ((ErlNode *) venp1)->creation == ((ErlNode *) venp2)->creation)
+ return ((((ErlNode *) venp1)->sysname == ((ErlNode *) venp2)->sysname) &&
+ ((((ErlNode *) venp1)->creation == ((ErlNode *) venp2)->creation) ||
+ (((ErlNode *) venp1)->creation == 0 || ((ErlNode *) venp2)->creation == 0))
? 0
: 1);
}
@@ -816,11 +818,16 @@ node_table_alloc(void *venp_tmpl)
node_entries++;
- erts_refc_init(&enp->refc, -1);
+ erts_init_node_entry(enp, -1);
enp->creation = ((ErlNode *) venp_tmpl)->creation;
enp->sysname = ((ErlNode *) venp_tmpl)->sysname;
enp->dist_entry = erts_find_or_insert_dist_entry(((ErlNode *) venp_tmpl)->sysname);
+#ifdef ERL_NODE_BOOKKEEP
+ erts_atomic_init_nob(&enp->slot, 0);
+ sys_memzero(enp->books, sizeof(struct erl_node_bookkeeping) * 1024);
+#endif
+
return (void *) enp;
}
@@ -873,7 +880,7 @@ erts_node_table_info(fmtfn_t to, void *to_arg)
}
-ErlNode *erts_find_or_insert_node(Eterm sysname, Uint32 creation)
+ErlNode *erts_find_or_insert_node(Eterm sysname, Uint32 creation, Eterm book)
{
ErlNode *res;
ErlNode ne;
@@ -883,9 +890,9 @@ ErlNode *erts_find_or_insert_node(Eterm sysname, Uint32 creation)
erts_rwmtx_rlock(&erts_node_table_rwmtx);
res = hash_get(&erts_node_table, (void *) &ne);
if (res && res != erts_this_node) {
- erts_aint_t refc = erts_refc_inctest(&res->refc, 0);
+ erts_aint_t refc = erts_ref_node_entry(res, 0, book);
if (refc < 2) /* New or pending delete */
- erts_refc_inc(&res->refc, 1);
+ erts_ref_node_entry(res, 1, THE_NON_VALUE);
}
erts_rwmtx_runlock(&erts_node_table_rwmtx);
if (res)
@@ -895,9 +902,9 @@ ErlNode *erts_find_or_insert_node(Eterm sysname, Uint32 creation)
res = hash_put(&erts_node_table, (void *) &ne);
ASSERT(res);
if (res != erts_this_node) {
- erts_aint_t refc = erts_refc_inctest(&res->refc, 0);
+ erts_aint_t refc = erts_ref_node_entry(res, 0, book);
if (refc < 2) /* New or pending delete */
- erts_refc_inc(&res->refc, 1);
+ erts_ref_node_entry(res, 1, THE_NON_VALUE);
}
erts_rwmtx_rwunlock(&erts_node_table_rwmtx);
return res;
@@ -924,6 +931,7 @@ static void try_delete_node(void *venp)
*
* If refc > 0, the entry is in use. Keep the entry.
*/
+ erts_node_bookkeep(enp, THE_NON_VALUE, ERL_NODE_DEC);
refc = erts_refc_dectest(&enp->refc, -1);
if (refc == -1)
(void) hash_erase(&erts_node_table, (void *) enp);
@@ -1021,7 +1029,7 @@ erts_set_this_node(Eterm sysname, Uint creation)
erts_deref_dist_entry(erts_this_dist_entry);
erts_this_node = NULL; /* to make sure refc is bumped for this node */
- erts_this_node = erts_find_or_insert_node(sysname, creation);
+ erts_this_node = erts_find_or_insert_node(sysname, creation, THE_NON_VALUE);
erts_this_dist_entry = erts_this_node->dist_entry;
erts_ref_dist_entry(erts_this_dist_entry);
@@ -1090,7 +1098,7 @@ void erts_init_node_tables(int dd_sec)
node_tmpl.creation = 0;
erts_this_node = hash_put(&erts_node_table, &node_tmpl);
/* +1 for erts_this_node */
- erts_refc_init(&erts_this_node->refc, 1);
+ erts_init_node_entry(erts_this_node, 1);
ASSERT(erts_this_node->dist_entry != NULL);
erts_this_dist_entry = erts_this_node->dist_entry;
@@ -1177,6 +1185,7 @@ static Eterm AM_system;
static Eterm AM_timer;
static Eterm AM_delayed_delete_timer;
static Eterm AM_thread_progress_delete_timer;
+static Eterm AM_sequence;
static Eterm AM_signal;
static void setup_reference_table(void);
@@ -1218,6 +1227,7 @@ typedef struct dist_referrer_ {
int ctrl_ref;
int system_ref;
int signal_ref;
+ int sequence_ref;
Eterm id;
Uint creation;
Uint id_heap[ID_HEAP_SIZE];
@@ -1272,6 +1282,7 @@ erts_get_node_and_dist_references(struct process *proc)
INIT_AM(delayed_delete_timer);
INIT_AM(thread_progress_delete_timer);
INIT_AM(signal);
+ INIT_AM(sequence);
references_atoms_need_init = 0;
}
@@ -1309,8 +1320,9 @@ erts_get_node_and_dist_references(struct process *proc)
#define TIMER_REF 8
#define SYSTEM_REF 9
#define SIGNAL_REF 10
+#define SEQUENCE_REF 11
-#define INC_TAB_SZ 10
+#define INC_TAB_SZ 11
static void
insert_dist_referrer(ReferredDist *referred_dist,
@@ -1344,6 +1356,7 @@ insert_dist_referrer(ReferredDist *referred_dist,
drp->ctrl_ref = 0;
drp->system_ref = 0;
drp->signal_ref = 0;
+ drp->sequence_ref = 0;
}
switch (type) {
@@ -1353,6 +1366,7 @@ insert_dist_referrer(ReferredDist *referred_dist,
case ETS_REF: drp->ets_ref++; break;
case SYSTEM_REF: drp->system_ref++; break;
case SIGNAL_REF: drp->signal_ref++; break;
+ case SEQUENCE_REF: drp->sequence_ref++; break;
default: ASSERT(0);
}
}
@@ -1509,7 +1523,7 @@ insert_offheap(ErlOffHeap *oh, int type, Eterm id)
}
}
else if (IsSendCtxBinary(u.mref->mb)) {
- ErtsSendContext* ctx = ERTS_MAGIC_BIN_DATA(u.mref->mb);
+ ErtsDSigSendContext* ctx = ERTS_MAGIC_BIN_DATA(u.mref->mb);
if (ctx->deref_dep)
insert_dist_entry(ctx->dep, type, id, 0);
}
@@ -1544,16 +1558,18 @@ static void insert_monitor_data(ErtsMonitor *mon, int type, Eterm id)
mdp->origin.flags |= ERTS_ML_FLG_DBG_VISITED;
}
-static void insert_monitor(ErtsMonitor *mon, void *idp)
+static int insert_monitor(ErtsMonitor *mon, void *idp, Sint reds)
{
Eterm id = *((Eterm *) idp);
insert_monitor_data(mon, MONITOR_REF, id);
+ return 1;
}
-static void clear_visited_monitor(ErtsMonitor *mon, void *p)
+static int clear_visited_monitor(ErtsMonitor *mon, void *p, Sint reds)
{
ErtsMonitorData *mdp = erts_monitor_to_data(mon);
mdp->origin.flags &= ~ERTS_ML_FLG_DBG_VISITED;
+ return 1;
}
static void
@@ -1581,6 +1597,20 @@ insert_dist_monitors(DistEntry *dep)
}
}
+
+static int
+insert_sequence(ErtsDistExternal *edep, void *arg, Sint reds)
+{
+ insert_dist_entry(edep->dep, SEQUENCE_REF, *(Eterm*)arg, 0);
+ return 1;
+}
+
+static void
+insert_dist_sequences(DistEntry *dep)
+{
+ erts_dist_seq_tree_foreach(dep, insert_sequence, (void *) &dep->sysname);
+}
+
static void
clear_visited_p_monitors(ErtsPTabElementCommon *p)
{
@@ -1621,16 +1651,18 @@ static void insert_link_data(ErtsLink *lnk, int type, Eterm id)
ldp->a.flags |= ERTS_ML_FLG_DBG_VISITED;
}
-static void insert_link(ErtsLink *lnk, void *idp)
+static int insert_link(ErtsLink *lnk, void *idp, Sint reds)
{
Eterm id = *((Eterm *) idp);
insert_link_data(lnk, LINK_REF, id);
+ return 1;
}
-static void clear_visited_link(ErtsLink *lnk, void *p)
+static int clear_visited_link(ErtsLink *lnk, void *p, Sint reds)
{
ErtsLinkData *ldp = erts_link_to_data(lnk);
ldp->a.flags &= ~ERTS_ML_FLG_DBG_VISITED;
+ return 1;
}
static void
@@ -1770,11 +1802,9 @@ insert_message(ErtsMessage *msg, int type, Process *proc)
else if (ERTS_SIG_IS_INTERNAL_MSG(msg))
heap_frag = msg->data.heap_frag;
else {
- if (msg->data.dist_ext->dep)
- insert_dist_entry(msg->data.dist_ext->dep,
- type, proc->common.id, 0);
- if (is_not_nil(ERL_MESSAGE_TOKEN(msg)))
- heap_frag = erts_dist_ext_trailer(msg->data.dist_ext);
+ heap_frag = msg->data.heap_frag;
+ insert_dist_entry(erts_get_dist_ext(heap_frag)->dep,
+ type, proc->common.id, 0);
}
}
while (heap_frag) {
@@ -1798,24 +1828,115 @@ insert_sig_offheap(ErlOffHeap *ohp, void *arg)
insert_offheap(ohp, SIGNAL_REF, proc->common.id);
}
-static void
-insert_sig_monitor(ErtsMonitor *mon, void *arg)
+static int
+insert_sig_monitor(ErtsMonitor *mon, void *arg, Sint reds)
{
Process *proc = arg;
insert_monitor_data(mon, SIGNAL_REF, proc->common.id);
+ return 1;
}
-static void
-insert_sig_link(ErtsLink *lnk, void *arg)
+static int
+insert_sig_link(ErtsLink *lnk, void *arg, Sint reds)
{
Process *proc = arg;
insert_link_data(lnk, SIGNAL_REF, proc->common.id);
+ return 1;
}
static void
-setup_reference_table(void)
+insert_sig_ext(ErtsDistExternal *edep, void *arg)
{
+ Process *proc = arg;
+ insert_dist_entry(edep->dep, SIGNAL_REF, proc->common.id, 0);
+}
+
+static void
+insert_process(Process *proc)
+{
+ int mli;
+ ErtsMessage *msg_list[] = {proc->msg_frag};
ErlHeapFragment *hfp;
+
+ /* Insert Heap */
+ insert_offheap(&(proc->off_heap),
+ HEAP_REF,
+ proc->common.id);
+ /* Insert heap fragments buffers */
+ for(hfp = proc->mbuf; hfp; hfp = hfp->next)
+ insert_offheap(&(hfp->off_heap),
+ HEAP_REF,
+ proc->common.id);
+
+ /* Insert msg buffers */
+ for (mli = 0; mli < sizeof(msg_list)/sizeof(msg_list[0]); mli++) {
+ ErtsMessage *msg;
+ for (msg = msg_list[mli]; msg; msg = msg->next)
+ insert_message(msg, HEAP_REF, proc);
+ }
+
+ /* Insert signal queue */
+ erts_proc_sig_debug_foreach_sig(proc,
+ insert_sig_msg,
+ insert_sig_offheap,
+ insert_sig_monitor,
+ insert_sig_link,
+ insert_sig_ext,
+ (void *) proc);
+
+ /* If the process is FREE, the proc->common field has been
+ re-used by the ptab delete, so we cannot trust it. */
+ if (!(erts_atomic32_read_nob(&proc->state) & ERTS_PSFLG_FREE)) {
+ /* Insert links */
+ insert_p_links(&proc->common);
+
+ /* Insert monitors */
+ insert_p_monitors(&proc->common);
+ }
+
+ {
+ DistEntry *dep = ERTS_PROC_GET_DIST_ENTRY(proc);
+ if (dep)
+ insert_dist_entry(dep,
+ CTRL_REF,
+ proc->common.id,
+ 0);
+ }
+}
+
+static void
+insert_dist_suspended_procs(DistEntry *dep)
+{
+ ErtsProcList *plist = erts_proclist_peek_first(dep->suspended);
+ while (plist) {
+ if (is_not_immed(plist->u.pid))
+ insert_process(plist->u.p);
+ plist = erts_proclist_peek_next(dep->suspended, plist);
+ }
+}
+
+#ifdef ERL_NODE_BOOKKEEP
+void
+erts_node_bookkeep(ErlNode *np, Eterm term, int what)
+{
+ erts_aint_t slot = (erts_atomic_inc_read_nob(&np->slot) - 1) % 1024;
+ ErtsSchedulerData *esdp = erts_get_scheduler_data();
+ Eterm who = THE_NON_VALUE;
+ ASSERT(np);
+ np->books[slot].what = what;
+ np->books[slot].term = term;
+ if (esdp->current_process) {
+ who = esdp->current_process->common.id;
+ } else if (esdp->current_port) {
+ who = esdp->current_port->common.id;
+ }
+ np->books[slot].who = who;
+}
+#endif
+
+static void
+setup_reference_table(void)
+{
DistEntry *dep;
HashInfo hi;
int i, max;
@@ -1865,52 +1986,10 @@ setup_reference_table(void)
/* Insert all processes */
for (i = 0; i < max; i++) {
Process *proc = erts_pix2proc(i);
- if (proc) {
- int mli;
- ErtsMessage *msg_list[] = {proc->msg_frag};
-
- /* Insert Heap */
- insert_offheap(&(proc->off_heap),
- HEAP_REF,
- proc->common.id);
- /* Insert heap fragments buffers */
- for(hfp = proc->mbuf; hfp; hfp = hfp->next)
- insert_offheap(&(hfp->off_heap),
- HEAP_REF,
- proc->common.id);
-
- /* Insert msg buffers */
- for (mli = 0; mli < sizeof(msg_list)/sizeof(msg_list[0]); mli++) {
- ErtsMessage *msg;
- for (msg = msg_list[mli]; msg; msg = msg->next)
- insert_message(msg, HEAP_REF, proc);
- }
-
- /* Insert signal queue */
- erts_proc_sig_debug_foreach_sig(proc,
- insert_sig_msg,
- insert_sig_offheap,
- insert_sig_monitor,
- insert_sig_link,
- (void *) proc);
-
- /* Insert links */
- insert_p_links(&proc->common);
-
- /* Insert monitors */
- insert_p_monitors(&proc->common);
-
- {
- DistEntry *dep = ERTS_PROC_GET_DIST_ENTRY(proc);
- if (dep)
- insert_dist_entry(dep,
- CTRL_REF,
- proc->common.id,
- 0);
- }
- }
+ if (proc)
+ insert_process(proc);
}
-
+
erts_foreach_sys_msg_in_q(insert_sys_msg);
/* Insert all ports */
@@ -1983,16 +2062,22 @@ setup_reference_table(void)
for(dep = erts_visible_dist_entries; dep; dep = dep->next) {
insert_dist_links(dep);
insert_dist_monitors(dep);
+ insert_dist_sequences(dep);
+ insert_dist_suspended_procs(dep);
}
for(dep = erts_hidden_dist_entries; dep; dep = dep->next) {
insert_dist_links(dep);
insert_dist_monitors(dep);
+ insert_dist_sequences(dep);
+ insert_dist_suspended_procs(dep);
}
for(dep = erts_pending_dist_entries; dep; dep = dep->next) {
insert_dist_links(dep);
insert_dist_monitors(dep);
+ insert_dist_sequences(dep);
+ insert_dist_suspended_procs(dep);
}
/* Not connected dist entries should not have any links,
@@ -2000,6 +2085,8 @@ setup_reference_table(void)
for(dep = erts_not_connected_dist_entries; dep; dep = dep->next) {
insert_dist_links(dep);
insert_dist_monitors(dep);
+ insert_dist_sequences(dep);
+ insert_dist_suspended_procs(dep);
}
/* Insert all ets tables */
@@ -2173,6 +2260,10 @@ reference_table_term(Uint **hpp, ErlOffHeap *ohp, Uint *szp)
tup = MK_2TUP(AM_system, MK_UINT(drp->system_ref));
drl = MK_CONS(tup, drl);
}
+ if(drp->sequence_ref) {
+ tup = MK_2TUP(AM_sequence, MK_UINT(drp->sequence_ref));
+ drl = MK_CONS(tup, drl);
+ }
if(drp->signal_ref) {
tup = MK_2TUP(AM_signal, MK_UINT(drp->signal_ref));
drl = MK_CONS(tup, drl);
@@ -2247,6 +2338,12 @@ static void noop_sig_offheap(ErlOffHeap *oh, void *arg)
}
+static void noop_sig_ext(ErtsDistExternal *ext, void *arg)
+{
+
+}
+
+
static void
delete_reference_table(void)
{
@@ -2297,6 +2394,7 @@ delete_reference_table(void)
noop_sig_offheap,
clear_visited_monitor,
clear_visited_link,
+ noop_sig_ext,
(void *) proc);
}
}
diff --git a/erts/emulator/beam/erl_node_tables.h b/erts/emulator/beam/erl_node_tables.h
index c44f1f8991..d5daf0c2df 100644
--- a/erts/emulator/beam/erl_node_tables.h
+++ b/erts/emulator/beam/erl_node_tables.h
@@ -98,11 +98,22 @@ struct ErtsDistOutputBuf_ {
byte *alloc_endp;
#endif
ErtsDistOutputBuf *next;
- Uint hopefull_flags;
+ Binary *bin;
+ /* Pointers to the distribution header,
+ if NULL the distr header is in the extp */
+ byte *hdrp;
+ byte *hdr_endp;
+ /* Pointers to the ctl + payload */
byte *extp;
byte *ext_endp;
+ /* Start of payload and hopefull_flags, used by transcode */
+ Uint hopefull_flags;
byte *msg_start;
- byte data[1];
+ /* start of the ext buffer, this is not always the same as extp
+ as the atom cache handling can use less then the allotted buffer.
+ This value is needed to calculate the size of this output buffer.*/
+ byte *ext_start;
+
};
typedef struct {
@@ -161,7 +172,46 @@ struct dist_entry_ {
ErtsThrPrgrLaterOp later_op;
struct transcode_context* transcode_ctx;
+
+ struct dist_sequences *sequences; /* Ongoing distribution sequences */
+};
+
+/*
+#define ERL_NODE_BOOKKEEP
+ * Bookkeeping of ErlNode inc and dec operations to help debug refc problems.
+ * This is best used together with cerl -rr. Type the below into gdb:
+ * gdb:
+set pagination off
+set $i = 0
+set $node = referred_nodes[$node_ix].node
+while $i < $node->slot.counter
+ printf "%p: ", $node->books[$i].term
+ etp-1 $node->books[$i].who
+ printf " "
+ p $node->books[$i].what
+ set $i++
+end
+
+ * Then save that into a file called test.txt and run the below in
+ * an erlang shell in order to get all inc/dec that do not have a
+ * match.
+
+f(), {ok, B} = file:read_file("test.txt").
+Vs = [begin [Val, _, _, _, What] = All = string:lexemes(Ln, " "),{Val,What,All} end || Ln <- string:lexemes(B,"\n")].
+Accs = lists:foldl(fun({V,<<"ERL_NODE_INC">>,_},M) -> Val = maps:get(V,M,0), M#{ V => Val + 1 }; ({V,<<"ERL_NODE_DEC">>,_},M) -> Val = maps:get(V,M,0), M#{ V => Val - 1 } end, #{}, Vs).
+lists:usort(lists:filter(fun({V,N}) -> N /= 0 end, maps:to_list(Accs))).
+
+ * There are bound to be bugs in the the instrumentation code, but
+ * atleast this is a place to start when hunting refc bugs.
+ *
+ */
+#ifdef ERL_NODE_BOOKKEEP
+struct erl_node_bookkeeping {
+ Eterm who;
+ Eterm term;
+ enum { ERL_NODE_INC, ERL_NODE_DEC } what;
};
+#endif
typedef struct erl_node_ {
HashBucket hash_bucket; /* Hash bucket */
@@ -169,6 +219,10 @@ typedef struct erl_node_ {
Eterm sysname; /* name@host atom for efficiency */
Uint32 creation; /* Creation */
DistEntry *dist_entry; /* Corresponding dist entry */
+#ifdef ERL_NODE_BOOKKEEP
+ struct erl_node_bookkeeping books[1024];
+ erts_atomic_t slot;
+#endif
} ErlNode;
@@ -201,7 +255,7 @@ void erts_dist_table_info(fmtfn_t, void *);
void erts_set_dist_entry_not_connected(DistEntry *);
void erts_set_dist_entry_pending(DistEntry *);
void erts_set_dist_entry_connected(DistEntry *, Eterm, Uint);
-ErlNode *erts_find_or_insert_node(Eterm, Uint32);
+ErlNode *erts_find_or_insert_node(Eterm, Uint32, Eterm);
void erts_schedule_delete_node(ErlNode *);
void erts_set_this_node(Eterm, Uint);
Uint erts_node_table_size(void);
@@ -219,18 +273,38 @@ DistEntry *erts_dhandle_to_dist_entry(Eterm dhandle, Uint32* connection_id);
Eterm erts_build_dhandle(Eterm **hpp, ErlOffHeap*, DistEntry*, Uint32 conn_id);
Eterm erts_make_dhandle(Process *c_p, DistEntry*, Uint32 conn_id);
-ERTS_GLB_INLINE void erts_deref_node_entry(ErlNode *np);
+ERTS_GLB_INLINE void erts_init_node_entry(ErlNode *np, erts_aint_t val);
+ERTS_GLB_INLINE erts_aint_t erts_ref_node_entry(ErlNode *np, int min_val, Eterm term);
+ERTS_GLB_INLINE void erts_deref_node_entry(ErlNode *np, Eterm term);
ERTS_GLB_INLINE void erts_de_rlock(DistEntry *dep);
ERTS_GLB_INLINE void erts_de_runlock(DistEntry *dep);
ERTS_GLB_INLINE void erts_de_rwlock(DistEntry *dep);
ERTS_GLB_INLINE void erts_de_rwunlock(DistEntry *dep);
+#ifdef ERL_NODE_BOOKKEEP
+void erts_node_bookkeep(ErlNode *, Eterm , int);
+#else
+#define erts_node_bookkeep(...)
+#endif
#if ERTS_GLB_INLINE_INCL_FUNC_DEF
ERTS_GLB_INLINE void
-erts_deref_node_entry(ErlNode *np)
+erts_init_node_entry(ErlNode *np, erts_aint_t val)
+{
+ erts_refc_init(&np->refc, val);
+}
+
+ERTS_GLB_INLINE erts_aint_t
+erts_ref_node_entry(ErlNode *np, int min_val, Eterm term)
+{
+ erts_node_bookkeep(np, term, ERL_NODE_INC);
+ return erts_refc_inctest(&np->refc, min_val);
+}
+
+ERTS_GLB_INLINE void
+erts_deref_node_entry(ErlNode *np, Eterm term)
{
- ASSERT(np);
+ erts_node_bookkeep(np, term, ERL_NODE_DEC);
if (erts_refc_dectest(&np->refc, 0) == 0)
erts_schedule_delete_node(np);
}
diff --git a/erts/emulator/beam/erl_port.h b/erts/emulator/beam/erl_port.h
index 25976d38cc..039d8cf67a 100644
--- a/erts/emulator/beam/erl_port.h
+++ b/erts/emulator/beam/erl_port.h
@@ -1018,6 +1018,6 @@ int erts_port_output_async(Port *, Eterm, Eterm);
/*
* Signals from ports to ports. Used by sys drivers.
*/
-int erl_drv_port_control(Eterm, char, char*, ErlDrvSizeT);
+int erl_drv_port_control(Eterm, unsigned int, char*, ErlDrvSizeT);
#endif
diff --git a/erts/emulator/beam/erl_port_task.c b/erts/emulator/beam/erl_port_task.c
index c8f2e88127..30a7875387 100644
--- a/erts/emulator/beam/erl_port_task.c
+++ b/erts/emulator/beam/erl_port_task.c
@@ -2094,7 +2094,7 @@ begin_port_cleanup(Port *pp, ErtsPortTask **execqp, int *processing_busy_q_p)
erts_snprintf(port_str, sizeof(DTRACE_CHARBUF_NAME(port_str)), "%T", pp->common.id);
while (plp2 != NULL) {
- erts_snprintf(pid_str, sizeof(DTRACE_CHARBUF_NAME(pid_str)), "%T", plp2->pid);
+ erts_snprintf(pid_str, sizeof(DTRACE_CHARBUF_NAME(pid_str)), "%T", plp2->u.pid);
DTRACE2(process_port_unblocked, pid_str, port_str);
}
}
diff --git a/erts/emulator/beam/erl_printf_term.c b/erts/emulator/beam/erl_printf_term.c
index 990a01b96f..2e33a8a782 100644
--- a/erts/emulator/beam/erl_printf_term.c
+++ b/erts/emulator/beam/erl_printf_term.c
@@ -364,13 +364,13 @@ print_term(fmtfn_t fn, void* arg, Eterm obj, long *dcount) {
int print_res;
char def_buf[64];
char *buf, *big_str;
- Uint sz = (Uint) big_decimal_estimate(wobj);
+ Uint sz = (Uint) big_integer_estimate(wobj, 10);
sz++;
if (sz <= 64)
buf = &def_buf[0];
else
buf = erts_alloc(ERTS_ALC_T_TMP, sz);
- big_str = erts_big_to_string(wobj, buf, sz);
+ big_str = erts_big_to_string(wobj, 10, buf, sz);
print_res = erts_printf_string(fn, arg, big_str);
if (buf != &def_buf[0])
erts_free(ERTS_ALC_T_TMP, (void *) buf);
@@ -487,6 +487,8 @@ print_term(fmtfn_t fn, void* arg, Eterm obj, long *dcount) {
PRINT_UWORD(res, fn, arg, 'u', 0, 1, octet);
++bytep;
--bytesize;
+ if ((*dcount)-- <= 0)
+ goto L_done;
}
if (bitsize) {
Uint bits = bitoffs + bitsize;
@@ -521,6 +523,8 @@ print_term(fmtfn_t fn, void* arg, Eterm obj, long *dcount) {
PRINT_CHAR(res, fn, arg, octet);
++bytep;
--bytesize;
+ if ((*dcount)-- <= 0)
+ goto L_done;
}
PRINT_STRING(res, fn, arg, "\">>");
}
diff --git a/erts/emulator/beam/erl_proc_sig_queue.c b/erts/emulator/beam/erl_proc_sig_queue.c
index f343e984f7..9c74a2c355 100644
--- a/erts/emulator/beam/erl_proc_sig_queue.c
+++ b/erts/emulator/beam/erl_proc_sig_queue.c
@@ -36,6 +36,7 @@
#include "erl_port_task.h"
#include "erl_trace.h"
#include "beam_bp.h"
+#include "erl_binary.h"
#include "big.h"
#include "erl_gc.h"
#include "bif.h"
@@ -80,6 +81,11 @@
#define ERTS_SIG_Q_TYPE_ADJUST_TRACE_INFO \
ERTS_SIG_Q_TYPE_MAX
+#define ERTS_SIG_IS_GEN_EXIT(sig) \
+ (ERTS_PROC_SIG_TYPE(((ErtsSignal *) sig)->common.tag) == ERTS_SIG_Q_TYPE_GEN_EXIT)
+#define ERTS_SIG_IS_GEN_EXIT_EXTERNAL(sig) \
+ (ASSERT(ERTS_SIG_IS_GEN_EXIT(sig)),is_non_value(get_exit_signal_data(sig)->reason))
+
Process *ERTS_WRITE_UNLIKELY(erts_dirty_process_signal_handler);
Process *ERTS_WRITE_UNLIKELY(erts_dirty_process_signal_handler_high);
Process *ERTS_WRITE_UNLIKELY(erts_dirty_process_signal_handler_max);
@@ -259,7 +265,7 @@ destroy_dist_proc_demonitor(ErtsSigDistProcDemonitor *dmon)
Eterm ref = dmon->ref;
if (is_external(ref)) {
ExternalThing *etp = external_thing_ptr(ref);
- erts_deref_node_entry(etp->node);
+ erts_deref_node_entry(etp->node, ref);
}
erts_free(ERTS_ALC_T_DIST_DEMONITOR, dmon);
}
@@ -294,7 +300,8 @@ destroy_sig_dist_link_op(ErtsSigDistLinkOp *sdlnk)
{
ASSERT(is_external_pid(sdlnk->remote));
ASSERT(boxed_val(sdlnk->remote) == &sdlnk->heap[0]);
- erts_deref_node_entry(((ExternalThing *) &sdlnk->heap[0])->node);
+ erts_deref_node_entry(((ExternalThing *) &sdlnk->heap[0])->node,
+ make_boxed(&sdlnk->heap[0]));
erts_free(ERTS_ALC_T_SIG_DATA, sdlnk);
}
@@ -936,29 +943,54 @@ erts_proc_sig_privqs_len(Process *c_p)
return proc_sig_privqs_len(c_p, 0);
}
+ErtsDistExternal *
+erts_proc_sig_get_external(ErtsMessage *msgp)
+{
+ if (ERTS_SIG_IS_EXTERNAL_MSG(msgp)) {
+ return erts_get_dist_ext(msgp->data.heap_frag);
+ } else if (ERTS_SIG_IS_NON_MSG(msgp) &&
+ ERTS_SIG_IS_GEN_EXIT(msgp) &&
+ ERTS_SIG_IS_GEN_EXIT_EXTERNAL(msgp)) {
+ ErtsDistExternal *edep;
+ ErtsExitSignalData *xsigd = get_exit_signal_data(msgp);
+ ASSERT(ERTS_PROC_SIG_TYPE(((ErtsSignal *) msgp)->common.tag) == ERTS_SIG_Q_TYPE_GEN_EXIT);
+ ASSERT(is_non_value(xsigd->reason));
+ if (msgp->hfrag.next == NULL)
+ edep = (ErtsDistExternal*)(xsigd + 1);
+ else
+ edep = erts_get_dist_ext(msgp->hfrag.next);
+ return edep;
+ }
+ return NULL;
+}
+
static void do_seq_trace_output(Eterm to, Eterm token, Eterm msg);
static void
send_gen_exit_signal(Process *c_p, Eterm from_tag,
Eterm from, Eterm to,
- Sint16 op, Eterm reason, Eterm ref,
- Eterm token, int normal_kills)
+ Sint16 op, Eterm reason, ErtsDistExternal *dist_ext,
+ ErlHeapFragment *dist_ext_hfrag,
+ Eterm ref, Eterm token, int normal_kills)
{
ErtsExitSignalData *xsigd;
Eterm *hp, *start_hp, s_reason, s_ref, s_message, s_token, s_from;
ErtsMessage *mp;
ErlHeapFragment *hfrag;
ErlOffHeap *ohp;
- Uint hsz, from_sz, reason_sz, ref_sz, token_sz;
+ Uint hsz, from_sz, reason_sz, ref_sz, token_sz, dist_ext_sz;
int seq_trace;
#ifdef USE_VM_PROBES
Eterm s_utag, utag;
Uint utag_sz;
#endif
+ ASSERT((is_value(reason) && dist_ext == NULL) ||
+ (is_non_value(reason) && dist_ext != NULL));
+
ASSERT(is_immed(from_tag));
- hsz = sizeof(ErtsExitSignalData)/sizeof(Uint);
+ hsz = sizeof(ErtsExitSignalData)/sizeof(Eterm);
seq_trace = c_p && have_seqtrace(token);
if (seq_trace)
@@ -977,33 +1009,42 @@ send_gen_exit_signal(Process *c_p, Eterm from_tag,
hsz += utag_sz;
#endif
- token_sz = is_immed(token) ? 0 : size_object(token);
+ token_sz = size_object(token);
hsz += token_sz;
- from_sz = is_immed(from) ? 0 : size_object(from);
+ from_sz = size_object(from);
hsz += from_sz;
- reason_sz = is_immed(reason) ? 0 : size_object(reason);
- hsz += reason_sz;
+ ref_sz = size_object(ref);
+ hsz += ref_sz;
- switch (op) {
- case ERTS_SIG_Q_OP_EXIT:
- case ERTS_SIG_Q_OP_EXIT_LINKED: {
- /* {'EXIT', From, Reason} */
- hsz += 4; /* 3-tuple */
- ref_sz = 0;
- break;
- }
- case ERTS_SIG_Q_OP_MONITOR_DOWN: {
- /* {'DOWN', Ref, process, From, Reason} */
- hsz += 6; /* 5-tuple */
- ref_sz = NC_HEAP_SIZE(ref);
- hsz += ref_sz;
- break;
- }
- default:
- ERTS_INTERNAL_ERROR("Invalid exit signal op");
- break;
+ /* The reason was part of the control message,
+ just use copy it into the xsigd */
+ if (is_value(reason)) {
+ reason_sz = size_object(reason);
+ hsz += reason_sz;
+
+ switch (op) {
+ case ERTS_SIG_Q_OP_EXIT:
+ case ERTS_SIG_Q_OP_EXIT_LINKED: {
+ /* {'EXIT', From, Reason} */
+ hsz += 4; /* 3-tuple */
+ break;
+ }
+ case ERTS_SIG_Q_OP_MONITOR_DOWN: {
+ /* {'DOWN', Ref, process, From, Reason} */
+ hsz += 6; /* 5-tuple */
+ break;
+ }
+ default:
+ ERTS_INTERNAL_ERROR("Invalid exit signal op");
+ break;
+ }
+ } else if (dist_ext != NULL && dist_ext_hfrag == NULL) {
+ /* The message was not fragmented so we need to create space
+ for a single dist_ext element */
+ dist_ext_sz = erts_dist_ext_size(dist_ext) / sizeof(Eterm);
+ hsz += dist_ext_sz;
}
/*
@@ -1015,35 +1056,33 @@ send_gen_exit_signal(Process *c_p, Eterm from_tag,
ohp = &hfrag->off_heap;
start_hp = hp;
- s_token = (is_immed(token)
- ? token
- : copy_struct(token, token_sz, &hp, ohp));
-
- s_reason = (is_immed(reason)
- ? reason
- : copy_struct(reason, reason_sz, &hp, ohp));
+ s_token = copy_struct(token, token_sz, &hp, ohp);
+ s_from = copy_struct(from, from_sz, &hp, ohp);
+ s_ref = copy_struct(ref, ref_sz, &hp, ohp);
- s_from = (is_immed(from)
- ? from
- : copy_struct(from, from_sz, &hp, ohp));
+ if (is_value(reason)) {
+ s_reason = copy_struct(reason, reason_sz, &hp, ohp);
- if (!ref_sz)
- s_ref = NIL;
- else
- s_ref = STORE_NC(&hp, ohp, ref);
-
- switch (op) {
- case ERTS_SIG_Q_OP_EXIT:
- case ERTS_SIG_Q_OP_EXIT_LINKED:
- /* {'EXIT', From, Reason} */
- s_message = TUPLE3(hp, am_EXIT, s_from, s_reason);
- hp += 4;
- break;
- case ERTS_SIG_Q_OP_MONITOR_DOWN:
- /* {'DOWN', Ref, process, From, Reason} */
- s_message = TUPLE5(hp, am_DOWN, s_ref, am_process, s_from, s_reason);
- hp += 6;
- break;
+ switch (op) {
+ case ERTS_SIG_Q_OP_EXIT:
+ case ERTS_SIG_Q_OP_EXIT_LINKED:
+ /* {'EXIT', From, Reason} */
+ s_message = TUPLE3(hp, am_EXIT, s_from, s_reason);
+ hp += 4;
+ break;
+ case ERTS_SIG_Q_OP_MONITOR_DOWN:
+ /* {'DOWN', Ref, process, From, Reason} */
+ s_message = TUPLE5(hp, am_DOWN, s_ref, am_process, s_from, s_reason);
+ hp += 6;
+ break;
+ default:
+ /* This cannot happen, used to silence gcc warning */
+ s_message = THE_NON_VALUE;
+ break;
+ }
+ } else {
+ s_message = THE_NON_VALUE;
+ s_reason = THE_NON_VALUE;
}
#ifdef USE_VM_PROBES
@@ -1061,11 +1100,13 @@ send_gen_exit_signal(Process *c_p, Eterm from_tag,
hfrag->used_size = hp - start_hp;
- xsigd = (ErtsExitSignalData *) (char *) hp;
+ xsigd = (ErtsExitSignalData *) hp;
xsigd->message = s_message;
xsigd->from = s_from;
xsigd->reason = s_reason;
+ hfrag->next = dist_ext_hfrag;
+
if (is_nil(s_ref))
xsigd->u.normal_kills = normal_kills;
else {
@@ -1073,6 +1114,15 @@ send_gen_exit_signal(Process *c_p, Eterm from_tag,
xsigd->u.ref = s_ref;
}
+ hp += sizeof(ErtsExitSignalData)/sizeof(Eterm);
+
+ if (dist_ext != NULL && dist_ext_hfrag == NULL && is_non_value(reason)) {
+ erts_make_dist_ext_copy(dist_ext, (ErtsDistExternal *) hp);
+ hp += dist_ext_sz;
+ }
+
+ ASSERT(hp == mp->hfrag.mem + mp->hfrag.alloc_size);
+
if (seq_trace)
do_seq_trace_output(to, s_token, s_message);
@@ -1205,7 +1255,19 @@ erts_proc_sig_send_exit(Process *c_p, Eterm from, Eterm to,
from_tag = dep->sysname;
}
send_gen_exit_signal(c_p, from_tag, from, to, ERTS_SIG_Q_OP_EXIT,
- reason, NIL, token, normal_kills);
+ reason, NULL, NULL, NIL, token, normal_kills);
+}
+
+void
+erts_proc_sig_send_dist_exit(DistEntry *dep,
+ Eterm from, Eterm to,
+ ErtsDistExternal *dist_ext,
+ ErlHeapFragment *hfrag,
+ Eterm reason, Eterm token)
+{
+ send_gen_exit_signal(NULL, dep->sysname, from, to, ERTS_SIG_Q_OP_EXIT,
+ reason, dist_ext, hfrag, NIL, token, 0);
+
}
void
@@ -1219,7 +1281,7 @@ erts_proc_sig_send_link_exit(Process *c_p, Eterm from, ErtsLink *lnk,
if (is_not_immed(reason) || is_not_nil(token)) {
ASSERT(is_internal_pid(from) || is_internal_port(from));
send_gen_exit_signal(c_p, from, from, to, ERTS_SIG_Q_OP_EXIT_LINKED,
- reason, NIL, token, 0);
+ reason, NULL, NULL, NIL, token, 0);
}
else {
/* Pass signal using old link structure... */
@@ -1274,10 +1336,13 @@ erts_proc_sig_send_unlink(Process *c_p, ErtsLink *lnk)
void
erts_proc_sig_send_dist_link_exit(DistEntry *dep,
Eterm from, Eterm to,
+ ErtsDistExternal *dist_ext,
+ ErlHeapFragment *hfrag,
Eterm reason, Eterm token)
{
send_gen_exit_signal(NULL, dep->sysname, from, to, ERTS_SIG_Q_OP_EXIT_LINKED,
- reason, NIL, token, 0);
+ reason, dist_ext, hfrag, NIL, token, 0);
+
}
void
@@ -1299,16 +1364,18 @@ erts_proc_sig_send_dist_unlink(DistEntry *dep, Eterm from, Eterm to)
void
erts_proc_sig_send_dist_monitor_down(DistEntry *dep, Eterm ref,
Eterm from, Eterm to,
+ ErtsDistExternal *dist_ext,
+ ErlHeapFragment *hfrag,
Eterm reason)
{
Eterm monitored, heap[3];
- if (is_atom(from))
+ if (is_atom(from))
monitored = TUPLE2(&heap[0], from, dep->sysname);
else
monitored = from;
send_gen_exit_signal(NULL, dep->sysname, monitored,
to, ERTS_SIG_Q_OP_MONITOR_DOWN,
- reason, ref, NIL, 0);
+ reason, dist_ext, hfrag, ref, NIL, 0);
}
void
@@ -1376,10 +1443,10 @@ erts_proc_sig_send_monitor_down(ErtsMonitor *mon, Eterm reason)
|| is_internal_pid(from_tag)
|| is_atom(from_tag));
monitored = TUPLE2(&heap[0], name, node);
- }
+ }
send_gen_exit_signal(NULL, from_tag, monitored,
to, ERTS_SIG_Q_OP_MONITOR_DOWN,
- reason, mdp->ref, NIL, 0);
+ reason, NULL, NULL, mdp->ref, NIL, 0);
}
erts_monitor_release(mon);
}
@@ -2037,7 +2104,6 @@ handle_exit_signal(Process *c_p, ErtsSigRecvTracing *tracing,
if (type == ERTS_SIG_Q_TYPE_GEN_EXIT) {
xsigd = get_exit_signal_data(sig);
from = xsigd->from;
- reason = xsigd->reason;
if (op != ERTS_SIG_Q_OP_EXIT_LINKED)
ignore = 0;
else {
@@ -2062,6 +2128,18 @@ handle_exit_signal(Process *c_p, ErtsSigRecvTracing *tracing,
}
}
+ /* This GEN_EXIT was received from another node, decode the exit reason */
+ if (ERTS_SIG_IS_GEN_EXIT_EXTERNAL(sig))
+ erts_proc_sig_decode_dist(c_p, ERTS_PROC_LOCK_MAIN, sig, 1);
+
+ reason = xsigd->reason;
+
+ if (is_non_value(reason)) {
+ /* Bad distribution message; remove it from queue... */
+ ignore = !0;
+ destroy = !0;
+ }
+
if (!ignore) {
if ((op != ERTS_SIG_Q_OP_EXIT || reason != am_kill)
@@ -2929,6 +3007,104 @@ handle_sync_suspend(Process *c_p, ErtsMessage *mp)
}
}
+int
+erts_proc_sig_decode_dist(Process *proc, ErtsProcLocks proc_locks,
+ ErtsMessage *msgp, int force_off_heap)
+{
+ ErtsHeapFactory factory;
+ ErlHeapFragment *hfrag;
+ Eterm msg;
+ Sint need;
+ ErtsDistExternal *edep;
+ ErtsExitSignalData *xsigd = NULL;
+
+ edep = erts_proc_sig_get_external(msgp);
+ if (!ERTS_SIG_IS_EXTERNAL_MSG(msgp))
+ xsigd = get_exit_signal_data(msgp);
+
+ if (edep->heap_size >= 0)
+ need = edep->heap_size;
+ else {
+ need = erts_decode_dist_ext_size(edep, 1);
+ if (need < 0) {
+ /* bad signal; remove it... */
+ return 0;
+ }
+
+ edep->heap_size = need;
+ }
+
+ if (ERTS_SIG_IS_NON_MSG(msgp)) {
+ switch (ERTS_PROC_SIG_OP(ERL_MESSAGE_TERM(msgp))) {
+ case ERTS_SIG_Q_OP_EXIT:
+ case ERTS_SIG_Q_OP_EXIT_LINKED:
+ /* {'EXIT', From, Reason} */
+ need += 4;
+ break;
+ case ERTS_SIG_Q_OP_MONITOR_DOWN:
+ /* {'DOWN', Ref, process, From, Reason} */
+ need += 6; /* 5-tuple */
+ break;
+ default:
+ ERTS_INTERNAL_ERROR("Invalid exit signal op");
+ break;
+ }
+ }
+
+ hfrag = new_message_buffer(need);
+ erts_factory_heap_frag_init(&factory, hfrag);
+
+ ASSERT(edep->heap_size >= 0);
+
+ msg = erts_decode_dist_ext(&factory, edep, 1);
+
+ if (is_non_value(msg)) {
+ erts_factory_undo(&factory);
+ return 0;
+ }
+
+ if (ERTS_SIG_IS_MSG(msgp)) {
+ ERL_MESSAGE_TERM(msgp) = msg;
+ if (msgp->data.heap_frag == &msgp->hfrag)
+ msgp->data.heap_frag = ERTS_MSG_COMBINED_HFRAG;
+ } else {
+ switch (ERTS_PROC_SIG_OP(ERL_MESSAGE_TERM(msgp))) {
+ case ERTS_SIG_Q_OP_EXIT:
+ case ERTS_SIG_Q_OP_EXIT_LINKED:
+ /* {'EXIT', From, Reason} */
+ erts_reserve_heap(&factory, 4);
+ xsigd->message = TUPLE3(factory.hp, am_EXIT, xsigd->from, msg);
+ factory.hp += 4;
+ break;
+ case ERTS_SIG_Q_OP_MONITOR_DOWN:
+ /* {'DOWN', Ref, process, From, Reason} */
+ erts_reserve_heap(&factory, 6);
+ xsigd->message = TUPLE5(factory.hp, am_DOWN, xsigd->u.ref, am_process, xsigd->from, msg);
+ factory.hp += 6;
+ break;
+ }
+ xsigd->reason = msg;
+ }
+
+ erts_free_dist_ext_copy(edep);
+
+ erts_factory_close(&factory);
+
+ hfrag = factory.heap_frags;
+ while (hfrag->next)
+ hfrag = hfrag->next;
+
+ if (ERTS_SIG_IS_MSG(msgp) && msgp->data.heap_frag != ERTS_MSG_COMBINED_HFRAG) {
+ hfrag->next = msgp->data.heap_frag;
+ msgp->data.heap_frag = factory.heap_frags;
+ } else {
+ hfrag->next = msgp->hfrag.next;
+ msgp->hfrag.next = factory.heap_frags;
+ }
+
+ return 1;
+}
+
void
erts_proc_sig_handle_pending_suspend(Process *c_p)
{
@@ -3045,7 +3221,7 @@ erts_proc_sig_handle_incoming(Process *c_p, erts_aint32_t *statep,
ASSERT(ERTS_SIG_IS_NON_MSG(sig));
tag = ((ErtsSignal *) sig)->common.tag;
-
+
switch (ERTS_PROC_SIG_OP(tag)) {
case ERTS_SIG_Q_OP_EXIT:
@@ -3091,6 +3267,12 @@ erts_proc_sig_handle_incoming(Process *c_p, erts_aint32_t *statep,
break;
case ERTS_SIG_Q_TYPE_GEN_EXIT:
xsigd = get_exit_signal_data(sig);
+
+ /* This GEN_EXIT was received from another node, decode the exit reason */
+ if (ERTS_SIG_IS_GEN_EXIT_EXTERNAL(sig))
+ if (!erts_proc_sig_decode_dist(c_p, ERTS_PROC_LOCK_MAIN, sig, 1))
+ break; /* Decode failed, just remove signal */
+
omon = erts_monitor_tree_lookup(ERTS_P_MONITORS(c_p),
xsigd->u.ref);
if (omon) {
@@ -3590,9 +3772,10 @@ stretch_limit(Process *c_p, ErtsSigRecvTracing *tp,
int
-erts_proc_sig_handle_exit(Process *c_p, int *redsp)
+erts_proc_sig_handle_exit(Process *c_p, Sint *redsp)
{
- int cnt, limit;
+ int cnt;
+ Sint limit;
ErtsMessage *sig, ***next_nm_sig;
ERTS_HDBG_CHECK_SIGNAL_PRIV_QUEUE(c_p, 0);
@@ -3671,9 +3854,9 @@ erts_proc_sig_handle_exit(Process *c_p, int *redsp)
break;
case ERTS_SIG_Q_OP_MONITOR: {
- ErtsProcExitContext pectxt = {c_p, am_noproc};
+ ErtsProcExitContext pectxt = {c_p, am_noproc, NULL, NULL, NIL};
erts_proc_exit_handle_monitor((ErtsMonitor *) sig,
- (void *) &pectxt);
+ (void *) &pectxt, -1);
cnt += 4;
break;
}
@@ -3687,7 +3870,7 @@ erts_proc_sig_handle_exit(Process *c_p, int *redsp)
case ERTS_SIG_Q_OP_LINK: {
ErtsProcExitContext pectxt = {c_p, am_noproc};
- erts_proc_exit_handle_link((ErtsLink *) sig, (void *) &pectxt);
+ erts_proc_exit_handle_link((ErtsLink *) sig, (void *) &pectxt, -1);
break;
}
@@ -3812,7 +3995,6 @@ clear_seq_trace_token(ErtsMessage *sig)
void
erts_proc_sig_clear_seq_trace_tokens(Process *c_p)
{
- ASSERT(erts_thr_progress_is_blocking());
erts_proc_sig_fetch(c_p);
ERTS_FOREACH_SIG_PRIVQS(c_p, sig, clear_seq_trace_token(sig));
}
@@ -4189,11 +4371,13 @@ handle_msg_tracing(Process *c_p, ErtsSigRecvTracing *tracing,
return -1; /* Yield... */
}
if (ERTS_SIG_IS_EXTERNAL_MSG(sig)) {
- cnt++;
- if (!erts_decode_dist_message(c_p, ERTS_PROC_LOCK_MAIN,
- sig, 0)) {
+ cnt += 50; /* Decode is expensive... */
+ if (!erts_proc_sig_decode_dist(c_p, ERTS_PROC_LOCK_MAIN,
+ sig, 0)) {
/* Bad dist message; remove it... */
remove_mq_m_sig(c_p, sig, next_sig, next_nm_sig);
+ sig->next = NULL;
+ erts_cleanup_messages(sig);
sig = *next_sig;
continue;
}
@@ -4265,18 +4449,12 @@ erts_proc_sig_prep_msgq_for_inspection(Process *c_p,
if (ERTS_SIG_IS_EXTERNAL_MSG(mp)) {
/* decode it... */
- if (mp->data.attached)
- erts_decode_dist_message(rp, rp_locks, mp, !0);
-
- msg = ERL_MESSAGE_TERM(mp);
-
- if (is_non_value(msg)) {
+ if (!erts_proc_sig_decode_dist(rp, rp_locks, mp, !0)) {
ErtsMessage *bad_mp = mp;
/*
* Bad distribution message; remove
* it from the queue...
*/
- ASSERT(!mp->data.attached);
ASSERT(*mpp == bad_mp);
@@ -4288,6 +4466,8 @@ erts_proc_sig_prep_msgq_for_inspection(Process *c_p,
erts_cleanup_messages(bad_mp);
continue;
}
+
+ msg = ERL_MESSAGE_TERM(mp);
}
ASSERT(is_value(msg));
@@ -4446,12 +4626,21 @@ debug_foreach_sig_fake_oh(Eterm term,
}
+static void
+debug_foreach_sig_external(ErtsMessage *msgp,
+ void (*ext_func)(ErtsDistExternal *, void *),
+ void *arg)
+{
+ ext_func(erts_proc_sig_get_external(msgp), arg);
+}
+
void
erts_proc_sig_debug_foreach_sig(Process *c_p,
void (*msg_func)(ErtsMessage *, void *),
void (*oh_func)(ErlOffHeap *, void *),
- void (*mon_func)(ErtsMonitor *, void *),
- void (*lnk_func)(ErtsLink *, void *),
+ ErtsMonitorFunc mon_func,
+ ErtsLinkFunc lnk_func,
+ void (*ext_func)(ErtsDistExternal *, void *),
void *arg)
{
ErtsMessage *queue[] = {c_p->sig_qs.first, c_p->sig_qs.cont, c_p->sig_inq.first};
@@ -4460,10 +4649,10 @@ erts_proc_sig_debug_foreach_sig(Process *c_p,
for (qix = 0; qix < sizeof(queue)/sizeof(queue[0]); qix++) {
ErtsMessage *sig;
for (sig = queue[qix]; sig; sig = sig->next) {
-
- if (ERTS_SIG_IS_MSG(sig))
+
+ if (ERTS_SIG_IS_MSG(sig)) {
msg_func(sig, arg);
- else {
+ } else {
Eterm tag;
Uint16 type;
int op;
@@ -4482,18 +4671,21 @@ erts_proc_sig_debug_foreach_sig(Process *c_p,
case ERTS_SIG_Q_OP_MONITOR_DOWN:
switch (type) {
case ERTS_SIG_Q_TYPE_GEN_EXIT:
- debug_foreach_sig_heap_frags(&sig->hfrag, oh_func, arg);
+ if (ERTS_SIG_IS_GEN_EXIT_EXTERNAL(sig))
+ debug_foreach_sig_external(sig, ext_func, arg);
+ else
+ debug_foreach_sig_heap_frags(&sig->hfrag, oh_func, arg);
break;
case ERTS_LNK_TYPE_PORT:
case ERTS_LNK_TYPE_PROC:
case ERTS_LNK_TYPE_DIST_PROC:
- lnk_func((ErtsLink *) sig, arg);
+ lnk_func((ErtsLink *) sig, arg, -1);
break;
case ERTS_MON_TYPE_PORT:
case ERTS_MON_TYPE_PROC:
case ERTS_MON_TYPE_DIST_PROC:
case ERTS_MON_TYPE_NODE:
- mon_func((ErtsMonitor *) sig, arg);
+ mon_func((ErtsMonitor *) sig, arg, -1);
break;
default:
ERTS_INTERNAL_ERROR("Unexpected sig type");
@@ -4514,7 +4706,7 @@ erts_proc_sig_debug_foreach_sig(Process *c_p,
/* Fall through... */
case ERTS_SIG_Q_OP_MONITOR:
- mon_func((ErtsMonitor *) sig, arg);
+ mon_func((ErtsMonitor *) sig, arg, -1);
break;
case ERTS_SIG_Q_OP_UNLINK:
@@ -4526,7 +4718,7 @@ erts_proc_sig_debug_foreach_sig(Process *c_p,
/* Fall through... */
case ERTS_SIG_Q_OP_LINK:
- lnk_func((ErtsLink *) sig, arg);
+ lnk_func((ErtsLink *) sig, arg, -1);
break;
case ERTS_SIG_Q_OP_GROUP_LEADER: {
diff --git a/erts/emulator/beam/erl_proc_sig_queue.h b/erts/emulator/beam/erl_proc_sig_queue.h
index 3fc2d06b2d..2b055e73bc 100644
--- a/erts/emulator/beam/erl_proc_sig_queue.h
+++ b/erts/emulator/beam/erl_proc_sig_queue.h
@@ -89,6 +89,7 @@
#endif
struct erl_mesg;
+struct erl_dist_external;
typedef struct {
struct erl_mesg *next;
@@ -212,6 +213,38 @@ erts_proc_sig_send_exit(Process *c_p, Eterm from, Eterm to,
/**
*
+ * @brief Send an exit signal to a process.
+ *
+ * This function is used instead of erts_proc_sig_send_link_exit()
+ * when the signal arrives via the distribution and
+ * therefore no link structure is available.
+ *
+ * @param[in] dep Distribution entry of channel
+ * that the signal arrived on.
+ *
+ * @param[in] from Identifier of sender.
+ *
+ * @param[in] to Identifier of receiver.
+ *
+ * @param[in] dist_ext The exit reason in external term format
+ *
+ * @param[in] hfrag Heap frag with trace token and dist_ext
+ * iff available, otherwise NULL.
+ *
+ * @param[in] reason Exit reason.
+ *
+ * @param[in] token Seq trace token.
+ *
+ */
+void
+erts_proc_sig_send_dist_exit(DistEntry *dep,
+ Eterm from, Eterm to,
+ ErtsDistExternal *dist_ext,
+ ErlHeapFragment *hfrag,
+ Eterm reason, Eterm token);
+
+/**
+ *
* @brief Send an exit signal due to broken link to a process.
*
*
@@ -282,7 +315,7 @@ erts_proc_sig_send_unlink(Process *c_p, ErtsLink *lnk);
*
* This function is used instead of erts_proc_sig_send_link_exit()
* when the signal arrives via the distribution and
- * no link structure is available.
+ * therefore no link structure is available.
*
* @param[in] dep Distribution entry of channel
* that the signal arrived on.
@@ -291,6 +324,11 @@ erts_proc_sig_send_unlink(Process *c_p, ErtsLink *lnk);
*
* @param[in] to Identifier of receiver.
*
+ * @param[in] dist_ext The exit reason in external term format
+ *
+ * @param[in] hfrag Heap frag with trace token and dist_ext
+ * iff available, otherwise NULL.
+ *
* @param[in] reason Exit reason.
*
* @param[in] token Seq trace token.
@@ -299,6 +337,8 @@ erts_proc_sig_send_unlink(Process *c_p, ErtsLink *lnk);
void
erts_proc_sig_send_dist_link_exit(struct dist_entry_ *dep,
Eterm from, Eterm to,
+ ErtsDistExternal *dist_ext,
+ ErlHeapFragment *hfrag,
Eterm reason, Eterm token);
/**
@@ -307,7 +347,7 @@ erts_proc_sig_send_dist_link_exit(struct dist_entry_ *dep,
*
* This function is used instead of erts_proc_sig_send_unlink()
* when the signal arrives via the distribution and
- * no link structure is available.
+ * therefore no link structure is available.
*
* @param[in] dep Distribution entry of channel
* that the signal arrived on.
@@ -380,7 +420,7 @@ erts_proc_sig_send_monitor(ErtsMonitor *mon, Eterm to);
*
* This function is used instead of erts_proc_sig_send_monitor_down()
* when the signal arrives via the distribution and
- * no link structure is available.
+ * therefore no monitor structure is available.
*
* @param[in] dep Pointer to distribution entry
* of channel that the signal
@@ -392,12 +432,19 @@ erts_proc_sig_send_monitor(ErtsMonitor *mon, Eterm to);
*
* @param[in] to Identifier of receiver.
*
+ * @param[in] dist_ext The exit reason in external term format
+ *
+ * @param[in] hfrag Heap frag with trace token and dist_ext
+ * iff available, otherwise NULL.
+ *
* @param[in] reason Exit reason.
*
*/
void
erts_proc_sig_send_dist_monitor_down(DistEntry *dep, Eterm ref,
Eterm from, Eterm to,
+ ErtsDistExternal *dist_ext,
+ ErlHeapFragment *hfrag,
Eterm reason);
/**
@@ -740,7 +787,7 @@ erts_proc_sig_handle_incoming(Process *c_p, erts_aint32_t *statep,
* queue.
*/
int
-erts_proc_sig_handle_exit(Process *c_p, int *redsp);
+erts_proc_sig_handle_exit(Process *c_p, Sint *redsp);
/**
*
@@ -962,6 +1009,34 @@ void
erts_proc_sig_handle_pending_suspend(Process *c_p);
/**
+ *
+ * @brief Decode the reason term in an external signal
+ *
+ * Any distributed signal with a payload only has the control
+ * message decoded by the dist entry. The final decode of the
+ * payload is done by the process when it inspects the signal
+ * by calling this function.
+ *
+ * This functions handles both messages and link/monitor exits.
+ *
+ * Return true if the decode was successful, false otherwise.
+ *
+ * @param[in] c_p Pointer to executing process
+ *
+ * @param[in] proc_lock Locks held by process. Should always be MAIN.
+ *
+ * @param[in] msgp The signal to decode
+ *
+ * @param[in] force_off_heap If the term should be forced to be off-heap
+ */
+int
+erts_proc_sig_decode_dist(Process *proc, ErtsProcLocks proc_locks,
+ ErtsMessage *msgp, int force_off_heap);
+
+ErtsDistExternal *
+erts_proc_sig_get_external(ErtsMessage *msgp);
+
+/**
* @brief Initialize this functionality
*/
void erts_proc_sig_queue_init(void);
@@ -970,8 +1045,9 @@ void
erts_proc_sig_debug_foreach_sig(Process *c_p,
void (*msg_func)(ErtsMessage *, void *),
void (*oh_func)(ErlOffHeap *, void *),
- void (*mon_func)(ErtsMonitor *, void *),
- void (*lnk_func)(ErtsLink *, void *),
+ ErtsMonitorFunc mon_func,
+ ErtsLinkFunc lnk_func,
+ void (*ext_func)(ErtsDistExternal *, void *),
void *arg);
extern Process *erts_dirty_process_signal_handler;
@@ -989,8 +1065,7 @@ erts_proc_sig_fetch(Process *proc)
Sint res = 0;
ErtsSignal *sig;
- ERTS_LC_ASSERT(erts_thr_progress_is_blocking()
- || ERTS_PROC_IS_EXITING(proc)
+ ERTS_LC_ASSERT(ERTS_PROC_IS_EXITING(proc)
|| ((erts_proc_lc_my_proc_locks(proc)
& (ERTS_PROC_LOCK_MAIN
| ERTS_PROC_LOCK_MSGQ))
diff --git a/erts/emulator/beam/erl_process.c b/erts/emulator/beam/erl_process.c
index dca502c939..0a099e69bb 100644
--- a/erts/emulator/beam/erl_process.c
+++ b/erts/emulator/beam/erl_process.c
@@ -735,12 +735,6 @@ erts_pre_init_process(void)
#endif
}
-static void
-release_process(void *vproc)
-{
- erts_proc_dec_refc((Process *) vproc);
-}
-
/* initialize the scheduler */
void
erts_init_process(int ncpu, int proc_tab_size, int legacy_proc_tab)
@@ -752,7 +746,7 @@ erts_init_process(int ncpu, int proc_tab_size, int legacy_proc_tab)
erts_ptab_init_table(&erts_proc,
ERTS_ALC_T_PROC_TABLE,
- release_process,
+ NULL,
(ErtsPTabElementCommon *) &erts_invalid_process.common,
proc_tab_size,
sizeof(Process),
@@ -1476,7 +1470,10 @@ proclist_create(Process *p)
{
ErtsProcList *plp = proclist_alloc();
ensure_later_proc_interval(p->common.u.alive.started_interval);
- plp->pid = p->common.id;
+ if (erts_atomic32_read_nob(&p->state) & ERTS_PSFLG_FREE)
+ plp->u.p = p;
+ else
+ plp->u.pid = p->common.id;
plp->started_interval = p->common.u.alive.started_interval;
return plp;
}
@@ -1485,7 +1482,7 @@ static ERTS_INLINE ErtsProcList *
proclist_copy(ErtsProcList *plp0)
{
ErtsProcList *plp1 = proclist_alloc();
- plp1->pid = plp0->pid;
+ plp1->u.pid = plp0->u.pid;
plp1->started_interval = plp0->started_interval;
return plp1;
}
@@ -1520,7 +1517,10 @@ erts_proclist_dump(fmtfn_t to, void *to_arg, ErtsProcList *plp)
ErtsProcList *first = plp;
while (plp) {
- erts_print(to, to_arg, "%T", plp->pid);
+ if (is_pid(plp->u.pid))
+ erts_print(to, to_arg, "%T", plp->u.pid);
+ else
+ erts_print(to, to_arg, "%T", plp->u.p->common.id);
plp = plp->next;
if (plp == first)
break;
@@ -2462,6 +2462,13 @@ handle_reap_ports(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, int waiting)
erts_port_lock(prt);
+ if (prt->common.u.alive.reg &&
+ prt->common.u.alive.reg->name == am_heart_port) {
+ /* Leave heart port to not get killed before flushing is done*/
+ erts_port_release(prt);
+ continue;
+ }
+
state = erts_atomic32_read_nob(&prt->state);
if (!(state & (ERTS_PORT_SFLGS_INVALID_DRIVER_LOOKUP
| ERTS_PORT_SFLG_HALT))) {
@@ -3370,7 +3377,12 @@ scheduler_wait(int *fcalls, ErtsSchedulerData *esdp, ErtsRunQueue *rq)
ErtsMonotonicTime current_time = 0;
aux_work = erts_atomic32_read_acqb(&ssi->aux_work);
- if (aux_work && !ERTS_SCHEDULER_IS_DIRTY(esdp)) {
+
+ if (aux_work && ERTS_SCHEDULER_IS_DIRTY(esdp)) {
+ ERTS_INTERNAL_ERROR("Executing aux work on a dirty scheduler.");
+ }
+
+ if (aux_work) {
if (!thr_prgr_active) {
erts_thr_progress_active(erts_thr_prgr_data(esdp), thr_prgr_active = 1);
sched_wall_time_change(esdp, 1);
@@ -3382,16 +3394,14 @@ scheduler_wait(int *fcalls, ErtsSchedulerData *esdp, ErtsRunQueue *rq)
}
if (aux_work) {
- if (!ERTS_SCHEDULER_IS_DIRTY(esdp)) {
- flgs = erts_atomic32_read_acqb(&ssi->flags);
- current_time = erts_get_monotonic_time(esdp);
- if (current_time >= erts_next_timeout_time(esdp->next_tmo_ref)) {
- if (!thr_prgr_active) {
- erts_thr_progress_active(erts_thr_prgr_data(esdp), thr_prgr_active = 1);
- sched_wall_time_change(esdp, 1);
- }
- erts_bump_timers(esdp->timer_wheel, current_time);
+ flgs = erts_atomic32_read_acqb(&ssi->flags);
+ current_time = erts_get_monotonic_time(esdp);
+ if (current_time >= erts_next_timeout_time(esdp->next_tmo_ref)) {
+ if (!thr_prgr_active) {
+ erts_thr_progress_active(erts_thr_prgr_data(esdp), thr_prgr_active = 1);
+ sched_wall_time_change(esdp, 1);
}
+ erts_bump_timers(esdp->timer_wheel, current_time);
}
}
else {
@@ -4134,9 +4144,7 @@ schedule_bound_processes(ErtsRunQueue *rq,
static ERTS_INLINE void
clear_proc_dirty_queue_bit(Process *p, ErtsRunQueue *rq, int prio_bit)
{
-#ifdef DEBUG
erts_aint32_t old;
-#endif
erts_aint32_t qb = prio_bit;
if (rq == ERTS_DIRTY_CPU_RUNQ)
qb <<= ERTS_PDSFLGS_IN_CPU_PRQ_MASK_OFFSET;
@@ -4144,13 +4152,8 @@ clear_proc_dirty_queue_bit(Process *p, ErtsRunQueue *rq, int prio_bit)
ASSERT(rq == ERTS_DIRTY_IO_RUNQ);
qb <<= ERTS_PDSFLGS_IN_IO_PRQ_MASK_OFFSET;
}
-#ifdef DEBUG
- old = (int)
-#else
- (void)
-#endif
- erts_atomic32_read_band_mb(&p->dirty_state, ~qb);
- ASSERT(old & qb);
+ old = (int) erts_atomic32_read_band_mb(&p->dirty_state, ~qb);
+ ASSERT(old & qb); (void)old;
}
@@ -6523,8 +6526,7 @@ schedule_out_process(ErtsRunQueue *c_rq, erts_aint32_t state, Process *p,
n &= ~running_flgs;
if ((!!(a & (ERTS_PSFLG_ACTIVE_SYS|ERTS_PSFLG_DIRTY_ACTIVE_SYS))
- | ((a & (ERTS_PSFLG_ACTIVE|ERTS_PSFLG_SUSPENDED)) == ERTS_PSFLG_ACTIVE))
- & !(a & ERTS_PSFLG_FREE)) {
+ | ((a & (ERTS_PSFLG_ACTIVE|ERTS_PSFLG_SUSPENDED)) == ERTS_PSFLG_ACTIVE))) {
enqueue = check_enqueue_in_prio_queue(p, &enq_prio, &n, a);
}
a = erts_atomic32_cmpxchg_mb(&p->state, n, e);
@@ -6559,7 +6561,6 @@ schedule_out_process(ErtsRunQueue *c_rq, erts_aint32_t state, Process *p,
else {
Process* sched_p;
- ASSERT(!(n & ERTS_PSFLG_FREE));
ASSERT(!(n & ERTS_PSFLG_SUSPENDED) || (n & (ERTS_PSFLG_ACTIVE_SYS
| ERTS_PSFLG_DIRTY_ACTIVE_SYS)));
@@ -6689,8 +6690,8 @@ change_proc_schedule_state(Process *p,
enqueue = ERTS_ENQUEUE_NOT;
- if (a & ERTS_PSFLG_FREE)
- break; /* We don't want to schedule free processes... */
+ if ((a & (ERTS_PSFLG_FREE|ERTS_PSFLG_ACTIVE)) == ERTS_PSFLG_FREE)
+ break; /* If free and not active, do not schedule */
if (clear_state_flags)
n &= ~clear_state_flags;
@@ -7216,8 +7217,7 @@ schdlr_sspnd_resume_procs(ErtsSchedType sched_type,
while (resume->msb.chngrs) {
ErtsProcList *plp = resume->msb.chngrs;
resume->msb.chngrs = plp->next;
- schdlr_sspnd_resume_proc(sched_type,
- plp->pid);
+ schdlr_sspnd_resume_proc(sched_type, plp->u.pid);
proclist_destroy(plp);
}
}
@@ -7293,9 +7293,7 @@ msb_scheduler_type_switch(ErtsSchedType sched_type,
Uint32 nrml_prio, dcpu_prio, dio_prio;
ErtsSchedType exec_type;
ErtsRunQueue *exec_rq;
-#ifdef DEBUG
erts_aint32_t dbg_val;
-#endif
ASSERT(schdlr_sspnd.msb.ongoing);
@@ -7410,16 +7408,12 @@ msb_scheduler_type_switch(ErtsSchedType sched_type,
* Suspend this scheduler and wake up scheduler
* number one of another type...
*/
-#ifdef DEBUG
dbg_val =
-#else
- (void)
-#endif
erts_atomic32_read_bset_mb(&esdp->ssi->flags,
(ERTS_SSI_FLG_SUSPENDED
| ERTS_SSI_FLG_MSB_EXEC),
ERTS_SSI_FLG_SUSPENDED);
- ASSERT(dbg_val & ERTS_SSI_FLG_MSB_EXEC);
+ ASSERT(dbg_val & ERTS_SSI_FLG_MSB_EXEC); (void)dbg_val;
switch (exec_type) {
case ERTS_SCHED_NORMAL:
@@ -7437,11 +7431,7 @@ msb_scheduler_type_switch(ErtsSchedType sched_type,
break;
}
-#ifdef DEBUG
dbg_val =
-#else
- (void)
-#endif
erts_atomic32_read_bset_mb(&exec_rq->scheduler->ssi->flags,
(ERTS_SSI_FLG_SUSPENDED
| ERTS_SSI_FLG_MSB_EXEC),
@@ -7659,7 +7649,7 @@ suspend_scheduler(ErtsSchedulerData *esdp)
else {
schdlr_sspnd.changer = am_true; /* change right in transit */
/* resume process that is queued for next change... */
- resume.onln.nxt = plp->pid;
+ resume.onln.nxt = plp->u.pid;
ASSERT(is_internal_pid(resume.onln.nxt));
}
}
@@ -7879,7 +7869,7 @@ abort_sched_onln_chng_waitq(Process *p)
proclist_destroy(plp);
plp = erts_proclist_peek_first(schdlr_sspnd.chngq);
if (plp)
- resume = plp->pid;
+ resume = plp->u.pid;
else
schdlr_sspnd.changer = am_false;
}
@@ -8145,7 +8135,8 @@ done:
ErtsSchedSuspendResult
erts_block_multi_scheduling(Process *p, ErtsProcLocks plocks, int on, int normal, int all)
{
- int resume_proc, ix, res, have_unlocked_plocks = 0;
+ ErtsSchedSuspendResult res;
+ int resume_proc, ix, have_unlocked_plocks = 0;
ErtsProcList *plp;
ErtsMultiSchedulingBlock *msbp;
erts_aint32_t chng_flg;
@@ -8378,10 +8369,10 @@ erts_multi_scheduling_blockers(Process *p, int normal)
plp1;
plp1 = erts_proclist_peek_next(msbp->blckrs, plp1)) {
for (plp2 = erts_proclist_peek_first(msbp->blckrs);
- plp2->pid != plp1->pid;
+ plp2->u.pid != plp1->u.pid;
plp2 = erts_proclist_peek_next(msbp->blckrs, plp2));
if (plp2 == plp1) {
- res = CONS(hp, plp1->pid, res);
+ res = CONS(hp, plp1->u.pid, res);
hp += 2;
}
/* else: already in result list */
@@ -9018,11 +9009,8 @@ erts_suspend(Process* c_p, ErtsProcLocks c_p_locks, Port *busy_port)
suspend = 1;
if (suspend) {
-#ifdef DEBUG
- int res =
-#endif
- suspend_process(c_p, c_p);
- ASSERT(res);
+ int res = suspend_process(c_p, c_p);
+ ASSERT(res); (void)res;
}
if (!(c_p_locks & ERTS_PROC_LOCK_STATUS))
@@ -9053,8 +9041,13 @@ erts_resume_processes(ErtsProcList *list)
while (plp) {
Process *proc;
ErtsProcList *fplp;
- ASSERT(is_internal_pid(plp->pid));
- proc = erts_pid2proc(NULL, 0, plp->pid, ERTS_PROC_LOCK_STATUS);
+ ASSERT(is_internal_pid(plp->u.pid) || is_CP((Eterm)plp->u.p));
+ if (is_internal_pid(plp->u.pid))
+ proc = erts_pid2proc(NULL, 0, plp->u.pid, ERTS_PROC_LOCK_STATUS);
+ else {
+ proc = plp->u.p;
+ erts_proc_lock(proc, ERTS_PROC_LOCK_STATUS);
+ }
if (proc) {
if (erts_proclist_same(plp, proc)) {
resume_process(proc, ERTS_PROC_LOCK_STATUS);
@@ -9646,7 +9639,8 @@ Process *erts_schedule(ErtsSchedulerData *esdp, Process *p, int calls)
while (1) {
erts_aint32_t exp, new;
- int run_process;
+ int run_process, not_running, exiting_on_normal_sched,
+ not_suspended, not_exiting_on_dirty_sched;
new = exp = state;
new &= psflg_band_mask;
/*
@@ -9655,29 +9649,33 @@ Process *erts_schedule(ErtsSchedulerData *esdp, Process *p, int calls)
* scheduler, and not suspended (and not in a
* state where suspend should be ignored).
*/
- run_process = (((!(state & (ERTS_PSFLG_RUNNING
- | ERTS_PSFLG_RUNNING_SYS
- | ERTS_PSFLG_DIRTY_RUNNING
- | ERTS_PSFLG_DIRTY_RUNNING_SYS
- | ERTS_PSFLG_FREE)))
- | (((state & (ERTS_PSFLG_RUNNING
-
- | ERTS_PSFLG_FREE
- | ERTS_PSFLG_RUNNING_SYS
- | ERTS_PSFLG_DIRTY_RUNNING_SYS
- | ERTS_PSFLG_EXITING))
- == ERTS_PSFLG_EXITING)
- & (!!is_normal_sched))
- )
- & ((state & (ERTS_PSFLG_SUSPENDED
- | ERTS_PSFLG_EXITING
- | ERTS_PSFLG_FREE
- | ERTS_PSFLG_ACTIVE_SYS
- | ERTS_PSFLG_DIRTY_ACTIVE_SYS))
- != ERTS_PSFLG_SUSPENDED)
- & (!(state & ERTS_PSFLG_EXITING)
- | (!!is_normal_sched))
- );
+ not_running = !(state & (ERTS_PSFLG_RUNNING
+ | ERTS_PSFLG_RUNNING_SYS
+ | ERTS_PSFLG_DIRTY_RUNNING
+ | ERTS_PSFLG_DIRTY_RUNNING_SYS
+ | ERTS_PSFLG_FREE));
+ exiting_on_normal_sched =
+ ((state & (ERTS_PSFLG_RUNNING
+ | ERTS_PSFLG_ACTIVE
+ | ERTS_PSFLG_RUNNING_SYS
+ | ERTS_PSFLG_DIRTY_RUNNING_SYS
+ | ERTS_PSFLG_EXITING))
+ == (ERTS_PSFLG_EXITING|ERTS_PSFLG_ACTIVE))
+ & (!!is_normal_sched);
+
+
+ not_suspended = ((state & (ERTS_PSFLG_SUSPENDED
+ | ERTS_PSFLG_EXITING
+ | ERTS_PSFLG_FREE
+ | ERTS_PSFLG_ACTIVE_SYS
+ | ERTS_PSFLG_DIRTY_ACTIVE_SYS))
+ != ERTS_PSFLG_SUSPENDED);
+
+ not_exiting_on_dirty_sched = !(state & ERTS_PSFLG_EXITING) | (!!is_normal_sched);
+
+ run_process = (not_running | exiting_on_normal_sched)
+ & not_suspended
+ & not_exiting_on_dirty_sched;
if (run_process) {
if (state & (ERTS_PSFLG_ACTIVE_SYS
@@ -9968,7 +9966,7 @@ trace_schedule_in(Process *p, erts_aint32_t state)
/* Clear tracer if it has been removed */
if (erts_is_tracer_proc_enabled(p, ERTS_PROC_LOCK_MAIN, &p->common)) {
- if (state & ERTS_PSFLG_EXITING) {
+ if (state & ERTS_PSFLG_EXITING && p->u.terminate) {
if (ARE_TRACE_FLAGS_ON(p, F_TRACE_SCHED_EXIT))
trace_sched(p, ERTS_PROC_LOCK_MAIN, am_in_exiting);
}
@@ -9992,12 +9990,9 @@ trace_schedule_out(Process *p, erts_aint32_t state)
if (IS_TRACED_FL(p, F_TRACE_CALLS) && !(state & ERTS_PSFLG_FREE))
erts_schedule_time_break(p, ERTS_BP_CALL_TIME_SCHEDULE_OUT);
- if ((state & (ERTS_PSFLG_FREE|ERTS_PSFLG_EXITING)) == ERTS_PSFLG_EXITING) {
+ if (state & ERTS_PSFLG_EXITING && p->u.terminate) {
if (ARE_TRACE_FLAGS_ON(p, F_TRACE_SCHED_EXIT))
- trace_sched(p, ERTS_PROC_LOCK_MAIN,
- ((state & ERTS_PSFLG_FREE)
- ? am_out_exited
- : am_out_exiting));
+ trace_sched(p, ERTS_PROC_LOCK_MAIN, am_out_exiting);
}
else {
if (ARE_TRACE_FLAGS_ON(p, F_TRACE_SCHED) ||
@@ -12072,12 +12067,91 @@ erts_set_self_exiting(Process *c_p, Eterm reason)
add2runq(enqueue, enq_prio, c_p, state, NULL);
}
-void
-erts_proc_exit_handle_monitor(ErtsMonitor *mon, void *vctxt)
+static int
+erts_proc_exit_handle_dist_monitor(ErtsMonitor *mon, void *vctxt, Sint reds)
+{
+ ErtsProcExitContext *ctxt = (ErtsProcExitContext *) vctxt;
+ Process *c_p = ctxt->c_p;
+ Eterm reason = ctxt->reason;
+ int code;
+ ErtsDSigSendContext ctx;
+ ErtsMonLnkDist *dist;
+ DistEntry *dep;
+ Eterm watcher;
+ ErtsMonitorData *mdp = NULL;
+ Eterm watched;
+
+ ASSERT(erts_monitor_is_target(mon) && mon->type == ERTS_MON_TYPE_DIST_PROC);
+
+ mdp = erts_monitor_to_data(mon);
+
+ if (mon->flags & ERTS_ML_FLG_NAME)
+ watched = ((ErtsMonitorDataExtended *) mdp)->u.name;
+ else
+ watched = c_p->common.id;
+ ASSERT(is_internal_pid(watched) || is_atom(watched));
+
+ watcher = mon->other.item;
+ ASSERT(is_external_pid(watcher));
+ dep = external_pid_dist_entry(watcher);
+ ASSERT(dep);
+ dist = ((ErtsMonitorDataExtended *) mdp)->dist;
+ ASSERT(dist);
+
+ code = erts_dsig_prepare(&ctx, dep, c_p, ERTS_PROC_LOCK_MAIN,
+ ERTS_DSP_NO_LOCK, 0, 0, 1);
+
+ ctx.reds = (Sint) (reds * TERM_TO_BINARY_LOOP_FACTOR);
+
+ switch (code) {
+ case ERTS_DSIG_PREP_NOT_ALIVE:
+ case ERTS_DSIG_PREP_NOT_CONNECTED:
+ break;
+ case ERTS_DSIG_PREP_PENDING:
+ case ERTS_DSIG_PREP_CONNECTED:
+ if (dist->connection_id != ctx.connection_id)
+ break;
+ code = erts_dsig_send_m_exit(&ctx,
+ watcher,
+ watched,
+ mdp->ref,
+ reason);
+ switch (code) {
+ case ERTS_DSIG_SEND_CONTINUE:
+ erts_set_gc_state(c_p, 0);
+ ctxt->dist_state = erts_dsend_export_trap_context(c_p, &ctx);
+ /* fall-through */
+ case ERTS_DSIG_SEND_YIELD:
+ break;
+ case ERTS_DSIG_SEND_OK:
+ break;
+ case ERTS_DSIG_SEND_TOO_LRG:
+ erts_set_gc_state(c_p, 1);
+ break;
+ default:
+ ASSERT(! "Invalid dsig send exit monitor result");
+ break;
+ }
+ break;
+ default:
+ ASSERT(! "Invalid dsig prep exit monitor result");
+ break;
+ }
+ if (!erts_monitor_dist_delete(&mdp->origin))
+ erts_monitor_release(mon);
+ else
+ erts_monitor_release_both(mdp);
+ return reds - (ctx.reds / TERM_TO_BINARY_LOOP_FACTOR);
+}
+
+int
+erts_proc_exit_handle_monitor(ErtsMonitor *mon, void *vctxt, Sint reds)
{
- Process *c_p = ((ErtsProcExitContext *) vctxt)->c_p;
- Eterm reason = ((ErtsProcExitContext *) vctxt)->reason;
+ ErtsProcExitContext *ctxt = (ErtsProcExitContext *) vctxt;
+ Process *c_p = ctxt->c_p;
+ Eterm reason = ctxt->reason;
ErtsMonitorData *mdp = NULL;
+ int res = 1;
if (erts_monitor_is_target(mon)) {
/* We are being watched... */
@@ -12107,43 +12181,48 @@ erts_proc_exit_handle_monitor(ErtsMonitor *mon, void *vctxt)
case ERTS_MON_TYPE_DIST_PROC: {
ErtsMonLnkDist *dist;
DistEntry *dep;
- ErtsDSigData dsd;
+ ErtsDSigSendContext ctx;
int code;
Eterm watcher;
Eterm watched;
- mdp = erts_monitor_to_data(mon);
-
- if (mon->flags & ERTS_ML_FLG_NAME)
- watched = ((ErtsMonitorDataExtended *) mdp)->u.name;
- else
- watched = c_p->common.id;
- ASSERT(is_internal_pid(watched) || is_atom(watched));
+ if (is_immed(reason)) {
+ mdp = erts_monitor_to_data(mon);
- watcher = mon->other.item;
- ASSERT(is_external_pid(watcher));
- dep = external_pid_dist_entry(watcher);
- ASSERT(dep);
- dist = ((ErtsMonitorDataExtended *) mdp)->dist;
- ASSERT(dist);
- code = erts_dsig_prepare(&dsd, dep, NULL, 0,
- ERTS_DSP_NO_LOCK, 0, 0);
- switch (code) {
- case ERTS_DSIG_PREP_CONNECTED:
- case ERTS_DSIG_PREP_PENDING:
- if (dist->connection_id == dsd.connection_id) {
- code = erts_dsig_send_m_exit(&dsd,
- watcher,
- watched,
- mdp->ref,
- reason);
- ASSERT(code == ERTS_DSIG_SEND_OK);
+ if (mon->flags & ERTS_ML_FLG_NAME)
+ watched = ((ErtsMonitorDataExtended *) mdp)->u.name;
+ else
+ watched = c_p->common.id;
+ ASSERT(is_internal_pid(watched) || is_atom(watched));
+
+ watcher = mon->other.item;
+ ASSERT(is_external_pid(watcher));
+ dep = external_pid_dist_entry(watcher);
+ ASSERT(dep);
+ dist = ((ErtsMonitorDataExtended *) mdp)->dist;
+ ASSERT(dist);
+ code = erts_dsig_prepare(&ctx, dep, NULL, 0,
+ ERTS_DSP_NO_LOCK, 1, 1, 0);
+ switch (code) {
+ case ERTS_DSIG_PREP_CONNECTED:
+ case ERTS_DSIG_PREP_PENDING:
+ if (dist->connection_id == ctx.connection_id) {
+ code = erts_dsig_send_m_exit(&ctx,
+ watcher,
+ watched,
+ mdp->ref,
+ reason);
+ ASSERT(code == ERTS_DSIG_SEND_OK);
+ }
+ default:
+ break;
}
- default:
- break;
+ if (!erts_monitor_dist_delete(&mdp->origin))
+ mdp = NULL;
+ } else {
+ erts_monitor_tree_insert(&ctxt->dist_monitors, mon);
+ return 1;
}
- if (!erts_monitor_dist_delete(&mdp->origin))
- mdp = NULL;
break;
}
default:
@@ -12185,7 +12264,7 @@ erts_proc_exit_handle_monitor(ErtsMonitor *mon, void *vctxt)
case ERTS_MON_TYPE_DIST_PROC: {
ErtsMonLnkDist *dist;
DistEntry *dep;
- ErtsDSigData dsd;
+ ErtsDSigSendContext ctx;
int code;
Eterm watched;
@@ -12202,17 +12281,16 @@ erts_proc_exit_handle_monitor(ErtsMonitor *mon, void *vctxt)
ASSERT(is_external_pid(watched));
dep = external_pid_dist_entry(watched);
}
- code = erts_dsig_prepare(&dsd, dep, NULL, 0,
- ERTS_DSP_NO_LOCK, 0, 0);
+ code = erts_dsig_prepare(&ctx, dep, NULL, 0,
+ ERTS_DSP_NO_LOCK, 1, 1, 0);
switch (code) {
case ERTS_DSIG_PREP_CONNECTED:
case ERTS_DSIG_PREP_PENDING:
- if (dist->connection_id == dsd.connection_id) {
- code = erts_dsig_send_demonitor(&dsd,
+ if (dist->connection_id == ctx.connection_id) {
+ code = erts_dsig_send_demonitor(&ctx,
c_p->common.id,
watched,
- mdp->ref,
- 1);
+ mdp->ref);
ASSERT(code == ERTS_DSIG_SEND_OK);
}
default:
@@ -12220,6 +12298,7 @@ erts_proc_exit_handle_monitor(ErtsMonitor *mon, void *vctxt)
}
if (!erts_monitor_dist_delete(&mdp->target))
mdp = NULL;
+ res = 100;
break;
}
default:
@@ -12232,11 +12311,84 @@ erts_proc_exit_handle_monitor(ErtsMonitor *mon, void *vctxt)
erts_monitor_release_both(mdp);
else if (mon)
erts_monitor_release(mon);
+ return res;
}
-void
-erts_proc_exit_handle_link(ErtsLink *lnk, void *vctxt)
+static int
+erts_proc_exit_handle_dist_link(ErtsLink *lnk, void *vctxt, Sint reds)
+{
+ ErtsProcExitContext *ctxt = (ErtsProcExitContext *) vctxt;
+ Process *c_p = ctxt->c_p;
+ Eterm reason = ctxt->reason;
+ int code;
+ ErtsDSigSendContext ctx;
+ ErtsMonLnkDist *dist;
+ DistEntry *dep;
+ ErtsLink *dlnk;
+ ErtsLinkData *ldp = NULL;
+
+ ASSERT(lnk->type == ERTS_LNK_TYPE_DIST_PROC);
+ dlnk = erts_link_to_other(lnk, &ldp);
+ dist = ((ErtsLinkDataExtended *) ldp)->dist;
+
+ ASSERT(is_external_pid(lnk->other.item));
+ dep = external_pid_dist_entry(lnk->other.item);
+
+ ASSERT(dep != erts_this_dist_entry);
+
+ if (!erts_link_dist_delete(dlnk))
+ ldp = NULL;
+
+ code = erts_dsig_prepare(&ctx, dep, c_p, ERTS_PROC_LOCK_MAIN,
+ ERTS_DSP_NO_LOCK, 0, 0, 0);
+
+ ctx.reds = (Sint) (reds * TERM_TO_BINARY_LOOP_FACTOR);
+
+ switch (code) {
+ case ERTS_DSIG_PREP_NOT_ALIVE:
+ case ERTS_DSIG_PREP_NOT_CONNECTED:
+ break;
+ case ERTS_DSIG_PREP_PENDING:
+ case ERTS_DSIG_PREP_CONNECTED:
+ if (dist->connection_id != ctx.connection_id)
+ break;
+ code = erts_dsig_send_exit_tt(&ctx,
+ c_p->common.id,
+ lnk->other.item,
+ reason,
+ SEQ_TRACE_TOKEN(c_p));
+ switch (code) {
+ case ERTS_DSIG_SEND_CONTINUE:
+ erts_set_gc_state(c_p, 0);
+ ctxt->dist_state = erts_dsend_export_trap_context(c_p, &ctx);
+ /* fall-through */
+ case ERTS_DSIG_SEND_YIELD:
+ break;
+ case ERTS_DSIG_SEND_OK:
+ break;
+ case ERTS_DSIG_SEND_TOO_LRG:
+ erts_set_gc_state(c_p, 1);
+ break;
+ default:
+ ASSERT(! "Invalid dsig send exit monitor result");
+ break;
+ }
+ break;
+ default:
+ ASSERT(! "Invalid dsig prep exit monitor result");
+ break;
+ }
+ if (ldp)
+ erts_link_release_both(ldp);
+ else if (lnk)
+ erts_link_release(lnk);
+ return reds - (ctx.reds / TERM_TO_BINARY_LOOP_FACTOR);
+}
+
+int
+erts_proc_exit_handle_link(ErtsLink *lnk, void *vctxt, Sint reds)
{
+ ErtsProcExitContext *ctxt = (ErtsProcExitContext *) vctxt;
Process *c_p = ((ErtsProcExitContext *) vctxt)->c_p;
Eterm reason = ((ErtsProcExitContext *) vctxt)->reason;
ErtsLinkData *ldp = NULL;
@@ -12267,32 +12419,40 @@ erts_proc_exit_handle_link(ErtsLink *lnk, void *vctxt)
DistEntry *dep;
ErtsMonLnkDist *dist;
ErtsLink *dlnk;
- ErtsDSigData dsd;
+ ErtsDSigSendContext ctx;
int code;
- dlnk = erts_link_to_other(lnk, &ldp);
- dist = ((ErtsLinkDataExtended *) ldp)->dist;
+ if (is_immed(reason)) {
+ dlnk = erts_link_to_other(lnk, &ldp);
+ dist = ((ErtsLinkDataExtended *) ldp)->dist;
- ASSERT(is_external_pid(lnk->other.item));
- dep = external_pid_dist_entry(lnk->other.item);
+ ASSERT(is_external_pid(lnk->other.item));
+ dep = external_pid_dist_entry(lnk->other.item);
- ASSERT(dep != erts_this_dist_entry);
+ ASSERT(dep != erts_this_dist_entry);
- if (!erts_link_dist_delete(dlnk))
- ldp = NULL;
+ if (!erts_link_dist_delete(dlnk))
+ ldp = NULL;
- code = erts_dsig_prepare(&dsd, dep, c_p, 0, ERTS_DSP_NO_LOCK, 0, 0);
- switch (code) {
- case ERTS_DSIG_PREP_CONNECTED:
- case ERTS_DSIG_PREP_PENDING:
- if (dist->connection_id == dsd.connection_id) {
- code = erts_dsig_send_exit_tt(&dsd,
- c_p->common.id,
- lnk->other.item,
- reason,
- SEQ_TRACE_TOKEN(c_p));
- ASSERT(code == ERTS_DSIG_SEND_OK);
+ code = erts_dsig_prepare(&ctx, dep, c_p, 0, ERTS_DSP_NO_LOCK, 1, 1, 0);
+ switch (code) {
+ case ERTS_DSIG_PREP_CONNECTED:
+ case ERTS_DSIG_PREP_PENDING:
+ if (dist->connection_id == ctx.connection_id) {
+ code = erts_dsig_send_exit_tt(&ctx,
+ c_p->common.id,
+ lnk->other.item,
+ reason,
+ SEQ_TRACE_TOKEN(c_p));
+ ASSERT(code == ERTS_DSIG_SEND_OK);
+ }
+ break;
+ default:
+ break;
}
+ } else {
+ erts_link_tree_insert(&ctxt->dist_links, lnk);
+ return 1;
}
break;
}
@@ -12305,6 +12465,7 @@ erts_proc_exit_handle_link(ErtsLink *lnk, void *vctxt)
erts_link_release_both(ldp);
else if (lnk)
erts_link_release(lnk);
+ return 1;
}
/* this function fishishes a process and propagates exit messages - called
@@ -12338,11 +12499,8 @@ erts_do_exit_process(Process* p, Eterm reason)
set_self_exiting(p, reason, NULL, NULL, NULL);
- if (IS_TRACED(p)) {
- if (IS_TRACED_FL(p, F_TRACE_CALLS))
- erts_schedule_time_break(p, ERTS_BP_CALL_TIME_SCHEDULE_EXITING);
-
- }
+ if (IS_TRACED_FL(p, F_TRACE_CALLS))
+ erts_schedule_time_break(p, ERTS_BP_CALL_TIME_SCHEDULE_EXITING);
erts_trace_check_exiting(p->common.id);
@@ -12357,288 +12515,444 @@ erts_do_exit_process(Process* p, Eterm reason)
erts_proc_unlock(p, ERTS_PROC_LOCKS_ALL_MINOR);
- if (IS_TRACED_FL(p,F_TRACE_PROCS))
+ if (IS_TRACED_FL(p, F_TRACE_PROCS))
trace_proc(p, ERTS_PROC_LOCK_MAIN, p, am_exit, reason);
-
/*
* p->u.initial of this process can *not* be used anymore;
* will be overwritten by misc termination data.
*/
p->u.terminate = NULL;
+ BUMP_REDS(p, 100);
+
erts_continue_exit_process(p);
}
-void
-erts_continue_exit_process(Process *p)
-{
+enum continue_exit_phase {
+ ERTS_CONTINUE_EXIT_TIMERS,
+ ERTS_CONTINUE_EXIT_BLCKD_MSHED,
+ ERTS_CONTINUE_EXIT_BLCKD_NMSHED,
+ ERTS_CONTINUE_EXIT_USING_DB,
+ ERTS_CONTINUE_EXIT_CLEAN_SYS_TASKS,
+ ERTS_CONTINUE_EXIT_FREE,
+ ERTS_CONTINUE_EXIT_CLEAN_SYS_TASKS_AFTER,
+ ERTS_CONTINUE_EXIT_LINKS,
+ ERTS_CONTINUE_EXIT_MONITORS,
+ ERTS_CONTINUE_EXIT_LT_MONITORS,
+ ERTS_CONTINUE_EXIT_HANDLE_PROC_SIG,
+ ERTS_CONTINUE_EXIT_DIST_LINKS,
+ ERTS_CONTINUE_EXIT_DIST_MONITORS,
+ ERTS_CONTINUE_EXIT_DONE,
+};
+
+struct continue_exit_state {
+ enum continue_exit_phase phase;
ErtsLink *links;
ErtsMonitor *monitors;
ErtsMonitor *lt_monitors;
+ Eterm reason;
+ ErtsProcExitContext pectxt;
+ DistEntry *dep;
+ void *yield_state;
+};
+
+void
+erts_continue_exit_process(Process *p)
+{
+ struct continue_exit_state static_state, *trap_state = &static_state;
ErtsProcLocks curr_locks = ERTS_PROC_LOCK_MAIN;
- Eterm reason = p->fvalue;
- DistEntry *dep = NULL;
erts_aint32_t state;
int delay_del_proc = 0;
- ErtsProcExitContext pectxt;
-
+ Sint reds = ERTS_BIF_REDS_LEFT(p);
#ifdef DEBUG
int yield_allowed = 1;
#endif
+ if (p->u.terminate) {
+ trap_state = p->u.terminate;
+ } else {
+ trap_state->phase = ERTS_CONTINUE_EXIT_TIMERS;
+ trap_state->reason = p->fvalue;
+ trap_state->dep = NULL;
+ trap_state->yield_state = NULL;
+ }
+
ERTS_LC_ASSERT(ERTS_PROC_LOCK_MAIN == erts_proc_lc_my_proc_locks(p));
ASSERT(ERTS_PROC_IS_EXITING(p));
ASSERT(erts_proc_read_refc(p) > 0);
- if (p->bif_timers) {
- if (erts_cancel_bif_timers(p, &p->bif_timers, &p->u.terminate)) {
- ASSERT(erts_proc_read_refc(p) > 0);
- goto yield;
- }
- ASSERT(erts_proc_read_refc(p) > 0);
- p->bif_timers = NULL;
- }
-
- if (p->flags & F_SCHDLR_ONLN_WAITQ)
- abort_sched_onln_chng_waitq(p);
-
- if (p->flags & F_HAVE_BLCKD_MSCHED) {
- ErtsSchedSuspendResult ssr;
- ssr = erts_block_multi_scheduling(p, ERTS_PROC_LOCK_MAIN, 0, 0, 1);
- switch (ssr) {
- case ERTS_SCHDLR_SSPND_YIELD_RESTART:
- goto yield;
- case ERTS_SCHDLR_SSPND_DONE_MSCHED_BLOCKED:
- case ERTS_SCHDLR_SSPND_DONE_NMSCHED_BLOCKED:
- case ERTS_SCHDLR_SSPND_YIELD_DONE_MSCHED_BLOCKED:
- case ERTS_SCHDLR_SSPND_YIELD_DONE_NMSCHED_BLOCKED:
- case ERTS_SCHDLR_SSPND_DONE:
- case ERTS_SCHDLR_SSPND_YIELD_DONE:
- p->flags &= ~F_HAVE_BLCKD_MSCHED;
- break;
- case ERTS_SCHDLR_SSPND_EINVAL:
- default:
- erts_exit(ERTS_ABORT_EXIT, "%s:%d: Internal error: %d\n",
- __FILE__, __LINE__, (int) ssr);
- }
- }
- if (p->flags & F_HAVE_BLCKD_NMSCHED) {
- ErtsSchedSuspendResult ssr;
- ssr = erts_block_multi_scheduling(p, ERTS_PROC_LOCK_MAIN, 0, 1, 1);
- switch (ssr) {
- case ERTS_SCHDLR_SSPND_YIELD_RESTART:
- goto yield;
- case ERTS_SCHDLR_SSPND_DONE_MSCHED_BLOCKED:
- case ERTS_SCHDLR_SSPND_DONE_NMSCHED_BLOCKED:
- case ERTS_SCHDLR_SSPND_YIELD_DONE_MSCHED_BLOCKED:
- case ERTS_SCHDLR_SSPND_YIELD_DONE_NMSCHED_BLOCKED:
- case ERTS_SCHDLR_SSPND_DONE:
- case ERTS_SCHDLR_SSPND_YIELD_DONE:
- p->flags &= ~F_HAVE_BLCKD_MSCHED;
- break;
- case ERTS_SCHDLR_SSPND_EINVAL:
- default:
- erts_exit(ERTS_ABORT_EXIT, "%s:%d: Internal error: %d\n",
- __FILE__, __LINE__, (int) ssr);
- }
- }
+restart:
+ switch (trap_state->phase) {
+ case ERTS_CONTINUE_EXIT_TIMERS:
+ if (p->bif_timers) {
+ reds = erts_cancel_bif_timers(p, &p->bif_timers, &trap_state->yield_state, reds);
+ if (reds <= 0) goto yield;
+ p->bif_timers = NULL;
+ }
- if (p->flags & F_USING_DB) {
- if (erts_db_process_exiting(p, ERTS_PROC_LOCK_MAIN))
- goto yield;
- p->flags &= ~F_USING_DB;
- }
+ if (p->flags & F_SCHDLR_ONLN_WAITQ) {
+ abort_sched_onln_chng_waitq(p);
+ reds -= 100;
+ }
- erts_set_gc_state(p, 1);
- state = erts_atomic32_read_acqb(&p->state);
- if ((state & ERTS_PSFLG_SYS_TASKS) || p->dirty_sys_tasks) {
- if (cleanup_sys_tasks(p, state, CONTEXT_REDS) >= CONTEXT_REDS/2)
- goto yield;
- }
+ trap_state->phase = ERTS_CONTINUE_EXIT_BLCKD_MSHED;
+ if (reds <= 0) goto yield;
+ case ERTS_CONTINUE_EXIT_BLCKD_MSHED:
+
+ if (p->flags & F_HAVE_BLCKD_MSCHED) {
+ ErtsSchedSuspendResult ssr;
+ ssr = erts_block_multi_scheduling(p, ERTS_PROC_LOCK_MAIN, 0, 0, 1);
+ switch (ssr) {
+ case ERTS_SCHDLR_SSPND_DONE:
+ case ERTS_SCHDLR_SSPND_DONE_MSCHED_BLOCKED:
+ case ERTS_SCHDLR_SSPND_DONE_NMSCHED_BLOCKED:
+ p->flags &= ~F_HAVE_BLCKD_MSCHED;
+ break;
+ default:
+ erts_exit(ERTS_ABORT_EXIT, "%s:%d: Internal error: %d\n",
+ __FILE__, __LINE__, (int) ssr);
+ }
+ reds -= 100;
+ }
-#ifdef DEBUG
- erts_proc_lock(p, ERTS_PROC_LOCK_STATUS);
- ASSERT(ERTS_PROC_GET_DELAYED_GC_TASK_QS(p) == NULL);
- ASSERT(p->dirty_sys_tasks == NULL);
- erts_proc_unlock(p, ERTS_PROC_LOCK_STATUS);
-#endif
+ trap_state->phase = ERTS_CONTINUE_EXIT_BLCKD_NMSHED;
+ if (reds <= 0) goto yield;
+ case ERTS_CONTINUE_EXIT_BLCKD_NMSHED:
+
+ if (p->flags & F_HAVE_BLCKD_NMSCHED) {
+ ErtsSchedSuspendResult ssr;
+ ssr = erts_block_multi_scheduling(p, ERTS_PROC_LOCK_MAIN, 0, 1, 1);
+ switch (ssr) {
+ case ERTS_SCHDLR_SSPND_DONE:
+ case ERTS_SCHDLR_SSPND_DONE_MSCHED_BLOCKED:
+ case ERTS_SCHDLR_SSPND_DONE_NMSCHED_BLOCKED:
+ p->flags &= ~F_HAVE_BLCKD_MSCHED;
+ break;
+ default:
+ erts_exit(ERTS_ABORT_EXIT, "%s:%d: Internal error: %d\n",
+ __FILE__, __LINE__, (int) ssr);
+ }
+ reds -= 100;
+ }
- if (p->flags & F_USING_DDLL) {
- erts_ddll_proc_dead(p, ERTS_PROC_LOCK_MAIN);
- p->flags &= ~F_USING_DDLL;
- }
+ trap_state->yield_state = NULL;
+ trap_state->phase = ERTS_CONTINUE_EXIT_USING_DB;
+ if (reds <= 0) goto yield;
+ case ERTS_CONTINUE_EXIT_USING_DB:
- /*
- * The registered name *should* be the last "erlang resource" to
- * cleanup.
- */
- if (p->common.u.alive.reg) {
- (void) erts_unregister_name(p, ERTS_PROC_LOCK_MAIN, NULL, THE_NON_VALUE);
- ASSERT(!p->common.u.alive.reg);
- }
+ if (p->flags & F_USING_DB) {
+ if (erts_db_process_exiting(p, ERTS_PROC_LOCK_MAIN, &trap_state->yield_state))
+ goto yield;
+ p->flags &= ~F_USING_DB;
+ }
- if (IS_TRACED_FL(p, F_TRACE_SCHED_EXIT))
- trace_sched(p, curr_locks, am_out_exited);
+ erts_set_gc_state(p, 1);
- erts_proc_lock(p, ERTS_PROC_LOCKS_ALL_MINOR);
- curr_locks = ERTS_PROC_LOCKS_ALL;
+ trap_state->phase = ERTS_CONTINUE_EXIT_CLEAN_SYS_TASKS;
+ case ERTS_CONTINUE_EXIT_CLEAN_SYS_TASKS:
+
+ state = erts_atomic32_read_acqb(&p->state);
+ if ((state & ERTS_PSFLG_SYS_TASKS) || p->dirty_sys_tasks) {
+ reds -= cleanup_sys_tasks(p, state, reds);
+ if (reds <= 0) goto yield;
+ }
+
+ trap_state->phase = ERTS_CONTINUE_EXIT_FREE;
+ case ERTS_CONTINUE_EXIT_FREE:
- /*
- * From this point on we are no longer allowed to yield
- * this process.
- */
#ifdef DEBUG
- yield_allowed = 0;
+ erts_proc_lock(p, ERTS_PROC_LOCK_STATUS);
+ ASSERT(ERTS_PROC_GET_DELAYED_GC_TASK_QS(p) == NULL);
+ ASSERT(p->dirty_sys_tasks == NULL);
+ erts_proc_unlock(p, ERTS_PROC_LOCK_STATUS);
#endif
- /*
- * Note! The monitor and link fields will be overwritten
- * by erts_ptab_delete_element() below.
- */
- links = ERTS_P_LINKS(p);
- monitors = ERTS_P_MONITORS(p);
- lt_monitors = ERTS_P_LT_MONITORS(p);
+ if (p->flags & F_USING_DDLL) {
+ erts_ddll_proc_dead(p, ERTS_PROC_LOCK_MAIN);
+ p->flags &= ~F_USING_DDLL;
+ }
- {
- /* Do *not* use erts_get_runq_proc() */
- ErtsRunQueue *rq;
- rq = erts_get_runq_current(erts_proc_sched_data(p));
+ /*
+ * The registered name *should* be the last "erlang resource" to
+ * cleanup.
+ */
+ if (p->common.u.alive.reg) {
+ (void) erts_unregister_name(p, ERTS_PROC_LOCK_MAIN, NULL, THE_NON_VALUE);
+ ASSERT(!p->common.u.alive.reg);
+ }
- erts_runq_lock(rq);
+ erts_proc_lock(p, ERTS_PROC_LOCKS_ALL_MINOR);
+ curr_locks = ERTS_PROC_LOCKS_ALL;
- ASSERT(p->scheduler_data);
- ASSERT(p->scheduler_data->current_process == p);
- ASSERT(p->scheduler_data->free_process == NULL);
+ /*
+ * Note! The monitor and link fields will be overwritten
+ * by erts_ptab_delete_element() below.
+ */
+ trap_state->links = ERTS_P_LINKS(p);
+ trap_state->monitors = ERTS_P_MONITORS(p);
+ trap_state->lt_monitors = ERTS_P_LT_MONITORS(p);
- p->scheduler_data->current_process = NULL;
- p->scheduler_data->free_process = p;
+ {
+ /* Do *not* use erts_get_runq_proc() */
+ ErtsRunQueue *rq;
+ rq = erts_get_runq_current(erts_proc_sched_data(p));
- /* Time of death! */
- erts_ptab_delete_element(&erts_proc, &p->common);
+ erts_runq_lock(rq);
- erts_runq_unlock(rq);
- }
+ ASSERT(p->scheduler_data);
+ ASSERT(p->scheduler_data->current_process == p);
+ ASSERT(p->scheduler_data->free_process == NULL);
- /*
- * All "erlang resources" have to be deallocated before this point,
- * e.g. registered name, so monitoring and linked processes can
- * be sure that all interesting resources have been deallocated
- * when the monitors and/or links hit.
- */
+ /* Time of death! */
+ erts_ptab_delete_element(&erts_proc, &p->common);
- {
- /* Inactivate and notify free */
- erts_aint32_t n, e, a = erts_atomic32_read_nob(&p->state);
- int refc_inced = 0;
- while (1) {
- n = e = a;
- ASSERT(a & ERTS_PSFLG_EXITING);
- n |= ERTS_PSFLG_FREE;
- n &= ~(ERTS_PSFLG_ACTIVE
- | ERTS_PSFLG_ACTIVE_SYS
- | ERTS_PSFLG_DIRTY_ACTIVE_SYS);
- if ((n & ERTS_PSFLG_IN_RUNQ) && !refc_inced) {
- erts_proc_inc_refc(p);
- refc_inced = 1;
- }
- a = erts_atomic32_cmpxchg_mb(&p->state, n, e);
- if (a == e)
- break;
- }
+ erts_runq_unlock(rq);
+ }
- if (a & (ERTS_PSFLG_DIRTY_RUNNING
- | ERTS_PSFLG_DIRTY_RUNNING_SYS)) {
- p->flags |= F_DELAYED_DEL_PROC;
- delay_del_proc = 1;
- /*
- * The dirty scheduler decrease refc
- * when done with the process...
- */
- }
+ /*
+ * All "erlang resources" have to be deallocated before this point,
+ * e.g. registered name, so monitoring and linked processes can
+ * be sure that all interesting resources have been deallocated
+ * when the monitors and/or links hit.
+ */
- if (refc_inced && !(n & ERTS_PSFLG_IN_RUNQ))
- erts_proc_dec_refc(p);
- }
+ {
+ /* Inactivate and notify free */
+ erts_aint32_t n, e, a = erts_atomic32_read_nob(&p->state);
+ int refc_inced = 0;
+ while (1) {
+ n = e = a;
+ ASSERT(a & ERTS_PSFLG_EXITING);
+ n |= ERTS_PSFLG_FREE;
+ if ((n & ERTS_PSFLG_IN_RUNQ) && !refc_inced) {
+ erts_proc_inc_refc(p);
+ refc_inced = 1;
+ }
+ a = erts_atomic32_cmpxchg_mb(&p->state, n, e);
+ if (a == e)
+ break;
+ }
+
+ if (refc_inced && !(n & ERTS_PSFLG_IN_RUNQ))
+ erts_proc_dec_refc(p);
+ }
- dep = ((p->flags & F_DISTRIBUTION)
- ? ERTS_PROC_SET_DIST_ENTRY(p, NULL)
- : NULL);
+ trap_state->dep = ((p->flags & F_DISTRIBUTION)
+ ? ERTS_PROC_SET_DIST_ENTRY(p, NULL)
+ : NULL);
+ reds -= 50;
- /*
- * It might show up signal prio elevation tasks until we
- * have entered free state. Cleanup such tasks now.
- */
- state = erts_atomic32_read_acqb(&p->state);
- if (!(state & ERTS_PSFLG_SYS_TASKS))
- erts_proc_unlock(p, ERTS_PROC_LOCKS_ALL);
- else {
erts_proc_unlock(p, ERTS_PROC_LOCKS_ALL_MINOR);
+ curr_locks = ERTS_PROC_LOCK_MAIN;
+ trap_state->phase = ERTS_CONTINUE_EXIT_CLEAN_SYS_TASKS_AFTER;
+ case ERTS_CONTINUE_EXIT_CLEAN_SYS_TASKS_AFTER:
+ /*
+ * It might show up signal prio elevation tasks until we
+ * have entered free state. Cleanup such tasks now.
+ */
- do {
- (void) cleanup_sys_tasks(p, state, CONTEXT_REDS);
- state = erts_atomic32_read_acqb(&p->state);
- } while (state & ERTS_PSFLG_SYS_TASKS);
-
- erts_proc_unlock(p, ERTS_PROC_LOCK_MAIN);
- }
+ state = erts_atomic32_read_acqb(&p->state);
+ if ((state & ERTS_PSFLG_SYS_TASKS) || p->dirty_sys_tasks) {
+ reds -= cleanup_sys_tasks(p, state, reds);
+ if (reds <= 0) goto yield;
+ }
+
+ /* Needs to be unlocked for erts_do_net_exits to work?!? */
+ // erts_proc_unlock(p, ERTS_PROC_LOCK_MAIN);
#ifdef DEBUG
- erts_proc_lock(p, ERTS_PROC_LOCK_STATUS);
- ASSERT(p->sys_task_qs == NULL);
- erts_proc_unlock(p, ERTS_PROC_LOCK_STATUS);
+ erts_proc_lock(p, ERTS_PROC_LOCK_STATUS);
+ ASSERT(p->sys_task_qs == NULL);
+ erts_proc_unlock(p, ERTS_PROC_LOCK_STATUS);
#endif
- if (dep) {
- erts_do_net_exits(dep, (reason == am_kill) ? am_killed : reason);
- erts_deref_dist_entry(dep);
- }
+ if (trap_state->dep) {
+ erts_do_net_exits(trap_state->dep,
+ (trap_state->reason == am_kill) ? am_killed : trap_state->reason);
+ erts_deref_dist_entry(trap_state->dep);
+ }
- pectxt.c_p = p;
- pectxt.reason = reason;
+ trap_state->pectxt.c_p = p;
+ trap_state->pectxt.reason = trap_state->reason;
+ trap_state->pectxt.dist_links = NULL;
+ trap_state->pectxt.dist_monitors = NULL;
+ trap_state->pectxt.dist_state = NIL;
+
+ erts_proc_lock(p, ERTS_PROC_LOCK_MSGQ);
+
+ erts_proc_sig_fetch(p);
+
+ erts_proc_unlock(p, ERTS_PROC_LOCK_MSGQ);
+
+ trap_state->yield_state = NULL;
+ trap_state->phase = ERTS_CONTINUE_EXIT_LINKS;
+ if (reds <= 0) goto yield;
+ case ERTS_CONTINUE_EXIT_LINKS:
+
+ reds = erts_link_tree_foreach_delete_yielding(
+ &trap_state->links,
+ erts_proc_exit_handle_link,
+ (void *) &trap_state->pectxt,
+ &trap_state->yield_state,
+ reds);
+ if (reds <= 0)
+ goto yield;
+
+ ASSERT(!trap_state->links);
+ trap_state->yield_state = NULL;
+ trap_state->phase = ERTS_CONTINUE_EXIT_MONITORS;
+ case ERTS_CONTINUE_EXIT_MONITORS:
+
+ reds = erts_monitor_tree_foreach_delete_yielding(
+ &trap_state->monitors,
+ erts_proc_exit_handle_monitor,
+ (void *) &trap_state->pectxt,
+ &trap_state->yield_state,
+ reds);
+ if (reds <= 0)
+ goto yield;
+
+ ASSERT(!trap_state->monitors);
+ trap_state->yield_state = NULL;
+ trap_state->phase = ERTS_CONTINUE_EXIT_LT_MONITORS;
+ case ERTS_CONTINUE_EXIT_LT_MONITORS:
+
+ reds = erts_monitor_list_foreach_delete_yielding(
+ &trap_state->lt_monitors,
+ erts_proc_exit_handle_monitor,
+ (void *) &trap_state->pectxt,
+ &trap_state->yield_state,
+ reds);
+ if (reds <= 0)
+ goto yield;
+
+ ASSERT(!trap_state->lt_monitors);
+ trap_state->phase = ERTS_CONTINUE_EXIT_HANDLE_PROC_SIG;
+ case ERTS_CONTINUE_EXIT_HANDLE_PROC_SIG: {
+ Sint r = reds;
+
+ if (!erts_proc_sig_handle_exit(p, &r))
+ goto yield;
+
+ reds -= r;
+
+ trap_state->phase = ERTS_CONTINUE_EXIT_DIST_LINKS;
+ }
+ case ERTS_CONTINUE_EXIT_DIST_LINKS: {
+
+ continue_dist_send:
+ if (is_not_nil(trap_state->pectxt.dist_state)) {
+ Binary* bin = erts_magic_ref2bin(trap_state->pectxt.dist_state);
+ ErtsDSigSendContext* ctx = (ErtsDSigSendContext*) ERTS_MAGIC_BIN_DATA(bin);
+ Sint initial_reds = (Sint) (ERTS_BIF_REDS_LEFT(p) * TERM_TO_BINARY_LOOP_FACTOR);
+ int result;
+
+ ctx->reds = initial_reds;
+ result = erts_dsig_send(ctx);
+
+ /* erts_dsig_send bumps reductions on the process in the ctx */
+ reds = ERTS_BIF_REDS_LEFT(p);
+
+ switch (result) {
+ case ERTS_DSIG_SEND_OK:
+ case ERTS_DSIG_SEND_TOO_LRG: /*SEND_SYSTEM_LIMIT*/
+ case ERTS_DSIG_SEND_YIELD: /*SEND_YIELD_RETURN*/
+ break;
+ case ERTS_DSIG_SEND_CONTINUE: { /*SEND_YIELD_CONTINUE*/
+ goto yield;
+ }
+ }
+ erts_set_gc_state(p, 1);
+ trap_state->pectxt.dist_state = NIL;
+ if (reds <= 0)
+ goto yield;
+ goto restart;
+ }
- erts_proc_lock(p, ERTS_PROC_LOCK_MAIN|ERTS_PROC_LOCK_MSGQ);
+ reds = erts_link_tree_foreach_delete_yielding(
+ &trap_state->pectxt.dist_links,
+ erts_proc_exit_handle_dist_link,
+ (void *) &trap_state->pectxt,
+ &trap_state->yield_state,
+ reds);
+ if (reds <= 0 || is_not_nil(trap_state->pectxt.dist_state))
+ goto yield;
+ trap_state->phase = ERTS_CONTINUE_EXIT_DIST_MONITORS;
+ }
+ case ERTS_CONTINUE_EXIT_DIST_MONITORS: {
+
+ if (is_not_nil(trap_state->pectxt.dist_state))
+ goto continue_dist_send;
+
+ reds = erts_monitor_tree_foreach_delete_yielding(
+ &trap_state->pectxt.dist_monitors,
+ erts_proc_exit_handle_dist_monitor,
+ (void *) &trap_state->pectxt,
+ &trap_state->yield_state,
+ reds);
+ if (reds <= 0 || is_not_nil(trap_state->pectxt.dist_state))
+ goto yield;
+
+ trap_state->phase = ERTS_CONTINUE_EXIT_DONE;
+ }
+ case ERTS_CONTINUE_EXIT_DONE: {
+ erts_aint_t state;
+ /*
+ * From this point on we are no longer allowed to yield
+ * this process.
+ */
- erts_proc_sig_fetch(p);
+#ifdef DEBUG
+ yield_allowed = 0;
+#endif
- erts_proc_unlock(p, ERTS_PROC_LOCK_MSGQ);
+ /* Set state to not active as we don't want this process
+ to be scheduled in again after this. */
+ state = erts_atomic32_read_band_relb(&p->state,
+ ~(ERTS_PSFLG_ACTIVE
+ | ERTS_PSFLG_ACTIVE_SYS
+ | ERTS_PSFLG_DIRTY_ACTIVE_SYS));
- if (links) {
- erts_link_tree_foreach_delete(&links,
- erts_proc_exit_handle_link,
- (void *) &pectxt);
- ASSERT(!links);
- }
+ ASSERT(p->scheduler_data);
+ ASSERT(p->scheduler_data->current_process == p);
+ ASSERT(p->scheduler_data->free_process == NULL);
- if (monitors) {
- erts_monitor_tree_foreach_delete(&monitors,
- erts_proc_exit_handle_monitor,
- (void *) &pectxt);
- ASSERT(!monitors);
- }
+ p->scheduler_data->current_process = NULL;
+ p->scheduler_data->free_process = p;
- if (lt_monitors) {
- erts_monitor_list_foreach_delete(&lt_monitors,
- erts_proc_exit_handle_monitor,
- (void *) &pectxt);
- ASSERT(!lt_monitors);
- }
+ if (state & (ERTS_PSFLG_DIRTY_RUNNING
+ | ERTS_PSFLG_DIRTY_RUNNING_SYS)) {
+ p->flags |= F_DELAYED_DEL_PROC;
+ delay_del_proc = 1;
+ /*
+ * The dirty scheduler decrease refc
+ * when done with the process...
+ */
+ }
- /*
- * erts_proc_sig_handle_exit() implements yielding.
- * However, this function cannot handle it yet... loop
- * until done...
- */
- while (!0) {
- int reds = CONTEXT_REDS;
- if (erts_proc_sig_handle_exit(p, &reds))
- break;
+ erts_schedule_thr_prgr_later_cleanup_op(
+ (void (*)(void*))erts_proc_dec_refc,
+ (void *) &p->common,
+ &p->common.u.release,
+ sizeof(Process));
+
+ break;
+ }
}
+ if (trap_state != &static_state) {
+ erts_free(ERTS_ALC_T_CONT_EXIT_TRAP, trap_state);
+ p->u.terminate = NULL;
+ }
+
ERTS_CHK_HAVE_ONLY_MAIN_PROC_LOCK(p);
+ if (IS_TRACED_FL(p, F_TRACE_SCHED_EXIT))
+ trace_sched(p, curr_locks, am_out_exited);
+
erts_flush_trace_messages(p, ERTS_PROC_LOCK_MAIN);
ERTS_TRACER_CLEAR(&ERTS_TRACER(p));
@@ -12650,15 +12964,30 @@ erts_continue_exit_process(Process *p)
yield:
-#ifdef DEBUG
ASSERT(yield_allowed);
-#endif
ERTS_LC_ASSERT(curr_locks == erts_proc_lc_my_proc_locks(p));
ERTS_LC_ASSERT(ERTS_PROC_LOCK_MAIN & curr_locks);
+ ASSERT(erts_proc_read_refc(p) > 0);
+
+ if (trap_state == &static_state) {
+ trap_state = erts_alloc(ERTS_ALC_T_CONT_EXIT_TRAP, sizeof(*trap_state));
+ sys_memcpy(trap_state, &static_state, sizeof(*trap_state));
+ p->u.terminate = trap_state;
+ }
+
+ ASSERT(p->scheduler_data);
+ ASSERT(p->scheduler_data->current_process == p);
+ ASSERT(p->scheduler_data->free_process == NULL);
+
+ if (trap_state->phase >= ERTS_CONTINUE_EXIT_FREE) {
+ p->scheduler_data->current_process = NULL;
+ p->scheduler_data->free_process = p;
+ }
p->i = (BeamInstr *) beam_continue_exit;
+ /* Why is this lock take??? */
if (!(curr_locks & ERTS_PROC_LOCK_STATUS)) {
erts_proc_lock(p, ERTS_PROC_LOCK_STATUS);
curr_locks |= ERTS_PROC_LOCK_STATUS;
diff --git a/erts/emulator/beam/erl_process.h b/erts/emulator/beam/erl_process.h
index 43937f216c..3b593bce02 100644
--- a/erts/emulator/beam/erl_process.h
+++ b/erts/emulator/beam/erl_process.h
@@ -381,7 +381,10 @@ struct ErtsSchedulerSleepInfo_ {
typedef struct ErtsProcList_ ErtsProcList;
struct ErtsProcList_ {
- Eterm pid;
+ union {
+ Eterm pid;
+ Process *p;
+ } u;
Uint64 started_interval;
ErtsProcList* next;
ErtsProcList* prev;
@@ -1580,7 +1583,7 @@ ERTS_GLB_INLINE int erts_proclist_is_last(ErtsProcList *, ErtsProcList *);
ERTS_GLB_INLINE int
erts_proclist_same(ErtsProcList *plp, Process *p)
{
- return (plp->pid == p->common.id
+ return ((plp->u.pid == p->common.id || plp->u.p == p)
&& (plp->started_interval
== p->common.u.alive.started_interval));
}
@@ -1819,9 +1822,12 @@ Eterm erts_process_info(Process *c_p, ErtsHeapFactory *hfact,
typedef struct {
Process *c_p;
Eterm reason;
+ ErtsLink *dist_links;
+ ErtsMonitor *dist_monitors;
+ Eterm dist_state;
} ErtsProcExitContext;
-void erts_proc_exit_handle_monitor(ErtsMonitor *mon, void *vctxt);
-void erts_proc_exit_handle_link(ErtsLink *lnk, void *vctxt);
+int erts_proc_exit_handle_monitor(ErtsMonitor *mon, void *vctxt, Sint reds);
+int erts_proc_exit_handle_link(ErtsLink *lnk, void *vctxt, Sint reds);
Eterm erts_get_process_priority(erts_aint32_t state);
Eterm erts_set_process_priority(Process *p, Eterm prio);
diff --git a/erts/emulator/beam/erl_process_dump.c b/erts/emulator/beam/erl_process_dump.c
index ac5054ea10..a164ed543e 100644
--- a/erts/emulator/beam/erl_process_dump.c
+++ b/erts/emulator/beam/erl_process_dump.c
@@ -100,16 +100,18 @@ erts_deep_process_dump(fmtfn_t to, void *to_arg)
dump_binaries(to, to_arg, all_binaries);
}
-static void
-monitor_size(ErtsMonitor *mon, void *vsize)
+static int
+monitor_size(ErtsMonitor *mon, void *vsize, Sint reds)
{
*((Uint *) vsize) += erts_monitor_size(mon);
+ return 1;
}
-static void
-link_size(ErtsMonitor *lnk, void *vsize)
+static int
+link_size(ErtsMonitor *lnk, void *vsize, Sint reds)
{
*((Uint *) vsize) += erts_link_size(lnk);
+ return 1;
}
Uint erts_process_memory(Process *p, int include_sigs_in_transit)
@@ -189,11 +191,11 @@ static ERTS_INLINE void
dump_msg(fmtfn_t to, void *to_arg, ErtsMessage *mp)
{
if (ERTS_SIG_IS_MSG((ErtsSignal *) mp)) {
- Eterm mesg = ERL_MESSAGE_TERM(mp);
- if (is_value(mesg))
- dump_element(to, to_arg, mesg);
+ Eterm mesg;
+ if (ERTS_SIG_IS_INTERNAL_MSG(mp))
+ dump_element(to, to_arg, ERL_MESSAGE_TERM(mp));
else
- dump_dist_ext(to, to_arg, mp->data.dist_ext);
+ dump_dist_ext(to, to_arg, erts_get_dist_ext(mp->data.heap_frag));
mesg = ERL_MESSAGE_TOKEN(mp);
erts_print(to, to_arg, ":");
dump_element(to, to_arg, mesg);
@@ -265,6 +267,7 @@ dump_dist_ext(fmtfn_t to, void *to_arg, ErtsDistExternal *edep)
else {
byte *e;
size_t sz;
+ int i;
if (!(edep->flags & ERTS_DIST_EXT_ATOM_TRANS_TAB))
erts_print(to, to_arg, "D0:");
@@ -274,8 +277,8 @@ dump_dist_ext(fmtfn_t to, void *to_arg, ErtsDistExternal *edep)
for (i = 0; i < edep->attab.size; i++)
dump_element(to, to_arg, edep->attab.atom[i]);
}
- sz = edep->ext_endp - edep->extp;
- e = edep->extp;
+ sz = edep->data->ext_endp - edep->data->extp;
+ e = edep->data->extp;
if (edep->flags & ERTS_DIST_EXT_DFLAG_HDR) {
ASSERT(*e != VERSION_MAGIC);
sz++;
@@ -286,15 +289,19 @@ dump_dist_ext(fmtfn_t to, void *to_arg, ErtsDistExternal *edep)
erts_print(to, to_arg, "E%X:", sz);
if (edep->flags & ERTS_DIST_EXT_DFLAG_HDR) {
byte sbuf[3];
- int i = 0;
+
+ i = 0;
sbuf[i++] = VERSION_MAGIC;
- while (i < sizeof(sbuf) && e < edep->ext_endp) {
+ while (i < sizeof(sbuf) && e < edep->data->ext_endp) {
sbuf[i++] = *e++;
}
erts_print_base64(to, to_arg, sbuf, i);
}
- erts_print_base64(to, to_arg, e, edep->ext_endp - e);
+ erts_print_base64(to, to_arg, e, edep->data->ext_endp - e);
+ for (i = 1; i < edep->data->frag_id; i++)
+ erts_print_base64(to, to_arg, edep->data[i].extp,
+ edep->data[i].ext_endp - edep->data[i].extp);
}
}
@@ -1005,6 +1012,10 @@ dump_module_literals(fmtfn_t to, void *to_arg, ErtsLiteralArea* lit_area)
}
size = 1 + header_arity(w);
switch (w & _HEADER_SUBTAG_MASK) {
+ case FUN_SUBTAG:
+ ASSERT(((ErlFunThing*)(htop))->num_free == 0);
+ size += 1;
+ break;
case MAP_SUBTAG:
if (is_flatmap_header(w)) {
size += 1 + flatmap_get_size(htop);
diff --git a/erts/emulator/beam/erl_ptab.h b/erts/emulator/beam/erl_ptab.h
index 94f0247492..c30a684002 100644
--- a/erts/emulator/beam/erl_ptab.h
+++ b/erts/emulator/beam/erl_ptab.h
@@ -68,8 +68,11 @@ typedef struct {
Uint64 started_interval;
struct reg_proc *reg;
ErtsLink *links;
- ErtsMonitor *monitors;
+ /* Local target monitors, double linked list
+ contains the remote part of local monitors */
ErtsMonitor *lt_monitors;
+ /* other monitors, rb tree */
+ ErtsMonitor *monitors;
} alive;
/* --- While being released --- */
diff --git a/erts/emulator/beam/erl_rbtree.h b/erts/emulator/beam/erl_rbtree.h
index e50abf5cec..ce401fa7e7 100644
--- a/erts/emulator/beam/erl_rbtree.h
+++ b/erts/emulator/beam/erl_rbtree.h
@@ -161,7 +161,7 @@
*
* - void <ERTS_RBT_PREFIX>_rbt_foreach(
* ERTS_RBT_T *tree,
- * void (*op)(ERTS_RBT_T *, void *),
+ * int (*op)(ERTS_RBT_T *, void *),
* void *arg);
* Operate by calling the operator 'op' on each element.
* Order is undefined.
@@ -170,7 +170,7 @@
*
* - void <ERTS_RBT_PREFIX>_rbt_foreach_destroy(
* ERTS_RBT_T *tree,
- * void (*op)(ERTS_RBT_T *, void *),
+ * int (*op)(ERTS_RBT_T *, void *),
* void *arg);
* Operate by calling the operator 'op' on each element.
* Order is undefined. Each element should be destroyed
@@ -180,39 +180,46 @@
*
* - int <ERTS_RBT_PREFIX>_rbt_foreach_yielding(
* ERTS_RBT_T *tree,
- * void (*op)(ERTS_RBT_T *, void *),
+ * int (*op)(ERTS_RBT_T *, void *),
* void *arg,
* <ERTS_RBT_PREFIX>_rbt_yield_state_t *ystate,
- * Sint ylimit);
+ * Sint reds);
* Operate by calling the operator 'op' on each element.
* Order is undefined.
*
- * Yield when 'ylimit' elements has been processed. True is
- * returned when yielding, and false is returned when
- * the whole tree has been processed. The tree should not be
- * modified until all of it has been processed.
+ * Yield when 'reds' reductions has been processed. The 'op'
+ * function return the number of reductions that each element
+ * took to process. The number of reductions remaining is returned,
+ * meaning that if 0 is returned, there are more elements to be
+ * processed. If a value greater than 0 is returned the foreach has
+ * ended. The tree should not be modified until all of it has been
+ * processed.
*
* 'arg' is passed as argument to 'op'.
*
* - int <ERTS_RBT_PREFIX>_rbt_foreach_destroy_yielding(
* ERTS_RBT_T *tree,
- * void (*op)(ERTS_RBT_T *, void *),
+ * int (*op)(ERTS_RBT_T *, void *),
* void *arg,
* <ERTS_RBT_PREFIX>_rbt_yield_state_t *ystate,
- * Sint ylimit);
+ * Sint reds);
* Operate by calling the operator 'op' on each element.
* Order is undefined. Each element should be destroyed
* by 'op'.
*
- * Yield when 'ylimit' elements has been processed. True is
- * returned when yielding, and false is returned when
- * the whole tree has been processed.
+ * Yield when 'reds' reductions has been processed. The 'op'
+ * function return the number of reductions that each element
+ * took to process. The number of reductions remaining is returned,
+ * meaning that if 0 is returned, there are more elements to be
+ * processed. If a value greater than 0 is returned the foreach has
+ * ended. The tree should not be modified until all of it has been
+ * processed.
*
* 'arg' is passed as argument to 'op'.
*
* - void <ERTS_RBT_PREFIX>_rbt_foreach_small(
* ERTS_RBT_T *tree,
- * void (*op)(ERTS_RBT_T *, void *),
+ * int (*op)(ERTS_RBT_T *, void *),
* void *arg);
* Operate by calling the operator 'op' on each element from
* smallest towards larger elements.
@@ -221,7 +228,7 @@
*
* - void <ERTS_RBT_PREFIX>_rbt_foreach_large(
* ERTS_RBT_T *tree,
- * void (*op)(ERTS_RBT_T *, void *),
+ * int (*op)(ERTS_RBT_T *, void *),
* void *arg);
* Operate by calling the operator 'op' on each element from
* largest towards smaller elements.
@@ -230,40 +237,46 @@
*
* - int <ERTS_RBT_PREFIX>_rbt_foreach_small_yielding(
* ERTS_RBT_T *tree,
- * void (*op)(ERTS_RBT_T *, void *),
+ * int (*op)(ERTS_RBT_T *, void *),
* void *arg,
* <ERTS_RBT_PREFIX>_rbt_yield_state_t *ystate,
- * Sint ylimit);
+ * Sint reds);
* Operate by calling the operator 'op' on each element from
* smallest towards larger elements.
*
- * Yield when 'ylimit' elements has been processed. True is
- * returned when yielding, and false is returned when
- * the whole tree has been processed. The tree should not be
- * modified until all of it has been processed.
+ * Yield when 'reds' reductions has been processed. The 'op'
+ * function return the number of reductions that each element
+ * took to process. The number of reductions remaining is returned,
+ * meaning that if 0 is returned, there are more elements to be
+ * processed. If a value greater than 0 is returned the foreach has
+ * ended. The tree should not be modified until all of it has been
+ * processed.
*
* 'arg' is passed as argument to 'op'.
*
* - int <ERTS_RBT_PREFIX>_rbt_foreach_large_yielding(
* ERTS_RBT_T *tree,
- * void (*op)(ERTS_RBT_T *, void *),
+ * int (*op)(ERTS_RBT_T *, void *),
* void *arg,
* <ERTS_RBT_PREFIX>_rbt_yield_state_t *ystate,
- * Sint ylimit);
+ * Sint reds);
* Operate by calling the operator 'op' on each element from
* largest towards smaller elements.
*
- * Yield when 'ylimit' elements has been processed. True is
- * returned when yielding, and false is returned when
- * the whole tree has been processed. The tree should not be
- * modified until all of it has been processed.
+ * Yield when 'reds' reductions has been processed. The 'op'
+ * function return the number of reductions that each element
+ * took to process. The number of reductions remaining is returned,
+ * meaning that if 0 is returned, there are more elements to be
+ * processed. If a value greater than 0 is returned the foreach has
+ * ended. The tree should not be modified until all of it has been
+ * processed.
*
* 'arg' is passed as argument to 'op'.
*
* - void <ERTS_RBT_PREFIX>_rbt_foreach_small_destroy(
* ERTS_RBT_T **tree,
- * void (*op)(ERTS_RBT_T *, void *),
- * void (*destr)(ERTS_RBT_T *, void *),
+ * int (*op)(ERTS_RBT_T *, void *),
+ * int (*destr)(ERTS_RBT_T *, void *),
* void *arg);
* Operate by calling the operator 'op' on each element from
* smallest towards larger elements.
@@ -277,8 +290,8 @@
*
* - void <ERTS_RBT_PREFIX>_rbt_foreach_large_destroy(
* ERTS_RBT_T **tree,
- * void (*op)(ERTS_RBT_T *, void *),
- * void (*destr)(ERTS_RBT_T *, void *),
+ * int (*op)(ERTS_RBT_T *, void *),
+ * int (*destr)(ERTS_RBT_T *, void *),
* void *arg);
* Operate by calling the operator 'op' on each element from
* largest towards smaller elements.
@@ -292,11 +305,11 @@
*
* - int <ERTS_RBT_PREFIX>_rbt_foreach_small_destroy_yielding(
* ERTS_RBT_T **tree,
- * void (*op)(ERTS_RBT_T *, void *),
- * void (*destr)(ERTS_RBT_T *, void *),
+ * int (*op)(ERTS_RBT_T *, void *),
+ * int (*destr)(ERTS_RBT_T *, void *),
* void *arg,
* <ERTS_RBT_PREFIX>_rbt_yield_state_t *ystate,
- * Sint ylimit);
+ * Sint reds);
* Operate by calling the operator 'op' on each element from
* smallest towards larger elements.
*
@@ -305,20 +318,23 @@
* Note that elements are often destroyed in another order
* than the order that the elements are operated on.
*
- * Yield when 'ylimit' elements has been processed. True is
- * returned when yielding, and false is returned when
- * the whole tree has been processed. The tree should not be
- * modified until all of it has been processed.
+ * Yield when 'reds' reductions has been processed. The 'op' and
+ * 'destr' functions return the number of reductions that each element
+ * took to process. The number of reductions remaining is returned,
+ * meaning that if 0 is returned, there are more elements to be
+ * processed. If a value greater than 0 is returned the foreach has
+ * ended. The tree should not be modified until all of it has been
+ * processed.
*
* 'arg' is passed as argument to 'op' and 'destroy'.
*
* - int <ERTS_RBT_PREFIX>_rbt_foreach_large_destroy_yielding(
* ERTS_RBT_T **tree,
- * void (*op)(ERTS_RBT_T *, void *),
- * void (*destr)(ERTS_RBT_T *, void *),
+ * int (*op)(ERTS_RBT_T *, void *),
+ * int (*destr)(ERTS_RBT_T *, void *),
* void *arg,
* <ERTS_RBT_PREFIX>_rbt_yield_state_t *ystate,
- * Sint ylimit);
+ * Sint reds);
* Operate by calling the operator 'op' on each element from
* largest towards smaller elements.
*
@@ -327,10 +343,13 @@
* Note that elements are often destroyed in another order
* than the order that the elements are operated on.
*
- * Yield when 'ylimit' elements has been processed. True is
- * returned when yielding, and false is returned when
- * the whole tree has been processed. The tree should not be
- * modified until all of it has been processed.
+ * Yield when 'reds' reductions has been processed. The 'op' and
+ * 'destr' functions return the number of reductions that each element
+ * took to process. The number of reductions remaining is returned,
+ * meaning that if 0 is returned, there are more elements to be
+ * processed. If a value greater than 0 is returned the foreach has
+ * ended. The tree should not be modified until all of it has been
+ * processed.
*
* 'arg' is passed as argument to 'op' and 'destroy'.
*
@@ -447,17 +466,6 @@
# define ERTS_RBT_API_INLINE__ ERTS_INLINE
#endif
-#ifndef ERTS_RBT_YIELD_STAT_INITER
-# define ERTS_RBT_YIELD_STAT_INITER {NULL, 0}
-#endif
-#ifndef ERTS_RBT_YIELD_STAT_INIT
-# define ERTS_RBT_YIELD_STAT_INIT(YS) \
- do { \
- (YS)->x = NULL; \
- (YS)->up = 0; \
- } while (0)
-#endif
-
#define ERTS_RBT_CONCAT_MACRO_VALUES___(X, Y) \
X ## Y
#define ERTS_RBT_CONCAT_MACRO_VALUES__(X, Y) \
@@ -470,8 +478,38 @@
typedef struct {
ERTS_RBT_T *x;
int up;
+#ifdef DEBUG
+ int debug_red_adj;
+#endif
} ERTS_RBT_YIELD_STATE_T__;
+#define ERTS_RBT_CALLBACK_FOREACH_FUNC(NAME) int (*NAME)(ERTS_RBT_T *, void *, Sint)
+
+#ifndef ERTS_RBT_YIELD_STAT_INITER
+# ifdef DEBUG
+# define ERTS_RBT_YIELD_STAT_INITER {NULL, 0, CONTEXT_REDS}
+# else
+# define ERTS_RBT_YIELD_STAT_INITER {NULL, 0}
+# endif
+#endif
+#ifndef ERTS_RBT_YIELD_STAT_INIT
+# define ERTS_RBT_YIELD_STAT_INIT__(YS) \
+ do { \
+ (YS)->x = NULL; \
+ (YS)->up = 0; \
+ } while (0)
+# ifdef DEBUG
+# define ERTS_RBT_YIELD_STAT_INIT(YS) \
+ do { \
+ ERTS_RBT_YIELD_STAT_INIT__(YS); \
+ (YS)->debug_red_adj = CONTEXT_REDS; \
+ } while(0)
+# else
+# define ERTS_RBT_YIELD_STAT_INIT(YS) ERTS_RBT_YIELD_STAT_INIT__(YS)
+# endif
+#endif
+
+
#define ERTS_RBT_FUNC__(Name) \
ERTS_RBT_CONCAT_MACRO_VALUES__(ERTS_RBT_PREFIX, _rbt_ ## Name)
@@ -1302,11 +1340,11 @@ ERTS_RBT_FUNC__(largest)(ERTS_RBT_T *root)
static ERTS_INLINE int
ERTS_RBT_FUNC__(foreach_unordered__)(ERTS_RBT_T **root,
int destroying,
- void (*op)(ERTS_RBT_T *, void *),
+ ERTS_RBT_CALLBACK_FOREACH_FUNC(op),
void *arg,
- int yielding,
+ int yielding,
ERTS_RBT_YIELD_STATE_T__ *ystate,
- Sint ylimit)
+ Sint reds)
{
ERTS_RBT_T *c, *p, *x;
@@ -1314,13 +1352,17 @@ ERTS_RBT_FUNC__(foreach_unordered__)(ERTS_RBT_T **root,
if (yielding && ystate->x) {
x = ystate->x;
+#ifdef DEBUG
+ if (ystate->debug_red_adj > 0)
+ ystate->debug_red_adj -= 100;
+#endif
ERTS_RBT_ASSERT(ystate->up);
goto restart_up;
}
else {
x = *root;
if (!x)
- return 0;
+ return reds;
if (destroying)
*root = NULL;
}
@@ -1346,10 +1388,10 @@ ERTS_RBT_FUNC__(foreach_unordered__)(ERTS_RBT_T **root,
#ifdef ERTS_RBT_DEBUG
int cdir;
#endif
- if (yielding && ylimit-- <= 0) {
+ if (yielding && reds <= 0) {
ystate->x = x;
ystate->up = 1;
- return 1;
+ return 0;
}
restart_up:
@@ -1375,14 +1417,20 @@ ERTS_RBT_FUNC__(foreach_unordered__)(ERTS_RBT_T **root,
}
#endif
- (*op)(x, arg);
+ reds -= (*op)(x, arg, reds);
+#ifdef DEBUG
+ if (yielding)
+ reds -= ystate->debug_red_adj;
+#endif
if (!p) {
+ /* Done */
if (yielding) {
ystate->x = NULL;
ystate->up = 0;
+ return reds <= 0 ? 1 : reds;
}
- return 0; /* Done */
+ return 1;
}
c = ERTS_RBT_GET_RIGHT(p);
@@ -1407,20 +1455,26 @@ static ERTS_INLINE int
ERTS_RBT_FUNC__(foreach_ordered__)(ERTS_RBT_T **root,
int from_small,
int destroying,
- void (*op)(ERTS_RBT_T *, void *),
- void (*destroy)(ERTS_RBT_T *, void *),
+ ERTS_RBT_CALLBACK_FOREACH_FUNC(op),
+ ERTS_RBT_CALLBACK_FOREACH_FUNC(destroy),
void *arg,
- int yielding,
+ int yielding,
ERTS_RBT_YIELD_STATE_T__ *ystate,
- Sint ylimit)
+ Sint reds)
{
ERTS_RBT_T *c, *p, *x;
ERTS_RBT_ASSERT(!yielding || ystate);
ERTS_RBT_ASSERT(!destroying || destroy);
+ ERTS_RBT_ASSERT(!yielding || yop);
+ ERTS_RBT_ASSERT(yielding || op);
if (yielding && ystate->x) {
x = ystate->x;
+#ifdef DEBUG
+ if (ystate->debug_red_adj > 0)
+ ystate->debug_red_adj -= 100;
+#endif
if (ystate->up)
goto restart_up;
else
@@ -1429,7 +1483,7 @@ ERTS_RBT_FUNC__(foreach_ordered__)(ERTS_RBT_T **root,
else {
x = *root;
if (!x)
- return 0;
+ return reds;
if (destroying)
*root = NULL;
}
@@ -1445,12 +1499,16 @@ ERTS_RBT_FUNC__(foreach_ordered__)(ERTS_RBT_T **root,
x = c;
}
- (*op)(x, arg);
+ reds -= (*op)(x, arg, reds);
+#ifdef DEBUG
+ if (yielding)
+ reds -= ystate->debug_red_adj;
+#endif
- if (yielding && --ylimit <= 0) {
+ if (yielding && reds <= 0) {
ystate->x = x;
ystate->up = 0;
- return 1;
+ return 0;
}
restart_down:
@@ -1472,12 +1530,16 @@ ERTS_RBT_FUNC__(foreach_ordered__)(ERTS_RBT_T **root,
? ERTS_RBT_GET_LEFT(p)
: ERTS_RBT_GET_RIGHT(p)) == x);
- (*op)(p, arg);
+ reds -= (*op)(p, arg, reds);
+#ifdef DEBUG
+ if (yielding)
+ reds -= ystate->debug_red_adj;
+#endif
- if (yielding && --ylimit <= 0) {
+ if (yielding && reds <= 0) {
ystate->x = x;
ystate->up = 1;
- return 1;
+ return 0;
restart_up:
p = ERTS_RBT_GET_PARENT(x);
}
@@ -1510,15 +1572,20 @@ ERTS_RBT_FUNC__(foreach_ordered__)(ERTS_RBT_T **root,
}
#endif
- (*destroy)(x, arg);
+ reds -= (*destroy)(x, arg, reds);
+#ifdef DEBUG
+ if (yielding)
+ reds -= ystate->debug_red_adj;
+#endif
}
if (!p) {
if (yielding) {
ystate->x = NULL;
ystate->up = 0;
+ return reds <= 0 ? 1 : reds;
}
- return 0; /* Done */
+ return 1; /* Done */
}
x = p;
}
@@ -1531,7 +1598,7 @@ ERTS_RBT_FUNC__(foreach_ordered__)(ERTS_RBT_T **root,
static ERTS_RBT_API_INLINE__ void
ERTS_RBT_FUNC__(foreach)(ERTS_RBT_T *root,
- void (*op)(ERTS_RBT_T *, void *),
+ ERTS_RBT_CALLBACK_FOREACH_FUNC(op),
void *arg)
{
(void) ERTS_RBT_FUNC__(foreach_unordered__)(&root, 0, op, arg,
@@ -1544,7 +1611,7 @@ ERTS_RBT_FUNC__(foreach)(ERTS_RBT_T *root,
static ERTS_RBT_API_INLINE__ void
ERTS_RBT_FUNC__(foreach_small)(ERTS_RBT_T *root,
- void (*op)(ERTS_RBT_T *, void *),
+ ERTS_RBT_CALLBACK_FOREACH_FUNC(op),
void *arg)
{
(void) ERTS_RBT_FUNC__(foreach_ordered__)(&root, 1, 0,
@@ -1558,7 +1625,7 @@ ERTS_RBT_FUNC__(foreach_small)(ERTS_RBT_T *root,
static ERTS_RBT_API_INLINE__ void
ERTS_RBT_FUNC__(foreach_large)(ERTS_RBT_T *root,
- void (*op)(ERTS_RBT_T *, void *),
+ ERTS_RBT_CALLBACK_FOREACH_FUNC(op),
void *arg)
{
(void) ERTS_RBT_FUNC__(foreach_ordered__)(&root, 0, 0,
@@ -1572,13 +1639,13 @@ ERTS_RBT_FUNC__(foreach_large)(ERTS_RBT_T *root,
static ERTS_RBT_API_INLINE__ int
ERTS_RBT_FUNC__(foreach_yielding)(ERTS_RBT_T *root,
- void (*op)(ERTS_RBT_T *, void *),
+ ERTS_RBT_CALLBACK_FOREACH_FUNC(op),
void *arg,
ERTS_RBT_YIELD_STATE_T__ *ystate,
- Sint ylimit)
+ Sint reds)
{
return ERTS_RBT_FUNC__(foreach_unordered__)(&root, 0, op, arg,
- 1, ystate, ylimit);
+ 1, ystate, reds);
}
#endif /* ERTS_RBT_WANT_FOREACH_YIELDING */
@@ -1587,14 +1654,14 @@ ERTS_RBT_FUNC__(foreach_yielding)(ERTS_RBT_T *root,
static ERTS_RBT_API_INLINE__ int
ERTS_RBT_FUNC__(foreach_small_yielding)(ERTS_RBT_T *root,
- void (*op)(ERTS_RBT_T *, void *),
+ ERTS_RBT_CALLBACK_FOREACH_FUNC(op),
void *arg,
ERTS_RBT_YIELD_STATE_T__ *ystate,
- Sint ylimit)
+ Sint reds)
{
return ERTS_RBT_FUNC__(foreach_ordered__)(&root, 1, 0,
op, NULL, arg,
- 1, ystate, ylimit);
+ 1, ystate, reds);
}
#endif /* ERTS_RBT_WANT_FOREACH_SMALL_YIELDING */
@@ -1603,14 +1670,14 @@ ERTS_RBT_FUNC__(foreach_small_yielding)(ERTS_RBT_T *root,
static ERTS_RBT_API_INLINE__ int
ERTS_RBT_FUNC__(foreach_large_yielding)(ERTS_RBT_T *root,
- void (*op)(ERTS_RBT_T *, void *),
+ ERTS_RBT_CALLBACK_FOREACH_FUNC(op),
void *arg,
ERTS_RBT_YIELD_STATE_T__ *ystate,
- Sint ylimit)
+ Sint reds)
{
return ERTS_RBT_FUNC__(foreach_ordered__)(&root, 0, 0,
op, NULL, arg,
- 1, ystate, ylimit);
+ 1, ystate, reds);
}
#endif /* ERTS_RBT_WANT_FOREACH_LARGE_YIELDING */
@@ -1619,11 +1686,11 @@ ERTS_RBT_FUNC__(foreach_large_yielding)(ERTS_RBT_T *root,
static ERTS_RBT_API_INLINE__ void
ERTS_RBT_FUNC__(foreach_destroy)(ERTS_RBT_T **root,
- void (*op)(ERTS_RBT_T *, void *),
+ ERTS_RBT_CALLBACK_FOREACH_FUNC(op),
void *arg)
{
(void) ERTS_RBT_FUNC__(foreach_unordered__)(root, 1, op, arg,
- 0, NULL, 0);
+ 0, NULL, 0);
}
#endif /* ERTS_RBT_WANT_FOREACH_DESTROY */
@@ -1632,8 +1699,8 @@ ERTS_RBT_FUNC__(foreach_destroy)(ERTS_RBT_T **root,
static ERTS_RBT_API_INLINE__ void
ERTS_RBT_FUNC__(foreach_small_destroy)(ERTS_RBT_T **root,
- void (*op)(ERTS_RBT_T *, void *),
- void (*destr)(ERTS_RBT_T *, void *),
+ ERTS_RBT_CALLBACK_FOREACH_FUNC(op),
+ ERTS_RBT_CALLBACK_FOREACH_FUNC(destr),
void *arg)
{
(void) ERTS_RBT_FUNC__(foreach_ordered__)(root, 1, 1,
@@ -1647,8 +1714,8 @@ ERTS_RBT_FUNC__(foreach_small_destroy)(ERTS_RBT_T **root,
static ERTS_RBT_API_INLINE__ void
ERTS_RBT_FUNC__(foreach_large_destroy)(ERTS_RBT_T **root,
- void (*op)(ERTS_RBT_T *, void *),
- void (*destr)(ERTS_RBT_T *, void *),
+ ERTS_RBT_CALLBACK_FOREACH_FUNC(op),
+ ERTS_RBT_CALLBACK_FOREACH_FUNC(destr),
void *arg)
{
(void) ERTS_RBT_FUNC__(foreach_ordered__)(root, 0, 1,
@@ -1662,13 +1729,13 @@ ERTS_RBT_FUNC__(foreach_large_destroy)(ERTS_RBT_T **root,
static ERTS_RBT_API_INLINE__ int
ERTS_RBT_FUNC__(foreach_destroy_yielding)(ERTS_RBT_T **root,
- void (*op)(ERTS_RBT_T *, void *),
+ ERTS_RBT_CALLBACK_FOREACH_FUNC(op),
void *arg,
ERTS_RBT_YIELD_STATE_T__ *ystate,
- Sint ylimit)
+ Sint reds)
{
return ERTS_RBT_FUNC__(foreach_unordered__)(root, 1, op, arg,
- 1, ystate, ylimit);
+ 1, ystate, reds);
}
#endif /* ERTS_RBT_WANT_FOREACH_DESTROY_YIELDING */
@@ -1677,15 +1744,15 @@ ERTS_RBT_FUNC__(foreach_destroy_yielding)(ERTS_RBT_T **root,
static ERTS_RBT_API_INLINE__ int
ERTS_RBT_FUNC__(foreach_small_destroy_yielding)(ERTS_RBT_T **root,
- void (*op)(ERTS_RBT_T *, void *),
- void (*destr)(ERTS_RBT_T *, void *),
+ ERTS_RBT_CALLBACK_FOREACH_FUNC(op),
+ ERTS_RBT_CALLBACK_FOREACH_FUNC(destr),
void *arg,
ERTS_RBT_YIELD_STATE_T__ *ystate,
- Sint ylimit)
+ Sint reds)
{
return ERTS_RBT_FUNC__(foreach_ordered__)(root, 1, 1,
op, destr, arg,
- 1, ystate, ylimit);
+ 1, ystate, reds);
}
#endif /* ERTS_RBT_WANT_FOREACH_SMALL_DESTROY_YIELDING */
@@ -1694,15 +1761,15 @@ ERTS_RBT_FUNC__(foreach_small_destroy_yielding)(ERTS_RBT_T **root,
static ERTS_RBT_API_INLINE__ int
ERTS_RBT_FUNC__(foreach_large_destroy_yielding)(ERTS_RBT_T **root,
- void (*op)(ERTS_RBT_T *, void *),
- void (*destr)(ERTS_RBT_T *, void *),
+ ERTS_RBT_CALLBACK_FOREACH_FUNC(op),
+ ERTS_RBT_CALLBACK_FOREACH_FUNC(destr),
void *arg,
ERTS_RBT_YIELD_STATE_T__ *ystate,
- Sint ylimit)
+ Sint reds)
{
return ERTS_RBT_FUNC__(foreach_ordered__)(root, 0, 1,
op, destr, arg,
- 1, ystate, ylimit);
+ 1, ystate, reds);
}
#endif /* ERTS_RBT_WANT_FOREACH_LARGE_DESTROY_YIELDING */
@@ -1855,6 +1922,7 @@ ERTS_RBT_FUNC__(hdbg_check_tree)(ERTS_RBT_T *root, ERTS_RBT_T *n)
#ifdef ERTS_RBT_UNDEF
# undef ERTS_RBT_PREFIX
# undef ERTS_RBT_T
+# undef ERTS_RBT_CALLBACK_FOREACH_FUNC
# undef ERTS_RBT_KEY_T
# undef ERTS_RBT_FLAGS_T
# undef ERTS_RBT_INIT_EMPTY_TNODE
diff --git a/erts/emulator/beam/erl_time_sup.c b/erts/emulator/beam/erl_time_sup.c
index 29c698e34f..d26ea19494 100644
--- a/erts/emulator/beam/erl_time_sup.c
+++ b/erts/emulator/beam/erl_time_sup.c
@@ -1911,8 +1911,8 @@ typedef struct {
ErtsTimeOffsetMonitorInfo *to_mon_info;
} ErtsTimeOffsetMonitorContext;
-static void
-save_time_offset_monitor(ErtsMonitor *mon, void *vcntxt)
+static int
+save_time_offset_monitor(ErtsMonitor *mon, void *vcntxt, Sint reds)
{
ErtsTimeOffsetMonitorContext *cntxt;
ErtsMonitorData *mdp = erts_monitor_to_data(mon);
@@ -1935,7 +1935,7 @@ save_time_offset_monitor(ErtsMonitor *mon, void *vcntxt)
cntxt->to_mon_info[mix].ref
= make_internal_ref(&cntxt->to_mon_info[mix].heap[0]);
-
+ return 1;
}
static void
diff --git a/erts/emulator/beam/erl_trace.c b/erts/emulator/beam/erl_trace.c
index 2350d4c02f..ae7084b7f4 100644
--- a/erts/emulator/beam/erl_trace.c
+++ b/erts/emulator/beam/erl_trace.c
@@ -72,6 +72,7 @@ static ErtsTracer default_port_tracer;
static Eterm system_monitor;
static Eterm system_profile;
+static erts_atomic_t system_logger;
#ifdef HAVE_ERTS_NOW_CPU
int erts_cpu_timestamp;
@@ -340,6 +341,7 @@ void erts_init_trace(void) {
default_port_trace_flags = F_INITIAL_TRACE_FLAGS;
default_port_tracer = erts_tracer_nil;
system_seq_tracer = erts_tracer_nil;
+ erts_atomic_init_nob(&system_logger, am_logger);
init_sys_msg_dispatcher();
init_tracer_nif();
}
@@ -2027,10 +2029,24 @@ enqueue_sys_msg(enum ErtsSysMsgType type,
erts_mtx_unlock(&smq_mtx);
}
+Eterm
+erts_get_system_logger(void)
+{
+ return (Eterm)erts_atomic_read_nob(&system_logger);
+}
+
+Eterm
+erts_set_system_logger(Eterm logger)
+{
+ if (logger != am_logger && logger != am_undefined && !is_internal_pid(logger))
+ return THE_NON_VALUE;
+ return (Eterm)erts_atomic_xchg_nob(&system_logger, logger);
+}
+
void
erts_queue_error_logger_message(Eterm from, Eterm msg, ErlHeapFragment *bp)
{
- enqueue_sys_msg(SYS_MSG_TYPE_ERRLGR, from, am_logger, msg, bp);
+ enqueue_sys_msg(SYS_MSG_TYPE_ERRLGR, from, erts_get_system_logger(), msg, bp);
}
void
@@ -2271,7 +2287,7 @@ sys_msg_dispatcher_func(void *unused)
}
break;
case SYS_MSG_TYPE_ERRLGR:
- receiver = am_logger;
+ receiver = smqp->to;
break;
default:
receiver = NIL;
@@ -2285,8 +2301,15 @@ sys_msg_dispatcher_func(void *unused)
if (is_internal_pid(receiver)) {
proc = erts_pid2proc(NULL, 0, receiver, proc_locks);
if (!proc) {
- /* Bad tracer */
- goto failure;
+ if (smqp->type == SYS_MSG_TYPE_ERRLGR) {
+ /* Bad logger process, send to kernel 'logger' process */
+ erts_set_system_logger(am_logger);
+ receiver = erts_get_system_logger();
+ goto logger;
+ } else {
+ /* Bad tracer */
+ goto failure;
+ }
}
else {
ErtsMessage *mp;
@@ -2299,9 +2322,9 @@ sys_msg_dispatcher_func(void *unused)
#endif
erts_proc_unlock(proc, proc_locks);
}
- }
- else if (receiver == am_logger) {
- proc = erts_whereis_process(NULL,0,receiver,proc_locks,0);
+ } else if (receiver == am_logger) {
+ logger:
+ proc = erts_whereis_process(NULL,0,am_logger,proc_locks,0);
if (!proc)
goto failure;
else if (smqp->from == proc->common.id)
@@ -2309,7 +2332,10 @@ sys_msg_dispatcher_func(void *unused)
else
goto queue_proc_msg;
}
- else if (is_internal_port(receiver)) {
+ else if (receiver == am_undefined) {
+ goto drop_sys_msg;
+ }
+ else if (is_internal_port(receiver)) {
port = erts_thr_id2port_sflgs(receiver,
ERTS_PORT_SFLGS_INVALID_TRACER_LOOKUP);
if (!port)
@@ -2366,7 +2392,7 @@ erts_foreach_sys_msg_in_q(void (*func)(Eterm,
to = erts_get_system_profile();
break;
case SYS_MSG_TYPE_ERRLGR:
- to = am_logger;
+ to = erts_get_system_logger();
break;
default:
to = NIL;
diff --git a/erts/emulator/beam/erl_trace.h b/erts/emulator/beam/erl_trace.h
index bccf31606e..b7844d1cb0 100644
--- a/erts/emulator/beam/erl_trace.h
+++ b/erts/emulator/beam/erl_trace.h
@@ -94,6 +94,8 @@ void erts_foreach_sys_msg_in_q(void (*func)(Eterm,
Eterm,
Eterm,
ErlHeapFragment *));
+Eterm erts_set_system_logger(Eterm);
+Eterm erts_get_system_logger(void);
void erts_queue_error_logger_message(Eterm, Eterm, ErlHeapFragment *);
void erts_send_sys_msg_proc(Eterm, Eterm, Eterm, ErlHeapFragment *);
diff --git a/erts/emulator/beam/erl_unicode.c b/erts/emulator/beam/erl_unicode.c
index d225916ac5..1d6869a7cd 100644
--- a/erts/emulator/beam/erl_unicode.c
+++ b/erts/emulator/beam/erl_unicode.c
@@ -1358,11 +1358,9 @@ Uint erts_atom_to_string_length(Eterm atom)
else {
byte* err_pos;
Uint num_chars;
-#ifdef DEBUG
int ares =
-#endif
erts_analyze_utf8(ap->name, ap->len, &err_pos, &num_chars, NULL);
- ASSERT(ares == ERTS_UTF8_OK);
+ ASSERT(ares == ERTS_UTF8_OK); (void)ares;
return num_chars;
}
diff --git a/erts/emulator/beam/erl_vm.h b/erts/emulator/beam/erl_vm.h
index 4089fac48e..35eae18394 100644
--- a/erts/emulator/beam/erl_vm.h
+++ b/erts/emulator/beam/erl_vm.h
@@ -41,8 +41,8 @@
#define MAX_REG 1024 /* Max number of x(N) registers used */
/*
- * The new arithmetic operations need some extra X registers in the register array.
- * so does the gc_bif's (i_gc_bif3 need 3 extra).
+ * The new trapping length/1 implementation need 3 extra registers in the
+ * register array.
*/
#define ERTS_X_REGS_ALLOCATED (MAX_REG+3)
@@ -146,6 +146,21 @@
(HEAP_TOP(p) = HEAP_TOP(p) + (sz), HEAP_TOP(p) - (sz))))
#endif
+/*
+ * Always allocate in a heap fragment, never on the heap.
+ */
+#if defined(VALGRIND)
+/* Running under valgrind, allocate exactly as much as needed.*/
+# define HeapFragOnlyAlloc(p, sz) \
+ (ASSERT((sz) >= 0), \
+ ErtsHAllocLockCheck(p), \
+ erts_heap_alloc((p),(sz),0))
+#else
+# define HeapFragOnlyAlloc(p, sz) \
+ (ASSERT((sz) >= 0), \
+ ErtsHAllocLockCheck(p), \
+ erts_heap_alloc((p),(sz),512))
+#endif
/*
* Description for each instruction (defined here because the name and
diff --git a/erts/emulator/beam/external.c b/erts/emulator/beam/external.c
index 9a66e491f3..73eae614fa 100644
--- a/erts/emulator/beam/external.c
+++ b/erts/emulator/beam/external.c
@@ -102,7 +102,7 @@ static byte* enc_term(ErtsAtomCacheMap *, Eterm, byte*, Uint32, struct erl_off_h
struct TTBEncodeContext_;
static int enc_term_int(struct TTBEncodeContext_*,ErtsAtomCacheMap *acmp, Eterm obj, byte* ep, Uint32 dflags,
struct erl_off_heap_header** off_heap, Sint *reds, byte **res);
-static Uint is_external_string(Eterm obj, int* p_is_string);
+static int is_external_string(Eterm obj, Uint* lenp);
static byte* enc_atom(ErtsAtomCacheMap *, Eterm, byte*, Uint32);
static byte* enc_pid(ErtsAtomCacheMap *, Eterm, byte*, Uint32);
struct B2TContext_t;
@@ -262,19 +262,12 @@ erts_finalize_atom_cache_map(ErtsAtomCacheMap *acmp, Uint32 dflags)
if (acmp) {
int long_atoms = 0; /* !0 if one or more atoms are longer than 255. */
int i;
- int sz;
- int fix_sz
- = 1 /* VERSION_MAGIC */
- + 1 /* DIST_HEADER */
- + 1 /* dist header flags */
- + 1 /* number of internal cache entries */
- ;
+ int sz = 0;
int min_sz;
ASSERT(dflags & DFLAG_UTF8_ATOMS);
ASSERT(acmp->hdr_sz < 0);
/* Make sure cache update instructions fit */
- min_sz = fix_sz+(2+4)*acmp->sz;
- sz = fix_sz;
+ min_sz = (2+4)*acmp->sz;
for (i = 0; i < acmp->sz; i++) {
Atom *a;
Eterm atom;
@@ -302,17 +295,28 @@ erts_finalize_atom_cache_map(ErtsAtomCacheMap *acmp, Uint32 dflags)
}
Uint
-erts_encode_ext_dist_header_size(ErtsAtomCacheMap *acmp)
+erts_encode_ext_dist_header_size(ErtsAtomCacheMap *acmp, Uint fragments)
{
if (!acmp)
return 0;
else {
+ int fix_sz
+ = 1 /* VERSION_MAGIC */
+ + 1 /* DIST_HEADER */
+ + 1 /* dist header flags */
+ + 1 /* number of internal cache entries */
+ ;
ASSERT(acmp->hdr_sz >= 0);
- return acmp->hdr_sz;
+ if (fragments > 1)
+ fix_sz += 8 /* sequence id */
+ + 8 /* number of fragments */
+ ;
+ return fix_sz + acmp->hdr_sz;
}
}
-byte *erts_encode_ext_dist_header_setup(byte *ctl_ext, ErtsAtomCacheMap *acmp)
+byte *erts_encode_ext_dist_header_setup(byte *ctl_ext, ErtsAtomCacheMap *acmp,
+ Uint fragments, Eterm from)
{
/* Maximum number of atom must be less than the maximum of a 32 bits
unsigned integer. Check is done in erl_init.c, erl_start function. */
@@ -346,12 +350,37 @@ byte *erts_encode_ext_dist_header_setup(byte *ctl_ext, ErtsAtomCacheMap *acmp)
put_int8(acmp->sz, ep);
--ep;
put_int8(dist_hdr_flags, ep);
- *--ep = DIST_HEADER;
- *--ep = VERSION_MAGIC;
+ if (fragments > 1) {
+ ASSERT(is_pid(from));
+ ep -= 8;
+ put_int64(fragments, ep);
+ ep -= 8;
+ put_int64(from, ep);
+ *--ep = DIST_FRAG_HEADER;
+ } else {
+ *--ep = DIST_HEADER;
+ }
+ *--ep = VERSION_MAGIC;
return ep;
}
}
+byte *erts_encode_ext_dist_header_fragment(byte **hdrpp,
+ Uint fragment,
+ Eterm from)
+{
+ byte *ep = *hdrpp, *start = ep;
+ ASSERT(is_pid(from));
+ *ep++ = VERSION_MAGIC;
+ *ep++ = DIST_FRAG_CONT;
+ put_int64(from, ep);
+ ep += 8;
+ put_int64(fragment, ep);
+ ep += 8;
+ *hdrpp = ep;
+ return start;
+}
+
#define PASS_THROUGH 'p'
@@ -365,7 +394,8 @@ Sint erts_encode_ext_dist_header_finalize(ErtsDistOutputBuf* ob,
int ci, sz;
byte dist_hdr_flags;
int long_atoms;
- register byte *ep = ob->extp;
+ Uint64 seq_id = 0, frag_id = 0;
+ register byte *ep = ob->hdrp ? ob->hdrp : ob->extp;
ASSERT(dflags & DFLAG_UTF8_ATOMS);
/*
@@ -416,7 +446,7 @@ Sint erts_encode_ext_dist_header_finalize(ErtsDistOutputBuf* ob,
}
goto done;
}
- else if (ep[1] != DIST_HEADER) {
+ else if (ep[1] != DIST_HEADER && ep[1] != DIST_FRAG_HEADER && ep[1] != DIST_FRAG_CONT) {
ASSERT(ep[1] == SMALL_TUPLE_EXT || ep[1] == LARGE_TUPLE_EXT);
ASSERT(!(dflags & DFLAG_DIST_HDR_ATOM_CACHE));
/* Node without atom cache, 'pass through' needed */
@@ -424,6 +454,17 @@ Sint erts_encode_ext_dist_header_finalize(ErtsDistOutputBuf* ob,
goto done;
}
+ if (ep[1] == DIST_FRAG_CONT) {
+ ep = ob->extp;
+ goto done;
+ } else if (ep[1] == DIST_FRAG_HEADER) {
+ /* skip the seq id and frag id */
+ seq_id = get_int64(&ep[2]);
+ ep += 8;
+ frag_id = get_int64(&ep[2]);
+ ep += 8;
+ }
+
dist_hdr_flags = ep[2];
long_atoms = ERTS_DIST_HDR_LONG_ATOMS_FLG & ((int) dist_hdr_flags);
@@ -546,11 +587,19 @@ Sint erts_encode_ext_dist_header_finalize(ErtsDistOutputBuf* ob,
}
--ep;
put_int8(ci, ep);
- *--ep = DIST_HEADER;
+ if (seq_id) {
+ ep -= 8;
+ put_int64(frag_id, ep);
+ ep -= 8;
+ put_int64(seq_id, ep);
+ *--ep = DIST_FRAG_HEADER;
+ } else {
+ *--ep = DIST_HEADER;
+ }
*--ep = VERSION_MAGIC;
done:
ob->extp = ep;
- ASSERT(&ob->data[0] <= ob->extp && ob->extp < ob->ext_endp);
+ ASSERT((byte*)ob->bin->orig_bytes <= ob->extp && ob->extp < ob->ext_endp);
return reds < 0 ? 0 : reds;
}
@@ -571,7 +620,7 @@ int erts_encode_dist_ext_size(Eterm term, Uint32 flags, ErtsAtomCacheMap *acmp,
}
}
-int erts_encode_dist_ext_size_int(Eterm term, struct erts_dsig_send_context* ctx, Uint* szp)
+int erts_encode_dist_ext_size_int(Eterm term, ErtsDSigSendContext *ctx, Uint* szp)
{
Uint sz;
if (encode_size_struct_int(&ctx->u.sc, ctx->acmp, term, ctx->flags, &ctx->reds, &sz)) {
@@ -635,56 +684,106 @@ byte* erts_encode_ext_ets(Eterm term, byte *ep, struct erl_off_heap_header** off
off_heap);
}
-ErtsDistExternal *
-erts_make_dist_ext_copy(ErtsDistExternal *edep, Uint xsize)
+
+static Uint
+dist_ext_size(ErtsDistExternal *edep)
{
- size_t align_sz;
- size_t dist_ext_sz;
- size_t ext_sz;
- byte *ep;
- ErtsDistExternal *new_edep;
+ Uint sz = sizeof(ErtsDistExternal);
+
+ ASSERT(edep->data->ext_endp && edep->data->extp);
+ ASSERT(edep->data->ext_endp >= edep->data->extp);
+
+ if (edep->flags & ERTS_DIST_EXT_ATOM_TRANS_TAB) {
+ ASSERT(0 <= edep->attab.size \
+ && edep->attab.size <= ERTS_ATOM_CACHE_SIZE);
+ sz -= sizeof(Eterm)*(ERTS_ATOM_CACHE_SIZE - edep->attab.size);
+ } else {
+ sz -= sizeof(ErtsAtomTranslationTable);
+ }
+ return sz;
+}
+
+Uint
+erts_dist_ext_size(ErtsDistExternal *edep)
+{
+ Uint sz = dist_ext_size(edep);
+ sz += edep->data[0].frag_id * sizeof(ErtsDistExternalData);
+ return sz + ERTS_EXTRA_DATA_ALIGN_SZ(sz);
+}
- dist_ext_sz = ERTS_DIST_EXT_SIZE(edep);
- ASSERT(edep->ext_endp && edep->extp);
- ASSERT(edep->ext_endp >= edep->extp);
- ext_sz = edep->ext_endp - edep->extp;
+Uint
+erts_dist_ext_data_size(ErtsDistExternal *edep)
+{
+ Uint sz = 0, i;
+ for (i = 0; i < edep->data->frag_id; i++)
+ sz += edep->data[i].ext_endp - edep->data[i].extp;
+ return sz;
+}
- align_sz = ERTS_EXTRA_DATA_ALIGN_SZ(dist_ext_sz + ext_sz);
+void
+erts_dist_ext_frag(ErtsDistExternalData *ede_datap, ErtsDistExternal *edep)
+{
+ ErtsDistExternalData *new_ede_datap = &edep->data[edep->data->frag_id - ede_datap->frag_id];
+ sys_memcpy(new_ede_datap, ede_datap, sizeof(ErtsDistExternalData));
+
+ /* If the data is not backed by a binary, we create one here to keep
+ things simple. Only custom distribution drivers should use lists. */
+ if (new_ede_datap->binp == NULL) {
+ size_t ext_sz = ede_datap->ext_endp - ede_datap->extp;
+ new_ede_datap->binp = erts_bin_nrml_alloc(ext_sz);
+ sys_memcpy(new_ede_datap->binp->orig_bytes, (void *) ede_datap->extp, ext_sz);
+ new_ede_datap->extp = (byte*)new_ede_datap->binp->orig_bytes;
+ new_ede_datap->ext_endp = (byte*)new_ede_datap->binp->orig_bytes + ext_sz;
+ } else {
+ erts_refc_inc(&new_ede_datap->binp->intern.refc, 2);
+ }
+}
- new_edep = erts_alloc(ERTS_ALC_T_EXT_TERM_DATA,
- dist_ext_sz + ext_sz + align_sz + xsize);
+void
+erts_make_dist_ext_copy(ErtsDistExternal *edep, ErtsDistExternal *new_edep)
+{
+ size_t dist_ext_sz = dist_ext_size(edep);
+ byte *ep;
ep = (byte *) new_edep;
sys_memcpy((void *) ep, (void *) edep, dist_ext_sz);
+ erts_ref_dist_entry(new_edep->dep);
+
ep += dist_ext_sz;
- if (new_edep->dep)
- erts_ref_dist_entry(new_edep->dep);
- new_edep->extp = ep;
- new_edep->ext_endp = ep + ext_sz;
- new_edep->heap_size = -1;
- sys_memcpy((void *) ep, (void *) edep->extp, ext_sz);
- return new_edep;
+
+ new_edep->data = (ErtsDistExternalData*)ep;
+ sys_memzero(new_edep->data, sizeof(ErtsDistExternalData) * edep->data->frag_id);
+ new_edep->data->frag_id = edep->data->frag_id;
+ erts_dist_ext_frag(edep->data, new_edep);
}
-int
+void
+erts_free_dist_ext_copy(ErtsDistExternal *edep)
+{
+ int i;
+ erts_deref_dist_entry(edep->dep);
+ for (i = 0; i < edep->data->frag_id; i++)
+ if (edep->data[i].binp)
+ erts_bin_release(edep->data[i].binp);
+}
+
+ErtsPrepDistExtRes
erts_prepare_dist_ext(ErtsDistExternal *edep,
byte *ext,
Uint size,
+ Binary *binp,
DistEntry *dep,
Uint32 conn_id,
ErtsAtomCache *cache)
{
register byte *ep;
- edep->heap_size = -1;
- edep->flags = 0;
- edep->dep = dep;
-
ASSERT(dep);
erts_de_rlock(dep);
ASSERT(dep->flags & DFLAG_UTF8_ATOMS);
+
if ((dep->state != ERTS_DE_STATE_CONNECTED &&
dep->state != ERTS_DE_STATE_PENDING)
|| dep->connection_id != conn_id) {
@@ -697,7 +796,7 @@ erts_prepare_dist_ext(ErtsDistExternal *edep,
ext++;
size--;
}
- edep->ext_endp = ext + size;
+
ep = ext;
if (size < 2)
@@ -713,16 +812,33 @@ erts_prepare_dist_ext(ErtsDistExternal *edep,
goto fail;
}
+ edep->heap_size = -1;
+ edep->flags = 0;
+ edep->dep = dep;
+ edep->connection_id = conn_id;
+ edep->data->ext_endp = ext+size;
+ edep->data->binp = binp;
+ edep->data->seq_id = 0;
+ edep->data->frag_id = 1;
+
if (dep->flags & DFLAG_DIST_HDR_ATOM_CACHE)
edep->flags |= ERTS_DIST_EXT_DFLAG_HDR;
- edep->connection_id = dep->connection_id;
-
- if (ep[1] != DIST_HEADER) {
+ if (ep[1] != DIST_HEADER && ep[1] != DIST_FRAG_HEADER && ep[1] != DIST_FRAG_CONT) {
if (edep->flags & ERTS_DIST_EXT_DFLAG_HDR)
goto bad_hdr;
edep->attab.size = 0;
- edep->extp = ext;
+ edep->data->extp = ext;
+ }
+ else if (ep[1] == DIST_FRAG_CONT) {
+ if (!(dep->flags & DFLAG_FRAGMENTS))
+ goto bad_hdr;
+ edep->attab.size = 0;
+ edep->data->extp = ext + 1 + 1 + 8 + 8;
+ edep->data->seq_id = get_int64(&ep[2]);
+ edep->data->frag_id = get_int64(&ep[2+8]);
+ erts_de_runlock(dep);
+ return ERTS_PREP_DIST_EXT_FRAG_CONT;
}
else {
int tix;
@@ -731,9 +847,17 @@ erts_prepare_dist_ext(ErtsDistExternal *edep,
if (!(edep->flags & ERTS_DIST_EXT_DFLAG_HDR))
goto bad_hdr;
+ if (ep[1] == DIST_FRAG_HEADER) {
+ if (!(dep->flags & DFLAG_FRAGMENTS))
+ goto bad_hdr;
+ edep->data->seq_id = get_int64(&ep[2]);
+ edep->data->frag_id = get_int64(&ep[2+8]);
+ ep += 16;
+ }
+
#undef CHKSIZE
#define CHKSIZE(SZ) \
- do { if ((SZ) > edep->ext_endp - ep) goto bad_hdr; } while(0)
+ do { if ((SZ) > edep->data->ext_endp - ep) goto bad_hdr; } while(0)
CHKSIZE(1+1+1);
ep += 2;
@@ -863,7 +987,7 @@ erts_prepare_dist_ext(ErtsDistExternal *edep,
#endif
}
}
- edep->extp = ep;
+ edep->data->extp = ep;
#ifdef ERTS_DEBUG_USE_DIST_SEP
if (*ep != VERSION_MAGIC)
goto bad_hdr;
@@ -888,7 +1012,7 @@ erts_prepare_dist_ext(ErtsDistExternal *edep,
erts_this_node->sysname,
edep->dep->sysname,
dist_entry_channel_no(edep->dep));
- for (ep = ext; ep < edep->ext_endp; ep++)
+ for (ep = ext; ep < edep->data->ext_endp; ep++)
erts_dsprintf(dsbufp, ep != ext ? ",%b8u" : "<<%b8u", *ep);
erts_dsprintf(dsbufp, ">>");
erts_send_warning_to_logger_nogl(dsbufp);
@@ -913,9 +1037,9 @@ bad_dist_ext(ErtsDistExternal *edep)
erts_this_node->sysname,
dep->sysname,
dist_entry_channel_no(dep));
- for (ep = edep->extp; ep < edep->ext_endp; ep++)
+ for (ep = edep->data->extp; ep < edep->data->ext_endp; ep++)
erts_dsprintf(dsbufp,
- ep != edep->extp ? ",%b8u" : "<<...,%b8u",
+ ep != edep->data->extp ? ",%b8u" : "<<...,%b8u",
*ep);
erts_dsprintf(dsbufp, ">>\n");
erts_dsprintf(dsbufp, "ATOM_CACHE_REF translations: ");
@@ -933,30 +1057,32 @@ bad_dist_ext(ErtsDistExternal *edep)
}
Sint
-erts_decode_dist_ext_size(ErtsDistExternal *edep)
+erts_decode_dist_ext_size(ErtsDistExternal *edep, int kill_connection)
{
Sint res;
byte *ep;
- if (edep->extp >= edep->ext_endp)
+
+ if (edep->data->extp >= edep->data->ext_endp)
goto fail;
#ifndef ERTS_DEBUG_USE_DIST_SEP
if (edep->flags & ERTS_DIST_EXT_DFLAG_HDR) {
- if (*edep->extp == VERSION_MAGIC)
+ if (*edep->data->extp == VERSION_MAGIC)
goto fail;
- ep = edep->extp;
+ ep = edep->data->extp;
}
else
#endif
{
- if (*edep->extp != VERSION_MAGIC)
+ if (*edep->data->extp != VERSION_MAGIC)
goto fail;
- ep = edep->extp+1;
+ ep = edep->data->extp+1;
}
- res = decoded_size(ep, edep->ext_endp, 0, NULL);
+ res = decoded_size(ep, edep->data->ext_endp, 0, NULL);
if (res >= 0)
return res;
fail:
- bad_dist_ext(edep);
+ if (kill_connection)
+ bad_dist_ext(edep);
return -1;
}
@@ -982,12 +1108,15 @@ Sint erts_decode_ext_size_ets(byte *ext, Uint size)
*/
Eterm
erts_decode_dist_ext(ErtsHeapFactory* factory,
- ErtsDistExternal *edep)
+ ErtsDistExternal *edep,
+ int kill_connection)
{
Eterm obj;
- byte* ep = edep->extp;
+ byte* ep;
- if (ep >= edep->ext_endp)
+ ep = edep->data->extp;
+
+ if (ep >= edep->data->ext_endp)
goto error;
#ifndef ERTS_DEBUG_USE_DIST_SEP
if (edep->flags & ERTS_DIST_EXT_DFLAG_HDR) {
@@ -1005,14 +1134,15 @@ erts_decode_dist_ext(ErtsHeapFactory* factory,
if (!ep)
goto error;
- edep->extp = ep;
+ edep->data->extp = ep;
return obj;
error:
erts_factory_undo(factory);
- bad_dist_ext(edep);
+ if (kill_connection)
+ bad_dist_ext(edep);
return THE_NON_VALUE;
}
@@ -1057,6 +1187,7 @@ BIF_RETTYPE erts_debug_dist_ext_to_term_2(BIF_ALIST_2)
Eterm res;
Sint hsz;
ErtsDistExternal ede;
+ ErtsDistExternalData ede_data;
Eterm *tp;
Eterm real_bin;
Uint offset;
@@ -1069,7 +1200,8 @@ BIF_RETTYPE erts_debug_dist_ext_to_term_2(BIF_ALIST_2)
ede.flags = ERTS_DIST_EXT_ATOM_TRANS_TAB;
ede.dep = NULL;
ede.heap_size = -1;
-
+ ede.data = &ede_data;
+
if (is_not_tuple(BIF_ARG_1))
goto badarg;
tp = tuple_val(BIF_ARG_1);
@@ -1094,15 +1226,15 @@ BIF_RETTYPE erts_debug_dist_ext_to_term_2(BIF_ALIST_2)
if (bitsize != 0)
goto badarg;
- ede.extp = binary_bytes(real_bin)+offset;
- ede.ext_endp = ede.extp + size;
+ ede.data->extp = binary_bytes(real_bin)+offset;
+ ede.data->ext_endp = ede.data->extp + size;
- hsz = erts_decode_dist_ext_size(&ede);
+ hsz = erts_decode_dist_ext_size(&ede, 1);
if (hsz < 0)
goto badarg;
erts_factory_proc_prealloc_init(&factory, BIF_P, hsz);
- res = erts_decode_dist_ext(&factory, &ede);
+ res = erts_decode_dist_ext(&factory, &ede, 1);
erts_factory_close(&factory);
if (is_value(res))
@@ -2348,7 +2480,7 @@ dec_atom(ErtsDistExternal *edep, byte* ep, Eterm* objp)
return ep;
}
-static ERTS_INLINE ErlNode* dec_get_node(Eterm sysname, Uint32 creation)
+static ERTS_INLINE ErlNode* dec_get_node(Eterm sysname, Uint32 creation, Eterm book)
{
if (sysname == INTERNAL_LOCAL_SYSNAME) /* && DFLAG_INTERNAL_TAGS */
return erts_this_node;
@@ -2357,7 +2489,7 @@ static ERTS_INLINE ErlNode* dec_get_node(Eterm sysname, Uint32 creation)
&& (creation == erts_this_node->creation || creation == ORIG_CREATION))
return erts_this_node;
- return erts_find_or_insert_node(sysname,creation);
+ return erts_find_or_insert_node(sysname,creation,book);
}
static byte*
@@ -2403,7 +2535,7 @@ dec_pid(ErtsDistExternal *edep, ErtsHeapFactory* factory, byte* ep,
* We are careful to create the node entry only after all
* validity tests are done.
*/
- node = dec_get_node(sysname, cre);
+ node = dec_get_node(sysname, cre, make_boxed(factory->hp));
if(node == erts_this_node) {
*objp = make_internal_pid(data);
@@ -2481,11 +2613,21 @@ enc_term_int(TTBEncodeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, byte* ep,
{
Eterm* cons = list_val(obj);
Eterm tl;
+ Uint len_cnt = WSTACK_POP(s);
obj = CAR(cons);
tl = CDR(cons);
- WSTACK_PUSH2(s, (is_list(tl) ? ENC_ONE_CONS : ENC_TERM),
- tl);
+ if (is_list(tl)) {
+ len_cnt++;
+ WSTACK_PUSH3(s, len_cnt, ENC_ONE_CONS, tl);
+ }
+ else {
+ byte* list_lenp = (byte*) WSTACK_POP(s);
+ ASSERT(list_lenp[-1] == LIST_EXT);
+ put_int32(len_cnt, list_lenp);
+
+ WSTACK_PUSH2(s, ENC_TERM, tl);
+ }
}
break;
case ENC_PATCH_FUN_SIZE:
@@ -2689,10 +2831,7 @@ enc_term_int(TTBEncodeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, byte* ep,
}
case LIST_DEF:
{
- int is_str;
-
- i = is_external_string(obj, &is_str);
- if (is_str) {
+ if (is_external_string(obj, &i)) {
*ep++ = STRING_EXT;
put_int16(i, ep);
ep += 2;
@@ -2701,9 +2840,12 @@ enc_term_int(TTBEncodeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, byte* ep,
*ep++ = unsigned_val(CAR(cons));
obj = CDR(cons);
}
+ r -= i;
} else {
+ r -= i/2;
*ep++ = LIST_EXT;
- put_int32(i, ep);
+ /* Patch list length when we find end of list */
+ WSTACK_PUSH2(s, (UWord)ep, 1);
ep += 4;
goto encode_one_cons;
}
@@ -2961,9 +3103,13 @@ enc_term_int(TTBEncodeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, byte* ep,
return 0;
}
+/** @brief Is it a list of bytes not longer than MAX_STRING_LEN?
+ * @param lenp out: string length or number of list cells traversed
+ * @return true/false
+ */
static
-Uint
-is_external_string(Eterm list, int* p_is_string)
+int
+is_external_string(Eterm list, Uint* lenp)
{
Uint len = 0;
@@ -2975,29 +3121,15 @@ is_external_string(Eterm list, int* p_is_string)
Eterm* consp = list_val(list);
Eterm hd = CAR(consp);
- if (!is_byte(hd)) {
- break;
+ if (!is_byte(hd) || ++len > MAX_STRING_LEN) {
+ *lenp = len;
+ return 0;
}
- len++;
list = CDR(consp);
}
- /*
- * If we have reached the end of the list, and we have
- * not exceeded the maximum length of a string, this
- * is a string.
- */
- *p_is_string = is_nil(list) && len < MAX_STRING_LEN;
-
- /*
- * Continue to calculate the length.
- */
- while (is_list(list)) {
- Eterm* consp = list_val(list);
- len++;
- list = CDR(consp);
- }
- return len;
+ *lenp = len;
+ return is_nil(list);
}
@@ -3397,7 +3529,7 @@ dec_term_atom_common:
cre = get_int32(ep);
ep += 4;
}
- node = dec_get_node(sysname, cre);
+ node = dec_get_node(sysname, cre, make_boxed(hp));
if(node == erts_this_node) {
*objp = make_internal_port(num);
}
@@ -3477,7 +3609,7 @@ dec_term_atom_common:
if (ref_words > ERTS_MAX_REF_NUMBERS)
goto error;
- node = dec_get_node(sysname, cre);
+ node = dec_get_node(sysname, cre, make_boxed(hp));
if(node == erts_this_node) {
rtp = (ErtsORefThing *) hp;
@@ -4075,8 +4207,8 @@ encode_size_struct_int(TTBSizeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj,
for (;;) {
ASSERT(!is_header(obj));
- if (ctx && --r == 0) {
- *reds = r;
+ if (ctx && --r <= 0) {
+ *reds = 0;
ctx->obj = obj;
ctx->result = result;
WSTACK_SAVE(s, &ctx->wstack);
@@ -4166,8 +4298,10 @@ encode_size_struct_int(TTBSizeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj,
result += (1 + encode_size_struct2(acmp, port_node_name(obj), dflags) +
4 + 1);
break;
- case LIST_DEF:
- if ((m = is_string(obj)) && (m < MAX_STRING_LEN)) {
+ case LIST_DEF: {
+ int is_str = is_external_string(obj, &m);
+ r -= m/2;
+ if (is_str) {
result += m + 2 + 1;
} else {
result += 5;
@@ -4176,6 +4310,7 @@ encode_size_struct_int(TTBSizeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj,
continue; /* big loop */
}
break;
+ }
case TUPLE_DEF:
{
Eterm* ptr = tuple_val(obj);
@@ -4317,7 +4452,7 @@ encode_size_struct_int(TTBSizeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj,
if (is_header(obj)) {
switch (obj) {
- case LIST_TAIL_OP:
+ case LIST_TAIL_OP:
obj = (Eterm) WSTACK_POP(s);
if (is_list(obj)) {
Eterm* cons = list_val(obj);
@@ -4343,7 +4478,7 @@ encode_size_struct_int(TTBSizeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj,
WSTACK_DESTROY(s);
if (ctx) {
ASSERT(ctx->wstack.wstart == NULL);
- *reds = r;
+ *reds = r < 0 ? 0 : r;
}
*res = result;
return 0;
diff --git a/erts/emulator/beam/external.h b/erts/emulator/beam/external.h
index edac177cc6..396cd9f802 100644
--- a/erts/emulator/beam/external.h
+++ b/erts/emulator/beam/external.h
@@ -58,6 +58,8 @@
#define SMALL_ATOM_UTF8_EXT 'w'
#define DIST_HEADER 'D'
+#define DIST_FRAG_HEADER 'E'
+#define DIST_FRAG_CONT 'F'
#define ATOM_CACHE_REF 'R'
#define ATOM_INTERNAL_REF2 'I'
#define ATOM_INTERNAL_REF3 'K'
@@ -122,13 +124,23 @@ typedef struct {
#define ERTS_DIST_CON_ID_MASK ((Uint32) 0x00ffffff) /* also in net_kernel.erl */
-typedef struct {
- DistEntry *dep;
+struct binary;
+typedef struct erl_dist_external_data ErtsDistExternalData;
+
+struct erl_dist_external_data {
+ Uint64 seq_id;
+ Uint64 frag_id;
byte *extp;
byte *ext_endp;
+ struct binary *binp;
+};
+
+typedef struct erl_dist_external {
Sint heap_size;
- Uint32 connection_id;
+ DistEntry *dep;
Uint32 flags;
+ Uint32 connection_id;
+ ErtsDistExternalData *data;
ErtsAtomTranslationTable attab;
} ErtsDistExternal;
@@ -155,8 +167,9 @@ void erts_reset_atom_cache_map(ErtsAtomCacheMap *);
void erts_destroy_atom_cache_map(ErtsAtomCacheMap *);
void erts_finalize_atom_cache_map(ErtsAtomCacheMap *, Uint32);
-Uint erts_encode_ext_dist_header_size(ErtsAtomCacheMap *);
-byte *erts_encode_ext_dist_header_setup(byte *, ErtsAtomCacheMap *);
+Uint erts_encode_ext_dist_header_size(ErtsAtomCacheMap *, Uint);
+byte *erts_encode_ext_dist_header_setup(byte *, ErtsAtomCacheMap *, Uint, Eterm);
+byte *erts_encode_ext_dist_header_fragment(byte **, Uint, Eterm);
Sint erts_encode_ext_dist_header_finalize(ErtsDistOutputBuf*, DistEntry *, Uint32 dflags, Sint reds);
struct erts_dsig_send_context;
int erts_encode_dist_ext_size(Eterm, Uint32, ErtsAtomCacheMap*, Uint* szp);
@@ -171,20 +184,24 @@ 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);
-ERTS_GLB_INLINE void erts_free_dist_ext_copy(ErtsDistExternal *);
-ERTS_GLB_INLINE void *erts_dist_ext_trailer(ErtsDistExternal *);
-ErtsDistExternal *erts_make_dist_ext_copy(ErtsDistExternal *, Uint);
-void *erts_dist_ext_trailer(ErtsDistExternal *);
-void erts_destroy_dist_ext_copy(ErtsDistExternal *);
-
-#define ERTS_PREP_DIST_EXT_FAILED (-1)
-#define ERTS_PREP_DIST_EXT_SUCCESS (0)
-#define ERTS_PREP_DIST_EXT_CLOSED (1)
-
-int erts_prepare_dist_ext(ErtsDistExternal *, byte *, Uint,
- DistEntry *, Uint32 conn_id, ErtsAtomCache *);
-Sint erts_decode_dist_ext_size(ErtsDistExternal *);
-Eterm erts_decode_dist_ext(ErtsHeapFactory* factory, ErtsDistExternal *);
+Uint erts_dist_ext_size(ErtsDistExternal *);
+Uint erts_dist_ext_data_size(ErtsDistExternal *);
+void erts_free_dist_ext_copy(ErtsDistExternal *);
+void erts_make_dist_ext_copy(ErtsDistExternal *, ErtsDistExternal *);
+void erts_dist_ext_frag(ErtsDistExternalData *, ErtsDistExternal *);
+#define erts_get_dist_ext(HFRAG) ((ErtsDistExternal*)((HFRAG)->mem + (HFRAG)->used_size))
+
+typedef enum {
+ ERTS_PREP_DIST_EXT_FAILED,
+ ERTS_PREP_DIST_EXT_SUCCESS,
+ ERTS_PREP_DIST_EXT_FRAG_CONT,
+ ERTS_PREP_DIST_EXT_CLOSED
+} ErtsPrepDistExtRes;
+
+ErtsPrepDistExtRes erts_prepare_dist_ext(ErtsDistExternal *, byte *, Uint, struct binary *,
+ DistEntry *, Uint32, ErtsAtomCache *);
+Sint erts_decode_dist_ext_size(ErtsDistExternal *, int);
+Eterm erts_decode_dist_ext(ErtsHeapFactory*, ErtsDistExternal *, int);
Sint erts_decode_ext_size(byte*, Uint);
Sint erts_decode_ext_size_ets(byte*, Uint);
@@ -200,25 +217,4 @@ int erts_debug_max_atom_out_cache_index(void);
int erts_debug_atom_to_out_cache_index(Eterm);
void transcode_free_ctx(DistEntry* dep);
-#if ERTS_GLB_INLINE_INCL_FUNC_DEF
-
-ERTS_GLB_INLINE void
-erts_free_dist_ext_copy(ErtsDistExternal *edep)
-{
- if (edep->dep)
- erts_deref_dist_entry(edep->dep);
- erts_free(ERTS_ALC_T_EXT_TERM_DATA, edep);
-}
-
-ERTS_GLB_INLINE void *
-erts_dist_ext_trailer(ErtsDistExternal *edep)
-{
- void *res = (void *) (edep->ext_endp
- + ERTS_EXTRA_DATA_ALIGN_SZ(edep->ext_endp));
- ASSERT((((UWord) res) % sizeof(Uint)) == 0);
- return res;
-}
-
-#endif
-
#endif /* ERL_EXTERNAL_H__ */
diff --git a/erts/emulator/beam/global.h b/erts/emulator/beam/global.h
index 0631404599..f9bbe4167f 100644
--- a/erts/emulator/beam/global.h
+++ b/erts/emulator/beam/global.h
@@ -113,6 +113,9 @@ extern Eterm erts_bld_resource_ref(Eterm** hp, ErlOffHeap*, ErtsResource*);
extern void erts_pre_nif(struct enif_environment_t*, Process*,
struct erl_module_nif*, Process* tracee);
extern void erts_post_nif(struct enif_environment_t* env);
+#ifdef DEBUG
+int erts_dbg_is_resource_dying(ErtsResource*);
+#endif
extern void erts_resource_stop(ErtsResource*, ErlNifEvent, int is_direct_call);
void erts_fire_nif_monitor(ErtsMonitor *tmon);
void erts_nif_demonitored(ErtsResource* resource);
@@ -131,6 +134,7 @@ extern Eterm erts_nif_call_function(Process *p, Process *tracee,
int erts_call_dirty_nif(ErtsSchedulerData *esdp, Process *c_p,
BeamInstr *I, Eterm *reg);
+ErtsMessage* erts_create_message_from_nif_env(ErlNifEnv* msg_env);
/* Driver handle (wrapper for old plain handle) */
@@ -292,7 +296,6 @@ union erl_off_heap_ptr {
/* controls warning mapping in error_logger */
extern Eterm node_cookie;
-extern Uint display_items; /* no of items to display in traces etc */
extern int erts_backtrace_depth;
extern erts_atomic32_t erts_max_gen_gcs;
@@ -892,6 +895,11 @@ void erts_init_bif(void);
Eterm erl_send(Process *p, Eterm to, Eterm msg);
int erts_set_group_leader(Process *proc, Eterm new_gl);
+/* erl_bif_guard.c */
+
+void erts_init_bif_guard(void);
+Eterm erts_trapping_length_1(Process* p, Eterm* args);
+
/* erl_bif_op.c */
Eterm erl_is_function(Process* p, Eterm arg1, Eterm arg2);
@@ -962,7 +970,7 @@ void init_break_handler(void);
void erts_set_ignore_break(void);
void erts_replace_intr(void);
void process_info(fmtfn_t, void *);
-void print_process_info(fmtfn_t, void *, Process*);
+void print_process_info(fmtfn_t, void *, Process*, ErtsProcLocks);
void info(fmtfn_t, void *);
void loaded(fmtfn_t, void *);
void erts_print_base64(fmtfn_t to, void *to_arg, byte* src, Uint size);
@@ -1088,7 +1096,7 @@ extern int distribution_info(fmtfn_t, void *);
extern int is_node_name_atom(Eterm a);
extern int erts_net_message(Port *, DistEntry *, Uint32 conn_id,
- byte *, ErlDrvSizeT, byte *, ErlDrvSizeT);
+ byte *, ErlDrvSizeT, Binary *, byte *, ErlDrvSizeT);
extern void init_dist(void);
extern int stop_dist(void);
@@ -1303,14 +1311,7 @@ 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);
Sint erts_unicode_list_to_buf_len(Eterm list);
-struct Sint_buf {
-#if defined(ARCH_64)
- char s[22];
-#else
- char s[12];
-#endif
-};
-char* Sint_to_buf(Sint, struct Sint_buf*);
+int Sint_to_buf(Sint num, int base, char **buf_p, size_t buf_size);
#define ERTS_IOLIST_STATE_INITER(C_P, OBJ) \
{(C_P), 0, 0, (OBJ), {NULL, NULL, NULL, ERTS_ALC_T_INVALID}, 0, 0}
diff --git a/erts/emulator/beam/instrs.tab b/erts/emulator/beam/instrs.tab
index 42c1168f85..fc88cab22f 100644
--- a/erts/emulator/beam/instrs.tab
+++ b/erts/emulator/beam/instrs.tab
@@ -238,6 +238,7 @@ HANDLE_APPLY_FUN_ERROR() {
}
DISPATCH_FUN(I) {
+ //| -no_next
SET_I($I);
Dispatchfun();
}
@@ -299,6 +300,7 @@ i_call_fun_last(Fun, Deallocate) {
}
return() {
+ //| -no_next
SET_I(c_p->cp);
DTRACE_RETURN_FROM_PC(c_p);
@@ -357,7 +359,7 @@ i_get_tuple_element2(Src, Element, Dst) {
dst[1] = E2;
}
-i_get_tuple_element2y(Src, Element, D1, D2) {
+i_get_tuple_element2_dst(Src, Element, D1, D2) {
Eterm* src;
Eterm E1, E2;
src = ADD_BYTE_OFFSET(tuple_val($Src), $Element);
@@ -434,6 +436,30 @@ init(Y) {
make_blank($Y);
}
+init_seq3(Y1) {
+ Eterm* dst = &$Y1;
+ make_blank(dst[0]);
+ make_blank(dst[1]);
+ make_blank(dst[2]);
+}
+
+init_seq4(Y1) {
+ Eterm* dst = &$Y1;
+ make_blank(dst[0]);
+ make_blank(dst[1]);
+ make_blank(dst[2]);
+ make_blank(dst[3]);
+}
+
+init_seq5(Y1) {
+ Eterm* dst = &$Y1;
+ make_blank(dst[0]);
+ make_blank(dst[1]);
+ make_blank(dst[2]);
+ make_blank(dst[3]);
+ make_blank(dst[4]);
+}
+
init2(Y1, Y2) {
make_blank($Y1);
make_blank($Y2);
@@ -559,17 +585,19 @@ update_list(Hd, Dst) {
HTOP += 2;
}
-i_put_tuple := i_put_tuple.make.fill;
-
-i_put_tuple.make(Dst) {
- $Dst = make_tuple(HTOP);
-}
-
-i_put_tuple.fill(Arity) {
+put_tuple2(Dst, Arity) {
Eterm* hp = HTOP;
Eterm arity = $Arity;
+ /*
+ * If operands are not packed (in the 32-bit VM),
+ * is is not safe to use $Dst directly after I
+ * has been updated.
+ */
+ Eterm* dst_ptr = &($Dst);
+
//| -no_next
+ ASSERT(arity != 0);
*hp++ = make_arityval(arity);
I = $NEXT_INSTRUCTION;
do {
@@ -586,6 +614,7 @@ i_put_tuple.fill(Arity) {
break;
}
} while (--arity != 0);
+ *dst_ptr = make_tuple(HTOP);
HTOP = hp;
ASSERT(VALID_INSTR(* (Eterm *)I));
Goto(*I);
@@ -637,12 +666,6 @@ is_nonempty_list(Fail, Src) {
}
}
-is_nonempty_list_test_heap(Fail, Need, Live) {
- //| -no_prefetch
- $is_nonempty_list($Fail, x(0));
- $test_heap($Need, $Live);
-}
-
is_nonempty_list_allocate(Fail, Src, Need, Live) {
//| -no_prefetch
$is_nonempty_list($Fail, $Src);
@@ -655,6 +678,18 @@ is_nonempty_list_get_list(Fail, Src, Hd, Tl) {
$get_list($Src, $Hd, $Tl);
}
+is_nonempty_list_get_hd(Fail, Src, Hd) {
+ //| -no_prefetch
+ $is_nonempty_list($Fail, $Src);
+ $get_hd($Src, $Hd);
+}
+
+is_nonempty_list_get_tl(Fail, Src, Tl) {
+ //| -no_prefetch
+ $is_nonempty_list($Fail, $Src);
+ $get_tl($Src, $Tl);
+}
+
jump(Fail) {
$JUMP($Fail);
}
@@ -704,12 +739,18 @@ is_function(Fail, Src) {
}
}
-is_function2(Fail, Fun, Arity) {
+cold_is_function2(Fail, Fun, Arity) {
if (erl_is_function(c_p, $Fun, $Arity) != am_true ) {
$FAIL($Fail);
}
}
+hot_is_function2(Fail, Fun, Arity) {
+ if (!is_function2($Fun, $Arity)) {
+ $FAIL($Fail);
+ }
+}
+
is_integer(Fail, Src) {
if (is_not_integer($Src)) {
$FAIL($Fail);
@@ -786,6 +827,16 @@ test_arity(Fail, Pointer, Arity) {
}
}
+test_arity_get_tuple_element(Fail, Pointer, Arity, Pos, Dst) {
+ Eterm* ptr = tuple_val($Pointer);
+ Eterm* src;
+ if (*ptr != $Arity) {
+ $FAIL($Fail);
+ }
+ src = ADD_BYTE_OFFSET(ptr, $Pos);
+ $Dst = *src;
+}
+
i_is_eq_exact_immed(Fail, X, Y) {
if ($X != $Y) {
$FAIL($Fail);
@@ -948,6 +999,7 @@ build_stacktrace() {
}
raw_raise() {
+ //| -no_prefetch
Eterm class = x(0);
Eterm value = x(1);
Eterm stacktrace = x(2);
diff --git a/erts/emulator/beam/io.c b/erts/emulator/beam/io.c
index 5325480901..b961c639f5 100644
--- a/erts/emulator/beam/io.c
+++ b/erts/emulator/beam/io.c
@@ -3644,20 +3644,22 @@ typedef struct {
Eterm reason;
} ErtsPortExitContext;
-static void link_port_exit(ErtsLink *lnk, void *vpectxt)
+static int link_port_exit(ErtsLink *lnk, void *vpectxt, Sint reds)
{
ErtsPortExitContext *pectxt = vpectxt;
erts_proc_sig_send_link_exit(NULL, pectxt->port_id,
lnk, pectxt->reason, NIL);
+ return 1;
}
-static void monitor_port_exit(ErtsMonitor *mon, void *vpectxt)
+static int monitor_port_exit(ErtsMonitor *mon, void *vpectxt, Sint reds)
{
ErtsPortExitContext *pectxt = vpectxt;
if (erts_monitor_is_target(mon))
erts_proc_sig_send_monitor_down(mon, pectxt->reason);
else
erts_proc_sig_send_demonitor(mon);
+ return 1;
}
/* 'from' is sending 'this_port' an exit signal, (this_port must be internal).
@@ -4073,7 +4075,7 @@ done:
* to the caller.
*/
int
-erl_drv_port_control(Eterm port_num, char cmd, char* buff, ErlDrvSizeT size)
+erl_drv_port_control(Eterm port_num, unsigned int cmd, char* buff, ErlDrvSizeT size)
{
ErtsProc2PortSigData *sigdp = erts_port_task_alloc_p2p_sig_data();
@@ -4836,7 +4838,7 @@ typedef struct {
void *arg;
} prt_one_lnk_data;
-static void prt_one_monitor(ErtsMonitor *mon, void *vprtd)
+static int prt_one_monitor(ErtsMonitor *mon, void *vprtd, Sint reds)
{
ErtsMonitorData *mdp = erts_monitor_to_data(mon);
prt_one_lnk_data *prtd = (prt_one_lnk_data *) vprtd;
@@ -4844,12 +4846,14 @@ static void prt_one_monitor(ErtsMonitor *mon, void *vprtd)
erts_print(prtd->to, prtd->arg, "(%p,%T)", mon->other.ptr, mdp->ref);
else
erts_print(prtd->to, prtd->arg, "(%T,%T)", mon->other.item, mdp->ref);
+ return 1;
}
-static void prt_one_lnk(ErtsLink *lnk, void *vprtd)
+static int prt_one_lnk(ErtsLink *lnk, void *vprtd, Sint reds)
{
prt_one_lnk_data *prtd = (prt_one_lnk_data *) vprtd;
erts_print(prtd->to, prtd->arg, "%T", lnk->other.item);
+ return 1;
}
static void dump_port_state(fmtfn_t to, void *arg, erts_aint32_t state)
@@ -5100,7 +5104,7 @@ erts_port_resume_procs(Port *prt)
erts_snprintf(port_str, sizeof(DTRACE_CHARBUF_NAME(port_str)), "%T", prt->common.id);
while (plp2 != NULL) {
- erts_snprintf(pid_str, sizeof(DTRACE_CHARBUF_NAME(pid_str)), "%T", plp2->pid);
+ erts_snprintf(pid_str, sizeof(DTRACE_CHARBUF_NAME(pid_str)), "%T", plp2->u.pid);
DTRACE2(process_port_unblocked, pid_str, port_str);
}
}
@@ -6177,6 +6181,7 @@ int driver_output_binary(ErlDrvPort ix, char* hbuf, ErlDrvSizeT hlen,
dep,
conn_id,
(byte*) hbuf, hlen,
+ ErlDrvBinary2Binary(bin),
(byte*) (bin->orig_bytes+offs), len);
}
else
@@ -6222,12 +6227,14 @@ int driver_output2(ErlDrvPort ix, char* hbuf, ErlDrvSizeT hlen,
dep,
conn_id,
NULL, 0,
+ NULL,
(byte*) hbuf, hlen);
else
return erts_net_message(prt,
dep,
conn_id,
(byte*) hbuf, hlen,
+ NULL,
(byte*) buf, len);
}
else if (state & ERTS_PORT_SFLG_LINEBUF_IO)
diff --git a/erts/emulator/beam/msg_instrs.tab b/erts/emulator/beam/msg_instrs.tab
index 9bf3aefaca..6f8d1469ef 100644
--- a/erts/emulator/beam/msg_instrs.tab
+++ b/erts/emulator/beam/msg_instrs.tab
@@ -137,8 +137,8 @@ i_loop_rec(Dest) {
if (ERTS_UNLIKELY(ERTS_SIG_IS_EXTERNAL_MSG(msgp))) {
FCALLS -= 10; /* FIXME: bump appropriate amount... */
- SWAPOUT; /* erts_decode_dist_message() may write to heap... */
- if (!erts_decode_dist_message(c_p, ERTS_PROC_LOCK_MAIN, msgp, 0)) {
+ SWAPOUT; /* erts_proc_sig_decode_dist() may write to heap... */
+ if (!erts_proc_sig_decode_dist(c_p, ERTS_PROC_LOCK_MAIN, msgp, 0)) {
/*
* A corrupt distribution message that we weren't able to decode;
* remove it...
diff --git a/erts/emulator/beam/ops.tab b/erts/emulator/beam/ops.tab
index e76d896ffc..3cfc685336 100644
--- a/erts/emulator/beam/ops.tab
+++ b/erts/emulator/beam/ops.tab
@@ -118,11 +118,21 @@ test_heap I t?
allocate_heap S u==0 R => allocate S R
allocate_heap_zero S u==0 R => allocate_zero S R
-init2 y y
-init3 y y y
+init Y1 | init Y2 | init Y3 | succ(Y1,Y2) | succ(Y2,Y3) => init_seq3 Y1
+init_seq3 Y1 | init Y4 | succ3(Y1,Y4) => init_seq4 Y1
+init_seq4 Y1 | init Y5 | succ4(Y1,Y5) => init_seq5 Y1
+
+init_seq3 y
+init_seq4 y
+init_seq5 y
+
init Y1 | init Y2 | init Y3 => init3 Y1 Y2 Y3
init Y1 | init Y2 => init2 Y1 Y2
+init2 y y
+init3 y y y
+
+
# Selecting values
select_val S=aiq Fail=f Size=u Rest=* => const_select_val(S, Fail, Size, Rest)
@@ -212,7 +222,8 @@ i_get_tuple_element xy P y
%hot
i_get_tuple_element2 x P x
-i_get_tuple_element2y x P y y
+i_get_tuple_element2_dst x P x x
+i_get_tuple_element2_dst x P y y
i_get_tuple_element3 x P x
@@ -288,7 +299,7 @@ move_window4 x x x x y
move_window5 x x x x x y
# Swap registers.
-move R1=x Tmp=x | move R2=xy R1 | move Tmp R2 => swap_temp R1 R2 Tmp
+move R1=x Tmp=x | move R2=x R1 | move Tmp R2 => swap_temp R1 R2 Tmp
swap_temp R1 R2 Tmp | line Loc | apply Live | is_killed_apply(Tmp, Live) => \
swap R1 R2 | line Loc | apply Live
@@ -307,73 +318,84 @@ swap_temp R1 R2 Tmp | line Loc | call_ext_only Live Addr | \
swap_temp R1 R2 Tmp | line Loc | call_ext_last Live Addr D | \
is_killed(Tmp, Live) => swap R1 R2 | line Loc | call_ext_last Live Addr D
-swap_temp x xy x
+swap_temp x x x
-swap x xy
+swap x x
+
+# move_dup
move Src=x D1=x | move Src=x D2=x => move_dup Src D1 D2
-move Src=x SD=x | move SD=x D=x => move_dup Src SD D
-move Src=x D1=x | move Src=x D2=y => move_dup Src D1 D2
-move Src=y SD=x | move SD=x D=y => move_dup Src SD D
-move Src=x SD=x | move SD=x D=y => move_dup Src SD D
-move Src=y SD=x | move SD=x D=x => move_dup Src SD D
-
-move SD=x D=x | move Src=xy SD=x => move_shift Src SD D
-move SD=y D=x | move Src=x SD=y => move_shift Src SD D
-move SD=x D=y | move Src=x SD=x => move_shift Src SD D
-
-# The transformations above guarantee that the source for
-# the second move is not the same as the destination for
-# the first move. That means that we can do the moves in
-# parallel (fetch both values, then store them) which could
-# be faster.
+move Src=x SD=x | move SD=x D=x => move_dup Src SD D
-move X1=x Y1=y | move X2=x Y2=y => move2_par X1 Y1 X2 Y2
-move Y1=y X1=x | move Y2=y X2=x => move2_par Y1 X1 Y2 X2
+move_dup x x x
-move X1=x X2=x | move X3=x X4=x => move2_par X1 X2 X3 X4
+# move_shift
-move X1=x X2=x | move X3=x Y1=y => move2_par X1 X2 X3 Y1
+move SD=x D=x | move Src=xy SD=x | distinct(D, Src) => move_shift Src SD D
+move SD=y D=x | move Src=x SD=y | distinct(D, Src) => move_shift Src SD D
+move SD=x D=y | move Src=x SD=x | distinct(D, Src) => move_shift Src SD D
-move S1=x S2=x | move X1=x Y1=y => move2_par S1 S2 X1 Y1
+move_shift x x x
+move_shift y x x
+move_shift x y x
+move_shift x x y
-move S1=y S2=x | move X1=x Y1=y => move2_par S1 S2 X1 Y1
+# move2_par x x x x
-move Y1=y X1=x | move S1=x D1=x => move2_par Y1 X1 S1 D1
-move S1=x D1=x | move Y1=y X1=x => move2_par S1 D1 Y1 X1
+move X1=x X2=x | move X3=x X4=x | independent_moves(X1, X2, X3, X4) => \
+ move2_par X1 X2 X3 X4
+move2_par x x x x
-move2_par X1=x Y1=y X2=x Y2=y | move X3=x Y3=y => move3 X1 Y1 X2 Y2 X3 Y3
-move2_par Y1=y X1=x Y2=y X2=x | move Y3=y X3=x => move3 Y1 X1 Y2 X2 Y3 X3
-move2_par X1=x X2=x X3=x X4=x | move X5=x X6=x => move3 X1 X2 X3 X4 X5 X6
+# move2_par x y x y
-move C=aiq X=x==1 => move_x1 C
-move C=aiq X=x==2 => move_x2 C
+move X1=x Y1=y | move X2=x Y2=y => move2_par X1 Y1 X2 Y2
+move2_par x y x y
-move_x1 c
-move_x2 c
+# move2_par x x x y
-move_shift x x x
-move_shift y x x
-move_shift x y x
-move_shift x x y
+move X1=x X2=x | move X3=x Y1=y | independent_moves(X1, X2, X3, Y1) => \
+ move2_par X1 X2 X3 Y1
+move X3=x Y1=y | move X1=x X2=x | independent_moves(X3, Y1, X1, X2) => \
+ move2_par X1 X2 X3 Y1
+move2_par x x x y
-move_dup xy x xy
+# move2_par y x y x
-move2_par x y x y
+move Y1=y X1=x | move Y2=y X2=x => move2_par Y1 X1 Y2 X2
move2_par y x y x
-move2_par x x x x
-move2_par x x x y
+# move2_par y x x y
+move S1=y S2=x | move X1=x Y1=y | independent_moves(S1, S2, X1, Y1) => \
+ move2_par S1 S2 X1 Y1
+move X1=x Y1=y | move S1=y S2=x | independent_moves(S1, S2, X1, Y1) => \
+ move2_par S1 S2 X1 Y1
move2_par y x x y
-move2_par x x y x
+# move2_par y x x x
+
+move Y1=y X1=x | move S1=x D1=x | independent_moves(Y1, X1, S1, D1) => \
+ move2_par Y1 X1 S1 D1
+move S1=x D1=x | move Y1=y X1=x | independent_moves(Y1, X1, S1, D1) => \
+ move2_par Y1 X1 S1 D1
move2_par y x x x
-move3 x y x y x y
+# move3
+
+move2_par Y1=y X1=x Y2=y X2=x | move Y3=y X3=x => move3 Y1 X1 Y2 X2 Y3 X3
+move2_par X1=x X2=x X3=x X4=x | move X5=x X6=x => move3 X1 X2 X3 X4 X5 X6
+
move3 y x y x y x
move3 x x x x x x
+# move_x1, move_x2
+
+move C=aiq X=x==1 => move_x1 C
+move C=aiq X=x==2 => move_x2 C
+
+move_x1 c
+move_x2 c
+
# The compiler almost never generates a "move Literal y(Y)" instruction,
# so let's cheat if we encounter one.
move S=n D=y => init D
@@ -483,9 +505,16 @@ is_eq f? s s
is_ne f? s s
#
-# Putting things.
+# Putting tuples.
+#
+# Code compiled with OTP 22 and later uses put_tuple2 to
+# to construct a tuple.
+#
+# Code compiled before OTP 22 uses put_tuple + one put instruction
+# per element. Translate to put_tuple2.
#
+i_put_tuple/2
put_tuple Arity Dst => i_put_tuple Dst u
i_put_tuple Dst Arity Puts=* | put S1 | put S2 | \
@@ -495,11 +524,13 @@ i_put_tuple Dst Arity Puts=* | put S1 | put S2 | \
i_put_tuple Dst Arity Puts=* | put S => \
tuple_append_put(Arity, Dst, Puts, S)
-i_put_tuple/2
+i_put_tuple Dst Arity Puts=* => put_tuple2 Dst Arity Puts
-i_put_tuple xy I
+put_tuple2 xy I
#
+# Putting lists.
+#
# The instruction "put_list Const [] Dst" were generated in rare
# circumstances up to and including OTP 18. Starting with OTP 19,
# AFAIK, it should never be generated.
@@ -602,9 +633,13 @@ is_tuple f? rxy
test_arity Fail Literal=q Arity => move Literal x | test_arity Fail x Arity
test_arity Fail=f c Arity => jump Fail
+test_arity Fail Tuple=x Arity | get_tuple_element Tuple Pos Dst=x => \
+ test_arity_get_tuple_element Fail Tuple Arity Pos Dst
test_arity f? xy A
+test_arity_get_tuple_element f? x A P x
+
get_tuple_element Reg=x P1 D1=x | get_tuple_element Reg=x P2 D2=x | \
get_tuple_element Reg=x P3 D3=x | \
succ(P1, P2) | succ(P2, P3) | \
@@ -613,8 +648,11 @@ get_tuple_element Reg=x P1 D1=x | get_tuple_element Reg=x P2 D2=x | \
get_tuple_element Reg=x P1 D1=x | get_tuple_element Reg=x P2 D2=x | \
succ(P1, P2) | succ(D1, D2) => i_get_tuple_element2 Reg P1 D1
+get_tuple_element Reg=x P1 D1=x | get_tuple_element Reg=x P2 D2=x | \
+ succ(P1, P2) | distinct(D1, Reg) => i_get_tuple_element2_dst Reg P1 D1 D2
+
get_tuple_element Reg=x P1 D1=y | get_tuple_element Reg=x P2 D2=y | \
- succ(P1, P2) => i_get_tuple_element2y Reg P1 D1 D2
+ succ(P1, P2) => i_get_tuple_element2_dst Reg P1 D1 D2
get_tuple_element Reg P Dst => i_get_tuple_element Reg P Dst
@@ -638,14 +676,21 @@ is_list f? y
is_nonempty_list Fail=f S=x | allocate Need Rs => is_nonempty_list_allocate Fail S Need Rs
-is_nonempty_list F=f x==0 | test_heap I1 I2 => is_nonempty_list_test_heap F I1 I2
-
is_nonempty_list Fail=f S=x | get_list S D1=x D2=x => \
is_nonempty_list_get_list Fail S D1 D2
+is_nonempty_list Fail=f S=x | get_hd S Dst=x => \
+ is_nonempty_list_get_hd Fail S Dst
+
+is_nonempty_list Fail=f S=x | get_tl S Dst=x => \
+ is_nonempty_list_get_tl Fail S Dst
+
is_nonempty_list_allocate f? rx t t
-is_nonempty_list_test_heap f? I t
+
is_nonempty_list_get_list f? rx x x
+is_nonempty_list_get_hd f? x x
+is_nonempty_list_get_tl f? x x
+
is_nonempty_list f? xy
is_atom f? x
@@ -710,11 +755,12 @@ is_boolean Fail=f ac => jump Fail
is_boolean f? xy
%hot
-is_function2 Fail=f Literal=q Arity | literal_is_export(Literal) =>
-is_function2 Fail=f c Arity => jump Fail
-is_function2 Fail=f Fun a => jump Fail
+is_function2 Fail=f Fun Arity => gen_is_function2(Fail, Fun, Arity)
-is_function2 f? S s
+%cold
+cold_is_function2 f? x x
+%hot
+hot_is_function2 f? S t
# Allocating & initializing.
allocate Need Regs | init Y => allocate_init Need Regs Y
@@ -993,10 +1039,11 @@ bif1 Fail Bif=u$bif:erlang:get/1 Src=s Dst=d => gen_get(Src, Dst)
bif2 Jump=j u$bif:erlang:element/2 S1=s S2=xy Dst=d => gen_element(Jump, S1, S2, Dst)
-bif1 p Bif S1 Dst => bif1_body Bif S1 Dst
+bif1 p Bif S1 Dst => i_bif1_body S1 Bif Dst
+bif1 Fail=f Bif S1 Dst => i_bif1 S1 Fail Bif Dst
-bif2 p Bif S1 S2 Dst => i_bif2_body Bif S1 S2 Dst
-bif2 Fail Bif S1 S2 Dst => i_bif2 Fail Bif S1 S2 Dst
+bif2 p Bif S1 S2 Dst => i_bif2_body S2 S1 Bif Dst
+bif2 Fail=f Bif S1 S2 Dst => i_bif2 S2 S1 Fail Bif Dst
i_get_hash c I d
i_get s d
@@ -1014,10 +1061,12 @@ i_fast_element xy j? I d
i_element xy j? s d
-bif1 f? b s d
-bif1_body b s d
-i_bif2 f? b s s d
-i_bif2_body b s s d
+i_bif1 s f? b d
+i_bif1_body s b d
+i_bif2 s s f? b d
+i_bif2_body s s b d
+i_bif3 s s s f? b d
+i_bif3_body s s s b d
#
# Internal calls.
@@ -1080,67 +1129,73 @@ func_info M F A => i_func_info u M F A
%warm
bs_start_match2 Fail=f ica X Y D => jump Fail
bs_start_match2 Fail Bin X Y D => i_bs_start_match2 Bin Fail X Y D
-i_bs_start_match2 xy f t t x
+i_bs_start_match2 xy f t t d
bs_save2 Reg Index => gen_bs_save(Reg, Index)
-i_bs_save2 x t
+i_bs_save2 xy t
bs_restore2 Reg Index => gen_bs_restore(Reg, Index)
-i_bs_restore2 x t
+i_bs_restore2 xy t
# Matching integers
bs_match_string Fail Ms Bits Val => i_bs_match_string Ms Fail Bits Val
-i_bs_match_string x f W W
+i_bs_match_string xy f W W
# Fetching integers from binaries.
-bs_get_integer2 Fail=f Ms=x Live=u Sz=sq Unit=u Flags=u Dst=d => \
+bs_get_integer2 Fail=f Ms=xy Live=u Sz=sq Unit=u Flags=u Dst=d => \
gen_get_integer2(Fail, Ms, Live, Sz, Unit, Flags, Dst)
-i_bs_get_integer_small_imm x W f? t x
-i_bs_get_integer_imm x W t f? t x
-i_bs_get_integer f? t t x s x
-i_bs_get_integer_8 x f? x
-i_bs_get_integer_16 x f? x
+i_bs_get_integer_small_imm Ms Bits Fail Flags Y=y => \
+ i_bs_get_integer_small_imm Ms Bits Fail Flags x | move x Y
+
+i_bs_get_integer_imm Ms Bits Live Fail Flags Y=y => \
+ i_bs_get_integer_imm Ms Bits Live Fail Flags x | move x Y
+
+i_bs_get_integer_small_imm xy W f? t x
+i_bs_get_integer_imm xy W t f? t x
+i_bs_get_integer f? t t xy s d
+i_bs_get_integer_8 xy f? d
+i_bs_get_integer_16 xy f? d
%if ARCH_64
-i_bs_get_integer_32 x f? x
+i_bs_get_integer_32 xy f? d
%endif
# Fetching binaries from binaries.
-bs_get_binary2 Fail=f Ms=x Live=u Sz=sq Unit=u Flags=u Dst=d => \
+bs_get_binary2 Fail=f Ms=xy Live=u Sz=sq Unit=u Flags=u Dst=d => \
gen_get_binary2(Fail, Ms, Live, Sz, Unit, Flags, Dst)
-i_bs_get_binary_imm2 f? x t W t x
-i_bs_get_binary2 f x t? s t x
-i_bs_get_binary_all2 f? x t t x
-i_bs_get_binary_all_reuse x f? t
+i_bs_get_binary_imm2 f? xy t W t d
+i_bs_get_binary2 f xy t? s t d
+i_bs_get_binary_all2 f? xy t t d
+i_bs_get_binary_all_reuse xy f? t
# Fetching float from binaries.
-bs_get_float2 Fail=f Ms=x Live=u Sz=s Unit=u Flags=u Dst=d => \
+bs_get_float2 Fail=f Ms=xy Live=u Sz=s Unit=u Flags=u Dst=d => \
gen_get_float2(Fail, Ms, Live, Sz, Unit, Flags, Dst)
bs_get_float2 Fail=f Ms=x Live=u Sz=q Unit=u Flags=u Dst=d => jump Fail
-i_bs_get_float2 f? x t s t x
+i_bs_get_float2 f? xy t s t d
# Miscellanous
-bs_skip_bits2 Fail=f Ms=x Sz=sq Unit=u Flags=u => \
+bs_skip_bits2 Fail=f Ms=xy Sz=sq Unit=u Flags=u => \
gen_skip_bits2(Fail, Ms, Sz, Unit, Flags)
-i_bs_skip_bits_imm2 f? x W
-i_bs_skip_bits2 f? x xy t
-i_bs_skip_bits_all2 f? x t
+i_bs_skip_bits_imm2 f? xy W
+i_bs_skip_bits2 f? xy xy t
+i_bs_skip_bits_all2 f? xy t
-bs_test_tail2 Fail=f Ms=x Bits=u==0 => bs_test_zero_tail2 Fail Ms
-bs_test_tail2 Fail=f Ms=x Bits=u => bs_test_tail_imm2 Fail Ms Bits
-bs_test_zero_tail2 f? x
-bs_test_tail_imm2 f? x W
+bs_test_tail2 Fail=f Ms=xy Bits=u==0 => bs_test_zero_tail2 Fail Ms
+bs_test_tail2 Fail=f Ms=xy Bits=u => bs_test_tail_imm2 Fail Ms Bits
+bs_test_zero_tail2 f? xy
+bs_test_tail_imm2 f? xy W
bs_test_unit F Ms Unit=u==8 => bs_test_unit8 F Ms
-bs_test_unit f? x t
-bs_test_unit8 f? x
+bs_test_unit f? xy t
+bs_test_unit8 f? xy
# An y register operand for bs_context_to_binary is rare,
# but can happen because of inlining.
@@ -1152,23 +1207,55 @@ bs_context_to_binary Y=y => move Y x | bs_context_to_binary x
bs_context_to_binary x
+# Gets a bitstring from the tail of a context.
+bs_get_tail xy d t
+
+# New bs_start_match variant for contexts with external position storage.
+#
+# bs_get/set_position is used to save positions into registers instead of
+# "slots" in the context itself, which lets us continue matching even after
+# we've passed it off to another function.
+
+%if ARCH_64
+bs_start_match3 Fail Bin Live Ctx | bs_get_position Ctx Pos=x Ignored => \
+ i_bs_start_match3_gp Bin Live Fail Ctx Pos
+i_bs_start_match3_gp xy t f d x
+%endif
+
+bs_start_match3 Fail=f ica Live Dst => jump Fail
+bs_start_match3 Fail Bin Live Dst => i_bs_start_match3 Bin Live Fail Dst
+
+i_bs_start_match3 xy t f d
+
+# Match context position instructions. 64-bit assumes that all positions can
+# fit into an unsigned small.
+
+%if ARCH_64
+ bs_get_position Src Dst Live => i_bs_get_position Src Dst
+ i_bs_get_position xy xy
+ bs_set_position xy xy
+%else
+ bs_get_position xy d t?
+ bs_set_position xy xy
+%endif
+
#
# Utf8/utf16/utf32 support. (R12B-5)
#
-bs_get_utf8 Fail=f Ms=x u u Dst=d => i_bs_get_utf8 Ms Fail Dst
-i_bs_get_utf8 x f? x
+bs_get_utf8 Fail=f Ms=xy u u Dst=d => i_bs_get_utf8 Ms Fail Dst
+i_bs_get_utf8 xy f? d
-bs_skip_utf8 Fail=f Ms=x u u => i_bs_get_utf8 Ms Fail x
+bs_skip_utf8 Fail=f Ms=xy u u => i_bs_get_utf8 Ms Fail x
-bs_get_utf16 Fail=f Ms=x u Flags=u Dst=d => i_bs_get_utf16 Ms Fail Flags Dst
-bs_skip_utf16 Fail=f Ms=x u Flags=u => i_bs_get_utf16 Ms Fail Flags x
+bs_get_utf16 Fail=f Ms=xy u Flags=u Dst=d => i_bs_get_utf16 Ms Fail Flags Dst
+bs_skip_utf16 Fail=f Ms=xy u Flags=u => i_bs_get_utf16 Ms Fail Flags x
-i_bs_get_utf16 x f? t x
+i_bs_get_utf16 xy f? t d
-bs_get_utf32 Fail=f Ms=x Live=u Flags=u Dst=d => \
+bs_get_utf32 Fail=f Ms=xy Live=u Flags=u Dst=d => \
bs_get_integer2 Fail Ms Live i=32 u=1 Flags Dst | \
i_bs_validate_unicode_retract Fail Dst Ms
-bs_skip_utf32 Fail=f Ms=x Live=u Flags=u => \
+bs_skip_utf32 Fail=f Ms=xy Live=u Flags=u => \
bs_get_integer2 Fail Ms Live i=32 u=1 Flags x | \
i_bs_validate_unicode_retract Fail x Ms
@@ -1182,6 +1269,9 @@ i_bs_validate_unicode_retract j s S
bs_init2 Fail Sz Words Regs Flags Dst | binary_too_big(Sz) => system_limit Fail
+bs_init2 Fail Sz Words Regs Flags Dst=y => \
+ bs_init2 Fail Sz Words Regs Flags x | move x Dst
+
bs_init2 Fail Sz=u Words=u==0 Regs Flags Dst => i_bs_init Sz Regs Dst
bs_init2 Fail Sz=u Words Regs Flags Dst => \
@@ -1202,6 +1292,8 @@ i_bs_init_heap W I t? x
bs_init_bits Fail Sz=o Words Regs Flags Dst => system_limit Fail
+bs_init_bits Fail Sz Words Regs Flags Dst=y => \
+ bs_init_bits Fail Sz Words Regs Flags x | move x Dst
bs_init_bits Fail Sz=u Words=u==0 Regs Flags Dst => i_bs_init_bits Sz Regs Dst
bs_init_bits Fail Sz=u Words Regs Flags Dst => i_bs_init_bits_heap Sz Words Regs Dst
@@ -1230,7 +1322,7 @@ bs_private_append Fail Size Unit Bin Flags Dst => \
bs_init_writable
-i_bs_append j? I t? t s x
+i_bs_append j? I t? t s xy
i_bs_private_append j? t s S x
#
@@ -1447,80 +1539,80 @@ gc_bif2 Fail Live u$bif:erlang:sminus/2 S1 S2 Dst => \
#
# Optimize addition and subtraction of small literals using
-# the i_increment/4 instruction (in bodies, not in guards).
+# the i_increment/3 instruction (in bodies, not in guards).
#
gen_plus p Live Int=i Reg=d Dst => \
- gen_increment(Reg, Int, Live, Dst)
+ gen_increment(Reg, Int, Dst)
gen_plus p Live Reg=d Int=i Dst => \
- gen_increment(Reg, Int, Live, Dst)
+ gen_increment(Reg, Int, Dst)
gen_minus p Live Reg=d Int=i Dst | negation_is_small(Int) => \
- gen_increment_from_minus(Reg, Int, Live, Dst)
+ gen_increment_from_minus(Reg, Int, Dst)
#
-# GCing arithmetic instructions.
+# Arithmetic instructions.
#
-gen_plus Fail Live S1 S2 Dst => i_plus S1 S2 Fail Live 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 Live Dst
+gen_minus Fail Live S1 S2 Dst => i_minus S1 S2 Fail Dst
gc_bif2 Fail Live u$bif:erlang:stimes/2 S1 S2 Dst => \
- i_times Fail Live S1 S2 Dst
+ i_times Fail S1 S2 Dst
gc_bif2 Fail Live u$bif:erlang:div/2 S1 S2 Dst => \
- i_m_div Fail Live S1 S2 Dst
+ i_m_div Fail S1 S2 Dst
gc_bif2 Fail Live u$bif:erlang:intdiv/2 S1 S2 Dst => \
- i_int_div Fail Live S1 S2 Dst
+ i_int_div Fail S1 S2 Dst
gc_bif2 Fail Live u$bif:erlang:rem/2 S1 S2 Dst => \
- i_rem S1 S2 Fail Live Dst
+ i_rem S1 S2 Fail Dst
gc_bif2 Fail Live u$bif:erlang:bsl/2 S1 S2 Dst => \
- i_bsl S1 S2 Fail Live Dst
+ i_bsl S1 S2 Fail Dst
gc_bif2 Fail Live u$bif:erlang:bsr/2 S1 S2 Dst => \
- i_bsr S1 S2 Fail Live Dst
+ i_bsr S1 S2 Fail Dst
gc_bif2 Fail Live u$bif:erlang:band/2 S1 S2 Dst => \
- i_band S1 S2 Fail Live Dst
+ i_band S1 S2 Fail Dst
gc_bif2 Fail Live u$bif:erlang:bor/2 S1 S2 Dst => \
- i_bor Fail Live S1 S2 Dst
+ i_bor Fail S1 S2 Dst
gc_bif2 Fail Live u$bif:erlang:bxor/2 S1 S2 Dst => \
- i_bxor Fail Live S1 S2 Dst
+ i_bxor Fail S1 S2 Dst
-gc_bif1 Fail I u$bif:erlang:bnot/1 Src Dst=d => i_int_bnot Fail Src I Dst
+gc_bif1 Fail Live u$bif:erlang:bnot/1 Src Dst=d => i_int_bnot Fail Src Dst
-i_increment rxy W t d
+i_increment rxy W d
-i_plus x xy j? t d
-i_plus s s j? t d
+i_plus x xy j? d
+i_plus s s j? d
-i_minus x x j? t d
-i_minus s s j? t d
+i_minus x x j? d
+i_minus s s j? d
-i_times j? t s s d
+i_times j? s s d
-i_m_div j? t s s d
-i_int_div j? t s s d
+i_m_div j? s s d
+i_int_div j? s s d
-i_rem x x j? t d
-i_rem s s j? t d
+i_rem x x j? d
+i_rem s s j? d
-i_bsl s s j? t d
-i_bsr s s j? t d
+i_bsl s s j? d
+i_bsr s s j? d
-i_band x c j? t d
-i_band s s j? t d
+i_band x c j? d
+i_band s s j? d
-i_bor j? I s s d
-i_bxor j? I s s d
+i_bor j? s s d
+i_bxor j? s s d
-i_int_bnot Fail Src=c Live Dst => move Src x | i_int_bnot Fail x Live Dst
+i_int_bnot Fail Src=c Dst => move Src x | i_int_bnot Fail x Dst
-i_int_bnot j? S t d
+i_int_bnot j? S d
#
# Old guard BIFs that creates heap fragments are no longer allowed.
@@ -1533,29 +1625,27 @@ bif1 Fail u$bif:erlang:round/1 s d => too_old_compiler
bif1 Fail u$bif:erlang:trunc/1 s d => too_old_compiler
#
-# Guard BIFs.
+# Handle the length/1 guard BIF specially to make it trappable.
#
-gc_bif1 Fail I Bif Src Dst => \
- gen_guard_bif1(Fail, I, Bif, Src, Dst)
-
-gc_bif2 Fail I Bif S1 S2 Dst => \
- gen_guard_bif2(Fail, I, Bif, S1, S2, Dst)
-gc_bif3 Fail I Bif S1 S2 S3 Dst => \
- gen_guard_bif3(Fail, I, Bif, S1, S2, S3, Dst)
+gc_bif1 Fail=j Live u$bif:erlang:length/1 Src Dst => \
+ i_length_setup Live Src | i_length Fail Live Dst
-i_gc_bif1 j? W s t? d
+i_length_setup t xyc
-i_gc_bif2 j? W t? s s d
+i_length j? t d
-ii_gc_bif3/7
+#
+# Guard BIFs.
+#
+gc_bif1 p Live Bif Src Dst => i_bif1_body Src Bif Dst
+gc_bif1 Fail=f Live Bif Src Dst => i_bif1 Src Fail Bif Dst
-# A specific instruction can only have 6 operands, so we must
-# pass one of the arguments in an x register.
-ii_gc_bif3 Fail Bif Live S1 S2 S3 Dst => \
- move S1 x | i_gc_bif3 Fail Bif Live S2 S3 Dst
+gc_bif2 p Live Bif S1 S2 Dst => i_bif2_body S2 S1 Bif Dst
+gc_bif2 Fail=f Live Bif S1 S2 Dst => i_bif2 S2 S1 Fail Bif Dst
-i_gc_bif3 j? W t? s s d
+gc_bif3 p Live Bif S1 S2 S3 Dst => i_bif3_body S3 S2 S1 Bif Dst
+gc_bif3 Fail=f Live Bif S1 S2 S3 Dst => i_bif3 S3 S2 S1 Fail Bif Dst
#
# The following instruction is specially handled in beam_load.c
diff --git a/erts/emulator/beam/sys.h b/erts/emulator/beam/sys.h
index 869a575cb4..a69da4d762 100644
--- a/erts/emulator/beam/sys.h
+++ b/erts/emulator/beam/sys.h
@@ -1291,4 +1291,13 @@ erts_raw_env_next_char(byte *p, int encoding)
#endif /* #if ERTS_GLB_INLINE_INCL_FUNC_DEF */
+/*
+ * Magic numbers for our driver port_control callbacks.
+ * Kept them below 1<<27 to not inflict extra bignum garbage on 32-bit.
+ */
+#define ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER 0x018b0900U
+#define ERTS_INET_DRV_CONTROL_MAGIC_NUMBER 0x03f1a300U
+#define ERTS_SPAWN_DRV_CONTROL_MAGIC_NUMBER 0x04c76a00U
+#define ERTS_FORKER_DRV_CONTROL_MAGIC_NUMBER 0x050a7800U
+
#endif
diff --git a/erts/emulator/beam/utils.c b/erts/emulator/beam/utils.c
index d81bd89a48..36cfe0548e 100644
--- a/erts/emulator/beam/utils.c
+++ b/erts/emulator/beam/utils.c
@@ -1569,7 +1569,7 @@ make_hash2(Eterm term)
* MUST BE USED AS INPUT FOR THE HASH. Two different terms must always have a
* chance of hashing different when salted: hash([Salt|A]) vs hash([Salt|B]).
*
- * This is why we can not use cached hash values for atoms for example.
+ * This is why we cannot use cached hash values for atoms for example.
*
*/
@@ -1946,7 +1946,7 @@ do_allocate_logger_message(Eterm gleader, ErtsMonotonicTime *ts, Eterm *pid,
else
sz += MAP4_SZ /* metadata map w gl w pid*/;
- *ts = ERTS_MONOTONIC_TO_USEC(erts_get_monotonic_time(NULL) + erts_get_time_offset());
+ *ts = ERTS_MONOTONIC_TO_USEC(erts_os_system_time());
erts_bld_sint64(NULL, &sz, *ts);
*bp = new_message_buffer(sz);
@@ -2701,7 +2701,8 @@ Sint erts_cmp_compound(Eterm a, Eterm b, int exact, int eq_only)
if((AN)->sysname != (BN)->sysname) \
RETURN_NEQ(erts_cmp_atoms((AN)->sysname, (BN)->sysname)); \
ASSERT((AN)->creation != (BN)->creation); \
- RETURN_NEQ(((AN)->creation < (BN)->creation) ? -1 : 1); \
+ if ((AN)->creation != 0 && (BN)->creation != 0) \
+ RETURN_NEQ(((AN)->creation < (BN)->creation) ? -1 : 1); \
} \
} while (0)
@@ -3486,7 +3487,7 @@ store_external_or_ref_(Uint **hpp, ErlOffHeap* oh, Eterm ns)
if (is_external_header(*from_hp)) {
ExternalThing *etp = (ExternalThing *) from_hp;
ASSERT(is_external(ns));
- erts_refc_inc(&etp->node->refc, 2);
+ erts_ref_node_entry(etp->node, 2, make_boxed(to_hp));
}
else if (is_ordinary_ref_thing(from_hp))
return make_internal_ref(to_hp);
@@ -3681,30 +3682,47 @@ erts_unicode_list_to_buf_len(Eterm list)
}
}
-/*
-** Convert an integer to a byte list
-** return pointer to converted stuff (need not to be at start of buf!)
-*/
-char* Sint_to_buf(Sint n, struct Sint_buf *buf)
+/* Prints an integer in the given base, returning the number of digits printed.
+ *
+ * (*buf) is a pointer to the buffer, and is set to the start of the string
+ * when returning. */
+int Sint_to_buf(Sint n, int base, char **buf, size_t buf_size)
{
- char* p = &buf->s[sizeof(buf->s)-1];
- int sign = 0;
-
- *p-- = '\0'; /* null terminate */
- if (n == 0)
- *p-- = '0';
- else if (n < 0) {
- sign = 1;
- n = -n;
+ char *p = &(*buf)[buf_size - 1];
+ int sign = 0, size = 0;
+
+ ASSERT(base >= 2 && base <= 36);
+
+ if (n == 0) {
+ *p-- = '0';
+ size++;
+ } else if (n < 0) {
+ sign = 1;
+ n = -n;
}
while (n != 0) {
- *p-- = (n % 10) + '0';
- n /= 10;
+ int digit = n % base;
+
+ if (digit < 10) {
+ *p-- = '0' + digit;
+ } else {
+ *p-- = 'A' + (digit - 10);
+ }
+
+ size++;
+
+ n /= base;
}
- if (sign)
- *p-- = '-';
- return p+1;
+
+ if (sign) {
+ *p-- = '-';
+ size++;
+ }
+
+ *buf = p + 1;
+
+ return size;
}
/* Build a list of integers in some safe memory area
@@ -4771,58 +4789,3 @@ erts_ptr_id(void *ptr)
return ptr;
}
-#ifdef DEBUG
-/*
- * Handy functions when using a debugger - don't use in the code!
- */
-
-void upp(byte *buf, size_t sz)
-{
- bin_write(ERTS_PRINT_STDERR, NULL, buf, sz);
-}
-
-void pat(Eterm atom)
-{
- upp(atom_tab(atom_val(atom))->name,
- atom_tab(atom_val(atom))->len);
-}
-
-
-void pinfo()
-{
- process_info(ERTS_PRINT_STDOUT, NULL);
-}
-
-
-void pp(p)
-Process *p;
-{
- if(p)
- print_process_info(ERTS_PRINT_STDERR, NULL, p);
-}
-
-void ppi(Eterm pid)
-{
- pp(erts_proc_lookup(pid));
-}
-
-void td(Eterm x)
-{
- erts_fprintf(stderr, "%T\n", x);
-}
-
-void
-ps(Process* p, Eterm* stop)
-{
- Eterm* sp = STACK_START(p) - 1;
-
- if (stop <= STACK_END(p)) {
- stop = STACK_END(p) + 1;
- }
-
- while(sp >= stop) {
- erts_printf("%p: %.75T\n", sp, *sp);
- sp--;
- }
-}
-#endif
diff --git a/erts/emulator/drivers/common/inet_drv.c b/erts/emulator/drivers/common/inet_drv.c
index 31c9ce2d0b..78411f324c 100644
--- a/erts/emulator/drivers/common/inet_drv.c
+++ b/erts/emulator/drivers/common/inet_drv.c
@@ -634,9 +634,13 @@ static size_t my_strnlen(const char *s, size_t maxlen)
* header length. To get the header length we use
* the pointer difference from the cmsg start pointer
* to the CMSG_DATA(cmsg) pointer.
+ *
+ * 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 - ((char*)CMSG_DATA(cmsg) - (char*)(cmsg)))
+ ((cmsg)->cmsg_len < sizeof (struct cmsghdr) ? 0 : \
+ (cmsg)->cmsg_len - ((char*)CMSG_DATA(cmsg) - (char*)(cmsg)))
#define NXT_CMSG_HDR(cmsg) \
((struct cmsghdr*)(((char*)(cmsg)) + CMSG_SPACE(LEN_CMSG_DATA(cmsg))))
#endif
@@ -4568,7 +4572,7 @@ static void desc_close_read(inet_descriptor* desc)
{
if (desc->s != INVALID_SOCKET) {
#ifdef __WIN32__
- /* This call can not be right???
+ /* This call cannot be right???
* We want to turn off read events but keep any write events.
* But on windows driver_select(...,READ,1) is only used as a
* way to hook into the pollset. sock_select is used to control
@@ -9951,6 +9955,7 @@ static ErlDrvSSizeT tcp_inet_ctl(ErlDrvData e, unsigned int cmd,
{
tcp_descriptor* desc = (tcp_descriptor*)e;
+ cmd -= ERTS_INET_DRV_CONTROL_MAGIC_NUMBER;
switch(cmd) {
case INET_REQ_OPEN: { /* open socket and return internal index */
int domain;
@@ -12180,6 +12185,7 @@ static ErlDrvSSizeT packet_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf,
int type = SOCK_DGRAM;
int af = AF_INET;
+ cmd -= ERTS_INET_DRV_CONTROL_MAGIC_NUMBER;
switch(cmd) {
case INET_REQ_OPEN: /* open socket and return internal index */
DEBUGF(("packet_inet_ctl(%ld): OPEN\r\n", (long)desc->port));
diff --git a/erts/emulator/drivers/unix/ttsl_drv.c b/erts/emulator/drivers/unix/ttsl_drv.c
index 28c6cc0f94..f6864f96da 100644
--- a/erts/emulator/drivers/unix/ttsl_drv.c
+++ b/erts/emulator/drivers/unix/ttsl_drv.c
@@ -31,7 +31,7 @@
static int ttysl_init(void);
static ErlDrvData ttysl_start(ErlDrvPort, char*);
-#ifdef HAVE_TERMCAP /* else make an empty driver that can not be opened */
+#ifdef HAVE_TERMCAP /* else make an empty driver that cannot be opened */
#ifndef WANT_NONBLOCKING
#define WANT_NONBLOCKING
@@ -394,6 +394,8 @@ static ErlDrvSSizeT ttysl_control(ErlDrvData drv_data,
{
char resbuff[2*sizeof(Uint32)];
ErlDrvSizeT res_size;
+
+ command -= ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER;
switch (command) {
case CTRL_OP_GET_WINSIZE:
{
@@ -419,7 +421,7 @@ static ErlDrvSSizeT ttysl_control(ErlDrvData drv_data,
}
break;
default:
- return 0;
+ return -1;
}
if (rlen < res_size) {
*rbuf = driver_alloc(res_size);
diff --git a/erts/emulator/drivers/win32/ttsl_drv.c b/erts/emulator/drivers/win32/ttsl_drv.c
index 99e7fb25a4..d19bfa3079 100644
--- a/erts/emulator/drivers/win32/ttsl_drv.c
+++ b/erts/emulator/drivers/win32/ttsl_drv.c
@@ -176,6 +176,8 @@ static ErlDrvSSizeT ttysl_control(ErlDrvData drv_data,
{
char resbuff[2*sizeof(Uint32)];
ErlDrvSizeT res_size;
+
+ command -= ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER;
switch (command) {
case CTRL_OP_GET_WINSIZE:
{
@@ -201,7 +203,7 @@ static ErlDrvSSizeT ttysl_control(ErlDrvData drv_data,
}
break;
default:
- return 0;
+ return -1;
}
if (rlen < res_size) {
*rbuf = driver_alloc(res_size);
diff --git a/erts/emulator/hipe/hipe_native_bif.c b/erts/emulator/hipe/hipe_native_bif.c
index 211ce0492a..80e5d81023 100644
--- a/erts/emulator/hipe/hipe_native_bif.c
+++ b/erts/emulator/hipe/hipe_native_bif.c
@@ -579,7 +579,7 @@ Eterm hipe_check_get_msg(Process *c_p)
if (ERTS_SIG_IS_EXTERNAL_MSG(msgp)) {
/* FIXME: bump appropriate amount... */
- if (!erts_decode_dist_message(c_p, ERTS_PROC_LOCK_MAIN, msgp, 0)) {
+ if (!erts_proc_sig_decode_dist(c_p, ERTS_PROC_LOCK_MAIN, msgp, 0)) {
/*
* A corrupt distribution message that we weren't able to decode;
* remove it...
diff --git a/erts/emulator/internal_doc/CarrierMigration.md b/erts/emulator/internal_doc/CarrierMigration.md
index 3a796d11b7..bb3d8aac28 100644
--- a/erts/emulator/internal_doc/CarrierMigration.md
+++ b/erts/emulator/internal_doc/CarrierMigration.md
@@ -34,8 +34,7 @@ Solution
--------
In order to prevent scenarios like this we've implemented support for
-migration of multi-block carriers between allocator instances of the
-same type.
+migration of multi-block carriers between allocator instances.
### Management of Free Blocks ###
@@ -130,10 +129,6 @@ threads may have references to it via the pool.
### Migration ###
-There exists one pool for each allocator type enabling migration of
-carriers between scheduler specific allocator instances of the same
-allocator type.
-
Each allocator instance keeps track of the current utilization of its
multi-block carriers. When the total utilization falls below the "abandon
carrier utilization limit" it starts to inspect the utilization of the
@@ -208,8 +203,8 @@ limited. We only inspect a limited number of carriers. If none of
those carriers had a free block large enough to satisfy the allocation
request, the search will fail. A carrier in the pool can also be BUSY
if another thread is currently doing block deallocation work on the
-carrier. A BUSY carrier will also be skipped by the search as it can
-not satisfy the request. The pool is lock-free and we do not want to
+carrier. A BUSY carrier will also be skipped by the search as it cannot
+satisfy the request. The pool is lock-free and we do not want to
block, waiting for the other thread to finish.
### The bad cluster problem ###
@@ -287,11 +282,3 @@ reduced using the `aoffcbf` strategy. A trade off between memory
consumption and performance is however inevitable, and it is up to
the user to decide what is most important.
-Further work
-------------
-
-It would be quite easy to extend this to allow migration of multi-block
-carriers between all allocator types. More or less the only obstacle
-is maintenance of the statistics information.
-
-
diff --git a/erts/emulator/internal_doc/CountingInstructions.md b/erts/emulator/internal_doc/CountingInstructions.md
new file mode 100644
index 0000000000..d4b1213d00
--- /dev/null
+++ b/erts/emulator/internal_doc/CountingInstructions.md
@@ -0,0 +1,53 @@
+Counting Instructions
+=====================
+
+Here is an example that shows how to count how many times each
+instruction is executed:
+
+ $ (cd erts/emulator && make icount)
+ MAKE icount
+ make[1]: Entering directory `/home/uabbgus/otp/erts/emulator'
+ .
+ .
+ .
+ make[1]: Leaving directory `/home/uabbgus/otp/erts/emulator'
+ $ cat t.erl
+ -module(t).
+ -compile([export_all,nowarn_export_all]).
+
+ count() ->
+ erts_debug:ic(fun benchmark/0).
+
+ benchmark() ->
+ %% Run dialyzer.
+ Root = code:root_dir(),
+ Wc1 = filename:join(Root, "lib/{kernel,stdlib}/ebin/*.beam"),
+ Wc2 = filename:join(Root, "erts/preloaded/ebin/*.beam"),
+ Files = filelib:wildcard(Wc1) ++ filelib:wildcard(Wc2),
+ Opts = [{analysis_type,plt_build},{files,Files},{get_warnings,true}],
+ dialyzer:run(Opts).
+ $ $ERL_TOP/bin/cerl -icount
+ Erlang/OTP 22 [RELEASE CANDIDATE 1] [erts-10.2.4] [source-ac0d451] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [instruction-counting]
+
+ Eshell V10.2.4 (abort with ^G)
+ 1> c(t).
+ {ok,t}
+ 2> t:count().
+ 0 badarg_j
+ 0 badmatch_x
+ 0 bs_add_jsstx
+ 0 bs_context_to_binary_x
+ .
+ .
+ .
+ 536461394 move_call_last_yfQ
+ 552405176 allocate_tt
+ 619920327 i_is_eq_exact_immed_frc
+ 636419163 is_nonempty_list_allocate_frtt
+ 641859278 i_get_tuple_element_xPx
+ 678196718 move_return_c
+ 786289914 is_tagged_tuple_frAa
+ 865826424 i_call_f
+ Total: 20728870321
+ []
+ 3>
diff --git a/erts/emulator/nifs/common/net_nif.c b/erts/emulator/nifs/common/net_nif.c
new file mode 100644
index 0000000000..9a18ed3b15
--- /dev/null
+++ b/erts/emulator/nifs/common/net_nif.c
@@ -0,0 +1,1702 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2018-2019. All 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 : The NIF (C) part of the net interface
+ * This is a module of miscellaneous functions.
+ * ----------------------------------------------------------------------
+ *
+ */
+
+#define STATIC_ERLANG_NIF 1
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+/* If we HAVE_SCTP_H and Solaris, we need to define the following in
+ * order to get SCTP working:
+ */
+#if (defined(HAVE_SCTP_H) && defined(__sun) && defined(__SVR4))
+#define SOLARIS10 1
+/* WARNING: This is not quite correct, it may also be Solaris 11! */
+#define _XPG4_2
+#define __EXTENSIONS__
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <time.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_SYS_UIO_H
+#include <sys/uio.h>
+#endif
+
+#ifdef HAVE_NET_IF_DL_H
+#include <net/if_dl.h>
+#endif
+
+#ifdef HAVE_IFADDRS_H
+#include <ifaddrs.h>
+#endif
+
+#ifdef HAVE_NETPACKET_PACKET_H
+#include <netpacket/packet.h>
+#endif
+
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h>
+#endif
+
+/* SENDFILE STUFF HERE IF WE NEED IT... */
+
+#if defined(__APPLE__) && defined(__MACH__) && !defined(__DARWIN__)
+#define __DARWIN__ 1
+#endif
+
+
+#ifdef __WIN32__
+#define STRNCASECMP strncasecmp
+#define INCL_WINSOCK_API_TYPEDEFS 1
+
+#ifndef WINDOWS_H_INCLUDES_WINSOCK2_H
+#include <winsock2.h>
+#endif
+#include <windows.h>
+#include <Ws2tcpip.h> /* NEED VC 6.0 or higher */
+
+/* Visual studio 2008+: NTDDI_VERSION needs to be set for iphlpapi.h
+ * to define the right structures. It needs to be set to WINXP (or LONGHORN)
+ * for IPV6 to work and it's set lower by default, so we need to change it.
+ */
+#ifdef HAVE_SDKDDKVER_H
+# include <sdkddkver.h>
+# ifdef NTDDI_VERSION
+# undef NTDDI_VERSION
+# endif
+# define NTDDI_VERSION NTDDI_WINXP
+#endif
+#include <iphlpapi.h>
+
+#undef WANT_NONBLOCKING
+#include "sys.h"
+
+#else /* !__WIN32__ */
+
+#include <sys/time.h>
+#ifdef NETDB_H_NEEDS_IN_H
+#include <netinet/in.h>
+#endif
+#include <netdb.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#ifdef DEF_INADDR_LOOPBACK_IN_RPC_TYPES_H
+#include <rpc/types.h>
+#endif
+
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+#include <arpa/inet.h>
+
+#include <sys/param.h>
+#ifdef HAVE_ARPA_NAMESER_H
+#include <arpa/nameser.h>
+#endif
+
+#ifdef HAVE_SYS_SOCKIO_H
+#include <sys/sockio.h>
+#endif
+
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+
+#include <net/if.h>
+
+#ifdef HAVE_SCHED_H
+#include <sched.h>
+#endif
+
+#ifdef HAVE_SETNS_H
+#include <setns.h>
+#endif
+
+#define HAVE_UDP
+
+#ifndef WANT_NONBLOCKING
+#define WANT_NONBLOCKING
+#endif
+#include "sys.h"
+
+#endif
+
+#include <erl_nif.h>
+
+#include "socket_dbg.h"
+#include "socket_int.h"
+#include "socket_util.h"
+
+
+/* All platforms fail on malloc errors. */
+#define FATAL_MALLOC
+
+
+#ifdef __WIN32__
+#define net_gethostname(__buf__, __bufSz__) gethostname((__buf__), (__bufSz__))
+#else
+#define net_gethostname(__buf__, __bufSz__) gethostname((__buf__), (__bufSz__))
+#endif // __WIN32__
+
+
+
+/* *** Misc macros and defines *** */
+
+#ifdef __WIN32__
+#define get_errno() WSAGetLastError()
+#else
+#define get_errno() errno
+#endif
+
+
+#define HOSTNAME_LEN 256
+#define SERVICE_LEN 256
+
+
+/* MAXHOSTNAMELEN could be 64 or 255 depending
+ * on the platform. Instead, use INET_MAXHOSTNAMELEN
+ * which is always 255 across all platforms
+ */
+#define NET_MAXHOSTNAMELEN 255
+
+
+/* =================================================================== *
+ * *
+ * Various enif macros *
+ * *
+ * =================================================================== */
+
+
+#ifdef HAVE_SOCKLEN_T
+# define SOCKLEN_T socklen_t
+#else
+# define SOCKLEN_T size_t
+#endif
+
+/* Debug stuff... */
+#define NET_NIF_DEBUG_DEFAULT FALSE
+
+#define NDBG( proto ) ESOCK_DBG_PRINTF( data.debug , proto )
+
+
+typedef struct {
+ BOOLEAN_T debug;
+} NetData;
+
+
+
+/* =================================================================== *
+ * *
+ * Static data *
+ * *
+ * =================================================================== */
+
+
+static NetData data;
+
+
+
+/* ----------------------------------------------------------------------
+ * F o r w a r d s
+ * ----------------------------------------------------------------------
+ */
+
+/* THIS IS JUST TEMPORARY */
+extern char* erl_errno_id(int error);
+
+
+static ERL_NIF_TERM nif_info(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM nif_command(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+
+static ERL_NIF_TERM nif_gethostname(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+
+static ERL_NIF_TERM nif_getnameinfo(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM nif_getaddrinfo(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+
+static ERL_NIF_TERM nif_if_name2index(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM nif_if_index2name(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM nif_if_names(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+
+static ERL_NIF_TERM ncommand(ErlNifEnv* env,
+ ERL_NIF_TERM cmd);
+static ERL_NIF_TERM ngethostname(ErlNifEnv* env);
+static ERL_NIF_TERM ngetnameinfo(ErlNifEnv* env,
+ const SocketAddress* saP,
+ SOCKLEN_T saLen,
+ int flags);
+static ERL_NIF_TERM ngetaddrinfo(ErlNifEnv* env,
+ char* host,
+ char* serv);
+static ERL_NIF_TERM nif_name2index(ErlNifEnv* env,
+ char* ifn);
+static ERL_NIF_TERM nif_index2name(ErlNifEnv* env,
+ unsigned int id);
+static ERL_NIF_TERM nif_names(ErlNifEnv* env);
+static unsigned int nif_names_length(struct if_nameindex* p);
+
+/*
+static void net_dtor(ErlNifEnv* env, void* obj);
+static void net_stop(ErlNifEnv* env,
+ void* obj,
+ int fd,
+ int is_direct_call);
+static void net_down(ErlNifEnv* env,
+ void* obj,
+ const ErlNifPid* pid,
+ const ErlNifMonitor* mon);
+*/
+
+static BOOLEAN_T decode_nameinfo_flags(ErlNifEnv* env,
+ const ERL_NIF_TERM eflags,
+ int* flags);
+static BOOLEAN_T decode_nameinfo_flags_list(ErlNifEnv* env,
+ const ERL_NIF_TERM eflags,
+ int* flags);
+static
+BOOLEAN_T decode_addrinfo_string(ErlNifEnv* env,
+ const ERL_NIF_TERM eString,
+ char** stringP);
+static ERL_NIF_TERM decode_bool(ErlNifEnv* env,
+ ERL_NIF_TERM eBool,
+ BOOLEAN_T* bool);
+static ERL_NIF_TERM encode_address_infos(ErlNifEnv* env,
+ struct addrinfo* addrInfo);
+static ERL_NIF_TERM encode_address_info(ErlNifEnv* env,
+ struct addrinfo* addrInfoP);
+static unsigned int address_info_length(struct addrinfo* addrInfoP);
+
+static ERL_NIF_TERM encode_address_info_family(ErlNifEnv* env,
+ int family);
+static ERL_NIF_TERM encode_address_info_type(ErlNifEnv* env,
+ int socktype);
+static ERL_NIF_TERM encode_address_info_proto(ErlNifEnv* env,
+ int proto);
+
+static char* make_address_info(ErlNifEnv* env,
+ ERL_NIF_TERM fam,
+ ERL_NIF_TERM sockType,
+ ERL_NIF_TERM proto,
+ ERL_NIF_TERM addr,
+ ERL_NIF_TERM* ai);
+
+static BOOLEAN_T extract_debug(ErlNifEnv* env,
+ ERL_NIF_TERM map);
+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 */
+
+
+
+/* *** String constants *** */
+static char str_address_info[] = "address_info";
+static char str_debug[] = "debug";
+static char str_idn[] = "idn";
+static char str_idna_allow_unassigned[] = "idna_allow_unassigned";
+static char str_idna_use_std3_ascii_rules[] = "idna_use_std3_ascii_rules";
+static char str_namereqd[] = "namereqd";
+static char str_name_info[] = "name_info";
+static char str_nofqdn[] = "nofqdn";
+static char str_numerichost[] = "numerichost";
+static char str_numericserv[] = "numericserv";
+
+/* (special) error string constants */
+static char str_eaddrfamily[] = "eaddrfamily";
+static char str_ebadflags[] = "ebadflags";
+static char str_efail[] = "efail";
+static char str_efamily[] = "efamily";
+static char str_efault[] = "efault";
+static char str_emem[] = "emem";
+static char str_enametoolong[] = "enametoolong";
+static char str_enodata[] = "enodata";
+static char str_enoname[] = "enoname";
+static char str_enxio[] = "enxio";
+static char str_eoverflow[] = "eoverflow";
+static char str_eservice[] = "eservice";
+static char str_esocktype[] = "esocktype";
+static char str_esystem[] = "esystem";
+
+
+/* *** Atoms *** */
+
+static ERL_NIF_TERM atom_address_info;
+static ERL_NIF_TERM atom_debug;
+static ERL_NIF_TERM atom_host;
+static ERL_NIF_TERM atom_idn;
+static ERL_NIF_TERM atom_idna_allow_unassigned;
+static ERL_NIF_TERM atom_idna_use_std3_ascii_rules;
+static ERL_NIF_TERM atom_namereqd;
+static ERL_NIF_TERM atom_name_info;
+static ERL_NIF_TERM atom_nofqdn;
+static ERL_NIF_TERM atom_numerichost;
+static ERL_NIF_TERM atom_numericserv;
+static ERL_NIF_TERM atom_service;
+
+
+static ERL_NIF_TERM atom_eaddrfamily;
+// static ERL_NIF_TERM atom_eagain;
+static ERL_NIF_TERM atom_ebadflags;
+static ERL_NIF_TERM atom_efail;
+static ERL_NIF_TERM atom_efamily;
+static ERL_NIF_TERM atom_efault;
+static ERL_NIF_TERM atom_emem;
+static ERL_NIF_TERM atom_enametoolong;
+static ERL_NIF_TERM atom_enodata;
+static ERL_NIF_TERM atom_enoname;
+static ERL_NIF_TERM atom_enxio;
+static ERL_NIF_TERM atom_eoverflow;
+static ERL_NIF_TERM atom_eservice;
+static ERL_NIF_TERM atom_esocktype;
+static ERL_NIF_TERM atom_esystem;
+
+
+/* *** net *** */
+static ErlNifResourceType* net;
+static ErlNifResourceTypeInit netInit = {
+ NULL, // net_dtor,
+ NULL, // net_stop,
+ NULL // (ErlNifResourceDown*) net_down
+};
+
+
+
+/* ----------------------------------------------------------------------
+ * N I F F u n c t i o n s
+ * ----------------------------------------------------------------------
+ *
+ * Utility and admin functions:
+ * ----------------------------
+ * nif_info/0
+ * nif_command/1
+ *
+ * The "proper" net functions:
+ * ------------------------------
+ * nif_gethostname/0
+ * nif_getnameinfo/2
+ * nif_getaddrinfo/3
+ * nif_if_name2index/1
+ * nif_if_index2name/1
+ * nif_if_names/0
+ *
+ */
+
+
+/* ----------------------------------------------------------------------
+ * nif_info
+ *
+ * Description:
+ * This is currently just a placeholder...
+ */
+static
+ERL_NIF_TERM nif_info(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ ERL_NIF_TERM info, tmp;
+
+ NDBG( ("NET", "info -> entry\r\n") );
+
+ tmp = enif_make_new_map(env);
+ if (!enif_make_map_put(env, tmp, atom_debug, BOOL2ATOM(data.debug), &info))
+ info = tmp;
+
+ NDBG( ("NET", "info -> done: %T\r\n", info) );
+
+ return info;
+#endif
+}
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_command
+ *
+ * Description:
+ * This is a general purpose utility function.
+ *
+ * Arguments:
+ * Command - This is a general purpose command, of any type.
+ * Currently, the only supported command is:
+ *
+ * {debug, boolean()}
+ */
+static
+ERL_NIF_TERM nif_command(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ ERL_NIF_TERM ecmd, result;
+
+ NDBG( ("NET", "command -> entry (%d)\r\n", argc) );
+
+ if (argc != 1)
+ return enif_make_badarg(env);
+
+ ecmd = argv[0];
+
+ NDBG( ("NET", "command -> ecmd: %T\r\n", ecmd) );
+
+ result = ncommand(env, ecmd);
+
+ NDBG( ("NET", "command -> result: %T\r\n", result) );
+
+ return result;
+#endif
+}
+
+
+
+/*
+ * The command can, in principle, be anything, though currently we only
+ * support a debug command.
+ */
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM ncommand(ErlNifEnv* env,
+ ERL_NIF_TERM cmd)
+{
+ const ERL_NIF_TERM* t;
+ int tsz;
+
+ if (IS_TUPLE(env, cmd)) {
+ /* Could be the debug tuple */
+ if (!GET_TUPLE(env, cmd, &tsz, &t))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (tsz != 2)
+ return esock_make_error(env, esock_atom_einval);
+
+ /* First element should be the atom 'debug' */
+ if (COMPARE(t[0], atom_debug) != 0)
+ return esock_make_error(env, esock_atom_einval);
+
+ return decode_bool(env, t[1], &data.debug);
+
+ } else {
+ return esock_make_error(env, esock_atom_einval);
+ }
+
+}
+#endif
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_gethostname
+ *
+ * Description:
+ * Access the hostname of the current processor.
+ *
+ */
+static
+ERL_NIF_TERM nif_gethostname(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ ERL_NIF_TERM result;
+
+ NDBG( ("NET", "nif_gethostname -> entry (%d)\r\n", argc) );
+
+ if (argc != 0)
+ return enif_make_badarg(env);
+
+ result = ngethostname(env);
+
+ NDBG( ("NET", "nif_gethostname -> done when result: %T\r\n", result) );
+
+ return result;
+#endif
+}
+
+
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM ngethostname(ErlNifEnv* env)
+{
+ ERL_NIF_TERM result;
+ char buf[NET_MAXHOSTNAMELEN + 1];
+ int res;
+
+ res = net_gethostname(buf, sizeof(buf));
+
+ NDBG( ("NET", "ngethostname -> gethostname res: %d\r\n", res) );
+
+ switch (res) {
+ case 0:
+ result = esock_make_ok2(env, MKS(env, buf));
+ break;
+
+ case EFAULT:
+ result = esock_make_error(env, atom_efault);
+ break;
+
+ case EINVAL:
+ result = esock_make_error(env, esock_atom_einval);
+ break;
+
+ case ENAMETOOLONG:
+ result = esock_make_error(env, atom_enametoolong);
+ break;
+
+ default:
+ result = esock_make_error(env, MKI(env, res));
+ break;
+ }
+
+ return result;
+}
+#endif
+
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_getnameinfo
+ *
+ * Description:
+ * Address-to-name translation in protocol-independent manner.
+ *
+ * Arguments:
+ * SockAddr - Socket Address (address and port)
+ * Flags - The flags argument modifies the behavior of getnameinfo().
+ */
+
+static
+ERL_NIF_TERM nif_getnameinfo(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ ERL_NIF_TERM result;
+ ERL_NIF_TERM eSockAddr, eFlags;
+ int flags = 0; // Just in case...
+ SocketAddress sa;
+ SOCKLEN_T saLen = 0; // Just in case...
+ char* xres;
+
+ NDBG( ("NET", "nif_getnameinfo -> entry (%d)\r\n", argc) );
+
+ if (argc != 2)
+ return enif_make_badarg(env);
+ eSockAddr = argv[0];
+ eFlags = argv[1];
+
+ NDBG( ("NET",
+ "nif_getnameinfo -> "
+ "\r\n SockAddr: %T"
+ "\r\n Flags: %T"
+ "\r\n", eSockAddr, eFlags) );
+
+ if ((xres = esock_decode_sockaddr(env, eSockAddr, &sa, &saLen)) != NULL) {
+ NDBG( ("NET", "nif_getnameinfo -> failed decode sockaddr: %s\r\n", xres) );
+ return esock_make_error_str(env, xres);
+ }
+
+ NDBG( ("NET", "nif_getnameinfo -> (try) decode flags\r\n") );
+
+ if (!decode_nameinfo_flags(env, eFlags, &flags))
+ return enif_make_badarg(env);
+
+ result = ngetnameinfo(env, &sa, saLen, flags);
+
+ NDBG( ("NET",
+ "nif_getnameinfo -> done when result: "
+ "\r\n %T\r\n", result) );
+
+ return result;
+#endif
+}
+
+
+
+/* Given the provided sock(et) address (and flags), retreive the host and
+ * service info.
+ */
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM ngetnameinfo(ErlNifEnv* env,
+ const SocketAddress* saP,
+ SOCKLEN_T saLen,
+ int flags)
+{
+ ERL_NIF_TERM result;
+ char host[HOSTNAME_LEN];
+ SOCKLEN_T hostLen = sizeof(host);
+ char serv[SERVICE_LEN];
+ SOCKLEN_T servLen = sizeof(serv);
+
+ int res = getnameinfo((struct sockaddr*) saP, saLen,
+ host, hostLen,
+ serv, servLen,
+ flags);
+
+ NDBG( ("NET", "ngetnameinfo -> res: %d\r\n", res) );
+
+ switch (res) {
+ case 0:
+ {
+ ERL_NIF_TERM keys[] = {atom_host, atom_service};
+ ERL_NIF_TERM vals[] = {MKS(env, host), MKS(env, serv)};
+ ERL_NIF_TERM info;
+ unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM);
+ unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM);
+
+ ESOCK_ASSERT( (numKeys == numVals) );
+
+ if (!MKMA(env, keys, vals, numKeys, &info))
+ return enif_make_badarg(env);
+
+ result = esock_make_ok2(env, info);
+ }
+ break;
+
+#if defined(EAI_AGAIN)
+ case EAI_AGAIN:
+ result = esock_make_error(env, esock_atom_eagain);
+ break;
+#endif
+
+#if defined(EAI_BADFLAGS)
+ case EAI_BADFLAGS:
+ result = esock_make_error(env, atom_ebadflags);
+ break;
+#endif
+
+#if defined(EAI_FAIL)
+ case EAI_FAIL:
+ result = esock_make_error(env, atom_efail);
+ break;
+#endif
+
+#if defined(EAI_FAMILY)
+ case EAI_FAMILY:
+ result = esock_make_error(env, atom_efamily);
+ break;
+#endif
+
+#if defined(EAI_MEMORY)
+ case EAI_MEMORY:
+ result = esock_make_error(env, atom_emem);
+ break;
+#endif
+
+#if defined(EAI_NONAME)
+ case EAI_NONAME:
+ result = esock_make_error(env, atom_enoname);
+ break;
+#endif
+
+#if defined(EAI_OVERFLOW)
+ case EAI_OVERFLOW:
+ result = esock_make_error(env, atom_eoverflow);
+ break;
+#endif
+
+#if defined(EAI_SYSTEM)
+ case EAI_SYSTEM:
+ result = esock_make_error_errno(env, get_errno());
+ break;
+#endif
+
+ default:
+ result = esock_make_error(env, esock_atom_einval);
+ break;
+ }
+
+ return result;
+}
+#endif
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_getaddrinfo
+ *
+ * Description:
+ * Network address and service translation.
+ *
+ * Arguments:
+ * Host - Host name (either a string or the atom undefined)
+ * Service - Service name (either a string or the atom undefined)
+ * Hints - Hints for the lookup (address info record) (currently *ignored*)
+ */
+
+static
+ERL_NIF_TERM nif_getaddrinfo(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ ERL_NIF_TERM result, eHostName, eServName; //, eHints;
+ char* hostName;
+ char* servName;
+ // struct addrinfo* hints;
+
+ NDBG( ("NET", "nif_getaddrinfo -> entry (%d)\r\n", argc) );
+
+ if (argc != 3) {
+ return enif_make_badarg(env);
+ }
+ eHostName = argv[0];
+ eServName = argv[1];
+ // eHints = argv[2];
+
+ NDBG( ("NET",
+ "nif_getaddrinfo -> "
+ "\r\n ehost: %T"
+ "\r\n eservice: %T"
+ "\r\n ehints: %T"
+ "\r\n", argv[0], argv[1], argv[2]) );
+
+ if (!decode_addrinfo_string(env, eHostName, &hostName))
+ return enif_make_badarg(env);
+
+ if (!decode_addrinfo_string(env, eServName, &servName))
+ return enif_make_badarg(env);
+
+ /*
+ if (decode_addrinfo_hints(env, eHints, &hints))
+ return enif_make_badarg(env);
+ */
+
+ if ((hostName == NULL) && (servName == NULL))
+ return enif_make_badarg(env);
+
+ result = ngetaddrinfo(env, hostName, servName);
+
+ if (hostName != NULL)
+ FREE(hostName);
+
+ if (servName != NULL)
+ FREE(servName);
+
+ /*
+ if (hints != NULL)
+ FREE(hints);
+ */
+
+ NDBG( ("NET",
+ "nif_getaddrinfo -> done when result: "
+ "\r\n %T\r\n", result) );
+
+ return result;
+#endif
+}
+
+
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM ngetaddrinfo(ErlNifEnv* env,
+ char* host,
+ char* serv)
+{
+ ERL_NIF_TERM result;
+ struct addrinfo* addrInfoP;
+ int res;
+
+ NDBG( ("NET", "ngetaddrinfo -> entry with"
+ "\r\n host: %s"
+ "\r\n serv: %s"
+ "\r\n",
+ ((host == NULL) ? "NULL" : host),
+ ((serv == NULL) ? "NULL" : serv)) );
+
+ res = getaddrinfo(host, serv, NULL, &addrInfoP);
+
+ NDBG( ("NET", "ngetaddrinfo -> res: %d\r\n", res) );
+
+ switch (res) {
+ case 0:
+ {
+ ERL_NIF_TERM addrInfo = encode_address_infos(env, addrInfoP);
+ freeaddrinfo(addrInfoP);
+ result = esock_make_ok2(env, addrInfo);
+ }
+ break;
+
+#if defined(EAI_ADDRFAMILY)
+ case EAI_ADDRFAMILY:
+ result = esock_make_error(env, atom_eaddrfamily);
+ break;
+#endif
+
+#if defined(EAI_AGAIN)
+ case EAI_AGAIN:
+ result = esock_make_error(env, esock_atom_eagain);
+ break;
+#endif
+
+#if defined(EAI_BADFLAGS)
+ case EAI_BADFLAGS:
+ result = esock_make_error(env, atom_ebadflags);
+ break;
+#endif
+
+#if defined(EAI_FAIL)
+ case EAI_FAIL:
+ result = esock_make_error(env, atom_efail);
+ break;
+#endif
+
+#if defined(EAI_FAMILY)
+ case EAI_FAMILY:
+ result = esock_make_error(env, atom_efamily);
+ break;
+#endif
+
+#if defined(EAI_MEMORY)
+ case EAI_MEMORY:
+ result = esock_make_error(env, atom_emem);
+ break;
+#endif
+
+#if defined(EAI_NODATA)
+ case EAI_NODATA:
+ result = esock_make_error(env, atom_enodata);
+ break;
+#endif
+
+#if defined(EAI_NONAME)
+ case EAI_NONAME:
+ result = esock_make_error(env, atom_enoname);
+ break;
+#endif
+
+#if defined(EAI_SERVICE)
+ case EAI_SERVICE:
+ result = esock_make_error(env, atom_eservice);
+ break;
+#endif
+
+#if defined(EAI_SOCKTYPE)
+ case EAI_SOCKTYPE:
+ result = esock_make_error(env, atom_esocktype);
+ break;
+#endif
+
+#if defined(EAI_SYSTEM)
+ case EAI_SYSTEM:
+ result = esock_make_error(env, atom_esystem);
+ break;
+#endif
+
+ default:
+ result = esock_make_error(env, esock_atom_einval);
+ break;
+ }
+
+ return result;
+}
+#endif
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_if_name2index
+ *
+ * Description:
+ * Perform a Interface Name to Interface Index translation.
+ *
+ * Arguments:
+ * Ifn - Interface name to be translated.
+ */
+
+static
+ERL_NIF_TERM nif_if_name2index(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ ERL_NIF_TERM eifn, result;
+ char ifn[IF_NAMESIZE+1];
+
+ NDBG( ("NET", "nif_if_name2index -> entry (%d)\r\n", argc) );
+
+ if (argc != 1) {
+ return enif_make_badarg(env);
+ }
+ eifn = argv[0];
+
+ NDBG( ("NET",
+ "nif_if_name2index -> "
+ "\r\n Ifn: %T"
+ "\r\n", argv[0]) );
+
+ if (0 >= GET_STR(env, eifn, ifn, sizeof(ifn)))
+ return esock_make_error(env, esock_atom_einval);
+
+ result = nif_name2index(env, ifn);
+
+ NDBG( ("NET", "nif_if_name2index -> done when result: %T\r\n", result) );
+
+ return result;
+#endif
+}
+
+
+
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nif_name2index(ErlNifEnv* env,
+ char* ifn)
+{
+ unsigned int idx;
+
+ NDBG( ("NET", "nif_name2index -> entry with ifn: %s\r\n", ifn) );
+
+ idx = if_nametoindex(ifn);
+
+ NDBG( ("NET", "nif_name2index -> idx: %d\r\n", idx) );
+
+ if (idx == 0) {
+ int save_errno = get_errno();
+ NDBG( ("NET", "nif_name2index -> failed: %d\r\n", save_errno) );
+ return esock_make_error_errno(env, save_errno);
+ } else {
+ return esock_make_ok2(env, MKI(env, idx));
+ }
+
+}
+#endif
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_if_index2name
+ *
+ * Description:
+ * Perform a Interface Index to Interface Name translation.
+ *
+ * Arguments:
+ * Idx - Interface index to be translated.
+ */
+
+static
+ERL_NIF_TERM nif_if_index2name(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ ERL_NIF_TERM result;
+ unsigned int idx;
+
+ NDBG( ("NET", "nif_if_index2name -> entry (%d)\r\n", argc) );
+
+ if ((argc != 1) ||
+ !GET_UINT(env, argv[0], &idx)) {
+ return enif_make_badarg(env);
+ }
+
+ NDBG( ("NET", "nif_index2name -> "
+ "\r\n Idx: %T"
+ "\r\n", argv[0]) );
+
+ result = nif_index2name(env, idx);
+
+ NDBG( ("NET", "nif_if_index2name -> done when result: %T\r\n", result) );
+
+ return result;
+#endif
+}
+
+
+
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nif_index2name(ErlNifEnv* env,
+ unsigned int idx)
+{
+ ERL_NIF_TERM result;
+ char* ifn = MALLOC(IF_NAMESIZE+1);
+
+ if (ifn == NULL)
+ return enif_make_badarg(env); // PLACEHOLDER
+
+ if (NULL != if_indextoname(idx, ifn)) {
+ result = esock_make_ok2(env, MKS(env, ifn));
+ } else {
+ result = esock_make_error(env, atom_enxio);
+ }
+
+ FREE(ifn);
+
+ return result;
+}
+#endif
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_if_names
+ *
+ * Description:
+ * Get network interface names and indexes.
+ *
+ */
+
+static
+ERL_NIF_TERM nif_if_names(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ ERL_NIF_TERM result;
+
+ NDBG( ("NET", "nif_if_names -> entry (%d)\r\n", argc) );
+
+ if (argc != 0) {
+ return enif_make_badarg(env);
+ }
+
+ result = nif_names(env);
+
+ NDBG( ("NET", "nif_if_names -> done when result: %T\r\n", result) );
+
+ return result;
+#endif
+}
+
+
+
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nif_names(ErlNifEnv* env)
+{
+ ERL_NIF_TERM result;
+ struct if_nameindex* ifs = if_nameindex();
+
+ NDBG( ("NET", "nif_names -> ifs: 0x%lX\r\n", ifs) );
+
+ if (ifs == NULL) {
+ result = esock_make_error_errno(env, get_errno());
+ } else {
+ /*
+ * We got some interfaces:
+ * 1) Calculate how many - the only way is to iterate through the list
+ * until its end (which is indicated by an entry with index = zero
+ * and if_name = NULL).
+ * 2) Allocate an ERL_NIF_TERM array of the calculated length.
+ * 3) Iterate through the array of interfaces and for each create
+ * a two tuple: {Idx, If}
+ *
+ * Or shall we instead build a list in reverse order and then when
+ * its done, reverse that? Check
+ */
+ unsigned int len = nif_names_length(ifs);
+
+ NDBG( ("NET", "nif_names -> len: %d\r\n", len) );
+
+ if (len > 0) {
+ ERL_NIF_TERM* array = MALLOC(len * sizeof(ERL_NIF_TERM));
+ unsigned int i;
+
+ for (i = 0; i < len; i++) {
+ array[i] = MKT2(env,
+ MKI(env, ifs[i].if_index),
+ MKS(env, ifs[i].if_name));
+ }
+
+ result = esock_make_ok2(env, MKLA(env, array, len));
+ FREE(array);
+ } else {
+ result = esock_make_ok2(env, enif_make_list(env, 0));
+ }
+ }
+
+ if (ifs != NULL)
+ if_freenameindex(ifs);
+
+ return result;
+}
+
+
+static
+unsigned int nif_names_length(struct if_nameindex* p)
+{
+ unsigned int len = 0;
+ BOOLEAN_T done = FALSE;
+
+ while (!done) {
+
+ NDBG( ("NET", "nif_names_length -> %d: "
+ "\r\n if_index: %d"
+ "\r\n if_name: 0x%lX"
+ "\r\n", len, p[len].if_index, p[len].if_name) );
+
+ if ((p[len].if_index == 0) && (p[len].if_name == NULL))
+ done = TRUE;
+ else
+ len++;
+ }
+
+ return len;
+}
+#endif // if !defined(__WIN32__)
+
+
+
+/* ----------------------------------------------------------------------
+ * U t i l i t y F u n c t i o n s
+ * ----------------------------------------------------------------------
+ */
+
+/* The erlang format for a set of flags is a list of atoms.
+ * A special case is when there is no flags, which is
+ * represented by the atom undefined.
+ */
+#if !defined(__WIN32__)
+static
+BOOLEAN_T decode_nameinfo_flags(ErlNifEnv* env,
+ const ERL_NIF_TERM eflags,
+ int* flags)
+{
+ BOOLEAN_T result;
+
+ if (IS_ATOM(env, eflags)) {
+ NDBG( ("NET", "decode_nameinfo_flags -> is atom (%T)\r\n", eflags) );
+ if (COMPARE(eflags, esock_atom_undefined) == 0) {
+ *flags = 0;
+ result = TRUE;
+ } else {
+ result = FALSE;
+ }
+ } else if (IS_LIST(env, eflags)) {
+ NDBG( ("NET", "decode_nameinfo_flags -> is list\r\n") );
+ result = decode_nameinfo_flags_list(env, eflags, flags);
+ } else {
+ result = FALSE;
+ }
+
+ NDBG( ("NET", "decode_nameinfo_flags -> result: %s\r\n", B2S(result)) );
+
+ return result;
+}
+
+
+
+static
+BOOLEAN_T decode_nameinfo_flags_list(ErlNifEnv* env,
+ const ERL_NIF_TERM eflags,
+ int* flags)
+{
+ ERL_NIF_TERM elem, tail, list = eflags;
+ int tmp = 0;
+ BOOLEAN_T done = FALSE;
+
+ while (!done) {
+ if (GET_LIST_ELEM(env, list, &elem, &tail)) {
+ if (COMPARE(elem, atom_namereqd) == 0) {
+ tmp |= NI_NAMEREQD;
+ } else if (COMPARE(elem, esock_atom_dgram) == 0) {
+ tmp |= NI_DGRAM;
+ } else if (COMPARE(elem, atom_nofqdn) == 0) {
+ tmp |= NI_NOFQDN;
+ } else if (COMPARE(elem, atom_numerichost) == 0) {
+ tmp |= NI_NUMERICHOST;
+ } else if (COMPARE(elem, atom_numericserv) == 0) {
+ tmp |= NI_NUMERICSERV;
+
+ /* Starting with glibc 2.3.4: */
+
+#if defined(NI_IDN)
+ } else if (COMPARE(elem, atom_idn) == 0) {
+ tmp |= NI_IDN;
+#endif
+
+#if defined(NI_IDN_ALLOW_UNASSIGNED)
+ } else if (COMPARE(elem, atom_idna_allow_unassigned) == 0) {
+ tmp |= NI_IDN_ALLOW_UNASSIGNED;
+#endif
+
+#if defined(NI_IDN_USE_STD3_ASCII_RULES)
+ } else if (COMPARE(elem, atom_idna_use_std3_ascii_rules) == 0) {
+ tmp |= NI_IDN_USE_STD3_ASCII_RULES;
+#endif
+
+ } else {
+ return FALSE;
+ }
+
+ list = tail;
+
+ } else {
+ done = TRUE;
+ }
+ }
+
+ *flags = tmp;
+
+ return TRUE;
+}
+
+
+
+/* Decode the address info string (hostname or service name)
+ * The string is either the atom undefined or an actual string.
+ */
+static
+BOOLEAN_T decode_addrinfo_string(ErlNifEnv* env,
+ const ERL_NIF_TERM eString,
+ char** stringP)
+{
+ BOOLEAN_T result;
+
+ if (IS_ATOM(env, eString)) {
+
+ if (COMPARE(eString, esock_atom_undefined) == 0) {
+ *stringP = NULL;
+ result = TRUE;
+ } else {
+ *stringP = NULL;
+ result = FALSE;
+ }
+
+ } else {
+
+ result = esock_decode_string(env, eString, stringP);
+
+ }
+
+ return result;
+
+}
+
+
+
+static
+ERL_NIF_TERM decode_bool(ErlNifEnv* env,
+ ERL_NIF_TERM eBool,
+ BOOLEAN_T* bool)
+{
+ if (COMPARE(eBool, esock_atom_true) == 0) {
+ *bool = TRUE;
+ return esock_atom_ok;
+ } else if (COMPARE(eBool, esock_atom_false) == 0) {
+ *bool = FALSE;
+ return esock_atom_ok;
+ } else {
+ return esock_make_error(env, esock_atom_einval);
+ }
+}
+
+
+
+/* Encode the address info
+ * The address info is a linked list och address info, which
+ * will result in the result being a list of zero or more length.
+ */
+static
+ERL_NIF_TERM encode_address_infos(ErlNifEnv* env,
+ struct addrinfo* addrInfo)
+{
+ ERL_NIF_TERM result;
+ unsigned int len = address_info_length(addrInfo);
+
+ NDBG( ("NET", "encode_address_infos -> len: %d\r\n", len) );
+
+ if (len > 0) {
+ ERL_NIF_TERM* array = MALLOC(len * sizeof(ERL_NIF_TERM)); // LEAK?
+ unsigned int i = 0;
+ struct addrinfo* p = addrInfo;
+
+ while (i < len) {
+ array[i] = encode_address_info(env, p);
+ p = p->ai_next;
+ i++;
+ }
+
+ result = MKLA(env, array, len);
+ } else {
+ result = MKEL(env);
+ }
+
+ NDBG( ("NET", "encode_address_infos -> result: "
+ "\r\n %T\r\n", result) );
+
+ return result;
+}
+
+
+
+/* Calculate the length of the adress info linked list
+ * The list is NULL-terminated, so the only way is to
+ * iterate through the list until we find next = NULL.
+ */
+static
+unsigned int address_info_length(struct addrinfo* addrInfoP)
+{
+ unsigned int len = 1;
+ struct addrinfo* tmp;
+ BOOLEAN_T done = FALSE;
+
+ tmp = addrInfoP;
+
+ while (!done) {
+ if (tmp->ai_next != NULL) {
+ len++;
+ tmp = tmp->ai_next;
+ } else {
+ done = TRUE;
+ }
+ }
+
+ return len;
+}
+
+
+
+/* Create one (erlang) instance of the address info record
+ * Should we have address info as a record or as a map?
+ *
+ * {address_info, Fam, Type, Proto, Addr}
+ */
+static
+ERL_NIF_TERM encode_address_info(ErlNifEnv* env,
+ struct addrinfo* addrInfoP)
+{
+ ERL_NIF_TERM fam, type, proto, addr, addrInfo;
+
+ fam = encode_address_info_family(env, addrInfoP->ai_family);
+ type = encode_address_info_type(env, addrInfoP->ai_socktype);
+ proto = encode_address_info_proto(env, addrInfoP->ai_protocol);
+ esock_encode_sockaddr(env,
+ (SocketAddress*) addrInfoP->ai_addr,
+ addrInfoP->ai_addrlen,
+ &addr);
+
+ if (make_address_info(env, fam, type, proto, addr, &addrInfo) == NULL)
+ return addrInfo;
+ else
+ return esock_atom_undefined; // We should to better...
+
+}
+
+
+/* Convert an "native" family to an erlang family (=domain).
+ * Note that this is not currently exhaustive, but only supports
+ * inet and inet6. Other values will be returned as is, that is
+ * in the form of an integer.
+ */
+static
+ERL_NIF_TERM encode_address_info_family(ErlNifEnv* env,
+ int family)
+{
+ ERL_NIF_TERM efam;
+
+ if (NULL != esock_encode_domain(env, family, &efam))
+ efam = MKI(env, family);
+
+ return efam;
+}
+
+
+
+/* Convert an "native" socket type to an erlang socket type.
+ * Note that this is not currently exhaustive, but only supports
+ * stream and dgram. Other values will be returned as is, that is
+ * in the form of an integer.
+ */
+static
+ERL_NIF_TERM encode_address_info_type(ErlNifEnv* env,
+ int socktype)
+{
+ ERL_NIF_TERM etype;
+
+ if (NULL != esock_encode_type(env, socktype, &etype))
+ etype = MKI(env, socktype);
+
+ return etype;
+}
+
+
+
+/* Convert an "native" protocol to an erlang protocol.
+ * Note that this is not currently exhaustive, but only supports
+ * tcp and udp. Other values will be returned as is, that is
+ * in the form of an integer.
+ */
+static
+ERL_NIF_TERM encode_address_info_proto(ErlNifEnv* env,
+ int proto)
+{
+ ERL_NIF_TERM eproto;
+
+ if (NULL != esock_encode_protocol(env, proto, &eproto))
+ eproto = MKI(env, proto);
+
+ return eproto;
+}
+
+
+
+static
+char* make_address_info(ErlNifEnv* env,
+ ERL_NIF_TERM fam,
+ ERL_NIF_TERM sockType,
+ ERL_NIF_TERM proto,
+ ERL_NIF_TERM addr,
+ ERL_NIF_TERM* ai)
+{
+ ERL_NIF_TERM keys[] = {esock_atom_family,
+ esock_atom_type,
+ esock_atom_protocol,
+ esock_atom_addr};
+ ERL_NIF_TERM vals[] = {fam, sockType, proto, addr};
+ unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM);
+ unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM);
+
+ ESOCK_ASSERT( (numKeys == numVals) );
+
+ if (!MKMA(env, keys, vals, numKeys, ai)) {
+ *ai = esock_atom_undefined;
+ return ESOCK_STR_EINVAL;
+ } else {
+ return NULL;
+ }
+}
+#endif // if !defined(__WIN32__)
+
+
+
+/* ----------------------------------------------------------------------
+ * C a l l b a c k F u n c t i o n s
+ * ----------------------------------------------------------------------
+ */
+
+/* =========================================================================
+ * net_dtor - Callback function for resource destructor
+ *
+ */
+/*
+static
+void net_dtor(ErlNifEnv* env, void* obj)
+{
+}
+*/
+
+
+/* =========================================================================
+ * net_stop - Callback function for resource stop
+ *
+ */
+/*
+static
+void net_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call)
+{
+}
+*/
+
+
+
+
+/* =========================================================================
+ * net_down - Callback function for resource down (monitored processes)
+ *
+ */
+/*
+static
+void net_down(ErlNifEnv* env,
+ void* obj,
+ const ErlNifPid* pid,
+ const ErlNifMonitor* mon)
+{
+}
+*/
+
+
+
+/* ----------------------------------------------------------------------
+ * L o a d / u n l o a d / u p g r a d e F u n c t i o n s
+ * ----------------------------------------------------------------------
+ */
+
+static
+ErlNifFunc net_funcs[] =
+{
+ // Some utility functions
+ {"nif_info", 0, nif_info, 0},
+ {"nif_command", 1, nif_command, 0}, // Shall we let this be dirty?
+
+ /* get/set hostname */
+ {"nif_gethostname", 0, nif_gethostname, 0},
+
+ /* address and name translation in protocol-independent manner */
+ {"nif_getnameinfo", 2, nif_getnameinfo, 0},
+ {"nif_getaddrinfo", 3, nif_getaddrinfo, 0},
+
+ /* Network interface (name and/or index) functions */
+ {"nif_if_name2index", 1, nif_if_name2index, 0},
+ {"nif_if_index2name", 1, nif_if_index2name, 0},
+ {"nif_if_names", 0, nif_if_names, 0}
+};
+
+
+#if !defined(__WIN32__)
+static
+BOOLEAN_T extract_debug(ErlNifEnv* env,
+ ERL_NIF_TERM map)
+{
+ /*
+ * We need to do this here since the "proper" atom has not been
+ * created when this function is called.
+ */
+ ERL_NIF_TERM debug = MKA(env, "debug");
+
+ return esock_extract_bool_from_map(env, map, debug, NET_NIF_DEBUG_DEFAULT);
+}
+#endif
+
+
+/* =======================================================================
+ * load_info - A map of misc info (e.g global debug)
+ */
+
+static
+int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
+{
+#if !defined(__WIN32__)
+ // We should make it possible to use load_info to get default values
+ data.debug = extract_debug(env, load_info);
+
+ NDBG( ("NET", "on_load -> entry\r\n") );
+#endif
+
+ /* +++ Misc atoms +++ */
+ atom_address_info = MKA(env, str_address_info);
+ atom_debug = MKA(env, str_debug);
+ atom_host = MKA(env, "host");
+ atom_idn = MKA(env, str_idn);
+ atom_idna_allow_unassigned = MKA(env, str_idna_allow_unassigned);
+ atom_idna_use_std3_ascii_rules = MKA(env, str_idna_use_std3_ascii_rules);
+ atom_namereqd = MKA(env, str_namereqd);
+ atom_name_info = MKA(env, str_name_info);
+ atom_nofqdn = MKA(env, str_nofqdn);
+ atom_numerichost = MKA(env, str_numerichost);
+ atom_numericserv = MKA(env, str_numericserv);
+ atom_service = MKA(env, "service");
+
+ /* Error codes */
+ atom_eaddrfamily = MKA(env, str_eaddrfamily);
+ atom_ebadflags = MKA(env, str_ebadflags);
+ atom_efail = MKA(env, str_efail);
+ atom_efamily = MKA(env, str_efamily);
+ atom_efault = MKA(env, str_efault);
+ atom_emem = MKA(env, str_emem);
+ atom_enametoolong = MKA(env, str_enametoolong);
+ atom_enodata = MKA(env, str_enodata);
+ atom_enoname = MKA(env, str_enoname);
+ atom_enxio = MKA(env, str_enxio);
+ atom_eoverflow = MKA(env, str_eoverflow);
+ atom_eservice = MKA(env, str_eservice);
+ atom_esocktype = MKA(env, str_esocktype);
+ atom_esystem = MKA(env, str_esystem);
+
+ // For storing "global" things...
+ // data.env = enif_alloc_env(); // We should really check
+ // data.version = MKA(env, ERTS_VERSION);
+ // data.buildDate = MKA(env, ERTS_BUILD_DATE);
+
+ net = enif_open_resource_type_x(env,
+ "net",
+ &netInit,
+ ERL_NIF_RT_CREATE,
+ NULL);
+
+#if !defined(__WIN32__)
+ NDBG( ("NET", "on_load -> done\r\n") );
+#endif
+
+ return !net;
+}
+
+ERL_NIF_INIT(net, net_funcs, on_load, NULL, NULL, NULL)
diff --git a/erts/emulator/nifs/common/prim_file_nif.c b/erts/emulator/nifs/common/prim_file_nif.c
index a78ee62677..3df04e42e2 100644
--- a/erts/emulator/nifs/common/prim_file_nif.c
+++ b/erts/emulator/nifs/common/prim_file_nif.c
@@ -476,7 +476,8 @@ static ERL_NIF_TERM open_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]
ERL_NIF_TERM result;
efile_path_t path;
- if(argc != 2 || !enif_is_list(env, argv[1])) {
+ ASSERT(argc == 2);
+ if(!enif_is_list(env, argv[1])) {
return enif_make_badarg(env);
}
@@ -551,7 +552,8 @@ static ERL_NIF_TERM read_nif_impl(efile_data_t *d, ErlNifEnv *env, int argc, con
SysIOVec read_vec[1];
ErlNifBinary result;
- if(argc != 1 || !enif_is_number(env, argv[0])) {
+ ASSERT(argc == 1);
+ if(!enif_is_number(env, argv[0])) {
return enif_make_badarg(env);
}
@@ -589,7 +591,8 @@ static ERL_NIF_TERM write_nif_impl(efile_data_t *d, ErlNifEnv *env, int argc, co
Sint64 bytes_written;
ERL_NIF_TERM tail;
- if(argc != 1 || !enif_inspect_iovec(env, 64, argv[0], &tail, &input)) {
+ ASSERT(argc == 1);
+ if(!enif_inspect_iovec(env, 64, argv[0], &tail, &input)) {
return enif_make_badarg(env);
}
@@ -612,8 +615,8 @@ static ERL_NIF_TERM pread_nif_impl(efile_data_t *d, ErlNifEnv *env, int argc, co
SysIOVec read_vec[1];
ErlNifBinary result;
- if(argc != 2 || !enif_is_number(env, argv[0])
- || !enif_is_number(env, argv[1])) {
+ ASSERT(argc == 2);
+ if(!enif_is_number(env, argv[0]) || !enif_is_number(env, argv[1])) {
return enif_make_badarg(env);
}
@@ -652,8 +655,9 @@ static ERL_NIF_TERM pwrite_nif_impl(efile_data_t *d, ErlNifEnv *env, int argc, c
Sint64 bytes_written, offset;
ERL_NIF_TERM tail;
- if(argc != 2 || !enif_is_number(env, argv[0])
- || !enif_inspect_iovec(env, 64, argv[1], &tail, &input)) {
+ ASSERT(argc == 2);
+ if(!enif_is_number(env, argv[0])
+ || !enif_inspect_iovec(env, 64, argv[1], &tail, &input)) {
return enif_make_badarg(env);
}
@@ -680,7 +684,8 @@ static ERL_NIF_TERM seek_nif_impl(efile_data_t *d, ErlNifEnv *env, int argc, con
Sint64 new_position, offset;
enum efile_seek_t seek;
- if(argc != 2 || !enif_get_int64(env, argv[1], &offset)) {
+ ASSERT(argc == 2);
+ if(!enif_get_int64(env, argv[1], &offset)) {
return enif_make_badarg(env);
}
@@ -704,7 +709,8 @@ static ERL_NIF_TERM seek_nif_impl(efile_data_t *d, ErlNifEnv *env, int argc, con
static ERL_NIF_TERM sync_nif_impl(efile_data_t *d, ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
int data_only;
- if(argc != 1 || !enif_get_int(env, argv[0], &data_only)) {
+ ASSERT(argc == 1);
+ if(!enif_get_int(env, argv[0], &data_only)) {
return enif_make_badarg(env);
}
@@ -716,9 +722,7 @@ static ERL_NIF_TERM sync_nif_impl(efile_data_t *d, ErlNifEnv *env, int argc, con
}
static ERL_NIF_TERM truncate_nif_impl(efile_data_t *d, ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
- if(argc != 0) {
- return enif_make_badarg(env);
- }
+ ASSERT(argc == 0);
if(!efile_truncate(d)) {
return posix_error_to_tuple(env, d->posix_errno);
@@ -730,8 +734,8 @@ static ERL_NIF_TERM truncate_nif_impl(efile_data_t *d, ErlNifEnv *env, int argc,
static ERL_NIF_TERM allocate_nif_impl(efile_data_t *d, ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
Sint64 offset, length;
- if(argc != 2 || !enif_is_number(env, argv[0])
- || !enif_is_number(env, argv[1])) {
+ ASSERT(argc == 2);
+ if(!enif_is_number(env, argv[0]) || !enif_is_number(env, argv[1])) {
return enif_make_badarg(env);
}
@@ -752,8 +756,8 @@ static ERL_NIF_TERM advise_nif_impl(efile_data_t *d, ErlNifEnv *env, int argc, c
enum efile_advise_t advise;
Sint64 offset, length;
- if(argc != 3 || !enif_is_number(env, argv[0])
- || !enif_is_number(env, argv[1])) {
+ ASSERT(argc == 3);
+ if(!enif_is_number(env, argv[0]) || !enif_is_number(env, argv[1])) {
return enif_make_badarg(env);
}
@@ -816,8 +820,8 @@ static ERL_NIF_TERM ipread_s32bu_p32bu_nif_impl(efile_data_t *d, ErlNifEnv *env,
ErlNifBinary payload;
- if(argc != 2 || !enif_is_number(env, argv[0])
- || !enif_is_number(env, argv[1])) {
+ ASSERT(argc == 2);
+ if(!enif_is_number(env, argv[0]) || !enif_is_number(env, argv[1])) {
return enif_make_badarg(env);
}
@@ -884,9 +888,7 @@ static ERL_NIF_TERM ipread_s32bu_p32bu_nif_impl(efile_data_t *d, ErlNifEnv *env,
}
static ERL_NIF_TERM get_handle_nif_impl(efile_data_t *d, ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
- if(argc != 0) {
- return enif_make_badarg(env);
- }
+ ASSERT(argc == 0);
return efile_get_handle(env, d);
}
@@ -898,7 +900,8 @@ static ERL_NIF_TERM read_info_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM a
efile_path_t path;
int follow_links;
- if(argc != 2 || !enif_get_int(env, argv[1], &follow_links)) {
+ ASSERT(argc == 2);
+ if(!enif_get_int(env, argv[1], &follow_links)) {
return enif_make_badarg(env);
}
@@ -933,7 +936,8 @@ static ERL_NIF_TERM set_permissions_nif(ErlNifEnv *env, int argc, const ERL_NIF_
efile_path_t path;
unsigned int permissions;
- if(argc != 2 || !enif_get_uint(env, argv[1], &permissions)) {
+ ASSERT(argc == 2);
+ if(!enif_get_uint(env, argv[1], &permissions)) {
return enif_make_badarg(env);
}
@@ -952,8 +956,8 @@ static ERL_NIF_TERM set_owner_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM a
efile_path_t path;
int uid, gid;
- if(argc != 3 || !enif_get_int(env, argv[1], &uid)
- || !enif_get_int(env, argv[2], &gid)) {
+ ASSERT(argc == 3);
+ if(!enif_get_int(env, argv[1], &uid) || !enif_get_int(env, argv[2], &gid)) {
return enif_make_badarg(env);
}
@@ -972,9 +976,10 @@ static ERL_NIF_TERM set_time_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM ar
Sint64 accessed, modified, created;
efile_path_t path;
- if(argc != 4 || !enif_get_int64(env, argv[1], &accessed)
- || !enif_get_int64(env, argv[2], &modified)
- || !enif_get_int64(env, argv[3], &created)) {
+ ASSERT(argc == 4);
+ if(!enif_get_int64(env, argv[1], &accessed)
+ || !enif_get_int64(env, argv[2], &modified)
+ || !enif_get_int64(env, argv[3], &created)) {
return enif_make_badarg(env);
}
@@ -993,9 +998,7 @@ static ERL_NIF_TERM read_link_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM a
efile_path_t path;
ERL_NIF_TERM result;
- if(argc != 1) {
- return enif_make_badarg(env);
- }
+ ASSERT(argc == 1);
if((posix_errno = efile_marshal_path(env, argv[0], &path))) {
return posix_error_to_tuple(env, posix_errno);
@@ -1012,9 +1015,7 @@ static ERL_NIF_TERM list_dir_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM ar
efile_path_t path;
ERL_NIF_TERM result;
- if(argc != 1) {
- return enif_make_badarg(env);
- }
+ ASSERT(argc == 1);
if((posix_errno = efile_marshal_path(env, argv[0], &path))) {
return posix_error_to_tuple(env, posix_errno);
@@ -1030,9 +1031,7 @@ static ERL_NIF_TERM rename_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv
efile_path_t existing_path, new_path;
- if(argc != 2) {
- return enif_make_badarg(env);
- }
+ ASSERT(argc == 2);
if((posix_errno = efile_marshal_path(env, argv[0], &existing_path))) {
return posix_error_to_tuple(env, posix_errno);
@@ -1050,9 +1049,7 @@ static ERL_NIF_TERM make_hard_link_nif(ErlNifEnv *env, int argc, const ERL_NIF_T
efile_path_t existing_path, new_path;
- if(argc != 2) {
- return enif_make_badarg(env);
- }
+ ASSERT(argc == 2);
if((posix_errno = efile_marshal_path(env, argv[0], &existing_path))) {
return posix_error_to_tuple(env, posix_errno);
@@ -1070,9 +1067,7 @@ static ERL_NIF_TERM make_soft_link_nif(ErlNifEnv *env, int argc, const ERL_NIF_T
efile_path_t existing_path, new_path;
- if(argc != 2) {
- return enif_make_badarg(env);
- }
+ ASSERT(argc == 2);
if((posix_errno = efile_marshal_path(env, argv[0], &existing_path))) {
return posix_error_to_tuple(env, posix_errno);
@@ -1090,9 +1085,7 @@ static ERL_NIF_TERM make_dir_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM ar
efile_path_t path;
- if(argc != 1) {
- return enif_make_badarg(env);
- }
+ ASSERT(argc == 1);
if((posix_errno = efile_marshal_path(env, argv[0], &path))) {
return posix_error_to_tuple(env, posix_errno);
@@ -1108,9 +1101,7 @@ static ERL_NIF_TERM del_file_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM ar
efile_path_t path;
- if(argc != 1) {
- return enif_make_badarg(env);
- }
+ ASSERT(argc == 1);
if((posix_errno = efile_marshal_path(env, argv[0], &path))) {
return posix_error_to_tuple(env, posix_errno);
@@ -1126,9 +1117,7 @@ static ERL_NIF_TERM del_dir_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM arg
efile_path_t path;
- if(argc != 1) {
- return enif_make_badarg(env);
- }
+ ASSERT(argc == 1);
if((posix_errno = efile_marshal_path(env, argv[0], &path))) {
return posix_error_to_tuple(env, posix_errno);
@@ -1145,7 +1134,8 @@ static ERL_NIF_TERM get_device_cwd_nif(ErlNifEnv *env, int argc, const ERL_NIF_T
ERL_NIF_TERM result;
int device_index;
- if(argc != 1 || !enif_get_int(env, argv[0], &device_index)) {
+ ASSERT(argc == 1);
+ if(!enif_get_int(env, argv[0], &device_index)) {
return enif_make_badarg(env);
}
@@ -1160,9 +1150,7 @@ static ERL_NIF_TERM get_cwd_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM arg
posix_errno_t posix_errno;
ERL_NIF_TERM result;
- if(argc != 0) {
- return enif_make_badarg(env);
- }
+ ASSERT(argc == 0);
if((posix_errno = efile_get_cwd(env, &result))) {
return posix_error_to_tuple(env, posix_errno);
@@ -1176,9 +1164,7 @@ static ERL_NIF_TERM set_cwd_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM arg
efile_path_t path;
- if(argc != 1) {
- return enif_make_badarg(env);
- }
+ ASSERT(argc == 1);
if((posix_errno = efile_marshal_path(env, argv[0], &path))) {
return posix_error_to_tuple(env, posix_errno);
@@ -1254,9 +1240,7 @@ static ERL_NIF_TERM read_file_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM a
ErlNifBinary result;
- if(argc != 1) {
- return enif_make_badarg(env);
- }
+ ASSERT(argc == 1);
if((posix_errno = efile_marshal_path(env, argv[0], &path))) {
return posix_error_to_tuple(env, posix_errno);
@@ -1284,9 +1268,7 @@ static ERL_NIF_TERM altname_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM arg
efile_path_t path;
ERL_NIF_TERM result;
- if(argc != 1) {
- return enif_make_badarg(env);
- }
+ ASSERT(argc == 1);
if((posix_errno = efile_marshal_path(env, argv[0], &path))) {
return posix_error_to_tuple(env, posix_errno);
diff --git a/erts/emulator/nifs/common/socket_dbg.c b/erts/emulator/nifs/common/socket_dbg.c
new file mode 100644
index 0000000000..fe9135e5a0
--- /dev/null
+++ b/erts/emulator/nifs/common/socket_dbg.c
@@ -0,0 +1,138 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2018-2018. All 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 : Debug functions for the socket and net NIF(s).
+ * ----------------------------------------------------------------------
+ *
+ */
+
+#include <stdarg.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <time.h>
+
+#include <erl_nif.h>
+#include "socket_dbg.h"
+
+#define TSELF() enif_thread_self()
+#define TNAME(__T__) enif_thread_name( __T__ )
+#define TSNAME() TNAME(TSELF())
+
+static FILE* dbgout = NULL;
+
+static int realtime(struct timespec* tsP);
+static int timespec2str(char *buf, unsigned int len, struct timespec *ts);
+
+
+extern
+void esock_dbg_init(char* filename)
+{
+ if (filename != NULL) {
+ if (strcmp(filename, ESOCK_DBGOUT_DEFAULT) == 0) {
+ dbgout = stdout;
+ } else if (strcmp(filename, ESOCK_DBGOUT_UNIQUE) == 0) {
+ char template[] = "/tmp/esock-dbg-XXXXXX";
+ dbgout = fdopen(mkstemp(template), "w+");
+ } else {
+ dbgout = fopen(filename, "w+");
+ }
+ } else {
+ char template[] = "/tmp/esock-dbg-XXXXXX";
+ dbgout = fdopen(mkstemp(template), "w+");
+ }
+}
+
+
+
+/*
+ * Print a debug format string *with* both a timestamp and the
+ * the name of the *current* thread.
+ */
+extern
+void esock_dbg_printf( const char* prefix, const char* format, ... )
+{
+ va_list args;
+ char f[512 + sizeof(format)]; // This has to suffice...
+ char stamp[30];
+ struct timespec ts;
+ 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.
+ */
+
+ if (!realtime(&ts)) {
+ if (timespec2str(stamp, sizeof(stamp), &ts) != 0) {
+ res = enif_snprintf(f, sizeof(f), "%s [%s] %s", prefix, TSNAME(), format);
+ // res = enif_snprintf(f, sizeof(f), "%s [%s]", prefix, format);
+ } else {
+ res = enif_snprintf(f, sizeof(f), "%s [%s] [%s] %s", prefix, stamp, TSNAME(), format);
+ // res = enif_snprintf(f, sizeof(f), "%s [%s] %s", prefix, stamp, format);
+ }
+
+ if (res > 0) {
+ va_start (args, format);
+ enif_vfprintf (dbgout, f, args);
+ va_end (args);
+ fflush(stdout);
+ }
+ }
+
+ return;
+}
+
+
+static
+int realtime(struct timespec* tsP)
+{
+ return clock_gettime(CLOCK_REALTIME, tsP);
+}
+
+
+
+
+/*
+ * Convert a timespec struct into a readable/printable string
+ */
+static
+int timespec2str(char *buf, unsigned int len, struct timespec *ts)
+{
+ int ret, buflen;
+ struct tm t;
+
+ tzset();
+ if (localtime_r(&(ts->tv_sec), &t) == NULL)
+ return 1;
+
+ ret = strftime(buf, len, "%F %T", &t);
+ if (ret == 0)
+ return 2;
+ len -= ret - 1;
+ buflen = strlen(buf);
+
+ ret = snprintf(&buf[buflen], len, ".%06ld", ts->tv_nsec/1000);
+ if (ret >= len)
+ return 3;
+
+ return 0;
+}
diff --git a/erts/emulator/nifs/common/socket_dbg.h b/erts/emulator/nifs/common/socket_dbg.h
new file mode 100644
index 0000000000..47739b46da
--- /dev/null
+++ b/erts/emulator/nifs/common/socket_dbg.h
@@ -0,0 +1,55 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2018-2018. All 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 : Defines and macros for the debug util of the socket and
+ * net NIF(s).
+ * ----------------------------------------------------------------------
+ *
+ */
+
+#ifndef SOCKET_DBG_H__
+#define SOCKET_DBG_H__
+
+/* Used when calling the init function */
+#define ESOCK_DBGOUT_DEFAULT "stdout"
+#define ESOCK_DBGOUT_UNIQUE "unique"
+
+
+/* Used in debug printouts */
+#ifdef __WIN32__
+#define LLU "%I64u"
+#else
+#define LLU "%llu"
+#endif
+typedef unsigned long long llu_t;
+
+
+
+#define ESOCK_DBG_PRINTF( ___COND___ , proto ) \
+ if ( ___COND___ ) { \
+ esock_dbg_printf proto; \
+ fflush(stdout); \
+ }
+
+
+extern void esock_dbg_init(char* filename);
+extern void esock_dbg_printf( const char* prefix, const char* format, ... );
+
+#endif // SOCKET_DBG_H__
diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h
new file mode 100644
index 0000000000..a9e83adc21
--- /dev/null
+++ b/erts/emulator/nifs/common/socket_int.h
@@ -0,0 +1,384 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2018-2018. All 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 : Utility "stuff" for socket and net.
+ * ----------------------------------------------------------------------
+ *
+ */
+
+#ifndef SOCKET_INT_H__
+#define SOCKET_INT_H__
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef __WIN32__
+
+/* All this just to replace sys/socket.h, netinet/in.h and sys/un.h??? */
+#define INCL_WINSOCK_API_TYPEDEFS 1
+#ifndef WINDOWS_H_INCLUDES_WINSOCK2_H
+#include <winsock2.h>
+#endif
+#include <windows.h>
+#include <Ws2tcpip.h> /* NEED VC 6.0 or higher */
+/* Visual studio 2008+: NTDDI_VERSION needs to be set for iphlpapi.h
+ * to define the right structures. It needs to be set to WINXP (or LONGHORN)
+ * for IPV6 to work and it's set lower by default, so we need to change it.
+ */
+#ifdef HAVE_SDKDDKVER_H
+# include <sdkddkver.h>
+# ifdef NTDDI_VERSION
+# undef NTDDI_VERSION
+# endif
+# define NTDDI_VERSION NTDDI_WINXP
+#endif
+#include <iphlpapi.h>
+
+#else /* !__WIN32__ */
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h>
+#endif
+
+#endif
+
+#include <erl_nif.h>
+
+/* The general purpose sockaddr */
+typedef union {
+ /* General sockaddr */
+ struct sockaddr sa;
+
+ /* IPv4 sockaddr */
+ struct sockaddr_in in4;
+
+ /* IPv6 sockaddr */
+#if defined(HAVE_IN6) && defined(AF_INET6)
+ struct sockaddr_in6 in6;
+#endif
+
+ /* Unix Domain Socket sockaddr */
+#if defined(HAVE_SYS_UN_H)
+ struct sockaddr_un un;
+#endif
+
+} SocketAddress;
+
+
+/* *** Boolean *type* stuff... *** */
+typedef unsigned int BOOLEAN_T;
+#define TRUE 1
+#define FALSE 0
+
+#define BOOL2ATOM(__B__) ((__B__) ? esock_atom_true : esock_atom_false)
+
+#define B2S(__B__) ((__B__) ? "true" : "false")
+
+/* Misc error strings */
+#define ESOCK_STR_EAFNOSUPPORT "eafnosupport"
+#define ESOCK_STR_EAGAIN "eagain"
+#define ESOCK_STR_EINVAL "einval"
+
+
+/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ * "Global" atoms
+ */
+extern ERL_NIF_TERM esock_atom_abort;
+extern ERL_NIF_TERM esock_atom_accept;
+extern ERL_NIF_TERM esock_atom_acceptconn;
+extern ERL_NIF_TERM esock_atom_acceptfilter;
+extern ERL_NIF_TERM esock_atom_adaption_layer;
+extern ERL_NIF_TERM esock_atom_addr;
+extern ERL_NIF_TERM esock_atom_addrform;
+extern ERL_NIF_TERM esock_atom_add_membership;
+extern ERL_NIF_TERM esock_atom_add_source_membership;
+extern ERL_NIF_TERM esock_atom_any;
+extern ERL_NIF_TERM esock_atom_associnfo;
+extern ERL_NIF_TERM esock_atom_authhdr;
+extern ERL_NIF_TERM esock_atom_auth_active_key;
+extern ERL_NIF_TERM esock_atom_auth_asconf;
+extern ERL_NIF_TERM esock_atom_auth_chunk;
+extern ERL_NIF_TERM esock_atom_auth_delete_key;
+extern ERL_NIF_TERM esock_atom_auth_key;
+extern ERL_NIF_TERM esock_atom_auth_level;
+extern ERL_NIF_TERM esock_atom_autoclose;
+extern ERL_NIF_TERM esock_atom_bindtodevice;
+extern ERL_NIF_TERM esock_atom_block_source;
+extern ERL_NIF_TERM esock_atom_broadcast;
+extern ERL_NIF_TERM esock_atom_busy_poll;
+extern ERL_NIF_TERM esock_atom_checksum;
+extern ERL_NIF_TERM esock_atom_close;
+extern ERL_NIF_TERM esock_atom_connect;
+extern ERL_NIF_TERM esock_atom_congestion;
+extern ERL_NIF_TERM esock_atom_context;
+extern ERL_NIF_TERM esock_atom_cork;
+extern ERL_NIF_TERM esock_atom_credentials;
+extern ERL_NIF_TERM esock_atom_ctrl;
+extern ERL_NIF_TERM esock_atom_ctrunc;
+extern ERL_NIF_TERM esock_atom_data;
+extern ERL_NIF_TERM esock_atom_debug;
+extern ERL_NIF_TERM esock_atom_default_send_params;
+extern ERL_NIF_TERM esock_atom_delayed_ack_time;
+extern ERL_NIF_TERM esock_atom_dgram;
+extern ERL_NIF_TERM esock_atom_disable_fragments;
+extern ERL_NIF_TERM esock_atom_domain;
+extern ERL_NIF_TERM esock_atom_dontfrag;
+extern ERL_NIF_TERM esock_atom_dontroute;
+extern ERL_NIF_TERM esock_atom_drop_membership;
+extern ERL_NIF_TERM esock_atom_drop_source_membership;
+extern ERL_NIF_TERM esock_atom_dstopts;
+extern ERL_NIF_TERM esock_atom_eor;
+extern ERL_NIF_TERM esock_atom_error;
+extern ERL_NIF_TERM esock_atom_errqueue;
+extern ERL_NIF_TERM esock_atom_esp_network_level;
+extern ERL_NIF_TERM esock_atom_esp_trans_level;
+extern ERL_NIF_TERM esock_atom_events;
+extern ERL_NIF_TERM esock_atom_explicit_eor;
+extern ERL_NIF_TERM esock_atom_faith;
+extern ERL_NIF_TERM esock_atom_false;
+extern ERL_NIF_TERM esock_atom_family;
+extern ERL_NIF_TERM esock_atom_flags;
+extern ERL_NIF_TERM esock_atom_flowinfo;
+extern ERL_NIF_TERM esock_atom_fragment_interleave;
+extern ERL_NIF_TERM esock_atom_freebind;
+extern ERL_NIF_TERM esock_atom_get_peer_addr_info;
+extern ERL_NIF_TERM esock_atom_hdrincl;
+extern ERL_NIF_TERM esock_atom_hmac_ident;
+extern ERL_NIF_TERM esock_atom_hoplimit;
+extern ERL_NIF_TERM esock_atom_hopopts;
+extern ERL_NIF_TERM esock_atom_ifindex;
+extern ERL_NIF_TERM esock_atom_inet;
+extern ERL_NIF_TERM esock_atom_inet6;
+extern ERL_NIF_TERM esock_atom_info;
+extern ERL_NIF_TERM esock_atom_initmsg;
+extern ERL_NIF_TERM esock_atom_iov;
+extern ERL_NIF_TERM esock_atom_ip;
+extern ERL_NIF_TERM esock_atom_ipcomp_level;
+extern ERL_NIF_TERM esock_atom_ipv6;
+extern ERL_NIF_TERM esock_atom_i_want_mapped_v4_addr;
+extern ERL_NIF_TERM esock_atom_join_group;
+extern ERL_NIF_TERM esock_atom_keepalive;
+extern ERL_NIF_TERM esock_atom_keepcnt;
+extern ERL_NIF_TERM esock_atom_keepidle;
+extern ERL_NIF_TERM esock_atom_keepintvl;
+extern ERL_NIF_TERM esock_atom_leave_group;
+extern ERL_NIF_TERM esock_atom_level;
+extern ERL_NIF_TERM esock_atom_linger;
+extern ERL_NIF_TERM esock_atom_local;
+extern ERL_NIF_TERM esock_atom_local_auth_chunks;
+extern ERL_NIF_TERM esock_atom_loopback;
+extern ERL_NIF_TERM esock_atom_lowdelay;
+extern ERL_NIF_TERM esock_atom_mark;
+extern ERL_NIF_TERM esock_atom_maxburst;
+extern ERL_NIF_TERM esock_atom_maxseg;
+extern ERL_NIF_TERM esock_atom_md5sig;
+extern ERL_NIF_TERM esock_atom_mincost;
+extern ERL_NIF_TERM esock_atom_minttl;
+extern ERL_NIF_TERM esock_atom_msfilter;
+extern ERL_NIF_TERM esock_atom_mtu;
+extern ERL_NIF_TERM esock_atom_mtu_discover;
+extern ERL_NIF_TERM esock_atom_multicast_all;
+extern ERL_NIF_TERM esock_atom_multicast_hops;
+extern ERL_NIF_TERM esock_atom_multicast_if;
+extern ERL_NIF_TERM esock_atom_multicast_loop;
+extern ERL_NIF_TERM esock_atom_multicast_ttl;
+extern ERL_NIF_TERM esock_atom_nodelay;
+extern ERL_NIF_TERM esock_atom_nodefrag;
+extern ERL_NIF_TERM esock_atom_noopt;
+extern ERL_NIF_TERM esock_atom_nopush;
+extern ERL_NIF_TERM esock_atom_not_found;
+extern ERL_NIF_TERM esock_atom_not_owner;
+extern ERL_NIF_TERM esock_atom_ok;
+extern ERL_NIF_TERM esock_atom_oob;
+extern ERL_NIF_TERM esock_atom_oobinline;
+extern ERL_NIF_TERM esock_atom_options;
+extern ERL_NIF_TERM esock_atom_origdstaddr;
+extern ERL_NIF_TERM esock_atom_partial_delivery_point;
+extern ERL_NIF_TERM esock_atom_passcred;
+extern ERL_NIF_TERM esock_atom_path;
+extern ERL_NIF_TERM esock_atom_peekcred;
+extern ERL_NIF_TERM esock_atom_peek_off;
+extern ERL_NIF_TERM esock_atom_peer_addr_params;
+extern ERL_NIF_TERM esock_atom_peer_auth_chunks;
+extern ERL_NIF_TERM esock_atom_pktinfo;
+extern ERL_NIF_TERM esock_atom_pktoptions;
+extern ERL_NIF_TERM esock_atom_port;
+extern ERL_NIF_TERM esock_atom_portrange;
+extern ERL_NIF_TERM esock_atom_primary_addr;
+extern ERL_NIF_TERM esock_atom_priority;
+extern ERL_NIF_TERM esock_atom_protocol;
+extern ERL_NIF_TERM esock_atom_raw;
+extern ERL_NIF_TERM esock_atom_rcvbuf;
+extern ERL_NIF_TERM esock_atom_rcvbufforce;
+extern ERL_NIF_TERM esock_atom_rcvlowat;
+extern ERL_NIF_TERM esock_atom_rcvtimeo;
+extern ERL_NIF_TERM esock_atom_rdm;
+extern ERL_NIF_TERM esock_atom_recv;
+extern ERL_NIF_TERM esock_atom_recvdstaddr;
+extern ERL_NIF_TERM esock_atom_recverr;
+extern ERL_NIF_TERM esock_atom_recvfrom;
+extern ERL_NIF_TERM esock_atom_recvif;
+extern ERL_NIF_TERM esock_atom_recvmsg;
+extern ERL_NIF_TERM esock_atom_recvopts;
+extern ERL_NIF_TERM esock_atom_recvorigdstaddr;
+extern ERL_NIF_TERM esock_atom_recvpktinfo;
+extern ERL_NIF_TERM esock_atom_recvtclass;
+extern ERL_NIF_TERM esock_atom_recvtos;
+extern ERL_NIF_TERM esock_atom_recvttl;
+extern ERL_NIF_TERM esock_atom_reliability;
+extern ERL_NIF_TERM esock_atom_reset_streams;
+extern ERL_NIF_TERM esock_atom_retopts;
+extern ERL_NIF_TERM esock_atom_reuseaddr;
+extern ERL_NIF_TERM esock_atom_reuseport;
+extern ERL_NIF_TERM esock_atom_rights;
+extern ERL_NIF_TERM esock_atom_router_alert;
+extern ERL_NIF_TERM esock_atom_rthdr;
+extern ERL_NIF_TERM esock_atom_rtoinfo;
+extern ERL_NIF_TERM esock_atom_rxq_ovfl;
+extern ERL_NIF_TERM esock_atom_scope_id;
+extern ERL_NIF_TERM esock_atom_sctp;
+extern ERL_NIF_TERM esock_atom_sec;
+extern ERL_NIF_TERM esock_atom_select_failed;
+extern ERL_NIF_TERM esock_atom_select_sent;
+extern ERL_NIF_TERM esock_atom_send;
+extern ERL_NIF_TERM esock_atom_sendmsg;
+extern ERL_NIF_TERM esock_atom_sendsrcaddr;
+extern ERL_NIF_TERM esock_atom_sendto;
+extern ERL_NIF_TERM esock_atom_seqpacket;
+extern ERL_NIF_TERM esock_atom_setfib;
+extern ERL_NIF_TERM esock_atom_set_peer_primary_addr;
+extern ERL_NIF_TERM esock_atom_sndbuf;
+extern ERL_NIF_TERM esock_atom_sndbufforce;
+extern ERL_NIF_TERM esock_atom_sndlowat;
+extern ERL_NIF_TERM esock_atom_sndtimeo;
+extern ERL_NIF_TERM esock_atom_socket;
+extern ERL_NIF_TERM esock_atom_socket_tag;
+extern ERL_NIF_TERM esock_atom_spec_dst;
+extern ERL_NIF_TERM esock_atom_status;
+extern ERL_NIF_TERM esock_atom_stream;
+extern ERL_NIF_TERM esock_atom_syncnt;
+extern ERL_NIF_TERM esock_atom_tclass;
+extern ERL_NIF_TERM esock_atom_tcp;
+extern ERL_NIF_TERM esock_atom_throughput;
+extern ERL_NIF_TERM esock_atom_timestamp;
+extern ERL_NIF_TERM esock_atom_tos;
+extern ERL_NIF_TERM esock_atom_transparent;
+extern ERL_NIF_TERM esock_atom_true;
+extern ERL_NIF_TERM esock_atom_trunc;
+extern ERL_NIF_TERM esock_atom_ttl;
+extern ERL_NIF_TERM esock_atom_type;
+extern ERL_NIF_TERM esock_atom_udp;
+extern ERL_NIF_TERM esock_atom_unblock_source;
+extern ERL_NIF_TERM esock_atom_undefined;
+extern ERL_NIF_TERM esock_atom_unicast_hops;
+extern ERL_NIF_TERM esock_atom_unknown;
+extern ERL_NIF_TERM esock_atom_usec;
+extern ERL_NIF_TERM esock_atom_user_timeout;
+extern ERL_NIF_TERM esock_atom_use_ext_recvinfo;
+extern ERL_NIF_TERM esock_atom_use_min_mtu;
+extern ERL_NIF_TERM esock_atom_v6only;
+
+
+/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ * Error value (=reason) atoms
+ */
+extern ERL_NIF_TERM esock_atom_eafnosupport;
+extern ERL_NIF_TERM esock_atom_eagain;
+extern ERL_NIF_TERM esock_atom_einval;
+
+
+
+/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ * Various wrapper macros for enif functions
+ */
+#define MALLOC(SZ) enif_alloc((SZ))
+#define REALLOC(P, SZ) enif_realloc((P), (SZ))
+#define FREE(P) enif_free((P))
+
+#define MKA(E,S) enif_make_atom((E), (S))
+#define MKBIN(E,B) enif_make_binary((E), (B))
+#define MKI(E,I) enif_make_int((E), (I))
+#define MKL(E,L) enif_make_long((E), (L))
+#define MKLA(E,A,L) enif_make_list_from_array((E), (A), (L))
+#define MKEL(E) enif_make_list((E), 0)
+#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))
+#define MKS(E,S) enif_make_string((E), (S), ERL_NIF_LATIN1)
+#define MKSL(E,S,L) enif_make_string_len((E), (S), (L), ERL_NIF_LATIN1)
+#define MKSBIN(E,B,ST,SZ) enif_make_sub_binary((E), (B), (ST), (SZ))
+#define MKT2(E,E1,E2) enif_make_tuple2((E), (E1), (E2))
+#define MKT3(E,E1,E2,E3) enif_make_tuple3((E), (E1), (E2), (E3))
+#define MKT4(E,E1,E2,E3,E4) enif_make_tuple4((E), (E1), (E2), (E3), (E4))
+#define MKT5(E,E1,E2,E3,E4,E5) \
+ enif_make_tuple5((E), (E1), (E2), (E3), (E4), (E5))
+#define MKT8(E,E1,E2,E3,E4,E5,E6,E7,E8) \
+ enif_make_tuple8((E), (E1), (E2), (E3), (E4), (E5), (E6), (E7), (E8))
+#define MKTA(E, A, AL) enif_make_tuple_from_array((E), (A), (AL))
+#define MKUI(E,UI) enif_make_uint((E), (UI))
+#define MKUL(E,UL) enif_make_ulong((E), (UL))
+
+#define MCREATE(N) enif_mutex_create((N))
+#define MDESTROY(M) enif_mutex_destroy((M))
+#define MLOCK(M) enif_mutex_lock((M))
+#define MUNLOCK(M) enif_mutex_unlock((M))
+
+// #define MONP(S,E,D,P,M) enif_monitor_process((E), (D), (P), (M))
+// #define DEMONP(S,E,D,M) enif_demonitor_process((E), (D), (M))
+#define MONP(S,E,D,P,M) esock_monitor((S), (E), (D), (P), (M))
+#define DEMONP(S,E,D,M) esock_demonitor((S), (E), (D), (M))
+#define MON_INIT(M) esock_monitor_init((M))
+// #define MON_COMP(M1, M2) esock_monitor_compare((M1), (M2))
+
+#define COMPARE(A, B) enif_compare((A), (B))
+
+#define IS_ATOM(E, TE) enif_is_atom((E), (TE))
+#define IS_BIN(E, TE) enif_is_binary((E), (TE))
+#define IS_LIST(E, TE) enif_is_list((E), (TE))
+#define IS_MAP(E, TE) enif_is_map((E), (TE))
+#define IS_NUM(E, TE) enif_is_number((E), (TE))
+#define IS_TUPLE(E, TE) enif_is_tuple((E), (TE))
+
+#define GET_ATOM_LEN(E, TE, LP) \
+ enif_get_atom_length((E), (TE), (LP), ERL_NIF_LATIN1)
+#define GET_ATOM(E, TE, BP, MAX) \
+ enif_get_atom((E), (TE), (BP), (MAX), ERL_NIF_LATIN1)
+#define GET_BIN(E, TE, BP) enif_inspect_iolist_as_binary((E), (TE), (BP))
+#define GET_INT(E, TE, IP) enif_get_int((E), (TE), (IP))
+#define GET_LIST_ELEM(E, L, HP, TP) enif_get_list_cell((E), (L), (HP), (TP))
+#define GET_LIST_LEN(E, L, LP) enif_get_list_length((E), (L), (LP))
+#define GET_LONG(E, TE, LP) enif_get_long((E), (TE), (LP))
+#define GET_LPID(E, T, P) enif_get_local_pid((E), (T), (P))
+#define GET_STR(E, L, B, SZ) \
+ enif_get_string((E), (L), (B), (SZ), ERL_NIF_LATIN1)
+#define GET_UINT(E, TE, UIP) enif_get_uint((E), (TE), (UIP))
+#define GET_ULONG(E, TE, ULP) enif_get_long((E), (TE), (ULP))
+#define GET_TUPLE(E, TE, TSZ, TA) enif_get_tuple((E), (TE), (TSZ), (TA))
+#define GET_MAP_VAL(E, M, K, V) enif_get_map_value((E), (M), (K), (V))
+
+#define ALLOC_BIN(SZ, BP) enif_alloc_binary((SZ), (BP))
+#define REALLOC_BIN(SZ, BP) enif_realloc_binary((SZ), (BP))
+#define FREE_BIN(BP) enif_release_binary((BP))
+
+
+#endif // SOCKET_INT_H__
diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c
new file mode 100644
index 0000000000..d56b70e3fd
--- /dev/null
+++ b/erts/emulator/nifs/common/socket_nif.c
@@ -0,0 +1,18649 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2018-2019. All 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 : The NIF (C) part of the socket interface
+ *
+ * All of the nif-functions which are part of the API has two parts.
+ * 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
+ * 'n<something>'.
+ * ----------------------------------------------------------------------
+ *
+ *
+ * This is just a code snippet in case there is need of extra debugging
+ *
+ * esock_dbg_printf("DEMONP", "[%d] %s: %T\r\n",
+ * descP->sock, slogan,
+ * my_make_monitor_term(env, &monP->mon));
+ *
+ */
+
+#define STATIC_ERLANG_NIF 1
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+/* If we HAVE_SCTP_H and Solaris, we need to define the following in
+ * order to get SCTP working:
+ */
+#if (defined(HAVE_SCTP_H) && defined(__sun) && defined(__SVR4))
+#define SOLARIS10 1
+/* WARNING: This is not quite correct, it may also be Solaris 11! */
+#define _XPG4_2
+#define __EXTENSIONS__
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <time.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_SYS_UIO_H
+#include <sys/uio.h>
+#endif
+
+#ifdef HAVE_NET_IF_DL_H
+#include <net/if_dl.h>
+#endif
+
+#ifdef HAVE_IFADDRS_H
+#include <ifaddrs.h>
+#endif
+
+#ifdef HAVE_NETPACKET_PACKET_H
+#include <netpacket/packet.h>
+#endif
+
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h>
+#endif
+
+/* SENDFILE STUFF HERE IF WE NEED IT... */
+
+#if defined(__APPLE__) && defined(__MACH__) && !defined(__DARWIN__)
+#define __DARWIN__ 1
+#endif
+
+
+#ifdef __WIN32__
+#define STRNCASECMP strncasecmp
+#define INCL_WINSOCK_API_TYPEDEFS 1
+
+#ifndef WINDOWS_H_INCLUDES_WINSOCK2_H
+#include <winsock2.h>
+#endif
+#include <windows.h>
+#include <Ws2tcpip.h> /* NEED VC 6.0 or higher */
+
+/* Visual studio 2008+: NTDDI_VERSION needs to be set for iphlpapi.h
+ * to define the right structures. It needs to be set to WINXP (or LONGHORN)
+ * for IPV6 to work and it's set lower by default, so we need to change it.
+ */
+#ifdef HAVE_SDKDDKVER_H
+# include <sdkddkver.h>
+# ifdef NTDDI_VERSION
+# undef NTDDI_VERSION
+# endif
+# define NTDDI_VERSION NTDDI_WINXP
+#endif
+#include <iphlpapi.h>
+
+#undef WANT_NONBLOCKING
+#include "sys.h"
+
+
+
+
+/* AND HERE WE MAY HAVE A BUNCH OF DEFINES....SEE INET DRIVER.... */
+
+
+
+
+#else /* ifdef __WIN32__ */
+
+#include <sys/time.h>
+#ifdef NETDB_H_NEEDS_IN_H
+#include <netinet/in.h>
+#endif
+#include <netdb.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#ifdef DEF_INADDR_LOOPBACK_IN_RPC_TYPES_H
+#include <rpc/types.h>
+#endif
+
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+#include <arpa/inet.h>
+
+#include <sys/param.h>
+#ifdef HAVE_ARPA_NAMESER_H
+#include <arpa/nameser.h>
+#endif
+
+#ifdef HAVE_SYS_SOCKIO_H
+#include <sys/sockio.h>
+#endif
+
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+
+#include <net/if.h>
+
+#ifdef HAVE_SCHED_H
+#include <sched.h>
+#endif
+
+#ifdef HAVE_SETNS_H
+#include <setns.h>
+#endif
+
+#define HAVE_UDP
+
+/* SCTP support -- currently for UNIX platforms only: */
+#undef HAVE_SCTP
+#if defined(HAVE_SCTP_H)
+
+#include <netinet/sctp.h>
+
+/* SCTP Socket API Draft from version 11 on specifies that netinet/sctp.h must
+ explicitly define HAVE_SCTP in case when SCTP is supported, but Solaris 10
+ still apparently uses Draft 10, and does not define that symbol, so we have
+ to define it explicitly:
+*/
+#ifndef HAVE_SCTP
+# define HAVE_SCTP
+#endif
+
+/* These changed in draft 11, so SOLARIS10 uses the old MSG_* */
+#if ! HAVE_DECL_SCTP_UNORDERED
+# define SCTP_UNORDERED MSG_UNORDERED
+#endif
+#if ! HAVE_DECL_SCTP_ADDR_OVER
+# define SCTP_ADDR_OVER MSG_ADDR_OVER
+#endif
+#if ! HAVE_DECL_SCTP_ABORT
+# define SCTP_ABORT MSG_ABORT
+#endif
+#if ! HAVE_DECL_SCTP_EOF
+# define SCTP_EOF MSG_EOF
+#endif
+
+/* More Solaris 10 fixes: */
+#if ! HAVE_DECL_SCTP_CLOSED && HAVE_DECL_SCTPS_IDLE
+# define SCTP_CLOSED SCTPS_IDLE
+# undef HAVE_DECL_SCTP_CLOSED
+# define HAVE_DECL_SCTP_CLOSED 1
+#endif
+#if ! HAVE_DECL_SCTP_BOUND && HAVE_DECL_SCTPS_BOUND
+# define SCTP_BOUND SCTPS_BOUND
+# undef HAVE_DECL_SCTP_BOUND
+# define HAVE_DECL_SCTP_BOUND 1
+#endif
+#if ! HAVE_DECL_SCTP_LISTEN && HAVE_DECL_SCTPS_LISTEN
+# define SCTP_LISTEN SCTPS_LISTEN
+# undef HAVE_DECL_SCTP_LISTEN
+# define HAVE_DECL_SCTP_LISTEN 1
+#endif
+#if ! HAVE_DECL_SCTP_COOKIE_WAIT && HAVE_DECL_SCTPS_COOKIE_WAIT
+# define SCTP_COOKIE_WAIT SCTPS_COOKIE_WAIT
+# undef HAVE_DECL_SCTP_COOKIE_WAIT
+# define HAVE_DECL_SCTP_COOKIE_WAIT 1
+#endif
+#if ! HAVE_DECL_SCTP_COOKIE_ECHOED && HAVE_DECL_SCTPS_COOKIE_ECHOED
+# define SCTP_COOKIE_ECHOED SCTPS_COOKIE_ECHOED
+# undef HAVE_DECL_SCTP_COOKIE_ECHOED
+# define HAVE_DECL_SCTP_COOKIE_ECHOED 1
+#endif
+#if ! HAVE_DECL_SCTP_ESTABLISHED && HAVE_DECL_SCTPS_ESTABLISHED
+# define SCTP_ESTABLISHED SCTPS_ESTABLISHED
+# undef HAVE_DECL_SCTP_ESTABLISHED
+# define HAVE_DECL_SCTP_ESTABLISHED 1
+#endif
+#if ! HAVE_DECL_SCTP_SHUTDOWN_PENDING && HAVE_DECL_SCTPS_SHUTDOWN_PENDING
+# define SCTP_SHUTDOWN_PENDING SCTPS_SHUTDOWN_PENDING
+# undef HAVE_DECL_SCTP_SHUTDOWN_PENDING
+# define HAVE_DECL_SCTP_SHUTDOWN_PENDING 1
+#endif
+#if ! HAVE_DECL_SCTP_SHUTDOWN_SENT && HAVE_DECL_SCTPS_SHUTDOWN_SENT
+# define SCTP_SHUTDOWN_SENT SCTPS_SHUTDOWN_SENT
+# undef HAVE_DECL_SCTP_SHUTDOWN_SENT
+# define HAVE_DECL_SCTP_SHUTDOWN_SENT 1
+#endif
+#if ! HAVE_DECL_SCTP_SHUTDOWN_RECEIVED && HAVE_DECL_SCTPS_SHUTDOWN_RECEIVED
+# define SCTP_SHUTDOWN_RECEIVED SCTPS_SHUTDOWN_RECEIVED
+# undef HAVE_DECL_SCTP_SHUTDOWN_RECEIVED
+# define HAVE_DECL_SCTP_SHUTDOWN_RECEIVED 1
+#endif
+#if ! HAVE_DECL_SCTP_SHUTDOWN_ACK_SENT && HAVE_DECL_SCTPS_SHUTDOWN_ACK_SENT
+# define SCTP_SHUTDOWN_ACK_SENT SCTPS_SHUTDOWN_ACK_SENT
+# undef HAVE_DECL_SCTP_SHUTDOWN_ACK_SENT
+# define HAVE_DECL_SCTP_SHUTDOWN_ACK_SENT 1
+#endif
+/* New spelling in lksctp 2.6.22 or maybe even earlier:
+ * adaption -> adaptation
+ */
+#if !defined(SCTP_ADAPTATION_LAYER) && defined (SCTP_ADAPTION_LAYER)
+# define SCTP_ADAPTATION_LAYER SCTP_ADAPTION_LAYER
+# define SCTP_ADAPTATION_INDICATION SCTP_ADAPTION_INDICATION
+# define sctp_adaptation_event sctp_adaption_event
+# define sctp_setadaptation sctp_setadaption
+# define sn_adaptation_event sn_adaption_event
+# define sai_adaptation_ind sai_adaption_ind
+# define ssb_adaptation_ind ssb_adaption_ind
+# define sctp_adaptation_layer_event sctp_adaption_layer_event
+#endif
+
+/*
+ * We *may* need this stuff later when we *fully* implement support for SCTP
+ *
+
+#if defined(__GNUC__) && defined(HAVE_SCTP_BINDX)
+static typeof(sctp_bindx) *esock_sctp_bindx = NULL;
+#else
+static int (*esock_sctp_bindx)
+ (int sd, struct sockaddr *addrs, int addrcnt, int flags) = NULL;
+#endif
+
+#if defined(__GNUC__) && defined(HAVE_SCTP_PEELOFF)
+static typeof(sctp_peeloff) *esock_sctp_peeloff = NULL;
+#else
+static int (*esock_sctp_peeloff)
+ (int sd, sctp_assoc_t assoc_id) = NULL;
+#endif
+
+#if defined(__GNUC__) && defined(HAVE_SCTP_GETLADDRS)
+static typeof(sctp_getladdrs) *esock_sctp_getladdrs = NULL;
+#else
+static int (*esock_sctp_getladdrs)
+ (int sd, sctp_assoc_t assoc_id, struct sockaddr **ss) = NULL;
+#endif
+
+#if defined(__GNUC__) && defined(HAVE_SCTP_FREELADDRS)
+static typeof(sctp_freeladdrs) *esock_sctp_freeladdrs = NULL;
+#else
+static void (*esock_sctp_freeladdrs)(struct sockaddr *addrs) = NULL;
+#endif
+
+#if defined(__GNUC__) && defined(HAVE_SCTP_GETPADDRS)
+static typeof(sctp_getpaddrs) *esock_sctp_getpaddrs = NULL;
+#else
+static int (*esock_sctp_getpaddrs)
+ (int sd, sctp_assoc_t assoc_id, struct sockaddr **ss) = NULL;
+#endif
+
+#if defined(__GNUC__) && defined(HAVE_SCTP_FREEPADDRS)
+static typeof(sctp_freepaddrs) *esock_sctp_freepaddrs = NULL;
+#else
+static void (*esock_sctp_freepaddrs)(struct sockaddr *addrs) = NULL;
+#endif
+
+*/
+
+#endif /* #if defined(HAVE_SCTP_H) */
+
+
+#ifndef WANT_NONBLOCKING
+#define WANT_NONBLOCKING
+#endif
+#include "sys.h"
+
+/* Socket stuff */
+#define INVALID_SOCKET -1
+// #define INVALID_EVENT -1
+#define SOCKET_ERROR -1
+
+#endif /* ifdef __WIN32__ */
+
+#include <erl_nif.h>
+
+#include "socket_dbg.h"
+#include "socket_tarray.h"
+#include "socket_int.h"
+#include "socket_util.h"
+
+#if defined(SOL_IPV6) || defined(IPPROTO_IPV6)
+#define HAVE_IPV6
+#endif
+
+/* All platforms fail on malloc errors. */
+#define FATAL_MALLOC
+
+
+/* Debug stuff... */
+#define SOCKET_GLOBAL_DEBUG_DEFAULT FALSE
+#define SOCKET_DEBUG_DEFAULT FALSE
+
+/* Counters and stuff (Don't know where to sent this stuff anyway) */
+#define SOCKET_NIF_IOW_DEFAULT FALSE
+
+
+
+/* Socket stuff */
+#define INVALID_EVENT -1
+
+#define SOCKET int
+#define HANDLE long int
+
+
+/* ==============================================================================
+ * The IS_SOCKET_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 IS_SOCKET_ERROR(val) ((val) == SOCKET_ERROR)
+#else
+#define IS_SOCKET_ERROR(val) ((val) < 0)
+#endif
+
+
+/* *** Misc macros and defines *** */
+
+/* This macro exist on some (linux) platforms */
+#if !defined(IPTOS_TOS_MASK)
+#define IPTOS_TOS_MASK 0x1E
+#endif
+#if !defined(IPTOS_TOS)
+#define IPTOS_TOS(tos) ((tos)&IPTOS_TOS_MASK)
+#endif
+
+
+#if defined(TCP_CA_NAME_MAX)
+#define SOCKET_OPT_TCP_CONGESTION_NAME_MAX TCP_CA_NAME_MAX
+#else
+/* This is really excessive, but just in case... */
+#define SOCKET_OPT_TCP_CONGESTION_NAME_MAX 256
+#endif
+
+
+/* *** Socket state defs *** */
+
+#define SOCKET_FLAG_OPEN 0x0001
+#define SOCKET_FLAG_ACTIVE 0x0004
+#define SOCKET_FLAG_LISTEN 0x0008
+#define SOCKET_FLAG_CON 0x0010
+#define SOCKET_FLAG_ACC 0x0020
+#define SOCKET_FLAG_BUSY 0x0040
+#define SOCKET_FLAG_CLOSE 0x0080
+
+#define SOCKET_STATE_CLOSED (0)
+#define SOCKET_STATE_OPEN (SOCKET_FLAG_OPEN)
+#define SOCKET_STATE_CONNECTED (SOCKET_STATE_OPEN | SOCKET_FLAG_ACTIVE)
+#define SOCKET_STATE_LISTENING (SOCKET_STATE_OPEN | SOCKET_FLAG_LISTEN)
+#define SOCKET_STATE_CONNECTING (SOCKET_STATE_OPEN | SOCKET_FLAG_CON)
+#define SOCKET_STATE_ACCEPTING (SOCKET_STATE_LISTENING | SOCKET_FLAG_ACC)
+#define SOCKET_STATE_CLOSING (SOCKET_FLAG_CLOSE)
+
+#define IS_CLOSED(d) \
+ ((d)->state == SOCKET_STATE_CLOSED)
+
+/*
+#define IS_STATE(d, f) \
+ (((d)->state & (f)) == (f))
+*/
+
+#define IS_CLOSING(d) \
+ (((d)->state & SOCKET_STATE_CLOSING) == SOCKET_STATE_CLOSING)
+
+#define IS_OPEN(d) \
+ (((d)->state & SOCKET_FLAG_OPEN) == SOCKET_FLAG_OPEN)
+
+#define IS_CONNECTED(d) \
+ (((d)->state & SOCKET_STATE_CONNECTED) == SOCKET_STATE_CONNECTED)
+
+#define IS_CONNECTING(d) \
+ (((d)->state & SOCKET_FLAG_CON) == SOCKET_FLAG_CON)
+
+/*
+#define IS_BUSY(d) \
+ (((d)->state & SOCKET_FLAG_BUSY) == SOCKET_FLAG_BUSY)
+*/
+
+#define SOCKET_SEND_FLAG_CONFIRM 0
+#define SOCKET_SEND_FLAG_DONTROUTE 1
+#define SOCKET_SEND_FLAG_EOR 2
+#define SOCKET_SEND_FLAG_MORE 3
+#define SOCKET_SEND_FLAG_NOSIGNAL 4
+#define SOCKET_SEND_FLAG_OOB 5
+#define SOCKET_SEND_FLAG_LOW SOCKET_SEND_FLAG_CONFIRM
+#define SOCKET_SEND_FLAG_HIGH SOCKET_SEND_FLAG_OOB
+
+#define SOCKET_RECV_FLAG_CMSG_CLOEXEC 0
+#define SOCKET_RECV_FLAG_ERRQUEUE 1
+#define SOCKET_RECV_FLAG_OOB 2
+#define SOCKET_RECV_FLAG_PEEK 3
+#define SOCKET_RECV_FLAG_TRUNC 4
+#define SOCKET_RECV_FLAG_LOW SOCKET_RECV_FLAG_CMSG_CLOEXEC
+#define SOCKET_RECV_FLAG_HIGH SOCKET_RECV_FLAG_TRUNC
+
+#define SOCKET_RECV_BUFFER_SIZE_DEFAULT 8192
+#define SOCKET_RECV_CTRL_BUFFER_SIZE_DEFAULT 1024
+#define SOCKET_SEND_CTRL_BUFFER_SIZE_DEFAULT 1024
+
+#define VT2S(__VT__) (((__VT__) == SOCKET_OPT_VALUE_TYPE_UNSPEC) ? "unspec" : \
+ (((__VT__) == SOCKET_OPT_VALUE_TYPE_INT) ? "int" : \
+ ((__VT__) == SOCKET_OPT_VALUE_TYPE_BOOL) ? "bool" : \
+ "undef"))
+
+#define SOCKET_OPT_VALUE_TYPE_UNSPEC 0
+#define SOCKET_OPT_VALUE_TYPE_INT 1
+#define SOCKET_OPT_VALUE_TYPE_BOOL 2
+
+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;
+
+/*
+#define IS_OPEN(d) ((d)->state.flags.open)
+#define IS_CONNECTED(d) ((d)->state.flags.connect == SOCKET_STATE_CONNECTED)
+#define IS_CONNECTING(d) ((d)->state.flags.connect == SOCKET_STATE_CONNECTING)
+*/
+
+
+/*----------------------------------------------------------------------------
+ * Interface constants.
+ *
+ * This section must be "identical" to the corresponding socket.hrl
+ */
+
+/* domain */
+#define SOCKET_DOMAIN_LOCAL 1
+#define SOCKET_DOMAIN_INET 2
+#define SOCKET_DOMAIN_INET6 3
+
+/* type */
+#define SOCKET_TYPE_STREAM 1
+#define SOCKET_TYPE_DGRAM 2
+#define SOCKET_TYPE_RAW 3
+// #define SOCKET_TYPE_RDM 4
+#define SOCKET_TYPE_SEQPACKET 5
+
+/* protocol */
+#define SOCKET_PROTOCOL_IP 1
+#define SOCKET_PROTOCOL_TCP 2
+#define SOCKET_PROTOCOL_UDP 3
+#define SOCKET_PROTOCOL_SCTP 4
+#define SOCKET_PROTOCOL_ICMP 5
+#define SOCKET_PROTOCOL_IGMP 6
+
+/* shutdown how */
+#define SOCKET_SHUTDOWN_HOW_RD 0
+#define SOCKET_SHUTDOWN_HOW_WR 1
+#define SOCKET_SHUTDOWN_HOW_RDWR 2
+
+
+#define SOCKET_OPT_LEVEL_OTP 0
+#define SOCKET_OPT_LEVEL_SOCKET 1
+#define SOCKET_OPT_LEVEL_IP 2
+#define SOCKET_OPT_LEVEL_IPV6 3
+#define SOCKET_OPT_LEVEL_TCP 4
+#define SOCKET_OPT_LEVEL_UDP 5
+#define SOCKET_OPT_LEVEL_SCTP 6
+
+#define SOCKET_OPT_OTP_DEBUG 1
+#define SOCKET_OPT_OTP_IOW 2
+#define SOCKET_OPT_OTP_CTRL_PROC 3
+#define SOCKET_OPT_OTP_RCVBUF 4
+#define SOCKET_OPT_OTP_RCVCTRLBUF 6
+#define SOCKET_OPT_OTP_SNDCTRLBUF 7
+#define SOCKET_OPT_OTP_FD 8
+#define SOCKET_OPT_OTP_DOMAIN 0xFF01 // INTERNAL AND ONLY GET
+#define SOCKET_OPT_OTP_TYPE 0xFF02 // INTERNAL AND ONLY GET
+#define SOCKET_OPT_OTP_PROTOCOL 0xFF03 // INTERNAL AND ONLY GET
+
+#define SOCKET_OPT_SOCK_ACCEPTCONN 1
+#define SOCKET_OPT_SOCK_BINDTODEVICE 3
+#define SOCKET_OPT_SOCK_BROADCAST 4
+#define SOCKET_OPT_SOCK_DEBUG 6
+#define SOCKET_OPT_SOCK_DOMAIN 7
+#define SOCKET_OPT_SOCK_DONTROUTE 8
+#define SOCKET_OPT_SOCK_KEEPALIVE 10
+#define SOCKET_OPT_SOCK_LINGER 11
+#define SOCKET_OPT_SOCK_OOBINLINE 13
+#define SOCKET_OPT_SOCK_PEEK_OFF 15
+#define SOCKET_OPT_SOCK_PRIORITY 17
+#define SOCKET_OPT_SOCK_PROTOCOL 18
+#define SOCKET_OPT_SOCK_RCVBUF 19
+#define SOCKET_OPT_SOCK_RCVLOWAT 21
+#define SOCKET_OPT_SOCK_RCVTIMEO 22
+#define SOCKET_OPT_SOCK_REUSEADDR 23
+#define SOCKET_OPT_SOCK_REUSEPORT 24
+#define SOCKET_OPT_SOCK_SNDBUF 27
+#define SOCKET_OPT_SOCK_SNDLOWAT 29
+#define SOCKET_OPT_SOCK_SNDTIMEO 30
+#define SOCKET_OPT_SOCK_TIMESTAMP 31
+#define SOCKET_OPT_SOCK_TYPE 32
+
+#define SOCKET_OPT_IP_ADD_MEMBERSHIP 1
+#define SOCKET_OPT_IP_ADD_SOURCE_MEMBERSHIP 2
+#define SOCKET_OPT_IP_BLOCK_SOURCE 3
+#define SOCKET_OPT_IP_DROP_MEMBERSHIP 5
+#define SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP 6
+#define SOCKET_OPT_IP_FREEBIND 7
+#define SOCKET_OPT_IP_HDRINCL 8
+#define SOCKET_OPT_IP_MINTTL 9
+#define SOCKET_OPT_IP_MSFILTER 10
+#define SOCKET_OPT_IP_MTU 11
+#define SOCKET_OPT_IP_MTU_DISCOVER 12
+#define SOCKET_OPT_IP_MULTICAST_ALL 13
+#define SOCKET_OPT_IP_MULTICAST_IF 14
+#define SOCKET_OPT_IP_MULTICAST_LOOP 15
+#define SOCKET_OPT_IP_MULTICAST_TTL 16
+#define SOCKET_OPT_IP_NODEFRAG 17
+#define SOCKET_OPT_IP_PKTINFO 19
+#define SOCKET_OPT_IP_RECVDSTADDR 20
+#define SOCKET_OPT_IP_RECVERR 21
+#define SOCKET_OPT_IP_RECVIF 22
+#define SOCKET_OPT_IP_RECVOPTS 23
+#define SOCKET_OPT_IP_RECVORIGDSTADDR 24
+#define SOCKET_OPT_IP_RECVTOS 25
+#define SOCKET_OPT_IP_RECVTTL 26
+#define SOCKET_OPT_IP_RETOPTS 27
+#define SOCKET_OPT_IP_ROUTER_ALERT 28
+#define SOCKET_OPT_IP_SENDSRCADDR 29 // Same as IP_RECVDSTADDR?
+#define SOCKET_OPT_IP_TOS 30
+#define SOCKET_OPT_IP_TRANSPARENT 31
+#define SOCKET_OPT_IP_TTL 32
+#define SOCKET_OPT_IP_UNBLOCK_SOURCE 33
+
+#define SOCKET_OPT_IPV6_ADDRFORM 1
+#define SOCKET_OPT_IPV6_ADD_MEMBERSHIP 2
+#define SOCKET_OPT_IPV6_AUTHHDR 3
+#define SOCKET_OPT_IPV6_DROP_MEMBERSHIP 6
+#define SOCKET_OPT_IPV6_DSTOPTS 7
+#define SOCKET_OPT_IPV6_FLOWINFO 11
+#define SOCKET_OPT_IPV6_HOPLIMIT 12
+#define SOCKET_OPT_IPV6_HOPOPTS 13
+#define SOCKET_OPT_IPV6_MTU 17
+#define SOCKET_OPT_IPV6_MTU_DISCOVER 18
+#define SOCKET_OPT_IPV6_MULTICAST_HOPS 19
+#define SOCKET_OPT_IPV6_MULTICAST_IF 20
+#define SOCKET_OPT_IPV6_MULTICAST_LOOP 21
+#define SOCKET_OPT_IPV6_RECVERR 24
+#define SOCKET_OPT_IPV6_RECVPKTINFO 25 // PKTINFO on FreeBSD
+#define SOCKET_OPT_IPV6_ROUTER_ALERT 27
+#define SOCKET_OPT_IPV6_RTHDR 28
+#define SOCKET_OPT_IPV6_UNICAST_HOPS 30
+#define SOCKET_OPT_IPV6_V6ONLY 32
+
+#define SOCKET_OPT_TCP_CONGESTION 1
+#define SOCKET_OPT_TCP_CORK 2
+#define SOCKET_OPT_TCP_MAXSEG 7
+#define SOCKET_OPT_TCP_NODELAY 9
+
+#define SOCKET_OPT_UDP_CORK 1
+
+#define SOCKET_OPT_SCTP_ASSOCINFO 2
+#define SOCKET_OPT_SCTP_AUTOCLOSE 8
+#define SOCKET_OPT_SCTP_DISABLE_FRAGMENTS 12
+#define SOCKET_OPT_SCTP_EVENTS 14
+#define SOCKET_OPT_SCTP_INITMSG 18
+#define SOCKET_OPT_SCTP_MAXSEG 21
+#define SOCKET_OPT_SCTP_NODELAY 23
+#define SOCKET_OPT_SCTP_RTOINFO 29
+
+/* We should *eventually* use this instead of hard-coding the size (to 1) */
+#define ESOCK_RECVMSG_IOVEC_SZ 1
+
+
+#define SOCKET_SUPPORTS_OPTIONS 0x0001
+#define SOCKET_SUPPORTS_SCTP 0x0002
+#define SOCKET_SUPPORTS_IPV6 0x0003
+
+
+
+/* =================================================================== *
+ * *
+ * Various enif macros *
+ * *
+ * =================================================================== */
+
+#define SGDBG( proto ) ESOCK_DBG_PRINTF( data.dbg , proto )
+#define SSDBG( __D__ , proto ) ESOCK_DBG_PRINTF( (__D__)->dbg , proto )
+
+
+
+/* =================================================================== *
+ * *
+ * Basic socket operations *
+ * *
+ * =================================================================== */
+
+#ifdef __WIN32__
+
+/* *** 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_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_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_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_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_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))
+
+
+#define SET_BLOCKING(s) ioctlsocket(s, FIONBIO, &zero_value)
+#define SET_NONBLOCKING(s) ioctlsocket(s, FIONBIO, &one_value)
+static unsigned long zero_value = 0;
+static unsigned long one_value = 1;
+
+
+#else /* !__WIN32__ */
+
+
+#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_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_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_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_setopt(s,l,o,v,ln) setsockopt((s),(l),(o),(v),(ln))
+#define sock_shutdown(s, how) shutdown((s), (how))
+
+#endif /* !__WIN32__ */
+
+#ifdef HAVE_SOCKLEN_T
+# define SOCKLEN_T socklen_t
+#else
+# define SOCKLEN_T size_t
+#endif
+
+#ifdef __WIN32__
+#define SOCKOPTLEN_T int
+#else
+#define SOCKOPTLEN_T SOCKLEN_T
+#endif
+
+/* 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 {
+ int is_active;
+ ErlNifMonitor mon;
+} ESockMonitor;
+
+typedef struct {
+ ErlNifPid pid; // PID of the requesting process
+ ESockMonitor mon; // Monitor to the requesting process
+ ERL_NIF_TERM ref; // The (unique) reference (ID) of the request
+} SocketRequestor;
+
+typedef struct socket_request_queue_element {
+ struct socket_request_queue_element* nextP;
+ SocketRequestor data;
+} SocketRequestQueueElement;
+
+typedef struct {
+ SocketRequestQueueElement* first;
+ SocketRequestQueueElement* last;
+} SocketRequestQueue;
+
+
+typedef struct {
+ /* +++ The actual socket +++ */
+ SOCKET sock;
+ HANDLE event;
+
+ /* +++ Stuff "about" the socket +++ */
+ int domain;
+ int type;
+ int protocol;
+
+ unsigned int state;
+ SocketAddress remote;
+ unsigned int addrLen;
+
+ ErlNifEnv* env;
+
+ /* +++ Controller (owner) process +++ */
+ ErlNifPid ctrlPid;
+ // ErlNifMonitor ctrlMon;
+ ESockMonitor ctrlMon;
+
+ /* +++ Write stuff +++ */
+ ErlNifMutex* writeMtx;
+ SocketRequestor currentWriter;
+ SocketRequestor* currentWriterP; // NULL or points to currentWriter
+ SocketRequestQueue writersQ;
+ BOOLEAN_T isWritable;
+ Uint32 writePkgCnt;
+ Uint32 writeByteCnt;
+ Uint32 writeTries;
+ Uint32 writeWaits;
+ Uint32 writeFails;
+
+ /* +++ Read stuff +++ */
+ ErlNifMutex* readMtx;
+ SocketRequestor currentReader;
+ SocketRequestor* currentReaderP; // NULL or points to currentReader
+ SocketRequestQueue readersQ;
+ BOOLEAN_T isReadable;
+ ErlNifBinary rbuffer; // DO WE NEED THIS
+ Uint32 readCapacity; // DO WE NEED THIS
+ Uint32 readPkgCnt;
+ Uint32 readByteCnt;
+ Uint32 readTries;
+ Uint32 readWaits;
+
+ /* +++ Accept stuff +++ */
+ ErlNifMutex* accMtx;
+ SocketRequestor currentAcceptor;
+ SocketRequestor* currentAcceptorP; // NULL or points to currentAcceptor
+ SocketRequestQueue acceptorsQ;
+
+ /* +++ Config & Misc 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
+ size_t wCtrlSz; // Write control buffer size
+ BOOLEAN_T iow; // Inform On (counter) Wrap
+ BOOLEAN_T dbg;
+
+ /* +++ Close stuff +++ */
+ ErlNifMutex* closeMtx;
+ ErlNifPid closerPid;
+ ESockMonitor closerMon;
+ ErlNifEnv* closeEnv;
+ ERL_NIF_TERM closeRef;
+ BOOLEAN_T closeLocal;
+
+} SocketDescriptor;
+
+
+/* Global stuff.
+ */
+typedef struct {
+ /* These are for debugging, testing and the like */
+ // ERL_NIF_TERM version;
+ // ERL_NIF_TERM buildDate;
+ BOOLEAN_T dbg;
+
+ BOOLEAN_T iow; // Where do we send this? Subscription?
+ ErlNifMutex* cntMtx;
+ Uint32 numSockets;
+ Uint32 numTypeStreams;
+ Uint32 numTypeDGrams;
+ Uint32 numTypeSeqPkgs;
+ Uint32 numDomainInet;
+ Uint32 numDomainInet6;
+ Uint32 numDomainLocal;
+ Uint32 numProtoIP;
+ Uint32 numProtoTCP;
+ Uint32 numProtoUDP;
+ Uint32 numProtoSCTP;
+} SocketData;
+
+
+/* ----------------------------------------------------------------------
+ * F o r w a r d s
+ * ----------------------------------------------------------------------
+ */
+
+
+
+static ERL_NIF_TERM nif_info(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM nif_supports(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+/*
+This is a *global* debug function (enable or disable for all
+operations and all sockets.
+static ERL_NIF_TERM nif_debug(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+*/
+static ERL_NIF_TERM nif_open(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM nif_bind(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM nif_connect(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM nif_listen(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM nif_accept(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM nif_send(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM nif_sendto(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM nif_sendmsg(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM nif_recv(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM nif_recvmsg(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM nif_close(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM nif_shutdown(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM nif_setopt(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM nif_getopt(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM nif_sockname(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM nif_peername(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM nif_finalize_connection(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM nif_finalize_close(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM nif_cancel(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+
+static ERL_NIF_TERM nsupports(ErlNifEnv* env, int key);
+static ERL_NIF_TERM nsupports_options(ErlNifEnv* env);
+static ERL_NIF_TERM nsupports_options_socket(ErlNifEnv* env);
+static ERL_NIF_TERM nsupports_options_ip(ErlNifEnv* env);
+static ERL_NIF_TERM nsupports_options_ipv6(ErlNifEnv* env);
+static ERL_NIF_TERM nsupports_options_tcp(ErlNifEnv* env);
+static ERL_NIF_TERM nsupports_options_udp(ErlNifEnv* env);
+static ERL_NIF_TERM nsupports_options_sctp(ErlNifEnv* env);
+static ERL_NIF_TERM nsupports_sctp(ErlNifEnv* env);
+static ERL_NIF_TERM nsupports_ipv6(ErlNifEnv* env);
+
+static ERL_NIF_TERM nopen(ErlNifEnv* env,
+ int domain,
+ int type,
+ int protocol,
+ char* netns);
+static ERL_NIF_TERM nbind(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ SocketAddress* sockAddrP,
+ unsigned int addrLen);
+static ERL_NIF_TERM nconnect(ErlNifEnv* env,
+ SocketDescriptor* descP);
+static ERL_NIF_TERM nlisten(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int backlog);
+static ERL_NIF_TERM naccept(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM ref);
+static ERL_NIF_TERM naccept_listening(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM ref);
+static ERL_NIF_TERM naccept_listening_error(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM ref,
+ ErlNifPid caller,
+ int save_errno);
+static ERL_NIF_TERM naccept_listening_accept(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ SOCKET accSock,
+ ErlNifPid caller,
+ SocketAddress* remote);
+static ERL_NIF_TERM naccept_accepting(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM ref);
+static ERL_NIF_TERM naccept_accepting_current(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM ref);
+static ERL_NIF_TERM naccept_accepting_current_accept(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ SOCKET accSock,
+ SocketAddress* remote);
+static ERL_NIF_TERM naccept_accepting_current_error(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM ref,
+ int save_errno);
+static ERL_NIF_TERM naccept_accepting_other(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM ref,
+ ErlNifPid caller);
+static ERL_NIF_TERM naccept_busy_retry(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM ref,
+ ErlNifPid* pid,
+ unsigned int nextState);
+static BOOLEAN_T naccept_accepted(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ SOCKET accSock,
+ ErlNifPid pid,
+ SocketAddress* remote,
+ ERL_NIF_TERM* result);
+static ERL_NIF_TERM nsend(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ ErlNifBinary* dataP,
+ int flags);
+static ERL_NIF_TERM nsendto(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ ErlNifBinary* dataP,
+ int flags,
+ SocketAddress* toAddrP,
+ unsigned int toAddrLen);
+static ERL_NIF_TERM nsendmsg(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ ERL_NIF_TERM eMsgHdr,
+ int flags);
+static ERL_NIF_TERM nrecv(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM sendRef,
+ ERL_NIF_TERM recvRef,
+ int len,
+ int flags);
+static ERL_NIF_TERM nrecvfrom(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef,
+ Uint16 bufSz,
+ int flags);
+static ERL_NIF_TERM nrecvmsg(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef,
+ Uint16 bufLen,
+ Uint16 ctrlLen,
+ int flags);
+static ERL_NIF_TERM nclose(ErlNifEnv* env,
+ SocketDescriptor* descP);
+static ERL_NIF_TERM nshutdown(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int how);
+static ERL_NIF_TERM nsetopt(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ BOOLEAN_T isEncoded,
+ BOOLEAN_T isOTP,
+ int level,
+ int eOpt,
+ ERL_NIF_TERM eVal);
+static ERL_NIF_TERM nsetopt_otp(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt,
+ ERL_NIF_TERM eVal);
+static ERL_NIF_TERM nsetopt_otp_debug(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+static ERL_NIF_TERM nsetopt_otp_iow(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+static ERL_NIF_TERM nsetopt_otp_ctrl_proc(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+static ERL_NIF_TERM nsetopt_otp_rcvbuf(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+static ERL_NIF_TERM nsetopt_otp_rcvctrlbuf(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+static ERL_NIF_TERM nsetopt_otp_sndctrlbuf(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+static ERL_NIF_TERM nsetopt_native(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int level,
+ int eOpt,
+ ERL_NIF_TERM eVal);
+static ERL_NIF_TERM nsetopt_level(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int level,
+ int eOpt,
+ ERL_NIF_TERM eVal);
+static ERL_NIF_TERM nsetopt_lvl_socket(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt,
+ ERL_NIF_TERM eVal);
+
+
+/* *** Handling set of socket options for level = socket *** */
+
+#if defined(SO_BINDTODEVICE)
+static ERL_NIF_TERM nsetopt_lvl_sock_bindtodevice(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(SO_BROADCAST)
+static ERL_NIF_TERM nsetopt_lvl_sock_broadcast(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(SO_DEBUG)
+static ERL_NIF_TERM nsetopt_lvl_sock_debug(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(SO_DONTROUTE)
+static ERL_NIF_TERM nsetopt_lvl_sock_dontroute(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(SO_KEEPALIVE)
+static ERL_NIF_TERM nsetopt_lvl_sock_keepalive(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(SO_LINGER)
+static ERL_NIF_TERM nsetopt_lvl_sock_linger(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(SO_OOBINLINE)
+static ERL_NIF_TERM nsetopt_lvl_sock_oobinline(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(SO_PEEK_OFF)
+static ERL_NIF_TERM nsetopt_lvl_sock_peek_off(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(SO_PRIORITY)
+static ERL_NIF_TERM nsetopt_lvl_sock_priority(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(SO_RCVBUF)
+static ERL_NIF_TERM nsetopt_lvl_sock_rcvbuf(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(SO_RCVLOWAT)
+static ERL_NIF_TERM nsetopt_lvl_sock_rcvlowat(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(SO_RCVTIMEO)
+static ERL_NIF_TERM nsetopt_lvl_sock_rcvtimeo(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(SO_REUSEADDR)
+static ERL_NIF_TERM nsetopt_lvl_sock_reuseaddr(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(SO_REUSEPORT)
+static ERL_NIF_TERM nsetopt_lvl_sock_reuseport(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(SO_SNDBUF)
+static ERL_NIF_TERM nsetopt_lvl_sock_sndbuf(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(SO_SNDLOWAT)
+static ERL_NIF_TERM nsetopt_lvl_sock_sndlowat(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(SO_SNDTIMEO)
+static ERL_NIF_TERM nsetopt_lvl_sock_sndtimeo(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(SO_TIMESTAMP)
+static ERL_NIF_TERM nsetopt_lvl_sock_timestamp(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+static ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt,
+ ERL_NIF_TERM eVal);
+
+/* *** Handling set of socket options for level = ip *** */
+#if defined(IP_ADD_MEMBERSHIP)
+static ERL_NIF_TERM nsetopt_lvl_ip_add_membership(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_ADD_SOURCE_MEMBERSHIP)
+static ERL_NIF_TERM nsetopt_lvl_ip_add_source_membership(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_BLOCK_SOURCE)
+static ERL_NIF_TERM nsetopt_lvl_ip_block_source(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_DROP_MEMBERSHIP)
+static ERL_NIF_TERM nsetopt_lvl_ip_drop_membership(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_DROP_SOURCE_MEMBERSHIP)
+static ERL_NIF_TERM nsetopt_lvl_ip_drop_source_membership(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_FREEBIND)
+static ERL_NIF_TERM nsetopt_lvl_ip_freebind(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_HDRINCL)
+static ERL_NIF_TERM nsetopt_lvl_ip_hdrincl(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_MINTTL)
+static ERL_NIF_TERM nsetopt_lvl_ip_minttl(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_MSFILTER) && defined(IP_MSFILTER_SIZE)
+static ERL_NIF_TERM nsetopt_lvl_ip_msfilter(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+static BOOLEAN_T decode_ip_msfilter_mode(ErlNifEnv* env,
+ ERL_NIF_TERM eVal,
+ Uint32* mode);
+static ERL_NIF_TERM nsetopt_lvl_ip_msfilter_set(ErlNifEnv* env,
+ SOCKET sock,
+ struct ip_msfilter* msfP,
+ SOCKLEN_T optLen);
+#endif
+#if defined(IP_MTU_DISCOVER)
+static ERL_NIF_TERM nsetopt_lvl_ip_mtu_discover(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_MULTICAST_ALL)
+static ERL_NIF_TERM nsetopt_lvl_ip_multicast_all(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_MULTICAST_IF)
+static ERL_NIF_TERM nsetopt_lvl_ip_multicast_if(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_MULTICAST_LOOP)
+static ERL_NIF_TERM nsetopt_lvl_ip_multicast_loop(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_MULTICAST_TTL)
+static ERL_NIF_TERM nsetopt_lvl_ip_multicast_ttl(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_NODEFRAG)
+static ERL_NIF_TERM nsetopt_lvl_ip_nodefrag(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_PKTINFO)
+static ERL_NIF_TERM nsetopt_lvl_ip_pktinfo(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_RECVDSTADDR)
+static ERL_NIF_TERM nsetopt_lvl_ip_recvdstaddr(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_RECVERR)
+static ERL_NIF_TERM nsetopt_lvl_ip_recverr(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_RECVIF)
+static ERL_NIF_TERM nsetopt_lvl_ip_recvif(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_RECVOPTS)
+static ERL_NIF_TERM nsetopt_lvl_ip_recvopts(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_RECVORIGDSTADDR)
+static ERL_NIF_TERM nsetopt_lvl_ip_recvorigdstaddr(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_RECVTOS)
+static ERL_NIF_TERM nsetopt_lvl_ip_recvtos(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_RECVTTL)
+static ERL_NIF_TERM nsetopt_lvl_ip_recvttl(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_RETOPTS)
+static ERL_NIF_TERM nsetopt_lvl_ip_retopts(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_ROUTER_ALERT)
+static ERL_NIF_TERM nsetopt_lvl_ip_router_alert(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_SENDSRCADDR)
+static ERL_NIF_TERM nsetopt_lvl_ip_sendsrcaddr(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_TOS)
+static ERL_NIF_TERM nsetopt_lvl_ip_tos(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_TRANSPARENT)
+static ERL_NIF_TERM nsetopt_lvl_ip_transparent(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_TTL)
+static ERL_NIF_TERM nsetopt_lvl_ip_ttl(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IP_UNBLOCK_SOURCE)
+static ERL_NIF_TERM nsetopt_lvl_ip_unblock_source(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+
+#if defined(IP_DROP_MEMBERSHIP) || defined(IP_ADD_MEMBERSHIP)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_update_membership(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal,
+ int opt);
+#endif
+#if defined(IP_ADD_SOURCE_MEMBERSHIP) || defined(IP_DROP_SOURCE_MEMBERSHIP) || defined(IP_BLOCK_SOURCE) || defined(IP_UNBLOCK_SOURCE)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_update_source(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal,
+ int opt);
+#endif
+
+
+/* *** Handling set of socket options for level = ipv6 *** */
+#if defined(HAVE_IPV6)
+static ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt,
+ ERL_NIF_TERM eVal);
+#if defined(IPV6_ADDRFORM)
+static ERL_NIF_TERM nsetopt_lvl_ipv6_addrform(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IPV6_ADD_MEMBERSHIP)
+static ERL_NIF_TERM nsetopt_lvl_ipv6_add_membership(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IPV6_AUTHHDR)
+static ERL_NIF_TERM nsetopt_lvl_ipv6_authhdr(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IPV6_DROP_MEMBERSHIP)
+static ERL_NIF_TERM nsetopt_lvl_ipv6_drop_membership(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IPV6_DSTOPTS)
+static ERL_NIF_TERM nsetopt_lvl_ipv6_dstopts(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IPV6_FLOWINFO)
+static ERL_NIF_TERM nsetopt_lvl_ipv6_flowinfo(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IPV6_HOPLIMIT)
+static ERL_NIF_TERM nsetopt_lvl_ipv6_hoplimit(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IPV6_HOPOPTS)
+static ERL_NIF_TERM nsetopt_lvl_ipv6_hopopts(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IPV6_MTU)
+static ERL_NIF_TERM nsetopt_lvl_ipv6_mtu(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IPV6_MTU_DISCOVER)
+static ERL_NIF_TERM nsetopt_lvl_ipv6_mtu_discover(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IPV6_MULTICAST_HOPS)
+static ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_hops(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IPV6_MULTICAST_IF)
+static ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_if(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IPV6_MULTICAST_LOOP)
+static ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_loop(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IPV6_RECVERR)
+static ERL_NIF_TERM nsetopt_lvl_ipv6_recverr(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO)
+static ERL_NIF_TERM nsetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IPV6_ROUTER_ALERT)
+static ERL_NIF_TERM nsetopt_lvl_ipv6_router_alert(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IPV6_RTHDR)
+static ERL_NIF_TERM nsetopt_lvl_ipv6_rthdr(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IPV6_UNICAST_HOPS)
+static ERL_NIF_TERM nsetopt_lvl_ipv6_unicast_hops(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(IPV6_V6ONLY)
+static ERL_NIF_TERM nsetopt_lvl_ipv6_v6only(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+
+#if defined(IPV6_ADD_MEMBERSHIP) || defined(IPV6_DROP_MEMBERSHIP)
+static ERL_NIF_TERM nsetopt_lvl_ipv6_update_membership(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal,
+ int opt);
+#endif
+
+#endif // defined(HAVE_IPV6)
+static ERL_NIF_TERM nsetopt_lvl_tcp(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt,
+ ERL_NIF_TERM eVal);
+#if defined(TCP_CONGESTION)
+static ERL_NIF_TERM nsetopt_lvl_tcp_congestion(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(TCP_MAXSEG)
+static ERL_NIF_TERM nsetopt_lvl_tcp_maxseg(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(TCP_NODELAY)
+static ERL_NIF_TERM nsetopt_lvl_tcp_nodelay(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+static ERL_NIF_TERM nsetopt_lvl_udp(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt,
+ ERL_NIF_TERM eVal);
+#if defined(UDP_CORK)
+static ERL_NIF_TERM nsetopt_lvl_udp_cork(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(HAVE_SCTP)
+static ERL_NIF_TERM nsetopt_lvl_sctp(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt,
+ ERL_NIF_TERM eVal);
+#if defined(SCTP_ASSOCINFO)
+static ERL_NIF_TERM nsetopt_lvl_sctp_associnfo(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(SCTP_AUTOCLOSE)
+static ERL_NIF_TERM nsetopt_lvl_sctp_autoclose(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(SCTP_DISABLE_FRAGMENTS)
+static ERL_NIF_TERM nsetopt_lvl_sctp_disable_fragments(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(SCTP_EVENTS)
+static ERL_NIF_TERM nsetopt_lvl_sctp_events(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(SCTP_INITMSG)
+static ERL_NIF_TERM nsetopt_lvl_sctp_initmsg(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(SCTP_MAXSEG)
+static ERL_NIF_TERM nsetopt_lvl_sctp_maxseg(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(SCTP_NODELAY)
+static ERL_NIF_TERM nsetopt_lvl_sctp_nodelay(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#if defined(SCTP_RTOINFO)
+static ERL_NIF_TERM nsetopt_lvl_sctp_rtoinfo(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal);
+#endif
+#endif // defined(HAVE_SCTP)
+
+static ERL_NIF_TERM ngetopt(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ BOOLEAN_T isEncoded,
+ BOOLEAN_T isOTP,
+ int level,
+ ERL_NIF_TERM eOpt);
+static ERL_NIF_TERM ngetopt_otp(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt);
+static ERL_NIF_TERM ngetopt_otp_debug(ErlNifEnv* env,
+ SocketDescriptor* descP);
+static ERL_NIF_TERM ngetopt_otp_iow(ErlNifEnv* env,
+ SocketDescriptor* descP);
+static ERL_NIF_TERM ngetopt_otp_ctrl_proc(ErlNifEnv* env,
+ SocketDescriptor* descP);
+static ERL_NIF_TERM ngetopt_otp_rcvbuf(ErlNifEnv* env,
+ SocketDescriptor* descP);
+static ERL_NIF_TERM ngetopt_otp_rcvctrlbuf(ErlNifEnv* env,
+ SocketDescriptor* descP);
+static ERL_NIF_TERM ngetopt_otp_sndctrlbuf(ErlNifEnv* env,
+ SocketDescriptor* descP);
+static ERL_NIF_TERM ngetopt_otp_fd(ErlNifEnv* env,
+ SocketDescriptor* descP);
+static ERL_NIF_TERM ngetopt_otp_domain(ErlNifEnv* env,
+ SocketDescriptor* descP);
+static ERL_NIF_TERM ngetopt_otp_type(ErlNifEnv* env,
+ SocketDescriptor* descP);
+static ERL_NIF_TERM ngetopt_otp_protocol(ErlNifEnv* env,
+ SocketDescriptor* descP);
+static ERL_NIF_TERM ngetopt_native(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int level,
+ ERL_NIF_TERM eOpt);
+static ERL_NIF_TERM ngetopt_native_unspec(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int level,
+ int opt,
+ SOCKOPTLEN_T valueSz);
+static ERL_NIF_TERM ngetopt_level(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int level,
+ int eOpt);
+static ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt);
+#if defined(SO_ACCEPTCONN)
+static ERL_NIF_TERM ngetopt_lvl_sock_acceptconn(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(SO_BINDTODEVICE)
+static ERL_NIF_TERM ngetopt_lvl_sock_bindtodevice(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(SO_BROADCAST)
+static ERL_NIF_TERM ngetopt_lvl_sock_broadcast(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(SO_DEBUG)
+static ERL_NIF_TERM ngetopt_lvl_sock_debug(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(SO_DOMAIN)
+static ERL_NIF_TERM ngetopt_lvl_sock_domain(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(SO_DONTROUTE)
+static ERL_NIF_TERM ngetopt_lvl_sock_dontroute(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(SO_KEEPALIVE)
+static ERL_NIF_TERM ngetopt_lvl_sock_keepalive(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(SO_LINGER)
+static ERL_NIF_TERM ngetopt_lvl_sock_linger(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(SO_OOBINLINE)
+static ERL_NIF_TERM ngetopt_lvl_sock_oobinline(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(SO_PEEK_OFF)
+static ERL_NIF_TERM ngetopt_lvl_sock_peek_off(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(SO_PRIORITY)
+static ERL_NIF_TERM ngetopt_lvl_sock_priority(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(SO_PROTOCOL)
+static ERL_NIF_TERM ngetopt_lvl_sock_protocol(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(SO_RCVBUF)
+static ERL_NIF_TERM ngetopt_lvl_sock_rcvbuf(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(SO_RCVLOWAT)
+static ERL_NIF_TERM ngetopt_lvl_sock_rcvlowat(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(SO_RCVTIMEO)
+static ERL_NIF_TERM ngetopt_lvl_sock_rcvtimeo(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(SO_REUSEADDR)
+static ERL_NIF_TERM ngetopt_lvl_sock_reuseaddr(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(SO_REUSEPORT)
+static ERL_NIF_TERM ngetopt_lvl_sock_reuseport(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(SO_SNDBUF)
+static ERL_NIF_TERM ngetopt_lvl_sock_sndbuf(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(SO_SNDLOWAT)
+static ERL_NIF_TERM ngetopt_lvl_sock_sndlowat(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(SO_SNDTIMEO)
+static ERL_NIF_TERM ngetopt_lvl_sock_sndtimeo(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(SO_TIMESTAMP)
+static ERL_NIF_TERM ngetopt_lvl_sock_timestamp(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(SO_TYPE)
+static ERL_NIF_TERM ngetopt_lvl_sock_type(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+static ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt);
+#if defined(IP_FREEBIND)
+static ERL_NIF_TERM ngetopt_lvl_ip_freebind(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IP_HDRINCL)
+static ERL_NIF_TERM ngetopt_lvl_ip_hdrincl(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IP_MINTTL)
+static ERL_NIF_TERM ngetopt_lvl_ip_minttl(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IP_MTU)
+static ERL_NIF_TERM ngetopt_lvl_ip_mtu(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IP_MTU_DISCOVER)
+static ERL_NIF_TERM ngetopt_lvl_ip_mtu_discover(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IP_MULTICAST_ALL)
+static ERL_NIF_TERM ngetopt_lvl_ip_multicast_all(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IP_MULTICAST_IF)
+static ERL_NIF_TERM ngetopt_lvl_ip_multicast_if(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IP_MULTICAST_LOOP)
+static ERL_NIF_TERM ngetopt_lvl_ip_multicast_loop(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IP_MULTICAST_TTL)
+static ERL_NIF_TERM ngetopt_lvl_ip_multicast_ttl(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IP_NODEFRAG)
+static ERL_NIF_TERM ngetopt_lvl_ip_nodefrag(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IP_PKTINFO)
+static ERL_NIF_TERM ngetopt_lvl_ip_pktinfo(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IP_RECVDSTADDR)
+static ERL_NIF_TERM ngetopt_lvl_ip_recvdstaddr(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IP_RECVERR)
+static ERL_NIF_TERM ngetopt_lvl_ip_recverr(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IP_RECVIF)
+static ERL_NIF_TERM ngetopt_lvl_ip_recvif(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IP_RECVOPTS)
+static ERL_NIF_TERM ngetopt_lvl_ip_recvopts(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IP_RECVORIGDSTADDR)
+static ERL_NIF_TERM ngetopt_lvl_ip_recvorigdstaddr(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IP_RECVTOS)
+static ERL_NIF_TERM ngetopt_lvl_ip_recvtos(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IP_RECVTTL)
+static ERL_NIF_TERM ngetopt_lvl_ip_recvttl(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IP_RETOPTS)
+static ERL_NIF_TERM ngetopt_lvl_ip_retopts(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IP_ROUTER_ALERT)
+static ERL_NIF_TERM ngetopt_lvl_ip_router_alert(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IP_SENDSRCADDR)
+static ERL_NIF_TERM ngetopt_lvl_ip_sendsrcaddr(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IP_TOS)
+static ERL_NIF_TERM ngetopt_lvl_ip_tos(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IP_TRANSPARENT)
+static ERL_NIF_TERM ngetopt_lvl_ip_transparent(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IP_TTL)
+static ERL_NIF_TERM ngetopt_lvl_ip_ttl(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(HAVE_IPV6)
+static ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt);
+#if defined(IPV6_AUTHHDR)
+static ERL_NIF_TERM ngetopt_lvl_ipv6_authhdr(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IPV6_DSTOPTS)
+static ERL_NIF_TERM ngetopt_lvl_ipv6_dstopts(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IPV6_FLOWINFO)
+static ERL_NIF_TERM ngetopt_lvl_ipv6_flowinfo(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IPV6_HOPLIMIT)
+static ERL_NIF_TERM ngetopt_lvl_ipv6_hoplimit(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IPV6_HOPOPTS)
+static ERL_NIF_TERM ngetopt_lvl_ipv6_hopopts(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IPV6_MTU)
+static ERL_NIF_TERM ngetopt_lvl_ipv6_mtu(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IPV6_MTU_DISCOVER)
+static ERL_NIF_TERM ngetopt_lvl_ipv6_mtu_discover(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IPV6_MULTICAST_HOPS)
+static ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_hops(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IPV6_MULTICAST_IF)
+static ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_if(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IPV6_MULTICAST_LOOP)
+static ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_loop(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IPV6_RECVERR)
+static ERL_NIF_TERM ngetopt_lvl_ipv6_recverr(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO)
+static ERL_NIF_TERM ngetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IPV6_ROUTER_ALERT)
+static ERL_NIF_TERM ngetopt_lvl_ipv6_router_alert(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IPV6_RTHDR)
+static ERL_NIF_TERM ngetopt_lvl_ipv6_rthdr(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IPV6_UNICAST_HOPS)
+static ERL_NIF_TERM ngetopt_lvl_ipv6_unicast_hops(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(IPV6_V6ONLY)
+static ERL_NIF_TERM ngetopt_lvl_ipv6_v6only(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+
+#endif // defined(HAVE_IPV6)
+
+static ERL_NIF_TERM ngetopt_lvl_tcp(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt);
+#if defined(TCP_CONGESTION)
+static ERL_NIF_TERM ngetopt_lvl_tcp_congestion(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(TCP_MAXSEG)
+static ERL_NIF_TERM ngetopt_lvl_tcp_maxseg(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(TCP_NODELAY)
+static ERL_NIF_TERM ngetopt_lvl_tcp_nodelay(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+static ERL_NIF_TERM ngetopt_lvl_udp(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt);
+#if defined(UDP_CORK)
+static ERL_NIF_TERM ngetopt_lvl_udp_cork(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(HAVE_SCTP)
+static ERL_NIF_TERM ngetopt_lvl_sctp(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt);
+#if defined(SCTP_ASSOCINFO)
+static ERL_NIF_TERM ngetopt_lvl_sctp_associnfo(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(SCTP_AUTOCLOSE)
+static ERL_NIF_TERM ngetopt_lvl_sctp_autoclose(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(SCTP_DISABLE_FRAGMENTS)
+static ERL_NIF_TERM ngetopt_lvl_sctp_disable_fragments(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(SCTP_MAXSEG)
+static ERL_NIF_TERM ngetopt_lvl_sctp_maxseg(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(SCTP_INITMSG)
+static ERL_NIF_TERM ngetopt_lvl_sctp_initmsg(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(SCTP_NODELAY)
+static ERL_NIF_TERM ngetopt_lvl_sctp_nodelay(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#if defined(SCTP_RTOINFO)
+static ERL_NIF_TERM ngetopt_lvl_sctp_rtoinfo(ErlNifEnv* env,
+ SocketDescriptor* descP);
+#endif
+#endif // defined(HAVE_SCTP)
+static ERL_NIF_TERM nsockname(ErlNifEnv* env,
+ SocketDescriptor* descP);
+static ERL_NIF_TERM npeername(ErlNifEnv* env,
+ SocketDescriptor* descP);
+static ERL_NIF_TERM ncancel(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM op,
+ ERL_NIF_TERM opRef);
+static ERL_NIF_TERM ncancel_connect(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM opRef);
+static ERL_NIF_TERM ncancel_accept(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM opRef);
+static ERL_NIF_TERM ncancel_accept_current(ErlNifEnv* env,
+ SocketDescriptor* descP);
+static ERL_NIF_TERM ncancel_accept_waiting(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM opRef);
+static ERL_NIF_TERM ncancel_send(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM opRef);
+static ERL_NIF_TERM ncancel_send_current(ErlNifEnv* env,
+ SocketDescriptor* descP);
+static ERL_NIF_TERM ncancel_send_waiting(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM opRef);
+static ERL_NIF_TERM ncancel_recv(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM opRef);
+static ERL_NIF_TERM ncancel_recv_current(ErlNifEnv* env,
+ SocketDescriptor* descP);
+static ERL_NIF_TERM ncancel_recv_waiting(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM opRef);
+static ERL_NIF_TERM ncancel_read_select(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM opRef);
+static ERL_NIF_TERM ncancel_write_select(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM opRef);
+static ERL_NIF_TERM ncancel_mode_select(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM opRef,
+ int smode,
+ int rmode);
+
+static ERL_NIF_TERM nsetopt_str_opt(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int level,
+ int opt,
+ int max,
+ ERL_NIF_TERM eVal);
+static ERL_NIF_TERM nsetopt_bool_opt(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int level,
+ int opt,
+ ERL_NIF_TERM eVal);
+static ERL_NIF_TERM nsetopt_int_opt(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int level,
+ int opt,
+ ERL_NIF_TERM eVal);
+static ERL_NIF_TERM nsetopt_timeval_opt(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int level,
+ int opt,
+ ERL_NIF_TERM eVal);
+
+static ERL_NIF_TERM ngetopt_str_opt(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int level,
+ int opt,
+ int max);
+static ERL_NIF_TERM ngetopt_bool_opt(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int level,
+ int opt);
+static ERL_NIF_TERM ngetopt_int_opt(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int level,
+ int opt);
+static ERL_NIF_TERM ngetopt_timeval_opt(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int level,
+ int opt);
+
+static BOOLEAN_T send_check_writer(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM ref,
+ ERL_NIF_TERM* checkResult);
+static ERL_NIF_TERM send_check_result(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ssize_t written,
+ ssize_t dataSize,
+ int saveErrno,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef);
+static BOOLEAN_T recv_check_reader(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM ref,
+ ERL_NIF_TERM* checkResult);
+static char* recv_init_current_reader(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM ref);
+static ERL_NIF_TERM recv_update_current_reader(ErlNifEnv* env,
+ SocketDescriptor* descP);
+static void recv_error_current_reader(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM reason);
+static ERL_NIF_TERM recv_check_result(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int read,
+ int toRead,
+ int saveErrno,
+ ErlNifBinary* bufP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef);
+static ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int read,
+ int saveErrno,
+ ErlNifBinary* bufP,
+ SocketAddress* fromAddrP,
+ unsigned int fromAddrLen,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef);
+static ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int read,
+ int saveErrno,
+ struct msghdr* msgHdrP,
+ ErlNifBinary* dataBufP,
+ ErlNifBinary* ctrlBufP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef);
+
+static ERL_NIF_TERM nfinalize_connection(ErlNifEnv* env,
+ SocketDescriptor* descP);
+static ERL_NIF_TERM nfinalize_close(ErlNifEnv* env,
+ SocketDescriptor* descP);
+
+extern char* encode_msghdr(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int read,
+ struct msghdr* msgHdrP,
+ ErlNifBinary* dataBufP,
+ ErlNifBinary* ctrlBufP,
+ ERL_NIF_TERM* eSockAddr);
+extern char* encode_cmsghdrs(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ErlNifBinary* cmsgBinP,
+ struct msghdr* msgHdrP,
+ ERL_NIF_TERM* eCMsgHdr);
+extern char* decode_cmsghdrs(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eCMsgHdr,
+ char* cmsgHdrBufP,
+ size_t cmsgHdrBufLen,
+ size_t* cmsgHdrBufUsed);
+extern char* decode_cmsghdr(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eCMsgHdr,
+ char* bufP,
+ size_t rem,
+ size_t* used);
+static char* encode_cmsghdr_level(ErlNifEnv* env,
+ int level,
+ ERL_NIF_TERM* eLevel);
+static char* decode_cmsghdr_level(ErlNifEnv* env,
+ ERL_NIF_TERM eLevel,
+ int* level);
+static char* encode_cmsghdr_type(ErlNifEnv* env,
+ int level,
+ int type,
+ ERL_NIF_TERM* eType);
+static char* decode_cmsghdr_type(ErlNifEnv* env,
+ int level,
+ ERL_NIF_TERM eType,
+ int* type);
+static char* encode_cmsghdr_data(ErlNifEnv* env,
+ ERL_NIF_TERM ctrlBuf,
+ int level,
+ int type,
+ unsigned char* dataP,
+ size_t dataPos,
+ size_t dataLen,
+ ERL_NIF_TERM* eCMsgHdrData);
+static char* encode_cmsghdr_data_socket(ErlNifEnv* env,
+ ERL_NIF_TERM ctrlBuf,
+ int type,
+ unsigned char* dataP,
+ size_t dataPos,
+ size_t dataLen,
+ ERL_NIF_TERM* eCMsgHdrData);
+static char* encode_cmsghdr_data_ip(ErlNifEnv* env,
+ ERL_NIF_TERM ctrlBuf,
+ int type,
+ unsigned char* dataP,
+ size_t dataPos,
+ size_t dataLen,
+ ERL_NIF_TERM* eCMsgHdrData);
+#if defined(HAVE_IPV6)
+static char* encode_cmsghdr_data_ipv6(ErlNifEnv* env,
+ ERL_NIF_TERM ctrlBuf,
+ int type,
+ unsigned char* dataP,
+ size_t dataPos,
+ size_t dataLen,
+ ERL_NIF_TERM* eCMsgHdrData);
+#endif
+extern char* encode_msghdr_flags(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int msgFlags,
+ ERL_NIF_TERM* flags);
+static char* decode_cmsghdr_data(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ char* bufP,
+ size_t rem,
+ int level,
+ int type,
+ ERL_NIF_TERM eData,
+ size_t* used);
+static char* decode_cmsghdr_final(SocketDescriptor* descP,
+ char* bufP,
+ size_t rem,
+ int level,
+ int type,
+ char* data,
+ int sz,
+ size_t* used);
+static BOOLEAN_T decode_sock_linger(ErlNifEnv* env,
+ ERL_NIF_TERM eVal,
+ struct linger* valP);
+#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 char* 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 char* 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 BOOLEAN_T decode_bool(ErlNifEnv* env,
+ ERL_NIF_TERM eVal,
+ BOOLEAN_T* val);
+*/
+static BOOLEAN_T decode_native_get_opt(ErlNifEnv* env,
+ ERL_NIF_TERM eVal,
+ int* opt,
+ Uint16* valueType,
+ int* valueSz);
+// static void encode_bool(BOOLEAN_T val, ERL_NIF_TERM* eVal);
+static ERL_NIF_TERM encode_ip_tos(ErlNifEnv* env, int val);
+
+static void inform_waiting_procs(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ SocketRequestQueue* q,
+ BOOLEAN_T free,
+ 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(SocketDescriptor* descP, int* err);
+
+static SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event);
+
+static int compare_pids(ErlNifEnv* env,
+ const ErlNifPid* pid1,
+ const ErlNifPid* pid2);
+
+
+
+static BOOLEAN_T edomain2domain(int edomain, int* domain);
+static BOOLEAN_T etype2type(int etype, int* type);
+static BOOLEAN_T eproto2proto(ErlNifEnv* env,
+ const ERL_NIF_TERM eproto,
+ int* proto);
+static BOOLEAN_T ehow2how(unsigned int ehow, int* how);
+static BOOLEAN_T esendflags2sendflags(unsigned int esendflags, int* sendflags);
+static BOOLEAN_T erecvflags2recvflags(unsigned int erecvflags, int* recvflags);
+static BOOLEAN_T elevel2level(BOOLEAN_T isEncoded,
+ int eLevel,
+ BOOLEAN_T* isOTP,
+ int* level);
+#ifdef HAVE_SETNS
+static BOOLEAN_T emap2netns(ErlNifEnv* env, ERL_NIF_TERM map, 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(Uint32* cnt, Uint32 inc);
+static void cnt_dec(Uint32* cnt, Uint32 dec);
+
+static void inc_socket(int domain, int type, int protocol);
+static void dec_socket(int domain, int type, int protocol);
+
+
+static BOOLEAN_T acceptor_search4pid(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ErlNifPid* pid);
+static ERL_NIF_TERM acceptor_push(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ErlNifPid pid,
+ ERL_NIF_TERM ref);
+static BOOLEAN_T acceptor_pop(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ErlNifPid* pid,
+ // ErlNifMonitor* mon,
+ ESockMonitor* mon,
+ ERL_NIF_TERM* ref);
+static BOOLEAN_T acceptor_unqueue(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ const ErlNifPid* pid);
+
+static BOOLEAN_T writer_search4pid(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ErlNifPid* pid);
+static ERL_NIF_TERM writer_push(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ErlNifPid pid,
+ ERL_NIF_TERM ref);
+static BOOLEAN_T writer_pop(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ErlNifPid* pid,
+ // ErlNifMonitor* mon,
+ ESockMonitor* mon,
+ ERL_NIF_TERM* ref);
+static BOOLEAN_T writer_unqueue(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ const ErlNifPid* pid);
+
+static BOOLEAN_T reader_search4pid(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ErlNifPid* pid);
+static ERL_NIF_TERM reader_push(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ErlNifPid pid,
+ ERL_NIF_TERM ref);
+static BOOLEAN_T reader_pop(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ErlNifPid* pid,
+ // ErlNifMonitor* mon,
+ ESockMonitor* mon,
+ ERL_NIF_TERM* ref);
+static BOOLEAN_T reader_unqueue(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ const ErlNifPid* pid);
+
+static BOOLEAN_T qsearch4pid(ErlNifEnv* env,
+ SocketRequestQueue* q,
+ ErlNifPid* pid);
+static void qpush(SocketRequestQueue* q,
+ SocketRequestQueueElement* e);
+static SocketRequestQueueElement* qpop(SocketRequestQueue* q);
+static BOOLEAN_T qunqueue(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ const char* slogan,
+ SocketRequestQueue* q,
+ const ErlNifPid* pid);
+
+static int esock_monitor(const char* slogan,
+ ErlNifEnv* env,
+ SocketDescriptor* descP,
+ const ErlNifPid* pid,
+ ESockMonitor* mon);
+static int esock_demonitor(const char* slogan,
+ ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ESockMonitor* monP);
+static void esock_monitor_init(ESockMonitor* mon);
+/*
+static int esock_monitor_compare(const ErlNifMonitor* mon1,
+ const ESockMonitor* mon2);
+*/
+
+
+/*
+#if defined(HAVE_SYS_UN_H) || defined(SO_BINDTODEVICE)
+static size_t my_strnlen(const char *s, size_t maxlen);
+#endif
+*/
+
+static void socket_dtor(ErlNifEnv* env, void* obj);
+static void socket_stop(ErlNifEnv* env,
+ void* obj,
+ int fd,
+ int is_direct_call);
+static void socket_down(ErlNifEnv* env,
+ void* obj,
+ const ErlNifPid* pid,
+ const ErlNifMonitor* mon);
+static void socket_down_acceptor(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ const ErlNifPid* pid);
+static void socket_down_writer(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ const ErlNifPid* pid);
+static void socket_down_reader(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ const ErlNifPid* pid);
+
+static char* esock_send_close_msg(ErlNifEnv* env,
+ SocketDescriptor* descP);
+static char* esock_send_abort_msg(ErlNifEnv* env,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef,
+ ERL_NIF_TERM reason,
+ ErlNifPid* pid);
+static char* esock_send_socket_msg(ErlNifEnv* env,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM tag,
+ ERL_NIF_TERM info,
+ ErlNifPid* pid,
+ ErlNifEnv* msg_env);
+static char* esock_send_msg(ErlNifEnv* env,
+ ERL_NIF_TERM msg,
+ ErlNifPid* pid,
+ ErlNifEnv* msg_env);
+
+static int esock_select_read(ErlNifEnv* env,
+ ErlNifEvent event,
+ void* obj,
+ const ErlNifPid* pid,
+ ERL_NIF_TERM ref);
+static int esock_select_write(ErlNifEnv* env,
+ ErlNifEvent event,
+ void* obj,
+ const ErlNifPid* pid,
+ ERL_NIF_TERM ref);
+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 BOOLEAN_T extract_debug(ErlNifEnv* env,
+ ERL_NIF_TERM map);
+static BOOLEAN_T extract_iow(ErlNifEnv* env,
+ ERL_NIF_TERM map);
+
+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 */
+
+
+
+/* *** String constants *** */
+static char str_adaptation_layer[] = "adaptation_layer";
+static char str_address[] = "address";
+static char str_association[] = "association";
+static char str_assoc_id[] = "assoc_id";
+static char str_authentication[] = "authentication";
+// static char str_any[] = "any";
+static char str_bool[] = "bool";
+static char str_close[] = "close";
+static char str_closed[] = "closed";
+static char str_closing[] = "closing";
+static char str_cookie_life[] = "cookie_life";
+static char str_data_in[] = "data_in";
+static char str_do[] = "do";
+static char str_dont[] = "dont";
+static char str_exclude[] = "exclude";
+static char str_false[] = "false";
+static char str_global_counters[] = "global_counters";
+static char str_in4_sockaddr[] = "in4_sockaddr";
+static char str_in6_sockaddr[] = "in6_sockaddr";
+static char str_include[] = "include";
+static char str_initial[] = "initial";
+static char str_int[] = "int";
+static char str_interface[] = "interface";
+static char str_iow[] = "iow";
+static char str_local_rwnd[] = "local_rwnd";
+// static char str_loopback[] = "loopback";
+static char str_max[] = "max";
+static char str_max_attempts[] = "max_attempts";
+static char str_max_init_timeo[] = "max_init_timeo";
+static char str_max_instreams[] = "max_instreams";
+static char str_max_rxt[] = "max_rxt";
+static char str_min[] = "min";
+static char str_mode[] = "mode";
+static char str_multiaddr[] = "multiaddr";
+// static char str_nif_abort[] = "nif_abort";
+static char str_null[] = "null";
+static char str_num_dlocal[] = "num_domain_local";
+static char str_num_dinet[] = "num_domain_inet";
+static char str_num_dinet6[] = "num_domain_inet6";
+static char str_num_outstreams[] = "num_outstreams";
+static char str_num_peer_dests[] = "num_peer_dests";
+static char str_num_pip[] = "num_proto_ip";
+static char str_num_psctp[] = "num_proto_sctp";
+static char str_num_ptcp[] = "num_proto_tcp";
+static char str_num_pudp[] = "num_proto_udp";
+static char str_num_sockets[] = "num_sockets";
+static char str_num_tdgrams[] = "num_type_dgram";
+static char str_num_tseqpkgs[] = "num_type_seqpacket";
+static char str_num_tstreams[] = "num_type_stream";
+static char str_partial_delivery[] = "partial_delivery";
+static char str_peer_error[] = "peer_error";
+static char str_peer_rwnd[] = "peer_rwnd";
+static char str_probe[] = "probe";
+static char str_select[] = "select";
+static char str_sender_dry[] = "sender_dry";
+static char str_send_failure[] = "send_failure";
+static char str_shutdown[] = "shutdown";
+static char str_slist[] = "slist";
+static char str_sourceaddr[] = "sourceaddr";
+static char str_timeout[] = "timeout";
+static char str_true[] = "true";
+static char str_want[] = "want";
+
+/* (special) error string constants */
+static char str_eisconn[] = "eisconn";
+static char str_enotclosing[] = "enotclosing";
+static char str_enotconn[] = "enotconn";
+static char str_exalloc[] = "exalloc";
+static char str_exbadstate[] = "exbadstate";
+static char str_exbusy[] = "exbusy";
+static char str_exmon[] = "exmonitor"; // failed monitor
+static char str_exself[] = "exself"; // failed self
+static char str_exsend[] = "exsend"; // failed send
+
+
+/* *** "Global" Atoms *** */
+ERL_NIF_TERM esock_atom_abort;
+ERL_NIF_TERM esock_atom_accept;
+ERL_NIF_TERM esock_atom_acceptconn;
+ERL_NIF_TERM esock_atom_acceptfilter;
+ERL_NIF_TERM esock_atom_adaption_layer;
+ERL_NIF_TERM esock_atom_addr;
+ERL_NIF_TERM esock_atom_addrform;
+ERL_NIF_TERM esock_atom_add_membership;
+ERL_NIF_TERM esock_atom_add_source_membership;
+ERL_NIF_TERM esock_atom_any;
+ERL_NIF_TERM esock_atom_associnfo;
+ERL_NIF_TERM esock_atom_authhdr;
+ERL_NIF_TERM esock_atom_auth_active_key;
+ERL_NIF_TERM esock_atom_auth_asconf;
+ERL_NIF_TERM esock_atom_auth_chunk;
+ERL_NIF_TERM esock_atom_auth_delete_key;
+ERL_NIF_TERM esock_atom_auth_key;
+ERL_NIF_TERM esock_atom_auth_level;
+ERL_NIF_TERM esock_atom_autoclose;
+ERL_NIF_TERM esock_atom_bindtodevice;
+ERL_NIF_TERM esock_atom_block_source;
+ERL_NIF_TERM esock_atom_broadcast;
+ERL_NIF_TERM esock_atom_busy_poll;
+ERL_NIF_TERM esock_atom_checksum;
+ERL_NIF_TERM esock_atom_close;
+ERL_NIF_TERM esock_atom_connect;
+ERL_NIF_TERM esock_atom_congestion;
+ERL_NIF_TERM esock_atom_context;
+ERL_NIF_TERM esock_atom_cork;
+ERL_NIF_TERM esock_atom_credentials;
+ERL_NIF_TERM esock_atom_ctrl;
+ERL_NIF_TERM esock_atom_ctrunc;
+ERL_NIF_TERM esock_atom_data;
+ERL_NIF_TERM esock_atom_debug;
+ERL_NIF_TERM esock_atom_default_send_params;
+ERL_NIF_TERM esock_atom_delayed_ack_time;
+ERL_NIF_TERM esock_atom_dgram;
+ERL_NIF_TERM esock_atom_disable_fragments;
+ERL_NIF_TERM esock_atom_domain;
+ERL_NIF_TERM esock_atom_dontfrag;
+ERL_NIF_TERM esock_atom_dontroute;
+ERL_NIF_TERM esock_atom_drop_membership;
+ERL_NIF_TERM esock_atom_drop_source_membership;
+ERL_NIF_TERM esock_atom_dstopts;
+ERL_NIF_TERM esock_atom_eor;
+ERL_NIF_TERM esock_atom_error;
+ERL_NIF_TERM esock_atom_errqueue;
+ERL_NIF_TERM esock_atom_esp_network_level;
+ERL_NIF_TERM esock_atom_esp_trans_level;
+ERL_NIF_TERM esock_atom_events;
+ERL_NIF_TERM esock_atom_explicit_eor;
+ERL_NIF_TERM esock_atom_faith;
+ERL_NIF_TERM esock_atom_false;
+ERL_NIF_TERM esock_atom_family;
+ERL_NIF_TERM esock_atom_flags;
+ERL_NIF_TERM esock_atom_flowinfo;
+ERL_NIF_TERM esock_atom_fragment_interleave;
+ERL_NIF_TERM esock_atom_freebind;
+ERL_NIF_TERM esock_atom_get_peer_addr_info;
+ERL_NIF_TERM esock_atom_hdrincl;
+ERL_NIF_TERM esock_atom_hmac_ident;
+ERL_NIF_TERM esock_atom_hoplimit;
+ERL_NIF_TERM esock_atom_hopopts;
+ERL_NIF_TERM esock_atom_ifindex;
+ERL_NIF_TERM esock_atom_inet;
+ERL_NIF_TERM esock_atom_inet6;
+ERL_NIF_TERM esock_atom_info;
+ERL_NIF_TERM esock_atom_initmsg;
+ERL_NIF_TERM esock_atom_iov;
+ERL_NIF_TERM esock_atom_ip;
+ERL_NIF_TERM esock_atom_ipcomp_level;
+ERL_NIF_TERM esock_atom_ipv6;
+ERL_NIF_TERM esock_atom_i_want_mapped_v4_addr;
+ERL_NIF_TERM esock_atom_join_group;
+ERL_NIF_TERM esock_atom_keepalive;
+ERL_NIF_TERM esock_atom_keepcnt;
+ERL_NIF_TERM esock_atom_keepidle;
+ERL_NIF_TERM esock_atom_keepintvl;
+ERL_NIF_TERM esock_atom_leave_group;
+ERL_NIF_TERM esock_atom_level;
+ERL_NIF_TERM esock_atom_linger;
+ERL_NIF_TERM esock_atom_local;
+ERL_NIF_TERM esock_atom_local_auth_chunks;
+ERL_NIF_TERM esock_atom_loopback;
+ERL_NIF_TERM esock_atom_lowdelay;
+ERL_NIF_TERM esock_atom_mark;
+ERL_NIF_TERM esock_atom_maxburst;
+ERL_NIF_TERM esock_atom_maxseg;
+ERL_NIF_TERM esock_atom_md5sig;
+ERL_NIF_TERM esock_atom_mincost;
+ERL_NIF_TERM esock_atom_minttl;
+ERL_NIF_TERM esock_atom_msfilter;
+ERL_NIF_TERM esock_atom_mtu;
+ERL_NIF_TERM esock_atom_mtu_discover;
+ERL_NIF_TERM esock_atom_multicast_all;
+ERL_NIF_TERM esock_atom_multicast_hops;
+ERL_NIF_TERM esock_atom_multicast_if;
+ERL_NIF_TERM esock_atom_multicast_loop;
+ERL_NIF_TERM esock_atom_multicast_ttl;
+ERL_NIF_TERM esock_atom_nodelay;
+ERL_NIF_TERM esock_atom_nodefrag;
+ERL_NIF_TERM esock_atom_noopt;
+ERL_NIF_TERM esock_atom_nopush;
+ERL_NIF_TERM esock_atom_not_found;
+ERL_NIF_TERM esock_atom_not_owner;
+ERL_NIF_TERM esock_atom_ok;
+ERL_NIF_TERM esock_atom_oob;
+ERL_NIF_TERM esock_atom_oobinline;
+ERL_NIF_TERM esock_atom_options;
+ERL_NIF_TERM esock_atom_origdstaddr;
+ERL_NIF_TERM esock_atom_partial_delivery_point;
+ERL_NIF_TERM esock_atom_passcred;
+ERL_NIF_TERM esock_atom_path;
+ERL_NIF_TERM esock_atom_peekcred;
+ERL_NIF_TERM esock_atom_peek_off;
+ERL_NIF_TERM esock_atom_peer_addr_params;
+ERL_NIF_TERM esock_atom_peer_auth_chunks;
+ERL_NIF_TERM esock_atom_pktinfo;
+ERL_NIF_TERM esock_atom_pktoptions;
+ERL_NIF_TERM esock_atom_port;
+ERL_NIF_TERM esock_atom_portrange;
+ERL_NIF_TERM esock_atom_primary_addr;
+ERL_NIF_TERM esock_atom_priority;
+ERL_NIF_TERM esock_atom_protocol;
+ERL_NIF_TERM esock_atom_raw;
+ERL_NIF_TERM esock_atom_rcvbuf;
+ERL_NIF_TERM esock_atom_rcvbufforce;
+ERL_NIF_TERM esock_atom_rcvlowat;
+ERL_NIF_TERM esock_atom_rcvtimeo;
+ERL_NIF_TERM esock_atom_rdm;
+ERL_NIF_TERM esock_atom_recv;
+ERL_NIF_TERM esock_atom_recvdstaddr;
+ERL_NIF_TERM esock_atom_recverr;
+ERL_NIF_TERM esock_atom_recvfrom;
+ERL_NIF_TERM esock_atom_recvif;
+ERL_NIF_TERM esock_atom_recvmsg;
+ERL_NIF_TERM esock_atom_recvopts;
+ERL_NIF_TERM esock_atom_recvorigdstaddr;
+ERL_NIF_TERM esock_atom_recvpktinfo;
+ERL_NIF_TERM esock_atom_recvtclass;
+ERL_NIF_TERM esock_atom_recvtos;
+ERL_NIF_TERM esock_atom_recvttl;
+ERL_NIF_TERM esock_atom_reliability;
+ERL_NIF_TERM esock_atom_reset_streams;
+ERL_NIF_TERM esock_atom_retopts;
+ERL_NIF_TERM esock_atom_reuseaddr;
+ERL_NIF_TERM esock_atom_reuseport;
+ERL_NIF_TERM esock_atom_rights;
+ERL_NIF_TERM esock_atom_router_alert;
+ERL_NIF_TERM esock_atom_rthdr;
+ERL_NIF_TERM esock_atom_rtoinfo;
+ERL_NIF_TERM esock_atom_rxq_ovfl;
+ERL_NIF_TERM esock_atom_scope_id;
+ERL_NIF_TERM esock_atom_sctp;
+ERL_NIF_TERM esock_atom_sec;
+ERL_NIF_TERM esock_atom_select_failed;
+ERL_NIF_TERM esock_atom_select_sent;
+ERL_NIF_TERM esock_atom_send;
+ERL_NIF_TERM esock_atom_sendmsg;
+ERL_NIF_TERM esock_atom_sendsrcaddr;
+ERL_NIF_TERM esock_atom_sendto;
+ERL_NIF_TERM esock_atom_seqpacket;
+ERL_NIF_TERM esock_atom_setfib;
+ERL_NIF_TERM esock_atom_set_peer_primary_addr;
+ERL_NIF_TERM esock_atom_socket;
+ERL_NIF_TERM esock_atom_socket_tag;
+ERL_NIF_TERM esock_atom_sndbuf;
+ERL_NIF_TERM esock_atom_sndbufforce;
+ERL_NIF_TERM esock_atom_sndlowat;
+ERL_NIF_TERM esock_atom_sndtimeo;
+ERL_NIF_TERM esock_atom_spec_dst;
+ERL_NIF_TERM esock_atom_status;
+ERL_NIF_TERM esock_atom_stream;
+ERL_NIF_TERM esock_atom_syncnt;
+ERL_NIF_TERM esock_atom_tclass;
+ERL_NIF_TERM esock_atom_tcp;
+ERL_NIF_TERM esock_atom_throughput;
+ERL_NIF_TERM esock_atom_timestamp;
+ERL_NIF_TERM esock_atom_tos;
+ERL_NIF_TERM esock_atom_transparent;
+ERL_NIF_TERM esock_atom_true;
+ERL_NIF_TERM esock_atom_trunc;
+ERL_NIF_TERM esock_atom_ttl;
+ERL_NIF_TERM esock_atom_type;
+ERL_NIF_TERM esock_atom_udp;
+ERL_NIF_TERM esock_atom_unblock_source;
+ERL_NIF_TERM esock_atom_undefined;
+ERL_NIF_TERM esock_atom_unicast_hops;
+ERL_NIF_TERM esock_atom_unknown;
+ERL_NIF_TERM esock_atom_usec;
+ERL_NIF_TERM esock_atom_user_timeout;
+ERL_NIF_TERM esock_atom_use_ext_recvinfo;
+ERL_NIF_TERM esock_atom_use_min_mtu;
+ERL_NIF_TERM esock_atom_v6only;
+
+/* *** "Global" error (=reason) atoms *** */
+ERL_NIF_TERM esock_atom_eagain;
+ERL_NIF_TERM esock_atom_eafnosupport;
+ERL_NIF_TERM esock_atom_einval;
+
+/* *** Atoms *** */
+static ERL_NIF_TERM atom_adaptation_layer;
+static ERL_NIF_TERM atom_address;
+static ERL_NIF_TERM atom_association;
+static ERL_NIF_TERM atom_assoc_id;
+static ERL_NIF_TERM atom_authentication;
+static ERL_NIF_TERM atom_bool;
+static ERL_NIF_TERM atom_close;
+static ERL_NIF_TERM atom_closed;
+static ERL_NIF_TERM atom_closing;
+static ERL_NIF_TERM atom_cookie_life;
+static ERL_NIF_TERM atom_data_in;
+static ERL_NIF_TERM atom_do;
+static ERL_NIF_TERM atom_dont;
+static ERL_NIF_TERM atom_exclude;
+static ERL_NIF_TERM atom_false;
+static ERL_NIF_TERM atom_global_counters;
+static ERL_NIF_TERM atom_in4_sockaddr;
+static ERL_NIF_TERM atom_in6_sockaddr;
+static ERL_NIF_TERM atom_include;
+static ERL_NIF_TERM atom_initial;
+static ERL_NIF_TERM atom_int;
+static ERL_NIF_TERM atom_interface;
+static ERL_NIF_TERM atom_iow;
+static ERL_NIF_TERM atom_local_rwnd;
+static ERL_NIF_TERM atom_max;
+static ERL_NIF_TERM atom_max_attempts;
+static ERL_NIF_TERM atom_max_init_timeo;
+static ERL_NIF_TERM atom_max_instreams;
+static ERL_NIF_TERM atom_max_rxt;
+static ERL_NIF_TERM atom_min;
+static ERL_NIF_TERM atom_mode;
+static ERL_NIF_TERM atom_multiaddr;
+// static ERL_NIF_TERM atom_nif_abort;
+static ERL_NIF_TERM atom_null;
+static ERL_NIF_TERM atom_num_dinet;
+static ERL_NIF_TERM atom_num_dinet6;
+static ERL_NIF_TERM atom_num_dlocal;
+static ERL_NIF_TERM atom_num_outstreams;
+static ERL_NIF_TERM atom_num_peer_dests;
+static ERL_NIF_TERM atom_num_pip;
+static ERL_NIF_TERM atom_num_psctp;
+static ERL_NIF_TERM atom_num_ptcp;
+static ERL_NIF_TERM atom_num_pudp;
+static ERL_NIF_TERM atom_num_sockets;
+static ERL_NIF_TERM atom_num_tdgrams;
+static ERL_NIF_TERM atom_num_tseqpkgs;
+static ERL_NIF_TERM atom_num_tstreams;
+static ERL_NIF_TERM atom_partial_delivery;
+static ERL_NIF_TERM atom_peer_error;
+static ERL_NIF_TERM atom_peer_rwnd;
+static ERL_NIF_TERM atom_probe;
+static ERL_NIF_TERM atom_select;
+static ERL_NIF_TERM atom_sender_dry;
+static ERL_NIF_TERM atom_send_failure;
+static ERL_NIF_TERM atom_shutdown;
+static ERL_NIF_TERM atom_slist;
+static ERL_NIF_TERM atom_sourceaddr;
+static ERL_NIF_TERM atom_timeout;
+static ERL_NIF_TERM atom_true;
+static ERL_NIF_TERM atom_want;
+
+static ERL_NIF_TERM atom_eisconn;
+static ERL_NIF_TERM atom_enotclosing;
+static ERL_NIF_TERM atom_enotconn;
+static ERL_NIF_TERM atom_exalloc;
+static ERL_NIF_TERM atom_exbadstate;
+static ERL_NIF_TERM atom_exbusy;
+static ERL_NIF_TERM atom_exmon;
+static ERL_NIF_TERM atom_exself;
+static ERL_NIF_TERM atom_exsend;
+
+
+/* *** Sockets *** */
+static ErlNifResourceType* sockets;
+static ErlNifResourceTypeInit socketInit = {
+ socket_dtor,
+ socket_stop,
+ (ErlNifResourceDown*) socket_down
+};
+
+// Initiated when the nif is loaded
+static SocketData data;
+
+
+/* ----------------------------------------------------------------------
+ * N I F F u n c t i o n s
+ * ----------------------------------------------------------------------
+ *
+ * Utility and admin functions:
+ * ----------------------------
+ * nif_info/0
+ * nif_supports/1
+ * (nif_debug/1)
+ *
+ * The "proper" socket functions:
+ * ------------------------------
+ * nif_open(Domain, Type, Protocol, Extra)
+ * nif_bind(Sock, LocalAddr)
+ * nif_connect(Sock, SockAddr)
+ * nif_listen(Sock, Backlog)
+ * nif_accept(LSock, Ref)
+ * nif_send(Sock, SendRef, Data, Flags)
+ * nif_sendto(Sock, SendRef, Data, Dest, Flags)
+ * nif_sendmsg(Sock, SendRef, MsgHdr, Flags)
+ * nif_recv(Sock, RecvRef, Length, Flags)
+ * nif_recvfrom(Sock, RecvRef, BufSz, Flags)
+ * nif_recvmsg(Sock, RecvRef, BufSz, CtrlSz, Flags)
+ * nif_close(Sock)
+ * nif_shutdown(Sock, How)
+ * nif_sockname(Sock)
+ * nif_peername(Sock)
+ *
+ * And some functions to manipulate and retrieve socket options:
+ * -------------------------------------------------------------
+ * nif_setopt/5
+ * nif_getopt/4
+ *
+ * And some utility functions:
+ * -------------------------------------------------------------
+ *
+ * And some socket admin functions:
+ * -------------------------------------------------------------
+ * nif_cancel(Sock, Ref)
+ */
+
+
+/* ----------------------------------------------------------------------
+ * nif_info
+ *
+ * Description:
+ * This is currently just a placeholder...
+ */
+#define MKCT(E, T, C) MKT2((E), (T), MKI((E), (C)))
+
+static
+ERL_NIF_TERM nif_info(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ if (argc != 0) {
+ return enif_make_badarg(env);
+ } else {
+ ERL_NIF_TERM numSockets = MKCT(env, atom_num_sockets, data.numSockets);
+ ERL_NIF_TERM numTypeDGrams = MKCT(env, atom_num_tdgrams, data.numTypeDGrams);
+ ERL_NIF_TERM numTypeStreams = MKCT(env, atom_num_tstreams, data.numTypeStreams);
+ ERL_NIF_TERM numTypeSeqPkgs = MKCT(env, atom_num_tseqpkgs, data.numTypeSeqPkgs);
+ ERL_NIF_TERM numDomLocal = MKCT(env, atom_num_dlocal, data.numDomainLocal);
+ ERL_NIF_TERM numDomInet = MKCT(env, atom_num_dinet, data.numDomainInet);
+ ERL_NIF_TERM numDomInet6 = MKCT(env, atom_num_dinet6, data.numDomainInet6);
+ ERL_NIF_TERM numProtoIP = MKCT(env, atom_num_pip, data.numProtoIP);
+ ERL_NIF_TERM numProtoTCP = MKCT(env, atom_num_ptcp, data.numProtoTCP);
+ ERL_NIF_TERM numProtoUDP = MKCT(env, atom_num_pudp, data.numProtoUDP);
+ ERL_NIF_TERM numProtoSCTP = MKCT(env, atom_num_psctp, data.numProtoSCTP);
+ ERL_NIF_TERM gcnt[] = {numSockets,
+ numTypeDGrams, numTypeStreams, numTypeSeqPkgs,
+ numDomLocal, numDomInet, numDomInet6,
+ numProtoIP, numProtoTCP, numProtoUDP, numProtoSCTP};
+ unsigned int lenGCnt = sizeof(gcnt) / sizeof(ERL_NIF_TERM);
+ ERL_NIF_TERM lgcnt = MKLA(env, gcnt, lenGCnt);
+ ERL_NIF_TERM keys[] = {esock_atom_debug, atom_iow, atom_global_counters};
+ ERL_NIF_TERM vals[] = {BOOL2ATOM(data.dbg), BOOL2ATOM(data.iow), lgcnt};
+ ERL_NIF_TERM info;
+ unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM);
+ unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM);
+
+ ESOCK_ASSERT( (numKeys == numVals) );
+
+ if (!MKMA(env, keys, vals, numKeys, &info))
+ return enif_make_badarg(env);
+
+ return info;
+ }
+#endif
+}
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_supports
+ *
+ * Description:
+ * This function is intended to answer the question: "Is X supported?"
+ * Currently only one key is "supported": options
+ * That results in a list of all *known options* (known by us) and if
+ * the platform supports (OS) it or not.
+ *
+ * Key
+ * ---
+ * options [{socket, [{Opt, boolean()}]},
+ * {ip, [{Opt, boolean()}]},
+ * {ipv6, [{Opt, boolean()}]},
+ * {tcp, [{Opt, boolean()}]},
+ * {udp, [{Opt, boolean()}]},
+ * {sctp, [{Opt, boolean()}]}]
+ */
+
+static
+ERL_NIF_TERM nif_supports(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ int key;
+
+ SGDBG( ("SOCKET", "nif_supports -> entry with %d args\r\n", argc) );
+
+ /* Extract arguments and perform preliminary validation */
+
+ if ((argc != 1) ||
+ !GET_INT(env, argv[0], &key)) {
+ return enif_make_badarg(env);
+ }
+
+ return nsupports(env, key);
+#endif
+}
+
+
+
+/* nopen - 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.
+ */
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nsupports(ErlNifEnv* env, int key)
+{
+ ERL_NIF_TERM result;
+
+ SGDBG( ("SOCKET", "nsupports -> entry with 0x%lX\r\n", key) );
+
+ switch (key) {
+ case SOCKET_SUPPORTS_OPTIONS:
+ result = nsupports_options(env);
+ break;
+
+ case SOCKET_SUPPORTS_SCTP:
+ result = nsupports_sctp(env);
+ break;
+
+ case SOCKET_SUPPORTS_IPV6:
+ result = nsupports_ipv6(env);
+ break;
+
+ default:
+ result = esock_atom_false;
+ break;
+ }
+
+ return result;
+}
+#endif
+
+
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nsupports_options(ErlNifEnv* env)
+{
+ ERL_NIF_TERM sockOpts = nsupports_options_socket(env);
+ ERL_NIF_TERM sockOptsT = MKT2(env, esock_atom_socket, sockOpts);
+ ERL_NIF_TERM ipOpts = nsupports_options_ip(env);
+ ERL_NIF_TERM ipOptsT = MKT2(env, esock_atom_ip, ipOpts);
+ ERL_NIF_TERM ipv6Opts = nsupports_options_ipv6(env);
+ ERL_NIF_TERM ipv6OptsT = MKT2(env, esock_atom_ipv6, ipv6Opts);
+ ERL_NIF_TERM tcpOpts = nsupports_options_tcp(env);
+ ERL_NIF_TERM tcpOptsT = MKT2(env, esock_atom_tcp, tcpOpts);
+ ERL_NIF_TERM udpOpts = nsupports_options_udp(env);
+ ERL_NIF_TERM udpOptsT = MKT2(env, esock_atom_udp, udpOpts);
+ ERL_NIF_TERM sctpOpts = nsupports_options_sctp(env);
+ ERL_NIF_TERM sctpOptsT = MKT2(env, esock_atom_sctp, sctpOpts);
+ ERL_NIF_TERM optsA[] = {sockOptsT,
+ ipOptsT, ipv6OptsT,
+ tcpOptsT, udpOptsT, sctpOptsT};
+ unsigned int lenOptsA = sizeof(optsA) / sizeof(ERL_NIF_TERM);
+ ERL_NIF_TERM optsL = MKLA(env, optsA, lenOptsA);
+
+ return optsL;
+}
+#endif
+
+
+
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nsupports_options_socket(ErlNifEnv* env)
+{
+ SocketTArray opts = TARRAY_CREATE(128);
+ ERL_NIF_TERM tmp, optsL;
+
+
+ /* *** SOCKET_OPT_SOCK_ACCEPTCONN => SO_ACCEPTCONN *** */
+#if defined(SO_ACCEPTCONN)
+ tmp = MKT2(env, esock_atom_acceptconn, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_acceptconn, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_ACCEPTFILTER => SO_ACCEPTFILTER *** */
+ tmp = MKT2(env, esock_atom_acceptfilter, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_BINDTODEVICE => SO_BINDTODEVICE *** */
+#if defined(SO_BINDTODEVICE)
+ tmp = MKT2(env, esock_atom_bindtodevice, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_bindtodevice, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_BROADCAST => SO_BROADCAST *** */
+#if defined(SO_BROADCAST)
+ tmp = MKT2(env, esock_atom_broadcast, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_broadcast, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_BUSY_POLL => SO_BUSY_POLL *** */
+ tmp = MKT2(env, esock_atom_busy_poll, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_DEBUG => SO_DEBUG *** */
+#if defined(SO_DEBUG)
+ tmp = MKT2(env, esock_atom_debug, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_debug, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_DOMAIN => SO_DOMAIN *** */
+#if defined(SO_DOMAIN)
+ tmp = MKT2(env, esock_atom_domain, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_domain, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_DONTROUTE => SO_DONTROUTE *** */
+#if defined(SO_DONTROUTE)
+ tmp = MKT2(env, esock_atom_dontroute, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_dontroute, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_ERROR => SO_ERROR *** */
+ tmp = MKT2(env, esock_atom_error, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_KEEPALIVE => SO_KEEPALIVE *** */
+#if defined(SO_KEEPALIVE)
+ tmp = MKT2(env, esock_atom_keepalive, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_keepalive, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_LINGER => SO_LINGER *** */
+#if defined(SO_LINGER)
+ tmp = MKT2(env, esock_atom_linger, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_linger, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_MARK => SO_MARK *** */
+ tmp = MKT2(env, esock_atom_mark, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_OOBINLINE => SO_OOBINLINE *** */
+#if defined(SO_OOBINLINE)
+ tmp = MKT2(env, esock_atom_oobinline, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_oobinline, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_PASSCRED => SO_PASSCRED *** */
+ tmp = MKT2(env, esock_atom_passcred, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_PEEK_OFF => SO_PEEK_OFF *** */
+#if defined(SO_PEEK_OFF)
+ tmp = MKT2(env, esock_atom_peek_off, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_peek_off, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_PEEKCRED => SO_PEEKCRED *** */
+ tmp = MKT2(env, esock_atom_peekcred, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_PRIORITY => SO_PRIORITY *** */
+#if defined(SO_PRIORITY)
+ tmp = MKT2(env, esock_atom_priority, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_priority, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_PROTOCOL => SO_PROTOCOL *** */
+#if defined(SO_PROTOCOL)
+ tmp = MKT2(env, esock_atom_protocol, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_protocol, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_RCVBUF => SO_RCVBUF *** */
+#if defined(SO_RCVBUF)
+ tmp = MKT2(env, esock_atom_rcvbuf, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_rcvbuf, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_RCVBUFFORCE => SO_RCVBUFFORCE *** */
+ tmp = MKT2(env, esock_atom_rcvbufforce, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_RCVLOWAT => SO_RCVLOWAT *** */
+#if defined(SO_RCVLOWAT)
+ tmp = MKT2(env, esock_atom_rcvlowat, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_rcvlowat, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_RCVTIMEO => SO_RCVTIMEO *** */
+#if defined(SO_RCVTIMEO)
+ tmp = MKT2(env, esock_atom_rcvtimeo, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_rcvtimeo, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_REUSEADDR => SO_REUSEADDR *** */
+#if defined(SO_REUSEADDR)
+ tmp = MKT2(env, esock_atom_reuseaddr, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_reuseaddr, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_REUSEPORT => SO_REUSEPORT *** */
+#if defined(SO_REUSEPORT)
+ tmp = MKT2(env, esock_atom_reuseport, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_reuseport, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_RXQ_OVFL => SO_RXQ_OVFL *** */
+ tmp = MKT2(env, esock_atom_rxq_ovfl, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_SETFIB => SO_SETFIB *** */
+ tmp = MKT2(env, esock_atom_setfib, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_SNDBUF => SO_SNDBUF *** */
+#if defined(SO_SNDBUF)
+ tmp = MKT2(env, esock_atom_sndbuf, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_sndbuf, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_SNDBUFFORCE => SO_SNDBUFFORCE *** */
+ tmp = MKT2(env, esock_atom_sndbufforce, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_SNDLOWAT => SO_SNDLOWAT *** */
+#if defined(SO_SNDLOWAT)
+ tmp = MKT2(env, esock_atom_sndlowat, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_sndlowat, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_SNDTIMEO => SO_SNDTIMEO *** */
+#if defined(SO_SNDTIMEO)
+ tmp = MKT2(env, esock_atom_sndtimeo, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_sndtimeo, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_TIMESTAMP => SO_TIMESTAMP *** */
+#if defined(SO_TIMESTAMP)
+ tmp = MKT2(env, esock_atom_timestamp, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_timestamp, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SOCK_TYPE => SO_TYPE *** */
+#if defined(SO_TYPE)
+ tmp = MKT2(env, esock_atom_type, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_type, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ TARRAY_TOLIST(opts, env, &optsL);
+
+ return optsL;
+}
+#endif
+
+
+
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nsupports_options_ip(ErlNifEnv* env)
+{
+ SocketTArray opts = TARRAY_CREATE(128);
+ ERL_NIF_TERM tmp, optsL;
+
+
+ /* *** SOCKET_OPT_IP_ADD_MEMBERSHIP => IP_ADD_MEMBERSHIP *** */
+#if defined(IP_ADD_MEMBERSHIP)
+ tmp = MKT2(env, esock_atom_add_membership, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_add_membership, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_ADD_SOURCE_MEMBERSHIP => IP_ADD_SOURCE_MEMBERSHIP *** */
+#if defined(IP_ADD_SOURCE_MEMBERSHIP)
+ tmp = MKT2(env, esock_atom_add_source_membership, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_add_source_membership, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_BLOCK_SOURCE => IP_BLOCK_SOURCE *** */
+#if defined(IP_BLOCK_SOURCE)
+ tmp = MKT2(env, esock_atom_block_source, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_block_source, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_DONTFRAG => IP_DONTFRAG *** */
+ tmp = MKT2(env, esock_atom_dontfrag, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_DROP_MEMBERSHIP => IP_DROP_MEMBERSHIP *** */
+#if defined(IP_DROP_MEMBERSHIP)
+ tmp = MKT2(env, esock_atom_drop_membership, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_drop_membership, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP => IP_DROP_SOURCE_MEMBERSHIP *** */
+#if defined(IP_DROP_SOURCE_MEMBERSHIP)
+ tmp = MKT2(env, esock_atom_drop_source_membership, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_drop_source_membership, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_FREEBIND => IP_FREEBIND *** */
+#if defined(IP_FREEBIND)
+ tmp = MKT2(env, esock_atom_freebind, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_freebind, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_HDRINCL => IP_HDRINCL *** */
+#if defined(IP_HDRINCL)
+ tmp = MKT2(env, esock_atom_hdrincl, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_hdrincl, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_MINTTL => IP_MINTTL *** */
+#if defined(IP_MINTTL)
+ tmp = MKT2(env, esock_atom_minttl, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_minttl, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_MSFILTER => IP_MSFILTER / IP_MSFILTER_SIZE *** */
+#if defined(IP_MSFILTER) && defined(IP_MSFILTER_SIZE)
+ tmp = MKT2(env, esock_atom_msfilter, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_msfilter, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_MTU => IP_MTU *** */
+ tmp = MKT2(env, esock_atom_mtu, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_MTU_DISCOVER => IP_MTU_DISCOVER *** */
+#if defined(IP_MTU_DISCOVER)
+ tmp = MKT2(env, esock_atom_mtu_discover, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_mtu_discover, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_MULTICAST_ALL => IP_MULTICAST_ALL *** */
+#if defined(IP_MULTICAST_ALL)
+ tmp = MKT2(env, esock_atom_multicast_all, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_multicast_all, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_MULTICAST_IF => IP_MULTICAST_IF *** */
+#if defined(IP_MULTICAST_IF)
+ tmp = MKT2(env, esock_atom_multicast_if, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_multicast_if, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_MULTICAST_LOOP => IP_MULTICAST_LOOP *** */
+#if defined(IP_MULTICAST_LOOP)
+ tmp = MKT2(env, esock_atom_multicast_loop, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_multicast_loop, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_MULTICAST_TTL => IP_MULTICAST_TTL *** */
+#if defined(IP_MULTICAST_TTL)
+ tmp = MKT2(env, esock_atom_multicast_ttl, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_multicast_ttl, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_NODEFRAG => IP_NODEFRAG *** */
+#if defined(IP_NODEFRAG)
+ tmp = MKT2(env, esock_atom_nodefrag, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_nodefrag, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_OPTIONS => IP_OPTIONS *** */
+ tmp = MKT2(env, esock_atom_options, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_PKTINFO => IP_PKTINFO *** */
+#if defined(IP_PKTINFO)
+ tmp = MKT2(env, esock_atom_pktinfo, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_pktinfo, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_RECVDSTADDR => IP_RECVDSTADDR *** */
+#if defined(IP_RECVDSTADDR)
+ tmp = MKT2(env, esock_atom_recvdstaddr, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_recvdstaddr, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_RECVERR => IP_RECVERR *** */
+#if defined(IP_RECVERR)
+ tmp = MKT2(env, esock_atom_recverr, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_recverr, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_RECVIF => IP_RECVIF *** */
+#if defined(IP_RECVIF)
+ tmp = MKT2(env, esock_atom_recvif, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_recvif, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_RECVOPTS => IP_RECVOPTS *** */
+#if defined(IP_RECVOPTS)
+ tmp = MKT2(env, esock_atom_recvopts, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_recvopts, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_RECVORIGDSTADDR => IP_RECVORIGDSTADDR *** */
+#if defined(IP_RECVORIGDSTADDR)
+ tmp = MKT2(env, esock_atom_recvorigdstaddr, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_recvorigdstaddr, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_RECVTOS => IP_RECVTOS *** */
+#if defined(IP_RECVTOS)
+ tmp = MKT2(env, esock_atom_recvtos, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_recvtos, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_RECVTTL => IP_RECVTTL *** */
+#if defined(IP_RECVTTL)
+ tmp = MKT2(env, esock_atom_recvttl, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_recvttl, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_RETOPTS => IP_RETOPTS *** */
+#if defined(IP_RETOPTS)
+ tmp = MKT2(env, esock_atom_retopts, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_retopts, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_ROUTER_ALERT => IP_ROUTER_ALERT *** */
+#if defined(IP_ROUTER_ALERT)
+ tmp = MKT2(env, esock_atom_router_alert, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_router_alert, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_SENDSRCADDR => IP_SENDSRCADDR *** */
+#if defined(IP_SENDSRCADDR)
+ tmp = MKT2(env, esock_atom_sendsrcaddr, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_sendsrcaddr, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_TOS => IP_TOS *** */
+#if defined(IP_TOS)
+ tmp = MKT2(env, esock_atom_tos, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_tos, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_TRANSPARENT => IP_TRANSPARENT *** */
+#if defined(IP_TRANSPARENT)
+ tmp = MKT2(env, esock_atom_transparent, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_transparent, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_TTL => IP_TTL *** */
+#if defined(IP_TTL)
+ tmp = MKT2(env, esock_atom_ttl, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_ttl, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IP_UNBLOCK_SOURCE => IP_UNBLOCK_SOURCE *** */
+#if defined(IP_UNBLOCK_SOURCE)
+ tmp = MKT2(env, esock_atom_unblock_source, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_unblock_source, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ TARRAY_TOLIST(opts, env, &optsL);
+
+ return optsL;
+}
+#endif
+
+
+
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nsupports_options_ipv6(ErlNifEnv* env)
+{
+ SocketTArray opts = TARRAY_CREATE(128);
+ ERL_NIF_TERM tmp, optsL;
+
+
+ /* *** SOCKET_OPT_IPV6_ADDRFORM => IPV6_ADDRFORM *** */
+#if defined(IPV6_ADDRFORM)
+ tmp = MKT2(env, esock_atom_addrform, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_addrform, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_ADD_MEMBERSHIP => IPV6_ADD_MEMBERSHIP *** */
+#if defined(IPV6_ADD_MEMBERSHIP)
+ tmp = MKT2(env, esock_atom_add_membership, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_add_membership, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_AUTHHDR => IPV6_AUTHHDR *** */
+#if defined(IPV6_AUTHHDR)
+ tmp = MKT2(env, esock_atom_authhdr, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_authhdr, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_AUTH_LEVEL => IPV6_AUTH_LEVEL *** */
+ tmp = MKT2(env, esock_atom_auth_level, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_CHECKSUM => IPV6_CHECKSUM *** */
+ tmp = MKT2(env, esock_atom_checksum, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_DROP_MEMBERSHIP => IPV6_DROP_MEMBERSHIP *** */
+#if defined(IPV6_DROP_MEMBERSHIP)
+ tmp = MKT2(env, esock_atom_drop_membership, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_drop_membership, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_DSTOPTS => IPV6_DSTOPTS *** */
+#if defined(IPV6_DSTOPTS)
+ tmp = MKT2(env, esock_atom_dstopts, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_dstopts, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_ESP_NETWORK_LEVEL => IPV6_ESP_NETWORK_LEVEL *** */
+ tmp = MKT2(env, esock_atom_esp_network_level, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_ESP_TRANS_LEVEL => IPV6_ESP_TRANS_LEVEL *** */
+ tmp = MKT2(env, esock_atom_esp_trans_level, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_FAITH => IPV6_FAITH *** */
+ tmp = MKT2(env, esock_atom_faith, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_FLOWINFO => IPV6_FLOWINFO *** */
+#if defined(IPV6_FLOWINFO)
+ tmp = MKT2(env, esock_atom_flowinfo, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_flowinfo, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_HOPLIMIT => IPV6_HOPLIMIT *** */
+#if defined(IPV6_HOPLIMIT)
+ tmp = MKT2(env, esock_atom_hoplimit, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_hoplimit, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_HOPOPTS => IPV6_HOPOPTS *** */
+#if defined(IPV6_HOPOPTS)
+ tmp = MKT2(env, esock_atom_hopopts, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_hopopts, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_IPCOMP_LEVEL => IPV6_IPCOMP_LEVEL *** */
+ tmp = MKT2(env, esock_atom_ipcomp_level, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_JOIN_GROUP => IPV6_JOIN_GROUP *** */
+ tmp = MKT2(env, esock_atom_join_group, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_LEAVE_GROUP => IPV6_LEAVE_GROUP *** */
+ tmp = MKT2(env, esock_atom_leave_group, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_MTU => IPV6_MTU *** */
+#if defined(IPV6_MTU)
+ tmp = MKT2(env, esock_atom_mtu, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_mtu, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_MTU_DISCOVER => IPV6_MTU_DISCOVER *** */
+#if defined(IPV6_MTU_DISCOVER)
+ tmp = MKT2(env, esock_atom_mtu_discover, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_mtu_discover, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_MULTICAST_HOPS => IPV6_MULTICAST_HOPS *** */
+#if defined(IPV6_MULTICAST_HOPS)
+ tmp = MKT2(env, esock_atom_multicast_hops, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_multicast_hops, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_MULTICAST_IF => IPV6_MULTICAST_IF *** */
+#if defined(IPV6_MULTICAST_IF)
+ tmp = MKT2(env, esock_atom_multicast_if, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_multicast_if, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_MULTICAST_LOOP => IPV6_MULTICAST_LOOP *** */
+#if defined(IPV6_MULTICAST_LOOP)
+ tmp = MKT2(env, esock_atom_multicast_loop, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_multicast_loop, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_PORTRANGE => IPV6_PORTRANGE *** */
+ tmp = MKT2(env, esock_atom_portrange, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_PKTOPTIONS => IPV6_PKTOPTIONS *** */
+ tmp = MKT2(env, esock_atom_pktoptions, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_RECVERR => IPV6_RECVERR *** */
+#if defined(IPV6_RECVERR)
+ tmp = MKT2(env, esock_atom_recverr, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_recverr, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_RECVPKTINFO => IPV6_RECVPKTINFO *** */
+#if defined(IPV6_RECVPKTINFO)
+ tmp = MKT2(env, esock_atom_recvpktinfo, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_recvpktinfo, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_RECVTCLASS => IPV6_RECVTCLASS *** */
+ tmp = MKT2(env, esock_atom_recvtclass, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_ROUTER_ALERT => IPV6_ROUTER_ALERT *** */
+#if defined(IPV6_ROUTER_ALERT)
+ tmp = MKT2(env, esock_atom_router_alert, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_router_alert, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_RTHDR => IPV6_RTHDR *** */
+#if defined(IPV6_RTHDR)
+ tmp = MKT2(env, esock_atom_rthdr, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_rthdr, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_TCLASS => IPV6_TCLASS *** */
+ tmp = MKT2(env, esock_atom_tclass, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_UNICAST_HOPS => IPV6_UNICAST_HOPS *** */
+#if defined(IPV6_UNICAST_HOPS)
+ tmp = MKT2(env, esock_atom_unicast_hops, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_unicast_hops, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_USE_MIN_MTU => IPV6_USE_MIN_MTU *** */
+ tmp = MKT2(env, esock_atom_use_min_mtu, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_IPV6_V6ONLY => IPV6_V6ONLY *** */
+#if defined(IPV6_V6ONLY)
+ tmp = MKT2(env, esock_atom_v6only, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_v6only, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ TARRAY_TOLIST(opts, env, &optsL);
+
+ return optsL;
+}
+#endif
+
+
+
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nsupports_options_tcp(ErlNifEnv* env)
+{
+ SocketTArray opts = TARRAY_CREATE(32);
+ ERL_NIF_TERM tmp, optsL;
+
+
+ /* *** SOCKET_OPT_TCP_CONGESTION => TCP_CONGESTION *** */
+#if defined(TCP_CONGESTION)
+ tmp = MKT2(env, esock_atom_congestion, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_congestion, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_TCP_CORK => TCP_CORK *** */
+#if defined(TCP_CORK)
+ tmp = MKT2(env, esock_atom_cork, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_cork, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_TCP_INFO => TCP_INFO *** */
+ tmp = MKT2(env, esock_atom_info, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_TCP_KEEPCNT => TCP_KEEPCNT *** */
+ tmp = MKT2(env, esock_atom_keepcnt, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_TCP_KEEPIDLE => TCP_KEEPIDLE *** */
+ tmp = MKT2(env, esock_atom_keepidle, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_TCP_KEEPINTVL => TCP_KEEPINTVL *** */
+ tmp = MKT2(env, esock_atom_keepintvl, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_TCP_MAXSEG => TCP_MAXSEG *** */
+#if defined(TCP_)
+ tmp = MKT2(env, esock_atom_maxseg, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_maxseg, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_TCP_MD5SIG => TCP_MD5SIG *** */
+ tmp = MKT2(env, esock_atom_md5sig, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_TCP_NODELAY => TCP_NODELAY *** */
+#if defined(TCP_)
+ tmp = MKT2(env, esock_atom_nodelay, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_nodelay, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_TCP_NOOPT => TCP_NOOPT *** */
+ tmp = MKT2(env, esock_atom_noopt, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_TCP_NOPUSH => TCP_NOPUSH *** */
+ tmp = MKT2(env, esock_atom_nopush, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_TCP_SYNCNT => TCP_SYNCNT *** */
+ tmp = MKT2(env, esock_atom_syncnt, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_TCP_USER_TIMEOUT => TCP_USER_TIMEOUT *** */
+ tmp = MKT2(env, esock_atom_user_timeout, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ TARRAY_TOLIST(opts, env, &optsL);
+
+ return optsL;
+}
+#endif
+
+
+
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nsupports_options_udp(ErlNifEnv* env)
+{
+ SocketTArray opts = TARRAY_CREATE(8);
+ ERL_NIF_TERM tmp, optsL;
+
+
+ /* *** SOCKET_OPT_UDP_CORK => UDP_CORK *** */
+#if defined(UDP_CORK)
+ tmp = MKT2(env, esock_atom_cork, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_cork, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ TARRAY_TOLIST(opts, env, &optsL);
+
+ return optsL;
+}
+#endif
+
+
+
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nsupports_options_sctp(ErlNifEnv* env)
+{
+ SocketTArray opts = TARRAY_CREATE(64);
+ ERL_NIF_TERM tmp, optsL;
+
+
+ /* *** SOCKET_OPT_SCTP_ADAPTION_LAYER => SCTP_ADAPTION_LAYER *** */
+ tmp = MKT2(env, esock_atom_adaption_layer, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_ASSOCINFO => SCTP_ASSOCINFO *** */
+#if defined(SCTP_ASSOCINFO)
+ tmp = MKT2(env, esock_atom_associnfo, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_associnfo, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_AUTH_ACTIVE_KEY => SCTP_AUTH_ACTIVE_KEY *** */
+ tmp = MKT2(env, esock_atom_auth_active_key, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_AUTH_ASCONF => SCTP_AUTH_ASCONF *** */
+ tmp = MKT2(env, esock_atom_auth_asconf, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_AUTH_CHUNK => SCTP_AUTH_CHUNK *** */
+ tmp = MKT2(env, esock_atom_auth_chunk, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_AUTH_DELETE_KEY => SCTP_AUTH_DELETE_KEY *** */
+ tmp = MKT2(env, esock_atom_auth_delete_key, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_AUTH_KEY => SCTP_AUTH_KEY *** */
+ tmp = MKT2(env, esock_atom_auth_key, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_AUTOCLOSE => SCTP_AUTOCLOSE *** */
+#if defined(SCTP_AUTOCLOSE)
+ tmp = MKT2(env, esock_atom_autoclose, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_autoclose, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_CONTEXT => SCTP_CONTEXT *** */
+ tmp = MKT2(env, esock_atom_context, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_DEFAULT_SEND_PARAMS => SCTP_DEFAULT_SEND_PARAMS *** */
+ tmp = MKT2(env, esock_atom_default_send_params, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_DELAYED_ACK_TIME => SCTP_DELAYED_ACK_TIME *** */
+ tmp = MKT2(env, esock_atom_delayed_ack_time, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_DISABLE_FRAGMENTS => SCTP_DISABLE_FRAGMENTS *** */
+#if defined(SCTP_DISABLE_FRAGMENTS)
+ tmp = MKT2(env, esock_atom_disable_fragments, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_disable_fragments, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_HMAC_IDENT => SCTP_HMAC_IDENT *** */
+ tmp = MKT2(env, esock_atom_hmac_ident, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_EVENTS => SCTP_EVENTS *** */
+#if defined(SCTP_EVENTS)
+ tmp = MKT2(env, esock_atom_events, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_events, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_EXPLICIT_EOR => SCTP_EXPLICIT_EOR *** */
+ tmp = MKT2(env, esock_atom_explicit_eor, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_FRAGMENT_INTERLEAVE => SCTP_FRAGMENT_INTERLEAVE *** */
+ tmp = MKT2(env, esock_atom_fragment_interleave, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_GET_PEER_ADDR_INFO => SCTP_GET_PEER_ADDR_INFO *** */
+ tmp = MKT2(env, esock_atom_get_peer_addr_info, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_INITMSG => SCTP_INITMSG *** */
+#if defined(SCTP_INITMSG)
+ tmp = MKT2(env, esock_atom_initmsg, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_initmsg, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_I_WANT_MAPPED_V4_ADDR => SCTP_I_WANT_MAPPED_V4_ADDR *** */
+ tmp = MKT2(env, esock_atom_i_want_mapped_v4_addr, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_LOCAL_AUTH_CHUNKS => SCTP_LOCAL_AUTH_CHUNKS *** */
+ tmp = MKT2(env, esock_atom_local_auth_chunks, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_MAXSEG => SCTP_MAXSEG *** */
+#if defined(SCTP_MAXSEG)
+ tmp = MKT2(env, esock_atom_maxseg, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_maxseg, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_MAXBURST => SCTP_MAXBURST *** */
+ tmp = MKT2(env, esock_atom_maxburst, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_NODELAY => SCTP_NODELAY *** */
+#if defined(SCTP_NODELAY)
+ tmp = MKT2(env, esock_atom_nodelay, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_nodelay, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_PARTIAL_DELIVERY_POINT => SCTP_PARTIAL_DELIVERY_POINT *** */
+ tmp = MKT2(env, esock_atom_partial_delivery_point, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_PEER_ADDR_PARAMS => SCTP_PEER_ADDR_PARAMS *** */
+ tmp = MKT2(env, esock_atom_peer_addr_params, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_PEER_AUTH_CHUNKS => SCTP_PEER_AUTH_CHUNKS *** */
+ tmp = MKT2(env, esock_atom_peer_auth_chunks, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_PRIMARY_ADDR => SCTP_PRIMARY_ADDR *** */
+ tmp = MKT2(env, esock_atom_primary_addr, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_RESET_STREAMS => SCTP_RESET_STREAMS *** */
+ tmp = MKT2(env, esock_atom_reset_streams, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_RTOINFO => SCTP_RTOINFO *** */
+#if defined(SCTP_RTOINFO)
+ tmp = MKT2(env, esock_atom_rtoinfo, esock_atom_true);
+#else
+ tmp = MKT2(env, esock_atom_rtoinfo, esock_atom_false);
+#endif
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_SET_PEER_PRIMARY_ADDR => SCTP_SET_PEER_PRIMARY_ADDR *** */
+ tmp = MKT2(env, esock_atom_set_peer_primary_addr, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_STATUS => SCTP_STATUS *** */
+ tmp = MKT2(env, esock_atom_status, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ /* *** SOCKET_OPT_SCTP_USE_EXT_RECVINFO => SCTP_USE_EXT_RECVINFO *** */
+ tmp = MKT2(env, esock_atom_use_ext_recvinfo, esock_atom_false);
+ TARRAY_ADD(opts, tmp);
+
+
+ TARRAY_TOLIST(opts, env, &optsL);
+
+ return optsL;
+}
+#endif
+
+
+
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nsupports_sctp(ErlNifEnv* env)
+{
+ ERL_NIF_TERM supports;
+
+#if defined(HAVE_SCTP)
+ supports = esock_atom_true;
+#else
+ supports = esock_atom_false;
+#endif
+
+ return supports;
+}
+#endif
+
+
+
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nsupports_ipv6(ErlNifEnv* env)
+{
+ ERL_NIF_TERM supports;
+
+ /* Is this (test) really sufficient for testing if we support IPv6? */
+#if defined(HAVE_IPV6)
+ supports = esock_atom_true;
+#else
+ supports = esock_atom_false;
+#endif
+
+ return supports;
+}
+#endif
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_open
+ *
+ * Description:
+ * Create an endpoint for communication.
+ *
+ * Arguments:
+ * Domain - The domain, for example 'inet'
+ * Type - Type of socket, for example 'stream'
+ * Protocol - The protocol, for example 'tcp'
+ * Extra - A map with "obscure" options.
+ * Currently the only allowed option is netns (network namespace).
+ * This is *only* allowed on linux!
+ * We sould also use this for the fd value, in case we should use
+ * an already existing (file) descriptor.
+ */
+static
+ERL_NIF_TERM nif_open(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ int edomain, etype, eproto;
+ int domain, type, proto;
+ char* netns;
+ ERL_NIF_TERM emap;
+ ERL_NIF_TERM result;
+
+ SGDBG( ("SOCKET", "nif_open -> entry with %d args\r\n", argc) );
+
+ /* Extract arguments and perform preliminary validation */
+
+ if ((argc != 4) ||
+ !GET_INT(env, argv[0], &edomain) ||
+ !GET_INT(env, argv[1], &etype) ||
+ !IS_MAP(env, argv[3])) {
+ return enif_make_badarg(env);
+ }
+ eproto = argv[2];
+ emap = argv[3];
+
+ SGDBG( ("SOCKET", "nif_open -> "
+ "\r\n edomain: %T"
+ "\r\n etype: %T"
+ "\r\n eproto: %T"
+ "\r\n extra: %T"
+ "\r\n", argv[0], argv[1], eproto, emap) );
+
+ if (!edomain2domain(edomain, &domain)) {
+ SGDBG( ("SOCKET", "nif_open -> invalid domain: %d\r\n", edomain) );
+ return esock_make_error(env, esock_atom_einval);
+ }
+
+ if (!etype2type(etype, &type)) {
+ SGDBG( ("SOCKET", "nif_open -> invalid type: %d\r\n", etype) );
+ return esock_make_error(env, esock_atom_einval);
+ }
+
+ if (!eproto2proto(env, eproto, &proto)) {
+ SGDBG( ("SOCKET", "nif_open -> invalid protocol: %d\r\n", eproto) );
+ return esock_make_error(env, esock_atom_einval);
+ }
+
+#ifdef HAVE_SETNS
+ /* We *currently* only support one extra option: netns */
+ if (!emap2netns(env, emap, &netns)) {
+ SGDBG( ("SOCKET", "nif_open -> namespace: %s\r\n", netns) );
+ return enif_make_badarg(env);
+ }
+#else
+ netns = NULL;
+#endif
+
+
+ result = nopen(env, domain, type, proto, netns);
+
+ SGDBG( ("SOCKET", "nif_open -> done with result: "
+ "\r\n %T"
+ "\r\n", result) );
+
+ return result;
+
+#endif // if defined(__WIN32__)
+}
+
+
+/* nopen - 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.
+ */
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nopen(ErlNifEnv* env,
+ int domain, int type, int protocol,
+ char* netns)
+{
+ SocketDescriptor* descP;
+ ERL_NIF_TERM res;
+ int save_errno = 0;
+ SOCKET sock;
+ HANDLE event;
+#ifdef HAVE_SETNS
+ int current_ns = 0;
+#endif
+
+ SGDBG( ("SOCKET", "nopen -> entry with"
+ "\r\n domain: %d"
+ "\r\n type: %d"
+ "\r\n protocol: %d"
+ "\r\n netns: %s"
+ "\r\n", domain, type, protocol, ((netns == NULL) ? "NULL" : netns)) );
+
+#ifdef HAVE_SETNS
+ if ((netns != NULL) &&
+ !change_network_namespace(netns, &current_ns, &save_errno))
+ return esock_make_error_errno(env, save_errno);
+#endif
+
+ if ((sock = sock_open(domain, type, protocol)) == INVALID_SOCKET)
+ return esock_make_error_errno(env, sock_errno());
+
+ SGDBG( ("SOCKET", "nopen -> open success: %d\r\n", sock) );
+
+#ifdef HAVE_SETNS
+ if ((netns != NULL) &&
+ !restore_network_namespace(current_ns, sock, &save_errno))
+ return esock_make_error_errno(env, save_errno);
+
+ if (netns != NULL)
+ FREE(netns);
+#endif
+
+
+ if ((event = sock_create_event(sock)) == INVALID_EVENT) {
+ save_errno = sock_errno();
+ while ((sock_close(sock) == INVALID_SOCKET) && (sock_errno() == EINTR));
+ return esock_make_error_errno(env, save_errno);
+ }
+
+ SGDBG( ("SOCKET", "nopen -> event success: %d\r\n", event) );
+
+ SET_NONBLOCKING(sock);
+
+
+ /* Create and initiate the socket "descriptor" */
+ if ((descP = alloc_descriptor(sock, event)) == NULL) {
+ sock_close(sock);
+ // Not sure if this is really the proper error, but...
+ return enif_make_badarg(env);
+ }
+
+ descP->state = SOCKET_STATE_OPEN;
+ descP->domain = domain;
+ descP->type = type;
+ descP->protocol = protocol;
+
+ /* Does this apply to other types? Such as RAW?
+ * Also, is this really correct? Should we not wait for bind?
+ */
+ if (type == SOCK_DGRAM) {
+ descP->isReadable = TRUE;
+ descP->isWritable = TRUE;
+ }
+
+ /*
+ * Should we keep track of sockets (resources) in some way?
+ * Doing it here will require mutex to ensure data integrity,
+ * which will be costly. Send it somewhere?
+ */
+ res = enif_make_resource(env, descP);
+ enif_release_resource(descP);
+
+ /* 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...
+ */
+ if (enif_self(env, &descP->ctrlPid) == NULL)
+ return esock_make_error(env, atom_exself);
+
+ if (MONP("nopen -> ctrl",
+ env, descP,
+ &descP->ctrlPid,
+ &descP->ctrlMon) != 0)
+ return esock_make_error(env, atom_exmon);
+
+
+ inc_socket(domain, type, protocol);
+
+ return esock_make_ok2(env, res);
+}
+#endif // if !defined(__WIN32__)
+
+
+
+#ifdef HAVE_SETNS
+/* We should really have another API, so that we can return errno... */
+
+/* *** change network namespace ***
+ * Retreive the current namespace and set the new.
+ * Return result and previous namespace if successfull.
+ */
+#if !defined(__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", netns) );
+
+ if (netns != NULL) {
+ current_ns = open("/proc/self/ns/net", O_RDONLY);
+ if (current_ns == INVALID_SOCKET) {
+ *cns = current_ns;
+ *err = sock_errno();
+ return FALSE;
+ }
+ new_ns = open(netns, O_RDONLY);
+ if (new_ns == INVALID_SOCKET) {
+ save_errno = sock_errno();
+ while (close(current_ns) == INVALID_SOCKET &&
+ sock_errno() == EINTR);
+ *cns = -1;
+ *err = save_errno;
+ return FALSE;
+ }
+ if (setns(new_ns, CLONE_NEWNET) != 0) {
+ save_errno = sock_errno();
+ while ((close(new_ns) == INVALID_SOCKET) &&
+ (sock_errno() == EINTR));
+ while ((close(current_ns) == INVALID_SOCKET) &&
+ (sock_errno() == EINTR));
+ *cns = -1;
+ *err = save_errno;
+ return FALSE;
+ } else {
+ while ((close(new_ns) == INVALID_SOCKET) &&
+ (sock_errno() == EINTR));
+ *cns = current_ns;
+ *err = 0;
+ return TRUE;
+ }
+ } else {
+ *cns = INVALID_SOCKET;
+ *err = 0;
+ return TRUE;
+ }
+}
+#endif // if !defined(__WIN32__)
+
+
+/* *** restore network namespace ***
+ * Restore the previous namespace (see above).
+ */
+#if !defined(__WIN32__)
+static
+BOOLEAN_T restore_network_namespace(int ns, SOCKET sock, int* err)
+{
+ int save_errno;
+
+ SGDBG( ("SOCKET", "restore_network_namespace -> entry with"
+ "\r\n ns: %d", ns) );
+
+ if (ns != INVALID_SOCKET) {
+ 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?
+ */
+ if (sock != INVALID_SOCKET)
+ save_errno = sock_errno();
+ while (close(sock) == INVALID_SOCKET &&
+ sock_errno() == EINTR);
+ sock = INVALID_SOCKET;
+ while (close(ns) == INVALID_SOCKET &&
+ sock_errno() == EINTR);
+ *err = save_errno;
+ return FALSE;
+ } else {
+ while (close(ns) == INVALID_SOCKET &&
+ sock_errno() == EINTR);
+ *err = 0;
+ return TRUE;
+ }
+ }
+
+ *err = 0;
+ return TRUE;
+}
+#endif // if !defined(__WIN32__)
+#endif // ifdef HAVE_SETNS
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_bind
+ *
+ * Description:
+ * Bind a name to a socket.
+ *
+ * Arguments:
+ * [0] Socket (ref) - Points to the socket descriptor.
+ * [1] LocalAddr - Local address is a sockaddr map ( socket:sockaddr() ).
+ */
+static
+ERL_NIF_TERM nif_bind(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ SocketDescriptor* descP;
+ ERL_NIF_TERM eSockAddr;
+ SocketAddress sockAddr;
+ unsigned int addrLen;
+ char* xres;
+
+ SGDBG( ("SOCKET", "nif_bind -> entry with argc: %d\r\n", argc) );
+
+ /* Extract arguments and perform preliminary validation */
+
+ if ((argc != 2) ||
+ !enif_get_resource(env, argv[0], sockets, (void**) &descP)) {
+ return enif_make_badarg(env);
+ }
+ eSockAddr = argv[1];
+
+ if (IS_CLOSED(descP) || IS_CLOSING(descP))
+ return esock_make_error(env, atom_closed);
+
+ SSDBG( descP,
+ ("SOCKET", "nif_bind -> args when sock = %d (0x%lX)"
+ "\r\n Socket: %T"
+ "\r\n SockAddr: %T"
+ "\r\n", descP->sock, descP->state, argv[0], eSockAddr) );
+
+ /* Make sure we are ready
+ * Not sure how this would even happen, but...
+ */
+ if (descP->state != SOCKET_STATE_OPEN)
+ return esock_make_error(env, atom_exbadstate);
+
+ if ((xres = esock_decode_sockaddr(env, eSockAddr, &sockAddr, &addrLen)) != NULL)
+ return esock_make_error_str(env, xres);
+
+ return nbind(env, descP, &sockAddr, addrLen);
+
+#endif // if defined(__WIN32__)
+}
+
+
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nbind(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ SocketAddress* sockAddrP,
+ unsigned int addrLen)
+{
+ int port, ntohs_port;
+
+ SSDBG( descP, ("SOCKET", "nbind -> try bind\r\n") );
+
+ if (IS_SOCKET_ERROR(sock_bind(descP->sock,
+ (struct sockaddr*) sockAddrP, addrLen))) {
+ return esock_make_error_errno(env, sock_errno());
+ }
+
+ SSDBG( descP, ("SOCKET", "nbind -> bound - get port\r\n") );
+
+ port = which_address_port(sockAddrP);
+ SSDBG( descP, ("SOCKET", "nbind -> port: %d\r\n", port) );
+ if (port == 0) {
+ SOCKLEN_T len = sizeof(SocketAddress);
+ sys_memzero((char *) sockAddrP, len);
+ sock_name(descP->sock, &sockAddrP->sa, &len);
+ port = which_address_port(sockAddrP);
+ } else if (port == -1) {
+ port = 0;
+ }
+
+ ntohs_port = sock_ntohs(port);
+
+ SSDBG( descP, ("SOCKET", "nbind -> done with port = %d\r\n", ntohs_port) );
+
+ return esock_make_ok2(env, MKI(env, ntohs_port));
+
+}
+#endif // if !defined(__WIN32__)
+
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_connect
+ *
+ * Description:
+ * Initiate a connection on a socket
+ *
+ * Arguments:
+ * Socket (ref) - Points to the socket descriptor.
+ * SockAddr - Socket Address of "remote" host.
+ * This is sockaddr(), which is either
+ * sockaddr_in4 or sockaddr_in6.
+ */
+static
+ERL_NIF_TERM nif_connect(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ SocketDescriptor* descP;
+ ERL_NIF_TERM res, eSockAddr;
+ char* xres;
+
+ SGDBG( ("SOCKET", "nif_connect -> entry with argc: %d\r\n", argc) );
+
+ /* Extract arguments and perform preliminary validation */
+
+ if ((argc != 2) ||
+ !enif_get_resource(env, argv[0], sockets, (void**) &descP)) {
+ return enif_make_badarg(env);
+ }
+ eSockAddr = argv[1];
+
+ SSDBG( descP,
+ ("SOCKET", "nif_connect -> args when sock = %d:"
+ "\r\n Socket: %T"
+ "\r\n SockAddr: %T"
+ "\r\n", descP->sock, argv[0], eSockAddr) );
+
+ if ((xres = esock_decode_sockaddr(env, eSockAddr,
+ &descP->remote, &descP->addrLen)) != NULL) {
+ return esock_make_error_str(env, xres);
+ }
+
+
+ MLOCK(descP->readMtx);
+ MLOCK(descP->writeMtx);
+
+ res = nconnect(env, descP);
+
+ MUNLOCK(descP->writeMtx);
+ MUNLOCK(descP->readMtx);
+
+ return res;
+
+#endif // if !defined(__WIN32__)
+}
+
+
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nconnect(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ ERL_NIF_TERM res, ref;
+ int code, sres, save_errno = 0;
+
+ /*
+ * Verify that we are where in the proper state
+ */
+
+ if (IS_CLOSED(descP) || IS_CLOSING(descP))
+ return esock_make_error(env, atom_closed);
+
+ if (!IS_OPEN(descP)) {
+ SSDBG( descP, ("SOCKET", "nif_connect -> not open\r\n") );
+ return esock_make_error(env, atom_exbadstate);
+ }
+
+ if (IS_CONNECTED(descP)) {
+ SSDBG( descP, ("SOCKET", "nif_connect -> already connected\r\n") );
+ return esock_make_error(env, atom_eisconn);
+ }
+
+ if (IS_CONNECTING(descP)) {
+ SSDBG( descP, ("SOCKET", "nif_connect -> already connecting\r\n") );
+ return esock_make_error(env, esock_atom_einval);
+ }
+
+
+ /*
+ * And attempt to connect
+ */
+
+ code = sock_connect(descP->sock,
+ (struct sockaddr*) &descP->remote,
+ descP->addrLen);
+ save_errno = sock_errno();
+
+ SSDBG( descP, ("SOCKET", "nif_connect -> connect result: %d, %d\r\n",
+ code, save_errno) );
+
+ if (IS_SOCKET_ERROR(code) &&
+ ((save_errno == ERRNO_BLOCK) || /* Winsock2 */
+ (save_errno == EINPROGRESS))) { /* Unix & OSE!! */
+ ref = MKREF(env);
+ descP->state = SOCKET_STATE_CONNECTING;
+ if ((sres = esock_select_write(env, descP->sock, descP, NULL, ref)) < 0) {
+ res = esock_make_error(env,
+ MKT2(env,
+ esock_atom_select_failed,
+ MKI(env, sres)));
+ } else {
+ res = esock_make_ok2(env, ref);
+ }
+ } else if (code == 0) { /* ok we are connected */
+
+ descP->state = SOCKET_STATE_CONNECTED;
+ descP->isReadable = TRUE;
+ descP->isWritable = TRUE;
+
+ res = esock_atom_ok;
+ } else {
+ res = esock_make_error_errno(env, save_errno);
+ }
+
+ return res;
+
+}
+#endif // if !defined(__WIN32__)
+
+
+/* ----------------------------------------------------------------------
+ * nif_finalize_connection
+ *
+ * Description:
+ * Make socket ready for input and output.
+ *
+ * Arguments:
+ * Socket (ref) - Points to the socket descriptor.
+ */
+static
+ERL_NIF_TERM nif_finalize_connection(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ SocketDescriptor* descP;
+
+ /* Extract arguments and perform preliminary validation */
+
+ if ((argc != 1) ||
+ !enif_get_resource(env, argv[0], sockets, (void**) &descP)) {
+ return enif_make_badarg(env);
+ }
+
+ return nfinalize_connection(env, descP);
+
+#endif
+}
+
+
+/* *** nfinalize_connection ***
+ * Perform the final check to verify a connection.
+ */
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nfinalize_connection(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ int error;
+
+ if (descP->state != SOCKET_STATE_CONNECTING)
+ return esock_make_error(env, atom_enotconn);
+
+ if (!verify_is_connected(descP, &error)) {
+ descP->state = SOCKET_STATE_OPEN; /* restore state */
+ return esock_make_error_errno(env, error);
+ }
+
+ descP->state = SOCKET_STATE_CONNECTED;
+ descP->isReadable = TRUE;
+ descP->isWritable = TRUE;
+
+ return esock_atom_ok;
+}
+#endif
+
+
+/* *** verify_is_connected ***
+ * Check if a connection has been established.
+ */
+#if !defined(__WIN32__)
+static
+BOOLEAN_T verify_is_connected(SocketDescriptor* 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", W.R.Stevens, p 412 for a
+ * discussion about Unix portability and non blocking connect.
+ */
+
+#ifndef SO_ERROR
+
+ int sz, code;
+
+ sz = sizeof(descP->remote);
+ sys_memzero((char *) &descP->remote, sz);
+ code = sock_peer(desc->sock,
+ (struct sockaddr*) &descP->remote, &sz);
+
+ if (IS_SOCKET_ERROR(code)) {
+ *err = sock_errno();
+ return FALSE;
+ }
+
+#else
+
+ int error = 0; /* Has to be initiated, we check it */
+ unsigned int sz = sizeof(error); /* even if we get -1 */
+ int code = sock_getopt(descP->sock,
+ SOL_SOCKET, SO_ERROR,
+ (void *)&error, &sz);
+
+ if ((code < 0) || error) {
+ *err = error;
+ return FALSE;
+ }
+
+#endif /* SO_ERROR */
+
+ *err = 0;
+
+ return TRUE;
+}
+#endif
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_listen
+ *
+ * Description:
+ * Listen for connections on a socket.
+ *
+ * Arguments:
+ * Socket (ref) - Points to the socket descriptor.
+ * Backlog - The maximum length to which the queue of pending
+ * connections for socket may grow.
+ */
+static
+ERL_NIF_TERM nif_listen(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ SocketDescriptor* descP;
+ int backlog;
+
+ SGDBG( ("SOCKET", "nif_listen -> entry with argc: %d\r\n", argc) );
+
+ /* Extract arguments and perform preliminary validation */
+
+ if ((argc != 2) ||
+ !enif_get_resource(env, argv[0], sockets, (void**) &descP) ||
+ !GET_INT(env, argv[1], &backlog)) {
+ return enif_make_badarg(env);
+ }
+
+ SSDBG( descP,
+ ("SOCKET", "nif_listen -> args when sock = %d:"
+ "\r\n Socket: %T"
+ "\r\n backlog: %d"
+ "\r\n", descP->sock, argv[0], backlog) );
+
+ return nlisten(env, descP, backlog);
+
+#endif // if defined(__WIN32__)
+}
+
+
+
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nlisten(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int backlog)
+{
+
+ /*
+ * Verify that we are where in the proper state
+ */
+
+ if (IS_CLOSED(descP) || IS_CLOSING(descP))
+ return esock_make_error(env, atom_closed);
+
+ if (descP->state == SOCKET_STATE_CLOSED)
+ return esock_make_error(env, atom_exbadstate);
+
+ if (!IS_OPEN(descP))
+ return esock_make_error(env, atom_exbadstate);
+
+
+ /*
+ * And attempt to make socket listening
+ */
+
+ if (IS_SOCKET_ERROR(sock_listen(descP->sock, backlog)))
+ return esock_make_error_errno(env, sock_errno());
+
+ descP->state = SOCKET_STATE_LISTENING;
+
+ return esock_atom_ok;
+
+}
+#endif // if !defined(__WIN32__)
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_accept
+ *
+ * Description:
+ * Accept a connection on a socket.
+ *
+ * Arguments:
+ * Socket (ref) - Points to the socket descriptor.
+ * Request ref - Unique "id" of this request
+ * (used for the select, if none is in queue).
+ */
+static
+ERL_NIF_TERM nif_accept(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ SocketDescriptor* descP;
+ ERL_NIF_TERM ref, res;
+
+ SGDBG( ("SOCKET", "nif_accept -> entry with argc: %d\r\n", argc) );
+
+ /* Extract arguments and perform preliminary validation */
+
+ if ((argc != 2) ||
+ !enif_get_resource(env, argv[0], sockets, (void**) &descP)) {
+ return enif_make_badarg(env);
+ }
+ ref = argv[1];
+
+ SSDBG( descP,
+ ("SOCKET", "nif_accept -> args when sock = %d:"
+ "\r\n Socket: %T"
+ "\r\n ReqRef: %T"
+ "\r\n", descP->sock, argv[0], ref) );
+
+ MLOCK(descP->accMtx);
+
+ res = naccept(env, descP, ref);
+
+ MUNLOCK(descP->accMtx);
+
+ return res;
+
+#endif // if defined(__WIN32__)
+}
+
+
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM naccept(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM ref)
+{
+ ERL_NIF_TERM res;
+
+ if (IS_CLOSED(descP) || IS_CLOSING(descP))
+ return esock_make_error(env, atom_closed);
+
+ switch (descP->state) {
+ case SOCKET_STATE_LISTENING:
+ res = naccept_listening(env, descP, ref);
+ break;
+
+ case SOCKET_STATE_ACCEPTING:
+ res = naccept_accepting(env, descP, ref);
+ break;
+
+ default:
+ res = esock_make_error(env, esock_atom_einval);
+ break;
+ }
+
+ return res;
+}
+#endif // if !defined(__WIN32__)
+
+
+/* *** naccept_listening ***
+ * We have no active acceptor (and no acceptors in queue).
+ */
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM naccept_listening(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM ref)
+{
+ SocketAddress remote;
+ unsigned int n;
+ SOCKET accSock;
+ int save_errno;
+ ErlNifPid caller;
+ ERL_NIF_TERM res;
+
+ SSDBG( descP, ("SOCKET", "naccept_listening -> get caller\r\n") );
+
+ if (enif_self(env, &caller) == NULL)
+ return esock_make_error(env, atom_exself);
+
+ n = sizeof(remote);
+ sys_memzero((char *) &remote, n);
+ SSDBG( descP, ("SOCKET", "naccept_listening -> try accept\r\n") );
+ accSock = sock_accept(descP->sock, (struct sockaddr*) &remote, &n);
+ if (accSock == INVALID_SOCKET) {
+
+ save_errno = sock_errno();
+
+ SSDBG( descP,
+ ("SOCKET",
+ "naccept_listening -> accept failed (%d)\r\n", save_errno) );
+
+ res = naccept_listening_error(env, descP, ref, caller, save_errno);
+
+ } else {
+
+ /*
+ * We got one
+ */
+
+ SSDBG( descP, ("SOCKET", "naccept_listening -> success\r\n") );
+
+ res = naccept_listening_accept(env, descP, accSock, caller, &remote);
+
+ }
+
+ return res;
+}
+
+
+/* *** naccept_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 naccept_listening_error(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM ref,
+ ErlNifPid caller,
+ int save_errno)
+{
+ ERL_NIF_TERM res;
+
+ if (save_errno == ERRNO_BLOCK) {
+
+ /* *** Try again later *** */
+ SSDBG( descP, ("SOCKET", "naccept_listening_error -> would block\r\n") );
+
+ descP->currentAcceptor.pid = caller;
+ if (MONP("naccept_listening -> current acceptor",
+ env, descP,
+ &descP->currentAcceptor.pid,
+ &descP->currentAcceptor.mon) != 0)
+ return esock_make_error(env, atom_exmon);
+
+ descP->currentAcceptor.ref = enif_make_copy(descP->env, ref);
+ descP->currentAcceptorP = &descP->currentAcceptor;
+
+ res = naccept_busy_retry(env, descP, ref, NULL, SOCKET_STATE_ACCEPTING);
+
+
+ } else {
+ SSDBG( descP,
+ ("SOCKET",
+ "naccept_listening -> errno: %d\r\n", save_errno) );
+ res = esock_make_error_errno(env, save_errno);
+ }
+
+ return res;
+}
+
+
+/* *** naccept_listening_accept ***
+ * The accept call was successful (accepted) - handle the new connection.
+ */
+static
+ERL_NIF_TERM naccept_listening_accept(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ SOCKET accSock,
+ ErlNifPid caller,
+ SocketAddress* remote)
+{
+ ERL_NIF_TERM res;
+
+ naccept_accepted(env, descP, accSock, caller, remote, &res);
+
+ return res;
+}
+#endif // if !defined(__WIN32__)
+
+
+
+/* *** naccept_accepting ***
+ * 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.
+ */
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM naccept_accepting(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM ref)
+{
+ ErlNifPid caller;
+ ERL_NIF_TERM res;
+
+ SSDBG( descP, ("SOCKET", "naccept_accepting -> get caller\r\n") );
+
+ if (enif_self(env, &caller) == NULL)
+ return esock_make_error(env, atom_exself);
+
+ SSDBG( descP, ("SOCKET", "naccept_accepting -> check: "
+ "are caller current acceptor:"
+ "\r\n Caller: %T"
+ "\r\n Current: %T"
+ "\r\n", caller, descP->currentAcceptor.pid) );
+
+
+
+
+ if (compare_pids(env, &descP->currentAcceptor.pid, &caller)) {
+
+ SSDBG( descP,
+ ("SOCKET", "naccept_accepting -> current acceptor\r\n") );
+
+ res = naccept_accepting_current(env, descP, ref);
+
+ } else {
+
+ /* Not the "current acceptor", so (maybe) push onto queue */
+
+ SSDBG( descP,
+ ("SOCKET", "naccept_accepting -> *not* current acceptor\r\n") );
+
+ res = naccept_accepting_other(env, descP, ref, caller);
+
+ }
+
+ return res;
+
+}
+
+
+
+/* *** naccept_accepting_current ***
+ * Handles when the current acceptor makes another attempt.
+ */
+static
+ERL_NIF_TERM naccept_accepting_current(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM ref)
+{
+ SocketAddress remote;
+ unsigned int n;
+ SOCKET accSock;
+ int save_errno;
+ ERL_NIF_TERM res;
+
+ SSDBG( descP, ("SOCKET", "naccept_accepting_current -> try accept\r\n") );
+ n = sizeof(descP->remote);
+ sys_memzero((char *) &remote, n);
+ accSock = sock_accept(descP->sock, (struct sockaddr*) &remote, &n);
+ if (accSock == INVALID_SOCKET) {
+
+ save_errno = sock_errno();
+
+ SSDBG( descP,
+ ("SOCKET",
+ "naccept_accepting_current -> accept failed: %d\r\n",
+ save_errno) );
+
+ res = naccept_accepting_current_error(env, descP, ref, save_errno);
+
+ } else {
+
+ SSDBG( descP, ("SOCKET", "naccept_accepting_current -> accepted\r\n") );
+
+ res = naccept_accepting_current_accept(env, descP, accSock, &remote);
+
+ }
+
+ return res;
+}
+
+
+/* *** naccept_accepting_current_accept ***
+ * Handles when the current acceptor succeeded in its accept call -
+ * handle the new connection.
+ */
+static
+ERL_NIF_TERM naccept_accepting_current_accept(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ SOCKET accSock,
+ SocketAddress* remote)
+{
+ int sres;
+ ERL_NIF_TERM res;
+
+ if (naccept_accepted(env, descP, accSock,
+ descP->currentAcceptor.pid, remote, &res)) {
+
+ /* We should really go through the queue until we succeed to activate
+ * a waiting acceptor. For now we just pop once and hope for the best...
+ * This will leave any remaining acceptors *hanging*...
+ *
+ * We need a "activate-next" function.
+ *
+ */
+
+ if (acceptor_pop(env, descP,
+ &descP->currentAcceptor.pid,
+ &descP->currentAcceptor.mon,
+ &descP->currentAcceptor.ref)) {
+
+ /* There was another one */
+
+ SSDBG( descP,
+ ("SOCKET",
+ "naccept_accepting_current_accept -> new (active) acceptor: "
+ "\r\n pid: %T"
+ "\r\n ref: %T"
+ "\r\n",
+ descP->currentAcceptor.pid,
+ descP->currentAcceptor.ref) );
+
+ if ((sres = esock_select_read(env, descP->sock, descP,
+ &descP->currentAcceptor.pid,
+ descP->currentAcceptor.ref)) < 0) {
+ esock_warning_msg("Failed select (%d) for new acceptor "
+ "after current (%T) died\r\n",
+ sres, descP->currentAcceptor.pid);
+ }
+ } else {
+ descP->currentAcceptorP = NULL;
+ descP->state = SOCKET_STATE_LISTENING;
+ }
+ }
+
+ return res;
+}
+
+
+/* *** naccept_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 naccept_accepting_current_error(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM ref,
+ int save_errno)
+{
+ ERL_NIF_TERM res;
+
+ if (save_errno == ERRNO_BLOCK) {
+
+ /*
+ * Just try again, no real error, just a ghost trigger from poll,
+ */
+
+ SSDBG( descP,
+ ("SOCKET",
+ "naccept_accepting_current_error -> would block: try again\r\n") );
+
+ res = naccept_busy_retry(env, descP, ref, &descP->currentAcceptor.pid,
+ /* No state change */
+ descP->state);
+
+ } else {
+ res = esock_make_error_errno(env, save_errno);
+ }
+
+ return res;
+}
+
+
+/* *** naccept_accepting_other ***
+ * Handles when the another acceptor makes an attempt, which
+ * results (maybe) in the request beeing pushed onto the
+ * acceptor queue.
+ */
+static
+ERL_NIF_TERM naccept_accepting_other(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM ref,
+ ErlNifPid caller)
+{
+ ERL_NIF_TERM result;
+
+ if (!acceptor_search4pid(env, descP, &caller)) // Ugh! (&caller)
+ result = acceptor_push(env, descP, caller, ref);
+ else
+ result = esock_make_error(env, esock_atom_eagain);
+
+ return result;
+}
+#endif // if !defined(__WIN32__)
+
+
+
+/* *** naccept_busy_retry ***
+ * Perform a retry select. If successful, set nextState.
+ */
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM naccept_busy_retry(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM ref,
+ ErlNifPid* pid,
+ unsigned int nextState)
+{
+ int sres;
+ ERL_NIF_TERM res, reason;
+
+ if ((sres = esock_select_read(env, descP->sock, descP, pid, ref)) < 0) {
+ reason = MKT2(env, esock_atom_select_failed, MKI(env, sres));
+ res = esock_make_error(env, reason);
+ } else {
+ descP->state = nextState;
+ res = esock_make_error(env, esock_atom_eagain);
+ }
+
+ return res;
+}
+
+
+
+/* *** naccept_accepted ***
+ * Generic function handling a successful accept.
+ */
+static
+BOOLEAN_T naccept_accepted(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ SOCKET accSock,
+ ErlNifPid pid,
+ SocketAddress* remote,
+ ERL_NIF_TERM* result)
+{
+ SocketDescriptor* accDescP;
+ HANDLE accEvent;
+ ERL_NIF_TERM accRef;
+ int save_errno;
+
+ /*
+ * We got one
+ */
+
+ if ((accEvent = sock_create_event(accSock)) == INVALID_EVENT) {
+ save_errno = sock_errno();
+ while ((sock_close(accSock) == INVALID_SOCKET) &&
+ (sock_errno() == EINTR));
+ *result = esock_make_error_errno(env, save_errno);
+ return FALSE;
+ }
+
+ if ((accDescP = alloc_descriptor(accSock, accEvent)) == NULL) {
+ sock_close(accSock);
+ *result = enif_make_badarg(env);
+ return FALSE;
+ }
+
+ accDescP->domain = descP->domain;
+ accDescP->type = descP->type;
+ accDescP->protocol = descP->protocol;
+ accDescP->rBufSz = descP->rBufSz; // Inherit buffer size
+ accDescP->rNum = descP->rNum; // Inherit buffer uses
+ accDescP->rNumCnt = 0;
+ accDescP->rCtrlSz = descP->rCtrlSz; // Inherit buffer siez
+ accDescP->wCtrlSz = descP->wCtrlSz; // Inherit buffer size
+
+ accRef = enif_make_resource(env, accDescP);
+ enif_release_resource(accDescP);
+
+ accDescP->ctrlPid = pid;
+ if (MONP("naccept_accepted -> ctrl",
+ env, accDescP,
+ &accDescP->ctrlPid,
+ &accDescP->ctrlMon) != 0) {
+ sock_close(accSock);
+ *result = esock_make_error(env, atom_exmon);
+ return FALSE;
+ }
+
+ accDescP->remote = *remote;
+ SET_NONBLOCKING(accDescP->sock);
+
+ accDescP->state = SOCKET_STATE_CONNECTED;
+ accDescP->isReadable = TRUE;
+ accDescP->isWritable = TRUE;
+
+ *result = esock_make_ok2(env, accRef);
+
+ return TRUE;
+
+}
+#endif // if !defined(__WIN32__)
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_send
+ *
+ * Description:
+ * Send a message on a socket
+ *
+ * Arguments:
+ * Socket (ref) - Points to the socket descriptor.
+ * SendRef - A unique id for this (send) request.
+ * Data - The data to send in the form of a IOVec.
+ * Flags - Send flags.
+ */
+
+static
+ERL_NIF_TERM nif_send(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ SocketDescriptor* descP;
+ ERL_NIF_TERM sockRef, sendRef;
+ ErlNifBinary sndData;
+ unsigned int eflags;
+ int flags;
+ ERL_NIF_TERM res;
+
+ SGDBG( ("SOCKET", "nif_send -> entry with argc: %d\r\n", argc) );
+
+ /* Extract arguments and perform preliminary validation */
+
+ if ((argc != 4) ||
+ !GET_BIN(env, argv[2], &sndData) ||
+ !GET_UINT(env, argv[3], &eflags)) {
+ return enif_make_badarg(env);
+ }
+ sockRef = argv[0]; // We need this in case we send in case we send abort
+ sendRef = argv[1];
+
+ if (!enif_get_resource(env, sockRef, sockets, (void**) &descP)) {
+ return enif_make_badarg(env);
+ }
+
+ SSDBG( descP,
+ ("SOCKET", "nif_send -> args when sock = %d:"
+ "\r\n Socket: %T"
+ "\r\n SendRef: %T"
+ "\r\n Size of data: %d"
+ "\r\n eFlags: %d"
+ "\r\n", descP->sock, sockRef, sendRef, sndData.size, eflags) );
+
+ if (!esendflags2sendflags(eflags, &flags))
+ return enif_make_badarg(env);
+
+ MLOCK(descP->writeMtx);
+
+ /* We need to handle the case when another process tries
+ * to write at the same time.
+ * If the current write could not write its entire package
+ * this time (resulting in an select). The write of the
+ * other process must be made to wait until current
+ * is done!
+ * Basically, we need a write queue!
+ *
+ * A 'writing' field (boolean), which is set if we did
+ * not manage to write the entire message and reset every
+ * time we do.
+ */
+
+ res = nsend(env, descP, sockRef, sendRef, &sndData, flags);
+
+ MUNLOCK(descP->writeMtx);
+
+ return res;
+#endif // if defined(__WIN32__)
+}
+
+
+
+/* *** nsend ***
+ *
+ * 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).
+ */
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nsend(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ ErlNifBinary* sndDataP,
+ int flags)
+{
+ int save_errno;
+ ssize_t written;
+ ERL_NIF_TERM writerCheck;
+
+ if (!descP->isWritable)
+ return enif_make_badarg(env);
+
+ /* Check if there is already a current writer and if its us */
+ if (!send_check_writer(env, descP, sendRef, &writerCheck))
+ return writerCheck;
+
+ /* We ignore the wrap for the moment.
+ * Maybe we should issue a wrap-message to controlling process...
+ */
+ cnt_inc(&descP->writeTries, 1);
+
+ written = sock_send(descP->sock, sndDataP->data, sndDataP->size, flags);
+ if (IS_SOCKET_ERROR(written))
+ save_errno = sock_errno();
+ else
+ save_errno = -1; // The value does not actually matter in this case
+
+ return send_check_result(env, descP,
+ written, sndDataP->size, save_errno,
+ sockRef, sendRef);
+
+}
+#endif // if !defined(__WIN32__)
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_sendto
+ *
+ * Description:
+ * Send a message on a socket
+ *
+ * Arguments:
+ * Socket (ref) - Points to the socket descriptor.
+ * SendRef - A unique id for this (send) request.
+ * Data - The data to send in the form of a IOVec.
+ * Dest - Destination (socket) address.
+ * Flags - Send flags.
+ */
+
+static
+ERL_NIF_TERM nif_sendto(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ SocketDescriptor* descP;
+ ERL_NIF_TERM sockRef, sendRef;
+ ErlNifBinary sndData;
+ unsigned int eflags;
+ int flags;
+ ERL_NIF_TERM eSockAddr;
+ SocketAddress remoteAddr;
+ unsigned int remoteAddrLen;
+ char* xres;
+ ERL_NIF_TERM res;
+
+ SGDBG( ("SOCKET", "nif_sendto -> entry with argc: %d\r\n", argc) );
+
+ /* Extract arguments and perform preliminary validation */
+
+ if ((argc != 5) ||
+ !enif_get_resource(env, argv[0], sockets, (void**) &descP) ||
+ !GET_BIN(env, argv[2], &sndData) ||
+ !GET_UINT(env, argv[4], &eflags)) {
+ return enif_make_badarg(env);
+ }
+ sockRef = argv[0]; // We need this in case we send in case we send abort
+ sendRef = argv[1];
+ eSockAddr = argv[3];
+
+ if (!enif_get_resource(env, sockRef, sockets, (void**) &descP)) {
+ return enif_make_badarg(env);
+ }
+
+ SSDBG( descP,
+ ("SOCKET", "nif_sendto -> args when sock = %d:"
+ "\r\n Socket: %T"
+ "\r\n sendRef: %T"
+ "\r\n size of data: %d"
+ "\r\n eSockAddr: %T"
+ "\r\n eflags: %d"
+ "\r\n",
+ descP->sock, sockRef, sendRef, sndData.size, eSockAddr, eflags) );
+
+ if (!esendflags2sendflags(eflags, &flags)) {
+ SSDBG( descP, ("SOCKET", "nif_sendto -> sendflags decode failed\r\n") );
+ return esock_make_error(env, esock_atom_einval);
+ }
+
+ if ((xres = esock_decode_sockaddr(env, eSockAddr,
+ &remoteAddr,
+ &remoteAddrLen)) != NULL) {
+ SSDBG( descP, ("SOCKET", "nif_sendto -> sockaddr decode: %s\r\n", xres) );
+ return esock_make_error_str(env, xres);
+ }
+
+ MLOCK(descP->writeMtx);
+
+ res = nsendto(env, descP, sockRef, sendRef, &sndData, flags,
+ &remoteAddr, remoteAddrLen);
+
+ MUNLOCK(descP->writeMtx);
+
+ SGDBG( ("SOCKET", "nif_sendto -> done with result: "
+ "\r\n %T"
+ "\r\n", res) );
+
+ return res;
+#endif // if defined(__WIN32__)
+}
+
+
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nsendto(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ ErlNifBinary* dataP,
+ int flags,
+ SocketAddress* toAddrP,
+ unsigned int toAddrLen)
+{
+ int save_errno;
+ ssize_t written;
+ ERL_NIF_TERM writerCheck;
+
+ if (!descP->isWritable)
+ return enif_make_badarg(env);
+
+ /* Check if there is already a current writer and if its us */
+ if (!send_check_writer(env, descP, sendRef, &writerCheck))
+ return writerCheck;
+
+ /* We ignore the wrap for the moment.
+ * Maybe we should issue a wrap-message to controlling process...
+ */
+ cnt_inc(&descP->writeTries, 1);
+
+ if (toAddrP != NULL) {
+ written = sock_sendto(descP->sock,
+ dataP->data, dataP->size, flags,
+ &toAddrP->sa, toAddrLen);
+ } else {
+ written = sock_sendto(descP->sock,
+ dataP->data, dataP->size, flags,
+ NULL, 0);
+ }
+ if (IS_SOCKET_ERROR(written))
+ save_errno = sock_errno();
+ else
+ save_errno = -1; // The value does not actually matter in this case
+
+ return send_check_result(env, descP, written, dataP->size, save_errno,
+ sockRef, sendRef);
+}
+#endif // if !defined(__WIN32__)
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_sendmsg
+ *
+ * Description:
+ * Send a message on a socket
+ *
+ * Arguments:
+ * Socket (ref) - Points to the socket descriptor.
+ * SendRef - A unique id for this (send) request.
+ * MsgHdr - Message Header - data and (maybe) control and dest
+ * Flags - Send flags.
+ */
+
+static
+ERL_NIF_TERM nif_sendmsg(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ ERL_NIF_TERM res, sockRef, sendRef, eMsgHdr;
+ SocketDescriptor* descP;
+ unsigned int eflags;
+ int flags;
+
+ SGDBG( ("SOCKET", "nif_sendmsg -> entry with argc: %d\r\n", argc) );
+
+ /* Extract arguments and perform preliminary validation */
+
+ if ((argc != 4) ||
+ !IS_MAP(env, argv[2]) ||
+ !GET_UINT(env, argv[3], &eflags)) {
+ return enif_make_badarg(env);
+ }
+ sockRef = argv[0]; // We need this in case we send in case we send abort
+ sendRef = argv[1];
+ eMsgHdr = argv[2];
+
+ if (!enif_get_resource(env, sockRef, sockets, (void**) &descP)) {
+ return enif_make_badarg(env);
+ }
+
+ SSDBG( descP,
+ ("SOCKET", "nif_sendmsg -> args when sock = %d:"
+ "\r\n Socket: %T"
+ "\r\n sendRef: %T"
+ "\r\n eflags: %d"
+ "\r\n",
+ descP->sock, argv[0], sendRef, eflags) );
+
+ if (!esendflags2sendflags(eflags, &flags))
+ return esock_make_error(env, esock_atom_einval);
+
+ MLOCK(descP->writeMtx);
+
+ res = nsendmsg(env, descP, sockRef, sendRef, eMsgHdr, flags);
+
+ MUNLOCK(descP->writeMtx);
+
+ SSDBG( descP,
+ ("SOCKET", "nif_sendmsg -> done with result: "
+ "\r\n %T"
+ "\r\n", res) );
+
+ return res;
+#endif // if defined(__WIN32__)
+}
+
+
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nsendmsg(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef,
+ ERL_NIF_TERM eMsgHdr,
+ int flags)
+{
+ ERL_NIF_TERM res, eAddr, eIOV, eCtrl;
+ SocketAddress addr;
+ struct msghdr msgHdr;
+ ErlNifBinary* iovBins;
+ struct iovec* iov;
+ unsigned int iovLen;
+ char* ctrlBuf;
+ size_t ctrlBufLen, ctrlBufUsed;
+ int save_errno;
+ ssize_t written, dataSize;
+ ERL_NIF_TERM writerCheck;
+ char* xres;
+
+ if (!descP->isWritable)
+ return enif_make_badarg(env);
+
+ /* Check if there is already a current writer and if its us */
+ if (!send_check_writer(env, descP, sendRef, &writerCheck))
+ return writerCheck;
+
+ /* Depending on if we are *connected* or not, we require
+ * different things in the msghdr map.
+ */
+ if (IS_CONNECTED(descP)) {
+
+ /* We don't need the address */
+
+ SSDBG( descP, ("SOCKET", "nsendmsg -> connected: no address\r\n") );
+
+ msgHdr.msg_name = NULL;
+ msgHdr.msg_namelen = 0;
+
+ } else {
+
+ /* We need the address */
+
+ msgHdr.msg_name = (void*) &addr;
+ msgHdr.msg_namelen = sizeof(addr);
+ sys_memzero((char *) msgHdr.msg_name, msgHdr.msg_namelen);
+ if (!GET_MAP_VAL(env, eMsgHdr, esock_atom_addr, &eAddr))
+ return esock_make_error(env, esock_atom_einval);
+
+ SSDBG( descP, ("SOCKET", "nsendmsg -> not connected: "
+ "\r\n address: %T"
+ "\r\n", eAddr) );
+
+ if ((xres = esock_decode_sockaddr(env, eAddr,
+ msgHdr.msg_name,
+ &msgHdr.msg_namelen)) != NULL)
+ return esock_make_error_str(env, xres);
+ }
+
+
+ /* Extract the (other) attributes of the msghdr map: iov and maybe ctrl */
+
+ /* The *mandatory* iov, which must be a list */
+ if (!GET_MAP_VAL(env, eMsgHdr, esock_atom_iov, &eIOV))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (!GET_LIST_LEN(env, eIOV, &iovLen) && (iovLen > 0))
+ return esock_make_error(env, esock_atom_einval);
+
+ SSDBG( descP, ("SOCKET", "nsendmsg -> iov length: %d\r\n", iovLen) );
+
+ iovBins = MALLOC(iovLen * sizeof(ErlNifBinary));
+ ESOCK_ASSERT( (iovBins != NULL) );
+
+ iov = MALLOC(iovLen * sizeof(struct iovec));
+ ESOCK_ASSERT( (iov != NULL) );
+
+ /* The *opional* ctrl */
+ if (GET_MAP_VAL(env, eMsgHdr, esock_atom_ctrl, &eCtrl)) {
+ ctrlBufLen = descP->wCtrlSz;
+ ctrlBuf = (char*) MALLOC(ctrlBufLen);
+ ESOCK_ASSERT( (ctrlBuf != NULL) );
+ } else {
+ eCtrl = esock_atom_undefined;
+ ctrlBufLen = 0;
+ ctrlBuf = NULL;
+ }
+ SSDBG( descP, ("SOCKET", "nsendmsg -> optional ctrl: "
+ "\r\n ctrlBuf: 0x%lX"
+ "\r\n ctrlBufLen: %d"
+ "\r\n eCtrl: %T\r\n", ctrlBuf, ctrlBufLen, eCtrl) );
+
+ /* Decode the iov and initiate that part of the msghdr */
+ if ((xres = esock_decode_iov(env, eIOV,
+ iovBins, iov, iovLen, &dataSize)) != NULL) {
+ FREE(iovBins);
+ FREE(iov);
+ if (ctrlBuf != NULL) FREE(ctrlBuf);
+ return esock_make_error_str(env, xres);
+ }
+ msgHdr.msg_iov = iov;
+ msgHdr.msg_iovlen = iovLen;
+
+
+ SSDBG( descP, ("SOCKET",
+ "nsendmsg -> total (iov) data size: %d\r\n", dataSize) );
+
+
+ /* Decode the ctrl and initiate that part of the msghdr.
+ */
+ if (ctrlBuf != NULL) {
+ if ((xres = decode_cmsghdrs(env, descP,
+ eCtrl,
+ ctrlBuf, ctrlBufLen, &ctrlBufUsed)) != NULL) {
+ FREE(iovBins);
+ FREE(iov);
+ if (ctrlBuf != NULL) FREE(ctrlBuf);
+ return esock_make_error_str(env, xres);
+ }
+ } 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;
+
+
+ /* We ignore the wrap for the moment.
+ * Maybe we should issue a wrap-message to controlling process...
+ */
+ cnt_inc(&descP->writeTries, 1);
+
+ /* And now, finally, try to send the message */
+ written = sock_sendmsg(descP->sock, &msgHdr, flags);
+
+ if (IS_SOCKET_ERROR(written))
+ save_errno = sock_errno();
+ else
+ save_errno = -1; // OK or not complete: this value should not matter in this case
+
+ res = send_check_result(env, descP, written, dataSize, save_errno,
+ sockRef, sendRef);
+
+ FREE(iovBins);
+ FREE(iov);
+ if (ctrlBuf != NULL) FREE(ctrlBuf);
+
+ return res;
+}
+#endif // if !defined(__WIN32__)
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_writev / nif_sendv
+ *
+ * Description:
+ * Send a message (vector) on a socket
+ *
+ * Arguments:
+ * Socket (ref) - Points to the socket descriptor.
+ * SendRef - A unique id for this (send) request.
+ * Data - A vector of binaries
+ * Flags - Send flags.
+ */
+
+#ifdef FOBAR
+static
+ERL_NIF_TERM nwritev(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM sendRef,
+ ERL_NIF_TERM data)
+{
+ ERL_NIF_TERM tail;
+ ErlNifIOVec vec;
+ ErlNifIOVec* iovec = &vec;
+ SysIOVec* sysiovec;
+ int save_errno;
+ int iovcnt, n;
+
+ if (!enif_inspect_iovec(env, MAX_VSZ, data, &tail, &iovec))
+ return enif_make_badarg(env);
+
+ if (enif_ioq_size(descP->outQ) > 0) {
+ /* If the I/O queue contains data we enqueue the iovec
+ * and then peek the data to write out of the queue.
+ */
+ if (!enif_ioq_enqv(q, iovec, 0))
+ return -3;
+
+ sysiovec = enif_ioq_peek(descP->outQ, &iovcnt);
+
+ } else {
+ /* If the I/O queue is empty we skip the trip through it. */
+ iovcnt = iovec->iovcnt;
+ sysiovec = iovec->iov;
+ }
+
+ /* Attempt to write the data */
+ n = writev(fd, sysiovec, iovcnt);
+ saved_errno = errno;
+
+ if (enif_ioq_size(descP->outQ) == 0) {
+ /* If the I/O queue was initially empty we enqueue any
+ remaining data into the queue for writing later. */
+ if (n >= 0 && !enif_ioq_enqv(descP->outQ, iovec, n))
+ return -3;
+ } else {
+ /* Dequeue any data that was written from the queue. */
+ if (n > 0 && !enif_ioq_deq(descP->outQ, n, NULL))
+ return -4;
+ }
+ /* return n, which is either number of bytes written or -1 if
+ some error happened */
+ errno = saved_errno;
+ return n;
+}
+#endif
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_recv
+ *
+ * Description:
+ * Receive a message on a socket.
+ * Normally used only on a connected socket!
+ * If we are trying to read > 0 bytes, then that is what we do.
+ * But if we have specified 0 bytes, then we want to read
+ * whatever is in the buffers (everything it got).
+ *
+ * Arguments:
+ * Socket (ref) - Points to the socket descriptor.
+ * RecvRef - A unique id for this (send) request.
+ * Length - The number of bytes to receive.
+ * Flags - Receive flags.
+ */
+
+static
+ERL_NIF_TERM nif_recv(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ SocketDescriptor* descP;
+ ERL_NIF_TERM sockRef, recvRef;
+ int len;
+ unsigned int eflags;
+ int flags;
+ ERL_NIF_TERM res;
+
+ if ((argc != 4) ||
+ !GET_INT(env, argv[2], &len) ||
+ !GET_UINT(env, argv[3], &eflags)) {
+ return enif_make_badarg(env);
+ }
+ sockRef = argv[0]; // We need this in case we case we send abort
+ recvRef = argv[1];
+
+ if (!enif_get_resource(env, sockRef, sockets, (void**) &descP)) {
+ return enif_make_badarg(env);
+ }
+
+ if (!erecvflags2recvflags(eflags, &flags))
+ return enif_make_badarg(env);
+
+ MLOCK(descP->readMtx);
+
+ /* We need to handle the case when another process tries
+ * to receive at the same time.
+ * If the current recv could not read its entire package
+ * this time (resulting in an select). The read of the
+ * other process must be made to wait until current
+ * is done!
+ * Basically, we need a read queue!
+ *
+ * A 'reading' field (boolean), which is set if we did
+ * not manage to read the entire message and reset every
+ * time we do.
+ */
+
+ res = nrecv(env, descP, sockRef, recvRef, len, flags);
+
+ MUNLOCK(descP->readMtx);
+
+ return res;
+#endif // if defined(__WIN32__)
+}
+
+
+/* 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...
+ */
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nrecv(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef,
+ int len,
+ int flags)
+{
+ ssize_t read;
+ ErlNifBinary buf;
+ ERL_NIF_TERM readerCheck;
+ int save_errno;
+ int bufSz = (len ? len : descP->rBufSz);
+
+ SSDBG( descP, ("SOCKET", "nrecv -> entry with"
+ "\r\n len: %d (%d:%d)"
+ "\r\n flags: %d"
+ "\r\n", len, descP->rNumCnt, bufSz, flags) );
+
+ if (!descP->isReadable)
+ return enif_make_badarg(env);
+
+ /* Check if there is already a current reader and if its us */
+ if (!recv_check_reader(env, descP, recvRef, &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).
+ */
+ if (!ALLOC_BIN(bufSz, &buf))
+ return esock_make_error(env, atom_exalloc);
+
+ /* We ignore the wrap for the moment.
+ * Maybe we should issue a wrap-message to controlling process...
+ */
+ cnt_inc(&descP->readTries, 1);
+
+ // If it fails (read = -1), we need errno...
+ SSDBG( descP, ("SOCKET", "nrecv -> try read (%d)\r\n", buf.size) );
+ read = sock_recv(descP->sock, buf.data, buf.size, flags);
+ if (IS_SOCKET_ERROR(read)) {
+ save_errno = sock_errno();
+ } else {
+ save_errno = -1; // The value does not actually matter in this case
+ }
+
+ SSDBG( descP, ("SOCKET", "nrecv -> read: %d (%d)\r\n", read, save_errno) );
+
+ return recv_check_result(env, descP,
+ read, len,
+ save_errno,
+ &buf,
+ sockRef,
+ recvRef);
+}
+#endif // if !defined(__WIN32__)
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_recvfrom
+ *
+ * Description:
+ * Receive a message on a socket.
+ * Normally used only on a (un-) connected socket!
+ * If a buffer size = 0 is specified, then the we will use the default
+ * buffer size for this socket (whatever has been configured).
+ *
+ * Arguments:
+ * Socket (ref) - Points to the socket descriptor.
+ * RecvRef - A unique id for this (send) request.
+ * BufSz - Size of the buffer into which we put the received message.
+ * Flags - Receive flags.
+ *
+ * <KOLLA>
+ *
+ * How do we handle if the peek flag is set? We need to basically keep
+ * track of if we expect any data from the read. Regardless of the
+ * number of bytes we try to read.
+ *
+ * </KOLLA>
+ */
+
+static
+ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ SocketDescriptor* descP;
+ ERL_NIF_TERM sockRef, recvRef;
+ unsigned int bufSz;
+ unsigned int eflags;
+ int flags;
+ ERL_NIF_TERM res;
+
+ SGDBG( ("SOCKET", "nif_recvfrom -> entry with argc: %d\r\n", argc) );
+
+ /* Extract arguments and perform preliminary validation */
+
+ if ((argc != 4) ||
+ !GET_UINT(env, argv[2], &bufSz) ||
+ !GET_UINT(env, argv[3], &eflags)) {
+ return enif_make_badarg(env);
+ }
+ sockRef = argv[0]; // We need this in case we send in case we send abort
+ recvRef = argv[1];
+
+ if (!enif_get_resource(env, sockRef, sockets, (void**) &descP)) {
+ return enif_make_badarg(env);
+ }
+
+ SSDBG( descP,
+ ("SOCKET", "nif_recvfrom -> args when sock = %d:"
+ "\r\n Socket: %T"
+ "\r\n recvRef: %T"
+ "\r\n bufSz: %d"
+ "\r\n eflags: %d"
+ "\r\n", descP->sock, argv[0], recvRef, bufSz, eflags) );
+
+ /* if (IS_OPEN(descP)) */
+ /* return esock_make_error(env, atom_enotconn); */
+
+ if (!erecvflags2recvflags(eflags, &flags)) {
+ SSDBG( descP, ("SOCKET", "nif_recvfrom -> recvflags decode failed\r\n") );
+ return enif_make_badarg(env);
+ }
+
+ MLOCK(descP->readMtx);
+
+ /* <KOLLA>
+ * We need to handle the case when another process tries
+ * to receive at the same time.
+ * If the current recv could not read its entire package
+ * this time (resulting in an select). The read of the
+ * other process must be made to wait until current
+ * is done!
+ * Basically, we need a read queue!
+ *
+ * A 'reading' field (boolean), which is set if we did
+ * not manage to read the entire message and reset every
+ * time we do.
+ * </KOLLA>
+ */
+
+ res = nrecvfrom(env, descP, sockRef, recvRef, bufSz, flags);
+
+ MUNLOCK(descP->readMtx);
+
+ return res;
+#endif // if defined(__WIN32__)
+}
+
+
+/* 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...
+ */
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nrecvfrom(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef,
+ Uint16 len,
+ int flags)
+{
+ SocketAddress fromAddr;
+ unsigned int addrLen;
+ ssize_t read;
+ int save_errno;
+ ErlNifBinary buf;
+ ERL_NIF_TERM readerCheck;
+ int bufSz = (len ? len : descP->rBufSz);
+
+ SSDBG( descP, ("SOCKET", "nrecvfrom -> entry with"
+ "\r\n len: %d (%d)"
+ "\r\n flags: %d"
+ "\r\n", len, bufSz, flags) );
+
+ if (!descP->isReadable)
+ return enif_make_badarg(env);
+
+ /* Check if there is already a current reader and if its us */
+ if (!recv_check_reader(env, descP, recvRef, &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).
+ */
+ if (!ALLOC_BIN(bufSz, &buf))
+ return esock_make_error(env, atom_exalloc);
+
+ /* We ignore the wrap for the moment.
+ * Maybe we should issue a wrap-message to controlling process...
+ */
+ cnt_inc(&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 (IS_SOCKET_ERROR(read))
+ save_errno = sock_errno();
+ else
+ save_errno = -1; // The value does not actually matter in this case
+
+ return recvfrom_check_result(env, descP,
+ read,
+ save_errno,
+ &buf,
+ &fromAddr, addrLen,
+ sockRef,
+ recvRef);
+}
+#endif // if !defined(__WIN32__)
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_recvmsg
+ *
+ * Description:
+ * Receive a message on a socket.
+ * Normally used only on a (un-) connected socket!
+ * If a buffer size = 0 is specified, then we will use the default
+ * buffer size for this socket (whatever has been configured).
+ * If ctrl (buffer) size = 0 is specified, then the default ctrl
+ * (buffer) size is used (1024).
+ *
+ * Arguments:
+ * Socket (ref) - Points to the socket descriptor.
+ * RecvRef - A unique id for this (send) request.
+ * BufSz - Size of the buffer into which we put the received message.
+ * CtrlSz - Size of the ctrl (buffer) into which we put the received
+ * ancillary data.
+ * Flags - Receive flags.
+ *
+ * <KOLLA>
+ *
+ * How do we handle if the peek flag is set? We need to basically keep
+ * track of if we expect any data from the read. Regardless of the
+ * number of bytes we try to read.
+ *
+ * </KOLLA>
+ */
+
+static
+ERL_NIF_TERM nif_recvmsg(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ SocketDescriptor* descP;
+ ERL_NIF_TERM sockRef, recvRef;
+ unsigned int bufSz;
+ unsigned int ctrlSz;
+ unsigned int eflags;
+ int flags;
+ ERL_NIF_TERM res;
+
+ SGDBG( ("SOCKET", "nif_recvmsg -> entry with argc: %d\r\n", argc) );
+
+ /* Extract arguments and perform preliminary validation */
+
+ if ((argc != 5) ||
+ !GET_UINT(env, argv[2], &bufSz) ||
+ !GET_UINT(env, argv[3], &ctrlSz) ||
+ !GET_UINT(env, argv[4], &eflags)) {
+ return enif_make_badarg(env);
+ }
+ sockRef = argv[0]; // We need this in case we send in case we send abort
+ recvRef = argv[1];
+
+ if (!enif_get_resource(env, sockRef, sockets, (void**) &descP)) {
+ return enif_make_badarg(env);
+ }
+
+ SSDBG( descP,
+ ("SOCKET", "nif_recvmsg -> args when sock = %d:"
+ "\r\n Socket: %T"
+ "\r\n recvRef: %T"
+ "\r\n bufSz: %d"
+ "\r\n ctrlSz: %d"
+ "\r\n eflags: %d"
+ "\r\n", descP->sock, argv[0], recvRef, bufSz, ctrlSz, eflags) );
+
+ /* if (IS_OPEN(descP)) */
+ /* return esock_make_error(env, atom_enotconn); */
+
+ if (!erecvflags2recvflags(eflags, &flags))
+ return enif_make_badarg(env);
+
+ MLOCK(descP->readMtx);
+
+ /* <KOLLA>
+ *
+ * We need to handle the case when another process tries
+ * to receive at the same time.
+ * If the current recv could not read its entire package
+ * this time (resulting in an select). The read of the
+ * other process must be made to wait until current
+ * is done!
+ * Basically, we need a read queue!
+ *
+ * A 'reading' field (boolean), which is set if we did
+ * not manage to read the entire message and reset every
+ * time we do.
+ *
+ * </KOLLA>
+ */
+
+ res = nrecvmsg(env, descP, sockRef, recvRef, bufSz, ctrlSz, flags);
+
+ MUNLOCK(descP->readMtx);
+
+ return res;
+#endif // if defined(__WIN32__)
+}
+
+
+/* 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...
+ */
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nrecvmsg(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef,
+ Uint16 bufLen,
+ Uint16 ctrlLen,
+ int flags)
+{
+ unsigned int addrLen;
+ ssize_t read;
+ int save_errno;
+ int bufSz = (bufLen ? bufLen : descP->rBufSz);
+ int ctrlSz = (ctrlLen ? ctrlLen : descP->rCtrlSz);
+ struct msghdr msgHdr;
+ struct iovec iov[1]; // Shall we always use 1?
+ ErlNifBinary data[1]; // Shall we always use 1?
+ ErlNifBinary ctrl;
+ ERL_NIF_TERM readerCheck;
+ SocketAddress addr;
+
+ SSDBG( descP, ("SOCKET", "nrecvmsg -> entry with"
+ "\r\n bufSz: %d (%d)"
+ "\r\n ctrlSz: %d (%d)"
+ "\r\n flags: %d"
+ "\r\n", bufSz, bufLen, ctrlSz, ctrlLen, flags) );
+
+ if (!descP->isReadable)
+ return enif_make_badarg(env);
+
+ /* Check if there is already a current reader and if its us */
+ if (!recv_check_reader(env, descP, recvRef, &readerCheck))
+ return readerCheck;
+
+ /*
+ for (i = 0; i < sizeof(buf); i++) {
+ if (!ALLOC_BIN(bifSz, &buf[i]))
+ return esock_make_error(env, atom_exalloc);
+ iov[i].iov_base = buf[i].data;
+ iov[i].iov_len = buf[i].size;
+ }
+ */
+
+ /* Allocate the (msg) data buffer:
+ */
+ if (!ALLOC_BIN(bufSz, &data[0]))
+ return esock_make_error(env, atom_exalloc);
+
+ /* Allocate the ctrl (buffer):
+ */
+ if (!ALLOC_BIN(ctrlSz, &ctrl))
+ return esock_make_error(env, atom_exalloc);
+
+ /* We ignore the wrap for the moment.
+ * Maybe we should issue a wrap-message to controlling process...
+ */
+ cnt_inc(&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 (IS_SOCKET_ERROR(read))
+ save_errno = sock_errno();
+ else
+ save_errno = -1; // 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 // if !defined(__WIN32__)
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_close
+ *
+ * Description:
+ * Close a (socket) file descriptor.
+ *
+ * Arguments:
+ * Socket (ref) - Points to the socket descriptor.
+ */
+
+static
+ERL_NIF_TERM nif_close(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ SocketDescriptor* descP;
+
+ if ((argc != 1) ||
+ !enif_get_resource(env, argv[0], sockets, (void**) &descP)) {
+ return enif_make_badarg(env);
+ }
+
+ if (IS_CLOSED(descP) || IS_CLOSING(descP))
+ return esock_make_error(env, atom_closed);
+
+ return nclose(env, descP);
+#endif // if defined(__WIN32__)
+}
+
+
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nclose(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ ERL_NIF_TERM reply, reason;
+ BOOLEAN_T doClose;
+ int selectRes;
+ int domain = descP->domain;
+ int type = descP->type;
+ int protocol = descP->protocol;
+
+ SSDBG( descP, ("SOCKET",
+ "nclose -> [%d] entry (0x%lX, 0x%lX, 0x%lX, 0x%lX)\r\n",
+ descP->sock,
+ descP->state,
+ descP->currentWriterP,
+ descP->currentReaderP,
+ descP->currentAcceptorP) );
+
+ MLOCK(descP->closeMtx);
+
+ if (descP->state == SOCKET_STATE_CLOSED) {
+ reason = atom_closed;
+ doClose = FALSE;
+ } else if (descP->state == SOCKET_STATE_CLOSING) {
+ reason = atom_closing;
+ doClose = FALSE;
+ } else {
+
+ /* Store the PID of the caller,
+ * since we need to inform it when we
+ * (that is, the stop callback function)
+ * completes.
+ */
+
+ if (enif_self(env, &descP->closerPid) == NULL) {
+ MUNLOCK(descP->closeMtx);
+ return esock_make_error(env, atom_exself);
+ }
+
+ /* Monitor the caller, since we should complete this operation even if
+ * the caller dies (for whatever reason).
+ *
+ * <KOLLA>
+ *
+ * Can we actiually use this for anything?
+ *
+ * </KOLLA>
+ */
+
+ if (MONP("nclose -> closer",
+ env, descP,
+ &descP->closerPid,
+ &descP->closerMon) != 0) {
+ MUNLOCK(descP->closeMtx);
+ return esock_make_error(env, atom_exmon);
+ }
+
+ descP->closeLocal = TRUE;
+ descP->state = SOCKET_STATE_CLOSING;
+ descP->isReadable = FALSE;
+ descP->isWritable = FALSE;
+ doClose = TRUE;
+ }
+
+ if (doClose) {
+ descP->closeEnv = enif_alloc_env();
+ descP->closeRef = MKREF(descP->closeEnv);
+ selectRes = esock_select_stop(env, descP->sock, descP);
+ if (selectRes & ERL_NIF_SELECT_STOP_CALLED) {
+ /* Prep done - inform the caller it can finalize (close) directly */
+ SSDBG( descP,
+ ("SOCKET", "nclose -> [%d] stop was called\r\n", descP->sock) );
+ dec_socket(domain, type, protocol);
+ reply = esock_atom_ok;
+ } else if (selectRes & ERL_NIF_SELECT_STOP_SCHEDULED) {
+ /* The stop callback function has been *scheduled* which means that we
+ * have to wait for it to complete. */
+ SSDBG( descP,
+ ("SOCKET", "nclose -> [%d] stop was scheduled\r\n",
+ descP->sock) );
+ dec_socket(domain, type, protocol); // SHALL WE DO THIS AT finalize?
+ reply = esock_make_ok2(env, descP->closeRef);
+ } else {
+
+ SSDBG( descP,
+ ("SOCKET", "nclose -> [%d] stop failed: %d\r\n",
+ descP->sock, selectRes) );
+
+ /* <KOLLA>
+ *
+ * WE SHOULD REALLY HAVE A WAY TO CLOBBER THE SOCKET,
+ * SO WE DON'T LET STUFF LEAK.
+ * NOW, BECAUSE WE FAILED TO SELECT, WE CANNOT FINISH
+ * THE CLOSE, WHAT TO DO? ABORT?
+ *
+ * </KOLLA>
+ */
+
+ // No point in having this?
+ DEMONP("nclose -> closer", env, descP, &descP->closerMon);
+
+ reason = MKT2(env, atom_select, MKI(env, selectRes));
+ reply = esock_make_error(env, reason);
+ }
+
+ } else {
+ reply = esock_make_error(env, reason);
+ }
+
+ MUNLOCK(descP->closeMtx);
+
+ SSDBG( descP,
+ ("SOCKET", "nclose -> [%d] done when: "
+ "\r\n state: 0x%lX"
+ "\r\n reply: %T"
+ "\r\n", descP->sock, descP->state, reply) );
+
+ return reply;
+}
+#endif // if !defined(__WIN32__)
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_finalize_close
+ *
+ * Description:
+ * Perform the actual socket close!
+ * Note that this function is executed in a dirty scheduler.
+ *
+ * Arguments:
+ * Socket (ref) - Points to the socket descriptor.
+ */
+static
+ERL_NIF_TERM nif_finalize_close(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ SocketDescriptor* descP;
+
+ /* Extract arguments and perform preliminary validation */
+
+ if ((argc != 1) ||
+ !enif_get_resource(env, argv[0], sockets, (void**) &descP)) {
+ return enif_make_badarg(env);
+ }
+
+ return nfinalize_close(env, descP);
+#endif // if defined(__WIN32__)
+}
+
+
+/* *** nfinalize_close ***
+ * Perform the final step in the socket close.
+ */
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nfinalize_close(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ ERL_NIF_TERM reply;
+
+ if (IS_CLOSED(descP))
+ return esock_atom_ok;
+
+ if (!IS_CLOSING(descP))
+ return esock_make_error(env, atom_enotclosing);
+
+ /* This nif is executed in a dirty scheduler just so that
+ * it can "hang" (whith minumum 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);
+
+ if (sock_close(descP->sock) != 0) {
+ int save_errno = sock_errno();
+
+ if (save_errno != ERRNO_BLOCK) {
+ /* Not all data in the buffers where sent,
+ * make sure the caller gets this.
+ */
+ reply = esock_make_error(env, atom_timeout);
+ } else {
+ reply = esock_make_error_errno(env, save_errno);
+ }
+ } else {
+ reply = esock_atom_ok;
+ }
+ sock_close_event(descP->event);
+
+ descP->sock = INVALID_SOCKET;
+ descP->event = INVALID_EVENT;
+
+ descP->state = SOCKET_STATE_CLOSED;
+
+ return reply;
+}
+#endif // if !defined(__WIN32__)
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_shutdown
+ *
+ * Description:
+ * Disable sends and/or receives on a socket.
+ *
+ * Arguments:
+ * [0] Socket (ref) - Points to the socket descriptor.
+ * [1] How - What will be shutdown.
+ */
+
+static
+ERL_NIF_TERM nif_shutdown(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ SocketDescriptor* descP;
+ unsigned int ehow;
+ int how;
+
+ if ((argc != 2) ||
+ !enif_get_resource(env, argv[0], sockets, (void**) &descP) ||
+ !GET_UINT(env, argv[1], &ehow)) {
+ return enif_make_badarg(env);
+ }
+
+ if (IS_CLOSED(descP) || IS_CLOSING(descP))
+ return esock_make_error(env, atom_closed);
+
+ if (!ehow2how(ehow, &how))
+ return enif_make_badarg(env);
+
+ return nshutdown(env, descP, how);
+#endif // if defined(__WIN32__)
+}
+
+
+
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nshutdown(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int how)
+{
+ ERL_NIF_TERM reply;
+
+ if (sock_shutdown(descP->sock, how) == 0) {
+ switch (how) {
+ case SHUT_RD:
+ descP->isReadable = FALSE;
+ break;
+ case SHUT_WR:
+ descP->isWritable = FALSE;
+ break;
+ case SHUT_RDWR:
+ descP->isReadable = FALSE;
+ descP->isWritable = FALSE;
+ break;
+ }
+ reply = esock_atom_ok;
+ } else {
+ reply = esock_make_error_errno(env, sock_errno());
+ }
+
+ return reply;
+}
+#endif // if !defined(__WIN32__)
+
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_setopt
+ *
+ * Description:
+ * Set socket option.
+ * Its possible to use a "raw" mode (not encoded). That is, we do not
+ * interpret level, opt and value. They are passed "as is" to the
+ * setsockopt function call (the value arguments is assumed to be a
+ * binary, already encoded).
+ *
+ * Arguments:
+ * Socket (ref) - Points to the socket descriptor.
+ * Encoded - Are the "arguments" encoded or not.
+ * Level - Level of the socket option.
+ * Opt - The socket option.
+ * Value - Value of the socket option (type depend on the option).
+ */
+
+static
+ERL_NIF_TERM nif_setopt(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ SocketDescriptor* descP = NULL;
+ int eLevel, level = -1;
+ int eOpt;
+ ERL_NIF_TERM eIsEncoded;
+ ERL_NIF_TERM eVal;
+ BOOLEAN_T isEncoded, isOTP;
+ ERL_NIF_TERM result;
+
+ SGDBG( ("SOCKET", "nif_setopt -> entry with argc: %d\r\n", argc) );
+
+ /* Extract arguments and perform preliminary validation */
+
+ if ((argc != 5) ||
+ !enif_get_resource(env, argv[0], sockets, (void**) &descP) ||
+ !GET_INT(env, argv[2], &eLevel) ||
+ !GET_INT(env, argv[3], &eOpt)) {
+ SGDBG( ("SOCKET", "nif_setopt -> failed initial arg check\r\n") );
+ return enif_make_badarg(env);
+ }
+ eIsEncoded = argv[1];
+ eVal = argv[4];
+
+ if (IS_CLOSED(descP) || IS_CLOSING(descP))
+ return esock_make_error(env, atom_closed);
+
+ isEncoded = esock_decode_bool(eIsEncoded);
+
+ /* SGDBG( ("SOCKET", "nif_setopt -> eIsDecoded (%T) decoded: %d\r\n", */
+ /* eIsEncoded, isEncoded) ); */
+
+ if (!elevel2level(isEncoded, eLevel, &isOTP, &level)) {
+ SSDBG( descP, ("SOCKET", "nif_seopt -> failed decode level\r\n") );
+ return esock_make_error(env, esock_atom_einval);
+ }
+
+ SSDBG( descP,
+ ("SOCKET", "nif_setopt -> args when sock = %d:"
+ "\r\n Socket: %T"
+ "\r\n Encoded: %d (%T)"
+ "\r\n Level: %d (%d)"
+ "\r\n Opt: %d"
+ "\r\n Value: %T"
+ "\r\n",
+ descP->sock, argv[0],
+ isEncoded, eIsEncoded,
+ level, eLevel,
+ eOpt, eVal) );
+
+ result = nsetopt(env, descP, isEncoded, isOTP, level, eOpt, eVal);
+
+ SSDBG( descP,
+ ("SOCKET", "nif_setopt -> done when"
+ "\r\n result: %T"
+ "\r\n", result) );
+
+ return result;
+#endif // if defined(__WIN32__)
+}
+
+
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nsetopt(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ BOOLEAN_T isEncoded,
+ BOOLEAN_T isOTP,
+ int level,
+ int eOpt,
+ ERL_NIF_TERM eVal)
+{
+ ERL_NIF_TERM result;
+
+ if (isOTP) {
+ /* These are not actual socket options,
+ * but options for our implementation.
+ */
+ result = nsetopt_otp(env, descP, eOpt, eVal);
+ } else if (!isEncoded) {
+ result = nsetopt_native(env, descP, level, eOpt, eVal);
+ } else {
+ result = nsetopt_level(env, descP, level, eOpt, eVal);
+ }
+
+ return result;
+}
+
+
+
+/* nsetopt_otp - Handle OTP (level) options
+ */
+static
+ERL_NIF_TERM nsetopt_otp(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt,
+ ERL_NIF_TERM eVal)
+{
+ ERL_NIF_TERM result;
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_otp -> entry with"
+ "\r\n eOpt: %d"
+ "\r\n eVal: %T"
+ "\r\n", eOpt, eVal) );
+
+ switch (eOpt) {
+ case SOCKET_OPT_OTP_DEBUG:
+ result = nsetopt_otp_debug(env, descP, eVal);
+ break;
+
+ case SOCKET_OPT_OTP_IOW:
+ result = nsetopt_otp_iow(env, descP, eVal);
+ break;
+
+ case SOCKET_OPT_OTP_CTRL_PROC:
+ result = nsetopt_otp_ctrl_proc(env, descP, eVal);
+ break;
+
+ case SOCKET_OPT_OTP_RCVBUF:
+ result = nsetopt_otp_rcvbuf(env, descP, eVal);
+ break;
+
+ case SOCKET_OPT_OTP_RCVCTRLBUF:
+ result = nsetopt_otp_rcvctrlbuf(env, descP, eVal);
+ break;
+
+ case SOCKET_OPT_OTP_SNDCTRLBUF:
+ result = nsetopt_otp_sndctrlbuf(env, descP, eVal);
+ break;
+
+ default:
+ result = esock_make_error(env, esock_atom_einval);
+ break;
+ }
+
+ return result;
+}
+
+
+/* nsetopt_otp_debug - Handle the OTP (level) debug options
+ */
+static
+ERL_NIF_TERM nsetopt_otp_debug(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ descP->dbg = esock_decode_bool(eVal);
+
+ return esock_atom_ok;
+}
+
+
+/* nsetopt_otp_iow - Handle the OTP (level) iow options
+ */
+static
+ERL_NIF_TERM nsetopt_otp_iow(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ descP->iow = esock_decode_bool(eVal);
+
+ return esock_atom_ok;
+}
+
+
+
+/* nsetopt_otp_ctrl_proc - Handle the OTP (level) controlling_process options
+ */
+static
+ERL_NIF_TERM nsetopt_otp_ctrl_proc(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ ErlNifPid caller, newCtrlPid;
+ // ErlNifMonitor newCtrlMon;
+ ESockMonitor newCtrlMon;
+ int xres;
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_otp_ctrl_proc -> entry with"
+ "\r\n eVal: %T"
+ "\r\n", eVal) );
+
+ /* Before we begin, ensure that caller is (current) controlling-process */
+ if (enif_self(env, &caller) == NULL)
+ return esock_make_error(env, atom_exself);
+
+ if (!compare_pids(env, &descP->ctrlPid, &caller)) {
+ SSDBG( descP, ("SOCKET", "nsetopt_otp_ctrl_proc -> not owner (%T)\r\n",
+ descP->ctrlPid) );
+ return esock_make_error(env, esock_atom_not_owner);
+ }
+
+ if (!GET_LPID(env, eVal, &newCtrlPid)) {
+ esock_warning_msg("Failed get pid of new controlling process\r\n");
+ return esock_make_error(env, esock_atom_einval);
+ }
+
+ if ((xres = MONP("nsetopt_otp_ctrl_proc -> (new) ctrl",
+ env, descP, &newCtrlPid, &newCtrlMon)) != 0) {
+ esock_warning_msg("Failed monitor %d) (new) controlling process\r\n", xres);
+ return esock_make_error(env, esock_atom_einval);
+ }
+
+ if ((xres = DEMONP("nsetopt_otp_ctrl_proc -> (old) ctrl",
+ env, descP, &descP->ctrlMon)) != 0) {
+ esock_warning_msg("Failed demonitor (%d) "
+ "old controlling process %T (%T)\r\n",
+ xres, descP->ctrlPid, descP->ctrlMon);
+ }
+
+ descP->ctrlPid = newCtrlPid;
+ descP->ctrlMon = newCtrlMon;
+
+ SSDBG( descP, ("SOCKET", "nsetopt_otp_ctrl_proc -> done\r\n") );
+
+ return esock_atom_ok;
+}
+
+
+
+/* nsetopt_otp_rcvbuf - Handle the OTP (level) rcvbuf option
+ * The (otp) rcvbuf option is provided as:
+ *
+ * BufSz :: integer() | {N :: pos_integer(), BufSz :: pod_integer()}
+ *
+ * Where N is the max number of reads.
+ */
+static
+ERL_NIF_TERM nsetopt_otp_rcvbuf(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ const ERL_NIF_TERM* t; // The array of the elements of the tuple
+ int tsz; // The size of the tuple - should be 2
+ unsigned int n;
+ size_t bufSz;
+ char* xres;
+
+ if (IS_NUM(env, eVal)) {
+
+ /* This will have the effect that the buffer size will be
+ * reported as an integer (getopt).
+ */
+ n = 0;
+
+ if ((xres = esock_decode_bufsz(env,
+ eVal,
+ SOCKET_RECV_BUFFER_SIZE_DEFAULT,
+ &bufSz)) != NULL)
+ return esock_make_error_str(env, xres);
+
+ } else if (IS_TUPLE(env, eVal)) {
+
+ if (!GET_TUPLE(env, eVal, &tsz, &t))
+ return enif_make_badarg(env); // We should use a "proper" error value...
+
+ if (tsz != 2)
+ return enif_make_badarg(env); // We should use a "proper" error value...
+
+ if (!GET_UINT(env, t[0], &n))
+ return enif_make_badarg(env); // We should use a "proper" error value...
+
+ if ((xres = esock_decode_bufsz(env,
+ t[1],
+ SOCKET_RECV_BUFFER_SIZE_DEFAULT,
+ &bufSz)) != NULL)
+ return esock_make_error_str(env, xres);
+
+ } else {
+ return enif_make_badarg(env); // We should use a "proper" error value...
+ }
+
+ descP->rNum = n;
+ descP->rBufSz = bufSz;
+
+ return esock_atom_ok;
+}
+
+
+
+/* nsetopt_otp_rcvctrlbuf - Handle the OTP (level) rcvctrlbuf option
+ */
+static
+ERL_NIF_TERM nsetopt_otp_rcvctrlbuf(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ size_t val;
+ char* xres;
+
+ if ((xres = esock_decode_bufsz(env,
+ eVal,
+ SOCKET_RECV_CTRL_BUFFER_SIZE_DEFAULT,
+ &val)) != NULL)
+ return esock_make_error_str(env, xres);
+
+ descP->rCtrlSz = val;
+
+ return esock_atom_ok;
+}
+
+
+
+/* nsetopt_otp_sndctrlbuf - Handle the OTP (level) sndctrlbuf option
+ */
+static
+ERL_NIF_TERM nsetopt_otp_sndctrlbuf(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ size_t val;
+ char* xres;
+
+ if ((xres = esock_decode_bufsz(env,
+ eVal,
+ SOCKET_SEND_CTRL_BUFFER_SIZE_DEFAULT,
+ &val)) != NULL)
+ return esock_make_error_str(env, xres);
+
+ descP->wCtrlSz = val;
+
+ return esock_atom_ok;
+}
+
+
+
+/* The option has *not* been encoded. Instead it has been provided
+ * in "native mode" (option is provided as is and value as a binary).
+ */
+static
+ERL_NIF_TERM nsetopt_native(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int level,
+ int opt,
+ ERL_NIF_TERM eVal)
+{
+ ErlNifBinary val;
+ ERL_NIF_TERM result;
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_native -> entry with"
+ "\r\n level: %d"
+ "\r\n opt: %d"
+ "\r\n eVal: %T"
+ "\r\n", level, opt, eVal) );
+
+ if (GET_BIN(env, eVal, &val)) {
+ int res = socket_setopt(descP->sock, level, opt,
+ val.data, val.size);
+ if (res != 0)
+ result = esock_make_error_errno(env, sock_errno());
+ else
+ result = esock_atom_ok;
+ } else {
+ result = esock_make_error(env, esock_atom_einval);
+ }
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_native -> done when"
+ "\r\n result: %T"
+ "\r\n", result) );
+
+ return result;
+}
+
+
+
+/* nsetopt_level - A "proper" level (option) has been specified
+ */
+static
+ERL_NIF_TERM nsetopt_level(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int level,
+ int eOpt,
+ ERL_NIF_TERM eVal)
+{
+ ERL_NIF_TERM result;
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_level -> entry with"
+ "\r\n level: %d"
+ "\r\n", level) );
+
+ switch (level) {
+ case SOL_SOCKET:
+ result = nsetopt_lvl_socket(env, descP, eOpt, eVal);
+ break;
+
+#if defined(SOL_IP)
+ case SOL_IP:
+#else
+ case IPPROTO_IP:
+#endif
+ result = nsetopt_lvl_ip(env, descP, eOpt, eVal);
+ break;
+
+#if defined(HAVE_IPV6)
+#if defined(SOL_IPV6)
+ case SOL_IPV6:
+#else
+ case IPPROTO_IPV6:
+#endif
+ result = nsetopt_lvl_ipv6(env, descP, eOpt, eVal);
+ break;
+#endif
+
+ case IPPROTO_TCP:
+ result = nsetopt_lvl_tcp(env, descP, eOpt, eVal);
+ break;
+
+ case IPPROTO_UDP:
+ result = nsetopt_lvl_udp(env, descP, eOpt, eVal);
+ break;
+
+#if defined(HAVE_SCTP)
+ case IPPROTO_SCTP:
+ result = nsetopt_lvl_sctp(env, descP, eOpt, eVal);
+ break;
+#endif
+
+ default:
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_level -> unknown level (%d)\r\n", level) );
+ result = esock_make_error(env, esock_atom_einval);
+ break;
+ }
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_level -> done when"
+ "\r\n result: %T"
+ "\r\n", result) );
+
+ return result;
+}
+
+
+
+/* nsetopt_lvl_socket - Level *SOCKET* option
+ */
+static
+ERL_NIF_TERM nsetopt_lvl_socket(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt,
+ ERL_NIF_TERM eVal)
+{
+ ERL_NIF_TERM result;
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_socket -> entry with"
+ "\r\n opt: %d"
+ "\r\n", eOpt) );
+
+ switch (eOpt) {
+#if defined(SO_BINDTODEVICE)
+ case SOCKET_OPT_SOCK_BINDTODEVICE:
+ result = nsetopt_lvl_sock_bindtodevice(env, descP, eVal);
+ break;
+#endif
+
+#if defined(SO_BROADCAST)
+ case SOCKET_OPT_SOCK_BROADCAST:
+ result = nsetopt_lvl_sock_broadcast(env, descP, eVal);
+ break;
+#endif
+
+#if defined(SO_DEBUG)
+ case SOCKET_OPT_SOCK_DEBUG:
+ result = nsetopt_lvl_sock_debug(env, descP, eVal);
+ break;
+#endif
+
+#if defined(SO_DONTROUTE)
+ case SOCKET_OPT_SOCK_DONTROUTE:
+ result = nsetopt_lvl_sock_dontroute(env, descP, eVal);
+ break;
+#endif
+
+#if defined(SO_KEEPALIVE)
+ case SOCKET_OPT_SOCK_KEEPALIVE:
+ result = nsetopt_lvl_sock_keepalive(env, descP, eVal);
+ break;
+#endif
+
+#if defined(SO_LINGER)
+ case SOCKET_OPT_SOCK_LINGER:
+ result = nsetopt_lvl_sock_linger(env, descP, eVal);
+ break;
+#endif
+
+#if defined(SO_PEEK_OFF)
+ case SOCKET_OPT_SOCK_PEEK_OFF:
+ result = nsetopt_lvl_sock_peek_off(env, descP, eVal);
+ break;
+#endif
+
+#if defined(SO_OOBINLINE)
+ case SOCKET_OPT_SOCK_OOBINLINE:
+ result = nsetopt_lvl_sock_oobinline(env, descP, eVal);
+ break;
+#endif
+
+#if defined(SO_PRIORITY)
+ case SOCKET_OPT_SOCK_PRIORITY:
+ result = nsetopt_lvl_sock_priority(env, descP, eVal);
+ break;
+#endif
+
+#if defined(SO_RCVBUF)
+ case SOCKET_OPT_SOCK_RCVBUF:
+ result = nsetopt_lvl_sock_rcvbuf(env, descP, eVal);
+ break;
+#endif
+
+#if defined(SO_RCVLOWAT)
+ case SOCKET_OPT_SOCK_RCVLOWAT:
+ result = nsetopt_lvl_sock_rcvlowat(env, descP, eVal);
+ break;
+#endif
+
+#if defined(SO_RCVTIMEO)
+ case SOCKET_OPT_SOCK_RCVTIMEO:
+ result = nsetopt_lvl_sock_rcvtimeo(env, descP, eVal);
+ break;
+#endif
+
+#if defined(SO_REUSEADDR)
+ case SOCKET_OPT_SOCK_REUSEADDR:
+ result = nsetopt_lvl_sock_reuseaddr(env, descP, eVal);
+ break;
+#endif
+
+#if defined(SO_REUSEPORT)
+ case SOCKET_OPT_SOCK_REUSEPORT:
+ result = nsetopt_lvl_sock_reuseport(env, descP, eVal);
+ break;
+#endif
+
+#if defined(SO_SNDBUF)
+ case SOCKET_OPT_SOCK_SNDBUF:
+ result = nsetopt_lvl_sock_sndbuf(env, descP, eVal);
+ break;
+#endif
+
+#if defined(SO_SNDLOWAT)
+ case SOCKET_OPT_SOCK_SNDLOWAT:
+ result = nsetopt_lvl_sock_sndlowat(env, descP, eVal);
+ break;
+#endif
+
+#if defined(SO_SNDTIMEO)
+ case SOCKET_OPT_SOCK_SNDTIMEO:
+ result = nsetopt_lvl_sock_sndtimeo(env, descP, eVal);
+ break;
+#endif
+
+#if defined(SO_TIMESTAMP)
+ case SOCKET_OPT_SOCK_TIMESTAMP:
+ result = nsetopt_lvl_sock_timestamp(env, descP, eVal);
+ break;
+#endif
+
+ default:
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_socket -> unknown opt (%d)\r\n", eOpt) );
+ result = esock_make_error(env, esock_atom_einval);
+ break;
+ }
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_socket -> done when"
+ "\r\n result: %T"
+ "\r\n", result) );
+
+ return result;
+}
+
+
+#if defined(SO_BINDTODEVICE)
+static
+ERL_NIF_TERM nsetopt_lvl_sock_bindtodevice(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_str_opt(env, descP,
+ SOL_SOCKET, SO_BROADCAST,
+ IFNAMSIZ, eVal);
+}
+#endif
+
+
+#if defined(SO_BROADCAST)
+static
+ERL_NIF_TERM nsetopt_lvl_sock_broadcast(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_bool_opt(env, descP, SOL_SOCKET, SO_BROADCAST, eVal);
+}
+#endif
+
+
+#if defined(SO_DEBUG)
+static
+ERL_NIF_TERM nsetopt_lvl_sock_debug(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_int_opt(env, descP, SOL_SOCKET, SO_DEBUG, eVal);
+}
+#endif
+
+
+#if defined(SO_DONTROUTE)
+static
+ERL_NIF_TERM nsetopt_lvl_sock_dontroute(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_bool_opt(env, descP, SOL_SOCKET, SO_DONTROUTE, eVal);
+}
+#endif
+
+
+#if defined(SO_KEEPALIVE)
+static
+ERL_NIF_TERM nsetopt_lvl_sock_keepalive(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_bool_opt(env, descP, SOL_SOCKET, SO_KEEPALIVE, eVal);
+}
+#endif
+
+
+#if defined(SO_LINGER)
+static
+ERL_NIF_TERM nsetopt_lvl_sock_linger(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ ERL_NIF_TERM result;
+ struct linger val;
+
+ if (decode_sock_linger(env, eVal, &val)) {
+ int optLen = sizeof(val);
+ int res = socket_setopt(descP->sock, SOL_SOCKET, SO_LINGER,
+ (void*) &val, optLen);
+ if (res != 0)
+ result = esock_make_error_errno(env, sock_errno());
+ else
+ result = esock_atom_ok;
+ } else {
+ result = esock_make_error(env, esock_atom_einval);
+ }
+
+ return result;
+}
+#endif
+
+
+#if defined(SO_OOBINLINE)
+static
+ERL_NIF_TERM nsetopt_lvl_sock_oobinline(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_bool_opt(env, descP, SOL_SOCKET, SO_OOBINLINE, eVal);
+}
+#endif
+
+
+#if defined(SO_PEEK_OFF)
+static
+ERL_NIF_TERM nsetopt_lvl_sock_peek_off(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_int_opt(env, descP, SOL_SOCKET, SO_PEEK_OFF, eVal);
+}
+#endif
+
+
+#if defined(SO_PRIORITY)
+static
+ERL_NIF_TERM nsetopt_lvl_sock_priority(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_int_opt(env, descP, SOL_SOCKET, SO_PRIORITY, eVal);
+}
+#endif
+
+
+#if defined(SO_RCVBUF)
+static
+ERL_NIF_TERM nsetopt_lvl_sock_rcvbuf(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_int_opt(env, descP, SOL_SOCKET, SO_RCVBUF, eVal);
+}
+#endif
+
+
+#if defined(SO_RCVLOWAT)
+static
+ERL_NIF_TERM nsetopt_lvl_sock_rcvlowat(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_int_opt(env, descP, SOL_SOCKET, SO_RCVLOWAT, eVal);
+}
+#endif
+
+
+#if defined(SO_RCVTIMEO)
+static
+ERL_NIF_TERM nsetopt_lvl_sock_rcvtimeo(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_timeval_opt(env, descP, SOL_SOCKET, SO_RCVTIMEO, eVal);
+}
+#endif
+
+
+#if defined(SO_REUSEADDR)
+static
+ERL_NIF_TERM nsetopt_lvl_sock_reuseaddr(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_bool_opt(env, descP, SOL_SOCKET, SO_REUSEADDR, eVal);
+}
+#endif
+
+
+#if defined(SO_REUSEPORT)
+static
+ERL_NIF_TERM nsetopt_lvl_sock_reuseport(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_bool_opt(env, descP, SOL_SOCKET, SO_REUSEPORT, eVal);
+}
+#endif
+
+
+#if defined(SO_SNDBUF)
+static
+ERL_NIF_TERM nsetopt_lvl_sock_sndbuf(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_int_opt(env, descP, SOL_SOCKET, SO_SNDBUF, eVal);
+}
+#endif
+
+
+#if defined(SO_SNDLOWAT)
+static
+ERL_NIF_TERM nsetopt_lvl_sock_sndlowat(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_int_opt(env, descP, SOL_SOCKET, SO_SNDLOWAT, eVal);
+}
+#endif
+
+
+#if defined(SO_SNDTIMEO)
+static
+ERL_NIF_TERM nsetopt_lvl_sock_sndtimeo(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_sock_sndtimeo -> entry with"
+ "\r\n eVal: %T"
+ "\r\n", eVal) );
+
+ return nsetopt_timeval_opt(env, descP, SOL_SOCKET, SO_SNDTIMEO, eVal);
+}
+#endif
+
+
+#if defined(SO_TIMESTAMP)
+static
+ERL_NIF_TERM nsetopt_lvl_sock_timestamp(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_bool_opt(env, descP, SOL_SOCKET, SO_TIMESTAMP, eVal);
+}
+#endif
+
+
+
+/* nsetopt_lvl_ip - Level *IP* option(s)
+ */
+static
+ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt,
+ ERL_NIF_TERM eVal)
+{
+ ERL_NIF_TERM result;
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_ip -> entry with"
+ "\r\n opt: %d"
+ "\r\n", eOpt) );
+
+ switch (eOpt) {
+#if defined(IP_ADD_MEMBERSHIP)
+ case SOCKET_OPT_IP_ADD_MEMBERSHIP:
+ result = nsetopt_lvl_ip_add_membership(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_ADD_SOURCE_MEMBERSHIP)
+ case SOCKET_OPT_IP_ADD_SOURCE_MEMBERSHIP:
+ result = nsetopt_lvl_ip_add_source_membership(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_BLOCK_SOURCE)
+ case SOCKET_OPT_IP_BLOCK_SOURCE:
+ result = nsetopt_lvl_ip_block_source(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_DROP_MEMBERSHIP)
+ case SOCKET_OPT_IP_DROP_MEMBERSHIP:
+ result = nsetopt_lvl_ip_drop_membership(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_DROP_SOURCE_MEMBERSHIP)
+ case SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP:
+ result = nsetopt_lvl_ip_drop_source_membership(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_FREEBIND)
+ case SOCKET_OPT_IP_FREEBIND:
+ result = nsetopt_lvl_ip_freebind(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_HDRINCL)
+ case SOCKET_OPT_IP_HDRINCL:
+ result = nsetopt_lvl_ip_hdrincl(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_MINTTL)
+ case SOCKET_OPT_IP_MINTTL:
+ result = nsetopt_lvl_ip_minttl(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_MSFILTER) && defined(IP_MSFILTER_SIZE)
+ case SOCKET_OPT_IP_MSFILTER:
+ result = nsetopt_lvl_ip_msfilter(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_MTU_DISCOVER)
+ case SOCKET_OPT_IP_MTU_DISCOVER:
+ result = nsetopt_lvl_ip_mtu_discover(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_MULTICAST_ALL)
+ case SOCKET_OPT_IP_MULTICAST_ALL:
+ result = nsetopt_lvl_ip_multicast_all(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_MULTICAST_IF)
+ case SOCKET_OPT_IP_MULTICAST_IF:
+ result = nsetopt_lvl_ip_multicast_if(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_MULTICAST_LOOP)
+ case SOCKET_OPT_IP_MULTICAST_LOOP:
+ result = nsetopt_lvl_ip_multicast_loop(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_MULTICAST_TTL)
+ case SOCKET_OPT_IP_MULTICAST_TTL:
+ result = nsetopt_lvl_ip_multicast_ttl(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_NODEFRAG)
+ case SOCKET_OPT_IP_NODEFRAG:
+ result = nsetopt_lvl_ip_nodefrag(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_PKTINFO)
+ case SOCKET_OPT_IP_PKTINFO:
+ result = nsetopt_lvl_ip_pktinfo(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_RECVDSTADDR)
+ case SOCKET_OPT_IP_RECVDSTADDR:
+ result = nsetopt_lvl_ip_recvdstaddr(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_RECVERR)
+ case SOCKET_OPT_IP_RECVERR:
+ result = nsetopt_lvl_ip_recverr(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_RECVIF)
+ case SOCKET_OPT_IP_RECVIF:
+ result = nsetopt_lvl_ip_recvif(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_RECVOPTS)
+ case SOCKET_OPT_IP_RECVOPTS:
+ result = nsetopt_lvl_ip_recvopts(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_RECVORIGDSTADDR)
+ case SOCKET_OPT_IP_RECVORIGDSTADDR:
+ result = nsetopt_lvl_ip_recvorigdstaddr(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_RECVTOS)
+ case SOCKET_OPT_IP_RECVTOS:
+ result = nsetopt_lvl_ip_recvtos(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_RECVTTL)
+ case SOCKET_OPT_IP_RECVTTL:
+ result = nsetopt_lvl_ip_recvttl(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_RETOPTS)
+ case SOCKET_OPT_IP_RETOPTS:
+ result = nsetopt_lvl_ip_retopts(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_ROUTER_ALERT)
+ case SOCKET_OPT_IP_ROUTER_ALERT:
+ result = nsetopt_lvl_ip_router_alert(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_SENDSRCADDR)
+ case SOCKET_OPT_IP_SENDSRCADDR:
+ result = nsetopt_lvl_ip_sendsrcaddr(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_TOS)
+ case SOCKET_OPT_IP_TOS:
+ result = nsetopt_lvl_ip_tos(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_TRANSPARENT)
+ case SOCKET_OPT_IP_TRANSPARENT:
+ result = nsetopt_lvl_ip_transparent(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_TTL)
+ case SOCKET_OPT_IP_TTL:
+ result = nsetopt_lvl_ip_ttl(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IP_UNBLOCK_SOURCE)
+ case SOCKET_OPT_IP_UNBLOCK_SOURCE:
+ result = nsetopt_lvl_ip_unblock_source(env, descP, eVal);
+ break;
+#endif
+
+ default:
+ SSDBG( descP, ("SOCKET", "nsetopt_lvl_ip -> unknown opt (%d)\r\n", eOpt) );
+ result = esock_make_error(env, esock_atom_einval);
+ break;
+ }
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_ip -> done when"
+ "\r\n result: %T"
+ "\r\n", result) );
+
+ return result;
+}
+
+
+/* nsetopt_lvl_ip_add_membership - Level IP ADD_MEMBERSHIP option
+ *
+ * The value is a map with two attributes: multiaddr and interface.
+ * The attribute 'multiaddr' is always a 4-tuple (IPv4 address).
+ * The attribute 'interface' is either the atom 'any' or a 4-tuple
+ * (IPv4 address).
+ */
+#if defined(IP_ADD_MEMBERSHIP)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_add_membership(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_lvl_ip_update_membership(env, descP, eVal, IP_ADD_MEMBERSHIP);
+}
+#endif
+
+
+/* nsetopt_lvl_ip_add_source_membership - Level IP ADD_SOURCE_MEMBERSHIP option
+ *
+ * The value is a map with three attributes: multiaddr, interface and
+ * sourceaddr.
+ * The attribute 'multiaddr' is always a 4-tuple (IPv4 address).
+ * The attribute 'interface' is always a 4-tuple (IPv4 address).
+ * The attribute 'sourceaddr' is always a 4-tuple (IPv4 address).
+ * (IPv4 address).
+ */
+#if defined(IP_ADD_SOURCE_MEMBERSHIP)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_add_source_membership(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_lvl_ip_update_source(env, descP, eVal,
+ IP_ADD_SOURCE_MEMBERSHIP);
+}
+#endif
+
+
+/* nsetopt_lvl_ip_block_source - Level IP BLOCK_SOURCE option
+ *
+ * The value is a map with three attributes: multiaddr, interface and
+ * sourceaddr.
+ * The attribute 'multiaddr' is always a 4-tuple (IPv4 address).
+ * The attribute 'interface' is always a 4-tuple (IPv4 address).
+ * The attribute 'sourceaddr' is always a 4-tuple (IPv4 address).
+ * (IPv4 address).
+ */
+#if defined(IP_BLOCK_SOURCE)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_block_source(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_lvl_ip_update_source(env, descP, eVal, IP_BLOCK_SOURCE);
+}
+#endif
+
+
+/* nsetopt_lvl_ip_drop_membership - Level IP DROP_MEMBERSHIP option
+ *
+ * The value is a map with two attributes: multiaddr and interface.
+ * The attribute 'multiaddr' is always a 4-tuple (IPv4 address).
+ * The attribute 'interface' is either the atom 'any' or a 4-tuple
+ * (IPv4 address).
+ *
+ * We should really have a common function with add_membership,
+ * since the code is virtually identical (except for the option
+ * value).
+ */
+#if defined(IP_DROP_MEMBERSHIP)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_drop_membership(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_lvl_ip_update_membership(env, descP, eVal,
+ IP_DROP_MEMBERSHIP);
+}
+#endif
+
+
+
+/* nsetopt_lvl_ip_drop_source_membership - Level IP DROP_SOURCE_MEMBERSHIP option
+ *
+ * The value is a map with three attributes: multiaddr, interface and
+ * sourceaddr.
+ * The attribute 'multiaddr' is always a 4-tuple (IPv4 address).
+ * The attribute 'interface' is always a 4-tuple (IPv4 address).
+ * The attribute 'sourceaddr' is always a 4-tuple (IPv4 address).
+ * (IPv4 address).
+ */
+#if defined(IP_DROP_SOURCE_MEMBERSHIP)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_drop_source_membership(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_lvl_ip_update_source(env, descP, eVal,
+ IP_DROP_SOURCE_MEMBERSHIP);
+}
+#endif
+
+
+
+/* nsetopt_lvl_ip_freebind - Level IP FREEBIND option
+ */
+#if defined(IP_FREEBIND)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_freebind(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return nsetopt_bool_opt(env, descP, level, IP_FREEBIND, eVal);
+}
+#endif
+
+
+
+/* nsetopt_lvl_ip_hdrincl - Level IP HDRINCL option
+ */
+#if defined(IP_HDRINCL)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_hdrincl(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return nsetopt_bool_opt(env, descP, level, IP_HDRINCL, eVal);
+}
+#endif
+
+
+
+/* nsetopt_lvl_ip_minttl - Level IP MINTTL option
+ */
+#if defined(IP_MINTTL)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_minttl(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return nsetopt_int_opt(env, descP, level, IP_MINTTL, eVal);
+}
+#endif
+
+
+
+/* nsetopt_lvl_ip_msfilter - Level IP MSFILTER option
+ *
+ * The value can be *either* the atom 'null' or a map of type ip_msfilter().
+ */
+#if defined(IP_MSFILTER) && defined(IP_MSFILTER_SIZE)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_msfilter(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ ERL_NIF_TERM result;
+
+ if (COMPARE(eVal, atom_null) == 0) {
+ return nsetopt_lvl_ip_msfilter_set(env, descP->sock, NULL, 0);
+ } else {
+ struct ip_msfilter* msfP;
+ Uint32 msfSz;
+ ERL_NIF_TERM eMultiAddr, eInterface, eFMode, eSList, elem, tail;
+ size_t sz;
+ unsigned int slistLen, idx;
+
+ if (!IS_MAP(env, eVal))
+ return esock_make_error(env, esock_atom_einval);
+
+ // It must have atleast four attributes
+ if (!enif_get_map_size(env, eVal, &sz) || (sz < 4))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (!GET_MAP_VAL(env, eVal, atom_multiaddr, &eMultiAddr))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (!GET_MAP_VAL(env, eVal, atom_interface, &eInterface))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (!GET_MAP_VAL(env, eVal, atom_mode, &eFMode))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (!GET_MAP_VAL(env, eVal, atom_slist, &eSList))
+ return esock_make_error(env, esock_atom_einval);
+
+ /* We start (decoding) with the slist, since without it we don't
+ * really know how much (memory) to allocate.
+ */
+ if (!GET_LIST_LEN(env, eSList, &slistLen))
+ return esock_make_error(env, esock_atom_einval);
+
+ msfSz = IP_MSFILTER_SIZE(slistLen);
+ msfP = MALLOC(msfSz);
+
+ if (!esock_decode_ip4_address(env, eMultiAddr, &msfP->imsf_multiaddr)) {
+ FREE(msfP);
+ return esock_make_error(env, esock_atom_einval);
+ }
+
+ if (!esock_decode_ip4_address(env, eInterface, &msfP->imsf_interface)) {
+ FREE(msfP);
+ return esock_make_error(env, esock_atom_einval);
+ }
+
+ if (!decode_ip_msfilter_mode(env, eFMode, &msfP->imsf_fmode)) {
+ FREE(msfP);
+ return esock_make_error(env, esock_atom_einval);
+ }
+
+ /* And finally, extract the source addresses */
+ msfP->imsf_numsrc = slistLen;
+ for (idx = 0; idx < slistLen; idx++) {
+ if (GET_LIST_ELEM(env, eSList, &elem, &tail)) {
+ if (!esock_decode_ip4_address(env, elem, &msfP->imsf_slist[idx])) {
+ FREE(msfP);
+ return esock_make_error(env, esock_atom_einval);
+ } else {
+ eSList = tail;
+ }
+ }
+ }
+
+ /* And now, finally, set the option */
+ result = nsetopt_lvl_ip_msfilter_set(env, descP->sock, msfP, msfSz);
+ FREE(msfP);
+ return result;
+ }
+
+}
+
+
+static
+BOOLEAN_T decode_ip_msfilter_mode(ErlNifEnv* env,
+ ERL_NIF_TERM eVal,
+ Uint32* mode)
+{
+ BOOLEAN_T result;
+
+ if (COMPARE(eVal, atom_include) == 0) {
+ *mode = MCAST_INCLUDE;
+ result = TRUE;
+ } else if (COMPARE(eVal, atom_exclude) == 0) {
+ *mode = MCAST_EXCLUDE;
+ result = TRUE;
+ } else {
+ result = FALSE;
+ }
+
+ return result;
+}
+
+
+static
+ERL_NIF_TERM nsetopt_lvl_ip_msfilter_set(ErlNifEnv* env,
+ SOCKET sock,
+ struct ip_msfilter* msfP,
+ SOCKLEN_T optLen)
+{
+ ERL_NIF_TERM result;
+ int res;
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ res = socket_setopt(sock, level, IP_MSFILTER, (void*) msfP, optLen);
+ if (res != 0)
+ result = esock_make_error_errno(env, sock_errno());
+ else
+ result = esock_atom_ok;
+
+ return result;
+}
+#endif // IP_MSFILTER
+
+
+
+/* nsetopt_lvl_ip_mtu_discover - Level IP MTU_DISCOVER option
+ *
+ * The value is an atom of the type ip_pmtudisc().
+ */
+#if defined(IP_MTU_DISCOVER)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_mtu_discover(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ ERL_NIF_TERM result;
+ int val;
+ char* xres;
+ int res;
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ if ((xres = decode_ip_pmtudisc(env, eVal, &val)) != NULL) {
+
+ result = esock_make_error_str(env, xres);
+
+ } else {
+
+ res = socket_setopt(descP->sock, level, IP_MTU_DISCOVER,
+ &val, sizeof(val));
+
+ if (res != 0)
+ result = esock_make_error_errno(env, sock_errno());
+ else
+ result = esock_atom_ok;
+
+ }
+
+ return result;
+}
+#endif
+
+
+/* nsetopt_lvl_ip_multicast_all - Level IP MULTICAST_ALL option
+ */
+#if defined(IP_MULTICAST_ALL)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_multicast_all(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return nsetopt_bool_opt(env, descP, level, IP_MULTICAST_ALL, eVal);
+}
+#endif
+
+
+/* nsetopt_lvl_ip_multicast_if - Level IP MULTICAST_IF option
+ *
+ * The value is either the atom 'any' or a 4-tuple.
+ */
+#if defined(IP_MULTICAST_IF)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_multicast_if(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ ERL_NIF_TERM result;
+ struct in_addr ifAddr;
+ char* xres;
+ int res;
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ if ((xres = esock_decode_ip4_address(env, eVal, &ifAddr)) != NULL) {
+ result = esock_make_error_str(env, xres);
+ } else {
+
+ res = socket_setopt(descP->sock, level, IP_MULTICAST_LOOP,
+ &ifAddr, sizeof(ifAddr));
+
+ if (res != 0)
+ result = esock_make_error_errno(env, sock_errno());
+ else
+ result = esock_atom_ok;
+
+ }
+
+ return result;
+}
+#endif
+
+
+/* nsetopt_lvl_ip_multicast_loop - Level IP MULTICAST_LOOP option
+ */
+#if defined(IP_MULTICAST_LOOP)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_multicast_loop(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return nsetopt_bool_opt(env, descP, level, IP_MULTICAST_LOOP, eVal);
+}
+#endif
+
+
+/* nsetopt_lvl_ip_multicast_ttl - Level IP MULTICAST_TTL option
+ */
+#if defined(IP_MULTICAST_TTL)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_multicast_ttl(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return nsetopt_int_opt(env, descP, level, IP_MULTICAST_TTL, eVal);
+}
+#endif
+
+
+/* nsetopt_lvl_ip_nodefrag - Level IP NODEFRAG option
+ */
+#if defined(IP_NODEFRAG)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_nodefrag(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return nsetopt_bool_opt(env, descP, level, IP_NODEFRAG, eVal);
+}
+#endif
+
+
+/* nsetopt_lvl_ip_pktinfo - Level IP PKTINFO option
+ */
+#if defined(IP_PKTINFO)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_pktinfo(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return nsetopt_bool_opt(env, descP, level, IP_PKTINFO, eVal);
+}
+#endif
+
+
+/* nsetopt_lvl_ip_recvdstaddr - Level IP RECVDSTADDR option
+ */
+#if defined(IP_RECVDSTADDR)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_recvdstaddr(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return nsetopt_bool_opt(env, descP, level, IP_RECVDSTADDR, eVal);
+}
+#endif
+
+
+/* nsetopt_lvl_ip_recverr - Level IP RECVERR option
+ */
+#if defined(IP_RECVERR)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_recverr(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return nsetopt_bool_opt(env, descP, level, IP_RECVERR, eVal);
+}
+#endif
+
+
+/* nsetopt_lvl_ip_recvif - Level IP RECVIF option
+ */
+#if defined(IP_RECVIF)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_recvif(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return nsetopt_bool_opt(env, descP, level, IP_RECVIF, eVal);
+}
+#endif
+
+
+/* nsetopt_lvl_ip_recvopts - Level IP RECVOPTS option
+ */
+#if defined(IP_RECVOPTS)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_recvopts(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return nsetopt_bool_opt(env, descP, level, IP_RECVOPTS, eVal);
+}
+#endif
+
+
+/* nsetopt_lvl_ip_recvorigdstaddr - Level IP RECVORIGDSTADDR option
+ */
+#if defined(IP_RECVORIGDSTADDR)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_recvorigdstaddr(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return nsetopt_bool_opt(env, descP, level, IP_RECVORIGDSTADDR, eVal);
+}
+#endif
+
+
+/* nsetopt_lvl_ip_recvtos - Level IP RECVTOS option
+ */
+#if defined(IP_RECVTOS)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_recvtos(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return nsetopt_bool_opt(env, descP, level, IP_RECVTOS, eVal);
+}
+#endif
+
+
+/* nsetopt_lvl_ip_recvttl - Level IP RECVTTL option
+ */
+#if defined(IP_RECVTTL)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_recvttl(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return nsetopt_bool_opt(env, descP, level, IP_RECVTTL, eVal);
+}
+#endif
+
+
+/* nsetopt_lvl_ip_retopts - Level IP RETOPTS option
+ */
+#if defined(IP_RETOPTS)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_retopts(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return nsetopt_bool_opt(env, descP, level, IP_RETOPTS, eVal);
+}
+#endif
+
+
+/* nsetopt_lvl_ip_router_alert - Level IP ROUTER_ALERT option
+ */
+#if defined(IP_ROUTER_ALERT)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_router_alert(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return nsetopt_int_opt(env, descP, level, IP_ROUTER_ALERT, eVal);
+}
+#endif
+
+
+/* nsetopt_lvl_ip_sendsrcaddr - Level IP SENDSRCADDR option
+ */
+#if defined(IP_SENDSRCADDR)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_sendsrcaddr(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return nsetopt_bool_opt(env, descP, level, IP_SENDSRCADDR, eVal);
+}
+#endif
+
+
+/* nsetopt_lvl_ip_tos - Level IP TOS option
+ */
+#if defined(IP_TOS)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_tos(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+ ERL_NIF_TERM result;
+ int val;
+
+ if (decode_ip_tos(env, eVal, &val)) {
+ int res = socket_setopt(descP->sock, level, IP_TOS, &val, sizeof(val));
+
+ if (res != 0)
+ result = esock_make_error_errno(env, sock_errno());
+ else
+ result = esock_atom_ok;
+
+ } else {
+ result = esock_make_error(env, esock_atom_einval);
+ }
+
+ return result;
+}
+#endif
+
+
+/* nsetopt_lvl_ip_transparent - Level IP TRANSPARENT option
+ */
+#if defined(IP_TRANSPARENT)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_transparent(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return nsetopt_bool_opt(env, descP, level, IP_TRANSPARENT, eVal);
+}
+#endif
+
+
+
+/* nsetopt_lvl_ip_ttl - Level IP TTL option
+ */
+#if defined(IP_TTL)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_ttl(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return nsetopt_int_opt(env, descP, level, IP_TTL, eVal);
+}
+#endif
+
+
+
+/* nsetopt_lvl_ip_unblock_source - Level IP UNBLOCK_SOURCE option
+ *
+ * The value is a map with three attributes: multiaddr, interface and
+ * sourceaddr.
+ * The attribute 'multiaddr' is always a 4-tuple (IPv4 address).
+ * The attribute 'interface' is always a 4-tuple (IPv4 address).
+ * The attribute 'sourceaddr' is always a 4-tuple (IPv4 address).
+ * (IPv4 address).
+ */
+#if defined(IP_UNBLOCK_SOURCE)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_unblock_source(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_lvl_ip_update_source(env, descP, eVal, IP_UNBLOCK_SOURCE);
+}
+#endif
+
+
+
+#if defined(IP_ADD_MEMBERSHIP) || defined(IP_DROP_MEMBERSHIP)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_update_membership(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal,
+ int opt)
+{
+ ERL_NIF_TERM result, eMultiAddr, eInterface;
+ struct ip_mreq mreq;
+ char* xres;
+ int res;
+ size_t sz;
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ // It must be a map
+ if (!IS_MAP(env, eVal))
+ return enif_make_badarg(env);
+
+ // It must have atleast two attributes
+ if (!enif_get_map_size(env, eVal, &sz) || (sz >= 2))
+ return enif_make_badarg(env);
+
+ if (!GET_MAP_VAL(env, eVal, atom_multiaddr, &eMultiAddr))
+ return enif_make_badarg(env);
+
+ if (!GET_MAP_VAL(env, eVal, atom_interface, &eInterface))
+ return enif_make_badarg(env);
+
+ if ((xres = esock_decode_ip4_address(env,
+ eMultiAddr,
+ &mreq.imr_multiaddr)) != NULL)
+ return esock_make_error_str(env, xres);
+
+ if ((xres = esock_decode_ip4_address(env,
+ eInterface,
+ &mreq.imr_interface)) != NULL)
+ return esock_make_error_str(env, xres);
+
+ res = socket_setopt(descP->sock, level, opt, &mreq, sizeof(mreq));
+
+ if (res != 0)
+ result = esock_make_error_errno(env, sock_errno());
+ else
+ result = esock_atom_ok;
+
+ return result;
+}
+#endif
+
+
+#if defined(IP_ADD_SOURCE_MEMBERSHIP) || defined(IP_DROP_SOURCE_MEMBERSHIP) || defined(IP_BLOCK_SOURCE) || defined(IP_UNBLOCK_SOURCE)
+static
+ERL_NIF_TERM nsetopt_lvl_ip_update_source(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal,
+ int opt)
+{
+ ERL_NIF_TERM result, eMultiAddr, eInterface, eSourceAddr;
+ struct ip_mreq_source mreq;
+ char* xres;
+ int res;
+ size_t sz;
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ // It must be a map
+ if (!IS_MAP(env, eVal))
+ return enif_make_badarg(env);
+
+ // It must have atleast three attributes
+ if (!enif_get_map_size(env, eVal, &sz) || (sz >= 3))
+ return enif_make_badarg(env);
+
+ if (!GET_MAP_VAL(env, eVal, atom_multiaddr, &eMultiAddr))
+ return enif_make_badarg(env);
+
+ if (!GET_MAP_VAL(env, eVal, atom_interface, &eInterface))
+ return enif_make_badarg(env);
+
+ if (!GET_MAP_VAL(env, eVal, atom_sourceaddr, &eSourceAddr))
+ return enif_make_badarg(env);
+
+ if ((xres = esock_decode_ip4_address(env,
+ eMultiAddr,
+ &mreq.imr_multiaddr)) != NULL)
+ return esock_make_error_str(env, xres);
+
+ if ((xres = esock_decode_ip4_address(env,
+ eInterface,
+ &mreq.imr_interface)) != NULL)
+ return esock_make_error_str(env, xres);
+
+ if ((xres = esock_decode_ip4_address(env,
+ eSourceAddr,
+ &mreq.imr_sourceaddr)) != NULL)
+ return esock_make_error_str(env, xres);
+
+ res = socket_setopt(descP->sock, level, opt, &mreq, sizeof(mreq));
+
+ if (res != 0)
+ result = esock_make_error_errno(env, sock_errno());
+ else
+ result = esock_atom_ok;
+
+ return result;
+}
+#endif
+
+
+
+/* *** Handling set of socket options for level = ipv6 *** */
+
+/* nsetopt_lvl_ipv6 - Level *IPv6* option(s)
+ */
+#if defined(HAVE_IPV6)
+static
+ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt,
+ ERL_NIF_TERM eVal)
+{
+ ERL_NIF_TERM result;
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_ipv6 -> entry with"
+ "\r\n opt: %d"
+ "\r\n", eOpt) );
+
+ switch (eOpt) {
+#if defined(IPV6_ADDRFORM)
+ case SOCKET_OPT_IPV6_ADDRFORM:
+ result = nsetopt_lvl_ipv6_addrform(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IPV6_ADD_MEMBERSHIP)
+ case SOCKET_OPT_IPV6_ADD_MEMBERSHIP:
+ result = nsetopt_lvl_ipv6_add_membership(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IPV6_AUTHHDR)
+ case SOCKET_OPT_IPV6_AUTHHDR:
+ result = nsetopt_lvl_ipv6_authhdr(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IPV6_DROP_MEMBERSHIP)
+ case SOCKET_OPT_IPV6_DROP_MEMBERSHIP:
+ result = nsetopt_lvl_ipv6_drop_membership(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IPV6_DSTOPTS)
+ case SOCKET_OPT_IPV6_DSTOPTS:
+ result = nsetopt_lvl_ipv6_dstopts(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IPV6_FLOWINFO)
+ case SOCKET_OPT_IPV6_FLOWINFO:
+ result = nsetopt_lvl_ipv6_flowinfo(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IPV6_HOPLIMIT)
+ case SOCKET_OPT_IPV6_HOPLIMIT:
+ result = nsetopt_lvl_ipv6_hoplimit(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IPV6_HOPOPTS)
+ case SOCKET_OPT_IPV6_HOPOPTS:
+ result = nsetopt_lvl_ipv6_hopopts(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IPV6_MTU)
+ case SOCKET_OPT_IPV6_MTU:
+ result = nsetopt_lvl_ipv6_mtu(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IPV6_MTU_DISCOVER)
+ case SOCKET_OPT_IPV6_MTU_DISCOVER:
+ result = nsetopt_lvl_ipv6_mtu_discover(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IPV6_MULTICAST_HOPS)
+ case SOCKET_OPT_IPV6_MULTICAST_HOPS:
+ result = nsetopt_lvl_ipv6_multicast_hops(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IPV6_MULTICAST_IF)
+ case SOCKET_OPT_IPV6_MULTICAST_IF:
+ result = nsetopt_lvl_ipv6_multicast_if(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IPV6_MULTICAST_LOOP)
+ case SOCKET_OPT_IPV6_MULTICAST_LOOP:
+ result = nsetopt_lvl_ipv6_multicast_loop(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IPV6_RECVERR)
+ case SOCKET_OPT_IPV6_RECVERR:
+ result = nsetopt_lvl_ipv6_recverr(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO)
+ case SOCKET_OPT_IPV6_RECVPKTINFO:
+ result = nsetopt_lvl_ipv6_recvpktinfo(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IPV6_ROUTER_ALERT)
+ case SOCKET_OPT_IPV6_ROUTER_ALERT:
+ result = nsetopt_lvl_ipv6_router_alert(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IPV6_RTHDR)
+ case SOCKET_OPT_IPV6_RTHDR:
+ result = nsetopt_lvl_ipv6_rthdr(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IPV6_UNICAST_HOPS)
+ case SOCKET_OPT_IPV6_UNICAST_HOPS:
+ result = nsetopt_lvl_ipv6_unicast_hops(env, descP, eVal);
+ break;
+#endif
+
+#if defined(IPV6_V6ONLY)
+ case SOCKET_OPT_IPV6_V6ONLY:
+ result = nsetopt_lvl_ipv6_v6only(env, descP, eVal);
+ break;
+#endif
+
+ default:
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_ipv6 -> unknown opt (%d)\r\n", eOpt) );
+ result = esock_make_error(env, esock_atom_einval);
+ break;
+ }
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_ipv6 -> done when"
+ "\r\n result: %T"
+ "\r\n", result) );
+
+ return result;
+}
+
+
+#if defined(IPV6_ADDRFORM)
+static
+ERL_NIF_TERM nsetopt_lvl_ipv6_addrform(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ ERL_NIF_TERM result;
+ int res, edomain, domain;
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_ipv6_addrform -> entry with"
+ "\r\n eVal: %T"
+ "\r\n", eVal) );
+
+ if (!GET_INT(env, eVal, &edomain))
+ return esock_make_error(env, esock_atom_einval);
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_ipv6_addrform -> decode"
+ "\r\n edomain: %d"
+ "\r\n", edomain) );
+
+ if (!edomain2domain(edomain, &domain))
+ return esock_make_error(env, esock_atom_einval);
+
+ SSDBG( descP, ("SOCKET", "nsetopt_lvl_ipv6_addrform -> try set opt to %d\r\n",
+ domain) );
+
+ res = socket_setopt(descP->sock,
+ SOL_IPV6, IPV6_ADDRFORM,
+ &domain, sizeof(domain));
+
+ if (res != 0)
+ result = esock_make_error_errno(env, sock_errno());
+ else
+ result = esock_atom_ok;
+
+ return result;
+}
+#endif
+
+
+#if defined(IPV6_ADD_MEMBERSHIP)
+static
+ERL_NIF_TERM nsetopt_lvl_ipv6_add_membership(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_lvl_ipv6_update_membership(env, descP, eVal,
+ IPV6_ADD_MEMBERSHIP);
+}
+#endif
+
+
+#if defined(IPV6_AUTHHDR)
+static
+ERL_NIF_TERM nsetopt_lvl_ipv6_authhdr(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_bool_opt(env, descP, SOL_IPV6, IPV6_AUTHHDR, eVal);
+}
+#endif
+
+
+#if defined(IPV6_DROP_MEMBERSHIP)
+static
+ERL_NIF_TERM nsetopt_lvl_ipv6_drop_membership(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_lvl_ipv6_update_membership(env, descP, eVal,
+ IPV6_DROP_MEMBERSHIP);
+}
+#endif
+
+
+#if defined(IPV6_DSTOPTS)
+static
+ERL_NIF_TERM nsetopt_lvl_ipv6_dstopts(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+ return nsetopt_bool_opt(env, descP, level, IPV6_DSTOPTS, eVal);
+}
+#endif
+
+
+#if defined(IPV6_FLOWINFO)
+static
+ERL_NIF_TERM nsetopt_lvl_ipv6_flowinfo(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+ return nsetopt_bool_opt(env, descP, level, IPV6_FLOWINFO, eVal);
+}
+#endif
+
+
+#if defined(IPV6_HOPLIMIT)
+static
+ERL_NIF_TERM nsetopt_lvl_ipv6_hoplimit(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+ return nsetopt_bool_opt(env, descP, level, IPV6_HOPLIMIT, eVal);
+}
+#endif
+
+
+#if defined(IPV6_HOPOPTS)
+static
+ERL_NIF_TERM nsetopt_lvl_ipv6_hopopts(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+ return nsetopt_bool_opt(env, descP, level, IPV6_HOPOPTS, eVal);
+}
+#endif
+
+
+#if defined(IPV6_MTU)
+static
+ERL_NIF_TERM nsetopt_lvl_ipv6_mtu(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+ return nsetopt_int_opt(env, descP, level, IPV6_MTU, eVal);
+}
+#endif
+
+
+/* nsetopt_lvl_ipv6_mtu_discover - Level IPv6 MTU_DISCOVER option
+ *
+ * The value is an atom of the type ipv6_pmtudisc().
+ */
+#if defined(IPV6_MTU_DISCOVER)
+static
+ERL_NIF_TERM nsetopt_lvl_ipv6_mtu_discover(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ ERL_NIF_TERM result;
+ int val;
+ char* xres;
+ int res;
+
+ if ((xres = decode_ipv6_pmtudisc(env, eVal, &val)) != NULL) {
+
+ result = esock_make_error_str(env, xres);
+
+ } else {
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+
+ res = socket_setopt(descP->sock, level, IPV6_MTU_DISCOVER,
+ &val, sizeof(val));
+
+ if (res != 0)
+ result = esock_make_error_errno(env, sock_errno());
+ else
+ result = esock_atom_ok;
+
+ }
+
+ return result;
+}
+#endif
+
+
+#if defined(IPV6_MULTICAST_HOPS)
+static
+ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_hops(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+ return nsetopt_int_opt(env, descP, level, IPV6_MULTICAST_HOPS, eVal);
+}
+#endif
+
+
+
+#if defined(IPV6_MULTICAST_IF)
+static
+ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_if(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+ return nsetopt_int_opt(env, descP, level, IPV6_MULTICAST_IF, eVal);
+}
+#endif
+
+
+
+#if defined(IPV6_MULTICAST_LOOP)
+static
+ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_loop(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+ return nsetopt_bool_opt(env, descP, level, IPV6_MULTICAST_LOOP, eVal);
+}
+#endif
+
+
+#if defined(IPV6_RECVERR)
+static
+ERL_NIF_TERM nsetopt_lvl_ipv6_recverr(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+ return nsetopt_bool_opt(env, descP, level, IPV6_RECVERR, eVal);
+}
+#endif
+
+
+#if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO)
+static
+ERL_NIF_TERM nsetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+#if defined(IPV6_RECVPKTINFO)
+ int opt = IPV6_RECVPKTINFO;
+#else
+ int opt = IPV6_PKTINFO;
+#endif
+
+ return nsetopt_bool_opt(env, descP, level, opt, eVal);
+}
+#endif
+
+
+#if defined(IPV6_ROUTER_ALERT)
+static
+ERL_NIF_TERM nsetopt_lvl_ipv6_router_alert(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+ return nsetopt_int_opt(env, descP, level, IPV6_ROUTER_ALERT, eVal);
+}
+#endif
+
+
+
+#if defined(IPV6_RTHDR)
+static
+ERL_NIF_TERM nsetopt_lvl_ipv6_rthdr(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+ return nsetopt_bool_opt(env, descP, level, IPV6_RTHDR, eVal);
+}
+#endif
+
+
+#if defined(IPV6_UNICAST_HOPS)
+static
+ERL_NIF_TERM nsetopt_lvl_ipv6_unicast_hops(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+ return nsetopt_int_opt(env, descP, level, IPV6_UNICAST_HOPS, eVal);
+}
+#endif
+
+
+
+#if defined(IPV6_V6ONLY)
+static
+ERL_NIF_TERM nsetopt_lvl_ipv6_v6only(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+ return nsetopt_bool_opt(env, descP, level, IPV6_V6ONLY, eVal);
+}
+#endif
+
+
+#if defined(IPV6_ADD_MEMBERSHIP) || defined(IPV6_DROP_MEMBERSHIP)
+static
+ERL_NIF_TERM nsetopt_lvl_ipv6_update_membership(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal,
+ int opt)
+{
+ ERL_NIF_TERM result, eMultiAddr, eInterface;
+ struct ipv6_mreq mreq;
+ char* xres;
+ int res;
+ size_t sz;
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+ // It must be a map
+ if (!IS_MAP(env, eVal))
+ return enif_make_badarg(env);
+
+ // It must have atleast two attributes
+ if (!enif_get_map_size(env, eVal, &sz) || (sz >= 2))
+ return enif_make_badarg(env);
+
+ if (!GET_MAP_VAL(env, eVal, atom_multiaddr, &eMultiAddr))
+ return enif_make_badarg(env);
+
+ if (!GET_MAP_VAL(env, eVal, atom_interface, &eInterface))
+ return enif_make_badarg(env);
+
+ if ((xres = esock_decode_ip6_address(env,
+ eMultiAddr,
+ &mreq.ipv6mr_multiaddr)) != NULL)
+ return esock_make_error_str(env, xres);
+
+ if (!GET_UINT(env, eInterface, &mreq.ipv6mr_interface))
+ return esock_make_error(env, esock_atom_einval);
+
+ res = socket_setopt(descP->sock, level, opt, &mreq, sizeof(mreq));
+
+ if (res != 0)
+ result = esock_make_error_errno(env, sock_errno());
+ else
+ result = esock_atom_ok;
+
+ return result;
+}
+#endif
+
+
+
+#endif // defined(HAVE_IPV6)
+
+
+
+/* nsetopt_lvl_tcp - Level *TCP* option(s)
+ */
+static
+ERL_NIF_TERM nsetopt_lvl_tcp(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt,
+ ERL_NIF_TERM eVal)
+{
+ ERL_NIF_TERM result;
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_tcp -> entry with"
+ "\r\n opt: %d"
+ "\r\n", eOpt) );
+
+ switch (eOpt) {
+#if defined(TCP_CONGESTION)
+ case SOCKET_OPT_TCP_CONGESTION:
+ result = nsetopt_lvl_tcp_congestion(env, descP, eVal);
+ break;
+#endif
+
+#if defined(TCP_MAXSEG)
+ case SOCKET_OPT_TCP_MAXSEG:
+ result = nsetopt_lvl_tcp_maxseg(env, descP, eVal);
+ break;
+#endif
+
+#if defined(TCP_NODELAY)
+ case SOCKET_OPT_TCP_NODELAY:
+ result = nsetopt_lvl_tcp_nodelay(env, descP, eVal);
+ break;
+#endif
+
+ default:
+ result = esock_make_error(env, esock_atom_einval);
+ break;
+ }
+
+ return result;
+}
+
+
+/* nsetopt_lvl_tcp_congestion - Level TCP CONGESTION option
+ */
+#if defined(TCP_CONGESTION)
+static
+ERL_NIF_TERM nsetopt_lvl_tcp_congestion(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ int max = SOCKET_OPT_TCP_CONGESTION_NAME_MAX+1;
+
+ return nsetopt_str_opt(env, descP, IPPROTO_TCP, TCP_CONGESTION, max, eVal);
+}
+#endif
+
+
+/* nsetopt_lvl_tcp_maxseg - Level TCP MAXSEG option
+ */
+#if defined(TCP_MAXSEG)
+static
+ERL_NIF_TERM nsetopt_lvl_tcp_maxseg(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_int_opt(env, descP, IPPROTO_TCP, TCP_MAXSEG, eVal);
+}
+#endif
+
+
+/* nsetopt_lvl_tcp_nodelay - Level TCP NODELAY option
+ */
+#if defined(TCP_NODELAY)
+static
+ERL_NIF_TERM nsetopt_lvl_tcp_nodelay(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_bool_opt(env, descP, IPPROTO_TCP, TCP_NODELAY, eVal);
+}
+#endif
+
+
+
+/* nsetopt_lvl_udp - Level *UDP* option(s)
+ */
+static
+ERL_NIF_TERM nsetopt_lvl_udp(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt,
+ ERL_NIF_TERM eVal)
+{
+ ERL_NIF_TERM result;
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_udp -> entry with"
+ "\r\n opt: %d"
+ "\r\n", eOpt) );
+
+ switch (eOpt) {
+#if defined(UDP_CORK)
+ case SOCKET_OPT_UDP_CORK:
+ result = nsetopt_lvl_udp_cork(env, descP, eVal);
+ break;
+#endif
+
+ default:
+ result = esock_make_error(env, esock_atom_einval);
+ break;
+ }
+
+ return result;
+}
+
+
+/* nsetopt_lvl_udp_cork - Level UDP CORK option
+ */
+#if defined(UDP_CORK)
+static
+ERL_NIF_TERM nsetopt_lvl_udp_cork(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_bool_opt(env, descP, IPPROTO_UDP, UDP_CORK, eVal);
+}
+#endif
+
+
+
+
+/* nsetopt_lvl_sctp - Level *SCTP* option(s)
+ */
+#if defined(HAVE_SCTP)
+static
+ERL_NIF_TERM nsetopt_lvl_sctp(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt,
+ ERL_NIF_TERM eVal)
+{
+ ERL_NIF_TERM result;
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_sctp -> entry with"
+ "\r\n opt: %d"
+ "\r\n", eOpt) );
+
+ switch (eOpt) {
+#if defined(SCTP_ASSOCINFO)
+ case SOCKET_OPT_SCTP_ASSOCINFO:
+ result = nsetopt_lvl_sctp_associnfo(env, descP, eVal);
+ break;
+#endif
+
+#if defined(SCTP_AUTOCLOSE)
+ case SOCKET_OPT_SCTP_AUTOCLOSE:
+ result = nsetopt_lvl_sctp_autoclose(env, descP, eVal);
+ break;
+#endif
+
+#if defined(SCTP_DISABLE_FRAGMENTS)
+ case SOCKET_OPT_SCTP_DISABLE_FRAGMENTS:
+ result = nsetopt_lvl_sctp_disable_fragments(env, descP, eVal);
+ break;
+#endif
+
+#if defined(SCTP_EVENTS)
+ case SOCKET_OPT_SCTP_EVENTS:
+ result = nsetopt_lvl_sctp_events(env, descP, eVal);
+ break;
+#endif
+
+#if defined(SCTP_INITMSG)
+ case SOCKET_OPT_SCTP_INITMSG:
+ result = nsetopt_lvl_sctp_initmsg(env, descP, eVal);
+ break;
+#endif
+
+#if defined(SCTP_MAXSEG)
+ case SOCKET_OPT_SCTP_MAXSEG:
+ result = nsetopt_lvl_sctp_maxseg(env, descP, eVal);
+ break;
+#endif
+
+#if defined(SCTP_NODELAY)
+ case SOCKET_OPT_SCTP_NODELAY:
+ result = nsetopt_lvl_sctp_nodelay(env, descP, eVal);
+ break;
+#endif
+
+#if defined(SCTP_RTOINFO)
+ case SOCKET_OPT_SCTP_RTOINFO:
+ result = nsetopt_lvl_sctp_rtoinfo(env, descP, eVal);
+ break;
+#endif
+
+ default:
+ result = esock_make_error(env, esock_atom_einval);
+ break;
+ }
+
+ return result;
+}
+
+
+/* nsetopt_lvl_sctp_associnfo - Level SCTP ASSOCINFO option
+ */
+#if defined(SCTP_ASSOCINFO)
+static
+ERL_NIF_TERM nsetopt_lvl_sctp_associnfo(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ ERL_NIF_TERM result;
+ ERL_NIF_TERM eAssocId, eMaxRxt, eNumPeerDests;
+ ERL_NIF_TERM ePeerRWND, eLocalRWND, eCookieLife;
+ struct sctp_assocparams assocParams;
+ int res;
+ size_t sz;
+ unsigned int tmp;
+ Sint32 tmpAssocId;
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_sctp_associnfo -> entry with"
+ "\r\n eVal: %T"
+ "\r\n", eVal) );
+
+ // It must be a map
+ if (!IS_MAP(env, eVal))
+ return esock_make_error(env, esock_atom_einval);
+
+ // It must have atleast ten attributes
+ if (!enif_get_map_size(env, eVal, &sz) || (sz < 6))
+ return esock_make_error(env, esock_atom_einval);
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_sctp_associnfo -> extract attributes\r\n") );
+
+ if (!GET_MAP_VAL(env, eVal, atom_assoc_id, &eAssocId))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (!GET_MAP_VAL(env, eVal, atom_max_rxt, &eMaxRxt))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (!GET_MAP_VAL(env, eVal, atom_num_peer_dests, &eNumPeerDests))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (!GET_MAP_VAL(env, eVal, atom_peer_rwnd, &ePeerRWND))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (!GET_MAP_VAL(env, eVal, atom_local_rwnd, &eLocalRWND))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (!GET_MAP_VAL(env, eVal, atom_cookie_life, &eCookieLife))
+ return esock_make_error(env, esock_atom_einval);
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_sctp_associnfo -> decode attributes\r\n") );
+
+ /* On some platforms the assoc id is typed as an unsigned integer (uint32)
+ * So, to avoid warnings there, we always make an explicit cast...
+ */
+ if (!GET_INT(env, eAssocId, &tmpAssocId))
+ return esock_make_error(env, esock_atom_einval);
+ assocParams.sasoc_assoc_id = (typeof(assocParams.sasoc_assoc_id)) tmpAssocId;
+
+ /*
+ * We should really make sure this is ok in erlang (to ensure that
+ * the values (max-rxt and num-peer-dests) fits in 16-bits).
+ * The value should be a 16-bit unsigned int...
+ * Both sasoc_asocmaxrxt and sasoc_number_peer_destinations.
+ */
+
+ if (!GET_UINT(env, eMaxRxt, &tmp))
+ return esock_make_error(env, esock_atom_einval);
+ assocParams.sasoc_asocmaxrxt = (Uint16) tmp;
+
+ if (!GET_UINT(env, eNumPeerDests, &tmp))
+ return esock_make_error(env, esock_atom_einval);
+ assocParams.sasoc_number_peer_destinations = (Uint16) tmp;
+
+ if (!GET_UINT(env, ePeerRWND, &assocParams.sasoc_peer_rwnd))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (!GET_UINT(env, eLocalRWND, &assocParams.sasoc_local_rwnd))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (!GET_UINT(env, eCookieLife, &assocParams.sasoc_cookie_life))
+ return esock_make_error(env, esock_atom_einval);
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_sctp_associnfo -> set associnfo option\r\n") );
+
+ res = socket_setopt(descP->sock, IPPROTO_SCTP, SCTP_ASSOCINFO,
+ &assocParams, sizeof(assocParams));
+
+ if (res != 0)
+ result = esock_make_error_errno(env, sock_errno());
+ else
+ result = esock_atom_ok;
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_sctp_associnfo -> done with"
+ "\r\n result: %T"
+ "\r\n", result) );
+
+ return result;
+
+}
+#endif
+
+
+/* nsetopt_lvl_sctp_autoclose - Level SCTP AUTOCLOSE option
+ */
+#if defined(SCTP_AUTOCLOSE)
+static
+ERL_NIF_TERM nsetopt_lvl_sctp_autoclose(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_int_opt(env, descP, IPPROTO_SCTP, SCTP_AUTOCLOSE, eVal);
+}
+#endif
+
+
+/* nsetopt_lvl_sctp_disable_fragments - Level SCTP DISABLE_FRAGMENTS option
+ */
+#if defined(SCTP_DISABLE_FRAGMENTS)
+static
+ERL_NIF_TERM nsetopt_lvl_sctp_disable_fragments(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_bool_opt(env, descP, IPPROTO_SCTP, SCTP_DISABLE_FRAGMENTS, eVal);
+}
+#endif
+
+
+/* nsetopt_lvl_sctp_events - Level SCTP EVENTS option
+ */
+#if defined(SCTP_EVENTS)
+static
+ERL_NIF_TERM nsetopt_lvl_sctp_events(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ ERL_NIF_TERM result;
+ ERL_NIF_TERM eDataIn, eAssoc, eAddr, eSndFailure;
+ ERL_NIF_TERM ePeerError, eShutdown, ePartialDelivery;
+ ERL_NIF_TERM eAdaptLayer, eAuth;
+#if defined(HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_SENDER_DRY_EVENT)
+ ERL_NIF_TERM eSndDry;
+#endif
+ struct sctp_event_subscribe events;
+ int res;
+ size_t sz;
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_sctp_events -> entry with"
+ "\r\n eVal: %T"
+ "\r\n", eVal) );
+
+ // It must be a map
+ if (!IS_MAP(env, eVal))
+ return esock_make_error(env, esock_atom_einval);
+
+ // It must have atleast ten attributes
+ if (!enif_get_map_size(env, eVal, &sz) || (sz < 10))
+ return esock_make_error(env, esock_atom_einval);
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_sctp_events -> extract attributes\r\n") );
+
+ if (!GET_MAP_VAL(env, eVal, atom_data_in, &eDataIn))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (!GET_MAP_VAL(env, eVal, atom_association, &eAssoc))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (!GET_MAP_VAL(env, eVal, atom_address, &eAddr))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (!GET_MAP_VAL(env, eVal, atom_send_failure, &eSndFailure))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (!GET_MAP_VAL(env, eVal, atom_peer_error, &ePeerError))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (!GET_MAP_VAL(env, eVal, atom_shutdown, &eShutdown))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (!GET_MAP_VAL(env, eVal, atom_partial_delivery, &ePartialDelivery))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (!GET_MAP_VAL(env, eVal, atom_adaptation_layer, &eAdaptLayer))
+ return esock_make_error(env, esock_atom_einval);
+
+#if defined(HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_AUTHENTICATION_EVENT)
+ if (!GET_MAP_VAL(env, eVal, atom_authentication, &eAuth))
+ return esock_make_error(env, esock_atom_einval);
+#endif
+
+#if defined(HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_SENDER_DRY_EVENT)
+ if (!GET_MAP_VAL(env, eVal, atom_sender_dry, &eSndDry))
+ return esock_make_error(env, esock_atom_einval);
+#endif
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_sctp_events -> decode attributes\r\n") );
+
+ events.sctp_data_io_event = esock_decode_bool(eDataIn);
+ events.sctp_association_event = esock_decode_bool(eAssoc);
+ events.sctp_address_event = esock_decode_bool(eAddr);
+ events.sctp_send_failure_event = esock_decode_bool(eSndFailure);
+ events.sctp_peer_error_event = esock_decode_bool(ePeerError);
+ events.sctp_shutdown_event = esock_decode_bool(eShutdown);
+ events.sctp_partial_delivery_event = esock_decode_bool(ePartialDelivery);
+ events.sctp_adaptation_layer_event = esock_decode_bool(eAdaptLayer);
+#if defined(HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_AUTHENTICATION_EVENT)
+ events.sctp_authentication_event = esock_decode_bool(eAuth);
+#endif
+#if defined(HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_SENDER_DRY_EVENT)
+ events.sctp_sender_dry_event = esock_decode_bool(eSndDry);
+#endif
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_sctp_events -> set events option\r\n") );
+
+ res = socket_setopt(descP->sock, IPPROTO_SCTP, SCTP_EVENTS,
+ &events, sizeof(events));
+
+ if (res != 0)
+ result = esock_make_error_errno(env, sock_errno());
+ else
+ result = esock_atom_ok;
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_sctp_events -> done with"
+ "\r\n result: %T"
+ "\r\n", result) );
+
+ return result;
+
+}
+#endif
+
+
+/* nsetopt_lvl_sctp_initmsg - Level SCTP INITMSG option
+ */
+#if defined(SCTP_INITMSG)
+static
+ERL_NIF_TERM nsetopt_lvl_sctp_initmsg(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ ERL_NIF_TERM result;
+ ERL_NIF_TERM eNumOut, eMaxIn, eMaxAttempts, eMaxInitTO;
+ struct sctp_initmsg initMsg;
+ int res;
+ size_t sz;
+ unsigned int tmp;
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_sctp_initmsg -> entry with"
+ "\r\n eVal: %T"
+ "\r\n", eVal) );
+
+ // It must be a map
+ if (!IS_MAP(env, eVal))
+ return esock_make_error(env, esock_atom_einval);
+
+ // It must have atleast ten attributes
+ if (!enif_get_map_size(env, eVal, &sz) || (sz < 4))
+ return esock_make_error(env, esock_atom_einval);
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_sctp_initmsg -> extract attributes\r\n") );
+
+ if (!GET_MAP_VAL(env, eVal, atom_num_outstreams, &eNumOut))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (!GET_MAP_VAL(env, eVal, atom_max_instreams, &eMaxIn))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (!GET_MAP_VAL(env, eVal, atom_max_attempts, &eMaxAttempts))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (!GET_MAP_VAL(env, eVal, atom_max_init_timeo, &eMaxInitTO))
+ return esock_make_error(env, esock_atom_einval);
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_sctp_initmsg -> decode attributes\r\n") );
+
+ if (!GET_UINT(env, eNumOut, &tmp))
+ return esock_make_error(env, esock_atom_einval);
+ initMsg.sinit_num_ostreams = (Uint16) tmp;
+
+ if (!GET_UINT(env, eMaxIn, &tmp))
+ return esock_make_error(env, esock_atom_einval);
+ initMsg.sinit_max_instreams = (Uint16) tmp;
+
+ if (!GET_UINT(env, eMaxAttempts, &tmp))
+ return esock_make_error(env, esock_atom_einval);
+ initMsg.sinit_max_attempts = (Uint16) tmp;
+
+ if (!GET_UINT(env, eMaxInitTO, &tmp))
+ return esock_make_error(env, esock_atom_einval);
+ initMsg.sinit_max_init_timeo = (Uint16) tmp;
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_sctp_initmsg -> set initmsg option\r\n") );
+
+ res = socket_setopt(descP->sock, IPPROTO_SCTP, SCTP_INITMSG,
+ &initMsg, sizeof(initMsg));
+
+ if (res != 0)
+ result = esock_make_error_errno(env, sock_errno());
+ else
+ result = esock_atom_ok;
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_sctp_initmsg -> done with"
+ "\r\n result: %T"
+ "\r\n", result) );
+
+ return result;
+
+}
+#endif
+
+
+/* nsetopt_lvl_sctp_maxseg - Level SCTP MAXSEG option
+ */
+#if defined(SCTP_MAXSEG)
+static
+ERL_NIF_TERM nsetopt_lvl_sctp_maxseg(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_int_opt(env, descP, IPPROTO_SCTP, SCTP_MAXSEG, eVal);
+}
+#endif
+
+
+/* nsetopt_lvl_sctp_nodelay - Level SCTP NODELAY option
+ */
+#if defined(SCTP_NODELAY)
+static
+ERL_NIF_TERM nsetopt_lvl_sctp_nodelay(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ return nsetopt_bool_opt(env, descP, IPPROTO_SCTP, SCTP_NODELAY, eVal);
+}
+#endif
+
+
+/* nsetopt_lvl_sctp_rtoinfo - Level SCTP RTOINFO option
+ */
+#if defined(SCTP_RTOINFO)
+static
+ERL_NIF_TERM nsetopt_lvl_sctp_rtoinfo(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eVal)
+{
+ ERL_NIF_TERM result;
+ ERL_NIF_TERM eAssocId, eInitial, eMax, eMin;
+ struct sctp_rtoinfo rtoInfo;
+ int res;
+ size_t sz;
+ Sint32 tmpAssocId;
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_sctp_rtoinfo -> entry with"
+ "\r\n eVal: %T"
+ "\r\n", eVal) );
+
+ // It must be a map
+ if (!IS_MAP(env, eVal))
+ return esock_make_error(env, esock_atom_einval);
+
+ // It must have atleast ten attributes
+ if (!enif_get_map_size(env, eVal, &sz) || (sz < 4))
+ return esock_make_error(env, esock_atom_einval);
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_sctp_rtoinfo -> extract attributes\r\n") );
+
+ if (!GET_MAP_VAL(env, eVal, atom_assoc_id, &eAssocId))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (!GET_MAP_VAL(env, eVal, atom_initial, &eInitial))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (!GET_MAP_VAL(env, eVal, atom_max, &eMax))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (!GET_MAP_VAL(env, eVal, atom_min, &eMin))
+ return esock_make_error(env, esock_atom_einval);
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_sctp_rtoinfo -> decode attributes\r\n") );
+
+ /* On some platforms the assoc id is typed as an unsigned integer (uint32)
+ * So, to avoid warnings there, we always make an explicit cast...
+ */
+ if (!GET_INT(env, eAssocId, &tmpAssocId))
+ return esock_make_error(env, esock_atom_einval);
+ rtoInfo.srto_assoc_id = (typeof(rtoInfo.srto_assoc_id)) tmpAssocId;
+
+ if (!GET_UINT(env, eInitial, &rtoInfo.srto_initial))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (!GET_UINT(env, eMax, &rtoInfo.srto_max))
+ return esock_make_error(env, esock_atom_einval);
+
+ if (!GET_UINT(env, eMin, &rtoInfo.srto_min))
+ return esock_make_error(env, esock_atom_einval);
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_sctp_rtoinfo -> set associnfo option\r\n") );
+
+ res = socket_setopt(descP->sock, IPPROTO_SCTP, SCTP_RTOINFO,
+ &rtoInfo, sizeof(rtoInfo));
+
+ if (res != 0)
+ result = esock_make_error_errno(env, sock_errno());
+ else
+ result = esock_atom_ok;
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_sctp_rtoinfo -> done with"
+ "\r\n result: %T"
+ "\r\n", result) );
+
+ return result;
+
+}
+#endif
+
+
+
+#endif // defined(HAVE_SCTP)
+
+
+
+
+/* nsetopt_bool_opt - set an option that has an (integer) bool value
+ */
+static
+ERL_NIF_TERM nsetopt_bool_opt(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int level,
+ int opt,
+ ERL_NIF_TERM eVal)
+{
+ ERL_NIF_TERM result;
+ BOOLEAN_T val;
+ int ival, res;
+
+ val = esock_decode_bool(eVal);
+
+ ival = (val) ? 1 : 0;
+ res = socket_setopt(descP->sock, level, opt, &ival, sizeof(ival));
+
+ if (res != 0)
+ result = esock_make_error_errno(env, sock_errno());
+ else
+ result = esock_atom_ok;
+
+ return result;
+}
+
+
+/* nsetopt_int_opt - set an option that has an integer value
+ */
+static
+ERL_NIF_TERM nsetopt_int_opt(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int level,
+ int opt,
+ ERL_NIF_TERM eVal)
+{
+ ERL_NIF_TERM result;
+ int val;
+
+ if (GET_INT(env, eVal, &val)) {
+ int res;
+
+ /*
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_int_opt -> set option"
+ "\r\n opt: %d"
+ "\r\n val: %d"
+ "\r\n", opt, val) );
+ */
+
+ res = socket_setopt(descP->sock, level, opt, &val, sizeof(val));
+
+ if (res != 0)
+ result = esock_make_error_errno(env, sock_errno());
+ else
+ result = esock_atom_ok;
+
+ } else {
+ result = esock_make_error(env, esock_atom_einval);
+ }
+
+ return result;
+}
+
+
+/* nsetopt_str_opt - set an option that has an string value
+ */
+static
+ERL_NIF_TERM nsetopt_str_opt(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int level,
+ int opt,
+ int max,
+ ERL_NIF_TERM eVal)
+{
+ ERL_NIF_TERM result;
+ char* val = MALLOC(max);
+
+ if (GET_STR(env, eVal, val, max) > 0) {
+ int optLen = strlen(val);
+ int res = socket_setopt(descP->sock, level, opt, &val, optLen);
+
+ if (res != 0)
+ result = esock_make_error_errno(env, sock_errno());
+ else
+ result = esock_atom_ok;
+
+ } else {
+ result = esock_make_error(env, esock_atom_einval);
+ }
+
+ FREE(val);
+
+ return result;
+}
+
+
+/* nsetopt_timeval_opt - set an option that has an (timeval) bool value
+ */
+static
+ERL_NIF_TERM nsetopt_timeval_opt(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int level,
+ int opt,
+ ERL_NIF_TERM eVal)
+{
+ ERL_NIF_TERM result;
+ struct timeval timeVal;
+ int res;
+ char* xres;
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_timeval_opt -> entry with"
+ "\r\n eVal: %T"
+ "\r\n", eVal) );
+
+ if ((xres = esock_decode_timeval(env, eVal, &timeVal)) != NULL)
+ return esock_make_error_str(env, xres);
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_timeval_opt -> set timeval option\r\n") );
+
+ res = socket_setopt(descP->sock, level, opt, &timeVal, sizeof(timeVal));
+
+ if (res != 0)
+ result = esock_make_error_errno(env, sock_errno());
+ else
+ result = esock_atom_ok;
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_timeval_opt -> done with"
+ "\r\n result: %T"
+ "\r\n", result) );
+
+ return result;
+
+}
+
+
+
+static
+BOOLEAN_T elevel2level(BOOLEAN_T isEncoded,
+ int eLevel,
+ BOOLEAN_T* isOTP,
+ int* level)
+{
+ BOOLEAN_T result;
+
+ if (isEncoded) {
+ switch (eLevel) {
+ case SOCKET_OPT_LEVEL_OTP:
+ *isOTP = TRUE;
+ *level = -1;
+ result = TRUE;
+ break;
+
+ case SOCKET_OPT_LEVEL_SOCKET:
+ *isOTP = FALSE;
+ *level = SOL_SOCKET;
+ result = TRUE;
+ break;
+
+ case SOCKET_OPT_LEVEL_IP:
+ *isOTP = FALSE;
+#if defined(SOL_IP)
+ *level = SOL_IP;
+#else
+ *level = IPPROTO_IP;
+#endif
+ result = TRUE;
+ break;
+
+#if defined(HAVE_IPV6)
+ case SOCKET_OPT_LEVEL_IPV6:
+ *isOTP = FALSE;
+#if defined(SOL_IPV6)
+ *level = SOL_IPV6;
+#else
+ *level = IPPROTO_IPV6;
+#endif
+ result = TRUE;
+ break;
+#endif
+
+ case SOCKET_OPT_LEVEL_TCP:
+ *isOTP = FALSE;
+ *level = IPPROTO_TCP;
+ result = TRUE;
+ break;
+
+ case SOCKET_OPT_LEVEL_UDP:
+ *isOTP = FALSE;
+ *level = IPPROTO_UDP;
+ result = TRUE;
+ break;
+
+#ifdef HAVE_SCTP
+ case SOCKET_OPT_LEVEL_SCTP:
+ *isOTP = FALSE;
+ *level = IPPROTO_SCTP;
+ result = TRUE;
+ break;
+#endif
+
+ default:
+ *isOTP = FALSE;
+ *level = -1;
+ result = FALSE;
+ break;
+ }
+ } else {
+ *isOTP = FALSE;
+ *level = eLevel;
+ result = TRUE;
+ }
+
+ return result;
+}
+
+
+
+/* +++ socket_setopt +++
+ *
+ * <Per H @ Tail-f>
+ * The original code here had problems that possibly
+ * only occur if you abuse it for non-INET sockets, but anyway:
+ * a) If the getsockopt for SO_PRIORITY or IP_TOS failed, the actual
+ * requested setsockopt was never even attempted.
+ * b) If {get,set}sockopt for one of IP_TOS and SO_PRIORITY failed,
+ * but ditto for the other worked and that was actually the requested
+ * option, failure was still reported to erlang.
+ * </Per H @ Tail-f>
+ *
+ * <PaN>
+ * The relations between SO_PRIORITY, TOS and other options
+ * is not what you (or at least I) would expect...:
+ * If TOS is set after priority, priority is zeroed.
+ * If any other option is set after tos, tos might be zeroed.
+ * Therefore, save tos and priority. If something else is set,
+ * restore both after setting, if tos is set, restore only
+ * prio and if prio is set restore none... All to keep the
+ * user feeling socket options are independent.
+ * </PaN>
+ */
+static
+int socket_setopt(int sock, int level, int opt,
+ const void* optVal, const socklen_t optLen)
+{
+ int res;
+
+#if defined(IP_TOS) && defined(SOL_IP) && defined(SO_PRIORITY)
+ int tmpIValPRIO;
+ int tmpIValTOS;
+ int resPRIO;
+ int resTOS;
+ SOCKOPTLEN_T tmpArgSzPRIO = sizeof(tmpIValPRIO);
+ SOCKOPTLEN_T tmpArgSzTOS = sizeof(tmpIValTOS);
+
+ resPRIO = sock_getopt(sock, SOL_SOCKET, SO_PRIORITY,
+ &tmpIValPRIO, &tmpArgSzPRIO);
+ resTOS = sock_getopt(sock, SOL_IP, IP_TOS,
+ &tmpIValTOS, &tmpArgSzTOS);
+
+ res = sock_setopt(sock, level, opt, optVal, optLen);
+ if (res == 0) {
+
+ /* Ok, now we *maybe* need to "maybe" restore PRIO and TOS...
+ * maybe, possibly, ...
+ */
+
+ if (opt != SO_PRIORITY) {
+ if ((opt != IP_TOS) && (resTOS == 0)) {
+ resTOS = sock_setopt(sock, SOL_IP, IP_TOS,
+ (void *) &tmpIValTOS,
+ tmpArgSzTOS);
+ res = resTOS;
+ }
+ if ((res == 0) && (resPRIO == 0)) {
+ resPRIO = sock_setopt(sock, SOL_SOCKET, SO_PRIORITY,
+ &tmpIValPRIO,
+ tmpArgSzPRIO);
+
+ /* Some kernels set a SO_PRIORITY by default
+ * that you are not permitted to reset,
+ * silently ignore this error condition.
+ */
+
+ if ((resPRIO != 0) && (sock_errno() == EPERM)) {
+ res = 0;
+ } else {
+ res = resPRIO;
+ }
+ }
+ }
+ }
+
+#else
+
+ res = sock_setopt(sock, level, opt, optVal, optLen);
+
+#endif
+
+ return res;
+}
+#endif // if !defined(__WIN32__)
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_getopt
+ *
+ * Description:
+ * Get socket option.
+ * Its possible to use a "raw" mode (not encoded). That is, we do not
+ * interpret level and opt. They are passed "as is" to the
+ * getsockopt function call. The value in this case will "copied" as
+ * is and provided to the user in the form of a binary.
+ *
+ * Arguments:
+ * Socket (ref) - Points to the socket descriptor.
+ * IsEncoded - Are the "arguments" encoded or not.
+ * Level - Level of the socket option.
+ * Opt - The socket option.
+ */
+
+static
+ERL_NIF_TERM nif_getopt(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ SocketDescriptor* descP;
+ int eLevel, level = -1;
+ ERL_NIF_TERM eIsEncoded, eOpt;
+ BOOLEAN_T isEncoded, isOTP;
+
+ SGDBG( ("SOCKET", "nif_getopt -> entry with argc: %d\r\n", argc) );
+
+ if ((argc != 4) ||
+ !enif_get_resource(env, argv[0], sockets, (void**) &descP) ||
+ !GET_INT(env, argv[2], &eLevel)) {
+ SGDBG( ("SOCKET", "nif_getopt -> failed processing args\r\n") );
+ return enif_make_badarg(env);
+ }
+ eIsEncoded = argv[1];
+ eOpt = argv[3]; // Is "normally" an int, but if raw mode: {Int, ValueSz}
+
+ if (IS_CLOSED(descP) || IS_CLOSING(descP))
+ return esock_make_error(env, atom_closed);
+
+ SSDBG( descP,
+ ("SOCKET", "nif_getopt -> args when sock = %d:"
+ "\r\n Socket: %T"
+ "\r\n eIsEncoded: %T"
+ "\r\n eLevel: %d"
+ "\r\n eOpt: %T"
+ "\r\n", descP->sock, argv[0], eIsEncoded, eLevel, eOpt) );
+
+ isEncoded = esock_decode_bool(eIsEncoded);
+
+ if (!elevel2level(isEncoded, eLevel, &isOTP, &level))
+ return esock_make_error(env, esock_atom_einval);
+
+ return ngetopt(env, descP, isEncoded, isOTP, level, eOpt);
+#endif // if defined(__WIN32__)
+}
+
+
+
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM ngetopt(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ BOOLEAN_T isEncoded,
+ BOOLEAN_T isOTP,
+ int level,
+ ERL_NIF_TERM eOpt)
+{
+ ERL_NIF_TERM result;
+ int opt;
+
+ SSDBG( descP,
+ ("SOCKET", "ngetopt -> entry with"
+ "\r\n isEncoded: %s"
+ "\r\n isOTP: %s"
+ "\r\n level: %d"
+ "\r\n eOpt: %T"
+ "\r\n", B2S(isEncoded), B2S(isOTP), level, eOpt) );
+
+ if (isOTP) {
+ /* These are not actual socket options,
+ * but options for our implementation.
+ */
+ if (GET_INT(env, eOpt, &opt))
+ result = ngetopt_otp(env, descP, opt);
+ else
+ result = esock_make_error(env, esock_atom_einval);
+ } else if (!isEncoded) {
+ result = ngetopt_native(env, descP, level, eOpt);
+ } else {
+ if (GET_INT(env, eOpt, &opt))
+ result = ngetopt_level(env, descP, level, opt);
+ else
+ result = esock_make_error(env, esock_atom_einval);
+ }
+
+ SSDBG( descP,
+ ("SOCKET", "ngetopt -> done when"
+ "\r\n result: %T"
+ "\r\n", result) );
+
+ return result;
+}
+
+
+
+/* ngetopt_otp - Handle OTP (level) options
+ */
+static
+ERL_NIF_TERM ngetopt_otp(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt)
+{
+ ERL_NIF_TERM result;
+
+ SSDBG( descP,
+ ("SOCKET", "ngetopt_otp -> entry with"
+ "\r\n eOpt: %d"
+ "\r\n", eOpt) );
+
+ switch (eOpt) {
+ case SOCKET_OPT_OTP_DEBUG:
+ result = ngetopt_otp_debug(env, descP);
+ break;
+
+ case SOCKET_OPT_OTP_IOW:
+ result = ngetopt_otp_iow(env, descP);
+ break;
+
+ case SOCKET_OPT_OTP_CTRL_PROC:
+ result = ngetopt_otp_ctrl_proc(env, descP);
+ break;
+
+ case SOCKET_OPT_OTP_RCVBUF:
+ result = ngetopt_otp_rcvbuf(env, descP);
+ break;
+
+ case SOCKET_OPT_OTP_RCVCTRLBUF:
+ result = ngetopt_otp_rcvctrlbuf(env, descP);
+ break;
+
+ case SOCKET_OPT_OTP_SNDCTRLBUF:
+ result = ngetopt_otp_sndctrlbuf(env, descP);
+ break;
+
+ case SOCKET_OPT_OTP_FD:
+ result = ngetopt_otp_fd(env, descP);
+ break;
+
+ /* *** INTERNAL *** */
+ case SOCKET_OPT_OTP_DOMAIN:
+ result = ngetopt_otp_domain(env, descP);
+ break;
+
+ case SOCKET_OPT_OTP_TYPE:
+ result = ngetopt_otp_type(env, descP);
+ break;
+
+ case SOCKET_OPT_OTP_PROTOCOL:
+ result = ngetopt_otp_protocol(env, descP);
+ break;
+
+ default:
+ result = esock_make_error(env, esock_atom_einval);
+ break;
+ }
+
+ SSDBG( descP,
+ ("SOCKET", "ngetopt_otp -> done when"
+ "\r\n result: %T"
+ "\r\n", result) );
+
+ return result;
+}
+
+
+/* ngetopt_otp_debug - Handle the OTP (level) debug option
+ */
+static
+ERL_NIF_TERM ngetopt_otp_debug(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ ERL_NIF_TERM eVal = esock_encode_bool(descP->dbg);
+
+ return esock_make_ok2(env, eVal);
+}
+
+
+/* ngetopt_otp_iow - Handle the OTP (level) iow option
+ */
+static
+ERL_NIF_TERM ngetopt_otp_iow(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ ERL_NIF_TERM eVal = esock_encode_bool(descP->iow);
+
+ return esock_make_ok2(env, eVal);
+}
+
+
+/* ngetopt_otp_ctrl_proc - Handle the OTP (level) controlling_process option
+ */
+static
+ERL_NIF_TERM ngetopt_otp_ctrl_proc(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ ERL_NIF_TERM eVal = MKPID(env, &descP->ctrlPid);
+
+ return esock_make_ok2(env, eVal);
+}
+
+
+
+/* ngetopt_otp_rcvbuf - Handle the OTP (level) rcvbuf option
+ */
+static
+ERL_NIF_TERM ngetopt_otp_rcvbuf(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ ERL_NIF_TERM eVal;
+
+ if (descP->rNum == 0) {
+ eVal = MKI(env, descP->rBufSz);
+ } else {
+ eVal = MKT2(env, MKI(env, descP->rNum), MKI(env, descP->rBufSz));
+ }
+
+ return esock_make_ok2(env, eVal);
+}
+
+
+/* ngetopt_otp_rcvctrlbuf - Handle the OTP (level) rcvctrlbuf option
+ */
+static
+ERL_NIF_TERM ngetopt_otp_rcvctrlbuf(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ ERL_NIF_TERM eVal = MKI(env, descP->rCtrlSz);
+
+ return esock_make_ok2(env, eVal);
+}
+
+
+/* ngetopt_otp_sndctrlbuf - Handle the OTP (level) sndctrlbuf option
+ */
+static
+ERL_NIF_TERM ngetopt_otp_sndctrlbuf(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ ERL_NIF_TERM eVal = MKI(env, descP->wCtrlSz);
+
+ return esock_make_ok2(env, eVal);
+}
+
+
+/* ngetopt_otp_fd - Handle the OTP (level) fd option
+ */
+static
+ERL_NIF_TERM ngetopt_otp_fd(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ ERL_NIF_TERM eVal = MKI(env, descP->sock);
+
+ return esock_make_ok2(env, eVal);
+}
+
+
+/* ngetopt_otp_domain - Handle the OTP (level) domain option
+ */
+static
+ERL_NIF_TERM ngetopt_otp_domain(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ ERL_NIF_TERM result;
+ int val = descP->domain;
+
+ switch (val) {
+ case AF_INET:
+ result = esock_make_ok2(env, esock_atom_inet);
+ break;
+
+#if defined(HAVE_IN6) && defined(AF_INET6)
+ case AF_INET6:
+ result = esock_make_ok2(env, esock_atom_inet6);
+ break;
+#endif
+
+#if defined(HAVE_SYS_UN_H)
+ case AF_UNIX:
+ result = esock_make_ok2(env, esock_atom_local);
+ break;
+#endif
+
+ default:
+ result = esock_make_error(env,
+ MKT2(env,
+ esock_atom_unknown,
+ MKI(env, val)));
+ break;
+ }
+
+ return result;
+}
+
+
+/* ngetopt_otp_type - Handle the OTP (level) type options.
+ */
+static
+ERL_NIF_TERM ngetopt_otp_type(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ ERL_NIF_TERM result;
+ int val = descP->type;
+
+ switch (val) {
+ case SOCK_STREAM:
+ result = esock_make_ok2(env, esock_atom_stream);
+ break;
+
+ case SOCK_DGRAM:
+ result = esock_make_ok2(env, esock_atom_dgram);
+ break;
+
+#ifdef HAVE_SCTP
+ case SOCK_SEQPACKET:
+ result = esock_make_ok2(env, esock_atom_seqpacket);
+ break;
+#endif
+ case SOCK_RAW:
+ result = esock_make_ok2(env, esock_atom_raw);
+ break;
+
+ case SOCK_RDM:
+ result = esock_make_ok2(env, esock_atom_rdm);
+ break;
+
+ default:
+ result = esock_make_error(env,
+ MKT2(env, esock_atom_unknown, MKI(env, val)));
+ break;
+ }
+
+ return result;
+}
+
+
+/* ngetopt_otp_protocol - Handle the OTP (level) protocol options.
+ */
+static
+ERL_NIF_TERM ngetopt_otp_protocol(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ ERL_NIF_TERM result;
+ int val = descP->protocol;
+
+ switch (val) {
+ case IPPROTO_IP:
+ result = esock_make_ok2(env, esock_atom_ip);
+ break;
+
+ case IPPROTO_TCP:
+ result = esock_make_ok2(env, esock_atom_tcp);
+ break;
+
+ case IPPROTO_UDP:
+ result = esock_make_ok2(env, esock_atom_udp);
+ break;
+
+#if defined(HAVE_SCTP)
+ case IPPROTO_SCTP:
+ result = esock_make_ok2(env, esock_atom_sctp);
+ break;
+#endif
+
+ default:
+ result = esock_make_error(env,
+ MKT2(env, esock_atom_unknown, MKI(env, val)));
+ break;
+ }
+
+ return result;
+}
+
+
+
+/* The option has *not* been encoded. Instead it has been provided
+ * in "native mode" (option is provided as is). In this case it will have the
+ * format: {NativeOpt :: integer(), ValueSize :: non_neg_integer()}
+ */
+static
+ERL_NIF_TERM ngetopt_native(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int level,
+ ERL_NIF_TERM eOpt)
+{
+ ERL_NIF_TERM result = enif_make_badarg(env);
+ int opt;
+ Uint16 valueType;
+ SOCKOPTLEN_T valueSz;
+
+ SSDBG( descP,
+ ("SOCKET", "ngetopt_native -> entry with"
+ "\r\n level: %d"
+ "\r\n eOpt: %T"
+ "\r\n", level, eOpt) );
+
+ /* <KOLLA>
+ * We should really make it possible to specify more common specific types,
+ * such as integer or boolean (instead of the size)...
+ * </KOLLA>
+ */
+
+ if (decode_native_get_opt(env, eOpt, &opt, &valueType, (int*) &valueSz)) {
+
+ SSDBG( descP,
+ ("SOCKET", "ngetopt_native -> decoded opt"
+ "\r\n valueType: %d (%s)"
+ "\r\n ValueSize: %d"
+ "\r\n", valueType, VT2S(valueType), valueSz) );
+
+ switch (valueType) {
+ case SOCKET_OPT_VALUE_TYPE_UNSPEC:
+ result = ngetopt_native_unspec(env, descP, level, opt, valueSz);
+ break;
+ case SOCKET_OPT_VALUE_TYPE_INT:
+ result = ngetopt_int_opt(env, descP, level, opt);
+ break;
+ case SOCKET_OPT_VALUE_TYPE_BOOL:
+ result = ngetopt_bool_opt(env, descP, level, opt);
+ break;
+ default:
+ result = esock_make_error(env, esock_atom_einval);
+ break;
+ }
+ } else {
+ result = esock_make_error(env, esock_atom_einval);
+ }
+
+ SSDBG( descP,
+ ("SOCKET", "ngetopt_native -> done when"
+ "\r\n result: %T"
+ "\r\n", result) );
+
+ return result;
+}
+
+
+static
+ERL_NIF_TERM ngetopt_native_unspec(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int level,
+ int opt,
+ SOCKOPTLEN_T valueSz)
+{
+ ERL_NIF_TERM result = esock_make_error(env, esock_atom_einval);
+ int res;
+
+ SSDBG( descP,
+ ("SOCKET", "ngetopt_native_unspec -> entry with"
+ "\r\n level: %d"
+ "\r\n opt: %d"
+ "\r\n valueSz: %d"
+ "\r\n", level, opt, valueSz) );
+
+ if (valueSz == 0) {
+ res = sock_getopt(descP->sock, level, opt, NULL, NULL);
+ if (res != 0)
+ result = esock_make_error_errno(env, sock_errno());
+ else
+ result = esock_atom_ok;
+ } else {
+ SOCKOPTLEN_T vsz = valueSz;
+ ErlNifBinary val;
+
+ SSDBG( descP, ("SOCKET", "ngetopt_native_unspec -> try alloc buffer\r\n") );
+
+ if (ALLOC_BIN(vsz, &val)) {
+ int saveErrno;
+ res = sock_getopt(descP->sock, level, opt, val.data, &vsz);
+ if (res != 0) {
+ saveErrno = sock_errno();
+
+ result = esock_make_error_errno(env, saveErrno);
+ } else {
+
+ /* Did we use all of the buffer? */
+ if (vsz == val.size) {
+
+ result = esock_make_ok2(env, MKBIN(env, &val));
+
+ } else {
+
+ ERL_NIF_TERM tmp;
+
+ tmp = MKBIN(env, &val);
+ tmp = MKSBIN(env, tmp, 0, vsz);
+
+ result = esock_make_ok2(env, tmp);
+ }
+ }
+ } else {
+ result = enif_make_badarg(env);
+ }
+ }
+
+ SSDBG( descP,
+ ("SOCKET", "ngetopt_native_unspec -> done when"
+ "\r\n result: %T"
+ "\r\n", result) );
+
+ return result;
+}
+
+
+
+/* ngetopt_level - A "proper" level (option) has been specified
+ */
+static
+ERL_NIF_TERM ngetopt_level(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int level,
+ int eOpt)
+{
+ ERL_NIF_TERM result;
+
+ SSDBG( descP,
+ ("SOCKET", "ngetopt_level -> entry with"
+ "\r\n level: %d"
+ "\r\n eOpt: %d"
+ "\r\n", level, eOpt) );
+
+ switch (level) {
+ case SOL_SOCKET:
+ result = ngetopt_lvl_socket(env, descP, eOpt);
+ break;
+
+#if defined(SOL_IP)
+ case SOL_IP:
+#else
+ case IPPROTO_IP:
+#endif
+ result = ngetopt_lvl_ip(env, descP, eOpt);
+ break;
+
+#if defined(HAVE_IPV6)
+#if defined(SOL_IPV6)
+ case SOL_IPV6:
+#else
+ case IPPROTO_IPV6:
+#endif
+ result = ngetopt_lvl_ipv6(env, descP, eOpt);
+ break;
+#endif
+
+ case IPPROTO_TCP:
+ result = ngetopt_lvl_tcp(env, descP, eOpt);
+ break;
+
+ case IPPROTO_UDP:
+ result = ngetopt_lvl_udp(env, descP, eOpt);
+ break;
+
+#if defined(HAVE_SCTP)
+ case IPPROTO_SCTP:
+ result = ngetopt_lvl_sctp(env, descP, eOpt);
+ break;
+#endif
+
+ default:
+ result = esock_make_error(env, esock_atom_einval);
+ break;
+ }
+
+ SSDBG( descP,
+ ("SOCKET", "ngetopt_level -> done when"
+ "\r\n result: %T"
+ "\r\n", result) );
+
+ return result;
+}
+
+
+/* ngetopt_lvl_socket - Level *SOCKET* option
+ */
+static
+ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt)
+{
+ ERL_NIF_TERM result;
+
+ SSDBG( descP,
+ ("SOCKET", "ngetopt_lvl_socket -> entry with"
+ "\r\n eOpt: %d"
+ "\r\n", eOpt) );
+
+ switch (eOpt) {
+#if defined(SO_ACCEPTCONN)
+ case SOCKET_OPT_SOCK_ACCEPTCONN:
+ result = ngetopt_lvl_sock_acceptconn(env, descP);
+ break;
+#endif
+
+#if defined(SO_BINDTODEVICE)
+ case SOCKET_OPT_SOCK_BINDTODEVICE:
+ result = ngetopt_lvl_sock_bindtodevice(env, descP);
+ break;
+#endif
+
+#if defined(SO_BROADCAST)
+ case SOCKET_OPT_SOCK_BROADCAST:
+ result = ngetopt_lvl_sock_broadcast(env, descP);
+ break;
+#endif
+
+#if defined(SO_DEBUG)
+ case SOCKET_OPT_SOCK_DEBUG:
+ result = ngetopt_lvl_sock_debug(env, descP);
+ break;
+#endif
+
+#if defined(SO_DOMAIN)
+ case SOCKET_OPT_SOCK_DOMAIN:
+ result = ngetopt_lvl_sock_domain(env, descP);
+ break;
+#endif
+
+#if defined(SO_DONTROUTE)
+ case SOCKET_OPT_SOCK_DONTROUTE:
+ result = ngetopt_lvl_sock_dontroute(env, descP);
+ break;
+#endif
+
+#if defined(SO_KEEPALIVE)
+ case SOCKET_OPT_SOCK_KEEPALIVE:
+ result = ngetopt_lvl_sock_keepalive(env, descP);
+ break;
+#endif
+
+#if defined(SO_LINGER)
+ case SOCKET_OPT_SOCK_LINGER:
+ result = ngetopt_lvl_sock_linger(env, descP);
+ break;
+#endif
+
+#if defined(SO_OOBINLINE)
+ case SOCKET_OPT_SOCK_OOBINLINE:
+ result = ngetopt_lvl_sock_oobinline(env, descP);
+ break;
+#endif
+
+#if defined(SO_PEEK_OFF)
+ case SOCKET_OPT_SOCK_PEEK_OFF:
+ result = ngetopt_lvl_sock_peek_off(env, descP);
+ break;
+#endif
+
+#if defined(SO_PRIORITY)
+ case SOCKET_OPT_SOCK_PRIORITY:
+ result = ngetopt_lvl_sock_priority(env, descP);
+ break;
+#endif
+
+#if defined(SO_PROTOCOL)
+ case SOCKET_OPT_SOCK_PROTOCOL:
+ result = ngetopt_lvl_sock_protocol(env, descP);
+ break;
+#endif
+
+#if defined(SO_RCVBUF)
+ case SOCKET_OPT_SOCK_RCVBUF:
+ result = ngetopt_lvl_sock_rcvbuf(env, descP);
+ break;
+#endif
+
+#if defined(SO_RCVLOWAT)
+ case SOCKET_OPT_SOCK_RCVLOWAT:
+ result = ngetopt_lvl_sock_rcvlowat(env, descP);
+ break;
+#endif
+
+#if defined(SO_RCVTIMEO)
+ case SOCKET_OPT_SOCK_RCVTIMEO:
+ result = ngetopt_lvl_sock_rcvtimeo(env, descP);
+ break;
+#endif
+
+#if defined(SO_REUSEADDR)
+ case SOCKET_OPT_SOCK_REUSEADDR:
+ result = ngetopt_lvl_sock_reuseaddr(env, descP);
+ break;
+#endif
+
+#if defined(SO_REUSEPORT)
+ case SOCKET_OPT_SOCK_REUSEPORT:
+ result = ngetopt_lvl_sock_reuseport(env, descP);
+ break;
+#endif
+
+#if defined(SO_SNDBUF)
+ case SOCKET_OPT_SOCK_SNDBUF:
+ result = ngetopt_lvl_sock_sndbuf(env, descP);
+ break;
+#endif
+
+#if defined(SO_SNDLOWAT)
+ case SOCKET_OPT_SOCK_SNDLOWAT:
+ result = ngetopt_lvl_sock_sndlowat(env, descP);
+ break;
+#endif
+
+#if defined(SO_SNDTIMEO)
+ case SOCKET_OPT_SOCK_SNDTIMEO:
+ result = ngetopt_lvl_sock_sndtimeo(env, descP);
+ break;
+#endif
+
+#if defined(SO_TIMESTAMP)
+ case SOCKET_OPT_SOCK_TIMESTAMP:
+ result = ngetopt_lvl_sock_timestamp(env, descP);
+ break;
+#endif
+
+#if defined(SO_TYPE)
+ case SOCKET_OPT_SOCK_TYPE:
+ result = ngetopt_lvl_sock_type(env, descP);
+ break;
+#endif
+
+ default:
+ result = esock_make_error(env, esock_atom_einval);
+ break;
+ }
+
+ SSDBG( descP,
+ ("SOCKET", "ngetopt_lvl_socket -> done when"
+ "\r\n result: %T"
+ "\r\n", result) );
+
+ return result;
+}
+
+
+#if defined(SO_ACCEPTCONN)
+static
+ERL_NIF_TERM ngetopt_lvl_sock_acceptconn(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ return ngetopt_bool_opt(env, descP, SOL_SOCKET, SO_ACCEPTCONN);
+}
+#endif
+
+
+#if defined(SO_BINDTODEVICE)
+static
+ERL_NIF_TERM ngetopt_lvl_sock_bindtodevice(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ SSDBG( descP,
+ ("SOCKET", "ngetopt_lvl_sock_bindtodevice -> entry with\r\n") );
+
+ return ngetopt_str_opt(env, descP, SOL_SOCKET, SO_BROADCAST, IFNAMSIZ+1);
+}
+#endif
+
+
+#if defined(SO_BROADCAST)
+static
+ERL_NIF_TERM ngetopt_lvl_sock_broadcast(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ return ngetopt_bool_opt(env, descP, SOL_SOCKET, SO_BROADCAST);
+}
+#endif
+
+
+#if defined(SO_DEBUG)
+static
+ERL_NIF_TERM ngetopt_lvl_sock_debug(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ return ngetopt_int_opt(env, descP, SOL_SOCKET, SO_DEBUG);
+}
+#endif
+
+
+#if defined(SO_DOMAIN)
+static
+ERL_NIF_TERM ngetopt_lvl_sock_domain(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ ERL_NIF_TERM result;
+ int val;
+ SOCKOPTLEN_T valSz = sizeof(val);
+ int res;
+
+ res = sock_getopt(descP->sock, SOL_SOCKET, SO_DOMAIN,
+ &val, &valSz);
+
+ if (res != 0) {
+ result = esock_make_error_errno(env, sock_errno());
+ } else {
+ switch (val) {
+ case AF_INET:
+ result = esock_make_ok2(env, esock_atom_inet);
+ break;
+
+#if defined(HAVE_IN6) && defined(AF_INET6)
+ case AF_INET6:
+ result = esock_make_ok2(env, esock_atom_inet6);
+ break;
+#endif
+
+#ifdef HAVE_SYS_UN_H
+ case AF_UNIX:
+ result = esock_make_ok2(env, esock_atom_local);
+ break;
+#endif
+
+ default:
+ result = esock_make_error(env,
+ MKT2(env,
+ esock_atom_unknown,
+ MKI(env, val)));
+ break;
+ }
+ }
+
+ return result;
+}
+#endif
+
+
+#if defined(SO_DONTROUTE)
+static
+ERL_NIF_TERM ngetopt_lvl_sock_dontroute(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ return ngetopt_bool_opt(env, descP, SOL_SOCKET, SO_DONTROUTE);
+}
+#endif
+
+
+#if defined(SO_KEEPALIVE)
+static
+ERL_NIF_TERM ngetopt_lvl_sock_keepalive(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ return ngetopt_bool_opt(env, descP, SOL_SOCKET, SO_KEEPALIVE);
+}
+#endif
+
+
+#if defined(SO_LINGER)
+static
+ERL_NIF_TERM ngetopt_lvl_sock_linger(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ ERL_NIF_TERM result;
+ struct linger val;
+ SOCKOPTLEN_T valSz = sizeof(val);
+ int res;
+
+ sys_memzero((void *) &val, sizeof(val));
+
+ res = sock_getopt(descP->sock, SOL_SOCKET, SO_LINGER,
+ &val, &valSz);
+
+ if (res != 0) {
+ result = esock_make_error_errno(env, sock_errno());
+ } else {
+ ERL_NIF_TERM lOnOff = ((val.l_onoff) ? atom_true : atom_false);
+ ERL_NIF_TERM lSecs = MKI(env, val.l_linger);
+ ERL_NIF_TERM linger = MKT2(env, lOnOff, lSecs);
+
+ result = esock_make_ok2(env, linger);
+ }
+
+ return result;
+}
+#endif
+
+
+#if defined(SO_OOBINLINE)
+static
+ERL_NIF_TERM ngetopt_lvl_sock_oobinline(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ return ngetopt_bool_opt(env, descP, SOL_SOCKET, SO_OOBINLINE);
+}
+#endif
+
+
+#if defined(SO_PEEK_OFF)
+static
+ERL_NIF_TERM ngetopt_lvl_sock_peek_off(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ return ngetopt_int_opt(env, descP, SOL_SOCKET, SO_PEEK_OFF);
+}
+#endif
+
+
+#if defined(SO_PRIORITY)
+static
+ERL_NIF_TERM ngetopt_lvl_sock_priority(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ return ngetopt_int_opt(env, descP, SOL_SOCKET, SO_PRIORITY);
+}
+#endif
+
+
+#if defined(SO_PROTOCOL)
+static
+ERL_NIF_TERM ngetopt_lvl_sock_protocol(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ ERL_NIF_TERM result;
+ int val;
+ SOCKOPTLEN_T valSz = sizeof(val);
+ int res;
+
+ res = sock_getopt(descP->sock, SOL_SOCKET, SO_PROTOCOL,
+ &val, &valSz);
+
+ if (res != 0) {
+ result = esock_make_error_errno(env, sock_errno());
+ } else {
+ switch (val) {
+ case IPPROTO_IP:
+ result = esock_make_ok2(env, esock_atom_ip);
+ break;
+
+ case IPPROTO_TCP:
+ result = esock_make_ok2(env, esock_atom_tcp);
+ break;
+
+ case IPPROTO_UDP:
+ result = esock_make_ok2(env, esock_atom_udp);
+ break;
+
+#if defined(HAVE_SCTP)
+ case IPPROTO_SCTP:
+ result = esock_make_ok2(env, esock_atom_sctp);
+ break;
+#endif
+
+ default:
+ result = esock_make_error(env,
+ MKT2(env, esock_atom_unknown, MKI(env, val)));
+ break;
+ }
+ }
+
+ return result;
+}
+#endif
+
+
+#if defined(SO_RCVBUF)
+static
+ERL_NIF_TERM ngetopt_lvl_sock_rcvbuf(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ return ngetopt_int_opt(env, descP, SOL_SOCKET, SO_RCVBUF);
+}
+#endif
+
+
+#if defined(SO_RCVLOWAT)
+static
+ERL_NIF_TERM ngetopt_lvl_sock_rcvlowat(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ return ngetopt_int_opt(env, descP, SOL_SOCKET, SO_RCVLOWAT);
+}
+#endif
+
+
+#if defined(SO_RCVTIMEO)
+static
+ERL_NIF_TERM ngetopt_lvl_sock_rcvtimeo(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ return ngetopt_timeval_opt(env, descP, SOL_SOCKET, SO_RCVTIMEO);
+}
+#endif
+
+
+#if defined(SO_REUSEADDR)
+static
+ERL_NIF_TERM ngetopt_lvl_sock_reuseaddr(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ return ngetopt_bool_opt(env, descP, SOL_SOCKET, SO_REUSEADDR);
+}
+#endif
+
+
+#if defined(SO_REUSEPORT)
+static
+ERL_NIF_TERM ngetopt_lvl_sock_reuseport(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ return ngetopt_bool_opt(env, descP, SOL_SOCKET, SO_REUSEPORT);
+}
+#endif
+
+
+#if defined(SO_SNDBUF)
+static
+ERL_NIF_TERM ngetopt_lvl_sock_sndbuf(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ return ngetopt_int_opt(env, descP, SOL_SOCKET, SO_SNDBUF);
+}
+#endif
+
+
+#if defined(SO_SNDLOWAT)
+static
+ERL_NIF_TERM ngetopt_lvl_sock_sndlowat(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ return ngetopt_int_opt(env, descP, SOL_SOCKET, SO_SNDLOWAT);
+}
+#endif
+
+
+#if defined(SO_SNDTIMEO)
+static
+ERL_NIF_TERM ngetopt_lvl_sock_sndtimeo(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ return ngetopt_timeval_opt(env, descP, SOL_SOCKET, SO_SNDTIMEO);
+}
+#endif
+
+
+#if defined(SO_TIMESTAMP)
+static
+ERL_NIF_TERM ngetopt_lvl_sock_timestamp(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ return ngetopt_bool_opt(env, descP, SOL_SOCKET, SO_TIMESTAMP);
+}
+#endif
+
+
+#if defined(SO_TYPE)
+static
+ERL_NIF_TERM ngetopt_lvl_sock_type(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ ERL_NIF_TERM result;
+ int val;
+ SOCKOPTLEN_T valSz = sizeof(val);
+ int res;
+
+ res = sock_getopt(descP->sock, SOL_SOCKET, SO_TYPE, &val, &valSz);
+
+ if (res != 0) {
+ result = esock_make_error_errno(env, sock_errno());
+ } else {
+ switch (val) {
+ case SOCK_STREAM:
+ result = esock_make_ok2(env, esock_atom_stream);
+ break;
+ case SOCK_DGRAM:
+ result = esock_make_ok2(env, esock_atom_dgram);
+ break;
+#ifdef HAVE_SCTP
+ case SOCK_SEQPACKET:
+ result = esock_make_ok2(env, esock_atom_seqpacket);
+ break;
+#endif
+ case SOCK_RAW:
+ result = esock_make_ok2(env, esock_atom_raw);
+ break;
+ case SOCK_RDM:
+ result = esock_make_ok2(env, esock_atom_rdm);
+ break;
+ default:
+ result = esock_make_error(env,
+ MKT2(env, esock_atom_unknown, MKI(env, val)));
+ break;
+ }
+ }
+
+ return result;
+}
+#endif
+
+
+/* ngetopt_lvl_ip - Level *IP* option(s)
+ */
+static
+ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt)
+{
+ ERL_NIF_TERM result;
+
+ SSDBG( descP,
+ ("SOCKET", "ngetopt_lvl_ip -> entry with"
+ "\r\n eOpt: %d"
+ "\r\n", eOpt) );
+
+ switch (eOpt) {
+#if defined(IP_FREEBIND)
+ case SOCKET_OPT_IP_FREEBIND:
+ result = ngetopt_lvl_ip_freebind(env, descP);
+ break;
+#endif
+
+#if defined(IP_HDRINCL)
+ case SOCKET_OPT_IP_HDRINCL:
+ result = ngetopt_lvl_ip_hdrincl(env, descP);
+ break;
+#endif
+
+#if defined(IP_MINTTL)
+ case SOCKET_OPT_IP_MINTTL:
+ result = ngetopt_lvl_ip_minttl(env, descP);
+ break;
+#endif
+
+#if defined(IP_MTU)
+ case SOCKET_OPT_IP_MTU:
+ result = ngetopt_lvl_ip_mtu(env, descP);
+ break;
+#endif
+
+#if defined(IP_MTU_DISCOVER)
+ case SOCKET_OPT_IP_MTU_DISCOVER:
+ result = ngetopt_lvl_ip_mtu_discover(env, descP);
+ break;
+#endif
+
+#if defined(IP_MULTICAST_ALL)
+ case SOCKET_OPT_IP_MULTICAST_ALL:
+ result = ngetopt_lvl_ip_multicast_all(env, descP);
+ break;
+#endif
+
+#if defined(IP_MULTICAST_IF)
+ case SOCKET_OPT_IP_MULTICAST_IF:
+ result = ngetopt_lvl_ip_multicast_if(env, descP);
+ break;
+#endif
+
+#if defined(IP_MULTICAST_LOOP)
+ case SOCKET_OPT_IP_MULTICAST_LOOP:
+ result = ngetopt_lvl_ip_multicast_loop(env, descP);
+ break;
+#endif
+
+#if defined(IP_MULTICAST_TTL)
+ case SOCKET_OPT_IP_MULTICAST_TTL:
+ result = ngetopt_lvl_ip_multicast_ttl(env, descP);
+ break;
+#endif
+
+#if defined(IP_NODEFRAG)
+ case SOCKET_OPT_IP_NODEFRAG:
+ result = ngetopt_lvl_ip_nodefrag(env, descP);
+ break;
+#endif
+
+#if defined(IP_PKTINFO)
+ case SOCKET_OPT_IP_PKTINFO:
+ result = ngetopt_lvl_ip_pktinfo(env, descP);
+ break;
+#endif
+
+#if defined(IP_RECVDSTADDR)
+ case SOCKET_OPT_IP_RECVDSTADDR:
+ result = ngetopt_lvl_ip_recvdstaddr(env, descP);
+ break;
+#endif
+
+#if defined(IP_RECVERR)
+ case SOCKET_OPT_IP_RECVERR:
+ result = ngetopt_lvl_ip_recverr(env, descP);
+ break;
+#endif
+
+#if defined(IP_RECVIF)
+ case SOCKET_OPT_IP_RECVIF:
+ result = ngetopt_lvl_ip_recvif(env, descP);
+ break;
+#endif
+
+#if defined(IP_RECVOPTS)
+ case SOCKET_OPT_IP_RECVOPTS:
+ result = ngetopt_lvl_ip_recvopts(env, descP);
+ break;
+#endif
+
+#if defined(IP_RECVORIGDSTADDR)
+ case SOCKET_OPT_IP_RECVORIGDSTADDR:
+ result = ngetopt_lvl_ip_recvorigdstaddr(env, descP);
+ break;
+#endif
+
+#if defined(IP_RECVTOS)
+ case SOCKET_OPT_IP_RECVTOS:
+ result = ngetopt_lvl_ip_recvtos(env, descP);
+ break;
+#endif
+
+#if defined(IP_RECVTTL)
+ case SOCKET_OPT_IP_RECVTTL:
+ result = ngetopt_lvl_ip_recvttl(env, descP);
+ break;
+#endif
+
+#if defined(IP_RETOPTS)
+ case SOCKET_OPT_IP_RETOPTS:
+ result = ngetopt_lvl_ip_retopts(env, descP);
+ break;
+#endif
+
+#if defined(IP_ROUTER_ALERT)
+ case SOCKET_OPT_IP_ROUTER_ALERT:
+ result = ngetopt_lvl_ip_router_alert(env, descP);
+ break;
+#endif
+
+#if defined(IP_SENDSRCADDR)
+ case SOCKET_OPT_IP_SENDSRCADDR:
+ result = ngetopt_lvl_ip_sendsrcaddr(env, descP);
+ break;
+#endif
+
+#if defined(IP_TOS)
+ case SOCKET_OPT_IP_TOS:
+ result = ngetopt_lvl_ip_tos(env, descP);
+ break;
+#endif
+
+#if defined(IP_TRANSPARENT)
+ case SOCKET_OPT_IP_TRANSPARENT:
+ result = ngetopt_lvl_ip_transparent(env, descP);
+ break;
+#endif
+
+#if defined(IP_TTL)
+ case SOCKET_OPT_IP_TTL:
+ result = ngetopt_lvl_ip_ttl(env, descP);
+ break;
+#endif
+
+ default:
+ SSDBG( descP, ("SOCKET", "ngetopt_lvl_ip -> unknown opt %d\r\n", eOpt) );
+ result = esock_make_error(env, esock_atom_einval);
+ break;
+ }
+
+ SSDBG( descP,
+ ("SOCKET", "ngetopt_lvl_ip -> done when"
+ "\r\n result: %T"
+ "\r\n", result) );
+
+ return result;
+}
+
+
+/* ngetopt_lvl_ip_minttl - Level IP MINTTL option
+ */
+#if defined(IP_MINTTL)
+static
+ERL_NIF_TERM ngetopt_lvl_ip_minttl(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return ngetopt_int_opt(env, descP, level, IP_MINTTL);
+}
+#endif
+
+
+/* ngetopt_lvl_ip_freebind - Level IP FREEBIND option
+ */
+#if defined(IP_FREEBIND)
+static
+ERL_NIF_TERM ngetopt_lvl_ip_freebind(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return ngetopt_bool_opt(env, descP, level, IP_FREEBIND);
+}
+#endif
+
+
+/* ngetopt_lvl_ip_hdrincl - Level IP HDRINCL option
+ */
+#if defined(IP_HDRINCL)
+static
+ERL_NIF_TERM ngetopt_lvl_ip_hdrincl(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return ngetopt_bool_opt(env, descP, level, IP_HDRINCL);
+}
+#endif
+
+
+/* ngetopt_lvl_ip_mtu - Level IP MTU option
+ */
+#if defined(IP_MTU)
+static
+ERL_NIF_TERM ngetopt_lvl_ip_mtu(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return ngetopt_int_opt(env, descP, level, IP_MTU);
+}
+#endif
+
+
+/* ngetopt_lvl_ip_mtu_discover - Level IP MTU_DISCOVER option
+ */
+#if defined(IP_MTU_DISCOVER)
+static
+ERL_NIF_TERM ngetopt_lvl_ip_mtu_discover(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ ERL_NIF_TERM result;
+ ERL_NIF_TERM eMtuDisc;
+ int mtuDisc;
+ SOCKOPTLEN_T mtuDiscSz = sizeof(mtuDisc);
+ int res;
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ res = sock_getopt(descP->sock, level, IP_MTU_DISCOVER,
+ &mtuDisc, &mtuDiscSz);
+
+ if (res != 0) {
+ result = esock_make_error_errno(env, sock_errno());
+ } else {
+ encode_ip_pmtudisc(env, mtuDisc, &eMtuDisc);
+ result = esock_make_ok2(env, eMtuDisc);
+ }
+
+ return result;
+
+}
+#endif
+
+
+/* ngetopt_lvl_ip_multicast_all - Level IP MULTICAST_ALL option
+ */
+#if defined(IP_MULTICAST_ALL)
+static
+ERL_NIF_TERM ngetopt_lvl_ip_multicast_all(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return ngetopt_bool_opt(env, descP, level, IP_MULTICAST_ALL);
+}
+#endif
+
+
+/* ngetopt_lvl_ip_multicast_if - Level IP MULTICAST_IF option
+ */
+#if defined(IP_MULTICAST_IF)
+static
+ERL_NIF_TERM ngetopt_lvl_ip_multicast_if(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ ERL_NIF_TERM result;
+ ERL_NIF_TERM eAddr;
+ struct in_addr ifAddr;
+ SOCKOPTLEN_T ifAddrSz = sizeof(ifAddr);
+ char* xres;
+ int res;
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ res = sock_getopt(descP->sock, level, IP_MULTICAST_IF, &ifAddr, &ifAddrSz);
+
+ if (res != 0) {
+ result = esock_make_error_errno(env, sock_errno());
+ } else {
+ if ((xres = esock_encode_ip4_address(env, &ifAddr, &eAddr)) != NULL) {
+ result = esock_make_error_str(env, xres);
+ } else {
+ result = esock_make_ok2(env, eAddr);
+ }
+ }
+
+ return result;
+
+}
+#endif
+
+
+/* ngetopt_lvl_ip_multicast_loop - Level IP MULTICAST_LOOP option
+ */
+#if defined(IP_MULTICAST_LOOP)
+static
+ERL_NIF_TERM ngetopt_lvl_ip_multicast_loop(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return ngetopt_bool_opt(env, descP, level, IP_MULTICAST_LOOP);
+}
+#endif
+
+
+/* ngetopt_lvl_ip_multicast_ttl - Level IP MULTICAST_TTL option
+ */
+#if defined(IP_MULTICAST_TTL)
+static
+ERL_NIF_TERM ngetopt_lvl_ip_multicast_ttl(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return ngetopt_int_opt(env, descP, level, IP_MULTICAST_TTL);
+}
+#endif
+
+
+/* ngetopt_lvl_ip_nodefrag - Level IP NODEFRAG option
+ */
+#if defined(IP_NODEFRAG)
+static
+ERL_NIF_TERM ngetopt_lvl_ip_nodefrag(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return ngetopt_bool_opt(env, descP, level, IP_NODEFRAG);
+}
+#endif
+
+
+/* ngetopt_lvl_ip_pktinfo - Level IP PKTINFO option
+ */
+#if defined(IP_PKTINFO)
+static
+ERL_NIF_TERM ngetopt_lvl_ip_pktinfo(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return ngetopt_bool_opt(env, descP, level, IP_PKTINFO);
+}
+#endif
+
+
+/* ngetopt_lvl_ip_recvtos - Level IP RECVTOS option
+ */
+#if defined(IP_RECVTOS)
+static
+ERL_NIF_TERM ngetopt_lvl_ip_recvtos(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return ngetopt_bool_opt(env, descP, level, IP_RECVTOS);
+}
+#endif
+
+
+/* ngetopt_lvl_ip_recvdstaddr - Level IP RECVDSTADDR option
+ */
+#if defined(IP_RECVDSTADDR)
+static
+ERL_NIF_TERM ngetopt_lvl_ip_recvdstaddr(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return ngetopt_bool_opt(env, descP, level, IP_RECVDSTADDR);
+}
+#endif
+
+
+/* ngetopt_lvl_ip_recverr - Level IP RECVERR option
+ */
+#if defined(IP_RECVERR)
+static
+ERL_NIF_TERM ngetopt_lvl_ip_recverr(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return ngetopt_bool_opt(env, descP, level, IP_RECVERR);
+}
+#endif
+
+
+/* ngetopt_lvl_ip_recvif - Level IP RECVIF option
+ */
+#if defined(IP_RECVIF)
+static
+ERL_NIF_TERM ngetopt_lvl_ip_recvif(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return ngetopt_bool_opt(env, descP, level, IP_RECVIF);
+}
+#endif
+
+
+/* ngetopt_lvl_ip_recvopt - Level IP RECVOPTS option
+ */
+#if defined(IP_RECVOPTS)
+static
+ERL_NIF_TERM ngetopt_lvl_ip_recvopts(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return ngetopt_bool_opt(env, descP, level, IP_RECVOPTS);
+}
+#endif
+
+
+/* ngetopt_lvl_ip_recvorigdstaddr - Level IP RECVORIGDSTADDR option
+ */
+#if defined(IP_RECVORIGDSTADDR)
+static
+ERL_NIF_TERM ngetopt_lvl_ip_recvorigdstaddr(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return ngetopt_bool_opt(env, descP, level, IP_RECVORIGDSTADDR);
+}
+#endif
+
+
+/* ngetopt_lvl_ip_recvttl - Level IP RECVTTL option
+ */
+#if defined(IP_RECVTTL)
+static
+ERL_NIF_TERM ngetopt_lvl_ip_recvttl(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return ngetopt_bool_opt(env, descP, level, IP_RECVTTL);
+}
+#endif
+
+
+/* ngetopt_lvl_ip_retopts - Level IP RETOPTS option
+ */
+#if defined(IP_RETOPTS)
+static
+ERL_NIF_TERM ngetopt_lvl_ip_retopts(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return ngetopt_bool_opt(env, descP, level, IP_RETOPTS);
+}
+#endif
+
+
+/* ngetopt_lvl_ip_router_alert - Level IP ROUTER_ALERT option
+ */
+#if defined(IP_ROUTER_ALERT)
+static
+ERL_NIF_TERM ngetopt_lvl_ip_router_alert(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return ngetopt_int_opt(env, descP, level, IP_ROUTER_ALERT);
+}
+#endif
+
+
+/* ngetopt_lvl_ip_sendsrcaddr - Level IP SENDSRCADDR option
+ */
+#if defined(IP_SENDSRCADDR)
+static
+ERL_NIF_TERM ngetopt_lvl_ip_sendsrcaddr(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return ngetopt_bool_opt(env, descP, level, IP_SENDSRCADDR);
+}
+#endif
+
+
+/* ngetopt_lvl_ip_tos - Level IP TOS option
+ */
+#if defined(IP_TOS)
+static
+ERL_NIF_TERM ngetopt_lvl_ip_tos(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+ ERL_NIF_TERM result;
+ int val;
+ SOCKOPTLEN_T valSz = sizeof(val);
+ int res;
+
+ res = sock_getopt(descP->sock, level, IP_TOS, &val, &valSz);
+
+ if (res != 0) {
+ result = esock_make_error_errno(env, sock_errno());
+ } else {
+ result = encode_ip_tos(env, val);
+ }
+
+ return result;
+}
+#endif
+
+
+/* ngetopt_lvl_ip_transparent - Level IP TRANSPARENT option
+ */
+#if defined(IP_TRANSPARENT)
+static
+ERL_NIF_TERM ngetopt_lvl_ip_transparent(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return ngetopt_bool_opt(env, descP, level, IP_TRANSPARENT);
+}
+#endif
+
+
+
+/* ngetopt_lvl_ip_ttl - Level IP TTL option
+ */
+#if defined(IP_TTL)
+static
+ERL_NIF_TERM ngetopt_lvl_ip_ttl(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IP)
+ int level = SOL_IP;
+#else
+ int level = IPPROTO_IP;
+#endif
+
+ return ngetopt_int_opt(env, descP, level, IP_TTL);
+}
+#endif
+
+
+
+/* ngetopt_lvl_ipv6 - Level *IPv6* option(s)
+ */
+#if defined(HAVE_IPV6)
+static
+ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt)
+{
+ ERL_NIF_TERM result;
+
+ SSDBG( descP,
+ ("SOCKET", "ngetopt_lvl_ipv6 -> entry with"
+ "\r\n eOpt: %d"
+ "\r\n", eOpt) );
+
+ switch (eOpt) {
+#if defined(IPV6_AUTHHDR)
+ case SOCKET_OPT_IPV6_AUTHHDR:
+ result = ngetopt_lvl_ipv6_authhdr(env, descP);
+ break;
+#endif
+
+#if defined(IPV6_DSTOPTS)
+ case SOCKET_OPT_IPV6_DSTOPTS:
+ result = ngetopt_lvl_ipv6_dstopts(env, descP);
+ break;
+#endif
+
+#if defined(IPV6_FLOWINFO)
+ case SOCKET_OPT_IPV6_FLOWINFO:
+ result = ngetopt_lvl_ipv6_flowinfo(env, descP);
+ break;
+#endif
+
+#if defined(IPV6_HOPLIMIT)
+ case SOCKET_OPT_IPV6_HOPLIMIT:
+ result = ngetopt_lvl_ipv6_hoplimit(env, descP);
+ break;
+#endif
+
+#if defined(IPV6_HOPOPTS)
+ case SOCKET_OPT_IPV6_HOPOPTS:
+ result = ngetopt_lvl_ipv6_hopopts(env, descP);
+ break;
+#endif
+
+#if defined(IPV6_MTU)
+ case SOCKET_OPT_IPV6_MTU:
+ result = ngetopt_lvl_ipv6_mtu(env, descP);
+ break;
+#endif
+
+#if defined(IPV6_MTU_DISCOVER)
+ case SOCKET_OPT_IPV6_MTU_DISCOVER:
+ result = ngetopt_lvl_ipv6_mtu_discover(env, descP);
+ break;
+#endif
+
+#if defined(IPV6_MULTICAST_HOPS)
+ case SOCKET_OPT_IPV6_MULTICAST_HOPS:
+ result = ngetopt_lvl_ipv6_multicast_hops(env, descP);
+ break;
+#endif
+
+#if defined(IPV6_MULTICAST_IF)
+ case SOCKET_OPT_IPV6_MULTICAST_IF:
+ result = ngetopt_lvl_ipv6_multicast_if(env, descP);
+ break;
+#endif
+
+#if defined(IPV6_MULTICAST_LOOP)
+ case SOCKET_OPT_IPV6_MULTICAST_LOOP:
+ result = ngetopt_lvl_ipv6_multicast_loop(env, descP);
+ break;
+#endif
+
+#if defined(IPV6_RECVERR)
+ case SOCKET_OPT_IPV6_RECVERR:
+ result = ngetopt_lvl_ipv6_recverr(env, descP);
+ break;
+#endif
+
+#if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO)
+ case SOCKET_OPT_IPV6_RECVPKTINFO:
+ result = ngetopt_lvl_ipv6_recvpktinfo(env, descP);
+ break;
+#endif
+
+#if defined(IPV6_ROUTER_ALERT)
+ case SOCKET_OPT_IPV6_ROUTER_ALERT:
+ result = ngetopt_lvl_ipv6_router_alert(env, descP);
+ break;
+#endif
+
+#if defined(IPV6_RTHDR)
+ case SOCKET_OPT_IPV6_RTHDR:
+ result = ngetopt_lvl_ipv6_rthdr(env, descP);
+ break;
+#endif
+
+#if defined(IPV6_UNICAST_HOPS)
+ case SOCKET_OPT_IPV6_UNICAST_HOPS:
+ result = ngetopt_lvl_ipv6_unicast_hops(env, descP);
+ break;
+#endif
+
+#if defined(IPV6_V6ONLY)
+ case SOCKET_OPT_IPV6_V6ONLY:
+ result = ngetopt_lvl_ipv6_v6only(env, descP);
+ break;
+#endif
+
+ default:
+ result = esock_make_error(env, esock_atom_einval);
+ break;
+ }
+
+ SSDBG( descP,
+ ("SOCKET", "ngetopt_lvl_ipv6 -> done when"
+ "\r\n result: %T"
+ "\r\n", result) );
+
+ return result;
+}
+
+
+#if defined(IPV6_AUTHHDR)
+static
+ERL_NIF_TERM ngetopt_lvl_ipv6_authhdr(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ return ngetopt_bool_opt(env, descP, SOL_IPV6, IPV6_AUTHHDR);
+}
+#endif
+
+
+#if defined(IPV6_DSTOPTS)
+static
+ERL_NIF_TERM ngetopt_lvl_ipv6_dstopts(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+ return ngetopt_bool_opt(env, descP, level, IPV6_DSTOPTS);
+}
+#endif
+
+
+#if defined(IPV6_FLOWINFO)
+static
+ERL_NIF_TERM ngetopt_lvl_ipv6_flowinfo(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+ return ngetopt_bool_opt(env, descP, level, IPV6_FLOWINFO);
+}
+#endif
+
+
+#if defined(IPV6_HOPLIMIT)
+static
+ERL_NIF_TERM ngetopt_lvl_ipv6_hoplimit(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+ return ngetopt_bool_opt(env, descP, level, IPV6_HOPLIMIT);
+}
+#endif
+
+
+#if defined(IPV6_HOPOPTS)
+static
+ERL_NIF_TERM ngetopt_lvl_ipv6_hopopts(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+ return ngetopt_bool_opt(env, descP, level, IPV6_HOPOPTS);
+}
+#endif
+
+
+#if defined(IPV6_MTU)
+static
+ERL_NIF_TERM ngetopt_lvl_ipv6_mtu(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+ return ngetopt_int_opt(env, descP, level, IPV6_MTU);
+}
+#endif
+
+
+/* ngetopt_lvl_ipv6_mtu_discover - Level IPv6 MTU_DISCOVER option
+ */
+#if defined(IPV6_MTU_DISCOVER)
+static
+ERL_NIF_TERM ngetopt_lvl_ipv6_mtu_discover(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ ERL_NIF_TERM result;
+ ERL_NIF_TERM eMtuDisc;
+ int mtuDisc;
+ SOCKOPTLEN_T mtuDiscSz = sizeof(mtuDisc);
+ int res;
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+ res = sock_getopt(descP->sock, level, IPV6_MTU_DISCOVER,
+ &mtuDisc, &mtuDiscSz);
+
+ if (res != 0) {
+ result = esock_make_error_errno(env, sock_errno());
+ } else {
+ encode_ipv6_pmtudisc(env, mtuDisc, &eMtuDisc);
+ result = esock_make_ok2(env, eMtuDisc);
+ }
+
+ return result;
+
+}
+#endif
+
+
+#if defined(IPV6_MULTICAST_HOPS)
+static
+ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_hops(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+ return ngetopt_int_opt(env, descP, level, IPV6_MULTICAST_HOPS);
+}
+#endif
+
+
+#if defined(IPV6_MULTICAST_IF)
+static
+ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_if(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+ return ngetopt_int_opt(env, descP, level, IPV6_MULTICAST_IF);
+}
+#endif
+
+
+#if defined(IPV6_MULTICAST_LOOP)
+static
+ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_loop(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+ return ngetopt_bool_opt(env, descP, level, IPV6_MULTICAST_LOOP);
+}
+#endif
+
+
+#if defined(IPV6_RECVERR)
+static
+ERL_NIF_TERM ngetopt_lvl_ipv6_recverr(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+ return ngetopt_bool_opt(env, descP, level, IPV6_RECVERR);
+}
+#endif
+
+
+#if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO)
+static
+ERL_NIF_TERM ngetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+#if defined(IPV6_RECVPKTINFO)
+ int opt = IPV6_RECVPKTINFO;
+#else
+ int opt = IPV6_PKTINFO;
+#endif
+
+ return ngetopt_bool_opt(env, descP, level, opt);
+}
+#endif
+
+
+#if defined(IPV6_ROUTER_ALERT)
+static
+ERL_NIF_TERM ngetopt_lvl_ipv6_router_alert(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+ return ngetopt_int_opt(env, descP, level, IPV6_ROUTER_ALERT);
+}
+#endif
+
+
+#if defined(IPV6_RTHDR)
+static
+ERL_NIF_TERM ngetopt_lvl_ipv6_rthdr(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+ return ngetopt_bool_opt(env, descP, level, IPV6_RTHDR);
+}
+#endif
+
+
+#if defined(IPV6_UNICAST_HOPS)
+static
+ERL_NIF_TERM ngetopt_lvl_ipv6_unicast_hops(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+ return ngetopt_int_opt(env, descP, level, IPV6_UNICAST_HOPS);
+}
+#endif
+
+
+#if defined(IPV6_V6ONLY)
+static
+ERL_NIF_TERM ngetopt_lvl_ipv6_v6only(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+#if defined(SOL_IPV6)
+ int level = SOL_IPV6;
+#else
+ int level = IPPROTO_IPV6;
+#endif
+
+ return ngetopt_bool_opt(env, descP, level, IPV6_V6ONLY);
+}
+#endif
+
+
+#endif // defined(HAVE_IPV6)
+
+
+
+/* ngetopt_lvl_tcp - Level *TCP* option(s)
+ */
+static
+ERL_NIF_TERM ngetopt_lvl_tcp(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt)
+{
+ ERL_NIF_TERM result;
+
+ switch (eOpt) {
+#if defined(TCP_CONGESTION)
+ case SOCKET_OPT_TCP_CONGESTION:
+ result = ngetopt_lvl_tcp_congestion(env, descP);
+ break;
+#endif
+
+#if defined(TCP_MAXSEG)
+ case SOCKET_OPT_TCP_MAXSEG:
+ result = ngetopt_lvl_tcp_maxseg(env, descP);
+ break;
+#endif
+
+#if defined(TCP_NODELAY)
+ case SOCKET_OPT_TCP_NODELAY:
+ result = ngetopt_lvl_tcp_nodelay(env, descP);
+ break;
+#endif
+
+ default:
+ result = esock_make_error(env, esock_atom_einval);
+ break;
+ }
+
+ return result;
+}
+
+
+/* ngetopt_lvl_tcp_congestion - Level TCP CONGESTION option
+ */
+#if defined(TCP_CONGESTION)
+static
+ERL_NIF_TERM ngetopt_lvl_tcp_congestion(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ int max = SOCKET_OPT_TCP_CONGESTION_NAME_MAX+1;
+
+ return ngetopt_str_opt(env, descP, IPPROTO_TCP, TCP_CONGESTION, max);
+}
+#endif
+
+
+/* ngetopt_lvl_tcp_maxseg - Level TCP MAXSEG option
+ */
+#if defined(TCP_MAXSEG)
+static
+ERL_NIF_TERM ngetopt_lvl_tcp_maxseg(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ return ngetopt_int_opt(env, descP, IPPROTO_TCP, TCP_MAXSEG);
+}
+#endif
+
+
+/* ngetopt_lvl_tcp_nodelay - Level TCP NODELAY option
+ */
+#if defined(TCP_NODELAY)
+static
+ERL_NIF_TERM ngetopt_lvl_tcp_nodelay(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ return ngetopt_bool_opt(env, descP, IPPROTO_TCP, TCP_NODELAY);
+}
+#endif
+
+
+
+/* ngetopt_lvl_udp - Level *UDP* option(s)
+ */
+static
+ERL_NIF_TERM ngetopt_lvl_udp(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt)
+{
+ ERL_NIF_TERM result;
+
+ switch (eOpt) {
+#if defined(UDP_CORK)
+ case SOCKET_OPT_UDP_CORK:
+ result = ngetopt_lvl_udp_cork(env, descP);
+ break;
+#endif
+
+ default:
+ result = esock_make_error(env, esock_atom_einval);
+ break;
+ }
+
+ return result;
+}
+
+
+/* ngetopt_lvl_udp_cork - Level UDP CORK option
+ */
+#if defined(UDP_CORK)
+static
+ERL_NIF_TERM ngetopt_lvl_udp_cork(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ return ngetopt_bool_opt(env, descP, IPPROTO_UDP, UDP_CORK);
+}
+#endif
+
+
+
+/* ngetopt_lvl_sctp - Level *SCTP* option(s)
+ */
+#if defined(HAVE_SCTP)
+static
+ERL_NIF_TERM ngetopt_lvl_sctp(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int eOpt)
+{
+ ERL_NIF_TERM result;
+
+ SSDBG( descP,
+ ("SOCKET", "ngetopt_lvl_sctp -> entry with"
+ "\r\n opt: %d"
+ "\r\n", eOpt) );
+
+ switch (eOpt) {
+#if defined(SCTP_ASSOCINFO)
+ case SOCKET_OPT_SCTP_ASSOCINFO:
+ result = ngetopt_lvl_sctp_associnfo(env, descP);
+ break;
+#endif
+
+#if defined(SCTP_AUTOCLOSE)
+ case SOCKET_OPT_SCTP_AUTOCLOSE:
+ result = ngetopt_lvl_sctp_autoclose(env, descP);
+ break;
+#endif
+
+#if defined(SCTP_DISABLE_FRAGMENTS)
+ case SOCKET_OPT_SCTP_DISABLE_FRAGMENTS:
+ result = ngetopt_lvl_sctp_disable_fragments(env, descP);
+ break;
+#endif
+
+#if defined(SCTP_INITMSG)
+ case SOCKET_OPT_SCTP_INITMSG:
+ result = ngetopt_lvl_sctp_initmsg(env, descP);
+ break;
+#endif
+
+#if defined(SCTP_MAXSEG)
+ case SOCKET_OPT_SCTP_MAXSEG:
+ result = ngetopt_lvl_sctp_maxseg(env, descP);
+ break;
+#endif
+
+#if defined(SCTP_NODELAY)
+ case SOCKET_OPT_SCTP_NODELAY:
+ result = ngetopt_lvl_sctp_nodelay(env, descP);
+ break;
+#endif
+
+#if defined(SCTP_RTOINFO)
+ case SOCKET_OPT_SCTP_RTOINFO:
+ result = ngetopt_lvl_sctp_rtoinfo(env, descP);
+ break;
+#endif
+
+ default:
+ result = esock_make_error(env, esock_atom_einval);
+ break;
+ }
+
+ SSDBG( descP,
+ ("SOCKET", "ngetopt_lvl_sctp -> done when"
+ "\r\n result: %T"
+ "\r\n", result) );
+
+ return result;
+}
+
+
+/* ngetopt_lvl_sctp_associnfo - Level SCTP ASSOCINFO option
+ *
+ * <KOLLA>
+ *
+ * We should really specify which association this relates to,
+ * as it is now we get assoc-id = 0. If this socket is an
+ * association (and not an endpoint) then it will have an
+ * assoc id. But since the sctp support at present is "limited",
+ * we leave it for now.
+ * What do we do if this is an endpoint? Invalid op?
+ *
+ * </KOLLA>
+ */
+#if defined(SCTP_ASSOCINFO)
+static
+ERL_NIF_TERM ngetopt_lvl_sctp_associnfo(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ ERL_NIF_TERM result;
+ struct sctp_assocparams val;
+ SOCKOPTLEN_T valSz = sizeof(val);
+ int res;
+
+ SSDBG( descP, ("SOCKET", "ngetopt_lvl_sctp_associnfo -> entry\r\n") );
+
+ sys_memzero((char*) &val, valSz);
+ res = sock_getopt(descP->sock, IPPROTO_SCTP, SCTP_ASSOCINFO, &val, &valSz);
+
+ if (res != 0) {
+ result = esock_make_error_errno(env, sock_errno());
+ } else {
+ ERL_NIF_TERM eAssocParams;
+ ERL_NIF_TERM keys[] = {atom_assoc_id, atom_max_rxt, atom_num_peer_dests,
+ atom_peer_rwnd, atom_local_rwnd, atom_cookie_life};
+ ERL_NIF_TERM vals[] = {MKUI(env, val.sasoc_assoc_id),
+ MKUI(env, val.sasoc_asocmaxrxt),
+ MKUI(env, val.sasoc_number_peer_destinations),
+ MKUI(env, val.sasoc_peer_rwnd),
+ MKUI(env, val.sasoc_local_rwnd),
+ MKUI(env, val.sasoc_cookie_life)};
+ unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM);
+ unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM);
+
+ ESOCK_ASSERT( (numKeys == numVals) );
+
+ if (!MKMA(env, keys, vals, numKeys, &eAssocParams))
+ return esock_make_error(env, esock_atom_einval);;
+
+ result = esock_make_ok2(env, eAssocParams);
+ }
+
+ SSDBG( descP,
+ ("SOCKET", "ngetopt_lvl_sctp_associnfo -> done with"
+ "\r\n res: %d"
+ "\r\n result: %T"
+ "\r\n", res, result) );
+
+ return result;
+}
+#endif
+
+
+/* ngetopt_lvl_sctp_autoclose - Level SCTP AUTOCLOSE option
+ */
+#if defined(SCTP_AUTOCLOSE)
+static
+ERL_NIF_TERM ngetopt_lvl_sctp_autoclose(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ return ngetopt_int_opt(env, descP, IPPROTO_SCTP, SCTP_AUTOCLOSE);
+}
+#endif
+
+
+/* ngetopt_lvl_sctp_disable_fragments - Level SCTP DISABLE:FRAGMENTS option
+ */
+#if defined(SCTP_DISABLE_FRAGMENTS)
+static
+ERL_NIF_TERM ngetopt_lvl_sctp_disable_fragments(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ return ngetopt_bool_opt(env, descP, IPPROTO_SCTP, SCTP_DISABLE_FRAGMENTS);
+}
+#endif
+
+
+/* ngetopt_lvl_sctp_initmsg - Level SCTP INITMSG option
+ *
+ */
+#if defined(SCTP_INITMSG)
+static
+ERL_NIF_TERM ngetopt_lvl_sctp_initmsg(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ ERL_NIF_TERM result;
+ struct sctp_initmsg val;
+ SOCKOPTLEN_T valSz = sizeof(val);
+ int res;
+
+ SSDBG( descP, ("SOCKET", "ngetopt_lvl_sctp_initmsg -> entry\r\n") );
+
+ sys_memzero((char*) &val, valSz);
+ res = sock_getopt(descP->sock, IPPROTO_SCTP, SCTP_INITMSG, &val, &valSz);
+
+ if (res != 0) {
+ result = esock_make_error_errno(env, sock_errno());
+ } else {
+ ERL_NIF_TERM eInitMsg;
+ ERL_NIF_TERM keys[] = {atom_num_outstreams, atom_max_instreams,
+ atom_max_attempts, atom_max_init_timeo};
+ ERL_NIF_TERM vals[] = {MKUI(env, val.sinit_num_ostreams),
+ MKUI(env, val.sinit_max_instreams),
+ MKUI(env, val.sinit_max_attempts),
+ MKUI(env, val.sinit_max_init_timeo)};
+ unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM);
+ unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM);
+
+ ESOCK_ASSERT( (numKeys == numVals) );
+
+ if (!MKMA(env, keys, vals, numKeys, &eInitMsg))
+ return esock_make_error(env, esock_atom_einval);;
+
+ result = esock_make_ok2(env, eInitMsg);
+ }
+
+ SSDBG( descP,
+ ("SOCKET", "ngetopt_lvl_sctp_initmsg -> done with"
+ "\r\n res: %d"
+ "\r\n result: %T"
+ "\r\n", res, result) );
+
+ return result;
+}
+#endif
+
+
+/* ngetopt_lvl_sctp_maxseg - Level SCTP MAXSEG option
+ */
+#if defined(SCTP_MAXSEG)
+static
+ERL_NIF_TERM ngetopt_lvl_sctp_maxseg(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ return ngetopt_int_opt(env, descP, IPPROTO_SCTP, SCTP_MAXSEG);
+}
+#endif
+
+
+/* ngetopt_lvl_sctp_nodelay - Level SCTP NODELAY option
+ */
+#if defined(SCTP_NODELAY)
+static
+ERL_NIF_TERM ngetopt_lvl_sctp_nodelay(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ return ngetopt_bool_opt(env, descP, IPPROTO_SCTP, SCTP_NODELAY);
+}
+#endif
+
+
+/* ngetopt_lvl_sctp_associnfo - Level SCTP ASSOCINFO option
+ *
+ * <KOLLA>
+ *
+ * We should really specify which association this relates to,
+ * as it is now we get assoc-id = 0. If this socket is an
+ * association (and not an endpoint) then it will have an
+ * assoc id (we can assume). But since the sctp support at
+ * present is "limited", we leave it for now.
+ * What do we do if this is an endpoint? Invalid op?
+ *
+ * </KOLLA>
+ */
+#if defined(SCTP_RTOINFO)
+static
+ERL_NIF_TERM ngetopt_lvl_sctp_rtoinfo(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ ERL_NIF_TERM result;
+ struct sctp_rtoinfo val;
+ SOCKOPTLEN_T valSz = sizeof(val);
+ int res;
+
+ SSDBG( descP, ("SOCKET", "ngetopt_lvl_sctp_rtoinfo -> entry\r\n") );
+
+ sys_memzero((char*) &val, valSz);
+ res = sock_getopt(descP->sock, IPPROTO_SCTP, SCTP_RTOINFO, &val, &valSz);
+
+ if (res != 0) {
+ result = esock_make_error_errno(env, sock_errno());
+ } else {
+ ERL_NIF_TERM eRTOInfo;
+ ERL_NIF_TERM keys[] = {atom_assoc_id, atom_initial, atom_max, atom_min};
+ ERL_NIF_TERM vals[] = {MKUI(env, val.srto_assoc_id),
+ MKUI(env, val.srto_initial),
+ MKUI(env, val.srto_max),
+ MKUI(env, val.srto_min)};
+ unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM);
+ unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM);
+
+ ESOCK_ASSERT( (numKeys == numVals) );
+
+ if (!MKMA(env, keys, vals, numKeys, &eRTOInfo))
+ return esock_make_error(env, esock_atom_einval);;
+
+ result = esock_make_ok2(env, eRTOInfo);
+ }
+
+ SSDBG( descP,
+ ("SOCKET", "ngetopt_lvl_sctp_rtoinfo -> done with"
+ "\r\n res: %d"
+ "\r\n result: %T"
+ "\r\n", res, result) );
+
+ return result;
+}
+#endif
+
+
+
+#endif // defined(HAVE_SCTP)
+
+
+
+/* ngetopt_bool_opt - get an (integer) bool option
+ */
+static
+ERL_NIF_TERM ngetopt_bool_opt(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int level,
+ int opt)
+{
+ ERL_NIF_TERM result;
+ int val;
+ SOCKOPTLEN_T valSz = sizeof(val);
+ int res;
+
+ /*
+ SSDBG( descP, ("SOCKET", "ngetopt_bool_opt -> entry with"
+ "\r\n: level: %d"
+ "\r\n: opt: %d"
+ "\r\n", level, opt) );
+ */
+
+ res = sock_getopt(descP->sock, level, opt, &val, &valSz);
+
+ if (res != 0) {
+ result = esock_make_error_errno(env, sock_errno());
+ } else {
+ ERL_NIF_TERM bval = ((val) ? atom_true : atom_false);
+
+ result = esock_make_ok2(env, bval);
+ }
+
+ /*
+ SSDBG( descP, ("SOCKET", "ngetopt_bool_opt -> done when"
+ "\r\n: res: %d"
+ "\r\n: result: %T"
+ "\r\n", res, result) );
+ */
+
+ return result;
+}
+
+
+/* ngetopt_int_opt - get an integer option
+ */
+static
+ERL_NIF_TERM ngetopt_int_opt(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int level,
+ int opt)
+{
+ ERL_NIF_TERM result;
+ int val;
+ SOCKOPTLEN_T valSz = sizeof(val);
+ int res;
+
+ res = sock_getopt(descP->sock, level, opt, &val, &valSz);
+
+ if (res != 0) {
+ result = esock_make_error_errno(env, sock_errno());
+ } else {
+ result = esock_make_ok2(env, MKI(env, val));
+ }
+
+ return result;
+}
+
+
+
+/* ngetopt_timeval_opt - get an timeval option
+ */
+static
+ERL_NIF_TERM ngetopt_timeval_opt(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int level,
+ int opt)
+{
+ ERL_NIF_TERM result;
+ struct timeval val;
+ SOCKOPTLEN_T valSz = sizeof(val);
+ int res;
+
+ SSDBG( descP,
+ ("SOCKET", "ngetopt_timeval_opt -> entry with"
+ "\r\n level: %d"
+ "\r\n opt: %d"
+ "\r\n", level, opt) );
+
+ sys_memzero((char*) &val, valSz);
+ res = sock_getopt(descP->sock, level, opt, &val, &valSz);
+
+ if (res != 0) {
+ result = esock_make_error_errno(env, sock_errno());
+ } else {
+ ERL_NIF_TERM eTimeVal;
+ char* xres;
+
+ if ((xres = esock_encode_timeval(env, &val, &eTimeVal)) != NULL)
+ result = esock_make_error_str(env, xres);
+ else
+ result = esock_make_ok2(env, eTimeVal);
+ }
+
+ SSDBG( descP,
+ ("SOCKET", "ngetopt_timeval_opt -> done when"
+ "\r\n result: %T"
+ "\r\n", result) );
+
+ return result;
+}
+
+
+
+/* ngetopt_str_opt - get an string option
+ *
+ * We provide the max size of the string. This is the
+ * size of the buffer we allocate for the value.
+ * The actual size of the (read) value will be communicated
+ * in the optSz variable.
+ */
+static
+ERL_NIF_TERM ngetopt_str_opt(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int level,
+ int opt,
+ int max)
+{
+ ERL_NIF_TERM result;
+ char* val = MALLOC(max);
+ SOCKOPTLEN_T valSz = max;
+ int res;
+
+ SSDBG( descP,
+ ("SOCKET", "ngetopt_str_opt -> entry with"
+ "\r\n level: %d"
+ "\r\n opt: %d"
+ "\r\n max: %d"
+ "\r\n", level, opt, max) );
+
+ res = sock_getopt(descP->sock, level, opt, val, &valSz);
+
+ if (res != 0) {
+ result = esock_make_error_errno(env, sock_errno());
+ } else {
+ ERL_NIF_TERM sval = MKSL(env, val, valSz);
+
+ result = esock_make_ok2(env, sval);
+ }
+
+ SSDBG( descP,
+ ("SOCKET", "ngetopt_str_opt -> done when"
+ "\r\n result: %T"
+ "\r\n", result) );
+
+ FREE(val);
+
+ return result;
+}
+#endif // if !defined(__WIN32__)
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_sockname - get socket name
+ *
+ * Description:
+ * Returns the current address to which the socket is bound.
+ *
+ * Arguments:
+ * Socket (ref) - Points to the socket descriptor.
+ */
+
+static
+ERL_NIF_TERM nif_sockname(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ SocketDescriptor* descP;
+ ERL_NIF_TERM res;
+
+ SGDBG( ("SOCKET", "nif_sockname -> entry with argc: %d\r\n", argc) );
+
+ /* Extract arguments and perform preliminary validation */
+
+ if ((argc != 1) ||
+ !enif_get_resource(env, argv[0], sockets, (void**) &descP)) {
+ return enif_make_badarg(env);
+ }
+
+ if (IS_CLOSED(descP) || IS_CLOSING(descP))
+ return esock_make_error(env, atom_closed);
+
+ SSDBG( descP,
+ ("SOCKET", "nif_sockname -> args when sock = %d:"
+ "\r\n Socket: %T"
+ "\r\n", descP->sock, argv[0]) );
+
+ res = nsockname(env, descP);
+
+ SSDBG( descP, ("SOCKET", "nif_sockname -> done with res = %T\r\n", res) );
+
+ return res;
+#endif // if defined(__WIN32__)
+}
+
+
+
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM nsockname(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ SocketAddress sa;
+ SocketAddress* saP = &sa;
+ unsigned int sz = sizeof(SocketAddress);
+
+ sys_memzero((char*) saP, sz);
+ if (IS_SOCKET_ERROR(sock_name(descP->sock, (struct sockaddr*) saP, &sz))) {
+ return esock_make_error_errno(env, sock_errno());
+ } else {
+ ERL_NIF_TERM esa;
+ char* xres;
+
+ if ((xres = esock_encode_sockaddr(env, saP, sz, &esa)) != NULL)
+ return esock_make_error_str(env, xres);
+ else
+ return esock_make_ok2(env, esa);
+ }
+}
+#endif // if !defined(__WIN32__)
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_peername - get name of the connected peer socket
+ *
+ * Description:
+ * Returns the address of the peer connected to the socket.
+ *
+ * Arguments:
+ * Socket (ref) - Points to the socket descriptor.
+ */
+
+static
+ERL_NIF_TERM nif_peername(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ SocketDescriptor* descP;
+ ERL_NIF_TERM res;
+
+ SGDBG( ("SOCKET", "nif_peername -> entry with argc: %d\r\n", argc) );
+
+ /* Extract arguments and perform preliminary validation */
+
+ if ((argc != 1) ||
+ !enif_get_resource(env, argv[0], sockets, (void**) &descP)) {
+ return enif_make_badarg(env);
+ }
+
+ if (IS_CLOSED(descP) || IS_CLOSING(descP))
+ return esock_make_error(env, atom_closed);
+
+ SSDBG( descP,
+ ("SOCKET", "nif_peername -> args when sock = %d:"
+ "\r\n Socket: %T"
+ "\r\n", descP->sock, argv[0]) );
+
+ res = npeername(env, descP);
+
+ SSDBG( descP, ("SOCKET", "nif_peername -> done with res = %T\r\n", res) );
+
+ return res;
+#endif // if defined(__WIN32__)
+}
+
+
+
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM npeername(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ SocketAddress sa;
+ SocketAddress* saP = &sa;
+ unsigned int sz = sizeof(SocketAddress);
+
+ sys_memzero((char*) saP, sz);
+ if (IS_SOCKET_ERROR(sock_peer(descP->sock, (struct sockaddr*) saP, &sz))) {
+ return esock_make_error_errno(env, sock_errno());
+ } else {
+ ERL_NIF_TERM esa;
+ char* xres;
+
+ if ((xres = esock_encode_sockaddr(env, saP, sz, &esa)) != NULL)
+ return esock_make_error_str(env, xres);
+ else
+ return esock_make_ok2(env, esa);
+ }
+}
+#endif // if !defined(__WIN32__)
+
+
+
+/* ----------------------------------------------------------------------
+ * nif_cancel
+ *
+ * Description:
+ * Cancel a previous select!
+ *
+ * Arguments:
+ * Socket (ref) - Points to the socket descriptor.
+ * Operation (atom) - What kind of operation (accept, send, ...) is to be cancelled
+ * Ref (ref) - Unique id for the operation
+ */
+static
+ERL_NIF_TERM nif_cancel(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[])
+{
+#if defined(__WIN32__)
+ return enif_raise_exception(env, MKA(env, "notsup"));
+#else
+ SocketDescriptor* descP;
+ ERL_NIF_TERM op, opRef, result;
+
+ SGDBG( ("SOCKET", "nif_cancel -> entry with argc: %d\r\n", argc) );
+
+ /* Extract arguments and perform preliminary validation */
+
+ if ((argc != 3) ||
+ !enif_get_resource(env, argv[0], sockets, (void**) &descP)) {
+ return enif_make_badarg(env);
+ }
+ op = argv[1];
+ opRef = argv[2];
+
+ if (IS_CLOSED(descP) || IS_CLOSING(descP))
+ return esock_make_error(env, atom_closed);
+
+ SSDBG( descP,
+ ("SOCKET", "nif_cancel -> args when sock = %d:"
+ "\r\n op: %T"
+ "\r\n opRef: %T"
+ "\r\n", descP->sock, op, opRef) );
+
+ result = ncancel(env, descP, op, opRef);
+
+ SSDBG( descP,
+ ("SOCKET", "nif_cancel -> done with result: "
+ "\r\n %T"
+ "\r\n", result) );
+
+ return result;
+#endif // if !defined(__WIN32__)
+}
+
+
+#if !defined(__WIN32__)
+static
+ERL_NIF_TERM ncancel(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM op,
+ ERL_NIF_TERM opRef)
+{
+ /* <KOLLA>
+ *
+ * Do we really need all these variants? Should it not be enough with:
+ *
+ * connect | accept | send | recv
+ *
+ * </KOLLA>
+ */
+ if (COMPARE(op, esock_atom_connect) == 0) {
+ return ncancel_connect(env, descP, opRef);
+ } else if (COMPARE(op, esock_atom_accept) == 0) {
+ return ncancel_accept(env, descP, opRef);
+ } else if (COMPARE(op, esock_atom_send) == 0) {
+ return ncancel_send(env, descP, opRef);
+ } else if (COMPARE(op, esock_atom_sendto) == 0) {
+ return ncancel_send(env, descP, opRef);
+ } else if (COMPARE(op, esock_atom_sendmsg) == 0) {
+ return ncancel_send(env, descP, opRef);
+ } else if (COMPARE(op, esock_atom_recv) == 0) {
+ return ncancel_recv(env, descP, opRef);
+ } else if (COMPARE(op, esock_atom_recvfrom) == 0) {
+ return ncancel_recv(env, descP, opRef);
+ } else if (COMPARE(op, esock_atom_recvmsg) == 0) {
+ return ncancel_recv(env, descP, opRef);
+ } else {
+ return esock_make_error(env, esock_atom_einval);
+ }
+}
+
+
+
+/* *** ncancel_connect ***
+ *
+ *
+ */
+static
+ERL_NIF_TERM ncancel_connect(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM opRef)
+{
+ return ncancel_write_select(env, descP, opRef);
+}
+
+
+/* *** ncancel_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.
+ *
+ */
+static
+ERL_NIF_TERM ncancel_accept(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM opRef)
+{
+ ERL_NIF_TERM res;
+
+ SSDBG( descP,
+ ("SOCKET", "ncancel_accept -> entry with"
+ "\r\n opRef: %T"
+ "\r\n %s"
+ "\r\n", opRef,
+ ((descP->currentAcceptorP == NULL) ? "without acceptor" : "with acceptor")) );
+
+ MLOCK(descP->accMtx);
+
+ if (descP->currentAcceptorP != NULL) {
+ if (COMPARE(opRef, descP->currentAcceptor.ref) == 0) {
+ res = ncancel_accept_current(env, descP);
+ } else {
+ res = ncancel_accept_waiting(env, descP, opRef);
+ }
+ } else {
+ /* Or badarg? */
+ res = esock_make_error(env, esock_atom_einval);
+ }
+
+ MUNLOCK(descP->accMtx);
+
+ SSDBG( descP,
+ ("SOCKET", "ncancel_accept -> done with result:"
+ "\r\n %T"
+ "\r\n", 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 ncancel_accept_current(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ int sres;
+ ERL_NIF_TERM res;
+
+ SSDBG( descP, ("SOCKET", "ncancel_accept_current -> entry\r\n") );
+
+ DEMONP("ncancel_accept_current -> current acceptor",
+ env, descP, &descP->currentAcceptor.mon);
+ res = ncancel_read_select(env, descP, descP->currentAcceptor.ref);
+
+ SSDBG( descP, ("SOCKET", "ncancel_accept_current -> cancel res: %T\r\n", res) );
+
+ if (acceptor_pop(env, descP,
+ &descP->currentAcceptor.pid,
+ &descP->currentAcceptor.mon,
+ &descP->currentAcceptor.ref)) {
+
+ /* There was another one */
+
+ SSDBG( descP, ("SOCKET", "ncancel_accept_current -> new (active) acceptor: "
+ "\r\n pid: %T"
+ "\r\n ref: %T"
+ "\r\n",
+ descP->currentAcceptor.pid,
+ descP->currentAcceptor.ref) );
+
+ if ((sres = esock_select_read(env, descP->sock, descP,
+ &descP->currentAcceptor.pid,
+ descP->currentAcceptor.ref)) < 0) {
+ return esock_make_error(env,
+ MKT2(env,
+ esock_atom_select_failed,
+ MKI(env, sres)));
+ }
+ } else {
+ SSDBG( descP, ("SOCKET", "ncancel_accept_current -> no more acceptors\r\n") );
+ descP->currentAcceptorP = NULL;
+ descP->state = SOCKET_STATE_LISTENING;
+ }
+
+ SSDBG( descP, ("SOCKET", "ncancel_accept_current -> done with result:"
+ "\r\n %T"
+ "\r\n", res) );
+
+ return res;
+}
+
+
+/* These processes have not performed a select, so we can simply
+ * remove them from the acceptor queue.
+ */
+static
+ERL_NIF_TERM ncancel_accept_waiting(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM opRef)
+{
+ ErlNifPid caller;
+
+ if (enif_self(env, &caller) == NULL)
+ return esock_make_error(env, atom_exself);
+
+ /* unqueue request from (acceptor) queue */
+
+ if (acceptor_unqueue(env, descP, &caller)) {
+ return esock_atom_ok;
+ } else {
+ /* Race? */
+ return esock_make_error(env, esock_atom_not_found);
+ }
+}
+
+
+
+/* *** ncancel_send ***
+ *
+ * Cancel a send operation.
+ * Its either the current writer or one of the waiting writers.
+ */
+static
+ERL_NIF_TERM ncancel_send(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM opRef)
+{
+ ERL_NIF_TERM res;
+
+ MLOCK(descP->writeMtx);
+
+ SSDBG( descP,
+ ("SOCKET", "ncancel_send -> entry with"
+ "\r\n opRef: %T"
+ "\r\n %s"
+ "\r\n", opRef,
+ ((descP->currentWriterP == NULL) ? "without writer" : "with writer")) );
+
+ if (descP->currentWriterP != NULL) {
+ if (COMPARE(opRef, descP->currentWriter.ref) == 0) {
+ res = ncancel_send_current(env, descP);
+ } else {
+ res = ncancel_send_waiting(env, descP, opRef);
+ }
+ } else {
+ /* Or badarg? */
+ res = esock_make_error(env, esock_atom_einval);
+ }
+
+ MUNLOCK(descP->writeMtx);
+
+ SSDBG( descP,
+ ("SOCKET", "ncancel_send -> done with result:"
+ "\r\n %T"
+ "\r\n", 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 ncancel_send_current(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ int sres;
+ ERL_NIF_TERM res;
+
+ SSDBG( descP, ("SOCKET", "ncancel_send_current -> entry\r\n") );
+
+ DEMONP("ncancel_recv_current -> current writer",
+ env, descP, &descP->currentWriter.mon);
+ res = ncancel_write_select(env, descP, descP->currentWriter.ref);
+
+ SSDBG( descP, ("SOCKET", "ncancel_send_current -> cancel res: %T\r\n", res) );
+
+ if (writer_pop(env, descP,
+ &descP->currentWriter.pid,
+ &descP->currentWriter.mon,
+ &descP->currentWriter.ref)) {
+
+ /* There was another one */
+
+ SSDBG( descP, ("SOCKET", "ncancel_send_current -> new (active) writer: "
+ "\r\n pid: %T"
+ "\r\n ref: %T"
+ "\r\n",
+ descP->currentWriter.pid,
+ descP->currentWriter.ref) );
+
+ if ((sres = esock_select_write(env, descP->sock, descP,
+ &descP->currentWriter.pid,
+ descP->currentWriter.ref)) < 0) {
+ return esock_make_error(env,
+ MKT2(env,
+ esock_atom_select_failed,
+ MKI(env, sres)));
+ }
+ } else {
+ SSDBG( descP, ("SOCKET", "ncancel_send_current -> no more writers\r\n") );
+ descP->currentWriterP = NULL;
+ }
+
+ SSDBG( descP, ("SOCKET", "ncancel_send_current -> done with result:"
+ "\r\n %T"
+ "\r\n", res) );
+
+ return res;
+}
+
+
+/* These processes have not performed a select, so we can simply
+ * remove them from the writer queue.
+ */
+static
+ERL_NIF_TERM ncancel_send_waiting(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM opRef)
+{
+ ErlNifPid caller;
+
+ if (enif_self(env, &caller) == NULL)
+ return esock_make_error(env, atom_exself);
+
+ /* unqueue request from (writer) queue */
+
+ if (writer_unqueue(env, descP, &caller)) {
+ return esock_atom_ok;
+ } else {
+ /* Race? */
+ return esock_make_error(env, esock_atom_not_found);
+ }
+}
+
+
+
+/* *** ncancel_recv ***
+ *
+ * Cancel a read operation.
+ * Its either the current reader or one of the waiting readers.
+ */
+static
+ERL_NIF_TERM ncancel_recv(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM opRef)
+{
+ ERL_NIF_TERM res;
+
+ MLOCK(descP->readMtx);
+
+ SSDBG( descP,
+ ("SOCKET", "ncancel_recv -> entry with"
+ "\r\n opRef: %T"
+ "\r\n %s"
+ "\r\n", opRef,
+ ((descP->currentReaderP == NULL) ? "without reader" : "with reader")) );
+
+ if (descP->currentReaderP != NULL) {
+ if (COMPARE(opRef, descP->currentReader.ref) == 0) {
+ res = ncancel_recv_current(env, descP);
+ } else {
+ res = ncancel_recv_waiting(env, descP, opRef);
+ }
+ } else {
+ /* Or badarg? */
+ res = esock_make_error(env, esock_atom_einval);
+ }
+
+ MUNLOCK(descP->readMtx);
+
+ SSDBG( descP,
+ ("SOCKET", "ncancel_recv -> done with result:"
+ "\r\n %T"
+ "\r\n", 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 ncancel_recv_current(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ int sres;
+ ERL_NIF_TERM res;
+
+ SSDBG( descP, ("SOCKET", "ncancel_recv_current -> entry\r\n") );
+
+ DEMONP("ncancel_recv_current -> current reader",
+ env, descP, &descP->currentReader.mon);
+ res = ncancel_read_select(env, descP, descP->currentReader.ref);
+
+ SSDBG( descP, ("SOCKET", "ncancel_recv_current -> cancel res: %T\r\n", res) );
+
+ if (reader_pop(env, descP,
+ &descP->currentReader.pid,
+ &descP->currentReader.mon,
+ &descP->currentReader.ref)) {
+
+ /* There was another one */
+
+ SSDBG( descP, ("SOCKET", "ncancel_recv_current -> new (active) reader: "
+ "\r\n pid: %T"
+ "\r\n ref: %T"
+ "\r\n",
+ descP->currentReader.pid,
+ descP->currentReader.ref) );
+
+ if ((sres = esock_select_read(env, descP->sock, descP,
+ &descP->currentReader.pid,
+ descP->currentReader.ref)) < 0) {
+ return esock_make_error(env,
+ MKT2(env,
+ esock_atom_select_failed,
+ MKI(env, sres)));
+ }
+ } else {
+ SSDBG( descP, ("SOCKET", "ncancel_recv_current -> no more readers\r\n") );
+ descP->currentReaderP = NULL;
+ }
+
+ SSDBG( descP, ("SOCKET", "ncancel_recv_current -> done with result:"
+ "\r\n %T"
+ "\r\n", res) );
+
+ return res;
+}
+
+
+/* These processes have not performed a select, so we can simply
+ * remove them from the reader queue.
+ */
+static
+ERL_NIF_TERM ncancel_recv_waiting(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM opRef)
+{
+ ErlNifPid caller;
+
+ if (enif_self(env, &caller) == NULL)
+ return esock_make_error(env, atom_exself);
+
+ /* unqueue request from (reader) queue */
+
+ if (reader_unqueue(env, descP, &caller)) {
+ return esock_atom_ok;
+ } else {
+ /* Race? */
+ return esock_make_error(env, esock_atom_not_found);
+ }
+}
+
+
+
+static
+ERL_NIF_TERM ncancel_read_select(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM opRef)
+{
+ return ncancel_mode_select(env, descP, opRef,
+ ERL_NIF_SELECT_READ,
+ ERL_NIF_SELECT_READ_CANCELLED);
+}
+
+
+static
+ERL_NIF_TERM ncancel_write_select(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM opRef)
+{
+ return ncancel_mode_select(env, descP, opRef,
+ ERL_NIF_SELECT_WRITE,
+ ERL_NIF_SELECT_WRITE_CANCELLED);
+}
+
+
+static
+ERL_NIF_TERM ncancel_mode_select(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM opRef,
+ int smode,
+ int rmode)
+{
+ int selectRes = esock_select_cancel(env, descP->sock, smode, descP);
+
+ if (selectRes & rmode) {
+ /* Was cancelled */
+ return esock_atom_ok;
+ } else if (selectRes > 0) {
+ /* Has already sent the message */
+ return esock_make_error(env, esock_atom_select_sent);
+ } else {
+ /* Stopped? */
+ SSDBG( descP, ("SOCKET", "ncancel_mode_select -> failed: %d (0x%lX)"
+ "\r\n", selectRes, selectRes) );
+ return esock_make_error(env, esock_atom_einval);
+ }
+
+}
+#endif // if !defined(__WIN32__)
+
+
+
+
+/* ----------------------------------------------------------------------
+ * 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, then we must
+ * be made to wait for our turn. This is done by pushing us unto the writer queue.
+ */
+#if !defined(__WIN32__)
+static
+BOOLEAN_T send_check_writer(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM ref,
+ ERL_NIF_TERM* checkResult)
+{
+ if (descP->currentWriterP != NULL) {
+ ErlNifPid caller;
+
+ if (enif_self(env, &caller) == NULL) {
+ *checkResult = esock_make_error(env, atom_exself);
+ return FALSE;
+ }
+
+ if (!compare_pids(env, &descP->currentWriter.pid, &caller)) {
+ /* Not the "current writer", so (maybe) push onto queue */
+
+ SSDBG( descP,
+ ("SOCKET", "send_check_writer -> not (current) writer\r\n") );
+
+ if (!writer_search4pid(env, descP, &caller))
+ *checkResult = writer_push(env, descP, caller, ref);
+ else
+ *checkResult = esock_make_error(env, esock_atom_eagain);
+
+ SSDBG( descP,
+ ("SOCKET",
+ "send_check_writer -> queue (push) result: %T\r\n",
+ checkResult) );
+
+ return FALSE;
+
+ }
+
+ }
+
+ *checkResult = esock_atom_ok; // Does not actually matter in this case, but ...
+
+ 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,
+ SocketDescriptor* descP,
+ ssize_t written,
+ ssize_t dataSize,
+ int saveErrno,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM sendRef)
+{
+ int sres;
+
+ SSDBG( descP,
+ ("SOCKET", "send_check_result -> entry with"
+ "\r\n written: %d"
+ "\r\n dataSize: %d"
+ "\r\n saveErrno: %d"
+ "\r\n", written, dataSize, saveErrno) );
+
+ if (written >= dataSize) {
+
+ cnt_inc(&descP->writePkgCnt, 1);
+ cnt_inc(&descP->writeByteCnt, written);
+ if (descP->currentWriterP != NULL)
+ DEMONP("send_check_result -> current writer",
+ env, descP, &descP->currentWriter.mon);
+
+ SSDBG( descP,
+ ("SOCKET", "send_check_result -> "
+ "everything written (%d,%d) - done\r\n", dataSize, written) );
+
+ /* Ok, this write is done maybe activate the next (if any) */
+
+ if (writer_pop(env, descP,
+ &descP->currentWriter.pid,
+ &descP->currentWriter.mon,
+ &descP->currentWriter.ref)) {
+
+ /* There was another one */
+
+ SSDBG( descP, ("SOCKET", "send_check_result -> new (active) writer: "
+ "\r\n pid: %T"
+ "\r\n ref: %T"
+ "\r\n",
+ descP->currentWriter.pid,
+ descP->currentWriter.ref) );
+
+ if ((sres = esock_select_write(env, descP->sock, descP,
+ &descP->currentWriter.pid,
+ descP->currentWriter.ref)) < 0) {
+ return esock_make_error(env,
+ MKT2(env,
+ esock_atom_select_failed,
+ MKI(env, sres)));
+ }
+ } else {
+ descP->currentWriterP = NULL;
+ }
+
+ return esock_atom_ok;
+
+ } else if (written < 0) {
+
+ /* Some kind of send failure - check what kind */
+
+ if ((saveErrno != EAGAIN) && (saveErrno != EINTR)) {
+ ErlNifPid pid;
+ // ErlNifMonitor mon;
+ ESockMonitor mon;
+ ERL_NIF_TERM ref, res;
+
+ /*
+ * An actual failure - we (and everyone waiting) give up
+ */
+
+ cnt_inc(&descP->writeFails, 1);
+
+ SSDBG( descP,
+ ("SOCKET", "send_check_result -> error: %d\r\n", saveErrno) );
+
+ res = esock_make_error_errno(env, saveErrno);
+
+ if (descP->currentWriterP != NULL) {
+ DEMONP("send_check_result -> current writer",
+ env, descP, &descP->currentWriter.mon);
+
+ while (writer_pop(env, descP, &pid, &mon, &ref)) {
+ SSDBG( descP,
+ ("SOCKET", "send_check_result -> abort %T\r\n", pid) );
+ esock_send_abort_msg(env, sockRef, ref, res, &pid);
+ DEMONP("send_check_result -> pop'ed writer",
+ env, descP, &mon);
+ }
+ }
+
+ return res;
+
+ } else {
+ /* Ok, try again later */
+
+ SSDBG( descP, ("SOCKET", "send_check_result -> try again\r\n") );
+ }
+
+ }
+ else {
+ SSDBG( descP,
+ ("SOCKET", "send_check_result -> "
+ "not entire package written (%d of %d)\r\n", written, dataSize) );
+ }
+
+ /* 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.
+ */
+
+ if (descP->currentWriterP == NULL) {
+ ErlNifPid caller;
+
+ if (enif_self(env, &caller) == NULL)
+ return esock_make_error(env, atom_exself);
+ descP->currentWriter.pid = caller;
+ if (MONP("send_check_result -> current writer",
+ env, descP,
+ &descP->currentWriter.pid,
+ &descP->currentWriter.mon) != 0)
+ return esock_make_error(env, atom_exmon);
+ descP->currentWriter.ref = enif_make_copy(descP->env, sendRef);
+ descP->currentWriterP = &descP->currentWriter;
+ }
+
+ cnt_inc(&descP->writeWaits, 1);
+
+ sres = esock_select_write(env, descP->sock, descP, NULL, sendRef);
+
+ if (written >= 0) {
+ if (sres < 0) {
+ /* Returned: {error, Reason}
+ * Reason: {select_failed, sres, written}
+ */
+ return esock_make_error(env,
+ MKT3(env,
+ esock_atom_select_failed,
+ MKI(env, sres),
+ MKI(env, written)));
+ } else {
+ return esock_make_ok2(env, MKI(env, written));
+ }
+ } else {
+ if (sres < 0) {
+ /* Returned: {error, Reason}
+ * Reason: {select_failed, sres}
+ */
+ return esock_make_error(env,
+ MKT2(env,
+ esock_atom_select_failed,
+ MKI(env, sres)));
+ } else {
+ return esock_make_error(env, esock_atom_eagain);
+ }
+ }
+}
+
+
+
+/* *** 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.
+ */
+static
+BOOLEAN_T recv_check_reader(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM ref,
+ ERL_NIF_TERM* checkResult)
+{
+ if (descP->currentReaderP != NULL) {
+ ErlNifPid caller;
+
+ if (enif_self(env, &caller) == NULL) {
+ *checkResult = esock_make_error(env, atom_exself);
+ return FALSE;
+ }
+
+ if (!compare_pids(env, &descP->currentReader.pid, &caller)) {
+ ERL_NIF_TERM tmp;
+
+ /* Not the "current reader", so (maybe) push onto queue */
+
+ SSDBG( descP,
+ ("SOCKET", "recv_check_reader -> not (current) reader\r\n") );
+
+ if (!reader_search4pid(env, descP, &caller))
+ tmp = reader_push(env, descP, caller, ref);
+ else
+ tmp = esock_make_error(env, esock_atom_eagain);
+
+ SSDBG( descP,
+ ("SOCKET",
+ "recv_check_reader -> queue (push) result: %T\r\n", tmp) );
+
+ *checkResult = tmp;
+
+ return FALSE;
+
+ }
+
+ }
+
+ *checkResult = esock_atom_ok; // Does not actually matter in this case, but ...
+
+ return TRUE;
+}
+
+
+
+/* *** recv_init_current_reader ***
+ *
+ * Initiate (maybe) the currentReader structure of the descriptor.
+ * Including monitoring the calling process.
+ */
+static
+char* recv_init_current_reader(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM recvRef)
+{
+ if (descP->currentReaderP == NULL) {
+ ErlNifPid caller;
+
+ if (enif_self(env, &caller) == NULL)
+ return str_exself;
+
+ descP->currentReader.pid = caller;
+ if (MONP("recv_init_current_reader -> current reader",
+ env, descP,
+ &descP->currentReader.pid,
+ &descP->currentReader.mon) != 0) {
+ return str_exmon;
+ }
+ descP->currentReader.ref = enif_make_copy(descP->env, recvRef);
+ descP->currentReaderP = &descP->currentReader;
+ }
+
+ return NULL;
+}
+
+
+
+/* *** 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
+ERL_NIF_TERM recv_update_current_reader(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ int sres;
+ ERL_NIF_TERM res = esock_atom_ok;
+
+ if (descP->currentReaderP != NULL) {
+
+ DEMONP("recv_update_current_reader -> current reader",
+ env, descP, &descP->currentReader.mon);
+
+ if (reader_pop(env, descP,
+ &descP->currentReader.pid,
+ &descP->currentReader.mon,
+ &descP->currentReader.ref)) {
+
+ /* There was another one */
+
+ SSDBG( descP,
+ ("SOCKET", "recv_update_current_reader -> new (active) reader: "
+ "\r\n pid: %T"
+ "\r\n ref: %T"
+ "\r\n",
+ descP->currentReader.pid,
+ descP->currentReader.ref) );
+
+ if ((sres = esock_select_read(env, descP->sock, descP,
+ &descP->currentReader.pid,
+ descP->currentReader.ref)) < 0) {
+ res = esock_make_error(env,
+ MKT2(env,
+ esock_atom_select_failed,
+ MKI(env, sres)));
+ }
+ } else {
+ descP->currentReaderP = NULL;
+ }
+ }
+
+ return res;
+}
+
+
+
+/* *** recv_error_current_reader ***
+ *
+ * Process the current reader and any waiting readers
+ * when a read (fatal) error has occured.
+ * All waiting readers will be "aborted", that is a
+ * nif_abort message will be sent (with reaf and reason).
+ */
+static
+void recv_error_current_reader(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM reason)
+{
+ if (descP->currentReaderP != NULL) {
+ ErlNifPid pid;
+ // ErlNifMonitor mon;
+ ESockMonitor mon;
+ ERL_NIF_TERM ref;
+
+ DEMONP("recv_error_current_reader -> current reader",
+ env, descP, &descP->currentReader.mon);
+
+ while (reader_pop(env, descP, &pid, &mon, &ref)) {
+ SSDBG( descP,
+ ("SOCKET", "recv_error_current_reader -> abort %T\r\n", pid) );
+ esock_send_abort_msg(env, sockRef, ref, reason, &pid);
+ DEMONP("recv_error_current_reader -> pop'ed reader",
+ env, descP, &mon);
+ }
+ }
+}
+
+
+
+/* *** recv_check_result ***
+ *
+ * Process the result of a call to recv.
+ */
+static
+ERL_NIF_TERM recv_check_result(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int read,
+ int toRead,
+ int saveErrno,
+ ErlNifBinary* bufP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef)
+{
+ char* xres;
+ int sres;
+ ERL_NIF_TERM res, data;
+
+ SSDBG( descP,
+ ("SOCKET", "recv_check_result -> entry with"
+ "\r\n read: %d"
+ "\r\n toRead: %d"
+ "\r\n saveErrno: %d"
+ "\r\n recvRef: %T"
+ "\r\n", read, 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)) {
+
+ res = esock_make_error(env, atom_closed);
+
+ /*
+ * 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 from a stream socket!
+ *
+ * We must also notify any waiting readers!
+ */
+
+ recv_error_current_reader(env, descP, sockRef, res);
+
+ FREE_BIN(bufP);
+
+ return res;
+
+ }
+
+ /* There is a special case: If the provided 'to read' value is
+ * zero (0) (only for type =/= stream).
+ * That means that we reads 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 -> [%d] filled the buffer\r\n", toRead) );
+
+ if (toRead == 0) {
+
+ /* +++ Give us everything you have got => *
+ * (maybe) needs to continue +++ */
+
+ /* How do we do this?
+ * Either:
+ * 1) Send up each chunk of data for each of the read
+ * and let the erlang code assemble it: {ok, false, Bin}
+ * (when complete it should return {ok, true, Bin}).
+ * We need to read atleast one more time to be sure if its
+ * done...
+ * 2) Or put it in a buffer here, and then let the erlang code
+ * know that it should call again (special return value)
+ * (continuous binary realloc "here").
+ *
+ * => We choose alt 1 for now.
+ *
+ * Also, we need to check if the rNumCnt has reached its max (rNum),
+ * in which case we will assume the read to be done!
+ */
+
+ cnt_inc(&descP->readByteCnt, read);
+
+ SSDBG( descP,
+ ("SOCKET", "recv_check_result -> shall we continue reading"
+ "\r\n read: %d"
+ "\r\n rNum: %d"
+ "\r\n rNumCnt: %d"
+ "\r\n", read, descP->rNum, descP->rNumCnt) );
+
+ if (descP->rNum > 0) {
+
+ descP->rNumCnt++;
+ if (descP->rNumCnt >= descP->rNum) {
+
+ descP->rNumCnt = 0;
+
+ cnt_inc(&descP->readPkgCnt, 1);
+
+ recv_update_current_reader(env, descP);
+
+ /* This transfers "ownership" of the *allocated* binary to an
+ * erlang term (no need for an explicit free).
+ */
+ data = MKBIN(env, bufP);
+
+ return esock_make_ok3(env, atom_true, data);
+
+ }
+ }
+
+ /* Yes, we *do* need to continue reading */
+
+ if ((xres = recv_init_current_reader(env,
+ descP, recvRef)) != NULL) {
+ descP->rNumCnt = 0;
+ FREE_BIN(bufP);
+ return esock_make_error_str(env, xres);
+ }
+
+ /* This transfers "ownership" of the *allocated* binary to an
+ * erlang term (no need for an explicit free).
+ */
+ data = MKBIN(env, bufP);
+
+ SSDBG( descP,
+ ("SOCKET",
+ "recv_check_result -> [%d] "
+ "we are done for now - read more\r\n", toRead) );
+
+ return esock_make_ok3(env, atom_false, data);
+
+ } else {
+
+ /* +++ We got exactly as much as we requested => We are done +++ */
+
+ cnt_inc(&descP->readPkgCnt, 1);
+ cnt_inc(&descP->readByteCnt, read);
+
+ SSDBG( descP,
+ ("SOCKET",
+ "recv_check_result -> [%d] "
+ "we got exactly what we could fit\r\n", toRead) );
+
+ recv_update_current_reader(env, descP);
+
+ /* This transfers "ownership" of the *allocated* binary to an
+ * erlang term (no need for an explicit free).
+ */
+ data = MKBIN(env, bufP);
+
+ return esock_make_ok3(env, atom_true, data);
+
+ }
+
+ } else if (read < 0) {
+
+ /* +++ Error handling +++ */
+
+ FREE_BIN(bufP);
+
+ if (saveErrno == ECONNRESET) {
+
+ res = esock_make_error(env, atom_closed);
+
+ /* +++ Oups - closed +++ */
+
+ SSDBG( descP, ("SOCKET",
+ "recv_check_result -> [%d] closed\r\n", toRead) );
+
+ /* <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>
+ */
+
+ descP->closeLocal = FALSE;
+ descP->state = SOCKET_STATE_CLOSING;
+
+ recv_error_current_reader(env, descP, sockRef, res);
+
+ if ((sres = esock_select_stop(env, descP->sock, descP)) < 0) {
+ esock_warning_msg("Failed stop select (closed) "
+ "for current reader (%T): %d\r\n",
+ recvRef, sres);
+ }
+
+ return res;
+
+ } else if ((saveErrno == ERRNO_BLOCK) ||
+ (saveErrno == EAGAIN)) {
+
+ SSDBG( descP, ("SOCKET",
+ "recv_check_result -> [%d] eagain\r\n", toRead) );
+
+ descP->rNumCnt = 0;
+ if ((xres = recv_init_current_reader(env, descP, recvRef)) != NULL)
+ return esock_make_error_str(env, xres);
+
+ SSDBG( descP, ("SOCKET", "recv_check_result -> SELECT for more\r\n") );
+
+ if ((sres = esock_select_read(env, descP->sock, descP,
+ NULL, recvRef)) < 0) {
+ res = esock_make_error(env,
+ MKT2(env,
+ esock_atom_select_failed,
+ MKI(env, sres)));
+ } else {
+ res = esock_make_error(env, esock_atom_eagain);
+ }
+
+ return res;
+ } else {
+ ERL_NIF_TERM res = esock_make_error_errno(env, saveErrno);
+
+ SSDBG( descP, ("SOCKET", "recv_check_result -> [%d] errno: %d\r\n",
+ toRead, saveErrno) );
+
+ recv_error_current_reader(env, descP, sockRef, res);
+
+ return res;
+ }
+
+ } else {
+
+ /* +++ We did not fill the buffer +++ */
+
+ SSDBG( descP,
+ ("SOCKET",
+ "recv_check_result -> [%d] "
+ "did not fill the buffer (%d of %d)\r\n",
+ toRead, read, bufP->size) );
+
+ if (toRead == 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_result -> [%d] split buffer\r\n", toRead) );
+
+ descP->rNumCnt = 0;
+ cnt_inc(&descP->readPkgCnt, 1);
+ cnt_inc(&descP->readByteCnt, read);
+
+ recv_update_current_reader(env, descP);
+
+ /* 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_result -> [%d] done\r\n", toRead) );
+
+ return esock_make_ok3(env, atom_true, data);
+
+ } else {
+
+ /* +++ We got only a part of what was expected +++
+ * +++ => select for more more later and +++
+ * +++ deliver what we got. +++ */
+
+ SSDBG( descP, ("SOCKET", "recv_check_result -> [%d] "
+ "only part of message - expect more\r\n", toRead) );
+
+ if ((xres = recv_init_current_reader(env, descP, recvRef)) != NULL) {
+ FREE_BIN(bufP);
+ return esock_make_error_str(env, xres);
+ }
+
+ data = MKBIN(env, bufP);
+ data = MKSBIN(env, data, 0, read);
+
+ cnt_inc(&descP->readByteCnt, read);
+
+ /* SELECT for more data */
+
+ if ((sres = esock_select_read(env, descP->sock, descP,
+ NULL, recvRef)) < 0) {
+ /* Result: {error, Reason}
+ * Reason: {select_failed, sres, data}
+ */
+ res = esock_make_error(env,
+ MKT3(env,
+ esock_atom_select_failed,
+ MKI(env, sres),
+ data));
+ } else {
+ res = esock_make_ok3(env, atom_false, data);
+ }
+
+ /* This transfers "ownership" of the *allocated* binary to an
+ * erlang term (no need for an explicit free).
+ */
+ return res;
+ }
+ }
+}
+
+
+/* The recvfrom 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 recvfrom_check_result(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int read,
+ int saveErrno,
+ ErlNifBinary* bufP,
+ SocketAddress* fromAddrP,
+ unsigned int fromAddrLen,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef)
+{
+ char* xres;
+ int sres;
+ ERL_NIF_TERM data, res;
+
+ SSDBG( descP,
+ ("SOCKET", "recvfrom_check_result -> entry with"
+ "\r\n read: %d"
+ "\r\n saveErrno: %d"
+ "\r\n recvRef: %T"
+ "\r\n", read, saveErrno, recvRef) );
+
+
+ /* There is a special case: If the provided 'to read' value is
+ * zero (0). That means that we reads as much as we can, using
+ * the default read buffer size.
+ */
+
+ if (read < 0) {
+
+ /* +++ Error handling +++ */
+
+ if (saveErrno == ECONNRESET) {
+ res = esock_make_error(env, atom_closed);
+
+ /* +++ Oups - closed +++ */
+
+ SSDBG( descP, ("SOCKET", "recvfrom_check_result -> closed\r\n") );
+
+ /* <KOLLA>
+ * IF THE CURRENT PROCESS IS *NOT* THE CONTROLLING
+ * PROCESS, WE NEED TO INFORM IT!!!
+ *
+ * ALL WAITING PROCESSES MUST ALSO GET THE ERROR!!
+ *
+ * </KOLLA>
+ */
+
+ descP->closeLocal = FALSE;
+ descP->state = SOCKET_STATE_CLOSING;
+
+ recv_error_current_reader(env, descP, sockRef, res);
+
+ if ((sres = esock_select_stop(env, descP->sock, descP)) < 0) {
+ esock_warning_msg("Failed stop select (closed) "
+ "for current reader (%T): %d\r\n",
+ recvRef, sres);
+ }
+
+ FREE_BIN(bufP);
+
+ return res;
+
+ } else if ((saveErrno == ERRNO_BLOCK) ||
+ (saveErrno == EAGAIN)) {
+
+ SSDBG( descP, ("SOCKET", "recvfrom_check_result -> eagain\r\n") );
+
+ FREE_BIN(bufP);
+
+ if ((xres = recv_init_current_reader(env, descP, recvRef)) != NULL)
+ return esock_make_error_str(env, xres);
+
+ if ((sres = esock_select_read(env, descP->sock, descP,
+ NULL, recvRef)) < 0) {
+ res = esock_make_error(env,
+ MKT2(env,
+ esock_atom_select_failed,
+ MKI(env, sres)));
+ } else {
+ res = esock_make_error(env, esock_atom_eagain);
+ }
+
+ return res;
+ } else {
+
+ res = esock_make_error_errno(env, saveErrno);
+
+ SSDBG( descP,
+ ("SOCKET",
+ "recvfrom_check_result -> errno: %d\r\n", saveErrno) );
+
+ recv_error_current_reader(env, descP, sockRef, res);
+
+ FREE_BIN(bufP);
+
+ return res;
+ }
+
+ } else {
+
+ /* +++ We sucessfully 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);
+ }
+
+ recv_update_current_reader(env, descP);
+
+ return esock_make_ok2(env, MKT2(env, eSockAddr, data));
+
+ }
+}
+
+
+
+/* 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,
+ SocketDescriptor* descP,
+ int read,
+ int saveErrno,
+ struct msghdr* msgHdrP,
+ ErlNifBinary* dataBufP,
+ ErlNifBinary* ctrlBufP,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef)
+{
+ int sres;
+ ERL_NIF_TERM res;
+
+ SSDBG( descP,
+ ("SOCKET", "recvmsg_check_result -> entry with"
+ "\r\n read: %d"
+ "\r\n saveErrno: %d"
+ "\r\n recvRef: %T"
+ "\r\n", read, 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)) {
+
+ /*
+ * 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 from a stream socket!
+ */
+
+
+ FREE_BIN(dataBufP); FREE_BIN(ctrlBufP);
+
+ return esock_make_error(env, atom_closed);
+
+ }
+
+
+ /* There is a special case: If the provided 'to read' value is
+ * zero (0). That means that we reads as much as we can, using
+ * the default read buffer size.
+ */
+
+ if (read < 0) {
+
+ /* +++ Error handling +++ */
+
+ if (saveErrno == ECONNRESET) {
+
+ /* +++ Oups - closed +++ */
+
+ SSDBG( descP, ("SOCKET", "recvmsg_check_result -> closed\r\n") );
+
+ /* <KOLLA>
+ * IF THE CURRENT PROCESS IS *NOT* THE CONTROLLING
+ * PROCESS, WE NEED TO INFORM IT!!!
+ *
+ * ALL WAITING PROCESSES MUST ALSO GET THE ERROR!!
+ *
+ * </KOLLA>
+ */
+
+ res = esock_make_error(env, atom_closed);
+ descP->closeLocal = FALSE;
+ descP->state = SOCKET_STATE_CLOSING;
+
+ recv_error_current_reader(env, descP, sockRef, res);
+
+ if ((sres = esock_select_stop(env, descP->sock, descP)) < 0) {
+ esock_warning_msg("Failed stop select (closed) "
+ "for current reader (%T): %d\r\n",
+ recvRef, sres);
+ }
+
+ FREE_BIN(dataBufP); FREE_BIN(ctrlBufP);
+
+ return res;;
+
+ } else if ((saveErrno == ERRNO_BLOCK) ||
+ (saveErrno == EAGAIN)) {
+ char* xres;
+
+ SSDBG( descP, ("SOCKET", "recvmsg_check_result -> eagain\r\n") );
+
+ FREE_BIN(dataBufP); FREE_BIN(ctrlBufP);
+
+ if ((xres = recv_init_current_reader(env, descP, recvRef)) != NULL)
+ return esock_make_error_str(env, xres);
+
+ if ((sres = esock_select_read(env, descP->sock, descP,
+ NULL, recvRef)) < 0) {
+ res = esock_make_error(env,
+ MKT2(env,
+ esock_atom_select_failed,
+ MKI(env, sres)));
+ } else {
+ res = esock_make_error(env, esock_atom_eagain);
+ }
+
+ return res;
+
+ } else {
+
+ res = esock_make_error_errno(env, saveErrno);
+
+ SSDBG( descP,
+ ("SOCKET",
+ "recvmsg_check_result -> errno: %d\r\n", saveErrno) );
+
+ recv_error_current_reader(env, descP, sockRef, res);
+
+ FREE_BIN(dataBufP); FREE_BIN(ctrlBufP);
+
+ return res;
+ }
+
+ } else {
+
+ /* +++ We sucessfully got a message - time to encode it +++ */
+
+ ERL_NIF_TERM eMsgHdr;
+ char* xres;
+
+ /*
+ * <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>
+ */
+
+ if ((xres = encode_msghdr(env, descP,
+ read, msgHdrP, dataBufP, ctrlBufP,
+ &eMsgHdr)) != NULL) {
+
+ SSDBG( descP,
+ ("SOCKET",
+ "recvmsg_check_result -> "
+ "(msghdr) encode failed: %s\r\n", xres) );
+
+ recv_update_current_reader(env, descP);
+
+ FREE_BIN(dataBufP); FREE_BIN(ctrlBufP);
+
+ return esock_make_error_str(env, xres);
+ } else {
+
+ SSDBG( descP,
+ ("SOCKET",
+ "recvmsg_check_result -> "
+ "(msghdr) encode ok: %T\r\n", eMsgHdr) );
+
+ recv_update_current_reader(env, descP);
+
+ return esock_make_ok2(env, eMsgHdr);
+ }
+
+ }
+}
+
+
+
+
+/* +++ encode_msghdr +++
+ *
+ * Encode a msghdr (recvmsg). In erlang its represented as
+ * a map, which has a specific set of attributes:
+ *
+ * addr (source address) - sockaddr()
+ * iov - [binary()]
+ * ctrl - [cmsghdr()]
+ * flags - msghdr_flags()
+ */
+
+extern
+char* encode_msghdr(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int read,
+ struct msghdr* msgHdrP,
+ ErlNifBinary* dataBufP,
+ ErlNifBinary* ctrlBufP,
+ ERL_NIF_TERM* eSockAddr)
+{
+ char* xres;
+ ERL_NIF_TERM addr, iov, ctrl, flags;
+
+ SSDBG( descP,
+ ("SOCKET", "encode_msghdr -> entry with"
+ "\r\n read: %d"
+ "\r\n", read) );
+
+ /* The address is not used if we are connected,
+ * so check (length = 0) before we try to encodel
+ */
+ if (msgHdrP->msg_namelen != 0) {
+ if ((xres = esock_encode_sockaddr(env,
+ (SocketAddress*) msgHdrP->msg_name,
+ msgHdrP->msg_namelen,
+ &addr)) != NULL)
+ return xres;
+ } else {
+ addr = esock_atom_undefined;
+ }
+
+ SSDBG( descP, ("SOCKET", "encode_msghdr -> try encode iov\r\n") );
+ if ((xres = esock_encode_iov(env,
+ read,
+ msgHdrP->msg_iov,
+ msgHdrP->msg_iovlen,
+ dataBufP,
+ &iov)) != NULL)
+ return xres;
+
+ SSDBG( descP, ("SOCKET", "encode_msghdr -> try encode cmsghdrs\r\n") );
+ if ((xres = encode_cmsghdrs(env, descP, ctrlBufP, msgHdrP, &ctrl)) != NULL)
+ return xres;
+
+ SSDBG( descP, ("SOCKET", "encode_msghdr -> try encode flags\r\n") );
+ if ((xres = encode_msghdr_flags(env, descP, msgHdrP->msg_flags, &flags)) != NULL)
+ return xres;
+
+ SSDBG( descP,
+ ("SOCKET", "encode_msghdr -> components encoded:"
+ "\r\n addr: %T"
+ "\r\n iov: %T"
+ "\r\n ctrl: %T"
+ "\r\n flags: %T"
+ "\r\n", addr, iov, ctrl, flags) );
+ {
+ ERL_NIF_TERM keys[] = {esock_atom_addr,
+ esock_atom_iov,
+ esock_atom_ctrl,
+ esock_atom_flags};
+ ERL_NIF_TERM vals[] = {addr, iov, ctrl, flags};
+ unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM);
+ unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM);
+ ERL_NIF_TERM tmp;
+
+ ESOCK_ASSERT( (numKeys == numVals) );
+
+ SSDBG( descP, ("SOCKET", "encode_msghdr -> create msghdr map\r\n") );
+ if (!MKMA(env, keys, vals, numKeys, &tmp))
+ return ESOCK_STR_EINVAL;
+
+ SSDBG( descP, ("SOCKET", "encode_msghdr -> msghdr: "
+ "\r\n %T"
+ "\r\n", tmp) );
+
+ *eSockAddr = tmp;
+ }
+
+ SSDBG( descP, ("SOCKET", "encode_msghdr -> done\r\n") );
+
+ return NULL;
+}
+
+
+
+
+/* +++ encode_cmsghdrs +++
+ *
+ * Encode a list of cmsghdr(). 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.
+ */
+
+extern
+char* encode_cmsghdrs(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ErlNifBinary* cmsgBinP,
+ struct msghdr* msgHdrP,
+ ERL_NIF_TERM* eCMsgHdr)
+{
+ 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_cmsghdrs -> entry\r\n") );
+
+ for (currentP = firstP;
+ currentP != NULL;
+ currentP = CMSG_NXTHDR(msgHdrP, currentP)) {
+
+ SSDBG( descP,
+ ("SOCKET", "encode_cmsghdrs -> process cmsg header when"
+ "\r\n TArray Size: %d"
+ "\r\n", 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.
+ */
+ TARRAY_DELETE(cmsghdrs);
+ return ESOCK_STR_EINVAL;
+ } else {
+ ERL_NIF_TERM level, type, data;
+ unsigned char* dataP = (unsigned char*) CMSG_DATA(currentP);
+ size_t dataPos = dataP - cmsgBinP->data;
+ size_t dataLen = currentP->cmsg_len - (CHARP(currentP)-CHARP(dataP));
+
+ SSDBG( descP,
+ ("SOCKET", "encode_cmsghdrs -> cmsg header data: "
+ "\r\n dataPos: %d"
+ "\r\n dataLen: %d"
+ "\r\n", dataPos, dataLen) );
+
+ /* We can't give up just because its an unknown protocol,
+ * so if its a protocol we don't know, we return its integer
+ * value and leave it to the user.
+ */
+ if (encode_cmsghdr_level(env, currentP->cmsg_level, &level) != NULL)
+ level = MKI(env, currentP->cmsg_level);
+
+ if (encode_cmsghdr_type(env,
+ currentP->cmsg_level, currentP->cmsg_type,
+ &type) != NULL)
+ type = MKI(env, currentP->cmsg_type);
+
+ if (encode_cmsghdr_data(env, ctrlBuf,
+ currentP->cmsg_level,
+ currentP->cmsg_type,
+ dataP, dataPos, dataLen,
+ &data) != NULL)
+ data = MKSBIN(env, ctrlBuf, dataPos, dataLen);
+
+ SSDBG( descP,
+ ("SOCKET", "encode_cmsghdrs -> "
+ "\r\n level: %T"
+ "\r\n type: %T"
+ "\r\n data: %T"
+ "\r\n", level, type, data) );
+
+ /* And finally create the 'cmsghdr' map -
+ * and if successfull add it to the tarray.
+ */
+ {
+ ERL_NIF_TERM keys[] = {esock_atom_level,
+ esock_atom_type,
+ esock_atom_data};
+ ERL_NIF_TERM vals[] = {level, type, data};
+ unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM);
+ unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM);
+ ERL_NIF_TERM cmsgHdr;
+
+ /* Guard agains cut-and-paste errors */
+ ESOCK_ASSERT( (numKeys == numVals) );
+
+ if (!MKMA(env, keys, vals, numKeys, &cmsgHdr)) {
+ TARRAY_DELETE(cmsghdrs);
+ return ESOCK_STR_EINVAL;
+ }
+
+ /* And finally add it to the list... */
+ TARRAY_ADD(cmsghdrs, cmsgHdr);
+ }
+ }
+ }
+
+ SSDBG( descP,
+ ("SOCKET", "encode_cmsghdrs -> cmsg headers processed when"
+ "\r\n TArray Size: %d"
+ "\r\n", TARRAY_SZ(cmsghdrs)) );
+
+ /* The tarray is populated - convert it to a list */
+ TARRAY_TOLIST(cmsghdrs, env, eCMsgHdr);
+
+ return NULL;
+}
+
+
+
+/* +++ decode_cmsghdrs +++
+ *
+ * Decode a list of cmsghdr(). There can be 0 or more cmsghdr "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.
+ *
+ */
+
+extern
+char* decode_cmsghdrs(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eCMsgHdr,
+ 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;
+ char* xres;
+
+ SSDBG( descP, ("SOCKET", "decode_cmsghdrs -> entry with"
+ "\r\n cmsgHdrBufP: 0x%lX"
+ "\r\n cmsgHdrBufLen: %d"
+ "\r\n", cmsgHdrBufP, cmsgHdrBufLen) );
+
+ if (IS_LIST(env, eCMsgHdr) && GET_LIST_LEN(env, eCMsgHdr, &len)) {
+
+ SSDBG( descP, ("SOCKET", "decode_cmsghdrs -> list length: %d\r\n", len) );
+
+ for (i = 0, list = eCMsgHdr, rem = cmsgHdrBufLen, bufP = cmsgHdrBufP;
+ i < len; i++) {
+
+ SSDBG( descP, ("SOCKET", "decode_cmsghdrs -> process elem %d:"
+ "\r\n (buffer) rem: %u"
+ "\r\n (buffer) totUsed: %u"
+ "\r\n", i, rem, totUsed) );
+
+ /* Extract the (current) head of the (cmsg hdr) list */
+ if (!GET_LIST_ELEM(env, list, &elem, &tail))
+ return ESOCK_STR_EINVAL;
+
+ used = 0; // Just in case...
+ if ((xres = decode_cmsghdr(env, descP, elem, bufP, rem, &used)) != NULL)
+ return xres;
+
+ bufP = CHARP( ULONG(bufP) + used );
+ rem = SZT( rem - used );
+ list = tail;
+ totUsed += used;
+
+ }
+
+ SSDBG( descP, ("SOCKET",
+ "decode_cmsghdrs -> all %d ctrl headers processed\r\n",
+ len) );
+
+ xres = NULL;
+ } else {
+ xres = ESOCK_STR_EINVAL;
+ }
+
+ *cmsgHdrBufUsed = totUsed;
+
+ SSDBG( descP, ("SOCKET", "decode_cmsghdrs -> done with %s when"
+ "\r\n totUsed = %u\r\n",
+ ((xres != NULL) ? xres : "NULL"), totUsed) );
+
+ return xres;
+}
+
+
+/* +++ decode_cmsghdr +++
+ *
+ * Decode one cmsghdr(). 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 eCMsgHdr should be a map with three fields:
+ *
+ * level :: cmsghdr_level() (socket | protocol() | integer())
+ * type :: cmsghdr_type() (atom() | integer())
+ * What values are valid depend on the level
+ * data :: cmsghdr_data() (term() | binary())
+ * The type of the data depends on
+ * level and type, but can be a binary,
+ * which means that the data is already coded.
+ */
+extern
+char* decode_cmsghdr(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ERL_NIF_TERM eCMsgHdr,
+ char* bufP,
+ size_t rem,
+ size_t* used)
+{
+ SSDBG( descP, ("SOCKET", "decode_cmsghdr -> entry with"
+ "\r\n eCMsgHdr: %T"
+ "\r\n", eCMsgHdr) );
+
+ if (IS_MAP(env, eCMsgHdr)) {
+ ERL_NIF_TERM eLevel, eType, eData;
+ int level, type;
+ char* xres;
+
+ /* First extract all three attributes (as terms) */
+
+ if (!GET_MAP_VAL(env, eCMsgHdr, esock_atom_level, &eLevel))
+ return ESOCK_STR_EINVAL;
+
+ SSDBG( descP, ("SOCKET", "decode_cmsghdr -> eLevel: %T"
+ "\r\n", eLevel) );
+
+ if (!GET_MAP_VAL(env, eCMsgHdr, esock_atom_type, &eType))
+ return ESOCK_STR_EINVAL;
+
+ SSDBG( descP, ("SOCKET", "decode_cmsghdr -> eType: %T"
+ "\r\n", eType) );
+
+ if (!GET_MAP_VAL(env, eCMsgHdr, esock_atom_data, &eData))
+ return ESOCK_STR_EINVAL;
+
+ SSDBG( descP, ("SOCKET", "decode_cmsghdr -> eData: %T"
+ "\r\n", eData) );
+
+ /* Second, decode level */
+ if ((xres = decode_cmsghdr_level(env, eLevel, &level)) != NULL)
+ return xres;
+
+ SSDBG( descP, ("SOCKET", "decode_cmsghdr -> level: %d\r\n", level) );
+
+ /* third, decode type */
+ if ((xres = decode_cmsghdr_type(env, level, eType, &type)) != NULL)
+ return xres;
+
+ SSDBG( descP, ("SOCKET", "decode_cmsghdr -> type: %d\r\n", type) );
+
+ /* And finally data
+ * If its a binary, we are done. Otherwise, we need to check
+ * level and type to know what kind of data to expect.
+ */
+
+ return decode_cmsghdr_data(env, descP, bufP, rem, level, type, eData, used);
+
+ } else {
+ *used = 0;
+ return ESOCK_STR_EINVAL;
+ }
+
+ return NULL;
+}
+
+
+/* *** decode_cmsghdr_data ***
+ *
+ * For all combinations of level and type we accept a binary as data,
+ * so we begin by testing for that. If its not a binary, then we check
+ * level (ip) and type (tos or ttl), in which case the data *must* be
+ * an integer and ip_tos() respectively.
+ */
+static
+char* decode_cmsghdr_data(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ char* bufP,
+ size_t rem,
+ int level,
+ int type,
+ ERL_NIF_TERM eData,
+ size_t* used)
+{
+ char* xres;
+
+ SSDBG( descP, ("SOCKET", "decode_cmsghdr_data -> entry with"
+ "\r\n eData: %T"
+ "\r\n", eData) );
+
+ if (IS_BIN(env, eData)) {
+ ErlNifBinary bin;
+
+ if (GET_BIN(env, eData, &bin)) {
+ SSDBG( descP, ("SOCKET", "decode_cmsghdr_data -> "
+ "do final decode with binary\r\n") );
+ return decode_cmsghdr_final(descP, bufP, rem, level, type,
+ (char*) bin.data, bin.size,
+ used);
+ } else {
+ *used = 0;
+ xres = ESOCK_STR_EINVAL;
+ }
+ } else {
+
+ /* Its *not* a binary so we need to look at what level and type
+ * we have and treat them individually.
+ */
+
+ switch (level) {
+#if defined(SOL_IP)
+ case SOL_IP:
+#else
+ case IPPROTO_IP:
+#endif
+ switch (type) {
+#if defined(IP_TOS)
+ case IP_TOS:
+ {
+ int data;
+ if (decode_ip_tos(env, eData, &data)) {
+ SSDBG( descP, ("SOCKET", "decode_cmsghdr_data -> "
+ "do final decode with tos\r\n") );
+ return decode_cmsghdr_final(descP, bufP, rem, level, type,
+ (char*) &data,
+ sizeof(data),
+ used);
+ } else {
+ *used = 0;
+ xres = ESOCK_STR_EINVAL;
+ }
+ }
+ break;
+#endif
+
+#if defined(IP_TTL)
+ case IP_TTL:
+ {
+ int data;
+ if (GET_INT(env, eData, &data)) {
+ SSDBG( descP, ("SOCKET", "decode_cmsghdr_data -> "
+ "do final decode with ttl\r\n") );
+ return decode_cmsghdr_final(descP, bufP, rem, level, type,
+ (char*) &data,
+ sizeof(data),
+ used);
+ } else {
+ *used = 0;
+ xres = ESOCK_STR_EINVAL;
+ }
+ }
+ break;
+#endif
+
+ }
+ break;
+
+ default:
+ *used = 0;
+ xres = ESOCK_STR_EINVAL;
+ break;
+ }
+
+ }
+
+ return xres;
+}
+
+
+/* *** decode_cmsghdr_final ***
+ *
+ * This does the final create of the cmsghdr (including the data copy).
+ */
+static
+char* decode_cmsghdr_final(SocketDescriptor* descP,
+ char* bufP,
+ size_t rem,
+ int level,
+ int type,
+ char* data,
+ int sz,
+ size_t* used)
+{
+ int len = CMSG_LEN(sz);
+ int space = CMSG_SPACE(sz);
+
+ SSDBG( descP, ("SOCKET", "decode_cmsghdr_data -> entry when"
+ "\r\n level: %d"
+ "\r\n type: %d"
+ "\r\n sz: %d => %d, %d"
+ "\r\n", level, type, sz, len, space) );
+
+ if (rem >= space) {
+ struct cmsghdr* cmsgP = (struct cmsghdr*) bufP;
+
+ /* The header */
+ cmsgP->cmsg_len = len;
+ cmsgP->cmsg_level = level;
+ cmsgP->cmsg_type = type;
+
+ sys_memcpy(CMSG_DATA(cmsgP), data, sz);
+ *used = space;
+ } else {
+ *used = 0;
+ return ESOCK_STR_EINVAL;
+ }
+
+ SSDBG( descP, ("SOCKET", "decode_cmsghdr_final -> done\r\n") );
+
+ return NULL;
+}
+
+
+/* +++ encode_cmsghdr_level +++
+ *
+ * Encode the level part of the cmsghdr().
+ *
+ */
+
+static
+char* encode_cmsghdr_level(ErlNifEnv* env,
+ int level,
+ ERL_NIF_TERM* eLevel)
+{
+ char* xres;
+
+ switch (level) {
+ case SOL_SOCKET:
+ *eLevel = esock_atom_socket;
+ xres = NULL;
+ break;
+
+#if defined(SOL_IP)
+ case SOL_IP:
+#else
+ case IPPROTO_IP:
+#endif
+ *eLevel = esock_atom_ip;
+ xres = NULL;
+ break;
+
+#if defined(HAVE_IPV6)
+#if defined(SOL_IPV6)
+ case SOL_IPV6:
+#else
+ case IPPROTO_IPV6:
+#endif
+ *eLevel = esock_atom_ip;
+ xres = NULL;
+ break;
+#endif
+
+ case IPPROTO_UDP:
+ *eLevel = esock_atom_udp;
+ xres = NULL;
+ break;
+
+ default:
+ *eLevel = MKI(env, level);
+ xres = NULL;
+ break;
+ }
+
+ return xres;
+}
+
+
+
+/* +++ decode_cmsghdr_level +++
+ *
+ * Decode the level part of the cmsghdr().
+ *
+ */
+
+static
+char* decode_cmsghdr_level(ErlNifEnv* env,
+ ERL_NIF_TERM eLevel,
+ int* level)
+{
+ char* xres = NULL;
+
+ if (IS_ATOM(env, eLevel)) {
+
+ if (COMPARE(eLevel, esock_atom_socket) == 0) {
+ *level = SOL_SOCKET;
+ xres = NULL;
+ } else if (COMPARE(eLevel, esock_atom_ip) == 0) {
+#if defined(SOL_IP)
+ *level = SOL_IP;
+#else
+ *level = IPPROTO_IP;
+#endif
+ xres = NULL;
+#if defined(HAVE_IPV6)
+ } else if (COMPARE(eLevel, esock_atom_ipv6) == 0) {
+#if defined(SOL_IPV6)
+ *level = SOL_IPV6;
+#else
+ *level = IPPROTO_IPV6;
+#endif
+ xres = NULL;
+#endif
+ } else if (COMPARE(eLevel, esock_atom_udp) == 0) {
+ *level = IPPROTO_UDP;
+ xres = NULL;
+ } else {
+ *level = -1;
+ xres = ESOCK_STR_EINVAL;
+ }
+ } else if (IS_NUM(env, eLevel)) {
+ if (!GET_INT(env, eLevel, level))
+ xres = ESOCK_STR_EINVAL;
+ } else {
+ *level = -1;
+ xres = ESOCK_STR_EINVAL;
+ }
+
+ return xres;
+}
+
+
+
+/* +++ encode_cmsghdr_type +++
+ *
+ * Encode the type part of the cmsghdr().
+ *
+ */
+
+static
+char* encode_cmsghdr_type(ErlNifEnv* env,
+ int level,
+ int type,
+ ERL_NIF_TERM* eType)
+{
+ char* xres = NULL;
+
+ switch (level) {
+ case SOL_SOCKET:
+ switch (type) {
+#if defined(SO_TIMESTAMP)
+ case SO_TIMESTAMP:
+ *eType = esock_atom_timestamp;
+ break;
+#endif
+
+#if defined(SCM_RIGHTS)
+ case SCM_RIGHTS:
+ *eType = esock_atom_rights;
+ break;
+#endif
+
+#if defined(SCM_CREDENTIALS)
+ case SCM_CREDENTIALS:
+ *eType = esock_atom_credentials;
+ break;
+#endif
+
+ default:
+ xres = ESOCK_STR_EINVAL;
+ break;
+ }
+ break;
+
+#if defined(SOL_IP)
+ case SOL_IP:
+#else
+ case IPPROTO_IP:
+#endif
+ switch (type) {
+#if defined(IP_TOS)
+ case IP_TOS:
+ *eType = esock_atom_tos;
+ break;
+#endif
+
+#if defined(IP_TTL)
+ case IP_TTL:
+ *eType = esock_atom_ttl;
+ break;
+#endif
+
+#if defined(IP_PKTINFO)
+ case IP_PKTINFO:
+ *eType = esock_atom_pktinfo;
+ break;
+#endif
+
+#if defined(IP_ORIGDSTADDR)
+ case IP_ORIGDSTADDR:
+ *eType = esock_atom_origdstaddr;
+ break;
+#endif
+
+ default:
+ xres = ESOCK_STR_EINVAL;
+ break;
+ }
+ break;
+
+#if defined(HAVE_IPV6)
+#if defined(SOL_IPV6)
+ case SOL_IPV6:
+#else
+ case IPPROTO_IPV6:
+#endif
+ switch (type) {
+#if defined(IPV6_PKTINFO)
+ case IPV6_PKTINFO:
+ *eType = esock_atom_pktinfo;
+ break;
+#endif
+
+ default:
+ xres = ESOCK_STR_EINVAL;
+ break;
+ }
+ break;
+#endif
+
+ case IPPROTO_TCP:
+ switch (type) {
+ default:
+ xres = ESOCK_STR_EINVAL;
+ break;
+ }
+ break;
+
+ case IPPROTO_UDP:
+ switch (type) {
+ default:
+ xres = ESOCK_STR_EINVAL;
+ break;
+ }
+ break;
+
+#if defined(HAVE_SCTP)
+ case IPPROTO_SCTP:
+ switch (type) {
+ default:
+ xres = ESOCK_STR_EINVAL;
+ break;
+ }
+ break;
+#endif
+
+ default:
+ xres = ESOCK_STR_EINVAL;
+ break;
+ }
+
+ return xres;
+}
+
+
+
+/* +++ decode_cmsghdr_type +++
+ *
+ * Decode the type part of the cmsghdr().
+ *
+ */
+
+static
+char* decode_cmsghdr_type(ErlNifEnv* env,
+ int level,
+ ERL_NIF_TERM eType,
+ int* type)
+{
+ char* xres = NULL;
+
+ switch (level) {
+ case SOL_SOCKET:
+ if (IS_NUM(env, eType)) {
+ if (!GET_INT(env, eType, type)) {
+ *type = -1;
+ xres = ESOCK_STR_EINVAL;
+ }
+ } else {
+ *type = -1;
+ xres = ESOCK_STR_EINVAL;
+ }
+ break;
+
+
+#if defined(SOL_IP)
+ case SOL_IP:
+#else
+ case IPPROTO_IP:
+#endif
+ if (IS_ATOM(env, eType)) {
+ if (COMPARE(eType, esock_atom_tos) == 0) {
+#if defined(IP_TOS)
+ *type = IP_TOS;
+#else
+ xres = ESOCK_STR_EINVAL;
+#endif
+ } else if (COMPARE(eType, esock_atom_ttl) == 0) {
+#if defined(IP_TTL)
+ *type = IP_TTL;
+#else
+ xres = ESOCK_STR_EINVAL;
+#endif
+ } else {
+ xres = ESOCK_STR_EINVAL;
+ }
+ } else if (IS_NUM(env, eType)) {
+ if (!GET_INT(env, eType, type)) {
+ *type = -1;
+ xres = ESOCK_STR_EINVAL;
+ }
+ } else {
+ *type = -1;
+ xres = ESOCK_STR_EINVAL;
+ }
+ break;
+
+#if defined(HAVE_IPV6)
+#if defined(SOL_IPV6)
+ case SOL_IPV6:
+#else
+ case IPPROTO_IPV6:
+#endif
+ if (IS_NUM(env, eType)) {
+ if (!GET_INT(env, eType, type)) {
+ *type = -1;
+ xres = ESOCK_STR_EINVAL;
+ }
+ } else {
+ *type = -1;
+ xres = ESOCK_STR_EINVAL;
+ }
+ break;
+#endif
+
+ case IPPROTO_UDP:
+ if (IS_NUM(env, eType)) {
+ if (!GET_INT(env, eType, type)) {
+ *type = -1;
+ xres = ESOCK_STR_EINVAL;
+ }
+ } else {
+ *type = -1;
+ xres = ESOCK_STR_EINVAL;
+ }
+ break;
+
+ default:
+ *type = -1;
+ xres = ESOCK_STR_EINVAL;
+ break;
+ }
+
+ return xres;
+}
+
+
+
+/* +++ encode_cmsghdr_data +++
+ *
+ * Encode the data part of the cmsghdr().
+ *
+ */
+
+static
+char* encode_cmsghdr_data(ErlNifEnv* env,
+ ERL_NIF_TERM ctrlBuf,
+ int level,
+ int type,
+ unsigned char* dataP,
+ size_t dataPos,
+ size_t dataLen,
+ ERL_NIF_TERM* eCMsgHdrData)
+{
+ char* xres;
+
+ switch (level) {
+#if defined(SOL_SOCKET)
+ case SOL_SOCKET:
+ xres = encode_cmsghdr_data_socket(env, ctrlBuf, type,
+ dataP, dataPos, dataLen,
+ eCMsgHdrData);
+ break;
+#endif
+
+#if defined(SOL_IP)
+ case SOL_IP:
+#else
+ case IPPROTO_IP:
+#endif
+ xres = encode_cmsghdr_data_ip(env, ctrlBuf, type,
+ dataP, dataPos, dataLen,
+ eCMsgHdrData);
+ break;
+
+#if defined(HAVE_IPV6)
+#if defined(SOL_IPV6)
+ case SOL_IPV6:
+#else
+ case IPPROTO_IPV6:
+#endif
+ xres = encode_cmsghdr_data_ipv6(env, ctrlBuf, type,
+ dataP, dataPos, dataLen,
+ eCMsgHdrData);
+ break;
+#endif
+
+ /*
+ case IPPROTO_TCP:
+ xres = encode_cmsghdr_data_tcp(env, type, dataP, eCMsgHdrData);
+ break;
+ */
+
+ /*
+ case IPPROTO_UDP:
+ xres = encode_cmsghdr_data_udp(env, type, dataP, eCMsgHdrData);
+ break;
+ */
+
+ /*
+ #if defined(HAVE_SCTP)
+ case IPPROTO_SCTP:
+ xres = encode_cmsghdr_data_sctp(env, type, dataP, eCMsgHdrData);
+ break;
+ #endif
+ */
+
+ default:
+ *eCMsgHdrData = MKSBIN(env, ctrlBuf, dataPos, dataLen);
+ xres = NULL;
+ break;
+ }
+
+ return xres;
+}
+
+
+
+/* +++ encode_cmsghdr_data_socket +++
+ *
+ * Encode the data part when "protocol" = socket of the cmsghdr().
+ *
+ */
+
+static
+char* encode_cmsghdr_data_socket(ErlNifEnv* env,
+ ERL_NIF_TERM ctrlBuf,
+ int type,
+ unsigned char* dataP,
+ size_t dataPos,
+ size_t dataLen,
+ ERL_NIF_TERM* eCMsgHdrData)
+{
+ // char* xres;
+
+ switch (type) {
+#if defined(SO_TIMESTAMP)
+ case SO_TIMESTAMP:
+ {
+ struct timeval* timeP = (struct timeval*) dataP;
+
+ if (esock_encode_timeval(env, timeP, eCMsgHdrData) != NULL)
+ *eCMsgHdrData = MKSBIN(env, ctrlBuf, dataPos, dataLen);
+ }
+ break;
+#endif
+
+ default:
+ *eCMsgHdrData = MKSBIN(env, ctrlBuf, dataPos, dataLen);
+ break;
+ }
+
+ return NULL;
+}
+
+
+
+/* +++ encode_cmsghdr_data_ip +++
+ *
+ * Encode the data part when protocol = IP of the cmsghdr().
+ *
+ */
+
+static
+char* encode_cmsghdr_data_ip(ErlNifEnv* env,
+ ERL_NIF_TERM ctrlBuf,
+ int type,
+ unsigned char* dataP,
+ size_t dataPos,
+ size_t dataLen,
+ ERL_NIF_TERM* eCMsgHdrData)
+{
+ char* xres = NULL;
+
+ switch (type) {
+#if defined(IP_TOS)
+ case IP_TOS:
+ {
+ unsigned char tos = *dataP;
+ switch (IPTOS_TOS(tos)) {
+ case IPTOS_LOWDELAY:
+ *eCMsgHdrData = esock_atom_lowdelay;
+ break;
+ case IPTOS_THROUGHPUT:
+ *eCMsgHdrData = esock_atom_throughput;
+ break;
+ case IPTOS_RELIABILITY:
+ *eCMsgHdrData = esock_atom_reliability;
+ break;
+#if defined(IPTOS_MINCOST)
+ case IPTOS_MINCOST:
+ *eCMsgHdrData = esock_atom_mincost;
+ break;
+#endif
+ default:
+ *eCMsgHdrData = MKUI(env, tos);
+ break;
+ }
+ }
+ break;
+#endif
+
+#if defined(IP_TTL)
+ case IP_TTL:
+ {
+ int ttl = *((int*) dataP);
+ *eCMsgHdrData = MKI(env, ttl);
+ }
+ break;
+#endif
+
+#if defined(IP_PKTINFO)
+ case IP_PKTINFO:
+ {
+ struct in_pktinfo* pktInfoP = (struct in_pktinfo*) dataP;
+ ERL_NIF_TERM ifIndex = MKUI(env, pktInfoP->ipi_ifindex);
+ ERL_NIF_TERM specDst, addr;
+
+ if ((xres = esock_encode_ip4_address(env,
+ &pktInfoP->ipi_spec_dst,
+ &specDst)) != NULL) {
+ *eCMsgHdrData = esock_atom_undefined;
+ return xres;
+ }
+
+ if ((xres = esock_encode_ip4_address(env,
+ &pktInfoP->ipi_addr,
+ &addr)) != NULL) {
+ *eCMsgHdrData = esock_atom_undefined;
+ return xres;
+ }
+
+
+ {
+ ERL_NIF_TERM keys[] = {esock_atom_ifindex,
+ esock_atom_spec_dst,
+ esock_atom_addr};
+ ERL_NIF_TERM vals[] = {ifIndex, specDst, addr};
+ unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM);
+ unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM);
+
+ ESOCK_ASSERT( (numKeys == numVals) );
+
+ if (!MKMA(env, keys, vals, numKeys, eCMsgHdrData)) {
+ *eCMsgHdrData = esock_atom_undefined;
+ return ESOCK_STR_EINVAL;
+ }
+ }
+ }
+ break;
+#endif
+
+#if defined(IP_ORIGDSTADDR)
+ case IP_ORIGDSTADDR:
+ if ((xres = esock_encode_sockaddr_in4(env,
+ (struct sockaddr_in*) dataP,
+ dataLen,
+ eCMsgHdrData)) != NULL) {
+ *eCMsgHdrData = esock_atom_undefined;
+ return xres;
+ }
+ break;
+#endif
+
+ default:
+ *eCMsgHdrData = MKSBIN(env, ctrlBuf, dataPos, dataLen);
+ break;
+ }
+
+ return xres;
+}
+
+
+
+/* +++ encode_cmsghdr_data_ipv6 +++
+ *
+ * Encode the data part when protocol = IPv6 of the cmsghdr().
+ *
+ */
+#if defined(HAVE_IPV6)
+static
+char* encode_cmsghdr_data_ipv6(ErlNifEnv* env,
+ ERL_NIF_TERM ctrlBuf,
+ int type,
+ unsigned char* dataP,
+ size_t dataPos,
+ size_t dataLen,
+ ERL_NIF_TERM* eCMsgHdrData)
+{
+ char* xres;
+
+ switch (type) {
+#if defined(IPV6_PKTINFO)
+ case IPV6_PKTINFO:
+ {
+ struct in6_pktinfo* pktInfoP = (struct in6_pktinfo*) dataP;
+ ERL_NIF_TERM ifIndex = MKI(env, pktInfoP->ipi6_ifindex);
+ ERL_NIF_TERM addr;
+
+ if ((xres = esock_encode_ip6_address(env,
+ &pktInfoP->ipi6_addr,
+ &addr)) != NULL) {
+ *eCMsgHdrData = esock_atom_undefined;
+ return xres;
+ }
+
+ {
+ ERL_NIF_TERM keys[] = {esock_atom_addr, esock_atom_ifindex};
+ ERL_NIF_TERM vals[] = {addr, ifIndex};
+ unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM);
+ unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM);
+
+ ESOCK_ASSERT( (numKeys == numVals) );
+
+ if (!MKMA(env, keys, vals, numKeys, eCMsgHdrData)) {
+ *eCMsgHdrData = esock_atom_undefined;
+ return ESOCK_STR_EINVAL;
+ }
+ }
+ }
+ break;
+#endif
+
+ default:
+ *eCMsgHdrData = MKSBIN(env, ctrlBuf, dataPos, dataLen);
+ break;
+ }
+
+ return NULL;
+}
+#endif
+
+
+
+/* +++ encode_msghdr_flags +++
+ *
+ * Encode a list of msghdr_flag().
+ *
+ * The following flags are handled: eor | trunc | ctrunc | oob | errqueue.
+ */
+
+extern
+char* encode_msghdr_flags(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ int msgFlags,
+ ERL_NIF_TERM* flags)
+{
+ SSDBG( descP,
+ ("SOCKET", "encode_cmsghdrs_flags -> entry with"
+ "\r\n msgFlags: %d (0x%lX)"
+ "\r\n", msgFlags, msgFlags) );
+
+ if (msgFlags == 0) {
+ *flags = MKEL(env);
+ return NULL;
+ } else {
+ SocketTArray ta = TARRAY_CREATE(10); // Just to be on the safe side
+
+#if defined(MSG_EOR)
+ if ((msgFlags & MSG_EOR) == MSG_EOR)
+ TARRAY_ADD(ta, esock_atom_eor);
+#endif
+
+#if defined(MSG_TRUNC)
+ if ((msgFlags & MSG_TRUNC) == MSG_TRUNC)
+ TARRAY_ADD(ta, esock_atom_trunc);
+#endif
+
+#if defined(MSG_CTRUNC)
+ if ((msgFlags & MSG_CTRUNC) == MSG_CTRUNC)
+ TARRAY_ADD(ta, esock_atom_ctrunc);
+#endif
+
+#if defined(MSG_OOB)
+ if ((msgFlags & MSG_OOB) == MSG_OOB)
+ TARRAY_ADD(ta, esock_atom_oob);
+#endif
+
+#if defined(MSG_ERRQUEUE)
+ if ((msgFlags & MSG_ERRQUEUE) == MSG_ERRQUEUE)
+ TARRAY_ADD(ta, esock_atom_errqueue);
+#endif
+
+ SSDBG( descP,
+ ("SOCKET", "esock_encode_cmsghdrs -> flags processed when"
+ "\r\n TArray size: %d"
+ "\r\n", TARRAY_SZ(ta)) );
+
+ TARRAY_TOLIST(ta, env, flags);
+
+ return NULL;
+ }
+}
+
+
+
+
+/* +++ decode the linger value +++
+ * The (socket) linger option is provided as a two tuple:
+ *
+ * {OnOff :: boolean(), Time :: integer()}
+ *
+ */
+static
+BOOLEAN_T decode_sock_linger(ErlNifEnv* env, ERL_NIF_TERM eVal, struct linger* valP)
+{
+ const ERL_NIF_TERM* lt; // The array of the elements of the tuple
+ int sz; // The size of the tuple - should be 2
+ BOOLEAN_T onOff;
+ int secs;
+
+ if (!GET_TUPLE(env, eVal, &sz, &lt))
+ return FALSE;
+
+ if (sz != 2)
+ return FALSE;
+
+
+ /* So fas so good - now check the two elements of the tuple. */
+
+ onOff = esock_decode_bool(lt[0]);
+
+ if (!GET_INT(env, lt[1], &secs))
+ return FALSE;
+
+ valP->l_onoff = (onOff) ? 1 : 0;
+ valP->l_linger = secs;
+
+ return TRUE;
+}
+
+
+
+/* +++ decode the ip socket option TOS +++
+ * The (ip) option can be provide in two ways:
+ *
+ * atom() | integer()
+ *
+ * When its an atom it can have the values:
+ *
+ * lowdelay | throughput | reliability | mincost
+ *
+ */
+#if defined(IP_TOS)
+static
+BOOLEAN_T decode_ip_tos(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val)
+{
+ BOOLEAN_T result = FALSE;
+
+ if (IS_ATOM(env, eVal)) {
+
+ if (COMPARE(eVal, esock_atom_lowdelay) == 0) {
+ *val = IPTOS_LOWDELAY;
+ result = TRUE;
+ } else if (COMPARE(eVal, esock_atom_throughput) == 0) {
+ *val = IPTOS_THROUGHPUT;
+ result = TRUE;
+ } else if (COMPARE(eVal, esock_atom_reliability) == 0) {
+ *val = IPTOS_RELIABILITY;
+ result = TRUE;
+#if defined(IPTOS_MINCOST)
+ } else if (COMPARE(eVal, esock_atom_mincost) == 0) {
+ *val = IPTOS_MINCOST;
+ result = TRUE;
+#endif
+ } else {
+ *val = -1;
+ result = FALSE;
+ }
+
+ } else if (IS_NUM(env, eVal)) {
+
+ if (GET_INT(env, eVal, val)) {
+ result = TRUE;
+ } else {
+ *val = -1;
+ result = FALSE;
+ }
+
+ } else {
+ *val = -1;
+ result = FALSE;
+ }
+
+ return result;
+}
+#endif
+
+
+
+/* +++ decode the ip socket option MTU_DISCOVER +++
+ * The (ip) option can be provide in two ways:
+ *
+ * atom() | integer()
+ *
+ * When its an atom it can have the values:
+ *
+ * want | dont | do | probe
+ *
+ */
+#if defined(IP_MTU_DISCOVER)
+static
+char* decode_ip_pmtudisc(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val)
+{
+ char* res = NULL;
+
+ if (IS_ATOM(env, eVal)) {
+
+ if (COMPARE(eVal, atom_want) == 0) {
+ *val = IP_PMTUDISC_WANT;
+ } else 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_PROBE)
+ } else if (COMPARE(eVal, atom_probe) == 0) {
+ *val = IP_PMTUDISC_PROBE;
+#endif
+ } else {
+ *val = -1;
+ res = ESOCK_STR_EINVAL;
+ }
+
+ } else if (IS_NUM(env, eVal)) {
+
+ if (!GET_INT(env, eVal, val)) {
+ *val = -1;
+ res = ESOCK_STR_EINVAL;
+ }
+
+ } else {
+
+ *val = -1;
+ res = ESOCK_STR_EINVAL;
+
+ }
+
+ return res;
+}
+#endif
+
+
+
+/* +++ decode the ipv6 socket option MTU_DISCOVER +++
+ * The (ip) option can be provide in two ways:
+ *
+ * atom() | integer()
+ *
+ * When its an atom it can have the values:
+ *
+ * want | dont | do | probe
+ *
+ */
+#if defined(IPV6_MTU_DISCOVER)
+static
+char* decode_ipv6_pmtudisc(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val)
+{
+ char* res = NULL;
+
+ if (IS_ATOM(env, eVal)) {
+
+ if (COMPARE(eVal, atom_want) == 0) {
+ *val = IPV6_PMTUDISC_WANT;
+ } else if (COMPARE(eVal, atom_dont) == 0) {
+ *val = IPV6_PMTUDISC_DONT;
+ } else if (COMPARE(eVal, atom_do) == 0) {
+ *val = IPV6_PMTUDISC_DO;
+#if defined(IPV6_PMTUDISC_PROBE)
+ } else if (COMPARE(eVal, atom_probe) == 0) {
+ *val = IPV6_PMTUDISC_PROBE;
+#endif
+ } else {
+ *val = -1;
+ res = ESOCK_STR_EINVAL;
+ }
+
+ } else if (IS_NUM(env, eVal)) {
+
+ if (!GET_INT(env, eVal, val)) {
+ *val = -1;
+ res = ESOCK_STR_EINVAL;
+ }
+
+ } else {
+
+ *val = -1;
+ res = ESOCK_STR_EINVAL;
+
+ }
+
+ return res;
+}
+#endif
+
+
+
+/* +++ encode the ip socket option MTU_DISCOVER +++
+ * The (ip) option can be provide in two ways:
+ *
+ * atom() | integer()
+ *
+ * If its one of the "known" values, it will be an atom:
+ *
+ * want | dont | do | probe
+ *
+ */
+#if defined(IP_MTU_DISCOVER)
+static
+void encode_ip_pmtudisc(ErlNifEnv* env, int val, ERL_NIF_TERM* eVal)
+{
+ switch (val) {
+ case IP_PMTUDISC_WANT:
+ *eVal = atom_want;
+ break;
+
+ case IP_PMTUDISC_DONT:
+ *eVal = atom_dont;
+ break;
+
+ case IP_PMTUDISC_DO:
+ *eVal = atom_do;
+ break;
+
+#if defined(IP_PMTUDISC_PROBE)
+ case IP_PMTUDISC_PROBE:
+ *eVal = atom_probe;
+ break;
+#endif
+
+ default:
+ *eVal = MKI(env, val);
+ break;
+ }
+
+ return;
+}
+#endif
+
+
+
+/* +++ encode the ipv6 socket option MTU_DISCOVER +++
+ * The (ipv6) option can be provide in two ways:
+ *
+ * atom() | integer()
+ *
+ * If its one of the "known" values, it will be an atom:
+ *
+ * want | dont | do | probe
+ *
+ */
+#if defined(IPV6_MTU_DISCOVER)
+static
+void encode_ipv6_pmtudisc(ErlNifEnv* env, int val, ERL_NIF_TERM* eVal)
+{
+ switch (val) {
+ case IPV6_PMTUDISC_WANT:
+ *eVal = atom_want;
+ break;
+
+ case IPV6_PMTUDISC_DONT:
+ *eVal = atom_dont;
+ break;
+
+ case IPV6_PMTUDISC_DO:
+ *eVal = atom_do;
+ break;
+
+#if defined(IPV6_PMTUDISC_PROBE)
+ case IPV6_PMTUDISC_PROBE:
+ *eVal = atom_probe;
+ break;
+#endif
+
+ default:
+ *eVal = MKI(env, val);
+ break;
+ }
+
+ return;
+}
+#endif
+
+
+
+/* +++ decocde the native getopt option +++
+ * The option is in this case provide in the form of a two tuple:
+ *
+ * {NativeOpt, ValueSize}
+ *
+ * NativeOpt :: integer()
+ * ValueSize :: int | bool | non_neg_integer()
+ *
+ */
+static
+BOOLEAN_T decode_native_get_opt(ErlNifEnv* env, ERL_NIF_TERM eVal,
+ int* opt, Uint16* valueType, int* valueSz)
+{
+ const ERL_NIF_TERM* nativeOptT;
+ int nativeOptTSz;
+
+ /* First, get the tuple and verify its size (2) */
+
+ if (!GET_TUPLE(env, eVal, &nativeOptTSz, &nativeOptT))
+ return FALSE;
+
+ if (nativeOptTSz != 2)
+ return FALSE;
+
+ /* So far so good.
+ * First element is an integer.
+ * Second element is an atom or an integer.
+ * The only "types" that we support at the moment are:
+ *
+ * bool - Which is actually a integer
+ * (but will be *returned* as a boolean())
+ * int - Just short for integer
+ */
+
+ if (!GET_INT(env, nativeOptT[0], opt))
+ return FALSE;
+
+ if (IS_ATOM(env, nativeOptT[1])) {
+
+ if (COMPARE(nativeOptT[1], atom_int) == 0) {
+ SGDBG( ("SOCKET", "decode_native_get_opt -> int\r\n") );
+ *valueType = SOCKET_OPT_VALUE_TYPE_INT;
+ *valueSz = sizeof(int); // Just to be sure
+ } else if (COMPARE(nativeOptT[1], atom_bool) == 0) {
+ SGDBG( ("SOCKET", "decode_native_get_opt -> bool\r\n") );
+ *valueType = SOCKET_OPT_VALUE_TYPE_BOOL;
+ *valueSz = sizeof(int); // Just to be sure
+ } else {
+ return FALSE;
+ }
+ } else if (IS_NUM(env, nativeOptT[1])) {
+ if (GET_INT(env, nativeOptT[1], valueSz)) {
+ SGDBG( ("SOCKET", "decode_native_get_opt -> unspec\r\n") );
+ *valueType = SOCKET_OPT_VALUE_TYPE_UNSPEC;
+ } else {
+ return FALSE;
+ }
+ } else {
+ return FALSE;
+ }
+
+ SGDBG( ("SOCKET", "decode_native_get_opt -> done\r\n") );
+
+ return TRUE;
+}
+
+
+
+/* +++ encode the ip socket option tos +++
+ * The (ip) option can be provide as:
+ *
+ * lowdelay | throughput | reliability | mincost | integer()
+ *
+ */
+static
+ERL_NIF_TERM encode_ip_tos(ErlNifEnv* env, int val)
+{
+ ERL_NIF_TERM result;
+
+ switch (IPTOS_TOS(val)) {
+ case IPTOS_LOWDELAY:
+ result = esock_make_ok2(env, esock_atom_lowdelay);
+ break;
+
+ case IPTOS_THROUGHPUT:
+ result = esock_make_ok2(env, esock_atom_throughput);
+ break;
+
+ case IPTOS_RELIABILITY:
+ result = esock_make_ok2(env, esock_atom_reliability);
+ break;
+
+#if defined(IPTOS_MINCOST)
+ case IPTOS_MINCOST:
+ result = esock_make_ok2(env, esock_atom_mincost);
+ break;
+#endif
+
+ default:
+ result = esock_make_ok2(env, MKI(env, val));
+ break;
+ }
+
+ return result;
+}
+
+
+
+
+
+/* *** alloc_descriptor ***
+ * Allocate and perform basic initialization of a socket descriptor.
+ *
+ */
+static
+SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event)
+{
+ SocketDescriptor* descP;
+
+ if ((descP = enif_alloc_resource(sockets, sizeof(SocketDescriptor))) != NULL) {
+ char buf[64]; /* Buffer used for building the mutex name */
+
+ // This needs to be released when the socket is closed!
+ descP->env = enif_alloc_env();
+
+ sprintf(buf, "socket[w,%d]", sock);
+ descP->writeMtx = MCREATE(buf);
+ descP->currentWriterP = NULL; // currentWriter not used
+ descP->writersQ.first = NULL;
+ descP->writersQ.last = NULL;
+ descP->isWritable = FALSE; // TRUE;
+ descP->writePkgCnt = 0;
+ descP->writeByteCnt = 0;
+ descP->writeTries = 0;
+ descP->writeWaits = 0;
+ descP->writeFails = 0;
+
+ sprintf(buf, "socket[r,%d]", sock);
+ descP->readMtx = MCREATE(buf);
+ descP->currentReaderP = NULL; // currentReader not used
+ descP->readersQ.first = NULL;
+ descP->readersQ.last = NULL;
+ descP->isReadable = FALSE; // TRUE;
+ descP->readPkgCnt = 0;
+ descP->readByteCnt = 0;
+ descP->readTries = 0;
+ descP->readWaits = 0;
+
+ sprintf(buf, "socket[acc,%d]", sock);
+ descP->accMtx = MCREATE(buf);
+ descP->currentAcceptorP = NULL; // currentAcceptor not used
+ descP->acceptorsQ.first = NULL;
+ descP->acceptorsQ.last = NULL;
+
+ sprintf(buf, "socket[close,%d]", sock);
+ descP->closeMtx = MCREATE(buf);
+ descP->closeEnv = NULL;
+ descP->closeRef = esock_atom_undefined;
+
+ descP->rBufSz = SOCKET_RECV_BUFFER_SIZE_DEFAULT;
+ descP->rNum = 0;
+ descP->rNumCnt = 0;
+ descP->rCtrlSz = SOCKET_RECV_CTRL_BUFFER_SIZE_DEFAULT;
+ descP->wCtrlSz = SOCKET_SEND_CTRL_BUFFER_SIZE_DEFAULT;
+ descP->iow = FALSE;
+ descP->dbg = SOCKET_DEBUG_DEFAULT;
+
+ descP->sock = sock;
+ descP->event = event;
+
+ MON_INIT(&descP->currentWriter.mon);
+ MON_INIT(&descP->currentReader.mon);
+ MON_INIT(&descP->currentAcceptor.mon);
+ MON_INIT(&descP->ctrlMon);
+ MON_INIT(&descP->closerMon);
+ }
+
+ return descP;
+}
+
+
+
+/* decrement counters for when a socket is closed */
+static
+void dec_socket(int domain, int type, int protocol)
+{
+ MLOCK(data.cntMtx);
+
+ cnt_dec(&data.numSockets, 1);
+
+ if (domain == AF_INET)
+ cnt_dec(&data.numDomainInet, 1);
+#if defined(HAVE_IN6) && defined(AF_INET6)
+ else if (domain == AF_INET6)
+ cnt_dec(&data.numDomainInet6, 1);
+#endif
+#if defined(HAVE_SYS_UN_H)
+ else if (domain == AF_UNIX)
+ cnt_dec(&data.numDomainInet6, 1);
+#endif
+
+ if (type == SOCK_STREAM)
+ cnt_dec(&data.numTypeStreams, 1);
+ else if (type == SOCK_DGRAM)
+ cnt_dec(&data.numTypeDGrams, 1);
+#ifdef HAVE_SCTP
+ else if (type == SOCK_SEQPACKET)
+ cnt_dec(&data.numTypeSeqPkgs, 1);
+#endif
+
+ if (protocol == IPPROTO_IP)
+ cnt_dec(&data.numProtoIP, 1);
+ else if (protocol == IPPROTO_TCP)
+ cnt_dec(&data.numProtoTCP, 1);
+ else if (protocol == IPPROTO_UDP)
+ cnt_dec(&data.numProtoUDP, 1);
+#if defined(HAVE_SCTP)
+ else if (protocol == IPPROTO_SCTP)
+ cnt_dec(&data.numProtoSCTP, 1);
+#endif
+
+ MUNLOCK(data.cntMtx);
+}
+
+
+/* increment counters for when a socket is opened */
+static
+void inc_socket(int domain, int type, int protocol)
+{
+ MLOCK(data.cntMtx);
+
+ cnt_inc(&data.numSockets, 1);
+
+ if (domain == AF_INET)
+ cnt_inc(&data.numDomainInet, 1);
+#if defined(HAVE_IN6) && defined(AF_INET6)
+ else if (domain == AF_INET6)
+ cnt_inc(&data.numDomainInet6, 1);
+#endif
+#if defined(HAVE_SYS_UN_H)
+ else if (domain == AF_UNIX)
+ cnt_inc(&data.numDomainInet6, 1);
+#endif
+
+ if (type == SOCK_STREAM)
+ cnt_inc(&data.numTypeStreams, 1);
+ else if (type == SOCK_DGRAM)
+ cnt_inc(&data.numTypeDGrams, 1);
+#ifdef HAVE_SCTP
+ else if (type == SOCK_SEQPACKET)
+ cnt_inc(&data.numTypeSeqPkgs, 1);
+#endif
+
+ if (protocol == IPPROTO_IP)
+ cnt_inc(&data.numProtoIP, 1);
+ else if (protocol == IPPROTO_TCP)
+ cnt_inc(&data.numProtoTCP, 1);
+ else if (protocol == IPPROTO_UDP)
+ cnt_inc(&data.numProtoUDP, 1);
+#if defined(HAVE_SCTP)
+ else if (protocol == IPPROTO_SCTP)
+ cnt_inc(&data.numProtoSCTP, 1);
+#endif
+
+ MUNLOCK(data.cntMtx);
+}
+
+
+
+/* compare_pids - Test if two pids are equal
+ *
+ */
+static
+int compare_pids(ErlNifEnv* env,
+ const ErlNifPid* pid1,
+ const ErlNifPid* pid2)
+{
+ ERL_NIF_TERM p1 = enif_make_pid(env, pid1);
+ ERL_NIF_TERM p2 = enif_make_pid(env, pid2);
+
+ return enif_is_identical(p1, p2);
+}
+#endif // if !defined(__WIN32__)
+
+
+
+/* ----------------------------------------------------------------------
+ * D e c o d e / E n c o d e F u n c t i o n s
+ * ----------------------------------------------------------------------
+ */
+
+/* edomain2domain - convert internal (erlang) domain to (proper) domain
+ *
+ * Note that only a subset is supported.
+ */
+#if !defined(__WIN32__)
+static
+BOOLEAN_T edomain2domain(int edomain, int* domain)
+{
+ switch (edomain) {
+ case SOCKET_DOMAIN_INET:
+ *domain = AF_INET;
+ break;
+
+#if defined(HAVE_IN6) && defined(AF_INET6)
+ case SOCKET_DOMAIN_INET6:
+ *domain = AF_INET6;
+ break;
+#endif
+#ifdef HAVE_SYS_UN_H
+ case SOCKET_DOMAIN_LOCAL:
+ *domain = AF_UNIX;
+ break;
+#endif
+
+ default:
+ *domain = -1;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+/* etype2type - convert internal (erlang) type to (proper) type
+ *
+ * Note that only a subset is supported.
+ */
+static
+BOOLEAN_T etype2type(int etype, int* type)
+{
+ switch (etype) {
+ case SOCKET_TYPE_STREAM:
+ *type = SOCK_STREAM;
+ break;
+
+ case SOCKET_TYPE_DGRAM:
+ *type = SOCK_DGRAM;
+ break;
+
+ case SOCKET_TYPE_RAW:
+ *type = SOCK_RAW;
+ break;
+
+#ifdef HAVE_SCTP
+ case SOCKET_TYPE_SEQPACKET:
+ *type = SOCK_SEQPACKET;
+ break;
+#endif
+
+ default:
+ *type = -1;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+/* eproto2proto - convert internal (erlang) protocol to (proper) protocol
+ *
+ * Note that only a subset is supported.
+ */
+static
+BOOLEAN_T eproto2proto(ErlNifEnv* env,
+ ERL_NIF_TERM eproto,
+ int* proto)
+{
+ if (IS_NUM(env, eproto)) {
+ int ep;
+
+ if (!GET_INT(env, eproto, &ep)) {
+ *proto = -1;
+ return FALSE;
+ }
+
+ switch (ep) {
+ case SOCKET_PROTOCOL_IP:
+ *proto = IPPROTO_IP;
+ break;
+
+ case SOCKET_PROTOCOL_TCP:
+ *proto = IPPROTO_TCP;
+ break;
+
+ case SOCKET_PROTOCOL_UDP:
+ *proto = IPPROTO_UDP;
+ break;
+
+#if defined(HAVE_SCTP)
+ case SOCKET_PROTOCOL_SCTP:
+ *proto = IPPROTO_SCTP;
+ break;
+#endif
+
+ case SOCKET_PROTOCOL_ICMP:
+ *proto = IPPROTO_ICMP;
+ break;
+
+ case SOCKET_PROTOCOL_IGMP:
+ *proto = IPPROTO_IGMP;
+ break;
+
+ default:
+ *proto = -2;
+ return FALSE;
+ }
+ } else {
+ const ERL_NIF_TERM* a;
+ int sz;
+
+ if (!GET_TUPLE(env, eproto, &sz, &a)) {
+ *proto = -3;
+ return FALSE;
+ }
+
+ if (sz != 2) {
+ *proto = -4;
+ return FALSE;
+ }
+
+ if (COMPARE(a[0], esock_atom_raw) != 0) {
+ *proto = -5;
+ return FALSE;
+ }
+
+ if (!GET_INT(env, a[1], proto)) {
+ *proto = -6;
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+
+#ifdef HAVE_SETNS
+ /* emap2netns - extract the netns field from the extra map
+ *
+ * Note that currently we only support one extra option, the netns.
+ */
+static
+BOOLEAN_T emap2netns(ErlNifEnv* env, ERL_NIF_TERM map, char** netns)
+{
+ size_t sz;
+ ERL_NIF_TERM key;
+ ERL_NIF_TERM value;
+ unsigned int len;
+ char* buf;
+ int written;
+
+ /* Note that its acceptable that the extra map is empty */
+ if (!enif_get_map_size(env, map, &sz) ||
+ (sz != 1)) {
+ *netns = NULL;
+ return TRUE;
+ }
+
+ /* The currently only supported extra option is: netns */
+ key = enif_make_atom(env, "netns");
+ if (!GET_MAP_VAL(env, map, key, &value)) {
+ *netns = NULL; // Just in case...
+ return FALSE;
+ }
+
+ /* So far so good. The value should be a string, check. */
+ if (!enif_is_list(env, value)) {
+ *netns = NULL; // Just in case...
+ return FALSE;
+ }
+
+ if (!enif_get_list_length(env, value, &len)) {
+ *netns = NULL; // Just in case...
+ return FALSE;
+ }
+
+ if ((buf = MALLOC(len+1)) == NULL) {
+ *netns = NULL; // Just in case...
+ return FALSE;
+ }
+
+ written = enif_get_string(env, value, buf, len+1, ERL_NIF_LATIN1);
+ if (written == (len+1)) {
+ *netns = buf;
+ return TRUE;
+ } else {
+ *netns = NULL; // Just in case...
+ return FALSE;
+ }
+}
+#endif
+
+
+/* esendflags2sendflags - convert internal (erlang) send flags to (proper)
+ * send flags.
+ */
+static
+BOOLEAN_T esendflags2sendflags(unsigned int eflags, int* flags)
+{
+ unsigned int ef;
+ int tmp = 0;
+
+ /* First, check if we have any flags at all */
+ if (eflags == 0) {
+ *flags = 0;
+ return TRUE;
+ }
+
+ for (ef = SOCKET_SEND_FLAG_LOW; ef <= SOCKET_SEND_FLAG_HIGH; ef++) {
+
+ switch (ef) {
+#if defined(MSG_CONFIRM)
+ case SOCKET_SEND_FLAG_CONFIRM:
+ if ((1 << SOCKET_SEND_FLAG_CONFIRM) & eflags)
+ tmp |= MSG_CONFIRM;
+ break;
+#endif
+
+#if defined(MSG_DONTROUTE)
+ case SOCKET_SEND_FLAG_DONTROUTE:
+ if ((1 << SOCKET_SEND_FLAG_DONTROUTE) & eflags)
+ tmp |= MSG_DONTROUTE;
+ break;
+#endif
+
+#if defined(MSG_EOR)
+ case SOCKET_SEND_FLAG_EOR:
+ if ((1 << SOCKET_SEND_FLAG_EOR) & eflags)
+ tmp |= MSG_EOR;
+ break;
+#endif
+
+#if defined(MSG_MORE)
+ case SOCKET_SEND_FLAG_MORE:
+ if ((1 << SOCKET_SEND_FLAG_MORE) & eflags)
+ tmp |= MSG_MORE;
+ break;
+#endif
+
+#if defined(MSG_NOSIGNAL)
+ case SOCKET_SEND_FLAG_NOSIGNAL:
+ if ((1 << SOCKET_SEND_FLAG_NOSIGNAL) & eflags)
+ tmp |= MSG_NOSIGNAL;
+ break;
+#endif
+
+#if defined(MSG_OOB)
+ case SOCKET_SEND_FLAG_OOB:
+ if ((1 << SOCKET_SEND_FLAG_OOB) & eflags)
+ tmp |= MSG_OOB;
+ break;
+#endif
+
+ default:
+ return FALSE;
+ }
+
+ }
+
+ *flags = tmp;
+
+ return TRUE;
+}
+
+
+
+/* erecvflags2recvflags - convert internal (erlang) send flags to (proper)
+ * send flags.
+ */
+static
+BOOLEAN_T erecvflags2recvflags(unsigned int eflags, int* flags)
+{
+ unsigned int ef;
+ int tmp = 0;
+
+ SGDBG( ("SOCKET", "erecvflags2recvflags -> entry with"
+ "\r\n eflags: %d"
+ "\r\n", eflags) );
+
+ if (eflags == 0) {
+ *flags = 0;
+ return TRUE;
+ }
+
+ for (ef = SOCKET_RECV_FLAG_LOW; ef <= SOCKET_RECV_FLAG_HIGH; ef++) {
+
+ SGDBG( ("SOCKET", "erecvflags2recvflags -> iteration"
+ "\r\n ef: %d"
+ "\r\n tmp: %d"
+ "\r\n", ef, tmp) );
+
+ switch (ef) {
+#if defined(MSG_CMSG_CLOEXEC)
+ case SOCKET_RECV_FLAG_CMSG_CLOEXEC:
+ if ((1 << SOCKET_RECV_FLAG_CMSG_CLOEXEC) & eflags)
+ tmp |= MSG_CMSG_CLOEXEC;
+ break;
+#endif
+
+#if defined(MSG_ERRQUEUE)
+ case SOCKET_RECV_FLAG_ERRQUEUE:
+ if ((1 << SOCKET_RECV_FLAG_ERRQUEUE) & eflags)
+ tmp |= MSG_ERRQUEUE;
+ break;
+#endif
+
+#if defined(MSG_OOB)
+ case SOCKET_RECV_FLAG_OOB:
+ if ((1 << SOCKET_RECV_FLAG_OOB) & eflags)
+ tmp |= MSG_OOB;
+ break;
+#endif
+
+ /*
+ * <KOLLA>
+ *
+ * We need to handle this, because it may effect the read algorithm
+ *
+ * </KOLLA>
+ */
+#if defined(MSG_PEEK)
+ case SOCKET_RECV_FLAG_PEEK:
+ if ((1 << SOCKET_RECV_FLAG_PEEK) & eflags)
+ tmp |= MSG_PEEK;
+ break;
+#endif
+
+#if defined(MSG_TRUNC)
+ case SOCKET_RECV_FLAG_TRUNC:
+ if ((1 << SOCKET_RECV_FLAG_TRUNC) & eflags)
+ tmp |= MSG_TRUNC;
+ break;
+#endif
+
+ default:
+ return FALSE;
+ }
+
+ }
+
+ *flags = tmp;
+
+ return TRUE;
+}
+
+
+
+/* eproto2proto - convert internal (erlang) protocol to (proper) protocol
+ *
+ * Note that only a subset is supported.
+ */
+static
+BOOLEAN_T ehow2how(unsigned int ehow, int* how)
+{
+ switch (ehow) {
+ case SOCKET_SHUTDOWN_HOW_RD:
+ *how = SHUT_RD;
+ break;
+
+ case SOCKET_SHUTDOWN_HOW_WR:
+ *how = SHUT_WR;
+ break;
+
+ case SOCKET_SHUTDOWN_HOW_RDWR:
+ *how = SHUT_RDWR;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+
+#if defined(HAVE_SYS_UN_H) || defined(SO_BINDTODEVICE)
+/* strnlen doesn't exist everywhere */
+/*
+static
+size_t my_strnlen(const char *s, size_t maxlen)
+{
+ size_t i = 0;
+ while (i < maxlen && s[i] != '\0')
+ i++;
+ return i;
+}
+*/
+#endif
+
+
+/* Send an error closed message to the specified process:
+ *
+ * This message is for processes that are waiting in the
+ * erlang API functions for a select message.
+ */
+/*
+static
+char* send_msg_error_closed(ErlNifEnv* env,
+ ErlNifPid* pid)
+{
+ return send_msg_error(env, atom_closed, pid);
+}
+*/
+
+/* Send an error message to the specified process:
+ * A message in the form:
+ *
+ * {error, Reason}
+ *
+ * This message is for processes that are waiting in the
+ * erlang API functions for a select message.
+ */
+/*
+static
+char* send_msg_error(ErlNifEnv* env,
+ ERL_NIF_TERM reason,
+ ErlNifPid* pid)
+{
+ ERL_NIF_TERM msg = enif_make_tuple2(env, atom_error, reason);
+
+ return send_msg(env, msg, pid);
+}
+*/
+
+
+/* Send an close message to the specified process:
+ * A message in the form:
+ *
+ * {'$socket', SockRef, close, CloseRef}
+ *
+ * This message is for processes that is waiting in the
+ * erlang API (close-) function for the socket to be "closed"
+ * (actually that the 'stop' callback function has been called).
+ */
+static
+char* esock_send_close_msg(ErlNifEnv* env,
+ SocketDescriptor* descP)
+{
+ ERL_NIF_TERM sockRef = enif_make_resource(descP->closeEnv, descP);
+ char* res = esock_send_socket_msg(env,
+ sockRef,
+ esock_atom_close,
+ descP->closeRef,
+ &descP->closerPid,
+ descP->closeEnv);
+
+ descP->closeEnv = NULL;
+
+ return res;
+}
+
+
+/* Send an abort message to the specified process:
+ * A message in the form:
+ *
+ * {'$socket', SockRef, abort, {RecvRef, Reason}}
+ *
+ * This message is for processes that is waiting in the
+ * erlang API functions for a select message.
+ */
+static
+char* esock_send_abort_msg(ErlNifEnv* env,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM recvRef,
+ ERL_NIF_TERM reason,
+ ErlNifPid* pid)
+{
+ ErlNifEnv* msg_env = enif_alloc_env();
+ ERL_NIF_TERM info = MKT2(msg_env,
+ enif_make_copy(msg_env, recvRef),
+ enif_make_copy(msg_env, reason));
+
+ return esock_send_socket_msg(env, sockRef, esock_atom_abort, info, pid,
+ msg_env);
+}
+
+
+/* *** esock_send_socket_msg ***
+ *
+ * This function sends a general purpose socket message to an erlang
+ * process. A general 'socket' message has the ("erlang") form:
+ *
+ * {'$socket', SockRef, Tag, Info}
+ *
+ * Where
+ *
+ * SockRef: reference()
+ * Tag: atom()
+ * Info: term()
+ *
+ */
+
+static
+char* esock_send_socket_msg(ErlNifEnv* env,
+ ERL_NIF_TERM sockRef,
+ ERL_NIF_TERM tag,
+ ERL_NIF_TERM info,
+ ErlNifPid* pid,
+ ErlNifEnv* msg_env)
+{
+ ERL_NIF_TERM msg;
+ if (!msg_env) {
+ msg_env = enif_alloc_env();
+ sockRef = enif_make_copy(msg_env, sockRef);
+ tag = enif_make_copy(msg_env, tag);
+ info = enif_make_copy(msg_env, info);
+ }
+ msg = MKT4(msg_env, esock_atom_socket_tag, sockRef, tag, info);
+
+ return esock_send_msg(env, msg, pid, msg_env);
+}
+
+
+/* Send a message to the specified process.
+ */
+static
+char* esock_send_msg(ErlNifEnv* env,
+ ERL_NIF_TERM msg,
+ ErlNifPid* pid,
+ ErlNifEnv* msg_env)
+{
+ int res = enif_send(env, pid, msg_env, msg);
+ if (msg_env)
+ enif_free_env(msg_env);
+
+ if (!res)
+ return str_exsend;
+ else
+ return NULL;
+}
+#endif // #if defined(__WIN32__)
+
+
+/* ----------------------------------------------------------------------
+ * S e l e c t W r a p p e r F u n c t i o n s
+ * ----------------------------------------------------------------------
+ */
+
+static
+int esock_select_read(ErlNifEnv* env,
+ ErlNifEvent event,
+ void* obj,
+ const ErlNifPid* pid,
+ ERL_NIF_TERM ref)
+{
+ return enif_select(env, event, (ERL_NIF_SELECT_READ), obj, pid, ref);
+}
+
+
+static
+int esock_select_write(ErlNifEnv* env,
+ ErlNifEvent event,
+ void* obj,
+ const ErlNifPid* pid,
+ ERL_NIF_TERM ref)
+{
+ return enif_select(env, event, (ERL_NIF_SELECT_WRITE), obj, pid, ref);
+}
+
+
+static
+int esock_select_stop(ErlNifEnv* env,
+ ErlNifEvent event,
+ void* obj)
+{
+ return enif_select(env, event, (ERL_NIF_SELECT_STOP), obj, NULL,
+ esock_atom_undefined);
+}
+
+static
+int esock_select_cancel(ErlNifEnv* env,
+ ErlNifEvent event,
+ enum ErlNifSelectFlags mode,
+ void* obj)
+{
+ return enif_select(env, event, (ERL_NIF_SELECT_CANCEL | mode), obj, NULL,
+ esock_atom_undefined);
+}
+
+
+/* ----------------------------------------------------------------------
+ * R e q u e s t Q u e u e F u n c t i o n s
+ * ----------------------------------------------------------------------
+ */
+
+/* *** acceptor search for pid ***
+ *
+ * Search for a pid in the acceptor queue.
+ */
+#if !defined(__WIN32__)
+static
+BOOLEAN_T acceptor_search4pid(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ErlNifPid* pid)
+{
+ return qsearch4pid(env, &descP->acceptorsQ, pid);
+}
+
+
+/* *** acceptor push ***
+ *
+ * Push an acceptor onto the acceptor queue.
+ * This happens when we already have atleast one current acceptor.
+ */
+static
+ERL_NIF_TERM acceptor_push(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ErlNifPid pid,
+ ERL_NIF_TERM ref)
+{
+ SocketRequestQueueElement* e = MALLOC(sizeof(SocketRequestQueueElement));
+ SocketRequestor* reqP = &e->data;
+
+ reqP->pid = pid;
+ reqP->ref = enif_make_copy(descP->env, ref);
+
+ if (MONP("acceptor_push -> acceptor request",
+ env, descP, &pid, &reqP->mon) != 0) {
+ FREE(reqP);
+ return esock_make_error(env, atom_exmon);
+ }
+
+ qpush(&descP->acceptorsQ, e);
+
+ // THIS IS OK => MAKES THE CALLER WAIT FOR ITS TURN
+ return esock_make_error(env, esock_atom_eagain);
+}
+
+
+/* *** acceptor pop ***
+ *
+ * Pop an acceptor from the acceptor queue.
+ */
+static
+BOOLEAN_T acceptor_pop(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ErlNifPid* pid,
+ // ErlNifMonitor* mon,
+ ESockMonitor* mon,
+ ERL_NIF_TERM* ref)
+{
+ SocketRequestQueueElement* e = qpop(&descP->acceptorsQ);
+
+ if (e != NULL) {
+ *pid = e->data.pid;
+ *mon = e->data.mon;
+ *ref = e->data.ref;
+ FREE(e);
+ return TRUE;
+ } else {
+ /* (acceptors) Queue was empty */
+ // *pid = NULL; we have no null value for pids
+ // *mon = NULL; we have no null value for monitors
+ *ref = esock_atom_undefined; // Just in case
+ return FALSE;
+ }
+
+}
+
+
+/* *** acceptor unqueue ***
+ *
+ * Remove an acceptor from the acceptor queue.
+ */
+static
+BOOLEAN_T acceptor_unqueue(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ const ErlNifPid* pid)
+{
+ return qunqueue(env, descP, "qunqueue -> waiting acceptor",
+ &descP->acceptorsQ, pid);
+}
+
+
+
+/* *** writer search for pid ***
+ *
+ * Search for a pid in the writer queue.
+ */
+static
+BOOLEAN_T writer_search4pid(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ErlNifPid* pid)
+{
+ return qsearch4pid(env, &descP->writersQ, pid);
+}
+
+
+/* *** writer push ***
+ *
+ * Push an writer onto the writer queue.
+ * This happens when we already have atleast one current writer.
+ */
+static
+ERL_NIF_TERM writer_push(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ErlNifPid pid,
+ ERL_NIF_TERM ref)
+{
+ SocketRequestQueueElement* e = MALLOC(sizeof(SocketRequestQueueElement));
+ SocketRequestor* reqP = &e->data;
+
+ reqP->pid = pid;
+ reqP->ref = enif_make_copy(descP->env, ref);
+
+ if (MONP("writer_push -> writer request",
+ env, descP, &pid, &reqP->mon) != 0) {
+ FREE(reqP);
+ return esock_make_error(env, atom_exmon);
+ }
+
+ qpush(&descP->writersQ, e);
+
+ // THIS IS OK => MAKES THE CALLER WAIT FOR ITS TURN
+ return esock_make_error(env, esock_atom_eagain);
+}
+
+
+/* *** writer pop ***
+ *
+ * Pop an writer from the writer queue.
+ */
+static
+BOOLEAN_T writer_pop(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ErlNifPid* pid,
+ // ErlNifMonitor* mon,
+ ESockMonitor* mon,
+ ERL_NIF_TERM* ref)
+{
+ SocketRequestQueueElement* e = qpop(&descP->writersQ);
+
+ if (e != NULL) {
+ *pid = e->data.pid;
+ *mon = e->data.mon;
+ *ref = e->data.ref; // At this point the ref has already been copied (env)
+ FREE(e);
+ return TRUE;
+ } else {
+ /* (writers) Queue was empty */
+ // *pid = NULL; we have no null value for pids
+ // *mon = NULL; we have no null value for monitors
+ *ref = esock_atom_undefined; // Just in case
+ return FALSE;
+ }
+
+}
+
+
+/* *** writer unqueue ***
+ *
+ * Remove an writer from the writer queue.
+ */
+static
+BOOLEAN_T writer_unqueue(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ const ErlNifPid* pid)
+{
+ return qunqueue(env, descP, "qunqueue -> waiting writer",
+ &descP->writersQ, pid);
+}
+
+
+
+/* *** reader search for pid ***
+ *
+ * Search for a pid in the reader queue.
+ */
+static
+BOOLEAN_T reader_search4pid(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ErlNifPid* pid)
+{
+ return qsearch4pid(env, &descP->readersQ, pid);
+}
+
+
+/* *** reader push ***
+ *
+ * Push an reader onto the raeder queue.
+ * This happens when we already have atleast one current reader.
+ */
+static
+ERL_NIF_TERM reader_push(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ErlNifPid pid,
+ ERL_NIF_TERM ref)
+{
+ SocketRequestQueueElement* e = MALLOC(sizeof(SocketRequestQueueElement));
+ SocketRequestor* reqP = &e->data;
+
+ reqP->pid = pid;
+ reqP->ref = enif_make_copy(descP->env, ref);
+
+ if (MONP("reader_push -> reader request",
+ env, descP, &pid, &reqP->mon) != 0) {
+ FREE(reqP);
+ return esock_make_error(env, atom_exmon);
+ }
+
+ qpush(&descP->readersQ, e);
+
+ // THIS IS OK => MAKES THE CALLER WAIT FOR ITS TURN
+ return esock_make_error(env, esock_atom_eagain);
+}
+
+
+/* *** reader pop ***
+ *
+ * Pop an writer from the reader queue.
+ */
+static
+BOOLEAN_T reader_pop(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ErlNifPid* pid,
+ // ErlNifMonitor* mon,
+ ESockMonitor* mon,
+ ERL_NIF_TERM* ref)
+{
+ SocketRequestQueueElement* e = qpop(&descP->readersQ);
+
+ if (e != NULL) {
+ *pid = e->data.pid;
+ *mon = e->data.mon;
+ *ref = e->data.ref; // At this point the ref has already been copied (env)
+ FREE(e);
+ return TRUE;
+ } else {
+ /* (readers) Queue was empty */
+ // *pid = NULL; we have no null value for pids
+ // *mon = NULL; we have no null value for monitors
+ *ref = esock_atom_undefined; // Just in case
+ return FALSE;
+ }
+
+}
+
+
+/* *** reader unqueue ***
+ *
+ * Remove an reader from the reader queue.
+ */
+static
+BOOLEAN_T reader_unqueue(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ const ErlNifPid* pid)
+{
+ return qunqueue(env, descP, "qunqueue -> waiting reader",
+ &descP->readersQ, pid);
+}
+
+
+
+
+
+static
+BOOLEAN_T qsearch4pid(ErlNifEnv* env,
+ SocketRequestQueue* q,
+ ErlNifPid* pid)
+{
+ SocketRequestQueueElement* tmp = q->first;
+
+ while (tmp != NULL) {
+ if (compare_pids(env, &tmp->data.pid, pid))
+ return TRUE;
+ else
+ tmp = tmp->nextP;
+ }
+
+ return FALSE;
+}
+
+
+static
+void qpush(SocketRequestQueue* q,
+ SocketRequestQueueElement* e)
+{
+ if (q->first != NULL) {
+ q->last->nextP = e;
+ q->last = e;
+ e->nextP = NULL;
+ } else {
+ q->first = e;
+ q->last = e;
+ e->nextP = NULL;
+ }
+}
+
+
+static
+SocketRequestQueueElement* qpop(SocketRequestQueue* q)
+{
+ SocketRequestQueueElement* e = q->first;
+
+ if (e != NULL) {
+ /* Atleast one element in the queue */
+ if (e == q->last) {
+ /* Only one element in the queue */
+ q->first = q->last = NULL;
+ } else {
+ /* More than one element in the queue */
+ q->first = e->nextP;
+ }
+ }
+
+ return e;
+}
+
+
+
+static
+BOOLEAN_T qunqueue(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ const char* slogan,
+ SocketRequestQueue* q,
+ const ErlNifPid* pid)
+{
+ SocketRequestQueueElement* e = q->first;
+ SocketRequestQueueElement* p = NULL;
+
+ /* Check if it was one of the waiting acceptor processes */
+ while (e != NULL) {
+ if (compare_pids(env, &e->data.pid, pid)) {
+
+ /* We have a match */
+
+ DEMONP(slogan, env, descP, &e->data.mon);
+
+ if (p != NULL) {
+ /* Not the first, but could be the last */
+ if (q->last == e) {
+ q->last = p;
+ p->nextP = NULL;
+ } else {
+ p->nextP = e->nextP;
+ }
+
+ } else {
+ /* The first and could also be the last */
+ if (q->last == e) {
+ q->last = NULL;
+ q->first = NULL;
+ } else {
+ q->first = e->nextP;
+ }
+ }
+
+ FREE(e);
+
+ return TRUE;
+ }
+
+ /* Try next */
+ p = e;
+ e = e->nextP;
+ }
+
+ return FALSE;
+}
+#endif // if !defined(__WIN32__)
+
+
+
+/* ----------------------------------------------------------------------
+ * C o u n t e r F u n c t i o n s
+ * ----------------------------------------------------------------------
+ */
+
+#if !defined(__WIN32__)
+static
+BOOLEAN_T cnt_inc(Uint32* cnt, Uint32 inc)
+{
+ BOOLEAN_T wrap;
+ Uint32 max = 0xFFFFFFFF;
+ Uint32 current = *cnt;
+
+ if ((max - inc) >= current) {
+ *cnt += inc;
+ wrap = FALSE;
+ } else {
+ *cnt = inc - (max - current) - 1;
+ wrap = TRUE;
+ }
+
+ return (wrap);
+}
+
+
+static
+void cnt_dec(Uint32* cnt, Uint32 dec)
+{
+ Uint32 current = *cnt;
+
+ if (dec > current)
+ *cnt = 0; // The counter cannot be < 0 so this is the best we can do...
+ else
+ *cnt -= dec;
+
+ return;
+}
+#endif // if !defined(__WIN32__)
+
+
+
+
+/* ----------------------------------------------------------------------
+ * M o n i t o r W r a p p e r F u n c t i o n s
+ * ----------------------------------------------------------------------
+ */
+
+#if !defined(__WIN32__)
+
+static
+ERL_NIF_TERM my_make_monitor_term(ErlNifEnv* env, const ErlNifMonitor* mon)
+{
+ return ((ERL_NIF_TERM)&mon->data) + 2;
+}
+
+static
+int esock_monitor(const char* slogan,
+ ErlNifEnv* env,
+ SocketDescriptor* descP,
+ const ErlNifPid* pid,
+ ESockMonitor* monP)
+{
+ int res;
+
+ SSDBG( descP, ("SOCKET", "[%d] %s: try monitor\r\n", descP->sock, slogan) );
+ res = enif_monitor_process(env, descP, pid, &monP->mon);
+
+ if (res != 0) {
+ monP->is_active = 0;
+ SSDBG( descP, ("SOCKET", "[%d] monitor failed: %d\r\n", descP->sock, res) );
+ } else {
+ monP->is_active = 1;
+ }
+
+ return res;
+}
+
+
+static
+int esock_demonitor(const char* slogan,
+ ErlNifEnv* env,
+ SocketDescriptor* descP,
+ ESockMonitor* monP)
+{
+ int res;
+
+ if (!monP->is_active)
+ return 1;
+
+ SSDBG( descP, ("SOCKET", "[%d] %s: try demonitor\r\n", descP->sock, slogan) );
+
+ res = enif_demonitor_process(env, descP, &monP->mon);
+
+ if (res == 0) {
+ esock_monitor_init(monP);
+ } else {
+ SSDBG( descP,
+ ("SOCKET", "[%d] demonitor failed: %d\r\n", descP->sock, res) );
+ }
+
+ return res;
+}
+
+
+static
+void esock_monitor_init(ESockMonitor* monP)
+{
+ monP->is_active = 0;
+}
+
+#endif // if !defined(__WIN32__)
+
+
+/*
+static
+int esock_monitor_compare(const ErlNifMonitor* mon1,
+ const ESockMonitor* mon2)
+{
+ return enif_compare_monitors(mon1, &mon2->mon);
+}
+*/
+
+
+/* ----------------------------------------------------------------------
+ * C a l l b a c k F u n c t i o n s
+ * ----------------------------------------------------------------------
+ */
+
+/* =========================================================================
+ * socket_dtor - Callback function for resource destructor
+ *
+ */
+static
+void socket_dtor(ErlNifEnv* env, void* obj)
+{
+#if !defined(__WIN32__)
+ SocketDescriptor* descP = (SocketDescriptor*) obj;
+
+ enif_clear_env(descP->env);
+ enif_free_env(descP->env);
+ descP->env = NULL;
+
+ MDESTROY(descP->writeMtx);
+ MDESTROY(descP->readMtx);
+ MDESTROY(descP->accMtx);
+ MDESTROY(descP->closeMtx);
+#endif
+}
+
+
+/* =========================================================================
+ * socket_stop - Callback function for resource stop
+ *
+ * When the socket is stopped, we need to inform:
+ *
+ * * the controlling process
+ * * the current writer and any waiting writers
+ * * the current reader and any waiting readers
+ * * the current acceptor and any waiting acceptor
+ *
+ * Also, make sure no process gets the message twice
+ * (in case it is, for instance, both controlling process
+ * and a writer).
+ *
+ */
+static
+void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call)
+{
+#if !defined(__WIN32__)
+ SocketDescriptor* descP = (SocketDescriptor*) obj;
+ ERL_NIF_TERM sockRef;
+
+ SSDBG( descP,
+ ("SOCKET", "socket_stop -> entry when %s"
+ "\r\n sock: %d (%d)"
+ "\r\n",
+ ((is_direct_call) ? "called" : "scheduled"), descP->sock, fd) );
+
+ /* +++ Lock it down +++ */
+
+ MLOCK(descP->writeMtx);
+ MLOCK(descP->readMtx);
+ MLOCK(descP->accMtx);
+ if (!is_direct_call) MLOCK(descP->closeMtx);
+
+ SSDBG( descP, ("SOCKET", "socket_stop -> "
+ "[%d, %T] all mutex(s) locked when counters:"
+ "\r\n writePkgCnt: %u"
+ "\r\n writeByteCnt: %u"
+ "\r\n writeTries: %u"
+ "\r\n writeWaits: %u"
+ "\r\n writeFails: %u"
+ "\r\n readPkgCnt: %u"
+ "\r\n readByteCnt: %u"
+ "\r\n readTries: %u"
+ "\r\n readWaits: %u"
+ "\r\n",
+ descP->sock, descP->ctrlPid,
+ descP->writePkgCnt,
+ descP->writeByteCnt,
+ descP->writeTries,
+ descP->writeWaits,
+ descP->writeFails,
+ descP->readPkgCnt,
+ descP->readByteCnt,
+ descP->readTries,
+ descP->readWaits) );
+
+ sockRef = enif_make_resource(env, descP),
+ descP->state = SOCKET_STATE_CLOSING; // Just in case...???
+ descP->isReadable = FALSE;
+ descP->isWritable = FALSE;
+
+ /* We should check that we actually have a monitor.
+ * This *should* be done with a "NULL" monitor value,
+ * which there currently is none...
+ * If we got here because the controlling process died,
+ * there is no point to demonitor. Also, we do not actually
+ * have a monitor in that case...
+ */
+ DEMONP("socket_stop -> ctrl", env, descP, &descP->ctrlMon);
+
+
+
+ /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ *
+ * Check current and waiting Writers
+ *
+ * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ */
+
+ if (descP->currentWriterP != NULL) {
+ /* We have a (current) writer and *may* therefor also have
+ * writers waiting.
+ */
+
+ DEMONP("socket_stop -> current writer",
+ env, descP, &descP->currentWriter.mon);
+
+ SSDBG( descP, ("SOCKET", "socket_stop -> handle current writer\r\n") );
+ if (!compare_pids(env,
+ &descP->closerPid,
+ &descP->currentWriter.pid)) {
+ SSDBG( descP, ("SOCKET", "socket_stop -> "
+ "send abort message to current writer %T\r\n",
+ descP->currentWriter.pid) );
+ if (esock_send_abort_msg(env,
+ sockRef,
+ descP->currentWriter.ref,
+ atom_closed,
+ &descP->currentWriter.pid) != NULL) {
+ /* Shall we really do this?
+ * This happens if the controlling process has been killed!
+ */
+ esock_warning_msg("Failed sending abort (%T) message to "
+ "current writer %T\r\n",
+ descP->currentWriter.ref,
+ descP->currentWriter.pid);
+ }
+ }
+
+ /* And also deal with the waiting writers (in the same way) */
+ SSDBG( descP, ("SOCKET", "socket_stop -> handle waiting writer(s)\r\n") );
+ inform_waiting_procs(env, descP, &descP->writersQ, TRUE, atom_closed);
+ }
+
+
+ /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ *
+ * Check current and waiting Readers
+ *
+ * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ */
+
+ if (descP->currentReaderP != NULL) {
+
+ /* We have a (current) reader and *may* therefor also have
+ * readers waiting.
+ */
+
+ DEMONP("socket_stop -> current reader",
+ env, descP, &descP->currentReader.mon);
+
+ SSDBG( descP, ("SOCKET", "socket_stop -> handle current reader\r\n") );
+ if (!compare_pids(env,
+ &descP->closerPid,
+ &descP->currentReader.pid)) {
+ SSDBG( descP, ("SOCKET", "socket_stop -> "
+ "send abort message to current reader %T\r\n",
+ descP->currentReader.pid) );
+ if (esock_send_abort_msg(env,
+ sockRef,
+ descP->currentReader.ref,
+ atom_closed,
+ &descP->currentReader.pid) != NULL) {
+ /* Shall we really do this?
+ * This happens if the controlling process has been killed!
+ */
+ esock_warning_msg("Failed sending abort (%T) message to "
+ "current reader %T\r\n",
+ descP->currentReader.ref,
+ descP->currentReader.pid);
+ }
+ }
+
+ /* And also deal with the waiting readers (in the same way) */
+ SSDBG( descP, ("SOCKET", "socket_stop -> handle waiting reader(s)\r\n") );
+ inform_waiting_procs(env, descP, &descP->readersQ, TRUE, atom_closed);
+ }
+
+
+
+ /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ *
+ * Check current and waiting Acceptors
+ *
+ * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ */
+
+ if (descP->currentAcceptorP != NULL) {
+ /* We have a (current) acceptor and *may* therefor also have
+ * acceptors waiting.
+ */
+
+ DEMONP("socket_stop -> current acceptor",
+ env, descP, &descP->currentAcceptor.mon);
+
+ SSDBG( descP, ("SOCKET", "socket_stop -> handle current acceptor\r\n") );
+ if (!compare_pids(env,
+ &descP->closerPid,
+ &descP->currentAcceptor.pid)) {
+ SSDBG( descP, ("SOCKET", "socket_stop -> "
+ "send abort message to current acceptor %T\r\n",
+ descP->currentWriter.pid) );
+ if (esock_send_abort_msg(env,
+ sockRef,
+ descP->currentAcceptor.ref,
+ atom_closed,
+ &descP->currentAcceptor.pid) != NULL) {
+ /* Shall we really do this?
+ * This happens if the controlling process has been killed!
+ */
+ esock_warning_msg("Failed sending abort (%T) message to "
+ "current acceptor %T\r\n",
+ descP->currentAcceptor.ref,
+ descP->currentAcceptor.pid);
+ }
+ }
+
+ /* And also deal with the waiting acceptors (in the same way) */
+ SSDBG( descP, ("SOCKET", "socket_stop -> handle waiting acceptor(s)\r\n") );
+ inform_waiting_procs(env, descP, &descP->acceptorsQ, TRUE, atom_closed);
+ }
+
+
+
+ /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ *
+ * Maybe inform waiting closer
+ *
+ * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ */
+
+ if (descP->sock != INVALID_SOCKET) {
+
+ if (descP->closeLocal) {
+ if (!is_direct_call) {
+
+ /* +++ send close message to the waiting process +++ */
+
+ esock_send_close_msg(env, descP);
+
+ DEMONP("socket_stop -> closer", env, descP, &descP->closerMon);
+
+ } else {
+
+ /* We only need to explicitly free the environment here
+ * since the message send takes care of it if scheduled.
+ */
+
+ if (descP->closeEnv != NULL) enif_free_env(descP->closeEnv);
+
+ }
+ }
+ }
+
+
+ SSDBG( descP, ("SOCKET", "socket_stop -> unlock all mutex(s)\r\n") );
+
+ if (!is_direct_call) MUNLOCK(descP->closeMtx);
+ MUNLOCK(descP->accMtx);
+ MUNLOCK(descP->readMtx);
+ MUNLOCK(descP->writeMtx);
+
+ SSDBG( descP,
+ ("SOCKET", "socket_stop -> done (%d, %d)\r\n", descP->sock, fd) );
+
+#endif // if !defined(__WIN32__)
+}
+
+
+
+/* This function traverse the queue and sends the specified
+ * nif_abort message with the specified reason to each member,
+ * and if the 'free' argument is TRUE, the queue will be emptied.
+ */
+#if !defined(__WIN32__)
+static
+void inform_waiting_procs(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ SocketRequestQueue* q,
+ BOOLEAN_T free,
+ ERL_NIF_TERM reason)
+{
+ SocketRequestQueueElement* currentP = q->first;
+ SocketRequestQueueElement* nextP;
+
+ while (currentP != NULL) {
+
+ /* <KOLLA>
+ *
+ * Should we inform anyone if we fail to demonitor?
+ * NOT SURE WHAT THAT WOULD REPRESENT AND IT IS NOT
+ * IMPORTANT IN *THIS* CASE, BUT ITS A FUNDAMENTAL OP...
+ *
+ * </KOLLA>
+ */
+
+ SSDBG( descP,
+ ("SOCKET", "inform_waiting_procs -> abort request %T (from %T)\r\n",
+ currentP->data.ref, currentP->data.pid) );
+
+ ESOCK_ASSERT( (NULL == esock_send_abort_msg(env,
+ esock_atom_undefined,
+ currentP->data.ref,
+ reason,
+ &currentP->data.pid)) );
+
+ DEMONP("inform_waiting_procs -> current 'request'",
+ env, descP, &currentP->data.mon);
+ nextP = currentP->nextP;
+ if (free) FREE(currentP);
+ currentP = nextP;
+ }
+
+ if (free) {
+ q->first = NULL;
+ q->last = NULL;
+ }
+}
+#endif // if !defined(__WIN32__)
+
+
+/* =========================================================================
+ * socket_down - Callback function for resource down (monitored processes)
+ *
+ */
+static
+void socket_down(ErlNifEnv* env,
+ void* obj,
+ const ErlNifPid* pid,
+ const ErlNifMonitor* mon)
+{
+#if !defined(__WIN32__)
+ SocketDescriptor* descP = (SocketDescriptor*) obj;
+ int sres;
+
+ SSDBG( descP, ("SOCKET", "socket_down -> entry with"
+ "\r\n sock: %d"
+ "\r\n pid: %T"
+ "\r\n Close: %s (%s)"
+ "\r\n",
+ descP->sock, *pid,
+ B2S(IS_CLOSED(descP)),
+ B2S(IS_CLOSING(descP))) );
+
+ if (!IS_CLOSED(descP)) {
+
+ if (compare_pids(env, &descP->ctrlPid, pid)) {
+
+ /* We don't bother with the queue cleanup here -
+ * we leave it to the stop callback function.
+ */
+
+ SSDBG( descP,
+ ("SOCKET", "socket_down -> controlling process exit\r\n") );
+
+ descP->state = SOCKET_STATE_CLOSING;
+ descP->closeLocal = TRUE;
+ descP->closerPid = *pid;
+ MON_INIT(&descP->closerMon);
+
+ sres = esock_select_stop(env, descP->sock, descP);
+
+ if (sres & ERL_NIF_SELECT_STOP_CALLED) {
+
+ /* We are done - we can finalize (socket close) directly */
+ SSDBG( descP,
+ ("SOCKET",
+ "socket_down -> [%d] stop called\r\n", descP->sock) );
+
+ dec_socket(descP->domain, descP->type, descP->protocol);
+ descP->state = SOCKET_STATE_CLOSED;
+
+ /* And finally close the socket.
+ * Since we close the socket because of an exiting owner,
+ * we do not need to wait for buffers to sync (linger).
+ * If the owner wish to ensure the buffer are written,
+ * it should have closed the socket explicitly...
+ */
+ if (sock_close(descP->sock) != 0) {
+ int save_errno = sock_errno();
+
+ esock_warning_msg("Failed closing socket for terminating "
+ "controlling process: "
+ "\r\n Controlling Process: %T"
+ "\r\n Descriptor: %d"
+ "\r\n Errno: %d"
+ "\r\n", pid, descP->sock, save_errno);
+ }
+ sock_close_event(descP->event);
+
+ descP->sock = INVALID_SOCKET;
+ descP->event = INVALID_EVENT;
+
+ descP->state = SOCKET_STATE_CLOSED;
+
+ } else if (sres & ERL_NIF_SELECT_STOP_SCHEDULED) {
+
+ /* The stop callback function has been *scheduled* which means
+ * that "should" wait for it to complete. But since we are in
+ * a callback (down) function, we cannot...
+ * So, we must close the socket
+ */
+ SSDBG( descP,
+ ("SOCKET",
+ "socket_down -> [%d] stop scheduled\r\n",
+ descP->sock) );
+
+ dec_socket(descP->domain, descP->type, descP->protocol);
+
+ /* And now what? We can't wait for the stop function here...
+ * So, we simply close it here and leave the rest of the "close"
+ * for later (when the stop function actually gets called...
+ */
+
+ if (sock_close(descP->sock) != 0) {
+ int save_errno = sock_errno();
+
+ esock_warning_msg("Failed closing socket for terminating "
+ "controlling process: "
+ "\r\n Controlling Process: %T"
+ "\r\n Descriptor: %d"
+ "\r\n Errno: %d"
+ "\r\n", pid, descP->sock, save_errno);
+ }
+ sock_close_event(descP->event);
+
+ } else {
+
+ esock_warning_msg("Failed selecting stop when handling down "
+ "of controlling process: "
+ "\r\n Select Res: %d"
+ "\r\n Controlling Process: %T"
+ "\r\n Descriptor: %d"
+ "\r\n Monitor: %T"
+ "\r\n", sres, pid, descP->sock,
+ my_make_monitor_term(env, mon));
+ }
+
+ } else {
+
+ /* check all operation queue(s): acceptor, writer and reader.
+ *
+ * Is it really any point in doing this if the socket is closed?
+ *
+ */
+
+ SSDBG( descP, ("SOCKET", "socket_down -> other process term\r\n") );
+
+ MLOCK(descP->accMtx);
+ if (descP->currentAcceptorP != NULL)
+ socket_down_acceptor(env, descP, pid);
+ MUNLOCK(descP->accMtx);
+
+ MLOCK(descP->writeMtx);
+ if (descP->currentWriterP != NULL)
+ socket_down_writer(env, descP, pid);
+ MUNLOCK(descP->writeMtx);
+
+ MLOCK(descP->readMtx);
+ if (descP->currentReaderP != NULL)
+ socket_down_reader(env, descP, pid);
+ MUNLOCK(descP->readMtx);
+
+ }
+ }
+
+ SSDBG( descP, ("SOCKET", "socket_down -> done\r\n") );
+
+#endif // if !defined(__WIN32__)
+}
+
+
+
+/* *** socket_down_acceptor ***
+ *
+ * Check and then handle a downed acceptor process.
+ *
+ */
+#if !defined(__WIN32__)
+static
+void socket_down_acceptor(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ const ErlNifPid* pid)
+{
+ if (compare_pids(env, &descP->currentAcceptor.pid, pid)) {
+
+ SSDBG( descP, ("SOCKET",
+ "socket_down_acceptor -> "
+ "current acceptor - try pop the queue\r\n") );
+
+ if (acceptor_pop(env, descP,
+ &descP->currentAcceptor.pid,
+ &descP->currentAcceptor.mon,
+ &descP->currentAcceptor.ref)) {
+ int res;
+
+ /* There was another one, so we will still be in accepting state */
+
+ SSDBG( descP, ("SOCKET",
+ "socket_down_acceptor -> new (active) acceptor: "
+ "\r\n pid: %T"
+ "\r\n ref: %T"
+ "\r\n",
+ descP->currentAcceptor.pid,
+ descP->currentAcceptor.ref) );
+
+ if ((res = esock_select_read(env, descP->sock, descP,
+ &descP->currentAcceptor.pid,
+ descP->currentAcceptor.ref) < 0)) {
+
+ esock_warning_msg("Failed select (%d) for new acceptor "
+ "after current (%T) died\r\n",
+ res, *pid);
+
+ }
+
+ } else {
+
+ SSDBG( descP, ("SOCKET",
+ "socket_down_acceptor -> no active acceptor\r\n") );
+
+ descP->currentAcceptorP = NULL;
+ descP->state = SOCKET_STATE_LISTENING;
+ }
+
+ } else {
+
+ /* Maybe unqueue one of the waiting acceptors */
+
+ SSDBG( descP, ("SOCKET",
+ "socket_down_acceptor -> "
+ "not current acceptor - maybe a waiting acceptor\r\n") );
+
+ acceptor_unqueue(env, descP, pid);
+ }
+}
+
+
+
+
+/* *** socket_down_writer ***
+ *
+ * Check and then handle a downed writer process.
+ *
+ */
+static
+void socket_down_writer(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ const ErlNifPid* pid)
+{
+ if (compare_pids(env, &descP->currentWriter.pid, pid)) {
+
+ SSDBG( descP, ("SOCKET",
+ "socket_down_writer -> "
+ "current writer - try pop the queue\r\n") );
+
+ if (writer_pop(env, descP,
+ &descP->currentWriter.pid,
+ &descP->currentWriter.mon,
+ &descP->currentWriter.ref)) {
+ int res;
+
+ /* There was another one */
+
+ SSDBG( descP, ("SOCKET", "socket_down_writer -> new (current) writer: "
+ "\r\n pid: %T"
+ "\r\n ref: %T"
+ "\r\n",
+ descP->currentWriter.pid,
+ descP->currentWriter.ref) );
+
+ if ((res = esock_select_write(env, descP->sock, descP,
+ &descP->currentWriter.pid,
+ descP->currentWriter.ref) < 0)) {
+
+ esock_warning_msg("Failed select (%d) for new writer "
+ "after current (%T) died\r\n",
+ res, *pid);
+
+ }
+
+ } else {
+
+ SSDBG( descP, ("SOCKET",
+ "socket_down_writer -> no active writer\r\n") );
+
+ descP->currentWriterP = NULL;
+ }
+
+ } else {
+
+ /* Maybe unqueue one of the waiting writer(s) */
+
+ SSDBG( descP, ("SOCKET",
+ "socket_down_writer -> "
+ "not current writer - maybe a waiting writer\r\n") );
+
+ writer_unqueue(env, descP, pid);
+ }
+}
+
+
+
+
+/* *** socket_down_reader ***
+ *
+ * Check and then handle a downed reader process.
+ *
+ */
+static
+void socket_down_reader(ErlNifEnv* env,
+ SocketDescriptor* descP,
+ const ErlNifPid* pid)
+{
+ if (compare_pids(env, &descP->currentReader.pid, pid)) {
+
+ SSDBG( descP, ("SOCKET",
+ "socket_down_reader -> "
+ "current reader - try pop the queue\r\n") );
+
+ if (reader_pop(env, descP,
+ &descP->currentReader.pid,
+ &descP->currentReader.mon,
+ &descP->currentReader.ref)) {
+ int res;
+
+ /* There was another one */
+
+ SSDBG( descP, ("SOCKET", "socket_down_reader -> new (current) reader: "
+ "\r\n pid: %T"
+ "\r\n ref: %T"
+ "\r\n",
+ descP->currentReader.pid,
+ descP->currentReader.ref) );
+
+ if ((res = esock_select_read(env, descP->sock, descP,
+ &descP->currentReader.pid,
+ descP->currentReader.ref) < 0)) {
+
+ esock_warning_msg("Failed select (%d) for new reader "
+ "after current (%T) died\r\n",
+ res, *pid);
+
+ }
+
+ } else {
+
+ SSDBG( descP, ("SOCKET",
+ "socket_down_reader -> no active reader\r\n") );
+
+ descP->currentReaderP = NULL;
+ }
+
+ } else {
+
+ /* Maybe unqueue one of the waiting reader(s) */
+
+ SSDBG( descP, ("SOCKET",
+ "socket_down_reader -> "
+ "not current reader - maybe a waiting reader\r\n") );
+
+ reader_unqueue(env, descP, pid);
+ }
+}
+#endif // if !defined(__WIN32__)
+
+
+
+/* ----------------------------------------------------------------------
+ * L o a d / u n l o a d / u p g r a d e F u n c t i o n s
+ * ----------------------------------------------------------------------
+ */
+
+static
+ErlNifFunc socket_funcs[] =
+{
+ // Some utility and support functions
+ {"nif_info", 0, nif_info, 0},
+ {"nif_supports", 1, nif_supports, 0},
+ // {"nif_debug", 1, nif_debug, 0},
+ // {"nif_command", 1, nif_command, 0},
+
+ // The proper "socket" interface
+ // nif_open/1 is used when we already have a file descriptor
+ // {"nif_open", 1, nif_open, 0},
+ {"nif_open", 4, nif_open, 0},
+ {"nif_bind", 2, nif_bind, 0},
+ {"nif_connect", 2, nif_connect, 0},
+ {"nif_listen", 2, nif_listen, 0},
+ {"nif_accept", 2, nif_accept, 0},
+ {"nif_send", 4, nif_send, 0},
+ {"nif_sendto", 5, nif_sendto, 0},
+ {"nif_sendmsg", 4, nif_sendmsg, 0},
+ {"nif_recv", 4, nif_recv, 0},
+ {"nif_recvfrom", 4, nif_recvfrom, 0},
+ {"nif_recvmsg", 5, nif_recvmsg, 0},
+ {"nif_close", 1, nif_close, 0},
+ {"nif_shutdown", 2, nif_shutdown, 0},
+ {"nif_setopt", 5, nif_setopt, 0},
+ {"nif_getopt", 4, nif_getopt, 0},
+ {"nif_sockname", 1, nif_sockname, 0},
+ {"nif_peername", 1, nif_peername, 0},
+
+ /* Misc utility functions */
+
+ /* "Extra" functions to "complete" the socket interface.
+ * For instance, the function nif_finalize_connection
+ * is called after the connect *select* has "completed".
+ */
+ {"nif_finalize_connection", 1, nif_finalize_connection, 0},
+ {"nif_cancel", 3, nif_cancel, 0},
+ {"nif_finalize_close", 1, nif_finalize_close, ERL_NIF_DIRTY_JOB_IO_BOUND}
+};
+
+
+#if !defined(__WIN32__)
+static
+BOOLEAN_T extract_debug(ErlNifEnv* env,
+ ERL_NIF_TERM map)
+{
+ /*
+ * We need to do this here since the "proper" atom has not been
+ * created when this function is called.
+ */
+ ERL_NIF_TERM debug = MKA(env, "debug");
+
+ return esock_extract_bool_from_map(env, map, debug, SOCKET_GLOBAL_DEBUG_DEFAULT);
+}
+
+static
+BOOLEAN_T extract_iow(ErlNifEnv* env,
+ ERL_NIF_TERM map)
+{
+ /*
+ * We need to do this here since the "proper" atom has not been
+ * created when this function is called.
+ */
+ ERL_NIF_TERM iow = MKA(env, "iow");
+
+ return esock_extract_bool_from_map(env, map, iow, SOCKET_NIF_IOW_DEFAULT);
+}
+#endif // if !defined(__WIN32__)
+
+
+
+/* =======================================================================
+ * load_info - A map of misc info (e.g global debug)
+ */
+
+static
+int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
+{
+#if !defined(__WIN32__)
+ esock_dbg_init(ESOCK_DBGOUT_DEFAULT);
+ // esock_dbg_init(ESOCK_DBGOUT_UNIQUE);
+
+ data.dbg = extract_debug(env, load_info);
+ data.iow = extract_iow(env, load_info);
+
+ /* +++ Global Counters +++ */
+ data.cntMtx = MCREATE("socket[gcnt]");
+ data.numSockets = 0;
+ data.numTypeDGrams = 0;
+ data.numTypeStreams = 0;
+ data.numTypeSeqPkgs = 0;
+ data.numDomainLocal = 0;
+ data.numDomainInet = 0;
+ data.numDomainInet6 = 0;
+ data.numProtoIP = 0;
+ data.numProtoTCP = 0;
+ data.numProtoUDP = 0;
+ data.numProtoSCTP = 0;
+#endif
+
+ /* +++ Misc atoms +++ */
+ atom_adaptation_layer = MKA(env, str_adaptation_layer);
+ atom_address = MKA(env, str_address);
+ atom_association = MKA(env, str_association);
+ atom_assoc_id = MKA(env, str_assoc_id);
+ atom_authentication = MKA(env, str_authentication);
+ atom_bool = MKA(env, str_bool);
+ atom_close = MKA(env, str_close);
+ atom_closed = MKA(env, str_closed);
+ atom_closing = MKA(env, str_closing);
+ atom_cookie_life = MKA(env, str_cookie_life);
+ atom_data_in = MKA(env, str_data_in);
+ atom_do = MKA(env, str_do);
+ atom_dont = MKA(env, str_dont);
+ atom_exclude = MKA(env, str_exclude);
+ atom_false = MKA(env, str_false);
+ atom_global_counters = MKA(env, str_global_counters);
+ atom_in4_sockaddr = MKA(env, str_in4_sockaddr);
+ atom_in6_sockaddr = MKA(env, str_in6_sockaddr);
+ atom_include = MKA(env, str_include);
+ atom_initial = MKA(env, str_initial);
+ atom_int = MKA(env, str_int);
+ atom_interface = MKA(env, str_interface);
+ atom_iow = MKA(env, str_iow);
+ atom_local_rwnd = MKA(env, str_local_rwnd);
+ atom_max = MKA(env, str_max);
+ atom_max_attempts = MKA(env, str_max_attempts);
+ atom_max_init_timeo = MKA(env, str_max_init_timeo);
+ atom_max_instreams = MKA(env, str_max_instreams);
+ atom_max_rxt = MKA(env, str_max_rxt);
+ atom_min = MKA(env, str_min);
+ atom_mode = MKA(env, str_mode);
+ atom_multiaddr = MKA(env, str_multiaddr);
+ // atom_nif_abort = MKA(env, str_nif_abort);
+ atom_null = MKA(env, str_null);
+ atom_num_dinet = MKA(env, str_num_dinet);
+ atom_num_dinet6 = MKA(env, str_num_dinet6);
+ atom_num_dlocal = MKA(env, str_num_dlocal);
+ atom_num_outstreams = MKA(env, str_num_outstreams);
+ atom_num_peer_dests = MKA(env, str_num_peer_dests);
+ atom_num_pip = MKA(env, str_num_pip);
+ atom_num_psctp = MKA(env, str_num_psctp);
+ atom_num_ptcp = MKA(env, str_num_ptcp);
+ atom_num_pudp = MKA(env, str_num_pudp);
+ atom_num_sockets = MKA(env, str_num_sockets);
+ atom_num_tdgrams = MKA(env, str_num_tdgrams);
+ atom_num_tseqpkgs = MKA(env, str_num_tseqpkgs);
+ atom_num_tstreams = MKA(env, str_num_tstreams);
+ atom_partial_delivery = MKA(env, str_partial_delivery);
+ atom_peer_rwnd = MKA(env, str_peer_rwnd);
+ atom_peer_error = MKA(env, str_peer_error);
+ atom_probe = MKA(env, str_probe);
+ atom_select = MKA(env, str_select);
+ atom_sender_dry = MKA(env, str_sender_dry);
+ atom_send_failure = MKA(env, str_send_failure);
+ atom_shutdown = MKA(env, str_shutdown);
+ atom_slist = MKA(env, str_slist);
+ atom_sourceaddr = MKA(env, str_sourceaddr);
+ atom_timeout = MKA(env, str_timeout);
+ atom_true = MKA(env, str_true);
+ atom_want = MKA(env, str_want);
+
+ /* Global atom(s) */
+ esock_atom_abort = MKA(env, "abort");
+ esock_atom_accept = MKA(env, "accept");
+ esock_atom_acceptconn = MKA(env, "acceptconn");
+ esock_atom_acceptfilter = MKA(env, "acceptfilter");
+ esock_atom_adaption_layer = MKA(env, "adaption_layer");
+ esock_atom_addr = MKA(env, "addr");
+ esock_atom_addrform = MKA(env, "addrform");
+ esock_atom_add_membership = MKA(env, "add_membership");
+ esock_atom_add_source_membership = MKA(env, "add_source_membership");
+ esock_atom_any = MKA(env, "any");
+ esock_atom_associnfo = MKA(env, "associnfo");
+ esock_atom_authhdr = MKA(env, "authhdr");
+ esock_atom_auth_active_key = MKA(env, "auth_active_key");
+ esock_atom_auth_asconf = MKA(env, "auth_asconf");
+ esock_atom_auth_chunk = MKA(env, "auth_chunk");
+ esock_atom_auth_delete_key = MKA(env, "auth_delete_key");
+ esock_atom_auth_key = MKA(env, "auth_key");
+ esock_atom_auth_level = MKA(env, "auth_level");
+ esock_atom_autoclose = MKA(env, "autoclose");
+ esock_atom_bindtodevice = MKA(env, "bindtodevice");
+ esock_atom_block_source = MKA(env, "block_source");
+ esock_atom_broadcast = MKA(env, "broadcast");
+ esock_atom_busy_poll = MKA(env, "busy_poll");
+ esock_atom_checksum = MKA(env, "checksum");
+ esock_atom_close = MKA(env, "close");
+ esock_atom_connect = MKA(env, "connect");
+ esock_atom_congestion = MKA(env, "congestion");
+ esock_atom_context = MKA(env, "context");
+ esock_atom_cork = MKA(env, "cork");
+ esock_atom_credentials = MKA(env, "credentials");
+ esock_atom_ctrl = MKA(env, "ctrl");
+ esock_atom_ctrunc = MKA(env, "ctrunc");
+ esock_atom_data = MKA(env, "data");
+ esock_atom_debug = MKA(env, "debug");
+ esock_atom_default_send_params = MKA(env, "default_send_params");
+ esock_atom_delayed_ack_time = MKA(env, "delayed_ack_time");
+ esock_atom_dgram = MKA(env, "dgram");
+ esock_atom_disable_fragments = MKA(env, "disable_fragments");
+ esock_atom_domain = MKA(env, "domain");
+ esock_atom_dontfrag = MKA(env, "dontfrag");
+ esock_atom_dontroute = MKA(env, "dontroute");
+ esock_atom_drop_membership = MKA(env, "drop_membership");
+ esock_atom_drop_source_membership = MKA(env, "drop_source_membership");
+ esock_atom_dstopts = MKA(env, "dstpopts");
+ esock_atom_eor = MKA(env, "eor");
+ esock_atom_error = MKA(env, "error");
+ esock_atom_errqueue = MKA(env, "errqueue");
+ esock_atom_esp_network_level = MKA(env, "esp_network_level");
+ esock_atom_esp_trans_level = MKA(env, "esp_trans_level");
+ esock_atom_events = MKA(env, "events");
+ esock_atom_explicit_eor = MKA(env, "explicit_eor");
+ esock_atom_faith = MKA(env, "faith");
+ esock_atom_false = MKA(env, "false");
+ esock_atom_family = MKA(env, "family");
+ esock_atom_flags = MKA(env, "flags");
+ esock_atom_flowinfo = MKA(env, "flowinfo");
+ esock_atom_fragment_interleave = MKA(env, "fragment_interleave");
+ esock_atom_freebind = MKA(env, "freebind");
+ esock_atom_get_peer_addr_info = MKA(env, "get_peer_addr_info");
+ esock_atom_hdrincl = MKA(env, "hdrincl");
+ esock_atom_hmac_ident = MKA(env, "hmac_ident");
+ esock_atom_hoplimit = MKA(env, "hoplimit");
+ esock_atom_hopopts = MKA(env, "hopopts");
+ esock_atom_ifindex = MKA(env, "ifindex");
+ esock_atom_inet = MKA(env, "inet");
+ esock_atom_inet6 = MKA(env, "inet6");
+ esock_atom_info = MKA(env, "info");
+ esock_atom_initmsg = MKA(env, "initmsg");
+ esock_atom_iov = MKA(env, "iov");
+ esock_atom_ip = MKA(env, "ip");
+ esock_atom_ipcomp_level = MKA(env, "ipcomp_level");
+ esock_atom_ipv6 = MKA(env, "ipv6");
+ esock_atom_i_want_mapped_v4_addr = MKA(env, "i_want_mapped_v4_addr");
+ esock_atom_join_group = MKA(env, "join_group");
+ esock_atom_keepalive = MKA(env, "keepalive");
+ esock_atom_keepcnt = MKA(env, "keepcnt");
+ esock_atom_keepidle = MKA(env, "keepidle");
+ esock_atom_keepintvl = MKA(env, "keepintvl");
+ esock_atom_leave_group = MKA(env, "leave_group");
+ esock_atom_level = MKA(env, "level");
+ esock_atom_linger = MKA(env, "linger");
+ esock_atom_local = MKA(env, "local");
+ esock_atom_local_auth_chunks = MKA(env, "local_auth_chunks");
+ esock_atom_loopback = MKA(env, "loopback");
+ esock_atom_lowdelay = MKA(env, "lowdelay");
+ esock_atom_mark = MKA(env, "mark");
+ esock_atom_maxburst = MKA(env, "maxburst");
+ esock_atom_maxseg = MKA(env, "maxseg");
+ esock_atom_md5sig = MKA(env, "md5sig");
+ esock_atom_mincost = MKA(env, "mincost");
+ esock_atom_minttl = MKA(env, "minttl");
+ esock_atom_msfilter = MKA(env, "msfilter");
+ esock_atom_mtu = MKA(env, "mtu");
+ esock_atom_mtu_discover = MKA(env, "mtu_discover");
+ esock_atom_multicast_all = MKA(env, "multicast_all");
+ esock_atom_multicast_hops = MKA(env, "multicast_hops");
+ esock_atom_multicast_if = MKA(env, "multicast_if");
+ esock_atom_multicast_loop = MKA(env, "multicast_loop");
+ esock_atom_multicast_ttl = MKA(env, "multicast_ttl");
+ esock_atom_nodefrag = MKA(env, "nodefrag");
+ esock_atom_nodelay = MKA(env, "nodelay");
+ esock_atom_noopt = MKA(env, "noopt");
+ esock_atom_nopush = MKA(env, "nopush");
+ esock_atom_not_found = MKA(env, "not_found");
+ esock_atom_not_owner = MKA(env, "not_owner");
+ esock_atom_ok = MKA(env, "ok");
+ esock_atom_oob = MKA(env, "oob");
+ esock_atom_oobinline = MKA(env, "oobinline");
+ esock_atom_options = MKA(env, "options");
+ esock_atom_origdstaddr = MKA(env, "origdstaddr");
+ esock_atom_partial_delivery_point = MKA(env, "partial_delivery_point");
+ esock_atom_passcred = MKA(env, "passcred");
+ esock_atom_path = MKA(env, "path");
+ esock_atom_peekcred = MKA(env, "peekcred");
+ esock_atom_peek_off = MKA(env, "peek_off");
+ esock_atom_peer_addr_params = MKA(env, "peer_addr_params");
+ esock_atom_peer_auth_chunks = MKA(env, "peer_auth_chunks");
+ esock_atom_pktinfo = MKA(env, "pktinfo");
+ esock_atom_pktoptions = MKA(env, "pktoptions");
+ esock_atom_port = MKA(env, "port");
+ esock_atom_portrange = MKA(env, "portrange");
+ esock_atom_primary_addr = MKA(env, "primary_addr");
+ esock_atom_priority = MKA(env, "priority");
+ esock_atom_protocol = MKA(env, "protocol");
+ esock_atom_raw = MKA(env, "raw");
+ esock_atom_rcvbuf = MKA(env, "rcvbuf");
+ esock_atom_rcvbufforce = MKA(env, "rcvbufforce");
+ esock_atom_rcvlowat = MKA(env, "rcvlowat");
+ esock_atom_rcvtimeo = MKA(env, "rcvtimeo");
+ esock_atom_rdm = MKA(env, "rdm");
+ esock_atom_recv = MKA(env, "recv");
+ esock_atom_recvdstaddr = MKA(env, "recvdstaddr");
+ esock_atom_recverr = MKA(env, "recverr");
+ esock_atom_recvfrom = MKA(env, "recvfrom");
+ esock_atom_recvif = MKA(env, "recvif");
+ esock_atom_recvmsg = MKA(env, "recvmsg");
+ esock_atom_recvopts = MKA(env, "recvopts");
+ esock_atom_recvorigdstaddr = MKA(env, "recvorigdstaddr");
+ esock_atom_recvpktinfo = MKA(env, "recvpktinfo");
+ esock_atom_recvtclass = MKA(env, "recvtclass");
+ esock_atom_recvtos = MKA(env, "recvtos");
+ esock_atom_recvttl = MKA(env, "recvttl");
+ esock_atom_reliability = MKA(env, "reliability");
+ esock_atom_reset_streams = MKA(env, "reset_streams");
+ esock_atom_retopts = MKA(env, "retopts");
+ esock_atom_reuseaddr = MKA(env, "reuseaddr");
+ esock_atom_reuseport = MKA(env, "reuseport");
+ esock_atom_rights = MKA(env, "rights");
+ esock_atom_router_alert = MKA(env, "router_alert");
+ esock_atom_rthdr = MKA(env, "rthdr");
+ esock_atom_rtoinfo = MKA(env, "rtoinfo");
+ esock_atom_rxq_ovfl = MKA(env, "rxq_ovfl");
+ esock_atom_scope_id = MKA(env, "scope_id");
+ esock_atom_sctp = MKA(env, "sctp");
+ esock_atom_sec = MKA(env, "sec");
+ esock_atom_select_failed = MKA(env, "select_failed");
+ esock_atom_select_sent = MKA(env, "select_sent");
+ esock_atom_send = MKA(env, "send");
+ esock_atom_sendmsg = MKA(env, "sendmsg");
+ esock_atom_sendsrcaddr = MKA(env, "sendsrcaddr");
+ esock_atom_sendto = MKA(env, "sendto");
+ esock_atom_seqpacket = MKA(env, "seqpacket");
+ esock_atom_setfib = MKA(env, "setfib");
+ esock_atom_set_peer_primary_addr = MKA(env, "set_peer_primary_addr");
+ esock_atom_sndbuf = MKA(env, "sndbuf");
+ esock_atom_sndbufforce = MKA(env, "sndbufforce");
+ esock_atom_sndlowat = MKA(env, "sndlowat");
+ esock_atom_sndtimeo = MKA(env, "sndtimeo");
+ esock_atom_socket = MKA(env, "socket");
+ esock_atom_socket_tag = MKA(env, "$socket");
+ esock_atom_spec_dst = MKA(env, "spec_dst");
+ esock_atom_status = MKA(env, "status");
+ esock_atom_stream = MKA(env, "stream");
+ esock_atom_syncnt = MKA(env, "syncnt");
+ esock_atom_tclass = MKA(env, "tclass");
+ esock_atom_tcp = MKA(env, "tcp");
+ esock_atom_throughput = MKA(env, "throughput");
+ esock_atom_timestamp = MKA(env, "timestamp");
+ esock_atom_tos = MKA(env, "tos");
+ esock_atom_transparent = MKA(env, "transparent");
+ esock_atom_true = MKA(env, "true");
+ esock_atom_trunc = MKA(env, "trunc");
+ esock_atom_ttl = MKA(env, "ttl");
+ esock_atom_type = MKA(env, "type");
+ esock_atom_udp = MKA(env, "udp");
+ esock_atom_unblock_source = MKA(env, "unblock_source");
+ esock_atom_undefined = MKA(env, "undefined");
+ esock_atom_unicast_hops = MKA(env, "unicast_hops");
+ esock_atom_unknown = MKA(env, "unknown");
+ esock_atom_usec = MKA(env, "usec");
+ esock_atom_user_timeout = MKA(env, "user_timeout");
+ esock_atom_use_ext_recvinfo = MKA(env, "use_ext_recvinfo");
+ esock_atom_use_min_mtu = MKA(env, "use_min_mtu");
+ esock_atom_v6only = MKA(env, "v6only");
+
+ /* Global error codes */
+ esock_atom_eafnosupport = MKA(env, ESOCK_STR_EAFNOSUPPORT);
+ esock_atom_eagain = MKA(env, ESOCK_STR_EAGAIN);
+ esock_atom_einval = MKA(env, ESOCK_STR_EINVAL);
+
+ /* Error codes */
+ atom_eisconn = MKA(env, str_eisconn);
+ atom_enotclosing = MKA(env, str_enotclosing);
+ atom_enotconn = MKA(env, str_enotconn);
+ atom_exalloc = MKA(env, str_exalloc);
+ atom_exbadstate = MKA(env, str_exbadstate);
+ atom_exbusy = MKA(env, str_exbusy);
+ atom_exmon = MKA(env, str_exmon);
+ atom_exself = MKA(env, str_exself);
+ atom_exsend = MKA(env, str_exsend);
+
+ // For storing "global" things...
+ // data.env = enif_alloc_env(); // We should really check
+ // data.version = MKA(env, ERTS_VERSION);
+ // data.buildDate = MKA(env, ERTS_BUILD_DATE);
+
+ sockets = enif_open_resource_type_x(env,
+ "sockets",
+ &socketInit,
+ ERL_NIF_RT_CREATE,
+ NULL);
+
+ return !sockets;
+}
+
+ERL_NIF_INIT(socket, socket_funcs, on_load, NULL, NULL, NULL)
diff --git a/erts/emulator/nifs/common/socket_tarray.c b/erts/emulator/nifs/common/socket_tarray.c
new file mode 100644
index 0000000000..def22c4919
--- /dev/null
+++ b/erts/emulator/nifs/common/socket_tarray.c
@@ -0,0 +1,143 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2018-2019. All 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 : Build and "maintain" a (erlang) term array of
+ * variable length.
+ * ----------------------------------------------------------------------
+ *
+ */
+
+/* #ifdef HAVE_CONFIG_H */
+/* #include "config.h" */
+/* #endif */
+
+#include <stdio.h>
+
+#include <erl_nif.h>
+
+#include "socket_int.h"
+#include <sys.h>
+#include "socket_util.h"
+#include "socket_tarray.h"
+
+
+
+/* ----------------------------------------------------------------------
+ * Types
+ */
+
+typedef struct {
+ Uint32 sz;
+ Uint32 idx;
+ ERL_NIF_TERM* array;
+} SocketTArrayInt;
+
+
+/* ----------------------------------------------------------------------
+ * Forward for internal functions
+ */
+
+static void esock_tarray_add1(SocketTArrayInt* taP, ERL_NIF_TERM t);
+static void esock_tarray_ensure_fits(SocketTArrayInt* taP, Uint32 needs);
+
+
+/* ----------------------------------------------------------------------
+ * API
+ */
+
+extern
+void* esock_tarray_create(Uint32 sz)
+{
+ SocketTArrayInt* tarrayP;
+
+ ESOCK_ASSERT( (sz > 0) );
+
+ tarrayP = MALLOC(sizeof(SocketTArrayInt));
+ ESOCK_ASSERT( (tarrayP != NULL) );
+
+ tarrayP->array = MALLOC(sz * sizeof(ERL_NIF_TERM));
+ ESOCK_ASSERT( (tarrayP->array != NULL) );
+ tarrayP->sz = sz;
+ tarrayP->idx = 0;
+
+ return ((SocketTArray) tarrayP);
+}
+
+extern
+void esock_tarray_delete(SocketTArray ta)
+{
+ SocketTArrayInt* taP = (SocketTArrayInt*) ta;
+
+ FREE(taP->array);
+ FREE(taP);
+}
+
+
+extern
+Uint32 esock_tarray_sz(SocketTArray a)
+{
+ return ( ((SocketTArrayInt*) a)->idx );
+}
+
+extern
+void esock_tarray_add(SocketTArray ta, ERL_NIF_TERM t)
+{
+ esock_tarray_add1((SocketTArrayInt*) ta, t);
+}
+
+extern
+void esock_tarray_tolist(SocketTArray ta,
+ ErlNifEnv* env,
+ ERL_NIF_TERM* list)
+{
+ SocketTArrayInt* taP = (SocketTArrayInt*) ta;
+
+ *list = MKLA(env, taP->array, taP->idx);
+
+ esock_tarray_delete(taP);
+}
+
+
+
+/* ----------------------------------------------------------------------
+ * "Internal" functions
+ */
+
+static
+void esock_tarray_add1(SocketTArrayInt* taP, ERL_NIF_TERM t)
+{
+ esock_tarray_ensure_fits(taP, 1);
+
+ taP->array[taP->idx++] = t;
+}
+
+static
+void esock_tarray_ensure_fits(SocketTArrayInt* taP, Uint32 needs)
+{
+ if (taP->sz < (taP->idx + needs)) {
+ Uint32 newSz = (needs < taP->sz) ? 2*taP->sz : 2*needs;
+ void* mem = REALLOC(taP->array, newSz * sizeof(ERL_NIF_TERM));
+
+ ESOCK_ASSERT( (mem != NULL) );
+
+ taP->sz = newSz;
+ taP->array = (ERL_NIF_TERM*) mem;
+ }
+}
diff --git a/erts/emulator/nifs/common/socket_tarray.h b/erts/emulator/nifs/common/socket_tarray.h
new file mode 100644
index 0000000000..4f1152fb9e
--- /dev/null
+++ b/erts/emulator/nifs/common/socket_tarray.h
@@ -0,0 +1,47 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2018-2019. All 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 : Build and "maintain" a (erlang) term array of
+ * variable length.
+ * ----------------------------------------------------------------------
+ *
+ */
+
+#ifndef SOCKET_TARRAY_H__
+#define SOCKET_TARRAY_H__
+
+typedef void* SocketTArray;
+
+extern SocketTArray esock_tarray_create(Uint32 sz);
+extern void esock_tarray_delete(SocketTArray ta);
+extern Uint32 esock_tarray_sz(SocketTArray ta);
+extern void esock_tarray_add(SocketTArray ta, ERL_NIF_TERM t);
+extern void esock_tarray_tolist(SocketTArray ta,
+ ErlNifEnv* env,
+ ERL_NIF_TERM* list);
+
+#define TARRAY_CREATE(SZ) esock_tarray_create((SZ))
+#define TARRAY_DELETE(TA) esock_tarray_delete((TA))
+#define TARRAY_SZ(TA) esock_tarray_sz((TA))
+#define TARRAY_ADD(TA, T) esock_tarray_add((TA), (T))
+#define TARRAY_TOLIST(TA, E, L) esock_tarray_tolist((TA), (E), (L))
+
+
+#endif // SOCKET_TARRAY_H__
diff --git a/erts/emulator/nifs/common/socket_util.c b/erts/emulator/nifs/common/socket_util.c
new file mode 100644
index 0000000000..f6e4781977
--- /dev/null
+++ b/erts/emulator/nifs/common/socket_util.c
@@ -0,0 +1,1658 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2018-2019. All 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 : Utility functions for the socket and net NIF(s).
+ * ----------------------------------------------------------------------
+ *
+ */
+
+#include <stdarg.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <time.h>
+#include <stddef.h>
+
+#include "socket_int.h"
+#include "sys.h"
+#include "socket_util.h"
+#include "socket_dbg.h"
+
+/* We don't have a "debug flag" to check here, so we
+ * should use the compile debug flag, whatever that is...
+ */
+
+// #define COMPILE_DEBUG_FLAG_WE_NEED_TO_CHECK 1
+#if defined(COMPILE_DEBUG_FLAG_WE_NEED_TO_CHECK)
+#define UTIL_DEBUG TRUE
+#else
+#define UTIL_DEBUG FALSE
+#endif
+
+#define UDBG( proto ) ESOCK_DBG_PRINTF( UTIL_DEBUG , proto )
+
+
+extern char* erl_errno_id(int error); /* THIS IS JUST TEMPORARY??? */
+
+static int realtime(struct timespec* tsP);
+static int timespec2str(char *buf, unsigned int len, struct timespec *ts);
+
+static char* make_sockaddr_in4(ErlNifEnv* env,
+ ERL_NIF_TERM port,
+ ERL_NIF_TERM addr,
+ ERL_NIF_TERM* sa);
+static char* make_sockaddr_in6(ErlNifEnv* env,
+ ERL_NIF_TERM port,
+ ERL_NIF_TERM addr,
+ ERL_NIF_TERM flowInfo,
+ ERL_NIF_TERM scopeId,
+ ERL_NIF_TERM* sa);
+static char* make_sockaddr_un(ErlNifEnv* env,
+ ERL_NIF_TERM path,
+ ERL_NIF_TERM* sa);
+
+
+/* +++ esock_encode_iov +++
+ *
+ * Encode an IO Vector. In erlang we represented this as a list of binaries.
+ *
+ * We iterate through the IO vector, and as long as the remaining (rem)
+ * number of bytes is greater than the size of the current buffer, we
+ * contunue. When we have a buffer that is greater than rem, we have found
+ * the last buffer (it may be empty, and then the previous was last).
+ * We may need to split this (if 0 < rem < bufferSz).
+ */
+
+extern
+char* esock_encode_iov(ErlNifEnv* env,
+ int read,
+ struct iovec* iov,
+ size_t len,
+ ErlNifBinary* data,
+ ERL_NIF_TERM* eIOV)
+{
+ int rem = read;
+ Uint16 i;
+ BOOLEAN_T done = FALSE;
+ ERL_NIF_TERM a[len]; // At most this length
+
+ UDBG( ("SUTIL", "esock_encode_iov -> entry with"
+ "\r\n read: %d"
+ "\r\n (IOV) len: %d"
+ "\r\n", read, len) );
+
+ if (len == 0) {
+ *eIOV = MKEL(env);
+ return NULL;
+ }
+
+ for (i = 0; (!done) && (i < len); i++) {
+ UDBG( ("SUTIL", "esock_encode_iov -> process iov:"
+ "\r\n iov[%d].iov_len: %d"
+ "\r\n rem: %d"
+ "\r\n", i, iov[i].iov_len, rem) );
+ if (iov[i].iov_len == rem) {
+ /* We have the exact amount - we are done */
+ UDBG( ("SUTIL", "esock_encode_iov -> exact => done\r\n") );
+ a[i] = MKBIN(env, &data[i]);
+ rem = 0; // Besserwisser
+ done = TRUE;
+ } else if (iov[i].iov_len < rem) {
+ /* Filled another buffer - continue */
+ UDBG( ("SUTIL", "esock_encode_iov -> filled => continue\r\n") );
+ a[i] = MKBIN(env, &data[i]);
+ rem -= iov[i].iov_len;
+ } else if (iov[i].iov_len > rem) {
+ /* Partly filled buffer (=> split) - we are done */
+ ERL_NIF_TERM tmp;
+ UDBG( ("SUTIL", "esock_encode_iov -> split => done\r\n") );
+ tmp = MKBIN(env, &data[i]);
+ a[i] = MKSBIN(env, tmp, 0, rem);
+ rem = 0; // Besserwisser
+ done = TRUE;
+ }
+ }
+
+ UDBG( ("SUTIL", "esock_encode_iov -> create the IOV list (%d)\r\n", i) );
+
+ *eIOV = MKLA(env, a, i);
+
+ UDBG( ("SUTIL", "esock_encode_msghdr -> done\r\n") );
+
+ return NULL;
+}
+
+
+
+/* +++ esock_decode_iov +++
+ *
+ * Decode an IO Vector. In erlang we represented this as a list of binaries.
+ *
+ * We assume that we have already figured out how long the iov (actually
+ * eIOV) is (len), and therefor allocated an array of bins and iov to be
+ * used.
+ */
+
+extern
+char* esock_decode_iov(ErlNifEnv* env,
+ ERL_NIF_TERM eIOV,
+ ErlNifBinary* bufs,
+ struct iovec* iov,
+ size_t len,
+ ssize_t* totSize)
+{
+ Uint16 i;
+ ssize_t sz;
+ ERL_NIF_TERM elem, tail, list;
+
+ UDBG( ("SUTIL", "esock_decode_iov -> entry with"
+ "\r\n (IOV) len: %d"
+ "\r\n", read, len) );
+
+ for (i = 0, list = eIOV, sz = 0; (i < len); i++) {
+
+ UDBG( ("SUTIL", "esock_decode_iov -> "
+ "\r\n iov[%d].iov_len: %d"
+ "\r\n rem: %d"
+ "\r\n", i) );
+
+ if (!GET_LIST_ELEM(env, list, &elem, &tail))
+ return ESOCK_STR_EINVAL;
+
+ if (IS_BIN(env, elem) && GET_BIN(env, elem, &bufs[i])) {
+ iov[i].iov_base = bufs[i].data;
+ iov[i].iov_len = bufs[i].size;
+ sz += bufs[i].size;
+ } else {
+ return ESOCK_STR_EINVAL;
+ }
+
+ list = tail;
+ }
+
+ *totSize = sz;
+
+ UDBG( ("SUTIL", "esock_decode_msghdr -> done (%d)\r\n", sz) );
+
+ return NULL;
+}
+
+
+
+/* +++ esock_decode_sockaddr +++
+ *
+ * Decode a socket address - sockaddr. In erlang its represented as
+ * a map, which has a specific set of attributes, depending on one
+ * mandatory attribute; family. So depending on the value of the family
+ * attribute:
+ *
+ * local - sockaddr_un: path
+ * inet - sockaddr_in4: port, addr
+ * inet6 - sockaddr_in6: port, addr, flowinfo, scope_id
+ */
+
+extern
+char* esock_decode_sockaddr(ErlNifEnv* env,
+ ERL_NIF_TERM eSockAddr,
+ SocketAddress* sockAddrP,
+ unsigned int* addrLen)
+{
+ ERL_NIF_TERM efam;
+ int fam;
+ char* xres;
+
+ UDBG( ("SUTIL", "esock_decode_sockaddr -> entry\r\n") );
+
+ if (!IS_MAP(env, eSockAddr))
+ return ESOCK_STR_EINVAL;
+
+ if (!GET_MAP_VAL(env, eSockAddr, esock_atom_family, &efam))
+ return ESOCK_STR_EINVAL;
+
+ UDBG( ("SUTIL", "esock_decode_sockaddr -> try decode domain (%T)\r\n", efam) );
+ if ((xres = esock_decode_domain(env, efam, &fam)) != NULL)
+ return xres;
+
+ UDBG( ("SUTIL", "esock_decode_sockaddr -> fam: %d\r\n", fam) );
+ switch (fam) {
+ case AF_INET:
+ xres = esock_decode_sockaddr_in4(env, eSockAddr,
+ &sockAddrP->in4, addrLen);
+ break;
+
+#if defined(HAVE_IN6) && defined(AF_INET6)
+ case AF_INET6:
+ xres = esock_decode_sockaddr_in6(env, eSockAddr,
+ &sockAddrP->in6, addrLen);
+ break;
+#endif
+
+#ifdef HAVE_SYS_UN_H
+ case AF_UNIX:
+ xres = esock_decode_sockaddr_un(env, eSockAddr,
+ &sockAddrP->un, addrLen);
+ break;
+#endif
+
+ default:
+ xres = ESOCK_STR_EAFNOSUPPORT;
+ break;
+
+ }
+
+ return xres;
+}
+
+
+
+/* +++ esock_encode_sockaddr +++
+ *
+ * Encode a socket address - sockaddr. In erlang its represented as
+ * a map, which has a specific set of attributes, depending on one
+ * mandatory attribute; family. So depending on the value of the family
+ * attribute:
+ *
+ * local - sockaddr_un: path
+ * inet - sockaddr_in4: port, addr
+ * inet6 - sockaddr_in6: port, addr, flowinfo, scope_id
+ */
+
+extern
+char* esock_encode_sockaddr(ErlNifEnv* env,
+ SocketAddress* sockAddrP,
+ unsigned int addrLen,
+ ERL_NIF_TERM* eSockAddr)
+{
+ char* xres;
+
+ UDBG( ("SUTIL", "esock_encode_sockaddr -> entry with"
+ "\r\n family: %d"
+ "\r\n addrLen: %d"
+ "\r\n", sockAddrP->sa.sa_family, addrLen) );
+
+ switch (sockAddrP->sa.sa_family) {
+ case AF_INET:
+ xres = esock_encode_sockaddr_in4(env, &sockAddrP->in4, addrLen, eSockAddr);
+ break;
+
+#if defined(HAVE_IN6) && defined(AF_INET6)
+ case AF_INET6:
+ xres = esock_encode_sockaddr_in6(env, &sockAddrP->in6, addrLen, eSockAddr);
+ break;
+#endif
+
+#ifdef HAVE_SYS_UN_H
+ case AF_UNIX:
+ xres = esock_encode_sockaddr_un(env, &sockAddrP->un, addrLen, eSockAddr);
+ break;
+#endif
+
+ default:
+ *eSockAddr = esock_atom_undefined;
+ xres = ESOCK_STR_EAFNOSUPPORT;
+ break;
+
+ }
+
+ return xres;
+}
+
+
+
+/* +++ esock_decode_sockaddr_in4 +++
+ *
+ * Decode a IPv4 socket address - sockaddr_in4. In erlang its represented as
+ * a map, which has a specific set of attributes (beside the mandatory family
+ * attribute, which is "inherited" from the "sockaddr" type):
+ *
+ * port :: port_numbber()
+ * addr :: ip4_address()
+ *
+ * The erlang module ensures that both of these has values exist, so there
+ * is no need for any elaborate error handling.
+ */
+
+extern
+char* esock_decode_sockaddr_in4(ErlNifEnv* env,
+ ERL_NIF_TERM eSockAddr,
+ struct sockaddr_in* sockAddrP,
+ unsigned int* addrLen)
+{
+ ERL_NIF_TERM eport, eaddr;
+ int port;
+ char* xres;
+
+ UDBG( ("SUTIL", "esock_decode_sockaddr_in4 -> entry\r\n") );
+
+ /* Basic init */
+ sys_memzero((char*) sockAddrP, sizeof(struct sockaddr_in));
+
+#ifndef NO_SA_LEN
+ sockAddrP->sin_len = sizeof(struct sockaddr_in);
+#endif
+
+ sockAddrP->sin_family = AF_INET;
+
+ /* Extract (e) port number from map */
+ UDBG( ("SUTIL", "esock_decode_sockaddr_in4 -> try get port number\r\n") );
+ if (!GET_MAP_VAL(env, eSockAddr, esock_atom_port, &eport))
+ return ESOCK_STR_EINVAL;
+
+ /* Decode port number */
+ UDBG( ("SUTIL", "esock_decode_sockaddr_in4 -> try decode port number\r\n") );
+ if (!GET_INT(env, eport, &port))
+ return ESOCK_STR_EINVAL;
+
+ sockAddrP->sin_port = htons(port);
+
+ /* Extract (e) address from map */
+ UDBG( ("SUTIL", "esock_decode_sockaddr_in4 -> try get (ip) address\r\n") );
+ if (!GET_MAP_VAL(env, eSockAddr, esock_atom_addr, &eaddr))
+ return ESOCK_STR_EINVAL;
+
+ /* Decode address */
+ UDBG( ("SUTIL", "esock_decode_sockaddr_in4 -> try decode (ip) address\r\n") );
+ if ((xres = esock_decode_ip4_address(env,
+ eaddr,
+ &sockAddrP->sin_addr)) != NULL)
+ return xres;
+
+ *addrLen = sizeof(struct sockaddr_in);
+
+ UDBG( ("SUTIL", "esock_decode_sockaddr_in4 -> done\r\n") );
+
+ return NULL;
+}
+
+
+
+/* +++ esock_encode_sockaddr_in4 +++
+ *
+ * Encode a IPv4 socket address - sockaddr_in4. In erlang its represented as
+ * a map, which has a specific set of attributes (beside the mandatory family
+ * attribute, which is "inherited" from the "sockaddr" type):
+ *
+ * port :: port_numbber()
+ * addr :: ip4_address()
+ *
+ */
+
+extern
+char* esock_encode_sockaddr_in4(ErlNifEnv* env,
+ struct sockaddr_in* sockAddrP,
+ unsigned int addrLen,
+ ERL_NIF_TERM* eSockAddr)
+{
+ ERL_NIF_TERM ePort, eAddr;
+ int port;
+ char* xres = NULL;
+
+ UDBG( ("SUTIL", "esock_encode_sockaddr_in4 -> entry\r\n") );
+
+ if (addrLen >= sizeof(struct sockaddr_in)) {
+ /* The port */
+ port = ntohs(sockAddrP->sin_port);
+ ePort = MKI(env, port);
+
+ /* The address */
+ if ((xres = esock_encode_ip4_address(env, &sockAddrP->sin_addr,
+ &eAddr)) == NULL) {
+ /* And finally construct the in4_sockaddr record */
+ xres = make_sockaddr_in4(env, ePort, eAddr, eSockAddr);
+ } else {
+ UDBG( ("SUTIL", "esock_encode_sockaddr_in4 -> "
+ "failed encoding (ip) address: "
+ "\r\n xres: %s"
+ "\r\n", xres) );
+ *eSockAddr = esock_atom_undefined;
+ xres = ESOCK_STR_EINVAL;
+ }
+
+ } else {
+ UDBG( ("SUTIL", "esock_encode_sockaddr_in4 -> wrong size: "
+ "\r\n addrLen: %d"
+ "\r\n addr size: %d"
+ "\r\n", addrLen, sizeof(struct sockaddr_in)) );
+ *eSockAddr = esock_atom_undefined;
+ xres = ESOCK_STR_EINVAL;
+ }
+
+ return xres;
+}
+
+
+
+/* +++ esock_decode_sockaddr_in6 +++
+ *
+ * Decode a IPv6 socket address - sockaddr_in6. In erlang its represented as
+ * a map, which has a specific set of attributes (beside the mandatory family
+ * attribute, which is "inherited" from the "sockaddr" type):
+ *
+ * port :: port_numbber() (integer)
+ * addr :: ip6_address() (tuple)
+ * flowinfo :: in6_flow_info() (integer)
+ * scope_id :: in6_scope_id() (integer)
+ *
+ * The erlang module ensures that all of these has values exist, so there
+ * is no need for any elaborate error handling here.
+ */
+
+#if defined(HAVE_IN6) && defined(AF_INET6)
+extern
+char* esock_decode_sockaddr_in6(ErlNifEnv* env,
+ ERL_NIF_TERM eSockAddr,
+ struct sockaddr_in6* sockAddrP,
+ unsigned int* addrLen)
+{
+ ERL_NIF_TERM eport, eaddr, eflowInfo, escopeId;
+ int port;
+ unsigned int flowInfo, scopeId;
+ char* xres;
+
+ UDBG( ("SUTIL", "esock_decode_sockaddr_in6 -> entry\r\n") );
+
+ /* Basic init */
+ sys_memzero((char*) sockAddrP, sizeof(struct sockaddr_in6));
+#ifndef NO_SA_LEN
+ sockAddrP->sin6_len = sizeof(struct sockaddr_in);
+#endif
+
+ sockAddrP->sin6_family = AF_INET6;
+
+ /* *** Extract (e) port number from map *** */
+ if (!GET_MAP_VAL(env, eSockAddr, esock_atom_port, &eport))
+ return ESOCK_STR_EINVAL;
+
+ /* Decode port number */
+ if (!GET_INT(env, eport, &port))
+ return ESOCK_STR_EINVAL;
+
+ UDBG( ("SUTIL", "esock_decode_sockaddr_in6 -> port: %d\r\n", port) );
+
+ sockAddrP->sin6_port = htons(port);
+
+ /* *** Extract (e) flowinfo from map *** */
+ if (!GET_MAP_VAL(env, eSockAddr, esock_atom_flowinfo, &eflowInfo))
+ return ESOCK_STR_EINVAL;
+
+ /* 4: Get the flowinfo */
+ if (!GET_UINT(env, eflowInfo, &flowInfo))
+ return ESOCK_STR_EINVAL;
+
+ UDBG( ("SUTIL", "esock_decode_sockaddr_in6 -> flowinfo: %d\r\n", flowInfo) );
+
+ sockAddrP->sin6_flowinfo = flowInfo;
+
+ /* *** Extract (e) scope_id from map *** */
+ if (!GET_MAP_VAL(env, eSockAddr, esock_atom_scope_id, &escopeId))
+ return ESOCK_STR_EINVAL;
+
+ /* *** Get the scope_id *** */
+ if (!GET_UINT(env, escopeId, &scopeId))
+ return ESOCK_STR_EINVAL;
+
+ UDBG( ("SUTIL", "esock_decode_sockaddr_in6 -> scopeId: %d\r\n", scopeId) );
+
+ sockAddrP->sin6_scope_id = scopeId;
+
+ /* *** Extract (e) address from map *** */
+ if (!GET_MAP_VAL(env, eSockAddr, esock_atom_addr, &eaddr))
+ return ESOCK_STR_EINVAL;
+
+ /* Decode address */
+ if ((xres = esock_decode_ip6_address(env,
+ eaddr,
+ &sockAddrP->sin6_addr)) != NULL)
+ return xres;
+
+ *addrLen = sizeof(struct sockaddr_in6);
+
+ UDBG( ("SUTIL", "esock_decode_sockaddr_in6 -> done\r\n") );
+
+ return NULL;
+}
+#endif
+
+
+
+/* +++ esock_encode_sockaddr_in6 +++
+ *
+ * Encode a IPv6 socket address - sockaddr_in6. In erlang its represented as
+ * a map, which has a specific set of attributes (beside the mandatory family
+ * attribute, which is "inherited" from the "sockaddr" type):
+ *
+ * port :: port_numbber() (integer)
+ * addr :: ip6_address() (tuple)
+ * flowinfo :: in6_flow_info() (integer)
+ * scope_id :: in6_scope_id() (integer)
+ *
+ */
+
+#if defined(HAVE_IN6) && defined(AF_INET6)
+extern
+char* esock_encode_sockaddr_in6(ErlNifEnv* env,
+ struct sockaddr_in6* sockAddrP,
+ unsigned int addrLen,
+ ERL_NIF_TERM* eSockAddr)
+{
+ ERL_NIF_TERM ePort, eAddr, eFlowInfo, eScopeId;
+ char* xres;
+
+ if (addrLen >= sizeof(struct sockaddr_in6)) {
+ /* The port */
+ ePort = MKI(env, ntohs(sockAddrP->sin6_port));
+
+ /* The flowInfo */
+ eFlowInfo = MKI(env, sockAddrP->sin6_flowinfo);
+
+ /* The scopeId */
+ eScopeId = MKI(env, sockAddrP->sin6_scope_id);
+
+ /* The address */
+ if ((xres = esock_encode_ip6_address(env, &sockAddrP->sin6_addr,
+ &eAddr)) == NULL) {
+ /* And finally construct the in6_sockaddr record */
+ xres = make_sockaddr_in6(env,
+ ePort, eAddr, eFlowInfo, eScopeId, eSockAddr);
+ } else {
+ *eSockAddr = esock_atom_undefined;
+ xres = ESOCK_STR_EINVAL;
+ }
+
+ } else {
+ *eSockAddr = esock_atom_undefined;
+ xres = ESOCK_STR_EINVAL;
+ }
+
+ return xres;
+}
+#endif
+
+
+
+/* +++ esock_decode_sockaddr_un +++
+ *
+ * Decode a Unix Domain socket address - sockaddr_un. In erlang its
+ * represented as a map, which has a specific set of attributes
+ * (beside the mandatory family attribute, which is "inherited" from
+ * the "sockaddr" type):
+ *
+ * path :: binary()
+ *
+ * The erlang module ensures that this value exist, so there
+ * is no need for any elaborate error handling here.
+ */
+
+#ifdef HAVE_SYS_UN_H
+extern
+char* esock_decode_sockaddr_un(ErlNifEnv* env,
+ ERL_NIF_TERM eSockAddr,
+ struct sockaddr_un* sockAddrP,
+ unsigned int* addrLen)
+{
+ ErlNifBinary bin;
+ ERL_NIF_TERM epath;
+ unsigned int len;
+
+ /* *** Extract (e) path (a binary) from map *** */
+ if (!GET_MAP_VAL(env, eSockAddr, esock_atom_path, &epath))
+ return ESOCK_STR_EINVAL;
+
+ /* Get the path */
+ if (!GET_BIN(env, epath, &bin))
+ return ESOCK_STR_EINVAL;
+
+ if ((bin.size +
+#ifdef __linux__
+ /* Make sure the address gets zero terminated
+ * except when the first byte is \0 because then it is
+ * sort of zero terminated although the zero termination
+ * comes before the address...
+ * This fix handles Linux's nonportable
+ * abstract socket address extension.
+ */
+ (bin.data[0] == '\0' ? 0 : 1)
+#else
+ 1
+#endif
+ ) > sizeof(sockAddrP->sun_path))
+ return ESOCK_STR_EINVAL;
+
+
+ sys_memzero((char*) sockAddrP, sizeof(struct sockaddr_un));
+ sockAddrP->sun_family = AF_UNIX;
+
+ sys_memcpy(sockAddrP->sun_path, bin.data, bin.size);
+ len = offsetof(struct sockaddr_un, sun_path) + bin.size;
+
+#ifndef NO_SA_LEN
+ sockAddrP->sun_len = len;
+#endif
+ *addrLen = len;
+
+ return NULL;
+}
+#endif
+
+
+
+/* +++ esock_encode_sockaddr_un +++
+ *
+ * Encode a Unix Domain socket address - sockaddr_un. In erlang its
+ * represented as a map, which has a specific set of attributes
+ * (beside the mandatory family attribute, which is "inherited" from
+ * the "sockaddr" type):
+ *
+ * path :: binary()
+ *
+ */
+
+#ifdef HAVE_SYS_UN_H
+extern
+char* esock_encode_sockaddr_un(ErlNifEnv* env,
+ struct sockaddr_un* sockAddrP,
+ unsigned int addrLen,
+ ERL_NIF_TERM* eSockAddr)
+{
+ ERL_NIF_TERM ePath;
+ size_t n, m;
+ char* xres;
+
+ if (addrLen >= offsetof(struct sockaddr_un, sun_path)) {
+ n = addrLen - offsetof(struct sockaddr_un, sun_path);
+ if (255 < n) {
+ *eSockAddr = esock_atom_undefined;
+ xres = ESOCK_STR_EINVAL;
+ } else {
+ m = esock_strnlen(sockAddrP->sun_path, n);
+#ifdef __linux__
+ /* Assume that the address is a zero terminated string,
+ * except when the first byte is \0 i.e the string length is 0,
+ * then use the reported length instead.
+ * This fix handles Linux's nonportable
+ * abstract socket address extension.
+ */
+ if (m == 0) {
+ m = n;
+ }
+#endif
+
+ /* And finally build the 'path' attribute */
+ ePath = MKSL(env, sockAddrP->sun_path, m);
+
+ /* And the socket address */
+ xres = make_sockaddr_un(env, ePath, eSockAddr);
+ }
+ } else {
+ *eSockAddr = esock_atom_undefined;
+ xres = ESOCK_STR_EINVAL;
+ }
+
+ return xres;
+}
+#endif
+
+
+
+/* +++ esock_decode_ip4_address +++
+ *
+ * Decode a IPv4 address. This can be three things:
+ *
+ * + Then atom 'any'
+ * + Then atom 'loopback'
+ * + An ip4_address() (4 tuple)
+ *
+ * Note that this *only* decodes the "address" part of a
+ * (IPv4) socket address.
+ */
+
+extern
+char* esock_decode_ip4_address(ErlNifEnv* env,
+ ERL_NIF_TERM eAddr,
+ struct in_addr* inAddrP)
+{
+ struct in_addr addr;
+
+ UDBG( ("SUTIL", "esock_decode_ip4_address -> entry with"
+ "\r\n eAddr: %T"
+ "\r\n", eAddr) );
+
+ if (IS_ATOM(env, eAddr)) {
+ /* This is either 'any' or 'loopback' */
+
+ if (COMPARE(esock_atom_loopback, eAddr) == 0) {
+ UDBG( ("SUTIL", "esock_decode_ip4_address -> address: lookback\r\n") );
+ addr.s_addr = htonl(INADDR_LOOPBACK);
+ } else if (COMPARE(esock_atom_any, eAddr) == 0) {
+ UDBG( ("SUTIL", "esock_decode_ip4_address -> address: any\r\n") );
+ addr.s_addr = htonl(INADDR_ANY);
+ } else {
+ UDBG( ("SUTIL", "esock_decode_ip4_address -> address: unknown\r\n") );
+ return ESOCK_STR_EINVAL;
+ }
+
+ inAddrP->s_addr = addr.s_addr;
+
+ } else {
+ /* This is a 4-tuple */
+
+ const ERL_NIF_TERM* addrt;
+ int addrtSz;
+ int a, v;
+ char addr[4];
+
+ if (!GET_TUPLE(env, eAddr, &addrtSz, &addrt))
+ return ESOCK_STR_EINVAL;
+
+ if (addrtSz != 4)
+ return ESOCK_STR_EINVAL;
+
+ for (a = 0; a < 4; a++) {
+ if (!GET_INT(env, addrt[a], &v))
+ return ESOCK_STR_EINVAL;
+ addr[a] = v;
+ }
+
+ sys_memcpy(inAddrP, &addr, sizeof(addr));
+
+ }
+
+ return NULL;
+}
+
+
+
+/* +++ esock_encode_ip4_address +++
+ *
+ * Encode a IPv4 address:
+ *
+ * + An ip4_address() (4 tuple)
+ *
+ * Note that this *only* decodes the "address" part of a
+ * (IPv4) socket address. There are several other things (port).
+ */
+
+extern
+char* esock_encode_ip4_address(ErlNifEnv* env,
+ struct in_addr* addrP,
+ ERL_NIF_TERM* eAddr)
+{
+ unsigned int i;
+ ERL_NIF_TERM at[4];
+ unsigned int atLen = sizeof(at) / sizeof(ERL_NIF_TERM);
+ unsigned char* a = (unsigned char*) addrP;
+ ERL_NIF_TERM addr;
+
+ /* The address */
+ for (i = 0; i < atLen; i++) {
+ at[i] = MKI(env, a[i]);
+ }
+
+ addr = MKTA(env, at, atLen);
+ UDBG( ("SUTIL", "esock_encode_ip4_address -> addr: %T\r\n", addr) );
+ // *eAddr = MKTA(env, at, atLen);
+ *eAddr = addr;
+
+ return NULL;
+}
+
+
+
+/* +++ esock_decode_ip6_address +++
+ *
+ * Decode a IPv6 address. This can be three things:
+ *
+ * + Then atom 'any'
+ * + Then atom 'loopback'
+ * + An ip6_address() (8 tuple)
+ *
+ * Note that this *only* decodes the "address" part of a
+ * (IPv6) socket address. There are several other things
+ * (port, flowinfo and scope_id) that are handled elsewhere).
+ */
+
+#if defined(HAVE_IN6) && defined(AF_INET6)
+extern
+char* esock_decode_ip6_address(ErlNifEnv* env,
+ ERL_NIF_TERM eAddr,
+ struct in6_addr* inAddrP)
+{
+ UDBG( ("SUTIL", "esock_decode_ip6_address -> entry with"
+ "\r\n eAddr: %T"
+ "\r\n", eAddr) );
+
+ if (IS_ATOM(env, eAddr)) {
+ /* This is either 'any' or 'loopback' */
+ const struct in6_addr* addr;
+
+ if (COMPARE(esock_atom_loopback, eAddr) == 0) {
+ addr = &in6addr_loopback;
+ } else if (COMPARE(esock_atom_any, eAddr) == 0) {
+ addr = &in6addr_any;
+ } else {
+ return ESOCK_STR_EINVAL;
+ }
+
+ *inAddrP = *addr;
+
+ } else {
+ /* This is a 8-tuple */
+
+ const ERL_NIF_TERM* addrt;
+ int addrtSz;
+ int ai, v;
+ unsigned char addr[16];
+ unsigned char* a = addr;
+ unsigned int addrLen = sizeof(addr) / sizeof(unsigned char);
+
+ if (!GET_TUPLE(env, eAddr, &addrtSz, &addrt))
+ return ESOCK_STR_EINVAL;
+
+ if (addrtSz != 8)
+ return ESOCK_STR_EINVAL;
+
+ for (ai = 0; ai < 8; ai++) {
+ if (!GET_INT(env, addrt[ai], &v))
+ return ESOCK_STR_EINVAL;
+ put_int16(v, a);
+ a += 2;
+ }
+
+ sys_memcpy(inAddrP, &addr, addrLen);
+
+ }
+
+ return NULL;
+}
+#endif
+
+
+
+/* +++ esock_encode_ip6_address +++
+ *
+ * Encode a IPv6 address:
+ *
+ * + An ip6_address() (8 tuple)
+ *
+ * Note that this *only* encodes the "address" part of a
+ * (IPv6) socket address. There are several other things
+ * (port, flowinfo and scope_id) that are handled elsewhere).
+ */
+
+#if defined(HAVE_IN6) && defined(AF_INET6)
+extern
+char* esock_encode_ip6_address(ErlNifEnv* env,
+ struct in6_addr* addrP,
+ ERL_NIF_TERM* eAddr)
+{
+ unsigned int i;
+ ERL_NIF_TERM at[8];
+ unsigned int atLen = sizeof(at) / sizeof(ERL_NIF_TERM);
+ unsigned char* a = (unsigned char*) addrP;
+
+ /* The address */
+ for (i = 0; i < atLen; i++) {
+ at[i] = MKI(env, get_int16(a + i*2));
+ }
+
+ *eAddr = MKTA(env, at, atLen);
+
+ return NULL;
+}
+#endif
+
+
+
+/* +++ esock_encode_timeval +++
+ *
+ * Encode a timeval struct into its erlang form, a map with two fields:
+ *
+ * sec
+ * usec
+ *
+ */
+extern
+char* esock_encode_timeval(ErlNifEnv* env,
+ struct timeval* timeP,
+ ERL_NIF_TERM* eTime)
+{
+ ERL_NIF_TERM keys[] = {esock_atom_sec, esock_atom_usec};
+ ERL_NIF_TERM vals[] = {MKL(env, timeP->tv_sec), MKL(env, timeP->tv_usec)};
+
+ unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM);
+ unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM);
+
+ ESOCK_ASSERT( (numKeys == numVals) );
+
+ if (!MKMA(env, keys, vals, numKeys, eTime))
+ return ESOCK_STR_EINVAL;
+
+ return NULL;
+}
+
+
+
+/* +++ esock_decode_timeval +++
+ *
+ * Decode a timeval in its erlang form (a map) into its native form,
+ * a timeval struct.
+ *
+ */
+extern
+char* esock_decode_timeval(ErlNifEnv* env,
+ ERL_NIF_TERM eTime,
+ struct timeval* timeP)
+{
+ ERL_NIF_TERM eSec, eUSec;
+ size_t sz;
+
+ // It must be a map
+ if (!IS_MAP(env, eTime))
+ return ESOCK_STR_EINVAL;
+
+ // It must have atleast two attributes
+ if (!enif_get_map_size(env, eTime, &sz) || (sz < 2))
+ return ESOCK_STR_EINVAL;
+
+ if (!GET_MAP_VAL(env, eTime, esock_atom_sec, &eSec))
+ return ESOCK_STR_EINVAL;
+
+ if (!GET_MAP_VAL(env, eTime, esock_atom_usec, &eUSec))
+ return ESOCK_STR_EINVAL;
+
+ if (!GET_LONG(env, eSec, &timeP->tv_sec))
+ return ESOCK_STR_EINVAL;
+
+ if (!GET_LONG(env, eUSec, &timeP->tv_usec))
+ return ESOCK_STR_EINVAL;
+
+ return NULL;
+}
+
+
+
+/* +++ esock_decode_domain +++
+ *
+ * Decode the Erlang form of the 'domain' type, that is:
+ *
+ * inet => AF_INET
+ * inet6 => AF_INET6
+ * local => AF_UNIX
+ *
+ */
+extern
+char* esock_decode_domain(ErlNifEnv* env,
+ ERL_NIF_TERM eDomain,
+ int* domain)
+{
+ char* xres = NULL;
+
+ if (COMPARE(esock_atom_inet, eDomain) == 0) {
+ *domain = AF_INET;
+
+#if defined(HAVE_IN6) && defined(AF_INET6)
+ } else if (COMPARE(esock_atom_inet6, eDomain) == 0) {
+ *domain = AF_INET6;
+#endif
+
+#ifdef HAVE_SYS_UN_H
+ } else if (COMPARE(esock_atom_local, eDomain) == 0) {
+ *domain = AF_UNIX;
+#endif
+
+ } else {
+ *domain = -1;
+ xres = ESOCK_STR_EAFNOSUPPORT;
+ }
+
+ return xres;
+}
+
+
+
+/* +++ esock_encode_domain +++
+ *
+ * Encode the native domain to the Erlang form, that is:
+ *
+ * AF_INET => inet
+ * AF_INET6 => inet6
+ * AF_UNIX => local
+ *
+ */
+extern
+char* esock_encode_domain(ErlNifEnv* env,
+ int domain,
+ ERL_NIF_TERM* eDomain)
+{
+ char* xres = NULL;
+
+ switch (domain) {
+ case AF_INET:
+ *eDomain = esock_atom_inet;
+ break;
+
+#if defined(HAVE_IN6) && defined(AF_INET6)
+ case AF_INET6:
+ *eDomain = esock_atom_inet6;
+ break;
+#endif
+
+#ifdef HAVE_SYS_UN_H
+ case AF_UNIX:
+ *eDomain = esock_atom_local;
+ break;
+#endif
+
+ default:
+ *eDomain = esock_atom_undefined; // Just in case
+ xres = ESOCK_STR_EAFNOSUPPORT;
+ }
+
+ return xres;
+}
+
+
+
+/* +++ esock_decode_type +++
+ *
+ * Decode the Erlang form of the 'type' type, that is:
+ *
+ * stream => SOCK_STREAM
+ * dgram => SOCK_DGRAM
+ * raw => SOCK_RAW
+ * seqpacket => SOCK_SEQPACKET
+ *
+ */
+extern
+char* esock_decode_type(ErlNifEnv* env,
+ ERL_NIF_TERM eType,
+ int* type)
+{
+ char* xres = NULL;
+
+ if (COMPARE(esock_atom_stream, eType) == 0) {
+ *type = SOCK_STREAM;
+ } else if (COMPARE(esock_atom_dgram, eType) == 0) {
+ *type = SOCK_DGRAM;
+ } else if (COMPARE(esock_atom_raw, eType) == 0) {
+ *type = SOCK_RAW;
+
+#if defined(HAVE_SCTP)
+ } else if (COMPARE(esock_atom_seqpacket, eType) == 0) {
+ *type = SOCK_SEQPACKET;
+#endif
+
+ } else {
+ *type = -1;
+ xres = ESOCK_STR_EAFNOSUPPORT;
+ }
+
+ return xres;
+}
+
+
+
+/* +++ esock_decode_type +++
+ *
+ * Encode the native type to the Erlang form, that is:
+ *
+ * SOCK_STREAM => stream
+ * SOCK_DGRAM => dgram
+ * SOCK_RAW => raw
+ * SOCK_SEQPACKET => seqpacket
+ *
+ */
+extern
+char* esock_encode_type(ErlNifEnv* env,
+ int type,
+ ERL_NIF_TERM* eType)
+{
+ char* xres = NULL;
+
+ switch (type) {
+ case SOCK_STREAM:
+ *eType = esock_atom_stream;
+ break;
+
+ case SOCK_DGRAM:
+ *eType = esock_atom_dgram;
+ break;
+
+ case SOCK_RAW:
+ *eType = esock_atom_raw;
+ break;
+
+#if defined(HAVE_SCTP)
+ case SOCK_SEQPACKET:
+ *eType = esock_atom_seqpacket;
+ break;
+#endif
+
+ default:
+ *eType = esock_atom_undefined; // Just in case
+ xres = ESOCK_STR_EAFNOSUPPORT;
+ }
+
+ return xres;
+}
+
+
+
+/* +++ esock_encode_protocol +++
+ *
+ * Encode the native protocol to the Erlang form, that is:
+ *
+ * SOL_IP | IPPROTO_IP => ip
+ * SOL_IPV6 => ipv6
+ * SOL_TCP => tcp
+ * SOL_UDP => udp
+ * SOL_SCTP => sctp
+ *
+ */
+extern
+char* esock_encode_protocol(ErlNifEnv* env,
+ int proto,
+ ERL_NIF_TERM* eProto)
+{
+ char* xres = NULL;
+
+ switch (proto) {
+#if defined(SOL_IP)
+ case SOL_IP:
+#else
+ case IPPROTO_IP:
+#endif
+ *eProto = esock_atom_ip;
+ break;
+
+#if defined(SOL_IPV6)
+ case SOL_IPV6:
+ *eProto = esock_atom_ipv6;
+ break;
+#endif
+
+ case IPPROTO_TCP:
+ *eProto = esock_atom_tcp;
+ break;
+
+ case IPPROTO_UDP:
+ *eProto = esock_atom_udp;
+ break;
+
+#if defined(HAVE_SCTP)
+ case IPPROTO_SCTP:
+ *eProto = esock_atom_sctp;
+ break;
+#endif
+
+ default:
+ *eProto = esock_atom_undefined;
+ xres = ESOCK_STR_EAFNOSUPPORT;
+ break;
+ }
+
+ return xres;
+}
+
+
+
+/* +++ esock_decode_protocol +++
+ *
+ * Decode the Erlang form of the 'protocol' type, that is:
+ *
+ * ip => SOL_IP | IPPROTO_IP
+ * ipv6 => SOL_IPV6
+ * tcp => SOL_TCP
+ * udp => SOL_UDP
+ * sctp => SOL_SCTP
+ *
+ */
+extern
+char* esock_decode_protocol(ErlNifEnv* env,
+ ERL_NIF_TERM eProto,
+ int* proto)
+{
+ char* xres = NULL;
+
+ if (COMPARE(esock_atom_ip, eProto) == 0) {
+#if defined(SOL_IP)
+ *proto = SOL_IP;
+#else
+ *proto = IPPROTO_IP;
+#endif
+ } else if (COMPARE(esock_atom_ipv6, eProto) == 0) {
+#if defined(SOL_IPV6)
+ *proto = SOL_IPV6;
+#else
+ *proto = IPPROTO_IPV6;
+#endif
+ } else if (COMPARE(esock_atom_tcp, eProto) == 0) {
+ *proto = IPPROTO_TCP;
+ } else if (COMPARE(esock_atom_udp, eProto) == 0) {
+ *proto = IPPROTO_UDP;
+#if defined(HAVE_SCTP)
+ } else if (COMPARE(esock_atom_sctp, eProto) == 0) {
+ *proto = IPPROTO_SCTP;
+#endif
+ } else {
+ *proto = -1;
+ xres = ESOCK_STR_EAFNOSUPPORT;
+ }
+
+ return xres;
+}
+
+
+
+/* +++ esock_decode_bufsz +++
+ *
+ * Decode an buffer size. The size of a buffer is:
+ *
+ * Sz > 0 => Use provided value
+ * Sz => Use provided default
+ *
+ */
+extern
+char* esock_decode_bufsz(ErlNifEnv* env,
+ ERL_NIF_TERM eVal,
+ size_t defSz,
+ size_t* sz)
+{
+ int val;
+
+ if (!GET_INT(env, eVal, &val))
+ return ESOCK_STR_EINVAL;
+
+ if (val > 0)
+ *sz = (size_t) val;
+ else
+ *sz = defSz;
+
+ return NULL;
+}
+
+
+
+/* *** esock_decode_string ***
+ *
+ * Decode a string value. A successful decode results in an
+ * allocation of the string, which the caller has to free
+ * once the string has been used.
+ */
+extern
+BOOLEAN_T esock_decode_string(ErlNifEnv* env,
+ const ERL_NIF_TERM eString,
+ char** stringP)
+{
+ BOOLEAN_T result;
+ unsigned int len;
+ char* bufP;
+
+ if (!GET_LIST_LEN(env, eString, &len) && (len != 0)) {
+ *stringP = NULL;
+ result = FALSE;
+ } else {
+
+ UDBG( ("SUTIL", "esock_decode_string -> len: %d\r\n", len) );
+
+ bufP = MALLOC(len + 1); // We shall NULL-terminate
+
+ if (GET_STR(env, eString, bufP, len+1)) {
+ UDBG( ("SUTIL", "esock_decode_string -> buf: %s\r\n", bufP) );
+ // bufP[len] = '\0';
+ *stringP = bufP;
+ result = TRUE;
+ } else {
+ *stringP = NULL;
+ result = FALSE;
+ FREE(bufP);
+ }
+ }
+
+ return result;
+}
+
+
+
+/* *** esock_extract_bool_from_map ***
+ *
+ * Extract an boolean item from a map.
+ *
+ */
+extern
+BOOLEAN_T esock_extract_bool_from_map(ErlNifEnv* env,
+ ERL_NIF_TERM map,
+ ERL_NIF_TERM key,
+ BOOLEAN_T def)
+{
+ ERL_NIF_TERM val;
+
+ if (!GET_MAP_VAL(env, map, key, &val))
+ return def;
+
+ if (!IS_ATOM(env, val))
+ return def;
+
+ if (COMPARE(val, esock_atom_true) == 0)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+
+
+/* *** esock_decode_bool ***
+ *
+ * Decode a boolean value.
+ *
+ */
+extern
+BOOLEAN_T esock_decode_bool(ERL_NIF_TERM val)
+{
+ if (COMPARE(esock_atom_true, val) == 0)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+
+/* *** esock_encode_bool ***
+ *
+ * Encode a boolean value.
+ *
+ */
+extern
+ERL_NIF_TERM esock_encode_bool(BOOLEAN_T val)
+{
+ if (val)
+ return esock_atom_true;
+ else
+ return esock_atom_false;
+}
+
+
+/* Create an ok two (2) tuple in the form:
+ *
+ * {ok, Any}
+ *
+ * The second element (Any) is 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_ok2(ErlNifEnv* env, ERL_NIF_TERM any)
+{
+ return MKT2(env, esock_atom_ok, any);
+}
+
+
+/* Create an ok three (3) tuple in the form:
+ *
+ * {ok, Val1, Val2}
+ *
+ * The second (Val1) and third (Val2) elements 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_ok3(ErlNifEnv* env, ERL_NIF_TERM val1, ERL_NIF_TERM val2)
+{
+ return MKT3(env, esock_atom_ok, val1, val2);
+}
+
+
+
+/* Create an error two (2) tuple in the form:
+ *
+ * {error, Reason}
+ *
+ * The second element (Reason) is 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(ErlNifEnv* env, ERL_NIF_TERM reason)
+{
+ return MKT2(env, esock_atom_error, reason);
+}
+
+
+
+/* Create an error two (2) tuple in the form: {error, Reason}.
+ *
+ * {error, Reason}
+ *
+ * The second element, Reason, is the reason string that has
+ * converted into an atom.
+ */
+extern
+ERL_NIF_TERM esock_make_error_str(ErlNifEnv* env, char* reason)
+{
+ return esock_make_error(env, MKA(env, reason));
+}
+
+
+/* Create an error two (2) tuple in the form:
+ *
+ * {error, Reason}
+ *
+ * The second element, Reason, is the errno value in its
+ * basic form (integer) which has been converted into an atom.
+ */
+extern
+ERL_NIF_TERM esock_make_error_errno(ErlNifEnv* env, int err)
+{
+ return esock_make_error_str(env, erl_errno_id(err));
+}
+
+
+
+/* strnlen doesn't exist everywhere */
+extern
+size_t esock_strnlen(const char *s, size_t maxlen)
+{
+ size_t i = 0;
+ while (i < maxlen && s[i] != '\0')
+ i++;
+ return i;
+}
+
+
+
+/* *** esock_abort ***
+ *
+ * Generate an abort with "extra" info. This should be called
+ * via the ESOCK_ABORT macro.
+ * Basically it prints the extra info onto stderr before aborting.
+ *
+ */
+extern
+void esock_abort(const char* expr,
+ const char* func,
+ const char* file,
+ int line)
+{
+ fflush(stdout);
+ fprintf(stderr, "%s:%d:%s() Assertion failed: %s\n",
+ file, line, func, expr);
+ fflush(stderr);
+ abort();
+}
+
+
+
+/* *** esock_warning_msg ***
+ *
+ * Temporary function for issuing warning messages.
+ *
+ */
+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...
+ struct timespec ts;
+ 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 (!realtime(&ts)) {
+ if (timespec2str(stamp, sizeof(stamp), &ts) != 0) {
+ res = enif_snprintf(f, sizeof(f), "=WARNING MSG==== %s", format);
+ } else {
+ res = enif_snprintf(f, sizeof(f),
+ "=WARNING MSG==== %s ===\r\n%s" , stamp, format);
+ }
+
+ if (res > 0) {
+ va_start (args, format);
+ enif_vfprintf (stdout, f, args);
+ va_end (args);
+ fflush(stdout);
+ }
+ }
+
+ return;
+}
+
+
+static
+int realtime(struct timespec* tsP)
+{
+ return clock_gettime(CLOCK_REALTIME, tsP);
+}
+
+
+/*
+ * Convert a timespec struct into a readable/printable string.
+ *
+ * "%F::%T" => 2018-06-29 12:13:21[.232089]
+ * "%d-%b-%Y::%T" => 29-Jun-2018::13:47:25.097097
+ */
+static
+int timespec2str(char *buf, unsigned int len, struct timespec *ts)
+{
+ int ret, buflen;
+ struct tm t;
+
+ tzset();
+ if (localtime_r(&(ts->tv_sec), &t) == NULL)
+ return 1;
+
+ ret = strftime(buf, len, "%d-%B-%Y::%T", &t);
+ if (ret == 0)
+ return 2;
+ len -= ret - 1;
+ buflen = strlen(buf);
+
+ ret = snprintf(&buf[buflen], len, ".%06ld", ts->tv_nsec/1000);
+ if (ret >= len)
+ return 3;
+
+ return 0;
+}
+
+
+/* =================================================================== *
+ * *
+ * Various (internal) utility functions *
+ * *
+ * =================================================================== */
+
+/* Construct the IPv4 socket address */
+static
+char* make_sockaddr_in4(ErlNifEnv* env,
+ ERL_NIF_TERM port,
+ ERL_NIF_TERM addr,
+ ERL_NIF_TERM* sa)
+{
+ ERL_NIF_TERM keys[] = {esock_atom_family, esock_atom_port, esock_atom_addr};
+ ERL_NIF_TERM vals[] = {esock_atom_inet, port, addr};
+ unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM);
+ unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM);
+
+ ESOCK_ASSERT( (numKeys == numVals) );
+
+ if (!MKMA(env, keys, vals, numKeys, sa)) {
+ *sa = esock_atom_undefined;
+ return ESOCK_STR_EINVAL;
+ } else {
+ return NULL;
+ }
+}
+
+
+/* Construct the IPv6 socket address */
+static
+char* make_sockaddr_in6(ErlNifEnv* env,
+ ERL_NIF_TERM port,
+ ERL_NIF_TERM addr,
+ ERL_NIF_TERM flowInfo,
+ ERL_NIF_TERM scopeId,
+ ERL_NIF_TERM* sa)
+{
+ ERL_NIF_TERM keys[] = {esock_atom_family,
+ esock_atom_port,
+ esock_atom_addr,
+ esock_atom_flowinfo,
+ esock_atom_scope_id};
+ ERL_NIF_TERM vals[] = {esock_atom_inet6, port, addr, flowInfo, scopeId};
+ unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM);
+ unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM);
+
+ ESOCK_ASSERT( (numKeys == numVals) );
+
+ if (!MKMA(env, keys, vals, numKeys, sa)) {
+ *sa = esock_atom_undefined;
+ return ESOCK_STR_EINVAL;
+ } else {
+ return NULL;
+ }
+}
+
+
+/* Construct the Unix Domain socket address */
+static
+char* make_sockaddr_un(ErlNifEnv* env,
+ ERL_NIF_TERM path,
+ ERL_NIF_TERM* sa)
+{
+ ERL_NIF_TERM keys[] = {esock_atom_family, esock_atom_path};
+ ERL_NIF_TERM vals[] = {esock_atom_inet, path};
+ unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM);
+ unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM);
+
+ ESOCK_ASSERT( (numKeys == numVals) );
+
+ if (!MKMA(env, keys, vals, numKeys, sa)) {
+ *sa = esock_atom_undefined;
+ return ESOCK_STR_EINVAL;
+ } else {
+ return NULL;
+ }
+}
+
+
diff --git a/erts/emulator/nifs/common/socket_util.h b/erts/emulator/nifs/common/socket_util.h
new file mode 100644
index 0000000000..1b5d003155
--- /dev/null
+++ b/erts/emulator/nifs/common/socket_util.h
@@ -0,0 +1,205 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2018-2018. All 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 : Utility "stuff" for socket and net.
+ * ----------------------------------------------------------------------
+ *
+ */
+
+#ifndef SOCKET_UTIL_H__
+#define SOCKET_UTIL_H__
+
+#include <erl_nif.h>
+#include "socket_int.h"
+
+#define CHAR(C) ((char) (C))
+#define UCHAR(C) ((unsigned char) (C))
+#define INT(I) ((int) (I))
+#define UINT(U) ((unsigned int) (U))
+#define LONG(L) ((long) (L))
+#define ULONG(L) ((unsigned long) (L))
+#define SZT(I) ((size_t) (I))
+#define VOIDP(P) ((void*) (P))
+#define CHARP(P) ((char*) (P))
+
+#define ESOCK_ABORT(E) esock_abort(E, __func__, __FILE__, __LINE__)
+#define ESOCK_ASSERT(e) ((void) ((e) ? 1 : (ESOCK_ABORT(#e), 0)))
+
+extern
+char* esock_encode_iov(ErlNifEnv* env,
+ int read,
+ struct iovec* iov,
+ size_t len,
+ ErlNifBinary* data,
+ ERL_NIF_TERM* eIOV);
+extern
+char* esock_decode_iov(ErlNifEnv* env,
+ ERL_NIF_TERM eIOV,
+ ErlNifBinary* bufs,
+ struct iovec* iov,
+ size_t len,
+ ssize_t* totSize);
+extern
+char* esock_decode_sockaddr(ErlNifEnv* env,
+ ERL_NIF_TERM eSockAddr,
+ SocketAddress* sockAddrP,
+ unsigned int* addrLen);
+extern
+char* esock_encode_sockaddr(ErlNifEnv* env,
+ SocketAddress* sockAddrP,
+ unsigned int addrLen,
+ ERL_NIF_TERM* eSockAddr);
+
+extern
+char* esock_decode_sockaddr_in4(ErlNifEnv* env,
+ ERL_NIF_TERM eSockAddr,
+ struct sockaddr_in* sockAddrP,
+ unsigned int* addrLen);
+extern
+char* esock_encode_sockaddr_in4(ErlNifEnv* env,
+ struct sockaddr_in* sockAddrP,
+ unsigned int addrLen,
+ ERL_NIF_TERM* eSockAddr);
+
+#if defined(HAVE_IN6) && defined(AF_INET6)
+extern
+char* esock_decode_sockaddr_in6(ErlNifEnv* env,
+ ERL_NIF_TERM eSockAddr,
+ struct sockaddr_in6* sockAddrP,
+ unsigned int* addrLen);
+extern
+char* esock_encode_sockaddr_in6(ErlNifEnv* env,
+ struct sockaddr_in6* sockAddrP,
+ unsigned int addrLen,
+ ERL_NIF_TERM* eSockAddr);
+#endif
+
+#ifdef HAVE_SYS_UN_H
+extern
+char* esock_decode_sockaddr_un(ErlNifEnv* env,
+ ERL_NIF_TERM eSockAddr,
+ struct sockaddr_un* sockAddrP,
+ unsigned int* addrLen);
+extern
+char* esock_encode_sockaddr_un(ErlNifEnv* env,
+ struct sockaddr_un* sockAddrP,
+ unsigned int addrLen,
+ ERL_NIF_TERM* eSockAddr);
+#endif
+
+extern
+char* esock_decode_ip4_address(ErlNifEnv* env,
+ ERL_NIF_TERM eAddr,
+ struct in_addr* inAddrP);
+extern
+char* esock_encode_ip4_address(ErlNifEnv* env,
+ struct in_addr* addrP,
+ ERL_NIF_TERM* eAddr);
+
+#if defined(HAVE_IN6) && defined(AF_INET6)
+extern
+char* esock_decode_ip6_address(ErlNifEnv* env,
+ ERL_NIF_TERM eAddr,
+ struct in6_addr* inAddrP);
+extern
+char* esock_encode_ip6_address(ErlNifEnv* env,
+ struct in6_addr* addrP,
+ ERL_NIF_TERM* eAddr);
+#endif
+
+extern char* esock_encode_timeval(ErlNifEnv* env,
+ struct timeval* timeP,
+ ERL_NIF_TERM* eTime);
+extern char* esock_decode_timeval(ErlNifEnv* env,
+ ERL_NIF_TERM eTime,
+ struct timeval* timeP);
+extern
+char* esock_decode_domain(ErlNifEnv* env,
+ ERL_NIF_TERM eDomain,
+ int* domain);
+extern
+char* esock_encode_domain(ErlNifEnv* env,
+ int domain,
+ ERL_NIF_TERM* eDomain);
+
+extern
+char* esock_decode_type(ErlNifEnv* env,
+ ERL_NIF_TERM eType,
+ int* type);
+extern
+char* esock_encode_type(ErlNifEnv* env,
+ int type,
+ ERL_NIF_TERM* eType);
+
+extern
+char* esock_decode_protocol(ErlNifEnv* env,
+ ERL_NIF_TERM eProtocol,
+ int* protocol);
+extern
+char* esock_encode_protocol(ErlNifEnv* env,
+ int type,
+ ERL_NIF_TERM* eProtocol);
+
+extern
+char* esock_decode_bufsz(ErlNifEnv* env,
+ ERL_NIF_TERM eVal,
+ size_t defSz,
+ size_t* sz);
+
+extern
+BOOLEAN_T esock_decode_string(ErlNifEnv* env,
+ const ERL_NIF_TERM eString,
+ char** stringP);
+
+extern
+BOOLEAN_T esock_extract_bool_from_map(ErlNifEnv* env,
+ ERL_NIF_TERM map,
+ ERL_NIF_TERM key,
+ BOOLEAN_T def);
+extern
+BOOLEAN_T esock_decode_bool(ERL_NIF_TERM val);
+extern
+ERL_NIF_TERM esock_encode_bool(BOOLEAN_T val);
+
+extern
+size_t esock_strnlen(const char *s, size_t maxlen);
+extern
+void esock_abort(const char* expr,
+ const char* func,
+ const char* file,
+ int line);
+
+extern
+ERL_NIF_TERM esock_make_ok2(ErlNifEnv* env, ERL_NIF_TERM any);
+extern
+ERL_NIF_TERM esock_make_ok3(ErlNifEnv* env, ERL_NIF_TERM val1, ERL_NIF_TERM val2);
+
+extern
+ERL_NIF_TERM esock_make_error(ErlNifEnv* env, ERL_NIF_TERM reason);
+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
+void esock_warning_msg(const char* format, ... );
+
+
+#endif // SOCKET_UTIL_H__
diff --git a/erts/emulator/sys/common/erl_check_io.c b/erts/emulator/sys/common/erl_check_io.c
index ac9a070bce..80e8030d74 100644
--- a/erts/emulator/sys/common/erl_check_io.c
+++ b/erts/emulator/sys/common/erl_check_io.c
@@ -44,7 +44,7 @@
#include "erl_time.h"
#if 0
-#define DEBUG_PRINT(FMT, ...) erts_printf(FMT "\r\n", ##__VA_ARGS__)
+#define DEBUG_PRINT(FMT, ...) do { erts_printf(FMT "\r\n", ##__VA_ARGS__); fflush(stdout); } while(0)
#define DEBUG_PRINT_FD(FMT, STATE, ...) \
DEBUG_PRINT("%d: " FMT " (ev=%s, ac=%s, flg=%s)", \
(STATE) ? (STATE)->fd : (ErtsSysFdType)-1, ##__VA_ARGS__, \
@@ -591,6 +591,96 @@ abort_tasks(ErtsDrvEventState *state, int mode)
}
}
+static void prepare_select_msg(struct erts_nif_select_event* e,
+ enum ErlNifSelectFlags mode,
+ Eterm recipient,
+ ErtsResource* resource,
+ Eterm msg,
+ ErlNifEnv* msg_env,
+ Eterm event_atom)
+{
+ ErtsMessage* mp;
+ Eterm* hp;
+ Uint hsz;
+
+ if (is_not_nil(e->pid)) {
+ ASSERT(e->mp);
+ erts_cleanup_messages(e->mp);
+ }
+
+ if (mode & ERL_NIF_SELECT_CUSTOM_MSG) {
+ if (msg_env) {
+ mp = erts_create_message_from_nif_env(msg_env);
+ ERL_MESSAGE_TERM(mp) = msg;
+ }
+ else {
+ hsz = size_object(msg);
+ mp = erts_alloc_message(hsz, &hp);
+ ERL_MESSAGE_TERM(mp) = copy_struct(msg, hsz, &hp, &mp->hfrag.off_heap);
+ }
+ }
+ else {
+ ErtsBinary* bin;
+ Eterm resource_term, ref_term, tuple;
+ Eterm* hp_start;
+
+ /* {select, Resource, Ref, EventAtom} */
+ hsz = 5 + ERTS_MAGIC_REF_THING_SIZE;
+ if (is_internal_ref(msg))
+ hsz += ERTS_REF_THING_SIZE;
+ else
+ ASSERT(is_immed(msg));
+
+ mp = erts_alloc_message(hsz, &hp);
+ hp_start = hp;
+
+ bin = ERTS_MAGIC_BIN_FROM_UNALIGNED_DATA(resource);
+ resource_term = erts_mk_magic_ref(&hp, &mp->hfrag.off_heap, &bin->binary);
+ if (is_internal_ref(msg)) {
+ Uint32* refn = internal_ref_numbers(msg);
+ write_ref_thing(hp, refn[0], refn[1], refn[2]);
+ ref_term = make_internal_ref(hp);
+ hp += ERTS_REF_THING_SIZE;
+ }
+ else {
+ ASSERT(is_immed(msg));
+ ref_term = msg;
+ }
+ tuple = TUPLE4(hp, am_select, resource_term, ref_term, event_atom);
+ hp += 5;
+ ERL_MESSAGE_TERM(mp) = tuple;
+ ASSERT(hp == hp_start + hsz); (void)hp_start;
+ }
+
+ ASSERT(is_not_nil(recipient));
+ e->pid = recipient;
+ e->mp = mp;
+}
+
+static ERTS_INLINE void send_select_msg(struct erts_nif_select_event* e)
+{
+ Process* rp = erts_proc_lookup(e->pid);
+
+ ASSERT(is_internal_pid(e->pid));
+ if (!rp) {
+ erts_cleanup_messages(e->mp);
+ return;
+ }
+
+ erts_queue_message(rp, 0, e->mp, ERL_MESSAGE_TERM(e->mp), am_system);
+}
+
+static void clear_select_event(struct erts_nif_select_event* e)
+{
+ if (is_not_nil(e->pid)) {
+ /* Discard unsent message */
+ ASSERT(e->mp);
+ erts_cleanup_messages(e->mp);
+ e->mp = NULL;
+ e->pid = NIL;
+ }
+}
+
static void
deselect(ErtsDrvEventState *state, int mode)
{
@@ -621,8 +711,8 @@ deselect(ErtsDrvEventState *state, int mode)
erts_io_control(state, ERTS_POLL_OP_DEL, 0);
switch (state->type) {
case ERTS_EV_TYPE_NIF:
- state->driver.nif->in.pid = NIL;
- state->driver.nif->out.pid = NIL;
+ clear_select_event(&state->driver.nif->in);
+ clear_select_event(&state->driver.nif->out);
enif_release_resource(state->driver.stop.resource->data);
state->driver.stop.resource = NULL;
break;
@@ -943,12 +1033,21 @@ done_unknown:
}
int
-enif_select(ErlNifEnv* env,
- ErlNifEvent e,
- enum ErlNifSelectFlags mode,
- void* obj,
- const ErlNifPid* pid,
- Eterm ref)
+enif_select(ErlNifEnv* env, ErlNifEvent e, enum ErlNifSelectFlags mode,
+ void* obj, const ErlNifPid* pid, Eterm msg)
+{
+ return enif_select_x(env, e, mode, obj, pid, msg, NULL);
+}
+
+
+int
+enif_select_x(ErlNifEnv* env,
+ ErlNifEvent e,
+ enum ErlNifSelectFlags mode,
+ void* obj,
+ const ErlNifPid* pid,
+ Eterm msg,
+ ErlNifEnv* msg_env)
{
int on;
ErtsResource* resource = DATA_TO_RESOURCE(obj);
@@ -962,11 +1061,11 @@ enif_select(ErlNifEnv* env,
ErtsDrvSelectDataState *free_select = NULL;
ErtsNifSelectDataState *free_nif = NULL;
- ASSERT(!resource->monitors);
+ ASSERT(!erts_dbg_is_resource_dying(resource));
#ifdef ERTS_SYS_CONTINOUS_FD_NUMBERS
if (!grow_drv_ev_state(fd)) {
- if (fd > 0) nif_select_large_fd_error(fd, mode, resource, ref);
+ if (fd > 0) nif_select_large_fd_error(fd, mode, resource, msg);
return INT_MIN | ERL_NIF_SELECT_INVALID_EVENT;
}
#endif
@@ -993,7 +1092,7 @@ enif_select(ErlNifEnv* env,
ctl_op = ERTS_POLL_OP_DEL;
}
else {
- on = 1;
+ on = !(mode & ERL_NIF_SELECT_CANCEL);
ASSERT(mode);
if (mode & ERL_DRV_READ) {
ctl_events |= ERTS_POLL_EV_IN;
@@ -1012,21 +1111,21 @@ enif_select(ErlNifEnv* env,
* Changing process and/or ref is ok (I think?).
*/
if (state->driver.stop.resource != resource)
- nif_select_steal(state, ERL_DRV_READ | ERL_DRV_WRITE, resource, ref);
+ nif_select_steal(state, ERL_DRV_READ | ERL_DRV_WRITE, resource, msg);
break;
case ERTS_EV_TYPE_DRV_SEL:
- nif_select_steal(state, mode, resource, ref);
+ nif_select_steal(state, mode, resource, msg);
break;
case ERTS_EV_TYPE_STOP_USE: {
erts_dsprintf_buf_t *dsbufp = erts_create_logger_dsbuf();
- print_nif_select_op(dsbufp, fd, mode, resource, ref);
+ print_nif_select_op(dsbufp, fd, mode, resource, msg);
steal_pending_stop_use(dsbufp, ERTS_INVALID_ERL_DRV_PORT, state, mode, on);
ASSERT(state->type == ERTS_EV_TYPE_NONE);
break;
}
case ERTS_EV_TYPE_STOP_NIF: {
erts_dsprintf_buf_t *dsbufp = erts_create_logger_dsbuf();
- print_nif_select_op(dsbufp, fd, mode, resource, ref);
+ print_nif_select_op(dsbufp, fd, mode, resource, msg);
steal_pending_stop_nif(dsbufp, resource, state, mode, on);
if (state->type == ERTS_EV_TYPE_STOP_NIF) {
ret = ERL_NIF_SELECT_STOP_SCHEDULED; /* ?? */
@@ -1082,7 +1181,7 @@ enif_select(ErlNifEnv* env,
if (on) {
const Eterm recipient = pid ? pid->pid : env->proc->common.id;
- Uint32* refn;
+ ASSERT(is_internal_pid(recipient));
if (!state->driver.nif)
state->driver.nif = alloc_nif_select_data();
if (state->type == ERTS_EV_TYPE_NONE) {
@@ -1093,64 +1192,62 @@ enif_select(ErlNifEnv* env,
ASSERT(state->type == ERTS_EV_TYPE_NIF);
ASSERT(state->driver.stop.resource == resource);
if (mode & ERL_DRV_READ) {
- state->driver.nif->in.pid = recipient;
- if (is_immed(ref)) {
- state->driver.nif->in.immed = ref;
- } else {
- ASSERT(is_internal_ref(ref));
- refn = internal_ref_numbers(ref);
- state->driver.nif->in.immed = THE_NON_VALUE;
- sys_memcpy(state->driver.nif->in.refn, refn,
- sizeof(state->driver.nif->in.refn));
- }
+ prepare_select_msg(&state->driver.nif->in, mode, recipient,
+ resource, msg, msg_env, am_ready_input);
+ msg_env = NULL;
}
if (mode & ERL_DRV_WRITE) {
- state->driver.nif->out.pid = recipient;
- if (is_immed(ref)) {
- state->driver.nif->out.immed = ref;
- } else {
- ASSERT(is_internal_ref(ref));
- refn = internal_ref_numbers(ref);
- state->driver.nif->out.immed = THE_NON_VALUE;
- sys_memcpy(state->driver.nif->out.refn, refn,
- sizeof(state->driver.nif->out.refn));
- }
+ prepare_select_msg(&state->driver.nif->out, mode, recipient,
+ resource, msg, msg_env, am_ready_output);
}
ret = 0;
}
else { /* off */
+ ret = 0;
if (state->type == ERTS_EV_TYPE_NIF) {
- state->driver.nif->in.pid = NIL;
- state->driver.nif->out.pid = NIL;
- }
- ASSERT(state->events==0);
- if (!wake_poller) {
- /*
- * Safe to close fd now as it is not in pollset
- * or there was no need to eject fd (kernel poll)
- */
- if (state->type == ERTS_EV_TYPE_NIF) {
- ASSERT(state->driver.stop.resource == resource);
- call_stop = CALL_STOP_AND_RELEASE;
- state->driver.stop.resource = NULL;
+ if (mode & ERL_NIF_SELECT_READ
+ && is_not_nil(state->driver.nif->in.pid)) {
+ clear_select_event(&state->driver.nif->in);
+ ret |= ERL_NIF_SELECT_READ_CANCELLED;
}
- else {
- ASSERT(!state->driver.stop.resource);
- call_stop = CALL_STOP;
+ if (mode & ERL_NIF_SELECT_WRITE
+ && is_not_nil(state->driver.nif->out.pid)) {
+ clear_select_event(&state->driver.nif->out);
+ ret |= ERL_NIF_SELECT_WRITE_CANCELLED;
}
- state->type = ERTS_EV_TYPE_NONE;
- ret = ERL_NIF_SELECT_STOP_CALLED;
}
- else {
- /* Not safe to close fd, postpone stop_select callback. */
- if (state->type == ERTS_EV_TYPE_NONE) {
- ASSERT(!state->driver.stop.resource);
- state->driver.stop.resource = resource;
- enif_keep_resource(resource);
+ if (mode & ERL_NIF_SELECT_STOP) {
+ ASSERT(state->events==0);
+ if (!wake_poller) {
+ /*
+ * Safe to close fd now as it is not in pollset
+ * or there was no need to eject fd (kernel poll)
+ */
+ if (state->type == ERTS_EV_TYPE_NIF) {
+ ASSERT(state->driver.stop.resource == resource);
+ call_stop = CALL_STOP_AND_RELEASE;
+ state->driver.stop.resource = NULL;
+ }
+ else {
+ ASSERT(!state->driver.stop.resource);
+ call_stop = CALL_STOP;
+ }
+ state->type = ERTS_EV_TYPE_NONE;
+ ret |= ERL_NIF_SELECT_STOP_CALLED;
+ }
+ else {
+ /* Not safe to close fd, postpone stop_select callback. */
+ if (state->type == ERTS_EV_TYPE_NONE) {
+ ASSERT(!state->driver.stop.resource);
+ state->driver.stop.resource = resource;
+ enif_keep_resource(resource);
+ }
+ state->type = ERTS_EV_TYPE_STOP_NIF;
+ ret |= ERL_NIF_SELECT_STOP_SCHEDULED;
}
- state->type = ERTS_EV_TYPE_STOP_NIF;
- ret = ERL_NIF_SELECT_STOP_SCHEDULED;
}
+ else
+ ASSERT(mode & ERL_NIF_SELECT_CANCEL);
}
done:
@@ -1328,7 +1425,8 @@ print_nif_select_op(erts_dsprintf_buf_t *dsbufp,
(int) fd,
mode & ERL_NIF_SELECT_READ ? " READ" : "",
mode & ERL_NIF_SELECT_WRITE ? " WRITE" : "",
- mode & ERL_NIF_SELECT_STOP ? " STOP" : "",
+ (mode & ERL_NIF_SELECT_STOP ? " STOP"
+ : (mode & ERL_NIF_SELECT_CANCEL ? " CANCEL" : "")),
resource->type->module,
resource->type->name,
ref);
@@ -1531,53 +1629,6 @@ oready(Eterm id, ErtsDrvEventState *state)
}
}
-static ERTS_INLINE void
-send_event_tuple(struct erts_nif_select_event* e, ErtsResource* resource,
- Eterm event_atom)
-{
- Process* rp = erts_proc_lookup(e->pid);
- ErtsProcLocks rp_locks = 0;
- ErtsMessage* mp;
- ErlOffHeap* ohp;
- ErtsBinary* bin;
- Eterm* hp;
- Uint hsz;
- Eterm resource_term, ref_term, tuple;
-
- if (!rp) {
- return;
- }
-
- bin = ERTS_MAGIC_BIN_FROM_UNALIGNED_DATA(resource);
-
- /* {select, Resource, Ref, EventAtom} */
- if (is_value(e->immed)) {
- hsz = 5 + ERTS_MAGIC_REF_THING_SIZE;
- }
- else {
- hsz = 5 + ERTS_MAGIC_REF_THING_SIZE + ERTS_REF_THING_SIZE;
- }
-
- mp = erts_alloc_message_heap(rp, &rp_locks, hsz, &hp, &ohp);
-
- resource_term = erts_mk_magic_ref(&hp, ohp, &bin->binary);
- if (is_value(e->immed)) {
- ASSERT(is_immed(e->immed));
- ref_term = e->immed;
- }
- else {
- write_ref_thing(hp, e->refn[0], e->refn[1], e->refn[2]);
- ref_term = make_internal_ref(hp);
- hp += ERTS_REF_THING_SIZE;
- }
- tuple = TUPLE4(hp, am_select, resource_term, ref_term, event_atom);
-
- erts_queue_message(rp, rp_locks, mp, tuple, am_system);
-
- if (rp_locks)
- erts_proc_unlock(rp, rp_locks);
-}
-
static void bad_fd_in_pollset(ErtsDrvEventState *, Eterm inport, Eterm outport);
void
@@ -1751,7 +1802,6 @@ erts_check_io(ErtsPollThread *psi, ErtsMonotonicTime timeout_time)
case ERTS_EV_TYPE_NIF: { /* Requested via enif_select()... */
struct erts_nif_select_event in = {NIL};
struct erts_nif_select_event out = {NIL};
- ErtsResource* resource = NULL;
if (revents & (ERTS_POLL_EV_IN|ERTS_POLL_EV_OUT)) {
if (revents & ERTS_POLL_EV_OUT) {
@@ -1759,6 +1809,7 @@ erts_check_io(ErtsPollThread *psi, ErtsMonotonicTime timeout_time)
out = state->driver.nif->out;
resource = state->driver.stop.resource;
state->driver.nif->out.pid = NIL;
+ state->driver.nif->out.mp = NULL;
}
}
if (revents & ERTS_POLL_EV_IN) {
@@ -1766,6 +1817,7 @@ erts_check_io(ErtsPollThread *psi, ErtsMonotonicTime timeout_time)
in = state->driver.nif->in;
resource = state->driver.stop.resource;
state->driver.nif->in.pid = NIL;
+ state->driver.nif->in.mp = NULL;
}
}
state->events &= ~revents;
@@ -1778,10 +1830,10 @@ erts_check_io(ErtsPollThread *psi, ErtsMonotonicTime timeout_time)
erts_mtx_unlock(fd_mtx(fd));
if (is_not_nil(in.pid)) {
- send_event_tuple(&in, resource, am_ready_input);
+ send_select_msg(&in);
}
if (is_not_nil(out.pid)) {
- send_event_tuple(&out, resource, am_ready_output);
+ send_select_msg(&out);
}
continue;
}
@@ -2448,10 +2500,16 @@ drvmode2str(int mode) {
static ERTS_INLINE char *
nifmode2str(enum ErlNifSelectFlags mode) {
+ if (mode & ERL_NIF_SELECT_STOP)
+ return "STOP";
switch (mode) {
case ERL_NIF_SELECT_READ: return "READ";
case ERL_NIF_SELECT_WRITE: return "WRITE";
- case ERL_NIF_SELECT_STOP: return "STOP";
+ case ERL_NIF_SELECT_READ|ERL_NIF_SELECT_WRITE: return "READ|WRITE";
+ case ERL_NIF_SELECT_CANCEL|ERL_NIF_SELECT_READ: return "CANCEL|READ";
+ case ERL_NIF_SELECT_CANCEL|ERL_NIF_SELECT_WRITE: return "CANCEL|WRITE";
+ case ERL_NIF_SELECT_CANCEL|ERL_NIF_SELECT_READ|ERL_NIF_SELECT_WRITE:
+ return "CANCEL|READ|WRITE";
default: return "UNKNOWN";
}
}
diff --git a/erts/emulator/sys/common/erl_check_io.h b/erts/emulator/sys/common/erl_check_io.h
index 31182be5ec..0f3fc4f7a2 100644
--- a/erts/emulator/sys/common/erl_check_io.h
+++ b/erts/emulator/sys/common/erl_check_io.h
@@ -138,8 +138,7 @@ typedef struct {
struct erts_nif_select_event {
Eterm pid;
- Eterm immed;
- Uint32 refn[ERTS_REF_NUMBERS];
+ ErtsMessage *mp;
};
typedef struct {
diff --git a/erts/emulator/sys/common/erl_mmap.h b/erts/emulator/sys/common/erl_mmap.h
index 539daea419..3085bf7e19 100644
--- a/erts/emulator/sys/common/erl_mmap.h
+++ b/erts/emulator/sys/common/erl_mmap.h
@@ -176,4 +176,61 @@ void hard_dbg_remove_mseg(void* seg, UWord sz);
#endif /* HAVE_ERTS_MMAP */
+/* Marks the given memory region as unused without freeing it, letting the OS
+ * reclaim its physical memory with the promise that we'll get it back (without
+ * its contents) the next time it's accessed. */
+ERTS_GLB_INLINE void erts_mem_discard(void *p, UWord size);
+
+#if ERTS_GLB_INLINE_INCL_FUNC_DEF
+
+#ifdef VALGRIND
+ #include <valgrind/memcheck.h>
+
+ ERTS_GLB_INLINE void erts_mem_discard(void *ptr, UWord size) {
+ VALGRIND_MAKE_MEM_UNDEFINED(ptr, size);
+ }
+#elif defined(DEBUG)
+ /* Try to provoke crashes by filling the discard region with garbage. It's
+ * extremely hard to find bugs where we've discarded too much, as the
+ * region often retains its old contents if it's accessed before the OS
+ * reclaims it. */
+ ERTS_GLB_INLINE void erts_mem_discard(void *ptr, UWord size) {
+ static const char pattern[] = "DISCARDED";
+ char *data;
+ int i;
+
+ for(i = 0, data = ptr; i < size; i++) {
+ data[i] = pattern[i % sizeof(pattern)];
+ }
+ }
+#elif defined(HAVE_SYS_MMAN_H) && !(defined(__sun) || defined(__sun__))
+ #include <sys/mman.h>
+
+ ERTS_GLB_INLINE void erts_mem_discard(void *ptr, UWord size) {
+ #ifdef MADV_FREE
+ /* This is preferred as it doesn't necessarily free the pages right
+ * away, which is a bit faster than MADV_DONTNEED. */
+ madvise(ptr, size, MADV_FREE);
+ #else
+ madvise(ptr, size, MADV_DONTNEED);
+ #endif
+ }
+#elif defined(_WIN32)
+ #include <winbase.h>
+
+ /* MEM_RESET is defined on all supported versions of Windows, and has the
+ * same semantics as MADV_FREE. */
+ ERTS_GLB_INLINE void erts_mem_discard(void *ptr, UWord size) {
+ VirtualAlloc(ptr, size, MEM_RESET, PAGE_READWRITE);
+ }
+#else
+ /* Dummy implementation. */
+ ERTS_GLB_INLINE void erts_mem_discard(void *ptr, UWord size) {
+ (void)ptr;
+ (void)size;
+ }
+#endif
+
+#endif /* ERTS_GLB_INLINE_INCL_FUNC_DEF */
+
#endif /* ERL_MMAP_H__ */
diff --git a/erts/emulator/sys/common/erl_osenv.c b/erts/emulator/sys/common/erl_osenv.c
index 6a16377736..f055c5f854 100644
--- a/erts/emulator/sys/common/erl_osenv.c
+++ b/erts/emulator/sys/common/erl_osenv.c
@@ -167,9 +167,10 @@ void erts_osenv_init(erts_osenv_t *env) {
env->tree = NULL;
}
-static void destroy_foreach(env_rbtnode_t *node, void *_state) {
+static int destroy_foreach(env_rbtnode_t *node, void *_state, Sint reds) {
erts_free(ERTS_ALC_T_ENVIRONMENT, node);
(void)_state;
+ return 1;
}
void erts_osenv_clear(erts_osenv_t *env) {
@@ -182,7 +183,7 @@ struct __env_merge {
erts_osenv_t *env;
};
-static void merge_foreach(env_rbtnode_t *node, void *_state) {
+static int merge_foreach(env_rbtnode_t *node, void *_state, Sint reds) {
struct __env_merge *state = (struct __env_merge*)(_state);
env_rbtnode_t *existing_node;
@@ -191,6 +192,7 @@ static void merge_foreach(env_rbtnode_t *node, void *_state) {
if(existing_node == NULL || state->overwrite_existing) {
erts_osenv_put_native(state->env, &node->key, &node->value);
}
+ return 1;
}
void erts_osenv_merge(erts_osenv_t *env, const erts_osenv_t *with, int overwrite) {
@@ -208,7 +210,7 @@ struct __env_foreach_term {
void *user_state;
};
-static void foreach_term_wrapper(env_rbtnode_t *node, void *_state) {
+static int foreach_term_wrapper(env_rbtnode_t *node, void *_state, Sint reds) {
struct __env_foreach_term *state = (struct __env_foreach_term*)_state;
Eterm key, value;
@@ -218,6 +220,7 @@ static void foreach_term_wrapper(env_rbtnode_t *node, void *_state) {
node->value.length, (byte*)node->value.data);
state->user_callback(state->process, state->user_state, key, value);
+ return 1;
}
void erts_osenv_foreach_term(const erts_osenv_t *env, struct process *process,
@@ -314,10 +317,11 @@ struct __env_foreach_native {
void *user_state;
};
-static void foreach_native_wrapper(env_rbtnode_t *node, void *_state) {
+static int foreach_native_wrapper(env_rbtnode_t *node, void *_state, Sint reds) {
struct __env_foreach_native *state = (struct __env_foreach_native*)_state;
state->user_callback(state->user_state, &node->key, &node->value);
+ return 1;
}
void erts_osenv_foreach_native(const erts_osenv_t *env, void *state,
diff --git a/erts/emulator/sys/common/erl_poll.c b/erts/emulator/sys/common/erl_poll.c
index 27ffba58bd..c71d23f58c 100644
--- a/erts/emulator/sys/common/erl_poll.c
+++ b/erts/emulator/sys/common/erl_poll.c
@@ -872,8 +872,8 @@ update_pollset(ErtsPollSet *ps, int fd, ErtsPollOp op, ErtsPollEvents events)
}
}
-#if defined(EV_DISPATCH) && !defined(__OpenBSD__)
- /* If we have EV_DISPATCH we use it, unless we are on OpenBSD as the
+#if defined(EV_DISPATCH) && !(defined(__OpenBSD__) || defined(__NetBSD__))
+ /* If we have EV_DISPATCH we use it, unless we are on OpenBSD/NetBSD as the
behavior of EV_EOF seems to be edge triggered there and we need it
to be level triggered.
diff --git a/erts/emulator/sys/unix/sys_drivers.c b/erts/emulator/sys/unix/sys_drivers.c
index 2f5459bee5..042a091db1 100644
--- a/erts/emulator/sys/unix/sys_drivers.c
+++ b/erts/emulator/sys/unix/sys_drivers.c
@@ -732,7 +732,8 @@ static ErlDrvData spawn_start(ErlDrvPort port_num, char* name,
proto->u.start.fds[1] = ifd[1];
proto->u.start.fds[2] = stderrfd;
proto->u.start.port_id = opts->exit_status ? erts_drvport2id(port_num) : THE_NON_VALUE;
- if (erl_drv_port_control(forker_port, 'S', (char*)proto, sizeof(*proto))) {
+ if (erl_drv_port_control(forker_port, ERTS_FORKER_DRV_CONTROL_MAGIC_NUMBER,
+ (char*)proto, sizeof(*proto))) {
/* The forker port has been killed, we close both fd's which will
make open_port throw an epipe error */
close(ofd[0]);
@@ -759,6 +760,9 @@ static ErlDrvSSizeT spawn_control(ErlDrvData e, unsigned int cmd, char *buf,
ErtsSysDriverData *dd = (ErtsSysDriverData*)e;
ErtsSysForkerProto *proto = (ErtsSysForkerProto *)buf;
+ if (cmd != ERTS_SPAWN_DRV_CONTROL_MAGIC_NUMBER)
+ return -1;
+
ASSERT(len == sizeof(*proto));
ASSERT(proto->action == ErtsSysForkerProtoAction_SigChld);
@@ -799,6 +803,8 @@ static ErlDrvSSizeT fd_control(ErlDrvData drv_data,
{
int fd = (int)(long)drv_data;
char resbuff[2*sizeof(Uint32)];
+
+ command -= ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER;
switch (command) {
case FD_CTRL_OP_GET_WINSIZE:
{
@@ -810,7 +816,7 @@ static ErlDrvSSizeT fd_control(ErlDrvData drv_data,
}
break;
default:
- return 0;
+ return -1;
}
if (rlen < 2*sizeof(Uint32)) {
*rbuf = driver_alloc(2*sizeof(Uint32));
@@ -1693,7 +1699,8 @@ static void forker_sigchld(Eterm port_id, int error)
already used by the spawn_driver, we use control instead.
Note that when using erl_drv_port_control it is an asynchronous
control. */
- erl_drv_port_control(port_id, 'S', (char*)proto, sizeof(*proto));
+ erl_drv_port_control(port_id, ERTS_SPAWN_DRV_CONTROL_MAGIC_NUMBER,
+ (char*)proto, sizeof(*proto));
}
static void forker_ready_input(ErlDrvData e, ErlDrvEvent fd)
@@ -1778,6 +1785,9 @@ static ErlDrvSSizeT forker_control(ErlDrvData e, unsigned int cmd, char *buf,
ErlDrvPort port_num = (ErlDrvPort)e;
int res;
+ if (cmd != ERTS_FORKER_DRV_CONTROL_MAGIC_NUMBER)
+ return -1;
+
if (first_call) {
/*
* Do driver_select here when schedulers and their pollsets have started.
diff --git a/erts/emulator/sys/win32/sys.c b/erts/emulator/sys/win32/sys.c
index a1c630d68a..b95aadc9b2 100644
--- a/erts/emulator/sys/win32/sys.c
+++ b/erts/emulator/sys/win32/sys.c
@@ -186,7 +186,9 @@ void sys_primitive_init(HMODULE beam)
UWord
erts_sys_get_page_size(void)
{
- return (UWord) 4*1024; /* Guess 4 KB */
+ SYSTEM_INFO info;
+ GetSystemInfo(&info);
+ return (UWord)info.dwPageSize;
}
Uint
diff --git a/erts/emulator/test/Makefile b/erts/emulator/test/Makefile
index 6a064ec8d4..8c2054cb51 100644
--- a/erts/emulator/test/Makefile
+++ b/erts/emulator/test/Makefile
@@ -28,6 +28,24 @@ EBIN = .
# Target Specs
# ----------------------------------------------------
+SOCKET_MODULES = \
+ socket_test_lib \
+ socket_test_logger \
+ socket_test_evaluator \
+ socket_test_ttest_lib \
+ socket_test_ttest_tcp_gen \
+ socket_test_ttest_tcp_socket \
+ socket_test_ttest_tcp_client \
+ socket_test_ttest_tcp_client_gen \
+ socket_test_ttest_tcp_client_socket \
+ socket_test_ttest_tcp_server \
+ socket_test_ttest_tcp_server_gen \
+ socket_test_ttest_tcp_server_socket \
+ socket_SUITE
+
+NET_MODULES = \
+ net_SUITE
+
MODULES= \
a_SUITE \
after_SUITE \
@@ -84,6 +102,7 @@ MODULES= \
monitor_SUITE \
multi_load_SUITE \
nested_SUITE \
+ $(NET_MODULES) \
nif_SUITE \
node_container_SUITE \
nofrag_SUITE \
@@ -106,6 +125,7 @@ MODULES= \
sensitive_SUITE \
signal_SUITE \
smoke_test_SUITE \
+ $(SOCKET_MODULES) \
statistics_SUITE \
system_info_SUITE \
system_profile_SUITE \
@@ -130,6 +150,7 @@ MODULES= \
ignore_cores \
dgawd_handler \
random_iolist \
+ erts_test_utils \
crypto_reference
NO_OPT= bs_bincomp \
@@ -152,8 +173,14 @@ NATIVE_MODULES= $(NATIVE:%=%_native_SUITE)
NATIVE_ERL_FILES= $(NATIVE_MODULES:%=%.erl)
ERL_FILES= $(MODULES:%=%.erl)
+HRL_FILES= \
+ socket_test_evaluator.hrl \
+ socket_test_ttest.hrl \
+ socket_test_ttest_client.hrl
TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR))
+NET_TARGETS = $(NET_MODULES:%=$(EBIN)/%.$(EMULATOR))
+SOCKET_TARGETS = $(SOCKET_MODULES:%=$(EBIN)/%.$(EMULATOR))
EMAKEFILE=Emakefile
@@ -200,6 +227,10 @@ clean:
docs:
+targets: $(TARGET_FILES)
+socket_targets: $(SOCKET_TARGETS)
+net_targets: $(NET_TARGETS)
+
# ----------------------------------------------------
# Special targets
# ----------------------------------------------------
@@ -220,7 +251,7 @@ release_spec:
release_tests_spec: make_emakefile
$(INSTALL_DIR) "$(RELSYSDIR)"
$(INSTALL_DATA) $(EMAKEFILE) $(TEST_SPEC_FILES) \
- $(ERL_FILES) "$(RELSYSDIR)"
+ $(ERL_FILES) $(HRL_FILES) "$(RELSYSDIR)"
$(INSTALL_DATA) $(NO_OPT_ERL_FILES) "$(RELSYSDIR)"
$(INSTALL_DATA) $(NATIVE_ERL_FILES) "$(RELSYSDIR)"
chmod -R u+w "$(RELSYSDIR)"
diff --git a/erts/emulator/test/alloc_SUITE.erl b/erts/emulator/test/alloc_SUITE.erl
index 343afe85e6..4e0243c1cd 100644
--- a/erts/emulator/test/alloc_SUITE.erl
+++ b/erts/emulator/test/alloc_SUITE.erl
@@ -71,7 +71,8 @@ migration(Cfg) ->
%% Disable driver_alloc to avoid recursive alloc_util calls
%% through enif_mutex_create() in my_creating_mbc().
drv_case(Cfg, concurrent, "+MZe true +MRe false"),
- drv_case(Cfg, concurrent, "+MZe true +MRe false +MZas ageffcbf").
+ drv_case(Cfg, concurrent, "+MZe true +MRe false +MZas ageffcbf"),
+ drv_case(Cfg, concurrent, "+MZe true +MRe false +MZas chaosff").
erts_mmap(Config) when is_list(Config) ->
case {os:type(), mmsc_flags()} of
diff --git a/erts/emulator/test/bif_SUITE.erl b/erts/emulator/test/bif_SUITE.erl
index 9e7bcd5255..3eedf2f6a6 100644
--- a/erts/emulator/test/bif_SUITE.erl
+++ b/erts/emulator/test/bif_SUITE.erl
@@ -37,7 +37,8 @@
group_leader_prio/1, group_leader_prio_dirty/1,
is_process_alive/1,
process_info_blast/1,
- os_env_case_sensitivity/1]).
+ os_env_case_sensitivity/1,
+ test_length/1]).
suite() ->
[{ct_hooks,[ts_install_cth]},
@@ -52,7 +53,8 @@ all() ->
erl_crash_dump_bytes, min_max, erlang_halt, is_builtin,
error_stacktrace, error_stacktrace_during_call_trace,
group_leader_prio, group_leader_prio_dirty,
- is_process_alive, process_info_blast, os_env_case_sensitivity].
+ is_process_alive, process_info_blast, os_env_case_sensitivity,
+ test_length].
%% Uses erlang:display to test that erts_printf does not do deep recursion
display(Config) when is_list(Config) ->
@@ -1181,7 +1183,53 @@ consume_msgs() ->
after 0 ->
ok
end.
-
+
+%% Test that length/1 returns the correct result after trapping, and
+%% also that the argument is correct in the stacktrace for a badarg
+%% exception.
+
+test_length(_Config) ->
+ {Start,Inc} = case test_server:timetrap_scale_factor() of
+ 1 -> {16*4000,3977};
+ _ -> {100,1}
+ end,
+ Good = lists:reverse(lists:seq(1, Start)),
+ Bad = Good ++ [bad|cons],
+ test_length(Start, 10*Start, Inc, Good, Bad),
+
+ %% Test that calling length/1 from a match spec works.
+ MsList = lists:seq(1, 2*Start),
+ MsInput = [{tag,Good},{tag,MsList}],
+ Ms0 = [{{tag,'$1'},[{'>',{length,'$1'},Start}],['$1']}],
+ Ms = ets:match_spec_compile(Ms0),
+ [MsList] = ets:match_spec_run(MsInput, Ms),
+ ok.
+
+test_length(I, N, Inc, Good, Bad) when I < N ->
+ Length = id(length),
+ I = length(Good),
+ I = erlang:Length(Good),
+
+ %% Test length/1 in guards.
+ if
+ length(Good) =:= I ->
+ ok
+ end,
+ if
+ length(Bad) =:= I ->
+ error(should_fail);
+ true ->
+ ok
+ end,
+
+ {'EXIT',{badarg,[{erlang,length,[[I|_]],_}|_]}} = (catch length(Bad)),
+ {'EXIT',{badarg,[{erlang,length,[[I|_]],_}|_]}} = (catch erlang:Length(Bad)),
+ IncSeq = lists:seq(I + 1, I + Inc),
+ test_length(I+Inc, N, Inc,
+ lists:reverse(IncSeq, Good),
+ lists:reverse(IncSeq, Bad));
+test_length(_, _, _, _, _) -> ok.
+
%% helpers
id(I) -> I.
diff --git a/erts/emulator/test/big_SUITE.erl b/erts/emulator/test/big_SUITE.erl
index 29508ffc7c..3b9b9e5989 100644
--- a/erts/emulator/test/big_SUITE.erl
+++ b/erts/emulator/test/big_SUITE.erl
@@ -168,7 +168,11 @@ eval({op,_,Op,A0,B0}, LFH) ->
Res = eval_op(Op, A, B),
erlang:garbage_collect(),
Res;
-eval({integer,_,I}, _) -> I;
+eval({integer,_,I}, _) ->
+ %% "Parasitic" ("symbiotic"?) test of squaring all numbers
+ %% found in the test data.
+ test_squaring(I),
+ I;
eval({call,_,{atom,_,Local},Args0}, LFH) ->
Args = eval_list(Args0, LFH),
LFH(Local, Args).
@@ -192,6 +196,18 @@ eval_op('bxor', A, B) -> A bxor B;
eval_op('bsl', A, B) -> A bsl B;
eval_op('bsr', A, B) -> A bsr B.
+test_squaring(I) ->
+ %% Multiplying an integer by itself is specially optimized, so we
+ %% should take special care to test squaring. The optimization
+ %% will kick in when the two operands have the same address.
+ Sqr = I * I,
+
+ %% This expression will be multiplied in the usual way, because
+ %% the the two operands for '*' are stored at different addresses.
+ Sqr = I * ((I + id(1)) - id(1)),
+
+ ok.
+
%% Built in test functions
fac(0) -> 1;
diff --git a/erts/emulator/test/binary_SUITE.erl b/erts/emulator/test/binary_SUITE.erl
index 23c675733c..1406ddc9dc 100644
--- a/erts/emulator/test/binary_SUITE.erl
+++ b/erts/emulator/test/binary_SUITE.erl
@@ -40,6 +40,7 @@
%%
-include_lib("common_test/include/ct.hrl").
+-include_lib("common_test/include/ct_event.hrl").
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
@@ -50,6 +51,14 @@
terms/1, terms_float/1, float_middle_endian/1,
b2t_used_big/1,
external_size/1, t_iolist_size/1,
+ t_iolist_size_huge_list/1,
+ t_iolist_size_huge_bad_arg_list/1,
+ t_iolist_size_shallow_trapping/1,
+ t_iolist_size_shallow_short_lists/1,
+ t_iolist_size_shallow_tiny_lists/1,
+ t_iolist_size_deep_trapping/1,
+ t_iolist_size_deep_short_lists/1,
+ t_iolist_size_deep_tiny_lists/1,
t_hash/1,
bad_size/1,
bad_term_to_binary/1,
@@ -75,6 +84,9 @@ all() ->
t_split_binary, bad_split,
bad_list_to_binary, bad_binary_to_list, terms,
terms_float, float_middle_endian, external_size, t_iolist_size,
+ t_iolist_size_huge_list,
+ t_iolist_size_huge_bad_arg_list,
+ {group, iolist_size_benchmarks},
b2t_used_big,
bad_binary_to_term_2, safe_binary_to_term2,
bad_binary_to_term, bad_terms, t_hash, bad_size,
@@ -86,13 +98,36 @@ all() ->
error_after_yield, cmp_old_impl].
groups() ->
- [].
+ [
+ {
+ iolist_size_benchmarks,
+ [],
+ [t_iolist_size_shallow_trapping,
+ t_iolist_size_shallow_short_lists,
+ t_iolist_size_shallow_tiny_lists,
+ t_iolist_size_deep_trapping,
+ t_iolist_size_deep_short_lists,
+ t_iolist_size_deep_tiny_lists
+ ]
+ }
+ ].
init_per_suite(Config) ->
+ A0 = case application:start(sasl) of
+ ok -> [sasl];
+ _ -> []
+ end,
+ A = case application:start(os_mon) of
+ ok -> [os_mon|A0];
+ _ -> A0
+ end,
+ [{started_apps, A}|Config].
+
+end_per_suite(Config) ->
+ As = proplists:get_value(started_apps, Config),
+ lists:foreach(fun (A) -> application:stop(A) end, As),
Config.
-end_per_suite(_Config) ->
- ok.
init_per_group(_GroupName, Config) ->
Config.
@@ -615,6 +650,143 @@ build_iolist(N0, Base) ->
[47,L,L|Seq]
end.
+approx_4GB_bin() ->
+ Bin = lists:duplicate(4194304, 255),
+ BinRet = erlang:iolist_to_binary(lists:duplicate(1124, Bin)),
+ BinRet.
+
+duplicate_iolist(IOList, 0) ->
+ IOList;
+duplicate_iolist(IOList, NrOfTimes) ->
+ duplicate_iolist([IOList, IOList], NrOfTimes - 1).
+
+t_iolist_size_huge_list(Config) when is_list(Config) ->
+ run_when_enough_resources(
+ fun() ->
+ {TimeToCreateIOList, IOList} = timer:tc(fun()->duplicate_iolist(approx_4GB_bin(), 32) end),
+ {IOListSizeTime, CalculatedSize} = timer:tc(fun()->erlang:iolist_size(IOList) end),
+ 20248183924657750016 = CalculatedSize,
+ {comment, io_lib:format("Time to create iolist: ~f s. Time to calculate size: ~f s.",
+ [TimeToCreateIOList / 1000000, IOListSizeTime / 1000000])}
+ end).
+
+t_iolist_size_huge_bad_arg_list(Config) when is_list(Config) ->
+ run_when_enough_resources(
+ fun() ->
+ P = self(),
+ spawn_link(fun()-> IOListTmp = duplicate_iolist(approx_4GB_bin(), 32),
+ IOList = [IOListTmp, [badarg]],
+ {'EXIT',{badarg,_}} = (catch erlang:iolist_size(IOList)),
+ P ! ok
+ end),
+ receive ok -> ok end
+ end).
+
+%% iolist_size tests for shallow lists
+
+t_iolist_size_shallow_trapping(Config) when is_list(Config) ->
+ Lengths = [2000, 20000, 200000, 200000, 2000000, 20000000],
+ run_iolist_size_test_and_benchmark(Lengths, fun make_shallow_iolist/2).
+
+t_iolist_size_shallow_short_lists(Config) when is_list(Config) ->
+ Lengths = lists:duplicate(15000, 300),
+ run_iolist_size_test_and_benchmark(Lengths, fun make_shallow_iolist/2).
+
+t_iolist_size_shallow_tiny_lists(Config) when is_list(Config) ->
+ Lengths = lists:duplicate(250000, 18),
+ run_iolist_size_test_and_benchmark(Lengths, fun make_shallow_iolist/2).
+
+make_shallow_iolist(SizeDiv2, LastItem) ->
+ lists:map(
+ fun(I) ->
+ case I of
+ SizeDiv2 -> [1, LastItem];
+ _ -> [1, 1]
+ end
+ end,
+ lists:seq(1, SizeDiv2)).
+
+%% iolist_size tests for deep lists
+
+t_iolist_size_deep_trapping(Config) when is_list(Config) ->
+ Lengths = [2000, 20000, 200000, 200000, 2000000, 10000000],
+ run_iolist_size_test_and_benchmark(Lengths, fun make_deep_iolist/2).
+
+t_iolist_size_deep_short_lists(Config) when is_list(Config) ->
+ Lengths = lists:duplicate(10000, 300),
+ run_iolist_size_test_and_benchmark(Lengths, fun make_deep_iolist/2).
+
+t_iolist_size_deep_tiny_lists(Config) when is_list(Config) ->
+ Lengths = lists:duplicate(150000, 18),
+ run_iolist_size_test_and_benchmark(Lengths, fun make_deep_iolist/2).
+
+make_deep_iolist(1, LastItem) ->
+ [1, LastItem];
+make_deep_iolist(Depth, LastItem) ->
+ [[1, 1], make_deep_iolist(Depth - 1, LastItem)].
+
+% Helper functions for iolist_size tests
+
+run_iolist_size_test_and_benchmark(Lengths, ListGenerator) ->
+ run_when_enough_resources(
+ fun() ->
+ GoodListsWithSizes =
+ lists:map(fun(Length) -> {Length*2, ListGenerator(Length, 1)} end, Lengths),
+ BadListsWithSizes =
+ lists:map(fun(Length) -> {Length*2, ListGenerator(Length, bad)} end, Lengths),
+ erlang:garbage_collect(),
+ report_throughput(
+ fun() ->
+ lists:foreach(
+ fun(_)->
+ lists:foreach(
+ fun({Size, List}) -> Size = iolist_size(List) end,
+ GoodListsWithSizes),
+ lists:foreach(
+ fun({_, List}) -> {'EXIT',_} = (catch (iolist_size(List))) end,
+ BadListsWithSizes)
+ end,
+ lists:seq(1,3))
+ end,
+ lists:sum(Lengths)*4)
+ end).
+
+report_throughput(Fun, NrOfItems) ->
+ Parent = self(),
+ spawn(fun() -> Parent ! timer:tc(Fun) end),
+ {Time, _} = receive D -> D end,
+ ItemsPerMicrosecond = NrOfItems / Time,
+ ct_event:notify(#event{ name = benchmark_data, data = [{value, ItemsPerMicrosecond}]}),
+ {comment, io_lib:format("Items per microsecond: ~p, Nr of items: ~p, Benchmark time: ~p seconds)",
+ [ItemsPerMicrosecond, NrOfItems, Time/1000000])}.
+
+total_memory() ->
+ %% Total memory in GB.
+ try
+ MemoryData = memsup:get_system_memory_data(),
+ case lists:keysearch(total_memory, 1, MemoryData) of
+ {value, {total_memory, TM}} ->
+ TM div (1024*1024*1024);
+ false ->
+ {value, {system_total_memory, STM}} =
+ lists:keysearch(system_total_memory, 1, MemoryData),
+ STM div (1024*1024*1024)
+ end
+ catch
+ _ : _ ->
+ undefined
+ end.
+
+run_when_enough_resources(Fun) ->
+ case {total_memory(), erlang:system_info(wordsize)} of
+ {Mem, 8} when is_integer(Mem) andalso Mem >= 15 ->
+ Fun();
+ {Mem, WordSize} ->
+ {skipped,
+ io_lib:format("Not enough resources (System Memory >= ~p, Word Size = ~p)",
+ [Mem, WordSize])}
+ end.
+
%% OTP-4053
bad_binary_to_term_2(Config) when is_list(Config) ->
diff --git a/erts/emulator/test/bs_construct_SUITE.erl b/erts/emulator/test/bs_construct_SUITE.erl
index ce50bcdd86..ad05cb3689 100644
--- a/erts/emulator/test/bs_construct_SUITE.erl
+++ b/erts/emulator/test/bs_construct_SUITE.erl
@@ -26,7 +26,7 @@
init_per_suite/1, end_per_suite/1,
test1/1, test2/1, test3/1, test4/1, test5/1, testf/1,
not_used/1, in_guard/1,
- mem_leak/1, coerce_to_float/1, bjorn/1,
+ mem_leak/1, coerce_to_float/1, bjorn/1, append_empty_is_same/1,
huge_float_field/1, huge_binary/1, system_limit/1, badarg/1,
copy_writable_binary/1, kostis/1, dynamic/1, bs_add/1,
otp_7422/1, zero_width/1, bad_append/1, bs_add_overflow/1]).
@@ -39,7 +39,7 @@ suite() ->
all() ->
[test1, test2, test3, test4, test5, testf, not_used,
- in_guard, mem_leak, coerce_to_float, bjorn,
+ in_guard, mem_leak, coerce_to_float, bjorn, append_empty_is_same,
huge_float_field, huge_binary, system_limit, badarg,
copy_writable_binary, kostis, dynamic, bs_add, otp_7422, zero_width,
bad_append, bs_add_overflow].
@@ -520,6 +520,16 @@ do_more(Bin, Sz) ->
do_something() ->
throw(blurf).
+append_empty_is_same(Config) when is_list(Config) ->
+ NonWritableBin = <<"123">>,
+ true = erts_debug:same(NonWritableBin, append(NonWritableBin, <<>>)),
+ WritableBin = <<(id(<<>>))/binary,0,1,2,3,4,5,6,7>>,
+ true = erts_debug:same(WritableBin, append(WritableBin, <<>>)),
+ ok.
+
+append(A, B) ->
+ <<A/binary, B/binary>>.
+
huge_float_field(Config) when is_list(Config) ->
{'EXIT',{badarg,_}} = (catch <<0.0:9/float-unit:8>>),
huge_float_check(catch <<0.0:67108865/float-unit:64>>),
diff --git a/erts/emulator/test/call_trace_SUITE.erl b/erts/emulator/test/call_trace_SUITE.erl
index d19f7f81ad..742592f88e 100644
--- a/erts/emulator/test/call_trace_SUITE.erl
+++ b/erts/emulator/test/call_trace_SUITE.erl
@@ -1395,7 +1395,7 @@ seq(M, N, R) when M =< N ->
seq(M, N-1, [N|R]);
seq(_, _, R) -> R.
-%% lists:reverse can not be called since it is traced
+%% lists:reverse cannot be called since it is traced
reverse(L) ->
reverse(L, []).
%%
diff --git a/erts/emulator/test/code_SUITE.erl b/erts/emulator/test/code_SUITE.erl
index 0444ba4f89..493c6ebe99 100644
--- a/erts/emulator/test/code_SUITE.erl
+++ b/erts/emulator/test/code_SUITE.erl
@@ -332,6 +332,7 @@ constant_pools(Config) when is_list(Config) ->
A = literals:a(),
B = literals:b(),
C = literals:huge_bignum(),
+ D = literals:funs(),
process_flag(trap_exit, true),
Self = self(),
@@ -345,7 +346,7 @@ constant_pools(Config) when is_list(Config) ->
true = erlang:purge_module(literals),
NoOldHeap ! done,
receive
- {'EXIT',NoOldHeap,{A,B,C}} ->
+ {'EXIT',NoOldHeap,{A,B,C,D}} ->
ok;
Other ->
ct:fail({unexpected,Other})
@@ -362,7 +363,7 @@ constant_pools(Config) when is_list(Config) ->
erlang:purge_module(literals),
OldHeap ! done,
receive
- {'EXIT',OldHeap,{A,B,C,[1,2,3|_]=Seq}} when length(Seq) =:= 16 ->
+ {'EXIT',OldHeap,{A,B,C,D,[1,2,3|_]=Seq}} when length(Seq) =:= 16 ->
ok
end,
@@ -390,7 +391,7 @@ constant_pools(Config) when is_list(Config) ->
{'DOWN', Mon, process, Hib, Reason} ->
{undef, [{no_module,
no_function,
- [{A,B,C,[1,2,3|_]=Seq}], _}]} = Reason,
+ [{A,B,C,D,[1,2,3|_]=Seq}], _}]} = Reason,
16 = length(Seq)
end,
HeapSz = TotHeapSz, %% Ensure restored to hibernated state...
@@ -400,7 +401,9 @@ constant_pools(Config) when is_list(Config) ->
no_old_heap(Parent) ->
A = literals:a(),
B = literals:b(),
- Res = {A,B,literals:huge_bignum()},
+ C = literals:huge_bignum(),
+ D = literals:funs(),
+ Res = {A,B,C,D},
Parent ! go,
receive
done ->
@@ -410,7 +413,9 @@ no_old_heap(Parent) ->
old_heap(Parent) ->
A = literals:a(),
B = literals:b(),
- Res = {A,B,literals:huge_bignum(),lists:seq(1, 16)},
+ C = literals:huge_bignum(),
+ D = literals:funs(),
+ Res = {A,B,C,D,lists:seq(1, 16)},
create_old_heap(),
Parent ! go,
receive
@@ -421,7 +426,9 @@ old_heap(Parent) ->
hibernated(Parent) ->
A = literals:a(),
B = literals:b(),
- Res = {A,B,literals:huge_bignum(),lists:seq(1, 16)},
+ C = literals:huge_bignum(),
+ D = literals:funs(),
+ Res = {A,B,C,D,lists:seq(1, 16)},
Parent ! go,
erlang:hibernate(no_module, no_function, [Res]).
@@ -755,7 +762,8 @@ t_copy_literals_frags(Config) when is_list(Config) ->
0, 1, 2, 3, 4, 5, 6, 7,
8, 9,10,11,12,13,14,15,
0, 1, 2, 3, 4, 5, 6, 7,
- 8, 9,10,11,12,13,14,15>>}]),
+ 8, 9,10,11,12,13,14,15>>},
+ {f, fun ?MODULE:all/0}]),
{module, ?mod} = erlang:load_module(?mod, Bin),
N = 6000,
@@ -796,6 +804,7 @@ literal_receiver() ->
C = ?mod:c(),
D = ?mod:d(),
E = ?mod:e(),
+ F = ?mod:f(),
literal_receiver();
{Pid, sender_confirm} ->
io:format("sender confirm ~w~n", [Pid]),
@@ -811,7 +820,8 @@ literal_sender(N, Recv) ->
?mod:b(),
?mod:c(),
?mod:d(),
- ?mod:e()]},
+ ?mod:e(),
+ ?mod:f()]},
literal_sender(N - 1, Recv).
literal_switcher() ->
diff --git a/erts/emulator/test/code_SUITE_data/literals.erl b/erts/emulator/test/code_SUITE_data/literals.erl
index 7c3b0ebe73..13c8b412b0 100644
--- a/erts/emulator/test/code_SUITE_data/literals.erl
+++ b/erts/emulator/test/code_SUITE_data/literals.erl
@@ -19,7 +19,8 @@
%%
-module(literals).
--export([a/0,b/0,huge_bignum/0,binary/0,unused_binaries/0,bits/0]).
+-export([a/0,b/0,huge_bignum/0,funs/0,
+ binary/0,unused_binaries/0,bits/0]).
-export([msg1/0,msg2/0,msg3/0,msg4/0,msg5/0]).
a() ->
@@ -108,3 +109,8 @@ msg2() -> {"hello","world"}.
msg3() -> <<"halloj">>.
msg4() -> #{ 1=> "hello", b => "world"}.
msg5() -> {1,2,3,4,5,6}.
+
+funs() ->
+ %% Literal funs (in a non-literal list).
+ [fun ?MODULE:a/0,
+ fun() -> ok end]. %No environment.
diff --git a/erts/emulator/test/distribution_SUITE.erl b/erts/emulator/test/distribution_SUITE.erl
index 885c66331c..4f70b51aa0 100644
--- a/erts/emulator/test/distribution_SUITE.erl
+++ b/erts/emulator/test/distribution_SUITE.erl
@@ -62,7 +62,12 @@
bad_dist_ext_control/1,
bad_dist_ext_connection_id/1,
bad_dist_ext_size/1,
- start_epmd_false/1, epmd_module/1]).
+ start_epmd_false/1, epmd_module/1,
+ bad_dist_fragments/1,
+ message_latency_large_message/1,
+ message_latency_large_link_exit/1,
+ message_latency_large_monitor_exit/1,
+ message_latency_large_exit2/1]).
%% Internal exports.
-export([sender/3, receiver2/2, dummy_waiter/0, dead_process/0,
@@ -90,7 +95,8 @@ all() ->
dist_parallel_send, atom_roundtrip, unicode_atom_roundtrip,
atom_roundtrip_r16b,
contended_atom_cache_entry, contended_unicode_atom_cache_entry,
- bad_dist_structure, {group, bad_dist_ext},
+ {group, message_latency},
+ {group, bad_dist}, {group, bad_dist_ext},
start_epmd_false, epmd_module].
groups() ->
@@ -100,10 +106,18 @@ groups() ->
{trap_bif, [], [trap_bif_1, trap_bif_2, trap_bif_3]},
{dist_auto_connect, [],
[dist_auto_connect_never, dist_auto_connect_once]},
+ {bad_dist, [],
+ [bad_dist_structure, bad_dist_fragments]},
{bad_dist_ext, [],
[bad_dist_ext_receive, bad_dist_ext_process_info,
bad_dist_ext_size,
- bad_dist_ext_control, bad_dist_ext_connection_id]}].
+ bad_dist_ext_control, bad_dist_ext_connection_id]},
+ {message_latency, [],
+ [message_latency_large_message,
+ message_latency_large_link_exit,
+ message_latency_large_monitor_exit,
+ message_latency_large_exit2]}
+ ].
%% Tests pinging a node in different ways.
ping(Config) when is_list(Config) ->
@@ -568,10 +582,20 @@ do_busy_test(Node, Fun) ->
%% Don't match arity; it is different in debug and
%% optimized emulator
[{status, suspended},
- {current_function, {erlang, bif_return_trap, _}}] = Pinfo,
+ {current_function, {Mod, Func, _}}] = Pinfo,
+ if
+ Mod =:= erlang andalso Func =:= bif_return_trap ->
+ true;
+ Mod =:= erts_internal andalso Func =:= dsend_continue_trap ->
+ true;
+ true ->
+ ct:fail({incorrect, pinfo, Pinfo})
+ end,
receive
{'DOWN', M, process, P, Reason} ->
io:format("~p died with exit reason ~p~n", [P, Reason]),
+ verify_nc(node()),
+ verify_nc(Node),
normal = Reason
end
end.
@@ -931,7 +955,9 @@ dist_auto_connect_never(Config) when is_list(Config) ->
ok;
{do_dist_auto_connect, Error} ->
{error, Error};
- Other ->
+ %% The io:formats in dos_dist_auto_connect will
+ %% generate port output messages that are ok
+ Other when not is_port(element(1, Other))->
{error, Other}
after 32000 ->
timeout
@@ -1364,6 +1390,131 @@ get_conflicting_unicode_atoms(CIX, N) ->
get_conflicting_unicode_atoms(CIX, N)
end.
+
+%% The message_latency_large tests that small distribution messages are
+%% not blocked by other large distribution messages. Basically it tests
+%% that fragmentation of distribution messages works.
+message_latency_large_message(Config) when is_list(Config) ->
+ measure_latency_large_message(?FUNCTION_NAME, fun(Dropper, Payload) -> Dropper ! Payload end).
+
+message_latency_large_exit2(Config) when is_list(Config) ->
+ measure_latency_large_message(?FUNCTION_NAME, fun erlang:exit/2).
+
+message_latency_large_link_exit(Config) when is_list(Config) ->
+ message_latency_large_exit(?FUNCTION_NAME, fun erlang:link/1).
+
+message_latency_large_monitor_exit(Config) when is_list(Config) ->
+ message_latency_large_exit(?FUNCTION_NAME, fun(Dropper) ->
+ Dropper ! {monitor, self()},
+ receive ok -> ok end
+ end).
+
+message_latency_large_exit(Nodename, ReasonFun) ->
+ measure_latency_large_message(
+ Nodename,
+ fun(Dropper, Payload) ->
+ Pid = spawn(fun() ->
+ receive go -> ok end,
+ ReasonFun(Dropper),
+ exit(Payload)
+ end),
+
+ FlushTrace = fun F() ->
+ receive
+ {trace, Pid, _, _} = M ->
+ F()
+ after 0 ->
+ ok
+ end
+ end,
+
+ erlang:trace(Pid, true, [exiting]),
+ Pid ! go,
+ receive
+ {trace, Pid, out_exited, 0} ->
+ FlushTrace()
+ end
+ end).
+
+measure_latency_large_message(Nodename, DataFun) ->
+
+ erlang:system_monitor(self(), [busy_dist_port]),
+
+ {ok, N} = start_node(Nodename),
+
+ Dropper = spawn(N, fun F() ->
+ process_flag(trap_exit, true),
+ receive
+ {monitor,Pid} ->
+ erlang:monitor(process, Pid),
+ Pid ! ok;
+ _ -> ok
+ end,
+ F()
+ end),
+
+ Echo = spawn(N, fun F() -> receive {From, Msg} -> From ! Msg, F() end end),
+
+ %% Test 32 MB and 320 MB and test the latency difference of sent messages
+ Payloads = [{I, <<0:(I * 32 * 1024 * 1024 * 8)>>} || I <- [1,10]],
+
+ IndexTimes = [{I, measure_latency(DataFun, Dropper, Echo, P)}
+ || {I, P} <- Payloads],
+
+ Times = [ Time || {_I, Time} <- IndexTimes],
+
+ ct:pal("~p",[IndexTimes]),
+
+ case {lists:max(Times), lists:min(Times)} of
+ {Max, Min} when Max * 0.25 > Min ->
+ ct:fail({incorrect_latency, IndexTimes});
+ _ ->
+ ok
+ end.
+
+measure_latency(DataFun, Dropper, Echo, Payload) ->
+
+ flush(),
+
+ Senders = [spawn_monitor(
+ fun F() ->
+ DataFun(Dropper, Payload),
+ receive
+ die -> ok
+ after 0 ->
+ F()
+ end
+ end) || _ <- lists:seq(1,2)],
+
+ [receive
+ {monitor, _Sender, busy_dist_port, _Info} = M ->
+ ok
+ end || _ <- lists:seq(1,10)],
+
+ {TS, _} =
+ timer:tc(fun() ->
+ [begin
+ Echo ! {self(), hello},
+ receive hello -> ok end
+ end || _ <- lists:seq(1,100)]
+ end),
+ [begin
+ Sender ! die,
+ receive
+ {'DOWN', Ref, process, _, _} ->
+ ok
+ end
+ end || {Sender, Ref} <- Senders],
+ TS.
+
+flush() ->
+ receive
+ _ ->
+ flush()
+ after 0 ->
+ ok
+ end.
+
-define(COOKIE, '').
-define(DOP_LINK, 1).
-define(DOP_SEND, 2).
@@ -1382,6 +1533,15 @@ get_conflicting_unicode_atoms(CIX, N) ->
-define(DOP_DEMONITOR_P, 20).
-define(DOP_MONITOR_P_EXIT, 21).
+-define(DOP_SEND_SENDER, 22).
+-define(DOP_SEND_SENDER_TT, 23).
+
+-define(DOP_PAYLOAD_EXIT, 24).
+-define(DOP_PAYLOAD_EXIT_TT, 25).
+-define(DOP_PAYLOAD_EXIT2, 26).
+-define(DOP_PAYLOAD_EXIT2_TT, 27).
+-define(DOP_PAYLOAD_MONITOR_P_EXIT, 28).
+
start_monitor(Offender,P) ->
Parent = self(),
Q = spawn(Offender,
@@ -1515,7 +1675,145 @@ bad_dist_structure(Config) when is_list(Config) ->
stop_node(Victim),
ok.
+%% Test various dist fragmentation errors
+bad_dist_fragments(Config) when is_list(Config) ->
+ ct:timetrap({seconds, 15}),
+
+ {ok, Offender} = start_node(bad_dist_fragment_offender),
+ {ok, Victim} = start_node(bad_dist_fragment_victim),
+
+ Msg = iolist_to_binary(dmsg_ext(lists:duplicate(255,255))),
+
+ start_node_monitors([Offender,Victim]),
+ Parent = self(),
+ P = spawn(Victim,
+ fun () ->
+ process_flag(trap_exit,true),
+ Parent ! {self(), started},
+ receive check_msgs -> ok end,
+ bad_dist_struct_check_msgs([one,
+ two]),
+ Parent ! {self(), messages_checked},
+ receive done -> ok end
+ end),
+ receive {P, started} -> ok end,
+ pong = rpc:call(Victim, net_adm, ping, [Offender]),
+ verify_up(Offender, Victim),
+ true = lists:member(Offender, rpc:call(Victim, erlang, nodes, [])),
+ start_monitor(Offender,P),
+ P ! one,
+
+ start_monitor(Offender,P),
+ send_bad_fragments(Offender, Victim, P,{?DOP_SEND,?COOKIE,P},3,
+ [{frg, 1, binary:part(Msg, 10,byte_size(Msg)-10)}]),
+ start_monitor(Offender,P),
+ send_bad_fragments(Offender, Victim, P,{?DOP_SEND,?COOKIE,P},3,
+ [{hdr, 3, binary:part(Msg, 0,10)},
+ {frg, 1, binary:part(Msg, 10,byte_size(Msg)-10)}]),
+
+ start_monitor(Offender,P),
+ send_bad_fragments(Offender, Victim, P,{?DOP_SEND,?COOKIE,P},3,
+ [{hdr, 3, binary:part(Msg, 0,10)},
+ {hdr, 3, binary:part(Msg, 0,10)}]),
+
+ start_monitor(Offender,P),
+ send_bad_fragments(Offender, Victim, P,{?DOP_SEND,?COOKIE,P,broken},3,
+ [{hdr, 1, binary:part(Msg, 10,byte_size(Msg)-10)}]),
+
+ start_monitor(Offender,P),
+ send_bad_fragments(Offender, Victim, P,{?DOP_SEND,?COOKIE,P},3,
+ [{hdr, 3, binary:part(Msg, 10,byte_size(Msg)-10)},
+ close]),
+
+ start_monitor(Offender,P),
+ ExitVictim = spawn(Victim, fun() -> receive ok -> ok end end),
+ send_bad_fragments(Offender, Victim, P,{?DOP_PAYLOAD_EXIT,P,ExitVictim},2,
+ [{hdr, 1, [131]}]),
+
+ start_monitor(Offender,P),
+ Exit2Victim = spawn(Victim, fun() -> receive ok -> ok end end),
+ send_bad_fragments(Offender, Victim, P,{?DOP_PAYLOAD_EXIT2,P,ExitVictim},2,
+ [{hdr, 1, [132]}]),
+
+ start_monitor(Offender,P),
+ DownVictim = spawn(Victim, fun() -> receive ok -> ok end end),
+ DownRef = erlang:monitor(process, DownVictim),
+ send_bad_fragments(Offender, Victim, P,{?DOP_PAYLOAD_MONITOR_P_EXIT,P,DownVictim,DownRef},2,
+ [{hdr, 1, [133]}]),
+
+ P ! two,
+ P ! check_msgs,
+ receive
+ {P, messages_checked} -> ok
+ after 5000 ->
+ exit(victim_is_dead)
+ end,
+
+ {message_queue_len, 0}
+ = rpc:call(Victim, erlang, process_info, [P, message_queue_len]),
+
+ unlink(P),
+ P ! done,
+ stop_node(Offender),
+ stop_node(Victim),
+ ok.
+
+dmsg_frag_hdr(Frag) ->
+ dmsg_frag_hdr(erlang:phash2(self()), Frag).
+dmsg_frag_hdr(Seq, Frag) ->
+ [131, $E, uint64_be(Seq), uint64_be(Frag), 0].
+
+dmsg_frag(Frag) ->
+ dmsg_frag(erlang:phash2(self()), Frag).
+dmsg_frag(Seq, Frag) ->
+ [131, $F, uint64_be(Seq), uint64_be(Frag)].
+
+send_bad_fragments(Offender,VictimNode,Victim,Ctrl,WhereToPutSelf,Fragments) ->
+ Parent = self(),
+ Done = make_ref(),
+ ct:pal("Send: ~p",[Fragments]),
+ spawn_link(Offender,
+ fun () ->
+ Node = node(Victim),
+ pong = net_adm:ping(Node),
+ erlang:monitor_node(Node, true),
+ DCtrl = dctrl(Node),
+ Ctrl1 = case WhereToPutSelf of
+ 0 ->
+ Ctrl;
+ N when N > 0 ->
+ setelement(N,Ctrl,self())
+ end,
+
+ FragData = [case Type of
+ hdr ->
+ [dmsg_frag_hdr(FragId),
+ dmsg_ext(Ctrl1), FragPayload];
+ frg ->
+ [dmsg_frag(FragId), FragPayload]
+ end || {Type, FragId, FragPayload} <- Fragments],
+
+ receive {nodedown, Node} -> exit("premature nodedown")
+ after 10 -> ok
+ end,
+
+ [ dctrl_send(DCtrl, D) || D <- FragData ],
+ [ erlang:port_close(DCtrl) || close <- Fragments],
+
+ receive {nodedown, Node} -> ok
+ after 5000 -> exit("missing nodedown")
+ end,
+ Parent ! {FragData,Done}
+ end),
+ receive
+ {WhatSent,Done} ->
+ io:format("Offender sent ~p~n",[WhatSent]),
+ verify_nc(VictimNode),
+ ok
+ after 7000 ->
+ exit(unable_to_send)
+ end.
bad_dist_ext_receive(Config) when is_list(Config) ->
{ok, Offender} = start_node(bad_dist_ext_receive_offender),
@@ -2124,8 +2422,25 @@ start_node(Config, Args, Rel) when is_list(Config), is_list(Rel) ->
start_node(Name, Args, Rel).
stop_node(Node) ->
+ verify_nc(Node),
test_server:stop_node(Node).
+verify_nc(Node) ->
+ P = self(),
+ Ref = make_ref(),
+ spawn(Node,
+ fun() ->
+ R = erts_test_utils:check_node_dist(fun(E) -> E end),
+ P ! {Ref, R}
+ end),
+ receive
+ {Ref, ok} ->
+ ok;
+ {Ref, Error} ->
+ ct:log("~s",[Error]),
+ ct:fail(failed_nc_refc_check)
+ end.
+
freeze_node(Node, MS) ->
Own = 300,
DoingIt = make_ref(),
@@ -2485,6 +2800,17 @@ mk_ref({NodeNameExt, Creation}, Numbers) when is_integer(Creation),
exit({unexpected_binary_to_term_result, Other})
end.
+uint64_be(Uint) when is_integer(Uint), 0 =< Uint, Uint < 1 bsl 64 ->
+ [(Uint bsr 56) band 16#ff,
+ (Uint bsr 48) band 16#ff,
+ (Uint bsr 40) band 16#ff,
+ (Uint bsr 32) band 16#ff,
+ (Uint bsr 24) band 16#ff,
+ (Uint bsr 16) band 16#ff,
+ (Uint bsr 8) band 16#ff,
+ Uint band 16#ff];
+uint64_be(Uint) ->
+ exit({badarg, uint64_be, [Uint]}).
uint32_be(Uint) when is_integer(Uint), 0 =< Uint, Uint < 1 bsl 32 ->
[(Uint bsr 24) band 16#ff,
diff --git a/erts/emulator/test/driver_SUITE.erl b/erts/emulator/test/driver_SUITE.erl
index 1d2ae4fb51..bb0f3498ab 100644
--- a/erts/emulator/test/driver_SUITE.erl
+++ b/erts/emulator/test/driver_SUITE.erl
@@ -2665,24 +2665,7 @@ wait_deallocations() ->
driver_alloc_size() ->
wait_deallocations(),
- case erlang:system_info({allocator_sizes, driver_alloc}) of
- false ->
- undefined;
- MemInfo ->
- CS = lists:foldl(
- fun ({instance, _, L}, Acc) ->
- {value,{_,MBCS}} = lists:keysearch(mbcs, 1, L),
- {value,{_,SBCS}} = lists:keysearch(sbcs, 1, L),
- [MBCS,SBCS | Acc]
- end,
- [],
- MemInfo),
- lists:foldl(
- fun(L, Sz0) ->
- {value,{_,Sz,_,_}} = lists:keysearch(blocks_size, 1, L),
- Sz0+Sz
- end, 0, CS)
- end.
+ erts_debug:alloc_blocks_size(driver_alloc).
rpc(Config, Fun) ->
case proplists:get_value(node, Config) of
diff --git a/erts/emulator/test/emulator_bench.spec b/erts/emulator/test/emulator_bench.spec
index f709d913b7..2a180b440c 100644
--- a/erts/emulator/test/emulator_bench.spec
+++ b/erts/emulator/test/emulator_bench.spec
@@ -1 +1,2 @@
{groups,"../emulator_test",estone_SUITE,[estone_bench]}.
+{groups,"../emulator_test",binary_SUITE,[iolist_size_benchmarks]}.
diff --git a/erts/emulator/test/erts_debug_SUITE.erl b/erts/emulator/test/erts_debug_SUITE.erl
index 6aa7a445b5..f39dbedd8f 100644
--- a/erts/emulator/test/erts_debug_SUITE.erl
+++ b/erts/emulator/test/erts_debug_SUITE.erl
@@ -22,8 +22,10 @@
-include_lib("common_test/include/ct.hrl").
-export([all/0, suite/0,
- test_size/1,flat_size_big/1,df/1,term_type/1,
- instructions/1, stack_check/1]).
+ test_size/1,flat_size_big/1,df/1,term_type/1,
+ instructions/1, stack_check/1, alloc_blocks_size/1]).
+
+-export([do_alloc_blocks_size/0]).
suite() ->
[{ct_hooks,[ts_install_cth]},
@@ -31,7 +33,7 @@ suite() ->
all() ->
[test_size, flat_size_big, df, instructions, term_type,
- stack_check].
+ stack_check, alloc_blocks_size].
test_size(Config) when is_list(Config) ->
ConsCell1 = id([a|b]),
@@ -210,5 +212,28 @@ instructions(Config) when is_list(Config) ->
_ = [list_to_atom(I) || I <- Is],
ok.
+alloc_blocks_size(Config) when is_list(Config) ->
+ F = fun(Args) ->
+ Node = start_slave(Args),
+ ok = rpc:call(Node, ?MODULE, do_alloc_blocks_size, []),
+ true = test_server:stop_node(Node)
+ end,
+ F("+Meamax"),
+ F("+Meamin"),
+ F(""),
+ ok.
+
+do_alloc_blocks_size() ->
+ _ = erts_debug:alloc_blocks_size(binary_alloc),
+ ok.
+
+start_slave(Args) ->
+ Name = ?MODULE_STRING ++ "_slave",
+ Pa = filename:dirname(code:which(?MODULE)),
+ {ok, Node} = test_server:start_node(list_to_atom(Name),
+ slave,
+ [{args, "-pa " ++ Pa ++ " " ++ Args}]),
+ Node.
+
id(I) ->
I.
diff --git a/erts/emulator/test/erts_test_utils.erl b/erts/emulator/test/erts_test_utils.erl
new file mode 100644
index 0000000000..0c3ef3e0fc
--- /dev/null
+++ b/erts/emulator/test/erts_test_utils.erl
@@ -0,0 +1,271 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-2018. All 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_test_utils).
+
+%%
+%% THIS MODULE IS ALSO USED BY *OTHER* APPLICATIONS TEST CODE
+%%
+
+-export([mk_ext_pid/3,
+ mk_ext_port/2,
+ mk_ext_ref/2,
+ available_internal_state/1,
+ check_node_dist/0, check_node_dist/1, check_node_dist/3]).
+
+
+
+-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]}).
+
+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).
+
+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),
+ ?ATOM_EXT,
+ uint16_be(length(NodeName)),
+ NodeName,
+ uint32_be(Number),
+ uint32_be(Serial),
+ enc_creation(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.
+
+port_tag(bad_creation) -> ?PORT_EXT;
+port_tag(Creation) when Creation =< 3 -> ?PORT_EXT;
+port_tag(_Creation) -> ?NEW_PORT_EXT.
+
+mk_ext_port({NodeName, Creation}, Number) when is_atom(NodeName) ->
+ mk_ext_port({atom_to_list(NodeName), Creation}, Number);
+mk_ext_port({NodeName, Creation}, Number) ->
+ case catch binary_to_term(list_to_binary([?VERSION_MAGIC,
+ port_tag(Creation),
+ ?ATOM_EXT,
+ uint16_be(length(NodeName)),
+ NodeName,
+ uint32_be(Number),
+ enc_creation(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.
+
+ref_tag(bad_creation) -> ?NEW_REFERENCE_EXT;
+ref_tag(Creation) when Creation =< 3 -> ?NEW_REFERENCE_EXT;
+ref_tag(_Creation) -> ?NEWER_REFERENCE_EXT.
+
+mk_ext_ref({NodeName, Creation}, Numbers) when is_atom(NodeName),
+ is_list(Numbers) ->
+ mk_ext_ref({atom_to_list(NodeName), Creation}, Numbers);
+mk_ext_ref({NodeName, Creation}, [Number]) when is_list(NodeName),
+ Creation =< 3,
+ is_integer(Number) ->
+ case catch binary_to_term(list_to_binary([?VERSION_MAGIC,
+ ?REFERENCE_EXT,
+ ?ATOM_EXT,
+ uint16_be(length(NodeName)),
+ NodeName,
+ uint32_be(Number),
+ uint8(Creation)])) of
+ Ref when is_reference(Ref) ->
+ Ref;
+ {'EXIT', {badarg, _}} ->
+ exit({badarg, mk_ref, [{NodeName, Creation}, [Number]]});
+ Other ->
+ exit({unexpected_binary_to_term_result, Other})
+ end;
+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),
+ uint16_be(length(Numbers)),
+ ?ATOM_EXT,
+ uint16_be(length(NodeName)),
+ NodeName,
+ enc_creation(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.
+
+
+available_internal_state(Bool) when Bool == true; Bool == false ->
+ case {Bool,
+ (catch erts_debug:get_internal_state(available_internal_state))} of
+ {true, true} ->
+ true;
+ {false, true} ->
+ erts_debug:set_internal_state(available_internal_state, false),
+ true;
+ {true, _} ->
+ erts_debug:set_internal_state(available_internal_state, true),
+ false;
+ {false, _} ->
+ false
+ end.
+
+
+%%
+%% Check reference counters for node- and dist entries.
+%%
+check_node_dist() ->
+ check_node_dist(fun(ErrMsg) ->
+ io:format("check_node_dist ERROR:\n~p\n", [ErrMsg]),
+ error
+ end).
+
+check_node_dist(Fail) ->
+ AIS = available_internal_state(true),
+ [erlang:garbage_collect(P) || P <- erlang:processes()],
+ {{node_references, NodeRefs},
+ {dist_references, DistRefs}} =
+ erts_debug:get_internal_state(node_and_dist_references),
+ R = check_node_dist(Fail, NodeRefs, DistRefs),
+ available_internal_state(AIS),
+ R.
+
+check_node_dist(Fail, NodeRefs, DistRefs) ->
+ AIS = available_internal_state(true),
+ R = check_nd_refc({node(),erlang:system_info(creation)},
+ NodeRefs, DistRefs, Fail),
+ available_internal_state(AIS),
+ R.
+
+
+check_nd_refc({ThisNodeName, ThisCreation}, NodeRefs, DistRefs, Fail) ->
+ case catch begin
+ check_refc(ThisNodeName,ThisCreation,"node table",NodeRefs),
+ check_refc(ThisNodeName,ThisCreation,"dist table",DistRefs),
+ ok
+ end of
+ ok ->
+ ok;
+ {'EXIT', Reason} ->
+ {Y,Mo,D} = date(),
+ {H,Mi,S} = time(),
+ ErrMsg = io_lib:format("~n"
+ "*** Reference count check of node ~w "
+ "failed (~p) at ~w~w~w ~w:~w:~w~n"
+ "*** Node table references:~n ~p~n"
+ "*** Dist table references:~n ~p~n",
+ [node(), Reason, Y, Mo, D, H, Mi, S,
+ NodeRefs, DistRefs]),
+ Fail(lists:flatten(ErrMsg))
+ end.
+
+
+check_refc(ThisNodeName,ThisCreation,Table,EntryList) when is_list(EntryList) ->
+ lists:foreach(
+ fun ({Entry, Refc, ReferrerList}) ->
+ {DelayedDeleteTimer,
+ FoundRefs} =
+ lists:foldl(
+ fun ({Referrer, ReferencesList}, {DDT, A1}) ->
+ {case Referrer of
+ {system,delayed_delete_timer} ->
+ true;
+ {system,thread_progress_delete_timer} ->
+ true;
+ _ ->
+ DDT
+ end,
+ A1 + lists:foldl(fun ({_T,Rs},A2) ->
+ A2+Rs
+ end,
+ 0,
+ ReferencesList)}
+ end,
+ {false, 0},
+ ReferrerList),
+
+ %% Reference count equals found references?
+ case {Refc, FoundRefs, DelayedDeleteTimer} of
+ {X, X, _} ->
+ ok;
+ {0, 1, true} ->
+ ok;
+ _ ->
+ exit({invalid_reference_count, Table, Entry})
+ end,
+
+ %% All entries in table referred to?
+ case {Entry, Refc} of
+ {ThisNodeName, 0} -> ok;
+ {{ThisNodeName, ThisCreation}, 0} -> ok;
+ {_, 0} when DelayedDeleteTimer == false ->
+ exit({not_referred_entry_in_table, Table, Entry});
+ {_, _} -> ok
+ end
+
+ end,
+ EntryList),
+ ok.
diff --git a/erts/emulator/test/esock_misc/socket_client.erl b/erts/emulator/test/esock_misc/socket_client.erl
new file mode 100644
index 0000000000..1c07e799b8
--- /dev/null
+++ b/erts/emulator/test/esock_misc/socket_client.erl
@@ -0,0 +1,538 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2018. All 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(socket_client).
+
+-export([
+ start/1, start/2, start/5, start/6,
+ start_tcp/1, start_tcp/2, start_tcp/3,
+ start_tcp4/1, start_tcp4/2, start_tcp6/1, start_tcp6/2,
+ start_udp/1, start_udp/2, start_udp/3,
+ start_udp4/1, start_udp4/2, start_udp6/1, start_udp6/2
+ ]).
+
+-define(LIB, socket_lib).
+
+-record(client, {socket, verbose = true, msg = true, type, dest, msg_id = 1}).
+
+start(Port) ->
+ start(Port, 1).
+
+start(Port, Num) ->
+ start_tcp(Port, Num).
+
+start_tcp(Port) ->
+ start_tcp(Port, 1).
+
+start_tcp(Port, Num) ->
+ start_tcp4(Port, Num).
+
+start_tcp4(Port) ->
+ start_tcp4(Port, 1).
+
+start_tcp4(Port, Num) ->
+ start(inet, stream, tcp, Port, Num).
+
+start_tcp6(Port) ->
+ start_tcp6(Port, 1).
+
+start_tcp6(Port, Num) ->
+ start(inet6, stream, tcp, Port, Num).
+
+start_tcp(Addr, Port, Num) when (size(Addr) =:= 4) andalso
+ is_integer(Num) andalso
+ (Num > 0) ->
+ start(inet, stream, tcp, Addr, Port, Num);
+start_tcp(Addr, Port, Num) when (size(Addr) =:= 8) andalso
+ is_integer(Num) andalso
+ (Num > 0) ->
+ start(inet6, stream, tcp, Addr, Port, Num).
+
+
+start_udp(Port) ->
+ start_udp(Port, 1).
+
+start_udp(Port, Num) ->
+ start_udp4(Port, Num).
+
+start_udp4(Port) ->
+ start_udp4(Port, 1).
+
+start_udp4(Port, Num) ->
+ start(inet, dgram, udp, Port, Num).
+
+start_udp6(Port) ->
+ start_udp6(Port, 1).
+
+start_udp6(Port, Num) ->
+ start(inet6, dgram, udp, Port, Num).
+
+start_udp(Addr, Port, Num) when (size(Addr) =:= 4) ->
+ start(inet, dgram, udp, Addr, Port, Num);
+start_udp(Addr, Port, Num) when (size(Addr) =:= 8) ->
+ start(inet6, dgram, udp, Addr, Port, Num).
+
+
+start(Domain, Type, Proto, Port, Num)
+ when is_integer(Port) andalso is_integer(Num) ->
+ start(Domain, Type, Proto, which_addr(Domain), Port, Num);
+
+start(Domain, Type, Proto, Addr, Port) ->
+ start(Domain, Type, Proto, Addr, Port, 1).
+
+start(Domain, Type, Proto, Addr, Port, 1 = Num) ->
+ start(Domain, Type, Proto, Addr, Port, Num, true);
+start(Domain, Type, Proto, Addr, Port, Num)
+ when is_integer(Num) andalso (Num > 1) ->
+ start(Domain, Type, Proto, Addr, Port, Num, false).
+
+start(Domain, Type, Proto, Addr, Port, Num, Verbose) ->
+ put(sname, "starter"),
+ Clients = start_clients(Num, Domain, Type, Proto, Addr, Port, Verbose),
+ await_clients(Clients).
+
+start_clients(Num, Domain, Type, Proto, Addr, Port, Verbose) ->
+ start_clients(Num, 1, Domain, Type, Proto, Addr, Port, Verbose, []).
+
+start_clients(Num, ID, Domain, Type, Proto, Addr, Port, Verbose, Acc)
+ when (Num > 0) ->
+ StartClient = fun() ->
+ start_client(ID, Domain, Type, Proto, Addr, Port, Verbose)
+ end,
+ {Pid, _} = spawn_monitor(StartClient),
+ ?LIB:sleep(500),
+ i("start client ~w", [ID]),
+ start_clients(Num-1, ID+1, Domain, Type, Proto, Addr, Port, Verbose, [Pid|Acc]);
+start_clients(_, _, _, _, _, _, _, _, Acc) ->
+ i("all client(s) started"),
+ lists:reverse(Acc).
+
+await_clients([]) ->
+ i("all clients done");
+await_clients(Clients) ->
+ receive
+ {'DOWN', _MRef, process, Pid, _Reason} ->
+ case lists:delete(Pid, Clients) of
+ Clients2 when (Clients2 =/= Clients) ->
+ i("client ~p done", [Pid]),
+ await_clients(Clients2);
+ _ ->
+ await_clients(Clients)
+ end
+ end.
+
+
+start_client(ID, Domain, Type, Proto, Addr, Port, Verbose) ->
+ put(sname, ?LIB:f("client[~w]", [ID])),
+ SA = #{family => Domain,
+ addr => Addr,
+ port => Port},
+ %% The way we use tos only works because we
+ %% send so few messages (a new value for every
+ %% message).
+ tos_init(),
+ do_start(Domain, Type, Proto, SA, Verbose).
+
+do_start(Domain, stream = Type, Proto, SA, Verbose) ->
+ try do_init(Domain, Type, Proto) of
+ Sock ->
+ connect(Sock, SA),
+ maybe_print_start_info(Verbose, Sock, Type),
+ %% Give the server some time...
+ ?LIB:sleep(5000),
+ %% ok = socket:close(Sock),
+ send_loop(#client{socket = Sock,
+ type = Type,
+ verbose = Verbose})
+ catch
+ throw:E ->
+ e("Failed initiate: "
+ "~n Error: ~p", [E])
+ end;
+do_start(Domain, dgram = Type, Proto, SA, Verbose) ->
+ try do_init(Domain, Type, Proto) of
+ Sock ->
+ maybe_print_start_info(Verbose, Sock, Type),
+ %% Give the server some time...
+ ?LIB:sleep(5000),
+ %% ok = socket:close(Sock),
+ send_loop(#client{socket = Sock,
+ type = Type,
+ dest = SA,
+ verbose = Verbose})
+ catch
+ throw:E ->
+ e("Failed initiate: "
+ "~n Error: ~p", [E])
+ end.
+
+maybe_print_start_info(true = _Verbose, Sock, stream = _Type) ->
+ {ok, Name} = socket:sockname(Sock),
+ {ok, Peer} = socket:peername(Sock),
+ {ok, Domain} = socket:getopt(Sock, socket, domain),
+ {ok, Type} = socket:getopt(Sock, socket, type),
+ {ok, Proto} = socket:getopt(Sock, socket, protocol),
+ {ok, OOBI} = socket:getopt(Sock, socket, oobinline),
+ {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf),
+ {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf),
+ {ok, Linger} = socket:getopt(Sock, socket, linger),
+ {ok, MTU} = socket:getopt(Sock, ip, mtu),
+ {ok, MTUDisc} = socket:getopt(Sock, ip, mtu_discover),
+ {ok, MALL} = socket:getopt(Sock, ip, multicast_all),
+ {ok, MIF} = socket:getopt(Sock, ip, multicast_if),
+ {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop),
+ {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl),
+ {ok, RecvTOS} = socket:getopt(Sock, ip, recvtos),
+ i("connected: "
+ "~n From: ~p"
+ "~n To: ~p"
+ "~nwhen"
+ "~n (socket) Domain: ~p"
+ "~n (socket) Type: ~p"
+ "~n (socket) Protocol: ~p"
+ "~n (socket) OOBInline: ~p"
+ "~n (socket) SndBuf: ~p"
+ "~n (socket) RcvBuf: ~p"
+ "~n (socket) Linger: ~p"
+ "~n (ip) MTU: ~p"
+ "~n (ip) MTU Discovery: ~p"
+ "~n (ip) Multicast ALL: ~p"
+ "~n (ip) Multicast IF: ~p"
+ "~n (ip) Multicast Loop: ~p"
+ "~n (ip) Multicast TTL: ~p"
+ "~n (ip) RecvTOS: ~p"
+ "~n => wait some",
+ [Name, Peer,
+ Domain, Type, Proto,
+ OOBI, SndBuf, RcvBuf, Linger,
+ MTU, MTUDisc, MALL, MIF, MLoop, MTTL,
+ RecvTOS]);
+maybe_print_start_info(true = _Verbose, Sock, dgram = _Type) ->
+ {ok, Domain} = socket:getopt(Sock, socket, domain),
+ {ok, Type} = socket:getopt(Sock, socket, type),
+ {ok, Proto} = socket:getopt(Sock, socket, protocol),
+ {ok, OOBI} = socket:getopt(Sock, socket, oobinline),
+ {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf),
+ {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf),
+ {ok, Linger} = socket:getopt(Sock, socket, linger),
+ {ok, MALL} = socket:getopt(Sock, ip, multicast_all),
+ {ok, MIF} = socket:getopt(Sock, ip, multicast_if),
+ {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop),
+ {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl),
+ {ok, RecvTOS} = socket:getopt(Sock, ip, recvtos),
+ {ok, RecvTTL} = socket:getopt(Sock, ip, recvttl),
+ i("initiated when: "
+ "~n (socket) Domain: ~p"
+ "~n (socket) Type: ~p"
+ "~n (socket) Protocol: ~p"
+ "~n (socket) OOBInline: ~p"
+ "~n (socket) SndBuf: ~p"
+ "~n (socket) RcvBuf: ~p"
+ "~n (socket) Linger: ~p"
+ "~n (ip) Multicast ALL: ~p"
+ "~n (ip) Multicast IF: ~p"
+ "~n (ip) Multicast Loop: ~p"
+ "~n (ip) Multicast TTL: ~p"
+ "~n (ip) RecvTOS: ~p"
+ "~n (ip) RecvTTL: ~p"
+ "~n => wait some",
+ [Domain, Type, Proto,
+ OOBI, SndBuf, RcvBuf, Linger,
+ MALL, MIF, MLoop, MTTL,
+ RecvTOS, RecvTTL]);
+maybe_print_start_info(_Verbose, _Sock, _Type) ->
+ ok.
+
+
+do_init(Domain, stream = Type, Proto) ->
+ i("try (socket) open"),
+ Sock = case socket:open(Domain, Type, Proto) of
+ {ok, S} ->
+ S;
+ {error, OReason} ->
+ throw({open, OReason})
+ end,
+ i("try (socket) bind"),
+ case socket:bind(Sock, any) of
+ {ok, _P} ->
+ ok = socket:setopt(Sock, socket, timestamp, true),
+ ok = socket:setopt(Sock, ip, tos, mincost),
+ ok = socket:setopt(Sock, ip, recvtos, true),
+ Sock;
+ {error, BReason} ->
+ throw({bind, BReason})
+ end;
+do_init(Domain, dgram = Type, Proto) ->
+ i("try (socket) open"),
+ Sock = case socket:open(Domain, Type, Proto) of
+ {ok, S} ->
+ S;
+ {error, OReason} ->
+ throw({open, OReason})
+ end,
+ case socket:bind(Sock, any) of
+ {ok, _} ->
+ ok = socket:setopt(Sock, socket, timestamp, true),
+ ok = socket:setopt(Sock, ip, tos, mincost),
+ ok = socket:setopt(Sock, ip, recvtos, true),
+ ok = socket:setopt(Sock, ip, recvttl, true),
+ Sock;
+ {error, BReason} ->
+ throw({bind, BReason})
+ end.
+
+
+which_addr(Domain) ->
+ Iflist = case inet:getifaddrs() of
+ {ok, IFL} ->
+ IFL;
+ {error, Reason} ->
+ throw({inet,getifaddrs,Reason})
+ end,
+ which_addr(Domain, Iflist).
+
+
+connect(Sock, SA) ->
+ i("try (socket) connect to:"
+ "~n ~p", [SA]),
+ case socket:connect(Sock, SA) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ e("connect failure: "
+ "~n ~p", [Reason]),
+ exit({connect, Reason})
+ end.
+
+
+send_loop(#client{msg_id = N} = C) when (N =< 10) ->
+ i("try send request ~w", [N]),
+ Req = ?LIB:enc_req_msg(N, "hejsan"),
+ case send(C, Req) of
+ ok ->
+ i("request ~w sent - now try read answer", [N]),
+ case recv(C) of
+ {ok, {Source, Msg}} ->
+ if
+ (C#client.verbose =:= true) ->
+ i("received ~w bytes of data~s",
+ [size(Msg), case Source of
+ undefined -> "";
+ _ -> ?LIB:f(" from:~n ~p", [Source])
+ end]);
+ true ->
+ i("received ~w bytes", [size(Msg)])
+ end,
+ case ?LIB:dec_msg(Msg) of
+ {reply, N, Reply} ->
+ if
+ (C#client.verbose =:= true) ->
+ i("received reply ~w: ~p", [N, Reply]);
+ true ->
+ i("received reply ~w", [N])
+ end,
+ ?LIB:sleep(500), % Just to spread it out a bit
+ send_loop(C#client{msg_id = N+1})
+ end;
+ {error, RReason} ->
+ e("Failed recv response for request ~w: "
+ "~n ~p", [N, RReason]),
+ exit({failed_recv, RReason})
+ end;
+ {error, SReason} ->
+ e("Failed send request ~w: "
+ "~n ~p", [N, SReason]),
+ exit({failed_send, SReason})
+ end;
+send_loop(Client) ->
+ sock_close(Client).
+
+sock_close(#client{socket = Sock, verbose = true}) ->
+ i("we are done - close the socket when: "
+ "~n ~p", [socket:info()]),
+ ok = socket:close(Sock),
+ i("we are done - socket closed when: "
+ "~n ~p", [socket:info()]);
+sock_close(#client{socket = Sock}) ->
+ i("we are done"),
+ ok = socket:close(Sock).
+
+
+
+send(#client{socket = Sock, type = stream}, Msg) ->
+ socket:send(Sock, Msg);
+send(#client{socket = Sock, type = dgram, dest = Dest}, Msg) ->
+ %% i("try send to: "
+ %% "~n ~p", [Dest]),
+ %% ok = socket:setopt(Sock, otp, debug, true),
+ TOS = tos_next(),
+ ok = socket:setopt(Sock, ip, tos, TOS),
+ case socket:sendto(Sock, Msg, Dest) of
+ ok = OK ->
+ OK;
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+recv(#client{socket = Sock, type = stream, msg = false}) ->
+ case socket:recv(Sock) of
+ {ok, Msg} ->
+ {ok, {undefined, Msg}};
+ {error, _} = ERROR ->
+ ERROR
+ end;
+recv(#client{socket = Sock, verbose = Verbose, type = stream, msg = true}) ->
+ case socket:recvmsg(Sock) of
+ %% An iov of length 1 is an simplification...
+ {ok, #{addr := undefined = Source,
+ iov := [Msg],
+ ctrl := CMsgHdrs,
+ flags := Flags}} ->
+ if
+ (Verbose =:= true) ->
+ i("received message: "
+ "~n CMsgHdr: ~p"
+ "~n Flags: ~p", [CMsgHdrs, Flags]);
+ true ->
+ ok
+ end,
+ {ok, {Source, Msg}};
+ {error, _} = ERROR ->
+ ERROR
+ end;
+recv(#client{socket = Sock, type = dgram, msg = false}) ->
+ socket:recvfrom(Sock);
+recv(#client{socket = Sock, verbose = Verbose, type = dgram, msg = true}) ->
+ case socket:recvmsg(Sock) of
+ {ok, #{addr := Source,
+ iov := [Msg],
+ ctrl := CMsgHdrs,
+ flags := Flags}} ->
+ if
+ (Verbose =:= true) ->
+ i("received message: "
+ "~n CMsgHdr: ~p"
+ "~n Flags: ~p", [CMsgHdrs, Flags]);
+ true ->
+ ok
+ end,
+ {ok, {Source, Msg}};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+
+which_addr(_Domain, []) ->
+ throw(no_address);
+which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") ->
+ which_addr2(Domain, IFO);
+which_addr(Domain, [_|IFL]) ->
+ which_addr(Domain, IFL).
+
+which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) ->
+ Addr;
+which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) ->
+ Addr;
+which_addr2(Domain, [_|IFO]) ->
+ which_addr2(Domain, IFO).
+
+
+%% ---
+
+%% enc_req_msg(N, Data) ->
+%% enc_msg(?REQ, N, Data).
+
+%% enc_rep_msg(N, Data) ->
+%% enc_msg(?REP, N, Data).
+
+%% enc_msg(Type, N, Data) when is_list(Data) ->
+%% enc_msg(Type, N, list_to_binary(Data));
+%% enc_msg(Type, N, Data)
+%% when is_integer(Type) andalso is_integer(N) andalso is_binary(Data) ->
+%% <<Type:32/integer, N:32/integer, Data/binary>>.
+
+%% dec_msg(<<?REQ:32/integer, N:32/integer, Data/binary>>) ->
+%% {request, N, Data};
+%% dec_msg(<<?REP:32/integer, N:32/integer, Data/binary>>) ->
+%% {reply, N, Data}.
+
+
+%% ---
+
+%% sleep(T) ->
+%% receive after T -> ok end.
+
+
+%% ---
+
+%% 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).
+
+
+%% ---
+
+tos_init() ->
+ put(tos, 1).
+
+tos_next() ->
+ case get(tos) of
+ TOS when (TOS < 100) ->
+ put(tos, TOS + 1),
+ TOS;
+ _ ->
+ put(tos, 1),
+ 1
+ end.
+
+
+%% ---
+
+e(F, A) ->
+ ?LIB:e(F, A).
+
+i(F) ->
+ ?LIB:i(F).
+
+i(F, A) ->
+ ?LIB:i(F, A).
+
diff --git a/erts/emulator/test/esock_misc/socket_lib.erl b/erts/emulator/test/esock_misc/socket_lib.erl
new file mode 100644
index 0000000000..9d6524d467
--- /dev/null
+++ b/erts/emulator/test/esock_misc/socket_lib.erl
@@ -0,0 +1,133 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2018. All 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(socket_lib).
+
+-export([
+ sleep/1,
+ req/0, rep/0,
+ enc_req_msg/2, enc_rep_msg/2,
+ enc_msg/3, dec_msg/1,
+ request/3, reply/4,
+ f/2,
+ i/1, i/2,
+ e/2
+ ]).
+
+
+-define(REQ, 0).
+-define(REP, 1).
+
+
+%% ---
+
+sleep(T) ->
+ receive after T -> ok end.
+
+
+%% ---
+
+req() -> ?REQ.
+rep() -> ?REP.
+
+enc_req_msg(N, Data) ->
+ enc_msg(?REQ, N, Data).
+
+enc_rep_msg(N, Data) ->
+ enc_msg(?REP, N, Data).
+
+enc_msg(Type, N, Data) when is_list(Data) ->
+ enc_msg(Type, N, list_to_binary(Data));
+enc_msg(Type, N, Data)
+ when is_integer(Type) andalso is_integer(N) andalso is_binary(Data) ->
+ <<Type:32/integer, N:32/integer, Data/binary>>.
+
+dec_msg(<<?REQ:32/integer, N:32/integer, Data/binary>>) ->
+ {request, N, Data};
+dec_msg(<<?REP:32/integer, N:32/integer, Data/binary>>) ->
+ {reply, N, Data}.
+
+
+%% ---
+
+request(Tag, Pid, Request) ->
+ Ref = make_ref(),
+ Pid ! {Tag, self(), Ref, Request},
+ receive
+ {Tag, Pid, Ref, Reply} ->
+ Reply
+ end.
+
+reply(Tag, Pid, Ref, Reply) ->
+ Pid ! {Tag, self(), Ref, Reply}.
+
+
+%% ---
+
+f(F, A) ->
+ lists:flatten(io_lib:format(F, A)).
+
+
+%% ---
+
+e(F, A) ->
+ p("<ERROR> " ++ F, A).
+
+i(F) ->
+ i(F, []).
+i(F, A) ->
+ p("*** " ++ F, A).
+
+p(F, A) ->
+ p(get(sname), F, A).
+
+p(SName, F, A) ->
+ io:format("[~s,~p][~s] " ++ F ++ "~n",
+ [SName,self(),formated_timestamp()|A]).
+
+
+%% ---
+
+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).
+
+
diff --git a/erts/emulator/test/esock_misc/socket_server.erl b/erts/emulator/test/esock_misc/socket_server.erl
new file mode 100644
index 0000000000..45adffc5e6
--- /dev/null
+++ b/erts/emulator/test/esock_misc/socket_server.erl
@@ -0,0 +1,954 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2018. All 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(socket_server).
+
+-export([
+ start/0, start/5,
+ start_tcp/0, start_tcp/1, start_tcp/3,
+ start_tcp4/0, start_tcp4/1, start_tcp4/2,
+ start_tcp6/0, start_tcp6/1, start_tcp6/2,
+ start_udp/0, start_udp/1, start_udp/3,
+ start_udp4/0, start_udp4/1, start_udp4/2,
+ start_udp6/0, start_udp6/1, start_udp6/2,
+ start_sctp/0, start_sctp/1
+ ]).
+
+-define(LIB, socket_lib).
+
+-record(manager, {socket, msg, peek, acceptors, handler_id, handlers}).
+-record(acceptor, {id, socket, manager,
+ atimeout = 5000}).
+-record(handler, {socket, peek, msg, type, manager,
+ stimeout = 5000, rtimeout = 5000}).
+
+-define(NUM_ACCEPTORS, 5).
+
+start() ->
+ start_tcp().
+
+start_tcp() ->
+ start_tcp4().
+
+start_tcp(Peek) ->
+ start_tcp4(Peek).
+
+start_tcp4() ->
+ start_tcp4(false).
+
+start_tcp4(Peek) ->
+ start_tcp4(false, Peek).
+
+start_tcp4(UseMsg, Peek) ->
+ start_tcp(inet, UseMsg, Peek).
+
+start_tcp6() ->
+ start_tcp6(false).
+
+start_tcp6(Peek) ->
+ start_tcp6(false, Peek).
+
+start_tcp6(UseMsg, Peek) ->
+ start_tcp(inet6, UseMsg, Peek).
+
+start_tcp(Domain, UseMsg, Peek) when is_boolean(UseMsg) andalso is_boolean(Peek) ->
+ start(Domain, stream, tcp, UseMsg, Peek).
+
+start_udp() ->
+ start_udp4().
+
+start_udp(Peek) ->
+ start_udp4(Peek).
+
+start_udp4() ->
+ start_udp4(false).
+
+start_udp4(Peek) ->
+ start_udp4(false, Peek).
+
+start_udp4(UseMsg, Peek) ->
+ start_udp(inet, UseMsg, Peek).
+
+start_udp6() ->
+ start_udp6(false, false).
+
+start_udp6(Peek) ->
+ start_udp6(false, Peek).
+
+start_udp6(UseMsg, Peek) ->
+ start_udp(inet6, UseMsg, Peek).
+
+start_udp(Domain, UseMsg, Peek) when is_boolean(UseMsg) andalso is_boolean(Peek) ->
+ start(Domain, dgram, udp, UseMsg, Peek).
+
+
+start_sctp() ->
+ start_sctp(inet).
+
+start_sctp(Domain) when ((Domain =:= inet) orelse (Domain =:= inet6)) ->
+ start(Domain, seqpacket, sctp, true, false).
+
+start(Domain, Type, Proto, UseMsg, Peek) ->
+ put(sname, "starter"),
+ i("try start manager"),
+ {Pid, MRef} = manager_start(Domain, Type, Proto, UseMsg, Peek),
+ i("manager (~p) started", [Pid]),
+ loop(Pid, MRef).
+
+loop(Pid, MRef) ->
+ receive
+ {'DOWN', MRef, process, Pid, Reason} ->
+ i("manager process exited: "
+ "~n ~p", [Reason]),
+ ok
+ end.
+
+
+%% =========================================================================
+
+manager_start(Domain, Type, Proto, UseMsg, Peek) ->
+ spawn_monitor(fun() -> manager_init(Domain, Type, Proto, UseMsg, Peek) end).
+
+manager_start_handler(Pid, Sock) ->
+ manager_request(Pid, {start_handler, Sock}).
+
+manager_stop(Pid, Reason) ->
+ manager_request(Pid, {stop, Reason}).
+
+manager_request(Pid, Request) ->
+ ?LIB:request(manager, Pid, Request).
+
+manager_reply(Pid, Ref, Reply) ->
+ ?LIB:reply(manager, Pid, Ref, Reply).
+
+
+manager_init(Domain, Type, Proto, UseMsg, Peek) ->
+ put(sname, "manager"),
+ do_manager_init(Domain, Type, Proto, UseMsg, Peek).
+
+do_manager_init(Domain, stream = Type, Proto, UseMsg, Peek) ->
+ i("try start acceptor(s)"),
+ {Sock, Acceptors} = manager_stream_init(Domain, Type, Proto),
+ manager_loop(#manager{socket = Sock,
+ msg = UseMsg,
+ peek = Peek,
+ acceptors = Acceptors,
+ handler_id = 1,
+ handlers = []});
+do_manager_init(Domain, dgram = Type, Proto, UseMsg, Peek) ->
+ i("try open socket"),
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ F = fun(X) -> case socket:getopt(Sock, socket, X) of
+ {ok, V} -> f("~p", [V]);
+ {error, R} -> f("error: ~p", [R])
+ end
+ end,
+ i("socket opened (~s,~s,~s): "
+ "~n broadcast: ~s"
+ "~n dontroute: ~s"
+ "~n keepalive: ~s"
+ "~n reuseaddr: ~s"
+ "~n linger: ~s"
+ "~n debug: ~s"
+ "~n prio: ~s"
+ "~n rcvbuf: ~s"
+ "~n rcvtimeo: ~s"
+ "~n sndbuf: ~s"
+ "~n sndtimeo: ~s"
+ "~n => try find (local) address",
+ [F(domain), F(type), F(protocol),
+ F(broadcast), F(dontroute), F(keepalive), F(reuseaddr), F(linger),
+ F(debug), F(priority),
+ F(rcvbuf), F(rcvtimeo), F(sndbuf), F(sndtimeo)]),
+ Addr = which_addr(Domain),
+ SA = #{family => Domain,
+ addr => Addr},
+ i("try bind to: "
+ "~n ~p", [Addr]),
+ case socket:bind(Sock, SA) of
+ {ok, _P} ->
+ ok;
+ {error, BReason} ->
+ throw({bind, BReason})
+ end,
+ i("bound to: "
+ "~n ~s"
+ "~n => try start handler",
+ [case socket:sockname(Sock) of
+ {ok, Name} -> f("~p", [Name]);
+ {error, R} -> f("error: ~p", [R])
+ end]),
+ case handler_start(1, Sock, UseMsg, Peek) of
+ {ok, {Pid, MRef}} ->
+ i("handler (~p) started", [Pid]),
+ handler_continue(Pid),
+ manager_loop(#manager{peek = Peek,
+ msg = UseMsg,
+ handler_id = 2, % Just in case
+ handlers = [{1, Pid, MRef}]});
+ {error, SReason} ->
+ e("Failed starting handler: "
+ "~n ~p", [SReason]),
+ exit({failed_start_handler, SReason})
+ end;
+ {error, OReason} ->
+ e("Failed open socket: "
+ "~n ~p", [OReason]),
+ exit({failed_open_socket, OReason})
+ end;
+do_manager_init(Domain, seqpacket = Type, sctp = Proto, _UseMsg, _Peek) ->
+ %% This is as far as I have got with SCTP at the moment...
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ i("(sctp) socket opened: "
+ "~n ~p", [Sock]),
+ EXP = fun(_Desc, Expect, Expect) ->
+ Expect;
+ (Desc, Expect, Actual) ->
+ e("Unexpected result ~w: "
+ "~n Expect: ~p"
+ "~n Actual: ~p", [Desc, Expect, Actual]),
+ exit({Desc, Expect, Actual})
+ end,
+ GO = fun(O) -> case socket:getopt(Sock, sctp, O) of
+ {ok, V} -> f("~p", [V]);
+ {error, R} -> f("error: ~p", [R])
+ end
+ end,
+ %% ok = socket:setopt(Sock, otp, debug, true),
+
+ i("Miscellaneous options: "
+ "~n associnfo: ~s"
+ "~n autoclose: ~s"
+ "~n disable-fragments: ~s"
+ "~n initmsg: ~s"
+ "~n maxseg: ~s"
+ "~n nodelay: ~s"
+ "~n rtoinfo: ~s",
+ [GO(associnfo),
+ GO(autoclose),
+ GO(disable_fragments),
+ GO(initmsg),
+ GO(maxseg),
+ GO(nodelay),
+ GO(rtoinfo)]),
+
+ Events = #{data_in => true,
+ association => true,
+ address => true,
+ send_failure => true,
+ peer_error => true,
+ shutdown => true,
+ partial_delivery => true,
+ adaptation_layer => true,
+ authentication => true,
+ sender_dry => true},
+ EXP(set_sctp_events, ok, socket:setopt(Sock, sctp, events, Events)),
+ EXP(close_socket, ok, socket:close(Sock));
+ {error, Reason} ->
+ exit({failed_open, Reason})
+ end;
+do_manager_init(Domain, raw = Type, Proto, UseMsg, Peek) when is_integer(Proto) ->
+ do_manager_init(Domain, Type, {raw, Proto}, UseMsg, Peek);
+do_manager_init(Domain, raw = Type, Proto, _UseMsg, _Peek) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ i("(sctp) socket opened: "
+ "~n ~p", [Sock]),
+ socket:close(Sock);
+ {error, Reason} ->
+ exit({failed_open, Reason})
+ end.
+
+
+
+manager_stream_init(Domain, Type, Proto) ->
+ i("try (socket) open"),
+ Sock = case socket:open(Domain, Type, Proto) of
+ {ok, S} ->
+ S;
+ {error, OReason} ->
+ throw({open, OReason})
+ end,
+ F = fun(X) -> case socket:getopt(Sock, socket, X) of
+ {ok, V} -> f("~p", [V]);
+ {error, R} -> f("error: ~p", [R])
+ end
+ end,
+ i("(socket) open (~s,~s,~s): "
+ "~n debug: ~s"
+ "~n prio: ~s"
+ "~n => try find (local) address",
+ [F(domain), F(type), F(protocol), F(debug), F(priority)]),
+ Addr = which_addr(Domain),
+ SA = #{family => Domain,
+ addr => Addr},
+ i("found: "
+ "~n ~p"
+ "~n => try (socket) bind", [Addr]),
+ %% ok = socket:setopt(Sock, otp, debug, true),
+ %% ok = socket:setopt(Sock, socket, debug, 1), %% must have rights!!
+ Port = case socket:bind(Sock, SA) of
+ {ok, P} ->
+ %% ok = socket:setopt(Sock, socket, debug, 0), %% must have rights!!
+ %% ok = socket:setopt(Sock, otp, debug, false),
+ P;
+ {error, BReason} ->
+ throw({bind, BReason})
+ end,
+ i("bound to: "
+ "~n ~p"
+ "~n => try (socket) listen (acceptconn: ~s)",
+ [Port, F(acceptconn)]),
+ case socket:listen(Sock) of
+ ok ->
+ i("listening (acceptconn: ~s)",
+ [F(acceptconn)]),
+ manager_stream_init(Sock, 1, ?NUM_ACCEPTORS, []);
+ {error, LReason} ->
+ throw({listen, LReason})
+ end.
+
+which_addr(Domain) ->
+ Iflist = case inet:getifaddrs() of
+ {ok, IFL} ->
+ IFL;
+ {error, Reason} ->
+ throw({inet,getifaddrs,Reason})
+ end,
+ which_addr(Domain, Iflist).
+
+which_addr(_Domain, []) ->
+ throw(no_address);
+which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") ->
+ which_addr2(Domain, IFO);
+which_addr(Domain, [_|IFL]) ->
+ which_addr(Domain, IFL).
+
+which_addr2(_, []) ->
+ throw(no_address);
+which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) ->
+ Addr;
+which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) ->
+ Addr;
+which_addr2(Domain, [_|IFO]) ->
+ which_addr2(Domain, IFO).
+
+
+manager_stream_init(Sock, ID, NumAcceptors, Acc)
+ when (NumAcceptors > 0) ->
+ i("try start acceptor"),
+ case acceptor_start(Sock, ID) of
+ {ok, {Pid, MRef}} ->
+ i("acceptor ~w (~p) started", [ID, Pid]),
+ ?LIB:sleep(2000),
+ manager_stream_init(Sock, ID+1, NumAcceptors-1,
+ [{ID, Pid, MRef}|Acc]);
+ {error, Reason} ->
+ exit({failed_starting_acceptor, Reason})
+ end;
+manager_stream_init(Sock, _ID, 0, Acc) ->
+ %% Req = {kill_acceptor, length(Acc)}, % Last in the queue
+ %% Req = {kill_acceptor, 3}, % In the "middle" of the queue
+ %% Req = {kill_acceptor, 2}, % The first in the queue
+ %% Req = {kill_acceptor, 1}, % Current acceptor
+ %% Msg = {manager, self(), make_ref(), Req},
+ %% erlang:send_after(timer:seconds(10), self(), Msg),
+ {Sock, lists:reverse(Acc)}.
+
+
+manager_loop(M) ->
+ receive
+ {'DOWN', MRef, process, Pid, Reason} ->
+ M2 = manager_handle_down(M, MRef, Pid, Reason),
+ manager_loop(M2);
+
+ {manager, Pid, Ref, Request} ->
+ M2 = manager_handle_request(M, Pid, Ref, Request),
+ manager_loop(M2)
+ end.
+
+
+manager_handle_down(#manager{acceptors = Acceptors,
+ handlers = Handlers} = M, MRef, Pid, Reason) ->
+ case lists:keysearch(Pid, 2, Acceptors) of
+ {value, {ID, Pid, MRef}} when (Reason =:= normal) ->
+ i("acceptor ~w exited (normally)", [ID]),
+ case lists:keydelete(Pid, 2, Acceptors) of
+ [] ->
+ %% We are done
+ i("the last acceptor - we are done"),
+ exit(normal);
+ Acceptors2 ->
+ M#manager{acceptors = Acceptors2}
+ end;
+ {value, {ID, Pid, MRef}} ->
+ e("acceptor ~w crashed: "
+ "~n ~p", [ID, Reason]),
+ exit({acceptor_died, Reason});
+
+ false -> %% handler!
+ if
+ (Reason =/= normal) ->
+ e("handler ~p died: "
+ "~n ~p", [Pid, Reason]);
+ true ->
+ i("handler ~p terminated", [Pid])
+ end,
+ Handlers2 = lists:keydelete(Pid, 2, Handlers),
+ M#manager{handlers = Handlers2}
+ end.
+
+
+manager_handle_request(#manager{peek = Peek,
+ msg = UseMsg,
+ handler_id = HID,
+ handlers = Handlers} = M, Pid, Ref,
+ {start_handler, Sock}) ->
+ i("try start handler (~w)", [HID]),
+ case handler_start(HID, Sock, UseMsg, Peek) of
+ {ok, {HPid, HMRef}} ->
+ i("handler ~w started", [HID]),
+ manager_reply(Pid, Ref, {ok, HPid}),
+ M#manager{handler_id = HID+1,
+ handlers = [{HID, HPid, HMRef}|Handlers]};
+ {error, Reason} = ERROR ->
+ e("Failed starting new handler: "
+ "~n Sock: ~p"
+ "~n Reason: ~p", [Sock, Reason]),
+ manager_reply(Pid, Ref, ERROR),
+ M
+ end;
+manager_handle_request(#manager{socket = Sock,
+ acceptors = [{AID, APid, AMRef}]} = M, _Pid, _Ref,
+ {kill_acceptor, AID}) ->
+ i("try kill (only remeining) acceptor ~w", [AID]),
+ socket:setopt(Sock, otp, debug, true),
+ manager_stop_acceptor(APid, AMRef, AID, kill),
+ M#manager{acceptors = []};
+manager_handle_request(#manager{socket = Sock,
+ acceptors = Acceptors} = M, _Pid, _Ref,
+ {kill_acceptor, AID}) ->
+ i("try kill acceptor ~w", [AID]),
+ case lists:keysearch(AID, 1, Acceptors) of
+ {value, {AID, APid, AMRef}} ->
+ socket:setopt(Sock, otp, debug, true),
+ manager_stop_acceptor(APid, AMRef, AID, kill),
+ Acceptors2 = lists:keydelete(AID, 1, Acceptors),
+ M#manager{acceptors = Acceptors2};
+ false ->
+ e("no such acceptor"),
+ M
+ end;
+manager_handle_request(#manager{acceptors = Acceptors,
+ handlers = Handlers}, Pid, Ref,
+ {stop, Reason}) ->
+ i("stop"),
+ manager_reply(Pid, Ref, ok),
+ manager_stop_handlers(Handlers, Reason),
+ manager_stop_acceptors(Acceptors, Reason),
+ i("stopped", []),
+ exit(Reason).
+
+manager_stop_acceptors(Acceptors, Reason) ->
+ lists:foreach(fun({ID,P,M}) ->
+ manager_stop_acceptor(P, M, ID, Reason)
+ end, Acceptors).
+
+manager_stop_acceptor(Pid, MRef, ID, Reason) ->
+ i("try stop acceptor ~w (~p): ~p", [ID, Pid, Reason]),
+ erlang:demonitor(MRef, [flush]),
+ acceptor_stop(Pid, Reason),
+ ok.
+
+manager_stop_handlers(Handlers, Reason) ->
+ lists:foreach(fun({ID,P,M}) ->
+ manager_stop_handler(P, M, ID, Reason)
+ end, Handlers).
+
+manager_stop_handler(Pid, MRef, ID, Reason) ->
+ i("try stop handler ~w (~p): ~p", [ID, Pid, Reason]),
+ erlang:demonitor(MRef, [flush]),
+ handler_stop(Pid, Reason),
+ ok.
+
+
+
+%% =========================================================================
+
+acceptor_start(Sock, ID) ->
+ Self = self(),
+ A = {Pid, _} = spawn_monitor(fun() ->
+ acceptor_init(Self, Sock, ID)
+ end),
+ receive
+ {acceptor, Pid, ok} ->
+ {ok, A};
+ {acceptor, Pid, {error, _} = Error} ->
+ exit(Pid, kill), % Just in case
+ Error;
+ {'DOWN', _MRef, process, Pid, Reason} ->
+ {error, {crashed, Reason}}
+ end.
+
+acceptor_stop(Pid, _Reason) ->
+ %% acceptor_request(Pid, {stop, Reason}).
+ exit(Pid, kill).
+
+%% acceptor_request(Pid, Request) ->
+%% request(acceptor, Pid, Request).
+
+%% acceptor_reply(Pid, Ref, Reply) ->
+%% reply(acceptor, Pid, Ref, Reply).
+
+
+acceptor_init(Manager, Sock, ID) ->
+ put(sname, f("acceptor[~w]", [ID])),
+ Manager ! {acceptor, self(), ok},
+ %% ok = socket:setopt(Sock, otp, debug, true),
+ acceptor_loop(#acceptor{id = ID,
+ manager = Manager,
+ socket = Sock}).
+
+acceptor_loop(#acceptor{socket = LSock, atimeout = Timeout} = A) ->
+ i("try accept"),
+ case socket:accept(LSock, Timeout) of
+ {ok, Sock} ->
+ i("accepted: "
+ "~n ~p"
+ "~nwhen"
+ "~n ~p", [Sock, socket:info()]),
+ case acceptor_handle_accept_success(A, Sock) of
+ ok ->
+ acceptor_loop(A);
+ {error, Reason} ->
+ e("Failed starting handler: "
+ "~n ~p", [Reason]),
+ socket:close(Sock),
+ exit({failed_starting_handler, Reason})
+ end;
+ {error, timeout} ->
+ i("timeout"),
+ acceptor_loop(A);
+ {error, Reason} ->
+ e("accept failure: "
+ "~n ~p", [Reason]),
+ exit({accept, Reason})
+ end.
+
+acceptor_handle_accept_success(#acceptor{manager = Manager}, Sock) ->
+ i("try start handler for peer"
+ "~n ~p", [case socket:peername(Sock) of
+ {ok, Peer} -> Peer;
+ {error, _} = E -> E
+ end]),
+ case manager_start_handler(Manager, Sock) of
+ {ok, Pid} ->
+ i("handler (~p) started - now change 'ownership'", [Pid]),
+ case socket:setopt(Sock, otp, controlling_process, Pid) of
+ ok ->
+ %% Normally we should have a msgs collection here
+ %% (of messages we receive before the control was
+ %% handled over to Handler), but since we don't
+ %% have active implemented yet...
+ i("new handler (~p) now controlling process", [Pid]),
+ handler_continue(Pid),
+ ok;
+ {error, _} = ERROR ->
+ exit(Pid, kill),
+ ERROR
+ end;
+ {error, Reason2} ->
+ e("failed starting handler: "
+ "~n (new) Socket: ~p"
+ "~n Reason: ~p", [Sock, Reason2]),
+ exit({failed_starting_handler, Reason2})
+ end.
+
+
+
+%% =========================================================================
+
+handler_start(ID, Sock, UseMsg, Peek) ->
+ Self = self(),
+ H = {Pid, _} = spawn_monitor(fun() ->
+ handler_init(Self, ID, UseMsg, Peek, Sock)
+ end),
+ receive
+ {handler, Pid, ok} ->
+ {ok, H};
+ {handler, Pid, {error, _} = ERROR} ->
+ exit(Pid, kill), % Just in case
+ ERROR
+ end.
+
+handler_stop(Pid, _Reason) ->
+ %% handler_request(Pid, {stop, Reason}).
+ exit(Pid, kill).
+
+handler_continue(Pid) ->
+ handler_request(Pid, continue).
+
+handler_request(Pid, Request) ->
+ ?LIB:request(handler, Pid, Request).
+
+handler_reply(Pid, Ref, Reply) ->
+ ?LIB:reply(handler, Pid, Ref, Reply).
+
+
+handler_init(Manager, ID, Msg, Peek, Sock) ->
+ put(sname, f("handler:~w", [ID])),
+ i("starting"),
+ Manager ! {handler, self(), ok},
+ receive
+ {handler, Pid, Ref, continue} ->
+ i("got continue"),
+ handler_reply(Pid, Ref, ok),
+ G = fun(L, O) -> case socket:getopt(Sock, L, O) of
+ {ok, Val} ->
+ f("~p", [Val]);
+ {error, R} when is_atom(R) ->
+ f("error: ~w", [R]);
+ {error, {T, R}} when is_atom(T) ->
+ f("error: ~w, ~p", [T, R]);
+ {error, R} ->
+ f("error: ~p", [R])
+ end
+ end,
+ GSO = fun(O) -> G(socket, O) end,
+ GIP4 = fun(O) -> G(ip, O) end,
+ GIP6 = fun(O) -> G(ipv6, O) end,
+ {ok, Domain} = socket:getopt(Sock, socket, domain),
+ {ok, Type} = socket:getopt(Sock, socket, type),
+ {ok, Proto} = socket:getopt(Sock, socket, protocol),
+ B2D = GSO(bindtodevice),
+ RA = GSO(reuseaddr),
+ RP = GSO(reuseport),
+ OOBI = GSO(oobinline),
+ RcvBuf = GSO(rcvbuf),
+ RcvLW = GSO(rcvlowat),
+ RcvTO = GSO(rcvtimeo),
+ SndBuf = GSO(sndbuf),
+ SndLW = GSO(sndlowat),
+ SndTO = GSO(sndtimeo),
+ Linger = GSO(linger),
+ Timestamp = GSO(timestamp),
+ FreeBind = GIP4(freebind),
+ MTU = GIP4(mtu),
+ MTUDisc = GIP4(mtu_discover),
+ MALL = GIP4(multicast_all),
+ MIF4 = GIP4(multicast_if),
+ MLoop4 = GIP4(multicast_loop),
+ MTTL = GIP4(multicast_ttl),
+ NF = GIP4(nodefrag), % raw only
+ PktInfo = GIP4(pktinfo), % dgram only
+ RecvErr4 = GIP4(recverr),
+ RecvIF = GIP4(recvif), % Only dgram and raw (and FreeBSD)
+ RecvOPTS = GIP4(recvopts), % Not stream
+ RecvOrigDstAddr = GIP4(recvorigdstaddr),
+ RecvTOS = GIP4(recvtos),
+ RecvTTL = GIP4(recvttl), % not stream
+ RetOpts = GIP4(retopts), % not stream
+ SendSrcAddr = GIP4(sendsrcaddr),
+ TOS = GIP4(tos),
+ Transparent = GIP4(transparent),
+ TTL = GIP4(ttl),
+ MHops = GIP6(multicast_hops),
+ MIF6 = GIP6(multicast_if), % Only dgram and raw
+ MLoop6 = GIP6(multicast_loop),
+ RecvErr6 = GIP6(recverr),
+ RecvPktInfo = GIP6(recvpktinfo),
+ RtHdr = GIP6(rthdr),
+ AuthHdr = GIP6(authhdr),
+ HopLimit = GIP6(hoplimit),
+ HopOpts = GIP6(hopopts),
+ DstOpts = GIP6(dstopts),
+ FlowInfo = GIP6(flowinfo),
+ UHops = GIP6(unicast_hops),
+ i("got continue when: "
+ "~n (socket) Domain: ~p"
+ "~n (socket) Type: ~p"
+ "~n (socket) Protocol: ~p"
+ "~n (socket) Reuse Address: ~s"
+ "~n (socket) Reuse Port: ~s"
+ "~n (socket) Bind To Device: ~s"
+ "~n (socket) OOBInline: ~s"
+ "~n (socket) RcvBuf: ~s"
+ "~n (socket) RcvLW: ~s"
+ "~n (socket) RcvTO: ~s"
+ "~n (socket) SndBuf: ~s"
+ "~n (socket) SndLW: ~s"
+ "~n (socket) SndTO: ~s"
+ "~n (socket) Linger: ~s"
+ "~n (socket) Timestamp: ~s"
+ "~n (ip) FreeBind: ~s"
+ "~n (ip) MTU: ~s"
+ "~n (ip) MTU Discovery: ~s"
+ "~n (ip) Multicast ALL: ~s"
+ "~n (ip) Multicast IF: ~s"
+ "~n (ip) Multicast Loop: ~s"
+ "~n (ip) Multicast TTL: ~s"
+ "~n (ip) Node Frag: ~s"
+ "~n (ip) Pkt Info: ~s"
+ "~n (ip) Recv Err: ~s"
+ "~n (ip) Recv IF: ~s"
+ "~n (ip) Recv OPTS: ~s"
+ "~n (ip) Recv Orig Dst Addr: ~s"
+ "~n (ip) Recv TOS: ~s"
+ "~n (ip) Recv TTL: ~s"
+ "~n (ip) Ret Opts: ~s"
+ "~n (ip) Send Src Addr: ~s"
+ "~n (ip) TOS: ~s"
+ "~n (ip) Transparent: ~s"
+ "~n (ip) TTL: ~s"
+ "~n (ipv6) Multicast Hops: ~s"
+ "~n (ipv6) Multicast IF: ~s"
+ "~n (ipv6) Multicast Loop: ~s"
+ "~n (ipv6) Recv Err: ~s"
+ "~n (ipv6) Recv Pkt Info: ~s"
+ "~n (ipv6) RT Hdr: ~s"
+ "~n (ipv6) Auth Hdr: ~s"
+ "~n (ipv6) Hop Limit: ~s"
+ "~n (ipv6) Hop Opts: ~s"
+ "~n (ipv6) Dst Opts: ~s"
+ "~n (ipv6) Flow Info: ~s"
+ "~n (ipv6) Unicast Hops: ~s",
+ [Domain, Type, Proto,
+ RA, RP, B2D, OOBI,
+ RcvBuf, RcvLW, RcvTO, SndBuf, SndLW, SndTO,
+ Linger, Timestamp,
+ FreeBind, MTU, MTUDisc, MALL, MIF4, MLoop4, MTTL,
+ NF, PktInfo,RecvErr4,
+ RecvIF, RecvOPTS, RecvOrigDstAddr, RecvTOS, RecvTTL, RetOpts,
+ SendSrcAddr, TOS, Transparent, TTL,
+ MHops, MIF6, MLoop6, RecvErr6, RecvPktInfo,
+ RtHdr, AuthHdr, HopLimit, HopOpts, DstOpts, FlowInfo,
+ UHops]),
+
+ %% ok = socket:setopt(Sock, otp, debug, true),
+ %% case socket:getopt(Sock, 0, {13, int}) of
+ %% {ok, Val} ->
+ %% i("PktOpts ok: ~p", [Val]);
+ %% {error, Reason} ->
+ %% e("PktOpts err: ~p", [Reason])
+ %% end,
+ %% ok = socket:setopt(Sock, otp, debug, false),
+ SSO = fun(O, V) -> soso(Sock, O, V) end,
+ SIP4 =
+ fun(O, V) ->
+ if
+ (Type =:= dgram) ->
+ ok = soip(Sock, O, V);
+ true ->
+ ok
+ end
+ end,
+ SSO(timestamp, true),
+ SIP4(pktinfo, true),
+ ok = soip(Sock, recvtos, true),
+ SIP4(recvttl, true),
+ ok = soip(Sock, recvorigdstaddr, true),
+
+ handler_loop(#handler{msg = Msg,
+ peek = Peek,
+ manager = Manager,
+ type = Type,
+ socket = Sock})
+ end.
+
+so(Sock, Lvl, Opt, Val) ->
+ ok = socket:setopt(Sock, Lvl, Opt, Val).
+
+soso(Sock, Opt, Val) ->
+ so(Sock, socket, Opt, Val).
+
+soip(Sock, Opt, Val) ->
+ so(Sock, ip, Opt, Val).
+
+%% soipv6(Sock, Opt, Val) ->
+%% so(Sock, ipv6, Opt, Val).
+
+handler_loop(H) ->
+ i("try read message"),
+ case recv(H) of
+ {ok, {Source, Msg}} ->
+ i("received ~w bytes of data~s",
+ [size(Msg), case Source of
+ undefined -> "";
+ _ -> f(" from:~n ~p", [Source])
+ end]),
+ case ?LIB:dec_msg(Msg) of
+ {request, N, Req} ->
+ i("received request ~w: "
+ "~n ~p", [N, Req]),
+ Reply = ?LIB:enc_rep_msg(N, "hoppsan"),
+ case send(H, Reply, Source) of
+ ok ->
+ i("successfully sent reply ~w", [N]),
+ handler_loop(H);
+ {error, SReason} ->
+ e("failed sending reply ~w:"
+ "~n ~p", [N, SReason]),
+ exit({failed_sending_reply, SReason})
+ end
+ end;
+
+ {error, closed} ->
+ i("closed when"
+ "~n ~p", [socket:info()]),
+ exit(normal);
+
+ {error, RReason} ->
+ e("failed reading request: "
+ "~n ~p", [RReason]),
+ exit({failed_reading_request, RReason})
+ end.
+
+
+recv(#handler{peek = true, socket = Sock, type = stream}) ->
+ peek_recv(Sock);
+recv(#handler{socket = Sock, msg = true, type = stream}) ->
+ case socket:recvmsg(Sock) of
+ {ok, #{addr := undefined = Source,
+ iov := [Data],
+ ctrl := CMsgHdrs,
+ flags := Flags}} ->
+ i("received message: "
+ "~n CMsgHdrs: ~p"
+ "~n Flags: ~p", [CMsgHdrs, Flags]),
+ {ok, {Source, Data}};
+ {ok, X} ->
+ e("received *unexpected* message: "
+ "~n ~p", [X]),
+ {error, {unexpected, X}};
+ {error, _} = ERROR ->
+ ERROR
+ end;
+recv(#handler{socket = Sock, msg = true, type = dgram}) ->
+ case socket:recvmsg(Sock) of
+ {ok, #{addr := Source,
+ iov := [Data],
+ ctrl := CMsgHdrs,
+ flags := Flags}} ->
+ i("received message: "
+ "~n CMsgHdrs: ~p"
+ "~n Flags: ~p", [CMsgHdrs, Flags]),
+ {ok, {Source, Data}};
+ {ok, X} ->
+ {error, {unexpected, X}};
+ {error, _} = ERROR ->
+ ERROR
+ end;
+recv(#handler{peek = false, socket = Sock, type = stream}) ->
+ do_recv(Sock);
+recv(#handler{peek = Peek, socket = Sock, type = dgram})
+ when (Peek =:= true) ->
+ %% ok = socket:setopt(Sock, otp, debug, true),
+ RES = peek_recvfrom(Sock, 5),
+ %% ok = socket:setopt(Sock, otp, debug, false),
+ RES;
+recv(#handler{peek = Peek, socket = Sock, type = dgram})
+ when (Peek =:= false) ->
+ %% ok = socket:setopt(Sock, otp, debug, true),
+ socket:recvfrom(Sock).
+
+do_recv(Sock) ->
+ case socket:recv(Sock) of
+ {ok, Msg} ->
+ {ok, {undefined, Msg}};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+peek_recv(Sock) ->
+ i("try peek on the message type (expect request)"),
+ Type = ?LIB:req(),
+ case socket:recv(Sock, 4, [peek]) of
+ {ok, <<Type:32>>} ->
+ i("was request - do proper recv"),
+ do_recv(Sock);
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+peek_recvfrom(Sock, BufSz) ->
+ i("try peek recvfrom with buffer size ~w", [BufSz]),
+ case socket:recvfrom(Sock, BufSz, [peek]) of
+ {ok, {_Source, Msg}} when (BufSz =:= size(Msg)) ->
+ %% i("we filled the buffer: "
+ %% "~n ~p", [Msg]),
+ %% It *may not* fit => try again with double size
+ peek_recvfrom(Sock, BufSz*2);
+ {ok, _} ->
+ %% It fits => read for real
+ i("we did *not* fill the buffer - do the 'real' read"),
+ socket:recvfrom(Sock);
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+send(#handler{socket = Sock, msg = true, type = stream, stimeout = Timeout},
+ Msg, _) ->
+ CMsgHdr = #{level => ip, type => tos, data => reliability},
+ CMsgHdrs = [CMsgHdr],
+ MsgHdr = #{iov => [Msg], ctrl => CMsgHdrs},
+ %% socket:setopt(Sock, otp, debug, true),
+ Res = socket:sendmsg(Sock, MsgHdr, Timeout),
+ %% socket:setopt(Sock, otp, debug, false),
+ Res;
+send(#handler{socket = Sock, type = stream, stimeout = Timeout}, Msg, _) ->
+ socket:send(Sock, Msg, Timeout);
+send(#handler{socket = Sock, msg = true, type = dgram, stimeout = Timeout},
+ Msg, Dest) ->
+ CMsgHdr = #{level => ip, type => tos, data => reliability},
+ CMsgHdrs = [CMsgHdr],
+ MsgHdr = #{addr => Dest,
+ ctrl => CMsgHdrs,
+ iov => [Msg]},
+ %% ok = socket:setopt(Sock, otp, debug, true),
+ Res = socket:sendmsg(Sock, MsgHdr, Timeout),
+ %% ok = socket:setopt(Sock, otp, debug, false),
+ Res;
+send(#handler{socket = Sock, type = dgram, stimeout = Timeout}, Msg, Dest) ->
+ socket:sendto(Sock, Msg, Dest, Timeout).
+
+%% filler() ->
+%% list_to_binary(lists:duplicate(2048, " FILLER ")).
+
+
+
+%% =========================================================================
+
+f(F, A) ->
+ ?LIB:f(F, A).
+
+e(F) ->
+ e(F, []).
+e(F, A) ->
+ ?LIB:e(F, A).
+
+i(F) ->
+ ?LIB:i(F).
+
+i(F, A) ->
+ ?LIB:i(F, A).
+
diff --git a/lib/os_mon/mibs/v1/.gitignore b/erts/emulator/test/esock_ttest/.gitignore
index e69de29bb2..e69de29bb2 100644
--- a/lib/os_mon/mibs/v1/.gitignore
+++ b/erts/emulator/test/esock_ttest/.gitignore
diff --git a/erts/emulator/test/esock_ttest/esock-ttest b/erts/emulator/test/esock_ttest/esock-ttest
new file mode 100755
index 0000000000..f0d363ab30
--- /dev/null
+++ b/erts/emulator/test/esock_ttest/esock-ttest
@@ -0,0 +1,352 @@
+#!/usr/bin/env escript
+
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2019. All 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 is a simple wrapper escript on top of the socket ttest program(s).
+%% The idea is to make it simple to run in a normal shell (bash).
+%%
+%% ==========================================================================
+
+-define(SECS(I), timer:seconds(I)).
+
+-define(CLIENT_MSG_1_MAX_OUTSTANDING, 100).
+-define(CLIENT_MSG_2_MAX_OUTSTANDING, 10).
+-define(CLIENT_MSG_3_MAX_OUTSTANDING, 1).
+
+main(Args) ->
+ State = process_args(Args),
+ exec(State),
+ ok.
+
+usage(ErrorString) when is_list(ErrorString) ->
+ eprint(ErrorString),
+ usage(),
+ erlang:halt(0).
+
+usage() ->
+ io:format("usage: ~s [options]"
+ "~n"
+ "~n This erlang script is used to start the (e)socket ttest "
+ "~n units (server or client)."
+ "~n"
+ "~n options: "
+ "~n --help Display this info and exit. "
+ "~n --server [server-options] Start a server. "
+ "~n There are no mandatory server options."
+ "~n --client client-options Start a client"
+ "~n Some client options are mandatory and"
+ "~n others optional."
+ "~n --active <active> boolean() | once."
+ "~n Valid for both client and server."
+ "~n Defaults to: false"
+ "~n --transport <transport> Which transport to use: gen|sock[:plain|msg]"
+ "~n gen: gen_tcp"
+ "~n sock: socket"
+ "~n plain: recv/send (default)"
+ "~n msg: recvmsg/sendmsg"
+ "~n Defaults to: sock:plain"
+ "~n --scon <addr>:<port> Address and port of the server."
+ "~n The address part is in the standard form:"
+ "~n \"a.b.c.d\"."
+ "~n Only valid for client."
+ "~n Mandatory."
+ "~n --msg-id <1|2|3> Choose which message to use during the test."
+ "~n Basically: "
+ "~n 1: small"
+ "~n 2: medium"
+ "~n 3: large"
+ "~n Defaults to: 1"
+ "~n --max-outstanding <Num> How many messages to send before waiting for"
+ "~n a reply."
+ "~n Valid only for client."
+ "~n Defaults to: "
+ "~n MsgID 1: 100"
+ "~n MsgID 2: 10"
+ "~n MsgID 3: 1"
+ "~n --runtime <Time> Time of the test in seconds."
+ "~n Only valid for client."
+ "~n Mandatory."
+ "~n Defaults to: 60 (seconds)"
+ "~n"
+ "~n"
+ "~n",
+ [scriptname()]),
+ ok.
+
+process_args(["--help"|_]) ->
+ usage();
+process_args(["--server"|ServerArgs]) ->
+ process_server_args(ServerArgs);
+process_args(["--client"|ClientArgs]) ->
+ process_client_args(ClientArgs);
+process_args(Args) ->
+ usage(f("Invalid Args: "
+ "~n ~p", [Args])).
+
+
+process_server_args(Args) ->
+ Defaults = #{role => server,
+ active => false,
+ transport => {sock, plain}},
+ process_server_args(Args, Defaults).
+
+process_server_args([], State) ->
+ State;
+
+process_server_args(["--active", Active|Args], State)
+ when ((Active =:= "false") orelse
+ (Active =:= "once") orelse
+ (Active =:= "true")) ->
+ process_server_args(Args, State#{active => list_to_atom(Active)});
+
+process_server_args(["--transport", "gen" | Args], State) ->
+ process_server_args(Args, State#{transport => gen});
+process_server_args(["--transport", "sock" | Args], State) ->
+ process_server_args(Args, State#{transport => {sock, plain}});
+process_server_args(["--transport", "sock:plain" | Args], State) ->
+ process_server_args(Args, State#{transport => {sock, plain}});
+process_server_args(["--transport", "sock:msg" | Args], State) ->
+ process_server_args(Args, State#{transport => {sock, msg}});
+
+process_server_args([Arg|_], _State) ->
+ usage(f("Invalid Server arg: ~s", [Arg])).
+
+
+process_client_args(Args) ->
+ Defaults = #{role => client,
+ active => false,
+ transport => {sock, plain},
+ %% Will cause error if not provided
+ %% Should be "addr:port"
+ server => undefined,
+ msg_id => 1,
+ %% Will be filled in based on msg_id if not provided
+ max_outstanding => undefined,
+ runtime => ?SECS(60)},
+ process_client_args(Args, Defaults).
+
+process_client_args([], State) ->
+ process_client_args_ensure_max_outstanding(State);
+
+process_client_args(["--active", Active|Args], State)
+ when ((Active =:= "false") orelse
+ (Active =:= "once") orelse
+ (Active =:= "true")) ->
+ process_client_args(Args, State#{active => list_to_atom(Active)});
+
+process_client_args(["--transport", "gen" | Args], State) ->
+ process_client_args(Args, State#{transport => gen});
+process_client_args(["--transport", "sock" | Args], State) ->
+ process_client_args(Args, State#{transport => {sock, plain}});
+process_client_args(["--transport", "sock:plain" | Args], State) ->
+ process_client_args(Args, State#{transport => {sock, plain}});
+process_client_args(["--transport", "sock:msg" | Args], State) ->
+ process_client_args(Args, State#{transport => {sock, msg}});
+
+process_client_args(["--msg-id", MsgID|Args], State)
+ when ((MsgID =:= "1") orelse
+ (MsgID =:= "2") orelse
+ (MsgID =:= "3")) ->
+ process_client_args(Args, State#{msg_id => list_to_integer(MsgID)});
+
+process_client_args(["--max-outstanding", Max|Args], State) ->
+ try list_to_integer(Max) of
+ I when (I > 0) ->
+ process_client_args(Args, State#{max_outstanding => I});
+ _ ->
+ usage(f("Invalid Max Outstanding: ~s", [Max]))
+ catch
+ _:_:_ ->
+ usage(f("Invalid Max Outstanding: ~s", [Max]))
+ end;
+
+process_client_args(["--scon", Server|Args], State) ->
+ case string:tokens(Server, [$:]) of
+ [AddrStr,PortStr] ->
+ Addr = case inet:parse_address(AddrStr) of
+ {ok, A} ->
+ A;
+ {error, _} ->
+ usage(f("Invalid Server Address: ~s", [AddrStr]))
+ end,
+ Port = try list_to_integer(PortStr) of
+ I when (I > 0) ->
+ I;
+ _ ->
+ usage(f("Invalid Server Port: ~s", [PortStr]))
+ catch
+ _:_:_ ->
+ usage(f("Invalid Server Port: ~s", [PortStr]))
+ end,
+ process_client_args(Args, State#{server => {Addr, Port}});
+ _ ->
+ usage(f("Invalid Server: ~s", [Server]))
+ end;
+
+process_client_args(["--runtime", T|Args], State) ->
+ try list_to_integer(T) of
+ I when (I > 0) ->
+ process_client_args(Args, State#{runtime => ?SECS(I)});
+ _ ->
+ usage(f("Invalid Run Time: ~s", [T]))
+ catch
+ _:_:_ ->
+ usage(f("Invalid Run Time: ~s", [T]))
+ end;
+
+process_client_args([Arg|_], _State) ->
+ usage(f("Invalid Client arg: ~s", [Arg])).
+
+
+process_client_args_ensure_max_outstanding(
+ #{msg_id := 1,
+ max_outstanding := undefined} = State) ->
+ State#{max_outstanding => ?CLIENT_MSG_1_MAX_OUTSTANDING};
+process_client_args_ensure_max_outstanding(
+ #{msg_id := 2,
+ max_outstanding := undefined} = State) ->
+ State#{max_outstanding => ?CLIENT_MSG_2_MAX_OUTSTANDING};
+process_client_args_ensure_max_outstanding(
+ #{msg_id := 3,
+ max_outstanding := undefined} = State) ->
+ State#{max_outstanding => ?CLIENT_MSG_3_MAX_OUTSTANDING};
+process_client_args_ensure_max_outstanding(
+ #{msg_id := MsgID,
+ max_outstanding := MaxOutstanding} = State)
+ when ((MsgID =:= 1) orelse
+ (MsgID =:= 2) orelse
+ (MsgID =:= 3)) andalso
+ (is_integer(MaxOutstanding) andalso (MaxOutstanding > 0)) ->
+ State;
+process_client_args_ensure_max_outstanding(
+ #{msg_id := MsgID,
+ max_outstanding := MaxOutstanding}) ->
+ usage(f("Invalid Msg ID (~w) and Max Outstanding (~w)",
+ [MsgID, MaxOutstanding])).
+
+
+
+%% ==========================================================================
+
+exec(#{role := server,
+ active := Active,
+ transport := gen}) ->
+ case socket_test_ttest_tcp_server_gen:start(Active) of
+ {ok, {Pid, _}} ->
+ MRef = erlang:monitor(process, Pid),
+ receive
+ {'DOWN', MRef, process, Pid, Info} ->
+ Info
+ end;
+ {error, Reason} ->
+ eprint(f("Failed starting server: "
+ "~n ~p", [Reason])),
+ error
+ end;
+exec(#{role := server,
+ active := Active,
+ transport := {sock, Method}}) ->
+ case socket_test_ttest_tcp_server_socket:start(Method, Active) of
+ {ok, {Pid, _}} ->
+ MRef = erlang:monitor(process, Pid),
+ receive
+ {'DOWN', MRef, process, Pid, Info} ->
+ Info
+ end;
+ {error, Reason} ->
+ eprint(f("Failed starting server: "
+ "~n ~p", [Reason])),
+ error
+ end;
+
+exec(#{role := client,
+ server := undefined}) ->
+ usage("Mandatory option 'server' not provided");
+exec(#{role := client,
+ server := {Addr, Port},
+ active := Active,
+ transport := gen,
+ msg_id := MsgID,
+ max_outstanding := MaxOutstanding,
+ runtime := RunTime}) ->
+ case socket_test_ttest_tcp_client_gen:start(true,
+ Active,
+ Addr, Port,
+ MsgID, MaxOutstanding,
+ RunTime) of
+ {ok, Pid} ->
+ MRef = erlang:monitor(process, Pid),
+ receive
+ {'DOWN', MRef, process, Pid, Info} ->
+ Info
+ end;
+ {error, Reason} ->
+ eprint(f("Failed starting server: "
+ "~n ~p", [Reason])),
+ error
+ end;
+exec(#{role := client,
+ server := {Addr, Port},
+ active := Active,
+ transport := {sock, Method},
+ msg_id := MsgID,
+ max_outstanding := MaxOutstanding,
+ runtime := RunTime}) ->
+ case socket_test_ttest_tcp_client_socket:start(true,
+ Method,
+ Active,
+ Addr, Port,
+ MsgID, MaxOutstanding,
+ RunTime) of
+ {ok, Pid} ->
+ MRef = erlang:monitor(process, Pid),
+ receive
+ {'DOWN', MRef, process, Pid, Info} ->
+ Info
+ end;
+ {error, Reason} ->
+ eprint(f("Failed starting server: "
+ "~n ~p", [Reason])),
+ error
+ end;
+exec(_) ->
+ usage("Unexpected option combo"),
+ ok.
+
+
+
+%% ==========================================================================
+
+f(F, A) ->
+ socket_test_ttest_lib:format(F, A).
+
+eprint(ErrorString) when is_list(ErrorString) ->
+ print("<ERROR> " ++ ErrorString ++ "~n", []).
+
+print(F, A) ->
+ io:format(F ++ "~n", A).
+
+scriptname() ->
+ FullName = escript:script_name(),
+ filename:basename(FullName).
+
diff --git a/erts/emulator/test/esock_ttest/esock-ttest-client b/erts/emulator/test/esock_ttest/esock-ttest-client
new file mode 100755
index 0000000000..1ab56f2d44
--- /dev/null
+++ b/erts/emulator/test/esock_ttest/esock-ttest-client
@@ -0,0 +1,72 @@
+#!/bin/sh
+
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2019-2019. All 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%
+#
+
+EMU=$ERL_TOP/erts/emulator
+EMU_TEST=$EMU/test
+ESOCK_TTEST=$EMU_TEST/esock_ttest
+
+RUNTIME=30
+
+MSGID=$1
+SERVER_ADDR=$2
+SERVER_PORT=$3
+
+# ---------------------------------------------------------------------------
+
+ITERATIONS="\
+ gen false $MSGID
+ gen true $MSGID
+ gen once $MSGID
+ sock false $MSGID
+ sock true $MSGID
+ sock once $MSGID"
+
+# gen false 2
+# gen true 2
+# gen once 2
+# sock false 2
+# sock true 2
+# sock once 2
+# gen false 3
+# gen true 3
+# gen once 3
+# sock false 3
+# sock true 3
+# sock once 3
+#
+
+# ---------------------------------------------------------------------------
+
+echo "$ITERATIONS" |
+ while read TRANSPORT ACTIVE MSG_ID; do
+
+ echo ""
+ echo "=========== transport = $TRANSPORT, active = $ACTIVE, msg-id = $MSG_ID ==========="
+ # The /dev/null at the end is necessary because erlang "does things" with stdin
+ # and this case would cause the 'while read' to "fail" so that we only would
+ # loop one time
+ $ESOCK_TTEST/esock-ttest --client --transport $TRANSPORT --active $ACTIVE --msg-id $MSG_ID --scon $SERVER_ADDR:$SERVER_PORT --runtime $RUNTIME </dev/null
+ echo ""
+
+ done
+
+
diff --git a/erts/emulator/test/esock_ttest/esock-ttest-server-gen b/erts/emulator/test/esock_ttest/esock-ttest-server-gen
new file mode 100755
index 0000000000..c29184772e
--- /dev/null
+++ b/erts/emulator/test/esock_ttest/esock-ttest-server-gen
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2019-2019. All 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%
+#
+
+EMU=$ERL_TOP/erts/emulator
+EMU_TEST=$EMU/test
+ESOCK_TTEST=$EMU_TEST/esock_ttest
+
+if [ $# = 1 ]; then
+ ACTIVE="--active $1"
+fi
+
+$ESOCK_TTEST/esock-ttest --server --transport gen $ACTIVE
+
diff --git a/erts/emulator/test/esock_ttest/esock-ttest-server-sock b/erts/emulator/test/esock_ttest/esock-ttest-server-sock
new file mode 100755
index 0000000000..4ec0d335d9
--- /dev/null
+++ b/erts/emulator/test/esock_ttest/esock-ttest-server-sock
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2019-2019. All 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%
+#
+
+EMU=$ERL_TOP/erts/emulator
+EMU_TEST=$EMU/test
+ESOCK_TTEST=$EMU_TEST/esock_ttest
+
+if [ $# = 1 ]; then
+ ACTIVE="--active $1"
+fi
+
+$ESOCK_TTEST/esock-ttest --server --transport sock $ACTIVE
+
diff --git a/erts/emulator/test/exception_SUITE.erl b/erts/emulator/test/exception_SUITE.erl
index aec66cb9a3..c4d9ea515a 100644
--- a/erts/emulator/test/exception_SUITE.erl
+++ b/erts/emulator/test/exception_SUITE.erl
@@ -36,6 +36,11 @@
%% during compilation instead of at runtime, so do not perform this analysis.
-compile([{hipe, [no_icode_range]}]).
+%% Module-level type optimization propagates the constants used when testing
+%% increment1/1 and increment2/1, which makes it test something completely
+%% different, so we're turning it off.
+-compile(no_module_opt).
+
suite() ->
[{ct_hooks,[ts_install_cth]},
{timetrap, {minutes, 1}}].
diff --git a/erts/emulator/test/fun_SUITE.erl b/erts/emulator/test/fun_SUITE.erl
index 73fe9b0d8f..4042b58ff2 100644
--- a/erts/emulator/test/fun_SUITE.erl
+++ b/erts/emulator/test/fun_SUITE.erl
@@ -576,7 +576,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,
@@ -598,7 +598,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
@@ -626,7 +626,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),
@@ -710,6 +710,16 @@ t_is_function2(Config) when is_list(Config) ->
bad_arity({}),
bad_arity({a,b}),
bad_arity(self()),
+
+ %% Bad arity argument in guard test.
+ Fun = fun erlang:abs/1,
+ ok = if
+ is_function(Fun, -1) -> error;
+ is_function(Fun, 256) -> error;
+ is_function(Fun, a) -> error;
+ is_function(Fun, Fun) -> error;
+ true -> ok
+ end,
ok.
bad_arity(A) ->
@@ -806,11 +816,13 @@ verify_not_undef(Fun, Tag) ->
ct:fail("tag ~w not defined in fun_info", [Tag]);
{Tag,_} -> ok
end.
-
+
id(X) ->
X.
spawn_call(Node, AFun) ->
+ Parent = self(),
+ Init = erlang:whereis(init),
Pid = spawn_link(Node,
fun() ->
receive
@@ -821,8 +833,10 @@ spawn_call(Node, AFun) ->
_ -> lists:seq(0, Arity-1)
end,
Res = apply(Fun, Args),
- {pid,Creator} = erlang:fun_info(Fun, pid),
- Creator ! {result,Res}
+ case erlang:fun_info(Fun, pid) of
+ {pid,Init} -> Parent ! {result,Res};
+ {pid,Creator} -> Creator ! {result,Res}
+ end
end
end),
Pid ! {AFun,AFun,AFun},
diff --git a/erts/emulator/test/net_SUITE.erl b/erts/emulator/test/net_SUITE.erl
new file mode 100644
index 0000000000..1a973cacb2
--- /dev/null
+++ b/erts/emulator/test/net_SUITE.erl
@@ -0,0 +1,481 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019-2019. All 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 test suite is basically a "placeholder" for a proper test suite...
+%%
+
+%% Run the entire test suite:
+%% ts:run(emulator, net_SUITE, [batch]).
+%%
+%% Run a specific group:
+%% ts:run(emulator, net_SUITE, {group, foo}, [batch]).
+%%
+%% Run a specific test case:
+%% ts:run(emulator, net_SUITE, foo, [batch]).
+
+-module(net_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("common_test/include/ct_event.hrl").
+
+%% Suite exports
+-export([suite/0, all/0, groups/0]).
+-export([init_per_suite/1, end_per_suite/1,
+ init_per_group/2, end_per_group/2,
+ init_per_testcase/2, end_per_testcase/2]).
+
+%% Test cases
+-export([
+ %% *** API Basic ***
+ api_b_gethostname/1,
+ api_b_name_and_addr_info/1,
+
+ api_b_name_and_index/1
+
+ %% Tickets
+ ]).
+
+
+%% -include("socket_test_evaluator.hrl").
+
+%% Internal exports
+%% -export([]).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-define(SLEEP(T), receive after T -> ok end).
+
+-define(FAIL(R), exit(R)).
+
+-define(MINS(M), timer:minutes(M)).
+-define(SECS(S), timer:seconds(S)).
+
+-define(TT(T), ct:timetrap(T)).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+suite() ->
+ [{ct_hooks,[ts_install_cth]},
+ {timetrap,{minutes,1}}].
+
+all() ->
+ Groups = [{api, "ENET_TEST_API", include}],
+ [use_group(Group, Env, Default) || {Group, Env, Default} <- Groups].
+
+use_group(Group, Env, Default) ->
+ case os:getenv(Env) of
+ false when (Default =:= include) ->
+ [{group, Group}];
+ false ->
+ [];
+ Val ->
+ case list_to_atom(string:to_lower(Val)) of
+ Use when (Use =:= include) orelse
+ (Use =:= enable) orelse
+ (Use =:= true) ->
+ [{group, Group}];
+ _ ->
+ []
+ end
+ end.
+
+
+groups() ->
+ [{api, [], api_cases()},
+ {api_basic, [], api_basic_cases()}
+
+ %% {tickets, [], ticket_cases()}
+ ].
+
+api_cases() ->
+ [
+ {group, api_basic}
+ ].
+
+api_basic_cases() ->
+ [
+ api_b_gethostname,
+ api_b_name_and_addr_info,
+ api_b_name_and_index
+ ].
+
+%% ticket_cases() ->
+%% [].
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+init_per_suite(Config) ->
+ case os:type() of
+ {win32, _} ->
+ not_yet_implemented();
+ _ ->
+ %% ?LOGGER:start(),
+ Config
+ end.
+
+end_per_suite(_) ->
+ %% ?LOGGER:stop(),
+ ok.
+
+init_per_group(_Group, Config) ->
+ Config.
+
+end_per_group(_Group, Config) ->
+ Config.
+
+
+init_per_testcase(_TC, Config) ->
+ Config.
+
+end_per_testcase(_TC, Config) ->
+ Config.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% %%
+%% API BASIC %%
+%% %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Get the hostname of the host.
+api_b_gethostname(suite) ->
+ [];
+api_b_gethostname(doc) ->
+ [];
+api_b_gethostname(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_b_gethostname,
+ fun() ->
+ ok = api_b_gethostname()
+ end).
+
+
+api_b_gethostname() ->
+ case net:gethostname() of
+ {ok, Hostname} ->
+ i("hostname: ~s", [Hostname]),
+ ok;
+ {error, Reason} ->
+ ?FAIL(Reason)
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Get name and address info.
+api_b_name_and_addr_info(suite) ->
+ [];
+api_b_name_and_addr_info(doc) ->
+ [];
+api_b_name_and_addr_info(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_b_name_and_addr_info,
+ fun() ->
+ ok = api_b_name_and_addr_info()
+ end).
+
+
+api_b_name_and_addr_info() ->
+ Domain = inet,
+ Addr = which_local_addr(Domain),
+ SA = #{family => Domain, addr => Addr},
+ Hostname =
+ case net:getnameinfo(SA) of
+ {ok, #{host := Name, service := Service} = NameInfo}
+ when is_list(Name) andalso is_list(Service) ->
+ i("getnameinfo: "
+ "~n ~p", [NameInfo]),
+ Name;
+ {ok, BadNameInfo} ->
+ ?FAIL({getnameinfo, SA, BadNameInfo});
+ {error, Reason1} ->
+ ?FAIL({getnameinfo, SA, Reason1})
+ end,
+ case net:getaddrinfo(Hostname) of
+ {ok, AddrInfos} when is_list(AddrInfos) ->
+ i("getaddrinfo: "
+ "~n ~p", [AddrInfos]),
+ verify_addr_info(AddrInfos, Domain);
+ {ok, BadAddrInfo} ->
+ ?FAIL({getaddrinfo, Hostname, BadAddrInfo});
+ {error, Reason2} ->
+ ?FAIL({getaddrinfo, Hostname, Reason2})
+ end.
+
+
+verify_addr_info(AddrInfos, Domain) when (AddrInfos =/= []) ->
+ verify_addr_info2(AddrInfos, Domain).
+
+verify_addr_info2([], _Domain) ->
+ ok;
+verify_addr_info2([#{addr := #{addr := Addr,
+ family := Domain,
+ port := Port},
+ family := Domain,
+ type := _Type,
+ protocol := _Proto}|T], Domain)
+ when is_integer(Port) andalso
+ (((Domain =:= inet) andalso is_tuple(Addr) andalso (size(Addr) =:= 4)) orelse
+ ((Domain =:= inet6) andalso is_tuple(Addr) andalso (size(Addr) =:= 8))) ->
+ verify_addr_info2(T, Domain);
+verify_addr_info2([#{family := DomainA}|T], DomainB)
+ when (DomainA =/= DomainB) ->
+ %% Ignore entries for other domains
+ verify_addr_info2(T, DomainB);
+verify_addr_info2([BadAddrInfo|_], Domain) ->
+ ?FAIL({bad_address_info, BadAddrInfo, Domain}).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Verify (interface) name and index functions.
+%% if_names/0,
+%% if_name2index/1
+%% if_index2name/1
+api_b_name_and_index(suite) ->
+ [];
+api_b_name_and_index(doc) ->
+ [];
+api_b_name_and_index(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_b_name_and_index,
+ fun() ->
+ ok = api_b_name_and_index()
+ end).
+
+
+api_b_name_and_index() ->
+ Names =
+ case net:if_names() of
+ {ok, N} when is_list(N) andalso (N =/= []) ->
+ N;
+ {error, Reason} ->
+ ?FAIL({if_names, Reason})
+ end,
+ verify_if_names(Names).
+
+verify_if_names([]) ->
+ ok;
+verify_if_names([{Index, Name}|T]) ->
+ case net:if_name2index(Name) of
+ {ok, Index} ->
+ ok;
+ {ok, BadIndex} ->
+ ?FAIL({name2index, Name, Index, BadIndex});
+ {error, ReasonN2I} ->
+ ?FAIL({name2index, Name, ReasonN2I})
+ end,
+ case net:if_index2name(Index) of
+ {ok, Name} ->
+ ok;
+ {ok, BadName} ->
+ ?FAIL({index2name, Index, Name, BadName});
+ {error, ReasonI2N} ->
+ ?FAIL({index2name, Index, ReasonI2N})
+ end,
+ verify_if_names(T).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% local_host() ->
+%% try net_adm:localhost() of
+%% Host when is_list(Host) ->
+%% %% Convert to shortname if long
+%% case string:tokens(Host, [$.]) of
+%% [H|_] ->
+%% list_to_atom(H)
+%% end
+%% catch
+%% C:E:S ->
+%% erlang:raise(C, E, S)
+%% end.
+
+
+%% This gets the local address (not 127.0...)
+%% We should really implement this using the (new) net module,
+%% but until that gets the necessary functionality...
+which_local_addr(Domain) ->
+ case inet:getifaddrs() of
+ {ok, IFL} ->
+ which_addr(Domain, IFL);
+ {error, Reason} ->
+ ?FAIL({inet, getifaddrs, Reason})
+ end.
+
+which_addr(_Domain, []) ->
+ skip(no_address);
+which_addr(Domain, [{"lo" ++ _, _}|IFL]) ->
+ which_addr(Domain, IFL);
+which_addr(Domain, [{_Name, IFO}|IFL]) ->
+ case which_addr2(Domain, IFO) of
+ {ok, Addr} ->
+ Addr;
+ {error, no_address} ->
+ which_addr(Domain, IFL)
+ end;
+which_addr(Domain, [_|IFL]) ->
+ which_addr(Domain, IFL).
+
+which_addr2(_Domain, []) ->
+ {error, no_address};
+which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) ->
+ {ok, Addr};
+which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) ->
+ {ok, Addr};
+which_addr2(Domain, [_|IFO]) ->
+ which_addr2(Domain, IFO).
+
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+not_yet_implemented() ->
+ skip("not yet implemented").
+
+skip(Reason) ->
+ throw({skip, Reason}).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% t() ->
+%% os:timestamp().
+
+
+%% tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) ->
+%% T1 = A1*1000000000+B1*1000+(C1 div 1000),
+%% T2 = A2*1000000000+B2*1000+(C2 div 1000),
+%% T2 - T1.
+
+
+formated_timestamp() ->
+ format_timestamp(os:timestamp()).
+
+format_timestamp({_N1, _N2, _N3} = TS) ->
+ {_Date, Time} = calendar:now_to_local_time(TS),
+ %% {YYYY,MM,DD} = Date,
+ {Hour,Min,Sec} = Time,
+ %% FormatTS =
+ %% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w.~w",
+ %% [YYYY, MM, DD, Hour, Min, Sec, N3]),
+ FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w", [Hour, Min, Sec]),
+ lists:flatten(FormatTS).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+set_tc_name(N) when is_atom(N) ->
+ set_tc_name(atom_to_list(N));
+set_tc_name(N) when is_list(N) ->
+ put(tc_name, N).
+
+%% get_tc_name() ->
+%% get(tc_name).
+
+tc_begin(TC) ->
+ set_tc_name(TC),
+ tc_print("begin ***",
+ "~n----------------------------------------------------~n", "").
+
+tc_end(Result) when is_list(Result) ->
+ tc_print("done: ~s", [Result],
+ "", "----------------------------------------------------~n~n"),
+ ok.
+
+
+tc_try(Case, Fun) when is_atom(Case) andalso is_function(Fun, 0) ->
+ tc_begin(Case),
+ try
+ begin
+ Fun(),
+ ?SLEEP(?SECS(1)),
+ tc_end("ok")
+ end
+ catch
+ throw:{skip, _} = SKIP ->
+ tc_end("skipping"),
+ SKIP;
+ Class:Error:Stack ->
+ tc_end("failed"),
+ erlang:raise(Class, Error, Stack)
+ end.
+
+
+tc_print(F, Before, After) ->
+ tc_print(F, [], Before, After).
+
+tc_print(F, A, Before, After) ->
+ Name = tc_which_name(),
+ FStr = f("*** [~s][~s][~p] " ++ F ++ "~n",
+ [formated_timestamp(),Name,self()|A]),
+ io:format(user, Before ++ FStr ++ After, []).
+
+tc_which_name() ->
+ case get(tc_name) of
+ undefined ->
+ case get(sname) of
+ undefined ->
+ "";
+ SName when is_list(SName) ->
+ SName
+ end;
+ Name when is_list(Name) ->
+ Name
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% l2a(S) when is_list(S) ->
+%% list_to_atom(S).
+
+%% l2b(L) when is_list(L) ->
+%% list_to_binary(L).
+
+%% b2l(B) when is_binary(B) ->
+%% binary_to_list(B).
+
+f(F, A) ->
+ lists:flatten(io_lib:format(F, A)).
+
+
+%% i(F) ->
+%% i(F, []).
+
+i(F, A) ->
+ FStr = f("[~s] " ++ F, [formated_timestamp()|A]),
+ io:format(user, FStr ++ "~n", []),
+ io:format(FStr, []).
+
diff --git a/erts/emulator/test/nif_SUITE.erl b/erts/emulator/test/nif_SUITE.erl
index a2f3489943..4a0ad9c1d5 100644
--- a/erts/emulator/test/nif_SUITE.erl
+++ b/erts/emulator/test/nif_SUITE.erl
@@ -64,7 +64,8 @@
nif_phash2/1,
nif_whereis/1, nif_whereis_parallel/1,
nif_whereis_threaded/1, nif_whereis_proxy/1,
- nif_ioq/1
+ nif_ioq/1,
+ pid/1
]).
-export([many_args_100/100]).
@@ -103,7 +104,8 @@ all() ->
nif_internal_hash_salted,
nif_phash2,
nif_whereis, nif_whereis_parallel, nif_whereis_threaded,
- nif_ioq].
+ nif_ioq,
+ pid].
groups() ->
[{G, [], api_repeaters()} || G <- api_groups()]
@@ -485,91 +487,118 @@ t_on_load(Config) when is_list(Config) ->
-define(ERL_NIF_SELECT_READ, (1 bsl 0)).
-define(ERL_NIF_SELECT_WRITE, (1 bsl 1)).
-define(ERL_NIF_SELECT_STOP, (1 bsl 2)).
+-define(ERL_NIF_SELECT_CANCEL, (1 bsl 3)).
+-define(ERL_NIF_SELECT_CUSTOM_MSG, (1 bsl 4)).
-define(ERL_NIF_SELECT_STOP_CALLED, (1 bsl 0)).
-define(ERL_NIF_SELECT_STOP_SCHEDULED, (1 bsl 1)).
-define(ERL_NIF_SELECT_INVALID_EVENT, (1 bsl 2)).
-define(ERL_NIF_SELECT_FAILED, (1 bsl 3)).
-
+-define(ERL_NIF_SELECT_READ_CANCELLED, (1 bsl 4)).
+-define(ERL_NIF_SELECT_WRITE_CANCELLED, (1 bsl 5)).
select(Config) when is_list(Config) ->
ensure_lib_loaded(Config),
+ select_do(0, make_ref(), make_ref(), null),
+
+ RefBin = list_to_binary(lists:duplicate(100, $x)),
+ [select_do(?ERL_NIF_SELECT_CUSTOM_MSG,
+ small, {a, tuple, with, "some", RefBin}, MSG_ENV)
+ || MSG_ENV <- [null, alloc_env]],
+ ok.
+
+select_do(Flag, Ref, Ref2, MSG_ENV) ->
+ io:format("select_do(~p, ~p, ~p)\n", [Ref, Ref2, MSG_ENV]),
- Ref = make_ref(),
- Ref2 = make_ref(),
{{R, R_ptr}, {W, W_ptr}} = pipe_nif(),
ok = write_nif(W, <<"hej">>),
<<"hej">> = read_nif(R, 3),
%% Wait for read
eagain = read_nif(R, 3),
- 0 = select_nif(R,?ERL_NIF_SELECT_READ,R,null,Ref),
+ 0 = select_nif(R,?ERL_NIF_SELECT_READ bor Flag, R,null,Ref,MSG_ENV),
[] = flush(0),
ok = write_nif(W, <<"hej">>),
- [{select, R, Ref, ready_input}] = flush(),
- 0 = select_nif(R,?ERL_NIF_SELECT_READ,R,self(),Ref2),
- [{select, R, Ref2, ready_input}] = flush(),
+ receive_ready(R, Ref, ready_input),
+ 0 = select_nif(R,?ERL_NIF_SELECT_READ bor Flag,R,self(),Ref2,MSG_ENV),
+ receive_ready(R, Ref2, ready_input),
Papa = self(),
Pid = spawn_link(fun() ->
- [{select, R, Ref, ready_input}] = flush(),
+ receive_ready(R, Ref, ready_input),
Papa ! {self(), done}
end),
- 0 = select_nif(R,?ERL_NIF_SELECT_READ,R,Pid,Ref),
+ 0 = select_nif(R, ?ERL_NIF_SELECT_READ bor Flag, R, Pid, Ref,MSG_ENV),
{Pid, done} = receive_any(1000),
+
+ %% Cancel read
+ 0 = select_nif(R,?ERL_NIF_SELECT_READ bor ?ERL_NIF_SELECT_CANCEL,R,null,Ref,null),
<<"hej">> = read_nif(R, 3),
+ 0 = select_nif(R, ?ERL_NIF_SELECT_READ bor Flag, R, null, Ref, MSG_ENV),
+ ?ERL_NIF_SELECT_READ_CANCELLED =
+ select_nif(R,?ERL_NIF_SELECT_READ bor ?ERL_NIF_SELECT_CANCEL,R,null,Ref,null),
+ ok = write_nif(W, <<"hej again">>),
+ [] = flush(0),
+ <<"hej again">> = read_nif(R, 9),
%% Wait for write
Written = write_full(W, $a),
- 0 = select_nif(W,?ERL_NIF_SELECT_WRITE,W,self(),Ref),
+ 0 = select_nif(W, ?ERL_NIF_SELECT_WRITE bor Flag, W, self(), Ref, MSG_ENV),
[] = flush(0),
Written = read_nif(R,byte_size(Written)),
- [{select, W, Ref, ready_output}] = flush(),
+ receive_ready(W, Ref, ready_output),
+
+ %% Cancel write
+ 0 = select_nif(W, ?ERL_NIF_SELECT_WRITE bor ?ERL_NIF_SELECT_CANCEL, W, null, Ref, null),
+ Written2 = write_full(W, $b),
+ 0 = select_nif(W, ?ERL_NIF_SELECT_WRITE bor Flag, W, null, Ref, MSG_ENV),
+ ?ERL_NIF_SELECT_WRITE_CANCELLED =
+ select_nif(W, ?ERL_NIF_SELECT_WRITE bor ?ERL_NIF_SELECT_CANCEL, W, null, Ref, null),
+ Written2 = read_nif(R,byte_size(Written2)),
+ [] = flush(0),
%% Close write and wait for EOF
eagain = read_nif(R, 1),
- check_stop_ret(select_nif(W,?ERL_NIF_SELECT_STOP,W,null,Ref)),
+ check_stop_ret(select_nif(W, ?ERL_NIF_SELECT_STOP, W, null, Ref, null)),
[{fd_resource_stop, W_ptr, _}] = flush(),
{1, {W_ptr,_}} = last_fd_stop_call(),
true = is_closed_nif(W),
[] = flush(0),
- 0 = select_nif(R,?ERL_NIF_SELECT_READ,R,self(),Ref),
- [{select, R, Ref, ready_input}] = flush(),
+ 0 = select_nif(R, ?ERL_NIF_SELECT_READ bor Flag, R, self(), Ref, MSG_ENV),
+ receive_ready(R, Ref, ready_input),
eof = read_nif(R,1),
- check_stop_ret(select_nif(R,?ERL_NIF_SELECT_STOP,R,null,Ref)),
+ check_stop_ret(select_nif(R, ?ERL_NIF_SELECT_STOP, R, null, Ref, null)),
[{fd_resource_stop, R_ptr, _}] = flush(),
{1, {R_ptr,_}} = last_fd_stop_call(),
true = is_closed_nif(R),
- select_2(Config).
+ select_2(Flag, Ref, Ref2, MSG_ENV).
-select_2(Config) ->
+select_2(Flag, Ref1, Ref2, MSG_ENV) ->
erlang:garbage_collect(),
{_,_,2} = last_resource_dtor_call(),
- Ref1 = make_ref(),
- Ref2 = make_ref(),
{{R, R_ptr}, {W, W_ptr}} = pipe_nif(),
%% Change ref
eagain = read_nif(R, 1),
- 0 = select_nif(R,?ERL_NIF_SELECT_READ,R,null,Ref1),
- 0 = select_nif(R,?ERL_NIF_SELECT_READ,R,self(),Ref2),
+ 0 = select_nif(R, ?ERL_NIF_SELECT_READ bor Flag, R, null, Ref1, MSG_ENV),
+ 0 = select_nif(R, ?ERL_NIF_SELECT_READ bor Flag, R, self(), Ref2, MSG_ENV),
[] = flush(0),
ok = write_nif(W, <<"hej">>),
- [{select, R, Ref2, ready_input}] = flush(),
+ receive_ready(R, Ref2, ready_input),
<<"hej">> = read_nif(R, 3),
%% Change pid
eagain = read_nif(R, 1),
- 0 = select_nif(R,?ERL_NIF_SELECT_READ,R,null,Ref1),
+ 0 = select_nif(R, ?ERL_NIF_SELECT_READ bor Flag, R, null, Ref1, MSG_ENV),
Papa = self(),
spawn_link(fun() ->
- 0 = select_nif(R,?ERL_NIF_SELECT_READ,R,null,Ref1),
+ 0 = select_nif(R, ?ERL_NIF_SELECT_READ bor Flag, R, null, Ref1, MSG_ENV),
[] = flush(0),
Papa ! sync,
- [{select, R, Ref1, ready_input}] = flush(),
+ receive_ready(R, Ref1, ready_input),
<<"hej">> = read_nif(R, 3),
Papa ! done
end),
@@ -578,24 +607,30 @@ select_2(Config) ->
done = receive_any(),
[] = flush(0),
- check_stop_ret(select_nif(R,?ERL_NIF_SELECT_STOP,R,null,Ref1)),
+ check_stop_ret(select_nif(R,?ERL_NIF_SELECT_STOP,R,null,Ref1, null)),
[{fd_resource_stop, R_ptr, _}] = flush(),
{1, {R_ptr,_}} = last_fd_stop_call(),
true = is_closed_nif(R),
%% Stop without previous read/write select
- ?ERL_NIF_SELECT_STOP_CALLED = select_nif(W,?ERL_NIF_SELECT_STOP,W,null,Ref1),
+ ?ERL_NIF_SELECT_STOP_CALLED = select_nif(W,?ERL_NIF_SELECT_STOP,W,null,Ref1,null),
[{fd_resource_stop, W_ptr, 1}] = flush(),
{1, {W_ptr,1}} = last_fd_stop_call(),
true = is_closed_nif(W),
- select_3(Config).
+ select_3().
-select_3(_Config) ->
+select_3() ->
erlang:garbage_collect(),
{_,_,2} = last_resource_dtor_call(),
ok.
+receive_ready(R, Ref, IOatom) when is_reference(Ref) ->
+ [{select, R, Ref, IOatom}] = flush();
+receive_ready(_, Msg, _) ->
+ [Got] = flush(),
+ {true,_,_} = {Got=:=Msg, Got, Msg}.
+
%% @doc The stealing child process for the select_steal test. Duplicates given
%% W/RFds and runs select on them to steal
select_steal_child_process(Parent, RFd) ->
@@ -604,7 +639,7 @@ select_steal_child_process(Parent, RFd) ->
Ref2 = make_ref(),
%% Try to select from the child pid (steal from parent)
- ?assertEqual(0, select_nif(R2Fd, ?ERL_NIF_SELECT_READ, R2Fd, null, Ref2)),
+ ?assertEqual(0, select_nif(R2Fd, ?ERL_NIF_SELECT_READ, R2Fd, null, Ref2, null)),
?assertEqual([], flush(0)),
?assertEqual(eagain, read_nif(R2Fd, 1)),
@@ -612,7 +647,7 @@ select_steal_child_process(Parent, RFd) ->
Parent ! {self(), stage1}, % signal parent to send the <<"stolen1">>
%% Receive <<"stolen1">> via enif_select
- ?assertEqual(0, select_nif(R2Fd, ?ERL_NIF_SELECT_READ, R2Fd, null, Ref2)),
+ ?assertEqual(0, select_nif(R2Fd, ?ERL_NIF_SELECT_READ, R2Fd, null, Ref2, null)),
?assertMatch([{select, R2Fd, Ref2, ready_input}], flush()),
?assertEqual(<<"stolen1">>, read_nif(R2Fd, 7)),
@@ -630,7 +665,7 @@ select_steal(Config) when is_list(Config) ->
{{RFd, RPtr}, {WFd, WPtr}} = pipe_nif(),
%% Bind the socket to current pid in enif_select
- ?assertEqual(0, select_nif(RFd, ?ERL_NIF_SELECT_READ, RFd, null, Ref)),
+ ?assertEqual(0, select_nif(RFd, ?ERL_NIF_SELECT_READ, RFd, null, Ref, null)),
?assertEqual([], flush(0)),
%% Spawn a process and do some stealing
@@ -644,15 +679,15 @@ select_steal(Config) when is_list(Config) ->
?assertMatch([{Pid, done}], flush(1)), % synchronize with the child
%% Try to select from the parent pid (steal back)
- ?assertEqual(0, select_nif(RFd, ?ERL_NIF_SELECT_READ, RFd, Pid, Ref)),
+ ?assertEqual(0, select_nif(RFd, ?ERL_NIF_SELECT_READ, RFd, Pid, Ref, null)),
%% Ensure that no data is hanging and close.
%% Rfd is stolen at this point.
- check_stop_ret(select_nif(WFd, ?ERL_NIF_SELECT_STOP, WFd, null, Ref)),
+ check_stop_ret(select_nif(WFd, ?ERL_NIF_SELECT_STOP, WFd, null, Ref, null)),
?assertMatch([{fd_resource_stop, WPtr, _}], flush()),
{1, {WPtr, 1}} = last_fd_stop_call(),
- check_stop_ret(select_nif(RFd, ?ERL_NIF_SELECT_STOP, RFd, null, Ref)),
+ check_stop_ret(select_nif(RFd, ?ERL_NIF_SELECT_STOP, RFd, null, Ref, null)),
?assertMatch([{fd_resource_stop, RPtr, _}], flush()),
{1, {RPtr, _DirectCall}} = last_fd_stop_call(),
@@ -789,8 +824,11 @@ demonitor_process(Config) ->
end),
R_ptr = alloc_monitor_resource_nif(),
{0,MonBin1} = monitor_process_nif(R_ptr, Pid, true, self()),
+ MonTerm1 = make_monitor_term_nif(MonBin1),
[R_ptr] = monitored_by(Pid),
{0,MonBin2} = monitor_process_nif(R_ptr, Pid, true, self()),
+ MonTerm2 = make_monitor_term_nif(MonBin2),
+ true = (MonTerm1 =/= MonTerm2),
[R_ptr, R_ptr] = monitored_by(Pid),
0 = demonitor_process_nif(R_ptr, MonBin1),
[R_ptr] = monitored_by(Pid),
@@ -804,6 +842,10 @@ demonitor_process(Config) ->
{R_ptr, _, 1} = last_resource_dtor_call(),
[] = monitored_by(Pid),
Pid ! return,
+
+ erlang:garbage_collect(),
+ true = (MonTerm1 =/= MonTerm2),
+ io:format("MonTerm1 = ~p\nMonTerm2 = ~p\n", [MonTerm1, MonTerm2]),
ok.
@@ -1175,6 +1217,15 @@ maps(Config) when is_list(Config) ->
M2 = maps_from_list_nif(maps:to_list(M2)),
M3 = maps_from_list_nif(maps:to_list(M3)),
+ %% Test different map sizes (OTP-15567)
+ repeat_while(fun({35,_}) -> false;
+ ({K,Map}) ->
+ Map = maps_from_list_nif(maps:to_list(Map)),
+ Map = maps:filter(fun(K,V) -> V =:= K*100 end, Map),
+ {K+1, maps:put(K,K*100,Map)}
+ end,
+ {1,#{}}),
+
has_duplicate_keys = maps_from_list_nif([{1,1},{1,1}]),
verify_tmpmem(TmpMem),
@@ -2471,6 +2522,13 @@ repeat(0, _, Arg) ->
repeat(N, Fun, Arg0) ->
repeat(N-1, Fun, Fun(Arg0)).
+repeat_while(Fun, Acc0) ->
+ case Fun(Acc0) of
+ false -> ok;
+ Acc1 ->
+ repeat_while(Fun, Acc1)
+ end.
+
check(Exp,Got,Line) ->
case Got of
Exp -> Exp;
@@ -3311,6 +3369,30 @@ make_unaligned_binary(Bin0) ->
<<0:3,Bin:Size/binary,31:5>> = id(<<0:3,Bin0/binary,31:5>>),
Bin.
+pid(Config) ->
+ ensure_lib_loaded(Config),
+ Self = self(),
+ {true, ErlNifPid} = get_local_pid_nif(Self),
+ false = is_pid_undefined_nif(ErlNifPid),
+ Self = make_pid_nif(ErlNifPid),
+
+ UndefPid = set_pid_undefined_nif(),
+ true = is_pid_undefined_nif(UndefPid),
+ undefined = make_pid_nif(UndefPid),
+ 0 = send_term(UndefPid, message),
+
+ Other = spawn(fun() -> ok end),
+ {true,OtherNifPid} = get_local_pid_nif(Other),
+ Cmp = compare_pids_nif(ErlNifPid, OtherNifPid),
+ true = if Cmp < 0 -> Self < Other;
+ Cmp > 0 -> Self > Other
+ end,
+ 0 = compare_pids_nif(ErlNifPid, ErlNifPid),
+
+ {false, _} = get_local_pid_nif(undefined),
+ ok.
+
+
id(I) -> I.
%% The NIFs:
@@ -3376,7 +3458,7 @@ term_to_binary_nif(_, _) -> ?nif_stub.
binary_to_term_nif(_, _, _) -> ?nif_stub.
port_command_nif(_, _) -> ?nif_stub.
format_term_nif(_,_) -> ?nif_stub.
-select_nif(_,_,_,_,_) -> ?nif_stub.
+select_nif(_,_,_,_,_,_) -> ?nif_stub.
dupe_resource_nif(_) -> ?nif_stub.
pipe_nif() -> ?nif_stub.
write_nif(_,_) -> ?nif_stub.
@@ -3388,6 +3470,7 @@ alloc_monitor_resource_nif() -> ?nif_stub.
monitor_process_nif(_,_,_,_) -> ?nif_stub.
demonitor_process_nif(_,_) -> ?nif_stub.
compare_monitors_nif(_,_) -> ?nif_stub.
+make_monitor_term_nif(_) -> ?nif_stub.
monitor_frenzy_nif(_,_,_,_) -> ?nif_stub.
ioq_nif(_) -> ?nif_stub.
ioq_nif(_,_) -> ?nif_stub.
@@ -3418,5 +3501,11 @@ convert_time_unit(_,_,_) -> ?nif_stub.
now_time() -> ?nif_stub.
cpu_time() -> ?nif_stub.
+get_local_pid_nif(_) -> ?nif_stub.
+make_pid_nif(_) -> ?nif_stub.
+set_pid_undefined_nif() -> ?nif_stub.
+is_pid_undefined_nif(_) -> ?nif_stub.
+compare_pids_nif(_, _) -> ?nif_stub.
+
nif_stub_error(Line) ->
exit({nif_not_loaded,module,?MODULE,line,Line}).
diff --git a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c
index f2ce6dbe67..0d5d900d31 100644
--- a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c
+++ b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c
@@ -1882,12 +1882,23 @@ static ERL_NIF_TERM copy_blob(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[
return enif_make_copy(env, mti.p->blob);
}
+static int get_pidbin(ErlNifEnv* env, ERL_NIF_TERM pidbin, ErlNifPid* pid)
+{
+ ErlNifBinary bin;
+
+ if (!enif_inspect_binary(env, pidbin, &bin) || bin.size != sizeof(ErlNifPid))
+ return 0;
+
+ memcpy(pid, bin.data, bin.size);
+ return 1;
+}
+
static ERL_NIF_TERM send_term(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ErlNifEnv* menv;
ErlNifPid pid;
int ret;
- if (!enif_get_local_pid(env, argv[0], &pid)) {
+ if (!enif_get_local_pid(env, argv[0], &pid) && !get_pidbin(env, argv[0], &pid)) {
return enif_make_badarg(env);
}
menv = enif_alloc_env();
@@ -2486,7 +2497,8 @@ static ERL_NIF_TERM select_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv
enum ErlNifSelectFlags mode;
void* obj;
ErlNifPid nifpid, *pid = NULL;
- ERL_NIF_TERM ref;
+ ERL_NIF_TERM ref_or_msg;
+ ErlNifEnv* msg_env = NULL;
int retval;
if (!get_fd(env, argv[0], &fdr)
@@ -2501,11 +2513,27 @@ static ERL_NIF_TERM select_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv
return enif_make_badarg(env);
pid = &nifpid;
}
- ref = argv[4];
+ ref_or_msg = argv[4];
+ if (argv[5] != atom_null) {
+ msg_env = enif_alloc_env();
+ ref_or_msg = enif_make_copy(msg_env, ref_or_msg);
+ }
fdr->was_selected = 1;
enif_self(env, &fdr->pid);
- retval = enif_select(env, fdr->fd, mode, obj, pid, ref);
+ switch (mode) {
+ case ERL_NIF_SELECT_CUSTOM_MSG | ERL_NIF_SELECT_READ:
+ retval = enif_select_read(env, fdr->fd, obj, pid, ref_or_msg, msg_env);
+ break;
+ case ERL_NIF_SELECT_CUSTOM_MSG | ERL_NIF_SELECT_WRITE:
+ retval = enif_select_write(env, fdr->fd, obj, pid, ref_or_msg, msg_env);
+ break;
+ default:
+ retval = enif_select(env, fdr->fd, mode, obj, pid, ref_or_msg);
+ }
+
+ if (msg_env)
+ enif_free_env(msg_env);
return enif_make_int(env, retval);
}
@@ -2830,6 +2858,16 @@ static ERL_NIF_TERM compare_monitors_nif(ErlNifEnv* env, int argc, const ERL_NIF
return enif_make_int(env, enif_compare_monitors(&m1, &m2));
}
+static ERL_NIF_TERM make_monitor_term_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ ErlNifMonitor m;
+ if (!get_monitor(env, argv[0], &m)) {
+ return enif_make_badarg(env);
+ }
+
+ return enif_make_monitor_term(env, &m);
+}
+
/*********** monitor_frenzy ************/
@@ -3486,6 +3524,65 @@ static ERL_NIF_TERM ioq(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
return enif_make_badarg(env);
}
+static ERL_NIF_TERM make_bool(ErlNifEnv* env, int bool)
+{
+ return bool ? atom_true : atom_false;
+}
+
+static ERL_NIF_TERM get_local_pid_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ ErlNifPid pid;
+ ERL_NIF_TERM pid_bin;
+ int ret = enif_get_local_pid(env, argv[0], &pid);
+
+ memcpy(enif_make_new_binary(env, sizeof(ErlNifPid), &pid_bin),
+ &pid, sizeof(ErlNifPid));
+
+ return enif_make_tuple2(env, make_bool(env, ret), pid_bin);
+}
+
+static ERL_NIF_TERM make_pid_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ ErlNifPid pid;
+
+ if (!get_pidbin(env, argv[0], &pid))
+ return enif_make_badarg(env);
+
+ return enif_make_pid(env, &pid);
+}
+
+static ERL_NIF_TERM set_pid_undefined_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ ErlNifPid pid;
+ ERL_NIF_TERM pid_bin;
+
+ enif_set_pid_undefined(&pid);
+ memcpy(enif_make_new_binary(env, sizeof(ErlNifPid), &pid_bin),
+ &pid, sizeof(ErlNifPid));
+
+ return pid_bin;
+}
+
+static ERL_NIF_TERM is_pid_undefined_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ ErlNifPid pid;
+
+ if (!get_pidbin(env, argv[0], &pid))
+ return enif_make_badarg(env);
+
+ return make_bool(env, enif_is_pid_undefined(&pid));
+}
+
+static ERL_NIF_TERM compare_pids_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ ErlNifPid a, b;
+
+ if (!get_pidbin(env, argv[0], &a) || !get_pidbin(env, argv[1], &b))
+ return enif_make_badarg(env);
+
+ return enif_make_int(env, enif_compare_pids(&a, &b));
+}
+
static ErlNifFunc nif_funcs[] =
{
{"lib_version", 0, lib_version},
@@ -3565,7 +3662,7 @@ static ErlNifFunc nif_funcs[] =
{"binary_to_term_nif", 3, binary_to_term},
{"port_command_nif", 2, port_command},
{"format_term_nif", 2, format_term},
- {"select_nif", 5, select_nif},
+ {"select_nif", 6, select_nif},
#ifndef __WIN32__
{"pipe_nif", 0, pipe_nif},
{"write_nif", 2, write_nif},
@@ -3579,6 +3676,7 @@ static ErlNifFunc nif_funcs[] =
{"monitor_process_nif", 4, monitor_process_nif},
{"demonitor_process_nif", 2, demonitor_process_nif},
{"compare_monitors_nif", 2, compare_monitors_nif},
+ {"make_monitor_term_nif", 1, make_monitor_term_nif},
{"monitor_frenzy_nif", 4, monitor_frenzy_nif},
{"whereis_send", 3, whereis_send},
{"whereis_term", 2, whereis_term},
@@ -3587,7 +3685,12 @@ static ErlNifFunc nif_funcs[] =
{"ioq_nif", 1, ioq},
{"ioq_nif", 2, ioq},
{"ioq_nif", 3, ioq},
- {"ioq_nif", 4, ioq}
+ {"ioq_nif", 4, ioq},
+ {"get_local_pid_nif", 1, get_local_pid_nif},
+ {"make_pid_nif", 1, make_pid_nif},
+ {"set_pid_undefined_nif", 0, set_pid_undefined_nif},
+ {"is_pid_undefined_nif", 1, is_pid_undefined_nif},
+ {"compare_pids_nif", 2, compare_pids_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 300b4ed036..ef4635a6f5 100644
--- a/erts/emulator/test/node_container_SUITE.erl
+++ b/erts/emulator/test/node_container_SUITE.erl
@@ -71,25 +71,10 @@ init_per_suite(Config) ->
end_per_suite(_Config) ->
erts_debug:set_internal_state(available_internal_state, true),
erts_debug:set_internal_state(node_tab_delayed_delete, -1), %% restore original value
- available_internal_state(false).
-
-available_internal_state(Bool) when Bool == true; Bool == false ->
- case {Bool,
- (catch erts_debug:get_internal_state(available_internal_state))} of
- {true, true} ->
- true;
- {false, true} ->
- erts_debug:set_internal_state(available_internal_state, false),
- true;
- {true, _} ->
- erts_debug:set_internal_state(available_internal_state, true),
- false;
- {false, _} ->
- false
- end.
+ erts_test_utils:available_internal_state(false).
init_per_testcase(_Case, Config) when is_list(Config) ->
- available_internal_state(true),
+ erts_test_utils:available_internal_state(true),
Config.
end_per_testcase(_Case, Config) when is_list(Config) ->
@@ -928,9 +913,9 @@ id(X) ->
-define(ND_REFS, erts_debug:get_internal_state(node_and_dist_references)).
node_container_refc_check(Node) when is_atom(Node) ->
- AIS = available_internal_state(true),
+ AIS = erts_test_utils:available_internal_state(true),
nc_refc_check(Node),
- available_internal_state(AIS).
+ erts_test_utils:available_internal_state(AIS).
nc_refc_check(Node) when is_atom(Node) ->
Ref = make_ref(),
@@ -938,15 +923,11 @@ nc_refc_check(Node) when is_atom(Node) ->
io:format("Starting reference count check of node ~w~n", [Node]),
spawn_link(Node,
fun () ->
- {{node_references, NodeRefs},
- {dist_references, DistRefs}} = ?ND_REFS,
- check_nd_refc({node(), erlang:system_info(creation)},
- NodeRefs,
- DistRefs,
- fun (ErrMsg) ->
- Self ! {Ref, ErrMsg, failed},
- exit(normal)
- end),
+ erts_test_utils:check_node_dist(
+ fun (ErrMsg) ->
+ Self ! {Ref, ErrMsg, failed},
+ exit(normal)
+ end),
Self ! {Ref, succeded}
end),
receive
@@ -958,98 +939,26 @@ nc_refc_check(Node) when is_atom(Node) ->
ok
end.
-check_nd_refc({ThisNodeName, ThisCreation}, NodeRefs, DistRefs, Fail) ->
- case catch begin
- check_refc(ThisNodeName,ThisCreation,"node table",NodeRefs),
- check_refc(ThisNodeName,ThisCreation,"dist table",DistRefs),
- ok
- end of
- ok ->
- ok;
- {'EXIT', Reason} ->
- {Y,Mo,D} = date(),
- {H,Mi,S} = time(),
- ErrMsg = io_lib:format("~n"
- "*** Reference count check of node ~w "
- "failed (~p) at ~w~w~w ~w:~w:~w~n"
- "*** Node table references:~n ~p~n"
- "*** Dist table references:~n ~p~n",
- [node(), Reason, Y, Mo, D, H, Mi, S,
- NodeRefs, DistRefs]),
- Fail(lists:flatten(ErrMsg))
- end.
-
-
-check_refc(ThisNodeName,ThisCreation,Table,EntryList) when is_list(EntryList) ->
- lists:foreach(
- fun ({Entry, Refc, ReferrerList}) ->
- {DelayedDeleteTimer,
- FoundRefs} =
- lists:foldl(
- fun ({Referrer, ReferencesList}, {DDT, A1}) ->
- {case Referrer of
- {system,delayed_delete_timer} ->
- true;
- {system,thread_progress_delete_timer} ->
- true;
- _ ->
- DDT
- end,
- A1 + lists:foldl(fun ({_T,Rs},A2) ->
- A2+Rs
- end,
- 0,
- ReferencesList)}
- end,
- {false, 0},
- ReferrerList),
-
- %% Reference count equals found references?
- case {Refc, FoundRefs, DelayedDeleteTimer} of
- {X, X, _} ->
- ok;
- {0, 1, true} ->
- ok;
- _ ->
- exit({invalid_reference_count, Table, Entry})
- end,
-
- %% All entries in table referred to?
- case {Entry, Refc} of
- {ThisNodeName, 0} -> ok;
- {{ThisNodeName, ThisCreation}, 0} -> ok;
- {_, 0} when DelayedDeleteTimer == false ->
- exit({not_referred_entry_in_table, Table, Entry});
- {_, _} -> ok
- end
-
- end,
- EntryList),
- ok.
-
get_node_references({NodeName, Creation} = Node) when is_atom(NodeName),
is_integer(Creation) ->
{{node_references, NodeRefs},
{dist_references, DistRefs}} = ?ND_REFS,
- check_nd_refc({node(), erlang:system_info(creation)},
- NodeRefs,
- DistRefs,
- fun (ErrMsg) ->
- io:format("~s", [ErrMsg]),
- ct:fail(reference_count_check_failed)
- end),
+ erts_test_utils:check_node_dist(
+ fun (ErrMsg) ->
+ io:format("~s", [ErrMsg]),
+ ct:fail(reference_count_check_failed)
+ end,
+ NodeRefs, DistRefs),
find_references(Node, NodeRefs).
get_dist_references(NodeName) when is_atom(NodeName) ->
{{node_references, NodeRefs},
{dist_references, DistRefs}} = ?ND_REFS,
- check_nd_refc({node(), erlang:system_info(creation)},
- NodeRefs,
- DistRefs,
- fun (ErrMsg) ->
- io:format("~s", [ErrMsg]),
- ct:fail(reference_count_check_failed)
- end),
+ erts_test_utils:check_node_dist(fun (ErrMsg) ->
+ io:format("~s", [ErrMsg]),
+ ct:fail(reference_count_check_failed)
+ end,
+ NodeRefs, DistRefs),
find_references(NodeName, DistRefs).
find_references(N, NRefList) ->
@@ -1138,133 +1047,15 @@ get_nodename() ->
++ "@"
++ hostname()).
-
-
--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]}).
-
-
-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).
-
-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,
- pid_tag(Creation),
- ?ATOM_EXT,
- uint16_be(length(NodeName)),
- NodeName,
- uint32_be(Number),
- uint32_be(Serial),
- enc_creation(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.
+ erts_test_utils:mk_ext_pid({NodeName, Creation}, Number, Serial).
-port_tag(bad_creation) -> ?PORT_EXT;
-port_tag(Creation) when Creation =< 3 -> ?PORT_EXT;
-port_tag(_Creation) -> ?NEW_PORT_EXT.
-
-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,
- port_tag(Creation),
- ?ATOM_EXT,
- uint16_be(length(NodeName)),
- NodeName,
- uint32_be(Number),
- enc_creation(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.
+ erts_test_utils:mk_ext_port({NodeName, Creation}, Number).
+
+mk_ref({NodeName, Creation}, Numbers) ->
+ erts_test_utils:mk_ext_ref({NodeName, Creation}, Numbers).
-ref_tag(bad_creation) -> ?NEW_REFERENCE_EXT;
-ref_tag(Creation) when Creation =< 3 -> ?NEW_REFERENCE_EXT;
-ref_tag(_Creation) -> ?NEWER_REFERENCE_EXT.
-
-mk_ref({NodeName, Creation}, Numbers) when is_atom(NodeName),
- is_list(Numbers) ->
- mk_ref({atom_to_list(NodeName), Creation}, Numbers);
-mk_ref({NodeName, Creation}, [Number]) when is_list(NodeName),
- Creation =< 3,
- is_integer(Number) ->
- case catch binary_to_term(list_to_binary([?VERSION_MAGIC,
- ?REFERENCE_EXT,
- ?ATOM_EXT,
- uint16_be(length(NodeName)),
- NodeName,
- uint32_be(Number),
- uint8(Creation)])) of
- Ref when is_reference(Ref) ->
- Ref;
- {'EXIT', {badarg, _}} ->
- exit({badarg, mk_ref, [{NodeName, Creation}, [Number]]});
- Other ->
- exit({unexpected_binary_to_term_result, Other})
- end;
-mk_ref({NodeName, Creation}, Numbers) when is_list(NodeName),
- is_list(Numbers) ->
- case catch binary_to_term(list_to_binary([?VERSION_MAGIC,
- ref_tag(Creation),
- uint16_be(length(Numbers)),
- ?ATOM_EXT,
- uint16_be(length(NodeName)),
- NodeName,
- enc_creation(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.
exec_loop() ->
receive
diff --git a/erts/emulator/test/num_bif_SUITE.erl b/erts/emulator/test/num_bif_SUITE.erl
index f15217814a..6b834705cf 100644
--- a/erts/emulator/test/num_bif_SUITE.erl
+++ b/erts/emulator/test/num_bif_SUITE.erl
@@ -504,6 +504,10 @@ t_integer_to_string(Config) when is_list(Config) ->
test_its("A", 10, 16),
test_its("D4BE", 54462, 16),
test_its("-D4BE", -54462, 16),
+ test_its("FFFFFFFFFF", 1099511627775, 16),
+ test_its("123456789ABCDEF123456789ABCDEF123456789ABCDEF",
+ 108977460683796539709587792812439445667270661579197935,
+ 16),
lists:foreach(fun(Value) ->
{'EXIT', {badarg, _}} =
@@ -515,12 +519,14 @@ t_integer_to_string(Config) when is_list(Config) ->
ok.
test_its(List,Int) ->
- Int = list_to_integer(List),
- Int = binary_to_integer(list_to_binary(List)).
+ List = integer_to_list(Int),
+ Binary = list_to_binary(List),
+ Binary = integer_to_binary(Int).
test_its(List,Int,Base) ->
- Int = list_to_integer(List, Base),
- Int = binary_to_integer(list_to_binary(List), Base).
+ List = integer_to_list(Int, Base),
+ Binary = list_to_binary(List),
+ Binary = integer_to_binary(Int, Base).
%% Tests binary_to_integer/1.
diff --git a/erts/emulator/test/persistent_term_SUITE.erl b/erts/emulator/test/persistent_term_SUITE.erl
index 58cd3276b0..93eb026ced 100644
--- a/erts/emulator/test/persistent_term_SUITE.erl
+++ b/erts/emulator/test/persistent_term_SUITE.erl
@@ -6,7 +6,7 @@
%% 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
-%5
+%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
@@ -21,7 +21,7 @@
-module(persistent_term_SUITE).
-include_lib("common_test/include/ct.hrl").
--export([all/0,suite/0,
+-export([all/0,suite/0,init_per_suite/1,end_per_suite/1,
basic/1,purging/1,sharing/1,get_trapping/1,
info/1,info_trapping/1,killed_while_trapping/1,
off_heap_values/1,keys/1,collisions/1,
@@ -39,39 +39,50 @@ all() ->
killed_while_trapping,off_heap_values,keys,collisions,
init_restart].
+init_per_suite(Config) ->
+ %% Put a term in the dict so that we know that the testcases handle
+ %% stray terms left by stdlib or other test suites.
+ persistent_term:put(init_per_suite, {?MODULE}),
+ Config.
+
+end_per_suite(Config) ->
+ persistent_term:erase(init_per_suite),
+ Config.
+
basic(_Config) ->
Chk = chk(),
N = 777,
Seq = lists:seq(1, N),
- par(2, N, Seq),
- seq(3, Seq),
- seq(3, Seq), %Same values.
+ par(2, N, Seq, Chk),
+ seq(3, Seq, Chk),
+ seq(3, Seq, Chk), %Same values.
_ = [begin
Key = {?MODULE,{key,I}},
true = persistent_term:erase(Key),
false = persistent_term:erase(Key),
- {'EXIT',{badarg,_}} = (catch persistent_term:get(Key))
+ {'EXIT',{badarg,_}} = (catch persistent_term:get(Key)),
+ {not_present,Key} = persistent_term:get(Key, {not_present,Key})
end || I <- Seq],
- [] = [P || {{?MODULE,_},_}=P <- persistent_term:get()],
+ [] = [P || {{?MODULE,_},_}=P <- pget(Chk)],
chk(Chk).
-par(C, N, Seq) ->
+par(C, N, Seq, Chk) ->
_ = [spawn_link(fun() ->
ok = persistent_term:put({?MODULE,{key,I}},
{value,C*I})
end) || I <- Seq],
- Result = wait(N),
+ Result = wait(N, Chk),
_ = [begin
Double = C*I,
{{?MODULE,{key,I}},{value,Double}} = Res
end || {I,Res} <- lists:zip(Seq, Result)],
ok.
-seq(C, Seq) ->
+seq(C, Seq, Chk) ->
_ = [ok = persistent_term:put({?MODULE,{key,I}}, {value,C*I}) ||
I <- Seq],
- All = persistent_term:get(),
- All = [P || {{?MODULE,_},_}=P <- persistent_term:get()],
+ All = pget(Chk),
+ All = [P || {{?MODULE,_},_}=P <- All],
All = [{Key,persistent_term:get(Key)} || {Key,_} <- All],
Result = lists:sort(All),
_ = [begin
@@ -80,15 +91,15 @@ seq(C, Seq) ->
end || {I,Res} <- lists:zip(Seq, Result)],
ok.
-wait(N) ->
- All = [P || {{?MODULE,_},_}=P <- persistent_term:get()],
+wait(N, Chk) ->
+ All = [P || {{?MODULE,_},_}=P <- pget(Chk)],
case length(All) of
N ->
All = [{Key,persistent_term:get(Key)} || {Key,_} <- All],
lists:sort(All);
_ ->
receive after 10 -> ok end,
- wait(N)
+ wait(N, Chk)
end.
%% Make sure that terms that have been erased are copied into all
@@ -200,23 +211,23 @@ get_trapping(_Config) ->
_ -> 1000
end,
spawn_link(fun() -> get_trapping_create(N) end),
- All = do_get_trapping(N, []),
+ All = do_get_trapping(N, [], Chk),
N = get_trapping_check_result(lists:sort(All), 1),
erlang:garbage_collect(),
get_trapping_erase(N),
chk(Chk).
-do_get_trapping(N, Prev) ->
- case persistent_term:get() of
+do_get_trapping(N, Prev, Chk) ->
+ case pget(Chk) of
Prev when length(Prev) >= N ->
All = [P || {{?MODULE,{get_trapping,_}},_}=P <- Prev],
case length(All) of
N -> All;
- _ -> do_get_trapping(N, Prev)
+ _ -> do_get_trapping(N, Prev, Chk)
end;
New ->
receive after 1 -> ok end,
- do_get_trapping(N, New)
+ do_get_trapping(N, New, Chk)
end.
get_trapping_create(0) ->
@@ -331,25 +342,25 @@ info_trapping(_Config) ->
_ -> 1000
end,
spawn_link(fun() -> info_trapping_create(N) end),
- All = do_info_trapping(N, 0),
+ All = do_info_trapping(N, 0, Chk),
N = info_trapping_check_result(lists:sort(All), 1),
erlang:garbage_collect(),
info_trapping_erase(N),
chk(Chk).
-do_info_trapping(N, PrevMem) ->
+do_info_trapping(N, PrevMem, Chk) ->
case info_info() of
- {N,Mem} ->
+ {M,Mem} when M >= N ->
true = Mem >= PrevMem,
- All = [P || {{?MODULE,{info_trapping,_}},_}=P <- persistent_term:get()],
+ All = [P || {{?MODULE,{info_trapping,_}},_}=P <- pget(Chk)],
case length(All) of
N -> All;
- _ -> do_info_trapping(N, PrevMem)
+ _ -> do_info_trapping(N, PrevMem, Chk)
end;
{_,Mem} ->
true = Mem >= PrevMem,
receive after 1 -> ok end,
- do_info_trapping(N, Mem)
+ do_info_trapping(N, Mem, Chk)
end.
info_trapping_create(0) ->
@@ -462,17 +473,17 @@ collisions(_Config) ->
_ = [V = persistent_term:get(K) || {K,V} <- Kvs],
%% Now delete the persistent terms in random order.
- collisions_delete(lists:keysort(2, Kvs)),
+ collisions_delete(lists:keysort(2, Kvs), Chk),
chk(Chk).
-collisions_delete([{Key,Val}|Kvs]) ->
+collisions_delete([{Key,Val}|Kvs], Chk) ->
Val = persistent_term:get(Key),
true = persistent_term:erase(Key),
- true = lists:sort(persistent_term:get()) =:= lists:sort(Kvs),
+ true = lists:sort(pget(Chk)) =:= lists:sort(Kvs),
_ = [V = persistent_term:get(K) || {K,V} <- Kvs],
- collisions_delete(Kvs);
-collisions_delete([]) ->
+ collisions_delete(Kvs, Chk);
+collisions_delete([], _) ->
ok.
colliding_keys() ->
@@ -589,15 +600,16 @@ do_test_init_restart_cmd(File) ->
%% and after each test case.
chk() ->
- persistent_term:info().
+ {persistent_term:info(), persistent_term:get()}.
-chk(Chk) ->
- Chk = persistent_term:info(),
+chk({Info, _Initial} = Chk) ->
+ Info = persistent_term:info(),
Key = {?MODULE,?FUNCTION_NAME},
- ok = persistent_term:put(Key, {term,Chk}),
+ ok = persistent_term:put(Key, {term,Info}),
Term = persistent_term:get(Key),
true = persistent_term:erase(Key),
chk_not_stuck(Term),
+ [persistent_term:erase(K) || {K, _} <- pget(Chk)],
ok.
chk_not_stuck(Term) ->
@@ -612,3 +624,6 @@ chk_not_stuck(Term) ->
_ ->
ok
end.
+
+pget({_, Initial}) ->
+ persistent_term:get() -- Initial.
diff --git a/erts/emulator/test/process_SUITE.erl b/erts/emulator/test/process_SUITE.erl
index 57eb082d64..edf08ce0bd 100644
--- a/erts/emulator/test/process_SUITE.erl
+++ b/erts/emulator/test/process_SUITE.erl
@@ -2104,6 +2104,13 @@ spawn_opt_max_heap_size(_Config) ->
error_logger:add_report_handler(?MODULE, self()),
+ %% flush any prior messages in error_logger
+ Pid = spawn(fun() -> ok = nok end),
+ receive
+ {error, _, {emulator, _, [Pid|_]}} ->
+ flush()
+ end,
+
%% Test that numerical limit works
max_heap_size_test(1024, 1024, true, true),
@@ -2208,6 +2215,13 @@ receive_unexpected() ->
ok
end.
+flush() ->
+ receive
+ _M -> flush()
+ after 0 ->
+ ok
+ end.
+
%% error_logger report handler proxy
init(Pid) ->
{ok, Pid}.
@@ -2233,8 +2247,8 @@ processes_term_proc_list(Config) when is_list(Config) ->
%% We have to run this test case with +S1 since instrument:allocations()
%% will report a free()'d block as present until it's actually deallocated
%% by its employer.
- Run("+MSe true +MSatags false +S1"),
- Run("+MSe true +MSatags true +S1"),
+ Run("+MSe true +Muatags false +S1"),
+ Run("+MSe true +Muatags true +S1"),
ok.
@@ -2242,10 +2256,12 @@ processes_term_proc_list(Config) when is_list(Config) ->
chk_term_proc_list(?LINE, MC, XB)).
chk_term_proc_list(Line, MustChk, ExpectBlks) ->
- Allocs = instrument:allocations(#{ allocator_types => [sl_alloc] }),
+ Allocs = instrument:allocations(),
case {MustChk, Allocs} of
{false, {error, not_enabled}} ->
not_enabled;
+ {false, {ok, {_Shift, _Unscanned, ByOrigin}}} when ByOrigin =:= #{} ->
+ not_enabled;
{_, {ok, {_Shift, _Unscanned, ByOrigin}}} ->
ByType = maps:get(system, ByOrigin, #{}),
Hist = maps:get(ptab_list_deleted_el, ByType, {}),
diff --git a/erts/emulator/test/smoke_test_SUITE.erl b/erts/emulator/test/smoke_test_SUITE.erl
index 26c610e3a8..5b46342127 100644
--- a/erts/emulator/test/smoke_test_SUITE.erl
+++ b/erts/emulator/test/smoke_test_SUITE.erl
@@ -56,7 +56,7 @@ end_per_testcase(_Case, Config) when is_list(Config) ->
%%%
boot_combo(Config) when is_list(Config) ->
- ZFlags = os:getenv("ERL_ZFLAGS"),
+ ZFlags = os:getenv("ERL_ZFLAGS", ""),
NOOP = fun () -> ok end,
A42 = fun () ->
case erlang:system_info(threads) of
@@ -87,10 +87,7 @@ boot_combo(Config) when is_list(Config) ->
%% A lot more combos could be implemented...
ok
after
- os:putenv("ERL_ZFLAGS", case ZFlags of
- false -> "";
- _ -> ZFlags
- end)
+ os:putenv("ERL_ZFLAGS", ZFlags)
end.
native_atomics(Config) when is_list(Config) ->
diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl
new file mode 100644
index 0000000000..52d002c9b8
--- /dev/null
+++ b/erts/emulator/test/socket_SUITE.erl
@@ -0,0 +1,17990 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2019. All 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%
+%%
+
+%% There are some environment variables that can be used to "manipulate"
+%% the test suite:
+%%
+%% Variable that controls which 'groups' are to run (with default values)
+%%
+%% ESOCK_TEST_API: include
+%% ESOCK_TEST_SOCK_CLOSE: include
+%% ESOCK_TEST_TRAFFIC: include
+%% ESOCK_TEST_TTEST: exclude
+%%
+%% Defines the runtime of the ttest cases
+%% (This is the time during which "measurement" is performed.
+%% the actual time it takes for the test case to complete
+%% will be longer)
+%%
+%% ESOCK_TEST_TTEST_RUNTIME: 10 seconds
+%% Format of values: <integer>[<unit>]
+%% Where unit is: ms | s | m
+%% ms - milli seconds
+%% s - seconds (default)
+%% m - minutes
+%%
+
+%% Run the entire test suite:
+%% ts:run(emulator, socket_SUITE, [batch]).
+%%
+%% Run a specific group:
+%% ts:run(emulator, socket_SUITE, {group, foo}, [batch]).
+%%
+%% Run a specific test case:
+%% ts:run(emulator, socket_SUITE, foo, [batch]).
+
+-module(socket_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("common_test/include/ct_event.hrl").
+
+%% Suite exports
+-export([suite/0, all/0, groups/0]).
+-export([init_per_suite/1, end_per_suite/1,
+ init_per_group/2, end_per_group/2,
+ init_per_testcase/2, end_per_testcase/2]).
+
+%% Test cases
+-export([
+ %% *** API Basic ***
+ api_b_open_and_close_udp4/1,
+ api_b_open_and_close_tcp4/1,
+ api_b_sendto_and_recvfrom_udp4/1,
+ api_b_sendmsg_and_recvmsg_udp4/1,
+ api_b_send_and_recv_tcp4/1,
+ api_b_sendmsg_and_recvmsg_tcp4/1,
+
+ %% *** API Options ***
+ api_opt_simple_otp_options/1,
+ api_opt_simple_otp_rcvbuf_option/1,
+ api_opt_simple_otp_controlling_process/1,
+
+ %% *** API Operation Timeout ***
+ api_to_connect_tcp4/1,
+ api_to_connect_tcp6/1,
+ api_to_accept_tcp4/1,
+ api_to_accept_tcp6/1,
+ api_to_maccept_tcp4/1,
+ api_to_maccept_tcp6/1,
+ api_to_send_tcp4/1,
+ api_to_send_tcp6/1,
+ api_to_sendto_udp4/1,
+ api_to_sendto_udp6/1,
+ api_to_sendmsg_tcp4/1,
+ api_to_sendmsg_tcp6/1,
+ api_to_recv_udp4/1,
+ api_to_recv_udp6/1,
+ api_to_recv_tcp4/1,
+ api_to_recv_tcp6/1,
+ api_to_recvfrom_udp4/1,
+ api_to_recvfrom_udp6/1,
+ api_to_recvmsg_udp4/1,
+ api_to_recvmsg_udp6/1,
+ api_to_recvmsg_tcp4/1,
+ api_to_recvmsg_tcp6/1,
+
+ %% *** Socket Closure ***
+ sc_cpe_socket_cleanup_tcp4/1,
+ sc_cpe_socket_cleanup_tcp6/1,
+ sc_cpe_socket_cleanup_udp4/1,
+ sc_cpe_socket_cleanup_udp6/1,
+
+ sc_lc_recv_response_tcp4/1,
+ sc_lc_recv_response_tcp6/1,
+ sc_lc_recvfrom_response_udp4/1,
+ sc_lc_recvfrom_response_udp6/1,
+ sc_lc_recvmsg_response_tcp4/1,
+ sc_lc_recvmsg_response_tcp6/1,
+ sc_lc_recvmsg_response_udp4/1,
+ sc_lc_recvmsg_response_udp6/1,
+ sc_lc_acceptor_response_tcp4/1,
+ sc_lc_acceptor_response_tcp6/1,
+
+ sc_rc_recv_response_tcp4/1,
+ sc_rc_recv_response_tcp6/1,
+ sc_rc_recvmsg_response_tcp4/1,
+ sc_rc_recvmsg_response_tcp6/1,
+
+ sc_rs_recv_send_shutdown_receive_tcp4/1,
+ sc_rs_recv_send_shutdown_receive_tcp6/1,
+ sc_rs_recvmsg_send_shutdown_receive_tcp4/1,
+ sc_rs_recvmsg_send_shutdown_receive_tcp6/1,
+
+ %% *** Traffic ***
+ traffic_send_and_recv_chunks_tcp4/1,
+ traffic_send_and_recv_chunks_tcp6/1,
+
+ traffic_ping_pong_small_send_and_recv_tcp4/1,
+ traffic_ping_pong_small_send_and_recv_tcp6/1,
+ traffic_ping_pong_medium_send_and_recv_tcp4/1,
+ traffic_ping_pong_medium_send_and_recv_tcp6/1,
+ traffic_ping_pong_large_send_and_recv_tcp4/1,
+ traffic_ping_pong_large_send_and_recv_tcp6/1,
+
+ traffic_ping_pong_small_sendto_and_recvfrom_udp4/1,
+ traffic_ping_pong_small_sendto_and_recvfrom_udp6/1,
+ traffic_ping_pong_medium_sendto_and_recvfrom_udp4/1,
+ traffic_ping_pong_medium_sendto_and_recvfrom_udp6/1,
+
+ traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4/1,
+ traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6/1,
+ traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4/1,
+ traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6/1,
+ traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4/1,
+ traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6/1,
+
+ traffic_ping_pong_small_sendmsg_and_recvmsg_udp4/1,
+ traffic_ping_pong_small_sendmsg_and_recvmsg_udp6/1,
+ traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4/1,
+ traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6/1,
+
+ %% *** Time Test ***
+ %% Server: transport = gen_tcp, active = false
+ %% Client: transport = gen_tcp
+ ttest_sgenf_cgenf_small_tcp4/1,
+ ttest_sgenf_cgenf_small_tcp6/1,
+ ttest_sgenf_cgenf_medium_tcp4/1,
+ ttest_sgenf_cgenf_medium_tcp6/1,
+ ttest_sgenf_cgenf_large_tcp4/1,
+ ttest_sgenf_cgenf_large_tcp6/1,
+
+ ttest_sgenf_cgeno_small_tcp4/1,
+ ttest_sgenf_cgeno_small_tcp6/1,
+ ttest_sgenf_cgeno_medium_tcp4/1,
+ ttest_sgenf_cgeno_medium_tcp6/1,
+ ttest_sgenf_cgeno_large_tcp4/1,
+ ttest_sgenf_cgeno_large_tcp6/1,
+
+ ttest_sgenf_cgent_small_tcp4/1,
+ ttest_sgenf_cgent_small_tcp6/1,
+ ttest_sgenf_cgent_medium_tcp4/1,
+ ttest_sgenf_cgent_medium_tcp6/1,
+ ttest_sgenf_cgent_large_tcp4/1,
+ ttest_sgenf_cgent_large_tcp6/1,
+
+ %% Server: transport = gen_tcp, active = false
+ %% Client: transport = socket(tcp)
+ ttest_sgenf_csockf_small_tcp4/1,
+ ttest_sgenf_csockf_small_tcp6/1,
+ ttest_sgenf_csockf_medium_tcp4/1,
+ ttest_sgenf_csockf_medium_tcp6/1,
+ ttest_sgenf_csockf_large_tcp4/1,
+ ttest_sgenf_csockf_large_tcp6/1,
+
+ ttest_sgenf_csocko_small_tcp4/1,
+ ttest_sgenf_csocko_small_tcp6/1,
+ ttest_sgenf_csocko_medium_tcp4/1,
+ ttest_sgenf_csocko_medium_tcp6/1,
+ ttest_sgenf_csocko_large_tcp4/1,
+ ttest_sgenf_csocko_large_tcp6/1,
+
+ ttest_sgenf_csockt_small_tcp4/1,
+ ttest_sgenf_csockt_small_tcp6/1,
+ ttest_sgenf_csockt_medium_tcp4/1,
+ ttest_sgenf_csockt_medium_tcp6/1,
+ ttest_sgenf_csockt_large_tcp4/1,
+ ttest_sgenf_csockt_large_tcp6/1,
+
+ %% Server: transport = gen_tcp, active = once
+ %% Client: transport = gen_tcp
+ ttest_sgeno_cgenf_small_tcp4/1,
+ ttest_sgeno_cgenf_small_tcp6/1,
+ ttest_sgeno_cgenf_medium_tcp4/1,
+ ttest_sgeno_cgenf_medium_tcp6/1,
+ ttest_sgeno_cgenf_large_tcp4/1,
+ ttest_sgeno_cgenf_large_tcp6/1,
+
+ ttest_sgeno_cgeno_small_tcp4/1,
+ ttest_sgeno_cgeno_small_tcp6/1,
+ ttest_sgeno_cgeno_medium_tcp4/1,
+ ttest_sgeno_cgeno_medium_tcp6/1,
+ ttest_sgeno_cgeno_large_tcp4/1,
+ ttest_sgeno_cgeno_large_tcp6/1,
+
+ ttest_sgeno_cgent_small_tcp4/1,
+ ttest_sgeno_cgent_small_tcp6/1,
+ ttest_sgeno_cgent_medium_tcp4/1,
+ ttest_sgeno_cgent_medium_tcp6/1,
+ ttest_sgeno_cgent_large_tcp4/1,
+ ttest_sgeno_cgent_large_tcp6/1,
+
+ %% Server: transport = gen_tcp, active = once
+ %% Client: transport = socket(tcp)
+ ttest_sgeno_csockf_small_tcp4/1,
+ ttest_sgeno_csockf_small_tcp6/1,
+ ttest_sgeno_csockf_medium_tcp4/1,
+ ttest_sgeno_csockf_medium_tcp6/1,
+ ttest_sgeno_csockf_large_tcp4/1,
+ ttest_sgeno_csockf_large_tcp6/1,
+
+ ttest_sgeno_csocko_small_tcp4/1,
+ ttest_sgeno_csocko_small_tcp6/1,
+ ttest_sgeno_csocko_medium_tcp4/1,
+ ttest_sgeno_csocko_medium_tcp6/1,
+ ttest_sgeno_csocko_large_tcp4/1,
+ ttest_sgeno_csocko_large_tcp6/1,
+
+ ttest_sgeno_csockt_small_tcp4/1,
+ ttest_sgeno_csockt_small_tcp6/1,
+ ttest_sgeno_csockt_medium_tcp4/1,
+ ttest_sgeno_csockt_medium_tcp6/1,
+ ttest_sgeno_csockt_large_tcp4/1,
+ ttest_sgeno_csockt_large_tcp6/1,
+
+ %% Server: transport = gen_tcp, active = true
+ %% Client: transport = gen_tcp
+ ttest_sgent_cgenf_small_tcp4/1,
+ ttest_sgent_cgenf_small_tcp6/1,
+ ttest_sgent_cgenf_medium_tcp4/1,
+ ttest_sgent_cgenf_medium_tcp6/1,
+ ttest_sgent_cgenf_large_tcp4/1,
+ ttest_sgent_cgenf_large_tcp6/1,
+
+ ttest_sgent_cgeno_small_tcp4/1,
+ ttest_sgent_cgeno_small_tcp6/1,
+ ttest_sgent_cgeno_medium_tcp4/1,
+ ttest_sgent_cgeno_medium_tcp6/1,
+ ttest_sgent_cgeno_large_tcp4/1,
+ ttest_sgent_cgeno_large_tcp6/1,
+
+ ttest_sgent_cgent_small_tcp4/1,
+ ttest_sgent_cgent_small_tcp6/1,
+ ttest_sgent_cgent_medium_tcp4/1,
+ ttest_sgent_cgent_medium_tcp6/1,
+ ttest_sgent_cgent_large_tcp4/1,
+ ttest_sgent_cgent_large_tcp6/1,
+
+ %% Server: transport = gen_tcp, active = true
+ %% Client: transport = socket(tcp)
+ ttest_sgent_csockf_small_tcp4/1,
+ ttest_sgent_csockf_small_tcp6/1,
+ ttest_sgent_csockf_medium_tcp4/1,
+ ttest_sgent_csockf_medium_tcp6/1,
+ ttest_sgent_csockf_large_tcp4/1,
+ ttest_sgent_csockf_large_tcp6/1,
+
+ ttest_sgent_csocko_small_tcp4/1,
+ ttest_sgent_csocko_small_tcp6/1,
+ ttest_sgent_csocko_medium_tcp4/1,
+ ttest_sgent_csocko_medium_tcp6/1,
+ ttest_sgent_csocko_large_tcp4/1,
+ ttest_sgent_csocko_large_tcp6/1,
+
+ ttest_sgent_csockt_small_tcp4/1,
+ ttest_sgent_csockt_small_tcp6/1,
+ ttest_sgent_csockt_medium_tcp4/1,
+ ttest_sgent_csockt_medium_tcp6/1,
+ ttest_sgent_csockt_large_tcp4/1,
+ ttest_sgent_csockt_large_tcp6/1,
+
+ %% Server: transport = socket(tcp), active = false
+ %% Client: transport = gen_tcp
+ ttest_ssockf_cgenf_small_tcp4/1,
+ ttest_ssockf_cgenf_small_tcp6/1,
+ ttest_ssockf_cgenf_medium_tcp4/1,
+ ttest_ssockf_cgenf_medium_tcp6/1,
+ ttest_ssockf_cgenf_large_tcp4/1,
+ ttest_ssockf_cgenf_large_tcp6/1,
+
+ ttest_ssockf_cgeno_small_tcp4/1,
+ ttest_ssockf_cgeno_small_tcp6/1,
+ ttest_ssockf_cgeno_medium_tcp4/1,
+ ttest_ssockf_cgeno_medium_tcp6/1,
+ ttest_ssockf_cgeno_large_tcp4/1,
+ ttest_ssockf_cgeno_large_tcp6/1,
+
+ ttest_ssockf_cgent_small_tcp4/1,
+ ttest_ssockf_cgent_small_tcp6/1,
+ ttest_ssockf_cgent_medium_tcp4/1,
+ ttest_ssockf_cgent_medium_tcp6/1,
+ ttest_ssockf_cgent_large_tcp4/1,
+ ttest_ssockf_cgent_large_tcp6/1,
+
+ %% Server: transport = socket(tcp), active = false
+ %% Client: transport = socket(tcp)
+ ttest_ssockf_csockf_small_tcp4/1,
+ ttest_ssockf_csockf_small_tcp6/1,
+ ttest_ssockf_csockf_medium_tcp4/1,
+ ttest_ssockf_csockf_medium_tcp6/1,
+ ttest_ssockf_csockf_large_tcp4/1,
+ ttest_ssockf_csockf_large_tcp6/1,
+
+ ttest_ssockf_csocko_small_tcp4/1,
+ ttest_ssockf_csocko_small_tcp6/1,
+ ttest_ssockf_csocko_medium_tcp4/1,
+ ttest_ssockf_csocko_medium_tcp6/1,
+ ttest_ssockf_csocko_large_tcp4/1,
+ ttest_ssockf_csocko_large_tcp6/1,
+
+ ttest_ssockf_csockt_small_tcp4/1,
+ ttest_ssockf_csockt_small_tcp6/1,
+ ttest_ssockf_csockt_medium_tcp4/1,
+ ttest_ssockf_csockt_medium_tcp6/1,
+ ttest_ssockf_csockt_large_tcp4/1,
+ ttest_ssockf_csockt_large_tcp6/1,
+
+ %% Server: transport = socket(tcp), active = once
+ %% Client: transport = gen_tcp
+ ttest_ssocko_cgenf_small_tcp4/1,
+ ttest_ssocko_cgenf_small_tcp6/1,
+ ttest_ssocko_cgenf_medium_tcp4/1,
+ ttest_ssocko_cgenf_medium_tcp6/1,
+ ttest_ssocko_cgenf_large_tcp4/1,
+ ttest_ssocko_cgenf_large_tcp6/1,
+
+ ttest_ssocko_cgeno_small_tcp4/1,
+ ttest_ssocko_cgeno_small_tcp6/1,
+ ttest_ssocko_cgeno_medium_tcp4/1,
+ ttest_ssocko_cgeno_medium_tcp6/1,
+ ttest_ssocko_cgeno_large_tcp4/1,
+ ttest_ssocko_cgeno_large_tcp6/1,
+
+ ttest_ssocko_cgent_small_tcp4/1,
+ ttest_ssocko_cgent_small_tcp6/1,
+ ttest_ssocko_cgent_medium_tcp4/1,
+ ttest_ssocko_cgent_medium_tcp6/1,
+ ttest_ssocko_cgent_large_tcp4/1,
+ ttest_ssocko_cgent_large_tcp6/1,
+
+ %% Server: transport = socket(tcp), active = once
+ %% Client: transport = socket(tcp)
+ ttest_ssocko_csockf_small_tcp4/1,
+ ttest_ssocko_csockf_small_tcp6/1,
+ ttest_ssocko_csockf_medium_tcp4/1,
+ ttest_ssocko_csockf_medium_tcp6/1,
+ ttest_ssocko_csockf_large_tcp4/1,
+ ttest_ssocko_csockf_large_tcp6/1,
+
+ ttest_ssocko_csocko_small_tcp4/1,
+ ttest_ssocko_csocko_small_tcp6/1,
+ ttest_ssocko_csocko_medium_tcp4/1,
+ ttest_ssocko_csocko_medium_tcp6/1,
+ ttest_ssocko_csocko_large_tcp4/1,
+ ttest_ssocko_csocko_large_tcp6/1,
+
+ ttest_ssocko_csockt_small_tcp4/1,
+ ttest_ssocko_csockt_small_tcp6/1,
+ ttest_ssocko_csockt_medium_tcp4/1,
+ ttest_ssocko_csockt_medium_tcp6/1,
+ ttest_ssocko_csockt_large_tcp4/1,
+ ttest_ssocko_csockt_large_tcp6/1,
+
+ %% Server: transport = socket(tcp), active = true
+ %% Client: transport = gen_tcp
+ ttest_ssockt_cgenf_small_tcp4/1,
+ ttest_ssockt_cgenf_small_tcp6/1,
+ ttest_ssockt_cgenf_medium_tcp4/1,
+ ttest_ssockt_cgenf_medium_tcp6/1,
+ ttest_ssockt_cgenf_large_tcp4/1,
+ ttest_ssockt_cgenf_large_tcp6/1,
+
+ ttest_ssockt_cgeno_small_tcp4/1,
+ ttest_ssockt_cgeno_small_tcp6/1,
+ ttest_ssockt_cgeno_medium_tcp4/1,
+ ttest_ssockt_cgeno_medium_tcp6/1,
+ ttest_ssockt_cgeno_large_tcp4/1,
+ ttest_ssockt_cgeno_large_tcp6/1,
+
+ ttest_ssockt_cgent_small_tcp4/1,
+ ttest_ssockt_cgent_small_tcp6/1,
+ ttest_ssockt_cgent_medium_tcp4/1,
+ ttest_ssockt_cgent_medium_tcp6/1,
+ ttest_ssockt_cgent_large_tcp4/1,
+ ttest_ssockt_cgent_large_tcp6/1,
+
+ %% Server: transport = socket(tcp), active = true
+ %% Client: transport = socket(tcp)
+ ttest_ssockt_csockf_small_tcp4/1,
+ ttest_ssockt_csockf_small_tcp6/1,
+ ttest_ssockt_csockf_medium_tcp4/1,
+ ttest_ssockt_csockf_medium_tcp6/1,
+ ttest_ssockt_csockf_large_tcp4/1,
+ ttest_ssockt_csockf_large_tcp6/1,
+
+ ttest_ssockt_csocko_small_tcp4/1,
+ ttest_ssockt_csocko_small_tcp6/1,
+ ttest_ssockt_csocko_medium_tcp4/1,
+ ttest_ssockt_csocko_medium_tcp6/1,
+ ttest_ssockt_csocko_large_tcp4/1,
+ ttest_ssockt_csocko_large_tcp6/1,
+
+ ttest_ssockt_csockt_small_tcp4/1,
+ ttest_ssockt_csockt_small_tcp6/1,
+ ttest_ssockt_csockt_medium_tcp4/1,
+ ttest_ssockt_csockt_medium_tcp6/1,
+ ttest_ssockt_csockt_large_tcp4/1,
+ ttest_ssockt_csockt_large_tcp6/1
+
+ %% Tickets
+ ]).
+
+
+-include("socket_test_evaluator.hrl").
+
+%% Internal exports
+%% -export([]).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-define(BASIC_REQ, <<"hejsan">>).
+-define(BASIC_REP, <<"hoppsan">>).
+
+-define(DATA, <<"HOPPSAN">>). % Temporary
+-define(FAIL(R), exit(R)).
+
+-define(SLEEP(T), receive after T -> ok end).
+
+-define(MINS(M), timer:minutes(M)).
+-define(SECS(S), timer:seconds(S)).
+
+-define(TT(T), ct:timetrap(T)).
+
+-define(LIB, socket_test_lib).
+-define(TTEST_LIB, socket_test_ttest_lib).
+-define(LOGGER, socket_test_logger).
+
+-define(TPP_SMALL, lists:seq(1, 8)).
+-define(TPP_MEDIUM, lists:flatten(lists:duplicate(1024, ?TPP_SMALL))).
+-define(TPP_LARGE, lists:flatten(lists:duplicate(1024, ?TPP_MEDIUM))).
+
+-define(TPP_SMALL_NUM, 10000).
+-define(TPP_MEDIUM_NUM, 1000).
+-define(TPP_LARGE_NUM, 100).
+
+-define(TTEST_RUNTIME, ?SECS(10)).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+suite() ->
+ [{ct_hooks,[ts_install_cth]},
+ {timetrap,{minutes,1}}].
+
+all() ->
+ Groups = [{api, "ESOCK_TEST_API", include},
+ {socket_closure, "ESOCK_TEST_SOCK_CLOSE", include},
+ {traffic, "ESOCK_TEST_TRAFFIC", include},
+ {ttest, "ESOCK_TEST_TTEST", exclude}],
+ [use_group(Group, Env, Default) || {Group, Env, Default} <- Groups].
+
+use_group(Group, Env, Default) ->
+ case os:getenv(Env) of
+ false when (Default =:= include) ->
+ [{group, Group}];
+ false ->
+ [];
+ Val ->
+ case list_to_atom(string:to_lower(Val)) of
+ Use when (Use =:= include) orelse
+ (Use =:= enable) orelse
+ (Use =:= true) ->
+ [{group, Group}];
+ _ ->
+ []
+ end
+ end.
+
+
+groups() ->
+ [{api, [], api_cases()},
+ {api_basic, [], api_basic_cases()},
+ {api_options, [], api_options_cases()},
+ {api_op_with_timeout, [], api_op_with_timeout_cases()},
+ {socket_closure, [], socket_closure_cases()},
+ {sc_ctrl_proc_exit, [], sc_cp_exit_cases()},
+ {sc_local_close, [], sc_lc_cases()},
+ {sc_remote_close, [], sc_rc_cases()},
+ {sc_remote_shutdown, [], sc_rs_cases()},
+ {traffic, [], traffic_cases()},
+ {ttest, [], ttest_cases()},
+ {ttest_sgenf, [], ttest_sgenf_cases()},
+ {ttest_sgenf_cgen, [], ttest_sgenf_cgen_cases()},
+ {ttest_sgenf_cgenf, [], ttest_sgenf_cgenf_cases()},
+ {ttest_sgenf_cgeno, [], ttest_sgenf_cgeno_cases()},
+ {ttest_sgenf_cgent, [], ttest_sgenf_cgent_cases()},
+ {ttest_sgenf_csock, [], ttest_sgenf_csock_cases()},
+ {ttest_sgenf_csockf, [], ttest_sgenf_csockf_cases()},
+ {ttest_sgenf_csocko, [], ttest_sgenf_csocko_cases()},
+ {ttest_sgenf_csockt, [], ttest_sgenf_csockt_cases()},
+ {ttest_sgeno, [], ttest_sgeno_cases()},
+ {ttest_sgeno_cgen, [], ttest_sgeno_cgen_cases()},
+ {ttest_sgeno_cgenf, [], ttest_sgeno_cgenf_cases()},
+ {ttest_sgeno_cgeno, [], ttest_sgeno_cgeno_cases()},
+ {ttest_sgeno_cgent, [], ttest_sgeno_cgent_cases()},
+ {ttest_sgeno_csock, [], ttest_sgeno_csock_cases()},
+ {ttest_sgeno_csockf, [], ttest_sgeno_csockf_cases()},
+ {ttest_sgeno_csocko, [], ttest_sgeno_csocko_cases()},
+ {ttest_sgeno_csockt, [], ttest_sgeno_csockt_cases()},
+ {ttest_sgent, [], ttest_sgent_cases()},
+ {ttest_sgent_cgen, [], ttest_sgent_cgen_cases()},
+ {ttest_sgent_cgenf, [], ttest_sgent_cgenf_cases()},
+ {ttest_sgent_cgeno, [], ttest_sgent_cgeno_cases()},
+ {ttest_sgent_cgent, [], ttest_sgent_cgent_cases()},
+ {ttest_sgent_csock, [], ttest_sgent_csock_cases()},
+ {ttest_sgent_csockf, [], ttest_sgent_csockf_cases()},
+ {ttest_sgent_csocko, [], ttest_sgent_csocko_cases()},
+ {ttest_sgent_csockt, [], ttest_sgent_csockt_cases()},
+ {ttest_ssockf, [], ttest_ssockf_cases()},
+ {ttest_ssockf_cgen, [], ttest_ssockf_cgen_cases()},
+ {ttest_ssockf_cgenf, [], ttest_ssockf_cgenf_cases()},
+ {ttest_ssockf_cgeno, [], ttest_ssockf_cgeno_cases()},
+ {ttest_ssockf_cgent, [], ttest_ssockf_cgent_cases()},
+ {ttest_ssockf_csock, [], ttest_ssockf_csock_cases()},
+ {ttest_ssockf_csockf, [], ttest_ssockf_csockf_cases()},
+ {ttest_ssockf_csocko, [], ttest_ssockf_csocko_cases()},
+ {ttest_ssockf_csockt, [], ttest_ssockf_csockt_cases()},
+ {ttest_ssocko, [], ttest_ssocko_cases()},
+ {ttest_ssocko_cgen, [], ttest_ssocko_cgen_cases()},
+ {ttest_ssocko_cgenf, [], ttest_ssocko_cgenf_cases()},
+ {ttest_ssocko_cgeno, [], ttest_ssocko_cgeno_cases()},
+ {ttest_ssocko_cgent, [], ttest_ssocko_cgent_cases()},
+ {ttest_ssocko_csock, [], ttest_ssocko_csock_cases()},
+ {ttest_ssocko_csockf, [], ttest_ssocko_csockf_cases()},
+ {ttest_ssocko_csocko, [], ttest_ssocko_csocko_cases()},
+ {ttest_ssocko_csockt, [], ttest_ssocko_csockt_cases()},
+ {ttest_ssockt, [], ttest_ssockt_cases()},
+ {ttest_ssockt_cgen, [], ttest_ssockt_cgen_cases()},
+ {ttest_ssockt_cgenf, [], ttest_ssockt_cgenf_cases()},
+ {ttest_ssockt_cgeno, [], ttest_ssockt_cgeno_cases()},
+ {ttest_ssockt_cgent, [], ttest_ssockt_cgent_cases()},
+ {ttest_ssockt_csock, [], ttest_ssockt_csock_cases()},
+ {ttest_ssockt_csockf, [], ttest_ssockt_csockf_cases()},
+ {ttest_ssockt_csocko, [], ttest_ssockt_csocko_cases()},
+ {ttest_ssockt_csockt, [], ttest_ssockt_csockt_cases()}
+
+ %% {tickets, [], ticket_cases()}
+ ].
+
+api_cases() ->
+ [
+ {group, api_basic},
+ {group, api_options},
+ {group, api_op_with_timeout}
+ ].
+
+api_basic_cases() ->
+ [
+ api_b_open_and_close_udp4,
+ api_b_open_and_close_tcp4,
+ api_b_sendto_and_recvfrom_udp4,
+ api_b_sendmsg_and_recvmsg_udp4,
+ api_b_send_and_recv_tcp4,
+ api_b_sendmsg_and_recvmsg_tcp4
+ ].
+
+api_options_cases() ->
+ [
+ api_opt_simple_otp_options,
+ api_opt_simple_otp_rcvbuf_option,
+ api_opt_simple_otp_controlling_process
+ ].
+
+api_op_with_timeout_cases() ->
+ [
+ api_to_connect_tcp4,
+ api_to_connect_tcp6,
+ api_to_accept_tcp4,
+ api_to_accept_tcp6,
+ api_to_maccept_tcp4,
+ api_to_maccept_tcp6,
+ api_to_send_tcp4,
+ api_to_send_tcp6,
+ api_to_sendto_udp4,
+ api_to_sendto_udp6,
+ api_to_sendmsg_tcp4,
+ api_to_sendmsg_tcp6,
+ api_to_recv_udp4,
+ api_to_recv_udp6,
+ api_to_recv_tcp4,
+ api_to_recv_tcp6,
+ api_to_recvfrom_udp4,
+ api_to_recvfrom_udp6,
+ api_to_recvmsg_udp4,
+ api_to_recvmsg_udp6,
+ api_to_recvmsg_tcp4,
+ api_to_recvmsg_tcp6
+ ].
+
+%% These cases tests what happens when the socket is closed/shutdown,
+%% locally or remotely.
+socket_closure_cases() ->
+ [
+ {group, sc_ctrl_proc_exit},
+ {group, sc_local_close},
+ {group, sc_remote_close},
+ {group, sc_remote_shutdown}
+ ].
+
+%% These cases are all about socket cleanup after the controlling process
+%% exits *without* calling socket:close/1.
+sc_cp_exit_cases() ->
+ [
+ sc_cpe_socket_cleanup_tcp4,
+ sc_cpe_socket_cleanup_tcp6,
+ sc_cpe_socket_cleanup_udp4,
+ sc_cpe_socket_cleanup_udp6
+ ].
+
+%% These cases tests what happens when the socket is closed locally.
+sc_lc_cases() ->
+ [
+ sc_lc_recv_response_tcp4,
+ sc_lc_recv_response_tcp6,
+
+ sc_lc_recvfrom_response_udp4,
+ sc_lc_recvfrom_response_udp6,
+
+ sc_lc_recvmsg_response_tcp4,
+ sc_lc_recvmsg_response_tcp6,
+ sc_lc_recvmsg_response_udp4,
+ sc_lc_recvmsg_response_udp6,
+
+ sc_lc_acceptor_response_tcp4,
+ sc_lc_acceptor_response_tcp6
+ ].
+
+%% These cases tests what happens when the socket is closed remotely.
+sc_rc_cases() ->
+ [
+ sc_rc_recv_response_tcp4,
+ sc_rc_recv_response_tcp6,
+
+ sc_rc_recvmsg_response_tcp4,
+ sc_rc_recvmsg_response_tcp6
+ ].
+
+%% These cases tests what happens when the socket is shutdown/closed remotely
+%% after writing and reading is ongoing.
+sc_rs_cases() ->
+ [
+ sc_rs_recv_send_shutdown_receive_tcp4,
+ sc_rs_recv_send_shutdown_receive_tcp6,
+
+ sc_rs_recvmsg_send_shutdown_receive_tcp4,
+ sc_rs_recvmsg_send_shutdown_receive_tcp6
+ ].
+
+
+traffic_cases() ->
+ [
+ traffic_send_and_recv_chunks_tcp4,
+ traffic_send_and_recv_chunks_tcp6,
+
+ traffic_ping_pong_small_send_and_recv_tcp4,
+ traffic_ping_pong_small_send_and_recv_tcp6,
+ traffic_ping_pong_medium_send_and_recv_tcp4,
+ traffic_ping_pong_medium_send_and_recv_tcp6,
+ traffic_ping_pong_large_send_and_recv_tcp4,
+ traffic_ping_pong_large_send_and_recv_tcp6,
+
+ traffic_ping_pong_small_sendto_and_recvfrom_udp4,
+ traffic_ping_pong_small_sendto_and_recvfrom_udp6,
+ traffic_ping_pong_medium_sendto_and_recvfrom_udp4,
+ traffic_ping_pong_medium_sendto_and_recvfrom_udp6,
+
+ traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4,
+ traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6,
+ traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4,
+ traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6,
+ traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4,
+ traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6,
+
+ traffic_ping_pong_small_sendmsg_and_recvmsg_udp4,
+ traffic_ping_pong_small_sendmsg_and_recvmsg_udp6,
+ traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4,
+ traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6
+ ].
+
+
+ttest_cases() ->
+ [
+ %% Server: transport = gen_tcp, active = false
+ {group, ttest_sgenf},
+
+ %% Server: transport = gen_tcp, active = once
+ {group, ttest_sgeno},
+
+ %% Server: transport = gen_tcp, active = true
+ {group, ttest_sgent},
+
+ %% Server: transport = socket(tcp), active = false
+ {group, ttest_ssockf},
+
+ %% Server: transport = socket(tcp), active = once
+ {group, ttest_ssocko},
+
+ %% Server: transport = socket(tcp), active = true
+ {group, ttest_ssockt}
+
+ ].
+
+
+%% Server: transport = gen_tcp, active = false
+ttest_sgenf_cases() ->
+ [
+ {group, ttest_sgenf_cgen},
+ {group, ttest_sgenf_csock}
+ ].
+
+%% Server: transport = gen_tcp, active = false
+%% Client: transport = gen_tcp
+ttest_sgenf_cgen_cases() ->
+ [
+ {group, ttest_sgenf_cgenf},
+ {group, ttest_sgenf_cgeno},
+ {group, ttest_sgenf_cgent}
+ ].
+
+%% Server: transport = gen_tcp, active = false
+%% Client: transport = gen_tcp, active = false
+ttest_sgenf_cgenf_cases() ->
+ [
+ ttest_sgenf_cgenf_small_tcp4,
+ ttest_sgenf_cgenf_small_tcp6,
+
+ ttest_sgenf_cgenf_medium_tcp4,
+ ttest_sgenf_cgenf_medium_tcp6,
+
+ ttest_sgenf_cgenf_large_tcp4,
+ ttest_sgenf_cgenf_large_tcp6
+ ].
+
+%% Server: transport = gen_tcp, active = false
+%% Client: transport = gen_tcp, active = once
+ttest_sgenf_cgeno_cases() ->
+ [
+ ttest_sgenf_cgeno_small_tcp4,
+ ttest_sgenf_cgeno_small_tcp6,
+
+ ttest_sgenf_cgeno_medium_tcp4,
+ ttest_sgenf_cgeno_medium_tcp6,
+
+ ttest_sgenf_cgeno_large_tcp4,
+ ttest_sgenf_cgeno_large_tcp6
+ ].
+
+%% Server: transport = gen_tcp, active = false
+%% Client: transport = gen_tcp, active = true
+ttest_sgenf_cgent_cases() ->
+ [
+ ttest_sgenf_cgent_small_tcp4,
+ ttest_sgenf_cgent_small_tcp6,
+
+ ttest_sgenf_cgent_medium_tcp4,
+ ttest_sgenf_cgent_medium_tcp6,
+
+ ttest_sgenf_cgent_large_tcp4,
+ ttest_sgenf_cgent_large_tcp6
+ ].
+
+%% Server: transport = gen_tcp, active = false
+%% Client: transport = socket(tcp)
+ttest_sgenf_csock_cases() ->
+ [
+ {group, ttest_sgenf_csockf},
+ {group, ttest_sgenf_csocko},
+ {group, ttest_sgenf_csockt}
+ ].
+
+ttest_sgenf_csockf_cases() ->
+ [
+ ttest_sgenf_csockf_small_tcp4,
+ ttest_sgenf_csockf_small_tcp6,
+
+ ttest_sgenf_csockf_medium_tcp4,
+ ttest_sgenf_csockf_medium_tcp6,
+
+ ttest_sgenf_csockf_large_tcp4,
+ ttest_sgenf_csockf_large_tcp6
+ ].
+
+ttest_sgenf_csocko_cases() ->
+ [
+ ttest_sgenf_csocko_small_tcp4,
+ ttest_sgenf_csocko_small_tcp6,
+
+ ttest_sgenf_csocko_medium_tcp4,
+ ttest_sgenf_csocko_medium_tcp6,
+
+ ttest_sgenf_csocko_large_tcp4,
+ ttest_sgenf_csocko_large_tcp6
+ ].
+
+ttest_sgenf_csockt_cases() ->
+ [
+ ttest_sgenf_csockt_small_tcp4,
+ ttest_sgenf_csockt_small_tcp6,
+
+ ttest_sgenf_csockt_medium_tcp4,
+ ttest_sgenf_csockt_medium_tcp6,
+
+ ttest_sgenf_csockt_large_tcp4,
+ ttest_sgenf_csockt_large_tcp6
+ ].
+
+%% Server: transport = gen_tcp, active = once
+ttest_sgeno_cases() ->
+ [
+ {group, ttest_sgeno_cgen},
+ {group, ttest_sgeno_csock}
+ ].
+
+%% Server: transport = gen_tcp, active = once
+%% Client: transport = gen_tcp
+ttest_sgeno_cgen_cases() ->
+ [
+ {group, ttest_sgeno_cgenf},
+ {group, ttest_sgeno_cgeno},
+ {group, ttest_sgeno_cgent}
+ ].
+
+%% Server: transport = gen_tcp, active = once
+%% Client: transport = gen_tcp, active = false
+ttest_sgeno_cgenf_cases() ->
+ [
+ ttest_sgeno_cgenf_small_tcp4,
+ ttest_sgeno_cgenf_small_tcp6,
+
+ ttest_sgeno_cgenf_medium_tcp4,
+ ttest_sgeno_cgenf_medium_tcp6,
+
+ ttest_sgeno_cgenf_large_tcp4,
+ ttest_sgeno_cgenf_large_tcp6
+ ].
+
+%% Server: transport = gen_tcp, active = once
+%% Client: transport = gen_tcp, active = once
+ttest_sgeno_cgeno_cases() ->
+ [
+ ttest_sgeno_cgeno_small_tcp4,
+ ttest_sgeno_cgeno_small_tcp6,
+
+ ttest_sgeno_cgeno_medium_tcp4,
+ ttest_sgeno_cgeno_medium_tcp6,
+
+ ttest_sgeno_cgeno_large_tcp4,
+ ttest_sgeno_cgeno_large_tcp6
+ ].
+
+%% Server: transport = gen_tcp, active = once
+%% Client: transport = gen_tcp, active = true
+ttest_sgeno_cgent_cases() ->
+ [
+ ttest_sgeno_cgent_small_tcp4,
+ ttest_sgeno_cgent_small_tcp6,
+
+ ttest_sgeno_cgent_medium_tcp4,
+ ttest_sgeno_cgent_medium_tcp6,
+
+ ttest_sgeno_cgent_large_tcp4,
+ ttest_sgeno_cgent_large_tcp6
+ ].
+
+%% Server: transport = gen_tcp, active = once
+%% Client: transport = socket(tcp)
+ttest_sgeno_csock_cases() ->
+ [
+ {group, ttest_sgeno_csockf},
+ {group, ttest_sgeno_csocko},
+ {group, ttest_sgeno_csockt}
+ ].
+
+ttest_sgeno_csockf_cases() ->
+ [
+ ttest_sgeno_csockf_small_tcp4,
+ ttest_sgeno_csockf_small_tcp6,
+
+ ttest_sgeno_csockf_medium_tcp4,
+ ttest_sgeno_csockf_medium_tcp6,
+
+ ttest_sgeno_csockf_large_tcp4,
+ ttest_sgeno_csockf_large_tcp6
+ ].
+
+ttest_sgeno_csocko_cases() ->
+ [
+ ttest_sgeno_csocko_small_tcp4,
+ ttest_sgeno_csocko_small_tcp6,
+
+ ttest_sgeno_csocko_medium_tcp4,
+ ttest_sgeno_csocko_medium_tcp6,
+
+ ttest_sgeno_csocko_large_tcp4,
+ ttest_sgeno_csocko_large_tcp6
+ ].
+
+ttest_sgeno_csockt_cases() ->
+ [
+ ttest_sgeno_csockt_small_tcp4,
+ ttest_sgeno_csockt_small_tcp6,
+
+ ttest_sgeno_csockt_medium_tcp4,
+ ttest_sgeno_csockt_medium_tcp6,
+
+ ttest_sgeno_csockt_large_tcp4,
+ ttest_sgeno_csockt_large_tcp6
+ ].
+
+%% Server: transport = gen_tcp, active = true
+ttest_sgent_cases() ->
+ [
+ {group, ttest_sgent_cgen},
+ {group, ttest_sgent_csock}
+ ].
+
+%% Server: transport = gen_tcp, active = true
+%% Client: transport = gen_tcp
+ttest_sgent_cgen_cases() ->
+ [
+ {group, ttest_sgent_cgenf},
+ {group, ttest_sgent_cgeno},
+ {group, ttest_sgent_cgent}
+ ].
+
+%% Server: transport = gen_tcp, active = true
+%% Client: transport = gen_tcp, active = false
+ttest_sgent_cgenf_cases() ->
+ [
+ ttest_sgent_cgenf_small_tcp4,
+ ttest_sgent_cgenf_small_tcp6,
+
+ ttest_sgent_cgenf_medium_tcp4,
+ ttest_sgent_cgenf_medium_tcp6,
+
+ ttest_sgent_cgenf_large_tcp4,
+ ttest_sgent_cgenf_large_tcp6
+ ].
+
+%% Server: transport = gen_tcp, active = true
+%% Client: transport = gen_tcp, active = once
+ttest_sgent_cgeno_cases() ->
+ [
+ ttest_sgent_cgeno_small_tcp4,
+ ttest_sgent_cgeno_small_tcp6,
+
+ ttest_sgent_cgeno_medium_tcp4,
+ ttest_sgent_cgeno_medium_tcp6,
+
+ ttest_sgent_cgeno_large_tcp4,
+ ttest_sgent_cgeno_large_tcp6
+ ].
+
+%% Server: transport = gen_tcp, active = true
+%% Client: transport = gen_tcp, active = true
+ttest_sgent_cgent_cases() ->
+ [
+ ttest_sgent_cgent_small_tcp4,
+ ttest_sgent_cgent_small_tcp6,
+
+ ttest_sgent_cgent_medium_tcp4,
+ ttest_sgent_cgent_medium_tcp6,
+
+ ttest_sgent_cgent_large_tcp4,
+ ttest_sgent_cgent_large_tcp6
+ ].
+
+%% Server: transport = gen_tcp, active = true
+%% Client: transport = socket(tcp)
+ttest_sgent_csock_cases() ->
+ [
+ {group, ttest_sgent_csockf},
+ {group, ttest_sgent_csocko},
+ {group, ttest_sgent_csockt}
+ ].
+
+ttest_sgent_csockf_cases() ->
+ [
+ ttest_sgent_csockf_small_tcp4,
+ ttest_sgent_csockf_small_tcp6,
+
+ ttest_sgent_csockf_medium_tcp4,
+ ttest_sgent_csockf_medium_tcp6,
+
+ ttest_sgent_csockf_large_tcp4,
+ ttest_sgent_csockf_large_tcp6
+ ].
+
+ttest_sgent_csocko_cases() ->
+ [
+ ttest_sgent_csocko_small_tcp4,
+ ttest_sgent_csocko_small_tcp6,
+
+ ttest_sgent_csocko_medium_tcp4,
+ ttest_sgent_csocko_medium_tcp6,
+
+ ttest_sgent_csocko_large_tcp4,
+ ttest_sgent_csocko_large_tcp6
+ ].
+
+ttest_sgent_csockt_cases() ->
+ [
+ ttest_sgent_csockt_small_tcp4,
+ ttest_sgent_csockt_small_tcp6,
+
+ ttest_sgent_csockt_medium_tcp4,
+ ttest_sgent_csockt_medium_tcp6,
+
+ ttest_sgent_csockt_large_tcp4,
+ ttest_sgent_csockt_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = false
+ttest_ssockf_cases() ->
+ [
+ {group, ttest_ssockf_cgen},
+ {group, ttest_ssockf_csock}
+ ].
+
+%% Server: transport = socket(tcp), active = false
+%% Client: transport = gen_tcp
+ttest_ssockf_cgen_cases() ->
+ [
+ {group, ttest_ssockf_cgenf},
+ {group, ttest_ssockf_cgeno},
+ {group, ttest_ssockf_cgent}
+ ].
+
+%% Server: transport = socket(tcp), active = false
+%% Client: transport = gen_tcp, active = false
+ttest_ssockf_cgenf_cases() ->
+ [
+ ttest_ssockf_cgenf_small_tcp4,
+ ttest_ssockf_cgenf_small_tcp6,
+
+ ttest_ssockf_cgenf_medium_tcp4,
+ ttest_ssockf_cgenf_medium_tcp6,
+
+ ttest_ssockf_cgenf_large_tcp4,
+ ttest_ssockf_cgenf_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = false
+%% Client: transport = gen_tcp, active = once
+ttest_ssockf_cgeno_cases() ->
+ [
+ ttest_ssockf_cgeno_small_tcp4,
+ ttest_ssockf_cgeno_small_tcp6,
+
+ ttest_ssockf_cgeno_medium_tcp4,
+ ttest_ssockf_cgeno_medium_tcp6,
+
+ ttest_ssockf_cgeno_large_tcp4,
+ ttest_ssockf_cgeno_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = false
+%% Client: transport = gen_tcp, active = true
+ttest_ssockf_cgent_cases() ->
+ [
+ ttest_ssockf_cgent_small_tcp4,
+ ttest_ssockf_cgent_small_tcp6,
+
+ ttest_ssockf_cgent_medium_tcp4,
+ ttest_ssockf_cgent_medium_tcp6,
+
+ ttest_ssockf_cgent_large_tcp4,
+ ttest_ssockf_cgent_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = false
+%% Client: transport = socket(tcp)
+ttest_ssockf_csock_cases() ->
+ [
+ {group, ttest_ssockf_csockf},
+ {group, ttest_ssockf_csocko},
+ {group, ttest_ssockf_csockt}
+ ].
+
+%% Server: transport = socket(tcp), active = false
+%% Client: transport = socket(tcp), active = false
+ttest_ssockf_csockf_cases() ->
+ [
+ ttest_ssockf_csockf_small_tcp4,
+ ttest_ssockf_csockf_small_tcp6,
+
+ ttest_ssockf_csockf_medium_tcp4,
+ ttest_ssockf_csockf_medium_tcp6,
+
+ ttest_ssockf_csockf_large_tcp4,
+ ttest_ssockf_csockf_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = false
+%% Client: transport = socket(tcp), active = once
+ttest_ssockf_csocko_cases() ->
+ [
+ ttest_ssockf_csocko_small_tcp4,
+ ttest_ssockf_csocko_small_tcp6,
+
+ ttest_ssockf_csocko_medium_tcp4,
+ ttest_ssockf_csocko_medium_tcp6,
+
+ ttest_ssockf_csocko_large_tcp4,
+ ttest_ssockf_csocko_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = false
+%% Client: transport = socket(tcp), active = true
+ttest_ssockf_csockt_cases() ->
+ [
+ ttest_ssockf_csockt_small_tcp4,
+ ttest_ssockf_csockt_small_tcp6,
+
+ ttest_ssockf_csockt_medium_tcp4,
+ ttest_ssockf_csockt_medium_tcp6,
+
+ ttest_ssockf_csockt_large_tcp4,
+ ttest_ssockf_csockt_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = once
+ttest_ssocko_cases() ->
+ [
+ {group, ttest_ssocko_cgen},
+ {group, ttest_ssocko_csock}
+ ].
+
+%% Server: transport = socket(tcp), active = once
+%% Client: transport = gen_tcp
+ttest_ssocko_cgen_cases() ->
+ [
+ {group, ttest_ssocko_cgenf},
+ {group, ttest_ssocko_cgeno},
+ {group, ttest_ssocko_cgent}
+ ].
+
+%% Server: transport = socket(tcp), active = once
+%% Client: transport = gen_tcp, active = false
+ttest_ssocko_cgenf_cases() ->
+ [
+ ttest_ssocko_cgenf_small_tcp4,
+ ttest_ssocko_cgenf_small_tcp6,
+
+ ttest_ssocko_cgenf_medium_tcp4,
+ ttest_ssocko_cgenf_medium_tcp6,
+
+ ttest_ssocko_cgenf_large_tcp4,
+ ttest_ssocko_cgenf_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = once
+%% Client: transport = gen_tcp, active = once
+ttest_ssocko_cgeno_cases() ->
+ [
+ ttest_ssocko_cgeno_small_tcp4,
+ ttest_ssocko_cgeno_small_tcp6,
+
+ ttest_ssocko_cgeno_medium_tcp4,
+ ttest_ssocko_cgeno_medium_tcp6,
+
+ ttest_ssocko_cgeno_large_tcp4,
+ ttest_ssocko_cgeno_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = once
+%% Client: transport = gen_tcp, active = true
+ttest_ssocko_cgent_cases() ->
+ [
+ ttest_ssocko_cgent_small_tcp4,
+ ttest_ssocko_cgent_small_tcp6,
+
+ ttest_ssocko_cgent_medium_tcp4,
+ ttest_ssocko_cgent_medium_tcp6,
+
+ ttest_ssocko_cgent_large_tcp4,
+ ttest_ssocko_cgent_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = once
+%% Client: transport = socket(tcp)
+ttest_ssocko_csock_cases() ->
+ [
+ {group, ttest_ssocko_csockf},
+ {group, ttest_ssocko_csocko},
+ {group, ttest_ssocko_csockt}
+ ].
+
+%% Server: transport = socket(tcp), active = once
+%% Client: transport = socket(tcp), active = false
+ttest_ssocko_csockf_cases() ->
+ [
+ ttest_ssocko_csockf_small_tcp4,
+ ttest_ssocko_csockf_small_tcp6,
+
+ ttest_ssocko_csockf_medium_tcp4,
+ ttest_ssocko_csockf_medium_tcp6,
+
+ ttest_ssocko_csockf_large_tcp4,
+ ttest_ssocko_csockf_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = once
+%% Client: transport = socket(tcp), active = once
+ttest_ssocko_csocko_cases() ->
+ [
+ ttest_ssocko_csocko_small_tcp4,
+ ttest_ssocko_csocko_small_tcp6,
+
+ ttest_ssocko_csocko_medium_tcp4,
+ ttest_ssocko_csocko_medium_tcp6,
+
+ ttest_ssocko_csocko_large_tcp4,
+ ttest_ssocko_csocko_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = once
+%% Client: transport = socket(tcp), active = true
+ttest_ssocko_csockt_cases() ->
+ [
+ ttest_ssocko_csockt_small_tcp4,
+ ttest_ssocko_csockt_small_tcp6,
+
+ ttest_ssocko_csockt_medium_tcp4,
+ ttest_ssocko_csockt_medium_tcp6,
+
+ ttest_ssocko_csockt_large_tcp4,
+ ttest_ssocko_csockt_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = true
+ttest_ssockt_cases() ->
+ [
+ {group, ttest_ssockt_cgen},
+ {group, ttest_ssockt_csock}
+ ].
+
+%% Server: transport = socket(tcp), active = true
+%% Client: transport = gen_tcp
+ttest_ssockt_cgen_cases() ->
+ [
+ {group, ttest_ssockt_cgenf},
+ {group, ttest_ssockt_cgeno},
+ {group, ttest_ssockt_cgent}
+ ].
+
+%% Server: transport = socket(tcp), active = true
+%% Client: transport = gen_tcp, active = false
+ttest_ssockt_cgenf_cases() ->
+ [
+ ttest_ssockt_cgenf_small_tcp4,
+ ttest_ssockt_cgenf_small_tcp6,
+
+ ttest_ssockt_cgenf_medium_tcp4,
+ ttest_ssockt_cgenf_medium_tcp6,
+
+ ttest_ssockt_cgenf_large_tcp4,
+ ttest_ssockt_cgenf_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = true
+%% Client: transport = gen_tcp, active = once
+ttest_ssockt_cgeno_cases() ->
+ [
+ ttest_ssockt_cgeno_small_tcp4,
+ ttest_ssockt_cgeno_small_tcp6,
+
+ ttest_ssockt_cgeno_medium_tcp4,
+ ttest_ssockt_cgeno_medium_tcp6,
+
+ ttest_ssockt_cgeno_large_tcp4,
+ ttest_ssockt_cgeno_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = true
+%% Client: transport = gen_tcp, active = true
+ttest_ssockt_cgent_cases() ->
+ [
+ ttest_ssockt_cgent_small_tcp4,
+ ttest_ssockt_cgent_small_tcp6,
+
+ ttest_ssockt_cgent_medium_tcp4,
+ ttest_ssockt_cgent_medium_tcp6,
+
+ ttest_ssockt_cgent_large_tcp4,
+ ttest_ssockt_cgent_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = true
+%% Client: transport = socket(tcp)
+ttest_ssockt_csock_cases() ->
+ [
+ {group, ttest_ssockt_csockf},
+ {group, ttest_ssockt_csocko},
+ {group, ttest_ssockt_csockt}
+ ].
+
+%% Server: transport = socket(tcp), active = true
+%% Client: transport = socket(tcp), active = false
+ttest_ssockt_csockf_cases() ->
+ [
+ ttest_ssockt_csockf_small_tcp4,
+ ttest_ssockt_csockf_small_tcp6,
+
+ ttest_ssockt_csockf_medium_tcp4,
+ ttest_ssockt_csockf_medium_tcp6,
+
+ ttest_ssockt_csockf_large_tcp4,
+ ttest_ssockt_csockf_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = true
+%% Client: transport = socket(tcp), active = once
+ttest_ssockt_csocko_cases() ->
+ [
+ ttest_ssockt_csocko_small_tcp4,
+ ttest_ssockt_csocko_small_tcp6,
+
+ ttest_ssockt_csocko_medium_tcp4,
+ ttest_ssockt_csocko_medium_tcp6,
+
+ ttest_ssockt_csocko_large_tcp4,
+ ttest_ssockt_csocko_large_tcp6
+ ].
+
+%% Server: transport = socket(tcp), active = true
+%% Client: transport = socket(tcp), active = true
+ttest_ssockt_csockt_cases() ->
+ [
+ ttest_ssockt_csockt_small_tcp4,
+ ttest_ssockt_csockt_small_tcp6,
+
+ ttest_ssockt_csockt_medium_tcp4,
+ ttest_ssockt_csockt_medium_tcp6,
+
+ ttest_ssockt_csockt_large_tcp4,
+ ttest_ssockt_csockt_large_tcp6
+ ].
+
+%% ticket_cases() ->
+%% [].
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+init_per_suite(Config) ->
+ case os:type() of
+ {win32, _} ->
+ not_yet_implemented();
+ _ ->
+ case quiet_mode(Config) of
+ default ->
+ ?LOGGER:start(),
+ Config;
+ Quiet ->
+ ?LOGGER:start(Quiet),
+ [{esock_test_quiet, Quiet}|Config]
+ end
+ end.
+
+end_per_suite(_) ->
+ ?LOGGER:stop(),
+ ok.
+
+
+init_per_group(ttest = _GroupName, Config) ->
+ io:format("init_per_group(~w) -> entry with"
+ "~n Config: ~p"
+ "~n", [_GroupName, Config]),
+ case lists:keysearch(esock_test_ttest_runtime, 1, Config) of
+ {value, _} ->
+ Config;
+ false ->
+ [{esock_test_ttest_runtime, which_ttest_runtime_env()}|Config]
+ end;
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(ttest = _GroupName, Config) ->
+ io:format("init_per_group(~w) -> entry with"
+ "~n Config: ~p"
+ "~n", [_GroupName, Config]),
+ lists:keydelete(esock_test_ttest_runtime, 1, Config);
+end_per_group(_GroupName, Config) ->
+ Config.
+
+
+init_per_testcase(_TC, Config) ->
+ io:format("init_per_testcase(~w) -> entry with"
+ "~n Config: ~p"
+ "~n", [_TC, Config]),
+ case quiet_mode(Config) of
+ default ->
+ ?LOGGER:start();
+ Quiet ->
+ ?LOGGER:start(Quiet)
+ end,
+ Config.
+
+end_per_testcase(_TC, Config) ->
+ ?LOGGER:stop(),
+ Config.
+
+
+quiet_mode(Config) ->
+ case lists:keysearch(esock_test_quiet, 1, Config) of
+ {value, {esock_test_quiet, Quiet}} ->
+ Quiet;
+ false ->
+ case os:getenv("ESOCK_TEST_QUIET") of
+ "true" -> true;
+ "false" -> false;
+ _ -> default
+ end
+ end.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% %%
+%% API BASIC %%
+%% %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) and close an IPv4 UDP (dgram) socket.
+%% With some extra checks...
+api_b_open_and_close_udp4(suite) ->
+ [];
+api_b_open_and_close_udp4(doc) ->
+ [];
+api_b_open_and_close_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_b_open_and_close_udp4,
+ fun() ->
+ InitState = #{domain => inet,
+ type => dgram,
+ protocol => udp},
+ ok = api_b_open_and_close(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically open (create) and close an IPv4 TCP (stream) socket.
+%% With some extra checks...
+api_b_open_and_close_tcp4(suite) ->
+ [];
+api_b_open_and_close_tcp4(doc) ->
+ [];
+api_b_open_and_close_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_b_open_and_close_tcp4,
+ fun() ->
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp},
+ ok = api_b_open_and_close(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_b_open_and_close(InitState) ->
+ Seq =
+ [
+ #{desc => "open",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Protocol} = S) ->
+ Res = socket:open(Domain, Type, Protocol),
+ {ok, {S, Res}}
+ end},
+ #{desc => "validate open",
+ cmd => fun({S, {ok, Sock}}) ->
+ NewS = S#{socket => Sock},
+ {ok, NewS};
+ ({_, {error, _} = ERROR}) ->
+ ERROR
+ end},
+ #{desc => "get domain (maybe)",
+ cmd => fun(#{socket := Sock} = S) ->
+ Res = socket:getopt(Sock, socket, domain),
+ {ok, {S, Res}}
+ end},
+ #{desc => "validate domain (maybe)",
+ cmd => fun({#{domain := Domain} = S, {ok, Domain}}) ->
+ {ok, S};
+ ({#{domain := ExpDomain}, {ok, Domain}}) ->
+ {error, {unexpected_domain, ExpDomain, Domain}};
+ %% Some platforms do not support this option
+ ({S, {error, einval}}) ->
+ {ok, S};
+ ({_, {error, _} = ERROR}) ->
+ ERROR
+ end},
+ #{desc => "get type",
+ cmd => fun(#{socket := Sock} = State) ->
+ Res = socket:getopt(Sock, socket, type),
+ {ok, {State, Res}}
+ end},
+ #{desc => "validate type",
+ cmd => fun({#{type := Type} = State, {ok, Type}}) ->
+ {ok, State};
+ ({#{type := ExpType}, {ok, Type}}) ->
+ {error, {unexpected_type, ExpType, Type}};
+ ({_, {error, _} = ERROR}) ->
+ ERROR
+ end},
+ #{desc => "get protocol",
+ cmd => fun(#{socket := Sock} = State) ->
+ case socket:supports(options, socket, protocol) of
+ true ->
+ Res = socket:getopt(Sock, socket, protocol),
+ {ok, {State, Res}};
+ false ->
+ {ok, {State, not_supported}}
+ end
+ end},
+ #{desc => "validate protocol",
+ cmd => fun({State, not_supported}) ->
+ ?SEV_IPRINT("socket option 'protocol' "
+ "not supported"),
+ {ok, State};
+ ({#{protocol := Protocol} = State, {ok, Protocol}}) ->
+ {ok, State};
+ ({#{protocol := ExpProtocol}, {ok, Protocol}}) ->
+ {error, {unexpected_type, ExpProtocol, Protocol}};
+ ({_, {error, _} = ERROR}) ->
+ ERROR
+ end},
+ #{desc => "get controlling-process",
+ cmd => fun(#{socket := Sock} = State) ->
+ Res = socket:getopt(Sock, otp, controlling_process),
+ {ok, {State, Res}}
+ end},
+ #{desc => "validate controlling-process",
+ cmd => fun({State, {ok, Pid}}) ->
+ case self() of
+ Pid ->
+ {ok, State};
+ _ ->
+ {error, {unexpected_owner, Pid}}
+ end;
+ ({_, {error, _} = ERROR}) ->
+ ERROR
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{socket := Sock} = State) ->
+ Res = socket:close(Sock),
+ {ok, {State, Res}}
+ end},
+ #{desc => "validate socket close",
+ cmd => fun({_, ok}) ->
+ ok;
+ ({_, {error, _} = ERROR}) ->
+ ERROR
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+ Evaluator = ?SEV_START("tester", Seq, InitState),
+ ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically send and receive on an IPv4 UDP (dgram) socket using
+%% sendto and recvfrom..
+api_b_sendto_and_recvfrom_udp4(suite) ->
+ [];
+api_b_sendto_and_recvfrom_udp4(doc) ->
+ [];
+api_b_sendto_and_recvfrom_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_b_sendto_and_recvfrom_udp4,
+ fun() ->
+ Send = fun(Sock, Data, Dest) ->
+ socket:sendto(Sock, Data, Dest)
+ end,
+ Recv = fun(Sock) ->
+ socket:recvfrom(Sock)
+ end,
+ InitState = #{domain => inet,
+ send => Send,
+ recv => Recv},
+ ok = api_b_send_and_recv_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically send and receive on an IPv4 UDP (dgram) socket
+%% using sendmsg and recvmsg.
+api_b_sendmsg_and_recvmsg_udp4(suite) ->
+ [];
+api_b_sendmsg_and_recvmsg_udp4(doc) ->
+ [];
+api_b_sendmsg_and_recvmsg_udp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_b_sendmsg_and_recvmsg_udp4,
+ fun() ->
+ Send = fun(Sock, Data, Dest) ->
+ %% CMsgHdr = #{level => ip,
+ %% type => tos,
+ %% data => reliability},
+ %% CMsgHdrs = [CMsgHdr],
+ MsgHdr = #{addr => Dest,
+ %% ctrl => CMsgHdrs,
+ iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock) of
+ {ok, #{addr := Source,
+ iov := [Data]}} ->
+ {ok, {Source, Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet,
+ send => Send,
+ recv => Recv},
+ ok = api_b_send_and_recv_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_b_send_and_recv_udp(InitState) ->
+ Seq =
+ [
+ #{desc => "local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain, addr => LAddr},
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "open src socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ Sock = sock_open(Domain, dgram, udp),
+ SASrc = sock_sockname(Sock),
+ {ok, State#{sock_src => Sock, sa_src => SASrc}}
+ end},
+ #{desc => "bind src",
+ cmd => fun(#{sock_src := Sock, lsa := LSA}) ->
+ sock_bind(Sock, LSA),
+ ok
+ end},
+ #{desc => "sockname src socket",
+ cmd => fun(#{sock_src := Sock} = State) ->
+ SASrc = sock_sockname(Sock),
+ %% ei("src sockaddr: ~p", [SASrc]),
+ {ok, State#{sa_src => SASrc}}
+ end},
+ #{desc => "open dst socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ Sock = sock_open(Domain, dgram, udp),
+ {ok, State#{sock_dst => Sock}}
+ end},
+ #{desc => "bind dst",
+ cmd => fun(#{sock_dst := Sock, lsa := LSA}) ->
+ sock_bind(Sock, LSA),
+ ok
+ end},
+ #{desc => "sockname dst socket",
+ cmd => fun(#{sock_dst := Sock} = State) ->
+ SADst = sock_sockname(Sock),
+ %% ei("dst sockaddr: ~p", [SADst]),
+ {ok, State#{sa_dst => SADst}}
+ end},
+ #{desc => "send req (to dst)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) ->
+ ok = Send(Sock, ?BASIC_REQ, Dst)
+ end},
+ #{desc => "recv req (from src)",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) ->
+ {ok, {Src, ?BASIC_REQ}} = Recv(Sock),
+ ok
+ end},
+ #{desc => "send rep (to src)",
+ cmd => fun(#{sock_dst := Sock, sa_src := Src, send := Send}) ->
+ ok = Send(Sock, ?BASIC_REP, Src)
+ end},
+ #{desc => "recv rep (from dst)",
+ cmd => fun(#{sock_src := Sock, sa_dst := Dst, recv := Recv}) ->
+ {ok, {Dst, ?BASIC_REP}} = Recv(Sock),
+ ok
+ end},
+ #{desc => "close src socket",
+ cmd => fun(#{sock_src := Sock}) ->
+ ok = socket:close(Sock)
+ end},
+ #{desc => "close dst socket",
+ cmd => fun(#{sock_dst := Sock}) ->
+ ok = socket:close(Sock)
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+ Evaluator = ?SEV_START("tester", Seq, InitState),
+ ok = ?SEV_AWAIT_FINISH([Evaluator]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically send and receive using the "common" functions (send and recv)
+%% on an IPv4 TCP (stream) socket.
+api_b_send_and_recv_tcp4(suite) ->
+ [];
+api_b_send_and_recv_tcp4(doc) ->
+ [];
+api_b_send_and_recv_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_b_send_and_recv_tcp4,
+ fun() ->
+ Send = fun(Sock, Data) ->
+ socket:send(Sock, Data)
+ end,
+ Recv = fun(Sock) ->
+ socket:recv(Sock)
+ end,
+ InitState = #{domain => inet,
+ send => Send,
+ recv => Recv},
+ ok = api_b_send_and_recv_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Basically send and receive using the msg functions (sendmsg and recvmsg)
+%% on an IPv4 TCP (stream) socket.
+api_b_sendmsg_and_recvmsg_tcp4(suite) ->
+ [];
+api_b_sendmsg_and_recvmsg_tcp4(doc) ->
+ [];
+api_b_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(10)),
+ tc_try(api_b_sendmsg_and_recvmsg_tcp4,
+ fun() ->
+ Send = fun(Sock, Data) ->
+ MsgHdr = #{iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock) of
+ {ok, #{addr := undefined,
+ iov := [Data]}} ->
+ {ok, Data};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState = #{domain => inet,
+ send => Send,
+ recv => Recv},
+ ok = api_b_send_and_recv_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_b_send_and_recv_tcp(InitState) ->
+ process_flag(trap_exit, true),
+ ServerSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain, addr => LAddr},
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{lsock := LSock, lsa := LSA} = State) ->
+ case socket:bind(LSock, LSA) of
+ {ok, Port} ->
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, lport := Port}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Port),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "await connection",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ ?SEV_IPRINT("accepted: ~n ~p", [Sock]),
+ {ok, State#{csock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+ #{desc => "await (recv) request",
+ cmd => fun(#{csock := Sock, recv := Recv}) ->
+ case Recv(Sock) of
+ {ok, ?BASIC_REQ} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_req),
+ ok
+ end},
+ #{desc => "await continue (with send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply)
+ end},
+ #{desc => "send reply",
+ cmd => fun(#{csock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REP)
+ end},
+ #{desc => "announce ready (send reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_reply),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close connection socket",
+ cmd => fun(#{csock := Sock}) ->
+ socket:close(Sock)
+ end},
+ #{desc => "close listen socket",
+ cmd => fun(#{lsock := Sock}) ->
+ socket:close(Sock)
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ ClientSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ {Tester, Port} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, server_port => Port}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** The init part ***
+ #{desc => "which server (local) address",
+ cmd => fun(#{domain := Domain, server_port := Port} = State) ->
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain,
+ addr => LAddr},
+ SSA = LSA#{port => Port},
+ {ok, State#{local_sa => LSA, server_sa => SSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, local_sa := LSA} = _State) ->
+ case socket:bind(Sock, LSA) of
+ {ok, _Port} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, connect)
+ end},
+ #{desc => "connect to server",
+ cmd => fun(#{sock := Sock, server_sa := SSA}) ->
+ socket:connect(Sock, SSA)
+ end},
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+ #{desc => "await continue (send request)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send_req)
+ end},
+ #{desc => "send request (to server)",
+ cmd => fun(#{sock := Sock, send := Send}) ->
+ Send(Sock, ?BASIC_REQ)
+ end},
+ #{desc => "announce ready (send request)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_req),
+ ok
+ end},
+ #{desc => "await recv reply (from server)",
+ cmd => fun(#{sock := Sock, recv := Recv}) ->
+ {ok, ?BASIC_REP} = Recv(Sock),
+ ok
+ end},
+ #{desc => "announce ready (recv reply)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_reply),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock}) ->
+ socket:close(Sock)
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_port => Port}}
+ end},
+
+ %% Start the client
+ #{desc => "order client start",
+ cmd => fun(#{client := Pid, server_port := Port} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Port),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, init)
+ end},
+
+ %% *** The actual test ***
+ #{desc => "order server to continue (with accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, accept),
+ ok
+ end},
+ #{desc => "sleep",
+ cmd => fun(_) ->
+ ?SLEEP(?SECS(1)),
+ ok
+ end},
+ #{desc => "order client to continue (with connect)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, connect),
+ ok
+ end},
+ #{desc => "await client ready (connect)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, connect)
+ end},
+ #{desc => "await server ready (accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, accept)
+ end},
+ #{desc => "order client to continue (with send request)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send_req),
+ ok
+ end},
+ #{desc => "await client ready (with send request)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, send_req)
+ end},
+ #{desc => "await server ready (request recv)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_req)
+ end},
+ #{desc => "order server to continue (with send reply)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, send_reply),
+ ok
+ end},
+ #{desc => "await server ready (with reply sent)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, send_reply)
+ end},
+ #{desc => "await client ready (reply recv)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, recv_reply)
+ end},
+
+
+ %% *** Termination ***
+ #{desc => "order client to terminate",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(client, State),
+ {ok, State1}
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Server} = State) ->
+ ?SEV_AWAIT_TERMINATION(Server),
+ State1 = maps:remove(server, State),
+ {ok, State1}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ Server = ?SEV_START("server", ServerSeq, InitState),
+
+ i("start client evaluator"),
+ Client = ?SEV_START("client", ClientSeq, InitState),
+ i("await evaluator(s)"),
+
+ i("start tester evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client => Client#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% %%
+%% API OPTIONS %%
+%% %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Perform some simple getopt and setopt with the level = otp options
+api_opt_simple_otp_options(suite) ->
+ [];
+api_opt_simple_otp_options(doc) ->
+ [];
+api_opt_simple_otp_options(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_simple_otp_options,
+ fun() -> api_opt_simple_otp_options() end).
+
+api_opt_simple_otp_options() ->
+ Get = fun(S, Key) ->
+ socket:getopt(S, otp, Key)
+ end,
+ Set = fun(S, Key, Val) ->
+ socket:setopt(S, otp, Key, Val)
+ end,
+
+ Seq =
+ [
+ %% *** Init part ***
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Protocol} = State) ->
+ Sock = sock_open(Domain, Type, Protocol),
+ {ok, State#{sock => Sock}}
+ end},
+ #{desc => "create dummy process",
+ cmd => fun(State) ->
+ Pid = spawn_link(fun() ->
+ put(sname, "dummy"),
+ receive
+ die ->
+ exit(normal)
+ end
+ end),
+ {ok, State#{dummy => Pid}}
+ end},
+
+ %% *** Check iow part ***
+ #{desc => "get iow",
+ cmd => fun(#{sock := Sock} = State) ->
+ case Get(Sock, iow) of
+ {ok, IOW} when is_boolean(IOW) ->
+ {ok, State#{iow => IOW}};
+ {ok, InvalidIOW} ->
+ {error, {invalid, InvalidIOW}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% #{desc => "enable debug",
+ %% cmd => fun(#{sock := Sock}) ->
+ %% ok = socket:setopt(Sock, otp, debug, true)
+ %% end},
+
+ #{desc => "set (new) iow",
+ cmd => fun(#{sock := Sock, iow := OldIOW} = State) ->
+ NewIOW = not OldIOW,
+ case Set(Sock, iow, NewIOW) of
+ ok ->
+ {ok, State#{iow => NewIOW}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "get (new) iow",
+ cmd => fun(#{sock := Sock, iow := IOW}) ->
+ case Get(Sock, iow) of
+ {ok, IOW} ->
+ ok;
+ {ok, InvalidIOW} ->
+ {error, {invalid, InvalidIOW}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** Check rcvbuf part ***
+ #{desc => "get rcvbuf",
+ cmd => fun(#{sock := Sock} = State) ->
+ case Get(Sock, rcvbuf) of
+ {ok, RcvBuf} when is_integer(RcvBuf) ->
+ {ok, State#{rcvbuf => RcvBuf}};
+ {ok, {N, RcvBuf} = V} when is_integer(N) andalso
+ is_integer(RcvBuf) ->
+ {ok, State#{rcvbuf => V}};
+ {ok, InvalidRcvBuf} ->
+ {error, {invalid, InvalidRcvBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "set (new) rcvbuf",
+ cmd => fun(#{sock := Sock, rcvbuf := {OldN, OldRcvBuf}} = State) ->
+ NewRcvBuf = {OldN+2, OldRcvBuf + 1024},
+ case Set(Sock, rcvbuf, NewRcvBuf) of
+ ok ->
+ {ok, State#{rcvbuf => NewRcvBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end;
+ (#{sock := Sock, rcvbuf := OldRcvBuf} = State) when is_integer(OldRcvBuf) ->
+ NewRcvBuf = 2 * OldRcvBuf,
+ case Set(Sock, rcvbuf, NewRcvBuf) of
+ ok ->
+ {ok, State#{rcvbuf => NewRcvBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end;
+ (#{sock := Sock, rcvbuf := OldRcvBuf,
+ type := stream,
+ protocol := tcp} = State) when is_integer(OldRcvBuf) ->
+ NewRcvBuf = {2, OldRcvBuf},
+ case Set(Sock, rcvbuf, NewRcvBuf) of
+ ok ->
+ {ok, State#{rcvbuf => NewRcvBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "get (new) rcvbuf",
+ cmd => fun(#{sock := Sock, rcvbuf := RcvBuf}) ->
+ case Get(Sock, rcvbuf) of
+ {ok, RcvBuf} ->
+ ok;
+ {ok, InvalidRcvBuf} ->
+ {error, {invalid, InvalidRcvBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** Check rcvctrlbuf part ***
+ #{desc => "get rcvctrlbuf",
+ cmd => fun(#{sock := Sock} = State) ->
+ case Get(Sock, rcvctrlbuf) of
+ {ok, RcvCtrlBuf} when is_integer(RcvCtrlBuf) ->
+ {ok, State#{rcvctrlbuf => RcvCtrlBuf}};
+ {ok, InvalidRcvCtrlBuf} ->
+ {error, {invalid, InvalidRcvCtrlBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "set (new) rcvctrlbuf",
+ cmd => fun(#{sock := Sock, rcvctrlbuf := OldRcvCtrlBuf} = State) ->
+ NewRcvCtrlBuf = 2 * OldRcvCtrlBuf,
+ case Set(Sock, rcvctrlbuf, NewRcvCtrlBuf) of
+ ok ->
+ {ok, State#{rcvctrlbuf => NewRcvCtrlBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "get (new) rcvctrlbuf",
+ cmd => fun(#{sock := Sock, rcvctrlbuf := RcvCtrlBuf}) ->
+ case Get(Sock, rcvctrlbuf) of
+ {ok, RcvCtrlBuf} ->
+ ok;
+ {ok, InvalidRcvCtrlBuf} ->
+ {error, {invalid, InvalidRcvCtrlBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ %% *** Check rcvctrlbuf part ***
+ #{desc => "get rcvctrlbuf",
+ cmd => fun(#{sock := Sock} = State) ->
+ case Get(Sock, rcvctrlbuf) of
+ {ok, RcvCtrlBuf} when is_integer(RcvCtrlBuf) ->
+ {ok, State#{rcvctrlbuf => RcvCtrlBuf}};
+ {ok, InvalidRcvCtrlBuf} ->
+ {error, {invalid, InvalidRcvCtrlBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "set (new) rcvctrlbuf",
+ cmd => fun(#{sock := Sock, rcvctrlbuf := OldRcvCtrlBuf} = State) ->
+ NewRcvCtrlBuf = 2 * OldRcvCtrlBuf,
+ case Set(Sock, rcvctrlbuf, NewRcvCtrlBuf) of
+ ok ->
+ {ok, State#{rcvctrlbuf => NewRcvCtrlBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "get (new) rcvctrlbuf",
+ cmd => fun(#{sock := Sock, rcvctrlbuf := RcvCtrlBuf}) ->
+ case Get(Sock, rcvctrlbuf) of
+ {ok, RcvCtrlBuf} ->
+ ok;
+ {ok, InvalidRcvCtrlBuf} ->
+ {error, {invalid, InvalidRcvCtrlBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% *** Check sndctrlbuf part ***
+ #{desc => "get sndctrlbuf",
+ cmd => fun(#{sock := Sock} = State) ->
+ case Get(Sock, sndctrlbuf) of
+ {ok, SndCtrlBuf} when is_integer(SndCtrlBuf) ->
+ {ok, State#{sndctrlbuf => SndCtrlBuf}};
+ {ok, InvalidSndCtrlBuf} ->
+ {error, {invalid, InvalidSndCtrlBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "set (new) sndctrlbuf",
+ cmd => fun(#{sock := Sock, sndctrlbuf := OldSndCtrlBuf} = State) ->
+ NewSndCtrlBuf = 2 * OldSndCtrlBuf,
+ case Set(Sock, sndctrlbuf, NewSndCtrlBuf) of
+ ok ->
+ {ok, State#{sndctrlbuf => NewSndCtrlBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "get (new) sndctrlbuf",
+ cmd => fun(#{sock := Sock, sndctrlbuf := SndCtrlBuf}) ->
+ case Get(Sock, sndctrlbuf) of
+ {ok, SndCtrlBuf} ->
+ ok;
+ {ok, InvalidSndCtrlBuf} ->
+ {error, {invalid, InvalidSndCtrlBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** Check controlling-process part ***
+ #{desc => "verify self as controlling-process",
+ cmd => fun(#{sock := Sock}) ->
+ Self = self(),
+ case Get(Sock, controlling_process) of
+ {ok, Self} ->
+ ok;
+ {ok, InvalidPid} ->
+ {error, {invalid, InvalidPid}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "set dummy as controlling-process",
+ cmd => fun(#{sock := Sock, dummy := Dummy}) ->
+ Set(Sock, controlling_process, Dummy)
+ end},
+ #{desc => "verify dummy as controlling-process",
+ cmd => fun(#{sock := Sock, dummy := Dummy}) ->
+ case Get(Sock, controlling_process) of
+ {ok, Dummy} ->
+ ok;
+ {ok, InvalidPid} ->
+ {error, {invalid, InvalidPid}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ #{desc => "finish",
+ cmd => fun(_) ->
+ {ok, normal}
+ end}
+ ],
+
+ i("start tcp (stream) evaluator"),
+ InitState1 = #{domain => inet, type => stream, protocol => tcp},
+ Tester1 = ?SEV_START("tcp-tester", Seq, InitState1),
+ i("await tcp evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Tester1]),
+
+ i("start udp (dgram) socket"),
+ InitState2 = #{domain => inet, type => dgram, protocol => udp},
+ Tester2 = ?SEV_START("udp-tester", Seq, InitState2),
+ i("await udp evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Tester2]).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Perform some simple operations with the rcvbuf otp option
+%% The operations we test here are only for type = stream and
+%% protocol = tcp.
+api_opt_simple_otp_rcvbuf_option(suite) ->
+ [];
+api_opt_simple_otp_rcvbuf_option(doc) ->
+ [];
+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).
+
+api_opt_simple_otp_rcvbuf_option() ->
+ Get = fun(S) ->
+ socket:getopt(S, otp, rcvbuf)
+ end,
+ Set = fun(S, Val) ->
+ socket:setopt(S, otp, rcvbuf, Val)
+ end,
+
+ ServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain, addr => LAddr},
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{lsock := LSock, local_sa := LSA} = State) ->
+ case socket:bind(LSock, LSA) of
+ {ok, Port} ->
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester,
+ local_sa := LocalSA,
+ lport := Port}) ->
+ ServerSA = LocalSA#{port => Port},
+ ?SEV_ANNOUNCE_READY(Tester, init, ServerSA),
+ ok
+ end},
+
+
+ %% *** The actual test part ***
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "attempt to accept",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+
+ %% Recv with default size for (otp) rcvbuf
+ #{desc => "await continue (recv initial)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester, recv) of
+ {ok, MsgSz} ->
+ ?SEV_IPRINT("MsgSz: ~p", [MsgSz]),
+ {ok, State#{msg_sz => MsgSz}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ 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;
+ {ok, Data} ->
+ {error, {invalid_msg_sz, MsgSz, size(Data)}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv initial)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv),
+ ok
+ end},
+
+ %% Recv with new size (1) for (otp) rcvbuf
+ #{desc => "await continue (recv 1)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester, recv) of
+ {ok, NewRcvBuf} ->
+ ?SEV_IPRINT("set new rcvbuf: ~p", [NewRcvBuf]),
+ {ok, State#{rcvbuf => NewRcvBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "attempt to setopt rcvbuf",
+ cmd => fun(#{sock := Sock, rcvbuf := NewRcvBuf} = _State) ->
+ case Set(Sock, NewRcvBuf) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "attempt to recv",
+ cmd => fun(#{sock := Sock, msg_sz := MsgSz} = _State) ->
+ case socket:recv(Sock) of
+ {ok, Data} when (size(Data) =:= MsgSz) ->
+ ok;
+ {ok, Data} ->
+ {error, {invalid_msg_sz, MsgSz, size(Data)}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv 1)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv),
+ ok
+ end},
+
+ %% Recv with new size (2) for (otp) rcvbuf
+ #{desc => "await continue (recv 2)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester, recv) of
+ {ok, NewRcvBuf} ->
+ ?SEV_IPRINT("set new rcvbuf: ~p", [NewRcvBuf]),
+ {ok, State#{rcvbuf => NewRcvBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "attempt to setopt rcvbuf",
+ cmd => fun(#{sock := Sock, rcvbuf := NewRcvBuf} = _State) ->
+ case Set(Sock, NewRcvBuf) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "attempt to recv",
+ cmd => fun(#{sock := Sock, msg_sz := MsgSz} = _State) ->
+ case socket:recv(Sock) of
+ {ok, Data} when (size(Data) =:= MsgSz) ->
+ ok;
+ {ok, Data} ->
+ {error, {invalid_msg_sz, MsgSz, size(Data)}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv 2)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv),
+ ok
+ end},
+
+ %% Recv with new size (3) for (otp) rcvbuf
+ #{desc => "await continue (recv 3, truncated)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester, recv) of
+ {ok, {ExpSz, NewRcvBuf}} ->
+ {ok, State#{msg_sz => ExpSz,
+ rcvbuf => NewRcvBuf}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "attempt to setopt rcvbuf",
+ cmd => fun(#{sock := Sock, rcvbuf := NewRcvBuf} = _State) ->
+ case Set(Sock, NewRcvBuf) of
+ ok ->
+ ?SEV_IPRINT("set new rcvbuf: ~p", [NewRcvBuf]),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "attempt to recv",
+ cmd => fun(#{sock := Sock, msg_sz := MsgSz} = _State) ->
+ ?SEV_IPRINT("try recv ~w bytes of data", [MsgSz]),
+ case socket:recv(Sock) of
+ {ok, Data} when (size(Data) =:= MsgSz) ->
+ ok;
+ {ok, Data} ->
+ {error, {invalid_msg_sz, MsgSz, size(Data)}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv),
+ ok
+ end},
+
+
+ %% Termination
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket(s)",
+ cmd => fun(#{lsock := LSock, sock := Sock} = State) ->
+ sock_close(Sock),
+ sock_close(LSock),
+ State1 = maps:remove(sock, State),
+ State2 = maps:remove(lport, State1),
+ State3 = maps:remove(lsock, State2),
+ {ok, State3}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, ServerSA} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ server_sa => ServerSA}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain, addr => LAddr},
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, local_sa := LSA} = _State) ->
+ case socket:bind(Sock, LSA) of
+ {ok, _Port} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, connect)
+ end},
+ #{desc => "connect to server",
+ cmd => fun(#{sock := Sock, server_sa := SSA}) ->
+ socket:connect(Sock, SSA)
+ end},
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+
+ #{desc => "await continue (send initial)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester, send) of
+ {ok, Data} ->
+ {ok, State#{data => Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "send (initial) data to server",
+ cmd => fun(#{sock := Sock, data := Data} = _State) ->
+ ?SEV_IPRINT("try send ~w bytes", [size(Data)]),
+ socket:send(Sock, Data)
+ end},
+ #{desc => "announce ready (send initial)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send),
+ ok
+ end},
+
+ #{desc => "await continue (send 1)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send)
+ end},
+ #{desc => "send (1) data to server",
+ cmd => fun(#{sock := Sock, data := Data}) ->
+ ?SEV_IPRINT("try send ~w bytes", [size(Data)]),
+ socket:send(Sock, Data)
+ end},
+ #{desc => "announce ready (send 1)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send),
+ ok
+ end},
+
+ #{desc => "await continue (send 2)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send)
+ end},
+ #{desc => "send (2) data to server",
+ cmd => fun(#{sock := Sock, data := Data}) ->
+ ?SEV_IPRINT("try send ~w bytes", [size(Data)]),
+ socket:send(Sock, Data)
+ end},
+ #{desc => "announce ready (send 2)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send),
+ ok
+ end},
+
+ #{desc => "await continue (send 3)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, send)
+ end},
+ #{desc => "send (3) data to server",
+ cmd => fun(#{sock := Sock, data := Data}) ->
+ ?SEV_IPRINT("try send ~w bytes", [size(Data)]),
+ socket:send(Sock, Data)
+ end},
+ #{desc => "announce ready (send 3)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send),
+ ok
+ end},
+
+
+ %% Termination
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock}) ->
+ socket:close(Sock)
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Server} = _State) ->
+ _MRef = erlang:monitor(process, Server),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Client} = _State) ->
+ _MRef = erlang:monitor(process, Client),
+ ok
+ end},
+ #{desc => "order server start",
+ cmd => fun(#{server := Server}) ->
+ ?SEV_ANNOUNCE_START(Server)
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Server} = State) ->
+ {ok, ServerSA} = ?SEV_AWAIT_READY(Server, server, init),
+ {ok, State#{server_sa => ServerSA}}
+ end},
+ #{desc => "order client start",
+ cmd => fun(#{client := Client,
+ server_sa := ServerSA}) ->
+ ?SEV_ANNOUNCE_START(Client, ServerSA),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, init)
+ end},
+
+
+ %% The actual test (connecting)
+ #{desc => "order server accept (accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, accept),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client continue (connect)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, connect),
+ ok
+ end},
+ #{desc => "await client ready (connect)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, connect)
+ end},
+ #{desc => "await server ready (accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, accept)
+ end},
+
+ %% The actual test (initial part)
+ #{desc => "order client continue (send initial)",
+ cmd => fun(#{client := Client, data := Data} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, Data),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order server continue (recv initial)",
+ cmd => fun(#{server := Server, data := Data} = _State) ->
+ ExpMsgSz = size(Data),
+ ?SEV_ANNOUNCE_CONTINUE(Server, recv, ExpMsgSz),
+ ok
+ end},
+ #{desc => "await client ready (send initial)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, client, send,
+ [{server, Server}]) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await server ready (recv initial)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Server, client, recv,
+ [{client, Client}]) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% The actual test (part 1)
+ #{desc => "order client continue (send 1)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order server continue (recv 1)",
+ cmd => fun(#{server := Server, data := Data} = _State) ->
+ MsgSz = size(Data),
+ NewRcvBuf = {2 + (MsgSz div 1024), 1024},
+ ?SEV_ANNOUNCE_CONTINUE(Server, recv, NewRcvBuf),
+ ok
+ end},
+ #{desc => "await client ready (send 1)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, client, send,
+ [{server, Server}]) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await server ready (recv 1)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Server, client, recv,
+ [{client, Client}]) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% The actual test (part 2)
+ #{desc => "order client continue (send 2)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order server continue (recv 2)",
+ cmd => fun(#{server := Server, data := Data} = _State) ->
+ MsgSz = size(Data),
+ NewRcvBuf = {2 + (MsgSz div 2048), 2048},
+ ?SEV_ANNOUNCE_CONTINUE(Server, recv, NewRcvBuf),
+ ok
+ end},
+ #{desc => "await client ready (send 2)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, client, send,
+ [{server, Server}]) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await server ready (recv 2)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Server, client, recv,
+ [{client, Client}]) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% The actual test (part 3)
+ #{desc => "order client continue (send 3)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send),
+ ok
+ end},
+ ?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},
+ ?SEV_ANNOUNCE_CONTINUE(Server, recv,
+ {N*BufSz, NewRcvBuf})
+ end},
+ #{desc => "await client ready (send 3)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, client, send,
+ [{server, Server}]) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await server ready (recv 3)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Server, client, recv,
+ [{client, Client}]) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ %% *** Terminate server ***
+ #{desc => "order client terminate",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client down",
+ cmd => fun(#{client := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(client, State),
+ {ok, State1}
+ end},
+ #{desc => "order server terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server down",
+ cmd => fun(#{server := Server} = State) ->
+ ?SEV_AWAIT_TERMINATION(Server),
+ State1 = maps:remove(server, State),
+ State2 = maps:remove(server_sa, State1),
+ {ok, State2}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ %% Create a data binary of 6*1024 bytes
+ Data = list_to_binary(lists:duplicate(6*4, lists:seq(0, 255))),
+ InitState = #{domain => inet,
+ data => Data},
+
+ i("create server evaluator"),
+ ServerInitState = #{domain => maps:get(domain, InitState)},
+ Server = ?SEV_START("server", ServerSeq, ServerInitState),
+
+ i("create client evaluator"),
+ ClientInitState = #{host => local_host(),
+ domain => maps:get(domain, InitState)},
+ Client = ?SEV_START("client", ClientSeq, ClientInitState),
+
+ i("create tester evaluator"),
+ TesterInitState = InitState#{server => Server#ev.pid,
+ client => Client#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Perform some simple getopt and setopt with the level = otp options
+api_opt_simple_otp_controlling_process(suite) ->
+ [];
+api_opt_simple_otp_controlling_process(doc) ->
+ [];
+api_opt_simple_otp_controlling_process(_Config) when is_list(_Config) ->
+ ?TT(?SECS(5)),
+ tc_try(api_opt_simple_otp_controlling_process,
+ fun() -> api_opt_simple_otp_controlling_process() end).
+
+api_opt_simple_otp_controlling_process() ->
+ Get = fun(S, Key) ->
+ socket:getopt(S, otp, Key)
+ end,
+ Set = fun(S, Key, Val) ->
+ socket:setopt(S, otp, Key, Val)
+ end,
+
+ ClientSeq =
+ [
+ %% *** Init part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, Sock} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ sock => Sock}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "verify tester as controlling-process",
+ cmd => fun(#{tester := Tester, sock := Sock} = _State) ->
+ case Get(Sock, controlling_process) of
+ {ok, Tester} ->
+ ok;
+ {ok, InvalidPid} ->
+ {error, {invalid, InvalidPid}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "attempt invalid controlling-process transfer (to self)",
+ cmd => fun(#{sock := Sock} = _State) ->
+ case Set(Sock, controlling_process, self()) of
+ {error, not_owner} ->
+ ok;
+ ok ->
+ {error, unexpected_success};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (not owner)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, not_owner),
+ ok
+ end},
+ #{desc => "await continue (owner)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, owner)
+ end},
+ #{desc => "verify self as controlling-process",
+ cmd => fun(#{sock := Sock} = _State) ->
+ Self = self(),
+ case Get(Sock, controlling_process) of
+ {ok, Self} ->
+ ok;
+ {ok, InvalidPid} ->
+ {error, {invalid, InvalidPid}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "attempt controlling-process transfer to tester",
+ cmd => fun(#{tester := Tester, sock := Sock} = _State) ->
+ Set(Sock, controlling_process, Tester)
+ end},
+ #{desc => "attempt invalid controlling-process transfer (to self)",
+ cmd => fun(#{sock := Sock} = _State) ->
+ case Set(Sock, controlling_process, self()) of
+ {error, not_owner} ->
+ ok;
+ ok ->
+ {error, unexpected_success};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (owner)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, owner),
+ ok
+
+ end},
+
+ %% *** Termination ***
+ #{desc => "await termination",
+ cmd => fun(#{tester := Tester} = State) ->
+ ?SEV_AWAIT_TERMINATE(Tester, tester),
+ State1 = maps:remove(tester, State),
+ State2 = maps:remove(sock, State1),
+ {ok, State2}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Protocol} = State) ->
+ Sock = sock_open(Domain, Type, Protocol),
+ {ok, State#{sock => Sock}}
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Client} = _State) ->
+ _MRef = erlang:monitor(process, Client),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "verify self as controlling-process",
+ cmd => fun(#{sock := Sock} = _State) ->
+ Self = self(),
+ case Get(Sock, controlling_process) of
+ {ok, Self} ->
+ ok;
+ {ok, InvalidPid} ->
+ {error, {invalid, InvalidPid}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order (client) start",
+ cmd => fun(#{client := Client, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_START(Client, Sock),
+ ok
+ end},
+ #{desc => "await (client) ready (not owner)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, not_owner)
+ end},
+ #{desc => "attempt controlling-process transfer to client",
+ cmd => fun(#{client := Client, sock := Sock} = _State) ->
+ Set(Sock, controlling_process, Client)
+ end},
+ #{desc => "verify client as controlling-process",
+ cmd => fun(#{client := Client, sock := Sock} = _State) ->
+ case Get(Sock, controlling_process) of
+ {ok, Client} ->
+ ok;
+ {ok, InvalidPid} ->
+ {error, {invalid, InvalidPid}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "attempt invalid controlling-process transfer (to self)",
+ cmd => fun(#{sock := Sock} = _State) ->
+ case Set(Sock, controlling_process, self()) of
+ {error, not_owner} ->
+ ok;
+ ok ->
+ {error, unexpected_success};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order (client) continue (owner)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, owner),
+ ok
+ end},
+ #{desc => "await (client) ready (2)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, owner),
+ ok
+ end},
+ #{desc => "verify self as controlling-process",
+ cmd => fun(#{sock := Sock} = _State) ->
+ Self = self(),
+ case Get(Sock, controlling_process) of
+ {ok, Self} ->
+ ok;
+ {ok, InvalidPid} ->
+ {error, {invalid, InvalidPid}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** Termination ***
+ #{desc => "order (client) terminate",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ {ok, maps:remove(client, State)}
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ sock_close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start tcp (stream) client evaluator"),
+ ClientInitState1 = #{},
+ Client1 = ?SEV_START("tcp-client", ClientSeq, ClientInitState1),
+
+ i("start tcp (stream) tester evaluator"),
+ TesterInitState1 = #{domain => inet,
+ type => stream,
+ protocol => tcp,
+ client => Client1#ev.pid},
+ Tester1 = ?SEV_START("tcp-tester", TesterSeq, TesterInitState1),
+
+ i("await tcp evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Tester1, Client1]),
+
+ i("start udp (dgram) client evaluator"),
+ ClientInitState2 = #{},
+ Client2 = ?SEV_START("udp-client", ClientSeq, ClientInitState2),
+
+ i("start udp (dgram) tester evaluator"),
+ TesterInitState2 = #{domain => inet,
+ type => dgram,
+ protocol => udp,
+ client => Client2#ev.pid},
+ Tester2 = ?SEV_START("udp-tester", TesterSeq, TesterInitState2),
+
+ i("await udp evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Tester2, Client2]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% %%
+%% API OPERATIONS WITH TIMEOUT %%
+%% %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the connect timeout option
+%% on an IPv4 TCP (stream) socket.
+api_to_connect_tcp4(suite) ->
+ [];
+api_to_connect_tcp4(doc) ->
+ [];
+api_to_connect_tcp4(_Config) when is_list(_Config) ->
+ tc_try(api_to_connect_tcp4,
+ fun() ->
+ ?TT(?SECS(10)),
+ InitState = #{domain => inet,
+ backlog => 1,
+ timeout => 5000,
+ connect_limit => 3},
+ ok = api_to_connect_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the connect timeout option
+%% on an IPv6 TCP (stream) socket.
+api_to_connect_tcp6(suite) ->
+ [];
+api_to_connect_tcp6(doc) ->
+ [];
+api_to_connect_tcp6(_Config) when is_list(_Config) ->
+ tc_try(api_to_connect_tcp6,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(10)),
+ InitState = #{domain => inet6,
+ backlog => 1,
+ timeout => 5000,
+ connect_limit => 3},
+ ok = api_to_connect_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% We use the backlog (listen) argument to test this.
+%% Note that the behaviour of the TCP "server side" can vary when
+%% a client connect to a "busy" server (full backlog).
+%% For instance, on FreeBSD (11.2) the reponse when the backlog is full
+%% is a econreset.
+
+api_to_connect_tcp(InitState) ->
+ process_flag(trap_exit, true),
+
+ ServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ {Tester, Backlog} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ backlog => Backlog}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain, addr => LAddr},
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{lsock := LSock, local_sa := LSA} = State) ->
+ case socket:bind(LSock, LSA) of
+ {ok, Port} ->
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket (with backlog = 1)",
+ cmd => fun(#{lsock := LSock, backlog := Backlog}) ->
+ socket:listen(LSock, Backlog)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, lport := Port}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Port),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{lsock := Sock} = State) ->
+ sock_close(Sock),
+ State1 = maps:remove(lport, State),
+ State2 = maps:remove(sock, State1),
+ {ok, State2}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, ServerSA} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ server_sa => ServerSA}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain, addr => LAddr},
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create node",
+ cmd => fun(#{host := Host} = State) ->
+ ?SEV_IPRINT("try create node on ~p", [Host]),
+ case start_node(Host, client) of
+ {ok, Node} ->
+ ?SEV_IPRINT("client node ~p started",
+ [Node]),
+ {ok, State#{node => Node}};
+ {error, Reason, _} ->
+ {error, Reason}
+ end
+ end},
+ #{desc => "monitor client node",
+ cmd => fun(#{node := Node} = _State) ->
+ true = erlang:monitor_node(Node, true),
+ ok
+ end},
+ #{desc => "start remote client on client node",
+ cmd => fun(#{node := Node} = State) ->
+ Pid = api_toc_tcp_client_start(Node),
+ ?SEV_IPRINT("remote client ~p started", [Pid]),
+ {ok, State#{rclient => Pid}}
+ end},
+ #{desc => "monitor remote client",
+ cmd => fun(#{rclient := Pid}) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order remote client to start",
+ cmd => fun(#{rclient := Client,
+ server_sa := ServerSA}) ->
+ ?SEV_ANNOUNCE_START(Client, ServerSA),
+ ok
+ end},
+ #{desc => "await remote client ready",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, init,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester, connect,
+ [{rclient, Client}]) of
+ {ok, {ConTimeout, ConLimit}} ->
+ {ok, State#{connect_timeout => ConTimeout,
+ connect_limit => ConLimit}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to continue (connect)",
+ cmd => fun(#{rclient := RClient,
+ connect_timeout := ConTimeout,
+ connect_limit := ConLimit}) ->
+ ?SEV_ANNOUNCE_CONTINUE(RClient, connect,
+ {ConTimeout, ConLimit}),
+ ok
+ end},
+ #{desc => "await remote client ready (connect)",
+ cmd => fun(#{tester := Tester,
+ rclient := RClient} = State) ->
+ case ?SEV_AWAIT_READY(RClient, rclient, connect,
+ [{tester, Tester}]) of
+ {ok, ok = _Result} ->
+ {ok, maps:remove(connect_limit, State)};
+ {ok, {error, {connect_limit_reached,R,L}}} ->
+ {skip,
+ ?LIB:f("Connect limit reached ~w: ~w",
+ [L, R])};
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester,
+ rclient := RClient} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester,
+ [{rclient, RClient}]) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "kill remote client",
+ cmd => fun(#{rclient := Client}) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await remote client termination",
+ cmd => fun(#{rclient := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(rclient, State),
+ {ok, State1}
+ end},
+ #{desc => "stop client node",
+ cmd => fun(#{node := Node} = _State) ->
+ stop_node(Node)
+ end},
+ #{desc => "await client node termination",
+ cmd => fun(#{node := Node} = State) ->
+ receive
+ {nodedown, Node} ->
+ State1 = maps:remove(node_id, State),
+ State2 = maps:remove(node, State1),
+ {ok, State2}
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Server} = _State) ->
+ _MRef = erlang:monitor(process, Server),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Client} = _State) ->
+ _MRef = erlang:monitor(process, Client),
+ ok
+ end},
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain, addr => LAddr},
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "order server start",
+ cmd => fun(#{server := Server,
+ backlog := Backlog}) ->
+ ?SEV_ANNOUNCE_START(Server, Backlog),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Server, local_sa := LSA} = State) ->
+ {ok, Port} = ?SEV_AWAIT_READY(Server, server, init),
+ ServerSA = LSA#{port => Port},
+ {ok, State#{server_sa => ServerSA}}
+ end},
+ #{desc => "order client start",
+ cmd => fun(#{client := Client,
+ server_sa := ServerSA}) ->
+ ?SEV_ANNOUNCE_START(Client, ServerSA),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, init),
+ ok
+ end},
+
+ %% The actual test
+ %% The server does nothing (this is the point), no accept,
+ %% the client tries to connect.
+ #{desc => "order client continue (connect)",
+ cmd => fun(#{client := Client,
+ timeout := Timeout,
+ connect_limit := ConLimit} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, connect,
+ {Timeout, ConLimit}),
+ ok
+ end},
+ #{desc => "await client ready (connect)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, client, connect,
+ [{server, Server}]) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% *** Terminate server ***
+ #{desc => "order client terminate",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client down",
+ cmd => fun(#{client := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(client, State),
+ {ok, State1}
+ end},
+ #{desc => "order server terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server down",
+ cmd => fun(#{server := Server} = State) ->
+ ?SEV_AWAIT_TERMINATION(Server),
+ State1 = maps:remove(server, State),
+ State2 = maps:remove(server_sa, State1),
+ {ok, State2}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("create server evaluator"),
+ ServerInitState = #{domain => maps:get(domain, InitState)},
+ Server = ?SEV_START("server", ServerSeq, ServerInitState),
+
+ i("create client evaluator"),
+ ClientInitState = #{host => local_host(),
+ domain => maps:get(domain, InitState)},
+ Client = ?SEV_START("client", ClientSeq, ClientInitState),
+
+ i("create tester evaluator"),
+ TesterInitState = InitState#{server => Server#ev.pid,
+ client => Client#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+api_toc_tcp_client_start(Node) ->
+ Self = self(),
+ Fun = fun() -> api_toc_tcp_client(Self) end,
+ erlang:spawn(Node, Fun).
+
+api_toc_tcp_client(Parent) ->
+ api_toc_tcp_client_init(Parent),
+ ServerSA = api_toc_tcp_client_await_start(Parent),
+ Domain = maps:get(family, ServerSA),
+ api_toc_tcp_client_announce_ready(Parent, init),
+ {To, ConLimit} = api_toc_tcp_client_await_continue(Parent, connect),
+ Result = api_to_connect_tcp_await_timeout(To, ServerSA, Domain, ConLimit),
+ ?SEV_IPRINT("result: ~p", [Result]),
+ api_toc_tcp_client_announce_ready(Parent, connect, Result),
+ Reason = api_toc_tcp_client_await_terminate(Parent),
+ exit(Reason).
+
+api_toc_tcp_client_init(Parent) ->
+ put(sname, "rclient"),
+ %% i("api_toc_tcp_client_init -> entry"),
+ _MRef = erlang:monitor(process, Parent),
+ ok.
+
+api_toc_tcp_client_await_start(Parent) ->
+ %% i("api_toc_tcp_client_await_start -> entry"),
+ ?SEV_AWAIT_START(Parent).
+
+api_toc_tcp_client_announce_ready(Parent, Slogan) ->
+ ?SEV_ANNOUNCE_READY(Parent, Slogan).
+api_toc_tcp_client_announce_ready(Parent, Slogan, Result) ->
+ ?SEV_ANNOUNCE_READY(Parent, Slogan, Result).
+
+api_toc_tcp_client_await_continue(Parent, Slogan) ->
+ %% i("api_toc_tcp_client_await_continue -> entry"),
+ case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of
+ ok ->
+ ok;
+ {ok, Extra} ->
+ Extra;
+ {error, Reason} ->
+ exit({await_continue, Slogan, Reason})
+ end.
+
+api_toc_tcp_client_await_terminate(Parent) ->
+ %% i("api_toc_tcp_client_await_terminate -> entry"),
+ case ?SEV_AWAIT_TERMINATE(Parent, parent) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ Reason
+ end.
+
+api_to_connect_tcp_await_timeout(To, ServerSA, Domain, ConLimit) ->
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain,
+ addr => LAddr},
+ NewSock = fun() ->
+ S = case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ Sock;
+ {error, OReason} ->
+ ?FAIL({open, OReason})
+ end,
+ case socket:bind(S, LSA) of
+ {ok, _} ->
+ S;
+ {error, BReason} ->
+ ?FAIL({bind, BReason})
+ end
+ end,
+ api_to_connect_tcp_await_timeout(1, ConLimit, To, ServerSA, NewSock, []).
+
+api_to_connect_tcp_await_timeout(ID, ConLimit, _To, _ServerSA, _NewSock, Acc)
+ when (ID > ConLimit) ->
+ api_to_connect_tcp_await_timeout3(Acc),
+ {error, {connect_limit_reached, ID, ConLimit}};
+api_to_connect_tcp_await_timeout(ID, ConLimit, To, ServerSA, NewSock, Acc) ->
+ case api_to_connect_tcp_await_timeout2(ID, To, ServerSA, NewSock) of
+ ok ->
+ %% ?SEV_IPRINT("success when number of socks: ~w", [length(Acc)]),
+ api_to_connect_tcp_await_timeout3(Acc),
+ ok;
+ {ok, Sock} ->
+ %% ?SEV_IPRINT("~w: unexpected success (connect)", [ID]),
+ api_to_connect_tcp_await_timeout(ID+1, ConLimit,
+ To, ServerSA, NewSock,
+ [Sock|Acc]);
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+api_to_connect_tcp_await_timeout2(_ID, To, ServerSA, NewSock) ->
+ Sock = NewSock(),
+ %% ?SEV_IPRINT("~w: try connect", [ID]),
+ Start = t(),
+ case socket:connect(Sock, ServerSA, To) of
+ {error, timeout} ->
+ Stop = t(),
+ TDiff = tdiff(Start, Stop),
+ if
+ (TDiff >= To) ->
+ (catch socket:close(Sock)),
+ ok;
+ true ->
+ (catch socket:close(Sock)),
+ ?FAIL({unexpected_timeout, TDiff, To})
+ end;
+ {error, econnreset = _Reason} ->
+ (catch socket:close(Sock)),
+ ok;
+ {error, Reason} ->
+ (catch socket:close(Sock)),
+ ?FAIL({connect, Reason});
+ ok ->
+ {ok, Sock}
+ end.
+
+api_to_connect_tcp_await_timeout3([]) ->
+ ok;
+api_to_connect_tcp_await_timeout3([Sock|Socka]) ->
+ (catch socket:close(Sock)),
+ api_to_connect_tcp_await_timeout3(Socka).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the accept timeout option
+%% on an IPv4 TCP (stream) socket.
+api_to_accept_tcp4(suite) ->
+ [];
+api_to_accept_tcp4(doc) ->
+ [];
+api_to_accept_tcp4(_Config) when is_list(_Config) ->
+ tc_try(api_to_accept_tcp4,
+ fun() ->
+ ?TT(?SECS(10)),
+ InitState = #{domain => inet, timeout => 5000},
+ ok = api_to_accept_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the accept timeout option
+%% on an IPv6 TCP (stream) socket.
+api_to_accept_tcp6(suite) ->
+ [];
+api_to_accept_tcp6(doc) ->
+ [];
+api_to_accept_tcp6(_Config) when is_list(_Config) ->
+ tc_try(api_to_accept_tcp4,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(10)),
+ InitState = #{domain => inet6, timeout => 5000},
+ ok = api_to_accept_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_to_accept_tcp(InitState) ->
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain, addr => LAddr},
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create (listen) socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{lsock := LSock, lsa := LSA} = _State) ->
+ case socket:bind(LSock, LSA) of
+ {ok, _} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+
+ %% *** The actual test part ***
+ #{desc => "attempt to accept (without success)",
+ cmd => fun(#{lsock := LSock, timeout := To} = State) ->
+ Start = t(),
+ case socket:accept(LSock, To) of
+ {error, timeout} ->
+ {ok, State#{start => Start, stop => t()}};
+ {ok, Sock} ->
+ (catch socket:close(Sock)),
+ {error, unexpected_success};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "validate timeout time",
+ cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) ->
+ TDiff = tdiff(Start, Stop),
+ if
+ (TDiff >= To) ->
+ ok;
+ true ->
+ {error, {unexpected_timeout, TDiff, To}}
+ end
+ end},
+
+ %% *** Close (listen) socket ***
+ #{desc => "close (listen) socket",
+ cmd => fun(#{lsock := LSock} = State) ->
+ sock_close(LSock),
+ {ok, maps:remove(sock3, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("create tester evaluator"),
+ Tester = ?SEV_START("tester", TesterSeq, InitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the multi accept timeout option
+%% on an IPv4 TCP (stream) socket with multiple acceptor processes
+%% (three in this case).
+api_to_maccept_tcp4(suite) ->
+ [];
+api_to_maccept_tcp4(doc) ->
+ [];
+api_to_maccept_tcp4(_Config) when is_list(_Config) ->
+ ?TT(?SECS(20)),
+ tc_try(api_to_maccept_tcp4,
+ fun() ->
+ InitState = #{domain => inet, timeout => 5000},
+ ok = api_to_maccept_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the accept timeout option
+%% on an IPv6 TCP (stream) socket.
+api_to_maccept_tcp6(suite) ->
+ [];
+api_to_maccept_tcp6(doc) ->
+ [];
+api_to_maccept_tcp6(_Config) when is_list(_Config) ->
+ ?TT(?SECS(20)),
+ tc_try(api_to_maccept_tcp4,
+ fun() ->
+ not_yet_implemented(),
+ InitState = #{domain => inet6, timeout => 5000},
+ ok = api_to_maccept_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_to_maccept_tcp(InitState) ->
+ PrimAcceptorSeq =
+ [
+ %% *** Init part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain, addr => LAddr},
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create (listen) socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{lsock := LSock, lsa := LSA} = _State) ->
+ case socket:bind(LSock, LSA) of
+ {ok, _} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{lsock := LSock, tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, LSock),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "attempt to accept (without success)",
+ cmd => fun(#{lsock := LSock, timeout := To} = State) ->
+ Start = t(),
+ case socket:accept(LSock, To) of
+ {error, timeout} ->
+ {ok, State#{start => Start, stop => t()}};
+ {ok, Sock} ->
+ ?SEV_EPRINT("Unexpected accept success: "
+ "~n ~p", [Sock]),
+ (catch socket:close(Sock)),
+ {error, unexpected_success};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "validate timeout time",
+ cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) ->
+ TDiff = tdiff(Start, Stop),
+ if
+ (TDiff >= To) ->
+ ok;
+ true ->
+ {error, {unexpected_timeout, TDiff, To}}
+ end
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+
+ %% *** Terminate ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_TERMINATE(Tester, tester),
+ ok
+ end},
+ %% *** Close (listen) socket ***
+ #{desc => "close (listen) socket",
+ cmd => fun(#{lsock := LSock} = State) ->
+ sock_close(LSock),
+ {ok, maps:remove(lsock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ SecAcceptorSeq =
+ [
+ %% *** Init part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, LSock} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ lsock => LSock}}
+
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% *** The actual test part ***
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "attempt to accept (without success)",
+ cmd => fun(#{lsock := LSock, timeout := To} = State) ->
+ Start = t(),
+ case socket:accept(LSock, To) of
+ {error, timeout} ->
+ {ok, State#{start => Start, stop => t()}};
+ {ok, Sock} ->
+ (catch socket:close(Sock)),
+ {error, unexpected_success};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "validate timeout time",
+ cmd => fun(#{start := Start, stop := Stop, timeout := To} = State) ->
+ TDiff = tdiff(Start, Stop),
+ if
+ (TDiff >= To) ->
+ State1 = maps:remove(start, State),
+ State2 = maps:remove(stop, State1),
+ {ok, State2};
+ true ->
+ {error, {unexpected_timeout, TDiff, To}}
+ end
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+
+ %% *** Terminate ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ TesterSeq =
+ [
+ %% Init part
+ #{desc => "monitor prim-acceptor",
+ cmd => fun(#{prim_acceptor := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor sec-acceptor 1",
+ cmd => fun(#{sec_acceptor1 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor sec-acceptor 2",
+ cmd => fun(#{sec_acceptor2 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+
+ %% Start the prim-acceptor
+ #{desc => "start prim-acceptor",
+ cmd => fun(#{prim_acceptor := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await prim-acceptor ready (init)",
+ cmd => fun(#{prim_acceptor := Pid} = State) ->
+ {ok, Sock} = ?SEV_AWAIT_READY(Pid, prim_acceptor, init),
+ {ok, State#{lsock => Sock}}
+ end},
+
+ %% Start sec-acceptor-1
+ #{desc => "start sec-acceptor 1",
+ cmd => fun(#{sec_acceptor1 := Pid, lsock := LSock} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, LSock),
+ ok
+ end},
+ #{desc => "await sec-acceptor 1 ready (init)",
+ cmd => fun(#{sec_acceptor1 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, sec_acceptor1, init)
+ end},
+
+ %% Start sec-acceptor-2
+ #{desc => "start sec-acceptor 2",
+ cmd => fun(#{sec_acceptor2 := Pid, lsock := LSock} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, LSock),
+ ok
+ end},
+ #{desc => "await sec-acceptor 2 ready (init)",
+ cmd => fun(#{sec_acceptor2 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, sec_acceptor2, init)
+ end},
+
+ %% Activate the acceptor(s)
+ #{desc => "active prim-acceptor",
+ cmd => fun(#{prim_acceptor := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept),
+ ok
+ end},
+ #{desc => "active sec-acceptor 1",
+ cmd => fun(#{sec_acceptor1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept),
+ ok
+ end},
+ #{desc => "active sec-acceptor 2",
+ cmd => fun(#{sec_acceptor2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept),
+ ok
+ end},
+
+ %% Await acceptor(s) completions
+ #{desc => "await prim-acceptor ready (accept)",
+ cmd => fun(#{prim_acceptor := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, prim_acceptor, accept)
+ end},
+ #{desc => "await sec-acceptor 1 ready (accept)",
+ cmd => fun(#{sec_acceptor1 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, sec_acceptor1, accept)
+ end},
+ #{desc => "await sec-acceptor 2 ready (accept)",
+ cmd => fun(#{sec_acceptor2 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, sec_acceptor2, accept)
+ end},
+
+ %% Terminate
+ #{desc => "order prim-acceptor to terminate",
+ cmd => fun(#{prim_acceptor := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await prim-acceptor termination",
+ cmd => fun(#{prim_acceptor := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(prim_acceptor, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order sec-acceptor 1 to terminate",
+ cmd => fun(#{sec_acceptor1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await sec-acceptor 1 termination",
+ cmd => fun(#{sec_acceptor1 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(sec_acceptor1, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order sec-acceptor 2 to terminate",
+ cmd => fun(#{sec_acceptor2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await sec-acceptor 2 termination",
+ cmd => fun(#{sec_acceptor2 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(sec_acceptor2, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("create prim-acceptor evaluator"),
+ PrimAInitState = InitState,
+ PrimAcceptor = ?SEV_START("prim-acceptor", PrimAcceptorSeq, PrimAInitState),
+
+ i("create sec-acceptor 1 evaluator"),
+ SecAInitState1 = maps:remove(domain, InitState),
+ SecAcceptor1 = ?SEV_START("sec-acceptor-1", SecAcceptorSeq, SecAInitState1),
+
+ i("create sec-acceptor 2 evaluator"),
+ SecAInitState2 = SecAInitState1,
+ SecAcceptor2 = ?SEV_START("sec-acceptor-2", SecAcceptorSeq, SecAInitState2),
+
+ i("create tester evaluator"),
+ TesterInitState = #{prim_acceptor => PrimAcceptor#ev.pid,
+ sec_acceptor1 => SecAcceptor1#ev.pid,
+ sec_acceptor2 => SecAcceptor2#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([PrimAcceptor, SecAcceptor1, SecAcceptor2, Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the send timeout option
+%% on an IPv4 TCP (stream) socket.
+api_to_send_tcp4(suite) ->
+ [];
+api_to_send_tcp4(doc) ->
+ [];
+api_to_send_tcp4(_Config) when is_list(_Config) ->
+ tc_try(api_to_send_tcp4,
+ fun() ->
+ not_yet_implemented()%% ,
+ %% ok = api_to_send_tcp(inet)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the send timeout option
+%% on an IPv6 TCP (stream) socket.
+api_to_send_tcp6(suite) ->
+ [];
+api_to_send_tcp6(doc) ->
+ [];
+api_to_send_tcp6(_Config) when is_list(_Config) ->
+ tc_try(api_to_send_tcp6,
+ fun() ->
+ not_yet_implemented()%% ,
+ %% ok = api_to_send_tcp(inet6)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the sendto timeout option
+%% on an IPv4 UDP (dgram) socket.
+api_to_sendto_udp4(suite) ->
+ [];
+api_to_sendto_udp4(doc) ->
+ [];
+api_to_sendto_udp4(_Config) when is_list(_Config) ->
+ tc_try(api_to_sendto_udp4,
+ fun() ->
+ not_yet_implemented()%% ,
+ %% ok = api_to_sendto_to_udp(inet)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the sendto timeout option
+%% on an IPv6 UDP (dgram) socket.
+api_to_sendto_udp6(suite) ->
+ [];
+api_to_sendto_udp6(doc) ->
+ [];
+api_to_sendto_udp6(_Config) when is_list(_Config) ->
+ tc_try(api_to_sendto_udp6,
+ fun() ->
+ not_yet_implemented()%% ,
+ %% ok = api_to_sendto_to_udp(inet6)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the sendmsg timeout option
+%% on an IPv4 TCP (stream) socket.
+api_to_sendmsg_tcp4(suite) ->
+ [];
+api_to_sendmsg_tcp4(doc) ->
+ [];
+api_to_sendmsg_tcp4(_Config) when is_list(_Config) ->
+ tc_try(api_to_sendmsg_tcp4,
+ fun() ->
+ not_yet_implemented()%% ,
+ %% ok = api_to_sendmsg_tcp(inet)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the sendmsg timeout option
+%% on an IPv6 TCP (stream) socket.
+api_to_sendmsg_tcp6(suite) ->
+ [];
+api_to_sendmsg_tcp6(doc) ->
+ [];
+api_to_sendmsg_tcp6(_Config) when is_list(_Config) ->
+ tc_try(api_to_sendmsg_tcp6,
+ fun() ->
+ not_yet_implemented()%% ,
+ %% ok = api_to_sendmsg_tcp(inet6)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the recv timeout option
+%% on an IPv4 UDP (dgram) socket. To test this we must connect
+%% the socket.
+api_to_recv_udp4(suite) ->
+ [];
+api_to_recv_udp4(doc) ->
+ [];
+api_to_recv_udp4(_Config) when is_list(_Config) ->
+ tc_try(api_to_recv_udp4,
+ fun() ->
+ not_yet_implemented()%%,
+ %%ok = api_to_recv_udp(inet)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the recv timeout option
+%% on an IPv6 UDP (dgram) socket. To test this we must connect
+%% the socket.
+api_to_recv_udp6(suite) ->
+ [];
+api_to_recv_udp6(doc) ->
+ [];
+api_to_recv_udp6(_Config) when is_list(_Config) ->
+ tc_try(api_to_recv_udp6,
+ fun() ->
+ not_yet_implemented()%% ,
+ %% ok = api_to_recv_udp(inet6)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the recv timeout option
+%% on an IPv4 TCP (stream) socket.
+api_to_recv_tcp4(suite) ->
+ [];
+api_to_recv_tcp4(doc) ->
+ [];
+api_to_recv_tcp4(_Config) when is_list(_Config) ->
+ tc_try(api_to_recv_tcp4,
+ fun() ->
+ ?TT(?SECS(10)),
+ Recv = fun(Sock, To) -> socket:recv(Sock, 0, To) end,
+ InitState = #{domain => inet,
+ recv => Recv,
+ timeout => 5000},
+ ok = api_to_receive_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the recv timeout option
+%% on an IPv6 TCP (stream) socket.
+api_to_recv_tcp6(suite) ->
+ [];
+api_to_recv_tcp6(doc) ->
+ [];
+api_to_recv_tcp6(_Config) when is_list(_Config) ->
+ tc_try(api_to_recv_tcp6,
+ fun() ->
+ not_yet_implemented(),
+ case socket:supports(ipv6) of
+ true ->
+ ?TT(?SECS(10)),
+ Recv = fun(Sock, To) ->
+ socket:recv(Sock, 0, To)
+ end,
+ InitState = #{domain => inet6,
+ recv => Recv,
+ timeout => 5000},
+ ok = api_to_receive_tcp(InitState);
+ false ->
+ skip("ipv6 not supported")
+ end
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_to_receive_tcp(InitState) ->
+ process_flag(trap_exit, true),
+
+ ServerSeq =
+ [
+ %% *** Wait for start order ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester}) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain, addr => LAddr},
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{lsock := LSock, local_sa := LSA} = State) ->
+ case socket:bind(LSock, LSA) of
+ {ok, Port} ->
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket (with backlog = 1)",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock, 1)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, lport := Port}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Port),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "await continue (accept and recv)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept_recv)
+ end},
+ #{desc => "attempt accept",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "attempt to recv (without success)",
+ cmd => fun(#{sock := Sock, recv := Recv, timeout := To} = State) ->
+ Start = t(),
+ case Recv(Sock, To) of
+ {error, timeout} ->
+ {ok, State#{start => Start, stop => t()}};
+ {ok, _Data} ->
+ {error, unexpected_success};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "validate timeout time",
+ cmd => fun(#{start := Start, stop := Stop, timeout := To} = State) ->
+ TDiff = tdiff(Start, Stop),
+ if
+ (TDiff >= To) ->
+ State1 = maps:remove(start, State),
+ State2 = maps:remove(stop, State1),
+ {ok, State2};
+ true ->
+ {error, {unexpected_timeout, TDiff, To}}
+ end
+ end},
+ #{desc => "announce ready (recv timeout success)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept_recv),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close (traffic) socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ sock_close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+ #{desc => "close (listen) socket",
+ cmd => fun(#{lsock := LSock} = State) ->
+ sock_close(LSock),
+ {ok, maps:remove(lsock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ {Tester, Port} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ server_port => Port}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain, server_port := Port} = State) ->
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain,
+ addr => LAddr},
+ SSA = LSA#{port => Port},
+ {ok, State#{local_sa => LSA, server_sa => SSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, local_sa := LSA} = _State) ->
+ case socket:bind(Sock, LSA) of
+ {ok, _} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% *** The actual test ***
+ #{desc => "await continue (with connect)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, connect)
+ end},
+ #{desc => "connect",
+ cmd => fun(#{sock := Sock, server_sa := SSA}) ->
+ sock_connect(Sock, SSA),
+ ok
+ end},
+
+ %% *** Termination ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ sock_close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Server} = _State) ->
+ _MRef = erlang:monitor(process, Server),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Client} = _State) ->
+ _MRef = erlang:monitor(process, Client),
+ ok
+ end},
+
+ %% *** Activate server ***
+ #{desc => "start server",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_START(Server),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Server} = State) ->
+ {ok, Port} = ?SEV_AWAIT_READY(Server, server, init),
+ {ok, State#{server_port => Port}}
+ end},
+ #{desc => "order server to continue (with accept)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, accept_recv),
+ ok
+ end},
+
+ %% *** Activate client ***
+ #{desc => "start client",
+ cmd => fun(#{client := Client, server_port := Port} = _State) ->
+ ?SEV_ANNOUNCE_START(Client, Port),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, init)
+ end},
+
+ %% *** The actual test ***
+ #{desc => "order client to continue (with connect)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, connect),
+ ok
+ end},
+ #{desc => "await server ready (accept/recv)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, accept_recv)
+ end},
+
+ %% *** Termination ***
+ #{desc => "order client to terminate",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Client} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Client) of
+ ok ->
+ State1 = maps:remove(client, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Server} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Server) of
+ ok ->
+ State1 = maps:remove(server, State),
+ State2 = maps:remove(server_port, State1),
+ {ok, State2};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ i("start server evaluator"),
+ ServerInitState = InitState,
+ Server = ?SEV_START("server", ServerSeq, ServerInitState),
+
+ i("start client evaluator"),
+ ClientInitState = InitState,
+ Client = ?SEV_START("client", ClientSeq, ClientInitState),
+
+ i("start tester evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client => Client#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the recvfrom timeout option
+%% on an IPv4 UDP (dgram) socket.
+api_to_recvfrom_udp4(suite) ->
+ [];
+api_to_recvfrom_udp4(doc) ->
+ [];
+api_to_recvfrom_udp4(_Config) when is_list(_Config) ->
+ tc_try(api_to_recvfrom_udp4,
+ fun() ->
+ ?TT(?SECS(10)),
+ Recv = fun(Sock, To) -> socket:recvfrom(Sock, 0, To) end,
+ InitState = #{domain => inet,
+ recv => Recv,
+ timeout => 5000},
+ ok = api_to_receive_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the recvfrom timeout option
+%% on an IPv6 UDP (dgram) socket.
+api_to_recvfrom_udp6(suite) ->
+ [];
+api_to_recvfrom_udp6(doc) ->
+ [];
+api_to_recvfrom_udp6(_Config) when is_list(_Config) ->
+ tc_try(api_to_recvfrom_udp6,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(10)),
+ Recv = fun(Sock, To) -> socket:recvfrom(Sock, 0, To) end,
+ InitState = #{domain => inet6,
+ recv => Recv,
+ timeout => 5000},
+ ok = api_to_receive_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+api_to_receive_udp(InitState) ->
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain, addr => LAddr},
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, dgram, udp) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, lsa := LSA} = _State) ->
+ case socket:bind(Sock, LSA) of
+ {ok, _Port} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** The actual test ***
+ #{desc => "attempt to read (without success)",
+ cmd => fun(#{sock := Sock, recv := Recv, timeout := To} = State) ->
+ Start = t(),
+ case Recv(Sock, To) of
+ {error, timeout} ->
+ {ok, State#{start => Start, stop => t()}};
+ {ok, _} ->
+ {error, unexpected_sucsess};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "validate timeout time",
+ cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) ->
+ TDiff = tdiff(Start, Stop),
+ if
+ (TDiff >= To) ->
+ ok;
+ true ->
+ {error, {unexpected_timeout, TDiff, To}}
+ end
+ end},
+
+ %% *** Termination ***
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = _State) ->
+ sock_close(Sock),
+ ok
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start tester evaluator"),
+ Tester = ?SEV_START("tester", TesterSeq, InitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the recvmsg timeout option
+%% on an IPv4 UDP (dgram) socket.
+api_to_recvmsg_udp4(suite) ->
+ [];
+api_to_recvmsg_udp4(doc) ->
+ [];
+api_to_recvmsg_udp4(_Config) when is_list(_Config) ->
+ tc_try(api_to_recvmsg_udp4,
+ fun() ->
+ ?TT(?SECS(10)),
+ Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end,
+ InitState = #{domain => inet,
+ recv => Recv,
+ timeout => 5000},
+ ok = api_to_receive_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the recvmsg timeout option
+%% on an IPv6 UDP (dgram) socket.
+api_to_recvmsg_udp6(suite) ->
+ [];
+api_to_recvmsg_udp6(doc) ->
+ [];
+api_to_recvmsg_udp6(_Config) when is_list(_Config) ->
+ tc_try(api_to_recvmsg_udp6,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(10)),
+ Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end,
+ InitState = #{domain => inet6,
+ recv => Recv,
+ timeout => 5000},
+ ok = api_to_receive_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the recvmsg timeout option
+%% on an IPv4 TCP (stream) socket.
+api_to_recvmsg_tcp4(suite) ->
+ [];
+api_to_recvmsg_tcp4(doc) ->
+ [];
+api_to_recvmsg_tcp4(_Config) when is_list(_Config) ->
+ tc_try(api_to_recvmsg_tcp4,
+ fun() ->
+ ?TT(?SECS(10)),
+ Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end,
+ InitState = #{domain => inet,
+ recv => Recv,
+ timeout => 5000},
+ ok = api_to_receive_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This test case is intended to test the recvmsg timeout option
+%% on an IPv6 TCP (stream) socket.
+api_to_recvmsg_tcp6(suite) ->
+ [];
+api_to_recvmsg_tcp6(doc) ->
+ [];
+api_to_recvmsg_tcp6(_Config) when is_list(_Config) ->
+ tc_try(api_to_recvmsg_tcp6,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(10)),
+ Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end,
+ InitState = #{domain => inet6,
+ recv => Recv,
+ timeout => 5000},
+ ok = api_to_receive_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% %%
+%% SOCKET CLOSURE %%
+%% %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sockets are cleaned up
+%% ("removed") when the controlling process terminates (without explicitly
+%% calling the close function). For a IPv4 TCP (stream) socket.
+
+sc_cpe_socket_cleanup_tcp4(suite) ->
+ [];
+sc_cpe_socket_cleanup_tcp4(doc) ->
+ [];
+sc_cpe_socket_cleanup_tcp4(_Config) when is_list(_Config) ->
+ tc_try(sc_cpe_socket_cleanup_tcp4,
+ fun() ->
+ %% not_yet_implemented(),
+ ?TT(?SECS(5)),
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp},
+ ok = sc_cpe_socket_cleanup(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sockets are cleaned up
+%% ("removed") when the controlling process terminates (without explicitly
+%% calling the close function). For a IPv6 TCP (stream) socket.
+
+sc_cpe_socket_cleanup_tcp6(suite) ->
+ [];
+sc_cpe_socket_cleanup_tcp6(doc) ->
+ [];
+sc_cpe_socket_cleanup_tcp6(_Config) when is_list(_Config) ->
+ tc_try(sc_cpe_socket_cleanup_tcp6,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(5)),
+ InitState = #{domain => inet6,
+ type => stream,
+ protocol => tcp},
+ ok = sc_cpe_socket_cleanup(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sockets are cleaned up
+%% ("removed") when the controlling process terminates (without explicitly
+%% calling the close function). For a IPv4 UDP (dgram) socket.
+
+sc_cpe_socket_cleanup_udp4(suite) ->
+ [];
+sc_cpe_socket_cleanup_udp4(doc) ->
+ [];
+sc_cpe_socket_cleanup_udp4(_Config) when is_list(_Config) ->
+ tc_try(sc_cpe_socket_cleanup_udp4,
+ fun() ->
+ ?TT(?SECS(5)),
+ InitState = #{domain => inet,
+ type => dgram,
+ protocol => udp},
+ ok = sc_cpe_socket_cleanup(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sockets are cleaned up
+%% (removed) when the controlling process terminates (without explicitly
+%% calling the close function). For a IPv6 UDP (dgram) socket.
+
+sc_cpe_socket_cleanup_udp6(suite) ->
+ [];
+sc_cpe_socket_cleanup_udp6(doc) ->
+ [];
+sc_cpe_socket_cleanup_udp6(_Config) when is_list(_Config) ->
+ tc_try(sc_cpe_socket_cleanup_udp6,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(5)),
+ InitState = #{domain => inet6,
+ type => dgram,
+ protocol => udp},
+ ok = sc_cpe_socket_cleanup(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+sc_cpe_socket_cleanup(InitState) ->
+ OwnerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Sock),
+ ok
+ end},
+
+ %% *** The actual test ***
+ %% We *intentially* leave the socket "as is", no explicit close
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor owner",
+ cmd => fun(#{owner := Owner} = _State) ->
+ _MRef = erlang:monitor(process, Owner),
+ ok
+ end},
+ #{desc => "order (owner) start",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await (owner) ready",
+ cmd => fun(#{owner := Pid} = State) ->
+ {ok, Sock} = ?SEV_AWAIT_READY(Pid, owner, init),
+ {ok, State#{sock => Sock}}
+ end},
+ #{desc => "verify owner as controlling-process",
+ cmd => fun(#{owner := Pid, sock := Sock} = _State) ->
+ case socket:getopt(Sock, otp, controlling_process) of
+ {ok, Pid} ->
+ ok;
+ {ok, Other} ->
+ {error, {unexpected_owner, Other}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order (owner) terminate",
+ cmd => fun(#{owner := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await (owner) termination",
+ cmd => fun(#{owner := Pid} = _State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ %% The reason we get closed, is that as long as there is a ref to
+ %% the resource (socket), then it will not be garbage collected.
+ #{desc => "verify no socket (closed)",
+ cmd => fun(#{owner := Pid, sock := Sock} = _State) ->
+ case socket:getopt(Sock, otp, controlling_process) of
+ {ok, OtherPid} ->
+ {error, {unexpected_success, Pid, OtherPid}};
+ {error, closed} ->
+ ok;
+ {error, Reason} ->
+ ?SEV_IPRINT("expected failure: ~p", [Reason]),
+ {error, {unexpected_failure, Reason}}
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start (socket) owner evaluator"),
+ Owner = ?SEV_START("owner", OwnerSeq, InitState),
+
+ i("start tester evaluator"),
+ TesterInitState = #{owner => Owner#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Owner, Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% locally closed while a process is calling the recv function.
+%% Socket is IPv4.
+%%
+%% <KOLLA>
+%%
+%% We should really have a similar test cases for when the controlling
+%% process exits and there are other processes in recv, accept, and
+%% all the other functions.
+%%
+%% </KOLLA>
+
+sc_lc_recv_response_tcp4(suite) ->
+ [];
+sc_lc_recv_response_tcp4(doc) ->
+ [];
+sc_lc_recv_response_tcp4(_Config) when is_list(_Config) ->
+ tc_try(sc_lc_recv_response_tcp4,
+ fun() ->
+ ?TT(?SECS(10)),
+ Recv = fun(Sock) -> socket:recv(Sock) end,
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp,
+ recv => Recv},
+ ok = sc_lc_receive_response_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% locally closed while the process is calling the recv function.
+%% Socket is IPv6.
+
+sc_lc_recv_response_tcp6(suite) ->
+ [];
+sc_lc_recv_response_tcp6(doc) ->
+ [];
+sc_lc_recv_response_tcp6(_Config) when is_list(_Config) ->
+ tc_try(sc_lc_recv_response_tcp6,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(10)),
+ Recv = fun(Sock) -> socket:recv(Sock) end,
+ InitState = #{domain => inet6,
+ type => stream,
+ protocol => tcp,
+ recv => Recv},
+ ok = sc_lc_receive_response_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+sc_lc_receive_response_tcp(InitState) ->
+ %% This (acceptor) is the server that accepts connections.
+ %% But it is also suppose to close the connection socket,
+ %% and trigger the read failure (=closed) for the handler process.
+ AcceptorSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain, addr => LAddr},
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create (listen) socket",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{lsock := LSock, local_sa := LSA} = State) ->
+ case socket:bind(LSock, LSA) of
+ {ok, Port} ->
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, lport := Port}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Port),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester, accept) of
+ {ok, {H1, H2, H3}} ->
+ {ok, State#{handler1 => H1,
+ handler2 => H2,
+ handler3 => H3}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await accept",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ ?SEV_IPRINT("connection accepted"),
+ {ok, State#{csock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+ #{desc => "transfer connection to handler 1",
+ cmd => fun(#{handler1 := Handler, csock := Sock}) ->
+ ?SEV_ANNOUNCE_CONTINUE(Handler, transfer, Sock),
+ ok
+ end},
+ #{desc => "transfer connection to handler 2",
+ cmd => fun(#{handler2 := Handler, csock := Sock}) ->
+ ?SEV_ANNOUNCE_CONTINUE(Handler, transfer, Sock),
+ ok
+ end},
+ #{desc => "transfer connection to handler 3",
+ cmd => fun(#{handler3 := Handler, csock := Sock}) ->
+ ?SEV_ANNOUNCE_CONTINUE(Handler, transfer, Sock),
+ ok
+ end},
+ #{desc => "await continue (close)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, close),
+ ok
+ end},
+ #{desc => "close the connection socket",
+ cmd => fun(#{csock := Sock} = State) ->
+ %% ok = socket:setopt(Sock, otp, debug, true),
+ case socket:close(Sock) of
+ ok ->
+ {ok, maps:remove(csock, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (close)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, close),
+ ok
+ end},
+
+ %% *** Terminate ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{lsock := Sock} = State) ->
+ case socket:close(Sock) of
+ ok ->
+ State1 = maps:remove(lsock, State),
+ State2 = maps:remove(lport, State1),
+ {ok, State2};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ %% The point of this is to perform the recv for which
+ %% we are testing the reponse.
+ HandlerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ {Tester, Acceptor} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ acceptor => Acceptor}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+ #{desc => "monitor acceptor",
+ cmd => fun(#{acceptor := Acceptor} = _State) ->
+ _MRef = erlang:monitor(process, Acceptor),
+ ok
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (transfer)",
+ cmd => fun(#{acceptor := Pid} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Pid, acceptor, transfer) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (transfer)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, transfer),
+ ok
+ end},
+ #{desc => "attempt recv (=> closed)",
+ cmd => fun(#{sock := Sock, recv := Recv} = State) ->
+ case Recv(Sock) of
+ {ok, _Data} ->
+ ?SEV_EPRINT("Unexpected data received"),
+ {error, unexpected_success};
+ {error, closed} ->
+ ?SEV_IPRINT("received expected 'closed' "
+ "result"),
+ State1 = maps:remove(sock, State),
+ {ok, State1};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Unexpected read faulure: "
+ "~n ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv closed)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_closed),
+ ok
+ end},
+
+ %% *** Terminate ***
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ %% The point of this is basically just to create the connection.
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain, addr => LAddr},
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind socket to local address",
+ cmd => fun(#{sock := Sock, local_sa := LSA} = _State) ->
+ case socket:bind(Sock, LSA) of
+ {ok, _} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester, local_sa := LSA} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester, connect) of
+ {ok, Port} ->
+ ServerSA = LSA#{port => Port},
+ {ok, State#{server_sa => ServerSA}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "connect to server",
+ cmd => fun(#{sock := Sock, server_sa := ServerSA}) ->
+ socket:connect(Sock, ServerSA)
+ end},
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+
+ %% *** Terminate ***
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ sock_close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor acceptor",
+ cmd => fun(#{acceptor := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor handler 1",
+ cmd => fun(#{handler1 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor handler 2",
+ cmd => fun(#{handler2 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor handler 3",
+ cmd => fun(#{handler3 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the acceptor
+ #{desc => "order acceptor start",
+ cmd => fun(#{acceptor := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await acceptor ready (init)",
+ cmd => fun(#{acceptor := Pid} = State) ->
+ case ?SEV_AWAIT_READY(Pid, acceptor, init) of
+ {ok, Port} ->
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% Start the handler(s)
+ #{desc => "order handler 1 start",
+ cmd => fun(#{acceptor := Acceptor, handler1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Acceptor),
+ ok
+ end},
+ #{desc => "await handler 1 ready (init)",
+ cmd => fun(#{handler1 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, handler1, init)
+ end},
+ #{desc => "order handler 2 start",
+ cmd => fun(#{acceptor := Acceptor, handler2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Acceptor),
+ ok
+ end},
+ #{desc => "await handler 2 ready (init)",
+ cmd => fun(#{handler2 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, handler2, init)
+ end},
+ #{desc => "order handler 3 start",
+ cmd => fun(#{acceptor := Acceptor, handler3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Acceptor),
+ ok
+ end},
+ #{desc => "await handler 3 ready (init)",
+ cmd => fun(#{handler3 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, handler3, init)
+ end},
+
+ %% Start the client
+ #{desc => "order client start",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, client, init)
+ end},
+
+ %% The actual test
+ #{desc => "order acceptor to continue (accept)",
+ cmd => fun(#{acceptor := Pid,
+ handler1 := H1,
+ handler2 := H2,
+ handler3 := H3} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept, {H1, H2, H3}),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client to continue (connect)",
+ cmd => fun(#{client := Pid, lport := Port} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, connect, Port),
+ ok
+ end},
+ #{desc => "await acceptor ready (accept)",
+ cmd => fun(#{acceptor := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, acceptor, accept)
+ end},
+ #{desc => "await client ready (connect)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, connect)
+ end},
+ #{desc => "await handler 1 ready (transfer)",
+ cmd => fun(#{handler1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, handler1, transfer)
+ end},
+ #{desc => "await handler 2 ready (transfer)",
+ cmd => fun(#{handler2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, handler2, transfer)
+ end},
+ #{desc => "await handler 3 ready (transfer)",
+ cmd => fun(#{handler3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, handler3, transfer)
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order acceptor to continue (close connection socket)",
+ cmd => fun(#{acceptor := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close),
+ ok
+ end},
+ #{desc => "await acceptor ready (close)",
+ cmd => fun(#{acceptor := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, acceptor, close)
+ end},
+ #{desc => "await handler 1 ready (recv closed)",
+ cmd => fun(#{handler1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, handler1, recv_closed)
+ end},
+ #{desc => "await handler 2 ready (recv closed)",
+ cmd => fun(#{handler2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, handler2, recv_closed)
+ end},
+ #{desc => "await handler 3 ready (recv closed)",
+ cmd => fun(#{handler3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, handler3, recv_closed)
+ end},
+
+ %% Terminations
+ #{desc => "order client to terminate",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(client, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order handler 1 to terminate",
+ cmd => fun(#{handler1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await handler 1 termination",
+ cmd => fun(#{handler1 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(handler1, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order handler 2 to terminate",
+ cmd => fun(#{handler2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await handler 2 termination",
+ cmd => fun(#{handler2 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(handler2, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order handler 3 to terminate",
+ cmd => fun(#{handler3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await handler 3 termination",
+ cmd => fun(#{handler3 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(handler3, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order acceptor to terminate",
+ cmd => fun(#{acceptor := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await acceptor termination",
+ cmd => fun(#{acceptor := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(acceptor, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start acceptor evaluator"),
+ AccInitState = InitState,
+ Acceptor = ?SEV_START("acceptor", AcceptorSeq, AccInitState),
+
+ i("start handler 1 evaluator"),
+ HandlerInitState = #{recv => maps:get(recv, InitState)},
+ Handler1 = ?SEV_START("handler-1", HandlerSeq, HandlerInitState),
+
+ i("start handler 2 evaluator"),
+ Handler2 = ?SEV_START("handler-2", HandlerSeq, HandlerInitState),
+
+ i("start handler 3 evaluator"),
+ Handler3 = ?SEV_START("handler-3", HandlerSeq, HandlerInitState),
+
+ i("start client evaluator"),
+ ClientInitState = InitState,
+ Client = ?SEV_START("client", ClientSeq, ClientInitState),
+
+ i("start tester evaluator"),
+ TesterInitState = #{acceptor => Acceptor#ev.pid,
+ handler1 => Handler1#ev.pid,
+ handler2 => Handler2#ev.pid,
+ handler3 => Handler3#ev.pid,
+ client => Client#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Acceptor,
+ Handler1, Handler2, Handler3,
+ Client, Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% locally closed while a process is calling the recvfrom function.
+%% Socket is IPv4.
+%%
+
+sc_lc_recvfrom_response_udp4(suite) ->
+ [];
+sc_lc_recvfrom_response_udp4(doc) ->
+ [];
+sc_lc_recvfrom_response_udp4(_Config) when is_list(_Config) ->
+ tc_try(sc_lc_recvfrom_response_udp4,
+ fun() ->
+ ?TT(?SECS(30)),
+ Recv = fun(Sock, To) -> socket:recvfrom(Sock, [], To) end,
+ InitState = #{domain => inet,
+ type => dgram,
+ protocol => udp,
+ recv => Recv},
+ ok = sc_lc_receive_response_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% locally closed while the process is calling the recv function.
+%% Socket is IPv6.
+
+sc_lc_recvfrom_response_udp6(suite) ->
+ [];
+sc_lc_recvfrom_response_udp6(doc) ->
+ [];
+sc_lc_recvfrom_response_udp6(_Config) when is_list(_Config) ->
+ tc_try(sc_lc_recvfrom_response_udp6,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(30)),
+ Recv = fun(Sock, To) -> socket:recvfrom(Sock, [], To) end,
+ InitState = #{domain => inet6,
+ recv => Recv},
+ ok = sc_lc_receive_response_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+sc_lc_receive_response_udp(InitState) ->
+ PrimServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain, addr => LAddr},
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "open socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ Sock = sock_open(Domain, dgram, udp),
+ SA = sock_sockname(Sock),
+ {ok, State#{sock => Sock, sa => SA}}
+ end},
+ #{desc => "bind socket",
+ cmd => fun(#{sock := Sock, local_sa := LSA}) ->
+ sock_bind(Sock, LSA),
+ ok
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, sock := Sock}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Sock),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (recv, with timeout)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester, recv) of
+ {ok, Timeout} ->
+ {ok, State#{timeout => Timeout}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "receive, with timeout",
+ cmd => fun(#{sock := Sock, recv := Recv, timeout := Timeout}) ->
+ case Recv(Sock, Timeout) of
+ {error, timeout} ->
+ ok;
+ {ok, _} ->
+ {error, unexpected_success};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv, with timeout)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv),
+ ok
+ end},
+ #{desc => "await continue (close)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ok = ?SEV_AWAIT_CONTINUE(Tester, tester, close)
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ case socket:close(Sock) of
+ ok ->
+ {ok, maps:remove(sock, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (close)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, close),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, terminate) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ SecServerSeq =
+ [
+ %% *** Init part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, Sock} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, sock => Sock}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (recv)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ok = ?SEV_AWAIT_CONTINUE(Tester, tester, recv)
+
+ end},
+ #{desc => "receive",
+ cmd => fun(#{sock := Sock, recv := Recv} = State) ->
+ case Recv(Sock, infinity) of
+ {error, closed} ->
+ {ok, maps:remove(sock, State)};
+ {ok, _} ->
+ {error, unexpected_success};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv closed)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_closed),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor primary server",
+ cmd => fun(#{prim_server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor secondary server 1",
+ cmd => fun(#{sec_server1 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor secondary server 2",
+ cmd => fun(#{sec_server2 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor secondary server 3",
+ cmd => fun(#{sec_server3 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the primary server
+ #{desc => "order 'primary server' start",
+ cmd => fun(#{prim_server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await 'primary server' ready (init)",
+ cmd => fun(#{prim_server := Pid} = State) ->
+ {ok, Sock} = ?SEV_AWAIT_READY(Pid, prim_server, init),
+ {ok, State#{sock => Sock}}
+ end},
+
+ %% Start the secondary server 1
+ #{desc => "order 'secondary server 1' start",
+ cmd => fun(#{sec_server1 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Sock),
+ ok
+ end},
+ #{desc => "await 'secondary server 1' ready (init)",
+ cmd => fun(#{sec_server1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, sec_server1, init)
+ end},
+
+ %% Start the secondary server 2
+ #{desc => "order 'secondary server 2' start",
+ cmd => fun(#{sec_server2 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Sock),
+ ok
+ end},
+ #{desc => "await 'secondary server 2' ready (init)",
+ cmd => fun(#{sec_server2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, sec_server2, init)
+ end},
+
+ %% Start the secondary server 3
+ #{desc => "order 'secondary server 3' start",
+ cmd => fun(#{sec_server3 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Sock),
+ ok
+ end},
+ #{desc => "await 'secondary server 3' ready (init)",
+ cmd => fun(#{sec_server3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, sec_server3, init)
+ end},
+
+
+ %% The actual test
+ %% Make all the seondary servers continue, with an infinit recvfrom
+ %% and then the prim-server with a timed recvfrom.
+ %% After the prim server notifies us (about the timeout) we order it
+ %% to close the socket, which should cause the all the secondary
+ %% server to return with error-closed.
+
+ #{desc => "order 'secondary server 1' to continue (recv)",
+ cmd => fun(#{sec_server1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order 'secondary server 2' to continue (recv)",
+ cmd => fun(#{sec_server2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order 'secondary server 3' to continue (recv)",
+ cmd => fun(#{sec_server3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order 'primary server' to continue (recv, with timeout)",
+ cmd => fun(#{prim_server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv, ?SECS(5)),
+ ok
+ end},
+ #{desc => "await 'primary server' ready (recv, with timeout)",
+ cmd => fun(#{prim_server := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, prim_server, recv)
+ end},
+ #{desc => "order 'primary server' to continue (close)",
+ cmd => fun(#{prim_server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close),
+ ok
+ end},
+ #{desc => "await 'primary server' ready (close)",
+ cmd => fun(#{prim_server := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, prim_server, close)
+ end},
+ #{desc => "await 'secondary server 1' ready (closed)",
+ cmd => fun(#{sec_server1 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, sec_server1, recv_closed)
+ end},
+ #{desc => "await 'secondary server 2' ready (closed)",
+ cmd => fun(#{sec_server2 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, sec_server2, recv_closed)
+ end},
+ #{desc => "await 'secondary server 3' ready (closed)",
+ cmd => fun(#{sec_server3 := Pid} = _State) ->
+ ?SEV_AWAIT_READY(Pid, sec_server3, recv_closed)
+ end},
+
+ %% Terminations
+ #{desc => "order 'secondary server 3' to terminate",
+ cmd => fun(#{sec_server3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await 'secondary server 3' termination",
+ cmd => fun(#{sec_server3 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(sec_server3, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order 'secondary server 2' to terminate",
+ cmd => fun(#{sec_server2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await 'secondary server 2' termination",
+ cmd => fun(#{sec_server2 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(sec_server2, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order 'secondary server 1' to terminate",
+ cmd => fun(#{sec_server1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await 'secondary server 1' termination",
+ cmd => fun(#{sec_server1 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(sec_server1, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order 'primary server' to terminate",
+ cmd => fun(#{prim_server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await 'primary server' termination",
+ cmd => fun(#{prim_server := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(prim_server, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ i("start 'primary server' evaluator"),
+ PrimSrvInitState = InitState,
+ PrimServer = ?SEV_START("prim-server", PrimServerSeq, PrimSrvInitState),
+
+ i("start 'secondary server 1' evaluator"),
+ SecSrvInitState = #{recv => maps:get(recv, InitState)},
+ SecServer1 = ?SEV_START("sec-server-1", SecServerSeq, SecSrvInitState),
+
+ i("start 'secondary server 2' evaluator"),
+ SecServer2 = ?SEV_START("sec-server-2", SecServerSeq, SecSrvInitState),
+
+ i("start 'secondary server 3' evaluator"),
+ SecServer3 = ?SEV_START("sec-server-3", SecServerSeq, SecSrvInitState),
+
+ i("start 'tester' evaluator"),
+ TesterInitState = #{prim_server => PrimServer#ev.pid,
+ sec_server1 => SecServer1#ev.pid,
+ sec_server2 => SecServer2#ev.pid,
+ sec_server3 => SecServer3#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([PrimServer,
+ SecServer1, SecServer2, SecServer3,
+ Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% locally closed while the process is calling the recvmsg function.
+%% Socket is IPv4.
+
+sc_lc_recvmsg_response_tcp4(suite) ->
+ [];
+sc_lc_recvmsg_response_tcp4(doc) ->
+ [];
+sc_lc_recvmsg_response_tcp4(_Config) when is_list(_Config) ->
+ tc_try(sc_lc_recvmsg_response_tcp4,
+ fun() ->
+ ?TT(?SECS(10)),
+ Recv = fun(Sock) -> socket:recvmsg(Sock) end,
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp,
+ recv => Recv},
+ ok = sc_lc_receive_response_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% locally closed while the process is calling the recvmsg function.
+%% Socket is IPv6.
+
+sc_lc_recvmsg_response_tcp6(suite) ->
+ [];
+sc_lc_recvmsg_response_tcp6(doc) ->
+ [];
+sc_lc_recvmsg_response_tcp6(_Config) when is_list(_Config) ->
+ tc_try(sc_recvmsg_response_tcp6,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(10)),
+ Recv = fun(Sock) -> socket:recvmsg(Sock) end,
+ InitState = #{domain => inet6,
+ type => stream,
+ protocol => tcp,
+ recv => Recv},
+ ok = sc_lc_receive_response_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% locally closed while the process is calling the recvmsg function.
+%% Socket is IPv4.
+
+sc_lc_recvmsg_response_udp4(suite) ->
+ [];
+sc_lc_recvmsg_response_udp4(doc) ->
+ [];
+sc_lc_recvmsg_response_udp4(_Config) when is_list(_Config) ->
+ tc_try(sc_lc_recvmsg_response_udp4,
+ fun() ->
+ ?TT(?SECS(10)),
+ Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end,
+ InitState = #{domain => inet,
+ recv => Recv},
+ ok = sc_lc_receive_response_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% locally closed while the process is calling the recvmsg function.
+%% Socket is IPv6.
+
+sc_lc_recvmsg_response_udp6(suite) ->
+ [];
+sc_lc_recvmsg_response_udp6(doc) ->
+ [];
+sc_lc_recvmsg_response_udp6(_Config) when is_list(_Config) ->
+ tc_try(sc_recvmsg_response_udp6,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(10)),
+ Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end,
+ InitState = #{domain => inet6,
+ recv => Recv},
+ ok = sc_lc_receive_response_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% locally closed while the process is calling the accept function.
+%% We test what happens with a non-controlling_process also, since we
+%% git the setup anyway.
+%% Socket is IPv4.
+
+sc_lc_acceptor_response_tcp4(suite) ->
+ [];
+sc_lc_acceptor_response_tcp4(doc) ->
+ [];
+sc_lc_acceptor_response_tcp4(_Config) when is_list(_Config) ->
+ tc_try(sc_lc_acceptor_response_tcp4,
+ fun() ->
+ ?TT(?SECS(10)),
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp},
+ ok = sc_lc_acceptor_response_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% locally closed while the process is calling the accept function.
+%% We test what happens with a non-controlling_process also, since we
+%% git the setup anyway.
+%% Socket is IPv6.
+
+sc_lc_acceptor_response_tcp6(suite) ->
+ [];
+sc_lc_acceptor_response_tcp6(doc) ->
+ [];
+sc_lc_acceptor_response_tcp6(_Config) when is_list(_Config) ->
+ tc_try(sc_lc_acceptor_response_tcp6,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(10)),
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp},
+ ok = sc_lc_acceptor_response_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+sc_lc_acceptor_response_tcp(InitState) ->
+ PrimAcceptorSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start (from tester)",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain, addr => LAddr},
+ {ok, State#{lsa => LSA}}
+ end},
+ #{desc => "create (listen) socket",
+ cmd => fun(#{domain := Domain,
+ type := Type,
+ protocol := Proto} = State) ->
+ case socket:open(Domain, Type, Proto) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, lsa := LSA} = _State) ->
+ case socket:bind(Sock, LSA) of
+ {ok, _Port} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{sock := Sock}) ->
+ socket:listen(Sock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, Sock),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester, accept) of
+ {ok, Timeout} ->
+ {ok, State#{timeout => Timeout}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await connection",
+ cmd => fun(#{sock := Sock, timeout := Timeout} = _State) ->
+ case socket:accept(Sock, Timeout) of
+ {error, timeout} ->
+ ok;
+ {ok, Sock} ->
+ ?SEV_EPRINT("unexpected success"),
+ (catch socket:close(Sock)),
+ {error, unexpected_success};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept timeout)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept_timeout),
+ ok
+ end},
+ #{desc => "await continue (close)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ok = ?SEV_AWAIT_CONTINUE(Tester, tester, close)
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ case socket:close(Sock) of
+ ok ->
+ {ok, maps:remove(sock, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (close)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, close),
+ ok
+ end},
+
+ % Termination
+ #{desc => "await terminate",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ SecAcceptorSeq =
+ [
+ %% *** Init part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, Sock} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, sock => Sock}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init)
+ end},
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ok = ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "accept",
+ cmd => fun(#{sock := Sock} = State) ->
+ case socket:accept(Sock) of
+ {error, closed} ->
+ {ok, maps:remove(sock, State)};
+ {ok, _} ->
+ {error, unexpected_success};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept closed)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept_closed)
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor 'primary acceptor'",
+ cmd => fun(#{prim_acc := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor 'secondary acceptor 1'",
+ cmd => fun(#{sec_acc1 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor secondary acceptor 2",
+ cmd => fun(#{sec_acc2 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor secondary acceptor 3",
+ cmd => fun(#{sec_acc3 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the primary server
+ #{desc => "order 'primary acceptor' start",
+ cmd => fun(#{prim_acc := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await 'primary acceptor' ready (init)",
+ cmd => fun(#{prim_acc := Pid} = State) ->
+ {ok, Sock} = ?SEV_AWAIT_READY(Pid, prim_acc, init),
+ {ok, State#{sock => Sock}}
+ end},
+
+ %% Start the secondary acceptor 1
+ #{desc => "order 'secondary acceptor 1' start",
+ cmd => fun(#{sec_acc1 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Sock),
+ ok
+ end},
+ #{desc => "await 'secondary acceptor 1' ready (init)",
+ cmd => fun(#{sec_acc1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, sec_acc1, init)
+ end},
+
+ %% Start the secondary acceptor 2
+ #{desc => "order 'secondary acceptor 2' start",
+ cmd => fun(#{sec_acc2 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Sock),
+ ok
+ end},
+ #{desc => "await 'secondary acceptor 2' ready (init)",
+ cmd => fun(#{sec_acc2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, sec_acc2, init)
+ end},
+
+ %% Start the secondary acceptor 3
+ #{desc => "order 'secondary acceptor 3' start",
+ cmd => fun(#{sec_acc3 := Pid, sock := Sock} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, Sock),
+ ok
+ end},
+ #{desc => "await 'secondary acceptor 3' ready (init)",
+ cmd => fun(#{sec_acc3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, sec_acc3, init)
+ end},
+
+
+ %% The actual test
+ %% Make all the seondary servers continue, with an infinit recvfrom
+ %% and then the prim-server with a timed recvfrom.
+ %% After the prim server notifies us (about the timeout) we order it
+ %% to close the socket, which should cause the all the secondary
+ %% server to return with error-closed.
+
+ #{desc => "order 'secondary acceptor 1' to continue (accept)",
+ cmd => fun(#{sec_acc1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order 'secondary acceptor 2' to continue (accept)",
+ cmd => fun(#{sec_acc2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order 'secondary acceptor 3' to continue (accept)",
+ cmd => fun(#{sec_acc3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order 'primary acceptor' to continue",
+ cmd => fun(#{prim_acc := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept, ?SECS(5)),
+ ok
+ end},
+ #{desc => "await 'primary acceptor' ready (accept timeout)",
+ cmd => fun(#{prim_acc := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, prim_acc, accept_timeout)
+ end},
+ #{desc => "order 'primary acceptor' to continue (close)",
+ cmd => fun(#{prim_acc := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close),
+ ok
+ end},
+ #{desc => "await 'primary acceptor' ready (close)",
+ cmd => fun(#{prim_acc := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, prim_acc, close)
+ end},
+ #{desc => "await 'secondary acceptor 1' ready (accept closed)",
+ cmd => fun(#{sec_acc1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, sec_acc1, accept_closed)
+ end},
+ #{desc => "await 'secondary acceptor 2' ready (accept closed)",
+ cmd => fun(#{sec_acc2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, sec_acc2, accept_closed)
+ end},
+ #{desc => "await 'secondary acceptor 3' ready (accept closed)",
+ cmd => fun(#{sec_acc3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, sec_acc3, accept_closed)
+ end},
+
+
+ %% Terminations
+ #{desc => "order 'secondary acceptor 3' to terminate",
+ cmd => fun(#{sec_acc3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await 'secondary acceptor 3' termination",
+ cmd => fun(#{sec_acc3 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(sec_acc3, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order 'secondary acceptor 2' to terminate",
+ cmd => fun(#{sec_acc2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await 'secondary acceptor 2' termination",
+ cmd => fun(#{sec_acc2 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(sec_acc2, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order 'secondary acceptor 1' to terminate",
+ cmd => fun(#{sec_acc1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await 'secondary acceptor 1' termination",
+ cmd => fun(#{sec_acc1 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(sec_acc1, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order 'primary acceptor' to terminate",
+ cmd => fun(#{prim_acc := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await 'primary acceptor' termination",
+ cmd => fun(#{prim_acc := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ {ok, maps:remove(prim_acc, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ i("start 'primary acceptor' evaluator"),
+ PrimAccInitState = InitState,
+ PrimAcc = ?SEV_START("prim-acceptor", PrimAcceptorSeq, PrimAccInitState),
+
+ i("start 'secondary acceptor 1' evaluator"),
+ SecAccInitState = #{},
+ SecAcc1 = ?SEV_START("sec-acceptor-1", SecAcceptorSeq, SecAccInitState),
+
+ i("start 'secondary acceptor 2' evaluator"),
+ SecAcc2 = ?SEV_START("sec-acceptor-2", SecAcceptorSeq, SecAccInitState),
+
+ i("start 'secondary acceptor 3' evaluator"),
+ SecAcc3 = ?SEV_START("sec-acceptor-3", SecAcceptorSeq, SecAccInitState),
+
+ i("start 'tester' evaluator"),
+ TesterInitState = #{prim_acc => PrimAcc#ev.pid,
+ sec_acc1 => SecAcc1#ev.pid,
+ sec_acc2 => SecAcc2#ev.pid,
+ sec_acc3 => SecAcc3#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([PrimAcc, SecAcc1, SecAcc2, SecAcc3, Tester]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% remotely closed while the process is calling the recv function.
+%% Socket is IPv4.
+%%
+%% To minimize the chance of "weirdness", we should really have test cases
+%% where the two sides of the connection is on different machines. But for
+%% now, we will make do with different VMs on the same host.
+%%
+
+sc_rc_recv_response_tcp4(suite) ->
+ [];
+sc_rc_recv_response_tcp4(doc) ->
+ [];
+sc_rc_recv_response_tcp4(_Config) when is_list(_Config) ->
+ tc_try(sc_rc_recv_response_tcp4,
+ fun() ->
+ ?TT(?SECS(30)),
+ Recv = fun(Sock) -> socket:recv(Sock) end,
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp,
+ recv => Recv},
+ ok = sc_rc_receive_response_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% remotely closed while the process is calling the recv function.
+%% Socket is IPv6.
+
+sc_rc_recv_response_tcp6(suite) ->
+ [];
+sc_rc_recv_response_tcp6(doc) ->
+ [];
+sc_rc_recv_response_tcp6(_Config) when is_list(_Config) ->
+ tc_try(sc_rc_recv_response_tcp6,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(10)),
+ Recv = fun(Sock) -> socket:recv(Sock) end,
+ InitState = #{domain => inet6,
+ type => stream,
+ protocol => tcp,
+ recv => Recv},
+ ok = sc_rc_receive_response_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+sc_rc_receive_response_tcp(InitState) ->
+ %% Each connection are handled by handler processes.
+ %% These are created (on the fly) and handled internally
+ %% by the server!
+ ServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain, addr => LAddr},
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{lsock := LSock, local_sa := LSA} = State) ->
+ case socket:bind(LSock, LSA) of
+ {ok, Port} ->
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, local_sa := LSA, lport := Port}) ->
+ ServerSA = LSA#{port => Port},
+ ?SEV_ANNOUNCE_READY(Tester, init, ServerSA),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (accept all three connections)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "accept 1",
+ cmd => fun(#{lsock := LSock, recv := Recv} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ ?SEV_IPRINT("accepted: try start handler"),
+ Handler = sc_rc_tcp_handler_start(1, Recv, Sock),
+ ?SEV_IPRINT("handler started"),
+ {ok, State#{csock1 => Sock,
+ handler1 => Handler}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await handler 1 ready (init)",
+ cmd => fun(#{tester := Tester,
+ handler1 := Handler1} = _State) ->
+ ?SEV_AWAIT_READY(Handler1, handler1, init,
+ [{tester, Tester}])
+ end},
+ #{desc => "accept 2",
+ cmd => fun(#{lsock := LSock, recv := Recv} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ ?SEV_IPRINT("accepted: try start handler"),
+ Handler = sc_rc_tcp_handler_start(2, Recv, Sock),
+ ?SEV_IPRINT("handler started"),
+ {ok, State#{csock2 => Sock,
+ handler2 => Handler}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await handler 2 ready (init)",
+ cmd => fun(#{tester := Tester,
+ handler1 := Handler1,
+ handler2 := Handler2} = _State) ->
+ ?SEV_AWAIT_READY(Handler2, handler2, init,
+ [{tester, Tester},
+ {handler1, Handler1}])
+ end},
+ #{desc => "accept 3",
+ cmd => fun(#{lsock := LSock, recv := Recv} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ ?SEV_IPRINT("accepted: try start handler"),
+ Handler = sc_rc_tcp_handler_start(3, Recv, Sock),
+ ?SEV_IPRINT("handler started"),
+ {ok, State#{csock3 => Sock,
+ handler3 => Handler}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await handler 3 ready (init)",
+ cmd => fun(#{tester := Tester,
+ handler1 := Handler1,
+ handler2 := Handler2,
+ handler3 := Handler3} = _State) ->
+ ?SEV_AWAIT_READY(Handler3, handler3, init,
+ [{tester, Tester},
+ {handler1, Handler1},
+ {handler2, Handler2}])
+ end},
+ #{desc => "announce ready (accept all three connections)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+ #{desc => "await continue (recv)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv)
+ end},
+ #{desc => "order handler 1 to receive",
+ cmd => fun(#{handler1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ #{desc => "order handler 2 to receive",
+ cmd => fun(#{handler2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ #{desc => "order handler 3 to receive",
+ cmd => fun(#{handler3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ 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
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ 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
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ 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
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv closed from all handlers)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv_closed),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order handler 1 to terminate",
+ cmd => fun(#{handler1 := Pid} = _State) ->
+ %% Pid ! {terminate, self(), ok},
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await handler 1 termination",
+ cmd => fun(#{handler1 := Pid} = State) ->
+ ?SEV_AWAIT_TERMINATION(Pid),
+ State1 = maps:remove(csock1, State),
+ State2 = maps:remove(handler1, State1),
+ {ok, State2}
+ end},
+ #{desc => "order handler 2 to terminate",
+ cmd => fun(#{handler2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await handler 2 termination",
+ cmd => fun(#{handler2 := Pid} = State) ->
+ ?SEV_AWAIT_TERMINATION(Pid),
+ State1 = maps:remove(csock2, State),
+ State2 = maps:remove(handler2, State1),
+ {ok, State2}
+ end},
+ #{desc => "order handler 3 to terminate",
+ cmd => fun(#{handler3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await handler 3 termination",
+ cmd => fun(#{handler3 := Pid} = State) ->
+ ?SEV_AWAIT_TERMINATION(Pid),
+ State1 = maps:remove(csock3, State),
+ State2 = maps:remove(handler3, State1),
+ {ok, State2}
+ end},
+ #{desc => "close listen socket",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:close(LSock) of
+ ok ->
+ {ok, maps:remove(lsock, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, {NodeID, ServerSA}} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ node_id => NodeID,
+ server_sa => ServerSA}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "create node",
+ cmd => fun(#{host := Host, node_id := NodeID} = State) ->
+ case start_node(Host, l2a(f("client_~w", [NodeID]))) of
+ {ok, Node} ->
+ ?SEV_IPRINT("client node ~p started", [Node]),
+ {ok, State#{node => Node}};
+ {error, Reason, _} ->
+ {error, Reason}
+ end
+ end},
+ #{desc => "monitor client node 1",
+ cmd => fun(#{node := Node} = _State) ->
+ true = erlang:monitor_node(Node, true),
+ ok
+ end},
+ #{desc => "start remote client on client node",
+ cmd => fun(#{node := Node} = State) ->
+ Pid = sc_rc_tcp_client_start(Node),
+ ?SEV_IPRINT("client ~p started", [Pid]),
+ {ok, State#{rclient => Pid}}
+ end},
+ #{desc => "monitor remote client",
+ cmd => fun(#{rclient := Pid}) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order remote client to start",
+ cmd => fun(#{rclient := Client, server_sa := ServerSA}) ->
+ ?SEV_ANNOUNCE_START(Client, ServerSA),
+ ok
+ end},
+ #{desc => "await remote client ready",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, init,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, connect,
+ [{rclient, Client}]),
+ ok
+ end},
+ #{desc => "order remote client to continue (connect)",
+ cmd => fun(#{rclient := Client}) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, connect),
+ ok
+ end},
+ #{desc => "await client process ready (connect)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, connect,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (connected)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+ #{desc => "await continue (close)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, close,
+ [{rclient, Client}]),
+ ok
+ end},
+ #{desc => "order remote client to close",
+ cmd => fun(#{rclient := Client}) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, close),
+ ok
+ end},
+ #{desc => "await remote client ready (closed)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, close,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (close)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, close),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester,
+ [{rclient, Client}]) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "kill remote client",
+ cmd => fun(#{rclient := Client}) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await remote client termination",
+ cmd => fun(#{rclient := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(rclient, State),
+ {ok, State1}
+ end},
+ #{desc => "stop client node",
+ cmd => fun(#{node := Node} = _State) ->
+ stop_node(Node)
+ end},
+ #{desc => "await client node termination",
+ cmd => fun(#{node := Node} = State) ->
+ receive
+ {nodedown, Node} ->
+ State1 = maps:remove(node_id, State),
+ State2 = maps:remove(node, State1),
+ {ok, State2}
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client 1",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client 2",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client 3",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_sa => ServerSA}}
+ end},
+
+ %% Start the client(s)
+ #{desc => "order client 1 start",
+ cmd => fun(#{client1 := Pid,
+ server_sa := ServerSA} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, {1, ServerSA}),
+ ok
+ end},
+ #{desc => "await client 1 ready (init)",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client1, init)
+ end},
+ #{desc => "order client 2 start",
+ cmd => fun(#{client2 := Pid,
+ server_sa := ServerSA} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, {2, ServerSA}),
+ ok
+ end},
+ #{desc => "await client 2 ready (init)",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client2, init)
+ end},
+ #{desc => "order client 3 start",
+ cmd => fun(#{client3 := Pid,
+ server_sa := ServerSA} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, {3, ServerSA}),
+ ok
+ end},
+ #{desc => "await client 3 ready (init)",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client3, init)
+ end},
+
+ %% The actual test
+ #{desc => "order server continue (accept)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client 1 continue (connect)",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, connect),
+ ok
+ end},
+ #{desc => "await client 1 ready (connect)",
+ cmd => fun(#{server := Server,
+ client1 := Client1,
+ client2 := Client2,
+ client3 := Client3} = _State) ->
+ ?SEV_AWAIT_READY(Client1, client1, connect,
+ [{server, Server},
+ {client2, Client2},
+ {client3, Client3}]),
+ ok
+ end},
+ #{desc => "order client 2 continue (connect)",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, connect),
+ ok
+ end},
+ #{desc => "await client 2 ready (connect)",
+ cmd => fun(#{server := Server,
+ client1 := Client1,
+ client2 := Client2,
+ client3 := Client3} = _State) ->
+ ?SEV_AWAIT_READY(Client2, client2, connect,
+ [{server, Server},
+ {client1, Client1},
+ {client3, Client3}]),
+ ok
+ end},
+ #{desc => "order client 3 continue (connect)",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, connect),
+ ok
+ end},
+ #{desc => "await client 3 ready (connect)",
+ cmd => fun(#{server := Server,
+ client1 := Client1,
+ client2 := Client2,
+ client3 := Client3} = _State) ->
+ ?SEV_AWAIT_READY(Client3, client3, connect,
+ [{server, Server},
+ {client1, Client1},
+ {client2, Client2}]),
+ ok
+ end},
+ #{desc => "await server ready (accept from all connections)",
+ cmd => fun(#{server := Server,
+ client1 := Client1,
+ client2 := Client2,
+ client3 := Client3} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, accept,
+ [{client1, Client1},
+ {client2, Client2},
+ {client3, Client3}]),
+ ok
+ end},
+ #{desc => "order server continue (recv for all connections)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client 1 continue (close)",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close),
+ ok
+ end},
+ #{desc => "await client 1 ready (close)",
+ cmd => fun(#{server := Server,
+ client1 := Client1,
+ client2 := Client2,
+ client3 := Client3} = _State) ->
+ ?SEV_AWAIT_READY(Client1, client1, close,
+ [{server, Server},
+ {client2, Client2},
+ {client3, Client3}]),
+ ok
+ end},
+ #{desc => "order client 2 continue (close)",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close),
+ ok
+ end},
+ #{desc => "await client 2 ready (close)",
+ cmd => fun(#{server := Server,
+ client1 := Client1,
+ client2 := Client2,
+ client3 := Client3} = _State) ->
+ ?SEV_AWAIT_READY(Client2, client2, close,
+ [{server, Server},
+ {client1, Client1},
+ {client3, Client3}]),
+ ok
+ end},
+ #{desc => "order client 3 continue (close)",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close),
+ ok
+ end},
+ #{desc => "await client 3 ready (close)",
+ cmd => fun(#{server := Server,
+ client1 := Client1,
+ client2 := Client2,
+ client3 := Client3} = _State) ->
+ ?SEV_AWAIT_READY(Client3, client1, close,
+ [{server, Server},
+ {client1, Client1},
+ {client2, Client2}]),
+ ok
+ end},
+ #{desc => "await server ready (close for all connections)",
+ cmd => fun(#{server := Server,
+ client1 := Client1,
+ client2 := Client2,
+ client3 := Client3} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv_closed,
+ [{client1, Client1},
+ {client2, Client2},
+ {client3, Client3}]),
+ ok
+ end},
+
+ %% Terminations
+ #{desc => "order client 1 to terminate",
+ cmd => fun(#{client1 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await client 1 termination",
+ cmd => fun(#{client1 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(client1, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order client 2 to terminate",
+ cmd => fun(#{client2 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await client 2 termination",
+ cmd => fun(#{client2 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(client2, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order client 3 to terminate",
+ cmd => fun(#{client3 := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await client 3 termination",
+ cmd => fun(#{client3 := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(client3, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(server, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ ServerInitState = InitState,
+ Server = ?SEV_START("server", ServerSeq, ServerInitState),
+
+ i("start client evaluator(s)"),
+ ClientInitState = InitState#{host => local_host()},
+ Client1 = ?SEV_START("client-1", ClientSeq, ClientInitState),
+ Client2 = ?SEV_START("client-2", ClientSeq, ClientInitState),
+ Client3 = ?SEV_START("client-3", ClientSeq, ClientInitState),
+
+ i("start 'tester' evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client1 => Client1#ev.pid,
+ client2 => Client2#ev.pid,
+ client3 => Client3#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Server,
+ Client1, Client2, Client3,
+ Tester]).
+
+
+sc_rc_tcp_client_start(Node) ->
+ Self = self(),
+ Fun = fun() -> sc_rc_tcp_client(Self) end,
+ erlang:spawn(Node, Fun).
+
+
+sc_rc_tcp_client(Parent) ->
+ sc_rc_tcp_client_init(Parent),
+ ServerSA = sc_rc_tcp_client_await_start(Parent),
+ Domain = maps:get(family, ServerSA),
+ Sock = sc_rc_tcp_client_create(Domain),
+ sc_rc_tcp_client_bind(Sock, Domain),
+ sc_rc_tcp_client_announce_ready(Parent, init),
+ sc_rc_tcp_client_await_continue(Parent, connect),
+ sc_rc_tcp_client_connect(Sock, ServerSA),
+ sc_rc_tcp_client_announce_ready(Parent, connect),
+ sc_rc_tcp_client_await_continue(Parent, close),
+ sc_rc_tcp_client_close(Sock),
+ sc_rc_tcp_client_announce_ready(Parent, close),
+ Reason = sc_rc_tcp_client_await_terminate(Parent),
+ ?SEV_IPRINT("terminate"),
+ exit(Reason).
+
+sc_rc_tcp_client_init(Parent) ->
+ put(sname, "rclient"),
+ ?SEV_IPRINT("init"),
+ _MRef = erlang:monitor(process, Parent),
+ ok.
+
+sc_rc_tcp_client_await_start(Parent) ->
+ i("sc_rc_tcp_client_await_start -> entry"),
+ ?SEV_AWAIT_START(Parent).
+
+sc_rc_tcp_client_create(Domain) ->
+ i("sc_rc_tcp_client_create -> entry"),
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ case socket:getopt(Sock, otp, fd) of
+ {ok, FD} ->
+ put(sname, f("rclient-~w", [FD])); % Update SName
+ _ ->
+ ok
+ end,
+ Sock;
+ {error, Reason} ->
+ exit({open_failed, Reason})
+ end.
+
+sc_rc_tcp_client_bind(Sock, Domain) ->
+ i("sc_rc_tcp_client_bind -> entry"),
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain,
+ addr => LAddr},
+ case socket:bind(Sock, LSA) of
+ {ok, _} ->
+ ok;
+ {error, Reason} ->
+ exit({bind, Reason})
+ end.
+
+sc_rc_tcp_client_announce_ready(Parent, Slogan) ->
+ ?SEV_IPRINT("ready ~w", [Slogan]),
+ ?SEV_ANNOUNCE_READY(Parent, Slogan).
+
+sc_rc_tcp_client_await_continue(Parent, Slogan) ->
+ ?SEV_IPRINT("await ~w continue", [Slogan]),
+ ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan).
+
+sc_rc_tcp_client_connect(Sock, ServerSA) ->
+ i("sc_rc_tcp_client_connect -> entry"),
+ case socket:connect(Sock, ServerSA) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ exit({connect, Reason})
+ end.
+
+sc_rc_tcp_client_close(Sock) ->
+ i("sc_rc_tcp_client_close -> entry"),
+ case socket:close(Sock) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ exit({close, Reason})
+ end.
+
+sc_rc_tcp_client_await_terminate(Parent) ->
+ i("sc_rc_tcp_client_await_terminate -> entry"),
+ case ?SEV_AWAIT_TERMINATE(Parent, parent) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ Reason
+ end.
+
+
+%% The handlers run on the same node as the server (the local node).
+
+sc_rc_tcp_handler_start(ID, Recv, Sock) ->
+ Self = self(),
+ Fun = fun() -> sc_rc_tcp_handler(ID, Self, Recv, Sock) end,
+ {Pid, _} = erlang:spawn_monitor(Fun),
+ Pid.
+
+sc_rc_tcp_handler(ID, Parent, Recv, Sock) ->
+ sc_rc_tcp_handler_init(ID, socket:getopt(Sock, otp, fd), Parent),
+ sc_rc_tcp_handler_await(Parent, recv),
+ RecvRes = sc_rc_tcp_handler_recv(Recv, Sock),
+ sc_rc_tcp_handler_announce_ready(Parent, recv, RecvRes),
+ Reason = sc_rc_tcp_handler_await(Parent, terminate),
+ exit(Reason).
+
+sc_rc_tcp_handler_init(ID, {ok, FD}, Parent) ->
+ put(sname, f("handler-~w:~w", [ID, FD])),
+ _MRef = erlang:monitor(process, Parent),
+ ?SEV_IPRINT("started"),
+ ?SEV_ANNOUNCE_READY(Parent, init),
+ ok.
+
+sc_rc_tcp_handler_await(Parent, terminate) ->
+ ?SEV_IPRINT("await terminate"),
+ ?SEV_AWAIT_TERMINATE(Parent, tester);
+sc_rc_tcp_handler_await(Parent, Slogan) ->
+ ?SEV_IPRINT("await ~w", [Slogan]),
+ ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan).
+
+sc_rc_tcp_handler_recv(Recv, Sock) ->
+ ?SEV_IPRINT("recv"),
+ try Recv(Sock) of
+ {error, closed} ->
+ ok;
+ {ok, _} ->
+ ?SEV_IPRINT("unexpected success"),
+ {error, unexpected_success};
+ {error, Reason} = ERROR ->
+ ?SEV_IPRINT("receive error: "
+ "~n ~p", [Reason]),
+ ERROR
+ catch
+ C:E:S ->
+ ?SEV_IPRINT("receive failure: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [C, E, S]),
+ {error, {recv, C, E, S}}
+ end.
+
+sc_rc_tcp_handler_announce_ready(Parent, Slogan, Result) ->
+ ?SEV_IPRINT("announce ready"),
+ ?SEV_ANNOUNCE_READY(Parent, Slogan, Result),
+ ok.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% remotely closed while the process is calling the recvmsg function.
+%% Socket is IPv4.
+
+sc_rc_recvmsg_response_tcp4(suite) ->
+ [];
+sc_rc_recvmsg_response_tcp4(doc) ->
+ [];
+sc_rc_recvmsg_response_tcp4(_Config) when is_list(_Config) ->
+ tc_try(sc_rc_recvmsg_response_tcp4,
+ fun() ->
+ ?TT(?SECS(30)),
+ Recv = fun(Sock) -> socket:recvmsg(Sock) end,
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp,
+ recv => Recv},
+ ok = sc_rc_receive_response_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% remotely closed while the process is calling the recvmsg function.
+%% Socket is IPv6.
+
+sc_rc_recvmsg_response_tcp6(suite) ->
+ [];
+sc_rc_recvmsg_response_tcp6(doc) ->
+ [];
+sc_rc_recvmsg_response_tcp6(_Config) when is_list(_Config) ->
+ tc_try(sc_rc_recvmsg_response_tcp6,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(10)),
+ Recv = fun(Sock) -> socket:recvmsg(Sock) end,
+ InitState = #{domain => inet6,
+ type => stream,
+ protocol => tcp,
+ recv => Recv},
+ ok = sc_rc_receive_response_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% remotely closed while the process is calling the recv function.
+%% The remote client sends data, then shutdown(write) and then the
+%% reader attempts a recv.
+%% Socket is IPv4.
+%%
+%% To minimize the chance of "weirdness", we should really have test cases
+%% where the two sides of the connection is on different machines. But for
+%% now, we will make do with different VMs on the same host.
+%%
+
+sc_rs_recv_send_shutdown_receive_tcp4(suite) ->
+ [];
+sc_rs_recv_send_shutdown_receive_tcp4(doc) ->
+ [];
+sc_rs_recv_send_shutdown_receive_tcp4(_Config) when is_list(_Config) ->
+ tc_try(sc_rs_recv_send_shutdown_receive_tcp4,
+ fun() ->
+ ?TT(?SECS(30)),
+ MsgData = ?DATA,
+ Recv = fun(Sock) ->
+ socket:recv(Sock)
+ end,
+ Send = fun(Sock, Data) ->
+ socket:send(Sock, Data)
+ end,
+ InitState = #{domain => inet,
+ recv => Recv,
+ send => Send,
+ data => MsgData},
+ ok = sc_rs_send_shutdown_receive_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% remotely closed while the process is calling the recv function.
+%% The remote client sends data, then shutdown(write) and then the
+%% reader attempts a recv.
+%% Socket is IPv6.
+
+sc_rs_recv_send_shutdown_receive_tcp6(suite) ->
+ [];
+sc_rs_recv_send_shutdown_receive_tcp6(doc) ->
+ [];
+sc_rs_recv_send_shutdown_receive_tcp6(_Config) when is_list(_Config) ->
+ tc_try(sc_rs_recv_send_shutdown_receive_tcp6,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(10)),
+ MsgData = ?DATA,
+ Recv = fun(Sock) ->
+ socket:recv(Sock)
+ end,
+ Send = fun(Sock, Data) ->
+ socket:send(Sock, Data)
+ end,
+ InitState = #{domain => inet6,
+ recv => Recv,
+ send => Send,
+ data => MsgData},
+ ok = sc_rs_send_shutdown_receive_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+sc_rs_send_shutdown_receive_tcp(InitState) ->
+ %% The connection is handled by a handler processes.
+ %% This are created (on the fly) and handled internally
+ %% by the server!
+ ServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ i("get local address for ~p", [Domain]),
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain, addr => LAddr},
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{lsock := LSock, local_sa := LSA} = State) ->
+ case socket:bind(LSock, LSA) of
+ {ok, Port} ->
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, local_sa := LSA, lport := Port}) ->
+ ServerSA = LSA#{port => Port},
+ ?SEV_ANNOUNCE_READY(Tester, init, ServerSA),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "accept",
+ cmd => fun(#{lsock := LSock, recv := Recv} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ ?SEV_IPRINT("accepted: try start handler"),
+ Handler =
+ sc_rs_tcp_handler_start(Recv, Sock),
+ ?SEV_IPRINT("handler started"),
+ {ok, State#{csock => Sock,
+ handler => Handler}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await handler ready (init)",
+ cmd => fun(#{tester := Tester,
+ handler := Handler} = _State) ->
+ ?SEV_AWAIT_READY(Handler, handler, init,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+
+ #{desc => "await continue (first recv)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv)
+ end},
+ #{desc => "order handler to receive (first)",
+ cmd => fun(#{handler := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ #{desc => "await ready from handler (first recv)",
+ cmd => fun(#{tester := Tester, handler := Pid} = _State) ->
+ case ?SEV_AWAIT_READY(Pid, handler, recv,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ ?SEV_IPRINT("first recv: ~p", [Result]),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (first recv)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv),
+ ok
+ end},
+ #{desc => "await continue (second recv)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv)
+ end},
+ #{desc => "order handler to receive (second)",
+ cmd => fun(#{handler := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ #{desc => "await ready from handler (second recv)",
+ cmd => fun(#{tester := Tester, handler := Pid} = _State) ->
+ case ?SEV_AWAIT_READY(Pid, handler, recv,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ ?SEV_IPRINT("second recv: ~p", [Result]),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (second recv)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order handler to terminate",
+ cmd => fun(#{handler := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await handler termination",
+ cmd => fun(#{handler := Pid} = State) ->
+ ?SEV_AWAIT_TERMINATION(Pid),
+ State1 = maps:remove(csock, State),
+ State2 = maps:remove(handler, State1),
+ {ok, State2}
+ end},
+ #{desc => "close listen socket",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:close(LSock) of
+ ok ->
+ {ok, maps:remove(lsock, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, ServerSA} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ server_sa => ServerSA}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "create node",
+ cmd => fun(#{host := Host} = State) ->
+ case start_node(Host, client) of
+ {ok, Node} ->
+ ?SEV_IPRINT("client node ~p started",
+ [Node]),
+ {ok, State#{node => Node}};
+ {error, Reason, _} ->
+ {error, Reason}
+ end
+ end},
+ #{desc => "monitor client node",
+ cmd => fun(#{node := Node} = _State) ->
+ true = erlang:monitor_node(Node, true),
+ ok
+ end},
+ #{desc => "start remote client on client node",
+ cmd => fun(#{node := Node,
+ send := Send} = State) ->
+ Pid = sc_rs_tcp_client_start(Node, Send),
+ ?SEV_IPRINT("client ~p started", [Pid]),
+ {ok, State#{rclient => Pid}}
+ end},
+ #{desc => "monitor remote client",
+ cmd => fun(#{rclient := Pid}) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order remote client to start",
+ cmd => fun(#{rclient := Client, server_sa := ServerSA}) ->
+ ?SEV_ANNOUNCE_START(Client, ServerSA),
+ ok
+ end},
+ #{desc => "await remote client ready",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, init,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, connect,
+ [{rclient, Client}]),
+ ok
+ end},
+ #{desc => "order remote client to continue (connect)",
+ cmd => fun(#{rclient := Client}) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, connect),
+ ok
+ end},
+ #{desc => "await client process ready (connect)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, connect,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+
+ #{desc => "await continue (send)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester, send,
+ [{rclient, Client}]) of
+ {ok, Data} ->
+ {ok, State#{rclient_data => Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to send",
+ cmd => fun(#{rclient := Client,
+ rclient_data := Data}) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, Data),
+ ok
+ end},
+ #{desc => "await remote client ready (closed)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, send,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (send)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send),
+ ok
+ end},
+
+
+ #{desc => "await continue (shutdown)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, shutdown,
+ [{rclient, Client}]),
+ ok
+ end},
+ #{desc => "order remote client to shutdown",
+ cmd => fun(#{rclient := Client}) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, shutdown),
+ ok
+ end},
+ #{desc => "await remote client ready (shiutdown)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, shutdown,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (shutdown)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, shutdown),
+ ok
+ end},
+
+ #{desc => "await continue (close)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, close,
+ [{rclient, Client}]),
+ ok
+ end},
+ #{desc => "order remote client to close",
+ cmd => fun(#{rclient := Client}) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, close),
+ ok
+ end},
+ #{desc => "await remote client ready (closed)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, close,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (close)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, close),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester,
+ [{rclient, Client}]) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "kill remote client",
+ cmd => fun(#{rclient := Client}) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await remote client termination",
+ cmd => fun(#{rclient := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(rclient, State),
+ {ok, State1}
+ end},
+ #{desc => "stop client node",
+ cmd => fun(#{node := Node} = _State) ->
+ stop_node(Node)
+ end},
+ #{desc => "await client node termination",
+ cmd => fun(#{node := Node} = State) ->
+ receive
+ {nodedown, Node} ->
+ State1 = maps:remove(node_id, State),
+ State2 = maps:remove(node, State1),
+ {ok, State2}
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_sa => ServerSA}}
+ end},
+
+ %% Start the client(s)
+ #{desc => "order client start",
+ cmd => fun(#{client := Pid,
+ server_sa := ServerSA} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, ServerSA),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, init)
+ end},
+
+ %% The actual test
+ #{desc => "order server continue (accept)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client continue (connect)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, connect),
+ ok
+ end},
+ #{desc => "await client ready (connect)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, connect,
+ [{server, Server}]),
+ ok
+ end},
+ #{desc => "await server ready (accept)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, accept,
+ [{client, Client}]),
+ ok
+ end},
+
+ #{desc => "order client continue (send)",
+ cmd => fun(#{client := Pid,
+ data := Data} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, send, Data),
+ ok
+ end},
+ #{desc => "await client ready (send)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, send,
+ [{server, Server}]),
+ ok
+ end},
+
+ #{desc => "order client continue (shutdown)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, shutdown),
+ ok
+ end},
+ #{desc => "await client ready (shutdown)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, shutdown,
+ [{server, Server}]),
+ ok
+ end},
+
+ #{desc => "order server continue (first recv)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ #{desc => "await server ready (first recv)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv,
+ [{client, Client}]),
+ ok
+ end},
+
+ #{desc => "order server continue (second recv)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ #{desc => "await server ready (second recv)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, recv,
+ [{client, Client}]),
+ ok
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "order client continue (close)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close),
+ ok
+ end},
+ #{desc => "await client ready (close)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, close,
+ [{server, Server}]),
+ ok
+ end},
+
+ %% Terminations
+ #{desc => "order client to terminate",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(client, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(server, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ ServerInitState = #{domain => maps:get(domain, InitState),
+ recv => maps:get(recv, InitState)},
+ Server = ?SEV_START("server", ServerSeq, ServerInitState),
+
+ i("start client evaluator"),
+ ClientInitState = #{host => local_host(),
+ domain => maps:get(domain, InitState),
+ send => maps:get(send, InitState)},
+ Client = ?SEV_START("client", ClientSeq, ClientInitState),
+
+ i("start 'tester' evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client => Client#ev.pid,
+ data => maps:get(data, InitState)},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+sc_rs_tcp_client_start(Node, Send) ->
+ Self = self(),
+ Fun = fun() -> sc_rs_tcp_client(Self, Send) end,
+ erlang:spawn(Node, Fun).
+
+
+sc_rs_tcp_client(Parent, Send) ->
+ sc_rs_tcp_client_init(Parent),
+ ServerSA = sc_rs_tcp_client_await_start(Parent),
+ Domain = maps:get(family, ServerSA),
+ Sock = sc_rs_tcp_client_create(Domain),
+ sc_rs_tcp_client_bind(Sock, Domain),
+ sc_rs_tcp_client_announce_ready(Parent, init),
+ sc_rs_tcp_client_await_continue(Parent, connect),
+ sc_rs_tcp_client_connect(Sock, ServerSA),
+ sc_rs_tcp_client_announce_ready(Parent, connect),
+ Data = sc_rs_tcp_client_await_continue(Parent, send),
+ sc_rs_tcp_client_send(Sock, Send, Data),
+ sc_rs_tcp_client_announce_ready(Parent, send),
+ sc_rs_tcp_client_await_continue(Parent, shutdown),
+ sc_rs_tcp_client_shutdown(Sock),
+ sc_rs_tcp_client_announce_ready(Parent, shutdown),
+ sc_rs_tcp_client_await_continue(Parent, close),
+ sc_rs_tcp_client_close(Sock),
+ sc_rs_tcp_client_announce_ready(Parent, close),
+ Reason = sc_rs_tcp_client_await_terminate(Parent),
+ ?SEV_IPRINT("terminate"),
+ exit(Reason).
+
+sc_rs_tcp_client_init(Parent) ->
+ put(sname, "rclient"),
+ ?SEV_IPRINT("init"),
+ _MRef = erlang:monitor(process, Parent),
+ ok.
+
+sc_rs_tcp_client_await_start(Parent) ->
+ i("sc_rs_tcp_client_await_start -> entry"),
+ ?SEV_AWAIT_START(Parent).
+
+sc_rs_tcp_client_create(Domain) ->
+ i("sc_rs_tcp_client_create -> entry"),
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ Sock;
+ {error, Reason} ->
+ exit({open_failed, Reason})
+ end.
+
+sc_rs_tcp_client_bind(Sock, Domain) ->
+ i("sc_rs_tcp_client_bind -> entry"),
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain,
+ addr => LAddr},
+ case socket:bind(Sock, LSA) of
+ {ok, _} ->
+ ok;
+ {error, Reason} ->
+ exit({bind, Reason})
+ end.
+
+sc_rs_tcp_client_announce_ready(Parent, Slogan) ->
+ ?SEV_ANNOUNCE_READY(Parent, Slogan).
+
+sc_rs_tcp_client_await_continue(Parent, Slogan) ->
+ i("sc_rs_tcp_client_await_continue -> entry"),
+ case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of
+ ok ->
+ ok;
+ {ok, Extra} ->
+ Extra;
+ {error, Reason} ->
+ exit({await_continue, Slogan, Reason})
+ end.
+
+
+sc_rs_tcp_client_connect(Sock, ServerSA) ->
+ i("sc_rs_tcp_client_connect -> entry"),
+ case socket:connect(Sock, ServerSA) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ exit({connect, Reason})
+ end.
+
+sc_rs_tcp_client_send(Sock, Send, Data) ->
+ i("sc_rs_tcp_client_send -> entry"),
+ case Send(Sock, Data) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ exit({send, Reason})
+ end.
+
+sc_rs_tcp_client_shutdown(Sock) ->
+ i("sc_rs_tcp_client_shutdown -> entry"),
+ case socket:shutdown(Sock, write) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ exit({shutdown, Reason})
+ end.
+
+sc_rs_tcp_client_close(Sock) ->
+ i("sc_rs_tcp_client_close -> entry"),
+ case socket:close(Sock) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ exit({close, Reason})
+ end.
+
+sc_rs_tcp_client_await_terminate(Parent) ->
+ i("sc_rs_tcp_client_await_terminate -> entry"),
+ case ?SEV_AWAIT_TERMINATE(Parent, parent) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ Reason
+ end.
+
+
+%% The handlers run on the same node as the server (the local node).
+
+sc_rs_tcp_handler_start(Recv, Sock) ->
+ Self = self(),
+ Fun = fun() -> sc_rs_tcp_handler(Self, Recv, Sock) end,
+ {Pid, _} = erlang:spawn_monitor(Fun),
+ Pid.
+
+sc_rs_tcp_handler(Parent, Recv, Sock) ->
+ sc_rs_tcp_handler_init(Parent),
+ sc_rs_tcp_handler_await(Parent, recv),
+ ok = sc_rs_tcp_handler_recv(Recv, Sock, true),
+ sc_rs_tcp_handler_announce_ready(Parent, recv, received),
+ sc_rs_tcp_handler_await(Parent, recv),
+ ok = sc_rs_tcp_handler_recv(Recv, Sock, false),
+ sc_rs_tcp_handler_announce_ready(Parent, recv, closed),
+ Reason = sc_rs_tcp_handler_await(Parent, terminate),
+ exit(Reason).
+
+sc_rs_tcp_handler_init(Parent) ->
+ put(sname, "handler"),
+ _MRef = erlang:monitor(process, Parent),
+ ?SEV_IPRINT("started"),
+ ?SEV_ANNOUNCE_READY(Parent, init),
+ ok.
+
+sc_rs_tcp_handler_await(Parent, terminate) ->
+ ?SEV_IPRINT("await terminate"),
+ ?SEV_AWAIT_TERMINATE(Parent, tester);
+sc_rs_tcp_handler_await(Parent, Slogan) ->
+ ?SEV_IPRINT("await ~w", [Slogan]),
+ ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan).
+
+%% This hould actually work - we leave it for now
+sc_rs_tcp_handler_recv(Recv, Sock, First) ->
+ ?SEV_IPRINT("recv"),
+ try Recv(Sock) of
+ {ok, _} when (First =:= true) ->
+ ok;
+ {error, closed} when (First =:= false) ->
+ ok;
+ {ok, _} ->
+ ?SEV_IPRINT("unexpected success"),
+ {error, unexpected_success};
+ {error, Reason} = ERROR ->
+ ?SEV_IPRINT("receive error: "
+ "~n ~p", [Reason]),
+ ERROR
+ catch
+ C:E:S ->
+ ?SEV_IPRINT("receive failure: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Stack: ~p", [C, E, S]),
+ {error, {recv, C, E, S}}
+ end.
+
+sc_rs_tcp_handler_announce_ready(Parent, Slogan, Result) ->
+ ?SEV_IPRINT("announce ready"),
+ ?SEV_ANNOUNCE_READY(Parent, Slogan, Result),
+ ok.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% remotely closed while the process is calling the recvmsg function.
+%% The remote client sends data, then shutdown(write) and then the
+%% reader attempts a recv.
+%% Socket is IPv4.
+
+sc_rs_recvmsg_send_shutdown_receive_tcp4(suite) ->
+ [];
+sc_rs_recvmsg_send_shutdown_receive_tcp4(doc) ->
+ [];
+sc_rs_recvmsg_send_shutdown_receive_tcp4(_Config) when is_list(_Config) ->
+ tc_try(sc_rs_recvmsg_send_shutdown_receive_tcp4,
+ fun() ->
+ ?TT(?SECS(30)),
+ MsgData = ?DATA,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock) of
+ {ok, #{addr := undefined,
+ iov := [Data]}} ->
+ {ok, Data};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ Send = fun(Sock, Data) when is_binary(Data) ->
+ MsgHdr = #{iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ InitState = #{domain => inet,
+ type => stream,
+ protocol => tcp,
+ recv => Recv,
+ send => Send,
+ data => MsgData},
+ ok = sc_rs_send_shutdown_receive_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test what happens when a socket is
+%% remotely closed while the process is calling the recvmsg function.
+%% The remote client sends data, then shutdown(write) and then the
+%% reader attempts a recv.
+%% Socket is IPv6.
+
+sc_rs_recvmsg_send_shutdown_receive_tcp6(suite) ->
+ [];
+sc_rs_recvmsg_send_shutdown_receive_tcp6(doc) ->
+ [];
+sc_rs_recvmsg_send_shutdown_receive_tcp6(_Config) when is_list(_Config) ->
+ tc_try(sc_rs_recvmsg_send_shutdown_receive_tcp6,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(10)),
+ MsgData = ?DATA,
+ Recv = fun(Sock) ->
+ case socket:recvmsg(Sock) of
+ {ok, #{addr := undefined,
+ iov := [Data]}} ->
+ {ok, Data};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ Send = fun(Sock, Data) when is_binary(Data) ->
+ MsgHdr = #{iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ InitState = #{domain => inet6,
+ type => stream,
+ protocol => tcp,
+ recv => Recv,
+ send => Send,
+ data => MsgData},
+ ok = sc_rs_send_shutdown_receive_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the send and recv functions
+%% behave as expected when sending and/or reading chunks.
+%% First send data in one "big" chunk, and read it in "small" chunks.
+%% Second, send in a bunch of "small" chunks, and read in one "big" chunk.
+%% Socket is IPv4.
+
+traffic_send_and_recv_chunks_tcp4(suite) ->
+ [];
+traffic_send_and_recv_chunks_tcp4(doc) ->
+ [];
+traffic_send_and_recv_chunks_tcp4(_Config) when is_list(_Config) ->
+ tc_try(traffic_send_and_recv_chunks_tcp4,
+ fun() ->
+ ?TT(?SECS(30)),
+ InitState = #{domain => inet},
+ ok = traffic_send_and_recv_chunks_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the send and recv functions
+%% behave as expected when sending and/or reading chunks.
+%% First send data in one "big" chunk, and read it in "small" chunks.
+%% Second, send in a bunch of "small" chunks, and read in one "big" chunk.
+%% Socket is IPv6.
+
+traffic_send_and_recv_chunks_tcp6(suite) ->
+ [];
+traffic_send_and_recv_chunks_tcp6(doc) ->
+ [];
+traffic_send_and_recv_chunks_tcp6(_Config) when is_list(_Config) ->
+ tc_try(traffic_send_and_recv_chunks_tcp6,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(30)),
+ InitState = #{domain => inet6},
+ ok = traffic_send_and_recv_chunks_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+traffic_send_and_recv_chunks_tcp(InitState) ->
+ ServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain, addr => LAddr},
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{lsock := LSock, local_sa := LSA} = State) ->
+ case socket:bind(LSock, LSA) of
+ {ok, Port} ->
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, local_sa := LSA, lport := Port}) ->
+ ServerSA = LSA#{port => Port},
+ ?SEV_ANNOUNCE_READY(Tester, init, ServerSA),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "accept",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ {ok, State#{csock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+
+ #{desc => "await continue (recv-many-small)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv_many_small)
+ end},
+ #{desc => "recv chunk 1",
+ cmd => fun(#{csock := Sock} = State) ->
+ case socket:recv(Sock, 100) of
+ {ok, Chunk} ->
+ ?SEV_IPRINT("recv of chunk 1 of ~p bytes",
+ [size(Chunk)]),
+ {ok, State#{chunks => [b2l(Chunk)]}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "recv chunk 2",
+ cmd => fun(#{csock := Sock,
+ chunks := Chunks} = State) ->
+ case socket:recv(Sock, 100) of
+ {ok, Chunk} ->
+ ?SEV_IPRINT("recv of chunk 2 of ~p bytes",
+ [size(Chunk)]),
+ {ok, State#{chunks => [b2l(Chunk)|Chunks]}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "recv chunk 3",
+ cmd => fun(#{csock := Sock,
+ chunks := Chunks} = State) ->
+ case socket:recv(Sock, 100) of
+ {ok, Chunk} ->
+ ?SEV_IPRINT("recv of chunk 3 of ~p bytes",
+ [size(Chunk)]),
+ {ok, State#{chunks => [b2l(Chunk)|Chunks]}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "recv chunk 4",
+ cmd => fun(#{csock := Sock,
+ chunks := Chunks} = State) ->
+ case socket:recv(Sock, 100) of
+ {ok, Chunk} ->
+ ?SEV_IPRINT("recv of chunk 4 of ~p bytes",
+ [size(Chunk)]),
+ {ok, State#{chunks => [b2l(Chunk)|Chunks]}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "recv chunk 5",
+ cmd => fun(#{csock := Sock,
+ chunks := Chunks} = State) ->
+ case socket:recv(Sock, 100) of
+ {ok, Chunk} ->
+ ?SEV_IPRINT("recv of chunk 5 of ~p bytes",
+ [size(Chunk)]),
+ {ok, State#{chunks => [b2l(Chunk)|Chunks]}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "recv chunk 6",
+ cmd => fun(#{csock := Sock,
+ chunks := Chunks} = State) ->
+ case socket:recv(Sock, 100) of
+ {ok, Chunk} ->
+ ?SEV_IPRINT("recv of chunk 6 of ~p bytes",
+ [size(Chunk)]),
+ {ok, State#{chunks => [b2l(Chunk)|Chunks]}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "recv chunk 7",
+ cmd => fun(#{csock := Sock,
+ chunks := Chunks} = State) ->
+ case socket:recv(Sock, 100) of
+ {ok, Chunk} ->
+ ?SEV_IPRINT("recv of chunk 7 of ~p bytes",
+ [size(Chunk)]),
+ {ok, State#{chunks => [b2l(Chunk)|Chunks]}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "recv chunk 8",
+ cmd => fun(#{csock := Sock,
+ chunks := Chunks} = State) ->
+ case socket:recv(Sock, 100) of
+ {ok, Chunk} ->
+ ?SEV_IPRINT("recv of chunk 8 of ~p bytes",
+ [size(Chunk)]),
+ {ok, State#{chunks => [b2l(Chunk)|Chunks]}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "recv chunk 9",
+ cmd => fun(#{csock := Sock,
+ chunks := Chunks} = State) ->
+ case socket:recv(Sock, 100) of
+ {ok, Chunk} ->
+ ?SEV_IPRINT("recv of chunk 9 of ~p bytes",
+ [size(Chunk)]),
+ {ok, State#{chunks => [b2l(Chunk)|Chunks]}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "recv chunk 10",
+ cmd => fun(#{csock := Sock,
+ chunks := Chunks} = State) ->
+ case socket:recv(Sock, 100) of
+ {ok, Chunk} ->
+ ?SEV_IPRINT("recv of chunk 10 of ~p bytes",
+ [size(Chunk)]),
+ {ok, State#{chunks => [b2l(Chunk)|Chunks]}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv-many-small)",
+ cmd => fun(#{tester := Tester,
+ chunks := Chunks} = State) ->
+ Data = lists:flatten(lists:reverse(Chunks)),
+ ?SEV_ANNOUNCE_READY(Tester, recv_many_small, Data),
+ {ok, maps:remove(chunks, State)}
+ end},
+
+ #{desc => "await continue (recv-one-big)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester, recv_one_big) of
+ {ok, Size} ->
+ {ok, State#{size => Size}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "recv (one big)",
+ cmd => fun(#{tester := Tester, csock := Sock, size := Size} = _State) ->
+ case socket:recv(Sock, Size) of
+ {ok, Data} ->
+ ?SEV_ANNOUNCE_READY(Tester,
+ recv_one_big,
+ b2l(Data)),
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close connection socket (just in case)",
+ cmd => fun(#{csock := Sock} = State) ->
+ (catch socket:close(Sock)),
+ {ok, maps:remove(csock, State)}
+ end},
+ #{desc => "close listen socket",
+ cmd => fun(#{lsock := Sock} = State) ->
+ (catch socket:close(Sock)),
+ {ok, maps:remove(lsock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, ServerSA} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ server_sa => ServerSA}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "create node",
+ cmd => fun(#{host := Host} = State) ->
+ case start_node(Host, client) of
+ {ok, Node} ->
+ ?SEV_IPRINT("(remote) client node ~p started",
+ [Node]),
+ {ok, State#{node => Node}};
+ {error, Reason, _} ->
+ {error, Reason}
+ end
+ end},
+ #{desc => "monitor client node",
+ cmd => fun(#{node := Node} = _State) ->
+ true = erlang:monitor_node(Node, true),
+ ok
+ end},
+ #{desc => "start remote client",
+ cmd => fun(#{node := Node} = State) ->
+ Pid = traffic_snr_tcp_client_start(Node),
+ ?SEV_IPRINT("client ~p started", [Pid]),
+ {ok, State#{rclient => Pid}}
+ end},
+ #{desc => "monitor remote client",
+ cmd => fun(#{rclient := Pid}) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order remote client to start",
+ cmd => fun(#{rclient := Client, server_sa := ServerSA}) ->
+ ?SEV_ANNOUNCE_START(Client, ServerSA),
+ ok
+ end},
+ #{desc => "await remote client ready",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, init,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, connect,
+ [{rclient, Client}]),
+ ok
+ end},
+ #{desc => "order remote client to continue (connect)",
+ cmd => fun(#{rclient := Client}) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, connect),
+ ok
+ end},
+ #{desc => "await client process ready (connect)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, rclient, connect,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+
+ #{desc => "await continue (send-one-big)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester,
+ send_one_big,
+ [{rclient, Client}]) of
+ {ok, Data} ->
+ {ok, State#{data => Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to continue (send)",
+ cmd => fun(#{rclient := Client, data := Data}) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, Data),
+ ok
+ end},
+ #{desc => "await client process ready (send)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, rclient, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (send-one-big)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_one_big),
+ ok
+ end},
+
+ #{desc => "await continue (send-many-small)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = State) ->
+ case ?SEV_AWAIT_CONTINUE(Tester, tester,
+ send_many_small,
+ [{rclient, Client}]) of
+ {ok, Data} ->
+ {ok, State#{data => Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to continue (send chunk 1)",
+ cmd => fun(#{rclient := Client,
+ data := Data} = State) ->
+ {Chunk, RestData} = lists:split(100, Data),
+ %% ?SEV_IPRINT("order send of chunk 1: "
+ %% "~n Size: ~p"
+ %% "~n ~p", [length(Chunk), Chunk]),
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk),
+ {ok, State#{data => RestData}}
+ end},
+ #{desc => "await client process ready (send chunk 1)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, rclient, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to continue (send chunk 2)",
+ cmd => fun(#{rclient := Client,
+ data := Data} = State) ->
+ {Chunk, RestData} = lists:split(100, Data),
+ %% ?SEV_IPRINT("order send of chunk 2: "
+ %% "~n Size: ~p"
+ %% "~n ~p", [length(Chunk), Chunk]),
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk),
+ {ok, State#{data => RestData}}
+ end},
+ #{desc => "await client process ready (send chunk 2)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, rclient, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to continue (send chunk 3)",
+ cmd => fun(#{rclient := Client,
+ data := Data} = State) ->
+ {Chunk, RestData} = lists:split(100, Data),
+ %% ?SEV_IPRINT("order send of chunk 3: "
+ %% "~n Size: ~p"
+ %% "~n ~p", [length(Chunk), Chunk]),
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk),
+ {ok, State#{data => RestData}}
+ end},
+ #{desc => "await client process ready (send chunk 3)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, rclient, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to continue (send chunk 4)",
+ cmd => fun(#{rclient := Client,
+ data := Data} = State) ->
+ {Chunk, RestData} = lists:split(100, Data),
+ %% ?SEV_IPRINT("order send of chunk 4: "
+ %% "~n Size: ~p"
+ %% "~n ~p", [length(Chunk), Chunk]),
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk),
+ {ok, State#{data => RestData}}
+ end},
+ #{desc => "await client process ready (send chunk 4)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, rclient, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to continue (send chunk 5)",
+ cmd => fun(#{rclient := Client,
+ data := Data} = State) ->
+ {Chunk, RestData} = lists:split(100, Data),
+ %% ?SEV_IPRINT("order send of chunk 5: "
+ %% "~n Size: ~p"
+ %% "~n ~p", [length(Chunk), Chunk]),
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk),
+ {ok, State#{data => RestData}}
+ end},
+ #{desc => "await client process ready (send chunk 5)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, rclient, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to continue (send chunk 6)",
+ cmd => fun(#{rclient := Client,
+ data := Data} = State) ->
+ {Chunk, RestData} = lists:split(100, Data),
+ %% ?SEV_IPRINT("order send of chunk 6: "
+ %% "~n Size: ~p"
+ %% "~n ~p", [length(Chunk), Chunk]),
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk),
+ {ok, State#{data => RestData}}
+ end},
+ #{desc => "await client process ready (send chunk 6)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, rclient, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to continue (send chunk 7)",
+ cmd => fun(#{rclient := Client,
+ data := Data} = State) ->
+ {Chunk, RestData} = lists:split(100, Data),
+ %% ?SEV_IPRINT("order send of chunk 7: "
+ %% "~n Size: ~p"
+ %% "~n ~p", [length(Chunk), Chunk]),
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk),
+ {ok, State#{data => RestData}}
+ end},
+ #{desc => "await client process ready (send chunk 7)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, rclient, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to continue (send chunk 8)",
+ cmd => fun(#{rclient := Client,
+ data := Data} = State) ->
+ {Chunk, RestData} = lists:split(100, Data),
+ %% ?SEV_IPRINT("order send of chunk 8: "
+ %% "~n Size: ~p"
+ %% "~n ~p", [length(Chunk), Chunk]),
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk),
+ {ok, State#{data => RestData}}
+ end},
+ #{desc => "await client process ready (send chunk 8)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, rclient, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to continue (send chunk 9)",
+ cmd => fun(#{rclient := Client,
+ data := Data} = State) ->
+ {Chunk, RestData} = lists:split(100, Data),
+ %% ?SEV_IPRINT("order send of chunk 9: "
+ %% "~n Size: ~p"
+ %% "~n ~p", [length(Chunk), Chunk]),
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk),
+ {ok, State#{data => RestData}}
+ end},
+ #{desc => "await client process ready (send chunk 9)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, rclient, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to continue (send chunk 10)",
+ cmd => fun(#{rclient := Client,
+ data := Data} = State) ->
+ {Chunk, []} = lists:split(100, Data),
+ %% ?SEV_IPRINT("order send of chunk 10: "
+ %% "~n Size: ~p"
+ %% "~n ~p", [length(Chunk), Chunk]),
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk),
+ {ok, maps:remove(data, State)}
+ end},
+ #{desc => "await client process ready (send chunk 10)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, rclient, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order remote client to continue (send stop)",
+ cmd => fun(#{rclient := Client} = State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, send, stop),
+ {ok, maps:remove(data, State)}
+ end},
+ #{desc => "await client process ready (send stop)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Client, rclient, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ Result;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (send-many-small)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, send_many_small),
+ ok
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester,
+ rclient := Client} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester,
+ [{rclient, Client}]) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "kill remote client",
+ cmd => fun(#{rclient := Client}) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await remote client termination",
+ cmd => fun(#{rclient := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(rclient, State),
+ {ok, State1}
+ end},
+ #{desc => "stop client node",
+ cmd => fun(#{node := Node} = _State) ->
+ stop_node(Node)
+ end},
+ #{desc => "await client node termination",
+ cmd => fun(#{node := Node} = State) ->
+ receive
+ {nodedown, Node} ->
+ {ok, maps:remove(node, State)}
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_sa => ServerSA}}
+ end},
+
+ %% Start the client
+ #{desc => "order client start",
+ cmd => fun(#{client := Pid,
+ server_sa := ServerSA} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, ServerSA),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, init)
+ end},
+
+ %% The actual test
+ #{desc => "order server continue (accept)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client continue (connect)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, connect),
+ ok
+ end},
+ #{desc => "await server ready (accept)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, accept,
+ [{client, Client}]),
+ ok
+ end},
+ #{desc => "await client ready (connect)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, connect,
+ [{server, Server}])
+ end},
+
+ #{desc => "generate data",
+ cmd => fun(State) ->
+ D1 = lists:seq(1,250),
+ D2 = lists:duplicate(4, D1),
+ D3 = lists:flatten(D2),
+ {ok, State#{data => D3}}
+ end},
+
+ %% (client) Send one big and (server) recv may small
+ #{desc => "order server continue (recv-many-small)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv_many_small),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client continue (send-one-big)",
+ cmd => fun(#{client := Pid, data := Data} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, send_one_big, Data),
+ ok
+ end},
+ #{desc => "await client ready (send-one-big)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ok = ?SEV_AWAIT_READY(Client, client, send_one_big,
+ [{server, Server}])
+ end},
+ #{desc => "await server ready (recv-many-small)",
+ cmd => fun(#{server := Server,
+ client := Client,
+ data := Data} = _State) ->
+ case ?SEV_AWAIT_READY(Server, server, recv_many_small,
+ [{client, Client}]) of
+ {ok, Data} ->
+ ok;
+ {ok, OtherData} ->
+ {error, {mismatched_data, Data, OtherData}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ #{desc => "order server continue (recv-one-big)",
+ cmd => fun(#{server := Pid, data := Data} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv_one_big, length(Data)),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client continue (send-many-small)",
+ cmd => fun(#{client := Pid, data := Data} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, send_many_small, Data),
+ ok
+ end},
+ #{desc => "await client ready (send-many-small)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ok = ?SEV_AWAIT_READY(Client, client, send_many_small,
+ [{server, Server}])
+ end},
+ #{desc => "await server ready (recv-one-big)",
+ cmd => fun(#{server := Server,
+ client := Client,
+ data := Data} = State) ->
+ case ?SEV_AWAIT_READY(Server, server, recv_one_big,
+ [{client, Client}]) of
+ {ok, Data} ->
+ {ok, maps:remove(data, State)};
+ {ok, OtherData} ->
+ {error, {mismatched_data, Data, OtherData}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+ %% Terminations
+ #{desc => "order client to terminate",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(client, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(server, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ ServerInitState = InitState,
+ Server = ?SEV_START("server", ServerSeq, ServerInitState),
+
+ i("start client evaluator(s)"),
+ ClientInitState = InitState#{host => local_host()},
+ Client = ?SEV_START("client", ClientSeq, ClientInitState),
+
+ i("start 'tester' evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client => Client#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+
+traffic_snr_tcp_client_start(Node) ->
+ Self = self(),
+ Fun = fun() -> traffic_snr_tcp_client(Self) end,
+ erlang:spawn(Node, Fun).
+
+traffic_snr_tcp_client(Parent) ->
+ {Sock, ServerSA} = traffic_snr_tcp_client_init(Parent),
+ traffic_snr_tcp_client_announce_ready(Parent, init),
+ traffic_snr_tcp_client_await_continue(Parent, connect),
+ traffic_snr_tcp_client_connect(Sock, ServerSA),
+ traffic_snr_tcp_client_announce_ready(Parent, connect),
+ traffic_snr_tcp_client_send_loop(Parent, Sock),
+ Reason = traffic_snr_tcp_client_await_terminate(Parent),
+ traffic_snr_tcp_client_close(Sock),
+ exit(Reason).
+
+
+traffic_snr_tcp_client_send_loop(Parent, Sock) ->
+ case ?SEV_AWAIT_CONTINUE(Parent, parent, send) of
+ {ok, stop} -> % Breakes the loop
+ ?SEV_ANNOUNCE_READY(Parent, send, ok),
+ ok;
+ {ok, Data} ->
+ case socket:send(Sock, Data) of
+ ok ->
+ ?SEV_ANNOUNCE_READY(Parent, send, ok),
+ traffic_snr_tcp_client_send_loop(Parent, Sock);
+ {error, Reason} = ERROR ->
+ ?SEV_ANNOUNCE_READY(Parent, send, ERROR),
+ exit({send, Reason})
+ end;
+ {error, Reason} ->
+ exit({await_continue, Reason})
+ end.
+
+traffic_snr_tcp_client_init(Parent) ->
+ put(sname, "rclient"),
+ ?SEV_IPRINT("init"),
+ _MRef = erlang:monitor(process, Parent),
+ ServerSA = traffic_snr_tcp_client_await_start(Parent),
+ Domain = maps:get(family, ServerSA),
+ Sock = traffic_snr_tcp_client_create(Domain),
+ traffic_snr_tcp_client_bind(Sock, Domain),
+ {Sock, ServerSA}.
+
+traffic_snr_tcp_client_await_start(Parent) ->
+ i("traffic_snr_tcp_client_await_start -> entry"),
+ ?SEV_AWAIT_START(Parent).
+
+traffic_snr_tcp_client_create(Domain) ->
+ i("traffic_snr_tcp_client_create -> entry"),
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ Sock;
+ {error, Reason} ->
+ exit({open_failed, Reason})
+ end.
+
+traffic_snr_tcp_client_bind(Sock, Domain) ->
+ i("traffic_snr_tcp_client_bind -> entry"),
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain,
+ addr => LAddr},
+ case socket:bind(Sock, LSA) of
+ {ok, _} ->
+ ok;
+ {error, Reason} ->
+ exit({bind, Reason})
+ end.
+
+traffic_snr_tcp_client_announce_ready(Parent, Slogan) ->
+ ?SEV_ANNOUNCE_READY(Parent, Slogan).
+
+traffic_snr_tcp_client_await_continue(Parent, Slogan) ->
+ i("traffic_snr_tcp_client_await_continue -> entry"),
+ ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan).
+
+traffic_snr_tcp_client_connect(Sock, ServerSA) ->
+ i("traffic_snr_tcp_client_connect -> entry"),
+ case socket:connect(Sock, ServerSA) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ exit({connect, Reason})
+ end.
+
+traffic_snr_tcp_client_close(Sock) ->
+ i("traffic_snr_tcp_client_close -> entry"),
+ case socket:close(Sock) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ exit({close, Reason})
+ end.
+
+traffic_snr_tcp_client_await_terminate(Parent) ->
+ i("traffic_snr_tcp_client_await_terminate -> entry"),
+ case ?SEV_AWAIT_TERMINATE(Parent, parent) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ Reason
+ end.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the send and recv functions
+%% by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'small' message test case, for IPv4.
+
+traffic_ping_pong_small_send_and_recv_tcp4(suite) ->
+ [];
+traffic_ping_pong_small_send_and_recv_tcp4(doc) ->
+ [];
+traffic_ping_pong_small_send_and_recv_tcp4(_Config) when is_list(_Config) ->
+ Msg = l2b(?TPP_SMALL),
+ Num = ?TPP_SMALL_NUM,
+ tc_try(traffic_ping_pong_small_send_and_recv_tcp4,
+ fun() ->
+ ?TT(?SECS(15)),
+ InitState = #{domain => inet,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_send_and_recv_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the send and recv functions
+%% by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'small' message test case, for IPv6.
+
+traffic_ping_pong_small_send_and_recv_tcp6(suite) ->
+ [];
+traffic_ping_pong_small_send_and_recv_tcp6(doc) ->
+ [];
+traffic_ping_pong_small_send_and_recv_tcp6(_Config) when is_list(_Config) ->
+ Msg = l2b(?TPP_SMALL),
+ Num = ?TPP_SMALL_NUM,
+ tc_try(traffic_ping_pong_small_send_and_recv_tcp6,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(15)),
+ InitState = #{domain => inet6,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_send_and_recv_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the send and recv functions
+%% by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'medium' message test case, for IPv4.
+
+traffic_ping_pong_medium_send_and_recv_tcp4(suite) ->
+ [];
+traffic_ping_pong_medium_send_and_recv_tcp4(doc) ->
+ [];
+traffic_ping_pong_medium_send_and_recv_tcp4(_Config) when is_list(_Config) ->
+ Msg = l2b(?TPP_MEDIUM),
+ Num = ?TPP_MEDIUM_NUM,
+ tc_try(traffic_ping_pong_medium_send_and_recv_tcp4,
+ fun() ->
+ ?TT(?SECS(30)),
+ InitState = #{domain => inet,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_send_and_recv_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the send and recv functions
+%% by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'medium' message test case, for IPv6.
+
+traffic_ping_pong_medium_send_and_recv_tcp6(suite) ->
+ [];
+traffic_ping_pong_medium_send_and_recv_tcp6(doc) ->
+ [];
+traffic_ping_pong_medium_send_and_recv_tcp6(_Config) when is_list(_Config) ->
+ Msg = l2b(?TPP_MEDIUM),
+ Num = ?TPP_MEDIUM_NUM,
+ tc_try(traffic_ping_pong_medium_send_and_recv_tcp6,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(30)),
+ InitState = #{domain => inet6,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_send_and_recv_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the send and recv functions
+%% by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'large' message test case, for IPv4.
+
+traffic_ping_pong_large_send_and_recv_tcp4(suite) ->
+ [];
+traffic_ping_pong_large_send_and_recv_tcp4(doc) ->
+ [];
+traffic_ping_pong_large_send_and_recv_tcp4(_Config) when is_list(_Config) ->
+ Msg = l2b(?TPP_LARGE),
+ Num = ?TPP_LARGE_NUM,
+ tc_try(traffic_ping_pong_large_send_and_recv_tcp4,
+ fun() ->
+ ?TT(?SECS(45)),
+ InitState = #{domain => inet,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_send_and_recv_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the send and recv functions
+%% by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'large' message test case, for IPv6.
+
+traffic_ping_pong_large_send_and_recv_tcp6(suite) ->
+ [];
+traffic_ping_pong_large_send_and_recv_tcp6(doc) ->
+ [];
+traffic_ping_pong_large_send_and_recv_tcp6(_Config) when is_list(_Config) ->
+ Msg = l2b(?TPP_LARGE),
+ Num = ?TPP_LARGE_NUM,
+ tc_try(traffic_ping_pong_large_send_and_recv_tcp6,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(45)),
+ InitState = #{domain => inet6,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_send_and_recv_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendto and recvfrom
+%% functions by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for two different message sizes;
+%% small (8 bytes) and medium (8K).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'small' message test case, for IPv4.
+
+traffic_ping_pong_small_sendto_and_recvfrom_udp4(suite) ->
+ [];
+traffic_ping_pong_small_sendto_and_recvfrom_udp4(doc) ->
+ [];
+traffic_ping_pong_small_sendto_and_recvfrom_udp4(_Config) when is_list(_Config) ->
+ Msg = l2b(?TPP_SMALL),
+ Num = ?TPP_SMALL_NUM,
+ tc_try(traffic_ping_pong_small_sendto_and_recvfrom_udp4,
+ fun() ->
+ ?TT(?SECS(45)),
+ InitState = #{domain => inet,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendto_and_recvfrom_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendto and recvfrom
+%% functions by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for two different message sizes;
+%% small (8 bytes) and medium (8K).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'small' message test case, for IPv6.
+
+traffic_ping_pong_small_sendto_and_recvfrom_udp6(suite) ->
+ [];
+traffic_ping_pong_small_sendto_and_recvfrom_udp6(doc) ->
+ [];
+traffic_ping_pong_small_sendto_and_recvfrom_udp6(_Config) when is_list(_Config) ->
+ Msg = l2b(?TPP_SMALL),
+ Num = ?TPP_SMALL_NUM,
+ tc_try(traffic_ping_pong_small_sendto_and_recvfrom_udp6,
+ fun() ->
+ ?TT(?SECS(45)),
+ InitState = #{domain => inet,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendto_and_recvfrom_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendto and recvfrom
+%% functions by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for two different message sizes;
+%% small (8 bytes) and medium (8K).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'medium' message test case, for IPv4.
+
+traffic_ping_pong_medium_sendto_and_recvfrom_udp4(suite) ->
+ [];
+traffic_ping_pong_medium_sendto_and_recvfrom_udp4(doc) ->
+ [];
+traffic_ping_pong_medium_sendto_and_recvfrom_udp4(_Config) when is_list(_Config) ->
+ Msg = l2b(?TPP_MEDIUM),
+ Num = ?TPP_MEDIUM_NUM,
+ tc_try(traffic_ping_pong_medium_sendto_and_recvfrom_udp4,
+ fun() ->
+ ?TT(?SECS(45)),
+ InitState = #{domain => inet,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendto_and_recvfrom_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendto and recvfrom
+%% functions by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for two different message sizes;
+%% small (8 bytes) and medium (8K).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'medium' message test case, for IPv6.
+
+traffic_ping_pong_medium_sendto_and_recvfrom_udp6(suite) ->
+ [];
+traffic_ping_pong_medium_sendto_and_recvfrom_udp6(doc) ->
+ [];
+traffic_ping_pong_medium_sendto_and_recvfrom_udp6(_Config) when is_list(_Config) ->
+ Msg = l2b(?TPP_MEDIUM),
+ Num = ?TPP_MEDIUM_NUM,
+ tc_try(traffic_ping_pong_medium_sendto_and_recvfrom_udp6,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(45)),
+ InitState = #{domain => inet6,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendto_and_recvfrom_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendmsg and recvmsg
+%% functions by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'small' message test case, for IPv4.
+
+traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4(suite) ->
+ [];
+traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4(doc) ->
+ [];
+traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) ->
+ Msg = l2b(?TPP_SMALL),
+ Num = ?TPP_SMALL_NUM,
+ tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4,
+ fun() ->
+ ?TT(?SECS(20)),
+ InitState = #{domain => inet,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendmsg and recvmsg functions
+%% by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'small' message test case, for IPv6.
+
+traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6(suite) ->
+ [];
+traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6(doc) ->
+ [];
+traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6(_Config) when is_list(_Config) ->
+ Msg = l2b(?TPP_SMALL),
+ Num = ?TPP_SMALL_NUM,
+ tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(20)),
+ InitState = #{domain => inet6,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendmsg and recvmsg
+%% functions by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'medium' message test case, for IPv4.
+
+traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4(suite) ->
+ [];
+traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4(doc) ->
+ [];
+traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) ->
+ Msg = l2b(?TPP_MEDIUM),
+ Num = ?TPP_MEDIUM_NUM,
+ tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4,
+ fun() ->
+ ?TT(?SECS(30)),
+ InitState = #{domain => inet,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendmsg and recvmsg functions
+%% by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'medium' message test case, for IPv6.
+
+traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6(suite) ->
+ [];
+traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6(doc) ->
+ [];
+traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6(_Config) when is_list(_Config) ->
+ Msg = l2b(?TPP_MEDIUM),
+ Num = ?TPP_MEDIUM_NUM,
+ tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(20)),
+ InitState = #{domain => ine6,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendmsg and recvmsg
+%% functions by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'large' message test case, for IPv4.
+
+traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4(suite) ->
+ [];
+traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4(doc) ->
+ [];
+traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) ->
+ Msg = l2b(?TPP_LARGE),
+ Num = ?TPP_LARGE_NUM,
+ tc_try(traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4,
+ fun() ->
+ ?TT(?SECS(30)),
+ InitState = #{domain => inet,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendmsg and recvmsg functions
+%% by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes), medium (8K) and large (8M).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'large' message test case, for IPv6.
+
+traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6(suite) ->
+ [];
+traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6(doc) ->
+ [];
+traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6(_Config) when is_list(_Config) ->
+ Msg = l2b(?TPP_LARGE),
+ Num = ?TPP_LARGE_NUM,
+ tc_try(traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(30)),
+ InitState = #{domain => inet6,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendmsg and recvmsg
+%% functions by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes) and medium (8K).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'small' message test case, for IPv4.
+
+traffic_ping_pong_small_sendmsg_and_recvmsg_udp4(suite) ->
+ [];
+traffic_ping_pong_small_sendmsg_and_recvmsg_udp4(doc) ->
+ [];
+traffic_ping_pong_small_sendmsg_and_recvmsg_udp4(_Config) when is_list(_Config) ->
+ Msg = l2b(?TPP_SMALL),
+ Num = ?TPP_SMALL_NUM,
+ tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_udp4,
+ fun() ->
+ ?TT(?SECS(20)),
+ InitState = #{domain => inet,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendmsg and recvmsg functions
+%% by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes) and medium (8K).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'small' message test case, for IPv6.
+
+traffic_ping_pong_small_sendmsg_and_recvmsg_udp6(suite) ->
+ [];
+traffic_ping_pong_small_sendmsg_and_recvmsg_udp6(doc) ->
+ [];
+traffic_ping_pong_small_sendmsg_and_recvmsg_udp6(_Config) when is_list(_Config) ->
+ Msg = l2b(?TPP_SMALL),
+ Num = ?TPP_SMALL_NUM,
+ tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_udp6,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(20)),
+ InitState = #{domain => inet,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendmsg and recvmsg
+%% functions by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes) and medium (8K).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'medium' message test case, for IPv4.
+
+traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4(suite) ->
+ [];
+traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4(doc) ->
+ [];
+traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4(_Config) when is_list(_Config) ->
+ Msg = l2b(?TPP_MEDIUM),
+ Num = ?TPP_MEDIUM_NUM,
+ tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4,
+ fun() ->
+ ?TT(?SECS(30)),
+ InitState = #{domain => inet,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case is intended to test that the sendmsg and recvmsg
+%% functions by repeatedly sending a meassage between two entities.
+%% The same basic test case is used for three different message sizes;
+%% small (8 bytes) and medium (8K).
+%% The message is sent from A to B and then back again. This is
+%% repeated a set number of times (more times the small the message).
+%% This is the 'medium' message test case, for IPv6.
+
+traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6(suite) ->
+ [];
+traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6(doc) ->
+ [];
+traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6(_Config) when is_list(_Config) ->
+ Msg = l2b(?TPP_MEDIUM),
+ Num = ?TPP_MEDIUM_NUM,
+ tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6,
+ fun() ->
+ not_yet_implemented(),
+ ?TT(?SECS(20)),
+ InitState = #{domain => ine6,
+ msg => Msg,
+ num => Num},
+ ok = traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState)
+ end).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Ping-Pong for TCP
+
+traffic_ping_pong_send_and_recv_tcp(InitState) ->
+ Send = fun(Sock, Data) -> socket:send(Sock, Data) end,
+ Recv = fun(Sock, Sz) -> socket:recv(Sock, Sz) end,
+ InitState2 = InitState#{send => Send, % Send function
+ recv => Recv % Receive function
+ },
+ traffic_ping_pong_send_and_receive_tcp(InitState2).
+
+traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) ->
+ Send = fun(Sock, Data) when is_binary(Data) ->
+ MsgHdr = #{iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr);
+ (Sock, Data) when is_list(Data) -> %% We assume iovec...
+ MsgHdr = #{iov => Data},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock, Sz) ->
+ case socket:recvmsg(Sock, Sz, 0) of
+ {ok, #{addr := undefined,
+ iov := [Data]}} ->
+ {ok, Data};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState2 = InitState#{send => Send, % Send function
+ recv => Recv % Receive function
+ },
+ traffic_ping_pong_send_and_receive_tcp(InitState2).
+
+
+traffic_ping_pong_send_and_receive_tcp(#{msg := Msg} = InitState) ->
+ Fun = fun(Sock) ->
+ {ok, RcvSz} = socket:getopt(Sock, socket, rcvbuf),
+ ?SEV_IPRINT("RcvBuf is ~p (needs atleast ~p)",
+ [RcvSz, 16+size(Msg)]),
+ if (RcvSz < size(Msg)) ->
+ case socket:setopt(Sock,
+ socket, rcvbuf, 1024+size(Msg)) of
+ ok ->
+ ok;
+ {error, enobufs} ->
+ skip({failed_change, rcvbuf});
+ {error, Reason1} ->
+ ?FAIL({rcvbuf, Reason1})
+ end;
+ true ->
+ ok
+ end,
+ {ok, SndSz} = socket:getopt(Sock, socket, sndbuf),
+ ?SEV_IPRINT("SndBuf is ~p (needs atleast ~p)",
+ [SndSz, 16+size(Msg)]),
+ if (SndSz < size(Msg)) ->
+ case socket:setopt(Sock,
+ socket, sndbuf, 1024+size(Msg)) of
+ ok ->
+ ok;
+ {error, enobufs} ->
+ skip({failed_change, sndbuf});
+ {error, Reason2} ->
+ ?FAIL({sndbuf, Reason2})
+ end;
+ true ->
+ ok
+ end,
+ ok = socket:setopt(Sock, otp, rcvbuf, {12, 1024})
+ end,
+ traffic_ping_pong_send_and_receive_tcp2(InitState#{buf_init => Fun}).
+
+traffic_ping_pong_send_and_receive_tcp2(InitState) ->
+ ServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain, addr => LAddr},
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ {ok, State#{lsock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{lsock := LSock, local_sa := LSA} = State) ->
+ case socket:bind(LSock, LSA) of
+ {ok, Port} ->
+ {ok, State#{lport => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "maybe init buffers",
+ cmd => fun(#{lsock := LSock, buf_init := BufInit} = _State) ->
+ BufInit(LSock)
+ end},
+ #{desc => "make listen socket",
+ cmd => fun(#{lsock := LSock}) ->
+ socket:listen(LSock)
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, local_sa := LSA, lport := Port}) ->
+ ServerSA = LSA#{port => Port},
+ ?SEV_ANNOUNCE_READY(Tester, init, ServerSA),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (accept)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, accept)
+ end},
+ #{desc => "accept",
+ cmd => fun(#{lsock := LSock} = State) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ {ok, State#{csock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "create handler",
+ cmd => fun(State) ->
+ Handler = tpp_tcp_handler_create(),
+ ?SEV_IPRINT("handler created: ~p", [Handler]),
+ {ok, State#{handler => Handler}}
+ end},
+ #{desc => "monitor handler",
+ cmd => fun(#{handler := Handler} = _State) ->
+ _MRef = erlang:monitor(process, Handler),
+ ok
+ end},
+ #{desc => "transfer connection socket ownership to handler",
+ cmd => fun(#{handler := Handler, csock := Sock} = _State) ->
+ socket:setopt(Sock, otp, controlling_process, Handler)
+ end},
+ #{desc => "start handler",
+ cmd => fun(#{handler := Handler,
+ csock := Sock,
+ send := Send,
+ recv := Recv} = _State) ->
+ ?SEV_ANNOUNCE_START(Handler, {Sock, Send, Recv}),
+ ok
+ end},
+ #{desc => "await handler ready (init)",
+ cmd => fun(#{tester := Tester,
+ handler := Handler} = State) ->
+ case ?SEV_AWAIT_READY(Handler, handler, init,
+ [{tester, Tester}]) of
+ ok ->
+ {ok, maps:remove(csock, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (accept)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, accept),
+ ok
+ end},
+ #{desc => "await continue (recv)",
+ cmd => fun(#{tester := Tester,
+ handler := Handler} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv,
+ [{handler, Handler}])
+ end},
+ #{desc => "order handler to recv",
+ cmd => fun(#{handler := Handler} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Handler, recv),
+ ok
+ end},
+ #{desc => "await handler ready (recv)",
+ cmd => fun(#{tester := Tester,
+ handler := Handler} = State) ->
+ case ?SEV_AWAIT_READY(Handler, handler, recv,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ %% ?SEV_IPRINT("Result: ~p", [Result]),
+ {ok, State#{result => Result}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv)",
+ cmd => fun(#{tester := Tester,
+ result := Result} = State) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv, Result),
+ {ok, maps:remove(result, State)}
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "stop handler",
+ cmd => fun(#{handler := Handler}) ->
+ ?SEV_ANNOUNCE_TERMINATE(Handler),
+ ok
+ end},
+ #{desc => "await handler termination",
+ cmd => fun(#{handler := Handler} = State) ->
+ ?SEV_AWAIT_TERMINATION(Handler),
+ State1 = maps:remove(handler, State),
+ {ok, State1}
+ end},
+ #{desc => "close listen socket",
+ cmd => fun(#{lsock := Sock} = State) ->
+ (catch socket:close(Sock)),
+ {ok, maps:remove(lsock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, ServerSA} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ server_sa => ServerSA}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "create node",
+ cmd => fun(#{host := Host} = State) ->
+ case start_node(Host, client) of
+ {ok, Node} ->
+ ?SEV_IPRINT("(remote) client node ~p started",
+ [Node]),
+ {ok, State#{node => Node}};
+ {error, Reason, _} ->
+ {error, Reason}
+ end
+ end},
+ #{desc => "monitor client node",
+ cmd => fun(#{node := Node} = _State) ->
+ true = erlang:monitor_node(Node, true),
+ ok
+ end},
+ #{desc => "create remote client",
+ cmd => fun(#{node := Node} = State) ->
+ Pid = tpp_tcp_client_create(Node),
+ ?SEV_IPRINT("remote client created: ~p", [Pid]),
+ {ok, State#{rclient => Pid}}
+ end},
+ #{desc => "monitor remote client",
+ cmd => fun(#{rclient := Pid}) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order remote client to start",
+ cmd => fun(#{rclient := RClient,
+ server_sa := ServerSA,
+ buf_init := BufInit,
+ send := Send,
+ recv := Recv}) ->
+ ?SEV_ANNOUNCE_START(RClient,
+ {ServerSA, BufInit, Send, Recv}),
+ ok
+ end},
+ #{desc => "await remote client ready",
+ cmd => fun(#{tester := Tester,
+ rclient := RClient} = _State) ->
+ ?SEV_AWAIT_READY(RClient, rclient, init,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (connect)",
+ cmd => fun(#{tester := Tester,
+ rclient := RClient} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, connect,
+ [{rclient, RClient}]),
+ ok
+ end},
+ #{desc => "order remote client to continue (connect)",
+ cmd => fun(#{rclient := RClient}) ->
+ ?SEV_ANNOUNCE_CONTINUE(RClient, connect),
+ ok
+ end},
+ #{desc => "await remote client ready (connect)",
+ cmd => fun(#{tester := Tester,
+ rclient := RClient} = _State) ->
+ ?SEV_AWAIT_READY(RClient, rclient, connect,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (connect)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, connect),
+ ok
+ end},
+ #{desc => "await continue (send)",
+ cmd => fun(#{tester := Tester,
+ rclient := RClient} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester,
+ send,
+ [{rclient, RClient}])
+ end},
+ #{desc => "order remote client to continue (send)",
+ cmd => fun(#{rclient := RClient,
+ msg := Msg,
+ num := Num} = State) ->
+ Data = {Msg, Num},
+ ?SEV_ANNOUNCE_CONTINUE(RClient, send, Data),
+ {ok, maps:remove(data, State)}
+ end},
+ #{desc => "await remote client ready (send)",
+ cmd => fun(#{tester := Tester,
+ rclient := RClient} = State) ->
+ case ?SEV_AWAIT_READY(RClient, rclient, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ %% ?SEV_IPRINT("remote client result: "
+ %% "~n ~p", [Result]),
+ {ok, State#{result => Result}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (send)",
+ cmd => fun(#{tester := Tester, result := Result} = State) ->
+ ?SEV_ANNOUNCE_READY(Tester, send, Result),
+ {ok, maps:remove(result, State)}
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester,
+ rclient := RClient} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester,
+ [{rclient, RClient}]) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "stop remote client",
+ cmd => fun(#{rclient := RClient}) ->
+ ?SEV_ANNOUNCE_TERMINATE(RClient),
+ ok
+ end},
+ #{desc => "await remote client termination",
+ cmd => fun(#{rclient := RClient} = State) ->
+ ?SEV_AWAIT_TERMINATION(RClient),
+ State1 = maps:remove(rclient, State),
+ {ok, State1}
+ end},
+ #{desc => "stop client node",
+ cmd => fun(#{node := Node} = _State) ->
+ stop_node(Node)
+ end},
+ #{desc => "await client node termination",
+ cmd => fun(#{node := Node} = State) ->
+ receive
+ {nodedown, Node} ->
+ {ok, maps:remove(node, State)}
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_sa => ServerSA}}
+ end},
+
+ %% Start the client
+ #{desc => "order client start",
+ cmd => fun(#{client := Pid,
+ server_sa := ServerSA} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, ServerSA),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, init)
+ end},
+
+ %% The actual test
+ #{desc => "order server continue (accept)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, accept),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client continue (connect)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, connect),
+ ok
+ end},
+ #{desc => "await server ready (accept)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, accept,
+ [{client, Client}]),
+ ok
+ end},
+ #{desc => "await client ready (connect)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ ?SEV_AWAIT_READY(Client, client, connect,
+ [{server, Server}])
+ end},
+ #{desc => "order server continue (recv)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client continue (send)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, send),
+ ok
+ end},
+ #{desc => "await client ready (send)",
+ cmd => fun(#{server := Server,
+ client := Client} = State) ->
+ case ?SEV_AWAIT_READY(Client, client, send,
+ [{server, Server}]) of
+ {ok, {_, _, _, _} = Result} ->
+ ?SEV_IPRINT("client result: "
+ "~n ~p", [Result]),
+ {ok, State#{client_result => Result}};
+ {ok, BadResult} ->
+ ?SEV_EPRINT("client result: "
+ "~n ~p", [BadResult]),
+ {error, {invalid_client_result, BadResult}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await server ready (recv)",
+ cmd => fun(#{server := Server,
+ client := Client,
+ num := Num} = State) ->
+ case ?SEV_AWAIT_READY(Server, server, recv,
+ [{client, Client}]) of
+ {ok, {Num, _, _, _, _} = Result} ->
+ ?SEV_IPRINT("server result: "
+ "~n ~p", [Result]),
+ Result2 = erlang:delete_element(1, Result),
+ {ok, State#{server_result => Result2}};
+ {ok, BadResult} ->
+ ?SEV_EPRINT("bad server result: "
+ "~n ~p", [BadResult]),
+ {error, {invalid_server_result, BadResult}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "present result",
+ cmd => fun(#{server_result := SRes,
+ client_result := CRes,
+ num := Num} = State) ->
+ {SSent, SReceived, SStart, SStop} = SRes,
+ {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 cource
+ %% 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},
+
+ %% Terminations
+ #{desc => "order client to terminate",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(client, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(server, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ ServerInitState = #{domain => maps:get(domain, InitState),
+ recv => maps:get(recv, InitState),
+ send => maps:get(send, InitState),
+ buf_init => maps:get(buf_init, InitState)},
+ Server = ?SEV_START("server", ServerSeq, ServerInitState),
+
+ i("start client evaluator(s)"),
+ ClientInitState = InitState#{host => local_host()},
+ Client = ?SEV_START("client", ClientSeq, ClientInitState),
+
+ i("start 'tester' evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client => Client#ev.pid,
+ num => maps:get(num, InitState)},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+tpp_tcp_handler_create() ->
+ Self = self(),
+ erlang:spawn(fun() -> tpp_tcp_handler(Self) end).
+
+tpp_tcp_handler(Parent) ->
+ tpp_tcp_handler_init(Parent),
+ {Sock, Send, Recv} = tpp_tcp_handler_await_start(Parent),
+ tpp_tcp_handler_announce_ready(Parent, init),
+ tpp_tcp_handler_await_continue(Parent, recv),
+ Result = tpp_tcp_handler_msg_exchange(Sock, Send, Recv),
+ tpp_tcp_handler_announce_ready(Parent, recv, Result),
+ Reason = tpp_tcp_handler_await_terminate(Parent),
+ ?SEV_IPRINT("terminating"),
+ exit(Reason).
+
+tpp_tcp_handler_init(Parent) ->
+ put(sname, "handler"),
+ ?SEV_IPRINT("init"),
+ _MRef = erlang:monitor(process, Parent),
+ ok.
+
+tpp_tcp_handler_await_start(Parent) ->
+ ?SEV_IPRINT("await start"),
+ ?SEV_AWAIT_START(Parent).
+
+tpp_tcp_handler_announce_ready(Parent, Slogan) ->
+ ?SEV_IPRINT("announce ready (~p)", [Slogan]),
+ ?SEV_ANNOUNCE_READY(Parent, Slogan).
+tpp_tcp_handler_announce_ready(Parent, Slogan, Extra) ->
+ ?SEV_IPRINT("announce ready (~p)", [Slogan]),
+ ?SEV_ANNOUNCE_READY(Parent, Slogan, Extra).
+
+tpp_tcp_handler_await_continue(Parent, Slogan) ->
+ ?SEV_IPRINT("await continue (~p)", [Slogan]),
+ case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of
+ ok ->
+ %% ?SEV_IPRINT("continue (~p): ok", [Slogan]),
+ ok;
+ {error, Reason} ->
+ ?SEV_EPRINT("continue (~p): error"
+ "~n ~p", [Slogan, Reason]),
+ exit({continue, Slogan, Reason})
+ end.
+
+tpp_tcp_handler_await_terminate(Parent) ->
+ ?SEV_IPRINT("await terminate"),
+ case ?SEV_AWAIT_TERMINATE(Parent, parent) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ Reason
+ end.
+
+tpp_tcp_handler_msg_exchange(Sock, Send, Recv) ->
+ tpp_tcp_handler_msg_exchange_loop(Sock, Send, Recv, 0, 0, 0, undefined).
+
+tpp_tcp_handler_msg_exchange_loop(Sock, Send, Recv, N, Sent, Received, Start) ->
+ %% ?SEV_IPRINT("[~w] try receive", [N]),
+ case tpp_tcp_recv_req(Sock, Recv) of
+ {ok, Msg, RecvSz} ->
+ NewStart = if (Start =:= undefined) -> ?LIB:timestamp();
+ true -> Start end,
+ %% ?SEV_IPRINT("[~w] received - now try send", [N]),
+ case tpp_tcp_send_rep(Sock, Send, Msg) of
+ {ok, SendSz} ->
+ tpp_tcp_handler_msg_exchange_loop(Sock, Send, Recv,
+ N+1,
+ Sent+SendSz,
+ Received+RecvSz,
+ NewStart);
+ {error, SReason} ->
+ ?SEV_EPRINT("send (~w): ~p", [N, SReason]),
+ exit({send, SReason, N})
+ end;
+ %% {error, timeout} ->
+ %% ?SEV_IPRINT("timeout(~w) - try again", [N]),
+ %% case Send(Sock, list_to_binary("ping")) of
+ %% ok ->
+ %% exit({'ping-send', ok, N});
+ %% {error, Reason} ->
+ %% exit({'ping-send', Reason, N})
+ %% end;
+ {error, closed} ->
+ ?SEV_IPRINT("closed - we are done: ~w, ~w, ~w", [N, Sent, Received]),
+ Stop = ?LIB:timestamp(),
+ {N, Sent, Received, Start, Stop};
+ {error, RReason} ->
+ ?SEV_EPRINT("recv (~w): ~p", [N, RReason]),
+ exit({recv, RReason, N})
+ end.
+
+%% The (remote) client process
+
+tpp_tcp_client_create(Node) ->
+ Self = self(),
+ Fun = fun() -> tpp_tcp_client(Self) end,
+ erlang:spawn(Node, Fun).
+
+tpp_tcp_client(Parent) ->
+ tpp_tcp_client_init(Parent),
+ {ServerSA, BufInit, Send, Recv} = tpp_tcp_client_await_start(Parent),
+ Domain = maps:get(family, ServerSA),
+ Sock = tpp_tcp_client_sock_open(Domain, BufInit),
+ tpp_tcp_client_sock_bind(Sock, Domain),
+ tpp_tcp_client_announce_ready(Parent, init),
+ tpp_tcp_client_await_continue(Parent, connect),
+ tpp_tcp_client_sock_connect(Sock, ServerSA),
+ tpp_tcp_client_announce_ready(Parent, connect),
+ {InitMsg, Num} = tpp_tcp_client_await_continue(Parent, send),
+ Result = tpp_tcp_client_msg_exchange(Sock, Send, Recv, InitMsg, Num),
+ tpp_tcp_client_announce_ready(Parent, send, Result),
+ Reason = tpp_tcp_client_await_terminate(Parent),
+ tpp_tcp_client_sock_close(Sock),
+ ?SEV_IPRINT("terminating"),
+ exit(Reason).
+
+tpp_tcp_client_init(Parent) ->
+ put(sname, "rclient"),
+ ?SEV_IPRINT("init"),
+ _MRef = erlang:monitor(process, Parent),
+ ok.
+
+tpp_tcp_client_await_start(Parent) ->
+ ?SEV_IPRINT("await start"),
+ ?SEV_AWAIT_START(Parent).
+
+tpp_tcp_client_announce_ready(Parent, Slogan) ->
+ ?SEV_IPRINT("announce ready (~p)", [Slogan]),
+ ?SEV_ANNOUNCE_READY(Parent, Slogan).
+tpp_tcp_client_announce_ready(Parent, Slogan, Extra) ->
+ ?SEV_IPRINT("announce ready (~p): ~p", [Slogan, Extra]),
+ ?SEV_ANNOUNCE_READY(Parent, Slogan, Extra).
+
+tpp_tcp_client_await_continue(Parent, Slogan) ->
+ ?SEV_IPRINT("await continue (~p)", [Slogan]),
+ case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of
+ ok ->
+ ?SEV_IPRINT("continue (~p): ok", [Slogan]),
+ ok;
+ {ok, Data} ->
+ ?SEV_IPRINT("continue (~p): ok with data", [Slogan]),
+ Data;
+ {error, Reason} ->
+ ?SEV_EPRINT("continue (~p): error"
+ "~n ~p", [Slogan, Reason]),
+ exit({continue, Slogan, Reason})
+ end.
+
+tpp_tcp_client_await_terminate(Parent) ->
+ ?SEV_IPRINT("await terminate"),
+ case ?SEV_AWAIT_TERMINATE(Parent, parent) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ Reason
+ end.
+
+tpp_tcp_client_msg_exchange(Sock, Send, Recv, InitMsg, Num) ->
+ Start = ?LIB:timestamp(),
+ tpp_tcp_client_msg_exchange_loop(Sock, Send, Recv, InitMsg,
+ Num, 0, 0, 0, Start).
+
+tpp_tcp_client_msg_exchange_loop(Sock, _Send, _Recv, _Msg,
+ Num, Num, Sent, Received,
+ Start) ->
+ Stop = ?LIB:timestamp(),
+ case socket:close(Sock) of
+ ok ->
+ {Sent, Received, Start, Stop};
+ {error, Reason} ->
+ exit({failed_closing, Reason})
+ end;
+tpp_tcp_client_msg_exchange_loop(Sock, Send, Recv, Data,
+ Num, N, Sent, Received, Start) ->
+ %% d("tpp_tcp_client_msg_exchange_loop(~w,~w) try send", [Num,N]),
+ case tpp_tcp_send_req(Sock, Send, Data) of
+ {ok, SendSz} ->
+ %% d("tpp_tcp_client_msg_exchange_loop(~w,~w) sent - "
+ %% "now try recv", [Num,N]),
+ case tpp_tcp_recv_rep(Sock, Recv) of
+ {ok, NewData, RecvSz} ->
+ tpp_tcp_client_msg_exchange_loop(Sock, Send, Recv,
+ NewData, Num, N+1,
+ Sent+SendSz,
+ Received+RecvSz,
+ Start);
+ {error, RReason} ->
+ ?SEV_EPRINT("recv (~w of ~w): ~p", [N, Num, RReason]),
+ exit({recv, RReason, N})
+ end;
+ {error, SReason} ->
+ ?SEV_EPRINT("send (~w of ~w): ~p", [N, Num, SReason]),
+ exit({send, SReason, N})
+ end.
+
+tpp_tcp_client_sock_open(Domain, BufInit) ->
+ case socket:open(Domain, stream, tcp) of
+ {ok, Sock} ->
+ ok = BufInit(Sock),
+ Sock;
+ {error, Reason} ->
+ exit({open_failed, Reason})
+ end.
+
+tpp_tcp_client_sock_bind(Sock, Domain) ->
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain,
+ addr => LAddr},
+ case socket:bind(Sock, LSA) of
+ {ok, _} ->
+ ok;
+ {error, Reason} ->
+ exit({bind, Reason})
+ end.
+
+tpp_tcp_client_sock_connect(Sock, ServerSA) ->
+ case socket:connect(Sock, ServerSA) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ exit({connect, Reason})
+ end.
+
+tpp_tcp_client_sock_close(Sock) ->
+ case socket:close(Sock) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ exit({close, Reason})
+ end.
+
+-define(TPP_REQUEST, 1).
+-define(TPP_REPLY, 2).
+
+tpp_tcp_recv_req(Sock, Recv) ->
+ tpp_tcp_recv(Sock, Recv, ?TPP_REQUEST).
+
+tpp_tcp_recv_rep(Sock, Recv) ->
+ tpp_tcp_recv(Sock, Recv, ?TPP_REPLY).
+
+tpp_tcp_recv(Sock, Recv, Tag) ->
+ case Recv(Sock, 0) of
+ {ok, <<Tag:32/integer, Sz:32/integer, Data/binary>> = Msg}
+ when (Sz =:= size(Data)) ->
+ %% We got it all
+ {ok, Data, size(Msg)};
+ {ok, <<Tag:32/integer, Sz:32/integer, Data/binary>> = Msg} ->
+ Remains = Sz - size(Data),
+ tpp_tcp_recv(Sock, Recv, Tag, Remains, size(Msg), [Data]);
+ {ok, <<Tag:32/integer, _/binary>>} ->
+ {error, {invalid_msg_tag, Tag}};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+tpp_tcp_recv(Sock, Recv, Tag, Remaining, AccSz, Acc) ->
+ case Recv(Sock, Remaining) of
+ {ok, Data} when (Remaining =:= size(Data)) ->
+ %% We got the rest
+ TotSz = AccSz + size(Data),
+ {ok, erlang:iolist_to_binary(lists:reverse([Data | Acc])), TotSz};
+ {ok, Data} when (Remaining > size(Data)) ->
+ tpp_tcp_recv(Sock, Recv, Tag,
+ Remaining - size(Data), AccSz + size(Data),
+ [Data | Acc]);
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+tpp_tcp_send_req(Sock, Send, Data) ->
+ tpp_tcp_send(Sock, Send, ?TPP_REQUEST, Data).
+
+tpp_tcp_send_rep(Sock, Send, Data) ->
+ tpp_tcp_send(Sock, Send, ?TPP_REPLY, Data).
+
+tpp_tcp_send(Sock, Send, Tag, Data) ->
+ DataSz = size(Data),
+ Msg = <<Tag:32/integer, DataSz:32/integer, Data/binary>>,
+ tpp_tcp_send_msg(Sock, Send, Msg, 0).
+
+tpp_tcp_send_msg(Sock, Send, Msg, AccSz) when is_binary(Msg) ->
+ case Send(Sock, Msg) of
+ ok ->
+ {ok, AccSz+size(Msg)};
+ {ok, Rest} -> % This is an IOVec
+ RestBin = list_to_binary(Rest),
+ tpp_tcp_send_msg(Sock, Send, RestBin, AccSz+(size(Msg)-size(RestBin)));
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+%% size_of_data(Data) when is_binary(Data) ->
+%% size(Data);
+%% size_of_data(Data) when is_list(Data) ->
+%% size_of_iovec(Data, 0).
+
+%% size_of_iovec([], Sz) ->
+%% Sz;
+%% size_of_iovec([B|IOVec], Sz) ->
+%% size_of_iovec(IOVec, Sz+size(B)).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Ping-Pong for UDP
+
+traffic_ping_pong_sendto_and_recvfrom_udp(InitState) ->
+ Send = fun(Sock, Data, Dest) ->
+ socket:sendto(Sock, Data, Dest)
+ end,
+ Recv = fun(Sock, Sz) ->
+ socket:recvfrom(Sock, Sz)
+ end,
+ InitState2 = InitState#{send => Send, % Send function
+ recv => Recv % Receive function
+ },
+ traffic_ping_pong_send_and_receive_udp(InitState2).
+
+traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState) ->
+ Send = fun(Sock, Data, Dest) when is_binary(Data) ->
+ MsgHdr = #{addr => Dest, iov => [Data]},
+ socket:sendmsg(Sock, MsgHdr);
+ (Sock, Data, Dest) when is_list(Data) -> %% We assume iovec...
+ MsgHdr = #{addr => Dest, iov => Data},
+ socket:sendmsg(Sock, MsgHdr)
+ end,
+ Recv = fun(Sock, Sz) ->
+ case socket:recvmsg(Sock, Sz, 0) of
+ {ok, #{addr := Source,
+ iov := [Data]}} ->
+ {ok, {Source, Data}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end,
+ InitState2 = InitState#{send => Send, % Send function
+ recv => Recv % Receive function
+ },
+ traffic_ping_pong_send_and_receive_udp(InitState2).
+
+
+traffic_ping_pong_send_and_receive_udp(#{msg := Msg} = InitState) ->
+ Fun = fun(Sock) ->
+ {ok, RcvSz} = socket:getopt(Sock, socket, rcvbuf),
+ if (RcvSz =< (8+size(Msg))) ->
+ i("adjust socket rcvbuf buffer size"),
+ ok = socket:setopt(Sock, socket, rcvbuf, 1024+size(Msg));
+ true ->
+ ok
+ end,
+ {ok, SndSz} = socket:getopt(Sock, socket, sndbuf),
+ if (SndSz =< (8+size(Msg))) ->
+ i("adjust socket sndbuf buffer size"),
+ ok = socket:setopt(Sock, socket, sndbuf, 1024+size(Msg));
+ true ->
+ ok
+ end,
+ {ok, OtpRcvBuf} = socket:getopt(Sock, otp, rcvbuf),
+ if
+ (OtpRcvBuf =< (8+size(Msg))) ->
+ i("adjust otp rcvbuf buffer size"),
+ ok = socket:setopt(Sock, otp, rcvbuf, 1024+size(Msg));
+ true ->
+ ok
+ end
+ end,
+ traffic_ping_pong_send_and_receive_udp2(InitState#{buf_init => Fun}).
+
+traffic_ping_pong_send_and_receive_udp2(InitState) ->
+ ServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain, addr => LAddr},
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create listen socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, dgram, udp) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "bind to local address",
+ cmd => fun(#{sock := Sock, local_sa := LSA} = State) ->
+ case socket:bind(Sock, LSA) of
+ {ok, Port} ->
+ {ok, State#{port => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "maybe init buffers",
+ cmd => fun(#{sock := Sock, buf_init := BufInit} = _State) ->
+ BufInit(Sock)
+ end},
+ #{desc => "create handler",
+ cmd => fun(State) ->
+ Handler = tpp_udp_server_handler_create(),
+ ?SEV_IPRINT("handler created: ~p", [Handler]),
+ {ok, State#{handler => Handler}}
+ end},
+ #{desc => "monitor handler",
+ cmd => fun(#{handler := Handler} = _State) ->
+ _MRef = erlang:monitor(process, Handler),
+ ok
+ end},
+ #{desc => "start handler",
+ cmd => fun(#{handler := Handler,
+ sock := Sock,
+ send := Send,
+ recv := Recv} = _State) ->
+ ?SEV_ANNOUNCE_START(Handler, {Sock, Send, Recv}),
+ ok
+ end},
+ #{desc => "await handler ready (init)",
+ cmd => fun(#{tester := Tester,
+ handler := Handler} = State) ->
+ case ?SEV_AWAIT_READY(Handler, handler, init,
+ [{tester, Tester}]) of
+ ok ->
+ {ok, maps:remove(csock, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester, local_sa := LSA, port := Port}) ->
+ ServerSA = LSA#{port => Port},
+ ?SEV_ANNOUNCE_READY(Tester, init, ServerSA),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (recv)",
+ cmd => fun(#{tester := Tester,
+ handler := Handler} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, recv,
+ [{handler, Handler}])
+ end},
+ #{desc => "order handler to recv",
+ cmd => fun(#{handler := Handler} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Handler, recv),
+ ok
+ end},
+ #{desc => "await continue (close)",
+ cmd => fun(#{tester := Tester,
+ handler := Handler} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, close,
+ [{handler, Handler}])
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ %% socket:setopt(Sock, otp, debug, true),
+ case socket:close(Sock) of
+ ok ->
+ {ok, maps:remove(sock, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (close)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_ANNOUNCE_READY(Tester, close),
+ ok
+ end},
+ #{desc => "await handler ready (recv)",
+ cmd => fun(#{tester := Tester,
+ handler := Handler} = State) ->
+ case ?SEV_AWAIT_READY(Handler, handler, recv,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ %% ?SEV_IPRINT("Result: ~p", [Result]),
+ {ok, State#{result => Result}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (recv)",
+ cmd => fun(#{tester := Tester,
+ result := Result} = State) ->
+ ?SEV_ANNOUNCE_READY(Tester, recv, Result),
+ {ok, maps:remove(result, State)}
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "stop handler",
+ cmd => fun(#{handler := Handler}) ->
+ ?SEV_ANNOUNCE_TERMINATE(Handler),
+ ok
+ end},
+ #{desc => "await handler termination",
+ cmd => fun(#{handler := Handler} = State) ->
+ ?SEV_AWAIT_TERMINATION(Handler),
+ State1 = maps:remove(handler, State),
+ {ok, State1}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, ServerSA} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ server_sa => ServerSA}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "create node",
+ cmd => fun(#{host := Host} = State) ->
+ case start_node(Host, client) of
+ {ok, Node} ->
+ ?SEV_IPRINT("(remote) client node ~p started",
+ [Node]),
+ {ok, State#{node => Node}};
+ {error, Reason, _} ->
+ {error, Reason}
+ end
+ end},
+ #{desc => "monitor client node",
+ cmd => fun(#{node := Node} = _State) ->
+ true = erlang:monitor_node(Node, true),
+ ok
+ end},
+ #{desc => "create (remote) handler",
+ cmd => fun(#{node := Node} = State) ->
+ Pid = tpp_udp_client_handler_create(Node),
+ ?SEV_IPRINT("handler created: ~p", [Pid]),
+ {ok, State#{handler => Pid}}
+ end},
+ #{desc => "monitor remote handler",
+ cmd => fun(#{handler := Pid}) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "order remote handler to start",
+ cmd => fun(#{handler := Handler,
+ server_sa := ServerSA,
+ buf_init := BufInit,
+ send := Send,
+ recv := Recv}) ->
+ ?SEV_ANNOUNCE_START(Handler,
+ {ServerSA, BufInit, Send, Recv}),
+ ok
+ end},
+ #{desc => "await (remote) handler ready",
+ cmd => fun(#{tester := Tester,
+ handler := Handler} = _State) ->
+ ?SEV_AWAIT_READY(Handler, handler, init,
+ [{tester, Tester}])
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (send)",
+ cmd => fun(#{tester := Tester,
+ handler := Handler} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester,
+ send,
+ [{handler, Handler}])
+ end},
+ #{desc => "order handler to continue (send)",
+ cmd => fun(#{handler := Handler,
+ msg := Msg,
+ num := Num} = State) ->
+ Data = {Msg, Num},
+ ?SEV_ANNOUNCE_CONTINUE(Handler, send, Data),
+ {ok, maps:remove(data, State)}
+ end},
+ #{desc => "await remote handler ready (send)",
+ cmd => fun(#{tester := Tester,
+ handler := Handler} = State) ->
+ case ?SEV_AWAIT_READY(Handler, handler, send,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ %% ?SEV_IPRINT("remote client result: "
+ %% "~n ~p", [Result]),
+ {ok, State#{result => Result}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (send)",
+ cmd => fun(#{tester := Tester, result := Result} = State) ->
+ ?SEV_ANNOUNCE_READY(Tester, send, Result),
+ {ok, maps:remove(result, State)}
+ end},
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester,
+ handler := Handler} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester,
+ [{handler, Handler}]) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "stop (remote) handler",
+ cmd => fun(#{handler := Handler}) ->
+ ?SEV_ANNOUNCE_TERMINATE(Handler),
+ ok
+ end},
+ #{desc => "await (remote) handler termination",
+ cmd => fun(#{handler := Handler} = State) ->
+ ?SEV_AWAIT_TERMINATION(Handler),
+ State1 = maps:remove(handler, State),
+ {ok, State1}
+ end},
+ #{desc => "stop client node",
+ cmd => fun(#{node := Node} = _State) ->
+ stop_node(Node)
+ end},
+ #{desc => "await client node termination",
+ cmd => fun(#{node := Node} = State) ->
+ receive
+ {nodedown, Node} ->
+ {ok, maps:remove(node, State)}
+ end
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_sa => ServerSA}}
+ end},
+
+ %% Start the client
+ #{desc => "order client start",
+ cmd => fun(#{client := Pid,
+ server_sa := ServerSA} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, ServerSA),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, client, init)
+ end},
+
+ %% The actual test
+ #{desc => "order server continue (recv)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, recv),
+ ok
+ end},
+ ?SEV_SLEEP(?SECS(1)),
+ #{desc => "order client continue (send)",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, send),
+ ok
+ end},
+ #{desc => "await client ready (send)",
+ cmd => fun(#{server := Server,
+ client := Client} = State) ->
+ case ?SEV_AWAIT_READY(Client, client, send,
+ [{server, Server}]) of
+ {ok, {_, _, _, _} = Result} ->
+ ?SEV_IPRINT("client result: "
+ "~n ~p", [Result]),
+ {ok, State#{client_result => Result}};
+ {ok, BadResult} ->
+ ?SEV_EPRINT("client result: "
+ "~n ~p", [BadResult]),
+ {error, {invalid_client_result, BadResult}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order server continue (close)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Pid, close),
+ ok
+ end},
+ #{desc => "await server ready (close)",
+ cmd => fun(#{server := Pid} = _State) ->
+ ok = ?SEV_AWAIT_READY(Pid, server, close)
+ end},
+ %% Because of the way we control the server, there is no real
+ %% point in collecting statistics from it (the time will include
+ %% our communication with it).
+ #{desc => "await server ready (recv)",
+ cmd => fun(#{server := Server,
+ client := Client} = _State) ->
+ case ?SEV_AWAIT_READY(Server, server, recv,
+ [{client, Client}]) of
+ {ok, _Result} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "present result",
+ cmd => fun(#{client_result := CRes,
+ 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 cource
+ %% 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},
+
+ %% Terminations
+ #{desc => "order client to terminate",
+ cmd => fun(#{client := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await client termination",
+ cmd => fun(#{client := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(client, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "order server to terminate",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Pid),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Pid} = State) ->
+ case ?SEV_AWAIT_TERMINATION(Pid) of
+ ok ->
+ State1 = maps:remove(server, State),
+ {ok, State1};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ i("start server evaluator"),
+ ServerInitState = #{domain => maps:get(domain, InitState),
+ recv => maps:get(recv, InitState),
+ send => maps:get(send, InitState),
+ buf_init => maps:get(buf_init, InitState)},
+ Server = ?SEV_START("server", ServerSeq, ServerInitState),
+
+ i("start client evaluator(s)"),
+ ClientInitState = InitState#{host => local_host()},
+ Client = ?SEV_START("client", ClientSeq, ClientInitState),
+
+ i("start 'tester' evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client => Client#ev.pid,
+ num => maps:get(num, InitState)},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator"),
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+
+%% Server side handler process
+%% We don't actually need a separate process for this socket,
+%% but we do it anyway to simplify the sequence.
+tpp_udp_server_handler_create() ->
+ Self = self(),
+ erlang:spawn(fun() -> tpp_udp_server_handler(Self) end).
+
+tpp_udp_server_handler(Parent) ->
+ tpp_udp_server_handler_init(Parent),
+ {Sock, Send, Recv} = tpp_udp_handler_await_start(Parent),
+ tpp_udp_handler_announce_ready(Parent, init),
+ tpp_udp_handler_await_continue(Parent, recv),
+ Result = tpp_udp_server_handler_msg_exchange(Sock, Send, Recv),
+ tpp_udp_handler_announce_ready(Parent, recv, Result),
+ Reason = tpp_udp_handler_await_terminate(Parent),
+ ?SEV_IPRINT("terminating"),
+ exit(Reason).
+
+tpp_udp_server_handler_init(Parent) ->
+ put(sname, "shandler"),
+ ?SEV_IPRINT("init"),
+ _MRef = erlang:monitor(process, Parent),
+ ok.
+
+tpp_udp_server_handler_msg_exchange(Sock, Send, Recv) ->
+ tpp_udp_server_handler_msg_exchange_loop(Sock, Send, Recv,
+ 0, 0, 0, undefined).
+
+tpp_udp_server_handler_msg_exchange_loop(Sock, Send, Recv,
+ N, Sent, Received, Start) ->
+ %% ?SEV_IPRINT("[~w] try receive", [N]),
+ %% if
+ %% (N =:= (?TPP_SMALL_NUM-2)) ->
+ %% ?SEV_IPRINT("[~w] try receive", [N]),
+ %% socket:setopt(Sock, otp, debug, true);
+ %% true -> ok
+ %% end,
+ try tpp_udp_recv_req(Sock, Recv) of
+ {ok, Msg, RecvSz, From} ->
+ NewStart = if (Start =:= undefined) -> ?LIB:timestamp();
+ true -> Start end,
+ %% ?SEV_IPRINT("[~w] received - now try send", [N]),
+ try tpp_udp_send_rep(Sock, Send, Msg, From) of
+ {ok, SendSz} ->
+ tpp_udp_server_handler_msg_exchange_loop(Sock, Send, Recv,
+ N+1,
+ Sent+SendSz,
+ Received+RecvSz,
+ NewStart);
+ {error, SReason} ->
+ ?SEV_EPRINT("send (~w): ~p", [N, SReason]),
+ exit({send, SReason, N})
+ catch
+ SC:SE:SS ->
+ exit({send, {SC, SE, SS}, N})
+ end;
+ {error, closed} ->
+ ?SEV_IPRINT("closed - we are done: ~w, ~w, ~w",
+ [N, Sent, Received]),
+ Stop = ?LIB:timestamp(),
+ {N, Sent, Received, Start, Stop};
+ {error, RReason} ->
+ ?SEV_EPRINT("recv (~w): ~p", [N, RReason]),
+ exit({recv, RReason, N})
+ catch
+ RC:RE:RS ->
+ exit({recv, {RC, RE, RS}, N})
+ end.
+
+
+%% The (remote) client side handler process
+
+tpp_udp_client_handler_create(Node) ->
+ Self = self(),
+ Fun = fun() -> put(sname, "chandler"), tpp_udp_client_handler(Self) end,
+ erlang:spawn(Node, Fun).
+
+tpp_udp_client_handler(Parent) ->
+ tpp_udp_client_handler_init(Parent),
+ ?SEV_IPRINT("await start command"),
+ {ServerSA, BufInit, Send, Recv} = tpp_udp_handler_await_start(Parent),
+ ?SEV_IPRINT("start command with"
+ "~n ServerSA: ~p", [ServerSA]),
+ Domain = maps:get(family, ServerSA),
+ Sock = tpp_udp_sock_open(Domain, BufInit),
+ tpp_udp_sock_bind(Sock, Domain),
+ ?SEV_IPRINT("announce ready", []),
+ tpp_udp_handler_announce_ready(Parent, init),
+ {InitMsg, Num} = tpp_udp_handler_await_continue(Parent, send),
+ ?SEV_IPRINT("received continue with"
+ "~n Num: ~p", [Num]),
+ Result = tpp_udp_client_handler_msg_exchange(Sock, ServerSA,
+ Send, Recv, InitMsg, Num),
+ ?SEV_IPRINT("ready"),
+ tpp_udp_handler_announce_ready(Parent, send, Result),
+ ?SEV_IPRINT("await terminate"),
+ Reason = tpp_udp_handler_await_terminate(Parent),
+ ?SEV_IPRINT("terminate with ~p", [Reason]),
+ tpp_udp_sock_close(Sock),
+ ?SEV_IPRINT("terminating"),
+ exit(Reason).
+
+tpp_udp_client_handler_init(Parent) ->
+ put(sname, "chandler"),
+ ?SEV_IPRINT("init"),
+ _MRef = erlang:monitor(process, Parent),
+ ok.
+
+tpp_udp_client_handler_msg_exchange(Sock, ServerSA,
+ Send, Recv, InitMsg, Num) ->
+ Start = ?LIB:timestamp(),
+ tpp_udp_client_handler_msg_exchange_loop(Sock, ServerSA,
+ Send, Recv, InitMsg,
+ Num, 0, 0, 0, Start).
+
+tpp_udp_client_handler_msg_exchange_loop(_Sock, _Dest, _Send, _Recv, _Msg,
+ Num, Num, Sent, Received,
+ Start) ->
+ Stop = ?LIB:timestamp(),
+ {Sent, Received, Start, Stop};
+tpp_udp_client_handler_msg_exchange_loop(Sock, Dest, Send, Recv, Data,
+ Num, N, Sent, Received, Start) ->
+ case tpp_udp_send_req(Sock, Send, Data, Dest) of
+ {ok, SendSz} ->
+ case tpp_udp_recv_rep(Sock, Recv) of
+ {ok, NewData, RecvSz, Dest} ->
+ tpp_udp_client_handler_msg_exchange_loop(Sock, Dest,
+ Send, Recv,
+ NewData, Num, N+1,
+ Sent+SendSz,
+ Received+RecvSz,
+ Start);
+ {error, RReason} ->
+ ?SEV_EPRINT("recv (~w of ~w): ~p", [N, Num, RReason]),
+ exit({recv, RReason, N})
+ end;
+ {error, SReason} ->
+ ?SEV_EPRINT("send (~w of ~w): ~p", [N, Num, SReason]),
+ exit({send, SReason, N})
+ end.
+
+
+tpp_udp_recv_req(Sock, Recv) ->
+ tpp_udp_recv(Sock, Recv, ?TPP_REQUEST).
+
+tpp_udp_recv_rep(Sock, Recv) ->
+ tpp_udp_recv(Sock, Recv, ?TPP_REPLY).
+
+tpp_udp_recv(Sock, Recv, Tag) ->
+ %% ok = socket:setopt(Sock, otp, debug, true),
+ try Recv(Sock, 0) of
+ {ok, {Source, <<Tag:32/integer, Sz:32/integer, Data/binary>> = Msg}}
+ when (Sz =:= size(Data)) ->
+ %% ok = socket:setopt(Sock, otp, debug, false),
+ %% We got it all
+ %% ?SEV_IPRINT("tpp_udp_recv -> got all: "
+ %% "~n Source: ~p"
+ %% "~n Tag: ~p"
+ %% "~n Sz: ~p"
+ %% "~n size(Data): ~p",
+ %% [Source, Tag, Sz, size(Data)]),
+ {ok, Data, size(Msg), Source};
+ {ok, {_Source, <<Tag:32/integer, Sz:32/integer, Data/binary>>}} ->
+ %% ok = socket:setopt(Sock, otp, debug, false),
+ {error, {invalid_msg, Sz, size(Data)}};
+ {ok, {_, <<Tag:32/integer, _/binary>>}} ->
+ %% ok = socket:setopt(Sock, otp, debug, false),
+ {error, {invalid_msg_tag, Tag}};
+ {error, _} = ERROR ->
+ %% ok = socket:setopt(Sock, otp, debug, false),
+ ERROR
+ catch
+ C:E:S ->
+ {error, {catched, C, E, S}}
+ end.
+
+tpp_udp_send_req(Sock, Send, Data, Dest) ->
+ tpp_udp_send(Sock, Send, ?TPP_REQUEST, Data, Dest).
+
+tpp_udp_send_rep(Sock, Send, Data, Dest) ->
+ tpp_udp_send(Sock, Send, ?TPP_REPLY, Data, Dest).
+
+tpp_udp_send(Sock, Send, Tag, Data, Dest) ->
+ DataSz = size(Data),
+ Msg = <<Tag:32/integer, DataSz:32/integer, Data/binary>>,
+ tpp_udp_send_msg(Sock, Send, Msg, Dest, 0).
+
+tpp_udp_send_msg(Sock, Send, Msg, Dest, AccSz) when is_binary(Msg) ->
+ case Send(Sock, Msg, Dest) of
+ ok ->
+ {ok, AccSz+size(Msg)};
+ {ok, Rest} -> % This is an IOVec
+ RestBin = list_to_binary(Rest),
+ tpp_udp_send_msg(Sock, Send, RestBin, Dest,
+ AccSz+(size(Msg)-size(RestBin)));
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+tpp_udp_handler_await_start(Parent) ->
+ ?SEV_IPRINT("await start"),
+ ?SEV_AWAIT_START(Parent).
+
+tpp_udp_handler_announce_ready(Parent, Slogan) ->
+ ?SEV_IPRINT("announce ready (~p)", [Slogan]),
+ ?SEV_ANNOUNCE_READY(Parent, Slogan).
+tpp_udp_handler_announce_ready(Parent, Slogan, Extra) ->
+ ?SEV_IPRINT("announce ready (~p)", [Slogan]),
+ ?SEV_ANNOUNCE_READY(Parent, Slogan, Extra).
+
+tpp_udp_handler_await_continue(Parent, Slogan) ->
+ ?SEV_IPRINT("await continue (~p)", [Slogan]),
+ case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of
+ ok ->
+ ?SEV_IPRINT("continue (~p): ok", [Slogan]),
+ ok;
+ {ok, Data} ->
+ ?SEV_IPRINT("continue (~p): ok with data", [Slogan]),
+ Data;
+ {error, Reason} ->
+ ?SEV_EPRINT("continue (~p): error"
+ "~n ~p", [Slogan, Reason]),
+ exit({continue, Slogan, Reason})
+ end.
+
+tpp_udp_handler_await_terminate(Parent) ->
+ ?SEV_IPRINT("await terminate"),
+ case ?SEV_AWAIT_TERMINATE(Parent, parent) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ Reason
+ end.
+
+
+tpp_udp_sock_open(Domain, BufInit) ->
+ case socket:open(Domain, dgram, udp) of
+ {ok, Sock} ->
+ ok = BufInit(Sock),
+ Sock;
+ {error, Reason} ->
+ exit({open_failed, Reason})
+ end.
+
+tpp_udp_sock_bind(Sock, Domain) ->
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain,
+ addr => LAddr},
+ case socket:bind(Sock, LSA) of
+ {ok, _} ->
+ ok;
+ {error, Reason} ->
+ exit({bind, Reason})
+ end.
+
+tpp_udp_sock_close(Sock) ->
+ case socket:close(Sock) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ exit({close, Reason})
+ end.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgenf_cgenf_small_tcp4(suite) ->
+ [];
+ttest_sgenf_cgenf_small_tcp4(doc) ->
+ [];
+ttest_sgenf_cgenf_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgenf_small_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ gen, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgenf_cgenf_small_tcp6(suite) ->
+ [];
+ttest_sgenf_cgenf_small_tcp6(doc) ->
+ [];
+ttest_sgenf_cgenf_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgenf_small_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ gen, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgenf_cgenf_medium_tcp4(suite) ->
+ [];
+ttest_sgenf_cgenf_medium_tcp4(doc) ->
+ [];
+ttest_sgenf_cgenf_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgenf_medium_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ gen, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgenf_cgenf_medium_tcp6(suite) ->
+ [];
+ttest_sgenf_cgenf_medium_tcp6(doc) ->
+ [];
+ttest_sgenf_cgenf_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgenf_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ gen, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgenf_cgenf_large_tcp4(suite) ->
+ [];
+ttest_sgenf_cgenf_large_tcp4(doc) ->
+ [];
+ttest_sgenf_cgenf_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgenf_large_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ gen, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgenf_cgenf_large_tcp6(suite) ->
+ [];
+ttest_sgenf_cgenf_large_tcp6(doc) ->
+ [];
+ttest_sgenf_cgenf_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgenf_large_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ gen, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgenf_cgeno_small_tcp4(suite) ->
+ [];
+ttest_sgenf_cgeno_small_tcp4(doc) ->
+ [];
+ttest_sgenf_cgeno_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgeno_small_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ gen, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgenf_cgeno_small_tcp6(suite) ->
+ [];
+ttest_sgenf_cgeno_small_tcp6(doc) ->
+ [];
+ttest_sgenf_cgeno_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgeno_small_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ gen, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgenf_cgeno_medium_tcp4(suite) ->
+ [];
+ttest_sgenf_cgeno_medium_tcp4(doc) ->
+ [];
+ttest_sgenf_cgeno_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgeno_medium_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ gen, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgenf_cgeno_medium_tcp6(suite) ->
+ [];
+ttest_sgenf_cgeno_medium_tcp6(doc) ->
+ [];
+ttest_sgenf_cgeno_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgeno_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ gen, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgenf_cgeno_large_tcp4(suite) ->
+ [];
+ttest_sgenf_cgeno_large_tcp4(doc) ->
+ [];
+ttest_sgenf_cgeno_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgeno_large_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ gen, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgenf_cgeno_large_tcp6(suite) ->
+ [];
+ttest_sgenf_cgeno_large_tcp6(doc) ->
+ [];
+ttest_sgenf_cgeno_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgeno_large_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ gen, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgenf_cgent_small_tcp4(suite) ->
+ [];
+ttest_sgenf_cgent_small_tcp4(doc) ->
+ [];
+ttest_sgenf_cgent_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgent_small_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ gen, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgenf_cgent_small_tcp6(suite) ->
+ [];
+ttest_sgenf_cgent_small_tcp6(doc) ->
+ [];
+ttest_sgenf_cgent_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgeno_small_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ gen, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgenf_cgent_medium_tcp4(suite) ->
+ [];
+ttest_sgenf_cgent_medium_tcp4(doc) ->
+ [];
+ttest_sgenf_cgent_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgent_medium_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ gen, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgenf_cgent_medium_tcp6(suite) ->
+ [];
+ttest_sgenf_cgent_medium_tcp6(doc) ->
+ [];
+ttest_sgenf_cgent_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgent_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ gen, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgenf_cgent_large_tcp4(suite) ->
+ [];
+ttest_sgenf_cgent_large_tcp4(doc) ->
+ [];
+ttest_sgenf_cgent_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgent_large_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ gen, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgenf_cgent_large_tcp6(suite) ->
+ [];
+ttest_sgenf_cgent_large_tcp6(doc) ->
+ [];
+ttest_sgenf_cgent_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_cgent_large_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ gen, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgenf_csockf_small_tcp4(suite) ->
+ [];
+ttest_sgenf_csockf_small_tcp4(doc) ->
+ [];
+ttest_sgenf_csockf_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csockf_small_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ sock, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgenf_csockf_small_tcp6(suite) ->
+ [];
+ttest_sgenf_csockf_small_tcp6(doc) ->
+ [];
+ttest_sgenf_csockf_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csockf_small_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ sock, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgenf_csockf_medium_tcp4(suite) ->
+ [];
+ttest_sgenf_csockf_medium_tcp4(doc) ->
+ [];
+ttest_sgenf_csockf_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csockf_medium_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ sock, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgenf_csockf_medium_tcp6(suite) ->
+ [];
+ttest_sgenf_csockf_medium_tcp6(doc) ->
+ [];
+ttest_sgenf_csockf_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csockf_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ sock, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgenf_csockf_large_tcp4(suite) ->
+ [];
+ttest_sgenf_csockf_large_tcp4(doc) ->
+ [];
+ttest_sgenf_csockf_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csockf_large_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ sock, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgenf_csockf_large_tcp6(suite) ->
+ [];
+ttest_sgenf_csockf_large_tcp6(doc) ->
+ [];
+ttest_sgenf_csockf_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csockf_large_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ sock, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgenf_csocko_small_tcp4(suite) ->
+ [];
+ttest_sgenf_csocko_small_tcp4(doc) ->
+ [];
+ttest_sgenf_csocko_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csocko_small_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ sock, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgenf_csocko_small_tcp6(suite) ->
+ [];
+ttest_sgenf_csocko_small_tcp6(doc) ->
+ [];
+ttest_sgenf_csocko_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csocko_small_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ sock, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgenf_csocko_medium_tcp4(suite) ->
+ [];
+ttest_sgenf_csocko_medium_tcp4(doc) ->
+ [];
+ttest_sgenf_csocko_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csocko_medium_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ sock, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgenf_csocko_medium_tcp6(suite) ->
+ [];
+ttest_sgenf_csocko_medium_tcp6(doc) ->
+ [];
+ttest_sgenf_csocko_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csocko_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ sock, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgenf_csocko_large_tcp4(suite) ->
+ [];
+ttest_sgenf_csocko_large_tcp4(doc) ->
+ [];
+ttest_sgenf_csocko_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csocko_large_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ sock, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgenf_csocko_large_tcp6(suite) ->
+ [];
+ttest_sgenf_csocko_large_tcp6(doc) ->
+ [];
+ttest_sgenf_csocko_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csocko_large_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ sock, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgenf_csockt_small_tcp4(suite) ->
+ [];
+ttest_sgenf_csockt_small_tcp4(doc) ->
+ [];
+ttest_sgenf_csockt_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csockt_small_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ sock, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgenf_csockt_small_tcp6(suite) ->
+ [];
+ttest_sgenf_csockt_small_tcp6(doc) ->
+ [];
+ttest_sgenf_csockt_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csocko_small_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ sock, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgenf_csockt_medium_tcp4(suite) ->
+ [];
+ttest_sgenf_csockt_medium_tcp4(doc) ->
+ [];
+ttest_sgenf_csockt_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csockt_medium_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ sock, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgenf_csockt_medium_tcp6(suite) ->
+ [];
+ttest_sgenf_csockt_medium_tcp6(doc) ->
+ [];
+ttest_sgenf_csockt_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csockt_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ sock, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgenf_csockt_large_tcp4(suite) ->
+ [];
+ttest_sgenf_csockt_large_tcp4(doc) ->
+ [];
+ttest_sgenf_csockt_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csockt_large_tcp4,
+ Runtime,
+ inet,
+ gen, false,
+ sock, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = false
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgenf_csockt_large_tcp6(suite) ->
+ [];
+ttest_sgenf_csockt_large_tcp6(doc) ->
+ [];
+ttest_sgenf_csockt_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgenf_csockt_large_tcp6,
+ Runtime,
+ inet6,
+ gen, false,
+ sock, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgeno_cgenf_small_tcp4(suite) ->
+ [];
+ttest_sgeno_cgenf_small_tcp4(doc) ->
+ [];
+ttest_sgeno_cgenf_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgenf_small_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ gen, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgeno_cgenf_small_tcp6(suite) ->
+ [];
+ttest_sgeno_cgenf_small_tcp6(doc) ->
+ [];
+ttest_sgeno_cgenf_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgenf_small_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ gen, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgeno_cgenf_medium_tcp4(suite) ->
+ [];
+ttest_sgeno_cgenf_medium_tcp4(doc) ->
+ [];
+ttest_sgeno_cgenf_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgenf_medium_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ gen, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgeno_cgenf_medium_tcp6(suite) ->
+ [];
+ttest_sgeno_cgenf_medium_tcp6(doc) ->
+ [];
+ttest_sgeno_cgenf_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgenf_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ gen, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgeno_cgenf_large_tcp4(suite) ->
+ [];
+ttest_sgeno_cgenf_large_tcp4(doc) ->
+ [];
+ttest_sgeno_cgenf_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgenf_large_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ gen, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgeno_cgenf_large_tcp6(suite) ->
+ [];
+ttest_sgeno_cgenf_large_tcp6(doc) ->
+ [];
+ttest_sgeno_cgenf_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgenf_large_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ gen, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgeno_cgeno_small_tcp4(suite) ->
+ [];
+ttest_sgeno_cgeno_small_tcp4(doc) ->
+ [];
+ttest_sgeno_cgeno_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgeno_small_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ gen, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgeno_cgeno_small_tcp6(suite) ->
+ [];
+ttest_sgeno_cgeno_small_tcp6(doc) ->
+ [];
+ttest_sgeno_cgeno_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgeno_small_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ gen, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgeno_cgeno_medium_tcp4(suite) ->
+ [];
+ttest_sgeno_cgeno_medium_tcp4(doc) ->
+ [];
+ttest_sgeno_cgeno_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgeno_medium_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ gen, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgeno_cgeno_medium_tcp6(suite) ->
+ [];
+ttest_sgeno_cgeno_medium_tcp6(doc) ->
+ [];
+ttest_sgeno_cgeno_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgeno_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ gen, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgeno_cgeno_large_tcp4(suite) ->
+ [];
+ttest_sgeno_cgeno_large_tcp4(doc) ->
+ [];
+ttest_sgeno_cgeno_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgeno_large_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ gen, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgeno_cgeno_large_tcp6(suite) ->
+ [];
+ttest_sgeno_cgeno_large_tcp6(doc) ->
+ [];
+ttest_sgeno_cgeno_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgeno_large_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ gen, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgeno_cgent_small_tcp4(suite) ->
+ [];
+ttest_sgeno_cgent_small_tcp4(doc) ->
+ [];
+ttest_sgeno_cgent_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgent_small_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ gen, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgeno_cgent_small_tcp6(suite) ->
+ [];
+ttest_sgeno_cgent_small_tcp6(doc) ->
+ [];
+ttest_sgeno_cgent_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgeno_small_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ gen, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgeno_cgent_medium_tcp4(suite) ->
+ [];
+ttest_sgeno_cgent_medium_tcp4(doc) ->
+ [];
+ttest_sgeno_cgent_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgent_medium_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ gen, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgeno_cgent_medium_tcp6(suite) ->
+ [];
+ttest_sgeno_cgent_medium_tcp6(doc) ->
+ [];
+ttest_sgeno_cgent_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgent_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ gen, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgeno_cgent_large_tcp4(suite) ->
+ [];
+ttest_sgeno_cgent_large_tcp4(doc) ->
+ [];
+ttest_sgeno_cgent_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgent_large_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ gen, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgeno_cgent_large_tcp6(suite) ->
+ [];
+ttest_sgeno_cgent_large_tcp6(doc) ->
+ [];
+ttest_sgeno_cgent_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_cgent_large_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ gen, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgeno_csockf_small_tcp4(suite) ->
+ [];
+ttest_sgeno_csockf_small_tcp4(doc) ->
+ [];
+ttest_sgeno_csockf_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csockf_small_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ sock, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgeno_csockf_small_tcp6(suite) ->
+ [];
+ttest_sgeno_csockf_small_tcp6(doc) ->
+ [];
+ttest_sgeno_csockf_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csockf_small_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ sock, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgeno_csockf_medium_tcp4(suite) ->
+ [];
+ttest_sgeno_csockf_medium_tcp4(doc) ->
+ [];
+ttest_sgeno_csockf_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csockf_medium_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ sock, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgeno_csockf_medium_tcp6(suite) ->
+ [];
+ttest_sgeno_csockf_medium_tcp6(doc) ->
+ [];
+ttest_sgeno_csockf_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csockf_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ sock, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgeno_csockf_large_tcp4(suite) ->
+ [];
+ttest_sgeno_csockf_large_tcp4(doc) ->
+ [];
+ttest_sgeno_csockf_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csockf_large_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ sock, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgeno_csockf_large_tcp6(suite) ->
+ [];
+ttest_sgeno_csockf_large_tcp6(doc) ->
+ [];
+ttest_sgeno_csockf_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csockf_large_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ sock, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgeno_csocko_small_tcp4(suite) ->
+ [];
+ttest_sgeno_csocko_small_tcp4(doc) ->
+ [];
+ttest_sgeno_csocko_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csocko_small_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ sock, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgeno_csocko_small_tcp6(suite) ->
+ [];
+ttest_sgeno_csocko_small_tcp6(doc) ->
+ [];
+ttest_sgeno_csocko_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csocko_small_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ sock, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgeno_csocko_medium_tcp4(suite) ->
+ [];
+ttest_sgeno_csocko_medium_tcp4(doc) ->
+ [];
+ttest_sgeno_csocko_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csocko_medium_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ sock, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgeno_csocko_medium_tcp6(suite) ->
+ [];
+ttest_sgeno_csocko_medium_tcp6(doc) ->
+ [];
+ttest_sgeno_csocko_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csocko_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ sock, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgeno_csocko_large_tcp4(suite) ->
+ [];
+ttest_sgeno_csocko_large_tcp4(doc) ->
+ [];
+ttest_sgeno_csocko_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csocko_large_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ sock, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgeno_csocko_large_tcp6(suite) ->
+ [];
+ttest_sgeno_csocko_large_tcp6(doc) ->
+ [];
+ttest_sgeno_csocko_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csocko_large_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ sock, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgeno_csockt_small_tcp4(suite) ->
+ [];
+ttest_sgeno_csockt_small_tcp4(doc) ->
+ [];
+ttest_sgeno_csockt_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csockt_small_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ sock, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgeno_csockt_small_tcp6(suite) ->
+ [];
+ttest_sgeno_csockt_small_tcp6(doc) ->
+ [];
+ttest_sgeno_csockt_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csocko_small_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ sock, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgeno_csockt_medium_tcp4(suite) ->
+ [];
+ttest_sgeno_csockt_medium_tcp4(doc) ->
+ [];
+ttest_sgeno_csockt_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csockt_medium_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ sock, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgeno_csockt_medium_tcp6(suite) ->
+ [];
+ttest_sgeno_csockt_medium_tcp6(doc) ->
+ [];
+ttest_sgeno_csockt_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csockt_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ sock, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgeno_csockt_large_tcp4(suite) ->
+ [];
+ttest_sgeno_csockt_large_tcp4(doc) ->
+ [];
+ttest_sgeno_csockt_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csockt_large_tcp4,
+ Runtime,
+ inet,
+ gen, once,
+ sock, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = once
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgeno_csockt_large_tcp6(suite) ->
+ [];
+ttest_sgeno_csockt_large_tcp6(doc) ->
+ [];
+ttest_sgeno_csockt_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgeno_csockt_large_tcp6,
+ Runtime,
+ inet6,
+ gen, once,
+ sock, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgent_cgenf_small_tcp4(suite) ->
+ [];
+ttest_sgent_cgenf_small_tcp4(doc) ->
+ [];
+ttest_sgent_cgenf_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgenf_small_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ gen, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgent_cgenf_small_tcp6(suite) ->
+ [];
+ttest_sgent_cgenf_small_tcp6(doc) ->
+ [];
+ttest_sgent_cgenf_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgenf_small_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ gen, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgent_cgenf_medium_tcp4(suite) ->
+ [];
+ttest_sgent_cgenf_medium_tcp4(doc) ->
+ [];
+ttest_sgent_cgenf_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgenf_medium_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ gen, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgent_cgenf_medium_tcp6(suite) ->
+ [];
+ttest_sgent_cgenf_medium_tcp6(doc) ->
+ [];
+ttest_sgent_cgenf_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgenf_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ gen, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgent_cgenf_large_tcp4(suite) ->
+ [];
+ttest_sgent_cgenf_large_tcp4(doc) ->
+ [];
+ttest_sgent_cgenf_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgenf_large_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ gen, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgent_cgenf_large_tcp6(suite) ->
+ [];
+ttest_sgent_cgenf_large_tcp6(doc) ->
+ [];
+ttest_sgent_cgenf_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgenf_large_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ gen, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgent_cgeno_small_tcp4(suite) ->
+ [];
+ttest_sgent_cgeno_small_tcp4(doc) ->
+ [];
+ttest_sgent_cgeno_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgeno_small_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ gen, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgent_cgeno_small_tcp6(suite) ->
+ [];
+ttest_sgent_cgeno_small_tcp6(doc) ->
+ [];
+ttest_sgent_cgeno_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgeno_small_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ gen, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgent_cgeno_medium_tcp4(suite) ->
+ [];
+ttest_sgent_cgeno_medium_tcp4(doc) ->
+ [];
+ttest_sgent_cgeno_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgeno_medium_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ gen, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgent_cgeno_medium_tcp6(suite) ->
+ [];
+ttest_sgent_cgeno_medium_tcp6(doc) ->
+ [];
+ttest_sgent_cgeno_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgeno_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ gen, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgent_cgeno_large_tcp4(suite) ->
+ [];
+ttest_sgent_cgeno_large_tcp4(doc) ->
+ [];
+ttest_sgent_cgeno_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgeno_large_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ gen, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgent_cgeno_large_tcp6(suite) ->
+ [];
+ttest_sgent_cgeno_large_tcp6(doc) ->
+ [];
+ttest_sgent_cgeno_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgeno_large_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ gen, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgent_cgent_small_tcp4(suite) ->
+ [];
+ttest_sgent_cgent_small_tcp4(doc) ->
+ [];
+ttest_sgent_cgent_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgent_small_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ gen, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgent_cgent_small_tcp6(suite) ->
+ [];
+ttest_sgent_cgent_small_tcp6(doc) ->
+ [];
+ttest_sgent_cgent_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgeno_small_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ gen, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgent_cgent_medium_tcp4(suite) ->
+ [];
+ttest_sgent_cgent_medium_tcp4(doc) ->
+ ["Server(gen,true), Client(gen,true), Domain=inet, msg=medium"];
+ttest_sgent_cgent_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgent_medium_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ gen, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgent_cgent_medium_tcp6(suite) ->
+ [];
+ttest_sgent_cgent_medium_tcp6(doc) ->
+ ["Server(gen,true), Client(gen,true), Domain=inet6, msg=medium"];
+ttest_sgent_cgent_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgent_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ gen, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgent_cgent_large_tcp4(suite) ->
+ [];
+ttest_sgent_cgent_large_tcp4(doc) ->
+ ["Server(gen,true), Client(gen,true), Domain=inet, msg=large"];
+ttest_sgent_cgent_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgent_large_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ gen, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgent_cgent_large_tcp6(suite) ->
+ [];
+ttest_sgent_cgent_large_tcp6(doc) ->
+ ["Server(gen,true), Client(gen,true), Domain=inet6, msg=large"];
+ttest_sgent_cgent_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_cgent_large_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ gen, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgent_csockf_small_tcp4(suite) ->
+ [];
+ttest_sgent_csockf_small_tcp4(doc) ->
+ [];
+ttest_sgent_csockf_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csockf_small_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ sock, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgent_csockf_small_tcp6(suite) ->
+ [];
+ttest_sgent_csockf_small_tcp6(doc) ->
+ [];
+ttest_sgent_csockf_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csockf_small_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ sock, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgent_csockf_medium_tcp4(suite) ->
+ [];
+ttest_sgent_csockf_medium_tcp4(doc) ->
+ [];
+ttest_sgent_csockf_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csockf_medium_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ sock, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgent_csockf_medium_tcp6(suite) ->
+ [];
+ttest_sgent_csockf_medium_tcp6(doc) ->
+ [];
+ttest_sgent_csockf_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csockf_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ sock, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgent_csockf_large_tcp4(suite) ->
+ [];
+ttest_sgent_csockf_large_tcp4(doc) ->
+ [];
+ttest_sgent_csockf_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csockf_large_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ sock, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgent_csockf_large_tcp6(suite) ->
+ [];
+ttest_sgent_csockf_large_tcp6(doc) ->
+ [];
+ttest_sgent_csockf_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csockf_large_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ sock, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgent_csocko_small_tcp4(suite) ->
+ [];
+ttest_sgent_csocko_small_tcp4(doc) ->
+ [];
+ttest_sgent_csocko_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csocko_small_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ sock, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgent_csocko_small_tcp6(suite) ->
+ [];
+ttest_sgent_csocko_small_tcp6(doc) ->
+ [];
+ttest_sgent_csocko_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csocko_small_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ sock, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgent_csocko_medium_tcp4(suite) ->
+ [];
+ttest_sgent_csocko_medium_tcp4(doc) ->
+ [];
+ttest_sgent_csocko_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csocko_medium_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ sock, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgent_csocko_medium_tcp6(suite) ->
+ [];
+ttest_sgent_csocko_medium_tcp6(doc) ->
+ [];
+ttest_sgent_csocko_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csocko_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ sock, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgent_csocko_large_tcp4(suite) ->
+ [];
+ttest_sgent_csocko_large_tcp4(doc) ->
+ [];
+ttest_sgent_csocko_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csocko_large_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ sock, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgent_csocko_large_tcp6(suite) ->
+ [];
+ttest_sgent_csocko_large_tcp6(doc) ->
+ [];
+ttest_sgent_csocko_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csocko_large_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ sock, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_sgent_csockt_small_tcp4(suite) ->
+ [];
+ttest_sgent_csockt_small_tcp4(doc) ->
+ [];
+ttest_sgent_csockt_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csockt_small_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ sock, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_sgent_csockt_small_tcp6(suite) ->
+ [];
+ttest_sgent_csockt_small_tcp6(doc) ->
+ [];
+ttest_sgent_csockt_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csocko_small_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ sock, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_sgent_csockt_medium_tcp4(suite) ->
+ [];
+ttest_sgent_csockt_medium_tcp4(doc) ->
+ [];
+ttest_sgent_csockt_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csockt_medium_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ sock, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_sgent_csockt_medium_tcp6(suite) ->
+ [];
+ttest_sgent_csockt_medium_tcp6(doc) ->
+ [];
+ttest_sgent_csockt_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csockt_medium_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ sock, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_sgent_csockt_large_tcp4(suite) ->
+ [];
+ttest_sgent_csockt_large_tcp4(doc) ->
+ [];
+ttest_sgent_csockt_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csockt_large_tcp4,
+ Runtime,
+ inet,
+ gen, true,
+ sock, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = gen_tcp, Active = true
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_sgent_csockt_large_tcp6(suite) ->
+ [];
+ttest_sgent_csockt_large_tcp6(doc) ->
+ [];
+ttest_sgent_csockt_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_sgent_csockt_large_tcp6,
+ Runtime,
+ inet6,
+ gen, true,
+ sock, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssockf_cgenf_small_tcp4(suite) ->
+ [];
+ttest_ssockf_cgenf_small_tcp4(doc) ->
+ [];
+ttest_ssockf_cgenf_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgenf_small_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ gen, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssockf_cgenf_small_tcp6(suite) ->
+ [];
+ttest_ssockf_cgenf_small_tcp6(doc) ->
+ [];
+ttest_ssockf_cgenf_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgenf_small_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ gen, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssockf_cgenf_medium_tcp4(suite) ->
+ [];
+ttest_ssockf_cgenf_medium_tcp4(doc) ->
+ [];
+ttest_ssockf_cgenf_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgenf_medium_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ gen, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssockf_cgenf_medium_tcp6(suite) ->
+ [];
+ttest_ssockf_cgenf_medium_tcp6(doc) ->
+ [];
+ttest_ssockf_cgenf_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgenf_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ gen, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssockf_cgenf_large_tcp4(suite) ->
+ [];
+ttest_ssockf_cgenf_large_tcp4(doc) ->
+ [];
+ttest_ssockf_cgenf_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgenf_large_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ gen, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssockf_cgenf_large_tcp6(suite) ->
+ [];
+ttest_ssockf_cgenf_large_tcp6(doc) ->
+ [];
+ttest_ssockf_cgenf_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgenf_large_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ gen, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssockf_cgeno_small_tcp4(suite) ->
+ [];
+ttest_ssockf_cgeno_small_tcp4(doc) ->
+ [];
+ttest_ssockf_cgeno_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgeno_small_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ gen, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssockf_cgeno_small_tcp6(suite) ->
+ [];
+ttest_ssockf_cgeno_small_tcp6(doc) ->
+ [];
+ttest_ssockf_cgeno_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgeno_small_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ gen, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssockf_cgeno_medium_tcp4(suite) ->
+ [];
+ttest_ssockf_cgeno_medium_tcp4(doc) ->
+ [];
+ttest_ssockf_cgeno_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgeno_medium_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ gen, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssockf_cgeno_medium_tcp6(suite) ->
+ [];
+ttest_ssockf_cgeno_medium_tcp6(doc) ->
+ [];
+ttest_ssockf_cgeno_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgeno_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ gen, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssockf_cgeno_large_tcp4(suite) ->
+ [];
+ttest_ssockf_cgeno_large_tcp4(doc) ->
+ [];
+ttest_ssockf_cgeno_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgeno_large_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ gen, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssockf_cgeno_large_tcp6(suite) ->
+ [];
+ttest_ssockf_cgeno_large_tcp6(doc) ->
+ [];
+ttest_ssockf_cgeno_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgeno_large_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ gen, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssockf_cgent_small_tcp4(suite) ->
+ [];
+ttest_ssockf_cgent_small_tcp4(doc) ->
+ [];
+ttest_ssockf_cgent_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgent_small_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ gen, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssockf_cgent_small_tcp6(suite) ->
+ [];
+ttest_ssockf_cgent_small_tcp6(doc) ->
+ [];
+ttest_ssockf_cgent_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgeno_small_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ gen, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssockf_cgent_medium_tcp4(suite) ->
+ [];
+ttest_ssockf_cgent_medium_tcp4(doc) ->
+ [];
+ttest_ssockf_cgent_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgent_medium_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ gen, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssockf_cgent_medium_tcp6(suite) ->
+ [];
+ttest_ssockf_cgent_medium_tcp6(doc) ->
+ [];
+ttest_ssockf_cgent_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgent_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ gen, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssockf_cgent_large_tcp4(suite) ->
+ [];
+ttest_ssockf_cgent_large_tcp4(doc) ->
+ [];
+ttest_ssockf_cgent_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgent_large_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ gen, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssockf_cgent_large_tcp6(suite) ->
+ [];
+ttest_ssockf_cgent_large_tcp6(doc) ->
+ [];
+ttest_ssockf_cgent_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_cgent_large_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ gen, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssockf_csockf_small_tcp4(suite) ->
+ [];
+ttest_ssockf_csockf_small_tcp4(doc) ->
+ [];
+ttest_ssockf_csockf_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csockf_small_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ sock, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssockf_csockf_small_tcp6(suite) ->
+ [];
+ttest_ssockf_csockf_small_tcp6(doc) ->
+ [];
+ttest_ssockf_csockf_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csockf_small_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ sock, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssockf_csockf_medium_tcp4(suite) ->
+ [];
+ttest_ssockf_csockf_medium_tcp4(doc) ->
+ [];
+ttest_ssockf_csockf_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csockf_medium_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ sock, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssockf_csockf_medium_tcp6(suite) ->
+ [];
+ttest_ssockf_csockf_medium_tcp6(doc) ->
+ [];
+ttest_ssockf_csockf_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csockf_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ sock, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssockf_csockf_large_tcp4(suite) ->
+ [];
+ttest_ssockf_csockf_large_tcp4(doc) ->
+ [];
+ttest_ssockf_csockf_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csockf_large_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ sock, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssockf_csockf_large_tcp6(suite) ->
+ [];
+ttest_ssockf_csockf_large_tcp6(doc) ->
+ [];
+ttest_ssockf_csockf_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csockf_large_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ sock, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssockf_csocko_small_tcp4(suite) ->
+ [];
+ttest_ssockf_csocko_small_tcp4(doc) ->
+ [];
+ttest_ssockf_csocko_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csocko_small_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ sock, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssockf_csocko_small_tcp6(suite) ->
+ [];
+ttest_ssockf_csocko_small_tcp6(doc) ->
+ [];
+ttest_ssockf_csocko_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csocko_small_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ sock, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssockf_csocko_medium_tcp4(suite) ->
+ [];
+ttest_ssockf_csocko_medium_tcp4(doc) ->
+ [];
+ttest_ssockf_csocko_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csocko_medium_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ sock, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssockf_csocko_medium_tcp6(suite) ->
+ [];
+ttest_ssockf_csocko_medium_tcp6(doc) ->
+ [];
+ttest_ssockf_csocko_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csocko_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ sock, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssockf_csocko_large_tcp4(suite) ->
+ [];
+ttest_ssockf_csocko_large_tcp4(doc) ->
+ [];
+ttest_ssockf_csocko_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csocko_large_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ sock, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssockf_csocko_large_tcp6(suite) ->
+ [];
+ttest_ssockf_csocko_large_tcp6(doc) ->
+ [];
+ttest_ssockf_csocko_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csocko_large_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ sock, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssockf_csockt_small_tcp4(suite) ->
+ [];
+ttest_ssockf_csockt_small_tcp4(doc) ->
+ [];
+ttest_ssockf_csockt_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csockt_small_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ sock, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssockf_csockt_small_tcp6(suite) ->
+ [];
+ttest_ssockf_csockt_small_tcp6(doc) ->
+ [];
+ttest_ssockf_csockt_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csocko_small_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ sock, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssockf_csockt_medium_tcp4(suite) ->
+ [];
+ttest_ssockf_csockt_medium_tcp4(doc) ->
+ [];
+ttest_ssockf_csockt_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csockt_medium_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ sock, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssockf_csockt_medium_tcp6(suite) ->
+ [];
+ttest_ssockf_csockt_medium_tcp6(doc) ->
+ [];
+ttest_ssockf_csockt_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csockt_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ sock, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssockf_csockt_large_tcp4(suite) ->
+ [];
+ttest_ssockf_csockt_large_tcp4(doc) ->
+ [];
+ttest_ssockf_csockt_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csockt_large_tcp4,
+ Runtime,
+ inet,
+ sock, false,
+ sock, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssockf_csockt_large_tcp6(suite) ->
+ [];
+ttest_ssockf_csockt_large_tcp6(doc) ->
+ [];
+ttest_ssockf_csockt_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockf_csockt_large_tcp6,
+ Runtime,
+ inet6,
+ sock, false,
+ sock, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssocko_cgenf_small_tcp4(suite) ->
+ [];
+ttest_ssocko_cgenf_small_tcp4(doc) ->
+ [];
+ttest_ssocko_cgenf_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgenf_small_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ gen, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssocko_cgenf_small_tcp6(suite) ->
+ [];
+ttest_ssocko_cgenf_small_tcp6(doc) ->
+ [];
+ttest_ssocko_cgenf_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgenf_small_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ gen, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssocko_cgenf_medium_tcp4(suite) ->
+ [];
+ttest_ssocko_cgenf_medium_tcp4(doc) ->
+ [];
+ttest_ssocko_cgenf_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgenf_medium_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ gen, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssocko_cgenf_medium_tcp6(suite) ->
+ [];
+ttest_ssocko_cgenf_medium_tcp6(doc) ->
+ [];
+ttest_ssocko_cgenf_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgenf_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ gen, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssocko_cgenf_large_tcp4(suite) ->
+ [];
+ttest_ssocko_cgenf_large_tcp4(doc) ->
+ [];
+ttest_ssocko_cgenf_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgenf_large_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ gen, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssocko_cgenf_large_tcp6(suite) ->
+ [];
+ttest_ssocko_cgenf_large_tcp6(doc) ->
+ [];
+ttest_ssocko_cgenf_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgenf_large_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ gen, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssocko_cgeno_small_tcp4(suite) ->
+ [];
+ttest_ssocko_cgeno_small_tcp4(doc) ->
+ [];
+ttest_ssocko_cgeno_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgeno_small_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ gen, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssocko_cgeno_small_tcp6(suite) ->
+ [];
+ttest_ssocko_cgeno_small_tcp6(doc) ->
+ [];
+ttest_ssocko_cgeno_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgeno_small_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ gen, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssocko_cgeno_medium_tcp4(suite) ->
+ [];
+ttest_ssocko_cgeno_medium_tcp4(doc) ->
+ [];
+ttest_ssocko_cgeno_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgeno_medium_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ gen, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssocko_cgeno_medium_tcp6(suite) ->
+ [];
+ttest_ssocko_cgeno_medium_tcp6(doc) ->
+ [];
+ttest_ssocko_cgeno_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgeno_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ gen, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssocko_cgeno_large_tcp4(suite) ->
+ [];
+ttest_ssocko_cgeno_large_tcp4(doc) ->
+ [];
+ttest_ssocko_cgeno_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgeno_large_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ gen, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssocko_cgeno_large_tcp6(suite) ->
+ [];
+ttest_ssocko_cgeno_large_tcp6(doc) ->
+ [];
+ttest_ssocko_cgeno_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgeno_large_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ gen, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssocko_cgent_small_tcp4(suite) ->
+ [];
+ttest_ssocko_cgent_small_tcp4(doc) ->
+ [];
+ttest_ssocko_cgent_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgent_small_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ gen, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssocko_cgent_small_tcp6(suite) ->
+ [];
+ttest_ssocko_cgent_small_tcp6(doc) ->
+ [];
+ttest_ssocko_cgent_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgent_small_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ gen, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssocko_cgent_medium_tcp4(suite) ->
+ [];
+ttest_ssocko_cgent_medium_tcp4(doc) ->
+ [];
+ttest_ssocko_cgent_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgent_medium_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ gen, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssocko_cgent_medium_tcp6(suite) ->
+ [];
+ttest_ssocko_cgent_medium_tcp6(doc) ->
+ [];
+ttest_ssocko_cgent_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgent_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ gen, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssocko_cgent_large_tcp4(suite) ->
+ [];
+ttest_ssocko_cgent_large_tcp4(doc) ->
+ [];
+ttest_ssocko_cgent_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgent_large_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ gen, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = false
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssocko_cgent_large_tcp6(suite) ->
+ [];
+ttest_ssocko_cgent_large_tcp6(doc) ->
+ [];
+ttest_ssocko_cgent_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_cgent_large_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ gen, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssocko_csockf_small_tcp4(suite) ->
+ [];
+ttest_ssocko_csockf_small_tcp4(doc) ->
+ [];
+ttest_ssocko_csockf_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csockf_small_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ sock, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssocko_csockf_small_tcp6(suite) ->
+ [];
+ttest_ssocko_csockf_small_tcp6(doc) ->
+ [];
+ttest_ssocko_csockf_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csockf_small_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ sock, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssocko_csockf_medium_tcp4(suite) ->
+ [];
+ttest_ssocko_csockf_medium_tcp4(doc) ->
+ [];
+ttest_ssocko_csockf_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csockf_medium_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ sock, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssocko_csockf_medium_tcp6(suite) ->
+ [];
+ttest_ssocko_csockf_medium_tcp6(doc) ->
+ [];
+ttest_ssocko_csockf_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csockf_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ sock, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssocko_csockf_large_tcp4(suite) ->
+ [];
+ttest_ssocko_csockf_large_tcp4(doc) ->
+ [];
+ttest_ssocko_csockf_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csockf_large_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ sock, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssocko_csockf_large_tcp6(suite) ->
+ [];
+ttest_ssocko_csockf_large_tcp6(doc) ->
+ [];
+ttest_ssocko_csockf_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csockf_large_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ sock, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssocko_csocko_small_tcp4(suite) ->
+ [];
+ttest_ssocko_csocko_small_tcp4(doc) ->
+ [];
+ttest_ssocko_csocko_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csocko_small_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ sock, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssocko_csocko_small_tcp6(suite) ->
+ [];
+ttest_ssocko_csocko_small_tcp6(doc) ->
+ [];
+ttest_ssocko_csocko_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csocko_small_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ sock, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssocko_csocko_medium_tcp4(suite) ->
+ [];
+ttest_ssocko_csocko_medium_tcp4(doc) ->
+ [];
+ttest_ssocko_csocko_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csocko_medium_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ sock, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssocko_csocko_medium_tcp6(suite) ->
+ [];
+ttest_ssocko_csocko_medium_tcp6(doc) ->
+ [];
+ttest_ssocko_csocko_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csocko_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ sock, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssocko_csocko_large_tcp4(suite) ->
+ [];
+ttest_ssocko_csocko_large_tcp4(doc) ->
+ [];
+ttest_ssocko_csocko_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csocko_large_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ sock, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssocko_csocko_large_tcp6(suite) ->
+ [];
+ttest_ssocko_csocko_large_tcp6(doc) ->
+ [];
+ttest_ssocko_csocko_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csocko_large_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ sock, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssocko_csockt_small_tcp4(suite) ->
+ [];
+ttest_ssocko_csockt_small_tcp4(doc) ->
+ [];
+ttest_ssocko_csockt_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csockt_small_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ sock, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssocko_csockt_small_tcp6(suite) ->
+ [];
+ttest_ssocko_csockt_small_tcp6(doc) ->
+ [];
+ttest_ssocko_csockt_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csocko_small_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ sock, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssocko_csockt_medium_tcp4(suite) ->
+ [];
+ttest_ssocko_csockt_medium_tcp4(doc) ->
+ [];
+ttest_ssocko_csockt_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csockt_medium_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ sock, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssocko_csockt_medium_tcp6(suite) ->
+ [];
+ttest_ssocko_csockt_medium_tcp6(doc) ->
+ [];
+ttest_ssocko_csockt_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csockt_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ sock, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssocko_csockt_large_tcp4(suite) ->
+ [];
+ttest_ssocko_csockt_large_tcp4(doc) ->
+ [];
+ttest_ssocko_csockt_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csockt_large_tcp4,
+ Runtime,
+ inet,
+ sock, once,
+ sock, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = once
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssocko_csockt_large_tcp6(suite) ->
+ [];
+ttest_ssocko_csockt_large_tcp6(doc) ->
+ [];
+ttest_ssocko_csockt_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssocko_csockt_large_tcp6,
+ Runtime,
+ inet6,
+ sock, once,
+ sock, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssockt_cgenf_small_tcp4(suite) ->
+ [];
+ttest_ssockt_cgenf_small_tcp4(doc) ->
+ [];
+ttest_ssockt_cgenf_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgenf_small_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ gen, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssockt_cgenf_small_tcp6(suite) ->
+ [];
+ttest_ssockt_cgenf_small_tcp6(doc) ->
+ [];
+ttest_ssockt_cgenf_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgenf_small_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ gen, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssockt_cgenf_medium_tcp4(suite) ->
+ [];
+ttest_ssockt_cgenf_medium_tcp4(doc) ->
+ [];
+ttest_ssockt_cgenf_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgenf_medium_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ gen, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssockt_cgenf_medium_tcp6(suite) ->
+ [];
+ttest_ssockt_cgenf_medium_tcp6(doc) ->
+ [];
+ttest_ssockt_cgenf_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgenf_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ gen, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssockt_cgenf_large_tcp4(suite) ->
+ [];
+ttest_ssockt_cgenf_large_tcp4(doc) ->
+ [];
+ttest_ssockt_cgenf_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgenf_large_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ gen, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = false
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssockt_cgenf_large_tcp6(suite) ->
+ [];
+ttest_ssockt_cgenf_large_tcp6(doc) ->
+ [];
+ttest_ssockt_cgenf_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgenf_large_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ gen, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssockt_cgeno_small_tcp4(suite) ->
+ [];
+ttest_ssockt_cgeno_small_tcp4(doc) ->
+ [];
+ttest_ssockt_cgeno_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgeno_small_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ gen, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssockt_cgeno_small_tcp6(suite) ->
+ [];
+ttest_ssockt_cgeno_small_tcp6(doc) ->
+ [];
+ttest_ssockt_cgeno_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgeno_small_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ gen, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssockt_cgeno_medium_tcp4(suite) ->
+ [];
+ttest_ssockt_cgeno_medium_tcp4(doc) ->
+ [];
+ttest_ssockt_cgeno_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgeno_medium_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ gen, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssockt_cgeno_medium_tcp6(suite) ->
+ [];
+ttest_ssockt_cgeno_medium_tcp6(doc) ->
+ [];
+ttest_ssockt_cgeno_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgeno_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ gen, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssockt_cgeno_large_tcp4(suite) ->
+ [];
+ttest_ssockt_cgeno_large_tcp4(doc) ->
+ [];
+ttest_ssockt_cgeno_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgeno_large_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ gen, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = once
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssockt_cgeno_large_tcp6(suite) ->
+ [];
+ttest_ssockt_cgeno_large_tcp6(doc) ->
+ [];
+ttest_ssockt_cgeno_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgeno_large_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ gen, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssockt_cgent_small_tcp4(suite) ->
+ [];
+ttest_ssockt_cgent_small_tcp4(doc) ->
+ [];
+ttest_ssockt_cgent_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgent_small_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ gen, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssockt_cgent_small_tcp6(suite) ->
+ [];
+ttest_ssockt_cgent_small_tcp6(doc) ->
+ [];
+ttest_ssockt_cgent_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgent_small_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ gen, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssockt_cgent_medium_tcp4(suite) ->
+ [];
+ttest_ssockt_cgent_medium_tcp4(doc) ->
+ [];
+ttest_ssockt_cgent_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgent_medium_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ gen, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssockt_cgent_medium_tcp6(suite) ->
+ [];
+ttest_ssockt_cgent_medium_tcp6(doc) ->
+ [];
+ttest_ssockt_cgent_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgent_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ gen, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssockt_cgent_large_tcp4(suite) ->
+ [];
+ttest_ssockt_cgent_large_tcp4(doc) ->
+ [];
+ttest_ssockt_cgent_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgent_large_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ gen, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = gen_tcp, Active = true
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssockt_cgent_large_tcp6(suite) ->
+ [];
+ttest_ssockt_cgent_large_tcp6(doc) ->
+ [];
+ttest_ssockt_cgent_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_cgent_large_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ gen, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssockt_csockf_small_tcp4(suite) ->
+ [];
+ttest_ssockt_csockf_small_tcp4(doc) ->
+ [];
+ttest_ssockt_csockf_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csockf_small_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ sock, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssockt_csockf_small_tcp6(suite) ->
+ [];
+ttest_ssockt_csockf_small_tcp6(doc) ->
+ [];
+ttest_ssockt_csockf_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csockf_small_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ sock, false,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssockt_csockf_medium_tcp4(suite) ->
+ [];
+ttest_ssockt_csockf_medium_tcp4(doc) ->
+ [];
+ttest_ssockt_csockf_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csockf_medium_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ sock, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssockt_csockf_medium_tcp6(suite) ->
+ [];
+ttest_ssockt_csockf_medium_tcp6(doc) ->
+ [];
+ttest_ssockt_csockf_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csockf_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ sock, false,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssockt_csockf_large_tcp4(suite) ->
+ [];
+ttest_ssockt_csockf_large_tcp4(doc) ->
+ [];
+ttest_ssockt_csockf_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csockf_large_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ sock, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = false
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssockt_csockf_large_tcp6(suite) ->
+ [];
+ttest_ssockt_csockf_large_tcp6(doc) ->
+ [];
+ttest_ssockt_csockf_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csockf_large_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ sock, false,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssockt_csocko_small_tcp4(suite) ->
+ [];
+ttest_ssockt_csocko_small_tcp4(doc) ->
+ [];
+ttest_ssockt_csocko_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csocko_small_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ sock, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssockt_csocko_small_tcp6(suite) ->
+ [];
+ttest_ssockt_csocko_small_tcp6(doc) ->
+ [];
+ttest_ssockt_csocko_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csocko_small_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ sock, once,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssockt_csocko_medium_tcp4(suite) ->
+ [];
+ttest_ssockt_csocko_medium_tcp4(doc) ->
+ [];
+ttest_ssockt_csocko_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csocko_medium_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ sock, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssockt_csocko_medium_tcp6(suite) ->
+ [];
+ttest_ssockt_csocko_medium_tcp6(doc) ->
+ [];
+ttest_ssockt_csocko_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csocko_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ sock, once,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssockt_csocko_large_tcp4(suite) ->
+ [];
+ttest_ssockt_csocko_large_tcp4(doc) ->
+ [];
+ttest_ssockt_csocko_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csocko_large_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ sock, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = once
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssockt_csocko_large_tcp6(suite) ->
+ [];
+ttest_ssockt_csocko_large_tcp6(doc) ->
+ [];
+ttest_ssockt_csocko_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csocko_large_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ sock, once,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: small (=1)
+%% Domain: inet
+%%
+
+ttest_ssockt_csockt_small_tcp4(suite) ->
+ [];
+ttest_ssockt_csockt_small_tcp4(doc) ->
+ [];
+ttest_ssockt_csockt_small_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csockt_small_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ sock, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: small (=1)
+%% Domain: inet6
+%%
+
+ttest_ssockt_csockt_small_tcp6(suite) ->
+ [];
+ttest_ssockt_csockt_small_tcp6(doc) ->
+ [];
+ttest_ssockt_csockt_small_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csocko_small_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ sock, true,
+ 1, 200).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: medium (=2)
+%% Domain: inet
+%%
+
+ttest_ssockt_csockt_medium_tcp4(suite) ->
+ [];
+ttest_ssockt_csockt_medium_tcp4(doc) ->
+ [];
+ttest_ssockt_csockt_medium_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csockt_medium_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ sock, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: medium (=2)
+%% Domain: inet6
+%%
+
+ttest_ssockt_csockt_medium_tcp6(suite) ->
+ [];
+ttest_ssockt_csockt_medium_tcp6(doc) ->
+ [];
+ttest_ssockt_csockt_medium_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csockt_medium_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ sock, true,
+ 2, 20).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: large (=3)
+%% Domain: inet
+%%
+
+ttest_ssockt_csockt_large_tcp4(suite) ->
+ [];
+ttest_ssockt_csockt_large_tcp4(doc) ->
+ [];
+ttest_ssockt_csockt_large_tcp4(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csockt_large_tcp4,
+ Runtime,
+ inet,
+ sock, true,
+ sock, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% This test case uses the time test (ttest) utility to implement a
+%% ping-pong like test case.
+%% Server: Transport = socket(tcp), Active = true
+%% Client: Transport = socket(tcp), Active = true
+%% Message Size: large (=3)
+%% Domain: inet6
+%%
+
+ttest_ssockt_csockt_large_tcp6(suite) ->
+ [];
+ttest_ssockt_csockt_large_tcp6(doc) ->
+ [];
+ttest_ssockt_csockt_large_tcp6(Config) when is_list(Config) ->
+ Runtime = which_ttest_runtime(Config),
+ ttest_tcp(ttest_ssockt_csockt_large_tcp6,
+ Runtime,
+ inet6,
+ sock, true,
+ sock, true,
+ 3, 2).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+which_ttest_runtime(Config) when is_list(Config) ->
+ case lists:keysearch(esock_test_ttest_runtime, 1, Config) of
+ {value, {esock_test_ttest_runtime, Runtime}} ->
+ Runtime;
+ false ->
+ which_ttest_runtime_env()
+ end.
+
+which_ttest_runtime_env() ->
+ which_ttest_runtime_env(os:getenv("ESOCK_TEST_TTEST_RUNTIME")).
+
+which_ttest_runtime_env(TStr) when is_list(TStr) ->
+ which_ttest_runtime_env2(lists:reverse(TStr));
+which_ttest_runtime_env(false) ->
+ ?TTEST_RUNTIME.
+
+
+%% The format is: <int>[unit]
+%% where the optional unit can be:
+%% ms: milliseconds
+%% s: seconds (default)
+%% m: minutes
+which_ttest_runtime_env2([$m, $s | MS]) when (length(MS) > 0) ->
+ convert_time(MS, fun(X) -> X end);
+which_ttest_runtime_env2([$m | M]) when (length(M) > 0) ->
+ convert_time(M, fun(X) -> ?MINS(X) end);
+which_ttest_runtime_env2([$s | S]) when (length(S) > 0) ->
+ convert_time(S, fun(X) -> ?SECS(X) end);
+which_ttest_runtime_env2(S) ->
+ convert_time(S, fun(X) -> ?SECS(X) end).
+
+convert_time(TStrRev, Convert) ->
+ try list_to_integer(lists:reverse(TStrRev)) of
+ I -> Convert(I)
+ catch
+ _:_ ->
+ ?TTEST_RUNTIME
+ end.
+
+ttest_tcp(TC,
+ Domain,
+ ServerMod, ServerActive,
+ ClientMod, ClientActive,
+ MsgID, MaxOutstanding) ->
+ ttest_tcp(TC,
+ ?TTEST_RUNTIME,
+ Domain,
+ ServerMod, ServerActive,
+ ClientMod, ClientActive,
+ MsgID, MaxOutstanding).
+ttest_tcp(TC,
+ Runtime,
+ Domain,
+ ServerMod, ServerActive,
+ ClientMod, ClientActive,
+ MsgID, MaxOutstanding) ->
+ tc_try(TC,
+ fun() ->
+ if (Domain =/= inet) -> not_yet_implemented(); true -> ok end,
+ %% This may be overkill, depending on the runtime,
+ %% but better safe then sorry...
+ ?TT(Runtime + ?SECS(60)),
+ InitState = #{domain => Domain,
+ msg_id => MsgID,
+ max_outstanding => MaxOutstanding,
+ runtime => Runtime,
+ server_mod => ServerMod,
+ server_active => ServerActive,
+ client_mod => ClientMod,
+ client_active => ClientActive},
+ ok = ttest_tcp(InitState)
+ end).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ttest_tcp(InitState) ->
+ ServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ Tester = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+
+ %% *** Init part ***
+ #{desc => "create node",
+ cmd => fun(#{host := Host} = State) ->
+ case start_node(Host, server) of
+ {ok, Node} ->
+ {ok, State#{node => Node}};
+ {error, Reason, _} ->
+ {error, Reason}
+ end
+ end},
+ #{desc => "monitor server node",
+ cmd => fun(#{node := Node} = _State) ->
+ true = erlang:monitor_node(Node, true),
+ ok
+ end},
+ #{desc => "start ttest (remote) server",
+ cmd => fun(#{mod := Mod,
+ active := Active,
+ node := Node} = State) ->
+ case ttest_tcp_server_start(Node, Mod, Active) of
+ {ok, {{Pid, _MRef}, {Addr, Port}}} ->
+ {ok, State#{rserver => Pid,
+ addr => Addr,
+ port => Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester,
+ addr := Addr,
+ port := Port}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init, {Addr, Port}),
+ ok
+ end},
+
+
+ %% *** Termination ***
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester,
+ rserver := RServer} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester,
+ [{rserver, RServer}]) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ %% The remote server is in a accept, with a timeout of 5 seconds,
+ %% so may have to wait a bit...
+ #{desc => "order (remote) ttest server terminate",
+ cmd => fun(#{node := _Node,
+ rserver := RServer}) ->
+ ttest_tcp_server_stop(RServer),
+ ok
+ end},
+ #{desc => "await ttest (remote) server termination",
+ cmd => fun(#{rserver := RServer} = State) ->
+ ?SEV_AWAIT_TERMINATION(RServer),
+ State1 = maps:remove(rserver, State),
+ {ok, State1}
+ end},
+ #{desc => "stop (server) node",
+ cmd => fun(#{node := Node} = _State) ->
+ stop_node(Node)
+ end},
+ #{desc => "await (server) node termination",
+ cmd => fun(#{node := Node} = State) ->
+ receive
+ {nodedown, Node} ->
+ {ok, maps:remove(node, State)}
+ end
+ end},
+
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ ClientSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, {ServerAddr, ServerPort}} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester,
+ server_addr => ServerAddr,
+ server_port => ServerPort}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+
+ %% *** Init part ***
+ #{desc => "create node",
+ cmd => fun(#{host := Host} = State) ->
+ case start_node(Host, client) of
+ {ok, Node} ->
+ {ok, State#{node => Node}};
+ {error, Reason, _} ->
+ {error, Reason}
+ end
+ end},
+ #{desc => "monitor client node",
+ cmd => fun(#{node := Node} = _State) ->
+ true = erlang:monitor_node(Node, true),
+ ok
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+
+ %% The actual test
+ #{desc => "await continue (ttest)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, ttest),
+ ok
+ end},
+ #{desc => "start ttest (remote) client",
+ cmd => fun(#{node := Node,
+ mod := Mod,
+ active := Active,
+ msg_id := MsgID,
+ max_outstanding := MaxOutstanding,
+ runtime := RunTime,
+ server_addr := Addr,
+ server_port := Port} = State) ->
+ Self = self(),
+ Notify =
+ fun(Result) ->
+ ?SEV_ANNOUNCE_READY(Self, ttest, Result)
+ end,
+ case ttest_tcp_client_start(Node, Notify,
+ Mod, Active,
+ Addr, Port,
+ MsgID, MaxOutstanding,
+ RunTime) of
+ {ok, {Pid, _MRef}} ->
+ {ok, State#{rclient => Pid}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await ttest ready",
+ cmd => fun(#{tester := Tester,
+ rclient := RClient} = State) ->
+ %% TTestResult = ?SEV_AWAIT_READY(RClient, rclient, ttest,
+ %% [{tester, Tester}]),
+ case ?SEV_AWAIT_READY(RClient, rclient, ttest,
+ [{tester, Tester}]) of
+ {ok, Result} ->
+ {ok, State#{result => Result}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "await ttest (remote) client termination",
+ cmd => fun(#{rclient := RClient} = State) ->
+ ?SEV_AWAIT_TERMINATION(RClient),
+ State1 = maps:remove(rclient, State),
+ {ok, State1}
+ end},
+ #{desc => "announce ready (ttest)",
+ cmd => fun(#{tester := Tester,
+ result := Result} = State) ->
+ ?SEV_ANNOUNCE_READY(Tester, ttest, Result),
+ {ok, maps:remove(result, State)}
+ end},
+
+
+ %% *** Termination ***
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "stop (client) node",
+ cmd => fun(#{node := Node} = _State) ->
+ stop_node(Node)
+ end},
+ #{desc => "await (client) node termination",
+ cmd => fun(#{node := Node} = State) ->
+ receive
+ {nodedown, Node} ->
+ {ok, maps:remove(node, State)}
+ end
+ end},
+
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+ #{desc => "monitor client",
+ cmd => fun(#{client := Pid} = _State) ->
+ _MRef = erlang:monitor(process, Pid),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = State) ->
+ {ok, {Addr, Port}} = ?SEV_AWAIT_READY(Pid, server, init),
+ {ok, State#{server_addr => Addr,
+ server_port => Port}}
+ end},
+
+
+ %% Start the client
+ #{desc => "order client start",
+ cmd => fun(#{client := Pid,
+ server_addr := Addr,
+ server_port := Port} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, {Addr, Port}),
+ ok
+ end},
+ #{desc => "await client ready (init)",
+ cmd => fun(#{client := Client} = _State) ->
+ ok = ?SEV_AWAIT_READY(Client, client, init)
+ end},
+
+ %% The actual test
+ #{desc => "order client continue (ttest)",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Client, ttest),
+ ok
+ end},
+ #{desc => "await client ready (ttest)",
+ cmd => fun(#{server := Server,
+ client := Client} = State) ->
+ case ?SEV_AWAIT_READY(Client, client, ttest,
+ [{server, Server}]) of
+ {ok, Result} ->
+ {ok, State#{result => Result}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+
+
+ %% *** Terminate server ***
+ #{desc => "order client terminate",
+ cmd => fun(#{client := Client} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Client),
+ ok
+ end},
+ #{desc => "await client down",
+ cmd => fun(#{client := Client} = State) ->
+ ?SEV_AWAIT_TERMINATION(Client),
+ State1 = maps:remove(client, State),
+ {ok, State1}
+ end},
+ #{desc => "order server terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server down",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_TERMINATION(Server),
+ ok
+ end},
+
+
+ %% Present the results
+ #{desc => "present the results",
+ cmd => fun(#{result := Result} = State) ->
+ case Result of
+ #{status := ok,
+ runtime := RunTime,
+ cnt := Cnt,
+ bcnt := BCnt} ->
+ ?SEV_IPRINT(
+ "TTest results: "
+ "~n Run Time: ~s"
+ "~n Byte Count: ~s"
+ "~n Number of message exchanges: ~s"
+ "~n~n",
+ [
+ ?TTEST_LIB:format_time(RunTime),
+ if ((BCnt =:= 0) orelse (RunTime =:= 0)) ->
+ ?TTEST_LIB:format("~w, ~w",
+ [BCnt, RunTime]);
+ true ->
+ ?TTEST_LIB:format("~p => ~p byte / ms",
+ [BCnt, BCnt div RunTime])
+ end,
+ if (RunTime =:= 0) ->
+ "-";
+ true ->
+ ?TTEST_LIB:format("~p => ~p iterations / ms",
+ [Cnt, Cnt div RunTime])
+ end
+ ]),
+ {ok, maps:remove(result, State)};
+
+ #{status := Failure,
+ runtime := RunTime,
+ sid := SID,
+ rid := RID,
+ scnt := SCnt,
+ rcnt := RCnt,
+ bcnt := BCnt,
+ num := Num} ->
+ ?SEV_EPRINT("Time Test failed: "
+ "~n ~p"
+ "~n"
+ "~nwhen"
+ "~n"
+ "~n Run Time: ~s"
+ "~n Send ID: ~p"
+ "~n Recv ID: ~p"
+ "~n Send Count: ~p"
+ "~n Recv Count: ~p"
+ "~n Byte Count: ~p"
+ "~n Num Iterations: ~p",
+ [Failure,
+ ?TTEST_LIB:format_time(RunTime),
+ SID, RID, SCnt, RCnt, BCnt, Num]),
+ {error, Failure}
+ end
+ end},
+
+ %% This is just so that the printout above shall have time to come
+ %% out before then end of the test case.
+ ?SEV_SLEEP(?SECS(1)),
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+ i("start server evaluator"),
+ ServerInitState = #{host => local_host(),
+ domain => maps:get(domain, InitState),
+ mod => maps:get(server_mod, InitState),
+ active => maps:get(server_active, InitState)},
+ Server = ?SEV_START("server", ServerSeq, ServerInitState),
+
+ i("start client evaluator"),
+ ClientInitState = #{host => local_host(),
+ domain => maps:get(domain, InitState),
+ mod => maps:get(client_mod, InitState),
+ active => maps:get(client_active, InitState),
+ msg_id => maps:get(msg_id, InitState),
+ max_outstanding => maps:get(max_outstanding, InitState),
+ runtime => maps:get(runtime, InitState)},
+ Client = ?SEV_START("client", ClientSeq, ClientInitState),
+
+ i("start 'tester' evaluator"),
+ TesterInitState = #{server => Server#ev.pid,
+ client => Client#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]).
+
+
+
+ttest_tcp_server_start(Node, gen, Active) ->
+ Transport = socket_test_ttest_tcp_gen,
+ socket_test_ttest_tcp_server:start_monitor(Node, Transport, Active);
+ttest_tcp_server_start(Node, sock, Active) ->
+ TransportMod = socket_test_ttest_tcp_socket,
+ Transport = {TransportMod, #{method => plain}},
+ socket_test_ttest_tcp_server:start_monitor(Node, Transport, Active).
+
+ttest_tcp_server_stop(Pid) ->
+ socket_test_ttest_tcp_server:stop(Pid).
+
+ttest_tcp_client_start(Node,
+ Notify,
+ gen,
+ Active, Addr, Port, MsgID, MaxOutstanding, RunTime) ->
+ Transport = socket_test_ttest_tcp_gen,
+ socket_test_ttest_tcp_client:start_monitor(Node,
+ Notify,
+ Transport,
+ Active,
+ Addr, Port,
+ MsgID, MaxOutstanding, RunTime);
+ttest_tcp_client_start(Node,
+ Notify,
+ sock,
+ Active, Addr, Port, MsgID, MaxOutstanding, RunTime) ->
+ TransportMod = socket_test_ttest_tcp_socket,
+ Transport = {TransportMod, #{method => plain}},
+ socket_test_ttest_tcp_client:start_monitor(Node,
+ Notify,
+ Transport,
+ Active,
+ Addr, Port,
+ MsgID, MaxOutstanding, RunTime).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+start_node(Host, NodeName) ->
+ UniqueNodeName = f("~w_~w", [NodeName, erlang:system_time(millisecond)]),
+ case do_start_node(Host, UniqueNodeName) of
+ {ok, _} = OK ->
+ global:sync(),
+ %% i("Node ~p started: "
+ %% "~n Nodes: ~p"
+ %% "~n Logger: ~p"
+ %% "~n Global Names: ~p",
+ %% [NodeName, nodes(),
+ %% global:whereis_name(socket_test_logger),
+ %% global:registered_names()]),
+ OK;
+ {error, Reason, _} ->
+ {error, Reason}
+ end.
+
+do_start_node(Host, NodeName) when is_list(NodeName) ->
+ do_start_node(Host, list_to_atom(NodeName));
+do_start_node(Host, NodeName) when is_atom(NodeName) ->
+ Dir = filename:dirname(code:which(?MODULE)),
+ Flags = "-pa " ++ Dir,
+ Opts = [{monitor_master, true}, {erl_flags, Flags}],
+ ct_slave:start(Host, NodeName, Opts).
+
+
+stop_node(Node) ->
+ case ct_slave:stop(Node) of
+ {ok, _} ->
+ ok;
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+sock_open(Domain, Type, Proto) ->
+ try socket:open(Domain, Type, Proto) of
+ {ok, Socket} ->
+ Socket;
+ {error, Reason} ->
+ ?FAIL({open, Reason})
+ catch
+ C:E:S ->
+ ?FAIL({open, C, E, S})
+ end.
+
+
+sock_bind(Sock, SockAddr) ->
+ try socket:bind(Sock, SockAddr) of
+ {ok, Port} ->
+ Port;
+ {error, Reason} ->
+ i("sock_bind -> error: ~p", [Reason]),
+ ?FAIL({bind, Reason})
+ catch
+ C:E:S ->
+ i("sock_bind -> failed: ~p, ~p, ~p", [C, E, S]),
+ ?FAIL({bind, C, E, S})
+ end.
+
+sock_connect(Sock, SockAddr) ->
+ try socket:connect(Sock, SockAddr) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ ?FAIL({connect, Reason})
+ catch
+ C:E:S ->
+ ?FAIL({connect, C, E, S})
+ end.
+
+sock_sockname(Sock) ->
+ try socket:sockname(Sock) of
+ {ok, SockAddr} ->
+ SockAddr;
+ {error, Reason} ->
+ ?FAIL({sockname, Reason})
+ catch
+ C:E:S ->
+ ?FAIL({sockname, C, E, S})
+ end.
+
+
+%% sock_listen(Sock) ->
+%% sock_listen2(fun() -> socket:listen(Sock) end).
+
+%% sock_listen(Sock, BackLog) ->
+%% sock_listen2(fun() -> socket:listen(Sock, BackLog) end).
+
+%% sock_listen2(Listen) ->
+%% try Listen() of
+%% ok ->
+%% ok;
+%% {error, Reason} ->
+%% ?FAIL({listen, Reason})
+%% catch
+%% C:E:S ->
+%% ?FAIL({listen, C, E, S})
+%% end.
+
+
+%% sock_accept(LSock) ->
+%% try socket:accept(LSock) of
+%% {ok, Sock} ->
+%% Sock;
+%% {error, Reason} ->
+%% i("sock_accept -> error: ~p", [Reason]),
+%% ?FAIL({accept, Reason})
+%% catch
+%% C:E:S ->
+%% i("sock_accept -> failed: ~p, ~p, ~p", [C, E, S]),
+%% ?FAIL({accept, C, E, S})
+%% end.
+
+
+sock_close(Sock) ->
+ try socket:close(Sock) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ i("sock_close -> error: ~p", [Reason]),
+ ?FAIL({close, Reason})
+ catch
+ C:E:S ->
+ i("sock_close -> failed: ~p, ~p, ~p", [C, E, S]),
+ ?FAIL({close, C, E, S})
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+local_host() ->
+ try net_adm:localhost() of
+ Host when is_list(Host) ->
+ %% Convert to shortname if long
+ case string:tokens(Host, [$.]) of
+ [H|_] ->
+ list_to_atom(H)
+ end
+ catch
+ C:E:S ->
+ erlang:raise(C, E, S)
+ end.
+
+
+%% This gets the local address (not 127.0...)
+%% We should really implement this using the (new) net module,
+%% but until that gets the necessary functionality...
+which_local_addr(Domain) ->
+ case inet:getifaddrs() of
+ {ok, IFL} ->
+ which_addr(Domain, IFL);
+ {error, Reason} ->
+ ?FAIL({inet, getifaddrs, Reason})
+ end.
+
+which_addr(_Domain, []) ->
+ ?FAIL(no_address);
+which_addr(Domain, [{"lo" ++ _, _}|IFL]) ->
+ which_addr(Domain, IFL);
+which_addr(Domain, [{_Name, IFO}|IFL]) ->
+ case which_addr2(Domain, IFO) of
+ {ok, Addr} ->
+ Addr;
+ {error, no_address} ->
+ which_addr(Domain, IFL)
+ end;
+which_addr(Domain, [_|IFL]) ->
+ which_addr(Domain, IFL).
+
+which_addr2(_Domain, []) ->
+ {error, no_address};
+which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) ->
+ {ok, Addr};
+which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) ->
+ {ok, Addr};
+which_addr2(Domain, [_|IFO]) ->
+ which_addr2(Domain, IFO).
+
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+not_yet_implemented() ->
+ skip("not yet implemented").
+
+skip(Reason) ->
+ throw({skip, Reason}).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+t() ->
+ os:timestamp().
+
+
+tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) ->
+ T1 = A1*1000000000+B1*1000+(C1 div 1000),
+ T2 = A2*1000000000+B2*1000+(C2 div 1000),
+ T2 - T1.
+
+
+formated_timestamp() ->
+ format_timestamp(os:timestamp()).
+
+format_timestamp({_N1, _N2, _N3} = TS) ->
+ {_Date, Time} = calendar:now_to_local_time(TS),
+ %% {YYYY,MM,DD} = Date,
+ {Hour,Min,Sec} = Time,
+ %% FormatTS =
+ %% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w.~w",
+ %% [YYYY, MM, DD, Hour, Min, Sec, N3]),
+ FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w", [Hour, Min, Sec]),
+ lists:flatten(FormatTS).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+set_tc_name(N) when is_atom(N) ->
+ set_tc_name(atom_to_list(N));
+set_tc_name(N) when is_list(N) ->
+ put(tc_name, N).
+
+%% get_tc_name() ->
+%% get(tc_name).
+
+tc_begin(TC) ->
+ set_tc_name(TC),
+ tc_print("begin ***",
+ "~n----------------------------------------------------~n", "").
+
+tc_end(Result) when is_list(Result) ->
+ tc_print("done: ~s", [Result],
+ "", "----------------------------------------------------~n~n"),
+ ok.
+
+
+tc_try(Case, Fun) when is_atom(Case) andalso is_function(Fun, 0) ->
+ tc_begin(Case),
+ try
+ begin
+ Fun(),
+ ?SLEEP(?SECS(1)),
+ tc_end("ok")
+ end
+ catch
+ throw:{skip, _} = SKIP ->
+ tc_end("skipping"),
+ SKIP;
+ Class:Error:Stack ->
+ tc_end("failed"),
+ erlang:raise(Class, Error, Stack)
+ end.
+
+
+tc_print(F, Before, After) ->
+ tc_print(F, [], Before, After).
+
+tc_print(F, A, Before, After) ->
+ Name = tc_which_name(),
+ FStr = f("*** [~s][~s][~p] " ++ F ++ "~n",
+ [formated_timestamp(),Name,self()|A]),
+ io:format(user, Before ++ FStr ++ After, []).
+
+tc_which_name() ->
+ case get(tc_name) of
+ undefined ->
+ case get(sname) of
+ undefined ->
+ "";
+ SName when is_list(SName) ->
+ SName
+ end;
+ Name when is_list(Name) ->
+ Name
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+l2a(S) when is_list(S) ->
+ list_to_atom(S).
+
+l2b(L) when is_list(L) ->
+ list_to_binary(L).
+
+b2l(B) when is_binary(B) ->
+ binary_to_list(B).
+
+f(F, A) ->
+ lists:flatten(io_lib:format(F, A)).
+
+%% p(F) ->
+%% p(F, []).
+
+%% p(F, A) ->
+%% p(F, A, "", "").
+
+%% p(F, A, Before, After) when is_list(Before) andalso is_list(After) ->
+%% TcName =
+%% case get(tc_name) of
+%% undefined ->
+%% case get(sname) of
+%% undefined ->
+%% "";
+%% SName when is_list(SName) ->
+%% SName
+%% end;
+%% Name when is_list(Name) ->
+%% Name
+%% end,
+%% FStr = f("*** [~s][~s][~p] " ++ F ++ "~n",
+%% [formated_timestamp(),TcName,self()|A]),
+%% i(Before ++ FStr ++ After, []).
+
+
+%% d(F, A) ->
+%% d(get(dbg_fd), F, A).
+
+%% d(undefined, F, A) ->
+%% [NodeNameStr|_] = string:split(atom_to_list(node()), [$@]),
+%% DbgFileName = f("~s-dbg.txt", [NodeNameStr]),
+%% case file:open(DbgFileName, [write]) of
+%% {ok, FD} ->
+%% put(dbg_fd, FD),
+%% d(FD, F, A);
+%% {error, Reason} ->
+%% exit({failed_open_dbg_file, Reason})
+%% end;
+%% d(FD, F, A) ->
+%% io:format(FD, "~s~n", [f("[~s] " ++ F, [formated_timestamp()|A])]).
+
+i(F) ->
+ i(F, []).
+
+i(F, A) ->
+ FStr = f("[~s] " ++ F, [formated_timestamp()|A]),
+ io:format(user, FStr ++ "~n", []),
+ io:format(FStr, []).
+
diff --git a/erts/emulator/test/socket_test_evaluator.erl b/erts/emulator/test/socket_test_evaluator.erl
new file mode 100644
index 0000000000..fe6a6ff70a
--- /dev/null
+++ b/erts/emulator/test/socket_test_evaluator.erl
@@ -0,0 +1,524 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2018. All 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(socket_test_evaluator).
+
+%% Evaluator control functions
+-export([
+ start/3,
+ await_finish/1
+ ]).
+
+%% Functions used by evaluators to interact with eachother
+-export([
+ %% Announce functions
+ %% (Send an announcement from one evaluator to another)
+ announce_start/1, announce_start/2,
+ announce_continue/2, announce_continue/3,
+ announce_ready/2, announce_ready/3,
+ announce_terminate/1,
+
+ %% Await functions
+ %% (Wait for an announcement from another evaluator)
+ await_start/0, await_start/1,
+ await_continue/3, await_continue/4,
+ await_ready/3, await_ready/4,
+ await_terminate/2, await_terminate/3,
+ await_termination/1, await_termination/2
+ ]).
+
+%% Utility functions
+-export([
+ iprint/2, % Info printouts
+ eprint/2 % Error printouts
+ ]).
+
+-export_type([
+ ev/0,
+ initial_evaluator_state/0,
+ evaluator_state/0,
+ command_fun/0,
+ command/0
+ ]).
+
+
+-include("socket_test_evaluator.hrl").
+
+-type ev() :: #ev{}.
+-type initial_evaluator_state() :: map().
+-type evaluator_state() :: term().
+-type command_fun() ::
+ fun((State :: evaluator_state()) -> ok) |
+ fun((State :: evaluator_state()) -> {ok, evaluator_state()}) |
+ fun((State :: evaluator_state()) -> {error, term()}).
+
+-type command() :: #{desc := string(),
+ cmd := command_fun()}.
+
+
+%% ============================================================================
+
+-define(LIB, socket_test_lib).
+-define(LOGGER, socket_test_logger).
+
+-define(EXTRA_NOTHING, '$nothing').
+-define(ANNOUNCEMENT_START, '$start').
+-define(ANNOUNCEMENT_READY, '$ready').
+-define(ANNOUNCEMENT_CONTINUE, '$continue').
+-define(ANNOUNCEMENT_TERMINATE, '$terminate').
+
+-define(START_NAME_NONE, '$no-name').
+-define(START_SLOGAN, ?ANNOUNCEMENT_START).
+-define(TERMINATE_SLOGAN, ?ANNOUNCEMENT_TERMINATE).
+
+
+%% ============================================================================
+
+-spec start(Name, Seq, Init) -> ev() when
+ Name :: string(),
+ Seq :: [command()],
+ Init :: initial_evaluator_state().
+
+start(Name, Seq, InitState)
+ when is_list(Name) andalso is_list(Seq) andalso (Seq =/= []) ->
+ %% Make sure 'parent' is not already used
+ case maps:find(parent, InitState) of
+ {ok, _} ->
+ erlang:error({already_used, parent});
+ error ->
+ InitState2 = InitState#{parent => self()},
+ {Pid, MRef} = erlang:spawn_monitor(
+ fun() -> init(Name, Seq, InitState2) end),
+ #ev{name = Name, pid = Pid, mref = MRef}
+ end.
+
+init(Name, Seq, Init) ->
+ put(sname, Name),
+ loop(1, Seq, Init).
+
+loop(_ID, [], FinalState) ->
+ exit(FinalState);
+loop(ID, [#{desc := Desc,
+ cmd := Cmd}|Cmds], State) when is_function(Cmd, 1) ->
+ iprint("evaluate command ~2w: ~s", [ID, Desc]),
+ try Cmd(State) of
+ ok ->
+ loop(ID + 1, Cmds, State);
+ {ok, NewState} ->
+ loop(ID + 1, Cmds, NewState);
+ {skip, Reason} ->
+ exit({skip, Reason});
+ {error, Reason} ->
+ eprint("command ~w failed: "
+ "~n Reason: ~p", [ID, Reason]),
+ exit({command_failed, ID, Reason, State})
+ catch
+ throw:{skip, R} = E:_ ->
+ eprint("command ~w skip: "
+ "~n Skip Reason: ~p", [ID, R]),
+ exit(E);
+ C:E:S ->
+ eprint("command ~w crashed: "
+ "~n Class: ~p"
+ "~n Error: ~p"
+ "~n Call Stack: ~p", [ID, C, E, S]),
+ exit({command_crashed, ID, {C,E,S}, State})
+ end.
+
+
+%% ============================================================================
+
+-spec await_finish(Evs) -> term() when
+ Evs :: [ev()].
+
+await_finish(Evs) ->
+ await_finish(Evs, []).
+
+await_finish([], []) ->
+ ok;
+await_finish([], Fails) ->
+ ?SEV_EPRINT("Fails: "
+ "~n ~p", [Fails]),
+ Fails;
+await_finish(Evs, Fails) ->
+ receive
+ %% Successfull termination of evaluator
+ {'DOWN', _MRef, process, Pid, normal} ->
+ case lists:keysearch(Pid, #ev.pid, Evs) of
+ {value, #ev{name = Name}} ->
+ iprint("evaluator '~s' (~p) success", [Name, Pid]),
+ NewEvs = lists:keydelete(Pid, #ev.pid, Evs),
+ await_finish(NewEvs, Fails);
+ false ->
+ iprint("unknown process ~p died (normal)", [Pid]),
+ await_finish(Evs, Fails)
+ end;
+
+ %% The evaluator can skip the teat case:
+ {'DOWN', _MRef, process, Pid, {skip, Reason}} ->
+ case lists:keysearch(Pid, #ev.pid, Evs) of
+ {value, #ev{name = Name}} ->
+ iprint("evaluator '~s' (~p) issued SKIP: "
+ "~n ~p", [Name, Pid, Reason]);
+ false ->
+ iprint("unknown process ~p issued SKIP: "
+ "~n ~p", [Pid, Reason])
+ end,
+ ?LIB:skip(Reason);
+
+ %% Evaluator failed
+ {'DOWN', _MRef, process, Pid, Reason} ->
+ case lists:keysearch(Pid, #ev.pid, Evs) of
+ {value, #ev{name = Name}} ->
+ iprint("evaluator '~s' (~p) failed", [Name, Pid]),
+ NewEvs = lists:keydelete(Pid, #ev.pid, Evs),
+ await_finish(NewEvs, [{Pid, Reason}|Fails]);
+ false ->
+ iprint("unknown process ~p died: "
+ "~n ~p", [Pid, Reason]),
+ await_finish(Evs, Fails)
+ end
+ end.
+
+
+%% ============================================================================
+
+-spec announce_start(To) -> ok when
+ To :: pid().
+
+announce_start(To) ->
+ announce(To, ?ANNOUNCEMENT_START, ?START_SLOGAN).
+
+-spec announce_start(To, Extra) -> ok when
+ To :: pid(),
+ Extra :: term().
+
+announce_start(To, Extra) ->
+ announce(To, ?ANNOUNCEMENT_START, ?START_SLOGAN, Extra).
+
+
+%% ============================================================================
+
+-spec announce_continue(To, Slogan) -> ok when
+ To :: pid(),
+ Slogan :: atom().
+
+announce_continue(To, Slogan) ->
+ announce_continue(To, Slogan, ?EXTRA_NOTHING).
+
+-spec announce_continue(To, Slogan, Extra) -> ok when
+ To :: pid(),
+ Slogan :: atom(),
+ Extra :: term().
+
+announce_continue(To, Slogan, Extra) ->
+ announce(To, ?ANNOUNCEMENT_CONTINUE, Slogan, Extra).
+
+
+%% ============================================================================
+
+-spec announce_ready(To, Slogan) -> ok when
+ To :: pid(),
+ Slogan :: atom().
+
+announce_ready(To, Slogan) ->
+ announce_ready(To, Slogan, ?EXTRA_NOTHING).
+
+-spec announce_ready(To, Slogan, Extra) -> ok when
+ To :: pid(),
+ Slogan :: atom(),
+ Extra :: term().
+
+announce_ready(To, Slogan, Extra) ->
+ announce(To, ?ANNOUNCEMENT_READY, Slogan, Extra).
+
+
+%% ============================================================================
+
+-spec announce_terminate(To) -> ok when
+ To :: pid().
+
+announce_terminate(To) ->
+ announce(To, ?ANNOUNCEMENT_TERMINATE, ?TERMINATE_SLOGAN).
+
+
+%% ============================================================================
+
+-spec announce(To, Announcement, Slogan) -> ok when
+ To :: pid(),
+ Announcement :: atom(),
+ Slogan :: atom().
+
+announce(To, Announcement, Slogan) ->
+ announce(To, Announcement, Slogan, ?EXTRA_NOTHING).
+
+-spec announce(To, Announcement, Slogan, Extra) -> ok when
+ To :: pid(),
+ Announcement :: atom(),
+ Slogan :: atom(),
+ Extra :: term().
+
+announce(To, Announcement, Slogan, Extra)
+ when is_pid(To) andalso
+ is_atom(Announcement) andalso
+ is_atom(Slogan) ->
+ %% iprint("announce -> entry with: "
+ %% "~n To: ~p"
+ %% "~n Announcement: ~p"
+ %% "~n Slogan: ~p"
+ %% "~n Extra: ~p",
+ %% [To, Announcement, Slogan, Extra]),
+ To ! {Announcement, self(), Slogan, Extra},
+ ok.
+
+
+
+%% ============================================================================
+
+-spec await_start() -> Pid | {Pid, Extra} when
+ Pid :: pid(),
+ Extra :: term().
+
+await_start() ->
+ await_start(any).
+
+-spec await_start(Pid) -> Pid | {Pid, Extra} when
+ Pid :: pid(),
+ Extra :: term().
+
+await_start(P) when is_pid(P) orelse (P =:= any) ->
+ case await(P, ?START_NAME_NONE, ?ANNOUNCEMENT_START, ?START_SLOGAN, []) of
+ {ok, Any} when is_pid(P) ->
+ Any;
+ {ok, Pid} when is_pid(Pid) andalso (P =:= any) ->
+ Pid;
+ {ok, {Pid, _} = OK} when is_pid(Pid) andalso (P =:= any) ->
+ OK
+ end.
+
+
+%% ============================================================================
+
+-spec await_continue(From, Name, Slogan) -> ok | {ok, Extra} | {error, Reason} when
+ From :: pid(),
+ Name :: atom(),
+ Slogan :: atom(),
+ Extra :: term(),
+ Reason :: term().
+
+await_continue(From, Name, Slogan) ->
+ await_continue(From, Name, Slogan, []).
+
+-spec await_continue(From, Name, Slogan, OtherPids) ->
+ ok | {ok, Extra} | {error, Reason} when
+ From :: pid(),
+ Name :: atom(),
+ Slogan :: atom(),
+ OtherPids :: [{pid(), atom()}],
+ Extra :: term(),
+ Reason :: term().
+
+await_continue(From, Name, Slogan, OtherPids)
+ when is_pid(From) andalso
+ is_atom(Name) andalso
+ is_atom(Slogan) andalso
+ is_list(OtherPids) ->
+ await(From, Name, ?ANNOUNCEMENT_CONTINUE, Slogan, OtherPids).
+
+
+
+%% ============================================================================
+
+-spec await_ready(From, Name, Slogan) -> ok | {ok, Extra} | {error, Reason} when
+ From :: pid(),
+ Name :: atom(),
+ Slogan :: atom(),
+ Extra :: term(),
+ Reason :: term().
+
+await_ready(From, Name, Slogan) ->
+ await_ready(From, Name, Slogan, []).
+
+-spec await_ready(From, Name, Slogan, OtherPids) ->
+ ok | {ok, Extra} | {error, Reason} when
+ From :: pid(),
+ Name :: atom(),
+ Slogan :: atom(),
+ OtherPids :: [{pid(), atom()}],
+ Extra :: term(),
+ Reason :: term().
+
+await_ready(From, Name, Slogan, OtherPids)
+ when is_pid(From) andalso
+ is_atom(Name) andalso
+ is_atom(Slogan) andalso
+ is_list(OtherPids) ->
+ await(From, Name, ?ANNOUNCEMENT_READY, Slogan, OtherPids).
+
+
+
+%% ============================================================================
+
+-spec await_terminate(Pid, Name) -> ok | {error, Reason} when
+ Pid :: pid(),
+ Name :: atom(),
+ Reason :: term().
+
+await_terminate(Pid, Name) when is_pid(Pid) andalso is_atom(Name) ->
+ await_terminate(Pid, Name, []).
+
+-spec await_terminate(Pid, Name, OtherPids) -> ok | {error, Reason} when
+ Pid :: pid(),
+ Name :: atom(),
+ OtherPids :: [{pid(), atom()}],
+ Reason :: term().
+
+await_terminate(Pid, Name, OtherPids) ->
+ await(Pid, Name, ?ANNOUNCEMENT_TERMINATE, ?TERMINATE_SLOGAN, OtherPids).
+
+
+%% ============================================================================
+
+-spec await_termination(Pid) -> ok | {error, Reason} when
+ Pid :: pid(),
+ Reason :: term().
+
+await_termination(Pid) when is_pid(Pid) ->
+ await_termination(Pid, any).
+
+-spec await_termination(Pid, ExpReason) -> ok | {error, Reason} when
+ Pid :: pid(),
+ ExpReason :: term(),
+ Reason :: term().
+
+await_termination(Pid, ExpReason) ->
+ receive
+ {'DOWN', _, process, Pid, _} when (ExpReason =:= any) ->
+ ok;
+ {'DOWN', _, process, Pid, Reason} when (ExpReason =:= Reason) ->
+ ok;
+ {'DOWN', _, process, Pid, Reason} ->
+ {error, {unexpected_exit, ExpReason, Reason}}
+ end.
+
+
+%% ============================================================================
+
+%% We expect a message (announcement) from Pid, but we also watch for DOWN from
+%% both Pid and OtherPids, in which case the test has failed!
+
+-spec await(ExpPid, Name, Announcement, Slogan, OtherPids) ->
+ ok | {ok, Extra} | {error, Reason} when
+ ExpPid :: any | pid(),
+ Name :: atom(),
+ Announcement :: atom(),
+ Slogan :: atom(),
+ OtherPids :: [{pid(), atom()}],
+ Extra :: term(),
+ Reason :: term().
+
+await(ExpPid, Name, Announcement, Slogan, OtherPids)
+ when (is_pid(ExpPid) orelse (ExpPid =:= any)) andalso
+ is_atom(Name) andalso
+ is_atom(Announcement) andalso
+ is_atom(Slogan) andalso
+ is_list(OtherPids) ->
+ receive
+ {Announcement, Pid, Slogan, ?EXTRA_NOTHING} when (ExpPid =:= any) ->
+ {ok, Pid};
+ {Announcement, Pid, Slogan, Extra} when (ExpPid =:= any) ->
+ {ok, {Pid, Extra}};
+ {Announcement, Pid, Slogan, ?EXTRA_NOTHING} when (Pid =:= ExpPid) ->
+ ok;
+ {Announcement, Pid, Slogan, Extra} when (Pid =:= ExpPid) ->
+ {ok, Extra};
+ {'DOWN', _, process, Pid, {skip, SkipReason}} when (Pid =:= ExpPid) ->
+ iprint("Unexpected SKIP from ~w (~p): "
+ "~n ~p", [Name, Pid, SkipReason]),
+ ?LIB:skip({Name, SkipReason});
+ {'DOWN', _, process, Pid, Reason} when (Pid =:= ExpPid) ->
+ eprint("Unexpected DOWN from ~w (~p): "
+ "~n ~p", [Name, Pid, Reason]),
+ {error, {unexpected_exit, Name}};
+ {'DOWN', _, process, OtherPid, Reason} ->
+ case check_down(OtherPid, Reason, OtherPids) of
+ ok ->
+ iprint("DOWN from unknown process ~p: "
+ "~n ~p", [OtherPid, Reason]),
+ await(ExpPid, Name, Announcement, Slogan, OtherPids);
+ {error, _} = ERROR ->
+ ERROR
+ end
+ after infinity -> % For easy debugging, just change to some valid time (5000)
+ iprint("await -> timeout for msg from ~p (~w): "
+ "~n Announcement: ~p"
+ "~n Slogan: ~p"
+ "~nwhen"
+ "~n Messages: ~p",
+ [ExpPid, Name, Announcement, Slogan, pi(messages)]),
+ await(ExpPid, Name, Announcement, Slogan, OtherPids)
+ end.
+
+pi(Item) ->
+ pi(self(), Item).
+
+pi(Pid, Item) ->
+ {Item, Info} = process_info(Pid, Item),
+ Info.
+
+check_down(Pid, DownReason, Pids) ->
+ case lists:keymember(Pid, 1, Pids) of
+ {value, {_, Name}} ->
+ eprint("Unexpected DOWN from ~w (~p): "
+ "~n ~p", [Name, Pid, DownReason]),
+ {error, {unexpected_exit, Name}};
+ false ->
+ ok
+ end.
+
+
+%% ============================================================================
+
+f(F, A) ->
+ lists:flatten(io_lib:format(F, A)).
+
+
+iprint(F, A) ->
+ print("", F, A).
+
+eprint(F, A) ->
+ print("<ERROR> ", F, A).
+
+print(Prefix, F, A) ->
+ %% The two prints is to get the output both in the shell (for when
+ %% "personal" testing is going on) and in the logs.
+ IDStr =
+ case get(sname) of
+ undefined ->
+ %% This means its not an evaluator,
+ %% or a named process. Instead its
+ %% most likely the test case itself,
+ %% so skip the name and the pid.
+ "";
+ SName ->
+ f("[~s][~p]", [SName, self()])
+ end,
+ ?LOGGER:format("[~s]~s ~s" ++ F,
+ [?LIB:formated_timestamp(), IDStr, Prefix | A]).
diff --git a/erts/emulator/test/socket_test_evaluator.hrl b/erts/emulator/test/socket_test_evaluator.hrl
new file mode 100644
index 0000000000..5be49dc022
--- /dev/null
+++ b/erts/emulator/test/socket_test_evaluator.hrl
@@ -0,0 +1,68 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2018. All 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(socket_test_evaluator).
+-define(socket_test_evaluator, true).
+
+-record(ev, {name :: string(),
+ pid :: pid(),
+ mref :: reference()}).
+
+-define(SEV, socket_test_evaluator).
+
+-define(SEV_START(N, S, IS), ?SEV:start(N, S, IS)).
+-define(SEV_AWAIT_FINISH(Evs), ?SEV:await_finish(Evs)).
+
+-define(SEV_ANNOUNCE_START(To), ?SEV:announce_start(To)).
+-define(SEV_ANNOUNCE_START(To, Ex), ?SEV:announce_start(To, Ex)).
+-define(SEV_ANNOUNCE_CONTINUE(To, S), ?SEV:announce_continue(To, S)).
+-define(SEV_ANNOUNCE_CONTINUE(To, S, Ex), ?SEV:announce_continue(To, S, Ex)).
+-define(SEV_ANNOUNCE_READY(To, S), ?SEV:announce_ready(To, S)).
+-define(SEV_ANNOUNCE_READY(To, S, Ex), ?SEV:announce_ready(To, S, Ex)).
+-define(SEV_ANNOUNCE_TERMINATE(To), ?SEV:announce_terminate(To)).
+
+-define(SEV_AWAIT_START(), ?SEV:await_start()).
+-define(SEV_AWAIT_START(P), ?SEV:await_start(P)).
+-define(SEV_AWAIT_CONTINUE(F, N, S), ?SEV:await_continue(F, N, S)).
+-define(SEV_AWAIT_CONTINUE(F, N, S, Ps), ?SEV:await_continue(F, N, S, Ps)).
+-define(SEV_AWAIT_READY(F, N, S), ?SEV:await_ready(F, N, S)).
+-define(SEV_AWAIT_READY(F, N, S, Ps), ?SEV:await_ready(F, N, S, Ps)).
+-define(SEV_AWAIT_TERMINATE(F, N), ?SEV:await_terminate(F, N)).
+-define(SEV_AWAIT_TERMINATE(F, N, Ps), ?SEV:await_terminate(F, N, Ps)).
+-define(SEV_AWAIT_TERMINATION(P), ?SEV:await_termination(P)).
+-define(SEV_AWAIT_TERMINATION(P, R), ?SEV:await_termination(P, R)).
+
+-define(SEV_IPRINT(F, A), ?SEV:iprint(F, A)).
+-define(SEV_IPRINT(F), ?SEV_IPRINT(F, [])).
+-define(SEV_EPRINT(F, A), ?SEV:eprint(F, A)).
+-define(SEV_EPRINT(F), ?SEV_EPRINT(F, [])).
+
+-define(SEV_SLEEP(T), #{desc => "sleep",
+ cmd => fun(_) ->
+ ?SLEEP(T),
+ ok
+ end}).
+-define(SEV_FINISH_NORMAL, #{desc => "finish",
+ cmd => fun(_) ->
+ {ok, normal}
+ end}).
+
+-endif. % -ifdef(socket_test_evaluator).
+
diff --git a/erts/emulator/test/socket_test_lib.erl b/erts/emulator/test/socket_test_lib.erl
new file mode 100644
index 0000000000..4e65c4f3c0
--- /dev/null
+++ b/erts/emulator/test/socket_test_lib.erl
@@ -0,0 +1,98 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2018. All 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(socket_test_lib).
+
+-export([
+ pi/1, pi/2, pi/3,
+
+ %% Time stuff
+ timestamp/0,
+ tdiff/2,
+ formated_timestamp/0,
+ format_timestamp/1,
+
+ %% String and format
+ f/2,
+
+ %% Skipping
+ not_yet_implemented/0,
+ skip/1
+ ]).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+pi(Item) when is_atom(Item) ->
+ pi(self(), Item).
+
+pi(Pid, Item) when is_pid(Pid) andalso is_atom(Item) ->
+ {Item, Info} = process_info(Pid, Item),
+ Info;
+pi(Node, Pid) when is_pid(Pid) ->
+ rpc:call(Node, erlang, process_info, [Pid]).
+
+pi(Node, Pid, Item) when is_pid(Pid) andalso is_atom(Item) ->
+ rpc:call(Node, erlang, process_info, [Pid, Item]).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+timestamp() ->
+ os:timestamp().
+
+
+tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) ->
+ T1 = A1*1000000000+B1*1000+(C1 div 1000),
+ T2 = A2*1000000000+B2*1000+(C2 div 1000),
+ T2 - T1.
+
+
+formated_timestamp() ->
+ format_timestamp(os:timestamp()).
+
+format_timestamp({_N1, _N2, _N3} = TS) ->
+ {_Date, Time} = calendar:now_to_local_time(TS),
+ %% {YYYY,MM,DD} = Date,
+ {Hour,Min,Sec} = Time,
+ %% FormatTS =
+ %% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w.~w",
+ %% [YYYY, MM, DD, Hour, Min, Sec, N3]),
+ FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w", [Hour, Min, Sec]),
+ lists:flatten(FormatTS).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+f(F, A) ->
+ lists:flatten(io_lib:format(F, A)).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+not_yet_implemented() ->
+ skip("not yet implemented").
+
+skip(Reason) ->
+ throw({skip, Reason}).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/erts/emulator/test/socket_test_logger.erl b/erts/emulator/test/socket_test_logger.erl
new file mode 100644
index 0000000000..26610e9ef3
--- /dev/null
+++ b/erts/emulator/test/socket_test_logger.erl
@@ -0,0 +1,118 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2019. All 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(socket_test_logger).
+
+-export([
+ start/0, start/1,
+ stop/0,
+ format/2
+ ]).
+
+
+-define(QUIET, true).
+-define(LIB, socket_test_lib).
+-define(LOGGER, ?MODULE).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+start() ->
+ start(?QUIET).
+
+start(Quiet) ->
+ case global:whereis_name(?LOGGER) of
+ Pid when is_pid(Pid) ->
+ ok;
+ undefined ->
+ Self = self(),
+ Pid = spawn_link(fun() -> init(Self, Quiet) end),
+ yes = global:register_name(?LOGGER, Pid),
+ ok
+ end.
+
+
+stop() ->
+ case global:whereis_name(?LOGGER) of
+ undefined ->
+ ok;
+ Pid when is_pid(Pid) ->
+ global:unregister_name(?LOGGER),
+ Pid ! {?LOGGER, '$logger', stop},
+ ok
+ end.
+
+
+format(F, []) ->
+ do_format(F);
+format(F, A) ->
+ do_format(?LIB:f(F, A)).
+
+do_format(Msg) ->
+ case global:whereis_name(?LOGGER) of
+ undefined ->
+ ok;
+ Pid when is_pid(Pid) ->
+ Pid ! {?MODULE, '$logger', {msg, Msg}},
+ ok
+ end.
+
+init(Parent, Quiet) ->
+ put(sname, "logger"),
+ print("[~s][logger] starting~n", [?LIB:formated_timestamp()]),
+ loop(#{parent => Parent, quiet => Quiet}).
+
+loop(#{parent := Parent,
+ quiet := Quiet} = State) ->
+ receive
+ {'EXIT', Parent, _} ->
+ print("[~s][logger] parent exit~n", [?LIB:formated_timestamp()]),
+ exit(normal);
+
+ {?MODULE, '$logger', stop} ->
+ print("[~s][logger] stopping~n", [?LIB:formated_timestamp()]),
+ exit(normal);
+
+ {?MODULE, '$logger', {msg, Msg}} ->
+ print_str(Quiet, Msg),
+ loop(State)
+ end.
+
+
+print(F, A) ->
+ print_str(false, ?LIB:f(F, A)).
+
+print_str(Quiet, Str) ->
+ try
+ begin
+ if (Quiet =/= true) -> io:format(user, Str ++ "~n", []);
+ true -> ok
+ end,
+ io:format(Str, [])
+ end
+ catch
+ _:_:_ ->
+ io:format(user,
+ "~nFailed Format message:"
+ "~n~p~n", [Str]),
+ io:format("~nFailed Format message:"
+ "~n~p~n", [Str])
+ end.
+
diff --git a/lib/otp_mibs/src/otp_mibs.appup.src b/erts/emulator/test/socket_test_ttest.hrl
index 9437ae2222..1a004a9a7a 100644
--- a/lib/otp_mibs/src/otp_mibs.appup.src
+++ b/erts/emulator/test/socket_test_ttest.hrl
@@ -1,7 +1,7 @@
-%% -*- erlang -*-
+%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2018. All 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,17 @@
%% limitations under the License.
%%
%% %CopyrightEnd%
-{"%VSN%",
- [{<<".*">>,[{restart_application, otp_mibs}]}],
- [{<<".*">>,[{restart_application, otp_mibs}]}]
-}.
+%%
+
+-ifndef(socket_test_ttest).
+-define(socket_test_ttest, true).
+
+-define(TTEST_TAG, 42).
+-define(TTEST_TYPE_REQUEST, 101).
+-define(TTEST_TYPE_REPLY, 102).
+
+-define(SECS(I), timer:seconds(I)).
+
+-define(SLEEP(T), receive after T -> ok end).
+
+-endif. % -ifdef(socket_test_ttest).
diff --git a/erts/emulator/test/socket_test_ttest_client.hrl b/erts/emulator/test/socket_test_ttest_client.hrl
new file mode 100644
index 0000000000..84e736cc34
--- /dev/null
+++ b/erts/emulator/test/socket_test_ttest_client.hrl
@@ -0,0 +1,141 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2018. All 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(socket_test_ttest_client).
+-define(socket_test_ttest_client, true).
+
+-define(MSG_ID_DEFAULT, 2).
+-define(RUNTIME_DEFAULT, ?SECS(10)).
+-define(MAX_ID, 16#FFFFFFFF).
+
+-define(MSG_DATA1, <<"This is test data 0123456789 0123456789 0123456789">>).
+-define(MSG_DATA2, <<"This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789">>).
+-define(MSG_DATA3, <<"This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789"
+ "This is test data 0123456789 0123456789 0123456789">>).
+
+
+-endif. % -ifdef(socket_test_ttest_client).
diff --git a/erts/emulator/test/socket_test_ttest_lib.erl b/erts/emulator/test/socket_test_ttest_lib.erl
new file mode 100644
index 0000000000..7fc13df46a
--- /dev/null
+++ b/erts/emulator/test/socket_test_ttest_lib.erl
@@ -0,0 +1,127 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2018. All 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(socket_test_ttest_lib).
+
+-compile({no_auto_import, [error/2]}).
+
+-export([
+ t/0, tdiff/2,
+ formated_timestamp/0, format_timestamp/1,
+ format_time/1,
+
+ formated_process_stats/1, formated_process_stats/2,
+
+ format/2,
+ error/1, error/2,
+ info/1, info/2
+ ]).
+
+%% ==========================================================================
+
+t() ->
+ os:timestamp().
+
+tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) ->
+ T1 = A1*1000000000+B1*1000+(C1 div 1000),
+ T2 = A2*1000000000+B2*1000+(C2 div 1000),
+ T2 - T1.
+
+formated_timestamp() ->
+ format_timestamp(os:timestamp()).
+
+format_timestamp({_N1, _N2, N3} = TS) ->
+ {_Date, Time} = calendar:now_to_local_time(TS),
+ {Hour,Min,Sec} = Time,
+ FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w.4~w",
+ [Hour, Min, Sec, round(N3/1000)]),
+ lists:flatten(FormatTS).
+
+%% Time is always in number os ms (milli seconds)
+%% At some point, we should convert this to a more readable format...
+format_time(T) when (T < 1000) ->
+ format("~w ms", [T]);
+format_time(T) ->
+ format("~w sec (~w ms)", [T div 1000, T]).
+
+
+formated_process_stats(Pid) ->
+ formated_process_stats("", Pid).
+
+formated_process_stats(Prefix, Pid) when is_list(Prefix) andalso is_pid(Pid) ->
+ try
+ begin
+ TotHeapSz = pi(Pid, total_heap_size),
+ HeapSz = pi(Pid, heap_size),
+ StackSz = pi(Pid, stack_size),
+ Reds = pi(Pid, reductions),
+ GCInfo = pi(Pid, garbage_collection),
+ MinBinVHeapSz = proplists:get_value(min_bin_vheap_size, GCInfo),
+ MinHeapSz = proplists:get_value(min_heap_size, GCInfo),
+ MinGCS = proplists:get_value(minor_gcs, GCInfo),
+ format("~n ~sTotal Heap Size: ~p"
+ "~n ~sHeap Size: ~p"
+ "~n ~sStack Size: ~p"
+ "~n ~sReductions: ~p"
+ "~n ~s[GC] Min Bin VHeap Size: ~p"
+ "~n ~s[GC] Min Heap Size: ~p"
+ "~n ~s[GC] Minor GCS: ~p",
+ [Prefix, TotHeapSz,
+ Prefix, HeapSz,
+ Prefix, StackSz,
+ Prefix, Reds,
+ Prefix, MinBinVHeapSz,
+ Prefix, MinHeapSz,
+ Prefix, MinGCS])
+ end
+ catch
+ _:_:_ ->
+ ""
+ end.
+
+
+pi(Pid, Item) ->
+ {Item, Info} = process_info(Pid, Item),
+ Info.
+
+
+
+%% ==========================================================================
+
+format(F, A) ->
+ lists:flatten(io_lib:format(F, A)).
+
+error(F) ->
+ error(F, []).
+
+error(F, A) ->
+ print(get(sname), "<ERROR> " ++ F, A).
+
+info(F) ->
+ info(F, []).
+
+info(F, A) ->
+ print(get(sname), "<INFO> " ++ F, A).
+
+print(undefined, F, A) ->
+ print("- ", F, A);
+print(Prefix, F, A) ->
+ io:format("[~s, ~s] " ++ F ++ "~n", [formated_timestamp(), Prefix |A]).
+
diff --git a/erts/emulator/test/socket_test_ttest_tcp_client.erl b/erts/emulator/test/socket_test_ttest_tcp_client.erl
new file mode 100644
index 0000000000..5efa3fe491
--- /dev/null
+++ b/erts/emulator/test/socket_test_ttest_tcp_client.erl
@@ -0,0 +1,678 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2019. All 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 is the "simple" client using gen_tcp. The client is supposed to be
+%% as simple as possible in order to incur as little overhead as possible.
+%%
+%% There are three ways to run the client: active, passive or active-once.
+%%
+%% The client is the entity that controls the test, timing and counting.
+%%
+%% ==========================================================================
+%%
+%% Before the actual test starts, the client performs a "warmup".
+%% The warmup has two functions. First, to ensure that everything is "loaded"
+%% and, second, to calculate an approximate roundtrip time, in order to
+%% "know" how many iterations we should make (to run for the expected time).
+%% This is not intended to be exact, but just to ensure that all tests take
+%% approx the same time to run.
+%%
+%% ==========================================================================
+
+-module(socket_test_ttest_tcp_client).
+
+-export([
+ %% These are for the test suite
+ start_monitor/6, start_monitor/7, start_monitor/9,
+
+ %% These are for starting in a shell when run "manually"
+ start/4, start/5, start/7, start/8,
+ stop/1
+ ]).
+
+%% Internal exports
+-export([
+ do_start/10
+ ]).
+
+-include_lib("kernel/include/inet.hrl").
+-include("socket_test_ttest.hrl").
+-include("socket_test_ttest_client.hrl").
+
+-define(RECV_TIMEOUT, 10000).
+-define(MAX_OUTSTANDING_DEFAULT_1, 100).
+-define(MAX_OUTSTANDING_DEFAULT_2, 10).
+-define(MAX_OUTSTANDING_DEFAULT_3, 3).
+
+-define(LIB, socket_test_ttest_lib).
+-define(I(F), ?LIB:info(F)).
+-define(I(F,A), ?LIB:info(F, A)).
+-define(E(F,A), ?LIB:error(F, A)).
+-define(F(F,A), ?LIB:format(F, A)).
+-define(FORMAT_TIME(T), ?LIB:format_time(T)).
+-define(T(), ?LIB:t()).
+-define(TDIFF(T1,T2), ?LIB:tdiff(T1, T2)).
+
+-type active() :: once | boolean().
+-type msg_id() :: 1..3.
+-type max_outstanding() :: pos_integer().
+-type runtime() :: pos_integer().
+
+
+%% ==========================================================================
+
+start_monitor(Node, Notify, Transport, Active, Addr, Port) ->
+ start_monitor(Node, Notify, Transport, Active, Addr, Port, ?MSG_ID_DEFAULT).
+
+start_monitor(Node, Notify, Transport, Active, Addr, Port, 1 = MsgID) ->
+ start_monitor(Node, Notify, Transport, Active, Addr, Port, MsgID,
+ ?MAX_OUTSTANDING_DEFAULT_1, ?RUNTIME_DEFAULT);
+start_monitor(Node, Notify, Transport, Active, Addr, Port, 2 = MsgID) ->
+ start_monitor(Node, Notify, Transport, Active, Addr, Port, MsgID,
+ ?MAX_OUTSTANDING_DEFAULT_2, ?RUNTIME_DEFAULT);
+start_monitor(Node, Notify, Transport, Active, Addr, Port, 3 = MsgID) ->
+ start_monitor(Node, Notify, Transport, Active, Addr, Port, MsgID,
+ ?MAX_OUTSTANDING_DEFAULT_3, ?RUNTIME_DEFAULT).
+
+start_monitor(Node, Notify, Transport, Active, Addr, Port,
+ MsgID, MaxOutstanding, RunTime)
+ when (Node =/= node()) ->
+ Args = [false,
+ self(), Notify,
+ Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime],
+ case rpc:call(Node, ?MODULE, do_start, Args) of
+ {badrpc, _} = Reason ->
+ {error, Reason};
+ {ok, Pid} when is_pid(Pid) ->
+ MRef = erlang:monitor(process, Pid),
+ {ok, {Pid, MRef}};
+ {error, _} = ERROR ->
+ ERROR
+ end;
+start_monitor(_, Notify, Transport, Active, Addr, Port,
+ MsgID, MaxOutstanding, RunTime) ->
+ case do_start(false,
+ self(), Notify,
+ Transport, Active, Addr, Port,
+ MsgID, MaxOutstanding, RunTime) of
+ {ok, Pid} ->
+ MRef = erlang:monitor(process, Pid),
+ {ok, {Pid, MRef}};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+start(Transport, Active, Addr, Port) ->
+ start(Transport, Active, Addr, Port, ?MSG_ID_DEFAULT).
+
+start(Transport, Active, Addr, Port, 1 = MsgID) ->
+ start(false,
+ Transport, Active, Addr, Port, MsgID,
+ ?MAX_OUTSTANDING_DEFAULT_1, ?RUNTIME_DEFAULT);
+start(Transport, Active, Addr, Port, 2 = MsgID) ->
+ start(false,
+ Transport, Active, Addr, Port, MsgID,
+ ?MAX_OUTSTANDING_DEFAULT_2, ?RUNTIME_DEFAULT);
+start(Transport, Active, Addr, Port, 3 = MsgID) ->
+ start(false,
+ Transport, Active, Addr, Port, MsgID,
+ ?MAX_OUTSTANDING_DEFAULT_3, ?RUNTIME_DEFAULT).
+
+start(Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime) ->
+ start(false,
+ Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime).
+
+start(Quiet, Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime) ->
+ Notify = fun(R) -> present_results(R) end,
+ do_start(Quiet,
+ self(), Notify,
+ Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime).
+
+
+-spec do_start(Quiet,
+ Parent,
+ Notify,
+ Transport,
+ Active,
+ Addr,
+ Port,
+ MsgID,
+ MaxOutstanding,
+ RunTime) -> {ok, Pid} | {error, Reason} when
+ Quiet :: pid(),
+ Parent :: pid(),
+ Notify :: function(),
+ Transport :: atom() | tuple(),
+ Active :: active(),
+ Addr :: inet:ip_address(),
+ Port :: inet:port_number(),
+ MsgID :: msg_id(),
+ MaxOutstanding :: max_outstanding(),
+ RunTime :: runtime(),
+ Pid :: pid(),
+ Reason :: term().
+
+do_start(Quiet,
+ Parent, Notify,
+ Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime)
+ when is_boolean(Quiet) andalso
+ is_pid(Parent) andalso
+ is_function(Notify) andalso
+ (is_atom(Transport) orelse is_tuple(Transport)) andalso
+ (is_boolean(Active) orelse (Active =:= once)) andalso
+ is_tuple(Addr) andalso
+ (is_integer(Port) andalso (Port > 0)) andalso
+ (is_integer(MsgID) andalso (MsgID >= 1) andalso (MsgID =< 3)) andalso
+ (is_integer(MaxOutstanding) andalso (MaxOutstanding > 0)) andalso
+ (is_integer(RunTime) andalso (RunTime > 0)) ->
+ Starter = self(),
+ Init = fun() -> put(sname, "client"),
+ init(Quiet,
+ Starter,
+ Parent,
+ Notify,
+ Transport, Active, Addr, Port,
+ MsgID, MaxOutstanding, RunTime)
+ end,
+ {Pid, MRef} = spawn_monitor(Init),
+ receive
+ {'DOWN', MRef, process, Pid, Reason} ->
+ {error, Reason};
+ {?MODULE, Pid, ok} ->
+ erlang:demonitor(MRef),
+ {ok, Pid};
+ {?MODULE, Pid, {error, _} = ERROR} ->
+ erlang:demonitor(MRef, [flush]),
+ ERROR
+ end.
+
+
+%% We should not normally stop this (it terminates when its done).
+stop(Pid) when is_pid(Pid) ->
+ req(Pid, stop).
+
+
+%% ==========================================================================
+
+init(Quiet,
+ Starter,
+ Parent, Notify,
+ Transport, Active, Addr, Port,
+ MsgID, MaxOutstanding, RunTime) ->
+ if
+ not Quiet ->
+ ?I("init with"
+ "~n Transport: ~p"
+ "~n Active: ~p"
+ "~n Addr: ~s"
+ "~n Port: ~p"
+ "~n Msg ID: ~p (=> 16 + ~w bytes)"
+ "~n Max Outstanding: ~p"
+ "~n (Suggested) Run Time: ~p ms",
+ [Transport, Active, inet:ntoa(Addr), Port,
+ MsgID, size(which_msg_data(MsgID)), MaxOutstanding, RunTime]);
+ true ->
+ ok
+ end,
+ {Mod, Connect} = process_transport(Transport),
+ case Connect(Addr, Port) of
+ {ok, Sock} ->
+ if not Quiet -> ?I("connected");
+ true -> ok
+ end,
+ Starter ! {?MODULE, self(), ok},
+ initial_activation(Mod, Sock, Active),
+ Results = loop(#{quiet => Quiet,
+ slogan => run,
+ runtime => RunTime,
+ start => ?T(),
+ parent => Parent,
+ mod => Mod,
+ sock => Sock,
+ active => Active,
+ msg_data => which_msg_data(MsgID),
+ outstanding => 0,
+ max_outstanding => MaxOutstanding,
+ sid => 1,
+ rid => 1,
+ scnt => 0,
+ rcnt => 0,
+ bcnt => 0,
+ num => undefined,
+ acc => <<>>}),
+ Notify(Results),
+ (catch Mod:close(Sock)),
+ exit(normal);
+ {error, Reason} ->
+ ?E("connect failed: ~p", [Reason]),
+ exit({connect, Reason})
+ end.
+
+process_transport(Mod) when is_atom(Mod) ->
+ {Mod, fun(A, P) -> Mod:connect(A, P) end};
+process_transport({Mod, Opts}) ->
+ {Mod, fun(A, P) -> Mod:connect(A, P, Opts) end}.
+
+
+which_msg_data(1) -> ?MSG_DATA1;
+which_msg_data(2) -> ?MSG_DATA2;
+which_msg_data(3) -> ?MSG_DATA3.
+
+
+present_results(#{status := ok,
+ runtime := RunTime,
+ bcnt := ByteCnt,
+ cnt := NumIterations}) ->
+ ?I("Results: "
+ "~n Run Time: ~s"
+ "~n ByteCnt: ~s"
+ "~n NumIterations: ~s",
+ [?FORMAT_TIME(RunTime),
+ if ((ByteCnt =:= 0) orelse (RunTime =:= 0)) ->
+ ?F("~w, ~w", [ByteCnt, RunTime]);
+ true ->
+ ?F("~p => ~p byte / ms", [ByteCnt, ByteCnt div RunTime])
+ end,
+ if (RunTime =:= 0) ->
+ "-";
+ true ->
+ ?F("~p => ~p iterations / ms",
+ [NumIterations, NumIterations div RunTime])
+ end]),
+ ok;
+present_results(#{status := Failure,
+ runtime := RunTime,
+ sid := SID,
+ rid := RID,
+ scnt := SCnt,
+ rcnt := RCnt,
+ bcnt := BCnt,
+ num := Num}) ->
+ ?I("Time Test failed: "
+ "~n ~p"
+ "~n"
+ "~nwhen"
+ "~n"
+ "~n Run Time: ~s"
+ "~n Send ID: ~p"
+ "~n Recv ID: ~p"
+ "~n Send Count: ~p"
+ "~n Recv Count: ~p"
+ "~n Byte Count: ~p"
+ "~n Num Iterations: ~p",
+ [Failure,
+ ?FORMAT_TIME(RunTime),
+ SID, RID, SCnt, RCnt, BCnt, Num]).
+
+
+
+loop(#{runtime := RunTime} = State) ->
+ erlang:start_timer(RunTime, self(), stop),
+ try do_loop(State)
+ catch
+ throw:Results ->
+ Results
+ end.
+
+do_loop(State) ->
+ do_loop( handle_message( msg_exchange(State) ) ).
+
+msg_exchange(#{rcnt := Num, num := Num} = State) ->
+ finish(ok, State);
+msg_exchange(#{scnt := Num, num := Num} = State) ->
+ %% We are done sending more requests - now we will just await
+ %% the replies for the (still) outstanding replies.
+ msg_exchange( recv_reply(State) );
+msg_exchange(#{outstanding := Outstanding,
+ max_outstanding := MaxOutstanding} = State)
+ when (Outstanding < MaxOutstanding) ->
+ msg_exchange( send_request(State) );
+msg_exchange(State) ->
+ send_request( recv_reply(State) ).
+
+
+finish(ok,
+ #{start := Start, bcnt := BCnt, num := Num}) ->
+ Stop = ?T(),
+ throw(#{status => ok,
+ runtime => ?TDIFF(Start, Stop),
+ bcnt => BCnt,
+ cnt => Num});
+finish(Reason,
+ #{start := Start,
+ sid := SID, rid := RID,
+ scnt := SCnt, rcnt := RCnt, bcnt := BCnt,
+ num := Num}) ->
+ Stop = ?T(),
+ throw(#{status => Reason,
+ runtime => ?TDIFF(Start, Stop),
+ sid => SID,
+ rid => RID,
+ scnt => SCnt,
+ rcnt => RCnt,
+ bcnt => BCnt,
+ num => Num}).
+
+send_request(#{mod := Mod,
+ sock := Sock,
+ sid := ID,
+ scnt := Cnt,
+ outstanding := Outstanding,
+ max_outstanding := MaxOutstanding,
+ msg_data := Data} = State)
+ when (MaxOutstanding > Outstanding) ->
+ SZ = size(Data),
+ Req = <<?TTEST_TAG:32,
+ ?TTEST_TYPE_REQUEST:32,
+ ID:32,
+ SZ:32,
+ Data/binary>>,
+ case Mod:send(Sock, Req) of
+ ok ->
+ State#{sid => next_id(ID),
+ scnt => Cnt + 1,
+ outstanding => Outstanding + 1};
+ {error, Reason} ->
+ ?E("Failed sending request: ~p", [Reason]),
+ exit({send, Reason})
+ end;
+send_request(State) ->
+ State.
+
+
+
+recv_reply(#{mod := Mod,
+ sock := Sock,
+ rid := ID,
+ active := false,
+ bcnt := BCnt,
+ rcnt := Cnt,
+ outstanding := Outstanding} = State) ->
+ case recv_reply_message1(Mod, Sock, ID) of
+ {ok, MsgSz} ->
+ State#{rid => next_id(ID),
+ bcnt => BCnt + MsgSz,
+ rcnt => Cnt + 1,
+ outstanding => Outstanding - 1};
+
+ {error, timeout} ->
+ ?I("receive timeout"),
+ State;
+
+ {error, Reason} ->
+ finish(Reason, State)
+ end;
+recv_reply(#{mod := Mod,
+ sock := Sock,
+ rid := ID,
+ active := Active,
+ bcnt := BCnt,
+ scnt := SCnt,
+ rcnt := RCnt,
+ outstanding := Outstanding,
+ acc := Acc} = State) ->
+ case recv_reply_message2(Mod, Sock, ID, Acc) of
+ {ok, {MsgSz, NewAcc}} when is_integer(MsgSz) andalso is_binary(NewAcc) ->
+ maybe_activate(Mod, Sock, Active),
+ State#{rid => next_id(ID),
+ bcnt => BCnt + MsgSz,
+ rcnt => RCnt + 1,
+ outstanding => Outstanding - 1,
+ acc => NewAcc};
+
+ ok ->
+ State;
+
+ {error, stop} ->
+ ?I("receive [~w] -> stop", [Active]),
+ %% This will have the effect that no more requests are sent...
+ State#{num => SCnt, stop_started => ?T()};
+
+ {error, timeout} ->
+ ?I("receive[~w] -> timeout", [Active]),
+ State;
+
+ {error, Reason} ->
+ finish(Reason, State)
+ end.
+
+
+%% This function reads exactly one (reply) message. No more no less.
+recv_reply_message1(Mod, Sock, ID) ->
+ case Mod:recv(Sock, 4*4, ?RECV_TIMEOUT) of
+ {ok, <<?TTEST_TAG:32,
+ ?TTEST_TYPE_REPLY:32,
+ ID:32,
+ SZ:32>> = Hdr} ->
+ %% Receive the ping-pong reply boby
+ case Mod:recv(Sock, SZ, ?RECV_TIMEOUT) of
+ {ok, Data} when (size(Data) =:= SZ) ->
+ {ok, size(Hdr) + size(Data)};
+ {error, Reason2} ->
+ ?E("Failed reading body: "
+ "~n ~p: ~p", [Reason2]),
+ {error, {recv_body, Reason2}}
+ end;
+
+ {ok, <<BadTag:32,
+ BadType:32,
+ BadID:32,
+ BadSZ:32>>} ->
+ {error, {invalid_hdr,
+ {?TTEST_TAG, BadTag},
+ {?TTEST_TYPE_REPLY, BadType},
+ {ID, BadID},
+ BadSZ}};
+ {ok, _InvHdr} ->
+ {error, invalid_hdr};
+
+ {error, Reason1} ->
+ ?E("Feiled reading header: "
+ "~n ~p", [Reason1]),
+ {error, {recv_hdr, Reason1}}
+ end.
+
+
+%% This function first attempts to process the data we have already
+%% accumulated. If that is not enough for a (complete) reply, it
+%% will attempt to receive more.
+recv_reply_message2(Mod, Sock, ID, Acc) ->
+ case process_acc_data(ID, Acc) of
+ ok ->
+ %% No or insufficient data, so get more
+ recv_reply_message3(Mod, Sock, ID, Acc);
+
+ {ok, _} = OK -> % We already had a reply accumulated - no need to read more
+ OK;
+
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+%% This function receives a "chunk" of data, then it tries to extract
+%% one (reply) message from the accumulated and new data (combined).
+recv_reply_message3(_Mod, Sock, ID, Acc) ->
+ receive
+ {timeout, _TRef, stop} ->
+ {error, stop};
+
+ {TagClosed, Sock} when (TagClosed =:= tcp_closed) orelse
+ (TagClosed =:= socket_closed) ->
+ {error, closed};
+
+ {TagErr, Sock, Reason} when (TagErr =:= tcp_error) orelse
+ (TagErr =:= socket_error) ->
+ {error, Reason};
+
+ {Tag, Sock, Msg} when (Tag =:= tcp) orelse
+ (Tag =:= socket) ->
+ process_acc_data(ID, <<Acc/binary, Msg/binary>>)
+
+ after ?RECV_TIMEOUT ->
+ ?I("timeout when"
+ "~n ID: ~p"
+ "~n size(Acc): ~p",
+ [ID, size(Acc)]),
+ %% {error, timeout}
+ recv_reply_message3(_Mod, Sock, ID, Acc)
+ end.
+
+
+process_acc_data(ID, <<?TTEST_TAG:32,
+ ?TTEST_TYPE_REPLY:32,
+ ID:32,
+ SZ:32,
+ Data/binary>>) when (SZ =< size(Data)) ->
+ <<_Body:SZ/binary, Rest/binary>> = Data,
+ {ok, {4*4+SZ, Rest}};
+process_acc_data(ID, <<BadTag:32,
+ BadType:32,
+ BadID:32,
+ BadSZ:32,
+ _Data/binary>>)
+ when ((BadTag =/= ?TTEST_TAG) orelse
+ (BadType =/= ?TTEST_TYPE_REPLY) orelse
+ (BadID =/= ID)) ->
+ {error, {invalid_hdr,
+ {?TTEST_TAG, BadTag},
+ {?TTEST_TYPE_REPLY, BadType},
+ {ID, BadID},
+ BadSZ}};
+%% Not enough for an entire (reply) message
+process_acc_data(_ID, _Data) ->
+ ok.
+
+
+handle_message(#{quiet := Quiet,
+ parent := Parent, sock := Sock, scnt := SCnt} = State) ->
+ receive
+ {timeout, _TRef, stop} ->
+ if not Quiet -> ?I("STOP");
+ true -> ok
+ end,
+ %% This will have the effect that no more requests are sent...
+ State#{num => SCnt, stop_started => ?T()};
+
+ {?MODULE, Ref, Parent, stop} ->
+ %% This *aborts* the test
+ reply(Parent, Ref, ok),
+ exit(normal);
+
+ %% Only when active
+ {TagClosed, Sock, Reason} when (TagClosed =:= tcp_closed) orelse
+ (TagClosed =:= socket_closed) ->
+ %% We should never get this (unless the server crashed)
+ exit({closed, Reason});
+
+ %% Only when active
+ {TagErr, Sock, Reason} when (TagErr =:= tcp_error) orelse
+ (TagErr =:= socket_error) ->
+ exit({error, Reason})
+
+ after 0 ->
+ State
+ end.
+
+
+initial_activation(_Mod, _Sock, false = _Active) ->
+ ok;
+initial_activation(Mod, Sock, Active) ->
+ Mod:active(Sock, Active).
+
+
+maybe_activate(Mod, Sock, once = Active) ->
+ Mod:active(Sock, Active);
+maybe_activate(_, _, _) ->
+ ok.
+
+
+%% ==========================================================================
+
+req(Pid, Req) ->
+ Ref = make_ref(),
+ Pid ! {?MODULE, Ref, Pid, Req},
+ receive
+ {'EXIT', Pid, Reason} ->
+ {error, {exit, Reason}};
+ {?MODULE, Ref, Reply} ->
+ Reply
+ end.
+
+reply(Pid, Ref, Reply) ->
+ Pid ! {?MODULE, Ref, Reply}.
+
+
+%% ==========================================================================
+
+next_id(ID) when (ID < ?MAX_ID) ->
+ ID + 1;
+next_id(_) ->
+ 1.
+
+
+%% ==========================================================================
+
+%% t() ->
+%% os:timestamp().
+
+%% tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) ->
+%% T1 = A1*1000000000+B1*1000+(C1 div 1000),
+%% T2 = A2*1000000000+B2*1000+(C2 div 1000),
+%% T2 - T1.
+
+%% formated_timestamp() ->
+%% format_timestamp(os:timestamp()).
+
+%% format_timestamp({_N1, _N2, N3} = TS) ->
+%% {_Date, Time} = calendar:now_to_local_time(TS),
+%% {Hour,Min,Sec} = Time,
+%% FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w.4~w",
+%% [Hour, Min, Sec, round(N3/1000)]),
+%% lists:flatten(FormatTS).
+
+%% %% Time is always in number os ms (milli seconds)
+%% format_time(T) ->
+%% f("~p", [T]).
+
+
+%% ==========================================================================
+
+%% f(F, A) ->
+%% lists:flatten(io_lib:format(F, A)).
+
+%% %% e(F) ->
+%% %% i("<ERROR> " ++ F).
+
+%% e(F, A) ->
+%% p(get(sname), "<ERROR> " ++ F, A).
+
+%% i(F) ->
+%% i(F, []).
+
+%% i(F, A) ->
+%% p(get(sname), "<INFO> " ++ F, A).
+
+%% p(undefined, F, A) ->
+%% p("- ", F, A);
+%% p(Prefix, F, A) ->
+%% io:format("[~s, ~s] " ++ F ++ "~n", [formated_timestamp(), Prefix |A]).
diff --git a/erts/emulator/test/socket_test_ttest_tcp_client_gen.erl b/erts/emulator/test/socket_test_ttest_tcp_client_gen.erl
new file mode 100644
index 0000000000..0ec2e908d7
--- /dev/null
+++ b/erts/emulator/test/socket_test_ttest_tcp_client_gen.erl
@@ -0,0 +1,49 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2019. All 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(socket_test_ttest_tcp_client_gen).
+
+-export([
+ start/3, start/4, start/6, start/7,
+ stop/1
+ ]).
+
+-define(TRANSPORT_MOD, socket_test_ttest_tcp_gen).
+
+start(Active, Addr, Port) ->
+ socket_test_ttest_tcp_client:start(?TRANSPORT_MOD, Active, Addr, Port).
+
+start(Active, Addr, Port, MsgID) ->
+ socket_test_ttest_tcp_client:start(?TRANSPORT_MOD, Active, Addr, Port, MsgID).
+
+start(Active, Addr, Port, MsgID, MaxOutstanding, RunTime) ->
+ socket_test_ttest_tcp_client:start(false,
+ ?TRANSPORT_MOD,
+ Active, Addr, Port,
+ MsgID, MaxOutstanding, RunTime).
+
+start(Quiet, Active, Addr, Port, MsgID, MaxOutstanding, RunTime) ->
+ socket_test_ttest_tcp_client:start(Quiet,
+ ?TRANSPORT_MOD,
+ Active, Addr, Port,
+ MsgID, MaxOutstanding, RunTime).
+
+stop(Pid) ->
+ socket_test_ttest_tcp_client:stop(Pid).
diff --git a/erts/emulator/test/socket_test_ttest_tcp_client_socket.erl b/erts/emulator/test/socket_test_ttest_tcp_client_socket.erl
new file mode 100644
index 0000000000..acf2556793
--- /dev/null
+++ b/erts/emulator/test/socket_test_ttest_tcp_client_socket.erl
@@ -0,0 +1,51 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2019. All 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(socket_test_ttest_tcp_client_socket).
+
+-export([
+ start/4, start/5, start/7, start/8,
+ stop/1
+ ]).
+
+-define(TRANSPORT_MOD, socket_test_ttest_tcp_socket).
+-define(MOD(M), {?TRANSPORT_MOD, #{method => Method}}).
+
+start(Method, Active, Addr, Port) ->
+ socket_test_ttest_tcp_client:start_monitor(?MOD(Method), Active, Addr, Port).
+
+start(Method, Active, Addr, Port, MsgID) ->
+ socket_test_ttest_tcp_client:start(?MOD(Method),
+ Active, Addr, Port, MsgID).
+
+start(Method, Active, Addr, Port, MsgID, MaxOutstanding, RunTime) ->
+ socket_test_ttest_tcp_client:start(false,
+ ?MOD(Method),
+ Active, Addr, Port,
+ MsgID, MaxOutstanding, RunTime).
+
+start(Quiet, Method, Active, Addr, Port, MsgID, MaxOutstanding, RunTime) ->
+ socket_test_ttest_tcp_client:start(Quiet,
+ ?MOD(Method),
+ Active, Addr, Port,
+ MsgID, MaxOutstanding, RunTime).
+
+stop(Pid) ->
+ socket_test_ttest_client:stop(Pid).
diff --git a/erts/emulator/test/socket_test_ttest_tcp_gen.erl b/erts/emulator/test/socket_test_ttest_tcp_gen.erl
new file mode 100644
index 0000000000..604408c489
--- /dev/null
+++ b/erts/emulator/test/socket_test_ttest_tcp_gen.erl
@@ -0,0 +1,138 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2018. All 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(socket_test_ttest_tcp_gen).
+
+-export([
+ accept/1, accept/2,
+ active/2,
+ close/1,
+ connect/2,
+ controlling_process/2,
+ listen/0, listen/1,
+ peername/1,
+ port/1,
+ recv/2, recv/3,
+ send/2,
+ shutdown/2,
+ sockname/1
+ ]).
+
+
+%% ==========================================================================
+
+%% getopt(Sock, Opt) when is_atom(Opt) ->
+%% case inet:getopts(Sock, [Opt]) of
+%% {ok, [{Opt, Value}]} ->
+%% {ok, Value};
+%% {error, _} = ERROR ->
+%% ERROR
+%% end.
+
+%% setopt(Sock, Opt, Value) when is_atom(Opt) ->
+%% inet:setopts(Sock, [{Opt, Value}]).
+
+
+%% ==========================================================================
+
+accept(Sock) ->
+ case gen_tcp:accept(Sock) of
+ {ok, NewSock} ->
+ {ok, NewSock};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+accept(Sock, Timeout) ->
+ case gen_tcp:accept(Sock, Timeout) of
+ {ok, NewSock} ->
+ {ok, NewSock};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+active(Sock, NewActive)
+ when (is_boolean(NewActive) orelse (NewActive =:= once)) ->
+ inet:setopts(Sock, [{active, NewActive}]).
+
+
+close(Sock) ->
+ gen_tcp:close(Sock).
+
+
+connect(Addr, Port) ->
+ Opts = [binary, {packet, raw}, {active, false}, {buffer, 32*1024}],
+ case gen_tcp:connect(Addr, Port, Opts) of
+ {ok, Sock} ->
+ {ok, Sock};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+controlling_process(Sock, NewPid) ->
+ gen_tcp:controlling_process(Sock, NewPid).
+
+
+%% Create a listen socket
+listen() ->
+ listen(0).
+
+listen(Port) when is_integer(Port) andalso (Port >= 0) ->
+ Opts = [binary, {ip, {0,0,0,0}}, {packet, raw}, {active, false},
+ {buffer, 32*1024}],
+ case gen_tcp:listen(Port, Opts) of
+ {ok, Sock} ->
+ {ok, Sock};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+peername(Sock) ->
+ inet:peername(Sock).
+
+
+port(Sock) ->
+ inet:port(Sock).
+
+
+recv(Sock, Length) ->
+ gen_tcp:recv(Sock, Length).
+recv(Sock, Length, Timeout) ->
+ gen_tcp:recv(Sock, Length, Timeout).
+
+
+send(Sock, Data) ->
+ gen_tcp:send(Sock, Data).
+
+
+shutdown(Sock, How) ->
+ gen_tcp:shutdown(Sock, How).
+
+
+sockname(Sock) ->
+ inet:sockname(Sock).
+
+
+%% ==========================================================================
+
+
+
diff --git a/erts/emulator/test/socket_test_ttest_tcp_server.erl b/erts/emulator/test/socket_test_ttest_tcp_server.erl
new file mode 100644
index 0000000000..e8d626e3d8
--- /dev/null
+++ b/erts/emulator/test/socket_test_ttest_tcp_server.erl
@@ -0,0 +1,642 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2018. All 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 is the "simple" server using gen_tcp. The server is supposed to be
+%% as simple as possible in order to incur as little overhead as possible.
+%%
+%% There are three ways to run the server: active, passive or active-once.
+%%
+%% The server does only two things; accept connnections and then reply
+%% to requests (actually the handler(s) does that). No timing or counting.
+%% That is all done by the clients.
+%%
+%% ==========================================================================
+
+-module(socket_test_ttest_tcp_server).
+
+-export([
+ %% This are for the test suite
+ start_monitor/3,
+
+ %% This are for starting in a shell when run "manually"
+ start/2,
+
+ stop/1
+ ]).
+
+%% Internal exports
+-export([
+ do_start/3
+ ]).
+
+-include_lib("kernel/include/inet.hrl").
+-include("socket_test_ttest.hrl").
+
+-define(ACC_TIMEOUT, 5000).
+-define(RECV_TIMEOUT, 5000).
+
+-define(LIB, socket_test_ttest_lib).
+-define(I(F), ?LIB:info(F)).
+-define(I(F,A), ?LIB:info(F, A)).
+-define(E(F,A), ?LIB:error(F, A)).
+-define(F(F,A), ?LIB:format(F, A)).
+-define(FORMAT_TIME(T), ?LIB:format_time(T)).
+-define(T(), ?LIB:t()).
+-define(TDIFF(T1,T2), ?LIB:tdiff(T1, T2)).
+
+
+%% ==========================================================================
+
+start_monitor(Node, Transport, Active) when (Node =/= node()) ->
+ case rpc:call(Node, ?MODULE, do_start, [self(), Transport, Active]) of
+ {badrpc, _} = Reason ->
+ {error, Reason};
+ {ok, {Pid, AddrPort}} ->
+ MRef = erlang:monitor(process, Pid),
+ {ok, {{Pid, MRef}, AddrPort}};
+ {error, _} = ERROR ->
+ ERROR
+ end;
+start_monitor(_, Transport, Active) ->
+ case do_start(self(), Transport, Active) of
+ {ok, {Pid, AddrPort}} ->
+ MRef = erlang:monitor(process, Pid),
+ {ok, {{Pid, MRef}, AddrPort}};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+
+start(Transport, Active) ->
+ do_start(self(), Transport, Active).
+
+
+do_start(Parent, Transport, Active)
+ when is_pid(Parent) andalso
+ (is_atom(Transport) orelse is_tuple(Transport)) andalso
+ (is_boolean(Active) orelse (Active =:= once)) ->
+ Starter = self(),
+ ServerInit = fun() -> put(sname, "server"),
+ server_init(Starter, Parent, Transport, Active)
+ end,
+ {Pid, MRef} = spawn_monitor(ServerInit),
+ receive
+ {'DOWN', MRef, process, Pid, Reason} ->
+ {error, Reason};
+ {?MODULE, Pid, {ok, AddrPort}} ->
+ erlang:demonitor(MRef),
+ {ok, {Pid, AddrPort}};
+ {?MODULE, Pid, {error, _} = ERROR} ->
+ erlang:demonitor(MRef, [flush]),
+ ERROR
+ end.
+
+
+stop(Pid) when is_pid(Pid) ->
+ req(Pid, stop).
+
+
+%% ==========================================================================
+
+server_init(Starter, Parent, Transport, Active) ->
+ ?I("init with"
+ "~n Transport: ~p"
+ "~n Active: ~p", [Transport, Active]),
+ {Mod, Listen, StatsInterval} = process_transport(Transport, Active),
+ case Listen(0) of
+ {ok, LSock} ->
+ case Mod:port(LSock) of
+ {ok, Port} ->
+ Addr = which_addr(), % This is just for convenience
+ ?I("listening on:"
+ "~n Addr: ~p (~s)"
+ "~n Port: ~w"
+ "~n", [Addr, inet:ntoa(Addr), Port]),
+ Starter ! {?MODULE, self(), {ok, {Addr, Port}}},
+ server_loop(#{parent => Parent,
+ mod => Mod,
+ active => Active,
+ lsock => LSock,
+ handlers => [],
+ stats_interval => StatsInterval,
+ %% Accumulation
+ runtime => 0,
+ mcnt => 0,
+ bcnt => 0,
+ hcnt => 0
+ });
+ {error, PReason} ->
+ (catch Mod:close(LSock)),
+ exit({port, PReason})
+ end;
+ {error, LReason} ->
+ exit({listen, LReason})
+ end.
+
+process_transport(Mod, _) when is_atom(Mod) ->
+ {Mod, fun(Port) -> Mod:listen(Port) end, infinity};
+process_transport({Mod, #{stats_interval := T} = Opts}, Active)
+ when (Active =/= false) ->
+ {Mod, fun(Port) -> Mod:listen(Port, Opts#{stats_to => self()}) end, T};
+process_transport({Mod, #{stats_interval := T} = Opts}, _Active) ->
+ {Mod, fun(Port) -> Mod:listen(Port, Opts) end, T};
+process_transport({Mod, Opts}, _Active) ->
+ {Mod, fun(Port) -> Mod:listen(Port, Opts) end, infinity}.
+
+
+
+server_loop(State) ->
+ server_loop( server_handle_message( server_accept(State) ) ).
+
+server_accept(#{mod := Mod,
+ active := Active,
+ lsock := LSock,
+ handlers := Handlers} = State) ->
+ case Mod:accept(LSock, ?ACC_TIMEOUT) of
+ {ok, Sock} ->
+ ?I("accepted connection from ~s",
+ [case Mod:peername(Sock) of
+ {ok, Peer} ->
+ format_peername(Peer);
+ {error, _} ->
+ "-"
+ end]),
+ {Pid, _} = handler_start(),
+ ?I("handler ~p started -> try transfer socket control", [Pid]),
+ case Mod:controlling_process(Sock, Pid) of
+ ok ->
+ maybe_start_stats_timer(State, Pid),
+ ?I("server-accept: handler ~p started", [Pid]),
+ handler_continue(Pid, Mod, Sock, Active),
+ Handlers2 = [Pid | Handlers],
+ State#{handlers => Handlers2};
+ {error, CPReason} ->
+ (catch Mod:close(Sock)),
+ (catch Mod:close(LSock)),
+ exit({controlling_process, CPReason})
+ end;
+ {error, timeout} ->
+ State;
+ {error, AReason} ->
+ (catch Mod:close(LSock)),
+ exit({accept, AReason})
+ end.
+
+format_peername({Addr, Port}) ->
+ case inet:gethostbyaddr(Addr) of
+ {ok, #hostent{h_name = N}} ->
+ ?F("~s (~s:~w)", [N, inet:ntoa(Addr), Port]);
+ {error, _} ->
+ ?F("~p, ~p", [Addr, Port])
+ end.
+
+maybe_start_stats_timer(#{active := Active, stats_interval := Time}, Handler)
+ when (Active =/= false) andalso (is_integer(Time) andalso (Time > 0)) ->
+ start_stats_timer(Time, "handler", Handler);
+maybe_start_stats_timer(_, _) ->
+ ok.
+
+start_stats_timer(Time, ProcStr, Pid) ->
+ erlang:start_timer(Time, self(), {stats, Time, ProcStr, Pid}).
+
+server_handle_message(#{parent := Parent, handlers := H} = State) ->
+ receive
+ {timeout, _TRef, {stats, Interval, ProcStr, Pid}} ->
+ case server_handle_stats(ProcStr, Pid) of
+ ok ->
+ start_stats_timer(Interval, ProcStr, Pid);
+ skip ->
+ ok
+ end,
+ State;
+
+ {?MODULE, Ref, Parent, stop} ->
+ reply(Parent, Ref, ok),
+ lists:foreach(fun(P) -> handler_stop(P) end, H),
+ exit(normal);
+
+ {'DOWN', _MRef, process, Pid, Reason} ->
+ server_handle_down(Pid, Reason, State)
+
+ after 0 ->
+ State
+ end.
+
+server_handle_stats(ProcStr, Pid) ->
+ case ?LIB:formated_process_stats(Pid) of
+ "" ->
+ skip;
+ FormatedStats ->
+ ?I("Statistics for ~s ~p:~s", [ProcStr, Pid, FormatedStats]),
+ ok
+ end.
+
+
+server_handle_down(Pid, Reason, #{handlers := Handlers} = State) ->
+ case lists:delete(Pid, Handlers) of
+ Handlers ->
+ ?I("unknown process ~p died", [Pid]),
+ State;
+ Handlers2 ->
+ server_handle_handler_down(Pid, Reason, State#{handlers => Handlers2})
+ end.
+
+
+server_handle_handler_down(Pid,
+ {done, RunTime, MCnt, BCnt},
+ #{runtime := AccRunTime,
+ mcnt := AccMCnt,
+ bcnt := AccBCnt,
+ hcnt := AccHCnt} = State) ->
+ AccRunTime2 = AccRunTime + RunTime,
+ AccMCnt2 = AccMCnt + MCnt,
+ AccBCnt2 = AccBCnt + BCnt,
+ AccHCnt2 = AccHCnt + 1,
+ ?I("handler ~p (~w) done => accumulated results: "
+ "~n Run Time: ~s ms"
+ "~n Message Count: ~s"
+ "~n Byte Count: ~s",
+ [Pid, AccHCnt2,
+ ?FORMAT_TIME(AccRunTime2),
+ if (AccRunTime2 > 0) ->
+ ?F("~w => ~w (~w) msgs / ms",
+ [AccMCnt2,
+ AccMCnt2 div AccRunTime2,
+ (AccMCnt2 div AccHCnt2) div AccRunTime2]);
+ true ->
+ ?F("~w", [AccMCnt2])
+ end,
+ if (AccRunTime2 > 0) ->
+ ?F("~w => ~w (~w) bytes / ms",
+ [AccBCnt2,
+ AccBCnt2 div AccRunTime2,
+ (AccBCnt2 div AccHCnt2) div AccRunTime2]);
+ true ->
+ ?F("~w", [AccBCnt2])
+ end]),
+ State#{runtime => AccRunTime2,
+ mcnt => AccMCnt2,
+ bcnt => AccBCnt2,
+ hcnt => AccHCnt2};
+server_handle_handler_down(Pid, Reason, State) ->
+ ?I("handler ~p terminated: "
+ "~n ~p", [Pid, Reason]),
+ State.
+
+
+
+%% ==========================================================================
+
+handler_start() ->
+ Self = self(),
+ HandlerInit = fun() -> put(sname, "handler"), handler_init(Self) end,
+ spawn_monitor(HandlerInit).
+
+handler_continue(Pid, Mod, Sock, Active) ->
+ req(Pid, {continue, Mod, Sock, Active}).
+
+handler_stop(Pid) ->
+ req(Pid, stop).
+
+handler_init(Parent) ->
+ ?I("starting"),
+ receive
+ {?MODULE, Ref, Parent, {continue, Mod, Sock, Active}} ->
+ ?I("received continue"),
+ reply(Parent, Ref, ok),
+ handler_initial_activation(Mod, Sock, Active),
+ handler_loop(#{parent => Parent,
+ mod => Mod,
+ sock => Sock,
+ active => Active,
+ start => ?T(),
+ mcnt => 0,
+ bcnt => 0,
+ last_reply => none,
+ acc => <<>>})
+
+ after 5000 ->
+ ?I("timeout when message queue: "
+ "~n ~p"
+ "~nwhen"
+ "~n Parent: ~p", [process_info(self(), messages), Parent]),
+ handler_init(Parent)
+ end.
+
+handler_loop(State) ->
+ handler_loop( handler_handle_message( handler_recv_message(State) ) ).
+
+%% When passive, we read *one* request and then attempt to reply to it.
+handler_recv_message(#{mod := Mod,
+ sock := Sock,
+ active := false,
+ mcnt := MCnt,
+ bcnt := BCnt,
+ last_reply := LID} = State) ->
+ case handler_recv_message2(Mod, Sock) of
+ {ok, {MsgSz, ID, Body}} ->
+ handler_send_reply(Mod, Sock, ID, Body),
+ State#{mcnt => MCnt + 1,
+ bcnt => BCnt + MsgSz,
+ last_reply => ID};
+ {error, closed} ->
+ handler_done(State);
+ {error, timeout} ->
+ ?I("timeout when: "
+ "~n MCnt: ~p"
+ "~n BCnt: ~p"
+ "~n LID: ~p", [MCnt, BCnt, LID]),
+ State
+ end;
+
+
+%% When "active" (once or true), we receive one data "message", which may
+%% contain any number of requests or only part of a request. Then we
+%% process this data together with whatever we had "accumulated" from
+%% prevous messages. Each request will be extracted and replied to. If
+%% there is some data left, not enough for a complete request, we store
+%% this in 'acc' (accumulate it).
+handler_recv_message(#{mod := Mod,
+ sock := Sock,
+ active := Active,
+ mcnt := MCnt,
+ bcnt := BCnt,
+ last_reply := LID,
+ acc := Acc} = State) ->
+ case handler_recv_message3(Mod, Sock, Acc, LID) of
+ {ok, {MCnt2, BCnt2, LID2}, NewAcc} ->
+ handler_maybe_activate(Mod, Sock, Active),
+ State#{mcnt => MCnt + MCnt2,
+ bcnt => BCnt + BCnt2,
+ last_reply => LID2,
+ acc => NewAcc};
+
+ {error, closed} ->
+ if
+ (size(Acc) =:= 0) ->
+ handler_done(State);
+ true ->
+ ?E("client done with partial message: "
+ "~n Last Reply Sent: ~w"
+ "~n Message Count: ~w"
+ "~n Byte Count: ~w"
+ "~n Partial Message: ~w bytes",
+ [LID, MCnt, BCnt, size(Acc)]),
+ exit({closed_with_partial_message, LID})
+ end;
+
+ {error, timeout} ->
+ ?I("timeout when: "
+ "~n MCnt: ~p"
+ "~n BCnt: ~p"
+ "~n LID: ~p"
+ "~n size(Acc): ~p", [MCnt, BCnt, LID, size(Acc)]),
+ State
+ end.
+
+handler_process_data(Acc, Mod, Sock, LID) ->
+ handler_process_data(Acc, Mod, Sock, 0, 0, LID).
+
+%% Extract each complete request, one at a time.
+handler_process_data(<<?TTEST_TAG:32,
+ ?TTEST_TYPE_REQUEST:32,
+ ID:32,
+ SZ:32,
+ Rest/binary>>,
+ Mod, Sock,
+ MCnt, BCnt, _LID) when (size(Rest) >= SZ) ->
+ <<Body:SZ/binary, Rest2/binary>> = Rest,
+ case handler_send_reply(Mod, Sock, ID, Body) of
+ ok ->
+ handler_process_data(Rest2, Mod, Sock, MCnt+1, BCnt+16+SZ, ID);
+ {error, _} = ERROR ->
+ ERROR
+ end;
+handler_process_data(Data, _Mod, _Sock, MCnt, BCnt, LID) ->
+ {ok, {MCnt, BCnt, LID}, Data}.
+
+
+handler_recv_message2(Mod, Sock) ->
+ case Mod:recv(Sock, 4*4, ?RECV_TIMEOUT) of
+ {ok, <<?TTEST_TAG:32,
+ ?TTEST_TYPE_REQUEST:32,
+ ID:32,
+ SZ:32>> = Hdr} ->
+ case Mod:recv(Sock, SZ, ?RECV_TIMEOUT) of
+ {ok, Body} when (SZ =:= size(Body)) ->
+ {ok, {size(Hdr) + size(Body), ID, Body}};
+ {error, BReason} ->
+ ?E("failed reading body (~w) of message ~w:"
+ "~n ~p", [SZ, ID, BReason]),
+ exit({recv, body, ID, SZ, BReason})
+ end;
+ {error, timeout} = ERROR ->
+ ERROR;
+ {error, closed} = ERROR ->
+ ERROR;
+ {error, HReason} ->
+ ?E("Failed reading header of message:"
+ "~n ~p", [HReason]),
+ exit({recv, header, HReason})
+ end.
+
+
+handler_recv_message3(Mod, Sock, Acc, LID) ->
+ receive
+ {TagClosed, Sock} when (TagClosed =:= tcp_closed) orelse
+ (TagClosed =:= socket_closed) ->
+ {error, closed};
+
+ {TagErr, Sock, Reason} when (TagErr =:= tcp_error) orelse
+ (TagErr =:= socket_error) ->
+ {error, Reason};
+
+ {Tag, Sock, Msg} when (Tag =:= tcp) orelse
+ (Tag =:= socket) ->
+ handler_process_data(<<Acc/binary, Msg/binary>>, Mod, Sock, LID)
+
+ after ?RECV_TIMEOUT ->
+ {error, timeout}
+ end.
+
+
+
+handler_send_reply(Mod, Sock, ID, Data) ->
+ SZ = size(Data),
+ Msg = <<?TTEST_TAG:32,
+ ?TTEST_TYPE_REPLY:32,
+ ID:32,
+ SZ:32,
+ Data/binary>>,
+ case Mod:send(Sock, Msg) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ (catch Mod:close(Sock)),
+ exit({send, Reason})
+ end.
+
+
+handler_done(State) ->
+ handler_done(State, ?T()).
+
+handler_done(#{start := Start,
+ mod := Mod,
+ sock := Sock,
+ mcnt := MCnt,
+ bcnt := BCnt}, Stop) ->
+ (catch Mod:close(Sock)),
+ exit({done, ?TDIFF(Start, Stop), MCnt, BCnt}).
+
+
+handler_handle_message(#{parent := Parent} = State) ->
+ receive
+ {'EXIT', Parent, Reason} ->
+ exit({parent_exit, Reason})
+ after 0 ->
+ State
+ end.
+
+
+handler_initial_activation(_Mod, _Sock, false = _Active) ->
+ ok;
+handler_initial_activation(Mod, Sock, Active) ->
+ Mod:active(Sock, Active).
+
+
+handler_maybe_activate(Mod, Sock, once = Active) ->
+ Mod:active(Sock, Active);
+handler_maybe_activate(_, _, _) ->
+ ok.
+
+
+
+%% ==========================================================================
+
+which_addr() ->
+ case inet:getifaddrs() of
+ {ok, IfAddrs} ->
+ which_addrs(inet, IfAddrs);
+ {error, Reason} ->
+ exit({getifaddrs, Reason})
+ end.
+
+which_addrs(_Family, []) ->
+ exit({getifaddrs, not_found});
+which_addrs(Family, [{"lo", _} | IfAddrs]) ->
+ %% Skip
+ which_addrs(Family, IfAddrs);
+which_addrs(Family, [{"docker" ++ _, _} | IfAddrs]) ->
+ %% Skip docker
+ which_addrs(Family, IfAddrs);
+which_addrs(Family, [{"br-" ++ _, _} | IfAddrs]) ->
+ %% Skip docker
+ which_addrs(Family, IfAddrs);
+which_addrs(Family, [{"en" ++ _, IfOpts} | IfAddrs]) ->
+ %% Maybe take this one
+ case which_addr(Family, IfOpts) of
+ {ok, Addr} ->
+ Addr;
+ error ->
+ which_addrs(Family, IfAddrs)
+ end;
+which_addrs(Family, [{_IfName, IfOpts} | IfAddrs]) ->
+ case which_addr(Family, IfOpts) of
+ {ok, Addr} ->
+ Addr;
+ error ->
+ which_addrs(Family, IfAddrs)
+ end.
+
+which_addr(_, []) ->
+ error;
+which_addr(inet, [{addr, Addr}|_])
+ when is_tuple(Addr) andalso (size(Addr) =:= 4) ->
+ {ok, Addr};
+which_addr(inet6, [{addr, Addr}|_])
+ when is_tuple(Addr) andalso (size(Addr) =:= 8) ->
+ {ok, Addr};
+which_addr(Family, [_|IfOpts]) ->
+ which_addr(Family, IfOpts).
+
+
+%% ==========================================================================
+
+req(Pid, Req) ->
+ Ref = make_ref(),
+ Pid ! {?MODULE, Ref, self(), Req},
+ receive
+ {'EXIT', Pid, Reason} ->
+ {error, {exit, Reason}};
+ {?MODULE, Ref, Reply} ->
+ Reply
+ end.
+
+reply(Pid, Ref, Reply) ->
+ Pid ! {?MODULE, Ref, Reply}.
+
+
+%% ==========================================================================
+
+%% t() ->
+%% os:timestamp().
+
+%% tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) ->
+%% T1 = A1*1000000000+B1*1000+(C1 div 1000),
+%% T2 = A2*1000000000+B2*1000+(C2 div 1000),
+%% T2 - T1.
+
+%% formated_timestamp() ->
+%% format_timestamp(os:timestamp()).
+
+%% format_timestamp({_N1, _N2, N3} = TS) ->
+%% {_Date, Time} = calendar:now_to_local_time(TS),
+%% {Hour,Min,Sec} = Time,
+%% FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w.4~w",
+%% [Hour, Min, Sec, round(N3/1000)]),
+%% lists:flatten(FormatTS).
+
+%% %% Time is always in number os ms (milli seconds)
+%% format_time(T) ->
+%% f("~p", [T]).
+
+
+%% ==========================================================================
+
+%% f(F, A) ->
+%% lists:flatten(io_lib:format(F, A)).
+
+%% e(F, A) ->
+%% p(get(sname), "<ERROR> " ++ F, A).
+
+%% i(F) ->
+%% i(F, []).
+
+%% i(F, A) ->
+%% p(get(sname), "<INFO> " ++ F, A).
+
+%% p(undefined, F, A) ->
+%% p("- ", F, A);
+%% p(Prefix, F, A) ->
+%% io:format("[~s, ~s] " ++ F ++ "~n", [formated_timestamp(), Prefix |A]).
+
diff --git a/lib/kernel/src/net.erl b/erts/emulator/test/socket_test_ttest_tcp_server_gen.erl
index 2d0ae2ed0c..b1b31f5158 100644
--- a/lib/kernel/src/net.erl
+++ b/erts/emulator/test/socket_test_ttest_tcp_server_gen.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2018. All 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,24 +17,25 @@
%%
%% %CopyrightEnd%
%%
--module(net).
-%% Various network functions, kept here for compatibility
+-module(socket_test_ttest_tcp_server_gen).
--export([call/4,
- cast/4,
- broadcast/3,
- ping/1,
- relay/1,
- sleep/1]).
+-export([
+ start/1,
+ stop/1
+ ]).
--deprecated(module).
-
-call(N,M,F,A) -> rpc:call(N,M,F,A).
-cast(N,M,F,A) -> rpc:cast(N,M,F,A).
-broadcast(M,F,A) -> rpc:eval_everywhere(M,F,A).
-ping(Node) -> net_adm:ping(Node).
-sleep(T) -> receive after T -> ok end.
-relay(X) -> slave:relay(X).
+-define(TRANSPORT_MOD, socket_test_ttest_tcp_gen).
+start(Active) ->
+ socket_test_ttest_tcp_server:start(?TRANSPORT_MOD, Active).
+ %% {ok, {Pid, AddrPort}} ->
+ %% MRef = erlang:monitor(process, Pid),
+ %% {ok, {Pid, MRef, AddrPort}};
+ %% {error, _} = ERROR ->
+ %% ERROR
+ %% end.
+
+stop(Pid) ->
+ socket_test_ttest_tcp_server:stop(Pid).
diff --git a/erts/emulator/test/socket_test_ttest_tcp_server_socket.erl b/erts/emulator/test/socket_test_ttest_tcp_server_socket.erl
new file mode 100644
index 0000000000..b7ea1e8e93
--- /dev/null
+++ b/erts/emulator/test/socket_test_ttest_tcp_server_socket.erl
@@ -0,0 +1,43 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2018. All 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(socket_test_ttest_tcp_server_socket).
+
+-export([
+ start/2,
+ stop/1
+ ]).
+
+-define(TRANSPORT_MOD, socket_test_ttest_tcp_socket).
+%% -define(MOD(M), {?TRANSPORT_MOD, #{method => M,
+%% stats_interval => 10000}}).
+-define(MOD(M), {?TRANSPORT_MOD, #{method => M}}).
+
+start(Method, Active) ->
+ socket_test_ttest_tcp_server:start(?MOD(Method), Active).
+ %% {ok, {Pid, AddrPort}} ->
+ %% MRef = erlang:monitor(process, Pid),
+ %% {ok, {Pid, MRef, AddrPort}};
+ %% {error, _} = ERROR ->
+ %% ERROR
+ %% end.
+
+stop(Pid) ->
+ socket_test_ttest_tcp_server:stop(Pid).
diff --git a/erts/emulator/test/socket_test_ttest_tcp_socket.erl b/erts/emulator/test/socket_test_ttest_tcp_socket.erl
new file mode 100644
index 0000000000..0ae2412e4c
--- /dev/null
+++ b/erts/emulator/test/socket_test_ttest_tcp_socket.erl
@@ -0,0 +1,414 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2018. All 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(socket_test_ttest_tcp_socket).
+
+-export([
+ accept/1, accept/2,
+ active/2,
+ close/1,
+ connect/2, connect/3,
+ controlling_process/2,
+ listen/0, listen/1, listen/2,
+ port/1,
+ peername/1,
+ recv/2, recv/3,
+ send/2,
+ shutdown/2,
+ sockname/1
+ ]).
+
+
+-define(READER_RECV_TIMEOUT, 1000).
+
+-define(DATA_MSG(Sock, Method, Data),
+ {socket,
+ #{sock => Sock, reader => self(), method => Method},
+ Data}).
+
+-define(CLOSED_MSG(Sock, Method),
+ {socket_closed,
+ #{sock => Sock, reader => self(), method => Method}}).
+
+-define(ERROR_MSG(Sock, Method, Reason),
+ {socket_error,
+ #{sock => Sock, reader => self(), method => Method},
+ Reason}).
+
+
+%% ==========================================================================
+
+%% This does not really work. Its just a placeholder for the time beeing...
+
+%% getopt(Sock, Opt) when is_atom(Opt) ->
+%% socket:getopt(Sock, socket, Opt).
+
+%% setopt(Sock, Opt, Value) when is_atom(Opt) ->
+%% socket:setopts(Sock, socket, Opt, Value).
+
+
+%% ==========================================================================
+
+accept(#{sock := LSock, opts := #{method := Method} = Opts}) ->
+ case socket:accept(LSock) of
+ {ok, Sock} ->
+ Self = self(),
+ Reader = spawn(fun() -> reader_init(Self, Sock, false, Method) end),
+ maybe_start_stats_timer(Opts, Reader),
+ {ok, #{sock => Sock, reader => Reader, method => Method}};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+accept(#{sock := LSock, opts := #{method := Method} = Opts}, Timeout) ->
+ case socket:accept(LSock, Timeout) of
+ {ok, Sock} ->
+ Self = self(),
+ Reader = spawn(fun() -> reader_init(Self, Sock, false, Method) end),
+ maybe_start_stats_timer(Opts, Reader),
+ {ok, #{sock => Sock, reader => Reader, method => Method}};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+active(#{reader := Pid}, NewActive)
+ when (is_boolean(NewActive) orelse (NewActive =:= once)) ->
+ Pid ! {?MODULE, active, NewActive},
+ ok.
+
+
+close(#{sock := Sock, reader := Pid}) ->
+ Pid ! {?MODULE, stop},
+ socket:close(Sock).
+
+%% Create a socket and connect it to a peer
+connect(Addr, Port) ->
+ connect(Addr, Port, #{method => plain}).
+
+connect(Addr, Port, #{method := Method} = Opts) ->
+ try
+ begin
+ Sock =
+ case socket:open(inet, stream, tcp) of
+ {ok, S} ->
+ S;
+ {error, OReason} ->
+ throw({error, {open, OReason}})
+ end,
+ case socket:bind(Sock, any) of
+ {ok, _} ->
+ ok;
+ {error, BReason} ->
+ (catch socket:close(Sock)),
+ throw({error, {bind, BReason}})
+ end,
+ SA = #{family => inet,
+ addr => Addr,
+ port => Port},
+ case socket:connect(Sock, SA) of
+ ok ->
+ ok;
+ {error, CReason} ->
+ (catch socket:close(Sock)),
+ throw({error, {connect, CReason}})
+ end,
+ Self = self(),
+ Reader = spawn(fun() -> reader_init(Self, Sock, false, Method) end),
+ maybe_start_stats_timer(Opts, Reader),
+ {ok, #{sock => Sock, reader => Reader, method => Method}}
+ end
+ catch
+ throw:ERROR:_ ->
+ ERROR
+ end.
+
+
+maybe_start_stats_timer(#{stats_to := Pid,
+ stats_interval := T},
+ Reader) when is_pid(Pid) ->
+ erlang:start_timer(T, Pid, {stats, T, "reader", Reader});
+maybe_start_stats_timer(_O, _) ->
+ ok.
+
+controlling_process(#{sock := Sock, reader := Pid}, NewPid) ->
+ case socket:setopt(Sock, otp, controlling_process, NewPid) of
+ ok ->
+ Pid ! {?MODULE, self(), controlling_process, NewPid},
+ receive
+ {?MODULE, Pid, controlling_process} ->
+ ok
+ end;
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+%% Create a listen socket
+listen() ->
+ listen(0, #{method => plain}).
+
+listen(Port) ->
+ listen(Port, #{method => plain}).
+listen(Port, #{method := Method} = Opts)
+ when (is_integer(Port) andalso (Port >= 0)) andalso
+ ((Method =:= plain) orelse (Method =:= msg)) ->
+ try
+ begin
+ Sock = case socket:open(inet, stream, tcp) of
+ {ok, S} ->
+ S;
+ {error, OReason} ->
+ throw({error, {open, OReason}})
+ end,
+ SA = #{family => inet,
+ port => Port},
+ case socket:bind(Sock, SA) of
+ {ok, _} ->
+ ok;
+ {error, BReason} ->
+ (catch socket:close(Sock)),
+ throw({error, {bind, BReason}})
+ end,
+ case socket:listen(Sock) of
+ ok ->
+ ok;
+ {error, LReason} ->
+ (catch socket:close(Sock)),
+ throw({error, {listen, LReason}})
+ end,
+ {ok, #{sock => Sock, opts => Opts}}
+ end
+ catch
+ throw:{error, Reason}:_ ->
+ {error, Reason}
+ end.
+
+
+port(#{sock := Sock}) ->
+ case socket:sockname(Sock) of
+ {ok, #{port := Port}} ->
+ {ok, Port};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+peername(#{sock := Sock}) ->
+ case socket:peername(Sock) of
+ {ok, #{addr := Addr, port := Port}} ->
+ {ok, {Addr, Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+recv(#{sock := Sock, method := plain}, Length) ->
+ socket:recv(Sock, Length);
+recv(#{sock := Sock, method := msg}, Length) ->
+ case socket:recvmsg(Sock, Length, 0, [], infinity) of
+ {ok, #{iov := [Bin]}} ->
+ {ok, Bin};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+recv(#{sock := Sock, method := plain}, Length, Timeout) ->
+ socket:recv(Sock, Length, Timeout);
+recv(#{sock := Sock, method := msg}, Length, Timeout) ->
+ case socket:recvmsg(Sock, Length, 0, [], Timeout) of
+ {ok, #{iov := [Bin]}} ->
+ {ok, Bin};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+send(#{sock := Sock, method := plain}, Bin) ->
+ socket:send(Sock, Bin);
+send(#{sock := Sock, method := msg}, Bin) ->
+ socket:sendmsg(Sock, #{iov => [Bin]}).
+
+
+shutdown(#{sock := Sock}, How) ->
+ socket:shutdown(Sock, How).
+
+
+sockname(#{sock := Sock}) ->
+ case socket:sockname(Sock) of
+ {ok, #{addr := Addr, port := Port}} ->
+ {ok, {Addr, Port}};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+%% ==========================================================================
+
+reader_init(ControllingProcess, Sock, Active, Method)
+ when is_pid(ControllingProcess) andalso
+ (is_boolean(Active) orelse (Active =:= once)) andalso
+ ((Method =:= plain) orelse (Method =:= msg)) ->
+ MRef = erlang:monitor(process, ControllingProcess),
+ reader_loop(#{ctrl_proc => ControllingProcess,
+ ctrl_proc_mref => MRef,
+ active => Active,
+ sock => Sock,
+ method => Method}).
+
+
+%% Never read
+reader_loop(#{active := false,
+ ctrl_proc := Pid} = State) ->
+ receive
+ {?MODULE, stop} ->
+ exit(normal);
+
+ {?MODULE, Pid, controlling_process, NewPid} ->
+ MRef = maps:get(ctrl_proc_mref, State),
+ erlang:demonitor(MRef, [flush]),
+ NewMRef = erlang:monitor(process, NewPid),
+ Pid ! {?MODULE, self(), controlling_process},
+ reader_loop(State#{ctrl_proc => NewPid,
+ ctrl_proc_mref => NewMRef});
+
+ {?MODULE, active, NewActive} ->
+ reader_loop(State#{active => NewActive});
+
+ {'DOWN', MRef, process, Pid, Reason} ->
+ case maps:get(ctrl_proc_mref, State) of
+ MRef when (Reason =:= normal) ->
+ exit(normal);
+ MRef ->
+ exit({controlling_process, Reason});
+ _ ->
+ reader_loop(State)
+ end
+ end;
+
+%% Read *once* and then change to false
+reader_loop(#{active := once,
+ sock := Sock,
+ method := Method,
+ ctrl_proc := Pid} = State) ->
+ case do_recv(Method, Sock) of
+ {ok, Data} ->
+ Pid ! ?DATA_MSG(Sock, Method, Data),
+ reader_loop(State#{active => false});
+ {error, timeout} ->
+ receive
+ {?MODULE, stop} ->
+ exit(normal);
+
+ {?MODULE, Pid, controlling_process, NewPid} ->
+ MRef = maps:get(ctrl_proc_mref, State),
+ erlang:demonitor(MRef, [flush]),
+ MRef = erlang:monitor(process, NewPid),
+ Pid ! {?MODULE, self(), controlling_process},
+ reader_loop(State#{ctrl_proc => NewPid,
+ ctrl_proc_mref => MRef});
+
+ {?MODULE, active, NewActive} ->
+ reader_loop(State#{active => NewActive});
+
+ {'DOWN', MRef, process, Pid, Reason} ->
+ case maps:get(ctrl_proc_mref, State) of
+ MRef when (Reason =:= normal) ->
+ exit(normal);
+ MRef ->
+ exit({controlling_process, Reason});
+ _ ->
+ reader_loop(State)
+ end
+ after 0 ->
+ reader_loop(State)
+ end;
+
+ {error, closed} ->
+ Pid ! ?CLOSED_MSG(Sock, Method),
+ exit(normal);
+
+ {error, Reason} ->
+ Pid ! ?ERROR_MSG(Sock, Method, Reason),
+ exit(Reason)
+ end;
+
+%% Read and forward data continuously
+reader_loop(#{active := true,
+ sock := Sock,
+ method := Method,
+ ctrl_proc := Pid} = State) ->
+ case do_recv(Method, Sock) of
+ {ok, Data} ->
+ Pid ! ?DATA_MSG(Sock, Method, Data),
+ reader_loop(State);
+ {error, timeout} ->
+ receive
+ {?MODULE, stop} ->
+ exit(normal);
+
+ {?MODULE, Pid, controlling_process, NewPid} ->
+ MRef = maps:get(ctrl_proc_mref, State),
+ erlang:demonitor(MRef, [flush]),
+ MRef = erlang:monitor(process, NewPid),
+ Pid ! {?MODULE, self(), controlling_process},
+ reader_loop(State#{ctrl_proc => NewPid,
+ ctrl_proc_mref => MRef});
+
+ {?MODULE, active, NewActive} ->
+ reader_loop(State#{active => NewActive});
+
+ {'DOWN', MRef, process, Pid, Reason} ->
+ case maps:get(ctrl_proc_mref, State) of
+ MRef when (Reason =:= normal) ->
+ exit(normal);
+ MRef ->
+ exit({controlling_process, Reason});
+ _ ->
+ reader_loop(State)
+ end
+ after 0 ->
+ reader_loop(State)
+ end;
+
+ {error, closed} ->
+ Pid ! ?CLOSED_MSG(Sock, Method),
+ exit(normal);
+
+ {error, Reason} ->
+ Pid ! ?ERROR_MSG(Sock, Method, Reason),
+ exit(Reason)
+ end.
+
+
+do_recv(plain, Sock) ->
+ socket:recv(Sock, 0, ?READER_RECV_TIMEOUT);
+do_recv(msg, Sock) ->
+ case socket:recvmsg(Sock, 0, 0, [], ?READER_RECV_TIMEOUT) of
+ {ok, #{iov := [Bin]}} ->
+ {ok, Bin};
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+
+%% ==========================================================================
+
diff --git a/erts/emulator/test/system_info_SUITE.erl b/erts/emulator/test/system_info_SUITE.erl
index 21ab6b378a..55b1162cfb 100644
--- a/erts/emulator/test/system_info_SUITE.erl
+++ b/erts/emulator/test/system_info_SUITE.erl
@@ -37,8 +37,9 @@
-export([process_count/1, system_version/1, misc_smoke_tests/1,
heap_size/1, wordsize/1, memory/1, ets_limit/1, atom_limit/1,
- ets_count/1,
- atom_count/1]).
+ ets_count/1, atom_count/1, system_logger/1]).
+
+-export([init/1, handle_event/2, handle_call/2]).
suite() ->
[{ct_hooks,[ts_install_cth]},
@@ -46,8 +47,8 @@ suite() ->
all() ->
[process_count, system_version, misc_smoke_tests,
- ets_count,
- heap_size, wordsize, memory, ets_limit, atom_limit, atom_count].
+ ets_count, heap_size, wordsize, memory, ets_limit, atom_limit, atom_count,
+ system_logger].
%%%
%%% The test cases -------------------------------------------------------------
@@ -457,11 +458,16 @@ cmp_memory(MWs, Str) ->
%% Total, processes, processes_used, and system will seldom
%% give us exactly the same result since the two readings
%% aren't taken atomically.
+ %%
+ %% Torerance is scaled according to the number of schedulers
+ %% to match spawn_mem_workers.
- cmp_memory(total, EM, EDM, 1.05),
- cmp_memory(processes, EM, EDM, 1.05),
- cmp_memory(processes_used, EM, EDM, 1.05),
- cmp_memory(system, EM, EDM, 1.05),
+ Tolerance = 1.05 + 0.01 * erlang:system_info(schedulers_online),
+
+ cmp_memory(total, EM, EDM, Tolerance),
+ cmp_memory(processes, EM, EDM, Tolerance),
+ cmp_memory(processes_used, EM, EDM, Tolerance),
+ cmp_memory(system, EM, EDM, Tolerance),
ok.
@@ -573,3 +579,78 @@ atom_count(Config) when is_list(Config) ->
true = Limit >= Count2,
true = Count2 > Count1,
ok.
+
+
+system_logger(Config) when is_list(Config) ->
+
+ TC = self(),
+
+ ok = error_logger:add_report_handler(?MODULE, [TC]),
+
+ generate_log_event(),
+
+ flush(1, report_handler),
+
+ Initial = erlang:system_info(system_logger),
+
+ {Logger,_} = spawn_monitor(fun F() -> receive M -> TC ! {system_logger,M}, F() end end),
+
+ Initial = erlang:system_flag(system_logger, Logger),
+ Logger = erlang:system_info(system_logger),
+
+ generate_log_event(),
+ flush(1, system_logger),
+
+ Logger = erlang:system_flag(system_logger, Logger),
+
+ generate_log_event(),
+ flush(1, system_logger),
+
+ exit(Logger, die),
+ receive {'DOWN',_,_,_,_} -> ok end,
+
+ generate_log_event(),
+ flush(1, report_handler),
+
+ logger = erlang:system_info(system_logger),
+
+ logger = erlang:system_flag(system_logger, undefined),
+ generate_log_event(),
+ flush(),
+
+ undefined = erlang:system_flag(system_logger, Initial),
+
+ ok.
+
+flush() ->
+ receive
+ M ->
+ ct:fail({unexpected_message, M})
+ after 0 ->
+ ok
+ end.
+
+flush(0, _Pat) ->
+ flush();
+flush(Cnt, Pat) ->
+ receive
+ M when element(1,M) =:= Pat ->
+ ct:log("~p",[M]),
+ flush(Cnt-1, Pat)
+ after 500 ->
+ ct:fail({missing, Cnt, Pat})
+ end.
+
+generate_log_event() ->
+ {_Pid, Ref} = spawn_monitor(fun() -> ok = nok end),
+ receive {'DOWN', Ref, _, _, _} -> ok end.
+
+init([To]) ->
+ {ok, To}.
+
+handle_call(Msg, State) ->
+ {ok, Msg, State}.
+
+handle_event(Event, State) ->
+ State ! {report_handler, Event},
+ {ok, State}.
diff --git a/erts/emulator/test/timer_bif_SUITE.erl b/erts/emulator/test/timer_bif_SUITE.erl
index fc11a04a31..15fe13c8c0 100644
--- a/erts/emulator/test/timer_bif_SUITE.erl
+++ b/erts/emulator/test/timer_bif_SUITE.erl
@@ -361,7 +361,7 @@ evil_timers(Config) when is_list(Config) ->
%%
%% 1. A timer started with erlang:start_timer(Time, Receiver, Msg),
%% where Msg is a composite term, expires, and the receivers main
- %% lock *can not* be acquired immediately (typically when the
+ %% lock *cannot* be acquired immediately (typically when the
%% receiver *is* running).
%%
%% The wrap tuple ({timeout, TRef, Msg}) will in this case
@@ -372,7 +372,7 @@ evil_timers(Config) when is_list(Config) ->
RecvTimeOutMsgs0 = evil_recv_timeouts(200),
%% 2. A timer started with erlang:start_timer(Time, Receiver, Msg),
%% where Msg is an immediate term, expires, and the receivers main
- %% lock *can not* be acquired immediately (typically when the
+ %% lock *cannot* be acquired immediately (typically when the
%% receiver *is* running).
%%
%% The wrap tuple will in this case be allocated in a new
diff --git a/erts/emulator/utils/beam_makeops b/erts/emulator/utils/beam_makeops
index da994fae3e..f73e2362bf 100755
--- a/erts/emulator/utils/beam_makeops
+++ b/erts/emulator/utils/beam_makeops
@@ -1840,12 +1840,57 @@ sub do_pack_one {
}
#
- # Return if there is nothing to pack.
- #
- if ($packable_args == 0) {
- return (-1);
- } elsif ($packable_args == 1 and $options == 0) {
- return (-1);
+ # Check whether any packing can be done.
+ #
+ my $nothing_to_pack = $packable_args == 0 ||
+ $packable_args == 1 && $options == 0;
+ if ($nothing_to_pack) {
+ # The packing engine in the loader processes the operands from
+ # right to left. Rightmost operands that are not packed must
+ # be stacked and then unstacked.
+ #
+ # Because instructions may be broken up into micro
+ # instructions, we might not see all operands at once. So
+ # there could be a micro instructions that packs the operands
+ # to the left of the current micro instruction. If that is the
+ # case, it is essential that we generate stacking and
+ # unstacking instructions even when no packing is
+ # possible. (build_pack_spec() will remove any unecessary
+ # stacking and unstacking operations.)
+ #
+ # Here is an example. Say that we have this instruction:
+ #
+ # i_plus x x j d
+ #
+ # that comprises two micro instructions:
+ #
+ # i_plus.fetch x x
+ # i_plus.execute j d
+ #
+ # This function (do_pack_one()) will be called twice, once to pack
+ # 'x' and 'x', and once to pack 'j' and 'd'.
+ #
+ # On a 32-bit machine, the 'j' and 'd' operands can't be
+ # packed because 'j' requires a full word. The two 'x'
+ # operands in the i_plus.fetch micro instruction will be
+ # packed, though, so we must generate instructions for packing
+ # and unpacking the 'j' and 'd' operands.
+ my $down = '';
+ my $up = '';
+ foreach my $arg (@args) {
+ my $push = 'g';
+ if ($type_bit{$arg} & $type_bit{'q'}) {
+ # The operand may be a literal.
+ $push = 'q';
+ } elsif ($type_bit{$arg} & $type_bit{'f'}) {
+ # The operand may be a failure label.
+ $push = 'f';
+ }
+ $down = "$push${down}";
+ $up = "${up}p";
+ }
+ my $pack_spec = "$down:$up";
+ return (1, ['',$pack_spec,@args]);
}
#
diff --git a/erts/emulator/zlib/adler32.c b/erts/emulator/zlib/adler32.c
index c693a42b7c..d0be4380a3 100644
--- a/erts/emulator/zlib/adler32.c
+++ b/erts/emulator/zlib/adler32.c
@@ -1,20 +1,15 @@
/* adler32.c -- compute the Adler-32 checksum of a data stream
- * Copyright (C) 1995-2011 Mark Adler
+ * Copyright (C) 1995-2011, 2016 Mark Adler
* For conditions of distribution and use, see copyright notice in zlib.h
*/
/* @(#) $Id$ */
-#ifdef HAVE_CONFIG_H
-# include "config.h"
-#endif
#include "zutil.h"
-#define local static
-
local uLong adler32_combine_ OF((uLong adler1, uLong adler2, z_off64_t len2));
-#define BASE 65521 /* largest prime smaller than 65536 */
+#define BASE 65521U /* largest prime smaller than 65536 */
#define NMAX 5552
/* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */
@@ -65,10 +60,10 @@ local uLong adler32_combine_ OF((uLong adler1, uLong adler2, z_off64_t len2));
#endif
/* ========================================================================= */
-uLong ZEXPORT adler32(adler, buf, len)
+uLong ZEXPORT adler32_z(adler, buf, len)
uLong adler;
const Bytef *buf;
- uInt len;
+ z_size_t len;
{
unsigned long sum2;
unsigned n;
@@ -136,6 +131,15 @@ uLong ZEXPORT adler32(adler, buf, len)
}
/* ========================================================================= */
+uLong ZEXPORT adler32(adler, buf, len)
+ uLong adler;
+ const Bytef *buf;
+ uInt len;
+{
+ return adler32_z(adler, buf, len);
+}
+
+/* ========================================================================= */
local uLong adler32_combine_(adler1, adler2, len2)
uLong adler1;
uLong adler2;
@@ -159,7 +163,7 @@ local uLong adler32_combine_(adler1, adler2, len2)
sum2 += ((adler1 >> 16) & 0xffff) + ((adler2 >> 16) & 0xffff) + BASE - rem;
if (sum1 >= BASE) sum1 -= BASE;
if (sum1 >= BASE) sum1 -= BASE;
- if (sum2 >= (BASE << 1)) sum2 -= (BASE << 1);
+ if (sum2 >= ((unsigned long)BASE << 1)) sum2 -= ((unsigned long)BASE << 1);
if (sum2 >= BASE) sum2 -= BASE;
return sum1 | (sum2 << 16);
}
diff --git a/erts/emulator/zlib/compress.c b/erts/emulator/zlib/compress.c
index 8ecef0f790..e2db404abf 100644
--- a/erts/emulator/zlib/compress.c
+++ b/erts/emulator/zlib/compress.c
@@ -1,13 +1,10 @@
/* compress.c -- compress a memory buffer
- * Copyright (C) 1995-2005 Jean-loup Gailly.
+ * Copyright (C) 1995-2005, 2014, 2016 Jean-loup Gailly, Mark Adler
* For conditions of distribution and use, see copyright notice in zlib.h
*/
/* @(#) $Id$ */
-#ifdef HAVE_CONFIG_H
-# include "config.h"
-#endif
#define ZLIB_INTERNAL
#include "zlib.h"
@@ -31,16 +28,11 @@ int ZEXPORT compress2 (dest, destLen, source, sourceLen, level)
{
z_stream stream;
int err;
+ const uInt max = (uInt)-1;
+ uLong left;
- stream.next_in = (z_const Bytef *)source;
- stream.avail_in = (uInt)sourceLen;
-#ifdef MAXSEG_64K
- /* Check for source > 64K on 16-bit machine: */
- if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR;
-#endif
- stream.next_out = dest;
- stream.avail_out = (uInt)*destLen;
- if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR;
+ left = *destLen;
+ *destLen = 0;
stream.zalloc = (alloc_func)0;
stream.zfree = (free_func)0;
@@ -49,15 +41,26 @@ int ZEXPORT compress2 (dest, destLen, source, sourceLen, level)
err = deflateInit(&stream, level);
if (err != Z_OK) return err;
- err = deflate(&stream, Z_FINISH);
- if (err != Z_STREAM_END) {
- deflateEnd(&stream);
- return err == Z_OK ? Z_BUF_ERROR : err;
- }
- *destLen = stream.total_out;
+ stream.next_out = dest;
+ stream.avail_out = 0;
+ stream.next_in = (z_const Bytef *)source;
+ stream.avail_in = 0;
+
+ do {
+ if (stream.avail_out == 0) {
+ stream.avail_out = left > (uLong)max ? max : (uInt)left;
+ left -= stream.avail_out;
+ }
+ if (stream.avail_in == 0) {
+ stream.avail_in = sourceLen > (uLong)max ? max : (uInt)sourceLen;
+ sourceLen -= stream.avail_in;
+ }
+ err = deflate(&stream, sourceLen ? Z_NO_FLUSH : Z_FINISH);
+ } while (err == Z_OK);
- err = deflateEnd(&stream);
- return err;
+ *destLen = stream.total_out;
+ deflateEnd(&stream);
+ return err == Z_STREAM_END ? Z_OK : err;
}
/* ===========================================================================
diff --git a/erts/emulator/zlib/crc32.c b/erts/emulator/zlib/crc32.c
index ba506d8dd3..9580440c0e 100644
--- a/erts/emulator/zlib/crc32.c
+++ b/erts/emulator/zlib/crc32.c
@@ -1,5 +1,5 @@
/* crc32.c -- compute the CRC-32 of a data stream
- * Copyright (C) 1995-2006, 2010, 2011, 2012 Mark Adler
+ * Copyright (C) 1995-2006, 2010, 2011, 2012, 2016 Mark Adler
* For conditions of distribution and use, see copyright notice in zlib.h
*
* Thanks to Rodney Brown <[email protected]> for his contribution of faster
@@ -28,23 +28,17 @@
# endif /* !DYNAMIC_CRC_TABLE */
#endif /* MAKECRCH */
-#ifdef HAVE_CONFIG_H
-# include "config.h"
-#endif
-
#include "zutil.h" /* for STDC and FAR definitions */
-#define local static
-
/* Definitions for doing the crc four data bytes at a time. */
#if !defined(NOBYFOUR) && defined(Z_U4)
# define BYFOUR
#endif
#ifdef BYFOUR
local unsigned long crc32_little OF((unsigned long,
- const unsigned char FAR *, unsigned));
+ const unsigned char FAR *, z_size_t));
local unsigned long crc32_big OF((unsigned long,
- const unsigned char FAR *, unsigned));
+ const unsigned char FAR *, z_size_t));
# define TBLS 8
#else
# define TBLS 1
@@ -205,10 +199,10 @@ const z_crc_t FAR * ZEXPORT get_crc_table()
#define DO8 DO1; DO1; DO1; DO1; DO1; DO1; DO1; DO1
/* ========================================================================= */
-unsigned long ZEXPORT crc32(crc, buf, len)
+unsigned long ZEXPORT crc32_z(crc, buf, len)
unsigned long crc;
const unsigned char FAR *buf;
- uInt len;
+ z_size_t len;
{
if (buf == Z_NULL) return 0UL;
@@ -239,8 +233,29 @@ unsigned long ZEXPORT crc32(crc, buf, len)
return crc ^ 0xffffffffUL;
}
+/* ========================================================================= */
+unsigned long ZEXPORT crc32(crc, buf, len)
+ unsigned long crc;
+ const unsigned char FAR *buf;
+ uInt len;
+{
+ return crc32_z(crc, buf, len);
+}
+
#ifdef BYFOUR
+/*
+ This BYFOUR code accesses the passed unsigned char * buffer with a 32-bit
+ integer pointer type. This violates the strict aliasing rule, where a
+ compiler can assume, for optimization purposes, that two pointers to
+ fundamentally different types won't ever point to the same memory. This can
+ manifest as a problem only if one of the pointers is written to. This code
+ only reads from those pointers. So long as this code remains isolated in
+ this compilation unit, there won't be a problem. For this reason, this code
+ should not be copied and pasted into a compilation unit in which other code
+ writes to the buffer that is passed to these routines.
+ */
+
/* ========================================================================= */
#define DOLIT4 c ^= *buf4++; \
c = crc_table[3][c & 0xff] ^ crc_table[2][(c >> 8) & 0xff] ^ \
@@ -251,7 +266,7 @@ unsigned long ZEXPORT crc32(crc, buf, len)
local unsigned long crc32_little(crc, buf, len)
unsigned long crc;
const unsigned char FAR *buf;
- unsigned len;
+ z_size_t len;
{
register z_crc_t c;
register const z_crc_t FAR *buf4;
@@ -282,7 +297,7 @@ local unsigned long crc32_little(crc, buf, len)
}
/* ========================================================================= */
-#define DOBIG4 c ^= *++buf4; \
+#define DOBIG4 c ^= *buf4++; \
c = crc_table[4][c & 0xff] ^ crc_table[5][(c >> 8) & 0xff] ^ \
crc_table[6][(c >> 16) & 0xff] ^ crc_table[7][c >> 24]
#define DOBIG32 DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4
@@ -291,7 +306,7 @@ local unsigned long crc32_little(crc, buf, len)
local unsigned long crc32_big(crc, buf, len)
unsigned long crc;
const unsigned char FAR *buf;
- unsigned len;
+ z_size_t len;
{
register z_crc_t c;
register const z_crc_t FAR *buf4;
@@ -304,7 +319,6 @@ local unsigned long crc32_big(crc, buf, len)
}
buf4 = (const z_crc_t FAR *)(const void FAR *)buf;
- buf4--;
while (len >= 32) {
DOBIG32;
len -= 32;
@@ -313,7 +327,6 @@ local unsigned long crc32_big(crc, buf, len)
DOBIG4;
len -= 4;
}
- buf4++;
buf = (const unsigned char FAR *)buf4;
if (len) do {
diff --git a/erts/emulator/zlib/deflate.c b/erts/emulator/zlib/deflate.c
index 943c26dfb2..1ec761448d 100644
--- a/erts/emulator/zlib/deflate.c
+++ b/erts/emulator/zlib/deflate.c
@@ -1,5 +1,5 @@
/* deflate.c -- compress data using the deflation algorithm
- * Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler
+ * Copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler
* For conditions of distribution and use, see copyright notice in zlib.h
*/
@@ -49,13 +49,10 @@
/* @(#) $Id$ */
-#ifdef HAVE_CONFIG_H
-# include "config.h"
-#endif
#include "deflate.h"
const char deflate_copyright[] =
- " deflate 1.2.8 Copyright 1995-2013 Jean-loup Gailly and Mark Adler ";
+ " deflate 1.2.11 Copyright 1995-2017 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
@@ -76,6 +73,8 @@ typedef enum {
typedef block_state (*compress_func) OF((deflate_state *s, int flush));
/* Compression function. Returns the block state after the call. */
+local int deflateStateCheck OF((z_streamp strm));
+local void slide_hash OF((deflate_state *s));
local void fill_window OF((deflate_state *s));
local block_state deflate_stored OF((deflate_state *s, int flush));
local block_state deflate_fast OF((deflate_state *s, int flush));
@@ -87,15 +86,16 @@ local block_state deflate_huff OF((deflate_state *s, int flush));
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 int read_buf OF((z_streamp strm, Bytef *buf, unsigned size));
+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 DEBUG
+#ifdef ZLIB_DEBUG
local void check_match OF((deflate_state *s, IPos start, IPos match,
int length));
#endif
@@ -151,21 +151,14 @@ local const config configuration_table[10] = {
* meaning.
*/
-#define EQUAL 0
-/* result of memcmp for equal strings */
-
-#ifndef NO_DUMMY_DECL
-struct static_tree_desc_s {int dummy;}; /* for buggy compilers */
-#endif
-
/* rank Z_BLOCK between Z_NO_FLUSH and Z_PARTIAL_FLUSH */
-#define RANK(f) (((f) << 1) - ((f) > 4 ? 9 : 0))
+#define RANK(f) (((f) * 2) - ((f) > 4 ? 9 : 0))
/* ===========================================================================
* Update a hash value with the given input byte
- * IN assertion: all calls to to UPDATE_HASH are made with consecutive
- * input characters, so that a running hash key can be computed from the
- * previous key instead of complete recalculation each time.
+ * IN assertion: all calls to UPDATE_HASH are made with consecutive input
+ * 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)
@@ -176,9 +169,9 @@ struct static_tree_desc_s {int dummy;}; /* for buggy compilers */
* the previous length of the hash chain.
* If this file is compiled with -DFASTEST, the compression level is forced
* to 1, and no hash chains are maintained.
- * IN assertion: all calls to to INSERT_STRING are made with consecutive
- * input characters and the first MIN_MATCH bytes of str are valid
- * (except for the last MIN_MATCH-1 bytes of the input file).
+ * IN assertion: all calls to INSERT_STRING are made with consecutive input
+ * characters and the first MIN_MATCH bytes of str are valid (except for
+ * the last MIN_MATCH-1 bytes of the input file).
*/
#ifdef FASTEST
#define INSERT_STRING(s, str, match_head) \
@@ -200,6 +193,37 @@ struct static_tree_desc_s {int dummy;}; /* for buggy compilers */
s->head[s->hash_size-1] = NIL; \
zmemzero((Bytef *)s->head, (unsigned)(s->hash_size-1)*sizeof(*s->head));
+/* ===========================================================================
+ * Slide the hash table when sliding the window down (could be avoided with 32
+ * bit values at the expense of memory usage). We slide even when level == 0 to
+ * keep the hash table consistent if we switch back to level > 0 later.
+ */
+local void slide_hash(s)
+ deflate_state *s;
+{
+ unsigned n, m;
+ Posf *p;
+ uInt wsize = s->w_size;
+
+ n = s->hash_size;
+ p = &s->head[n];
+ do {
+ m = *--p;
+ *p = (Pos)(m >= wsize ? m - wsize : NIL);
+ } while (--n);
+ n = wsize;
+#ifndef FASTEST
+ p = &s->prev[n];
+ do {
+ m = *--p;
+ *p = (Pos)(m >= wsize ? m - wsize : NIL);
+ /* If n is not on any hash chain, prev[n] is garbage but
+ * its value will never be used.
+ */
+ } while (--n);
+#endif
+}
+
/* ========================================================================= */
int ZEXPORT deflateInit_(strm, level, version, stream_size)
z_streamp strm;
@@ -273,7 +297,7 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy,
#endif
if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method != Z_DEFLATED ||
windowBits < 8 || windowBits > 15 || level < 0 || level > 9 ||
- strategy < 0 || strategy > Z_FIXED) {
+ strategy < 0 || strategy > Z_FIXED || (windowBits == 8 && wrap != 1)) {
return Z_STREAM_ERROR;
}
if (windowBits == 8) windowBits = 9; /* until 256-byte window bug fixed */
@@ -281,14 +305,15 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy,
if (s == Z_NULL) return Z_MEM_ERROR;
strm->state = (struct internal_state FAR *)s;
s->strm = strm;
+ s->status = INIT_STATE; /* to pass state test in deflateReset() */
s->wrap = wrap;
s->gzhead = Z_NULL;
- s->w_bits = windowBits;
+ s->w_bits = (uInt)windowBits;
s->w_size = 1 << s->w_bits;
s->w_mask = s->w_size - 1;
- s->hash_bits = memLevel + 7;
+ 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);
@@ -322,6 +347,31 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy,
return deflateReset(strm);
}
+/* =========================================================================
+ * Check for a valid deflate stream state. Return 0 if ok, 1 if not.
+ */
+local int deflateStateCheck (strm)
+ z_streamp strm;
+{
+ deflate_state *s;
+ if (strm == Z_NULL ||
+ strm->zalloc == (alloc_func)0 || strm->zfree == (free_func)0)
+ return 1;
+ s = strm->state;
+ if (s == Z_NULL || s->strm != strm || (s->status != INIT_STATE &&
+#ifdef GZIP
+ s->status != GZIP_STATE &&
+#endif
+ s->status != EXTRA_STATE &&
+ s->status != NAME_STATE &&
+ s->status != COMMENT_STATE &&
+ s->status != HCRC_STATE &&
+ s->status != BUSY_STATE &&
+ s->status != FINISH_STATE))
+ return 1;
+ return 0;
+}
+
/* ========================================================================= */
int ZEXPORT deflateSetDictionary (strm, dictionary, dictLength)
z_streamp strm;
@@ -334,7 +384,7 @@ int ZEXPORT deflateSetDictionary (strm, dictionary, dictLength)
unsigned avail;
z_const unsigned char *next;
- if (strm == Z_NULL || strm->state == Z_NULL || dictionary == Z_NULL)
+ if (deflateStateCheck(strm) || dictionary == Z_NULL)
return Z_STREAM_ERROR;
s = strm->state;
wrap = s->wrap;
@@ -392,13 +442,34 @@ int ZEXPORT deflateSetDictionary (strm, dictionary, dictLength)
}
/* ========================================================================= */
+int ZEXPORT deflateGetDictionary (strm, dictionary, dictLength)
+ z_streamp strm;
+ Bytef *dictionary;
+ uInt *dictLength;
+{
+ deflate_state *s;
+ uInt len;
+
+ if (deflateStateCheck(strm))
+ return Z_STREAM_ERROR;
+ s = strm->state;
+ len = s->strstart + s->lookahead;
+ if (len > s->w_size)
+ len = s->w_size;
+ if (dictionary != Z_NULL && len)
+ zmemcpy(dictionary, s->window + s->strstart + s->lookahead - len, len);
+ if (dictLength != Z_NULL)
+ *dictLength = len;
+ return Z_OK;
+}
+
+/* ========================================================================= */
int ZEXPORT deflateResetKeep (strm)
z_streamp strm;
{
deflate_state *s;
- if (strm == Z_NULL || strm->state == Z_NULL ||
- strm->zalloc == (alloc_func)0 || strm->zfree == (free_func)0) {
+ if (deflateStateCheck(strm)) {
return Z_STREAM_ERROR;
}
@@ -413,7 +484,11 @@ int ZEXPORT deflateResetKeep (strm)
if (s->wrap < 0) {
s->wrap = -s->wrap; /* was made negative by deflate(..., Z_FINISH); */
}
- s->status = s->wrap ? INIT_STATE : BUSY_STATE;
+ s->status =
+#ifdef GZIP
+ s->wrap == 2 ? GZIP_STATE :
+#endif
+ s->wrap ? INIT_STATE : BUSY_STATE;
strm->adler =
#ifdef GZIP
s->wrap == 2 ? crc32(0L, Z_NULL, 0) :
@@ -443,8 +518,8 @@ int ZEXPORT deflateSetHeader (strm, head)
z_streamp strm;
gz_headerp head;
{
- if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
- if (strm->state->wrap != 2) return Z_STREAM_ERROR;
+ if (deflateStateCheck(strm) || strm->state->wrap != 2)
+ return Z_STREAM_ERROR;
strm->state->gzhead = head;
return Z_OK;
}
@@ -455,7 +530,7 @@ int ZEXPORT deflatePending (strm, pending, bits)
int *bits;
z_streamp strm;
{
- if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ if (deflateStateCheck(strm)) return Z_STREAM_ERROR;
if (pending != Z_NULL)
*pending = strm->state->pending;
if (bits != Z_NULL)
@@ -472,7 +547,7 @@ int ZEXPORT deflatePrime (strm, bits, value)
deflate_state *s;
int put;
- if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ if (deflateStateCheck(strm)) return Z_STREAM_ERROR;
s = strm->state;
if ((Bytef *)(s->d_buf) < s->pending_out + ((Buf_size + 7) >> 3))
return Z_BUF_ERROR;
@@ -497,9 +572,8 @@ int ZEXPORT deflateParams(strm, level, strategy)
{
deflate_state *s;
compress_func func;
- int err = Z_OK;
- if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ if (deflateStateCheck(strm)) return Z_STREAM_ERROR;
s = strm->state;
#ifdef FASTEST
@@ -513,13 +587,22 @@ int ZEXPORT deflateParams(strm, level, strategy)
func = configuration_table[s->level].func;
if ((strategy != s->strategy || func != configuration_table[level].func) &&
- strm->total_in != 0) {
+ s->high_water) {
/* Flush the last buffer: */
- err = deflate(strm, Z_BLOCK);
- if (err == Z_BUF_ERROR && s->pending == 0)
- err = Z_OK;
+ int err = deflate(strm, Z_BLOCK);
+ if (err == Z_STREAM_ERROR)
+ return err;
+ if (strm->avail_out == 0)
+ return Z_BUF_ERROR;
}
if (s->level != level) {
+ if (s->level == 0 && s->matches != 0) {
+ if (s->matches == 1)
+ slide_hash(s);
+ else
+ CLEAR_HASH(s);
+ s->matches = 0;
+ }
s->level = level;
s->max_lazy_match = configuration_table[level].max_lazy;
s->good_match = configuration_table[level].good_length;
@@ -527,7 +610,7 @@ int ZEXPORT deflateParams(strm, level, strategy)
s->max_chain_length = configuration_table[level].max_chain;
}
s->strategy = strategy;
- return err;
+ return Z_OK;
}
/* ========================================================================= */
@@ -540,12 +623,12 @@ int ZEXPORT deflateTune(strm, good_length, max_lazy, nice_length, max_chain)
{
deflate_state *s;
- if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ if (deflateStateCheck(strm)) return Z_STREAM_ERROR;
s = strm->state;
- s->good_match = good_length;
- s->max_lazy_match = max_lazy;
+ s->good_match = (uInt)good_length;
+ s->max_lazy_match = (uInt)max_lazy;
s->nice_match = nice_length;
- s->max_chain_length = max_chain;
+ s->max_chain_length = (uInt)max_chain;
return Z_OK;
}
@@ -572,14 +655,13 @@ uLong ZEXPORT deflateBound(strm, sourceLen)
{
deflate_state *s;
uLong complen, wraplen;
- Bytef *str;
/* conservative upper bound for compressed data */
complen = sourceLen +
((sourceLen + 7) >> 3) + ((sourceLen + 63) >> 6) + 5;
/* if can't get parameters, return conservative bound plus zlib wrapper */
- if (strm == Z_NULL || strm->state == Z_NULL)
+ if (deflateStateCheck(strm))
return complen + 6;
/* compute wrapper length */
@@ -591,9 +673,11 @@ uLong ZEXPORT deflateBound(strm, sourceLen)
case 1: /* zlib wrapper */
wraplen = 6 + (s->strstart ? 4 : 0);
break;
+#ifdef GZIP
case 2: /* gzip wrapper */
wraplen = 18;
if (s->gzhead != Z_NULL) { /* user-supplied gzip header */
+ Bytef *str;
if (s->gzhead->extra != Z_NULL)
wraplen += 2 + s->gzhead->extra_len;
str = s->gzhead->name;
@@ -610,6 +694,7 @@ uLong ZEXPORT deflateBound(strm, sourceLen)
wraplen += 2;
}
break;
+#endif
default: /* for compiler happiness */
wraplen = 6;
}
@@ -637,10 +722,10 @@ local void putShortMSB (s, b)
}
/* =========================================================================
- * Flush as much pending output as possible. All deflate() output goes
- * through this function so some applications may wish to modify it
- * to avoid allocating a large strm->next_out buffer and copying into it.
- * (See also read_buf()).
+ * Flush as much pending output as possible. All deflate() output, except for
+ * some deflate_stored() output, goes through this function so some
+ * applications may wish to modify it to avoid allocating a large
+ * strm->next_out buffer and copying into it. (See also read_buf()).
*/
local void flush_pending(strm)
z_streamp strm;
@@ -657,13 +742,23 @@ local void flush_pending(strm)
strm->next_out += len;
s->pending_out += len;
strm->total_out += len;
- strm->avail_out -= len;
- s->pending -= len;
+ strm->avail_out -= len;
+ s->pending -= len;
if (s->pending == 0) {
s->pending_out = s->pending_buf;
}
}
+/* ===========================================================================
+ * Update the header CRC with the bytes s->pending_buf[beg..s->pending - 1].
+ */
+#define HCRC_UPDATE(beg) \
+ do { \
+ if (s->gzhead->hcrc && s->pending > (beg)) \
+ strm->adler = crc32(strm->adler, s->pending_buf + (beg), \
+ s->pending - (beg)); \
+ } while (0)
+
/* ========================================================================= */
int ZEXPORT deflate (strm, flush)
z_streamp strm;
@@ -672,230 +767,229 @@ int ZEXPORT deflate (strm, flush)
int old_flush; /* value of flush param for previous deflate call */
deflate_state *s;
- if (strm == Z_NULL || strm->state == Z_NULL ||
- flush > Z_BLOCK || flush < 0) {
+ if (deflateStateCheck(strm) || flush > Z_BLOCK || flush < 0) {
return Z_STREAM_ERROR;
}
s = strm->state;
if (strm->next_out == Z_NULL ||
- (strm->next_in == Z_NULL && strm->avail_in != 0) ||
+ (strm->avail_in != 0 && strm->next_in == Z_NULL) ||
(s->status == FINISH_STATE && flush != Z_FINISH)) {
ERR_RETURN(strm, Z_STREAM_ERROR);
}
if (strm->avail_out == 0) ERR_RETURN(strm, Z_BUF_ERROR);
- s->strm = strm; /* just in case */
old_flush = s->last_flush;
s->last_flush = flush;
+ /* Flush as much pending output as possible */
+ if (s->pending != 0) {
+ flush_pending(strm);
+ if (strm->avail_out == 0) {
+ /* Since avail_out is 0, deflate will be called again with
+ * more output space, but possibly with both pending and
+ * avail_in equal to zero. There won't be anything to do,
+ * but this is not an error situation so make sure we
+ * return OK instead of BUF_ERROR at next call of deflate:
+ */
+ s->last_flush = -1;
+ return Z_OK;
+ }
+
+ /* Make sure there is something to do and avoid duplicate consecutive
+ * flushes. For repeated and useless calls with Z_FINISH, we keep
+ * returning Z_STREAM_END instead of Z_BUF_ERROR.
+ */
+ } else if (strm->avail_in == 0 && RANK(flush) <= RANK(old_flush) &&
+ flush != Z_FINISH) {
+ ERR_RETURN(strm, Z_BUF_ERROR);
+ }
+
+ /* User must not provide more input after the first FINISH: */
+ if (s->status == FINISH_STATE && strm->avail_in != 0) {
+ ERR_RETURN(strm, Z_BUF_ERROR);
+ }
+
/* Write the header */
if (s->status == INIT_STATE) {
-#ifdef GZIP
- if (s->wrap == 2) {
- strm->adler = crc32(0L, Z_NULL, 0);
- put_byte(s, 31);
- put_byte(s, 139);
- put_byte(s, 8);
- if (s->gzhead == Z_NULL) {
- put_byte(s, 0);
- put_byte(s, 0);
- put_byte(s, 0);
- put_byte(s, 0);
- put_byte(s, 0);
- put_byte(s, s->level == 9 ? 2 :
- (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2 ?
- 4 : 0));
- put_byte(s, OS_CODE);
- s->status = BUSY_STATE;
- }
- else {
- put_byte(s, (s->gzhead->text ? 1 : 0) +
- (s->gzhead->hcrc ? 2 : 0) +
- (s->gzhead->extra == Z_NULL ? 0 : 4) +
- (s->gzhead->name == Z_NULL ? 0 : 8) +
- (s->gzhead->comment == Z_NULL ? 0 : 16)
- );
- put_byte(s, (Byte)(s->gzhead->time & 0xff));
- put_byte(s, (Byte)((s->gzhead->time >> 8) & 0xff));
- put_byte(s, (Byte)((s->gzhead->time >> 16) & 0xff));
- put_byte(s, (Byte)((s->gzhead->time >> 24) & 0xff));
- put_byte(s, s->level == 9 ? 2 :
- (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2 ?
- 4 : 0));
- put_byte(s, s->gzhead->os & 0xff);
- if (s->gzhead->extra != Z_NULL) {
- put_byte(s, s->gzhead->extra_len & 0xff);
- put_byte(s, (s->gzhead->extra_len >> 8) & 0xff);
- }
- if (s->gzhead->hcrc)
- strm->adler = crc32(strm->adler, s->pending_buf,
- s->pending);
- s->gzindex = 0;
- s->status = EXTRA_STATE;
- }
- }
+ /* zlib header */
+ uInt header = (Z_DEFLATED + ((s->w_bits-8)<<4)) << 8;
+ uInt level_flags;
+
+ if (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2)
+ level_flags = 0;
+ else if (s->level < 6)
+ level_flags = 1;
+ else if (s->level == 6)
+ level_flags = 2;
else
-#endif
- {
- uInt header = (Z_DEFLATED + ((s->w_bits-8)<<4)) << 8;
- uInt level_flags;
-
- if (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2)
- level_flags = 0;
- else if (s->level < 6)
- level_flags = 1;
- else if (s->level == 6)
- level_flags = 2;
- else
- level_flags = 3;
- header |= (level_flags << 6);
- if (s->strstart != 0) header |= PRESET_DICT;
- header += 31 - (header % 31);
+ level_flags = 3;
+ header |= (level_flags << 6);
+ if (s->strstart != 0) header |= PRESET_DICT;
+ header += 31 - (header % 31);
+
+ putShortMSB(s, header);
+
+ /* Save the adler32 of the preset dictionary: */
+ if (s->strstart != 0) {
+ putShortMSB(s, (uInt)(strm->adler >> 16));
+ putShortMSB(s, (uInt)(strm->adler & 0xffff));
+ }
+ strm->adler = adler32(0L, Z_NULL, 0);
+ s->status = BUSY_STATE;
+ /* Compression must start with an empty pending buffer */
+ flush_pending(strm);
+ if (s->pending != 0) {
+ s->last_flush = -1;
+ return Z_OK;
+ }
+ }
+#ifdef GZIP
+ if (s->status == GZIP_STATE) {
+ /* gzip header */
+ strm->adler = crc32(0L, Z_NULL, 0);
+ put_byte(s, 31);
+ put_byte(s, 139);
+ put_byte(s, 8);
+ if (s->gzhead == Z_NULL) {
+ put_byte(s, 0);
+ put_byte(s, 0);
+ put_byte(s, 0);
+ put_byte(s, 0);
+ put_byte(s, 0);
+ put_byte(s, s->level == 9 ? 2 :
+ (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2 ?
+ 4 : 0));
+ put_byte(s, OS_CODE);
s->status = BUSY_STATE;
- putShortMSB(s, header);
- /* Save the adler32 of the preset dictionary: */
- if (s->strstart != 0) {
- putShortMSB(s, (uInt)(strm->adler >> 16));
- putShortMSB(s, (uInt)(strm->adler & 0xffff));
+ /* Compression must start with an empty pending buffer */
+ flush_pending(strm);
+ if (s->pending != 0) {
+ s->last_flush = -1;
+ return Z_OK;
+ }
+ }
+ else {
+ put_byte(s, (s->gzhead->text ? 1 : 0) +
+ (s->gzhead->hcrc ? 2 : 0) +
+ (s->gzhead->extra == Z_NULL ? 0 : 4) +
+ (s->gzhead->name == Z_NULL ? 0 : 8) +
+ (s->gzhead->comment == Z_NULL ? 0 : 16)
+ );
+ put_byte(s, (Byte)(s->gzhead->time & 0xff));
+ put_byte(s, (Byte)((s->gzhead->time >> 8) & 0xff));
+ put_byte(s, (Byte)((s->gzhead->time >> 16) & 0xff));
+ put_byte(s, (Byte)((s->gzhead->time >> 24) & 0xff));
+ put_byte(s, s->level == 9 ? 2 :
+ (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2 ?
+ 4 : 0));
+ put_byte(s, s->gzhead->os & 0xff);
+ if (s->gzhead->extra != Z_NULL) {
+ put_byte(s, s->gzhead->extra_len & 0xff);
+ put_byte(s, (s->gzhead->extra_len >> 8) & 0xff);
}
- strm->adler = adler32(0L, Z_NULL, 0);
+ if (s->gzhead->hcrc)
+ strm->adler = crc32(strm->adler, s->pending_buf,
+ s->pending);
+ s->gzindex = 0;
+ s->status = EXTRA_STATE;
}
}
-#ifdef GZIP
if (s->status == EXTRA_STATE) {
if (s->gzhead->extra != Z_NULL) {
- uInt beg = s->pending; /* start of bytes to update crc */
-
- while (s->gzindex < (s->gzhead->extra_len & 0xffff)) {
- if (s->pending == s->pending_buf_size) {
- if (s->gzhead->hcrc && s->pending > beg)
- strm->adler = crc32(strm->adler, s->pending_buf + beg,
- s->pending - beg);
- flush_pending(strm);
- beg = s->pending;
- if (s->pending == s->pending_buf_size)
- break;
+ ulg beg = s->pending; /* start of bytes to update crc */
+ uInt left = (s->gzhead->extra_len & 0xffff) - s->gzindex;
+ while (s->pending + left > s->pending_buf_size) {
+ uInt copy = s->pending_buf_size - s->pending;
+ zmemcpy(s->pending_buf + s->pending,
+ s->gzhead->extra + s->gzindex, copy);
+ s->pending = s->pending_buf_size;
+ HCRC_UPDATE(beg);
+ s->gzindex += copy;
+ flush_pending(strm);
+ if (s->pending != 0) {
+ s->last_flush = -1;
+ return Z_OK;
}
- put_byte(s, s->gzhead->extra[s->gzindex]);
- s->gzindex++;
- }
- if (s->gzhead->hcrc && s->pending > beg)
- strm->adler = crc32(strm->adler, s->pending_buf + beg,
- s->pending - beg);
- if (s->gzindex == s->gzhead->extra_len) {
- s->gzindex = 0;
- s->status = NAME_STATE;
+ beg = 0;
+ left -= copy;
}
+ zmemcpy(s->pending_buf + s->pending,
+ s->gzhead->extra + s->gzindex, left);
+ s->pending += left;
+ HCRC_UPDATE(beg);
+ s->gzindex = 0;
}
- else
- s->status = NAME_STATE;
+ s->status = NAME_STATE;
}
if (s->status == NAME_STATE) {
if (s->gzhead->name != Z_NULL) {
- uInt beg = s->pending; /* start of bytes to update crc */
+ ulg beg = s->pending; /* start of bytes to update crc */
int val;
-
do {
if (s->pending == s->pending_buf_size) {
- if (s->gzhead->hcrc && s->pending > beg)
- strm->adler = crc32(strm->adler, s->pending_buf + beg,
- s->pending - beg);
+ HCRC_UPDATE(beg);
flush_pending(strm);
- beg = s->pending;
- if (s->pending == s->pending_buf_size) {
- val = 1;
- break;
+ if (s->pending != 0) {
+ s->last_flush = -1;
+ return Z_OK;
}
+ beg = 0;
}
val = s->gzhead->name[s->gzindex++];
put_byte(s, val);
} while (val != 0);
- if (s->gzhead->hcrc && s->pending > beg)
- strm->adler = crc32(strm->adler, s->pending_buf + beg,
- s->pending - beg);
- if (val == 0) {
- s->gzindex = 0;
- s->status = COMMENT_STATE;
- }
+ HCRC_UPDATE(beg);
+ s->gzindex = 0;
}
- else
- s->status = COMMENT_STATE;
+ s->status = COMMENT_STATE;
}
if (s->status == COMMENT_STATE) {
if (s->gzhead->comment != Z_NULL) {
- uInt beg = s->pending; /* start of bytes to update crc */
+ ulg beg = s->pending; /* start of bytes to update crc */
int val;
-
do {
if (s->pending == s->pending_buf_size) {
- if (s->gzhead->hcrc && s->pending > beg)
- strm->adler = crc32(strm->adler, s->pending_buf + beg,
- s->pending - beg);
+ HCRC_UPDATE(beg);
flush_pending(strm);
- beg = s->pending;
- if (s->pending == s->pending_buf_size) {
- val = 1;
- break;
+ if (s->pending != 0) {
+ s->last_flush = -1;
+ return Z_OK;
}
+ beg = 0;
}
val = s->gzhead->comment[s->gzindex++];
put_byte(s, val);
} while (val != 0);
- if (s->gzhead->hcrc && s->pending > beg)
- strm->adler = crc32(strm->adler, s->pending_buf + beg,
- s->pending - beg);
- if (val == 0)
- s->status = HCRC_STATE;
+ HCRC_UPDATE(beg);
}
- else
- s->status = HCRC_STATE;
+ s->status = HCRC_STATE;
}
if (s->status == HCRC_STATE) {
if (s->gzhead->hcrc) {
- if (s->pending + 2 > s->pending_buf_size)
+ if (s->pending + 2 > s->pending_buf_size) {
flush_pending(strm);
- if (s->pending + 2 <= s->pending_buf_size) {
- put_byte(s, (Byte)(strm->adler & 0xff));
- put_byte(s, (Byte)((strm->adler >> 8) & 0xff));
- strm->adler = crc32(0L, Z_NULL, 0);
- s->status = BUSY_STATE;
+ if (s->pending != 0) {
+ s->last_flush = -1;
+ return Z_OK;
+ }
}
+ put_byte(s, (Byte)(strm->adler & 0xff));
+ put_byte(s, (Byte)((strm->adler >> 8) & 0xff));
+ strm->adler = crc32(0L, Z_NULL, 0);
}
- else
- s->status = BUSY_STATE;
- }
-#endif
+ s->status = BUSY_STATE;
- /* Flush as much pending output as possible */
- if (s->pending != 0) {
+ /* Compression must start with an empty pending buffer */
flush_pending(strm);
- if (strm->avail_out == 0) {
- /* Since avail_out is 0, deflate will be called again with
- * more output space, but possibly with both pending and
- * avail_in equal to zero. There won't be anything to do,
- * but this is not an error situation so make sure we
- * return OK instead of BUF_ERROR at next call of deflate:
- */
+ if (s->pending != 0) {
s->last_flush = -1;
return Z_OK;
}
-
- /* Make sure there is something to do and avoid duplicate consecutive
- * flushes. For repeated and useless calls with Z_FINISH, we keep
- * returning Z_STREAM_END instead of Z_BUF_ERROR.
- */
- } else if (strm->avail_in == 0 && RANK(flush) <= RANK(old_flush) &&
- flush != Z_FINISH) {
- ERR_RETURN(strm, Z_BUF_ERROR);
- }
-
- /* User must not provide more input after the first FINISH: */
- if (s->status == FINISH_STATE && strm->avail_in != 0) {
- ERR_RETURN(strm, Z_BUF_ERROR);
}
+#endif
/* Start a new block or continue the current one.
*/
@@ -903,9 +997,10 @@ int ZEXPORT deflate (strm, flush)
(flush != Z_NO_FLUSH && s->status != FINISH_STATE)) {
block_state bstate;
- bstate = s->strategy == Z_HUFFMAN_ONLY ? deflate_huff(s, flush) :
- (s->strategy == Z_RLE ? deflate_rle(s, flush) :
- (*(configuration_table[s->level].func))(s, flush));
+ bstate = s->level == 0 ? deflate_stored(s, flush) :
+ s->strategy == Z_HUFFMAN_ONLY ? deflate_huff(s, flush) :
+ s->strategy == Z_RLE ? deflate_rle(s, flush) :
+ (*(configuration_table[s->level].func))(s, flush);
if (bstate == finish_started || bstate == finish_done) {
s->status = FINISH_STATE;
@@ -947,7 +1042,6 @@ int ZEXPORT deflate (strm, flush)
}
}
}
- Assert(strm->avail_out > 0, "bug2");
if (flush != Z_FINISH) return Z_OK;
if (s->wrap <= 0) return Z_STREAM_END;
@@ -984,18 +1078,9 @@ int ZEXPORT deflateEnd (strm)
{
int status;
- if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ if (deflateStateCheck(strm)) return Z_STREAM_ERROR;
status = strm->state->status;
- if (status != INIT_STATE &&
- status != EXTRA_STATE &&
- status != NAME_STATE &&
- status != COMMENT_STATE &&
- status != HCRC_STATE &&
- status != BUSY_STATE &&
- status != FINISH_STATE) {
- return Z_STREAM_ERROR;
- }
/* Deallocate in reverse order of allocations: */
TRY_FREE(strm, strm->state->pending_buf);
@@ -1026,7 +1111,7 @@ int ZEXPORT deflateCopy (dest, source)
ushf *overlay;
- if (source == Z_NULL || dest == Z_NULL || source->state == Z_NULL) {
+ if (deflateStateCheck(source) || dest == Z_NULL) {
return Z_STREAM_ERROR;
}
@@ -1076,7 +1161,7 @@ int ZEXPORT deflateCopy (dest, source)
* allocating a large strm->next_in buffer and copying from it.
* (See also flush_pending()).
*/
-local int read_buf(strm, buf, size)
+local unsigned read_buf(strm, buf, size)
z_streamp strm;
Bytef *buf;
unsigned size;
@@ -1100,7 +1185,7 @@ local int read_buf(strm, buf, size)
strm->next_in += len;
strm->total_in += len;
- return (int)len;
+ return len;
}
/* ===========================================================================
@@ -1154,9 +1239,9 @@ local uInt longest_match(s, cur_match)
{
unsigned chain_length = s->max_chain_length;/* max hash chain length */
register Bytef *scan = s->window + s->strstart; /* current string */
- register Bytef *match; /* matched string */
+ register Bytef *match; /* matched string */
register int len; /* length of current match */
- int best_len = s->prev_length; /* best match length so far */
+ int best_len = (int)s->prev_length; /* best match length so far */
int nice_match = s->nice_match; /* stop if match long enough */
IPos limit = s->strstart > (IPos)MAX_DIST(s) ?
s->strstart - (IPos)MAX_DIST(s) : NIL;
@@ -1191,7 +1276,7 @@ local uInt longest_match(s, cur_match)
/* Do not look for matches beyond the end of the input. This is necessary
* to make deflate deterministic.
*/
- if ((uInt)nice_match > s->lookahead) nice_match = s->lookahead;
+ if ((uInt)nice_match > s->lookahead) nice_match = (int)s->lookahead;
Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead");
@@ -1352,7 +1437,11 @@ local uInt longest_match(s, cur_match)
#endif /* FASTEST */
-#ifdef DEBUG
+#ifdef ZLIB_DEBUG
+
+#define EQUAL 0
+/* result of memcmp for equal strings */
+
/* ===========================================================================
* Check that the match at match_start is indeed a match.
*/
@@ -1378,7 +1467,7 @@ local void check_match(s, start, match, length)
}
#else
# define check_match(s, start, match, length)
-#endif /* DEBUG */
+#endif /* ZLIB_DEBUG */
/* ===========================================================================
* Fill the window when the lookahead becomes insufficient.
@@ -1393,8 +1482,7 @@ local void check_match(s, start, match, length)
local void fill_window(s)
deflate_state *s;
{
- register unsigned n, m;
- register Posf *p;
+ unsigned n;
unsigned more; /* Amount of free space at the end of the window. */
uInt wsize = s->w_size;
@@ -1421,35 +1509,11 @@ local void fill_window(s)
*/
if (s->strstart >= wsize+MAX_DIST(s)) {
- zmemcpy(s->window, s->window+wsize, (unsigned)wsize);
+ 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;
-
- /* Slide the hash table (could be avoided with 32 bit values
- at the expense of memory usage). We slide even when level == 0
- to keep the hash table consistent if we switch back to level > 0
- later. (Using level 0 permanently is not an optimal usage of
- zlib, so we don't care about this pathological case.)
- */
- n = s->hash_size;
- p = &s->head[n];
- do {
- m = *--p;
- *p = (Pos)(m >= wsize ? m-wsize : NIL);
- } while (--n);
-
- n = wsize;
-#ifndef FASTEST
- p = &s->prev[n];
- do {
- m = *--p;
- *p = (Pos)(m >= wsize ? m-wsize : NIL);
- /* If n is not on any hash chain, prev[n] is garbage but
- * its value will never be used.
- */
- } while (--n);
-#endif
+ slide_hash(s);
more += wsize;
}
if (s->strm->avail_in == 0) break;
@@ -1555,70 +1619,199 @@ local void fill_window(s)
if (s->strm->avail_out == 0) return (last) ? finish_started : need_more; \
}
+/* Maximum stored block length in deflate format (not including header). */
+#define MAX_STORED 65535
+
+/* Minimum of a and b. */
+#define MIN(a, b) ((a) > (b) ? (b) : (a))
+
/* ===========================================================================
* Copy without compression as much as possible from the input stream, return
* the current block state.
- * This function does not insert new strings in the dictionary since
- * uncompressible data is probably not useful. This function is used
- * only for the level=0 compression option.
- * NOTE: this function should be optimized to avoid extra copying from
- * window to pending_buf.
+ *
+ * In case deflateParams() is used to later switch to a non-zero compression
+ * level, s->matches (otherwise unused when storing) keeps track of the number
+ * of hash table slides to perform. If s->matches is 1, then one hash table
+ * slide will be done when switching. If s->matches is 2, the maximum value
+ * allowed here, then the hash table will be cleared, since two or more slides
+ * is the same as a clear.
+ *
+ * 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.
*/
local block_state deflate_stored(s, flush)
deflate_state *s;
int flush;
{
- /* Stored blocks are limited to 0xffff bytes, pending_buf is limited
- * to pending_buf_size, and each stored block has a 5 byte header:
+ /* Smallest worthy block size when not flushing or finishing. By default
+ * this is 32K. This can be as small as 507 bytes for memLevel == 1. For
+ * large input and output buffers, the stored block size will be larger.
*/
- ulg max_block_size = 0xffff;
- ulg max_start;
-
- if (max_block_size > s->pending_buf_size - 5) {
- max_block_size = s->pending_buf_size - 5;
- }
-
- /* Copy as much as possible from input to output: */
- for (;;) {
- /* Fill the window as much as possible: */
- if (s->lookahead <= 1) {
+ unsigned min_block = MIN(s->pending_buf_size - 5, s->w_size);
- Assert(s->strstart < s->w_size+MAX_DIST(s) ||
- s->block_start >= (long)s->w_size, "slide too late");
+ /* Copy as many min_block or larger stored blocks directly to next_out as
+ * possible. If flushing, copy the remaining available input to next_out as
+ * stored blocks, if there is enough space.
+ */
+ unsigned len, left, have, last = 0;
+ unsigned used = s->strm->avail_in;
+ do {
+ /* Set len to the maximum size block that we can copy directly with the
+ * available input data and output space. Set left to how much of that
+ * would be copied from what's left in the window.
+ */
+ len = MAX_STORED; /* maximum deflate stored block length */
+ have = (s->bi_valid + 42) >> 3; /* number of header bytes */
+ if (s->strm->avail_out < have) /* need room for header */
+ break;
+ /* maximum stored block length that will fit in avail_out: */
+ have = s->strm->avail_out - have;
+ left = s->strstart - s->block_start; /* bytes left in window */
+ if (len > (ulg)left + s->strm->avail_in)
+ len = left + s->strm->avail_in; /* limit len to the input */
+ if (len > have)
+ len = have; /* limit len to the output */
+
+ /* If the stored block would be less than min_block in length, or if
+ * unable to copy all of the available input when flushing, then try
+ * copying to the window and the pending buffer instead. Also don't
+ * write an empty block when flushing -- deflate() does that.
+ */
+ if (len < min_block && ((len == 0 && flush != Z_FINISH) ||
+ flush == Z_NO_FLUSH ||
+ len != left + s->strm->avail_in))
+ break;
- fill_window(s);
- if (s->lookahead == 0 && flush == Z_NO_FLUSH) return need_more;
+ /* Make a dummy stored block in pending to get the header bytes,
+ * including any pending bits. This also updates the debugging counts.
+ */
+ last = flush == Z_FINISH && len == left + s->strm->avail_in ? 1 : 0;
+ _tr_stored_block(s, (char *)0, 0L, last);
+
+ /* Replace the lengths in the dummy stored block with len. */
+ s->pending_buf[s->pending - 4] = len;
+ s->pending_buf[s->pending - 3] = len >> 8;
+ s->pending_buf[s->pending - 2] = ~len;
+ s->pending_buf[s->pending - 1] = ~len >> 8;
+
+ /* Write the stored block header bytes. */
+ flush_pending(s->strm);
+
+#ifdef ZLIB_DEBUG
+ /* Update debugging counts for the data about to be copied. */
+ s->compressed_len += len << 3;
+ s->bits_sent += len << 3;
+#endif
- if (s->lookahead == 0) break; /* flush the current block */
+ /* Copy uncompressed bytes from the window to next_out. */
+ if (left) {
+ if (left > len)
+ left = len;
+ zmemcpy(s->strm->next_out, s->window + s->block_start, left);
+ s->strm->next_out += left;
+ s->strm->avail_out -= left;
+ s->strm->total_out += left;
+ s->block_start += left;
+ len -= left;
}
- Assert(s->block_start >= 0L, "block gone");
-
- s->strstart += s->lookahead;
- s->lookahead = 0;
-
- /* Emit a stored block if pending_buf will be full: */
- max_start = s->block_start + max_block_size;
- if (s->strstart == 0 || (ulg)s->strstart >= max_start) {
- /* strstart == 0 is possible when wraparound on 16-bit machine */
- s->lookahead = (uInt)(s->strstart - max_start);
- s->strstart = (uInt)max_start;
- FLUSH_BLOCK(s, 0);
+
+ /* Copy uncompressed bytes directly from next_in to next_out, updating
+ * the check value.
+ */
+ if (len) {
+ read_buf(s->strm, s->strm->next_out, len);
+ s->strm->next_out += len;
+ s->strm->avail_out -= len;
+ s->strm->total_out += len;
}
- /* Flush if we may have to slide, otherwise block_start may become
- * negative and the data will be gone:
+ } while (last == 0);
+
+ /* Update the sliding window with the last s->w_size bytes of the copied
+ * data, or append all of the copied data to the existing window if less
+ * than s->w_size bytes were copied. Also update the number of bytes to
+ * insert in the hash tables, in the event that deflateParams() switches to
+ * a non-zero compression level.
+ */
+ used -= s->strm->avail_in; /* number of input bytes directly copied */
+ if (used) {
+ /* If any input was used, then no unused input remains in the window,
+ * therefore s->block_start == s->strstart.
*/
- if (s->strstart - (uInt)s->block_start >= MAX_DIST(s)) {
- FLUSH_BLOCK(s, 0);
+ if (used >= s->w_size) { /* supplant the previous history */
+ s->matches = 2; /* clear hash */
+ zmemcpy(s->window, s->strm->next_in - s->w_size, s->w_size);
+ s->strstart = s->w_size;
}
+ else {
+ if (s->window_size - s->strstart <= used) {
+ /* Slide the window down. */
+ s->strstart -= s->w_size;
+ zmemcpy(s->window, s->window + s->w_size, s->strstart);
+ if (s->matches < 2)
+ s->matches++; /* add a pending slide_hash() */
+ }
+ zmemcpy(s->window + s->strstart, s->strm->next_in - used, used);
+ s->strstart += used;
+ }
+ s->block_start = s->strstart;
+ s->insert += MIN(used, s->w_size - s->insert);
}
- s->insert = 0;
- if (flush == Z_FINISH) {
- FLUSH_BLOCK(s, 1);
+ if (s->high_water < s->strstart)
+ s->high_water = s->strstart;
+
+ /* If the last block was written to next_out, then done. */
+ if (last)
return finish_done;
+
+ /* If flushing and all input has been consumed, then done. */
+ if (flush != Z_NO_FLUSH && flush != Z_FINISH &&
+ s->strm->avail_in == 0 && (long)s->strstart == s->block_start)
+ return block_done;
+
+ /* Fill the window with any remaining input. */
+ have = s->window_size - s->strstart - 1;
+ if (s->strm->avail_in > have && s->block_start >= (long)s->w_size) {
+ /* Slide the window down. */
+ s->block_start -= s->w_size;
+ s->strstart -= s->w_size;
+ zmemcpy(s->window, s->window + s->w_size, s->strstart);
+ if (s->matches < 2)
+ s->matches++; /* add a pending slide_hash() */
+ have += s->w_size; /* more space now */
}
- if ((long)s->strstart > s->block_start)
- FLUSH_BLOCK(s, 0);
- return block_done;
+ if (have > s->strm->avail_in)
+ have = s->strm->avail_in;
+ if (have) {
+ read_buf(s->strm, s->window + s->strstart, have);
+ s->strstart += have;
+ }
+ if (s->high_water < s->strstart)
+ s->high_water = s->strstart;
+
+ /* There was not enough avail_out to write a complete worthy or flushed
+ * stored block to next_out. Write a stored block to pending instead, if we
+ * have enough input for a worthy block, or if flushing and there is enough
+ * room for the remaining input as a stored block in the pending buffer.
+ */
+ have = (s->bi_valid + 42) >> 3; /* number of header bytes */
+ /* maximum stored block length that will fit in pending: */
+ have = MIN(s->pending_buf_size - have, MAX_STORED);
+ min_block = MIN(have, s->w_size);
+ left = s->strstart - s->block_start;
+ if (left >= min_block ||
+ ((left || flush == Z_FINISH) && flush != Z_NO_FLUSH &&
+ s->strm->avail_in == 0 && left <= have)) {
+ len = MIN(left, have);
+ last = flush == Z_FINISH && s->strm->avail_in == 0 &&
+ len == left ? 1 : 0;
+ _tr_stored_block(s, (charf *)s->window + s->block_start, len, last);
+ s->block_start += len;
+ flush_pending(s->strm);
+ }
+
+ /* We've done all we can with the available input and output. */
+ return last ? finish_started : need_more;
}
/* ===========================================================================
@@ -1895,7 +2088,7 @@ local block_state deflate_rle(s, flush)
prev == *++scan && prev == *++scan &&
prev == *++scan && prev == *++scan &&
scan < strend);
- s->match_length = MAX_MATCH - (int)(strend - scan);
+ s->match_length = MAX_MATCH - (uInt)(strend - scan);
if (s->match_length > s->lookahead)
s->match_length = s->lookahead;
}
diff --git a/erts/emulator/zlib/deflate.h b/erts/emulator/zlib/deflate.h
index ce0299edd1..23ecdd312b 100644
--- a/erts/emulator/zlib/deflate.h
+++ b/erts/emulator/zlib/deflate.h
@@ -1,5 +1,5 @@
/* deflate.h -- internal compression state
- * Copyright (C) 1995-2012 Jean-loup Gailly
+ * Copyright (C) 1995-2016 Jean-loup Gailly
* For conditions of distribution and use, see copyright notice in zlib.h
*/
@@ -51,13 +51,16 @@
#define Buf_size 16
/* size of bit buffer in bi_buf */
-#define INIT_STATE 42
-#define EXTRA_STATE 69
-#define NAME_STATE 73
-#define COMMENT_STATE 91
-#define HCRC_STATE 103
-#define BUSY_STATE 113
-#define FINISH_STATE 666
+#define INIT_STATE 42 /* zlib header -> BUSY_STATE */
+#ifdef GZIP
+# define GZIP_STATE 57 /* gzip header -> BUSY_STATE | EXTRA_STATE */
+#endif
+#define EXTRA_STATE 69 /* gzip extra block -> NAME_STATE */
+#define NAME_STATE 73 /* gzip file name -> COMMENT_STATE */
+#define COMMENT_STATE 91 /* gzip comment -> HCRC_STATE */
+#define HCRC_STATE 103 /* gzip header CRC -> BUSY_STATE */
+#define BUSY_STATE 113 /* deflate -> FINISH_STATE */
+#define FINISH_STATE 666 /* stream complete */
/* Stream status */
@@ -83,7 +86,7 @@ typedef struct static_tree_desc_s static_tree_desc;
typedef struct tree_desc_s {
ct_data *dyn_tree; /* the dynamic tree */
int max_code; /* largest code with non zero frequency */
- static_tree_desc *stat_desc; /* the corresponding static tree */
+ const static_tree_desc *stat_desc; /* the corresponding static tree */
} FAR tree_desc;
typedef ush Pos;
@@ -100,10 +103,10 @@ typedef struct internal_state {
Bytef *pending_buf; /* output still pending */
ulg pending_buf_size; /* size of pending_buf */
Bytef *pending_out; /* next pending byte to output to the stream */
- uInt pending; /* nb of bytes in the pending buffer */
+ ulg pending; /* nb of bytes in the pending buffer */
int wrap; /* bit 0 true for zlib, bit 1 true for gzip */
gz_headerp gzhead; /* gzip header information to write */
- uInt gzindex; /* where in extra, name, or comment */
+ ulg gzindex; /* where in extra, name, or comment */
Byte method; /* can only be DEFLATED */
int last_flush; /* value of flush param for previous deflate call */
@@ -249,7 +252,7 @@ typedef struct internal_state {
uInt matches; /* number of string matches in current block */
uInt insert; /* bytes at end of window left to insert */
-#ifdef DEBUG
+#ifdef ZLIB_DEBUG
ulg compressed_len; /* total bit length of compressed file mod 2^32 */
ulg bits_sent; /* bit length of compressed data sent mod 2^32 */
#endif
@@ -275,7 +278,7 @@ typedef struct internal_state {
/* Output a byte on the stream.
* IN assertion: there is enough room in pending_buf.
*/
-#define put_byte(s, c) {s->pending_buf[s->pending++] = (c);}
+#define put_byte(s, c) {s->pending_buf[s->pending++] = (Bytef)(c);}
#define MIN_LOOKAHEAD (MAX_MATCH+MIN_MATCH+1)
@@ -309,7 +312,7 @@ void ZLIB_INTERNAL _tr_stored_block OF((deflate_state *s, charf *buf,
* used.
*/
-#ifndef DEBUG
+#ifndef ZLIB_DEBUG
/* Inline versions of _tr_tally for speed: */
#if defined(GEN_TREES_H) || !defined(STDC)
@@ -328,8 +331,8 @@ void ZLIB_INTERNAL _tr_stored_block OF((deflate_state *s, charf *buf,
flush = (s->last_lit == s->lit_bufsize-1); \
}
# define _tr_tally_dist(s, distance, length, flush) \
- { uch len = (length); \
- ush dist = (distance); \
+ { uch len = (uch)(length); \
+ ush dist = (ush)(distance); \
s->d_buf[s->last_lit] = dist; \
s->l_buf[s->last_lit++] = len; \
dist--; \
diff --git a/erts/emulator/zlib/gzguts.h b/erts/emulator/zlib/gzguts.h
index d87659d031..990a4d2514 100644
--- a/erts/emulator/zlib/gzguts.h
+++ b/erts/emulator/zlib/gzguts.h
@@ -1,5 +1,5 @@
/* gzguts.h -- zlib internal header definitions for gz* operations
- * Copyright (C) 2004, 2005, 2010, 2011, 2012, 2013 Mark Adler
+ * Copyright (C) 2004, 2005, 2010, 2011, 2012, 2013, 2016 Mark Adler
* For conditions of distribution and use, see copyright notice in zlib.h
*/
@@ -25,6 +25,10 @@
# include <stdlib.h>
# include <limits.h>
#endif
+
+#ifndef _POSIX_SOURCE
+# define _POSIX_SOURCE
+#endif
#include <fcntl.h>
#ifdef _WIN32
@@ -35,6 +39,10 @@
# include <io.h>
#endif
+#if defined(_WIN32) || defined(__CYGWIN__)
+# define WIDECHAR
+#endif
+
#ifdef WINAPI_FAMILY
# define open _open
# define read _read
@@ -95,18 +103,19 @@
# endif
#endif
-/* unlike snprintf (which is required in C99, yet still not supported by
- Microsoft more than a decade later!), _snprintf does not guarantee null
- termination of the result -- however this is only used in gzlib.c where
+/* unlike snprintf (which is required in C99), _snprintf does not guarantee
+ null termination of the result -- however this is only used in gzlib.c where
the result is assured to fit in the space provided */
-#ifdef _MSC_VER
+#if defined(_MSC_VER) && _MSC_VER < 1900
# define snprintf _snprintf
#endif
#ifndef local
# define local static
#endif
-/* compile with -Dlocal if your debugger can't find static symbols */
+/* since "static" is used to mean two completely different things in C, we
+ define "local" for the non-static meaning of "static", for readability
+ (compile with -Dlocal if your debugger can't find static symbols) */
/* gz* functions always use library allocation functions */
#ifndef STDC
@@ -170,7 +179,7 @@ typedef struct {
char *path; /* path or fd for error messages */
unsigned size; /* buffer size, zero if not allocated yet */
unsigned want; /* requested buffer size, default is GZBUFSIZE */
- unsigned char *in; /* input buffer */
+ unsigned char *in; /* input buffer (double-sized when writing) */
unsigned char *out; /* output buffer (double-sized when reading) */
int direct; /* 0 if processing gzip, 1 if transparent */
/* just for reading */
diff --git a/erts/emulator/zlib/inffast.c b/erts/emulator/zlib/inffast.c
index 5187743fde..0dbd1dbc09 100644
--- a/erts/emulator/zlib/inffast.c
+++ b/erts/emulator/zlib/inffast.c
@@ -1,36 +1,16 @@
/* inffast.c -- fast decoding
- * Copyright (C) 1995-2008, 2010, 2013 Mark Adler
+ * Copyright (C) 1995-2017 Mark Adler
* For conditions of distribution and use, see copyright notice in zlib.h
*/
-#ifdef HAVE_CONFIG_H
-# include "config.h"
-#endif
#include "zutil.h"
#include "inftrees.h"
#include "inflate.h"
#include "inffast.h"
-#ifndef ASMINF
-
-/* Allow machine dependent optimization for post-increment or pre-increment.
- Based on testing to date,
- Pre-increment preferred for:
- - PowerPC G3 (Adler)
- - MIPS R5000 (Randers-Pehrson)
- Post-increment preferred for:
- - none
- No measurable difference:
- - Pentium III (Anderson)
- - M68060 (Nikl)
- */
-#ifdef POSTINC
-# define OFF 0
-# define PUP(a) *(a)++
+#ifdef ASMINF
+# pragma message("Assembler code may have bugs -- use at your own risk")
#else
-# define OFF 1
-# define PUP(a) *++(a)
-#endif
/*
Decode literal, length, and distance codes and write out the resulting
@@ -99,9 +79,9 @@ unsigned start; /* inflate()'s starting value for strm->avail_out */
/* copy state to local variables */
state = (struct inflate_state FAR *)strm->state;
- in = strm->next_in - OFF;
+ in = strm->next_in;
last = in + (strm->avail_in - 5);
- out = strm->next_out - OFF;
+ out = strm->next_out;
beg = out - (start - strm->avail_out);
end = out + (strm->avail_out - 257);
#ifdef INFLATE_STRICT
@@ -122,9 +102,9 @@ unsigned start; /* inflate()'s starting value for strm->avail_out */
input data or output space */
do {
if (bits < 15) {
- hold += (unsigned long)(PUP(in)) << bits;
+ hold += (unsigned long)(*in++) << bits;
bits += 8;
- hold += (unsigned long)(PUP(in)) << bits;
+ hold += (unsigned long)(*in++) << bits;
bits += 8;
}
here = lcode[hold & lmask];
@@ -137,14 +117,14 @@ unsigned start; /* inflate()'s starting value for strm->avail_out */
Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
"inflate: literal '%c'\n" :
"inflate: literal 0x%02x\n", here.val));
- PUP(out) = (unsigned char)(here.val);
+ *out++ = (unsigned char)(here.val);
}
else if (op & 16) { /* length base */
len = (unsigned)(here.val);
op &= 15; /* number of extra bits */
if (op) {
if (bits < op) {
- hold += (unsigned long)(PUP(in)) << bits;
+ hold += (unsigned long)(*in++) << bits;
bits += 8;
}
len += (unsigned)hold & ((1U << op) - 1);
@@ -153,9 +133,9 @@ unsigned start; /* inflate()'s starting value for strm->avail_out */
}
Tracevv((stderr, "inflate: length %u\n", len));
if (bits < 15) {
- hold += (unsigned long)(PUP(in)) << bits;
+ hold += (unsigned long)(*in++) << bits;
bits += 8;
- hold += (unsigned long)(PUP(in)) << bits;
+ hold += (unsigned long)(*in++) << bits;
bits += 8;
}
here = dcode[hold & dmask];
@@ -168,10 +148,10 @@ unsigned start; /* inflate()'s starting value for strm->avail_out */
dist = (unsigned)(here.val);
op &= 15; /* number of extra bits */
if (bits < op) {
- hold += (unsigned long)(PUP(in)) << bits;
+ hold += (unsigned long)(*in++) << bits;
bits += 8;
if (bits < op) {
- hold += (unsigned long)(PUP(in)) << bits;
+ hold += (unsigned long)(*in++) << bits;
bits += 8;
}
}
@@ -199,30 +179,30 @@ unsigned start; /* inflate()'s starting value for strm->avail_out */
#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
if (len <= op - whave) {
do {
- PUP(out) = 0;
+ *out++ = 0;
} while (--len);
continue;
}
len -= op - whave;
do {
- PUP(out) = 0;
+ *out++ = 0;
} while (--op > whave);
if (op == 0) {
from = out - dist;
do {
- PUP(out) = PUP(from);
+ *out++ = *from++;
} while (--len);
continue;
}
#endif
}
- from = window - OFF;
+ from = window;
if (wnext == 0) { /* very common case */
from += wsize - op;
if (op < len) { /* some from window */
len -= op;
do {
- PUP(out) = PUP(from);
+ *out++ = *from++;
} while (--op);
from = out - dist; /* rest from output */
}
@@ -233,14 +213,14 @@ unsigned start; /* inflate()'s starting value for strm->avail_out */
if (op < len) { /* some from end of window */
len -= op;
do {
- PUP(out) = PUP(from);
+ *out++ = *from++;
} while (--op);
- from = window - OFF;
+ from = window;
if (wnext < len) { /* some from start of window */
op = wnext;
len -= op;
do {
- PUP(out) = PUP(from);
+ *out++ = *from++;
} while (--op);
from = out - dist; /* rest from output */
}
@@ -251,35 +231,35 @@ unsigned start; /* inflate()'s starting value for strm->avail_out */
if (op < len) { /* some from window */
len -= op;
do {
- PUP(out) = PUP(from);
+ *out++ = *from++;
} while (--op);
from = out - dist; /* rest from output */
}
}
while (len > 2) {
- PUP(out) = PUP(from);
- PUP(out) = PUP(from);
- PUP(out) = PUP(from);
+ *out++ = *from++;
+ *out++ = *from++;
+ *out++ = *from++;
len -= 3;
}
if (len) {
- PUP(out) = PUP(from);
+ *out++ = *from++;
if (len > 1)
- PUP(out) = PUP(from);
+ *out++ = *from++;
}
}
else {
from = out - dist; /* copy direct from output */
do { /* minimum length is three */
- PUP(out) = PUP(from);
- PUP(out) = PUP(from);
- PUP(out) = PUP(from);
+ *out++ = *from++;
+ *out++ = *from++;
+ *out++ = *from++;
len -= 3;
} while (len > 2);
if (len) {
- PUP(out) = PUP(from);
+ *out++ = *from++;
if (len > 1)
- PUP(out) = PUP(from);
+ *out++ = *from++;
}
}
}
@@ -316,8 +296,8 @@ unsigned start; /* inflate()'s starting value for strm->avail_out */
hold &= (1U << bits) - 1;
/* update state and return */
- strm->next_in = in + OFF;
- strm->next_out = out + OFF;
+ strm->next_in = in;
+ strm->next_out = out;
strm->avail_in = (unsigned)(in < last ? 5 + (last - in) : 5 - (in - last));
strm->avail_out = (unsigned)(out < end ?
257 + (end - out) : 257 - (out - end));
diff --git a/erts/emulator/zlib/inflate.c b/erts/emulator/zlib/inflate.c
index 532330b06b..ac333e8c2e 100644
--- a/erts/emulator/zlib/inflate.c
+++ b/erts/emulator/zlib/inflate.c
@@ -1,5 +1,5 @@
/* inflate.c -- zlib decompression
- * Copyright (C) 1995-2012 Mark Adler
+ * Copyright (C) 1995-2016 Mark Adler
* For conditions of distribution and use, see copyright notice in zlib.h
*/
@@ -80,9 +80,6 @@
* The history for versions after 1.2.0 are in ChangeLog in zlib distribution.
*/
-#ifdef HAVE_CONFIG_H
-# include "config.h"
-#endif
#include "zutil.h"
#include "inftrees.h"
#include "inflate.h"
@@ -95,6 +92,7 @@
#endif
/* function prototypes */
+local int inflateStateCheck OF((z_streamp strm));
local void fixedtables OF((struct inflate_state FAR *state));
local int updatewindow OF((z_streamp strm, const unsigned char FAR *end,
unsigned copy));
@@ -104,12 +102,26 @@ local int updatewindow OF((z_streamp strm, const unsigned char FAR *end,
local unsigned syncsearch OF((unsigned FAR *have, const unsigned char FAR *buf,
unsigned len));
+local int inflateStateCheck(strm)
+z_streamp strm;
+{
+ struct inflate_state FAR *state;
+ if (strm == Z_NULL ||
+ strm->zalloc == (alloc_func)0 || strm->zfree == (free_func)0)
+ return 1;
+ state = (struct inflate_state FAR *)strm->state;
+ if (state == Z_NULL || state->strm != strm ||
+ state->mode < HEAD || state->mode > SYNC)
+ return 1;
+ return 0;
+}
+
int ZEXPORT inflateResetKeep(strm)
z_streamp strm;
{
struct inflate_state FAR *state;
- if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
state = (struct inflate_state FAR *)strm->state;
strm->total_in = strm->total_out = state->total = 0;
strm->msg = Z_NULL;
@@ -134,7 +146,7 @@ z_streamp strm;
{
struct inflate_state FAR *state;
- if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
state = (struct inflate_state FAR *)strm->state;
state->wsize = 0;
state->whave = 0;
@@ -150,7 +162,7 @@ int windowBits;
struct inflate_state FAR *state;
/* get the state */
- if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
state = (struct inflate_state FAR *)strm->state;
/* extract wrap request from windowBits parameter */
@@ -159,7 +171,7 @@ int windowBits;
windowBits = -windowBits;
}
else {
- wrap = (windowBits >> 4) + 1;
+ wrap = (windowBits >> 4) + 5;
#ifdef GUNZIP
if (windowBits < 48)
windowBits &= 15;
@@ -213,7 +225,9 @@ int stream_size;
if (state == Z_NULL) return Z_MEM_ERROR;
Tracev((stderr, "inflate: allocated\n"));
strm->state = (struct internal_state FAR *)state;
+ state->strm = strm;
state->window = Z_NULL;
+ state->mode = HEAD; /* to pass state test in inflateReset2() */
ret = inflateReset2(strm, windowBits);
if (ret != Z_OK) {
ZFREE(strm, state);
@@ -237,17 +251,17 @@ int value;
{
struct inflate_state FAR *state;
- if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
state = (struct inflate_state FAR *)strm->state;
if (bits < 0) {
state->hold = 0;
state->bits = 0;
return Z_OK;
}
- if (bits > 16 || state->bits + bits > 32) return Z_STREAM_ERROR;
+ if (bits > 16 || state->bits + (uInt)bits > 32) return Z_STREAM_ERROR;
value &= (1L << bits) - 1;
- state->hold += value << state->bits;
- state->bits += bits;
+ state->hold += (unsigned)value << state->bits;
+ state->bits += (uInt)bits;
return Z_OK;
}
@@ -628,7 +642,7 @@ int flush;
static const unsigned short order[19] = /* permutation of code lengths */
{16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
- if (strm == Z_NULL || strm->state == Z_NULL || strm->next_out == Z_NULL ||
+ if (inflateStateCheck(strm) || strm->next_out == Z_NULL ||
(strm->next_in == Z_NULL && strm->avail_in != 0))
return Z_STREAM_ERROR;
@@ -648,6 +662,8 @@ int flush;
NEEDBITS(16);
#ifdef GUNZIP
if ((state->wrap & 2) && hold == 0x8b1f) { /* gzip header */
+ if (state->wbits == 0)
+ state->wbits = 15;
state->check = crc32(0L, Z_NULL, 0);
CRC2(state->check, hold);
INITBITS();
@@ -675,7 +691,7 @@ int flush;
len = BITS(4) + 8;
if (state->wbits == 0)
state->wbits = len;
- else if (len > state->wbits) {
+ if (len > 15 || len > state->wbits) {
strm->msg = (char *)"invalid window size";
state->mode = BAD;
break;
@@ -702,14 +718,16 @@ int flush;
}
if (state->head != Z_NULL)
state->head->text = (int)((hold >> 8) & 1);
- if (state->flags & 0x0200) CRC2(state->check, hold);
+ if ((state->flags & 0x0200) && (state->wrap & 4))
+ CRC2(state->check, hold);
INITBITS();
state->mode = TIME;
case TIME:
NEEDBITS(32);
if (state->head != Z_NULL)
state->head->time = hold;
- if (state->flags & 0x0200) CRC4(state->check, hold);
+ if ((state->flags & 0x0200) && (state->wrap & 4))
+ CRC4(state->check, hold);
INITBITS();
state->mode = OS;
case OS:
@@ -718,7 +736,8 @@ int flush;
state->head->xflags = (int)(hold & 0xff);
state->head->os = (int)(hold >> 8);
}
- if (state->flags & 0x0200) CRC2(state->check, hold);
+ if ((state->flags & 0x0200) && (state->wrap & 4))
+ CRC2(state->check, hold);
INITBITS();
state->mode = EXLEN;
case EXLEN:
@@ -727,7 +746,8 @@ int flush;
state->length = (unsigned)(hold);
if (state->head != Z_NULL)
state->head->extra_len = (unsigned)hold;
- if (state->flags & 0x0200) CRC2(state->check, hold);
+ if ((state->flags & 0x0200) && (state->wrap & 4))
+ CRC2(state->check, hold);
INITBITS();
}
else if (state->head != Z_NULL)
@@ -745,7 +765,7 @@ int flush;
len + copy > state->head->extra_max ?
state->head->extra_max - len : copy);
}
- if (state->flags & 0x0200)
+ if ((state->flags & 0x0200) && (state->wrap & 4))
state->check = crc32(state->check, next, copy);
have -= copy;
next += copy;
@@ -764,9 +784,9 @@ int flush;
if (state->head != Z_NULL &&
state->head->name != Z_NULL &&
state->length < state->head->name_max)
- state->head->name[state->length++] = len;
+ state->head->name[state->length++] = (Bytef)len;
} while (len && copy < have);
- if (state->flags & 0x0200)
+ if ((state->flags & 0x0200) && (state->wrap & 4))
state->check = crc32(state->check, next, copy);
have -= copy;
next += copy;
@@ -785,9 +805,9 @@ int flush;
if (state->head != Z_NULL &&
state->head->comment != Z_NULL &&
state->length < state->head->comm_max)
- state->head->comment[state->length++] = len;
+ state->head->comment[state->length++] = (Bytef)len;
} while (len && copy < have);
- if (state->flags & 0x0200)
+ if ((state->flags & 0x0200) && (state->wrap & 4))
state->check = crc32(state->check, next, copy);
have -= copy;
next += copy;
@@ -799,7 +819,7 @@ int flush;
case HCRC:
if (state->flags & 0x0200) {
NEEDBITS(16);
- if (hold != (state->check & 0xffff)) {
+ if ((state->wrap & 4) && hold != (state->check & 0xffff)) {
strm->msg = (char *)"header crc mismatch";
state->mode = BAD;
break;
@@ -1180,11 +1200,11 @@ int flush;
out -= left;
strm->total_out += out;
state->total += out;
- if (out)
+ if ((state->wrap & 4) && out)
strm->adler = state->check =
UPDATE(state->check, put - out, out);
out = left;
- if ((
+ if ((state->wrap & 4) && (
#ifdef GUNZIP
state->flags ? hold :
#endif
@@ -1243,10 +1263,10 @@ int flush;
strm->total_in += in;
strm->total_out += out;
state->total += out;
- if (state->wrap && out)
+ if ((state->wrap & 4) && out)
strm->adler = state->check =
UPDATE(state->check, strm->next_out - out, out);
- strm->data_type = state->bits + (state->last ? 64 : 0) +
+ strm->data_type = (int)state->bits + (state->last ? 64 : 0) +
(state->mode == TYPE ? 128 : 0) +
(state->mode == LEN_ || state->mode == COPY_ ? 256 : 0);
if (((in == 0 && out == 0) || flush == Z_FINISH) && ret == Z_OK)
@@ -1258,7 +1278,7 @@ int ZEXPORT inflateEnd(strm)
z_streamp strm;
{
struct inflate_state FAR *state;
- if (strm == Z_NULL || strm->state == Z_NULL || strm->zfree == (free_func)0)
+ if (inflateStateCheck(strm))
return Z_STREAM_ERROR;
state = (struct inflate_state FAR *)strm->state;
if (state->window != Z_NULL) ZFREE(strm, state->window);
@@ -1276,7 +1296,7 @@ uInt *dictLength;
struct inflate_state FAR *state;
/* check state */
- if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
state = (struct inflate_state FAR *)strm->state;
/* copy dictionary */
@@ -1301,7 +1321,7 @@ uInt dictLength;
int ret;
/* check state */
- if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
state = (struct inflate_state FAR *)strm->state;
if (state->wrap != 0 && state->mode != DICT)
return Z_STREAM_ERROR;
@@ -1333,7 +1353,7 @@ gz_headerp head;
struct inflate_state FAR *state;
/* check state */
- if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
state = (struct inflate_state FAR *)strm->state;
if ((state->wrap & 2) == 0) return Z_STREAM_ERROR;
@@ -1386,7 +1406,7 @@ z_streamp strm;
struct inflate_state FAR *state;
/* check parameters */
- if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
state = (struct inflate_state FAR *)strm->state;
if (strm->avail_in == 0 && state->bits < 8) return Z_BUF_ERROR;
@@ -1433,7 +1453,7 @@ z_streamp strm;
{
struct inflate_state FAR *state;
- if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
state = (struct inflate_state FAR *)strm->state;
return state->mode == STORED && state->bits == 0;
}
@@ -1448,8 +1468,7 @@ z_streamp source;
unsigned wsize;
/* check input */
- if (dest == Z_NULL || source == Z_NULL || source->state == Z_NULL ||
- source->zalloc == (alloc_func)0 || source->zfree == (free_func)0)
+ if (inflateStateCheck(source) || dest == Z_NULL)
return Z_STREAM_ERROR;
state = (struct inflate_state FAR *)source->state;
@@ -1470,6 +1489,7 @@ z_streamp source;
/* copy state */
zmemcpy((voidpf)dest, (voidpf)source, sizeof(z_stream));
zmemcpy((voidpf)copy, (voidpf)state, sizeof(struct inflate_state));
+ copy->strm = dest;
if (state->lencode >= state->codes &&
state->lencode <= state->codes + ENOUGH - 1) {
copy->lencode = copy->codes + (state->lencode - state->codes);
@@ -1491,25 +1511,51 @@ int subvert;
{
struct inflate_state FAR *state;
- if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+ if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
state = (struct inflate_state FAR *)strm->state;
- state->sane = !subvert;
#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
+ state->sane = !subvert;
return Z_OK;
#else
+ (void)subvert;
state->sane = 1;
return Z_DATA_ERROR;
#endif
}
+int ZEXPORT inflateValidate(strm, check)
+z_streamp strm;
+int check;
+{
+ struct inflate_state FAR *state;
+
+ if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
+ state = (struct inflate_state FAR *)strm->state;
+ if (check)
+ state->wrap |= 4;
+ else
+ state->wrap &= ~4;
+ return Z_OK;
+}
+
long ZEXPORT inflateMark(strm)
z_streamp strm;
{
struct inflate_state FAR *state;
- if (strm == Z_NULL || strm->state == Z_NULL) return -1L << 16;
+ if (inflateStateCheck(strm))
+ return -(1L << 16);
state = (struct inflate_state FAR *)strm->state;
- return ((long)(state->back) << 16) +
+ return (long)(((unsigned long)((long)state->back)) << 16) +
(state->mode == COPY ? state->length :
(state->mode == MATCH ? state->was - state->length : 0));
}
+
+unsigned long ZEXPORT inflateCodesUsed(strm)
+z_streamp strm;
+{
+ struct inflate_state FAR *state;
+ if (inflateStateCheck(strm)) return (unsigned long)-1;
+ state = (struct inflate_state FAR *)strm->state;
+ return (unsigned long)(state->next - state->codes);
+}
diff --git a/erts/emulator/zlib/inflate.h b/erts/emulator/zlib/inflate.h
index 95f4986d40..a46cce6b6d 100644
--- a/erts/emulator/zlib/inflate.h
+++ b/erts/emulator/zlib/inflate.h
@@ -1,5 +1,5 @@
/* inflate.h -- internal inflate state definition
- * Copyright (C) 1995-2009 Mark Adler
+ * Copyright (C) 1995-2016 Mark Adler
* For conditions of distribution and use, see copyright notice in zlib.h
*/
@@ -18,7 +18,7 @@
/* Possible inflate modes between inflate() calls */
typedef enum {
- HEAD, /* i: waiting for magic header */
+ HEAD = 16180, /* i: waiting for magic header */
FLAGS, /* i: waiting for method and flags (gzip) */
TIME, /* i: waiting for modification time (gzip) */
OS, /* i: waiting for extra flags and operating system (gzip) */
@@ -77,11 +77,14 @@ typedef enum {
CHECK -> LENGTH -> DONE
*/
-/* state maintained between inflate() calls. Approximately 10K bytes. */
+/* State maintained between inflate() calls -- approximately 7K bytes, not
+ including the allocated sliding window, which is up to 32K bytes. */
struct inflate_state {
+ z_streamp strm; /* pointer back to this zlib stream */
inflate_mode mode; /* current inflate mode */
int last; /* true if processing last block */
- int wrap; /* bit 0 true for zlib, bit 1 true for gzip */
+ int wrap; /* bit 0 true for zlib, bit 1 true for gzip,
+ bit 2 true to validate check value */
int havedict; /* true if dictionary provided */
int flags; /* gzip header method and flags (0 if zlib) */
unsigned dmax; /* zlib header max distance (INFLATE_STRICT) */
diff --git a/erts/emulator/zlib/inftrees.c b/erts/emulator/zlib/inftrees.c
index 3766fa2646..2ea08fc13e 100644
--- a/erts/emulator/zlib/inftrees.c
+++ b/erts/emulator/zlib/inftrees.c
@@ -1,18 +1,15 @@
/* inftrees.c -- generate Huffman trees for efficient decoding
- * Copyright (C) 1995-2013 Mark Adler
+ * Copyright (C) 1995-2017 Mark Adler
* For conditions of distribution and use, see copyright notice in zlib.h
*/
-#ifdef HAVE_CONFIG_H
-# include "config.h"
-#endif
#include "zutil.h"
#include "inftrees.h"
#define MAXBITS 15
const char inflate_copyright[] =
- " inflate 1.2.8 Copyright 1995-2013 Mark Adler ";
+ " inflate 1.2.11 Copyright 1995-2017 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
@@ -57,7 +54,7 @@ unsigned short FAR *work;
code FAR *next; /* next available space in table */
const unsigned short FAR *base; /* base value table to use */
const unsigned short FAR *extra; /* extra bits table to use */
- int end; /* use base and extra for symbol > end */
+ unsigned match; /* use base and extra for symbol >= match */
unsigned short count[MAXBITS+1]; /* number of codes of each length */
unsigned short offs[MAXBITS+1]; /* offsets in table for each length */
static const unsigned short lbase[31] = { /* Length codes 257..285 base */
@@ -65,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, 72, 78};
+ 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 77, 202};
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,
@@ -184,19 +181,17 @@ unsigned short FAR *work;
switch (type) {
case CODES:
base = extra = work; /* dummy value--not used */
- end = 19;
+ match = 20;
break;
case LENS:
base = lbase;
- base -= 257;
extra = lext;
- extra -= 257;
- end = 256;
+ match = 257;
break;
- default: /* DISTS */
+ default: /* DISTS */
base = dbase;
extra = dext;
- end = -1;
+ match = 0;
}
/* initialize state for loop */
@@ -219,13 +214,13 @@ unsigned short FAR *work;
for (;;) {
/* create table entry */
here.bits = (unsigned char)(len - drop);
- if ((int)(work[sym]) < end) {
+ if (work[sym] + 1U < match) {
here.op = (unsigned char)0;
here.val = work[sym];
}
- else if ((int)(work[sym]) > end) {
- here.op = (unsigned char)(extra[work[sym]]);
- here.val = base[work[sym]];
+ else if (work[sym] >= match) {
+ here.op = (unsigned char)(extra[work[sym] - match]);
+ here.val = base[work[sym] - match];
}
else {
here.op = (unsigned char)(32 + 64); /* end of block */
diff --git a/erts/emulator/zlib/trees.c b/erts/emulator/zlib/trees.c
index 465e944e5b..50cf4b4571 100644
--- a/erts/emulator/zlib/trees.c
+++ b/erts/emulator/zlib/trees.c
@@ -1,5 +1,5 @@
/* trees.c -- output deflated data using Huffman coding
- * Copyright (C) 1995-2012 Jean-loup Gailly
+ * Copyright (C) 1995-2017 Jean-loup Gailly
* detect_data_type() function provided freely by Cosmin Truta, 2006
* For conditions of distribution and use, see copyright notice in zlib.h
*/
@@ -34,12 +34,9 @@
/* #define GEN_TREES_H */
-#ifdef HAVE_CONFIG_H
-# include "config.h"
-#endif
#include "deflate.h"
-#ifdef DEBUG
+#ifdef ZLIB_DEBUG
# include <ctype.h>
#endif
@@ -125,13 +122,13 @@ struct static_tree_desc_s {
int max_length; /* max bit length for the codes */
};
-local static_tree_desc static_l_desc =
+local const static_tree_desc static_l_desc =
{static_ltree, extra_lbits, LITERALS+1, L_CODES, MAX_BITS};
-local static_tree_desc static_d_desc =
+local const static_tree_desc static_d_desc =
{static_dtree, extra_dbits, 0, D_CODES, MAX_BITS};
-local static_tree_desc static_bl_desc =
+local const static_tree_desc static_bl_desc =
{(const ct_data *)0, extra_blbits, 0, BL_CODES, MAX_BL_BITS};
/* ===========================================================================
@@ -155,18 +152,16 @@ local int detect_data_type OF((deflate_state *s));
local unsigned bi_reverse OF((unsigned value, int length));
local void bi_windup OF((deflate_state *s));
local void bi_flush OF((deflate_state *s));
-local void copy_block OF((deflate_state *s, charf *buf, unsigned len,
- int header));
#ifdef GEN_TREES_H
local void gen_trees_header OF((void));
#endif
-#ifndef DEBUG
+#ifndef ZLIB_DEBUG
# define send_code(s, c, tree) send_bits(s, tree[c].Code, tree[c].Len)
/* Send a code of the given tree. c and tree must not have side effects */
-#else /* DEBUG */
+#else /* !ZLIB_DEBUG */
# define send_code(s, c, tree) \
{ if (z_verbose>2) fprintf(stderr,"\ncd %3d ",(c)); \
send_bits(s, tree[c].Code, tree[c].Len); }
@@ -185,7 +180,7 @@ local void gen_trees_header OF((void));
* Send a value on a given number of bits.
* IN assertion: length <= 16 and value fits in length bits.
*/
-#ifdef DEBUG
+#ifdef ZLIB_DEBUG
local void send_bits OF((deflate_state *s, int value, int length));
local void send_bits(s, value, length)
@@ -211,12 +206,12 @@ local void send_bits(s, value, length)
s->bi_valid += length;
}
}
-#else /* !DEBUG */
+#else /* !ZLIB_DEBUG */
#define send_bits(s, value, length) \
{ int len = length;\
if (s->bi_valid > (int)Buf_size - len) {\
- int val = value;\
+ int val = (int)value;\
s->bi_buf |= (ush)val << s->bi_valid;\
put_short(s, s->bi_buf);\
s->bi_buf = (ush)val >> (Buf_size - s->bi_valid);\
@@ -226,7 +221,7 @@ local void send_bits(s, value, length)
s->bi_valid += len;\
}\
}
-#endif /* DEBUG */
+#endif /* ZLIB_DEBUG */
/* the arguments must not have side effects */
@@ -320,7 +315,7 @@ local void tr_static_init()
* Genererate the file trees.h describing the static trees.
*/
#ifdef GEN_TREES_H
-# ifndef DEBUG
+# ifndef ZLIB_DEBUG
# include <stdio.h>
# endif
@@ -397,7 +392,7 @@ void ZLIB_INTERNAL _tr_init(s)
s->bi_buf = 0;
s->bi_valid = 0;
-#ifdef DEBUG
+#ifdef ZLIB_DEBUG
s->compressed_len = 0L;
s->bits_sent = 0L;
#endif
@@ -525,12 +520,12 @@ local void gen_bitlen(s, desc)
xbits = 0;
if (n >= base) xbits = extra[n-base];
f = tree[n].Freq;
- s->opt_len += (ulg)f * (bits + xbits);
- if (stree) s->static_len += (ulg)f * (stree[n].Len + xbits);
+ s->opt_len += (ulg)f * (unsigned)(bits + xbits);
+ if (stree) s->static_len += (ulg)f * (unsigned)(stree[n].Len + xbits);
}
if (overflow == 0) return;
- Trace((stderr,"\nbit length overflow\n"));
+ Tracev((stderr,"\nbit length overflow\n"));
/* This happens for example on obj2 and pic of the Calgary corpus */
/* Find the first bit length which could increase: */
@@ -557,9 +552,8 @@ local void gen_bitlen(s, desc)
m = s->heap[--h];
if (m > max_code) continue;
if ((unsigned) tree[m].Len != (unsigned) bits) {
- Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits));
- s->opt_len += ((long)bits - (long)tree[m].Len)
- *(long)tree[m].Freq;
+ Tracev((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits));
+ s->opt_len += ((ulg)bits - tree[m].Len) * tree[m].Freq;
tree[m].Len = (ush)bits;
}
n--;
@@ -581,7 +575,7 @@ local void gen_codes (tree, max_code, bl_count)
ushf *bl_count; /* number of codes at each bit length */
{
ush next_code[MAX_BITS+1]; /* next code value for each bit length */
- ush code = 0; /* running code value */
+ unsigned code = 0; /* running code value */
int bits; /* bit index */
int n; /* code index */
@@ -589,7 +583,8 @@ local void gen_codes (tree, max_code, bl_count)
* without bit reversal.
*/
for (bits = 1; bits <= MAX_BITS; bits++) {
- next_code[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.
@@ -602,7 +597,7 @@ local void gen_codes (tree, max_code, bl_count)
int len = tree[n].Len;
if (len == 0) continue;
/* Now reverse the bits */
- tree[n].Code = bi_reverse(next_code[len]++, len);
+ 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));
@@ -824,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*(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));
@@ -872,11 +867,17 @@ void ZLIB_INTERNAL _tr_stored_block(s, buf, stored_len, last)
int last; /* one if this is the last block for a file */
{
send_bits(s, (STORED_BLOCK<<1)+last, 3); /* send block type */
-#ifdef DEBUG
+ bi_windup(s); /* align on byte boundary */
+ put_short(s, (ush)stored_len);
+ put_short(s, (ush)~stored_len);
+ zmemcpy(s->pending_buf + s->pending, (Bytef *)buf, stored_len);
+ s->pending += stored_len;
+#ifdef ZLIB_DEBUG
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;
#endif
- copy_block(s, buf, (unsigned)stored_len, 1); /* with header */
}
/* ===========================================================================
@@ -897,7 +898,7 @@ void ZLIB_INTERNAL _tr_align(s)
{
send_bits(s, STATIC_TREES<<1, 3);
send_code(s, END_BLOCK, static_ltree);
-#ifdef DEBUG
+#ifdef ZLIB_DEBUG
s->compressed_len += 10L; /* 3 for block type, 7 for EOB */
#endif
bi_flush(s);
@@ -905,7 +906,7 @@ void ZLIB_INTERNAL _tr_align(s)
/* ===========================================================================
* Determine the best encoding for the current block: dynamic trees, static
- * trees or store, and output the encoded block to the zip file.
+ * trees or store, and write out the encoded block.
*/
void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last)
deflate_state *s;
@@ -977,7 +978,7 @@ void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last)
send_bits(s, (STATIC_TREES<<1)+last, 3);
compress_block(s, (const ct_data *)static_ltree,
(const ct_data *)static_dtree);
-#ifdef DEBUG
+#ifdef ZLIB_DEBUG
s->compressed_len += 3 + s->static_len;
#endif
} else {
@@ -986,7 +987,7 @@ void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last)
max_blindex+1);
compress_block(s, (const ct_data *)s->dyn_ltree,
(const ct_data *)s->dyn_dtree);
-#ifdef DEBUG
+#ifdef ZLIB_DEBUG
s->compressed_len += 3 + s->opt_len;
#endif
}
@@ -998,7 +999,7 @@ void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last)
if (last) {
bi_windup(s);
-#ifdef DEBUG
+#ifdef ZLIB_DEBUG
s->compressed_len += 7; /* align on byte boundary */
#endif
}
@@ -1093,7 +1094,7 @@ local void compress_block(s, ltree, dtree)
send_code(s, code, dtree); /* send the distance code */
extra = extra_dbits[code];
if (extra != 0) {
- dist -= base_dist[code];
+ dist -= (unsigned)base_dist[code];
send_bits(s, dist, extra); /* send the extra distance bits */
}
} /* literal or match pair ? */
@@ -1196,34 +1197,7 @@ local void bi_windup(s)
}
s->bi_buf = 0;
s->bi_valid = 0;
-#ifdef DEBUG
+#ifdef ZLIB_DEBUG
s->bits_sent = (s->bits_sent+7) & ~7;
#endif
}
-
-/* ===========================================================================
- * Copy a stored block, storing first the length and its
- * one's complement if requested.
- */
-local void copy_block(s, buf, len, header)
- deflate_state *s;
- charf *buf; /* the input data */
- unsigned len; /* its length */
- int header; /* true if block header must be written */
-{
- bi_windup(s); /* align on byte boundary */
-
- if (header) {
- put_short(s, (ush)len);
- put_short(s, (ush)~len);
-#ifdef DEBUG
- s->bits_sent += 2*16;
-#endif
- }
-#ifdef DEBUG
- s->bits_sent += (ulg)len<<3;
-#endif
- while (len--) {
- put_byte(s, *buf++);
- }
-}
diff --git a/erts/emulator/zlib/uncompr.c b/erts/emulator/zlib/uncompr.c
index 864d571719..f03a1a865e 100644
--- a/erts/emulator/zlib/uncompr.c
+++ b/erts/emulator/zlib/uncompr.c
@@ -1,62 +1,93 @@
/* uncompr.c -- decompress a memory buffer
- * Copyright (C) 1995-2003, 2010 Jean-loup Gailly.
+ * Copyright (C) 1995-2003, 2010, 2014, 2016 Jean-loup Gailly, Mark Adler
* For conditions of distribution and use, see copyright notice in zlib.h
*/
/* @(#) $Id$ */
-#ifdef HAVE_CONFIG_H
-# include "config.h"
-#endif
#define ZLIB_INTERNAL
#include "zlib.h"
/* ===========================================================================
- Decompresses the source buffer into the destination buffer. sourceLen is
- the byte length of the source buffer. Upon entry, destLen is the total
- size of the destination buffer, which must be large enough to hold the
- entire uncompressed data. (The size of the uncompressed data must have
- been saved previously by the compressor and transmitted to the decompressor
- by some mechanism outside the scope of this compression library.)
- Upon exit, destLen is the actual size of the compressed buffer.
-
- uncompress returns Z_OK if success, Z_MEM_ERROR if there was not
- enough memory, Z_BUF_ERROR if there was not enough room in the output
- buffer, or Z_DATA_ERROR if the input data was corrupted.
+ Decompresses the source buffer into the destination buffer. *sourceLen is
+ the byte length of the source buffer. Upon entry, *destLen is the total size
+ of the destination buffer, which must be large enough to hold the entire
+ uncompressed data. (The size of the uncompressed data must have been saved
+ previously by the compressor and transmitted to the decompressor by some
+ mechanism outside the scope of this compression library.) Upon exit,
+ *destLen is the size of the decompressed data and *sourceLen is the number
+ of source bytes consumed. Upon return, source + *sourceLen points to the
+ first unused input byte.
+
+ uncompress returns Z_OK if success, Z_MEM_ERROR if there was not enough
+ memory, Z_BUF_ERROR if there was not enough room in the output buffer, or
+ Z_DATA_ERROR if the input data was corrupted, including if the input data is
+ an incomplete zlib stream.
*/
-int ZEXPORT uncompress (dest, destLen, source, sourceLen)
+int ZEXPORT uncompress2 (dest, destLen, source, sourceLen)
Bytef *dest;
uLongf *destLen;
const Bytef *source;
- uLong sourceLen;
+ uLong *sourceLen;
{
z_stream stream;
int err;
+ const uInt max = (uInt)-1;
+ uLong len, left;
+ Byte buf[1]; /* for detection of incomplete stream when *destLen == 0 */
- stream.next_in = (z_const Bytef *)source;
- stream.avail_in = (uInt)sourceLen;
- /* Check for source > 64K on 16-bit machine: */
- if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR;
-
- stream.next_out = dest;
- stream.avail_out = (uInt)*destLen;
- if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR;
+ len = *sourceLen;
+ if (*destLen) {
+ left = *destLen;
+ *destLen = 0;
+ }
+ else {
+ left = 1;
+ dest = buf;
+ }
+ stream.next_in = (z_const Bytef *)source;
+ stream.avail_in = 0;
stream.zalloc = (alloc_func)0;
stream.zfree = (free_func)0;
+ stream.opaque = (voidpf)0;
err = inflateInit(&stream);
if (err != Z_OK) return err;
- err = inflate(&stream, Z_FINISH);
- if (err != Z_STREAM_END) {
- inflateEnd(&stream);
- if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0))
- return Z_DATA_ERROR;
- return err;
- }
- *destLen = stream.total_out;
+ stream.next_out = dest;
+ stream.avail_out = 0;
- err = inflateEnd(&stream);
- return err;
+ do {
+ if (stream.avail_out == 0) {
+ stream.avail_out = left > (uLong)max ? max : (uInt)left;
+ left -= stream.avail_out;
+ }
+ if (stream.avail_in == 0) {
+ stream.avail_in = len > (uLong)max ? max : (uInt)len;
+ len -= stream.avail_in;
+ }
+ err = inflate(&stream, Z_NO_FLUSH);
+ } while (err == Z_OK);
+
+ *sourceLen -= len + stream.avail_in;
+ if (dest != buf)
+ *destLen = stream.total_out;
+ else if (stream.total_out && err == Z_BUF_ERROR)
+ left = 1;
+
+ inflateEnd(&stream);
+ return err == Z_STREAM_END ? Z_OK :
+ err == Z_NEED_DICT ? Z_DATA_ERROR :
+ err == Z_BUF_ERROR && left + stream.avail_out ? Z_DATA_ERROR :
+ err;
+}
+
+int ZEXPORT uncompress (dest, destLen, source, sourceLen)
+ Bytef *dest;
+ uLongf *destLen;
+ const Bytef *source;
+ uLong sourceLen;
+{
+ return uncompress2(dest, destLen, source, &sourceLen);
}
diff --git a/erts/emulator/zlib/zconf.h b/erts/emulator/zlib/zconf.h
index 9987a77553..5e1d68a004 100644
--- a/erts/emulator/zlib/zconf.h
+++ b/erts/emulator/zlib/zconf.h
@@ -1,5 +1,5 @@
/* zconf.h -- configuration of the zlib compression library
- * Copyright (C) 1995-2013 Jean-loup Gailly.
+ * Copyright (C) 1995-2016 Jean-loup Gailly, Mark Adler
* For conditions of distribution and use, see copyright notice in zlib.h
*/
@@ -17,7 +17,7 @@
#ifdef Z_PREFIX /* may be set to #if 1 by ./configure */
# define Z_PREFIX_SET
-/* all linked symbols */
+/* all linked symbols and init macros */
# define _dist_code z__dist_code
# define _length_code z__length_code
# define _tr_align z__tr_align
@@ -29,6 +29,7 @@
# define adler32 z_adler32
# define adler32_combine z_adler32_combine
# define adler32_combine64 z_adler32_combine64
+# define adler32_z z_adler32_z
# ifndef Z_SOLO
# define compress z_compress
# define compress2 z_compress2
@@ -37,10 +38,14 @@
# define crc32 z_crc32
# define crc32_combine z_crc32_combine
# define crc32_combine64 z_crc32_combine64
+# define crc32_z z_crc32_z
# define deflate z_deflate
# define deflateBound z_deflateBound
# define deflateCopy z_deflateCopy
# define deflateEnd z_deflateEnd
+# define deflateGetDictionary z_deflateGetDictionary
+# define deflateInit z_deflateInit
+# define deflateInit2 z_deflateInit2
# define deflateInit2_ z_deflateInit2_
# define deflateInit_ z_deflateInit_
# define deflateParams z_deflateParams
@@ -67,6 +72,8 @@
# define gzeof z_gzeof
# define gzerror z_gzerror
# define gzflush z_gzflush
+# define gzfread z_gzfread
+# define gzfwrite z_gzfwrite
# define gzgetc z_gzgetc
# define gzgetc_ z_gzgetc_
# define gzgets z_gzgets
@@ -78,7 +85,6 @@
# define gzopen_w z_gzopen_w
# endif
# define gzprintf z_gzprintf
-# define gzvprintf z_gzvprintf
# define gzputc z_gzputc
# define gzputs z_gzputs
# define gzread z_gzread
@@ -89,32 +95,39 @@
# define gztell z_gztell
# define gztell64 z_gztell64
# define gzungetc z_gzungetc
+# define gzvprintf z_gzvprintf
# define gzwrite z_gzwrite
# endif
# define inflate z_inflate
# define inflateBack z_inflateBack
# define inflateBackEnd z_inflateBackEnd
+# define inflateBackInit z_inflateBackInit
# define inflateBackInit_ z_inflateBackInit_
+# define inflateCodesUsed z_inflateCodesUsed
# define inflateCopy z_inflateCopy
# define inflateEnd z_inflateEnd
+# define inflateGetDictionary z_inflateGetDictionary
# define inflateGetHeader z_inflateGetHeader
+# define inflateInit z_inflateInit
+# define inflateInit2 z_inflateInit2
# define inflateInit2_ z_inflateInit2_
# define inflateInit_ z_inflateInit_
# define inflateMark z_inflateMark
# define inflatePrime z_inflatePrime
# define inflateReset z_inflateReset
# define inflateReset2 z_inflateReset2
+# define inflateResetKeep z_inflateResetKeep
# define inflateSetDictionary z_inflateSetDictionary
-# define inflateGetDictionary z_inflateGetDictionary
# define inflateSync z_inflateSync
# define inflateSyncPoint z_inflateSyncPoint
# define inflateUndermine z_inflateUndermine
-# define inflateResetKeep z_inflateResetKeep
+# define inflateValidate z_inflateValidate
# define inflate_copyright z_inflate_copyright
# define inflate_fast z_inflate_fast
# define inflate_table z_inflate_table
# ifndef Z_SOLO
# define uncompress z_uncompress
+# define uncompress2 z_uncompress2
# endif
# define zError z_zError
# ifndef Z_SOLO
@@ -224,9 +237,19 @@
# define z_const
#endif
-/* Some Mac compilers merge all .h files incorrectly: */
-#if defined(__MWERKS__)||defined(applec)||defined(THINK_C)||defined(__SC__)
-# define NO_DUMMY_DECL
+#ifdef Z_SOLO
+ typedef unsigned long z_size_t;
+#else
+# define z_longlong long long
+# if defined(NO_SIZE_T)
+ typedef unsigned NO_SIZE_T z_size_t;
+# elif defined(STDC)
+# include <stddef.h>
+ typedef size_t z_size_t;
+# else
+ typedef unsigned long z_size_t;
+# endif
+# undef z_longlong
#endif
/* Maximum value for memLevel in deflateInit2 */
@@ -256,7 +279,7 @@
Of course this will generally degrade compression (there's no free lunch).
The memory requirements for inflate are (in bytes) 1 << windowBits
- that is, 32K for windowBits=15 (default value) plus a few kilobytes
+ that is, 32K for windowBits=15 (default value) plus about 7 kilobytes
for small objects.
*/
diff --git a/erts/emulator/zlib/zlib.h b/erts/emulator/zlib/zlib.h
index 3e0c7672ac..f09cdaf1e0 100644
--- a/erts/emulator/zlib/zlib.h
+++ b/erts/emulator/zlib/zlib.h
@@ -1,7 +1,7 @@
/* zlib.h -- interface of the 'zlib' general purpose compression library
- version 1.2.8, April 28th, 2013
+ version 1.2.11, January 15th, 2017
- Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler
+ Copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
@@ -37,11 +37,11 @@
extern "C" {
#endif
-#define ZLIB_VERSION "1.2.8"
-#define ZLIB_VERNUM 0x1280
+#define ZLIB_VERSION "1.2.11"
+#define ZLIB_VERNUM 0x12b0
#define ZLIB_VER_MAJOR 1
#define ZLIB_VER_MINOR 2
-#define ZLIB_VER_REVISION 8
+#define ZLIB_VER_REVISION 11
#define ZLIB_VER_SUBREVISION 0
/*
@@ -65,7 +65,8 @@ extern "C" {
with "gz". The gzip format is different from the zlib format. gzip is a
gzip wrapper, documented in RFC 1952, wrapped around a deflate stream.
- This library can optionally read and write gzip streams in memory as well.
+ This library can optionally read and write gzip and raw deflate streams in
+ memory as well.
The zlib format was designed to be compact and fast for use in memory
and on communications channels. The gzip format was designed for single-
@@ -74,7 +75,7 @@ extern "C" {
The library does not install any signal handler. The decoder checks
the consistency of the compressed data, so the library should never crash
- even in case of corrupted input.
+ even in the case of corrupted input.
*/
typedef voidpf (*alloc_func) OF((voidpf opaque, uInt items, uInt size));
@@ -87,7 +88,7 @@ typedef struct z_stream_s {
uInt avail_in; /* number of bytes available at next_in */
uLong total_in; /* total number of input bytes read so far */
- Bytef *next_out; /* next output byte should be put there */
+ Bytef *next_out; /* next output byte will go here */
uInt avail_out; /* remaining free space at next_out */
uLong total_out; /* total number of bytes output so far */
@@ -98,8 +99,9 @@ typedef struct z_stream_s {
free_func zfree; /* used to free the internal state */
voidpf opaque; /* private data object passed to zalloc and zfree */
- int data_type; /* best guess about the data type: binary or text */
- uLong adler; /* adler32 value of the uncompressed data */
+ int data_type; /* best guess about the data type: binary or text
+ for deflate, or the decoding state for inflate */
+ uLong adler; /* Adler-32 or CRC-32 value of the uncompressed data */
uLong reserved; /* reserved for future use */
} z_stream;
@@ -142,7 +144,9 @@ typedef gz_header FAR *gz_headerp;
zalloc must return Z_NULL if there is not enough memory for the object.
If zlib is used in a multi-threaded application, zalloc and zfree must be
- thread safe.
+ thread safe. In that case, zlib is thread-safe. When zalloc and zfree are
+ Z_NULL on entry to the initialization function, they are set to internal
+ routines that use the standard library functions malloc() and free().
On 16-bit systems, the functions zalloc and zfree must be able to allocate
exactly 65536 bytes, but will not be required to allocate more than this if
@@ -155,7 +159,7 @@ typedef gz_header FAR *gz_headerp;
The fields total_in and total_out can be used for statistics or progress
reports. After compression, total_in holds the total size of the
- uncompressed data and may be saved for use in the decompressor (particularly
+ uncompressed data and may be saved for use by the decompressor (particularly
if the decompressor wants to decompress everything in a single step).
*/
@@ -200,7 +204,7 @@ typedef gz_header FAR *gz_headerp;
#define Z_TEXT 1
#define Z_ASCII Z_TEXT /* for compatibility with 1.2.2 and earlier */
#define Z_UNKNOWN 2
-/* Possible values of the data_type field (though see inflate()) */
+/* Possible values of the data_type field for deflate() */
#define Z_DEFLATED 8
/* The deflate compression method (the only one supported in this version) */
@@ -258,11 +262,11 @@ ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush));
enough room in the output buffer), next_in and avail_in are updated and
processing will resume at this point for the next call of deflate().
- - Provide more output starting at next_out and update next_out and avail_out
+ - Generate more output starting at next_out and update next_out and avail_out
accordingly. This action is forced if the parameter flush is non zero.
Forcing flush frequently degrades the compression ratio, so this parameter
- should be set only when necessary (in interactive applications). Some
- output may be provided even if flush is not set.
+ should be set only when necessary. Some output may be provided even if
+ flush is zero.
Before the call of deflate(), the application should ensure that at least
one of the actions is possible, by providing more input and/or consuming more
@@ -271,7 +275,9 @@ ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush));
output when it wants, for example when the output buffer is full (avail_out
== 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.
+ 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
+ in that case.
Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to
decide how much data to accumulate before producing output, in order to
@@ -292,8 +298,8 @@ ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush));
input data so far will be available to the decompressor, as for Z_SYNC_FLUSH.
This completes the current deflate block and follows it with an empty fixed
codes block that is 10 bits long. This assures that enough bytes are output
- in order for the decompressor to finish the block before the empty fixed code
- block.
+ in order for the decompressor to finish the block before the empty fixed
+ codes block.
If flush is set to Z_BLOCK, a deflate block is completed and emitted, as
for Z_SYNC_FLUSH, but the output is not aligned on a byte boundary, and up to
@@ -319,34 +325,38 @@ ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush));
If the parameter flush is set to Z_FINISH, pending input is processed,
pending output is flushed and deflate returns with Z_STREAM_END if there was
- enough output space; if deflate returns with Z_OK, this function must be
- called again with Z_FINISH and more output space (updated avail_out) but no
- more input data, until it returns with Z_STREAM_END or an error. After
- deflate has returned Z_STREAM_END, the only possible operations on the stream
- are deflateReset or deflateEnd.
-
- Z_FINISH can be used immediately after deflateInit if all the compression
- is to be done in a single step. In this case, avail_out must be at least the
- value returned by deflateBound (see below). Then deflate is guaranteed to
- return Z_STREAM_END. If not enough output space is provided, deflate will
- not return Z_STREAM_END, and it must be called again as described above.
-
- deflate() sets strm->adler to the adler32 checksum of all input read
- so far (that is, total_in bytes).
+ enough output space. If deflate returns with Z_OK or Z_BUF_ERROR, this
+ function must be called again with Z_FINISH and more output space (updated
+ avail_out) but no more input data, until it returns with Z_STREAM_END or an
+ error. After deflate has returned Z_STREAM_END, the only possible operations
+ on the stream are deflateReset or deflateEnd.
+
+ Z_FINISH can be used in the first deflate call after deflateInit if all the
+ compression is to be done in a single step. In order to complete in one
+ call, avail_out must be at least the value returned by deflateBound (see
+ below). Then deflate is guaranteed to return Z_STREAM_END. If not enough
+ output space is provided, deflate will not return Z_STREAM_END, and it must
+ be called again as described above.
+
+ deflate() sets strm->adler to the Adler-32 checksum of all input read
+ so far (that is, total_in bytes). If a gzip stream is being generated, then
+ strm->adler will be the CRC-32 checksum of the input read so far. (See
+ deflateInit2 below.)
deflate() may update strm->data_type if it can make a good guess about
- the input data type (Z_BINARY or Z_TEXT). In doubt, the data is considered
- binary. This field is only for information purposes and does not affect the
- compression algorithm in any manner.
+ the input data type (Z_BINARY or Z_TEXT). If in doubt, the data is
+ considered binary. This field is only for information purposes and does not
+ affect the compression algorithm in any manner.
deflate() returns Z_OK if some progress has been made (more input
processed or more output produced), Z_STREAM_END if all input has been
consumed and all output has been produced (only when flush is set to
Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example
- if next_in or next_out was Z_NULL), Z_BUF_ERROR if no progress is possible
- (for example avail_in or avail_out was zero). Note that Z_BUF_ERROR is not
- fatal, and deflate() can be called again with more input and more output
- space to continue compressing.
+ if next_in or next_out was Z_NULL or the state was inadvertently written over
+ by the application), or Z_BUF_ERROR if no progress is possible (for example
+ avail_in or avail_out was zero). Note that Z_BUF_ERROR is not fatal, and
+ deflate() can be called again with more input and more output space to
+ continue compressing.
*/
@@ -369,23 +379,21 @@ ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm));
Initializes the internal stream state for decompression. The fields
next_in, avail_in, zalloc, zfree and opaque must be initialized before by
- the caller. If next_in is not Z_NULL and avail_in is large enough (the
- exact value depends on the compression method), inflateInit determines the
- compression method from the zlib header and allocates all data structures
- accordingly; otherwise the allocation will be deferred to the first call of
- inflate. If zalloc and zfree are set to Z_NULL, inflateInit updates them to
- use default allocation functions.
+ the caller. In the current version of inflate, the provided input is not
+ read or consumed. The allocation of a sliding window will be deferred to
+ the first call of inflate (if the decompression does not complete on the
+ first call). If zalloc and zfree are set to Z_NULL, inflateInit updates
+ them to use default allocation functions.
inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough
memory, Z_VERSION_ERROR if the zlib library version is incompatible with the
version assumed by the caller, or Z_STREAM_ERROR if the parameters are
invalid, such as a null pointer to the structure. msg is set to null if
- there is no error message. inflateInit does not perform any decompression
- apart from possibly reading the zlib header if present: actual decompression
- will be done by inflate(). (So next_in and avail_in may be modified, but
- next_out and avail_out are unused and unchanged.) The current implementation
- of inflateInit() does not process any header information -- that is deferred
- until inflate() is called.
+ there is no error message. inflateInit does not perform any decompression.
+ Actual decompression will be done by inflate(). So next_in, and avail_in,
+ next_out, and avail_out are unused and unchanged. The current
+ implementation of inflateInit() does not process any header information --
+ that is deferred until inflate() is called.
*/
@@ -401,17 +409,20 @@ ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush));
- Decompress more input starting at next_in and update next_in and avail_in
accordingly. If not all input can be processed (because there is not
- enough room in the output buffer), next_in is updated and processing will
- resume at this point for the next call of inflate().
+ enough room in the output buffer), then next_in and avail_in are updated
+ accordingly, and processing will resume at this point for the next call of
+ inflate().
- - Provide more output starting at next_out and update next_out and avail_out
+ - Generate more output starting at next_out and update next_out and avail_out
accordingly. inflate() provides as much output as possible, until there is
no more input data or no more space in the output buffer (see below about
the flush parameter).
Before the call of inflate(), the application should ensure that at least
one of the actions is possible, by providing more input and/or consuming more
- output, and updating the next_* and avail_* values accordingly. The
+ output, and updating the next_* and avail_* values accordingly. If the
+ caller of inflate() does not provide both available input and available
+ output space, it is possible that there will be no progress made. The
application can consume the uncompressed output when it wants, for example
when the output buffer is full (avail_out == 0), or after each call of
inflate(). If inflate returns Z_OK and with zero avail_out, it must be
@@ -428,7 +439,7 @@ ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush));
gets to the end of that block, or when it runs out of data.
The Z_BLOCK option assists in appending to or combining deflate streams.
- Also to assist in this, on return inflate() will set strm->data_type to the
+ To assist in this, on return inflate() always sets strm->data_type to the
number of unused bits in the last byte taken from strm->next_in, plus 64 if
inflate() is currently decoding the last block in the deflate stream, plus
128 if inflate() returned immediately after decoding an end-of-block code or
@@ -454,7 +465,7 @@ ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush));
this case all pending input is processed and all pending output is flushed;
avail_out must be large enough to hold all of the uncompressed data for the
operation to complete. (The size of the uncompressed data may have been
- saved by the compressor for this purpose.) The use of Z_FINISH is not
+ saved by the compressor for this purpose.) The use of Z_FINISH is not
required to perform an inflation in one step. However it may be used to
inform inflate that a faster approach can be used for the single inflate()
call. Z_FINISH also informs inflate to not maintain a sliding window if the
@@ -476,32 +487,33 @@ ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush));
chosen by the compressor and returns Z_NEED_DICT; otherwise it sets
strm->adler to the Adler-32 checksum of all output produced so far (that is,
total_out bytes) and returns Z_OK, Z_STREAM_END or an error code as described
- below. At the end of the stream, inflate() checks that its computed adler32
+ below. At the end of the stream, inflate() checks that its computed Adler-32
checksum is equal to that saved by the compressor and returns Z_STREAM_END
only if the checksum is correct.
inflate() can decompress and check either zlib-wrapped or gzip-wrapped
deflate data. The header type is detected automatically, if requested when
initializing with inflateInit2(). Any information contained in the gzip
- header is not retained, so applications that need that information should
- instead use raw inflate, see inflateInit2() below, or inflateBack() and
- perform their own processing of the gzip header and trailer. When processing
+ header is not retained unless inflateGetHeader() is used. When processing
gzip-wrapped deflate data, strm->adler32 is set to the CRC-32 of the output
- producted so far. The CRC-32 is checked against the gzip trailer.
+ produced so far. The CRC-32 is checked against the gzip trailer, as is the
+ uncompressed length, modulo 2^32.
inflate() returns Z_OK if some progress has been made (more input processed
or more output produced), Z_STREAM_END if the end of the compressed data has
been reached and all uncompressed output has been produced, Z_NEED_DICT if a
preset dictionary is needed at this point, Z_DATA_ERROR if the input data was
corrupted (input stream not conforming to the zlib format or incorrect check
- value), Z_STREAM_ERROR if the stream structure was inconsistent (for example
- next_in or next_out was Z_NULL), Z_MEM_ERROR if there was not enough memory,
- Z_BUF_ERROR if no progress is possible or if there was not enough room in the
- output buffer when Z_FINISH is used. Note that Z_BUF_ERROR is not fatal, and
+ value, in which case strm->msg points to a string with a more specific
+ error), Z_STREAM_ERROR if the stream structure was inconsistent (for example
+ next_in or next_out was Z_NULL, or the state was inadvertently written over
+ by the application), Z_MEM_ERROR if there was not enough memory, Z_BUF_ERROR
+ if no progress was possible or if there was not enough room in the output
+ buffer when Z_FINISH is used. Note that Z_BUF_ERROR is not fatal, and
inflate() can be called again with more input and more output space to
continue decompressing. If Z_DATA_ERROR is returned, the application may
then call inflateSync() to look for a good compression block if a partial
- recovery of the data is desired.
+ recovery of the data is to be attempted.
*/
@@ -511,9 +523,8 @@ ZEXTERN int ZEXPORT inflateEnd OF((z_streamp strm));
This function discards any unprocessed input and does not flush any pending
output.
- inflateEnd returns Z_OK if success, Z_STREAM_ERROR if the stream state
- was inconsistent. In the error case, msg may be set but then points to a
- static string (which must not be deallocated).
+ inflateEnd returns Z_OK if success, or Z_STREAM_ERROR if the stream state
+ was inconsistent.
*/
@@ -544,16 +555,29 @@ ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm,
compression at the expense of memory usage. The default value is 15 if
deflateInit is used instead.
+ For the current implementation of deflate(), a windowBits value of 8 (a
+ window size of 256 bytes) is not supported. As a result, a request for 8
+ will result in 9 (a 512-byte window). In that case, providing 8 to
+ inflateInit2() will result in an error when the zlib header with 9 is
+ checked against the initialization of inflate(). The remedy is to not use 8
+ with deflateInit2() with this initialization, or at least in that case use 9
+ with inflateInit2().
+
windowBits can also be -8..-15 for raw deflate. In this case, -windowBits
determines the window size. deflate() will then generate raw deflate data
- with no zlib header or trailer, and will not compute an adler32 check value.
+ with no zlib header or trailer, and will not compute a check value.
windowBits can also be greater than 15 for optional gzip encoding. Add
16 to windowBits to write a simple gzip header and trailer around the
compressed data instead of a zlib wrapper. The gzip header will have no
file name, no extra data, no comment, no modification time (set to zero), no
- header crc, and the operating system will be set to 255 (unknown). If a
- gzip stream is being written, strm->adler is a crc32 instead of an adler32.
+ header crc, and the operating system will be set to the appropriate value,
+ if the operating system was determined at compile time. If a gzip stream is
+ being written, strm->adler is a CRC-32 instead of an Adler-32.
+
+ For raw deflate or gzip encoding, a request for a 256-byte window is
+ rejected as invalid, since only the zlib header provides a means of
+ transmitting the window size to the decompressor.
The memLevel parameter specifies how much memory should be allocated
for the internal compression state. memLevel=1 uses minimum memory but is
@@ -614,12 +638,12 @@ ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm,
addition, the current implementation of deflate will use at most the window
size minus 262 bytes of the provided dictionary.
- Upon return of this function, strm->adler is set to the adler32 value
+ Upon return of this function, strm->adler is set to the Adler-32 value
of the dictionary; the decompressor may later use this value to determine
- which dictionary has been used by the compressor. (The adler32 value
+ which dictionary has been used by the compressor. (The Adler-32 value
applies to the whole dictionary even if only a subset of the dictionary is
actually used by the compressor.) If a raw deflate was requested, then the
- adler32 value is not computed and strm->adler is not set.
+ Adler-32 value is not computed and strm->adler is not set.
deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a
parameter is invalid (e.g. dictionary being Z_NULL) or the stream state is
@@ -628,6 +652,28 @@ ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm,
not perform any compression: this will be done by deflate().
*/
+ZEXTERN int ZEXPORT deflateGetDictionary OF((z_streamp strm,
+ Bytef *dictionary,
+ uInt *dictLength));
+/*
+ Returns the sliding dictionary being maintained by deflate. dictLength is
+ set to the number of bytes in the dictionary, and that many bytes are copied
+ 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.
+
+ 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
+ to 258 bytes less in that case, due to how zlib's implementation of deflate
+ manages the sliding window and lookahead for matches, where matches can be
+ up to 258 bytes long. If the application needs the last window-size bytes of
+ input, then that would need to be saved by the application outside of zlib.
+
+ deflateGetDictionary returns Z_OK on success, or Z_STREAM_ERROR if the
+ stream state is inconsistent.
+*/
+
ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest,
z_streamp source));
/*
@@ -648,10 +694,10 @@ ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest,
ZEXTERN int ZEXPORT deflateReset OF((z_streamp strm));
/*
- This function is equivalent to deflateEnd followed by deflateInit,
- but does not free and reallocate all the internal compression state. The
- stream will keep the same compression level and any other attributes that
- may have been set by deflateInit2.
+ This function is equivalent to deflateEnd followed by deflateInit, but
+ does not free and reallocate the internal compression state. The stream
+ will leave the compression level and any other attributes that may have been
+ set unchanged.
deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source
stream state was inconsistent (such as zalloc or state being Z_NULL).
@@ -662,20 +708,36 @@ ZEXTERN int ZEXPORT deflateParams OF((z_streamp strm,
int strategy));
/*
Dynamically update the compression level and compression strategy. The
- interpretation of level and strategy is as in deflateInit2. This can be
+ interpretation of level and strategy is as in deflateInit2(). This can be
used to switch between compression and straight copy of the input data, or
to switch to a different kind of input data requiring a different strategy.
- If the compression level is changed, the input available so far is
- compressed with the old level (and may be flushed); the new level will take
- effect only at the next call of deflate().
-
- Before the call of deflateParams, the stream state must be set as for
- a call of deflate(), since the currently available input may have to be
- compressed and flushed. In particular, strm->avail_out must be non-zero.
-
- deflateParams returns Z_OK if success, Z_STREAM_ERROR if the source
- stream state was inconsistent or if a parameter was invalid, Z_BUF_ERROR if
- strm->avail_out was zero.
+ If the compression approach (which is a function of the level) or the
+ strategy is changed, and if any input has been consumed in a previous
+ deflate() call, then the input available so far is compressed with the old
+ level and strategy using deflate(strm, Z_BLOCK). There are three approaches
+ for the compression levels 0, 1..3, and 4..9 respectively. The new level
+ and strategy will take effect at the next call of deflate().
+
+ If a deflate(strm, Z_BLOCK) is performed by deflateParams(), and it does
+ not have enough output space to complete, then the parameter change will not
+ take effect. In this case, deflateParams() can be called again with the
+ same parameters and more output space to try again.
+
+ In order to assure a change in the parameters on the first try, the
+ deflate stream should be flushed using deflate() with Z_BLOCK or other flush
+ request until strm.avail_out is not zero, before calling deflateParams().
+ Then no more input data should be provided before the deflateParams() call.
+ If this is done, the old level and strategy will be applied to the data
+ compressed before deflateParams(), and the new level and strategy will be
+ applied to the the data compressed after deflateParams().
+
+ deflateParams returns Z_OK on success, Z_STREAM_ERROR if the source stream
+ state was inconsistent or if a parameter was invalid, or Z_BUF_ERROR if
+ there was not enough output space to complete the compression of the
+ available input data before a change in the strategy or approach. Note that
+ in the case of a Z_BUF_ERROR, the parameters are not changed. A return
+ value of Z_BUF_ERROR is not fatal, in which case deflateParams() can be
+ retried with more output space.
*/
ZEXTERN int ZEXPORT deflateTune OF((z_streamp strm,
@@ -793,7 +855,7 @@ ZEXTERN int ZEXPORT inflateInit2 OF((z_streamp strm,
is for use with other formats that use the deflate compressed data format
such as zip. Those formats provide their own check values. If a custom
format is developed using the raw deflate format for compressed data, it is
- recommended that a check value such as an adler32 or a crc32 be applied to
+ recommended that a check value such as an Adler-32 or a CRC-32 be applied to
the uncompressed data as is done in the zlib, gzip, and zip formats. For
most applications, the zlib format should be used as is. Note that comments
above on the use in deflateInit2() applies to the magnitude of windowBits.
@@ -802,7 +864,10 @@ ZEXTERN int ZEXPORT inflateInit2 OF((z_streamp strm,
32 to windowBits to enable zlib and gzip decoding with automatic header
detection, or add 16 to decode only the gzip format (the zlib format will
return a Z_DATA_ERROR). If a gzip stream is being decoded, strm->adler is a
- crc32 instead of an adler32.
+ CRC-32 instead of an Adler-32. Unlike the gunzip utility and gzread() (see
+ below), inflate() will not automatically decode concatenated gzip streams.
+ inflate() will return Z_STREAM_END at the end of the gzip stream. The state
+ would need to be reset to continue decoding a subsequent gzip stream.
inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
memory, Z_VERSION_ERROR if the zlib library version is incompatible with the
@@ -823,7 +888,7 @@ ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm,
Initializes the decompression dictionary from the given uncompressed byte
sequence. This function must be called immediately after a call of inflate,
if that call returned Z_NEED_DICT. The dictionary chosen by the compressor
- can be determined from the adler32 value returned by that call of inflate.
+ can be determined from the Adler-32 value returned by that call of inflate.
The compressor and decompressor must use exactly the same dictionary (see
deflateSetDictionary). For raw inflate, this function can be called at any
time to set the dictionary. If the provided dictionary is smaller than the
@@ -834,7 +899,7 @@ ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm,
inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a
parameter is invalid (e.g. dictionary being Z_NULL) or the stream state is
inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the
- expected one (incorrect adler32 value). inflateSetDictionary does not
+ expected one (incorrect Adler-32 value). inflateSetDictionary does not
perform any decompression: this will be done by subsequent calls of
inflate().
*/
@@ -892,7 +957,7 @@ ZEXTERN int ZEXPORT inflateCopy OF((z_streamp dest,
ZEXTERN int ZEXPORT inflateReset OF((z_streamp strm));
/*
This function is equivalent to inflateEnd followed by inflateInit,
- but does not free and reallocate all the internal decompression state. The
+ but does not free and reallocate the internal decompression state. The
stream will keep attributes that may have been set by inflateInit2.
inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source
@@ -904,7 +969,9 @@ ZEXTERN int ZEXPORT inflateReset2 OF((z_streamp strm,
/*
This function is the same as inflateReset, but it also permits changing
the wrap and window size requests. The windowBits parameter is interpreted
- the same as it is for inflateInit2.
+ the same as it is for inflateInit2. If the window size is changed, then the
+ memory allocated for the window is freed, and the window will be reallocated
+ by inflate() if needed.
inflateReset2 returns Z_OK if success, or Z_STREAM_ERROR if the source
stream state was inconsistent (such as zalloc or state being Z_NULL), or if
@@ -956,7 +1023,7 @@ ZEXTERN long ZEXPORT inflateMark OF((z_streamp strm));
location in the input stream can be determined from avail_in and data_type
as noted in the description for the Z_BLOCK flush parameter for inflate.
- inflateMark returns the value noted above or -1 << 16 if the provided
+ inflateMark returns the value noted above, or -65536 if the provided
source stream state was inconsistent.
*/
@@ -1048,9 +1115,9 @@ ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm,
This routine would normally be used in a utility that reads zip or gzip
files and writes out uncompressed files. The utility would decode the
header and process the trailer on its own, hence this routine expects only
- the raw deflate stream to decompress. This is different from the normal
- behavior of inflate(), which expects either a zlib or gzip header and
- trailer around the deflate stream.
+ the raw deflate stream to decompress. This is different from the default
+ behavior of inflate(), which expects a zlib header and trailer around the
+ deflate stream.
inflateBack() uses two subroutines supplied by the caller that are then
called by inflateBack() for input and output. inflateBack() calls those
@@ -1059,12 +1126,12 @@ ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm,
parameters and return types are defined above in the in_func and out_func
typedefs. inflateBack() will call in(in_desc, &buf) which should return the
number of bytes of provided input, and a pointer to that input in buf. If
- there is no input available, in() must return zero--buf is ignored in that
- case--and inflateBack() will return a buffer error. inflateBack() will call
- out(out_desc, buf, len) to write the uncompressed data buf[0..len-1]. out()
- should return zero on success, or non-zero on failure. If out() returns
- non-zero, inflateBack() will return with an error. Neither in() nor out()
- are permitted to change the contents of the window provided to
+ there is no input available, in() must return zero -- buf is ignored in that
+ case -- and inflateBack() will return a buffer error. inflateBack() will
+ call out(out_desc, buf, len) to write the uncompressed data buf[0..len-1].
+ out() should return zero on success, or non-zero on failure. If out()
+ returns non-zero, inflateBack() will return with an error. Neither in() nor
+ out() are permitted to change the contents of the window provided to
inflateBackInit(), which is also the buffer that out() uses to write from.
The length written by out() will be at most the window size. Any non-zero
amount of input may be provided by in().
@@ -1092,7 +1159,7 @@ ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm,
using strm->next_in which will be Z_NULL only if in() returned an error. If
strm->next_in is not Z_NULL, then the Z_BUF_ERROR was due to out() returning
non-zero. (in() will always be called before out(), so strm->next_in is
- assured to be defined if out() returns non-zero.) Note that inflateBack()
+ assured to be defined if out() returns non-zero.) Note that inflateBack()
cannot return Z_OK.
*/
@@ -1114,7 +1181,7 @@ ZEXTERN uLong ZEXPORT zlibCompileFlags OF((void));
7.6: size of z_off_t
Compiler, assembler, and debug options:
- 8: DEBUG
+ 8: ZLIB_DEBUG
9: ASMV or ASMINF -- use ASM code
10: ZLIB_WINAPI -- exported functions use the WINAPI calling convention
11: 0 (reserved)
@@ -1164,7 +1231,8 @@ ZEXTERN int ZEXPORT compress OF((Bytef *dest, uLongf *destLen,
the byte length of the source buffer. Upon entry, destLen is the total size
of the destination buffer, which must be at least the value returned by
compressBound(sourceLen). Upon exit, destLen is the actual size of the
- compressed buffer.
+ compressed data. compress() is equivalent to compress2() with a level
+ parameter of Z_DEFAULT_COMPRESSION.
compress returns Z_OK if success, Z_MEM_ERROR if there was not
enough memory, Z_BUF_ERROR if there was not enough room in the output
@@ -1180,7 +1248,7 @@ ZEXTERN int ZEXPORT compress2 OF((Bytef *dest, uLongf *destLen,
length of the source buffer. Upon entry, destLen is the total size of the
destination buffer, which must be at least the value returned by
compressBound(sourceLen). Upon exit, destLen is the actual size of the
- compressed buffer.
+ compressed data.
compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
memory, Z_BUF_ERROR if there was not enough room in the output buffer,
@@ -1203,7 +1271,7 @@ ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen,
uncompressed data. (The size of the uncompressed data must have been saved
previously by the compressor and transmitted to the decompressor by some
mechanism outside the scope of this compression library.) Upon exit, destLen
- is the actual size of the uncompressed buffer.
+ is the actual size of the uncompressed data.
uncompress returns Z_OK if success, Z_MEM_ERROR if there was not
enough memory, Z_BUF_ERROR if there was not enough room in the output
@@ -1212,6 +1280,14 @@ ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen,
buffer with the uncompressed data up to that point.
*/
+ZEXTERN int ZEXPORT uncompress2 OF((Bytef *dest, uLongf *destLen,
+ const Bytef *source, uLong *sourceLen));
+/*
+ Same as uncompress, except that sourceLen is a pointer, where the
+ length of the source is *sourceLen. On return, *sourceLen is the number of
+ source bytes consumed.
+*/
+
/* gzip file access functions */
/*
@@ -1290,10 +1366,9 @@ ZEXTERN int ZEXPORT gzbuffer OF((gzFile file, unsigned size));
default buffer size is 8192 bytes. This function must be called after
gzopen() or gzdopen(), and before any other calls that read or write the
file. The buffer memory allocation is always deferred to the first read or
- write. Two buffers are allocated, either both of the specified size when
- writing, or one of the specified size and the other twice that size when
- reading. A larger buffer size of, for example, 64K or 128K bytes will
- noticeably increase the speed of decompression (reading).
+ write. Three times that size in buffer space is allocated. A larger buffer
+ size of, for example, 64K or 128K bytes will noticeably increase the speed
+ of decompression (reading).
The new buffer size also affects the maximum length for gzprintf().
@@ -1304,10 +1379,12 @@ ZEXTERN int ZEXPORT gzbuffer OF((gzFile file, unsigned size));
ZEXTERN int ZEXPORT gzsetparams OF((gzFile file, int level, int strategy));
/*
Dynamically update the compression level or strategy. See the description
- of deflateInit2 for the meaning of these parameters.
+ of deflateInit2 for the meaning of these parameters. Previously provided
+ data is flushed before the parameter change.
- gzsetparams returns Z_OK if success, or Z_STREAM_ERROR if the file was not
- opened for writing.
+ gzsetparams returns Z_OK if success, Z_STREAM_ERROR if the file was not
+ opened for writing, Z_ERRNO if there is an error writing the flushed data,
+ or Z_MEM_ERROR if there is a memory allocation error.
*/
ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len));
@@ -1335,7 +1412,35 @@ ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len));
case.
gzread returns the number of uncompressed bytes actually read, less than
- len for end of file, or -1 for error.
+ len for end of file, or -1 for error. If len is too large to fit in an int,
+ then nothing is read, -1 is returned, and the error state is set to
+ Z_STREAM_ERROR.
+*/
+
+ZEXTERN z_size_t ZEXPORT gzfread OF((voidp buf, z_size_t size, z_size_t nitems,
+ gzFile file));
+/*
+ Read up to nitems items of size size from file to buf, otherwise operating
+ as gzread() does. This duplicates the interface of stdio's fread(), with
+ size_t request and return types. If the library defines size_t, then
+ z_size_t is identical to size_t. If not, then z_size_t is an unsigned
+ integer type that can contain a pointer.
+
+ gzfread() returns the number of full items read of size size, or zero if
+ the end of the file was reached and a full item could not be read, or if
+ there was an error. gzerror() must be consulted if zero is returned in
+ order to determine if there was an error. If the multiplication of size and
+ nitems overflows, i.e. the product does not fit in a z_size_t, then nothing
+ is read, zero is returned, and the error state is set to Z_STREAM_ERROR.
+
+ 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
+ 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.
*/
ZEXTERN int ZEXPORT gzwrite OF((gzFile file,
@@ -1346,19 +1451,33 @@ ZEXTERN int ZEXPORT gzwrite OF((gzFile file,
error.
*/
+ZEXTERN z_size_t ZEXPORT gzfwrite OF((voidpc buf, z_size_t size,
+ z_size_t nitems, gzFile file));
+/*
+ gzfwrite() writes nitems items of size size from buf to file, duplicating
+ the interface of stdio's fwrite(), with size_t request and return types. If
+ the library defines size_t, then z_size_t is identical to size_t. If not,
+ then z_size_t is an unsigned integer type that can contain a pointer.
+
+ gzfwrite() returns the number of full items written of size size, or zero
+ if there was an error. If the multiplication of size and nitems overflows,
+ i.e. the product does not fit in a z_size_t, then nothing is written, zero
+ is returned, and the error state is set to Z_STREAM_ERROR.
+*/
+
ZEXTERN int ZEXPORTVA gzprintf Z_ARG((gzFile file, const char *format, ...));
/*
Converts, formats, and writes the arguments to the compressed file under
control of the format string, as in fprintf. gzprintf returns the number of
- uncompressed bytes actually written, or 0 in case of error. The number of
- uncompressed bytes written is limited to 8191, or one less than the buffer
- size given to gzbuffer(). The caller should assure that this limit is not
- exceeded. If it is exceeded, then gzprintf() will return an error (0) with
- nothing written. In this case, there may also be a buffer overflow with
- unpredictable consequences, which is possible only if zlib was compiled with
- the insecure functions sprintf() or vsprintf() because the secure snprintf()
- or vsnprintf() functions were not available. This can be determined using
- zlibCompileFlags().
+ uncompressed bytes actually written, or a negative zlib error code in case
+ of error. The number of uncompressed bytes written is limited to 8191, or
+ one less than the buffer size given to gzbuffer(). The caller should assure
+ that this limit is not exceeded. If it is exceeded, then gzprintf() will
+ return an error (0) with nothing written. In this case, there may also be a
+ buffer overflow with unpredictable consequences, which is possible only if
+ zlib was compiled with the insecure functions sprintf() or vsprintf()
+ because the secure snprintf() or vsnprintf() functions were not available.
+ This can be determined using zlibCompileFlags().
*/
ZEXTERN int ZEXPORT gzputs OF((gzFile file, const char *s));
@@ -1418,7 +1537,7 @@ ZEXTERN int ZEXPORT gzflush OF((gzFile file, int flush));
If the flush parameter is Z_FINISH, the remaining data is written and the
gzip stream is completed in the output. If gzwrite() is called again, a new
gzip stream will be started in the output. gzread() is able to read such
- concatented gzip streams.
+ concatenated gzip streams.
gzflush should be called only when strictly necessary because it will
degrade compression if called too often.
@@ -1572,7 +1691,7 @@ ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len));
return the updated checksum. If buf is Z_NULL, this function returns the
required initial value for the checksum.
- An Adler-32 checksum is almost as reliable as a CRC32 but can be computed
+ An Adler-32 checksum is almost as reliable as a CRC-32 but can be computed
much faster.
Usage example:
@@ -1585,6 +1704,12 @@ ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len));
if (adler != original_adler) error();
*/
+ZEXTERN uLong ZEXPORT adler32_z OF((uLong adler, const Bytef *buf,
+ z_size_t len));
+/*
+ Same as adler32(), but with a size_t length.
+*/
+
/*
ZEXTERN uLong ZEXPORT adler32_combine OF((uLong adler1, uLong adler2,
z_off_t len2));
@@ -1614,6 +1739,12 @@ ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len));
if (crc != original_crc) error();
*/
+ZEXTERN uLong ZEXPORT crc32_z OF((uLong adler, const Bytef *buf,
+ z_size_t len));
+/*
+ Same as crc32(), but with a size_t length.
+*/
+
/*
ZEXTERN uLong ZEXPORT crc32_combine OF((uLong crc1, uLong crc2, z_off_t len2));
@@ -1644,19 +1775,35 @@ ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits,
unsigned char FAR *window,
const char *version,
int stream_size));
-#define deflateInit(strm, level) \
- deflateInit_((strm), (level), ZLIB_VERSION, (int)sizeof(z_stream))
-#define inflateInit(strm) \
- inflateInit_((strm), ZLIB_VERSION, (int)sizeof(z_stream))
-#define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \
- deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\
- (strategy), ZLIB_VERSION, (int)sizeof(z_stream))
-#define inflateInit2(strm, windowBits) \
- inflateInit2_((strm), (windowBits), ZLIB_VERSION, \
- (int)sizeof(z_stream))
-#define inflateBackInit(strm, windowBits, window) \
- inflateBackInit_((strm), (windowBits), (window), \
- ZLIB_VERSION, (int)sizeof(z_stream))
+#ifdef Z_PREFIX_SET
+# define z_deflateInit(strm, level) \
+ deflateInit_((strm), (level), ZLIB_VERSION, (int)sizeof(z_stream))
+# define z_inflateInit(strm) \
+ inflateInit_((strm), ZLIB_VERSION, (int)sizeof(z_stream))
+# define z_deflateInit2(strm, level, method, windowBits, memLevel, strategy) \
+ deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\
+ (strategy), ZLIB_VERSION, (int)sizeof(z_stream))
+# define z_inflateInit2(strm, windowBits) \
+ inflateInit2_((strm), (windowBits), ZLIB_VERSION, \
+ (int)sizeof(z_stream))
+# define z_inflateBackInit(strm, windowBits, window) \
+ inflateBackInit_((strm), (windowBits), (window), \
+ ZLIB_VERSION, (int)sizeof(z_stream))
+#else
+# define deflateInit(strm, level) \
+ deflateInit_((strm), (level), ZLIB_VERSION, (int)sizeof(z_stream))
+# define inflateInit(strm) \
+ inflateInit_((strm), ZLIB_VERSION, (int)sizeof(z_stream))
+# define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \
+ deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\
+ (strategy), ZLIB_VERSION, (int)sizeof(z_stream))
+# define inflateInit2(strm, windowBits) \
+ inflateInit2_((strm), (windowBits), ZLIB_VERSION, \
+ (int)sizeof(z_stream))
+# define inflateBackInit(strm, windowBits, window) \
+ inflateBackInit_((strm), (windowBits), (window), \
+ ZLIB_VERSION, (int)sizeof(z_stream))
+#endif
#ifndef Z_SOLO
@@ -1676,10 +1823,10 @@ ZEXTERN int ZEXPORT gzgetc_ OF((gzFile file)); /* backward compatibility */
#ifdef Z_PREFIX_SET
# undef z_gzgetc
# define z_gzgetc(g) \
- ((g)->have ? ((g)->have--, (g)->pos++, *((g)->next)++) : gzgetc(g))
+ ((g)->have ? ((g)->have--, (g)->pos++, *((g)->next)++) : (gzgetc)(g))
#else
# define gzgetc(g) \
- ((g)->have ? ((g)->have--, (g)->pos++, *((g)->next)++) : gzgetc(g))
+ ((g)->have ? ((g)->have--, (g)->pos++, *((g)->next)++) : (gzgetc)(g))
#endif
/* provide 64-bit offset functions if _LARGEFILE64_SOURCE defined, and/or
@@ -1737,19 +1884,16 @@ ZEXTERN int ZEXPORT gzgetc_ OF((gzFile file)); /* backward compatibility */
#endif /* !Z_SOLO */
-/* hack for buggy compilers */
-#if !defined(ZUTIL_H) && !defined(NO_DUMMY_DECL)
- struct internal_state {int dummy;};
-#endif
-
/* undocumented functions */
ZEXTERN const char * ZEXPORT zError OF((int));
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 int ZEXPORT inflateResetKeep OF((z_streamp));
ZEXTERN int ZEXPORT deflateResetKeep OF((z_streamp));
-#if defined(_WIN32) && !defined(Z_SOLO)
+#if (defined(_WIN32) || defined(__CYGWIN__)) && !defined(Z_SOLO)
ZEXTERN gzFile ZEXPORT gzopen_w OF((const wchar_t *path,
const char *mode));
#endif
diff --git a/erts/emulator/zlib/zlib.mk b/erts/emulator/zlib/zlib.mk
index 3f0d64d250..b51b4ec8d6 100644
--- a/erts/emulator/zlib/zlib.mk
+++ b/erts/emulator/zlib/zlib.mk
@@ -52,7 +52,7 @@ ifeq ($(TYPE),gcov)
ZLIB_CFLAGS = -O0 -fprofile-arcs -ftest-coverage $(DEBUG_CFLAGS) $(DEFS) $(THR_DEFS)
else # gcov
ifeq ($(TYPE),debug)
-ZLIB_CFLAGS = $(DEBUG_CFLAGS) $(DEFS) $(THR_DEFS)
+ZLIB_CFLAGS = -DZLIB_DEBUG=1 $(DEBUG_CFLAGS) $(DEFS) $(THR_DEFS)
else # debug
ZLIB_CFLAGS = $(subst -O2, -O3, $(CONFIGURE_CFLAGS) $(DEFS) $(THR_DEFS))
#ZLIB_CFLAGS=-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7
@@ -62,6 +62,9 @@ ZLIB_CFLAGS = $(subst -O2, -O3, $(CONFIGURE_CFLAGS) $(DEFS) $(THR_DEFS))
endif # debug
endif # gcov
+# Don't fail if _LFS64_LARGEFILE is undefined
+ZLIB_CFLAGS := $(filter-out -Werror=undef,$(ZLIB_CFLAGS))
+
ifeq ($(TARGET), win32)
$(ZLIB_LIBRARY): $(ZLIB_OBJS)
$(V_AR) -out:$@ $(ZLIB_OBJS)
diff --git a/erts/emulator/zlib/zutil.c b/erts/emulator/zlib/zutil.c
index 27a8af4a2b..a76c6b0c7e 100644
--- a/erts/emulator/zlib/zutil.c
+++ b/erts/emulator/zlib/zutil.c
@@ -1,33 +1,27 @@
/* zutil.c -- target dependent utility functions for the compression library
- * Copyright (C) 1995-2005, 2010, 2011, 2012 Jean-loup Gailly.
+ * Copyright (C) 1995-2017 Jean-loup Gailly
* For conditions of distribution and use, see copyright notice in zlib.h
*/
/* @(#) $Id$ */
-#ifdef HAVE_CONFIG_H
-# include "config.h"
-#endif
#include "zutil.h"
#ifndef Z_SOLO
# include "gzguts.h"
#endif
-#ifndef NO_DUMMY_DECL
-struct internal_state {int dummy;}; /* for buggy compilers */
-#endif
-
z_const char * const z_errmsg[10] = {
-"need dictionary", /* Z_NEED_DICT 2 */
-"stream end", /* Z_STREAM_END 1 */
-"", /* Z_OK 0 */
-"file error", /* Z_ERRNO (-1) */
-"stream error", /* Z_STREAM_ERROR (-2) */
-"data error", /* Z_DATA_ERROR (-3) */
-"insufficient memory", /* Z_MEM_ERROR (-4) */
-"buffer error", /* Z_BUF_ERROR (-5) */
-"incompatible version",/* Z_VERSION_ERROR (-6) */
-""};
+ (z_const char *)"need dictionary", /* Z_NEED_DICT 2 */
+ (z_const char *)"stream end", /* Z_STREAM_END 1 */
+ (z_const char *)"", /* Z_OK 0 */
+ (z_const char *)"file error", /* Z_ERRNO (-1) */
+ (z_const char *)"stream error", /* Z_STREAM_ERROR (-2) */
+ (z_const char *)"data error", /* Z_DATA_ERROR (-3) */
+ (z_const char *)"insufficient memory", /* Z_MEM_ERROR (-4) */
+ (z_const char *)"buffer error", /* Z_BUF_ERROR (-5) */
+ (z_const char *)"incompatible version",/* Z_VERSION_ERROR (-6) */
+ (z_const char *)""
+};
const char * ZEXPORT zlibVersion()
@@ -64,7 +58,7 @@ uLong ZEXPORT zlibCompileFlags()
case 8: flags += 2 << 6; break;
default: flags += 3 << 6;
}
-#ifdef DEBUG
+#ifdef ZLIB_DEBUG
flags += 1 << 8;
#endif
#if defined(ASMV) || defined(ASMINF)
@@ -118,8 +112,8 @@ uLong ZEXPORT zlibCompileFlags()
return flags;
}
-#ifdef DEBUG
-
+#ifdef ZLIB_DEBUG
+#include <stdlib.h>
# ifndef verbose
# define verbose 0
# endif
@@ -222,9 +216,11 @@ local ptr_table table[MAX_PTR];
voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, unsigned items, unsigned size)
{
- voidpf buf = opaque; /* just to make some compilers happy */
+ voidpf buf;
ulg bsize = (ulg)items*size;
+ (void)opaque;
+
/* If we allocate less than 65520 bytes, we assume that farmalloc
* will return a usable pointer which doesn't have to be normalized.
*/
@@ -247,6 +243,9 @@ voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, unsigned items, unsigned size)
void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr)
{
int n;
+
+ (void)opaque;
+
if (*(ush*)&ptr != 0) { /* object < 64K */
farfree(ptr);
return;
@@ -262,7 +261,6 @@ void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr)
next_ptr--;
return;
}
- ptr = opaque; /* just to make some compilers happy */
Assert(0, "zcfree: ptr not found");
}
@@ -281,13 +279,13 @@ void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr)
voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, uInt items, uInt size)
{
- if (opaque) opaque = 0; /* to make compiler happy */
+ (void)opaque;
return _halloc((long)items, size);
}
void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr)
{
- if (opaque) opaque = 0; /* to make compiler happy */
+ (void)opaque;
_hfree(ptr);
}
@@ -309,7 +307,7 @@ voidpf ZLIB_INTERNAL zcalloc (opaque, items, size)
unsigned items;
unsigned size;
{
- if (opaque) items += size - size; /* make compiler happy */
+ (void)opaque;
return sizeof(uInt) > 2 ? (voidpf)malloc(items * size) :
(voidpf)calloc(items, size);
}
@@ -318,8 +316,8 @@ void ZLIB_INTERNAL zcfree (opaque, ptr)
voidpf opaque;
voidpf ptr;
{
+ (void)opaque;
free(ptr);
- if (opaque) return; /* make compiler happy */
}
#endif /* MY_ZCALLOC */
diff --git a/erts/emulator/zlib/zutil.h b/erts/emulator/zlib/zutil.h
index 24ab06b1cf..b079ea6a80 100644
--- a/erts/emulator/zlib/zutil.h
+++ b/erts/emulator/zlib/zutil.h
@@ -1,5 +1,5 @@
/* zutil.h -- internal interface and configuration of the compression library
- * Copyright (C) 1995-2013 Jean-loup Gailly.
+ * Copyright (C) 1995-2016 Jean-loup Gailly, Mark Adler
* For conditions of distribution and use, see copyright notice in zlib.h
*/
@@ -36,7 +36,9 @@
#ifndef local
# define local static
#endif
-/* compile with -Dlocal if your debugger can't find static symbols */
+/* since "static" is used to mean two completely different things in C, we
+ define "local" for the non-static meaning of "static", for readability
+ (compile with -Dlocal if your debugger can't find static symbols) */
typedef unsigned char uch;
typedef uch FAR uchf;
@@ -98,28 +100,38 @@ extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */
#endif
#ifdef AMIGA
-# define OS_CODE 0x01
+# define OS_CODE 1
#endif
#if defined(VAXC) || defined(VMS)
-# define OS_CODE 0x02
+# define OS_CODE 2
# define F_OPEN(name, mode) \
fopen((name), (mode), "mbc=60", "ctx=stm", "rfm=fix", "mrs=512")
#endif
+#ifdef __370__
+# if __TARGET_LIB__ < 0x20000000
+# define OS_CODE 4
+# elif __TARGET_LIB__ < 0x40000000
+# define OS_CODE 11
+# else
+# define OS_CODE 8
+# endif
+#endif
+
#if defined(ATARI) || defined(atarist)
-# define OS_CODE 0x05
+# define OS_CODE 5
#endif
#ifdef OS2
-# define OS_CODE 0x06
+# define OS_CODE 6
# if defined(M_I86) && !defined(Z_SOLO)
# include <malloc.h>
# endif
#endif
#if defined(MACOS) || defined(TARGET_OS_MAC)
-# define OS_CODE 0x07
+# define OS_CODE 7
# ifndef Z_SOLO
# if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os
# include <unix.h> /* for fdopen */
@@ -131,18 +143,24 @@ extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */
# endif
#endif
-#ifdef TOPS20
-# define OS_CODE 0x0a
+#ifdef __acorn
+# define OS_CODE 13
#endif
-#ifdef WIN32
-# ifndef __CYGWIN__ /* Cygwin is Unix, not Win32 */
-# define OS_CODE 0x0b
-# endif
+#if defined(WIN32) && !defined(__CYGWIN__)
+# define OS_CODE 10
+#endif
+
+#ifdef _BEOS_
+# define OS_CODE 16
+#endif
+
+#ifdef __TOS_OS400__
+# define OS_CODE 18
#endif
-#ifdef __50SERIES /* Prime/PRIMOS */
-# define OS_CODE 0x0f
+#ifdef __APPLE__
+# define OS_CODE 19
#endif
#if defined(_BEOS_) || defined(RISCOS)
@@ -177,7 +195,7 @@ extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */
/* common defaults */
#ifndef OS_CODE
-# define OS_CODE 0x03 /* assume Unix */
+# define OS_CODE 3 /* assume Unix */
#endif
#ifndef F_OPEN
@@ -216,7 +234,7 @@ extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */
#endif
/* Diagnostic functions */
-#ifdef DEBUG
+#ifdef ZLIB_DEBUG
# include <stdio.h>
extern int ZLIB_INTERNAL z_verbose;
extern void ZLIB_INTERNAL z_error OF((char *m));
diff --git a/erts/epmd/src/epmd.c b/erts/epmd/src/epmd.c
index 44e997e609..8313678b5d 100644
--- a/erts/epmd/src/epmd.c
+++ b/erts/epmd/src/epmd.c
@@ -437,6 +437,11 @@ static void usage(EpmdVars *g)
fprintf(stderr, " epmd -kill even if there "
"are registered nodes.\n");
fprintf(stderr, " Also allows forced unregister (epmd -stop).\n");
+#ifdef HAVE_SYSTEMD_DAEMON
+ fprintf(stderr, " -systemd\n");
+ fprintf(stderr, " Wait for socket from systemd. The option makes sense\n");
+ fprintf(stderr, " when started from .socket unit.\n");
+#endif /* HAVE_SYSTEMD_DAEMON */
fprintf(stderr, "\nDbgExtra options\n");
fprintf(stderr, " -packet_timeout Seconds\n");
fprintf(stderr, " Set the number of seconds a connection can be\n");
@@ -462,11 +467,6 @@ static void usage(EpmdVars *g)
fprintf(stderr, " Forcibly unregisters a name with epmd\n");
fprintf(stderr, " (only allowed if -relaxed_command_check was given when \n");
fprintf(stderr, " epmd was started).\n");
-#ifdef HAVE_SYSTEMD_DAEMON
- fprintf(stderr, " -systemd\n");
- fprintf(stderr, " Wait for socket from systemd. The option makes sense\n");
- fprintf(stderr, " when started from .socket unit.\n");
-#endif /* HAVE_SYSTEMD_DAEMON */
epmd_cleanup_exit(g,1);
}
diff --git a/erts/etc/common/erlexec.c b/erts/etc/common/erlexec.c
index 0cb01fd4ef..ec4a4ead23 100644
--- a/erts/etc/common/erlexec.c
+++ b/erts/etc/common/erlexec.c
@@ -819,10 +819,8 @@ int main(int argc, char **argv)
case '+':
switch (argv[i][1]) {
- case '#':
case 'a':
case 'A':
- case 'b':
case 'C':
case 'e':
case 'i':
diff --git a/erts/etc/common/heart.c b/erts/etc/common/heart.c
index bd218ff725..bb843a616b 100644
--- a/erts/etc/common/heart.c
+++ b/erts/etc/common/heart.c
@@ -500,7 +500,7 @@ message_loop(erlin_fd, erlout_fd)
#if defined(__WIN32__)
static void
-kill_old_erlang(void){
+kill_old_erlang(int reason){
HANDLE erlh;
DWORD exit_code;
char* envvar = NULL;
@@ -536,7 +536,8 @@ kill_old_erlang(void){
}
#else
static void
-kill_old_erlang(void){
+kill_old_erlang(int reason)
+{
pid_t pid;
int i, res;
int sig = SIGKILL;
@@ -546,14 +547,25 @@ kill_old_erlang(void){
if (envvar && strcmp(envvar, "TRUE") == 0)
return;
- envvar = get_env(HEART_KILL_SIGNAL);
- if (envvar && strcmp(envvar, "SIGABRT") == 0) {
- print_error("kill signal SIGABRT requested");
- sig = SIGABRT;
- }
-
if(heart_beat_kill_pid != 0){
- pid = (pid_t) heart_beat_kill_pid;
+ pid = (pid_t) heart_beat_kill_pid;
+ if (reason == R_CLOSED) {
+ print_error("Wait 5 seconds for Erlang to terminate nicely");
+ for (i=0; i < 5; ++i) {
+ res = kill(pid, 0); /* check if alive */
+ if (res < 0 && errno == ESRCH)
+ return;
+ sleep(1);
+ }
+ print_error("Erlang still alive, kill it");
+ }
+
+ envvar = get_env(HEART_KILL_SIGNAL);
+ if (envvar && strcmp(envvar, "SIGABRT") == 0) {
+ print_error("kill signal SIGABRT requested");
+ sig = SIGABRT;
+ }
+
res = kill(pid,sig);
for(i=0; i < 5 && res == 0; ++i){
sleep(1);
@@ -677,7 +689,7 @@ do_terminate(int erlin_fd, int reason) {
if(!command)
print_error("Would reboot. Terminating.");
else {
- kill_old_erlang();
+ kill_old_erlang(reason);
/* High prio combined with system() works badly indeed... */
SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS);
win_system(command);
@@ -685,7 +697,7 @@ do_terminate(int erlin_fd, int reason) {
}
free_env_val(command);
} else {
- kill_old_erlang();
+ kill_old_erlang(reason);
/* High prio combined with system() works badly indeed... */
SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS);
win_system(&cmd[0]);
@@ -697,13 +709,13 @@ do_terminate(int erlin_fd, int reason) {
if(!command)
print_error("Would reboot. Terminating.");
else {
- kill_old_erlang();
+ kill_old_erlang(reason);
ret = system(command);
print_error("Executed \"%s\" -> %d. Terminating.",command, ret);
}
free_env_val(command);
} else {
- kill_old_erlang();
+ kill_old_erlang(reason);
ret = system((char*)&cmd[0]);
print_error("Executed \"%s\" -> %d. Terminating.",cmd, ret);
}
diff --git a/erts/etc/unix/Makefile b/erts/etc/unix/Makefile
index 83c64d35fd..21a725cb88 100644
--- a/erts/etc/unix/Makefile
+++ b/erts/etc/unix/Makefile
@@ -30,7 +30,8 @@ opt debug lcnt: etc
etc: etp-commands
etp-commands: etp-commands.in
- $(gen_verbose)sed 's:@ERL_TOP@:${ERL_TOP}:g' etp-commands.in > etp-commands
+ $(gen_verbose)sed -e 's:@ERL_TOP@:${ERL_TOP}:g' \
+ etp-commands.in > etp-commands
.PHONY: docs
docs:
diff --git a/erts/etc/unix/cerl.src b/erts/etc/unix/cerl.src
index 2e034513b0..bcd64d242e 100644
--- a/erts/etc/unix/cerl.src
+++ b/erts/etc/unix/cerl.src
@@ -224,7 +224,13 @@ while [ $# -gt 0 ]; do
shift
cargs="$cargs -rr"
run_rr=yes
- skip_erlexec=yes
+ case "$1" in
+ "replay"|"ps")
+ ;;
+ *)
+ skip_erlexec=yes
+ ;;
+ esac
;;
*)
break
@@ -307,7 +313,26 @@ if [ "x$GDB" = "x" ]; then
exec $taskset1 valgrind $valgrind_xml $valgrind_log $valgrind_misc_flags $BINDIR/$EMU_NAME $sched_arg $emu_xargs "$@"
elif [ $run_rr = yes ]; then
- exec rr record --ignore-nested $BINDIR/$EMU_NAME $emu_xargs "$@"
+ if [ $1 = replay ]; then
+ shift
+ cmdfile="/tmp/.cerlgdb.$$"
+ echo "set \$etp_beam_executable = \"$BINDIR/$EMU_NAME\"" > $cmdfile
+ if [ "$1" = "-p" ]; then
+ echo 'set $etp_rr_run_until_beam = 1' >> $cmdfile
+ fi
+ cat $ROOTDIR/erts/etc/unix/etp-commands.in >> $cmdfile
+ exec rr replay -x $cmdfile $*
+ elif [ $1 = ps ]; then
+ shift
+ rr ps $* | head -1
+ ChildSetup=`rr ps $* | grep 'erl_child_setup' | awk '{ print $2 }'`
+ for CS in $ChildSetup; do
+ rr ps $* | grep -E "^$CS"
+ done
+ exit 0
+ else
+ exec rr record --ignore-nested $BINDIR/$EMU_NAME $emu_xargs "$@"
+ fi
else
exec $EXEC $xargs ${1+"$@"}
fi
diff --git a/erts/etc/unix/etp-commands.in b/erts/etc/unix/etp-commands.in
index b12a205ba7..e8dc59156f 100644
--- a/erts/etc/unix/etp-commands.in
+++ b/erts/etc/unix/etp-commands.in
@@ -149,7 +149,7 @@ define etp-1
else
# (($arg0) & 0x3) == 0
if (($arg0) == etp_the_non_value)
- printf "<the non-value>"
+ printf "<the-non-value>"
else
etp-cp-1 ($arg0)
end
@@ -1241,7 +1241,7 @@ define etp-sig-int
if $etp_sig_tag != etp_the_non_value
etp-1 $etp_sig_tag 0
else
- print "!ENCODED-DIST-MSG"
+ printf "!ENCODED-DIST-MSG"
end
if ($arg0)->m[1] != $etp_nil
printf " @token= "
@@ -1251,7 +1251,7 @@ define etp-sig-int
etp-1 ($arg0)->m[2] 0
else
if ($etp_sig_tag & 0x3f) != 0x30
- print "!INVALID-SIGNAL"
+ printf "!INVALID-SIGNAL"
else
set $etp_sig_op = (($etp_sig_tag >> 6) & 0xff)
set $etp_sig_type = (($etp_sig_tag >> 14) & 0xff)
@@ -1789,7 +1789,7 @@ define etp-pix2proc
# Args: Eterm
#
set $proc = (Process *) *((UWord *) &erts_proc.r.o.tab[((int) $arg0)])
- printf "(Process *) %p\n", $proc
+ printf "(Process*)%p\n", $proc
end
define etp-pid2proc-1
@@ -1803,7 +1803,7 @@ define etp-pid2proc
# Args: Eterm
#
etp-pid2proc-1 $arg0
- printf "(Process *) %p\n", $proc
+ printf "(Process*)%p\n", $proc
end
define etp-proc-state-int
@@ -2083,7 +2083,7 @@ define etp-process-info-int
printf "\n Flags: "
etp-proc-flags $etp_proc
if $proxy_process != 0
- printf " Pointer: (Process *) %p\n", $etp_proc
+ printf " Pointer: (Process*)%p\n", $etp_proc
printf " *** PROXY process struct *** refer to: \n"
etp-pid2proc-1 $etp_proc->common.id
etp-process-info $proc
@@ -2096,7 +2096,7 @@ define etp-process-info-int
end
end
printf " Current function: "
- if ($etp_proc->current)
+ if ($etp_proc->current && !($etp_proc->state.counter & 0x800))
etp-1 $etp_proc->current->module
printf ":"
etp-1 $etp_proc->current->function
@@ -2133,7 +2133,7 @@ define etp-process-info-int
end
printf " Parent: "
etp-1 ((Eterm)($etp_proc->parent))
- printf "\n Pointer: (Process *) %p\n", $etp_proc
+ printf "\n Pointer: (Process*)%p\n", $etp_proc
end
if ($arg1)
etp-sigqs $etp_proc
@@ -2156,6 +2156,43 @@ document etp-process-info
%---------------------------------------------------------------------------
end
+define etp-processes-free-runq-int
+ set $runq_prio = 0
+ while $runq_prio < 3
+ set $runq_proc = ($arg0)->procs.prio[$runq_prio].first
+ while $runq_proc != 0
+ if $runq_proc->state.counter & 0x400
+ printf "---\n"
+ printf " Pix: FREE -> run_queue %p\n", ($arg0)
+ etp-process-info-int $runq_proc ($arg1)
+ end
+ set $runq_proc = $runq_proc->next
+ end
+ set $runq_prio++
+ end
+end
+
+define etp-processes-free-de-int
+ set $de_ix = 0
+ while $de_ix < ($arg1)
+ set $de = ($arg0)+$de_ix
+ set $susp = $de->suspended
+ set $susp_curr = $susp
+ set $first_loop = 1
+ while $susp_curr != 0 && (($susp_curr != $susp) || $first_loop)
+ if ($susp_curr->u.pid & 0x3) == 0
+ printf "---\n"
+ printf " Pix: FREE "
+ etp $de->sysname
+ etp-process-info-int $susp_curr->u.pid ($arg2)
+ end
+ set $first_loop = 0
+ set $susp_curr = $susp_curr->next
+ end
+ set $de_ix++
+ end
+end
+
define etp-processes-int
if (!erts_initialized)
printf "No processes, since system isn't initialized!\n"
@@ -2176,11 +2213,36 @@ define etp-processes-int
set $proc_cnt--
end
if $proc_ix == $proc_printile
- printf "--- %d%% (%d / %d) searched\n", $proc_printile / $proc_decentile * 10, $proc_ix, $proc_max_ix
+ printf "--- %d%% (%d / %d) searched, looking for %d more\n", $proc_printile / $proc_decentile * 10, $proc_ix, $proc_max_ix, $proc_cnt
set $proc_printile += $proc_decentile
end
set $proc_ix++
end
+
+ ## We should also check for any FREE processes that are running
+ ## They can be found in esdp->current_process, dep->suspendees and
+ ## runq. Running FREE processes are processes that either are yielding
+ ## when exiting or running on a dirty scheduler while having exited.
+ set $sched_ix = 0
+ while $sched_ix < erts_no_schedulers
+ set $sched_data = &erts_aligned_scheduler_data[$sched_ix].esd
+ if $sched_data->current_process != 0
+ if $sched_data->current_process.state.counter & 0x400
+ printf "---\n"
+ printf " Pix: FREE -> scheduler %d\n", $sched_ix+1
+ etp-process-info-int $sched_data->current_process ($arg0)
+ end
+ end
+ etp-processes-free-runq-int $sched_data->run_queue ($arg0)
+ set $sched_ix++
+ end
+ etp-processes-free-runq-int &erts_aligned_run_queues[erts_no_run_queues].runq ($arg0)
+ etp-processes-free-runq-int &erts_aligned_run_queues[erts_no_run_queues+1].runq ($arg0)
+ etp-processes-free-de-int erts_hidden_dist_entries erts_no_of_hidden_dist_entries ($arg0)
+ etp-processes-free-de-int erts_visible_dist_entries erts_no_of_visible_dist_entries ($arg0)
+ etp-processes-free-de-int erts_pending_dist_entries erts_no_of_pending_dist_entries ($arg0)
+ etp-processes-free-de-int erts_not_connected_dist_entries erts_no_of_not_connected_dist_entries ($arg0)
+ etp-processes-free-de-int erts_this_dist_entry 1 ($arg0)
printf "---\n",
end
end
@@ -2237,9 +2299,9 @@ define etp-process-memory-info
end
printf " "
etp-1 $etp_pmem_proc->common.id
- printf ": (Process *) %p ", $etp_pmem_proc
+ printf ": (Process*)%p ", $etp_pmem_proc
if $proxy_process != 0
- printf "(Process *) %p ", $etp_pmem_proc
+ printf "(Process*)%p ", $etp_pmem_proc
printf " *** PROXY process struct *** refer to next: \n"
etp-pid2proc-1 $etp_pmem_proc->common.id
printf " -"
@@ -2313,7 +2375,7 @@ define etp-pix2port
# Args: Eterm
#
set $port = (Port *) *((UWord *) &erts_port.r.o.tab[((int) $arg0)])
- printf "(Port *) %p\n", $port
+ printf "(Port*)%p\n", $port
end
define etp-id2port-1
@@ -2327,7 +2389,7 @@ define etp-id2port
# Args: Eterm
#
etp-id2port-1 $arg0
- printf "(Port *) %p\n", $port
+ printf "(Port*)%p\n", $port
end
define etp-port-sched-flags-int
@@ -2501,7 +2563,7 @@ define etp-port-info
printf " Connected: "
set $connected = *(((Eterm *) &(((Port *) $etp_pinfo_port)->connected)))
etp-1 $connected
- printf "\n Pointer: (Port *) %p\n", $etp_pinfo_port
+ printf "\n Pointer: (Port*)%p\n", $etp_pinfo_port
end
document etp-port-info
@@ -2851,7 +2913,7 @@ define etp-scheduler-info-internal
printf " Sleep Info Flags:"
set $ssi_flags = *((Uint32 *) &$sched_data->ssi->flags)
etp-ssi-flags $ssi_flags
- printf " Pointer: (ErtsSchedulerData *) %p\n", $sched_data
+ printf " Pointer: (ErtsSchedulerData*)%p\n", $sched_data
end
define etp-run-queue-info-internal
@@ -2884,7 +2946,7 @@ define etp-run-queue-info-internal
end
set $rq_flags = *((Uint32 *) &($runq->flags))
etp-rq-flags-int $rq_flags
- printf " Pointer: (ErtsRunQueue *) %p\n", $runq
+ printf " Pointer: (ErtsRunQueue*)%p\n", $runq
end
define etp-fds
@@ -2961,7 +3023,7 @@ define etp-timer-wheel
printf "\n"
while 1
printf "- Timeout pos: %ld\n", $tmr->timeout_pos
- printf " Pointer: (ErtsTWheelTimer *) %p\n", $tmr
+ printf " Pointer: (ErtsTWheelTimer*)%p\n", $tmr
set $tmr = $tmr->next
if ($tmr == $tiw->w[$ix])
loop_break
@@ -2991,7 +3053,7 @@ define etp-timer-wheel
printf "\n"
while 1
printf "- Timeout pos: %ld\n", $tmr->timeout_pos
- printf " Pointer: (ErtsTWheelTimer *) %p\n", $tmr
+ printf " Pointer: (ErtsTWheelTimer*)%p\n", $tmr
set $tmr = $tmr->next
if ($tmr == $tiw->w[$ix])
loop_break
@@ -4326,6 +4388,20 @@ document etp-show
%---------------------------------------------------------------------------
end
+define etp-rr-run-until-beam
+ source @ERL_TOP@/erts/etc/unix/etp-rr-run-until-beam.py
+end
+
+document etp-rr-run-until-beam
+%---------------------------------------------------------------------------
+% etp-rr-run-until-beam
+%
+% Use this gdb macro to make cerl -rr replay -p PID walk until
+% the correct execute has been made. You may have to change the
+% file that is used to debug with.
+%---------------------------------------------------------------------------
+end
+
############################################################################
# Init
#
@@ -4363,7 +4439,13 @@ define hook-run
set $_exitsignal = -1
end
+handle SIGPIPE nostop
+
etp-init
help etp-init
-etp-show
-etp-system-info
+if $etp_rr_run_until_beam
+ help etp-rr-run-until-beam
+else
+ etp-show
+ etp-system-info
+end
diff --git a/erts/etc/unix/etp-rr-run-until-beam.py b/erts/etc/unix/etp-rr-run-until-beam.py
new file mode 100644
index 0000000000..078998b910
--- /dev/null
+++ b/erts/etc/unix/etp-rr-run-until-beam.py
@@ -0,0 +1,45 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2013-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%
+#
+
+has_exited = False
+
+def stop_handler (event):
+ global has_exited
+ if isinstance(event, gdb.SignalEvent):
+ print("exit code: %s" % (event.stop_signal))
+ has_exited = True
+
+gdb.events.stop.connect (stop_handler)
+
+gdb.execute('continue')
+
+while not has_exited:
+ r = gdb.execute('when', to_string=True)
+ m = re.match("[^0-9]*([0-9]+)", r)
+ if m:
+ event = int(m.group(1));
+ gdb.execute('start ' + str(event + 1));
+ gdb.execute('continue')
+
+gdb.events.stop.disconnect (stop_handler)
+
+gdb.execute('file ' + str(gdb.parse_and_eval("$etp_beam_executable")))
+gdb.execute('break main')
+gdb.execute('reverse-continue')
diff --git a/erts/etc/unix/to_erl.c b/erts/etc/unix/to_erl.c
index afff8f7e54..ed4fe12e8b 100644
--- a/erts/etc/unix/to_erl.c
+++ b/erts/etc/unix/to_erl.c
@@ -245,7 +245,6 @@ int main(int argc, char **argv)
tty_smode.c_iflag =
1*BRKINT |/*Signal interrupt on break.*/
1*IGNPAR |/*Ignore characters with parity errors.*/
- 1*ISTRIP |/*Strip character.*/
0;
#if 0
diff --git a/erts/etc/win32/msys_tools/vc/ld.sh b/erts/etc/win32/msys_tools/vc/ld.sh
index 8917251f51..22184faf76 100644
--- a/erts/etc/win32/msys_tools/vc/ld.sh
+++ b/erts/etc/win32/msys_tools/vc/ld.sh
@@ -178,7 +178,10 @@ if [ "$RES" = "0" -a -f "$CMANIFEST" ]; then
RES=$?
if [ "$RES" != "0" ]; then
REMOVE=`echo "$OUTPUTRES" | sed 's,\\\;[12]$,,g'`
- CREMOVE=`cygpath $REMOVE`
+ CREMOVE=`win2msys_path.sh $REMOVE`
+ ## If Defender or Search are enabled, they will lock just created files
+ ## and then mt will fail :/
+ echo "If you get this error, make sure Windows Defender AND Windows Search is disabled">>/tmp/link.exe.${p}.1
rm -f "$CREMOVE"
fi
rm -f "$CMANIFEST"
diff --git a/erts/include/internal/gcc/ethr_dw_atomic.h b/erts/include/internal/gcc/ethr_dw_atomic.h
index 47158b7295..dd116c81ce 100644
--- a/erts/include/internal/gcc/ethr_dw_atomic.h
+++ b/erts/include/internal/gcc/ethr_dw_atomic.h
@@ -78,7 +78,7 @@ typedef volatile ETHR_NATIVE_SU_DW_SINT_T * ethr_native_dw_ptr_t;
* runtime. We, therefore, need an extra word allocated.
*/
#define ETHR_DW_NATMC_MEM__(VAR) \
- (&var->c[(int) ((ethr_uint_t) &(VAR)->c[0]) & ETHR_DW_NATMC_ALIGN_MASK__])
+ (&(VAR)->c[(int) ((ethr_uint_t) &(VAR)->c[0]) & ETHR_DW_NATMC_ALIGN_MASK__])
typedef union {
volatile ETHR_NATIVE_SU_DW_SINT_T dw_sint;
volatile ethr_sint_t sint[3];
diff --git a/erts/include/internal/gcc/ethr_membar.h b/erts/include/internal/gcc/ethr_membar.h
index 07960ce040..5cbda5582d 100644
--- a/erts/include/internal/gcc/ethr_membar.h
+++ b/erts/include/internal/gcc/ethr_membar.h
@@ -96,7 +96,7 @@
* issue an aquire memory barrier and an __atomic
* builtin memory acess with the __ATOMIC_RELEASE
* memory model must at least issue a release memory
- * barrier. Otherwise the two can not be paired.
+ * barrier. Otherwise the two cannot be paired.
* 4. All __atomic builtins accessing memory using the
* __ATOMIC_CONSUME builtin can be used for the same
* reason __ATOMIC_ACQUIRE can be used. The ethread
diff --git a/erts/include/internal/i386/ethr_dw_atomic.h b/erts/include/internal/i386/ethr_dw_atomic.h
index 91acdb0483..3c47da9758 100644
--- a/erts/include/internal/i386/ethr_dw_atomic.h
+++ b/erts/include/internal/i386/ethr_dw_atomic.h
@@ -73,7 +73,7 @@ typedef volatile ethr_native_sint128_t__ * ethr_native_dw_ptr_t;
* runtime. We, therefore, need an extra word allocated.
*/
#define ETHR_DW_NATMC_MEM__(VAR) \
- (&var->c[(int) ((ethr_uint_t) &(VAR)->c[0]) & ETHR_DW_NATMC_ALIGN_MASK__])
+ (&(VAR)->c[(int) ((ethr_uint_t) &(VAR)->c[0]) & ETHR_DW_NATMC_ALIGN_MASK__])
typedef union {
#ifdef ETHR_NATIVE_SU_DW_SINT_T
volatile ETHR_NATIVE_SU_DW_SINT_T dw_sint;
diff --git a/erts/include/internal/win/ethr_dw_atomic.h b/erts/include/internal/win/ethr_dw_atomic.h
index a6b26ab7bb..f7a1900a82 100644
--- a/erts/include/internal/win/ethr_dw_atomic.h
+++ b/erts/include/internal/win/ethr_dw_atomic.h
@@ -80,7 +80,7 @@ typedef volatile __int64 * ethr_native_dw_ptr_t;
* runtime. We, therefore, need an extra word allocated.
*/
#define ETHR_DW_NATMC_MEM__(VAR) \
- (&var->c[(int) ((ethr_uint_t) &(VAR)->c[0]) & ETHR_DW_NATMC_ALIGN_MASK__])
+ (&(VAR)->c[(int) ((ethr_uint_t) &(VAR)->c[0]) & ETHR_DW_NATMC_ALIGN_MASK__])
typedef union {
#ifdef ETHR_NATIVE_SU_DW_SINT_T
volatile ETHR_NATIVE_SU_DW_SINT_T dw_sint;
diff --git a/erts/lib_src/common/erl_printf.c b/erts/lib_src/common/erl_printf.c
index 259ba8c81d..86f5da1c40 100644
--- a/erts/lib_src/common/erl_printf.c
+++ b/erts/lib_src/common/erl_printf.c
@@ -27,6 +27,11 @@
#include "config.h"
#endif
+#if defined(__sun) || defined(__sun__)
+ /* For flockfile(3c), putc_unlocked(3c), etc */
+ #define __EXTENSIONS__
+#endif
+
#include <string.h>
#include "erl_errno.h"
#ifdef __WIN32__
diff --git a/erts/preloaded/ebin/atomics.beam b/erts/preloaded/ebin/atomics.beam
index a5ac24f0b8..ef402b5fee 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 a1aa34a415..674d0d27fa 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
new file mode 100644
index 0000000000..81be5b021a
--- /dev/null
+++ 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 37903d24b6..661bcd8413 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 2509f238bf..ec4d6153d1 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 7563663807..1b0cb5b50c 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 bc697d11d7..669149df82 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 5b788368af..6d3528c2dc 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 cd15cea885..b3af713809 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 e2a8c65f38..fc2bf6f6bd 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 fee2da33a6..1d89174b25 100644
--- a/erts/preloaded/ebin/init.beam
+++ b/erts/preloaded/ebin/init.beam
Binary files differ
diff --git a/erts/preloaded/ebin/net.beam b/erts/preloaded/ebin/net.beam
new file mode 100644
index 0000000000..ebb1296b95
--- /dev/null
+++ b/erts/preloaded/ebin/net.beam
Binary files differ
diff --git a/erts/preloaded/ebin/otp_ring0.beam b/erts/preloaded/ebin/otp_ring0.beam
index 324e111ad1..0d194896c7 100644
--- a/erts/preloaded/ebin/otp_ring0.beam
+++ b/erts/preloaded/ebin/otp_ring0.beam
Binary files differ
diff --git a/erts/preloaded/ebin/persistent_term.beam b/erts/preloaded/ebin/persistent_term.beam
index c73da80a98..c882e4fad4 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 75e5b7c9cb..cf671bf8f4 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 ddda4764e1..24911123f9 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 834135cca5..0efd954e50 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 558968b58a..ff9268ad38 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_zip.beam b/erts/preloaded/ebin/prim_zip.beam
index 51721a27a8..d319d7a343 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.beam b/erts/preloaded/ebin/socket.beam
new file mode 100644
index 0000000000..e44dff8475
--- /dev/null
+++ b/erts/preloaded/ebin/socket.beam
Binary files differ
diff --git a/erts/preloaded/ebin/zlib.beam b/erts/preloaded/ebin/zlib.beam
index 4519b540c4..9610c94ac2 100644
--- a/erts/preloaded/ebin/zlib.beam
+++ b/erts/preloaded/ebin/zlib.beam
Binary files differ
diff --git a/erts/preloaded/src/Makefile b/erts/preloaded/src/Makefile
index e1bd5bc295..efeb92dce9 100644
--- a/erts/preloaded/src/Makefile
+++ b/erts/preloaded/src/Makefile
@@ -39,9 +39,11 @@ PRE_LOADED_ERL_MODULES = \
prim_buffer \
prim_file \
prim_inet \
+ socket \
+ net \
zlib \
prim_zip \
- otp_ring0 \
+ erl_init \
erts_code_purger \
erlang \
erts_internal \
@@ -79,6 +81,10 @@ STDLIB_INCLUDE=$(ERL_TOP)/lib/stdlib/include
ERL_COMPILE_FLAGS += +debug_info -I$(KERNEL_SRC) -I$(KERNEL_INCLUDE)
+DIA_PLT = erts-preloaded.plt
+DIA_ANALYSIS = $(basename $(DIA_PLT)).dialyzer_analysis
+
+
debug opt: $(TARGET_FILES)
clean:
@@ -90,7 +96,6 @@ copy:
$(APP_TARGET): $(APP_SRC) $(ERL_TOP)/erts/vsn.mk
$(vsn_verbose)sed -e 's;%VSN%;$(VSN);' $< > $@
-
include $(ERL_TOP)/make/otp_release_targets.mk
release_spec: $(APP_TARGET)
@@ -105,6 +110,30 @@ release_docs_spec:
list_preloaded:
@echo $(PRE_LOADED_MODULES)
+dclean:
+ rm -f $(DIA_PLT)
+ rm -f $(DIA_ANALYSIS)
+
+dialyzer_plt: $(DIA_PLT)
+
+$(DIA_PLT): $(ERL_FILES)
+ @echo "Building ($(basename $(DIA_PLT))) plt file"
+ @dialyzer --build_plt \
+ --output_plt $@ \
+ -r ../ebin \
+ ../../../lib/kernel/ebin \
+ ../../../lib/stdlib/ebin \
+ ../../../lib/crypto/ebin \
+ ../../../lib/compiler/ebin \
+ --output $(DIA_ANALYSIS) \
+ --verbose
+
+dialyzer: $(DIA_PLT)
+ @echo "Running dialyzer on $(basename $(DIA_PLT))"
+ @dialyzer --plt $< \
+ ../ebin \
+ --verbose
+
#
# Combine a BEAM assembly script file a stub Erlang file into a BEAM file.
# See add_abstract_chunk script.
diff --git a/erts/preloaded/src/otp_ring0.erl b/erts/preloaded/src/erl_init.erl
index 62a60fffe2..6edead362c 100644
--- a/erts/preloaded/src/otp_ring0.erl
+++ b/erts/preloaded/src/erl_init.erl
@@ -17,15 +17,28 @@
%%
%% %CopyrightEnd%
%%
--module(otp_ring0).
+-module(erl_init).
-%% Purpose : Start up of erlang system.
+%% Initial process of an Erlang system.
-export([start/2]).
--spec start(_, term()) -> term().
-start(_Env, Argv) ->
- run(init, boot, Argv).
+%% This gets the module name given by the +i option (default 'init')
+%% and the list of command line arguments
+
+-spec start(Mod, BootArgs) -> no_return() when
+ Mod :: module(),
+ BootArgs :: [binary()].
+start(Mod, BootArgs) ->
+ %% Load the static nifs
+ zlib:on_load(),
+ erl_tracer:on_load(),
+ prim_buffer:on_load(),
+ prim_file:on_load(),
+ socket:on_load(),
+ net:on_load(),
+ %% Proceed to the specified boot module
+ run(Mod, boot, BootArgs).
run(M, F, A) ->
case erlang:function_exported(M, F, 1) of
diff --git a/erts/preloaded/src/erl_prim_loader.erl b/erts/preloaded/src/erl_prim_loader.erl
index ae5f86e017..1605c20f2c 100644
--- a/erts/preloaded/src/erl_prim_loader.erl
+++ b/erts/preloaded/src/erl_prim_loader.erl
@@ -297,12 +297,13 @@ check_file_result(Func, Target, {error,Reason}) ->
"Target: " ++ TargetStr ++ ". " ++
"Function: " ++ atom_to_list(Func) ++ ". " ++ Process
end,
- %% this is equal to calling error_logger:error_report/1 which
- %% we don't want to do from code_server during system boot
+ %% This is equal to calling logger:error/2 which
+ %% we don't want to do from code_server during system boot.
+ %% We don't want to call logger:timestamp() either.
logger ! {log,error,#{label=>{?MODULE,file_error},report=>Report},
#{pid=>self(),
gl=>group_leader(),
- time=>erlang:monotonic_time(microsecond),
+ time=>os:system_time(microsecond),
error_logger=>#{tag=>error_report,
type=>std_error}}},
error
diff --git a/erts/preloaded/src/erlang.erl b/erts/preloaded/src/erlang.erl
index 1ed6b6b284..a5b60cc845 100644
--- a/erts/preloaded/src/erlang.erl
+++ b/erts/preloaded/src/erlang.erl
@@ -2295,7 +2295,7 @@ process_flag(_Flag, _Value) ->
non_neg_integer()}]} |
{catchlevel, CatchLevel :: non_neg_integer()} |
{current_function,
- {Module :: module(), Function :: atom(), Arity :: arity()}} |
+ {Module :: module(), Function :: atom(), Arity :: arity()} | undefined} |
{current_location,
{Module :: module(), Function :: atom(), Arity :: arity(),
Location :: [{file, Filename :: string()} | % not a stack_item()!
@@ -2526,6 +2526,9 @@ subtract(_,_) ->
OldSchedulersOnline when
SchedulersOnline :: pos_integer(),
OldSchedulersOnline :: pos_integer();
+ (system_logger, Logger) -> PrevLogger when
+ Logger :: logger | undefined | pid(),
+ PrevLogger :: logger | undefined | pid();
(trace_control_word, TCW) -> OldTCW when
TCW :: non_neg_integer(),
OldTCW :: non_neg_integer();
@@ -2534,7 +2537,7 @@ subtract(_,_) ->
%% These are deliberately not documented
(internal_cpu_topology, term()) -> term();
(sequential_tracer, pid() | port() | {module(), term()} | false) -> pid() | port() | false;
- (1,0) -> true.
+ (reset_seq_trace,true) -> true.
system_flag(_Flag, _Value) ->
erlang:nif_error(undefined).
@@ -2731,8 +2734,9 @@ tuple_to_list(_Tuple) ->
(schedulers | schedulers_online) -> pos_integer();
(smp_support) -> boolean();
(start_time) -> integer();
- (system_version) -> string();
(system_architecture) -> string();
+ (system_logger) -> logger | undefined | pid();
+ (system_version) -> string();
(threads) -> boolean();
(thread_pool_size) -> non_neg_integer();
(time_correction) -> true | false;
@@ -3395,60 +3399,15 @@ get_cookie() ->
-spec integer_to_list(Integer, Base) -> string() when
Integer :: integer(),
Base :: 2..36.
-integer_to_list(I, 10) ->
- erlang:integer_to_list(I);
-integer_to_list(I, Base)
- when erlang:is_integer(I), erlang:is_integer(Base),
- Base >= 2, Base =< 1+$Z-$A+10 ->
- if I < 0 ->
- [$-|integer_to_list(-I, Base, [])];
- true ->
- integer_to_list(I, Base, [])
- end;
-integer_to_list(I, Base) ->
- erlang:error(badarg, [I, Base]).
-
-integer_to_list(I0, Base, R0) ->
- D = I0 rem Base,
- I1 = I0 div Base,
- R1 = if D >= 10 ->
- [D-10+$A|R0];
- true ->
- [D+$0|R0]
- end,
- if I1 =:= 0 ->
- R1;
- true ->
- integer_to_list(I1, Base, R1)
- end.
+integer_to_list(_I, _Base) ->
+ erlang:nif_error(undefined).
-spec integer_to_binary(Integer, Base) -> binary() when
Integer :: integer(),
Base :: 2..36.
-integer_to_binary(I, 10) ->
- erlang:integer_to_binary(I);
-integer_to_binary(I, Base)
- when erlang:is_integer(I), erlang:is_integer(Base),
- Base >= 2, Base =< 1+$Z-$A+10 ->
- if I < 0 ->
- <<$-,(integer_to_binary(-I, Base, <<>>))/binary>>;
- true ->
- integer_to_binary(I, Base, <<>>)
- end;
-integer_to_binary(I, Base) ->
- erlang:error(badarg, [I, Base]).
-
-integer_to_binary(I0, Base, R0) ->
- D = I0 rem Base,
- I1 = I0 div Base,
- R1 = if
- D >= 10 -> <<(D-10+$A),R0/binary>>;
- true -> <<(D+$0),R0/binary>>
- end,
- if
- I1 =:= 0 -> R1;
- true -> integer_to_binary(I1, Base, R1)
- end.
+integer_to_binary(_I, _Base) ->
+ erlang:nif_error(undefined).
+
-record(cpu, {node = -1,
processor = -1,
@@ -3654,90 +3613,28 @@ memory() ->
-spec erlang:memory(Type :: memory_type()) -> non_neg_integer();
(TypeList :: [memory_type()]) -> [{memory_type(), non_neg_integer()}].
memory(Type) when erlang:is_atom(Type) ->
- {AA, ALCU, ChkSup, BadArgZero} = need_mem_info(Type),
- case get_mem_data(ChkSup, ALCU, AA) of
- notsup ->
- erlang:error(notsup, [Type]);
- Mem ->
- Value = get_memval(Type, Mem),
- case {BadArgZero, Value} of
- {true, 0} -> erlang:error(badarg, [Type]);
- _ -> Value
- end
+ try
+ case aa_mem_data(au_mem_data(?ALL_NEEDED_ALLOCS)) of
+ notsup -> erlang:error(notsup);
+ Mem -> get_memval(Type, Mem)
+ end
+ catch
+ error:badarg -> erlang:error(badarg)
end;
memory(Types) when erlang:is_list(Types) ->
- {AA, ALCU, ChkSup, BadArgZeroList} = need_mem_info_list(Types),
- case get_mem_data(ChkSup, ALCU, AA) of
- notsup ->
- erlang:error(notsup, [Types]);
- Mem ->
- case memory_result_list(Types, BadArgZeroList, Mem) of
- badarg -> erlang:error(badarg, [Types]);
- Result -> Result
- end
- end.
-
-memory_result_list([], [], _Mem) ->
- [];
-memory_result_list([T|Ts], [BAZ|BAZs], Mem) ->
- case memory_result_list(Ts, BAZs, Mem) of
- badarg -> badarg;
- TVs ->
- V = get_memval(T, Mem),
- case {BAZ, V} of
- {true, 0} -> badarg;
- _ -> [{T, V}| TVs]
- end
- end.
-
-get_mem_data(true, AlcUAllocs, NeedAllocatedAreas) ->
- case memory_is_supported() of
- false -> notsup;
- true -> get_mem_data(false, AlcUAllocs, NeedAllocatedAreas)
- end;
-get_mem_data(false, AlcUAllocs, NeedAllocatedAreas) ->
- AlcUMem = case AlcUAllocs of
- [] -> #memory{};
- _ ->
- au_mem_data(AlcUAllocs)
- end,
- case NeedAllocatedAreas of
- true -> aa_mem_data(AlcUMem);
- false -> AlcUMem
+ try
+ case aa_mem_data(au_mem_data(?ALL_NEEDED_ALLOCS)) of
+ notsup -> erlang:error(notsup);
+ Mem -> memory_1(Types, Mem)
+ end
+ catch
+ error:badarg -> erlang:error(badarg)
end.
-need_mem_info_list([]) ->
- {false, [], false, []};
-need_mem_info_list([T|Ts]) ->
- {MAA, MALCU, MChkSup, MBadArgZero} = need_mem_info_list(Ts),
- {AA, ALCU, ChkSup, BadArgZero} = need_mem_info(T),
- {case AA of
- true -> true;
- _ -> MAA
- end,
- ALCU ++ (MALCU -- ALCU),
- case ChkSup of
- true -> true;
- _ -> MChkSup
- end,
- [BadArgZero|MBadArgZero]}.
-
-need_mem_info(Type) when Type == total;
- Type == system ->
- {true, ?ALL_NEEDED_ALLOCS, false, false};
-need_mem_info(Type) when Type == processes;
- Type == processes_used ->
- {true, [eheap_alloc, fix_alloc], true, false};
-need_mem_info(Type) when Type == atom;
- Type == atom_used;
- Type == code ->
- {true, [], true, false};
-need_mem_info(binary) ->
- {false, [binary_alloc], true, false};
-need_mem_info(ets) ->
- {true, [ets_alloc], true, false};
-need_mem_info(_) ->
- {false, [], false, true}.
+memory_1([Type | Types], Mem) ->
+ [{Type, get_memval(Type, Mem)} | memory_1(Types, Mem)];
+memory_1([], _Mem) ->
+ [].
get_memval(total, #memory{total = V}) -> V;
get_memval(processes, #memory{processes = V}) -> V;
@@ -3748,16 +3645,7 @@ get_memval(atom_used, #memory{atom_used = V}) -> V;
get_memval(binary, #memory{binary = V}) -> V;
get_memval(code, #memory{code = V}) -> V;
get_memval(ets, #memory{ets = V}) -> V;
-get_memval(_, #memory{}) -> 0.
-
-memory_is_supported() ->
- {_, _, FeatureList, _} = erlang:system_info(allocator),
- case ((erlang:system_info(alloc_util_allocators)
- -- ?CARRIER_ALLOCS)
- -- FeatureList) of
- [] -> true;
- _ -> false
- end.
+get_memval(_, #memory{}) -> erlang:error(badarg).
get_blocks_size([{blocks_size, Sz, _, _} | Rest], Acc) ->
get_blocks_size(Rest, Acc+Sz);
@@ -3768,16 +3656,6 @@ get_blocks_size([_ | Rest], Acc) ->
get_blocks_size([], Acc) ->
Acc.
-
-blocks_size([{Carriers, SizeList} | Rest], Acc) when Carriers == mbcs;
- Carriers == mbcs_pool;
- Carriers == sbcs ->
- blocks_size(Rest, get_blocks_size(SizeList, Acc));
-blocks_size([_ | Rest], Acc) ->
- blocks_size(Rest, Acc);
-blocks_size([], Acc) ->
- Acc.
-
get_fix_proc([{ProcType, A1, U1}| Rest], {A0, U0}) when ProcType == proc;
ProcType == monitor;
ProcType == link;
@@ -3802,64 +3680,78 @@ fix_proc([_ | Rest], Acc) ->
fix_proc([], Acc) ->
Acc.
+au_mem_fix(#memory{ processes = Proc,
+ processes_used = ProcU,
+ system = Sys } = Mem, Data) ->
+ case fix_proc(Data, {0, 0}) of
+ {A, U} ->
+ Mem#memory{ processes = Proc+A,
+ processes_used = ProcU+U,
+ system = Sys-A };
+ {Mask, A, U} ->
+ Mem#memory{ processes = Mask band (Proc+A),
+ processes_used = Mask band (ProcU+U),
+ system = Mask band (Sys-A) }
+ end.
+
+au_mem_acc(#memory{ total = Tot,
+ processes = Proc,
+ processes_used = ProcU } = Mem,
+ eheap_alloc, Data) ->
+ Sz = get_blocks_size(Data, 0),
+ Mem#memory{ total = Tot+Sz,
+ processes = Proc+Sz,
+ processes_used = ProcU+Sz};
+au_mem_acc(#memory{ total = Tot,
+ system = Sys,
+ ets = Ets } = Mem, ets_alloc, Data) ->
+ Sz = get_blocks_size(Data, 0),
+ Mem#memory{ total = Tot+Sz,
+ system = Sys+Sz,
+ ets = Ets+Sz };
+au_mem_acc(#memory{total = Tot,
+ system = Sys,
+ binary = Bin } = Mem,
+ binary_alloc, Data) ->
+ Sz = get_blocks_size(Data, 0),
+ Mem#memory{ total = Tot+Sz,
+ system = Sys+Sz,
+ binary = Bin+Sz};
+au_mem_acc(#memory{ total = Tot,
+ system = Sys } = Mem,
+ _Type, Data) ->
+ Sz = get_blocks_size(Data, 0),
+ Mem#memory{ total = Tot+Sz,
+ system = Sys+Sz }.
+
+au_mem_foreign(Mem, [{Type, SizeList} | Rest]) ->
+ au_mem_foreign(au_mem_acc(Mem, Type, SizeList), Rest);
+au_mem_foreign(Mem, []) ->
+ Mem.
+
+au_mem_current(Mem0, Type, [{mbcs_pool, MBCS} | Rest]) ->
+ [Foreign] = [Foreign || {foreign_blocks, Foreign} <- MBCS],
+ SizeList = MBCS -- [Foreign],
+ Mem = au_mem_foreign(Mem0, Foreign),
+ au_mem_current(au_mem_acc(Mem, Type, SizeList), Type, Rest);
+au_mem_current(Mem, Type, [{mbcs, SizeList} | Rest]) ->
+ au_mem_current(au_mem_acc(Mem, Type, SizeList), Type, Rest);
+au_mem_current(Mem, Type, [{sbcs, SizeList} | Rest]) ->
+ au_mem_current(au_mem_acc(Mem, Type, SizeList), Type, Rest);
+au_mem_current(Mem, Type, [_ | Rest]) ->
+ au_mem_current(Mem, Type, Rest);
+au_mem_current(Mem, _Type, []) ->
+ Mem.
+
au_mem_data(notsup, _) ->
notsup;
au_mem_data(_, [{_, false} | _]) ->
notsup;
-au_mem_data(#memory{total = Tot,
- processes = Proc,
- processes_used = ProcU} = Mem,
- [{eheap_alloc, _, Data} | Rest]) ->
- Sz = blocks_size(Data, 0),
- au_mem_data(Mem#memory{total = Tot+Sz,
- processes = Proc+Sz,
- processes_used = ProcU+Sz},
- Rest);
-au_mem_data(#memory{total = Tot,
- system = Sys,
- ets = Ets} = Mem,
- [{ets_alloc, _, Data} | Rest]) ->
- Sz = blocks_size(Data, 0),
- au_mem_data(Mem#memory{total = Tot+Sz,
- system = Sys+Sz,
- ets = Ets+Sz},
- Rest);
-au_mem_data(#memory{total = Tot,
- system = Sys,
- binary = Bin} = Mem,
- [{binary_alloc, _, Data} | Rest]) ->
- Sz = blocks_size(Data, 0),
- au_mem_data(Mem#memory{total = Tot+Sz,
- system = Sys+Sz,
- binary = Bin+Sz},
- Rest);
-au_mem_data(#memory{total = Tot,
- processes = Proc,
- processes_used = ProcU,
- system = Sys} = Mem,
- [{fix_alloc, _, Data} | Rest]) ->
- Sz = blocks_size(Data, 0),
- case fix_proc(Data, {0, 0}) of
- {A, U} ->
- au_mem_data(Mem#memory{total = Tot+Sz,
- processes = Proc+A,
- processes_used = ProcU+U,
- system = Sys+Sz-A},
- Rest);
- {Mask, A, U} ->
- au_mem_data(Mem#memory{total = Tot+Sz,
- processes = Mask band (Proc+A),
- processes_used = Mask band (ProcU+U),
- system = Mask band (Sys+Sz-A)},
- Rest)
- end;
-au_mem_data(#memory{total = Tot,
- system = Sys} = Mem,
- [{_, _, Data} | Rest]) ->
- Sz = blocks_size(Data, 0),
- au_mem_data(Mem#memory{total = Tot+Sz,
- system = Sys+Sz},
- Rest);
+au_mem_data(#memory{} = Mem0, [{fix_alloc, _, Data} | Rest]) ->
+ Mem = au_mem_fix(Mem0, Data),
+ au_mem_data(au_mem_current(Mem, fix_alloc, Data), Rest);
+au_mem_data(#memory{} = Mem, [{Type, _, Data} | Rest]) ->
+ au_mem_data(au_mem_current(Mem, Type, Data), Rest);
au_mem_data(EMD, []) ->
EMD.
diff --git a/erts/preloaded/src/erts.app.src b/erts/preloaded/src/erts.app.src
index ab0b9494b0..c2a8511b6d 100644
--- a/erts/preloaded/src/erts.app.src
+++ b/erts/preloaded/src/erts.app.src
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2019. All 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 @@
erl_prim_loader,
erts_internal,
init,
- otp_ring0,
+ erl_init,
erts_code_purger,
prim_buffer,
prim_eval,
@@ -35,7 +35,9 @@
prim_zip,
atomics,
counters,
- zlib
+ zlib,
+ net,
+ socket
]},
{registered, []},
{applications, []},
diff --git a/erts/preloaded/src/init.erl b/erts/preloaded/src/init.erl
index b4b8b3bf9b..5ea67347ec 100644
--- a/erts/preloaded/src/init.erl
+++ b/erts/preloaded/src/init.erl
@@ -200,12 +200,6 @@ boot(BootArgs) ->
register(init, self()),
process_flag(trap_exit, true),
- %% Load the static nifs
- zlib:on_load(),
- erl_tracer:on_load(),
- prim_buffer:on_load(),
- prim_file:on_load(),
-
{Start0,Flags,Args} = parse_boot_args(BootArgs),
%% We don't get to profile parsing of BootArgs
case b2a(get_flag(profile_boot, Flags, false)) of
@@ -483,13 +477,16 @@ do_handle_msg(Msg,State) ->
{From, {ensure_loaded, _}} ->
From ! {init, not_allowed};
X ->
+ %% This is equal to calling logger:info/3 which we don't
+ %% want to do from the init process, at least not during
+ %% system boot. We don't want to call logger:timestamp()
+ %% either.
case whereis(user) of
undefined ->
- Time = erlang:monotonic_time(microsecond),
catch logger ! {log, info, "init got unexpected: ~p", [X],
#{pid=>self(),
gl=>self(),
- time=>Time,
+ time=>os:system_time(microsecond),
error_logger=>#{tag=>info_msg}}};
User ->
User ! X,
diff --git a/erts/preloaded/src/net.erl b/erts/preloaded/src/net.erl
new file mode 100644
index 0000000000..a24b5c8ce3
--- /dev/null
+++ b/erts/preloaded/src/net.erl
@@ -0,0 +1,336 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2018. All 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(net).
+
+-compile(no_native).
+
+%% Administrative and "global" utility functions
+-export([
+ on_load/0, on_load/1,
+ info/0,
+ command/1
+ ]).
+
+-export([
+ gethostname/0,
+ getnameinfo/1, getnameinfo/2,
+ getaddrinfo/1, getaddrinfo/2,
+
+ if_name2index/1,
+ if_index2name/1,
+ if_names/0
+ ]).
+
+%% Deprecated functions from the "old" net module
+-export([call/4,
+ cast/4,
+ broadcast/3,
+ ping/1,
+ relay/1,
+ sleep/1]).
+
+-export_type([
+ address_info/0,
+ name_info/0,
+
+ name_info_flags/0,
+ name_info_flag/0,
+ name_info_flag_ext/0,
+
+ network_interface_name/0,
+ network_interface_index/0
+ ]).
+
+-deprecated({call, 4, eventually}).
+-deprecated({cast, 4, eventually}).
+-deprecated({broadcast, 3, eventually}).
+-deprecated({ping, 1, eventually}).
+-deprecated({relay, 1, eventually}).
+-deprecated({sleep, 1, eventually}).
+
+
+-type name_info_flags() :: [name_info_flag()|name_info_flag_ext()].
+-type name_info_flag() :: namereqd |
+ dgram |
+ nofqdn |
+ numerichost |
+ nomericserv.
+-type name_info_flag_ext() :: idn |
+ idna_allow_unassigned |
+ idna_use_std3_ascii_rules.
+-type name_info() :: #{host := string(),
+ service := string()}.
+-type address_info() :: #{family := socket:domain(),
+ socktype := socket:type(),
+ protocol := socket:protocol(),
+ address := socket:sockaddr()}.
+-type network_interface_name() :: string().
+-type network_interface_index() :: non_neg_integer().
+
+
+%% ===========================================================================
+%%
+%% D E P R E C A T E D F U N C T I O N S
+%%
+%% ===========================================================================
+
+call(N,M,F,A) -> rpc:call(N,M,F,A).
+cast(N,M,F,A) -> rpc:cast(N,M,F,A).
+broadcast(M,F,A) -> rpc:eval_everywhere(M,F,A).
+ping(Node) -> net_adm:ping(Node).
+sleep(T) -> receive after T -> ok end.
+relay(X) -> slave:relay(X).
+
+
+
+%% ===========================================================================
+%%
+%% Administrative and utility API
+%%
+%% ===========================================================================
+
+-spec on_load() -> ok.
+
+%% Should we require that the Extra arg is a map?
+on_load() ->
+ on_load(#{}).
+
+-spec on_load(Extra) -> ok when
+ Extra :: map().
+
+on_load(Extra) ->
+ ok = erlang:load_nif(atom_to_list(?MODULE), Extra).
+
+
+-spec info() -> list().
+
+info() ->
+ nif_info().
+
+
+-spec command(Cmd :: term()) -> term().
+
+command(Cmd) ->
+ nif_command(Cmd).
+
+
+
+%% ===========================================================================
+%%
+%% The proper net API
+%%
+%% ===========================================================================
+
+%% ===========================================================================
+%%
+%% gethostname - Get the name of the current host.
+%%
+%%
+
+-spec gethostname() -> {ok, HostName} | {error, Reason} when
+ HostName :: string(),
+ Reason :: term().
+
+gethostname() ->
+ nif_gethostname().
+
+
+%% ===========================================================================
+%%
+%% getnameinfo - Address-to-name translation in protocol-independent manner.
+%%
+%%
+
+-spec getnameinfo(SockAddr) -> {ok, Info} | {error, Reason} when
+ SockAddr :: socket:sockaddr(),
+ Info :: name_info(),
+ Reason :: term().
+
+getnameinfo(SockAddr) ->
+ getnameinfo(SockAddr, undefined).
+
+-spec getnameinfo(SockAddr, Flags) -> {ok, Info} | {error, Reason} when
+ SockAddr :: socket:sockaddr(),
+ Flags :: name_info_flags() | undefined,
+ Info :: name_info(),
+ Reason :: term().
+
+getnameinfo(SockAddr, [] = _Flags) ->
+ getnameinfo(SockAddr, undefined);
+getnameinfo(#{family := Fam, addr := _Addr} = SockAddr, Flags)
+ when ((Fam =:= inet) orelse (Fam =:= inet6)) andalso
+ (is_list(Flags) orelse (Flags =:= undefined)) ->
+ nif_getnameinfo(socket:ensure_sockaddr(SockAddr), Flags);
+getnameinfo(#{family := Fam, path := _Path} = SockAddr, Flags)
+ when (Fam =:= local) andalso (is_list(Flags) orelse (Flags =:= undefined)) ->
+ nif_getnameinfo(SockAddr, Flags).
+
+
+
+%% ===========================================================================
+%%
+%% getaddrinfo - Network address and service translation
+%%
+%% There is also a "hint" argument that we "at some point" should implement.
+
+-spec getaddrinfo(Host) -> {ok, Info} | {error, Reason} when
+ Host :: string(),
+ Info :: [address_info()],
+ Reason :: term().
+
+getaddrinfo(Host) when is_list(Host) ->
+ getaddrinfo(Host, undefined).
+
+
+-spec getaddrinfo(Host, undefined) -> {ok, Info} | {error, Reason} when
+ Host :: string(),
+ Info :: [address_info()],
+ Reason :: term()
+ ; (undefined, Service) -> {ok, Info} | {error, Reason} when
+ Service :: string(),
+ Info :: [address_info()],
+ Reason :: term()
+ ; (Host, Service) -> {ok, Info} | {error, Reason} when
+ Host :: string(),
+ Service :: string(),
+ Info :: [address_info()],
+ Reason :: term().
+
+getaddrinfo(Host, Service)
+ when (is_list(Host) orelse (Host =:= undefined)) andalso
+ (is_list(Service) orelse (Service =:= undefined)) andalso
+ (not ((Service =:= undefined) andalso (Host =:= undefined))) ->
+ nif_getaddrinfo(Host, Service, undefined).
+
+
+
+%% ===========================================================================
+%%
+%% if_name2index - Mappings between network interface names and indexes:
+%% name -> idx
+%%
+%%
+
+-spec if_name2index(Name) -> {ok, Idx} | {error, Reason} when
+ Name :: network_interface_name(),
+ Idx :: network_interface_index(),
+ Reason :: term().
+
+if_name2index(If) when is_list(If) ->
+ nif_if_name2index(If).
+
+
+
+%% ===========================================================================
+%%
+%% if_index2name - Mappings between network interface index and names:
+%% idx -> name
+%%
+%%
+
+-spec if_index2name(Idx) -> {ok, Name} | {error, Reason} when
+ Idx :: network_interface_index(),
+ Name :: network_interface_name(),
+ Reason :: term().
+
+if_index2name(Idx) when is_integer(Idx) ->
+ nif_if_index2name(Idx).
+
+
+
+%% ===========================================================================
+%%
+%% if_names - Get network interface names and indexes
+%%
+%%
+
+-spec if_names() -> Names | {error, Reason} when
+ Names :: [{Idx, If}],
+ Idx :: network_interface_index(),
+ If :: network_interface_name(),
+ Reason :: term().
+
+if_names() ->
+ nif_if_names().
+
+
+
+%% ===========================================================================
+%%
+%% Misc utility functions
+%%
+%% ===========================================================================
+
+%% 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).
+
+
+%% ===========================================================================
+%%
+%% The actual NIF-functions.
+%%
+%% ===========================================================================
+
+nif_info() ->
+ erlang:nif_error(undef).
+
+nif_command(_Cmd) ->
+ erlang:nif_error(undef).
+
+nif_gethostname() ->
+ erlang:nif_error(undef).
+
+nif_getnameinfo(_Addr, _Flags) ->
+ erlang:nif_error(undef).
+
+nif_getaddrinfo(_Host, _Service, _Hints) ->
+ erlang:nif_error(undef).
+
+nif_if_name2index(_Name) ->
+ erlang:nif_error(undef).
+
+nif_if_index2name(_Id) ->
+ erlang:nif_error(undef).
+
+nif_if_names() ->
+ erlang:nif_error(undef).
diff --git a/erts/preloaded/src/persistent_term.erl b/erts/preloaded/src/persistent_term.erl
index 5d0c266127..ee7e49b6cb 100644
--- a/erts/preloaded/src/persistent_term.erl
+++ b/erts/preloaded/src/persistent_term.erl
@@ -19,7 +19,7 @@
%%
-module(persistent_term).
--export([erase/1,get/0,get/1,info/0,put/2]).
+-export([erase/1,get/0,get/1,get/2,info/0,put/2]).
-type key() :: term().
-type value() :: term().
@@ -41,6 +41,13 @@ get() ->
get(_Key) ->
erlang:nif_error(undef).
+-spec get(Key, Default) -> Value when
+ Key :: key(),
+ Default :: value(),
+ Value :: value().
+get(_Key, _Default) ->
+ erlang:nif_error(undef).
+
-spec info() -> Info when
Info :: #{'count':=Count,'memory':=Memory},
Count :: non_neg_integer(),
diff --git a/erts/preloaded/src/prim_eval.S b/erts/preloaded/src/prim_eval.S
index e4b1560517..900fda5d89 100644
--- a/erts/preloaded/src/prim_eval.S
+++ b/erts/preloaded/src/prim_eval.S
@@ -42,6 +42,10 @@
{label,3}.
{loop_rec,{f,5},{x,0}}.
{move,{y,1},{x,1}}.
+ %% Tell the validator that it's safe to pass the message as an argument,
+ %% as the match fun is "known" not to build a term with it, and the
+ %% loop_rec instruction has disabled the GC.
+ {'%', {remove_fragility, {x,0}}}.
{call_fun,1}.
{test,is_ne_exact,{f,4},[{x,0},{atom,nomatch}]}.
remove_message.
diff --git a/erts/preloaded/src/prim_file.erl b/erts/preloaded/src/prim_file.erl
index 0994e2a9f4..1aa5d85c64 100644
--- a/erts/preloaded/src/prim_file.erl
+++ b/erts/preloaded/src/prim_file.erl
@@ -572,12 +572,13 @@ list_dir_convert([RawName | Rest], SkipInvalid, Result) ->
{error, ignore} ->
list_dir_convert(Rest, SkipInvalid, Result);
{error, warning} ->
- %% this is equal to calling error_logger:warning_msg/2 which
- %% we don't want to do from code_server during system boot
+ %% This is equal to calling logger:warning/3 which
+ %% we don't want to do from code_server during system boot.
+ %% We don't want to call logger:timestamp() either.
logger ! {log,warning,"Non-unicode filename ~p ignored\n", [RawName],
#{pid=>self(),
gl=>group_leader(),
- time=>erlang:system_time(microsecond),
+ time=>os:system_time(microsecond),
error_logger=>#{tag=>warning_msg}}},
list_dir_convert(Rest, SkipInvalid, Result);
{error, _} ->
diff --git a/erts/preloaded/src/prim_inet.erl b/erts/preloaded/src/prim_inet.erl
index f1d938c9a4..d5abdd2483 100644
--- a/erts/preloaded/src/prim_inet.erl
+++ b/erts/preloaded/src/prim_inet.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2019. All 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,8 +172,18 @@ close(S) when is_port(S) ->
%% and is a contradiction in itself.
%% We have hereby done our best...
%%
- Tref = erlang:start_timer(T * 1000, self(), close_port),
- close_pend_loop(S, Tref, undefined);
+ case subscribe(S, [subs_empty_out_q]) of
+ {ok, [{subs_empty_out_q,0}]} ->
+ close_port(S);
+ {ok, [{subs_empty_out_q,N}]} when N > 0 ->
+ %% Wait for pending output to be sent
+ Tref = erlang:start_timer(T * 1000, self(), close_port),
+ close_pend_loop(S, Tref, N);
+ _ ->
+ %% Subscribe failed - wait full time
+ Tref = erlang:start_timer(T * 1000, self(), close_port),
+ close_pend_loop(S, Tref, undefined)
+ end;
_ -> % Regard this as {ok,{false,_}}
case subscribe(S, [subs_empty_out_q]) of
{ok, [{subs_empty_out_q,N}]} when N > 0 ->
@@ -1742,7 +1752,7 @@ type_opt_1(O) when is_atom(O) -> undefined.
%% Get. No supplied value.
type_value(get, undefined) -> false; % Undefined type
-%% These two clauses can not happen since they are only used
+%% These two clauses cannot happen since they are only used
%% in record fields - from record fields they must have a
%% value though it might be 'undefined', so record fields
%% calls type_value/3, not type_value/2.
@@ -1908,7 +1918,7 @@ type_value_2(_, _) -> false.
%% Get. No supplied value.
%%
-%% These two clauses can not happen since they are only used
+%% These two clauses cannot happen since they are only used
%% in record fields - from record fields they must have a
%% value though it might be 'undefined', so record fields
%% calls enc_value/3, not enc_value/2.
@@ -2679,12 +2689,13 @@ get_ip6([X1,X2,X3,X4,X5,X6,X7,X8,X9,X10,X11,X12,X13,X14,X15,X16 | T]) ->
?u16(X9,X10),?u16(X11,X12),?u16(X13,X14),?u16(X15,X16)},
T }.
+-define(ERTS_INET_DRV_CONTROL_MAGIC_NUMBER, 16#03f1a300).
%% Control command
ctl_cmd(Port, Cmd, Args) ->
?DBG_FORMAT("prim_inet:ctl_cmd(~p, ~p, ~p)~n", [Port,Cmd,Args]),
Result =
- try erlang:port_control(Port, Cmd, Args) of
+ try erlang:port_control(Port, Cmd+?ERTS_INET_DRV_CONTROL_MAGIC_NUMBER, Args) of
[?INET_REP_OK|Reply] -> {ok,Reply};
[?INET_REP] -> inet_reply;
[?INET_REP_ERROR|Err] -> {error,list_to_atom(Err)}
diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl
new file mode 100644
index 0000000000..5c1647290d
--- /dev/null
+++ b/erts/preloaded/src/socket.erl
@@ -0,0 +1,3680 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2019. All 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(socket).
+
+-compile(no_native).
+-compile({no_auto_import,[error/1]}).
+
+%% Administrative and "global" utility functions
+-export([
+ on_load/0, on_load/1,
+ info/0,
+ supports/0, supports/1, supports/2, supports/3,
+ ensure_sockaddr/1
+ ]).
+
+-export([
+ open/2, open/3, open/4,
+ bind/2, bind/3,
+ connect/2, connect/3,
+ listen/1, listen/2,
+ accept/1, accept/2,
+
+ send/2, send/3, send/4,
+ sendto/3, sendto/4, sendto/5,
+ sendmsg/2, sendmsg/3, sendmsg/4,
+
+ recv/1, recv/2, recv/3, recv/4,
+ recvfrom/1, recvfrom/2, recvfrom/3, recvfrom/4,
+ recvmsg/1, recvmsg/2, recvmsg/3, recvmsg/5,
+
+ close/1,
+ shutdown/2,
+
+ setopt/4,
+ getopt/3,
+
+ sockname/1,
+ peername/1
+ ]).
+
+-export_type([
+ domain/0,
+ type/0,
+ protocol/0,
+ socket/0,
+
+ port_number/0,
+ ip_address/0,
+ ip4_address/0,
+ ip6_address/0,
+ sockaddr/0,
+ sockaddr_in4/0,
+ sockaddr_in6/0,
+ sockaddr_un/0,
+
+ accept_flags/0,
+ accept_flag/0,
+
+ send_flags/0,
+ send_flag/0,
+
+ recv_flags/0,
+ recv_flag/0,
+
+ shutdown_how/0,
+
+ sockopt_level/0,
+ otp_socket_option/0,
+ socket_option/0,
+ ip_socket_option/0,
+ ipv6_socket_option/0,
+ tcp_socket_option/0,
+ udp_socket_option/0,
+ sctp_socket_option/0,
+ raw_socket_option/0,
+
+ timeval/0,
+ ip_tos/0,
+ ip_mreq/0,
+ ip_mreq_source/0,
+ ip_pmtudisc/0,
+ ip_msfilter_mode/0,
+ ip_msfilter/0,
+ ip_pktinfo/0,
+ ipv6_mreq/0,
+ ipv6_pmtudisc/0,
+ ipv6_pktinfo/0,
+ in6_flow_info/0,
+ in6_scope_id/0,
+ sctp_assoc_id/0,
+ sctp_sndrcvinfo/0,
+ sctp_event_subscribe/0,
+ sctp_assocparams/0,
+ sctp_initmsg/0,
+ sctp_rtoinfo/0,
+
+
+ msghdr_flag/0,
+ msghdr_flags/0,
+ msghdr/0,
+ cmsghdr_level/0,
+ cmsghdr_type/0,
+ %% cmsghdr_data/0,
+ cmsghdr_recv/0, cmsghdr_send/0,
+
+ uint8/0,
+ uint16/0,
+ uint20/0,
+ uint32/0,
+ int32/0
+ ]).
+
+
+-type uint8() :: 0..16#FF.
+-type uint16() :: 0..16#FFFF.
+-type uint20() :: 0..16#FFFFF.
+-type uint32() :: 0..16#FFFFFFFF.
+-type int32() :: -2147483648..2147483647.
+
+
+%% We support only a subset of all domains.
+-type domain() :: local | inet | inet6.
+
+%% We support only a subset of all types.
+%% RDM - Reliably Delivered Messages
+-type type() :: stream | dgram | raw | rdm | seqpacket.
+
+%% We support only a subset of all protocols:
+%% Note that the '{raw, integer()}' construct is intended
+%% to be used with type = raw.
+%% Note also that only the "superuser" can create a raw socket.
+-type protocol() :: ip | tcp | udp | sctp | icmp | igmp | {raw, integer()}.
+
+-type port_number() :: 0..65535.
+
+-type ip_address() :: ip4_address() | ip6_address().
+
+-type ip4_address() :: {0..255, 0..255, 0..255, 0..255}.
+
+-type in6_flow_info() :: uint20().
+-type in6_scope_id() :: uint32().
+
+-type ip6_address() ::
+ {0..65535,
+ 0..65535,
+ 0..65535,
+ 0..65535,
+ 0..65535,
+ 0..65535,
+ 0..65535,
+ 0..65535}.
+
+-type timeval() :: #{sec := integer(),
+ usec := integer()}.
+
+-type ip_pktinfo() :: #{
+ ifindex := non_neg_integer(), % Interface Index
+ spec_dst := ip4_address(), % Local Address
+ addr := ip4_address() % Header Destination address
+ }.
+
+%% If the integer value is used, its up to the caller to ensure its valid!
+-type ip_tos() :: lowdeley |
+ throughput |
+ reliability |
+ mincost |
+ integer().
+
+%% This type is used when requesting to become member of a multicast
+%% group with a call to setopt. Example:
+%%
+%% socket:setopt(Socket, ip, add_membership, #{multiaddr => Addr,
+%% interface => any}).
+%%
+%% Its also used when removing from a multicast group. Example:
+%%
+%% socket:setopt(Socket, ip, drop_membership, #{multiaddr => Addr,
+%% interface => any}).
+%%
+
+-type ip_mreq() :: #{multiaddr := ip4_address(),
+ interface := any | ip4_address()}.
+%% -type ip_mreqn() :: #{multiaddr := ip4_address(),
+%% address := any | ip4_address(),
+%% ifindex := integer()}.
+
+-type ip_mreq_source() :: #{multiaddr := ip4_address(),
+ interface := ip4_address(),
+ sourceaddr := ip4_address()}.
+
+-type ip_pmtudisc() :: want | dont | do | probe.
+
+%% multiaddr: Multicast group address
+%% interface: Address of local interface
+%% mode: Filter mode
+%% slist: List of source addresses
+-type ip_msfilter_mode() :: include | exclude.
+
+-type ip_msfilter() :: #{multiaddr := ip4_address(),
+ interface := ip4_address(),
+ mode := ip_msfilter_mode(),
+ slist := [ip4_address()]}.
+
+-type ipv6_mreq() :: #{multiaddr := ip6_address(),
+ interface := non_neg_integer()}.
+
+-type ipv6_pmtudisc() :: ip_pmtudisc().
+
+-type ipv6_pktinfo() :: #{
+ addr := ip6_address(),
+ ifindex := integer()
+ }.
+
+
+-type sctp_assoc_id() :: int32().
+-type sctp_sndrcvinfo() :: #{
+ stream := uint16(),
+ ssn := uint16(),
+ flags := uint16(),
+ ppid := uint16(),
+ context := uint16(),
+ timetolive := uint16(),
+ tsn := uint16(),
+ cumtsn := uint16(),
+ assoc_id := sctp_assoc_id()
+ }.
+
+-type sctp_event_subscribe() :: #{data_in := boolean(),
+ association := boolean(),
+ address := boolean(),
+ send_failure := boolean(),
+ peer_error := boolean(),
+ shutdown := boolean(),
+ partial_delivery := boolean(),
+ adaptation_layer := boolean(),
+ authentication := boolean(),
+ sender_dry := boolean()}.
+
+-type sctp_assocparams() :: #{assoc_id := sctp_assoc_id(),
+ max_rxt := uint16(),
+ num_peer_dests := uint16(),
+ peer_rwnd := uint32(),
+ local_rwnd := uint32(),
+ cookie_life := uint32()}.
+
+-type sctp_initmsg() :: #{num_outstreams := uint16(),
+ max_instreams := uint16(),
+ max_attempts := uint16(),
+ max_init_timeo := uint16()
+ }.
+
+-type sctp_rtoinfo() :: #{assoc_id := sctp_assoc_id(),
+ initial := uint32(),
+ max := uint32(),
+ min := uint32()}.
+
+-type sockaddr_un() :: #{family := local,
+ path := binary() | string()}.
+-type sockaddr_in4() :: #{family := inet,
+ port := port_number(),
+ addr := any | loopback | ip4_address()}.
+-type sockaddr_in6() :: #{family := inet6,
+ port := port_number(),
+ addr := any | loopback | ip6_address(),
+ flowinfo := in6_flow_info(),
+ scope_id := in6_scope_id()}.
+-type sockaddr() :: sockaddr_in4() |
+ sockaddr_in6() |
+ sockaddr_un().
+
+-define(SOCKADDR_IN4_DEFAULTS(A), #{port => 0,
+ addr => A}).
+-define(SOCKADDR_IN4_DEFAULTS, ?SOCKADDR_IN4_DEFAULTS(any)).
+-define(SOCKADDR_IN4_DEFAULT(A), (?SOCKADDR_IN4_DEFAULTS(A))#{family => inet}).
+-define(SOCKADDR_IN6_DEFAULTS(A), #{port => 0,
+ addr => A,
+ flowinfo => 0,
+ scope_id => 0}).
+-define(SOCKADDR_IN6_DEFAULTS, ?SOCKADDR_IN6_DEFAULTS(any)).
+-define(SOCKADDR_IN6_DEFAULT(A), (?SOCKADDR_IN6_DEFAULTS(A))#{family => inet6}).
+
+%% otp - The option is internal to our (OTP) imeplementation.
+%% socket - The socket layer (SOL_SOCKET).
+%% ip - The IP layer (SOL_IP or is it IPPROTO_IP?).
+%% ipv6 - The IPv6 layer (SOL_IPV6).
+%% tcp - The TCP (Transport Control Protocol) layer (IPPROTO_TCP).
+%% udp - The UDP (User Datagram Protocol) layer (IPPROTO_UDP).
+%% sctp - The SCTP (Stream Control Transmission Protocol) layer (IPPROTO_SCTP).
+%% Int - Raw level, sent down and used "as is".
+-type sockopt_level() :: otp |
+ socket |
+ ip | ipv6 | tcp | udp | sctp |
+ non_neg_integer().
+
+%% There are some options that are 'read-only'.
+%% Should those be included here or in a special list?
+%% Should we just document it and leave it to the user?
+%% Or catch it in the encode functions?
+%% A setopt for a readonly option leads to einval?
+%% Do we really need a sndbuf?
+
+-type otp_socket_option() :: debug |
+ iow |
+ controlling_process |
+ rcvbuf | % sndbuf |
+ rcvctrlbuf |
+ sndctrlbuf |
+ fd.
+%% Shall we have special treatment of linger??
+%% read-only options:
+%% domain | protocol | type.
+%% FreeBSD (only?): acceptfilter
+-type socket_option() :: acceptconn |
+ acceptfilter |
+ bindtodevice |
+ broadcast |
+ busy_poll |
+ debug |
+ domain |
+ dontroute |
+ error |
+ keepalive |
+ linger |
+ mark |
+ oobinline |
+ passcred |
+ peek_off |
+ peekcred |
+ priority |
+ protocol |
+ rcvbuf |
+ rcvbufforce |
+ rcvlowat |
+ rcvtimeo |
+ reuseaddr |
+ reuseport |
+ rxq_ovfl |
+ setfib |
+ sndbuf |
+ sndbufforce |
+ sndlowat |
+ sndtimeo |
+ timestamp |
+ type.
+
+%% Read-only options:
+%% mtu
+%%
+%% Options only valid on FreeBSD?:
+%% dontfrag
+%% Options only valid for RAW sockets:
+%% nodefrag (linux only?)
+-type ip_socket_option() :: add_membership |
+ add_source_membership |
+ block_source |
+ dontfrag |
+ drop_membership |
+ drop_source_membership |
+ freebind |
+ hdrincl |
+ minttl |
+ msfilter |
+ mtu |
+ mtu_discover |
+ multicast_all |
+ multicast_if |
+ multicast_loop |
+ multicast_ttl |
+ nodefrag |
+ options |
+ pktinfo |
+ recverr |
+ recvif |
+ recvdstaddr |
+ recvopts |
+ recvorigdstaddr |
+ recvtos |
+ recvttl |
+ retopts |
+ router_alert |
+ sndsrcaddr |
+ tos |
+ transparent |
+ ttl |
+ unblock_source.
+-type ipv6_socket_option() ::
+ addrform |
+ add_membership |
+ authhdr |
+ auth_level |
+ checksum |
+ drop_membership |
+ dstopts |
+ esp_trans_level |
+ esp_network_level |
+ faith |
+ flowinfo |
+ hoplimit |
+ hopopts |
+ ipcomp_level |
+ join_group |
+ leave_group |
+ mtu |
+ mtu_discover |
+ multicast_hops |
+ multicast_if |
+ multicast_loop |
+ portrange |
+ pktoptions |
+ recverr |
+ recvpktinfo | pktinfo |
+ recvtclass |
+ router_alert |
+ rthdr |
+ tclass |
+ unicast_hops |
+ use_min_mtu |
+ v6only.
+
+-type tcp_socket_option() :: congestion |
+ cork |
+ info |
+ keepcnt |
+ keepidle |
+ keepintvl |
+ maxseg |
+ md5sig |
+ nodelay |
+ noopt |
+ nopush |
+ syncnt |
+ user_timeout.
+
+-type udp_socket_option() :: cork.
+
+-type sctp_socket_option() ::
+ adaption_layer |
+ associnfo |
+ auth_active_key |
+ auth_asconf |
+ auth_chunk |
+ auth_key |
+ auth_delete_key |
+ autoclose |
+ context |
+ default_send_params |
+ delayed_ack_time |
+ disable_fragments |
+ hmac_ident |
+ events |
+ explicit_eor |
+ fragment_interleave |
+ get_peer_addr_info |
+ initmsg |
+ i_want_mapped_v4_addr |
+ local_auth_chunks |
+ maxseg |
+ maxburst |
+ nodelay |
+ partial_delivery_point |
+ peer_addr_params |
+ peer_auth_chunks |
+ primary_addr |
+ reset_streams |
+ rtoinfo |
+ set_peer_primary_addr |
+ status |
+ use_ext_recvinfo.
+
+-type raw_socket_option() :: filter.
+
+%% -type plain_socket_option() :: integer().
+%% -type sockopt() :: otp_socket_option() |
+%% socket_option() |
+%% ip_socket_option() |
+%% ipv6_socket_option() |
+%% tcp_socket_option() |
+%% udp_socket_option() |
+%% sctp_socket_option() |
+%% raw_socket_option() |
+%% plain_socket_option().
+
+-record(socket, {ref :: reference()}).
+
+-opaque socket() :: #socket{}.
+
+-type accept_flags() :: [accept_flag()].
+-type accept_flag() :: nonblock | cloexec.
+
+-type send_flags() :: [send_flag()].
+-type send_flag() :: confirm |
+ dontroute |
+ eor |
+ more |
+ nosignal |
+ oob.
+
+%% Extend with OWN flags for other usage:
+%% - adapt-buffer-sz:
+%% This will have the effect that the nif recvfrom will use
+%% MSG_PEEK to ensure no part of the message is lost, but if
+%% necessary adapt (increase) the buffer size until all of
+%% it fits.
+%%
+%% Note that not all of these flags is useful for every recv function!
+%%
+-type recv_flags() :: [recv_flag()].
+-type recv_flag() :: cmsg_cloexec |
+ errqueue |
+ oob |
+ peek |
+ trunc.
+
+-type shutdown_how() :: read | write | read_write.
+
+%% These are just place-holder(s) - used by the sendmsg/recvmsg functions...
+-type msghdr_flag() :: ctrunc | eor | errqueue | oob | trunc.
+-type msghdr_flags() :: [msghdr_flag()].
+-type msghdr() :: #{
+ %% *Optional* target address
+ %% Used on an unconnected socket to specify the
+ %% target address for a datagram.
+ addr := sockaddr(),
+
+ iov := [binary()],
+
+ %% The maximum size of the control buffer is platform
+ %% specific. It is the users responsibility to ensure
+ %% that its not exceeded.
+ ctrl := [cmsghdr_recv()] | [cmsghdr_send()],
+
+ %% Only valid with recvmsg
+ flags := msghdr_flags()
+ }.
+%% We are able to (completely) decode *some* control message headers.
+%% Even if we are able to decode both level and type, we may not be
+%% able to decode the data, in which case it will be a binary.
+
+-type cmsghdr_level() :: socket | ip | ipv6 | integer().
+-type cmsghdr_type() :: timestamp |
+ pktinfo |
+ tos |
+ ttl |
+ rights |
+ credentials |
+ origdstaddr |
+ integer().
+-type cmsghdr_recv() ::
+ #{level := socket, type := timestamp, data := timeval()} |
+ #{level := socket, type := rights, data := binary()} |
+ #{level := socket, type := credentials, data := binary()} |
+ #{level := socket, type := integer(), data := binary()} |
+ #{level := ip, type := tos, data := ip_tos()} |
+ #{level := ip, type := ttl, data := integer()} |
+ #{level := ip, type := pktinfo, data := ip_pktinfo()} |
+ #{level := ip, type := origdstaddr, data := sockaddr_in4()} |
+ #{level := ip, type := integer(), data := binary()} |
+ #{level := ipv6, type := pktinfo, data := ipv6_pktinfo()} |
+ #{level := ipv6, type := integer(), data := binary()} |
+ #{level := integer(), type := integer(), data := binary()}.
+-type cmsghdr_send() ::
+ #{level := socket, type := integer(), data := binary()} |
+ #{level := ip, type := tos, data := ip_tos() | binary()} |
+ #{level := ip, type := ttl, data := integer() | binary()} |
+ #{level := ip, type := integer(), data := binary()} |
+ #{level := ipv6, type := integer(), data := binary()} |
+ #{level := udp, type := integer(), data := binary()} |
+ #{level := integer(), type := integer(), data := binary()}.
+
+
+-define(SOCKET_DOMAIN_LOCAL, 1).
+-define(SOCKET_DOMAIN_UNIX, ?SOCKET_DOMAIN_LOCAL).
+-define(SOCKET_DOMAIN_INET, 2).
+-define(SOCKET_DOMAIN_INET6, 3).
+
+-define(SOCKET_TYPE_STREAM, 1).
+-define(SOCKET_TYPE_DGRAM, 2).
+-define(SOCKET_TYPE_RAW, 3).
+%% -define(SOCKET_TYPE_RDM, 4).
+-define(SOCKET_TYPE_SEQPACKET, 5).
+
+-define(SOCKET_PROTOCOL_IP, 1).
+-define(SOCKET_PROTOCOL_TCP, 2).
+-define(SOCKET_PROTOCOL_UDP, 3).
+-define(SOCKET_PROTOCOL_SCTP, 4).
+-define(SOCKET_PROTOCOL_ICMP, 5).
+-define(SOCKET_PROTOCOL_IGMP, 6).
+
+-define(SOCKET_LISTEN_BACKLOG_DEFAULT, 5).
+
+-define(SOCKET_ACCEPT_TIMEOUT_DEFAULT, infinity).
+
+-define(SOCKET_SEND_FLAG_CONFIRM, 0).
+-define(SOCKET_SEND_FLAG_DONTROUTE, 1).
+-define(SOCKET_SEND_FLAG_EOR, 2).
+-define(SOCKET_SEND_FLAG_MORE, 3).
+-define(SOCKET_SEND_FLAG_NOSIGNAL, 4).
+-define(SOCKET_SEND_FLAG_OOB, 5).
+
+-define(SOCKET_SEND_FLAGS_DEFAULT, []).
+-define(SOCKET_SEND_TIMEOUT_DEFAULT, infinity).
+-define(SOCKET_SENDTO_FLAGS_DEFAULT, []).
+-define(SOCKET_SENDTO_TIMEOUT_DEFAULT, ?SOCKET_SEND_TIMEOUT_DEFAULT).
+-define(SOCKET_SENDMSG_FLAGS_DEFAULT, []).
+-define(SOCKET_SENDMSG_TIMEOUT_DEFAULT, ?SOCKET_SEND_TIMEOUT_DEFAULT).
+
+-define(SOCKET_RECV_FLAG_CMSG_CLOEXEC, 0).
+-define(SOCKET_RECV_FLAG_ERRQUEUE, 1).
+-define(SOCKET_RECV_FLAG_OOB, 2).
+-define(SOCKET_RECV_FLAG_PEEK, 3).
+-define(SOCKET_RECV_FLAG_TRUNC, 4).
+
+-define(SOCKET_RECV_FLAGS_DEFAULT, []).
+-define(SOCKET_RECV_TIMEOUT_DEFAULT, infinity).
+
+-define(SOCKET_OPT_LEVEL_OTP, 0).
+-define(SOCKET_OPT_LEVEL_SOCKET, 1).
+-define(SOCKET_OPT_LEVEL_IP, 2).
+-define(SOCKET_OPT_LEVEL_IPV6, 3).
+-define(SOCKET_OPT_LEVEL_TCP, 4).
+-define(SOCKET_OPT_LEVEL_UDP, 5).
+-define(SOCKET_OPT_LEVEL_SCTP, 6).
+
+%% *** OTP (socket) options
+-define(SOCKET_OPT_OTP_DEBUG, 1).
+-define(SOCKET_OPT_OTP_IOW, 2).
+-define(SOCKET_OPT_OTP_CTRL_PROC, 3).
+-define(SOCKET_OPT_OTP_RCVBUF, 4).
+%%-define(SOCKET_OPT_OTP_SNDBUF, 5).
+-define(SOCKET_OPT_OTP_RCVCTRLBUF, 6).
+-define(SOCKET_OPT_OTP_SNDCTRLBUF, 7).
+-define(SOCKET_OPT_OTP_FD, 8).
+-define(SOCKET_OPT_OTP_DOMAIN, 16#FF01). % INTERNAL
+-define(SOCKET_OPT_OTP_TYPE, 16#FF02). % INTERNAL
+-define(SOCKET_OPT_OTP_PROTOCOL, 16#FF03). % INTERNAL
+
+%% *** SOCKET (socket) options
+-define(SOCKET_OPT_SOCK_ACCEPTCONN, 1).
+%% -define(SOCKET_OPT_SOCK_ACCEPTFILTER, 2). % FreeBSD
+-define(SOCKET_OPT_SOCK_BINDTODEVICE, 3).
+-define(SOCKET_OPT_SOCK_BROADCAST, 4).
+%% -define(SOCKET_OPT_SOCK_BUSY_POLL, 5).
+-define(SOCKET_OPT_SOCK_DEBUG, 6).
+-define(SOCKET_OPT_SOCK_DOMAIN, 7).
+-define(SOCKET_OPT_SOCK_DONTROUTE, 8).
+%% -define(SOCKET_OPT_SOCK_ERROR, 9).
+-define(SOCKET_OPT_SOCK_KEEPALIVE, 10).
+-define(SOCKET_OPT_SOCK_LINGER, 11).
+%% -define(SOCKET_OPT_SOCK_MARK, 12).
+-define(SOCKET_OPT_SOCK_OOBINLINE, 13).
+%% -define(SOCKET_OPT_SOCK_PASSCRED, 14).
+-define(SOCKET_OPT_SOCK_PEEK_OFF, 15).
+%% -define(SOCKET_OPT_SOCK_PEEKCRED, 16).
+-define(SOCKET_OPT_SOCK_PRIORITY, 17).
+-define(SOCKET_OPT_SOCK_PROTOCOL, 18).
+-define(SOCKET_OPT_SOCK_RCVBUF, 19).
+%% -define(SOCKET_OPT_SOCK_RCVBUFFORCE, 20).
+-define(SOCKET_OPT_SOCK_RCVLOWAT, 21).
+-define(SOCKET_OPT_SOCK_RCVTIMEO, 22).
+-define(SOCKET_OPT_SOCK_REUSEADDR, 23).
+-define(SOCKET_OPT_SOCK_REUSEPORT, 24).
+%% -define(SOCKET_OPT_SOCK_RXQ_OVFL, 25).
+%% -define(SOCKET_OPT_SOCK_SETFIB, 26). % FreeBSD
+-define(SOCKET_OPT_SOCK_SNDBUF, 27).
+%% -define(SOCKET_OPT_SOCK_SNDBUFFORCE, 28).
+-define(SOCKET_OPT_SOCK_SNDLOWAT, 29).
+-define(SOCKET_OPT_SOCK_SNDTIMEO, 30).
+-define(SOCKET_OPT_SOCK_TIMESTAMP, 31).
+-define(SOCKET_OPT_SOCK_TYPE, 32).
+
+%% *** IP (socket) options
+-define(SOCKET_OPT_IP_ADD_MEMBERSHIP, 1).
+-define(SOCKET_OPT_IP_ADD_SOURCE_MEMBERSHIP, 2).
+-define(SOCKET_OPT_IP_BLOCK_SOURCE, 3).
+%% -define(SOCKET_OPT_IP_DONTFRAG, 4). % FreeBSD
+-define(SOCKET_OPT_IP_DROP_MEMBERSHIP, 5).
+-define(SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP, 6).
+-define(SOCKET_OPT_IP_FREEBIND, 7).
+-define(SOCKET_OPT_IP_HDRINCL, 8).
+-define(SOCKET_OPT_IP_MINTTL, 9).
+-define(SOCKET_OPT_IP_MSFILTER, 10).
+-define(SOCKET_OPT_IP_MTU, 11).
+-define(SOCKET_OPT_IP_MTU_DISCOVER, 12).
+-define(SOCKET_OPT_IP_MULTICAST_ALL, 13).
+-define(SOCKET_OPT_IP_MULTICAST_IF, 14).
+-define(SOCKET_OPT_IP_MULTICAST_LOOP, 15).
+-define(SOCKET_OPT_IP_MULTICAST_TTL, 16).
+-define(SOCKET_OPT_IP_NODEFRAG, 17).
+%% -define(SOCKET_OPT_IP_OPTIONS, 18). % FreeBSD
+-define(SOCKET_OPT_IP_PKTINFO, 19).
+-define(SOCKET_OPT_IP_RECVDSTADDR, 20). % FreeBSD
+-define(SOCKET_OPT_IP_RECVERR, 21).
+-define(SOCKET_OPT_IP_RECVIF, 22).
+-define(SOCKET_OPT_IP_RECVOPTS, 23).
+-define(SOCKET_OPT_IP_RECVORIGDSTADDR, 24).
+-define(SOCKET_OPT_IP_RECVTOS, 25).
+-define(SOCKET_OPT_IP_RECVTTL, 26).
+-define(SOCKET_OPT_IP_RETOPTS, 27).
+-define(SOCKET_OPT_IP_ROUTER_ALERT, 28).
+-define(SOCKET_OPT_IP_SENDSRCADDR, 29). % FreeBSD
+-define(SOCKET_OPT_IP_TOS, 30).
+-define(SOCKET_OPT_IP_TRANSPARENT, 31).
+-define(SOCKET_OPT_IP_TTL, 32).
+-define(SOCKET_OPT_IP_UNBLOCK_SOURCE, 33).
+
+%% *** IPv6 (socket) options
+-define(SOCKET_OPT_IPV6_ADDRFORM, 1).
+-define(SOCKET_OPT_IPV6_ADD_MEMBERSHIP, 2).
+-define(SOCKET_OPT_IPV6_AUTHHDR, 3). % Obsolete?
+%% -define(SOCKET_OPT_IPV6_AUTH_LEVEL, 4). % FreeBSD
+%% -define(SOCKET_OPT_IPV6_CHECKSUM, 5). % FreeBSD
+-define(SOCKET_OPT_IPV6_DROP_MEMBERSHIP, 6).
+-define(SOCKET_OPT_IPV6_DSTOPTS, 7).
+%% -define(SOCKET_OPT_IPV6_ESP_NETWORK_LEVEL, 8). % FreeBSD
+%% -define(SOCKET_OPT_IPV6_ESP_TRANS_LEVEL, 9). % FreeBSD
+%% -define(SOCKET_OPT_IPV6_FAITH, 10). % FreeBSD
+-define(SOCKET_OPT_IPV6_FLOWINFO, 11).
+-define(SOCKET_OPT_IPV6_HOPLIMIT, 12).
+-define(SOCKET_OPT_IPV6_HOPOPTS, 13).
+%% -define(SOCKET_OPT_IPV6_IPCOMP_LEVEL, 14). % FreeBSD
+%% -define(SOCKET_OPT_IPV6_JOIN_GROUP, 15). % FreeBSD
+%% -define(SOCKET_OPT_IPV6_LEAVE_GROUP, 16). % FreeBSD
+-define(SOCKET_OPT_IPV6_MTU, 17).
+-define(SOCKET_OPT_IPV6_MTU_DISCOVER, 18).
+-define(SOCKET_OPT_IPV6_MULTICAST_HOPS, 19).
+-define(SOCKET_OPT_IPV6_MULTICAST_IF, 20).
+-define(SOCKET_OPT_IPV6_MULTICAST_LOOP, 21).
+%% -define(SOCKET_OPT_IPV6_PORTRANGE, 22). % FreeBSD
+%% -define(SOCKET_OPT_IPV6_PKTOPTIONS, 23). % FreeBSD
+-define(SOCKET_OPT_IPV6_RECVERR, 24).
+-define(SOCKET_OPT_IPV6_RECVPKTINFO, 25). % On FreeBSD: PKTINFO
+%% -define(SOCKET_OPT_IPV6_RECVTCLASS, 26).
+-define(SOCKET_OPT_IPV6_ROUTER_ALERT, 27).
+-define(SOCKET_OPT_IPV6_RTHDR, 28).
+%% -define(SOCKET_OPT_IPV6_TCLASS, 29). % FreeBSD
+-define(SOCKET_OPT_IPV6_UNICAST_HOPS, 30).
+%% -define(SOCKET_OPT_IPV6_USE_MIN_MTU, 31). % FreeBSD
+-define(SOCKET_OPT_IPV6_V6ONLY, 32).
+
+%% *** TCP (socket) options
+-define(SOCKET_OPT_TCP_CONGESTION, 1).
+-define(SOCKET_OPT_TCP_CORK, 2).
+%% -define(SOCKET_OPT_TCP_INFO, 3).
+%% -define(SOCKET_OPT_TCP_KEEPCNT, 4).
+%% -define(SOCKET_OPT_TCP_KEEPIDLE, 5).
+%% -define(SOCKET_OPT_TCP_KEEPINTVL, 6).
+-define(SOCKET_OPT_TCP_MAXSEG, 7).
+%% -define(SOCKET_OPT_TCP_MD5SIG, 8).
+-define(SOCKET_OPT_TCP_NODELAY, 9).
+%% -define(SOCKET_OPT_TCP_NOOPT, 10).
+%% -define(SOCKET_OPT_TCP_NOPUSH, 11).
+%% -define(SOCKET_OPT_TCP_SYNCNT, 12).
+%% -define(SOCKET_OPT_TCP_USER_TIMEOUT, 13).
+
+%% *** UDP (socket) options
+-define(SOCKET_OPT_UDP_CORK, 1).
+
+%% *** SCTP (socket) options
+%% -define(SOCKET_OPT_SCTP_ADAPTION_LAYER, 1).
+-define(SOCKET_OPT_SCTP_ASSOCINFO, 2).
+%% -define(SOCKET_OPT_SCTP_AUTH_ACTIVE_KEY, 3).
+%% -define(SOCKET_OPT_SCTP_AUTH_ASCONF, 4).
+%% -define(SOCKET_OPT_SCTP_AUTH_CHUNK, 5).
+%% -define(SOCKET_OPT_SCTP_AUTH_KEY, 6).
+%% -define(SOCKET_OPT_SCTP_AUTH_DELETE_KEY, 7).
+-define(SOCKET_OPT_SCTP_AUTOCLOSE, 8).
+%% -define(SOCKET_OPT_SCTP_CONTEXT, 9).
+%% -define(SOCKET_OPT_SCTP_DEFAULT_SEND_PARAMS, 10).
+%% -define(SOCKET_OPT_SCTP_DELAYED_ACK_TIME, 11).
+-define(SOCKET_OPT_SCTP_DISABLE_FRAGMENTS, 12).
+%% -define(SOCKET_OPT_SCTP_HMAC_IDENT, 13).
+-define(SOCKET_OPT_SCTP_EVENTS, 14).
+%% -define(SOCKET_OPT_SCTP_EXPLICIT_EOR, 15).
+%% -define(SOCKET_OPT_SCTP_FRAGMENT_INTERLEAVE, 16).
+%% -define(SOCKET_OPT_SCTP_GET_PEER_ADDR_INFO, 17).
+-define(SOCKET_OPT_SCTP_INITMSG, 18).
+%% -define(SOCKET_OPT_SCTP_I_WANT_MAPPED_V4_ADDR, 19).
+%% -define(SOCKET_OPT_SCTP_LOCAL_AUTH_CHUNKS, 20).
+-define(SOCKET_OPT_SCTP_MAXSEG, 21).
+%% -define(SOCKET_OPT_SCTP_MAXBURST, 22).
+-define(SOCKET_OPT_SCTP_NODELAY, 23).
+%% -define(SOCKET_OPT_SCTP_PARTIAL_DELIVERY_POINT, 24).
+%% -define(SOCKET_OPT_SCTP_PEER_ADDR_PARAMS, 25).
+%% -define(SOCKET_OPT_SCTP_PEER_AUTH_CHUNKS, 26).
+%% -define(SOCKET_OPT_SCTP_PRIMARY_ADDR, 27).
+%% -define(SOCKET_OPT_SCTP_RESET_STREAMS, 28).
+-define(SOCKET_OPT_SCTP_RTOINFO, 29).
+%% -define(SOCKET_OPT_SCTP_SET_PEER_PRIMARY_ADDR, 30).
+%% -define(SOCKET_OPT_SCTP_STATUS, 31).
+%% -define(SOCKET_OPT_SCTP_USE_EXT_RECVINFO, 32).
+
+-define(SOCKET_SHUTDOWN_HOW_READ, 0).
+-define(SOCKET_SHUTDOWN_HOW_WRITE, 1).
+-define(SOCKET_SHUTDOWN_HOW_READ_WRITE, 2).
+
+
+-define(SOCKET_SUPPORTS_OPTIONS, 16#0001).
+-define(SOCKET_SUPPORTS_SCTP, 16#0002).
+-define(SOCKET_SUPPORTS_IPV6, 16#0003).
+
+
+%% ===========================================================================
+%%
+%% Administrative and utility API
+%%
+%% ===========================================================================
+
+-spec on_load() -> ok.
+
+%% Should we require that the Extra arg is a map?
+on_load() ->
+ on_load(#{}).
+
+-spec on_load(Extra) -> ok when
+ Extra :: map().
+
+on_load(Extra) ->
+ ok = erlang:load_nif(atom_to_list(?MODULE), Extra).
+
+
+
+-spec info() -> list().
+
+info() ->
+ nif_info().
+
+
+
+%% ===========================================================================
+%%
+%% supports - get information about what the platform "supports".
+%%
+%% Generates a list of various info about what the plaform can support.
+%% The most obvious case is 'options'.
+%%
+%% Each item in a 'supports'-list will appear only *one* time.
+%%
+%% ===========================================================================
+
+-type supports_options_socket() :: [{socket_option(), boolean()}].
+-type supports_options_ip() :: [{ip_socket_option(), boolean()}].
+-type supports_options_ipv6() :: [{ipv6_socket_option(), boolean()}].
+-type supports_options_tcp() :: [{tcp_socket_option(), boolean()}].
+-type supports_options_udp() :: [{udp_socket_option(), boolean()}].
+-type supports_options_sctp() :: [{sctp_socket_option(), boolean()}].
+-type supports_options() :: [{socket, supports_options_socket()} |
+ {ip, supports_options_ip()} |
+ {ipv6, supports_options_ipv6()} |
+ {tcp, supports_options_tcp()} |
+ {udp, supports_options_udp()} |
+ {sctp, supports_options_sctp()}].
+
+-spec supports() -> [{options, supports_options()} |
+ {sctp, boolean()} |
+ {ipv6, boolean()}].
+
+supports() ->
+ [{options, supports(options)},
+ {sctp, supports(sctp)},
+ {ipv6, supports(ipv6)}].
+
+
+-dialyzer({nowarn_function, supports/1}).
+-spec supports(options) -> supports_options();
+ (sctp) -> boolean();
+ (ipv6) -> boolean();
+ (Key1) -> false when
+ Key1 :: term().
+
+supports(options) ->
+ nif_supports(?SOCKET_SUPPORTS_OPTIONS);
+supports(sctp) ->
+ nif_supports(?SOCKET_SUPPORTS_SCTP);
+supports(ipv6) ->
+ nif_supports(?SOCKET_SUPPORTS_IPV6);
+supports(_Key1) ->
+ false.
+
+-dialyzer({nowarn_function, supports/2}).
+-spec supports(options, socket) -> supports_options_socket();
+ (options, ip) -> supports_options_ip();
+ (options, ipv6) -> supports_options_ipv6();
+ (options, tcp) -> supports_options_tcp();
+ (options, udp) -> supports_options_udp();
+ (options, sctp) -> supports_options_sctp();
+ (Key1, Key2) -> false when
+ Key1 :: term(),
+ Key2 :: term().
+
+supports(options, Level) ->
+ proplists:get_value(Level, supports(options), false);
+supports(_Key1, _Level) ->
+ false.
+
+
+-dialyzer({nowarn_function, supports/3}).
+-spec supports(options, socket, Opt) -> boolean() when
+ Opt :: socket_option();
+ (options, ip, Opt) -> boolean() when
+ Opt :: ip_socket_option();
+ (options, ipv6, Opt) -> boolean() when
+ Opt :: ipv6_socket_option();
+ (options, tcp, Opt) -> boolean() when
+ Opt :: tcp_socket_option();
+ (options, udp, Opt) -> boolean() when
+ Opt :: udp_socket_option();
+ (options, sctp, Opt) -> boolean() when
+ Opt :: sctp_socket_option();
+ (Key1, Key2, Key3) -> false when
+ Key1 :: term(),
+ Key2 :: term(),
+ Key3 :: term().
+
+supports(options, Level, Opt) ->
+ case supports(options, Level) of
+ S when is_list(S) ->
+ proplists:get_value(Opt, S, false);
+ _ ->
+ false
+ end;
+supports(_Key1, _Key2, _Key3) ->
+ false.
+
+
+
+%% ===========================================================================
+%%
+%% The proper socket API
+%%
+%% ===========================================================================
+
+%% ===========================================================================
+%%
+%% open - create an endpoint for communication
+%%
+%% Extra: netns
+%%
+%% <KOLLA>
+%%
+%% How do we handle the case when an fd has been created (somehow)
+%% and we shall create a socket "from it".
+%% Can we figure out Domain, Type and Protocol from fd?
+%% Yes we can: SO_DOMAIN, SO_PROTOCOL, SO_TYPE
+%% But does that work on all platforms? Or shall we require that the
+%% caller provide this explicitly?
+%%
+%% </KOLLA>
+%%
+%%
+%% <KOLLA>
+%%
+%% Start a controller process here, *before* the nif_open call.
+%% If that call is successful, update with owner process (controlling
+%% process) and SockRef. If the open fails, kill the process.
+%% "Register" the process on success:
+%%
+%% nif_register(SockRef, self()).
+%%
+%% <ALSO>
+%%
+%% Maybe register the process under a name?
+%% Something like:
+%%
+%% list_to_atom(lists:flatten(io_lib:format("socket-~p", [SockRef]))).
+%%
+%% </ALSO>
+%%
+%% The nif sets up a monitor to this process, and if it dies the socket
+%% is closed. It is also used if someone wants to monitor the socket.
+%%
+%% We therefor need monitor function(s):
+%%
+%% socket:monitor(Socket)
+%% socket:demonitor(Socket)
+%%
+%% These are basically used to monitor the controller process.
+%% Should the socket record therefor contain the pid of the controller process?
+%%
+%% </KOLLA>
+%%
+
+%% -spec open(FD) -> {ok, Socket} | {error, Reason} when
+%% Socket :: socket(),
+%% Reason :: term().
+
+%% open(FD) ->
+%% try
+%% begin
+%% case nif_open(FD) of
+%% {ok, {SockRef, Domain, Type, Protocol}} ->
+%% SocketInfo = #{domain => Domain,
+%% type => Type,
+%% protocol => Protocol},
+%% Socket = #socket{info = SocketInfo,
+%% ref = SockRef},
+%% {ok, Socket};
+%% {error, _} = ERROR ->
+%% ERROR
+%% end
+%% end
+%% catch
+%% _:_ -> % This must be improved!!
+%% {error, einval}
+%% end.
+
+-spec open(Domain, Type) -> {ok, Socket} | {error, Reason} when
+ Domain :: domain(),
+ Type :: type(),
+ Socket :: socket(),
+ Reason :: term().
+
+open(Domain, Type) ->
+ open(Domain, Type, null).
+
+-spec open(Domain, Type, Protocol) -> {ok, Socket} | {error, Reason} when
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: null | protocol(),
+ Socket :: socket(),
+ Reason :: term().
+
+open(Domain, Type, Protocol) ->
+ open(Domain, Type, Protocol, #{}).
+
+-spec open(Domain, Type, Protocol, Extra) -> {ok, Socket} | {error, Reason} when
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: null | protocol(),
+ Extra :: map(),
+ Socket :: socket(),
+ Reason :: term().
+
+open(Domain, Type, Protocol0, Extra) when is_map(Extra) ->
+ try
+ begin
+ Protocol = default_protocol(Protocol0, Type),
+ EDomain = enc_domain(Domain),
+ EType = enc_type(Domain, Type),
+ EProtocol = enc_protocol(Type, Protocol),
+ case nif_open(EDomain, EType, EProtocol, Extra) of
+ {ok, SockRef} ->
+ Socket = #socket{ref = SockRef},
+ {ok, Socket};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end
+ catch
+ throw:T ->
+ T;
+ %% <WIN32-TEMPORARY>
+ error:notsup:S ->
+ erlang:raise(error, notsup, S);
+ %% </WIN32-TEMPORARY>
+ error:Reason ->
+ {error, Reason}
+ end.
+
+%% Note that this is just a convenience function for when the protocol was
+%% not specified. If its actually specified, then that will be selected.
+%% Also, this only works for the some of the type's (stream, dgram and
+%% seqpacket).
+default_protocol(null, stream) -> tcp;
+default_protocol(null, dgram) -> udp;
+default_protocol(null, seqpacket) -> sctp;
+default_protocol(null, Type) -> throw({error, {no_default_protocol, Type}});
+default_protocol(Protocol, _) -> Protocol.
+
+
+%% ===========================================================================
+%%
+%% bind - bind a name to a socket
+%%
+
+-spec bind(Socket, Addr) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Addr :: any | loopback | sockaddr(),
+ Reason :: term().
+
+bind(#socket{ref = SockRef}, Addr)
+ when ((Addr =:= any) orelse (Addr =:= loopback)) ->
+ try which_domain(SockRef) of
+ inet ->
+ nif_bind(SockRef, ?SOCKADDR_IN4_DEFAULT(Addr));
+ inet6 ->
+ nif_bind(SockRef, ?SOCKADDR_IN6_DEFAULT(Addr))
+ catch
+ %% <WIN32-TEMPORARY>
+ error:notsup:S ->
+ erlang:raise(error, notsup, S);
+ %% </WIN32-TEMPORARY>
+ throw:ERROR ->
+ ERROR
+ end;
+bind(#socket{ref = SockRef} = _Socket, Addr) when is_map(Addr) ->
+ try
+ begin
+ nif_bind(SockRef, ensure_sockaddr(Addr))
+ end
+ catch
+ %% <WIN32-TEMPORARY>
+ error:notsup:S ->
+ erlang:raise(error, notsup, S);
+ %% </WIN32-TEMPORARY>
+ throw:ERROR ->
+ ERROR
+ end.
+
+
+
+%% ===========================================================================
+%%
+%% bind - Add or remove a bind addresses on a socket
+%%
+%% Calling this function is only valid if the socket is:
+%% type = seqpacket
+%% protocol = sctp
+%%
+%% If the domain is inet, then all addresses *must* be IPv4.
+%% If the domain is inet6, the addresses can be aither IPv4 or IPv6.
+%%
+
+-spec bind(Socket, Addrs, Action) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Addrs :: [sockaddr()],
+ Action :: add | remove,
+ Reason :: term().
+
+bind(#socket{ref = SockRef}, Addrs, Action)
+ when is_list(Addrs) andalso ((Action =:= add) orelse (Action =:= remove)) ->
+ try
+ begin
+ ensure_type(SockRef, seqpacket),
+ ensure_proto(SockRef, sctp),
+ validate_addrs(which_domain(SockRef), Addrs),
+ nif_bind(SockRef, Addrs, Action)
+ end
+ catch
+ %% <WIN32-TEMPORARY>
+ error:notsup:S ->
+ erlang:raise(error, notsup, S);
+ %% </WIN32-TEMPORARY>
+ throw:ERROR ->
+ ERROR
+ end.
+
+ensure_type(SockRef, Type) ->
+ case which_type(SockRef) of
+ Type ->
+ ok;
+ _InvalidType ->
+ einval()
+ end.
+
+ensure_proto(SockRef, Proto) ->
+ case which_protocol(SockRef) of
+ Proto ->
+ ok;
+ _InvalidProto ->
+ einval()
+ end.
+
+validate_addrs(inet = _Domain, Addrs) ->
+ validate_inet_addrs(Addrs);
+validate_addrs(inet6 = _Domain, Addrs) ->
+ validate_inet6_addrs(Addrs).
+
+validate_inet_addrs(Addrs) ->
+ Validator = fun(#{family := inet,
+ addrs := Addr}) when is_tuple(Addr) andalso
+ (size(Addr) =:= 4) ->
+ ok;
+ (X) ->
+ throw({error, {invalid_address, X}})
+ end,
+ lists:foreach(Validator, Addrs).
+
+validate_inet6_addrs(Addrs) ->
+ Validator = fun(#{family := inet,
+ addrs := Addr}) when is_tuple(Addr) andalso
+ (size(Addr) =:= 4) ->
+ ok;
+ (#{family := inet6,
+ addrs := Addr}) when is_tuple(Addr) andalso
+ (size(Addr) =:= 8) ->
+ ok;
+ (X) ->
+ throw({error, {invalid_address, X}})
+ end,
+ lists:foreach(Validator, Addrs).
+
+
+%% ===========================================================================
+%%
+%% connect - initiate a connection on a socket
+%%
+
+-spec connect(Socket, SockAddr) -> ok | {error, Reason} when
+ Socket :: socket(),
+ SockAddr :: sockaddr(),
+ Reason :: term().
+
+connect(Socket, SockAddr) ->
+ connect(Socket, SockAddr, infinity).
+
+-spec connect(Socket, SockAddr, Timeout) -> ok | {error, Reason} when
+ Socket :: socket(),
+ SockAddr :: sockaddr(),
+ Timeout :: timeout(),
+ Reason :: term().
+
+%% <KOLLA>
+%% Is it possible to connect with family = local for the (dest) sockaddr?
+%% </KOLLA>
+connect(_Socket, _SockAddr, Timeout)
+ when (is_integer(Timeout) andalso (Timeout =< 0)) ->
+ {error, timeout};
+connect(#socket{ref = SockRef}, #{family := Fam} = SockAddr, Timeout)
+ when ((Fam =:= inet) orelse (Fam =:= inet6) orelse (Fam =:= local)) andalso
+ ((Timeout =:= infinity) orelse is_integer(Timeout)) ->
+ TS = timestamp(Timeout),
+ case nif_connect(SockRef, SockAddr) of
+ ok ->
+ %% Connected!
+ ok;
+ {ok, Ref} ->
+ %% Connecting...
+ NewTimeout = next_timeout(TS, Timeout),
+ receive
+ {select, SockRef, Ref, ready_output} ->
+ %% <KOLLA>
+ %%
+ %% See open above!!
+ %%
+ %% * Here we should start and *register* the reader process
+ %% (This will cause the nif code to create a monitor to
+ %% the process)
+ %% * The reader is basically used to implement the active-X
+ %% feature!
+ %% * If the reader dies for whatever reason, then the socket
+ %% (resource) closes and the owner (controlling) process
+ %% is informed (closed message).
+ %%
+ %% </KOLLA>
+ nif_finalize_connection(SockRef)
+ after NewTimeout ->
+ cancel(SockRef, connect, Ref),
+ {error, timeout}
+ end;
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+%% ===========================================================================
+%%
+%% listen - listen for connections on a socket
+%%
+
+-spec listen(Socket) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Reason :: term().
+
+listen(Socket) ->
+ listen(Socket, ?SOCKET_LISTEN_BACKLOG_DEFAULT).
+
+-spec listen(Socket, Backlog) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Backlog :: pos_integer(),
+ Reason :: term().
+
+listen(#socket{ref = SockRef}, Backlog)
+ when (is_integer(Backlog) andalso (Backlog >= 0)) ->
+ nif_listen(SockRef, Backlog).
+
+
+
+
+%% ===========================================================================
+%%
+%% accept, accept4 - accept a connection on a socket
+%%
+
+-spec accept(LSocket) -> {ok, Socket} | {error, Reason} when
+ LSocket :: socket(),
+ Socket :: socket(),
+ Reason :: term().
+
+accept(Socket) ->
+ accept(Socket, ?SOCKET_ACCEPT_TIMEOUT_DEFAULT).
+
+-spec accept(LSocket, Timeout) -> {ok, Socket} | {error, Reason} when
+ LSocket :: socket(),
+ Timeout :: timeout(),
+ Socket :: socket(),
+ Reason :: term().
+
+%% Do we really need this optimization?
+accept(_, Timeout) when is_integer(Timeout) andalso (Timeout =< 0) ->
+ {error, timeout};
+accept(#socket{ref = LSockRef}, Timeout)
+ when is_integer(Timeout) orelse (Timeout =:= infinity) ->
+ do_accept(LSockRef, Timeout).
+
+do_accept(LSockRef, Timeout) ->
+ TS = timestamp(Timeout),
+ AccRef = make_ref(),
+ case nif_accept(LSockRef, AccRef) of
+ {ok, SockRef} ->
+ %% <KOLLA>
+ %%
+ %% * Here we should start and *register* the reader process
+ %% (This will cause the nif code to create a monitor to the process)
+ %% * The reader is basically used to implement the active-X feature!
+ %% * If the reader dies for whatever reason, then the socket (resource)
+ %% closes and the owner (controlling) process is informed (closed
+ %% message).
+ %%
+ %% </KOLLA>
+ Socket = #socket{ref = SockRef},
+ {ok, Socket};
+
+ {error, eagain} ->
+ %% Each call is non-blocking, but even then it takes
+ %% *some* time, so just to be sure, recalculate before
+ %% the receive.
+ NewTimeout = next_timeout(TS, Timeout),
+ receive
+ {select, LSockRef, AccRef, ready_input} ->
+ do_accept(LSockRef, next_timeout(TS, Timeout));
+
+ {'$socket', _, abort, {AccRef, Reason}} ->
+ {error, Reason}
+
+ after NewTimeout ->
+ cancel(LSockRef, accept, AccRef),
+ {error, timeout}
+ end;
+
+ {error, _} = ERROR ->
+ cancel(LSockRef, accept, AccRef), % Just to be on the safe side...
+ ERROR
+ end.
+
+
+
+%% ===========================================================================
+%%
+%% send, sendto, sendmsg - send a message on a socket
+%%
+
+-spec send(Socket, Data) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Data :: iodata(),
+ Reason :: term().
+
+send(Socket, Data) ->
+ send(Socket, Data, ?SOCKET_SEND_FLAGS_DEFAULT, ?SOCKET_SEND_TIMEOUT_DEFAULT).
+
+-spec send(Socket, Data, Flags) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Data :: iodata(),
+ Flags :: send_flags(),
+ Reason :: term()
+ ; (Socket, Data, Timeout) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Data :: iodata(),
+ Timeout :: timeout(),
+ Reason :: term().
+
+send(Socket, Data, Flags) when is_list(Flags) ->
+ send(Socket, Data, Flags, ?SOCKET_SEND_TIMEOUT_DEFAULT);
+send(Socket, Data, Timeout) ->
+ send(Socket, Data, ?SOCKET_SEND_FLAGS_DEFAULT, Timeout).
+
+-spec send(Socket, Data, Flags, Timeout) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Data :: iodata(),
+ Flags :: send_flags(),
+ Timeout :: timeout(),
+ Reason :: term().
+
+send(Socket, Data, Flags, Timeout) when is_list(Data) ->
+ Bin = erlang:list_to_binary(Data),
+ send(Socket, Bin, Flags, Timeout);
+send(#socket{ref = SockRef}, Data, Flags, Timeout)
+ when is_binary(Data) andalso is_list(Flags) ->
+ EFlags = enc_send_flags(Flags),
+ do_send(SockRef, Data, EFlags, Timeout).
+
+do_send(SockRef, Data, EFlags, Timeout) ->
+ TS = timestamp(Timeout),
+ SendRef = make_ref(),
+ case nif_send(SockRef, SendRef, Data, EFlags) of
+ ok ->
+ ok;
+ {ok, Written} ->
+ NewTimeout = next_timeout(TS, Timeout),
+ %% We are partially done, wait for continuation
+ receive
+ {select, SockRef, SendRef, ready_output} when (Written > 0) ->
+ <<_:Written/binary, Rest/binary>> = Data,
+ do_send(SockRef, Rest, EFlags,
+ next_timeout(TS, Timeout));
+ {select, SockRef, SendRef, ready_output} ->
+ do_send(SockRef, Data, EFlags,
+ next_timeout(TS, Timeout));
+
+ {'$socket', _, abort, {SendRef, Reason}} ->
+ {error, Reason}
+
+ after NewTimeout ->
+ cancel(SockRef, send, SendRef),
+ {error, {timeout, size(Data)}}
+ end;
+ {error, eagain} ->
+ receive
+ {select, SockRef, SendRef, ready_output} ->
+ do_send(SockRef, Data, EFlags,
+ next_timeout(TS, Timeout));
+
+ {'$socket', _, abort, {SendRef, Reason}} ->
+ {error, Reason}
+
+ after Timeout ->
+ cancel(SockRef, send, SendRef),
+ {error, {timeout, size(Data)}}
+ end;
+
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+
+
+%% ---------------------------------------------------------------------------
+%%
+
+-spec sendto(Socket, Data, Dest) ->
+ ok | {error, Reason} when
+ Socket :: socket(),
+ Data :: binary(),
+ Dest :: null | sockaddr(),
+ Reason :: term().
+
+sendto(Socket, Data, Dest) ->
+ sendto(Socket, Data, Dest, ?SOCKET_SENDTO_FLAGS_DEFAULT).
+
+-spec sendto(Socket, Data, Dest, Flags) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Data :: binary(),
+ Dest :: null | sockaddr(),
+ Flags :: send_flags(),
+ Reason :: term()
+ ; (Socket, Data, Dest, Timeout) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Data :: iodata(),
+ Dest :: null | sockaddr(),
+ Timeout :: timeout(),
+ Reason :: term().
+
+sendto(Socket, Data, Dest, Flags) when is_list(Flags) ->
+ sendto(Socket, Data, Dest, Flags, ?SOCKET_SENDTO_TIMEOUT_DEFAULT);
+sendto(Socket, Data, Dest, Timeout) ->
+ sendto(Socket, Data, Dest, ?SOCKET_SENDTO_FLAGS_DEFAULT, Timeout).
+
+
+-spec sendto(Socket, Data, Dest, Flags, Timeout) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Data :: binary(),
+ Dest :: null | sockaddr(),
+ Flags :: send_flags(),
+ Timeout :: timeout(),
+ Reason :: term().
+
+sendto(Socket, Data, Dest, Flags, Timeout) when is_list(Data) ->
+ Bin = erlang:list_to_binary(Data),
+ sendto(Socket, Bin, Dest, Flags, Timeout);
+sendto(#socket{ref = SockRef}, Data, Dest, Flags, Timeout)
+ when is_binary(Data) andalso
+ (Dest =:= null) andalso
+ is_list(Flags) andalso
+ (is_integer(Timeout) orelse (Timeout =:= infinity)) ->
+ EFlags = enc_send_flags(Flags),
+ do_sendto(SockRef, Data, Dest, EFlags, Timeout);
+sendto(#socket{ref = SockRef}, Data, #{family := Fam} = Dest, Flags, Timeout)
+ when is_binary(Data) andalso
+ ((Fam =:= inet) orelse (Fam =:= inet6) orelse (Fam =:= local)) andalso
+ is_list(Flags) andalso
+ (is_integer(Timeout) orelse (Timeout =:= infinity)) ->
+ EFlags = enc_send_flags(Flags),
+ do_sendto(SockRef, Data, Dest, EFlags, Timeout).
+
+do_sendto(SockRef, Data, Dest, EFlags, Timeout) ->
+ TS = timestamp(Timeout),
+ SendRef = make_ref(),
+ case nif_sendto(SockRef, SendRef, Data, Dest, EFlags) of
+ ok ->
+ %% We are done
+ ok;
+
+ {ok, Written} ->
+ %% We are partially done, wait for continuation
+ receive
+ {select, SockRef, SendRef, ready_output} when (Written > 0) ->
+ <<_:Written/binary, Rest/binary>> = Data,
+ do_sendto(SockRef, Rest, Dest, EFlags,
+ next_timeout(TS, Timeout));
+ {select, SockRef, SendRef, ready_output} ->
+ do_sendto(SockRef, Data, Dest, EFlags,
+ next_timeout(TS, Timeout));
+
+ {'$socket', _, abort, {SendRef, Reason}} ->
+ {error, Reason}
+
+ after Timeout ->
+ cancel(SockRef, sendto, SendRef),
+ {error, timeout}
+ end;
+
+ {error, eagain} ->
+ receive
+ {select, SockRef, SendRef, ready_output} ->
+ do_sendto(SockRef, Data, Dest, EFlags,
+ next_timeout(TS, Timeout));
+
+ {'$socket', _, abort, {SendRef, Reason}} ->
+ {error, Reason}
+
+ after Timeout ->
+ cancel(SockRef, sendto, SendRef),
+ {error, timeout}
+ end;
+
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+
+%% ---------------------------------------------------------------------------
+%%
+%% The only part of the msghdr() that *must* exist (a connected
+%% socket need not specify the addr field) is the iov.
+%% The ctrl field is optional, and the addr and flags are not
+%% used when sending.
+%%
+
+-spec sendmsg(Socket, MsgHdr) -> ok | {error, Reason} when
+ Socket :: socket(),
+ MsgHdr :: msghdr(),
+ Reason :: term().
+
+sendmsg(Socket, MsgHdr) ->
+ sendmsg(Socket, MsgHdr,
+ ?SOCKET_SENDMSG_FLAGS_DEFAULT, ?SOCKET_SENDMSG_TIMEOUT_DEFAULT).
+
+
+-spec sendmsg(Socket, MsgHdr, Flags) -> ok | {error, Reason} when
+ Socket :: socket(),
+ MsgHdr :: msghdr(),
+ Flags :: send_flags(),
+ Reason :: term()
+ ; (Socket, MsgHdr, Timeout) -> ok | {error, Reason} when
+ Socket :: socket(),
+ MsgHdr :: msghdr(),
+ Timeout :: timeout(),
+ Reason :: term().
+
+sendmsg(Socket, MsgHdr, Flags) when is_list(Flags) ->
+ sendmsg(Socket, MsgHdr, Flags, ?SOCKET_SENDMSG_TIMEOUT_DEFAULT);
+sendmsg(Socket, MsgHdr, Timeout)
+ when is_integer(Timeout) orelse (Timeout =:= infinity) ->
+ sendmsg(Socket, MsgHdr, ?SOCKET_SENDMSG_FLAGS_DEFAULT, Timeout).
+
+
+-spec sendmsg(Socket, MsgHdr, Flags, Timeout) -> ok | {ok, Remaining} | {error, Reason} when
+ Socket :: socket(),
+ MsgHdr :: msghdr(),
+ Flags :: send_flags(),
+ Timeout :: timeout(),
+ Remaining :: erlang:iovec(),
+ Reason :: term().
+
+sendmsg(#socket{ref = SockRef}, #{iov := IOV} = MsgHdr, Flags, Timeout)
+ when is_list(IOV) andalso
+ is_list(Flags) andalso
+ (is_integer(Timeout) orelse (Timeout =:= infinity)) ->
+ try ensure_msghdr(MsgHdr) of
+ M ->
+ EFlags = enc_send_flags(Flags),
+ do_sendmsg(SockRef, M, EFlags, Timeout)
+ catch
+ throw:T ->
+ T;
+ error:Reason ->
+ {error, Reason}
+ end.
+
+do_sendmsg(SockRef, MsgHdr, EFlags, Timeout) ->
+ TS = timestamp(Timeout),
+ SendRef = make_ref(),
+ case nif_sendmsg(SockRef, SendRef, MsgHdr, EFlags) of
+ ok ->
+ %% We are done
+ ok;
+
+ {ok, Written} when is_integer(Written) andalso (Written > 0) ->
+
+ %% We should not retry here since the protocol may not
+ %% be able to handle a message being split. Leave it to
+ %% the caller to figure out (call again with the rest).
+ %%
+ %% We should really not need to cancel, since this is
+ %% accepted for sendmsg!
+ %%
+ cancel(SockRef, sendmsg, SendRef),
+ {ok, do_sendmsg_rest(maps:get(iov, MsgHdr), Written)};
+
+ {error, eagain} ->
+ receive
+ {select, SockRef, SendRef, ready_output} ->
+ do_sendmsg(SockRef, MsgHdr, EFlags,
+ next_timeout(TS, Timeout))
+ after Timeout ->
+ cancel(SockRef, sendmsg, SendRef),
+ {error, timeout}
+ end;
+
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+do_sendmsg_rest([B|IOVec], Written) when (Written >= size(B)) ->
+ do_sendmsg_rest(IOVec, Written - size(B));
+do_sendmsg_rest([B|IOVec], Written) ->
+ <<_:Written/binary, Rest/binary>> = B,
+ [Rest|IOVec].
+
+ensure_msghdr(#{ctrl := []} = M) ->
+ ensure_msghdr(maps:remove(ctrl, M));
+ensure_msghdr(#{iov := IOV} = M) when is_list(IOV) andalso (IOV =/= []) ->
+ M#{iov := erlang:iolist_to_iovec(IOV)};
+ensure_msghdr(_) ->
+ einval().
+
+
+
+
+%% ===========================================================================
+%%
+%% writev - write data into multiple buffers
+%%
+
+
+
+%% ===========================================================================
+%%
+%% recv, recvfrom, recvmsg - receive a message from a socket
+%%
+%% Description:
+%% There is a special case for the argument Length. If its set to zero (0),
+%% it means "give me everything you have".
+%%
+%% Returns: {ok, Binary} | {error, Reason}
+%% Binary - The received data as a binary
+%% Reason - The error reason:
+%% timeout | {timeout, AccData} |
+%% posix() | {posix(), AccData} |
+%% atom() | {atom(), AccData}
+%% AccData - The data (as a binary) that we did manage to receive
+%% before the timeout.
+%%
+%% Arguments:
+%% Socket - The socket to read from.
+%% Length - The number of bytes to read.
+%% Flags - A list of "options" for the read.
+%% Timeout - Time-out in milliseconds.
+
+-spec recv(Socket) -> {ok, Data} | {error, Reason} when
+ Socket :: socket(),
+ Data :: binary(),
+ Reason :: term().
+
+recv(Socket) ->
+ recv(Socket, 0).
+
+-spec recv(Socket, Length) -> {ok, Data} | {error, Reason} when
+ Socket :: socket(),
+ Length :: non_neg_integer(),
+ Data :: binary(),
+ Reason :: term().
+
+recv(Socket, Length) ->
+ recv(Socket, Length,
+ ?SOCKET_RECV_FLAGS_DEFAULT,
+ ?SOCKET_RECV_TIMEOUT_DEFAULT).
+
+-spec recv(Socket, Length, Flags) -> {ok, Data} | {error, Reason} when
+ Socket :: socket(),
+ Length :: non_neg_integer(),
+ Flags :: recv_flags(),
+ Data :: binary(),
+ Reason :: term()
+ ; (Socket, Length, Timeout) -> {ok, Data} | {error, Reason} when
+ Socket :: socket(),
+ Length :: non_neg_integer(),
+ Timeout :: timeout(),
+ Data :: binary(),
+ Reason :: term().
+
+recv(Socket, Length, Flags) when is_list(Flags) ->
+ recv(Socket, Length, Flags, ?SOCKET_RECV_TIMEOUT_DEFAULT);
+recv(Socket, Length, Timeout) ->
+ recv(Socket, Length, ?SOCKET_RECV_FLAGS_DEFAULT, Timeout).
+
+-spec recv(Socket, Length, Flags, Timeout) -> {ok, Data} | {error, Reason} when
+ Socket :: socket(),
+ Length :: non_neg_integer(),
+ Flags :: recv_flags(),
+ Timeout :: timeout(),
+ Data :: binary(),
+ Reason :: term().
+
+recv(#socket{ref = SockRef}, Length, Flags, Timeout)
+ when (is_integer(Length) andalso (Length >= 0)) andalso
+ is_list(Flags) andalso
+ (is_integer(Timeout) orelse (Timeout =:= infinity)) ->
+ EFlags = enc_recv_flags(Flags),
+ do_recv(SockRef, undefined, Length, EFlags, <<>>, Timeout).
+
+%% We need to pass the "old recv ref" around because of the special case
+%% with Length = 0. This case makes it neccessary to have a timeout function
+%% clause since we may never wait for anything (no receive select), and so the
+%% the only timeout check will be the function clause.
+do_recv(SockRef, _OldRef, Length, EFlags, Acc, Timeout)
+ when (Timeout =:= infinity) orelse
+ (is_integer(Timeout) andalso (Timeout > 0)) ->
+ TS = timestamp(Timeout),
+ RecvRef = make_ref(),
+ %% p("do_recv -> try read with"
+ %% "~n Length: ~p", [Length]),
+ case nif_recv(SockRef, RecvRef, Length, EFlags) of
+ {ok, true = _Complete, Bin} when (size(Acc) =:= 0) ->
+ %% p("do_recv -> complete success: ~w", [size(Bin)]),
+ {ok, Bin};
+ {ok, true = _Complete, Bin} ->
+ %% p("do_recv -> completed success: ~w (~w)", [size(Bin), size(Acc)]),
+ {ok, <<Acc/binary, Bin/binary>>};
+
+ %% It depends on the amount of bytes we tried to read:
+ %% 0 - Read everything available
+ %% We got something, but there may be more - keep reading.
+ %% > 0 - We got a part of the message and we will be notified
+ %% when there is more to read (a select message)
+ {ok, false = _Complete, Bin} when (Length =:= 0) ->
+ %% p("do_recv -> partial success: ~w", [size(Bin)]),
+ do_recv(SockRef, RecvRef,
+ Length, EFlags,
+ <<Acc/binary, Bin/binary>>,
+ next_timeout(TS, Timeout));
+
+ {ok, false = _Completed, Bin} when (size(Acc) =:= 0) ->
+ %% We got the first chunk of it.
+ %% We will be notified (select message) when there
+ %% is more to read.
+ %% p("do_recv -> partial success(~w): ~w"
+ %% "~n ~p", [Length, size(Bin), Bin]),
+ NewTimeout = next_timeout(TS, Timeout),
+ receive
+ {select, SockRef, RecvRef, ready_input} ->
+ do_recv(SockRef, RecvRef,
+ Length-size(Bin), EFlags,
+ Bin,
+ next_timeout(TS, Timeout));
+
+ {'$socket', _, abort, {RecvRef, Reason}} ->
+ {error, Reason}
+
+ after NewTimeout ->
+ cancel(SockRef, recv, RecvRef),
+ {error, {timeout, Acc}}
+ end;
+
+ {ok, false = _Completed, Bin} ->
+ %% We got a chunk of it!
+ %% p("do_recv -> partial success(~w): ~w (~w)",
+ %% [Length, size(Bin), size(Acc)]),
+ NewTimeout = next_timeout(TS, Timeout),
+ receive
+ {select, SockRef, RecvRef, ready_input} ->
+ do_recv(SockRef, RecvRef,
+ Length-size(Bin), EFlags,
+ <<Acc/binary, Bin/binary>>,
+ next_timeout(TS, Timeout));
+
+ {'$socket', _, abort, {RecvRef, Reason}} ->
+ {error, Reason}
+
+ after NewTimeout ->
+ cancel(SockRef, recv, RecvRef),
+ {error, {timeout, Acc}}
+ end;
+
+ %% We return with the accumulated binary (if its non-empty)
+ {error, eagain} when (Length =:= 0) andalso (size(Acc) > 0) ->
+ %% CAN WE REALLY DO THIS? THE NIF HAS SELECTED!! OR?
+ {ok, Acc};
+
+ {error, eagain} ->
+ %% There is nothing just now, but we will be notified when there
+ %% is something to read (a select message).
+ %% p("do_recv -> eagain(~w): ~w", [Length, size(Acc)]),
+ NewTimeout = next_timeout(TS, Timeout),
+ receive
+ {select, SockRef, RecvRef, ready_input} ->
+ do_recv(SockRef, RecvRef,
+ Length, EFlags,
+ Acc,
+ next_timeout(TS, Timeout));
+
+ {'$socket', _, abort, {RecvRef, Reason}} ->
+ {error, Reason}
+
+ after NewTimeout ->
+ cancel(SockRef, recv, RecvRef),
+ {error, timeout}
+ end;
+
+ {error, closed = Reason} ->
+ do_close(SockRef),
+ if
+ (size(Acc) =:= 0) ->
+ {error, Reason};
+ true ->
+ {error, {Reason, Acc}}
+ end;
+
+ {error, _} = ERROR when (size(Acc) =:= 0) ->
+ ERROR;
+
+ {error, Reason} ->
+ {error, {Reason, Acc}}
+
+ end;
+
+do_recv(SockRef, RecvRef, 0 = _Length, _Eflags, Acc, _Timeout) ->
+ %% The current recv operation is to be cancelled, so no need for a ref...
+ %% The cancel will end our 'read everything you have' and "activate"
+ %% any waiting reader.
+ cancel(SockRef, recv, RecvRef),
+ {ok, Acc};
+do_recv(_SockRef, _RecvRef, _Length, _EFlags, Acc, _Timeout)
+ when (size(Acc) > 0) ->
+ {error, {timeout, Acc}};
+do_recv(_SockRef, _RecvRef, _Length, _EFlags, _Acc, _Timeout) ->
+ {error, timeout}.
+
+
+
+%% ---------------------------------------------------------------------------
+%%
+%% With recvfrom we get messages, which means that regardless of how
+%% much we want to read, we return when we get a message.
+%% The MaxSize argument basically defines the size of our receive
+%% buffer. By setting the size to zero (0), we use the configured
+%% size (see setopt).
+%% It may be impossible to know what (buffer) size is appropriate
+%% "in advance", and in those cases it may be convenient to use the
+%% (recv) 'peek' flag. When this flag is provided the message is *not*
+%% "consumed" from the underlying buffers, so another recvfrom call
+%% is needed, possibly with a then adjusted buffer size.
+%%
+
+-spec recvfrom(Socket) -> {ok, {Source, Data}} | {error, Reason} when
+ Socket :: socket(),
+ Source :: sockaddr() | undefined,
+ Data :: binary(),
+ Reason :: term().
+
+recvfrom(Socket) ->
+ recvfrom(Socket, 0).
+
+-spec recvfrom(Socket, BufSz) -> {ok, {Source, Data}} | {error, Reason} when
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ Source :: sockaddr() | undefined,
+ Data :: binary(),
+ Reason :: term().
+
+recvfrom(Socket, BufSz) ->
+ recvfrom(Socket, BufSz,
+ ?SOCKET_RECV_FLAGS_DEFAULT,
+ ?SOCKET_RECV_TIMEOUT_DEFAULT).
+
+-spec recvfrom(Socket, Flags, Timeout) ->
+ {ok, {Source, Data}} | {error, Reason} when
+ Socket :: socket(),
+ Flags :: recv_flags(),
+ Timeout :: timeout(),
+ Source :: sockaddr() | undefined,
+ Data :: binary(),
+ Reason :: term()
+ ; (Socket, BufSz, Flags) ->
+ {ok, {Source, Data}} | {error, Reason} when
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ Flags :: recv_flags(),
+ Source :: sockaddr() | undefined,
+ Data :: binary(),
+ Reason :: term()
+ ; (Socket, BufSz, Timeout) ->
+ {ok, {Source, Data}} | {error, Reason} when
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ Timeout :: timeout(),
+ Source :: sockaddr() | undefined,
+ Data :: binary(),
+ Reason :: term().
+
+recvfrom(Socket, Flags, Timeout) when is_list(Flags) ->
+ recvfrom(Socket, 0, Flags, Timeout);
+recvfrom(Socket, BufSz, Flags) when is_list(Flags) ->
+ recvfrom(Socket, BufSz, Flags, ?SOCKET_RECV_TIMEOUT_DEFAULT);
+recvfrom(Socket, BufSz, Timeout) ->
+ recvfrom(Socket, BufSz, ?SOCKET_RECV_FLAGS_DEFAULT, Timeout).
+
+-spec recvfrom(Socket, BufSz, Flags, Timeout) ->
+ {ok, {Source, Data}} | {error, Reason} when
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ Flags :: recv_flags(),
+ Timeout :: timeout(),
+ Source :: sockaddr() | undefined,
+ Data :: binary(),
+ Reason :: term().
+
+recvfrom(#socket{ref = SockRef}, BufSz, Flags, Timeout)
+ when (is_integer(BufSz) andalso (BufSz >= 0)) andalso
+ is_list(Flags) andalso
+ (is_integer(Timeout) orelse (Timeout =:= infinity)) ->
+ EFlags = enc_recv_flags(Flags),
+ do_recvfrom(SockRef, BufSz, EFlags, Timeout).
+
+do_recvfrom(SockRef, BufSz, EFlags, Timeout) ->
+ TS = timestamp(Timeout),
+ RecvRef = make_ref(),
+ case nif_recvfrom(SockRef, RecvRef, BufSz, EFlags) of
+ {ok, {_Source, _NewData}} = OK ->
+ OK;
+
+ {error, eagain} ->
+ %% There is nothing just now, but we will be notified when there
+ %% is something to read (a select message).
+ NewTimeout = next_timeout(TS, Timeout),
+ receive
+ {select, SockRef, RecvRef, ready_input} ->
+ do_recvfrom(SockRef, BufSz, EFlags,
+ next_timeout(TS, Timeout));
+
+ {'$socket', _, abort, {RecvRef, Reason}} ->
+ {error, Reason}
+
+ after NewTimeout ->
+ cancel(SockRef, recvfrom, RecvRef),
+ {error, timeout}
+ end;
+
+ {error, _Reason} = ERROR ->
+ ERROR
+
+ end.
+
+%% pi(Item) ->
+%% pi(self(), Item).
+
+%% pi(Pid, Item) ->
+%% {Item, Info} = process_info(Pid, Item),
+%% Info.
+
+
+%% ---------------------------------------------------------------------------
+%%
+
+-spec recvmsg(Socket) -> {ok, MsgHdr} | {error, Reason} when
+ Socket :: socket(),
+ MsgHdr :: msghdr(),
+ Reason :: term().
+
+recvmsg(Socket) ->
+ recvmsg(Socket, 0, 0,
+ ?SOCKET_RECV_FLAGS_DEFAULT, ?SOCKET_RECV_TIMEOUT_DEFAULT).
+
+-spec recvmsg(Socket, Flags) -> {ok, MsgHdr} | {error, Reason} when
+ Socket :: socket(),
+ Flags :: recv_flags(),
+ MsgHdr :: msghdr(),
+ Reason :: term()
+ ; (Socket, Timeout) -> {ok, MsgHdr} | {error, Reason} when
+ Socket :: socket(),
+ Timeout :: timeout(),
+ MsgHdr :: msghdr(),
+ Reason :: term().
+
+recvmsg(Socket, Flags) when is_list(Flags) ->
+ recvmsg(Socket, 0, 0, Flags, ?SOCKET_RECV_TIMEOUT_DEFAULT);
+recvmsg(Socket, Timeout) ->
+ recvmsg(Socket, 0, 0, ?SOCKET_RECV_FLAGS_DEFAULT, Timeout).
+
+-spec recvmsg(Socket, Flags, Timeout) -> {ok, MsgHdr} | {error, Reason} when
+ Socket :: socket(),
+ Flags :: recv_flags(),
+ Timeout :: timeout(),
+ MsgHdr :: msghdr(),
+ Reason :: term()
+ ; (Socket, BufSz, CtrlSz) -> {ok, MsgHdr} | {error, Reason} when
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ CtrlSz :: non_neg_integer(),
+ MsgHdr :: msghdr(),
+ Reason :: term().
+
+recvmsg(Socket, Flags, Timeout) when is_list(Flags) ->
+ recvmsg(Socket, 0, 0, Flags, Timeout);
+recvmsg(Socket, BufSz, CtrlSz) when is_integer(BufSz) andalso is_integer(CtrlSz) ->
+ recvmsg(Socket, BufSz, CtrlSz,
+ ?SOCKET_RECV_FLAGS_DEFAULT, ?SOCKET_RECV_TIMEOUT_DEFAULT).
+
+
+-spec recvmsg(Socket,
+ BufSz, CtrlSz,
+ Flags, Timeout) -> {ok, MsgHdr} | {error, Reason} when
+ Socket :: socket(),
+ BufSz :: non_neg_integer(),
+ CtrlSz :: non_neg_integer(),
+ Flags :: recv_flags(),
+ Timeout :: timeout(),
+ MsgHdr :: msghdr(),
+ Reason :: term().
+
+recvmsg(#socket{ref = SockRef}, BufSz, CtrlSz, Flags, Timeout)
+ when (is_integer(BufSz) andalso (BufSz >= 0)) andalso
+ (is_integer(CtrlSz) andalso (CtrlSz >= 0)) andalso
+ is_list(Flags) andalso
+ (is_integer(Timeout) orelse (Timeout =:= infinity)) ->
+ EFlags = enc_recv_flags(Flags),
+ do_recvmsg(SockRef, BufSz, CtrlSz, EFlags, Timeout).
+
+do_recvmsg(SockRef, BufSz, CtrlSz, EFlags, Timeout) ->
+ TS = timestamp(Timeout),
+ RecvRef = make_ref(),
+ case nif_recvmsg(SockRef, RecvRef, BufSz, CtrlSz, EFlags) of
+ {ok, _MsgHdr} = OK ->
+ OK;
+
+ {error, eagain} ->
+ %% There is nothing just now, but we will be notified when there
+ %% is something to read (a select message).
+ NewTimeout = next_timeout(TS, Timeout),
+ receive
+ {select, SockRef, RecvRef, ready_input} ->
+ do_recvmsg(SockRef, BufSz, CtrlSz, EFlags,
+ next_timeout(TS, Timeout));
+
+ {'$socket', _, abort, {RecvRef, Reason}} ->
+ {error, Reason}
+
+ after NewTimeout ->
+ cancel(SockRef, recvmsg, RecvRef),
+ {error, timeout}
+ end;
+
+ {error, closed} = ERROR ->
+ do_close(SockRef),
+ ERROR;
+
+ {error, _Reason} = ERROR ->
+ ERROR
+
+ end.
+
+
+
+%% ===========================================================================
+%%
+%% readv - read data into multiple buffers
+%%
+
+
+
+%% ===========================================================================
+%%
+%% close - close a file descriptor
+%%
+%% Closing a socket is a two stage rocket (because of linger).
+%% We need to perform the actual socket close while in BLOCKING mode.
+%% But that would hang the entire VM, so what we do is divide the
+%% close in two steps:
+%% 1) nif_close + the socket_stop (nif) callback function
+%% This is for everything that can be done safely NON-BLOCKING.
+%% 2) nif_finalize_close which is executed by a *dirty* scheduler
+%% Before we call the socket close function, we se the socket
+%% BLOCKING. Thereby linger is handled properly.
+
+
+-spec close(Socket) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Reason :: term().
+
+close(#socket{ref = SockRef}) ->
+ do_close(SockRef).
+
+do_close(SockRef) ->
+ case nif_close(SockRef) of
+ ok ->
+ nif_finalize_close(SockRef);
+ {ok, CloseRef} ->
+ %% We must wait for the socket_stop callback function to
+ %% complete its work
+ receive
+ {'$socket', SockRef, close, CloseRef} ->
+ nif_finalize_close(SockRef)
+ end;
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+
+
+%% ===========================================================================
+%%
+%% shutdown - shut down part of a full-duplex connection
+%%
+
+-spec shutdown(Socket, How) -> ok | {error, Reason} when
+ Socket :: socket(),
+ How :: shutdown_how(),
+ Reason :: term().
+
+shutdown(#socket{ref = SockRef}, How) ->
+ try
+ begin
+ EHow = enc_shutdown_how(How),
+ nif_shutdown(SockRef, EHow)
+ end
+ catch
+ throw:T ->
+ T;
+ %% <WIN32-TEMPORARY>
+ error:notsup:S ->
+ erlang:raise(error, notsup, S);
+ %% </WIN32-TEMPORARY>
+ error:Reason ->
+ {error, Reason}
+ end.
+
+
+
+
+%% ===========================================================================
+%%
+%% setopt - manipulate individual properties of a socket
+%%
+%% What properties are valid depend on what kind of socket it is
+%% (domain, type and protocol)
+%% If its an "invalid" option (or value), we should not crash but return some
+%% useful error...
+%%
+%% <KOLLA>
+%%
+%% WE NEED TO MAKE SURE THAT THE USER DOES NOT MAKE US BLOCKING
+%% AS MUCH OF THE CODE EXPECTS TO BE NON-BLOCKING!!
+%%
+%% </KOLLA>
+
+-spec setopt(Socket, otp, otp_socket_option(), Value) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: term()
+ ; (Socket, socket, socket_option(), Value) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: term()
+ ; (Socket, ip, ip_socket_option(), Value) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: term()
+ ; (Socket, ipv6, ipv6_socket_option(), Value) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: term()
+ ; (Socket, tcp, tcp_socket_option(), Value) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: term()
+ ; (Socket, udp, udp_socket_option(), Value) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: term()
+ ; (Socket, sctp, sctp_socket_option(), Value) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: term()
+ ; (Socket, Level, Key, Value) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Level :: non_neg_integer(),
+ Key :: non_neg_integer(),
+ Value :: binary(),
+ Reason :: term().
+
+setopt(#socket{ref = SockRef}, Level, Key, Value) ->
+ try
+ begin
+ Domain = which_domain(SockRef),
+ Type = which_type(SockRef),
+ Protocol = which_protocol(SockRef),
+ {EIsEncoded, ELevel} = enc_setopt_level(Level),
+ EKey = enc_setopt_key(Level, Key, Domain, Type, Protocol),
+ EVal = enc_setopt_value(Level, Key, Value, Domain, Type, Protocol),
+ nif_setopt(SockRef, EIsEncoded, ELevel, EKey, EVal)
+ end
+ catch
+ throw:T ->
+ T;
+ %% <WIN32-TEMPORARY>
+ error:notsup:S ->
+ erlang:raise(error, notsup, S);
+ %% </WIN32-TEMPORARY>
+ error:Reason ->
+ {error, Reason} % Process more?
+ end.
+
+
+
+
+%% ===========================================================================
+%%
+%% getopt - retrieve individual properties of a socket
+%%
+%% What properties are valid depend on what kind of socket it is
+%% (domain, type and protocol).
+%% If its an "invalid" option, we should not crash but return some
+%% useful error...
+%%
+%% When specifying level as an integer, and therefor using "native mode",
+%% we should make it possible to specify common types instead of the
+%% value size. Example: int | bool | {string, pos_integer()} | non_neg_integer()
+%%
+
+-spec getopt(Socket, otp, otp_socket_option()) -> {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: term()
+ ; (Socket, socket, socket_option()) -> {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: term()
+ ; (Socket, ip, ip_socket_option()) -> {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: term()
+ ; (Socket, ipv6, ipv6_socket_option()) -> {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: term()
+ ; (Socket, tcp, tcp_socket_option()) -> {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: term()
+ ; (Socket, udp, udp_socket_option()) -> {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: term()
+ ; (Socket, sctp, sctp_socket_option()) -> {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Value :: term(),
+ Reason :: term()
+ ; (Socket, Level, Key) -> ok | {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Level :: integer(),
+ Key :: {NativeOpt, ValueSize},
+ NativeOpt :: integer(),
+ ValueSize :: int | bool | non_neg_integer(),
+ Value :: term(),
+ Reason :: term().
+
+getopt(#socket{ref = SockRef}, Level, Key) ->
+ try
+ begin
+ Domain = which_domain(SockRef),
+ Type = which_type(SockRef),
+ Protocol = which_protocol(SockRef),
+ {EIsEncoded, ELevel} = enc_getopt_level(Level),
+ EKey = enc_getopt_key(Level, Key, Domain, Type, Protocol),
+ %% We may need to decode the value (for the same reason
+ %% we (may have) needed to encode the value for setopt).
+ case nif_getopt(SockRef, EIsEncoded, ELevel, EKey) of
+ ok ->
+ ok;
+ {ok, EVal} ->
+ Val = dec_getopt_value(Level, Key, EVal,
+ Domain, Type, Protocol),
+ {ok, Val};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end
+ catch
+ throw:E:_S ->
+ E;
+ %% <WIN32-TEMPORARY>
+ error:notsup:S ->
+ erlang:raise(error, notsup, S);
+ %% </WIN32-TEMPORARY>
+ error:Reason:_Stack ->
+ {error, Reason} % Process more?
+ end.
+
+
+%% These are internal "shortcut" functions for the options
+%% domain, type and protocol.
+
+-spec which_domain(SockRef) -> Domain when
+ SockRef :: reference(),
+ Domain :: domain().
+
+which_domain(SockRef) ->
+ case nif_getopt(SockRef, true,
+ ?SOCKET_OPT_LEVEL_OTP, ?SOCKET_OPT_OTP_DOMAIN) of
+ {ok, Domain} ->
+ Domain;
+ {error, _} = ERROR ->
+ throw(ERROR)
+ end.
+
+
+-spec which_type(SockRef) -> Type when
+ SockRef :: reference(),
+ Type :: type().
+
+which_type(SockRef) ->
+ case nif_getopt(SockRef, true,
+ ?SOCKET_OPT_LEVEL_OTP, ?SOCKET_OPT_OTP_TYPE) of
+ {ok, Type} ->
+ Type;
+ {error, _} = ERROR ->
+ throw(ERROR)
+ end.
+
+-spec which_protocol(SockRef) -> Protocol when
+ SockRef :: reference(),
+ Protocol :: protocol().
+
+which_protocol(SockRef) ->
+ case nif_getopt(SockRef, true,
+ ?SOCKET_OPT_LEVEL_OTP, ?SOCKET_OPT_OTP_PROTOCOL) of
+ {ok, Proto} ->
+ Proto;
+ {error, _} = ERROR ->
+ throw(ERROR)
+ end.
+
+
+%% ===========================================================================
+%%
+%% sockname - return the current address of the socket.
+%%
+%%
+
+-spec sockname(Socket) -> {ok, SockAddr} | {error, Reason} when
+ Socket :: socket(),
+ SockAddr :: sockaddr(),
+ Reason :: term().
+
+sockname(#socket{ref = SockRef}) ->
+ nif_sockname(SockRef).
+
+
+
+%% ===========================================================================
+%%
+%% peername - return the address of the peer *connected* to the socket.
+%%
+%%
+
+-spec peername(Socket) -> {ok, SockAddr} | {error, Reason} when
+ Socket :: socket(),
+ SockAddr :: sockaddr(),
+ Reason :: term().
+
+peername(#socket{ref = SockRef}) ->
+ nif_peername(SockRef).
+
+
+
+%% ===========================================================================
+%%
+%% Encode / decode
+%%
+%% ===========================================================================
+
+-spec enc_domain(Domain) -> non_neg_integer() when
+ Domain :: domain().
+
+enc_domain(local) -> ?SOCKET_DOMAIN_LOCAL;
+enc_domain(inet) -> ?SOCKET_DOMAIN_INET;
+enc_domain(inet6) -> ?SOCKET_DOMAIN_INET6;
+enc_domain(Domain) -> throw({error, {invalid_domain, Domain}}).
+
+-spec enc_type(Domain, Type) -> non_neg_integer() when
+ Domain :: domain(),
+ Type :: type().
+
+%% What combos are valid?
+enc_type(_, stream) -> ?SOCKET_TYPE_STREAM;
+enc_type(_, dgram) -> ?SOCKET_TYPE_DGRAM;
+enc_type(_, raw) -> ?SOCKET_TYPE_RAW;
+enc_type(_, seqpacket) -> ?SOCKET_TYPE_SEQPACKET;
+enc_type(_, Type) -> throw({error, {invalid_type, Type}}).
+
+-spec enc_protocol(Type, Protocol) -> non_neg_integer() |
+ {raw, non_neg_integer()} when
+ Type :: type(),
+ Protocol :: protocol().
+
+enc_protocol(dgram, ip) -> ?SOCKET_PROTOCOL_IP;
+enc_protocol(stream, tcp) -> ?SOCKET_PROTOCOL_TCP;
+enc_protocol(dgram, udp) -> ?SOCKET_PROTOCOL_UDP;
+enc_protocol(seqpacket, sctp) -> ?SOCKET_PROTOCOL_SCTP;
+enc_protocol(raw, icmp) -> ?SOCKET_PROTOCOL_ICMP;
+enc_protocol(raw, igmp) -> ?SOCKET_PROTOCOL_IGMP;
+enc_protocol(raw, {raw, P} = RAW) when is_integer(P) -> RAW;
+enc_protocol(Type, Proto) ->
+ throw({error, {invalid_protocol, {Type, Proto}}}).
+
+
+-spec enc_send_flags(Flags) -> non_neg_integer() when
+ Flags :: send_flags().
+
+enc_send_flags(Flags) ->
+ EFlags = [{confirm, ?SOCKET_SEND_FLAG_CONFIRM},
+ {dontroute, ?SOCKET_SEND_FLAG_DONTROUTE},
+ {eor, ?SOCKET_SEND_FLAG_EOR},
+ {more, ?SOCKET_SEND_FLAG_MORE},
+ {nosignal, ?SOCKET_SEND_FLAG_NOSIGNAL},
+ {oob, ?SOCKET_SEND_FLAG_OOB}],
+ enc_flags(Flags, EFlags).
+
+-spec enc_recv_flags(Flags) -> non_neg_integer() when
+ Flags :: recv_flags().
+
+enc_recv_flags(Flags) ->
+ EFlags = [{cmsg_cloexec, ?SOCKET_RECV_FLAG_CMSG_CLOEXEC},
+ {errqueue, ?SOCKET_RECV_FLAG_ERRQUEUE},
+ {oob, ?SOCKET_RECV_FLAG_OOB},
+ {peek, ?SOCKET_RECV_FLAG_PEEK},
+ {trunc, ?SOCKET_RECV_FLAG_TRUNC}],
+ enc_flags(Flags, EFlags).
+
+
+enc_flags([], _) ->
+ 0;
+enc_flags(Flags, EFlags) ->
+ F = fun(Flag, Acc) ->
+ case lists:keysearch(Flag, 1, EFlags) of
+ {value, {Flag, EFlag}} ->
+ Acc bor (1 bsl EFlag);
+ false ->
+ throw({error, {unknown_flag, Flag}})
+ end
+ end,
+ lists:foldl(F, 0, Flags).
+
+
+%% +++ Encode setopt level +++
+
+-spec enc_setopt_level(Level) -> {IsEncoded, EncodedLevel} when
+ Level :: sockopt_level(),
+ IsEncoded :: boolean(),
+ EncodedLevel :: integer().
+
+enc_setopt_level(otp) ->
+ {true, ?SOCKET_OPT_LEVEL_OTP};
+enc_setopt_level(socket) ->
+ {true, ?SOCKET_OPT_LEVEL_SOCKET};
+enc_setopt_level(ip) ->
+ {true, ?SOCKET_OPT_LEVEL_IP};
+enc_setopt_level(ipv6) ->
+ {true, ?SOCKET_OPT_LEVEL_IPV6};
+enc_setopt_level(tcp) ->
+ {true, ?SOCKET_OPT_LEVEL_TCP};
+enc_setopt_level(udp) ->
+ {true, ?SOCKET_OPT_LEVEL_UDP};
+enc_setopt_level(sctp) ->
+ {true, ?SOCKET_OPT_LEVEL_SCTP};
+%% Any option that is of an plain level must be provided as a binary
+%% already fully encoded!
+enc_setopt_level(L) when is_integer(L) ->
+ {false, L}.
+
+
+%% +++ Encode setopt key +++
+
+%% We should ...really... do something with the domain, type and protocol args...
+%% Also, any option (key) which has an integer level (plain) must also be provided
+%% in a plain mode, that is, as an integer.
+%% Also, not all options are available on all platforms. That is something we
+%% don't check here, but in the nif-code.
+
+enc_setopt_key(Level, Opt, Domain, Type, Protocol) ->
+ enc_sockopt_key(Level, Opt, set, Domain, Type, Protocol).
+
+
+%% +++ Encode setopt value +++
+%%
+%% For the most part this function does *not* do an actual encode,
+%% it simply validates the value type. But in some cases it will
+%% encode the value into an more "manageable" type.
+%% It also handles "aliases" (see linger).
+
+-dialyzer({nowarn_function, enc_setopt_value/6}).
+-spec enc_setopt_value(otp, otp_socket_option(),
+ Value, Domain, Type, Protocol) -> term() when
+ Value :: term(),
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: protocol()
+ ; (socket, socket_option(),
+ Value, Domain, Type, Protocol) -> term() when
+ Value :: term(),
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: protocol()
+ ; (ip, ip_socket_option(),
+ Value, Domain, Type, Protocol) -> term() when
+ Value :: term(),
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: protocol()
+ ; (ipv6, ipv6_socket_option(),
+ Value, Domain, Type, Protocol) -> term() when
+ Value :: term(),
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: protocol()
+ ; (tcp, tcp_socket_option(),
+ Value, Domain, Type, Protocol) -> term() when
+ Value :: term(),
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: protocol()
+ ; (udp, udp_socket_option(),
+ Value, Domain, Type, Protocol) -> term() when
+ Value :: term(),
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: protocol()
+ ; (sctp, sctp_socket_option(),
+ Value, Domain, Type, Protocol) -> term() when
+ Value :: term(),
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: protocol()
+ ; (Level, Opt,
+ Value, Domain, Type, Protocol) -> term() when
+ Level :: integer(),
+ Opt :: integer(),
+ Value :: binary(),
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: protocol().
+
+enc_setopt_value(otp, debug, V, _, _, _) when is_boolean(V) ->
+ V;
+enc_setopt_value(otp, iow, V, _, _, _) when is_boolean(V) ->
+ V;
+enc_setopt_value(otp, controlling_process, V, _, _, _) when is_pid(V) ->
+ V;
+enc_setopt_value(otp, rcvbuf, V, _, _, _) when (V =:= default) ->
+ 0; % This will cause the nif-code to choose the default value
+enc_setopt_value(otp, rcvbuf, V, _, _, _) when is_integer(V) andalso (V > 0) ->
+ V;
+%% N: Number of reads (when specifying length = 0)
+%% V: Size of the "read" buffer
+enc_setopt_value(otp, rcvbuf, {N, BufSz} = V, _, stream = _T, tcp = _P)
+ when (is_integer(N) andalso (N > 0)) andalso
+ (is_integer(BufSz) andalso (BufSz > 0)) ->
+ V;
+enc_setopt_value(otp, rcvctrlbuf, V, _, _, _) when (V =:= default) ->
+ 0;
+enc_setopt_value(otp, rcvctrlbuf, V, _, _, _) when is_integer(V) andalso (V > 0) ->
+ V;
+enc_setopt_value(otp, sndctrlbuf, V, _, _, _) when (V =:= default) ->
+ 0;
+enc_setopt_value(otp, sndctrlbuf, V, _, _, _) when is_integer(V) andalso (V > 0) ->
+ V;
+enc_setopt_value(otp = L, Opt, V, _D, _T, _P) ->
+ not_supported({L, Opt, V});
+
+enc_setopt_value(socket, bindtodevice, V, _D, _T, _P) when is_list(V) ->
+ V;
+enc_setopt_value(socket, broadcast, V, _D, _T, _P) when is_boolean(V) ->
+ V;
+enc_setopt_value(socket, debug, V, _D, _T, _P) when is_integer(V) ->
+ V;
+enc_setopt_value(socket, dontroute, V, _D, _T, _P) when is_boolean(V) ->
+ V;
+enc_setopt_value(socket, keepalive, V, _D, _T, _P) when is_boolean(V) ->
+ V;
+enc_setopt_value(socket, linger, abort, D, T, P) ->
+ enc_setopt_value(socket, linger, {true, 0}, D, T, P);
+enc_setopt_value(socket, linger, {OnOff, Secs} = V, _D, _T, _P)
+ when is_boolean(OnOff) andalso is_integer(Secs) andalso (Secs >= 0) ->
+ V;
+enc_setopt_value(socket, oobinline, V, _D, _T, _P) when is_boolean(V) ->
+ V;
+enc_setopt_value(socket, peek_off, V, _D, _T, _P) when is_integer(V) ->
+ V;
+enc_setopt_value(socket, priority, V, _D, _T, _P) when is_integer(V) ->
+ V;
+enc_setopt_value(socket, rcvbuf, V, _D, _T, _P) when is_integer(V) ->
+ V;
+enc_setopt_value(socket, rcvlowat, V, _D, _T, _P) when is_integer(V) ->
+ V;
+enc_setopt_value(socket, rcvtimeo, #{sec := Sec, usec := USec} = V, _D, _T, _P)
+ when is_integer(Sec) andalso is_integer(USec) ->
+ V;
+enc_setopt_value(socket, reuseaddr, V, _D, _T, _P) when is_boolean(V) ->
+ V;
+enc_setopt_value(socket, reuseport, V, _D, _T, _P) when is_boolean(V) ->
+ V;
+enc_setopt_value(socket, sndbuf, V, _D, _T, _P) when is_integer(V) ->
+ V;
+enc_setopt_value(socket, sndlowat, V, _D, _T, _P) when is_integer(V) ->
+ V;
+enc_setopt_value(socket, sndtimeo, #{sec := Sec, usec := USec} = V, _D, _T, _P)
+ when is_integer(Sec) andalso is_integer(USec) ->
+ V;
+enc_setopt_value(socket, timestamp, V, _D, _T, _P) when is_boolean(V) ->
+ V;
+enc_setopt_value(socket = L, Opt, V, _D, _T, _P) ->
+ not_supported({L, Opt, V});
+
+enc_setopt_value(ip, add_membership, #{multiaddr := MA,
+ interface := IF} = V, _D, _T, _P)
+ when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso
+ ((IF =:= any) orelse (is_tuple(IF) andalso (size(IF) =:= 4))) ->
+ V;
+enc_setopt_value(ip, add_source_membership, #{multiaddr := MA,
+ interface := IF,
+ sourceaddr := SA} = V, _D, _T, _P)
+ when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso
+ (is_tuple(IF) andalso (size(IF) =:= 4)) andalso
+ (is_tuple(SA) andalso (size(SA) =:= 4)) ->
+ V;
+enc_setopt_value(ip, block_source, #{multiaddr := MA,
+ interface := IF,
+ sourceaddr := SA} = V, _D, _T, _P)
+ when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso
+ (is_tuple(IF) andalso (size(IF) =:= 4)) andalso
+ (is_tuple(SA) andalso (size(SA) =:= 4)) ->
+ V;
+enc_setopt_value(ip, drop_membership, #{multiaddr := MA,
+ interface := IF} = V, _D, _T, _P)
+ when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso
+ ((IF =:= any) orelse (is_tuple(IF) andalso (size(IF) =:= 4))) ->
+ V;
+enc_setopt_value(ip, drop_source_membership, #{multiaddr := MA,
+ interface := IF,
+ sourceaddr := SA} = V, _D, _T, _P)
+ when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso
+ (is_tuple(IF) andalso (size(IF) =:= 4)) andalso
+ (is_tuple(SA) andalso (size(SA) =:= 4)) ->
+ V;
+enc_setopt_value(ip, freebind, V, _D, _T, _P) when is_boolean(V) ->
+ V;
+enc_setopt_value(ip, hdrincl, V, _D, _T, _P) when is_boolean(V) ->
+ V;
+enc_setopt_value(ip, minttl, V, _D, _T, _P) when is_integer(V) ->
+ V;
+enc_setopt_value(ip, msfilter, null = V, _D, _T, _P) ->
+ V;
+enc_setopt_value(ip, msfilter, #{multiaddr := MA,
+ interface := IF,
+ fmode := FMode,
+ slist := SL} = V, _D, _T, _P)
+ when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso
+ (is_tuple(IF) andalso (size(IF) =:= 4)) andalso
+ ((FMode =:= include) orelse (FMode =:= exclude)) andalso
+ is_list(SL) ->
+ ensure_ip_msfilter_slist(SL),
+ V;
+enc_setopt_value(ip, mtu_discover, V, _D, _T, _P)
+ when (V =:= want) orelse
+ (V =:= dont) orelse
+ (V =:= do) orelse
+ (V =:= probe) orelse
+ is_integer(V) ->
+ V;
+enc_setopt_value(ip, multicast_all, V, _D, _T, _P)
+ when is_boolean(V) ->
+ V;
+enc_setopt_value(ip, multicast_if, V, _D, _T, _P)
+ when (V =:= any) orelse (is_tuple(V) andalso (size(V) =:= 4)) ->
+ V;
+enc_setopt_value(ip, multicast_loop, V, _D, _T, _P)
+ when is_boolean(V) ->
+ V;
+enc_setopt_value(ip, multicast_ttl, V, _D, _T, _P)
+ when is_integer(V) andalso (0 =< V) andalso (V =< 255) ->
+ V;
+enc_setopt_value(ip, nodefrag, V, _D, _T, _P)
+ when is_boolean(V) ->
+ V;
+enc_setopt_value(ip, pktinfo, V, _D, _T, _P)
+ when is_boolean(V) ->
+ V;
+enc_setopt_value(ip, recvdstaddr, V, _D, _T, _P)
+ when is_boolean(V) ->
+ V;
+enc_setopt_value(ip, recverr, V, _D, _T, _P)
+ when is_boolean(V) ->
+ V;
+enc_setopt_value(ip, recvif, V, _D, _T, _P)
+ when is_boolean(V) ->
+ V;
+enc_setopt_value(ip, recvopts, V, _D, _T, _P)
+ when is_boolean(V) ->
+ V;
+enc_setopt_value(ip, recvorigdstaddr, V, _D, _T, _P)
+ when is_boolean(V) ->
+ V;
+enc_setopt_value(ip, recvtos, V, _D, _T, _P)
+ when is_boolean(V) ->
+ V;
+enc_setopt_value(ip, recvttl, V, _D, _T, _P)
+ when is_boolean(V) ->
+ V;
+enc_setopt_value(ip, retopts, V, _D, _T, _P)
+ when is_boolean(V) ->
+ V;
+enc_setopt_value(ip, router_alert, V, _D, _T, _P)
+ when is_integer(V) ->
+ V;
+enc_setopt_value(ip, sendsrcaddr, V, _D, _T, _P)
+ when is_boolean(V) ->
+ V;
+enc_setopt_value(ip, tos, V, _D, _T, _P)
+ when (V =:= lowdelay) orelse
+ (V =:= throughput) orelse
+ (V =:= reliability) orelse
+ (V =:= mincost) orelse
+ is_integer(V) ->
+ V;
+enc_setopt_value(ip, transparent, V, _D, _T, _P)
+ when is_boolean(V) ->
+ V;
+enc_setopt_value(ip, ttl, V, _D, _T, _P)
+ when is_integer(V) ->
+ V;
+enc_setopt_value(ip, unblock_source, #{multiaddr := MA,
+ interface := IF,
+ sourceaddr := SA} = V, _D, _T, _P)
+ when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso
+ (is_tuple(IF) andalso (size(IF) =:= 4)) andalso
+ (is_tuple(SA) andalso (size(SA) =:= 4)) ->
+ V;
+enc_setopt_value(ip = L, Opt, V, _D, _T, _P) ->
+ not_supported({L, Opt, V});
+
+enc_setopt_value(ipv6, addrform, inet = V, _D, _T, _P) ->
+ enc_domain(V);
+enc_setopt_value(ipv6, add_membership, #{multiaddr := MA,
+ interface := IF} = V, _D, _T, _P)
+ when ((is_tuple(MA) andalso (size(MA) =:= 8)) andalso
+ (is_integer(IF) andalso (IF >= 0))) ->
+ V;
+%% Is this obsolete? When get, the result is enoprotoopt and in the
+%% header file it says 'obsolete'...
+%% But there might be (old?) versions of linux where it still works...
+enc_setopt_value(ipv6, authhdr, V, _D, T, _P)
+ when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) ->
+ V;
+enc_setopt_value(ipv6, dstopts, V, _D, T, _P)
+ when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) ->
+ V;
+enc_setopt_value(ipv6, drop_membership, #{multiaddr := MA,
+ interface := IF} = V, _D, _T, _P)
+ when ((is_tuple(MA) andalso (size(MA) =:= 8)) andalso
+ (is_integer(IF) andalso (IF >= 0))) ->
+ V;
+enc_setopt_value(ipv6, flowinfo, V, _D, T, _P)
+ when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) ->
+ V;
+enc_setopt_value(ipv6, hoplimit, V, _D, T, _P)
+ when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) ->
+ V;
+enc_setopt_value(ipv6, hopopts, V, _D, T, _P)
+ when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) ->
+ V;
+enc_setopt_value(ipv6, mtu, V, _D, _T, _P) when is_integer(V) ->
+ V;
+enc_setopt_value(ipv6, mtu_discover, V, _D, _T, _P)
+ when (V =:= want) orelse
+ (V =:= dont) orelse
+ (V =:= do) orelse
+ (V =:= probe) orelse
+ is_integer(V) ->
+ V;
+enc_setopt_value(ipv6, multicast_hops, V, _D, _T, _P)
+ when (V =:= default) ->
+ -1;
+enc_setopt_value(ipv6, multicast_hops, V, _D, _T, _P)
+ when is_integer(V) andalso (V >= 0) andalso (V =< 255) ->
+ V;
+enc_setopt_value(ipv6, multicast_if, V, _D, _T, _P)
+ when is_integer(V) ->
+ V;
+enc_setopt_value(ipv6, multicast_loop, V, _D, _T, _P)
+ when is_boolean(V) ->
+ V;
+enc_setopt_value(ipv6, recverr, V, _D, _T, _P)
+ when is_boolean(V) ->
+ V;
+enc_setopt_value(ipv6, Opt, V, _D, _T, _P)
+ when ((Opt =:= recvpktinfo) orelse (Opt =:= pktinfo)) andalso
+ is_boolean(V) ->
+ V;
+enc_setopt_value(ipv6, router_alert, V, _D, T, _P)
+ when is_integer(V) andalso (T =:= raw) ->
+ V;
+enc_setopt_value(ipv6, rthdr, V, _D, T, _P)
+ when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) ->
+ V;
+enc_setopt_value(ipv6, unicast_hops, V, _D, _T, _P)
+ when (V =:= default) ->
+ -1;
+enc_setopt_value(ipv6, unicast_hops, V, _D, _T, _P)
+ when is_integer(V) andalso (V >= 0) andalso (V =< 255) ->
+ V;
+enc_setopt_value(ipv6, v6only, V, _D, _T, _P) when is_boolean(V) ->
+ V;
+enc_setopt_value(ipv6 = L, Opt, V, _D, _T, _P) ->
+ not_supported({L, Opt, V});
+
+enc_setopt_value(tcp, congestion, V, _D, T, P)
+ when is_list(V) andalso
+ (T =:= stream) andalso
+ (P =:= tcp) ->
+ V;
+enc_setopt_value(tcp, maxseg, V, _D, T, P)
+ when is_integer(V) andalso
+ (T =:= stream) andalso
+ (P =:= tcp) ->
+ V;
+enc_setopt_value(tcp, nodelay, V, _D, T, P)
+ when is_boolean(V) andalso
+ (T =:= stream) andalso
+ (P =:= tcp) ->
+ V;
+enc_setopt_value(tcp = L, Opt, V, _D, _T, _P) ->
+ not_supported({L, Opt, V});
+
+enc_setopt_value(udp, cork, V, _D, T, P)
+ when is_boolean(V) andalso (T =:= dgram) andalso (P =:= udp) ->
+ V;
+enc_setopt_value(udp = L, Opt, _V, _D, _T, _P) ->
+ not_supported({L, Opt});
+
+enc_setopt_value(sctp, associnfo, #{assoc_id := AssocId,
+ asocmaxrxt := MaxRxt,
+ num_peer_dests := NumPeerDests,
+ peer_rwnd := PeerRWND,
+ local_rwnd := LocalRWND,
+ cookie_life := CLife} = V,
+ _D, _T, P)
+ when is_integer(AssocId) andalso
+ is_integer(MaxRxt) andalso (MaxRxt >= 0) andalso
+ is_integer(NumPeerDests) andalso (NumPeerDests >= 0) andalso
+ is_integer(PeerRWND) andalso (PeerRWND >= 0) andalso
+ is_integer(LocalRWND) andalso (LocalRWND >= 0) andalso
+ is_integer(CLife) andalso (CLife >= 0) andalso
+ (P =:= sctp) ->
+ V;
+enc_setopt_value(sctp, autoclose, V, _D, _T, P)
+ when is_integer(V) andalso (V >= 0) andalso (P =:= sctp) ->
+ V;
+enc_setopt_value(sctp, disable_fragments, V, _D, _T, P)
+ when is_boolean(V) andalso (P =:= sctp) ->
+ V;
+enc_setopt_value(sctp, events, #{data_in := DataIn,
+ association := Assoc,
+ address := Addr,
+ send_failure := SndFailure,
+ peer_error := PeerError,
+ shutdown := Shutdown,
+ partial_delivery := PartialDelivery,
+ adaptation_layer := AdaptLayer,
+ authentication := Auth,
+ sender_dry := SndDry} = V, _D, _T, P)
+ when is_boolean(DataIn) andalso
+ is_boolean(Assoc) andalso
+ is_boolean(Addr) andalso
+ is_boolean(SndFailure) andalso
+ is_boolean(PeerError) andalso
+ is_boolean(Shutdown) andalso
+ is_boolean(PartialDelivery) andalso
+ is_boolean(AdaptLayer) andalso
+ is_boolean(Auth) andalso
+ is_boolean(SndDry) andalso
+ (P =:= sctp) ->
+ V;
+enc_setopt_value(sctp, initmsg, #{num_outstreams := NumOut,
+ max_instreams := MaxIn,
+ max_attempts := MaxAttempts,
+ max_init_timeo := MaxInitTO} = V,
+ _D, _T, P)
+ when is_integer(NumOut) andalso (NumOut >= 0) andalso
+ is_integer(MaxIn) andalso (MaxIn >= 0) andalso
+ is_integer(MaxAttempts) andalso (MaxAttempts >= 0) andalso
+ is_integer(MaxInitTO) andalso (MaxInitTO >= 0) andalso
+ (P =:= sctp) ->
+ V;
+enc_setopt_value(sctp, maxseg, V, _D, _T, P)
+ when is_integer(V) andalso (V >= 0) andalso (P =:= sctp) ->
+ V;
+enc_setopt_value(sctp, nodelay, V, _D, _T, P)
+ when is_boolean(V) andalso (P =:= sctp) ->
+ V;
+enc_setopt_value(sctp, rtoinfo, #{assoc_id := AssocId,
+ initial := Init,
+ max := Max,
+ min := Min} = V,
+ _D, _T, P)
+ when is_integer(AssocId) andalso
+ is_integer(Init) andalso (Init >= 0) andalso
+ is_integer(Max) andalso (Max >= 0) andalso
+ is_integer(Min) andalso (Min >= 0) andalso
+ (P =:= sctp) ->
+ V;
+enc_setopt_value(sctp = L, Opt, V, _D, _T, _P) ->
+ not_supported({L, Opt, V});
+
+%% enc_setopt_value(raw = L, Opt, _V, _D, _T, _P) ->
+%% not_supported({L, Opt});
+
+%% Is this correct? What about getopt?
+enc_setopt_value(L, Opt, V, _, _, _)
+ when is_integer(L) andalso is_integer(Opt) andalso is_binary(V) ->
+ V.
+
+
+
+
+%% +++ Encode getopt value +++
+
+enc_getopt_level(Level) ->
+ enc_setopt_level(Level).
+
+
+%% +++ Encode getopt key +++
+
+enc_getopt_key(Level, Opt, Domain, Type, Protocol) ->
+ enc_sockopt_key(Level, Opt, get, Domain, Type, Protocol).
+
+
+%% +++ Decode getopt value +++
+%%
+%% For the most part, we simply let the value pass through, but for some
+%% values we may need to do an actual decode.
+%%
+
+%% Let the user deal with this for now...
+dec_getopt_value(_L, _Opt, V, _D, _T, _P) ->
+ V.
+
+
+
+%% +++ Encode socket option key +++
+
+%% Most options are usable both for set and get, but some are
+%% are only available for e.g. get.
+-spec enc_sockopt_key(Level, Opt,
+ Direction,
+ Domain, Type, Protocol) -> non_neg_integer() when
+ Level :: otp,
+ Direction :: set | get,
+ Opt :: otp_socket_option(),
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: protocol()
+ ; (Level, Direction, Opt,
+ Domain, Type, Protocol) -> non_neg_integer() when
+ Level :: socket,
+ Direction :: set | get,
+ Opt :: socket_option(),
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: protocol()
+ ; (Level, Direction, Opt,
+ Domain, Type, Protocol) -> non_neg_integer() when
+ Level :: ip,
+ Direction :: set | get,
+ Opt :: ip_socket_option(),
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: protocol()
+ ; (Level, Direction, Opt,
+ Domain, Type, Protocol) -> non_neg_integer() when
+ Level :: ipv6,
+ Direction :: set | get,
+ Opt :: ipv6_socket_option(),
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: protocol()
+ ; (Level, Direction, Opt,
+ Domain, Type, Protocol) -> non_neg_integer() when
+ Level :: tcp,
+ Direction :: set | get,
+ Opt :: tcp_socket_option(),
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: protocol()
+ ; (Level, Direction, Opt,
+ Domain, Type, Protocol) -> non_neg_integer() when
+ Level :: udp,
+ Direction :: set | get,
+ Opt :: udp_socket_option(),
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: protocol()
+ ; (Level, Direction, Opt,
+ Domain, Type, Protocol) -> non_neg_integer() when
+ Level :: sctp,
+ Direction :: set | get,
+ Opt :: sctp_socket_option(),
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: protocol()
+ ; (Level, Direction, Opt,
+ Domain, Type, Protocol) -> non_neg_integer() when
+ Level :: integer(),
+ Direction :: set,
+ Opt :: integer(),
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: protocol()
+ ; (Level, Direction, Opt,
+ Domain, Type, Protocol) -> non_neg_integer() when
+ Level :: integer(),
+ Direction :: get,
+ Opt :: {NativeOpt, ValueSize},
+ NativeOpt :: integer(),
+ ValueSize :: non_neg_integer(),
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: protocol().
+
+
+%% +++ OTP socket options +++
+enc_sockopt_key(otp, debug, _, _, _, _) ->
+ ?SOCKET_OPT_OTP_DEBUG;
+enc_sockopt_key(otp, iow, _, _, _, _) ->
+ ?SOCKET_OPT_OTP_IOW;
+enc_sockopt_key(otp, controlling_process, _, _, _, _) ->
+ ?SOCKET_OPT_OTP_CTRL_PROC;
+enc_sockopt_key(otp, rcvbuf, _, _, _, _) ->
+ ?SOCKET_OPT_OTP_RCVBUF;
+enc_sockopt_key(otp, rcvctrlbuf, _, _, _, _) ->
+ ?SOCKET_OPT_OTP_RCVCTRLBUF;
+enc_sockopt_key(otp, sndctrlbuf, _, _, _, _) ->
+ ?SOCKET_OPT_OTP_SNDCTRLBUF;
+enc_sockopt_key(otp, fd, get = _Dir, _, _, _) ->
+ ?SOCKET_OPT_OTP_FD;
+enc_sockopt_key(otp = L, Opt, _, _, _, _) ->
+ not_supported({L, Opt});
+
+%% +++ SOCKET socket options +++
+enc_sockopt_key(socket = _L, acceptconn = _Opt, get = _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SOCK_ACCEPTCONN;
+enc_sockopt_key(socket = L, acceptfilter = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+%% Before linux 3.8, this socket option could be set.
+%% Maximum size of buffer for name: IFNAMSZIZ
+%% So, we let the implementation decide.
+enc_sockopt_key(socket = _L, bindtodevice = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SOCK_BINDTODEVICE;
+enc_sockopt_key(socket, broadcast = _Opt, _Dir, _D, dgram = _T, _P) ->
+ ?SOCKET_OPT_SOCK_BROADCAST;
+enc_sockopt_key(socket = L, busy_poll = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(socket = _L, debug = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SOCK_DEBUG;
+enc_sockopt_key(socket, domain = _Opt, get = _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SOCK_DOMAIN;
+enc_sockopt_key(socket, dontroute = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SOCK_DONTROUTE;
+enc_sockopt_key(socket = L, error = Opt, get = _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+%% This is only for connection-oriented sockets, but who are those?
+%% Type = stream or Protocol = tcp?
+%% For now, we just let is pass and it will fail later if not ok...
+enc_sockopt_key(socket, keepalive = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SOCK_KEEPALIVE;
+enc_sockopt_key(socket, linger = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SOCK_LINGER;
+enc_sockopt_key(socket = L, mark = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(socket = _L, oobinline = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SOCK_OOBINLINE;
+enc_sockopt_key(socket = L, passcred = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(socket = _L, peek_off = _Opt, _Dir, local = _D, _T, _P) ->
+ ?SOCKET_OPT_SOCK_PEEK_OFF;
+enc_sockopt_key(socket = L, peekcred = Opt, get = _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(socket, priority = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SOCK_PRIORITY;
+enc_sockopt_key(socket, protocol = _Opt, get = _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SOCK_PROTOCOL;
+enc_sockopt_key(socket, rcvbuf = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SOCK_RCVBUF;
+enc_sockopt_key(socket = L, rcvbufforce = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+%% May not work on linux.
+enc_sockopt_key(socket = _L, rcvlowat = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SOCK_RCVLOWAT;
+enc_sockopt_key(socket = _L, rcvtimeo = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SOCK_RCVTIMEO;
+enc_sockopt_key(socket = _L, reuseaddr = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SOCK_REUSEADDR;
+enc_sockopt_key(socket = _L, reuseport = _Opt, _Dir, D, _T, _P)
+ when ((D =:= inet) orelse (D =:= inet6)) ->
+ ?SOCKET_OPT_SOCK_REUSEPORT;
+enc_sockopt_key(socket = L, rxq_ovfl = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(socket = L, setfib = Opt, set = _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(socket = _L, sndbuf = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SOCK_SNDBUF;
+enc_sockopt_key(socket = L, sndbufforce = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+%% Not changeable on linux.
+enc_sockopt_key(socket = _L, sndlowat = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SOCK_SNDLOWAT;
+enc_sockopt_key(socket = _L, sndtimeo = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SOCK_SNDTIMEO;
+enc_sockopt_key(socket = _L, timestamp = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SOCK_TIMESTAMP;
+enc_sockopt_key(socket = _L, type = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SOCK_TYPE;
+enc_sockopt_key(socket = L, UnknownOpt, _Dir, _D, _T, _P) ->
+ unknown({L, UnknownOpt});
+
+%% +++ IP socket options +++
+enc_sockopt_key(ip = _L, add_membership = _Opt, set = _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IP_ADD_MEMBERSHIP;
+enc_sockopt_key(ip = _L, add_source_membership = _Opt, set = _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IP_ADD_SOURCE_MEMBERSHIP;
+enc_sockopt_key(ip = _L, block_source = _Opt, set = _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IP_BLOCK_SOURCE;
+%% FreeBSD only?
+%% Only respected on udp and raw ip (unless the hdrincl option has been set).
+enc_sockopt_key(ip = L, dontfrag = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(ip = _L, drop_membership = _Opt, set = _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IP_DROP_MEMBERSHIP;
+enc_sockopt_key(ip = _L, drop_source_membership = _Opt, set = _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP;
+%% Linux only?
+enc_sockopt_key(ip = _L, freebind = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IP_FREEBIND;
+enc_sockopt_key(ip = _L, hdrincl = _Opt, _Dir, _D, raw = _T, _P) ->
+ ?SOCKET_OPT_IP_HDRINCL;
+enc_sockopt_key(ip = _L, minttl = _Opt, _Dir, _D, raw = _T, _P) ->
+ ?SOCKET_OPT_IP_MINTTL;
+enc_sockopt_key(ip = _L, msfilter = _Opt, set = _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IP_MSFILTER;
+enc_sockopt_key(ip = _L, mtu = _Opt, get = _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IP_MTU;
+enc_sockopt_key(ip = _L, mtu_discover = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IP_MTU_DISCOVER;
+enc_sockopt_key(ip = _L, multicast_all = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IP_MULTICAST_ALL;
+enc_sockopt_key(ip = _L, multicast_if = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IP_MULTICAST_IF;
+enc_sockopt_key(ip = _L, multicast_loop = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IP_MULTICAST_LOOP;
+enc_sockopt_key(ip = _L, multicast_ttl = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IP_MULTICAST_TTL;
+enc_sockopt_key(ip = _L, nodefrag = _Opt, _Dir, _D, raw = _T, _P) ->
+ ?SOCKET_OPT_IP_NODEFRAG;
+enc_sockopt_key(ip = L, options = Opt, _Dir, _D, _T, _P) ->
+ not_supported({Opt, L});
+enc_sockopt_key(ip = _L, pktinfo = _Opt, _Dir, _D, dgram = _T, _P) ->
+ ?SOCKET_OPT_IP_PKTINFO;
+enc_sockopt_key(ip = _L, recvdstaddr = _Opt, _Dir, _D, T, _P) when (T =:= dgram) ->
+ ?SOCKET_OPT_IP_RECVDSTADDR;
+enc_sockopt_key(ip = _L, recverr = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IP_RECVERR;
+enc_sockopt_key(ip = _L, recvif = _Opt, _Dir, _D, T, _P)
+ when (T =:= dgram) orelse (T =:= raw) ->
+ ?SOCKET_OPT_IP_RECVIF;
+enc_sockopt_key(ip = _L, recvopts = _Opt, _Dir, _D, T, _P) when (T =/= stream) ->
+ ?SOCKET_OPT_IP_RECVOPTS;
+enc_sockopt_key(ip = _L, recvorigdstaddr = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IP_RECVORIGDSTADDR;
+enc_sockopt_key(ip, recvtos = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IP_RECVTOS;
+enc_sockopt_key(ip = _L, recvttl = _Opt, _Dir, _D, T, _P) when (T =/= stream) ->
+ ?SOCKET_OPT_IP_RECVTTL;
+enc_sockopt_key(ip = _L, retopts = _Opt, _Dir, _D, T, _P) when (T =/= stream) ->
+ ?SOCKET_OPT_IP_RETOPTS;
+enc_sockopt_key(ip, router_alert = _Opt, _Dir, _D, raw = _T, _P) ->
+ ?SOCKET_OPT_IP_ROUTER_ALERT;
+enc_sockopt_key(ip, sendsrcaddr = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IP_SENDSRCADDR;
+%% On FreeBSD it specifies that this option is only valid
+%% for stream, dgram and "some" raw sockets...
+%% No such condition on linux (in the man page)...
+enc_sockopt_key(ip, tos = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IP_TOS;
+enc_sockopt_key(ip = _L, transparent = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IP_TRANSPARENT;
+enc_sockopt_key(ip, ttl = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IP_TTL;
+enc_sockopt_key(ip = _L, unblock_source = _Opt, set = _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IP_UNBLOCK_SOURCE;
+enc_sockopt_key(ip = L, UnknownOpt, _Dir, _D, _T, _P) ->
+ unknown({L, UnknownOpt});
+
+%% IPv6 socket options
+enc_sockopt_key(ipv6 = _L, addrform = _Opt, set = _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IPV6_ADDRFORM;
+enc_sockopt_key(ipv6, add_membership = _Opt, set = _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IPV6_ADD_MEMBERSHIP;
+enc_sockopt_key(ipv6 = _L, authhdr = _Opt, _Dir, _D, T, _P)
+ when ((T =:= dgram) orelse (T =:= raw)) ->
+ ?SOCKET_OPT_IPV6_AUTHHDR;
+enc_sockopt_key(ipv6 = L, auth_level = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(ipv6 = L, checksum = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(ipv6, drop_membership = _Opt, set = _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IPV6_DROP_MEMBERSHIP;
+enc_sockopt_key(ipv6 = _L, dstopts = _Opt, _Dir, _D, T, _P)
+ when (T =:= dgram) orelse (T =:= raw) ->
+ ?SOCKET_OPT_IPV6_DSTOPTS;
+enc_sockopt_key(ipv6 = L, esp_trans_level = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(ipv6 = L, esp_network_level = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(ipv6 = _L, flowinfo = _Opt, _Dir, _D, T, _P)
+ when (T =:= dgram) orelse (T =:= raw) ->
+ ?SOCKET_OPT_IPV6_DSTOPTS;
+enc_sockopt_key(ipv6, hoplimit = _Opt, _Dir, _D, T, _P)
+ when (T =:= dgram) orelse (T =:= raw) ->
+ ?SOCKET_OPT_IPV6_HOPLIMIT;
+enc_sockopt_key(ipv6 = _L, hopopts = _Opt, _Dir, _D, T, _P)
+ when ((T =:= dgram) orelse (T =:= raw)) ->
+ ?SOCKET_OPT_IPV6_HOPOPTS;
+enc_sockopt_key(ipv6 = L, ipcomp_level = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(ipv6 = L, join_group = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(ipv6 = L, leave_group = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(ipv6 = _L, mtu = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IPV6_MTU;
+enc_sockopt_key(ipv6 = _L, mtu_discover = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IPV6_MTU_DISCOVER;
+enc_sockopt_key(ipv6 = _L, multicast_hops = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IPV6_MULTICAST_HOPS;
+enc_sockopt_key(ipv6 = _L, multicast_if = _Opt, _Dir, _D, T, _P)
+ when (T =:= dgram) orelse (T =:= raw) ->
+ ?SOCKET_OPT_IPV6_MULTICAST_IF;
+enc_sockopt_key(ipv6 = _L, multicast_loop = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IPV6_MULTICAST_LOOP;
+enc_sockopt_key(ipv6 = L, portrange = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(ipv6 = L, pktoptions = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(ipv6 = _L, recverr = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IPV6_RECVERR;
+enc_sockopt_key(ipv6 = _L, Opt, _Dir, _D, T, _P)
+ when ((Opt =:= recvpktinfo) orelse (Opt =:= pktinfo)) andalso
+ ((T =:= dgram) orelse (T =:= raw)) ->
+ ?SOCKET_OPT_IPV6_RECVPKTINFO;
+enc_sockopt_key(ipv6 = L, recvtclass = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(ipv6 = _L, router_alert = _Opt, _Dir, _D, T, _P) when (T =:= raw) ->
+ ?SOCKET_OPT_IPV6_ROUTER_ALERT;
+enc_sockopt_key(ipv6 = _L, rthdr = _Opt, _Dir, _D, T, _P)
+ when ((T =:= dgram) orelse (T =:= raw)) ->
+ ?SOCKET_OPT_IPV6_RTHDR;
+enc_sockopt_key(ipv6 = L, tclass = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(ipv6 = _L, unicast_hops = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IPV6_UNICAST_HOPS;
+enc_sockopt_key(ipv6 = L, use_min_mtu = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(ipv6 = _L, v6only = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IPV6_V6ONLY;
+enc_sockopt_key(ipv6 = L, UnknownOpt, _Dir, _D, _T, _P) ->
+ unknown({L, UnknownOpt});
+
+%% TCP socket options
+%% There are other options that would be useful; info,
+%% but they are difficult to get portable...
+enc_sockopt_key(tcp, congestion = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_TCP_CONGESTION;
+enc_sockopt_key(tcp = L, cork = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(tcp = L, keepidle = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(tcp = L, keepintvl = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(tcp = L, keepcnt = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(tcp, maxseg = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_TCP_MAXSEG;
+enc_sockopt_key(tcp = L, md5sig = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(tcp, nodelay = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_TCP_NODELAY;
+enc_sockopt_key(tcp = L, noopt = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(tcp = L, nopush = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(tcp = L, syncnt = Opt, _Dir, _D, _T, _P) -> % Only set? 1..255
+ not_supported({L, Opt});
+enc_sockopt_key(tcp = L, user_timeout = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(tcp = L, UnknownOpt, _Dir, _D, _T, _P) ->
+ unknown({L, UnknownOpt});
+
+%% UDP socket options
+enc_sockopt_key(udp, cork = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_UDP_CORK;
+enc_sockopt_key(udp = L, UnknownOpt, _Dir, _D, _T, _P) ->
+ unknown({L, UnknownOpt});
+
+%% SCTP socket options
+enc_sockopt_key(sctp = L, adaption_layer = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(sctp = _L, associnfo = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SCTP_ASSOCINFO;
+enc_sockopt_key(sctp = L, auth_active_key = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(sctp = L, auth_asconf = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(sctp = L, auth_chunk = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(sctp = L, auth_key = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(sctp = L, auth_delete_key = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(sctp, autoclose = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SCTP_AUTOCLOSE;
+enc_sockopt_key(sctp = L, context = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(sctp = L, default_send_params = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(sctp = L, delayed_ack_time = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(sctp = _L, disable_fragments = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SCTP_DISABLE_FRAGMENTS;
+enc_sockopt_key(sctp = L, hmac_ident = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(sctp = _L, events = _Opt, set = _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SCTP_EVENTS;
+enc_sockopt_key(sctp = L, explicit_eor = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(sctp = L, fragment_interleave = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(sctp = L, get_peer_addr_info = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(sctp = _L, initmsg = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SCTP_INITMSG;
+enc_sockopt_key(sctp = L, i_want_mapped_v4_addr = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(sctp = L, local_auth_chunks = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(sctp = _L, maxseg = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SCTP_MAXSEG;
+enc_sockopt_key(sctp = L, maxburst = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(sctp, nodelay = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SCTP_NODELAY;
+enc_sockopt_key(sctp = L, partial_delivery_point = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(sctp = L, peer_addr_params = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(sctp = L, peer_auth_chunks = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(sctp = L, primary_addr = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(sctp = L, reset_streams = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(sctp = _L, rtoinfo = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SCTP_RTOINFO;
+enc_sockopt_key(sctp = L, set_peer_primary_addr = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(sctp = L, status = Opt, get = _Dir, _D, _T, _P) ->
+ not_supported({L, Opt}); % ?SOCKET_OPT_SCTP_RTOINFO;
+enc_sockopt_key(sctp = L, use_exp_recvinfo = Opt, _Dir, _D, _T, _P) ->
+ not_supported({L, Opt});
+enc_sockopt_key(sctp = L, UnknownOpt, _Dir, _D, _T, _P) ->
+ unknown({L, UnknownOpt});
+
+%% +++ "Native" socket options +++
+enc_sockopt_key(Level, Opt, set = _Dir, _D, _T, _P)
+ when is_integer(Level) andalso is_integer(Opt) ->
+ Opt;
+enc_sockopt_key(Level, {NativeOpt, ValueSize} = Opt, get = _Dir, _D, _T, _P)
+ when is_integer(Level) andalso
+ is_integer(NativeOpt) andalso
+ ((is_integer(ValueSize) andalso (ValueSize >= 0)) orelse
+ ((ValueSize =:= int) orelse (ValueSize =:= bool))) ->
+ Opt;
+
+enc_sockopt_key(Level, Opt, _Dir, _Domain, _Type, _Protocol) ->
+ unknown({Level, Opt}).
+
+
+
+enc_shutdown_how(read) ->
+ ?SOCKET_SHUTDOWN_HOW_READ;
+enc_shutdown_how(write) ->
+ ?SOCKET_SHUTDOWN_HOW_WRITE;
+enc_shutdown_how(read_write) ->
+ ?SOCKET_SHUTDOWN_HOW_READ_WRITE.
+
+
+
+
+%% ===========================================================================
+%%
+%% Misc utility functions
+%%
+%% ===========================================================================
+
+-dialyzer({nowarn_function, ensure_ip_msfilter_slist/1}).
+ensure_ip_msfilter_slist(SL) ->
+ EnsureSA = fun(SA) when is_tuple(SA) andalso (size(SA) =:= 4) -> ok;
+ (_) -> einval()
+ end,
+ lists:foreach(EnsureSA, SL).
+
+
+ensure_sockaddr(#{family := inet} = SockAddr) ->
+ maps:merge(?SOCKADDR_IN4_DEFAULTS, SockAddr);
+ensure_sockaddr(#{family := inet6} = SockAddr) ->
+ maps:merge(?SOCKADDR_IN6_DEFAULTS, SockAddr);
+ensure_sockaddr(#{family := local, path := Path} = SockAddr)
+ when is_list(Path) andalso
+ (length(Path) > 0) andalso
+ (length(Path) =< 255) ->
+ BinPath = unicode:characters_to_binary(Path, file:native_name_encoding()),
+ ensure_sockaddr(SockAddr#{path => BinPath});
+ensure_sockaddr(#{family := local, path := Path} = SockAddr)
+ when is_binary(Path) andalso
+ (byte_size(Path) > 0) andalso
+ (byte_size(Path) =< 255) ->
+ SockAddr;
+ensure_sockaddr(_SockAddr) ->
+ einval().
+
+
+
+cancel(SockRef, Op, OpRef) ->
+ case nif_cancel(SockRef, Op, OpRef) of
+ %% The select has already completed
+ {error, select_sent} ->
+ flush_select_msgs(SockRef, OpRef);
+ Other ->
+ Other
+ end.
+
+flush_select_msgs(SockRef, Ref) ->
+ receive
+ {select, SockRef, Ref, _} ->
+ flush_select_msgs(SockRef, Ref)
+ after 0 ->
+ ok
+ end.
+
+
+%% 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).
+
+
+%% A timestamp in ms
+
+timestamp(infinity) ->
+ undefined;
+timestamp(_) ->
+ timestamp().
+
+timestamp() ->
+ {A,B,C} = os:timestamp(),
+ A*1000000000+B*1000+(C div 1000).
+
+next_timeout(_, infinity = Timeout) ->
+ Timeout;
+next_timeout(TS, Timeout) ->
+ NewTimeout = Timeout - tdiff(TS, timestamp()),
+ if
+ (NewTimeout > 0) ->
+ NewTimeout;
+ true ->
+ 0
+ end.
+
+tdiff(T1, T2) ->
+ T2 - T1.
+
+
+
+%% p(F) ->
+%% p(F, []).
+
+%% p(F, A) ->
+%% p(get(sname), F, A).
+
+%% p(undefined, F, A) ->
+%% p("***", F, A);
+%% p(SName, F, A) ->
+%% io:format(user,"[~s,~p] " ++ F ++ "~n", [SName, self()|A]),
+%% io:format("[~s,~p] " ++ F ++ "~n", [SName, self()|A]).
+
+
+
+%% ===========================================================================
+%%
+%% Error functions
+%%
+%% ===========================================================================
+
+-spec not_supported(What) -> no_return() when
+ What :: term().
+
+not_supported(What) ->
+ error({not_supported, What}).
+
+-spec unknown(What) -> no_return() when
+ What :: term().
+
+unknown(What) ->
+ error({unknown, What}).
+
+-spec einval() -> no_return().
+
+einval() ->
+ error(einval).
+
+-spec error(Reason) -> no_return() when
+ Reason :: term().
+
+error(Reason) ->
+ throw({error, Reason}).
+
+
+%% ===========================================================================
+%%
+%% Below follows the actual NIF-functions.
+%%
+%% ===========================================================================
+
+nif_info() ->
+ erlang:nif_error(undef).
+
+nif_supports(_Key) ->
+ erlang:nif_error(undef).
+
+nif_open(_Domain, _Type, _Protocol, _Extra) ->
+ erlang:nif_error(undef).
+
+nif_bind(_SRef, _SockAddr) ->
+ erlang:nif_error(undef).
+
+nif_bind(_SRef, _SockAddrs, _Action) ->
+ erlang:nif_error(undef).
+
+nif_connect(_SRef, _SockAddr) ->
+ erlang:nif_error(undef).
+
+nif_finalize_connection(_SRef) ->
+ erlang:nif_error(undef).
+
+nif_listen(_SRef, _Backlog) ->
+ erlang:nif_error(undef).
+
+nif_accept(_SRef, _Ref) ->
+ erlang:nif_error(undef).
+
+nif_send(_SockRef, _SendRef, _Data, _Flags) ->
+ erlang:nif_error(undef).
+
+nif_sendto(_SRef, _SendRef, _Data, _Dest, _Flags) ->
+ erlang:nif_error(undef).
+
+nif_sendmsg(_SRef, _SendRef, _MsgHdr, _Flags) ->
+ erlang:nif_error(undef).
+
+nif_recv(_SRef, _RecvRef, _Length, _Flags) ->
+ erlang:nif_error(undef).
+
+nif_recvfrom(_SRef, _RecvRef, _Length, _Flags) ->
+ erlang:nif_error(undef).
+
+nif_recvmsg(_SRef, _RecvRef, _BufSz, _CtrlSz, _Flags) ->
+ erlang:nif_error(undef).
+
+nif_cancel(_SRef, _Op, _Ref) ->
+ erlang:nif_error(undef).
+
+nif_close(_SRef) ->
+ erlang:nif_error(undef).
+
+nif_shutdown(_SRef, _How) ->
+ erlang:nif_error(undef).
+
+nif_finalize_close(_SRef) ->
+ erlang:nif_error(undef).
+
+nif_setopt(_Ref, _IsEnc, _Lev, _Key, _Val) ->
+ erlang:nif_error(undef).
+
+nif_getopt(_Ref, _IsEnc, _Lev, _Key) ->
+ erlang:nif_error(undef).
+
+nif_sockname(_Ref) ->
+ erlang:nif_error(undef).
+
+nif_peername(_Ref) ->
+ erlang:nif_error(undef).
+
diff --git a/erts/test/upgrade_SUITE.erl b/erts/test/upgrade_SUITE.erl
index c32dbabe8d..f92c25bdb4 100644
--- a/erts/test/upgrade_SUITE.erl
+++ b/erts/test/upgrade_SUITE.erl
@@ -25,7 +25,7 @@
-define(upgr_sname,otp_upgrade).
-%% Applications that are excluded from this test because they can not
+%% Applications that are excluded from this test because they cannot
%% just be started in a new node with out specific configuration.
-define(start_exclude,
[cosEvent,cosEventDomain,cosFileTransfer,cosNotification,
diff --git a/erts/vsn.mk b/erts/vsn.mk
index 9c912a422b..e4bdb1a8eb 100644
--- a/erts/vsn.mk
+++ b/erts/vsn.mk
@@ -18,7 +18,7 @@
# %CopyrightEnd%
#
-VSN = 10.2.3
+VSN = 10.2.4
# Port number 4365 in 4.2
# Port number 4366 in 4.3
diff --git a/lib/Makefile b/lib/Makefile
index cdb3f3f3dc..6605c6145c 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -28,7 +28,7 @@ ERTS_APPLICATIONS = stdlib sasl kernel compiler
ERLANG_APPLICATIONS = tools common_test runtime_tools inets parsetools
# These are only build if -a is given to otp_build or make is used directly
-ALL_ERLANG_APPLICATIONS = xmerl edoc erl_docgen snmp otp_mibs erl_interface \
+ALL_ERLANG_APPLICATIONS = xmerl edoc erl_docgen snmp erl_interface \
asn1 jinterface \
wx debugger reltool \
mnesia crypto os_mon syntax_tools \
diff --git a/lib/asn1/doc/src/asn1ct.xml b/lib/asn1/doc/src/asn1ct.xml
index ccf07a9cc1..e86dbd9f5e 100644
--- a/lib/asn1/doc/src/asn1ct.xml
+++ b/lib/asn1/doc/src/asn1ct.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>asn1.sgml</file>
</header>
- <module>asn1ct</module>
+ <module since="">asn1ct</module>
<modulesummary>ASN.1 compiler and compile-time support functions</modulesummary>
<description>
<p>The ASN.1 compiler takes an ASN.1 module as input and generates a
@@ -72,8 +72,8 @@
<funcs>
<func>
- <name>compile(Asn1module) -> ok | {error, Reason}</name>
- <name>compile(Asn1module, Options) -> ok | {error, Reason}</name>
+ <name since="">compile(Asn1module) -> ok | {error, Reason}</name>
+ <name since="">compile(Asn1module, Options) -> ok | {error, Reason}</name>
<fsummary>Compiles an ASN.1 module and generates encode/decode functions according to encoding rules BER or PER.</fsummary>
<type>
<v>Asn1module = atom() | string()</v>
@@ -336,7 +336,7 @@ File3.asn</pre>
</func>
<func>
- <name>value(Module, Type) -> {ok, Value} | {error, Reason}</name>
+ <name since="">value(Module, Type) -> {ok, Value} | {error, Reason}</name>
<fsummary>Creates an ASN.1 value for test purposes.</fsummary>
<type>
<v>Module = Type = atom()</v>
@@ -361,9 +361,9 @@ File3.asn</pre>
</func>
<func>
- <name>test(Module) -> ok | {error, Reason}</name>
- <name>test(Module, Type | Options) -> ok | {error, Reason}</name>
- <name>test(Module, Type, Value | Options) -> ok | {error, Reason}</name>
+ <name since="">test(Module) -> ok | {error, Reason}</name>
+ <name since="">test(Module, Type | Options) -> ok | {error, Reason}</name>
+ <name since="">test(Module, Type, Value | Options) -> ok | {error, Reason}</name>
<fsummary>Performs a test of encode and decode for types in an ASN.1 module.</fsummary>
<type>
<v>Module = Type = atom()</v>
diff --git a/lib/asn1/src/asn1ct_check.erl b/lib/asn1/src/asn1ct_check.erl
index 321980e5e4..9ec0d93e93 100644
--- a/lib/asn1/src/asn1ct_check.erl
+++ b/lib/asn1/src/asn1ct_check.erl
@@ -5770,7 +5770,7 @@ format_error({missing_ocft,Component}) ->
format_error(multiple_uniqs) ->
"implementation limitation: only one UNIQUE field is allowed in CLASS";
format_error({namelist_redefinition,Name}) ->
- io_lib:format("the name '~s' can not be redefined", [Name]);
+ io_lib:format("the name '~s' cannot be redefined", [Name]);
format_error({param_bad_type, Ref}) ->
io_lib:format("'~p' is not a parameterized type", [Ref]);
format_error(param_wrong_number_of_arguments) ->
diff --git a/lib/asn1/test/asn1_SUITE_data/IN-CS-1-Datatypes.asn b/lib/asn1/test/asn1_SUITE_data/IN-CS-1-Datatypes.asn
index ff0361f5c5..fb092f3f9c 100644
--- a/lib/asn1/test/asn1_SUITE_data/IN-CS-1-Datatypes.asn
+++ b/lib/asn1/test/asn1_SUITE_data/IN-CS-1-Datatypes.asn
@@ -1152,7 +1152,7 @@ FilteringCriteria ::= CHOICE {
-- In case calledAddressValue is specified, the numbers to be filtered are from calledAddressValue
-- up to and including calledAddressValue + maximumNumberOfCounters-1.
--- The last two digits of calledAddressvalue can not exceed 100-maximumNumberOfCounters.
+-- The last two digits of calledAddressvalue cannot exceed 100-maximumNumberOfCounters.
FilteringTimeOut ::= CHOICE {
duration [0] Duration,
stopTime [1] DateAndTime
diff --git a/lib/common_test/doc/src/basics_chapter.xml b/lib/common_test/doc/src/basics_chapter.xml
index 95599ca1f1..899a52fa31 100644
--- a/lib/common_test/doc/src/basics_chapter.xml
+++ b/lib/common_test/doc/src/basics_chapter.xml
@@ -125,7 +125,7 @@
The test case is the smallest unit that the <c>Common Test</c> test server deals with.
</p>
<p>
- Subsets of test cases, called test case groups, can also be defined. A test case
+ Sets of test cases, called test case groups, can also be defined. A test case
group can have execution properties associated with it. Execution properties
specify if the test cases in the group are to be executed in
random order, in parallel, or in sequence, and if the execution of the group
diff --git a/lib/common_test/doc/src/common_test_app.xml b/lib/common_test/doc/src/common_test_app.xml
index a3b3f927eb..7887a2c3ea 100644
--- a/lib/common_test/doc/src/common_test_app.xml
+++ b/lib/common_test/doc/src/common_test_app.xml
@@ -32,7 +32,7 @@
<rev>PA1</rev>
<file>common_test_app.sgml</file>
</header>
- <module>common_test</module>
+ <module since="">common_test</module>
<modulesummary>A framework for automated testing of any target nodes.</modulesummary>
<description>
@@ -68,7 +68,7 @@
<funcs>
<func>
- <name>Module:all() -> Tests | {skip,Reason} </name>
+ <name since="">Module:all() -> Tests | {skip,Reason} </name>
<fsummary>Returns the list of all test case groups and test cases
in the module.</fsummary>
<type>
@@ -115,7 +115,7 @@
</func>
<func>
- <name>Module:groups() -> GroupDefs</name>
+ <name since="">Module:groups() -> GroupDefs</name>
<fsummary>Returns a list of test case group definitions.</fsummary>
<type>
<v>GroupDefs = [Group]</v>
@@ -140,7 +140,7 @@
</func>
<func>
- <name>Module:suite() -> [Info] </name>
+ <name since="">Module:suite() -> [Info] </name>
<fsummary>Test suite info function (providing default data
for the suite).</fsummary>
<type>
@@ -213,7 +213,7 @@
</func>
<func>
- <name>Module:init_per_suite(Config) -> NewConfig | {skip,Reason} |
+ <name since="">Module:init_per_suite(Config) -> NewConfig | {skip,Reason} |
{skip_and_save,Reason,SaveConfig}</name>
<fsummary>Test suite initializations.</fsummary>
<type>
@@ -248,7 +248,7 @@
</func>
<func>
- <name>Module:end_per_suite(Config) -> term() |
+ <name since="">Module:end_per_suite(Config) -> term() |
{save_config,SaveConfig}</name>
<fsummary>Test suite finalization.</fsummary>
<type>
@@ -272,7 +272,7 @@
</func>
<func>
- <name>Module:group(GroupName) -> [Info] </name>
+ <name since="OTP R15B">Module:group(GroupName) -> [Info] </name>
<fsummary>Test case group information function (providing default data
for a test case group, that is, its test cases and
subgroups).</fsummary>
@@ -352,7 +352,7 @@
</func>
<func>
- <name>Module:init_per_group(GroupName, Config) -> NewConfig |
+ <name since="">Module:init_per_group(GroupName, Config) -> NewConfig |
{skip,Reason}</name>
<fsummary>Test case group initializations.</fsummary>
<type>
@@ -390,7 +390,7 @@
</func>
<func>
- <name>Module:end_per_group(GroupName, Config) -> term() |
+ <name since="">Module:end_per_group(GroupName, Config) -> term() |
{return_group_result,Status}</name>
<fsummary>Test case group finalization.</fsummary>
<type>
@@ -424,7 +424,7 @@
</func>
<func>
- <name>Module:init_per_testcase(TestCase, Config) -> NewConfig | {fail,Reason} | {skip,Reason}</name>
+ <name since="">Module:init_per_testcase(TestCase, Config) -> NewConfig | {fail,Reason} | {skip,Reason}</name>
<fsummary>Test case initializations.</fsummary>
<type>
<v> TestCase = atom()</v>
@@ -454,7 +454,7 @@
</func>
<func>
- <name>Module:end_per_testcase(TestCase, Config) -> term() | {fail,Reason} | {save_config,SaveConfig}</name>
+ <name since="">Module:end_per_testcase(TestCase, Config) -> term() | {fail,Reason} | {save_config,SaveConfig}</name>
<fsummary>Test case finalization.</fsummary>
<type>
<v>TestCase = atom()</v>
@@ -486,7 +486,7 @@
</func>
<func>
- <name>Module:Testcase() -> [Info] </name>
+ <name since="OTP R14B">Module:Testcase() -> [Info] </name>
<fsummary>Test case information function.</fsummary>
<type>
<v>Info = {timetrap,Time} | {require,Required} | {require,Name,Required} | {userdata,UserData} | {silent_connections,Conns}</v>
@@ -560,7 +560,7 @@
</func>
<func>
- <name>Module:Testcase(Config) -> term() | {skip,Reason} | {comment,Comment} | {save_config,SaveConfig} | {skip_and_save,Reason,SaveConfig} | exit() </name>
+ <name since="OTP R14B">Module:Testcase(Config) -> term() | {skip,Reason} | {comment,Comment} | {save_config,SaveConfig} | {skip_and_save,Reason,SaveConfig} | exit() </name>
<fsummary>A test case.</fsummary>
<type>
<v>Config = SaveConfig = [{Key,Value}]</v>
diff --git a/lib/common_test/doc/src/ct.xml b/lib/common_test/doc/src/ct.xml
index c0380c4142..83c0ecb309 100644
--- a/lib/common_test/doc/src/ct.xml
+++ b/lib/common_test/doc/src/ct.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>ct.xml</file>
</header>
- <module>ct</module>
+ <module since="">ct</module>
<modulesummary>Main user interface for the Common Test framework.</modulesummary>
<description>
@@ -139,7 +139,7 @@
<funcs>
<func>
- <name>abort_current_testcase(Reason) -&gt; ok | {error, ErrorReason}</name>
+ <name since="">abort_current_testcase(Reason) -&gt; ok | {error, ErrorReason}</name>
<fsummary>Aborts the currently executing test case.</fsummary>
<type>
<v>Reason = term()</v>
@@ -157,7 +157,7 @@
</func>
<func>
- <name>add_config(Callback, Config) -&gt; ok | {error, Reason}</name>
+ <name since="OTP R14B">add_config(Callback, Config) -&gt; ok | {error, Reason}</name>
<fsummary>Loads configuration variables using the specified callback
module and configuration string.</fsummary>
<type>
@@ -176,7 +176,7 @@
</func>
<func>
- <name>break(Comment) -&gt; ok | {error, Reason}</name>
+ <name since="OTP R15B02">break(Comment) -&gt; ok | {error, Reason}</name>
<fsummary>Cancels any active timetrap and pause the execution of the
current test case until the user calls function continue/0.</fsummary>
<type>
@@ -206,7 +206,7 @@
</func>
<func>
- <name>break(TestCase, Comment) -&gt; ok | {error, Reason}</name>
+ <name since="OTP R15B02">break(TestCase, Comment) -&gt; ok | {error, Reason}</name>
<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>
@@ -228,7 +228,7 @@
</func>
<func>
- <name>capture_get() -&gt; ListOfStrings</name>
+ <name since="OTP R15B">capture_get() -&gt; ListOfStrings</name>
<fsummary>Equivalent to capture_get([default]).</fsummary>
<type>
<v>ListOfStrings = [string()]</v>
@@ -240,7 +240,7 @@
</func>
<func>
- <name>capture_get(ExclCategories) -&gt; ListOfStrings</name>
+ <name since="OTP R15B">capture_get(ExclCategories) -&gt; ListOfStrings</name>
<fsummary>Returns and purges the list of text strings buffered during
the latest session of capturing printouts to stdout.</fsummary>
<type>
@@ -262,7 +262,7 @@
</func>
<func>
- <name>capture_start() -&gt; ok</name>
+ <name since="OTP R15B">capture_start() -&gt; ok</name>
<fsummary>Starts capturing all text strings printed to stdout
during execution of the test case.</fsummary>
<desc><marker id="capture_start-0"/>
@@ -276,7 +276,7 @@
</func>
<func>
- <name>capture_stop() -&gt; ok</name>
+ <name since="OTP R15B">capture_stop() -&gt; ok</name>
<fsummary>Stops capturing text strings (a session started with
capture_start/0).</fsummary>
<desc><marker id="capture_stop-0"/>
@@ -290,7 +290,7 @@
</func>
<func>
- <name>comment(Comment) -&gt; ok</name>
+ <name since="">comment(Comment) -&gt; ok</name>
<fsummary>Prints the specified Comment in the comment field in the
table on the test suite result page.</fsummary>
<type>
@@ -307,7 +307,7 @@
</func>
<func>
- <name>comment(Format, Args) -&gt; ok</name>
+ <name since="OTP R15B">comment(Format, Args) -&gt; ok</name>
<fsummary>Prints the formatted string in the comment field in the
table on the test suite result page.</fsummary>
<type>
@@ -326,7 +326,7 @@
</func>
<func>
- <name>continue() -&gt; ok</name>
+ <name since="OTP R15B02">continue() -&gt; ok</name>
<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"/>
@@ -337,7 +337,7 @@
</func>
<func>
- <name>continue(TestCase) -&gt; ok</name>
+ <name since="OTP R15B02">continue(TestCase) -&gt; ok</name>
<fsummary>This function must be called to continue after a test case
has called break/2.</fsummary>
<type>
@@ -353,7 +353,7 @@
</func>
<func>
- <name>decrypt_config_file(EncryptFileName, TargetFileName) -&gt; ok | {error, Reason}</name>
+ <name since="">decrypt_config_file(EncryptFileName, TargetFileName) -&gt; ok | {error, Reason}</name>
<fsummary>Decrypts EncryptFileName, previously generated with
encrypt_config_file/2,3.</fsummary>
<type>
@@ -372,7 +372,7 @@
</func>
<func>
- <name>decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile) -&gt; ok | {error, Reason}</name>
+ <name since="">decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile) -&gt; ok | {error, Reason}</name>
<fsummary>Decrypts EncryptFileName, previously generated with
encrypt_config_file/2,3.</fsummary>
<type>
@@ -390,7 +390,7 @@
</func>
<func>
- <name>encrypt_config_file(SrcFileName, EncryptFileName) -&gt; ok | {error, Reason}</name>
+ <name since="">encrypt_config_file(SrcFileName, EncryptFileName) -&gt; ok | {error, Reason}</name>
<fsummary>Encrypts the source configuration file with DES3 and saves the
result in file EncryptFileName.</fsummary>
<type>
@@ -416,7 +416,7 @@
</func>
<func>
- <name>encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile) -&gt; ok | {error, Reason}</name>
+ <name since="">encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile) -&gt; ok | {error, Reason}</name>
<fsummary>Encrypts the source configuration file with DES3 and saves the
result in the target file EncryptFileName.</fsummary>
<type>
@@ -442,7 +442,7 @@
</func>
<func>
- <name>fail(Reason) -&gt; ok</name>
+ <name since="">fail(Reason) -&gt; ok</name>
<fsummary>Terminates a test case with the specified error
Reason.</fsummary>
<type>
@@ -454,7 +454,7 @@
</func>
<func>
- <name>fail(Format, Args) -&gt; ok</name>
+ <name since="OTP R15B">fail(Format, Args) -&gt; ok</name>
<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>
@@ -470,7 +470,7 @@
</func>
<func>
- <name>get_config(Required) -&gt; Value</name>
+ <name since="">get_config(Required) -&gt; Value</name>
<fsummary>Equivalent to get_config(Required, undefined, []).</fsummary>
<desc><marker id="get_config-1"/>
<p>Equivalent to <seealso marker="#get_config-3"><c>ct:get_config(Required,
@@ -479,7 +479,7 @@
</func>
<func>
- <name>get_config(Required, Default) -&gt; Value</name>
+ <name since="">get_config(Required, Default) -&gt; Value</name>
<fsummary>Equivalent to get_config(Required, Default, []).</fsummary>
<desc><marker id="get_config-2"/>
<p>Equivalent to <seealso marker="#get_config-3"><c>ct:get_config(Required,
@@ -488,7 +488,7 @@
</func>
<func>
- <name>get_config(Required, Default, Opts) -&gt; ValueOrElement</name>
+ <name since="">get_config(Required, Default, Opts) -&gt; ValueOrElement</name>
<fsummary>Reads configuration data values.</fsummary>
<type>
<v>Required = KeyOrName | {KeyOrName, SubKey} | {KeyOrName, SubKey, SubKey}</v>
@@ -554,7 +554,7 @@
</func>
<func>
- <name>get_event_mgr_ref() -&gt; EvMgrRef</name>
+ <name since="OTP 17.5">get_event_mgr_ref() -&gt; EvMgrRef</name>
<fsummary>Gets a reference to the <c>Common Test</c> event manager.</fsummary>
<type>
<v>EvMgrRef = atom()</v>
@@ -572,7 +572,7 @@
</func>
<func>
- <name>get_progname() -&gt; string()</name>
+ <name since="OTP 21.0">get_progname() -&gt; string()</name>
<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.
@@ -582,7 +582,7 @@
</func>
<func>
- <name>get_status() -&gt; TestStatus | {error, Reason} | no_tests_running</name>
+ <name since="">get_status() -&gt; TestStatus | {error, Reason} | no_tests_running</name>
<fsummary>Returns status of ongoing test.</fsummary>
<type>
<v>TestStatus = [StatusElem]</v>
@@ -608,7 +608,7 @@
</func>
<func>
- <name>get_target_name(Handle) -&gt; {ok, TargetName} | {error, Reason}</name>
+ <name since="">get_target_name(Handle) -&gt; {ok, TargetName} | {error, Reason}</name>
<fsummary>Returns the name of the target that the specified connection
belongs to.</fsummary>
<type>
@@ -622,7 +622,7 @@
</func>
<func>
- <name>get_testspec_terms() -&gt; TestSpecTerms | undefined</name>
+ <name since="OTP 18.0">get_testspec_terms() -&gt; TestSpecTerms | undefined</name>
<fsummary>Gets a list of all test specification terms used to
configure and run this test.</fsummary>
<type>
@@ -636,7 +636,7 @@
</func>
<func>
- <name>get_testspec_terms(Tags) -&gt; TestSpecTerms | undefined</name>
+ <name since="OTP 18.0">get_testspec_terms(Tags) -&gt; TestSpecTerms | undefined</name>
<fsummary>Reads one or more terms from the test specification used to
configure and run this test.</fsummary>
<type>
@@ -663,7 +663,7 @@
</func>
<func>
- <name>get_timetrap_info() -&gt; {Time, {Scaling,ScaleVal}}</name>
+ <name since="OTP R15B">get_timetrap_info() -&gt; {Time, {Scaling,ScaleVal}}</name>
<fsummary>Reads information about the timetrap set for the current
test case.</fsummary>
<type>
@@ -682,7 +682,7 @@
</func>
<func>
- <name>get_verbosity(Category) -&gt; Level | undefined</name>
+ <name since="OTP 19.1">get_verbosity(Category) -&gt; Level | undefined</name>
<fsummary>Read the verbosity level for a logging category.</fsummary>
<type>
<v>Category = default | atom()</v>
@@ -697,7 +697,7 @@
</func>
<func>
- <name>install(Opts) -&gt; ok | {error, Reason}</name>
+ <name since="">install(Opts) -&gt; ok | {error, Reason}</name>
<fsummary>Installs configuration files and event handlers.</fsummary>
<type>
<v>Opts = [Opt]</v>
@@ -724,7 +724,7 @@
</func>
<func>
- <name>listenv(Telnet) -&gt; [Env]</name>
+ <name since="">listenv(Telnet) -&gt; [Env]</name>
<fsummary>Performs command listenv on the specified Telnet connection
and returns the result as a list of key-value pairs.</fsummary>
<type>
@@ -740,7 +740,7 @@
</func>
<func>
- <name>log(Format) -&gt; ok</name>
+ <name since="">log(Format) -&gt; ok</name>
<fsummary>Equivalent to log(default, 50, Format, [], []).</fsummary>
<desc><marker id="log-1"/>
<p>Equivalent to
@@ -749,7 +749,7 @@
</func>
<func>
- <name>log(X1, X2) -&gt; ok</name>
+ <name since="">log(X1, X2) -&gt; ok</name>
<fsummary>Equivalent to log(Category, Importance, Format,
FormatArgs, []).</fsummary>
<type>
@@ -763,7 +763,7 @@
</func>
<func>
- <name>log(X1, X2, X3) -&gt; ok</name>
+ <name since="">log(X1, X2, X3) -&gt; ok</name>
<fsummary>Equivalent to log(Category, Importance, Format,
FormatArgs, Opts).</fsummary>
<type>
@@ -778,7 +778,7 @@
</func>
<func>
- <name>log(X1, X2, X3, X4) -&gt; ok</name>
+ <name since="OTP R15B02">log(X1, X2, X3, X4) -&gt; ok</name>
<fsummary>Equivalent to log(Category, Importance, Format,
FormatArgs, Opts).</fsummary>
<type>
@@ -794,7 +794,7 @@
</func>
<func>
- <name>log(Category, Importance, Format, FormatArgs, Opts) -&gt; ok</name>
+ <name since="OTP 18.3">log(Category, Importance, Format, FormatArgs, Opts) -&gt; ok</name>
<fsummary>Prints from a test case to the log file.</fsummary>
<type>
<v>Category = atom()</v>
@@ -825,7 +825,7 @@
</func>
<func>
- <name>make_priv_dir() -&gt; ok | {error, Reason}</name>
+ <name since="OTP R15B01">make_priv_dir() -&gt; ok | {error, Reason}</name>
<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>
@@ -841,7 +841,7 @@
</func>
<func>
- <name>notify(Name, Data) -&gt; ok</name>
+ <name since="OTP R15B02">notify(Name, Data) -&gt; ok</name>
<fsummary>Sends an asynchronous notification of type Name with Data
to the <c>Common Test</c> event manager.</fsummary>
<type>
@@ -859,7 +859,7 @@
</func>
<func>
- <name>pal(Format) -&gt; ok</name>
+ <name since="">pal(Format) -&gt; ok</name>
<fsummary>Equivalent to pal(default, 50, Format, [], []).</fsummary>
<desc><marker id="pal-1"/>
<p>Equivalent to
@@ -869,7 +869,7 @@
</func>
<func>
- <name>pal(X1, X2) -&gt; ok</name>
+ <name since="">pal(X1, X2) -&gt; ok</name>
<fsummary>Equivalent to pal(Category, Importance, Format,
FormatArgs, []).</fsummary>
<type>
@@ -883,7 +883,7 @@
</func>
<func>
- <name>pal(X1, X2, X3) -&gt; ok</name>
+ <name since="">pal(X1, X2, X3) -&gt; ok</name>
<fsummary>Equivalent to pal(Category, Importance, Format,
FormatArgs, Opts).</fsummary>
<type>
@@ -898,7 +898,7 @@
</func>
<func>
- <name>pal(X1, X2, X3, X4) -&gt; ok</name>
+ <name since="OTP R15B02">pal(X1, X2, X3, X4) -&gt; ok</name>
<fsummary>Equivalent to pal(Category, Importance, Format,
FormatArgs, Opts).</fsummary>
<type>
@@ -914,7 +914,7 @@
</func>
<func>
- <name>pal(Category, Importance, Format, FormatArgs, Opts) -&gt; ok</name>
+ <name since="OTP 19.2">pal(Category, Importance, Format, FormatArgs, Opts) -&gt; ok</name>
<fsummary>Prints and logs from a test case.</fsummary>
<type>
<v>Category = atom()</v>
@@ -945,7 +945,7 @@
</func>
<func>
- <name>parse_table(Data) -&gt; {Heading, Table}</name>
+ <name since="">parse_table(Data) -&gt; {Heading, Table}</name>
<fsummary>Parses the printout from an SQL table and returns a list of
tuples.</fsummary>
<type>
@@ -967,7 +967,7 @@
</func>
<func>
- <name>print(Format) -&gt; ok</name>
+ <name since="">print(Format) -&gt; ok</name>
<fsummary>Equivalent to print(default, 50, Format, [], []).</fsummary>
<desc><marker id="print-1"/>
<p>Equivalent to <seealso marker="#print-5"><c>ct:print(default,
@@ -976,7 +976,7 @@
</func>
<func>
- <name>print(X1, X2) -&gt; ok</name>
+ <name since="OTP R15B02">print(X1, X2) -&gt; ok</name>
<fsummary>Equivalent to print(Category, Importance, Format,
FormatArgs, []).</fsummary>
<type>
@@ -990,7 +990,7 @@
</func>
<func>
- <name>print(X1, X2, X3) -&gt; ok</name>
+ <name since="">print(X1, X2, X3) -&gt; ok</name>
<fsummary>Equivalent to print(Category, Importance, Format,
FormatArgs, Opts).</fsummary>
<type>
@@ -1005,7 +1005,7 @@
</func>
<func>
- <name>print(X1, X2, X3, X4) -&gt; ok</name>
+ <name since="OTP R15B02">print(X1, X2, X3, X4) -&gt; ok</name>
<fsummary>Equivalent to print(Category, Importance, Format,
FormatArgs, Opts).</fsummary>
<type>
@@ -1021,7 +1021,7 @@
</func>
<func>
- <name>print(Category, Importance, Format, FormatArgs, Opts) -&gt; ok</name>
+ <name since="OTP 19.2">print(Category, Importance, Format, FormatArgs, Opts) -&gt; ok</name>
<fsummary>Prints from a test case to the console.</fsummary>
<type>
<v>Category = atom()</v>
@@ -1048,7 +1048,7 @@
</func>
<func>
- <name>reload_config(Required) -&gt; ValueOrElement | {error, Reason}</name>
+ <name since="OTP R14B">reload_config(Required) -&gt; ValueOrElement | {error, Reason}</name>
<fsummary>Reloads configuration file containing specified configuration
key.</fsummary>
<type>
@@ -1071,7 +1071,7 @@
</func>
<func>
- <name>remaining_test_procs() -&gt; {TestProcs,SharedGL,OtherGLs}</name>
+ <name since="OTP 20.2">remaining_test_procs() -&gt; {TestProcs,SharedGL,OtherGLs}</name>
<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>
@@ -1107,7 +1107,7 @@
</func>
<func>
- <name>remove_config(Callback, Config) -&gt; ok</name>
+ <name since="OTP R14B">remove_config(Callback, Config) -&gt; ok</name>
<fsummary>Removes configuration variables (together with
their aliases) that were loaded with specified callback module and
configuration string.</fsummary>
@@ -1124,7 +1124,7 @@
</func>
<func>
- <name>require(Required) -&gt; ok | {error, Reason}</name>
+ <name since="">require(Required) -&gt; ok | {error, Reason}</name>
<fsummary>Checks if the required configuration is available.</fsummary>
<type>
<v>Required = Key | {Key, SubKeys} | {Key, SubKey, SubKeys}</v>
@@ -1178,7 +1178,7 @@
</func>
<func>
- <name>require(Name, Required) -&gt; ok | {error, Reason}</name>
+ <name since="">require(Name, Required) -&gt; ok | {error, Reason}</name>
<fsummary>Checks if the required configuration is available and gives
it a name.</fsummary>
<type>
@@ -1237,7 +1237,7 @@
</func>
<func>
- <name>run(TestDirs) -&gt; Result</name>
+ <name since="">run(TestDirs) -&gt; Result</name>
<fsummary>Runs all test cases in all suites in the specified
directories.</fsummary>
<type>
@@ -1251,7 +1251,7 @@
</func>
<func>
- <name>run(TestDir, Suite) -&gt; Result</name>
+ <name since="">run(TestDir, Suite) -&gt; Result</name>
<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>
@@ -1261,7 +1261,7 @@
</func>
<func>
- <name>run(TestDir, Suite, Cases) -&gt; Result</name>
+ <name since="">run(TestDir, Suite, Cases) -&gt; Result</name>
<fsummary>Runs the specified test cases.</fsummary>
<type>
<v>TestDir = string()</v>
@@ -1283,7 +1283,7 @@
</func>
<func>
- <name>run_test(Opts) -&gt; Result</name>
+ <name since="">run_test(Opts) -&gt; Result</name>
<fsummary>Runs tests as specified by the combination of options in
Opts.</fsummary>
<type>
@@ -1355,7 +1355,7 @@
</func>
<func>
- <name>run_testspec(TestSpec) -&gt; Result</name>
+ <name since="">run_testspec(TestSpec) -&gt; Result</name>
<fsummary>Runs a test specified by TestSpec.</fsummary>
<type>
<v>TestSpec = [term()]</v>
@@ -1375,7 +1375,7 @@
</func>
<func>
- <name>set_verbosity(Category, Level) -&gt; ok</name>
+ <name since="OTP 19.1">set_verbosity(Category, Level) -&gt; ok</name>
<fsummary>Set the verbosity level for a logging category.</fsummary>
<type>
<v>Category = default | atom()</v>
@@ -1390,7 +1390,7 @@
</func>
<func>
- <name>sleep(Time) -&gt; ok</name>
+ <name since="OTP R14B">sleep(Time) -&gt; ok</name>
<fsummary>This function, similar to timer:sleep/1, suspends the
test case for a specified time.</fsummary>
<type>
@@ -1412,7 +1412,7 @@
</func>
<func>
- <name>start_interactive() -&gt; ok</name>
+ <name since="">start_interactive() -&gt; ok</name>
<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>
@@ -1440,7 +1440,7 @@
</func>
<func>
- <name>step(TestDir, Suite, Case) -&gt; Result</name>
+ <name since="">step(TestDir, Suite, Case) -&gt; Result</name>
<fsummary>Steps through a test case with the debugger.</fsummary>
<type>
<v>Case = atom()</v>
@@ -1453,7 +1453,7 @@
</func>
<func>
- <name>step(TestDir, Suite, Case, Opts) -&gt; Result</name>
+ <name since="">step(TestDir, Suite, Case, Opts) -&gt; Result</name>
<fsummary>Steps through a test case with the debugger.</fsummary>
<type>
<v>Case = atom()</v>
@@ -1470,7 +1470,7 @@
</func>
<func>
- <name>stop_interactive() -&gt; ok</name>
+ <name since="">stop_interactive() -&gt; ok</name>
<fsummary>Exits the interactive mode.</fsummary>
<desc><marker id="stop_interactive-0"/>
<p>Exits the interactive mode.</p>
@@ -1482,7 +1482,7 @@
</func>
<func>
- <name>sync_notify(Name, Data) -&gt; ok</name>
+ <name since="OTP R15B02">sync_notify(Name, Data) -&gt; ok</name>
<fsummary>Sends a synchronous notification of type Name with Data to
the <c>Common Test</c> event manager.</fsummary>
<type>
@@ -1501,7 +1501,7 @@
</func>
<func>
- <name>testcases(TestDir, Suite) -&gt; Testcases | {error, Reason}</name>
+ <name since="">testcases(TestDir, Suite) -&gt; Testcases | {error, Reason}</name>
<fsummary>Returns all test cases in the specified suite.</fsummary>
<type>
<v>TestDir = string()</v>
@@ -1515,7 +1515,7 @@
</func>
<func>
- <name>timetrap(Time) -&gt; ok</name>
+ <name since="OTP R14B">timetrap(Time) -&gt; ok</name>
<fsummary>Sets a new timetrap for the running test case.</fsummary>
<type>
<v>Time = {hours, Hours} | {minutes, Mins} | {seconds, Secs} | Millisecs | infinity | Func</v>
@@ -1539,7 +1539,7 @@
</func>
<func>
- <name>userdata(TestDir, Suite) -&gt; SuiteUserData | {error, Reason}</name>
+ <name since="">userdata(TestDir, Suite) -&gt; SuiteUserData | {error, Reason}</name>
<fsummary>Returns any data specified with tag userdata in the list of
tuples returned from Suite:suite/0.</fsummary>
<type>
@@ -1556,7 +1556,7 @@
</func>
<func>
- <name>userdata(TestDir, Suite, Case::GroupOrCase) -&gt; TCUserData | {error, Reason}</name>
+ <name since="">userdata(TestDir, Suite, Case::GroupOrCase) -&gt; TCUserData | {error, Reason}</name>
<fsummary>Returns any data specified with tag userdata in the list of
tuples returned from Suite:group(GroupName) or Suite:Case().</fsummary>
<type>
diff --git a/lib/common_test/doc/src/ct_cover.xml b/lib/common_test/doc/src/ct_cover.xml
index 89d944acbe..61365d3522 100644
--- a/lib/common_test/doc/src/ct_cover.xml
+++ b/lib/common_test/doc/src/ct_cover.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>ct_cover.xml</file>
</header>
- <module>ct_cover</module>
+ <module since="">ct_cover</module>
<modulesummary>Common Test framework code coverage support module.
</modulesummary>
@@ -47,7 +47,7 @@
<funcs>
<func>
- <name>add_nodes(Nodes) -&gt; {ok, StartedNodes} | {error, Reason}</name>
+ <name since="">add_nodes(Nodes) -&gt; {ok, StartedNodes} | {error, Reason}</name>
<fsummary>Adds nodes to current cover test (only works if cover support
is active).</fsummary>
<type>
@@ -67,7 +67,7 @@
</func>
<func>
- <name>cross_cover_analyse(Level, Tests) -&gt; ok</name>
+ <name since="OTP R16B">cross_cover_analyse(Level, Tests) -&gt; ok</name>
<fsummary>Accumulates cover results over multiple tests.</fsummary>
<type>
<v>Level = overview | details</v>
@@ -83,7 +83,7 @@
</func>
<func>
- <name>remove_nodes(Nodes) -&gt; ok | {error, Reason}</name>
+ <name since="">remove_nodes(Nodes) -&gt; ok | {error, Reason}</name>
<fsummary>Removes nodes from the current cover test.</fsummary>
<type>
<v>Nodes = [atom()]</v>
diff --git a/lib/common_test/doc/src/ct_ftp.xml b/lib/common_test/doc/src/ct_ftp.xml
index 592c5eb05d..7ee6049486 100644
--- a/lib/common_test/doc/src/ct_ftp.xml
+++ b/lib/common_test/doc/src/ct_ftp.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>ct_ftp.xml</file>
</header>
- <module>ct_ftp</module>
+ <module since="">ct_ftp</module>
<modulesummary>FTP client module (based on the FTP application).</modulesummary>
<description>
@@ -59,7 +59,7 @@
<funcs>
<func>
- <name>cd(Connection, Dir) -&gt; ok | {error, Reason}</name>
+ <name since="">cd(Connection, Dir) -&gt; ok | {error, Reason}</name>
<fsummary>Changes directory on remote host.</fsummary>
<type>
<v>Connection = connection()</v>
@@ -71,7 +71,7 @@
</func>
<func>
- <name>close(Connection) -&gt; ok | {error, Reason}</name>
+ <name since="">close(Connection) -&gt; ok | {error, Reason}</name>
<fsummary>Closes the FTP connection.</fsummary>
<type>
<v>Connection = connection()</v>
@@ -82,7 +82,7 @@
</func>
<func>
- <name>delete(Connection, File) -&gt; ok | {error, Reason}</name>
+ <name since="">delete(Connection, File) -&gt; ok | {error, Reason}</name>
<fsummary>Deletes a file on remote host.</fsummary>
<type>
<v>Connection = connection()</v>
@@ -94,7 +94,7 @@
</func>
<func>
- <name>get(KeyOrName, RemoteFile, LocalFile) -&gt; ok | {error, Reason}</name>
+ <name since="">get(KeyOrName, RemoteFile, LocalFile) -&gt; ok | {error, Reason}</name>
<fsummary>Opens an FTP connection and fetches a file from the remote
host.</fsummary>
<type>
@@ -122,7 +122,7 @@
</func>
<func>
- <name>ls(Connection, Dir) -&gt; {ok, Listing} | {error, Reason}</name>
+ <name since="">ls(Connection, Dir) -&gt; {ok, Listing} | {error, Reason}</name>
<fsummary>Lists directory Dir.</fsummary>
<type>
<v>Connection = connection()</v>
@@ -135,7 +135,7 @@
</func>
<func>
- <name>open(KeyOrName) -&gt; {ok, Handle} | {error, Reason}</name>
+ <name since="">open(KeyOrName) -&gt; {ok, Handle} | {error, Reason}</name>
<fsummary>Opens an FTP connection to the specified node.</fsummary>
<type>
<v>KeyOrName = Key | Name</v>
@@ -164,7 +164,7 @@
</func>
<func>
- <name>put(KeyOrName, LocalFile, RemoteFile) -&gt; ok | {error, Reason}</name>
+ <name since="">put(KeyOrName, LocalFile, RemoteFile) -&gt; ok | {error, Reason}</name>
<fsummary>Opens an FTP connection and sends a file to the remote
host.</fsummary>
<type>
@@ -203,7 +203,7 @@
</func>
<func>
- <name>recv(Connection, RemoteFile) -&gt; ok | {error, Reason}</name>
+ <name since="">recv(Connection, RemoteFile) -&gt; ok | {error, Reason}</name>
<fsummary>Fetches a file over FTP.</fsummary>
<desc><marker id="recv-2"/>
<p>Fetches a file over FTP.</p>
@@ -215,7 +215,7 @@
</func>
<func>
- <name>recv(Connection, RemoteFile, LocalFile) -&gt; ok | {error, Reason}</name>
+ <name since="">recv(Connection, RemoteFile, LocalFile) -&gt; ok | {error, Reason}</name>
<fsummary>Fetches a file over FTP.</fsummary>
<type>
<v>Connection = connection()</v>
@@ -230,7 +230,7 @@
</func>
<func>
- <name>send(Connection, LocalFile) -&gt; ok | {error, Reason}</name>
+ <name since="">send(Connection, LocalFile) -&gt; ok | {error, Reason}</name>
<fsummary>Sends a file over FTP.</fsummary>
<desc><marker id="send-2"/>
<p>Sends a file over FTP.</p>
@@ -243,7 +243,7 @@
</func>
<func>
- <name>send(Connection, LocalFile, RemoteFile) -&gt; ok | {error, Reason}</name>
+ <name since="">send(Connection, LocalFile, RemoteFile) -&gt; ok | {error, Reason}</name>
<fsummary>Sends a file over FTP.</fsummary>
<type>
<v>Connection = connection()</v>
@@ -258,7 +258,7 @@
</func>
<func>
- <name>type(Connection, Type) -&gt; ok | {error, Reason}</name>
+ <name since="">type(Connection, Type) -&gt; ok | {error, Reason}</name>
<fsummary>Changes the file transfer type.</fsummary>
<type>
<v>Connection = connection()</v>
diff --git a/lib/common_test/doc/src/ct_hooks.xml b/lib/common_test/doc/src/ct_hooks.xml
index 954be0ffba..ff0d0117cd 100644
--- a/lib/common_test/doc/src/ct_hooks.xml
+++ b/lib/common_test/doc/src/ct_hooks.xml
@@ -32,7 +32,7 @@
<rev>PA1</rev>
<file>ct_hooks.sgml</file>
</header>
- <module>ct_hooks</module>
+ <module since="OTP R14B02">ct_hooks</module>
<modulesummary>A callback interface on top of Common Test.</modulesummary>
<description>
@@ -75,7 +75,7 @@
<funcs>
<func>
- <name>Module:init(Id, Opts) -&gt; {ok, State} | {ok, State, Priority}</name>
+ <name since="OTP R14B02">Module:init(Id, Opts) -&gt; {ok, State} | {ok, State, Priority}</name>
<fsummary>Initiates the Common Test Hook.</fsummary>
<type>
<v>Id = reference() | term()</v>
@@ -109,7 +109,7 @@
</func>
<func>
- <name>Module:pre_init_per_suite(SuiteName, InitData, CTHState) -&gt; Result</name>
+ <name since="OTP R14B02">Module:pre_init_per_suite(SuiteName, InitData, CTHState) -&gt; Result</name>
<fsummary>Called before init_per_suite.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -161,7 +161,7 @@
</func>
<func>
- <name>Module:post_init_per_suite(SuiteName, Config, Return, CTHState) -&gt; Result</name>
+ <name since="OTP R14B02">Module:post_init_per_suite(SuiteName, Config, Return, CTHState) -&gt; Result</name>
<fsummary>Called after init_per_suite.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -208,7 +208,7 @@
</func>
<func>
- <name>Module:pre_init_per_group(SuiteName, GroupName, InitData, CTHState) -&gt; Result</name>
+ <name since="OTP 19.3">Module:pre_init_per_group(SuiteName, GroupName, InitData, CTHState) -&gt; Result</name>
<fsummary>Called before init_per_group.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -241,7 +241,7 @@
</func>
<func>
- <name>Module:post_init_per_group(SuiteName, GroupName, Config, Return, CTHState) -&gt; Result</name>
+ <name since="OTP 19.3">Module:post_init_per_group(SuiteName, GroupName, Config, Return, CTHState) -&gt; Result</name>
<fsummary>Called after init_per_group.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -274,7 +274,7 @@
</func>
<func>
- <name>Module:pre_init_per_testcase(SuiteName, TestcaseName, InitData, CTHState) -&gt; Result</name>
+ <name since="OTP 19.3">Module:pre_init_per_testcase(SuiteName, TestcaseName, InitData, CTHState) -&gt; Result</name>
<fsummary>Called before init_per_testcase.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -311,7 +311,7 @@
</func>
<func>
- <name>Module:post_init_per_testcase(SuiteName, TestcaseName, Config, Return, CTHState) -&gt; Result</name>
+ <name since="OTP 19.3">Module:post_init_per_testcase(SuiteName, TestcaseName, Config, Return, CTHState) -&gt; Result</name>
<fsummary>Called after init_per_testcase.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -344,7 +344,7 @@
</func>
<func>
- <name>Module:pre_end_per_testcase(SuiteName, TestcaseName, EndData, CTHState) -&gt; Result</name>
+ <name since="OTP 19.3">Module:pre_end_per_testcase(SuiteName, TestcaseName, EndData, CTHState) -&gt; Result</name>
<fsummary>Called before end_per_testcase.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -368,7 +368,7 @@
<seealso marker="common_test#Module:end_per_testcase-2"><c>end_per_testcase</c></seealso>
instead.</p>
- <p>This function can not change the result of the test case by returning skip or fail
+ <p>This function cannot change the result of the test case by returning skip or fail
tuples, but it may insert items in <c>Config</c> that can be read in
<c>end_per_testcase/2</c> or in <c>post_end_per_testcase/5</c>.</p>
@@ -380,7 +380,7 @@
</func>
<func>
- <name>Module:post_end_per_testcase(SuiteName, TestcaseName, Config, Return, CTHState) -&gt; Result</name>
+ <name since="OTP 19.3">Module:post_end_per_testcase(SuiteName, TestcaseName, Config, Return, CTHState) -&gt; Result</name>
<fsummary>Called after end_per_testcase.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -413,7 +413,7 @@
</func>
<func>
- <name>Module:pre_end_per_group(SuiteName, GroupName, EndData, CTHState) -&gt; Result</name>
+ <name since="OTP 19.3">Module:pre_end_per_group(SuiteName, GroupName, EndData, CTHState) -&gt; Result</name>
<fsummary>Called before end_per_group.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -446,7 +446,7 @@
</func>
<func>
- <name>Module:post_end_per_group(SuiteName, GroupName, Config, Return, CTHState) -&gt; Result</name>
+ <name since="OTP 19.3">Module:post_end_per_group(SuiteName, GroupName, Config, Return, CTHState) -&gt; Result</name>
<fsummary>Called after end_per_group.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -479,7 +479,7 @@
</func>
<func>
- <name>Module:pre_end_per_suite(SuiteName, EndData, CTHState) -&gt; Result</name>
+ <name since="OTP R14B02">Module:pre_end_per_suite(SuiteName, EndData, CTHState) -&gt; Result</name>
<fsummary>Called before end_per_suite.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -506,7 +506,7 @@
</func>
<func>
- <name>Module:post_end_per_suite(SuiteName, Config, Return, CTHState) -&gt; Result</name>
+ <name since="OTP R14B02">Module:post_end_per_suite(SuiteName, Config, Return, CTHState) -&gt; Result</name>
<fsummary>Called after end_per_suite.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -533,7 +533,7 @@
</func>
<func>
- <name>Module:on_tc_fail(SuiteName, TestName, Reason, CTHState) -&gt; NewCTHState</name>
+ <name since="OTP 19.3">Module:on_tc_fail(SuiteName, TestName, Reason, CTHState) -&gt; NewCTHState</name>
<fsummary>Called after the CTH scope ends.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -577,7 +577,7 @@
</func>
<func>
- <name>Module:on_tc_skip(SuiteName, TestName, Reason, CTHState) -&gt; NewCTHState</name>
+ <name since="OTP 19.3">Module:on_tc_skip(SuiteName, TestName, Reason, CTHState) -&gt; NewCTHState</name>
<fsummary>Called after the CTH scope ends.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -623,7 +623,7 @@
</func>
<func>
- <name>Module:terminate(CTHState)</name>
+ <name since="OTP R14B02">Module:terminate(CTHState)</name>
<fsummary>Called after the CTH scope ends.</fsummary>
<type>
<v>CTHState = term()</v>
@@ -637,7 +637,7 @@
</func>
<func>
- <name>Module:id(Opts) -&gt; Id</name>
+ <name since="OTP R14B02">Module:id(Opts) -&gt; Id</name>
<fsummary>Called before the init function of a CTH.</fsummary>
<type>
<v>Opts = term()</v>
diff --git a/lib/common_test/doc/src/ct_master.xml b/lib/common_test/doc/src/ct_master.xml
index 6bde4644c6..2ab421fe9e 100644
--- a/lib/common_test/doc/src/ct_master.xml
+++ b/lib/common_test/doc/src/ct_master.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>ct_master.xml</file>
</header>
- <module>ct_master</module>
+ <module since="">ct_master</module>
<modulesummary>Distributed test execution control for Common Test.</modulesummary>
<description>
@@ -46,7 +46,7 @@
<funcs>
<func>
- <name>abort() -&gt; ok</name>
+ <name since="">abort() -&gt; ok</name>
<fsummary>Stops all running tests.</fsummary>
<desc><marker id="abort-0"/>
<p>Stops all running tests.</p>
@@ -54,7 +54,7 @@
</func>
<func>
- <name>abort(Nodes) -&gt; ok</name>
+ <name since="">abort(Nodes) -&gt; ok</name>
<fsummary>Stops tests on specified nodes.</fsummary>
<type>
<v>Nodes = atom() | [atom()]</v>
@@ -65,7 +65,7 @@
</func>
<func>
- <name>basic_html(Bool) -&gt; ok</name>
+ <name since="OTP R15B01">basic_html(Bool) -&gt; ok</name>
<fsummary>If set to true, the ct_master logs are written on a primitive
HTML format, not using the <c>Common Test</c> CSS style sheet.</fsummary>
<type>
@@ -79,7 +79,7 @@
</func>
<func>
- <name>get_event_mgr_ref() -&gt; MasterEvMgrRef</name>
+ <name since="OTP 17.5">get_event_mgr_ref() -&gt; MasterEvMgrRef</name>
<fsummary>Gets a reference to the <c>Common Test</c> master event
manager.</fsummary>
<type>
@@ -98,7 +98,7 @@
</func>
<func>
- <name>progress() -&gt; [{Node, Status}]</name>
+ <name since="">progress() -&gt; [{Node, Status}]</name>
<fsummary>Returns test progress.</fsummary>
<type>
<v>Node = atom()</v>
@@ -112,7 +112,7 @@
</func>
<func>
- <name>run(TestSpecs) -&gt; ok</name>
+ <name since="">run(TestSpecs) -&gt; ok</name>
<fsummary>Equivalent to run(TestSpecs, false, [], []).</fsummary>
<type>
<v>TestSpecs = string() | [SeparateOrMerged]</v>
@@ -124,7 +124,7 @@
</func>
<func>
- <name>run(TestSpecs, InclNodes, ExclNodes) -&gt; ok</name>
+ <name since="">run(TestSpecs, InclNodes, ExclNodes) -&gt; ok</name>
<fsummary>Equivalent to run(TestSpecs, false, InclNodes, ExclNodes).
</fsummary>
<type>
@@ -140,7 +140,7 @@
</func>
<func>
- <name>run(TestSpecs, AllowUserTerms, InclNodes, ExclNodes) -&gt; ok</name>
+ <name since="">run(TestSpecs, AllowUserTerms, InclNodes, ExclNodes) -&gt; ok</name>
<fsummary>Tests are spawned on the nodes as specified in TestSpecs.
</fsummary>
<type>
@@ -162,7 +162,7 @@
</func>
<func>
- <name>run_on_node(TestSpecs, Node) -&gt; ok</name>
+ <name since="">run_on_node(TestSpecs, Node) -&gt; ok</name>
<fsummary>Equivalent to run_on_node(TestSpecs, false, Node).</fsummary>
<type>
<v>TestSpecs = string() | [SeparateOrMerged]</v>
@@ -177,7 +177,7 @@
</func>
<func>
- <name>run_on_node(TestSpecs, AllowUserTerms, Node) -&gt; ok</name>
+ <name since="">run_on_node(TestSpecs, AllowUserTerms, Node) -&gt; ok</name>
<fsummary>Tests are spawned on Node according to TestSpecs.</fsummary>
<type>
<v>TestSpecs = string() | [SeparateOrMerged]</v>
@@ -191,7 +191,7 @@
</func>
<func>
- <name>run_test(Node, Opts) -&gt; ok</name>
+ <name since="">run_test(Node, Opts) -&gt; ok</name>
<fsummary>Tests are spawned on Node using ct:run_test/1.</fsummary>
<type>
<v>Node = atom()</v>
diff --git a/lib/common_test/doc/src/ct_netconfc.xml b/lib/common_test/doc/src/ct_netconfc.xml
index 7ec8f23073..8fbe5f3df6 100644
--- a/lib/common_test/doc/src/ct_netconfc.xml
+++ b/lib/common_test/doc/src/ct_netconfc.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>ct_netconfc.xml</file>
</header>
- <module>ct_netconfc</module>
+ <module since="OTP R15B02">ct_netconfc</module>
<modulesummary>NETCONF client module.</modulesummary>
<description>
@@ -312,8 +312,8 @@
<funcs>
<func>
- <name name="action" arity="2"/>
- <name name="action" arity="3"/>
+ <name name="action" arity="2" since="OTP R15B02"/>
+ <name name="action" arity="3" since="OTP R15B02"/>
<fsummary>Executes an action.</fsummary>
<desc>
<p>Executes an action. If the return type is void, <c>ok</c> is
@@ -322,8 +322,8 @@
</func>
<func>
- <name name="close_session" arity="1"/>
- <name name="close_session" arity="2"/>
+ <name name="close_session" arity="1" since="OTP R15B02"/>
+ <name name="close_session" arity="2" since="OTP R15B02"/>
<fsummary>Requests graceful termination of the session associated with
the client.</fsummary>
<desc>
@@ -339,7 +339,7 @@
</func>
<func>
- <name name="connect" arity="1"/>
+ <name name="connect" arity="1" since="OTP 20.0"/>
<fsummary>Opens an SSH connection to a NETCONF server.</fsummary>
<desc>
<p>Opens an SSH connection to a NETCONF server.</p>
@@ -361,7 +361,7 @@
</func>
<func>
- <name name="connect" arity="2"/>
+ <name name="connect" arity="2" since="OTP 20.0"/>
<fsummary>Opens an SSH connection to a named NETCONF server.</fsummary>
<desc>
<p>Open an SSH connection to a named NETCONF server.</p>
@@ -399,8 +399,8 @@
</func>
<func>
- <name name="copy_config" arity="3"/>
- <name name="copy_config" arity="4"/>
+ <name name="copy_config" arity="3" since="OTP R15B02"/>
+ <name name="copy_config" arity="4" since="OTP R15B02"/>
<fsummary>Copies configuration data.</fsummary>
<desc>
<p>Copies configuration data.</p>
@@ -412,12 +412,12 @@
</func>
<func>
- <name>create_subscription(Client) -> Result</name>
- <name>create_subscription(Client, Stream) -> Result</name>
- <name>create_subscription(Client, Stream, Filter) -> Result</name>
- <name>create_subscription(Client, Stream, Filter, Timeout) -> Result</name>
- <name name="create_subscription" arity="5" clause_i="2"/>
- <name name="create_subscription" arity="6"/>
+ <name since="OTP R15B02">create_subscription(Client) -> Result</name>
+ <name since="OTP R15B02">create_subscription(Client, Stream) -> Result</name>
+ <name since="OTP R15B02">create_subscription(Client, Stream, Filter) -> Result</name>
+ <name since="OTP R15B02">create_subscription(Client, Stream, Filter, Timeout) -> Result</name>
+ <name name="create_subscription" arity="5" clause_i="2" since="OTP R15B02"/>
+ <name name="create_subscription" arity="6" since="OTP R15B02"/>
<fsummary>Creates a subscription for event notifications.</fsummary>
<desc>
<p>Creates a subscription for event notifications.</p>
@@ -490,8 +490,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="delete_config" arity="2"/>
- <name name="delete_config" arity="3"/>
+ <name name="delete_config" arity="2" since="OTP R15B02"/>
+ <name name="delete_config" arity="3" since="OTP R15B02"/>
<fsummary>Deletes configuration data.</fsummary>
<desc>
<p>Deletes configuration data.</p>
@@ -502,7 +502,7 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="disconnect" arity="1"/>
+ <name name="disconnect" arity="1" since="OTP 20.0"/>
<fsummary>Closes the given SSH connection.</fsummary>
<desc>
<p>Closes the given SSH connection.</p>
@@ -514,10 +514,10 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="edit_config" arity="3"/>
- <name name="edit_config" arity="4" clause_i="1"/>
- <name name="edit_config" arity="4" clause_i="2"/>
- <name name="edit_config" arity="5"/>
+ <name name="edit_config" arity="3" since="OTP R15B02"/>
+ <name name="edit_config" arity="4" clause_i="1" since="OTP 18.0"/>
+ <name name="edit_config" arity="4" clause_i="2" since="OTP R15B02"/>
+ <name name="edit_config" arity="5" since="OTP 18.0"/>
<fsummary>Edits configuration data.</fsummary>
<desc>
<p>Edits configuration data.</p>
@@ -542,8 +542,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="get" arity="2"/>
- <name name="get" arity="3"/>
+ <name name="get" arity="2" since="OTP R15B02"/>
+ <name name="get" arity="3" since="OTP R15B02"/>
<fsummary>Gets data.</fsummary>
<desc>
<p>Gets data.</p>
@@ -557,8 +557,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="get_capabilities" arity="1"/>
- <name name="get_capabilities" arity="2"/>
+ <name name="get_capabilities" arity="1" since="OTP R15B02"/>
+ <name name="get_capabilities" arity="2" since="OTP R15B02"/>
<fsummary>Returns the server side capabilities.</fsummary>
<desc>
<p>Returns the server side capabilities.</p>
@@ -582,8 +582,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="get_config" arity="3"/>
- <name name="get_config" arity="4"/>
+ <name name="get_config" arity="3" since="OTP R15B02"/>
+ <name name="get_config" arity="4" since="OTP R15B02"/>
<fsummary>Gets configuration data.</fsummary>
<desc>
<p>Gets configuration data.</p>
@@ -597,10 +597,10 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="get_event_streams" arity="1"/>
- <name name="get_event_streams" arity="2" clause_i="1"/>
- <name name="get_event_streams" arity="2" clause_i="2"/>
- <name name="get_event_streams" arity="3"/>
+ <name name="get_event_streams" arity="1" since="OTP 20.0"/>
+ <name name="get_event_streams" arity="2" clause_i="1" since="OTP R15B02"/>
+ <name name="get_event_streams" arity="2" clause_i="2" since="OTP 20.0"/>
+ <name name="get_event_streams" arity="3" since="OTP R15B02"/>
<fsummary>Sends a request to get the specified event streams.</fsummary>
<desc>
<p>Sends a request to get the specified event streams.</p>
@@ -637,8 +637,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="get_session_id" arity="1"/>
- <name name="get_session_id" arity="2"/>
+ <name name="get_session_id" arity="1" since="OTP R15B02"/>
+ <name name="get_session_id" arity="2" since="OTP R15B02"/>
<fsummary>Returns the session Id associated with the specified
client.</fsummary>
<desc>
@@ -647,9 +647,9 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="hello" arity="1"/>
- <name name="hello" arity="2"/>
- <name name="hello" arity="3"/>
+ <name name="hello" arity="1" since="OTP R15B02"/>
+ <name name="hello" arity="2" since="OTP R15B02"/>
+ <name name="hello" arity="3" since="OTP 17.5.3"/>
<fsummary>Exchanges hello messages with the server.</fsummary>
<desc>
<p>Exchanges <c>hello</c> messages with the server.</p>
@@ -660,8 +660,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="kill_session" arity="2"/>
- <name name="kill_session" arity="3"/>
+ <name name="kill_session" arity="2" since="OTP R15B02"/>
+ <name name="kill_session" arity="3" since="OTP R15B02"/>
<fsummary>Forces termination of the session associated with the supplied
session Id.</fsummary>
<desc>
@@ -682,8 +682,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="lock" arity="2"/>
- <name name="lock" arity="3"/>
+ <name name="lock" arity="2" since="OTP R15B02"/>
+ <name name="lock" arity="3" since="OTP R15B02"/>
<fsummary>Locks the configuration target.</fsummary>
<desc>
<p>Locks the configuration target.</p>
@@ -703,7 +703,7 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="only_open" arity="1"/>
+ <name name="only_open" arity="1" since="OTP R15B02"/>
<fsummary>Opens a NETCONF session, but does not send hello.</fsummary>
<desc>
<p>Opens a NETCONF session, but does not send <c>hello</c>.</p>
@@ -714,7 +714,7 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="only_open" arity="2"/>
+ <name name="only_open" arity="2" since="OTP R15B02"/>
<fsummary>Opens a named NETCONF session, but does not send hello.</fsummary>
<desc>
<p>Opens a named NETCONF session, but does not send <c>hello</c>.</p>
@@ -725,7 +725,7 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="open" arity="1"/>
+ <name name="open" arity="1" since="OTP R15B02"/>
<fsummary>Opens a NETCONF session and exchanges hello messages.</fsummary>
<desc>
<p>Opens a NETCONF session and exchanges <c>hello</c> messages.</p>
@@ -749,7 +749,7 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="open" arity="2"/>
+ <name name="open" arity="2" since="OTP R15B02"/>
<fsummary>Opens a named NETCONF session and exchanges hello
messages.</fsummary>
<desc>
@@ -791,8 +791,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="send" arity="2"/>
- <name name="send" arity="3"/>
+ <name name="send" arity="2" since="OTP R16B02"/>
+ <name name="send" arity="3" since="OTP R16B02"/>
<fsummary>Sends an XML document to the server.</fsummary>
<desc>
<p>Sends an XML document to the server.</p>
@@ -804,8 +804,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="send_rpc" arity="2"/>
- <name name="send_rpc" arity="3"/>
+ <name name="send_rpc" arity="2" since="OTP R16B02"/>
+ <name name="send_rpc" arity="3" since="OTP R16B02"/>
<fsummary>Sends a NETCONF rpc request to the server.</fsummary>
<desc>
<p>Sends a NETCONF <c>rpc</c> request to the server.</p>
@@ -820,10 +820,10 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="session" arity="1"/>
- <name name="session" arity="2" clause_i="1"/>
- <name name="session" arity="2" clause_i="2"/>
- <name name="session" arity="3"/>
+ <name name="session" arity="1" since="OTP 20.0"/>
+ <name name="session" arity="2" clause_i="1" since="OTP 20.0"/>
+ <name name="session" arity="2" clause_i="2" since="OTP 20.0"/>
+ <name name="session" arity="3" since="OTP 20.0"/>
<fsummary>Opens a NETCONF session as a channel on the given SSH
connection, and exchanges hello messages with the
server.</fsummary>
@@ -848,8 +848,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="unlock" arity="2"/>
- <name name="unlock" arity="3"/>
+ <name name="unlock" arity="2" since="OTP R15B02"/>
+ <name name="unlock" arity="3" since="OTP R15B02"/>
<fsummary>Unlocks the configuration target.</fsummary>
<desc>
<p>Unlocks the configuration target.</p>
diff --git a/lib/common_test/doc/src/ct_property_test.xml b/lib/common_test/doc/src/ct_property_test.xml
index 028e5eb69f..1e01d9a5d7 100644
--- a/lib/common_test/doc/src/ct_property_test.xml
+++ b/lib/common_test/doc/src/ct_property_test.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>ct_property_test.xml</file>
</header>
- <module>ct_property_test</module>
+ <module since="OTP 17.3">ct_property_test</module>
<modulesummary>EXPERIMENTAL support in Common Test for calling
property-based tests.</modulesummary>
@@ -79,7 +79,7 @@
<funcs>
<func>
- <name>init_per_suite(Config) -&gt; Config | {skip, Reason}</name>
+ <name since="OTP 17.3">init_per_suite(Config) -&gt; Config | {skip, Reason}</name>
<fsummary>Initializes Config for property testing.</fsummary>
<desc><marker id="init_per_suite-1"/>
<p>Initializes <c>Config</c> for property testing.</p>
@@ -98,7 +98,7 @@
</func>
<func>
- <name>quickcheck(Property, Config) -&gt; true | {fail, Reason}</name>
+ <name since="OTP 17.3">quickcheck(Property, Config) -&gt; true | {fail, Reason}</name>
<fsummary>Calls quickcheck and returns the result in a form suitable for
Common Test.</fsummary>
<desc><marker id="quickcheck-2"/>
diff --git a/lib/common_test/doc/src/ct_rpc.xml b/lib/common_test/doc/src/ct_rpc.xml
index 90e6b833f7..00a4dcec08 100644
--- a/lib/common_test/doc/src/ct_rpc.xml
+++ b/lib/common_test/doc/src/ct_rpc.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>ct_rpc.xml</file>
</header>
- <module>ct_rpc</module>
+ <module since="">ct_rpc</module>
<modulesummary>Common Test specific layer on Erlang/OTP rpc.</modulesummary>
<description>
@@ -43,7 +43,7 @@
<funcs>
<func>
- <name>app_node(App, Candidates) -&gt; NodeName</name>
+ <name since="">app_node(App, Candidates) -&gt; NodeName</name>
<fsummary>From a set of candidate nodes determines which of them is
running the application App.</fsummary>
<type>
@@ -61,7 +61,7 @@
</func>
<func>
- <name>app_node(App, Candidates, FailOnBadRPC) -&gt; NodeName</name>
+ <name since="">app_node(App, Candidates, FailOnBadRPC) -&gt; NodeName</name>
<fsummary>Same as app_node/2, except that argument FailOnBadRPC
determines if the search for a candidate node is to stop if
badrpc is received at some point.</fsummary>
@@ -81,7 +81,7 @@
</func>
<func>
- <name>app_node(App, Candidates, FailOnBadRPC, Cookie) -&gt; NodeName</name>
+ <name since="">app_node(App, Candidates, FailOnBadRPC, Cookie) -&gt; NodeName</name>
<fsummary>Same as app_node/2, except that argument FailOnBadRPC
determines if the search for a candidate node is to stop if badrpc is
received at some point.</fsummary>
@@ -105,7 +105,7 @@
</func>
<func>
- <name>call(Node, Module, Function, Args) -&gt; term() | {badrpc, Reason}</name>
+ <name since="">call(Node, Module, Function, Args) -&gt; term() | {badrpc, Reason}</name>
<fsummary>Same as call(Node, Module, Function, Args, infinity).</fsummary>
<desc><marker id="call-4"/>
<p>Same as <c>call(Node, Module, Function, Args, infinity)</c>.</p>
@@ -113,7 +113,7 @@
</func>
<func>
- <name>call(Node, Module, Function, Args, TimeOut) -&gt; term() | {badrpc, Reason}</name>
+ <name since="">call(Node, Module, Function, Args, TimeOut) -&gt; term() | {badrpc, Reason}</name>
<fsummary>Evaluates apply(Module, Function, Args) on the node
Node.</fsummary>
<type>
@@ -136,7 +136,7 @@
</func>
<func>
- <name>call(Node, Module, Function, Args, TimeOut, Cookie) -&gt; term() | {badrpc, Reason}</name>
+ <name since="">call(Node, Module, Function, Args, TimeOut, Cookie) -&gt; term() | {badrpc, Reason}</name>
<fsummary>Evaluates apply(Module, Function, Args) on the node
Node.</fsummary>
<type>
@@ -163,7 +163,7 @@
</func>
<func>
- <name>cast(Node, Module, Function, Args) -&gt; ok</name>
+ <name since="">cast(Node, Module, Function, Args) -&gt; ok</name>
<fsummary>Evaluates apply(Module, Function, Args) on the node
Node.</fsummary>
<type>
@@ -187,7 +187,7 @@
</func>
<func>
- <name>cast(Node, Module, Function, Args, Cookie) -&gt; ok</name>
+ <name since="">cast(Node, Module, Function, Args, Cookie) -&gt; ok</name>
<fsummary>Evaluates apply(Module, Function, Args) on the node
Node.</fsummary>
<type>
diff --git a/lib/common_test/doc/src/ct_slave.xml b/lib/common_test/doc/src/ct_slave.xml
index 9d9aa50051..84e619482d 100644
--- a/lib/common_test/doc/src/ct_slave.xml
+++ b/lib/common_test/doc/src/ct_slave.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>ct_slave.xml</file>
</header>
- <module>ct_slave</module>
+ <module since="OTP R14B">ct_slave</module>
<modulesummary>Common Test framework functions for starting and stopping
nodes for Large-Scale Testing.</modulesummary>
@@ -50,7 +50,7 @@
<funcs>
<func>
- <name>start(Node) -&gt; Result</name>
+ <name since="OTP R14B">start(Node) -&gt; Result</name>
<fsummary>Starts an Erlang node with name Node on the local
host.</fsummary>
<type>
@@ -68,7 +68,7 @@
</func>
<func>
- <name>start(HostOrNode, NodeOrOpts) -&gt; Result</name>
+ <name since="OTP R14B">start(HostOrNode, NodeOrOpts) -&gt; Result</name>
<fsummary>Starts an Erlang node with default options on a specified
host, or on the local host with specified options.</fsummary>
<type>
@@ -90,7 +90,7 @@
</func>
<func>
- <name>start(Host, Node, Opts) -&gt; Result</name>
+ <name since="OTP R14B">start(Host, Node, Opts) -&gt; Result</name>
<fsummary>Starts an Erlang node with name Node on host Host as
specified by the combination of options in Opts.</fsummary>
<type>
@@ -184,7 +184,7 @@
</func>
<func>
- <name>stop(Node) -&gt; Result</name>
+ <name since="OTP R14B">stop(Node) -&gt; Result</name>
<fsummary>Stops the running Erlang node with name Node on the local
host.</fsummary>
<type>
@@ -199,7 +199,7 @@
</func>
<func>
- <name>stop(Host, Node) -&gt; Result</name>
+ <name since="OTP R14B">stop(Host, Node) -&gt; Result</name>
<fsummary>Stops the running Erlang node with name Node on host
Host.</fsummary>
<type>
diff --git a/lib/common_test/doc/src/ct_snmp.xml b/lib/common_test/doc/src/ct_snmp.xml
index 0a5e52b16c..343781814a 100644
--- a/lib/common_test/doc/src/ct_snmp.xml
+++ b/lib/common_test/doc/src/ct_snmp.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>ct_snmp.xml</file>
</header>
- <module>ct_snmp</module>
+ <module since="">ct_snmp</module>
<modulesummary>Common Test user interface module for the SNMP application.</modulesummary>
<description>
@@ -240,7 +240,7 @@
<funcs>
<func>
- <name>get_next_values(Agent, Oids, MgrAgentConfName) -&gt; SnmpReply</name>
+ <name since="">get_next_values(Agent, Oids, MgrAgentConfName) -&gt; SnmpReply</name>
<fsummary>Issues a synchronous SNMP get next request.</fsummary>
<type>
<v>Agent = agent_name()</v>
@@ -254,7 +254,7 @@
</func>
<func>
- <name>get_values(Agent, Oids, MgrAgentConfName) -&gt; SnmpReply</name>
+ <name since="">get_values(Agent, Oids, MgrAgentConfName) -&gt; SnmpReply</name>
<fsummary>Issues a synchronous SNMP get request.</fsummary>
<type>
<v>Agent = agent_name()</v>
@@ -268,7 +268,7 @@
</func>
<func>
- <name>load_mibs(Mibs) -&gt; ok | {error, Reason}</name>
+ <name since="">load_mibs(Mibs) -&gt; ok | {error, Reason}</name>
<fsummary>Loads the MIBs into agent snmp_master_agent.</fsummary>
<type>
<v>Mibs = [MibName]</v>
@@ -281,7 +281,7 @@
</func>
<func>
- <name>register_agents(MgrAgentConfName, ManagedAgents) -&gt; ok | {error, Reason}</name>
+ <name since="">register_agents(MgrAgentConfName, ManagedAgents) -&gt; ok | {error, Reason}</name>
<fsummary>Explicitly instructs the manager to handle this
agent.</fsummary>
<type>
@@ -300,7 +300,7 @@
</func>
<func>
- <name>register_users(MgrAgentConfName, Users) -&gt; ok | {error, Reason}</name>
+ <name since="">register_users(MgrAgentConfName, Users) -&gt; ok | {error, Reason}</name>
<fsummary>Registers the manager entity (=user) responsible for specific
agent(s).</fsummary>
<type>
@@ -319,7 +319,7 @@
</func>
<func>
- <name>register_usm_users(MgrAgentConfName, UsmUsers) -&gt; ok | {error, Reason}</name>
+ <name since="">register_usm_users(MgrAgentConfName, UsmUsers) -&gt; ok | {error, Reason}</name>
<fsummary>Explicitly instructs the manager to handle this USM user.</fsummary>
<type>
<v>MgrAgentConfName = atom()</v>
@@ -337,7 +337,7 @@
</func>
<func>
- <name>set_info(Config) -&gt; [{Agent, OldVarsAndVals, NewVarsAndVals}]</name>
+ <name since="">set_info(Config) -&gt; [{Agent, OldVarsAndVals, NewVarsAndVals}]</name>
<fsummary>Returns a list of all successful set requests performed in the
test case in reverse order.</fsummary>
<type>
@@ -357,7 +357,7 @@
</func>
<func>
- <name>set_values(Agent, VarsAndVals, MgrAgentConfName, Config) -&gt; SnmpReply</name>
+ <name since="">set_values(Agent, VarsAndVals, MgrAgentConfName, Config) -&gt; SnmpReply</name>
<fsummary>Issues a synchronous SNMP set request.</fsummary>
<type>
<v>Agent = agent_name()</v>
@@ -372,7 +372,7 @@
</func>
<func>
- <name>start(Config, MgrAgentConfName) -&gt; ok</name>
+ <name since="">start(Config, MgrAgentConfName) -&gt; ok</name>
<fsummary>Equivalent to start(Config, MgrAgentConfName,
undefined).</fsummary>
<desc><marker id="start-2"/>
@@ -383,7 +383,7 @@
</func>
<func>
- <name>start(Config, MgrAgentConfName, SnmpAppConfName) -&gt; ok</name>
+ <name since="">start(Config, MgrAgentConfName, SnmpAppConfName) -&gt; ok</name>
<fsummary>Starts an SNMP manager and/or agent.</fsummary>
<type>
<v>Config = [{Key, Value}]</v>
@@ -415,7 +415,7 @@
</func>
<func>
- <name>stop(Config) -&gt; ok</name>
+ <name since="">stop(Config) -&gt; ok</name>
<fsummary>Stops the SNMP manager and/or agent, and removes all files
created.</fsummary>
<type>
@@ -430,7 +430,7 @@
</func>
<func>
- <name>unload_mibs(Mibs) -&gt; ok | {error, Reason}</name>
+ <name since="OTP R16B">unload_mibs(Mibs) -&gt; ok | {error, Reason}</name>
<fsummary>Unloads the MIBs from agent snmp_master_agent.</fsummary>
<type>
<v>Mibs = [MibName]</v>
@@ -443,7 +443,7 @@
</func>
<func>
- <name>unregister_agents(MgrAgentConfName) -&gt; ok</name>
+ <name since="">unregister_agents(MgrAgentConfName) -&gt; ok</name>
<fsummary>Unregisters all managed agents.</fsummary>
<type>
<v>MgrAgentConfName = atom()</v>
@@ -455,7 +455,7 @@
</func>
<func>
- <name>unregister_agents(MgrAgentConfName, ManagedAgents) -&gt; ok</name>
+ <name since="OTP R16B">unregister_agents(MgrAgentConfName, ManagedAgents) -&gt; ok</name>
<fsummary>Unregisters the specified managed agents.</fsummary>
<type>
<v>MgrAgentConfName = atom()</v>
@@ -468,7 +468,7 @@
</func>
<func>
- <name>unregister_users(MgrAgentConfName) -&gt; ok</name>
+ <name since="">unregister_users(MgrAgentConfName) -&gt; ok</name>
<fsummary>Unregisters all users.</fsummary>
<type>
<v>MgrAgentConfName = atom()</v>
@@ -480,7 +480,7 @@
</func>
<func>
- <name>unregister_users(MgrAgentConfName, Users) -&gt; ok</name>
+ <name since="OTP R16B">unregister_users(MgrAgentConfName, Users) -&gt; ok</name>
<fsummary>Unregisters the specified users.</fsummary>
<type>
<v>MgrAgentConfName = atom()</v>
@@ -493,7 +493,7 @@
</func>
<func>
- <name>unregister_usm_users(MgrAgentConfName) -&gt; ok</name>
+ <name since="OTP R16B">unregister_usm_users(MgrAgentConfName) -&gt; ok</name>
<fsummary>Unregisters all USM users.</fsummary>
<type>
<v>MgrAgentConfName = atom()</v>
@@ -505,7 +505,7 @@
</func>
<func>
- <name>unregister_usm_users(MgrAgentConfName, UsmUsers) -&gt; ok</name>
+ <name since="OTP R16B">unregister_usm_users(MgrAgentConfName, UsmUsers) -&gt; ok</name>
<fsummary>Unregisters the specified USM users.</fsummary>
<type>
<v>MgrAgentConfName = atom()</v>
diff --git a/lib/common_test/doc/src/ct_ssh.xml b/lib/common_test/doc/src/ct_ssh.xml
index 0c7efed154..8d9f31aff8 100644
--- a/lib/common_test/doc/src/ct_ssh.xml
+++ b/lib/common_test/doc/src/ct_ssh.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>ct_ssh.xml</file>
</header>
- <module>ct_ssh</module>
+ <module since="">ct_ssh</module>
<modulesummary>SSH/SFTP client module.</modulesummary>
<description>
@@ -95,7 +95,7 @@
<funcs>
<func>
- <name>apread(SSH, Handle, Position, Length) -&gt; Result</name>
+ <name since="">apread(SSH, Handle, Position, Length) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -109,7 +109,7 @@
</func>
<func>
- <name>apread(SSH, Server, Handle, Position, Length) -&gt; Result</name>
+ <name since="">apread(SSH, Server, Handle, Position, Length) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -123,7 +123,7 @@
</func>
<func>
- <name>apwrite(SSH, Handle, Position, Data) -&gt; Result</name>
+ <name since="">apwrite(SSH, Handle, Position, Data) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -137,7 +137,7 @@
</func>
<func>
- <name>apwrite(SSH, Server, Handle, Position, Data) -&gt; Result</name>
+ <name since="">apwrite(SSH, Server, Handle, Position, Data) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -151,7 +151,7 @@
</func>
<func>
- <name>aread(SSH, Handle, Len) -&gt; Result</name>
+ <name since="">aread(SSH, Handle, Len) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -165,7 +165,7 @@
</func>
<func>
- <name>aread(SSH, Server, Handle, Len) -&gt; Result</name>
+ <name since="">aread(SSH, Server, Handle, Len) -&gt; Result</name>
<fsummary>For inforamtion and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -179,7 +179,7 @@
</func>
<func>
- <name>awrite(SSH, Handle, Data) -&gt; Result</name>
+ <name since="">awrite(SSH, Handle, Data) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -193,7 +193,7 @@
</func>
<func>
- <name>awrite(SSH, Server, Handle, Data) -&gt; Result</name>
+ <name since="">awrite(SSH, Server, Handle, Data) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -207,7 +207,7 @@
</func>
<func>
- <name>close(SSH, Handle) -&gt; Result</name>
+ <name since="">close(SSH, Handle) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -221,7 +221,7 @@
</func>
<func>
- <name>close(SSH, Server, Handle) -&gt; Result</name>
+ <name since="">close(SSH, Server, Handle) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -235,7 +235,7 @@
</func>
<func>
- <name>connect(KeyOrName) -&gt; {ok, Handle} | {error, Reason}</name>
+ <name since="">connect(KeyOrName) -&gt; {ok, Handle} | {error, Reason}</name>
<fsummary>Equivalent to connect(KeyOrName, host, []).</fsummary>
<desc><marker id="connect-1"/>
<p>Equivalent to
@@ -245,7 +245,7 @@
</func>
<func>
- <name>connect(KeyOrName, ConnType) -&gt; {ok, Handle} | {error, Reason}</name>
+ <name since="">connect(KeyOrName, ConnType) -&gt; {ok, Handle} | {error, Reason}</name>
<fsummary>Equivalent to connect(KeyOrName, ConnType, []).</fsummary>
<desc><marker id="connect-2"/>
<p>Equivalent to
@@ -255,7 +255,7 @@
</func>
<func>
- <name>connect(KeyOrName, ConnType, ExtraOpts) -&gt; {ok, Handle} | {error, Reason}</name>
+ <name since="">connect(KeyOrName, ConnType, ExtraOpts) -&gt; {ok, Handle} | {error, Reason}</name>
<fsummary>Opens an SSH or SFTP connection using the information
associated with KeyOrName.</fsummary>
<type>
@@ -301,7 +301,7 @@
</func>
<func>
- <name>del_dir(SSH, Name) -&gt; Result</name>
+ <name since="">del_dir(SSH, Name) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -315,7 +315,7 @@
</func>
<func>
- <name>del_dir(SSH, Server, Name) -&gt; Result</name>
+ <name since="">del_dir(SSH, Server, Name) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -329,7 +329,7 @@
</func>
<func>
- <name>delete(SSH, Name) -&gt; Result</name>
+ <name since="">delete(SSH, Name) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -343,7 +343,7 @@
</func>
<func>
- <name>delete(SSH, Server, Name) -&gt; Result</name>
+ <name since="">delete(SSH, Server, Name) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -357,7 +357,7 @@
</func>
<func>
- <name>disconnect(SSH) -&gt; ok | {error, Reason}</name>
+ <name since="">disconnect(SSH) -&gt; ok | {error, Reason}</name>
<fsummary>Closes an SSH/SFTP connection.</fsummary>
<type>
<v>SSH = connection()</v>
@@ -369,7 +369,7 @@
</func>
<func>
- <name>exec(SSH, Command) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">exec(SSH, Command) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Equivalent to exec(SSH, Command, DefaultTimeout).</fsummary>
<desc><marker id="exec-2"/>
<p>Equivalent to
@@ -379,7 +379,7 @@
</func>
<func>
- <name>exec(SSH, Command, Timeout) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">exec(SSH, Command, Timeout) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Requests server to perform Command.</fsummary>
<type>
<v>SSH = connection()</v>
@@ -396,7 +396,7 @@
</func>
<func>
- <name>exec(SSH, ChannelId, Command, Timeout) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">exec(SSH, ChannelId, Command, Timeout) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Requests server to perform Command.</fsummary>
<type>
<v>SSH = connection()</v>
@@ -414,7 +414,7 @@
</func>
<func>
- <name>get_file_info(SSH, Handle) -&gt; Result</name>
+ <name since="">get_file_info(SSH, Handle) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -428,7 +428,7 @@
</func>
<func>
- <name>get_file_info(SSH, Server, Handle) -&gt; Result</name>
+ <name since="">get_file_info(SSH, Server, Handle) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -442,7 +442,7 @@
</func>
<func>
- <name>list_dir(SSH, Path) -&gt; Result</name>
+ <name since="">list_dir(SSH, Path) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -456,7 +456,7 @@
</func>
<func>
- <name>list_dir(SSH, Server, Path) -&gt; Result</name>
+ <name since="">list_dir(SSH, Server, Path) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -470,7 +470,7 @@
</func>
<func>
- <name>make_dir(SSH, Name) -&gt; Result</name>
+ <name since="">make_dir(SSH, Name) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -484,7 +484,7 @@
</func>
<func>
- <name>make_dir(SSH, Server, Name) -&gt; Result</name>
+ <name since="">make_dir(SSH, Server, Name) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -498,7 +498,7 @@
</func>
<func>
- <name>make_symlink(SSH, Name, Target) -&gt; Result</name>
+ <name since="">make_symlink(SSH, Name, Target) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -512,7 +512,7 @@
</func>
<func>
- <name>make_symlink(SSH, Server, Name, Target) -&gt; Result</name>
+ <name since="">make_symlink(SSH, Server, Name, Target) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -526,7 +526,7 @@
</func>
<func>
- <name>open(SSH, File, Mode) -&gt; Result</name>
+ <name since="">open(SSH, File, Mode) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -540,7 +540,7 @@
</func>
<func>
- <name>open(SSH, Server, File, Mode) -&gt; Result</name>
+ <name since="">open(SSH, Server, File, Mode) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -554,7 +554,7 @@
</func>
<func>
- <name>opendir(SSH, Path) -&gt; Result</name>
+ <name since="">opendir(SSH, Path) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -568,7 +568,7 @@
</func>
<func>
- <name>opendir(SSH, Server, Path) -&gt; Result</name>
+ <name since="">opendir(SSH, Server, Path) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -582,7 +582,7 @@
</func>
<func>
- <name>position(SSH, Handle, Location) -&gt; Result</name>
+ <name since="">position(SSH, Handle, Location) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -596,7 +596,7 @@
</func>
<func>
- <name>position(SSH, Server, Handle, Location) -&gt; Result</name>
+ <name since="">position(SSH, Server, Handle, Location) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -610,7 +610,7 @@
</func>
<func>
- <name>pread(SSH, Handle, Position, Length) -&gt; Result</name>
+ <name since="">pread(SSH, Handle, Position, Length) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -624,7 +624,7 @@
</func>
<func>
- <name>pread(SSH, Server, Handle, Position, Length) -&gt; Result</name>
+ <name since="">pread(SSH, Server, Handle, Position, Length) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -638,7 +638,7 @@
</func>
<func>
- <name>pwrite(SSH, Handle, Position, Data) -&gt; Result</name>
+ <name since="">pwrite(SSH, Handle, Position, Data) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -652,7 +652,7 @@
</func>
<func>
- <name>pwrite(SSH, Server, Handle, Position, Data) -&gt; Result</name>
+ <name since="">pwrite(SSH, Server, Handle, Position, Data) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -666,7 +666,7 @@
</func>
<func>
- <name>read(SSH, Handle, Len) -&gt; Result</name>
+ <name since="">read(SSH, Handle, Len) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -680,7 +680,7 @@
</func>
<func>
- <name>read(SSH, Server, Handle, Len) -&gt; Result</name>
+ <name since="">read(SSH, Server, Handle, Len) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -694,7 +694,7 @@
</func>
<func>
- <name>read_file(SSH, File) -&gt; Result</name>
+ <name since="">read_file(SSH, File) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -708,7 +708,7 @@
</func>
<func>
- <name>read_file(SSH, Server, File) -&gt; Result</name>
+ <name since="">read_file(SSH, Server, File) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -722,7 +722,7 @@
</func>
<func>
- <name>read_file_info(SSH, Name) -&gt; Result</name>
+ <name since="">read_file_info(SSH, Name) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -736,7 +736,7 @@
</func>
<func>
- <name>read_file_info(SSH, Server, Name) -&gt; Result</name>
+ <name since="">read_file_info(SSH, Server, Name) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -750,7 +750,7 @@
</func>
<func>
- <name>read_link(SSH, Name) -&gt; Result</name>
+ <name since="">read_link(SSH, Name) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -764,7 +764,7 @@
</func>
<func>
- <name>read_link(SSH, Server, Name) -&gt; Result</name>
+ <name since="">read_link(SSH, Server, Name) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -778,7 +778,7 @@
</func>
<func>
- <name>read_link_info(SSH, Name) -&gt; Result</name>
+ <name since="">read_link_info(SSH, Name) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -792,7 +792,7 @@
</func>
<func>
- <name>read_link_info(SSH, Server, Name) -&gt; Result</name>
+ <name since="">read_link_info(SSH, Server, Name) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -806,7 +806,7 @@
</func>
<func>
- <name>receive_response(SSH, ChannelId) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">receive_response(SSH, ChannelId) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Equivalent to receive_response(SSH, ChannelId,
close).</fsummary>
<desc><marker id="receive_response-2"/>
@@ -817,7 +817,7 @@ ChannelId, close)</c></seealso>.</p>
</func>
<func>
- <name>receive_response(SSH, ChannelId, End) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">receive_response(SSH, ChannelId, End) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Equivalent to receive_response(SSH, ChannelId, End,
DefaultTimeout).</fsummary>
<desc><marker id="receive_response-3"/>
@@ -828,7 +828,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p>
</func>
<func>
- <name>receive_response(SSH, ChannelId, End, Timeout) -&gt; {ok, Data} | {timeout, Data} | {error, Reason}</name>
+ <name since="">receive_response(SSH, ChannelId, End, Timeout) -&gt; {ok, Data} | {timeout, Data} | {error, Reason}</name>
<fsummary>Receives expected data from server on the specified session
channel.</fsummary>
<type>
@@ -863,7 +863,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p>
</func>
<func>
- <name>rename(SSH, OldName, NewName) -&gt; Result</name>
+ <name since="">rename(SSH, OldName, NewName) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -877,7 +877,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p>
</func>
<func>
- <name>rename(SSH, Server, OldName, NewName) -&gt; Result</name>
+ <name since="">rename(SSH, Server, OldName, NewName) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -891,7 +891,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p>
</func>
<func>
- <name>send(SSH, ChannelId, Data) -&gt; ok | {error, Reason}</name>
+ <name since="">send(SSH, ChannelId, Data) -&gt; ok | {error, Reason}</name>
<fsummary>Equivalent to send(SSH, ChannelId, 0, Data,
DefaultTimeout).</fsummary>
<desc><marker id="send-3"/>
@@ -901,7 +901,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p>
</func>
<func>
- <name>send(SSH, ChannelId, Data, Timeout) -&gt; ok | {error, Reason}</name>
+ <name since="">send(SSH, ChannelId, Data, Timeout) -&gt; ok | {error, Reason}</name>
<fsummary>Equivalent to send(SSH, ChannelId, 0, Data, Timeout).</fsummary>
<desc><marker id="send-4"/>
<p>Equivalent to <seealso marker="#send-5"><c>ct_ssh:send(SSH,
@@ -910,7 +910,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p>
</func>
<func>
- <name>send(SSH, ChannelId, Type, Data, Timeout) -&gt; ok | {error, Reason}</name>
+ <name since="">send(SSH, ChannelId, Type, Data, Timeout) -&gt; ok | {error, Reason}</name>
<fsummary>Sends data to server on specified session channel.</fsummary>
<type>
<v>SSH = connection()</v>
@@ -926,7 +926,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p>
</func>
<func>
- <name>send_and_receive(SSH, ChannelId, Data) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">send_and_receive(SSH, ChannelId, Data) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Equivalent to send_and_receive(SSH, ChannelId, Data,
close).</fsummary>
<desc><marker id="send_and_receive-3"/>
@@ -937,7 +937,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p>
</func>
<func>
- <name>send_and_receive(SSH, ChannelId, Data, End) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">send_and_receive(SSH, ChannelId, Data, End) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Equivalent to send_and_receive(SSH, ChannelId, 0, Data, End,
DefaultTimeout).</fsummary>
<desc><marker id="send_and_receive-4"/>
@@ -948,7 +948,7 @@ ChannelId, 0, Data, End, DefaultTimeout)</c></seealso>.</p>
</func>
<func>
- <name>send_and_receive(SSH, ChannelId, Data, End, Timeout) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">send_and_receive(SSH, ChannelId, Data, End, Timeout) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Equivalent to send_and_receive(SSH, ChannelId, 0, Data, End,
Timeout).</fsummary>
<desc><marker id="send_and_receive-5"/>
@@ -959,7 +959,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>send_and_receive(SSH, ChannelId, Type, Data, End, Timeout) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">send_and_receive(SSH, ChannelId, Type, Data, End, Timeout) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Sends data to server on specified session channel and waits
to receive the server response.</fsummary>
<type>
@@ -981,7 +981,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>session_close(SSH, ChannelId) -&gt; ok | {error, Reason}</name>
+ <name since="">session_close(SSH, ChannelId) -&gt; ok | {error, Reason}</name>
<fsummary>Closes an SSH session channel.</fsummary>
<type>
<v>SSH = connection()</v>
@@ -994,7 +994,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>session_open(SSH) -&gt; {ok, ChannelId} | {error, Reason}</name>
+ <name since="">session_open(SSH) -&gt; {ok, ChannelId} | {error, Reason}</name>
<fsummary>Equivalent to session_open(SSH, DefaultTimeout).</fsummary>
<desc><marker id="session_open-1"/>
<p>Equivalent to
@@ -1004,7 +1004,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>session_open(SSH, Timeout) -&gt; {ok, ChannelId} | {error, Reason}</name>
+ <name since="">session_open(SSH, Timeout) -&gt; {ok, ChannelId} | {error, Reason}</name>
<fsummary>Opens a channel for an SSH session.</fsummary>
<type>
<v>SSH = connection()</v>
@@ -1018,7 +1018,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>sftp_connect(SSH) -&gt; {ok, Server} | {error, Reason}</name>
+ <name since="">sftp_connect(SSH) -&gt; {ok, Server} | {error, Reason}</name>
<fsummary>Starts an SFTP session on an already existing SSH
connection.</fsummary>
<type>
@@ -1034,7 +1034,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>shell(SSH, ChannelId) -&gt; ok | {error, Reason}</name>
+ <name since="OTP 20.0">shell(SSH, ChannelId) -&gt; ok | {error, Reason}</name>
<fsummary>Equivalent to shell(SSH, ChannelId, DefaultTimeout).</fsummary>
<desc><marker id="shell-2"/>
<p>Equivalent to
@@ -1044,7 +1044,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>shell(SSH, ChannelId, Timeout) -&gt; ok | {error, Reason}</name>
+ <name since="OTP 20.0">shell(SSH, ChannelId, Timeout) -&gt; ok | {error, Reason}</name>
<fsummary>Requests that the user default shell is executed at the
server end.</fsummary>
<type>
@@ -1061,7 +1061,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>subsystem(SSH, ChannelId, Subsystem) -&gt; Status | {error, Reason}</name>
+ <name since="">subsystem(SSH, ChannelId, Subsystem) -&gt; Status | {error, Reason}</name>
<fsummary>Equivalent to subsystem(SSH, ChannelId, Subsystem,
DefaultTimeout).</fsummary>
<desc><marker id="subsystem-3"/>
@@ -1072,7 +1072,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>subsystem(SSH, ChannelId, Subsystem, Timeout) -&gt; Status | {error, Reason}</name>
+ <name since="">subsystem(SSH, ChannelId, Subsystem, Timeout) -&gt; Status | {error, Reason}</name>
<fsummary>Sends a request to execute a predefined subsystem.</fsummary>
<type>
<v>SSH = connection()</v>
@@ -1088,7 +1088,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>write(SSH, Handle, Data) -&gt; Result</name>
+ <name since="">write(SSH, Handle, Data) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -1102,7 +1102,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>write(SSH, Server, Handle, Data) -&gt; Result</name>
+ <name since="">write(SSH, Server, Handle, Data) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -1116,7 +1116,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>write_file(SSH, File, Iolist) -&gt; Result</name>
+ <name since="">write_file(SSH, File, Iolist) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -1130,7 +1130,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>write_file(SSH, Server, File, Iolist) -&gt; Result</name>
+ <name since="">write_file(SSH, Server, File, Iolist) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -1144,7 +1144,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>write_file_info(SSH, Name, Info) -&gt; Result</name>
+ <name since="">write_file_info(SSH, Name, Info) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -1158,7 +1158,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>write_file_info(SSH, Server, Name, Info) -&gt; Result</name>
+ <name since="">write_file_info(SSH, Server, Name, Info) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
diff --git a/lib/common_test/doc/src/ct_telnet.xml b/lib/common_test/doc/src/ct_telnet.xml
index 8e85cccc99..76f5305c46 100644
--- a/lib/common_test/doc/src/ct_telnet.xml
+++ b/lib/common_test/doc/src/ct_telnet.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>ct_telnet.xml</file>
</header>
- <module>ct_telnet</module>
+ <module since="">ct_telnet</module>
<modulesummary>Common Test specific layer on top of Telnet client ct_telnet_client.erl</modulesummary>
<description>
@@ -205,7 +205,7 @@
<funcs>
<func>
- <name>close(Connection) -&gt; ok | {error, Reason}</name>
+ <name since="">close(Connection) -&gt; ok | {error, Reason}</name>
<fsummary>Closes the Telnet connection and stops the process managing
it.</fsummary>
<type>
@@ -223,7 +223,7 @@
</func>
<func>
- <name>cmd(Connection, Cmd) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">cmd(Connection, Cmd) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Equivalent to cmd(Connection, Cmd, []).</fsummary>
<desc><marker id="cmd-2"/>
<p>Equivalent to
@@ -233,24 +233,27 @@
</func>
<func>
- <name>cmd(Connection, Cmd, Opts) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">cmd(Connection, Cmd, Opts) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Sends a command through Telnet and waits for prompt.</fsummary>
<type>
<v>Connection = connection()</v>
<v>Cmd = string()</v>
<v>Opts = [Opt]</v>
- <v>Opt = {timeout, timeout()} | {newline, boolean()}</v>
+ <v>Opt = {timeout, timeout()} | {newline, boolean() | string()}</v>
<v>Data = [string()]</v>
<v>Reason = term()</v>
</type>
<desc><marker id="cmd-3"/>
<p>Sends a command through Telnet and waits for prompt.</p>
- <p>By default, this function adds a new line to the end of the
+ <p>By default, this function adds "\n" to the end of the
specified command. If this is not desired, use option
<c>{newline,false}</c>. This is necessary, for example, when
sending Telnet command sequences prefixed with character
- Interprete As Command (IAC).</p>
+ Interpret As Command (IAC). Option <c>{newline,string()}</c>
+ can also be used if a different line end than "\n" is
+ required, for instance <c>{newline,"\r\n"}</c>, to add both
+ carriage return and newline characters.</p>
<p>Option <c>timeout</c> specifies how long the client must wait
for prompt. If the time expires, the function returns
@@ -262,7 +265,7 @@
</func>
<func>
- <name>cmdf(Connection, CmdFormat, Args) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">cmdf(Connection, CmdFormat, Args) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Equivalent to cmdf(Connection, CmdFormat, Args, []).</fsummary>
<desc><marker id="cmdf-3"/>
<p>Equivalent to
@@ -272,7 +275,7 @@
</func>
<func>
- <name>cmdf(Connection, CmdFormat, Args, Opts) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">cmdf(Connection, CmdFormat, Args, Opts) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Sends a Telnet command and waits for prompt (uses a format
string and a list of arguments to build the command).</fsummary>
<type>
@@ -280,7 +283,7 @@
<v>CmdFormat = string()</v>
<v>Args = list()</v>
<v>Opts = [Opt]</v>
- <v>Opt = {timeout, timeout()} | {newline, boolean()}</v>
+ <v>Opt = {timeout, timeout()} | {newline, boolean() | string()}</v>
<v>Data = [string()]</v>
<v>Reason = term()</v>
</type>
@@ -294,7 +297,7 @@
</func>
<func>
- <name>expect(Connection, Patterns) -&gt; term()</name>
+ <name since="">expect(Connection, Patterns) -&gt; term()</name>
<fsummary>Equivalent to expect(Connections, Patterns, []).</fsummary>
<desc><marker id="expect-2"/>
<p>Equivalent to
@@ -304,7 +307,7 @@
</func>
<func>
- <name>expect(Connection, Patterns, Opts) -&gt; {ok, Match} | {ok, MatchList, HaltReason} | {error, Reason}</name>
+ <name since="">expect(Connection, Patterns, Opts) -&gt; {ok, Match} | {ok, MatchList, HaltReason} | {error, Reason}</name>
<fsummary>Gets data from Telnet and waits for the expected
pattern.</fsummary>
<type>
@@ -339,7 +342,7 @@
subexpression number <c>N</c>. Subexpressions are denoted with
<c>'(' ')'</c> in the regular expression.</p>
- <p>If a <c>Tag</c> is speciifed, the returned <c>Match</c> also
+ <p>If a <c>Tag</c> is specified, the returned <c>Match</c> also
includes the matched <c>Tag</c>. Otherwise, only <c>RxMatch</c>
is returned.</p>
@@ -382,7 +385,7 @@
can abort the operation of waiting for prompt.</p></item>
<tag><c>repeat | repeat, N</c></tag>
<item><p>The pattern(s) must be matched multiple times. If <c>N</c>
- is speciified, the pattern(s) are matched <c>N</c> times, and
+ is specified, the pattern(s) are matched <c>N</c> times, and
the function returns <c>HaltReason = done</c>. This option can be
interrupted by one or more <c>HaltPatterns</c>. <c>MatchList</c>
is always returned, that is, a list of <c>Match</c> instead of
@@ -422,7 +425,7 @@
</func>
<func>
- <name>get_data(Connection) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">get_data(Connection) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Gets all data received by the Telnet client since the last
command was sent.</fsummary>
<type>
@@ -446,7 +449,7 @@
</func>
<func>
- <name>open(Name) -&gt; {ok, Handle} | {error, Reason}</name>
+ <name since="">open(Name) -&gt; {ok, Handle} | {error, Reason}</name>
<fsummary>Equivalent to open(Name, telnet).</fsummary>
<desc><marker id="open-1"/>
<p>Equivalent to
@@ -456,7 +459,7 @@
</func>
<func>
- <name>open(Name, ConnType) -&gt; {ok, Handle} | {error, Reason}</name>
+ <name since="">open(Name, ConnType) -&gt; {ok, Handle} | {error, Reason}</name>
<fsummary>Opens a Telnet connection to the specified target
host.</fsummary>
<type>
@@ -471,7 +474,7 @@
</func>
<func>
- <name>open(KeyOrName, ConnType, TargetMod) -&gt; {ok, Handle} | {error, Reason}</name>
+ <name since="">open(KeyOrName, ConnType, TargetMod) -&gt; {ok, Handle} | {error, Reason}</name>
<fsummary>Equivalent to open(KeyOrName, ConnType, TargetMod, []).</fsummary>
<desc><marker id="open-3"/>
<p>Equivalent to
@@ -481,7 +484,7 @@
</func>
<func>
- <name>open(KeyOrName, ConnType, TargetMod, Extra) -&gt; {ok, Handle} | {error, Reason}</name>
+ <name since="">open(KeyOrName, ConnType, TargetMod, Extra) -&gt; {ok, Handle} | {error, Reason}</name>
<fsummary>Opens a Telnet connection to the specified target
host.</fsummary>
<type>
@@ -531,7 +534,7 @@
</func>
<func>
- <name>send(Connection, Cmd) -&gt; ok | {error, Reason}</name>
+ <name since="">send(Connection, Cmd) -&gt; ok | {error, Reason}</name>
<fsummary>Equivalent to send(Connection, Cmd, []).</fsummary>
<desc><marker id="send-2"/>
<p>Equivalent to
@@ -541,23 +544,26 @@
</func>
<func>
- <name>send(Connection, Cmd, Opts) -&gt; ok | {error, Reason}</name>
+ <name since="OTP 17.4">send(Connection, Cmd, Opts) -&gt; ok | {error, Reason}</name>
<fsummary>Sends a Telnet command and returns immediately.</fsummary>
<type>
<v>Connection = connection()</v>
<v>Cmd = string()</v>
<v>Opts = [Opt]</v>
- <v>Opt = {newline, boolean()}</v>
+ <v>Opt = {newline, boolean() | string()}</v>
<v>Reason = term()</v>
</type>
<desc><marker id="send-3"/>
<p>Sends a Telnet command and returns immediately.</p>
- <p>By default, this function adds a newline to the end of the
+ <p>By default, this function adds "\n" to the end of the
specified command. If this is not desired, option
<c>{newline,false}</c> can be used. This is necessary, for example,
when sending Telnet command sequences prefixed with character
- Interprete As Command (IAC).</p>
+ Interpret As Command (IAC). Option <c>{newline,string()}</c>
+ can also be used if a different line end than "\n" is
+ required, for instance <c>{newline,"\r\n"}</c>, to add both
+ carriage return and newline characters.</p>
<p>The resulting output from the command can be read with
<seealso marker="#get_data-1"><c>ct_telnet:get_data/2</c></seealso> or
@@ -566,7 +572,7 @@
</func>
<func>
- <name>sendf(Connection, CmdFormat, Args) -&gt; ok | {error, Reason}</name>
+ <name since="">sendf(Connection, CmdFormat, Args) -&gt; ok | {error, Reason}</name>
<fsummary>Equivalent to sendf(Connection, CmdFormat, Args, []).</fsummary>
<desc><marker id="sendf-3"/>
<p>Equivalent to
@@ -576,7 +582,7 @@
</func>
<func>
- <name>sendf(Connection, CmdFormat, Args, Opts) -&gt; ok | {error, Reason}</name>
+ <name since="OTP 17.4">sendf(Connection, CmdFormat, Args, Opts) -&gt; ok | {error, Reason}</name>
<fsummary>Sends a Telnet command and returns immediately (uses a format
string and a list of arguments to build the command).</fsummary>
<type>
@@ -584,12 +590,15 @@
<v>CmdFormat = string()</v>
<v>Args = list()</v>
<v>Opts = [Opt]</v>
- <v>Opt = {newline, boolean()}</v>
+ <v>Opt = {newline, boolean() | string()}</v>
<v>Reason = term()</v>
</type>
<desc><marker id="sendf-4"/>
<p>Sends a Telnet command and returns immediately (uses a format
string and a list of arguments to build the command).</p>
+
+ <p>For details, see
+ <seealso marker="#send-3"><c>ct_telnet:send/3</c></seealso>.</p>
</desc>
</func>
</funcs>
diff --git a/lib/common_test/doc/src/ct_testspec.xml b/lib/common_test/doc/src/ct_testspec.xml
index 36893f66cf..9cb9a2ae9f 100644
--- a/lib/common_test/doc/src/ct_testspec.xml
+++ b/lib/common_test/doc/src/ct_testspec.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>ct_testspec.xml</file>
</header>
- <module>ct_testspec</module>
+ <module since="OTP 19.3">ct_testspec</module>
<modulesummary>Parsing of test specifications for Common Test.
</modulesummary>
@@ -46,7 +46,7 @@
<funcs>
<func>
- <name>get_tests(SpecsIn) -&gt; {ok, [{Specs,Tests}]} | {error, Reason}</name>
+ <name since="OTP 19.3">get_tests(SpecsIn) -&gt; {ok, [{Specs,Tests}]} | {error, Reason}</name>
<fsummary>Parse the given test specification files and return the tests to run and skip.</fsummary>
<type>
<v>SpecsIn = [string()] | [[string()]]</v>
diff --git a/lib/common_test/doc/src/notes.xml b/lib/common_test/doc/src/notes.xml
index 118dcd88bd..2f53f1c29e 100644
--- a/lib/common_test/doc/src/notes.xml
+++ b/lib/common_test/doc/src/notes.xml
@@ -75,6 +75,44 @@
</section>
+<section><title>Common_Test 1.15.4.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The status of a test case which failed with timetrap
+ timeout in <c>end_per_testcase</c> could not be modified
+ by returning <c>{fail,Reason}</c> from a
+ <c>post_end_per_testcase</c> hook function. This is now
+ corrected.</p>
+ <p>
+ Own Id: OTP-15584 Aux Id: ERIERL-282 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Common_Test 1.15.4.0.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The status of a test case which failed with timetrap
+ timeout in <c>end_per_testcase</c> could not be modified
+ by returning <c>{fail,Reason}</c> from a
+ <c>post_end_per_testcase</c> hook function. This is now
+ corrected.</p>
+ <p>
+ Own Id: OTP-15584 Aux Id: ERIERL-282 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Common_Test 1.15.4</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -2109,7 +2147,7 @@
ct_netconfc:close_session sometimes returned
{error,closed} because the ssh connection was closed
(from the server side) before the rpc-reply was received
- by the client. This is normal and can not be helped. It
+ by the client. This is normal and cannot be helped. It
has been corrected so the return will be 'ok' in this
case. Other error situations will still give
{error,Reason}.</p>
@@ -2123,7 +2161,7 @@
{error,{process_down,Pid,normal}} because the ssh
connection was closed (from the server side) before the
rpc-reply was received by the client. This is normal and
- can not be helped. It has been corrected so the return
+ cannot be helped. It has been corrected so the return
will be 'ok' in this situation.</p>
<p>
Own Id: OTP-10570</p>
@@ -2775,8 +2813,8 @@
The info function for <c>init/end_per_suite(Config)</c>
is <c>init/end_per_suite()</c>, and for
<c>init/end_per_group(GroupName,Config)</c> it's
- <c>init/end_per_group(GroupName)</c>. Info functions can
- not be used with <c>init/end_per_testcase(TestCase,
+ <c>init/end_per_group(GroupName)</c>. Info functions
+ cannot be used with <c>init/end_per_testcase(TestCase,
Config)</c>, since these configuration functions execute
on the test case process and will use the same properties
as the test case (i.e. properties set by the test case
@@ -3860,7 +3898,7 @@
If the Erlang runtime system was started without access
to an erlang shell (e.g. -noshell), compilation errors
would cause a crash in the Common Test application.
- Without access to a shell, Common Test can not prompt the
+ Without access to a shell, Common Test cannot prompt the
user to choose to continue or abort the test session, but
must assume that the session should proceed.</p>
<p>
@@ -4026,8 +4064,3 @@
<section><title>common_test 1.3.0</title>
</section>
</chapter>
-
-
-
-
-
diff --git a/lib/common_test/doc/src/unix_telnet.xml b/lib/common_test/doc/src/unix_telnet.xml
index b2314a53ec..03d91b7dbe 100644
--- a/lib/common_test/doc/src/unix_telnet.xml
+++ b/lib/common_test/doc/src/unix_telnet.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>unix_telnet.xml</file>
</header>
- <module>unix_telnet</module>
+ <module since="">unix_telnet</module>
<modulesummary>Callback module for ct_telnet, for connecting to a Telnet
server on a UNIX host.</modulesummary>
@@ -80,7 +80,7 @@
<funcs>
<func>
- <name>connect(ConnName, Ip, Port, Timeout, KeepAlive, TCPNoDelay, Extra) -&gt; {ok, Handle} | {error, Reason}</name>
+ <name since="OTP 18.3.3">connect(ConnName, Ip, Port, Timeout, KeepAlive, TCPNoDelay, Extra) -&gt; {ok, Handle} | {error, Reason}</name>
<fsummary>Callback for ct_telnet.erl.</fsummary>
<type>
<v>ConnName = target_name()</v>
@@ -107,7 +107,7 @@
</func>
<func>
- <name>get_prompt_regexp() -&gt; PromptRegexp</name>
+ <name since="">get_prompt_regexp() -&gt; PromptRegexp</name>
<fsummary>Callback for ct_telnet.erl.</fsummary>
<type>
<v>PromptRegexp = prompt_regexp()</v>
diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl
index 778ea2e9e2..bfa7b25862 100644
--- a/lib/common_test/src/ct.erl
+++ b/lib/common_test/src/ct.erl
@@ -390,11 +390,7 @@ testcases(TestDir, Suite) ->
end.
make_and_load(Dir, Suite) ->
- EnvInclude =
- case os:getenv("CT_INCLUDE_PATH") of
- false -> [];
- CtInclPath -> string:lexemes(CtInclPath, [$:,$ ,$,])
- end,
+ EnvInclude = string:lexemes(os:getenv("CT_INCLUDE_PATH", ""), [$:,$ ,$,]),
StartInclude =
case init:get_argument(include) of
{ok,[Dirs]} -> Dirs;
diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl
index a10d939919..a07e61199b 100644
--- a/lib/common_test/src/ct_config.erl
+++ b/lib/common_test/src/ct_config.erl
@@ -592,7 +592,7 @@ encrypt_config_file(SrcFileName, EncryptFileName, {file,KeyFile}) ->
encrypt_config_file(SrcFileName, EncryptFileName, {key,Key}) ->
_ = crypto:start(),
- {Key,IVec} = make_crypto_key(Key),
+ {CryptoKey,IVec} = make_crypto_key(Key),
case file:read_file(SrcFileName) of
{ok,Bin0} ->
Bin1 = term_to_binary({SrcFileName,Bin0}),
@@ -600,7 +600,7 @@ encrypt_config_file(SrcFileName, EncryptFileName, {key,Key}) ->
0 -> Bin1;
N -> list_to_binary([Bin1,random_bytes(8-N)])
end,
- EncBin = crypto:block_encrypt(des3_cbc, Key, IVec, Bin2),
+ EncBin = crypto:block_encrypt(des3_cbc, CryptoKey, IVec, Bin2),
case file:write_file(EncryptFileName, EncBin) of
ok ->
io:format("~ts --(encrypt)--> ~ts~n",
@@ -631,10 +631,10 @@ decrypt_config_file(EncryptFileName, TargetFileName, {file,KeyFile}) ->
decrypt_config_file(EncryptFileName, TargetFileName, {key,Key}) ->
_ = crypto:start(),
- {Key,IVec} = make_crypto_key(Key),
+ {CryptoKey,IVec} = make_crypto_key(Key),
case file:read_file(EncryptFileName) of
{ok,Bin} ->
- DecBin = crypto:block_decrypt(des3_cbc, Key, IVec, Bin),
+ DecBin = crypto:block_decrypt(des3_cbc, CryptoKey, IVec, Bin),
case catch binary_to_term(DecBin) of
{'EXIT',_} ->
{error,bad_file};
diff --git a/lib/common_test/src/ct_cover.erl b/lib/common_test/src/ct_cover.erl
index d286f20a4d..bcd98dcc58 100644
--- a/lib/common_test/src/ct_cover.erl
+++ b/lib/common_test/src/ct_cover.erl
@@ -262,6 +262,11 @@ get_app_info(App=#cover{app=Name}, [{src_files,Name,Src1}|Terms], Dir) ->
Src = App#cover.src,
get_app_info(App#cover{src=Src++Src1},Terms,Dir);
+get_app_info(App=#cover{app=none}, [{local_only,Bool}|Terms], Dir) ->
+ get_app_info(App, [{local_only,none,Bool}|Terms], Dir);
+get_app_info(App=#cover{app=Name}, [{local_only,Name,Bool}|Terms], Dir) ->
+ get_app_info(App#cover{local_only=Bool},Terms,Dir);
+
get_app_info(App, [_|Terms], Dir) ->
get_app_info(App, Terms, Dir);
diff --git a/lib/common_test/src/ct_framework.erl b/lib/common_test/src/ct_framework.erl
index 134ae0e1cc..506147474f 100644
--- a/lib/common_test/src/ct_framework.erl
+++ b/lib/common_test/src/ct_framework.erl
@@ -314,8 +314,8 @@ add_defaults(Mod,Func, GroupPath) ->
ErrStr = io_lib:format("~n*** ERROR *** "
"~w:suite/0 failed: ~tp~n",
[Suite,Reason]),
- io:format(ErrStr, []),
- io:format(?def_gl, ErrStr, []),
+ io:format("~ts", [ErrStr]),
+ io:format(?def_gl, "~ts", [ErrStr]),
{suite0_failed,{exited,Reason}};
SuiteInfo when is_list(SuiteInfo) ->
case lists:all(fun(E) when is_tuple(E) -> true;
@@ -337,16 +337,16 @@ add_defaults(Mod,Func, GroupPath) ->
"Invalid return value from "
"~w:suite/0: ~tp~n",
[Suite,SuiteInfo]),
- io:format(ErrStr, []),
- io:format(?def_gl, ErrStr, []),
+ io:format("~ts", [ErrStr]),
+ io:format(?def_gl, "~ts", [ErrStr]),
{suite0_failed,bad_return_value}
end;
SuiteInfo ->
ErrStr = io_lib:format("~n*** ERROR *** "
"Invalid return value from "
"~w:suite/0: ~tp~n", [Suite,SuiteInfo]),
- io:format(ErrStr, []),
- io:format(?def_gl, ErrStr, []),
+ io:format("~ts", [ErrStr]),
+ io:format(?def_gl, "~ts", [ErrStr]),
{suite0_failed,bad_return_value}
end.
@@ -373,8 +373,8 @@ add_defaults1(Mod,Func, GroupPath, SuiteInfo) ->
"Invalid return value from "
"~w:group(~tw): ~tp~n",
[Mod,GrName,BadGr0Val]),
- io:format(Gr0ErrStr, []),
- io:format(?def_gl, Gr0ErrStr, []),
+ io:format("~ts", [Gr0ErrStr]),
+ io:format(?def_gl, "~ts", [Gr0ErrStr]),
{group0_failed,bad_return_value};
_ ->
Args = if Func == init_per_group ; Func == end_per_group ->
@@ -395,8 +395,8 @@ add_defaults1(Mod,Func, GroupPath, SuiteInfo) ->
"Invalid return value from "
"~w:~tw/0: ~tp~n",
[Mod,Func,BadTC0Val]),
- io:format(TC0ErrStr, []),
- io:format(?def_gl, TC0ErrStr, []),
+ io:format("~ts", [TC0ErrStr]),
+ io:format(?def_gl, "~ts", [TC0ErrStr]),
{testcase0_failed,bad_return_value};
_ ->
%% let test case info (also for all config funcs) override
@@ -972,11 +972,10 @@ error_notification(Mod,Func,_Args,{Error,Loc}) ->
end,
PrintError = fun(ErrorFormat, ErrorArgs) ->
- Div = "~n- - - - - - - - - - - - - - - - - - - "
- "- - - - - - - - - - - - - - - - - - - - -~n",
+ Div = "\n- - - - - - - - - - - - - - - - - - - "
+ "- - - - - - - - - - - - - - - - - - - - -\n",
ErrorStr2 = io_lib:format(ErrorFormat, ErrorArgs),
- io:format(?def_gl, lists:concat([Div,ErrorStr2,Div,"~n"]),
- []),
+ io:format(?def_gl, "~ts~n", [lists:concat([Div,ErrorStr2,Div])]),
Link =
"\n\n<a href=\"#end\">"
"Full error description and stacktrace"
@@ -985,7 +984,8 @@ error_notification(Mod,Func,_Args,{Error,Loc}) ->
ct_logs:tc_log(ct_error_notify,
?MAX_IMPORTANCE,
"CT Error Notification",
- ErrorHtml2++Link, [], [])
+ "~ts", [ErrorHtml2++Link],
+ [])
end,
case Loc of
[{?MODULE,error_in_suite}] ->
@@ -1168,7 +1168,7 @@ get_all(Mod, ConfTests) ->
case code:which(Mod) of
non_existing ->
list_to_atom(atom_to_list(Mod)++
- " can not be compiled or loaded");
+ " cannot be compiled or loaded");
_ ->
list_to_atom(atom_to_list(Mod)++":all/0 is missing")
end,
@@ -1181,7 +1181,7 @@ get_all(Mod, ConfTests) ->
ErrStr = io_lib:format("~n*** ERROR *** "
"~w:all/0 failed: ~tp~n",
[Mod,ExitReason]),
- io:format(?def_gl, ErrStr, []),
+ io:format(?def_gl, "~ts", [ErrStr]),
%% save the error info so it doesn't get printed twice
ct_util:set_testdata_async({{error_in_suite,Mod},
ExitReason});
diff --git a/lib/common_test/src/ct_logs.erl b/lib/common_test/src/ct_logs.erl
index 07a1693d5d..814b80b8bd 100644
--- a/lib/common_test/src/ct_logs.erl
+++ b/lib/common_test/src/ct_logs.erl
@@ -542,7 +542,7 @@ tc_print(Category,Importance,Format,Args,Opts) ->
undefined -> atom_to_list(Category);
Hd -> Hd
end,
- Str = lists:concat([get_header(Heading),Format,"\n\n"]),
+ Str = lists:flatten([get_header(Heading),Format,"\n\n"]),
try
io:format(?def_gl, Str, Args)
catch
@@ -935,7 +935,7 @@ create_io_fun(FromPid, CtLogFd, EscChars) ->
{_HdOrFt,S,A} -> {false,S,A};
{S,A} -> {true,S,A}
end,
- try io_lib:format(Str, Args) of
+ try io_lib:format(lists:flatten(Str), Args) of
IoStr when Escapable, EscChars, IoList == [] ->
escape_chars(IoStr);
IoStr when Escapable, EscChars ->
@@ -1138,10 +1138,10 @@ set_evmgr_gl(GL) ->
open_ctlog(MiscIoName) ->
{ok,Fd} = file:open(?ct_log_name,[write,{encoding,utf8}]),
- io:format(Fd, header("Common Test Framework Log", {[],[1,2],[]}), []),
+ io:format(Fd, "~ts", [header("Common Test Framework Log", {[],[1,2],[]})]),
case file:consult(ct_run:variables_file_name("../")) of
{ok,Vars} ->
- io:format(Fd, config_table(Vars), []);
+ io:format(Fd, "~ts", [config_table(Vars)]);
{error,Reason} ->
{ok,Cwd} = file:get_cwd(),
Dir = filename:dirname(Cwd),
@@ -1213,7 +1213,7 @@ print_style_error(Fd, IoFormat, StyleSheet, Reason) ->
close_ctlog(Fd) ->
io:format(Fd, "\n</pre>\n", []),
- io:format(Fd, [xhtml("<br><br>\n", "<br /><br />\n") | footer()], []),
+ io:format(Fd, "~ts", [[xhtml("<br><br>\n", "<br /><br />\n") | footer()]]),
ok = file:close(Fd).
%%%-----------------------------------------------------------------
diff --git a/lib/common_test/src/ct_master.erl b/lib/common_test/src/ct_master.erl
index fd33ee2280..9fc169789c 100644
--- a/lib/common_test/src/ct_master.erl
+++ b/lib/common_test/src/ct_master.erl
@@ -350,7 +350,7 @@ master_loop(#state{node_ctrl_pids=[],
io_lib:format("~-40.40.*ts~tp\n",
[$_,atom_to_list(Node),Result])
end,lists:reverse(Finished)),
- log(all,"TEST RESULTS",Str,[]),
+ log(all,"TEST RESULTS","~ts", [Str]),
log(all,"Info","Updating log files",[]),
refresh_logs(LogDirs,[]),
@@ -574,7 +574,7 @@ refresh_logs([],Refreshed) ->
io_lib:format("Refreshing logs in ~tp... ~tp",
[D,Result])
end,Refreshed),
- log(all,"Info",Str,[]).
+ log(all,"Info","~ts", [Str]).
%%%-----------------------------------------------------------------
%%% NODE CONTROLLER, runs and controls tests on a test node.
diff --git a/lib/common_test/src/ct_netconfc.erl b/lib/common_test/src/ct_netconfc.erl
index 29188a648e..6a758c4ea3 100644
--- a/lib/common_test/src/ct_netconfc.erl
+++ b/lib/common_test/src/ct_netconfc.erl
@@ -583,7 +583,7 @@ get_config(Client, Source, Filter, Timeout) ->
-spec edit_config(Client, Target, Config) -> Result when
Client :: client(),
Target :: netconf_db(),
- Config :: simple_xml(),
+ Config :: simple_xml() | [simple_xml()],
Result :: ok | {error,error_reason()}.
edit_config(Client, Target, Config) ->
edit_config(Client, Target, Config, ?DEFAULT_TIMEOUT).
@@ -591,7 +591,7 @@ edit_config(Client, Target, Config) ->
-spec edit_config(Client, Target, Config, OptParams) -> Result when
Client :: client(),
Target :: netconf_db(),
- Config :: simple_xml(),
+ Config :: simple_xml() | [simple_xml()],
OptParams :: [simple_xml()],
Result :: ok | {error,error_reason()};
(Client, Target, Config, Timeout) -> Result when
@@ -608,10 +608,12 @@ edit_config(Client, Target, Config, OptParams) when is_list(OptParams) ->
-spec edit_config(Client, Target, Config, OptParams, Timeout) -> Result when
Client :: client(),
Target :: netconf_db(),
- Config :: simple_xml(),
+ Config :: simple_xml() | [simple_xml()],
OptParams :: [simple_xml()],
Timeout :: timeout(),
Result :: ok | {error,error_reason()}.
+edit_config(Client, Target, Config, OptParams, Timeout) when not is_list(Config)->
+ edit_config(Client, Target, [Config], OptParams, Timeout);
edit_config(Client, Target, Config, OptParams, Timeout) ->
call(Client, {send_rpc_op, edit_config, [Target,Config,OptParams], Timeout}).
@@ -1113,7 +1115,7 @@ encode_rpc_operation(get,[Filter]) ->
encode_rpc_operation(get_config,[Source,Filter]) ->
{'get-config',[{source,[Source]}] ++ filter(Filter)};
encode_rpc_operation(edit_config,[Target,Config,OptParams]) ->
- {'edit-config',[{target,[Target]}] ++ OptParams ++ [{config,[Config]}]};
+ {'edit-config',[{target,[Target]}] ++ OptParams ++ [{config,Config}]};
encode_rpc_operation(delete_config,[Target]) ->
{'delete-config',[{target,[Target]}]};
encode_rpc_operation(copy_config,[Target,Source]) ->
diff --git a/lib/common_test/src/ct_release_test.erl b/lib/common_test/src/ct_release_test.erl
index 60d17f43dc..ac3dcab7c9 100644
--- a/lib/common_test/src/ct_release_test.erl
+++ b/lib/common_test/src/ct_release_test.erl
@@ -152,10 +152,10 @@
%% returned configuration must therefore also be returned from
%% the calling `init_per_*'.
%%
-%% If the initialization fails, e.g. if a required release can
-%% not be found, the function returns `{skip,Reason}'. In
+%% If the initialization fails, e.g. if a required release
+%% cannot be found, the function returns `{skip,Reason}'. In
%% this case the other test support functions in this mudule
-%% can not be used.
+%% cannot be used.
%%
%% Example:
%%
@@ -426,7 +426,7 @@ init_upgrade_test(Level) ->
case OldRel of
false ->
ct:log("Release ~tp is not available."
- " Upgrade on '~p' level can not be tested.",
+ " Upgrade on '~p' level cannot be tested.",
[FromVsn,Level]),
undefined;
_ ->
@@ -528,7 +528,7 @@ target_system(Apps,CreateDir,InstallDir,{FromVsn,_,AllAppsVsns,Path}) ->
{path,Path}]]),
%% Unpack the tar to complete the installation
- erl_tar:extract(RelName ++ ".tar.gz", [{cwd, InstallDir}, compressed]),
+ ok = erl_tar:extract(RelName ++ ".tar.gz", [{cwd, InstallDir}, compressed]),
%% Add bin and log dirs
BinDir = filename:join([InstallDir, "bin"]),
@@ -554,11 +554,11 @@ target_system(Apps,CreateDir,InstallDir,{FromVsn,_,AllAppsVsns,Path}) ->
%% create start_erl.data, sys.config and start.src
StartErlData = filename:join([InstallDir, "releases", "start_erl.data"]),
- write_file(StartErlData, io_lib:fwrite("~s ~s~n", [ErtsVsn, FromVsn])),
+ ok = write_file(StartErlData, io_lib:fwrite("~s ~s~n", [ErtsVsn, FromVsn])),
SysConfig = filename:join([InstallDir, "releases", FromVsn, "sys.config"]),
- write_file(SysConfig, "[]."),
+ ok = write_file(SysConfig, "[]."),
StartSrc = filename:join(ErtsBinDir,"start.src"),
- write_file(StartSrc,start_script()),
+ ok = write_file(StartSrc,start_script()),
ok = file:change_mode(StartSrc,8#0755),
%% Make start_erl executable
@@ -620,7 +620,7 @@ upgrade_system(Apps, FromRel, CreateDir, InstallDir, {_,ToVsn,_,_}) ->
[{path,[FromPath]},
{outdir,CreateDir}]]),
SysConfig = filename:join([CreateDir, "sys.config"]),
- write_file(SysConfig, "[]."),
+ ok = write_file(SysConfig, "[]."),
ok = systools(make_tar,[RelName,[{erts,code:root_dir()}]]),
@@ -858,7 +858,7 @@ subst_file(Src, Dest, Vars, Opts) ->
{ok, Bin} = file:read_file(Src),
Conts = unicode:characters_to_list(Bin),
NConts = subst(Conts, Vars),
- write_file(Dest, NConts),
+ ok = write_file(Dest, NConts),
case lists:member(preserve, Opts) of
true ->
{ok, FileInfo} = file:read_file_info(Src),
diff --git a/lib/common_test/src/ct_repeat.erl b/lib/common_test/src/ct_repeat.erl
index 8b1c7d47bb..b97c6e59e7 100644
--- a/lib/common_test/src/ct_repeat.erl
+++ b/lib/common_test/src/ct_repeat.erl
@@ -278,7 +278,7 @@ log_loop_info(Args) ->
ForceStop ->
io_lib:format("force_stop is set to: ~w",[ForceStop])
end,
- ct_logs:log("Test loop info",LogStr1++LogStr2++LogStr3++LogStr4,[])
+ ct_logs:log("Test loop info","~ts", [LogStr1++LogStr2++LogStr3++LogStr4])
end.
ts(Secs) ->
diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl
index c9d406f1fd..960252a6fe 100644
--- a/lib/common_test/src/ct_run.erl
+++ b/lib/common_test/src/ct_run.erl
@@ -2345,18 +2345,24 @@ start_cover(Opts=#opts{coverspec=CovData,cover_stop=CovStop},LogDir) ->
CovImport,
_CovExport,
#cover{app = CovApp,
+ local_only = LocalOnly,
level = CovLevel,
excl_mods = CovExcl,
incl_mods = CovIncl,
cross = CovCross,
src = _CovSrc}} = CovData,
+ case LocalOnly of
+ true -> cover:local_only();
+ false -> ok
+ end,
ct_logs:log("COVER INFO",
"Using cover specification file: ~ts~n"
"App: ~w~n"
+ "Local only: ~w~n"
"Cross cover: ~w~n"
"Including ~w modules~n"
"Excluding ~w modules",
- [CovFile,CovApp,CovCross,
+ [CovFile,CovApp,LocalOnly,CovCross,
length(CovIncl),length(CovExcl)]),
%% Tell test_server to print a link in its coverlog
diff --git a/lib/common_test/src/ct_telnet.erl b/lib/common_test/src/ct_telnet.erl
index f9abecfd38..219f58dcf5 100644
--- a/lib/common_test/src/ct_telnet.erl
+++ b/lib/common_test/src/ct_telnet.erl
@@ -194,6 +194,15 @@ send(Connection,Cmd,Opts) ->
check_send_opts([{newline,Bool}|Opts]) when is_boolean(Bool) ->
check_send_opts(Opts);
+check_send_opts([{newline,String}|Opts]) when is_list(String) ->
+ case lists:all(fun(I) when is_integer(I), I>=0, I=<127 -> true;
+ (_) -> false
+ end, String) of
+ true ->
+ check_send_opts(Opts);
+ false ->
+ {error,{invalid_option,{newline,String}}}
+ end;
check_send_opts([Invalid|_]) ->
{error,{invalid_option,Invalid}};
check_send_opts([]) ->
@@ -211,10 +220,16 @@ expect(Connection,Patterns) ->
expect(Connection,Patterns,Opts) ->
case get_handle(Connection) of
- {ok,Pid} ->
- call(Pid,{expect,Patterns,Opts});
- Error ->
- Error
+ {ok,Pid} ->
+ case call(Pid,{expect,Patterns,Opts}) of
+ {error,Reason} when element(1,Reason)==bad_pattern ->
+ %% Faulty user input - should fail the test case
+ exit({Reason,{?MODULE,?FUNCTION_NAME,3}});
+ Other ->
+ Other
+ end;
+ Error ->
+ Error
end.
%%%=================================================================
@@ -674,60 +689,68 @@ silent_teln_expect(Name,Pid,Data,Pattern,Prx,Opts) ->
%% 3b) Repeat (sequence): 2) is repeated either N times or until a
%% halt condition is fulfilled.
teln_expect(Name,Pid,Data,Pattern0,Prx,Opts) ->
- HaltPatterns =
+ HaltPatterns0 =
case get_ignore_prompt(Opts) of
true ->
get_haltpatterns(Opts);
false ->
[prompt | get_haltpatterns(Opts)]
end,
-
- PromptCheck = get_prompt_check(Opts),
-
- {WaitForPrompt,Pattern1,Opts1} = wait_for_prompt(Pattern0,Opts),
-
- Seq = get_seq(Opts1),
- Pattern2 = convert_pattern(Pattern1,Seq),
- {IdleTimeout,TotalTimeout} = get_timeouts(Opts1),
-
- EO = #eo{teln_pid=Pid,
- prx=Prx,
- idle_timeout=IdleTimeout,
- total_timeout=TotalTimeout,
- seq=Seq,
- haltpatterns=HaltPatterns,
- prompt_check=PromptCheck},
+ case convert_pattern(HaltPatterns0,false) of
+ {ok,HaltPatterns} ->
+ {WaitForPrompt,Pattern1,Opts1} = wait_for_prompt(Pattern0,Opts),
+ Seq = get_seq(Opts1),
+ case convert_pattern(Pattern1,Seq) of
+ {ok,Pattern2} ->
+ {IdleTimeout,TotalTimeout} = get_timeouts(Opts1),
+ PromptCheck = get_prompt_check(Opts1),
+
+ EO = #eo{teln_pid=Pid,
+ prx=Prx,
+ idle_timeout=IdleTimeout,
+ total_timeout=TotalTimeout,
+ seq=Seq,
+ haltpatterns=HaltPatterns,
+ prompt_check=PromptCheck},
- case get_repeat(Opts1) of
- false ->
- case teln_expect1(Name,Pid,Data,Pattern2,[],EO) of
- {ok,Matched,Rest} when WaitForPrompt ->
- case lists:reverse(Matched) of
- [{prompt,_},Matched1] ->
- {ok,Matched1,Rest};
- [{prompt,_}|Matched1] ->
- {ok,lists:reverse(Matched1),Rest}
- end;
- {ok,Matched,Rest} ->
- {ok,Matched,Rest};
- {halt,Why,Rest} ->
- {error,Why,Rest};
- {error,Reason} ->
- {error,Reason}
- end;
- N ->
- EO1 = EO#eo{repeat=N},
- repeat_expect(Name,Pid,Data,Pattern2,[],EO1)
+ case get_repeat(Opts1) of
+ false ->
+ case teln_expect1(Name,Pid,Data,Pattern2,[],EO) of
+ {ok,Matched,Rest} when WaitForPrompt ->
+ case lists:reverse(Matched) of
+ [{prompt,_},Matched1] ->
+ {ok,Matched1,Rest};
+ [{prompt,_}|Matched1] ->
+ {ok,lists:reverse(Matched1),Rest}
+ end;
+ {ok,Matched,Rest} ->
+ {ok,Matched,Rest};
+ {halt,Why,Rest} ->
+ {error,Why,Rest};
+ {error,Reason} ->
+ {error,Reason}
+ end;
+ N ->
+ EO1 = EO#eo{repeat=N},
+ repeat_expect(Name,Pid,Data,Pattern2,[],EO1)
+ end;
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
end.
-convert_pattern(Pattern,Seq)
- when is_list(Pattern) and not is_integer(hd(Pattern)) ->
- case Seq of
- true -> Pattern;
- false -> rm_dupl(Pattern,[])
- end;
+convert_pattern(Pattern0,Seq)
+ when Pattern0==[] orelse (is_list(Pattern0) and not is_integer(hd(Pattern0))) ->
+ Pattern =
+ case Seq of
+ true -> Pattern0;
+ false -> rm_dupl(Pattern0,[])
+ end,
+ compile_pattern(Pattern,[]);
convert_pattern(Pattern,_Seq) ->
- [Pattern].
+ compile_pattern([Pattern],[]).
rm_dupl([P|Ps],Acc) ->
case lists:member(P,Acc) of
@@ -739,6 +762,25 @@ rm_dupl([P|Ps],Acc) ->
rm_dupl([],Acc) ->
lists:reverse(Acc).
+compile_pattern([prompt|Patterns],Acc) ->
+ compile_pattern(Patterns,[prompt|Acc]);
+compile_pattern([{prompt,_}=P|Patterns],Acc) ->
+ compile_pattern(Patterns,[P|Acc]);
+compile_pattern([{Tag,Pattern}|Patterns],Acc) ->
+ try re:compile(Pattern,[unicode]) of
+ {ok,MP} -> compile_pattern(Patterns,[{Tag,MP}|Acc]);
+ {error,Error} -> {error,{bad_pattern,{Tag,Pattern},Error}}
+ catch error:badarg -> {error,{bad_pattern,{Tag,Pattern}}}
+ end;
+compile_pattern([Pattern|Patterns],Acc) ->
+ try re:compile(Pattern,[unicode]) of
+ {ok,MP} -> compile_pattern(Patterns,[MP|Acc]);
+ {error,Error} -> {error,{bad_pattern,Pattern,Error}}
+ catch error:badarg -> {error,{bad_pattern,Pattern}}
+ end;
+compile_pattern([],Acc) ->
+ {ok,lists:reverse(Acc)}.
+
get_timeouts(Opts) ->
{case lists:keysearch(idle_timeout,1,Opts) of
{value,{_,T}} ->
@@ -772,7 +814,7 @@ get_seq(Opts) ->
get_haltpatterns(Opts) ->
case lists:keysearch(halt,1,Opts) of
{value,{halt,HaltPatterns}} ->
- convert_pattern(HaltPatterns,false);
+ HaltPatterns;
false ->
[]
end.
@@ -1068,7 +1110,7 @@ match_line(Name,Pid,Line,[{prompt,PromptType}|Patterns],FoundPrompt,Term,
when PromptType=/=FoundPrompt ->
match_line(Name,Pid,Line,Patterns,FoundPrompt,Term,EO,RetTag);
match_line(Name,Pid,Line,[{Tag,Pattern}|Patterns],FoundPrompt,Term,EO,RetTag) ->
- case re:run(Line,Pattern,[{capture,all,list},unicode]) of
+ case re:run(Line,Pattern,[{capture,all,list}]) of
nomatch ->
match_line(Name,Pid,Line,Patterns,FoundPrompt,Term,EO,RetTag);
{match,Match} ->
@@ -1076,7 +1118,7 @@ match_line(Name,Pid,Line,[{Tag,Pattern}|Patterns],FoundPrompt,Term,EO,RetTag) ->
{RetTag,{Tag,Match}}
end;
match_line(Name,Pid,Line,[Pattern|Patterns],FoundPrompt,Term,EO,RetTag) ->
- case re:run(Line,Pattern,[{capture,all,list},unicode]) of
+ case re:run(Line,Pattern,[{capture,all,list}]) of
nomatch ->
match_line(Name,Pid,Line,Patterns,FoundPrompt,Term,EO,RetTag);
{match,Match} ->
@@ -1085,7 +1127,7 @@ match_line(Name,Pid,Line,[Pattern|Patterns],FoundPrompt,Term,EO,RetTag) ->
end;
match_line(Name,Pid,Line,[],FoundPrompt,Term,EO,match) ->
match_line(Name,Pid,Line,EO#eo.haltpatterns,FoundPrompt,Term,EO,halt);
-%% print any terminated line that can not be matched
+%% print any terminated line that cannot be matched
match_line(Name,Pid,Line,[],_FoundPrompt,true,_EO,halt) ->
log(name_or_pid(Name,Pid)," ~ts",[Line]),
nomatch;
diff --git a/lib/common_test/src/ct_telnet_client.erl b/lib/common_test/src/ct_telnet_client.erl
index 76e4b9ea70..007477c855 100644
--- a/lib/common_test/src/ct_telnet_client.erl
+++ b/lib/common_test/src/ct_telnet_client.erl
@@ -101,9 +101,11 @@ close(Pid) ->
end.
send_data(Pid, Data) ->
- send_data(Pid, Data, true).
+ send_data(Pid, Data, "\n").
send_data(Pid, Data, true) ->
- send_data(Pid, Data++"\n", false);
+ send_data(Pid, Data, "\n");
+send_data(Pid, Data, Newline) when is_list(Newline) ->
+ send_data(Pid, Data++Newline, false);
send_data(Pid, Data, false) ->
Pid ! {send_data, Data},
ok.
diff --git a/lib/common_test/src/ct_util.erl b/lib/common_test/src/ct_util.erl
index d8fd401a64..9f489e9bfb 100644
--- a/lib/common_test/src/ct_util.erl
+++ b/lib/common_test/src/ct_util.erl
@@ -192,7 +192,10 @@ do_start(Parent, Mode, LogDir, Verbosity) ->
ok
end,
- ct_default_gl:start_link(group_leader()),
+ case ct_default_gl:start_link(group_leader()) of
+ {ok, _} -> ok;
+ ignore -> ok
+ end,
{StartTime,TestLogDir} = ct_logs:init(Mode, Verbosity),
diff --git a/lib/common_test/src/ct_util.hrl b/lib/common_test/src/ct_util.hrl
index 039c8168ec..d5c93d05ba 100644
--- a/lib/common_test/src/ct_util.hrl
+++ b/lib/common_test/src/ct_util.hrl
@@ -62,6 +62,7 @@
merge_tests=true}).
-record(cover, {app=none,
+ local_only=false,
level=details,
excl_mods=[],
incl_mods=[],
diff --git a/lib/common_test/src/cth_log_redirect.erl b/lib/common_test/src/cth_log_redirect.erl
index 4980d1ee4b..fe869a4373 100644
--- a/lib/common_test/src/cth_log_redirect.erl
+++ b/lib/common_test/src/cth_log_redirect.erl
@@ -124,7 +124,8 @@ start_log_handler() ->
shutdown=>2000,
type=>worker,
modules=>[?MODULE]},
- {ok,_} = supervisor:start_child(logger_sup,ChildSpec);
+ {ok,_} = supervisor:start_child(logger_sup,ChildSpec),
+ ok;
_Pid ->
ok
end,
@@ -193,10 +194,10 @@ handle_call({log,
case LogFunc of
tc_log ->
ct_logs:tc_log(Category, ?STD_IMPORTANCE,
- Header, String, [], []);
+ Header, "~ts", [String], []);
tc_log_async ->
ct_logs:tc_log_async(sasl, ?STD_IMPORTANCE,
- Header, String, [])
+ Header, "~ts", [String])
end,
{reply,ok,State};
@@ -260,34 +261,34 @@ handle_remote_events(Bool) ->
format_header(#eh_state{curr_suite = undefined,
curr_group = undefined,
curr_func = undefined}) ->
- io_lib:format("System report", []);
+ lists:flatten(io_lib:format("System report", []));
format_header(#eh_state{curr_suite = Suite,
curr_group = undefined,
curr_func = undefined}) ->
- io_lib:format("System report during ~w", [Suite]);
+ lists:flatten(io_lib:format("System report during ~w", [Suite]));
format_header(#eh_state{curr_suite = Suite,
curr_group = undefined,
curr_func = TcOrConf}) ->
- io_lib:format("System report during ~w:~tw/1",
- [Suite,TcOrConf]);
+ lists:flatten(io_lib:format("System report during ~w:~tw/1",
+ [Suite,TcOrConf]));
format_header(#eh_state{curr_suite = Suite,
curr_group = Group,
curr_func = Conf}) when Conf == init_per_group;
Conf == end_per_group ->
- io_lib:format("System report during ~w:~w/2 for ~tw",
- [Suite,Conf,Group]);
+ lists:flatten(io_lib:format("System report during ~w:~w/2 for ~tw",
+ [Suite,Conf,Group]));
format_header(#eh_state{curr_suite = Suite,
curr_group = Group,
parallel_tcs = true}) ->
- io_lib:format("System report during ~tw in ~w",
- [Group,Suite]);
+ lists:flatten(io_lib:format("System report during ~tw in ~w",
+ [Group,Suite]));
format_header(#eh_state{curr_suite = Suite,
curr_group = Group,
curr_func = TC}) ->
- io_lib:format("System report during ~w:~tw/1 in ~tw",
- [Suite,TC,Group]).
+ lists:flatten(io_lib:format("System report during ~w:~tw/1 in ~tw",
+ [Suite,TC,Group])).
diff --git a/lib/common_test/src/cth_surefire.erl b/lib/common_test/src/cth_surefire.erl
index b0742717ae..c9b4cb10c6 100644
--- a/lib/common_test/src/cth_surefire.erl
+++ b/lib/common_test/src/cth_surefire.erl
@@ -235,7 +235,7 @@ close_suite(#state{ test_cases = TCs, url_base = UrlBase } = State) ->
terminate(State = #state{ test_cases = [] }) ->
{ok,D} = file:open(State#state.filepath,[write,{encoding,utf8}]),
io:format(D, "<?xml version=\"1.0\" encoding= \"UTF-8\" ?>", []),
- io:format(D, to_xml(State), []),
+ io:format(D, "~ts", [to_xml(State)]),
catch file:sync(D),
catch file:close(D);
terminate(State) ->
diff --git a/lib/common_test/src/test_server.erl b/lib/common_test/src/test_server.erl
index a896a0551b..9eda3f2152 100644
--- a/lib/common_test/src/test_server.erl
+++ b/lib/common_test/src/test_server.erl
@@ -850,17 +850,23 @@ spawn_fw_call(Mod,EPTC={end_per_testcase,Func},EndConf,Pid,
"WARNING: end_per_testcase failed!</font>",
{died,W}
end,
- try do_end_tc_call(Mod,EPTC,{Pid,Report,[EndConf]}, Why) of
- _ -> ok
- catch
- _:FwEndTCErr ->
- exit({fw_notify_done,end_tc,FwEndTCErr})
- end,
- FailLoc = proplists:get_value(tc_fail_loc, EndConf),
+ FailLoc0 = proplists:get_value(tc_fail_loc, EndConf),
+ {RetVal1,FailLoc} =
+ try do_end_tc_call(Mod,EPTC,{Pid,Report,[EndConf]}, Why) of
+ Why ->
+ {RetVal,FailLoc0};
+ {failed,_} = R ->
+ {R,[{Mod,Func}]};
+ R ->
+ {R,FailLoc0}
+ catch
+ _:FwEndTCErr ->
+ exit({fw_notify_done,end_tc,FwEndTCErr})
+ end,
%% finished, report back (if end_per_testcase fails, a warning
%% should be printed as part of the comment)
SendTo ! {self(),fw_notify_done,
- {Time,RetVal,FailLoc,[],Warn}}
+ {Time,RetVal1,FailLoc,[],Warn}}
end,
spawn_link(FwCall);
@@ -902,14 +908,25 @@ spawn_fw_call(Mod,Func,CurrConf,Pid,Error,Loc,SendTo) ->
FwErrorNotifyErr})
end,
Conf = [{tc_status,{failed,Error}}|CurrConf],
- try do_end_tc_call(Mod,EndTCFunc,{Pid,Error,[Conf]},Error) of
- _ -> ok
- catch
- _:FwEndTCErr ->
- exit({fw_notify_done,end_tc,FwEndTCErr})
- end,
+ {Time,RetVal,Loc1} =
+ try do_end_tc_call(Mod,EndTCFunc,{Pid,Error,[Conf]},Error) of
+ Error ->
+ {died, Error, Loc};
+ {failed,Reason} = NewReturn ->
+ fw_error_notify(Mod,Func1,Conf,Reason),
+ {died, NewReturn, [{Mod,Func}]};
+ NewReturn ->
+ T = case Error of
+ {timetrap_timeout,TT} -> TT;
+ _ -> 0
+ end,
+ {T, NewReturn, Loc}
+ catch
+ _:FwEndTCErr ->
+ exit({fw_notify_done,end_tc,FwEndTCErr})
+ end,
%% finished, report back
- SendTo ! {self(),fw_notify_done,{died,Error,Loc,[],undefined}}
+ SendTo ! {self(),fw_notify_done,{Time,RetVal,Loc1,[],undefined}}
end,
spawn_link(FwCall).
diff --git a/lib/common_test/src/test_server_ctrl.erl b/lib/common_test/src/test_server_ctrl.erl
index 8bd6cd583a..1518c6e8d6 100644
--- a/lib/common_test/src/test_server_ctrl.erl
+++ b/lib/common_test/src/test_server_ctrl.erl
@@ -1393,7 +1393,7 @@ temp_nodename([Chr|Base], Acc) ->
%%
%% Counts the test cases that are about to run and returns that number.
%% If there's a conf group in TestSpec with a repeat property, the total number
-%% of cases can not be calculated and NoOfCases = unknown.
+%% of cases cannot be calculated and NoOfCases = unknown.
count_test_cases(TopCases, SkipCases) when is_list(TopCases) ->
case collect_all_cases(TopCases, SkipCases) of
{error,_Why} = Error ->
@@ -1558,7 +1558,7 @@ do_test_cases(TopCases, SkipCases,
Html1}
end,
- print(html, Header),
+ print(html, "~ts", [Header]),
print(html, xhtml("<p>", "<h4>")),
print_timestamp(html, "Test started at "),
@@ -1605,10 +1605,10 @@ do_test_cases(TopCases, SkipCases,
[?suitelog_name,CoverLog,?unexpected_io_log]),
print(html,
"<p>~ts</p>\n" ++
- xhtml(["<table bgcolor=\"white\" border=\"3\" cellpadding=\"5\">\n",
- "<thead>\n"],
- ["<table id=\"",?sortable_table_name,"\">\n",
- "<thead>\n"]) ++
+ xhtml("<table bgcolor=\"white\" border=\"3\" cellpadding=\"5\">\n" ++
+ "<thead>\n",
+ "<table id=\"" ++ ?sortable_table_name ++ "\">\n" ++
+ "<thead>\n") ++
"<tr><th>Num</th><th>Module</th><th>Group</th>" ++
"<th>Case</th><th>Log</th><th>Time</th><th>Result</th>" ++
"<th>Comment</th></tr>\n</thead>\n<tbody>\n",
@@ -3306,7 +3306,8 @@ skip_case1(Type, CaseNum, Mod, Func, Comment, Mode) ->
true ->
print(2,"*** Skipping test case #~w ~tw ***", [CaseNum,{Mod,Func}])
end,
- TR = xhtml("<tr valign=\"top\">", ["<tr class=\"",odd_or_even(),"\">"]),
+ TR = xhtml("<tr valign=\"top\">",
+ "<tr class=\"" ++ odd_or_even() ++ "\">"),
GroupName = case get_name(Mode) of
undefined -> "";
Name -> cast_to_list(Name)
@@ -3796,8 +3797,8 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit,
end,
print(minor,
- escape_chars(io_lib:format("Config value:\n\n ~tp\n", [Args2Print])),
- []),
+ "~ts",
+ [escape_chars(io_lib:format("Config value:\n\n ~tp\n", [Args2Print]))]),
print(minor, "Current directory is ~tp\n", [Cwd]),
GrNameStr = case GrName of
@@ -3806,7 +3807,7 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit,
end,
print(major, "=started ~s", [lists:flatten(timestamp_get(""))]),
{{Col0,Col1},Style} = get_font_style((RunInit==run_init), Mode),
- TR = xhtml("<tr valign=\"top\">", ["<tr class=\"",odd_or_even(),"\">"]),
+ TR = xhtml("<tr valign=\"top\">", "<tr class=\"" ++ odd_or_even() ++ "\">"),
EncMinorBase = uri_encode(MinorBase),
print(html, TR ++ "<td>" ++ Col0 ++ "~ts" ++ Col1 ++ "</td>"
"<td>" ++ Col0 ++ "~w" ++ Col1 ++ "</td>"
@@ -3831,7 +3832,7 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit,
print(minor, "<a name=\"end\"></a>", [], internal_raw),
print(minor, "\n", [], internal_raw),
print_timestamp(minor, "Ended at "),
- print(major, "=ended ~s", [lists:flatten(timestamp_get(""))]),
+ print(major, "=ended ~s", [timestamp_get("")]),
do_unless_parallel(Main, fun() -> file:set_cwd(filename:dirname(TSDir)) end),
@@ -4075,9 +4076,9 @@ progress(failed, CaseNum, Mod, Func, GrName, Loc, {testcase_aborted,Reason}, _T,
FormatLoc = test_server_sup:format_loc(Loc),
print(minor, "=== Location: ~ts", [FormatLoc]),
print(minor,
- escape_chars(io_lib:format("=== Reason: {testcase_aborted,~tp}",
- [Reason])),
- []),
+ "~ts",
+ [escape_chars(io_lib:format("=== Reason: {testcase_aborted,~tp}",
+ [Reason]))]),
failed;
progress(failed, CaseNum, Mod, Func, GrName, unknown, Reason, Time,
@@ -4115,8 +4116,8 @@ progress(failed, CaseNum, Mod, Func, GrName, unknown, Reason, Time,
print(minor, "=== Location: ~w", [unknown]),
{FStr,FormattedReason} = format_exception(Reason),
print(minor,
- escape_chars(io_lib:format("=== Reason: " ++ FStr, [FormattedReason])),
- []),
+ "~ts",
+ [escape_chars(io_lib:format("=== Reason: " ++ FStr, [FormattedReason]))]),
failed;
progress(failed, CaseNum, Mod, Func, GrName, Loc, Reason, Time,
@@ -4150,8 +4151,9 @@ progress(failed, CaseNum, Mod, Func, GrName, Loc, Reason, Time,
FormatLoc = test_server_sup:format_loc(LocMin),
print(minor, "=== Location: ~ts", [FormatLoc]),
{FStr,FormattedReason} = format_exception(Reason),
- print(minor, "=== Reason: " ++
- escape_chars(io_lib:format(FStr, [FormattedReason])), []),
+ print(minor, "~ts",
+ ["=== Reason: " ++
+ escape_chars(io_lib:format(FStr, [FormattedReason]))]),
failed;
progress(ok, _CaseNum, Mod, Func, GrName, _Loc, RetVal, Time,
@@ -4184,8 +4186,8 @@ progress(ok, _CaseNum, Mod, Func, GrName, _Loc, RetVal, Time,
"~ts</tr>\n",
[TimeStr,Comment]),
print(minor,
- escape_chars(io_lib:format("=== Returned value: ~tp", [RetVal])),
- []),
+ "~ts",
+ [escape_chars(io_lib:format("=== Returned value: ~tp", [RetVal]))]),
ok.
%%--------------------------------------------------------------------
@@ -4542,7 +4544,7 @@ timestamp_get(Leader) ->
timestamp_get_internal(Leader, Format) ->
{YY,MM,DD,H,M,S} = time_get(),
- io_lib:format(Format, [Leader,YY,MM,DD,H,M,S]).
+ lists:flatten(io_lib:format(Format, [Leader,YY,MM,DD,H,M,S])).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% time_get() -> {YY,MM,DD,H,M,S}
@@ -4906,7 +4908,7 @@ collect_files(Dir, Pattern, St, Mode) ->
fullname_to_mod(Path) when is_list(Path) ->
%% If this is called with a binary, then we are probably in +fnu
%% mode and have found a beam file with name encoded as latin1. We
- %% will let this crash since it can not work to load such a module
+ %% will let this crash since it cannot work to load such a module
%% anyway. It should be removed or renamed!
list_to_atom(filename:rootname(filename:basename(Path))).
diff --git a/lib/common_test/test/ct_auto_compile_SUITE.erl b/lib/common_test/test/ct_auto_compile_SUITE.erl
index dface99b8f..f88f13c889 100644
--- a/lib/common_test/test/ct_auto_compile_SUITE.erl
+++ b/lib/common_test/test/ct_auto_compile_SUITE.erl
@@ -169,7 +169,7 @@ test_events(ac_flag) ->
{?eh,start_info,{1,1,3}},
{?eh,tc_start,{ct_framework,error_in_suite}},
{?eh,tc_done,{ct_framework,error_in_suite,
- {failed,{error,'bad_SUITE can not be compiled or loaded'}}}},
+ {failed,{error,'bad_SUITE cannot be compiled or loaded'}}}},
{?eh,tc_start,{dummy_SUITE,init_per_suite}},
{?eh,tc_done,{dummy_SUITE,init_per_suite,ok}},
{?eh,test_stats,{1,1,{1,0}}},
@@ -186,7 +186,7 @@ test_events(ac_spec) ->
{?eh,start_info,{1,1,3}},
{?eh,tc_start,{ct_framework,error_in_suite}},
{?eh,tc_done,{ct_framework,error_in_suite,
- {failed,{error,'bad_SUITE can not be compiled or loaded'}}}},
+ {failed,{error,'bad_SUITE cannot be compiled or loaded'}}}},
{?eh,tc_start,{dummy_SUITE,init_per_suite}},
{?eh,tc_done,{dummy_SUITE,init_per_suite,ok}},
{?eh,test_stats,{1,1,{1,0}}},
diff --git a/lib/common_test/test/ct_config_SUITE.erl b/lib/common_test/test/ct_config_SUITE.erl
index 5ffc735d6a..ec5278b96d 100644
--- a/lib/common_test/test/ct_config_SUITE.erl
+++ b/lib/common_test/test/ct_config_SUITE.erl
@@ -211,18 +211,12 @@ reformat_events(Events, EH) ->
%%% Test related to 'localtime' will often fail if the test host is
%%% time warping, so let's just skip the 'dynamic' tests then.
skip_dynamic() ->
- case os:getenv("TS_EXTRA_PLATFORM_LABEL") of
- TSExtraPlatformLabel when is_list(TSExtraPlatformLabel) ->
- case string:find(TSExtraPlatformLabel,"TimeWarpingOS") of
- nomatch -> false;
- _ -> true
- end;
- _ ->
- false
+ case string:find(os:getenv("TS_EXTRA_PLATFORM_LABEL", ""), "TimeWarpingOS") of
+ nomatch -> false;
+ _ -> true
end.
-
%%%-----------------------------------------------------------------
%%% TEST EVENTS
%%%-----------------------------------------------------------------
diff --git a/lib/common_test/test/ct_hooks_SUITE.erl b/lib/common_test/test/ct_hooks_SUITE.erl
index 0f5636a789..44b86b1dfe 100644
--- a/lib/common_test/test/ct_hooks_SUITE.erl
+++ b/lib/common_test/test/ct_hooks_SUITE.erl
@@ -84,7 +84,7 @@ all(suite) ->
fail_post_suite_cth, skip_pre_suite_cth, skip_pre_end_cth,
skip_pre_init_tc_cth,
skip_post_suite_cth, recover_post_suite_cth, update_config_cth,
- state_update_cth, options_cth, same_id_cth,
+ state_update_cth, update_result_cth, options_cth, same_id_cth,
fail_n_skip_with_minimal_cth, prio_cth, no_config,
no_init_suite_config, no_init_config, no_end_config,
failed_sequence, repeat_force_stop, config_clash,
@@ -209,6 +209,10 @@ state_update_cth(Config) when is_list(Config) ->
do_test(state_update_cth, "ct_cth_fail_one_skip_one_SUITE.erl",
[state_update_cth,state_update_cth],Config).
+update_result_cth(Config) ->
+ do_test(update_result_cth, "ct_cth_update_result_post_end_tc_SUITE.erl",
+ [update_result_post_end_tc_cth],Config).
+
options_cth(Config) when is_list(Config) ->
do_test(options_cth, "ct_cth_empty_SUITE.erl",
[{empty_cth,[test]}],Config).
@@ -1099,6 +1103,106 @@ test_events(state_update_cth) ->
{?eh,stop_logging,[]}
];
+test_events(update_result_cth) ->
+ Suite = ct_cth_update_result_post_end_tc_SUITE,
+ [
+ {?eh,start_logging,'_'},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,cth,{'_',init,['_',[]]}},
+ {?eh,tc_start,{Suite,init_per_suite}},
+ {?eh,tc_done,{Suite,init_per_suite,ok}},
+
+ {?eh,tc_start,{Suite,tc_ok_to_fail}},
+ {?eh,cth,{'_',post_end_per_testcase,[Suite,tc_ok_to_fail,'_',ok,[]]}},
+ {?eh,tc_done,{Suite,tc_ok_to_fail,{failed,{error,"Test failure"}}}},
+ {?eh,cth,{'_',on_tc_fail,'_'}},
+ {?eh,test_stats,{0,1,{0,0}}},
+
+ {?eh,tc_start,{Suite,tc_ok_to_skip}},
+ {?eh,cth,{'_',post_end_per_testcase,[Suite,tc_ok_to_skip,'_',ok,[]]}},
+ {?eh,tc_done,{Suite,tc_ok_to_skip,{skipped,"Test skipped"}}},
+ {?eh,cth,{'_',on_tc_skip,'_'}},
+ {?eh,test_stats,{0,1,{1,0}}},
+
+ {?eh,tc_start,{Suite,tc_fail_to_ok}},
+ {?eh,cth,{'_',post_end_per_testcase,
+ [Suite,tc_fail_to_ok,'_',
+ {error,{test_case_failed,"should be changed to ok"}},[]]}},
+ {?eh,tc_done,{Suite,tc_fail_to_ok,ok}},
+ {?eh,test_stats,{1,1,{1,0}}},
+
+ {?eh,tc_start,{Suite,tc_fail_to_skip}},
+ {?eh,cth,{'_',post_end_per_testcase,
+ [Suite,tc_fail_to_skip,'_',
+ {error,{test_case_failed,"should be changed to skip"}},[]]}},
+ {?eh,tc_done,{Suite,tc_fail_to_skip,{skipped,"Test skipped"}}},
+ {?eh,cth,{'_',on_tc_skip,'_'}},
+ {?eh,test_stats,{1,1,{2,0}}},
+
+ {?eh,tc_start,{Suite,tc_timetrap_to_ok}},
+ {?eh,cth,{'_',post_end_per_testcase,
+ [Suite,tc_timetrap_to_ok,'_',{timetrap_timeout,3000},[]]}},
+ {?eh,tc_done,{Suite,tc_timetrap_to_ok,ok}},
+ {?eh,test_stats,{2,1,{2,0}}},
+
+ {?eh,tc_start,{Suite,tc_timetrap_to_skip}},
+ {?eh,cth,{'_',post_end_per_testcase,
+ [Suite,tc_timetrap_to_skip,'_',{timetrap_timeout,3000},[]]}},
+ {?eh,tc_done,{Suite,tc_timetrap_to_skip,{skipped,"Test skipped"}}},
+ {?eh,cth,{'_',on_tc_skip,'_'}},
+ {?eh,test_stats,{2,1,{3,0}}},
+
+ {?eh,tc_start,{Suite,tc_skip_to_fail}},
+ {?eh,cth,{'_',post_end_per_testcase,
+ [Suite,tc_skip_to_fail,'_',
+ {skip,"should be changed to fail"},[]]}},
+ {?eh,tc_done,{Suite,tc_skip_to_fail,{failed,{error,"Test failure"}}}},
+ {?eh,cth,{'_',on_tc_fail,'_'}},
+ {?eh,test_stats,{2,2,{3,0}}},
+
+ {?eh,tc_start,{Suite,end_fail_to_fail}},
+ {?eh,cth,{'_',post_end_per_testcase,
+ [Suite,end_fail_to_fail,'_',
+ {failed,
+ {Suite,end_per_testcase,
+ {'EXIT',{test_case_failed,"change result when end fails"}}}},[]]}},
+ {?eh,tc_done,{Suite,end_fail_to_fail,{failed,{error,"Test failure"}}}},
+ {?eh,cth,{'_',on_tc_fail,'_'}},
+ {?eh,test_stats,{2,3,{3,0}}},
+
+ {?eh,tc_start,{Suite,end_fail_to_skip}},
+ {?eh,cth,{'_',post_end_per_testcase,
+ [Suite,end_fail_to_skip,'_',
+ {failed,
+ {Suite,end_per_testcase,
+ {'EXIT',{test_case_failed,"change result when end fails"}}}},[]]}},
+ {?eh,tc_done,{Suite,end_fail_to_skip,{skipped,"Test skipped"}}},
+ {?eh,cth,{'_',on_tc_skip,'_'}},
+ {?eh,test_stats,{2,3,{4,0}}},
+
+ {?eh,tc_start,{Suite,end_timetrap_to_fail}},
+ {?eh,cth,{'_',post_end_per_testcase,
+ [Suite,end_timetrap_to_fail,'_',
+ {failed,{Suite,end_per_testcase,{timetrap_timeout,3000}}},[]]}},
+ {?eh,tc_done,{Suite,end_timetrap_to_fail,{failed,{error,"Test failure"}}}},
+ {?eh,cth,{'_',on_tc_fail,'_'}},
+ {?eh,test_stats,{2,4,{4,0}}},
+
+ {?eh,tc_start,{Suite,end_timetrap_to_skip}},
+ {?eh,cth,{'_',post_end_per_testcase,
+ [Suite,end_timetrap_to_skip,'_',
+ {failed,{Suite,end_per_testcase,{timetrap_timeout,3000}}},[]]}},
+ {?eh,tc_done,{Suite,end_timetrap_to_skip,{skipped,"Test skipped"}}},
+ {?eh,cth,{'_',on_tc_skip,'_'}},
+ {?eh,test_stats,{2,4,{5,0}}},
+
+ {?eh,tc_start,{Suite,end_per_suite}},
+ {?eh,tc_done,{Suite,end_per_suite,ok}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,cth,{'_',terminate,[[]]}},
+ {?eh,stop_logging,[]}
+ ];
+
test_events(options_cth) ->
[
{?eh,start_logging,{'DEF','RUNDIR'}},
diff --git a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_cth_update_result_post_end_tc_SUITE.erl b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_cth_update_result_post_end_tc_SUITE.erl
new file mode 100644
index 0000000000..a16138ce6f
--- /dev/null
+++ b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_cth_update_result_post_end_tc_SUITE.erl
@@ -0,0 +1,101 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2017. All 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(ct_cth_update_result_post_end_tc_SUITE).
+
+-compile(export_all).
+
+-include("ct.hrl").
+
+suite() ->
+ [{timetrap,{seconds,3}}].
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(Config) ->
+ ok.
+
+init_per_group(_,Config) ->
+ Config.
+
+end_per_group(_,_) ->
+ ok.
+
+init_per_testcase(_,Config) ->
+ Config.
+
+end_per_testcase(EndTimetrap,_) when EndTimetrap==end_timetrap_to_fail;
+ EndTimetrap==end_timetrap_to_skip->
+ timer:sleep(10000);
+end_per_testcase(EndFail,_) when EndFail==end_fail_to_fail;
+ EndFail==end_fail_to_skip->
+ ct:fail("change result when end fails");
+end_per_testcase(_,_) ->
+ ok.
+
+all() ->
+ [tc_ok_to_fail,
+ tc_ok_to_skip,
+ tc_fail_to_ok,
+ tc_fail_to_skip,
+ tc_timetrap_to_ok,
+ tc_timetrap_to_skip,
+ tc_skip_to_fail,
+ end_fail_to_fail,
+ end_fail_to_skip,
+ end_timetrap_to_fail,
+ end_timetrap_to_skip].
+
+%% Test cases starts here.
+tc_ok_to_fail(_Config) ->
+ ok.
+
+tc_ok_to_skip(_Config) ->
+ ok.
+
+tc_fail_to_ok(_Config) ->
+ ct:fail("should be changed to ok").
+
+tc_fail_to_skip(_Config) ->
+ ct:fail("should be changed to skip").
+
+tc_timetrap_to_ok(_Config) ->
+ timer:sleep(10000), % will time out after 3 sek
+ ok.
+
+tc_timetrap_to_skip(_Config) ->
+ timer:sleep(10000), % will time out after 3 sek
+ ok.
+
+tc_skip_to_fail(_Config) ->
+ {skip,"should be changed to fail"}.
+
+end_fail_to_fail(_Config) ->
+ ok.
+
+end_fail_to_skip(_Config) ->
+ ok.
+
+end_timetrap_to_fail(_Config) ->
+ ok.
+
+end_timetrap_to_skip(_Config) ->
+ ok.
diff --git a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/update_result_post_end_tc_cth.erl b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/update_result_post_end_tc_cth.erl
new file mode 100644
index 0000000000..7afb3d8781
--- /dev/null
+++ b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/update_result_post_end_tc_cth.erl
@@ -0,0 +1,98 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2017. All 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(update_result_post_end_tc_cth).
+
+
+-include_lib("common_test/src/ct_util.hrl").
+-include_lib("common_test/include/ct_event.hrl").
+
+
+%% CT Hooks
+-compile(export_all).
+
+init(Id, Opts) ->
+ empty_cth:init(Id, Opts).
+
+pre_init_per_suite(Suite, Config, State) ->
+ empty_cth:pre_init_per_suite(Suite,Config,State).
+
+post_init_per_suite(Suite,Config,Return,State) ->
+ empty_cth:post_init_per_suite(Suite,Config,Return,State).
+
+pre_end_per_suite(Suite,Config,State) ->
+ empty_cth:pre_end_per_suite(Suite,Config,State).
+
+post_end_per_suite(Suite,Config,Return,State) ->
+ empty_cth:post_end_per_suite(Suite,Config,Return,State).
+
+pre_init_per_group(Suite,Group,Config,State) ->
+ empty_cth:pre_init_per_group(Suite,Group,Config,State).
+
+post_init_per_group(Suite,Group,Config,Return,State) ->
+ empty_cth:post_init_per_group(Suite,Group,Config,Return,State).
+
+pre_end_per_group(Suite,Group,Config,State) ->
+ empty_cth:pre_end_per_group(Suite,Group,Config,State).
+
+post_end_per_group(Suite,Group,Config,Return,State) ->
+ empty_cth:post_end_per_group(Suite,Group,Config,Return,State).
+
+pre_init_per_testcase(Suite,TC,Config,State) ->
+ empty_cth:pre_init_per_testcase(Suite,TC,Config,State).
+
+post_end_per_testcase(Suite,TC,Config,Return,State) ->
+ empty_cth:post_end_per_testcase(Suite,TC,Config,Return,State),
+ change_result(TC,Config,State).
+
+on_tc_fail(Suite,TC, Reason, State) ->
+ empty_cth:on_tc_fail(Suite,TC,Reason,State).
+
+on_tc_skip(Suite,TC, Reason, State) ->
+ empty_cth:on_tc_skip(Suite,TC,Reason,State).
+
+terminate(State) ->
+ empty_cth:terminate(State).
+
+%%%-----------------------------------------------------------------
+%%%
+change_result(tc_ok_to_fail,_Config,State) ->
+ {{fail, "Test failure"}, State};
+change_result(tc_ok_to_skip,_Config,State) ->
+ {{skip, "Test skipped"}, State};
+change_result(tc_fail_to_ok,Config,State) ->
+ {lists:keydelete(tc_status,1,Config),State};
+change_result(tc_fail_to_skip,Config,State) ->
+ {{skip,"Test skipped"},State};
+change_result(tc_timetrap_to_ok,Config,State) ->
+ {lists:keydelete(tc_status,1,Config),State};
+change_result(tc_timetrap_to_skip,Config,State) ->
+ {{skip,"Test skipped"},State};
+change_result(tc_skip_to_fail,_Config,State) ->
+ {{fail, "Test failure"}, State};
+change_result(end_fail_to_fail,_Config,State) ->
+ {{fail, "Test failure"}, State};
+change_result(end_fail_to_skip,_Config,State) ->
+ {{skip, "Test skipped"}, State};
+change_result(end_timetrap_to_fail,_Config,State) ->
+ {{fail, "Test failure"}, State};
+change_result(end_timetrap_to_skip,_Config,State) ->
+ {{skip, "Test skipped"}, State}.
diff --git a/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl
index a2fa099a8c..7aaf33839f 100644
--- a/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl
+++ b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl
@@ -205,7 +205,7 @@ hello_required_exists(Config) ->
SshDir = ?config(ssh_dir,Config),
{ok,_Client1} = open_configured_success(my_named_connection,SshDir),
- %% Check that same name can not be used twice
+ %% Check that same name cannot be used twice
{error,{connection_exists,_Client1}} =
ct_netconfc:open(my_named_connection,[{user_dir,SshDir}]),
@@ -385,7 +385,7 @@ timeout_get(Config) ->
%% received, the timeout message might already be sent when the timer
%% is cancelled. This test checks that the timeout message is flushed
%% from the message queue. If it isn't, the client crashes and the
-%% session can not be closed afterwards.
+%% session cannot be closed afterwards.
%% Note that we can only hope that the test case triggers the problem
%% every now and then, as it is very timing dependent...
flush_timeout_get(Config) ->
@@ -440,6 +440,12 @@ edit_config(Config) ->
?ok = ct_netconfc:edit_config(Client,running,
{server,[{xmlns,"myns"}],
[{name,["myserver"]}]}),
+ ?NS:expect_reply('edit-config',ok),
+ ?ok = ct_netconfc:edit_config(Client,running,
+ [{server,[{xmlns,"myns"}],
+ [{name,["server1"]}]},
+ {server,[{xmlns,"myns"}],
+ [{name,["server2"]}]}]),
?NS:expect_do_reply('close-session',close,ok),
?ok = ct_netconfc:close_session(Client),
ok.
diff --git a/lib/common_test/test/ct_telnet_SUITE.erl b/lib/common_test/test/ct_telnet_SUITE.erl
index a0089c9bc9..f71b7c370f 100644
--- a/lib/common_test/test/ct_telnet_SUITE.erl
+++ b/lib/common_test/test/ct_telnet_SUITE.erl
@@ -50,10 +50,10 @@
suite() -> [{ct_hooks,[ts_install_cth]}].
groups() ->
- [{legacy, [], [unix_telnet,own_server,timetrap]},
- {raw, [], [unix_telnet,own_server,timetrap]},
- {html, [], [unix_telnet,own_server]},
- {silent, [], [unix_telnet,own_server]}].
+ [{legacy, [], [unix_telnet,own_server,faulty_regexp,timetrap]},
+ {raw, [], [unix_telnet,own_server,faulty_regexp,timetrap]},
+ {html, [], [unix_telnet,own_server,faulty_regexp]},
+ {silent, [], [unix_telnet,own_server,faulty_regexp]}].
all() ->
[
@@ -119,6 +119,12 @@ own_server(Config) ->
all_tests_in_suite(own_server,"ct_telnet_own_server_SUITE",
CfgFile,Config).
+faulty_regexp(Config) ->
+ CfgFile = "telnet.faulty_regexp." ++
+ atom_to_list(groupname(Config)) ++ ".cfg",
+ all_tests_in_suite(faulty_regexp,"ct_telnet_faulty_regexp_SUITE",
+ CfgFile,Config).
+
timetrap(Config) ->
CfgFile = "telnet.timetrap." ++
atom_to_list(groupname(Config)) ++ ".cfg",
@@ -225,6 +231,31 @@ events_to_check(unix_telnet,Config) ->
all_cases(ct_telnet_basic_SUITE,Config);
events_to_check(own_server,Config) ->
all_cases(ct_telnet_own_server_SUITE,Config);
+events_to_check(faulty_regexp,_Config) ->
+ [{?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,tc_done,
+ {ct_telnet_faulty_regexp_SUITE,expect_pattern,
+ {failed,
+ {error,{{bad_pattern,"invalid(pattern",{"missing )",15}},
+ {ct_telnet,expect,3}}}}}},
+ {?eh,tc_done,
+ {ct_telnet_faulty_regexp_SUITE,expect_pattern_no_string,
+ {failed,
+ {error,{{bad_pattern,invalid_pattern},
+ {ct_telnet,expect,3}}}}}},
+ {?eh,tc_done,
+ {ct_telnet_faulty_regexp_SUITE,expect_tag_pattern,
+ {failed,
+ {error,{{bad_pattern,{tag,"invalid(pattern"},{"missing )",15}},
+ {ct_telnet,expect,3}}}}}},
+ {?eh,tc_done,
+ {ct_telnet_faulty_regexp_SUITE,expect_tag_pattern_no_string,
+ {failed,
+ {error,{{bad_pattern,{tag,invalid_pattern}},
+ {ct_telnet,expect,3}}}}}},
+ {?eh,tc_done,{ct_telnet_faulty_regexp_SUITE,expect_pattern_unicode,ok}},
+ {?eh,tc_done,{ct_telnet_faulty_regexp_SUITE,expect_tag_pattern_unicode,ok}},
+ {?eh,stop_logging,[]}];
events_to_check(timetrap,_Config) ->
[{?eh,start_logging,{'DEF','RUNDIR'}},
{?eh,tc_done,{ct_telnet_timetrap_SUITE,expect_timetrap,
diff --git a/lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_faulty_regexp_SUITE.erl b/lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_faulty_regexp_SUITE.erl
new file mode 100644
index 0000000000..a5c9451a9c
--- /dev/null
+++ b/lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_faulty_regexp_SUITE.erl
@@ -0,0 +1,79 @@
+-module(ct_telnet_faulty_regexp_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+-define(name, telnet_server_conn1).
+
+%%--------------------------------------------------------------------
+%% TEST SERVER CALLBACK FUNCTIONS
+%%--------------------------------------------------------------------
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+suite() -> [{require,?name,{unix,[telnet]}},
+ {require,ct_conn_log},
+ {ct_hooks, [{cth_conn_log,[]}]}].
+
+all() ->
+ [expect_pattern,
+ expect_pattern_no_string,
+ expect_tag_pattern,
+ expect_tag_pattern_no_string,
+ expect_pattern_unicode,
+ expect_tag_pattern_unicode].
+
+groups() ->
+ [].
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+init_per_testcase(_,Config) ->
+ ct:log("init_per_testcase: opening telnet connection...",[]),
+ {ok,_} = ct_telnet:open(?name),
+ ct:log("...done",[]),
+ Config.
+
+end_per_testcase(_,_Config) ->
+ ct:log("end_per_testcase: closing telnet connection...",[]),
+ _ = ct_telnet:close(?name),
+ ct:log("...done",[]),
+ ok.
+
+expect_pattern(_) ->
+ ok = ct_telnet:send(?name, "echo ayt"),
+ ok = ct_telnet:expect(?name, "invalid(pattern").
+
+expect_pattern_no_string(_) ->
+ ok = ct_telnet:send(?name, "echo ayt"),
+ ok = ct_telnet:expect(?name, invalid_pattern).
+
+expect_tag_pattern(_) ->
+ ok = ct_telnet:send(?name, "echo ayt"),
+ ok = ct_telnet:expect(?name, {tag,"invalid(pattern"}).
+
+expect_tag_pattern_no_string(_) ->
+ ok = ct_telnet:send(?name, "echo ayt"),
+ ok = ct_telnet:expect(?name, {tag,invalid_pattern}).
+
+%% Test that a unicode pattern can be given without the testcase
+%% failing. Do however notice that there is no real unicode support
+%% in ct_telnet yet, that is, the telnet binary mode is not supported.
+expect_pattern_unicode(_) ->
+ ok = ct_telnet:send(?name, "echo ayt"),
+ {error,{prompt,_}} = ct_telnet:expect(?name, "pattern_with_unicode_αβ"),
+ ok.
+
+expect_tag_pattern_unicode(_) ->
+ ok = ct_telnet:send(?name, "echo ayt"),
+ {error,{prompt,_}} = ct_telnet:expect(?name, "pattern_with_unicode_αβ"),
+ ok.
diff --git a/lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl b/lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl
index 985fa40ad2..34df57027e 100644
--- a/lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl
+++ b/lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl
@@ -58,7 +58,8 @@ all() ->
server_speaks,
server_disconnects,
newline_ayt,
- newline_break
+ newline_break,
+ newline_string
].
groups() ->
@@ -393,3 +394,11 @@ newline_break(_) ->
"> " = lists:flatten(R),
ok = ct_telnet:close(Handle),
ok.
+
+%% Test option {newline,String} to specify an own newline, e.g. "\r\n"
+newline_string(_) ->
+ {ok, Handle} = ct_telnet:open(telnet_server_conn1),
+ ok = ct_telnet:send(Handle, "echo hello-", [{newline,"own_nl\n"}]),
+ {ok,["hello-own_nl"]} = ct_telnet:expect(Handle, ["hello-own_nl"]),
+ ok = ct_telnet:close(Handle),
+ ok.
diff --git a/lib/common_test/test/test_server_SUITE.erl b/lib/common_test/test/test_server_SUITE.erl
index 05737cfac9..6e52f24025 100644
--- a/lib/common_test/test/test_server_SUITE.erl
+++ b/lib/common_test/test/test_server_SUITE.erl
@@ -260,7 +260,7 @@ translate_filename(Filename,EncodingOnTestNode) ->
end.
get_latest_run_dir(Dir) ->
- %% For the time being, filelib:wildcard can not take a binary
+ %% For the time being, filelib:wildcard cannot take a binary
%% argument, so we avoid using this here.
case file:list_dir(Dir) of
{ok,Files} ->
@@ -315,7 +315,7 @@ generate_and_run_unicode_test(Config0,Encoding) ->
DataDir = ?config(data_dir,Config0),
Suite = create_unicode_test_suite(DataDir,Encoding),
- %% We can not run this test on default node since it must be
+ %% We cannot run this test on default node since it must be
%% started with correct file name mode (+fnu/+fnl).
%% OBS: the node are stopped by end_per_testcase/2
Config1 = lists:keydelete(node,1,Config0),
diff --git a/lib/common_test/test/test_server_test_lib.erl b/lib/common_test/test/test_server_test_lib.erl
index 9ee946af0b..58b3aaee9b 100644
--- a/lib/common_test/test/test_server_test_lib.erl
+++ b/lib/common_test/test/test_server_test_lib.erl
@@ -22,7 +22,7 @@
-export([parse_suite/1]).
-export([init/2, pre_init_per_testcase/3, post_end_per_testcase/4]).
-%% for test_server_SUITE when node can not be started as slave
+%% for test_server_SUITE when node cannot be started as slave
-export([prepare_tester_node/2]).
-include("test_server_test_lib.hrl").
diff --git a/lib/common_test/test_server/configure.in b/lib/common_test/test_server/configure.in
index 0511d126b4..e07bd4c2aa 100644
--- a/lib/common_test/test_server/configure.in
+++ b/lib/common_test/test_server/configure.in
@@ -459,11 +459,11 @@ dnl Freely inspired by AC_TRY_LINK. (Maybe better to create a
dnl AC_LANG_JAVA instead...)
AC_DEFUN(ERL_TRY_LINK_JAVA,
[java_link='$JAVAC conftest.java 1>&AC_FD_CC'
-changequote(�, �)dnl
+changequote(, )dnl
cat > conftest.java <<EOF
-�$1�
+$1
class conftest { public static void main(String[] args) {
- �$2�
+ $2
; return; }}
EOF
changequote([, ])dnl
diff --git a/lib/common_test/test_server/ts_benchmark.erl b/lib/common_test/test_server/ts_benchmark.erl
index e4e06b54c2..a9486262b3 100644
--- a/lib/common_test/test_server/ts_benchmark.erl
+++ b/lib/common_test/test_server/ts_benchmark.erl
@@ -45,7 +45,7 @@ run(Specs, Opts, Vars) ->
|| Spec <- Specs],
file:delete(filename:join(Cwd,"latest_benchmark")),
{ok,D} = file:open(filename:join(Cwd,"latest_benchmark"),[write]),
- io:format(D,BDir,[]),
+ io:format(D,"~ts", [BDir]),
file:close(D).
diff --git a/lib/common_test/test_server/ts_erl_config.erl b/lib/common_test/test_server/ts_erl_config.erl
index 537628e39a..c2852131d6 100644
--- a/lib/common_test/test_server/ts_erl_config.erl
+++ b/lib/common_test/test_server/ts_erl_config.erl
@@ -197,20 +197,28 @@ system_include(Root, Vars) ->
" -I" ++ quote(filename:nativename(filename:join([Root, "erts", "emulator", SysDir]))).
erl_interface(Vars,OsType) ->
- {Incl, {LibPath, MkIncl}} =
+ {Incl, TargetIncl, {LibPath, MkIncl}} =
case lib_dir(Vars, erl_interface) of
{error, bad_name} ->
throw({cannot_find_app, erl_interface});
Dir ->
- {filename:join(Dir, "include"),
- case erl_root(Vars) of
- {installed, _Root} ->
- {filename:join(Dir, "lib"),
- filename:join([Dir, "src", "eidefs.mk"])};
- {srctree, _Root, Target} ->
- {filename:join([Dir, "obj", Target]),
- filename:join([Dir, "src", Target, "eidefs.mk"])}
- end}
+ BaseIncl = filename:join(Dir, "include"),
+ case erl_root(Vars) of
+ {installed, _Root} ->
+ {BaseIncl,
+ [],
+ {filename:join(Dir, "lib"),
+ filename:join([Dir, "src", "eidefs.mk"])}};
+ {srctree, _Root, Target} ->
+ Obj = case is_debug_build() of
+ true -> "obj.debug";
+ false -> "obj"
+ end,
+ {BaseIncl,
+ filename:join(BaseIncl, Target),
+ {filename:join([Dir, Obj, Target]),
+ filename:join([Dir, "src", Target, "eidefs.mk"])}}
+ end
end,
Lib = link_library("erl_interface",OsType),
Lib1 = link_library("ei",OsType),
@@ -256,6 +264,10 @@ erl_interface(Vars,OsType) ->
{erl_interface_eilib_drv, quote(filename:join(LibPath, Lib1Drv))},
{erl_interface_threadlib, ThreadLib},
{erl_interface_include, quote(filename:nativename(Incl))},
+ {erl_interface_target_include, case TargetIncl of
+ [] -> [];
+ _ -> "-I" ++ quote(filename:nativename(TargetIncl))
+ end},
{erl_interface_mk_include, quote(filename:nativename(MkIncl))}
| Vars].
diff --git a/lib/common_test/test_server/ts_install.erl b/lib/common_test/test_server/ts_install.erl
index 048e5493d2..86f16fa072 100644
--- a/lib/common_test/test_server/ts_install.erl
+++ b/lib/common_test/test_server/ts_install.erl
@@ -112,12 +112,6 @@ get_vars([], name, [], Result) ->
get_vars(_, _, _, _) ->
{error, fatal_bad_conf_vars}.
-config_flags() ->
- case os:getenv("CONFIG_FLAGS") of
- false -> [];
- CF -> string:lexemes(CF, " \t\n")
- end.
-
unix_autoconf(XConf) ->
Configure = filename:absname("configure"),
Flags = proplists:get_value(crossflags,XConf,[]),
@@ -128,7 +122,7 @@ unix_autoconf(XConf) ->
erlang:system_info(threads) /= false],
Debug = [" --enable-debug-mode" ||
string:find(erlang:system_info(system_version),"debug") =/= nomatch],
- MXX_Build = [Y || Y <- config_flags(),
+ MXX_Build = [Y || Y <- string:lexemes(os:getenv("CONFIG_FLAGS", ""), " \t\n"),
Y == "--enable-m64-build"
orelse Y == "--enable-m32-build"],
Args = Host ++ Build ++ Threads ++ Debug ++ " " ++ MXX_Build,
@@ -164,7 +158,7 @@ assign_vars(FlagsStr) ->
assign_all_vars([$$ | Rest], FlagSoFar) ->
{VarName,Rest1} = get_var_name(Rest, []),
- assign_all_vars(Rest1, FlagSoFar ++ assign_var(VarName));
+ assign_all_vars(Rest1, FlagSoFar ++ os:getenv(VarName, ""));
assign_all_vars([Char | Rest], FlagSoFar) ->
assign_all_vars(Rest, FlagSoFar ++ [Char]);
assign_all_vars([], Flag) ->
@@ -177,12 +171,6 @@ get_var_name([Ch | Rest] = Str, VarR) ->
end;
get_var_name([], VarR) ->
{lists:reverse(VarR),[]}.
-
-assign_var(VarName) ->
- case os:getenv(VarName) of
- false -> "";
- Val -> Val
- end.
valid_char(Ch) when Ch >= $a, Ch =< $z -> true;
valid_char(Ch) when Ch >= $A, Ch =< $Z -> true;
@@ -280,7 +268,7 @@ add_vars(Vars0, Opts0) ->
{Opts, [{longnames, LongNames},
{platform_id, PlatformId},
{platform_filename, PlatformFilename},
- {rsh_name, get_rsh_name()},
+ {rsh_name, os:getenv("ERL_RSH", "ssh")},
{platform_label, PlatformLabel},
{ts_net_dir, Mounted},
{erl_flags, []},
@@ -301,16 +289,10 @@ get_testcase_callback() ->
end
end.
-get_rsh_name() ->
- case os:getenv("ERL_RSH") of
- false -> "rsh";
- Str -> Str
- end.
-
platform_id(Vars) ->
{Id,_,_,_} = platform(Vars),
Id.
-
+
platform(Vars) ->
Hostname = hostname(),
diff --git a/lib/compiler/doc/src/compile.xml b/lib/compiler/doc/src/compile.xml
index cfbd4c7fda..5219ba0f5d 100644
--- a/lib/compiler/doc/src/compile.xml
+++ b/lib/compiler/doc/src/compile.xml
@@ -29,7 +29,7 @@
<rev>A</rev>
<file>compile.sgml</file>
</header>
- <module>compile</module>
+ <module since="">compile</module>
<modulesummary>Erlang Compiler</modulesummary>
<description>
<p>This module provides an interface to the standard Erlang
@@ -40,7 +40,7 @@
<funcs>
<func>
- <name>env_compiler_options()</name>
+ <name since="OTP 19.0">env_compiler_options()</name>
<fsummary>
Compiler options defined via the environment variable
<c>ERL_COMPILER_OPTIONS</c>
@@ -53,7 +53,7 @@
</desc>
</func>
<func>
- <name>file(File)</name>
+ <name since="">file(File)</name>
<fsummary>Compiles a file.</fsummary>
<desc>
<p>Is the same as
@@ -63,7 +63,7 @@
</func>
<func>
- <name>file(File, Options) -> CompRet</name>
+ <name since="">file(File, Options) -> CompRet</name>
<fsummary>Compiles a file.</fsummary>
<type>
<v>CompRet = ModRet | BinRet | ErrRet</v>
@@ -416,6 +416,17 @@ module.beam: module.erl \
is not documented, and can change between releases.</p>
</item>
+ <tag><c>no_spawn_compiler_process</c></tag>
+ <item>
+ <p>By default, all code is compiled in a separate process
+ which is terminated at the end of compilation. However,
+ some tools, like Dialyzer or compilers for other BEAM languages,
+ may already manage their own worker processes and spawning
+ an extra process may slow the compilation down.
+ In such scenarios, you can pass this option to stop the
+ compiler from spawning an additional process.</p>
+ </item>
+
<tag><c>no_strict_record_tests</c></tag>
<item>
<p>This option is not recommended.</p>
@@ -684,12 +695,13 @@ module.beam: module.erl \
</note>
<note>
- <p>The options <c>{nowarn_unused_function, FAs}</c>,
- <c>{nowarn_bif_clash, FAs}</c>, and
- <c>{nowarn_deprecated_function, MFAs}</c> are only
- recognized when given in files. They are not affected by
- options <c>warn_unused_function</c>, <c>warn_bif_clash</c>, or
- <c>warn_deprecated_function</c>.</p>
+ <p>Before OTP 22, the option <c>{nowarn_deprecated_function,
+ MFAs}</c> was only recognized when given in the file with
+ attribute <c>-compile()</c>. (The option
+ <c>{nowarn_unused_function,FAs}</c> was incorrectly documented
+ to only work in a file, but it also worked when given in the
+ option list.) Starting from OTP 22, all options that can be
+ given in the file can also be given in the option list.</p>
</note>
<p>For debugging of the compiler, or for pure curiosity,
@@ -718,7 +730,7 @@ module.beam: module.erl \
</func>
<func>
- <name>forms(Forms)</name>
+ <name since="">forms(Forms)</name>
<fsummary>Compiles a list of forms.</fsummary>
<desc>
<p>Is the same as
@@ -728,7 +740,7 @@ module.beam: module.erl \
</func>
<func>
- <name>forms(Forms, Options) -> CompRet</name>
+ <name since="">forms(Forms, Options) -> CompRet</name>
<fsummary>Compiles a list of forms.</fsummary>
<type>
<v>Forms = [Form]</v>
@@ -749,7 +761,7 @@ module.beam: module.erl \
</func>
<func>
- <name>format_error(ErrorDescriptor) -> chars()</name>
+ <name since="">format_error(ErrorDescriptor) -> chars()</name>
<fsummary>Formats an error descriptor.</fsummary>
<type>
<v>ErrorDescriptor = errordesc()</v>
@@ -764,7 +776,7 @@ module.beam: module.erl \
</func>
<func>
- <name>output_generated(Options) -> true | false</name>
+ <name since="">output_generated(Options) -> true | false</name>
<fsummary>Determines whether the compiler generates an output file.</fsummary>
<type>
<v>Options = [term()]</v>
@@ -779,7 +791,7 @@ module.beam: module.erl \
</func>
<func>
- <name>noenv_file(File, Options) -> CompRet</name>
+ <name since="">noenv_file(File, Options) -> CompRet</name>
<fsummary>Compiles a file (ignoring <c>ERL_COMPILER_OPTIONS)</c>.</fsummary>
<desc>
<p>Works like <seealso marker="#file/2">file/2</seealso>,
@@ -789,7 +801,7 @@ module.beam: module.erl \
</func>
<func>
- <name>noenv_forms(Forms, Options) -> CompRet</name>
+ <name since="">noenv_forms(Forms, Options) -> CompRet</name>
<fsummary>Compiles a list of forms (ignoring <c>ERL_COMPILER_OPTIONS)</c>.</fsummary>
<desc>
<p>Works like <seealso marker="#forms/2">forms/2</seealso>,
@@ -799,7 +811,7 @@ module.beam: module.erl \
</func>
<func>
- <name>noenv_output_generated(Options) -> true | false</name>
+ <name since="">noenv_output_generated(Options) -> true | false</name>
<fsummary>Determines whether the compiler generates an output file
(ignoring <c>ERL_COMPILER_OPTIONS)</c>.</fsummary>
<type>
diff --git a/lib/compiler/scripts/.gitignore b/lib/compiler/scripts/.gitignore
new file mode 100644
index 0000000000..4e4eba766d
--- /dev/null
+++ b/lib/compiler/scripts/.gitignore
@@ -0,0 +1 @@
+/smoke-build
diff --git a/lib/compiler/scripts/smoke b/lib/compiler/scripts/smoke
new file mode 100755
index 0000000000..2429f104c0
--- /dev/null
+++ b/lib/compiler/scripts/smoke
@@ -0,0 +1,122 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+-mode(compile).
+
+main(_Args) ->
+ setup(),
+ clone_elixir(),
+ build_elixir(),
+ test_elixir(),
+ setup_mix(),
+ smoke(main),
+ smoke(rabbitmq),
+ halt(0).
+
+setup() ->
+ ScriptsDir = scripts_dir(),
+ SmokeBuildDir = filename:join(ScriptsDir, "smoke-build"),
+ _ = file:make_dir(SmokeBuildDir),
+ ok = file:set_cwd(SmokeBuildDir),
+ ok.
+
+clone_elixir() ->
+ {ok,SmokeDir} = file:get_cwd(),
+ DotGitDir = filename:join([SmokeDir,"elixir",".git"]),
+ ElixirRepo = "[email protected]:elixir-lang/elixir.git",
+ case filelib:is_dir(DotGitDir) of
+ false ->
+ cmd("git clone " ++ ElixirRepo);
+ true ->
+ GetHeadSHA1 = "cd elixir && git rev-parse --verify HEAD",
+ Before = os:cmd(GetHeadSHA1),
+ cmd("cd elixir && git pull --ff-only origin master"),
+ case os:cmd(GetHeadSHA1) of
+ Before ->
+ ok;
+ _After ->
+ %% There were some changes. Clean to force a re-build.
+ cmd("cd elixir && make clean")
+ end
+ end.
+
+build_elixir() ->
+ cmd("cd elixir && make compile").
+
+test_elixir() ->
+ cmd("cd elixir && make test_stdlib").
+
+setup_mix() ->
+ MixExsFile = filename:join(scripts_dir(), "smoke-mix.exs"),
+ {ok,MixExs} = file:read_file(MixExsFile),
+ ok = file:write_file("mix.exs", MixExs),
+
+ {ok,SmokeDir} = file:get_cwd(),
+ ElixirBin = filename:join([SmokeDir,"elixir","bin"]),
+ PATH = ElixirBin ++ ":" ++ os:getenv("PATH"),
+ os:putenv("PATH", PATH),
+ mix("local.rebar --force"),
+ ok.
+
+smoke(Set) ->
+ os:putenv("SMOKE_DEPS_SET", atom_to_list(Set)),
+ _ = file:delete("mix.lock"),
+ cmd("touch mix.exs"),
+ mix("deps.clean --all"),
+ mix("deps.get"),
+ mix("deps.compile"),
+ ok.
+
+scripts_dir() ->
+ Root = code:lib_dir(compiler),
+ filename:join(Root, "scripts").
+
+mix(Cmd) ->
+ cmd("mix " ++ Cmd).
+
+cmd(Cmd) ->
+ run("sh", ["-c",Cmd]).
+
+run(Program0, Args) ->
+ Program = case os:find_executable(Program0) of
+ Path when is_list(Path) ->
+ Path;
+ false ->
+ abort("Unable to find program: ~s\n", [Program0])
+ end,
+ Cmd = case {Program0,Args} of
+ {"sh",["-c"|ShCmd]} ->
+ ShCmd;
+ {_,_} ->
+ lists:join(" ", [Program0|Args])
+ end,
+ io:format("\n# ~s\n", [Cmd]),
+ Options = [{args,Args},binary,exit_status,stderr_to_stdout],
+ try open_port({spawn_executable,Program}, Options) of
+ Port ->
+ case run_loop(Port, <<>>) of
+ 0 ->
+ ok;
+ ExitCode ->
+ abort("*** Failed with exit code: ~p\n",
+ [ExitCode])
+ end
+ catch
+ error:_ ->
+ abort("Failed to execute ~s\n", [Program0])
+ end.
+
+run_loop(Port, Output) ->
+ receive
+ {Port,{exit_status,Status}} ->
+ Status;
+ {Port,{data,Bin}} ->
+ io:put_chars(Bin),
+ run_loop(Port, <<Output/binary,Bin/binary>>);
+ Msg ->
+ io:format("L: ~p~n", [Msg]),
+ run_loop(Port, Output)
+ end.
+
+abort(Format, Args) ->
+ io:format(Format, Args),
+ halt(1).
diff --git a/lib/compiler/scripts/smoke-mix.exs b/lib/compiler/scripts/smoke-mix.exs
new file mode 100644
index 0000000000..82ae3370fe
--- /dev/null
+++ b/lib/compiler/scripts/smoke-mix.exs
@@ -0,0 +1,95 @@
+defmodule Smoke.MixProject do
+ use Mix.Project
+
+ def project do
+ [
+ app: :smoke,
+ version: "0.1.0",
+ elixir: "~> 1.8",
+ start_permanent: Mix.env() == :prod,
+ deps: deps()
+ ]
+ end
+
+ # Run "mix help compile.app" to learn about applications.
+ def application do
+ [
+ extra_applications: [:logger]
+ ]
+ end
+
+ # Run "mix help deps" to learn about dependencies.
+ defp deps do
+ case :os.getenv('SMOKE_DEPS_SET') do
+ 'main' ->
+ [
+ {:bear, "~> 0.8.7"},
+ {:cloudi_core, "~> 1.7"},
+ {:concuerror, "~> 0.20.0"},
+ {:cowboy, "~> 2.6.1"},
+ {:ecto, "~> 3.0.6"},
+ {:ex_doc, "~> 0.19.3"},
+ {:distillery, "~> 2.0.12"},
+ {:erlydtl, "~> 0.12.1"},
+ {:gen_smtp, "~> 0.13.0"},
+ {:getopt, "~> 1.0.1"},
+ {:gettext, "~> 0.16.1"},
+ {:gpb, "~> 4.6"},
+ {:gproc, "~> 0.8.0"},
+ {:graphql, "~> 0.15.0", hex: :graphql_erl},
+ {:hackney, "~> 1.15.0"},
+ {:ibrowse, "~> 4.4.1"},
+ {:jose, "~> 1.9.0"},
+ {:lager, "~> 3.6"},
+ {:locus, "~> 1.6"},
+ {:nimble_parsec, "~> 0.5.0"},
+ {:phoenix, "~> 1.4.0"},
+ {:riak_pb, "~> 2.3"},
+ {:scalaris, git: "https://github.com/scalaris-team/scalaris",
+ compile: build_scalaris()},
+ {:tdiff, "~> 0.1.2"},
+ {:webmachine, "~> 1.11"},
+ {:wings, git: "https://github.com/dgud/wings.git",
+ compile: build_wings()},
+ {:zotonic_stdlib, "~> 1.0"},
+ ]
+ 'rabbitmq' ->
+ [{:rabbit_common, "~> 3.7"}]
+ _ ->
+ []
+ end
+ end
+
+ defp build_scalaris do
+ # Only compile the Erlang code.
+
+ """
+ echo '-include("rt_simple.hrl").' >include/rt.hrl
+ (cd src && erlc -W0 -I ../include -I ../contrib/log4erl/include -I ../contrib/yaws/include *.erl)
+ (cd src/comm_layer && erlc -W0 -I ../../include -I *.erl)
+ (cd src/cp && erlc -W0 -I ../../include -I *.erl)
+ (cd src/crdt && erlc -W0 -I ../../include -I *.erl)
+ (cd src/json && erlc -W0 -I ../../include -I *.erl)
+ (cd src/paxos && erlc -W0 -I ../../include -I *.erl)
+ (cd src/rbr && erlc -W0 -I ../../include -I *.erl)
+ (cd src/rrepair && erlc -W0 -I ../../include -I *.erl)
+ (cd src/time && erlc -W0 -I ../../include -I *.erl)
+ (cd src/transactions && erlc -W0 -I ../../include -I *.erl)
+ (cd src/tx && erlc -W0 -I ../../include -I *.erl)
+ """
+ end
+
+ defp build_wings do
+ # If the Erlang system is not installed, the build will
+ # crash in plugins_src/accel when attempting to build
+ # the accel driver. Since there is very little Erlang code in
+ # the directory, skip the entire directory.
+
+ """
+ echo "all:\n\t" >plugins_src/accel/Makefile
+ git commit -a -m'Disable for smoke testing'
+ git tag -a -m'Smoke test' vsmoke_test
+ make
+ """
+ end
+end
diff --git a/lib/compiler/src/Makefile b/lib/compiler/src/Makefile
index 2408c76b48..c971e8844d 100644
--- a/lib/compiler/src/Makefile
+++ b/lib/compiler/src/Makefile
@@ -49,10 +49,7 @@ MODULES = \
beam_a \
beam_asm \
beam_block \
- beam_bs \
- beam_bsm \
beam_clean \
- beam_dead \
beam_dict \
beam_disasm \
beam_except \
@@ -61,12 +58,20 @@ MODULES = \
beam_listing \
beam_opcodes \
beam_peep \
- beam_receive \
- beam_reorder \
- beam_record \
- beam_split \
+ beam_ssa \
+ beam_ssa_bsm \
+ beam_ssa_codegen \
+ beam_ssa_dead \
+ beam_ssa_funs \
+ beam_ssa_lint \
+ beam_ssa_opt \
+ beam_ssa_pp \
+ beam_ssa_pre_codegen \
+ beam_ssa_recv \
+ beam_ssa_share \
+ beam_ssa_type \
+ beam_kernel_to_ssa \
beam_trim \
- beam_type \
beam_utils \
beam_validator \
beam_z \
@@ -85,12 +90,10 @@ MODULES = \
rec_env \
sys_core_alias \
sys_core_bsm \
- sys_core_dsetel \
sys_core_fold \
sys_core_fold_lists \
sys_core_inline \
sys_pre_attributes \
- v3_codegen \
v3_core \
v3_kernel \
v3_kernel_pp
@@ -99,6 +102,8 @@ BEAM_H = $(wildcard ../priv/beam_h/*.h)
HRL_FILES= \
beam_disasm.hrl \
+ beam_ssa_opt.hrl \
+ beam_ssa.hrl \
core_parse.hrl \
v3_kernel.hrl
@@ -185,7 +190,17 @@ release_docs_spec:
# ----------------------------------------------------
$(EBIN)/beam_disasm.beam: $(EGEN)/beam_opcodes.hrl beam_disasm.hrl
-$(EBIN)/beam_listing.beam: core_parse.hrl v3_kernel.hrl
+$(EBIN)/beam_listing.beam: core_parse.hrl v3_kernel.hrl beam_ssa.hrl
+$(EBIN)/beam_kernel_to_ssa.beam: v3_kernel.hrl beam_ssa.hrl
+$(EBIN)/beam_ssa.beam: beam_ssa.hrl
+$(EBIN)/beam_ssa_codegen.beam: beam_ssa.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_pre_codegen.beam: beam_ssa.hrl
+$(EBIN)/beam_ssa_recv.beam: beam_ssa.hrl
+$(EBIN)/beam_ssa_type.beam: beam_ssa.hrl
$(EBIN)/cerl.beam: core_parse.hrl
$(EBIN)/compile.beam: core_parse.hrl ../../stdlib/include/erl_compile.hrl
$(EBIN)/core_lib.beam: core_parse.hrl
@@ -193,11 +208,9 @@ $(EBIN)/core_lint.beam: core_parse.hrl
$(EBIN)/core_parse.beam: core_parse.hrl $(EGEN)/core_parse.erl
$(EBIN)/core_pp.beam: core_parse.hrl
$(EBIN)/sys_core_alias.beam: core_parse.hrl
-$(EBIN)/sys_core_dsetel.beam: core_parse.hrl
$(EBIN)/sys_core_fold.beam: core_parse.hrl
$(EBIN)/sys_core_fold_lists.beam: core_parse.hrl
$(EBIN)/sys_core_inline.beam: core_parse.hrl
-$(EBIN)/v3_codegen.beam: v3_kernel.hrl
$(EBIN)/v3_core.beam: core_parse.hrl
$(EBIN)/v3_kernel.beam: core_parse.hrl v3_kernel.hrl
$(EBIN)/v3_kernel_pp.beam: v3_kernel.hrl
diff --git a/lib/compiler/src/beam_a.erl b/lib/compiler/src/beam_a.erl
index 6fd4ace540..0bccad1ecd 100644
--- a/lib/compiler/src/beam_a.erl
+++ b/lib/compiler/src/beam_a.erl
@@ -39,14 +39,29 @@ function({function,Name,Arity,CLabel,Is0}) ->
%% Remove unusued labels for cleanliness and to help
%% optimization passes and HiPE.
- Is = beam_jump:remove_unused_labels(Is1),
- {function,Name,Arity,CLabel,Is}
+ Is2 = beam_jump:remove_unused_labels(Is1),
+
+ %% Some optimization passes can't handle consecutive labels.
+ %% Coalesce multiple consecutive labels.
+ Is = coalesce_consecutive_labels(Is2, [], []),
+
+ {function,Name,Arity,CLabel,Is}
catch
Class:Error:Stack ->
io:fwrite("Function: ~w/~w\n", [Name,Arity]),
erlang:raise(Class, Error, Stack)
end.
+rename_instrs([{test,is_eq_exact,_,[Dst,Src]}=Test,
+ {move,Src,Dst}|Is]) ->
+ %% The move instruction is not needed.
+ rename_instrs([Test|Is]);
+rename_instrs([{test,is_eq_exact,_,[Same,Same]}|Is]) ->
+ %% Same literal or same register. Will always succeed.
+ rename_instrs(Is);
+rename_instrs([{loop_rec,{f,Fail},{x,0}},{loop_rec_end,_},{label,Fail}|Is]) ->
+ %% This instruction sequence does nothing.
+ rename_instrs(Is);
rename_instrs([{apply_last,A,N}|Is]) ->
[{apply,A},{deallocate,N},return|rename_instrs(Is)];
rename_instrs([{call_last,A,F,N}|Is]) ->
@@ -85,8 +100,12 @@ rename_instr({bs_put_utf16=I,F,Fl,Src}) ->
{bs_put,F,{I,Fl},[Src]};
rename_instr({bs_put_utf32=I,F,Fl,Src}) ->
{bs_put,F,{I,Fl},[Src]};
-rename_instr({bs_put_string,_,_}=I) ->
- {bs_put,{f,0},I,[]};
+rename_instr({bs_put_string,_,{string,String}}) ->
+ %% Only happens when compiling from .S files. In old
+ %% .S files, String is a list. In .S in OTP 22 and later,
+ %% String is a binary.
+ {bs_put,{f,0},{bs_put_binary,8,{field_flags,[unsigned,big]}},
+ [{atom,all},{literal,iolist_to_binary([String])}]};
rename_instr({bs_add=I,F,[Src1,Src2,U],Dst}) when is_integer(U) ->
{bif,I,F,[Src1,Src2,{integer,U}],Dst};
rename_instr({bs_utf8_size=I,F,Src,Dst}) ->
@@ -103,16 +122,14 @@ rename_instr({bs_private_append=I,F,Sz,U,Src,Flags,Dst}) ->
{bs_init,F,{I,U,Flags},none,[Sz,Src],Dst};
rename_instr(bs_init_writable=I) ->
{bs_init,{f,0},I,1,[{x,0}],{x,0}};
-rename_instr({test,Op,F,[Ctx,Bits,{string,Str}]}) ->
- %% When compiling from a .S file.
- <<Bs:Bits/bits,_/bits>> = list_to_binary(Str),
- {test,Op,F,[Ctx,Bs]};
rename_instr({put_map_assoc,Fail,S,D,R,L}) ->
{put_map,Fail,assoc,S,D,R,L};
rename_instr({put_map_exact,Fail,S,D,R,L}) ->
{put_map,Fail,exact,S,D,R,L};
rename_instr({test,has_map_fields,Fail,Src,{list,List}}) ->
{test,has_map_fields,Fail,[Src|List]};
+rename_instr({test,is_nil,Fail,[Src]}) ->
+ {test,is_eq_exact,Fail,[Src,nil]};
rename_instr({select_val=I,Reg,Fail,{list,List}}) ->
{select,I,Reg,Fail,List};
rename_instr({select_tuple_arity=I,Reg,Fail,{list,List}}) ->
@@ -120,3 +137,11 @@ rename_instr({select_tuple_arity=I,Reg,Fail,{list,List}}) ->
rename_instr(send) ->
{call_ext,2,send};
rename_instr(I) -> I.
+
+coalesce_consecutive_labels([{label,L}=Lbl,{label,Alias}|Is], Replace, Acc) ->
+ coalesce_consecutive_labels([Lbl|Is], [{Alias,L}|Replace], Acc);
+coalesce_consecutive_labels([I|Is], Replace, Acc) ->
+ coalesce_consecutive_labels(Is, Replace, [I|Acc]);
+coalesce_consecutive_labels([], Replace, Acc) ->
+ D = maps:from_list(Replace),
+ beam_utils:replace_labels(Acc, [], D, fun(L) -> L end).
diff --git a/lib/compiler/src/beam_asm.erl b/lib/compiler/src/beam_asm.erl
index df0321e85a..bc1290f6fd 100644
--- a/lib/compiler/src/beam_asm.erl
+++ b/lib/compiler/src/beam_asm.erl
@@ -424,8 +424,8 @@ encode_arg({f, W}, Dict) ->
{encode(?tag_f, W), Dict};
%% encode_arg({'char', C}, Dict) ->
%% {encode(?tag_h, C), Dict};
-encode_arg({string, String}, Dict0) ->
- {Offset, Dict} = beam_dict:string(String, Dict0),
+encode_arg({string, BinString}, Dict0) when is_binary(BinString) ->
+ {Offset, Dict} = beam_dict:string(BinString, Dict0),
{encode(?tag_u, Offset), Dict};
encode_arg({extfunc, M, F, A}, Dict0) ->
{Index, Dict} = beam_dict:import(M, F, A, Dict0),
diff --git a/lib/compiler/src/beam_block.erl b/lib/compiler/src/beam_block.erl
index fe43163455..707974b2c1 100644
--- a/lib/compiler/src/beam_block.erl
+++ b/lib/compiler/src/beam_block.erl
@@ -17,39 +17,24 @@
%%
%% %CopyrightEnd%
%%
-%% Purpose : Partitions assembly instructions into basic blocks and
-%% optimizes them.
+%% Purpose: Partition BEAM instructions into basic blocks.
-module(beam_block).
-export([module/2]).
--import(lists, [reverse/1,reverse/2,member/2]).
+-import(lists, [keysort/2,reverse/1,splitwith/2]).
-spec module(beam_utils:module_code(), [compile:option()]) ->
{'ok',beam_utils:module_code()}.
-module({Mod,Exp,Attr,Fs0,Lc}, Opts) ->
- Blockify = not member(no_blockify, Opts),
- Fs = [function(F, Blockify) || F <- Fs0],
+module({Mod,Exp,Attr,Fs0,Lc}, _Opts) ->
+ Fs = [function(F) || F <- Fs0],
{ok,{Mod,Exp,Attr,Fs,Lc}}.
-function({function,Name,Arity,CLabel,Is0}, Blockify) ->
+function({function,Name,Arity,CLabel,Is0}) ->
try
- %% Collect basic blocks and optimize them.
- Is1 = case Blockify of
- false -> Is0;
- true -> blockify(Is0)
- end,
- Is2 = embed_lines(Is1),
- Is3 = local_cse(Is2),
- Is4 = beam_utils:anno_defs(Is3),
- Is5 = move_allocates(Is4),
- Is6 = beam_utils:live_opt(Is5),
- Is7 = opt_blocks(Is6),
- Is8 = beam_utils:delete_annos(Is7),
- Is = opt_allocs(Is8),
-
- %% Done.
+ Is1 = blockify(Is0),
+ Is = embed_lines(Is1),
{function,Name,Arity,CLabel,Is}
catch
Class:Error:Stack ->
@@ -64,14 +49,12 @@ function({function,Name,Arity,CLabel,Is0}, Blockify) ->
blockify(Is) ->
blockify(Is, []).
-blockify([{loop_rec,{f,Fail},{x,0}},{loop_rec_end,_Lbl},{label,Fail}|Is], Acc) ->
- %% Useless instruction sequence.
- blockify(Is, Acc);
blockify([I|Is0]=IsAll, Acc) ->
case collect(I) of
error -> blockify(Is0, [I|Acc]);
Instr when is_tuple(Instr) ->
- {Block,Is} = collect_block(IsAll),
+ {Block0,Is} = collect_block(IsAll),
+ Block = sort_moves(Block0),
blockify(Is, [{block,Block}|Acc])
end;
blockify([], Acc) -> reverse(Acc).
@@ -80,12 +63,10 @@ collect_block(Is) ->
collect_block(Is, []).
collect_block([{allocate,N,R}|Is0], Acc) ->
- {Inits,Is} = lists:splitwith(fun ({init,{y,_}}) -> true;
- (_) -> false
- end, Is0),
+ {Inits,Is} = splitwith(fun ({init,{y,_}}) -> true;
+ (_) -> false
+ end, Is0),
collect_block(Is, [{set,[],[],{alloc,R,{nozero,N,0,Inits}}}|Acc]);
-collect_block([{allocate_zero,Ns,R},{test_heap,Nh,R}|Is], Acc) ->
- collect_block(Is, [{set,[],[],{alloc,R,{zero,Ns,Nh,[]}}}|Acc]);
collect_block([I|Is]=Is0, Acc) ->
case collect(I) of
error -> {reverse(Acc),Is0};
@@ -100,23 +81,20 @@ collect({allocate_heap,Ns,Nh,R}) -> {set,[],[],{alloc,R,{nozero,Ns,Nh,[]}}};
collect({allocate_heap_zero,Ns,Nh,R}) -> {set,[],[],{alloc,R,{zero,Ns,Nh,[]}}};
collect({init,D}) -> {set,[D],[],init};
collect({test_heap,N,R}) -> {set,[],[],{alloc,R,{nozero,nostack,N,[]}}};
-collect({bif,N,F,As,D}) -> {set,[D],As,{bif,N,F}};
-collect({gc_bif,N,F,R,As,D}) -> {set,[D],As,{alloc,R,{gc_bif,N,F}}};
+collect({bif,N,{f,0},As,D}) -> {set,[D],As,{bif,N,{f,0}}};
+collect({gc_bif,N,{f,0},R,As,D}) -> {set,[D],As,{alloc,R,{gc_bif,N,{f,0}}}};
collect({move,S,D}) -> {set,[D],[S],move};
collect({put_list,S1,S2,D}) -> {set,[D],[S1,S2],put_list};
collect({put_tuple,A,D}) -> {set,[D],[],{put_tuple,A}};
collect({put,S}) -> {set,[],[S],put};
+collect({put_tuple2,D,{list,Els}}) -> {set,[D],Els,put_tuple2};
collect({get_tuple_element,S,I,D}) -> {set,[D],[S],{get_tuple_element,I}};
collect({set_tuple_element,S,D,I}) -> {set,[],[S,D],{set_tuple_element,I}};
collect({get_hd,S,D}) -> {set,[D],[S],get_hd};
collect({get_tl,S,D}) -> {set,[D],[S],get_tl};
collect(remove_message) -> {set,[],[],remove_message};
-collect({put_map,F,Op,S,D,R,{list,Puts}}) ->
- {set,[D],[S|Puts],{alloc,R,{put_map,Op,F}}};
-collect({'catch'=Op,R,L}) ->
- {set,[R],[],{try_catch,Op,L}};
-collect({'try'=Op,R,L}) ->
- {set,[R],[],{try_catch,Op,L}};
+collect({put_map,{f,0},Op,S,D,R,{list,Puts}}) ->
+ {set,[D],[S|Puts],{alloc,R,{put_map,Op,{f,0}}}};
collect(fclearerror) -> {set,[],[],fclearerror};
collect({fcheckerror,{f,0}}) -> {set,[],[],fcheckerror};
collect({fmove,S,D}) -> {set,[D],[S],fmove};
@@ -137,557 +115,39 @@ embed_lines([{block,B2},{line,_}=Line,{block,B1}|T], Acc) ->
embed_lines([{block,B1},{line,_}=Line|T], Acc) ->
B = {block,[{set,[],[],Line}|B1]},
embed_lines([B|T], Acc);
-embed_lines([{block,B2},{block,B1}|T], Acc) ->
- %% This can only happen when beam_block is run for
- %% the second time.
- B = {block,B1++B2},
- embed_lines([B|T], Acc);
embed_lines([I|Is], Acc) ->
embed_lines(Is, [I|Acc]);
embed_lines([], Acc) -> Acc.
-opt_blocks([{block,Bl0}|Is]) ->
- %% The live annotation at the beginning is not useful.
- [{'%anno',_}|Bl] = Bl0,
- [{block,opt_block(Bl)}|opt_blocks(Is)];
-opt_blocks([I|Is]) ->
- [I|opt_blocks(Is)];
-opt_blocks([]) -> [].
-
-opt_block(Is0) ->
- find_fixpoint(fun(Is) ->
- opt_tuple_element(opt(Is))
- end, Is0).
-
-find_fixpoint(OptFun, Is0) ->
- case OptFun(Is0) of
- Is0 -> Is0;
- Is1 -> find_fixpoint(OptFun, Is1)
- end.
-
-%% move_allocates(Is0) -> Is
-%% Move allocate instructions upwards in the instruction stream
-%% (within the same block), in the hope of getting more possibilities
-%% for optimizing away moves later.
-%%
-%% For example, we can transform the following instructions:
-%%
-%% get_tuple_element x(1) Element => x(2)
-%% allocate_zero StackSize 3 %% x(0), x(1), x(2) are live
-%%
-%% to the following instructions:
-%%
-%% allocate_zero StackSize 2 %% x(0) and x(1) are live
-%% get_tuple_element x(1) Element => x(2)
-%%
-%% NOTE: Since the beam_reorder pass has been run, it is no longer
-%% safe to assume that if x(N) is initialized, then all lower-numbered
-%% x registers are also initialized.
-%%
-%% For example, we must be careful when transforming the following
-%% instructions:
-%%
-%% get_tuple_element x(0) Element => x(1)
-%% allocate_zero StackSize 3 %x(0), x(1), x(2) are live
-%%
-%% to the following instructions:
-%%
-%% allocate_zero StackSize 3
-%% get_tuple_element x(0) Element => x(1)
-%%
-%% The transformation is safe if and only if x(1) has been
-%% initialized previously. We will use the annotations added by
-%% beam_utils:anno_defs/1 to determine whether x(a) has been
-%% initialized.
-
-move_allocates([{block,Bl0}|Is]) ->
- Bl = move_allocates_1(reverse(Bl0), []),
- [{block,Bl}|move_allocates(Is)];
-move_allocates([I|Is]) ->
- [I|move_allocates(Is)];
-move_allocates([]) -> [].
-
-move_allocates_1([{'%anno',_}|Is], Acc) ->
- move_allocates_1(Is, Acc);
-move_allocates_1([I|Is], [{set,[],[],{alloc,Live0,Info0}}|Acc]=Acc0) ->
- case alloc_may_pass(I) of
- false ->
- move_allocates_1(Is, [I|Acc0]);
- true ->
- case alloc_live_regs(I, Is, Live0) of
- not_possible ->
- move_allocates_1(Is, [I|Acc0]);
- Live when is_integer(Live) ->
- Info = safe_info(Info0),
- A = {set,[],[],{alloc,Live,Info}},
- move_allocates_1(Is, [A,I|Acc])
- end
- end;
-move_allocates_1([I|Is], Acc) ->
- move_allocates_1(Is, [I|Acc]);
-move_allocates_1([], Acc) -> Acc.
-
-alloc_may_pass({set,_,[{fr,_}],fmove}) -> false;
-alloc_may_pass({set,_,_,{alloc,_,_}}) -> false;
-alloc_may_pass({set,_,_,{set_tuple_element,_}}) -> false;
-alloc_may_pass({set,_,_,put_list}) -> false;
-alloc_may_pass({set,_,_,put}) -> false;
-alloc_may_pass({set,_,_,_}) -> true.
-
-safe_info({nozero,Stack,Heap,_}) ->
- %% nozero is not safe if the allocation instruction is moved
- %% upwards past an instruction that may throw an exception
- %% (such as element/2).
- {zero,Stack,Heap,[]};
-safe_info(Info) -> Info.
-
-%% opt([Instruction]) -> [Instruction]
-%% Optimize the instruction stream inside a basic block.
-
-opt([{set,[X],[X],move}|Is]) -> opt(Is);
-opt([{set,[Dst],_,move},{set,[Dst],[Src],move}=I|Is]) when Dst =/= Src ->
- opt([I|Is]);
-opt([{set,[{x,0}],[S1],move}=I1,{set,[D2],[{x,0}],move}|Is]) ->
- opt([I1,{set,[D2],[S1],move}|Is]);
-opt([{set,[{x,0}],[S1],move}=I1,{set,[D2],[S2],move}|Is0]) when S1 =/= D2 ->
- %% Place move S x0 at the end of move sequences so that
- %% loader can merge with the following instruction
- {Ds,Is} = opt_moves([D2], Is0),
- [{set,Ds,[S2],move}|opt([I1|Is])];
-opt([{set,_,_,{line,_}}=Line1,
- {set,[D1],[{integer,Idx1},Reg],{bif,element,{f,0}}}=I1,
- {set,_,_,{line,_}}=Line2,
- {set,[D2],[{integer,Idx2},Reg],{bif,element,{f,0}}}=I2|Is])
- when Idx1 < Idx2, D1 =/= D2, D1 =/= Reg, D2 =/= Reg ->
- opt([Line2,I2,Line1,I1|Is]);
-opt([{set,[D1],[{integer,Idx1},Reg],{bif,element,{f,L}}}=I1,
- {set,[D2],[{integer,Idx2},Reg],{bif,element,{f,L}}}=I2|Is])
- when Idx1 < Idx2, D1 =/= D2, D1 =/= Reg, D2 =/= Reg ->
- opt([I2,I1|Is]);
-opt([{set,Hd0,Cons,get_hd}=GetHd,
- {set,Tl0,Cons,get_tl}=GetTl|Is0]) ->
- case {opt_moves(Hd0, [GetTl|Is0]),opt_moves(Tl0, [GetHd|Is0])} of
- {{Hd0,Is},{Tl0,_}} ->
- [GetHd|opt(Is)];
- {{Hd,Is},{Tl0,_}} ->
- [{set,Hd,Cons,get_hd}|opt(Is)];
- {{_,_},{Tl,Is}} ->
- [{set,Tl,Cons,get_tl}|opt(Is)]
- end;
-opt([{set,Ds0,Ss,Op}|Is0]) ->
- {Ds,Is} = opt_moves(Ds0, Is0),
- [{set,Ds,Ss,Op}|opt(Is)];
-opt([{'%anno',_}=I|Is]) ->
- [I|opt(Is)];
-opt([]) -> [].
-
-%% opt_moves([Dest], [Instruction]) -> {[Dest],[Instruction]}
-%% For each Dest, does the optimization described in opt_move/2.
-
-opt_moves([], Is0) -> {[],Is0};
-opt_moves([D0]=Ds, Is0) ->
- case opt_move(D0, Is0) of
- not_possible -> {Ds,Is0};
- {D1,Is} -> {[D1],Is}
- end.
-
-%% opt_move(Dest, [Instruction]) -> {UpdatedDest,[Instruction]} | not_possible
-%% If there is a {move,Dest,FinalDest} instruction
-%% in the instruction stream, remove the move instruction
-%% and let FinalDest be the destination.
-
-opt_move(Dest, Is) ->
- opt_move_1(Dest, Is, []).
-
-opt_move_1(R, [{set,[D],[R],move}|Is0], Acc) ->
- %% Provided that the source register is killed by instructions
- %% that follow, the optimization is safe.
- case eliminate_use_of_from_reg(Is0, R, D) of
- {yes,Is} -> opt_move_rev(D, Acc, Is);
- no -> not_possible
- end;
-opt_move_1(_R, [{set,_,_,{alloc,_,_}}|_], _) ->
- %% The optimization is either not possible or not safe.
- %%
- %% If R is an X register killed by allocation, the optimization is
- %% not safe. On the other hand, if the X register is killed, there
- %% will not follow a 'move' instruction with this X register as
- %% the source.
- %%
- %% If R is a Y register, the optimization is still not safe
- %% because the new target register is an X register that cannot
- %% safely pass the alloc instruction.
- not_possible;
-opt_move_1(R, [{set,_,_,_}=I|Is], Acc) ->
- %% If the source register is either killed or used by this
- %% instruction, the optimimization is not possible.
- case is_killed_or_used(R, I) of
- true -> not_possible;
- false -> opt_move_1(R, Is, [I|Acc])
- end;
-opt_move_1(_, _, _) ->
- not_possible.
-
-%% opt_tuple_element([Instruction]) -> [Instruction]
-%% If possible, move get_tuple_element instructions forward
-%% in the instruction stream to a move instruction, eliminating
-%% the move instruction. Example:
-%%
-%% get_tuple_element Tuple Pos Dst1
-%% ...
-%% move Dst1 Dst2
-%%
-%% This code may be possible to rewrite to:
-%%
-%% %%(Moved get_tuple_element instruction)
-%% ...
-%% get_tuple_element Tuple Pos Dst2
-%%
-
-opt_tuple_element([{set,[D],[S],{get_tuple_element,_}}=I|Is0]) ->
- case opt_tuple_element_1(Is0, I, {S,D}, []) of
- no ->
- [I|opt_tuple_element(Is0)];
- {yes,Is} ->
- opt_tuple_element(Is)
- end;
-opt_tuple_element([I|Is]) ->
- [I|opt_tuple_element(Is)];
-opt_tuple_element([]) -> [].
-
-opt_tuple_element_1([{set,_,_,{alloc,_,_}}|_], _, _, _) ->
- no;
-opt_tuple_element_1([{set,_,_,{try_catch,_,_}}|_], _, _, _) ->
- no;
-opt_tuple_element_1([{set,[D],[S],move}|Is0], I0, {_,S}, Acc) ->
- case eliminate_use_of_from_reg(Is0, S, D) of
- no ->
- no;
- {yes,Is1} ->
- {set,[S],Ss,Op} = I0,
- I = {set,[D],Ss,Op},
- case opt_move_rev(S, Acc, [I|Is1]) of
- not_possible ->
- %% Not safe because the move of the
- %% get_tuple_element instruction would cause the
- %% result of a previous instruction to be ignored.
- no;
- {_,Is} ->
- {yes,Is}
- end
- end;
-opt_tuple_element_1([{set,Ds,Ss,_}=I|Is], MovedI, {S,D}=Regs, Acc) ->
- case member(S, Ds) orelse member(D, Ss) of
- true ->
- no;
- false ->
- opt_tuple_element_1(Is, MovedI, Regs, [I|Acc])
- end;
-opt_tuple_element_1(_, _, _, _) -> no.
-
-%% Reverse the instructions, while checking that there are no
-%% instructions that would interfere with using the new destination
-%% register (D).
-
-opt_move_rev(D, [I|Is], Acc) ->
- case is_killed_or_used(D, I) of
- true -> not_possible;
- false -> opt_move_rev(D, Is, [I|Acc])
- end;
-opt_move_rev(D, [], Acc) -> {D,Acc}.
-
-%% is_killed_or_used(Register, {set,_,_,_}) -> bool()
-%% Test whether the register is used by the instruction.
-
-is_killed_or_used(R, {set,Ss,Ds,_}) ->
- member(R, Ds) orelse member(R, Ss).
-
-%% eliminate_use_of_from_reg([Instruction], FromRegister, ToRegister, Acc) ->
-%% {yes,Is} | no
-%% Eliminate any use of FromRegister in the instruction sequence
-%% by replacing uses of FromRegister with ToRegister. If FromRegister
-%% is referenced by an allocation instruction, return 'no' to indicate
-%% that FromRegister is still used and that the optimization is not
-%% possible.
-
-eliminate_use_of_from_reg(Is, From, To) ->
- try
- eliminate_use_of_from_reg(Is, From, To, [])
- catch
- throw:not_possible ->
- no
- end.
-
-eliminate_use_of_from_reg([{set,_,_,{alloc,Live,_}}|_]=Is0, {x,X}, _, Acc) ->
- if
- X < Live ->
- no;
- true ->
- {yes,reverse(Acc, Is0)}
- end;
-eliminate_use_of_from_reg([{set,Ds,Ss0,Op}=I0|Is], From, To, Acc) ->
- ensure_safe_tuple(I0, To),
- I = case member(From, Ss0) of
- true ->
- Ss = [case S of
- From -> To;
- _ -> S
- end || S <- Ss0],
- {set,Ds,Ss,Op};
- false ->
- I0
- end,
- case member(From, Ds) of
- true ->
- {yes,reverse(Acc, [I|Is])};
- false ->
- case member(To, Ds) of
- true ->
- case beam_utils:is_killed_block(From, Is) of
- true ->
- {yes,reverse(Acc, [I|Is])};
- false ->
- no
- end;
- false ->
- eliminate_use_of_from_reg(Is, From, To, [I|Acc])
- end
- end;
-eliminate_use_of_from_reg([I]=Is, From, _To, Acc) ->
- case beam_utils:is_killed_block(From, [I]) of
- true ->
- {yes,reverse(Acc, Is)};
- false ->
- no
- end.
-
-ensure_safe_tuple({set,[To],[],{put_tuple,_}}, To) ->
- throw(not_possible);
-ensure_safe_tuple(_, _) -> ok.
-
-%% opt_allocs(Instructions) -> Instructions. Optimize allocate
-%% instructions inside blocks. If safe, replace an allocate_zero
-%% instruction with the slightly cheaper allocate instruction.
-
-opt_allocs(Is) ->
- D = beam_utils:index_labels(Is),
- opt_allocs_1(Is, D).
-
-opt_allocs_1([{block,Bl0}|Is], D) ->
- Bl = opt_alloc(Bl0, {D,Is}),
- [{block,Bl}|opt_allocs_1(Is, D)];
-opt_allocs_1([I|Is], D) ->
- [I|opt_allocs_1(Is, D)];
-opt_allocs_1([], _) -> [].
-
-%% opt_alloc(Instructions) -> Instructions'
-%% Optimises all allocate instructions.
-
-opt_alloc([{set,[],[],{alloc,Live0,Info0}},
- {set,[],[],{alloc,Live,Info}}|Is], D) ->
- Live = Live0, %Assertion.
- Alloc = combine_alloc(Info0, Info),
- I = {set,[],[],{alloc,Live,Alloc}},
- opt_alloc([I|Is], D);
-opt_alloc([{set,[],[],{alloc,R,{_,Ns,Nh,[]}}}|Is], D) ->
- [{set,[],[],opt_alloc(Is, D, Ns, Nh, R)}|Is];
-opt_alloc([I|Is], D) -> [I|opt_alloc(Is, D)];
-opt_alloc([], _) -> [].
-
-combine_alloc({_,Ns,Nh1,Init}, {_,nostack,Nh2,[]}) ->
- {zero,Ns,beam_utils:combine_heap_needs(Nh1, Nh2),Init}.
-
-%% opt_alloc(Instructions, FrameSize, HeapNeed, LivingRegs) -> [Instr]
-%% Generates the optimal sequence of instructions for
-%% allocating and initalizing the stack frame and needed heap.
-
-opt_alloc(_Is, _D, nostack, Nh, LivingRegs) ->
- {alloc,LivingRegs,{nozero,nostack,Nh,[]}};
-opt_alloc(Bl, {D,OuterIs}, Ns, Nh, LivingRegs) ->
- Is = [{block,Bl}|OuterIs],
- InitRegs = init_yregs(Ns, Is, D),
- case count_ones(InitRegs) of
- N when N*2 > Ns ->
- {alloc,LivingRegs,{nozero,Ns,Nh,gen_init(Ns, InitRegs)}};
- _ ->
- {alloc,LivingRegs,{zero,Ns,Nh,[]}}
- end.
-
-gen_init(Fs, Regs) -> gen_init(Fs, Regs, 0, []).
-
-gen_init(SameFs, _Regs, SameFs, Acc) -> reverse(Acc);
-gen_init(Fs, Regs, Y, Acc) when Regs band 1 =:= 0 ->
- gen_init(Fs, Regs bsr 1, Y+1, [{init,{y,Y}}|Acc]);
-gen_init(Fs, Regs, Y, Acc) ->
- gen_init(Fs, Regs bsr 1, Y+1, Acc).
-
-init_yregs(Y, Is, D) when Y >= 0 ->
- case beam_utils:is_killed({y,Y}, Is, D) of
- true ->
- (1 bsl Y) bor init_yregs(Y-1, Is, D);
- false ->
- init_yregs(Y-1, Is, D)
- end;
-init_yregs(_, _, _) -> 0.
-
-count_ones(Bits) -> count_ones(Bits, 0).
-count_ones(0, Acc) -> Acc;
-count_ones(Bits, Acc) ->
- count_ones(Bits bsr 1, Acc + (Bits band 1)).
-
-%% Calculate the new number of live registers when we move an allocate
-%% instruction upwards, passing a 'set' instruction.
-
-alloc_live_regs({set,Ds,Ss,_}, Is, Regs0) ->
- Rset = x_live(Ss, x_dead(Ds, (1 bsl Regs0)-1)),
- Live = live_regs(0, Rset),
- case ensure_contiguous(Rset, Live) of
- not_possible ->
- %% Liveness information (looking forward in the
- %% instruction stream) can't prove that moving this
- %% allocation instruction is safe. Now use the annotation
- %% of defined registers at the beginning of the current
- %% block to see whether moving would be safe.
- Def0 = defined_regs(Is, 0),
- Def = Def0 band ((1 bsl Live) - 1),
- ensure_contiguous(Rset bor Def, Live);
- Live ->
- %% Safe based on liveness information.
- Live
- end.
-
-live_regs(N, 0) ->
- N;
-live_regs(N, Regs) ->
- live_regs(N+1, Regs bsr 1).
-
-ensure_contiguous(Regs, Live) ->
- case (1 bsl Live) - 1 of
- Regs -> Live;
- _ -> not_possible
- end.
-
-x_dead([{x,N}|Rs], Regs) -> x_dead(Rs, Regs band (bnot (1 bsl N)));
-x_dead([_|Rs], Regs) -> x_dead(Rs, Regs);
-x_dead([], Regs) -> Regs.
-
-x_live([{x,N}|Rs], Regs) -> x_live(Rs, Regs bor (1 bsl N));
-x_live([_|Rs], Regs) -> x_live(Rs, Regs);
-x_live([], Regs) -> Regs.
-
-%% defined_regs(ReversedInstructions) -> RegBitmap.
-%% Given a reversed instruction stream, determine the
-%% the registers that are defined.
-
-defined_regs([{'%anno',{def,Def}}|_], Regs) ->
- Def bor Regs;
-defined_regs([{set,Ds,_,{alloc,Live,_}}|_], Regs) ->
- x_live(Ds, Regs bor ((1 bsl Live) - 1));
-defined_regs([{set,Ds,_,_}|Is], Regs) ->
- defined_regs(Is, x_live(Ds, Regs)).
-
-%%%
-%%% Do local common sub expression elimination (CSE) in each block.
-%%%
-
-local_cse([{block,Bl0}|Is]) ->
- Bl = cse_block(Bl0, orddict:new(), []),
- [{block,Bl}|local_cse(Is)];
-local_cse([I|Is]) ->
- [I|local_cse(Is)];
-local_cse([]) -> [].
-
-cse_block([I|Is], Es0, Acc0) ->
- Es1 = cse_clear(I, Es0),
- case cse_expr(I) of
- none ->
- %% Instruction is not suitable for CSE.
- cse_block(Is, Es1, [I|Acc0]);
- {ok,D,Expr} ->
- %% Suitable instruction. First update the dictionary of
- %% suitable expressions for the next iteration.
- Es = cse_add(D, Expr, Es1),
-
- %% Search for a previous identical expression.
- case cse_find(Expr, Es0) of
- error ->
- %% Nothing found
- cse_block(Is, Es, [I|Acc0]);
- Src ->
- %% Use the previously calculated result.
- %% Also eliminate any line instruction.
- Move = {set,[D],[Src],move},
- case Acc0 of
- [{set,_,_,{line,_}}|Acc] ->
- cse_block(Is, Es, [Move|Acc]);
- [_|_] ->
- cse_block(Is, Es, [Move|Acc0])
- end
- end
- end;
-cse_block([], _, Acc) ->
- reverse(Acc).
-
-%% cse_find(Expr, Expressions) -> error | Register.
-%% Find a previously evaluated expression whose result can be reused,
-%% or return 'error' if no such expression is found.
-
-cse_find(Expr, Es) ->
- case orddict:find(Expr, Es) of
- {ok,{Src,_}} -> Src;
- error -> error
- end.
-
-cse_expr({set,[D],Ss,{bif,N,_}}) ->
- case D of
- {fr,_} ->
- %% There are too many things that can go wrong.
- none;
- _ ->
- {ok,D,{{bif,N},Ss}}
- end;
-cse_expr({set,[D],Ss,{alloc,_,{gc_bif,N,_}}}) ->
- {ok,D,{{gc_bif,N},Ss}};
-cse_expr({set,[D],Ss,put_list}) ->
- {ok,D,{put_list,Ss}};
-cse_expr(_) -> none.
-
-%% cse_clear(Instr, Expressions0) -> Expressions.
-%% Remove all previous expressions that will become
-%% invalid when this instruction is executed. Basically,
-%% an expression is no longer safe to reuse when the
-%% register it has been stored to has been modified, killed,
-%% or if any of the source operands have changed.
-
-cse_clear({set,Ds,_,{alloc,Live,_}}, Es) ->
- cse_clear_1(Es, Live, Ds);
-cse_clear({set,Ds,_,_}, Es) ->
- cse_clear_1(Es, all, Ds).
-
-cse_clear_1(Es, Live, Ds0) ->
- Ds = ordsets:from_list(Ds0),
- [E || E <- Es, cse_is_safe(E, Live, Ds)].
-
-cse_is_safe({_,{Dst,Interfering}}, Live, Ds) ->
- ordsets:is_disjoint(Interfering, Ds) andalso
- case Dst of
- {x,X} ->
- X < Live;
- _ ->
- true
- end.
-
-%% cse_add(Dest, Expr, Expressions0) -> Expressions.
-%% Provided that it is safe, add a new expression to the dictionary
-%% of already evaluated expressions.
-
-cse_add(D, {_,Ss}=Expr, Es) ->
- case member(D, Ss) of
- false ->
- Interfering = ordsets:from_list([D|Ss]),
- orddict:store(Expr, {D,Interfering}, Es);
- true ->
- %% Unsafe because the instruction overwrites one of
- %% source operands.
- Es
+%% sort_moves([Instruction]) -> [Instruction].
+%% Sort move instructions on the Y register to give the loader
+%% more opportunities for combining instructions.
+
+sort_moves([{set,[{x,_}],[{y,_}],move}=I|Is0]) ->
+ {Moves,Is} = sort_moves_1(Is0, x, y, [I]),
+ Moves ++ sort_moves(Is);
+sort_moves([{set,[{y,_}],[{x,_}],move}=I|Is0]) ->
+ {Moves,Is} = sort_moves_1(Is0, y, x, [I]),
+ Moves ++ sort_moves(Is);
+sort_moves([I|Is]) ->
+ [I|sort_moves(Is)];
+sort_moves([]) -> [].
+
+sort_moves_1([{set,[{x,0}],[_],move}=I|Is], _DTag, _STag, Acc) ->
+ %% The loader sometimes combines a move to x0 with the
+ %% instruction that follows, producing, for example, a move_call
+ %% instruction. Therefore, we don't want include this move
+ %% instruction in the sorting.
+ {sort_on_yreg(Acc)++[I],Is};
+sort_moves_1([{set,[{DTag,_}],[{STag,_}],move}=I|Is], DTag, STag, Acc) ->
+ sort_moves_1(Is, DTag, STag, [I|Acc]);
+sort_moves_1(Is, _DTag, _STag, Acc) ->
+ {sort_on_yreg(Acc),Is}.
+
+sort_on_yreg([{set,[Dst],[Src],move}|_]=Moves) ->
+ case {Dst,Src} of
+ {{y,_},{x,_}} ->
+ keysort(2, Moves);
+ {{x,_},{y,_}} ->
+ keysort(3, Moves)
end.
diff --git a/lib/compiler/src/beam_bs.erl b/lib/compiler/src/beam_bs.erl
deleted file mode 100644
index 5f1b9ed488..0000000000
--- a/lib/compiler/src/beam_bs.erl
+++ /dev/null
@@ -1,280 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1999-2018. All 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 : Partitions assembly instructions into basic blocks and
-%% optimizes them.
-
--module(beam_bs).
-
--export([module/2]).
--import(lists, [mapfoldl/3,reverse/1]).
-
--spec module(beam_utils:module_code(), [compile:option()]) ->
- {'ok',beam_utils:module_code()}.
-
-module({Mod,Exp,Attr,Fs0,Lc0}, _Opt) ->
- {Fs,Lc} = mapfoldl(fun function/2, Lc0, Fs0),
- {ok,{Mod,Exp,Attr,Fs,Lc}}.
-
-function({function,Name,Arity,CLabel,Is0}, Lc0) ->
- try
- Is1 = bs_put_opt(Is0),
- {Is,Lc} = bsm_opt(Is1, Lc0),
- {{function,Name,Arity,CLabel,Is},Lc}
- catch
- Class:Error:Stack ->
- io:fwrite("Function: ~w/~w\n", [Name,Arity]),
- erlang:raise(Class, Error, Stack)
- end.
-
-%%%
-%%% Evaluation of constant bit fields.
-%%%
-
-bs_put_opt([{bs_put,_,_,_}=I|Is0]) ->
- {BsPuts0,Is} = collect_bs_puts(Is0, [I]),
- BsPuts = opt_bs_puts(BsPuts0),
- BsPuts ++ bs_put_opt(Is);
-bs_put_opt([I|Is]) ->
- [I|bs_put_opt(Is)];
-bs_put_opt([]) -> [].
-
-collect_bs_puts([{bs_put,_,_,_}=I|Is], Acc) ->
- collect_bs_puts(Is, [I|Acc]);
-collect_bs_puts([_|_]=Is, Acc) ->
- {reverse(Acc),Is}.
-
-opt_bs_puts(Is) ->
- opt_bs_1(Is, []).
-
-opt_bs_1([{bs_put,Fail,
- {bs_put_float,1,Flags0},[{integer,Sz},Src]}=I0|Is], Acc) ->
- try eval_put_float(Src, Sz, Flags0) of
- <<Int:Sz>> ->
- Flags = force_big(Flags0),
- I = {bs_put,Fail,{bs_put_integer,1,Flags},
- [{integer,Sz},{integer,Int}]},
- opt_bs_1([I|Is], Acc)
- catch
- error:_ ->
- opt_bs_1(Is, [I0|Acc])
- end;
-opt_bs_1([{bs_put,_,{bs_put_integer,1,_},[{integer,8},{integer,_}]}|_]=IsAll,
- Acc0) ->
- {Is,Acc} = bs_collect_string(IsAll, Acc0),
- opt_bs_1(Is, Acc);
-opt_bs_1([{bs_put,Fail,{bs_put_integer,1,F},[{integer,Sz},{integer,N}]}=I|Is0],
- Acc) when Sz > 8 ->
- case field_endian(F) of
- big ->
- %% We can do this optimization for any field size without
- %% risk for code explosion.
- case bs_split_int(N, Sz, Fail, Is0) of
- no_split -> opt_bs_1(Is0, [I|Acc]);
- Is -> opt_bs_1(Is, Acc)
- end;
- little when Sz < 128 ->
- %% We only try to optimize relatively small fields, to
- %% avoid an explosion in code size.
- <<Int:Sz>> = <<N:Sz/little>>,
- Flags = force_big(F),
- Is = [{bs_put,Fail,{bs_put_integer,1,Flags},
- [{integer,Sz},{integer,Int}]}|Is0],
- opt_bs_1(Is, Acc);
- _ -> %native or too wide little field
- opt_bs_1(Is0, [I|Acc])
- end;
-opt_bs_1([{bs_put,Fail,{Op,U,F},[{integer,Sz},Src]}|Is], Acc) when U > 1 ->
- opt_bs_1([{bs_put,Fail,{Op,1,F},[{integer,U*Sz},Src]}|Is], Acc);
-opt_bs_1([I|Is], Acc) ->
- opt_bs_1(Is, [I|Acc]);
-opt_bs_1([], Acc) -> reverse(Acc).
-
-eval_put_float(Src, Sz, Flags) when Sz =< 256 ->
- %%Only evaluate if Sz is reasonable.
- Val = value(Src),
- case field_endian(Flags) of
- little -> <<Val:Sz/little-float-unit:1>>;
- big -> <<Val:Sz/big-float-unit:1>>
- %% native intentionally not handled here - we can't optimize
- %% it.
- end.
-
-value({integer,I}) -> I;
-value({float,F}) -> F.
-
-bs_collect_string(Is, [{bs_put,_,{bs_put_string,Len,{string,Str}},[]}|Acc]) ->
- bs_coll_str_1(Is, Len, reverse(Str), Acc);
-bs_collect_string(Is, Acc) ->
- bs_coll_str_1(Is, 0, [], Acc).
-
-bs_coll_str_1([{bs_put,_,{bs_put_integer,U,_},[{integer,Sz},{integer,V}]}|Is],
- Len, StrAcc, IsAcc) when U*Sz =:= 8 ->
- Byte = V band 16#FF,
- bs_coll_str_1(Is, Len+1, [Byte|StrAcc], IsAcc);
-bs_coll_str_1(Is, Len, StrAcc, IsAcc) ->
- {Is,[{bs_put,{f,0},{bs_put_string,Len,{string,reverse(StrAcc)}},[]}|IsAcc]}.
-
-field_endian({field_flags,F}) -> field_endian_1(F).
-
-field_endian_1([big=E|_]) -> E;
-field_endian_1([little=E|_]) -> E;
-field_endian_1([native=E|_]) -> E;
-field_endian_1([_|Fs]) -> field_endian_1(Fs).
-
-force_big({field_flags,F}) ->
- {field_flags,force_big_1(F)}.
-
-force_big_1([big|_]=Fs) -> Fs;
-force_big_1([little|Fs]) -> [big|Fs];
-force_big_1([F|Fs]) -> [F|force_big_1(Fs)].
-
-bs_split_int(0, Sz, _, _) when Sz > 64 ->
- %% We don't want to split in this case because the
- %% string will consist of only zeroes.
- no_split;
-bs_split_int(-1, Sz, _, _) when Sz > 64 ->
- %% We don't want to split in this case because the
- %% string will consist of only 255 bytes.
- no_split;
-bs_split_int(N, Sz, Fail, Acc) ->
- FirstByteSz = case Sz rem 8 of
- 0 -> 8;
- Rem -> Rem
- end,
- bs_split_int_1(N, FirstByteSz, Sz, Fail, Acc).
-
-bs_split_int_1(-1, _, Sz, Fail, Acc) when Sz > 64 ->
- I = {bs_put,Fail,{bs_put_integer,1,{field_flags,[big]}},
- [{integer,Sz},{integer,-1}]},
- [I|Acc];
-bs_split_int_1(0, _, Sz, Fail, Acc) when Sz > 64 ->
- I = {bs_put,Fail,{bs_put_integer,1,{field_flags,[big]}},
- [{integer,Sz},{integer,0}]},
- [I|Acc];
-bs_split_int_1(N, ByteSz, Sz, Fail, Acc) when Sz > 0 ->
- Mask = (1 bsl ByteSz) - 1,
- I = {bs_put,Fail,{bs_put_integer,1,{field_flags,[big]}},
- [{integer,ByteSz},{integer,N band Mask}]},
- bs_split_int_1(N bsr ByteSz, 8, Sz-ByteSz, Fail, [I|Acc]);
-bs_split_int_1(_, _, _, _, Acc) -> Acc.
-
-%%%
-%%% Optimization of bit syntax matching: get rid
-%%% of redundant bs_restore2/2 instructions across select_val
-%%% instructions, as well as a few other simple peep-hole
-%%% optimizations.
-%%%
-
-bsm_opt(Is0, Lc0) ->
- {Is1,D0,Lc} = bsm_scan(Is0, [], Lc0, []),
- Is2 = case D0 of
- [] ->
- %% No bit syntax matching in this function.
- Is1;
- [_|_] ->
- %% Optimize the bit syntax matching.
- D = gb_trees:from_orddict(orddict:from_list(D0)),
- bsm_reroute(Is1, D, none, [])
- end,
- Is = beam_clean:bs_clean_saves(Is2),
- {bsm_opt_2(Is, []),Lc}.
-
-bsm_scan([{label,L}=Lbl,{bs_restore2,_,Save}=R|Is], D0, Lc, Acc0) ->
- D = [{{L,Save},Lc}|D0],
- Acc = [{label,Lc},R,Lbl|Acc0],
- bsm_scan(Is, D, Lc+1, Acc);
-bsm_scan([I|Is], D, Lc, Acc) ->
- bsm_scan(Is, D, Lc, [I|Acc]);
-bsm_scan([], D, Lc, Acc) ->
- {reverse(Acc),D,Lc}.
-
-bsm_reroute([{bs_save2,Reg,Save}=I|Is], D, _, Acc) ->
- bsm_reroute(Is, D, {Reg,Save}, [I|Acc]);
-bsm_reroute([{bs_restore2,Reg,Save}=I|Is], D, _, Acc) ->
- bsm_reroute(Is, D, {Reg,Save}, [I|Acc]);
-bsm_reroute([{label,_}=I|Is], D, S, Acc) ->
- bsm_reroute(Is, D, S, [I|Acc]);
-bsm_reroute([{select,select_val,Reg,F0,Lbls0}|Is], D, {_,Save}=S, Acc0) ->
- [F|Lbls] = bsm_subst_labels([F0|Lbls0], Save, D),
- Acc = [{select,select_val,Reg,F,Lbls}|Acc0],
- bsm_reroute(Is, D, S, Acc);
-bsm_reroute([{test,TestOp,F0,TestArgs}=I|Is], D, {_,Save}=S, Acc0) ->
- F = bsm_subst_label(F0, Save, D),
- Acc = [{test,TestOp,F,TestArgs}|Acc0],
- case bsm_not_bs_test(I) of
- true ->
- %% The test instruction will not update the bit offset for
- %% the binary being matched. Therefore the save position
- %% can be kept.
- bsm_reroute(Is, D, S, Acc);
- false ->
- %% The test instruction might update the bit offset. Kill
- %% our remembered Save position.
- bsm_reroute(Is, D, none, Acc)
- end;
-bsm_reroute([{test,TestOp,F0,Live,TestArgs,Dst}|Is], D, {_,Save}, Acc0) ->
- F = bsm_subst_label(F0, Save, D),
- Acc = [{test,TestOp,F,Live,TestArgs,Dst}|Acc0],
- %% The test instruction will update the bit offset. Kill our
- %% remembered Save position.
- bsm_reroute(Is, D, none, Acc);
-bsm_reroute([{block,[{set,[],[],{alloc,_,_}}]}=Bl,
- {bs_context_to_binary,_}=I|Is], D, S, Acc) ->
- %% To help further bit syntax optimizations.
- bsm_reroute([I,Bl|Is], D, S, Acc);
-bsm_reroute([I|Is], D, _, Acc) ->
- bsm_reroute(Is, D, none, [I|Acc]);
-bsm_reroute([], _, _, Acc) -> reverse(Acc).
-
-bsm_opt_2([{test,bs_test_tail2,F,[Ctx,Bits]}|Is],
- [{test,bs_skip_bits2,F,[Ctx,{integer,I},Unit,_Flags]}|Acc]) ->
- bsm_opt_2(Is, [{test,bs_test_tail2,F,[Ctx,Bits+I*Unit]}|Acc]);
-bsm_opt_2([{test,bs_skip_bits2,F,[Ctx,{integer,I1},Unit1,_]}|Is],
- [{test,bs_skip_bits2,F,[Ctx,{integer,I2},Unit2,Flags]}|Acc]) ->
- bsm_opt_2(Is, [{test,bs_skip_bits2,F,
- [Ctx,{integer,I1*Unit1+I2*Unit2},1,Flags]}|Acc]);
-bsm_opt_2([I|Is], Acc) ->
- bsm_opt_2(Is, [I|Acc]);
-bsm_opt_2([], Acc) -> reverse(Acc).
-
-%% bsm_not_bs_test({test,Name,_,Operands}) -> true|false.
-%% Test whether is the test is a "safe", i.e. does not move the
-%% bit offset for a binary.
-%%
-%% 'true' means that the test is safe, 'false' that we don't know or
-%% that the test moves the offset (e.g. bs_get_integer2).
-
-bsm_not_bs_test({test,bs_test_tail2,_,[_,_]}) -> true;
-bsm_not_bs_test(Test) -> beam_utils:is_pure_test(Test).
-
-bsm_subst_labels(Fs, Save, D) ->
- bsm_subst_labels_1(Fs, Save, D, []).
-
-bsm_subst_labels_1([F|Fs], Save, D, Acc) ->
- bsm_subst_labels_1(Fs, Save, D, [bsm_subst_label(F, Save, D)|Acc]);
-bsm_subst_labels_1([], _, _, Acc) ->
- reverse(Acc).
-
-bsm_subst_label({f,Lbl0}=F, Save, D) ->
- case gb_trees:lookup({Lbl0,Save}, D) of
- {value,Lbl} -> {f,Lbl};
- none -> F
- end;
-bsm_subst_label(Other, _, _) -> Other.
diff --git a/lib/compiler/src/beam_bsm.erl b/lib/compiler/src/beam_bsm.erl
deleted file mode 100644
index 1c8e0e9854..0000000000
--- a/lib/compiler/src/beam_bsm.erl
+++ /dev/null
@@ -1,708 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2007-2018. All 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_bsm).
--export([module/2,format_error/1]).
-
--import(lists, [member/2,foldl/3,reverse/1,sort/1,all/2]).
-
-%%%
-%%% We optimize bit syntax matching where the tail end of a binary is
-%%% matched out and immediately passed on to a bs_start_match2 instruction,
-%%% such as in this code sequence:
-%%%
-%%% func_info ...
-%%% L1 test bs_start_match2 {f,...} {x,0} Live SavePositions {x,0}
-%%% . . .
-%%% test bs_get_binary2 {f,...} {x,0} all 1 Flags {x,0}
-%%% . . .
-%%% call_only 2 L1
-%%%
-%%% The sequence can be optimized simply by removing the bs_get_binary2
-%%% instruction. Another example:
-%%%
-%%% func_info ...
-%%% L1 test bs_start_match2 {f,...} {x,0} Live SavePositions {x,0}
-%%% . . .
-%%% test bs_get_binary2 {f,...} {x,0} all 8 Flags {x,1}
-%%% . . .
-%%% move {x,1} {x,0}
-%%% call_only 2 L1
-%%%
-%%% In this case, the bs_get_binary2 instruction must be replaced by
-%%%
-%%% test bs_unit {x,1} 8
-%%%
-%%% to ensure that the match fail if the length of the binary in bits
-%%% is not evenly divisible by 8.
-%%%
-%%% Note that the bs_start_match2 instruction doesn't need to be in the same
-%%% function as the caller. It can be in the beginning of any function, or
-%%% follow the bs_get_binary2 instruction in the same function. The important
-%%% thing is that the match context register is not copied or built into
-%%% data structures or passed to BIFs.
-%%%
-
--type label() :: beam_asm:label().
--type func_info() :: {beam_asm:reg(),boolean()}.
-
--record(btb,
- {f :: gb_trees:tree(label(), func_info()),
- index :: beam_utils:code_index(), %{Label,Code} index (for liveness).
- ok_br=gb_sets:empty() :: gb_sets:set(label()), %Labels that are OK.
- must_not_save=false :: boolean(), %Must not save position when
- % optimizing (reaches
- % bs_context_to_binary).
- must_save=false :: boolean() %Must save position when optimizing.
- }).
-
-
--spec module(beam_utils:module_code(), [compile:option()]) ->
- {'ok',beam_utils:module_code()}.
-
-module({Mod,Exp,Attr,Fs0,Lc}, Opts) ->
- FIndex = btb_index(Fs0),
- Fs = [function(F, FIndex) || F <- Fs0],
- Code = {Mod,Exp,Attr,Fs,Lc},
- case proplists:get_bool(bin_opt_info, Opts) of
- true ->
- {ok,Code,collect_warnings(Fs)};
- false ->
- {ok,Code}
- end.
-
--spec format_error('bin_opt' | {'no_bin_opt', term()}) -> nonempty_string().
-
-format_error(bin_opt) ->
- "OPTIMIZED: creation of sub binary delayed";
-format_error({no_bin_opt,Reason}) ->
- lists:flatten(["NOT OPTIMIZED: "|format_error_1(Reason)]).
-
-%%%
-%%% Local functions.
-%%%
-
-function({function,Name,Arity,Entry,Is}, FIndex) ->
- try
- Index = beam_utils:index_labels(Is),
- D = #btb{f=FIndex,index=Index},
- {function,Name,Arity,Entry,btb_opt_1(Is, D, [])}
- catch
- Class:Error:Stack ->
- io:fwrite("Function: ~w/~w\n", [Name,Arity]),
- erlang:raise(Class, Error, Stack)
- end.
-
-btb_opt_1([{test,bs_get_binary2,F,_,[Reg,{atom,all},U,Fs],Reg}=I0|Is], D, Acc0) ->
- case btb_reaches_match(Is, [Reg], D) of
- {error,Reason} ->
- Comment = btb_comment_no_opt(Reason, Fs),
- btb_opt_1(Is, D, [Comment,I0|Acc0]);
- {ok,MustSave} ->
- Comment = btb_comment_opt(Fs),
- Acc1 = btb_gen_save(MustSave, Reg, [Comment|Acc0]),
- Acc = case U of
- 1 -> Acc1;
- _ -> [{test,bs_test_unit,F,[Reg,U]}|Acc1]
- end,
- btb_opt_1(Is, D, Acc)
- end;
-btb_opt_1([{test,bs_get_binary2,F,_,[Ctx,{atom,all},U,Fs],Dst}=I0|Is0], D, Acc0) ->
- case btb_reaches_match(Is0, [Ctx,Dst], D) of
- {error,Reason} ->
- Comment = btb_comment_no_opt(Reason, Fs),
- btb_opt_1(Is0, D, [Comment,I0|Acc0]);
- {ok,MustSave} when U =:= 1 ->
- Comment = btb_comment_opt(Fs),
- Acc = btb_gen_save(MustSave, Ctx, [Comment|Acc0]),
- Is = prepend_move(Ctx, Dst, Is0),
- btb_opt_1(Is, D, Acc);
- {ok,MustSave} ->
- Comment = btb_comment_opt(Fs),
- Acc1 = btb_gen_save(MustSave, Ctx, [Comment|Acc0]),
- Acc = [{test,bs_test_unit,F,[Ctx,U]}|Acc1],
- Is = prepend_move(Ctx, Dst, Is0),
- btb_opt_1(Is, D, Acc)
- end;
-btb_opt_1([I|Is], D, Acc) ->
- %%io:format("~p\n", [I]),
- btb_opt_1(Is, D, [I|Acc]);
-btb_opt_1([], _, Acc) ->
- reverse(Acc).
-
-btb_gen_save(true, Reg, Acc) ->
- [{bs_save2,Reg,{atom,start}}|Acc];
-btb_gen_save(false, _, Acc) -> Acc.
-
-prepend_move(Ctx, Dst, [{block,Bl0}|Is]) ->
- Bl = [{set,[Dst],[Ctx],move}|Bl0],
- [{block,Bl}|Is];
-prepend_move(Ctx, Dst, Is) ->
- [{move,Ctx,Dst}|Is].
-
-%% btb_reaches_match([Instruction], [Register], D) ->
-%% {ok,MustSave}|{error,Reason}
-%%
-%% The list of Registers should be a list of registers referencing a
-%% match context. The Register may contain one element if the
-%% bs_get_binary2 instruction looks like
-%%
-%% test bs_get_binary2 {f,...} Ctx all _ _ Ctx
-%%
-%% or two elements if the instruction looks like
-%%
-%% test bs_get_binary2 {f,...} Ctx all _ _ Dst
-%%
-%% This function determines whether the bs_get_binary2 instruction
-%% can be omitted (retaining the match context instead of creating
-%% a sub binary).
-%%
-%% The rule is that the match context ultimately must end up at a
-%% bs_start_match2 instruction and nowhere else. That it, it must not
-%% be passed to BIFs, or copied or put into data structures. There
-%% must only be one copy alive when the match context reaches the
-%% bs_start_match2 instruction.
-%%
-%% At a branch, we must follow all branches and make sure that the above
-%% rule is followed (or that the branch kills the match context).
-%%
-%% The MustSave return value will be true if control may end up at
-%% bs_context_to_binary instruction. Since that instruction uses the
-%% saved start position, we must use "bs_save2 Ctx start" to
-%% update the saved start position. An additional complication is that
-%% "bs_save2 Ctx start" must not be used if Dst and Ctx are
-%% different registers and both registers may be passed to
-%% a bs_context_to_binary instruction.
-%%
-
-btb_reaches_match(Is, RegList, D) ->
- try
- Regs = btb_regs_from_list(RegList),
- #btb{must_not_save=MustNotSave,must_save=MustSave} =
- btb_reaches_match_1(Is, Regs, D),
- case MustNotSave andalso MustSave of
- true -> btb_error(must_and_must_not_save);
- false -> {ok,MustSave}
- end
- catch
- throw:{error,_}=Error -> Error
- end.
-
-btb_reaches_match_1(Is, Regs, D) ->
- case btb_are_registers_empty(Regs) of
- false ->
- btb_reaches_match_2(Is, Regs, D);
- true ->
- %% The context was killed, which is OK.
- D
- end.
-
-btb_reaches_match_2([{block,Bl}|Is], Regs0, D) ->
- Regs = btb_reaches_match_block(Bl, Regs0),
- btb_reaches_match_1(Is, Regs, D);
-btb_reaches_match_2([{call,Arity,{f,Lbl}}|Is], Regs0, D) ->
- case is_tail_call(Is) of
- true ->
- Regs1 = btb_kill_not_live(Arity, Regs0),
- Regs = btb_kill_yregs(Regs1),
- btb_tail_call(Lbl, Regs, D);
- false ->
- btb_call(Arity, Lbl, Regs0, Is, D)
- end;
-btb_reaches_match_2([{apply,Arity}|Is], Regs, D) ->
- btb_call(Arity+2, apply, Regs, Is, D);
-btb_reaches_match_2([{call_fun,Live}=I|Is], Regs, D) ->
- btb_ensure_not_used([{x,Live}], I, Regs),
- btb_call(Live, I, Regs, Is, D);
-btb_reaches_match_2([{make_fun2,_,_,_,Live}|Is], Regs, D) ->
- btb_call(Live, make_fun2, Regs, Is, D);
-btb_reaches_match_2([{call_ext,Arity,Func}=I|Is], Regs0, D) ->
- %% Allow us scanning beyond the call in case the match
- %% context is saved on the stack.
- case beam_jump:is_exit_instruction(I) of
- false ->
- btb_call(Arity, Func, Regs0, Is, D);
- true ->
- Regs = btb_kill_not_live(Arity, Regs0),
- btb_tail_call(Func, Regs, D)
- end;
-btb_reaches_match_2([{kill,Y}|Is], Regs, D) ->
- btb_reaches_match_1(Is, btb_kill([Y], Regs), D);
-btb_reaches_match_2([{deallocate,_}|Is], Regs0, D) ->
- Regs = btb_kill_yregs(Regs0),
- btb_reaches_match_1(Is, Regs, D);
-btb_reaches_match_2([return=I|_], Regs0, D) ->
- btb_ensure_not_used([{x,0}], I, Regs0),
- D;
-btb_reaches_match_2([{gc_bif,_,{f,F},Live,Ss,Dst}=I|Is], Regs0, D0) ->
- btb_ensure_not_used(Ss, I, Regs0),
- Regs1 = btb_kill_not_live(Live, Regs0),
- Regs = btb_kill([Dst], Regs1),
- D = btb_follow_branch(F, Regs, D0),
- btb_reaches_match_1(Is, Regs, D);
-btb_reaches_match_2([{bif,_,{f,F},Ss,Dst}=I|Is], Regs0, D0) ->
- btb_ensure_not_used(Ss, I, Regs0),
- Regs = btb_kill([Dst], Regs0),
- D = btb_follow_branch(F, Regs, D0),
- btb_reaches_match_1(Is, Regs, D);
-btb_reaches_match_2([{get_map_elements,{f,F},Src,{list,Ls}}=I|Is], Regs0, D0) ->
- {Ss,Ds} = beam_utils:split_even(Ls),
- btb_ensure_not_used([Src|Ss], I, Regs0),
- Regs = btb_kill(Ds, Regs0),
- D = btb_follow_branch(F, Regs, D0),
- btb_reaches_match_1(Is, Regs, D);
-btb_reaches_match_2([{test,bs_start_match2,{f,F},Live,[Ctx,_],Ctx}=I|Is],
- Regs0, D0) ->
- CtxRegs = btb_context_regs(Regs0),
- case member(Ctx, CtxRegs) of
- false ->
- %% This bs_start_match2 instruction does not use "our"
- %% match state. Therefore we can continue the search
- %% for another bs_start_match2 instruction.
- D = btb_follow_branch(F, Regs0, D0),
- Regs = btb_kill_not_live(Live, Regs0),
- btb_reaches_match_2(Is, Regs, D);
- true ->
- %% OK. This instruction will use "our" match state,
- %% but we must make sure that all other copies of the
- %% match state are killed in the code that follows
- %% the instruction. (We know that the fail branch cannot
- %% be taken in this case.)
- OtherCtxRegs = CtxRegs -- [Ctx],
- case btb_are_all_unused(OtherCtxRegs, Is, D0) of
- false -> btb_error({OtherCtxRegs,not_all_unused_after,I});
- true -> D0
- end
- end;
-btb_reaches_match_2([{test,bs_start_match2,{f,F},Live,[Bin,_],Ctx}|Is],
- Regs0, D0) ->
- CtxRegs = btb_context_regs(Regs0),
- case member(Bin, CtxRegs) orelse member(Ctx, CtxRegs) of
- false ->
- %% This bs_start_match2 does not reference any copy of the
- %% match state. Therefore it can safely be passed on the
- %% way to another (perhaps more suitable) bs_start_match2
- %% instruction.
- D = btb_follow_branch(F, Regs0, D0),
- Regs = btb_kill_not_live(Live, Regs0),
- btb_reaches_match_2(Is, Regs, D);
- true ->
- %% This variant of the bs_start_match2 instruction does
- %% not accept a match state as source.
- btb_error(unsuitable_bs_start_match)
- end;
-btb_reaches_match_2([{test,_,{f,F},Ss}=I|Is], Regs, D0) ->
- btb_ensure_not_used(Ss, I, Regs),
- D = btb_follow_branch(F, Regs, D0),
- btb_reaches_match_1(Is, Regs, D);
-btb_reaches_match_2([{test,_,{f,F},_,Ss,_}=I|Is], Regs, D0) ->
- btb_ensure_not_used(Ss, I, Regs),
- D = btb_follow_branch(F, Regs, D0),
- btb_reaches_match_1(Is, Regs, D);
-btb_reaches_match_2([{select,_,Src,{f,F},Conds}=I|Is], Regs, D0) ->
- btb_ensure_not_used([Src], I, Regs),
- D1 = btb_follow_branch(F, Regs, D0),
- D = btb_follow_branches(Conds, Regs, D1),
- btb_reaches_match_1(Is, Regs, D);
-btb_reaches_match_2([{jump,{f,Lbl}}|_], Regs, #btb{index=Li}=D) ->
- Is = fetch_code_at(Lbl, Li),
- btb_reaches_match_2(Is, Regs, D);
-btb_reaches_match_2([{label,_}|Is], Regs, D) ->
- btb_reaches_match_2(Is, Regs, D);
-btb_reaches_match_2([{bs_init,{f,0},_,_,Ss,Dst}=I|Is], Regs, D) ->
- btb_ensure_not_used(Ss, I, Regs),
- btb_reaches_match_1(Is, btb_kill([Dst], Regs), D);
-btb_reaches_match_2([{bs_put,{f,0},_,Ss}=I|Is], Regs, D) ->
- btb_ensure_not_used(Ss, I, Regs),
- btb_reaches_match_1(Is, Regs, D);
-btb_reaches_match_2([{bs_restore2,Src,_}=I|Is], Regs0, D) ->
- case btb_contains_context(Src, Regs0) of
- false ->
- btb_reaches_match_1(Is, Regs0, D);
- true ->
- %% Check that all other copies of the context registers
- %% are unused by the following instructions.
- Regs = btb_kill([Src], Regs0),
- CtxRegs = btb_context_regs(Regs),
- case btb_are_all_unused(CtxRegs, Is, D) of
- false -> btb_error({CtxRegs,not_all_unused_after,I});
- true -> D#btb{must_not_save=true}
- end
- end;
-btb_reaches_match_2([{bs_context_to_binary,Src}=I|Is], Regs0, D) ->
- case btb_contains_context(Src, Regs0) of
- false ->
- btb_reaches_match_1(Is, Regs0, D);
- true ->
- %% Check that all other copies of the context registers
- %% are unused by the following instructions.
- Regs = btb_kill([Src], Regs0),
- CtxRegs = btb_context_regs(Regs),
- case btb_are_all_unused(CtxRegs, Is, D) of
- false -> btb_error({CtxRegs,not_all_unused_after,I});
- true -> D#btb{must_not_save=true}
- end
- end;
-btb_reaches_match_2([{badmatch,Src}=I|_], Regs, D) ->
- btb_ensure_not_used([Src], I, Regs),
- D;
-btb_reaches_match_2([{case_end,Src}=I|_], Regs, D) ->
- btb_ensure_not_used([Src], I, Regs),
- D;
-btb_reaches_match_2([if_end|_], _Regs, D) ->
- D;
-btb_reaches_match_2([{func_info,_,_,Arity}=I|_], Regs0, D) ->
- Regs = btb_kill_yregs(btb_kill_not_live(Arity, Regs0)),
- case btb_context_regs(Regs) of
- [] -> D;
- _ -> {binary_used_in,I}
- end;
-btb_reaches_match_2([{line,_}|Is], Regs, D) ->
- btb_reaches_match_1(Is, Regs, D);
-btb_reaches_match_2([I|_], Regs, _) ->
- btb_error({btb_context_regs(Regs),I,not_handled}).
-
-is_tail_call([{deallocate,_}|_]) -> true;
-is_tail_call([return|_]) -> true;
-is_tail_call(_) -> false.
-
-btb_call(Arity, Lbl, Regs0, Is, D0) ->
- Regs = btb_kill_not_live(Arity, Regs0),
- case btb_are_x_registers_empty(Regs) of
- false ->
- %% There is a match context in one of the x registers.
- %% First handle the call as if it were a tail call.
- D = btb_tail_call(Lbl, Regs, D0),
-
- %% No problem so far (the called function can handle a
- %% match context). Now we must make sure that we don't
- %% have any copies of the match context tucked away in an
- %% y register.
- RegList = btb_context_regs(Regs),
- case [R || {y,_}=R <- RegList] of
- [] ->
- D;
- [_|_] ->
- btb_error({multiple_uses,RegList})
- end;
- true ->
- %% No match context in any x register. It could have been
- %% saved to an y register, so continue to scan the code following
- %% the call.
- btb_reaches_match_1(Is, Regs, D0)
- end.
-
-btb_tail_call(Lbl, Regs, #btb{f=Ftree,must_save=MustSave0}=D) ->
- %% Ignore any y registers here.
- case [R || {x,_}=R <- btb_context_regs(Regs)] of
- [] ->
- D;
- [{x,_}=Reg] ->
- case gb_trees:lookup(Lbl, Ftree) of
- {value,{Reg,MustSave}} ->
- D#btb{must_save=MustSave0 or MustSave};
- _ when is_integer(Lbl) ->
- btb_error({{label,Lbl},no_suitable_bs_start_match});
- _ ->
- btb_error({binary_used_in,Lbl})
- end;
- [_|_] when not is_integer(Lbl) ->
- btb_error({binary_used_in,Lbl});
- [_|_]=RegList ->
- btb_error({multiple_uses,RegList})
- end.
-
-%% btb_follow_branches([Cond], Regs, D) -> D'
-%% Recursively follow all the branches.
-
-btb_follow_branches([{f,Lbl}|T], Regs, D0) ->
- D = btb_follow_branch(Lbl, Regs, D0),
- btb_follow_branches(T, Regs, D);
-btb_follow_branches([_|T], Regs, D) ->
- btb_follow_branches(T, Regs, D);
-btb_follow_branches([], _, D) -> D.
-
-%% btb_follow_branch(Lbl, Regs, D) -> D'
-%% Recursively follow the branch.
-
-btb_follow_branch(0, _Regs, D) -> D;
-btb_follow_branch(Lbl, Regs, #btb{ok_br=Br0,index=Li}=D) ->
- Key = {Lbl,Regs},
- case gb_sets:is_member(Key, Br0) of
- true ->
- %% We have already followed this branch and it was OK.
- D;
- false ->
- %% New branch. Try it.
- Is = fetch_code_at(Lbl, Li),
- #btb{ok_br=Br,must_not_save=MustNotSave,must_save=MustSave} =
- btb_reaches_match_1(Is, Regs, D),
-
- %% Since we got back, this branch is OK.
- D#btb{ok_br=gb_sets:insert(Key, Br),must_not_save=MustNotSave,
- must_save=MustSave}
- end.
-
-btb_reaches_match_block([{set,Ds,Ss,{alloc,Live,_}}=I|Is], Regs0) ->
- %% An allocation instruction or a GC bif. We'll kill all registers
- %% if any copy of the context is used as the source to the BIF.
- btb_ensure_not_used(Ss, I, Regs0),
- Regs1 = btb_kill_not_live(Live, Regs0),
- Regs = btb_kill(Ds, Regs1),
- btb_reaches_match_block(Is, Regs);
-btb_reaches_match_block([{set,[Dst]=Ds,[Src],move}|Is], Regs0) ->
- Regs1 = btb_kill(Ds, Regs0),
- Regs = case btb_contains_context(Src, Regs1) of
- false -> Regs1;
- true -> btb_set_context(Dst, Regs1)
- end,
- btb_reaches_match_block(Is, Regs);
-btb_reaches_match_block([{set,Ds,Ss,_}=I|Is], Regs0) ->
- btb_ensure_not_used(Ss, I, Regs0),
- Regs = btb_kill(Ds, Regs0),
- btb_reaches_match_block(Is, Regs);
-btb_reaches_match_block([], Regs) ->
- Regs.
-
-%% btb_are_all_killed([Register], [Instruction], D) -> true|false
-%% Test whether all of the register are unused in the instruction stream.
-
-btb_are_all_unused(RegList, Is, #btb{index=Li}) ->
- all(fun(R) ->
- beam_utils:is_not_used(R, Is, Li)
- end, RegList).
-
-%% btp_regs_from_list([Register]) -> RegisterSet.
-%% Create a register set from a list of registers.
-
-btb_regs_from_list(L) ->
- foldl(fun(R, Regs) ->
- btb_set_context(R, Regs)
- end, {0,0}, L).
-
-%% btb_set_context(Register, RegisterSet) -> RegisterSet'
-%% Update RegisterSet to indicate that Register contains the matching context.
-
-btb_set_context({x,N}, {Xregs,Yregs}) ->
- {Xregs bor (1 bsl N),Yregs};
-btb_set_context({y,N}, {Xregs,Yregs}) ->
- {Xregs,Yregs bor (1 bsl N)}.
-
-%% btb_ensure_not_used([Register], Instruction, RegisterSet) -> ok
-%% If any register in RegisterSet (the register(s) known to contain
-%% the match context) is used in the list of registers, generate an error.
-
-btb_ensure_not_used(Rs, I, Regs) ->
- case lists:any(fun(R) -> btb_contains_context(R, Regs) end, Rs) of
- true -> btb_error({binary_used_in,I});
- false -> ok
- end.
-
-%% btb_kill([Register], RegisterSet) -> RegisterSet'
-%% Kill all registers mentioned in the list of registers.
-
-btb_kill([{x,N}|Rs], {Xregs,Yregs}) ->
- btb_kill(Rs, {Xregs band (bnot (1 bsl N)),Yregs});
-btb_kill([{y,N}|Rs], {Xregs,Yregs}) ->
- btb_kill(Rs, {Xregs,Yregs band (bnot (1 bsl N))});
-btb_kill([{fr,_}|Rs], Regs) ->
- btb_kill(Rs, Regs);
-btb_kill([], Regs) -> Regs.
-
-%% btb_kill_not_live(Live, RegisterSet) -> RegisterSet'
-%% Kill all registers indicated not live by Live.
-
-btb_kill_not_live(Live, {Xregs,Yregs}) ->
- {Xregs band ((1 bsl Live)-1),Yregs}.
-
-%% btb_kill(Regs0) -> Regs
-%% Kill all y registers.
-
-btb_kill_yregs({Xregs,_}) -> {Xregs,0}.
-
-%% btb_are_registers_empty(RegisterSet) -> true|false
-%% Test whether the register set is empty.
-
-btb_are_registers_empty({0,0}) -> true;
-btb_are_registers_empty({_,_}) -> false.
-
-%% btb_are_x_registers_empty(Regs) -> true|false
-%% Test whether the x registers are empty.
-
-btb_are_x_registers_empty({0,_}) -> true;
-btb_are_x_registers_empty({_,_}) -> false.
-
-%% btb_contains_context(Register, RegisterSet) -> true|false
-%% Test whether Register contains the context.
-
-btb_contains_context({x,N}, {Regs,_}) -> Regs band (1 bsl N) =/= 0;
-btb_contains_context({y,N}, {_,Regs}) -> Regs band (1 bsl N) =/= 0;
-btb_contains_context(_, _) -> false.
-
-%% btb_context_regs(RegisterSet) -> [Register]
-%% Convert the register set to an explicit list of registers.
-btb_context_regs({Xregs,Yregs}) ->
- btb_context_regs_1(Xregs, 0, x, btb_context_regs_1(Yregs, 0, y, [])).
-
-btb_context_regs_1(0, _, _, Acc) ->
- Acc;
-btb_context_regs_1(Regs, N, Tag, Acc) when (Regs band 1) =:= 1 ->
- btb_context_regs_1(Regs bsr 1, N+1, Tag, [{Tag,N}|Acc]);
-btb_context_regs_1(Regs, N, Tag, Acc) ->
- btb_context_regs_1(Regs bsr 1, N+1, Tag, Acc).
-
-%% btb_index([Function]) -> GbTree({EntryLabel,{Register,MustSave}})
-%% Build an index of functions that accept a match context instead of
-%% a binary. MustSave is true if the function may pass the match
-%% context to the bs_context_to_binary instruction (in which case
-%% the current position in the binary must have saved into the
-%% start position using "bs_save_2 Ctx start").
-
-btb_index(Fs) ->
- btb_index_1(Fs, []).
-
-btb_index_1([{function,_,_,Entry,Is0}|Fs], Acc0) ->
- Is = drop_to_label(Is0, Entry),
- Acc = btb_index_2(Is, Entry, false, Acc0),
- btb_index_1(Fs, Acc);
-btb_index_1([], Acc) -> gb_trees:from_orddict(sort(Acc)).
-
-btb_index_2([{test,bs_start_match2,{f,_},_,[Reg,_],Reg}|_],
- Entry, MustSave, Acc) ->
- [{Entry,{Reg,MustSave}}|Acc];
-btb_index_2(Is0, Entry, _, Acc) ->
- try btb_index_find_start_match(Is0) of
- Is -> btb_index_2(Is, Entry, true, Acc)
- catch
- throw:none -> Acc
- end.
-
-drop_to_label([{label,L}|Is], L) -> Is;
-drop_to_label([_|Is], L) -> drop_to_label(Is, L).
-
-btb_index_find_start_match([{test,_,{f,F},_},{bs_context_to_binary,_}|Is]) ->
- btb_index_find_label(Is, F);
-btb_index_find_start_match(_) ->
- throw(none).
-
-btb_index_find_label([{label,L}|Is], L) -> Is;
-btb_index_find_label([_|Is], L) -> btb_index_find_label(Is, L).
-
-btb_error(Error) ->
- throw({error,Error}).
-
-fetch_code_at(Lbl, Li) ->
- case beam_utils:code_at(Lbl, Li) of
- Is when is_list(Is) -> Is
- end.
-
-%%%
-%%% Compilation information warnings.
-%%%
-
-btb_comment_opt({field_flags,[{anno,A}|_]}) ->
- {'%',{bin_opt,A}};
-btb_comment_opt(_) ->
- {'%',{bin_opt,[]}}.
-
-btb_comment_no_opt(Reason, {field_flags,[{anno,A}|_]}) ->
- {'%',{no_bin_opt,Reason,A}};
-btb_comment_no_opt(Reason, _) ->
- {'%',{no_bin_opt,Reason,[]}}.
-
-collect_warnings(Fs) ->
- D = warning_index_functions(Fs),
- foldl(fun(F, A) -> collect_warnings_fun(F, D, A) end, [], Fs).
-
-collect_warnings_fun({function,_,_,_,Is}, D, A) ->
- collect_warnings_instr(Is, D, A).
-
-collect_warnings_instr([{'%',{bin_opt,Where}}|Is], D, Acc0) ->
- Acc = add_warning(bin_opt, Where, Acc0),
- collect_warnings_instr(Is, D, Acc);
-collect_warnings_instr([{'%',{no_bin_opt,Reason0,Where}}|Is], D, Acc0) ->
- Reason = warning_translate_label(Reason0, D),
- Acc = add_warning({no_bin_opt,Reason}, Where, Acc0),
- collect_warnings_instr(Is, D, Acc);
-collect_warnings_instr([_|Is], D, Acc) ->
- collect_warnings_instr(Is, D, Acc);
-collect_warnings_instr([], _, Acc) -> Acc.
-
-add_warning(Term, Anno, Ws) ->
- Line = get_line(Anno),
- File = get_file(Anno),
- [{File,[{Line,?MODULE,Term}]}|Ws].
-
-warning_translate_label(Term, D) when is_tuple(Term) ->
- case element(1, Term) of
- {label,F} ->
- FA = gb_trees:get(F, D),
- setelement(1, Term, FA);
- _ -> Term
- end;
-warning_translate_label(Term, _) -> Term.
-
-get_line([Line|_]) when is_integer(Line) -> Line;
-get_line([_|T]) -> get_line(T);
-get_line([]) -> none.
-
-get_file([{file,File}|_]) -> File;
-get_file([_|T]) -> get_file(T);
-get_file([]) -> "no_file". % should not happen
-
-warning_index_functions(Fs) ->
- D = [{Entry,{F,A}} || {function,F,A,Entry,_} <- Fs],
- gb_trees:from_orddict(sort(D)).
-
-format_error_1({binary_used_in,{extfunc,M,F,A}}) ->
- [io_lib:format("sub binary used by ~p:~p/~p", [M,F,A])|
- case {M,F,A} of
- {erlang,split_binary,2} ->
- "; SUGGEST using binary matching instead of split_binary/2";
- _ ->
- ""
- end];
-format_error_1({binary_used_in,_}) ->
- "sub binary is used or returned";
-format_error_1({multiple_uses,_}) ->
- "sub binary is matched or used in more than one place";
-format_error_1(unsuitable_bs_start_match) ->
- "the binary matching instruction that follows in the same function "
- "have problems that prevent delayed sub binary optimization "
- "(probably indicated by INFO warnings)";
-format_error_1({{F,A},no_suitable_bs_start_match}) ->
- io_lib:format("called function ~p/~p does not begin with a suitable "
- "binary matching instruction", [F,A]);
-format_error_1(must_and_must_not_save) ->
- "different control paths use different positions in the binary";
-format_error_1({_,I,not_handled}) ->
- case I of
- {'catch',_,_} ->
- "the compiler currently does not attempt the delayed sub binary "
- "optimization when catch is used";
- {'try',_,_} ->
- "the compiler currently does not attempt the delayed sub binary "
- "optimization when try/catch is used";
- _ ->
- io_lib:format("compiler limitation: instruction ~p prevents "
- "delayed sub binary optimization", [I])
- end;
-format_error_1(Term) ->
- io_lib:format("~w", [Term]).
diff --git a/lib/compiler/src/beam_clean.erl b/lib/compiler/src/beam_clean.erl
index 207f1c4deb..7299654476 100644
--- a/lib/compiler/src/beam_clean.erl
+++ b/lib/compiler/src/beam_clean.erl
@@ -22,34 +22,21 @@
-module(beam_clean).
-export([module/2]).
--export([bs_clean_saves/1]).
-export([clean_labels/1]).
--import(lists, [foldl/3,reverse/1]).
-spec module(beam_utils:module_code(), [compile:option()]) ->
{'ok',beam_utils:module_code()}.
module({Mod,Exp,Attr,Fs0,_}, Opts) ->
Order = [Lbl || {function,_,_,Lbl,_} <- Fs0],
- All = foldl(fun({function,_,_,Lbl,_}=Func,D) -> dict:store(Lbl, Func, D) end,
- dict:new(), Fs0),
+ All = maps:from_list([{Lbl,Func} || {function,_,_,Lbl,_}=Func <- Fs0]),
WorkList = rootset(Fs0, Exp, Attr),
- Used = find_all_used(WorkList, All, sets:from_list(WorkList)),
+ Used = find_all_used(WorkList, All, cerl_sets:from_list(WorkList)),
Fs1 = remove_unused(Order, Used, All),
{Fs2,Lc} = clean_labels(Fs1),
- Fs3 = bs_fix(Fs2),
- Fs = maybe_remove_lines(Fs3, Opts),
+ Fs = maybe_remove_lines(Fs2, Opts),
{ok,{Mod,Exp,Attr,Fs,Lc}}.
-%% Remove all bs_save2/2 instructions not referenced by a bs_restore2/2.
-
--spec bs_clean_saves([beam_utils:instruction()]) ->
- [beam_utils:instruction()].
-
-bs_clean_saves(Is) ->
- Needed = bs_restores(Is, []),
- bs_clean_saves_1(Is, gb_sets:from_list(Needed), []).
-
%% Determine the rootset, i.e. exported functions and
%% the on_load function (if any).
@@ -66,16 +53,16 @@ rootset(Fs, Root0, Attr) ->
%% Remove the unused functions.
remove_unused([F|Fs], Used, All) ->
- case sets:is_element(F, Used) of
+ case cerl_sets:is_element(F, Used) of
false -> remove_unused(Fs, Used, All);
- true -> [dict:fetch(F, All)|remove_unused(Fs, Used, All)]
+ true -> [map_get(F, All)|remove_unused(Fs, Used, All)]
end;
remove_unused([], _, _) -> [].
-
+
%% Find all used functions.
find_all_used([F|Fs0], All, Used0) ->
- {function,_,_,_,Code} = dict:fetch(F, All),
+ {function,_,_,_,Code} = map_get(F, All),
{Fs,Used} = update_work_list(Code, {Fs0,Used0}),
find_all_used(Fs, All, Used);
find_all_used([], _All, Used) -> Used.
@@ -89,20 +76,16 @@ update_work_list([_|Is], Sets) ->
update_work_list([], Sets) -> Sets.
add_to_work_list(F, {Fs,Used}=Sets) ->
- case sets:is_element(F, Used) of
+ case cerl_sets:is_element(F, Used) of
true -> Sets;
- false -> {[F|Fs],sets:add_element(F, Used)}
+ false -> {[F|Fs],cerl_sets:add_element(F, Used)}
end.
%%%
%%% Coalesce adjacent labels. Renumber all labels to eliminate gaps.
-%%% This cleanup will slightly reduce file size and slightly speed up loading.
-%%%
-%%% We also expand is_record/3 to a sequence of instructions. It is done
-%%% here merely because this module will always be called even if optimization
-%%% is turned off. We don't want to do the expansion in beam_asm because we
-%%% want to see the expanded code in a .S file.
+%%% This cleanup will slightly reduce file size and slightly speed up
+%%% loading.
%%%
-type label() :: beam_asm:label().
@@ -127,45 +110,6 @@ function_renumber([{function,Name,Arity,_Entry,Asm0}|Fs], St0, Acc) ->
function_renumber(Fs, St, [{function,Name,Arity,St#st.entry,Asm}|Acc]);
function_renumber([], St, Acc) -> {Acc,St}.
-renumber_labels([{bif,is_record,{f,_},
- [Term,{atom,Tag}=TagAtom,{integer,Arity}],Dst}|Is0], Acc, St) ->
- ContLabel = 900000000+2*St#st.lc,
- FailLabel = ContLabel+1,
- Fail = {f,FailLabel},
- Tmp = Dst,
- Is = case is_record_tuple(Term, Tag, Arity) of
- yes ->
- [{move,{atom,true},Dst}|Is0];
- no ->
- [{move,{atom,false},Dst}|Is0];
- maybe ->
- [{test,is_tuple,Fail,[Term]},
- {test,test_arity,Fail,[Term,Arity]},
- {get_tuple_element,Term,0,Tmp},
- {test,is_eq_exact,Fail,[Tmp,TagAtom]},
- {move,{atom,true},Dst},
- {jump,{f,ContLabel}},
- {label,FailLabel},
- {move,{atom,false},Dst},
- {jump,{f,ContLabel}}, %Improves optimization by beam_dead.
- {label,ContLabel}|Is0]
- end,
- renumber_labels(Is, Acc, St);
-renumber_labels([{test,is_record,{f,_}=Fail,
- [Term,{atom,Tag}=TagAtom,{integer,Arity}]}|Is0], Acc, St) ->
- Tmp = {x,1022},
- Is = case is_record_tuple(Term, Tag, Arity) of
- yes ->
- Is0;
- no ->
- [{jump,Fail}|Is0];
- maybe ->
- [{test,is_tuple,Fail,[Term]},
- {test,test_arity,Fail,[Term,Arity]},
- {get_tuple_element,Term,0,Tmp},
- {test,is_eq_exact,Fail,[Tmp,TagAtom]}|Is0]
- end,
- renumber_labels(Is, Acc, St);
renumber_labels([{label,Old}|Is], [{label,New}|_]=Acc, #st{lmap=D0}=St) ->
D = [{Old,New}|D0],
renumber_labels(Is, Acc, St#st{lmap=D});
@@ -179,12 +123,6 @@ renumber_labels([I|Is], Acc, St0) ->
renumber_labels(Is, [I|Acc], St0);
renumber_labels([], Acc, St) -> {Acc,St}.
-is_record_tuple({x,_}, _, _) -> maybe;
-is_record_tuple({y,_}, _, _) -> maybe;
-is_record_tuple({literal,Tuple}, Tag, Arity)
- when element(1, Tuple) =:= Tag, tuple_size(Tuple) =:= Arity -> yes;
-is_record_tuple(_, _, _) -> no.
-
function_replace([{function,Name,Arity,Entry,Asm0}|Fs], Dict, Acc) ->
Asm = try
Fb = fun(Old) -> throw({error,{undefined_label,Old}}) end,
@@ -199,100 +137,6 @@ function_replace([{function,Name,Arity,Entry,Asm0}|Fs], Dict, Acc) ->
function_replace([], _, Acc) -> Acc.
%%%
-%%% Final fixup of bs_start_match2/5,bs_save2/bs_restore2 instructions for
-%%% new bit syntax matching (introduced in R11B).
-%%%
-%%% Pass 1: Scan the code, looking for bs_restore2/2 instructions.
-%%%
-%%% Pass 2: Update bs_save2/2 and bs_restore/2 instructions. Remove
-%%% any bs_save2/2 instruction whose save position are never referenced
-%%% by any bs_restore2/2 instruction.
-%%%
-%%% Note this module can be invoked several times, so we must be careful
-%%% not to touch instructions that have already been fixed up.
-%%%
-
-bs_fix(Fs) ->
- bs_fix(Fs, []).
-
-bs_fix([{function,Name,Arity,Entry,Asm0}|Fs], Acc) ->
- Asm = bs_function(Asm0),
- bs_fix(Fs, [{function,Name,Arity,Entry,Asm}|Acc]);
-bs_fix([], Acc) -> reverse(Acc).
-
-bs_function(Is) ->
- Dict0 = bs_restores(Is, []),
- S0 = sofs:relation(Dict0, [{context,save_point}]),
- S1 = sofs:relation_to_family(S0),
- S = sofs:to_external(S1),
- Dict = make_save_point_dict(S, []),
- bs_replace(Is, Dict, []).
-
-make_save_point_dict([{Ctx,Pts}|T], Acc0) ->
- Acc = make_save_point_dict_1(Pts, Ctx, 0, Acc0),
- make_save_point_dict(T, Acc);
-make_save_point_dict([], Acc) ->
- gb_trees:from_orddict(ordsets:from_list(Acc)).
-
-make_save_point_dict_1([H|T], Ctx, I, Acc) ->
- make_save_point_dict_1(T, Ctx, I+1, [{{Ctx,H},I}|Acc]);
-make_save_point_dict_1([], Ctx, I, Acc) ->
- [{Ctx,I}|Acc].
-
-%% Pass 1.
-bs_restores([{bs_restore2,_,{Same,Same}}|Is], Dict) ->
- %% This save point is special. No explicit save is needed.
- bs_restores(Is, Dict);
-bs_restores([{bs_restore2,_,{atom,start}}|Is], Dict) ->
- %% This instruction can occur if "compilation"
- %% started from a .S file.
- bs_restores(Is, Dict);
-bs_restores([{bs_restore2,_,{_,_}=SavePoint}|Is], Dict) ->
- bs_restores(Is, [SavePoint|Dict]);
-bs_restores([_|Is], Dict) ->
- bs_restores(Is, Dict);
-bs_restores([], Dict) -> Dict.
-
-%% Pass 2.
-bs_replace([{test,bs_start_match2,F,Live,[Src,{context,Ctx}],CtxR}|T], Dict, Acc) ->
- Slots = case gb_trees:lookup(Ctx, Dict) of
- {value,Slots0} -> Slots0;
- none -> 0
- end,
- I = {test,bs_start_match2,F,Live,[Src,Slots],CtxR},
- bs_replace(T, Dict, [I|Acc]);
-bs_replace([{bs_save2,CtxR,{_,_}=SavePoint}|T], Dict, Acc) ->
- case gb_trees:lookup(SavePoint, Dict) of
- {value,N} ->
- bs_replace(T, Dict, [{bs_save2,CtxR,N}|Acc]);
- none ->
- bs_replace(T, Dict, Acc)
- end;
-bs_replace([{bs_restore2,_,{atom,start}}=I|T], Dict, Acc) ->
- %% This instruction can occur if "compilation"
- %% started from a .S file.
- bs_replace(T, Dict, [I|Acc]);
-bs_replace([{bs_restore2,CtxR,{Same,Same}}|T], Dict, Acc) ->
- %% This save point refers to the point in the binary where the match
- %% started. It has a special name.
- bs_replace(T, Dict, [{bs_restore2,CtxR,{atom,start}}|Acc]);
-bs_replace([{bs_restore2,CtxR,{_,_}=SavePoint}|T], Dict, Acc) ->
- N = gb_trees:get(SavePoint, Dict),
- bs_replace(T, Dict, [{bs_restore2,CtxR,N}|Acc]);
-bs_replace([I|Is], Dict, Acc) ->
- bs_replace(Is, Dict, [I|Acc]);
-bs_replace([], _, Acc) -> reverse(Acc).
-
-bs_clean_saves_1([{bs_save2,_,{_,_}=SavePoint}=I|Is], Needed, Acc) ->
- case gb_sets:is_member(SavePoint, Needed) of
- false -> bs_clean_saves_1(Is, Needed, Acc);
- true -> bs_clean_saves_1(Is, Needed, [I|Acc])
- end;
-bs_clean_saves_1([I|Is], Needed, Acc) ->
- bs_clean_saves_1(Is, Needed, [I|Acc]);
-bs_clean_saves_1([], _, Acc) -> reverse(Acc).
-
-%%%
%%% Remove line instructions if requested.
%%%
diff --git a/lib/compiler/src/beam_dead.erl b/lib/compiler/src/beam_dead.erl
deleted file mode 100644
index efad082152..0000000000
--- a/lib/compiler/src/beam_dead.erl
+++ /dev/null
@@ -1,971 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2002-2018. All 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_dead).
-
--export([module/2]).
-
-%%% Dead code is code that is executed but has no effect. This
-%%% optimization pass either removes dead code or jumps around it,
-%%% potentially making it unreachable and a target for the
-%%% the beam_jump pass.
-
--import(lists, [mapfoldl/3,reverse/1]).
-
-
--spec module(beam_utils:module_code(), [compile:option()]) ->
- {'ok',beam_utils:module_code()}.
-
-module({Mod,Exp,Attr,Fs0,_}, _Opts) ->
- {Fs1,Lc1} = beam_clean:clean_labels(Fs0),
- {Fs,Lc} = mapfoldl(fun function/2, Lc1, Fs1),
- %%{Fs,Lc} = {Fs1,Lc1},
- {ok,{Mod,Exp,Attr,Fs,Lc}}.
-
-function({function,Name,Arity,CLabel,Is0}, Lc0) ->
- try
- Is1 = beam_jump:remove_unused_labels(Is0),
-
- %% Initialize label information with the code
- %% for the func_info label. Without it, a register
- %% may seem to be live when it is not.
- [{label,L}|FiIs] = Is1,
- D0 = beam_utils:empty_label_index(),
- D = beam_utils:index_label(L, FiIs, D0),
-
- %% Optimize away dead code.
- {Is2,Lc} = forward(Is1, Lc0),
- Is3 = backward(Is2, D),
- Is = move_move_into_block(Is3, []),
- {{function,Name,Arity,CLabel,Is},Lc}
- catch
- Class:Error:Stack ->
- io:fwrite("Function: ~w/~w\n", [Name,Arity]),
- erlang:raise(Class, Error, Stack)
- end.
-
-%% 'move' instructions outside of blocks may thwart the jump optimizer.
-%% Move them back into the block.
-
-move_move_into_block([{block,Bl0},{move,S,D}|Is], Acc) ->
- Bl = Bl0 ++ [{set,[D],[S],move}],
- move_move_into_block([{block,Bl}|Is], Acc);
-move_move_into_block([{move,S,D}|Is], Acc) ->
- Bl = [{set,[D],[S],move}],
- move_move_into_block([{block,Bl}|Is], Acc);
-move_move_into_block([I|Is], Acc) ->
- move_move_into_block(Is, [I|Acc]);
-move_move_into_block([], Acc) -> reverse(Acc).
-
-%%%
-%%% Scan instructions in execution order and remove redundant 'move'
-%%% instructions. 'move' instructions are redundant if we know that
-%%% the register already contains the value being assigned, as in the
-%%% following code:
-%%%
-%%% test is_eq_exact SomeLabel Src Dst
-%%% move Src Dst
-%%%
-%%% or in:
-%%%
-%%% test is_nil SomeLabel Dst
-%%% move nil Dst
-%%%
-%%% or in:
-%%%
-%%% select_val Register FailLabel [... Literal => L1...]
-%%% .
-%%% .
-%%% .
-%%% L1: move Literal Register
-%%%
-%%% Also add extra labels to help the second backward pass.
-%%%
-
-forward(Is, Lc) ->
- forward(Is, #{}, Lc, []).
-
-forward([{move,_,_}=Move|[{label,L}|_]=Is], D, Lc, Acc) ->
- %% move/2 followed by jump/1 is optimized by backward/3.
- forward([Move,{jump,{f,L}}|Is], D, Lc, Acc);
-forward([{bif,_,_,_,_}=Bif|[{label,L}|_]=Is], D, Lc, Acc) ->
- %% bif/4 followed by jump/1 is optimized by backward/3.
- forward([Bif,{jump,{f,L}}|Is], D, Lc, Acc);
-forward([{block,[]}|Is], D, Lc, Acc) ->
- %% Empty blocks can prevent optimizations.
- forward(Is, D, Lc, Acc);
-forward([{select,select_val,Reg,_,List}=I|Is], D0, Lc, Acc) ->
- D = update_value_dict(List, Reg, D0),
- forward(Is, D, Lc, [I|Acc]);
-forward([{label,Lbl}=LblI,{block,[{set,[Dst],[Lit],move}|BlkIs]}=Blk|Is], D, Lc, Acc) ->
- %% Assumption: The target labels in a select_val/3 instruction
- %% cannot be reached in any other way than through the select_val/3
- %% instruction (i.e. there can be no fallthrough to such label and
- %% it cannot be referenced by, for example, a jump/1 instruction).
- Key = {Lbl,Dst},
- Block = case D of
- #{Key := Lit} -> {block,BlkIs}; %Safe to remove move instruction.
- _ -> Blk %Must keep move instruction.
- end,
- forward([Block|Is], D, Lc, [LblI|Acc]);
-forward([{label,Lbl}=LblI|[{move,Lit,Dst}|Is1]=Is0], D, Lc, Acc) ->
- %% Assumption: The target labels in a select_val/3 instruction
- %% cannot be reached in any other way than through the select_val/3
- %% instruction (i.e. there can be no fallthrough to such label and
- %% it cannot be referenced by, for example, a jump/1 instruction).
- Is = case maps:find({Lbl,Dst}, D) of
- {ok,Lit} -> Is1; %Safe to remove move instruction.
- _ -> Is0 %Keep move instruction.
- end,
- forward(Is, D, Lc, [LblI|Acc]);
-forward([{test,is_eq_exact,_,[Same,Same]}|Is], D, Lc, Acc) ->
- forward(Is, D, Lc, Acc);
-forward([{test,is_eq_exact,_,[Dst,Src]}=I,
- {block,[{set,[Dst],[Src],move}|Bl]}|Is], D, Lc, Acc) ->
- forward([I,{block,Bl}|Is], D, Lc, Acc);
-forward([{test,is_nil,_,[Dst]}=I,
- {block,[{set,[Dst],[nil],move}|Bl]}|Is], D, Lc, Acc) ->
- forward([I,{block,Bl}|Is], D, Lc, Acc);
-forward([{test,is_eq_exact,_,[Dst,Src]}=I,{move,Src,Dst}|Is], D, Lc, Acc) ->
- forward([I|Is], D, Lc, Acc);
-forward([{test,is_nil,_,[Dst]}=I,{move,nil,Dst}|Is], D, Lc, Acc) ->
- forward([I|Is], D, Lc, Acc);
-forward([{test,_,_,_}=I|Is]=Is0, D, Lc, Acc) ->
- %% Help the second, backward pass to by inserting labels after
- %% relational operators so that they can be skipped if they are
- %% known to be true.
- case useful_to_insert_label(Is0) of
- false -> forward(Is, D, Lc, [I|Acc]);
- true -> forward(Is, D, Lc+1, [{label,Lc},I|Acc])
- end;
-forward([I|Is], D, Lc, Acc) ->
- forward(Is, D, Lc, [I|Acc]);
-forward([], _, Lc, Acc) -> {Acc,Lc}.
-
-update_value_dict([Lit,{f,Lbl}|T], Reg, D0) ->
- Key = {Lbl,Reg},
- D = case D0 of
- #{Key := inconsistent} -> D0;
- #{Key := _} -> D0#{Key := inconsistent};
- _ -> D0#{Key => Lit}
- end,
- update_value_dict(T, Reg, D);
-update_value_dict([], _, D) -> D.
-
-useful_to_insert_label([_,{label,_}|_]) ->
- false;
-useful_to_insert_label([{test,Op,_,_}|_]) ->
- case Op of
- is_lt -> true;
- is_ge -> true;
- is_eq_exact -> true;
- is_ne_exact -> true;
- _ -> false
- end.
-
-%%%
-%%% Scan instructions in reverse execution order and try to
-%%% shortcut branch instructions.
-%%%
-%%% For example, in this code:
-%%%
-%%% move Literal Register
-%%% jump L1
-%%% .
-%%% .
-%%% .
-%%% L1: test is_{integer,atom} FailLabel Register
-%%% select_val {x,0} FailLabel [... Literal => L2...]
-%%% .
-%%% .
-%%% .
-%%% L2: ...
-%%%
-%%% the 'selectval' instruction will always transfer control to L2,
-%%% so we can just as well jump to L2 directly by rewriting the
-%%% first part of the sequence like this:
-%%%
-%%% move Literal Register
-%%% jump L2
-%%%
-%%% If register Register is killed at label L2, we can remove the
-%%% 'move' instruction, leaving just the 'jump' instruction:
-%%%
-%%% jump L2
-%%%
-%%% These transformations may leave parts of the code unreachable.
-%%% The beam_jump pass will remove the unreachable code.
-
-backward(Is, D) ->
- backward(Is, D, []).
-
-backward([{test,is_eq_exact,Fail,[Dst,{integer,Arity}]}=I|
- [{bif,tuple_size,Fail,[Reg],Dst}|Is]=Is0], D, Acc) ->
- %% Provided that Dst is killed following this sequence,
- %% we can rewrite the instructions like this:
- %%
- %% bif tuple_size Fail Reg Dst ==> is_tuple Fail Reg
- %% is_eq_exact Fail Dst Integer test_arity Fail Reg Integer
- %%
- %% (still two instructions, but they they will be combined to
- %% one by the loader).
- case beam_utils:is_killed(Dst, Acc, D) andalso (Arity bsr 32) =:= 0 of
- false ->
- %% Not safe because the register Dst is not killed
- %% (probably cannot not happen in practice) or the arity
- %% does not fit in 32 bits (the loader will fail to load
- %% the module). We must move the first instruction to the
- %% accumulator to avoid an infinite loop.
- backward(Is0, D, [I|Acc]);
- true ->
- %% Safe.
- backward([{test,test_arity,Fail,[Reg,Arity]},
- {test,is_tuple,Fail,[Reg]}|Is], D, Acc)
- end;
-backward([{label,Lbl}=L|Is], D, Acc) ->
- backward(Is, beam_utils:index_label(Lbl, Acc, D), [L|Acc]);
-backward([{select,select_val,Reg,{f,Fail0},List0}|Is], D, Acc) ->
- List1 = shortcut_select_list(List0, Reg, D, []),
- Fail1 = shortcut_label(Fail0, D),
- Fail = shortcut_bs_test(Fail1, Is, D),
- List = prune_redundant(List1, Fail),
- case List of
- [] ->
- Jump = {jump,{f,Fail}},
- backward([Jump|Is], D, Acc);
- [V,F] ->
- Test = {test,is_eq_exact,{f,Fail},[Reg,V]},
- Jump = {jump,F},
- backward([Jump,Test|Is], D, Acc);
- [{atom,B1},F,{atom,B2},F] when B1 =:= not B2 ->
- Test = {test,is_boolean,{f,Fail},[Reg]},
- Jump = {jump,F},
- backward([Jump,Test|Is], D, Acc);
- [_|_] ->
- Sel = {select,select_val,Reg,{f,Fail},List},
- backward(Is, D, [Sel|Acc])
- end;
-backward([{jump,{f,To0}},{move,Src,Reg}=Move|Is], D, Acc) ->
- To = shortcut_select_label(To0, Reg, Src, D),
- Jump = {jump,{f,To}},
- case is_killed_at(Reg, To, D) of
- false -> backward([Move|Is], D, [Jump|Acc]);
- true -> backward([Jump|Is], D, Acc)
- end;
-backward([{jump,{f,To}}=J|[{bif,Op,{f,BifFail},Ops,Reg}|Is]=Is0], D, Acc) ->
- try replace_comp_op(To, Reg, Op, Ops, D) of
- {Test,Jump} ->
- backward([Jump,Test|Is], D, Acc)
- catch
- throw:not_possible ->
- case To =:= BifFail of
- true ->
- %% The bif instruction is redundant. See the comment
- %% in the next clause for why there is no need to
- %% test for liveness of Reg at label To.
- backward([J|Is], D, Acc);
- false ->
- backward(Is0, D, [J|Acc])
- end
- end;
-backward([{jump,{f,To}}=J|[{gc_bif,_,{f,To},_,_,_Dst}|Is]], D, Acc) ->
- %% The gc_bif instruction is redundant, since either the gc_bif
- %% instruction itself or the jump instruction will transfer control
- %% to label To. Note that a gc_bif instruction does not assign its
- %% destination register if the failure branch is taken; therefore,
- %% the code at label To is not allowed to assume that the destination
- %% register is initialized, and it is therefore no need to test
- %% for liveness of the destination register at label To.
- backward([J|Is], D, Acc);
-backward([{test,bs_start_match2,F,Live,[Src,_]=Args,Ctxt}|Is], D, Acc0) ->
- {f,To0} = F,
- case test_bs_literal(F, Ctxt, D, Acc0) of
- {none,Acc} ->
- %% Ctxt killed immediately after bs_start_match2.
- To = shortcut_bs_context_to_binary(To0, Src, D),
- I = {test,is_bitstr,{f,To},[Src]},
- backward(Is, D, [I|Acc]);
- {Literal,Acc} ->
- %% Ctxt killed after matching a literal.
- To = shortcut_bs_context_to_binary(To0, Src, D),
- Eq = {test,is_eq_exact,{f,To},[Src,{literal,Literal}]},
- backward(Is, D, [Eq|Acc]);
- not_killed ->
- %% Ctxt not killed. Not much to do.
- To = shortcut_bs_start_match(To0, Src, D),
- I = {test,bs_start_match2,{f,To},Live,Args,Ctxt},
- backward(Is, D, [I|Acc0])
- end;
-backward([{test,Op,{f,To0},Ops0}|Is], D, Acc) ->
- To1 = shortcut_bs_test(To0, Is, D),
- To2 = shortcut_label(To1, D),
- To3 = shortcut_rel_op(To2, Op, Ops0, D),
-
- %% Try to shortcut a repeated test:
- %%
- %% test Op {f,Fail1} Operands test Op {f,Fail2} Operands
- %% . . . ==> ...
- %% Fail1: test Op {f,Fail2} Operands Fail1: test Op {f,Fail2} Operands
- %%
- To = case beam_utils:code_at(To3, D) of
- [{test,Op,{f,To4},Ops}|_] ->
- case equal_ops(Ops0, Ops) of
- true -> To4;
- false -> To3
- end;
- _Code ->
- To3
- end,
- I = case Op of
- is_eq_exact -> combine_eqs(To, Ops0, D, Acc);
- _ -> {test,Op,{f,To},Ops0}
- end,
- case {I,Acc} of
- {{test,is_atom,Fail,Ops0},[{test,is_boolean,Fail,Ops0}|_]} ->
- %% An is_atom test before an is_boolean test (with the
- %% same failure label) is redundant.
- backward(Is, D, Acc);
- {{test,is_atom,Fail,[R]},
- [{test,is_eq_exact,Fail,[R,{atom,_}]}|_]} ->
- %% An is_atom test before a comparison with an atom (with
- %% the same failure label) is redundant.
- backward(Is, D, Acc);
- {{test,is_integer,Fail,[R]},
- [{test,is_eq_exact,Fail,[R,{integer,_}]}|_]} ->
- %% An is_integer test before a comparison with an integer
- %% (with the same failure label) is redundant.
- backward(Is, D, Acc);
- {{test,_,_,_},_} ->
- %% Still a test instruction. Done.
- backward(Is, D, [I|Acc]);
- {_,_} ->
- %% Rewritten to a select_val. Rescan.
- backward([I|Is], D, Acc)
- end;
-backward([{test,Op,{f,To0},Live,Ops0,Dst}|Is], D, Acc) ->
- To1 = shortcut_bs_test(To0, Is, D),
- To2 = shortcut_label(To1, D),
- %% Try to shortcut a repeated test:
- %%
- %% test Op {f,Fail1} _ Ops _ test Op {f,Fail2} _ Ops _
- %% . . . ==> ...
- %% Fail1: test Op {f,Fail2} _ Ops _ Fail1: test Op {f,Fail2} _ Ops _
- %%
- To = case beam_utils:code_at(To2, D) of
- [{test,Op,{f,To3},_,Ops,_}|_] ->
- case equal_ops(Ops0, Ops) of
- true -> To3;
- false -> To2
- end;
- _Code ->
- To2
- end,
- I = {test,Op,{f,To},Live,Ops0,Dst},
- backward(Is, D, [I|Acc]);
-backward([{kill,_}=I|Is], D, [{line,_},Exit|_]=Acc) ->
- case beam_jump:is_exit_instruction(Exit) of
- false -> backward(Is, D, [I|Acc]);
- true -> backward(Is, D, Acc)
- end;
-backward([{bif,'or',{f,To0},[Dst,{atom,false}],Dst}=I|Is], D,
- [{test,is_eq_exact,{f,To},[Dst,{atom,true}]}|_]=Acc) ->
- case shortcut_label(To0, D) of
- To ->
- backward(Is, D, Acc);
- _ ->
- backward(Is, D, [I|Acc])
- end;
-backward([{bif,map_get,{f,FF},[Key,Map],_}=I0,
- {test,has_map_fields,{f,FT}=F,[Map|Keys0]}=I1|Is], D, Acc) when FF =/= 0 ->
- case shortcut_label(FF, D) of
- FT ->
- case lists:delete(Key, Keys0) of
- [] ->
- backward([I0|Is], D, Acc);
- Keys ->
- Test = {test,has_map_fields,F,[Map|Keys]},
- backward([Test|Is], D, [I0|Acc])
- end;
- _ ->
- backward([I1|Is], D, [I0|Acc])
- end;
-backward([{bif,map_get,{f,FF},[_,Map],_}=I0,
- {test,is_map,{f,FT},[Map]}=I1|Is], D, Acc) when FF =/= 0 ->
- case shortcut_label(FF, D) of
- FT -> backward([I0|Is], D, Acc);
- _ -> backward([I1|Is], D, [I0|Acc])
- end;
-backward([I|Is], D, Acc) ->
- backward(Is, D, [I|Acc]);
-backward([], _D, Acc) -> Acc.
-
-equal_ops([{field_flags,FlA0}|T0], [{field_flags,FlB0}|T1]) ->
- FlA = lists:keydelete(anno, 1, FlA0),
- FlB = lists:keydelete(anno, 1, FlB0),
- FlA =:= FlB andalso equal_ops(T0, T1);
-equal_ops([Op|T0], [Op|T1]) ->
- equal_ops(T0, T1);
-equal_ops([], []) -> true;
-equal_ops(_, _) -> false.
-
-shortcut_select_list([Lit,{f,To0}|T], Reg, D, Acc) ->
- To = shortcut_select_label(To0, Reg, Lit, D),
- shortcut_select_list(T, Reg, D, [{f,To},Lit|Acc]);
-shortcut_select_list([], _, _, Acc) -> reverse(Acc).
-
-shortcut_label(0, _) ->
- 0;
-shortcut_label(To0, D) ->
- case beam_utils:code_at(To0, D) of
- [{jump,{f,To}}|_] -> shortcut_label(To, D);
- _ -> To0
- end.
-
-shortcut_select_label(To, Reg, Lit, D) ->
- shortcut_rel_op(To, is_ne_exact, [Reg,Lit], D).
-
-prune_redundant([_,{f,Fail}|T], Fail) ->
- prune_redundant(T, Fail);
-prune_redundant([V,F|T], Fail) ->
- [V,F|prune_redundant(T, Fail)];
-prune_redundant([], _) -> [].
-
-%% Replace a comparison operator with a test instruction and a jump.
-%% For example, if we have this code:
-%%
-%% bif '=:=' Fail Src1 Src2 {x,0}
-%% jump L1
-%% .
-%% .
-%% .
-%% L1: select_val {x,0} FailLabel [... true => L2..., ...false => L3...]
-%%
-%% the first two instructions can be replaced with
-%%
-%% test is_eq_exact L3 Src1 Src2
-%% jump L2
-%%
-%% provided that {x,0} is killed at both L2 and L3.
-
-replace_comp_op(To, Reg, Op, Ops, D) ->
- False = comp_op_find_shortcut(To, Reg, {atom,false}, D),
- True = comp_op_find_shortcut(To, Reg, {atom,true}, D),
- {bif_to_test(Op, Ops, False),{jump,{f,True}}}.
-
-comp_op_find_shortcut(To0, Reg, Val, D) ->
- case shortcut_select_label(To0, Reg, Val, D) of
- To0 ->
- not_possible();
- To ->
- case is_killed_at(Reg, To, D) of
- false -> not_possible();
- true -> To
- end
- end.
-
-bif_to_test(Name, Args, Fail) ->
- try
- beam_utils:bif_to_test(Name, Args, {f,Fail})
- catch
- error:_ -> not_possible()
- end.
-
-not_possible() -> throw(not_possible).
-
-%% combine_eqs(To, Operands, Acc) -> Instruction.
-%% Combine two is_eq_exact instructions or (an is_eq_exact
-%% instruction and a select_val instruction) to a select_val
-%% instruction if possible.
-%%
-%% Example:
-%%
-%% is_eq_exact F1 Reg Lit1 select_val Reg F2 [ Lit1 L1
-%% L1: . Lit2 L2 ]
-%% .
-%% . ==>
-%% .
-%% F1: is_eq_exact F2 Reg Lit2 F1: is_eq_exact F2 Reg Lit2
-%% L2: .... L2:
-%%
-combine_eqs(To, [Reg,{Type,_}=Lit1]=Ops, D, Acc)
- when Type =:= atom; Type =:= integer ->
- Next = case Acc of
- [{label,Lbl}|_] -> Lbl;
- [{jump,{f,Lbl}}|_] -> Lbl
- end,
- case beam_utils:code_at(To, D) of
- [{test,is_eq_exact,{f,F2},[Reg,{Type,_}=Lit2]},
- {label,L2}|_] when Lit1 =/= Lit2 ->
- {select,select_val,Reg,{f,F2},[Lit1,{f,Next},Lit2,{f,L2}]};
- [{test,is_eq_exact,{f,F2},[Reg,{Type,_}=Lit2]},
- {jump,{f,L2}}|_] when Lit1 =/= Lit2 ->
- {select,select_val,Reg,{f,F2},[Lit1,{f,Next},Lit2,{f,L2}]};
- [{select,select_val,Reg,{f,F2},[{Type,_}|_]=List0}|_] ->
- List = remove_from_list(Lit1, List0),
- {select,select_val,Reg,{f,F2},[Lit1,{f,Next}|List]};
- _Is ->
- {test,is_eq_exact,{f,To},Ops}
- end;
-combine_eqs(To, Ops, _D, _Acc) ->
- {test,is_eq_exact,{f,To},Ops}.
-
-remove_from_list(Lit, [Lit,{f,_}|T]) ->
- T;
-remove_from_list(Lit, [Val,{f,_}=Fail|T]) ->
- [Val,Fail|remove_from_list(Lit, T)];
-remove_from_list(_, []) -> [].
-
-
-test_bs_literal(F, Ctxt, D,
- [{test,bs_match_string,F,[Ctxt,Bs]},
- {test,bs_test_tail2,F,[Ctxt,0]}|Acc]) ->
- test_bs_literal_1(Ctxt, Acc, D, Bs);
-test_bs_literal(F, Ctxt, D, [{test,bs_test_tail2,F,[Ctxt,0]}|Acc]) ->
- test_bs_literal_1(Ctxt, Acc, D, <<>>);
-test_bs_literal(_, Ctxt, D, Acc) ->
- test_bs_literal_1(Ctxt, Acc, D, none).
-
-test_bs_literal_1(Ctxt, Is, D, Literal) ->
- case beam_utils:is_killed(Ctxt, Is, D) of
- true -> {Literal,Is};
- false -> not_killed
- end.
-
-%% shortcut_bs_test(TargetLabel, ReversedInstructions, D) -> TargetLabel'
-%% Try to shortcut the failure label for bit syntax matching.
-
-shortcut_bs_test(To, Is, D) ->
- shortcut_bs_test_1(beam_utils:code_at(To, D), Is, To, D).
-
-shortcut_bs_test_1([{bs_restore2,Reg,SavePoint},
- {label,_},
- {test,bs_test_tail2,{f,To},[_,TailBits]}|_],
- PrevIs, To0, D) ->
- case count_bits_matched(PrevIs, {Reg,SavePoint}, 0) of
- Bits when Bits > TailBits ->
- %% This instruction will fail. We know because a restore has been
- %% done from the previous point SavePoint in the binary, and we
- %% also know that the binary contains at least Bits bits from
- %% SavePoint.
- %%
- %% Since we will skip a bs_restore2 if we shortcut to label To,
- %% we must now make sure that code at To does not depend on
- %% the position in the context in any way.
- case shortcut_bs_pos_used(To, Reg, D) of
- false -> To;
- true -> To0
- end;
- _Bits ->
- To0
- end;
-shortcut_bs_test_1([_|_], _, To, _) -> To.
-
-%% counts_bits_matched(ReversedInstructions, SavePoint, Bits) -> Bits'
-%% Given a reversed instruction stream, determine the minimum number
-%% of bits that will be matched by bit syntax instructions up to the
-%% given save point.
-
-count_bits_matched([{test,bs_get_utf8,{f,_},_,_,_}|Is], SavePoint, Bits) ->
- count_bits_matched(Is, SavePoint, Bits+8);
-count_bits_matched([{test,bs_get_utf16,{f,_},_,_,_}|Is], SavePoint, Bits) ->
- count_bits_matched(Is, SavePoint, Bits+16);
-count_bits_matched([{test,bs_get_utf32,{f,_},_,_,_}|Is], SavePoint, Bits) ->
- count_bits_matched(Is, SavePoint, Bits+32);
-count_bits_matched([{test,_,_,_,[_,Sz,U,{field_flags,_}],_}|Is], SavePoint, Bits) ->
- case Sz of
- {integer,N} -> count_bits_matched(Is, SavePoint, Bits+N*U);
- _ -> count_bits_matched(Is, SavePoint, Bits)
- end;
-count_bits_matched([{test,bs_match_string,_,[_,Bs]}|Is], SavePoint, Bits) ->
- count_bits_matched(Is, SavePoint, Bits+bit_size(Bs));
-count_bits_matched([{test,_,_,_}|Is], SavePoint, Bits) ->
- count_bits_matched(Is, SavePoint, Bits);
-count_bits_matched([{bs_save2,Reg,SavePoint}|_], {Reg,SavePoint}, Bits) ->
- %% The save point we are looking for - we are done.
- Bits;
-count_bits_matched([_|_], _, Bits) -> Bits.
-
-shortcut_bs_pos_used(To, Reg, D) ->
- shortcut_bs_pos_used_1(beam_utils:code_at(To, D), Reg, D).
-
-shortcut_bs_pos_used_1([{bs_context_to_binary,Reg}|_], Reg, _) ->
- false;
-shortcut_bs_pos_used_1(Is, Reg, D) ->
- not beam_utils:is_killed(Reg, Is, D).
-
-%% shortcut_bs_start_match(TargetLabel, Reg) -> TargetLabel
-%% A failing bs_start_match2 instruction means that the source (Reg)
-%% cannot be a binary. That means that it is safe to skip
-%% bs_context_to_binary instructions operating on Reg, and
-%% bs_start_match2 instructions operating on Reg.
-
-shortcut_bs_start_match(To, Reg, D) ->
- shortcut_bs_start_match_1(beam_utils:code_at(To, D), Reg, To, D).
-
-shortcut_bs_start_match_1([{bs_context_to_binary,Reg}|Is], Reg, To, D) ->
- shortcut_bs_start_match_1(Is, Reg, To, D);
-shortcut_bs_start_match_1([{jump,{f,To}}|_], Reg, _, D) ->
- Code = beam_utils:code_at(To, D),
- shortcut_bs_start_match_1(Code, Reg, To, D);
-shortcut_bs_start_match_1([{test,bs_start_match2,{f,To},_,[Reg|_],_}|_],
- Reg, _, D) ->
- Code = beam_utils:code_at(To, D),
- shortcut_bs_start_match_1(Code, Reg, To, D);
-shortcut_bs_start_match_1(_, _, To, _) ->
- To.
-
-%% shortcut_bs_context_to_binary(TargetLabel, Reg) -> TargetLabel
-%% If a bs_start_match2 instruction has been eliminated, the
-%% bs_context_to_binary instruction can be eliminated too.
-
-shortcut_bs_context_to_binary(To, Reg, D) ->
- shortcut_bs_ctb_1(beam_utils:code_at(To, D), Reg, To, D).
-
-shortcut_bs_ctb_1([{bs_context_to_binary,Reg}|Is], Reg, To, D) ->
- shortcut_bs_ctb_1(Is, Reg, To, D);
-shortcut_bs_ctb_1([{jump,{f,To}}|_], Reg, _, D) ->
- Code = beam_utils:code_at(To, D),
- shortcut_bs_ctb_1(Code, Reg, To, D);
-shortcut_bs_ctb_1(_, _, To, _) ->
- To.
-
-%% shortcut_rel_op(FailLabel, Operator, [Operand], D) -> FailLabel'
-%% Try to shortcut the given test instruction. Example:
-%%
-%% is_ge L1 {x,0} 48
-%% .
-%% .
-%% .
-%% L1: is_ge L2 {x,0} 65
-%%
-%% The first test instruction can be rewritten to "is_ge L2 {x,0} 48"
-%% since the instruction at L1 will also fail.
-%%
-%% If there are instructions between L1 and the other test instruction
-%% it may still be possible to do the shortcut. For example:
-%%
-%% L1: is_eq_exact L3 {x,0} 92
-%% is_ge L2 {x,0} 65
-%%
-%% Since the first test instruction failed, we know that {x,0} must
-%% be less than 48; therefore, we know that {x,0} cannot be equal to
-%% 92 and the jump to L3 cannot happen.
-
-shortcut_rel_op(To, Op, Ops, D) ->
- case normalize_op({test,Op,{f,To},Ops}) of
- {{NormOp,A,B},_} ->
- Normalized = {negate_op(NormOp),A,B},
- shortcut_rel_op_fp(To, Normalized, D);
- {_,_} ->
- To;
- error ->
- To
- end.
-
-shortcut_rel_op_fp(To0, Normalized, D) ->
- Code = beam_utils:code_at(To0, D),
- case shortcut_any_label(Code, Normalized) of
- error ->
- To0;
- To ->
- shortcut_rel_op_fp(To, Normalized, D)
- end.
-
-%% shortcut_any_label([Instruction], PrevCondition) -> FailLabel | error
-%% Using PrevCondition (a previous condition known to be true),
-%% try to shortcut to another failure label.
-
-shortcut_any_label([{jump,{f,Lbl}}|_], _Prev) ->
- Lbl;
-shortcut_any_label([{label,Lbl}|_], _Prev) ->
- Lbl;
-shortcut_any_label([{select,select_val,R,{f,Fail},L}|_], Prev) ->
- shortcut_selectval(L, R, Fail, Prev);
-shortcut_any_label([I|Is], Prev) ->
- case normalize_op(I) of
- error ->
- error;
- {Normalized,Fail} ->
- %% We have a relational operator.
- case will_succeed(Prev, Normalized) of
- no ->
- %% This test instruction will always branch
- %% to Fail.
- Fail;
- yes ->
- %% This test instruction will never branch,
- %% so we will look at the next instruction.
- shortcut_any_label(Is, Prev);
- maybe ->
- %% May or may not branch. From now on, we can only
- %% shortcut to the this specific failure label
- %% Fail.
- shortcut_specific_label(Is, Fail, Prev)
- end
- end.
-
-%% shortcut_specific_label([Instruction], FailLabel, PrevCondition) ->
-%% FailLabel | error
-%% We have previously encountered a test instruction that may or
-%% may not branch to FailLabel. Therefore we are only allowed
-%% to do the shortcut to the same fail label (FailLabel).
-
-shortcut_specific_label([{label,_}|Is], Fail, Prev) ->
- shortcut_specific_label(Is, Fail, Prev);
-shortcut_specific_label([{select,select_val,R,{f,F},L}|_], Fail, Prev) ->
- case shortcut_selectval(L, R, F, Prev) of
- Fail -> Fail;
- _ -> error
- end;
-shortcut_specific_label([I|Is], Fail, Prev) ->
- case normalize_op(I) of
- error ->
- error;
- {Normalized,Fail} ->
- case will_succeed(Prev, Normalized) of
- no ->
- %% Will branch to FailLabel.
- Fail;
- yes ->
- %% Will definitely never branch.
- shortcut_specific_label(Is, Fail, Prev);
- maybe ->
- %% May branch, but still OK since it will branch
- %% to FailLabel.
- shortcut_specific_label(Is, Fail, Prev)
- end;
- {Normalized,_} ->
- %% This test instruction will branch to a different
- %% fail label, if it branches at all.
- case will_succeed(Prev, Normalized) of
- yes ->
- %% Still OK, since the branch will never be
- %% taken.
- shortcut_specific_label(Is, Fail, Prev);
- no ->
- %% Give up. The branch will definitely be taken
- %% to a different fail label.
- error;
- maybe ->
- %% Give up. If the branch is taken, it will be
- %% to a different fail label.
- error
- end
- end.
-
-
-%% shortcut_selectval(List, Reg, Fail, PrevCond) -> FailLabel | error
-%% Try to shortcut a selectval instruction. A selectval instruction
-%% is equivalent to the following instruction sequence:
-%%
-%% is_ne_exact L1 Reg Value1
-%% .
-%% .
-%% .
-%% is_ne_exact LN Reg ValueN
-%% jump DefaultFailLabel
-%%
-shortcut_selectval([Val,{f,Lbl}|T], R, Fail, Prev) ->
- case will_succeed(Prev, {'=/=',R,get_literal(Val)}) of
- yes -> shortcut_selectval(T, R, Fail, Prev);
- no -> Lbl;
- maybe -> error
- end;
-shortcut_selectval([], _, Fail, _) -> Fail.
-
-%% will_succeed(PrevCondition, Condition) -> yes | no | maybe
-%% PrevCondition is a condition known to be true. This function
-%% will tell whether Condition will succeed.
-
-will_succeed({Op1,Reg,A}, {Op2,Reg,B}) ->
- will_succeed_1(Op1, A, Op2, B);
-will_succeed({'=:=',Reg,{literal,A}}, {TypeTest,Reg}) ->
- case erlang:TypeTest(A) of
- false -> no;
- true -> yes
- end;
-will_succeed({_,_,_}, maybe) ->
- maybe;
-will_succeed({_,_,_}, Test) when is_tuple(Test) ->
- maybe.
-
-will_succeed_1('=:=', A, '<', B) ->
- if
- B =< A -> no;
- true -> yes
- end;
-will_succeed_1('=:=', A, '=<', B) ->
- if
- B < A -> no;
- true -> yes
- end;
-will_succeed_1('=:=', A, '=:=', B) ->
- if
- A =:= B -> yes;
- true -> no
- end;
-will_succeed_1('=:=', A, '=/=', B) ->
- if
- A =:= B -> no;
- true -> yes
- end;
-will_succeed_1('=:=', A, '>=', B) ->
- if
- B > A -> no;
- true -> yes
- end;
-will_succeed_1('=:=', A, '>', B) ->
- if
- B >= A -> no;
- true -> yes
- end;
-
-will_succeed_1('=/=', A, '=/=', B) when A =:= B -> yes;
-will_succeed_1('=/=', A, '=:=', B) when A =:= B -> no;
-
-will_succeed_1('<', A, '=:=', B) when B >= A -> no;
-will_succeed_1('<', A, '=/=', B) when B >= A -> yes;
-will_succeed_1('<', A, '<', B) when B >= A -> yes;
-will_succeed_1('<', A, '=<', B) when B > A -> yes;
-will_succeed_1('<', A, '>=', B) when B > A -> no;
-will_succeed_1('<', A, '>', B) when B >= A -> no;
-
-will_succeed_1('=<', A, '=:=', B) when B > A -> no;
-will_succeed_1('=<', A, '=/=', B) when B > A -> yes;
-will_succeed_1('=<', A, '<', B) when B > A -> yes;
-will_succeed_1('=<', A, '=<', B) when B >= A -> yes;
-will_succeed_1('=<', A, '>=', B) when B > A -> no;
-will_succeed_1('=<', A, '>', B) when B >= A -> no;
-
-will_succeed_1('>=', A, '=:=', B) when B < A -> no;
-will_succeed_1('>=', A, '=/=', B) when B < A -> yes;
-will_succeed_1('>=', A, '<', B) when B =< A -> no;
-will_succeed_1('>=', A, '=<', B) when B < A -> no;
-will_succeed_1('>=', A, '>=', B) when B =< A -> yes;
-will_succeed_1('>=', A, '>', B) when B < A -> yes;
-
-will_succeed_1('>', A, '=:=', B) when B =< A -> no;
-will_succeed_1('>', A, '=/=', B) when B =< A -> yes;
-will_succeed_1('>', A, '<', B) when B =< A -> no;
-will_succeed_1('>', A, '=<', B) when B < A -> no;
-will_succeed_1('>', A, '>=', B) when B =< A -> yes;
-will_succeed_1('>', A, '>', B) when B < A -> yes;
-
-will_succeed_1(_, _, _, _) -> maybe.
-
-%% normalize_op(Instruction) -> {Normalized,FailLabel} | error
-%% Normalized = {Operator,Register,Literal} |
-%% {TypeTest,Register} |
-%% maybe
-%% Operation = '<' | '=<' | '=:=' | '=/=' | '>=' | '>'
-%% TypeTest = is_atom | is_integer ...
-%% Literal = {literal,Term}
-%%
-%% Normalize a relational operator to facilitate further
-%% comparisons between operators. Always make the register
-%% operand the first operand. Thus the following instruction:
-%%
-%% {test,is_ge,{f,99},{integer,13},{x,0}}
-%%
-%% will be normalized to:
-%%
-%% {'=<',{x,0},{literal,13}}
-%%
-%% NOTE: Bit syntax test instructions are scary. They may change the
-%% state of match contexts and update registers, so we don't dare
-%% mess with them.
-
-normalize_op({test,is_ge,{f,Fail},Ops}) ->
- normalize_op_1('>=', Ops, Fail);
-normalize_op({test,is_lt,{f,Fail},Ops}) ->
- normalize_op_1('<', Ops, Fail);
-normalize_op({test,is_eq_exact,{f,Fail},Ops}) ->
- normalize_op_1('=:=', Ops, Fail);
-normalize_op({test,is_ne_exact,{f,Fail},Ops}) ->
- normalize_op_1('=/=', Ops, Fail);
-normalize_op({test,is_nil,{f,Fail},[R]}) ->
- normalize_op_1('=:=', [R,nil], Fail);
-normalize_op({test,Op,{f,Fail},[R]}) ->
- case erl_internal:new_type_test(Op, 1) of
- true -> {{Op,R},Fail};
- false -> {maybe,Fail}
- end;
-normalize_op({test,_,{f,Fail},_}=I) ->
- case beam_utils:is_pure_test(I) of
- true -> {maybe,Fail};
- false -> error
- end;
-normalize_op(_) ->
- error.
-
-normalize_op_1(Op, [Op1,Op2], Fail) ->
- case {get_literal(Op1),get_literal(Op2)} of
- {error,error} ->
- %% Both operands are registers.
- {maybe,Fail};
- {error,Lit} ->
- {{Op,Op1,Lit},Fail};
- {Lit,error} ->
- {{turn_op(Op),Op2,Lit},Fail};
- {_,_} ->
- %% Both operands are literals. Can probably only
- %% happen if the Core Erlang optimizations passes were
- %% turned off, so don't bother trying to do something
- %% smart here.
- {maybe,Fail}
- end.
-
-turn_op('<') -> '>';
-turn_op('>=') -> '=<';
-turn_op('=:='=Op) -> Op;
-turn_op('=/='=Op) -> Op.
-
-negate_op('>=') -> '<';
-negate_op('<') -> '>=';
-negate_op('=<') -> '>';
-negate_op('>') -> '=<';
-negate_op('=:=') -> '=/=';
-negate_op('=/=') -> '=:='.
-
-get_literal({atom,Val}) ->
- {literal,Val};
-get_literal({integer,Val}) ->
- {literal,Val};
-get_literal({float,Val}) ->
- {literal,Val};
-get_literal(nil) ->
- {literal,[]};
-get_literal({literal,_}=Lit) ->
- Lit;
-get_literal({_,_}) -> error.
-
-
-%%%
-%%% Removing stores to Y registers is not always safe
-%%% if there is an instruction that causes an exception
-%%% within a catch. In practice, there are few or no
-%%% opportunities for removing stores to Y registers anyway
-%%% if sys_core_fold has been run.
-%%%
-
-is_killed_at({x,_}=Reg, Lbl, D) ->
- beam_utils:is_killed_at(Reg, Lbl, D);
-is_killed_at({y,_}, _, _) ->
- false.
diff --git a/lib/compiler/src/beam_dict.erl b/lib/compiler/src/beam_dict.erl
index 990e86062a..b2056332e6 100644
--- a/lib/compiler/src/beam_dict.erl
+++ b/lib/compiler/src/beam_dict.erl
@@ -126,18 +126,17 @@ import(Mod0, Name0, Arity, #asm{imports=Imp0,next_import=NextIndex}=D0)
{NextIndex,D2#asm{imports=Imp,next_import=NextIndex+1}}
end.
-%% Returns the index for a string in the string table (adding the string to the
-%% table if necessary).
+%% Returns the index for a binary string in the string table (adding
+%% the string to the table if necessary).
%% string(String, Dict) -> {Offset, Dict'}
--spec string(string(), bdict()) -> {non_neg_integer(), bdict()}.
+-spec string(binary(), bdict()) -> {non_neg_integer(), bdict()}.
-string(Str, Dict) when is_list(Str) ->
+string(BinString, Dict) when is_binary(BinString) ->
#asm{strings=Strings,string_offset=NextOffset} = Dict,
- StrBin = list_to_binary(Str),
- case old_string(StrBin, Strings) of
+ case old_string(BinString, Strings) of
none ->
- NewDict = Dict#asm{strings = <<Strings/binary,StrBin/binary>>,
- string_offset=NextOffset+byte_size(StrBin)},
+ NewDict = Dict#asm{strings = <<Strings/binary,BinString/binary>>,
+ string_offset=NextOffset+byte_size(BinString)},
{NextOffset,NewDict};
Offset when is_integer(Offset) ->
{NextOffset-Offset,Dict}
diff --git a/lib/compiler/src/beam_disasm.erl b/lib/compiler/src/beam_disasm.erl
index 6cee9acae4..7d048716e4 100644
--- a/lib/compiler/src/beam_disasm.erl
+++ b/lib/compiler/src/beam_disasm.erl
@@ -373,6 +373,8 @@ disasm_instr(B, Bs, Atoms, Literals) ->
disasm_map_inst(get_map_elements, Arity, Bs, Atoms, Literals);
has_map_fields ->
disasm_map_inst(has_map_fields, Arity, Bs, Atoms, Literals);
+ put_tuple2 ->
+ disasm_put_tuple2(Bs, Atoms, Literals);
_ ->
try decode_n_args(Arity, Bs, Atoms, Literals) of
{Args, RestBs} ->
@@ -413,6 +415,14 @@ disasm_map_inst(Inst, Arity, Bs0, Atoms, Literals) ->
{List, RestBs} = decode_n_args(Len, Bs2, Atoms, Literals),
{{Inst, Args ++ [{Z,U,List}]}, RestBs}.
+disasm_put_tuple2(Bs, Atoms, Literals) ->
+ {X, Bs1} = decode_arg(Bs, Atoms, Literals),
+ {Z, Bs2} = decode_arg(Bs1, Atoms, Literals),
+ {U, Bs3} = decode_arg(Bs2, Atoms, Literals),
+ {u, Len} = U,
+ {List, RestBs} = decode_n_args(Len, Bs3, Atoms, Literals),
+ {{put_tuple2, [X,{Z,U,List}]}, RestBs}.
+
%%-----------------------------------------------------------------------
%% decode_arg([Byte]) -> {Arg, [Byte]}
%%
@@ -1095,6 +1105,23 @@ resolve_inst({get_hd,[Src,Dst]},_,_,_) ->
resolve_inst({get_tl,[Src,Dst]},_,_,_) ->
{get_tl,Src,Dst};
+%% OTP 22
+resolve_inst({bs_start_match3,[Fail,Bin,Live,Dst]},_,_,_) ->
+ {bs_start_match3,Fail,Bin,Live,Dst};
+resolve_inst({bs_get_tail,[Src,Dst,Live]},_,_,_) ->
+ {bs_get_tail,Src,Dst,Live};
+resolve_inst({bs_get_position,[Src,Dst,Live]},_,_,_) ->
+ {bs_get_position,Src,Dst,Live};
+resolve_inst({bs_set_position,[Src,Dst]},_,_,_) ->
+ {bs_set_position,Src,Dst};
+
+%%
+%% OTP 22.
+%%
+resolve_inst({put_tuple2,[Dst,{{z,1},{u,_},List0}]},_,_,_) ->
+ List = resolve_args(List0),
+ {put_tuple2,Dst,{list,List}};
+
%%
%% Catches instructions that are not yet handled.
%%
diff --git a/lib/compiler/src/beam_except.erl b/lib/compiler/src/beam_except.erl
index 05c0f4fbc7..28c89782c9 100644
--- a/lib/compiler/src/beam_except.erl
+++ b/lib/compiler/src/beam_except.erl
@@ -31,7 +31,7 @@
%%% erlang:error(function_clause, Args) => jump FuncInfoLabel
%%%
--import(lists, [reverse/1]).
+-import(lists, [reverse/1,reverse/2,seq/2,splitwith/2]).
-spec module(beam_utils:module_code(), [compile:option()]) ->
{'ok',beam_utils:module_code()}.
@@ -53,7 +53,7 @@ function({function,Name,Arity,CLabel,Is0}) ->
-record(st,
{lbl :: beam_asm:label(), %func_info label
loc :: [_], %location for func_info
- arity :: arity() %arity for function
+ arity :: arity() %arity for function
}).
function_1(Is0) ->
@@ -74,27 +74,33 @@ translate([I|Is], St, Acc) ->
translate([], _, Acc) ->
reverse(Acc).
-translate_1(Ar, I, Is, St, [{line,_}=Line|Acc1]=Acc0) ->
- case dig_out(Ar, Acc1) of
+translate_1(Ar, I, Is, #st{arity=Arity}=St, [{line,_}=Line|Acc1]=Acc0) ->
+ case dig_out(Ar, Arity, Acc1) of
no ->
translate(Is, St, [I|Acc0]);
- {yes,{function_clause,Arity},Acc2} ->
- case {Line,St} of
- {{line,Loc},#st{lbl=Fi,loc=Loc,arity=Arity}} ->
+ {yes,function_clause,Acc2} ->
+ case {Is,Line,St} of
+ {[return|_],{line,Loc},#st{lbl=Fi,loc=Loc}} ->
Instr = {jump,{f,Fi}},
translate(Is, St, [Instr|Acc2]);
- {_,_} ->
- %% This must be "error(function_clause, Args)" in
- %% the Erlang source code or a fun. Don't translate.
+ {_,_,_} ->
+ %% Not a call_only instruction, or not the same
+ %% location information as in in the line instruction
+ %% before the func_info instruction. Not safe
+ %% to translate to a jump.
translate(Is, St, [I|Acc0])
end;
{yes,Instr,Acc2} ->
translate(Is, St, [Instr,Line|Acc2])
end.
-dig_out(Ar, [{kill,_}|Is]) ->
- dig_out(Ar, Is);
-dig_out(1, [{block,Bl0}|Is]) ->
+dig_out(1, _Arity, Is) ->
+ dig_out(Is);
+dig_out(2, Arity, Is) ->
+ dig_out_fc(Arity, Is);
+dig_out(_, _, _) -> no.
+
+dig_out([{block,Bl0}|Is]) ->
case dig_out_block(reverse(Bl0)) of
no -> no;
{yes,What,[]} ->
@@ -102,25 +108,13 @@ dig_out(1, [{block,Bl0}|Is]) ->
{yes,What,Bl} ->
{yes,What,[{block,Bl}|Is]}
end;
-dig_out(2, [{block,Bl}|Is]) ->
- case dig_out_block_fc(Bl) of
- no -> no;
- {yes,What} -> {yes,What,Is}
- end;
-dig_out(_, _) -> no.
+dig_out(_) -> no.
dig_out_block([{set,[{x,0}],[{atom,if_clause}],move}]) ->
{yes,if_end,[]};
dig_out_block([{set,[{x,0}],[{literal,{Exc,Value}}],move}|Is]) ->
translate_exception(Exc, {literal,Value}, Is, 0);
-dig_out_block([{set,[{x,0}],[Tuple],move},
- {set,[],[Value],put},
- {set,[],[{atom,Exc}],put},
- {set,[Tuple],[],{put_tuple,2}}|Is]) ->
- translate_exception(Exc, Value, Is, 3);
-dig_out_block([{set,[],[Value],put},
- {set,[],[{atom,Exc}],put},
- {set,[{x,0}],[],{put_tuple,2}}|Is]) ->
+dig_out_block([{set,[{x,0}],[{atom,Exc},Value],put_tuple2}|Is]) ->
translate_exception(Exc, Value, Is, 3);
dig_out_block(_) -> no.
@@ -138,23 +132,113 @@ fix_block(Is, Words) ->
reverse(fix_block_1(Is, Words)).
fix_block_1([{set,[],[],{alloc,Live,{F1,F2,Needed0,F3}}}|Is], Words) ->
- Needed = Needed0 - Words,
- true = Needed >= 0, %Assertion.
- [{set,[],[],{alloc,Live,{F1,F2,Needed,F3}}}|Is];
+ case Needed0 - Words of
+ 0 ->
+ Is;
+ Needed ->
+ true = Needed >= 0, %Assertion.
+ [{set,[],[],{alloc,Live,{F1,F2,Needed,F3}}}|Is]
+ end;
fix_block_1([I|Is], Words) ->
[I|fix_block_1(Is, Words)].
-dig_out_block_fc([{set,[],[],{alloc,Live,_}}|Bl]) ->
- case dig_out_fc(Bl, Live-1, nil) of
- no ->
- no;
- yes ->
- {yes,{function_clause,Live}}
+
+dig_out_fc(Arity, Is0) ->
+ Regs0 = maps:from_list([{{x,X},{arg,X}} || X <- seq(0, Arity-1)]),
+ {Is,Acc0} = splitwith(fun({label,_}) -> false;
+ ({test,_,_,_}) -> false;
+ (_) -> true
+ end, Is0),
+ {Regs,Acc} = dig_out_fc_1(reverse(Is), Regs0, Acc0),
+ case Regs of
+ #{{x,0}:={atom,function_clause},{x,1}:=Args} ->
+ case moves_from_stack(Args, 0, []) of
+ {Moves,Arity} ->
+ {yes,function_clause,reverse(Moves, Acc)};
+ {_,_} ->
+ no
+ end;
+ #{} ->
+ no
+ end.
+
+dig_out_fc_1([{block,Bl}|Is], Regs0, Acc) ->
+ Regs = dig_out_fc_block(Bl, Regs0),
+ dig_out_fc_1(Is, Regs, Acc);
+dig_out_fc_1([{bs_set_position,_,_}=I|Is], Regs, Acc) ->
+ dig_out_fc_1(Is, Regs, [I|Acc]);
+dig_out_fc_1([{bs_get_tail,Src,Dst,Live0}|Is], Regs0, Acc) ->
+ Regs = prune_xregs(Live0, Regs0),
+ Live = dig_out_stack_live(Regs, Live0),
+ I = {bs_get_tail,Src,Dst,Live},
+ dig_out_fc_1(Is, Regs, [I|Acc]);
+dig_out_fc_1([_|_], _Regs, _Acc) ->
+ {#{},[]};
+dig_out_fc_1([], Regs, Acc) ->
+ {Regs,Acc}.
+
+dig_out_fc_block([{set,[],[],{alloc,Live,_}}|Is], Regs0) ->
+ Regs = prune_xregs(Live, Regs0),
+ dig_out_fc_block(Is, Regs);
+dig_out_fc_block([{set,[Dst],[Hd,Tl],put_list}|Is], Regs0) ->
+ Regs = Regs0#{Dst=>{cons,get_reg(Hd, Regs0),get_reg(Tl, Regs0)}},
+ dig_out_fc_block(Is, Regs);
+dig_out_fc_block([{set,[Dst],[Src],move}|Is], Regs0) ->
+ Regs = Regs0#{Dst=>get_reg(Src, Regs0)},
+ dig_out_fc_block(Is, Regs);
+dig_out_fc_block([{set,_,_,_}|_], _Regs) ->
+ %% Unknown instruction. Fail.
+ #{};
+dig_out_fc_block([], Regs) -> Regs.
+
+dig_out_stack_live(Regs, Default) ->
+ Reg = {x,2},
+ case Regs of
+ #{Reg:=List} ->
+ dig_out_stack_live_1(List, Default);
+ #{} ->
+ Default
+ end.
+
+dig_out_stack_live_1({cons,{arg,N},T}, Live) ->
+ dig_out_stack_live_1(T, max(N + 1, Live));
+dig_out_stack_live_1({cons,_,T}, Live) ->
+ dig_out_stack_live_1(T, Live);
+dig_out_stack_live_1(nil, Live) ->
+ Live;
+dig_out_stack_live_1(_, Live) -> Live.
+
+prune_xregs(Live, Regs) ->
+ maps:filter(fun({x,X}, _) -> X < Live end, Regs).
+
+moves_from_stack({cons,{arg,N},_}, I, _Acc) when N =/= I ->
+ %% Wrong argument. Give up.
+ {[],-1};
+moves_from_stack({cons,H,T}, I, Acc) ->
+ case H of
+ {arg,I} ->
+ moves_from_stack(T, I+1, Acc);
+ _ ->
+ moves_from_stack(T, I+1, [{move,H,{x,I}}|Acc])
end;
-dig_out_block_fc(_) -> no.
+moves_from_stack(nil, I, Acc) ->
+ {reverse(Acc),I};
+moves_from_stack({literal,[H|T]}, I, Acc) ->
+ Cons = {cons,tag_literal(H),tag_literal(T)},
+ moves_from_stack(Cons, I, Acc);
+moves_from_stack(_, _, _) ->
+ %% Not understood. Give up.
+ {[],-1}.
+
+
+get_reg(R, Regs) ->
+ case Regs of
+ #{R:=Val} -> Val;
+ #{} -> R
+ end.
-dig_out_fc([{set,[Dst],[{x,Reg},Dst0],put_list}|Is], Reg, Dst0) ->
- dig_out_fc(Is, Reg-1, Dst);
-dig_out_fc([{set,[{x,0}],[{atom,function_clause}],move}], -1, {x,1}) ->
- yes;
-dig_out_fc(_, _, _) -> no.
+tag_literal([]) -> nil;
+tag_literal(T) when is_atom(T) -> {atom,T};
+tag_literal(T) when is_float(T) -> {float,T};
+tag_literal(T) when is_integer(T) -> {integer,T};
+tag_literal(T) -> {literal,T}.
diff --git a/lib/compiler/src/beam_flatten.erl b/lib/compiler/src/beam_flatten.erl
index 20bd23a912..3e6bc1b1ed 100644
--- a/lib/compiler/src/beam_flatten.erl
+++ b/lib/compiler/src/beam_flatten.erl
@@ -32,8 +32,7 @@ module({Mod,Exp,Attr,Fs,Lc}, _Opt) ->
{ok,{Mod,Exp,Attr,[function(F) || F <- Fs],Lc}}.
function({function,Name,Arity,CLabel,Is0}) ->
- Is1 = block(Is0),
- Is = opt(Is1),
+ Is = block(Is0),
{function,Name,Arity,CLabel,Is}.
block(Is) ->
@@ -44,18 +43,11 @@ block([I|Is], Acc) -> block(Is, [I|Acc]);
block([], Acc) -> reverse(Acc).
norm_block([{set,[],[],{alloc,R,Alloc}}|Is], Acc0) ->
- case insert_alloc_in_bs_init(Acc0, Alloc) of
- impossible ->
- norm_block(Is, reverse(norm_allocate(Alloc, R), Acc0));
- Acc ->
- norm_block(Is, Acc)
- end;
-norm_block([{set,[D1],[S],get_hd},{set,[D2],[S],get_tl}|Is], Acc) ->
- I = {get_list,S,D1,D2},
- norm_block(Is, [I|Acc]);
-norm_block([I|Is], Acc) -> norm_block(Is, [norm(I)|Acc]);
+ norm_block(Is, reverse(norm_allocate(Alloc, R), Acc0));
+norm_block([I|Is], Acc) ->
+ norm_block(Is, [norm(I)|Acc]);
norm_block([], Acc) -> Acc.
-
+
norm({set,[D],As,{bif,N,F}}) -> {bif,N,F,As,D};
norm({set,[D],As,{alloc,R,{gc_bif,N,F}}}) -> {gc_bif,N,F,R,As,D};
norm({set,[D],[],init}) -> {init,D};
@@ -63,6 +55,7 @@ norm({set,[D],[S],move}) -> {move,S,D};
norm({set,[D],[S],fmove}) -> {fmove,S,D};
norm({set,[D],[S],fconv}) -> {fconv,S,D};
norm({set,[D],[S1,S2],put_list}) -> {put_list,S1,S2,D};
+norm({set,[D],Els,put_tuple2}) -> {put_tuple2,D,{list,Els}};
norm({set,[D],[],{put_tuple,A}}) -> {put_tuple,A,D};
norm({set,[],[S],put}) -> {put,S};
norm({set,[D],[S],{get_tuple_element,I}}) -> {get_tuple_element,S,I,D};
@@ -88,57 +81,3 @@ norm_allocate({nozero,Ns,0,Inits}, Regs) ->
[{allocate,Ns,Regs}|Inits];
norm_allocate({nozero,Ns,Nh,Inits}, Regs) ->
[{allocate_heap,Ns,Nh,Regs}|Inits].
-
-%% insert_alloc_in_bs_init(ReverseInstructionStream, AllocationInfo) ->
-%% impossible | ReverseInstructionStream'
-%% A bs_init/6 instruction should not be followed by a test heap instruction.
-%% Given the AllocationInfo from a test heap instruction, merge the
-%% allocation amounts into the previous bs_init/6 instruction (if any).
-%%
-insert_alloc_in_bs_init([{bs_put,_,_,_}=I|Is], Alloc) ->
- %% The instruction sequence ends with an bs_put/4 instruction.
- %% We'll need to search backwards for the bs_init/6 instruction.
- insert_alloc_1(Is, Alloc, [I]);
-insert_alloc_in_bs_init(_, _) -> impossible.
-
-insert_alloc_1([{bs_init=Op,Fail,Info0,Live,Ss,Dst}|Is],
- {_,nostack,Ws2,[]}, Acc) when is_integer(Live) ->
- %% The number of extra heap words is always in the second position
- %% in the Info tuple.
- Ws1 = element(2, Info0),
- Al = beam_utils:combine_heap_needs(Ws1, Ws2),
- Info = setelement(2, Info0, Al),
- I = {Op,Fail,Info,Live,Ss,Dst},
- reverse(Acc, [I|Is]);
-insert_alloc_1([{bs_put,_,_,_}=I|Is], Alloc, Acc) ->
- insert_alloc_1(Is, Alloc, [I|Acc]).
-
-%% opt(Is0) -> Is
-%% Simple peep-hole optimization to move a {move,Any,{x,0}} past
-%% any kill up to the next call instruction. (To give the loader
-%% an opportunity to combine the 'move' and the 'call' instructions.)
-%%
-opt(Is) ->
- opt_1(Is, []).
-
-opt_1([{move,_,{x,0}}=I|Is0], Acc0) ->
- case move_past_kill(Is0, I, Acc0) of
- impossible -> opt_1(Is0, [I|Acc0]);
- {Is,Acc} -> opt_1(Is, Acc)
- end;
-opt_1([I|Is], Acc) ->
- opt_1(Is, [I|Acc]);
-opt_1([], Acc) -> reverse(Acc).
-
-move_past_kill([{kill,Src}|_], {move,Src,_}, _) ->
- impossible;
-move_past_kill([{kill,_}=I|Is], Move, Acc) ->
- move_past_kill(Is, Move, [I|Acc]);
-move_past_kill([{trim,N,_}=I|Is], {move,Src,Dst}=Move, Acc) ->
- case Src of
- {y,Y} when Y < N-> impossible;
- {y,Y} -> {Is,[{move,{y,Y-N},Dst},I|Acc]};
- _ -> {Is,[Move,I|Acc]}
- end;
-move_past_kill(Is, Move, Acc) ->
- {Is,[Move|Acc]}.
diff --git a/lib/compiler/src/beam_jump.erl b/lib/compiler/src/beam_jump.erl
index 22974da398..74f80ca70e 100644
--- a/lib/compiler/src/beam_jump.erl
+++ b/lib/compiler/src/beam_jump.erl
@@ -22,7 +22,7 @@
-module(beam_jump).
-export([module/2,
- is_unreachable_after/1,is_exit_instruction/1,
+ is_exit_instruction/1,
remove_unused_labels/1]).
%%% The following optimisations are done:
@@ -101,6 +101,10 @@
%%% always keep the label. (beam_clean will remove any unused
%%% labels.)
%%%
+%%% (7) Replace a jump to a return instruction with a return instruction.
+%%% Similarly, replace a jump to deallocate + return with those
+%%% instructions.
+%%%
%%% Note: This modules depends on (almost) all branches and jumps only
%%% going forward, so that we can remove instructions (including definition
%%% of labels) after any label that has not been referenced by the code
@@ -128,27 +132,136 @@
%%% on the program state.
%%%
--import(lists, [dropwhile/2,reverse/1,reverse/2,foldl/3]).
+-import(lists, [foldl/3,mapfoldl/3,reverse/1,reverse/2]).
-type instruction() :: beam_utils:instruction().
-spec module(beam_utils:module_code(), [compile:option()]) ->
{'ok',beam_utils:module_code()}.
-module({Mod,Exp,Attr,Fs0,Lc}, _Opt) ->
- Fs = [function(F) || F <- Fs0],
+module({Mod,Exp,Attr,Fs0,Lc0}, _Opt) ->
+ {Fs,Lc} = mapfoldl(fun function/2, Lc0, Fs0),
{ok,{Mod,Exp,Attr,Fs,Lc}}.
%% function(Function) -> Function'
%% Optimize jumps and branches.
%%
%% NOTE: This function assumes that there are no labels inside blocks.
-function({function,Name,Arity,CLabel,Asm0}) ->
- Asm1 = share(Asm0),
- Asm2 = move(Asm1),
- Asm3 = opt(Asm2, CLabel),
- Asm = remove_unused_labels(Asm3),
- {function,Name,Arity,CLabel,Asm}.
+function({function,Name,Arity,CLabel,Asm0}, Lc0) ->
+ try
+ Asm1 = eliminate_moves(Asm0),
+ {Asm2,Lc} = insert_labels(Asm1, Lc0, []),
+ Asm3 = share(Asm2),
+ Asm4 = move(Asm3),
+ Asm5 = opt(Asm4, CLabel),
+ Asm6 = unshare(Asm5),
+ Asm = remove_unused_labels(Asm6),
+ {{function,Name,Arity,CLabel,Asm},Lc}
+ catch
+ Class:Error:Stack ->
+ io:fwrite("Function: ~w/~w\n", [Name,Arity]),
+ erlang:raise(Class, Error, Stack)
+ end.
+
+%%%
+%%% Scan instructions in execution order and remove redundant 'move'
+%%% instructions. 'move' instructions are redundant if we know that
+%%% the register already contains the value being assigned, as in the
+%%% following code:
+%%%
+%%% select_val Register FailLabel [... Literal => L1...]
+%%% .
+%%% .
+%%% .
+%%% L1: move Literal Register
+%%%
+
+eliminate_moves(Is) ->
+ eliminate_moves(Is, #{}, []).
+
+eliminate_moves([{select,select_val,Reg,{f,Fail},List}=I|Is], D0, Acc) ->
+ D1 = add_unsafe_label(Fail, D0),
+ D = update_value_dict(List, Reg, D1),
+ eliminate_moves(Is, D, [I|Acc]);
+eliminate_moves([{test,is_eq_exact,_,[Reg,Val]}=I,
+ {block,BlkIs0}|Is], D0, Acc) ->
+ D = update_unsafe_labels(I, D0),
+ RegVal = {Reg,Val},
+ BlkIs = eliminate_moves_blk(BlkIs0, RegVal),
+ eliminate_moves([{block,BlkIs}|Is], D, [I|Acc]);
+eliminate_moves([{label,Lbl},{block,BlkIs0}=Blk|Is], D, Acc0) ->
+ Acc = [{label,Lbl}|Acc0],
+ case {no_fallthrough(Acc0),D} of
+ {true,#{Lbl:={_,_}=RegVal}} ->
+ BlkIs = eliminate_moves_blk(BlkIs0, RegVal),
+ eliminate_moves([{block,BlkIs}|Is], D, Acc);
+ {_,_} ->
+ eliminate_moves([Blk|Is], D, Acc)
+ end;
+eliminate_moves([{block,[]}|Is], D, Acc) ->
+ %% Empty blocks can prevent further jump optimizations.
+ eliminate_moves(Is, D, Acc);
+eliminate_moves([I|Is], D0, Acc) ->
+ D = update_unsafe_labels(I, D0),
+ eliminate_moves(Is, D, [I|Acc]);
+eliminate_moves([], _, Acc) -> reverse(Acc).
+
+eliminate_moves_blk([{set,[Dst],[_],move}|_]=Is, {_,Dst}) ->
+ Is;
+eliminate_moves_blk([{set,[Dst],[Lit],move}|Is], {Dst,Lit}) ->
+ %% Remove redundant 'move' instruction.
+ Is;
+eliminate_moves_blk([{set,[Dst],[_],move}|_]=Is, {Dst,_}) ->
+ Is;
+eliminate_moves_blk([{set,[_],[_],move}=I|Is], {_,_}=RegVal) ->
+ [I|eliminate_moves_blk(Is, RegVal)];
+eliminate_moves_blk(Is, _) -> Is.
+
+no_fallthrough([I|_]) ->
+ is_unreachable_after(I).
+
+update_value_dict([Lit,{f,Lbl}|T], Reg, D0) ->
+ D = case D0 of
+ #{Lbl:=unsafe} -> D0;
+ #{Lbl:={Reg,Lit}} -> D0;
+ #{Lbl:=_} -> D0#{Lbl:=unsafe};
+ #{} -> D0#{Lbl=>{Reg,Lit}}
+ end,
+ update_value_dict(T, Reg, D);
+update_value_dict([], _, D) -> D.
+
+add_unsafe_label(L, D) ->
+ D#{L=>unsafe}.
+
+update_unsafe_labels(I, D) ->
+ Ls = instr_labels(I),
+ update_unsafe_labels_1(Ls, D).
+
+update_unsafe_labels_1([L|Ls], D) ->
+ update_unsafe_labels_1(Ls, D#{L=>unsafe});
+update_unsafe_labels_1([], D) -> D.
+
+%%%
+%%% It seems to be useful to insert extra labels after certain
+%%% test instructions. This used to be done by beam_dead.
+%%%
+
+insert_labels([{test,Op,_,_}=I|Is], Lc, Acc) ->
+ Useful = case Op of
+ is_lt -> true;
+ is_ge -> true;
+ is_eq_exact -> true;
+ is_ne_exact -> true;
+ _ -> false
+ end,
+ case Useful of
+ false -> insert_labels(Is, Lc, [I|Acc]);
+ true -> insert_labels(Is, Lc+1, [{label,Lc},I|Acc])
+ end;
+insert_labels([I|Is], Lc, Acc) ->
+ insert_labels(Is, Lc, [I|Acc]);
+insert_labels([], Lc, Acc) ->
+ {reverse(Acc),Lc}.
%%%
%%% (1) We try to share the code for identical code segments by replacing all
@@ -156,41 +269,51 @@ function({function,Name,Arity,CLabel,Asm0}) ->
%%%
share(Is0) ->
- %% We will get more sharing if we never fall through to a label.
- Is = eliminate_fallthroughs(Is0, []),
- share_1(Is, #{}, [], []).
+ Is1 = eliminate_fallthroughs(Is0, []),
+ Is2 = find_fixpoint(fun(Is) ->
+ share_1(Is, #{}, #{}, [], [])
+ end, Is1),
+ reverse(Is2).
-share_1([{label,L}=Lbl|Is], Dict0, [_|_]=Seq, Acc) ->
+share_1([{label,L}=Lbl|Is], Dict0, Lbls0, [_|_]=Seq, Acc) ->
case maps:find(Seq, Dict0) of
- error ->
- Dict = maps:put(Seq, L, Dict0),
- share_1(Is, Dict, [], [Lbl|Seq ++ Acc]);
- {ok,Label} ->
- share_1(Is, Dict0, [], [Lbl,{jump,{f,Label}}|Acc])
+ error ->
+ Dict = maps:put(Seq, L, Dict0),
+ share_1(Is, Dict, Lbls0, [], [[Lbl|Seq]|Acc]);
+ {ok,Label} ->
+ Lbls = maps:put(L, Label, Lbls0),
+ share_1(Is, Dict0, Lbls, [], [[Lbl,{jump,{f,Label}}]|Acc])
end;
-share_1([{func_info,_,_,_}=I|Is], _, [], Acc) ->
- reverse(Is, [I|Acc]);
-share_1([{'catch',_,_}=I|Is], Dict0, Seq, Acc) ->
- Dict = clean_non_sharable(Dict0),
- share_1(Is, Dict, [I|Seq], Acc);
-share_1([{'try',_,_}=I|Is], Dict0, Seq, Acc) ->
- Dict = clean_non_sharable(Dict0),
- share_1(Is, Dict, [I|Seq], Acc);
-share_1([{try_case,_}=I|Is], Dict0, Seq, Acc) ->
- Dict = clean_non_sharable(Dict0),
- share_1(Is, Dict, [I|Seq], Acc);
-share_1([{catch_end,_}=I|Is], Dict0, Seq, Acc) ->
- Dict = clean_non_sharable(Dict0),
- share_1(Is, Dict, [I|Seq], Acc);
-share_1([I|Is], Dict, Seq, Acc) ->
+share_1([{func_info,_,_,_}|_]=Is0, _, Lbls, [], Acc0) when Lbls =/= #{} ->
+ lists:foldl(fun(Is, Acc) ->
+ beam_utils:replace_labels(Is, Acc, Lbls, fun(Old) -> Old end)
+ end, Is0, Acc0);
+share_1([{func_info,_,_,_}|_]=Is, _, Lbls, [], Acc) when Lbls =:= #{} ->
+ lists:foldl(fun lists:reverse/2, Is, Acc);
+share_1([{'catch',_,_}=I|Is], Dict0, Lbls0, Seq, Acc) ->
+ {Dict,Lbls} = clean_non_sharable(Dict0, Lbls0),
+ share_1(Is, Dict, Lbls, [I|Seq], Acc);
+share_1([{'try',_,_}=I|Is], Dict0, Lbls0, Seq, Acc) ->
+ {Dict,Lbls} = clean_non_sharable(Dict0, Lbls0),
+ share_1(Is, Dict, Lbls, [I|Seq], Acc);
+share_1([{try_case,_}=I|Is], Dict0, Lbls0, Seq, Acc) ->
+ {Dict,Lbls} = clean_non_sharable(Dict0, Lbls0),
+ share_1(Is, Dict, Lbls, [I|Seq], Acc);
+share_1([{catch_end,_}=I|Is], Dict0, Lbls0, Seq, Acc) ->
+ {Dict,Lbls} = clean_non_sharable(Dict0, Lbls0),
+ share_1(Is, Dict, Lbls, [I|Seq], Acc);
+share_1([{jump,{f,To}}=I,{label,L}=Lbl|Is], Dict0, Lbls0, _Seq, Acc) ->
+ Lbls = maps:put(L, To, Lbls0),
+ share_1(Is, Dict0, Lbls, [], [[Lbl,I]|Acc]);
+share_1([I|Is], Dict, Lbls, Seq, Acc) ->
case is_unreachable_after(I) of
false ->
- share_1(Is, Dict, [I|Seq], Acc);
+ share_1(Is, Dict, Lbls, [I|Seq], Acc);
true ->
- share_1(Is, Dict, [I], Acc)
+ share_1(Is, Dict, Lbls, [I], Acc)
end.
-clean_non_sharable(Dict) ->
+clean_non_sharable(Dict0, Lbls0) ->
%% We are passing in or out of a 'catch' or 'try' block. Remove
%% sequences that should not be shared over the boundaries of the
%% block. Since the end of the sequence must match, the only
@@ -198,7 +321,17 @@ clean_non_sharable(Dict) ->
%% the 'catch'/'try' block is a sequence that ends with an
%% instruction that causes an exception. Any sequence that causes
%% an exception must contain a line/1 instruction.
- maps:filter(fun(K, _V) -> sharable_with_try(K) end, Dict).
+ Dict1 = maps:to_list(Dict0),
+ Lbls1 = maps:to_list(Lbls0),
+ {Dict2,Lbls2} = foldl(fun({K, V}, {Dict,Lbls}) ->
+ case sharable_with_try(K) of
+ true ->
+ {[{K,V}|Dict],lists:keydelete(V, 2, Lbls)};
+ false ->
+ {Dict,Lbls}
+ end
+ end, {[],Lbls1}, Dict1),
+ {maps:from_list(Dict2),maps:from_list(Lbls2)}.
sharable_with_try([{line,_}|_]) ->
%% This sequence may cause an exception and may potentially
@@ -251,8 +384,6 @@ extract_seq([{line,_}=Line|Is], Acc) ->
extract_seq(Is, [Line|Acc]);
extract_seq([{block,_}=Bl|Is], Acc) ->
extract_seq_1(Is, [Bl|Acc]);
-extract_seq([{bs_context_to_binary,_}=I|Is], Acc) ->
- extract_seq_1(Is, [I|Acc]);
extract_seq([{label,_}|_]=Is, Acc) ->
extract_seq_1(Is, Acc);
extract_seq(_, _) -> no.
@@ -276,14 +407,13 @@ extract_seq_1(_, _) -> no.
{
entry :: beam_asm:label(), %Entry label (must not be moved).
replace :: #{beam_asm:label() := beam_asm:label()}, %Labels to replace.
- labels :: cerl_sets:set(), %Set of referenced labels.
- index :: beam_utils:code_index() | {lazy,[beam_utils:instruction()]} %Index built lazily only if needed
+ labels :: cerl_sets:set() %Set of referenced labels.
}).
opt(Is0, CLabel) ->
find_fixpoint(fun(Is) ->
Lbls = initial_labels(Is),
- St = #st{entry=CLabel,replace=#{},labels=Lbls,index={lazy,Is}},
+ St = #st{entry=CLabel,replace=#{},labels=Lbls},
opt(Is, [], St)
end, Is0).
@@ -293,7 +423,7 @@ find_fixpoint(OptFun, Is0) ->
Is -> find_fixpoint(OptFun, Is)
end.
-opt([{test,_,{f,L}=Lbl,_}=I|[{jump,{f,L}}|_]=Is], Acc0, St0) ->
+opt([{test,_,{f,L}=Lbl,_}=I|[{jump,{f,L}}|_]=Is], Acc, St) ->
%% We have
%% Test Label Ops
%% jump Label
@@ -302,23 +432,10 @@ opt([{test,_,{f,L}=Lbl,_}=I|[{jump,{f,L}}|_]=Is], Acc0, St0) ->
case beam_utils:is_pure_test(I) of
false ->
%% Test is not pure; we must keep it.
- opt(Is, [I|Acc0], label_used(Lbl, St0));
+ opt(Is, [I|Acc], label_used(Lbl, St));
true ->
%% The test is pure and its failure label is the same
%% as in the jump that follows -- thus it is not needed.
- %% Check if any of the previous instructions could also be eliminated.
- {Acc,St} = opt_useless_loads(Acc0, L, St0),
- opt(Is, Acc, St)
- end;
-opt([{test,_,{f,L}=Lbl,_}=I|[{label,L}|_]=Is], Acc0, St0) ->
- %% Similar to the above, except we have a fall-through rather than jump
- %% Test Label Ops
- %% label Label
- case beam_utils:is_pure_test(I) of
- false ->
- opt(Is, [I|Acc0], label_used(Lbl, St0));
- true ->
- {Acc,St} = opt_useless_loads(Acc0, L, St0),
opt(Is, Acc, St)
end;
opt([{test,Test0,{f,L}=Lbl,Ops}=I|[{jump,To}|Is]=Is0], Acc, St) ->
@@ -385,51 +502,6 @@ normalize_replace([{From,To0}|Rest], Replace, Acc) ->
normalize_replace([], _Replace, Acc) ->
maps:from_list(Acc).
-%% After eliminating a test, it might happen, that a register was only used
-%% in this test. Let's check if that was the case and if it was so, we can
-%% eliminate the load into the register completely.
-opt_useless_loads([{block,_}|_]=Is, L, #st{index={lazy,FIs}}=St) ->
- opt_useless_loads(Is, L, St#st{index=beam_utils:index_labels(FIs)});
-opt_useless_loads([{block,Block0}|Is], L, #st{index=Index}=St) ->
- case opt_useless_block_loads(Block0, L, Index) of
- [] ->
- opt_useless_loads(Is, L, St);
- [_|_]=Block ->
- {[{block,Block}|Is],St}
- end;
-%% After eliminating the test and useless blocks, it might happen,
-%% that the previous test could also be eliminated.
-%% It might be that the label was already marked as used, even if ultimately,
-%% it never will be - we can't do much about it at that point, though
-opt_useless_loads([{test,_,{f,L},_}=I|Is], L, St) ->
- case beam_utils:is_pure_test(I) of
- false ->
- {[I|Is],St};
- true ->
- opt_useless_loads(Is, L, St)
- end;
-opt_useless_loads(Is, _L, St) ->
- {Is,St}.
-
-opt_useless_block_loads([{set,[Dst],_,_}=I|Is0], L, Index) ->
- BlockJump = [{block,Is0},{jump,{f,L}}],
- case beam_utils:is_killed(Dst, BlockJump, Index) of
- true ->
- %% The register is killed and not used, we can remove the load.
- %% Remove any `put` instructions in case we just
- %% removed a `put_tuple` instruction.
- Is = dropwhile(fun({set,_,_,put}) -> true;
- (_) -> false
- end, Is0),
- opt_useless_block_loads(Is, L, Index);
- false ->
- [I|opt_useless_block_loads(Is0, L, Index)]
- end;
-opt_useless_block_loads([I|Is], L, Index) ->
- [I|opt_useless_block_loads(Is, L, Index)];
-opt_useless_block_loads([], _L, _Index) ->
- [].
-
collect_labels(Is, Label, #st{entry=Entry,replace=Replace} = St) ->
collect_labels_1(Is, Label, Entry, Replace, St).
@@ -556,52 +628,109 @@ drop_upto_label([{label,_}|_]=Is) -> Is;
drop_upto_label([_|Is]) -> drop_upto_label(Is);
drop_upto_label([]) -> [].
-%% ulbl(Instruction, UsedGbSet) -> UsedGbSet'
-%% Update the gb_set UsedGbSet with any function-local labels
+%% unshare([Instruction]) -> [Instruction].
+%% Replace a jump to a return sequence (a `return` instruction
+%% optionally preced by a `deallocate` instruction) with the return
+%% sequence. This always saves execution time and may also save code
+%% space (depending on the architecture). Eliminating `jump`
+%% instructions also gives beam_trim more opportunities to trim the
+%% stack.
+
+unshare(Is) ->
+ Short = unshare_collect_short(Is, #{}),
+ unshare_short(Is, Short).
+
+unshare_collect_short([{label,L},return|Is], Map) ->
+ unshare_collect_short(Is, Map#{L=>[return]});
+unshare_collect_short([{label,L},{deallocate,_}=D,return|Is], Map) ->
+ %% `deallocate` and `return` are combined into one instruction by
+ %% the loader.
+ unshare_collect_short(Is, Map#{L=>[D,return]});
+unshare_collect_short([_|Is], Map) ->
+ unshare_collect_short(Is, Map);
+unshare_collect_short([], Map) -> Map.
+
+unshare_short([{jump,{f,F}}=I|Is], Map) ->
+ case Map of
+ #{F:=Seq} ->
+ Seq ++ unshare_short(Is, Map);
+ #{} ->
+ [I|unshare_short(Is, Map)]
+ end;
+unshare_short([I|Is], Map) ->
+ [I|unshare_short(Is, Map)];
+unshare_short([], _Map) -> [].
+
+%% ulbl(Instruction, UsedCerlSet) -> UsedCerlSet'
+%% Update the cerl_set UsedCerlSet with any function-local labels
%% (i.e. not with labels in call instructions) referenced by
%% the instruction Instruction.
%%
%% NOTE: This function does NOT look for labels inside blocks.
-ulbl({test,_,Fail,_}, Used) ->
- mark_used(Fail, Used);
-ulbl({test,_,Fail,_,_,_}, Used) ->
- mark_used(Fail, Used);
-ulbl({select,_,_,Fail,Vls}, Used) ->
- mark_used_list(Vls, mark_used(Fail, Used));
-ulbl({'try',_,Lbl}, Used) ->
- mark_used(Lbl, Used);
-ulbl({'catch',_,Lbl}, Used) ->
- mark_used(Lbl, Used);
-ulbl({jump,Lbl}, Used) ->
- mark_used(Lbl, Used);
-ulbl({loop_rec,Lbl,_}, Used) ->
- mark_used(Lbl, Used);
-ulbl({loop_rec_end,Lbl}, Used) ->
- mark_used(Lbl, Used);
-ulbl({wait,Lbl}, Used) ->
- mark_used(Lbl, Used);
-ulbl({wait_timeout,Lbl,_To}, Used) ->
- mark_used(Lbl, Used);
-ulbl({bif,_Name,Lbl,_As,_R}, Used) ->
- mark_used(Lbl, Used);
-ulbl({gc_bif,_Name,Lbl,_Live,_As,_R}, Used) ->
- mark_used(Lbl, Used);
-ulbl({bs_init,Lbl,_,_,_,_}, Used) ->
- mark_used(Lbl, Used);
-ulbl({bs_put,Lbl,_,_}, Used) ->
- mark_used(Lbl, Used);
-ulbl({put_map,Lbl,_Op,_Src,_Dst,_Live,_List}, Used) ->
- mark_used(Lbl, Used);
-ulbl({get_map_elements,Lbl,_Src,_List}, Used) ->
- mark_used(Lbl, Used);
-ulbl(_, Used) -> Used.
-
-mark_used({f,0}, Used) -> Used;
-mark_used({f,L}, Used) -> cerl_sets:add_element(L, Used).
-
-mark_used_list([{f,L}|T], Used) ->
- mark_used_list(T, cerl_sets:add_element(L, Used));
-mark_used_list([_|T], Used) ->
- mark_used_list(T, Used);
-mark_used_list([], Used) -> Used.
+ulbl(I, Used) ->
+ case instr_labels(I) of
+ [] ->
+ Used;
+ [Lbl] ->
+ cerl_sets:add_element(Lbl, Used);
+ [_|_]=L ->
+ ulbl_list(L, Used)
+ end.
+
+ulbl_list([L|Ls], Used) ->
+ ulbl_list(Ls, cerl_sets:add_element(L, Used));
+ulbl_list([], Used) -> Used.
+
+-spec instr_labels(Instruction) -> Labels when
+ Instruction :: instruction(),
+ Labels :: [beam_asm:label()].
+
+instr_labels({test,_,Fail,_}) ->
+ do_instr_labels(Fail);
+instr_labels({test,_,Fail,_,_,_}) ->
+ do_instr_labels(Fail);
+instr_labels({select,_,_,Fail,Vls}) ->
+ do_instr_labels_list(Vls, do_instr_labels(Fail));
+instr_labels({'try',_,Lbl}) ->
+ do_instr_labels(Lbl);
+instr_labels({'catch',_,Lbl}) ->
+ do_instr_labels(Lbl);
+instr_labels({jump,Lbl}) ->
+ do_instr_labels(Lbl);
+instr_labels({loop_rec,Lbl,_}) ->
+ do_instr_labels(Lbl);
+instr_labels({loop_rec_end,Lbl}) ->
+ do_instr_labels(Lbl);
+instr_labels({wait,Lbl}) ->
+ do_instr_labels(Lbl);
+instr_labels({wait_timeout,Lbl,_To}) ->
+ do_instr_labels(Lbl);
+instr_labels({bif,_Name,Lbl,_As,_R}) ->
+ do_instr_labels(Lbl);
+instr_labels({gc_bif,_Name,Lbl,_Live,_As,_R}) ->
+ do_instr_labels(Lbl);
+instr_labels({bs_init,Lbl,_,_,_,_}) ->
+ do_instr_labels(Lbl);
+instr_labels({bs_put,Lbl,_,_}) ->
+ do_instr_labels(Lbl);
+instr_labels({put_map,Lbl,_Op,_Src,_Dst,_Live,_List}) ->
+ do_instr_labels(Lbl);
+instr_labels({get_map_elements,Lbl,_Src,_List}) ->
+ do_instr_labels(Lbl);
+instr_labels({recv_mark,Lbl}) ->
+ do_instr_labels(Lbl);
+instr_labels({recv_set,Lbl}) ->
+ do_instr_labels(Lbl);
+instr_labels({fcheckerror,Lbl}) ->
+ do_instr_labels(Lbl);
+instr_labels(_) -> [].
+
+do_instr_labels({f,0}) -> [];
+do_instr_labels({f,F}) -> [F].
+
+do_instr_labels_list([{f,F}|T], Acc) ->
+ do_instr_labels_list(T, [F|Acc]);
+do_instr_labels_list([_|T], Acc) ->
+ do_instr_labels_list(T, Acc);
+do_instr_labels_list([], Acc) -> Acc.
diff --git a/lib/compiler/src/beam_kernel_to_ssa.erl b/lib/compiler/src/beam_kernel_to_ssa.erl
new file mode 100644
index 0000000000..df95749fb3
--- /dev/null
+++ b/lib/compiler/src/beam_kernel_to_ssa.erl
@@ -0,0 +1,1312 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018. All 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: Convert the Kernel Erlang format to the SSA format.
+
+-module(beam_kernel_to_ssa).
+
+%% The main interface.
+-export([module/2]).
+
+-import(lists, [append/1,duplicate/2,flatmap/2,foldl/3,
+ keysort/2,mapfoldl/3,map/2,member/2,
+ reverse/1,reverse/2,sort/1]).
+
+-include("v3_kernel.hrl").
+-include("beam_ssa.hrl").
+
+-type label() :: beam_ssa:label().
+
+%% Main codegen structure.
+-record(cg, {lcount=1 :: label(), %Label counter
+ bfail=1 :: label(),
+ catch_label=none :: 'none' | label(),
+ vars=#{} :: map(), %Defined variables.
+ break=0 :: label(), %Break label
+ recv=0 :: label(), %Receive label
+ ultimate_failure=0 :: label() %Label for ultimate match failure.
+ }).
+
+%% Internal records.
+-record(cg_break, {args :: [beam_ssa:value()],
+ phi :: label()
+ }).
+-record(cg_phi, {vars :: [beam_ssa:b_var()]
+ }).
+-record(cg_unreachable, {}).
+
+-spec module(#k_mdef{}, [compile:option()]) -> {'ok',#b_module{}}.
+
+module(#k_mdef{name=Mod,exports=Es,attributes=Attr,body=Forms}, _Opts) ->
+ Body = functions(Forms, Mod),
+ Module = #b_module{name=Mod,exports=Es,attributes=Attr,body=Body},
+ {ok,Module}.
+
+functions(Forms, Mod) ->
+ [function(F, Mod) || F <- Forms].
+
+function(#k_fdef{anno=Anno0,func=Name,arity=Arity,
+ vars=As0,body=Kb}, Mod) ->
+ try
+ #k_match{} = Kb, %Assertion.
+
+ %% Generate the SSA form immediate format.
+ St0 = #cg{},
+ {As,St1} = new_ssa_vars(As0, St0),
+ {Asm,St} = cg_fun(Kb, St1),
+ Anno1 = line_anno(Anno0),
+ Anno = Anno1#{func_info=>{Mod,Name,Arity}},
+ #b_function{anno=Anno,args=As,bs=Asm,cnt=St#cg.lcount}
+ catch
+ Class:Error:Stack ->
+ io:fwrite("Function: ~w/~w\n", [Name,Arity]),
+ erlang:raise(Class, Error, Stack)
+ end.
+
+%% cg_fun([Lkexpr], [HeadVar], State) -> {[Ainstr],State}
+
+cg_fun(Ke, St0) ->
+ {UltimateFail,FailIs,St1} = make_failure(badarg, St0),
+ St2 = St1#cg{bfail=UltimateFail,ultimate_failure=UltimateFail},
+ {B,St} = cg(Ke, St2),
+ Asm = [{label,0}|B++FailIs],
+ finalize(Asm, St).
+
+make_failure(Reason, St0) ->
+ {Lbl,St1} = new_label(St0),
+ {Dst,St} = new_ssa_var('@ssa_ret', St1),
+ Is = [{label,Lbl},
+ #b_set{op=call,dst=Dst,
+ args=[#b_remote{mod=#b_literal{val=erlang},
+ name=#b_literal{val=error},
+ arity=1},
+ #b_literal{val=Reason}]},
+ #b_ret{arg=Dst}],
+ {Lbl,Is,St}.
+
+%% cg(Lkexpr, State) -> {[Ainstr],State}.
+%% Generate code for a kexpr.
+
+cg(#k_match{body=M,ret=Rs}, St) ->
+ do_match_cg(M, Rs, St);
+cg(#k_guard_match{body=M,ret=Rs}, St) ->
+ do_match_cg(M, Rs, St);
+cg(#k_seq{arg=Arg,body=Body}, St0) ->
+ {ArgIs,St1} = cg(Arg, St0),
+ {BodyIs,St} = cg(Body, St1),
+ {ArgIs++BodyIs,St};
+cg(#k_call{anno=Le,op=Func,args=As,ret=Rs}, St) ->
+ call_cg(Func, As, Rs, Le, St);
+cg(#k_enter{anno=Le,op=Func,args=As}, St) ->
+ enter_cg(Func, As, Le, St);
+cg(#k_bif{anno=Le}=Bif, St) ->
+ bif_cg(Bif, Le, St);
+cg(#k_try{arg=Ta,vars=Vs,body=Tb,evars=Evs,handler=Th,ret=Rs}, St) ->
+ try_cg(Ta, Vs, Tb, Evs, Th, Rs, St);
+cg(#k_try_enter{arg=Ta,vars=Vs,body=Tb,evars=Evs,handler=Th}, St) ->
+ try_enter_cg(Ta, Vs, Tb, Evs, Th, St);
+cg(#k_catch{body=Cb,ret=[R]}, St) ->
+ do_catch_cg(Cb, R, St);
+cg(#k_receive{anno=Le,timeout=Te,var=Rvar,body=Rm,action=Tes,ret=Rs}, St) ->
+ recv_loop_cg(Te, Rvar, Rm, Tes, Rs, Le, St);
+cg(#k_receive_next{}, #cg{recv=Recv}=St) ->
+ Is = [#b_set{op=recv_next},make_uncond_branch(Recv)],
+ {Is,St};
+cg(#k_receive_accept{}, St) ->
+ Remove = #b_set{op=remove_message},
+ {[Remove],St};
+cg(#k_put{anno=Le,arg=Con,ret=Var}, St) ->
+ put_cg(Var, Con, Le, St);
+cg(#k_return{args=[Ret0]}, St) ->
+ Ret = ssa_arg(Ret0, St),
+ {[#b_ret{arg=Ret}],St};
+cg(#k_break{args=Bs}, #cg{break=Br}=St) ->
+ Args = ssa_args(Bs, St),
+ {[#cg_break{args=Args,phi=Br}],St};
+cg(#k_guard_break{args=Bs}, St) ->
+ cg(#k_break{args=Bs}, St).
+
+%% match_cg(Matc, [Ret], State) -> {[Ainstr],State}.
+%% Generate code for a match.
+
+do_match_cg(M, Rs, St0) ->
+ {B,St1} = new_label(St0),
+ {Mis,St2} = match_cg(M, St1#cg.bfail, St1#cg{break=B}),
+ {BreakVars,St} = new_ssa_vars(Rs, St2),
+ {Mis ++ [{label,B},#cg_phi{vars=BreakVars}],
+ St#cg{bfail=St0#cg.bfail,break=St1#cg.break}}.
+
+%% match_cg(Match, Fail, State) -> {[Ainstr],State}.
+%% Generate code for a match tree.
+
+match_cg(#k_alt{first=F,then=S}, Fail, St0) ->
+ {Tf,St1} = new_label(St0),
+ {Fis,St2} = match_cg(F, Tf, St1),
+ {Sis,St3} = match_cg(S, Fail, St2),
+ {Fis ++ [{label,Tf}] ++ Sis,St3};
+match_cg(#k_select{var=#k_var{}=V,types=Scs}, Fail, St) ->
+ match_fmf(fun (S, F, Sta) ->
+ select_cg(S, V, F, Fail, Sta)
+ end, Fail, St, Scs);
+match_cg(#k_guard{clauses=Gcs}, Fail, St) ->
+ match_fmf(fun (G, F, Sta) ->
+ guard_clause_cg(G, F, Sta)
+ end, Fail, St, Gcs);
+match_cg(Ke, _Fail, St0) ->
+ cg(Ke, St0).
+
+%% select_cg(Sclause, V, TypeFail, ValueFail, State) -> {Is,State}.
+%% Selecting type and value needs two failure labels, TypeFail is the
+%% label to jump to of the next type test when this type fails, and
+%% ValueFail is the label when this type is correct but the value is
+%% wrong. These are different as in the second case there is no need
+%% to try the next type, it will always fail.
+
+select_cg(#k_type_clause{type=k_binary,values=[S]}, Var, Tf, Vf, St) ->
+ select_binary(S, Var, Tf, Vf, St);
+select_cg(#k_type_clause{type=k_bin_seg,values=Vs}, Var, Tf, _Vf, St) ->
+ select_bin_segs(Vs, Var, Tf, St);
+select_cg(#k_type_clause{type=k_bin_int,values=Vs}, Var, Tf, _Vf, St) ->
+ select_bin_segs(Vs, Var, Tf, St);
+select_cg(#k_type_clause{type=k_bin_end,values=[S]}, Var, Tf, _Vf, St) ->
+ select_bin_end(S, Var, Tf, St);
+select_cg(#k_type_clause{type=k_map,values=Vs}, Var, Tf, Vf, St) ->
+ select_map(Vs, Var, Tf, Vf, St);
+select_cg(#k_type_clause{type=k_cons,values=[S]}, Var, Tf, Vf, St) ->
+ select_cons(S, Var, Tf, Vf, St);
+select_cg(#k_type_clause{type=k_nil,values=[S]}, Var, Tf, Vf, St) ->
+ select_nil(S, Var, Tf, Vf, St);
+select_cg(#k_type_clause{type=k_literal,values=Vs}, Var, Tf, Vf, St) ->
+ select_literal(Vs, Var, Tf, Vf, St);
+select_cg(#k_type_clause{type=Type,values=Scs}, Var, Tf, Vf, St0) ->
+ {Vis,St1} =
+ mapfoldl(fun (S, Sta) ->
+ {Val,Is,Stb} = select_val(S, Var, Vf, Sta),
+ {{Is,[Val]},Stb}
+ end, St0, Scs),
+ OptVls = combine(lists:sort(combine(Vis))),
+ {Vls,Sis,St2} = select_labels(OptVls, St1, [], []),
+ Arg = ssa_arg(Var, St2),
+ {Is,St} = select_val_cg(Type, Arg, Vls, Tf, Vf, Sis, St2),
+ {Is,St}.
+
+select_val_cg(k_tuple, Tuple, Vls, Tf, Vf, Sis, St0) ->
+ {Is0,St1} = make_cond_branch({bif,is_tuple}, [Tuple], Tf, St0),
+ {Arity,St2} = new_ssa_var('@ssa_arity', St1),
+ GetArity = #b_set{op={bif,tuple_size},dst=Arity,args=[Tuple]},
+ {Is,St} = select_val_cg(k_int, Arity, Vls, Vf, Vf, Sis, St2),
+ {Is0++[GetArity]++Is,St};
+select_val_cg(Type, R, Vls, Tf, Vf, Sis, St0) ->
+ {TypeIs,St1} = if
+ Tf =:= Vf ->
+ %% The type and value failure labels are the same;
+ %% we don't need a type test.
+ {[],St0};
+ true ->
+ %% Different labels for type failure and
+ %% label failure; we need a type test.
+ Test = select_type_test(Type),
+ make_cond_branch(Test, [R], Tf, St0)
+ end,
+ case Vls of
+ [{Val,Succ}] ->
+ {Is,St} = make_cond({bif,'=:='}, [R,Val], Vf, Succ, St1),
+ {TypeIs++Is++Sis,St};
+ [_|_] ->
+ {TypeIs++[#b_switch{arg=R,fail=Vf,list=Vls}|Sis],St1}
+ end.
+
+select_type_test(k_int) -> {bif,is_integer};
+select_type_test(k_atom) -> {bif,is_atom};
+select_type_test(k_float) -> {bif,is_float}.
+
+combine([{Is,Vs1},{Is,Vs2}|Vis]) -> combine([{Is,Vs1 ++ Vs2}|Vis]);
+combine([V|Vis]) -> [V|combine(Vis)];
+combine([]) -> [].
+
+select_labels([{Is,Vs}|Vis], St0, Vls, Sis) ->
+ {Lbl,St1} = new_label(St0),
+ select_labels(Vis, St1, add_vls(Vs, Lbl, Vls), [[{label,Lbl}|Is]|Sis]);
+select_labels([], St, Vls, Sis) ->
+ {Vls,append(Sis),St}.
+
+add_vls([V|Vs], Lbl, Acc) ->
+ add_vls(Vs, Lbl, [{#b_literal{val=V},Lbl}|Acc]);
+add_vls([], _, Acc) -> Acc.
+
+select_literal(S, V, Tf, Vf, St) ->
+ Src = ssa_arg(V, St),
+ F = fun(ValClause, Fail, St0) ->
+ {Val,ValIs,St1} = select_val(ValClause, V, Vf, St0),
+ Args = [Src,#b_literal{val=Val}],
+ {Is,St2} = make_cond_branch({bif,'=:='}, Args, Fail, St1),
+ {Is++ValIs,St2}
+ end,
+ match_fmf(F, Tf, St, S).
+
+select_cons(#k_val_clause{val=#k_cons{hd=Hd,tl=Tl},body=B},
+ V, Tf, Vf, St0) ->
+ Es = [Hd,Tl],
+ {Eis,St1} = select_extract_cons(V, Es, St0),
+ {Bis,St2} = match_cg(B, Vf, St1),
+ Src = ssa_arg(V, St2),
+ {Is,St} = make_cond_branch(is_nonempty_list, [Src], Tf, St2),
+ {Is ++ Eis ++ Bis,St}.
+
+select_nil(#k_val_clause{val=#k_nil{},body=B}, V, Tf, Vf, St0) ->
+ {Bis,St1} = match_cg(B, Vf, St0),
+ Src = ssa_arg(V, St1),
+ {Is,St} = make_cond_branch({bif,'=:='}, [Src,#b_literal{val=[]}], Tf, St1),
+ {Is ++ Bis,St}.
+
+select_binary(#k_val_clause{val=#k_binary{segs=#k_var{name=Ctx0}},body=B},
+ #k_var{}=Src, Tf, Vf, St0) ->
+ {Ctx,St1} = new_ssa_var(Ctx0, St0),
+ {Bis0,St2} = match_cg(B, Vf, St1),
+ {TestIs,St} = make_cond_branch(succeeded, [Ctx], Tf, St2),
+ Bis1 = [#b_set{op=bs_start_match,dst=Ctx,
+ args=[ssa_arg(Src, St)]}] ++ TestIs ++ Bis0,
+ Bis = finish_bs_matching(Bis1),
+ {Bis,St}.
+
+finish_bs_matching([#b_set{op=bs_match,
+ args=[#b_literal{val=string},Ctx,#b_literal{val=BinList}]}=Set|Is])
+ when is_list(BinList) ->
+ I = Set#b_set{args=[#b_literal{val=string},Ctx,
+ #b_literal{val=list_to_bitstring(BinList)}]},
+ finish_bs_matching([I|Is]);
+finish_bs_matching([I|Is]) ->
+ [I|finish_bs_matching(Is)];
+finish_bs_matching([]) -> [].
+
+make_cond(Cond, Args, Fail, Succ, St0) ->
+ {Bool,St} = new_ssa_var('@ssa_bool', St0),
+ Bif = #b_set{op=Cond,dst=Bool,args=Args},
+ Br = #b_br{bool=Bool,succ=Succ,fail=Fail},
+ {[Bif,Br],St}.
+
+make_cond_branch(Cond, Args, Fail, St0) ->
+ {Bool,St1} = new_ssa_var('@ssa_bool', St0),
+ {Succ,St} = new_label(St1),
+ Bif = #b_set{op=Cond,dst=Bool,args=Args},
+ Br = #b_br{bool=Bool,succ=Succ,fail=Fail},
+ {[Bif,Br,{label,Succ}],St}.
+
+make_uncond_branch(Fail) ->
+ #b_br{bool=#b_literal{val=true},succ=Fail,fail=Fail}.
+
+%% Instructions for selection of binary segments.
+
+select_bin_segs(Scs, Ivar, Tf, St) ->
+ match_fmf(fun(S, Fail, Sta) ->
+ select_bin_seg(S, Ivar, Fail, Sta)
+ end, Tf, St, Scs).
+
+select_bin_seg(#k_val_clause{val=#k_bin_seg{size=Size,unit=U,type=T,
+ seg=Seg,flags=Fs,next=Next},
+ body=B,anno=Anno},
+ #k_var{}=Src, Fail, St0) ->
+ LineAnno = line_anno(Anno),
+ Ctx = get_context(Src, St0),
+ {Mis,St1} = select_extract_bin(Next, Size, U, T, Fs, Fail,
+ Ctx, LineAnno, St0),
+ {Extracted,St2} = new_ssa_var(Seg#k_var.name, St1),
+ {Bis,St} = match_cg(B, Fail, St2),
+ BsGet = #b_set{op=bs_extract,dst=Extracted,args=[ssa_arg(Next, St)]},
+ Is = Mis ++ [BsGet] ++ Bis,
+ {Is,St};
+select_bin_seg(#k_val_clause{val=#k_bin_int{size=Sz,unit=U,flags=Fs,
+ val=Val,next=Next},
+ body=B},
+ #k_var{}=Src, Fail, St0) ->
+ Ctx = get_context(Src, St0),
+ {Mis,St1} = select_extract_int(Next, Val, Sz, U, Fs, Fail,
+ Ctx, St0),
+ {Bis,St} = match_cg(B, Fail, St1),
+ Is = case Mis ++ Bis of
+ [#b_set{op=bs_match,args=[#b_literal{val=string},OtherCtx1,Bin1]},
+ #b_set{op=succeeded,dst=Bool1},
+ #b_br{bool=Bool1,succ=Succ,fail=Fail},
+ {label,Succ},
+ #b_set{op=bs_match,dst=Dst,args=[#b_literal{val=string},_OtherCtx2,Bin2]}|
+ [#b_set{op=succeeded,dst=Bool2},
+ #b_br{bool=Bool2,fail=Fail}|_]=Is0] ->
+ %% We used to do this optimization later, but it
+ %% turns out that in huge functions with many
+ %% string matching instructions, it's a huge win
+ %% to do the combination now. To avoid copying the
+ %% binary data again and again, we'll combine bitstrings
+ %% in a list and convert all of it to a bitstring later.
+ {#b_literal{val=B1},#b_literal{val=B2}} = {Bin1,Bin2},
+ Bin = #b_literal{val=[B1,B2]},
+ Set = #b_set{op=bs_match,dst=Dst,args=[#b_literal{val=string},OtherCtx1,Bin]},
+ [Set|Is0];
+ Is0 ->
+ Is0
+ end,
+ {Is,St}.
+
+get_context(#k_var{}=Var, St) ->
+ ssa_arg(Var, St).
+
+select_bin_end(#k_val_clause{val=#k_bin_end{},body=B}, Src, Tf, St0) ->
+ Ctx = get_context(Src, St0),
+ {Bis,St1} = match_cg(B, Tf, St0),
+ {TestIs,St} = make_cond_branch(bs_test_tail, [Ctx,#b_literal{val=0}], Tf, St1),
+ Is = TestIs++Bis,
+ {Is,St}.
+
+select_extract_bin(#k_var{name=Hd}, Size0, Unit, Type, Flags, Vf,
+ Ctx, Anno, St0) ->
+ {Dst,St1} = new_ssa_var(Hd, St0),
+ Size = ssa_arg(Size0, St0),
+ build_bs_instr(Anno, Type, Vf, Ctx, Size, Unit, Flags, Dst, St1).
+
+select_extract_int(#k_var{name=Tl}, 0, #k_int{val=0}, _U, _Fs, _Vf,
+ Ctx, St0) ->
+ St = set_ssa_var(Tl, Ctx, St0),
+ {[],St};
+select_extract_int(#k_var{name=Tl}, Val, #k_int{val=Sz}, U, Fs, Vf,
+ Ctx, St0) ->
+ {Dst,St1} = new_ssa_var(Tl, St0),
+ Bits = U*Sz,
+ Bin = case member(big, Fs) of
+ true ->
+ <<Val:Bits>>;
+ false ->
+ true = member(little, Fs), %Assertion.
+ <<Val:Bits/little>>
+ end,
+ Bits = bit_size(Bin), %Assertion.
+ {TestIs,St} = make_cond_branch(succeeded, [Dst], Vf, St1),
+ Set = #b_set{op=bs_match,dst=Dst,
+ args=[#b_literal{val=string},Ctx,#b_literal{val=Bin}]},
+ {[Set|TestIs],St}.
+
+build_bs_instr(Anno, Type, Fail, Ctx, Size, Unit0, Flags0, Dst, St0) ->
+ Unit = #b_literal{val=Unit0},
+ Flags = #b_literal{val=Flags0},
+ NeedSize = bs_need_size(Type),
+ TypeArg = #b_literal{val=Type},
+ Get = case NeedSize of
+ true ->
+ #b_set{anno=Anno,op=bs_match,dst=Dst,
+ args=[TypeArg,Ctx,Flags,Size,Unit]};
+ false ->
+ #b_set{anno=Anno,op=bs_match,dst=Dst,
+ args=[TypeArg,Ctx,Flags]}
+ end,
+ {Is,St} = make_cond_branch(succeeded, [Dst], Fail, St0),
+ {[Get|Is],St}.
+
+select_val(#k_val_clause{val=#k_tuple{es=Es},body=B}, V, Vf, St0) ->
+ #k{us=Used} = k_get_anno(B),
+ {Eis,St1} = select_extract_tuple(V, Es, Used, St0),
+ {Bis,St2} = match_cg(B, Vf, St1),
+ {length(Es),Eis ++ Bis,St2};
+select_val(#k_val_clause{val=Val0,body=B}, _V, Vf, St0) ->
+ Val = case Val0 of
+ #k_atom{val=Lit} -> Lit;
+ #k_float{val=Lit} -> Lit;
+ #k_int{val=Lit} -> Lit;
+ #k_literal{val=Lit} -> Lit
+ end,
+ {Bis,St1} = match_cg(B, Vf, St0),
+ {Val,Bis,St1}.
+
+%% select_extract_tuple(Src, [V], State) -> {[E],State}.
+%% Extract tuple elements, but only if they are actually used.
+%%
+%% Not extracting tuple elements that are not used is an
+%% optimization for compile time and memory use during compilation.
+%% It is probably worthwhile because it is common to extract only a
+%% few elements from a huge record.
+
+select_extract_tuple(Src, Vs, Used, St0) ->
+ Tuple = ssa_arg(Src, St0),
+ F = fun (#k_var{name=V}, {Elem,S0}) ->
+ case member(V, Used) of
+ true ->
+ Args = [Tuple,#b_literal{val=Elem}],
+ {Dst,S} = new_ssa_var(V, S0),
+ Get = #b_set{op=get_tuple_element,dst=Dst,args=Args},
+ {[Get],{Elem+1,S}};
+ false ->
+ {[],{Elem+1,S0}}
+ end
+ end,
+ {Es,{_,St}} = flatmapfoldl(F, {0,St0}, Vs),
+ {Es,St}.
+
+select_map(Scs, V, Tf, Vf, St0) ->
+ MapSrc = ssa_arg(V, St0),
+ {Is,St1} =
+ match_fmf(fun(#k_val_clause{val=#k_map{op=exact,es=Es},
+ body=B}, Fail, St1) ->
+ select_map_val(V, Es, B, Fail, St1)
+ end, Vf, St0, Scs),
+ {TestIs,St} = make_cond_branch({bif,is_map}, [MapSrc], Tf, St1),
+ {TestIs++Is,St}.
+
+select_map_val(V, Es, B, Fail, St0) ->
+ {Eis,St1} = select_extract_map(Es, V, Fail, St0),
+ {Bis,St2} = match_cg(B, Fail, St1),
+ {Eis++Bis,St2}.
+
+select_extract_map([P|Ps], Src, Fail, St0) ->
+ MapSrc = ssa_arg(Src, St0),
+ #k_map_pair{key=Key0,val=#k_var{name=Dst0}} = P,
+ Key = ssa_arg(Key0, St0),
+ {Dst,St1} = new_ssa_var(Dst0, St0),
+ Set = #b_set{op=get_map_element,dst=Dst,args=[MapSrc,Key]},
+ {TestIs,St2} = make_cond_branch(succeeded, [Dst], Fail, St1),
+ {Is,St} = select_extract_map(Ps, Src, Fail, St2),
+ {[Set|TestIs]++Is,St};
+select_extract_map([], _, _, St) ->
+ {[],St}.
+
+select_extract_cons(Src0, [#k_var{name=Hd},#k_var{name=Tl}], St0) ->
+ Src = ssa_arg(Src0, St0),
+ {HdDst,St1} = new_ssa_var(Hd, St0),
+ {TlDst,St2} = new_ssa_var(Tl, St1),
+ GetHd = #b_set{op=get_hd,dst=HdDst,args=[Src]},
+ GetTl = #b_set{op=get_tl,dst=TlDst,args=[Src]},
+ {[GetHd,GetTl],St2}.
+
+guard_clause_cg(#k_guard_clause{guard=G,body=B}, Fail, St0) ->
+ {Gis,St1} = guard_cg(G, Fail, St0),
+ {Bis,St} = match_cg(B, Fail, St1),
+ {Gis ++ Bis,St}.
+
+%% guard_cg(Guard, Fail, State) -> {[Ainstr],State}.
+%% A guard is a boolean expression of tests. Tests return true or
+%% false. A fault in a test causes the test to return false. Tests
+%% never return the boolean, instead we generate jump code to go to
+%% the correct exit point. Primops and tests all go to the next
+%% instruction on success or jump to a failure label.
+
+guard_cg(#k_protected{arg=Ts,ret=Rs,inner=Inner}, Fail, St) ->
+ protected_cg(Ts, Rs, Inner, Fail, St);
+guard_cg(#k_test{op=Test0,args=As,inverted=Inverted}, Fail, St0) ->
+ #k_remote{mod=#k_atom{val=erlang},name=#k_atom{val=Test}} = Test0,
+ test_cg(Test, Inverted, As, Fail, St0);
+guard_cg(#k_seq{arg=Arg,body=Body}, Fail, St0) ->
+ {ArgIs,St1} = guard_cg(Arg, Fail, St0),
+ {BodyIs,St} = guard_cg(Body, Fail, St1),
+ {ArgIs++BodyIs,St};
+guard_cg(G, _Fail, St) ->
+ cg(G, St).
+
+test_cg('=/=', Inverted, As, Fail, St) ->
+ test_cg('=:=', not Inverted, As, Fail, St);
+test_cg('/=', Inverted, As, Fail, St) ->
+ test_cg('==', not Inverted, As, Fail, St);
+test_cg(Test, Inverted, As0, Fail, St0) ->
+ As = ssa_args(As0, St0),
+ case {Test,ssa_args(As0, St0)} of
+ {is_record,[Tuple,#b_literal{val=Atom}=Tag,#b_literal{val=Int}=Arity]}
+ when is_atom(Atom), is_integer(Int) ->
+ test_is_record_cg(Inverted, Fail, Tuple, Tag, Arity, St0);
+ {_,As} ->
+ {Bool,St1} = new_ssa_var('@ssa_bool', St0),
+ {Succ,St} = new_label(St1),
+ Bif = #b_set{op={bif,Test},dst=Bool,args=As},
+ Br = case Inverted of
+ false -> #b_br{bool=Bool,succ=Succ,fail=Fail};
+ true -> #b_br{bool=Bool,succ=Fail,fail=Succ}
+ end,
+ {[Bif,Br,{label,Succ}],St}
+ end.
+
+test_is_record_cg(false, Fail, Tuple, TagVal, ArityVal, St0) ->
+ {Arity,St1} = new_ssa_var('@ssa_arity', St0),
+ {Tag,St2} = new_ssa_var('@ssa_tag', St1),
+ {Is0,St3} = make_cond_branch({bif,is_tuple}, [Tuple], Fail, St2),
+ GetArity = #b_set{op={bif,tuple_size},dst=Arity,args=[Tuple]},
+ {Is1,St4} = make_cond_branch({bif,'=:='}, [Arity,ArityVal], Fail, St3),
+ GetTag = #b_set{op=get_tuple_element,dst=Tag,
+ args=[Tuple,#b_literal{val=0}]},
+ {Is2,St} = make_cond_branch({bif,'=:='}, [Tag,TagVal], Fail, St4),
+ Is = Is0 ++ [GetArity] ++ Is1 ++ [GetTag] ++ Is2,
+ {Is,St};
+test_is_record_cg(true, Fail, Tuple, TagVal, ArityVal, St0) ->
+ {Succ,St1} = new_label(St0),
+ {Arity,St2} = new_ssa_var('@ssa_arity', St1),
+ {Tag,St3} = new_ssa_var('@ssa_tag', St2),
+ {Is0,St4} = make_cond_branch({bif,is_tuple}, [Tuple], Succ, St3),
+ GetArity = #b_set{op={bif,tuple_size},dst=Arity,args=[Tuple]},
+ {Is1,St5} = make_cond_branch({bif,'=:='}, [Arity,ArityVal], Succ, St4),
+ GetTag = #b_set{op=get_tuple_element,dst=Tag,
+ args=[Tuple,#b_literal{val=0}]},
+ {Is2,St} = make_cond_branch({bif,'=:='}, [Tag,TagVal], Succ, St5),
+ Is3 = [make_uncond_branch(Fail),{label,Succ}],
+ Is = Is0 ++ [GetArity] ++ Is1 ++ [GetTag] ++ Is2 ++ Is3,
+ {Is,St}.
+
+%% protected_cg([Kexpr], [Ret], Fail, St) -> {[Ainstr],St}.
+%% Do a protected. Protecteds without return values are just done
+%% for effect, the return value is not checked, success passes on to
+%% the next instruction and failure jumps to Fail. If there are
+%% return values then these must be set to 'false' on failure,
+%% control always passes to the next instruction.
+
+protected_cg(Ts, [], _, Fail, St0) ->
+ %% Protect these calls, revert when done.
+ {Tis,St1} = guard_cg(Ts, Fail, St0#cg{bfail=Fail}),
+ {Tis,St1#cg{bfail=St0#cg.bfail}};
+protected_cg(Ts, Rs, Inner0, _Fail, St0) ->
+ {Pfail,St1} = new_label(St0),
+ {Br,St2} = new_label(St1),
+ Prot = duplicate(length(Rs), #b_literal{val=false}),
+ {Tis,St3} = guard_cg(Ts, Pfail, St2#cg{break=Pfail,bfail=Pfail}),
+ Inner = ssa_args(Inner0, St3),
+ {BreakVars,St} = new_ssa_vars(Rs, St3),
+ Is = Tis ++ [#cg_break{args=Inner,phi=Br},
+ {label,Pfail},#cg_break{args=Prot,phi=Br},
+ {label,Br},#cg_phi{vars=BreakVars}],
+ {Is,St#cg{break=St0#cg.break,bfail=St0#cg.bfail}}.
+
+%% match_fmf(Fun, LastFail, State, [Clause]) -> {Is,State}.
+%% This is a special flatmapfoldl for match code gen where we
+%% generate a "failure" label for each clause. The last clause uses
+%% an externally generated failure label, LastFail. N.B. We do not
+%% know or care how the failure labels are used.
+
+match_fmf(F, LastFail, St, [H]) ->
+ F(H, LastFail, St);
+match_fmf(F, LastFail, St0, [H|T]) ->
+ {Fail,St1} = new_label(St0),
+ {R,St2} = F(H, Fail, St1),
+ {Rs,St3} = match_fmf(F, LastFail, St2, T),
+ {R ++ [{label,Fail}] ++ Rs,St3}.
+
+%% fail_label(State) -> {Where,FailureLabel}.
+%% Where = guard | no_catch | in_catch
+%% Return an indication of which part of a function code is
+%% being generated for and the appropriate failure label to
+%% use.
+%%
+%% Where has the following meaning:
+%%
+%% guard - Inside a guard.
+%% no_catch - In a function body, not in the scope of
+%% a try/catch or catch.
+%% in_catch - In the scope of a try/catch or catch.
+
+fail_label(#cg{catch_label=Catch,bfail=Fail,ultimate_failure=Ult}) ->
+ if
+ Fail =/= Ult ->
+ {guard,Fail};
+ Catch =:= none ->
+ {no_catch,Fail};
+ is_integer(Catch) ->
+ {in_catch,Catch}
+ end.
+
+%% bif_fail_label(State) -> FailureLabel.
+%% Return the appropriate failure label for a guard BIF call or
+%% primop that fails.
+
+bif_fail_label(St) ->
+ {_,Fail} = fail_label(St),
+ Fail.
+
+%% call_cg(Func, [Arg], [Ret], Le, State) ->
+%% {[Ainstr],State}.
+%% enter_cg(Func, [Arg], Le, St) -> {[Ainstr],St}.
+%% Generate code for call and enter.
+
+call_cg(Func, As, [], Le, St) ->
+ call_cg(Func, As, [#k_var{name='@ssa_ignored'}], Le, St);
+call_cg(Func0, As, [#k_var{name=R}|MoreRs]=Rs, Le, St0) ->
+ case fail_label(St0) of
+ {guard,Fail} ->
+ %% Inside a guard. The only allowed function call is to
+ %% erlang:error/1,2. We will generate a branch to the
+ %% failure branch.
+ #k_remote{mod=#k_atom{val=erlang},
+ name=#k_atom{val=error}} = Func0, %Assertion.
+ [#k_var{name=DestVar}] = Rs,
+ St = set_ssa_var(DestVar, #b_literal{val=unused}, St0),
+ {[make_uncond_branch(Fail),#cg_unreachable{}],St};
+ {Catch,Fail} ->
+ %% Ordinary function call in a function body.
+ Args = ssa_args(As, St0),
+ {Ret,St1} = new_ssa_var(R, St0),
+ Func = call_target(Func0, Args, St0),
+ Call = #b_set{anno=line_anno(Le),op=call,dst=Ret,args=[Func|Args]},
+
+ %% If this is a call to erlang:error(), MoreRs could be a
+ %% nonempty list of variables that each need a value.
+ St2 = foldl(fun(#k_var{name=Dummy}, S) ->
+ set_ssa_var(Dummy, #b_literal{val=unused}, S)
+ end, St1, MoreRs),
+ case Catch of
+ no_catch ->
+ {[Call],St2};
+ in_catch ->
+ {TestIs,St} = make_cond_branch(succeeded, [Ret], Fail, St2),
+ {[Call|TestIs],St}
+ end
+ end.
+
+enter_cg(Func0, As0, Le, St0) ->
+ Anno = line_anno(Le),
+ Func = call_target(Func0, As0, St0),
+ As = ssa_args(As0, St0),
+ {Ret,St} = new_ssa_var('@ssa_ret', St0),
+ Call = #b_set{anno=Anno,op=call,dst=Ret,args=[Func|As]},
+ {[Call,#b_ret{arg=Ret}],St}.
+
+call_target(Func, As, St) ->
+ Arity = length(As),
+ case Func of
+ #k_remote{mod=Mod0,name=Name0} ->
+ Mod = ssa_arg(Mod0, St),
+ Name = ssa_arg(Name0, St),
+ #b_remote{mod=Mod,name=Name,arity=Arity};
+ #k_local{name=Name} when is_atom(Name) ->
+ #b_local{name=#b_literal{val=Name},arity=Arity};
+ #k_var{}=Var ->
+ ssa_arg(Var, St)
+ end.
+
+%% bif_cg(#k_bif{}, Le,State) -> {[Ainstr],State}.
+%% Generate code for a guard BIF or primop.
+
+bif_cg(#k_bif{op=#k_internal{name=Name},args=As,ret=Rs}, Le, St) ->
+ internal_cg(Name, As, Rs, Le, St);
+bif_cg(#k_bif{op=#k_remote{mod=#k_atom{val=erlang},name=#k_atom{val=Name}},
+ args=As,ret=Rs}, Le, St) ->
+ bif_cg(Name, As, Rs, Le, St).
+
+%% internal_cg(Bif, [Arg], [Ret], Le, State) ->
+%% {[Ainstr],State}.
+
+internal_cg(make_fun, [Name0,Arity0|As], Rs, _Le, St0) ->
+ #k_atom{val=Name} = Name0,
+ #k_int{val=Arity} = Arity0,
+ [#k_var{name=Dst0}] = Rs,
+ {Dst,St} = new_ssa_var(Dst0, St0),
+ Args = ssa_args(As, St),
+ Local = #b_local{name=#b_literal{val=Name},arity=Arity},
+ MakeFun = #b_set{op=make_fun,dst=Dst,args=[Local|Args]},
+ {[MakeFun],St};
+internal_cg(bs_init_writable=I, As, [#k_var{name=Dst0}], _Le, St0) ->
+ %% This behaves like a function call.
+ {Dst,St} = new_ssa_var(Dst0, St0),
+ Args = ssa_args(As, St),
+ Set = #b_set{op=I,dst=Dst,args=Args},
+ {[Set],St};
+internal_cg(build_stacktrace=I, As, [#k_var{name=Dst0}], _Le, St0) ->
+ {Dst,St} = new_ssa_var(Dst0, St0),
+ Args = ssa_args(As, St),
+ Set = #b_set{op=I,dst=Dst,args=Args},
+ {[Set],St};
+internal_cg(raise, As, [#k_var{name=Dst0}], _Le, St0) ->
+ Args = ssa_args(As, St0),
+ {Dst,St} = new_ssa_var(Dst0, St0),
+ Resume = #b_set{op=resume,dst=Dst,args=Args},
+ case St of
+ #cg{catch_label=none} ->
+ {[Resume],St};
+ #cg{catch_label=Catch} when is_integer(Catch) ->
+ Is = [Resume,make_uncond_branch(Catch),#cg_unreachable{}],
+ {Is,St}
+ end;
+internal_cg(raw_raise=I, As, [#k_var{name=Dst0}], _Le, St0) ->
+ %% This behaves like a function call.
+ {Dst,St} = new_ssa_var(Dst0, St0),
+ Args = ssa_args(As, St),
+ Set = #b_set{op=I,dst=Dst,args=Args},
+ {[Set],St}.
+
+bif_cg(Bif, As0, [#k_var{name=Dst0}], Le, St0) ->
+ {Dst,St1} = new_ssa_var(Dst0, St0),
+ case {Bif,ssa_args(As0, St0)} of
+ {is_record,[Tuple,#b_literal{val=Atom}=Tag,
+ #b_literal{val=Int}=Arity]}
+ when is_atom(Atom), is_integer(Int) ->
+ bif_is_record_cg(Dst, Tuple, Tag, Arity, St1);
+ {_,As} ->
+ I = #b_set{anno=line_anno(Le),op={bif,Bif},dst=Dst,args=As},
+ case erl_bifs:is_safe(erlang, Bif, length(As)) of
+ false ->
+ Fail = bif_fail_label(St1),
+ {Is,St} = make_cond_branch(succeeded, [Dst], Fail, St1),
+ {[I|Is],St};
+ true->
+ {[I],St1}
+ end
+ end.
+
+bif_is_record_cg(Dst, Tuple, TagVal, ArityVal, St0) ->
+ {Arity,St1} = new_ssa_var('@ssa_arity', St0),
+ {Tag,St2} = new_ssa_var('@ssa_tag', St1),
+ {Phi,St3} = new_label(St2),
+ {False,St4} = new_label(St3),
+ {Is0,St5} = make_cond_branch({bif,is_tuple}, [Tuple], False, St4),
+ GetArity = #b_set{op={bif,tuple_size},dst=Arity,args=[Tuple]},
+ {Is1,St6} = make_cond_branch({bif,'=:='}, [Arity,ArityVal], False, St5),
+ GetTag = #b_set{op=get_tuple_element,dst=Tag,
+ args=[Tuple,#b_literal{val=0}]},
+ {Is2,St} = make_cond_branch({bif,'=:='}, [Tag,TagVal], False, St6),
+ Is3 = [#cg_break{args=[#b_literal{val=true}],phi=Phi},
+ {label,False},
+ #cg_break{args=[#b_literal{val=false}],phi=Phi},
+ {label,Phi},
+ #cg_phi{vars=[Dst]}],
+ Is = Is0 ++ [GetArity] ++ Is1 ++ [GetTag] ++ Is2 ++ Is3,
+ {Is,St}.
+
+%% recv_loop_cg(TimeOut, ReceiveVar, ReceiveMatch, TimeOutExprs,
+%% [Ret], Le, St) -> {[Ainstr],St}.
+
+recv_loop_cg(Te, Rvar, Rm, Tes, Rs, Le, St0) ->
+ %% Get labels.
+ {Rl,St1} = new_label(St0),
+ {Tl,St2} = new_label(St1),
+ {Bl,St3} = new_label(St2),
+ St4 = St3#cg{break=Bl,recv=Rl},
+ {Ris,St5} = cg_recv_mesg(Rvar, Rm, Tl, Le, St4),
+ {Wis,St6} = cg_recv_wait(Te, Tes, St5),
+ {BreakVars,St} = new_ssa_vars(Rs, St6),
+ {Ris ++ [{label,Tl}] ++ Wis ++
+ [{label,Bl},#cg_phi{vars=BreakVars}],
+ St#cg{break=St0#cg.break,recv=St0#cg.recv}}.
+
+%% cg_recv_mesg( ) -> {[Ainstr],St}.
+
+cg_recv_mesg(#k_var{name=R}, Rm, Tl, Le, St0) ->
+ {Dst,St1} = new_ssa_var(R, St0),
+ {Mis,St2} = match_cg(Rm, none, St1),
+ RecvLbl = St1#cg.recv,
+ {TestIs,St} = make_cond_branch(succeeded, [Dst], Tl, St2),
+ Is = [#b_br{anno=line_anno(Le),bool=#b_literal{val=true},
+ succ=RecvLbl,fail=RecvLbl},
+ {label,RecvLbl},
+ #b_set{op=peek_message,dst=Dst}|TestIs],
+ {Is++Mis,St}.
+
+%% cg_recv_wait(Te, Tes, St) -> {[Ainstr],St}.
+
+cg_recv_wait(#k_int{val=0}, Es, St0) ->
+ {Tis,St} = cg(Es, St0),
+ {[#b_set{op=timeout}|Tis],St};
+cg_recv_wait(Te, Es, St0) ->
+ {Tis,St1} = cg(Es, St0),
+ Args = [ssa_arg(Te, St1)],
+ {WaitDst,St2} = new_ssa_var('@ssa_wait', St1),
+ {WaitIs,St} = make_cond_branch(succeeded, [WaitDst], St1#cg.recv, St2),
+ %% Infinite timeout will be optimized later.
+ Is = [#b_set{op=wait_timeout,dst=WaitDst,args=Args}] ++ WaitIs ++
+ [#b_set{op=timeout}] ++ Tis,
+ {Is,St}.
+
+%% try_cg(TryBlock, [BodyVar], TryBody, [ExcpVar], TryHandler, [Ret], St) ->
+%% {[Ainstr],St}.
+
+try_cg(Ta, Vs, Tb, Evs, Th, Rs, St0) ->
+ {B,St1} = new_label(St0), %Body label
+ {H,St2} = new_label(St1), %Handler label
+ {E,St3} = new_label(St2), %End label
+ {Next,St4} = new_label(St3),
+ {TryTag,St5} = new_ssa_var('@ssa_catch_tag', St4),
+ {SsaVs,St6} = new_ssa_vars(Vs, St5),
+ {SsaEvs,St7} = new_ssa_vars(Evs, St6),
+ {Ais,St8} = cg(Ta, St7#cg{break=B,catch_label=H}),
+ St9 = St8#cg{break=E,catch_label=St7#cg.catch_label},
+ {Bis,St10} = cg(Tb, St9),
+ {His,St11} = cg(Th, St10),
+ {BreakVars,St12} = new_ssa_vars(Rs, St11),
+ {CatchedAgg,St} = new_ssa_var('@ssa_agg', St12),
+ ExtractVs = extract_vars(SsaEvs, CatchedAgg, 0),
+ KillTryTag = #b_set{op=kill_try_tag,args=[TryTag]},
+ Args = [#b_literal{val='try'},TryTag],
+ Handler = [{label,H},
+ #b_set{op=landingpad,dst=CatchedAgg,args=Args}] ++
+ ExtractVs ++ [KillTryTag],
+ {[#b_set{op=new_try_tag,dst=TryTag,args=[#b_literal{val='try'}]},
+ #b_br{bool=TryTag,succ=Next,fail=H},
+ {label,Next}] ++ Ais ++
+ [{label,B},#cg_phi{vars=SsaVs},KillTryTag] ++ Bis ++
+ Handler ++ His ++
+ [{label,E},#cg_phi{vars=BreakVars}],
+ St#cg{break=St0#cg.break}}.
+
+try_enter_cg(Ta, Vs, Tb, Evs, Th, St0) ->
+ {B,St1} = new_label(St0), %Body label
+ {H,St2} = new_label(St1), %Handler label
+ {Next,St3} = new_label(St2),
+ {TryTag,St4} = new_ssa_var('@ssa_catch_tag', St3),
+ {SsaVs,St5} = new_ssa_vars(Vs, St4),
+ {SsaEvs,St6} = new_ssa_vars(Evs, St5),
+ {Ais,St7} = cg(Ta, St6#cg{break=B,catch_label=H}),
+ St8 = St7#cg{catch_label=St6#cg.catch_label},
+ {Bis,St9} = cg(Tb, St8),
+ {His,St10} = cg(Th, St9),
+ {CatchedAgg,St} = new_ssa_var('@ssa_agg', St10),
+ ExtractVs = extract_vars(SsaEvs, CatchedAgg, 0),
+ KillTryTag = #b_set{op=kill_try_tag,args=[TryTag]},
+ Args = [#b_literal{val='try'},TryTag],
+ Handler = [{label,H},
+ #b_set{op=landingpad,dst=CatchedAgg,args=Args}] ++
+ ExtractVs ++ [KillTryTag],
+ {[#b_set{op=new_try_tag,dst=TryTag,args=[#b_literal{val='try'}]},
+ #b_br{bool=TryTag,succ=Next,fail=H},
+ {label,Next}] ++ Ais ++
+ [{label,B},#cg_phi{vars=SsaVs},KillTryTag] ++ Bis ++
+ Handler ++ His,
+ St#cg{break=St0#cg.break}}.
+
+extract_vars([V|Vs], Agg, N) ->
+ I = #b_set{op=extract,dst=V,args=[Agg,#b_literal{val=N}]},
+ [I|extract_vars(Vs, Agg, N+1)];
+extract_vars([], _, _) -> [].
+
+%% do_catch_cg(CatchBlock, Ret, St) -> {[Ainstr],St}.
+
+do_catch_cg(Block, #k_var{name=R}, St0) ->
+ {B,St1} = new_label(St0),
+ {Next,St2} = new_label(St1),
+ {H,St3} = new_label(St2),
+ {CatchReg,St4} = new_ssa_var('@ssa_catch_tag', St3),
+ {Dst,St5} = new_ssa_var(R, St4),
+ {Succ,St6} = new_label(St5),
+ {Cis,St7} = cg(Block, St6#cg{break=Succ,catch_label=H}),
+ {CatchedVal,St8} = new_ssa_var('@catched_val', St7),
+ {SuccVal,St9} = new_ssa_var('@success_val', St8),
+ {CatchedAgg,St10} = new_ssa_var('@ssa_agg', St9),
+ {CatchEndVal,St} = new_ssa_var('@catch_end_val', St10),
+ Args = [#b_literal{val='catch'},CatchReg],
+ {[#b_set{op=new_try_tag,dst=CatchReg,args=[#b_literal{val='catch'}]},
+ #b_br{bool=CatchReg,succ=Next,fail=H},
+ {label,Next}] ++ Cis ++
+ [{label,H},
+ #b_set{op=landingpad,dst=CatchedAgg,args=Args},
+ #b_set{op=extract,dst=CatchedVal,
+ args=[CatchedAgg,#b_literal{val=0}]},
+ #cg_break{args=[CatchedVal],phi=B},
+ {label,Succ},
+ #cg_phi{vars=[SuccVal]},
+ #cg_break{args=[SuccVal],phi=B},
+ {label,B},#cg_phi{vars=[CatchEndVal]},
+ #b_set{op=catch_end,dst=Dst,args=[CatchReg,CatchEndVal]}],
+ St#cg{break=St1#cg.break,catch_label=St1#cg.catch_label}}.
+
+%% put_cg([Var], Constr, Le, Vdb, Bef, St) -> {[Ainstr],St}.
+%% Generate code for constructing terms.
+
+put_cg([#k_var{name=R}], #k_cons{hd=Hd,tl=Tl}, _Le, St0) ->
+ Args = ssa_args([Hd,Tl], St0),
+ {Dst,St} = new_ssa_var(R, St0),
+ PutList = #b_set{op=put_list,dst=Dst,args=Args},
+ {[PutList],St};
+put_cg([#k_var{name=R}], #k_tuple{es=Es}, _Le, St0) ->
+ {Ret,St} = new_ssa_var(R, St0),
+ Args = ssa_args(Es, St),
+ PutTuple = #b_set{op=put_tuple,dst=Ret,args=Args},
+ {[PutTuple],St};
+put_cg([#k_var{name=R}], #k_binary{segs=Segs}, Le, St0) ->
+ Fail = bif_fail_label(St0),
+ {Dst,St1} = new_ssa_var(R, St0),
+ cg_binary(Dst, Segs, Fail, Le, St1);
+put_cg([#k_var{name=R}], #k_map{op=Op,var=Map,
+ es=[#k_map_pair{key=#k_var{}=K,val=V}]},
+ Le, St0) ->
+ %% Map: single variable key.
+ SrcMap = ssa_arg(Map, St0),
+ LineAnno = line_anno(Le),
+ List = [ssa_arg(K, St0),ssa_arg(V, St0)],
+ {Dst,St1} = new_ssa_var(R, St0),
+ {Is,St} = put_cg_map(LineAnno, Op, SrcMap, Dst, List, St1),
+ {Is,St};
+put_cg([#k_var{name=R}], #k_map{op=Op,var=Map,es=Es}, Le, St0) ->
+ %% Map: one or more literal keys.
+ [] = [Var || #k_map_pair{key=#k_var{}=Var} <- Es], %Assertion
+ SrcMap = ssa_arg(Map, St0),
+ LineAnno = line_anno(Le),
+ List = flatmap(fun(#k_map_pair{key=K,val=V}) ->
+ [ssa_arg(K, St0),ssa_arg(V, St0)]
+ end, Es),
+ {Dst,St1} = new_ssa_var(R, St0),
+ {Is,St} = put_cg_map(LineAnno, Op, SrcMap, Dst, List, St1),
+ {Is,St};
+put_cg([#k_var{name=R}], Con0, _Le, St0) ->
+ %% Create an alias for a variable or literal.
+ Con = ssa_arg(Con0, St0),
+ St = set_ssa_var(R, Con, St0),
+ {[],St}.
+
+put_cg_map(LineAnno, Op, SrcMap, Dst, List, St0) ->
+ Fail = bif_fail_label(St0),
+ Args = [#b_literal{val=Op},SrcMap|List],
+ PutMap = #b_set{anno=LineAnno,op=put_map,dst=Dst,args=Args},
+ if
+ Op =:= assoc ->
+ {[PutMap],St0};
+ true ->
+ {Is,St} = make_cond_branch(succeeded, [Dst], Fail, St0),
+ {[PutMap|Is],St}
+ end.
+
+%%%
+%%% Code generation for constructing binaries.
+%%%
+
+cg_binary(Dst, Segs0, Fail, Le, St0) ->
+ {PutCode0,SzCalc0,St1} = cg_bin_put(Segs0, Fail, St0),
+ LineAnno = line_anno(Le),
+ Anno = Le#k.a,
+ case PutCode0 of
+ [#b_set{op=bs_put,dst=Bool,args=[_,_,Src,#b_literal{val=all}|_]},
+ #b_br{bool=Bool},
+ {label,_}|_] ->
+ #k_bin_seg{unit=Unit0,next=Segs} = Segs0,
+ Unit = #b_literal{val=Unit0},
+ {PutCode,SzCalc1,St2} = cg_bin_put(Segs, Fail, St1),
+ {_,SzVar,SzCode0,St3} = cg_size_calc(1, SzCalc1, Fail, St2),
+ SzCode = cg_bin_anno(SzCode0, LineAnno),
+ Args = case member(single_use, Anno) of
+ true ->
+ [#b_literal{val=private_append},Src,SzVar,Unit];
+ false ->
+ [#b_literal{val=append},Src,SzVar,Unit]
+ end,
+ BsInit = #b_set{anno=LineAnno,op=bs_init,dst=Dst,args=Args},
+ {TestIs,St} = make_cond_branch(succeeded, [Dst], Fail, St3),
+ {SzCode ++ [BsInit] ++ TestIs ++ PutCode,St};
+ [#b_set{op=bs_put}|_] ->
+ {Unit,SzVar,SzCode0,St2} = cg_size_calc(8, SzCalc0, Fail, St1),
+ SzCode = cg_bin_anno(SzCode0, LineAnno),
+ Args = [#b_literal{val=new},SzVar,Unit],
+ BsInit = #b_set{anno=LineAnno,op=bs_init,dst=Dst,args=Args},
+ {TestIs,St} = make_cond_branch(succeeded, [Dst], Fail, St2),
+ {SzCode ++ [BsInit] ++ TestIs ++ PutCode0,St}
+ end.
+
+cg_bin_anno([Set|Sets], Anno) ->
+ [Set#b_set{anno=Anno}|Sets];
+cg_bin_anno([], _) -> [].
+
+%% cg_size_calc(PreferredUnit, SzCalc, Fail, St0) ->
+%% {ActualUnit,SizeVariable,SizeCode,St}.
+%% Generate size calculation code.
+
+cg_size_calc(Unit, error, _Fail, St) ->
+ {#b_literal{val=Unit},#b_literal{val=badarg},[],St};
+cg_size_calc(8, [{1,_}|_]=SzCalc, Fail, St) ->
+ cg_size_calc(1, SzCalc, Fail, St);
+cg_size_calc(8, SzCalc, Fail, St0) ->
+ {Var,Pre,St} = cg_size_calc_1(SzCalc, Fail, St0),
+ {#b_literal{val=8},Var,Pre,St};
+cg_size_calc(1, SzCalc0, Fail, St0) ->
+ SzCalc = map(fun({8,#b_literal{val=Size}}) ->
+ {1,#b_literal{val=8*Size}};
+ ({8,{{bif,byte_size},Src}}) ->
+ {1,{{bif,bit_size},Src}};
+ ({8,{_,_}=UtfCalc}) ->
+ {1,{'*',#b_literal{val=8},UtfCalc}};
+ ({_,_}=Pair) ->
+ Pair
+ end, SzCalc0),
+ {Var,Pre,St} = cg_size_calc_1(SzCalc, Fail, St0),
+ {#b_literal{val=1},Var,Pre,St}.
+
+cg_size_calc_1(SzCalc, Fail, St0) ->
+ cg_size_calc_2(SzCalc, #b_literal{val=0}, Fail, St0).
+
+cg_size_calc_2([{_,{'*',Unit,{_,_}=Bif}}|T], Sum0, Fail, St0) ->
+ {Sum1,Pre0,St1} = cg_size_calc_2(T, Sum0, Fail, St0),
+ {BifDst,Pre1,St2} = cg_size_bif(Bif, Fail, St1),
+ {Sum,Pre2,St} = cg_size_add(Sum1, BifDst, Unit, Fail, St2),
+ {Sum,Pre0++Pre1++Pre2,St};
+cg_size_calc_2([{_,#b_literal{}=Sz}|T], Sum0, Fail, St0) ->
+ {Sum1,Pre0,St1} = cg_size_calc_2(T, Sum0, Fail, St0),
+ {Sum,Pre,St} = cg_size_add(Sum1, Sz, #b_literal{val=1}, Fail, St1),
+ {Sum,Pre0++Pre,St};
+cg_size_calc_2([{_,#b_var{}=Sz}|T], Sum0, Fail, St0) ->
+ {Sum1,Pre0,St1} = cg_size_calc_2(T, Sum0, Fail, St0),
+ {Sum,Pre,St} = cg_size_add(Sum1, Sz, #b_literal{val=1}, Fail, St1),
+ {Sum,Pre0++Pre,St};
+cg_size_calc_2([{_,{_,_}=Bif}|T], Sum0, Fail, St0) ->
+ {Sum1,Pre0,St1} = cg_size_calc_2(T, Sum0, Fail, St0),
+ {BifDst,Pre1,St2} = cg_size_bif(Bif, Fail, St1),
+ {Sum,Pre2,St} = cg_size_add(Sum1, BifDst, #b_literal{val=1}, Fail, St2),
+ {Sum,Pre0++Pre1++Pre2,St};
+cg_size_calc_2([], Sum, _Fail, St) ->
+ {Sum,[],St}.
+
+cg_size_bif(#b_var{}=Var, _Fail, St) ->
+ {Var,[],St};
+cg_size_bif({Name,Src}, Fail, St0) ->
+ {Dst,St1} = new_ssa_var('@ssa_bif', St0),
+ Bif = #b_set{op=Name,dst=Dst,args=[Src]},
+ {TestIs,St} = make_cond_branch(succeeded, [Dst], Fail, St1),
+ {Dst,[Bif|TestIs],St}.
+
+cg_size_add(#b_literal{val=0}, Val, #b_literal{val=1}, _Fail, St) ->
+ {Val,[],St};
+cg_size_add(A, B, Unit, Fail, St0) ->
+ {Dst,St1} = new_ssa_var('@ssa_sum', St0),
+ {TestIs,St} = make_cond_branch(succeeded, [Dst], Fail, St1),
+ BsAdd = #b_set{op=bs_add,dst=Dst,args=[A,B,Unit]},
+ {Dst,[BsAdd|TestIs],St}.
+
+cg_bin_put(Seg, Fail, St) ->
+ cg_bin_put_1(Seg, Fail, [], [], St).
+
+cg_bin_put_1(#k_bin_seg{size=Size0,unit=U,type=T,flags=Fs,seg=Src0,next=Next},
+ Fail, Acc, SzCalcAcc, St0) ->
+ [Src,Size] = ssa_args([Src0,Size0], St0),
+ NeedSize = bs_need_size(T),
+ TypeArg = #b_literal{val=T},
+ Flags = #b_literal{val=Fs},
+ Unit = #b_literal{val=U},
+ Args = case NeedSize of
+ true -> [TypeArg,Flags,Src,Size,Unit];
+ false -> [TypeArg,Flags,Src]
+ end,
+ {Is,St} = make_cond_branch(bs_put, Args, Fail, St0),
+ SzCalc = bin_size_calc(T, Src, Size, U),
+ cg_bin_put_1(Next, Fail, reverse(Is, Acc), [SzCalc|SzCalcAcc], St);
+cg_bin_put_1(#k_bin_end{}, _, Acc, SzCalcAcc, St) ->
+ SzCalc = fold_size_calc(SzCalcAcc, 0, []),
+ {reverse(Acc),SzCalc,St}.
+
+bs_need_size(utf8) -> false;
+bs_need_size(utf16) -> false;
+bs_need_size(utf32) -> false;
+bs_need_size(_) -> true.
+
+bin_size_calc(utf8, Src, _Size, _Unit) ->
+ {8,{bs_utf8_size,Src}};
+bin_size_calc(utf16, Src, _Size, _Unit) ->
+ {8,{bs_utf16_size,Src}};
+bin_size_calc(utf32, _Src, _Size, _Unit) ->
+ {8,#b_literal{val=4}};
+bin_size_calc(binary, Src, #b_literal{val=all}, Unit) ->
+ case Unit rem 8 of
+ 0 -> {8,{{bif,byte_size},Src}};
+ _ -> {1,{{bif,bit_size},Src}}
+ end;
+bin_size_calc(_Type, _Src, Size, Unit) ->
+ {Unit,Size}.
+
+fold_size_calc([{Unit,#b_literal{val=Size}}|T], Bits, Acc) ->
+ if
+ is_integer(Size) ->
+ fold_size_calc(T, Bits + Unit*Size, Acc);
+ true ->
+ error
+ end;
+fold_size_calc([{U,#b_var{}}=H|T], Bits, Acc) when U =:= 1; U =:= 8 ->
+ fold_size_calc(T, Bits, [H|Acc]);
+fold_size_calc([{U,#b_var{}=Var}|T], Bits, Acc) ->
+ fold_size_calc(T, Bits, [{1,{'*',#b_literal{val=U},Var}}|Acc]);
+fold_size_calc([{_,_}=H|T], Bits, Acc) ->
+ fold_size_calc(T, Bits, [H|Acc]);
+fold_size_calc([], Bits, Acc) ->
+ Bytes = Bits div 8,
+ RemBits = Bits rem 8,
+ Sizes = sort([{1,#b_literal{val=RemBits}},{8,#b_literal{val=Bytes}}|Acc]),
+ [Pair || {_,Sz}=Pair <- Sizes, Sz =/= #b_literal{val=0}].
+
+%%%
+%%% Utilities for creating the SSA types.
+%%%
+
+ssa_args(As, St) ->
+ [ssa_arg(A, St) || A <- As].
+
+ssa_arg(#k_var{name=V}, #cg{vars=Vars}) -> maps:get(V, Vars);
+ssa_arg(#k_literal{val=V}, _) -> #b_literal{val=V};
+ssa_arg(#k_atom{val=V}, _) -> #b_literal{val=V};
+ssa_arg(#k_float{val=V}, _) -> #b_literal{val=V};
+ssa_arg(#k_int{val=V}, _) -> #b_literal{val=V};
+ssa_arg(#k_nil{}, _) -> #b_literal{val=[]}.
+
+new_ssa_vars(Vs, St) ->
+ mapfoldl(fun(#k_var{name=V}, S) ->
+ new_ssa_var(V, S)
+ end, St, Vs).
+
+new_ssa_var(VarBase, #cg{lcount=Uniq,vars=Vars}=St0)
+ when is_atom(VarBase); is_integer(VarBase) ->
+ case Vars of
+ #{VarBase:=_} ->
+ Var = #b_var{name={VarBase,Uniq}},
+ St = St0#cg{lcount=Uniq+1,vars=Vars#{VarBase=>Var}},
+ {Var,St};
+ #{} ->
+ Var = #b_var{name=VarBase},
+ St = St0#cg{vars=Vars#{VarBase=>Var}},
+ {Var,St}
+ end.
+
+set_ssa_var(VarBase, Val, #cg{vars=Vars}=St)
+ when is_atom(VarBase); is_integer(VarBase) ->
+ St#cg{vars=Vars#{VarBase=>Val}}.
+
+%% new_label(St) -> {L,St}.
+
+new_label(#cg{lcount=Next}=St) ->
+ {Next,St#cg{lcount=Next+1}}.
+
+%% line_anno(Le) -> #{} | #{location:={File,Line}}.
+%% Create a location annotation, containing information about the
+%% current filename and line number. The annotation should be
+%% included in any operation that could cause an exception.
+
+line_anno(#k{a=Anno}) ->
+ line_anno_1(Anno).
+
+line_anno_1([Line,{file,Name}]) when is_integer(Line) ->
+ line_anno_2(Name, Line);
+line_anno_1([_|_]=A) ->
+ {Name,Line} = find_loc(A, no_file, 0),
+ line_anno_2(Name, Line);
+line_anno_1([]) ->
+ #{}.
+
+line_anno_2(no_file, _) ->
+ #{};
+line_anno_2(_, 0) ->
+ %% Missing line number or line number 0.
+ #{};
+line_anno_2(Name, Line) ->
+ #{location=>{Name,Line}}.
+
+find_loc([Line|T], File, _) when is_integer(Line) ->
+ find_loc(T, File, Line);
+find_loc([{file,File}|T], _, Line) ->
+ find_loc(T, File, Line);
+find_loc([_|T], File, Line) ->
+ find_loc(T, File, Line);
+find_loc([], File, Line) -> {File,Line}.
+
+flatmapfoldl(F, Accu0, [Hd|Tail]) ->
+ {R,Accu1} = F(Hd, Accu0),
+ {Rs,Accu2} = flatmapfoldl(F, Accu1, Tail),
+ {R++Rs,Accu2};
+flatmapfoldl(_, Accu, []) -> {[],Accu}.
+
+%%%
+%%% Finalize the code.
+%%%
+
+finalize(Asm0, St0) ->
+ Asm1 = fix_phis(Asm0),
+ {Asm,St} = fix_sets(Asm1, [], St0),
+ {build_map(Asm),St}.
+
+fix_phis(Is) ->
+ fix_phis_1(Is, none, #{}).
+
+fix_phis_1([{label,L},#cg_phi{vars=[]}=Phi|Is0], _Lbl, Map0) ->
+ case maps:is_key(L, Map0) of
+ false ->
+ %% No #cg_break{} references this label. Nothing else can
+ %% reference it, so it can be safely be removed.
+ {Is,Map} = drop_upto_label(Is0, Map0),
+ fix_phis_1(Is, none, Map);
+ true ->
+ %% There is a break referencing this label; probably caused
+ %% by a try/catch whose return value is ignored.
+ [{label,L}|fix_phis_1([Phi|Is0], L, Map0)]
+ end;
+fix_phis_1([{label,L}=I|Is], _Lbl, Map) ->
+ [I|fix_phis_1(Is, L, Map)];
+fix_phis_1([#cg_unreachable{}|Is0], _Lbl, Map0) ->
+ {Is,Map} = drop_upto_label(Is0, Map0),
+ fix_phis_1(Is, none, Map);
+fix_phis_1([#cg_break{args=Args,phi=Target}|Is], Lbl, Map) when is_integer(Lbl) ->
+ Pairs1 = case Map of
+ #{Target:=Pairs0} -> Pairs0;
+ #{} -> []
+ end,
+ Pairs = [[{Arg,Lbl} || Arg <- Args]|Pairs1],
+ I = make_uncond_branch(Target),
+ [I|fix_phis_1(Is, none, Map#{Target=>Pairs})];
+fix_phis_1([#cg_phi{vars=Vars}|Is0], Lbl, Map0) ->
+ Pairs = maps:get(Lbl, Map0),
+ Map1 = maps:remove(Lbl, Map0),
+ case gen_phis(Vars, Pairs) of
+ [#b_set{op=phi,args=[]}] ->
+ {Is,Map} = drop_upto_label(Is0, Map1),
+ Ret = #b_ret{arg=#b_literal{val=unreachable}},
+ [Ret|fix_phis_1(Is, none, Map)];
+ Phis ->
+ Phis ++ fix_phis_1(Is0, Lbl, Map1)
+ end;
+fix_phis_1([I|Is], Lbl, Map) ->
+ [I|fix_phis_1(Is, Lbl, Map)];
+fix_phis_1([], _, Map) ->
+ [] = maps:to_list(Map), %Assertion.
+ [].
+
+gen_phis([V|Vs], Preds0) ->
+ {Pairs,Preds} = collect_preds(Preds0, [], []),
+ [#b_set{op=phi,dst=V,args=Pairs}|gen_phis(Vs, Preds)];
+gen_phis([], _) -> [].
+
+collect_preds([[First|Rest]|T], ColAcc, RestAcc) ->
+ collect_preds(T, [First|ColAcc], [Rest|RestAcc]);
+collect_preds([], ColAcc, RestAcc) ->
+ {keysort(2, ColAcc),RestAcc}.
+
+fix_sets([#b_set{dst=none}=Set|Is], Acc, St0) ->
+ {Dst,St} = new_ssa_var('@ssa_ignored', St0),
+ I = Set#b_set{dst=Dst},
+ fix_sets(Is, [I|Acc], St);
+fix_sets([I|Is], Acc, St) ->
+ fix_sets(Is, [I|Acc], St);
+fix_sets([], Acc, St) ->
+ {reverse(Acc),St}.
+
+build_map(Is) ->
+ Blocks = build_graph_1(Is, [], []),
+ maps:from_list(Blocks).
+
+build_graph_1([{label,L}|Is], Lbls, []) ->
+ build_graph_1(Is, [L|Lbls], []);
+build_graph_1([{label,L}|Is], Lbls, [_|_]=BlockAcc) ->
+ make_blocks(Lbls, BlockAcc) ++ build_graph_1(Is, [L], []);
+build_graph_1([I|Is], Lbls, BlockAcc) ->
+ build_graph_1(Is, Lbls, [I|BlockAcc]);
+build_graph_1([], Lbls, BlockAcc) ->
+ make_blocks(Lbls, BlockAcc).
+
+make_blocks(Lbls, [Last|Is0]) ->
+ Is = reverse(Is0),
+ Block = #b_blk{is=Is,last=Last},
+ [{L,Block} || L <- Lbls].
+
+drop_upto_label([{label,_}|_]=Is, Map) ->
+ {Is,Map};
+drop_upto_label([#cg_break{phi=Target}|Is], Map) ->
+ Pairs = case Map of
+ #{Target:=Pairs0} -> Pairs0;
+ #{} -> []
+ end,
+ drop_upto_label(Is, Map#{Target=>Pairs});
+drop_upto_label([_|Is], Map) ->
+ drop_upto_label(Is, Map).
+
+k_get_anno(Thing) -> element(2, Thing).
diff --git a/lib/compiler/src/beam_listing.erl b/lib/compiler/src/beam_listing.erl
index 518b958794..6121593b11 100644
--- a/lib/compiler/src/beam_listing.erl
+++ b/lib/compiler/src/beam_listing.erl
@@ -23,6 +23,7 @@
-include("core_parse.hrl").
-include("v3_kernel.hrl").
+-include("beam_ssa.hrl").
-include("beam_disasm.hrl").
-import(lists, [foreach/2]).
@@ -41,6 +42,12 @@ 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]),
+ 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",
@@ -59,7 +66,7 @@ module(Stream, [_|_]=Fs) ->
foreach(fun (F) -> io:format(Stream, "~p.\n", [F]) end, Fs).
format_asm([{label,L}|Is]) ->
- [" {label,",integer_to_list(L),"}.\n"|format_asm(Is)];
+ [io_lib:format(" {label,~p}.\n", [L])|format_asm(Is)];
format_asm([I|Is]) ->
[io_lib:format(" ~p", [I]),".\n"|format_asm(Is)];
format_asm([]) -> [].
diff --git a/lib/compiler/src/beam_peep.erl b/lib/compiler/src/beam_peep.erl
index 2b8dd40e29..5730e9704e 100644
--- a/lib/compiler/src/beam_peep.erl
+++ b/lib/compiler/src/beam_peep.erl
@@ -94,38 +94,43 @@ peep([{gc_bif,_,_,_,_,Dst}=I|Is], SeenTests0, Acc) ->
peep([{jump,{f,L}},{label,L}=I|Is], _, Acc) ->
%% Sometimes beam_jump has missed this optimization.
peep(Is, gb_sets:empty(), [I|Acc]);
-peep([{select,Op,R,F,Vls0}|Is], SeenTests0, Acc0) ->
+peep([{select,select_val,R,F,Vls0}|Is], SeenTests0, Acc0) ->
case prune_redundant_values(Vls0, F) of
[] ->
%% No values left. Must convert to plain jump.
I = {jump,F},
peep([I|Is], gb_sets:empty(), Acc0);
- [{atom,_}=Value,Lbl] when Op =:= select_val ->
- %% Single value left. Convert to regular test and pop redundant tests.
+ [{atom,_}=Value,Lbl] ->
+ %% Single value left. Convert to regular test.
Is1 = [{test,is_eq_exact,F,[R,Value]},{jump,Lbl}|Is],
- case Acc0 of
- [{test,is_atom,F,[R]}|Acc] ->
- peep(Is1, SeenTests0, Acc);
- _ ->
- peep(Is1, SeenTests0, Acc0)
- end;
- [{integer,_}=Value,Lbl] when Op =:= select_val ->
- %% Single value left. Convert to regular test and pop redundant tests.
+ peep(Is1, SeenTests0, Acc0);
+ [{integer,_}=Value,Lbl] ->
+ %% Single value left. Convert to regular test.
Is1 = [{test,is_eq_exact,F,[R,Value]},{jump,Lbl}|Is],
- case Acc0 of
- [{test,is_integer,F,[R]}|Acc] ->
- peep(Is1, SeenTests0, Acc);
- _ ->
- peep(Is1, SeenTests0, Acc0)
- end;
- [Arity,Lbl] when Op =:= select_tuple_arity ->
- %% Single value left. Convert to regular test
- Is1 = [{test,test_arity,F,[R,Arity]},{jump,Lbl}|Is],
+ peep(Is1, SeenTests0, Acc0);
+ [{atom,B1},Lbl,{atom,B2},Lbl] when B1 =:= not B2 ->
+ %% Replace with is_boolean test.
+ Is1 = [{test,is_boolean,F,[R]},{jump,Lbl}|Is],
peep(Is1, SeenTests0, Acc0);
[_|_]=Vls ->
- I = {select,Op,R,F,Vls},
+ I = {select,select_val,R,F,Vls},
peep(Is, gb_sets:empty(), [I|Acc0])
end;
+peep([{get_map_elements,Fail,Src,List}=I|Is], _SeenTests, Acc0) ->
+ SeenTests = gb_sets:empty(),
+ case simplify_get_map_elements(Fail, Src, List, Acc0) of
+ {ok,Acc} ->
+ peep(Is, SeenTests, Acc);
+ error ->
+ peep(Is, SeenTests, [I|Acc0])
+ end;
+peep([{test,has_map_fields,Fail,Ops}=I|Is], SeenTests, Acc0) ->
+ case simplify_has_map_fields(Fail, Ops, Acc0) of
+ {ok,Acc} ->
+ peep(Is, SeenTests, Acc);
+ error ->
+ peep(Is, SeenTests, [I|Acc0])
+ end;
peep([{test,Op,_,Ops}=I|Is], SeenTests0, Acc) ->
case beam_utils:is_pure_test(I) of
false ->
@@ -176,3 +181,39 @@ prune_redundant_values([_Val,F|Vls], F) ->
prune_redundant_values([Val,Lbl|Vls], F) ->
[Val,Lbl|prune_redundant_values(Vls, F)];
prune_redundant_values([], _) -> [].
+
+simplify_get_map_elements(Fail, Src, {list,[Key,Dst]},
+ [{get_map_elements,Fail,Src,{list,List1}}|Acc]) ->
+ case are_keys_literals([Key]) andalso are_keys_literals(List1) of
+ true ->
+ case member(Key, List1) of
+ true ->
+ %% The key is already in the other list. That is
+ %% very unusual, because there are optimizations to get
+ %% rid of duplicate keys. Therefore, don't try to
+ %% do anything smart here; just keep the
+ %% get_map_elements instructions separate.
+ error;
+ false ->
+ List = [Key,Dst|List1],
+ {ok,[{get_map_elements,Fail,Src,{list,List}}|Acc]}
+ end;
+ false ->
+ error
+ end;
+simplify_get_map_elements(_, _, _, _) -> error.
+
+simplify_has_map_fields(Fail, [Src|Keys0],
+ [{test,has_map_fields,Fail,[Src|Keys1]}|Acc]) ->
+ case are_keys_literals(Keys0) andalso are_keys_literals(Keys1) of
+ true ->
+ Keys = Keys0 ++ Keys1,
+ {ok,[{test,has_map_fields,Fail,[Src|Keys]}|Acc]};
+ false ->
+ error
+ end;
+simplify_has_map_fields(_, _, _) -> error.
+
+are_keys_literals([{x,_}|_]) -> false;
+are_keys_literals([{y,_}|_]) -> false;
+are_keys_literals([_|_]) -> true.
diff --git a/lib/compiler/src/beam_receive.erl b/lib/compiler/src/beam_receive.erl
deleted file mode 100644
index ddbe67605a..0000000000
--- a/lib/compiler/src/beam_receive.erl
+++ /dev/null
@@ -1,416 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2010-2018. All 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_receive).
--export([module/2]).
--import(lists, [foldl/3,reverse/1,reverse/2]).
-
-%%%
-%%% In code such as:
-%%%
-%%% Ref = make_ref(), %Or erlang:monitor(process, Pid)
-%%% .
-%%% .
-%%% .
-%%% receive
-%%% {Ref,Reply} -> Reply
-%%% end.
-%%%
-%%% we know that none of the messages that exist in the message queue
-%%% before the call to make_ref/0 can be matched out in the receive
-%%% statement. Therefore we can avoid going through the entire message
-%%% queue if we introduce two new instructions (here written as
-%%% BIFs in pseudo-Erlang):
-%%%
-%%% recv_mark(SomeUniqInteger),
-%%% Ref = make_ref(),
-%%% .
-%%% .
-%%% .
-%%% recv_set(SomeUniqInteger),
-%%% receive
-%%% {Ref,Reply} -> Reply
-%%% end.
-%%%
-%%% The recv_mark/1 instruction will save the current position and
-%%% SomeUniqInteger in the process context. The recv_set
-%%% instruction will verify that SomeUniqInteger is still stored
-%%% in the process context. If it is, it will set the current pointer
-%%% for the message queue (the next message to be read out) to the
-%%% position that was saved by recv_mark/1.
-%%%
-%%% The remove_message instruction must be modified to invalidate
-%%% the information stored by the previous recv_mark/1, in case there
-%%% is another receive executed between the calls to recv_mark/1 and
-%%% recv_set/1.
-%%%
-%%% We use a reference to a label (i.e. a position in the loaded code)
-%%% as the SomeUniqInteger.
-%%%
-
--spec module(beam_utils:module_code(), [compile:option()]) ->
- {'ok',beam_utils:module_code()}.
-
-module({Mod,Exp,Attr,Fs0,Lc}, _Opts) ->
- Fs = [function(F) || F <- Fs0],
- Code = {Mod,Exp,Attr,Fs,Lc},
- {ok,Code}.
-
-%%%
-%%% Local functions.
-%%%
-
-function({function,Name,Arity,Entry,Is}) ->
- try
- D = beam_utils:index_labels(Is),
- {function,Name,Arity,Entry,opt(Is, D, [])}
- catch
- Class:Error:Stack ->
- io:fwrite("Function: ~w/~w\n", [Name,Arity]),
- erlang:raise(Class, Error, Stack)
- end.
-
-opt([{call_ext,A,{extfunc,erlang,spawn_monitor,A}}=I0|Is0], D, Acc)
- when A =:= 1; A =:= 3 ->
- case ref_in_tuple(Is0) of
- no ->
- opt(Is0, D, [I0|Acc]);
- {yes,Regs,Is1,MatchReversed} ->
- %% The call creates a brand new reference. Now
- %% search for a receive statement in the same
- %% function that will match against the reference.
- case opt_recv(Is1, Regs, D) of
- no ->
- opt(Is0, D, [I0|Acc]);
- {yes,Is,Lbl} ->
- opt(Is, D, MatchReversed++[I0,{recv_mark,{f,Lbl}}|Acc])
- end
- end;
-opt([{call_ext,Arity,{extfunc,erlang,Name,Arity}}=I|Is0], D, Acc) ->
- case creates_new_ref(Name, Arity) of
- true ->
- %% The call creates a brand new reference. Now
- %% search for a receive statement in the same
- %% function that will match against the reference.
- case opt_recv(Is0, regs_init_x0(), D) of
- no ->
- opt(Is0, D, [I|Acc]);
- {yes,Is,Lbl} ->
- opt(Is, D, [I,{recv_mark,{f,Lbl}}|Acc])
- end;
- false ->
- opt(Is0, D, [I|Acc])
- end;
-opt([I|Is], D, Acc) ->
- opt(Is, D, [I|Acc]);
-opt([], _, Acc) ->
- reverse(Acc).
-
-ref_in_tuple([{test,is_tuple,_,[{x,0}]}=I1,
- {test,test_arity,_,[{x,0},2]}=I2,
- {block,[{set,[_],[{x,0}],{get_tuple_element,0}},
- {set,[Dst],[{x,0}],{get_tuple_element,1}}|Bl]}=I3|Is]) ->
- ref_in_tuple_1(Bl, Dst, Is, [I3,I2,I1]);
-ref_in_tuple([{test,is_tuple,_,[{x,0}]}=I1,
- {test,test_arity,_,[{x,0},2]}=I2,
- {block,[{set,[Dst],[{x,0}],{get_tuple_element,1}}|Bl]}=I3|Is]) ->
- ref_in_tuple_1(Bl, Dst, Is, [I3,I2,I1]);
-ref_in_tuple(_) -> no.
-
-ref_in_tuple_1(Bl, Dst, Is, MatchReversed) ->
- Regs0 = regs_init_singleton(Dst),
- Regs = opt_update_regs_bl(Bl, Regs0),
- {yes,Regs,Is,MatchReversed}.
-
-%% creates_new_ref(Name, Arity) -> true|false.
-%% Return 'true' if the BIF Name/Arity will create a new reference.
-creates_new_ref(monitor, 2) -> true;
-creates_new_ref(make_ref, 0) -> true;
-creates_new_ref(_, _) -> false.
-
-%% opt_recv([Instruction], Regs, LabelIndex) -> no|{yes,[Instruction]}
-%% Search for a receive statement that will only retrieve messages
-%% that contain the newly created reference (which is currently in {x,0}).
-opt_recv(Is, Regs, D) ->
- L = gb_sets:empty(),
- opt_recv(Is, D, Regs, L, []).
-
-opt_recv([{label,L}=Lbl,{loop_rec,{f,Fail},_}=Loop|Is], D, R0, _, Acc) ->
- R = regs_kill_not_live(0, R0),
- case regs_empty(R) of
- false ->
- %% We now have the new reference in Y registers
- %% and the current instruction is the beginning of a
- %% receive statement. We must now verify that only messages
- %% that contain the reference will be matched.
- case opt_ref_used(Is, R, Fail, D) of
- false ->
- no;
- true ->
- RecvSet = {recv_set,{f,L}},
- {yes,reverse(Acc, [RecvSet,Lbl,Loop|Is]),L}
- end;
- true ->
- no
- end;
-opt_recv([I|Is], D, R0, L0, Acc) ->
- {R,L} = opt_update_regs(I, R0, L0),
- case regs_empty(R) of
- true ->
- %% The reference is no longer alive. There is no
- %% point in continuing the search.
- no;
- false ->
- opt_recv(Is, D, R, L, [I|Acc])
- end;
-opt_recv([], _, _, _, _) -> no.
-
-opt_update_regs({block,Bl}, R, L) ->
- {opt_update_regs_bl(Bl, R),L};
-opt_update_regs({call,_,_}, R, L) ->
- {regs_kill_not_live(0, R),L};
-opt_update_regs({call_ext,_,_}, R, L) ->
- {regs_kill_not_live(0, R),L};
-opt_update_regs({call_fun,_}, R, L) ->
- {regs_kill_not_live(0, R),L};
-opt_update_regs({kill,Y}, R, L) ->
- {regs_kill([Y], R),L};
-opt_update_regs({'catch',_,{f,Lbl}}, R, L) ->
- {R,gb_sets:add(Lbl, L)};
-opt_update_regs({catch_end,_}, R, L) ->
- {R,L};
-opt_update_regs({label,Lbl}, R, L) ->
- case gb_sets:is_member(Lbl, L) of
- false ->
- %% We can't allow arbitrary labels (since the receive
- %% could be entered without first creating the reference).
- {regs_init(),L};
- true ->
- %% A catch label for a previously seen catch instruction is OK.
- {R,L}
- end;
-opt_update_regs({'try',_,{f,Lbl}}, R, L) ->
- {R,gb_sets:add(Lbl, L)};
-opt_update_regs({try_end,_}, R, L) ->
- {R,L};
-opt_update_regs({line,_}, R, L) ->
- {R,L};
-opt_update_regs(_I, _R, L) ->
- %% Unrecognized instruction. Abort the search.
- {regs_init(),L}.
-
-opt_update_regs_bl([{set,Ds,_,{alloc,Live,_}}|Is], Regs0) ->
- Regs1 = regs_kill_not_live(Live, Regs0),
- Regs = regs_kill(Ds, Regs1),
- opt_update_regs_bl(Is, Regs);
-opt_update_regs_bl([{set,[Dst]=Ds,[Src],move}|Is], Regs0) ->
- Regs1 = regs_kill(Ds, Regs0),
- Regs = case regs_is_member(Src, Regs1) of
- false -> Regs1;
- true -> regs_add(Dst, Regs1)
- end,
- opt_update_regs_bl(Is, Regs);
-opt_update_regs_bl([{set,Ds,_,_}|Is], Regs0) ->
- Regs = regs_kill(Ds, Regs0),
- opt_update_regs_bl(Is, Regs);
-opt_update_regs_bl([], Regs) -> Regs.
-
-%% opt_ref_used([Instruction], RefRegs, FailLabel, LabelIndex) -> true|false
-%% Return 'true' if it is certain that only messages that contain the same
-%% reference as in RefRegs can be matched out. Otherwise return 'false'.
-%%
-%% Basically, we follow all possible paths through the receive statement.
-%% If all paths are safe, we return 'true'.
-%%
-%% A branch to FailLabel is safe, because it exits the receive statement
-%% and no further message may be matched out.
-%%
-%% If a path hits an comparision between RefRegs and part of the message,
-%% that path is safe (any messages that may be matched further down the
-%% path is guaranteed to contain the reference).
-%%
-%% Otherwise, if we hit a 'remove_message' instruction, we give up
-%% and return 'false' (the optimization is definitely unsafe). If
-%% we hit an unrecognized instruction, we also give up and return
-%% 'false' (the optimization may be unsafe).
-
-opt_ref_used(Is, RefRegs, Fail, D) ->
- Done = gb_sets:singleton(Fail),
- Regs = regs_init_x0(),
- try
- _ = opt_ref_used_1(Is, RefRegs, D, Done, Regs),
- true
- catch
- throw:not_used ->
- false
- end.
-
-%% This functions only returns if all paths through the receive
-%% statement are safe, and throws an 'not_used' term otherwise.
-opt_ref_used_1([{block,Bl}|Is], RefRegs, D, Done, Regs0) ->
- Regs = opt_ref_used_bl(Bl, Regs0),
- opt_ref_used_1(Is, RefRegs, D, Done, Regs);
-opt_ref_used_1([{test,is_eq_exact,{f,Fail},Args}|Is],
- RefRegs, D, Done0, Regs) ->
- Done = opt_ref_used_at(Fail, RefRegs, D, Done0, Regs),
- case is_ref_msg_comparison(Args, RefRegs, Regs) of
- false ->
- opt_ref_used_1(Is, RefRegs, D, Done, Regs);
- true ->
- %% The instructions that follow (Is) can only be executed
- %% if the message contains the same reference as in RefRegs.
- Done
- end;
-opt_ref_used_1([{test,is_ne_exact,{f,Fail},Args}|Is],
- RefRegs, D, Done0, Regs) ->
- Done = opt_ref_used_1(Is, RefRegs, D, Done0, Regs),
- case is_ref_msg_comparison(Args, RefRegs, Regs) of
- false ->
- opt_ref_used_at(Fail, RefRegs, D, Done, Regs);
- true ->
- Done
- end;
-opt_ref_used_1([{test,_,{f,Fail},_}|Is], RefRegs, D, Done0, Regs) ->
- Done = opt_ref_used_at(Fail, RefRegs, D, Done0, Regs),
- opt_ref_used_1(Is, RefRegs, D, Done, Regs);
-opt_ref_used_1([{select,_,_,{f,Fail},List}|_], RefRegs, D, Done, Regs) ->
- Lbls = [F || {f,F} <- List] ++ [Fail],
- opt_ref_used_in_all(Lbls, RefRegs, D, Done, Regs);
-opt_ref_used_1([{label,Lbl}|Is], RefRegs, D, Done, Regs) ->
- case gb_sets:is_member(Lbl, Done) of
- true -> Done;
- false -> opt_ref_used_1(Is, RefRegs, D, Done, Regs)
- end;
-opt_ref_used_1([{loop_rec_end,_}|_], _, _, Done, _) ->
- Done;
-opt_ref_used_1([_I|_], _RefReg, _D, _Done, _Regs) ->
- %% The optimization may be unsafe.
- throw(not_used).
-
-%% is_ref_msg_comparison(Args, RefRegs, RegisterSet) -> true|false.
-%% Return 'true' if Args denotes a comparison between the
-%% reference and message or part of the message.
-is_ref_msg_comparison([R1,R2], RefRegs, Regs) ->
- (regs_is_member(R2, RefRegs) andalso regs_is_member(R1, Regs)) orelse
- (regs_is_member(R1, RefRegs) andalso regs_is_member(R2, Regs)).
-
-opt_ref_used_in_all([L|Ls], RefRegs, D, Done0, Regs) ->
- Done = opt_ref_used_at(L, RefRegs, D, Done0, Regs),
- opt_ref_used_in_all(Ls, RefRegs, D, Done, Regs);
-opt_ref_used_in_all([], _, _, Done, _) -> Done.
-
-opt_ref_used_at(Fail, RefRegs, D, Done0, Regs) ->
- case gb_sets:is_member(Fail, Done0) of
- true ->
- Done0;
- false ->
- Is = beam_utils:code_at(Fail, D),
- Done = opt_ref_used_1(Is, RefRegs, D, Done0, Regs),
- gb_sets:add(Fail, Done)
- end.
-
-opt_ref_used_bl([{set,[],[],remove_message}|_], _) ->
- %% We have proved that a message that does not depend on the
- %% reference can be matched out.
- throw(not_used);
-opt_ref_used_bl([{set,Ds,Ss,_}|Is], Regs0) ->
- case regs_all_members(Ss, Regs0) of
- false ->
- %% The destination registers may be assigned values that
- %% are not dependent on the message being matched.
- Regs = regs_kill(Ds, Regs0),
- opt_ref_used_bl(Is, Regs);
- true ->
- %% All the sources depend on the message directly or
- %% indirectly.
- Regs = regs_add_list(Ds, Regs0),
- opt_ref_used_bl(Is, Regs)
- end;
-opt_ref_used_bl([], Regs) -> Regs.
-
-%%%
-%%% Functions for keeping track of a set of registers.
-%%%
-
-%% regs_init() -> RegisterSet
-%% Return an empty set of registers.
-
-regs_init() ->
- {0,0}.
-
-%% regs_init_singleton(Register) -> RegisterSet
-%% Return a set that only contains one register.
-
-regs_init_singleton(Reg) ->
- regs_add(Reg, regs_init()).
-
-%% regs_init_x0() -> RegisterSet
-%% Return a set that only contains the {x,0} register.
-
-regs_init_x0() ->
- {1 bsl 0,0}.
-
-%% regs_empty(Register) -> true|false
-%% Test whether the register set is empty.
-
-regs_empty(R) ->
- R =:= {0,0}.
-
-%% regs_kill_not_live(Live, RegisterSet) -> RegisterSet'
-%% Kill all registers indicated not live by Live.
-
-regs_kill_not_live(Live, {Xregs,Yregs}) ->
- {Xregs band ((1 bsl Live)-1),Yregs}.
-
-%% regs_kill([Register], RegisterSet) -> RegisterSet'
-%% Kill all registers mentioned in the list of registers.
-
-regs_kill([{x,N}|Rs], {Xregs,Yregs}) ->
- regs_kill(Rs, {Xregs band (bnot (1 bsl N)),Yregs});
-regs_kill([{y,N}|Rs], {Xregs,Yregs}) ->
- regs_kill(Rs, {Xregs,Yregs band (bnot (1 bsl N))});
-regs_kill([{fr,_}|Rs], Regs) ->
- regs_kill(Rs, Regs);
-regs_kill([], Regs) -> Regs.
-
-regs_add_list(List, Regs) ->
- foldl(fun(R, A) -> regs_add(R, A) end, Regs, List).
-
-%% regs_add(Register, RegisterSet) -> RegisterSet'
-%% Add a new register to the set of registers.
-
-regs_add({x,N}, {Xregs,Yregs}) ->
- {Xregs bor (1 bsl N),Yregs};
-regs_add({y,N}, {Xregs,Yregs}) ->
- {Xregs,Yregs bor (1 bsl N)}.
-
-%% regs_all_members([Register], RegisterSet) -> true|false
-%% Test whether all of the registers are part of the register set.
-
-regs_all_members([R|Rs], Regs) ->
- regs_is_member(R, Regs) andalso regs_all_members(Rs, Regs);
-regs_all_members([], _) -> true.
-
-%% regs_is_member(Register, RegisterSet) -> true|false
-%% Test whether Register is part of the register set.
-
-regs_is_member({x,N}, {Regs,_}) -> Regs band (1 bsl N) =/= 0;
-regs_is_member({y,N}, {_,Regs}) -> Regs band (1 bsl N) =/= 0;
-regs_is_member(_, _) -> false.
diff --git a/lib/compiler/src/beam_record.erl b/lib/compiler/src/beam_record.erl
deleted file mode 100644
index 58a6de6775..0000000000
--- a/lib/compiler/src/beam_record.erl
+++ /dev/null
@@ -1,131 +0,0 @@
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2014-2017. 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%
-%%
-
-%% Rewrite the instruction stream on tagged tuple tests.
-%% Tagged tuples means a tuple of any arity with an atom as its
-%% first element, such as records and error tuples.
-%%
-%% From:
-%% ...
-%% {test,is_tuple,Fail,[Src]}.
-%% {test,test_arity,Fail,[Src,Sz]}.
-%% ...
-%% {get_tuple_element,Src,0,Dst}.
-%% ...
-%% {test,is_eq_exact,Fail,[Dst,Atom]}.
-%% ...
-%% To:
-%% ...
-%% {test,is_tagged_tuple,Fail,[Src,Sz,Atom]}.
-%% ...
-%%
-
--module(beam_record).
--export([module/2]).
-
--import(lists, [reverse/1,reverse/2]).
-
--spec module(beam_utils:module_code(), [compile:option()]) ->
- {'ok',beam_utils:module_code()}.
-
-module({Mod,Exp,Attr,Fs0,Lc}, _Opt) ->
- Fs = [function(F) || F <- Fs0],
- {ok,{Mod,Exp,Attr,Fs,Lc}}.
-
-function({function,Name,Arity,CLabel,Is0}) ->
- try
- Is1 = beam_utils:anno_defs(Is0),
- Idx = beam_utils:index_labels(Is1),
- Is = rewrite(reverse(Is1), Idx),
- {function,Name,Arity,CLabel,Is}
- catch
- Class:Error:Stack ->
- io:fwrite("Function: ~w/~w\n", [Name,Arity]),
- erlang:raise(Class, Error, Stack)
- end.
-
-rewrite(Is, Idx) ->
- rewrite(Is, Idx, 0, []).
-
-rewrite([{test,test_arity,Fail,[Src,N]}=TA,
- {test,is_tuple,Fail,[Src]}=TT|Is], Idx, Def, Acc0) ->
- case is_tagged_tuple(Acc0, Def, Fail, Src, Idx) of
- no ->
- rewrite(Is, Idx, 0, [TT,TA|Acc0]);
- {yes,Atom,Acc} ->
- I = {test,is_tagged_tuple,Fail,[Src,N,Atom]},
- rewrite(Is, Idx, Def, [I|Acc])
- end;
-rewrite([{block,[{'%anno',{def,Def}}|Bl]}|Is], Idx, _Def, Acc) ->
- rewrite(Is, Idx, Def, [{block,Bl}|Acc]);
-rewrite([{label,L}=I|Is], Idx0, Def, Acc) ->
- Idx = beam_utils:index_label(L, Acc, Idx0),
- rewrite(Is, Idx, Def, [I|Acc]);
-rewrite([I|Is], Idx, Def, Acc) ->
- rewrite(Is, Idx, Def, [I|Acc]);
-rewrite([], _, _, Acc) -> Acc.
-
-is_tagged_tuple([{block,Bl},
- {test,is_eq_exact,Fail,[Dst,{atom,_}=Atom]}|Is],
- Def, Fail, Src, Idx) ->
- case is_tagged_tuple_1(Bl, Is, Fail, Src, Dst, Idx, Def, []) of
- no ->
- no;
- {yes,[]} ->
- {yes,Atom,Is};
- {yes,[_|_]=Block} ->
- {yes,Atom,[{block,Block}|Is]}
- end;
-is_tagged_tuple(_, _, _, _, _) ->
- no.
-
-is_tagged_tuple_1([{set,[Dst],[Src],{get_tuple_element,0}}=I|Bl],
- Is, Fail, Src, Dst, Idx, Def, Acc) ->
- %% Check usage of Dst to find out whether the get_tuple_element
- %% is needed.
- case usage(Dst, Is, Fail, Idx) of
- killed ->
- %% Safe to remove the get_tuple_element instruction.
- {yes,reverse(Acc, Bl)};
- used ->
- %% Actively used. Must keep instruction.
- {yes,reverse(Acc, [I|Bl])};
- not_used ->
- %% Not actually used (but must be initialized).
- case is_defined(Dst, Def) of
- false ->
- %% Dst must be initialized, but the
- %% actual value does not matter.
- Kill = {set,[Dst],[nil],move},
- {yes,reverse(Acc, [Kill|Bl])};
- true ->
- %% The register is previously initialized.
- %% We can remove the instruction.
- {yes,reverse(Acc, Bl)}
- end
- end;
-is_tagged_tuple_1([I|Bl], Is, Fail, Src, Dst, Idx, Def, Acc) ->
- is_tagged_tuple_1(Bl, Is, Fail, Src, Dst, Idx, Def, [I|Acc]);
-is_tagged_tuple_1(_, _, _, _, _, _, _, _) ->
- no.
-
-usage(Dst, Is, Fail, Idx) ->
- beam_utils:usage(Dst, [{test,is_number,Fail,[nil]}|Is], Idx).
-
-is_defined({x,X}, Def) ->
- (Def bsr X) band 1 =:= 1.
diff --git a/lib/compiler/src/beam_reorder.erl b/lib/compiler/src/beam_reorder.erl
deleted file mode 100644
index 8d2ef5a431..0000000000
--- a/lib/compiler/src/beam_reorder.erl
+++ /dev/null
@@ -1,150 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1999-2018. All 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_reorder).
-
--export([module/2]).
--import(lists, [member/2,reverse/1]).
-
--spec module(beam_utils:module_code(), [compile:option()]) ->
- {'ok',beam_utils:module_code()}.
-
-module({Mod,Exp,Attr,Fs0,Lc}, _Opt) ->
- Fs = [function(F) || F <- Fs0],
- {ok,{Mod,Exp,Attr,Fs,Lc}}.
-
-function({function,Name,Arity,CLabel,Is0}) ->
- try
- Is = reorder(Is0),
- {function,Name,Arity,CLabel,Is}
- catch
- Class:Error:Stack ->
- io:fwrite("Function: ~w/~w\n", [Name,Arity]),
- erlang:raise(Class, Error, Stack)
- end.
-
-%% reorder(Instructions0) -> Instructions
-%% Reorder instructions before the beam_block pass, because reordering
-%% will be more cumbersome when the blocks are in place.
-%%
-%% Execution of get_tuple_element instructions can be delayed until
-%% they are actually needed. Consider the sequence:
-%%
-%% get_tuple_element Tuple Pos Dst
-%% test Test Fail Operands
-%%
-%% If Dst is killed at label Fail (and not referenced in Operands),
-%% we can can swap the instructions:
-%%
-%% test Test Fail Operands
-%% get_tuple_element Tuple Pos Dst
-%%
-%% That can be beneficial in two ways: Firstly, if the branch is taken
-%% we have avoided execution of the get_tuple_element instruction.
-%% Secondly, even if the branch is not taken, subsequent optimization
-%% (opt_blocks/1) may be able to change Dst to the final destination
-%% register and eliminate a 'move' instruction.
-
-reorder(Is) ->
- D = beam_utils:index_labels(Is),
- reorder_1(Is, D, []).
-
-reorder_1([{Op,_,_}=TryCatch|[I|Is]=Is0], D, Acc)
- when Op =:= 'catch'; Op =:= 'try' ->
- %% Don't allow 'try' or 'catch' instructions to split blocks if
- %% it can be avoided.
- case is_safe(I) of
- false ->
- reorder_1(Is0, D, [TryCatch|Acc]);
- true ->
- reorder_1([TryCatch|Is], D, [I|Acc])
- end;
-reorder_1([{label,L}=I|_], D, Acc) ->
- Is = beam_utils:code_at(L, D),
- reorder_1(Is, D, [I|Acc]);
-reorder_1([{test,is_nonempty_list,_,_}=I|Is], D, Acc) ->
- %% The run-time system may combine the is_nonempty_list test with
- %% the following get_list instruction.
- reorder_1(Is, D, [I|Acc]);
-reorder_1([{test,_,_,_}=I,
- {select,_,_,_,_}=S|Is], D, Acc) ->
- %% There is nothing to gain by inserting a get_tuple_element
- %% instruction between the test instruction and the select
- %% instruction.
- reorder_1(Is, D, [S,I|Acc]);
-reorder_1([{test,_,{f,_},[Src|_]}=I|Is], D,
- [{get_tuple_element,Src,_,_}|_]=Acc) ->
- %% We want to avoid code that can confuse beam_validator such as:
- %% is_tuple Fail Src
- %% test_arity Fail Src Arity
- %% is_map Fail Src
- %% get_tuple_element Src Pos Dst
- %% Therefore, don't reorder the instructions in such cases.
- reorder_1(Is, D, [I|Acc]);
-reorder_1([{test,_,{f,L},Ss}=I|Is0], D0,
- [{get_tuple_element,_,_,El}=G|Acc0]=Acc) ->
- case member(El, Ss) of
- true ->
- reorder_1(Is0, D0, [I|Acc]);
- false ->
- case beam_utils:is_killed_at(El, L, D0) of
- true ->
- Is = [I,G|Is0],
- reorder_1(Is, D0, Acc0);
- false ->
- case beam_utils:is_killed(El, Is0, D0) of
- true ->
- Code0 = beam_utils:code_at(L, D0),
- Code = [G|Code0],
- D = beam_utils:index_label(L, Code, D0),
- Is = [I|Is0],
- reorder_1(Is, D, Acc0);
- false ->
- reorder_1(Is0, D0, [I|Acc])
- end
- end
- end;
-reorder_1([{allocate_zero,N,Live}=I0|Is], D,
- [{get_tuple_element,{x,Tup},_,{x,Dst}}=G|Acc]=Acc0) ->
- case Tup < Dst andalso Dst+1 =:= Live of
- true ->
- %% Move allocation instruction upwards past
- %% get_tuple_element instructions to create more
- %% opportunities for moving get_tuple_element
- %% instructions.
- I = {allocate_zero,N,Dst},
- reorder_1([I,G|Is], D, Acc);
- false ->
- reorder_1(Is, D, [I0|Acc0])
- end;
-reorder_1([I|Is], D, Acc) ->
- reorder_1(Is, D, [I|Acc]);
-reorder_1([], _, Acc) -> reverse(Acc).
-
-%% is_safe(Instruction) -> true|false
-%% Test whether an instruction is safe (cannot cause an exception).
-
-is_safe({kill,_}) -> true;
-is_safe({move,_,_}) -> true;
-is_safe({put,_}) -> true;
-is_safe({put_list,_,_,_}) -> true;
-is_safe({put_tuple,_,_}) -> true;
-is_safe({test_heap,_,_}) -> true;
-is_safe(_) -> false.
diff --git a/lib/compiler/src/beam_split.erl b/lib/compiler/src/beam_split.erl
deleted file mode 100644
index 809e49b3d0..0000000000
--- a/lib/compiler/src/beam_split.erl
+++ /dev/null
@@ -1,94 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2011-2018. All 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_split).
--export([module/2]).
-
--import(lists, [reverse/1]).
-
--spec module(beam_utils:module_code(), [compile:option()]) ->
- {'ok',beam_utils:module_code()}.
-
-module({Mod,Exp,Attr,Fs0,Lc}, _Opts) ->
- Fs = [split_blocks(F) || F <- Fs0],
- {ok,{Mod,Exp,Attr,Fs,Lc}}.
-
-%% We must split the basic block when we encounter instructions with labels,
-%% such as catches and BIFs. All labels must be visible outside the blocks.
-
-split_blocks({function,Name,Arity,CLabel,Is0}) ->
- Is = split_blocks(Is0, []),
- {function,Name,Arity,CLabel,Is}.
-
-split_blocks([{block,Bl}|Is], Acc0) ->
- Acc = split_block(Bl, [], Acc0),
- split_blocks(Is, Acc);
-split_blocks([I|Is], Acc) ->
- split_blocks(Is, [I|Acc]);
-split_blocks([], Acc) -> reverse(Acc).
-
-split_block([{set,[R],[_,_,_]=As,{bif,is_record,{f,Lbl}}}|Is], Bl, Acc) ->
- %% is_record/3 must be translated by beam_clean; therefore,
- %% it must be outside of any block.
- split_block(Is, [], [{bif,is_record,{f,Lbl},As,R}|make_block(Bl, Acc)]);
-split_block([{set,[R],As,{bif,N,{f,Lbl}=Fail}}|Is], Bl, Acc) when Lbl =/= 0 ->
- split_block(Is, [], [{bif,N,Fail,As,R}|make_block(Bl, Acc)]);
-split_block([{set,[],[],{line,_}=Line},
- {set,[R],As,{bif,raise,{f,_}=Fail}}|Is], Bl, Acc) ->
- split_block(Is, [], [{bif,raise,Fail,As,R},Line|make_block(Bl, Acc)]);
-split_block([{set,[R],As,{alloc,Live,{gc_bif,N,{f,Lbl}=Fail}}}|Is], Bl, Acc)
- when Lbl =/= 0 ->
- split_block(Is, [], [{gc_bif,N,Fail,Live,As,R}|make_block(Bl, Acc)]);
-split_block([{set,[D],[S|Puts],{alloc,R,{put_map,Op,{f,Lbl}=Fail}}}|Is],
- Bl, Acc) when Lbl =/= 0 ->
- split_block(Is, [], [{put_map,Fail,Op,S,D,R,{list,Puts}}|
- make_block(Bl, Acc)]);
-split_block([{set,[R],[],{try_catch,Op,L}}|Is], Bl, Acc) ->
- split_block(Is, [], [{Op,R,L}|make_block(Bl, Acc)]);
-split_block([I|Is], Bl, Acc) ->
- split_block(Is, [I|Bl], Acc);
-split_block([], Bl, Acc) -> make_block(Bl, Acc).
-
-make_block([], Acc) -> Acc;
-make_block([{set,[D],Ss,{bif,Op,Fail}}|Bl]=Bl0, Acc) ->
- %% If the last instruction in the block is a comparison or boolean operator
- %% (such as '=:='), move it out of the block to facilitate further
- %% optimizations.
- Arity = length(Ss),
- case erl_internal:comp_op(Op, Arity) orelse
- erl_internal:new_type_test(Op, Arity) orelse
- erl_internal:bool_op(Op, Arity) of
- false ->
- [{block,reverse(Bl0)}|Acc];
- true ->
- I = {bif,Op,Fail,Ss,D},
- case Bl =:= [] of
- true -> [I|Acc];
- false -> [I,{block,reverse(Bl)}|Acc]
- end
- end;
-make_block([{set,[Dst],[Src],move}|Bl], Acc) ->
- %% Make optimization of {move,Src,Dst}, {jump,...} possible.
- I = {move,Src,Dst},
- case Bl =:= [] of
- true -> [I|Acc];
- false -> [I,{block,reverse(Bl)}|Acc]
- end;
-make_block(Bl, Acc) -> [{block,reverse(Bl)}|Acc].
diff --git a/lib/compiler/src/beam_ssa.erl b/lib/compiler/src/beam_ssa.erl
new file mode 100644
index 0000000000..a9977b0b1d
--- /dev/null
+++ b/lib/compiler/src/beam_ssa.erl
@@ -0,0 +1,890 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018. All 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: Type definitions and utilities for the SSA format.
+
+-module(beam_ssa).
+-export([add_anno/3,get_anno/2,get_anno/3,
+ clobbers_xregs/1,def/2,def_used/2,
+ definitions/1,
+ dominators/1,common_dominators/3,
+ flatmapfold_instrs_rpo/4,
+ fold_po/3,fold_po/4,fold_rpo/3,fold_rpo/4,
+ fold_instrs_rpo/4,
+ linearize/1,
+ mapfold_blocks_rpo/4,
+ mapfold_instrs_rpo/4,
+ normalize/1,
+ no_side_effect/1,
+ predecessors/1,
+ rename_vars/3,
+ rpo/1,rpo/2,
+ split_blocks/3,
+ successors/1,successors/2,
+ trim_unreachable/1,
+ update_phi_labels/4,used/1,
+ uses/1,uses/2]).
+
+-export_type([b_module/0,b_function/0,b_blk/0,b_set/0,
+ b_ret/0,b_br/0,b_switch/0,terminator/0,
+ b_var/0,b_literal/0,b_remote/0,b_local/0,
+ value/0,argument/0,label/0,
+ var_name/0,var_base/0,literal_value/0,
+ op/0,anno/0,block_map/0,dominator_map/0,
+ rename_map/0,rename_proplist/0,usage_map/0,
+ definition_map/0]).
+
+-include("beam_ssa.hrl").
+
+-type b_module() :: #b_module{}.
+-type b_function() :: #b_function{}.
+-type b_blk() :: #b_blk{}.
+-type b_set() :: #b_set{}.
+
+-type b_br() :: #b_br{}.
+-type b_ret() :: #b_ret{}.
+-type b_switch() :: #b_switch{}.
+-type terminator() :: b_br() | b_ret() | b_switch().
+
+-type construct() :: b_module() | b_function() | b_blk() | b_set() |
+ terminator().
+
+-type b_var() :: #b_var{}.
+-type b_literal() :: #b_literal{}.
+-type b_remote() :: #b_remote{}.
+-type b_local() :: #b_local{}.
+
+-type value() :: b_var() | b_literal().
+-type phi_value() :: {value(),label()}.
+-type argument() :: value() | b_remote() | b_local() | phi_value().
+-type label() :: non_neg_integer().
+
+-type var_name() :: var_base() | {var_base(),non_neg_integer()}.
+-type var_base() :: atom() | non_neg_integer().
+
+-type literal_value() :: atom() | integer() | float() | list() |
+ nil() | tuple() | map() | binary().
+
+-type op() :: {'bif',atom()} | {'float',float_op()} | prim_op() | cg_prim_op().
+-type anno() :: #{atom() := any()}.
+
+-type block_map() :: #{label():=b_blk()}.
+-type dominator_map() :: #{label():=[label()]}.
+-type numbering_map() :: #{label():=non_neg_integer()}.
+-type usage_map() :: #{b_var():=[{label(),b_set() | terminator()}]}.
+-type definition_map() :: #{b_var():=b_set()}.
+-type rename_map() :: #{b_var():=value()}.
+-type rename_proplist() :: [{b_var(),value()}].
+
+%% Note: By default, dialyzer will collapse this type to atom().
+%% To avoid the collapsing, change the value of SET_LIMIT to 50 in the
+%% file erl_types.erl in the hipe application.
+
+-type prim_op() :: 'bs_add' | 'bs_extract' | 'bs_init' | 'bs_init_writable' |
+ 'bs_match' | 'bs_put' | 'bs_start_match' | 'bs_test_tail' |
+ 'bs_utf16_size' | 'bs_utf8_size' | 'build_stacktrace' |
+ 'call' | 'catch_end' |
+ 'extract' |
+ 'get_hd' | 'get_map_element' | 'get_tl' | 'get_tuple_element' |
+ 'has_map_field' |
+ 'is_nonempty_list' | 'is_tagged_tuple' |
+ 'kill_try_tag' |
+ 'landingpad' |
+ 'make_fun' | 'new_try_tag' |
+ 'peek_message' | 'phi' | 'put_list' | 'put_map' | 'put_tuple' |
+ 'raw_raise' | 'recv_next' | 'remove_message' | 'resume' |
+ 'succeeded' |
+ 'timeout' |
+ 'wait' | 'wait_timeout'.
+
+-type float_op() :: 'checkerror' | 'clearerror' | 'convert' | 'get' | 'put' |
+ '+' | '-' | '*' | '/'.
+
+%% Primops only used internally during code generation.
+-type cg_prim_op() :: 'bs_get' | 'bs_match_string' | 'bs_restore' | 'bs_skip' |
+ 'copy' | 'put_tuple_arity' | 'put_tuple_element' |
+ 'set_tuple_element'.
+
+-import(lists, [foldl/3,keyfind/3,mapfoldl/3,member/2,reverse/1]).
+
+-spec add_anno(Key, Value, Construct) -> Construct when
+ Key :: atom(),
+ Value :: any(),
+ Construct :: construct().
+
+add_anno(Key, Val, #b_function{anno=Anno}=Bl) ->
+ Bl#b_function{anno=Anno#{Key=>Val}};
+add_anno(Key, Val, #b_blk{anno=Anno}=Bl) ->
+ Bl#b_blk{anno=Anno#{Key=>Val}};
+add_anno(Key, Val, #b_set{anno=Anno}=Bl) ->
+ Bl#b_set{anno=Anno#{Key=>Val}};
+add_anno(Key, Val, #b_br{anno=Anno}=Bl) ->
+ Bl#b_br{anno=Anno#{Key=>Val}};
+add_anno(Key, Val, #b_ret{anno=Anno}=Bl) ->
+ Bl#b_ret{anno=Anno#{Key=>Val}};
+add_anno(Key, Val, #b_switch{anno=Anno}=Bl) ->
+ Bl#b_switch{anno=Anno#{Key=>Val}}.
+
+-spec get_anno(atom(), construct()) -> any().
+
+get_anno(Key, Construct) ->
+ map_get(Key, get_anno(Construct)).
+
+-spec get_anno(atom(), construct(),any()) -> any().
+
+get_anno(Key, Construct, Default) ->
+ maps:get(Key, get_anno(Construct), Default).
+
+get_anno(#b_function{anno=Anno}) -> Anno;
+get_anno(#b_blk{anno=Anno}) -> Anno;
+get_anno(#b_set{anno=Anno}) -> Anno;
+get_anno(#b_br{anno=Anno}) -> Anno;
+get_anno(#b_ret{anno=Anno}) -> Anno;
+get_anno(#b_switch{anno=Anno}) -> Anno.
+
+%% clobbers_xregs(#b_set{}) -> true|false.
+%% Test whether the instruction invalidates all X registers.
+
+-spec clobbers_xregs(b_set()) -> boolean().
+
+clobbers_xregs(#b_set{op=Op}) ->
+ case Op of
+ bs_init_writable -> true;
+ build_stacktrace -> true;
+ call -> true;
+ landingpad -> true;
+ make_fun -> true;
+ peek_message -> true;
+ raw_raise -> true;
+ _ -> false
+ end.
+
+%% no_side_effect(#b_set{}) -> true|false.
+%% Test whether this instruction has no side effect and thus is safe
+%% not to execute if its value is not used. Note that even if `true`
+%% is returned, the instruction could still be impure (e.g. bif:get).
+
+-spec no_side_effect(b_set()) -> boolean().
+
+no_side_effect(#b_set{op=Op}) ->
+ case Op of
+ {bif,_} -> true;
+ {float,get} -> true;
+ bs_init -> true;
+ bs_extract -> true;
+ bs_match -> true;
+ bs_start_match -> true;
+ bs_test_tail -> true;
+ bs_get_tail -> true;
+ bs_put -> true;
+ extract -> true;
+ get_hd -> true;
+ get_tl -> true;
+ get_map_element -> true;
+ get_tuple_element -> true;
+ has_map_field -> true;
+ is_nonempty_list -> true;
+ is_tagged_tuple -> true;
+ make_fun -> true;
+ put_map -> true;
+ put_list -> true;
+ put_tuple -> true;
+ succeeded -> true;
+ _ -> false
+ end.
+
+-spec predecessors(Blocks) -> #{BlockNumber:=[Predecessor]} when
+ Blocks :: block_map(),
+ BlockNumber :: label(),
+ Predecessor :: label().
+
+predecessors(Blocks) ->
+ P0 = [{S,L} || {L,Blk} <- maps:to_list(Blocks),
+ S <- successors(Blk)],
+ P1 = sofs:relation(P0),
+ P2 = sofs:rel2fam(P1),
+ P3 = sofs:to_external(P2),
+ P = [{0,[]}|P3],
+ maps:from_list(P).
+
+-spec successors(b_blk()) -> [label()].
+
+successors(#b_blk{last=Terminator}) ->
+ case Terminator of
+ #b_br{bool=#b_literal{val=true},succ=Succ} ->
+ [Succ];
+ #b_br{bool=#b_literal{val=false},fail=Fail} ->
+ [Fail];
+ #b_br{succ=Succ,fail=Fail} ->
+ [Fail,Succ];
+ #b_switch{fail=Fail,list=List} ->
+ [Fail|[L || {_,L} <- List]];
+ #b_ret{} ->
+ []
+ end.
+
+%% normalize(Instr0) -> Instr.
+%% Normalize instructions to help optimizations.
+%%
+%% For commutative operators (such as '+' and 'or'), always
+%% place a variable operand before a literal operand.
+%%
+%% Normalize #b_br{} to one of the following forms:
+%%
+%% #b_br{b_literal{val=true},succ=Label,fail=Label}
+%% #b_br{b_var{},succ=Label1,fail=Label2} where Label1 =/= Label2
+%%
+%% Simplify a #b_switch{} with a literal argument to a #b_br{}.
+%%
+%% Simplify a #b_switch{} with a variable argument and an empty
+%% switch list to a #b_br{}.
+
+-spec normalize(b_set() | terminator()) ->
+ b_set() | terminator().
+
+normalize(#b_set{op={bif,Bif},args=Args}=Set) ->
+ case {is_commutative(Bif),Args} of
+ {false,_} ->
+ Set;
+ {true,[#b_literal{}=Lit,#b_var{}=Var]} ->
+ Set#b_set{args=[Var,Lit]};
+ {true,_} ->
+ Set
+ end;
+normalize(#b_set{}=Set) ->
+ Set;
+normalize(#b_br{}=Br) ->
+ case Br of
+ #b_br{bool=Bool,succ=Same,fail=Same} ->
+ case Bool of
+ #b_literal{val=true} ->
+ Br;
+ _ ->
+ Br#b_br{bool=#b_literal{val=true}}
+ end;
+ #b_br{bool=#b_literal{val=true},succ=Succ} ->
+ Br#b_br{fail=Succ};
+ #b_br{bool=#b_literal{val=false},fail=Fail} ->
+ Br#b_br{bool=#b_literal{val=true},succ=Fail};
+ #b_br{} ->
+ Br
+ end;
+normalize(#b_switch{arg=Arg,fail=Fail,list=List}=Sw) ->
+ case Arg of
+ #b_literal{} ->
+ case keyfind(Arg, 1, List) of
+ false ->
+ #b_br{bool=#b_literal{val=true},succ=Fail,fail=Fail};
+ {Arg,L} ->
+ #b_br{bool=#b_literal{val=true},succ=L,fail=L}
+ end;
+ #b_var{} when List =:= [] ->
+ #b_br{bool=#b_literal{val=true},succ=Fail,fail=Fail};
+ #b_var{} ->
+ Sw
+ end;
+normalize(#b_ret{}=Ret) ->
+ Ret.
+
+-spec successors(label(), block_map()) -> [label()].
+
+successors(L, Blocks) ->
+ successors(map_get(L, Blocks)).
+
+-spec def(Ls, Blocks) -> Def when
+ Ls :: [label()],
+ Blocks :: block_map(),
+ Def :: ordsets:ordset(var_name()).
+
+def(Ls, Blocks) ->
+ Top = rpo(Ls, Blocks),
+ Blks = [map_get(L, Blocks) || L <- Top],
+ def_1(Blks, []).
+
+-spec def_used(Ls, Blocks) -> {Def,Used} when
+ Ls :: [label()],
+ Blocks :: block_map(),
+ Def :: ordsets:ordset(var_name()),
+ Used :: ordsets:ordset(var_name()).
+
+def_used(Ls, Blocks) ->
+ Top = rpo(Ls, Blocks),
+ Blks = [map_get(L, Blocks) || L <- Top],
+ Preds = cerl_sets:from_list(Top),
+ def_used_1(Blks, Preds, [], []).
+
+%% dominators(BlockMap) -> {Dominators,Numbering}.
+%% Calculate the dominator tree, returning a map where each entry
+%% in the map is a list that gives the path from that block to
+%% the top of the dominator tree. (Note that the suffixes of the
+%% paths are shared with each other, which make the representation
+%% of the dominator tree highly memory-efficient.)
+%%
+%% The implementation is based on:
+%%
+%% http://www.hipersoft.rice.edu/grads/publications/dom14.pdf
+%% Cooper, Keith D.; Harvey, Timothy J; Kennedy, Ken (2001).
+%% A Simple, Fast Dominance Algorithm.
+
+-spec dominators(Blocks) -> Result when
+ Blocks :: block_map(),
+ Result :: {dominator_map(), numbering_map()}.
+dominators(Blocks) ->
+ Preds = predecessors(Blocks),
+ Top0 = rpo(Blocks),
+ Df = maps:from_list(number(Top0, 0)),
+ [{0,[]}|Top] = [{L,map_get(L, Preds)} || L <- Top0],
+
+ %% The flow graph for an Erlang function is reducible, and
+ %% therefore one traversal in reverse postorder is sufficient.
+ Acc = #{0=>[0]},
+ {dominators_1(Top, Df, Acc),Df}.
+
+%% common_dominators([Label], Dominators, Numbering) -> [Label].
+%% Calculate the common dominators for the given list of blocks
+%% and Dominators and Numbering as returned from dominators/1.
+
+-spec common_dominators([label()], dominator_map(), numbering_map()) -> [label()].
+common_dominators(Ls, Dom, Numbering) ->
+ Doms = [map_get(L, Dom) || L <- Ls],
+ dom_intersection(Doms, Numbering).
+
+-spec fold_instrs_rpo(Fun, From, Acc0, Blocks) -> any() when
+ Fun :: fun((b_blk()|terminator(), any()) -> any()),
+ From :: [label()],
+ Acc0 :: any(),
+ Blocks :: block_map().
+
+fold_instrs_rpo(Fun, From, Acc0, Blocks) ->
+ Top = rpo(From, Blocks),
+ fold_instrs_rpo_1(Top, Fun, Blocks, Acc0).
+
+%% Like mapfold_instrs_rpo but at the block level to support lookahead and
+%% scope-dependent transformations.
+-spec mapfold_blocks_rpo(Fun, From, Acc, Blocks) -> Result when
+ Fun :: fun((label(), b_blk(), any()) -> {b_blk(), any()}),
+ From :: [label()],
+ Acc :: any(),
+ Blocks :: block_map(),
+ Result :: {block_map(), any()}.
+mapfold_blocks_rpo(Fun, From, Acc, Blocks) ->
+ Successors = rpo(From, Blocks),
+ foldl(fun(Lbl, A) ->
+ mapfold_blocks_rpo_1(Fun, Lbl, A)
+ end, {Blocks, Acc}, Successors).
+
+mapfold_blocks_rpo_1(Fun, Lbl, {Blocks0, Acc0}) ->
+ Block0 = map_get(Lbl, Blocks0),
+ {Block, Acc} = Fun(Lbl, Block0, Acc0),
+ Blocks = Blocks0#{Lbl:=Block},
+ {Blocks, Acc}.
+
+-spec mapfold_instrs_rpo(Fun, From, Acc0, Blocks0) -> {Blocks,Acc} when
+ Fun :: fun((b_blk()|terminator(), any()) -> any()),
+ From :: [label()],
+ Acc0 :: any(),
+ Acc :: any(),
+ Blocks0 :: block_map(),
+ Blocks :: block_map().
+
+mapfold_instrs_rpo(Fun, From, Acc0, Blocks) ->
+ Top = rpo(From, Blocks),
+ mapfold_instrs_rpo_1(Top, Fun, Blocks, Acc0).
+
+-spec flatmapfold_instrs_rpo(Fun, From, Acc0, Blocks0) -> {Blocks,Acc} when
+ Fun :: fun((b_blk()|terminator(), any()) -> any()),
+ From :: [label()],
+ Acc0 :: any(),
+ Acc :: any(),
+ Blocks0 :: block_map(),
+ Blocks :: block_map().
+
+flatmapfold_instrs_rpo(Fun, From, Acc0, Blocks) ->
+ Top = rpo(From, Blocks),
+ flatmapfold_instrs_rpo_1(Top, Fun, Blocks, Acc0).
+
+-type fold_fun() :: fun((label(), b_blk(), any()) -> any()).
+
+%% fold_rpo(Fun, [Label], Acc0, Blocks) -> Acc.
+%% Fold over all blocks a reverse postorder traversal of the block
+%% graph; that is, first visit a block, then visit its successors.
+
+-spec fold_rpo(Fun, Acc0, Blocks) -> any() when
+ Fun :: fold_fun(),
+ Acc0 :: any(),
+ Blocks :: #{label():=b_blk()}.
+
+fold_rpo(Fun, Acc0, Blocks) ->
+ fold_rpo(Fun, [0], Acc0, Blocks).
+
+%% fold_rpo(Fun, [Label], Acc0, Blocks) -> Acc. Fold over all blocks
+%% reachable from a given set of labels in a reverse postorder
+%% traversal of the block graph; that is, first visit a block, then
+%% visit its successors.
+
+-spec fold_rpo(Fun, Labels, Acc0, Blocks) -> any() when
+ Fun :: fold_fun(),
+ Labels :: [label()],
+ Acc0 :: any(),
+ Blocks :: #{label():=b_blk()}.
+
+fold_rpo(Fun, From, Acc0, Blocks) ->
+ Top = rpo(From, Blocks),
+ fold_rpo_1(Top, Fun, Blocks, Acc0).
+
+%% fold_po(Fun, Acc0, Blocks) -> Acc.
+%% Fold over all blocks in a postorder traversal of the block graph;
+%% that is, first visit all successors of block, then the block
+%% itself.
+
+-spec fold_po(Fun, Acc0, Blocks) -> any() when
+ Fun :: fold_fun(),
+ Acc0 :: any(),
+ Blocks :: #{label():=b_blk()}.
+
+%% fold_po(Fun, From, Acc0, Blocks) -> Acc.
+%% Fold over the blocks reachable from the block numbers given
+%% by From in a postorder traversal of the block graph.
+
+fold_po(Fun, Acc0, Blocks) ->
+ fold_po(Fun, [0], Acc0, Blocks).
+
+-spec fold_po(Fun, Labels, Acc0, Blocks) -> any() when
+ Fun :: fold_fun(),
+ Labels :: [label()],
+ Acc0 :: any(),
+ Blocks :: block_map().
+
+fold_po(Fun, From, Acc0, Blocks) ->
+ Top = reverse(rpo(From, Blocks)),
+ fold_rpo_1(Top, Fun, Blocks, Acc0).
+
+%% linearize(Blocks) -> [{BlockLabel,#b_blk{}}].
+%% Linearize the intermediate representation of the code.
+%% Unreachable blocks will be discarded, and phi nodes will
+%% be adjusted so that they no longer refers to discarded
+%% blocks or to blocks that no longer are predecessors of
+%% the phi node block.
+
+-spec linearize(Blocks) -> Linear when
+ Blocks :: block_map(),
+ Linear :: [{label(),b_blk()}].
+
+linearize(Blocks) ->
+ Seen = cerl_sets:new(),
+ {Linear0,_} = linearize_1([0], Blocks, Seen, []),
+ Linear = fix_phis(Linear0, #{}),
+ Linear.
+
+-spec rpo(Blocks) -> [Label] when
+ Blocks :: block_map(),
+ Label :: label().
+
+rpo(Blocks) ->
+ rpo([0], Blocks).
+
+-spec rpo(From, Blocks) -> Labels when
+ From :: [label()],
+ Blocks :: block_map(),
+ Labels :: [label()].
+
+rpo(From, Blocks) ->
+ Seen = cerl_sets:new(),
+ {Ls,_} = rpo_1(From, Blocks, Seen, []),
+ Ls.
+
+-spec rename_vars(Rename, [label()], block_map()) -> block_map() when
+ Rename :: rename_map() | rename_proplist().
+
+rename_vars(Rename, From, Blocks) when is_list(Rename) ->
+ rename_vars(maps:from_list(Rename), From, Blocks);
+rename_vars(Rename, From, Blocks) when is_map(Rename)->
+ Top = rpo(From, Blocks),
+ Preds = cerl_sets:from_list(Top),
+ F = fun(#b_set{op=phi,args=Args0}=Set) ->
+ Args = rename_phi_vars(Args0, Preds, Rename),
+ Set#b_set{args=Args};
+ (#b_set{args=Args0}=Set) ->
+ Args = [rename_var(A, Rename) || A <- Args0],
+ Set#b_set{args=Args};
+ (#b_switch{arg=Bool}=Sw) ->
+ Sw#b_switch{arg=rename_var(Bool, Rename)};
+ (#b_br{bool=Bool}=Br) ->
+ Br#b_br{bool=rename_var(Bool, Rename)};
+ (#b_ret{arg=Arg}=Ret) ->
+ Ret#b_ret{arg=rename_var(Arg, Rename)}
+ end,
+ map_instrs_1(Top, F, Blocks).
+
+%% split_blocks(Predicate, Blocks0, Count0) -> {Blocks,Count}.
+%% Call Predicate(Instruction) for each instruction in all
+%% blocks. If Predicate/1 returns true, split the block
+%% before this instruction.
+
+-spec split_blocks(Pred, Blocks0, Count0) -> {Blocks,Count} when
+ Pred :: fun((b_set()) -> boolean()),
+ Blocks :: block_map(),
+ Count0 :: beam_ssa:label(),
+ Blocks0 :: block_map(),
+ Blocks :: block_map(),
+ Count :: beam_ssa:label().
+
+split_blocks(P, Blocks, Count) ->
+ Ls = beam_ssa:rpo(Blocks),
+ split_blocks_1(Ls, P, Blocks, Count).
+
+-spec trim_unreachable(Blocks0) -> Blocks when
+ Blocks0 :: block_map(),
+ Blocks :: block_map().
+
+%% trim_unreachable(Blocks0) -> Blocks.
+%% Remove all unreachable blocks. Adjust all phi nodes so
+%% they don't refer to blocks that has been removed or no
+%% no longer branch to the phi node in question.
+
+trim_unreachable(Blocks) ->
+ %% Could perhaps be optimized if there is any need.
+ maps:from_list(linearize(Blocks)).
+
+%% update_phi_labels([BlockLabel], Old, New, Blocks0) -> Blocks.
+%% In the given blocks, replace label Old in with New in all
+%% phi nodes. This is useful after merging or splitting
+%% blocks.
+
+-spec update_phi_labels(From, Old, New, Blocks0) -> Blocks when
+ From :: [label()],
+ Old :: label(),
+ New :: label(),
+ Blocks0 :: block_map(),
+ Blocks :: block_map().
+
+update_phi_labels([L|Ls], Old, New, Blocks0) ->
+ case Blocks0 of
+ #{L:=#b_blk{is=[#b_set{op=phi}|_]=Is0}=Blk0} ->
+ Is = update_phi_labels_is(Is0, Old, New),
+ Blk = Blk0#b_blk{is=Is},
+ Blocks = Blocks0#{L:=Blk},
+ update_phi_labels(Ls, Old, New, Blocks);
+ #{L:=#b_blk{}} ->
+ %% No phi nodes in this block.
+ update_phi_labels(Ls, Old, New, Blocks0)
+ end;
+update_phi_labels([], _, _, Blocks) -> Blocks.
+
+-spec used(b_blk() | b_set() | terminator()) -> [var_name()].
+
+used(#b_blk{is=Is,last=Last}) ->
+ used_1([Last|Is], ordsets:new());
+used(#b_br{bool=#b_var{}=V}) ->
+ [V];
+used(#b_ret{arg=#b_var{}=V}) ->
+ [V];
+used(#b_set{op=phi,args=Args}) ->
+ ordsets:from_list([V || {#b_var{}=V,_} <- Args]);
+used(#b_set{args=Args}) ->
+ ordsets:from_list(used_args(Args));
+used(#b_switch{arg=#b_var{}=V}) ->
+ [V];
+used(_) -> [].
+
+-spec definitions(Blocks :: block_map()) -> definition_map().
+definitions(Blocks) ->
+ fold_instrs_rpo(fun(#b_set{ dst = Var }=I, Acc) ->
+ Acc#{Var => I};
+ (_Terminator, Acc) ->
+ Acc
+ end, [0], #{}, Blocks).
+
+-spec uses(Blocks :: block_map()) -> usage_map().
+uses(Blocks) ->
+ uses([0], Blocks).
+
+-spec uses(From, Blocks) -> usage_map() when
+ From :: [label()],
+ Blocks :: block_map().
+uses(From, Blocks) ->
+ fold_rpo(fun fold_uses_block/3, From, #{}, Blocks).
+
+fold_uses_block(Lbl, #b_blk{is=Is,last=Last}, UseMap0) ->
+ F = fun(I, UseMap) ->
+ foldl(fun(Var, Acc) ->
+ Uses0 = maps:get(Var, Acc, []),
+ Uses = [{Lbl, I} | Uses0],
+ maps:put(Var, Uses, Acc)
+ end, UseMap, used(I))
+ end,
+ F(Last, foldl(F, UseMap0, Is)).
+
+%%%
+%%% Internal functions.
+%%%
+
+is_commutative('and') -> true;
+is_commutative('or') -> true;
+is_commutative('xor') -> true;
+is_commutative('band') -> true;
+is_commutative('bor') -> true;
+is_commutative('bxor') -> true;
+is_commutative('+') -> true;
+is_commutative('*') -> true;
+is_commutative('=:=') -> true;
+is_commutative('==') -> true;
+is_commutative('=/=') -> true;
+is_commutative('/=') -> true;
+is_commutative(_) -> false.
+
+def_used_1([#b_blk{is=Is,last=Last}|Bs], Preds, Def0, Used0) ->
+ {Def,Used1} = def_used_is(Is, Preds, Def0, Used0),
+ Used = ordsets:union(used(Last), Used1),
+ def_used_1(Bs, Preds, Def, Used);
+def_used_1([], _Preds, Def, Used) ->
+ {ordsets:from_list(Def),Used}.
+
+def_used_is([#b_set{op=phi,dst=Dst,args=Args}|Is],
+ Preds, Def0, Used0) ->
+ Def = [Dst|Def0],
+ %% We must be careful to only include variables that will
+ %% be used when arriving from one of the predecessor blocks
+ %% in Preds.
+ Used1 = [V || {#b_var{}=V,L} <- Args, cerl_sets:is_element(L, Preds)],
+ Used = ordsets:union(ordsets:from_list(Used1), Used0),
+ def_used_is(Is, Preds, Def, Used);
+def_used_is([#b_set{dst=Dst}=I|Is], Preds, Def0, Used0) ->
+ Def = [Dst|Def0],
+ Used = ordsets:union(used(I), Used0),
+ def_used_is(Is, Preds, Def, Used);
+def_used_is([], _Preds, Def, Used) ->
+ {Def,Used}.
+
+def_1([#b_blk{is=Is}|Bs], Def0) ->
+ Def = def_is(Is, Def0),
+ def_1(Bs, Def);
+def_1([], Def) ->
+ ordsets:from_list(Def).
+
+def_is([#b_set{dst=Dst}|Is], Def) ->
+ def_is(Is, [Dst|Def]);
+def_is([], Def) -> Def.
+
+dominators_1([{L,Preds}|Ls], Df, Doms) ->
+ DomPreds = [map_get(P, Doms) || P <- Preds, is_map_key(P, Doms)],
+ Dom = [L|dom_intersection(DomPreds, Df)],
+ dominators_1(Ls, Df, Doms#{L=>Dom});
+dominators_1([], _Df, Doms) -> Doms.
+
+dom_intersection([S], _Df) ->
+ S;
+dom_intersection([S|Ss], Df) ->
+ dom_intersection(S, Ss, Df).
+
+dom_intersection(S1, [S2|Ss], Df) ->
+ dom_intersection(dom_intersection_1(S1, S2, Df), Ss, Df);
+dom_intersection(S, [], _Df) -> S.
+
+dom_intersection_1([E1|Es1]=Set1, [E2|Es2]=Set2, Df) ->
+ %% Blocks are numbered in the order they are found in
+ %% reverse postorder.
+ #{E1:=Df1,E2:=Df2} = Df,
+ if Df1 > Df2 ->
+ dom_intersection_1(Es1, Set2, Df);
+ Df2 > Df1 ->
+ dom_intersection_1(Es2, Set1, Df); %switch arguments!
+ true -> %Set1 == Set2
+ %% The common suffix of the sets is the intersection.
+ Set1
+ end.
+
+number([L|Ls], N) ->
+ [{L,N}|number(Ls, N+1)];
+number([], _) -> [].
+
+fold_rpo_1([L|Ls], Fun, Blocks, Acc0) ->
+ Block = map_get(L, Blocks),
+ Acc = Fun(L, Block, Acc0),
+ fold_rpo_1(Ls, Fun, Blocks, Acc);
+fold_rpo_1([], _, _, Acc) -> Acc.
+
+fold_instrs_rpo_1([L|Ls], Fun, Blocks, Acc0) ->
+ #b_blk{is=Is,last=Last} = map_get(L, Blocks),
+ Acc1 = foldl(Fun, Acc0, Is),
+ Acc = Fun(Last, Acc1),
+ fold_instrs_rpo_1(Ls, Fun, Blocks, Acc);
+fold_instrs_rpo_1([], _, _, Acc) -> Acc.
+
+mapfold_instrs_rpo_1([L|Ls], Fun, Blocks0, Acc0) ->
+ #b_blk{is=Is0,last=Last0} = Block0 = map_get(L, Blocks0),
+ {Is,Acc1} = mapfoldl(Fun, Acc0, Is0),
+ {Last,Acc} = Fun(Last0, Acc1),
+ Block = Block0#b_blk{is=Is,last=Last},
+ Blocks = Blocks0#{L:=Block},
+ mapfold_instrs_rpo_1(Ls, Fun, Blocks, Acc);
+mapfold_instrs_rpo_1([], _, Blocks, Acc) ->
+ {Blocks,Acc}.
+
+flatmapfold_instrs_rpo_1([L|Ls], Fun, Blocks0, Acc0) ->
+ #b_blk{is=Is0,last=Last0} = Block0 = map_get(L, Blocks0),
+ {Is,Acc1} = flatmapfoldl(Fun, Acc0, Is0),
+ {[Last],Acc} = Fun(Last0, Acc1),
+ Block = Block0#b_blk{is=Is,last=Last},
+ Blocks = Blocks0#{L:=Block},
+ flatmapfold_instrs_rpo_1(Ls, Fun, Blocks, Acc);
+flatmapfold_instrs_rpo_1([], _, Blocks, Acc) ->
+ {Blocks,Acc}.
+
+linearize_1([L|Ls], Blocks, Seen0, Acc0) ->
+ case cerl_sets:is_element(L, Seen0) of
+ true ->
+ linearize_1(Ls, Blocks, Seen0, Acc0);
+ false ->
+ Seen1 = cerl_sets:add_element(L, Seen0),
+ Block = map_get(L, Blocks),
+ Successors = successors(Block),
+ {Acc,Seen} = linearize_1(Successors, Blocks, Seen1, Acc0),
+ linearize_1(Ls, Blocks, Seen, [{L,Block}|Acc])
+ end;
+linearize_1([], _, Seen, Acc) ->
+ {Acc,Seen}.
+
+fix_phis([{L,Blk0}|Bs], S) ->
+ Blk = case Blk0 of
+ #b_blk{is=[#b_set{op=phi}|_]=Is0} ->
+ Is = fix_phis_1(Is0, L, S),
+ Blk0#b_blk{is=Is};
+ #b_blk{} ->
+ Blk0
+ end,
+ Successors = successors(Blk),
+ [{L,Blk}|fix_phis(Bs, S#{L=>Successors})];
+fix_phis([], _) -> [].
+
+fix_phis_1([#b_set{op=phi,args=Args0}=I|Is], L, S) ->
+ Args = [{Val,Pred} || {Val,Pred} <- Args0,
+ is_successor(L, Pred, S)],
+ [I#b_set{args=Args}|fix_phis_1(Is, L, S)];
+fix_phis_1(Is, _, _) -> Is.
+
+is_successor(L, Pred, S) ->
+ case S of
+ #{Pred:=Successors} ->
+ member(L, Successors);
+ #{} ->
+ %% This block has been removed.
+ false
+ end.
+
+rpo_1([L|Ls], Blocks, Seen0, Acc0) ->
+ case cerl_sets:is_element(L, Seen0) of
+ true ->
+ rpo_1(Ls, Blocks, Seen0, Acc0);
+ false ->
+ Block = map_get(L, Blocks),
+ Seen1 = cerl_sets:add_element(L, Seen0),
+ Successors = successors(Block),
+ {Acc,Seen} = rpo_1(Successors, Blocks, Seen1, Acc0),
+ rpo_1(Ls, Blocks, Seen, [L|Acc])
+ end;
+rpo_1([], _, Seen, Acc) ->
+ {Acc,Seen}.
+
+rename_var(#b_var{}=Old, Rename) ->
+ case Rename of
+ #{Old:=New} -> New;
+ #{} -> Old
+ end;
+rename_var(#b_remote{mod=Mod0,name=Name0}=Remote, Rename) ->
+ Mod = rename_var(Mod0, Rename),
+ Name = rename_var(Name0, Rename),
+ Remote#b_remote{mod=Mod,name=Name};
+rename_var(Old, _) -> Old.
+
+rename_phi_vars([{Var,L}|As], Preds, Ren) ->
+ case cerl_sets:is_element(L, Preds) of
+ true ->
+ [{rename_var(Var, Ren),L}|rename_phi_vars(As, Preds, Ren)];
+ false ->
+ [{Var,L}|rename_phi_vars(As, Preds, Ren)]
+ end;
+rename_phi_vars([], _, _) -> [].
+
+map_instrs_1([L|Ls], Fun, Blocks0) ->
+ #b_blk{is=Is0,last=Last0} = Blk0 = map_get(L, Blocks0),
+ Is = [Fun(I) || I <- Is0],
+ Last = Fun(Last0),
+ Blk = Blk0#b_blk{is=Is,last=Last},
+ Blocks = Blocks0#{L:=Blk},
+ map_instrs_1(Ls, Fun, Blocks);
+map_instrs_1([], _, Blocks) -> Blocks.
+
+flatmapfoldl(F, Accu0, [Hd|Tail]) ->
+ {R,Accu1} = F(Hd, Accu0),
+ {Rs,Accu2} = flatmapfoldl(F, Accu1, Tail),
+ {R++Rs,Accu2};
+flatmapfoldl(_, Accu, []) -> {[],Accu}.
+
+split_blocks_1([L|Ls], P, Blocks0, Count0) ->
+ #b_blk{is=Is0} = Blk = map_get(L, Blocks0),
+ case split_blocks_is(Is0, P, []) of
+ {yes,Bef,Aft} ->
+ NewLbl = Count0,
+ Count = Count0 + 1,
+ Br = #b_br{bool=#b_literal{val=true},succ=NewLbl,fail=NewLbl},
+ BefBlk = Blk#b_blk{is=Bef,last=Br},
+ NewBlk = Blk#b_blk{is=Aft},
+ Blocks1 = Blocks0#{L:=BefBlk,NewLbl=>NewBlk},
+ Successors = successors(NewBlk),
+ Blocks = update_phi_labels(Successors, L, NewLbl, Blocks1),
+ split_blocks_1([NewLbl|Ls], P, Blocks, Count);
+ no ->
+ split_blocks_1(Ls, P, Blocks0, Count0)
+ end;
+split_blocks_1([], _, Blocks, Count) ->
+ {Blocks,Count}.
+
+split_blocks_is([I|Is], P, []) ->
+ split_blocks_is(Is, P, [I]);
+split_blocks_is([I|Is], P, Acc) ->
+ case P(I) of
+ true ->
+ {yes,reverse(Acc),[I|Is]};
+ false ->
+ split_blocks_is(Is, P, [I|Acc])
+ end;
+split_blocks_is([], _, _) -> no.
+
+update_phi_labels_is([#b_set{op=phi,args=Args0}=I0|Is], Old, New) ->
+ Args = [{Arg,rename_label(Lbl, Old, New)} || {Arg,Lbl} <- Args0],
+ I = I0#b_set{args=Args},
+ [I|update_phi_labels_is(Is, Old, New)];
+update_phi_labels_is(Is, _, _) -> Is.
+
+rename_label(Old, Old, New) -> New;
+rename_label(Lbl, _Old, _New) -> Lbl.
+
+used_args([#b_var{}=V|As]) ->
+ [V|used_args(As)];
+used_args([#b_remote{mod=Mod,name=Name}|As]) ->
+ used_args([Mod,Name|As]);
+used_args([_|As]) ->
+ used_args(As);
+used_args([]) -> [].
+
+used_1([H|T], Used0) ->
+ Used = ordsets:union(used(H), Used0),
+ used_1(T, Used);
+used_1([], Used) -> Used.
diff --git a/lib/compiler/src/beam_ssa.hrl b/lib/compiler/src/beam_ssa.hrl
new file mode 100644
index 0000000000..fa76b08453
--- /dev/null
+++ b/lib/compiler/src/beam_ssa.hrl
@@ -0,0 +1,66 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018. All 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%
+%%
+
+-record(b_module, {anno=#{} :: beam_ssa:anno(),
+ name :: module(),
+ exports :: [{atom(),arity()}],
+ attributes :: list(),
+ body :: [beam_ssa:b_function()]}).
+-record(b_function, {anno=#{} :: beam_ssa:anno(),
+ args :: [beam_ssa:b_var()],
+ bs :: #{beam_ssa:label():=beam_ssa:b_blk()},
+ cnt :: beam_ssa:label()}).
+
+-record(b_blk, {anno=#{} :: beam_ssa:anno(),
+ is :: [beam_ssa:b_set()],
+ last :: beam_ssa:terminator()}).
+-record(b_set, {anno=#{} :: beam_ssa:anno(),
+ dst=none :: 'none'|beam_ssa:b_var(),
+ op :: beam_ssa:op(),
+ args=[] :: [beam_ssa:argument()]}).
+
+%% Terminators.
+-record(b_ret, {anno=#{} :: beam_ssa:anno(),
+ arg :: beam_ssa:value()}).
+
+-record(b_br, {anno=#{},
+ bool :: beam_ssa:value(),
+ succ :: beam_ssa:label(),
+ fail :: beam_ssa:label()}).
+
+-record(b_switch, {anno=#{} :: beam_ssa:anno(),
+ arg :: beam_ssa:value(),
+ fail :: beam_ssa:label(),
+ list :: [{beam_ssa:b_literal(),beam_ssa:label()}]}).
+
+%% Values.
+-record(b_var, {name :: beam_ssa:var_name()}).
+
+-record(b_literal, {val :: beam_ssa:literal_value()}).
+
+-record(b_remote, {mod :: beam_ssa:value(),
+ name :: beam_ssa:value(),
+ arity :: non_neg_integer()}).
+
+-record(b_local, {name :: beam_ssa:b_literal(),
+ arity :: non_neg_integer()}).
+
+%% If this block exists, it calls erlang:error(badarg).
+-define(BADARG_BLOCK, 1).
diff --git a/lib/compiler/src/beam_ssa_bsm.erl b/lib/compiler/src/beam_ssa_bsm.erl
new file mode 100644
index 0000000000..382e6f635e
--- /dev/null
+++ b/lib/compiler/src/beam_ssa_bsm.erl
@@ -0,0 +1,1046 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018. All 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 pass optimizes bit syntax matching, and is centered around the concept
+%%% of "match context reuse" which is best explained through example. To put it
+%%% shortly we attempt to turn this:
+%%%
+%%% <<0,B/bits>> = A,
+%%% <<1,C/bits>> = B,
+%%% <<D,_/bits>> = C,
+%%% D.
+%%%
+%%% ... Into this:
+%%%
+%%% <<0,1,D,_/bits>>=A,
+%%% D.
+%%%
+%%% Which is much faster as it avoids the creation of intermediate terms. This
+%%% is especially noticeable in loops where such garbage is generated on each
+%%% iteration.
+%%%
+%%% The optimization itself is very simple and can be applied whenever there's
+%%% matching on the tail end of a binary; instead of creating a new binary and
+%%% starting a new match context on it, we reuse the match context used to
+%%% extract the tail and avoid the creation of both objects.
+%%%
+%%% The catch is that a match context isn't a proper type and nothing outside
+%%% of bit syntax match operations can handle them. We therefore need to make
+%%% sure that they never "leak" into other instructions, and most of the pass
+%%% revolves around getting around that limitation.
+%%%
+%%% Unlike most other passes we look at the whole module so we can combine
+%%% matches across function boundaries, greatly increasing the performance of
+%%% complex matches and loops.
+%%%
+
+-module(beam_ssa_bsm).
+
+-export([module/2, format_error/1]).
+
+-include("beam_ssa.hrl").
+
+-import(lists, [member/2, reverse/1, splitwith/2, map/2, foldl/3, mapfoldl/3,
+ nth/2, max/1, unzip/1]).
+
+-spec format_error(term()) -> nonempty_string().
+
+format_error(OptInfo) ->
+ format_opt_info(OptInfo).
+
+-spec module(Module, Options) -> Result when
+ Module :: beam_ssa:b_module(),
+ Options :: [compile:option()],
+ Result :: {ok, beam_ssa:b_module(), list()}.
+
+-define(PASS(N), {N,fun N/1}).
+
+module(#b_module{body=Fs0}=Module, Opts) ->
+ ModInfo = analyze_module(Module),
+
+ %% combine_matches is repeated after accept_context_args as the control
+ %% flow changes can enable further optimizations, as in the example below:
+ %%
+ %% a(<<0,X/binary>>) -> a(X);
+ %% a(A) when bit_size(A) =:= 52 -> bar;
+ %% a(<<1,X/binary>>) -> X. %% Match context will be reused here when
+ %% %% when repeated.
+
+ {Fs, _} = compile:run_sub_passes(
+ [?PASS(combine_matches),
+ ?PASS(accept_context_args),
+ ?PASS(combine_matches),
+ ?PASS(allow_context_passthrough),
+ ?PASS(skip_outgoing_tail_extraction),
+ ?PASS(annotate_context_parameters)],
+ {Fs0, ModInfo}),
+
+ Ws = case proplists:get_bool(bin_opt_info, Opts) of
+ true -> collect_opt_info(Fs);
+ false -> []
+ end,
+
+ {ok, Module#b_module{body=Fs}, Ws}.
+
+-type module_info() :: #{ func_id() => func_info() }.
+
+-type func_id() :: {Name :: atom(), Arity :: non_neg_integer()}.
+
+-type func_info() :: #{ has_bsm_ops => boolean(),
+ parameters => [#b_var{}],
+ parameter_info => #{ #b_var{} => param_info() } }.
+
+-type param_info() :: suitable_for_reuse |
+ {Problem :: atom(), Where :: term()}.
+
+-spec analyze_module(#b_module{}) -> module_info().
+
+analyze_module(#b_module{body=Fs}) ->
+ foldl(fun(#b_function{args=Parameters}=F, I) ->
+ FuncInfo = #{ has_bsm_ops => has_bsm_ops(F),
+ parameters => Parameters,
+ parameter_info => #{} },
+ FuncId = get_fa(F),
+ I#{ FuncId => FuncInfo }
+ end, #{}, Fs).
+
+has_bsm_ops(#b_function{bs=Blocks}) ->
+ hbo_blocks(maps:to_list(Blocks)).
+
+hbo_blocks([{_,#b_blk{is=Is}} | Blocks]) ->
+ case hbo_is(Is) of
+ false -> hbo_blocks(Blocks);
+ true -> true
+ end;
+hbo_blocks([]) ->
+ false.
+
+hbo_is([#b_set{op=bs_start_match} | _]) -> true;
+hbo_is([_I | Is]) -> hbo_is(Is);
+hbo_is([]) -> false.
+
+%% Checks whether it's legal to make a call with the given argument as a match
+%% context, returning the param_info() of the relevant parameter.
+-spec check_context_call(#b_set{}, Arg, CtxChain, ModInfo) -> param_info() when
+ Arg :: #b_var{},
+ CtxChain :: [#b_var{}],
+ ModInfo :: module_info().
+check_context_call(#b_set{args=Args}, Arg, CtxChain, ModInfo) ->
+ Aliases = [Arg | CtxChain],
+ ccc_1(Args, Arg, Aliases, ModInfo).
+
+ccc_1([#b_local{}=Call | Args], Ctx, Aliases, ModInfo) ->
+ %% Matching operations assume that their context isn't aliased (as in
+ %% pointer aliasing), so we must reject calls whose arguments contain more
+ %% than one reference to the context.
+ %%
+ %% TODO: Try to fall back to passing binaries in these cases. Partial reuse
+ %% is better than nothing.
+ UseCount = foldl(fun(Arg, C) ->
+ case member(Arg, Aliases) of
+ true -> C + 1;
+ false -> C
+ end
+ end, 0, Args),
+ if
+ UseCount =:= 1 ->
+ #b_local{name=#b_literal{val=Name},arity=Arity} = Call,
+ Callee = {Name, Arity},
+
+ ParamInfo = funcinfo_get(Callee, parameter_info, ModInfo),
+ Parameters = funcinfo_get(Callee, parameters, ModInfo),
+ Parameter = nth(1 + arg_index(Ctx, Args), Parameters),
+
+ case maps:find(Parameter, ParamInfo) of
+ {ok, suitable_for_reuse} ->
+ suitable_for_reuse;
+ {ok, Other} ->
+ {unsuitable_call, {Call, Other}};
+ error ->
+ {no_match_on_entry, Call}
+ end;
+ UseCount > 1 ->
+ {multiple_uses_in_call, Call}
+ end;
+ccc_1([#b_remote{}=Call | _Args], _Ctx, _CtxChain, _ModInfo) ->
+ {remote_call, Call};
+ccc_1([Fun | _Args], _Ctx, _CtxChain, _ModInfo) ->
+ %% TODO: It may be possible to support this in the future for locally
+ %% defined funs, including ones with free variables.
+ {fun_call, Fun}.
+
+%% Returns the index of Var in Args.
+arg_index(Var, Args) -> arg_index_1(Var, Args, 0).
+
+arg_index_1(Var, [Var | _Args], Index) -> Index;
+arg_index_1(Var, [_Arg | Args], Index) -> arg_index_1(Var, Args, Index + 1).
+
+is_tail_binary(#b_set{op=bs_match,args=[#b_literal{val=binary} | Rest]}) ->
+ member(#b_literal{val=all}, Rest);
+is_tail_binary(#b_set{op=bs_get_tail}) ->
+ true;
+is_tail_binary(_) ->
+ false.
+
+is_tail_binary(#b_var{}=Var, Defs) ->
+ case find_match_definition(Var, Defs) of
+ {ok, Def} -> is_tail_binary(Def);
+ _ -> false
+ end;
+is_tail_binary(_Literal, _Defs) ->
+ false.
+
+assert_match_context(#b_var{}=Var, Defs) ->
+ case maps:find(Var, Defs) of
+ {ok, #b_set{op=bs_match,args=[_,#b_var{}=Ctx|_]}} ->
+ assert_match_context(Ctx, Defs);
+ {ok, #b_set{op=bs_start_match}} ->
+ ok
+ end.
+
+find_match_definition(#b_var{}=Var, Defs) ->
+ case maps:find(Var, Defs) of
+ {ok, #b_set{op=bs_extract,args=[Ctx]}} -> maps:find(Ctx, Defs);
+ {ok, #b_set{op=bs_get_tail}=Def} -> {ok, Def};
+ _ -> error
+ end.
+
+%% Returns a list of all contexts that were used to extract Var.
+context_chain_of(#b_var{}=Var, Defs) ->
+ case maps:find(Var, Defs) of
+ {ok, #b_set{op=bs_match,args=[_,#b_var{}=Ctx|_]}} ->
+ [Ctx | context_chain_of(Ctx, Defs)];
+ {ok, #b_set{op=bs_get_tail,args=[Ctx]}} ->
+ [Ctx | context_chain_of(Ctx, Defs)];
+ {ok, #b_set{op=bs_extract,args=[Ctx]}} ->
+ [Ctx | context_chain_of(Ctx, Defs)];
+ _ ->
+ []
+ end.
+
+%% Grabs the match context used to produce the given variable.
+match_context_of(#b_var{}=Var, Defs) ->
+ Ctx = match_context_of_1(Var, Defs),
+ assert_match_context(Ctx, Defs),
+ Ctx.
+
+match_context_of_1(Var, Defs) ->
+ case maps:get(Var, Defs) of
+ #b_set{op=bs_extract,args=[#b_var{}=Ctx0]} ->
+ #b_set{op=bs_match,
+ args=[_,#b_var{}=Ctx|_]} = maps:get(Ctx0, Defs),
+ Ctx;
+ #b_set{op=bs_get_tail,args=[#b_var{}=Ctx]} ->
+ Ctx
+ end.
+
+funcinfo_get(#b_function{}=F, Attribute, ModInfo) ->
+ funcinfo_get(get_fa(F), Attribute, ModInfo);
+funcinfo_get({_,_}=Key, Attribute, ModInfo) ->
+ FuncInfo = maps:get(Key, ModInfo),
+ maps:get(Attribute, FuncInfo).
+
+funcinfo_set(#b_function{}=F, Attribute, Value, ModInfo) ->
+ funcinfo_set(get_fa(F), Attribute, Value, ModInfo);
+funcinfo_set(Key, Attribute, Value, ModInfo) ->
+ FuncInfo = maps:put(Attribute, Value, maps:get(Key, ModInfo, #{})),
+ maps:put(Key, FuncInfo, ModInfo).
+
+get_fa(#b_function{ anno = Anno }) ->
+ {_,Name,Arity} = maps:get(func_info, Anno),
+ {Name,Arity}.
+
+%% Replaces matched-out binaries with aliases that are lazily converted to
+%% binary form when used, allowing us to keep the "match path" free of binary
+%% creation.
+
+-spec alias_matched_binaries(Blocks, Counter, AliasMap) -> Result when
+ Blocks :: beam_ssa:block_map(),
+ Counter :: non_neg_integer(),
+ AliasMap :: match_alias_map(),
+ Result :: {Blocks, Counter}.
+
+-type match_alias_map() ::
+ #{ Binary :: #b_var{} =>
+ { %% Replace all uses of Binary with an alias after this
+ %% label.
+ AliasAfter :: beam_ssa:label(),
+ %% The match context whose tail is equal to Binary.
+ Context :: #b_var{} } }.
+
+%% Keeps track of the promotions we need to insert. They're partially keyed by
+%% location because they may not be valid on all execution paths and we may
+%% need to add redundant promotions in some cases.
+-type promotion_map() ::
+ #{ { PromoteAt :: beam_ssa:label(),
+ Variable :: #b_var{} } =>
+ Instruction :: #b_set{} }.
+
+-record(amb, { dominators :: beam_ssa:dominator_map(),
+ match_aliases :: match_alias_map(),
+ cnt :: non_neg_integer(),
+ promotions = #{} :: promotion_map() }).
+
+alias_matched_binaries(Blocks0, Counter, AliasMap) when AliasMap =/= #{} ->
+ {Dominators, _} = beam_ssa:dominators(Blocks0),
+ State0 = #amb{ dominators = Dominators,
+ match_aliases = AliasMap,
+ cnt = Counter },
+ {Blocks, State} = beam_ssa:mapfold_blocks_rpo(fun amb_1/3, [0], State0,
+ Blocks0),
+ {amb_insert_promotions(Blocks, State), State#amb.cnt};
+alias_matched_binaries(Blocks, Counter, _AliasMap) ->
+ {Blocks, Counter}.
+
+amb_1(Lbl, #b_blk{is=Is0,last=Last0}=Block, State0) ->
+ {Is, State1} = mapfoldl(fun(I, State) ->
+ amb_assign_set(I, Lbl, State)
+ end, State0, Is0),
+ {Last, State} = amb_assign_last(Last0, Lbl, State1),
+ {Block#b_blk{is=Is,last=Last}, State}.
+
+amb_assign_set(#b_set{op=phi,args=Args0}=I, _Lbl, State0) ->
+ %% Phi node aliases are relative to their source block, not their
+ %% containing block.
+ {Args, State} =
+ mapfoldl(fun({Arg0, Lbl}, Acc) ->
+ {Arg, State} = amb_get_alias(Arg0, Lbl, Acc),
+ {{Arg, Lbl}, State}
+ end, State0, Args0),
+ {I#b_set{args=Args}, State};
+amb_assign_set(#b_set{args=Args0}=I, Lbl, State0) ->
+ {Args, State} = mapfoldl(fun(Arg0, Acc) ->
+ amb_get_alias(Arg0, Lbl, Acc)
+ end, State0, Args0),
+ {I#b_set{args=Args}, State}.
+
+amb_assign_last(#b_ret{arg=Arg0}=T, Lbl, State0) ->
+ {Arg, State} = amb_get_alias(Arg0, Lbl, State0),
+ {T#b_ret{arg=Arg}, State};
+amb_assign_last(#b_switch{arg=Arg0}=T, Lbl, State0) ->
+ {Arg, State} = amb_get_alias(Arg0, Lbl, State0),
+ {T#b_switch{arg=Arg}, State};
+amb_assign_last(#b_br{bool=Arg0}=T, Lbl, State0) ->
+ {Arg, State} = amb_get_alias(Arg0, Lbl, State0),
+ {T#b_br{bool=Arg}, State}.
+
+amb_get_alias(#b_var{}=Arg, Lbl, State) ->
+ case maps:find(Arg, State#amb.match_aliases) of
+ {ok, {AliasAfter, Context}} ->
+ %% Our context may not have been created yet, so we skip assigning
+ %% an alias unless the given block is among our dominators.
+ Dominators = maps:get(Lbl, State#amb.dominators),
+ case member(AliasAfter, Dominators) of
+ true -> amb_create_alias(Arg, Context, Lbl, State);
+ false -> {Arg, State}
+ end;
+ error ->
+ {Arg, State}
+ end;
+amb_get_alias(#b_remote{mod=Mod0,name=Name0}=Arg0, Lbl, State0) ->
+ {Mod, State1} = amb_get_alias(Mod0, Lbl, State0),
+ {Name, State} = amb_get_alias(Name0, Lbl, State1),
+ Arg = Arg0#b_remote{mod=Mod,name=Name},
+ {Arg, State};
+amb_get_alias(Arg, _Lbl, State) ->
+ {Arg, State}.
+
+amb_create_alias(#b_var{}=Arg0, Context, Lbl, State0) ->
+ Dominators = maps:get(Lbl, State0#amb.dominators),
+ Promotions0 = State0#amb.promotions,
+
+ PrevPromotions =
+ [maps:get({Dom, Arg0}, Promotions0)
+ || Dom <- Dominators, is_map_key({Dom, Arg0}, Promotions0)],
+
+ case PrevPromotions of
+ [_|_] ->
+ %% We've already created an alias prior to this block, so we'll
+ %% grab the most recent one to minimize stack use.
+
+ #b_set{dst=Alias} = max(PrevPromotions),
+ {Alias, State0};
+ [] ->
+ %% If we haven't created an alias we need to do so now. The
+ %% promotion will be inserted later by amb_insert_promotions/2.
+
+ Counter = State0#amb.cnt,
+ Alias = #b_var{name={'@ssa_bsm_alias', Counter}},
+ Promotion = #b_set{op=bs_get_tail,dst=Alias,args=[Context]},
+
+ Promotions = maps:put({Lbl, Arg0}, Promotion, Promotions0),
+ State = State0#amb{ promotions=Promotions, cnt=Counter+1 },
+
+ {Alias, State}
+ end.
+
+amb_insert_promotions(Blocks0, State) ->
+ F = fun({Lbl, #b_var{}}, Promotion, Blocks) ->
+ Block = maps:get(Lbl, Blocks),
+
+ Alias = Promotion#b_set.dst,
+ {Before, After} = splitwith(
+ fun(#b_set{args=Args}) ->
+ not is_var_in_args(Alias, Args)
+ end, Block#b_blk.is),
+ Is = Before ++ [Promotion | After],
+
+ maps:put(Lbl, Block#b_blk{is=Is}, Blocks)
+ end,
+ maps:fold(F, Blocks0, State#amb.promotions).
+
+is_var_in_args(Var, [Var | _]) -> true;
+is_var_in_args(Var, [#b_remote{name=Var} | _]) -> true;
+is_var_in_args(Var, [#b_remote{mod=Var} | _]) -> true;
+is_var_in_args(Var, [_ | Args]) -> is_var_in_args(Var, Args);
+is_var_in_args(_Var, []) -> false.
+
+%%%
+%%% Subpasses
+%%%
+
+%% Removes superflous chained bs_start_match instructions in the same
+%% function. When matching on an extracted tail binary, or on a binary we've
+%% already matched on, we reuse the original match context.
+%%
+%% This pass runs first since it makes subsequent optimizations more effective
+%% by removing spots where promotion would be required.
+
+-type prior_match_map() ::
+ #{ Binary :: #b_var{} =>
+ [{ %% The context and success label of a previous
+ %% bs_start_match made on this binary.
+ ValidAfter :: beam_ssa:label(),
+ Context :: #b_var{} }] }.
+
+-record(cm, { definitions :: beam_ssa:definition_map(),
+ dominators :: beam_ssa:dominator_map(),
+ blocks :: beam_ssa:block_map(),
+ match_aliases = #{} :: match_alias_map(),
+ prior_matches = #{} :: prior_match_map(),
+ renames = #{} :: beam_ssa:rename_map() }).
+
+combine_matches({Fs0, ModInfo}) ->
+ Fs = map(fun(F) -> combine_matches(F, ModInfo) end, Fs0),
+ {Fs, ModInfo}.
+
+combine_matches(#b_function{bs=Blocks0,cnt=Counter0}=F, ModInfo) ->
+ case funcinfo_get(F, has_bsm_ops, ModInfo) of
+ true ->
+ {Dominators, _} = beam_ssa:dominators(Blocks0),
+ {Blocks1, State} =
+ beam_ssa:mapfold_blocks_rpo(
+ fun(Lbl, #b_blk{is=Is0}=Block0, State0) ->
+ {Is, State} = cm_1(Is0, [], Lbl, State0),
+ {Block0#b_blk{is=Is}, State}
+ end, [0],
+ #cm{ definitions = beam_ssa:definitions(Blocks0),
+ dominators = Dominators,
+ blocks = Blocks0 },
+ Blocks0),
+
+ Blocks2 = beam_ssa:rename_vars(State#cm.renames, [0], Blocks1),
+
+ {Blocks, Counter} = alias_matched_binaries(Blocks2, Counter0,
+ State#cm.match_aliases),
+
+ F#b_function{ bs=Blocks, cnt=Counter };
+ false ->
+ F
+ end.
+
+cm_1([#b_set{ op=bs_start_match,
+ dst=Ctx,
+ args=[Src] },
+ #b_set{ op=succeeded,
+ dst=Bool,
+ args=[Ctx] }]=MatchSeq, Acc0, Lbl, State0) ->
+ Acc = reverse(Acc0),
+ case is_tail_binary(Src, State0#cm.definitions) of
+ true -> cm_combine_tail(Src, Ctx, Bool, Acc, State0);
+ false -> cm_handle_priors(Src, Ctx, Bool, Acc, MatchSeq, Lbl, State0)
+ end;
+cm_1([I | Is], Acc, Lbl, State) ->
+ cm_1(Is, [I | Acc], Lbl, State);
+cm_1([], Acc, _Lbl, State) ->
+ {reverse(Acc), State}.
+
+%% If we're dominated by at least one match on the same source, we can reuse
+%% the context created by that match.
+cm_handle_priors(Src, DstCtx, Bool, Acc, MatchSeq, Lbl, State0) ->
+ PriorCtxs = case maps:find(Src, State0#cm.prior_matches) of
+ {ok, Priors} ->
+ %% We've seen other match contexts on this source, but
+ %% we can only consider the ones whose success path
+ %% dominate us.
+ Dominators = maps:get(Lbl, State0#cm.dominators, []),
+ [Ctx || {ValidAfter, Ctx} <- Priors,
+ member(ValidAfter, Dominators)];
+ error ->
+ []
+ end,
+ case PriorCtxs of
+ [Ctx|_] ->
+ Renames0 = State0#cm.renames,
+ Renames = Renames0#{ Bool => #b_literal{val=true}, DstCtx => Ctx },
+ {Acc, State0#cm{ renames = Renames }};
+ [] ->
+ %% Since we lack a prior match, we need to register this one in
+ %% case we dominate another.
+ State = cm_register_prior(Src, DstCtx, Lbl, State0),
+ {Acc ++ MatchSeq, State}
+ end.
+
+cm_register_prior(Src, DstCtx, Lbl, State) ->
+ Block = maps:get(Lbl, State#cm.blocks),
+ #b_br{succ=ValidAfter} = Block#b_blk.last,
+
+ Priors0 = maps:get(Src, State#cm.prior_matches, []),
+ Priors = [{ValidAfter, DstCtx} | Priors0],
+
+ PriorMatches = maps:put(Src, Priors, State#cm.prior_matches),
+ State#cm{ prior_matches = PriorMatches }.
+
+cm_combine_tail(Src, DstCtx, Bool, Acc, State0) ->
+ SrcCtx = match_context_of(Src, State0#cm.definitions),
+
+ %% We replace the source with a context alias as it normally won't be used
+ %% on the happy path after being matched, and the added cost of conversion
+ %% is negligible if it is.
+ Aliases = maps:put(Src, {0, SrcCtx}, State0#cm.match_aliases),
+
+ Renames0 = State0#cm.renames,
+ Renames = Renames0#{ Bool => #b_literal{val=true}, DstCtx => SrcCtx },
+
+ State = State0#cm{ match_aliases = Aliases, renames = Renames },
+
+ {Acc, State}.
+
+%% Lets functions accept match contexts as arguments. The parameter must be
+%% unused before the bs_start_match instruction, and it must be matched in the
+%% first block.
+
+-record(aca, { unused_parameters :: ordsets:ordset(#b_var{}),
+ counter :: non_neg_integer(),
+ parameter_info = #{} :: #{ #b_var{} => param_info() },
+ match_aliases = #{} :: match_alias_map() }).
+
+accept_context_args({Fs, ModInfo}) ->
+ mapfoldl(fun accept_context_args/2, ModInfo, Fs).
+
+accept_context_args(#b_function{bs=Blocks0}=F, ModInfo0) ->
+ case funcinfo_get(F, has_bsm_ops, ModInfo0) of
+ true ->
+ Parameters = ordsets:from_list(funcinfo_get(F, parameters, ModInfo0)),
+ State0 = #aca{ unused_parameters = Parameters,
+ counter = F#b_function.cnt },
+
+ {Blocks1, State} = aca_1(Blocks0, State0),
+ {Blocks, Counter} = alias_matched_binaries(Blocks1,
+ State#aca.counter,
+ State#aca.match_aliases),
+
+ ModInfo = funcinfo_set(F, parameter_info, State#aca.parameter_info,
+ ModInfo0),
+
+ {F#b_function{bs=Blocks,cnt=Counter}, ModInfo};
+ false ->
+ {F, ModInfo0}
+ end.
+
+aca_1(Blocks, State) ->
+ %% We only handle block 0 as we don't yet support starting a match after a
+ %% test. This is generally good enough as the sys_core_bsm pass makes the
+ %% match instruction come first if possible, and it's rare for a function
+ %% to binary-match several parameters at once.
+ EntryBlock = maps:get(0, Blocks),
+ aca_enable_reuse(EntryBlock#b_blk.is, EntryBlock, Blocks, [], State).
+
+aca_enable_reuse([#b_set{op=bs_start_match,args=[Src]}=I0 | Rest],
+ EntryBlock, Blocks0, Acc, State0) ->
+ case aca_is_reuse_safe(Src, State0) of
+ true ->
+ {I, Last, Blocks1, State} =
+ aca_reuse_context(I0, EntryBlock, Blocks0, State0),
+
+ Is = reverse([I|Acc]) ++ Rest,
+ Blocks = maps:put(0, EntryBlock#b_blk{is=Is,last=Last}, Blocks1),
+
+ {Blocks, State};
+ false ->
+ {Blocks0, State0}
+ end;
+aca_enable_reuse([I | Is], EntryBlock, Blocks, Acc, State0) ->
+ UnusedParams0 = State0#aca.unused_parameters,
+ case ordsets:intersection(UnusedParams0, beam_ssa:used(I)) of
+ [] ->
+ aca_enable_reuse(Is, EntryBlock, Blocks, [I | Acc], State0);
+ PrematureUses ->
+ UnusedParams = ordsets:subtract(UnusedParams0, PrematureUses),
+
+ %% Mark the offending parameters as unsuitable for context reuse.
+ ParamInfo = foldl(fun(A, Ps) ->
+ maps:put(A, {used_before_match, I}, Ps)
+ end, State0#aca.parameter_info, PrematureUses),
+
+ State = State0#aca{ unused_parameters = UnusedParams,
+ parameter_info = ParamInfo },
+ aca_enable_reuse(Is, EntryBlock, Blocks, [I | Acc], State)
+ end;
+aca_enable_reuse([], _EntryBlock, Blocks, _Acc, State) ->
+ {Blocks, State}.
+
+aca_is_reuse_safe(Src, State) ->
+ %% Context reuse is unsafe unless all uses are dominated by the start_match
+ %% instruction. Since we only process block 0 it's enough to check if
+ %% they're unused so far.
+ ordsets:is_element(Src, State#aca.unused_parameters).
+
+aca_reuse_context(#b_set{dst=Dst, args=[Src]}=I0, Block, Blocks0, State0) ->
+ %% When matching fails on a reused context it needs to be converted back
+ %% to a binary. We only need to do this on the success path since it can't
+ %% be a context on the type failure path, but it's very common for these
+ %% to converge which requires special handling.
+ {State1, Last, Blocks} =
+ aca_handle_convergence(Src, State0, Block#b_blk.last, Blocks0),
+
+ Aliases = maps:put(Src, {Last#b_br.succ, Dst}, State1#aca.match_aliases),
+ ParamInfo = maps:put(Src, suitable_for_reuse, State1#aca.parameter_info),
+
+ State = State1#aca{ match_aliases = Aliases,
+ parameter_info = ParamInfo },
+
+ I = beam_ssa:add_anno(accepts_match_contexts, true, I0),
+
+ {I, Last, Blocks, State}.
+
+aca_handle_convergence(Src, State0, Last0, Blocks0) ->
+ #b_br{fail=Fail0,succ=Succ0} = Last0,
+
+ SuccPath = beam_ssa:rpo([Succ0], Blocks0),
+ FailPath = beam_ssa:rpo([Fail0], Blocks0),
+
+ %% The promotion logic in alias_matched_binaries breaks down if the source
+ %% is used after the fail/success paths converge, as we have no way to tell
+ %% whether the source is a match context or something else past that point.
+ %%
+ %% We could handle this through clever insertion of phi nodes but it's
+ %% far simpler to copy either branch in its entirety. It doesn't matter
+ %% which one as long as they become disjoint.
+ ConvergedPaths = ordsets:intersection(
+ ordsets:from_list(SuccPath),
+ ordsets:from_list(FailPath)),
+
+ case maps:is_key(Src, beam_ssa:uses(ConvergedPaths, Blocks0)) of
+ true ->
+ case shortest(SuccPath, FailPath) of
+ left ->
+ {Succ, Blocks, Counter} =
+ aca_copy_successors(Succ0, Blocks0, State0#aca.counter),
+ State = State0#aca{ counter = Counter },
+ {State, Last0#b_br{succ=Succ}, Blocks};
+ right ->
+ {Fail, Blocks, Counter} =
+ aca_copy_successors(Fail0, Blocks0, State0#aca.counter),
+ State = State0#aca{ counter = Counter },
+ {State, Last0#b_br{fail=Fail}, Blocks}
+ end;
+ false ->
+ {State0, Last0, Blocks0}
+ end.
+
+shortest([_|As], [_|Bs]) -> shortest(As, Bs);
+shortest([], _) -> left;
+shortest(_, []) -> right.
+
+%% Copies all successor blocks of Lbl, returning the label to the entry block
+%% of this copy. Since the copied blocks aren't referenced anywhere else, they
+%% are all guaranteed to be dominated by Lbl.
+aca_copy_successors(Lbl0, Blocks0, Counter0) ->
+ %% Building the block rename map up front greatly simplifies phi node
+ %% handling.
+ Path = beam_ssa:rpo([Lbl0], Blocks0),
+ {BRs, Counter1} = aca_cs_build_brs(Path, Counter0, #{}),
+ {Blocks, Counter} = aca_cs_1(Path, Blocks0, Counter1, #{}, BRs, #{}),
+ Lbl = maps:get(Lbl0, BRs),
+ {Lbl, Blocks, Counter}.
+
+aca_cs_build_brs([Lbl | Path], Counter0, Acc) ->
+ aca_cs_build_brs(Path, Counter0 + 1, maps:put(Lbl, Counter0, Acc));
+aca_cs_build_brs([], Counter, Acc) ->
+ {Acc, Counter}.
+
+aca_cs_1([Lbl0 | Path], Blocks, Counter0, VRs0, BRs, Acc0) ->
+ Block0 = maps:get(Lbl0, Blocks),
+ Lbl = maps:get(Lbl0, BRs),
+ {VRs, Block, Counter} = aca_cs_block(Block0, Counter0, VRs0, BRs),
+ Acc = maps:put(Lbl, Block, Acc0),
+ aca_cs_1(Path, Blocks, Counter, VRs, BRs, Acc);
+aca_cs_1([], Blocks, Counter, _VRs, _BRs, Acc) ->
+ {maps:merge(Blocks, Acc), Counter}.
+
+aca_cs_block(#b_blk{is=Is0,last=Last0}=Block0, Counter0, VRs0, BRs) ->
+ {VRs, Is, Counter} = aca_cs_is(Is0, Counter0, VRs0, BRs, []),
+ Last = aca_cs_last(Last0, VRs, BRs),
+ Block = Block0#b_blk{is=Is,last=Last},
+ {VRs, Block, Counter}.
+
+aca_cs_is([#b_set{op=Op,
+ dst=Dst0,
+ args=Args0}=I0 | Is],
+ Counter0, VRs0, BRs, Acc) ->
+ Args = case Op of
+ phi -> aca_cs_args_phi(Args0, VRs0, BRs);
+ _ -> aca_cs_args(Args0, VRs0)
+ end,
+ Counter = Counter0 + 1,
+ Dst = #b_var{name={'@ssa_bsm_aca',Counter}},
+ I = I0#b_set{dst=Dst,args=Args},
+ VRs = maps:put(Dst0, Dst, VRs0),
+ aca_cs_is(Is, Counter, VRs, BRs, [I | Acc]);
+aca_cs_is([], Counter, VRs, _BRs, Acc) ->
+ {VRs, reverse(Acc), Counter}.
+
+aca_cs_last(#b_switch{arg=Arg0,list=Switch0,fail=Fail0}=Sw, VRs, BRs) ->
+ Switch = [{Literal, maps:get(Lbl, BRs)} || {Literal, Lbl} <- Switch0],
+ Sw#b_switch{arg=aca_cs_arg(Arg0, VRs),
+ fail=maps:get(Fail0, BRs),
+ list=Switch};
+aca_cs_last(#b_br{bool=Arg0,succ=Succ0,fail=Fail0}=Br, VRs, BRs) ->
+ Br#b_br{bool=aca_cs_arg(Arg0, VRs),
+ succ=maps:get(Succ0, BRs),
+ fail=maps:get(Fail0, BRs)};
+aca_cs_last(#b_ret{arg=Arg0}=Ret, VRs, _BRs) ->
+ Ret#b_ret{arg=aca_cs_arg(Arg0, VRs)}.
+
+aca_cs_args_phi([{Arg, Lbl} | Args], VRs, BRs) ->
+ case BRs of
+ #{ Lbl := New } ->
+ [{aca_cs_arg(Arg, VRs), New} | aca_cs_args_phi(Args, VRs, BRs)];
+ #{} ->
+ aca_cs_args_phi(Args, VRs, BRs)
+ end;
+aca_cs_args_phi([], _VRs, _BRs) ->
+ [].
+
+aca_cs_args([Arg | Args], VRs) ->
+ [aca_cs_arg(Arg, VRs) | aca_cs_args(Args, VRs)];
+aca_cs_args([], _VRs) ->
+ [].
+
+aca_cs_arg(#b_remote{mod=Mod0,name=Name0}=Rem, VRs) ->
+ Mod = aca_cs_arg(Mod0, VRs),
+ Name = aca_cs_arg(Name0, VRs),
+ Rem#b_remote{mod=Mod,name=Name};
+aca_cs_arg(Arg, VRs) ->
+ case VRs of
+ #{ Arg := New } -> New;
+ #{} -> Arg
+ end.
+
+%% Allows contexts to pass through "wrapper functions" where the context is
+%% passed directly to a function that accepts match contexts (including other
+%% wrappers).
+%%
+%% This does not alter the function in any way, it only changes parameter info
+%% so that skip_outgoing_tail_extraction is aware that it's safe to pass
+%% contexts to us.
+
+allow_context_passthrough({Fs, ModInfo0}) ->
+ ModInfo =
+ acp_forward_params([{F, beam_ssa:uses(F#b_function.bs)} || F <- Fs],
+ ModInfo0),
+ {Fs, ModInfo}.
+
+acp_forward_params(FsUses, ModInfo0) ->
+ F = fun({#b_function{args=Parameters}=Func, UseMap}, ModInfo) ->
+ ParamInfo =
+ foldl(fun(Param, ParamInfo) ->
+ Uses = maps:get(Param, UseMap, []),
+ acp_1(Param, Uses, ModInfo, ParamInfo)
+ end,
+ funcinfo_get(Func, parameter_info, ModInfo),
+ Parameters),
+ funcinfo_set(Func, parameter_info, ParamInfo, ModInfo)
+ end,
+ %% Allowing context passthrough on one function may make it possible to
+ %% enable it on another, so it needs to be repeated for maximum effect.
+ case foldl(F, ModInfo0, FsUses) of
+ ModInfo0 -> ModInfo0;
+ Changed -> acp_forward_params(FsUses, Changed)
+ end.
+
+%% We have no way to know if an argument is a context, so it's only safe to
+%% forward them if they're passed exactly once in the first block. Any other
+%% uses are unsafe, including function_clause errors.
+acp_1(Param, [{0, #b_set{op=call}=I}], ModInfo, ParamInfo) ->
+ %% We don't need to provide a context chain as our callers make sure that
+ %% multiple arguments never reference the same context.
+ case check_context_call(I, Param, [], ModInfo) of
+ {no_match_on_entry, _} -> ParamInfo;
+ Other -> maps:put(Param, Other, ParamInfo)
+ end;
+acp_1(_Param, _Uses, _ModInfo, ParamInfo) ->
+ ParamInfo.
+
+%% This is conceptually similar to combine_matches but operates across
+%% functions. Whenever a tail binary is passed to a parameter that accepts
+%% match contexts we'll pass the context instead, improving performance by
+%% avoiding the creation of a new match context in the callee.
+%%
+%% We also create an alias to delay extraction until it's needed as an actual
+%% binary, which is often rare on the happy path. The cost of being wrong is
+%% negligible (`bs_test_unit + bs_get_tail` vs `bs_get_binary`) so we're
+%% applying it unconditionally to keep things simple.
+
+-record(sote, { definitions :: beam_ssa:definition_map(),
+ mod_info :: module_info(),
+ match_aliases = #{} :: match_alias_map() }).
+
+skip_outgoing_tail_extraction({Fs0, ModInfo}) ->
+ Fs = map(fun(F) -> skip_outgoing_tail_extraction(F, ModInfo) end, Fs0),
+ {Fs, ModInfo}.
+
+skip_outgoing_tail_extraction(#b_function{bs=Blocks0}=F, ModInfo) ->
+ case funcinfo_get(F, has_bsm_ops, ModInfo) of
+ true ->
+ State0 = #sote{ definitions = beam_ssa:definitions(Blocks0),
+ mod_info = ModInfo },
+
+ {Blocks1, State} = beam_ssa:mapfold_instrs_rpo(
+ fun sote_rewrite_calls/2, [0], State0, Blocks0),
+
+ {Blocks, Counter} = alias_matched_binaries(Blocks1,
+ F#b_function.cnt,
+ State#sote.match_aliases),
+
+ F#b_function{bs=Blocks,cnt=Counter};
+ false ->
+ F
+ end.
+
+sote_rewrite_calls(#b_set{op=call,args=Args}=Call, State) ->
+ sote_rewrite_call(Call, Args, [], State);
+sote_rewrite_calls(I, State) ->
+ {I, State}.
+
+sote_rewrite_call(Call, [], ArgsOut, State) ->
+ {Call#b_set{args=reverse(ArgsOut)}, State};
+sote_rewrite_call(Call0, [Arg | ArgsIn], ArgsOut, State0) ->
+ case is_tail_binary(Arg, State0#sote.definitions) of
+ true ->
+ CtxChain = context_chain_of(Arg, State0#sote.definitions),
+ case check_context_call(Call0, Arg, CtxChain, State0#sote.mod_info) of
+ suitable_for_reuse ->
+ Ctx = match_context_of(Arg, State0#sote.definitions),
+
+ MatchAliases0 = State0#sote.match_aliases,
+ MatchAliases = maps:put(Arg, {0, Ctx}, MatchAliases0),
+ State = State0#sote{ match_aliases = MatchAliases },
+
+ Call = beam_ssa:add_anno(bsm_info, context_reused, Call0),
+ sote_rewrite_call(Call, ArgsIn, [Ctx | ArgsOut], State);
+ Other ->
+ Call = beam_ssa:add_anno(bsm_info, Other, Call0),
+ sote_rewrite_call(Call, ArgsIn, [Arg | ArgsOut], State0)
+ end;
+ false ->
+ sote_rewrite_call(Call0, ArgsIn, [Arg | ArgsOut], State0)
+ end.
+
+%% Adds parameter_type_info annotations to help the validator determine whether
+%% our optimizations were safe.
+
+annotate_context_parameters({Fs, ModInfo}) ->
+ mapfoldl(fun annotate_context_parameters/2, ModInfo, Fs).
+
+annotate_context_parameters(F, ModInfo) ->
+ ParamInfo = funcinfo_get(F, parameter_info, ModInfo),
+ TypeAnno0 = beam_ssa:get_anno(parameter_type_info, F, #{}),
+ TypeAnno = maps:fold(fun(K, _V, Acc) when is_map_key(K, Acc) ->
+ %% Assertion.
+ error(conflicting_parameter_types);
+ (K, suitable_for_reuse, Acc) ->
+ T = beam_validator:type_anno(match_context),
+ Acc#{ K => T };
+ (_K, _V, Acc) ->
+ Acc
+ end, TypeAnno0, ParamInfo),
+ {beam_ssa:add_anno(parameter_type_info, TypeAnno, F), ModInfo}.
+
+%%%
+%%% +bin_opt_info
+%%%
+
+collect_opt_info(Fs) ->
+ foldl(fun(#b_function{bs=Blocks}=F, Acc0) ->
+ UseMap = beam_ssa:uses(Blocks),
+ Where = beam_ssa:get_anno(location, F, []),
+ beam_ssa:fold_instrs_rpo(
+ fun(I, Acc) ->
+ collect_opt_info_1(I, Where, UseMap, Acc)
+ end, [0], Acc0, Blocks)
+ end, [], Fs).
+
+collect_opt_info_1(#b_set{op=Op,anno=Anno,dst=Dst}=I, Where, UseMap, Acc0) ->
+ case is_tail_binary(I) of
+ true when Op =:= bs_match ->
+ %% The uses include when the context is passed raw, so we discard
+ %% everything but the bs_extract instruction to limit warnings to
+ %% unoptimized uses.
+ Uses0 = maps:get(Dst, UseMap, []),
+ case [E || {_, #b_set{op=bs_extract}=E} <- Uses0] of
+ [Use] -> add_unopt_binary_info(Use, false, Where, UseMap, Acc0);
+ [] -> Acc0
+ end;
+ true ->
+ %% Add a warning for each use. Note that we don't do anything
+ %% special if unused as a later pass will remove this instruction
+ %% anyway.
+ Uses = maps:get(Dst, UseMap, []),
+ foldl(fun({_Lbl, Use}, Acc) ->
+ add_unopt_binary_info(Use, false, Where, UseMap, Acc)
+ end, Acc0, Uses);
+ false ->
+ add_opt_info(Anno, Where, Acc0)
+ end;
+collect_opt_info_1(#b_ret{anno=Anno}, Where, _UseMap, Acc) ->
+ add_opt_info(Anno, Where, Acc);
+collect_opt_info_1(_I, _Where, _Uses, Acc) ->
+ Acc.
+
+add_opt_info(Anno, Where, Acc) ->
+ case maps:find(bsm_info, Anno) of
+ {ok, Term} -> [make_warning(Term, Anno, Where) | Acc];
+ error -> Acc
+ end.
+
+%% When an alias is promoted we need to figure out where it goes to ignore
+%% warnings for compiler-generated things, and provide more useful warnings in
+%% general.
+%%
+%% We track whether the binary has been used to build another term because it
+%% can be helpful when there's no line information.
+
+add_unopt_binary_info(#b_set{op=Follow,dst=Dst}, _Nested, Where, UseMap, Acc0)
+ when Follow =:= put_tuple;
+ Follow =:= put_list;
+ Follow =:= put_map ->
+ %% Term-building instructions.
+ {_, Uses} = unzip(maps:get(Dst, UseMap, [])),
+ foldl(fun(Use, Acc) ->
+ add_unopt_binary_info(Use, true, Where, UseMap, Acc)
+ end, Acc0, Uses);
+add_unopt_binary_info(#b_set{op=Follow,dst=Dst}, Nested, Where, UseMap, Acc0)
+ when Follow =:= bs_extract;
+ Follow =:= phi ->
+ %% Non-building instructions that need to be followed.
+ {_, Uses} = unzip(maps:get(Dst, UseMap, [])),
+ foldl(fun(Use, Acc) ->
+ add_unopt_binary_info(Use, Nested, Where, UseMap, Acc)
+ end, Acc0, Uses);
+add_unopt_binary_info(#b_set{op=call,
+ args=[#b_remote{mod=#b_literal{val=erlang},
+ name=#b_literal{val=error}} |
+ _Ignored]},
+ _Nested, _Where, _UseMap, Acc) ->
+ %% There's no nice way to tell compiler-generated exceptions apart from
+ %% user ones so we ignore them all. I doubt anyone cares.
+ Acc;
+add_unopt_binary_info(#b_switch{anno=Anno}=I, Nested, Where, _UseMap, Acc) ->
+ [make_promotion_warning(I, Nested, Anno, Where) | Acc];
+add_unopt_binary_info(#b_set{anno=Anno}=I, Nested, Where, _UseMap, Acc) ->
+ [make_promotion_warning(I, Nested, Anno, Where) | Acc];
+add_unopt_binary_info(#b_ret{anno=Anno}=I, Nested, Where, _UseMap, Acc) ->
+ [make_promotion_warning(I, Nested, Anno, Where) | Acc];
+add_unopt_binary_info(#b_br{anno=Anno}=I, Nested, Where, _UseMap, Acc) ->
+ [make_promotion_warning(I, Nested, Anno, Where) | Acc].
+
+make_promotion_warning(I, Nested, Anno, Where) ->
+ make_warning({binary_created, I, Nested}, Anno, Where).
+
+make_warning(Term, Anno, Where) ->
+ {File, Line} = maps:get(location, Anno, Where),
+ {File,[{Line,?MODULE,Term}]}.
+
+format_opt_info(context_reused) ->
+ "OPTIMIZED: match context reused";
+format_opt_info({binary_created, _, _}=Promotion) ->
+ io_lib:format("BINARY CREATED: ~s", [format_opt_info_1(Promotion)]);
+format_opt_info(Other) ->
+ io_lib:format("NOT OPTIMIZED: ~s", [format_opt_info_1(Other)]).
+
+format_opt_info_1({binary_created, #b_set{op=call,args=[Call|_]}, false}) ->
+ io_lib:format("binary is used in call to ~s which doesn't support "
+ "context reuse", [format_call(Call)]);
+format_opt_info_1({binary_created, #b_set{op=call,args=[Call|_]}, true}) ->
+ io_lib:format("binary is used in term passed to ~s",
+ [format_call(Call)]);
+format_opt_info_1({binary_created, #b_set{op={bif, BIF},args=Args}, false}) ->
+ io_lib:format("binary is used in ~p/~p which doesn't support context "
+ "reuse", [BIF, length(Args)]);
+format_opt_info_1({binary_created, #b_set{op={bif, BIF},args=Args}, true}) ->
+ io_lib:format("binary is used in term passed to ~p/~p",
+ [BIF, length(Args)]);
+format_opt_info_1({binary_created, #b_set{op=Op}, false}) ->
+ io_lib:format("binary is used in '~p' which doesn't support context "
+ "reuse", [Op]);
+format_opt_info_1({binary_created, #b_set{op=Op}, true}) ->
+ io_lib:format("binary is used in term passed to '~p'", [Op]);
+format_opt_info_1({binary_created, #b_ret{}, false}) ->
+ io_lib:format("binary is returned from the function", []);
+format_opt_info_1({binary_created, #b_ret{}, true}) ->
+ io_lib:format("binary is used in a term that is returned from the "
+ "function", []);
+format_opt_info_1({unsuitable_call, {Call, Inner}}) ->
+ io_lib:format("binary used in call to ~s, where ~s",
+ [format_call(Call), format_opt_info_1(Inner)]);
+format_opt_info_1({remote_call, Call}) ->
+ io_lib:format("binary is used in remote call to ~s", [format_call(Call)]);
+format_opt_info_1({fun_call, Call}) ->
+ io_lib:format("binary is used in fun call (~s)",
+ [format_call(Call)]);
+format_opt_info_1({multiple_uses_in_call, Call}) ->
+ io_lib:format("binary is passed as multiple arguments to ~s",
+ [format_call(Call)]);
+format_opt_info_1({no_match_on_entry, Call}) ->
+ io_lib:format("binary is used in call to ~s which does not begin with a "
+ "suitable binary match", [format_call(Call)]);
+format_opt_info_1({used_before_match, #b_set{op=call,args=[Call|_]}}) ->
+ io_lib:format("binary is used in call to ~s before being matched",
+ [format_call(Call)]);
+format_opt_info_1({used_before_match, #b_set{op={bif, BIF},args=Args}}) ->
+ io_lib:format("binary is used in ~p/~p before being matched",
+ [BIF, length(Args)]);
+format_opt_info_1({used_before_match, #b_set{op=phi}}) ->
+ io_lib:format("binary is returned from an expression before being "
+ "matched", []);
+format_opt_info_1({used_before_match, #b_set{op=Op}}) ->
+ io_lib:format("binary is used in '~p' before being matched",[Op]);
+format_opt_info_1(Term) ->
+ io_lib:format("~w", [Term]).
+
+format_call(#b_local{name=#b_literal{val=F},arity=A}) ->
+ io_lib:format("~p/~p", [F, A]);
+format_call(#b_remote{mod=#b_literal{val=M},name=#b_literal{val=F},arity=A}) ->
+ io_lib:format("~p:~p/~p", [M, F, A]);
+format_call(Fun) ->
+ io_lib:format("~p", [Fun]).
diff --git a/lib/compiler/src/beam_ssa_codegen.erl b/lib/compiler/src/beam_ssa_codegen.erl
new file mode 100644
index 0000000000..c2d5035b19
--- /dev/null
+++ b/lib/compiler/src/beam_ssa_codegen.erl
@@ -0,0 +1,2077 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018. All 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: Generate BEAM assembly code from the SSA format.
+
+-module(beam_ssa_codegen).
+
+-export([module/2]).
+-export([classify_heap_need/2]). %Called from beam_ssa_pre_codegen.
+
+-export_type([ssa_register/0]).
+
+-include("beam_ssa.hrl").
+
+-import(lists, [foldl/3,keymember/3,keysort/2,last/1,map/2,mapfoldl/3,
+ reverse/1,reverse/2,sort/1,splitwith/2,takewhile/2]).
+
+-record(cg, {lcount=1 :: beam_label(), %Label counter
+ functable=#{} :: #{fa()=>beam_label()},
+ labels=#{} :: #{ssa_label()=>0|beam_label()},
+ used_labels=gb_sets:empty() :: gb_sets:set(ssa_label()),
+ regs=#{} :: #{beam_ssa:var_name()=>ssa_register()},
+ ultimate_fail=1 :: beam_label(),
+ catches=gb_sets:empty() :: gb_sets:set(ssa_label())
+ }).
+
+-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}),
+ {ok,{Mod,Es,Attrs,Asm,St#cg.lcount}}.
+
+-record(need, {h=0 :: non_neg_integer(),
+ f=0 :: non_neg_integer()}).
+
+-record(cg_blk, {anno=#{} :: anno(),
+ is=[] :: [instruction()],
+ last :: terminator()}).
+
+-record(cg_set, {anno=#{} :: anno(),
+ dst :: b_var(),
+ op :: beam_ssa:op(),
+ args :: [beam_ssa:argument() | xreg()]}).
+
+-record(cg_alloc, {anno=#{} :: anno(),
+ stack=none :: 'none' | pos_integer(),
+ words=#need{} :: #need{},
+ live :: 'undefined' | pos_integer(),
+ def_yregs=[] :: [yreg()]
+ }).
+
+-record(cg_br, {bool :: beam_ssa:value(),
+ succ :: ssa_label(),
+ fail :: ssa_label()
+ }).
+-record(cg_ret, {arg :: cg_value(),
+ dealloc=none :: 'none' | pos_integer()
+ }).
+-record(cg_switch, {arg :: cg_value(),
+ fail :: ssa_label(),
+ list :: [sw_list_item()]
+ }).
+
+-type fa() :: {beam_asm:function_name(),arity()}.
+-type ssa_label() :: beam_ssa:label().
+-type beam_label() :: beam_asm:label().
+
+-type anno() :: beam_ssa:anno().
+
+-type b_var() :: beam_ssa:b_var().
+-type b_literal() :: beam_ssa:b_literal().
+
+-type cg_value() :: beam_ssa:value() | xreg().
+
+-type cg_set() :: #cg_set{}.
+-type cg_alloc() :: #cg_alloc{}.
+
+-type instruction() :: cg_set() | cg_alloc().
+
+-type cg_br() :: #cg_br{}.
+-type cg_ret() :: #cg_ret{}.
+-type cg_switch() :: #cg_switch{}.
+-type terminator() :: cg_br() | cg_ret() | cg_switch().
+
+-type sw_list_item() :: {b_literal(),ssa_label()}.
+
+-type reg_num() :: beam_asm:reg_num().
+-type xreg() :: {'x',reg_num()}.
+-type yreg() :: {'y',reg_num()}.
+
+-type ssa_register() :: xreg() | yreg() | {'fr',reg_num()} | {'z',reg_num()}.
+
+functions(Forms, AtomMod) ->
+ mapfoldl(fun (F, St) -> function(F, AtomMod, St) end,
+ #cg{lcount=1}, Forms).
+
+function(#b_function{anno=Anno,bs=Blocks}, AtomMod, St0) ->
+ #{func_info:={_,Name,Arity}} = Anno,
+ try
+ assert_badarg_block(Blocks), %Assertion.
+ Regs = maps:get(registers, Anno),
+ St1 = St0#cg{labels=#{},used_labels=gb_sets:empty(),
+ regs=Regs},
+ {Fi,St2} = new_label(St1), %FuncInfo label
+ {Entry,St3} = local_func_label(Name, Arity, St2),
+ {Ult,St4} = new_label(St3), %Ultimate failure
+ Labels = (St4#cg.labels)#{0=>Entry,?BADARG_BLOCK=>0},
+ St5 = St4#cg{labels=Labels,used_labels=gb_sets:singleton(Entry),
+ ultimate_fail=Ult},
+ {Body,St} = cg_fun(Blocks, St5),
+ Asm = [{label,Fi},line(Anno),
+ {func_info,AtomMod,{atom,Name},Arity}] ++
+ add_parameter_annos(Body, Anno) ++
+ [{label,Ult},if_end],
+ Func = {function,Name,Arity,Entry,Asm},
+ {Func,St}
+ catch
+ Class:Error:Stack ->
+ io:fwrite("Function: ~w/~w\n", [Name,Arity]),
+ erlang:raise(Class, Error, Stack)
+ end.
+
+assert_badarg_block(Blocks) ->
+ %% Assertion: ?BADARG_BLOCK must be the call erlang:error(badarg).
+ case Blocks of
+ #{?BADARG_BLOCK:=Blk} ->
+ #b_blk{is=[#b_set{op=call,dst=Ret,
+ args=[#b_remote{mod=#b_literal{val=erlang},
+ name=#b_literal{val=error}},
+ #b_literal{val=badarg}]}],
+ last=#b_ret{arg=Ret}} = Blk,
+ ok;
+ #{} ->
+ %% ?BADARG_BLOCK has been removed because it was never used.
+ ok
+ end.
+
+add_parameter_annos([{label, _}=Entry | Body], Anno) ->
+ ParamInfo = maps:get(parameter_type_info, Anno, #{}),
+ Annos = maps:fold(
+ fun(K, V, Acc) when is_map_key(K, ParamInfo) ->
+ TypeInfo = maps:get(K, ParamInfo),
+ [{'%', {type_info, V, TypeInfo}} | Acc];
+ (_K, _V, Acc) ->
+ Acc
+ end, [], maps:get(registers, Anno)),
+ [Entry | sort(Annos)] ++ Body.
+
+cg_fun(Blocks, St0) ->
+ Linear0 = linearize(Blocks),
+ St = collect_catch_labels(Linear0, St0),
+ Linear1 = need_heap(Linear0),
+ Linear2 = prefer_xregs(Linear1, St),
+ Linear3 = liveness(Linear2, St),
+ Linear4 = defined(Linear3, St),
+ Linear = opt_allocate(Linear4, St),
+ cg_linear(Linear, St).
+
+%% collect_catch_labels(Linear, St) -> St.
+%% Collect all catch labels (labels for blocks that begin
+%% with 'landingpad' instructions) for later use.
+
+collect_catch_labels(Linear, St) ->
+ Labels = collect_catch_labels_1(Linear),
+ St#cg{catches=gb_sets:from_list(Labels)}.
+
+collect_catch_labels_1([{L,#cg_blk{is=[#cg_set{op=landingpad}|_]}}|Bs]) ->
+ [L|collect_catch_labels_1(Bs)];
+collect_catch_labels_1([_|Bs]) ->
+ collect_catch_labels_1(Bs);
+collect_catch_labels_1([]) -> [].
+
+%% need_heap([{BlockLabel,Block]) -> [{BlockLabel,Block}].
+%% Insert need_heap instructions in the instruction list. Try to be smart and
+%% collect them together as much as possible.
+
+need_heap(Bs0) ->
+ Bs1 = need_heap_allocs(Bs0, #{}),
+ {Bs,#need{h=0,f=0}} = need_heap_blks(reverse(Bs1), #need{}, []),
+ Bs.
+
+need_heap_allocs([{L,#cg_blk{is=Is0,last=Terminator}=Blk0}|Bs], Counts0) ->
+ Next = next_block(Bs),
+ Successors = successors(Terminator),
+ Counts = foldl(fun(S, Cnts) ->
+ case Cnts of
+ #{S:=C} -> Cnts#{S:=C+1};
+ #{} when S =:= Next -> Cnts#{S=>1};
+ #{} -> Cnts#{S=>42}
+ end
+ end, Counts0, Successors),
+ case Counts of
+ #{L:=1} ->
+ [{L,Blk0}|need_heap_allocs(Bs, Counts)];
+ #{L:=_} ->
+ %% This block has multiple predecessors. Force an allocation
+ %% in this block so that the predecessors don't need to do
+ %% an allocation on behalf of this block.
+ Is = case need_heap_never(Is0) of
+ true -> Is0;
+ false -> [#cg_alloc{}|Is0]
+ end,
+ Blk = Blk0#cg_blk{is=Is},
+ [{L,Blk}|need_heap_allocs(Bs, Counts)];
+ #{} ->
+ [{L,Blk0}|need_heap_allocs(Bs, Counts)]
+ end;
+need_heap_allocs([], _) -> [].
+
+need_heap_never([#cg_alloc{}|_]) -> true;
+need_heap_never([#cg_set{op=recv_next}|_]) -> true;
+need_heap_never([#cg_set{op=wait}|_]) -> true;
+need_heap_never(_) -> false.
+
+need_heap_blks([{L,#cg_blk{is=Is0}=Blk0}|Bs], H0, Acc) ->
+ {Is1,H1} = need_heap_is(reverse(Is0), H0, []),
+ {Ns,H} = need_heap_terminator(Bs, L, H1),
+ Is = Ns ++ Is1,
+ Blk = Blk0#cg_blk{is=Is},
+ need_heap_blks(Bs, H, [{L,Blk}|Acc]);
+need_heap_blks([], H, Acc) ->
+ {Acc,H}.
+
+need_heap_is([#cg_alloc{words=Words}=Alloc0|Is], N, Acc) ->
+ Alloc = Alloc0#cg_alloc{words=add_heap_words(N, Words)},
+ need_heap_is(Is, #need{}, [Alloc|Acc]);
+need_heap_is([#cg_set{anno=Anno,op=bs_init}=I0|Is], N, Acc) ->
+ Alloc = case need_heap_need(N) of
+ [#cg_alloc{words=Need}] -> alloc(Need);
+ [] -> 0
+ end,
+ I = I0#cg_set{anno=Anno#{alloc=>Alloc}},
+ need_heap_is(Is, #need{}, [I|Acc]);
+need_heap_is([#cg_set{op=Op,args=Args}=I|Is], N, Acc) ->
+ case classify_heap_need(Op, Args) of
+ {put,Words} ->
+ %% Pass through adding to needed heap.
+ need_heap_is(Is, add_heap_words(N, Words), [I|Acc]);
+ put_float ->
+ need_heap_is(Is, add_heap_float(N), [I|Acc]);
+ neutral ->
+ need_heap_is(Is, N, [I|Acc]);
+ gc ->
+ need_heap_is(Is, #need{}, [I]++need_heap_need(N)++Acc)
+ end;
+need_heap_is([], N, Acc) ->
+ {Acc,N}.
+
+need_heap_terminator([{_,#cg_blk{last=#cg_br{succ=L,fail=L}}}|_], L, N) ->
+ %% Fallthrough.
+ {[],N};
+need_heap_terminator([{_,#cg_blk{is=Is,last=#cg_br{succ=L}}}|_], L, N) ->
+ case need_heap_need(N) of
+ [] ->
+ {[],#need{}};
+ [_|_]=Alloc ->
+ %% If the preceding instructions are a binary construction,
+ %% hoist the allocation and incorporate into the bs_init
+ %% instruction.
+ case reverse(Is) of
+ [#cg_set{op=succeeded},#cg_set{op=bs_init}|_] ->
+ {[],N};
+ [#cg_set{op=bs_put}|_] ->
+ {[],N};
+ _ ->
+ %% Not binary construction. Must emit an allocation
+ %% instruction in this block.
+ {Alloc,#need{}}
+ end
+ end;
+need_heap_terminator([{_,#cg_blk{}}|_], _, N) ->
+ {need_heap_need(N),#need{}};
+need_heap_terminator([], _, H) ->
+ {need_heap_need(H),#need{}}.
+
+need_heap_need(#need{h=0,f=0}) -> [];
+need_heap_need(#need{}=N) -> [#cg_alloc{words=N}].
+
+add_heap_words(#need{h=H1,f=F1}, #need{h=H2,f=F2}) ->
+ #need{h=H1+H2,f=F1+F2};
+add_heap_words(#need{h=Heap}=N, Words) when is_integer(Words) ->
+ N#need{h=Heap+Words}.
+
+add_heap_float(#need{f=F}=N) ->
+ N#need{f=F+1}.
+
+%% classify_heap_need(Operation, Arguments) ->
+%% gc | neutral | {put,Words} | put_float.
+%% Classify the heap need for this instruction. The return
+%% values have the following meaning.
+%%
+%% {put,Words} means that the instruction will use Words words to build
+%% something on the heap.
+%%
+%% 'put_float' means that the instruction will build one floating point
+%% number on the heap.
+%%
+%% 'gc' means that that the instruction can potentially do a GC or throw an
+%% exception. That means that an allocation instruction for any building
+%% must be placed after this instruction.
+%%
+%% 'neutral' means that the instruction does nothing to disturb the heap.
+
+-spec classify_heap_need(beam_ssa:op(), [beam_ssa:value()]) ->
+ 'gc' | 'neutral' |
+ {'put',non_neg_integer()} | 'put_float'.
+
+classify_heap_need(put_list, _) ->
+ {put,2};
+classify_heap_need(put_tuple_arity, [#b_literal{val=Words}]) ->
+ {put,Words+1};
+classify_heap_need(put_tuple, Elements) ->
+ {put,length(Elements)+1};
+classify_heap_need({bif,Name}, Args) ->
+ case is_gc_bif(Name, Args) of
+ false -> neutral;
+ true -> gc
+ end;
+classify_heap_need({float,Op}, _Args) ->
+ case Op of
+ get -> put_float;
+ _ -> neutral
+ end;
+classify_heap_need(Name, _Args) ->
+ classify_heap_need(Name).
+
+%% classify_heap_need(Operation) -> gc | neutral.
+%% Return either 'gc' or 'neutral'.
+%%
+%% 'gc' means that that the instruction can potentially do a GC or throw an
+%% exception. That means that an allocation instruction for any building
+%% must be placed after this instruction.
+%%
+%% 'neutral' means that the instruction does nothing to disturb the heap.
+%%
+%% Note: Only handle operations in this function that are not handled
+%% by classify_heap_need/2.
+
+classify_heap_need(bs_add) -> gc;
+classify_heap_need(bs_get) -> gc;
+classify_heap_need(bs_get_tail) -> gc;
+classify_heap_need(bs_init) -> gc;
+classify_heap_need(bs_init_writable) -> gc;
+classify_heap_need(bs_match_string) -> gc;
+classify_heap_need(bs_put) -> neutral;
+classify_heap_need(bs_restore) -> neutral;
+classify_heap_need(bs_save) -> neutral;
+classify_heap_need(bs_get_position) -> gc;
+classify_heap_need(bs_set_position) -> neutral;
+classify_heap_need(bs_skip) -> gc;
+classify_heap_need(bs_start_match) -> neutral;
+classify_heap_need(bs_test_tail) -> neutral;
+classify_heap_need(bs_utf16_size) -> neutral;
+classify_heap_need(bs_utf8_size) -> neutral;
+classify_heap_need(build_stacktrace) -> gc;
+classify_heap_need(call) -> gc;
+classify_heap_need(catch_end) -> gc;
+classify_heap_need(copy) -> neutral;
+classify_heap_need(extract) -> gc;
+classify_heap_need(get_hd) -> neutral;
+classify_heap_need(get_map_element) -> neutral;
+classify_heap_need(get_tl) -> neutral;
+classify_heap_need(get_tuple_element) -> neutral;
+classify_heap_need(has_map_field) -> neutral;
+classify_heap_need(is_nonempty_list) -> neutral;
+classify_heap_need(is_tagged_tuple) -> neutral;
+classify_heap_need(kill_try_tag) -> gc;
+classify_heap_need(landingpad) -> gc;
+classify_heap_need(make_fun) -> gc;
+classify_heap_need(new_try_tag) -> gc;
+classify_heap_need(peek_message) -> gc;
+classify_heap_need(put_map) -> gc;
+classify_heap_need(put_tuple_elements) -> neutral;
+classify_heap_need(raw_raise) -> gc;
+classify_heap_need(recv_next) -> gc;
+classify_heap_need(remove_message) -> neutral;
+classify_heap_need(resume) -> gc;
+classify_heap_need(set_tuple_element) -> gc;
+classify_heap_need(succeeded) -> neutral;
+classify_heap_need(timeout) -> gc;
+classify_heap_need(wait) -> gc;
+classify_heap_need(wait_timeout) -> gc.
+
+%%%
+%%% Because beam_ssa_pre_codegen has inserted 'copy' instructions to copy
+%%% variables that must be saved on the stack, a value can for some time
+%%% be in both an X register and a Y register.
+%%%
+%%% Here we will keep track of variables that have the same value and
+%%% rewrite instructions to use the variable that refers to the X
+%%% register instead of the Y register. That could improve performance,
+%%% since the BEAM interpreter have more optimized instructions
+%%% operating on X registers than on Y registers.
+%%%
+%%% 'call' and 'make_fun' are handled somewhat specially. If a value
+%%% already is in the correct X register, the X register will always
+%%% be used instead of the Y register. However, if there are one or more
+%%% values in the wrong X registers, the X registers variables will be
+%%% used only if that does not cause more 'move' instructions to be
+%%% be emitted than if the Y register variables were used.
+%%%
+%%% Here are some examples. The first example shows how a 'move' from
+%%% an Y register is eliminated:
+%%%
+%%% move x0 y1
+%%% move y1 x0 %%Will be eliminated.
+%%%
+%%% call f/1
+%%%
+%%% Here is an example when x0 and x1 must be swapped to load the argument
+%%% registers. Here the 'call' instruction will use the Y registers to
+%%% avoid introducing an extra 'move' insruction:
+%%%
+%%% move x0 y0
+%%% move x1 y1
+%%%
+%%% move y0 x1
+%%% move y1 x0
+%%%
+%%% call f/2
+%%%
+%%% Using the X register to load the argument registers would need
+%%% an extra 'move' instruction like this:
+%%%
+%%% move x0 y0
+%%% move x1 y1
+%%%
+%%% move x1 x2
+%%% move x0 x1
+%%% move x2 x0
+%%%
+%%% call f/2
+%%%
+
+prefer_xregs(Linear, St) ->
+ prefer_xregs(Linear, St, #{0=>#{}}).
+
+prefer_xregs([{L,#cg_blk{is=Is0,last=Last0}=Blk0}|Bs], St, Map0) ->
+ Copies0 = maps:get(L, Map0),
+ {Is,Copies} = prefer_xregs_is(Is0, St, Copies0, []),
+ Last = prefer_xregs_terminator(Last0, Copies, St),
+ Blk = Blk0#cg_blk{is=Is,last=Last},
+ Successors = successors(Last),
+ Map = prefer_xregs_successors(Successors, Copies, Map0),
+ [{L,Blk}|prefer_xregs(Bs, St, Map)];
+prefer_xregs([], _St, _Map) -> [].
+
+prefer_xregs_successors([L|Ls], Copies0, Map0) ->
+ case Map0 of
+ #{L:=Copies1} ->
+ Copies = merge_copies(Copies0, Copies1),
+ Map = Map0#{L:=Copies},
+ prefer_xregs_successors(Ls, Copies0, Map);
+ #{} ->
+ Map = Map0#{L=>Copies0},
+ prefer_xregs_successors(Ls, Copies0, Map)
+ end;
+prefer_xregs_successors([], _, Map) -> Map.
+
+prefer_xregs_is([#cg_alloc{}=I|Is], St, Copies0, Acc) ->
+ Copies = case I of
+ #cg_alloc{stack=none,words=#need{h=0,f=0}} ->
+ Copies0;
+ #cg_alloc{} ->
+ #{}
+ end,
+ prefer_xregs_is(Is, St, Copies, [I|Acc]);
+prefer_xregs_is([#cg_set{op=copy,dst=Dst,args=[Src]}=I|Is], St, Copies0, Acc) ->
+ Copies1 = prefer_xregs_prune(I, Copies0, St),
+ Copies = case beam_args([Src,Dst], St) of
+ [Same,Same] -> Copies1;
+ [_,_] -> Copies1#{Dst=>Src}
+ end,
+ prefer_xregs_is(Is, St, Copies, [I|Acc]);
+prefer_xregs_is([#cg_set{op=call,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=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=set_tuple_element}=I|Is], St, Copies, Acc) ->
+ %% FIXME: HiPE translates the following code segment incorrectly:
+ %% {call_ext,3,{extfunc,erlang,setelement,3}}.
+ %% {move,{x,0},{y,3}}.
+ %% {set_tuple_element,{y,1},{y,3},1}.
+ %% Therefore, skip the translation of the arguments for set_tuple_element.
+ 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},
+ Copies = prefer_xregs_prune(I, Copies0, St),
+ prefer_xregs_is(Is, St, Copies, [I|Acc]);
+prefer_xregs_is([], _St, Copies, Acc) ->
+ {reverse(Acc),Copies}.
+
+prefer_xregs_terminator(#cg_br{bool=Arg0}=I, Copies, St) ->
+ Arg = do_prefer_xreg(Arg0, Copies, St),
+ I#cg_br{bool=Arg};
+prefer_xregs_terminator(#cg_ret{arg=Arg0}=I, Copies, St) ->
+ Arg = do_prefer_xreg(Arg0, Copies, St),
+ I#cg_ret{arg=Arg};
+prefer_xregs_terminator(#cg_switch{arg=Arg0}=I, Copies, St) ->
+ Arg = do_prefer_xreg(Arg0, Copies, St),
+ I#cg_switch{arg=Arg}.
+
+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).
+
+%% prefer_xregs_call(Instruction, Copies, St) -> Instruction.
+%% Given a 'call' or 'make_fun' instruction, minimize the number
+%% of 'move' instructions to set up the argument registers.
+%% Prefer using X registers over Y registers, unless that will
+%% result in more 'move' instructions.
+
+prefer_xregs_call(#cg_set{args=[_]}=I, _Copies, _St) ->
+ I;
+prefer_xregs_call(#cg_set{args=[F|Args0]}=I, Copies, St) ->
+ case Args0 of
+ [A0] ->
+ %% Only one argument. Always prefer the X register
+ %% if available.
+ A = do_prefer_xreg(A0, Copies, St),
+ I#cg_set{args=[F,A]};
+ [_|_] ->
+ %% Two or more arguments. Try rewriting arguments in
+ %% two ways and see which way produces the least
+ %% number of 'move' instructions.
+ Args1 = prefer_xregs_call_1(Args0, Copies, 0, St),
+ Args2 = [do_prefer_xreg(A, Copies, St) || A <- Args0],
+ case {count_moves(Args1, St),count_moves(Args2, St)} of
+ {N1,N2} when N1 < N2 ->
+ %% There will be fewer 'move' instructions if
+ %% we keep using Y registers.
+ I#cg_set{args=[F|Args1]};
+ {_,_} ->
+ %% Always use the values in X registers.
+ I#cg_set{args=[F|Args2]}
+ end
+ end.
+
+count_moves(Args, St) ->
+ length(setup_args(beam_args(Args, St))).
+
+prefer_xregs_call_1([#b_var{}=A|As], Copies, X, St) ->
+ case {beam_arg(A, St),Copies} of
+ {{y,_},#{A:=Other}} ->
+ case beam_arg(Other, St) of
+ {x,X} ->
+ %% This value is already in the correct X register.
+ %% It is always benefical to use the X register variable.
+ [Other|prefer_xregs_call_1(As, Copies, X+1, St)];
+ _ ->
+ %% This value is another X register. Keep using
+ %% the Y register variable.
+ [A|prefer_xregs_call_1(As, Copies, X+1, St)]
+ end;
+ {_,_} ->
+ %% The value is not available in an X register.
+ [A|prefer_xregs_call_1(As, Copies, X+1, St)]
+ end;
+prefer_xregs_call_1([A|As], Copies, X, St) ->
+ [A|prefer_xregs_call_1(As, Copies, X+1, St)];
+prefer_xregs_call_1([], _, _, _) -> [].
+
+do_prefer_xreg(#b_var{}=A, Copies, St) ->
+ case {beam_arg(A, St),Copies} of
+ {{y,_},#{A:=Copy}} ->
+ Copy;
+ {_,_} ->
+ A
+ end;
+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);
+merge_copies(Copies0, Copies1) ->
+ merge_copies(Copies1, Copies0).
+
+
+%%%
+%%% Add annotations for the number of live registers.
+%%%
+
+liveness(Linear, #cg{regs=Regs}) ->
+ liveness(reverse(Linear), #{}, Regs, []).
+
+liveness([{L,#cg_blk{is=Is0,last=Last0}=Blk0}|Bs], LiveMap0, Regs, Acc) ->
+ Successors = liveness_successors(Last0),
+ Live0 = ordsets:union([liveness_get(S, LiveMap0) || S <- Successors]),
+ Live1 = liveness_terminator(Last0, Live0),
+ {Is,Live} = liveness_is(reverse(Is0), Regs, Live1, []),
+ LiveMap = LiveMap0#{L=>Live},
+ Blk = Blk0#cg_blk{is=Is},
+ liveness(Bs, LiveMap, Regs, [{L,Blk}|Acc]);
+liveness([], _LiveMap, _Regs, Acc) -> Acc.
+
+liveness_get(S, LiveMap) ->
+ case LiveMap of
+ #{S:=Live} -> Live;
+ #{} -> []
+ end.
+
+liveness_successors(Terminator) ->
+ successors(Terminator) -- [?BADARG_BLOCK].
+
+liveness_is([#cg_alloc{}=I0|Is], Regs, Live, Acc) ->
+ I = I0#cg_alloc{live=num_live(Live, Regs)},
+ liveness_is(Is, Regs, Live, [I|Acc]);
+liveness_is([#cg_set{dst=Dst,args=Args}=I0|Is], Regs, Live0, Acc) ->
+ Live1 = liveness_clobber(I0, Live0, Regs),
+ I1 = liveness_yregs_anno(I0, Live1, Regs),
+ Live2 = liveness_args(Args, Live1),
+ Live = ordsets:del_element(Dst, Live2),
+ I = liveness_anno(I1, Live, Regs),
+ liveness_is(Is, Regs, Live, [I|Acc]);
+liveness_is([], _, Live, Acc) ->
+ {Acc,Live}.
+
+liveness_terminator(#cg_br{bool=Arg}, Live) ->
+ liveness_terminator_1(Arg, Live);
+liveness_terminator(#cg_switch{arg=Arg}, Live) ->
+ liveness_terminator_1(Arg, Live);
+liveness_terminator(#cg_ret{arg=Arg}, Live) ->
+ liveness_terminator_1(Arg, Live).
+
+liveness_terminator_1(#b_var{}=V, Live) ->
+ ordsets:add_element(V, Live);
+liveness_terminator_1(#b_literal{}, Live) ->
+ Live;
+liveness_terminator_1(Reg, Live) ->
+ _ = verify_beam_register(Reg),
+ ordsets:add_element(Reg, Live).
+
+liveness_args([#b_var{}=V|As], Live) ->
+ liveness_args(As, ordsets:add_element(V, Live));
+liveness_args([#b_remote{mod=Mod,name=Name}|As], Live) ->
+ liveness_args([Mod,Name|As], Live);
+liveness_args([A|As], Live) ->
+ case is_beam_register(A) of
+ true ->
+ liveness_args(As, ordsets:add_element(A, Live));
+ false ->
+ liveness_args(As, Live)
+ end;
+liveness_args([], Live) -> Live.
+
+liveness_anno(#cg_set{op=Op}=I, Live, Regs) ->
+ case need_live_anno(Op) of
+ true ->
+ NumLive = num_live(Live, Regs),
+ Anno = (I#cg_set.anno)#{live=>NumLive},
+ I#cg_set{anno=Anno};
+ false ->
+ I
+ end.
+
+liveness_yregs_anno(#cg_set{op=Op,dst=Dst}=I, Live0, Regs) ->
+ case need_live_anno(Op) of
+ true ->
+ Live = ordsets:del_element(Dst, Live0),
+ LiveYregs = [V || V <- Live, is_yreg(V, Regs)],
+ Anno = (I#cg_set.anno)#{live_yregs=>LiveYregs},
+ I#cg_set{anno=Anno};
+ false ->
+ I
+ end.
+
+liveness_clobber(#cg_set{anno=Anno}, Live, Regs) ->
+ case Anno of
+ #{clobbers:=true} ->
+ [R || R <- Live, is_yreg(R, Regs)];
+ _ ->
+ Live
+ end.
+
+is_yreg(R, Regs) ->
+ case Regs of
+ #{R:={y,_}} -> true;
+ #{} -> false
+ end.
+
+num_live(Live, Regs) ->
+ Rs = ordsets:from_list([get_register(V, Regs) || V <- Live]),
+ num_live_1(Rs, 0).
+
+num_live_1([{x,X}|T], X) ->
+ num_live_1(T, X+1);
+num_live_1([{x,_}|_]=T, X) ->
+ %% error({hole,{x,X},expected,Next});
+ num_live_1(T, X+1);
+num_live_1([{y,_}|_], X) ->
+ X;
+num_live_1([{z,_}|_], X) ->
+ X;
+num_live_1([{fr,_}|T], X) ->
+ num_live_1(T, X);
+num_live_1([], X) ->
+ X.
+
+get_live(#cg_set{anno=#{live:=Live}}) ->
+ Live.
+
+%% need_live_anno(Operation) -> true|false.
+%% Return 'true' if the instruction needs a 'live' annotation with
+%% the number live X registers, or 'false' otherwise.
+
+need_live_anno(Op) ->
+ case Op of
+ {bif,_} -> true;
+ bs_get -> true;
+ bs_init -> true;
+ bs_get_position -> true;
+ bs_get_tail -> true;
+ bs_start_match -> true;
+ bs_skip -> true;
+ call -> true;
+ put_map -> true;
+ _ -> false
+ end.
+
+%%%
+%%% Add the following annotations for Y registers:
+%%%
+%%% def_yregs An ordset with variables that refer to live Y registers.
+%%% That is, Y registers that that have been killed
+%%% are not included. This annotation is added to all
+%%% instructions that require Y registers to be initialized.
+%%%
+%%% kill_yregs This annotation is added to call instructions. It is
+%%% an ordset containing variables referring to Y registers
+%%% that will no longer be used after the call instruction.
+%%%
+
+defined(Linear, #cg{regs=Regs}) ->
+ def(Linear, #{}, Regs).
+
+def([{L,#cg_blk{is=Is0,last=Last}=Blk0}|Bs], DefMap0, Regs) ->
+ Def0 = def_get(L, DefMap0),
+ {Is,Def} = def_is(Is0, Regs, Def0, []),
+ Successors = successors(Last),
+ DefMap = def_successors(Successors, Def, DefMap0),
+ Blk = Blk0#cg_blk{is=Is},
+ [{L,Blk}|def(Bs, DefMap, Regs)];
+def([], _, _) -> [].
+
+def_get(L, DefMap) ->
+ case DefMap of
+ #{L:=Def} -> Def;
+ #{} -> []
+ end.
+
+def_is([#cg_alloc{anno=Anno0}=I0|Is], Regs, Def, Acc) ->
+ I = I0#cg_alloc{anno=Anno0#{def_yregs=>Def}},
+ def_is(Is, Regs, Def, [I|Acc]);
+def_is([#cg_set{op=kill_try_tag,args=[#b_var{}=Tag]}=I|Is], Regs, Def0, Acc) ->
+ Def = ordsets:del_element(Tag, Def0),
+ def_is(Is, Regs, Def, [I|Acc]);
+def_is([#cg_set{op=catch_end,args=[#b_var{}=Tag|_]}=I|Is], Regs, Def0, Acc) ->
+ Def = ordsets:del_element(Tag, Def0),
+ def_is(Is, Regs, Def, [I|Acc]);
+def_is([#cg_set{anno=Anno0,op=call,dst=Dst}=I0|Is],
+ Regs, Def0, Acc) ->
+ #{live_yregs:=LiveYregVars} = Anno0,
+ LiveRegs = gb_sets:from_list([maps:get(V, Regs) || V <- LiveYregVars]),
+ Kill0 = ordsets:subtract(Def0, LiveYregVars),
+
+ %% Kill0 is the set of variables that have just died. However, the registers
+ %% used for killed variables may have been reused, so we must check that the
+ %% registers to be killed are not used by other variables.
+ Kill = [K || K <- Kill0, not gb_sets:is_element(maps:get(K, Regs), LiveRegs)],
+ Anno = Anno0#{def_yregs=>Def0,kill_yregs=>Kill},
+ I = I0#cg_set{anno=Anno},
+ Def1 = ordsets:subtract(Def0, Kill),
+ Def = def_add_yreg(Dst, Def1, Regs),
+ def_is(Is, Regs, Def, [I|Acc]);
+def_is([#cg_set{anno=Anno0,op={bif,Bif},dst=Dst,args=Args}=I0|Is],
+ Regs, Def0, Acc) ->
+ Arity = length(Args),
+ I = case is_gc_bif(Bif, Args) orelse not erl_bifs:is_safe(erlang, Bif, Arity) of
+ true ->
+ I0#cg_set{anno=Anno0#{def_yregs=>Def0}};
+ false ->
+ I0
+ end,
+ Def = def_add_yreg(Dst, Def0, Regs),
+ def_is(Is, Regs, Def, [I|Acc]);
+def_is([#cg_set{anno=Anno0,dst=Dst}=I0|Is], Regs, Def0, Acc) ->
+ I = case need_y_init(I0) of
+ true ->
+ I0#cg_set{anno=Anno0#{def_yregs=>Def0}};
+ false ->
+ I0
+ end,
+ Def = def_add_yreg(Dst, Def0, Regs),
+ def_is(Is, Regs, Def, [I|Acc]);
+def_is([], _, Def, Acc) ->
+ {reverse(Acc),Def}.
+
+def_add_yreg(Dst, Def, Regs) ->
+ case is_yreg(Dst, Regs) of
+ true -> ordsets:add_element(Dst, Def);
+ false -> Def
+ end.
+
+def_successors([S|Ss], Def0, DefMap) ->
+ case DefMap of
+ #{S:=Def1} ->
+ Def = ordsets:intersection(Def0, Def1),
+ def_successors(Ss, Def0, DefMap#{S:=Def});
+ #{} ->
+ def_successors(Ss, Def0, DefMap#{S=>Def0})
+ end;
+def_successors([], _, DefMap) -> DefMap.
+
+%% need_y_init(#cg_set{}) -> true|false.
+%% Return true if this instructions needs initialized Y registers
+%% (because the instruction may do a GC or cause an exception
+%% so that the stack will be scanned), or false otherwise.
+
+need_y_init(#cg_set{anno=#{clobbers:=Clobbers}}) -> Clobbers;
+need_y_init(#cg_set{op=bs_get}) -> true;
+need_y_init(#cg_set{op=bs_get_position}) -> true;
+need_y_init(#cg_set{op=bs_get_tail}) -> true;
+need_y_init(#cg_set{op=bs_init}) -> true;
+need_y_init(#cg_set{op=bs_skip,args=[#b_literal{val=Type}|_]}) ->
+ case Type of
+ utf8 -> true;
+ utf16 -> true;
+ utf32 -> true;
+ _ -> false
+ 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{}) -> false.
+
+%% opt_allocate([{BlockLabel,Block}], #st{}) -> [BeamInstruction].
+%% Update the def_yregs field of each #cg_alloc{} that allocates
+%% a stack frame. #cg_alloc.def_yregs will list all Y registers
+%% that will be initialized by the subsequent code (thus, the
+%% listed Y registers don't require init/1 instructions).
+
+opt_allocate(Linear, #cg{regs=Regs}) ->
+ opt_allocate_1(Linear, Regs).
+
+opt_allocate_1([{L,#cg_blk{is=[#cg_alloc{stack=Stk}=I0|Is]}=Blk0}|Bs]=Bs0, Regs)
+ when is_integer(Stk) ->
+ %% Collect the variables that are initialized by copy
+ %% instruction in this block.
+ case ordsets:from_list(opt_allocate_defs(Is, Regs)) of
+ Yregs when length(Yregs) =:= Stk ->
+ %% Those copy instructions are sufficient to fully
+ %% initialize the stack frame.
+ I = I0#cg_alloc{def_yregs=Yregs},
+ [{L,Blk0#cg_blk{is=[I|Is]}}|opt_allocate_1(Bs, Regs)];
+ Yregs0 ->
+ %% Determine a conservative approximation of the Y
+ %% registers that are guaranteed to be initialized by all
+ %% successors of this block, and to it add the variables
+ %% initialized by copy instructions in this block.
+ Yregs1 = opt_alloc_def(Bs0, gb_sets:singleton(L), []),
+ Yregs = ordsets:union(Yregs0, Yregs1),
+ I = I0#cg_alloc{def_yregs=Yregs},
+ [{L,Blk0#cg_blk{is=[I|Is]}}|opt_allocate_1(Bs, Regs)]
+ end;
+opt_allocate_1([B|Bs], Regs) ->
+ [B|opt_allocate_1(Bs, Regs)];
+opt_allocate_1([], _) -> [].
+
+opt_allocate_defs([#cg_set{op=copy,dst=Dst}|Is], Regs) ->
+ case is_yreg(Dst, Regs) of
+ true -> [Dst|opt_allocate_defs(Is, Regs)];
+ false -> []
+ end;
+opt_allocate_defs(_, _Regs) -> [].
+
+opt_alloc_def([{L,#cg_blk{is=Is,last=Last}}|Bs], Ws0, Def0) ->
+ case gb_sets:is_member(L, Ws0) of
+ false ->
+ opt_alloc_def(Bs, Ws0, Def0);
+ true ->
+ case opt_allocate_is(Is) of
+ none ->
+ Succ = successors(Last),
+ Ws = gb_sets:union(Ws0, gb_sets:from_list(Succ)),
+ opt_alloc_def(Bs, Ws, Def0);
+ Def1 when is_list(Def1) ->
+ Def = [Def1|Def0],
+ opt_alloc_def(Bs, Ws0, Def)
+ end
+ end;
+opt_alloc_def([], _, Def) ->
+ ordsets:intersection(Def).
+
+opt_allocate_is([#cg_set{anno=Anno}|Is]) ->
+ case Anno of
+ #{def_yregs:=Yregs} ->
+ Yregs;
+ #{} ->
+ opt_allocate_is(Is)
+ end;
+opt_allocate_is([#cg_alloc{anno=#{def_yregs:=Yregs},stack=none}|_]) ->
+ Yregs;
+opt_allocate_is([#cg_alloc{}|Is]) ->
+ opt_allocate_is(Is);
+opt_allocate_is([]) -> none.
+
+%%%
+%%% Here follows the main code generation functions.
+%%%
+
+%% cg_linear([{BlockLabel,Block}]) -> [BeamInstruction].
+%% Generate BEAM instructions.
+
+cg_linear([{L,#cg_blk{anno=#{recv_set:=L}=Anno0}=B0}|Bs], St0) ->
+ Anno = maps:remove(recv_set, Anno0),
+ B = B0#cg_blk{anno=Anno},
+ {Is,St1} = cg_linear([{L,B}|Bs], St0),
+ {Fail,St} = use_block_label(L, St1),
+ {[{recv_set,Fail}|Is],St};
+cg_linear([{L,#cg_blk{is=Is0,last=Last}}|Bs], St0) ->
+ Next = next_block(Bs),
+ St1 = new_block_label(L, St0),
+ {Is1,St2} = cg_block(Is0, Last, Next, St1),
+ {Is2,St} = cg_linear(Bs, St2),
+ {def_block_label(L, St)++Is1++Is2,St};
+cg_linear([], St) -> {[],St}.
+
+cg_block([#cg_set{op=recv_next}], #cg_br{succ=Lr0}, _Next, St0) ->
+ {Lr,St} = use_block_label(Lr0, St0),
+ {[{loop_rec_end,Lr}],St};
+cg_block([#cg_set{op=wait}], #cg_br{succ=Lr0}, _Next, St0) ->
+ {Lr,St} = use_block_label(Lr0, St0),
+ {[{wait,Lr}],St};
+cg_block(Is0, Last, Next, St0) ->
+ case Last of
+ #cg_br{succ=Next,fail=Next} ->
+ cg_block(Is0, none, St0);
+ #cg_br{succ=Same,fail=Same} ->
+ {Fail,St1} = use_block_label(Same, St0),
+ {Is,St} = cg_block(Is0, none, St1),
+ {Is++[jump(Fail)],St};
+ #cg_br{bool=Bool,succ=Next,fail=Fail0} ->
+ {Fail,St1} = use_block_label(Fail0, St0),
+ {Is,St} = cg_block(Is0, {Bool,Fail}, St1),
+ {Is,St};
+ #cg_br{bool=Bool,succ=Succ0,fail=Fail0} ->
+ {[Succ,Fail],St1} = use_block_labels([Succ0,Fail0], St0),
+ {Is,St} = cg_block(Is0, {Bool,Fail}, St1),
+ {Is++[jump(Succ)],St};
+ #cg_ret{arg=Src0,dealloc=N} ->
+ Src = beam_arg(Src0, St0),
+ cg_block(Is0, {return,Src,N}, St0);
+ #cg_switch{} ->
+ cg_switch(Is0, Last, St0)
+ end.
+
+cg_switch(Is0, Last, St0) ->
+ #cg_switch{arg=Src0,fail=Fail0,list=List0} = Last,
+ Src = beam_arg(Src0, St0),
+ {Fail1,St1} = use_block_label(Fail0, St0),
+ Fail = ensure_label(Fail1, St1),
+ {List1,St2} =
+ flatmapfoldl(fun({V,L}, S0) ->
+ {Lbl,S} = use_block_label(L, S0),
+ {[beam_arg(V, S),Lbl],S}
+ end, St1, List0),
+ {Is1,St} = cg_block(Is0, none, St2),
+ case reverse(Is1) of
+ [{bif,tuple_size,_,[Tuple],{z,_}=Src}|More] ->
+ List = map(fun({integer,Arity}) -> Arity;
+ ({f,_}=F) -> F
+ end, List1),
+ Is = reverse(More, [{select_tuple_arity,Tuple,Fail,{list,List}}]),
+ {Is,St};
+ _ ->
+ SelectVal = {select_val,Src,Fail,{list,List1}},
+ {Is1 ++ [SelectVal],St}
+ end.
+
+jump({f,_}=Fail) ->
+ {jump,Fail};
+jump({catch_tag,Fail}) ->
+ {jump,Fail}.
+
+bif_fail({f,_}=Fail) -> Fail;
+bif_fail({catch_tag,_}) -> {f,0}.
+
+next_block([]) -> none;
+next_block([{Next,_}|_]) -> Next.
+
+ensure_label(Fail0, #cg{ultimate_fail=Lbl}) ->
+ case bif_fail(Fail0) of
+ {f,0} -> {f,Lbl};
+ {f,_}=Fail -> Fail
+ end.
+
+cg_block([#cg_set{anno=#{recv_mark:=L}=Anno0}=I0|T], Context, St0) ->
+ Anno = maps:remove(recv_mark, Anno0),
+ I = I0#cg_set{anno=Anno},
+ {Is,St1} = cg_block([I|T], Context, St0),
+ {Fail,St} = use_block_label(L, St1),
+ {[{recv_mark,Fail}|Is],St};
+cg_block([#cg_set{op=new_try_tag,dst=Tag,args=Args}], {Tag,Fail0}, St) ->
+ {catch_tag,Fail} = Fail0,
+ [Reg,{atom,Kind}] = beam_args([Tag|Args], St),
+ {[{Kind,Reg,Fail}],St};
+cg_block([#cg_set{anno=Anno,op={bif,Name},dst=Dst0,args=Args0}=I,
+ #cg_set{op=succeeded,dst=Bool}], {Bool,Fail0}, St) ->
+ [Dst|Args] = beam_args([Dst0|Args0], St),
+ Line0 = call_line(body, {extfunc,erlang,Name,length(Args)}, Anno),
+ Fail = bif_fail(Fail0),
+ Line = case Fail of
+ {f,0} -> Line0;
+ {f,_} -> []
+ end,
+ case is_gc_bif(Name, Args) of
+ true ->
+ Live = get_live(I),
+ Kill = kill_yregs(Anno, St),
+ {Kill++Line++[{gc_bif,Name,Fail,Live,Args,Dst}],St};
+ false ->
+ {Line++[{bif,Name,Fail,Args,Dst}],St}
+ end;
+cg_block([#cg_set{op={bif,tuple_size},dst=Arity0,args=[Tuple0]},
+ #cg_set{op={bif,'=:='},dst=Bool,args=[Arity0,#b_literal{val=Ar}]}=Eq],
+ {Bool,Fail}=Context, St0) ->
+ Tuple = beam_arg(Tuple0, St0),
+ case beam_arg(Arity0, St0) of
+ {z,_} ->
+ %% The size will only be used once. Combine to a test_arity instruction.
+ Test = {test,test_arity,ensure_label(Fail, St0),[Tuple,Ar]},
+ {[Test],St0};
+ Arity ->
+ %% The size will be used more than once. Must do an explicit
+ %% BIF call followed by the '==' test.
+ TupleSize = {bif,tuple_size,{f,0},[Tuple],Arity},
+ {Is,St} = cg_block([Eq], Context, St0),
+ {[TupleSize|Is],St}
+ end;
+cg_block([#cg_set{op={bif,Name},dst=Dst0,args=Args0}]=Is0, {Dst0,Fail}, St0) ->
+ [Dst|Args] = beam_args([Dst0|Args0], St0),
+ case Dst of
+ {z,_} ->
+ %% The result of the BIF call will only be used once. Convert to
+ %% a test instruction.
+ {Test,St1} = bif_to_test(Name, Args, ensure_label(Fail, St0), St0),
+ {Test,St1};
+ _ ->
+ %% Must explicitly call the BIF since the result will be used
+ %% more than once.
+ {Is1,St1} = cg_block(Is0, none, St0),
+ {Is2,St} = cg_block([], {Dst0,Fail}, St1),
+ {Is1++Is2,St}
+ end;
+cg_block([#cg_set{anno=Anno,op={bif,Name},dst=Dst0,args=Args0}=I|T],
+ Context, St0) ->
+ [Dst|Args] = beam_args([Dst0|Args0], St0),
+ {Is0,St} = cg_block(T, Context, St0),
+ case is_gc_bif(Name, Args) of
+ true ->
+ Line = call_line(body, {extfunc,erlang,Name,length(Args)}, Anno),
+ Live = get_live(I),
+ Kill = kill_yregs(Anno, St),
+ Is = Kill++Line++[{gc_bif,Name,{f,0},Live,Args,Dst}|Is0],
+ {Is,St};
+ false ->
+ Is = [{bif,Name,{f,0},Args,Dst}|Is0],
+ {Is,St}
+ end;
+cg_block([#cg_set{op=bs_init,dst=Dst0,args=Args0,anno=Anno}=I,
+ #cg_set{op=succeeded,dst=Bool}], {Bool,Fail0}, St) ->
+ Fail = bif_fail(Fail0),
+ Line = line(Anno),
+ Alloc = map_get(alloc, Anno),
+ [#b_literal{val=Kind}|Args1] = Args0,
+ case Kind of
+ new ->
+ [Dst,Size,{integer,Unit}] = beam_args([Dst0|Args1], St),
+ Live = get_live(I),
+ {[Line|cg_bs_init(Dst, Size, Alloc, Unit, Live, Fail)],St};
+ private_append ->
+ [Dst,Src,Bits,{integer,Unit}] = beam_args([Dst0|Args1], St),
+ Flags = {field_flags,[]},
+ Is = [Line,{bs_private_append,Fail,Bits,Unit,Src,Flags,Dst}],
+ {Is,St};
+ append ->
+ [Dst,Src,Bits,{integer,Unit}] = beam_args([Dst0|Args1], St),
+ Flags = {field_flags,[]},
+ Live = get_live(I),
+ Is = [Line,{bs_append,Fail,Bits,Alloc,Live,Unit,Src,Flags,Dst}],
+ {Is,St}
+ end;
+cg_block([#cg_set{anno=Anno,op=bs_start_match,dst=Ctx0,args=[Bin0]}=I,
+ #cg_set{op=succeeded,dst=Bool}], {Bool,Fail}, St) ->
+ [Dst,Bin1] = beam_args([Ctx0,Bin0], St),
+ {Bin,Pre} = force_reg(Bin1, Dst),
+ Live = get_live(I),
+ %% num_slots is only set when using the old instructions.
+ case maps:find(num_slots, Anno) of
+ {ok, Slots} ->
+ Is = Pre ++ [{test,bs_start_match2,Fail,Live,[Bin,Slots],Dst}],
+ {Is,St};
+ error ->
+ Is = Pre ++ [{test,bs_start_match3,Fail,Live,[Bin],Dst}],
+ {Is,St}
+ end;
+cg_block([#cg_set{op=bs_get}=Set,
+ #cg_set{op=succeeded,dst=Bool}], {Bool,Fail}, St) ->
+ {cg_bs_get(Fail, Set, St),St};
+cg_block([#cg_set{op=bs_match_string,args=[CtxVar,#b_literal{val=String}]},
+ #cg_set{op=succeeded,dst=Bool}], {Bool,Fail}, St) ->
+ CtxReg = beam_arg(CtxVar, St),
+ Is = [{test,bs_match_string,Fail,[CtxReg,String]}],
+ {Is,St};
+cg_block([#cg_set{dst=Dst0,op=landingpad,args=Args0}|T], Context, St0) ->
+ [Dst,{atom,Kind},Tag] = beam_args([Dst0|Args0], St0),
+ case Kind of
+ 'catch' ->
+ cg_catch(Dst, T, Context, St0);
+ 'try' ->
+ cg_try(Dst, Tag, T, Context, St0)
+ end;
+cg_block([#cg_set{op=kill_try_tag,args=Args0}|Is], Context, St0) ->
+ [Reg] = beam_args(Args0, St0),
+ {Is0,St} = cg_block(Is, Context, St0),
+ {[{try_end,Reg}|Is0],St};
+cg_block([#cg_set{op=catch_end,dst=Dst0,args=Args0}|Is], Context, St0) ->
+ [Dst,Reg,{x,0}] = beam_args([Dst0|Args0], St0),
+ {Is0,St} = cg_block(Is, Context, St0),
+ {[{catch_end,Reg}|copy({x,0}, Dst)++Is0],St};
+cg_block([#cg_set{op=call}=I,
+ #cg_set{op=succeeded,dst=Bool}], {Bool,_Fail}, St) ->
+ %% A call in try/catch block.
+ cg_block([I], none, 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),
+ {cg_test(Op, bif_fail(Fail), Args, Dst, I),St};
+cg_block([#cg_set{op=bs_put,dst=Bool,args=Args0}], {Bool,Fail}, St) ->
+ Args = beam_args(Args0, St),
+ {cg_bs_put(bif_fail(Fail), Args),St};
+cg_block([#cg_set{op=bs_test_tail,dst=Bool,args=Args0}], {Bool,Fail}, St) ->
+ [Ctx,{integer,Bits}] = beam_args(Args0, St),
+ {[{test,bs_test_tail2,bif_fail(Fail),[Ctx,Bits]}],St};
+cg_block([#cg_set{op={float,checkerror},dst=Bool}], {Bool,Fail}, St) ->
+ {[{fcheckerror,bif_fail(Fail)}],St};
+cg_block([#cg_set{op=is_tagged_tuple,dst=Bool,args=Args0}], {Bool,Fail}, St) ->
+ [Src,{integer,Arity},Tag] = beam_args(Args0, St),
+ {[{test,is_tagged_tuple,ensure_label(Fail, St),[Src,Arity,Tag]}],St};
+cg_block([#cg_set{op=is_nonempty_list,dst=Bool,args=Args0}], {Bool,Fail}, St) ->
+ Args = beam_args(Args0, St),
+ {[{test,is_nonempty_list,ensure_label(Fail, St),Args}],St};
+cg_block([#cg_set{op=has_map_field,dst=Bool,args=Args0}], {Bool,Fail}, St) ->
+ [Src,Key] = beam_args(Args0, St),
+ {[{test,has_map_fields,Fail,Src,{list,[Key]}}],St};
+cg_block([#cg_set{op=call}=Call], {_Bool,_Fail}=Context, St0) ->
+ {Is0,St1} = cg_call(Call, body, none, St0),
+ {Is1,St} = cg_block([], Context, St1),
+ {Is0++Is1,St};
+cg_block([#cg_set{op=call,dst=Dst0}=Call], Context, St) ->
+ Dst = beam_arg(Dst0, St),
+ case Context of
+ {return,Dst,_} ->
+ cg_call(Call, tail, Context, St);
+ _ ->
+ cg_call(Call, body, Context, St)
+ end;
+cg_block([#cg_set{op=call}=Call|T], Context, St0) ->
+ {Is0,St1} = cg_call(Call, body, none, St0),
+ {Is1,St} = cg_block(T, Context, St1),
+ {Is0++Is1,St};
+cg_block([#cg_set{op=make_fun,dst=Dst0,args=[Local|Args0]}|T],
+ Context, St0) ->
+ #b_local{name=#b_literal{val=Func},arity=Arity} = Local,
+ [Dst|Args] = beam_args([Dst0|Args0], St0),
+ {FuncLbl,St1} = local_func_label(Func, Arity, St0),
+ Is0 = setup_args(Args) ++
+ [{make_fun2,{f,FuncLbl},0,0,length(Args)}|copy({x,0}, Dst)],
+ {Is1,St} = cg_block(T, Context, St1),
+ {Is0++Is1,St};
+cg_block([#cg_set{op=copy}|_]=T0, Context, St0) ->
+ {Is0,T} = cg_copy(T0, St0),
+ {Is1,St} = cg_block(T, Context, St0),
+ Is = Is0 ++ Is1,
+ case is_call(T) of
+ {yes,Arity} ->
+ {opt_call_moves(Is, Arity),St};
+ no ->
+ {Is,St}
+ end;
+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),
+ {Is,St};
+cg_block([#cg_set{op=Op,dst=Dst0,args=Args0}=Set|T], Context, St0) ->
+ [Dst|Args] = beam_args([Dst0|Args0], St0),
+ Is0 = cg_instr(Op, Args, Dst, Set),
+ {Is1,St} = cg_block(T, Context, St0),
+ {Is0++Is1,St};
+cg_block([#cg_alloc{}=Alloc|T], Context, St0) ->
+ Is0 = cg_alloc(Alloc, St0),
+ {Is1,St} = cg_block(T, Context, St0),
+ {Is0++Is1,St};
+cg_block([], {return,Arg,none}, St) ->
+ Is = copy(Arg, {x,0}) ++ [return],
+ {Is,St};
+cg_block([], {return,Arg,N}, St) ->
+ Is = copy(Arg, {x,0}) ++ [{deallocate,N},return],
+ {Is,St};
+cg_block([], none, St) ->
+ {[],St};
+cg_block([], {Bool0,Fail}, St) ->
+ [Bool] = beam_args([Bool0], St),
+ {[{test,is_eq_exact,Fail,[Bool,{atom,true}]}],St}.
+
+cg_copy(T0, St) ->
+ {Copies,T} = splitwith(fun(#cg_set{op=copy}) -> true;
+ (_) -> false
+ end, T0),
+ Moves0 = cg_copy_1(Copies, St),
+ Moves1 = [Move || {move,Src,Dst}=Move <- Moves0, Src =/= Dst],
+ Scratch = {x,1022},
+ Moves = order_moves(Moves1, Scratch),
+ {Moves,T}.
+
+cg_copy_1([#cg_set{dst=Dst0,args=Args}|T], St) ->
+ [Dst,Src] = beam_args([Dst0|Args], St),
+ Copies = cg_copy_1(T, St),
+ case keymember(Dst, 3, Copies) of
+ true ->
+ %% Will be overwritten. Don't generate a move instruction.
+ Copies;
+ false ->
+ [{move,Src,Dst}|Copies]
+ end;
+cg_copy_1([], _St) -> [].
+
+-define(IS_LITERAL(Val), (Val =:= nil orelse
+ element(1, Val) =:= integer orelse
+ element(1, Val) =:= float orelse
+ element(1, Val) =:= atom orelse
+ element(1, Val) =:= literal)).
+
+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}]},
+ {test,is_eq_exact,Fail,[V2,{atom,true}]},
+ {label,SuccLabel}],St};
+bif_to_test(Op, Args, Fail, St) ->
+ {bif_to_test(Op, Args, Fail),St}.
+
+bif_to_test('and', [V1,V2], Fail) ->
+ [{test,is_eq_exact,Fail,[V1,{atom,true}]},
+ {test,is_eq_exact,Fail,[V2,{atom,true}]}];
+bif_to_test('not', [Var], Fail) ->
+ [{test,is_eq_exact,Fail,[Var,{atom,false}]}];
+bif_to_test(Name, Args, Fail) ->
+ [bif_to_test_1(Name, Args, Fail)].
+
+bif_to_test_1(is_atom, [_]=Ops, Fail) ->
+ {test,is_atom,Fail,Ops};
+bif_to_test_1(is_boolean, [_]=Ops, Fail) ->
+ {test,is_boolean,Fail,Ops};
+bif_to_test_1(is_binary, [_]=Ops, Fail) ->
+ {test,is_binary,Fail,Ops};
+bif_to_test_1(is_bitstring,[_]=Ops, Fail) ->
+ {test,is_bitstr,Fail,Ops};
+bif_to_test_1(is_float, [_]=Ops, Fail) ->
+ {test,is_float,Fail,Ops};
+bif_to_test_1(is_function, [_]=Ops, Fail) ->
+ {test,is_function,Fail,Ops};
+bif_to_test_1(is_function, [_,_]=Ops, Fail) ->
+ {test,is_function2,Fail,Ops};
+bif_to_test_1(is_integer, [_]=Ops, Fail) ->
+ {test,is_integer,Fail,Ops};
+bif_to_test_1(is_list, [_]=Ops, Fail) ->
+ {test,is_list,Fail,Ops};
+bif_to_test_1(is_map, [_]=Ops, Fail) ->
+ {test,is_map,Fail,Ops};
+bif_to_test_1(is_number, [_]=Ops, Fail) ->
+ {test,is_number,Fail,Ops};
+bif_to_test_1(is_pid, [_]=Ops, Fail) ->
+ {test,is_pid,Fail,Ops};
+bif_to_test_1(is_port, [_]=Ops, Fail) ->
+ {test,is_port,Fail,Ops};
+bif_to_test_1(is_reference, [_]=Ops, Fail) ->
+ {test,is_reference,Fail,Ops};
+bif_to_test_1(is_tuple, [_]=Ops, Fail) ->
+ {test,is_tuple,Fail,Ops};
+bif_to_test_1('=<', [A,B], Fail) ->
+ {test,is_ge,Fail,[B,A]};
+bif_to_test_1('>', [A,B], Fail) ->
+ {test,is_lt,Fail,[B,A]};
+bif_to_test_1('<', [_,_]=Ops, Fail) ->
+ {test,is_lt,Fail,Ops};
+bif_to_test_1('>=', [_,_]=Ops, Fail) ->
+ {test,is_ge,Fail,Ops};
+bif_to_test_1('==', [C,A], Fail) when ?IS_LITERAL(C) ->
+ {test,is_eq,Fail,[A,C]};
+bif_to_test_1('==', [_,_]=Ops, Fail) ->
+ {test,is_eq,Fail,Ops};
+bif_to_test_1('/=', [C,A], Fail) when ?IS_LITERAL(C) ->
+ {test,is_ne,Fail,[A,C]};
+bif_to_test_1('/=', [_,_]=Ops, Fail) ->
+ {test,is_ne,Fail,Ops};
+bif_to_test_1('=:=', [C,A], Fail) when ?IS_LITERAL(C) ->
+ {test,is_eq_exact,Fail,[A,C]};
+bif_to_test_1('=:=', [_,_]=Ops, Fail) ->
+ {test,is_eq_exact,Fail,Ops};
+bif_to_test_1('=/=', [C,A], Fail) when ?IS_LITERAL(C) ->
+ {test,is_ne_exact,Fail,[A,C]};
+bif_to_test_1('=/=', [_,_]=Ops, Fail) ->
+ {test,is_ne_exact,Fail,Ops}.
+
+opt_call_moves(Is0, Arity) ->
+ {Moves0,Is} = splitwith(fun({move,_,_}) -> true;
+ ({kill,_}) -> true;
+ (_) -> false
+ end, Is0),
+ Moves = opt_call_moves_1(Moves0, Arity),
+ Moves ++ Is.
+
+opt_call_moves_1([{move,Src,{x,_}=Tmp}=M1|[{kill,_}|_]=Is], Arity) ->
+ %% There could be a {move,Tmp,{x,0}} instruction after the
+ %% kill/1 instructions (moved to there by opt_move_to_x0/1).
+ case splitwith(fun({kill,_}) -> true;
+ (_) -> false
+ end, Is) of
+ {Kills,[{move,{x,_}=Tmp,{x,0}}=M2]} ->
+ %% The two move/2 instructions (M1 and M2) can be combined
+ %% to one. The question is, though, is it safe to place
+ %% them after the kill/1 instructions?
+ case is_killed(Src, Kills, Arity) of
+ true ->
+ %% Src (a Y register) is killed by one of the
+ %% kill/1 instructions. Thus M1 and M2
+ %% must be placed before the kill/1 instructions
+ %% (essentially undoing what opt_move_to_x0/1
+ %% did, which turned out to be a pessimization
+ %% in this case).
+ opt_call_moves_1([M1,M2|Kills], Arity);
+ false ->
+ %% Src is not killed by any of the kill/1
+ %% instructions. Thus it is safe to place
+ %% M1 and M2 after the kill/1 instructions.
+ opt_call_moves_1(Kills++[M1,M2], Arity)
+ end;
+ {_,_} ->
+ [M1|Is]
+ end;
+opt_call_moves_1([{move,Src,{x,_}=Tmp}=M1,{move,Tmp,Dst}=M2|Is], Arity) ->
+ case is_killed(Tmp, Is, Arity) of
+ true ->
+ %% The X register Tmp is never used again. We can collapse
+ %% the two move instruction into one.
+ [{move,Src,Dst}|opt_call_moves_1(Is, Arity)];
+ false ->
+ [M1|opt_call_moves_1([M2|Is], Arity)]
+ end;
+opt_call_moves_1([M|Ms], Arity) ->
+ [M|opt_call_moves_1(Ms, Arity)];
+opt_call_moves_1([], _Arity) -> [].
+
+is_killed(Y, [{kill,Y}|_], _) ->
+ true;
+is_killed(R, [{kill,_}|Is], Arity) ->
+ is_killed(R, Is, Arity);
+is_killed(R, [{move,R,_}|_], _) ->
+ false;
+is_killed(R, [{move,_,R}|_], _) ->
+ true;
+is_killed(R, [{move,_,_}|Is], Arity) ->
+ is_killed(R, Is, Arity);
+is_killed({x,X}, [], Arity) ->
+ X >= Arity;
+is_killed({y,_}, [], _) ->
+ false.
+
+cg_alloc(#cg_alloc{stack=none,words=#need{h=0,f=0}}, _St) ->
+ [];
+cg_alloc(#cg_alloc{stack=none,words=Need,live=Live}, _St) ->
+ [{test_heap,alloc(Need),Live}];
+cg_alloc(#cg_alloc{stack=Stk,words=Need,live=Live,def_yregs=DefYregs},
+ #cg{regs=Regs}) when is_integer(Stk) ->
+ Alloc = alloc(Need),
+ All = [{y,Y} || Y <- lists:seq(0, Stk-1)],
+ Def = ordsets:from_list([maps:get(V, Regs) || V <- DefYregs]),
+ NeedInit = ordsets:subtract(All, Def),
+ NoZero = length(Def)*2 > Stk,
+ I = case {NoZero,Alloc} of
+ {true,0} -> {allocate,Stk,Live};
+ {true,_} -> {allocate_heap,Stk,Alloc,Live};
+ {false,0} -> {allocate_zero,Stk,Live};
+ {false,_} -> {allocate_heap_zero,Stk,Alloc,Live}
+ end,
+ [I|case NoZero of
+ true -> [{init,Y} || Y <- NeedInit];
+ false -> []
+ end].
+
+alloc(#need{h=Words,f=0}) ->
+ Words;
+alloc(#need{h=Words,f=Floats}) ->
+ {alloc,[{words,Words},{floats,Floats}]}.
+
+is_call([#cg_set{op=call,args=[#b_var{}|Args]}|_]) ->
+ {yes,1+length(Args)};
+is_call([#cg_set{op=call,args=[_|Args]}|_]) ->
+ {yes,length(Args)};
+is_call([#cg_set{op=make_fun,args=[_|Args]}|_]) ->
+ {yes,length(Args)};
+is_call(_) ->
+ no.
+
+cg_call(#cg_set{anno=Anno,op=call,dst=Dst0,args=[#b_local{}=Func0|Args0]},
+ Where, Context, St0) ->
+ [Dst|Args] = beam_args([Dst0|Args0], St0),
+ #b_local{name=Name0,arity=Arity} = Func0,
+ {atom,Name} = beam_arg(Name0, St0),
+ {FuncLbl,St} = local_func_label(Name, Arity, St0),
+ Line = call_line(Where, local, Anno),
+ Call = build_call(call, Arity, {f,FuncLbl}, Context, Dst),
+ Is = setup_args(Args, Anno, Context, St) ++ Line ++ Call,
+ case Anno of
+ #{ result_type := Info } ->
+ {Is ++ [{'%', {type_info, Dst, Info}}], St};
+ #{} ->
+ {Is, St}
+ end;
+cg_call(#cg_set{anno=Anno0,op=call,dst=Dst0,args=[#b_remote{}=Func0|Args0]},
+ Where, Context, St) ->
+ [Dst|Args] = beam_args([Dst0|Args0], St),
+ #b_remote{mod=Mod0,name=Name0,arity=Arity} = Func0,
+ case {beam_arg(Mod0, St),beam_arg(Name0, St)} of
+ {{atom,Mod},{atom,Name}} ->
+ Func = {extfunc,Mod,Name,Arity},
+ Line = call_line(Where, Func, Anno0),
+ Call = build_call(call_ext, Arity, Func, Context, Dst),
+ Anno = case erl_bifs:is_exit_bif(Mod, Name, Arity) of
+ true ->
+ %% There is no need to kill Y registers
+ %% before calling an exit BIF.
+ maps:remove(kill_yregs, Anno0);
+ false ->
+ Anno0
+ end,
+ Is = setup_args(Args, Anno, Context, St) ++ Line ++ Call,
+ {Is,St};
+ {Mod,Name} ->
+ Apply = build_apply(Arity, Context, Dst),
+ Is = setup_args(Args++[Mod,Name], Anno0, Context, St) ++
+ [line(Anno0)] ++ Apply,
+ {Is,St}
+ end;
+cg_call(#cg_set{anno=Anno,op=call,dst=Dst0,args=Args0},
+ Where, Context, St) ->
+ [Dst,Func|Args] = beam_args([Dst0|Args0], St),
+ Line = call_line(Where, Func, Anno),
+ Arity = length(Args),
+ Call = build_call(call_fun, Arity, Func, Context, Dst),
+ Is = setup_args(Args++[Func], Anno, Context, St) ++ Line ++ Call,
+ {Is,St}.
+
+build_call(call_fun, Arity, _Func, none, Dst) ->
+ [{call_fun,Arity}|copy({x,0}, Dst)];
+build_call(call_fun, Arity, _Func, {return,Dst,N}, Dst) when is_integer(N) ->
+ [{call_fun,Arity},{deallocate,N},return];
+build_call(call_fun, Arity, _Func, {return,Val,N}, _Dst) when is_integer(N) ->
+ [{call_fun,Arity},{move,Val,{x,0}},{deallocate,N},return];
+build_call(call_ext, 2, {extfunc,erlang,'!',2}, none, Dst) ->
+ [send|copy({x,0}, Dst)];
+build_call(call_ext, 2, {extfunc,erlang,'!',2}, {return,Dst,N}, Dst)
+ when is_integer(N) ->
+ [send,{deallocate,N},return];
+build_call(Prefix, Arity, Func, {return,Dst,none}, Dst) ->
+ I = case Prefix of
+ call -> call_only;
+ call_ext -> call_ext_only
+ end,
+ [{I,Arity,Func}];
+build_call(call_ext, Arity, {extfunc,Mod,Name,Arity}=Func, {return,_,none}, _Dst) ->
+ true = erl_bifs:is_exit_bif(Mod, Name, Arity), %Assertion.
+ [{call_ext_only,Arity,Func}];
+build_call(Prefix, Arity, Func, {return,Dst,N}, Dst) when is_integer(N) ->
+ I = case Prefix of
+ call -> call_last;
+ call_ext -> call_ext_last
+ end,
+ [{I,Arity,Func,N}];
+build_call(I, Arity, Func, {return,Val,N}, _Dst) when is_integer(N) ->
+ [{I,Arity,Func}|copy(Val, {x,0})++[{deallocate,N},return]];
+build_call(I, Arity, Func, none, Dst) ->
+ [{I,Arity,Func}|copy({x,0}, Dst)].
+
+build_apply(Arity, {return,Dst,N}, Dst) when is_integer(N) ->
+ [{apply_last,Arity,N}];
+build_apply(Arity, {return,Val,N}, _Dst) when is_integer(N) ->
+ [{apply,Arity}|copy(Val, {x,0})++[{deallocate,N},return]];
+build_apply(Arity, none, Dst) ->
+ [{apply,Arity}|copy({x,0}, Dst)].
+
+cg_instr(put_map, [{atom,assoc},SrcMap|Ss], Dst, Set) ->
+ Live = get_live(Set),
+ [{put_map_assoc,{f,0},SrcMap,Dst,Live,{list,Ss}}];
+cg_instr(bs_get_tail, [Src], Dst, Set) ->
+ Live = get_live(Set),
+ [{bs_get_tail,Src,Dst,Live}];
+cg_instr(bs_get_position, [Ctx], Dst, Set) ->
+ Live = get_live(Set),
+ [{bs_get_position,Ctx,Dst,Live}];
+cg_instr(Op, Args, Dst, _Set) ->
+ cg_instr(Op, Args, Dst).
+
+cg_instr(bs_init_writable, Args, Dst) ->
+ setup_args(Args) ++ [bs_init_writable|copy({x,0}, Dst)];
+cg_instr(bs_restore, [Ctx,Slot], _Dst) ->
+ case Slot of
+ {integer,N} ->
+ [{bs_restore2,Ctx,N}];
+ {atom,start} ->
+ [{bs_restore2,Ctx,Slot}]
+ end;
+cg_instr(bs_save, [Ctx,Slot], _Dst) ->
+ {integer,N} = Slot,
+ [{bs_save2,Ctx,N}];
+cg_instr(bs_set_position, [Ctx,Pos], _Dst) ->
+ [{bs_set_position,Ctx,Pos}];
+cg_instr(build_stacktrace, Args, Dst) ->
+ setup_args(Args) ++ [build_stacktrace|copy({x,0}, Dst)];
+cg_instr(set_tuple_element=Op, [New,Tuple,{integer,Index}], _Dst) ->
+ [{Op,New,Tuple,Index}];
+cg_instr({float,clearerror}, [], _Dst) ->
+ [fclearerror];
+cg_instr({float,get}, [Src], Dst) ->
+ [{fmove,Src,Dst}];
+cg_instr({float,put}, [Src], Dst) ->
+ [{fmove,Src,Dst}];
+cg_instr(get_hd=Op, [Src], Dst) ->
+ [{Op,Src,Dst}];
+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(put_list=Op, [Hd,Tl], Dst) ->
+ [{Op,Hd,Tl,Dst}];
+cg_instr(put_tuple, Elements, Dst) ->
+ [{put_tuple2,Dst,{list,Elements}}];
+cg_instr(put_tuple_arity, [{integer,Arity}], Dst) ->
+ [{put_tuple,Arity,Dst}];
+cg_instr(put_tuple_elements, Elements, _Dst) ->
+ [{put,E} || E <- Elements];
+cg_instr(raw_raise, Args, Dst) ->
+ setup_args(Args) ++ [raw_raise|copy({x,0}, Dst)];
+cg_instr(remove_message, [], _Dst) ->
+ [remove_message];
+cg_instr(resume, [A,B], _Dst) ->
+ [{bif,raise,{f,0},[A,B],{x,0}}];
+cg_instr(timeout, [], _Dst) ->
+ [timeout].
+
+cg_test(bs_add=Op, Fail, [Src1,Src2,{integer,Unit}], Dst, _I) ->
+ [{Op,Fail,[Src1,Src2,Unit],Dst}];
+cg_test(bs_skip, Fail, Args, _Dst, I) ->
+ cg_bs_skip(Fail, Args, I);
+cg_test(bs_utf8_size=Op, Fail, [Src], Dst, _I) ->
+ [{Op,Fail,Src,Dst}];
+cg_test(bs_utf16_size=Op, Fail, [Src], Dst, _I) ->
+ [{Op,Fail,Src,Dst}];
+cg_test({float,convert}, Fail, [Src], Dst, _I) ->
+ {f,0} = Fail, %Assertion.
+ [{fconv,Src,Dst}];
+cg_test({float,Op0}, Fail, Args, Dst, #cg_set{anno=Anno}) ->
+ Op = case Op0 of
+ '+' -> fadd;
+ '-' when length(Args) =:= 2 -> fsub;
+ '-' -> fnegate;
+ '*' -> fmul;
+ '/' -> fdiv
+ end,
+ [line(Anno),{bif,Op,Fail,Args,Dst}];
+cg_test(get_map_element, Fail, [Map,Key], Dst, _I) ->
+ [{get_map_elements,Fail,Map,{list,[Key,Dst]}}];
+cg_test(peek_message, Fail, [], Dst, _I) ->
+ [{loop_rec,Fail,{x,0}}|copy({x,0}, Dst)];
+cg_test(put_map, Fail, [{atom,exact},SrcMap|Ss], Dst, Set) ->
+ Live = get_live(Set),
+ [{put_map_exact,Fail,SrcMap,Dst,Live,{list,Ss}}];
+cg_test(wait_timeout, Fail, [Timeout], _Dst, _) ->
+ case Timeout of
+ {atom,infinity} ->
+ [{wait,Fail}];
+ _ ->
+ [{wait_timeout,Fail,Timeout}]
+ end.
+
+cg_bs_get(Fail, #cg_set{dst=Dst0,args=[#b_literal{val=Type}|Ss0]}=Set, St) ->
+ Op = case Type of
+ integer -> bs_get_integer2;
+ float -> bs_get_float2;
+ binary -> bs_get_binary2;
+ utf8 -> bs_get_utf8;
+ utf16 -> bs_get_utf16;
+ utf32 -> bs_get_utf32
+ end,
+ [Dst|Ss1] = beam_args([Dst0|Ss0], St),
+ Ss = case Ss1 of
+ [Ctx,{literal,Flags},Size,{integer,Unit}] ->
+ %% Plain integer/float/binary.
+ [Ctx,Size,Unit,field_flags(Flags, Set)];
+ [Ctx,{literal,Flags}] ->
+ %% Utf8/16/32.
+ [Ctx,field_flags(Flags, Set)]
+ end,
+ Live = get_live(Set),
+ [{test,Op,Fail,Live,Ss,Dst}].
+
+cg_bs_skip(Fail, [{atom,Type}|Ss0], Set) ->
+ Op = case Type of
+ utf8 -> bs_skip_utf8;
+ utf16 -> bs_skip_utf16;
+ utf32 -> bs_skip_utf32;
+ _ -> bs_skip_bits2
+ end,
+ Live = get_live(Set),
+ Ss = case Ss0 of
+ [Ctx,{literal,Flags},Size,{integer,Unit}] ->
+ %% Plain integer/float/binary.
+ [Ctx,Size,Unit,field_flags(Flags, Set)];
+ [Ctx,{literal,Flags}] ->
+ %% Utf8/16/32.
+ [Ctx,Live,field_flags(Flags, Set)]
+ end,
+ case {Type,Ss} of
+ {binary,[_,{atom,all},1,_]} ->
+ [];
+ {binary,[R,{atom,all},U,_]} ->
+ [{test,bs_test_unit,Fail,[R,U]}];
+ {_,_} ->
+ [{test,Op,Fail,Ss}]
+ end.
+
+field_flags(Flags, #cg_set{anno=#{location:={File,Line}}}) ->
+ {field_flags,[{anno,[Line,{file,File}]}|Flags]};
+field_flags(Flags, _) ->
+ {field_flags,Flags}.
+
+cg_bs_put(Fail, [{atom,Type},{literal,Flags}|Args]) ->
+ Op = case Type of
+ integer -> bs_put_integer;
+ float -> bs_put_float;
+ binary -> bs_put_binary;
+ utf8 -> bs_put_utf8;
+ utf16 -> bs_put_utf16;
+ utf32 -> bs_put_utf32
+ end,
+ case Args of
+ [Src,Size,{integer,Unit}] ->
+ [{Op,Fail,Size,Unit,{field_flags,Flags},Src}];
+ [Src] ->
+ [{Op,Fail,{field_flags,Flags},Src}]
+ end.
+
+cg_bs_init(Dst, Size0, Alloc, Unit, Live, Fail) ->
+ Op = case Unit of
+ 1 -> bs_init_bits;
+ 8 -> bs_init2
+ end,
+ Size = cg_bs_init_size(Size0),
+ [{Op,Fail,Size,Alloc,Live,{field_flags,[]},Dst}].
+
+cg_bs_init_size({x,_}=R) -> R;
+cg_bs_init_size({y,_}=R) -> R;
+cg_bs_init_size({integer,Int}) -> Int.
+
+cg_catch(Agg, T0, Context, St0) ->
+ {Moves,T1} = cg_extract(T0, Agg, St0),
+ {T,St} = cg_block(T1, Context, St0),
+ {Moves++T,St}.
+
+cg_try(Agg, Tag, T0, Context, St0) ->
+ {Moves0,T1} = cg_extract(T0, Agg, St0),
+ Moves = order_moves(Moves0, {x,3}),
+ [#cg_set{op=kill_try_tag}|T2] = T1,
+ {T,St} = cg_block(T2, Context, St0),
+ {[{try_case,Tag}|Moves++T],St}.
+
+cg_extract([#cg_set{op=extract,dst=Dst0,args=Args0}|Is0], Agg, St) ->
+ [Dst,Agg,{integer,X}] = beam_args([Dst0|Args0], St),
+ {Ds,Is} = cg_extract(Is0, Agg, St),
+ case keymember(Dst, 3, Ds) of
+ true ->
+ %% This destination will be overwritten.
+ {Ds,Is};
+ false ->
+ {copy({x,X}, Dst)++Ds,Is}
+ end;
+cg_extract(Is, _, _) ->
+ {[],Is}.
+
+copy(Src, Src) -> [];
+copy(Src, Dst) -> [{move,Src,Dst}].
+
+force_reg({literal,_}=Lit, Reg) ->
+ {Reg,[{move,Lit,Reg}]};
+force_reg({integer,_}=Lit, Reg) ->
+ {Reg,[{move,Lit,Reg}]};
+force_reg({atom,_}=Lit, Reg) ->
+ {Reg,[{move,Lit,Reg}]};
+force_reg({float,_}=Lit, Reg) ->
+ {Reg,[{move,Lit,Reg}]};
+force_reg(nil=Lit, Reg) ->
+ {Reg,[{move,Lit,Reg}]};
+force_reg({Kind,_}=R, _) when Kind =:= x; Kind =:= y ->
+ {R,[]}.
+
+%% successors(Terminator) -> [Successor].
+%% Return an ordset of all successors for the given terminator.
+
+successors(#cg_br{succ=Succ,fail=Fail}) ->
+ ordsets:from_list([Succ,Fail]);
+successors(#cg_switch{fail=Fail,list=List}) ->
+ ordsets:from_list([Fail|[Lbl || {_,Lbl} <- List]]);
+successors(#cg_ret{}) -> [].
+
+%% linearize(Blocks) -> [{BlockLabel,#cg_blk{}}].
+%% Linearize the intermediate representation of the code. Also
+%% translate blocks from the SSA records to internal record types
+%% used only in this module.
+
+linearize(Blocks) ->
+ Linear = beam_ssa:linearize(Blocks),
+ linearize_1(Linear, Blocks).
+
+linearize_1([{?BADARG_BLOCK,_}|Ls], Blocks) ->
+ linearize_1(Ls, Blocks);
+linearize_1([{L,Block0}|Ls], Blocks) ->
+ Block = translate_block(L, Block0, Blocks),
+ [{L,Block}|linearize_1(Ls, Blocks)];
+linearize_1([], _Blocks) -> [].
+
+%% translate_block(BlockLabel, #b_blk{}, Blocks) -> #cg_blk{}.
+%% Translate a block to the internal records used in this module.
+%% Also eliminate phi nodes, replacing them with 'copy' instructions
+%% in the predecessor blocks.
+
+translate_block(L, #b_blk{anno=Anno,is=Is0,last=Last0}, Blocks) ->
+ Last = translate_terminator(Last0),
+ PhiCopies = translate_phis(L, Last, Blocks),
+ Is1 = translate_is(Is0, PhiCopies),
+ Is = case Anno of
+ #{frame_size:=Size} ->
+ Alloc = #cg_alloc{stack=Size},
+ [Alloc|Is1];
+ #{} -> Is1
+ end,
+ #cg_blk{anno=Anno,is=Is,last=Last}.
+
+translate_is([#b_set{op=phi}|Is], Tail) ->
+ translate_is(Is, Tail);
+translate_is([#b_set{anno=Anno0,op=Op,dst=Dst,args=Args}=I|Is], Tail) ->
+ Anno = case beam_ssa:clobbers_xregs(I) of
+ true -> Anno0#{clobbers=>true};
+ false -> Anno0
+ end,
+ [#cg_set{anno=Anno,op=Op,dst=Dst,args=Args}|translate_is(Is, Tail)];
+translate_is([], Tail) -> Tail.
+
+translate_terminator(#b_ret{anno=Anno,arg=Arg}) ->
+ Dealloc = case Anno of
+ #{deallocate:=N} -> N;
+ #{} -> none
+ end,
+ #cg_ret{arg=Arg,dealloc=Dealloc};
+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=#b_literal{val=false},fail=Fail}) ->
+ #cg_br{bool=#b_literal{val=true},succ=Fail,fail=Fail};
+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_phis(L, #cg_br{succ=Target,fail=Target}, Blocks) ->
+ #b_blk{is=Is} = maps:get(Target, Blocks),
+ Phis = takewhile(fun(#b_set{op=phi}) -> true;
+ (#b_set{}) -> false
+ end, Is),
+ phi_copies(Phis, L);
+translate_phis(_, _, _) -> [].
+
+phi_copies([#b_set{dst=Dst,args=PhiArgs}|Sets], L) ->
+ CopyArgs = [V || {V,Target} <- PhiArgs, Target =:= L],
+ [#cg_set{op=copy,dst=Dst,args=CopyArgs}|phi_copies(Sets, L)];
+phi_copies([], _) -> [].
+
+%% opt_move_to_x0([Instruction]) -> [Instruction].
+%% Simple peep-hole optimization to move a {move,Any,{x,0}} past
+%% any kill up to the next call instruction. (To give the loader
+%% an opportunity to combine the 'move' and the 'call' instructions.)
+
+opt_move_to_x0(Moves) ->
+ opt_move_to_x0(Moves, []).
+
+opt_move_to_x0([{move,_,{x,0}}=I|Is0], Acc0) ->
+ case move_past_kill(Is0, I, Acc0) of
+ impossible -> opt_move_to_x0(Is0, [I|Acc0]);
+ {Is,Acc} -> opt_move_to_x0(Is, Acc)
+ end;
+opt_move_to_x0([I|Is], Acc) ->
+ opt_move_to_x0(Is, [I|Acc]);
+opt_move_to_x0([], Acc) -> reverse(Acc).
+
+move_past_kill([{kill,Src}|_], {move,Src,_}, _) ->
+ impossible;
+move_past_kill([{kill,_}=I|Is], Move, Acc) ->
+ move_past_kill(Is, Move, [I|Acc]);
+move_past_kill(Is, Move, Acc) ->
+ {Is,[Move|Acc]}.
+
+%% setup_args(Args, Anno, Context) -> [Instruction].
+%% setup_args(Args) -> [Instruction].
+%% Set up X registers for a call.
+
+setup_args(Args, Anno, none, St) ->
+ case {setup_args(Args),kill_yregs(Anno, St)} of
+ {Moves,[]} ->
+ Moves;
+ {Moves,Kills} ->
+ opt_move_to_x0(Moves ++ Kills)
+ end;
+setup_args(Args, _, _, _) ->
+ setup_args(Args).
+
+setup_args([]) ->
+ [];
+setup_args([_|_]=Args) ->
+ Moves = gen_moves(Args, 0, []),
+ Scratch = {x,1+last(sort([length(Args)-1|[X || {x,X} <- Args]]))},
+ order_moves(Moves, Scratch).
+
+%% kill_yregs(Anno, #cg{}) -> [{kill,{y,Y}}].
+%% Kill Y registers that will not be used again.
+
+kill_yregs(#{kill_yregs:=Kill}, #cg{regs=Regs}) ->
+ ordsets:from_list([{kill,maps:get(V, Regs)} || V <- Kill]);
+kill_yregs(#{}, #cg{}) -> [].
+
+%% gen_moves(As, I, Acc)
+%% Generate the basic move instruction to move the arguments
+%% to their proper registers. The list will be sorted on
+%% destinations. (I.e. the move to {x,0} will be first --
+%% see the comment to order_moves/2.)
+
+gen_moves([A|As], I, Acc) ->
+ gen_moves(As, I+1, copy(A, {x,I}) ++ Acc);
+gen_moves([], _, Acc) ->
+ keysort(3, Acc).
+
+%% order_moves([Move], ScratchReg) -> [Move]
+%% Orders move instruction so that source registers are not
+%% destroyed before they are used. If there are cycles
+%% (such as {move,{x,0},{x,1}}, {move,{x,1},{x,1}}),
+%% the scratch register is used to break up the cycle.
+%% If possible, the first move of the input list is placed
+%% last in the result list (to make the move to {x,0} occur
+%% just before the call to allow the Beam loader to coalesce
+%% the instructions).
+
+order_moves(Ms, Scr) -> order_moves(Ms, Scr, []).
+
+order_moves([{move,_,_}=M|Ms0], ScrReg, Acc0) ->
+ {Chain,Ms} = collect_chain(Ms0, [M], ScrReg),
+ Acc = reverse(Chain, Acc0),
+ order_moves(Ms, ScrReg, Acc);
+order_moves([], _, Acc) -> Acc.
+
+collect_chain(Ms, Path, ScrReg) ->
+ collect_chain(Ms, Path, [], ScrReg).
+
+collect_chain([{move,Src,Same}=M|Ms0], [{move,Same,_}|_]=Path, Others, ScrReg) ->
+ case keymember(Src, 3, Path) of
+ false ->
+ collect_chain(reverse(Others, Ms0), [M|Path], [], ScrReg);
+ true ->
+ %% There is a cycle, which we must break up.
+ {break_up_cycle(M, Path, ScrReg),reverse(Others, Ms0)}
+ end;
+collect_chain([M|Ms], Path, Others, ScrReg) ->
+ collect_chain(Ms, Path, [M|Others], ScrReg);
+collect_chain([], Path, Others, _) ->
+ {Path,Others}.
+
+break_up_cycle({move,Src,_}=M, Path, ScrReg) ->
+ [{move,ScrReg,Src},M|break_up_cycle1(Src, Path, ScrReg)].
+
+break_up_cycle1(Dst, [{move,Src,Dst}|Path], ScrReg) ->
+ [{move,Src,ScrReg}|Path];
+break_up_cycle1(Dst, [M|Path], LastMove) ->
+ [M|break_up_cycle1(Dst, Path, LastMove)].
+
+%%%
+%%% General utility functions.
+%%%
+
+verify_beam_register({x,_}=Reg) -> Reg.
+
+is_beam_register({x,_}) -> true;
+is_beam_register(_) -> false.
+
+get_register(V, Regs) ->
+ case is_beam_register(V) of
+ true -> V;
+ false -> maps:get(V, Regs)
+ end.
+
+beam_args(As, St) ->
+ [beam_arg(A, St) || A <- As].
+
+beam_arg(#b_var{}=Name, #cg{regs=Regs}) ->
+ maps:get(Name, Regs);
+beam_arg(#b_literal{val=Val}, _) ->
+ if
+ is_atom(Val) -> {atom,Val};
+ is_float(Val) -> {float,Val};
+ is_integer(Val) -> {integer,Val};
+ Val =:= [] -> nil;
+ true -> {literal,Val}
+ end;
+beam_arg(Reg, _) ->
+ verify_beam_register(Reg).
+
+new_block_label(L, St0) ->
+ {_Lbl,St} = label_for_block(L, St0),
+ St.
+
+def_block_label(L, #cg{labels=Labels,used_labels=Used}) ->
+ Lbl = maps:get(L, Labels),
+ case gb_sets:is_member(Lbl, Used) of
+ false -> [];
+ true -> [{label,Lbl}]
+ end.
+
+use_block_labels(Ls, St) ->
+ mapfoldl(fun use_block_label/2, St, Ls).
+
+use_block_label(L, #cg{used_labels=Used,catches=Catches}=St0) ->
+ {Lbl,St} = label_for_block(L, St0),
+ case gb_sets:is_member(L, Catches) of
+ true ->
+ {{catch_tag,{f,Lbl}},
+ St#cg{used_labels=gb_sets:add(Lbl, Used)}};
+ false ->
+ {{f,Lbl},St#cg{used_labels=gb_sets:add(Lbl, Used)}}
+ end.
+
+label_for_block(L, #cg{labels=Labels0}=St0) ->
+ case Labels0 of
+ #{L:=Lbl} ->
+ {Lbl,St0};
+ #{} ->
+ {Lbl,St} = new_label(St0),
+ Labels = Labels0#{L=>Lbl},
+ {Lbl,St#cg{labels=Labels}}
+ end.
+
+%% local_func_label(Name, Arity, State) -> {Label,State'}
+%% local_func_label({Name,Arity}, State) -> {Label,State'}
+%% Get the function entry label for a local function.
+
+local_func_label(Name, Arity, St) ->
+ local_func_label({Name,Arity}, St).
+
+local_func_label(Key, #cg{functable=Map}=St0) ->
+ case Map of
+ #{Key := Label} ->
+ {Label,St0};
+ _ ->
+ {Label,St} = new_label(St0),
+ {Label,St#cg{functable=Map#{Key => Label}}}
+ end.
+
+%% is_gc_bif(Name, Args) -> true|false.
+%% Determines whether the BIF Name/Arity might do a GC.
+
+-spec is_gc_bif(atom(), [beam_ssa:value()]) -> boolean().
+
+is_gc_bif(hd, [_]) -> false;
+is_gc_bif(tl, [_]) -> false;
+is_gc_bif(self, []) -> false;
+is_gc_bif(node, []) -> false;
+is_gc_bif(node, [_]) -> false;
+is_gc_bif(element, [_,_]) -> false;
+is_gc_bif(get, [_]) -> false;
+is_gc_bif(is_map_key, [_,_]) -> false;
+is_gc_bif(map_get, [_,_]) -> false;
+is_gc_bif(tuple_size, [_]) -> false;
+is_gc_bif(Bif, Args) ->
+ Arity = length(Args),
+ not (erl_internal:bool_op(Bif, Arity) orelse
+ erl_internal:new_type_test(Bif, Arity) orelse
+ erl_internal:comp_op(Bif, Arity)).
+
+%% new_label(St) -> {L,St}.
+
+new_label(#cg{lcount=Next}=St) ->
+ {Next,St#cg{lcount=Next+1}}.
+
+%% call_line(tail|body, Func, Anno) -> [] | [{line,...}].
+%% Produce a line instruction if it will be needed by the
+%% call to Func.
+
+call_line(_Context, {extfunc,Mod,Name,Arity}, Anno) ->
+ case erl_bifs:is_safe(Mod, Name, Arity) of
+ false ->
+ %% The call could be to a BIF.
+ %% We'll need a line instruction in case the
+ %% BIF call fails.
+ [line(Anno)];
+ true ->
+ %% Call to a safe BIF. Since it cannot fail,
+ %% we don't need any line instruction here.
+ []
+ end;
+call_line(body, _, Anno) ->
+ [line(Anno)];
+call_line(tail, local, _) ->
+ %% Tail-recursive call to a local function. A line
+ %% instruction will not be useful.
+ [];
+call_line(tail, _, Anno) ->
+ %% Call to a fun.
+ [line(Anno)].
+
+%% line(Le) -> {line,[] | {location,File,Line}}
+%% Create a line instruction, containing information about
+%% the current filename and line number. A line information
+%% instruction should be placed before any operation that could
+%% cause an exception.
+
+line(#{location:={File,Line}}) ->
+ {line,[{location,File,Line}]};
+line(#{}) ->
+ {line,[]}.
+
+flatmapfoldl(F, Accu0, [Hd|Tail]) ->
+ {R,Accu1} = F(Hd, Accu0),
+ {Rs,Accu2} = flatmapfoldl(F, Accu1, Tail),
+ {R++Rs,Accu2};
+flatmapfoldl(_, Accu, []) -> {[],Accu}.
diff --git a/lib/compiler/src/beam_ssa_dead.erl b/lib/compiler/src/beam_ssa_dead.erl
new file mode 100644
index 0000000000..bb43a550ae
--- /dev/null
+++ b/lib/compiler/src/beam_ssa_dead.erl
@@ -0,0 +1,1076 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018. All 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%
+%%
+%% Dead code is code that is executed but has no effect. This
+%% optimization pass either removes dead code or jumps around it,
+%% potentially making it unreachable so that it can be dropped
+%% the next time beam_ssa:linearize/1 is called.
+%%
+
+-module(beam_ssa_dead).
+-export([opt/1]).
+
+-include("beam_ssa.hrl").
+-import(lists, [append/1,keymember/3,last/1,member/2,
+ takewhile/2,reverse/1]).
+
+-type used_vars() :: #{beam_ssa:label():=ordsets:ordset(beam_ssa:var_name())}.
+
+-type basic_type_test() :: atom() | {'is_tagged_tuple',pos_integer(),atom()}.
+-type type_test() :: basic_type_test() | {'not',basic_type_test()}.
+-type op_name() :: atom().
+-type basic_rel_op() :: {op_name(),beam_ssa:b_var(),beam_ssa:value()} |
+ {basic_type_test(),beam_ssa:value()}.
+-type rel_op() :: {op_name(),beam_ssa:b_var(),beam_ssa:value()} |
+ {type_test(),beam_ssa:value()}.
+
+-record(st,
+ {bs :: beam_ssa:block_map(),
+ us :: used_vars(),
+ skippable :: #{beam_ssa:label():='true'},
+ rel_op=none :: 'none' | rel_op(),
+ target=any :: 'any' | 'one_way' | beam_ssa:label()
+ }).
+
+-spec opt([{Label0,Block0}]) -> [{Label,Block}] when
+ Label0 :: beam_ssa:label(),
+ Block0 :: beam_ssa:b_blk(),
+ Label :: beam_ssa:label(),
+ Block :: beam_ssa:b_blk().
+
+opt(Linear) ->
+ {Used,Skippable} = used_vars(Linear),
+ Blocks0 = maps:from_list(Linear),
+ St0 = #st{bs=Blocks0,us=Used,skippable=Skippable},
+ St = shortcut_opt(St0),
+ #st{bs=Blocks} = combine_eqs(St#st{us=#{}}),
+ beam_ssa:linearize(Blocks).
+
+%%%
+%%% Shortcut br/switch targets.
+%%%
+%%% A br/switch may branch to another br/switch that in turn always
+%%% branches to another target. Rewrite br/switch to refer to the
+%%% ultimate targets directly. That will save execution time, but
+%%% could also reduce the size of the code if some of the original
+%%% targets become unreachable and be deleted.
+%%%
+%%% When rewriting branches, we must be careful not to skip instructions
+%%% that have side effects or that bind variables that will be used
+%%% at the new target.
+%%%
+%%% We must also avoid branching to phi nodes. The reason is
+%%% twofold. First, we might create a critical edge which is strictly
+%%% forbidden. Second, there will be a branch from a block that is not
+%%% listed in the list of predecessors in the phi node. Those
+%%% limitations could probably be overcome, but it is not clear how
+%%% much that would improve the code.
+%%%
+
+shortcut_opt(#st{bs=Blocks}=St) ->
+ %% Processing the blocks in reverse post order seems to give more
+ %% opportunities for optimizations compared to post order. (Based on
+ %% running scripts/diffable with both PO and RPO and looking at
+ %% the diff.)
+ %%
+ %% Unfortunately, processing the blocks in reverse post order
+ %% potentially makes the time complexity quadratic or even cubic if
+ %% the ordset of unset variables grows large, instead of
+ %% linear for post order processing. We try to still get reasonable
+ %% compilation times by optimizations that will keep the constant
+ %% factor as low as possible, and we try to avoid the cubic time
+ %% complexity by trying to keep the set of unset variables as small
+ %% as possible.
+
+ Ls = beam_ssa:rpo(Blocks),
+ shortcut_opt(Ls, #{}, St).
+
+shortcut_opt([L|Ls], Bs, #st{bs=Blocks0}=St) ->
+ #b_blk{is=Is,last=Last0} = Blk0 = get_block(L, St),
+ case shortcut_terminator(Last0, Is, L, Bs, St) of
+ Last0 ->
+ %% No change. No need to update the block.
+ shortcut_opt(Ls, Bs, St);
+ Last ->
+ %% The terminator was simplified in some way.
+ %% Update the block.
+ Blk = Blk0#b_blk{last=Last},
+ Blocks = Blocks0#{L=>Blk},
+ shortcut_opt(Ls, Bs, St#st{bs=Blocks})
+ end;
+shortcut_opt([], _, St) -> St.
+
+shortcut_terminator(#b_br{bool=#b_literal{val=true},succ=Succ0},
+ _Is, From, Bs, St0) ->
+ St = St0#st{rel_op=none},
+ shortcut(Succ0, From, Bs, St);
+shortcut_terminator(#b_br{bool=#b_var{}=Bool,succ=Succ0,fail=Fail0}=Br,
+ Is, From, Bs, St0) ->
+ St = St0#st{target=one_way},
+ RelOp = get_rel_op(Bool, Is),
+ SuccBs = bind_var(Bool, #b_literal{val=true}, Bs),
+ BrSucc = shortcut(Succ0, From, SuccBs, St#st{rel_op=RelOp}),
+ FailBs = bind_var(Bool, #b_literal{val=false}, Bs),
+ BrFail = shortcut(Fail0, From, FailBs, St#st{rel_op=invert_op(RelOp)}),
+ case {BrSucc,BrFail} of
+ {#b_br{bool=#b_literal{val=true},succ=Succ},
+ #b_br{bool=#b_literal{val=true},succ=Fail}}
+ when Succ =/= Succ0; Fail =/= Fail0 ->
+ %% One or both of the targets were cut short.
+ beam_ssa:normalize(Br#b_br{succ=Succ,fail=Fail});
+ {_,_} ->
+ %% No change.
+ Br
+ end;
+shortcut_terminator(#b_switch{arg=Bool,list=List0}=Sw, _Is, From, Bs, St) ->
+ List = shortcut_switch(List0, Bool, From, Bs, St),
+ beam_ssa:normalize(Sw#b_switch{list=List});
+shortcut_terminator(Last, _Is, _Bs, _From, _St) ->
+ Last.
+
+shortcut_switch([{Lit,L0}|T], Bool, From, Bs, St0) ->
+ RelOp = {'=:=',Bool,Lit},
+ St = St0#st{rel_op=RelOp},
+ #b_br{bool=#b_literal{val=true},succ=L} =
+ shortcut(L0, From, bind_var(Bool, Lit, Bs), St#st{target=one_way}),
+ [{Lit,L}|shortcut_switch(T, Bool, From, Bs, St0)];
+shortcut_switch([], _, _, _, _) -> [].
+
+shortcut(L, From, Bs, St) ->
+ shortcut_1(L, From, Bs, ordsets:new(), St).
+
+shortcut_1(L, From, Bs0, UnsetVars0, St) ->
+ case shortcut_2(L, From, Bs0, UnsetVars0, St) of
+ none ->
+ %% No more shortcuts found. Package up the previous
+ %% label in an unconditional branch.
+ #b_br{bool=#b_literal{val=true},succ=L,fail=L};
+ {#b_br{bool=#b_var{}}=Br,_,_} ->
+ %% This is a two-way branch. We can't do any better.
+ Br;
+ {#b_br{bool=#b_literal{val=true},succ=Succ},Bs,UnsetVars} ->
+ %% This is a safe `br`, but try to find a better one.
+ shortcut_1(Succ, L, Bs, UnsetVars, St)
+ end.
+
+%% Try to shortcut this block, branching to a successor.
+shortcut_2(L, From, Bs0, UnsetVars0, St) ->
+ #b_blk{is=Is,last=Last} = get_block(L, St),
+ case eval_is(Is, From, Bs0, St) of
+ none ->
+ %% It is not safe to avoid this block because it
+ %% has instructions with potential side effects.
+ none;
+ Bs ->
+ %% The instructions in the block (if any) don't
+ %% have any side effects and can be skipped.
+ %% Evaluate the terminator.
+ case eval_terminator(Last, Bs, St) of
+ none ->
+ %% The terminator is not suitable (could be
+ %% because it is a switch that can't be simplified
+ %% or it is a ret instruction).
+ none;
+ #b_br{}=Br ->
+ %% We have a potentially suitable br.
+ %% Now update the set of variables that will never
+ %% be set if this block will be skipped.
+ case update_unset_vars(L, Is, Br, UnsetVars0, St) of
+ unsafe ->
+ %% It is unsafe to use this br,
+ %% because it refers to a variable defined
+ %% in this block.
+ shortcut_unsafe_br(Br, L, Bs, UnsetVars0, St);
+ UnsetVars ->
+ %% Continue checking whether this br is
+ %% suitable.
+ shortcut_test_br(Br, L, Bs, UnsetVars, St)
+ end
+ end
+ end.
+
+shortcut_test_br(Br, From, Bs, UnsetVars, St) ->
+ case is_br_safe(UnsetVars, Br, St) of
+ false ->
+ shortcut_unsafe_br(Br, From, Bs, UnsetVars, St);
+ true ->
+ shortcut_safe_br(Br, From, Bs, UnsetVars, St)
+ end.
+
+shortcut_unsafe_br(Br, From, Bs, UnsetVars, #st{target=Target}=St) ->
+ %% Branching using this `br` is unsafe, either because it
+ %% is an unconditional branch to a phi node, or because
+ %% one or more of the variables that are not set will be
+ %% used. Try to follow branches of this `br`, to find a
+ %% safe `br`.
+ case Br of
+ #b_br{bool=#b_literal{val=true},succ=L} ->
+ case Target of
+ L ->
+ %% We have reached the forced target, and it
+ %% is unsafe. Give up.
+ none;
+ _ ->
+ %% Try following this branch to see whether it
+ %% leads to a safe `br`.
+ shortcut_2(L, From, Bs, UnsetVars, St)
+ end;
+ #b_br{bool=#b_var{},succ=Succ,fail=Fail} ->
+ case {Succ,Fail} of
+ {L,Target} ->
+ %% The failure label is the forced target.
+ %% Try following the success label to see
+ %% whether it also ultimately ends up at the
+ %% forced target.
+ shortcut_2(L, From, Bs, UnsetVars, St);
+ {Target,L} ->
+ %% The success label is the forced target.
+ %% Try following the failure label to see
+ %% whether it also ultimately ends up at the
+ %% forced target.
+ shortcut_2(L, From, Bs, UnsetVars, St);
+ {_,_} ->
+ case Target of
+ any ->
+ %% This two-way branch is unsafe. Try
+ %% reducing it to a one-way branch.
+ shortcut_two_way(Br, From, Bs, UnsetVars, St);
+ one_way ->
+ %% This two-way branch is unsafe. Try
+ %% reducing it to a one-way branch.
+ shortcut_two_way(Br, From, Bs, UnsetVars, St);
+ _ when is_integer(Target) ->
+ %% This two-way branch is unsafe, and
+ %% there already is a forced target.
+ %% Give up.
+ none
+ end
+ end
+ end.
+
+shortcut_safe_br(Br, From, Bs, UnsetVars, #st{target=Target}=St) ->
+ %% This `br` instruction is safe. It does not branch to a phi
+ %% node, and all variables that will be used are guaranteed to be
+ %% defined.
+ case Br of
+ #b_br{bool=#b_literal{val=true},succ=L} ->
+ %% This is a one-way branch.
+ case Target of
+ any ->
+ %% No forced target. Success!
+ {Br,Bs,UnsetVars};
+ one_way ->
+ %% The target must be a one-way branch, which this
+ %% `br` is. Success!
+ {Br,Bs,UnsetVars};
+ L when is_integer(Target) ->
+ %% The forced target is L. Success!
+ {Br,Bs,UnsetVars};
+ _ when is_integer(Target) ->
+ %% Wrong forced target. Try following this branch
+ %% to see if it ultimately ends up at the forced
+ %% target.
+ shortcut_2(L, From, Bs, UnsetVars, St)
+ end;
+ #b_br{bool=#b_var{}} ->
+ %% This is a two-way branch.
+ if
+ Target =:= any; Target =:= one_way ->
+ %% No specific forced target. Try to reduce the
+ %% two-way branch to an one-way branch.
+ case shortcut_two_way(Br, From, Bs, UnsetVars, St) of
+ none when Target =:= any ->
+ %% This `br` can't be reduced to a one-way
+ %% branch. Return the `br` as-is.
+ {Br,Bs,UnsetVars};
+ none when Target =:= one_way ->
+ %% This `br` can't be reduced to a one-way
+ %% branch. The caller wants a one-way
+ %% branch. Give up.
+ none;
+ {_,_,_}=Res ->
+ %% This `br` was successfully reduced to a
+ %% one-way branch.
+ Res
+ end;
+ is_integer(Target) ->
+ %% There is a forced target, which can't
+ %% be reached because this `br` is a two-way
+ %% branch. Give up.
+ none
+ end
+ end.
+
+update_unset_vars(L, Is, Br, UnsetVars, #st{skippable=Skippable}) ->
+ case is_map_key(L, Skippable) of
+ true ->
+ %% None of the variables used in this block are used in
+ %% the successors. Thus, there is no need to add the
+ %% variables to the set of unset variables.
+ case Br of
+ #b_br{bool=#b_var{}=Bool} ->
+ case keymember(Bool, #b_set.dst, Is) of
+ true ->
+ %% Bool is a variable defined in this
+ %% block. Using the br instruction from
+ %% this block (and skipping the body of
+ %% the block) is unsafe.
+ unsafe;
+ false ->
+ %% Bool is either a variable not defined
+ %% in this block or a literal. Adding it
+ %% to the UnsetVars set would not change
+ %% the outcome of the tests in
+ %% is_br_safe/2.
+ UnsetVars
+ end;
+ #b_br{} ->
+ UnsetVars
+ end;
+ false ->
+ %% Some variables defined in this block are used by
+ %% successors. We must update the set of unset variables.
+ SetInThisBlock = [V || #b_set{dst=V} <- Is],
+ ordsets:union(UnsetVars, ordsets:from_list(SetInThisBlock))
+ end.
+
+shortcut_two_way(#b_br{succ=Succ,fail=Fail}, From, Bs0, UnsetVars0, St0) ->
+ case shortcut_2(Succ, From, Bs0, UnsetVars0, St0#st{target=Fail}) of
+ {#b_br{bool=#b_literal{},succ=Fail},_,_}=Res ->
+ Res;
+ none ->
+ St = St0#st{target=Succ},
+ case shortcut_2(Fail, From, Bs0, UnsetVars0, St) of
+ {#b_br{bool=#b_literal{},succ=Succ},_,_}=Res ->
+ Res;
+ none ->
+ none
+ end
+ end.
+
+get_block(L, St) ->
+ #st{bs=#{L:=Blk}} = St,
+ Blk.
+
+is_br_safe(UnsetVars, Br, #st{us=Us}=St) ->
+ %% Check that none of the unset variables will be used.
+ case Br of
+ #b_br{bool=#b_var{}=V,succ=Succ,fail=Fail} ->
+ #{Succ:=Used0,Fail:=Used1} = Us,
+
+ %% A two-way branch never branches to a phi node, so there
+ %% is no need to check for phi nodes here.
+ not member(V, UnsetVars) andalso
+ ordsets:is_disjoint(Used0, UnsetVars) andalso
+ ordsets:is_disjoint(Used1, UnsetVars);
+ #b_br{succ=Same,fail=Same} ->
+ %% An unconditional branch must not jump to
+ %% a phi node.
+ not is_forbidden(Same, St) andalso
+ ordsets:is_disjoint(map_get(Same, Us), UnsetVars)
+ end.
+
+is_forbidden(L, St) ->
+ case get_block(L, St) of
+ #b_blk{is=[#b_set{op=phi}|_]} -> true;
+ #b_blk{is=[#b_set{op=peek_message}|_]} -> true;
+ #b_blk{} -> false
+ end.
+
+
+%% Evaluate the instructions in the block.
+%% Return the updated bindings, or 'none' if there is
+%% any instruction with potential side effects.
+
+eval_is([#b_set{op=phi,dst=Dst,args=Args}|Is], From, Bs0, St) ->
+ Val = get_phi_arg(Args, From),
+ Bs = bind_var(Dst, Val, Bs0),
+ eval_is(Is, From, Bs, St);
+eval_is([#b_set{op={bif,_},dst=Dst}=I0|Is], From, Bs, St) ->
+ I = sub(I0, Bs),
+ case eval_bif(I, St) of
+ #b_literal{}=Val ->
+ eval_is(Is, From, bind_var(Dst, Val, Bs), St);
+ none ->
+ eval_is(Is, From, Bs, St)
+ end;
+eval_is([#b_set{op=Op,dst=Dst}=I|Is], From, Bs, St)
+ when Op =:= is_tagged_tuple; Op =:= is_nonempty_list ->
+ #b_set{args=Args} = sub(I, Bs),
+ case eval_rel_op(Op, Args, St) of
+ #b_literal{}=Val ->
+ eval_is(Is, From, bind_var(Dst, Val, Bs), St);
+ none ->
+ eval_is(Is, From, Bs, St)
+ end;
+eval_is([#b_set{}=I|Is], From, Bs, St) ->
+ case beam_ssa:no_side_effect(I) of
+ true ->
+ %% This instruction has no side effects. It can
+ %% safely be omitted.
+ eval_is(Is, From, Bs, St);
+ false ->
+ %% This instruction may have some side effect.
+ %% It is not safe to avoid this instruction.
+ none
+ end;
+eval_is([], _From, Bs, _St) -> Bs.
+
+get_phi_arg([{Val,From}|_], From) -> Val;
+get_phi_arg([_|As], From) -> get_phi_arg(As, From).
+
+eval_terminator(#b_br{bool=#b_var{}=Bool}=Br, Bs, _St) ->
+ Val = get_value(Bool, Bs),
+ beam_ssa:normalize(Br#b_br{bool=Val});
+eval_terminator(#b_br{bool=#b_literal{}}=Br, _Bs, _St) ->
+ beam_ssa:normalize(Br);
+eval_terminator(#b_switch{arg=Arg,fail=Fail,list=List}=Sw, Bs, St) ->
+ case get_value(Arg, Bs) of
+ #b_literal{}=Val ->
+ %% Literal argument. Simplify to a `br`.
+ beam_ssa:normalize(Sw#b_switch{arg=Val});
+ #b_var{} ->
+ %% Try optimizing the switch.
+ case eval_switch(List, Arg, St, Fail) of
+ none ->
+ none;
+ To when is_integer(To) ->
+ %% Either one of the values in the switch
+ %% matched a previous value in a '=:=' test, or
+ %% none of the values matched a previous test.
+ #b_br{bool=#b_literal{val=true},succ=To,fail=To}
+ end
+ end;
+eval_terminator(#b_ret{}, _Bs, _St) ->
+ none.
+
+eval_switch(List, Arg, #st{rel_op={_,Arg,_}=PrevOp}, Fail) ->
+ %% There is a previous relational operator testing the same variable.
+ %% Optimization may be possible.
+ eval_switch_1(List, Arg, PrevOp, Fail);
+eval_switch(_, _, _, _) ->
+ %% There is either no previous relational operator, or it tests
+ %% a different variable. Nothing to optimize.
+ none.
+
+eval_switch_1([{Lit,Lbl}|T], Arg, PrevOp, Fail) ->
+ RelOp = {'=:=',Arg,Lit},
+ case will_succeed(PrevOp, RelOp) of
+ yes ->
+ %% Success. This branch will always be taken.
+ Lbl;
+ no ->
+ %% This branch will never be taken.
+ eval_switch_1(T, Arg, PrevOp, Fail);
+ maybe ->
+ %% This label could be reached.
+ eval_switch_1(T, Arg, PrevOp, none)
+ end;
+eval_switch_1([], _Arg, _PrevOp, Fail) ->
+ %% Fail is now either the failure label or 'none'.
+ Fail.
+
+bind_var(Var, Val0, Bs) ->
+ Val = get_value(Val0, Bs),
+ Bs#{Var=>Val}.
+
+get_value(#b_var{}=Var, Bs) ->
+ case Bs of
+ #{Var:=Val} -> get_value(Val, Bs);
+ #{} -> Var
+ end;
+get_value(#b_literal{}=Lit, _Bs) -> Lit.
+
+eval_bif(#b_set{op={bif,Bif},args=Args}, St) ->
+ Arity = length(Args),
+ case erl_bifs:is_pure(erlang, Bif, Arity) of
+ false ->
+ none;
+ true ->
+ case get_lit_args(Args) of
+ none ->
+ %% Not literal arguments. Try to evaluate
+ %% it based on a previous relational operator.
+ eval_rel_op({bif,Bif}, Args, St);
+ LitArgs ->
+ try apply(erlang, Bif, LitArgs) of
+ Val -> #b_literal{val=Val}
+ catch
+ error:_ -> none
+ end
+ end
+ end.
+
+get_lit_args([#b_literal{val=Lit1}]) ->
+ [Lit1];
+get_lit_args([#b_literal{val=Lit1},
+ #b_literal{val=Lit2}]) ->
+ [Lit1,Lit2];
+get_lit_args([#b_literal{val=Lit1},
+ #b_literal{val=Lit2},
+ #b_literal{val=Lit3}]) ->
+ [Lit1,Lit2,Lit3];
+get_lit_args(_) -> none.
+
+%%%
+%%% Handling of relational operators.
+%%%
+
+get_rel_op(Bool, [_|_]=Is) ->
+ case last(Is) of
+ #b_set{op=Op,dst=Bool,args=Args} ->
+ normalize_op(Op, Args);
+ #b_set{} ->
+ none
+ end;
+get_rel_op(_, []) -> none.
+
+%% normalize_op(Instruction) -> {Normalized,FailLabel} | error
+%% Normalized = {Operator,Variable,Variable|Literal} |
+%% {TypeTest,Variable}
+%% Operation = '<' | '=<' | '=:=' | '=/=' | '>=' | '>'
+%% TypeTest = is_atom | is_integer ...
+%% Variable = #b_var{}
+%% Literal = #b_literal{}
+%%
+%% Normalize a relational operator to facilitate further
+%% comparisons between operators. Always make the register
+%% operand the first operand. If there are two registers,
+%% order the registers in lexical order.
+%%
+%% For example, this instruction:
+%%
+%% #b_set{op={bif,=<},args=[#b_literal{}, #b_var{}}
+%%
+%% will be normalized to:
+%%
+%% {'=<',#b_var{},#b_literal{}}
+
+-spec normalize_op(Op, Args) -> NormalizedOp | 'none' when
+ Op :: beam_ssa:op(),
+ Args :: [beam_ssa:value()],
+ NormalizedOp :: basic_rel_op().
+
+normalize_op(is_tagged_tuple, [Arg,#b_literal{val=Size},#b_literal{val=Tag}])
+ when is_integer(Size), is_atom(Tag) ->
+ {{is_tagged_tuple,Size,Tag},Arg};
+normalize_op(is_nonempty_list, [Arg]) ->
+ {is_nonempty_list,Arg};
+normalize_op({bif,Bif}, [Arg]) ->
+ case erl_internal:new_type_test(Bif, 1) of
+ true -> {Bif,Arg};
+ false -> none
+ end;
+normalize_op({bif,Bif}, [_,_]=Args) ->
+ case erl_internal:comp_op(Bif, 2) of
+ true ->
+ normalize_op_1(Bif, Args);
+ false ->
+ none
+ end;
+normalize_op(_, _) -> none.
+
+normalize_op_1(Bif, Args) ->
+ case Args of
+ [#b_literal{}=Arg1,#b_var{}=Arg2] ->
+ {turn_op(Bif),Arg2,Arg1};
+ [#b_var{}=Arg1,#b_literal{}=Arg2] ->
+ {Bif,Arg1,Arg2};
+ [#b_var{}=A,#b_var{}=B] ->
+ if A < B -> {Bif,A,B};
+ true -> {turn_op(Bif),B,A}
+ end;
+ [#b_literal{},#b_literal{}] ->
+ none
+ end.
+
+-spec invert_op(basic_rel_op() | 'none') -> rel_op() | 'none'.
+
+invert_op({Op,Arg1,Arg2}) ->
+ {invert_op_1(Op),Arg1,Arg2};
+invert_op({TypeTest,Arg}) ->
+ {{'not',TypeTest},Arg};
+invert_op(none) -> none.
+
+invert_op_1('>=') -> '<';
+invert_op_1('<') -> '>=';
+invert_op_1('=<') -> '>';
+invert_op_1('>') -> '=<';
+invert_op_1('=:=') -> '=/=';
+invert_op_1('=/=') -> '=:=';
+invert_op_1('==') -> '/=';
+invert_op_1('/=') -> '=='.
+
+turn_op('<') -> '>';
+turn_op('=<') -> '>=';
+turn_op('>') -> '<';
+turn_op('>=') -> '=<';
+turn_op('=:='=Op) -> Op;
+turn_op('=/='=Op) -> Op;
+turn_op('=='=Op) -> Op;
+turn_op('/='=Op) -> Op.
+
+eval_rel_op(_Bif, _Args, #st{rel_op=none}) ->
+ none;
+eval_rel_op(Bif, Args, #st{rel_op=Prev}) ->
+ case normalize_op(Bif, Args) of
+ none ->
+ none;
+ RelOp ->
+ case will_succeed(Prev, RelOp) of
+ yes -> #b_literal{val=true};
+ no -> #b_literal{val=false};
+ maybe -> none
+ end
+ end.
+
+%% will_succeed(PrevCondition, Condition) -> yes | no | maybe
+%% PrevCondition is a condition known to be true. This function
+%% will tell whether Condition will succeed.
+
+will_succeed({_Op,_Var,_Value}=Same, {_Op,_Var,_Value}=Same) ->
+ %% Repeated test.
+ yes;
+will_succeed({Op1,Var,#b_literal{val=A}}, {Op2,Var,#b_literal{val=B}}) ->
+ will_succeed_1(Op1, A, Op2, B);
+will_succeed({Op1,Var,#b_var{}=A}, {Op2,Var,#b_var{}=B}) ->
+ will_succeed_vars(Op1, A, Op2, B);
+will_succeed({'=:=',Var,#b_literal{val=A}}, {TypeTest,Var}) ->
+ eval_type_test(TypeTest, A);
+will_succeed({_,_}=Same, {_,_}=Same) ->
+ %% Repeated type test.
+ yes;
+will_succeed({Test1,Var}, {Test2,Var}) ->
+ will_succeed_test(Test1, Test2);
+will_succeed({_,_}, {_,_}) ->
+ maybe;
+will_succeed({_,_}, {_,_,_}) ->
+ maybe;
+will_succeed({_,_,_}, {_,_}) ->
+ maybe;
+will_succeed({_,_,_}, {_,_,_}) ->
+ maybe.
+
+will_succeed_test({'not',Test1}, Test2) ->
+ case Test1 =:= Test2 of
+ true -> no;
+ false -> maybe
+ end;
+will_succeed_test(is_tuple, {is_tagged_tuple,_,_}) ->
+ maybe;
+will_succeed_test({is_tagged_tuple,_,_}, is_tuple) ->
+ yes;
+will_succeed_test(is_list, is_nonempty_list) ->
+ maybe;
+will_succeed_test(is_nonempty_list, is_list) ->
+ yes;
+will_succeed_test(T1, T2) ->
+ case is_numeric_test(T1) andalso is_numeric_test(T2) of
+ true -> maybe;
+ false -> no
+ end.
+
+will_succeed_1('=:=', A, '<', B) ->
+ if
+ B =< A -> no;
+ true -> yes
+ end;
+will_succeed_1('=:=', A, '=<', B) ->
+ if
+ B < A -> no;
+ true -> yes
+ end;
+will_succeed_1('=:=', A, '=:=', B) when A =/= B ->
+ no;
+will_succeed_1('=:=', A, '=/=', B) ->
+ if
+ A =:= B -> no;
+ true -> yes
+ end;
+will_succeed_1('=:=', A, '>=', B) ->
+ if
+ B > A -> no;
+ true -> yes
+ end;
+will_succeed_1('=:=', A, '>', B) ->
+ if
+ B >= A -> no;
+ true -> yes
+ end;
+
+will_succeed_1('=/=', A, '=:=', B) when A =:= B -> no;
+
+will_succeed_1('<', A, '=:=', B) when B >= A -> no;
+will_succeed_1('<', A, '=/=', B) when B >= A -> yes;
+will_succeed_1('<', A, '<', B) when B >= A -> yes;
+will_succeed_1('<', A, '=<', B) when B > A -> yes;
+will_succeed_1('<', A, '>=', B) when B > A -> no;
+will_succeed_1('<', A, '>', B) when B >= A -> no;
+
+will_succeed_1('=<', A, '=:=', B) when B > A -> no;
+will_succeed_1('=<', A, '=/=', B) when B > A -> yes;
+will_succeed_1('=<', A, '<', B) when B > A -> yes;
+will_succeed_1('=<', A, '=<', B) when B >= A -> yes;
+will_succeed_1('=<', A, '>=', B) when B > A -> no;
+will_succeed_1('=<', A, '>', B) when B >= A -> no;
+
+will_succeed_1('>=', A, '=:=', B) when B < A -> no;
+will_succeed_1('>=', A, '=/=', B) when B < A -> yes;
+will_succeed_1('>=', A, '<', B) when B =< A -> no;
+will_succeed_1('>=', A, '=<', B) when B < A -> no;
+will_succeed_1('>=', A, '>=', B) when B =< A -> yes;
+will_succeed_1('>=', A, '>', B) when B < A -> yes;
+
+will_succeed_1('>', A, '=:=', B) when B =< A -> no;
+will_succeed_1('>', A, '=/=', B) when B =< A -> yes;
+will_succeed_1('>', A, '<', B) when B =< A -> no;
+will_succeed_1('>', A, '=<', B) when B < A -> no;
+will_succeed_1('>', A, '>=', B) when B =< A -> yes;
+will_succeed_1('>', A, '>', B) when B < A -> yes;
+
+will_succeed_1('==', A, '==', B) ->
+ if
+ A == B -> yes;
+ true -> no
+ end;
+will_succeed_1('==', A, '/=', B) ->
+ if
+ A == B -> no;
+ true -> yes
+ end;
+will_succeed_1('/=', A, '/=', B) when A == B -> yes;
+will_succeed_1('/=', A, '==', B) when A == B -> no;
+
+will_succeed_1(_, _, _, _) -> maybe.
+
+will_succeed_vars('=/=', Val, '=:=', Val) -> no;
+will_succeed_vars('=:=', Val, '=/=', Val) -> no;
+will_succeed_vars('=:=', Val, '>=', Val) -> yes;
+will_succeed_vars('=:=', Val, '=<', Val) -> yes;
+
+will_succeed_vars('/=', Val1, '==', Val2) when Val1 == Val2 -> no;
+will_succeed_vars('==', Val1, '/=', Val2) when Val1 == Val2 -> no;
+
+will_succeed_vars(_, _, _, _) -> maybe.
+
+is_numeric_test(is_float) -> true;
+is_numeric_test(is_integer) -> true;
+is_numeric_test(is_number) -> true;
+is_numeric_test(_) -> false.
+
+eval_type_test(Test, Arg) ->
+ case eval_type_test_1(Test, Arg) of
+ true -> yes;
+ false -> no
+ end.
+
+eval_type_test_1(is_nonempty_list, Arg) ->
+ case Arg of
+ [_|_] -> true;
+ _ -> false
+ end;
+eval_type_test_1({is_tagged_tuple,Sz,Tag}, Arg) ->
+ if
+ tuple_size(Arg) =:= Sz, element(1, Arg) =:= Tag ->
+ true;
+ true ->
+ false
+ end;
+eval_type_test_1(Test, Arg) ->
+ erlang:Test(Arg).
+
+%%%
+%%% Combine bif:'=:=' and switch instructions
+%%% to switch instructions.
+%%%
+%%% Consider this code:
+%%%
+%%% 0:
+%%% @ssa_bool = bif:'=:=' Var, literal 1
+%%% br @ssa_bool, label 2, label 3
+%%%
+%%% 2:
+%%% ret literal a
+%%%
+%%% 3:
+%%% @ssa_bool:7 = bif:'=:=' Var, literal 2
+%%% br @ssa_bool:7, label 4, label 999
+%%%
+%%% 4:
+%%% ret literal b
+%%%
+%%% 999:
+%%% .
+%%% .
+%%% .
+%%%
+%%% The two bif:'=:=' instructions can be combined
+%%% to a switch:
+%%%
+%%% 0:
+%%% switch Var, label 999, [ { literal 1, label 2 },
+%%% { literal 2, label 3 } ]
+%%%
+%%% 2:
+%%% ret literal a
+%%%
+%%% 4:
+%%% ret literal b
+%%%
+%%% 999:
+%%% .
+%%% .
+%%% .
+%%%
+
+combine_eqs(#st{bs=Blocks}=St) ->
+ Ls = reverse(beam_ssa:rpo(Blocks)),
+ combine_eqs_1(Ls, St).
+
+combine_eqs_1([L|Ls], #st{bs=Blocks0}=St0) ->
+ case comb_get_sw(L, St0) of
+ none ->
+ combine_eqs_1(Ls, St0);
+ {_,Arg,_,Fail0,List0} ->
+ case comb_get_sw(Fail0, St0) of
+ {true,Arg,Fail1,Fail,List1} ->
+ %% Another switch/br with the same arguments was
+ %% found. Try combining them.
+ case combine_lists(Fail1, List0, List1, Blocks0) of
+ none ->
+ %% Different types of literals in the lists,
+ %% or the success cases in the first switch
+ %% could branch to the second switch
+ %% (increasing code size and repeating tests).
+ combine_eqs_1(Ls, St0);
+ List ->
+ %% Everything OK! Combine the lists.
+ Sw0 = #b_switch{arg=Arg,fail=Fail,list=List},
+ Sw = beam_ssa:normalize(Sw0),
+ Blk0 = map_get(L, Blocks0),
+ Blk = Blk0#b_blk{last=Sw},
+ Blocks = Blocks0#{L:=Blk},
+ St = St0#st{bs=Blocks},
+ combine_eqs_1(Ls, St)
+ end;
+ {true,_OtherArg,_,_,_} ->
+ %% The other switch/br uses a different Arg.
+ combine_eqs_1(Ls, St0);
+ {false,_,_,_,_} ->
+ %% Not safe: Bindings of variables that will be used
+ %% or execution of instructions with potential
+ %% side effects will be skipped.
+ combine_eqs_1(Ls, St0);
+ none ->
+ %% No switch/br at this label.
+ combine_eqs_1(Ls, St0)
+ end
+ end;
+combine_eqs_1([], St) -> St.
+
+comb_get_sw(L, Blocks) ->
+ comb_get_sw(L, true, Blocks).
+
+comb_get_sw(L, Safe0, #st{bs=Blocks,skippable=Skippable}) ->
+ #b_blk{is=Is,last=Last} = map_get(L, Blocks),
+ Safe1 = Safe0 andalso is_map_key(L, Skippable),
+ case Last of
+ #b_ret{} ->
+ none;
+ #b_br{bool=#b_var{}=Bool,succ=Succ,fail=Fail} ->
+ case comb_is(Is, Bool, Safe1) of
+ {none,_} ->
+ none;
+ {#b_set{op={bif,'=:='},args=[#b_var{}=Arg,#b_literal{}=Lit]},Safe} ->
+ {Safe,Arg,L,Fail,[{Lit,Succ}]};
+ {#b_set{},_} ->
+ none
+ end;
+ #b_br{} ->
+ none;
+ #b_switch{arg=#b_var{}=Arg,fail=Fail,list=List} ->
+ {none,Safe} = comb_is(Is, none, Safe1),
+ {Safe,Arg,L,Fail,List}
+ end.
+
+comb_is([#b_set{dst=#b_var{}=Bool}=I], Bool, Safe) ->
+ {I,Safe};
+comb_is([#b_set{}=I|Is], Bool, Safe0) ->
+ Safe = Safe0 andalso beam_ssa:no_side_effect(I),
+ comb_is(Is, Bool, Safe);
+comb_is([], _Bool, Safe) ->
+ {none,Safe}.
+
+%% combine_list(Fail, List1, List2, Blocks) -> List|none.
+%% Try to combine two switch lists, returning the combined
+%% list or 'none' if not possible.
+%%
+%% The values in the two lists must be all of the same type.
+%%
+%% The code reached from the labels in the first list must
+%% not reach the failure label (if they do, tests could
+%% be repeated).
+%%
+
+combine_lists(Fail, L1, L2, Blocks) ->
+ Ls = beam_ssa:rpo([Lbl || {_,Lbl} <- L1], Blocks),
+ case member(Fail, Ls) of
+ true ->
+ %% One or more of labels in the first list
+ %% could reach the failure label. That
+ %% means that the second switch/br instruction
+ %% will be retained, increasing code size and
+ %% potentially also execution time.
+ none;
+ false ->
+ %% The combined switch will replace both original
+ %% br/switch instructions, leading to a reduction in code
+ %% size and potentially also in execution time.
+ combine_lists_1(L1, L2)
+ end.
+
+combine_lists_1(List0, List1) ->
+ case are_lists_compatible(List0, List1) of
+ true ->
+ First = maps:from_list(List0),
+ List0 ++ [{Val,Lbl} || {Val,Lbl} <- List1,
+ not is_map_key(Val, First)];
+ false ->
+ none
+ end.
+
+are_lists_compatible([{#b_literal{val=Val1},_}|_],
+ [{#b_literal{val=Val2},_}|_]) ->
+ case lit_type(Val1) of
+ none -> false;
+ Type -> Type =:= lit_type(Val2)
+ end.
+
+lit_type(Val) ->
+ if
+ is_atom(Val) -> atom;
+ is_float(Val) -> float;
+ is_integer(Val) -> integer;
+ true -> none
+ end.
+
+%%%
+%%% Calculate used variables for each block.
+%%%
+
+used_vars(Linear) ->
+ used_vars(reverse(Linear), #{}, #{}).
+
+used_vars([{L,#b_blk{is=Is}=Blk}|Bs], UsedVars0, Skip0) ->
+ %% Calculate the variables used by each block and its
+ %% successors. This information is used by
+ %% shortcut_opt/1.
+
+ Successors = beam_ssa:successors(Blk),
+ Used0 = used_vars_succ(Successors, L, UsedVars0, []),
+ Used = used_vars_blk(Blk, Used0),
+ UsedVars = used_vars_phis(Is, L, Used, UsedVars0),
+
+ %% combine_eqs/1 needs different variable usage information than
+ %% shortcut_opt/1. The Skip map will have an entry for each block
+ %% that can be skipped (does not bind any variable used in
+ %% successor). This information is also useful for speeding up
+ %% shortcut_opt/1.
+
+ Defined0 = [Def || #b_set{dst=Def} <- Is],
+ Defined = ordsets:from_list(Defined0),
+ MaySkip = ordsets:is_disjoint(Defined, Used0),
+ case MaySkip of
+ true ->
+ Skip = Skip0#{L=>true},
+ used_vars(Bs, UsedVars, Skip);
+ false ->
+ used_vars(Bs, UsedVars, Skip0)
+ end;
+used_vars([], UsedVars, Skip) ->
+ {UsedVars,Skip}.
+
+used_vars_succ([S|Ss], L, LiveMap, Live0) ->
+ Key = {S,L},
+ case LiveMap of
+ #{Key:=Live} ->
+ %% The successor has a phi node, and the value for
+ %% this block in the phi node is a variable.
+ used_vars_succ(Ss, L, LiveMap, ordsets:union(Live, Live0));
+ #{S:=Live} ->
+ %% No phi node in the successor, or the value for
+ %% this block in the phi node is a literal.
+ used_vars_succ(Ss, L, LiveMap, ordsets:union(Live, Live0));
+ #{} ->
+ %% A peek_message block which has not been processed yet.
+ used_vars_succ(Ss, L, LiveMap, Live0)
+ end;
+used_vars_succ([], _, _, Acc) -> Acc.
+
+used_vars_phis(Is, L, Live0, UsedVars0) ->
+ UsedVars = UsedVars0#{L=>Live0},
+ Phis = takewhile(fun(#b_set{op=Op}) -> Op =:= phi end, Is),
+ case Phis of
+ [] ->
+ UsedVars;
+ [_|_] ->
+ PhiArgs = append([Args || #b_set{args=Args} <- Phis]),
+ case [{P,V} || {#b_var{}=V,P} <- PhiArgs] of
+ [_|_]=PhiVars ->
+ PhiLive0 = rel2fam(PhiVars),
+ PhiLive = [{{L,P},ordsets:union(ordsets:from_list(Vs), Live0)} ||
+ {P,Vs} <- PhiLive0],
+ maps:merge(UsedVars, maps:from_list(PhiLive));
+ [] ->
+ %% There were only literals in the phi node(s).
+ UsedVars
+ end
+ end.
+
+used_vars_blk(#b_blk{is=Is,last=Last}, Used0) ->
+ Used = ordsets:union(Used0, beam_ssa:used(Last)),
+ used_vars_is(reverse(Is), Used).
+
+used_vars_is([#b_set{op=phi}|Is], Used) ->
+ used_vars_is(Is, Used);
+used_vars_is([#b_set{dst=Dst}=I|Is], Used0) ->
+ Used1 = ordsets:union(Used0, beam_ssa:used(I)),
+ Used = ordsets:del_element(Dst, Used1),
+ used_vars_is(Is, Used);
+used_vars_is([], Used) ->
+ Used.
+
+%%%
+%%% Common utilities.
+%%%
+
+sub(#b_set{args=Args}=I, Sub) ->
+ I#b_set{args=[sub_arg(A, Sub) || A <- Args]}.
+
+sub_arg(#b_var{}=Old, Sub) ->
+ case Sub of
+ #{Old:=New} -> New;
+ #{} -> Old
+ end;
+sub_arg(Old, _Sub) -> Old.
+
+rel2fam(S0) ->
+ S1 = sofs:relation(S0),
+ S = sofs:rel2fam(S1),
+ sofs:to_external(S).
diff --git a/lib/compiler/src/beam_ssa_funs.erl b/lib/compiler/src/beam_ssa_funs.erl
new file mode 100644
index 0000000000..e77c00fa89
--- /dev/null
+++ b/lib/compiler/src/beam_ssa_funs.erl
@@ -0,0 +1,149 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018. All 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%
+%%
+
+%%%
+%%% If a fun is defined locally and only used for calls, it can be replaced
+%%% with direct calls to the relevant function. This greatly speeds up "named
+%%% functions" (which rely on make_fun to recreate themselves) and macros that
+%%% wrap their body in a fun.
+%%%
+
+-module(beam_ssa_funs).
+
+-export([module/2]).
+
+-include("beam_ssa.hrl").
+
+-import(lists, [foldl/3]).
+
+-spec module(Module, Options) -> Result when
+ Module :: beam_ssa:b_module(),
+ Options :: [compile:option()],
+ Result :: {ok, beam_ssa:b_module()}.
+
+module(#b_module{body=Fs0}=Module, _Opts) ->
+ Trampolines = foldl(fun find_trampolines/2, #{}, Fs0),
+ Fs = [lfo(F, Trampolines) || F <- Fs0],
+ {ok, Module#b_module{body=Fs}}.
+
+%% If a function does absolutely nothing beyond calling another function with
+%% the same arguments in the same order, we can shave off a call by short-
+%% circuiting it.
+find_trampolines(#b_function{args=Args,bs=Blocks}=F, Trampolines) ->
+ case map_get(0, Blocks) of
+ #b_blk{is=[#b_set{op=call,
+ args=[#b_local{}=Actual | Args],
+ dst=Dst}],
+ last=#b_ret{arg=Dst}} ->
+ {_, Name, Arity} = beam_ssa:get_anno(func_info, F),
+ Trampoline = #b_local{name=#b_literal{val=Name},arity=Arity},
+ Trampolines#{Trampoline => Actual};
+ _ ->
+ Trampolines
+ end.
+
+lfo(#b_function{bs=Blocks0}=F, Trampolines) ->
+ Linear0 = beam_ssa:linearize(Blocks0),
+ Linear = lfo_optimize(Linear0, lfo_analyze(Linear0, #{}), Trampolines),
+ F#b_function{bs=maps:from_list(Linear)}.
+
+%% Gather a map of the locally defined funs that are only used for calls.
+lfo_analyze([{_L,#b_blk{is=Is,last=Last}}|Bs], LFuns0) ->
+ LFuns = lfo_analyze_last(Last, lfo_analyze_is(Is, LFuns0)),
+ lfo_analyze(Bs, LFuns);
+lfo_analyze([], LFuns) ->
+ LFuns.
+
+lfo_analyze_is([#b_set{op=make_fun,
+ dst=Dst,
+ args=[#b_local{} | FreeVars]}=Def | Is],
+ LFuns0) ->
+ LFuns = maps:put(Dst, Def, maps:without(FreeVars, LFuns0)),
+ lfo_analyze_is(Is, LFuns);
+lfo_analyze_is([#b_set{op=call,
+ args=[Fun | CallArgs]} | Is],
+ LFuns) when is_map_key(Fun, LFuns) ->
+ #b_set{args=[#b_local{arity=Arity} | FreeVars]} = map_get(Fun, LFuns),
+ case length(CallArgs) + length(FreeVars) of
+ Arity ->
+ lfo_analyze_is(Is, maps:without(CallArgs, LFuns));
+ _ ->
+ %% This will `badarity` at runtime, and it's easier to disable the
+ %% optimization than to simulate it.
+ lfo_analyze_is(Is, maps:without([Fun | CallArgs], LFuns))
+ end;
+lfo_analyze_is([#b_set{args=Args} | Is], LFuns) when map_size(LFuns) =/= 0 ->
+ %% We disqualify funs that are used outside calls because this forces them
+ %% to be created anyway, and the slight performance gain from direct calls
+ %% is not enough to offset the potential increase in stack frame size (the
+ %% free variables need to be kept alive until the call).
+ %%
+ %% This is also a kludge to make HiPE work, as the latter will generate
+ %% code with the assumption that the functions referenced in a make_fun
+ %% will only be used by funs, which will not be the case if we mix it with
+ %% direct calls. See cerl_cconv.erl for details.
+ %%
+ %% Future optimizations like delaying fun creation until use may require us
+ %% to copy affected functions so that HiPE gets its own to play with (until
+ %% HiPE is fixed anyway).
+ lfo_analyze_is(Is, maps:without(Args, LFuns));
+lfo_analyze_is([_ | Is], LFuns) ->
+ lfo_analyze_is(Is, LFuns);
+lfo_analyze_is([], LFuns) ->
+ LFuns.
+
+lfo_analyze_last(#b_switch{arg=Arg}, LFuns) ->
+ maps:remove(Arg, LFuns);
+lfo_analyze_last(#b_ret{arg=Arg}, LFuns) ->
+ maps:remove(Arg, LFuns);
+lfo_analyze_last(_, LFuns) ->
+ LFuns.
+
+%% Replace all calls of suitable funs with a direct call to their
+%% implementation. Liveness optimization will get rid of the make_fun
+%% instruction.
+lfo_optimize(Linear, LFuns, _Trampolines) when map_size(LFuns) =:= 0 ->
+ Linear;
+lfo_optimize(Linear, LFuns, Trampolines) ->
+ lfo_optimize_1(Linear, LFuns, Trampolines).
+
+lfo_optimize_1([{L,#b_blk{is=Is0}=Blk}|Bs], LFuns, Trampolines) ->
+ Is = lfo_optimize_is(Is0, LFuns, Trampolines),
+ [{L,Blk#b_blk{is=Is}} | lfo_optimize_1(Bs, LFuns, Trampolines)];
+lfo_optimize_1([], _LFuns, _Trampolines) ->
+ [].
+
+lfo_optimize_is([#b_set{op=call,
+ args=[Fun | CallArgs]}=Call0 | Is],
+ LFuns, Trampolines) when is_map_key(Fun, LFuns) ->
+ #b_set{args=[Local | FreeVars]} = map_get(Fun, LFuns),
+ Args = [lfo_short_circuit(Local, Trampolines) | CallArgs ++ FreeVars],
+ Call = beam_ssa:add_anno(local_fun_opt, Fun, Call0#b_set{args=Args}),
+ [Call | lfo_optimize_is(Is, LFuns, Trampolines)];
+lfo_optimize_is([I | Is], LFuns, Trampolines) ->
+ [I | lfo_optimize_is(Is, LFuns, Trampolines)];
+lfo_optimize_is([], _LFuns, _Trampolines) ->
+ [].
+
+lfo_short_circuit(Call, Trampolines) ->
+ case maps:find(Call, Trampolines) of
+ {ok, Other} -> lfo_short_circuit(Other, Trampolines);
+ error -> Call
+ end.
diff --git a/lib/compiler/src/beam_ssa_lint.erl b/lib/compiler/src/beam_ssa_lint.erl
new file mode 100644
index 0000000000..a003607dab
--- /dev/null
+++ b/lib/compiler/src/beam_ssa_lint.erl
@@ -0,0 +1,349 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018. All 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: Internal consistency checks for the beam_ssa format.
+
+-module(beam_ssa_lint).
+
+-export([module/2, format_error/1]).
+
+-import(lists, [append/1, foldl/3, foreach/2]).
+
+-include("beam_ssa.hrl").
+
+-spec module(#b_module{}, [compile:option()]) ->
+ {'ok',#b_module{}} | {'error',list()}.
+module(#b_module{body=Fs,name=Name}=Mod0, _Options) ->
+ Es0 = append([validate_function(F) || F <- Fs]),
+ case [{?MODULE,E} || E <- Es0] of
+ [] ->
+ {ok, Mod0};
+ [_|_]=Es ->
+ {error,[{atom_to_list(Name), Es}]}
+ end.
+
+-spec format_error(term()) -> iolist().
+format_error({{_M,F,A},{redefined_variable, Name, Old, I}}) ->
+ io_lib:format("~p/~p: Variable ~ts (~ts) redefined by ~ts",
+ [F, A, format_var(Name), format_instr(Old), format_instr(I)]);
+format_error({{_M,F,A},{missing_phi_paths, Paths, I}}) ->
+ io_lib:format("~p/~p: Phi node ~ts doesn't define a value for these "
+ "branches: ~w",
+ [F, A, format_instr(I), Paths]);
+format_error({{_M,F,A},{garbage_phi_paths, Paths, I}}) ->
+ io_lib:format("~p/~p: Phi node ~ts defines a value for these unreachable "
+ "or non-existent branches: ~w",
+ [F, A, format_instr(I), Paths]);
+format_error({{_M,F,A},{unknown_phi_variable, Name, {From, _To}, I}}) ->
+ io_lib:format("~p/~p: Variable ~ts used in phi node ~ts is undefined on "
+ "branch ~w",
+ [F, A, format_var(Name), format_instr(I), From]);
+format_error({{_M,F,A},{unknown_block, Label, I}}) ->
+ io_lib:format("~p/~p: Unknown block ~p referenced in ~ts",
+ [F, A, Label, I]);
+format_error({{_M,F,A},{unknown_variable, Name, I}}) ->
+ io_lib:format("~p/~p: Unbound variable ~ts used in ~ts",
+ [F, A, format_var(Name), format_instr(I)]);
+format_error({{_M,F,A},{phi_inside_block, Name, Id}}) ->
+ io_lib:format("~p/~p: Phi node defining ~ts is not at start of block ~p",
+ [F, A, format_var(Name), Id]);
+format_error({{_M,F,A},{undefined_label_in_phi, Label, I}}) ->
+ io_lib:format("~p/~p: Unknown block label ~p in phi node ~ts",
+ [F, A, Label, format_instr(I)]).
+
+format_instr(I) ->
+ [$',beam_ssa_pp:format_instr(I),$'].
+
+format_var(V) ->
+ beam_ssa_pp:format_var(#b_var{name=V}).
+
+validate_function(F) ->
+ try
+ validate_variables(F),
+ []
+ catch
+ throw:Reason ->
+ #{func_info:=MFA} = F#b_function.anno,
+ [{MFA,Reason}];
+ Class:Error:Stack ->
+ io:fwrite("Function: ~p\n", [F#b_function.anno]),
+ erlang:raise(Class, Error, Stack)
+ end.
+
+-type defined_vars() :: gb_sets:set(beam_ssa:var_name()).
+
+-record(vvars,
+ {blocks :: #{ beam_ssa:label() => beam_ssa:b_blk() },
+ branch_def_vars :: #{
+ %% Describes the variable state at the time of this exact branch (phi
+ %% node validation).
+ {From :: beam_ssa:label(), To :: beam_ssa:label()} => defined_vars(),
+ %% Describes the variable state common to all branches leading to this
+ %% label (un/redefined variable validation).
+ beam_ssa:label() => defined_vars() },
+ defined_vars :: defined_vars()}).
+
+-spec validate_variables(beam_ssa:b_function()) -> ok.
+validate_variables(#b_function{ args = Args, bs = Blocks }) ->
+ %% Prefill the mapping with function arguments.
+ ArgNames = vvars_get_varnames(Args),
+ DefVars = gb_sets:from_list(ArgNames),
+ Entry = 0,
+
+ State = #vvars{blocks = Blocks,
+ branch_def_vars = #{ Entry => DefVars },
+ defined_vars = DefVars},
+ ok = vvars_assert_unique(Blocks, ArgNames),
+ vvars_phi_nodes(vvars_block(Entry, State)).
+
+%% Checks the uniqueness of all variables across all blocks.
+-spec vvars_assert_unique(Blocks, [beam_ssa:var_name()]) -> ok when
+ 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]),
+ _ = foldl(fun(Is, Defined) ->
+ vvars_assert_unique_1(Is, Defined)
+ end, Defined0, BlockIs),
+ ok.
+
+-spec vvars_assert_unique_1(Is, Defined) -> ok when
+ Is :: list(beam_ssa:b_set()),
+ Defined :: #{ beam_ssa:var_name() => beam_ssa:b_set() }.
+vvars_assert_unique_1([#b_set{dst=#b_var{name=DstName}}=I|Is], Defined) ->
+ case Defined of
+ #{DstName:=Old} -> throw({redefined_variable, DstName, Old, I});
+ _ -> vvars_assert_unique_1(Is, Defined#{DstName=>I})
+ end;
+vvars_assert_unique_1([], Defined) ->
+ 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)],
+ ok.
+
+-spec vvars_phi_nodes_1(Is, Id, State) -> ok when
+ Is :: list(beam_ssa:b_set()),
+ Id :: beam_ssa:label(),
+ State :: #vvars{}.
+vvars_phi_nodes_1([#b_set{ op = phi, args = Phis }=I | Is], Id, State) ->
+ ok = vvars_assert_phi_paths(Phis, I, Id, State),
+ ok = vvars_assert_phi_vars(Phis, I, Id, State),
+ vvars_phi_nodes_1(Is, Id, State);
+vvars_phi_nodes_1([_ | Is], Id, _State) ->
+ case [Dst || #b_set{op=phi,dst=#b_var{name=Dst}} <- Is] of
+ [Name|_] ->
+ throw({phi_inside_block, Name, Id});
+ [] ->
+ ok
+ end;
+vvars_phi_nodes_1([], _Id, _State) ->
+ ok.
+
+%% Checks whether all paths leading to this phi node are represented, and that
+%% it doesn't reference any non-existent paths.
+-spec vvars_assert_phi_paths(Phis, I, Id, State) -> ok when
+ Phis :: list({beam_ssa:argument(), beam_ssa:label()}),
+ Id :: beam_ssa:label(),
+ I :: beam_ssa:b_set(),
+ State :: #vvars{}.
+vvars_assert_phi_paths(Phis, I, Id, State) ->
+ BranchKeys = maps:keys(State#vvars.branch_def_vars),
+ RequiredPaths = ordsets:from_list([From || {From, To} <- BranchKeys, To =:= Id]),
+ ProvidedPaths = ordsets:from_list([From || {_Value, From} <- Phis]),
+ case ordsets:subtract(RequiredPaths, ProvidedPaths) of
+ [_|_]=MissingPaths -> throw({missing_phi_paths, MissingPaths, I});
+ [] -> ok
+ end.
+ %% %% The following test is sometimes useful to find missing optimizations.
+ %% %% It is commented out, though, because it can be triggered by
+ %% %% by weird but legal code.
+ %% case ordsets:subtract(ProvidedPaths, RequiredPaths) of
+ %% [_|_]=GarbagePaths -> throw({garbage_phi_paths, GarbagePaths, I});
+ %% [] -> ok
+ %% end.
+
+%% Checks whether all variables used in this phi node are defined in the branch
+%% they arrived on.
+-spec vvars_assert_phi_vars(Phis, I, Id, State) -> ok when
+ Phis :: list({beam_ssa:argument(), beam_ssa:label()}),
+ Id :: beam_ssa:label(),
+ I :: beam_ssa:b_set(),
+ State :: #vvars{}.
+vvars_assert_phi_vars(Phis, I, Id, #vvars{blocks=Blocks,
+ branch_def_vars=BranchDefVars}) ->
+ Vars = [{Var, From} || {#b_var{}=Var, From} <- Phis],
+ foreach(fun({#b_var{name=VarName}, From}) ->
+ BranchKey = {From, Id},
+ case BranchDefVars of
+ #{BranchKey:=DefVars} ->
+ case gb_sets:is_member(VarName, DefVars) of
+ true -> ok;
+ false -> throw({unknown_variable, VarName, I})
+ end;
+ #{} ->
+ throw({unknown_phi_variable, VarName, BranchKey, I})
+ end
+ end, Vars),
+ Labels = [From || {#b_literal{},From} <- Phis],
+ foreach(fun(Label) ->
+ case Blocks of
+ #{Label:=_} ->
+ ok;
+ #{} ->
+ throw({undefined_label_in_phi, Label, I})
+ end
+ end, Labels).
+
+-spec vvars_block(Id, State) -> #vvars{} when
+ Id :: beam_ssa:label(),
+ State :: #vvars{}.
+vvars_block(Id, State0) ->
+ #{ Id := #b_blk{ is = Is, last = Terminator} } = State0#vvars.blocks,
+ #{ Id := DefVars } = State0#vvars.branch_def_vars,
+ State = State0#vvars{ defined_vars = DefVars },
+ vvars_terminator(Terminator, Id, vvars_block_1(Is, State)).
+
+-spec vvars_block_1(Blocks, State) -> #vvars{} when
+ Blocks :: list(beam_ssa:b_blk()),
+ State :: #vvars{}.
+vvars_block_1([], State) ->
+ State;
+vvars_block_1([#b_set{ dst = #b_var{ name = DstName }, op = phi } | Is], State0) ->
+ %% We don't check phi node arguments at this point since we may not have
+ %% visited their definition yet. They'll be handled later on in
+ %% vvars_phi_nodes/1 after all blocks are processed.
+ vvars_block_1(Is, vvars_save_var(DstName, State0));
+vvars_block_1([#b_set{ dst = #b_var{ name = DstName }, args = Args }=I | Is], State0) ->
+ ok = vvars_assert_args(Args, I, State0),
+ vvars_block_1(Is, vvars_save_var(DstName, State0)).
+
+-spec vvars_terminator(Terminator, From, State) -> #vvars{} when
+ Terminator :: beam_ssa:terminator(),
+ From :: beam_ssa:label(),
+ State :: #vvars{}.
+vvars_terminator(#b_ret{ arg = Arg }=I, _From, State) ->
+ ok = vvars_assert_args([Arg], I, State),
+ State;
+vvars_terminator(#b_switch{arg=Arg,fail=Fail,list=Switch}=I, From, State) ->
+ ok = vvars_assert_args([Arg], I, State),
+ ok = vvars_assert_args([A || {A,_Lbl} <- Switch], I, State),
+ Labels = [Fail | [Lbl || {_Arg, Lbl} <- Switch]],
+ ok = vvars_assert_labels(Labels, I, State),
+ vvars_terminator_1(Labels, From, State);
+vvars_terminator(#b_br{bool=#b_literal{val=true},succ=Succ}=I, From, State) ->
+ Labels = [Succ],
+ ok = vvars_assert_labels(Labels, I, State),
+ vvars_terminator_1(Labels, From, State);
+vvars_terminator(#b_br{bool=#b_literal{val=false},fail=Fail}=I, From, State) ->
+ Labels = [Fail],
+ ok = vvars_assert_labels(Labels, I, State),
+ vvars_terminator_1(Labels, From, State);
+vvars_terminator(#b_br{ bool = Arg, succ = Succ, fail = Fail }=I, From, State) ->
+ ok = vvars_assert_args([Arg], I, State),
+ Labels = [Fail, Succ],
+ ok = vvars_assert_labels(Labels, I, State),
+ vvars_terminator_1(Labels, From, State).
+
+-spec vvars_terminator_1(Labels, From, State) -> #vvars{} when
+ Labels :: list(beam_ssa:label()),
+ From :: beam_ssa:label(),
+ State :: #vvars{}.
+vvars_terminator_1(Labels0, From, State0) ->
+ %% Filter out all branches that have already been taken. This should result
+ %% in either all of Labels0 or an empty list.
+ Labels = [To || To <- Labels0,
+ not maps:is_key({From, To}, State0#vvars.branch_def_vars)],
+ true = Labels =:= Labels0 orelse Labels =:= [], %Assertion
+ State1 = foldl(fun(To, State) ->
+ vvars_save_branch(From, To, State)
+ end, State0, Labels),
+ foldl(fun(To, State) ->
+ vvars_block(To, State)
+ end, State1, Labels).
+
+%% Gets all variable names in args, ignoring literals etc
+-spec vvars_get_varnames(Args) -> list(beam_ssa:var_name()) when
+ Args :: list(beam_ssa:argument()).
+vvars_get_varnames(Args) ->
+ [Name || #b_var{ name = Name } <- Args].
+
+%% Checks that all variables in Args are defined in all paths leading to the
+%% current State.
+-spec vvars_assert_args(Args, I, State) -> ok when
+ Args :: list(beam_ssa:argument()),
+ I :: beam_ssa:terminator() | beam_ssa:b_set(),
+ State :: #vvars{}.
+vvars_assert_args(Args, I, #vvars{defined_vars=DefVars}=State) ->
+ foreach(fun(#b_remote{mod=Mod,name=Name}) ->
+ vvars_assert_args([Mod,Name], I, State);
+ (#b_var{name=Name}) ->
+ case gb_sets:is_member(Name, DefVars) of
+ true -> ok;
+ false -> throw({unknown_variable,Name,I})
+ end;
+ (_) -> ok
+ end, Args).
+
+%% Checks that all given labels are defined in State.
+-spec vvars_assert_labels(Labels, I, State) -> ok when
+ Labels :: list(beam_ssa:label()),
+ I :: beam_ssa:terminator(),
+ State :: #vvars{}.
+vvars_assert_labels(Labels, I, #vvars{blocks=Blocks}) ->
+ foreach(fun(Label) ->
+ case maps:is_key(Label, Blocks) of
+ false -> throw({unknown_block, Label, I});
+ true -> ok
+ end
+ end, Labels).
+
+-spec vvars_save_branch(From, To, State) -> #vvars{} when
+ From :: beam_ssa:label(),
+ To :: beam_ssa:label(),
+ State :: #vvars{}.
+vvars_save_branch(From, To, State) ->
+ DefVars = State#vvars.defined_vars,
+ Branches0 = State#vvars.branch_def_vars,
+ case Branches0 of
+ #{ To := LblDefVars } ->
+ MergedVars = vvars_merge_branches(DefVars, LblDefVars),
+
+ Branches = Branches0#{ To => MergedVars, {From, To} => DefVars },
+ State#vvars { branch_def_vars = Branches };
+ _ ->
+ Branches = Branches0#{ To => DefVars, {From, To} => DefVars },
+ State#vvars { branch_def_vars = Branches }
+ end.
+
+-spec vvars_merge_branches(New, Existing) -> defined_vars() when
+ New :: defined_vars(),
+ Existing :: defined_vars().
+vvars_merge_branches(New, Existing) ->
+ gb_sets:intersection(New, Existing).
+
+-spec vvars_save_var(VarName, State) -> #vvars{} when
+ VarName :: beam_ssa:var_name(),
+ State :: #vvars{}.
+vvars_save_var(VarName, State0) ->
+ %% vvars_assert_unique guarantees that variables are never set twice.
+ DefVars = gb_sets:insert(VarName, State0#vvars.defined_vars),
+ State0#vvars{ defined_vars = DefVars }.
diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl
new file mode 100644
index 0000000000..bcf55f3fda
--- /dev/null
+++ b/lib/compiler/src/beam_ssa_opt.erl
@@ -0,0 +1,2273 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018. All 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 is a collection of various optimizations that don't need a separate
+%%% pass by themselves and/or are mutually beneficial to other passes.
+%%%
+%%% The optimizations are applied in "phases," each with a list of sub-passes
+%%% to run. These sub-passes are applied on all functions in a module before
+%%% moving on to the next phase, which lets us gather module-level information
+%%% in one phase and then apply it in the next without having to risk working
+%%% with incomplete information.
+%%%
+%%% Each sub-pass operates on a #st{} record and a func_info_db(), where the
+%%% former is just a #b_function{} whose blocks can be represented either in
+%%% linear or map form, and the latter is a map with information about all
+%%% functions in the module (see beam_ssa_opt.hrl for more details).
+%%%
+
+-module(beam_ssa_opt).
+-export([module/2]).
+
+-include("beam_ssa_opt.hrl").
+
+-import(lists, [all/2,append/1,duplicate/2,foldl/3,keyfind/3,member/2,
+ reverse/1,reverse/2,
+ splitwith/2,sort/1,takewhile/2,unzip/1]).
+
+-define(DEFAULT_REPETITIONS, 2).
+
+-spec module(beam_ssa:b_module(), [compile:option()]) ->
+ {'ok',beam_ssa:b_module()}.
+
+-record(st, {ssa :: [{beam_ssa:label(),beam_ssa:b_blk()}] |
+ beam_ssa:block_map(),
+ args :: [beam_ssa:b_var()],
+ cnt :: beam_ssa:label(),
+ anno :: beam_ssa:anno()}).
+-type st_map() :: #{ func_id() => #st{} }.
+
+module(Module, Opts) ->
+ FuncDb0 = case proplists:get_value(no_module_opt, Opts, false) of
+ false -> build_func_db(Module);
+ true -> #{}
+ end,
+
+ %% Passes that perform module-level optimizations are often aided by
+ %% optimizing callers before callees and vice versa, so we optimize all
+ %% functions in call order, flipping it as required.
+ StMap0 = build_st_map(Module),
+ Order = get_call_order_po(StMap0, FuncDb0),
+
+ Phases =
+ [{Order, prologue_passes(Opts)}] ++
+ repeat(Opts, repeated_passes(Opts), Order) ++
+ [{Order, epilogue_passes(Opts)}],
+
+ {StMap, _FuncDb} = foldl(fun({FuncIds, Ps}, {StMap, FuncDb}) ->
+ phase(FuncIds, Ps, StMap, FuncDb)
+ end, {StMap0, FuncDb0}, Phases),
+
+ {ok, finish(Module, StMap)}.
+
+phase([FuncId | Ids], Ps, StMap, FuncDb0) ->
+ try compile:run_sub_passes(Ps, {map_get(FuncId, StMap), FuncDb0}) of
+ {St, FuncDb} ->
+ phase(Ids, Ps, StMap#{ FuncId => St }, FuncDb)
+ catch
+ Class:Error:Stack ->
+ #b_local{name=#b_literal{val=Name},arity=Arity} = FuncId,
+ io:fwrite("Function: ~w/~w\n", [Name,Arity]),
+ erlang:raise(Class, Error, Stack)
+ end;
+phase([], _Ps, StMap, FuncDb) ->
+ {StMap, FuncDb}.
+
+%% Repeats the given passes, alternating the order between runs to make the
+%% type pass more efficient.
+repeat(Opts, Ps, OrderA) ->
+ Repeat = proplists:get_value(ssa_opt_repeat, Opts, ?DEFAULT_REPETITIONS),
+ OrderB = reverse(OrderA),
+ repeat_1(Repeat, Ps, OrderA, OrderB).
+
+repeat_1(0, _Opts, _OrderA, _OrderB) ->
+ [];
+repeat_1(N, Ps, OrderA, OrderB) when N > 0, N rem 2 =:= 0 ->
+ [{OrderA, Ps} | repeat_1(N - 1, Ps, OrderA, OrderB)];
+repeat_1(N, Ps, OrderA, OrderB) when N > 0, N rem 2 =:= 1 ->
+ [{OrderB, Ps} | repeat_1(N - 1, Ps, OrderA, OrderB)].
+
+%%
+
+get_func_id(F) ->
+ {_Mod, Name, Arity} = beam_ssa:get_anno(func_info, F),
+ #b_local{name=#b_literal{val=Name}, arity=Arity}.
+
+-spec build_st_map(#b_module{}) -> st_map().
+build_st_map(#b_module{body=Fs}) ->
+ build_st_map_1(Fs, #{}).
+
+build_st_map_1([F | Fs], Map) ->
+ #b_function{anno=Anno,args=Args,cnt=Counter,bs=Bs} = F,
+ St = #st{anno=Anno,args=Args,cnt=Counter,ssa=Bs},
+ build_st_map_1(Fs, Map#{ get_func_id(F) => St });
+build_st_map_1([], Map) ->
+ Map.
+
+-spec finish(#b_module{}, st_map()) -> #b_module{}.
+finish(#b_module{body=Fs0}=Module, StMap) ->
+ Module#b_module{body=finish_1(Fs0, StMap)}.
+
+finish_1([F0 | Fs], StMap) ->
+ #st{anno=Anno,cnt=Counter,ssa=Blocks} = map_get(get_func_id(F0), StMap),
+ F = F0#b_function{anno=Anno,bs=Blocks,cnt=Counter},
+ [F | finish_1(Fs, StMap)];
+finish_1([], _StMap) ->
+ [].
+
+%%
+
+-define(PASS(N), {N,fun N/1}).
+
+prologue_passes(Opts) ->
+ Ps = [?PASS(ssa_opt_split_blocks),
+ ?PASS(ssa_opt_coalesce_phis),
+ ?PASS(ssa_opt_tail_phis),
+ ?PASS(ssa_opt_element),
+ ?PASS(ssa_opt_linearize),
+ ?PASS(ssa_opt_tuple_size),
+ ?PASS(ssa_opt_record),
+ ?PASS(ssa_opt_cse), %Helps the first type pass.
+ ?PASS(ssa_opt_type_start)],
+ passes_1(Ps, Opts).
+
+%% These passes all benefit from each other (in roughly this order), so they
+%% are repeated as required.
+repeated_passes(Opts) ->
+ Ps = [?PASS(ssa_opt_live),
+ ?PASS(ssa_opt_bs_puts),
+ ?PASS(ssa_opt_dead),
+ ?PASS(ssa_opt_cse),
+ ?PASS(ssa_opt_tail_phis),
+ ?PASS(ssa_opt_type_continue)], %Must run after ssa_opt_dead to
+ %clean up phi nodes.
+ passes_1(Ps, Opts).
+
+epilogue_passes(Opts) ->
+ Ps = [?PASS(ssa_opt_type_finish),
+ ?PASS(ssa_opt_float),
+ ?PASS(ssa_opt_sw),
+
+ %% Run live one more time to clean up after the float and sw
+ %% passes.
+ ?PASS(ssa_opt_live),
+ ?PASS(ssa_opt_bsm),
+ ?PASS(ssa_opt_bsm_units),
+ ?PASS(ssa_opt_bsm_shortcut),
+ ?PASS(ssa_opt_blockify),
+ ?PASS(ssa_opt_sink),
+ ?PASS(ssa_opt_merge_blocks),
+ ?PASS(ssa_opt_get_tuple_element),
+ ?PASS(ssa_opt_trim_unreachable)],
+ passes_1(Ps, Opts).
+
+passes_1(Ps, Opts0) ->
+ Negations = [{list_to_atom("no_"++atom_to_list(N)),N} ||
+ {N,_} <- Ps],
+ Opts = proplists:substitute_negations(Negations, Opts0),
+ [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].
+
+%% Builds a function information map with basic information about incoming and
+%% outgoing local calls, as well as whether the function is exported.
+-spec build_func_db(#b_module{}) -> func_info_db().
+build_func_db(#b_module{body=Fs,exports=Exports}) ->
+ try
+ fdb_1(Fs, gb_sets:from_list(Exports), #{})
+ catch
+ %% All module-level optimizations are invalid when a NIF can override a
+ %% function, so we have to bail out.
+ throw:load_nif -> #{}
+ end.
+
+fdb_1([#b_function{ args=Args,bs=Bs }=F | Fs], Exports, FuncDb0) ->
+ Id = get_func_id(F),
+
+ #b_local{name=#b_literal{val=Name}, arity=Arity} = Id,
+ Exported = gb_sets:is_element({Name, Arity}, Exports),
+ ArgTypes = duplicate(length(Args), #{}),
+
+ FuncDb1 = case FuncDb0 of
+ %% We may have an entry already if someone's called us.
+ #{ Id := Info } ->
+ FuncDb0#{ Id := Info#func_info{ exported=Exported,
+ arg_types=ArgTypes }};
+ #{} ->
+ FuncDb0#{ Id => #func_info{ exported=Exported,
+ arg_types=ArgTypes }}
+ end,
+
+ FuncDb = beam_ssa:fold_rpo(fun(_L, #b_blk{is=Is}, FuncDb) ->
+ fdb_is(Is, Id, FuncDb)
+ end, FuncDb1, Bs),
+
+ fdb_1(Fs, Exports, FuncDb);
+fdb_1([], _Exports, FuncDb) ->
+ FuncDb.
+
+fdb_is([#b_set{op=call,
+ args=[#b_local{}=Callee | _]} | Is],
+ Caller, FuncDb) ->
+ fdb_is(Is, Caller, fdb_update(Caller, Callee, FuncDb));
+fdb_is([#b_set{op=call,
+ args=[#b_remote{mod=#b_literal{val=erlang},
+ name=#b_literal{val=load_nif}},
+ _Path, _LoadInfo]} | _Is], _Caller, _FuncDb) ->
+ throw(load_nif);
+fdb_is([_ | Is], Caller, FuncDb) ->
+ fdb_is(Is, Caller, FuncDb);
+fdb_is([], _Caller, FuncDb) ->
+ FuncDb.
+
+fdb_update(Caller, Callee, FuncDb) ->
+ CallerVertex = maps:get(Caller, FuncDb, #func_info{}),
+ CalleeVertex = maps:get(Callee, FuncDb, #func_info{}),
+
+ Calls = ordsets:add_element(Callee, CallerVertex#func_info.out),
+ CalledBy = ordsets:add_element(Caller, CalleeVertex#func_info.in),
+
+ FuncDb#{ Caller => CallerVertex#func_info{out=Calls},
+ Callee => CalleeVertex#func_info{in=CalledBy} }.
+
+%% Returns the post-order of all local calls in this module. That is,
+%% called functions will be ordered before the functions calling them.
+%%
+%% Functions where module-level optimization is disabled are added last in
+%% arbitrary order.
+
+get_call_order_po(StMap, FuncDb) ->
+ Order = gco_po(FuncDb),
+ Order ++ maps:fold(fun(K, _V, Acc) ->
+ case is_map_key(K, FuncDb) of
+ false -> [K | Acc];
+ true -> Acc
+ end
+ end, [], StMap).
+
+gco_po(FuncDb) ->
+ All = sort(maps:keys(FuncDb)),
+ {RPO,_} = gco_rpo(All, FuncDb, cerl_sets:new(), []),
+ reverse(RPO).
+
+gco_rpo([Id|Ids], FuncDb, Seen0, Acc0) ->
+ case cerl_sets:is_element(Id, Seen0) of
+ true ->
+ gco_rpo(Ids, FuncDb, Seen0, Acc0);
+ false ->
+ #func_info{out=Successors} = map_get(Id, FuncDb),
+ Seen1 = cerl_sets:add_element(Id, Seen0),
+ {Acc,Seen} = gco_rpo(Successors, FuncDb, Seen1, Acc0),
+ gco_rpo(Ids, FuncDb, Seen, [Id|Acc])
+ end;
+gco_rpo([], _, Seen, Acc) ->
+ {Acc,Seen}.
+
+%%%
+%%% Trivial sub passes.
+%%%
+
+ssa_opt_dead({#st{ssa=Linear}=St, FuncDb}) ->
+ {St#st{ssa=beam_ssa_dead:opt(Linear)}, FuncDb}.
+
+ssa_opt_linearize({#st{ssa=Blocks}=St, FuncDb}) ->
+ {St#st{ssa=beam_ssa:linearize(Blocks)}, FuncDb}.
+
+ssa_opt_type_start({#st{ssa=Linear0,args=Args,anno=Anno}=St0, FuncDb0}) ->
+ {Linear, FuncDb} = beam_ssa_type:opt_start(Linear0, Args, Anno, FuncDb0),
+ {St0#st{ssa=Linear}, FuncDb}.
+
+ssa_opt_type_continue({#st{ssa=Linear0,args=Args,anno=Anno}=St0, FuncDb0}) ->
+ {Linear, FuncDb} = beam_ssa_type:opt_continue(Linear0, Args, Anno, FuncDb0),
+ {St0#st{ssa=Linear}, FuncDb}.
+
+ssa_opt_type_finish({#st{args=Args,anno=Anno0}=St0, FuncDb0}) ->
+ {Anno, FuncDb} = beam_ssa_type:opt_finish(Args, Anno0, FuncDb0),
+ {St0#st{anno=Anno}, FuncDb}.
+
+ssa_opt_blockify({#st{ssa=Linear}=St, FuncDb}) ->
+ {St#st{ssa=maps:from_list(Linear)}, FuncDb}.
+
+ssa_opt_trim_unreachable({#st{ssa=Blocks}=St, FuncDb}) ->
+ {St#st{ssa=beam_ssa:trim_unreachable(Blocks)}, FuncDb}.
+
+%%%
+%%% Split blocks before certain instructions to enable more optimizations.
+%%%
+%%% Splitting before element/2 enables the optimization that swaps
+%%% element/2 instructions.
+%%%
+%%% Splitting before call and make_fun instructions gives more opportunities
+%%% for sinking get_tuple_element instructions.
+%%%
+
+ssa_opt_split_blocks({#st{ssa=Blocks0,cnt=Count0}=St, FuncDb}) ->
+ P = fun(#b_set{op={bif,element}}) -> true;
+ (#b_set{op=call}) -> true;
+ (#b_set{op=make_fun}) -> true;
+ (_) -> false
+ end,
+ {Blocks,Count} = beam_ssa:split_blocks(P, Blocks0, Count0),
+ {St#st{ssa=Blocks,cnt=Count}, FuncDb}.
+
+%%%
+%%% Coalesce phi nodes.
+%%%
+%%% Nested cases can led to code such as this:
+%%%
+%%% 10:
+%%% _1 = phi {literal value1, label 8}, {Var, label 9}
+%%% br 11
+%%%
+%%% 11:
+%%% _2 = phi {_1, label 10}, {literal false, label 3}
+%%%
+%%% The phi nodes can be coalesced like this:
+%%%
+%%% 11:
+%%% _2 = phi {literal value1, label 8}, {Var, label 9}, {literal false, label 3}
+%%%
+%%% Coalescing can help other optimizations, and can in some cases reduce register
+%%% shuffling (if the phi variables for two phi nodes happens to be allocated to
+%%% different registers).
+%%%
+
+ssa_opt_coalesce_phis({#st{ssa=Blocks0}=St, FuncDb}) ->
+ Ls = beam_ssa:rpo(Blocks0),
+ Blocks = c_phis_1(Ls, Blocks0),
+ {St#st{ssa=Blocks}, FuncDb}.
+
+c_phis_1([L|Ls], Blocks0) ->
+ case map_get(L, Blocks0) of
+ #b_blk{is=[#b_set{op=phi}|_]}=Blk ->
+ Blocks = c_phis_2(L, Blk, Blocks0),
+ c_phis_1(Ls, Blocks);
+ #b_blk{} ->
+ c_phis_1(Ls, Blocks0)
+ end;
+c_phis_1([], Blocks) -> Blocks.
+
+c_phis_2(L, #b_blk{is=Is0}=Blk0, Blocks0) ->
+ case c_phis_args(Is0, Blocks0) of
+ none ->
+ Blocks0;
+ {_,_,Preds}=Info ->
+ Is = c_rewrite_phis(Is0, Info),
+ Blk = Blk0#b_blk{is=Is},
+ Blocks = Blocks0#{L:=Blk},
+ c_fix_branches(Preds, L, Blocks)
+ end.
+
+c_phis_args([#b_set{op=phi,args=Args0}|Is], Blocks) ->
+ case c_phis_args_1(Args0, Blocks) of
+ none ->
+ c_phis_args(Is, Blocks);
+ Res ->
+ Res
+ end;
+c_phis_args(_, _Blocks) -> none.
+
+c_phis_args_1([{Var,Pred}|As], Blocks) ->
+ case c_get_pred_vars(Var, Pred, Blocks) of
+ none ->
+ c_phis_args_1(As, Blocks);
+ Result ->
+ Result
+ end;
+c_phis_args_1([], _Blocks) -> none.
+
+c_get_pred_vars(Var, Pred, Blocks) ->
+ case map_get(Pred, Blocks) of
+ #b_blk{is=[#b_set{op=phi,dst=Var,args=Args}]} ->
+ {Var,Pred,Args};
+ #b_blk{} ->
+ none
+ end.
+
+c_rewrite_phis([#b_set{op=phi,args=Args0}=I|Is], Info) ->
+ Args = c_rewrite_phi(Args0, Info),
+ [I#b_set{args=Args}|c_rewrite_phis(Is, Info)];
+c_rewrite_phis(Is, _Info) -> Is.
+
+c_rewrite_phi([{Var,Pred}|As], {Var,Pred,Values}) ->
+ Values ++ As;
+c_rewrite_phi([{Value,Pred}|As], {_,Pred,Values}) ->
+ [{Value,P} || {_,P} <- Values] ++ As;
+c_rewrite_phi([A|As], Info) ->
+ [A|c_rewrite_phi(As, Info)];
+c_rewrite_phi([], _Info) -> [].
+
+c_fix_branches([{_,Pred}|As], L, Blocks0) ->
+ #b_blk{last=Last0} = Blk0 = map_get(Pred, Blocks0),
+ #b_br{bool=#b_literal{val=true}} = Last0, %Assertion.
+ Last = Last0#b_br{bool=#b_literal{val=true},succ=L,fail=L},
+ Blk = Blk0#b_blk{last=Last},
+ Blocks = Blocks0#{Pred:=Blk},
+ c_fix_branches(As, L, Blocks);
+c_fix_branches([], _, Blocks) -> Blocks.
+
+%%%
+%%% Eliminate phi nodes in the tail of a function.
+%%%
+%%% Try to eliminate short blocks that starts with a phi node
+%%% and end in a return. For example:
+%%%
+%%% Result = phi { Res1, 4 }, { literal true, 5 }
+%%% Ret = put_tuple literal ok, Result
+%%% ret Ret
+%%%
+%%% The code in this block can be inserted at the end blocks 4 and
+%%% 5. Thus, the following code can be inserted into block 4:
+%%%
+%%% Ret:1 = put_tuple literal ok, Res1
+%%% ret Ret:1
+%%%
+%%% And the following code into block 5:
+%%%
+%%% Ret:2 = put_tuple literal ok, literal true
+%%% ret Ret:2
+%%%
+%%% Which can be further simplified to:
+%%%
+%%% ret literal {ok, true}
+%%%
+%%% This transformation may lead to more code improvements:
+%%%
+%%% - Stack trimming
+%%% - Fewer test_heap instructions
+%%% - Smaller stack frames
+%%%
+
+ssa_opt_tail_phis({#st{ssa=SSA0,cnt=Count0}=St, FuncDb}) ->
+ {SSA,Count} = opt_tail_phis(SSA0, Count0),
+ {St#st{ssa=SSA,cnt=Count}, FuncDb}.
+
+opt_tail_phis(Blocks, Count) when is_map(Blocks) ->
+ opt_tail_phis(maps:values(Blocks), Blocks, Count);
+opt_tail_phis(Linear0, Count0) when is_list(Linear0) ->
+ Blocks0 = maps:from_list(Linear0),
+ {Blocks,Count} = opt_tail_phis(Blocks0, Count0),
+ {beam_ssa:linearize(Blocks),Count}.
+
+opt_tail_phis([#b_blk{is=Is0,last=Last}|Bs], Blocks0, Count0) ->
+ case {Is0,Last} of
+ {[#b_set{op=phi,args=[_,_|_]}|_],#b_ret{arg=#b_var{}}=Ret} ->
+ {Phis,Is} = splitwith(fun(#b_set{op=Op}) -> Op =:= phi end, Is0),
+ case suitable_tail_ops(Is) of
+ true ->
+ {Blocks,Count} = opt_tail_phi(Phis, Is, Ret,
+ Blocks0, Count0),
+ opt_tail_phis(Bs, Blocks, Count);
+ false ->
+ opt_tail_phis(Bs, Blocks0, Count0)
+ end;
+ {_,_} ->
+ opt_tail_phis(Bs, Blocks0, Count0)
+ end;
+opt_tail_phis([], Blocks, Count) ->
+ {Blocks,Count}.
+
+opt_tail_phi(Phis0, Is, Ret, Blocks0, Count0) ->
+ Phis = rel2fam(reduce_phis(Phis0)),
+ {Blocks,Count,Cost} =
+ foldl(fun(PhiArg, Acc) ->
+ opt_tail_phi_arg(PhiArg, Is, Ret, Acc)
+ end, {Blocks0,Count0,0}, Phis),
+ MaxCost = length(Phis) * 3 + 2,
+ if
+ Cost =< MaxCost ->
+ %% The transformation would cause at most a slight
+ %% increase in code size if no more optimizations
+ %% can be applied.
+ {Blocks,Count};
+ true ->
+ %% The code size would be increased too much.
+ {Blocks0,Count0}
+ end.
+
+reduce_phis([#b_set{dst=PhiDst,args=PhiArgs}|Is]) ->
+ [{L,{PhiDst,Val}} || {Val,L} <- PhiArgs] ++ reduce_phis(Is);
+reduce_phis([]) -> [].
+
+opt_tail_phi_arg({PredL,Sub0}, Is0, Ret0, {Blocks0,Count0,Cost0}) ->
+ Blk0 = map_get(PredL, Blocks0),
+ #b_blk{is=IsPrefix,last=#b_br{succ=Next,fail=Next}} = Blk0,
+ case is_exit_bif(IsPrefix) of
+ false ->
+ Sub1 = maps:from_list(Sub0),
+ {Is1,Count,Sub} = new_names(Is0, Sub1, Count0, []),
+ Is2 = [sub(I, Sub) || I <- Is1],
+ Cost = build_cost(Is2, Cost0),
+ Is = IsPrefix ++ Is2,
+ Ret = sub(Ret0, Sub),
+ Blk = Blk0#b_blk{is=Is,last=Ret},
+ Blocks = Blocks0#{PredL:=Blk},
+ {Blocks,Count,Cost};
+ true ->
+ %% The block ends in a call to a function that
+ %% will cause an exception.
+ {Blocks0,Count0,Cost0+3}
+ end.
+
+is_exit_bif([#b_set{op=call,
+ args=[#b_remote{mod=#b_literal{val=Mod},
+ name=#b_literal{val=Name}}|Args]}]) ->
+ erl_bifs:is_exit_bif(Mod, Name, length(Args));
+is_exit_bif(_) -> false.
+
+new_names([#b_set{dst=Dst}=I|Is], Sub0, Count0, Acc) ->
+ {NewDst,Count} = new_var(Dst, Count0),
+ Sub = Sub0#{Dst=>NewDst},
+ new_names(Is, Sub, Count, [I#b_set{dst=NewDst}|Acc]);
+new_names([], Sub, Count, Acc) ->
+ {reverse(Acc),Count,Sub}.
+
+suitable_tail_ops(Is) ->
+ all(fun(#b_set{op=Op}) ->
+ is_suitable_tail_op(Op)
+ end, Is).
+
+is_suitable_tail_op({bif,_}) -> true;
+is_suitable_tail_op(put_list) -> true;
+is_suitable_tail_op(put_tuple) -> true;
+is_suitable_tail_op(_) -> false.
+
+build_cost([#b_set{op=put_list,args=Args}|Is], Cost) ->
+ case are_all_literals(Args) of
+ true ->
+ build_cost(Is, Cost);
+ false ->
+ build_cost(Is, Cost + 1)
+ end;
+build_cost([#b_set{op=put_tuple,args=Args}|Is], Cost) ->
+ case are_all_literals(Args) of
+ true ->
+ build_cost(Is, Cost);
+ false ->
+ build_cost(Is, Cost + length(Args) + 1)
+ end;
+build_cost([#b_set{op={bif,_},args=Args}|Is], Cost) ->
+ case are_all_literals(Args) of
+ true ->
+ build_cost(Is, Cost);
+ false ->
+ build_cost(Is, Cost + 1)
+ end;
+build_cost([], Cost) -> Cost.
+
+are_all_literals(Args) ->
+ all(fun(#b_literal{}) -> true;
+ (_) -> false
+ end, Args).
+
+%%%
+%%% Order element/2 calls.
+%%%
+%%% Order an unbroken chain of element/2 calls for the same tuple
+%%% with the same failure label so that the highest element is
+%%% retrieved first. That will allow the other element/2 calls to
+%%% be replaced with get_tuple_element/3 instructions.
+%%%
+
+ssa_opt_element({#st{ssa=Blocks}=St, FuncDb}) ->
+ %% Collect the information about element instructions in this
+ %% function.
+ GetEls = collect_element_calls(beam_ssa:linearize(Blocks)),
+
+ %% Collect the element instructions into chains. The
+ %% element calls in each chain are ordered in reverse
+ %% execution order.
+ Chains = collect_chains(GetEls, []),
+
+ %% For each chain, swap the first element call with the
+ %% element call with the highest index.
+ {St#st{ssa=swap_element_calls(Chains, Blocks)}, FuncDb}.
+
+collect_element_calls([{L,#b_blk{is=Is0,last=Last}}|Bs]) ->
+ case {Is0,Last} of
+ {[#b_set{op={bif,element},dst=Element,
+ args=[#b_literal{val=N},#b_var{}=Tuple]},
+ #b_set{op=succeeded,dst=Bool,args=[Element]}],
+ #b_br{bool=Bool,succ=Succ,fail=Fail}} ->
+ Info = {L,Succ,{Tuple,Fail},N},
+ [Info|collect_element_calls(Bs)];
+ {_,_} ->
+ collect_element_calls(Bs)
+ end;
+collect_element_calls([]) -> [].
+
+collect_chains([{This,_,V,_}=El|Els], [{_,This,V,_}|_]=Chain) ->
+ %% Add to the previous chain.
+ collect_chains(Els, [El|Chain]);
+collect_chains([El|Els], [_,_|_]=Chain) ->
+ %% Save the previous chain and start a new chain.
+ [Chain|collect_chains(Els, [El])];
+collect_chains([El|Els], _Chain) ->
+ %% The previous chain is too short; discard it and start a new.
+ collect_chains(Els, [El]);
+collect_chains([], [_,_|_]=Chain) ->
+ %% Save the last chain.
+ [Chain];
+collect_chains([], _) -> [].
+
+swap_element_calls([[{L,_,_,N}|_]=Chain|Chains], Blocks0) ->
+ Blocks = swap_element_calls_1(Chain, {N,L}, Blocks0),
+ swap_element_calls(Chains, Blocks);
+swap_element_calls([], Blocks) -> Blocks.
+
+swap_element_calls_1([{L1,_,_,N1}], {N2,L2}, Blocks) when N2 > N1 ->
+ %% We have reached the end of the chain, and the first
+ %% element instrution to be executed. Its index is lower
+ %% than the maximum index found while traversing the chain,
+ %% so we will need to swap the instructions.
+ #{L1:=Blk1,L2:=Blk2} = Blocks,
+ [#b_set{dst=Dst1}=GetEl1,Succ1] = Blk1#b_blk.is,
+ [#b_set{dst=Dst2}=GetEl2,Succ2] = Blk2#b_blk.is,
+ Is1 = [GetEl2,Succ1#b_set{args=[Dst2]}],
+ Is2 = [GetEl1,Succ2#b_set{args=[Dst1]}],
+ Blocks#{L1:=Blk1#b_blk{is=Is1},L2:=Blk2#b_blk{is=Is2}};
+swap_element_calls_1([{L,_,_,N1}|Els], {N2,_}, Blocks) when N1 > N2 ->
+ swap_element_calls_1(Els, {N2,L}, Blocks);
+swap_element_calls_1([_|Els], Highest, Blocks) ->
+ swap_element_calls_1(Els, Highest, Blocks);
+swap_element_calls_1([], _, Blocks) ->
+ %% Nothing to do. The element call with highest index
+ %% is already the first one to be executed.
+ Blocks.
+
+%%%
+%%% Record optimization.
+%%%
+%%% Replace tuple matching with an is_tagged_tuple instruction
+%%% when applicable.
+%%%
+
+ssa_opt_record({#st{ssa=Linear}=St, FuncDb}) ->
+ Blocks = maps:from_list(Linear),
+ {St#st{ssa=record_opt(Linear, Blocks)}, FuncDb}.
+
+record_opt([{L,#b_blk{is=Is0,last=Last}=Blk0}|Bs], Blocks) ->
+ Is = record_opt_is(Is0, Last, Blocks),
+ Blk = Blk0#b_blk{is=Is},
+ [{L,Blk}|record_opt(Bs, Blocks)];
+record_opt([], _Blocks) -> [].
+
+record_opt_is([#b_set{op={bif,is_tuple},dst=Bool,args=[Tuple]}=Set],
+ Last, Blocks) ->
+ case is_tagged_tuple(Tuple, Bool, Last, Blocks) of
+ {yes,Size,Tag} ->
+ Args = [Tuple,Size,Tag],
+ [Set#b_set{op=is_tagged_tuple,args=Args}];
+ no ->
+ [Set]
+ end;
+record_opt_is([I|Is]=Is0, #b_br{bool=Bool}=Last, Blocks) ->
+ case is_tagged_tuple_1(Is0, Last, Blocks) of
+ {yes,_Fail,Tuple,Arity,Tag} ->
+ Args = [Tuple,Arity,Tag],
+ [I#b_set{op=is_tagged_tuple,dst=Bool,args=Args}];
+ no ->
+ [I|record_opt_is(Is, Last, Blocks)]
+ end;
+record_opt_is([I|Is], Last, Blocks) ->
+ [I|record_opt_is(Is, Last, Blocks)];
+record_opt_is([], _Last, _Blocks) -> [].
+
+is_tagged_tuple(#b_var{}=Tuple, Bool,
+ #b_br{bool=Bool,succ=Succ,fail=Fail},
+ Blocks) ->
+ #b_blk{is=Is,last=Last} = map_get(Succ, Blocks),
+ case is_tagged_tuple_1(Is, Last, Blocks) of
+ {yes,Fail,Tuple,Arity,Tag} ->
+ {yes,Arity,Tag};
+ _ ->
+ no
+ end;
+is_tagged_tuple(_, _, _, _) -> no.
+
+is_tagged_tuple_1(Is, Last, Blocks) ->
+ case {Is,Last} of
+ {[#b_set{op={bif,tuple_size},dst=ArityVar,
+ args=[#b_var{}=Tuple]},
+ #b_set{op={bif,'=:='},
+ dst=Bool,
+ args=[ArityVar, #b_literal{val=ArityVal}=Arity]}],
+ #b_br{bool=Bool,succ=Succ,fail=Fail}}
+ when is_integer(ArityVal) ->
+ SuccBlk = map_get(Succ, Blocks),
+ case is_tagged_tuple_2(SuccBlk, Tuple, Fail) of
+ no ->
+ no;
+ {yes,Tag} ->
+ {yes,Fail,Tuple,Arity,Tag}
+ end;
+ _ ->
+ no
+ end.
+
+is_tagged_tuple_2(#b_blk{is=Is,
+ last=#b_br{bool=#b_var{}=Bool,fail=Fail}},
+ Tuple, Fail) ->
+ is_tagged_tuple_3(Is, Bool, Tuple);
+is_tagged_tuple_2(#b_blk{}, _, _) -> no.
+
+is_tagged_tuple_3([#b_set{op=get_tuple_element,
+ dst=TagVar,
+ args=[#b_var{}=Tuple,#b_literal{val=0}]}|Is],
+ Bool, Tuple) ->
+ is_tagged_tuple_4(Is, Bool, TagVar);
+is_tagged_tuple_3([_|Is], Bool, Tuple) ->
+ is_tagged_tuple_3(Is, Bool, Tuple);
+is_tagged_tuple_3([], _, _) -> no.
+
+is_tagged_tuple_4([#b_set{op={bif,'=:='},dst=Bool,
+ args=[#b_var{}=TagVar,
+ #b_literal{val=TagVal}=Tag]}],
+ Bool, TagVar) when is_atom(TagVal) ->
+ {yes,Tag};
+is_tagged_tuple_4([_|Is], Bool, TagVar) ->
+ is_tagged_tuple_4(Is, Bool, TagVar);
+is_tagged_tuple_4([], _, _) -> no.
+
+%%%
+%%% Common subexpression elimination (CSE).
+%%%
+%%% Eliminate repeated evaluation of identical expressions. To avoid
+%%% increasing the size of the stack frame, we don't eliminate
+%%% subexpressions across instructions that clobber the X registers.
+%%%
+
+ssa_opt_cse({#st{ssa=Linear}=St, FuncDb}) ->
+ M = #{0=>#{}},
+ {St#st{ssa=cse(Linear, #{}, M)}, FuncDb}.
+
+cse([{L,#b_blk{is=Is0,last=Last0}=Blk}|Bs], Sub0, M0) ->
+ Es0 = map_get(L, M0),
+ {Is1,Es,Sub} = cse_is(Is0, Es0, Sub0, []),
+ Last = sub(Last0, Sub),
+ M = cse_successors(Is1, Blk, Es, M0),
+ Is = reverse(Is1),
+ [{L,Blk#b_blk{is=Is,last=Last}}|cse(Bs, Sub, M)];
+cse([], _, _) -> [].
+
+cse_successors([#b_set{op=succeeded,args=[Src]},Bif|_], Blk, EsSucc, M0) ->
+ case cse_suitable(Bif) of
+ true ->
+ %% The previous instruction only has a valid value at the success branch.
+ %% 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),
+ cse_successors_1([Fail], EsFail, M);
+ false ->
+ %% There can't be any replacement for Src in EsSucc. No need for
+ %% any special handling.
+ cse_successors_1(beam_ssa:successors(Blk), EsSucc, M0)
+ end;
+cse_successors(_Is, Blk, Es, M) ->
+ cse_successors_1(beam_ssa:successors(Blk), Es, M).
+
+cse_successors_1([L|Ls], Es0, M) ->
+ case M of
+ #{L:=Es1} when map_size(Es1) =:= 0 ->
+ %% The map is already empty. No need to do anything
+ %% since the intersection will be empty.
+ cse_successors_1(Ls, Es0, M);
+ #{L:=Es1} ->
+ %% Calculate the intersection of the two maps.
+ %% Both keys and values must match.
+ Es = maps:filter(fun(Key, Value) ->
+ case Es1 of
+ #{Key:=Value} -> true;
+ #{} -> false
+ end
+ end, Es0),
+ cse_successors_1(Ls, Es0, M#{L:=Es});
+ #{} ->
+ cse_successors_1(Ls, Es0, M#{L=>Es0})
+ end;
+cse_successors_1([], _, M) -> M.
+
+cse_is([#b_set{op=succeeded,dst=Bool,args=[Src]}=I0|Is], Es, Sub0, Acc) ->
+ I = sub(I0, Sub0),
+ case I of
+ #b_set{args=[Src]} ->
+ cse_is(Is, Es, Sub0, [I|Acc]);
+ #b_set{} ->
+ %% The previous instruction has been eliminated. Eliminate the
+ %% 'succeeded' instruction too.
+ Sub = Sub0#{Bool=>#b_literal{val=true}},
+ cse_is(Is, Es, Sub, Acc)
+ end;
+cse_is([#b_set{dst=Dst}=I0|Is], Es0, Sub0, Acc) ->
+ I = sub(I0, Sub0),
+ case beam_ssa:clobbers_xregs(I) of
+ true ->
+ %% Retaining the expressions map across calls and other
+ %% clobbering instructions would work, but it would cause
+ %% the common subexpressions to be saved to Y registers,
+ %% which would probably increase the size of the stack
+ %% frame.
+ cse_is(Is, #{}, Sub0, [I|Acc]);
+ false ->
+ case cse_expr(I) of
+ none ->
+ %% Not suitable for CSE.
+ cse_is(Is, Es0, Sub0, [I|Acc]);
+ {ok,ExprKey} ->
+ case Es0 of
+ #{ExprKey:=Src} ->
+ Sub = Sub0#{Dst=>Src},
+ cse_is(Is, Es0, Sub, Acc);
+ #{} ->
+ Es = Es0#{ExprKey=>Dst},
+ cse_is(Is, Es, Sub0, [I|Acc])
+ end
+ end
+ end;
+cse_is([], Es, Sub, Acc) ->
+ {Acc,Es,Sub}.
+
+cse_expr(#b_set{op=Op,args=Args}=I) ->
+ case cse_suitable(I) of
+ true -> {ok,{Op,Args}};
+ false -> none
+ end.
+
+cse_suitable(#b_set{op=get_hd}) -> true;
+cse_suitable(#b_set{op=get_tl}) -> true;
+cse_suitable(#b_set{op=put_list}) -> true;
+cse_suitable(#b_set{op=get_tuple_element}) -> true;
+cse_suitable(#b_set{op=put_tuple}) -> true;
+cse_suitable(#b_set{op={bif,tuple_size}}) ->
+ %% Doing CSE for tuple_size/1 can prevent the
+ %% creation of test_arity and select_tuple_arity
+ %% instructions. That could decrease performance
+ %% and beam_validator could fail to understand
+ %% that tuple operations that follow are safe.
+ false;
+cse_suitable(#b_set{anno=Anno,op={bif,Name},args=Args}) ->
+ %% Doing CSE for floating point operators is unsafe.
+ %% Doing CSE for comparison operators would prevent
+ %% creation of 'test' instructions.
+ Arity = length(Args),
+ not (is_map_key(float_op, Anno) orelse
+ erl_internal:new_type_test(Name, Arity) orelse
+ erl_internal:comp_op(Name, Arity) orelse
+ erl_internal:bool_op(Name, Arity));
+cse_suitable(#b_set{}) -> false.
+
+%%%
+%%% Using floating point instructions.
+%%%
+%%% Use the special floating points version of arithmetic
+%%% instructions, if the operands are known to be floats or the result
+%%% of the operation will be a float.
+%%%
+%%% The float instructions were never used in guards before, so we
+%%% will take special care to keep not using them in guards. Using
+%%% them in guards would require a new version of the 'fconv'
+%%% instruction that would take a failure label. Since it is unlikely
+%%% that using float instructions in guards would be benefical, why
+%%% bother implementing a new instruction? Also, implementing float
+%%% instructions in guards in HiPE could turn out to be a lot of work.
+%%%
+
+-record(fs,
+ {s=undefined :: 'undefined' | 'cleared',
+ regs=#{} :: #{beam_ssa:b_var():=beam_ssa:b_var()},
+ fail=none :: 'none' | beam_ssa:label(),
+ non_guards :: gb_sets:set(beam_ssa:label()),
+ bs :: beam_ssa:block_map()
+ }).
+
+ssa_opt_float({#st{ssa=Linear0,cnt=Count0}=St, FuncDb}) ->
+ NonGuards0 = float_non_guards(Linear0),
+ NonGuards = gb_sets:from_list(NonGuards0),
+ Blocks = maps:from_list(Linear0),
+ Fs = #fs{non_guards=NonGuards,bs=Blocks},
+ {Linear,Count} = float_opt(Linear0, Count0, Fs),
+ {St#st{ssa=Linear,cnt=Count}, FuncDb}.
+
+float_non_guards([{L,#b_blk{is=Is}}|Bs]) ->
+ case Is of
+ [#b_set{op=landingpad}|_] ->
+ [L|float_non_guards(Bs)];
+ _ ->
+ float_non_guards(Bs)
+ end;
+float_non_guards([]) -> [?BADARG_BLOCK].
+
+float_opt([{L,#b_blk{last=#b_br{fail=F}}=Blk}|Bs0],
+ Count0, #fs{non_guards=NonGuards}=Fs) ->
+ case gb_sets:is_member(F, NonGuards) of
+ true ->
+ %% This block is not inside a guard.
+ %% We can do the optimization.
+ float_opt_1(L, Blk, Bs0, Count0, Fs);
+ false ->
+ %% This block is inside a guard. Don't do
+ %% any floating point optimizations.
+ {Bs,Count} = float_opt(Bs0, Count0, Fs),
+ {[{L,Blk}|Bs],Count}
+ end;
+float_opt([{L,Blk}|Bs], Count, Fs) ->
+ float_opt_1(L, Blk, Bs, Count, Fs);
+float_opt([], Count, _Fs) ->
+ {[],Count}.
+
+float_opt_1(L, #b_blk{is=Is0}=Blk0, Bs0, Count0, Fs0) ->
+ case float_opt_is(Is0, Fs0, Count0, []) of
+ {Is1,Fs1,Count1} ->
+ Fs2 = float_fail_label(Blk0, Fs1),
+ Fail = Fs2#fs.fail,
+ {Flush,Blk,Fs,Count2} = float_maybe_flush(Blk0, Fs2, Count1),
+ Split = float_split_conv(Is1, Blk),
+ {Blks0,Count3} = float_number(Split, L, Count2),
+ {Blks,Count4} = float_conv(Blks0, Fail, Count3),
+ {Bs,Count} = float_opt(Bs0, Count4, Fs),
+ {Blks++Flush++Bs,Count};
+ none ->
+ {Bs,Count} = float_opt(Bs0, Count0, Fs0),
+ {[{L,Blk0}|Bs],Count}
+ end.
+
+%% Split {float,convert} instructions into individual blocks.
+float_split_conv(Is0, Blk) ->
+ Br = #b_br{bool=#b_literal{val=true},succ=0,fail=0},
+ case splitwith(fun(#b_set{op=Op}) ->
+ Op =/= {float,convert}
+ end, Is0) of
+ {Is,[]} ->
+ [Blk#b_blk{is=Is}];
+ {[_|_]=Is1,[#b_set{op={float,convert}}=Conv|Is2]} ->
+ [#b_blk{is=Is1,last=Br},
+ #b_blk{is=[Conv],last=Br}|float_split_conv(Is2, Blk)];
+ {[],[#b_set{op={float,convert}}=Conv|Is1]} ->
+ [#b_blk{is=[Conv],last=Br}|float_split_conv(Is1, Blk)]
+ end.
+
+%% Number the blocks that were split.
+float_number([B|Bs0], FirstL, Count0) ->
+ {Bs,Count} = float_number(Bs0, Count0),
+ {[{FirstL,B}|Bs],Count}.
+
+float_number([B|Bs0], Count0) ->
+ {Bs,Count} = float_number(Bs0, Count0+1),
+ {[{Count0,B}|Bs],Count};
+float_number([], Count) ->
+ {[],Count}.
+
+%% Insert 'succeeded' instructions after each {float,convert}
+%% instruction.
+float_conv([{L,#b_blk{is=Is0}=Blk0}|Bs0], Fail, Count0) ->
+ case Is0 of
+ [#b_set{op={float,convert}}=Conv] ->
+ {Bool0,Count1} = new_reg('@ssa_bool', Count0),
+ Bool = #b_var{name=Bool0},
+ Succeeded = #b_set{op=succeeded,dst=Bool,
+ args=[Conv#b_set.dst]},
+ Is = [Conv,Succeeded],
+ [{NextL,_}|_] = Bs0,
+ Br = #b_br{bool=Bool,succ=NextL,fail=Fail},
+ Blk = Blk0#b_blk{is=Is,last=Br},
+ {Bs,Count} = float_conv(Bs0, Fail, Count1),
+ {[{L,Blk}|Bs],Count};
+ [_|_] ->
+ case Bs0 of
+ [{NextL,_}|_] ->
+ Br = #b_br{bool=#b_literal{val=true},
+ succ=NextL,fail=NextL},
+ Blk = Blk0#b_blk{last=Br},
+ {Bs,Count} = float_conv(Bs0, Fail, Count0),
+ {[{L,Blk}|Bs],Count};
+ [] ->
+ {[{L,Blk0}],Count0}
+ end
+ end.
+
+float_maybe_flush(Blk0, #fs{s=cleared,fail=Fail,bs=Blocks}=Fs0, Count0) ->
+ #b_blk{last=#b_br{bool=#b_var{},succ=Succ}=Br} = Blk0,
+ #b_blk{is=Is} = map_get(Succ, Blocks),
+ case Is of
+ [#b_set{anno=#{float_op:=_}}|_] ->
+ %% The next operation is also a floating point operation.
+ %% No flush needed.
+ {[],Blk0,Fs0,Count0};
+ _ ->
+ %% Flush needed.
+ {Bool0,Count1} = new_reg('@ssa_bool', Count0),
+ Bool = #b_var{name=Bool0},
+
+ %% Allocate block numbers.
+ CheckL = Count1, %For checkerror.
+ FlushL = Count1 + 1, %For flushing of float regs.
+ Count = Count1 + 2,
+ Blk = Blk0#b_blk{last=Br#b_br{succ=CheckL}},
+
+ %% Build the block with the checkerror instruction.
+ CheckIs = [#b_set{op={float,checkerror},dst=Bool}],
+ CheckBr = #b_br{bool=Bool,succ=FlushL,fail=Fail},
+ CheckBlk = #b_blk{is=CheckIs,last=CheckBr},
+
+ %% Build the block that flushes all registers.
+ FlushIs = float_flush_regs(Fs0),
+ FlushBr = #b_br{bool=#b_literal{val=true},succ=Succ,fail=Succ},
+ FlushBlk = #b_blk{is=FlushIs,last=FlushBr},
+
+ %% Update state and blocks.
+ Fs = Fs0#fs{s=undefined,regs=#{},fail=none},
+ FlushBs = [{CheckL,CheckBlk},{FlushL,FlushBlk}],
+ {FlushBs,Blk,Fs,Count}
+ end;
+float_maybe_flush(Blk, Fs, Count) ->
+ {[],Blk,Fs,Count}.
+
+float_opt_is([#b_set{op=succeeded,args=[Src]}=I0],
+ #fs{regs=Rs}=Fs, Count, Acc) ->
+ case Rs of
+ #{Src:=Fr} ->
+ I = I0#b_set{args=[Fr]},
+ {reverse(Acc, [I]),Fs,Count};
+ #{} ->
+ {reverse(Acc, [I0]),Fs,Count}
+ end;
+float_opt_is([#b_set{anno=Anno0}=I0|Is0], Fs0, Count0, Acc) ->
+ case Anno0 of
+ #{float_op:=FTypes} ->
+ Anno = maps:remove(float_op, Anno0),
+ I1 = I0#b_set{anno=Anno},
+ {Is,Fs,Count} = float_make_op(I1, FTypes, Fs0, Count0),
+ float_opt_is(Is0, Fs, Count, reverse(Is, Acc));
+ #{} ->
+ float_opt_is(Is0, Fs0#fs{regs=#{}}, Count0, [I0|Acc])
+ end;
+float_opt_is([], Fs, _Count, _Acc) ->
+ #fs{s=undefined} = Fs, %Assertion.
+ none.
+
+float_make_op(#b_set{op={bif,Op},dst=Dst,args=As0}=I0,
+ Ts, #fs{s=S,regs=Rs0}=Fs, Count0) ->
+ {As1,Rs1,Count1} = float_load(As0, Ts, Rs0, Count0, []),
+ {As,Is0} = unzip(As1),
+ {Fr,Count2} = new_reg('@fr', Count1),
+ FrDst = #b_var{name=Fr},
+ I = I0#b_set{op={float,Op},dst=FrDst,args=As},
+ Rs = Rs1#{Dst=>FrDst},
+ Is = append(Is0) ++ [I],
+ case S of
+ undefined ->
+ {Ignore,Count} = new_reg('@ssa_ignore', Count2),
+ C = #b_set{op={float,clearerror},dst=#b_var{name=Ignore}},
+ {[C|Is],Fs#fs{s=cleared,regs=Rs},Count};
+ cleared ->
+ {Is,Fs#fs{regs=Rs},Count2}
+ end.
+
+float_load([A|As], [T|Ts], Rs0, Count0, Acc) ->
+ {Load,Rs,Count} = float_reg_arg(A, T, Rs0, Count0),
+ float_load(As, Ts, Rs, Count, [Load|Acc]);
+float_load([], [], Rs, Count, Acc) ->
+ {reverse(Acc),Rs,Count}.
+
+float_reg_arg(A, T, Rs, Count0) ->
+ case Rs of
+ #{A:=Fr} ->
+ {{Fr,[]},Rs,Count0};
+ #{} ->
+ {Fr,Count} = new_float_copy_reg(Count0),
+ Dst = #b_var{name=Fr},
+ I = float_load_reg(T, A, Dst),
+ {{Dst,[I]},Rs#{A=>Dst},Count}
+ end.
+
+float_load_reg(convert, #b_var{}=Src, Dst) ->
+ #b_set{op={float,convert},dst=Dst,args=[Src]};
+float_load_reg(convert, #b_literal{val=Val}=Src, Dst) ->
+ try float(Val) of
+ F ->
+ #b_set{op={float,put},dst=Dst,args=[#b_literal{val=F}]}
+ catch
+ error:_ ->
+ %% Let the exception happen at runtime.
+ #b_set{op={float,convert},dst=Dst,args=[Src]}
+ end;
+float_load_reg(float, Src, Dst) ->
+ #b_set{op={float,put},dst=Dst,args=[Src]}.
+
+new_float_copy_reg(Count) ->
+ new_reg('@fr_copy', Count).
+
+new_reg(Base, Count) ->
+ Fr = {Base,Count},
+ {Fr,Count+1}.
+
+float_fail_label(#b_blk{last=Last}, Fs) ->
+ case Last of
+ #b_br{bool=#b_var{},fail=Fail} ->
+ Fs#fs{fail=Fail};
+ _ ->
+ Fs
+ end.
+
+float_flush_regs(#fs{regs=Rs}) ->
+ maps:fold(fun(_, #b_var{name={'@fr_copy',_}}, Acc) ->
+ Acc;
+ (Dst, Fr, Acc) ->
+ [#b_set{op={float,get},dst=Dst,args=[Fr]}|Acc]
+ end, [], Rs).
+
+%%%
+%%% Live optimization.
+%%%
+%%% Optimize instructions whose values are not used. They could be
+%%% removed if they have no side effects, or in a few cases replaced
+%%% with a cheaper instructions
+%%%
+
+ssa_opt_live({#st{ssa=Linear0}=St, FuncDb}) ->
+ RevLinear = reverse(Linear0),
+ Blocks0 = maps:from_list(RevLinear),
+ Blocks = live_opt(RevLinear, #{}, Blocks0),
+ Linear = beam_ssa:linearize(Blocks),
+ {St#st{ssa=Linear}, FuncDb}.
+
+live_opt([{L,Blk0}|Bs], LiveMap0, Blocks) ->
+ Blk1 = beam_ssa_share:block(Blk0, Blocks),
+ Successors = beam_ssa:successors(Blk1),
+ Live0 = live_opt_succ(Successors, L, LiveMap0, gb_sets:empty()),
+ {Blk,Live} = live_opt_blk(Blk1, Live0),
+ LiveMap = live_opt_phis(Blk#b_blk.is, L, Live, LiveMap0),
+ live_opt(Bs, LiveMap, Blocks#{L:=Blk});
+live_opt([], _, Acc) -> Acc.
+
+live_opt_succ([S|Ss], L, LiveMap, Live0) ->
+ Key = {S,L},
+ case LiveMap of
+ #{Key:=Live} ->
+ %% The successor has a phi node, and the value for
+ %% this block in the phi node is a variable.
+ live_opt_succ(Ss, L, LiveMap, gb_sets:union(Live, Live0));
+ #{S:=Live} ->
+ %% No phi node in the successor, or the value for
+ %% this block in the phi node is a literal.
+ live_opt_succ(Ss, L, LiveMap, gb_sets:union(Live, Live0));
+ #{} ->
+ %% A peek_message block which has not been processed yet.
+ live_opt_succ(Ss, L, LiveMap, Live0)
+ end;
+live_opt_succ([], _, _, Acc) -> Acc.
+
+live_opt_phis(Is, L, Live0, LiveMap0) ->
+ LiveMap = LiveMap0#{L=>Live0},
+ Phis = takewhile(fun(#b_set{op=Op}) -> Op =:= phi end, Is),
+ case Phis of
+ [] ->
+ LiveMap;
+ [_|_] ->
+ PhiArgs = append([Args || #b_set{args=Args} <- Phis]),
+ case [{P,V} || {#b_var{}=V,P} <- PhiArgs] of
+ [_|_]=PhiVars ->
+ PhiLive0 = rel2fam(PhiVars),
+ PhiLive = [{{L,P},gb_sets:union(gb_sets:from_list(Vs), Live0)} ||
+ {P,Vs} <- PhiLive0],
+ maps:merge(LiveMap, maps:from_list(PhiLive));
+ [] ->
+ %% There were only literals in the phi node(s).
+ LiveMap
+ end
+ end.
+
+live_opt_blk(#b_blk{is=Is0,last=Last}=Blk, Live0) ->
+ Live1 = gb_sets:union(Live0, gb_sets:from_ordset(beam_ssa:used(Last))),
+ {Is,Live} = live_opt_is(reverse(Is0), Live1, []),
+ {Blk#b_blk{is=Is},Live}.
+
+live_opt_is([#b_set{op=phi,dst=Dst}=I|Is], Live, Acc) ->
+ case gb_sets:is_member(Dst, Live) of
+ true ->
+ live_opt_is(Is, Live, [I|Acc]);
+ false ->
+ live_opt_is(Is, Live, Acc)
+ end;
+live_opt_is([#b_set{op=succeeded,dst=SuccDst=SuccDstVar,
+ args=[Dst]}=SuccI,
+ #b_set{dst=Dst}=I|Is], Live0, Acc) ->
+ case gb_sets:is_member(Dst, Live0) of
+ true ->
+ Live1 = gb_sets:add(Dst, Live0),
+ Live = gb_sets:delete_any(SuccDst, Live1),
+ live_opt_is([I|Is], Live, [SuccI|Acc]);
+ false ->
+ case live_opt_unused(I) of
+ {replace,NewI0} ->
+ NewI = NewI0#b_set{dst=SuccDstVar},
+ live_opt_is([NewI|Is], Live0, Acc);
+ keep ->
+ case gb_sets:is_member(SuccDst, Live0) of
+ true ->
+ Live1 = gb_sets:add(Dst, Live0),
+ Live = gb_sets:delete(SuccDst, Live1),
+ live_opt_is([I|Is], Live, [SuccI|Acc]);
+ false ->
+ live_opt_is([I|Is], Live0, Acc)
+ end
+ end
+ end;
+live_opt_is([#b_set{dst=Dst}=I|Is], Live0, Acc) ->
+ case gb_sets:is_member(Dst, Live0) of
+ true ->
+ Live1 = gb_sets:union(Live0, gb_sets:from_ordset(beam_ssa:used(I))),
+ Live = gb_sets:delete(Dst, Live1),
+ live_opt_is(Is, Live, [I|Acc]);
+ false ->
+ case beam_ssa:no_side_effect(I) of
+ true ->
+ live_opt_is(Is, Live0, Acc);
+ false ->
+ Live = gb_sets:union(Live0, gb_sets:from_ordset(beam_ssa:used(I))),
+ live_opt_is(Is, Live, [I|Acc])
+ end
+ end;
+live_opt_is([], Live, Acc) ->
+ {Acc,Live}.
+
+live_opt_unused(#b_set{op=get_map_element}=Set) ->
+ {replace,Set#b_set{op=has_map_field}};
+live_opt_unused(_) -> keep.
+
+%%%
+%%% Optimize binary matching.
+%%%
+%%% * If the value of segment is never extracted, rewrite
+%%% to a bs_skip instruction.
+%%%
+%%% * Coalesce adjacent bs_skip instructions and skip instructions
+%%% with bs_test_tail.
+%%%
+
+ssa_opt_bsm({#st{ssa=Linear}=St, FuncDb}) ->
+ Extracted0 = bsm_extracted(Linear),
+ Extracted = cerl_sets:from_list(Extracted0),
+ {St#st{ssa=bsm_skip(Linear, Extracted)}, 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);
+bsm_skip([], _) -> [].
+
+bsm_skip_is([I0|Is], Extracted) ->
+ case I0 of
+ #b_set{op=bs_match,
+ dst=Ctx,
+ args=[#b_literal{val=T}=Type,PrevCtx|Args0]}
+ when T =/= string, T =/= skip ->
+ I = case cerl_sets:is_element(Ctx, Extracted) of
+ true ->
+ I0;
+ false ->
+ %% The value is never extracted.
+ Args = [#b_literal{val=skip},PrevCtx,Type|Args0],
+ I0#b_set{args=Args}
+ end,
+ [I|Is];
+ #b_set{} ->
+ [I0|bsm_skip_is(Is, Extracted)]
+ end;
+bsm_skip_is([], _) -> [].
+
+bsm_extracted([{_,#b_blk{is=Is}}|Bs]) ->
+ case Is of
+ [#b_set{op=bs_extract,args=[Ctx]}|_] ->
+ [Ctx|bsm_extracted(Bs)];
+ _ ->
+ bsm_extracted(Bs)
+ end;
+bsm_extracted([]) -> [].
+
+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
+ not_possible ->
+ [{L,Blk0}|Bs0];
+ {Is,Last,Bs} ->
+ Blk = Blk0#b_blk{is=[Extract|Is],last=Last},
+ [{L,Blk}|Bs]
+ end;
+coalesce_skips({L,#b_blk{is=Is0,last=Last0}=Blk0}, Bs0) ->
+ case coalesce_skips_is(Is0, Last0, Bs0) of
+ not_possible ->
+ [{L,Blk0}|Bs0];
+ {Is,Last,Bs} ->
+ Blk = Blk0#b_blk{is=Is,last=Last},
+ [{L,Blk}|Bs]
+ end.
+
+coalesce_skips_is([#b_set{op=bs_match,
+ args=[#b_literal{val=skip},
+ Ctx0,Type,Flags,
+ #b_literal{val=Size0},
+ #b_literal{val=Unit0}]}=Skip0,
+ #b_set{op=succeeded}],
+ #b_br{succ=L2,fail=Fail}=Br0,
+ Bs0) when is_integer(Size0) ->
+ case Bs0 of
+ [{L2,#b_blk{is=[#b_set{op=bs_match,
+ dst=SkipDst,
+ args=[#b_literal{val=skip},_,_,_,
+ #b_literal{val=Size1},
+ #b_literal{val=Unit1}]},
+ #b_set{op=succeeded}=Succeeded],
+ last=#b_br{fail=Fail}=Br}}|Bs] when is_integer(Size1) ->
+ SkipBits = Size0 * Unit0 + Size1 * Unit1,
+ Skip = Skip0#b_set{dst=SkipDst,
+ args=[#b_literal{val=skip},Ctx0,
+ Type,Flags,
+ #b_literal{val=SkipBits},
+ #b_literal{val=1}]},
+ Is = [Skip,Succeeded],
+ {Is,Br,Bs};
+ [{L2,#b_blk{is=[#b_set{op=bs_test_tail,
+ args=[_Ctx,#b_literal{val=TailSkip}]}],
+ last=#b_br{succ=NextSucc,fail=Fail}}}|Bs] ->
+ SkipBits = Size0 * Unit0,
+ TestTail = Skip0#b_set{op=bs_test_tail,
+ args=[Ctx0,#b_literal{val=SkipBits+TailSkip}]},
+ Br = Br0#b_br{bool=TestTail#b_set.dst,succ=NextSucc},
+ Is = [TestTail],
+ {Is,Br,Bs};
+ _ ->
+ not_possible
+ end;
+coalesce_skips_is(_, _, _) ->
+ not_possible.
+
+%%%
+%%% Short-cutting binary matching instructions.
+%%%
+
+ssa_opt_bsm_shortcut({#st{ssa=Linear}=St, FuncDb}) ->
+ Positions = bsm_positions(Linear, #{}),
+ case map_size(Positions) of
+ 0 ->
+ %% No binary matching instructions.
+ {St, FuncDb};
+ _ ->
+ {St#st{ssa=bsm_shortcut(Linear, Positions)}, FuncDb}
+ end.
+
+bsm_positions([{L,#b_blk{is=Is,last=Last}}|Bs], PosMap0) ->
+ PosMap = bsm_positions_is(Is, PosMap0),
+ case {Is,Last} of
+ {[#b_set{op=bs_test_tail,dst=Bool,args=[Ctx,#b_literal{val=Bits0}]}],
+ #b_br{bool=Bool,fail=Fail}} ->
+ Bits = Bits0 + map_get(Ctx, PosMap0),
+ bsm_positions(Bs, PosMap#{L=>{Bits,Fail}});
+ {_,_} ->
+ bsm_positions(Bs, PosMap)
+ end;
+bsm_positions([], PosMap) -> PosMap.
+
+bsm_positions_is([#b_set{op=bs_start_match,dst=New}|Is], PosMap0) ->
+ PosMap = PosMap0#{New=>0},
+ bsm_positions_is(Is, PosMap);
+bsm_positions_is([#b_set{op=bs_match,dst=New,args=Args}|Is], PosMap0) ->
+ [_,Old|_] = Args,
+ #{Old:=Bits0} = PosMap0,
+ Bits = bsm_update_bits(Args, Bits0),
+ PosMap = PosMap0#{New=>Bits},
+ bsm_positions_is(Is, PosMap);
+bsm_positions_is([_|Is], PosMap) ->
+ bsm_positions_is(Is, PosMap);
+bsm_positions_is([], PosMap) -> PosMap.
+
+bsm_update_bits([#b_literal{val=string},_,#b_literal{val=String}], Bits) ->
+ Bits + bit_size(String);
+bsm_update_bits([#b_literal{val=utf8}|_], Bits) ->
+ Bits + 8;
+bsm_update_bits([#b_literal{val=utf16}|_], Bits) ->
+ Bits + 16;
+bsm_update_bits([#b_literal{val=utf32}|_], Bits) ->
+ Bits + 32;
+bsm_update_bits([_,_,_,#b_literal{val=Sz},#b_literal{val=U}], Bits)
+ when is_integer(Sz) ->
+ Bits + Sz*U;
+bsm_update_bits(_, Bits) -> Bits.
+
+bsm_shortcut([{L,#b_blk{is=Is,last=Last0}=Blk}|Bs], PosMap) ->
+ case {Is,Last0} of
+ {[#b_set{op=bs_match,dst=New,args=[_,Old|_]},
+ #b_set{op=succeeded,dst=Bool,args=[New]}],
+ #b_br{bool=Bool,fail=Fail}} ->
+ case PosMap 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}|bsm_shortcut(Bs, PosMap)]
+ end;
+ {_,_} ->
+ [{L,Blk}|bsm_shortcut(Bs, PosMap)]
+ end;
+bsm_shortcut([], _PosMap) -> [].
+
+%%%
+%%% Eliminate redundant bs_test_unit2 instructions.
+%%%
+
+ssa_opt_bsm_units({#st{ssa=Linear}=St, FuncDb}) ->
+ {St#st{ssa=bsm_units(Linear, #{})}, FuncDb}.
+
+bsm_units([{L,#b_blk{last=#b_br{succ=Succ,fail=Fail}}=Block0} | Bs], UnitMaps0) ->
+ UnitsIn = maps:get(L, UnitMaps0, #{}),
+ {Block, UnitsOut} = bsm_units_skip(Block0, UnitsIn),
+ UnitMaps1 = bsm_units_join(Succ, UnitsOut, UnitMaps0),
+ UnitMaps = bsm_units_join(Fail, UnitsIn, UnitMaps1),
+ [{L, Block} | bsm_units(Bs, UnitMaps)];
+bsm_units([{L,#b_blk{last=#b_switch{fail=Fail,list=Switch}}=Block} | Bs], UnitMaps0) ->
+ UnitsIn = maps:get(L, UnitMaps0, #{}),
+ Labels = [Fail | [Lbl || {_Arg, Lbl} <- Switch]],
+ UnitMaps = foldl(fun(Lbl, UnitMaps) ->
+ bsm_units_join(Lbl, UnitsIn, UnitMaps)
+ end, UnitMaps0, Labels),
+ [{L, Block} | bsm_units(Bs, UnitMaps)];
+bsm_units([{L, Block} | Bs], UnitMaps) ->
+ [{L, Block} | bsm_units(Bs, UnitMaps)];
+bsm_units([], _UnitMaps) ->
+ [].
+
+bsm_units_skip(Block, Units) ->
+ bsm_units_skip_1(Block#b_blk.is, Block, Units).
+
+bsm_units_skip_1([#b_set{op=bs_start_match,dst=New}|_], Block, Units) ->
+ %% We bail early since there can't be more than one match per block.
+ {Block, Units#{ New => 1 }};
+bsm_units_skip_1([#b_set{op=bs_match,
+ dst=New,
+ args=[#b_literal{val=skip},
+ Ctx,
+ #b_literal{val=binary},
+ _Flags,
+ #b_literal{val=all},
+ #b_literal{val=OpUnit}]}=Skip | Test],
+ Block0, Units) ->
+ [#b_set{op=succeeded,dst=Bool,args=[New]}] = Test, %Assertion.
+ #b_br{bool=Bool} = Last0 = Block0#b_blk.last, %Assertion.
+ CtxUnit = map_get(Ctx, Units),
+ if
+ CtxUnit rem OpUnit =:= 0 ->
+ Is = takewhile(fun(I) -> I =/= Skip end, Block0#b_blk.is),
+ Last = Last0#b_br{bool=#b_literal{val=true}},
+ Block = Block0#b_blk{is=Is,last=Last},
+ {Block, Units#{ New => CtxUnit }};
+ CtxUnit rem OpUnit =/= 0 ->
+ {Block0, Units#{ New => OpUnit, Ctx => OpUnit }}
+ end;
+bsm_units_skip_1([#b_set{op=bs_match,dst=New,args=Args}|_], Block, Units) ->
+ [_,Ctx|_] = Args,
+ CtxUnit = map_get(Ctx, Units),
+ OpUnit = bsm_op_unit(Args),
+ {Block, Units#{ New => gcd(OpUnit, CtxUnit) }};
+bsm_units_skip_1([_I | Is], Block, Units) ->
+ bsm_units_skip_1(Is, Block, Units);
+bsm_units_skip_1([], Block, Units) ->
+ {Block, Units}.
+
+bsm_op_unit([_,_,_,Size,#b_literal{val=U}]) ->
+ case Size of
+ #b_literal{val=Sz} when is_integer(Sz) -> Sz*U;
+ _ -> U
+ end;
+bsm_op_unit([#b_literal{val=string},_,#b_literal{val=String}]) ->
+ bit_size(String);
+bsm_op_unit([#b_literal{val=utf8}|_]) ->
+ 8;
+bsm_op_unit([#b_literal{val=utf16}|_]) ->
+ 16;
+bsm_op_unit([#b_literal{val=utf32}|_]) ->
+ 32;
+bsm_op_unit(_) ->
+ 1.
+
+%% Several paths can lead to the same match instruction and the inferred units
+%% may differ between them, so we can only keep the information that is common
+%% to all paths.
+bsm_units_join(Lbl, MapA, UnitMaps0) when is_map_key(Lbl, UnitMaps0) ->
+ MapB = map_get(Lbl, UnitMaps0),
+ Merged = if
+ map_size(MapB) =< map_size(MapA) ->
+ bsm_units_join_1(maps:keys(MapB), MapA, MapB);
+ map_size(MapB) > map_size(MapA) ->
+ bsm_units_join_1(maps:keys(MapA), MapB, MapA)
+ end,
+ UnitMaps0#{Lbl := Merged};
+bsm_units_join(Lbl, MapA, UnitMaps0) when MapA =/= #{} ->
+ UnitMaps0#{Lbl => MapA};
+bsm_units_join(_Lbl, _MapA, UnitMaps0) ->
+ UnitMaps0.
+
+bsm_units_join_1([Key | Keys], Left, Right) when is_map_key(Key, Left) ->
+ UnitA = map_get(Key, Left),
+ UnitB = map_get(Key, Right),
+ bsm_units_join_1(Keys, Left, Right#{Key := gcd(UnitA, UnitB)});
+bsm_units_join_1([Key | Keys], Left, Right) ->
+ bsm_units_join_1(Keys, Left, maps:remove(Key, Right));
+bsm_units_join_1([], _MapA, Right) ->
+ Right.
+
+%%%
+%%% Optimize binary construction.
+%%%
+%%% If an integer segment or a float segment has a literal size and
+%%% a literal value, convert to a binary segment. Coalesce adjacent
+%%% literal binary segments. Literal binary segments will be converted
+%%% to bs_put_string instructions in later pass.
+%%%
+
+ssa_opt_bs_puts({#st{ssa=Linear0,cnt=Count0}=St, FuncDb}) ->
+ {Linear,Count} = opt_bs_puts(Linear0, Count0, []),
+ {St#st{ssa=Linear,cnt=Count}, FuncDb}.
+
+opt_bs_puts([{L,#b_blk{is=Is}=Blk0}|Bs], Count0, Acc0) ->
+ case Is of
+ [#b_set{op=bs_put}=I0] ->
+ case opt_bs_put(L, I0, Blk0, Count0, Acc0) of
+ not_possible ->
+ opt_bs_puts(Bs, Count0, [{L,Blk0}|Acc0]);
+ {Count,Acc1} ->
+ Acc = opt_bs_puts_merge(Acc1),
+ opt_bs_puts(Bs, Count, Acc)
+ end;
+ _ ->
+ opt_bs_puts(Bs, Count0, [{L,Blk0}|Acc0])
+ end;
+opt_bs_puts([], Count, Acc) ->
+ {reverse(Acc),Count}.
+
+opt_bs_puts_merge([{L1,#b_blk{is=Is}=Blk0},{L2,#b_blk{is=AccIs}}=BAcc|Acc]) ->
+ case {AccIs,Is} of
+ {[#b_set{op=bs_put,
+ args=[#b_literal{val=binary},
+ #b_literal{},
+ #b_literal{val=Bin0},
+ #b_literal{val=all},
+ #b_literal{val=1}]}],
+ [#b_set{op=bs_put,
+ args=[#b_literal{val=binary},
+ #b_literal{},
+ #b_literal{val=Bin1},
+ #b_literal{val=all},
+ #b_literal{val=1}]}=I0]} ->
+ %% Coalesce the two segments to one.
+ Bin = <<Bin0/bitstring,Bin1/bitstring>>,
+ I = I0#b_set{args=bs_put_args(binary, Bin, all)},
+ Blk = Blk0#b_blk{is=[I]},
+ [{L2,Blk}|Acc];
+ {_,_} ->
+ [{L1,Blk0},BAcc|Acc]
+ end.
+
+opt_bs_put(L, I0, #b_blk{last=Br0}=Blk0, Count0, Acc) ->
+ case opt_bs_put(I0) of
+ [Bin] when is_bitstring(Bin) ->
+ Args = bs_put_args(binary, Bin, all),
+ I = I0#b_set{args=Args},
+ Blk = Blk0#b_blk{is=[I]},
+ {Count0,[{L,Blk}|Acc]};
+ [{int,Int,Size},Bin] when is_bitstring(Bin) ->
+ %% Construct a bs_put_integer instruction following
+ %% by a bs_put_binary instruction.
+ IntArgs = bs_put_args(integer, Int, Size),
+ BinArgs = bs_put_args(binary, Bin, all),
+ {BinL,BinVarNum} = {Count0,Count0+1},
+ Count = Count0 + 2,
+ BinVar = #b_var{name={'@ssa_bool',BinVarNum}},
+ BinI = I0#b_set{dst=BinVar,args=BinArgs},
+ BinBlk = Blk0#b_blk{is=[BinI],last=Br0#b_br{bool=BinVar}},
+ IntI = I0#b_set{args=IntArgs},
+ IntBlk = Blk0#b_blk{is=[IntI],last=Br0#b_br{succ=BinL}},
+ {Count,[{BinL,BinBlk},{L,IntBlk}|Acc]};
+ not_possible ->
+ not_possible
+ end.
+
+opt_bs_put(#b_set{args=[#b_literal{val=binary},_,#b_literal{val=Val},
+ #b_literal{val=all},#b_literal{val=Unit}]})
+ when is_bitstring(Val) ->
+ if
+ bit_size(Val) rem Unit =:= 0 ->
+ [Val];
+ true ->
+ not_possible
+ end;
+opt_bs_put(#b_set{args=[#b_literal{val=Type},#b_literal{val=Flags},
+ #b_literal{val=Val},#b_literal{val=Size},
+ #b_literal{val=Unit}]}=I0) when is_integer(Size) ->
+ EffectiveSize = Size * Unit,
+ if
+ EffectiveSize > 0 ->
+ case {Type,opt_bs_put_endian(Flags)} of
+ {integer,big} when is_integer(Val) ->
+ if
+ EffectiveSize < 64 ->
+ [<<Val:EffectiveSize>>];
+ true ->
+ opt_bs_put_split_int(Val, EffectiveSize)
+ end;
+ {integer,little} when is_integer(Val), EffectiveSize < 128 ->
+ %% To avoid an explosion in code size, we only try
+ %% to optimize relatively small fields.
+ <<Int:EffectiveSize>> = <<Val:EffectiveSize/little>>,
+ Args = bs_put_args(Type, Int, EffectiveSize),
+ I = I0#b_set{args=Args},
+ opt_bs_put(I);
+ {binary,_} when is_bitstring(Val) ->
+ <<Bitstring:EffectiveSize/bits,_/bits>> = Val,
+ [Bitstring];
+ {float,Endian} ->
+ try
+ [opt_bs_put_float(Val, EffectiveSize, Endian)]
+ catch error:_ ->
+ not_possible
+ end;
+ {_,_} ->
+ not_possible
+ end;
+ true ->
+ not_possible
+ end;
+opt_bs_put(#b_set{}) -> not_possible.
+
+opt_bs_put_float(N, Sz, Endian) ->
+ case Endian of
+ big -> <<N:Sz/big-float-unit:1>>;
+ little -> <<N:Sz/little-float-unit:1>>
+ end.
+
+bs_put_args(Type, Val, Size) ->
+ [#b_literal{val=Type},
+ #b_literal{val=[unsigned,big]},
+ #b_literal{val=Val},
+ #b_literal{val=Size},
+ #b_literal{val=1}].
+
+opt_bs_put_endian([big=E|_]) -> E;
+opt_bs_put_endian([little=E|_]) -> E;
+opt_bs_put_endian([native=E|_]) -> E;
+opt_bs_put_endian([_|Fs]) -> opt_bs_put_endian(Fs).
+
+opt_bs_put_split_int(Int, Size) ->
+ Pos = opt_bs_put_split_int_1(Int, 0, Size - 1),
+ UpperSize = Size - Pos,
+ if
+ Pos =:= 0 ->
+ %% Value is 0 or -1 -- keep the original instruction.
+ not_possible;
+ UpperSize < 64 ->
+ %% No or few leading zeroes or ones.
+ [<<Int:Size>>];
+ true ->
+ %% There are 64 or more leading ones or zeroes in
+ %% the resulting binary. Split into two separate
+ %% segments to avoid an explosion in code size.
+ [{int,Int bsr Pos,UpperSize},<<Int:Pos>>]
+ end.
+
+opt_bs_put_split_int_1(_Int, L, R) when L > R ->
+ 8 * ((L + 7) div 8);
+opt_bs_put_split_int_1(Int, L, R) ->
+ Mid = (L + R) div 2,
+ case Int bsr Mid of
+ Upper when Upper =:= 0; Upper =:= -1 ->
+ opt_bs_put_split_int_1(Int, L, Mid - 1);
+ _ ->
+ opt_bs_put_split_int_1(Int, Mid + 1, R)
+ end.
+
+%%%
+%%% Optimize expressions such as "tuple_size(Var) =:= 2".
+%%%
+%%% Consider this code:
+%%%
+%%% 0:
+%%% .
+%%% .
+%%% .
+%%% Size = bif:tuple_size Var
+%%% BoolVar1 = succeeded Size
+%%% br BoolVar1, label 4, label 3
+%%%
+%%% 4:
+%%% BoolVar2 = bif:'=:=' Size, literal 2
+%%% br BoolVar2, label 6, label 3
+%%%
+%%% 6: ... %% OK
+%%%
+%%% 3: ... %% Not a tuple of size 2
+%%%
+%%% The BEAM code will look this:
+%%%
+%%% {bif,tuple_size,{f,3},[{x,0}],{x,0}}.
+%%% {test,is_eq_exact,{f,3},[{x,0},{integer,2}]}.
+%%%
+%%% Better BEAM code will be produced if we transform the
+%%% code like this:
+%%%
+%%% 0:
+%%% .
+%%% .
+%%% .
+%%% br label 10
+%%%
+%%% 10:
+%%% NewBoolVar = bif:is_tuple Var
+%%% br NewBoolVar, label 11, label 3
+%%%
+%%% 11:
+%%% Size = bif:tuple_size Var
+%%% br label 4
+%%%
+%%% 4:
+%%% BoolVar2 = bif:'=:=' Size, literal 2
+%%% br BoolVar2, label 6, label 3
+%%%
+%%% (The key part of the transformation is the removal of
+%%% the 'succeeded' instruction to signal to the code generator
+%%% that the call to tuple_size/1 can't fail.)
+%%%
+%%% The BEAM code will look like:
+%%%
+%%% {test,is_tuple,{f,3},[{x,0}]}.
+%%% {test_arity,{f,3},[{x,0},2]}.
+%%%
+%%% Those two instructions will be combined into a single
+%%% is_tuple_of_arity instruction by the loader.
+%%%
+
+ssa_opt_tuple_size({#st{ssa=Linear0,cnt=Count0}=St, FuncDb}) ->
+ {Linear,Count} = opt_tup_size(Linear0, Count0, []),
+ {St#st{ssa=Linear,cnt=Count}, FuncDb}.
+
+opt_tup_size([{L,#b_blk{is=Is,last=Last}=Blk}|Bs], Count0, Acc0) ->
+ case {Is,Last} of
+ {[#b_set{op={bif,'=:='},dst=Bool,args=[#b_var{}=Tup,#b_literal{val=Arity}]}],
+ #b_br{bool=Bool}} when is_integer(Arity), Arity >= 0 ->
+ {Acc,Count} = opt_tup_size_1(Tup, L, Count0, Acc0),
+ opt_tup_size(Bs, Count, [{L,Blk}|Acc]);
+ {_,_} ->
+ opt_tup_size(Bs, Count0, [{L,Blk}|Acc0])
+ end;
+opt_tup_size([], Count, Acc) ->
+ {reverse(Acc),Count}.
+
+opt_tup_size_1(Size, EqL, Count0, [{L,Blk0}|Acc]) ->
+ case Blk0 of
+ #b_blk{is=Is0,last=#b_br{bool=Bool,succ=EqL,fail=Fail}} ->
+ case opt_tup_size_is(Is0, Bool, Size, []) of
+ none ->
+ {[{L,Blk0}|Acc],Count0};
+ {PreIs,TupleSizeIs,Tuple} ->
+ opt_tup_size_2(PreIs, TupleSizeIs, L, EqL,
+ Tuple, Fail, Count0, Acc)
+ end;
+ #b_blk{} ->
+ {[{L,Blk0}|Acc],Count0}
+ end;
+opt_tup_size_1(_, _, Count, Acc) ->
+ {Acc,Count}.
+
+opt_tup_size_2(PreIs, TupleSizeIs, PreL, EqL, Tuple, Fail, Count0, Acc) ->
+ IsTupleL = Count0,
+ TupleSizeL = Count0 + 1,
+ Bool = #b_var{name={'@ssa_bool',Count0+2}},
+ Count = Count0 + 3,
+
+ True = #b_literal{val=true},
+ PreBr = #b_br{bool=True,succ=IsTupleL,fail=IsTupleL},
+ PreBlk = #b_blk{is=PreIs,last=PreBr},
+
+ IsTupleIs = [#b_set{op={bif,is_tuple},dst=Bool,args=[Tuple]}],
+ IsTupleBr = #b_br{bool=Bool,succ=TupleSizeL,fail=Fail},
+ IsTupleBlk = #b_blk{is=IsTupleIs,last=IsTupleBr},
+
+ TupleSizeBr = #b_br{bool=True,succ=EqL,fail=EqL},
+ TupleSizeBlk = #b_blk{is=TupleSizeIs,last=TupleSizeBr},
+ {[{TupleSizeL,TupleSizeBlk},
+ {IsTupleL,IsTupleBlk},
+ {PreL,PreBlk}|Acc],Count}.
+
+opt_tup_size_is([#b_set{op={bif,tuple_size},dst=Size,args=[Tuple]}=I,
+ #b_set{op=succeeded,dst=Bool,args=[Size]}],
+ Bool, Size, Acc) ->
+ {reverse(Acc),[I],Tuple};
+opt_tup_size_is([I|Is], Bool, Size, Acc) ->
+ opt_tup_size_is(Is, Bool, Size, [I|Acc]);
+opt_tup_size_is([], _, _, _Acc) -> none.
+
+%%%
+%%% Optimize #b_switch{} instructions.
+%%%
+%%% If the argument for a #b_switch{} comes from a phi node with all
+%%% literals, any values in the switch list which are not in the phi
+%%% node can be removed.
+%%%
+%%% If the values in the phi node and switch list are the same,
+%%% the failure label can't be reached and be eliminated.
+%%%
+%%% A #b_switch{} with only one value can be rewritten to
+%%% a #b_br{}. A switch that only verifies that the argument
+%%% is 'true' or 'false' can be rewritten to a is_boolean test.
+%%%
+
+ssa_opt_sw({#st{ssa=Linear0,cnt=Count0}=St, FuncDb}) ->
+ {Linear,Count} = opt_sw(Linear0, Count0, []),
+ {St#st{ssa=Linear,cnt=Count}, FuncDb}.
+
+opt_sw([{L,#b_blk{is=Is,last=#b_switch{}=Sw0}=Blk0}|Bs], Count0, Acc) ->
+ %% Ensure that no label in the switch list is the same
+ %% as the failure label.
+ #b_switch{fail=Fail,list=List0} = Sw0,
+ List = [{Val,Lbl} || {Val,Lbl} <- List0, Lbl =/= Fail],
+ Sw1 = beam_ssa:normalize(Sw0#b_switch{list=List}),
+ case Sw1 of
+ #b_switch{arg=Arg,fail=Fail,list=[{Lit,Lbl}]} ->
+ %% Rewrite a single value switch to a br.
+ Bool = #b_var{name={'@ssa_bool',Count0}},
+ Count = Count0 + 1,
+ IsEq = #b_set{op={bif,'=:='},dst=Bool,args=[Arg,Lit]},
+ Br = #b_br{bool=Bool,succ=Lbl,fail=Fail},
+ Blk = Blk0#b_blk{is=Is++[IsEq],last=Br},
+ opt_sw(Bs, Count, [{L,Blk}|Acc]);
+ #b_switch{arg=Arg,fail=Fail,
+ list=[{#b_literal{val=B1},Lbl},{#b_literal{val=B2},Lbl}]}
+ when B1 =:= not B2 ->
+ %% Replace with is_boolean test.
+ Bool = #b_var{name={'@ssa_bool',Count0}},
+ Count = Count0 + 1,
+ IsBool = #b_set{op={bif,is_boolean},dst=Bool,args=[Arg]},
+ Br = #b_br{bool=Bool,succ=Lbl,fail=Fail},
+ Blk = Blk0#b_blk{is=Is++[IsBool],last=Br},
+ opt_sw(Bs, Count, [{L,Blk}|Acc]);
+ Sw0 ->
+ opt_sw(Bs, Count0, [{L,Blk0}|Acc]);
+ Sw ->
+ Blk = Blk0#b_blk{last=Sw},
+ opt_sw(Bs, Count0, [{L,Blk}|Acc])
+ end;
+opt_sw([{L,#b_blk{}=Blk}|Bs], Count, Acc) ->
+ opt_sw(Bs, Count, [{L,Blk}|Acc]);
+opt_sw([], Count, Acc) ->
+ {reverse(Acc),Count}.
+
+%%%
+%%% Merge blocks.
+%%%
+
+ssa_opt_merge_blocks({#st{ssa=Blocks}=St, FuncDb}) ->
+ Preds = beam_ssa:predecessors(Blocks),
+ Merged = merge_blocks_1(beam_ssa:rpo(Blocks), Preds, Blocks),
+ {St#st{ssa=Merged}, FuncDb}.
+
+merge_blocks_1([L|Ls], Preds0, Blocks0) ->
+ case Preds0 of
+ #{L:=[P]} ->
+ #{P:=Blk0,L:=Blk1} = Blocks0,
+ case is_merge_allowed(L, Blk0, Blk1) of
+ true ->
+ #b_blk{is=Is0} = Blk0,
+ #b_blk{is=Is1} = Blk1,
+ verify_merge_is(Is1),
+ Is = Is0 ++ Is1,
+ Blk = Blk1#b_blk{is=Is},
+ Blocks1 = maps:remove(L, Blocks0),
+ Blocks2 = Blocks1#{P:=Blk},
+ Successors = beam_ssa:successors(Blk),
+ Blocks = beam_ssa:update_phi_labels(Successors, L, P, Blocks2),
+ Preds = merge_update_preds(Successors, L, P, Preds0),
+ merge_blocks_1(Ls, Preds, Blocks);
+ false ->
+ merge_blocks_1(Ls, Preds0, Blocks0)
+ end;
+ #{} ->
+ merge_blocks_1(Ls, Preds0, Blocks0)
+ end;
+merge_blocks_1([], _Preds, Blocks) -> Blocks.
+
+merge_update_preds([L|Ls], From, To, Preds0) ->
+ Ps = [rename_label(P, From, To) || P <- map_get(L, Preds0)],
+ Preds = Preds0#{L:=Ps},
+ merge_update_preds(Ls, From, To, Preds);
+merge_update_preds([], _, _, Preds) -> Preds.
+
+rename_label(From, From, To) -> To;
+rename_label(Lbl, _, _) -> Lbl.
+
+verify_merge_is([#b_set{op=Op}|_]) ->
+ %% The merged block has only one predecessor, so it should not have any phi
+ %% nodes.
+ true = Op =/= phi; %Assertion.
+verify_merge_is(_) ->
+ ok.
+
+is_merge_allowed(_, #b_blk{}, #b_blk{is=[#b_set{op=peek_message}|_]}) ->
+ false;
+is_merge_allowed(L, #b_blk{last=#b_br{}}=Blk, #b_blk{}) ->
+ %% The predecessor block must have exactly one successor (L) for
+ %% the merge to be safe.
+ case beam_ssa:successors(Blk) of
+ [L] -> true;
+ [_|_] -> false
+ end;
+is_merge_allowed(_, #b_blk{last=#b_switch{}}, #b_blk{}) ->
+ false.
+
+%%%
+%%% When a tuple is matched, the pattern matching compiler generates a
+%%% get_tuple_element instruction for every tuple element that will
+%%% ever be used in the rest of the function. That often forces the
+%%% extracted tuple elements to be stored in Y registers until it's
+%%% time to use them. It could also mean that there could be execution
+%%% paths that will never use the extracted elements.
+%%%
+%%% This optimization will sink get_tuple_element instructions, that
+%%% is, move them forward in the execution stream to the last possible
+%%% block there they will still dominate all uses. That may reduce the
+%%% size of stack frames, reduce register shuffling, and avoid
+%%% extracting tuple elements on execution paths that never use the
+%%% extracted values.
+%%%
+
+ssa_opt_sink({#st{ssa=Blocks0}=St, FuncDb}) ->
+ Linear = beam_ssa:linearize(Blocks0),
+
+ %% Create a map with all variables that define get_tuple_element
+ %% instructions. The variable name map to the block it is defined in.
+ case def_blocks(Linear) of
+ [] ->
+ %% No get_tuple_element instructions, so there is nothing to do.
+ {St, FuncDb};
+ [_|_]=Defs0 ->
+ Defs = maps:from_list(Defs0),
+ {do_ssa_opt_sink(Linear, Defs, St), FuncDb}
+ end.
+
+do_ssa_opt_sink(Linear, Defs, #st{ssa=Blocks0}=St) ->
+ %% Now find all the blocks that use variables defined by get_tuple_element
+ %% instructions.
+ Used = used_blocks(Linear, Defs, []),
+
+ %% Calculate dominators.
+ {Dom,Numbering} = beam_ssa:dominators(Blocks0),
+
+ %% It is not safe to move get_tuple_element instructions to blocks
+ %% that begin with certain instructions. It is also unsafe to move
+ %% the instructions into any part of a receive. To avoid such
+ %% unsafe moves, pretend that the unsuitable blocks are not
+ %% dominators.
+ Unsuitable = unsuitable(Linear, Blocks0),
+
+ %% Calculate new positions for get_tuple_element instructions. The new
+ %% position is a block that dominates all uses of the variable.
+ DefLoc = new_def_locations(Used, Defs, Dom, Numbering, Unsuitable),
+
+ %% Now move all suitable get_tuple_element instructions to their
+ %% new blocks.
+ Blocks = foldl(fun({V,To}, A) ->
+ From = map_get(V, Defs),
+ move_defs(V, From, To, A)
+ end, Blocks0, DefLoc),
+ St#st{ssa=Blocks}.
+
+def_blocks([{L,#b_blk{is=Is}}|Bs]) ->
+ def_blocks_is(Is, L, def_blocks(Bs));
+def_blocks([]) -> [].
+
+def_blocks_is([#b_set{op=get_tuple_element,dst=Dst}|Is], L, Acc) ->
+ def_blocks_is(Is, L, [{Dst,L}|Acc]);
+def_blocks_is([_|Is], L, Acc) ->
+ def_blocks_is(Is, L, Acc);
+def_blocks_is([], _, Acc) -> Acc.
+
+used_blocks([{L,Blk}|Bs], Def, Acc0) ->
+ Used = beam_ssa:used(Blk),
+ Acc = [{V,L} || V <- Used, maps:is_key(V, Def)] ++ Acc0,
+ used_blocks(Bs, Def, Acc);
+used_blocks([], _Def, Acc) ->
+ rel2fam(Acc).
+
+%% unsuitable(Linear, Blocks) -> Unsuitable.
+%% Return an ordset of block labels for the blocks that are not
+%% suitable for sinking of get_tuple_element instructions.
+
+unsuitable(Linear, Blocks) ->
+ Predecessors = beam_ssa:predecessors(Blocks),
+ Unsuitable0 = unsuitable_1(Linear),
+ Unsuitable1 = unsuitable_recv(Linear, Blocks, Predecessors),
+ gb_sets:from_list(Unsuitable0 ++ Unsuitable1).
+
+unsuitable_1([{L,#b_blk{is=[#b_set{op=Op}|_]}}|Bs]) ->
+ Unsuitable = case Op of
+ bs_extract -> true;
+ bs_put -> true;
+ {float,_} -> true;
+ landingpad -> true;
+ peek_message -> true;
+ wait_timeout -> true;
+ _ -> false
+ end,
+ case Unsuitable of
+ true ->
+ [L|unsuitable_1(Bs)];
+ false ->
+ unsuitable_1(Bs)
+ end;
+unsuitable_1([{_,#b_blk{}}|Bs]) ->
+ unsuitable_1(Bs);
+unsuitable_1([]) -> [].
+
+unsuitable_recv([{L,#b_blk{is=[#b_set{op=Op}|_]}}|Bs], Blocks, Predecessors) ->
+ Ls = case Op of
+ remove_message ->
+ unsuitable_loop(L, Blocks, Predecessors);
+ recv_next ->
+ unsuitable_loop(L, Blocks, Predecessors);
+ _ ->
+ []
+ end,
+ Ls ++ unsuitable_recv(Bs, Blocks, Predecessors);
+unsuitable_recv([_|Bs], Blocks, Predecessors) ->
+ unsuitable_recv(Bs, Blocks, Predecessors);
+unsuitable_recv([], _, _) -> [].
+
+unsuitable_loop(L, Blocks, Predecessors) ->
+ unsuitable_loop(L, Blocks, Predecessors, []).
+
+unsuitable_loop(L, Blocks, Predecessors, Acc) ->
+ Ps = map_get(L, Predecessors),
+ unsuitable_loop_1(Ps, Blocks, Predecessors, Acc).
+
+unsuitable_loop_1([P|Ps], Blocks, Predecessors, Acc0) ->
+ case map_get(P, Blocks) of
+ #b_blk{is=[#b_set{op=peek_message}|_]} ->
+ unsuitable_loop_1(Ps, Blocks, Predecessors, Acc0);
+ #b_blk{} ->
+ case ordsets:is_element(P, Acc0) of
+ false ->
+ Acc1 = ordsets:add_element(P, Acc0),
+ Acc = unsuitable_loop(P, Blocks, Predecessors, Acc1),
+ unsuitable_loop_1(Ps, Blocks, Predecessors, Acc);
+ true ->
+ unsuitable_loop_1(Ps, Blocks, Predecessors, Acc0)
+ end
+ end;
+unsuitable_loop_1([], _, _, Acc) -> Acc.
+
+%% new_def_locations([{Variable,[UsedInBlock]}|Vs], Defs,
+%% Dominators, Numbering, Unsuitable) ->
+%% [{Variable,NewDefinitionBlock}]
+%%
+%% Calculate new locations for get_tuple_element instructions. For
+%% each variable, the new location is a block that dominates all uses
+%% of the variable and as near to the uses of as possible.
+
+new_def_locations([{V,UsedIn}|Vs], Defs, Dom, Numbering, Unsuitable) ->
+ DefIn = map_get(V, Defs),
+ Common = common_dominator(UsedIn, Dom, Numbering, Unsuitable),
+ case member(Common, map_get(DefIn, Dom)) of
+ true ->
+ %% The common dominator is either DefIn or an
+ %% ancestor of DefIn.
+ new_def_locations(Vs, Defs, Dom, Numbering, Unsuitable);
+ false ->
+ %% We have found a suitable descendant of DefIn,
+ %% to which the get_tuple_element instruction can
+ %% be sunk.
+ [{V,Common}|new_def_locations(Vs, Defs, Dom, Numbering, Unsuitable)]
+ end;
+new_def_locations([], _, _, _, _) -> [].
+
+common_dominator(Ls0, Dom, Numbering, Unsuitable) ->
+ [Common|_] = beam_ssa:common_dominators(Ls0, Dom, Numbering),
+ case gb_sets:is_member(Common, Unsuitable) of
+ true ->
+ %% It is not allowed to place the instruction here. Try
+ %% to find another suitable dominating block by going up
+ %% one step in the dominator tree.
+ [Common,OneUp|_] = map_get(Common, Dom),
+ common_dominator([OneUp], Dom, Numbering, Unsuitable);
+ false ->
+ Common
+ end.
+
+%% Move get_tuple_element instructions to their new locations.
+
+move_defs(V, From, To, Blocks) ->
+ #{From:=FromBlk0,To:=ToBlk0} = Blocks,
+ {Def,FromBlk} = remove_def(V, FromBlk0),
+ try insert_def(V, Def, ToBlk0) of
+ ToBlk ->
+ %%io:format("~p: ~p => ~p\n", [V,From,To]),
+ Blocks#{From:=FromBlk,To:=ToBlk}
+ catch
+ throw:not_possible ->
+ Blocks
+ end.
+
+remove_def(V, #b_blk{is=Is0}=Blk) ->
+ {Def,Is} = remove_def_is(Is0, V, []),
+ {Def,Blk#b_blk{is=Is}}.
+
+remove_def_is([#b_set{dst=Dst}=Def|Is], Dst, Acc) ->
+ {Def,reverse(Acc, Is)};
+remove_def_is([I|Is], Dst, Acc) ->
+ remove_def_is(Is, Dst, [I|Acc]).
+
+insert_def(V, Def, #b_blk{is=Is0}=Blk) ->
+ Is = insert_def_is(Is0, V, Def),
+ Blk#b_blk{is=Is}.
+
+insert_def_is([#b_set{op=phi}=I|Is], V, Def) ->
+ case member(V, beam_ssa:used(I)) of
+ true ->
+ throw(not_possible);
+ false ->
+ [I|insert_def_is(Is, V, Def)]
+ end;
+insert_def_is([#b_set{op=Op}=I|Is]=Is0, V, Def) ->
+ Action0 = case Op of
+ call -> beyond;
+ 'catch_end' -> beyond;
+ timeout -> beyond;
+ _ -> here
+ end,
+ Action = case Is of
+ [#b_set{op=succeeded}|_] -> here;
+ _ -> Action0
+ end,
+ case Action of
+ beyond ->
+ case member(V, beam_ssa:used(I)) of
+ true ->
+ %% The variable is used by this instruction. We must
+ %% place the definition before this instruction.
+ [Def|Is0];
+ false ->
+ %% Place it beyond the current instruction.
+ [I|insert_def_is(Is, V, Def)]
+ end;
+ here ->
+ [Def|Is0]
+ end;
+insert_def_is([], _V, Def) ->
+ [Def].
+
+%%%
+%%% Order consecutive get_tuple_element instructions in ascending
+%%% position order. This will give the loader more opportunities
+%%% for combining get_tuple_element instructions.
+%%%
+
+ssa_opt_get_tuple_element({#st{ssa=Blocks0}=St, FuncDb}) ->
+ Blocks = opt_get_tuple_element(maps:to_list(Blocks0), Blocks0),
+ {St#st{ssa=Blocks}, FuncDb}.
+
+opt_get_tuple_element([{L,#b_blk{is=Is0}=Blk0}|Bs], Blocks) ->
+ case opt_get_tuple_element_is(Is0, false, []) of
+ {yes,Is} ->
+ Blk = Blk0#b_blk{is=Is},
+ opt_get_tuple_element(Bs, Blocks#{L:=Blk});
+ no ->
+ opt_get_tuple_element(Bs, Blocks)
+ end;
+opt_get_tuple_element([], Blocks) -> Blocks.
+
+opt_get_tuple_element_is([#b_set{op=get_tuple_element,
+ args=[#b_var{}=Src,_]}=I0|Is0],
+ _AnyChange, Acc) ->
+ {GetIs0,Is} = collect_get_tuple_element(Is0, Src, [I0]),
+ GetIs1 = sort([{Pos,I} || #b_set{args=[_,Pos]}=I <- GetIs0]),
+ GetIs = [I || {_,I} <- GetIs1],
+ opt_get_tuple_element_is(Is, true, reverse(GetIs, Acc));
+opt_get_tuple_element_is([I|Is], AnyChange, Acc) ->
+ opt_get_tuple_element_is(Is, AnyChange, [I|Acc]);
+opt_get_tuple_element_is([], AnyChange, Acc) ->
+ case AnyChange of
+ true -> {yes,reverse(Acc)};
+ false -> no
+ end.
+
+collect_get_tuple_element([#b_set{op=get_tuple_element,
+ args=[Src,_]}=I|Is], Src, Acc) ->
+ collect_get_tuple_element(Is, Src, [I|Acc]);
+collect_get_tuple_element(Is, _Src, Acc) ->
+ {Acc,Is}.
+
+%%%
+%%% Common utilities.
+%%%
+
+gcd(A, B) ->
+ case A rem B of
+ 0 -> B;
+ X -> gcd(B, X)
+ end.
+
+rel2fam(S0) ->
+ S1 = sofs:relation(S0),
+ S = sofs:rel2fam(S1),
+ sofs:to_external(S).
+
+sub(I, Sub) ->
+ beam_ssa:normalize(sub_1(I, Sub)).
+
+sub_1(#b_set{op=phi,args=Args}=I, Sub) ->
+ I#b_set{args=[{sub_arg(A, Sub),P} || {A,P} <- Args]};
+sub_1(#b_set{args=Args}=I, Sub) ->
+ I#b_set{args=[sub_arg(A, Sub) || A <- Args]};
+sub_1(#b_br{bool=#b_var{}=Old}=Br, Sub) ->
+ New = sub_arg(Old, Sub),
+ Br#b_br{bool=New};
+sub_1(#b_switch{arg=#b_var{}=Old}=Sw, Sub) ->
+ New = sub_arg(Old, Sub),
+ Sw#b_switch{arg=New};
+sub_1(#b_ret{arg=#b_var{}=Old}=Ret, Sub) ->
+ New = sub_arg(Old, Sub),
+ Ret#b_ret{arg=New};
+sub_1(Last, _) -> Last.
+
+sub_arg(#b_remote{mod=Mod,name=Name}=Rem, Sub) ->
+ Rem#b_remote{mod=sub_arg(Mod, Sub),name=sub_arg(Name, Sub)};
+sub_arg(Old, Sub) ->
+ case Sub of
+ #{Old:=New} -> New;
+ #{} -> Old
+ end.
+
+new_var(#b_var{name={Base,N}}, Count) ->
+ true = is_integer(N), %Assertion.
+ {#b_var{name={Base,Count}},Count+1};
+new_var(#b_var{name=Base}, Count) ->
+ {#b_var{name={Base,Count}},Count+1}.
diff --git a/lib/compiler/src/beam_ssa_opt.hrl b/lib/compiler/src/beam_ssa_opt.hrl
new file mode 100644
index 0000000000..37711a6f48
--- /dev/null
+++ b/lib/compiler/src/beam_ssa_opt.hrl
@@ -0,0 +1,53 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019. All 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("beam_ssa.hrl").
+
+-record(func_info,
+ {%% Local calls going in/out of this function.
+ in = ordsets:new() :: ordsets:ordset(func_id()),
+ out = ordsets:new() :: ordsets:ordset(func_id()),
+
+ %% Whether the function is exported or not; some optimizations may
+ %% need to be suppressed if it is.
+ exported = true :: boolean(),
+
+ %% The inferred types of each argument (as opposed to parameter),
+ %% indexed by call site.
+ %%
+ %% This is more effective than the naive approach of joining into a
+ %% "parameter_type" as we go as it lets us narrow parameter types
+ %% without having to visit all callers on each pass, which helps a lot
+ %% when dealing with co-recursive functions.
+ arg_types = [] :: list(arg_type_map()),
+
+ %% The inferred return type of this function, this is either [type()]
+ %% or [] to note absence.
+ ret_type = [] :: list()}).
+
+-type arg_key() :: {CallerId :: func_id(),
+ CallDst :: beam_ssa:b_var()}.
+-type arg_type_map() :: #{ arg_key() => term() }.
+
+%% Per-function metadata used by various optimization passes to perform
+%% module-level optimization. If a function is absent it means that
+%% module-level optimization has been turned off for said function.
+-type func_id() :: beam_ssa:b_local().
+-type func_info_db() :: #{ func_id() => #func_info{} }.
diff --git a/lib/compiler/src/beam_ssa_pp.erl b/lib/compiler/src/beam_ssa_pp.erl
new file mode 100644
index 0000000000..34ac08b32e
--- /dev/null
+++ b/lib/compiler/src/beam_ssa_pp.erl
@@ -0,0 +1,238 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018. All 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_pp).
+
+-export([format_function/1,format_instr/1,format_var/1]).
+
+-include("beam_ssa.hrl").
+
+-spec format_function(beam_ssa:b_function()) -> iolist().
+
+format_function(#b_function{anno=Anno0,args=Args,
+ bs=Blocks,cnt=Counter}) ->
+ #{func_info:={M,F,_}} = Anno0,
+ Anno = maps:without([func_info,location,live_intervals,registers], Anno0),
+ FuncAnno = case Anno0 of
+ #{live_intervals:=Intervals} ->
+ Anno0#{live_intervals:=maps:from_list(Intervals)};
+ #{} ->
+ Anno0
+ end,
+ ReachableBlocks = beam_ssa:rpo(Blocks),
+ All = maps:keys(Blocks),
+ Unreachable = ordsets:subtract(ordsets:from_list(All),
+ ordsets:from_list(ReachableBlocks)),
+ [case Anno0 of
+ #{location:={Filename,Line}} ->
+ io_lib:format("%% ~ts:~p\n", [Filename,Line]);
+ #{} ->
+ []
+ end,
+ io_lib:format("%% Counter = ~p\n", [Counter]),
+ [format_anno(Key, Value) ||
+ {Key,Value} <- lists:sort(maps:to_list(Anno))],
+ io_lib:format("function ~p:~p(~ts) {\n", [M,F,format_args(Args, FuncAnno)]),
+ [format_live_interval(Var, FuncAnno) || Var <- Args],
+ format_blocks(ReachableBlocks, Blocks, FuncAnno),
+ case Unreachable of
+ [] ->
+ [];
+ [_|_] ->
+ ["\n%% Unreachable blocks\n\n",
+ format_blocks(Unreachable, Blocks, FuncAnno)]
+ end,
+
+ "}\n"].
+
+
+-spec format_instr(beam_ssa:b_set()) -> iolist().
+
+format_instr(#b_set{}=I) ->
+ Cs = lists:flatten(format_instr(I#b_set{anno=#{}}, #{}, true)),
+ string:trim(Cs, leading);
+format_instr(I0) ->
+ I = setelement(2, I0, #{}),
+ Cs = lists:flatten(format_terminator(I, #{})),
+ string:trim(Cs, both).
+
+-spec format_var(beam_ssa:b_var()) -> iolist().
+
+format_var(V) ->
+ Cs = lists:flatten(format_var(V, #{})),
+ string:trim(Cs, leading).
+
+%%%
+%%% Local functions.
+%%%
+
+format_anno(Key, Map) when is_map(Map) ->
+ Sorted = lists:sort(maps:to_list(Map)),
+ [io_lib:format("%% ~s:\n", [Key]),
+ [io_lib:format("%% ~w => ~w\n", [K,V]) || {K,V} <- Sorted]];
+format_anno(Key, Value) ->
+ io_lib:format("%% ~s: ~p\n", [Key,Value]).
+
+format_blocks(Ls, Blocks, Anno) ->
+ PP = [format_block(L, Blocks, Anno) || L <- Ls],
+ lists:join($\n, PP).
+
+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])
+ end,
+ io_lib:format("~p:", [L]),
+ format_instrs(Is, FuncAnno, true),
+ $\n,
+ format_terminator(Last, FuncAnno)].
+
+format_instrs([I|Is], FuncAnno, First) ->
+ [$\n,
+ format_instr(I, FuncAnno, First),
+ format_instrs(Is, FuncAnno, false)];
+format_instrs([], _FuncAnno, _First) ->
+ [].
+
+format_instr(#b_set{anno=Anno,op=Op,dst=Dst,args=Args},
+ FuncAnno, First) ->
+ AnnoStr = format_anno(Anno),
+ LiveIntervalStr = format_live_interval(Dst, FuncAnno),
+ [if
+ First ->
+ [];
+ AnnoStr =/= []; LiveIntervalStr =/= [] ->
+ $\n;
+ true ->
+ []
+ end,
+ AnnoStr,
+ LiveIntervalStr,
+ io_lib:format(" ~s~ts = ~ts", [format_i_number(Anno),
+ format_var(Dst, FuncAnno),
+ format_op(Op)]),
+ case Args of
+ [] ->
+ [];
+ [_|_] ->
+ io_lib:format(" ~ts", [format_args(Args, FuncAnno)])
+ end].
+
+format_i_number(#{n:=N}) ->
+ io_lib:format("[~p] ", [N]);
+format_i_number(#{}) -> [].
+
+format_terminator(#b_br{anno=A,bool=#b_literal{val=true},succ=Lbl}, _) ->
+ io_lib:format(" ~sbr label ~p\n", [format_i_number(A),Lbl]);
+format_terminator(#b_br{anno=A,bool=#b_literal{val=false},fail=Lbl}, _) ->
+ io_lib:format(" ~sbr label ~p\n", [format_i_number(A),Lbl]);
+format_terminator(#b_br{anno=A,bool=Bool,succ=Succ,fail=Fail}, FuncAnno) ->
+ io_lib:format(" ~sbr ~ts, label ~p, label ~p\n",
+ [format_i_number(A),format_arg(Bool, FuncAnno),Succ,Fail]);
+format_terminator(#b_switch{anno=A,arg=Arg,fail=Fail,list=List}, FuncAnno) ->
+ io_lib:format(" ~sswitch ~ts, label ~p, ~ts\n",
+ [format_i_number(A),format_arg(Arg, FuncAnno),Fail,
+ format_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)]).
+
+format_op({Prefix,Name}) ->
+ io_lib:format("~p:~p", [Prefix,Name]);
+format_op(Name) ->
+ io_lib:format("~p", [Name]).
+
+format_register(#b_var{}=V, #{registers:=Regs}) ->
+ {Tag,N} = maps:get(V, Regs),
+ io_lib:format("~p~p", [Tag,N]);
+format_register(_, #{}) -> "".
+
+format_var(Var, FuncAnno) ->
+ VarString = format_var_1(Var),
+ case format_register(Var, FuncAnno) of
+ [] -> VarString;
+ [_|_]=Reg -> [Reg,$/,VarString]
+ end.
+
+format_var_1(#b_var{name={Name,Uniq}}) ->
+ if
+ is_atom(Name) ->
+ io_lib:format("~ts:~p", [Name,Uniq]);
+ is_integer(Name) ->
+ io_lib:format("_~p:~p", [Name,Uniq])
+ end;
+format_var_1(#b_var{name=Name}) when is_atom(Name) ->
+ atom_to_list(Name);
+format_var_1(#b_var{name=Name}) when is_integer(Name) ->
+ "_"++integer_to_list(Name).
+
+format_args(Args, FuncAnno) ->
+ Ss = [format_arg(Arg, FuncAnno) || Arg <- Args],
+ lists:join(", ", Ss).
+
+format_arg(#b_var{}=Arg, FuncAnno) ->
+ format_var(Arg, FuncAnno);
+format_arg(#b_literal{val=Val}, _FuncAnno) ->
+ io_lib:format("literal ~p", [Val]);
+format_arg(#b_remote{mod=Mod,name=Name,arity=Arity}, FuncAnno) ->
+ io_lib:format("remote (~ts):(~ts)/~p",
+ [format_arg(Mod, FuncAnno),format_arg(Name, FuncAnno),Arity]);
+format_arg(#b_local{name=Name,arity=Arity}, FuncAnno) ->
+ io_lib:format("local ~ts/~p", [format_arg(Name, FuncAnno),Arity]);
+format_arg({Value,Label}, FuncAnno) when is_integer(Label) ->
+ io_lib:format("{ ~ts, ~p }", [format_arg(Value, FuncAnno),Label]);
+format_arg(Other, _) ->
+ io_lib:format("*** ~p ***", [Other]).
+
+format_list(List, FuncAnno) ->
+ Ss = [io_lib:format("{ ~ts, ~ts }", [format_arg(Val, FuncAnno),format_label(L)]) ||
+ {Val,L} <- List],
+ io_lib:format("[ ~ts ]", [lists:join(", ", Ss)]).
+
+format_label(L) ->
+ ["label ",integer_to_list(L)].
+
+format_anno(#{n:=_}=Anno) ->
+ format_anno(maps:remove(n, Anno));
+format_anno(#{location:={File,Line}}=Anno0) ->
+ Anno = maps:remove(location, Anno0),
+ [io_lib:format(" %% ~ts:~p\n", [File,Line])|format_anno_1(Anno)];
+format_anno(Anno) ->
+ format_anno_1(Anno).
+
+format_anno_1(Anno) ->
+ case map_size(Anno) of
+ 0 ->
+ [];
+ _ ->
+ [io_lib:format(" %% Anno: ~p\n", [Anno])]
+ end.
+
+format_live_interval(#b_var{}=Dst, #{live_intervals:=Intervals}) ->
+ case Intervals of
+ #{Dst:=Rs0} ->
+ Rs1 = [io_lib:format("~p..~p", [Start,End]) ||
+ {Start,End} <- Rs0],
+ Rs = lists:join(" ", Rs1),
+ io_lib:format(" %% ~ts: ~s\n", [format_var_1(Dst),Rs]);
+ #{} ->
+ []
+ end;
+format_live_interval(_, _) -> [].
+
diff --git a/lib/compiler/src/beam_ssa_pre_codegen.erl b/lib/compiler/src/beam_ssa_pre_codegen.erl
new file mode 100644
index 0000000000..bad43a9c4e
--- /dev/null
+++ b/lib/compiler/src/beam_ssa_pre_codegen.erl
@@ -0,0 +1,2831 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018. All 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: Prepare for code generation, including register allocation.
+%%
+%% The output of this compiler pass is still in the SSA format, but
+%% it has been annotated and transformed to help the code generator.
+%%
+%% * Some instructions are translated to other instructions closer to
+%% the BEAM instructions. For example, the binary matching
+%% instructions are transformed from the optimization-friendly
+%% internal format to instruction more similar to the actual BEAM
+%% instructions.
+%%
+%% * Blocks that will need an instruction for allocating a stack frame
+%% are annotated with a {frame_size,Size} annotation.
+%%
+%% * 'copy' instructions are added for all variables that need
+%% to be saved to the stack frame. Additional 'copy' instructions
+%% can be added as an optimization to reuse y registers (see
+%% the copy_retval sub pass).
+%%
+%% * Each function is annotated with a {register,RegisterMap}
+%% annotation that maps each variable to a BEAM register. The linear
+%% scan algorithm is used to allocate registers.
+%%
+%% There are four kind of registers. x, y, fr (floating point register),
+%% and z. A variable will be allocated to a z register if it is only
+%% used by the instruction following the instruction that defines the
+%% the variable. The code generator will typically combine those
+%% instructions to a test instruction. z registers are also used for
+%% some instructions that don't have a return value.
+%%
+%% References:
+%%
+%% [1] H. Mössenböck and M. Pfeiffer. Linear scan register allocation
+%% in the context of SSA form and register constraints. In Proceedings
+%% of the International Conference on Compiler Construction, pages
+%% 229–246. LNCS 2304, Springer-Verlag, 2002.
+%%
+%% [2] C. Wimmer and H. Mössenböck. Optimized interval splitting in a
+%% linear scan register allocator. In Proceedings of the ACM/USENIX
+%% International Conference on Virtual Execution Environments, pages
+%% 132–141. ACM Press, 2005.
+%%
+%% [3] C. Wimmer and M. Franz. Linear Scan Register Allocation on SSA
+%% Form. In Proceedings of the International Symposium on Code
+%% Generation and Optimization, pages 170-179. ACM Press, 2010.
+%%
+
+-module(beam_ssa_pre_codegen).
+
+-export([module/2]).
+
+-include("beam_ssa.hrl").
+
+-import(lists, [all/2,any/2,append/1,duplicate/2,
+ foldl/3,last/1,map/2,member/2,partition/2,
+ reverse/1,reverse/2,sort/1,splitwith/2,zip/2]).
+
+-spec module(beam_ssa:b_module(), [compile:option()]) ->
+ {'ok',beam_ssa:b_module()}.
+
+module(#b_module{body=Fs0}=Module, Opts) ->
+ UseBSM3 = not proplists:get_bool(no_bsm3, Opts),
+ Ps = passes(Opts),
+ Fs = functions(Fs0, Ps, UseBSM3),
+ {ok,Module#b_module{body=Fs}}.
+
+functions([F|Fs], Ps, UseBSM3) ->
+ [function(F, Ps, UseBSM3)|functions(Fs, Ps, UseBSM3)];
+functions([], _Ps, _UseBSM3) -> [].
+
+-type b_var() :: beam_ssa:b_var().
+-type var_name() :: beam_ssa:var_name().
+-type instr_number() :: pos_integer().
+-type range() :: {instr_number(),instr_number()}.
+-type reg_num() :: beam_asm:reg_num().
+-type xreg() :: {'x',reg_num()}.
+-type yreg() :: {'y',reg_num()}.
+-type ypool() :: {'y',beam_ssa:label()}.
+-type reservation() :: 'fr' | {'prefer',xreg()} | 'x' | {'x',xreg()} |
+ ypool() | {yreg(),ypool()} | 'z'.
+-type ssa_register() :: beam_ssa_codegen:ssa_register().
+
+-define(TC(Body), tc(fun() -> Body end, ?FILE, ?LINE)).
+-record(st, {ssa :: beam_ssa:block_map(),
+ args :: [b_var()],
+ cnt :: beam_ssa:label(),
+ use_bsm3 :: boolean(),
+ frames=[] :: [beam_ssa:label()],
+ intervals=[] :: [{b_var(),[range()]}],
+ res=[] :: [{b_var(),reservation()}] | #{b_var():=reservation()},
+ regs=#{} :: #{b_var():=ssa_register()},
+ extra_annos=[] :: [{atom(),term()}]
+ }).
+-define(PASS(N), {N,fun N/1}).
+
+passes(Opts) ->
+ AddPrecgAnnos = proplists:get_bool(dprecg, Opts),
+ FixTuples = proplists:get_bool(no_put_tuple2, Opts),
+ Ps = [?PASS(assert_no_critical_edges),
+
+ %% Preliminaries.
+ ?PASS(fix_bs),
+ ?PASS(sanitize),
+ case FixTuples of
+ false -> ignore;
+ true -> ?PASS(fix_tuples)
+ end,
+ ?PASS(use_set_tuple_element),
+ ?PASS(place_frames),
+ ?PASS(fix_receives),
+
+ %% Find and reserve Y registers.
+ ?PASS(find_yregs),
+ ?PASS(reserve_yregs),
+
+ %% Handle legacy binary match instruction that don't
+ %% accept a Y register as destination.
+ ?PASS(legacy_bs),
+
+ %% Improve reuse of Y registers to potentially
+ %% reduce the size of the stack frame.
+ ?PASS(copy_retval),
+ ?PASS(opt_get_list),
+
+ %% Calculate live intervals.
+ ?PASS(number_instructions),
+ ?PASS(live_intervals),
+ ?PASS(reserve_regs),
+
+ %% If needed for a .precg file, save the live intervals
+ %% so they can be included in an annotation.
+ case AddPrecgAnnos of
+ false -> ignore;
+ true -> ?PASS(save_live_intervals)
+ end,
+
+ %% Allocate registers.
+ ?PASS(linear_scan),
+ ?PASS(frame_size),
+ ?PASS(turn_yregs)],
+ [P || P <- Ps, P =/= ignore].
+
+function(#b_function{anno=Anno,args=Args,bs=Blocks0,cnt=Count0}=F0,
+ Ps, UseBSM3) ->
+ try
+ St0 = #st{ssa=Blocks0,args=Args,use_bsm3=UseBSM3,cnt=Count0},
+ St = compile:run_sub_passes(Ps, St0),
+ #st{ssa=Blocks,cnt=Count,regs=Regs,extra_annos=ExtraAnnos} = St,
+ F1 = add_extra_annos(F0, ExtraAnnos),
+ F = beam_ssa:add_anno(registers, Regs, F1),
+ F#b_function{bs=Blocks,cnt=Count}
+ catch
+ Class:Error:Stack ->
+ #{func_info:={_,Name,Arity}} = Anno,
+ io:fwrite("Function: ~w/~w\n", [Name,Arity]),
+ erlang:raise(Class, Error, Stack)
+ end.
+
+save_live_intervals(#st{intervals=Intervals}=St) ->
+ St#st{extra_annos=[{live_intervals,Intervals}]}.
+
+%% Add extra annotations when a .precg listing file is being produced.
+add_extra_annos(F, Annos) ->
+ foldl(fun({Name,Value}, Acc) ->
+ beam_ssa:add_anno(Name, Value, Acc)
+ end, F, Annos).
+
+%% assert_no_critical_edges(St0) -> St.
+%% The code generator will not work if there are critial edges.
+%% Abort if any critical edges are found.
+
+assert_no_critical_edges(#st{ssa=Blocks}=St) ->
+ F = fun assert_no_ces/3,
+ beam_ssa:fold_rpo(F, Blocks, Blocks),
+ St.
+
+assert_no_ces(_, #b_blk{is=[#b_set{op=phi,args=[_,_]=Phis}|_]}, Blocks) ->
+ %% This block has multiple predecessors. Make sure that none
+ %% of the precessors have more than one successor.
+ true = all(fun({_,P}) ->
+ length(beam_ssa:successors(P, Blocks)) =:= 1
+ end, Phis), %Assertion.
+ Blocks;
+assert_no_ces(_, _, Blocks) -> Blocks.
+
+%% fix_bs(St0) -> St.
+%% Fix up the binary matching instructions:
+%%
+%% * Insert bs_save and bs_restore instructions where needed.
+%%
+%% * Combine bs_match and bs_extract instructions to bs_get
+%% instructions.
+
+fix_bs(#st{ssa=Blocks,cnt=Count0,use_bsm3=UseBSM3}=St) ->
+ F = fun(#b_set{op=bs_start_match,dst=Dst}, A) ->
+ %% Mark the root of the match context list.
+ [{Dst,{context,Dst}}|A];
+ (#b_set{op=bs_match,dst=Dst,args=[_,ParentCtx|_]}, A) ->
+ %% Link this match context the previous match context.
+ [{Dst,ParentCtx}|A];
+ (_, A) ->
+ A
+ end,
+ case beam_ssa:fold_instrs_rpo(F, [0], [],Blocks) of
+ [] ->
+ %% No binary matching in this function.
+ St;
+ [_|_]=M ->
+ CtxChain = maps:from_list(M),
+ Linear0 = beam_ssa:linearize(Blocks),
+
+ %% Insert position instructions where needed.
+ {Linear1,Count} = case UseBSM3 of
+ true ->
+ bs_pos_bsm3(Linear0, CtxChain, Count0);
+ false ->
+ bs_pos_bsm2(Linear0, CtxChain, Count0)
+ end,
+
+ %% Rename instructions.
+ Linear = bs_instrs(Linear1, CtxChain, []),
+
+ St#st{ssa=maps:from_list(Linear),cnt=Count}
+ end.
+
+%% Insert bs_get_position and bs_set_position instructions as needed.
+bs_pos_bsm3(Linear0, CtxChain, Count0) ->
+ Rs0 = bs_restores(Linear0, CtxChain, #{}, #{}),
+ Rs = maps:values(Rs0),
+ S0 = sofs:relation(Rs, [{context,save_point}]),
+ S1 = sofs:relation_to_family(S0),
+ S = sofs:to_external(S1),
+
+ {SavePoints,Count1} = make_bs_pos_dict(S, Count0, []),
+ {Gets,Count2} = make_bs_setpos_map(Rs, SavePoints, Count1, []),
+ {Sets,Count} = make_bs_getpos_map(maps:to_list(Rs0), SavePoints, Count2, []),
+
+ %% Now insert all saves and restores.
+ {bs_insert_bsm3(Linear0, Gets, Sets, SavePoints),Count}.
+
+make_bs_setpos_map([{Ctx,Save}=Ps|T], SavePoints, Count, Acc) ->
+ SavePoint = get_savepoint(Ps, SavePoints),
+ I = #b_set{op=bs_get_position,dst=SavePoint,args=[Ctx]},
+ make_bs_setpos_map(T, SavePoints, Count+1, [{Save,I}|Acc]);
+make_bs_setpos_map([], _, Count, Acc) ->
+ {maps:from_list(Acc),Count}.
+
+make_bs_getpos_map([{Bef,{Ctx,_}=Ps}|T], SavePoints, Count, Acc) ->
+ Ignored = #b_var{name={'@ssa_ignored',Count}},
+ Args = [Ctx, get_savepoint(Ps, SavePoints)],
+ I = #b_set{op=bs_set_position,dst=Ignored,args=Args},
+ make_bs_getpos_map(T, SavePoints, Count+1, [{Bef,I}|Acc]);
+make_bs_getpos_map([], _, Count, Acc) ->
+ {maps:from_list(Acc),Count}.
+
+get_savepoint({_,_}=Ps, SavePoints) ->
+ Name = {'@ssa_bs_position', map_get(Ps, SavePoints)},
+ #b_var{name=Name}.
+
+make_bs_pos_dict([{Ctx,Pts}|T], Count0, Acc0) ->
+ {Acc, Count} = make_bs_pos_dict_1(Pts, Ctx, Count0, Acc0),
+ make_bs_pos_dict(T, Count, Acc);
+make_bs_pos_dict([], Count, Acc) ->
+ {maps:from_list(Acc), Count}.
+
+make_bs_pos_dict_1([H|T], Ctx, I, Acc) ->
+ make_bs_pos_dict_1(T, Ctx, I+1, [{{Ctx,H},I}|Acc]);
+make_bs_pos_dict_1([], Ctx, I, Acc) ->
+ {[{Ctx,I}|Acc], I}.
+
+%% As bs_position but without OTP-22 instructions. This is only used when
+%% cross-compiling to older versions.
+bs_pos_bsm2(Linear0, CtxChain, Count0) ->
+ Rs0 = bs_restores(Linear0, CtxChain, #{}, #{}),
+ Rs = maps:values(Rs0),
+ S0 = sofs:relation(Rs, [{context,save_point}]),
+ S1 = sofs:relation_to_family(S0),
+ S = sofs:to_external(S1),
+ Slots = make_save_point_dict(S, []),
+ {Saves,Count1} = make_save_map(Rs, Slots, Count0, []),
+ {Restores,Count} = make_restore_map(maps:to_list(Rs0), Slots, Count1, []),
+
+ %% Now insert all saves and restores.
+ {bs_insert_bsm2(Linear0, Saves, Restores, Slots),Count}.
+
+make_save_map([{Ctx,Save}=Ps|T], Slots, Count, Acc) ->
+ Ignored = #b_var{name={'@ssa_ignored',Count}},
+ case make_slot(Ps, Slots) of
+ #b_literal{val=start} ->
+ make_save_map(T, Slots, Count, Acc);
+ Slot ->
+ I = #b_set{op=bs_save,dst=Ignored,args=[Ctx,Slot]},
+ make_save_map(T, Slots, Count+1, [{Save,I}|Acc])
+ end;
+make_save_map([], _, Count, Acc) ->
+ {maps:from_list(Acc),Count}.
+
+make_restore_map([{Bef,{Ctx,_}=Ps}|T], Slots, Count, Acc) ->
+ Ignored = #b_var{name={'@ssa_ignored',Count}},
+ I = #b_set{op=bs_restore,dst=Ignored,args=[Ctx,make_slot(Ps, Slots)]},
+ make_restore_map(T, Slots, Count+1, [{Bef,I}|Acc]);
+make_restore_map([], _, Count, Acc) ->
+ {maps:from_list(Acc),Count}.
+
+make_slot({Same,Same}, _Slots) ->
+ #b_literal{val=start};
+make_slot({_,_}=Ps, Slots) ->
+ #b_literal{val=map_get(Ps, Slots)}.
+
+make_save_point_dict([{Ctx,Pts}|T], Acc0) ->
+ Acc = make_save_point_dict_1(Pts, Ctx, 0, Acc0),
+ make_save_point_dict(T, Acc);
+make_save_point_dict([], Acc) ->
+ maps:from_list(Acc).
+
+make_save_point_dict_1([Ctx|T], Ctx, I, Acc) ->
+ %% Special {atom,start} save point. Does not need a
+ %% bs_save instruction.
+ make_save_point_dict_1(T, Ctx, I, Acc);
+make_save_point_dict_1([H|T], Ctx, I, Acc) ->
+ make_save_point_dict_1(T, Ctx, I+1, [{{Ctx,H},I}|Acc]);
+make_save_point_dict_1([], Ctx, I, Acc) ->
+ [{Ctx,I}|Acc].
+
+bs_restores([{L,#b_blk{is=Is,last=Last}}|Bs], CtxChain, D0, Rs0) ->
+ FPos = case D0 of
+ #{L:=Pos0} -> Pos0;
+ #{} -> #{}
+ end,
+ {SPos,Rs} = bs_restores_is(Is, CtxChain, FPos, Rs0),
+ D = bs_update_successors(Last, SPos, FPos, D0),
+ bs_restores(Bs, CtxChain, D, Rs);
+bs_restores([], _, _, Rs) -> Rs.
+
+bs_update_successors(#b_br{succ=Succ,fail=Fail}, SPos, FPos, D) ->
+ join_positions([{Succ,SPos},{Fail,FPos}], D);
+bs_update_successors(#b_switch{fail=Fail,list=List}, SPos, _FPos, D) ->
+ Update = [{L,SPos} || {_,L} <- List] ++ [{Fail,SPos}],
+ join_positions(Update, D);
+bs_update_successors(#b_ret{}, _, _, D) -> D.
+
+join_positions([{L,MapPos0}|T], D) ->
+ case D of
+ #{L:=MapPos0} ->
+ %% Same map.
+ join_positions(T, D);
+ #{L:=MapPos1} ->
+ %% Different maps.
+ MapPos = join_positions_1(MapPos0, MapPos1),
+ join_positions(T, D#{L:=MapPos});
+ #{} ->
+ join_positions(T, D#{L=>MapPos0})
+ end;
+join_positions([], D) -> D.
+
+join_positions_1(MapPos0, MapPos1) ->
+ MapPos2 = maps:map(fun(Start, Pos) ->
+ case MapPos0 of
+ #{Start:=Pos} -> Pos;
+ #{Start:=_} -> unknown;
+ #{} -> Pos
+ end
+ end, MapPos1),
+ maps:merge(MapPos0, MapPos2).
+
+bs_restores_is([#b_set{op=bs_start_match,dst=Start}|Is],
+ CtxChain, PosMap0, Rs) ->
+ PosMap = PosMap0#{Start=>Start},
+ bs_restores_is(Is, CtxChain, PosMap, Rs);
+bs_restores_is([#b_set{op=bs_match,dst=NewPos,args=Args}=I|Is],
+ CtxChain, PosMap0, Rs0) ->
+ Start = bs_subst_ctx(NewPos, CtxChain),
+ [_,FromPos|_] = Args,
+ case PosMap0 of
+ #{Start:=FromPos} ->
+ %% Same position, no restore needed.
+ PosMap = case bs_match_type(I) of
+ plain ->
+ %% Update position to new position.
+ PosMap0#{Start:=NewPos};
+ _ ->
+ %% Position will not change (test_unit
+ %% instruction or no instruction at
+ %% all).
+ PosMap0#{Start:=FromPos}
+ end,
+ bs_restores_is(Is, CtxChain, PosMap, Rs0);
+ #{Start:=_} ->
+ %% Different positions, might need a restore instruction.
+ case bs_match_type(I) of
+ none ->
+ %% The tail test will be optimized away.
+ %% No need to do a restore.
+ PosMap = PosMap0#{Start:=FromPos},
+ bs_restores_is(Is, CtxChain, PosMap, Rs0);
+ test_unit ->
+ %% This match instruction will be replaced by
+ %% a test_unit instruction. We will need a
+ %% restore. The new position will be the position
+ %% restored to (NOT NewPos).
+ PosMap = PosMap0#{Start:=FromPos},
+ Rs = Rs0#{NewPos=>{Start,FromPos}},
+ bs_restores_is(Is, CtxChain, PosMap, Rs);
+ plain ->
+ %% Match or skip. Position will be changed.
+ PosMap = PosMap0#{Start:=NewPos},
+ Rs = Rs0#{NewPos=>{Start,FromPos}},
+ bs_restores_is(Is, CtxChain, PosMap, Rs)
+ end
+ end;
+bs_restores_is([#b_set{op=bs_extract,args=[FromPos|_]}|Is],
+ CtxChain, PosMap, Rs) ->
+ Start = bs_subst_ctx(FromPos, CtxChain),
+ #{Start:=FromPos} = PosMap, %Assertion.
+ bs_restores_is(Is, CtxChain, PosMap, Rs);
+bs_restores_is([#b_set{op=call,dst=Dst,args=Args}|Is],
+ CtxChain, PosMap0, Rs0) ->
+ {Rs,PosMap1} = bs_restore_args(Args, PosMap0, CtxChain, Dst, Rs0),
+ PosMap = bs_invalidate_pos(Args, PosMap1, CtxChain),
+ bs_restores_is(Is, CtxChain, PosMap, Rs);
+bs_restores_is([#b_set{op=landingpad}|Is], CtxChain, PosMap0, Rs) ->
+ %% We can land here from any point, so all positions are invalid.
+ PosMap = maps:map(fun(_Start,_Pos) -> unknown end, PosMap0),
+ bs_restores_is(Is, CtxChain, PosMap, Rs);
+bs_restores_is([#b_set{op=Op,dst=Dst,args=Args}|Is],
+ CtxChain, PosMap0, Rs0)
+ when Op =:= bs_test_tail;
+ Op =:= bs_get_tail ->
+ {Rs,PosMap} = bs_restore_args(Args, PosMap0, CtxChain, Dst, Rs0),
+ bs_restores_is(Is, CtxChain, PosMap, Rs);
+bs_restores_is([_|Is], CtxChain, PosMap, Rs) ->
+ bs_restores_is(Is, CtxChain, PosMap, Rs);
+bs_restores_is([], _CtxChain, PosMap, Rs) ->
+ {PosMap,Rs}.
+
+bs_match_type(#b_set{args=[#b_literal{val=skip},_Ctx,
+ #b_literal{val=binary},_Flags,
+ #b_literal{val=all},#b_literal{val=U}]}) ->
+ case U of
+ 1 -> none;
+ _ -> test_unit
+ end;
+bs_match_type(_) ->
+ plain.
+
+%% Call instructions leave the match position in an undefined state,
+%% requiring us to invalidate each affected argument.
+bs_invalidate_pos([#b_var{}=Arg|Args], PosMap0, CtxChain) ->
+ Start = bs_subst_ctx(Arg, CtxChain),
+ case PosMap0 of
+ #{Start:=_} ->
+ PosMap = PosMap0#{Start:=unknown},
+ bs_invalidate_pos(Args, PosMap, CtxChain);
+ #{} ->
+ %% Not a match context.
+ bs_invalidate_pos(Args, PosMap0, CtxChain)
+ end;
+bs_invalidate_pos([_|Args], PosMap, CtxChain) ->
+ bs_invalidate_pos(Args, PosMap, CtxChain);
+bs_invalidate_pos([], PosMap, _CtxChain) ->
+ PosMap.
+
+bs_restore_args([#b_var{}=Arg|Args], PosMap0, CtxChain, Dst, Rs0) ->
+ Start = bs_subst_ctx(Arg, CtxChain),
+ case PosMap0 of
+ #{Start:=Arg} ->
+ %% Same position, no restore needed.
+ bs_restore_args(Args, PosMap0, CtxChain, Dst, Rs0);
+ #{Start:=_} ->
+ %% Different positions, need a restore instruction.
+ PosMap = PosMap0#{Start:=Arg},
+ Rs = Rs0#{Dst=>{Start,Arg}},
+ bs_restore_args(Args, PosMap, CtxChain, Dst, Rs);
+ #{} ->
+ %% Not a match context.
+ bs_restore_args(Args, PosMap0, CtxChain, Dst, Rs0)
+ end;
+bs_restore_args([_|Args], PosMap, CtxChain, Dst, Rs) ->
+ bs_restore_args(Args, PosMap, CtxChain, Dst, Rs);
+bs_restore_args([], PosMap, _CtxChain, _Dst, Rs) ->
+ {Rs,PosMap}.
+
+%% Insert all bs_save and bs_restore instructions.
+
+bs_insert_bsm3(Blocks, Saves, Restores, SavePoints) ->
+ bs_insert_1(Blocks, Saves, Restores, SavePoints, fun(I) -> I end).
+
+bs_insert_bsm2(Blocks, Saves, Restores, SavePoints) ->
+ %% The old instructions require bs_start_match to be annotated with the
+ %% number of position slots it needs.
+ bs_insert_1(Blocks, Saves, Restores, SavePoints,
+ fun(#b_set{op=bs_start_match,dst=Dst}=I0) ->
+ NumSlots = case SavePoints of
+ #{Dst:=NumSlots0} -> NumSlots0;
+ #{} -> 0
+ end,
+ beam_ssa:add_anno(num_slots, NumSlots, I0);
+ (I) ->
+ I
+ end).
+
+bs_insert_1([{L,#b_blk{is=Is0}=Blk}|Bs0], Saves, Restores, Slots, XFrm) ->
+ Is = bs_insert_is_1(Is0, Restores, Slots, XFrm),
+ Bs = bs_insert_saves(Is, Bs0, Saves),
+ [{L,Blk#b_blk{is=Is}}|bs_insert_1(Bs, Saves, Restores, Slots, XFrm)];
+bs_insert_1([], _, _, _, _) -> [].
+
+bs_insert_is_1([#b_set{op=Op,dst=Dst}=I0|Is], Restores, SavePoints, XFrm) ->
+ I = XFrm(I0),
+ if
+ Op =:= bs_test_tail;
+ Op =:= bs_get_tail;
+ Op =:= bs_match;
+ Op =:= call ->
+ Rs = case Restores of
+ #{Dst:=R} -> [R];
+ #{} -> []
+ end,
+ Rs ++ [I|bs_insert_is_1(Is, Restores, SavePoints, XFrm)];
+ true ->
+ [I|bs_insert_is_1(Is, Restores, SavePoints, XFrm)]
+ end;
+bs_insert_is_1([], _, _, _) -> [].
+
+bs_insert_saves([#b_set{dst=Dst}|Is], Bs, Saves) ->
+ case Saves of
+ #{Dst:=S} ->
+ bs_insert_save(S, Bs);
+ #{} ->
+ bs_insert_saves(Is, Bs, Saves)
+ end;
+bs_insert_saves([], Bs, _) -> Bs.
+
+bs_insert_save(Save, [{L,#b_blk{is=Is0}=Blk}|Bs]) ->
+ Is = case Is0 of
+ [#b_set{op=bs_extract}=Ex|Is1] ->
+ [Ex,Save|Is1];
+ _ ->
+ [Save|Is0]
+ end,
+ [{L,Blk#b_blk{is=Is}}|Bs].
+
+%% Translate bs_match instructions to bs_get, bs_match_string,
+%% or bs_skip. Also rename match context variables to use the
+%% variable assigned to by the start_match instruction.
+
+bs_instrs([{L,#b_blk{is=Is0}=Blk}|Bs], CtxChain, Acc0) ->
+ case bs_instrs_is(Is0, CtxChain, []) of
+ [#b_set{op=bs_extract,dst=Dst,args=[Ctx]}|Is] ->
+ %% Drop this instruction. Rewrite the corresponding
+ %% bs_match instruction in the previous block to
+ %% a bs_get instruction.
+ Acc = bs_combine(Dst, Ctx, Acc0),
+ bs_instrs(Bs, CtxChain, [{L,Blk#b_blk{is=Is}}|Acc]);
+ Is ->
+ bs_instrs(Bs, CtxChain, [{L,Blk#b_blk{is=Is}}|Acc0])
+ end;
+bs_instrs([], _, Acc) ->
+ reverse(Acc).
+
+bs_instrs_is([#b_set{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]};
+ {bs_match,[#b_literal{val=string},Ctx|As]} ->
+ I1#b_set{op=bs_match_string,args=[Ctx|As]};
+ {bs_get_tail,[Ctx|As]} ->
+ I1#b_set{op=bs_get_tail,args=[Ctx|As]};
+ {_,_} ->
+ I1
+ end,
+ bs_instrs_is(Is, CtxChain, [I|Acc]);
+bs_instrs_is([], _, Acc) ->
+ reverse(Acc).
+
+%% Combine a bs_match instruction with the destination register
+%% taken from a bs_extract instruction.
+
+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].
+
+bs_subst_ctx(#b_var{}=Var, CtxChain) ->
+ case CtxChain of
+ #{Var:={context,Ctx}} ->
+ Ctx;
+ #{Var:=ParentCtx} ->
+ bs_subst_ctx(ParentCtx, CtxChain);
+ #{} ->
+ %% Not a match context variable.
+ Var
+ end;
+bs_subst_ctx(Other, _CtxChain) ->
+ Other.
+
+%% legacy_bs(St0) -> St.
+%% Binary matching instructions in OTP 21 and earlier don't support
+%% a Y register as destination. If St#st.use_bsm3 is false,
+%% we will need to rewrite those instructions so that the result
+%% is first put in an X register and then moved to a Y register
+%% if the operation succeeded.
+
+legacy_bs(#st{use_bsm3=false,ssa=Blocks0,cnt=Count0,res=Res}=St) ->
+ IsYreg = maps:from_list([{V,true} || {V,{y,_}} <- Res]),
+ Linear0 = beam_ssa:linearize(Blocks0),
+ {Linear,Count} = legacy_bs(Linear0, IsYreg, Count0, #{}, []),
+ Blocks = maps:from_list(Linear),
+ St#st{ssa=Blocks,cnt=Count};
+legacy_bs(#st{use_bsm3=true}=St) -> St.
+
+legacy_bs([{L,Blk}|Bs], IsYreg, Count0, Copies0, Acc) ->
+ #b_blk{is=Is0,last=Last} = Blk,
+ Is1 = case Copies0 of
+ #{L:=Copy} -> [Copy|Is0];
+ #{} -> Is0
+ end,
+ {Is,Count,Copies} = legacy_bs_is(Is1, Last, IsYreg, Count0, Copies0, []),
+ legacy_bs(Bs, IsYreg, Count, Copies, [{L,Blk#b_blk{is=Is}}|Acc]);
+legacy_bs([], _IsYreg, Count, _Copies, Acc) ->
+ {Acc,Count}.
+
+legacy_bs_is([#b_set{op=Op,dst=Dst}=I0,
+ #b_set{op=succeeded,dst=SuccDst,args=[Dst]}=SuccI0],
+ Last, IsYreg, Count0, Copies0, Acc) ->
+ NeedsFix = is_map_key(Dst, IsYreg) andalso
+ case Op of
+ bs_get -> true;
+ bs_init -> true;
+ _ -> false
+ end,
+ case NeedsFix of
+ true ->
+ TempDst = #b_var{name={'@bs_temp_dst',Count0}},
+ Count = Count0 + 1,
+ I = I0#b_set{dst=TempDst},
+ SuccI = SuccI0#b_set{args=[TempDst]},
+ Copy = #b_set{op=copy,dst=Dst,args=[TempDst]},
+ #b_br{bool=SuccDst,succ=SuccL} = Last,
+ Copies = Copies0#{SuccL=>Copy},
+ legacy_bs_is([], Last, IsYreg, Count, Copies, [SuccI,I|Acc]);
+ false ->
+ legacy_bs_is([], Last, IsYreg, Count0, Copies0, [SuccI0,I0|Acc])
+ end;
+legacy_bs_is([I|Is], Last, IsYreg, Count, Copies, Acc) ->
+ legacy_bs_is(Is, Last, IsYreg, Count, Copies, [I|Acc]);
+legacy_bs_is([], _Last, _IsYreg, Count, Copies, Acc) ->
+ {reverse(Acc),Count,Copies}.
+
+%% sanitize(St0) -> St.
+%% Remove constructs that can cause problems later:
+%%
+%% * Unreachable blocks may cause problems for determination of
+%% dominators.
+%%
+%% * Some instructions (such as get_hd) don't accept literal
+%% arguments. Evaluate the instructions and remove them.
+
+sanitize(#st{ssa=Blocks0,cnt=Count0}=St) ->
+ Ls = beam_ssa:rpo(Blocks0),
+ {Blocks,Count} = sanitize(Ls, Count0, Blocks0, #{}),
+ St#st{ssa=Blocks,cnt=Count}.
+
+sanitize([L|Ls], Count0, Blocks0, Values0) ->
+ #b_blk{is=Is0} = Blk0 = map_get(L, Blocks0),
+ case sanitize_is(Is0, Count0, Values0, false, []) of
+ no_change ->
+ sanitize(Ls, Count0, Blocks0, Values0);
+ {Is,Count,Values} ->
+ Blk = Blk0#b_blk{is=Is},
+ Blocks = Blocks0#{L:=Blk},
+ sanitize(Ls, Count, Blocks, Values)
+ end;
+sanitize([], Count, Blocks0, Values) ->
+ Blocks = if
+ map_size(Values) =:= 0 ->
+ Blocks0;
+ true ->
+ beam_ssa:rename_vars(Values, [0], Blocks0)
+ end,
+
+ %% 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_is([#b_set{op=get_map_element,args=Args0}=I0|Is],
+ 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, Count, Values, true, [I,Copy|Acc]);
+ [_,_]=Args0 ->
+ sanitize_is(Is, Count0, Values, Changed, [I0|Acc]);
+ [_,_]=Args ->
+ I = I0#b_set{args=Args},
+ sanitize_is(Is, Count0, Values, Changed, [I|Acc])
+ end;
+sanitize_is([#b_set{op=Op,dst=Dst,args=Args0}=I0|Is0],
+ Count, Values, Changed0, Acc) ->
+ Args = sanitize_args(Args0, Values),
+ case sanitize_instr(Op, Args, I0) of
+ {value,Value0} ->
+ Value = #b_literal{val=Value0},
+ sanitize_is(Is0, Count, Values#{Dst=>Value}, true, Acc);
+ {ok,I} ->
+ sanitize_is(Is0, Count, Values, true, [I|Acc]);
+ ok ->
+ I = I0#b_set{args=Args},
+ Changed = Changed0 orelse Args =/= Args0,
+ sanitize_is(Is0, Count, Values, Changed, [I|Acc])
+ end;
+sanitize_is([], Count, Values, Changed, Acc) ->
+ case Changed of
+ true ->
+ {reverse(Acc),Count,Values};
+ false ->
+ no_change
+ end.
+
+sanitize_args(Args, Values) ->
+ map(fun(Var) ->
+ case Values of
+ #{Var:=New} -> New;
+ #{} -> Var
+ end
+ end, Args).
+
+sanitize_instr({bif,Bif}, [#b_literal{val=Lit}], _I) ->
+ case erl_bifs:is_pure(erlang, Bif, 1) of
+ false ->
+ ok;
+ true ->
+ try
+ {value,erlang:Bif(Lit)}
+ catch
+ error:_ ->
+ ok
+ end
+ end;
+sanitize_instr({bif,Bif}, [#b_literal{val=Lit1},#b_literal{val=Lit2}], _I) ->
+ true = erl_bifs:is_pure(erlang, Bif, 2), %Assertion.
+ try
+ {value,erlang:Bif(Lit1, Lit2)}
+ catch
+ error:_ ->
+ ok
+ end;
+sanitize_instr(get_hd, [#b_literal{val=[Hd|_]}], _I) ->
+ {value,Hd};
+sanitize_instr(get_tl, [#b_literal{val=[_|Tl]}], _I) ->
+ {value,Tl};
+sanitize_instr(get_tuple_element, [#b_literal{val=T},
+ #b_literal{val=I}], _I)
+ when I < tuple_size(T) ->
+ {value,element(I+1, T)};
+sanitize_instr(is_nonempty_list, [#b_literal{val=Lit}], _I) ->
+ {value,case Lit of
+ [_|_] -> true;
+ _ -> false
+ end};
+sanitize_instr(is_tagged_tuple, [#b_literal{val=Tuple},
+ #b_literal{val=Arity},
+ #b_literal{val=Tag}], _I)
+ when is_integer(Arity), is_atom(Tag) ->
+ if
+ tuple_size(Tuple) =:= Arity, element(1, Tuple) =:= Tag ->
+ {value,true};
+ true ->
+ {value,false}
+ end;
+sanitize_instr(bs_init, [#b_literal{val=new},#b_literal{val=Sz}|_], I0) ->
+ if
+ is_integer(Sz), Sz >= 0 -> ok;
+ true -> {ok,sanitize_badarg(I0)}
+ end;
+sanitize_instr(bs_init, [#b_literal{val=append},_,#b_literal{val=Sz}|_], I0) ->
+ if
+ is_integer(Sz), Sz >= 0 -> ok;
+ true -> {ok,sanitize_badarg(I0)}
+ end;
+sanitize_instr(succeeded, [#b_literal{}], _I) ->
+ {value,true};
+sanitize_instr(_, _, _) -> ok.
+
+sanitize_badarg(I) ->
+ Func = #b_remote{mod=#b_literal{val=erlang},
+ name=#b_literal{val=error},arity=1},
+ I#b_set{op=call,args=[Func,#b_literal{val=badarg}]}.
+
+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}.
+
+%%%
+%%% Fix tuples.
+%%%
+
+%% fix_tuples(St0) -> St.
+%% If compatibility with a previous version of Erlang has been
+%% requested, tuple creation must be split into two instruction to
+%% mirror the the way tuples are created in BEAM prior to OTP 22.
+%% Each put_tuple instruction is split into put_tuple_arity followed
+%% by put_tuple_elements.
+
+fix_tuples(#st{ssa=Blocks0,cnt=Count0}=St) ->
+ F = fun (#b_set{op=put_tuple,args=Args}=Put, C0) ->
+ Arity = #b_literal{val=length(Args)},
+ {Ignore,C} = new_var('@ssa_ignore', C0),
+ {[Put#b_set{op=put_tuple_arity,args=[Arity]},
+ #b_set{dst=Ignore,op=put_tuple_elements,args=Args}],C};
+ (I, C) -> {[I],C}
+ end,
+ {Blocks,Count} = beam_ssa:flatmapfold_instrs_rpo(F, [0], Count0, Blocks0),
+ St#st{ssa=Blocks,cnt=Count}.
+
+%%%
+%%% Introduce the set_tuple_element instructions to make
+%%% multiple-field record updates faster.
+%%%
+%%% 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, Blocks0) ->
+ {Blk0,Blocks} = use_ste_across(L, Uses, Blocks0),
+ #b_blk{is=Is0} = Blk0,
+ 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.
+
+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)
+ end;
+use_ste_is([], _Uses) -> [].
+
+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.
+
+%%% Optimize accross blocks within a try/catch block.
+
+use_ste_across(L, Uses, Blocks) ->
+ case map_get(L, Blocks) of
+ #b_blk{last=#b_br{bool=#b_var{}}}=Blk ->
+ try
+ use_ste_across_1(L, Blk, Uses, Blocks)
+ catch
+ throw:not_possible ->
+ {Blk,Blocks}
+ end;
+ #b_blk{}=Blk ->
+ {Blk,Blocks}
+ end.
+
+use_ste_across_1(L, Blk0, Uses, Blocks0) ->
+ #b_blk{is=IsThis,last=#b_br{bool=Bool,succ=Next}} = Blk0,
+ case reverse(IsThis) of
+ [#b_set{op=succeeded,dst=Bool,args=[Result]}=Succ0,
+ #b_set{op=call,args=[#b_remote{}|_],dst=Result}=Call1|Prefix] ->
+ case is_single_use(Bool, Uses) andalso
+ is_n_uses(2, Result, Uses) of
+ true -> ok;
+ false -> throw(not_possible)
+ end,
+ Call2 = use_ste_across_next(Next, Uses, Blocks0),
+ Is = [Call1,Call2],
+ case use_ste_is(Is, decrement_uses(Result, Uses)) of
+ [#b_set{}=Call,#b_set{op=set_tuple_element}=Ste] ->
+ Blocks1 = use_ste_fix_next(Ste, Next, Blocks0),
+ Succ = Succ0#b_set{args=[Call#b_set.dst]},
+ Blk = Blk0#b_blk{is=reverse(Prefix, [Call,Succ])},
+ Blocks = Blocks1#{L:=Blk},
+ {Blk,Blocks};
+ _ ->
+ throw(not_possible)
+ end;
+ _ ->
+ throw(not_possible)
+ end.
+
+use_ste_across_next(Next, Uses, Blocks) ->
+ case map_get(Next, Blocks) of
+ #b_blk{is=[#b_set{op=call,dst=Result,args=[#b_remote{}|_]}=Call,
+ #b_set{op=succeeded,dst=Bool,args=[Result]}],
+ last=#b_br{bool=Bool}} ->
+ case is_single_use(Bool, Uses) andalso
+ is_n_uses(2, Result, Uses) of
+ true -> ok;
+ false -> throw(not_possible)
+ end,
+ Call;
+ #b_blk{} ->
+ throw(not_possible)
+ end.
+
+use_ste_fix_next(Ste, Next, Blocks) ->
+ Blk0 = map_get(Next, Blocks),
+ #b_blk{is=[#b_set{op=call},#b_set{op=succeeded}],last=Br0} = Blk0,
+ Br = beam_ssa:normalize(Br0#b_br{bool=#b_literal{val=true}}),
+ Blk = Blk0#b_blk{is=[Ste],last=Br},
+ Blocks#{Next:=Blk}.
+
+%% 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:=3} -> 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.
+
+decrement_uses(V, Uses) ->
+ #{V:=C} = Uses,
+ Uses#{V:=C-1}.
+
+is_n_uses(N, V, Uses) ->
+ case Uses of
+ #{V:=N} -> true;
+ #{} -> false
+ end.
+
+is_single_use(V, Uses) ->
+ case Uses of
+ #{V:=1} -> true;
+ #{} -> false
+ end.
+
+%%%
+%%% Find out where frames should be placed.
+%%%
+
+%% place_frames(St0) -> St.
+%% Return a list of the labels for the blocks that need stack frame
+%% allocation instructions.
+%%
+%% This function attempts to place stack frames as tight as possible
+%% around the code, to avoid building stack frames for code paths
+%% that don't need one.
+%%
+%% Stack frames are placed in blocks that dominate all of their
+%% descendants. That guarantees that the deallocation instructions
+%% cannot be reached from other execution paths that didn't set up
+%% a stack frame or set up a stack frame with a different size.
+
+place_frames(#st{ssa=Blocks}=St) ->
+ {Doms,_} = beam_ssa:dominators(Blocks),
+ Ls = beam_ssa:rpo(Blocks),
+ Tried = gb_sets:empty(),
+ Frames0 = [],
+ {Frames,_} = place_frames_1(Ls, Blocks, Doms, Tried, Frames0),
+ St#st{frames=Frames}.
+
+place_frames_1([L|Ls], Blocks, Doms, Tried0, Frames0) ->
+ Blk = map_get(L, Blocks),
+ case need_frame(Blk) of
+ true ->
+ %% This block needs a frame. Try to place it here.
+ {Frames,Tried} = do_place_frame(L, Blocks, Doms, Tried0, Frames0),
+
+ %% Successfully placed. Try to place more frames in descendants
+ %% that are not dominated by this block.
+ place_frames_1(Ls, Blocks, Doms, Tried, Frames);
+ false ->
+ try
+ place_frames_1(Ls, Blocks, Doms, Tried0, Frames0)
+ catch
+ throw:{need_frame,For,Tried1}=Reason ->
+ %% An descendant block needs a stack frame. Try to
+ %% place it here.
+ case is_dominated_by(For, L, Doms) of
+ true ->
+ %% Try to place a frame here.
+ {Frames,Tried} = do_place_frame(L, Blocks, Doms,
+ Tried1, Frames0),
+ place_frames_1(Ls, Blocks, Doms, Tried, Frames);
+ false ->
+ %% Wrong place. This block does not dominate
+ %% the block that needs the frame. Pass it on
+ %% to our ancestors.
+ throw(Reason)
+ end
+ end
+ end;
+place_frames_1([], _, _, Tried, Frames) ->
+ {Frames,Tried}.
+
+%% do_place_frame(Label, Blocks, Dominators, Tried0, Frames0) -> {Frames,Tried}.
+%% Try to place a frame in this block. This function returns
+%% successfully if it either succeds at placing a frame in this
+%% block, if an ancestor that dominates this block has already placed
+%% a frame, or if we have already tried to put a frame in this block.
+%%
+%% An {need_frame,Label,Tried} exception will be thrown if this block
+%% block is not suitable for having a stack frame (i.e. it does not dominate
+%% all of its descendants). The exception means that an ancestor will have to
+%% place the frame needed by this block.
+
+do_place_frame(L, Blocks, Doms, Tried0, Frames) ->
+ case gb_sets:is_element(L, Tried0) of
+ true ->
+ %% We have already tried to put a frame in this block.
+ {Frames,Tried0};
+ false ->
+ %% Try to place a frame in this block.
+ Tried = gb_sets:insert(L, Tried0),
+ case place_frame_here(L, Blocks, Doms, Frames) of
+ yes ->
+ %% We need a frame and it is safe to place it here.
+ {[L|Frames],Tried};
+ no ->
+ %% An ancestor has a frame. Not needed.
+ {Frames,Tried};
+ ancestor ->
+ %% This block does not dominate all of its
+ %% descendants. We must place the frame in
+ %% an ancestor.
+ throw({need_frame,L,Tried})
+ end
+ end.
+
+%% place_frame_here(Label, Blocks, Doms, Frames) -> no|yes|ancestor.
+%% Determine whether a frame should be placed in block Label.
+
+place_frame_here(L, Blocks, Doms, Frames) ->
+ B0 = any(fun(DomBy) ->
+ is_dominated_by(L, DomBy, Doms)
+ end, Frames),
+ case B0 of
+ true ->
+ %% This block is dominated by an ancestor block that
+ %% defines a frame. Not needed/allowed to put a frame
+ %% here.
+ no;
+ false ->
+ %% No frame in any ancestor. We need a frame.
+ %% Now check whether the frame can be placed here.
+ %% If this block dominates all of its descendants
+ %% and the predecessors of any phi nodes it can be
+ %% placed here.
+ Descendants = beam_ssa:rpo([L], Blocks),
+ PhiPredecessors = phi_predecessors(L, Blocks),
+ MustDominate = ordsets:from_list(PhiPredecessors ++ Descendants),
+ Dominates = all(fun(?BADARG_BLOCK) ->
+ %% This block defines no variables and calls
+ %% erlang:error(badarg). It does not matter
+ %% whether L dominates ?BADARG_BLOCK or not;
+ %% it is still safe to put the frame in L.
+ true;
+ (Bl) ->
+ is_dominated_by(Bl, L, Doms)
+ end, MustDominate),
+
+ %% Also, this block must not be a loop header.
+ IsLoopHeader = is_loop_header(L, Blocks),
+ case Dominates andalso not IsLoopHeader of
+ true -> yes;
+ false -> ancestor
+ end
+ end.
+
+%% phi_predecessors(Label, Blocks) ->
+%% Return all predecessors referenced in phi nodes.
+
+phi_predecessors(L, Blocks) ->
+ #b_blk{is=Is} = map_get(L, Blocks),
+ [P || #b_set{op=phi,args=Args} <- Is, {_,P} <- Args].
+
+%% is_dominated_by(Label, DominatedBy, Dominators) -> true|false.
+%% Test whether block Label is dominated by block DominatedBy.
+
+is_dominated_by(L, DomBy, Doms) ->
+ DominatedBy = map_get(L, Doms),
+ member(DomBy, DominatedBy).
+
+%% need_frame(#b_blk{}) -> true|false.
+%% Test whether any of the instructions in the block requires a stack frame.
+
+need_frame(#b_blk{is=Is,last=#b_ret{arg=Ret}}) ->
+ need_frame_1(Is, {return,Ret});
+need_frame(#b_blk{is=Is}) ->
+ need_frame_1(Is, body).
+
+need_frame_1([#b_set{op=make_fun,dst=Fun}|Is], {return,_}=Context) ->
+ %% Since make_fun clobbers X registers, a stack frame is needed if
+ %% any of the following instructions use any other variable than
+ %% the one holding the reference to the created fun.
+ need_frame_1(Is, Context) orelse
+ case beam_ssa:used(#b_blk{is=Is,last=#b_ret{arg=Fun}}) of
+ [Fun] -> false;
+ [_|_] -> true
+ end;
+need_frame_1([#b_set{op=new_try_tag}|_], _) ->
+ true;
+need_frame_1([#b_set{op=call,dst=Val}]=Is, {return,Ret}) ->
+ if
+ Val =:= Ret -> need_frame_1(Is, tail);
+ true -> need_frame_1(Is, body)
+ end;
+need_frame_1([#b_set{op=call,args=[Func|_]}|Is], Context) ->
+ case Func of
+ #b_remote{mod=#b_literal{val=Mod},
+ name=#b_literal{val=Name},
+ arity=Arity} when is_atom(Mod), is_atom(Name) ->
+ case erl_bifs:is_exit_bif(Mod, Name, Arity) of
+ true ->
+ false;
+ false ->
+ Context =:= body orelse
+ Is =/= [] orelse
+ is_trap_bif(Mod, Name, Arity)
+ end;
+ #b_remote{} ->
+ %% This is an apply(), which always needs a frame.
+ true;
+ #b_local{} ->
+ Context =:= body orelse Is =/= [];
+ _ ->
+ %% A fun call always needs a frame.
+ true
+ end;
+need_frame_1([I|Is], Context) ->
+ beam_ssa:clobbers_xregs(I) orelse need_frame_1(Is, Context);
+need_frame_1([], _) -> false.
+
+%% is_trap_bif(Mod, Name, Arity) -> true|false.
+%% Test whether we need a stack frame for this BIF.
+
+is_trap_bif(erlang, '!', 2) -> true;
+is_trap_bif(erlang, link, 1) -> true;
+is_trap_bif(erlang, unlink, 1) -> true;
+is_trap_bif(erlang, monitor_node, 2) -> true;
+is_trap_bif(erlang, group_leader, 2) -> true;
+is_trap_bif(erlang, exit, 2) -> true;
+is_trap_bif(_, _, _) -> false.
+
+%%%
+%%% Fix variables used in matching in receive.
+%%%
+%%% The loop_rec/2 instruction may return a reference to a
+%%% message outside of any heap or heap fragment. If the message
+%%% does not match, it is not allowed to store any reference to
+%%% the message (or part of the message) on the stack. If we do,
+%%% the message will be corrupted if there happens to be a GC.
+%%%
+%%% Here we make sure to introduce copies of variables that are
+%%% matched out and subsequently used after the remove_message/0
+%%% instructions. That will make sure that only X registers are
+%%% used during matching.
+%%%
+%%% Depending on where variables are defined and used, they must
+%%% be handled in two different ways.
+%%%
+%%% Variables that are always defined in the receive (before branching
+%%% out into the different clauses of the receive) and used after the
+%%% receive must be handled in the following way: Before each
+%%% remove_message instruction, each such variable must be copied, and
+%%% all variables must be consolidated using a phi node in the
+%%% common exit block for the receive.
+%%%
+%%% Variables that are matched out and used in the same clause
+%%% need copy instructions before the remove_message instruction
+%%% in that clause.
+%%%
+
+fix_receives(#st{ssa=Blocks0,cnt=Count0}=St) ->
+ {Blocks,Count} = fix_receives_1(maps:to_list(Blocks0),
+ Blocks0, Count0),
+ St#st{ssa=Blocks,cnt=Count}.
+
+fix_receives_1([{L,Blk}|Ls], Blocks0, Count0) ->
+ case Blk of
+ #b_blk{is=[#b_set{op=peek_message}|_]} ->
+ Rm = find_rm_blocks(L, Blocks0),
+ LoopExit = find_loop_exit(Rm, Blocks0),
+ Defs0 = beam_ssa:def([L], Blocks0),
+ CommonUsed = recv_common(Defs0, LoopExit, Blocks0),
+ {Blocks1,Count1} = recv_fix_common(CommonUsed, LoopExit, Rm,
+ Blocks0, Count0),
+ Defs = ordsets:subtract(Defs0, CommonUsed),
+ {Blocks,Count} = fix_receive(Rm, Defs, Blocks1, Count1),
+ fix_receives_1(Ls, Blocks, Count);
+ #b_blk{} ->
+ fix_receives_1(Ls, Blocks0, Count0)
+ end;
+fix_receives_1([], Blocks, Count) ->
+ {Blocks,Count}.
+
+recv_common(_Defs, none, _Blocks) ->
+ %% There is no common exit block because receive is used
+ %% in the tail position of a function.
+ [];
+recv_common(Defs, Exit, Blocks) ->
+ {ExitDefs,ExitUsed} = beam_ssa:def_used([Exit], Blocks),
+ Def = ordsets:subtract(Defs, ExitDefs),
+ ordsets:intersection(Def, ExitUsed).
+
+%% recv_fix_common([CommonVar], LoopExit, [RemoveMessageLabel],
+%% Blocks0, Count0) -> {Blocks,Count}.
+%% Handle variables alwys defined in a receive and used
+%% in the exit block following the receive.
+
+recv_fix_common([Msg0|T], Exit, Rm, Blocks0, Count0) ->
+ {Msg,Count1} = new_var('@recv', Count0),
+ Blocks1 = beam_ssa:rename_vars(#{Msg0=>Msg}, [Exit], Blocks0),
+ N = length(Rm),
+ {MsgVars,Count} = new_vars(duplicate(N, '@recv'), Count1),
+ PhiArgs = fix_exit_phi_args(MsgVars, Rm, Exit, Blocks1),
+ Phi = #b_set{op=phi,dst=Msg,args=PhiArgs},
+ ExitBlk0 = map_get(Exit, Blocks1),
+ ExitBlk = ExitBlk0#b_blk{is=[Phi|ExitBlk0#b_blk.is]},
+ Blocks2 = Blocks1#{Exit:=ExitBlk},
+ Blocks = recv_fix_common_1(MsgVars, Rm, Msg0, Blocks2),
+ recv_fix_common(T, Exit, Rm, Blocks, Count);
+recv_fix_common([], _, _, Blocks, Count) ->
+ {Blocks,Count}.
+
+recv_fix_common_1([V|Vs], [Rm|Rms], Msg, Blocks0) ->
+ Ren = #{Msg=>V},
+ Blocks1 = beam_ssa:rename_vars(Ren, [Rm], Blocks0),
+ #b_blk{is=Is0} = Blk0 = map_get(Rm, Blocks1),
+ Copy = #b_set{op=copy,dst=V,args=[Msg]},
+ Is = insert_after_phis(Is0, [Copy]),
+ Blk = Blk0#b_blk{is=Is},
+ Blocks = Blocks1#{Rm:=Blk},
+ recv_fix_common_1(Vs, Rms, Msg, Blocks);
+recv_fix_common_1([], [], _Msg, Blocks) -> Blocks.
+
+fix_exit_phi_args([V|Vs], [Rm|Rms], Exit, Blocks) ->
+ Path = beam_ssa:rpo([Rm], Blocks),
+ Preds = exit_predecessors(Path, Exit, Blocks),
+ [{V,Pred} || Pred <- Preds] ++ fix_exit_phi_args(Vs, Rms, Exit, Blocks);
+fix_exit_phi_args([], [], _, _) -> [].
+
+exit_predecessors([L|Ls], Exit, Blocks) ->
+ Blk = map_get(L, Blocks),
+ case member(Exit, beam_ssa:successors(Blk)) of
+ true ->
+ [L|exit_predecessors(Ls, Exit, Blocks)];
+ false ->
+ exit_predecessors(Ls, Exit, Blocks)
+ end;
+exit_predecessors([], _Exit, _Blocks) -> [].
+
+%% fix_receive([Label], Defs, Blocks0, Count0) -> {Blocks,Count}.
+%% Add a copy instruction for all variables that are matched out and
+%% later used within a clause of the receive.
+
+fix_receive([L|Ls], Defs, Blocks0, Count0) ->
+ {RmDefs,Used0} = beam_ssa:def_used([L], Blocks0),
+ Def = ordsets:subtract(Defs, RmDefs),
+ Used = ordsets:intersection(Def, Used0),
+ {NewVars,Count} = new_vars([Base || #b_var{name=Base} <- Used], Count0),
+ Ren = zip(Used, NewVars),
+ Blocks1 = beam_ssa:rename_vars(Ren, [L], Blocks0),
+ #b_blk{is=Is0} = Blk1 = map_get(L, Blocks1),
+ CopyIs = [#b_set{op=copy,dst=New,args=[Old]} || {Old,New} <- Ren],
+ Is = insert_after_phis(Is0, CopyIs),
+ Blk = Blk1#b_blk{is=Is},
+ Blocks = Blocks1#{L:=Blk},
+ fix_receive(Ls, Defs, Blocks, Count);
+fix_receive([], _Defs, Blocks, Count) ->
+ {Blocks,Count}.
+
+%% find_loop_exit([Label], Blocks) -> Label | none.
+%% Find the block to which control is transferred when the
+%% the receive loop is exited.
+
+find_loop_exit([L1,L2|_Ls], Blocks) ->
+ Path1 = beam_ssa:rpo([L1], Blocks),
+ Path2 = beam_ssa:rpo([L2], Blocks),
+ find_loop_exit_1(reverse(Path1), reverse(Path2), none);
+find_loop_exit(_, _) -> none.
+
+find_loop_exit_1([H|T1], [H|T2], _) ->
+ find_loop_exit_1(T1, T2, H);
+find_loop_exit_1(_, _, Exit) -> Exit.
+
+%% find_rm_blocks(StartLabel, Blocks) -> [Label].
+%% Find all blocks that start with remove_message within the receive
+%% loop whose peek_message label is StartLabel.
+
+find_rm_blocks(L, Blocks) ->
+ Seen = gb_sets:singleton(L),
+ Blk = map_get(L, Blocks),
+ Succ = beam_ssa:successors(Blk),
+ find_rm_blocks_1(Succ, Seen, Blocks).
+
+find_rm_blocks_1([L|Ls], Seen0, Blocks) ->
+ case gb_sets:is_member(L, Seen0) of
+ true ->
+ find_rm_blocks_1(Ls, Seen0, Blocks);
+ false ->
+ Seen = gb_sets:insert(L, Seen0),
+ Blk = map_get(L, Blocks),
+ case find_rm_act(Blk#b_blk.is) of
+ prune ->
+ %% Looping back. Don't look at any successors.
+ find_rm_blocks_1(Ls, Seen, Blocks);
+ continue ->
+ %% Neutral block. Do nothing here, but look at
+ %% all successors.
+ Succ = beam_ssa:successors(Blk),
+ find_rm_blocks_1(Succ++Ls, Seen, Blocks);
+ found ->
+ %% Found remove_message instruction.
+ [L|find_rm_blocks_1(Ls, Seen, Blocks)]
+ end
+ end;
+find_rm_blocks_1([], _, _) -> [].
+
+find_rm_act([#b_set{op=Op}|Is]) ->
+ case Op of
+ remove_message -> found;
+ peek_message -> prune;
+ recv_next -> prune;
+ wait_timeout -> prune;
+ wait -> prune;
+ _ -> find_rm_act(Is)
+ end;
+find_rm_act([]) ->
+ continue.
+
+%%%
+%%% Find out which variables need to be stored in Y registers.
+%%%
+
+-record(dk, {d :: ordsets:ordset(var_name()),
+ k :: ordsets:ordset(var_name())
+ }).
+
+%% find_yregs(St0) -> St.
+%% Find all variables that must be stored in Y registers. Annotate
+%% the blocks that allocate frames with the set of Y registers
+%% used within that stack frame.
+%%
+%% Basically, we following all execution paths starting from a block
+%% that allocates a frame, keeping track of of all defined registers
+%% and all registers killed by an instruction that clobbers X
+%% registers. For every use of a variable, we check if if it is in
+%% the set of killed variables; if it is, it must be stored in an Y
+%% register.
+
+find_yregs(#st{frames=[]}=St) ->
+ St;
+find_yregs(#st{frames=[_|_]=Frames,args=Args,ssa=Blocks0}=St) ->
+ FrameDefs = find_defs(Frames, Blocks0, [V || #b_var{}=V <- Args]),
+ Blocks = find_yregs_1(FrameDefs, Blocks0),
+ St#st{ssa=Blocks}.
+
+find_yregs_1([{F,Defs}|Fs], Blocks0) ->
+ DK = #dk{d=Defs,k=[]},
+ D0 = #{F=>DK},
+ Ls = beam_ssa:rpo([F], Blocks0),
+ Yregs0 = [],
+ Yregs = find_yregs_2(Ls, Blocks0, D0, Yregs0),
+ Blk0 = map_get(F, Blocks0),
+ Blk = beam_ssa:add_anno(yregs, Yregs, Blk0),
+ Blocks = Blocks0#{F:=Blk},
+ find_yregs_1(Fs, Blocks);
+find_yregs_1([], Blocks) -> Blocks.
+
+find_yregs_2([L|Ls], Blocks0, D0, Yregs0) ->
+ Blk0 = map_get(L, Blocks0),
+ #b_blk{is=Is,last=Last} = Blk0,
+ Ys0 = map_get(L, D0),
+ {Yregs1,Ys} = find_yregs_is(Is, Ys0, Yregs0),
+ Yregs = find_yregs_terminator(Last, Ys, Yregs1),
+ Successors = beam_ssa:successors(Blk0),
+ D = find_update_succ(Successors, Ys, D0),
+ find_yregs_2(Ls, Blocks0, D, Yregs);
+find_yregs_2([], _Blocks, _D, Yregs) -> Yregs.
+
+find_defs(Frames, Blocks, Defs) ->
+ Seen = gb_sets:empty(),
+ FramesSet = gb_sets:from_list(Frames),
+ {FrameDefs,_} = find_defs_1([0], Blocks, FramesSet, Seen, Defs, []),
+ FrameDefs.
+
+find_defs_1([L|Ls], Blocks, Frames, Seen0, Defs0, Acc0) ->
+ case gb_sets:is_member(L, Frames) of
+ true ->
+ OrderedDefs = ordsets:from_list(Defs0),
+ find_defs_1(Ls, Blocks, Frames, Seen0, Defs0,
+ [{L,OrderedDefs}|Acc0]);
+ false ->
+ case gb_sets:is_member(L, Seen0) of
+ true ->
+ find_defs_1(Ls, Blocks, Frames, Seen0, Defs0, Acc0);
+ false ->
+ Seen1 = gb_sets:insert(L, Seen0),
+ {Acc,Seen} = find_defs_1(Ls, Blocks, Frames, Seen1, Defs0, Acc0),
+ #b_blk{is=Is} = Blk = map_get(L, Blocks),
+ Defs = find_defs_is(Is, Defs0),
+ Successors = beam_ssa:successors(Blk),
+ find_defs_1(Successors, Blocks, Frames, Seen, Defs, Acc)
+ end
+ end;
+find_defs_1([], _, _, Seen, _, Acc) ->
+ {Acc,Seen}.
+
+find_defs_is([#b_set{dst=Dst}|Is], Acc) ->
+ find_defs_is(Is, [Dst|Acc]);
+find_defs_is([], Acc) -> Acc.
+
+find_update_succ([S|Ss], #dk{d=Defs0,k=Killed0}=DK0, D0) ->
+ case D0 of
+ #{S:=#dk{d=Defs1,k=Killed1}} ->
+ Defs = ordsets:intersection(Defs0, Defs1),
+ Killed = ordsets:union(Killed0, Killed1),
+ DK = #dk{d=Defs,k=Killed},
+ D = D0#{S:=DK},
+ find_update_succ(Ss, DK0, D);
+ #{} ->
+ D = D0#{S=>DK0},
+ find_update_succ(Ss, DK0, D)
+ end;
+find_update_succ([], _, D) -> D.
+
+find_yregs_is([#b_set{dst=Dst}=I|Is], #dk{d=Defs0,k=Killed0}=Ys, Yregs0) ->
+ Used = beam_ssa:used(I),
+ Yregs1 = ordsets:intersection(Used, Killed0),
+ Yregs = ordsets:union(Yregs0, Yregs1),
+ case beam_ssa:clobbers_xregs(I) of
+ false ->
+ Defs = ordsets:add_element(Dst, Defs0),
+ find_yregs_is(Is, Ys#dk{d=Defs}, Yregs);
+ true ->
+ Killed = ordsets:union(Defs0, Killed0),
+ Defs = [Dst],
+ find_yregs_is(Is, Ys#dk{d=Defs,k=Killed}, Yregs)
+ end;
+find_yregs_is([], Ys, Yregs) -> {Yregs,Ys}.
+
+find_yregs_terminator(Terminator, #dk{k=Killed}, Yregs0) ->
+ Used = beam_ssa:used(Terminator),
+ Yregs = ordsets:intersection(Used, Killed),
+ ordsets:union(Yregs0, Yregs).
+
+%%%
+%%% Try to reduce the size of the stack frame, by adding an explicit
+%%% 'copy' instructions for return values from 'call' and 'make_fun' that
+%%% need to be saved in Y registers. Here is an example to show
+%%% how that's useful. First, here is the Erlang code:
+%%%
+%%% f(Pid) ->
+%%% Res = foo(42),
+%%% _ = node(Pid),
+%%% bar(),
+%%% Res.
+%%%
+%%% Compiled to SSA format, the main part of the code looks like this:
+%%%
+%%% 0:
+%%% Res = call local literal foo/1, literal 42
+%%% _1 = bif:node Pid
+%%% @ssa_bool = succeeded _1
+%%% br @ssa_bool, label 3, label 1
+%%% 3:
+%%% @ssa_ignored = call local literal bar/0
+%%% ret Res
+%%%
+%%% It can be seen that the variables Pid and Res must be saved in Y
+%%% registers in order to survive the function calls. A previous sub
+%%% pass has inserted a 'copy' instruction to save the value of the
+%%% variable Pid:
+%%%
+%%% 0:
+%%% Pid:4 = copy Pid
+%%% Res = call local literal foo/1, literal 42
+%%% _1 = bif:node Pid:4
+%%% @ssa_bool = succeeded _1
+%%% br @ssa_bool, label 3, label 1
+%%%
+%%% 3:
+%%% @ssa_ignored = call local literal bar/0
+%%% ret Res
+%%%
+%%% The Res and Pid:4 variables must be assigned to different Y registers
+%%% because they are live at the same time. copy_retval() inserts a
+%%% 'copy' instruction to copy Res to a new variable:
+%%%
+%%% 0:
+%%% Pid:4 = copy Pid
+%%% Res:6 = call local literal foo/1, literal 42
+%%% _1 = bif:node Pid:4
+%%% @ssa_bool = succeeded _1
+%%% br @ssa_bool, label 3, label 1
+%%%
+%%% 3:
+%%% Res = copy Res:6
+%%% @ssa_ignored = call local literal bar/0
+%%% ret Res
+%%%
+%%% The new variable Res:6 is used to capture the return value from the call.
+%%% The variables Pid:4 and Res are no longer live at the same time, so they
+%%% can be assigned to the same Y register.
+%%%
+
+copy_retval(#st{frames=Frames,ssa=Blocks0,cnt=Count0}=St) ->
+ {Blocks,Count} = copy_retval_1(Frames, Blocks0, Count0),
+ St#st{ssa=Blocks,cnt=Count}.
+
+copy_retval_1([F|Fs], Blocks0, Count0) ->
+ #b_blk{anno=#{yregs:=Yregs0},is=Is} = map_get(F, Blocks0),
+ Yregs1 = gb_sets:from_list(Yregs0),
+ Yregs = collect_yregs(Is, Yregs1),
+ Ls = beam_ssa:rpo([F], Blocks0),
+ {Blocks,Count} = copy_retval_2(Ls, Yregs, none, Blocks0, Count0),
+ copy_retval_1(Fs, Blocks, Count);
+copy_retval_1([], Blocks, Count) ->
+ {Blocks,Count}.
+
+collect_yregs([#b_set{op=copy,dst=Y,args=[#b_var{}=X]}|Is],
+ Yregs0) ->
+ true = gb_sets:is_member(X, Yregs0), %Assertion.
+ Yregs = gb_sets:insert(Y, gb_sets:delete(X, Yregs0)),
+ collect_yregs(Is, Yregs);
+collect_yregs([#b_set{}|Is], Yregs) ->
+ collect_yregs(Is, Yregs);
+collect_yregs([], Yregs) -> Yregs.
+
+copy_retval_2([L|Ls], Yregs, Copy0, Blocks0, Count0) ->
+ #b_blk{is=Is0,last=Last} = Blk = map_get(L, Blocks0),
+ RC = case {Last,Ls} of
+ {#b_br{succ=Succ,fail=?BADARG_BLOCK},[Succ|_]} ->
+ true;
+ {_,_} ->
+ false
+ end,
+ case copy_retval_is(Is0, RC, Yregs, Copy0, Count0, []) of
+ {Is,Count} ->
+ case Copy0 =:= none andalso Count0 =:= Count of
+ true ->
+ copy_retval_2(Ls, Yregs, none, Blocks0, Count0);
+ false ->
+ Blocks = Blocks0#{L=>Blk#b_blk{is=Is}},
+ copy_retval_2(Ls, Yregs, none, Blocks, Count)
+ end;
+ {Is,Count,Copy} ->
+ Blocks = Blocks0#{L=>Blk#b_blk{is=Is}},
+ copy_retval_2(Ls, Yregs, Copy, Blocks, Count)
+ end;
+copy_retval_2([], _Yregs, none, Blocks, Count) ->
+ {Blocks,Count}.
+
+copy_retval_is([#b_set{op=put_tuple_elements,args=Args0}=I0], false, _Yregs,
+ Copy, Count, Acc) ->
+ I = I0#b_set{args=copy_sub_args(Args0, Copy)},
+ {reverse(Acc, [I|acc_copy([], Copy)]),Count};
+copy_retval_is([#b_set{op=Op}=I0], false, Yregs, Copy, Count0, Acc0)
+ when Op =:= call; Op =:= make_fun ->
+ {I,Count,Acc} = place_retval_copy(I0, Yregs, Copy, Count0, Acc0),
+ {reverse(Acc, [I]),Count};
+copy_retval_is([#b_set{}]=Is, false, _Yregs, Copy, Count, Acc) ->
+ {reverse(Acc, acc_copy(Is, Copy)),Count};
+copy_retval_is([#b_set{},#b_set{op=succeeded}]=Is, false, _Yregs, Copy, Count, Acc) ->
+ {reverse(Acc, acc_copy(Is, Copy)),Count};
+copy_retval_is([#b_set{op=Op,dst=#b_var{name=RetName}=Dst}=I0|Is], RC, Yregs,
+ Copy0, Count0, Acc0) when Op =:= call; Op =:= make_fun ->
+ {I1,Count1,Acc} = place_retval_copy(I0, Yregs, Copy0, Count0, Acc0),
+ case gb_sets:is_member(Dst, Yregs) of
+ true ->
+ {NewVar,Count} = new_var(RetName, Count1),
+ Copy = #b_set{op=copy,dst=Dst,args=[NewVar]},
+ I = I1#b_set{dst=NewVar},
+ copy_retval_is(Is, RC, Yregs, Copy, Count, [I|Acc]);
+ false ->
+ copy_retval_is(Is, RC, Yregs, none, Count1, [I1|Acc])
+ end;
+copy_retval_is([#b_set{args=Args0}=I0|Is], RC, Yregs, Copy, Count, Acc) ->
+ I = I0#b_set{args=copy_sub_args(Args0, Copy)},
+ case beam_ssa:clobbers_xregs(I) of
+ true ->
+ copy_retval_is(Is, RC, Yregs, none, Count, [I|acc_copy(Acc, Copy)]);
+ false ->
+ copy_retval_is(Is, RC, Yregs, Copy, Count, [I|Acc])
+ end;
+copy_retval_is([], RC, _, Copy, Count, Acc) ->
+ case {Copy,RC} of
+ {none,_} ->
+ {reverse(Acc),Count};
+ {#b_set{},true} ->
+ {reverse(Acc),Count,Copy};
+ {#b_set{},false} ->
+ {reverse(Acc, [Copy]),Count}
+ end.
+
+%%
+%% Consider this code:
+%%
+%% Var = ...
+%% ...
+%% A1 = call foo/0
+%% A = copy A1
+%% B = call bar/1, Var
+%%
+%% If the Var variable is no longer used after this code, its Y register
+%% can't be reused for A. To allow the Y register to be reused
+%% we will need to insert 'copy' instructions for arguments that are
+%% in Y registers:
+%%
+%% Var = ...
+%% ...
+%% A1 = call foo/0
+%% Var1 = copy Var
+%% A = copy A1
+%% B = call bar/1, Var1
+%%
+
+place_retval_copy(I, _Yregs, none, Count, Acc) ->
+ {I,Count,Acc};
+place_retval_copy(#b_set{args=[F|Args0]}=I, Yregs, Copy, Count0, Acc0) ->
+ #b_set{dst=Avoid} = Copy,
+ {Args,Acc1,Count} = copy_func_args(Args0, Yregs, Avoid, Acc0, [], Count0),
+ Acc = [Copy|Acc1],
+ {I#b_set{args=[F|Args]},Count,Acc}.
+
+copy_func_args([#b_var{name=AName}=A|As], Yregs, Avoid, CopyAcc, Acc, Count0) ->
+ case gb_sets:is_member(A, Yregs) of
+ true when A =/= Avoid ->
+ {NewVar,Count} = new_var(AName, Count0),
+ Copy = #b_set{op=copy,dst=NewVar,args=[A]},
+ copy_func_args(As, Yregs, Avoid, [Copy|CopyAcc], [NewVar|Acc], Count);
+ _ ->
+ copy_func_args(As, Yregs, Avoid, CopyAcc, [A|Acc], Count0)
+ end;
+copy_func_args([A|As], Yregs, Avoid, CopyAcc, Acc, Count) ->
+ copy_func_args(As, Yregs, Avoid, CopyAcc, [A|Acc], Count);
+copy_func_args([], _Yregs, _Avoid, CopyAcc, Acc, Count) ->
+ {reverse(Acc),CopyAcc,Count}.
+
+acc_copy(Acc, none) -> Acc;
+acc_copy(Acc, #b_set{}=Copy) -> [Copy|Acc].
+
+copy_sub_args(Args, none) ->
+ Args;
+copy_sub_args(Args, #b_set{dst=Dst,args=[Src]}) ->
+ [sub_arg(A, Dst, Src) || A <- Args].
+
+sub_arg(Old, Old, New) -> New;
+sub_arg(Old, _, _) -> Old.
+
+%%%
+%%% Consider:
+%%%
+%%% x1/Hd = get_hd x0/Cons
+%%% y0/Tl = get_tl x0/Cons
+%%%
+%%% Register x0 can't be reused for Hd. If Hd needs to be in x0,
+%%% a 'move' instruction must be inserted.
+%%%
+%%% If we swap get_hd and get_tl when Tl is in a Y register,
+%%% x0 can be used for Hd if Cons is not used again:
+%%%
+%%% y0/Tl = get_tl x0/Cons
+%%% x0/Hd = get_hd x0/Cons
+%%%
+
+opt_get_list(#st{ssa=Blocks,res=Res}=St) ->
+ ResMap = maps:from_list(Res),
+ Ls = beam_ssa:rpo(Blocks),
+ St#st{ssa=opt_get_list_1(Ls, ResMap, Blocks)}.
+
+opt_get_list_1([L|Ls], Res, Blocks0) ->
+ #b_blk{is=Is0} = Blk = map_get(L, Blocks0),
+ case opt_get_list_is(Is0, Res, [], false) of
+ no ->
+ opt_get_list_1(Ls, Res, Blocks0);
+ {yes,Is} ->
+ Blocks = Blocks0#{L:=Blk#b_blk{is=Is}},
+ opt_get_list_1(Ls, Res, Blocks)
+ end;
+opt_get_list_1([], _, Blocks) -> Blocks.
+
+opt_get_list_is([#b_set{op=get_hd,dst=Hd,
+ args=[Cons]}=GetHd,
+ #b_set{op=get_tl,dst=Tl,
+ args=[Cons]}=GetTl|Is],
+ Res, Acc, Changed) ->
+ %% Note that when this pass is run, only Y registers have
+ %% reservations. The absence of an entry for a variable therefore
+ %% means that the variable will be in an X register.
+ case Res of
+ #{Hd:={y,_}} ->
+ %% Hd will be in a Y register. Don't swap.
+ opt_get_list_is([GetTl|Is], Res, [GetHd|Acc], Changed);
+ #{Tl:={y,_}} ->
+ %% Tl will be in a Y register. Swap.
+ opt_get_list_is([GetHd|Is], Res, [GetTl|Acc], true);
+ #{} ->
+ %% Both are in X registers. Nothing to do.
+ opt_get_list_is([GetTl|Is], Res, [GetHd|Acc], Changed)
+ end;
+opt_get_list_is([I|Is], Res, Acc, Changed) ->
+ opt_get_list_is(Is, Res, [I|Acc], Changed);
+opt_get_list_is([], _Res, Acc, Changed) ->
+ case Changed of
+ true ->
+ {yes,reverse(Acc)};
+ false ->
+ no
+ end.
+
+%%%
+%%% Number instructions in the order they are executed.
+%%%
+
+%% number_instructions(St0) -> St.
+%% Number instructions in the order they are executed. Use a step
+%% size of 2. Don't number phi instructions. All phi variables in
+%% a block will be live one unit before the first non-phi instruction
+%% in the block.
+
+number_instructions(#st{ssa=Blocks0}=St) ->
+ Ls = beam_ssa:rpo(Blocks0),
+ St#st{ssa=number_is_1(Ls, 1, Blocks0)}.
+
+number_is_1([L|Ls], N0, Blocks0) ->
+ #b_blk{is=Is0,last=Last0} = Bl0 = map_get(L, Blocks0),
+ {Is,N1} = number_is_2(Is0, N0, []),
+ Last = beam_ssa:add_anno(n, N1, Last0),
+ N = N1 + 2,
+ Bl = Bl0#b_blk{is=Is,last=Last},
+ Blocks = Blocks0#{L:=Bl},
+ number_is_1(Ls, N, Blocks);
+number_is_1([], _, Blocks) -> Blocks.
+
+number_is_2([#b_set{op=phi}=I|Is], N, Acc) ->
+ number_is_2(Is, N, [I|Acc]);
+number_is_2([I0|Is], N, Acc) ->
+ I = beam_ssa:add_anno(n, N, I0),
+ number_is_2(Is, N+2, [I|Acc]);
+number_is_2([], N, Acc) ->
+ {reverse(Acc),N}.
+
+%%%
+%%% Calculate live intervals.
+%%%
+
+live_intervals(#st{args=Args,ssa=Blocks}=St) ->
+ Vars0 = [{V,{0,1}} || #b_var{}=V <- Args],
+ F = fun(L, _, A) -> live_interval_blk(L, Blocks, A) end,
+ LiveMap0 = #{},
+ Acc0 = {[],LiveMap0},
+ {Vars,_} = beam_ssa:fold_po(F, Acc0, Blocks),
+ Intervals = merge_ranges(rel2fam(Vars0++Vars)),
+ St#st{intervals=Intervals}.
+
+merge_ranges([{V,Rs}|T]) ->
+ [{V,merge_ranges_1(Rs)}|merge_ranges(T)];
+merge_ranges([]) -> [].
+
+merge_ranges_1([{A,N},{N,Z}|Rs]) ->
+ merge_ranges_1([{A,Z}|Rs]);
+merge_ranges_1([R|Rs]) ->
+ [R|merge_ranges_1(Rs)];
+merge_ranges_1([]) -> [].
+
+live_interval_blk(L, Blocks, {Vars0,LiveMap0}) ->
+ Live0 = [],
+ Successors = beam_ssa:successors(L, Blocks),
+ Live1 = update_successors(Successors, L, Blocks, LiveMap0, Live0),
+
+ %% Add ranges for all variables that are live in the successors.
+ #b_blk{is=Is,last=Last} = map_get(L, Blocks),
+ End = beam_ssa:get_anno(n, Last),
+ Use = [{V,{use,End+1}} || V <- Live1],
+
+ %% Determine used and defined variables in this block.
+ FirstNumber = first_number(Is, Last),
+ UseDef0 = live_interval_blk_1([Last|reverse(Is)], FirstNumber, Use),
+ UseDef = rel2fam(UseDef0),
+
+ %% Update what is live at the beginning of this block and
+ %% store it.
+ Used = [V || {V,[{use,_}|_]} <- UseDef],
+ Live2 = ordsets:union(Live1, Used),
+ Killed = [V || {V,[{def,_}|_]} <- UseDef],
+ Live = ordsets:subtract(Live2, Killed),
+ LiveMap = LiveMap0#{L=>Live},
+
+ %% Construct the ranges for this block.
+ Vars = make_block_ranges(UseDef, FirstNumber, Vars0),
+ {Vars,LiveMap}.
+
+make_block_ranges([{V,[{def,Def}]}|Vs], First, Acc) ->
+ make_block_ranges(Vs, First, [{V,{Def,Def}}|Acc]);
+make_block_ranges([{V,[{def,Def}|Uses]}|Vs], First, Acc) ->
+ {use,Last} = last(Uses),
+ make_block_ranges(Vs, First, [{V,{Def,Last}}|Acc]);
+make_block_ranges([{V,[{use,_}|_]=Uses}|Vs], First, Acc) ->
+ {use,Last} = last(Uses),
+ make_block_ranges(Vs, First, [{V,{First,Last}}|Acc]);
+make_block_ranges([], _, Acc) -> Acc.
+
+live_interval_blk_1([#b_set{op=phi,dst=Dst}|Is], FirstNumber, Acc0) ->
+ Acc = [{Dst,{def,FirstNumber}}|Acc0],
+ live_interval_blk_1(Is, FirstNumber, Acc);
+live_interval_blk_1([#b_set{op=bs_start_match}=I|Is],
+ FirstNumber, Acc0) ->
+ N = beam_ssa:get_anno(n, I),
+ #b_set{dst=Dst} = I,
+ Acc1 = [{Dst,{def,N}}|Acc0],
+ Acc = [{V,{use,N}} || V <- beam_ssa:used(I)] ++ Acc1,
+ live_interval_blk_1(Is, FirstNumber, Acc);
+live_interval_blk_1([I|Is], FirstNumber, Acc0) ->
+ N = beam_ssa:get_anno(n, I),
+ Acc1 = case I of
+ #b_set{dst=Dst} ->
+ [{Dst,{def,N}}|Acc0];
+ _ ->
+ Acc0
+ end,
+ Used = beam_ssa:used(I),
+ Acc = [{V,{use,N}} || V <- Used] ++ Acc1,
+ live_interval_blk_1(Is, FirstNumber, Acc);
+live_interval_blk_1([], _FirstNumber, Acc) ->
+ Acc.
+
+%% first_number([#b_set{}]) -> InstructionNumber.
+%% Return the number for the first instruction for the block.
+%% Note that this number is one less than the first
+%% non-phi instruction in the block.
+
+first_number([#b_set{op=phi}|Is], Last) ->
+ first_number(Is, Last);
+first_number([I|_], _) ->
+ beam_ssa:get_anno(n, I) - 1;
+first_number([], Last) ->
+ beam_ssa:get_anno(n, Last) - 1.
+
+update_successors([L|Ls], Pred, Blocks, LiveMap, Live0) ->
+ Live1 = ordsets:union(Live0, get_live(L, LiveMap)),
+ #b_blk{is=Is} = map_get(L, Blocks),
+ Live = update_live_phis(Is, Pred, Live1),
+ update_successors(Ls, Pred, Blocks, LiveMap, Live);
+update_successors([], _, _, _, Live) -> Live.
+
+get_live(L, LiveMap) ->
+ case LiveMap of
+ #{L:=Live} -> Live;
+ #{} -> []
+ end.
+
+update_live_phis([#b_set{op=phi,dst=Killed,args=Args}|Is],
+ Pred, Live0) ->
+ Used = [V || {#b_var{}=V,L} <- Args, L =:= Pred],
+ Live1 = ordsets:union(ordsets:from_list(Used), Live0),
+ Live = ordsets:del_element(Killed, Live1),
+ update_live_phis(Is, Pred, Live);
+update_live_phis(_, _, Live) -> Live.
+
+%%%
+%%% Reserve Y registers.
+%%%
+
+%% reserve_yregs(St0) -> St.
+%% In each block that allocates a stack frame, insert instructions
+%% that copy variables that must be in Y registers (given by
+%% the `yregs` annotation) to new variables.
+%%
+%% Also allocate specific Y registers for try and catch tags.
+%% The outermost try/catch tag is placed in y0, any directly
+%% nested tag in y1, and so on. Note that this is the reversed
+%% order as required by BEAM; it will be corrected later by
+%% turn_yregs().
+
+reserve_yregs(#st{frames=Frames}=St0) ->
+ foldl(fun reserve_yregs_1/2, St0, Frames).
+
+reserve_yregs_1(L, #st{ssa=Blocks0,cnt=Count0,res=Res0}=St) ->
+ Blk = map_get(L, Blocks0),
+ Yregs = beam_ssa:get_anno(yregs, Blk),
+ {Def,Used} = beam_ssa:def_used([L], Blocks0),
+ UsedYregs = ordsets:intersection(Yregs, Used),
+ DefBefore = ordsets:subtract(UsedYregs, Def),
+ {BeforeVars,Blocks,Count} = rename_vars(DefBefore, L, Blocks0, Count0),
+ InsideVars = ordsets:subtract(UsedYregs, DefBefore),
+ ResTryTags0 = reserve_try_tags(L, Blocks),
+ ResTryTags = [{V,{Reg,Count}} || {V,Reg} <- ResTryTags0],
+ Vars = BeforeVars ++ InsideVars,
+ Res = [{V,{y,Count}} || V <- Vars] ++ ResTryTags ++ Res0,
+ St#st{res=Res,ssa=Blocks,cnt=Count+1}.
+
+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)],
+ Res = [{V,{y,Y}} || {V,Y} <- append(Res1)],
+ ordsets:from_list(Res).
+
+reserve_try_tags_1([L|Ls], Blocks, Seen0, ActMap0) ->
+ case gb_sets:is_element(L, Seen0) of
+ true ->
+ reserve_try_tags_1(Ls, Blocks, Seen0, ActMap0);
+ false ->
+ Seen1 = gb_sets:insert(L, Seen0),
+ #b_blk{is=Is} = Blk = map_get(L, Blocks),
+ Active0 = get_active(L, ActMap0),
+ Active = reserve_try_tags_is(Is, Active0),
+ Successors = beam_ssa:successors(Blk),
+ ActMap1 = update_act_map(Successors, Active, ActMap0),
+ {ActMap,Seen} = reserve_try_tags_1(Ls, Blocks, Seen1, ActMap1),
+ reserve_try_tags_1(Successors, Blocks, Seen,ActMap)
+ end;
+reserve_try_tags_1([], _Blocks, Seen, ActMap) ->
+ {ActMap,Seen}.
+
+get_active(L, ActMap) ->
+ case ActMap of
+ #{L:=Active} -> Active;
+ #{} -> #{}
+ end.
+
+reserve_try_tags_is([#b_set{op=new_try_tag,dst=V}|Is], Active) ->
+ N = map_size(Active),
+ reserve_try_tags_is(Is, Active#{V=>N});
+reserve_try_tags_is([#b_set{op=kill_try_tag,args=[Tag]}|Is], Active) ->
+ reserve_try_tags_is(Is, maps:remove(Tag, Active));
+reserve_try_tags_is([_|Is], Active) ->
+ reserve_try_tags_is(Is, Active);
+reserve_try_tags_is([], Active) -> Active.
+
+update_act_map([L|Ls], Active0, ActMap0) ->
+ case ActMap0 of
+ #{L:=Active1} ->
+ ActMap = ActMap0#{L=>maps:merge(Active0, Active1)},
+ update_act_map(Ls, Active0, ActMap);
+ #{} ->
+ ActMap = ActMap0#{L=>Active0},
+ update_act_map(Ls, Active0, ActMap)
+ end;
+update_act_map([], _, ActMap) -> ActMap.
+
+rename_vars([], _, Blocks, Count) ->
+ {[],Blocks,Count};
+rename_vars(Vs, L, Blocks0, Count0) ->
+ {NewVars,Count} = new_vars([Base || #b_var{name=Base} <- Vs], Count0),
+ Ren = zip(Vs, NewVars),
+ Blocks1 = beam_ssa:rename_vars(Ren, [L], Blocks0),
+ #b_blk{is=Is0} = Blk0 = map_get(L, Blocks1),
+ CopyIs = [#b_set{op=copy,dst=New,args=[Old]} || {Old,New} <- Ren],
+ Is = insert_after_phis(Is0, CopyIs),
+ Blk = Blk0#b_blk{is=Is},
+ Blocks = Blocks1#{L:=Blk},
+ {NewVars,Blocks,Count}.
+
+insert_after_phis([#b_set{op=phi}=I|Is], InsertIs) ->
+ [I|insert_after_phis(Is, InsertIs)];
+insert_after_phis(Is, InsertIs) ->
+ InsertIs ++ Is.
+
+%% frame_size(St0) -> St.
+%% Calculate the frame size for each block that allocates a frame.
+%% Annotate the block with the frame size. Also annotate all
+%% return instructions with {deallocate,FrameSize} to simplify
+%% code generation.
+
+frame_size(#st{frames=Frames,regs=Regs,ssa=Blocks0}=St) ->
+ Blocks = foldl(fun(L, Blks) ->
+ frame_size_1(L, Regs, Blks)
+ end, Blocks0, Frames),
+ St#st{ssa=Blocks}.
+
+frame_size_1(L, Regs, Blocks0) ->
+ Def = beam_ssa:def([L], Blocks0),
+ Yregs0 = [map_get(V, Regs) || V <- Def, is_yreg(map_get(V, Regs))],
+ Yregs = ordsets:from_list(Yregs0),
+ FrameSize = length(ordsets:from_list(Yregs)),
+ if
+ FrameSize =/= 0 ->
+ [{y,0}|_] = Yregs, %Assertion.
+ {y,Last} = last(Yregs),
+ Last = FrameSize - 1, %Assertion.
+ ok;
+ true ->
+ ok
+ end,
+ Blk0 = map_get(L, Blocks0),
+ Blk = beam_ssa:add_anno(frame_size, FrameSize, Blk0),
+
+ %% Insert an annotation for frame deallocation on
+ %% each #b_ret{}.
+ Blocks = Blocks0#{L:=Blk},
+ Reachable = beam_ssa:rpo([L], Blocks),
+ frame_deallocate(Reachable, FrameSize, Blocks).
+
+frame_deallocate([L|Ls], Size, Blocks0) ->
+ Blk0 = map_get(L, Blocks0),
+ Blk = case Blk0 of
+ #b_blk{last=#b_ret{}=Ret0} ->
+ Ret = beam_ssa:add_anno(deallocate, Size, Ret0),
+ Blk0#b_blk{last=Ret};
+ #b_blk{} ->
+ Blk0
+ end,
+ Blocks = Blocks0#{L:=Blk},
+ frame_deallocate(Ls, Size, Blocks);
+frame_deallocate([], _, Blocks) -> Blocks.
+
+
+%% turn_yregs(St0) -> St.
+%% Renumber y registers so that {y,0} becomes {y,FrameSize-1},
+%% {y,FrameSize-1} becomes {y,0} and so on. This is to make nested
+%% catches work. The register allocator (linear_scan()) has given
+%% a lower number to the outermost catch.
+
+turn_yregs(#st{frames=Frames,regs=Regs0,ssa=Blocks}=St) ->
+ Regs1 = foldl(fun(L, A) ->
+ Blk = map_get(L, Blocks),
+ FrameSize = beam_ssa:get_anno(frame_size, Blk),
+ Def = beam_ssa:def([L], Blocks),
+ [turn_yregs_1(Def, FrameSize, Regs0)|A]
+ end, [], Frames),
+ Regs = maps:merge(Regs0, maps:from_list(append(Regs1))),
+ St#st{regs=Regs}.
+
+turn_yregs_1(Def, FrameSize, Regs) ->
+ Yregs0 = [{map_get(V, Regs),V} || V <- Def, is_yreg(map_get(V, Regs))],
+ Yregs1 = rel2fam(Yregs0),
+ FrameSize = length(Yregs1),
+ Yregs2 = [{{y,FrameSize-Y-1},Vs} || {{y,Y},Vs} <- Yregs1],
+ R0 = sofs:family(Yregs2),
+ R1 = sofs:family_to_relation(R0),
+ R = sofs:converse(R1),
+ sofs:to_external(R).
+
+%%%
+%%% Reserving registers before register allocation.
+%%%
+
+%% reserve_regs(St0) -> St.
+%% Reserve registers prior to register allocation. Y registers
+%% have already been reserved. This function will reserve z,
+%% fr, and specific x registers.
+
+reserve_regs(#st{args=Args,ssa=Blocks,intervals=Intervals,res=Res0}=St) ->
+ %% Reserve x0, x1, and so on for the function arguments.
+ Res1 = reserve_arg_regs(Args, 0, Res0),
+
+ %% Reserve Z registers (dummy registers) for instructions with no
+ %% return values (e.g. remove_message) or pseudo-return values
+ %% (e.g. landingpad).
+ Res2 = reserve_zregs(Blocks, Intervals, Res1),
+
+ %% Reserve float registers.
+ Res3 = reserve_fregs(Blocks, Res2),
+
+ %% Reserve all remaining unreserved variables as X registers.
+ Res = maps:from_list(Res3),
+ St#st{res=reserve_xregs(Blocks, Res)}.
+
+reserve_arg_regs([#b_var{}=Arg|Is], N, Acc) ->
+ reserve_arg_regs(Is, N+1, [{Arg,{x,N}}|Acc]);
+reserve_arg_regs([], _, Acc) -> Acc.
+
+reserve_zregs(Blocks, Intervals, Res) ->
+ ShortLived0 = [V || {V,[{Start,End}]} <- Intervals, Start+2 =:= End],
+ ShortLived = cerl_sets:from_list(ShortLived0),
+ F = fun(_, #b_blk{is=Is,last=Last}, A) ->
+ reserve_zreg(Is, Last, ShortLived, A)
+ end,
+ beam_ssa:fold_rpo(F, [0], Res, Blocks).
+
+reserve_zreg([#b_set{op=Op,dst=Dst}],
+ #b_br{bool=Dst}, _ShortLived, A) when Op =:= call;
+ Op =:= get_tuple_element ->
+ %% If type optimization has determined that the result of these
+ %% instructions can be used directly in a branch, we must avoid reserving a
+ %% z register or code generation will fail.
+ A;
+reserve_zreg([#b_set{op={bif,tuple_size},dst=Dst},
+ #b_set{op={bif,'=:='},args=[Dst,Val]}], Last, ShortLived, A0) ->
+ case {Val,Last} of
+ {#b_literal{val=Arity},#b_br{bool=#b_var{}}} when Arity bsr 32 =:= 0 ->
+ %% These two instructions can be combined to a test_arity
+ %% instruction provided that the arity variable is short-lived.
+ reserve_zreg_1(Dst, ShortLived, A0);
+ {_,_} ->
+ %% Either the arity is too big, or the boolean value is not
+ %% used in a conditional branch.
+ A0
+ end;
+reserve_zreg([#b_set{op={bif,tuple_size},dst=Dst}],
+ #b_switch{}, ShortLived, A) ->
+ reserve_zreg_1(Dst, ShortLived, A);
+reserve_zreg([#b_set{op={bif,'xor'}}], _Last, _ShortLived, A) ->
+ %% There is no short, easy way to rewrite 'xor' to a series of
+ %% test instructions.
+ A;
+reserve_zreg([#b_set{op={bif,is_record}}], _Last, _ShortLived, A) ->
+ %% There is no short, easy way to rewrite is_record/2 to a series of
+ %% test instructions.
+ A;
+reserve_zreg([#b_set{op=Op,dst=Dst}|Is], Last, ShortLived, A0) ->
+ IsZReg = case Op of
+ bs_match_string -> true;
+ bs_save -> true;
+ bs_restore -> true;
+ bs_set_position -> true;
+ {float,clearerror} -> true;
+ kill_try_tag -> true;
+ landingpad -> true;
+ put_tuple_elements -> true;
+ remove_message -> true;
+ set_tuple_element -> true;
+ succeeded -> true;
+ timeout -> true;
+ wait_timeout -> true;
+ _ -> false
+ end,
+ A = case IsZReg of
+ true -> [{Dst,z}|A0];
+ false -> A0
+ end,
+ reserve_zreg(Is, Last, ShortLived, A);
+reserve_zreg([], #b_br{bool=Bool}, ShortLived, A) ->
+ reserve_zreg_1(Bool, ShortLived, A);
+reserve_zreg([], _, _, A) -> A.
+
+reserve_zreg_1(#b_var{}=V, ShortLived, A) ->
+ case cerl_sets:is_element(V, ShortLived) of
+ true -> [{V,z}|A];
+ false -> A
+ end;
+reserve_zreg_1(#b_literal{}, _, A) -> A.
+
+reserve_fregs(Blocks, Res) ->
+ F = fun(_, #b_blk{is=Is}, A) ->
+ reserve_freg(Is, A)
+ end,
+ beam_ssa:fold_rpo(F, [0], Res, Blocks).
+
+reserve_freg([#b_set{op={float,Op},dst=V}|Is], Res) ->
+ case Op of
+ get ->
+ reserve_freg(Is, Res);
+ _ ->
+ reserve_freg(Is, [{V,fr}|Res])
+ end;
+reserve_freg([_|Is], Res) ->
+ reserve_freg(Is, Res);
+reserve_freg([], Res) -> Res.
+
+%% reserve_xregs(St0) -> St.
+%% Reserve all remaining variables as X registers.
+%%
+%% If a variable will need to be in a specific X register for a
+%% 'call' or 'make_fun' (and there is nothing that will kill it
+%% between the definition and use), reserve the register using a
+%% {prefer,{x,X} annotation. That annotation means that the linear
+%% scan algorithm will place the variable in the preferred register,
+%% unless that register is already occupied.
+%%
+%% All remaining variables are reserved as X registers. Linear scan
+%% will allocate the lowest free X register for the variable.
+
+reserve_xregs(Blocks, Res) ->
+ Ls = reverse(beam_ssa:rpo(Blocks)),
+ reserve_xregs(Ls, Blocks, #{}, Res).
+
+reserve_xregs([L|Ls], Blocks, XsMap0, Res0) ->
+ #b_blk{anno=Anno,is=Is0,last=Last} = map_get(L, Blocks),
+
+ %% Calculate mapping from variable name to the preferred
+ %% register.
+ Xs0 = reserve_terminator(L, Is0, Last, Blocks, XsMap0, Res0),
+
+ %% We need to figure out where the code generator will
+ %% place instructions that will do a garbage collection.
+ %% Insert 'gc' markers as pseudo-instructions in the
+ %% instruction sequence.
+ Is1 = reverse(Is0),
+ Is2 = res_place_gc_instrs(Is1, []),
+ Is = res_place_allocate(Anno, Is2),
+
+ %% Add register hints for variables that are defined
+ %% in the (reversed) instruction sequence.
+ {Res,Xs} = reserve_xregs_is(Is, Res0, Xs0, []),
+
+ XsMap = XsMap0#{L=>Xs},
+ reserve_xregs(Ls, Blocks, XsMap, Res);
+reserve_xregs([], _, _, Res) -> Res.
+
+%% Insert explicit 'gc' markers points where there will
+%% be a garbage collection. (Note that the instruction
+%% sequence passed to this function is reversed.)
+
+res_place_gc_instrs([#b_set{op=phi}=I|Is], Acc) ->
+ res_place_gc_instrs(Is, [I|Acc]);
+res_place_gc_instrs([#b_set{op=Op}=I|Is], Acc)
+ when Op =:= call; Op =:= make_fun ->
+ case Acc of
+ [] ->
+ res_place_gc_instrs(Is, [I|Acc]);
+ [GC|_] when GC =:= gc; GC =:= test_heap ->
+ res_place_gc_instrs(Is, [I,gc|Acc]);
+ [_|_] ->
+ res_place_gc_instrs(Is, [I,gc|Acc])
+ end;
+res_place_gc_instrs([#b_set{op=Op,args=Args}=I|Is], Acc0) ->
+ case beam_ssa_codegen:classify_heap_need(Op, Args) of
+ neutral ->
+ case Acc0 of
+ [test_heap|Acc] ->
+ res_place_gc_instrs(Is, [test_heap,I|Acc]);
+ Acc ->
+ res_place_gc_instrs(Is, [I|Acc])
+ end;
+ {put,_} ->
+ case Acc0 of
+ [test_heap|Acc] ->
+ res_place_gc_instrs(Is, [test_heap,I|Acc]);
+ Acc ->
+ res_place_gc_instrs(Is, [test_heap,I|Acc])
+ end;
+ _ ->
+ res_place_gc_instrs(Is, [gc,I|Acc0])
+ end;
+res_place_gc_instrs([], Acc) ->
+ %% Reverse and replace 'test_heap' markers with 'gc'.
+ %% (The distinction is no longer useful.)
+ res_place_gc_instrs_rev(Acc, []).
+
+res_place_gc_instrs_rev([test_heap|Is], [gc|_]=Acc) ->
+ res_place_gc_instrs_rev(Is, Acc);
+res_place_gc_instrs_rev([test_heap|Is], Acc) ->
+ res_place_gc_instrs_rev(Is, [gc|Acc]);
+res_place_gc_instrs_rev([gc|Is], [gc|_]=Acc) ->
+ res_place_gc_instrs_rev(Is, Acc);
+res_place_gc_instrs_rev([I|Is], Acc) ->
+ res_place_gc_instrs_rev(Is, [I|Acc]);
+res_place_gc_instrs_rev([], Acc) -> Acc.
+
+res_place_allocate(#{yregs:=_}, Is) ->
+ %% There will be an 'allocate' instruction inserted here.
+ Is ++ [gc];
+res_place_allocate(#{}, Is) -> Is.
+
+reserve_xregs_is([gc|Is], Res, Xs0, Used) ->
+ %% At this point, the code generator will place an instruction
+ %% that does a garbage collection. We must prune the remembered
+ %% registers.
+ Xs = res_xregs_prune(Xs0, Used, Res),
+ reserve_xregs_is(Is, Res, Xs, Used);
+reserve_xregs_is([#b_set{op=Op,dst=Dst,args=Args}=I|Is], Res0, Xs0, Used0) ->
+ Res = reserve_xreg(Dst, Xs0, Res0),
+ Used1 = ordsets:union(Used0, beam_ssa:used(I)),
+ Used = ordsets:del_element(Dst, Used1),
+ case Op of
+ call ->
+ Xs = reserve_call_args(tl(Args)),
+ reserve_xregs_is(Is, Res, Xs, Used);
+ make_fun ->
+ Xs = reserve_call_args(tl(Args)),
+ reserve_xregs_is(Is, Res, Xs, Used);
+ _ ->
+ reserve_xregs_is(Is, Res, Xs0, Used)
+ end;
+reserve_xregs_is([], Res, Xs, _Used) ->
+ {Res,Xs}.
+
+%% Pick up register hints from the successors of this blocks.
+reserve_terminator(_L, _Is, #b_br{bool=#b_var{},succ=Succ,fail=?BADARG_BLOCK},
+ _Blocks, XsMap, _Res) ->
+ %% We know that no variables are used at ?BADARG_BLOCK, so
+ %% any register hints from the success blocks are safe to use.
+ map_get(Succ, XsMap);
+reserve_terminator(L, Is, #b_br{bool=#b_var{},succ=Succ,fail=Fail},
+ Blocks, XsMap, Res) when Succ =/= Fail ->
+ #{Succ:=SuccBlk,Fail:=FailBlk} = Blocks,
+ case {SuccBlk,FailBlk} of
+ {#b_blk{is=[],last=#b_br{succ=PhiL,fail=PhiL}},
+ #b_blk{is=[],last=#b_br{succ=PhiL,fail=PhiL}}} ->
+ %% Both branches ultimately transfer to the same
+ %% block (via two blocks with no instructions).
+ %% Pick up register hints from the phi nodes
+ %% in the common block.
+ #{PhiL:=#b_blk{is=PhiIs}} = Blocks,
+ Xs = res_xregs_from_phi(PhiIs, Succ, Res, #{}),
+ res_xregs_from_phi(PhiIs, Fail, Res, Xs);
+ {_,_} when Is =/= [] ->
+ case last(Is) of
+ #b_set{op=succeeded,args=[Arg]} ->
+ %% We know that Arg will not be used at the failure
+ %% label, so we can pick up register hints from the
+ %% success label.
+ Br = #b_br{bool=#b_literal{val=true},succ=Succ,fail=Succ},
+ case reserve_terminator(L, [], Br, Blocks, XsMap, Res) of
+ #{Arg:=Reg} -> #{Arg=>Reg};
+ #{} -> #{}
+ end;
+ _ ->
+ %% Register hints from the success block may not
+ %% be safe at the failure block, and vice versa.
+ #{}
+ end;
+ {_,_} ->
+ %% Register hints from the success block may not
+ %% be safe at the failure block, and vice versa.
+ #{}
+ end;
+reserve_terminator(L, Is, #b_br{bool=#b_literal{val=true},succ=Succ},
+ Blocks, XsMap, Res) ->
+ case map_get(Succ, Blocks) of
+ #b_blk{is=[],last=Last} ->
+ reserve_terminator(Succ, Is, Last, Blocks, XsMap, Res);
+ #b_blk{is=[_|_]=PhiIs} ->
+ res_xregs_from_phi(PhiIs, L, Res, #{})
+ end;
+reserve_terminator(_, _, _, _, _, _) -> #{}.
+
+%% Pick up a reservation from a phi node.
+res_xregs_from_phi([#b_set{op=phi,dst=Dst,args=Args}|Is],
+ Pred, Res, Acc) ->
+ case [V || {#b_var{}=V,L} <- Args, L =:= Pred] of
+ [] ->
+ %% The value of the phi node for this predecessor
+ %% is a literal. Nothing to do here.
+ res_xregs_from_phi(Is, Pred, Res, Acc);
+ [V] ->
+ case Res of
+ #{Dst:={prefer,Reg}} ->
+ %% Try placing V in the same register as for
+ %% the phi node.
+ res_xregs_from_phi(Is, Pred, Res, Acc#{V=>Reg});
+ #{Dst:=_} ->
+ res_xregs_from_phi(Is, Pred, Res, Acc)
+ end
+ end;
+res_xregs_from_phi(_, _, _, Acc) -> Acc.
+
+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([#b_literal{}|As], X, Xs) ->
+ reserve_call_args(As, X+1, Xs);
+reserve_call_args([], _, Xs) -> Xs.
+
+reserve_xreg(V, Xs, Res) ->
+ case Res of
+ #{V:=_} ->
+ %% Already reserved (but not as an X register).
+ Res;
+ #{} ->
+ case Xs of
+ #{V:=X} ->
+ %% Add a hint that this specific X register is
+ %% preferred, unless it is already in use.
+ Res#{V=>{prefer,X}};
+ #{} ->
+ %% Reserve as an X register in general.
+ Res#{V=>x}
+ end
+ end.
+
+%% res_xregs_prune(PreferredRegs, Used, Res) -> PreferredRegs.
+%% Prune the list of preferred registers, to make sure that
+%% there are no "holes" (uninitialized X registers) when
+%% 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
+ end
+ end, 0, Used),
+
+ %% Remove unsafe registers from the list of potential
+ %% preferred registers.
+ maps:filter(fun(_, {x,X}) -> X < NumSafe end, Xs);
+res_xregs_prune(Xs, _Used, _Res) -> Xs.
+
+%%%
+%%% Register allocation using linear scan.
+%%%
+
+-record(i,
+ {sort=1 :: instr_number(),
+ reg=none :: i_reg(),
+ pool=x :: pool_id(),
+ var=#b_var{} :: b_var(),
+ rs=[] :: [range()]
+ }).
+
+-record(l,
+ {cur=#i{} :: interval(),
+ unhandled_res=[] :: [interval()],
+ unhandled_any=[] :: [interval()],
+ active=[] :: [interval()],
+ inactive=[] :: [interval()],
+ free=#{} :: #{var_name()=>pool(),
+ {'next',pool_id()}:=reg_num()},
+ regs=[] :: [{b_var(),ssa_register()}]
+ }).
+
+-type interval() :: #i{}.
+-type i_reg() :: ssa_register() | {'prefer',xreg()} | 'none'.
+-type pool_id() :: 'fr' | 'x' | 'z' | instr_number().
+-type pool() :: ordsets:ordset(ssa_register()).
+
+linear_scan(#st{intervals=Intervals0,res=Res}=St0) ->
+ St = St0#st{intervals=[],res=[]},
+ Free = init_free(maps:to_list(Res)),
+ Intervals1 = [init_interval(Int, Res) || Int <- Intervals0],
+ Intervals = sort(Intervals1),
+ IsReserved = fun(#i{reg=Reg}) ->
+ case Reg of
+ none -> false;
+ {prefer,{_,_}} -> false;
+ {_,_} -> true
+ end
+ end,
+ {UnhandledRes,Unhandled} = partition(IsReserved, Intervals),
+ L = #l{unhandled_res=UnhandledRes,
+ unhandled_any=Unhandled,free=Free},
+ #l{regs=Regs} = do_linear(L),
+ St#st{regs=maps:from_list(Regs)}.
+
+init_interval({V,[{Start,_}|_]=Rs}, Res) ->
+ Info = map_get(V, Res),
+ Pool = case Info of
+ {prefer,{x,_}} -> x;
+ x -> x;
+ {x,_} -> x;
+ {y,Uniq} -> Uniq;
+ {{y,_},Uniq} -> Uniq;
+ z -> z;
+ fr -> fr
+ end,
+ Reg = case Info of
+ {prefer,{x,_}} -> Info;
+ {x,_} -> Info;
+ {{y,_}=Y,_} -> Y;
+ _ -> none
+ end,
+ #i{sort=Start,var=V,reg=Reg,pool=Pool,rs=Rs}.
+
+init_free(Res) ->
+ Free0 = rel2fam([{x,{x,0}}|init_free_1(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)).
+
+init_free_1([{_,{prefer,{x,_}=Reg}}|Res]) ->
+ [{x,Reg}|init_free_1(Res)];
+init_free_1([{_,{x,_}=Reg}|Res]) ->
+ [{x,Reg}|init_free_1(Res)];
+init_free_1([{_,{y,Uniq}}|Res]) ->
+ [{Uniq,{y,0}}|init_free_1(Res)];
+init_free_1([{_,{{y,_}=Reg,Uniq}}|Res]) ->
+ [{Uniq,Reg}|init_free_1(Res)];
+init_free_1([{_,z}|Res]) ->
+ [{z,{z,0}}|init_free_1(Res)];
+init_free_1([{_,fr}|Res]) ->
+ [{fr,{fr,0}}|init_free_1(Res)];
+init_free_1([{_,x}|Res]) ->
+ init_free_1(Res);
+init_free_1([]) -> [].
+
+%% Make sure that the pool of xregs is contiguous.
+init_xregs([{x,N},{x,M}|Is]) when N+1 =:= M ->
+ [{x,N}|init_xregs([{x,M}|Is])];
+init_xregs([{x,N}|[{x,_}|_]=Is]) ->
+ [{x,N}|init_xregs([{x,N+1}|Is])];
+init_xregs([{x,_}]=Is) -> Is.
+
+do_linear(L0) ->
+ case set_next_current(L0) of
+ done ->
+ L0;
+ L1 ->
+ L2 = expire_active(L1),
+ L3 = check_inactive(L2),
+ Available = collect_available(L3),
+ L4 = select_register(Available, L3),
+ L = make_cur_active(L4),
+ do_linear(L)
+ end.
+
+set_next_current(#l{unhandled_res=[Cur1|T1],
+ unhandled_any=[Cur2|T2]}=L) ->
+ case {Cur1,Cur2} of
+ {#i{sort=N1},#i{sort=N2}} when N1 < N2 ->
+ L#l{cur=Cur1,unhandled_res=T1};
+ {_,_} ->
+ L#l{cur=Cur2,unhandled_any=T2}
+ end;
+set_next_current(#l{unhandled_res=[],
+ unhandled_any=[Cur|T]}=L) ->
+ L#l{cur=Cur,unhandled_any=T};
+set_next_current(#l{unhandled_res=[Cur|T],
+ unhandled_any=[]}=L) ->
+ L#l{cur=Cur,unhandled_res=T};
+set_next_current(#l{unhandled_res=[],unhandled_any=[]}) ->
+ done.
+
+expire_active(#l{cur=#i{sort=CurBegin},active=Act0}=L0) ->
+ {Act,L} = expire_active(Act0, CurBegin, L0, []),
+ L#l{active=Act}.
+
+expire_active([#i{reg=Reg,rs=Rs0}=I|Is], CurBegin, L0, Acc) ->
+ {_,_} = Reg, %Assertion.
+ case overlap_status(Rs0, CurBegin) of
+ ends_before_cur ->
+ L = free_reg(I, L0),
+ expire_active(Is, CurBegin, L, Acc);
+ overlapping ->
+ expire_active(Is, CurBegin, L0, [I|Acc]);
+ not_overlapping ->
+ Rs = strip_before_current(Rs0, CurBegin),
+ L1 = free_reg(I, L0),
+ L = L1#l{inactive=[I#i{rs=Rs}|L1#l.inactive]},
+ expire_active(Is, CurBegin, L, Acc)
+ end;
+expire_active([], _CurBegin, L, Acc) ->
+ {Acc,L}.
+
+check_inactive(#l{cur=#i{sort=CurBegin},inactive=InAct0}=L0) ->
+ {InAct,L} = check_inactive(InAct0, CurBegin, L0, []),
+ L#l{inactive=InAct}.
+
+check_inactive([#i{rs=Rs0}=I|Is], CurBegin, L0, Acc) ->
+ case overlap_status(Rs0, CurBegin) of
+ ends_before_cur ->
+ check_inactive(Is, CurBegin, L0, Acc);
+ not_overlapping ->
+ check_inactive(Is, CurBegin, L0, [I|Acc]);
+ overlapping ->
+ Rs = strip_before_current(Rs0, CurBegin),
+ L1 = L0#l{active=[I#i{rs=Rs}|L0#l.active]},
+ L = reserve_reg(I, L1),
+ check_inactive(Is, CurBegin, L, Acc)
+ end;
+check_inactive([], _CurBegin, L, Acc) ->
+ {Acc,L}.
+
+strip_before_current([{_,E}|Rs], CurBegin) when E =< CurBegin ->
+ strip_before_current(Rs, CurBegin);
+strip_before_current(Rs, _CurBegin) -> Rs.
+
+collect_available(#l{cur=#i{reg={prefer,{_,_}=Prefer}}=I}=L) ->
+ %% Use the preferred register if it is available.
+ Avail = collect_available(L#l{cur=I#i{reg=none}}),
+ case member(Prefer, Avail) of
+ true -> [Prefer];
+ false -> Avail
+ end;
+collect_available(#l{cur=#i{reg={_,_}=ReservedReg}}) ->
+ %% Return the already reserved register.
+ [ReservedReg];
+collect_available(#l{unhandled_res=Unhandled,cur=Cur}=L) ->
+ Free = get_pool(Cur, L),
+
+ %% Note that since the live intervals are constructed from
+ %% SSA form, there cannot be any overlap of the current interval
+ %% with any inactive interval. See [3], page 175. Therefore we
+ %% only have check the unhandled intervals for overlap with
+ %% the current interval. As a further optimization, we only need
+ %% to check the intervals that have reserved registers.
+ collect_available(Unhandled, Cur, Free).
+
+collect_available([#i{pool=Pool1}|Is], #i{pool=Pool2}=Cur, Free)
+ when Pool1 =/= Pool2 ->
+ %% Wrong pool. Ignore this interval.
+ collect_available(Is, Cur, Free);
+collect_available([#i{reg={_,_}=Reg}=I|Is], Cur, Free0) ->
+ case overlaps(I, Cur) of
+ true ->
+ Free = ordsets:del_element(Reg, Free0),
+ collect_available(Is, Cur, Free);
+ false ->
+ collect_available(Is, Cur, Free0)
+ end;
+collect_available([], _, Free) -> Free.
+
+select_register([{_,_}=Reg|_], #l{cur=Cur0,regs=Regs}=L) ->
+ Cur = Cur0#i{reg=Reg},
+ reserve_reg(Cur, L#l{cur=Cur,regs=[{Cur#i.var,Reg}|Regs]});
+select_register([], #l{cur=Cur0,regs=Regs}=L0) ->
+ %% Allocate a new register in the pool.
+ {Reg,L1} = get_next_free(Cur0, L0),
+ Cur = Cur0#i{reg=Reg},
+ L = L1#l{cur=Cur,regs=[{Cur#i.var,Reg}|Regs]},
+ reserve_reg(Cur, L).
+
+make_cur_active(#l{cur=Cur,active=Act}=L) ->
+ L#l{active=[Cur|Act]}.
+
+overlaps(#i{rs=Rs1}, #i{rs=Rs2}) ->
+ are_overlapping(Rs1, Rs2).
+
+overlap_status([{S,E}], CurBegin) ->
+ if
+ E =< CurBegin -> ends_before_cur;
+ CurBegin < S -> not_overlapping;
+ true -> overlapping
+ end;
+overlap_status([{S,E}|Rs], CurBegin) ->
+ if
+ E =< CurBegin ->
+ overlap_status(Rs, CurBegin);
+ S =< CurBegin ->
+ overlapping;
+ true ->
+ not_overlapping
+ end.
+
+reserve_reg(#i{reg={_,_}=Reg}=I, L) ->
+ FreeRegs0 = get_pool(I, L),
+ FreeRegs = ordsets:del_element(Reg, FreeRegs0),
+ update_pool(I, FreeRegs, L).
+
+free_reg(#i{reg={_,_}=Reg}=I, L) ->
+ FreeRegs0 = get_pool(I, L),
+ FreeRegs = ordsets:add_element(Reg, FreeRegs0),
+ update_pool(I, FreeRegs, L).
+
+get_pool(#i{pool=Pool}, #l{free=Free}) ->
+ map_get(Pool, Free).
+
+update_pool(#i{pool=Pool}, New, #l{free=Free0}=L) ->
+ Free = Free0#{Pool:=New},
+ L#l{free=Free}.
+
+get_next_free(#i{pool=Pool}, #l{free=Free0}=L0) ->
+ K = {next,Pool},
+ N = map_get(K, Free0),
+ Free = Free0#{K:=N+1},
+ L = L0#l{free=Free},
+ if
+ is_integer(Pool) -> {{y,N},L};
+ is_atom(Pool) -> {{Pool,N},L}
+ end.
+
+%%%
+%%% Interval utilities.
+%%%
+
+are_overlapping([R|Rs1], Rs2) ->
+ case are_overlapping_1(R, Rs2) of
+ true ->
+ true;
+ false ->
+ are_overlapping(Rs1, Rs2)
+ end;
+are_overlapping([], _) -> false.
+
+are_overlapping_1({_S1,E1}, [{S2,_E2}|_]) when E1 < S2 ->
+ false;
+are_overlapping_1({S1,E1}=R, [{S2,E2}|Rs]) ->
+ (S2 < E1 andalso E2 > S1) orelse are_overlapping_1(R, Rs);
+are_overlapping_1({_,_}, []) -> false.
+
+%%%
+%%% Utilities.
+%%%
+
+%% is_loop_header(L, Blocks) -> false|true.
+%% Check whether the block is a loop header.
+
+is_loop_header(L, Blocks) ->
+ %% We KNOW that a loop header must start with a peek_message
+ %% instruction.
+ case map_get(L, Blocks) of
+ #b_blk{is=[#b_set{op=peek_message}|_]} -> true;
+ _ -> false
+ end.
+
+rel2fam(S0) ->
+ S1 = sofs:relation(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;
+is_yreg({fr,_}) -> false.
+
+new_vars([Base|Vs0], Count0) ->
+ {V,Count1} = new_var(Base, Count0),
+ {Vs,Count} = new_vars(Vs0, Count1),
+ {[V|Vs],Count};
+new_vars([], Count) -> {[],Count}.
+
+new_var({Base,Int}, Count) ->
+ true = is_integer(Int), %Assertion.
+ {#b_var{name={Base,Count}},Count+1};
+new_var(Base, Count) ->
+ {#b_var{name={Base,Count}},Count+1}.
diff --git a/lib/compiler/src/beam_ssa_recv.erl b/lib/compiler/src/beam_ssa_recv.erl
new file mode 100644
index 0000000000..1e0e1ecac2
--- /dev/null
+++ b/lib/compiler/src/beam_ssa_recv.erl
@@ -0,0 +1,278 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018. All 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_recv).
+-export([module/2]).
+
+%%%
+%%% In code such as:
+%%%
+%%% Ref = make_ref(), %Or erlang:monitor(process, Pid)
+%%% .
+%%% .
+%%% .
+%%% receive
+%%% {Ref,Reply} -> Reply
+%%% end.
+%%%
+%%% we know that none of the messages that exist in the message queue
+%%% before the call to make_ref/0 can be matched out in the receive
+%%% statement. Therefore we can avoid going through the entire message
+%%% queue if we introduce two new instructions (here written as
+%%% BIFs in pseudo-Erlang):
+%%%
+%%% recv_mark(SomeUniqInteger),
+%%% Ref = make_ref(),
+%%% .
+%%% .
+%%% .
+%%% recv_set(SomeUniqInteger),
+%%% receive
+%%% {Ref,Reply} -> Reply
+%%% end.
+%%%
+%%% The recv_mark/1 instruction will save the current position and
+%%% SomeUniqInteger in the process context. The recv_set
+%%% instruction will verify that SomeUniqInteger is still stored
+%%% in the process context. If it is, it will set the current pointer
+%%% for the message queue (the next message to be read out) to the
+%%% position that was saved by recv_mark/1.
+%%%
+%%% The remove_message instruction must be modified to invalidate
+%%% the information stored by the previous recv_mark/1, in case there
+%%% is another receive executed between the calls to recv_mark/1 and
+%%% recv_set/1.
+%%%
+%%% We use a reference to a label (i.e. a position in the loaded code)
+%%% as the SomeUniqInteger.
+%%%
+
+-include("beam_ssa.hrl").
+-import(lists, [all/2,reverse/2]).
+
+-spec module(beam_ssa:b_module(), [compile:option()]) ->
+ {'ok',beam_ssa:b_module()}.
+
+module(#b_module{body=Fs0}=Module, _Opts) ->
+ Fs = [function(F) || F <- Fs0],
+ {ok,Module#b_module{body=Fs}}.
+
+%%%
+%%% Local functions.
+%%%
+
+function(#b_function{anno=Anno,bs=Blocks0}=F) ->
+ try
+ Blocks = opt(Blocks0),
+ F#b_function{bs=Blocks}
+ catch
+ Class:Error:Stack ->
+ #{func_info:={_,Name,Arity}} = Anno,
+ io:fwrite("Function: ~w/~w\n", [Name,Arity]),
+ erlang:raise(Class, Error, Stack)
+ end.
+
+opt(Blocks) ->
+ Linear = beam_ssa:linearize(Blocks),
+ opt(Linear, Blocks, []).
+
+opt([{L,#b_blk{is=[#b_set{op=peek_message}|_]}=Blk0}|Bs], Blocks0, Preds) ->
+ %% Search for a suitable reference creating call in one of the predecessor
+ %% blocks. Whether we find such a call or not, we always clear the
+ %% the list of predecessors to ensure that any nested receive can't
+ %% search above the current receive.
+ case recv_opt(Preds, L, Blocks0) of
+ {yes,Blocks1} ->
+ Blk = beam_ssa:add_anno(recv_set, L, Blk0),
+ Blocks = Blocks1#{L:=Blk},
+ opt(Bs, Blocks, []);
+ no ->
+ opt(Bs, Blocks0, [])
+ end;
+opt([{L,_}|Bs], Blocks, Preds) ->
+ opt(Bs, Blocks, [L|Preds]);
+opt([], Blocks, _) -> Blocks.
+
+recv_opt([L|Ls], RecvLbl, Blocks) ->
+ #b_blk{is=Is0} = Blk0 = map_get(L, Blocks),
+ case recv_opt_is(Is0, RecvLbl, Blocks, []) of
+ {yes,Is} ->
+ Blk = Blk0#b_blk{is=Is},
+ {yes,Blocks#{L:=Blk}};
+ no ->
+ recv_opt(Ls, RecvLbl, Blocks)
+ end;
+recv_opt([], _, _Blocks) -> no.
+
+recv_opt_is([#b_set{op=call}=I0|Is], RecvLbl, Blocks0, Acc) ->
+ case makes_ref(I0, Blocks0) of
+ no ->
+ recv_opt_is(Is, RecvLbl, Blocks0, [I0|Acc]);
+ {yes,Ref} ->
+ case opt_ref_used(RecvLbl, Ref, Blocks0) of
+ false ->
+ recv_opt_is(Is, RecvLbl, Blocks0, [I0|Acc]);
+ true ->
+ I = beam_ssa:add_anno(recv_mark, RecvLbl, I0),
+ {yes,reverse(Acc, [I|Is])}
+ end
+ end;
+recv_opt_is([I|Is], RecvLbl, Blocks, Acc) ->
+ recv_opt_is(Is, RecvLbl, Blocks, [I|Acc]);
+recv_opt_is([], _, _, _) -> no.
+
+makes_ref(#b_set{dst=Dst,args=[Func0|_]}, Blocks) ->
+ Func = case Func0 of
+ #b_remote{mod=#b_literal{val=erlang},
+ name=#b_literal{val=Name},arity=A0} ->
+ {Name,A0};
+ _ ->
+ none
+ end,
+ case Func of
+ {make_ref,0} ->
+ {yes,Dst};
+ {monitor,2} ->
+ {yes,Dst};
+ {spawn_monitor,A} when A =:= 1; A =:= 3 ->
+ ref_in_tuple(Dst, Blocks);
+ _ ->
+ no
+ end.
+
+ref_in_tuple(Tuple, Blocks) ->
+ F = fun(#b_set{op=get_tuple_element,dst=Ref,
+ args=[#b_var{}=Tup,#b_literal{val=1}]}, no)
+ when Tup =:= Tuple -> {yes,Ref};
+ (_, A) -> A
+ end,
+ beam_ssa:fold_instrs_rpo(F, [0], no, Blocks).
+
+opt_ref_used(RecvLbl, Ref, Blocks) ->
+ Vs = #{Ref=>ref,ref=>Ref,ref_matched=>false},
+ case opt_ref_used_1(RecvLbl, Vs, Blocks) of
+ used -> true;
+ not_used -> false;
+ done -> false
+ end.
+
+opt_ref_used_1(L, Vs0, Blocks) ->
+ #b_blk{is=Is} = Blk = map_get(L, Blocks),
+ case opt_ref_used_is(Is, Vs0) of
+ #{}=Vs ->
+ opt_ref_used_last(Blk, Vs, Blocks);
+ Result ->
+ Result
+ end.
+
+opt_ref_used_is([#b_set{op=peek_message,dst=Msg}|Is], Vs0) ->
+ Vs = Vs0#{Msg=>message},
+ opt_ref_used_is(Is, Vs);
+opt_ref_used_is([#b_set{op={bif,Bif},args=Args,dst=Dst}=I|Is],
+ Vs0) ->
+ S = case Bif of
+ '=:=' -> true;
+ '==' -> true;
+ _ -> none
+ end,
+ case S of
+ none ->
+ Vs = update_vars(I, Vs0),
+ opt_ref_used_is(Is, Vs);
+ Bool when is_boolean(Bool) ->
+ case is_ref_msg_comparison(Args, Vs0) of
+ true ->
+ Vs = Vs0#{Dst=>{is_ref,Bool}},
+ opt_ref_used_is(Is, Vs);
+ false ->
+ opt_ref_used_is(Is, Vs0)
+ end
+ end;
+opt_ref_used_is([#b_set{op=remove_message}|_], Vs) ->
+ case Vs of
+ #{ref_matched:=true} ->
+ used;
+ #{ref_matched:=false} ->
+ not_used
+ end;
+opt_ref_used_is([#b_set{op=recv_next}|_], _Vs) ->
+ done;
+opt_ref_used_is([#b_set{op=wait_timeout}|_], _Vs) ->
+ done;
+opt_ref_used_is([#b_set{op=wait}|_], _Vs) ->
+ done;
+opt_ref_used_is([#b_set{}=I|Is], Vs0) ->
+ Vs = update_vars(I, Vs0),
+ opt_ref_used_is(Is, Vs);
+opt_ref_used_is([], Vs) -> Vs.
+
+opt_ref_used_last(#b_blk{last=Last}=Blk, Vs, Blocks) ->
+ case Last of
+ #b_br{bool=#b_var{}=Bool,succ=Succ,fail=Fail} ->
+ case Vs of
+ #{Bool:={is_ref,Matched}} ->
+ ref_used_in([{Succ,Vs#{ref_matched:=Matched}},
+ {Fail,Vs#{ref_matched:=not Matched}}],
+ Blocks);
+ #{} ->
+ ref_used_in([{Succ,Vs},{Fail,Vs}], Blocks)
+ end;
+ _ ->
+ SuccVs = [{Succ,Vs} || Succ <- beam_ssa:successors(Blk)],
+ ref_used_in(SuccVs, Blocks)
+ end.
+
+ref_used_in([{L,Vs0}|Ls], Blocks) ->
+ case opt_ref_used_1(L, Vs0, Blocks) of
+ not_used ->
+ not_used;
+ used ->
+ case ref_used_in(Ls, Blocks) of
+ done -> used;
+ Result -> Result
+ end;
+ done -> ref_used_in(Ls, Blocks)
+ end;
+ref_used_in([], _) -> done.
+
+update_vars(#b_set{args=Args,dst=Dst}, Vs) ->
+ Vars = [V || #b_var{}=V <- Args],
+ All = all(fun(Var) ->
+ case Vs of
+ #{Var:=message} -> true;
+ #{} -> false
+ end
+ end, Vars),
+ case All of
+ true -> Vs#{Dst=>message};
+ false -> Vs
+ end.
+
+%% is_ref_msg_comparison(Args, Variables) -> true|false.
+%% Return 'true' if Args denotes a comparison between the
+%% reference and message or part of the message.
+
+is_ref_msg_comparison([#b_var{}=V1,#b_var{}=V2], Vs) ->
+ case Vs of
+ #{V1:=ref,V2:=message} -> true;
+ #{V1:=message,V2:=ref} -> true;
+ #{} -> false
+ end;
+is_ref_msg_comparison(_, _) -> false.
diff --git a/lib/compiler/src/beam_ssa_share.erl b/lib/compiler/src/beam_ssa_share.erl
new file mode 100644
index 0000000000..426efa2cc9
--- /dev/null
+++ b/lib/compiler/src/beam_ssa_share.erl
@@ -0,0 +1,370 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018. All 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%
+%%
+
+%%
+%% Share code for semantically equivalent blocks referred to
+%% to by `br` and `switch` instructions.
+%%
+%% A similar optimization is done in beam_jump, but doing it here as
+%% well is beneficial as it may enable other optimizations. If there
+%% are many semantically equivalent clauses, this optimization can
+%% substanstially decrease compilation times.
+%%
+%% block/2 is called from the liveness optimization pass in
+%% beam_ssa_opt, as code sharing helps the liveness pass and vice
+%% versa.
+%%
+
+-module(beam_ssa_share).
+-export([module/2,block/2]).
+
+-include("beam_ssa.hrl").
+
+-import(lists, [keyfind/3,reverse/1,sort/1]).
+
+-spec module(beam_ssa:b_module(), [compile:option()]) ->
+ {'ok',beam_ssa:b_module()}.
+
+module(#b_module{body=Fs0}=Module, _Opts) ->
+ Fs = [function(F) || F <- Fs0],
+ {ok,Module#b_module{body=Fs}}.
+
+-spec block(Blk0, Blocks0) -> Blk when
+ Blk0 :: beam_ssa:b_blk(),
+ Blocks0 :: beam_ssa:block_map(),
+ Blk :: beam_ssa:b_blk().
+
+block(#b_blk{last=Last0}=Blk, Blocks) ->
+ case share_terminator(Last0, Blocks) of
+ none -> Blk;
+ Last -> Blk#b_blk{last=beam_ssa:normalize(Last)}
+ end.
+
+%%%
+%%% Local functions.
+%%%
+
+function(#b_function{anno=Anno,bs=Blocks0}=F) ->
+ try
+ PO = reverse(beam_ssa:rpo(Blocks0)),
+ {Blocks1,Changed} = blocks(PO, Blocks0, false),
+ Blocks = case Changed of
+ true ->
+ beam_ssa:trim_unreachable(Blocks1);
+ false ->
+ Blocks0
+ end,
+ F#b_function{bs=Blocks}
+ catch
+ Class:Error:Stack ->
+ #{func_info:={_,Name,Arity}} = Anno,
+ io:fwrite("Function: ~w/~w\n", [Name,Arity]),
+ erlang:raise(Class, Error, Stack)
+ end.
+
+blocks([L|Ls], Blocks, Changed) ->
+ #b_blk{last=Last0} = Blk0 = map_get(L, Blocks),
+ case block(Blk0, Blocks) of
+ #b_blk{last=Last0} ->
+ blocks(Ls, Blocks, Changed);
+ #b_blk{}=Blk ->
+ blocks(Ls, Blocks#{L:=Blk}, true)
+ end;
+blocks([], Blocks, Changed) ->
+ {Blocks,Changed}.
+
+share_terminator(#b_br{bool=#b_var{},succ=Succ0,fail=Fail0}=Br, Blocks) ->
+ {Succ,SuccBlk} = shortcut_nonempty_block(Succ0, Blocks),
+ {Fail,FailBlk} = shortcut_nonempty_block(Fail0, Blocks),
+ case are_equivalent(Succ, SuccBlk, Fail, FailBlk, Blocks) of
+ true ->
+ %% The blocks are semantically equivalent.
+ Br#b_br{succ=Succ,fail=Succ};
+ false ->
+ if
+ Succ =:= Succ0, Fail =:= Fail0 ->
+ %% None of blocks were cut short.
+ none;
+ true ->
+ %% One or both labels were cut short
+ %% to avoid jumping to an empty block.
+ Br#b_br{succ=Succ,fail=Fail}
+ end
+ end;
+share_terminator(#b_switch{}=Sw, Blocks) ->
+ share_switch(Sw, Blocks);
+share_terminator(_Last, _Blocks) -> none.
+
+%% Test whether the two blocks are semantically equivalent. This
+%% function is specially optimized to return `false` as fast as
+%% possible if the blocks are not equivalent, as that is the common
+%% case.
+
+are_equivalent(_Succ, _, ?BADARG_BLOCK, _, _Blocks) ->
+ %% ?BADARG_BLOCK is special. Sharing could be incorrect.
+ false;
+are_equivalent(_Succ, #b_blk{is=Is1,last=#b_ret{arg=RetVal1}=Ret1},
+ _Fail, #b_blk{is=Is2,last=#b_ret{arg=RetVal2}=Ret2}, _Blocks) ->
+ case {RetVal1,RetVal2} of
+ {#b_literal{},#b_literal{}} ->
+ case RetVal1 =:= RetVal2 of
+ true ->
+ %% The return values are identical literals. We
+ %% only need to compare the canonicalized bodies.
+ Can1 = canonical_is(Is1),
+ Can2 = canonical_is(Is2),
+ Can1 =:= Can2;
+ false ->
+ %% Non-equal literals.
+ false
+ end;
+ {#b_var{},#b_var{}} ->
+ %% The return values are varibles. We must canonicalize
+ %% the blocks (including returns) and compare them.
+ Can1 = canonical_is(Is1 ++ [Ret1]),
+ Can2 = canonical_is(Is2 ++ [Ret2]),
+ Can1 =:= Can2;
+ {_,_} ->
+ %% One literal and one variable.
+ false
+ end;
+are_equivalent(Succ,
+ #b_blk{is=Is1,
+ last=#b_br{bool=#b_literal{val=true},
+ succ=Target}},
+ Fail,
+ #b_blk{is=Is2,
+ last=#b_br{bool=#b_literal{val=true},
+ succ=Target}},
+ Blocks) ->
+ %% Both blocks end with an unconditional branch to the
+ %% same target block. If the target block has phi nodes,
+ %% we must pick up the values from the phi nodes and
+ %% compare them.
+ #b_blk{is=Is} = map_get(Target, Blocks),
+ Phis1 = canonical_terminator_phis(Is, Succ),
+ Phis2 = canonical_terminator_phis(Is, Fail),
+ case {Phis1,Phis2} of
+ {[#b_set{args=[#b_literal{}]}|_],_} when Phis1 =/= Phis2 ->
+ %% Different values are used in the phi nodes.
+ false;
+ {_,[#b_set{args=[#b_literal{}]}|_]} when Phis1 =/= Phis2 ->
+ %% Different values are used in the phi nodes.
+ false;
+ {_,_} ->
+ %% The values in the phi nodes are variables or identical
+ %% literals. We must canonicalize the blocks and compare
+ %% them.
+ Can1 = canonical_is(Is1 ++ Phis1),
+ Can2 = canonical_is(Is2 ++ Phis2),
+ Can1 =:= Can2
+ end;
+are_equivalent(Succ0, #b_blk{is=Is1,last=#b_br{bool=#b_var{},fail=Same}},
+ Fail0, #b_blk{is=Is2,last=#b_br{bool=#b_var{},fail=Same}},
+ Blocks) ->
+ %% Two-way branches with identical failure labels. First compare the
+ %% canonicalized bodies of the blocks.
+ case canonical_is(Is1) =:= canonical_is(Is2) of
+ false ->
+ %% Different bodies.
+ false;
+ true ->
+ %% Bodies were equal. That is fairly uncommon, so to keep
+ %% the code simple we will rewrite the `br` to a `switch`
+ %% and let share_switch/2 do the work of following the
+ %% branches.
+ Sw = #b_switch{arg=#b_var{name=not_used},fail=Fail0,
+ list=[{#b_literal{},Succ0}]},
+ #b_switch{fail=Fail,list=[{_,Succ}]} = share_switch(Sw, Blocks),
+ Fail =:= Succ
+ end;
+are_equivalent(_, _, _, _, _) -> false.
+
+share_switch(#b_switch{fail=Fail0,list=List0}=Sw, Blocks) ->
+ Prep = share_prepare_sw([{value,Fail0}|List0], Blocks, 0, []),
+ Res = do_share_switch(Prep, Blocks, []),
+ [{_,Fail}|List] = [VL || {_,VL} <- sort(Res)],
+ Sw#b_switch{fail=Fail,list=List}.
+
+share_prepare_sw([{V,L0}|T], Blocks, N, Acc) ->
+ {L,_Blk} = shortcut_nonempty_block(L0, Blocks),
+ share_prepare_sw(T, Blocks, N+1, [{{L,#{}},{N,{V,L}}}|Acc]);
+share_prepare_sw([], _, _, Acc) -> Acc.
+
+do_share_switch(Prep, Blocks, Acc) ->
+ Map = share_switch_1(Prep, Blocks, #{}),
+ share_switch_2(maps:values(Map), Blocks, Acc).
+
+share_switch_1([{Next0,Res}|T], Blocks, Map) ->
+ {Can,Next} = canonical_block(Next0, Blocks),
+ case Map of
+ #{Can:=Ls} ->
+ share_switch_1(T, Blocks, Map#{Can:=[{Next,Res}|Ls]});
+ #{} ->
+ share_switch_1(T, Blocks, Map#{Can=>[{Next,Res}]})
+ end;
+share_switch_1([], _Blocks, Map) -> Map.
+
+share_switch_2([[{_,{N,Res}}]|T], Blocks, Acc) ->
+ %% This block is not equivalent to any other block.
+ share_switch_2(T, Blocks, [{N,Res}|Acc]);
+share_switch_2([[{done,{_,{_,Common}}}|_]=Eqs|T], Blocks, Acc0) ->
+ %% Two or more blocks are semantically equivalent, and all blocks
+ %% are either terminated with a `ret` or a `br` to the same target
+ %% block. Replace the labels in the `switch` for all of those
+ %% blocks with the label for the first of the blocks.
+ Acc = [{N,{V,Common}} || {done,{N,{V,_}}} <- Eqs] ++ Acc0,
+ share_switch_2(T, Blocks, Acc);
+share_switch_2([[{_,_}|_]=Prep|T], Blocks, Acc0) ->
+ %% Two or more blocks are semantically equivalent, but they have
+ %% different successful successor blocks. Now we must check
+ %% recursively whether the successor blocks are equivalent too.
+ Acc = do_share_switch(Prep, Blocks, Acc0),
+ share_switch_2(T, Blocks, Acc);
+share_switch_2([], _, Acc) -> Acc.
+
+canonical_block({L,VarMap0}, Blocks) ->
+ #b_blk{is=Is,last=Last0} = map_get(L, Blocks),
+ case canonical_terminator(L, Last0, Blocks) of
+ none ->
+ %% The block has a terminator that we don't handle.
+ {{none,L},done};
+ {Last,done} ->
+ %% The block ends with a `ret` or an unconditional `br` to
+ %% another block.
+ {Can,_VarMap} = canonical_is(Is ++ Last, VarMap0, []),
+ {Can,done};
+ {Last,Next} ->
+ %% The block ends with a conditional branch.
+ {Can,VarMap} = canonical_is(Is ++ Last, VarMap0, []),
+ {Can,{Next,VarMap}}
+ end.
+
+%% Translate a sequence of instructions to a canonical representation. If the
+%% canonical representation of two blocks compare equal, the blocks are
+%% semantically equivalent. The following translations are done:
+%%
+%% * Variables defined in the instruction sequence are replaced with
+%% {var,0}, {var,1}, and so on. Free variables are not changed.
+%%
+%% * `location` annotations that would produce a `line` instruction are
+%% kept. All other annotations are cleared.
+%%
+%% * Instructions are repackaged into tuples instead of into the
+%% usual records. The main reason is to avoid violating the types for
+%% the SSA records. We can simplify things a little by linking the
+%% instructions directly instead of putting them into a list.
+
+canonical_is(Is) ->
+ {Can,_} = canonical_is(Is, #{}, []),
+ Can.
+
+canonical_is([#b_set{op=Op,dst=Dst,args=Args0}=I|Is], VarMap0, Acc) ->
+ Args = [canonical_arg(Arg, VarMap0) || Arg <-Args0],
+ Var = {var,map_size(VarMap0)},
+ VarMap = VarMap0#{Dst=>Var},
+ LineAnno = case Op of
+ bs_match ->
+ %% The location annotation for a bs_match instruction
+ %% is only used in warnings, never to emit a `line`
+ %% instruction. Therefore, it should not be included.
+ [];
+ _ ->
+ %% The location annotation will be used in a `line`
+ %% instruction. It must be included.
+ beam_ssa:get_anno(location, I, none)
+ end,
+ canonical_is(Is, VarMap, {Op,LineAnno,Var,Args,Acc});
+canonical_is([#b_ret{arg=Arg}], VarMap, Acc0) ->
+ Acc1 = case Acc0 of
+ {call,_Anno,Var,[#b_local{}|_]=Args,PrevAcc} ->
+ %% This is a tail-recursive call to a local function.
+ %% There will be no line instruction generated;
+ %% thus, the annotation is not significant.
+ {call,[],Var,Args,PrevAcc};
+ _ ->
+ Acc0
+ end,
+ {{ret,canonical_arg(Arg, VarMap),Acc1},VarMap};
+canonical_is([#b_br{bool=#b_var{},fail=Fail}], VarMap, Acc) ->
+ {{br,succ,Fail,Acc},VarMap};
+canonical_is([#b_br{succ=Succ}], VarMap, Acc) ->
+ {{br,Succ,Acc},VarMap};
+canonical_is([], VarMap, Acc) ->
+ {Acc,VarMap}.
+
+canonical_terminator(_L, #b_ret{}=Ret, _Blocks) ->
+ {[Ret],done};
+canonical_terminator(L, #b_br{bool=#b_literal{val=true},succ=Succ}=Br, Blocks) ->
+ #b_blk{is=Is} = map_get(Succ, Blocks),
+ case canonical_terminator_phis(Is, L) of
+ [] ->
+ {[],Succ};
+ [_|_]=Phis ->
+ {Phis ++ [Br],done}
+ end;
+canonical_terminator(_L, #b_br{bool=#b_var{},succ=Succ}=Br, _Blocks) ->
+ {[Br],Succ};
+canonical_terminator(_, _, _) -> none.
+
+canonical_terminator_phis([#b_set{op=phi,args=PhiArgs}=Phi|Is], L) ->
+ {Value,L} = keyfind(L, 2, PhiArgs),
+ [Phi#b_set{op=copy,args=[Value]}|canonical_terminator_phis(Is, L)];
+canonical_terminator_phis([#b_set{op=peek_message}=I|_], L) ->
+ %% We could get stuck into an infinite loop if we allowed the
+ %% comparisons to continue into this block. Force an unequal
+ %% compare with all other predecessors of this block.
+ [I#b_set{op=copy,args=[#b_literal{val=L}]}];
+canonical_terminator_phis(_, _) -> [].
+
+canonical_arg(#b_var{}=Var, VarMap) ->
+ case VarMap of
+ #{Var:=CanonicalVar} ->
+ CanonicalVar;
+ #{} ->
+ Var
+ end;
+canonical_arg(#b_remote{mod=Mod,name=Name}, VarMap) ->
+ {remote,canonical_arg(Mod, VarMap),
+ canonical_arg(Name, VarMap)};
+canonical_arg(Other, _VarMap) -> Other.
+
+%% Shortcut branches to empty blocks if safe.
+
+shortcut_nonempty_block(L, Blocks) ->
+ case map_get(L, Blocks) of
+ #b_blk{is=[],last=#b_br{bool=#b_literal{val=true},succ=Succ}}=Blk ->
+ %% This block is empty.
+ case is_forbidden(Succ, Blocks) of
+ false ->
+ shortcut_nonempty_block(Succ, Blocks);
+ true ->
+ {L,Blk}
+ end;
+ #b_blk{}=Blk ->
+ {L,Blk}
+ end.
+
+is_forbidden(L, Blocks) ->
+ case map_get(L, Blocks) of
+ #b_blk{is=[#b_set{op=phi}|_]} -> true;
+ #b_blk{is=[#b_set{op=peek_message}|_]} -> true;
+ #b_blk{} -> false
+ end.
diff --git a/lib/compiler/src/beam_ssa_type.erl b/lib/compiler/src/beam_ssa_type.erl
new file mode 100644
index 0000000000..c01ea4af91
--- /dev/null
+++ b/lib/compiler/src/beam_ssa_type.erl
@@ -0,0 +1,1937 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018. All 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_type).
+-export([opt_start/4, opt_continue/4, opt_finish/3]).
+
+-include("beam_ssa_opt.hrl").
+-import(lists, [all/2,any/2,droplast/1,foldl/3,last/1,member/2,
+ keyfind/3,partition/2,reverse/1,reverse/2,
+ seq/2,sort/1,split/2]).
+
+-define(UNICODE_INT, #t_integer{elements={0,16#10FFFF}}).
+
+-record(d,
+ {ds :: #{beam_ssa:b_var():=beam_ssa:b_set()},
+ ls :: #{beam_ssa:label():=type_db()},
+ once :: cerl_sets:set(beam_ssa:b_var()),
+ func_id :: func_id(),
+ func_db :: func_info_db(),
+ sub = #{} :: #{beam_ssa:b_var():=beam_ssa:value()},
+ ret_type = [] :: [type()]}).
+
+-define(ATOM_SET_SIZE, 5).
+
+%% Records that represent type information.
+-record(t_atom, {elements=any :: 'any' | [atom()]}).
+-record(t_integer, {elements=any :: 'any' | {integer(),integer()}}).
+-record(t_bs_match, {type :: type()}).
+-record(t_tuple, {size=0 :: integer(),
+ exact=false :: boolean(),
+ %% Known element types (1-based index), unknown elements are
+ %% are assumed to be 'any'.
+ elements=#{} :: #{ non_neg_integer() => type() }}).
+
+-type type() :: 'any' | 'none' |
+ #t_atom{} | #t_integer{} | #t_bs_match{} | #t_tuple{} |
+ {'binary',pos_integer()} | 'cons' | 'float' | 'list' | 'map' | 'nil' | 'number'.
+-type type_db() :: #{beam_ssa:var_name():=type()}.
+
+-spec opt_start(Linear, Args, Anno, FuncDb) -> {Linear, FuncDb} when
+ Linear :: [{non_neg_integer(), beam_ssa:b_blk()}],
+ Args :: [beam_ssa:b_var()],
+ Anno :: beam_ssa:anno(),
+ FuncDb :: func_info_db().
+opt_start(Linear, Args, Anno, FuncDb) ->
+ %% This is the first run through the module, so our arg_types can be
+ %% incomplete as we may not have visited all call sites at least once.
+ Ts = maps:from_list([{V,any} || #b_var{}=V <- Args]),
+ opt_continue_1(Linear, Args, get_func_id(Anno), Ts, FuncDb).
+
+-spec opt_continue(Linear, Args, Anno, FuncDb) -> {Linear, FuncDb} when
+ Linear :: [{non_neg_integer(), beam_ssa:b_blk()}],
+ Args :: [beam_ssa:b_var()],
+ Anno :: beam_ssa:anno(),
+ FuncDb :: func_info_db().
+opt_continue(Linear, Args, Anno, FuncDb) ->
+ Id = get_func_id(Anno),
+ case FuncDb of
+ #{ Id := #func_info{exported=false,arg_types=ArgTypes} } ->
+ %% This is a local function and we're guaranteed to have visited
+ %% every call site at least once, so we know that the parameter
+ %% types are at least as narrow as the join of all argument types.
+ Ts = join_arg_types(Args, ArgTypes, Anno),
+ opt_continue_1(Linear, Args, Id, Ts, FuncDb);
+ #{} ->
+ %% We can't infer the parameter types of exported functions, nor
+ %% the ones where module-level optimization is disabled, but
+ %% running the pass again could still help other functions.
+ Ts = maps:from_list([{V,any} || #b_var{}=V <- Args]),
+ opt_continue_1(Linear, Args, Id, Ts, FuncDb)
+ end.
+
+join_arg_types(Args, ArgTypes, Anno) ->
+ %% We suppress type optimization for parameters that have already been
+ %% optimized by another pass, as they may have done things we have no idea
+ %% how to interpret and running them over could generate incorrect code.
+ ParamTypes = maps:get(parameter_type_info, Anno, #{}),
+ Ts0 = join_arg_types_1(Args, ArgTypes, #{}),
+ maps:fold(fun(Arg, _V, Ts) ->
+ maps:put(Arg, any, Ts)
+ end, Ts0, ParamTypes).
+
+join_arg_types_1([Arg | Args], [TM | TMs], Ts) when map_size(TM) =/= 0 ->
+ join_arg_types_1(Args, TMs, Ts#{ Arg => join(maps:values(TM))});
+join_arg_types_1([Arg | Args], [_TM | TMs], Ts) ->
+ join_arg_types_1(Args, TMs, Ts#{ Arg => any });
+join_arg_types_1([], [], Ts) ->
+ Ts.
+
+-spec opt_continue_1(Linear, Args, Id, Ts, FuncDb) -> Result when
+ Linear :: [{non_neg_integer(), beam_ssa:b_blk()}],
+ Args :: [beam_ssa:b_var()],
+ Id :: func_id(),
+ Ts :: type_db(),
+ FuncDb :: func_info_db(),
+ Result :: {Linear, FuncDb}.
+opt_continue_1(Linear0, Args, Id, Ts, FuncDb0) ->
+ UsedOnce = used_once(Linear0, Args),
+ FakeCall = #b_set{op=call,args=[#b_remote{mod=#b_literal{val=unknown},
+ name=#b_literal{val=unknown},
+ arity=0}]},
+ Defs = maps:from_list([{Var,FakeCall#b_set{dst=Var}} ||
+ #b_var{}=Var <- Args]),
+
+ D = #d{ func_db=FuncDb0,
+ func_id=Id,
+ ds=Defs,
+ ls=#{0=>Ts,?BADARG_BLOCK=>#{}},
+ once=UsedOnce },
+
+ {Linear, FuncDb, NewRet} = opt(Linear0, D, []),
+
+ case FuncDb of
+ #{ Id := Entry0 } ->
+ Entry = Entry0#func_info{ret_type=NewRet},
+ {Linear, FuncDb#{ Id := Entry }};
+ #{} ->
+ %% Module-level optimizations have been turned off for this
+ %% function.
+ {Linear, FuncDb}
+ end.
+
+-spec opt_finish(Args, Anno, FuncDb) -> {Anno, FuncDb} when
+ Args :: [beam_ssa:b_var()],
+ Anno :: beam_ssa:anno(),
+ FuncDb :: func_info_db().
+opt_finish(Args, Anno, FuncDb) ->
+ Id = get_func_id(Anno),
+ case FuncDb of
+ #{ Id := #func_info{exported=false,arg_types=ArgTypes} } ->
+ ParamInfo0 = maps:get(parameter_type_info, Anno, #{}),
+ ParamInfo = opt_finish_1(Args, ArgTypes, ParamInfo0),
+ {Anno#{ parameter_type_info => ParamInfo }, FuncDb};
+ #{} ->
+ {Anno, FuncDb}
+ end.
+
+opt_finish_1([Arg | Args], [TypeMap | TypeMaps], ParamInfo)
+ when is_map_key(Arg, ParamInfo); %% See join_arg_types/3
+ map_size(TypeMap) =:= 0 ->
+ opt_finish_1(Args, TypeMaps, ParamInfo);
+opt_finish_1([Arg | Args], [TypeMap | TypeMaps], ParamInfo0) ->
+ case join(maps:values(TypeMap)) of
+ any ->
+ opt_finish_1(Args, TypeMaps, ParamInfo0);
+ JoinedType ->
+ JoinedType = verified_type(JoinedType),
+ ParamInfo = ParamInfo0#{ Arg => validator_anno(JoinedType) },
+ opt_finish_1(Args, TypeMaps, ParamInfo)
+ end;
+opt_finish_1([], [], ParamInfo) ->
+ ParamInfo.
+
+validator_anno(#t_tuple{size=Size,exact=Exact,elements=Elements0}) ->
+ Elements = maps:fold(fun(Index, Type, Acc) ->
+ Key = beam_validator:type_anno(integer, Index),
+ Acc#{ Key => validator_anno(Type) }
+ end, #{}, Elements0),
+ beam_validator:type_anno(tuple, Size, Exact, Elements);
+validator_anno(#t_integer{elements={Same,Same}}) ->
+ beam_validator:type_anno(integer, Same);
+validator_anno(#t_integer{}) ->
+ beam_validator:type_anno(integer);
+validator_anno(float) ->
+ beam_validator:type_anno(float);
+validator_anno(#t_atom{elements=[Val]}) ->
+ beam_validator:type_anno(atom, Val);
+validator_anno(#t_atom{}=A) ->
+ case t_is_boolean(A) of
+ true -> beam_validator:type_anno(bool);
+ false -> beam_validator:type_anno(atom)
+ end;
+validator_anno(T) ->
+ beam_validator:type_anno(T).
+
+get_func_id(Anno) ->
+ #{func_info:={_Mod, Name, Arity}} = Anno,
+ #b_local{name=#b_literal{val=Name}, arity=Arity}.
+
+opt([{L,Blk}|Bs], #d{ls=Ls}=D, Acc) ->
+ case Ls of
+ #{L:=Ts} ->
+ opt_1(L, Blk, Bs, Ts, D, Acc);
+ #{} ->
+ %% This block is never reached. Discard it.
+ opt(Bs, D, Acc)
+ end;
+opt([], D, Acc) ->
+ #d{func_db=FuncDb,ret_type=NewRet} = D,
+ {reverse(Acc), FuncDb, NewRet}.
+
+opt_1(L, #b_blk{is=Is0,last=Last0}=Blk0, Bs, Ts0,
+ #d{ds=Ds0,sub=Sub0,func_db=Fdb0}=D0, Acc) ->
+ case opt_is(Is0, Ts0, Ds0, Fdb0, D0, Sub0, []) of
+ {Is,Ts,Ds,Fdb,Sub} ->
+ D1 = D0#d{ds=Ds,sub=Sub,func_db=Fdb},
+ Last1 = simplify_terminator(Last0, Sub, Ts, Ds),
+ Last = opt_terminator(Last1, Ts, Ds),
+ D = update_successors(Last, Ts, D1),
+ Blk = Blk0#b_blk{is=Is,last=Last},
+ opt(Bs, D, [{L,Blk}|Acc]);
+ {no_return,Ret,Is,Ds,Fdb,Sub} ->
+ %% This call will never reach the successor block.
+ %% Rewrite the terminator to a 'ret', and remove
+ %% all type information for this label. That can
+ %% potentially narrow the type of the phi node
+ %% in the former successor.
+ Ls = maps:remove(L, D0#d.ls),
+ RetType = join([none|D0#d.ret_type]),
+ D = D0#d{ds=Ds,ls=Ls,sub=Sub,
+ func_db=Fdb,ret_type=[RetType]},
+ Blk = Blk0#b_blk{is=Is,last=Ret},
+ opt(Bs, D, [{L,Blk}|Acc])
+ end.
+
+simplify_terminator(#b_br{bool=Bool}=Br, Sub, Ts, _Ds) ->
+ Br#b_br{bool=simplify_arg(Bool, Sub, Ts)};
+simplify_terminator(#b_switch{arg=Arg}=Sw, Sub, Ts, _Ds) ->
+ Sw#b_switch{arg=simplify_arg(Arg, Sub, Ts)};
+simplify_terminator(#b_ret{arg=Arg}=Ret, Sub, Ts, Ds) ->
+ %% 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, Sub, Ts)}
+ end.
+
+opt_is([#b_set{op=phi,dst=Dst,args=Args0}=I0|Is],
+ Ts0, Ds0, Fdb, #d{ls=Ls}=D, Sub0, Acc) ->
+ %% Simplify the phi node by removing all predecessor blocks that no
+ %% longer exists or no longer branches to this block.
+ Args = [{simplify_arg(Arg, Sub0, Ts0),From} ||
+ {Arg,From} <- Args0, maps:is_key(From, Ls)],
+ case all_same(Args) of
+ true ->
+ %% Eliminate the phi node if there is just one source
+ %% value or if the values are identical.
+ [{Val,_}|_] = Args,
+ Sub = Sub0#{Dst=>Val},
+ opt_is(Is, Ts0, Ds0, Fdb, D, Sub, Acc);
+ false ->
+ I = I0#b_set{args=Args},
+ Ts = update_types(I, Ts0, Ds0),
+ Ds = Ds0#{Dst=>I},
+ opt_is(Is, Ts, Ds, Fdb, D, Sub0, [I|Acc])
+ end;
+opt_is([#b_set{op=call,args=Args0,dst=Dst}=I0|Is],
+ Ts0, Ds0, Fdb0, D, Sub0, Acc) ->
+ Args = simplify_args(Args0, Sub0, Ts0),
+ I1 = beam_ssa:normalize(I0#b_set{args=Args}),
+ {Ts1,Ds,Fdb,I2} = opt_call(I1, D, Ts0, Ds0, Fdb0),
+ case {map_get(Dst, Ts1),Is} of
+ {_,[#b_set{op=succeeded}]} ->
+ %% This call instruction is inside a try/catch
+ %% block. Don't attempt to optimize it.
+ opt_is(Is, Ts1, Ds, Fdb, D, Sub0, [I2|Acc]);
+ {none,_} ->
+ %% This call never returns. The rest of the
+ %% instructions will not be executed.
+ Ret = #b_ret{arg=Dst},
+ {no_return,Ret,reverse(Acc, [I2]),Ds,Fdb,Sub0};
+ {_,_} ->
+ case simplify_call(I2) of
+ #b_set{}=I ->
+ opt_is(Is, Ts1, Ds, Fdb, D, Sub0, [I|Acc]);
+ #b_literal{}=Lit ->
+ Sub = Sub0#{Dst=>Lit},
+ Ts = maps:remove(Dst, Ts1),
+ opt_is(Is, Ts, Ds0, Fdb, D, Sub, Acc);
+ #b_var{}=Var ->
+ Ts = maps:remove(Dst, Ts1),
+ Sub = Sub0#{Dst=>Var},
+ opt_is(Is, Ts, Ds0, Fdb, D, Sub, Acc)
+ end
+ end;
+opt_is([#b_set{op=succeeded,args=[Arg],dst=Dst}=I],
+ Ts0, Ds0, Fdb, D, Sub0, Acc) ->
+ case Ds0 of
+ #{ Arg := #b_set{op=call} } ->
+ %% The success check of a call is part of exception handling and
+ %% must not be optimized away. We still have to update its type
+ %% though.
+ Ts = update_types(I, Ts0, Ds0),
+ Ds = Ds0#{Dst=>I},
+
+ opt_is([], Ts, Ds, Fdb, D, Sub0, [I|Acc]);
+ #{} ->
+ Args = simplify_args([Arg], Sub0, Ts0),
+ Type = type(succeeded, Args, Ts0, Ds0),
+ case get_literal_from_type(Type) of
+ #b_literal{}=Lit ->
+ Sub = Sub0#{Dst=>Lit},
+ opt_is([], Ts0, Ds0, Fdb, D, Sub, Acc);
+ none ->
+ Ts = Ts0#{Dst=>Type},
+ Ds = Ds0#{Dst=>I},
+ opt_is([], Ts, Ds, Fdb, D, Sub0, [I|Acc])
+ end
+ end;
+opt_is([#b_set{args=Args0,dst=Dst}=I0|Is],
+ Ts0, Ds0, Fdb, D, Sub0, Acc) ->
+ Args = simplify_args(Args0, Sub0, Ts0),
+ I1 = beam_ssa:normalize(I0#b_set{args=Args}),
+ case simplify(I1, Ts0) of
+ #b_set{}=I2 ->
+ I = beam_ssa:normalize(I2),
+ Ts = update_types(I, Ts0, Ds0),
+ Ds = Ds0#{Dst=>I},
+ opt_is(Is, Ts, Ds, Fdb, D, Sub0, [I|Acc]);
+ #b_literal{}=Lit ->
+ Sub = Sub0#{Dst=>Lit},
+ opt_is(Is, Ts0, Ds0, Fdb, D, Sub, Acc);
+ #b_var{}=Var ->
+ case Is of
+ [#b_set{op=succeeded,dst=SuccDst,args=[Dst]}] ->
+ %% We must remove this 'succeeded' instruction.
+ Sub = Sub0#{Dst=>Var,SuccDst=>#b_literal{val=true}},
+ opt_is([], Ts0, Ds0, Fdb, D, Sub, Acc);
+ _ ->
+ Sub = Sub0#{Dst=>Var},
+ opt_is(Is, Ts0, Ds0, Fdb, D, Sub, Acc)
+ end
+ end;
+opt_is([], Ts, Ds, Fdb, _D, Sub, Acc) ->
+ {reverse(Acc), Ts, Ds, Fdb, Sub}.
+
+simplify_call(#b_set{op=call,args=[#b_remote{}=Rem|Args]}=I) ->
+ case Rem of
+ #b_remote{mod=#b_literal{val=Mod},
+ name=#b_literal{val=Name}} ->
+ case erl_bifs:is_pure(Mod, Name, length(Args)) of
+ true ->
+ simplify_remote_call(Mod, Name, Args, I);
+ false ->
+ I
+ end;
+ #b_remote{} ->
+ I
+ end;
+simplify_call(I) -> I.
+
+%% Simplify a remote call to a pure BIF.
+simplify_remote_call(erlang, '++', [#b_literal{val=[]},Tl], _I) ->
+ Tl;
+simplify_remote_call(erlang, setelement,
+ [#b_literal{val=Pos},
+ #b_literal{val=Tuple},
+ #b_var{}=Value], 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, Args0, I) ->
+ case make_literal_list(Args0) of
+ none ->
+ I;
+ Args ->
+ %% The arguments are literals. Try to evaluate the BIF.
+ try apply(Mod, Name, Args) of
+ Val ->
+ case cerl:is_literal_term(Val) of
+ true ->
+ #b_literal{val=Val};
+ false ->
+ %% The value can't be expressed as a literal
+ %% (e.g. a pid).
+ I
+ end
+ catch
+ _:_ ->
+ %% Failed. Don't bother trying to optimize
+ %% the call.
+ I
+ end
+ end.
+
+opt_call(#b_set{dst=Dst,args=[#b_local{}=Callee|Args]}=I0, D, Ts0, Ds0, Fdb0) ->
+ {Ts, Ds, I} = opt_local_call(I0, Ts0, Ds0, Fdb0),
+ case Fdb0 of
+ #{ Callee := #func_info{exported=false,arg_types=ArgTypes0}=Info } ->
+ %% Update the argument types of *this exact call*, the types
+ %% will be joined later when the callee is optimized.
+ CallId = {D#d.func_id, Dst},
+ ArgTypes = update_arg_types(Args, ArgTypes0, CallId, Ts0),
+
+ Fdb = Fdb0#{ Callee => Info#func_info{arg_types=ArgTypes} },
+ {Ts, Ds, Fdb, I};
+ #{} ->
+ %% We can't narrow the argument types of exported functions as they
+ %% can receive anything as part of an external call.
+ {Ts, Ds, Fdb0, I}
+ end;
+opt_call(#b_set{dst=Dst}=I, _D, Ts0, Ds0, Fdb) ->
+ Ts = update_types(I, Ts0, Ds0),
+ Ds = Ds0#{ Dst => I },
+ {Ts, Ds, Fdb, I}.
+
+opt_local_call(#b_set{dst=Dst,args=[Id|_]}=I0, Ts0, Ds0, Fdb) ->
+ Type = case Fdb of
+ #{ Id := #func_info{ret_type=[T]} } -> T;
+ #{} -> any
+ end,
+ I = case Type of
+ any -> I0;
+ none -> I0;
+ _ -> beam_ssa:add_anno(result_type, validator_anno(Type), I0)
+ end,
+ Ts = Ts0#{ Dst => Type },
+ Ds = Ds0#{ Dst => I },
+ {Ts, Ds, I}.
+
+update_arg_types([Arg | Args], [TypeMap0 | TypeMaps], CallId, Ts) ->
+ %% Match contexts are treated as bitstrings when optimizing arguments, as
+ %% we don't yet support removing the "bs_start_match3" instruction.
+ NewType = case get_type(Arg, Ts) of
+ #t_bs_match{} -> {binary, 1};
+ Type -> Type
+ end,
+ TypeMap = TypeMap0#{ CallId => NewType },
+ [TypeMap | update_arg_types(Args, TypeMaps, CallId, Ts)];
+update_arg_types([], [], _CallId, _Ts) ->
+ [].
+
+simplify(#b_set{op={bif,'and'},args=Args}=I, Ts) ->
+ 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)
+ end;
+ false ->
+ I
+ end;
+simplify(#b_set{op={bif,'or'},args=Args}=I, Ts) ->
+ 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)
+ end;
+ false ->
+ I
+ end;
+simplify(#b_set{op={bif,element},args=[#b_literal{val=Index},Tuple]}=I0, Ts) ->
+ case t_tuple_size(get_type(Tuple, Ts)) of
+ {_,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);
+ _ ->
+ eval_bif(I0, Ts)
+ end;
+simplify(#b_set{op={bif,hd},args=[List]}=I, Ts) ->
+ case get_type(List, Ts) of
+ cons ->
+ I#b_set{op=get_hd};
+ _ ->
+ eval_bif(I, Ts)
+ end;
+simplify(#b_set{op={bif,tl},args=[List]}=I, Ts) ->
+ case get_type(List, Ts) of
+ cons ->
+ I#b_set{op=get_tl};
+ _ ->
+ eval_bif(I, Ts)
+ end;
+simplify(#b_set{op={bif,size},args=[Term]}=I, Ts) ->
+ case get_type(Term, Ts) of
+ #t_tuple{} ->
+ simplify(I#b_set{op={bif,tuple_size}}, Ts);
+ _ ->
+ eval_bif(I, Ts)
+ end;
+simplify(#b_set{op={bif,tuple_size},args=[Term]}=I, Ts) ->
+ case get_type(Term, Ts) of
+ #t_tuple{size=Size,exact=true} ->
+ #b_literal{val=Size};
+ _ ->
+ I
+ end;
+simplify(#b_set{op={bif,'=='},args=Args}=I, Ts) ->
+ Types = get_types(Args, Ts),
+ EqEq = case {meet(Types),join(Types)} of
+ {none,any} -> true;
+ {#t_integer{},#t_integer{}} -> true;
+ {float,float} -> true;
+ {{binary,_},_} -> true;
+ {#t_atom{},_} -> true;
+ {_,_} -> false
+ end,
+ case EqEq of
+ true ->
+ simplify(I#b_set{op={bif,'=:='}}, Ts);
+ false ->
+ eval_bif(I, Ts)
+ end;
+simplify(#b_set{op={bif,'=:='},args=[Same,Same]}, _Ts) ->
+ #b_literal{val=true};
+simplify(#b_set{op={bif,'=:='},args=[A1,_A2]=Args}=I, Ts) ->
+ [T1,T2] = get_types(Args, Ts),
+ case meet(T1, T2) of
+ none ->
+ #b_literal{val=false};
+ _ ->
+ case {t_is_boolean(T1),T2} of
+ {true,#t_atom{elements=[true]}} ->
+ %% Bool =:= true ==> Bool
+ A1;
+ {_,_} ->
+ eval_bif(I, Ts)
+ end
+ end;
+simplify(#b_set{op={bif,Op},args=Args}=I, Ts) ->
+ Types = get_types(Args, Ts),
+ case is_float_op(Op, Types) of
+ false ->
+ eval_bif(I, Ts);
+ true ->
+ AnnoArgs = [anno_float_arg(A) || A <- Types],
+ eval_bif(beam_ssa:add_anno(float_op, AnnoArgs, I), Ts)
+ end;
+simplify(#b_set{op=get_tuple_element,args=[Tuple,#b_literal{val=N}]}=I, Ts) ->
+ case get_type(Tuple, Ts) of
+ #t_tuple{size=Size,elements=Es} when Size > N ->
+ ElemType = get_element_type(N + 1, Es),
+ case get_literal_from_type(ElemType) of
+ #b_literal{}=Lit -> Lit;
+ none -> I
+ end;
+ none ->
+ %% Will never be executed because of type conflict.
+ %% #b_literal{val=ignored};
+ I
+ end;
+simplify(#b_set{op=is_nonempty_list,args=[Src]}=I, Ts) ->
+ case get_type(Src, Ts) of
+ any -> I;
+ list -> I;
+ cons -> #b_literal{val=true};
+ _ -> #b_literal{val=false}
+ end;
+simplify(#b_set{op=is_tagged_tuple,
+ args=[Src,#b_literal{val=Size},#b_literal{}=Tag]}=I, Ts) ->
+ simplify_is_record(I, get_type(Src, Ts), Size, Tag, Ts);
+simplify(#b_set{op=put_list,args=[#b_literal{val=H},
+ #b_literal{val=T}]}, _Ts) ->
+ #b_literal{val=[H|T]};
+simplify(#b_set{op=put_tuple,args=Args}=I, _Ts) ->
+ case make_literal_list(Args) of
+ none -> I;
+ List -> #b_literal{val=list_to_tuple(List)}
+ end;
+simplify(#b_set{op=wait_timeout,args=[#b_literal{val=0}]}, _Ts) ->
+ #b_literal{val=true};
+simplify(#b_set{op=wait_timeout,args=[#b_literal{val=infinity}]}=I, _Ts) ->
+ I#b_set{op=wait,args=[]};
+simplify(I, _Ts) -> I.
+
+make_literal_list(Args) ->
+ make_literal_list(Args, []).
+
+make_literal_list([#b_literal{val=H}|T], Acc) ->
+ make_literal_list(T, [H|Acc]);
+make_literal_list([_|_], _) ->
+ none;
+make_literal_list([], Acc) ->
+ reverse(Acc).
+
+is_safe_bool_op(Args, Ts) ->
+ [T1,T2] = get_types(Args, Ts),
+ t_is_boolean(T1) andalso t_is_boolean(T2).
+
+all_same([{H,_}|T]) ->
+ all(fun({E,_}) -> E =:= H end, T).
+
+eval_bif(#b_set{op={bif,Bif},args=Args}=I, Ts) ->
+ Arity = length(Args),
+ case erl_bifs:is_pure(erlang, Bif, Arity) of
+ false ->
+ I;
+ true ->
+ case make_literal_list(Args) of
+ none ->
+ case get_types(Args, Ts) of
+ [any] ->
+ I;
+ [Type] ->
+ case will_succeed(Bif, Type) of
+ yes ->
+ #b_literal{val=true};
+ no ->
+ #b_literal{val=false};
+ maybe ->
+ I
+ end;
+ _ ->
+ I
+ end;
+ LitArgs ->
+ try apply(erlang, Bif, LitArgs) of
+ Val -> #b_literal{val=Val}
+ catch
+ error:_ -> I
+ end
+
+ end
+ end.
+
+simplify_args(Args, Sub, Ts) ->
+ [simplify_arg(Arg, Sub, Ts) || Arg <- Args].
+
+simplify_arg(#b_var{}=Arg0, Sub, Ts) ->
+ case sub_arg(Arg0, Sub) of
+ #b_literal{}=LitArg ->
+ LitArg;
+ #b_var{}=Arg ->
+ Type = get_type(Arg, Ts),
+ case get_literal_from_type(Type) of
+ none -> Arg;
+ #b_literal{}=Lit -> Lit
+ end
+ end;
+simplify_arg(#b_remote{mod=Mod,name=Name}=Rem, Sub, Ts) ->
+ Rem#b_remote{mod=simplify_arg(Mod, Sub, Ts),
+ name=simplify_arg(Name, Sub, Ts)};
+simplify_arg(Arg, _Sub, _Ts) -> Arg.
+
+sub_arg(#b_var{}=Old, Sub) ->
+ case Sub of
+ #{Old:=New} -> New;
+ #{} -> Old
+ end.
+
+is_float_op('-', [float]) ->
+ true;
+is_float_op('/', [_,_]) ->
+ true;
+is_float_op(Op, [float,_Other]) ->
+ is_float_op_1(Op);
+is_float_op(Op, [_Other,float]) ->
+ is_float_op_1(Op);
+is_float_op(_, _) -> false.
+
+is_float_op_1('+') -> true;
+is_float_op_1('-') -> true;
+is_float_op_1('*') -> true;
+is_float_op_1(_) -> false.
+
+anno_float_arg(float) -> float;
+anno_float_arg(_) -> convert.
+
+opt_terminator(#b_br{bool=#b_literal{}}=Br, _Ts, _Ds) ->
+ beam_ssa:normalize(Br);
+opt_terminator(#b_br{bool=#b_var{}}=Br, Ts, Ds) ->
+ simplify_not(Br, Ts, Ds);
+opt_terminator(#b_switch{arg=#b_literal{}}=Sw, _Ts, _Ds) ->
+ beam_ssa:normalize(Sw);
+opt_terminator(#b_switch{arg=#b_var{}=V}=Sw, Ts, Ds) ->
+ case get_type(V, Ts) of
+ any ->
+ beam_ssa:normalize(Sw);
+ Type ->
+ beam_ssa:normalize(opt_switch(Sw, Type, Ts, Ds))
+ end;
+opt_terminator(#b_ret{}=Ret, _Ts, _Ds) -> Ret.
+
+
+opt_switch(#b_switch{fail=Fail,list=List0}=Sw0, Type, Ts, Ds) ->
+ List = prune_switch_list(List0, Fail, Type, Ts),
+ Sw1 = Sw0#b_switch{list=List},
+ case Type of
+ #t_integer{elements={_,_}=Range} ->
+ simplify_switch_int(Sw1, Range);
+ #t_atom{elements=[_|_]} ->
+ case t_is_boolean(Type) of
+ true ->
+ #b_br{} = Br = simplify_switch_bool(Sw1, Ts, Ds),
+ opt_terminator(Br, Ts, Ds);
+ false ->
+ simplify_switch_atom(Type, Sw1)
+ end;
+ _ ->
+ Sw1
+ end.
+
+prune_switch_list([{_,Fail}|T], Fail, Type, Ts) ->
+ prune_switch_list(T, Fail, Type, Ts);
+prune_switch_list([{Arg,_}=Pair|T], Fail, Type, Ts) ->
+ case meet(get_type(Arg, Ts), Type) of
+ none ->
+ %% Different types. This value can never match.
+ prune_switch_list(T, Fail, Type, Ts);
+ _ ->
+ [Pair|prune_switch_list(T, Fail, Type, Ts)]
+ end;
+prune_switch_list([], _, _, _) -> [].
+
+update_successors(#b_br{bool=#b_literal{val=true},succ=S}, Ts, D) ->
+ update_successor(S, Ts, D);
+update_successors(#b_br{bool=#b_var{}=Bool,succ=Succ,fail=Fail}, Ts0, D0) ->
+ case cerl_sets:is_element(Bool, D0#d.once) of
+ true ->
+ %% This variable is defined in this block and is only
+ %% referenced by this br terminator. Therefore, there is
+ %% no need to include it in the type database passed on to
+ %% the successors of this block.
+ Ts = maps:remove(Bool, Ts0),
+ {SuccTs,FailTs} = infer_types_br(Bool, Ts, D0),
+ D = update_successor(Fail, FailTs, D0),
+ update_successor(Succ, SuccTs, D);
+ false ->
+ {SuccTs,FailTs} = infer_types_br(Bool, Ts0, D0),
+ D = update_successor_bool(Bool, false, Fail, FailTs, D0),
+ update_successor_bool(Bool, true, Succ, SuccTs, D)
+ end;
+update_successors(#b_switch{arg=#b_var{}=V,fail=Fail,list=List}, Ts, D0) ->
+ case cerl_sets:is_element(V, D0#d.once) of
+ true ->
+ %% This variable is defined in this block and is only
+ %% referenced by this switch terminator. Therefore, there is
+ %% no need to include it in the type database passed on to
+ %% the successors of this block.
+ D = update_successor(Fail, Ts, D0),
+ F = fun({Val,S}, A) ->
+ SuccTs0 = infer_types_switch(V, Val, Ts, D),
+ SuccTs = maps:remove(V, SuccTs0),
+ update_successor(S, SuccTs, A)
+ end,
+ foldl(F, D, List);
+ false ->
+ %% V can not be equal to any of the values in List at the fail
+ %% block.
+ FailTs = subtract_sw_list(V, List, Ts),
+ D = update_successor(Fail, FailTs, D0),
+ F = fun({Val,S}, A) ->
+ SuccTs = infer_types_switch(V, Val, Ts, D),
+ update_successor(S, SuccTs, A)
+ end,
+ foldl(F, D, List)
+ end;
+update_successors(#b_ret{arg=Arg}, Ts, D) ->
+ FuncId = D#d.func_id,
+ case D#d.ds of
+ #{ Arg := #b_set{op=call,args=[FuncId | _]} } ->
+ %% Returning a call to ourselves doesn't affect our own return
+ %% type.
+ D;
+ #{} ->
+ RetType = join([get_type(Arg, Ts) | D#d.ret_type]),
+ D#d{ret_type=[RetType]}
+ end.
+
+subtract_sw_list(V, List, Ts) ->
+ Ts#{ V := sub_sw_list_1(get_type(V, Ts), List, Ts) }.
+
+sub_sw_list_1(Type, [{Val,_}|T], Ts) ->
+ ValType = get_type(Val, Ts),
+ sub_sw_list_1(subtract(Type, ValType), T, Ts);
+sub_sw_list_1(Type, [], _Ts) ->
+ Type.
+
+update_successor_bool(#b_var{}=Var, BoolValue, S, Ts, D) ->
+ case t_is_boolean(get_type(Var, Ts)) of
+ true ->
+ update_successor(S, Ts#{Var:=t_atom(BoolValue)}, D);
+ false ->
+ %% The `br` terminator is preceeded by an instruction that
+ %% does not produce a boolean value, such a `new_try_tag`.
+ update_successor(S, Ts, D)
+ end.
+
+update_successor(?BADARG_BLOCK, _Ts, #d{}=D) ->
+ %% We KNOW that no variables are used in the ?BADARG_BLOCK,
+ %% so there is no need to update the type information. That
+ %% can be a huge timesaver for huge functions.
+ D;
+update_successor(S, Ts0, #d{ls=Ls}=D) ->
+ case Ls of
+ #{S:=Ts1} ->
+ Ts = join_types(Ts0, Ts1),
+ D#d{ls=Ls#{S:=Ts}};
+ #{} ->
+ D#d{ls=Ls#{S=>Ts0}}
+ end.
+
+update_types(#b_set{op=Op,dst=Dst,args=Args}, Ts, Ds) ->
+ T = type(Op, Args, Ts, Ds),
+ Ts#{Dst=>T}.
+
+type(phi, Args, Ts, _Ds) ->
+ Types = [get_type(A, Ts) || {A,_} <- Args],
+ join(Types);
+type({bif,'band'}, Args, Ts, _Ds) ->
+ band_type(Args, Ts);
+type({bif,Bif}, Args, Ts, _Ds) ->
+ case bif_type(Bif, Args) of
+ number ->
+ arith_op_type(Args, Ts);
+ Type ->
+ Type
+ end;
+type(bs_init, [#b_literal{val=Type}|Args], _Ts, _Ds) ->
+ case {Type,Args} of
+ {new,[_,#b_literal{val=Unit}]} ->
+ {binary,Unit};
+ {append,[_,_,#b_literal{val=Unit}]} ->
+ {binary,Unit};
+ {private_append,[_,_,#b_literal{val=Unit}]} ->
+ {binary,Unit}
+ end;
+type(bs_extract, [Ctx], Ts, _Ds) ->
+ #t_bs_match{type=Type} = get_type(Ctx, Ts),
+ Type;
+type(bs_match, Args, _Ts, _Ds) ->
+ #t_bs_match{type=bs_match_type(Args)};
+type(bs_get_tail, _Args, _Ts, _Ds) ->
+ {binary, 1};
+type(call, [#b_remote{mod=#b_literal{val=Mod},
+ name=#b_literal{val=Name}}|Args], Ts, _Ds) ->
+ case {Mod,Name,Args} of
+ {erlang,setelement,[Pos,Tuple,Arg]} ->
+ case {get_type(Pos, Ts),get_type(Tuple, Ts)} of
+ {#t_integer{elements={Index,Index}},
+ #t_tuple{elements=Es0,size=Size}=T} ->
+ %% This is an exact index, update the type of said element
+ %% or return 'none' if it's known to be out of bounds.
+ Es = set_element_type(Index, get_type(Arg, Ts), 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} ->
+ %% We know this will land between Min and Max, so kill the
+ %% types for those indexes.
+ Es = maps:without(seq(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
+ 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{}
+ end;
+ {erlang,'++',[List1,List2]} ->
+ case get_type(List1, Ts) =:= cons orelse
+ get_type(List2, Ts) =:= cons of
+ true -> cons;
+ false -> list
+ end;
+ {erlang,'--',[_,_]} ->
+ list;
+ {lists,F,Args} ->
+ Types = get_types(Args, Ts),
+ lists_function_type(F, Types);
+ {math,_,_} ->
+ case is_math_bif(Name, length(Args)) of
+ false -> any;
+ true -> float
+ end;
+ {_,_,_} ->
+ case erl_bifs:is_exit_bif(Mod, Name, length(Args)) of
+ true -> none;
+ false -> any
+ end
+ end;
+type(get_tuple_element, [Tuple, Offset], Ts, _Ds) ->
+ #t_tuple{size=Size,elements=Es} = get_type(Tuple, Ts),
+ #b_literal{val=N} = Offset,
+ true = Size > N, %Assertion.
+ get_element_type(N + 1, Es);
+type(is_nonempty_list, [_], _Ts, _Ds) ->
+ t_boolean();
+type(is_tagged_tuple, [_,#b_literal{},#b_literal{}], _Ts, _Ds) ->
+ t_boolean();
+type(put_map, _Args, _Ts, _Ds) ->
+ map;
+type(put_list, _Args, _Ts, _Ds) ->
+ cons;
+type(put_tuple, Args, Ts, _Ds) ->
+ {Es, _} = foldl(fun(Arg, {Es0, Index}) ->
+ Type = get_type(Arg, Ts),
+ Es = set_element_type(Index, Type, Es0),
+ {Es, Index + 1}
+ end, {#{}, 1}, Args),
+ #t_tuple{exact=true,size=length(Args),elements=Es};
+type(succeeded, [#b_var{}=Src], Ts, Ds) ->
+ case maps:get(Src, Ds) of
+ #b_set{op={bif,Bif},args=BifArgs} ->
+ Types = get_types(BifArgs, Ts),
+ case {Bif,Types} of
+ {BoolOp,[T1,T2]} when BoolOp =:= 'and'; BoolOp =:= 'or' ->
+ case t_is_boolean(T1) andalso t_is_boolean(T2) of
+ true -> t_atom(true);
+ false -> t_boolean()
+ end;
+ {byte_size,[{binary,_}]} ->
+ t_atom(true);
+ {bit_size,[{binary,_}]} ->
+ t_atom(true);
+ {map_size,[map]} ->
+ t_atom(true);
+ {'not',[Type]} ->
+ case t_is_boolean(Type) of
+ true -> t_atom(true);
+ false -> t_boolean()
+ end;
+ {size,[{binary,_}]} ->
+ t_atom(true);
+ {tuple_size,[#t_tuple{}]} ->
+ t_atom(true);
+ {_,_} ->
+ t_boolean()
+ end;
+ #b_set{op=get_hd} ->
+ t_atom(true);
+ #b_set{op=get_tl} ->
+ t_atom(true);
+ #b_set{op=get_tuple_element} ->
+ t_atom(true);
+ #b_set{op=wait} ->
+ t_atom(false);
+ #b_set{} ->
+ t_boolean()
+ end;
+type(succeeded, [#b_literal{}], _Ts, _Ds) ->
+ t_atom(true);
+type(_, _, _, _) -> any.
+
+arith_op_type(Args, Ts) ->
+ Types = get_types(Args, Ts),
+ foldl(fun(#t_integer{}, unknown) -> t_integer();
+ (#t_integer{}, number) -> number;
+ (#t_integer{}, float) -> float;
+ (#t_integer{}, #t_integer{}) -> t_integer();
+ (float, unknown) -> float;
+ (float, #t_integer{}) -> float;
+ (float, number) -> float;
+ (number, unknown) -> number;
+ (number, #t_integer{}) -> number;
+ (number, float) -> float;
+ (any, _) -> number;
+ (Same, Same) -> Same;
+ (_, _) -> none
+ end, unknown, Types).
+
+lists_function_type(F, Types) ->
+ case {F,Types} of
+ %% Functions that return booleans.
+ {all,[_,_]} ->
+ t_boolean();
+ {any,[_,_]} ->
+ t_boolean();
+ {keymember,[_,_,_]} ->
+ t_boolean();
+ {member,[_,_]} ->
+ t_boolean();
+ {prefix,[_,_]} ->
+ t_boolean();
+ {suffix,[_,_]} ->
+ t_boolean();
+
+ %% Functions that return lists.
+ {dropwhile,[_,_]} ->
+ list;
+ {duplicate,[_,_]} ->
+ list;
+ {filter,[_,_]} ->
+ list;
+ {flatten,[_]} ->
+ list;
+ {map,[_Fun,List]} ->
+ same_length_type(List);
+ {MapFold,[_Fun,_Acc,List]} when MapFold =:= mapfoldl;
+ MapFold =:= mapfoldr ->
+ #t_tuple{size=2,exact=true,
+ elements=#{1=>same_length_type(List)}};
+ {partition,[_,_]} ->
+ t_two_tuple(list, list);
+ {reverse,[List]} ->
+ same_length_type(List);
+ {sort,[List]} ->
+ same_length_type(List);
+ {splitwith,[_,_]} ->
+ t_two_tuple(list, list);
+ {takewhile,[_,_]} ->
+ list;
+ {unzip,[List]} ->
+ ListType = same_length_type(List),
+ t_two_tuple(ListType, ListType);
+ {usort,[List]} ->
+ same_length_type(List);
+ {zip,[_,_]} ->
+ list;
+ {zipwith,[_,_,_]} ->
+ list;
+ {_,_} ->
+ any
+ end.
+
+%% For a lists function that return a list of the same
+%% length as the input list, return the type of the list.
+same_length_type(cons) -> cons;
+same_length_type(nil) -> nil;
+same_length_type(_) -> list.
+
+t_two_tuple(Type1, Type2) ->
+ #t_tuple{size=2,exact=true,
+ elements=#{1=>Type1,2=>Type2}}.
+
+%% will_succeed(TestOperation, Type) -> yes|no|maybe.
+%% Test whether TestOperation applied to an argument of type Type
+%% will succeed. Return yes, no, or maybe.
+%%
+%% Type is a type as described in the comment for verified_type/1 at
+%% the very end of this file, but it will *never* be 'any'.
+
+will_succeed(is_atom, Type) ->
+ case Type of
+ #t_atom{} -> yes;
+ _ -> no
+ end;
+will_succeed(is_binary, Type) ->
+ case Type of
+ {binary,U} when U rem 8 =:= 0 -> yes;
+ {binary,_} -> maybe;
+ _ -> no
+ end;
+will_succeed(is_bitstring, Type) ->
+ case Type of
+ {binary,_} -> yes;
+ _ -> no
+ end;
+will_succeed(is_boolean, Type) ->
+ case Type of
+ #t_atom{elements=any} ->
+ maybe;
+ #t_atom{elements=Es} ->
+ case t_is_boolean(Type) of
+ true ->
+ yes;
+ false ->
+ case any(fun is_boolean/1, Es) of
+ true -> maybe;
+ false -> no
+ end
+ end;
+ _ ->
+ no
+ end;
+will_succeed(is_float, Type) ->
+ case Type of
+ float -> yes;
+ number -> maybe;
+ _ -> no
+ end;
+will_succeed(is_integer, Type) ->
+ case Type of
+ #t_integer{} -> yes;
+ number -> maybe;
+ _ -> no
+ end;
+will_succeed(is_list, Type) ->
+ case Type of
+ list -> yes;
+ cons -> yes;
+ _ -> no
+ end;
+will_succeed(is_map, Type) ->
+ case Type of
+ map -> yes;
+ _ -> no
+ end;
+will_succeed(is_number, Type) ->
+ case Type of
+ float -> yes;
+ #t_integer{} -> yes;
+ number -> yes;
+ _ -> no
+ end;
+will_succeed(is_tuple, Type) ->
+ case Type of
+ #t_tuple{} -> yes;
+ _ -> no
+ end;
+will_succeed(_, _) -> maybe.
+
+
+band_type([Other,#b_literal{val=Int}], Ts) when is_integer(Int) ->
+ band_type_1(Int, Other, Ts);
+band_type([_,_], _) -> t_integer().
+
+band_type_1(Int, OtherSrc, Ts) ->
+ Type = band_type_2(Int, 0),
+ OtherType = get_type(OtherSrc, Ts),
+ meet(Type, OtherType).
+
+band_type_2(N, Bits) when Bits < 64 ->
+ case 1 bsl Bits of
+ P when P =:= N + 1 ->
+ t_integer(0, N);
+ P when P > N + 1 ->
+ t_integer();
+ _ ->
+ band_type_2(N, Bits+1)
+ end;
+band_type_2(_, _) ->
+ %% Negative or large positive number. Give up.
+ t_integer().
+
+bs_match_type([#b_literal{val=Type}|Args]) ->
+ bs_match_type(Type, Args).
+
+bs_match_type(binary, Args) ->
+ [_,_,_,#b_literal{val=U}] = Args,
+ {binary,U};
+bs_match_type(float, _) ->
+ 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,
+ case member(unsigned, Flags) of
+ true ->
+ t_integer(0, (1 bsl NumBits)-1);
+ false ->
+ %% Signed integer. Don't bother.
+ t_integer()
+ end;
+ [_|_] ->
+ t_integer()
+ end;
+bs_match_type(skip, _) ->
+ any;
+bs_match_type(string, _) ->
+ any;
+bs_match_type(utf8, _) ->
+ ?UNICODE_INT;
+bs_match_type(utf16, _) ->
+ ?UNICODE_INT;
+bs_match_type(utf32, _) ->
+ ?UNICODE_INT.
+
+simplify_switch_atom(#t_atom{elements=Atoms}, #b_switch{list=List0}=Sw) ->
+ case sort([A || {#b_literal{val=A},_} <- List0]) of
+ Atoms ->
+ %% All possible atoms are included in the list. The
+ %% failure label will never be used.
+ [{_,Fail}|List] = List0,
+ Sw#b_switch{fail=Fail,list=List};
+ _ ->
+ Sw
+ end.
+
+simplify_switch_int(#b_switch{list=List0}=Sw, {Min,Max}) ->
+ List1 = sort(List0),
+ Vs = [V || {#b_literal{val=V},_} <- List1],
+ case eq_ranges(Vs, Min, Max) of
+ true ->
+ {_,LastL} = last(List1),
+ List = droplast(List1),
+ Sw#b_switch{fail=LastL,list=List};
+ false ->
+ Sw
+ end.
+
+eq_ranges([H], H, H) -> true;
+eq_ranges([H|T], H, Max) -> eq_ranges(T, H+1, Max);
+eq_ranges(_, _, _) -> false.
+
+simplify_is_record(I, #t_tuple{exact=Exact,
+ size=Size,
+ elements=Es},
+ RecSize, RecTag, Ts) ->
+ TagType = maps:get(1, Es, any),
+ TagMatch = case get_literal_from_type(TagType) of
+ #b_literal{}=RecTag -> yes;
+ #b_literal{} -> no;
+ none ->
+ %% Is it at all possible for the tag to match?
+ case meet(get_type(RecTag, Ts), TagType) of
+ none -> no;
+ _ -> maybe
+ end
+ end,
+ if
+ Size =/= RecSize, Exact; Size > RecSize; TagMatch =:= no ->
+ #b_literal{val=false};
+ Size =:= RecSize, Exact, TagMatch =:= yes ->
+ #b_literal{val=true};
+ true ->
+ I
+ end;
+simplify_is_record(I, any, _Size, _Tag, _Ts) ->
+ I;
+simplify_is_record(_I, _Type, _Size, _Tag, _Ts) ->
+ #b_literal{val=false}.
+
+simplify_switch_bool(#b_switch{arg=B,fail=Fail,list=List0}, Ts, Ds) ->
+ FalseVal = #b_literal{val=false},
+ TrueVal = #b_literal{val=true},
+ List1 = List0 ++ [{FalseVal,Fail},{TrueVal,Fail}],
+ {_,FalseLbl} = keyfind(FalseVal, 1, List1),
+ {_,TrueLbl} = keyfind(TrueVal, 1, List1),
+ Br = beam_ssa:normalize(#b_br{bool=B,succ=TrueLbl,fail=FalseLbl}),
+ simplify_not(Br, Ts, Ds).
+
+simplify_not(#b_br{bool=#b_var{}=V,succ=Succ,fail=Fail}=Br0, Ts, Ds) ->
+ case Ds of
+ #{V:=#b_set{op={bif,'not'},args=[Bool]}} ->
+ case t_is_boolean(get_type(Bool, Ts)) of
+ true ->
+ Br = Br0#b_br{bool=Bool,succ=Fail,fail=Succ},
+ beam_ssa:normalize(Br);
+ false ->
+ Br0
+ end;
+ #{} ->
+ Br0
+ end;
+simplify_not(#b_br{bool=#b_literal{}}=Br, _Ts, _Ds) -> Br.
+
+%%%
+%%% Calculate the set of variables that are only used once in the
+%%% terminator of the block that defines them. That will allow us to
+%%% discard type information for variables that will never be
+%%% referenced by the successor blocks, potentially improving
+%%% compilation times.
+%%%
+
+used_once(Linear, Args) ->
+ Map0 = used_once_1(reverse(Linear), #{}),
+ Map = maps:without(Args, Map0),
+ cerl_sets:from_list(maps:keys(Map)).
+
+used_once_1([{L,#b_blk{is=Is,last=Last}}|Bs], Uses0) ->
+ Uses1 = used_once_last_uses(beam_ssa:used(Last), L, Uses0),
+ Uses = used_once_2(reverse(Is), L, Uses1),
+ used_once_1(Bs, Uses);
+used_once_1([], Uses) -> Uses.
+
+used_once_2([#b_set{dst=Dst}=I|Is], L, Uses0) ->
+ Uses = used_once_uses(beam_ssa:used(I), L, Uses0),
+ case Uses of
+ #{Dst:=[L]} ->
+ used_once_2(Is, L, Uses);
+ #{} ->
+ %% Used more than once or used once in
+ %% in another block.
+ used_once_2(Is, L, maps:remove(Dst, Uses))
+ end;
+used_once_2([], _, Uses) -> Uses.
+
+used_once_uses([V|Vs], L, Uses) ->
+ case Uses of
+ #{V:=more_than_once} ->
+ used_once_uses(Vs, L, Uses);
+ #{} ->
+ %% Already used or first use is not in
+ %% a terminator.
+ used_once_uses(Vs, L, Uses#{V=>more_than_once})
+ end;
+used_once_uses([], _, Uses) -> Uses.
+
+used_once_last_uses([V|Vs], L, Uses) ->
+ case Uses of
+ #{V:=[_]} ->
+ %% Second time this variable is used.
+ used_once_last_uses(Vs, L, Uses#{V:=more_than_once});
+ #{V:=more_than_once} ->
+ %% Used at least twice before.
+ used_once_last_uses(Vs, L, Uses);
+ #{} ->
+ %% First time this variable is used.
+ used_once_last_uses(Vs, L, Uses#{V=>[L]})
+ end;
+used_once_last_uses([], _, Uses) -> Uses.
+
+
+get_types(Values, Ts) ->
+ [get_type(Val, Ts) || Val <- Values].
+-spec get_type(beam_ssa:value(), type_db()) -> type().
+
+get_type(#b_var{}=V, Ts) ->
+ #{V:=T} = Ts,
+ T;
+get_type(#b_literal{val=Val}, _Ts) ->
+ if
+ is_atom(Val) ->
+ t_atom(Val);
+ is_float(Val) ->
+ float;
+ is_integer(Val) ->
+ t_integer(Val);
+ is_list(Val), Val =/= [] ->
+ cons;
+ is_map(Val) ->
+ map;
+ Val =:= {} ->
+ #t_tuple{exact=true};
+ is_tuple(Val) ->
+ {Es, _} = foldl(fun(E, {Es0, Index}) ->
+ Type = get_type(#b_literal{val=E}, #{}),
+ Es = set_element_type(Index, Type, Es0),
+ {Es, Index + 1}
+ end, {#{}, 1}, tuple_to_list(Val)),
+ #t_tuple{exact=true,size=tuple_size(Val),elements=Es};
+ Val =:= [] ->
+ nil;
+ true ->
+ any
+ end.
+
+%% infer_types(Var, Types, #d{}) -> {SuccTypes,FailTypes}
+%% Looking at the expression that defines the variable Var, infer
+%% the types for the variables in the arguments. Return the updated
+%% type database for the case that the expression evaluates to
+%% true, and and for the case that it evaluates to false.
+%%
+%% Here is an example. The variable being asked about is
+%% the variable Bool, which is defined like this:
+%%
+%% Bool = is_nonempty_list L
+%%
+%% If 'is_nonempty_list L' evaluates to 'true', L must
+%% must be cons. The meet of the previously known type of L and 'cons'
+%% will be added to SuccTypes.
+%%
+%% On the other hand, if 'is_nonempty_list L' evaluates to false, L
+%% is not cons and cons can be subtracted from the previously known
+%% type for L. For example, if L was known to be 'list', subtracting
+%% 'cons' would give 'nil' as the only possible type. The result of the
+%% subtraction for L will be added to FailTypes.
+%%
+%% Here is another example, asking about the variable Bool:
+%%
+%% Head = bif:hd L
+%% Bool = succeeded Head
+%%
+%% 'succeeded Head' will evaluate to 'true' if the instrution that
+%% defined Head succeeded. In this case, it is the 'bif:hd L'
+%% instruction, which will succeed if L is 'cons'. Thus, the meet of
+%% the previous type for L and 'cons' will be added to SuccTypes.
+%%
+%% If 'succeeded Head' evaluates to 'false', it means that 'bif:hd L'
+%% failed and that L is not 'cons'. 'cons' can be subtracted from the
+%% previously known type for L and the result put in FailTypes.
+
+infer_types_br(#b_var{}=V, Ts, #d{ds=Ds}) ->
+ #{V:=#b_set{op=Op,args=Args}} = Ds,
+ Types0 = infer_type(Op, Args, Ds),
+
+ %% 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.
+ %%
+ %% However, it is safe to subtract a type inferred from '=:=' if
+ %% it is single-valued, e.g. if it is [] or the atom 'true'.
+ EqTypes0 = infer_eq_type(Op, Args, Ts, Ds),
+ {Types1,EqTypes} = partition(fun({_,T}) ->
+ is_singleton_type(T)
+ end, EqTypes0),
+
+ Types = Types1 ++ Types0,
+ {meet_types(EqTypes++Types, Ts),subtract_types(Types, Ts)}.
+
+infer_types_switch(V, Lit, Ts, #d{ds=Ds}) ->
+ Types = infer_eq_type({bif,'=:='}, [V, Lit], Ts, Ds),
+ meet_types(Types, Ts).
+
+infer_eq_type({bif,'=:='}, [#b_var{}=Src,#b_literal{}=Lit], Ts, Ds) ->
+ Def = maps:get(Src, Ds),
+ Type = get_type(Lit, Ts),
+ [{Src,Type} | infer_eq_lit(Def, Lit)];
+infer_eq_type({bif,'=:='}, [#b_var{}=Arg0,#b_var{}=Arg1], 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').
+ Type0 = get_type(Arg0, Ts),
+ Type1 = get_type(Arg1, Ts),
+ Type = meet(Type0, Type1),
+ [{V,MeetType} ||
+ {V,OrigType,MeetType} <-
+ [{Arg0,Type0,Type},{Arg1,Type1,Type}],
+ OrigType =/= MeetType];
+infer_eq_type(_Op, _Args, _Ts, _Ds) ->
+ [].
+
+infer_eq_lit(#b_set{op={bif,tuple_size},args=[#b_var{}=Tuple]},
+ #b_literal{val=Size}) when is_integer(Size) ->
+ [{Tuple,#t_tuple{exact=true,size=Size}}];
+infer_eq_lit(#b_set{op=get_tuple_element,
+ args=[#b_var{}=Tuple,#b_literal{val=N}]},
+ #b_literal{}=Lit) ->
+ Index = N + 1,
+ Es = set_element_type(Index, get_type(Lit, #{}), #{}),
+ [{Tuple,#t_tuple{size=Index,elements=Es}}];
+infer_eq_lit(_, _) -> [].
+
+infer_type({bif,element}, [#b_literal{val=Pos},#b_var{}=Tuple], _Ds) ->
+ if
+ is_integer(Pos), 1 =< Pos ->
+ [{Tuple,#t_tuple{size=Pos}}];
+ true ->
+ []
+ end;
+infer_type({bif,element}, [#b_var{}=Position,#b_var{}=Tuple], _Ds) ->
+ [{Position,t_integer()},{Tuple,#t_tuple{}}];
+infer_type({bif,Bif}, [#b_var{}=Src]=Args, _Ds) ->
+ case inferred_bif_type(Bif, Args) of
+ any -> [];
+ T -> [{Src,T}]
+ end;
+infer_type({bif,binary_part}, [#b_var{}=Src,_], _Ds) ->
+ [{Src,{binary,8}}];
+infer_type({bif,is_map_key}, [_,#b_var{}=Src], _Ds) ->
+ [{Src,map}];
+infer_type({bif,map_get}, [_,#b_var{}=Src], _Ds) ->
+ [{Src,map}];
+infer_type({bif,Bif}, [_,_]=Args, _Ds) ->
+ case inferred_bif_type(Bif, Args) of
+ any -> [];
+ T -> [{A,T} || #b_var{}=A <- Args]
+ end;
+infer_type({bif,binary_part}, [#b_var{}=Src,Pos,Len], _Ds) ->
+ [{Src,{binary,8}}|
+ [{V,t_integer()} || #b_var{}=V <- [Pos,Len]]];
+infer_type(bs_start_match, [#b_var{}=Bin], _Ds) ->
+ [{Bin,{binary,1}}];
+infer_type(is_nonempty_list, [#b_var{}=Src], _Ds) ->
+ [{Src,cons}];
+infer_type(is_tagged_tuple, [#b_var{}=Src,#b_literal{val=Size},
+ #b_literal{}=Tag], _Ds) ->
+ Es = set_element_type(1, get_type(Tag, #{}), #{}),
+ [{Src,#t_tuple{exact=true,size=Size,elements=Es}}];
+infer_type(succeeded, [#b_var{}=Src], Ds) ->
+ #b_set{op=Op,args=Args} = maps:get(Src, Ds),
+ infer_type(Op, Args, Ds);
+infer_type(_Op, _Args, _Ds) ->
+ [].
+
+%% bif_type(Name, Args) -> Type
+%% Return the return type for the guard BIF or operator Name with
+%% arguments Args.
+%%
+%% Note that that the following BIFs are handle elsewhere:
+%%
+%% band/2
+
+bif_type(abs, [_]) -> number;
+bif_type(bit_size, [_]) -> t_integer();
+bif_type(byte_size, [_]) -> t_integer();
+bif_type(ceil, [_]) -> t_integer();
+bif_type(float, [_]) -> float;
+bif_type(floor, [_]) -> t_integer();
+bif_type(is_map_key, [_,_]) -> t_boolean();
+bif_type(length, [_]) -> t_integer();
+bif_type(map_size, [_]) -> t_integer();
+bif_type(node, []) -> #t_atom{};
+bif_type(node, [_]) -> #t_atom{};
+bif_type(round, [_]) -> t_integer();
+bif_type(size, [_]) -> t_integer();
+bif_type(trunc, [_]) -> t_integer();
+bif_type(tuple_size, [_]) -> t_integer();
+bif_type('bnot', [_]) -> t_integer();
+bif_type('bor', [_,_]) -> t_integer();
+bif_type('bsl', [_,_]) -> t_integer();
+bif_type('bsr', [_,_]) -> t_integer();
+bif_type('bxor', [_,_]) -> t_integer();
+bif_type('div', [_,_]) -> t_integer();
+bif_type('rem', [_,_]) -> t_integer();
+bif_type('/', [_,_]) -> float;
+bif_type(Name, Args) ->
+ Arity = length(Args),
+ case erl_internal:new_type_test(Name, Arity) orelse
+ erl_internal:bool_op(Name, Arity) orelse
+ erl_internal:comp_op(Name, Arity) of
+ true ->
+ t_boolean();
+ false ->
+ case erl_internal:arith_op(Name, Arity) of
+ true -> number;
+ false -> any
+ end
+ end.
+
+inferred_bif_type(is_atom, [_]) -> t_atom();
+inferred_bif_type(is_binary, [_]) -> {binary,8};
+inferred_bif_type(is_bitstring, [_]) -> {binary,1};
+inferred_bif_type(is_boolean, [_]) -> t_boolean();
+inferred_bif_type(is_float, [_]) -> float;
+inferred_bif_type(is_integer, [_]) -> t_integer();
+inferred_bif_type(is_list, [_]) -> list;
+inferred_bif_type(is_map, [_]) -> map;
+inferred_bif_type(is_number, [_]) -> number;
+inferred_bif_type(is_tuple, [_]) -> #t_tuple{};
+inferred_bif_type(abs, [_]) -> number;
+inferred_bif_type(bit_size, [_]) -> {binary,1};
+inferred_bif_type('bnot', [_]) -> t_integer();
+inferred_bif_type(byte_size, [_]) -> {binary,1};
+inferred_bif_type(ceil, [_]) -> number;
+inferred_bif_type(float, [_]) -> number;
+inferred_bif_type(floor, [_]) -> number;
+inferred_bif_type(hd, [_]) -> cons;
+inferred_bif_type(length, [_]) -> list;
+inferred_bif_type(map_size, [_]) -> map;
+inferred_bif_type('not', [_]) -> t_boolean();
+inferred_bif_type(round, [_]) -> number;
+inferred_bif_type(trunc, [_]) -> number;
+inferred_bif_type(tl, [_]) -> cons;
+inferred_bif_type(tuple_size, [_]) -> #t_tuple{};
+inferred_bif_type('and', [_,_]) -> t_boolean();
+inferred_bif_type('or', [_,_]) -> t_boolean();
+inferred_bif_type('xor', [_,_]) -> t_boolean();
+inferred_bif_type('band', [_,_]) -> t_integer();
+inferred_bif_type('bor', [_,_]) -> t_integer();
+inferred_bif_type('bsl', [_,_]) -> t_integer();
+inferred_bif_type('bsr', [_,_]) -> t_integer();
+inferred_bif_type('bxor', [_,_]) -> t_integer();
+inferred_bif_type('div', [_,_]) -> t_integer();
+inferred_bif_type('rem', [_,_]) -> t_integer();
+inferred_bif_type('+', [_,_]) -> number;
+inferred_bif_type('-', [_,_]) -> number;
+inferred_bif_type('*', [_,_]) -> number;
+inferred_bif_type('/', [_,_]) -> number;
+inferred_bif_type(_, _) -> any.
+
+is_math_bif(cos, 1) -> true;
+is_math_bif(cosh, 1) -> true;
+is_math_bif(sin, 1) -> true;
+is_math_bif(sinh, 1) -> true;
+is_math_bif(tan, 1) -> true;
+is_math_bif(tanh, 1) -> true;
+is_math_bif(acos, 1) -> true;
+is_math_bif(acosh, 1) -> true;
+is_math_bif(asin, 1) -> true;
+is_math_bif(asinh, 1) -> true;
+is_math_bif(atan, 1) -> true;
+is_math_bif(atanh, 1) -> true;
+is_math_bif(erf, 1) -> true;
+is_math_bif(erfc, 1) -> true;
+is_math_bif(exp, 1) -> true;
+is_math_bif(log, 1) -> true;
+is_math_bif(log2, 1) -> true;
+is_math_bif(log10, 1) -> true;
+is_math_bif(sqrt, 1) -> true;
+is_math_bif(atan2, 2) -> true;
+is_math_bif(pow, 2) -> true;
+is_math_bif(ceil, 1) -> true;
+is_math_bif(floor, 1) -> true;
+is_math_bif(fmod, 2) -> true;
+is_math_bif(pi, 0) -> true;
+is_math_bif(_, _) -> false.
+
+join_types(Ts0, Ts1) ->
+ if
+ map_size(Ts0) < map_size(Ts1) ->
+ join_types_1(maps:keys(Ts0), Ts1, Ts0);
+ true ->
+ join_types_1(maps:keys(Ts1), Ts0, Ts1)
+ end.
+
+join_types_1([V|Vs], Ts0, Ts1) ->
+ case {Ts0,Ts1} of
+ {#{V:=Same},#{V:=Same}} ->
+ join_types_1(Vs, Ts0, Ts1);
+ {#{V:=T0},#{V:=T1}} ->
+ case join(T0, T1) of
+ T1 ->
+ join_types_1(Vs, Ts0, Ts1);
+ T ->
+ join_types_1(Vs, Ts0, Ts1#{V:=T})
+ end;
+ {#{},#{V:=_}} ->
+ join_types_1(Vs, Ts0, Ts1)
+ end;
+join_types_1([], Ts0, Ts1) ->
+ maps:merge(Ts0, Ts1).
+
+join([T1,T2|Ts]) ->
+ join([join(T1, T2)|Ts]);
+join([T]) -> T.
+
+get_literal_from_type(#t_atom{elements=[Atom]}) ->
+ #b_literal{val=Atom};
+get_literal_from_type(#t_integer{elements={Int,Int}}) ->
+ #b_literal{val=Int};
+get_literal_from_type(nil) ->
+ #b_literal{val=[]};
+get_literal_from_type(_) -> none.
+
+t_atom() ->
+ #t_atom{elements=any}.
+
+t_atom(Atom) when is_atom(Atom) ->
+ #t_atom{elements=[Atom]}.
+
+t_boolean() ->
+ #t_atom{elements=[false,true]}.
+
+t_integer() ->
+ #t_integer{elements=any}.
+
+t_integer(Int) when is_integer(Int) ->
+ #t_integer{elements={Int,Int}}.
+
+t_integer(Min, Max) when is_integer(Min), is_integer(Max) ->
+ #t_integer{elements={Min,Max}}.
+
+t_is_boolean(#t_atom{elements=[F,T]}) ->
+ F =:= false andalso T =:= true;
+t_is_boolean(#t_atom{elements=[B]}) ->
+ is_boolean(B);
+t_is_boolean(_) -> false.
+
+t_tuple_size(#t_tuple{size=Size,exact=false}) ->
+ {at_least,Size};
+t_tuple_size(#t_tuple{size=Size,exact=true}) ->
+ {exact,Size};
+t_tuple_size(_) ->
+ none.
+
+is_singleton_type(Type) ->
+ get_literal_from_type(Type) =/= none.
+
+get_element_type(Index, Es) ->
+ case Es of
+ #{ Index := T } -> T;
+ #{} -> any
+ end.
+
+set_element_type(_Key, none, Es) ->
+ Es;
+set_element_type(Key, any, Es) ->
+ maps:remove(Key, Es);
+set_element_type(Key, Type, Es) ->
+ Es#{ Key => Type }.
+
+%% join(Type1, Type2) -> Type
+%% Return the "join" of Type1 and Type2. The join is a more general
+%% type than Type1 and Type2. For example:
+%%
+%% join(#t_integer{elements=any}, #t_integer=elements={0,3}}) ->
+%% #t_integer{}
+%%
+%% The join for two different types result in 'any', which is
+%% the top element for our type lattice:
+%%
+%% join(#t_integer{}, map) -> any
+
+-spec join(type(), type()) -> type().
+
+join(T, T) ->
+ verified_type(T);
+join(none, T) ->
+ verified_type(T);
+join(T, none) ->
+ verified_type(T);
+join(any, _) -> any;
+join(_, any) -> any;
+join(#t_atom{elements=[_|_]=Set1}, #t_atom{elements=[_|_]=Set2}) ->
+ Set = ordsets:union(Set1, Set2),
+ case ordsets:size(Set) of
+ Size when Size =< ?ATOM_SET_SIZE ->
+ #t_atom{elements=Set};
+ _Size ->
+ #t_atom{elements=any}
+ end;
+join(#t_atom{elements=any}=T, #t_atom{elements=[_|_]}) -> T;
+join(#t_atom{elements=[_|_]}, #t_atom{elements=any}=T) -> T;
+join({binary,U1}, {binary,U2}) ->
+ {binary,gcd(U1, U2)};
+join(#t_integer{}, #t_integer{}) -> t_integer();
+join(list, cons) -> list;
+join(cons, list) -> list;
+join(nil, cons) -> list;
+join(cons, nil) -> list;
+join(nil, list) -> list;
+join(list, nil) -> list;
+join(#t_integer{}, float) -> number;
+join(float, #t_integer{}) -> number;
+join(#t_integer{}, number) -> number;
+join(number, #t_integer{}) -> number;
+join(float, number) -> number;
+join(number, float) -> number;
+join(#t_tuple{size=Sz,exact=ExactA,elements=EsA},
+ #t_tuple{size=Sz,exact=ExactB,elements=EsB}) ->
+ Exact = ExactA and ExactB,
+ Es = join_tuple_elements(Sz, EsA, EsB),
+ #t_tuple{size=Sz,exact=Exact,elements=Es};
+join(#t_tuple{size=SzA,elements=EsA}, #t_tuple{size=SzB,elements=EsB}) ->
+ Sz = min(SzA, SzB),
+ Es = join_tuple_elements(Sz, EsA, EsB),
+ #t_tuple{size=Sz,elements=Es};
+join(_T1, _T2) ->
+ %%io:format("~p ~p\n", [_T1,_T2]),
+ any.
+
+join_tuple_elements(MinSize, EsA, EsB) ->
+ Es0 = join_elements(EsA, EsB),
+ maps:filter(fun(Index, _Type) -> Index =< MinSize end, Es0).
+
+join_elements(Es1, Es2) ->
+ Keys = if
+ map_size(Es1) =< map_size(Es2) -> maps:keys(Es1);
+ map_size(Es1) > map_size(Es2) -> maps:keys(Es2)
+ end,
+ join_elements_1(Keys, Es1, Es2, #{}).
+
+join_elements_1([Key | Keys], Es1, Es2, Acc0) ->
+ case {Es1, Es2} of
+ {#{ Key := Type1 }, #{ Key := Type2 }} ->
+ Acc = set_element_type(Key, join(Type1, Type2), Acc0),
+ join_elements_1(Keys, Es1, Es2, Acc);
+ {#{}, #{}} ->
+ join_elements_1(Keys, Es1, Es2, Acc0)
+ end;
+join_elements_1([], _Es1, _Es2, Acc) ->
+ Acc.
+
+gcd(A, B) ->
+ case A rem B of
+ 0 -> B;
+ X -> gcd(B, X)
+ end.
+
+meet_types([{V,T0}|Vs], Ts) ->
+ #{V:=T1} = Ts,
+ case meet(T0, T1) of
+ T1 -> meet_types(Vs, Ts);
+ T -> meet_types(Vs, Ts#{V:=T})
+ end;
+meet_types([], Ts) -> Ts.
+
+meet([T1,T2|Ts]) ->
+ meet([meet(T1, T2)|Ts]);
+meet([T]) -> T.
+
+subtract_types([{V,T0}|Vs], Ts) ->
+ #{V:=T1} = Ts,
+ case subtract(T1, T0) of
+ T1 -> subtract_types(Vs, Ts);
+ T -> subtract_types(Vs, Ts#{V:=T})
+ end;
+subtract_types([], Ts) -> Ts.
+
+%% subtract(Type1, Type2) -> Type.
+%% Subtract Type2 from Type1. Example:
+%%
+%% subtract(list, cons) -> nil
+
+subtract(#t_atom{elements=[_|_]=Set0}, #t_atom{elements=[_|_]=Set1}) ->
+ case ordsets:subtract(Set0, Set1) of
+ [] -> none;
+ [_|_]=Set -> #t_atom{elements=Set}
+ end;
+subtract(number, float) -> #t_integer{};
+subtract(number, #t_integer{elements=any}) -> float;
+subtract(list, cons) -> nil;
+subtract(list, nil) -> cons;
+subtract(T, _) -> T.
+
+%% meet(Type1, Type2) -> Type
+%% Return the "meet" of Type1 and Type2. The meet is a narrower
+%% type than Type1 and Type2. For example:
+%%
+%% meet(#t_integer{elements=any}, #t_integer{elements={0,3}}) ->
+%% #t_integer{elements={0,3}}
+%%
+%% The meet for two different types result in 'none', which is
+%% the bottom element for our type lattice:
+%%
+%% meet(#t_integer{}, map) -> none
+
+-spec meet(type(), type()) -> type().
+
+meet(T, T) ->
+ verified_type(T);
+meet(#t_atom{elements=[_|_]=Set1}, #t_atom{elements=[_|_]=Set2}) ->
+ case ordsets:intersection(Set1, Set2) of
+ [] ->
+ none;
+ [_|_]=Set ->
+ #t_atom{elements=Set}
+ end;
+meet(#t_atom{elements=[_|_]}=T, #t_atom{elements=any}) ->
+ T;
+meet(#t_atom{elements=any}, #t_atom{elements=[_|_]}=T) ->
+ T;
+meet(#t_integer{elements={_,_}}=T, #t_integer{elements=any}) ->
+ T;
+meet(#t_integer{elements=any}, #t_integer{elements={_,_}}=T) ->
+ T;
+meet(#t_integer{elements={Min1,Max1}},
+ #t_integer{elements={Min2,Max2}}) ->
+ #t_integer{elements={max(Min1, Min2),min(Max1, Max2)}};
+meet(#t_integer{}=T, number) -> T;
+meet(float=T, number) -> T;
+meet(number, #t_integer{}=T) -> T;
+meet(number, float=T) -> T;
+meet(list, cons) -> cons;
+meet(list, nil) -> nil;
+meet(cons, list) -> cons;
+meet(nil, list) -> nil;
+meet(#t_tuple{}=T1, #t_tuple{}=T2) ->
+ meet_tuples(T1, T2);
+meet({binary,U1}, {binary,U2}) ->
+ {binary,max(U1, U2)};
+meet(any, T) ->
+ verified_type(T);
+meet(T, any) ->
+ verified_type(T);
+meet(_, _) ->
+ %% Inconsistent types. There will be an exception at runtime.
+ none.
+
+meet_tuples(#t_tuple{size=Sz1,exact=true},
+ #t_tuple{size=Sz2,exact=true}) when Sz1 =/= Sz2 ->
+ none;
+meet_tuples(#t_tuple{size=Sz1,exact=Ex1,elements=Es1},
+ #t_tuple{size=Sz2,exact=Ex2,elements=Es2}) ->
+ Size = max(Sz1, Sz2),
+ Exact = Ex1 or Ex2,
+ case meet_elements(Es1, Es2) of
+ none ->
+ none;
+ Es ->
+ #t_tuple{size=Size,exact=Exact,elements=Es}
+ end.
+
+meet_elements(Es1, Es2) ->
+ Keys = maps:keys(Es1) ++ maps:keys(Es2),
+ meet_elements_1(Keys, Es1, Es2, #{}).
+
+meet_elements_1([Key | Keys], Es1, Es2, Acc) ->
+ case {Es1, Es2} of
+ {#{ Key := Type1 }, #{ Key := Type2 }} ->
+ case meet(Type1, Type2) of
+ none -> none;
+ Type -> meet_elements_1(Keys, Es1, Es2, Acc#{ Key => Type })
+ end;
+ {#{ Key := Type1 }, _} ->
+ meet_elements_1(Keys, Es1, Es2, Acc#{ Key => Type1 });
+ {_, #{ Key := Type2 }} ->
+ meet_elements_1(Keys, Es1, Es2, Acc#{ Key => Type2 })
+ end;
+meet_elements_1([], _Es1, _Es2, Acc) ->
+ Acc.
+
+%% verified_type(Type) -> Type
+%% Returns the passed in type if it is one of the defined types.
+%% Crashes if there is anything wrong with the type.
+%%
+%% Here are all possible types:
+%%
+%% any Any Erlang term (top element for the type lattice).
+%%
+%% #t_atom{} Any atom or some specific atoms.
+%% {binary,Unit} Binary/bitstring aligned to unit Unit.
+%% float Floating point number.
+%% #t_integer{} Integer
+%% list Empty or nonempty list.
+%% map Map.
+%% nil Empty list.
+%% cons Cons (nonempty list).
+%% number A number (float or integer).
+%% #t_tuple{} Tuple.
+%%
+%% none No type (bottom element for the type lattice).
+
+-spec verified_type(T) -> T when
+ T :: type().
+
+verified_type(any=T) -> T;
+verified_type(none=T) -> T;
+verified_type(#t_atom{elements=any}=T) -> T;
+verified_type(#t_atom{elements=[_|_]}=T) -> T;
+verified_type({binary,U}=T) when is_integer(U) -> T;
+verified_type(#t_integer{elements=any}=T) -> T;
+verified_type(#t_integer{elements={Min,Max}}=T)
+ when is_integer(Min), is_integer(Max) -> T;
+verified_type(list=T) -> T;
+verified_type(map=T) -> T;
+verified_type(nil=T) -> T;
+verified_type(cons=T) -> T;
+verified_type(number=T) -> T;
+verified_type(#t_tuple{size=Size,elements=Es}=T) ->
+ %% All known elements must have a valid index and type. 'any' is prohibited
+ %% since it's implicit and should never be present in the map.
+ maps:fold(fun(Index, Element, _) when is_integer(Index),
+ 1 =< Index, Index =< Size,
+ Element =/= any, Element =/= none ->
+ verified_type(Element)
+ end, [], Es),
+ T;
+verified_type(float=T) -> T.
diff --git a/lib/compiler/src/beam_trim.erl b/lib/compiler/src/beam_trim.erl
index 4da0985085..acf3838da4 100644
--- a/lib/compiler/src/beam_trim.erl
+++ b/lib/compiler/src/beam_trim.erl
@@ -21,12 +21,11 @@
-module(beam_trim).
-export([module/2]).
--import(lists, [reverse/1,reverse/2,splitwith/2,sort/1]).
+-import(lists, [any/2,member/2,reverse/1,reverse/2,splitwith/2,sort/1]).
-record(st,
- {safe :: gb_sets:set(beam_asm:label()), %Safe labels.
- lbl :: beam_utils:code_index() %Code at each label.
- }).
+ {safe :: cerl_sets:set(beam_asm:label()) %Safe labels.
+ }).
-spec module(beam_utils:module_code(), [compile:option()]) ->
{'ok',beam_utils:module_code()}.
@@ -36,10 +35,15 @@ module({Mod,Exp,Attr,Fs0,Lc}, _Opts) ->
{ok,{Mod,Exp,Attr,Fs,Lc}}.
function({function,Name,Arity,CLabel,Is0}) ->
- %%ok = io:fwrite("~w: ~p\n", [?LINE,{Name,Arity}]),
- St = #st{safe=safe_labels(Is0, []),lbl=beam_utils:index_labels(Is0)},
- Is = trim(Is0, St, []),
- {function,Name,Arity,CLabel,Is}.
+ try
+ St = #st{safe=safe_labels(Is0, [])},
+ Is = trim(Is0, St, []),
+ {function,Name,Arity,CLabel,Is}
+ catch
+ Class:Error:Stack ->
+ io:fwrite("Function: ~w/~w\n", [Name,Arity]),
+ erlang:raise(Class, Error, Stack)
+ end.
trim([{kill,_}|_]=Is0, St, Acc) ->
{Kills0,Is1} = splitwith(fun({kill,_}) -> true;
@@ -47,14 +51,33 @@ trim([{kill,_}|_]=Is0, St, Acc) ->
end, Is0),
Kills = sort(Kills0),
try
- {FrameSize,Layout} = frame_layout(Is1, Kills, St),
- Configs = trim_instructions(Layout),
- try_remap(Configs, Is1, FrameSize)
- of
+ %% Find out the size and layout of the stack frame.
+ %% Example of a layout:
+ %%
+ %% [{kill,{y,0}},{dead,{y,1},{live,{y,2}},{kill,{y,3}}]
+ %%
+ %% That means that y0 and y3 are to be killed, that y1
+ %% has been killed previously, and that y2 is live.
+ {FrameSize,Layout} = frame_layout(Is1, Kills, St),
+
+ %% Calculate all recipes that are not worse in terms
+ %% of estimated execution time. The recipes are ordered
+ %% in descending order from how much they trim.
+ Recipes = trim_recipes(Layout),
+
+ %% Try the recipes in order. A recipe may not work out because
+ %% a register that was previously killed may be
+ %% resurrected. If that happens, the next recipe, which trims
+ %% less, will be tried.
+ try_remap(Recipes, Is1, FrameSize)
+ of
{Is,TrimInstr} ->
+ %% One of the recipes was applied.
trim(Is, St, reverse(TrimInstr)++Acc)
catch
not_possible ->
+ %% No recipe worked out. Use the original kill
+ %% instructions.
trim(Is1, St, reverse(Kills, Acc))
end;
trim([I|Is], St, Acc) ->
@@ -62,34 +85,42 @@ trim([I|Is], St, Acc) ->
trim([], _, Acc) ->
reverse(Acc).
-%% trim_instructions([{kill,R}|{live,R}|{dead,R}]) -> {[Instruction],MapFun}
-%% Figure out the sequence of moves and trim to use.
+%% trim_recipes([{kill,R}|{live,R}|{dead,R}]) -> [Recipe].
+%% Recipe = {Kills,NumberToTrim,Moves}
+%% Kills = [{kill,Y}]
+%% Moves = [{move,SrcY,DstY}]
+%%
+%% Calculate how to best trim the stack and kill the correct
+%% Y registers. Return a list of possible recipes. The best
+%% recipe (the one that trims the most) is first in the list.
+%% All of the recipes are no worse in estimated execution time
+%% than the original sequences of kill instructions.
-trim_instructions(Layout) ->
+trim_recipes(Layout) ->
Cost = length([I || {kill,_}=I <- Layout]),
- trim_instructions_1(Layout, 0, [], {Cost,[]}).
+ trim_recipes_1(Layout, 0, [], {Cost,[]}).
-trim_instructions_1([{kill,{y,Trim0}}|Ks], Trim0, Moves, Config0) ->
+trim_recipes_1([{kill,{y,Trim0}}|Ks], Trim0, Moves, Recipes0) ->
Trim = Trim0 + 1,
- Config = save_config(Ks, Trim, Moves, Config0),
- trim_instructions_1(Ks, Trim, Moves, Config);
-trim_instructions_1([{dead,{y,Trim0}}|Ks], Trim0, Moves, Config0) ->
+ Recipes = save_recipe(Ks, Trim, Moves, Recipes0),
+ trim_recipes_1(Ks, Trim, Moves, Recipes);
+trim_recipes_1([{dead,{y,Trim0}}|Ks], Trim0, Moves, Recipes0) ->
Trim = Trim0 + 1,
- Config = save_config(Ks, Trim, Moves, Config0),
- trim_instructions_1(Ks, Trim, Moves, Config);
-trim_instructions_1([{live,{y,Trim0}=Src}|Ks0], Trim0, Moves0, Config0) ->
+ Recipes = save_recipe(Ks, Trim, Moves, Recipes0),
+ trim_recipes_1(Ks, Trim, Moves, Recipes);
+trim_recipes_1([{live,{y,Trim0}=Src}|Ks0], Trim0, Moves0, Recipes0) ->
case take_last_dead(Ks0) of
none ->
- {_,ConfigList} = Config0,
- ConfigList;
+ {_,RecipesList} = Recipes0,
+ RecipesList;
{Dst,Ks} ->
Trim = Trim0 + 1,
Moves = [{move,Src,Dst}|Moves0],
- Config = save_config(Ks, Trim, Moves, Config0),
- trim_instructions_1(Ks, Trim, Moves, Config)
+ Recipes = save_recipe(Ks, Trim, Moves, Recipes0),
+ trim_recipes_1(Ks, Trim, Moves, Recipes)
end;
-trim_instructions_1([], _, _, {_,ConfigList}) ->
- ConfigList.
+trim_recipes_1([], _, _, {_,RecipesList}) ->
+ RecipesList.
take_last_dead(L) ->
take_last_dead_1(reverse(L)).
@@ -100,28 +131,48 @@ take_last_dead_1([{dead,Reg}|Is]) ->
{Reg,reverse(Is)};
take_last_dead_1(_) -> none.
-save_config(Ks, Trim, Moves, {MaxCost,Acc}=Config) ->
- case config_cost(Ks, Moves) of
- Cost when Cost =< MaxCost ->
- {MaxCost,[{Ks,Trim,Moves}|Acc]};
+save_recipe(Ks, Trim, Moves, {MaxCost,Acc}=Recipes) ->
+ case recipe_cost(Ks, Moves) of
+ Cost when Cost =< MaxCost ->
+ %% The price is right.
+ {MaxCost,[{Ks,Trim,Moves}|Acc]};
_Cost ->
- Config
+ %% Too expensive.
+ Recipes
end.
-config_cost(Ks, Moves) ->
+recipe_cost(Ks, Moves) ->
%% We estimate that a {move,{y,_},{y,_}} instruction is roughly twice as
%% expensive as a {kill,{y,_}} instruction. A {trim,_} instruction is
%% roughly as expensive as a {kill,{y,_}} instruction.
- config_cost_1(Ks, 1+2*length(Moves)).
+ recipe_cost_1(Ks, 1+2*length(Moves)).
-config_cost_1([{kill,_}|Ks], Cost) ->
- config_cost_1(Ks, Cost+1);
-config_cost_1([_|Ks], Cost) ->
- config_cost_1(Ks, Cost);
-config_cost_1([], Cost) -> Cost.
+recipe_cost_1([{kill,_}|Ks], Cost) ->
+ recipe_cost_1(Ks, Cost+1);
+recipe_cost_1([_|Ks], Cost) ->
+ recipe_cost_1(Ks, Cost);
+recipe_cost_1([], Cost) -> Cost.
-expand_config({Layout,Trim,Moves}, FrameSize) ->
+%% try_remap([Recipe], [Instruction], FrameSize) ->
+%% {[Instruction],[TrimInstruction]}.
+%% Try to renumber Y registers in the instruction stream. The
+%% first rececipe that works will be used.
+%%
+%% This function will issue a `not_possible` exception if none
+%% of the recipes were possible to apply.
+
+try_remap([R|Rs], Is, FrameSize) ->
+ {TrimInstr,Map} = expand_recipe(R, FrameSize),
+ try
+ {remap(Is, Map, []),TrimInstr}
+ catch
+ throw:not_possible ->
+ try_remap(Rs, Is, FrameSize)
+ end;
+try_remap([], _, _) -> throw(not_possible).
+
+expand_recipe({Layout,Trim,Moves}, FrameSize) ->
Kills = [Kill || {kill,_}=Kill <- Layout],
{Kills++reverse(Moves, [{trim,Trim,FrameSize-Trim}]),create_map(Trim, Moves)}.
@@ -132,16 +183,16 @@ create_map(Trim, []) ->
(Any) -> Any
end;
create_map(Trim, Moves) ->
- GbTree0 = [{Src,Dst-Trim} || {move,{y,Src},{y,Dst}} <- Moves],
- GbTree = gb_trees:from_orddict(sort(GbTree0)),
- IllegalTargets = gb_sets:from_list([Dst || {move,_,{y,Dst}} <- Moves]),
+ Map0 = [{Src,Dst-Trim} || {move,{y,Src},{y,Dst}} <- Moves],
+ Map = maps:from_list(Map0),
+ IllegalTargets = cerl_sets:from_list([Dst || {move,_,{y,Dst}} <- Moves]),
fun({y,Y0}) when Y0 < Trim ->
- case gb_trees:lookup(Y0, GbTree) of
- {value,Y} -> {y,Y};
- none -> throw(not_possible)
- end;
+ case Map of
+ #{Y0:=Y} -> {y,Y};
+ #{} -> throw(not_possible)
+ end;
({y,Y}) ->
- case gb_sets:is_element(Y, IllegalTargets) of
+ case cerl_sets:is_element(Y, IllegalTargets) of
true -> throw(not_possible);
false -> {y,Y-Trim}
end;
@@ -149,19 +200,17 @@ create_map(Trim, Moves) ->
(Any) -> Any
end.
-try_remap([C|Cs], Is, FrameSize) ->
- {TrimInstr,Map} = expand_config(C, FrameSize),
- try
- {remap(Is, Map, []),TrimInstr}
- catch
- throw:not_possible ->
- try_remap(Cs, Is, FrameSize)
- end;
-try_remap([], _, _) -> throw(not_possible).
-
+remap([{'%',_}=I|Is], Map, Acc) ->
+ remap(Is, Map, [I|Acc]);
remap([{block,Bl0}|Is], Map, Acc) ->
Bl = remap_block(Bl0, Map, []),
remap(Is, Map, [{block,Bl}|Acc]);
+remap([{bs_get_tail,Src,Dst,Live}|Is], Map, Acc) ->
+ I = {bs_get_tail,Map(Src),Map(Dst),Live},
+ remap(Is, Map, [I|Acc]);
+remap([{bs_set_position,Src1,Src2}|Is], Map, Acc) ->
+ I = {bs_set_position,Map(Src1),Map(Src2)},
+ remap(Is, Map, [I|Acc]);
remap([{call_fun,_}=I|Is], Map, Acc) ->
remap(Is, Map, [I|Acc]);
remap([{call,_,_}=I|Is], Map, Acc) ->
@@ -205,35 +254,68 @@ remap([return|_]=Is, _, Acc) ->
reverse(Acc, Is);
remap([{line,_}=I|Is], Map, Acc) ->
remap(Is, Map, [I|Acc]).
-
+
remap_block([{set,Ds0,Ss0,Info}|Is], Map, Acc) ->
Ds = [Map(D) || D <- Ds0],
Ss = [Map(S) || S <- Ss0],
remap_block(Is, Map, [{set,Ds,Ss,Info}|Acc]);
remap_block([], _, Acc) -> reverse(Acc).
-
-safe_labels([{label,L},{line,_},{badmatch,{Tag,_}}|Is], Acc) when Tag =/= y ->
- safe_labels(Is, [L|Acc]);
-safe_labels([{label,L},{line,_},{case_end,{Tag,_}}|Is], Acc) when Tag =/= y ->
- safe_labels(Is, [L|Acc]);
-safe_labels([{label,L},{line,_},if_end|Is], Acc) ->
- safe_labels(Is, [L|Acc]);
-safe_labels([{label,L},
- {block,[{set,[{x,0}],[{Tag,_}],move}]},
- {line,_},
- {call_ext,1,{extfunc,erlang,error,1}}|Is], Acc) when Tag =/= y ->
- safe_labels(Is, [L|Acc]);
+
+%% safe_labels([Instruction], Accumulator) -> gb_set()
+%% Build a gb_set of safe labels. The code at a safe
+%% label does not depend on the values in a specific
+%% Y register, only that all Y registers are initialized
+%% so that it safe to scan the stack when an exception
+%% is generated.
+%%
+%% In other words, code at a safe label will continue
+%% to work if Y registers have been renumbered and
+%% the size of the stack frame has changed.
+
+safe_labels([{label,L}|Is], Acc) ->
+ case is_safe_label(Is) of
+ true -> safe_labels(Is, [L|Acc]);
+ false -> safe_labels(Is, Acc)
+ end;
safe_labels([_|Is], Acc) ->
safe_labels(Is, Acc);
-safe_labels([], Acc) -> gb_sets:from_list(Acc).
+safe_labels([], Acc) -> cerl_sets:from_list(Acc).
+
+is_safe_label([{'%',_}|Is]) ->
+ is_safe_label(Is);
+is_safe_label([{line,_}|Is]) ->
+ is_safe_label(Is);
+is_safe_label([{badmatch,{Tag,_}}|_]) ->
+ Tag =/= y;
+is_safe_label([{case_end,{Tag,_}}|_]) ->
+ Tag =/= y;
+is_safe_label([{try_case_end,{Tag,_}}|_]) ->
+ Tag =/= y;
+is_safe_label([if_end|_]) ->
+ true;
+is_safe_label([{block,Bl}|Is]) ->
+ is_safe_label_block(Bl) andalso is_safe_label(Is);
+is_safe_label([{call_ext,_,{extfunc,M,F,A}}|_]) ->
+ erl_bifs:is_exit_bif(M, F, A);
+is_safe_label(_) -> false.
+
+is_safe_label_block([{set,Ds,Ss,_}|Is]) ->
+ IsYreg = fun({y,_}) -> true;
+ (_) -> false
+ end,
+ %% This instruction is safe if the instruction
+ %% neither reads or writes Y registers.
+ not (any(IsYreg, Ss) orelse any(IsYreg, Ds)) andalso
+ is_safe_label_block(Is);
+is_safe_label_block([]) -> true.
%% frame_layout([Instruction], [{kill,_}], St) ->
%% [{kill,Reg} | {live,Reg} | {dead,Reg}]
%% Figure out the layout of the stack frame.
-frame_layout(Is, Kills, #st{safe=Safe,lbl=D}) ->
+frame_layout(Is, Kills, #st{safe=Safe}) ->
N = frame_size(Is, Safe),
- IsKilled = fun(R) -> beam_utils:is_not_used(R, Is, D) end,
+ IsKilled = fun(R) -> is_not_used(R, Is) end,
{N,frame_layout_1(Kills, 0, N, IsKilled, [])}.
frame_layout_1([{kill,{y,Y}}=I|Ks], Y, N, IsKilled, Acc) ->
@@ -253,7 +335,14 @@ frame_layout_2(Is) -> reverse(Is).
%% frame_size([Instruction], SafeLabels) -> FrameSize
%% Find out the frame size by looking at the code that follows.
+%%
+%% Implicitly, also check that the instructions are a straight
+%% sequence of code that ends in a return. Any branches are
+%% to safe labels (i.e., the code at those labels don't depend
+%% on the contents of any Y register).
+frame_size([{'%',_}|Is], Safe) ->
+ frame_size(Is, Safe);
frame_size([{block,_}|Is], Safe) ->
frame_size(Is, Safe);
frame_size([{call_fun,_}|Is], Safe) ->
@@ -285,15 +374,94 @@ frame_size([{make_fun2,_,_,_,_}|Is], Safe) ->
frame_size(Is, Safe);
frame_size([{get_map_elements,{f,L},_,_}|Is], Safe) ->
frame_size_branch(L, Is, Safe);
-frame_size([{deallocate,N}|_], _) -> N;
+frame_size([{deallocate,N}|_], _) ->
+ N;
frame_size([{line,_}|Is], Safe) ->
frame_size(Is, Safe);
-frame_size([_|_], _) -> throw(not_possible).
+frame_size([{bs_set_position,_,_}|Is], Safe) ->
+ frame_size(Is, Safe);
+frame_size([{bs_get_tail,_,_,_}|Is], Safe) ->
+ frame_size(Is, Safe);
+frame_size(_, _) -> throw(not_possible).
frame_size_branch(0, Is, Safe) ->
frame_size(Is, Safe);
frame_size_branch(L, Is, Safe) ->
- case gb_sets:is_member(L, Safe) of
+ case cerl_sets:is_element(L, Safe) of
false -> throw(not_possible);
true -> frame_size(Is, Safe)
end.
+
+%% is_not_used(Y, [Instruction]) -> true|false.
+%% Test whether the value of Y is unused in the instruction sequence.
+%% Return true if the value of Y is not used, and false if it is used.
+%%
+%% This function handles the same instructions as frame_size/2. It
+%% assumes that any labels in the instructions are safe labels.
+
+is_not_used(Y, [{'%',_}|Is]) ->
+ is_not_used(Y, Is);
+is_not_used(Y, [{apply,_}|Is]) ->
+ is_not_used(Y, Is);
+is_not_used(Y, [{bif,_,{f,_},Ss,Dst}|Is]) ->
+ is_not_used_ss_dst(Y, Ss, Dst, Is);
+is_not_used(Y, [{block,Bl}|Is]) ->
+ case is_not_used_block(Y, Bl) of
+ used -> false;
+ killed -> true;
+ transparent -> is_not_used(Y, Is)
+ end;
+is_not_used(Y, [{bs_get_tail,Src,Dst,_}|Is]) ->
+ is_not_used_ss_dst(Y, [Src], Dst, Is);
+is_not_used(Y, [{bs_init,_,_,_,Ss,Dst}|Is]) ->
+ is_not_used_ss_dst(Y, Ss, Dst, Is);
+is_not_used(Y, [{bs_put,{f,_},_,Ss}|Is]) ->
+ not member(Y, Ss) andalso is_not_used(Y, Is);
+is_not_used(Y, [{bs_set_position,Src1,Src2}|Is]) ->
+ Y =/= Src1 andalso Y =/= Src2 andalso
+ is_not_used(Y, Is);
+is_not_used(Y, [{call,_,_}|Is]) ->
+ is_not_used(Y, Is);
+is_not_used(Y, [{call_ext,_,_}=I|Is]) ->
+ beam_jump:is_exit_instruction(I) orelse is_not_used(Y, Is);
+is_not_used(Y, [{call_fun,_}|Is]) ->
+ is_not_used(Y, Is);
+is_not_used(_Y, [{deallocate,_}|_]) ->
+ true;
+is_not_used(Y, [{gc_bif,_,{f,_},_Live,Ss,Dst}|Is]) ->
+ is_not_used_ss_dst(Y, Ss, Dst, Is);
+is_not_used(Y, [{get_map_elements,{f,_},S,{list,List}}|Is]) ->
+ {Ss,Ds} = beam_utils:split_even(List),
+ case member(Y, [S|Ss]) of
+ true ->
+ false;
+ false ->
+ member(Y, Ds) orelse is_not_used(Y, Is)
+ end;
+is_not_used(Y, [{kill,Yreg}|Is]) ->
+ Y =:= Yreg orelse is_not_used(Y, Is);
+is_not_used(Y, [{line,_}|Is]) ->
+ is_not_used(Y, Is);
+is_not_used(Y, [{make_fun2,_,_,_,_}|Is]) ->
+ is_not_used(Y, Is);
+is_not_used(Y, [{test,_,_,Ss}|Is]) ->
+ not member(Y, Ss) andalso is_not_used(Y, Is);
+is_not_used(Y, [{test,_Op,{f,_},_Live,Ss,Dst}|Is]) ->
+ is_not_used_ss_dst(Y, Ss, Dst, Is).
+
+is_not_used_block(Y, [{set,Ds,Ss,_}|Is]) ->
+ case member(Y, Ss) of
+ true ->
+ used;
+ false ->
+ case member(Y, Ds) of
+ true ->
+ killed;
+ false ->
+ is_not_used_block(Y, Is)
+ end
+ end;
+is_not_used_block(_Y, []) -> transparent.
+
+is_not_used_ss_dst(Y, Ss, Dst, Is) ->
+ not member(Y, Ss) andalso (Y =:= Dst orelse is_not_used(Y, Is)).
diff --git a/lib/compiler/src/beam_type.erl b/lib/compiler/src/beam_type.erl
deleted file mode 100644
index b5c979e529..0000000000
--- a/lib/compiler/src/beam_type.erl
+++ /dev/null
@@ -1,1117 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1999-2018. All 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: Type-based optimisations. See the comment for verified_type/1
-%% the very end of this file for a description of the types in the
-%% type database.
-
--module(beam_type).
-
--export([module/2]).
-
--import(lists, [foldl/3,member/2,reverse/1,reverse/2,sort/1]).
-
--define(UNICODE_INT, {integer,{0,16#10FFFF}}).
-
--spec module(beam_utils:module_code(), [compile:option()]) ->
- {'ok',beam_utils:module_code()}.
-
-module({Mod,Exp,Attr,Fs0,Lc}, _Opts) ->
- Fs = [function(F) || F <- Fs0],
- {ok,{Mod,Exp,Attr,Fs,Lc}}.
-
-function({function,Name,Arity,CLabel,Asm0}) ->
- try
- Asm1 = beam_utils:live_opt(Asm0),
- Asm2 = opt(Asm1, [], tdb_new()),
- Asm3 = beam_utils:live_opt(Asm2),
- Asm = beam_utils:delete_annos(Asm3),
- {function,Name,Arity,CLabel,Asm}
- catch
- Class:Error:Stack ->
- io:fwrite("Function: ~w/~w\n", [Name,Arity]),
- erlang:raise(Class, Error, Stack)
- end.
-
-%% opt([Instruction], Accumulator, TypeDb) -> {[Instruction'],TypeDb'}
-%% Keep track of type information; try to simplify.
-
-opt([{block,Body1}|Is], [{block,Body0}|Acc], Ts0) ->
- {Body2,Ts} = simplify(Body1, Ts0),
- Body = merge_blocks(Body0, Body2),
- opt(Is, [{block,Body}|Acc], Ts);
-opt([{block,Body0}|Is], Acc, Ts0) ->
- {Body,Ts} = simplify(Body0, Ts0),
- opt(Is, [{block,Body}|Acc], Ts);
-opt([I0|Is], Acc, Ts0) ->
- case simplify_basic([I0], Ts0) of
- {[],Ts} -> opt(Is, Acc, Ts);
- {[I],Ts} -> opt(Is, [I|Acc], Ts)
- end;
-opt([], Acc, _) -> reverse(Acc).
-
-%% simplify(Instruction, TypeDb) -> NewInstruction
-%% Simplify an instruction using type information (this is
-%% technically a "strength reduction").
-
-simplify(Is0, TypeDb0) ->
- {Is,_} = BasicRes = simplify_basic(Is0, TypeDb0),
- case simplify_float(Is, TypeDb0) of
- not_possible -> BasicRes;
- {_,_}=Res -> Res
- end.
-
-%% simplify_basic([Instruction], TypeDatabase) -> {[Instruction],TypeDatabase'}
-%% Basic simplification, mostly tuples, no floating point optimizations.
-
-simplify_basic(Is, Ts) ->
- simplify_basic(Is, Ts, []).
-
-simplify_basic([I0|Is], Ts0, Acc) ->
- case simplify_instr(I0, Ts0) of
- [] ->
- simplify_basic(Is, Ts0, Acc);
- [I] ->
- Ts = update(I, Ts0),
- simplify_basic(Is, Ts, [I|Acc])
- end;
-simplify_basic([], Ts, Acc) ->
- {reverse(Acc),Ts}.
-
-%% simplify_instr(Instruction, Ts) -> [Instruction].
-
-%% Simplify a simple instruction using type information. Return an
-%% empty list if the instruction should be removed, or a list with
-%% the original or modified instruction.
-
-simplify_instr({set,[D],[{integer,Index},Reg],{bif,element,_}}=I, Ts) ->
- case max_tuple_size(Reg, Ts) of
- Sz when 0 < Index, Index =< Sz ->
- [{set,[D],[Reg],{get_tuple_element,Index-1}}];
- _ -> [I]
- end;
-simplify_instr({test,Test,Fail,[R]}=I, Ts) ->
- case tdb_find(R, Ts) of
- any ->
- [I];
- Type ->
- case will_succeed(Test, Type) of
- yes -> [];
- no -> [{jump,Fail}];
- maybe -> [I]
- end
- end;
-simplify_instr({set,[D],[TupleReg],{get_tuple_element,0}}=I, Ts) ->
- case tdb_find(TupleReg, Ts) of
- {tuple,_,_,[Contents]} ->
- [{set,[D],[Contents],move}];
- _ ->
- [I]
- end;
-simplify_instr({test,test_arity,_,[R,Arity]}=I, Ts) ->
- case tdb_find(R, Ts) of
- {tuple,exact_size,Arity,_} -> [];
- _ -> [I]
- end;
-simplify_instr({test,is_eq_exact,Fail,[R,{atom,A}=Atom]}=I, Ts) ->
- case tdb_find(R, Ts) of
- {atom,_}=Atom -> [];
- boolean when is_boolean(A) -> [I];
- any -> [I];
- _ -> [{jump,Fail}]
- end;
-simplify_instr({test,is_record,_,[R,{atom,_}=Tag,{integer,Arity}]}=I, Ts) ->
- case tdb_find(R, Ts) of
- {tuple,exact_size,Arity,[Tag]} -> [];
- _ -> [I]
- end;
-simplify_instr({select,select_val,Reg,_,_}=I, Ts) ->
- [case tdb_find(Reg, Ts) of
- {integer,Range} ->
- simplify_select_val_int(I, Range);
- boolean ->
- simplify_select_val_bool(I);
- _ ->
- I
- end];
-simplify_instr({test,bs_test_unit,_,[Src,Unit]}=I, Ts) ->
- case tdb_find(Src, Ts) of
- {binary,U} when U rem Unit =:= 0 -> [];
- _ -> [I]
- end;
-simplify_instr(I, _) -> [I].
-
-simplify_select_val_int({select,select_val,R,_,L0}=I, {Min,Max}) ->
- Vs = sort([V || {integer,V} <- L0]),
- case eq_ranges(Vs, Min, Max) of
- false -> I;
- true -> simplify_select_val_1(L0, {integer,Max}, R, [])
- end.
-
-simplify_select_val_bool({select,select_val,R,_,L}=I) ->
- Vs = sort([V || {atom,V} <- L]),
- case Vs of
- [false,true] ->
- simplify_select_val_1(L, {atom,false}, R, []);
- _ ->
- I
- end.
-
-simplify_select_val_1([Val,F|T], Val, R, Acc) ->
- L = reverse(Acc, T),
- {select,select_val,R,F,L};
-simplify_select_val_1([V,F|T], Val, R, Acc) ->
- simplify_select_val_1(T, Val, R, [F,V|Acc]).
-
-eq_ranges([H], H, H) -> true;
-eq_ranges([H|T], H, Max) -> eq_ranges(T, H+1, Max);
-eq_ranges(_, _, _) -> false.
-
-%% will_succeed(TestOperation, Type) -> yes|no|maybe.
-%% Test whether TestOperation applied to an argument of type Type
-%% will succeed. Return yes, no, or maybe.
-%%
-%% Type is a type as described in the comment for verified_type/1 at
-%% the very end of this file, but it will *never* be 'any'.
-
-will_succeed(is_atom, Type) ->
- case Type of
- {atom,_} -> yes;
- boolean -> yes;
- _ -> no
- end;
-will_succeed(is_binary, Type) ->
- case Type of
- {binary,U} when U rem 8 =:= 0 -> yes;
- {binary,_} -> maybe;
- _ -> no
- end;
-will_succeed(is_bitstr, Type) ->
- case Type of
- {binary,_} -> yes;
- _ -> no
- end;
-will_succeed(is_integer, Type) ->
- case Type of
- integer -> yes;
- {integer,_} -> yes;
- _ -> no
- end;
-will_succeed(is_map, Type) ->
- case Type of
- map -> yes;
- _ -> no
- end;
-will_succeed(is_nonempty_list, Type) ->
- case Type of
- nonempty_list -> yes;
- _ -> no
- end;
-will_succeed(is_tuple, Type) ->
- case Type of
- {tuple,_,_,_} -> yes;
- _ -> no
- end;
-will_succeed(_, _) -> maybe.
-
-%% simplify_float([Instruction], TypeDatabase) ->
-%% {[Instruction],TypeDatabase'} | not_possible
-%% Simplify floating point operations in blocks.
-%%
-simplify_float(Is0, Ts0) ->
- {Is1,Ts} = simplify_float_1(Is0, Ts0, [], []),
- Is2 = opt_fmoves(Is1, []),
- Is3 = flt_need_heap(Is2),
- try
- {flt_liveness(Is3),Ts}
- catch
- throw:not_possible -> not_possible
- end.
-
-simplify_float_1([{set,[],[],fclearerror}|Is], Ts, Rs, Acc) ->
- simplify_float_1(Is, Ts, Rs, clearerror(Acc));
-simplify_float_1([{set,[],[],fcheckerror}|Is], Ts, Rs, Acc) ->
- simplify_float_1(Is, Ts, Rs, checkerror(Acc));
-simplify_float_1([{set,[{fr,_}],_,_}=I|Is], Ts, Rs, Acc) ->
- simplify_float_1(Is, Ts, Rs, [I|Acc]);
-simplify_float_1([{set,[D0],[A0],{alloc,_,{gc_bif,'-',{f,0}}}}=I|Is]=Is0,
- Ts0, Rs0, Acc0) ->
- case tdb_find(A0, Ts0) of
- float ->
- A = coerce_to_float(A0),
- {Rs1,Acc1} = load_reg(A, Ts0, Rs0, Acc0),
- {D,Rs} = find_dest(D0, Rs1),
- Areg = fetch_reg(A, Rs),
- Acc = [{set,[D],[Areg],{bif,fnegate,{f,0}}}|clearerror(Acc1)],
- Ts = tdb_store(D0, float, Ts0),
- simplify_float_1(Is, Ts, Rs, Acc);
- _Other ->
- Ts = update(I, Ts0),
- {Rs,Acc} = flush(Rs0, Is0, Acc0),
- simplify_float_1(Is, Ts, Rs, [I|checkerror(Acc)])
- end;
-simplify_float_1([{set,[D0],[A0,B0],{alloc,_,{gc_bif,Op0,{f,0}}}}=I|Is]=Is0,
- Ts0, Rs0, Acc0) ->
- case float_op(Op0, A0, B0, Ts0) of
- no ->
- Ts = update(I, Ts0),
- {Rs,Acc} = flush(Rs0, Is0, Acc0),
- simplify_float_1(Is, Ts, Rs, [I|checkerror(Acc)]);
- {yes,Op} ->
- A = coerce_to_float(A0),
- B = coerce_to_float(B0),
- {Rs1,Acc1} = load_reg(A, Ts0, Rs0, Acc0),
- {Rs2,Acc2} = load_reg(B, Ts0, Rs1, Acc1),
- {D,Rs} = find_dest(D0, Rs2),
- Areg = fetch_reg(A, Rs),
- Breg = fetch_reg(B, Rs),
- Acc = [{set,[D],[Areg,Breg],{bif,Op,{f,0}}}|clearerror(Acc2)],
- Ts = tdb_store(D0, float, Ts0),
- simplify_float_1(Is, Ts, Rs, Acc)
- end;
-simplify_float_1([{set,_,_,{try_catch,_,_}}=I|Is]=Is0, _Ts, Rs0, Acc0) ->
- Acc = flush_all(Rs0, Is0, Acc0),
- simplify_float_1(Is, tdb_new(), Rs0, [I|Acc]);
-simplify_float_1([{set,_,_,{line,_}}=I|Is], Ts, Rs, Acc) ->
- simplify_float_1(Is, Ts, Rs, [I|Acc]);
-simplify_float_1([I|Is], Ts0, [], Acc) ->
- Ts = update(I, Ts0),
- simplify_float_1(Is, Ts, [], [I|Acc]);
-simplify_float_1([I|Is]=Is0, Ts0, Rs0, Acc0) ->
- Ts = update(I, Ts0),
- {Rs,Acc} = flush(Rs0, Is0, Acc0),
- simplify_float_1(Is, Ts, Rs, [I|checkerror(Acc)]);
-simplify_float_1([], Ts, [], Acc) ->
- Is = reverse(Acc),
- {Is,Ts}.
-
-coerce_to_float({integer,I}=Int) ->
- try float(I) of
- F ->
- {float,F}
- catch _:_ ->
- %% Let the overflow happen at run-time.
- Int
- end;
-coerce_to_float(Other) -> Other.
-
-opt_fmoves([{set,[{x,_}=R],[{fr,_}]=Src,fmove}=I1,
- {set,[_]=Dst,[{x,_}=R],move}=I2|Is], Acc) ->
- case beam_utils:is_killed_block(R, Is) of
- false -> opt_fmoves(Is, [I2,I1|Acc]);
- true -> opt_fmoves(Is, [{set,Dst,Src,fmove}|Acc])
- end;
-opt_fmoves([I|Is], Acc) ->
- opt_fmoves(Is, [I|Acc]);
-opt_fmoves([], Acc) -> reverse(Acc).
-
-clearerror(Is) ->
- clearerror(Is, Is).
-
-clearerror([{set,[],[],fclearerror}|_], OrigIs) -> OrigIs;
-clearerror([{set,[],[],fcheckerror}|_], OrigIs) -> [{set,[],[],fclearerror}|OrigIs];
-clearerror([_|Is], OrigIs) -> clearerror(Is, OrigIs);
-clearerror([], OrigIs) -> [{set,[],[],fclearerror}|OrigIs].
-
-%% merge_blocks(Block1, Block2) -> Block.
-%% Combine two blocks and eliminate any move instructions that assign
-%% to registers that are killed later in the block.
-%%
-merge_blocks(B1, [{'%anno',_}|B2]) ->
- merge_blocks_1(B1++[{set,[],[],stop_here}|B2]).
-
-merge_blocks_1([{set,[],_,stop_here}|Is]) -> Is;
-merge_blocks_1([{set,[D],_,move}=I|Is]) ->
- case beam_utils:is_killed_block(D, Is) of
- true -> merge_blocks_1(Is);
- false -> [I|merge_blocks_1(Is)]
- end;
-merge_blocks_1([I|Is]) -> [I|merge_blocks_1(Is)].
-
-%% flt_need_heap([Instruction]) -> [Instruction]
-%% Insert need heap allocation instructions in the instruction stream
-%% to properly account for both inserted floating point operations and
-%% normal term build operations (such as put_list/3).
-%%
-%% Ignore old heap allocation instructions (except if they allocate a stack
-%% frame too), as they may be in the wrong place (because gc_bif instructions
-%% could have been converted to floating point operations).
-
-flt_need_heap(Is) ->
- flt_need_heap_1(reverse(Is), 0, 0, []).
-
-flt_need_heap_1([{set,[],[],{alloc,_,Alloc}}|Is], H, Fl, Acc) ->
- case Alloc of
- {_,nostack,_,_} ->
- %% Remove any existing test_heap/2 instruction.
- flt_need_heap_1(Is, H, Fl, Acc);
- {Z,Stk,_,Inits} when is_integer(Stk) ->
- %% Keep any allocate*/2 instruction and recalculate heap need.
- I = {set,[],[],{alloc,regs,{Z,Stk,build_alloc(H, Fl),Inits}}},
- flt_need_heap_1(Is, 0, 0, [I|Acc])
- end;
-flt_need_heap_1([I|Is], H0, Fl0, Acc) ->
- {Ns,H1,Fl1} = flt_need_heap_2(I, H0, Fl0),
- flt_need_heap_1(Is, H1, Fl1, [I|Ns]++Acc);
-flt_need_heap_1([], H, Fl, Acc) ->
- flt_alloc(H, Fl) ++ Acc.
-
-%% First come all instructions that build. We pass through, while we
-%% add to the need for heap words and floats on the heap.
-flt_need_heap_2({set,[_],[{fr,_}],fmove}, H, Fl) ->
- {[],H,Fl+1};
-flt_need_heap_2({set,_,_,put_list}, H, Fl) ->
- {[],H+2,Fl};
-flt_need_heap_2({set,_,_,{put_tuple,_}}, H, Fl) ->
- {[],H+1,Fl};
-flt_need_heap_2({set,_,_,put}, H, Fl) ->
- {[],H+1,Fl};
-%% The following instructions cause the insertion of an allocation
-%% instruction if needed.
-flt_need_heap_2({set,_,_,{alloc,_,_}}, H, Fl) ->
- {flt_alloc(H, Fl),0,0};
-flt_need_heap_2({set,_,_,{set_tuple_element,_}}, H, Fl) ->
- {flt_alloc(H, Fl),0,0};
-flt_need_heap_2({'%anno',_}, H, Fl) ->
- {flt_alloc(H, Fl),0,0};
-%% All other instructions are "neutral". We just pass them.
-flt_need_heap_2(_, H, Fl) ->
- {[],H,Fl}.
-
-flt_alloc(0, 0) ->
- [];
-flt_alloc(H, 0) ->
- [{set,[],[],{alloc,regs,{nozero,nostack,H,[]}}}];
-flt_alloc(H, F) ->
- [{set,[],[],{alloc,regs,{nozero,nostack,
- build_alloc(H, F),[]}}}].
-
-build_alloc(Words, 0) -> Words;
-build_alloc(Words, Floats) -> {alloc,[{words,Words},{floats,Floats}]}.
-
-
-%% flt_liveness([Instruction]) -> [Instruction]
-%% (Re)calculate the number of live registers for each heap allocation
-%% function. We base liveness of the number of register map at the
-%% beginning of the instruction sequence.
-%%
-%% A 'not_possible' term will be thrown if the set of live registers
-%% is not continous at an allocation function (e.g. if {x,0} and {x,2}
-%% are live, but not {x,1}).
-
-flt_liveness([{'%anno',{used,Regs}}=LiveInstr|Is]) ->
- flt_liveness_1(Is, Regs, [LiveInstr]).
-
-flt_liveness_1([{set,Ds,Ss,{alloc,Live0,Alloc}}|Is], Regs0, Acc) ->
- Live = min(Live0, live_regs(Regs0)),
- I = {set,Ds,Ss,{alloc,Live,Alloc}},
- Regs1 = init_regs(Live),
- Regs = x_live(Ds, Regs1),
- flt_liveness_1(Is, Regs, [I|Acc]);
-flt_liveness_1([{set,Ds,_,_}=I|Is], Regs0, Acc) ->
- Regs = x_live(Ds, Regs0),
- flt_liveness_1(Is, Regs, [I|Acc]);
-flt_liveness_1([{'%anno',_}], _Regs, Acc) ->
- reverse(Acc).
-
-init_regs(Live) ->
- (1 bsl Live) - 1.
-
-live_regs(Regs) ->
- live_regs_1(Regs, 0).
-
-live_regs_1(0, N) -> N;
-live_regs_1(R, N) ->
- case R band 1 of
- 0 -> throw(not_possible);
- 1 -> live_regs_1(R bsr 1, N+1)
- end.
-
-x_live([{x,N}|Rs], Regs) -> x_live(Rs, Regs bor (1 bsl N));
-x_live([_|Rs], Regs) -> x_live(Rs, Regs);
-x_live([], Regs) -> Regs.
-
-%% update(Instruction, TypeDb) -> NewTypeDb
-%% Update the type database to account for executing an instruction.
-%%
-%% First the cases for instructions inside basic blocks.
-update({'%anno',_}, Ts) ->
- Ts;
-update({set,[D],[S],move}, Ts) ->
- tdb_copy(S, D, Ts);
-update({set,[D],[Index,Reg],{bif,element,_}}, Ts0) ->
- MinSize = case Index of
- {integer,I} -> I;
- _ -> 0
- end,
- Ts = tdb_meet(Reg, {tuple,min_size,MinSize,[]}, Ts0),
- tdb_store(D, any, Ts);
-update({set,[D],[_Key,Map],{bif,map_get,_}}, Ts0) ->
- Ts = tdb_meet(Map, map, Ts0),
- tdb_store(D, any, Ts);
-update({set,[D],Args,{bif,N,_}}, Ts) ->
- Ar = length(Args),
- BoolOp = erl_internal:new_type_test(N, Ar) orelse
- erl_internal:comp_op(N, Ar) orelse
- erl_internal:bool_op(N, Ar),
- Type = case BoolOp of
- true -> boolean;
- false -> unary_op_type(N)
- end,
- tdb_store(D, Type, Ts);
-update({set,[D],[S],{get_tuple_element,0}}, Ts0) ->
- if
- D =:= S ->
- tdb_store(D, any, Ts0);
- true ->
- Ts = tdb_store(D, {tuple_element,S,0}, Ts0),
- tdb_store(S, {tuple,min_size,1,[]}, Ts)
- end;
-update({set,[D],[S],{alloc,_,{gc_bif,float,{f,0}}}}, Ts0) ->
- %% Make sure we reject non-numeric literal argument.
- case possibly_numeric(S) of
- true -> tdb_store(D, float, Ts0);
- false -> Ts0
- end;
-update({set,[D],[S1,S2],{alloc,_,{gc_bif,'band',{f,0}}}}, Ts) ->
- Type = band_type(S1, S2, Ts),
- tdb_store(D, Type, Ts);
-update({set,[D],[S1,S2],{alloc,_,{gc_bif,'/',{f,0}}}}, Ts) ->
- %% Make sure we reject non-numeric literals.
- case possibly_numeric(S1) andalso possibly_numeric(S2) of
- true -> tdb_store(D, float, Ts);
- false -> Ts
- end;
-update({set,[D],[S1,S2],{alloc,_,{gc_bif,Op,{f,0}}}}, Ts0) ->
- case op_type(Op) of
- integer ->
- tdb_store(D, integer, Ts0);
- {float,_} ->
- case {tdb_find(S1, Ts0),tdb_find(S2, Ts0)} of
- {float,_} -> tdb_store(D, float, Ts0);
- {_,float} -> tdb_store(D, float, Ts0);
- {_,_} -> tdb_store(D, any, Ts0)
- end;
- Type ->
- tdb_store(D, Type, Ts0)
- end;
-update({set,[D],[_],{alloc,_,{gc_bif,Op,{f,0}}}}, Ts) ->
- tdb_store(D, unary_op_type(Op), Ts);
-update({set,[],_Src,_Op}, Ts) ->
- Ts;
-update({set,[D],_Src,_Op}, Ts) ->
- tdb_store(D, any, Ts);
-update({kill,D}, Ts) ->
- tdb_store(D, any, Ts);
-
-%% Instructions outside of blocks.
-update({test,test_arity,_Fail,[Src,Arity]}, Ts) ->
- tdb_meet(Src, {tuple,exact_size,Arity,[]}, Ts);
-update({get_map_elements,_,Src,{list,Elems0}}, Ts0) ->
- Ts1 = tdb_meet(Src, map, Ts0),
- {_Ss,Ds} = beam_utils:split_even(Elems0),
- foldl(fun(Dst, A) -> tdb_store(Dst, any, A) end, Ts1, Ds);
-update({test,is_eq_exact,_,[Reg,{atom,_}=Atom]}, Ts0) ->
- Ts = case tdb_find_source_tuple(Reg, Ts0) of
- {source_tuple,TupleReg} ->
- tdb_meet(TupleReg, {tuple,min_size,1,[Atom]}, Ts0);
- none ->
- Ts0
- end,
- tdb_meet(Reg, Atom, Ts);
-update({test,is_record,_Fail,[Src,Tag,{integer,Arity}]}, Ts) ->
- tdb_meet(Src, {tuple,exact_size,Arity,[Tag]}, Ts);
-
-%% Binaries and binary matching.
-
-update({test,bs_get_integer2,_,_,Args,Dst}, Ts) ->
- tdb_store(Dst, get_bs_integer_type(Args), Ts);
-update({test,bs_get_utf8,_,_,_,Dst}, Ts) ->
- tdb_store(Dst, ?UNICODE_INT, Ts);
-update({test,bs_get_utf16,_,_,_,Dst}, Ts) ->
- tdb_store(Dst, ?UNICODE_INT, Ts);
-update({test,bs_get_utf32,_,_,_,Dst}, Ts) ->
- tdb_store(Dst, ?UNICODE_INT, Ts);
-update({bs_init,_,{bs_init2,_,_},_,_,Dst}, Ts) ->
- tdb_store(Dst, {binary,8}, Ts);
-update({bs_init,_,_,_,_,Dst}, Ts) ->
- tdb_store(Dst, {binary,1}, Ts);
-update({bs_put,_,_,_}, Ts) ->
- Ts;
-update({bs_save2,_,_}, Ts) ->
- Ts;
-update({bs_restore2,_,_}, Ts) ->
- Ts;
-update({bs_context_to_binary,Dst}, Ts) ->
- tdb_store(Dst, any, Ts);
-update({test,bs_start_match2,_,_,[Src,_],Dst}, Ts0) ->
- Ts = tdb_meet(Src, {binary,1}, Ts0),
- tdb_copy(Src, Dst, Ts);
-update({test,bs_get_binary2,_,_,[_,_,Unit,_],Dst}, Ts) ->
- true = is_integer(Unit), %Assertion.
- tdb_store(Dst, {binary,Unit}, Ts);
-update({test,bs_get_float2,_,_,_,Dst}, Ts) ->
- tdb_store(Dst, float, Ts);
-update({test,bs_test_unit,_,[Src,Unit]}, Ts) ->
- tdb_meet(Src, {binary,Unit}, Ts);
-
-%% Other test instructions
-update({test,Test,_Fail,[Src]}, Ts) ->
- Type = case Test of
- is_binary -> {binary,8};
- is_bitstr -> {binary,1};
- is_boolean -> boolean;
- is_float -> float;
- is_integer -> integer;
- is_map -> map;
- is_nonempty_list -> nonempty_list;
- _ -> any
- end,
- tdb_meet(Src, Type, Ts);
-update({test,_Test,_Fail,_Other}, Ts) ->
- Ts;
-
-%% Calls
-
-update({call_ext,Ar,{extfunc,math,Math,Ar}}, Ts) ->
- case is_math_bif(Math, Ar) of
- true -> tdb_store({x,0}, float, Ts);
- false -> tdb_kill_xregs(Ts)
- end;
-update({call_ext,3,{extfunc,erlang,setelement,3}}, Ts0) ->
- Ts = tdb_kill_xregs(Ts0),
- case tdb_find({x,1}, Ts0) of
- {tuple,SzKind,Sz,_}=T0 ->
- T = case tdb_find({x,0}, Ts0) of
- {integer,{I,I}} when I > 1 ->
- %% First element is not changed. The result
- %% will have the same type.
- T0;
- _ ->
- %% Position is 1 or unknown. May change the
- %% first element of the tuple.
- {tuple,SzKind,Sz,[]}
- end,
- tdb_store({x,0}, T, Ts);
- _ ->
- Ts
- end;
-update({call,_Arity,_Func}, Ts) -> tdb_kill_xregs(Ts);
-update({call_ext,_Arity,_Func}, Ts) -> tdb_kill_xregs(Ts);
-update({make_fun2,_,_,_,_}, Ts) -> tdb_kill_xregs(Ts);
-update({call_fun, _}, Ts) -> tdb_kill_xregs(Ts);
-update({apply, _}, Ts) -> tdb_kill_xregs(Ts);
-
-update({line,_}, Ts) -> Ts;
-update({'%',_}, Ts) -> Ts;
-
-%% The instruction is unknown. Kill all information.
-update(_I, _Ts) -> tdb_new().
-
-band_type({integer,Int}, Other, Ts) ->
- band_type_1(Int, Other, Ts);
-band_type(Other, {integer,Int}, Ts) ->
- band_type_1(Int, Other, Ts);
-band_type(_, _, _) -> integer.
-
-band_type_1(Int, OtherSrc, Ts) ->
- Type = band_type_2(Int, 0),
- OtherType = tdb_find(OtherSrc, Ts),
- meet(Type, OtherType).
-
-band_type_2(N, Bits) when Bits < 64 ->
- case 1 bsl Bits of
- P when P =:= N + 1 ->
- {integer,{0,N}};
- P when P > N + 1 ->
- integer;
- _ ->
- band_type_2(N, Bits+1)
- end;
-band_type_2(_, _) ->
- %% Negative or large positive number. Give up.
- integer.
-
-get_bs_integer_type([_,{integer,N},U,{field_flags,Fl}])
- when N*U < 64 ->
- NumBits = N*U,
- case member(unsigned, Fl) of
- true ->
- {integer,{0,(1 bsl NumBits)-1}};
- false ->
- %% Signed integer. Don't bother.
- integer
- end;
-get_bs_integer_type(_) ->
- %% Avoid creating ranges with a huge upper limit.
- integer.
-
-is_math_bif(cos, 1) -> true;
-is_math_bif(cosh, 1) -> true;
-is_math_bif(sin, 1) -> true;
-is_math_bif(sinh, 1) -> true;
-is_math_bif(tan, 1) -> true;
-is_math_bif(tanh, 1) -> true;
-is_math_bif(acos, 1) -> true;
-is_math_bif(acosh, 1) -> true;
-is_math_bif(asin, 1) -> true;
-is_math_bif(asinh, 1) -> true;
-is_math_bif(atan, 1) -> true;
-is_math_bif(atanh, 1) -> true;
-is_math_bif(erf, 1) -> true;
-is_math_bif(erfc, 1) -> true;
-is_math_bif(exp, 1) -> true;
-is_math_bif(log, 1) -> true;
-is_math_bif(log2, 1) -> true;
-is_math_bif(log10, 1) -> true;
-is_math_bif(sqrt, 1) -> true;
-is_math_bif(atan2, 2) -> true;
-is_math_bif(pow, 2) -> true;
-is_math_bif(ceil, 1) -> true;
-is_math_bif(floor, 1) -> true;
-is_math_bif(fmod, 2) -> true;
-is_math_bif(pi, 0) -> true;
-is_math_bif(_, _) -> false.
-
-%% Reject non-numeric literals.
-possibly_numeric({x,_}) -> true;
-possibly_numeric({y,_}) -> true;
-possibly_numeric({integer,_}) -> true;
-possibly_numeric({float,_}) -> true;
-possibly_numeric(_) -> false.
-
-max_tuple_size(Reg, Ts) ->
- case tdb_find(Reg, Ts) of
- {tuple,_,Sz,_} -> Sz;
- _Other -> 0
- end.
-
-float_op('/', A, B, _) ->
- case possibly_numeric(A) andalso possibly_numeric(B) of
- true -> {yes,fdiv};
- false -> no
- end;
-float_op(Op, {float,_}, B, _) ->
- case possibly_numeric(B) of
- true -> arith_op(Op);
- false -> no
- end;
-float_op(Op, A, {float,_}, _) ->
- case possibly_numeric(A) of
- true -> arith_op(Op);
- false -> no
- end;
-float_op(Op, A, B, Ts) ->
- case {tdb_find(A, Ts),tdb_find(B, Ts)} of
- {float,_} -> arith_op(Op);
- {_,float} -> arith_op(Op);
- {_,_} -> no
- end.
-
-find_dest(V, Rs0) ->
- case find_reg(V, Rs0) of
- {ok,FR} ->
- {FR,mark(V, Rs0, dirty)};
- error ->
- Rs = put_reg(V, Rs0, dirty),
- {ok,FR} = find_reg(V, Rs),
- {FR,Rs}
- end.
-
-load_reg({float,_}=F, _, Rs0, Is0) ->
- Rs = put_reg(F, Rs0, clean),
- {ok,FR} = find_reg(F, Rs),
- Is = [{set,[FR],[F],fmove}|Is0],
- {Rs,Is};
-load_reg(V, Ts, Rs0, Is0) ->
- case find_reg(V, Rs0) of
- {ok,_FR} -> {Rs0,Is0};
- error ->
- Rs = put_reg(V, Rs0, clean),
- {ok,FR} = find_reg(V, Rs),
- Op = case tdb_find(V, Ts) of
- float -> fmove;
- _ -> fconv
- end,
- Is = [{set,[FR],[V],Op}|Is0],
- {Rs,Is}
- end.
-
-arith_op(Op) ->
- case op_type(Op) of
- {float,Instr} -> {yes,Instr};
- _ -> no
- end.
-
-op_type('+') -> {float,fadd};
-op_type('-') -> {float,fsub};
-op_type('*') -> {float,fmul};
-%% '/' and 'band' are specially handled.
-op_type('bor') -> integer;
-op_type('bxor') -> integer;
-op_type('bsl') -> integer;
-op_type('bsr') -> integer;
-op_type('div') -> integer;
-op_type(_) -> any.
-
-unary_op_type(bit_size) -> integer;
-unary_op_type(byte_size) -> integer;
-unary_op_type(length) -> integer;
-unary_op_type(map_size) -> integer;
-unary_op_type(size) -> integer;
-unary_op_type(tuple_size) -> integer;
-unary_op_type(_) -> any.
-
-flush(Rs, [{set,[_],[_,_,_],{bif,is_record,_}}|_]=Is0, Acc0) ->
- Acc = flush_all(Rs, Is0, Acc0),
- {[],Acc};
-flush(Rs, [{set,[_],[],{put_tuple,_}}|_]=Is0, Acc0) ->
- Acc = flush_all(Rs, Is0, Acc0),
- {[],Acc};
-flush(Rs0, [{set,Ds,Ss,_Op}|_], Acc0) ->
- Save = cerl_sets:from_list(Ss),
- Acc = save_regs(Rs0, Save, Acc0),
- Rs1 = foldl(fun(S, A) -> mark(S, A, clean) end, Rs0, Ss),
- Kill = cerl_sets:from_list(Ds),
- Rs = kill_regs(Rs1, Kill),
- {Rs,Acc};
-flush(Rs0, Is, Acc0) ->
- Acc = flush_all(Rs0, Is, Acc0),
- {[],Acc}.
-
-flush_all([{_,{float,_},_}|Rs], Is, Acc) ->
- flush_all(Rs, Is, Acc);
-flush_all([{I,V,dirty}|Rs], Is, Acc0) ->
- Acc = checkerror(Acc0),
- case beam_utils:is_killed_block(V, Is) of
- true -> flush_all(Rs, Is, Acc);
- false -> flush_all(Rs, Is, [{set,[V],[{fr,I}],fmove}|Acc])
- end;
-flush_all([{_,_,clean}|Rs], Is, Acc) -> flush_all(Rs, Is, Acc);
-flush_all([free|Rs], Is, Acc) -> flush_all(Rs, Is, Acc);
-flush_all([], _, Acc) -> Acc.
-
-save_regs(Rs, Save, Acc) ->
- foldl(fun(R, A) -> save_reg(R, Save, A) end, Acc, Rs).
-
-save_reg({I,V,dirty}, Save, Acc) ->
- case cerl_sets:is_element(V, Save) of
- true -> [{set,[V],[{fr,I}],fmove}|checkerror(Acc)];
- false -> Acc
- end;
-save_reg(_, _, Acc) -> Acc.
-
-kill_regs(Rs, Kill) ->
- [kill_reg(R, Kill) || R <- Rs].
-
-kill_reg({_,V,_}=R, Kill) ->
- case cerl_sets:is_element(V, Kill) of
- true -> free;
- false -> R
- end;
-kill_reg(R, _) -> R.
-
-mark(V, [{I,V,_}|Rs], Mark) -> [{I,V,Mark}|Rs];
-mark(V, [R|Rs], Mark) -> [R|mark(V, Rs, Mark)];
-mark(_, [], _) -> [].
-
-fetch_reg(V, [{I,V,_}|_]) -> {fr,I};
-fetch_reg(V, [_|SRs]) -> fetch_reg(V, SRs).
-
-find_reg(V, [{I,V,_}|_]) -> {ok,{fr,I}};
-find_reg(V, [_|SRs]) -> find_reg(V, SRs);
-find_reg(_, []) -> error.
-
-put_reg(V, Rs, Dirty) -> put_reg_1(V, Rs, Dirty, 0).
-
-put_reg_1(V, [free|Rs], Dirty, I) -> [{I,V,Dirty}|Rs];
-put_reg_1(V, [R|Rs], Dirty, I) -> [R|put_reg_1(V, Rs, Dirty, I+1)];
-put_reg_1(V, [], Dirty, I) -> [{I,V,Dirty}].
-
-checkerror(Is) ->
- checkerror_1(Is, Is).
-
-checkerror_1([{set,[],[],fcheckerror}|_], OrigIs) -> OrigIs;
-checkerror_1([{set,_,_,{bif,fadd,_}}|_], OrigIs) -> checkerror_2(OrigIs);
-checkerror_1([{set,_,_,{bif,fsub,_}}|_], OrigIs) -> checkerror_2(OrigIs);
-checkerror_1([{set,_,_,{bif,fmul,_}}|_], OrigIs) -> checkerror_2(OrigIs);
-checkerror_1([{set,_,_,{bif,fdiv,_}}|_], OrigIs) -> checkerror_2(OrigIs);
-checkerror_1([{set,_,_,{bif,fnegate,_}}|_], OrigIs) -> checkerror_2(OrigIs);
-checkerror_1([_|Is], OrigIs) -> checkerror_1(Is, OrigIs);
-checkerror_1([], OrigIs) -> OrigIs.
-
-checkerror_2(OrigIs) -> [{set,[],[],fcheckerror}|OrigIs].
-
-
-%%% Routines for maintaining a type database. The type database
-%%% associates type information with registers.
-%%%
-%%% See the comment for verified_type/1 at the end of module for
-%%% a description of the possible types.
-
-%% tdb_new() -> EmptyDataBase
-%% Creates a new, empty type database.
-
-tdb_new() -> [].
-
-%% tdb_find(Register, Db) -> Type
-%% Returns type information or the atom error if there is no type
-%% information available for Register.
-%%
-%% See the comment for verified_type/1 at the end of module for
-%% a description of the possible types.
-
-tdb_find(Reg, Ts) ->
- case tdb_find_raw(Reg, Ts) of
- {tuple_element,_,_} -> any;
- Type -> Type
- end.
-
-%% tdb_find_source_tuple(Register, Ts) -> {source_tuple,Register} | 'none'.
-%% Find the tuple whose first element was fetched to the register Register.
-
-tdb_find_source_tuple(Reg, Ts) ->
- case tdb_find_raw(Reg, Ts) of
- {tuple_element,Src,0} ->
- {source_tuple,Src};
- _ ->
- none
- end.
-
-%% tdb_copy(Source, Dest, Db) -> Db'
-%% Update the type information for Dest to have the same type
-%% as the Source.
-
-tdb_copy({Tag,_}=S, D, Ts) when Tag =:= x; Tag =:= y ->
- case tdb_find_raw(S, Ts) of
- any -> orddict:erase(D, Ts);
- Type -> orddict:store(D, Type, Ts)
- end;
-tdb_copy(Literal, D, Ts) ->
- Type = case Literal of
- {atom,_} -> Literal;
- {float,_} -> float;
- {integer,Int} -> {integer,{Int,Int}};
- {literal,[_|_]} -> nonempty_list;
- {literal,#{}} -> map;
- {literal,Tuple} when tuple_size(Tuple) >= 1 ->
- Lit = tag_literal(element(1, Tuple)),
- {tuple,exact_size,tuple_size(Tuple),[Lit]};
- _ -> any
- end,
- tdb_store(D, verified_type(Type), Ts).
-
-%% tdb_store(Register, Type, Ts0) -> Ts.
-%% Store a new type for register Register. Return the update type
-%% database. Use this function when a new value is assigned to
-%% a register.
-%%
-%% See the comment for verified_type/1 at the end of module for
-%% a description of the possible types.
-
-tdb_store(Reg, any, Ts) ->
- erase(Reg, Ts);
-tdb_store(Reg, Type, Ts) ->
- store(Reg, verified_type(Type), Ts).
-
-store(Key, New, [{K,_}|_]=Dict) when Key < K ->
- [{Key,New}|Dict];
-store(Key, New, [{K,Val}=E|Dict]) when Key > K ->
- case Val of
- {tuple_element,Key,_} -> store(Key, New, Dict);
- _ -> [E|store(Key, New, Dict)]
- end;
-store(Key, New, [{_K,Old}|Dict]) -> %Key == K
- case Old of
- {tuple,_,_,_} ->
- [{Key,New}|erase_tuple_element(Key, Dict)];
- _ ->
- [{Key,New}|Dict]
- end;
-store(Key, New, []) -> [{Key,New}].
-
-erase(Key, [{K,_}=E|Dict]) when Key < K ->
- [E|Dict];
-erase(Key, [{K,Val}=E|Dict]) when Key > K ->
- case Val of
- {tuple_element,Key,_} -> erase(Key, Dict);
- _ -> [E|erase(Key, Dict)]
- end;
-erase(Key, [{_K,Val}|Dict]) -> %Key == K
- case Val of
- {tuple,_,_,_} -> erase_tuple_element(Key, Dict);
- _ -> Dict
- end;
-erase(_, []) -> [].
-
-erase_tuple_element(Key, [{_,{tuple_element,Key,_}}|Dict]) ->
- erase_tuple_element(Key, Dict);
-erase_tuple_element(Key, [E|Dict]) ->
- [E|erase_tuple_element(Key, Dict)];
-erase_tuple_element(_Key, []) -> [].
-
-%% tdb_meet(Register, Type, Ts0) -> Ts.
-%% Update information of a register that is used as the source for an
-%% instruction. The type Type will be combined using the meet operation
-%% with the previous type information for the register, resulting in
-%% narrower (more specific) type.
-%%
-%% For example, if the previous type is {tuple,min_size,2,[]} and the
-%% the new type is {tuple,exact_size,5,[]}, the meet of the types will
-%% be {tuple,exact_size,5,[]}.
-%%
-%% See the comment for verified_type/1 at the end of module for
-%% a description of the possible types.
-
-tdb_meet(Reg, NewType, Ts) ->
- Update = fun(Type0) -> meet(Type0, NewType) end,
- orddict:update(Reg, Update, NewType, Ts).
-
-%%%
-%%% Here follows internal helper functions for accessing and
-%%% updating the type database.
-%%%
-
-tdb_find_raw({x,_}=K, Ts) -> tdb_find_raw_1(K, Ts);
-tdb_find_raw({y,_}=K, Ts) -> tdb_find_raw_1(K, Ts);
-tdb_find_raw(_, _) -> any.
-
-tdb_find_raw_1(K, Ts) ->
- case orddict:find(K, Ts) of
- {ok,Val} -> Val;
- error -> any
- end.
-
-tag_literal(A) when is_atom(A) -> {atom,A};
-tag_literal(F) when is_float(F) -> {float,F};
-tag_literal(I) when is_integer(I) -> {integer,I};
-tag_literal([]) -> nil;
-tag_literal(Lit) -> {literal,Lit}.
-
-%% tdb_kill_xregs(Db) -> NewDb
-%% Kill all information about x registers. Also kill all tuple_element
-%% dependencies from y registers to x registers.
-
-tdb_kill_xregs([{{x,_},_Type}|Db]) -> tdb_kill_xregs(Db);
-tdb_kill_xregs([{{y,_},{tuple_element,{x,_},_}}|Db]) -> tdb_kill_xregs(Db);
-tdb_kill_xregs([Any|Db]) -> [Any|tdb_kill_xregs(Db)];
-tdb_kill_xregs([]) -> [].
-
-%% meet(Type1, Type2) -> Type
-%% Returns the "meet" of Type1 and Type2. The meet is a narrower
-%% type than Type1 and Type2. For example:
-%%
-%% meet(integer, {integer,{0,3}}) -> {integer,{0,3}}
-%%
-%% The meet for two different types result in 'none', which is
-%% the bottom element for our type lattice:
-%%
-%% meet(integer, map) -> none
-
-meet(T, T) ->
- T;
-meet({integer,_}=T, integer) ->
- T;
-meet(integer, {integer,_}=T) ->
- T;
-meet({integer,{Min1,Max1}}, {integer,{Min2,Max2}}) ->
- {integer,{max(Min1, Min2),min(Max1, Max2)}};
-meet({tuple,min_size,Sz1,Same}, {tuple,min_size,Sz2,Same}=Max) when Sz1 < Sz2 ->
- Max;
-meet({tuple,min_size,Sz1,Same}=Max, {tuple,min_size,Sz2,Same}) when Sz1 > Sz2 ->
- Max;
-meet({tuple,exact_size,_,Same}=Exact, {tuple,_,_,Same}) ->
- Exact;
-meet({tuple,_,_,Same},{tuple,exact_size,_,Same}=Exact) ->
- Exact;
-meet({tuple,SzKind1,Sz1,[]}, {tuple,_SzKind2,_Sz2,First}=Tuple2) ->
- meet({tuple,SzKind1,Sz1,First}, Tuple2);
-meet({tuple,_SzKind1,_Sz1,First}=Tuple1, {tuple,SzKind2,Sz2,_}) ->
- meet(Tuple1, {tuple,SzKind2,Sz2,First});
-meet({binary,U1}, {binary,U2}) ->
- {binary,max(U1, U2)};
-meet(T1, T2) ->
- case is_any(T1) of
- true ->
- verified_type(T2);
- false ->
- case is_any(T2) of
- true ->
- verified_type(T1);
- false ->
- none %The bottom element.
- end
- end.
-
-is_any(any) -> true;
-is_any({tuple_element,_,_}) -> true;
-is_any(_) -> false.
-
-%% verified_type(Type) -> Type
-%% Returns the passed in type if it is one of the defined types.
-%% Crashes if there is anything wrong with the type.
-%%
-%% Here are all possible types:
-%%
-%% any Any Erlang term (top element for the type lattice).
-%%
-%% {atom,Atom} The specific atom Atom.
-%% {binary,Unit} Binary/bitstring aligned to unit Unit.
-%% boolean 'true' | 'false'
-%% float Floating point number.
-%% integer Integer.
-%% {integer,{Min,Max}} Integer in the inclusive range Min through Max.
-%% map Map.
-%% nonempty_list Nonempty list.
-%% {tuple,_,_,_} Tuple (see below).
-%%
-%% none No type (bottom element for the type lattice).
-%%
-%% {tuple,min_size,Size,First} means that the corresponding register
-%% contains a tuple with *at least* Size elements (conversely,
-%% {tuple,exact_size,Size,First} means that it contains a tuple with
-%% *exactly* Size elements). An tuple with unknown size is
-%% represented as {tuple,min_size,0,[]}. First is either [] (meaning
-%% that the tuple's first element is unknown) or [FirstElement] (the
-%% contents of the first element).
-%%
-%% There is also a pseudo-type called {tuple_element,_,_}:
-%%
-%% {tuple_element,SrcTuple,ElementNumber}
-%%
-%% that does not provide any information about the type of the
-%% register itself, but provides a link back to the source tuple that
-%% the register got its value from.
-%%
-%% Note that {tuple_element,_,_} will *never* be returned by tdb_find/2.
-%% Use tdb_find_source_tuple/2 to locate the source tuple for a register.
-
-verified_type(any=T) -> T;
-verified_type({atom,_}=T) -> T;
-verified_type({binary,U}=T) when is_integer(U) -> T;
-verified_type(boolean=T) -> T;
-verified_type(integer=T) -> T;
-verified_type({integer,{Min,Max}}=T)
- when is_integer(Min), is_integer(Max) -> T;
-verified_type(map=T) -> T;
-verified_type(nonempty_list=T) -> T;
-verified_type({tuple,_,Sz,[]}=T) when is_integer(Sz) -> T;
-verified_type({tuple,_,Sz,[_]}=T) when is_integer(Sz) -> T;
-verified_type({tuple_element,_,_}=T) -> T;
-verified_type(float=T) -> T.
diff --git a/lib/compiler/src/beam_utils.erl b/lib/compiler/src/beam_utils.erl
index 6b2ab5a2a4..6e6574c0b3 100644
--- a/lib/compiler/src/beam_utils.erl
+++ b/lib/compiler/src/beam_utils.erl
@@ -18,29 +18,16 @@
%% %CopyrightEnd%
%%
%% Purpose : Common utilities used by several optimization passes.
-%%
+%%
-module(beam_utils).
--export([is_killed_block/2,is_killed/3,is_killed_at/3,
- is_not_used/3,usage/3,
- empty_label_index/0,index_label/3,index_labels/1,replace_labels/4,
- code_at/2,bif_to_test/3,is_pure_test/1,
- live_opt/1,delete_annos/1,combine_heap_needs/2,
- anno_defs/1,
- split_even/1
- ]).
+-export([replace_labels/4,is_pure_test/1,split_even/1]).
-export_type([code_index/0,module_code/0,instruction/0]).
--import(lists, [flatmap/2,map/2,member/2,sort/1,reverse/1,splitwith/2]).
-
--define(is_const(Val), (Val =:= nil orelse
- element(1, Val) =:= integer orelse
- element(1, Val) =:= float orelse
- element(1, Val) =:= atom orelse
- element(1, Val) =:= literal)).
+-import(lists, [map/2,reverse/1]).
-%% instruction() describes all instructions that are used during optimzation
+%% instruction() describes all instructions that are used during optimization
%% (from beam_a to beam_z).
-type instruction() :: atom() | tuple().
@@ -56,137 +43,6 @@
-type fail() :: beam_asm:fail() | 'fail'.
-type test() :: {'test',atom(),fail(),[beam_asm:src()]} |
{'test',atom(),fail(),integer(),list(),beam_asm:reg()}.
--type result_cache() :: gb_trees:tree(beam_asm:label(), 'killed' | 'used').
-
--record(live,
- {lbl :: code_index(), %Label to code index.
- res :: result_cache()}). %Result cache for each label.
-
-%% usage(Register, [Instruction], State) -> killed|not_used|used.
-%% Determine the usage of Register in the instruction sequence.
-%% The return value is one of:
-%%
-%% killed - The register is not used in any way.
-%% not_used - The register is referenced only by an allocating instruction
-%% (the actual value does not matter).
-%% used - The register is used (its value do matter).
-
--spec usage(beam_asm:reg(), [instruction()], code_index()) ->
- 'killed' | 'not_used' | 'used'.
-
-usage(R, Is, D) ->
- St = #live{lbl=D,res=gb_trees:empty()},
- {Usage,_} = check_liveness(R, Is, St),
- Usage.
-
-
-%% is_killed_block(Register, [Instruction]) -> true|false
-%% Determine whether a register is killed by the instruction sequence inside
-%% a block.
-%%
-%% If true is returned, it means that the register will not be
-%% referenced in ANY way (not even indirectly by an allocate instruction);
-%% i.e. it is OK to enter the instruction sequence with Register
-%% containing garbage.
-
--spec is_killed_block(beam_asm:reg(), [instruction()]) -> boolean().
-
-is_killed_block({x,X}, [{set,_,_,{alloc,Live,_}}|_]) ->
- X >= Live;
-is_killed_block(R, [{set,Ds,Ss,_Op}|Is]) ->
- not member(R, Ss) andalso (member(R, Ds) orelse is_killed_block(R, Is));
-is_killed_block(R, [{'%anno',{used,Regs}}|Is]) ->
- case R of
- {x,X} when (Regs bsr X) band 1 =:= 0 -> true;
- _ -> is_killed_block(R, Is)
- end;
-is_killed_block(_, []) -> false.
-
-%% is_killed(Register, [Instruction], State) -> true|false
-%% Determine whether a register is killed by the instruction sequence.
-%% If true is returned, it means that the register will not be
-%% referenced in ANY way (not even indirectly by an allocate instruction);
-%% i.e. it is OK to enter the instruction sequence with Register
-%% containing garbage.
-%%
-%% The state (constructed by index_instructions/1) is used to allow us
-%% to determine the kill state across branches.
-
--spec is_killed(beam_asm:reg(), [instruction()], code_index()) -> boolean().
-
-is_killed(R, Is, D) ->
- St = #live{lbl=D,res=gb_trees:empty()},
- case check_liveness(R, Is, St) of
- {killed,_} -> true;
- {exit_not_used,_} -> false;
- {_,_} -> false
- end.
-
-%% is_killed_at(Reg, Lbl, State) -> true|false
-%% Determine whether Reg is killed at label Lbl.
-
--spec is_killed_at(beam_asm:reg(), beam_asm:label(), code_index()) -> boolean().
-
-is_killed_at(R, Lbl, D) when is_integer(Lbl) ->
- St0 = #live{lbl=D,res=gb_trees:empty()},
- case check_liveness_at(R, Lbl, St0) of
- {killed,_} -> true;
- {exit_not_used,_} -> false;
- {_,_} -> false
- end.
-
-%% is_not_used(Register, [Instruction], State) -> true|false
-%% Determine whether a register is never used in the instruction sequence
-%% (it could still be referenced by an allocate instruction, meaning that
-%% it MUST be initialized, but that its value does not matter).
-%% The state is used to allow us to determine the usage state
-%% across branches.
-
--spec is_not_used(beam_asm:reg(), [instruction()], code_index()) -> boolean().
-
-is_not_used(R, Is, D) ->
- St = #live{lbl=D,res=gb_trees:empty()},
- case check_liveness(R, Is, St) of
- {used,_} -> false;
- {exit_not_used,_} -> true;
- {_,_} -> true
- end.
-
-%% index_labels(FunctionIs) -> State
-%% Index the instruction sequence so that we can quickly
-%% look up the instruction following a specific label.
-
--spec index_labels([instruction()]) -> code_index().
-
-index_labels(Is) ->
- index_labels_1(Is, []).
-
-%% empty_label_index() -> State
-%% Create an empty label index.
-
--spec empty_label_index() -> code_index().
-
-empty_label_index() ->
- gb_trees:empty().
-
-%% index_label(Label, [Instruction], State) -> State
-%% Add an index for a label.
-
--spec index_label(beam_asm:label(), [instruction()], code_index()) ->
- code_index().
-
-index_label(Lbl, Is0, Acc) ->
- Is = drop_labels(Is0),
- gb_trees:enter(Lbl, Is, Acc).
-
-
-%% code_at(Label, State) -> [I].
-%% Retrieve the code at the given label.
-
--spec code_at(beam_asm:label(), code_index()) -> [instruction()].
-
-code_at(L, Ll) ->
- gb_trees:get(L, Ll).
%% replace_labels(FunctionIs, Tail, ReplaceDb, Fallback) -> FunctionIs.
%% Replace all labels in instructions according to the ReplaceDb.
@@ -200,49 +56,6 @@ code_at(L, Ll) ->
replace_labels(Is, Acc, D, Fb) ->
replace_labels_1(Is, Acc, D, Fb).
-%% bif_to_test(Bif, [Op], Fail) -> {test,Test,Fail,[Op]}
-%% Convert a BIF to a test. Fail if not possible.
-
--spec bif_to_test(atom(), list(), fail()) -> test().
-
-bif_to_test(is_atom, [_]=Ops, Fail) -> {test,is_atom,Fail,Ops};
-bif_to_test(is_boolean, [_]=Ops, Fail) -> {test,is_boolean,Fail,Ops};
-bif_to_test(is_binary, [_]=Ops, Fail) -> {test,is_binary,Fail,Ops};
-bif_to_test(is_bitstring,[_]=Ops, Fail) -> {test,is_bitstr,Fail,Ops};
-bif_to_test(is_float, [_]=Ops, Fail) -> {test,is_float,Fail,Ops};
-bif_to_test(is_function, [_]=Ops, Fail) -> {test,is_function,Fail,Ops};
-bif_to_test(is_function, [_,_]=Ops, Fail) -> {test,is_function2,Fail,Ops};
-bif_to_test(is_integer, [_]=Ops, Fail) -> {test,is_integer,Fail,Ops};
-bif_to_test(is_list, [_]=Ops, Fail) -> {test,is_list,Fail,Ops};
-bif_to_test(is_map, [_]=Ops, Fail) -> {test,is_map,Fail,Ops};
-bif_to_test(is_number, [_]=Ops, Fail) -> {test,is_number,Fail,Ops};
-bif_to_test(is_pid, [_]=Ops, Fail) -> {test,is_pid,Fail,Ops};
-bif_to_test(is_port, [_]=Ops, Fail) -> {test,is_port,Fail,Ops};
-bif_to_test(is_reference, [_]=Ops, Fail) -> {test,is_reference,Fail,Ops};
-bif_to_test(is_tuple, [_]=Ops, Fail) -> {test,is_tuple,Fail,Ops};
-bif_to_test('=<', [A,B], Fail) -> {test,is_ge,Fail,[B,A]};
-bif_to_test('>', [A,B], Fail) -> {test,is_lt,Fail,[B,A]};
-bif_to_test('<', [_,_]=Ops, Fail) -> {test,is_lt,Fail,Ops};
-bif_to_test('>=', [_,_]=Ops, Fail) -> {test,is_ge,Fail,Ops};
-bif_to_test('==', [A,nil], Fail) -> {test,is_nil,Fail,[A]};
-bif_to_test('==', [nil,A], Fail) -> {test,is_nil,Fail,[A]};
-bif_to_test('==', [C,A], Fail) when ?is_const(C) ->
- {test,is_eq,Fail,[A,C]};
-bif_to_test('==', [_,_]=Ops, Fail) -> {test,is_eq,Fail,Ops};
-bif_to_test('/=', [C,A], Fail) when ?is_const(C) ->
- {test,is_ne,Fail,[A,C]};
-bif_to_test('/=', [_,_]=Ops, Fail) -> {test,is_ne,Fail,Ops};
-bif_to_test('=:=', [A,nil], Fail) -> {test,is_nil,Fail,[A]};
-bif_to_test('=:=', [nil,A], Fail) -> {test,is_nil,Fail,[A]};
-bif_to_test('=:=', [C,A], Fail) when ?is_const(C) ->
- {test,is_eq_exact,Fail,[A,C]};
-bif_to_test('=:=', [_,_]=Ops, Fail) -> {test,is_eq_exact,Fail,Ops};
-bif_to_test('=/=', [C,A], Fail) when ?is_const(C) ->
- {test,is_ne_exact,Fail,[A,C]};
-bif_to_test('=/=', [_,_]=Ops, Fail) -> {test,is_ne_exact,Fail,Ops};
-bif_to_test(is_record, [_,_,_]=Ops, Fail) -> {test,is_record,Fail,Ops}.
-
-
%% is_pure_test({test,Op,Fail,Ops}) -> true|false.
%% Return 'true' if the test instruction does not modify any
%% registers and/or bit syntax matching state.
@@ -256,82 +69,15 @@ is_pure_test({test,is_eq_exact,_,[_,_]}) -> true;
is_pure_test({test,is_ne_exact,_,[_,_]}) -> true;
is_pure_test({test,is_ge,_,[_,_]}) -> true;
is_pure_test({test,is_lt,_,[_,_]}) -> true;
-is_pure_test({test,is_nil,_,[_]}) -> true;
is_pure_test({test,is_nonempty_list,_,[_]}) -> true;
+is_pure_test({test,is_tagged_tuple,_,[_,_,_]}) -> true;
is_pure_test({test,test_arity,_,[_,_]}) -> true;
is_pure_test({test,has_map_fields,_,[_|_]}) -> true;
is_pure_test({test,is_bitstr,_,[_]}) -> true;
is_pure_test({test,is_function2,_,[_,_]}) -> true;
-is_pure_test({test,Op,_,Ops}) ->
+is_pure_test({test,Op,_,Ops}) ->
erl_internal:new_type_test(Op, length(Ops)).
-
-%% live_opt([Instruction]) -> [Instruction].
-%% Go through the instruction sequence in reverse execution
-%% order, keep track of liveness and remove 'move' instructions
-%% whose destination is a register that will not be used.
-%% Also insert {used,Regs} annotations at the beginning
-%% and end of each block.
-
--spec live_opt([instruction()]) -> [instruction()].
-
-live_opt(Is0) ->
- {[{label,Fail}|_]=Bef,[Fi|Is]} =
- splitwith(fun({func_info,_,_,_}) -> false;
- (_) -> true
- end, Is0),
- {func_info,_,_,Live} = Fi,
- D = gb_trees:insert(Fail, live_call(Live), gb_trees:empty()),
- Bef ++ [Fi|live_opt(reverse(Is), 0, D, [])].
-
-
-%% delete_annos([Instruction]) -> [Instruction].
-%% Delete all annotations.
-
--spec delete_annos([instruction()]) -> [instruction()].
-
-delete_annos([{block,Bl0}|Is]) ->
- case delete_annos(Bl0) of
- [] -> delete_annos(Is);
- [_|_]=Bl -> [{block,Bl}|delete_annos(Is)]
- end;
-delete_annos([{'%anno',_}|Is]) ->
- delete_annos(Is);
-delete_annos([I|Is]) ->
- [I|delete_annos(Is)];
-delete_annos([]) -> [].
-
-%% combine_heap_needs(HeapNeed1, HeapNeed2) -> HeapNeed
-%% Combine the heap need for two allocation instructions.
-
--type heap_need_tag() :: 'floats' | 'words'.
--type heap_need() :: non_neg_integer() |
- {'alloc',[{heap_need_tag(),non_neg_integer()}]}.
--spec combine_heap_needs(heap_need(), heap_need()) -> heap_need().
-
-combine_heap_needs(H1, H2) when is_integer(H1), is_integer(H2) ->
- H1 + H2;
-combine_heap_needs(H1, H2) ->
- {alloc,combine_alloc_lists([H1,H2])}.
-
-
-%% anno_defs(Instructions) -> Instructions'
-%% Add {def,RegisterBitmap} annotations to the beginning of
-%% each block. Iff bit X is set in the the bitmap, it means
-%% that {x,X} is defined when the block is entered.
-
--spec anno_defs([instruction()]) -> [instruction()].
-
-anno_defs(Is0) ->
- {Bef,[Fi|Is1]} =
- splitwith(fun({func_info,_,_,_}) -> false;
- (_) -> true
- end, Is0),
- {func_info,_,_,Arity} = Fi,
- Regs = init_def_regs(Arity),
- Is = defs(Is1, Regs, #{}),
- Bef ++ [Fi|Is].
-
%% split_even/1
%% [1,2,3,4,5,6] -> {[1,3,5],[2,4,6]}
@@ -343,446 +89,6 @@ split_even(Rs) -> split_even(Rs, [], []).
%%% Local functions.
%%%
-
-%% check_liveness(Reg, [Instruction], #live{}) ->
-%% {killed | not_used | used, #live{}}
-%% Find out whether Reg is used or killed in instruction sequence.
-%%
-%% killed - Reg is assigned or killed by an allocation instruction.
-%% not_used - the value of Reg is not used, but Reg must not be garbage
-%% exit_not_used - the value of Reg is not used, but must not be garbage
-%% because the stack will be scanned because an
-%% exit BIF will raise an exception
-%% used - Reg is used
-
-check_liveness({fr,_}, _, St) ->
- %% Conservatively always consider the floating point register used.
- {used,St};
-check_liveness(R, [{block,Blk}|Is], St0) ->
- case check_liveness_block(R, Blk, St0) of
- {transparent,St1} ->
- check_liveness(R, Is, St1);
- {alloc_used,St1} ->
- %% Used by an allocating instruction, but value not referenced.
- %% Must check the rest of the instructions.
- not_used(check_liveness(R, Is, St1));
- {Other,_}=Res when is_atom(Other) ->
- Res
- end;
-check_liveness(R, [{label,_}|Is], St) ->
- check_liveness(R, Is, St);
-check_liveness(R, [{test,_,{f,Fail},As}|Is], St0) ->
- case member(R, As) of
- true ->
- {used,St0};
- false ->
- case check_liveness_at(R, Fail, St0) of
- {killed,St1} ->
- check_liveness(R, Is, St1);
- {exit_not_used,St1} ->
- not_used(check_liveness(R, Is, St1));
- {not_used,St1} ->
- not_used(check_liveness(R, Is, St1));
- {used,_}=Used ->
- Used
- end
- end;
-check_liveness(R, [{test,Op,Fail,Live,Ss,Dst}|Is], St) ->
- %% Check this instruction as a block to get a less conservative
- %% result if the caller is is_not_used/3.
- Block = [{set,[Dst],Ss,{alloc,Live,{bif,Op,Fail}}}],
- check_liveness(R, [{block,Block}|Is], St);
-check_liveness(R, [{select,_,R,_,_}|_], St) ->
- {used,St};
-check_liveness(R, [{select,_,_,Fail,Branches}|_], St) ->
- check_liveness_everywhere(R, [Fail|Branches], St);
-check_liveness(R, [{jump,{f,F}}|_], St) ->
- check_liveness_at(R, F, St);
-check_liveness(R, [{case_end,Used}|_], St) ->
- check_liveness_exit(R, Used, St);
-check_liveness(R, [{try_case_end,Used}|_], St) ->
- check_liveness_exit(R, Used, St);
-check_liveness(R, [{badmatch,Used}|_], St) ->
- check_liveness_exit(R, Used, St);
-check_liveness(R, [if_end|_], St) ->
- check_liveness_exit(R, ignore, St);
-check_liveness(R, [{func_info,_,_,Ar}|_], St) ->
- case R of
- {x,X} when X < Ar -> {used,St};
- _ -> {killed,St}
- end;
-check_liveness(R, [{kill,R}|_], St) ->
- {killed,St};
-check_liveness(R, [{kill,_}|Is], St) ->
- check_liveness(R, Is, St);
-check_liveness(R, [{bs_init,_,_,none,Ss,Dst}|Is], St) ->
- case member(R, Ss) of
- true ->
- {used,St};
- false ->
- if
- R =:= Dst -> {killed,St};
- true -> check_liveness(R, Is, St)
- end
- end;
-check_liveness(R, [{bs_init,_,_,Live,Ss,Dst}|Is], St) ->
- case R of
- {x,X} ->
- case member(R, Ss) of
- true ->
- {used,St};
- false ->
- if
- X < Live ->
- not_used(check_liveness(R, Is, St));
- true ->
- {killed,St}
- end
- end;
- {y,_} ->
- case member(R, Ss) of
- true -> {used,St};
- false ->
- %% If the exception is taken, the stack may
- %% be scanned. Therefore the register is not
- %% guaranteed to be killed.
- if
- R =:= Dst -> {not_used,St};
- true -> not_used(check_liveness(R, Is, St))
- end
- end
- end;
-check_liveness(R, [{deallocate,_}|Is], St) ->
- case R of
- {y,_} -> {killed,St};
- _ -> check_liveness(R, Is, St)
- end;
-check_liveness({x,_}=R, [return|_], St) ->
- case R of
- {x,0} -> {used,St};
- {x,_} -> {killed,St}
- end;
-check_liveness(R, [{call,Live,_}|Is], St) ->
- case R of
- {x,X} when X < Live -> {used,St};
- {x,_} -> {killed,St};
- {y,_} -> not_used(check_liveness(R, Is, St))
- end;
-check_liveness(R, [{call_ext,Live,_}=I|Is], St) ->
- case R of
- {x,X} when X < Live ->
- {used,St};
- {x,_} ->
- {killed,St};
- {y,_} ->
- case beam_jump:is_exit_instruction(I) of
- false ->
- not_used(check_liveness(R, Is, St));
- true ->
- %% We must make sure we don't check beyond this
- %% instruction or we will fall through into random
- %% unrelated code and get stuck in a loop.
- {exit_not_used,St}
- end
- end;
-check_liveness(R, [{call_fun,Live}|Is], St) ->
- case R of
- {x,X} when X =< Live -> {used,St};
- {x,_} -> {killed,St};
- {y,_} -> not_used(check_liveness(R, Is, St))
- end;
-check_liveness(R, [{apply,Args}|Is], St) ->
- case R of
- {x,X} when X < Args+2 -> {used,St};
- {x,_} -> {killed,St};
- {y,_} -> not_used(check_liveness(R, Is, St))
- end;
-check_liveness(R, [{bif,Op,Fail,Ss,D}|Is], St) ->
- Set = {set,[D],Ss,{bif,Op,Fail}},
- check_liveness(R, [{block,[Set]}|Is], St);
-check_liveness(R, [{gc_bif,Op,{f,Fail},Live,Ss,D}|Is], St) ->
- Set = {set,[D],Ss,{alloc,Live,{gc_bif,Op,Fail}}},
- check_liveness(R, [{block,[Set]}|Is], St);
-check_liveness(R, [{bs_put,{f,0},_,Ss}|Is], St) ->
- case member(R, Ss) of
- true -> {used,St};
- false -> check_liveness(R, Is, St)
- end;
-check_liveness(R, [{bs_restore2,S,_}|Is], St) ->
- case R of
- S -> {used,St};
- _ -> check_liveness(R, Is, St)
- end;
-check_liveness(R, [{bs_save2,S,_}|Is], St) ->
- case R of
- S -> {used,St};
- _ -> check_liveness(R, Is, St)
- end;
-check_liveness(R, [{move,S,D}|Is], St) ->
- case R of
- S -> {used,St};
- D -> {killed,St};
- _ -> check_liveness(R, Is, St)
- end;
-check_liveness(R, [{make_fun2,_,_,_,NumFree}|Is], St) ->
- case R of
- {x,X} when X < NumFree -> {used,St};
- {x,_} -> {killed,St};
- {y,_} -> not_used(check_liveness(R, Is, St))
- end;
-check_liveness(R, [{'catch'=Op,Y,Fail}|Is], St) ->
- Set = {set,[Y],[],{try_catch,Op,Fail}},
- check_liveness(R, [{block,[Set]}|Is], St);
-check_liveness(R, [{'try'=Op,Y,Fail}|Is], St) ->
- Set = {set,[Y],[],{try_catch,Op,Fail}},
- check_liveness(R, [{block,[Set]}|Is], St);
-check_liveness(R, [{try_end,Y}|Is], St) ->
- case R of
- Y ->
- {killed,St};
- {y,_} ->
- %% y registers will be used if an exception occurs and
- %% control transfers to the label given in the previous
- %% try/2 instruction.
- {used,St};
- _ ->
- check_liveness(R, Is, St)
- end;
-check_liveness(R, [{catch_end,Y}|Is], St) ->
- case R of
- Y -> {killed,St};
- _ -> check_liveness(R, Is, St)
- end;
-check_liveness(R, [{get_tuple_element,S,_,D}|Is], St) ->
- case R of
- S -> {used,St};
- D -> {killed,St};
- _ -> check_liveness(R, Is, St)
- end;
-check_liveness(R, [{bs_context_to_binary,S}|Is], St) ->
- case R of
- S -> {used,St};
- _ -> check_liveness(R, Is, St)
- end;
-check_liveness(R, [{loop_rec,{f,_},{x,0}}|_], St) ->
- case R of
- {x,_} ->
- {killed,St};
- _ ->
- %% y register. Rarely happens. Be very conversative and
- %% assume it's used.
- {used,St}
- end;
-check_liveness(R, [{loop_rec_end,{f,Fail}}|_], St) ->
- check_liveness_at(R, Fail, St);
-check_liveness(R, [{line,_}|Is], St) ->
- check_liveness(R, Is, St);
-check_liveness(R, [{get_map_elements,{f,Fail},S,{list,L}}|Is], St0) ->
- {Ss,Ds} = split_even(L),
- case member(R, [S|Ss]) of
- true ->
- {used,St0};
- false ->
- case check_liveness_at(R, Fail, St0) of
- {killed,St}=Killed ->
- case member(R, Ds) of
- true -> Killed;
- false -> check_liveness(R, Is, St)
- end;
- Other ->
- Other
- end
- end;
-check_liveness(R, [{put_map,F,Op,S,D,Live,{list,Puts}}|Is], St) ->
- Set = {set,[D],[S|Puts],{alloc,Live,{put_map,Op,F}}},
- check_liveness(R, [{block,[Set]}||Is], St);
-check_liveness(R, [{put_tuple,Ar,D}|Is], St) ->
- Set = {set,[D],[],{put_tuple,Ar}},
- check_liveness(R, [{block,[Set]}||Is], St);
-check_liveness(R, [{put_list,S1,S2,D}|Is], St) ->
- Set = {set,[D],[S1,S2],put_list},
- check_liveness(R, [{block,[Set]}||Is], St);
-check_liveness(R, [{test_heap,N,Live}|Is], St) ->
- I = {block,[{set,[],[],{alloc,Live,{nozero,nostack,N,[]}}}]},
- check_liveness(R, [I|Is], St);
-check_liveness(R, [{allocate_zero,N,Live}|Is], St) ->
- I = {block,[{set,[],[],{alloc,Live,{zero,N,0,[]}}}]},
- check_liveness(R, [I|Is], St);
-check_liveness(R, [{get_hd,S,D}|Is], St) ->
- I = {block,[{set,[D],[S],get_hd}]},
- check_liveness(R, [I|Is], St);
-check_liveness(R, [{get_tl,S,D}|Is], St) ->
- I = {block,[{set,[D],[S],get_tl}]},
- check_liveness(R, [I|Is], St);
-check_liveness(R, [remove_message|Is], St) ->
- check_liveness(R, Is, St);
-check_liveness({x,X}, [build_stacktrace|_], St) when X > 0 ->
- {killed,St};
-check_liveness(R, [{recv_mark,_}|Is], St) ->
- check_liveness(R, Is, St);
-check_liveness(R, [{recv_set,_}|Is], St) ->
- check_liveness(R, Is, St);
-check_liveness(R, [{'%',_}|Is], St) ->
- check_liveness(R, Is, St);
-check_liveness(_R, Is, St) when is_list(Is) ->
- %% Not implemented. Conservatively assume that the register is used.
- {used,St}.
-
-check_liveness_everywhere(R, Lbls, St0) ->
- check_liveness_everywhere_1(R, Lbls, killed, St0).
-
-check_liveness_everywhere_1(R, [{f,Lbl}|T], Res0, St0) ->
- {Res1,St} = check_liveness_at(R, Lbl, St0),
- Res = case Res1 of
- killed -> Res0;
- _ -> Res1
- end,
- case Res of
- used -> {used,St};
- _ -> check_liveness_everywhere_1(R, T, Res, St)
- end;
-check_liveness_everywhere_1(R, [_|T], Res, St) ->
- check_liveness_everywhere_1(R, T, Res, St);
-check_liveness_everywhere_1(_, [], Res, St) ->
- {Res,St}.
-
-check_liveness_at(R, Lbl, #live{lbl=Ll,res=ResMemorized}=St0) ->
- case gb_trees:lookup(Lbl, ResMemorized) of
- {value,Res} ->
- {Res,St0};
- none ->
- {Res,St} = case gb_trees:lookup(Lbl, Ll) of
- {value,Is} -> check_liveness(R, Is, St0);
- none -> {used,St0}
- end,
- {Res,St#live{res=gb_trees:insert(Lbl, Res, St#live.res)}}
- end.
-
-not_used({used,_}=Res) -> Res;
-not_used({_,St}) -> {not_used,St}.
-
-check_liveness_exit(R, R, St) -> {used,St};
-check_liveness_exit({x,_}, _, St) -> {killed,St};
-check_liveness_exit({y,_}, _, St) -> {exit_not_used,St}.
-
-%% check_liveness_block(Reg, [Instruction], State) ->
-%% {killed | not_used | used | alloc_used | transparent,State'}
-%% Finds out how Reg is used in the instruction sequence inside a block.
-%% Returns one of:
-%% killed - Reg is assigned a new value or killed by an
-%% allocation instruction
-%% not_used - The value is not used, but the register is referenced
-%% e.g. by an allocation instruction
-%% transparent - Reg is neither used nor killed
-%% alloc_used - Used only in an allocate instruction
-%% used - Reg is explicitly used by an instruction
-%%
-%% Annotations are not allowed.
-%%
-%% (Unknown instructions will cause an exception.)
-
-check_liveness_block({x,X}=R, [{set,Ds,Ss,{alloc,Live,Op}}|Is], St0) ->
- if
- X >= Live ->
- {killed,St0};
- true ->
- case check_liveness_block_1(R, Ss, Ds, Op, Is, St0) of
- {transparent,St} -> {alloc_used,St};
- {_,_}=Res -> not_used(Res)
- end
- end;
-check_liveness_block({y,_}=R, [{set,Ds,Ss,{alloc,_Live,Op}}|Is], St0) ->
- case check_liveness_block_1(R, Ss, Ds, Op, Is, St0) of
- {transparent,St} -> {alloc_used,St};
- {_,_}=Res -> not_used(Res)
- end;
-check_liveness_block({y,_}=R, [{set,Ds,Ss,{try_catch,_,Op}}|Is], St0) ->
- case Ds of
- [R] ->
- {killed,St0};
- _ ->
- case check_liveness_block_1(R, Ss, Ds, Op, Is, St0) of
- {exit_not_used,St} ->
- {used,St};
- {transparent,St} ->
- %% Conservatively assumed that it is used.
- {used,St};
- {_,_}=Res ->
- Res
- end
- end;
-check_liveness_block(R, [{set,Ds,Ss,Op}|Is], St) ->
- check_liveness_block_1(R, Ss, Ds, Op, Is, St);
-check_liveness_block(_, [], St) -> {transparent,St}.
-
-check_liveness_block_1(R, Ss, Ds, Op, Is, St0) ->
- case member(R, Ss) of
- true ->
- {used,St0};
- false ->
- case check_liveness_block_2(R, Op, Ss, St0) of
- {killed,St} ->
- case member(R, Ds) of
- true -> {killed,St};
- false -> check_liveness_block(R, Is, St)
- end;
- {exit_not_used,St} ->
- case member(R, Ds) of
- true -> {exit_not_used,St};
- false -> check_liveness_block(R, Is, St)
- end;
- {not_used,St} ->
- not_used(case member(R, Ds) of
- true -> {killed,St};
- false -> check_liveness_block(R, Is, St)
- end);
- {used,St} ->
- {used,St}
- end
- end.
-
-check_liveness_block_2(R, {gc_bif,Op,{f,Lbl}}, Ss, St) ->
- check_liveness_block_3(R, Lbl, {Op,length(Ss)}, St);
-check_liveness_block_2(R, {bif,Op,{f,Lbl}}, Ss, St) ->
- Arity = length(Ss),
-
- %% Note that is_function/2 is a type test but is not safe.
- case erl_internal:comp_op(Op, Arity) orelse
- (erl_internal:new_type_test(Op, Arity) andalso
- erl_bifs:is_safe(erlang, Op, Arity)) of
- true ->
- {killed,St};
- false ->
- check_liveness_block_3(R, Lbl, {Op,length(Ss)}, St)
- end;
-check_liveness_block_2(R, {put_map,_Op,{f,Lbl}}, _Ss, St) ->
- check_liveness_block_3(R, Lbl, {unsafe,0}, St);
-check_liveness_block_2(_, _, _, St) ->
- {killed,St}.
-
-check_liveness_block_3({x,_}, 0, _FA, St) ->
- {killed,St};
-check_liveness_block_3({y,_}, 0, {F,A}, St) ->
- %% If the exception is thrown, the stack may be scanned,
- %% thus implicitly using the y register.
- case erl_bifs:is_safe(erlang, F, A) of
- true -> {killed,St};
- false -> {used,St}
- end;
-check_liveness_block_3(R, Lbl, _FA, St0) ->
- check_liveness_at(R, Lbl, St0).
-
-index_labels_1([{label,Lbl}|Is0], Acc) ->
- Is = drop_labels(Is0),
- index_labels_1(Is0, [{Lbl,Is}|Acc]);
-index_labels_1([_|Is], Acc) ->
- index_labels_1(Is, Acc);
-index_labels_1([], Acc) -> gb_trees:from_orddict(sort(Acc)).
-
-drop_labels([{label,_}|Is]) -> drop_labels(Is);
-drop_labels(Is) -> Is.
-
-
replace_labels_1([{test,Test,{f,Lbl},Ops}|Is], Acc, D, Fb) ->
replace_labels_1(Is, [{test,Test,{f,label(Lbl, D, Fb)},Ops}|Acc], D, Fb);
replace_labels_1([{test,Test,{f,Lbl},Live,Ops,Dst}|Is], Acc, D, Fb) ->
@@ -838,485 +144,7 @@ label(Old, D, Fb) ->
_ -> Fb(Old)
end.
-%% Help function for combine_heap_needs.
-
-combine_alloc_lists(Al0) ->
- Al1 = flatmap(fun(Words) when is_integer(Words) ->
- [{words,Words}];
- ({alloc,List}) ->
- List
- end, Al0),
- Al2 = sofs:relation(Al1),
- Al3 = sofs:relation_to_family(Al2),
- Al4 = sofs:to_external(Al3),
- [{Tag,lists:sum(L)} || {Tag,L} <- Al4].
-
-%% live_opt/4.
-
-%% Bit syntax instructions.
-live_opt([{bs_context_to_binary,Src}=I|Is], Regs0, D, Acc) ->
- Regs = x_live([Src], Regs0),
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{bs_init,Fail,_,none,Ss,Dst}=I|Is], Regs0, D, Acc) ->
- Regs1 = x_live(Ss, x_dead([Dst], Regs0)),
- Regs = live_join_label(Fail, D, Regs1),
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{bs_init,Fail,Info,Live0,Ss,Dst}|Is], Regs0, D, Acc) ->
- Regs1 = x_dead([Dst], Regs0),
- Live = live_regs(Regs1),
- true = Live =< Live0, %Assertion.
- Regs2 = live_call(Live),
- Regs3 = x_live(Ss, Regs2),
- Regs = live_join_label(Fail, D, Regs3),
- I = {bs_init,Fail,Info,Live,Ss,Dst},
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{bs_put,Fail,_,Ss}=I|Is], Regs0, D, Acc) ->
- Regs1 = x_live(Ss, Regs0),
- Regs = live_join_label(Fail, D, Regs1),
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{bs_restore2,Src,_}=I|Is], Regs0, D, Acc) ->
- Regs = x_live([Src], Regs0),
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{bs_save2,Src,_}=I|Is], Regs0, D, Acc) ->
- Regs = x_live([Src], Regs0),
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{test,bs_start_match2,Fail,Live,[Src,_],_}=I|Is], _, D, Acc) ->
- Regs0 = live_call(Live),
- Regs1 = x_live([Src], Regs0),
- Regs = live_join_label(Fail, D, Regs1),
- live_opt(Is, Regs, D, [I|Acc]);
-
-%% Other instructions.
-live_opt([{block,Bl0}|Is], Regs0, D, Acc) ->
- Live0 = make_anno({used,Regs0}),
- {Bl,Regs} = live_opt_block(reverse(Bl0), Regs0, D, [Live0]),
- Live = make_anno({used,Regs}),
- live_opt(Is, Regs, D, [{block,[Live|Bl]}|Acc]);
-live_opt([build_stacktrace=I|Is], _, D, Acc) ->
- live_opt(Is, live_call(1), D, [I|Acc]);
-live_opt([raw_raise=I|Is], _, D, Acc) ->
- live_opt(Is, live_call(3), D, [I|Acc]);
-live_opt([{label,L}=I|Is], Regs, D0, Acc) ->
- D = gb_trees:insert(L, Regs, D0),
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{jump,{f,L}}=I|Is], _, D, Acc) ->
- Regs = gb_trees:get(L, D),
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([return=I|Is], _, D, Acc) ->
- live_opt(Is, 1, D, [I|Acc]);
-live_opt([{catch_end,_}=I|Is], _, D, Acc) ->
- live_opt(Is, live_call(1), D, [I|Acc]);
-live_opt([{badmatch,Src}=I|Is], _, D, Acc) ->
- Regs = x_live([Src], 0),
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{case_end,Src}=I|Is], _, D, Acc) ->
- Regs = x_live([Src], 0),
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{try_case_end,Src}=I|Is], _, D, Acc) ->
- Regs = x_live([Src], 0),
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([if_end=I|Is], _, D, Acc) ->
- Regs = 0,
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{call,Arity,_}=I|Is], _, D, Acc) ->
- live_opt(Is, live_call(Arity), D, [I|Acc]);
-live_opt([{call_ext,Arity,_}=I|Is], _, D, Acc) ->
- live_opt(Is, live_call(Arity), D, [I|Acc]);
-live_opt([{call_fun,Arity}=I|Is], _, D, Acc) ->
- live_opt(Is, live_call(Arity+1), D, [I|Acc]);
-live_opt([{apply,Arity}=I|Is], _, D, Acc) ->
- live_opt(Is, live_call(Arity+2), D, [I|Acc]);
-live_opt([{make_fun2,_,_,_,Arity}=I|Is], _, D, Acc) ->
- live_opt(Is, live_call(Arity), D, [I|Acc]);
-live_opt([{test,_,Fail,Ss}=I|Is], Regs0, D, Acc) ->
- Regs1 = x_live(Ss, Regs0),
- Regs = live_join_label(Fail, D, Regs1),
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{test,_,Fail,Live,Ss,_}=I|Is], _, D, Acc) ->
- Regs0 = live_call(Live),
- Regs1 = x_live(Ss, Regs0),
- Regs = live_join_label(Fail, D, Regs1),
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{select,_,Src,Fail,List}=I|Is], _, D, Acc) ->
- Regs0 = 0,
- Regs1 = x_live([Src], Regs0),
- Regs = live_join_labels([Fail|List], D, Regs1),
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{try_case,Y}=I|Is], Regs0, D, Acc) ->
- Regs = live_call(1),
- case Regs0 of
- 0 ->
- live_opt(Is, Regs, D, [{try_end,Y}|Acc]);
- _ ->
- live_opt(Is, live_call(1), D, [I|Acc])
- end;
-live_opt([{loop_rec,_Fail,_Dst}=I|Is], _, D, Acc) ->
- live_opt(Is, 0, D, [I|Acc]);
-live_opt([timeout=I|Is], _, D, Acc) ->
- live_opt(Is, 0, D, [I|Acc]);
-live_opt([{wait,_}=I|Is], _, D, Acc) ->
- live_opt(Is, 0, D, [I|Acc]);
-live_opt([{get_map_elements,Fail,Src,{list,List}}=I|Is], Regs0, D, Acc) ->
- {Ss,Ds} = split_even(List),
- Regs1 = x_live([Src|Ss], x_dead(Ds, Regs0)),
- Regs = live_join_label(Fail, D, Regs1),
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{gc_bif,N,F,R,As,Dst}=I|Is], Regs0, D, Acc) ->
- Bl = [{set,[Dst],As,{alloc,R,{gc_bif,N,F}}}],
- {_,Regs} = live_opt_block(Bl, Regs0, D, []),
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{bif,N,F,As,Dst}=I|Is], Regs0, D, Acc) ->
- Bl = [{set,[Dst],As,{bif,N,F}}],
- {_,Regs} = live_opt_block(Bl, Regs0, D, []),
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{get_tuple_element,Src,Idx,Dst}=I|Is], Regs0, D, Acc) ->
- Bl = [{set,[Dst],[Src],{get_tuple_element,Idx}}],
- {_,Regs} = live_opt_block(Bl, Regs0, D, []),
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{move,Src,Dst}=I|Is], Regs0, D, Acc) ->
- Regs = x_live([Src], x_dead([Dst], Regs0)),
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{put_map,F,Op,S,Dst,R,{list,Puts}}=I|Is], Regs0, D, Acc) ->
- Bl = [{set,[Dst],[S|Puts],{alloc,R,{put_map,Op,F}}}],
- {_,Regs} = live_opt_block(Bl, Regs0, D, []),
- live_opt(Is, Regs, D, [I|Acc]);
-
-%% Transparent instructions - they neither use nor modify x registers.
-live_opt([{deallocate,_}=I|Is], Regs, D, Acc) ->
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{kill,_}=I|Is], Regs, D, Acc) ->
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{try_end,_}=I|Is], Regs, D, Acc) ->
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{loop_rec_end,_}=I|Is], Regs, D, Acc) ->
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{wait_timeout,_,nil}=I|Is], Regs, D, Acc) ->
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{wait_timeout,_,{Tag,_}}=I|Is], Regs, D, Acc) when Tag =/= x ->
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{line,_}=I|Is], Regs, D, Acc) ->
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{'catch',_,_}=I|Is], Regs, D, Acc) ->
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{'try',_,_}=I|Is], Regs, D, Acc) ->
- live_opt(Is, Regs, D, [I|Acc]);
-
-%% The following instructions can occur if the "compilation" has been
-%% started from a .S file using the 'from_asm' option.
-live_opt([{trim,_,_}=I|Is], Regs, D, Acc) ->
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{'%',_}=I|Is], Regs, D, Acc) ->
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{recv_set,_}=I|Is], Regs, D, Acc) ->
- live_opt(Is, Regs, D, [I|Acc]);
-live_opt([{recv_mark,_}=I|Is], Regs, D, Acc) ->
- live_opt(Is, Regs, D, [I|Acc]);
-
-live_opt([], _, _, Acc) -> Acc.
-
-live_opt_block([{set,[{x,X}]=Ds,Ss,move}=I|Is], Regs0, D, Acc) ->
- Regs = x_live(Ss, x_dead(Ds, Regs0)),
- case is_live(X, Regs0) of
- true ->
- live_opt_block(Is, Regs, D, [I|Acc]);
- false ->
- %% Useless move, will never be used.
- live_opt_block(Is, Regs, D, Acc)
- end;
-live_opt_block([{set,Ds,Ss,{alloc,Live0,AllocOp}}|Is], Regs0, D, Acc) ->
- %% Calculate liveness from the point of view of the GC.
- %% There will never be a GC if the instruction fails, so we should
- %% ignore the failure branch.
- GcRegs1 = x_dead(Ds, Regs0),
- GcRegs = x_live(Ss, GcRegs1),
- Live = live_regs(GcRegs),
-
- %% The life-time analysis used by the code generator is sometimes too
- %% conservative, so it may be possible to lower the number of live
- %% registers based on the exact liveness information. The main benefit is
- %% that more optimizations that depend on liveness information (such as the
- %% beam_dead pass) may be applied.
- true = Live =< Live0, %Assertion.
- I = {set,Ds,Ss,{alloc,Live,AllocOp}},
-
- %% Calculate liveness from the point of view of the preceding instruction.
- %% The liveness is the union of live registers in the GC and the live
- %% registers at the failure label.
- Regs1 = live_call(Live),
- Regs = live_join_alloc(AllocOp, D, Regs1),
- live_opt_block(Is, Regs, D, [I|Acc]);
-live_opt_block([{set,Ds,Ss,{bif,_,Fail}}=I|Is], Regs0, D, Acc) ->
- Regs1 = x_dead(Ds, Regs0),
- Regs2 = x_live(Ss, Regs1),
- Regs = live_join_label(Fail, D, Regs2),
- live_opt_block(Is, Regs, D, [I|Acc]);
-live_opt_block([{set,Ds,Ss,_}=I|Is], Regs0, D, Acc) ->
- Regs = x_live(Ss, x_dead(Ds, Regs0)),
- live_opt_block(Is, Regs, D, [I|Acc]);
-live_opt_block([{'%anno',_}|Is], Regs, D, Acc) ->
- live_opt_block(Is, Regs, D, Acc);
-live_opt_block([], Regs, _, Acc) -> {Acc,Regs}.
-
-live_join_alloc({Kind,_Name,Fail}, D, Regs) when Kind =:= gc_bif; Kind =:= put_map ->
- live_join_label(Fail, D, Regs);
-live_join_alloc(_, _, Regs) -> Regs.
-
-live_join_labels([{f,L}|T], D, Regs0) when L =/= 0 ->
- Regs = gb_trees:get(L, D) bor Regs0,
- live_join_labels(T, D, Regs);
-live_join_labels([_|T], D, Regs) ->
- live_join_labels(T, D, Regs);
-live_join_labels([], _, Regs) -> Regs.
-
-live_join_label({f,0}, _, Regs) ->
- Regs;
-live_join_label({f,L}, D, Regs) ->
- gb_trees:get(L, D) bor Regs.
-
-live_call(Live) -> (1 bsl Live) - 1.
-
-live_regs(Regs) ->
- live_regs_1(0, Regs).
-
-live_regs_1(N, 0) -> N;
-live_regs_1(N, Regs) -> live_regs_1(N+1, Regs bsr 1).
-
-x_dead([{x,N}|Rs], Regs) -> x_dead(Rs, Regs band (bnot (1 bsl N)));
-x_dead([_|Rs], Regs) -> x_dead(Rs, Regs);
-x_dead([], Regs) -> Regs.
-
-x_live([{x,N}|Rs], Regs) -> x_live(Rs, Regs bor (1 bsl N));
-x_live([_|Rs], Regs) -> x_live(Rs, Regs);
-x_live([], Regs) -> Regs.
-
-is_live(X, Regs) -> ((Regs bsr X) band 1) =:= 1.
-
split_even([], Ss, Ds) ->
{reverse(Ss),reverse(Ds)};
split_even([S,D|Rs], Ss, Ds) ->
split_even(Rs, [S|Ss], [D|Ds]).
-
-%%%
-%%% Add annotations for defined registers.
-%%%
-%%% This analysis is done by scanning the instructions in
-%%% execution order.
-%%%
-
-defs([{apply,_}=I|Is], _Regs, D) ->
- [I|defs(Is, 1, D)];
-defs([{bif,_,{f,Fail},_Src,Dst}=I|Is], Regs0, D) ->
- Regs = def_regs([Dst], Regs0),
- [I|defs(Is, Regs, update_regs(Fail, Regs0, D))];
-defs([{block,Block0}|Is], Regs0, D0) ->
- {Block,Regs,D} = defs_list(Block0, Regs0, D0),
- [{block,[make_anno({def,Regs0})|Block]}|defs(Is, Regs, D)];
-defs([{bs_init,{f,L},_,Live,_,Dst}=I|Is], Regs0, D) ->
- Regs1 = case Live of
- none -> Regs0;
- _ -> init_def_regs(Live)
- end,
- Regs = def_regs([Dst], Regs1),
- [I|defs(Is, Regs, update_regs(L, Regs, D))];
-defs([{test,bs_start_match2,{f,L},Live,_,Dst}=I|Is], _Regs, D) ->
- Regs0 = init_def_regs(Live),
- Regs = def_regs([Dst], Regs0),
- [I|defs(Is, Regs, update_regs(L, Regs0, D))];
-defs([{bs_put,{f,L},_,_}=I|Is], Regs, D) ->
- [I|defs(Is, Regs, update_regs(L, Regs, D))];
-defs([build_stacktrace=I|Is], _Regs, D) ->
- [I|defs(Is, 1, D)];
-defs([{call,_,_}=I|Is], _Regs, D) ->
- [I|defs(Is, 1, D)];
-defs([{call_ext,_,{extfunc,M,F,A}}=I|Is], _Regs, D) ->
- case erl_bifs:is_exit_bif(M, F, A) of
- false ->
- [I|defs(Is, 1, D)];
- true ->
- [I|defs_unreachable(Is, D)]
- end;
-defs([{call_ext,_,_}=I|Is], _Regs, D) ->
- [I|defs(Is, 1, D)];
-defs([{call_fun,_}=I|Is], _Regs, D) ->
- [I|defs(Is, 1, D)];
-defs([{'catch',_,{f,L}}=I|Is], Regs, D) ->
- RegsAtLabel = init_def_regs(1),
- [I|defs(Is, Regs, update_regs(L, RegsAtLabel, D))];
-defs([{catch_end,_}=I|Is], _Regs, D) ->
- Regs = init_def_regs(1),
- [I|defs(Is, Regs, D)];
-defs([{gc_bif,_,{f,Fail},Live,_Src,Dst}=I|Is], Regs0, D) ->
- true = all_defined(Live, Regs0), %Assertion.
- Regs = def_regs([Dst], init_def_regs(Live)),
- [I|defs(Is, Regs, update_regs(Fail, Regs0, D))];
-defs([{get_map_elements,{f,L},_Src,{list,DstList}}=I|Is], Regs0, D) ->
- {_,Ds} = beam_utils:split_even(DstList),
- Regs = def_regs(Ds, Regs0),
- [I|defs(Is, Regs, update_regs(L, Regs0, D))];
-defs([{get_tuple_element,_,_,Dst}=I|Is], Regs0, D) ->
- Regs = def_regs([Dst], Regs0),
- [I|defs(Is, Regs, D)];
-defs([{jump,{f,L}}=I|Is], Regs, D) ->
- [I|defs_unreachable(Is, update_regs(L, Regs, D))];
-defs([{label,L}=I|Is], Regs0, D) ->
- case D of
- #{L:=Regs1} ->
- Regs = Regs0 band Regs1,
- [I|defs(Is, Regs, D)];
- #{} ->
- [I|defs(Is, Regs0, D)]
- end;
-defs([{loop_rec,{f,L},{x,0}}=I|Is], _Regs, D0) ->
- RegsAtLabel = init_def_regs(0),
- D = update_regs(L, RegsAtLabel, D0),
- [I|defs(Is, init_def_regs(1), D)];
-defs([{loop_rec_end,_}=I|Is], _Regs, D) ->
- [I|defs_unreachable(Is, D)];
-defs([{make_fun2,_,_,_,_}=I|Is], _Regs, D) ->
- [I|defs(Is, 1, D)];
-defs([{move,_,Dst}=I|Is], Regs0, D) ->
- Regs = def_regs([Dst], Regs0),
- [I|defs(Is, Regs, D)];
-defs([{put_map,{f,Fail},_,_,Dst,_,_}=I|Is], Regs0, D) ->
- Regs = def_regs([Dst], Regs0),
- [I|defs(Is, Regs, update_regs(Fail, Regs0, D))];
-defs([raw_raise=I|Is], _Regs, D) ->
- [I|defs(Is, 1, D)];
-defs([return=I|Is], _Regs, D) ->
- [I|defs_unreachable(Is, D)];
-defs([{select,_,_Src,Fail,List}=I|Is], Regs, D0) ->
- D = update_list([Fail|List], Regs, D0),
- [I|defs_unreachable(Is, D)];
-defs([{test,_,{f,L},_}=I|Is], Regs, D) ->
- [I|defs(Is, Regs, update_regs(L, Regs, D))];
-defs([{test,_,{f,L},Live,_,Dst}=I|Is], Regs0, D) ->
- true = all_defined(Live, Regs0), %Assertion.
- Regs = def_regs([Dst], init_def_regs(Live)),
- [I|defs(Is, Regs, update_regs(L, Regs0, D))];
-defs([{'try',_,{f,L}}=I|Is], Regs, D) ->
- RegsAtLabel = init_def_regs(3),
- [I|defs(Is, Regs, update_regs(L, RegsAtLabel, D))];
-defs([{try_case,_}=I|Is], _Regs, D) ->
- [I|defs(Is, init_def_regs(3), D)];
-defs([{wait,_}=I|Is], _Regs, D) ->
- [I|defs_unreachable(Is, D)];
-defs([{wait_timeout,_,_}=I|Is], _Regs, D) ->
- [I|defs(Is, 0, D)];
-
-%% Exceptions.
-defs([{badmatch,_}=I|Is], _Regs, D) ->
- [I|defs_unreachable(Is, D)];
-defs([{case_end,_}=I|Is], _Regs, D) ->
- [I|defs_unreachable(Is, D)];
-defs([if_end=I|Is], _Regs, D) ->
- [I|defs_unreachable(Is, D)];
-defs([{try_case_end,_}=I|Is], _Regs, D) ->
- [I|defs_unreachable(Is, D)];
-
-%% Neutral instructions
-defs([{bs_context_to_binary,_}=I|Is], Regs, D) ->
- [I|defs(Is, Regs, D)];
-defs([{bs_restore2,_,_}=I|Is], Regs, D) ->
- [I|defs(Is, Regs, D)];
-defs([{bs_save2,_,_}=I|Is], Regs, D) ->
- [I|defs(Is, Regs, D)];
-defs([{deallocate,_}=I|Is], Regs, D) ->
- [I|defs(Is, Regs, D)];
-defs([{kill,_}=I|Is], Regs, D) ->
- [I|defs(Is, Regs, D)];
-defs([{line,_}=I|Is], Regs, D) ->
- [I|defs(Is, Regs, D)];
-defs([{recv_mark,_}=I|Is], Regs, D) ->
- [I|defs(Is, Regs, D)];
-defs([{recv_set,_}=I|Is], Regs, D) ->
- [I|defs(Is, Regs, D)];
-defs([timeout=I|Is], Regs, D) ->
- [I|defs(Is, Regs, D)];
-defs([{trim,_,_}=I|Is], Regs, D) ->
- [I|defs(Is, Regs, D)];
-defs([{try_end,_}=I|Is], Regs, D) ->
- [I|defs(Is, Regs, D)];
-defs([{'%',_}=I|Is], Regs, D) ->
- [I|defs(Is, Regs, D)];
-defs([], _, _) -> [].
-
-defs_unreachable([{label,L}=I|Is], D) ->
- case D of
- #{L:=Regs} ->
- [I|defs(Is, Regs, D)];
- #{} ->
- defs_unreachable(Is, D)
- end;
-defs_unreachable([_|Is], D) ->
- defs_unreachable(Is, D);
-defs_unreachable([], _D) -> [].
-
-defs_list(Is, Regs, D) ->
- defs_list(Is, Regs, D, []).
-
-defs_list([{set,Ds,_,{alloc,Live,Info}}=I|Is], Regs0, D0, Acc) ->
- true = all_defined(Live, Regs0), %Assertion.
- D = case Info of
- {gc_bif,_,{f,Fail}} ->
- update_regs(Fail, Regs0, D0);
- {put_map,_,{f,Fail}} ->
- update_regs(Fail, Regs0, D0);
- _ ->
- D0
- end,
- Regs = def_regs(Ds, init_def_regs(Live)),
- defs_list(Is, Regs, D, [I|Acc]);
-defs_list([{set,Ds,_,Info}=I|Is], Regs0, D0, Acc) ->
- D = case Info of
- {bif,_,{f,Fail}} ->
- update_regs(Fail, Regs0, D0);
- {try_catch,'catch',{f,Fail}} ->
- update_regs(Fail, init_def_regs(1), D0);
- {try_catch,'try',{f,Fail}} ->
- update_regs(Fail, init_def_regs(3), D0);
- _ ->
- D0
- end,
- Regs = def_regs(Ds, Regs0),
- defs_list(Is, Regs, D, [I|Acc]);
-defs_list([], Regs, D, Acc) ->
- {reverse(Acc),Regs,D}.
-
-init_def_regs(Arity) ->
- (1 bsl Arity) - 1.
-
-def_regs([{x,X}|T], Regs) ->
- def_regs(T, Regs bor (1 bsl X));
-def_regs([_|T], Regs) ->
- def_regs(T, Regs);
-def_regs([], Regs) -> Regs.
-
-update_list([{f,L}|T], Regs, D0) ->
- D = update_regs(L, Regs, D0),
- update_list(T, Regs, D);
-update_list([_|T], Regs, D) ->
- update_list(T, Regs, D);
-update_list([], _Regs, D) -> D.
-
-update_regs(L, Regs0, D) ->
- case D of
- #{L:=Regs1} ->
- Regs = Regs0 band Regs1,
- D#{L:=Regs};
- #{} ->
- D#{L=>Regs0}
- end.
-
-all_defined(Live, Regs) ->
- All = (1 bsl Live) - 1,
- Regs band All =:= All.
-
-%%%
-%%% Utilities.
-%%%
-
-%% make_anno(Anno) -> WrappedAnno.
-%% Wrap an annotation term.
-
-make_anno(Anno) ->
- {'%anno',Anno}.
diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl
index fb2e7df65c..296c095be2 100644
--- a/lib/compiler/src/beam_validator.erl
+++ b/lib/compiler/src/beam_validator.erl
@@ -26,8 +26,9 @@
%% Interface for compiler.
-export([module/2, format_error/1]).
+-export([type_anno/1, type_anno/2, type_anno/4]).
--import(lists, [any/2,dropwhile/2,foldl/3,foreach/2,reverse/1]).
+-import(lists, [dropwhile/2,foldl/3,member/2,reverse/1,sort/1,zip/2]).
%% To be called by the compiler.
@@ -44,6 +45,34 @@ module({Mod,Exp,Attr,Fs,Lc}=Code, _Opts)
{error,[{atom_to_list(Mod),Es}]}
end.
+%% Provides a stable interface for type annotations, used by certain passes to
+%% indicate that we can safely assume that a register has a given type.
+-spec type_anno(term()) -> term().
+type_anno(atom) -> {atom,[]};
+type_anno(bool) -> bool;
+type_anno({binary,_}) -> binary;
+type_anno(cons) -> cons;
+type_anno(float) -> {float,[]};
+type_anno(integer) -> {integer,[]};
+type_anno(list) -> list;
+type_anno(map) -> map;
+type_anno(match_context) -> match_context;
+type_anno(number) -> number;
+type_anno(nil) -> nil.
+
+-spec type_anno(term(), term()) -> term().
+type_anno(atom, Value) when is_atom(Value) -> {atom, Value};
+type_anno(float, Value) when is_float(Value) -> {float, Value};
+type_anno(integer, Value) when is_integer(Value) -> {integer, Value}.
+
+-spec type_anno(term(), term(), term(), term()) -> term().
+type_anno(tuple, Size, Exact, Elements) when is_integer(Size), Size >= 0,
+ is_map(Elements) ->
+ case Exact of
+ true -> {tuple, Size, Elements};
+ false -> {tuple, [Size], Elements}
+ end.
+
-spec format_error(term()) -> iolist().
format_error({{_M,F,A},{I,Off,limit}}) ->
@@ -90,34 +119,9 @@ format_error(Error) ->
%% format as used in the compiler and in .S files.
validate(Module, Fs) ->
- Ft = index_bs_start_match(Fs, []),
+ Ft = index_parameter_types(Fs, []),
validate_0(Module, Fs, Ft).
-index_bs_start_match([{function,_,_,Entry,Code0}|Fs], Acc0) ->
- Code = dropwhile(fun({label,L}) when L =:= Entry -> false;
- (_) -> true
- end, Code0),
- case Code of
- [{label,Entry}|Is] ->
- Acc = index_bs_start_match_1(Is, Entry, Acc0),
- index_bs_start_match(Fs, Acc);
- _ ->
- %% Something serious is wrong. Ignore it for now.
- %% It will be detected and diagnosed later.
- index_bs_start_match(Fs, Acc0)
- end;
-index_bs_start_match([], Acc) ->
- gb_trees:from_orddict(lists:sort(Acc)).
-
-index_bs_start_match_1([{test,bs_start_match2,_,_,_,_}=I|_], Entry, Acc) ->
- [{Entry,[I]}|Acc];
-index_bs_start_match_1([{test,_,{f,F},_},{bs_context_to_binary,_}|Is0], Entry, Acc) ->
- [{label,F}|Is] = dropwhile(fun({label,L}) when L =:= F -> false;
- (_) -> true
- end, Is0),
- index_bs_start_match_1(Is, Entry, Acc);
-index_bs_start_match_1(_, _, Acc) -> Acc.
-
validate_0(_Module, [], _) -> [];
validate_0(Module, [{function,Name,Ar,Entry,Code}|Fs], Ft) ->
try validate_1(Code, Name, Ar, Entry, Ft) of
@@ -132,41 +136,126 @@ validate_0(Module, [{function,Name,Ar,Entry,Code}|Fs], Ft) ->
erlang:raise(Class, Error, Stack)
end.
+-record(value_ref, {id :: index()}).
+-record(value, {op :: term(), args :: [argument()], type :: type()}).
+
+-type argument() :: #value_ref{} | literal().
+
-type index() :: non_neg_integer().
--type reg_tab() :: gb_trees:tree(index(), 'none' | {'value', _}).
-
--record(st, %Emulation state
- {x=init_regs(0, term) :: reg_tab(),%x register info.
- y=init_regs(0, initialized) :: reg_tab(),%y register info.
- f=init_fregs(), %
- numy=none, %Number of y registers.
- h=0, %Available heap size.
- hf=0, %Available heap size for floats.
- fls=undefined, %Floating point state.
- ct=[], %List of hot catch/try labels
- setelem=false, %Previous instruction was setelement/3.
- puts_left=none %put/1 instructions left.
- }).
+
+-type literal() :: {atom, [] | atom()} |
+ {float, [] | float()} |
+ {integer, [] | integer()} |
+ {literal, term()} |
+ nil.
+
+-type tuple_sz() :: [non_neg_integer()] | %% Inexact
+ non_neg_integer(). %% Exact.
+
+%% Match context type.
+-record(ms,
+ {id=make_ref() :: reference(), %Unique ID.
+ valid=0 :: non_neg_integer(), %Valid slots
+ slots=0 :: non_neg_integer() %Number of slots
+ }).
+
+-type type() :: binary |
+ cons |
+ list |
+ map |
+ nil |
+ #ms{} |
+ ms_position |
+ none |
+ number |
+ term |
+ tuple_in_progress |
+ {tuple, tuple_sz(), #{ literal() => type() }} |
+ literal().
+
+-type tag() :: initialized |
+ uninitialized |
+ {catchtag, [label()]} |
+ {trytag, [label()]}.
+
+-type x_regs() :: #{ {x, index()} => #value_ref{} }.
+-type y_regs() :: #{ {y, index()} => tag() | #value_ref{} }.
+
+%% Emulation state
+-record(st,
+ {%% All known values.
+ vs=#{} :: #{ #value_ref{} => #value{} },
+ %% Register states.
+ xs=#{} :: x_regs(),
+ ys=#{} :: y_regs(),
+ f=init_fregs(),
+ %% A set of all registers containing "fragile" terms. That is, terms
+ %% that don't exist on our process heap and would be destroyed by a
+ %% GC.
+ fragile=cerl_sets:new() :: cerl_sets:set(),
+ %% Number of Y registers.
+ %%
+ %% 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(),
+ %% Available heap size.
+ h=0,
+ %Available heap size for floats.
+ hf=0,
+ %% Floating point state.
+ fls=undefined,
+ %% List of hot catch/try labels
+ ct=[],
+ %% Previous instruction was setelement/3.
+ setelem=false,
+ %% put/1 instructions left.
+ puts_left=none
+ }).
-type label() :: integer().
-type label_set() :: gb_sets:set(label()).
-type branched_tab() :: gb_trees:tree(label(), #st{}).
-type ft_tab() :: gb_trees:tree().
--record(vst, %Validator state
- {current=none :: #st{} | 'none', %Current state
- branched=gb_trees:empty() :: branched_tab(), %States at jumps
- labels=gb_sets:empty() :: label_set(), %All defined labels
- ft=gb_trees:empty() :: ft_tab() %Some other functions
- % in the module (those that start with bs_start_match2).
- }).
-
-%% Match context type.
--record(ms,
- {id=make_ref() :: reference(), %Unique ID.
- valid=0 :: non_neg_integer(), %Valid slots
- slots=0 :: non_neg_integer() %Number of slots
- }).
+%% Validator state
+-record(vst,
+ {%% Current state
+ current=none :: #st{} | 'none',
+ %% States at labels
+ branched=gb_trees:empty() :: branched_tab(),
+ %% All defined labels
+ labels=gb_sets:empty() :: label_set(),
+ %% Argument information of other functions in the module
+ ft=gb_trees:empty() :: ft_tab(),
+ %% Counter for #value_ref{} creation
+ ref_ctr=0 :: index()
+ }).
+
+index_parameter_types([{function,_,_,Entry,Code0}|Fs], Acc0) ->
+ Code = dropwhile(fun({label,L}) when L =:= Entry -> false;
+ (_) -> true
+ end, Code0),
+ case Code of
+ [{label,Entry}|Is] ->
+ Acc = index_parameter_types_1(Is, Entry, Acc0),
+ index_parameter_types(Fs, Acc);
+ _ ->
+ %% Something serious is wrong. Ignore it for now.
+ %% It will be detected and diagnosed later.
+ index_parameter_types(Fs, Acc0)
+ end;
+index_parameter_types([], Acc) ->
+ gb_trees:from_orddict(sort(Acc)).
+
+index_parameter_types_1([{'%', {type_info, Reg, Type0}} | Is], Entry, Acc) ->
+ Type = case Type0 of
+ match_context -> #ms{};
+ _ -> Type0
+ end,
+ Key = {Entry, Reg},
+ index_parameter_types_1(Is, Entry, [{Key, Type} | Acc]);
+index_parameter_types_1(_, _, Acc) ->
+ Acc.
validate_1(Is, Name, Arity, Entry, Ft) ->
validate_2(labels(Is), Name, Arity, Entry, Ft).
@@ -179,14 +268,10 @@ validate_2({Ls1,Is}, Name, Arity, _Entry, _Ft) ->
validate_3({Ls2,Is}, Name, Arity, Entry, Mod, Ls1, Ft) ->
Offset = 1 + length(Ls1) + 1 + length(Ls2),
- EntryOK = lists:member(Entry, Ls2),
+ EntryOK = member(Entry, Ls2),
if
EntryOK ->
- St = init_state(Arity),
- Vst0 = #vst{current=St,
- branched=gb_trees_from_list([{L,St} || L <- Ls1]),
- labels=gb_sets:from_list(Ls1++Ls2),
- ft=Ft},
+ Vst0 = init_vst(Arity, Ls1, Ls2, Ft),
MFA = {Mod,Name,Arity},
Vst = valfun(Is, MFA, Offset, Vst0),
validate_fun_info_branches(Ls1, MFA, Vst);
@@ -203,7 +288,13 @@ validate_fun_info_branches([], _, _) -> ok.
validate_fun_info_branches_1(Arity, {_,_,Arity}, _) -> ok;
validate_fun_info_branches_1(X, {Mod,Name,Arity}=MFA, Vst) ->
try
- get_term_type({x,X}, Vst)
+ case Vst of
+ #vst{current=#st{numy=none}} ->
+ ok;
+ #vst{current=#st{numy=Size}} ->
+ error({unexpected_stack_frame,Size})
+ end,
+ assert_term({x,X}, Vst)
catch Error ->
I = {func_info,{atom,Mod},{atom,Name},Arity},
Offset = 2,
@@ -224,19 +315,22 @@ labels_1([{line,_}|Is], R) ->
labels_1(Is, R) ->
{reverse(R),Is}.
-init_state(Arity) ->
- Xs = init_regs(Arity, term),
- Ys = init_regs(0, initialized),
- kill_heap_allocation(#st{x=Xs,y=Ys,numy=none,ct=[]}).
+init_vst(Arity, Ls1, Ls2, Ft) ->
+ Vst0 = init_function_args(Arity - 1, #vst{current=#st{}}),
+ Branches = gb_trees_from_list([{L,Vst0#vst.current} || L <- Ls1]),
+ Labels = gb_sets:from_list(Ls1++Ls2),
+ Vst0#vst{branched=Branches,
+ labels=Labels,
+ ft=Ft}.
+
+init_function_args(-1, Vst) ->
+ Vst;
+init_function_args(X, Vst) ->
+ init_function_args(X - 1, create_term(term, argument, [], {x,X}, Vst)).
kill_heap_allocation(St) ->
St#st{h=0,hf=0}.
-init_regs(0, _) ->
- gb_trees:empty();
-init_regs(N, Type) ->
- gb_trees_from_list([{R,Type} || R <- lists:seq(0, N-1)]).
-
valfun([], MFA, _Offset, #vst{branched=Targets0,labels=Labels0}=Vst) ->
Targets = gb_trees:keys(Targets0),
Labels = gb_sets:to_list(Labels0),
@@ -257,20 +351,25 @@ valfun([I|Is], MFA, Offset, Vst0) ->
%% Instructions that are allowed in dead code or when failing,
%% that is while the state is undecided in some way.
-valfun_1({label,Lbl}, #vst{current=St0,branched=B,labels=Lbls}=Vst) ->
- St = merge_states(Lbl, St0, B),
- Vst#vst{current=St,branched=gb_trees:enter(Lbl, St, B),
- labels=gb_sets:add(Lbl, Lbls)};
+valfun_1({label,Lbl}, #vst{current=St0,
+ ref_ctr=Counter0,
+ branched=B,
+ labels=Lbls}=Vst) ->
+ {St, Counter} = merge_states(Lbl, St0, B, Counter0),
+ Vst#vst{current=St,
+ ref_ctr=Counter,
+ branched=gb_trees:enter(Lbl, St, B),
+ labels=gb_sets:add(Lbl, Lbls)};
valfun_1(_I, #vst{current=none}=Vst) ->
%% Ignore instructions after erlang:error/1,2, which
%% the original R10B compiler thought would return.
Vst;
valfun_1({badmatch,Src}, Vst) ->
- assert_term(Src, Vst),
+ assert_durable_term(Src, Vst),
verify_y_init(Vst),
kill_state(Vst);
valfun_1({case_end,Src}, Vst) ->
- assert_term(Src, Vst),
+ assert_durable_term(Src, Vst),
verify_y_init(Vst),
kill_state(Vst);
valfun_1(if_end, Vst) ->
@@ -278,35 +377,21 @@ valfun_1(if_end, Vst) ->
kill_state(Vst);
valfun_1({try_case_end,Src}, Vst) ->
verify_y_init(Vst),
- assert_term(Src, Vst),
+ assert_durable_term(Src, Vst),
kill_state(Vst);
-%% Instructions that can not cause exceptions
-valfun_1({bs_context_to_binary,Ctx}, #vst{current=#st{x=Xs}}=Vst) ->
- case Ctx of
- {Tag,X} when Tag =:= x; Tag =:= y ->
- Type = case gb_trees:lookup(X, Xs) of
- {value,#ms{}} -> term;
- _ -> get_term_type(Ctx, Vst)
- end,
- set_type_reg(Type, Ctx, Vst);
- _ ->
- error({bad_source,Ctx})
- end;
+%% Instructions that cannot cause exceptions
+valfun_1({bs_get_tail,Ctx,Dst,Live}, Vst0) ->
+ bsm_validate_context(Ctx, Vst0),
+ verify_live(Live, Vst0),
+ verify_y_init(Vst0),
+ Vst = prune_x_regs(Live, Vst0),
+ extract_term(binary, bs_get_tail, [Ctx], Dst, Vst, Vst0);
valfun_1(bs_init_writable=I, Vst) ->
call(I, 1, Vst);
valfun_1(build_stacktrace=I, Vst) ->
call(I, 1, Vst);
-valfun_1({move,{y,_}=Src,{y,_}=Dst}, Vst) ->
- %% The stack trimming optimization may generate a move from an initialized
- %% but unassigned Y register to another Y register.
- case get_term_type_1(Src, Vst) of
- {catchtag,_} -> error({catchtag,Src});
- {trytag,_} -> error({trytag,Src});
- Type -> set_type_reg(Type, Dst, Vst)
- end;
valfun_1({move,Src,Dst}, Vst) ->
- Type = get_move_term_type(Src, Vst),
- set_type_reg(Type, Dst, Vst);
+ assign(Src, Dst, Vst);
valfun_1({fmove,Src,{fr,_}=Dst}, Vst) ->
assert_type(float, Src, Vst),
set_freg(Dst, Vst);
@@ -314,15 +399,15 @@ valfun_1({fmove,{fr,_}=Src,Dst}, Vst0) ->
assert_freg_set(Src, Vst0),
assert_fls(checked, Vst0),
Vst = eat_heap_float(Vst0),
- set_type_reg({float,[]}, Dst, Vst);
-valfun_1({kill,{y,_}=Reg}, Vst) ->
- set_type_y(initialized, Reg, Vst);
-valfun_1({init,{y,_}=Reg}, Vst) ->
- set_type_y(initialized, Reg, Vst);
+ create_term({float,[]}, fmove, [], Dst, Vst);
+valfun_1({kill,Reg}, Vst) ->
+ create_tag(initialized, kill, [], Reg, Vst);
+valfun_1({init,Reg}, Vst) ->
+ create_tag(initialized, init, [], Reg, Vst);
valfun_1({test_heap,Heap,Live}, Vst) ->
test_heap(Heap, Live, Vst);
-valfun_1({bif,Op,{f,_},Src,Dst}=I, Vst) ->
- case is_bif_safe(Op, length(Src)) of
+valfun_1({bif,Op,{f,_},Ss,Dst}=I, Vst) ->
+ case is_bif_safe(Op, length(Ss)) of
false ->
%% Since the BIF can fail, make sure that any catch state
%% is updated.
@@ -330,21 +415,32 @@ valfun_1({bif,Op,{f,_},Src,Dst}=I, Vst) ->
true ->
%% It can't fail, so we finish handling it here (not updating
%% catch state).
- validate_src(Src, Vst),
- Type = bif_type(Op, Src, Vst),
- set_type_reg(Type, Dst, Vst)
+ validate_src(Ss, Vst),
+ Type = bif_return_type(Op, Ss, Vst),
+ extract_term(Type, {bif,Op}, Ss, Dst, Vst)
end;
%% Put instructions.
valfun_1({put_list,A,B,Dst}, Vst0) ->
assert_term(A, Vst0),
assert_term(B, Vst0),
Vst = eat_heap(2, Vst0),
- set_type_reg(cons, Dst, Vst);
+ create_term(cons, put_list, [A, B], Dst, Vst);
+valfun_1({put_tuple2,Dst,{list,Elements}}, Vst0) ->
+ _ = [assert_term(El, Vst0) || El <- Elements],
+ Size = length(Elements),
+ Vst = eat_heap(Size+1, Vst0),
+ {Es,_} = foldl(fun(Val, {Es0, Index}) ->
+ Type = get_term_type(Val, Vst0),
+ Es = set_element_type({integer,Index}, Type, Es0),
+ {Es, Index + 1}
+ end, {#{}, 1}, Elements),
+ Type = {tuple,Size,Es},
+ create_term(Type, put_tuple2, [], Dst, Vst);
valfun_1({put_tuple,Sz,Dst}, Vst0) when is_integer(Sz) ->
Vst1 = eat_heap(1, Vst0),
- Vst = set_type_reg(tuple_in_progress, Dst, Vst1),
+ Vst = create_term(tuple_in_progress, put_tuple, [], Dst, Vst1),
#vst{current=St0} = Vst,
- St = St0#st{puts_left={Sz,{Dst,{tuple,Sz}}}},
+ St = St0#st{puts_left={Sz,{Dst,Sz,#{}}}},
Vst#vst{current=St};
valfun_1({put,Src}, Vst0) ->
assert_term(Src, Vst0),
@@ -353,11 +449,14 @@ valfun_1({put,Src}, Vst0) ->
case St0 of
#st{puts_left=none} ->
error(not_building_a_tuple);
- #st{puts_left={1,{Dst,Type}}} ->
+ #st{puts_left={1,{Dst,Sz,Es0}}} ->
+ Es = Es0#{ {integer,Sz} => get_term_type(Src, Vst0) },
St = St0#st{puts_left=none},
- set_type_reg(Type, Dst, Vst#vst{current=St});
- #st{puts_left={PutsLeft,Info}} when is_integer(PutsLeft) ->
- St = St0#st{puts_left={PutsLeft-1,Info}},
+ create_term({tuple,Sz,Es}, put_tuple, [], Dst, Vst#vst{current=St});
+ #st{puts_left={PutsLeft,{Dst,Sz,Es0}}} when is_integer(PutsLeft) ->
+ Index = Sz - PutsLeft + 1,
+ Es = Es0#{ {integer,Index} => get_term_type(Src, Vst0) },
+ St = St0#st{puts_left={PutsLeft-1,{Dst,Sz,Es}}},
Vst#vst{current=St}
end;
%% Instructions for optimization of selective receives.
@@ -370,13 +469,28 @@ valfun_1(remove_message, Vst) ->
%% The message term is no longer fragile. It can be used
%% without restrictions.
remove_fragility(Vst);
+valfun_1({'%', {type_info, Reg, match_context}}, Vst) ->
+ update_type(fun meet/2, #ms{}, Reg, Vst);
+valfun_1({'%', {type_info, Reg, Type}}, Vst) ->
+ %% Explicit type information inserted by optimization passes to indicate
+ %% that Reg has a certain type, so that we can accept cross-function type
+ %% optimizations.
+ update_type(fun meet/2, Type, Reg, Vst);
+valfun_1({'%', {remove_fragility, Reg}}, Vst) ->
+ %% This is a hack to make prim_eval:'receive'/2 work.
+ %%
+ %% Normally it's illegal to pass fragile terms as a function argument as we
+ %% have no way of knowing what the callee will do with it, but we know that
+ %% prim_eval:'receive'/2 won't leak the term, nor cause a GC since it's
+ %% disabled while matching messages.
+ remove_fragility(Reg, Vst);
valfun_1({'%',_}, Vst) ->
Vst;
valfun_1({line,_}, Vst) ->
Vst;
%% Exception generating calls
valfun_1({call_ext,Live,Func}=I, Vst) ->
- case return_type(Func, Vst) of
+ case call_return_type(Func, Vst) of
exception ->
verify_live(Live, Vst),
%% The stack will be scanned, so Y registers
@@ -391,106 +505,133 @@ valfun_1(_I, #vst{current=#st{ct=undecided}}) ->
%%
%% Allocate and deallocate, et.al
valfun_1({allocate,Stk,Live}, Vst) ->
- allocate(false, Stk, 0, Live, Vst);
+ allocate(uninitialized, Stk, 0, Live, Vst);
valfun_1({allocate_heap,Stk,Heap,Live}, Vst) ->
- allocate(false, Stk, Heap, Live, Vst);
+ allocate(uninitialized, Stk, Heap, Live, Vst);
valfun_1({allocate_zero,Stk,Live}, Vst) ->
- allocate(true, Stk, 0, Live, Vst);
+ allocate(initialized, Stk, 0, Live, Vst);
valfun_1({allocate_heap_zero,Stk,Heap,Live}, Vst) ->
- allocate(true, Stk, Heap, Live, Vst);
+ allocate(initialized, Stk, Heap, Live, Vst);
valfun_1({deallocate,StkSize}, #vst{current=#st{numy=StkSize}}=Vst) ->
verify_no_ct(Vst),
deallocate(Vst);
valfun_1({deallocate,_}, #vst{current=#st{numy=NumY}}) ->
error({allocated,NumY});
-valfun_1({trim,N,Remaining}, #vst{current=#st{y=Yregs0,numy=NumY}=St}=Vst) ->
+valfun_1({trim,N,Remaining}, #vst{current=St0}=Vst) ->
+ #st{numy=NumY} = St0,
if
- N =< NumY, N+Remaining =:= NumY ->
- Yregs1 = [{Y-N,Type} || {Y,Type} <- gb_trees:to_list(Yregs0), Y >= N],
- Yregs = gb_trees_from_list(Yregs1),
- Vst#vst{current=St#st{y=Yregs,numy=NumY-N}};
- true ->
- error({trim,N,Remaining,allocated,NumY})
+ N =< NumY, N+Remaining =:= NumY ->
+ Vst#vst{current=trim_stack(N, 0, NumY, St0)};
+ N > NumY; N+Remaining =/= NumY ->
+ error({trim,N,Remaining,allocated,NumY})
end;
%% Catch & try.
-valfun_1({'catch',Dst,{f,Fail}}, Vst0) when Fail /= none ->
- Vst = #vst{current=#st{ct=Fails}=St} =
- set_type_y({catchtag,[Fail]}, Dst, Vst0),
- Vst#vst{current=St#st{ct=[[Fail]|Fails]}};
-valfun_1({'try',Dst,{f,Fail}}, Vst0) ->
- Vst = #vst{current=#st{ct=Fails}=St} =
- set_type_y({trytag,[Fail]}, Dst, Vst0),
- Vst#vst{current=St#st{ct=[[Fail]|Fails]}};
-valfun_1({catch_end,Reg}, #vst{current=#st{ct=[Fail|Fails]}}=Vst0) ->
- case get_special_y_type(Reg, Vst0) of
- {catchtag,Fail} ->
- Vst = #vst{current=St} = set_catch_end(Reg, Vst0),
- Xs = gb_trees_from_list([{0,term}]),
- Vst#vst{current=St#st{x=Xs,ct=Fails,fls=undefined}};
- Type ->
- error({bad_type,Type})
+valfun_1({'catch',Dst,{f,Fail}}, Vst) when Fail =/= none ->
+ init_try_catch_branch(catchtag, Dst, Fail, Vst);
+valfun_1({'try',Dst,{f,Fail}}, Vst) when Fail =/= none ->
+ init_try_catch_branch(trytag, Dst, Fail, Vst);
+valfun_1({catch_end,Reg}, #vst{current=#st{ct=[Fail|_]}}=Vst0) ->
+ case get_tag_type(Reg, Vst0) of
+ {catchtag,Fail} ->
+ %% {x,0} contains the caught term, if any.
+ create_term(term, catch_end, [], {x,0}, kill_catch_tag(Reg, Vst0));
+ Type ->
+ error({wrong_tag_type,Type})
end;
-valfun_1({try_end,Reg}, #vst{current=#st{ct=[Fail|Fails]}=St0}=Vst0) ->
- case get_special_y_type(Reg, Vst0) of
- {trytag,Fail} ->
- Vst = case Fail of
- [FailLabel] -> branch_state(FailLabel, Vst0);
- _ -> Vst0
- end,
- St = St0#st{ct=Fails,fls=undefined},
- set_catch_end(Reg, Vst#vst{current=St});
- Type ->
- error({bad_type,Type})
+valfun_1({try_end,Reg}, #vst{current=#st{ct=[Fail|_]}}=Vst) ->
+ case get_tag_type(Reg, Vst) of
+ {trytag,Fail} ->
+ %% Kill the catch tag, note that x registers are unaffected.
+ kill_catch_tag(Reg, Vst);
+ Type ->
+ error({wrong_tag_type,Type})
end;
-valfun_1({try_case,Reg}, #vst{current=#st{ct=[Fail|Fails]}}=Vst0) ->
- case get_special_y_type(Reg, Vst0) of
- {trytag,Fail} ->
- Vst = #vst{current=St} = set_catch_end(Reg, Vst0),
- Xs = gb_trees_from_list([{0,{atom,[]}},{1,term},{2,term}]),
- Vst#vst{current=St#st{x=Xs,ct=Fails,fls=undefined}};
- Type ->
- error({bad_type,Type})
+valfun_1({try_case,Reg}, #vst{current=#st{ct=[Fail|_]}}=Vst0) ->
+ case get_tag_type(Reg, Vst0) of
+ {trytag,Fail} ->
+ %% Kill the catch tag and all x registers.
+ Vst1 = prune_x_regs(0, kill_catch_tag(Reg, Vst0)),
+
+ %% Class:Error:Stacktrace
+ Vst2 = create_term({atom,[]}, try_case, [], {x,0}, Vst1),
+ Vst = create_term(term, try_case, [], {x,1}, Vst2),
+ create_term(term, try_case, [], {x,2}, Vst);
+ Type ->
+ error({wrong_tag_type,Type})
end;
valfun_1({get_list,Src,D1,D2}, Vst0) ->
+ assert_not_literal(Src),
assert_type(cons, Src, Vst0),
- Vst = set_type_reg(term, Src, D1, Vst0),
- set_type_reg(term, Src, D2, Vst);
+ Vst = extract_term(term, get_hd, [Src], D1, Vst0),
+ extract_term(term, get_tl, [Src], D2, Vst);
valfun_1({get_hd,Src,Dst}, Vst) ->
+ assert_not_literal(Src),
assert_type(cons, Src, Vst),
- set_type_reg(term, Src, Dst, Vst);
+ extract_term(term, get_hd, [Src], Dst, Vst);
valfun_1({get_tl,Src,Dst}, Vst) ->
+ assert_not_literal(Src),
assert_type(cons, Src, Vst),
- set_type_reg(term, Src, Dst, Vst);
-valfun_1({get_tuple_element,Src,I,Dst}, Vst) ->
- assert_type({tuple_element,I+1}, Src, Vst),
- set_type_reg(term, Src, Dst, Vst);
+ extract_term(term, get_tl, [Src], Dst, Vst);
+valfun_1({get_tuple_element,Src,N,Dst}, Vst) ->
+ assert_not_literal(Src),
+ assert_type({tuple_element,N+1}, Src, Vst),
+ Index = {integer,N+1},
+ Type = get_element_type(Index, Src, Vst),
+ extract_term(Type, {bif,element}, [Index, Src], Dst, Vst);
+valfun_1({jump,{f,Lbl}}, Vst) ->
+ kill_state(branch_state(Lbl, Vst));
valfun_1(I, Vst) ->
valfun_2(I, Vst).
+init_try_catch_branch(Tag, Dst, Fail, Vst0) ->
+ Vst1 = create_tag({Tag,[Fail]}, 'try_catch', [], Dst, Vst0),
+ #vst{current=#st{ct=Fails}=St0} = Vst1,
+ St = St0#st{ct=[[Fail]|Fails]},
+ Vst = Vst0#vst{current=St},
+
+ complex_test(Fail,
+ fun(CatchVst) ->
+ #vst{current=#st{ys=Ys}} = CatchVst,
+ maps:fold(fun init_catch_handler_1/3, CatchVst, Ys)
+ end,
+ fun(SuccVst) ->
+ SuccVst
+ end, Vst).
+
+%% Set the initial state at the try/catch label. Assume that Y registers
+%% contain terms or try/catch tags.
+init_catch_handler_1(Reg, initialized, Vst) ->
+ create_term(term, 'catch_handler', [], Reg, Vst);
+init_catch_handler_1(Reg, uninitialized, Vst) ->
+ create_term(term, 'catch_handler', [], Reg, Vst);
+init_catch_handler_1(_, _, Vst) ->
+ Vst.
+
%% Update branched state if necessary and try next set of instructions.
valfun_2(I, #vst{current=#st{ct=[]}}=Vst) ->
valfun_3(I, Vst);
valfun_2(I, #vst{current=#st{ct=[[Fail]|_]}}=Vst) when is_integer(Fail) ->
- %% Update branched state
+ %% Update branched state.
valfun_3(I, branch_state(Fail, Vst));
valfun_2(_, _) ->
error(ambiguous_catch_try_state).
%% Handle the remaining floating point instructions here.
%% Floating point.
-valfun_3({fconv,Src,{fr,_}=Dst}, Vst) ->
- assert_term(Src, Vst),
+valfun_3({fconv,Src,{fr,_}=Dst}, Vst0) ->
+ assert_term(Src, Vst0),
+ Vst = update_type(fun meet/2, number, Src, Vst0),
set_freg(Dst, Vst);
-valfun_3({bif,fadd,_,[_,_]=Src,Dst}, Vst) ->
- float_op(Src, Dst, Vst);
-valfun_3({bif,fdiv,_,[_,_]=Src,Dst}, Vst) ->
- float_op(Src, Dst, Vst);
-valfun_3({bif,fmul,_,[_,_]=Src,Dst}, Vst) ->
- float_op(Src, Dst, Vst);
-valfun_3({bif,fnegate,_,[_]=Src,Dst}, Vst) ->
- float_op(Src, Dst, Vst);
-valfun_3({bif,fsub,_,[_,_]=Src,Dst}, Vst) ->
- float_op(Src, Dst, Vst);
+valfun_3({bif,fadd,_,[_,_]=Ss,Dst}, Vst) ->
+ float_op(Ss, Dst, Vst);
+valfun_3({bif,fdiv,_,[_,_]=Ss,Dst}, Vst) ->
+ float_op(Ss, Dst, Vst);
+valfun_3({bif,fmul,_,[_,_]=Ss,Dst}, Vst) ->
+ float_op(Ss, Dst, Vst);
+valfun_3({bif,fnegate,_,[_]=Ss,Dst}, Vst) ->
+ float_op(Ss, Dst, Vst);
+valfun_3({bif,fsub,_,[_,_]=Ss,Dst}, Vst) ->
+ float_op(Ss, Dst, Vst);
valfun_3(fclearerror, Vst) ->
case get_fls(Vst) of
undefined -> ok;
@@ -541,67 +682,67 @@ valfun_4({call_ext_last,_,_,_}, #vst{current=#st{numy=NumY}}) ->
valfun_4({make_fun2,_,_,_,Live}, Vst) ->
call(make_fun, Live, Vst);
%% Other BIFs
-valfun_4({bif,tuple_size,{f,Fail},[Tuple],Dst}, Vst0) ->
- TupleType0 = get_term_type(Tuple, Vst0),
- Vst1 = branch_state(Fail, Vst0),
- TupleType = upgrade_tuple_type({tuple,[0]}, TupleType0),
- Vst = set_type(TupleType, Tuple, Vst1),
- set_type_reg({integer,[]}, Dst, Vst);
valfun_4({bif,element,{f,Fail},[Pos,Tuple],Dst}, Vst0) ->
- TupleType0 = get_term_type(Tuple, Vst0),
PosType = get_term_type(Pos, Vst0),
+ ElementType = get_element_type(PosType, Tuple, Vst0),
+ InferredType = {tuple,[get_tuple_size(PosType)],#{}},
Vst1 = branch_state(Fail, Vst0),
- TupleType = upgrade_tuple_type({tuple,[get_tuple_size(PosType)]}, TupleType0),
- Vst = set_type(TupleType, Tuple, Vst1),
- set_type_reg(term, Tuple, Dst, Vst);
+ Vst2 = update_type(fun meet/2, InferredType, Tuple, Vst1),
+ Vst = update_type(fun meet/2, {integer,[]}, Pos, Vst2),
+ extract_term(ElementType, {bif,element}, [Pos,Tuple], Dst, Vst);
valfun_4({bif,raise,{f,0},Src,_Dst}, Vst) ->
validate_src(Src, Vst),
kill_state(Vst);
valfun_4(raw_raise=I, Vst) ->
call(I, 3, Vst);
-valfun_4({bif,map_get,{f,Fail},[_Key,Map]=Src,Dst}, Vst0) ->
- validate_src(Src, Vst0),
+valfun_4({bif,Op,{f,Fail},[Cons]=Ss,Dst}, Vst0)
+ when Op =:= hd; Op =:= tl ->
+ validate_src(Ss, Vst0),
+ Vst = type_test(Fail, cons, Cons, Vst0),
+ Type = bif_return_type(Op, Ss, Vst),
+ extract_term(Type, {bif,Op}, Ss, Dst, Vst);
+valfun_4({bif,Op,{f,Fail},Ss,Dst}, Vst0) ->
+ validate_src(Ss, Vst0),
Vst1 = branch_state(Fail, Vst0),
- Vst = set_type(map, Map, Vst1),
- Type = propagate_fragility(term, Src, Vst),
- set_type_reg(Type, Dst, Vst);
-valfun_4({bif,is_map_key,{f,Fail},[_Key,Map]=Src,Dst}, Vst0) ->
- validate_src(Src, Vst0),
- Vst1 = branch_state(Fail, Vst0),
- Vst = set_type(map, Map, Vst1),
- Type = propagate_fragility(bool, Src, Vst),
- set_type_reg(Type, Dst, Vst);
-valfun_4({bif,Op,{f,Fail},Src,Dst}, Vst0) ->
- validate_src(Src, Vst0),
- Vst = branch_state(Fail, Vst0),
- Type0 = bif_type(Op, Src, Vst),
- Type = propagate_fragility(Type0, Src, Vst),
- set_type_reg(Type, Dst, Vst);
-valfun_4({gc_bif,Op,{f,Fail},Live,Src,Dst}, #vst{current=St0}=Vst0) ->
+
+ %% Infer argument types. Note that we can't type_test in the general case
+ %% as the BIF could fail for reasons other than bad argument types.
+ ArgTypes = bif_arg_types(Op, Ss),
+ Vst = foldl(fun({Arg, T}, Vsti) ->
+ update_type(fun meet/2, T, Arg, Vsti)
+ end, Vst1, zip(Ss, ArgTypes)),
+
+ Type = bif_return_type(Op, Ss, Vst),
+ extract_term(Type, {bif,Op}, Ss, Dst, Vst);
+valfun_4({gc_bif,Op,{f,Fail},Live,Ss,Dst}, #vst{current=St0}=Vst0) ->
+ validate_src(Ss, Vst0),
verify_live(Live, Vst0),
verify_y_init(Vst0),
St = kill_heap_allocation(St0),
Vst1 = Vst0#vst{current=St},
Vst2 = branch_state(Fail, Vst1),
- Vst = prune_x_regs(Live, Vst2),
- validate_src(Src, Vst),
- Type0 = bif_type(Op, Src, Vst),
- Type = propagate_fragility(Type0, Src, Vst),
- set_type_reg(Type, Dst, Vst);
+
+ ArgTypes = bif_arg_types(Op, Ss),
+ Vst3 = foldl(fun({Arg, T}, Vsti) ->
+ update_type(fun meet/2, T, Arg, Vsti)
+ end, Vst2, zip(Ss, ArgTypes)),
+
+ Type = bif_return_type(Op, Ss, Vst3),
+ Vst = prune_x_regs(Live, Vst3),
+ extract_term(Type, {gc_bif,Op}, Ss, Dst, Vst, Vst0);
valfun_4(return, #vst{current=#st{numy=none}}=Vst) ->
- assert_term({x,0}, Vst),
+ assert_durable_term({x,0}, Vst),
kill_state(Vst);
valfun_4(return, #vst{current=#st{numy=NumY}}) ->
error({stack_frame,NumY});
-valfun_4({jump,{f,Lbl}}, Vst) ->
- kill_state(branch_state(Lbl, Vst));
valfun_4({loop_rec,{f,Fail},Dst}, Vst0) ->
- Vst = branch_state(Fail, Vst0),
%% This term may not be part of the root set until
%% remove_message/0 is executed. If control transfers
%% to the loop_rec_end/1 instruction, no part of
%% this term must be stored in a Y register.
- set_type_reg({fragile,term}, Dst, Vst);
+ Vst1 = branch_state(Fail, Vst0),
+ {Ref, Vst} = new_value(term, loop_rec, [], Vst1),
+ mark_fragile(Dst, set_reg_vref(Ref, Dst, Vst));
valfun_4({wait,_}, Vst) ->
verify_y_init(Vst),
kill_state(Vst);
@@ -612,50 +753,36 @@ valfun_4({wait_timeout,_,Src}, Vst) ->
valfun_4({loop_rec_end,_}, Vst) ->
verify_y_init(Vst),
kill_state(Vst);
-valfun_4(timeout, #vst{current=St}=Vst) ->
- Vst#vst{current=St#st{x=init_regs(0, term)}};
+valfun_4(timeout, Vst) ->
+ prune_x_regs(0, Vst);
valfun_4(send, Vst) ->
call(send, 2, Vst);
-valfun_4({set_tuple_element,Src,Tuple,I}, Vst) ->
+valfun_4({set_tuple_element,Src,Tuple,N}, Vst) ->
+ I = N + 1,
assert_term(Src, Vst),
- assert_type({tuple_element,I+1}, Tuple, Vst),
- Vst;
+ assert_type({tuple_element,I}, Tuple, Vst),
+ %% Manually update the tuple type; we can't rely on the ordinary update
+ %% 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.
+ {tuple, Sz, Es0} = get_term_type(Tuple, Vst),
+ Es = set_element_type({integer,I}, get_term_type(Src, Vst), Es0),
+ override_type({tuple, Sz, Es}, Tuple, Vst);
%% Match instructions.
-valfun_4({select_val,Src,{f,Fail},{list,Choices}}, Vst) ->
- assert_term(Src, Vst),
- Lbls = [L || {f,L} <- Choices]++[Fail],
- kill_state(foldl(fun(L, S) -> branch_state(L, S) end, Vst, Lbls));
+valfun_4({select_val,Src,{f,Fail},{list,Choices}}, Vst0) ->
+ assert_term(Src, Vst0),
+ assert_choices(Choices),
+ select_val_branches(Fail, Src, Choices, Vst0);
valfun_4({select_tuple_arity,Tuple,{f,Fail},{list,Choices}}, Vst) ->
assert_type(tuple, Tuple, Vst),
- kill_state(branch_arities(Choices, Tuple, branch_state(Fail, Vst)));
+ assert_arities(Choices),
+ select_arity_branches(Fail, Choices, Tuple, Vst);
%% New bit syntax matching instructions.
-valfun_4({test,bs_start_match2,{f,Fail},Live,[Ctx,NeedSlots],Ctx}, Vst0) ->
- %% If source and destination registers are the same, match state
- %% is OK as input.
- CtxType = get_move_term_type(Ctx, Vst0),
- verify_live(Live, Vst0),
- verify_y_init(Vst0),
- Vst1 = prune_x_regs(Live, Vst0),
- BranchVst = case CtxType of
- #ms{} ->
- %% The failure branch will never be taken when Ctx
- %% is a match context. Therefore, the type for Ctx
- %% at the failure label must not be match_context
- %% (or we could reject legal code).
- set_type_reg(term, Ctx, Vst1);
- _ ->
- Vst1
- end,
- Vst = branch_state(Fail, BranchVst),
- set_type_reg(bsm_match_state(NeedSlots), Ctx, Vst);
-valfun_4({test,bs_start_match2,{f,Fail},Live,[Src,Slots],Dst}, Vst0) ->
- assert_term(Src, Vst0),
- verify_live(Live, Vst0),
- verify_y_init(Vst0),
- Vst1 = prune_x_regs(Live, Vst0),
- Vst = branch_state(Fail, Vst1),
- set_type_reg(bsm_match_state(Slots), Src, Dst, Vst);
+valfun_4({test,bs_start_match3,{f,Fail},Live,[Src],Dst}, Vst) ->
+ validate_bs_start_match(Fail, Live, bsm_match_state(), Src, Dst, Vst);
+valfun_4({test,bs_start_match2,{f,Fail},Live,[Src,Slots],Dst}, Vst) ->
+ validate_bs_start_match(Fail, Live, bsm_match_state(Slots), Src, Dst, Vst);
valfun_4({test,bs_match_string,{f,Fail},[Ctx,_,_]}, Vst) ->
bsm_validate_context(Ctx, Vst),
branch_state(Fail, Vst);
@@ -675,68 +802,118 @@ valfun_4({test,bs_skip_utf16,{f,Fail},[Ctx,Live,_]}, Vst) ->
validate_bs_skip_utf(Fail, Ctx, Live, Vst);
valfun_4({test,bs_skip_utf32,{f,Fail},[Ctx,Live,_]}, Vst) ->
validate_bs_skip_utf(Fail, Ctx, Live, Vst);
-valfun_4({test,bs_get_integer2,{f,Fail},Live,[Ctx,_,_,_],Dst}, Vst) ->
- validate_bs_get(Fail, Ctx, Live, {integer, []}, Dst, Vst);
-valfun_4({test,bs_get_float2,{f,Fail},Live,[Ctx,_,_,_],Dst}, Vst) ->
- validate_bs_get(Fail, Ctx, Live, {float, []}, Dst, Vst);
-valfun_4({test,bs_get_binary2,{f,Fail},Live,[Ctx,_,_,_],Dst}, Vst) ->
- Type = propagate_fragility(term, [Ctx], Vst),
- validate_bs_get(Fail, Ctx, Live, Type, Dst, Vst);
-valfun_4({test,bs_get_utf8,{f,Fail},Live,[Ctx,_],Dst}, Vst) ->
- validate_bs_get(Fail, Ctx, Live, {integer, []}, Dst, Vst);
-valfun_4({test,bs_get_utf16,{f,Fail},Live,[Ctx,_],Dst}, Vst) ->
- validate_bs_get(Fail, Ctx, Live, {integer, []}, Dst, Vst);
-valfun_4({test,bs_get_utf32,{f,Fail},Live,[Ctx,_],Dst}, Vst) ->
- validate_bs_get(Fail, Ctx, Live, {integer, []}, Dst, Vst);
+valfun_4({test,bs_get_integer2=Op,{f,Fail},Live,[Ctx,_,_,_],Dst}, Vst) ->
+ validate_bs_get(Op, Fail, Ctx, Live, {integer, []}, Dst, Vst);
+valfun_4({test,bs_get_float2=Op,{f,Fail},Live,[Ctx,_,_,_],Dst}, Vst) ->
+ validate_bs_get(Op, Fail, Ctx, Live, {float, []}, Dst, Vst);
+valfun_4({test,bs_get_binary2=Op,{f,Fail},Live,[Ctx,_,_,_],Dst}, Vst) ->
+ validate_bs_get(Op, Fail, Ctx, Live, binary, Dst, Vst);
+valfun_4({test,bs_get_utf8=Op,{f,Fail},Live,[Ctx,_],Dst}, Vst) ->
+ validate_bs_get(Op, Fail, Ctx, Live, {integer, []}, Dst, Vst);
+valfun_4({test,bs_get_utf16=Op,{f,Fail},Live,[Ctx,_],Dst}, Vst) ->
+ validate_bs_get(Op, Fail, Ctx, Live, {integer, []}, Dst, Vst);
+valfun_4({test,bs_get_utf32=Op,{f,Fail},Live,[Ctx,_],Dst}, Vst) ->
+ validate_bs_get(Op, Fail, Ctx, Live, {integer, []}, Dst, Vst);
valfun_4({bs_save2,Ctx,SavePoint}, Vst) ->
bsm_save(Ctx, SavePoint, Vst);
valfun_4({bs_restore2,Ctx,SavePoint}, Vst) ->
bsm_restore(Ctx, SavePoint, Vst);
+valfun_4({bs_get_position, Ctx, Dst, Live}, Vst0) ->
+ bsm_validate_context(Ctx, Vst0),
+ verify_live(Live, Vst0),
+ verify_y_init(Vst0),
+ Vst = prune_x_regs(Live, Vst0),
+ create_term(ms_position, bs_get_position, [Ctx], Dst, Vst, Vst0);
+valfun_4({bs_set_position, Ctx, Pos}, Vst) ->
+ bsm_validate_context(Ctx, Vst),
+ assert_type(ms_position, Pos, Vst),
+ Vst;
%% Other test instructions.
-valfun_4({test,is_float,{f,Lbl},[Float]}, Vst) ->
- assert_term(Float, Vst),
- set_type({float,[]}, Float, branch_state(Lbl, Vst));
-valfun_4({test,is_tuple,{f,Lbl},[Tuple]}, Vst) ->
- Type0 = get_term_type(Tuple, Vst),
- Type = upgrade_tuple_type({tuple,[0]}, Type0),
- set_type(Type, Tuple, branch_state(Lbl, Vst));
-valfun_4({test,is_nonempty_list,{f,Lbl},[Cons]}, Vst) ->
- assert_term(Cons, Vst),
- set_type(cons, Cons, branch_state(Lbl, Vst));
-valfun_4({test,test_arity,{f,Lbl},[Tuple,Sz]}, Vst) when is_integer(Sz) ->
- assert_type(tuple, Tuple, Vst),
- set_type_reg({tuple,Sz}, Tuple, branch_state(Lbl, Vst));
-valfun_4({test,is_tagged_tuple,{f,Lbl},[Src,Sz,_Atom]}, Vst) ->
- validate_src([Src], Vst),
- set_type_reg({tuple, Sz}, Src, branch_state(Lbl, Vst));
+valfun_4({test,is_atom,{f,Lbl},[Src]}, Vst) ->
+ type_test(Lbl, {atom,[]}, Src, Vst);
+valfun_4({test,is_binary,{f,Lbl},[Src]}, Vst) ->
+ type_test(Lbl, binary, Src, Vst);
+valfun_4({test,is_bitstr,{f,Lbl},[Src]}, Vst) ->
+ type_test(Lbl, binary, Src, Vst);
+valfun_4({test,is_boolean,{f,Lbl},[Src]}, Vst) ->
+ type_test(Lbl, bool, Src, Vst);
+valfun_4({test,is_float,{f,Lbl},[Src]}, Vst) ->
+ type_test(Lbl, {float,[]}, Src, Vst);
+valfun_4({test,is_tuple,{f,Lbl},[Src]}, Vst) ->
+ type_test(Lbl, {tuple,[0],#{}}, Src, Vst);
+valfun_4({test,is_integer,{f,Lbl},[Src]}, Vst) ->
+ type_test(Lbl, {integer,[]}, Src, Vst);
+valfun_4({test,is_nonempty_list,{f,Lbl},[Src]}, Vst) ->
+ type_test(Lbl, cons, Src, Vst);
+valfun_4({test,is_number,{f,Lbl},[Src]}, Vst) ->
+ type_test(Lbl, number, Src, Vst);
+valfun_4({test,is_list,{f,Lbl},[Src]}, Vst) ->
+ type_test(Lbl, list, Src, Vst);
+valfun_4({test,is_nil,{f,Lbl},[Src]}, Vst) ->
+ %% is_nil is an exact check against the 'nil' value, and should not be
+ %% treated as a simple type test.
+ assert_term(Src, Vst),
+ complex_test(Lbl,
+ fun(FailVst) ->
+ update_ne_types(Src, nil, FailVst)
+ end,
+ fun(SuccVst) ->
+ update_eq_types(Src, nil, SuccVst)
+ end, Vst);
+valfun_4({test,is_map,{f,Lbl},[Src]}, Vst) ->
+ case Src of
+ {Tag,_} when Tag =:= x; Tag =:= y ->
+ type_test(Lbl, map, Src, Vst);
+ {literal,Map} when is_map(Map) ->
+ Vst;
+ _ ->
+ assert_term(Src, Vst),
+ kill_state(Vst)
+ end;
+valfun_4({test,test_arity,{f,Lbl},[Tuple,Sz]}, Vst0) when is_integer(Sz) ->
+ assert_type(tuple, Tuple, Vst0),
+ Vst = branch_state(Lbl, Vst0),
+ update_type(fun meet/2, {tuple,Sz,#{}}, Tuple, Vst);
+valfun_4({test,is_tagged_tuple,{f,Lbl},[Src,Sz,Atom]}, Vst0) ->
+ assert_term(Src, Vst0),
+ Vst = branch_state(Lbl, Vst0),
+ update_type(fun meet/2, {tuple,Sz,#{ {integer,1} => Atom }}, Src, Vst);
valfun_4({test,has_map_fields,{f,Lbl},Src,{list,List}}, Vst) ->
assert_type(map, Src, Vst),
assert_unique_map_keys(List),
branch_state(Lbl, Vst);
-valfun_4({test,is_map,{f,Lbl},[Src]}, Vst0) ->
- Vst = branch_state(Lbl, Vst0),
- case Src of
- {Tag,_} when Tag =:= x; Tag =:= y ->
- set_type_reg(map, Src, Vst);
- {literal,Map} when is_map(Map) ->
- Vst;
- _ ->
- kill_state(Vst)
- end;
+valfun_4({test,is_eq_exact,{f,Lbl},[Src,Val]=Ss}, Vst) ->
+ validate_src(Ss, Vst),
+ complex_test(Lbl,
+ fun(FailVst) ->
+ update_ne_types(Src, Val, FailVst)
+ end,
+ fun(SuccVst) ->
+ update_eq_types(Src, Val, SuccVst)
+ end, Vst);
+valfun_4({test,is_ne_exact,{f,Lbl},[Src,Val]=Ss}, Vst) ->
+ validate_src(Ss, Vst),
+ complex_test(Lbl,
+ fun(FailVst) ->
+ update_eq_types(Src, Val, FailVst)
+ end,
+ fun(SuccVst) ->
+ update_ne_types(Src, Val, SuccVst)
+ end, Vst);
valfun_4({test,_Op,{f,Lbl},Src}, Vst) ->
validate_src(Src, Vst),
branch_state(Lbl, Vst);
valfun_4({bs_add,{f,Fail},[A,B,_],Dst}, Vst) ->
assert_term(A, Vst),
assert_term(B, Vst),
- set_type_reg({integer,[]}, Dst, branch_state(Fail, Vst));
+ create_term({integer,[]}, bs_add, [A, B], Dst, branch_state(Fail, Vst));
valfun_4({bs_utf8_size,{f,Fail},A,Dst}, Vst) ->
assert_term(A, Vst),
- set_type_reg({integer,[]}, Dst, branch_state(Fail, Vst));
+ create_term({integer,[]}, bs_utf8_size, [A], Dst, branch_state(Fail, Vst));
valfun_4({bs_utf16_size,{f,Fail},A,Dst}, Vst) ->
assert_term(A, Vst),
- set_type_reg({integer,[]}, Dst, branch_state(Fail, Vst));
+ create_term({integer,[]}, bs_utf16_size, [A], Dst, branch_state(Fail, Vst));
valfun_4({bs_init2,{f,Fail},Sz,Heap,Live,_,Dst}, Vst0) ->
verify_live(Live, Vst0),
verify_y_init(Vst0),
@@ -749,7 +926,7 @@ valfun_4({bs_init2,{f,Fail},Sz,Heap,Live,_,Dst}, Vst0) ->
Vst1 = heap_alloc(Heap, Vst0),
Vst2 = branch_state(Fail, Vst1),
Vst = prune_x_regs(Live, Vst2),
- set_type_reg(binary, Dst, Vst);
+ create_term(binary, bs_init2, [], Dst, Vst, Vst0);
valfun_4({bs_init_bits,{f,Fail},Sz,Heap,Live,_,Dst}, Vst0) ->
verify_live(Live, Vst0),
verify_y_init(Vst0),
@@ -762,7 +939,7 @@ valfun_4({bs_init_bits,{f,Fail},Sz,Heap,Live,_,Dst}, Vst0) ->
Vst1 = heap_alloc(Heap, Vst0),
Vst2 = branch_state(Fail, Vst1),
Vst = prune_x_regs(Live, Vst2),
- set_type_reg(binary, Dst, Vst);
+ create_term(binary, bs_init_bits, [], Dst, Vst);
valfun_4({bs_append,{f,Fail},Bits,Heap,Live,_Unit,Bin,_Flags,Dst}, Vst0) ->
verify_live(Live, Vst0),
verify_y_init(Vst0),
@@ -771,12 +948,12 @@ valfun_4({bs_append,{f,Fail},Bits,Heap,Live,_Unit,Bin,_Flags,Dst}, Vst0) ->
Vst1 = heap_alloc(Heap, Vst0),
Vst2 = branch_state(Fail, Vst1),
Vst = prune_x_regs(Live, Vst2),
- set_type_reg(binary, Dst, Vst);
+ create_term(binary, bs_append, [Bin], Dst, Vst, Vst0);
valfun_4({bs_private_append,{f,Fail},Bits,_Unit,Bin,_Flags,Dst}, Vst0) ->
assert_term(Bits, Vst0),
assert_term(Bin, Vst0),
Vst = branch_state(Fail, Vst0),
- set_type_reg(binary, Dst, Vst);
+ create_term(binary, bs_private_append, [Bin], Dst, Vst);
valfun_4({bs_put_string,Sz,_}, Vst) when is_integer(Sz) ->
Vst;
valfun_4({bs_put_binary,{f,Fail},Sz,_,_,Src}, Vst) ->
@@ -801,64 +978,104 @@ valfun_4({bs_put_utf32,{f,Fail},_,Src}, Vst) ->
assert_term(Src, Vst),
branch_state(Fail, Vst);
%% Map instructions.
-valfun_4({put_map_assoc,{f,Fail},Src,Dst,Live,{list,List}}, Vst) ->
- verify_put_map(Fail, Src, Dst, Live, List, Vst);
-valfun_4({put_map_exact,{f,Fail},Src,Dst,Live,{list,List}}, Vst) ->
- verify_put_map(Fail, Src, Dst, Live, List, Vst);
+valfun_4({put_map_assoc=Op,{f,Fail},Src,Dst,Live,{list,List}}, Vst) ->
+ verify_put_map(Op, Fail, Src, Dst, Live, List, Vst);
+valfun_4({put_map_exact=Op,{f,Fail},Src,Dst,Live,{list,List}}, Vst) ->
+ verify_put_map(Op, Fail, Src, Dst, Live, List, Vst);
valfun_4({get_map_elements,{f,Fail},Src,{list,List}}, Vst) ->
verify_get_map(Fail, Src, List, Vst);
valfun_4(_, _) ->
error(unknown_instruction).
verify_get_map(Fail, Src, List, Vst0) ->
+ assert_not_literal(Src), %OTP 22.
assert_type(map, Src, Vst0),
- Vst1 = foldl(fun(D, Vsti) ->
- case is_reg_defined(D,Vsti) of
- true -> set_type_reg(term,D,Vsti);
- false -> Vsti
- end
- end, Vst0, extract_map_vals(List)),
- Vst2 = branch_state(Fail, Vst1),
- Keys = extract_map_keys(List),
- assert_unique_map_keys(Keys),
- verify_get_map_pair(List, Src, Vst0, Vst2).
-extract_map_vals([_Key,Val|T]) ->
- [Val|extract_map_vals(T)];
-extract_map_vals([]) -> [].
+ complex_test(Fail,
+ fun(FailVst) ->
+ clobber_map_vals(List, Src, FailVst)
+ end,
+ fun(SuccVst) ->
+ Keys = extract_map_keys(List),
+ assert_unique_map_keys(Keys),
+ extract_map_vals(List, Src, SuccVst, SuccVst)
+ end, Vst0).
+
+%% get_map_elements may leave its destinations in an inconsistent state when
+%% the fail label is taken. Consider the following:
+%%
+%% {get_map_elements,{f,7},{x,1},{list,[{atom,a},{x,1},{atom,b},{x,2}]}}.
+%%
+%% If 'a' exists but not 'b', {x,1} is overwritten when we jump to {f,7}.
+clobber_map_vals([Key,Dst|T], Map, Vst0) ->
+ case is_reg_defined(Dst, Vst0) of
+ true ->
+ Vst = extract_term(term, {bif,map_get}, [Key, Map], Dst, Vst0),
+ clobber_map_vals(T, Map, Vst);
+ false ->
+ clobber_map_vals(T, Map, Vst0)
+ end;
+clobber_map_vals([], _Map, Vst) ->
+ Vst.
extract_map_keys([Key,_Val|T]) ->
[Key|extract_map_keys(T)];
extract_map_keys([]) -> [].
-verify_get_map_pair([Src,Dst|Vs], Map, Vst0, Vsti0) ->
- assert_term(Src, Vst0),
- Vsti = set_type_reg(term, Map, Dst, Vsti0),
- verify_get_map_pair(Vs, Map, Vst0, Vsti);
-verify_get_map_pair([], _Map, _Vst0, Vst) -> Vst.
+extract_map_vals([Key,Dst|Vs], Map, Vst0, Vsti0) ->
+ assert_term(Key, Vst0),
+ Vsti = extract_term(term, {bif,map_get}, [Key, Map], Dst, Vsti0),
+ extract_map_vals(Vs, Map, Vst0, Vsti);
+extract_map_vals([], _Map, _Vst0, Vst) ->
+ Vst.
-verify_put_map(Fail, Src, Dst, Live, List, Vst0) ->
+verify_put_map(Op, Fail, Src, Dst, Live, List, Vst0) ->
assert_type(map, Src, Vst0),
verify_live(Live, Vst0),
verify_y_init(Vst0),
- foreach(fun (Term) -> assert_term(Term, Vst0) end, List),
+ [assert_term(Term, Vst0) || Term <- List],
Vst1 = heap_alloc(0, Vst0),
Vst2 = branch_state(Fail, Vst1),
Vst = prune_x_regs(Live, Vst2),
Keys = extract_map_keys(List),
assert_unique_map_keys(Keys),
- set_type_reg(map, Dst, Vst).
+ create_term(map, Op, [Src], Dst, Vst, Vst0).
+
+%%
+%% Common code for validating bs_start_match* instructions.
+%%
+
+validate_bs_start_match(Fail, Live, Type, Src, Dst, Vst) ->
+ verify_live(Live, Vst),
+ verify_y_init(Vst),
+
+ %% #ms{} can represent either a match context or a term, so we have to mark
+ %% the source as a term if it fails with a match context as an input. This
+ %% hack is only needed until we get proper union types.
+ complex_test(Fail,
+ fun(FailVst) ->
+ case get_raw_type(Src, FailVst) of
+ #ms{} -> override_type(term, Src, FailVst);
+ _ -> FailVst
+ end
+ end,
+ fun(SuccVst0) ->
+ SuccVst1 = update_type(fun meet/2, binary, Src, SuccVst0),
+ SuccVst = prune_x_regs(Live, SuccVst1),
+ extract_term(Type, bs_start_match, [Src], Dst,
+ SuccVst, SuccVst0)
+ end, Vst).
%%
%% Common code for validating bs_get* instructions.
%%
-validate_bs_get(Fail, Ctx, Live, Type, Dst, Vst0) ->
+validate_bs_get(Op, Fail, Ctx, Live, Type, Dst, Vst0) ->
bsm_validate_context(Ctx, Vst0),
verify_live(Live, Vst0),
verify_y_init(Vst0),
Vst1 = prune_x_regs(Live, Vst0),
Vst = branch_state(Fail, Vst1),
- set_type_reg(Type, Dst, Vst).
+ extract_term(Type, Op, [Ctx], Dst, Vst, Vst0).
%%
%% Common code for validating bs_skip_utf* instructions.
@@ -885,34 +1102,29 @@ val_dsetel({set_tuple_element,_,_,_}, #vst{current=#st{setelem=false}}) ->
error(illegal_context_for_set_tuple_element);
val_dsetel({set_tuple_element,_,_,_}, #vst{current=#st{setelem=true}}=Vst) ->
Vst;
+val_dsetel({get_tuple_element,_,_,_}, Vst) ->
+ Vst;
val_dsetel({line,_}, Vst) ->
Vst;
val_dsetel(_, #vst{current=#st{setelem=true}=St}=Vst) ->
Vst#vst{current=St#st{setelem=false}};
val_dsetel(_, Vst) -> Vst.
-kill_state(#vst{current=#st{ct=[[Fail]|_]}}=Vst) when is_integer(Fail) ->
- %% There is an active catch. Make sure that we merge the state into
- %% the catch label before clearing it, so that that we can be sure
- %% that the label gets a state.
- kill_state_1(branch_state(Fail, Vst));
kill_state(Vst) ->
- kill_state_1(Vst).
-
-kill_state_1(Vst) ->
Vst#vst{current=none}.
%% A "plain" call.
%% The stackframe must be initialized.
%% The instruction will return to the instruction following the call.
-call(Name, Live, #vst{current=St}=Vst) ->
- verify_call_args(Name, Live, Vst),
- verify_y_init(Vst),
- case return_type(Name, Vst) of
- Type when Type =/= exception ->
- %% Type is never 'exception' because it has been handled earlier.
- Xs = gb_trees_from_list([{0,Type}]),
- Vst#vst{current=St#st{x=Xs,f=init_fregs()}}
+call(Name, Live, #vst{current=St0}=Vst0) ->
+ verify_call_args(Name, Live, Vst0),
+ verify_y_init(Vst0),
+ case call_return_type(Name, Vst0) of
+ Type when Type =/= exception ->
+ %% Type is never 'exception' because it has been handled earlier.
+ St = St0#st{f=init_fregs()},
+ Vst = prune_x_regs(0, Vst0#vst{current=St}),
+ create_term(Type, call, [], {x,0}, Vst)
end.
%% Tail call.
@@ -928,79 +1140,132 @@ tail_call(Name, Live, Vst0) ->
verify_call_args(_, 0, #vst{}) ->
ok;
verify_call_args({f,Lbl}, Live, Vst) when is_integer(Live)->
- verify_local_call(Lbl, Live, Vst);
+ verify_local_args(Live - 1, Lbl, #{}, Vst);
verify_call_args(_, Live, Vst) when is_integer(Live)->
- verify_call_args_1(Live, Vst);
+ verify_remote_args_1(Live - 1, Vst);
verify_call_args(_, Live, _) ->
error({bad_number_of_live_regs,Live}).
-verify_call_args_1(0, _) -> ok;
-verify_call_args_1(N, Vst) ->
- X = N - 1,
- get_term_type({x,X}, Vst),
- verify_call_args_1(X, Vst).
-
-verify_local_call(Lbl, Live, Vst) ->
- case all_ms_in_x_regs(Live, Vst) of
- [{R,Ctx}] ->
- %% Verify that there is a suitable bs_start_match2 instruction.
- verify_call_match_context(Lbl, R, Vst),
-
- %% Since the callee has consumed the match context,
- %% there must be no additional copies in Y registers.
- #ms{id=Id} = Ctx,
- case ms_in_y_regs(Id, Vst) of
- [] ->
- ok;
- [_|_]=Ys ->
- error({multiple_match_contexts,[R|Ys]})
- end;
- [_,_|_]=Xs0 ->
- Xs = [R || {R,_} <- Xs0],
- error({multiple_match_contexts,Xs});
- [] ->
- ok
+verify_remote_args_1(-1, _) ->
+ ok;
+verify_remote_args_1(X, Vst) ->
+ assert_durable_term({x, X}, Vst),
+ verify_remote_args_1(X - 1, Vst).
+
+verify_local_args(-1, _Lbl, _CtxIds, _Vst) ->
+ ok;
+verify_local_args(X, Lbl, CtxIds, Vst) ->
+ Reg = {x, X},
+ assert_movable(Reg, Vst),
+ assert_not_fragile(Reg, Vst),
+ case get_raw_type(Reg, Vst) of
+ #ms{id=Id}=Type ->
+ case CtxIds of
+ #{ Id := Other } ->
+ error({multiple_match_contexts, [Reg, Other]});
+ #{} ->
+ verify_arg_type(Lbl, Reg, Type, Vst),
+ verify_local_args(X - 1, Lbl, CtxIds#{ Id => Reg }, Vst)
+ end;
+ Type ->
+ verify_arg_type(Lbl, Reg, Type, Vst),
+ verify_local_args(X - 1, Lbl, CtxIds, Vst)
end.
-all_ms_in_x_regs(0, _Vst) ->
- [];
-all_ms_in_x_regs(Live0, Vst) ->
- Live = Live0 - 1,
- R = {x,Live},
- case get_move_term_type(R, Vst) of
- #ms{}=M ->
- [{R,M}|all_ms_in_x_regs(Live, Vst)];
- _ ->
- all_ms_in_x_regs(Live, Vst)
+%% Verifies that the given argument narrows to what the function expects.
+verify_arg_type(Lbl, Reg, #ms{}, #vst{ft=Ft}) ->
+ %% Match contexts require explicit support, and may not be passed to a
+ %% function that accepts arbitrary terms.
+ case gb_trees:lookup({Lbl, Reg}, Ft) of
+ {value, #ms{}} -> ok;
+ _ -> error(no_bs_start_match2)
+ end;
+verify_arg_type(Lbl, Reg, GivenType, #vst{ft=Ft}) ->
+ case gb_trees:lookup({Lbl, Reg}, Ft) of
+ {value, #ms{}} ->
+ %% Functions that accept match contexts also accept all other
+ %% terms. This will change once we support union types.
+ ok;
+ {value, RequiredType} ->
+ case vat_1(GivenType, RequiredType) of
+ true -> ok;
+ false -> error({bad_arg_type, Reg, GivenType, RequiredType})
+ end;
+ none ->
+ ok
end.
-ms_in_y_regs(Id, #vst{current=#st{y=Ys0}}) ->
- Ys = gb_trees:to_list(Ys0),
- [{y,Y} || {Y,#ms{id=OtherId}} <- Ys, OtherId =:= Id].
+%% Checks whether the Given argument is compatible with the Required one. This
+%% is essentially a relaxed version of 'meet(Given, Req) =:= Given', where we
+%% accept that the Given value has the right type but not necessarily the exact
+%% same value; if {atom,gurka} is required, we'll consider {atom,[]} valid.
+%%
+%% This will catch all problems that could crash the emulator, like passing a
+%% 1-tuple when the callee expects a 3-tuple, but some value errors might slip
+%% through.
+vat_1(Same, Same) -> true;
+vat_1({atom,A}, {atom,B}) -> A =:= B orelse is_list(A) orelse is_list(B);
+vat_1({atom,A}, bool) -> is_boolean(A) orelse is_list(A);
+vat_1(bool, {atom,B}) -> is_boolean(B) orelse is_list(B);
+vat_1(cons, list) -> true;
+vat_1({float,A}, {float,B}) -> A =:= B orelse is_list(A) orelse is_list(B);
+vat_1({float,_}, number) -> true;
+vat_1({integer,A}, {integer,B}) -> A =:= B orelse is_list(A) orelse is_list(B);
+vat_1({integer,_}, number) -> true;
+vat_1(_, {literal,_}) -> false;
+vat_1({literal,_}=Lit, Required) -> vat_1(get_literal_type(Lit), Required);
+vat_1(nil, list) -> true;
+vat_1({tuple,SzA,EsA}, {tuple,SzB,EsB}) ->
+ if
+ is_list(SzB) ->
+ tuple_sz(SzA) >= tuple_sz(SzB) andalso vat_elements(EsA, EsB);
+ SzA =:= SzB ->
+ vat_elements(EsA, EsB);
+ SzA =/= SzB ->
+ false
+ end;
+vat_1(_, _) -> false.
-verify_call_match_context(Lbl, Ctx, #vst{ft=Ft}) ->
- case gb_trees:lookup(Lbl, Ft) of
- none ->
- error(no_bs_start_match2);
- {value,[{test,bs_start_match2,_,_,[Ctx,_],Ctx}|_]} ->
- ok;
- {value,[{test,bs_start_match2,_,_,_,_}=I|_]} ->
- error({unsuitable_bs_start_match2,I})
- end.
+vat_elements(EsA, EsB) ->
+ maps:fold(fun(Key, Req, Acc) ->
+ case EsA of
+ #{ Key := Given } -> Acc andalso vat_1(Given, Req);
+ #{} -> false
+ end
+ end, true, EsB).
-allocate(Zero, Stk, Heap, Live, #vst{current=#st{numy=none}}=Vst0) ->
+allocate(Tag, Stk, Heap, Live, #vst{current=#st{numy=none}=St}=Vst0) ->
verify_live(Live, Vst0),
- Vst = #vst{current=St} = prune_x_regs(Live, Vst0),
- Ys = init_regs(Stk, case Zero of
- true -> initialized;
- false -> uninitialized
- end),
- heap_alloc(Heap, Vst#vst{current=St#st{y=Ys,numy=Stk}});
+ Vst1 = Vst0#vst{current=St#st{numy=Stk}},
+ Vst2 = prune_x_regs(Live, Vst1),
+ Vst = init_stack(Tag, Stk - 1, Vst2),
+ heap_alloc(Heap, Vst);
allocate(_, _, _, _, #vst{current=#st{numy=Numy}}) ->
error({existing_stack_frame,{size,Numy}}).
deallocate(#vst{current=St}=Vst) ->
- Vst#vst{current=St#st{y=init_regs(0, initialized),numy=none}}.
+ Vst#vst{current=St#st{ys=#{},numy=none}}.
+
+init_stack(_Tag, -1, Vst) ->
+ Vst;
+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),
+ St#st{numy=To,ys=Ys};
+trim_stack(From, To, Top, St0) ->
+ Src = {y, From},
+ Dst = {y, To},
+
+ #st{ys=Ys0} = St0,
+ Ys = case Ys0 of
+ #{ Src := Ref } -> Ys0#{ Dst => Ref };
+ #{} -> error({invalid_shift,Src,Dst})
+ end,
+ St = St0#st{ys=Ys},
+
+ trim_stack(From + 1, To + 1, Top, St).
test_heap(Heap, Live, Vst0) ->
verify_live(Live, Vst0),
@@ -1025,13 +1290,43 @@ heap_alloc_2([{floats,Floats}|T], St0) ->
St = St0#st{hf=Floats},
heap_alloc_2(T, St);
heap_alloc_2([], St) -> St.
-
-prune_x_regs(Live, #vst{current=#st{x=Xs0}=St0}=Vst) when is_integer(Live) ->
- Xs1 = gb_trees:to_list(Xs0),
- Xs = [P || {R,_}=P <- Xs1, R < Live],
- St = St0#st{x=gb_trees:from_orddict(Xs)},
+
+prune_x_regs(Live, #vst{current=St0}=Vst) when is_integer(Live) ->
+ #st{fragile=Fragile0,xs=Xs0} = St0,
+ Fragile = cerl_sets:filter(fun({x,X}) ->
+ X < Live;
+ ({y,_}) ->
+ true
+ end, Fragile0),
+ Xs = maps:filter(fun({x,X}, _) ->
+ X < Live
+ end, Xs0),
+ St = St0#st{fragile=Fragile,xs=Xs},
Vst#vst{current=St}.
+%% All choices in a select_val list must be integers, floats, or atoms.
+%% All must be of the same type.
+assert_choices([{Tag,_},{f,_}|T]) ->
+ if
+ Tag =:= atom; Tag =:= float; Tag =:= integer ->
+ assert_choices_1(T, Tag);
+ true ->
+ error(bad_select_list)
+ end;
+assert_choices([]) -> ok.
+
+assert_choices_1([{Tag,_},{f,_}|T], Tag) ->
+ assert_choices_1(T, Tag);
+assert_choices_1([_,{f,_}|_], _Tag) ->
+ error(bad_select_list);
+assert_choices_1([], _Tag) -> ok.
+
+assert_arities([Arity,{f,_}|T]) when is_integer(Arity) ->
+ assert_arities(T);
+assert_arities([]) -> ok;
+assert_arities(_) -> error(bad_tuple_arity_list).
+
+
%%%
%%% Floating point checking.
%%%
@@ -1051,8 +1346,8 @@ prune_x_regs(Live, #vst{current=#st{x=Xs0}=St0}=Vst) when is_integer(Live) ->
%%% fmove Src {fr,_} %% Move INTO floating point register.
%%%
-float_op(Src, Dst, Vst0) ->
- foreach (fun(S) -> assert_freg_set(S, Vst0) end, Src),
+float_op(Ss, Dst, Vst0) ->
+ [assert_freg_set(S, Vst0) || S <- Ss],
assert_fls(cleared, Vst0),
Vst = set_fls(cleared, Vst0),
set_freg(Dst, Vst).
@@ -1070,8 +1365,7 @@ get_fls(#vst{current=#st{fls=Fls}}) when is_atom(Fls) -> Fls.
init_fregs() -> 0.
-set_freg({fr,Fr}=Freg, #vst{current=#st{f=Fregs0}=St}=Vst)
- when is_integer(Fr), 0 =< Fr ->
+set_freg({fr,Fr}=Freg, #vst{current=#st{f=Fregs0}=St}=Vst) ->
check_limit(Freg),
Bit = 1 bsl Fr,
if
@@ -1107,7 +1401,10 @@ assert_unique_map_keys([]) ->
assert_unique_map_keys([_]) ->
ok;
assert_unique_map_keys([_,_|_]=Ls) ->
- Vs = [get_literal(L) || L <- Ls],
+ Vs = [begin
+ assert_literal(L),
+ L
+ end || L <- Ls],
case length(Vs) =:= sets:size(sets:from_list(Vs)) of
true -> ok;
false -> error(keys_not_unique)
@@ -1117,6 +1414,8 @@ assert_unique_map_keys([_,_|_]=Ls) ->
%%% New binary matching instructions.
%%%
+bsm_match_state() ->
+ #ms{}.
bsm_match_state(Slots) ->
#ms{slots=Slots}.
@@ -1124,13 +1423,13 @@ bsm_validate_context(Reg, Vst) ->
_ = bsm_get_context(Reg, Vst),
ok.
-bsm_get_context({x,X}=Reg, #vst{current=#st{x=Xs}}=_Vst) when is_integer(X) ->
- case gb_trees:lookup(X, Xs) of
- {value,#ms{}=Ctx} -> Ctx;
- {value,{fragile,#ms{}=Ctx}} -> Ctx;
- _ -> error({no_bsm_context,Reg})
+bsm_get_context({Kind,_}=Reg, Vst) when Kind =:= x; Kind =:= y->
+ case get_raw_type(Reg, Vst) of
+ #ms{}=Ctx -> Ctx;
+ _ -> error({no_bsm_context,Reg})
end;
-bsm_get_context(Reg, _) -> error({bad_source,Reg}).
+bsm_get_context(Reg, _) ->
+ error({bad_source,Reg}).
bsm_save(Reg, {atom,start}, Vst) ->
%% Save point refering to where the match started.
@@ -1141,7 +1440,7 @@ bsm_save(Reg, SavePoint, Vst) ->
case bsm_get_context(Reg, Vst) of
#ms{valid=Bits,slots=Slots}=Ctxt0 when SavePoint < Slots ->
Ctx = Ctxt0#ms{valid=Bits bor (1 bsl SavePoint),slots=Slots},
- set_type_reg(Ctx, Reg, Vst);
+ override_type(Ctx, Reg, Vst);
_ -> error({illegal_save,SavePoint})
end.
@@ -1160,104 +1459,347 @@ bsm_restore(Reg, SavePoint, Vst) ->
_ -> error({illegal_restore,SavePoint,range})
end.
+select_val_branches(Fail, Src, Choices, Vst0) ->
+ Vst = svb_1(Choices, Src, Vst0),
+ kill_state(branch_state(Fail, Vst)).
+
+svb_1([Val,{f,L}|T], Src, Vst0) ->
+ Vst = complex_test(L,
+ fun(BranchVst) ->
+ update_eq_types(Src, Val, BranchVst)
+ end,
+ fun(FailVst) ->
+ update_ne_types(Src, Val, FailVst)
+ end, Vst0),
+ svb_1(T, Src, Vst);
+svb_1([], _, Vst) ->
+ Vst.
+
+select_arity_branches(Fail, List, Tuple, Vst0) ->
+ Type = get_term_type(Tuple, Vst0),
+ Vst = sab_1(List, Tuple, Type, Vst0),
+ kill_state(branch_state(Fail, Vst)).
+
+sab_1([Sz,{f,L}|T], Tuple, {tuple,[_],Es}=Type0, Vst0) ->
+ #vst{current=St0} = Vst0,
+ Vst1 = update_type(fun meet/2, {tuple,Sz,Es}, Tuple, Vst0),
+ Vst2 = branch_state(L, Vst1),
+ Vst = Vst2#vst{current=St0},
+
+ sab_1(T, Tuple, Type0, Vst);
+sab_1([Sz,{f,L}|T], Tuple, {tuple,Sz,_Es}=Type, Vst0) ->
+ %% The type is already correct. (This test is redundant.)
+ Vst = branch_state(L, Vst0),
+ sab_1(T, Tuple, Type, Vst);
+sab_1([_,{f,_}|T], Tuple, Type, Vst) ->
+ %% We already have an established different exact size for the tuple.
+ %% This label can't possibly be reached.
+ sab_1(T, Tuple, Type, Vst);
+sab_1([], _, _, #vst{}=Vst) ->
+ Vst.
+
+infer_types({Kind,_}=Reg, Vst) when Kind =:= x; Kind =:= y ->
+ infer_types(get_reg_vref(Reg, Vst), Vst);
+infer_types(#value_ref{}=Ref, #vst{current=#st{vs=Vs}}) ->
+ case Vs of
+ #{ Ref := Entry } -> infer_types_1(Entry);
+ #{} -> fun(_, S) -> S end
+ end;
+infer_types(_, #vst{}) ->
+ fun(_, S) -> S end.
+
+infer_types_1(#value{op={bif,'=:='},args=[LHS,RHS]}) ->
+ fun({atom,true}, S) ->
+ Infer = infer_types(RHS, S),
+ Infer(LHS, S);
+ (_, S) -> S
+ end;
+infer_types_1(#value{op={bif,element},args=[{integer,Index}=Key,Tuple]}) ->
+ fun(Val, S) ->
+ Type = get_term_type(Val, S),
+ update_type(fun meet/2,{tuple,[Index],#{ Key => Type }}, Tuple, S)
+ end;
+infer_types_1(#value{op={bif,is_atom},args=[Src]}) ->
+ infer_type_test_bif({atom,[]}, Src);
+infer_types_1(#value{op={bif,is_boolean},args=[Src]}) ->
+ infer_type_test_bif(bool, Src);
+infer_types_1(#value{op={bif,is_binary},args=[Src]}) ->
+ infer_type_test_bif(binary, Src);
+infer_types_1(#value{op={bif,is_bitstring},args=[Src]}) ->
+ infer_type_test_bif(binary, Src);
+infer_types_1(#value{op={bif,is_float},args=[Src]}) ->
+ infer_type_test_bif(float, Src);
+infer_types_1(#value{op={bif,is_integer},args=[Src]}) ->
+ infer_type_test_bif({integer,{}}, Src);
+infer_types_1(#value{op={bif,is_list},args=[Src]}) ->
+ infer_type_test_bif(list, Src);
+infer_types_1(#value{op={bif,is_map},args=[Src]}) ->
+ infer_type_test_bif(map, Src);
+infer_types_1(#value{op={bif,is_number},args=[Src]}) ->
+ infer_type_test_bif(number, Src);
+infer_types_1(#value{op={bif,is_tuple},args=[Src]}) ->
+ infer_type_test_bif({tuple,[0],#{}}, Src);
+infer_types_1(#value{op={bif,tuple_size}, args=[Tuple]}) ->
+ fun({integer,Arity}, S) ->
+ update_type(fun meet/2, {tuple,Arity,#{}}, Tuple, S);
+ (_, S) -> S
+ end;
+infer_types_1(_) ->
+ fun(_, S) -> S end.
+
+infer_type_test_bif(Type, Src) ->
+ fun({atom,true}, S) ->
+ update_type(fun meet/2, Type, Src, S);
+ (_, S) ->
+ S
+ end.
+
%%%
%%% Keeping track of types.
%%%
-set_type(Type, {x,_}=Reg, Vst) -> set_type_reg(Type, Reg, Vst);
-set_type(Type, {y,_}=Reg, Vst) -> set_type_y(Type, Reg, Vst);
-set_type(_, _, #vst{}=Vst) -> Vst.
-
-set_type_reg(Type, Src, Dst, Vst) ->
- case get_term_type_1(Src, Vst) of
- {fragile,_} ->
- set_type_reg(make_fragile(Type), Dst, Vst);
+%% Assigns Src to Dst and marks them as aliasing each other.
+assign({y,_}=Src, {y,_}=Dst, Vst) ->
+ %% The stack trimming optimization may generate a move from an initialized
+ %% but unassigned Y register to another Y register.
+ case get_raw_type(Src, Vst) of
+ initialized -> create_tag(initialized, init, [], Dst, Vst);
+ _ -> assign_1(Src, Dst, Vst)
+ end;
+assign({Kind,_}=Src, Dst, Vst) when Kind =:= x; Kind =:= y ->
+ assign_1(Src, Dst, Vst);
+assign(Literal, Dst, Vst) ->
+ Type = get_literal_type(Literal),
+ create_term(Type, move, [Literal], Dst, Vst).
+
+%% Creates a special tag value that isn't a regular term, such as 'initialized'
+%% or 'catchtag'
+create_tag(Tag, _Op, _Ss, {y,_}=Dst, #vst{current=#st{ys=Ys0}=St0}=Vst) ->
+ case maps:get(Dst, Ys0, uninitialized) of
+ {catchtag,_}=Prev ->
+ error(Prev);
+ {trytag,_}=Prev ->
+ error(Prev);
_ ->
- set_type_reg(Type, Dst, Vst)
+ check_try_catch_tags(Tag, Dst, Vst),
+ Ys = Ys0#{ Dst => Tag },
+ St = St0#st{ys=Ys},
+ remove_fragility(Dst, Vst#vst{current=St})
+ end;
+create_tag(_Tag, _Op, _Ss, Dst, _Vst) ->
+ error({invalid_tag_register, Dst}).
+
+%% Wipes a special tag, leaving the register initialized but empty.
+kill_tag({y,_}=Reg, #vst{current=#st{ys=Ys0}=St0}=Vst) ->
+ _ = get_tag_type(Reg, Vst), %Assertion.
+ Ys = Ys0#{ Reg => initialized },
+ Vst#vst{current=St0#st{ys=Ys}}.
+
+%% Creates a completely new term with the given type.
+create_term(Type, Op, Ss0, Dst, Vst0) ->
+ create_term(Type, Op, Ss0, Dst, Vst0, Vst0).
+
+%% As create_term/4, but uses the incoming Vst for argument resolution in
+%% case x-regs have been pruned and the sources can no longer be found.
+create_term(Type, Op, Ss0, Dst, Vst0, OrigVst) ->
+ {Ref, Vst1} = new_value(Type, Op, resolve_args(Ss0, OrigVst), Vst0),
+ Vst = remove_fragility(Dst, Vst1),
+ set_reg_vref(Ref, Dst, Vst).
+
+%% Extracts a term from Ss, propagating fragility.
+extract_term(Type, Op, Ss0, Dst, Vst0) ->
+ extract_term(Type, Op, Ss0, Dst, Vst0, Vst0).
+
+%% As extract_term/4, but uses the incoming Vst for argument resolution in
+%% case x-regs have been pruned and the sources can no longer be found.
+extract_term(Type, Op, Ss0, Dst, Vst0, OrigVst) ->
+ {Ref, Vst1} = new_value(Type, Op, resolve_args(Ss0, OrigVst), Vst0),
+ Vst = propagate_fragility(Dst, Ss0, Vst1),
+ set_reg_vref(Ref, Dst, Vst).
+
+%% Translates instruction arguments into the argument() type, decoupling them
+%% from their registers, allowing us to infer their types after they've been
+%% clobbered or moved.
+resolve_args([{Kind,_}=Src | Args], Vst) when Kind =:= x; Kind =:= y ->
+ [get_reg_vref(Src, Vst) | resolve_args(Args, Vst)];
+resolve_args([Lit | Args], Vst) ->
+ assert_literal(Lit),
+ [Lit | resolve_args(Args, Vst)];
+resolve_args([], _Vst) ->
+ [].
+
+%% Helper functions for tests that alter state on both the success and fail
+%% branches, keeping the states from tainting each other.
+complex_test(Fail, FailFun, SuccFun, Vst0) ->
+ #vst{current=St0} = Vst0,
+ Vst1 = FailFun(Vst0),
+ Vst2 = branch_state(Fail, Vst1),
+ Vst = Vst2#vst{current=St0},
+ SuccFun(Vst).
+
+%% Helper function for simple "is_type" tests.
+type_test(Fail, Type, Reg, Vst) ->
+ assert_term(Reg, Vst),
+ complex_test(Fail,
+ fun(FailVst) ->
+ update_type(fun subtract/2, Type, Reg, FailVst)
+ end,
+ fun(SuccVst) ->
+ update_type(fun meet/2, Type, Reg, SuccVst)
+ end, Vst).
+
+%% Overrides the type of Reg. This is ugly but a necessity for certain
+%% destructive operations.
+override_type(Type, Reg, Vst) ->
+ update_type(fun(_, T) -> T end, Type, Reg, Vst).
+
+%% This is used when linear code finds out more and more information about a
+%% type, so that the type gets more specialized.
+update_type(Merge, Type0, #value_ref{}=Ref, Vst) ->
+ %% If the old type can't be merged with the new one, the type information
+ %% is inconsistent and we know that some instructions will never be
+ %% executed at run-time. For example:
+ %%
+ %% {test,is_list,Fail,[Reg]}.
+ %% {test,is_tuple,Fail,[Reg]}.
+ %% {test,test_arity,Fail,[Reg,5]}.
+ %%
+ %% Note that the test_arity instruction can never be reached, so we use the
+ %% new type instead of 'none'.
+ Type = case Merge(get_raw_type(Ref, Vst), Type0) of
+ none -> Type0;
+ T -> T
+ end,
+ set_type(Type, Ref, Vst);
+update_type(Merge, Type, {Kind,_}=Reg, Vst) when Kind =:= x; Kind =:= y ->
+ update_type(Merge, Type, get_reg_vref(Reg, Vst), Vst);
+update_type(_Merge, _Type, Literal, Vst) ->
+ assert_literal(Literal),
+ Vst.
+
+update_ne_types(LHS, RHS, Vst) ->
+ update_type(fun subtract/2, get_term_type(RHS, Vst), LHS, Vst).
+
+update_eq_types(LHS, RHS, Vst0) ->
+ Infer = infer_types(LHS, Vst0),
+ Vst1 = Infer(RHS, Vst0),
+
+ T1 = get_term_type(LHS, Vst1),
+ T2 = get_term_type(RHS, Vst1),
+
+ Vst = update_type(fun meet/2, T2, LHS, Vst1),
+ update_type(fun meet/2, T1, RHS, Vst).
+
+%% Helper functions for the above.
+
+assign_1(Src, Dst, Vst0) ->
+ assert_movable(Src, Vst0),
+ Vst = propagate_fragility(Dst, [Src], Vst0),
+ set_reg_vref(get_reg_vref(Src, Vst), Dst, Vst).
+
+set_reg_vref(Ref, {x,_}=Dst, Vst) ->
+ check_limit(Dst),
+ #vst{current=#st{xs=Xs0}=St0} = Vst,
+ St = St0#st{xs=Xs0#{ Dst => Ref }},
+ Vst#vst{current=St};
+set_reg_vref(Ref, {y,_}=Dst, #vst{current=#st{ys=Ys0}=St0} = Vst) ->
+ check_limit(Dst),
+ case Ys0 of
+ #{ Dst := {catchtag,_}=Tag } ->
+ error(Tag);
+ #{ Dst := {trytag,_}=Tag } ->
+ error(Tag);
+ #{ Dst := _ } ->
+ St = St0#st{ys=Ys0#{ Dst => Ref }},
+ Vst#vst{current=St};
+ #{} ->
+ %% Storing into a non-existent Y register means that we haven't set
+ %% up a (sufficiently large) stack.
+ error({invalid_store, Dst})
end.
-set_type_reg(Type, {x,_}=Reg, Vst) ->
- set_type_x(Type, Reg, Vst);
-set_type_reg(Type, Reg, Vst) ->
- set_type_y(Type, Reg, Vst).
-
-set_type_x(Type, {x,X}=Reg, #vst{current=#st{x=Xs0}=St}=Vst)
- when is_integer(X), 0 =< X ->
- check_limit(Reg),
- Xs = case gb_trees:lookup(X, Xs0) of
- none ->
- gb_trees:insert(X, Type, Xs0);
- {value,{fragile,_}} ->
- gb_trees:update(X, make_fragile(Type), Xs0);
- {value,_} ->
- gb_trees:update(X, Type, Xs0)
- end,
- Vst#vst{current=St#st{x=Xs}};
-set_type_x(Type, Reg, #vst{}) ->
- error({invalid_store,Reg,Type}).
-
-set_type_y(Type, {y,Y}=Reg, #vst{current=#st{y=Ys0}=St}=Vst)
- when is_integer(Y), 0 =< Y ->
- check_limit(Reg),
- Ys = case gb_trees:lookup(Y, Ys0) of
- none ->
- error({invalid_store,Reg,Type});
- {value,{catchtag,_}=Tag} ->
- error(Tag);
- {value,{trytag,_}=Tag} ->
- error(Tag);
- {value,_} ->
- gb_trees:update(Y, Type, Ys0)
- end,
- check_try_catch_tags(Type, Y, Ys0),
- Vst#vst{current=St#st{y=Ys}};
-set_type_y(Type, Reg, #vst{}) -> error({invalid_store,Reg,Type}).
-
-make_fragile({fragile,_}=Type) -> Type;
-make_fragile(Type) -> {fragile,Type}.
-
-set_catch_end({y,Y}, #vst{current=#st{y=Ys0}=St}=Vst) ->
- Ys = gb_trees:update(Y, initialized, Ys0),
- Vst#vst{current=St#st{y=Ys}}.
-
-check_try_catch_tags(Type, LastY, Ys) ->
- case is_try_catch_tag(Type) of
- false ->
- ok;
- true ->
- %% Every catch or try/catch must use a lower Y register
- %% number than any enclosing catch or try/catch. That will
- %% ensure that when the stack is scanned when an
- %% exception occurs, the innermost try/catch tag is found
- %% first.
- Bad = [{{y,Y},Tag} || {Y,Tag} <- gb_trees:to_list(Ys),
- Y < LastY, is_try_catch_tag(Tag)],
- case Bad of
- [] ->
- ok;
- [_|_] ->
- error({bad_try_catch_nesting,{y,LastY},Bad})
- end
+get_reg_vref({x,_}=Src, #vst{current=#st{xs=Xs}}) ->
+ check_limit(Src),
+ case Xs of
+ #{ Src := #value_ref{}=Ref } ->
+ Ref;
+ #{} ->
+ error({uninitialized_reg, Src})
+ end;
+get_reg_vref({y,_}=Src, #vst{current=#st{ys=Ys}}) ->
+ check_limit(Src),
+ case Ys of
+ #{ Src := #value_ref{}=Ref } ->
+ Ref;
+ #{ Src := initialized } ->
+ error({unassigned, Src});
+ #{ Src := Tag } when Tag =/= uninitialized ->
+ error(Tag);
+ #{} ->
+ error({uninitialized_reg, Src})
end.
-is_try_catch_tag({catchtag,_}) -> true;
-is_try_catch_tag({trytag,_}) -> true;
-is_try_catch_tag(_) -> false.
+set_type(Type, #value_ref{}=Ref, #vst{current=#st{vs=Vs0}=St}=Vst) ->
+ case Vs0 of
+ #{ Ref := #value{}=Entry } ->
+ Vs = Vs0#{ Ref => Entry#value{type=Type} },
+ Vst#vst{current=St#st{vs=Vs}};
+ #{} ->
+ %% Dead references may happen during type inference and are not an
+ %% error in and of themselves. If a problem were to arise from this
+ %% it'll explode elsewhere.
+ Vst
+ end.
-is_reg_defined({x,_}=Reg, Vst) -> is_type_defined_x(Reg, Vst);
-is_reg_defined({y,_}=Reg, Vst) -> is_type_defined_y(Reg, Vst);
-is_reg_defined(V, #vst{}) -> error({not_a_register, V}).
+new_value(Type, Op, Ss, #vst{current=#st{vs=Vs0}=St,ref_ctr=Counter}=Vst) ->
+ Ref = #value_ref{id=Counter},
+ Vs = Vs0#{ Ref => #value{op=Op,args=Ss,type=Type} },
+
+ {Ref, Vst#vst{current=St#st{vs=Vs},ref_ctr=Counter+1}}.
-is_type_defined_x({x,X}, #vst{current=#st{x=Xs}}) ->
- gb_trees:is_defined(X,Xs).
+kill_catch_tag(Reg, #vst{current=#st{ct=[Fail|Fails]}=St}=Vst0) ->
+ Vst = Vst0#vst{current=St#st{ct=Fails,fls=undefined}},
+ {_, Fail} = get_tag_type(Reg, Vst), %Assertion.
+ kill_tag(Reg, Vst).
-is_type_defined_y({y,Y}, #vst{current=#st{y=Ys}}) ->
- gb_trees:is_defined(Y,Ys).
+check_try_catch_tags(Type, {y,N}=Reg, Vst) ->
+ %% Every catch or try/catch must use a lower Y register number than any
+ %% enclosing catch or try/catch. That will ensure that when the stack is
+ %% scanned when an exception occurs, the innermost try/catch tag is found
+ %% first.
+ case is_try_catch_tag(Type) of
+ true ->
+ case collect_try_catch_tags(N - 1, Vst, []) of
+ [_|_]=Bad -> error({bad_try_catch_nesting, Reg, Bad});
+ [] -> ok
+ end;
+ false ->
+ ok
+ end.
+
+is_reg_defined({x,_}=Reg, #vst{current=#st{xs=Xs}}) -> is_map_key(Reg, Xs);
+is_reg_defined({y,_}=Reg, #vst{current=#st{ys=Ys}}) -> is_map_key(Reg, Ys);
+is_reg_defined(V, #vst{}) -> error({not_a_register, V}).
assert_term(Src, Vst) ->
- get_term_type(Src, Vst),
+ _ = get_term_type(Src, Vst),
+ ok.
+
+assert_movable(Src, Vst) ->
+ _ = get_move_term_type(Src, Vst),
ok.
+assert_literal(nil) -> ok;
+assert_literal({atom,A}) when is_atom(A) -> ok;
+assert_literal({float,F}) when is_float(F) -> ok;
+assert_literal({integer,I}) when is_integer(I) -> ok;
+assert_literal({literal,_L}) -> ok;
+assert_literal(T) -> error({literal_required,T}).
+
+assert_not_literal({x,_}) -> ok;
+assert_not_literal({y,_}) -> ok;
+assert_not_literal(Literal) -> error({literal_not_allowed,Literal}).
+
%% The possible types.
%%
%% First non-term types:
@@ -1276,10 +1818,10 @@ assert_term(Src, Vst) ->
%% used by the catch instructions; NOT safe to use in other
%% instructions.
%%
-%% exception Can only be used as a type returned by return_type/2
-%% (which gives the type of the value returned by a BIF).
-%% Thus 'exception' is never stored as type descriptor
-%% for a register.
+%% exception Can only be used as a type returned by
+%% call_return_type/2 (which gives the type of the value
+%% returned by a call). Thus 'exception' is never stored
+%% as type descriptor for a register.
%%
%% #ms{} A match context for bit syntax matching. We do allow
%% it to moved/to from stack, but otherwise it must only
@@ -1290,17 +1832,22 @@ assert_term(Src, Vst) ->
%%
%% term Any valid Erlang (but not of the special types above).
%%
+%% binary Binary or bitstring.
+%%
%% bool The atom 'true' or the atom 'false'.
%%
%% cons Cons cell: [_|_]
%%
%% nil Empty list: []
%%
-%% {tuple,[Sz]} Tuple. An element has been accessed using
-%% element/2 or setelement/3 so that it is known that
-%% the type is a tuple of size at least Sz.
+%% list List: [] or [_|_]
+%%
+%% {tuple,[Sz],Es} Tuple. An element has been accessed using
+%% element/2 or setelement/3 so that it is known that
+%% the type is a tuple of size at least Sz. Es is a map
+%% containing known types by tuple index.
%%
-%% {tuple,Sz} Tuple. A test_arity instruction has been seen
+%% {tuple,Sz,Es} Tuple. A test_arity instruction has been seen
%% so that it is known that the size is exactly Sz.
%%
%% {atom,[]} Atom.
@@ -1316,35 +1863,213 @@ assert_term(Src, Vst) ->
%%
%% map Map.
%%
+%% none A conflict in types. There will be an exception at runtime.
%%
-%%
-%% FRAGILITY
-%% ---------
-%%
-%% The loop_rec/2 instruction may return a reference to a term that is
-%% not part of the root set. That term or any part of it must not be
-%% included in a garbage collection. Therefore, the term (or any part
-%% of it) must not be stored in an Y register.
-%%
-%% Such terms are wrapped in a {fragile,Type} tuple, where Type is one
-%% of the types described above.
-assert_type(WantedType, Term, Vst) ->
- case get_term_type(Term, Vst) of
- {fragile,Type} ->
- assert_type(WantedType, Type);
- Type ->
- assert_type(WantedType, Type)
+%% join(Type1, Type2) -> Type
+%% Return the most specific type possible.
+join(Same, Same) ->
+ Same;
+join(none, Other) ->
+ Other;
+join(Other, none) ->
+ Other;
+join({literal,_}=T1, T2) ->
+ join_literal(T1, T2);
+join(T1, {literal,_}=T2) ->
+ join_literal(T2, T1);
+join({tuple,Size,EsA}, {tuple,Size,EsB}) ->
+ Es = join_tuple_elements(tuple_sz(Size), EsA, EsB),
+ {tuple, Size, Es};
+join({tuple,A,EsA}, {tuple,B,EsB}) ->
+ Size = min(tuple_sz(A), tuple_sz(B)),
+ Es = join_tuple_elements(Size, EsA, EsB),
+ {tuple, [Size], Es};
+join({Type,A}, {Type,B})
+ when Type =:= atom; Type =:= integer; Type =:= float ->
+ if A =:= B -> {Type,A};
+ true -> {Type,[]}
+ end;
+join({Type,_}, number)
+ when Type =:= integer; Type =:= float ->
+ number;
+join(number, {Type,_})
+ when Type =:= integer; Type =:= float ->
+ number;
+join({integer,_}, {float,_}) ->
+ number;
+join({float,_}, {integer,_}) ->
+ number;
+join(bool, {atom,A}) ->
+ join_bool(A);
+join({atom,A}, bool) ->
+ join_bool(A);
+join({atom,A}, {atom,B}) when is_boolean(A), is_boolean(B) ->
+ bool;
+join({atom,_}, {atom,_}) ->
+ {atom,[]};
+join(#ms{id=Id1,valid=B1,slots=Slots1},
+ #ms{id=Id2,valid=B2,slots=Slots2}) ->
+ Id = if
+ Id1 =:= Id2 -> Id1;
+ true -> make_ref()
+ end,
+ #ms{id=Id,valid=B1 band B2,slots=min(Slots1, Slots2)};
+join(T1, T2) when T1 =/= T2 ->
+ %% We've exhaused all other options, so the type must either be a list or
+ %% a 'term'.
+ join_list(T1, T2).
+
+join_tuple_elements(Limit, EsA, EsB) ->
+ Es0 = join_elements(EsA, EsB),
+ maps:filter(fun({integer,Index}, _Type) -> Index =< Limit end, Es0).
+
+join_elements(Es1, Es2) ->
+ Keys = if
+ map_size(Es1) =< map_size(Es2) -> maps:keys(Es1);
+ map_size(Es1) > map_size(Es2) -> maps:keys(Es2)
+ end,
+ join_elements_1(Keys, Es1, Es2, #{}).
+
+join_elements_1([Key | Keys], Es1, Es2, Acc0) ->
+ Type = case {Es1, Es2} of
+ {#{ Key := Same }, #{ Key := Same }} -> Same;
+ {#{ Key := Type1 }, #{ Key := Type2 }} -> join(Type1, Type2);
+ {#{}, #{}} -> term
+ end,
+ Acc = set_element_type(Key, Type, Acc0),
+ join_elements_1(Keys, Es1, Es2, Acc);
+join_elements_1([], _Es1, _Es2, Acc) ->
+ Acc.
+
+%% Joins types of literals; note that the left argument must either be a
+%% literal or exactly equal to the second argument.
+join_literal(Same, Same) ->
+ Same;
+join_literal({literal,_}=Lit, T) ->
+ join_literal(T, get_literal_type(Lit));
+join_literal(T1, T2) ->
+ %% We're done extracting the types, try merging them again.
+ join(T1, T2).
+
+join_list(nil, cons) -> list;
+join_list(nil, list) -> list;
+join_list(cons, list) -> list;
+join_list(T, nil) -> join_list(nil, T);
+join_list(T, cons) -> join_list(cons, T);
+join_list(_, _) ->
+ %% Not a list, so it must be a term.
+ term.
+
+join_bool([]) -> {atom,[]};
+join_bool(true) -> bool;
+join_bool(false) -> bool;
+join_bool(_) -> {atom,[]}.
+
+%% meet(Type1, Type2) -> Type
+%% Return the meet of two types. The meet is a more specific type.
+%% It will be 'none' if the types are in conflict.
+
+meet(Same, Same) ->
+ Same;
+meet(term, Other) ->
+ Other;
+meet(Other, term) ->
+ Other;
+meet(#ms{}, binary) ->
+ #ms{};
+meet(binary, #ms{}) ->
+ #ms{};
+meet({literal,_}, {literal,_}) ->
+ none;
+meet(T1, {literal,_}=T2) ->
+ meet(T2, T1);
+meet({literal,_}=T1, T2) ->
+ case meet(get_literal_type(T1), T2) of
+ none -> none;
+ _ -> T1
+ end;
+meet(T1, T2) ->
+ case {erlang:min(T1, T2),erlang:max(T1, T2)} of
+ {{atom,_}=A,{atom,[]}} -> A;
+ {bool,{atom,B}=Atom} when is_boolean(B) -> Atom;
+ {bool,{atom,[]}} -> bool;
+ {cons,list} -> cons;
+ {{float,_}=T,{float,[]}} -> T;
+ {{integer,_}=T,{integer,[]}} -> T;
+ {list,nil} -> nil;
+ {number,{integer,_}=T} -> T;
+ {number,{float,_}=T} -> T;
+ {{tuple,Size1,Es1},{tuple,Size2,Es2}} ->
+ Es = meet_elements(Es1, Es2),
+ case {Size1,Size2,Es} of
+ {_, _, none} ->
+ none;
+ {[Sz1],[Sz2],_} ->
+ Sz = erlang:max(Sz1, Sz2),
+ assert_tuple_elements(Sz, Es),
+ {tuple,[Sz],Es};
+ {Sz1,[Sz2],_} when Sz2 =< Sz1 ->
+ assert_tuple_elements(Sz1, Es),
+ {tuple,Sz1,Es};
+ {Sz,Sz,_} ->
+ assert_tuple_elements(Sz, Es),
+ {tuple,Sz,Es};
+ {_,_,_} ->
+ none
+ end;
+ {_,_} -> none
end.
+meet_elements(Es1, Es2) ->
+ Keys = maps:keys(Es1) ++ maps:keys(Es2),
+ meet_elements_1(Keys, Es1, Es2, #{}).
+
+meet_elements_1([Key | Keys], Es1, Es2, Acc) ->
+ case {Es1, Es2} of
+ {#{ Key := Type1 }, #{ Key := Type2 }} ->
+ case meet(Type1, Type2) of
+ none -> none;
+ Type -> meet_elements_1(Keys, Es1, Es2, Acc#{ Key => Type })
+ end;
+ {#{ Key := Type1 }, _} ->
+ meet_elements_1(Keys, Es1, Es2, Acc#{ Key => Type1 });
+ {_, #{ Key := Type2 }} ->
+ meet_elements_1(Keys, Es1, Es2, Acc#{ Key => Type2 })
+ end;
+meet_elements_1([], _Es1, _Es2, Acc) ->
+ Acc.
+
+%% No tuple elements may have an index above the known size.
+assert_tuple_elements(Limit, Es) ->
+ true = maps:fold(fun({integer,Index}, _T, true) ->
+ Index =< Limit
+ end, true, Es). %Assertion.
+
+%% subtract(Type1, Type2) -> Type
+%% Subtract Type2 from Type2. Example:
+%% subtract(list, nil) -> cons
+
+subtract(list, nil) -> cons;
+subtract(list, cons) -> nil;
+subtract(number, {integer,[]}) -> {float,[]};
+subtract(number, {float,[]}) -> {integer,[]};
+subtract(bool, {atom,false}) -> {atom, true};
+subtract(bool, {atom,true}) -> {atom, false};
+subtract(Type, _) -> Type.
+
+assert_type(WantedType, Term, Vst) ->
+ Type = get_term_type(Term, Vst),
+ assert_type(WantedType, Type).
+
assert_type(Correct, Correct) -> ok;
assert_type(float, {float,_}) -> ok;
-assert_type(tuple, {tuple,_}) -> ok;
+assert_type(tuple, {tuple,_,_}) -> ok;
assert_type(tuple, {literal,Tuple}) when is_tuple(Tuple) -> ok;
-assert_type({tuple_element,I}, {tuple,[Sz]})
+assert_type({tuple_element,I}, {tuple,[Sz],_})
when 1 =< I, I =< Sz ->
ok;
-assert_type({tuple_element,I}, {tuple,Sz})
+assert_type({tuple_element,I}, {tuple,Sz,_})
when is_integer(Sz), 1 =< I, I =< Sz ->
ok;
assert_type({tuple_element,I}, {literal,Lit}) when I =< tuple_size(Lit) ->
@@ -1354,53 +2079,32 @@ assert_type(cons, {literal,[_|_]}) ->
assert_type(Needed, Actual) ->
error({bad_type,{needed,Needed},{actual,Actual}}).
-%% upgrade_tuple_type(NewTupleType, OldType) -> TupleType.
-%% upgrade_tuple_type/2 is used when linear code finds out more and
-%% more information about a tuple type, so that the type gets more
-%% specialized. If OldType is not a tuple type, the type information
-%% is inconsistent, and we know that some instructions will never
-%% be executed at run-time.
-
-upgrade_tuple_type(NewType, {fragile,OldType}) ->
- make_fragile(upgrade_tuple_type_1(NewType, OldType));
-upgrade_tuple_type(NewType, OldType) ->
- upgrade_tuple_type_1(NewType, OldType).
-
-upgrade_tuple_type_1({tuple,[Sz]}, {tuple,[OldSz]}=T) when Sz < OldSz ->
- %% The old type has a higher value for the least tuple size.
- T;
-upgrade_tuple_type_1({tuple,[Sz]}, {tuple,OldSz}=T)
- when is_integer(Sz), is_integer(OldSz), Sz =< OldSz ->
- %% The old size is exact, and the new size is smaller than the old size.
- T;
-upgrade_tuple_type_1({tuple,_}=T, _) ->
- %% The new type information is exact or has a higher value for
- %% the least tuple size.
- %% Note that inconsistencies are also handled in this
- %% clause, e.g. if the old type was an integer or a tuple accessed
- %% outside its size; inconsistences will generally cause an exception
- %% at run-time but are safe from our point of view.
- T.
+get_element_type(Key, Src, Vst) ->
+ get_element_type_1(Key, get_term_type(Src, Vst)).
+
+get_element_type_1({integer,Index}=Key, {tuple,Sz,Es}) ->
+ case Es of
+ #{ Key := Type } -> Type;
+ #{} when Index =< Sz -> term;
+ #{} -> none
+ end;
+get_element_type_1(_Index, _Type) ->
+ term.
+
+set_element_type(_Key, none, Es) ->
+ Es;
+set_element_type(Key, term, Es) ->
+ maps:remove(Key, Es);
+set_element_type(Key, Type, Es) ->
+ Es#{ Key => Type }.
get_tuple_size({integer,[]}) -> 0;
get_tuple_size({integer,Sz}) -> Sz;
get_tuple_size(_) -> 0.
validate_src(Ss, Vst) when is_list(Ss) ->
- foreach(fun(S) -> get_term_type(S, Vst) end, Ss).
-
-%% get_move_term_type(Src, ValidatorState) -> Type
-%% Get the type of the source Src. The returned type Type will be
-%% a standard Erlang type (no catch/try tags). Match contexts are OK.
-
-get_move_term_type(Src, Vst) ->
- case get_term_type_1(Src, Vst) of
- initialized -> error({unassigned,Src});
- {catchtag,_} -> error({catchtag,Src});
- {trytag,_} -> error({trytag,Src});
- tuple_in_progress -> error({tuple_in_progress,Src});
- Type -> Type
- end.
+ [assert_term(S, Vst) || S <- Ss],
+ ok.
%% get_term_type(Src, ValidatorState) -> Type
%% Get the type of the source Src. The returned type Type will be
@@ -1408,51 +2112,85 @@ get_move_term_type(Src, Vst) ->
get_term_type(Src, Vst) ->
case get_move_term_type(Src, Vst) of
- #ms{} -> error({match_context,Src});
- Type -> Type
+ #ms{} -> error({match_context,Src});
+ Type -> Type
end.
-%% get_special_y_type(Src, ValidatorState) -> Type
-%% Return the type for the Y register without doing any validity checks.
-
-get_special_y_type({y,_}=Reg, Vst) -> get_term_type_1(Reg, Vst);
-get_special_y_type(Src, _) -> error({source_not_y_reg,Src}).
-
-get_term_type_1(nil=T, _) -> T;
-get_term_type_1({atom,A}=T, _) when is_atom(A) -> T;
-get_term_type_1({float,F}=T, _) when is_float(F) -> T;
-get_term_type_1({integer,I}=T, _) when is_integer(I) -> T;
-get_term_type_1({literal,Map}, _) when is_map(Map) -> map;
-get_term_type_1({literal,_}=T, _) -> T;
-get_term_type_1({x,X}=Reg, #vst{current=#st{x=Xs}}) when is_integer(X) ->
- case gb_trees:lookup(X, Xs) of
- {value,Type} -> Type;
- none -> error({uninitialized_reg,Reg})
- end;
-get_term_type_1({y,Y}=Reg, #vst{current=#st{y=Ys}}) when is_integer(Y) ->
- case gb_trees:lookup(Y, Ys) of
- none -> error({uninitialized_reg,Reg});
- {value,uninitialized} -> error({uninitialized_reg,Reg});
- {value,Type} -> Type
- end;
-get_term_type_1(Src, _) -> error({bad_source,Src}).
-
+%% get_move_term_type(Src, ValidatorState) -> Type
+%% Get the type of the source Src. The returned type Type will be
+%% a standard Erlang type (no catch/try tags). Match contexts are OK.
-%% get_literal(Src) -> literal_value().
-get_literal(nil) -> [];
-get_literal({atom,A}) when is_atom(A) -> A;
-get_literal({float,F}) when is_float(F) -> F;
-get_literal({integer,I}) when is_integer(I) -> I;
-get_literal({literal,L}) -> L;
-get_literal(T) -> error({not_literal,T}).
+get_move_term_type(Src, Vst) ->
+ case get_raw_type(Src, Vst) of
+ initialized -> error({unassigned,Src});
+ uninitialized -> error({uninitialized_reg,Src});
+ {catchtag,_} -> error({catchtag,Src});
+ {trytag,_} -> error({trytag,Src});
+ tuple_in_progress -> error({tuple_in_progress,Src});
+ Type -> Type
+ end.
+%% get_tag_type(Src, ValidatorState) -> Type
+%% Return the tag type of a Y register, erroring out if it contains a term.
-branch_arities([], _, #vst{}=Vst) -> Vst;
-branch_arities([Sz,{f,L}|T], Tuple, #vst{current=St}=Vst0)
- when is_integer(Sz) ->
- Vst1 = set_type_reg({tuple,Sz}, Tuple, Vst0),
- Vst = branch_state(L, Vst1),
- branch_arities(T, Tuple, Vst#vst{current=St}).
+get_tag_type({y,_}=Src, Vst) ->
+ case get_raw_type(Src, Vst) of
+ {catchtag, _}=Tag -> Tag;
+ {trytag, _}=Tag -> Tag;
+ uninitialized=Tag -> Tag;
+ initialized=Tag -> Tag;
+ Other -> error({invalid_tag,Src,Other})
+ end;
+get_tag_type(Src, _) ->
+ error({invalid_tag_register,Src}).
+
+%% get_raw_type(Src, ValidatorState) -> Type
+%% Return the type of a register without doing any validity checks.
+get_raw_type({x,X}=Src, #vst{current=#st{xs=Xs}}=Vst) when is_integer(X) ->
+ check_limit(Src),
+ case Xs of
+ #{ Src := #value_ref{}=Ref } -> get_raw_type(Ref, Vst);
+ #{} -> uninitialized
+ end;
+get_raw_type({y,Y}=Src, #vst{current=#st{ys=Ys}}=Vst) when is_integer(Y) ->
+ check_limit(Src),
+ case Ys of
+ #{ Src := #value_ref{}=Ref } -> get_raw_type(Ref, Vst);
+ #{ Src := Tag } -> Tag;
+ #{} -> uninitialized
+ end;
+get_raw_type(#value_ref{}=Ref, #vst{current=#st{vs=Vs}}) ->
+ case Vs of
+ #{ Ref := #value{type=Type} } -> Type;
+ #{} -> none
+ end;
+get_raw_type(Src, #vst{}) ->
+ get_literal_type(Src).
+
+get_literal_type(nil=T) -> T;
+get_literal_type({atom,A}=T) when is_atom(A) -> T;
+get_literal_type({float,F}=T) when is_float(F) -> T;
+get_literal_type({integer,I}=T) when is_integer(I) -> T;
+get_literal_type({literal,[_|_]}) -> cons;
+get_literal_type({literal,Bitstring}) when is_bitstring(Bitstring) -> binary;
+get_literal_type({literal,Map}) when is_map(Map) -> map;
+get_literal_type({literal,Tuple}) when is_tuple(Tuple) -> glt_1(Tuple);
+get_literal_type({literal,_}) -> term;
+get_literal_type(T) -> error({not_literal,T}).
+
+glt_1([]) -> nil;
+glt_1(A) when is_atom(A) -> {atom, A};
+glt_1(F) when is_float(F) -> {float, F};
+glt_1(I) when is_integer(I) -> {integer, I};
+glt_1(T) when is_tuple(T) ->
+ {Es,_} = foldl(fun(Val, {Es0, Index}) ->
+ Type = glt_1(Val),
+ Es = set_element_type({integer,Index}, Type, Es0),
+ {Es, Index + 1}
+ end, {#{}, 1}, tuple_to_list(T)),
+ {tuple, tuple_size(T), Es};
+glt_1(L) ->
+ {literal, L}.
branch_state(0, #vst{}=Vst) ->
%% If the instruction fails, the stack may be scanned
@@ -1460,35 +2198,134 @@ branch_state(0, #vst{}=Vst) ->
%% must be initialized at this point.
verify_y_init(Vst),
Vst;
-branch_state(L, #vst{current=St,branched=B}=Vst) ->
- Vst#vst{
- branched=case gb_trees:is_defined(L, B) of
- false ->
- gb_trees:insert(L, St, B);
- true ->
- MergedSt = merge_states(L, St, B),
- gb_trees:update(L, MergedSt, B)
- end}.
-
-%% merge_states/3 is used when there are more than one way to arrive
-%% at this point, and the type states for the different paths has
-%% to be merged. The type states are downgraded to the least common
-%% subset for the subsequent code.
-
-merge_states(L, St, Branched) when L =/= 0 ->
+branch_state(L, #vst{current=St,branched=B,ref_ctr=Counter0}=Vst) ->
+ case gb_trees:is_defined(L, B) of
+ true ->
+ {MergedSt, Counter} = merge_states(L, St, B, Counter0),
+ Branched = gb_trees:update(L, MergedSt, B),
+ Vst#vst{branched=Branched,ref_ctr=Counter};
+ false ->
+ Vst#vst{branched=gb_trees:insert(L, St, B)}
+ end.
+
+%% merge_states/3 is used when there's more than one way to arrive at a
+%% certain point, requiring the states to be merged down to the least
+%% common subset for the subsequent code.
+
+merge_states(L, St, Branched, Counter) when L =/= 0 ->
case gb_trees:lookup(L, Branched) of
- none -> St;
- {value,OtherSt} when St =:= none -> OtherSt;
- {value,OtherSt} -> merge_states_1(St, OtherSt)
+ none ->
+ {St, Counter};
+ {value,OtherSt} when St =:= none ->
+ {OtherSt, Counter};
+ {value,OtherSt} ->
+ merge_states_1(St, OtherSt, Counter)
end.
-merge_states_1(#st{x=Xs0,y=Ys0,numy=NumY0,h=H0,ct=Ct0},
- #st{x=Xs1,y=Ys1,numy=NumY1,h=H1,ct=Ct1}) ->
- NumY = merge_stk(NumY0, NumY1),
- Xs = merge_regs(Xs0, Xs1),
- Ys = merge_y_regs(Ys0, Ys1),
- Ct = merge_ct(Ct0, Ct1),
- #st{x=Xs,y=Ys,numy=NumY,h=min(H0, H1),ct=Ct}.
+merge_states_1(#st{xs=XsA,ys=YsA,vs=VsA,fragile=FragA,numy=NumYA,h=HA,ct=CtA},
+ #st{xs=XsB,ys=YsB,vs=VsB,fragile=FragB,numy=NumYB,h=HB,ct=CtB},
+ Counter0) ->
+ %% When merging registers we drop all registers that aren't defined in both
+ %% states, and resolve conflicts by creating new values (similar to phi
+ %% nodes in SSA).
+ %%
+ %% While doing this we build a "merge map" detailing which values need to
+ %% be kept and which new values need to be created to resolve conflicts.
+ %% This map is then used to create a new value database where the types of
+ %% all values have been joined.
+ {Xs, Merge0, Counter1} = merge_regs(XsA, XsB, #{}, Counter0),
+ {Ys, Merge, Counter} = merge_regs(YsA, YsB, Merge0, Counter1),
+ Vs = merge_values(Merge, VsA, VsB),
+
+ Fragile = merge_fragility(FragA, FragB),
+ NumY = merge_stk(NumYA, NumYB),
+ Ct = merge_ct(CtA, CtB),
+
+ St = #st{xs=Xs,ys=Ys,vs=Vs,fragile=Fragile,numy=NumY,h=min(HA, HB),ct=Ct},
+ {St, Counter}.
+
+%% Merges the contents of two register maps, returning the updated "merge map"
+%% and the new registers.
+merge_regs(RsA, RsB, Merge, Counter) ->
+ Keys = if
+ map_size(RsA) =< map_size(RsB) -> maps:keys(RsA);
+ map_size(RsA) > map_size(RsB) -> maps:keys(RsB)
+ end,
+ merge_regs_1(Keys, RsA, RsB, #{}, Merge, Counter).
+
+merge_regs_1([Reg | Keys], RsA, RsB, Regs, Merge0, Counter0) ->
+ case {RsA, RsB} of
+ {#{ Reg := #value_ref{}=RefA }, #{ Reg := #value_ref{}=RefB }} ->
+ {Ref, Merge, Counter} = merge_vrefs(RefA, RefB, Merge0, Counter0),
+ merge_regs_1(Keys, RsA, RsB, Regs#{ Reg => Ref }, Merge, Counter);
+ {#{ Reg := TagA }, #{ Reg := TagB }} ->
+ %% Tags describe the state of the register rather than the value it
+ %% contains, so if a register contains a tag in one state we have
+ %% to merge it as a tag regardless of whether the other state says
+ %% it's a value.
+ {y, _} = Reg, %Assertion.
+ merge_regs_1(Keys, RsA, RsB, Regs#{ Reg => merge_tags(TagA,TagB) },
+ Merge0, Counter0);
+ {#{}, #{}} ->
+ merge_regs_1(Keys, RsA, RsB, Regs, Merge0, Counter0)
+ end;
+merge_regs_1([], _, _, Regs, Merge, Counter) ->
+ {Regs, Merge, Counter}.
+
+merge_tags(Same, Same) ->
+ Same;
+merge_tags(uninitialized, _) ->
+ uninitialized;
+merge_tags(_, uninitialized) ->
+ uninitialized;
+merge_tags({catchtag,T0}, {catchtag,T1}) ->
+ {catchtag, ordsets:from_list(T0 ++ T1)};
+merge_tags({trytag,T0}, {trytag,T1}) ->
+ {trytag, ordsets:from_list(T0 ++ T1)};
+merge_tags(_A, _B) ->
+ %% All other combinations leave the register initialized. Errors arising
+ %% from this will be caught later on.
+ initialized.
+
+merge_vrefs(Ref, Ref, Merge, Counter) ->
+ %% We have two (potentially) different versions of the same value, so we
+ %% should join their types into the same value.
+ {Ref, Merge#{ Ref => Ref }, Counter};
+merge_vrefs(RefA, RefB, Merge, Counter) ->
+ %% We have two different values, so we need to create a new value from
+ %% their joined type if we haven't already done so.
+ Key = {RefA, RefB},
+ case Merge of
+ #{ Key := Ref } ->
+ {Ref, Merge, Counter};
+ #{} ->
+ Ref = #value_ref{id=Counter},
+ {Ref, Merge#{ Key => Ref }, Counter + 1}
+ end.
+
+merge_values(Merge, VsA, VsB) ->
+ maps:fold(fun(Spec, New, Acc) ->
+ merge_values_1(Spec, New, VsA, VsB, Acc)
+ end, #{}, Merge).
+
+merge_values_1(Same, Same, VsA, VsB, Acc) ->
+ %% We're merging different versions of the same value, so it's safe to
+ %% reuse old entries if the type's unchanged.
+ #value{type=TypeA}=EntryA = map_get(Same, VsA),
+ #value{type=TypeB}=EntryB = map_get(Same, VsB),
+ Entry = case join(TypeA, TypeB) of
+ TypeA -> EntryA;
+ TypeB -> EntryB;
+ JoinedType -> EntryA#value{type=JoinedType}
+ end,
+ Acc#{ Same => Entry };
+merge_values_1({RefA, RefB}, New, VsA, VsB, Acc) ->
+ #value{type=TypeA} = map_get(RefA, VsA),
+ #value{type=TypeB} = map_get(RefB, VsB),
+ Acc#{ New => #value{op=join,args=[],type=join(TypeA, TypeB)} }.
+
+merge_fragility(FragileA, FragileB) ->
+ cerl_sets:union(FragileA, FragileB).
merge_stk(S, S) -> S;
merge_stk(_, _) -> undecided.
@@ -1501,135 +2338,70 @@ merge_ct_1([C0|Ct0], [C1|Ct1]) ->
merge_ct_1([], []) -> [];
merge_ct_1(_, _) -> undecided.
-merge_regs(Rs0, Rs1) ->
- Rs = merge_regs_1(gb_trees:to_list(Rs0), gb_trees:to_list(Rs1)),
- gb_trees_from_list(Rs).
-
-merge_regs_1([Same|Rs1], [Same|Rs2]) ->
- [Same|merge_regs_1(Rs1, Rs2)];
-merge_regs_1([{R1,_}|Rs1], [{R2,_}|_]=Rs2) when R1 < R2 ->
- merge_regs_1(Rs1, Rs2);
-merge_regs_1([{R1,_}|_]=Rs1, [{R2,_}|Rs2]) when R1 > R2 ->
- merge_regs_1(Rs1, Rs2);
-merge_regs_1([{R,Type1}|Rs1], [{R,Type2}|Rs2]) ->
- [{R,merge_types(Type1, Type2)}|merge_regs_1(Rs1, Rs2)];
-merge_regs_1([], []) -> [];
-merge_regs_1([], [_|_]) -> [];
-merge_regs_1([_|_], []) -> [].
-
-merge_y_regs(Rs0, Rs1) ->
- case {gb_trees:size(Rs0),gb_trees:size(Rs1)} of
- {Sz0,Sz1} when Sz0 < Sz1 ->
- merge_y_regs_1(Sz0-1, Rs1, Rs0);
- {_,Sz1} ->
- merge_y_regs_1(Sz1-1, Rs0, Rs1)
- end.
+tuple_sz([Sz]) -> Sz;
+tuple_sz(Sz) -> Sz.
-merge_y_regs_1(Y, S, Regs0) when Y >= 0 ->
- Type0 = gb_trees:get(Y, Regs0),
- case gb_trees:get(Y, S) of
- Type0 ->
- merge_y_regs_1(Y-1, S, Regs0);
- Type1 ->
- Type = merge_types(Type0, Type1),
- Regs = gb_trees:update(Y, Type, Regs0),
- merge_y_regs_1(Y-1, S, Regs)
- end;
-merge_y_regs_1(_, _, Regs) -> Regs.
+verify_y_init(#vst{current=#st{numy=NumY,ys=Ys}}=Vst) when is_integer(NumY) ->
+ HighestY = maps:fold(fun({y,Y}, _, Acc) -> max(Y, Acc) end, -1, Ys),
+ true = NumY > HighestY, %Assertion.
+ verify_y_init_1(NumY - 1, Vst),
+ ok;
+verify_y_init(#vst{current=#st{numy=undecided,ys=Ys}}=Vst) ->
+ HighestY = maps:fold(fun({y,Y}, _, Acc) -> max(Y, Acc) end, -1, Ys),
+ verify_y_init_1(HighestY, Vst);
+verify_y_init(#vst{}) ->
+ ok.
-%% merge_types(Type1, Type2) -> Type
-%% Return the most specific type possible.
-%% Note: Type1 must NOT be the same as Type2.
-merge_types({fragile,Same}=Type, Same) ->
- Type;
-merge_types({fragile,T1}, T2) ->
- make_fragile(merge_types(T1, T2));
-merge_types(Same, {fragile,Same}=Type) ->
- Type;
-merge_types(T1, {fragile,T2}) ->
- make_fragile(merge_types(T1, T2));
-merge_types(uninitialized=I, _) -> I;
-merge_types(_, uninitialized=I) -> I;
-merge_types(initialized=I, _) -> I;
-merge_types(_, initialized=I) -> I;
-merge_types({catchtag,T0},{catchtag,T1}) ->
- {catchtag,ordsets:from_list(T0++T1)};
-merge_types({trytag,T0},{trytag,T1}) ->
- {trytag,ordsets:from_list(T0++T1)};
-merge_types({tuple,A}, {tuple,B}) ->
- {tuple,[min(tuple_sz(A), tuple_sz(B))]};
-merge_types({Type,A}, {Type,B})
- when Type =:= atom; Type =:= integer; Type =:= float ->
- if A =:= B -> {Type,A};
- true -> {Type,[]}
- end;
-merge_types({Type,_}, number)
- when Type =:= integer; Type =:= float ->
- number;
-merge_types(number, {Type,_})
- when Type =:= integer; Type =:= float ->
- number;
-merge_types(bool, {atom,A}) ->
- merge_bool(A);
-merge_types({atom,A}, bool) ->
- merge_bool(A);
-merge_types(#ms{id=Id1,valid=B1,slots=Slots1},
- #ms{id=Id2,valid=B2,slots=Slots2}) ->
- Id = if
- Id1 =:= Id2 -> Id1;
- true -> make_ref()
- end,
- #ms{id=Id,valid=B1 band B2,slots=min(Slots1, Slots2)};
-merge_types(T1, T2) when T1 =/= T2 ->
- %% Too different. All we know is that the type is a 'term'.
- term.
+verify_y_init_1(-1, _Vst) ->
+ ok;
+verify_y_init_1(Y, Vst) ->
+ Reg = {y, Y},
+ assert_not_fragile(Reg, Vst),
+ case get_raw_type(Reg, Vst) of
+ uninitialized -> error({uninitialized_reg,Reg});
+ _ -> verify_y_init_1(Y - 1, Vst)
+ end.
-tuple_sz([Sz]) -> Sz;
-tuple_sz(Sz) -> Sz.
+verify_live(0, _Vst) ->
+ ok;
+verify_live(Live, Vst) when is_integer(Live), 0 < Live, Live =< 1023 ->
+ verify_live_1(Live - 1, Vst);
+verify_live(Live, _Vst) ->
+ error({bad_number_of_live_regs,Live}).
-merge_bool([]) -> {atom,[]};
-merge_bool(true) -> bool;
-merge_bool(false) -> bool;
-merge_bool(_) -> {atom,[]}.
-
-verify_y_init(#vst{current=#st{y=Ys}}) ->
- verify_y_init_1(gb_trees:to_list(Ys)).
-
-verify_y_init_1([]) -> ok;
-verify_y_init_1([{Y,uninitialized}|_]) ->
- error({uninitialized_reg,{y,Y}});
-verify_y_init_1([{Y,{fragile,_}}|_]) ->
- %% Unsafe. This term may be outside any heap belonging
- %% to the process and would be corrupted by a GC.
- error({fragile_message_reference,{y,Y}});
-verify_y_init_1([{_,_}|Ys]) ->
- verify_y_init_1(Ys).
-
-verify_live(0, #vst{}) -> ok;
-verify_live(N, #vst{current=#st{x=Xs}}) ->
- verify_live_1(N, Xs).
-
-verify_live_1(0, _) -> ok;
-verify_live_1(N, Xs) when is_integer(N) ->
- X = N-1,
- case gb_trees:is_defined(X, Xs) of
- false -> error({{x,X},not_live});
- true -> verify_live_1(X, Xs)
- end;
-verify_live_1(N, _) -> error({bad_number_of_live_regs,N}).
+verify_live_1(-1, _) ->
+ ok;
+verify_live_1(X, Vst) when is_integer(X) ->
+ Reg = {x, X},
+ case get_raw_type(Reg, Vst) of
+ uninitialized -> error({Reg, not_live});
+ _ -> verify_live_1(X - 1, Vst)
+ end.
-verify_no_ct(#vst{current=#st{numy=none}}) -> ok;
+verify_no_ct(#vst{current=#st{numy=none}}) ->
+ ok;
verify_no_ct(#vst{current=#st{numy=undecided}}) ->
error(unknown_size_of_stackframe);
-verify_no_ct(#vst{current=#st{y=Ys}}) ->
- case [Y || Y <- gb_trees:to_list(Ys), verify_no_ct_1(Y)] of
- [] -> ok;
- CT -> error({unfinished_catch_try,CT})
+verify_no_ct(#vst{current=St}=Vst) ->
+ case collect_try_catch_tags(St#st.numy - 1, Vst, []) of
+ [_|_]=Bad -> error({unfinished_catch_try,Bad});
+ [] -> ok
end.
-verify_no_ct_1({_, {catchtag, _}}) -> true;
-verify_no_ct_1({_, {trytag, _}}) -> true;
-verify_no_ct_1({_, _}) -> false.
+%% Collects all try/catch tags, walking down from the Nth stack position.
+collect_try_catch_tags(-1, _Vst, Acc) ->
+ Acc;
+collect_try_catch_tags(Y, Vst, Acc0) ->
+ Tag = get_raw_type({y, Y}, Vst),
+ Acc = case is_try_catch_tag(Tag) of
+ true -> [{{y, Y}, Tag} | Acc0];
+ false -> Acc0
+ end,
+ collect_try_catch_tags(Y - 1, Vst, Acc).
+
+is_try_catch_tag({catchtag,_}) -> true;
+is_try_catch_tag({trytag,_}) -> true;
+is_try_catch_tag(_) -> false.
eat_heap(N, #vst{current=#st{h=Heap0}=St}=Vst) ->
case Heap0-N of
@@ -1647,89 +2419,190 @@ eat_heap_float(#vst{current=#st{hf=HeapFloats0}=St}=Vst) ->
Vst#vst{current=St#st{hf=HeapFloats}}
end.
-remove_fragility(#vst{current=#st{x=Xs0,y=Ys0}=St0}=Vst) ->
- F = fun(_, {fragile,Type}) -> Type;
- (_, Type) -> Type
- end,
- Xs = gb_trees:map(F, Xs0),
- Ys = gb_trees:map(F, Ys0),
- St = St0#st{x=Xs,y=Ys},
+%%% FRAGILITY
+%%%
+%%% The loop_rec/2 instruction may return a reference to a term that is not
+%%% part of the root set. That term or any part of it must not be included in a
+%%% garbage collection. Therefore, the term (or any part of it) must not be
+%%% passed to another function, placed in another term, or live in a Y register
+%%% over an instruction that may GC.
+%%%
+%%% Fragility is marked on a per-register (rather than per-value) basis.
+
+%% Marks Reg as fragile.
+mark_fragile(Reg, Vst) ->
+ #vst{current=#st{fragile=Fragile0}=St0} = Vst,
+ Fragile = cerl_sets:add_element(Reg, Fragile0),
+ St = St0#st{fragile=Fragile},
Vst#vst{current=St}.
-propagate_fragility(Type, Ss, Vst) ->
- F = fun(S) ->
- case get_term_type_1(S, Vst) of
- {fragile,_} -> true;
- _ -> false
- end
- end,
- case any(F, Ss) of
- true -> make_fragile(Type);
- false -> Type
+propagate_fragility(Reg, Args, #vst{current=St0}=Vst) ->
+ #st{fragile=Fragile0} = St0,
+
+ Sources = cerl_sets:from_list(Args),
+ Fragile = case cerl_sets:is_disjoint(Sources, Fragile0) of
+ true -> cerl_sets:del_element(Reg, Fragile0);
+ false -> cerl_sets:add_element(Reg, Fragile0)
+ end,
+
+ St = St0#st{fragile=Fragile},
+ Vst#vst{current=St}.
+
+%% Marks Reg as durable, must be used when assigning a newly created value to
+%% a register.
+remove_fragility(Reg, Vst) ->
+ #vst{current=#st{fragile=Fragile0}=St0} = Vst,
+ case cerl_sets:is_element(Reg, Fragile0) of
+ true ->
+ Fragile = cerl_sets:del_element(Reg, Fragile0),
+ St = St0#st{fragile=Fragile},
+ Vst#vst{current=St};
+ false ->
+ Vst
end.
-bif_type('-', Src, Vst) ->
- arith_type(Src, Vst);
-bif_type('+', Src, Vst) ->
- arith_type(Src, Vst);
-bif_type('*', Src, Vst) ->
- arith_type(Src, Vst);
-bif_type(abs, [Num], Vst) ->
+%% Marks all registers as durable.
+remove_fragility(#vst{current=St0}=Vst) ->
+ St = St0#st{fragile=cerl_sets:new()},
+ Vst#vst{current=St}.
+
+assert_durable_term(Src, Vst) ->
+ assert_term(Src, Vst),
+ assert_not_fragile(Src, Vst).
+
+assert_not_fragile({Kind,_}=Src, Vst) when Kind =:= x; Kind =:= y ->
+ check_limit(Src),
+ #vst{current=#st{fragile=Fragile}} = Vst,
+ case cerl_sets:is_element(Src, Fragile) of
+ true -> error({fragile_message_reference, Src});
+ false -> ok
+ end;
+assert_not_fragile(Lit, #vst{}) ->
+ assert_literal(Lit),
+ ok.
+
+%%%
+%%% Return/argument types of BIFs
+%%%
+
+bif_return_type('-', Src, Vst) ->
+ arith_return_type(Src, Vst);
+bif_return_type('+', Src, Vst) ->
+ arith_return_type(Src, Vst);
+bif_return_type('*', Src, Vst) ->
+ arith_return_type(Src, Vst);
+bif_return_type(abs, [Num], Vst) ->
case get_term_type(Num, Vst) of
- {float,_}=T -> T;
- {integer,_}=T -> T;
- _ -> number
+ {float,_}=T -> T;
+ {integer,_}=T -> T;
+ _ -> number
end;
-bif_type(float, _, _) -> {float,[]};
-bif_type('/', _, _) -> {float,[]};
+bif_return_type(float, _, _) -> {float,[]};
+bif_return_type('/', _, _) -> {float,[]};
+%% Binary operations
+bif_return_type('binary_part', [_,_], _) -> binary;
+bif_return_type('binary_part', [_,_,_], _) -> binary;
+bif_return_type('bit_size', [_], _) -> {integer,[]};
+bif_return_type('byte_size', [_], _) -> {integer,[]};
%% Integer operations.
-bif_type(ceil, [_], _) -> {integer,[]};
-bif_type('div', [_,_], _) -> {integer,[]};
-bif_type(floor, [_], _) -> {integer,[]};
-bif_type('rem', [_,_], _) -> {integer,[]};
-bif_type(length, [_], _) -> {integer,[]};
-bif_type(size, [_], _) -> {integer,[]};
-bif_type(trunc, [_], _) -> {integer,[]};
-bif_type(round, [_], _) -> {integer,[]};
-bif_type('band', [_,_], _) -> {integer,[]};
-bif_type('bor', [_,_], _) -> {integer,[]};
-bif_type('bxor', [_,_], _) -> {integer,[]};
-bif_type('bnot', [_], _) -> {integer,[]};
-bif_type('bsl', [_,_], _) -> {integer,[]};
-bif_type('bsr', [_,_], _) -> {integer,[]};
+bif_return_type(ceil, [_], _) -> {integer,[]};
+bif_return_type('div', [_,_], _) -> {integer,[]};
+bif_return_type(floor, [_], _) -> {integer,[]};
+bif_return_type('rem', [_,_], _) -> {integer,[]};
+bif_return_type(length, [_], _) -> {integer,[]};
+bif_return_type(size, [_], _) -> {integer,[]};
+bif_return_type(trunc, [_], _) -> {integer,[]};
+bif_return_type(round, [_], _) -> {integer,[]};
+bif_return_type('band', [_,_], _) -> {integer,[]};
+bif_return_type('bor', [_,_], _) -> {integer,[]};
+bif_return_type('bxor', [_,_], _) -> {integer,[]};
+bif_return_type('bnot', [_], _) -> {integer,[]};
+bif_return_type('bsl', [_,_], _) -> {integer,[]};
+bif_return_type('bsr', [_,_], _) -> {integer,[]};
%% Booleans.
-bif_type('==', [_,_], _) -> bool;
-bif_type('/=', [_,_], _) -> bool;
-bif_type('=<', [_,_], _) -> bool;
-bif_type('<', [_,_], _) -> bool;
-bif_type('>=', [_,_], _) -> bool;
-bif_type('>', [_,_], _) -> bool;
-bif_type('=:=', [_,_], _) -> bool;
-bif_type('=/=', [_,_], _) -> bool;
-bif_type('not', [_], _) -> bool;
-bif_type('and', [_,_], _) -> bool;
-bif_type('or', [_,_], _) -> bool;
-bif_type('xor', [_,_], _) -> bool;
-bif_type(is_atom, [_], _) -> bool;
-bif_type(is_boolean, [_], _) -> bool;
-bif_type(is_binary, [_], _) -> bool;
-bif_type(is_float, [_], _) -> bool;
-bif_type(is_function, [_], _) -> bool;
-bif_type(is_integer, [_], _) -> bool;
-bif_type(is_list, [_], _) -> bool;
-bif_type(is_map, [_], _) -> bool;
-bif_type(is_number, [_], _) -> bool;
-bif_type(is_pid, [_], _) -> bool;
-bif_type(is_port, [_], _) -> bool;
-bif_type(is_reference, [_], _) -> bool;
-bif_type(is_tuple, [_], _) -> bool;
+bif_return_type('==', [_,_], _) -> bool;
+bif_return_type('/=', [_,_], _) -> bool;
+bif_return_type('=<', [_,_], _) -> bool;
+bif_return_type('<', [_,_], _) -> bool;
+bif_return_type('>=', [_,_], _) -> bool;
+bif_return_type('>', [_,_], _) -> bool;
+bif_return_type('=:=', [_,_], _) -> bool;
+bif_return_type('=/=', [_,_], _) -> bool;
+bif_return_type('not', [_], _) -> bool;
+bif_return_type('and', [_,_], _) -> bool;
+bif_return_type('or', [_,_], _) -> bool;
+bif_return_type('xor', [_,_], _) -> bool;
+bif_return_type(is_atom, [_], _) -> bool;
+bif_return_type(is_boolean, [_], _) -> bool;
+bif_return_type(is_binary, [_], _) -> bool;
+bif_return_type(is_float, [_], _) -> bool;
+bif_return_type(is_function, [_], _) -> bool;
+bif_return_type(is_function, [_,_], _) -> bool;
+bif_return_type(is_integer, [_], _) -> bool;
+bif_return_type(is_list, [_], _) -> bool;
+bif_return_type(is_map, [_], _) -> bool;
+bif_return_type(is_number, [_], _) -> bool;
+bif_return_type(is_pid, [_], _) -> bool;
+bif_return_type(is_port, [_], _) -> bool;
+bif_return_type(is_reference, [_], _) -> bool;
+bif_return_type(is_tuple, [_], _) -> bool;
%% Misc.
-bif_type(node, [], _) -> {atom,[]};
-bif_type(node, [_], _) -> {atom,[]};
-bif_type(hd, [_], _) -> term;
-bif_type(tl, [_], _) -> term;
-bif_type(get, [_], _) -> term;
-bif_type(Bif, _, _) when is_atom(Bif) -> term.
+bif_return_type(tuple_size, [_], _) -> {integer,[]};
+bif_return_type(map_size, [_], _) -> {integer,[]};
+bif_return_type(node, [], _) -> {atom,[]};
+bif_return_type(node, [_], _) -> {atom,[]};
+bif_return_type(hd, [_], _) -> term;
+bif_return_type(tl, [_], _) -> term;
+bif_return_type(get, [_], _) -> term;
+bif_return_type(Bif, _, _) when is_atom(Bif) -> term.
+
+%% Generic
+bif_arg_types(tuple_size, [_]) -> [{tuple,[0],#{}}];
+bif_arg_types(map_size, [_]) -> [map];
+bif_arg_types(is_map_key, [_,_]) -> [term, map];
+bif_arg_types(map_get, [_,_]) -> [term, map];
+bif_arg_types(length, [_]) -> [list];
+bif_arg_types(hd, [_]) -> [cons];
+bif_arg_types(tl, [_]) -> [cons];
+%% Boolean
+bif_arg_types('not', [_]) -> [bool];
+bif_arg_types('and', [_,_]) -> [bool, bool];
+bif_arg_types('or', [_,_]) -> [bool, bool];
+bif_arg_types('xor', [_,_]) -> [bool, bool];
+%% Binary
+bif_arg_types('binary_part', [_,_]) ->
+ PosLen = {tuple, 2, #{ {integer,1} => {integer,[]},
+ {integer,2} => {integer,[]} }},
+ [binary, PosLen];
+bif_arg_types('binary_part', [_,_,_]) ->
+ [binary, {integer,[]}, {integer,[]}];
+bif_arg_types('bit_size', [_]) -> [binary];
+bif_arg_types('byte_size', [_]) -> [binary];
+%% Numerical
+bif_arg_types('-', [_]) -> [number];
+bif_arg_types('-', [_,_]) -> [number,number];
+bif_arg_types('+', [_]) -> [number];
+bif_arg_types('+', [_,_]) -> [number,number];
+bif_arg_types('*', [_,_]) -> [number, number];
+bif_arg_types('/', [_,_]) -> [number, number];
+bif_arg_types(abs, [_]) -> [number];
+bif_arg_types(ceil, [_]) -> [number];
+bif_arg_types(float, [_]) -> [number];
+bif_arg_types(floor, [_]) -> [number];
+bif_arg_types(trunc, [_]) -> [number];
+bif_arg_types(round, [_]) -> [number];
+%% Integer-specific
+bif_arg_types('div', [_,_]) -> [{integer,[]}, {integer,[]}];
+bif_arg_types('rem', [_,_]) -> [{integer,[]}, {integer,[]}];
+bif_arg_types('band', [_,_]) -> [{integer,[]}, {integer,[]}];
+bif_arg_types('bor', [_,_]) -> [{integer,[]}, {integer,[]}];
+bif_arg_types('bxor', [_,_]) -> [{integer,[]}, {integer,[]}];
+bif_arg_types('bnot', [_]) -> [{integer,[]}];
+bif_arg_types('bsl', [_,_]) -> [{integer,[]}, {integer,[]}];
+bif_arg_types('bsr', [_,_]) -> [{integer,[]}, {integer,[]}];
+%% Unsafe type tests that may fail if an argument doesn't have the right type.
+bif_arg_types(is_function, [_,_]) -> [term, {integer,[]}];
+bif_arg_types(_, Args) -> [term || _Arg <- Args].
is_bif_safe('/=', 2) -> true;
is_bif_safe('<', 2) -> true;
@@ -1758,86 +2631,200 @@ is_bif_safe(self, 0) -> true;
is_bif_safe(node, 0) -> true;
is_bif_safe(_, _) -> false.
-arith_type([A,B], Vst) ->
- case {get_term_type(A, Vst),get_term_type(B, Vst)} of
+arith_return_type([A], Vst) ->
+ %% Unary '+' or '-'.
+ case get_term_type(A, Vst) of
+ {integer,_} -> {integer,[]};
+ {float,_} -> {float,[]};
+ _ -> number
+ end;
+arith_return_type([A,B], Vst) ->
+ TypeA = get_term_type(A, Vst),
+ TypeB = get_term_type(B, Vst),
+ case {TypeA, TypeB} of
+ {{integer,_},{integer,_}} -> {integer,[]};
{{float,_},_} -> {float,[]};
{_,{float,_}} -> {float,[]};
{_,_} -> number
end;
-arith_type(_, _) -> number.
+arith_return_type(_, _) -> number.
-return_type({extfunc,M,F,A}, Vst) -> return_type_1(M, F, A, Vst);
-return_type(_, _) -> term.
+%%%
+%%% Return/argument types of calls
+%%%
+
+call_return_type({extfunc,M,F,A}, Vst) -> call_return_type_1(M, F, A, Vst);
+call_return_type(_, _) -> term.
-return_type_1(erlang, setelement, 3, Vst) ->
- Tuple = {x,1},
+call_return_type_1(erlang, setelement, 3, Vst) ->
+ IndexType = get_term_type({x,0}, Vst),
TupleType =
- case get_term_type(Tuple, Vst) of
- {tuple,_}=TT ->
- TT;
- {literal,Lit} when is_tuple(Lit) ->
- {tuple,tuple_size(Lit)};
- _ ->
- {tuple,[0]}
- end,
- case get_term_type({x,0}, Vst) of
- {integer,[]} -> TupleType;
- {integer,I} -> upgrade_tuple_type({tuple,[I]}, TupleType);
- _ -> TupleType
+ case get_term_type({x,1}, Vst) of
+ {literal,Tuple}=Lit when is_tuple(Tuple) -> get_literal_type(Lit);
+ {tuple,_,_}=TT -> TT;
+ _ -> {tuple,[0],#{}}
+ end,
+ case IndexType of
+ {integer,I} when is_integer(I) ->
+ case meet({tuple,[I],#{}}, TupleType) of
+ {tuple, Sz, Es0} ->
+ ValueType = get_term_type({x,2}, Vst),
+ Es = set_element_type({integer,I}, ValueType, Es0),
+ {tuple, Sz, Es};
+ none ->
+ TupleType
+ end;
+ _ ->
+ %% The index could point anywhere, so we must discard all element
+ %% information.
+ setelement(3, TupleType, #{})
end;
-return_type_1(erlang, F, A, _) ->
- return_type_erl(F, A);
-return_type_1(math, F, A, _) ->
- return_type_math(F, A);
-return_type_1(M, F, A, _) when is_atom(M), is_atom(F), is_integer(A), A >= 0 ->
+call_return_type_1(erlang, '++', 2, Vst) ->
+ case get_term_type({x,0}, Vst) =:= cons orelse
+ get_term_type({x,1}, Vst) =:= cons of
+ true -> cons;
+ false -> list
+ end;
+call_return_type_1(erlang, '--', 2, _Vst) ->
+ list;
+call_return_type_1(erlang, F, A, _) ->
+ erlang_mod_return_type(F, A);
+call_return_type_1(lists, F, A, Vst) ->
+ lists_mod_return_type(F, A, Vst);
+call_return_type_1(math, F, A, _) ->
+ math_mod_return_type(F, A);
+call_return_type_1(M, F, A, _) when is_atom(M), is_atom(F), is_integer(A), A >= 0 ->
term.
-return_type_erl(exit, 1) -> exception;
-return_type_erl(throw, 1) -> exception;
-return_type_erl(error, 1) -> exception;
-return_type_erl(error, 2) -> exception;
-return_type_erl(F, A) when is_atom(F), is_integer(A), A >= 0 -> term.
-
-return_type_math(cos, 1) -> {float,[]};
-return_type_math(cosh, 1) -> {float,[]};
-return_type_math(sin, 1) -> {float,[]};
-return_type_math(sinh, 1) -> {float,[]};
-return_type_math(tan, 1) -> {float,[]};
-return_type_math(tanh, 1) -> {float,[]};
-return_type_math(acos, 1) -> {float,[]};
-return_type_math(acosh, 1) -> {float,[]};
-return_type_math(asin, 1) -> {float,[]};
-return_type_math(asinh, 1) -> {float,[]};
-return_type_math(atan, 1) -> {float,[]};
-return_type_math(atanh, 1) -> {float,[]};
-return_type_math(erf, 1) -> {float,[]};
-return_type_math(erfc, 1) -> {float,[]};
-return_type_math(exp, 1) -> {float,[]};
-return_type_math(log, 1) -> {float,[]};
-return_type_math(log2, 1) -> {float,[]};
-return_type_math(log10, 1) -> {float,[]};
-return_type_math(sqrt, 1) -> {float,[]};
-return_type_math(atan2, 2) -> {float,[]};
-return_type_math(pow, 2) -> {float,[]};
-return_type_math(ceil, 1) -> {float,[]};
-return_type_math(floor, 1) -> {float,[]};
-return_type_math(fmod, 2) -> {float,[]};
-return_type_math(pi, 0) -> {float,[]};
-return_type_math(F, A) when is_atom(F), is_integer(A), A >= 0 -> term.
-
-check_limit({x,X}) when is_integer(X), X < 1023 ->
- %% Note: x(1023) is reserved for use by the BEAM loader.
- ok;
-check_limit({y,Y}) when is_integer(Y), Y < 1024 ->
- ok;
-check_limit({fr,Fr}) when is_integer(Fr), Fr < 1024 ->
- ok;
-check_limit(_) ->
- error(limit).
+erlang_mod_return_type(exit, 1) -> exception;
+erlang_mod_return_type(throw, 1) -> exception;
+erlang_mod_return_type(error, 1) -> exception;
+erlang_mod_return_type(error, 2) -> exception;
+erlang_mod_return_type(F, A) when is_atom(F), is_integer(A), A >= 0 -> term.
+
+math_mod_return_type(cos, 1) -> {float,[]};
+math_mod_return_type(cosh, 1) -> {float,[]};
+math_mod_return_type(sin, 1) -> {float,[]};
+math_mod_return_type(sinh, 1) -> {float,[]};
+math_mod_return_type(tan, 1) -> {float,[]};
+math_mod_return_type(tanh, 1) -> {float,[]};
+math_mod_return_type(acos, 1) -> {float,[]};
+math_mod_return_type(acosh, 1) -> {float,[]};
+math_mod_return_type(asin, 1) -> {float,[]};
+math_mod_return_type(asinh, 1) -> {float,[]};
+math_mod_return_type(atan, 1) -> {float,[]};
+math_mod_return_type(atanh, 1) -> {float,[]};
+math_mod_return_type(erf, 1) -> {float,[]};
+math_mod_return_type(erfc, 1) -> {float,[]};
+math_mod_return_type(exp, 1) -> {float,[]};
+math_mod_return_type(log, 1) -> {float,[]};
+math_mod_return_type(log2, 1) -> {float,[]};
+math_mod_return_type(log10, 1) -> {float,[]};
+math_mod_return_type(sqrt, 1) -> {float,[]};
+math_mod_return_type(atan2, 2) -> {float,[]};
+math_mod_return_type(pow, 2) -> {float,[]};
+math_mod_return_type(ceil, 1) -> {float,[]};
+math_mod_return_type(floor, 1) -> {float,[]};
+math_mod_return_type(fmod, 2) -> {float,[]};
+math_mod_return_type(pi, 0) -> {float,[]};
+math_mod_return_type(F, A) when is_atom(F), is_integer(A), A >= 0 -> term.
+
+lists_mod_return_type(all, 2, _Vst) ->
+ bool;
+lists_mod_return_type(any, 2, _Vst) ->
+ bool;
+lists_mod_return_type(keymember, 3, _Vst) ->
+ bool;
+lists_mod_return_type(member, 2, _Vst) ->
+ bool;
+lists_mod_return_type(prefix, 2, _Vst) ->
+ bool;
+lists_mod_return_type(suffix, 2, _Vst) ->
+ bool;
+lists_mod_return_type(dropwhile, 2, _Vst) ->
+ list;
+lists_mod_return_type(duplicate, 2, _Vst) ->
+ list;
+lists_mod_return_type(filter, 2, _Vst) ->
+ list;
+lists_mod_return_type(flatten, 1, _Vst) ->
+ list;
+lists_mod_return_type(flatten, 2, _Vst) ->
+ list;
+lists_mod_return_type(map, 2, Vst) ->
+ same_length_type({x,1}, Vst);
+lists_mod_return_type(MF, 3, Vst) when MF =:= mapfoldl; MF =:= mapfoldr ->
+ ListType = same_length_type({x,2}, Vst),
+ {tuple,2,#{ {integer,1} => ListType} };
+lists_mod_return_type(partition, 2, _Vst) ->
+ two_tuple(list, list);
+lists_mod_return_type(reverse, 1, Vst) ->
+ same_length_type({x,0}, Vst);
+lists_mod_return_type(seq, 2, _Vst) ->
+ list;
+lists_mod_return_type(seq, 3, _Vst) ->
+ list;
+lists_mod_return_type(sort, 1, Vst) ->
+ same_length_type({x,0}, Vst);
+lists_mod_return_type(sort, 2, Vst) ->
+ same_length_type({x,1}, Vst);
+lists_mod_return_type(splitwith, 2, _Vst) ->
+ two_tuple(list, list);
+lists_mod_return_type(takewhile, 2, _Vst) ->
+ list;
+lists_mod_return_type(unzip, 1, Vst) ->
+ ListType = same_length_type({x,0}, Vst),
+ two_tuple(ListType, ListType);
+lists_mod_return_type(usort, 1, Vst) ->
+ same_length_type({x,0}, Vst);
+lists_mod_return_type(usort, 2, Vst) ->
+ same_length_type({x,1}, Vst);
+lists_mod_return_type(zip, 2, _Vst) ->
+ list;
+lists_mod_return_type(zip3, 3, _Vst) ->
+ list;
+lists_mod_return_type(zipwith, 3, _Vst) ->
+ list;
+lists_mod_return_type(zipwith3, 4, _Vst) ->
+ list;
+lists_mod_return_type(_, _, _) ->
+ term.
+
+two_tuple(Type1, Type2) ->
+ {tuple,2,#{ {integer,1} => Type1,
+ {integer,2} => Type2 }}.
+
+same_length_type(Reg, Vst) ->
+ case get_term_type(Reg, Vst) of
+ {literal,[_|_]} -> cons;
+ cons -> cons;
+ nil -> nil;
+ _ -> list
+ end.
+
+check_limit({x,X}=Src) when is_integer(X) ->
+ if
+ %% Note: x(1023) is reserved for use by the BEAM loader.
+ 0 =< X, X < 1023 -> ok;
+ 1023 =< X -> error(limit);
+ X < 0 -> error({bad_register, Src})
+ end;
+check_limit({y,Y}=Src) when is_integer(Y) ->
+ if
+ 0 =< Y, Y < 1024 -> ok;
+ 1024 =< Y -> error(limit);
+ Y < 0 -> error({bad_register, Src})
+ end;
+check_limit({fr,Fr}=Src) when is_integer(Fr) ->
+ if
+ 0 =< Fr, Fr < 1023 -> ok;
+ 1023 =< Fr -> error(limit);
+ Fr < 0 -> error({bad_register, Src})
+ end.
min(A, B) when is_integer(A), is_integer(B), A < B -> A;
min(A, B) when is_integer(A), is_integer(B) -> B.
-gb_trees_from_list(L) -> gb_trees:from_orddict(lists:sort(L)).
+gb_trees_from_list(L) -> gb_trees:from_orddict(sort(L)).
error(Error) -> throw(Error).
diff --git a/lib/compiler/src/beam_z.erl b/lib/compiler/src/beam_z.erl
index 1c9d762eb1..415b579240 100644
--- a/lib/compiler/src/beam_z.erl
+++ b/lib/compiler/src/beam_z.erl
@@ -71,6 +71,31 @@ undo_renames([{get_hd,Src,Dst1},{get_tl,Src,Dst2}|Is]) ->
[{get_list,Src,Dst1,Dst2}|undo_renames(Is)];
undo_renames([{get_tl,Src,Dst2},{get_hd,Src,Dst1}|Is]) ->
[{get_list,Src,Dst1,Dst2}|undo_renames(Is)];
+undo_renames([{bs_put,_,{bs_put_binary,1,_},
+ [{atom,all},{literal,<<>>}]}|Is]) ->
+ undo_renames(Is);
+undo_renames([{bs_put,Fail,{bs_put_binary,1,_Flags},
+ [{atom,all},{literal,BinString}]}|Is0]) ->
+ Bits = bit_size(BinString),
+ Bytes = Bits div 8,
+ case Bits rem 8 of
+ 0 ->
+ I = {bs_put_string,byte_size(BinString),
+ {string,BinString}},
+ [undo_rename(I)|undo_renames(Is0)];
+ Rem ->
+ <<Binary:Bytes/bytes,Int:Rem>> = BinString,
+ PutInt = {bs_put_integer,Fail,{integer,Rem},1,
+ {field_flags,[unsigned,big]},{integer,Int}},
+ Is = [PutInt|undo_renames(Is0)],
+ case Binary of
+ <<>> ->
+ Is;
+ _ ->
+ [{bs_put_string,byte_size(Binary),
+ {string,Binary}}|Is]
+ end
+ end;
undo_renames([I|Is]) ->
[undo_rename(I)|undo_renames(Is)];
undo_renames([]) -> [].
@@ -79,8 +104,6 @@ undo_rename({bs_put,F,{I,U,Fl},[Sz,Src]}) ->
{I,F,Sz,U,Fl,Src};
undo_rename({bs_put,F,{I,Fl},[Src]}) ->
{I,F,Fl,Src};
-undo_rename({bs_put,{f,0},{bs_put_string,_,_}=I,[]}) ->
- I;
undo_rename({bif,bs_add=I,F,[Src1,Src2,{integer,U}],Dst}) ->
{I,F,[Src1,Src2,U],Dst};
undo_rename({bif,bs_utf8_size=I,F,[Src],Dst}) ->
@@ -101,7 +124,7 @@ undo_rename({test,bs_match_string=Op,F,[Ctx,Bin0]}) ->
0 -> Bin0;
Rem -> <<Bin0/bitstring,0:(8-Rem)>>
end,
- {test,Op,F,[Ctx,Bits,{string,binary_to_list(Bin)}]};
+ {test,Op,F,[Ctx,Bits,{string,Bin}]};
undo_rename({put_map,Fail,assoc,S,D,R,L}) ->
{put_map_assoc,Fail,S,D,R,L};
undo_rename({put_map,Fail,exact,S,D,R,L}) ->
@@ -110,6 +133,8 @@ undo_rename({test,has_map_fields,Fail,[Src|List]}) ->
{test,has_map_fields,Fail,Src,{list,List}};
undo_rename({get_map_elements,Fail,Src,{list,List}}) ->
{get_map_elements,Fail,Src,{list,List}};
+undo_rename({test,is_eq_exact,Fail,[Src,nil]}) ->
+ {test,is_nil,Fail,[Src]};
undo_rename({select,I,Reg,Fail,List}) ->
{I,Reg,Fail,{list,List}};
undo_rename(I) -> I.
diff --git a/lib/compiler/src/cerl_sets.erl b/lib/compiler/src/cerl_sets.erl
index 0361186713..f489baf238 100644
--- a/lib/compiler/src/cerl_sets.erl
+++ b/lib/compiler/src/cerl_sets.erl
@@ -204,4 +204,4 @@ fold(F, Init, D) ->
Set2 :: set(Element).
filter(F, D) ->
- maps:from_list(lists:filter(fun({K,_}) -> F(K) end, maps:to_list(D))).
+ maps:filter(fun(K,_) -> F(K) end, D).
diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl
index 6510571441..11dea9524b 100644
--- a/lib/compiler/src/compile.erl
+++ b/lib/compiler/src/compile.erl
@@ -31,6 +31,9 @@
%% Erlc interface.
-export([compile/3,compile_beam/3,compile_asm/3,compile_core/3]).
+%% Utility functions for compiler passes.
+-export([run_sub_passes/2]).
+
-export_type([option/0]).
-include("erl_compile.hrl").
@@ -39,6 +42,8 @@
-import(lists, [member/2,reverse/1,reverse/2,keyfind/3,last/1,
map/2,flatmap/2,foreach/2,foldr/3,any/2]).
+-define(SUB_PASS_TIMES, compile__sub_pass_times).
+
%%----------------------------------------------------------------------
-type abstract_code() :: [erl_parse:abstract_form()].
@@ -64,6 +69,7 @@
-type err_ret() :: 'error' | {'error', errors(), warnings()}.
-type comp_ret() :: mod_ret() | bin_ret() | err_ret().
+
%%----------------------------------------------------------------------
%%
@@ -143,6 +149,30 @@ noenv_output_generated(Opts) ->
env_compiler_options() -> env_default_opts().
+
+%%%
+%%% Run sub passes from a compiler pass.
+%%%
+
+-spec run_sub_passes([term()], term()) -> term().
+
+run_sub_passes(Ps, St) ->
+ case get(?SUB_PASS_TIMES) of
+ undefined ->
+ Runner = fun(_Name, Run, S) -> Run(S) end,
+ run_sub_passes_1(Ps, Runner, St);
+ Times when is_list(Times) ->
+ Runner = fun(Name, Run, S0) ->
+ T1 = erlang:monotonic_time(),
+ S = Run(S0),
+ T2 = erlang:monotonic_time(),
+ put(?SUB_PASS_TIMES,
+ [{Name,T2-T1}|get(?SUB_PASS_TIMES)]),
+ S
+ end,
+ run_sub_passes_1(Ps, Runner, St)
+ end.
+
%%
%% Local functions
%%
@@ -180,8 +210,11 @@ do_compile(Input, Opts0) ->
{error,Reason}
end
end,
- %% Dialyzer has already spawned workers.
- case lists:member(dialyzer, Opts) of
+ %% Some tools, like Dialyzer, has already spawned workers
+ %% and spawning extra workers actually slow the compilation
+ %% down instead of speeding it up, so we provide a mechanism
+ %% to bypass the compiler process.
+ case lists:member(no_spawn_compiler_process, Opts) of
true ->
IntFun();
false ->
@@ -218,23 +251,33 @@ expand_opt(report, Os) ->
[report_errors,report_warnings|Os];
expand_opt(return, Os) ->
[return_errors,return_warnings|Os];
+expand_opt(no_bsm3, Os) ->
+ %% The new bsm pass requires bsm3 instructions.
+ [no_bsm3,no_bsm_opt|Os];
expand_opt(r16, Os) ->
- [no_get_hd_tl,no_record_opt,no_utf8_atoms|Os];
+ expand_opt_before_21(Os);
expand_opt(r17, Os) ->
- [no_get_hd_tl,no_record_opt,no_utf8_atoms|Os];
+ expand_opt_before_21(Os);
expand_opt(r18, Os) ->
- [no_get_hd_tl,no_record_opt,no_utf8_atoms|Os];
+ expand_opt_before_21(Os);
expand_opt(r19, Os) ->
- [no_get_hd_tl,no_record_opt,no_utf8_atoms|Os];
+ expand_opt_before_21(Os);
expand_opt(r20, Os) ->
- [no_get_hd_tl,no_record_opt,no_utf8_atoms|Os];
+ expand_opt_before_21(Os);
+expand_opt(r21, Os) ->
+ [no_put_tuple2 | expand_opt(no_bsm3, Os)];
expand_opt({debug_info_key,_}=O, Os) ->
[encrypt_debug_info,O|Os];
-expand_opt(no_float_opt, Os) ->
- %%Turn off the entire type optimization pass.
- [no_topt|Os];
+expand_opt(no_type_opt, Os) ->
+ [no_ssa_opt_type_start,
+ no_ssa_opt_type_continue,
+ no_ssa_opt_type_finish | Os];
expand_opt(O, Os) -> [O|Os].
+expand_opt_before_21(Os) ->
+ [no_put_tuple2, no_get_hd_tl, no_ssa_opt_record,
+ no_utf8_atoms | expand_opt(no_bsm3, Os)].
+
%% format_error(ErrorDescriptor) -> string()
-spec format_error(term()) -> iolist().
@@ -387,17 +430,57 @@ fold_comp([{Name,Pass}|Ps], Run, Code0, St0) ->
end;
fold_comp([], _Run, Code, St) -> {ok,Code,St}.
+run_sub_passes_1([{Name,Run}|Ps], Runner, St0)
+ when is_atom(Name), is_function(Run, 1) ->
+ try Runner(Name, Run, St0) of
+ St ->
+ run_sub_passes_1(Ps, Runner, St)
+ catch
+ C:E:Stk ->
+ io:format("Sub pass ~s\n", [Name]),
+ erlang:raise(C, E, Stk)
+ end;
+run_sub_passes_1([], _, St) -> St.
+
run_tc({Name,Fun}, Code, St) ->
+ put(?SUB_PASS_TIMES, []),
T1 = erlang:monotonic_time(),
Val = (catch Fun(Code, St)),
T2 = erlang:monotonic_time(),
- Elapsed = erlang:convert_time_unit(T2 - T1, native, millisecond),
+ Times = erase(?SUB_PASS_TIMES),
+ Elapsed = erlang:convert_time_unit(T2 - T1, native, microsecond),
Mem0 = erts_debug:flat_size(Val)*erlang:system_info(wordsize),
Mem = lists:flatten(io_lib:format("~.1f kB", [Mem0/1024])),
io:format(" ~-30s: ~10.3f s ~12s\n",
- [Name,Elapsed/1000,Mem]),
+ [Name,Elapsed/1000000,Mem]),
+ print_times(Times, Name),
Val.
+print_times(Times0, Name) ->
+ Fam0 = sofs:relation(Times0),
+ Fam1 = sofs:rel2fam(Fam0),
+ Fam2 = sofs:to_external(Fam1),
+ Fam3 = [{W,lists:sum(Times)} || {W,Times} <- Fam2],
+ Fam = reverse(lists:keysort(2, Fam3)),
+ Total = case lists:sum([T || {_,T} <- Fam]) of
+ 0 -> 1;
+ Total0 -> Total0
+ end,
+ case Fam of
+ [] ->
+ ok;
+ [_|_] ->
+ io:format(" %% Sub passes of ~s from slowest to fastest:\n", [Name]),
+ print_times_1(Fam, Total)
+ end.
+
+print_times_1([{Name,T}|Ts], Total) ->
+ Elapsed = erlang:convert_time_unit(T, native, microsecond),
+ io:format(" ~-27s: ~10.3f s ~3w %\n",
+ [Name,Elapsed/1000000,round(100*T/Total)]),
+ print_times_1(Ts, Total);
+print_times_1([], _Total) -> ok.
+
run_eprof({Name,Fun}, Code, Name, St) ->
io:format("~p: Running eprof\n", [Name]),
c:appcall(tools, eprof, start_profiling, [[self()]]),
@@ -731,8 +814,6 @@ kernel_passes() ->
%% Optimizations that must be done after all other optimizations.
[{pass,sys_core_bsm},
{iff,dcbsm,{listing,"core_bsm"}},
- {pass,sys_core_dsetel},
- {iff,dsetel,{listing,"dsetel"}},
{iff,clint,?pass(core_lint_module)},
{iff,core,?pass(save_core_code)},
@@ -741,8 +822,30 @@ kernel_passes() ->
?pass(v3_kernel),
{iff,dkern,{listing,"kernel"}},
{iff,'to_kernel',{done,"kernel"}},
- {pass,v3_codegen},
- {iff,dcg,{listing,"codegen"}}
+ {pass,beam_kernel_to_ssa},
+ {iff,dssa,{listing,"ssa"}},
+ {iff,ssalint,{pass,beam_ssa_lint}},
+ {delay,
+ [{unless,no_share_opt,{pass,beam_ssa_share}},
+ {iff,dssashare,{listing,"ssashare"}},
+ {iff,ssalint,{pass,beam_ssa_lint}},
+ {unless,no_bsm_opt,{pass,beam_ssa_bsm}},
+ {iff,dssabsm,{listing,"ssabsm"}},
+ {iff,ssalint,{pass,beam_ssa_lint}},
+ {unless,no_fun_opt,{pass,beam_ssa_funs}},
+ {iff,dssafuns,{listing,"ssafuns"}},
+ {iff,ssalint,{pass,beam_ssa_lint}},
+ {unless,no_ssa_opt,{pass,beam_ssa_opt}},
+ {iff,dssaopt,{listing,"ssaopt"}},
+ {iff,ssalint,{pass,beam_ssa_lint}},
+ {unless,no_recv_opt,{pass,beam_ssa_recv}},
+ {iff,drecv,{listing,"recv"}}]},
+ {pass,beam_ssa_pre_codegen},
+ {iff,dprecg,{listing,"precodegen"}},
+ {iff,ssalint,{pass,beam_ssa_lint}},
+ {pass,beam_ssa_codegen},
+ {iff,dcg,{listing,"codegen"}},
+ {iff,doldcg,{listing,"codegen"}}
| asm_passes()].
asm_passes() ->
@@ -751,34 +854,16 @@ asm_passes() ->
[{pass,beam_a},
{iff,da,{listing,"a"}},
{unless,no_postopt,
- [{unless,no_reorder,{pass,beam_reorder}},
- {iff,dre,{listing,"reorder"}},
- {pass,beam_block},
+ [{pass,beam_block},
{iff,dblk,{listing,"block"}},
{unless,no_except,{pass,beam_except}},
{iff,dexcept,{listing,"except"}},
- {unless,no_bs_opt,{pass,beam_bs}},
- {iff,dbs,{listing,"bs"}},
- {unless,no_topt,{pass,beam_type}},
- {iff,dtype,{listing,"type"}},
- {pass,beam_split},
- {iff,dsplit,{listing,"split"}},
- {unless,no_dead,{pass,beam_dead}},
- {iff,ddead,{listing,"dead"}},
{unless,no_jopt,{pass,beam_jump}},
{iff,djmp,{listing,"jump"}},
{unless,no_peep_opt,{pass,beam_peep}},
{iff,dpeep,{listing,"peep"}},
{pass,beam_clean},
{iff,dclean,{listing,"clean"}},
- {unless,no_bsm_opt,{pass,beam_bsm}},
- {iff,dbsm,{listing,"bsm"}},
- {unless,no_recv_opt,{pass,beam_receive}},
- {iff,drecv,{listing,"recv"}},
- {unless,no_record_opt,{pass,beam_record}},
- {iff,drecord,{listing,"record"}},
- {unless,no_blk2,?pass(block2)},
- {iff,dblk2,{listing,"block2"}},
{unless,no_stack_trimming,{pass,beam_trim}},
{iff,dtrim,{listing,"trim"}},
{pass,beam_flatten}]},
@@ -787,7 +872,9 @@ asm_passes() ->
%% need to do a few clean-ups to code.
{iff,no_postopt,[{pass,beam_clean}]},
+ {iff,diffable,?pass(diffable)},
{pass,beam_z},
+ {iff,diffable,{listing,"S"}},
{iff,dz,{listing,"z"}},
{iff,dopt,{listing,"optimize"}},
{iff,'S',{listing,"S"}},
@@ -1360,10 +1447,6 @@ v3_kernel(Code0, #compile{options=Opts,warnings=Ws0}=St) ->
{ok,Code,St}
end.
-block2(Code0, #compile{options=Opts}=St) ->
- {ok,Code} = beam_block:module(Code0, [no_blockify|Opts]),
- {ok,Code,St}.
-
test_old_inliner(#compile{options=Opts}) ->
%% The point of this test is to avoid loading the old inliner
%% if we know that it will not be used.
@@ -1849,6 +1932,39 @@ restore_expand_module([F|Fs]) ->
[F|restore_expand_module(Fs)];
restore_expand_module([]) -> [].
+%%%
+%%% Transform the BEAM code to make it more friendly for
+%%% diffing: using function names instead of labels for
+%%% local calls and number labels relative to each function.
+%%%
+
+diffable(Code0, St) ->
+ {Mod,Exp,Attr,Fs0,NumLabels} = Code0,
+ EntryLabels0 = [{Entry,{Name,Arity}} ||
+ {function,Name,Arity,Entry,_} <- Fs0],
+ EntryLabels = maps:from_list(EntryLabels0),
+ Fs = [diffable_fix_function(F, EntryLabels) || F <- Fs0],
+ Code = {Mod,Exp,Attr,Fs,NumLabels},
+ {ok,Code,St}.
+
+diffable_fix_function({function,Name,Arity,Entry0,Is0}, LabelMap0) ->
+ Entry = maps:get(Entry0, LabelMap0),
+ {Is1,LabelMap} = diffable_label_map(Is0, 1, LabelMap0, []),
+ Fb = fun(Old) -> error({no_fb,Old}) end,
+ Is = beam_utils:replace_labels(Is1, [], LabelMap, Fb),
+ {function,Name,Arity,Entry,Is}.
+
+diffable_label_map([{label,Old}|Is], New, Map, Acc) ->
+ case Map of
+ #{Old:=NewLabel} ->
+ diffable_label_map(Is, New, Map, [{label,NewLabel}|Acc]);
+ #{} ->
+ diffable_label_map(Is, New+1, Map#{Old=>New}, [{label,New}|Acc])
+ end;
+diffable_label_map([I|Is], New, Map, Acc) ->
+ diffable_label_map(Is, New, Map, [I|Acc]);
+diffable_label_map([], _New, Map, Acc) ->
+ {Acc,Map}.
-spec options() -> 'ok'.
@@ -1969,22 +2085,25 @@ pre_load() ->
L = [beam_a,
beam_asm,
beam_block,
- beam_bs,
- beam_bsm,
beam_clean,
- beam_dead,
beam_dict,
beam_except,
beam_flatten,
beam_jump,
+ beam_kernel_to_ssa,
beam_opcodes,
beam_peep,
- beam_receive,
- beam_record,
- beam_reorder,
- beam_split,
+ beam_ssa,
+ beam_ssa_bsm,
+ beam_ssa_codegen,
+ beam_ssa_dead,
+ beam_ssa_funs,
+ beam_ssa_opt,
+ beam_ssa_pre_codegen,
+ beam_ssa_recv,
+ beam_ssa_share,
+ beam_ssa_type,
beam_trim,
- beam_type,
beam_utils,
beam_validator,
beam_z,
@@ -2001,9 +2120,7 @@ pre_load() ->
erl_scan,
sys_core_alias,
sys_core_bsm,
- sys_core_dsetel,
sys_core_fold,
- v3_codegen,
v3_core,
v3_kernel],
_ = code:ensure_modules_loaded(L),
diff --git a/lib/compiler/src/compiler.app.src b/lib/compiler/src/compiler.app.src
index cf32fd251c..a086a3a8d3 100644
--- a/lib/compiler/src/compiler.app.src
+++ b/lib/compiler/src/compiler.app.src
@@ -24,24 +24,29 @@
beam_a,
beam_asm,
beam_block,
- beam_bs,
- beam_bsm,
beam_clean,
- beam_dead,
beam_dict,
beam_disasm,
beam_except,
beam_flatten,
beam_jump,
+ beam_kernel_to_ssa,
beam_listing,
beam_opcodes,
beam_peep,
- beam_receive,
- beam_reorder,
- beam_record,
- beam_split,
+ beam_ssa,
+ beam_ssa_bsm,
+ beam_ssa_codegen,
+ beam_ssa_dead,
+ beam_ssa_funs,
+ beam_ssa_lint,
+ beam_ssa_opt,
+ beam_ssa_pp,
+ beam_ssa_pre_codegen,
+ beam_ssa_recv,
+ beam_ssa_share,
+ beam_ssa_type,
beam_trim,
- beam_type,
beam_utils,
beam_validator,
beam_z,
@@ -60,12 +65,10 @@
rec_env,
sys_core_alias,
sys_core_bsm,
- sys_core_dsetel,
sys_core_fold,
sys_core_fold_lists,
sys_core_inline,
sys_pre_attributes,
- v3_codegen,
v3_core,
v3_kernel,
v3_kernel_pp
diff --git a/lib/compiler/src/erl_bifs.erl b/lib/compiler/src/erl_bifs.erl
index 68489a0122..94a5dfe012 100644
--- a/lib/compiler/src/erl_bifs.erl
+++ b/lib/compiler/src/erl_bifs.erl
@@ -32,6 +32,22 @@
%% Returns `true' if the function `Module:Name/Arity' does not
%% affect the state, nor depend on the state, although its
%% evaluation is not guaranteed to complete normally for all input.
+%%
+%% NOTE: There is no need to include every new pure BIF
+%% here. Including it here means that the value of the function
+%% will be evaluated at compile-time if the arguments are
+%% constant. If that optimization is not useful/desired, there is
+%% no need to include the new BIF here.
+%%
+%% Functions whose return value could conceivably change in a
+%% future version of the runtime system must NOT be included here.
+%%
+%% Here are some example of functions that should not be
+%% included: `term_to_binary/1', hashing functions, non-trivial
+%% encode/decode functions.
+%%
+%% When unsure whether a new BIF should be included here, the
+%% conservative safe choice is NOT to include it.
-spec is_pure(atom(), atom(), arity()) -> boolean().
@@ -91,6 +107,7 @@ is_pure(erlang, is_bitstring, 1) -> true;
%% erlang:is_builtin/3 depends on the state (i.e. the version of the emulator).
is_pure(erlang, is_float, 1) -> true;
is_pure(erlang, is_function, 1) -> true;
+is_pure(erlang, is_function, 2) -> true;
is_pure(erlang, is_integer, 1) -> true;
is_pure(erlang, is_list, 1) -> true;
is_pure(erlang, is_map, 1) -> true;
@@ -107,6 +124,7 @@ is_pure(erlang, list_to_atom, 1) -> true;
is_pure(erlang, list_to_binary, 1) -> true;
is_pure(erlang, list_to_float, 1) -> true;
is_pure(erlang, list_to_integer, 1) -> true;
+is_pure(erlang, list_to_integer, 2) -> true;
is_pure(erlang, list_to_pid, 1) -> true;
is_pure(erlang, list_to_tuple, 1) -> true;
is_pure(erlang, max, 2) -> true;
@@ -193,6 +211,7 @@ is_safe(erlang, is_float, 1) -> true;
is_safe(erlang, is_function, 1) -> true;
is_safe(erlang, is_integer, 1) -> true;
is_safe(erlang, is_list, 1) -> true;
+is_safe(erlang, is_map, 1) -> true;
is_safe(erlang, is_number, 1) -> true;
is_safe(erlang, is_pid, 1) -> true;
is_safe(erlang, is_port, 1) -> true;
diff --git a/lib/compiler/src/genop.tab b/lib/compiler/src/genop.tab
index 02dead9e92..86590fad87 100755
--- a/lib/compiler/src/genop.tab
+++ b/lib/compiler/src/genop.tab
@@ -142,8 +142,7 @@ BEAM_FORMAT_NUMBER=0
20: send/0
## @spec remove_message
-## @doc Unlink the current message from the message queue and store a
-## pointer to the message in x(0). Remove any timeout.
+## @doc Unlink the current message from the message queue. Remove any timeout.
21: remove_message/0
## @spec timeout
@@ -574,3 +573,26 @@ BEAM_FORMAT_NUMBER=0
## @doc Get the tail (or cdr) part of a list (a cons cell) from Source and
## put it into the register Tail.
163: get_tl/2
+
+# OTP 22
+
+## @spec put_tuple2 Destination Elements
+## @doc Build a tuple with the elements in the list Elements and put it
+## put into register Destination.
+164: put_tuple2/2
+
+## @spec bs_get_tail Ctx Dst Live
+## @doc Sets Dst to the tail of Ctx at the current position
+165: bs_get_tail/3
+
+## @spec bs_start_match3 Fail Bin Live Dst
+## @doc Starts a binary match sequence
+166: bs_start_match3/4
+
+## @spec bs_get_position Ctx Dst Live
+## @doc Sets Dst to the current position of Ctx
+167: bs_get_position/3
+
+## @spec bs_set_positon Ctx Pos
+## @doc Sets the current position of Ctx to Pos
+168: bs_set_position/2
diff --git a/lib/compiler/src/sys_core_bsm.erl b/lib/compiler/src/sys_core_bsm.erl
index 62657933ee..685e807e65 100644
--- a/lib/compiler/src/sys_core_bsm.erl
+++ b/lib/compiler/src/sys_core_bsm.erl
@@ -24,223 +24,52 @@
-export([module/2,format_error/1]).
-include("core_parse.hrl").
--import(lists, [member/2,reverse/1,usort/1]).
-spec module(cerl:c_module(), [compile:option()]) -> {'ok', cerl:c_module()}.
-module(#c_module{defs=Ds0}=Mod, Opts) ->
- {Ds,Ws0} = function(Ds0, [], []),
- case member(bin_opt_info, Opts) of
- false ->
- {ok,Mod#c_module{defs=Ds}};
- true ->
- Ws1 = [make_warning(Where, What) || {Where,What} <- Ws0],
- Ws = usort(Ws1),
- {ok,Mod#c_module{defs=Ds},Ws}
- end.
+module(#c_module{defs=Ds}=Mod, _Opts) ->
+ {ok,Mod#c_module{defs=function(Ds)}}.
-function([{#c_var{name={F,Arity}}=Name,B0}|Fs], FsAcc, Ws0) ->
- try cerl_trees:mapfold(fun bsm_an/2, Ws0, B0) of
- {B,Ws} ->
- function(Fs, [{Name,B}|FsAcc], Ws)
+function([{#c_var{name={F,Arity}}=Name,B0}|Fs]) ->
+ try cerl_trees:map(fun bsm_reorder/1, B0) of
+ B -> [{Name,B} | function(Fs)]
catch
- throw:unsafe_bs_context_to_binary ->
- %% Unsafe bs_context_to_binary (in the sense that the
- %% contents of the binary will probably be wrong).
- %% Disable binary optimizations for the entire function.
- %% We don't generate an INFO message, because this happens
- %% very infrequently and it would be hard to explain in
- %% a comprehensible way in an INFO message.
- function(Fs, [{Name,B0}|FsAcc], Ws0);
Class:Error:Stack ->
- io:fwrite("Function: ~w/~w\n", [F,Arity]),
- erlang:raise(Class, Error, Stack)
+ io:fwrite("Function: ~w/~w\n", [F,Arity]),
+ erlang:raise(Class, Error, Stack)
end;
-function([], Fs, Ws) ->
- {reverse(Fs),Ws}.
+function([]) ->
+ [].
-type error() :: atom().
-spec format_error(error()) -> nonempty_string().
-format_error(bin_opt_alias) ->
- "INFO: the '=' operator will prevent delayed sub binary optimization";
-format_error(bin_partition) ->
- "INFO: matching non-variables after a previous clause matching a variable "
- "will prevent delayed sub binary optimization";
-format_error(bin_var_used) ->
- "INFO: using a matched out sub binary will prevent "
- "delayed sub binary optimization";
-format_error(orig_bin_var_used_in_guard) ->
- "INFO: using the original binary variable in a guard will prevent "
- "delayed sub binary optimization";
-format_error(bin_var_used_in_guard) ->
- "INFO: using a matched out sub binary in a guard will prevent "
- "delayed sub binary optimization".
-
+format_error(_) -> error(badarg).
-%%%
-%%% Annotate bit syntax matching to faciliate optimization in further passes.
-%%%
+%%% Reorder bit syntax matching to faciliate optimization in further passes.
-bsm_an(Core0, Ws0) ->
- case bsm_an(Core0) of
- {ok,Core} ->
- {Core,Ws0};
- {ok,Core,W} ->
- {Core,[W|Ws0]}
- end.
+bsm_reorder(#c_case{arg=#c_var{}=V}=Case) ->
+ bsm_reorder_1([V], Case);
+bsm_reorder(#c_case{arg=#c_values{es=Es}}=Case) ->
+ bsm_reorder_1(Es, Case);
+bsm_reorder(Core) ->
+ Core.
-bsm_an(#c_case{arg=#c_var{}=V}=Case) ->
- bsm_an_1([V], Case);
-bsm_an(#c_case{arg=#c_values{es=Es}}=Case) ->
- bsm_an_1(Es, Case);
-bsm_an(Other) ->
- {ok,Other}.
-
-bsm_an_1(Vs0, #c_case{clauses=Cs0}=Case) ->
+bsm_reorder_1(Vs0, #c_case{clauses=Cs0}=Case) ->
case bsm_leftmost(Cs0) of
- none ->
- {ok,Case};
- 1 ->
- bsm_an_2(Vs0, Cs0, Case);
- Pos ->
- Vs = move_from_col(Pos, Vs0),
- Cs = [C#c_clause{pats=move_from_col(Pos, Ps)} ||
- #c_clause{pats=Ps}=C <- Cs0],
- bsm_an_2(Vs, Cs, Case)
- end.
-
-bsm_an_2(Vs, Cs, Case) ->
- try
- bsm_ensure_no_partition(Cs),
- {ok,bsm_do_an(Vs, Cs, Case)}
- catch
- throw:{problem,Where,What} ->
- {ok,Case,{Where,What}}
+ Pos when Pos > 0, Pos =/= none ->
+ Vs = core_lib:make_values(move_from_col(Pos, Vs0)),
+ Cs = [C#c_clause{pats=move_from_col(Pos, Ps)}
+ || #c_clause{pats=Ps}=C <- Cs0],
+ Case#c_case{arg=Vs,clauses=Cs};
+ _ ->
+ Case
end.
move_from_col(Pos, L) ->
{First,[Col|Rest]} = lists:split(Pos - 1, L),
[Col|First] ++ Rest.
-bsm_do_an([#c_var{name=Vname}=V0|Vs0], Cs0, Case) ->
- bsm_inner_context_to_binary(Cs0),
- Cs = bsm_do_an_var(Vname, Cs0),
- V = bsm_annotate_for_reuse(V0),
- Vs = core_lib:make_values([V|Vs0]),
- Case#c_case{arg=Vs,clauses=Cs};
-bsm_do_an(_Vs, _Cs, Case) -> Case.
-
-bsm_inner_context_to_binary([#c_clause{body=B}|Cs]) ->
- %% Consider:
- %%
- %% foo(<<Length, Data/binary>>) -> %Line 1
- %% case {Data, Length} of %Line 2
- %% {_, 0} -> Data; %Line 3
- %% {<<...>>, 4} -> ... %Line 4
- %% end.
- %%
- %% No sub binary will be created for Data in line 1. The match
- %% context will be passed on to the `case` in line 2. In line 3,
- %% this pass inserts a `bs_context_to_binary` instruction to
- %% convert the match context representing Data to a binary before
- %% returning it. The problem is that the binary created will be
- %% the original binary (including the matched out Length field),
- %% not the tail of the binary as it is supposed to be.
- %%
- %% Here follows a heuristic to disable the binary optimizations
- %% for the entire function if this code kind of code is found.
-
- case cerl_trees:free_variables(B) of
- [] ->
- %% Since there are no free variables in the body of
- %% this clause, there can't be any troublesome
- %% bs_context_to_binary instructions.
- bsm_inner_context_to_binary(Cs);
- [_|_]=Free ->
- %% One of the free variables could refer to a match context
- %% created by the outer binary match.
- F = fun(#c_primop{name=#c_literal{val=bs_context_to_binary},
- args=[#c_var{name=V}]}, _) ->
- case member(V, Free) of
- true ->
- %% This bs_context_to_binary instruction will
- %% make a binary of the match context from an
- %% outer binary match. It is very likely that
- %% the contents of the binary will be wrong
- %% (the original binary as opposed to only
- %% the tail binary).
- throw(unsafe_bs_context_to_binary);
- false ->
- %% Safe. This bs_context_to_binary instruction
- %% will make a binary from a match context
- %% defined in the body of the clause.
- ok
- end;
- (_, _) ->
- ok
- end,
- cerl_trees:fold(F, ok, B)
- end;
-bsm_inner_context_to_binary([]) -> ok.
-
-bsm_do_an_var(V, [#c_clause{pats=[P|_],guard=G,body=B0}=C0|Cs]) ->
- case P of
- #c_var{name=VarName} ->
- case core_lib:is_var_used(V, G) of
- true -> bsm_problem(C0, orig_bin_var_used_in_guard);
- false -> ok
- end,
- case core_lib:is_var_used(VarName, G) of
- true -> bsm_problem(C0, bin_var_used_in_guard);
- false -> ok
- end,
- B1 = bsm_maybe_ctx_to_binary(VarName, B0),
- B = bsm_maybe_ctx_to_binary(V, B1),
- C = C0#c_clause{body=B},
- [C|bsm_do_an_var(V, Cs)];
- #c_alias{} ->
- case bsm_could_match_binary(P) of
- false ->
- [C0|bsm_do_an_var(V, Cs)];
- true ->
- bsm_problem(C0, bin_opt_alias)
- end;
- _ ->
- case bsm_could_match_binary(P) andalso bsm_is_var_used(V, G, B0) of
- false ->
- [C0|bsm_do_an_var(V, Cs)];
- true ->
- bsm_problem(C0, bin_var_used)
- end
- end;
-bsm_do_an_var(_, []) -> [].
-
-bsm_annotate_for_reuse(#c_var{anno=Anno}=Var) ->
- Var#c_var{anno=[reuse_for_context|Anno]}.
-
-bsm_is_var_used(V, G, B) ->
- core_lib:is_var_used(V, G) orelse core_lib:is_var_used(V, B).
-
-bsm_maybe_ctx_to_binary(V, B) ->
- case core_lib:is_var_used(V, B) andalso not previous_ctx_to_binary(V, B) of
- false ->
- B;
- true ->
- #c_seq{arg=#c_primop{name=#c_literal{val=bs_context_to_binary},
- args=[#c_var{name=V}]},
- body=B}
- end.
-
-previous_ctx_to_binary(V, Core) ->
- case Core of
- #c_seq{arg=#c_primop{name=#c_literal{val=bs_context_to_binary},
- args=[#c_var{name=V}]}} ->
- true;
- _ ->
- false
- end.
-
%% bsm_leftmost(Cs) -> none | ArgumentNumber
%% Find the leftmost argument that matches a nonempty binary.
%% Return either 'none' or the argument number (1-N).
@@ -262,94 +91,3 @@ bsm_leftmost_2([_|Ps], Cs, N, Pos) ->
bsm_leftmost_2(Ps, Cs, N+1, Pos);
bsm_leftmost_2([], Cs, _, Pos) ->
bsm_leftmost_1(Cs, Pos).
-
-%% bsm_ensure_no_partition(Cs) -> ok (exception if problem)
-%% There must only be a single bs_start_match2 instruction if we
-%% are to reuse the binary variable for the match context.
-%%
-%% To make sure that there is only a single bs_start_match2
-%% instruction, we will check for partitions such as:
-%%
-%% foo(<<...>>) -> ...
-%% foo(<Variable>) when ... -> ...
-%% foo(<Non-variable pattern>) ->
-%%
-%% If there is such partition, we reject the optimization.
-
-bsm_ensure_no_partition(Cs) ->
- bsm_ensure_no_partition_1(Cs, before).
-
-%% Loop through each clause.
-bsm_ensure_no_partition_1([#c_clause{pats=Ps,guard=G}|Cs], State0) ->
- State = bsm_ensure_no_partition_2(Ps, G, State0),
- case State of
- 'after' ->
- bsm_ensure_no_partition_after(Cs);
- _ ->
- ok
- end,
- bsm_ensure_no_partition_1(Cs, State);
-bsm_ensure_no_partition_1([], _) -> ok.
-
-bsm_ensure_no_partition_2([#c_binary{}|_], _, _State) ->
- within;
-bsm_ensure_no_partition_2([#c_alias{}=Alias|_], N, State) ->
- %% Retrieve the real pattern that the alias refers to and check that.
- P = bsm_real_pattern(Alias),
- bsm_ensure_no_partition_2([P], N, State);
-bsm_ensure_no_partition_2([_|_], _, before=State) ->
- %% No binary matching yet - therefore no partition.
- State;
-bsm_ensure_no_partition_2([P|_], _, State) ->
- case bsm_could_match_binary(P) of
- false ->
- State;
- true ->
- %% The pattern P *may* match a binary, so we must update the state.
- %% (P must be a variable.)
- 'after'
- end.
-
-bsm_ensure_no_partition_after([#c_clause{pats=Ps}=C|Cs]) ->
- case Ps of
- [#c_var{}|_] ->
- bsm_ensure_no_partition_after(Cs);
- _ ->
- bsm_problem(C, bin_partition)
- end;
-bsm_ensure_no_partition_after([]) -> ok.
-
-bsm_could_match_binary(#c_alias{pat=P}) -> bsm_could_match_binary(P);
-bsm_could_match_binary(#c_cons{}) -> false;
-bsm_could_match_binary(#c_tuple{}) -> false;
-bsm_could_match_binary(#c_literal{val=Lit}) -> is_bitstring(Lit);
-bsm_could_match_binary(_) -> true.
-
-bsm_real_pattern(#c_alias{pat=P}) -> bsm_real_pattern(P);
-bsm_real_pattern(P) -> P.
-
-bsm_problem(Where, What) ->
- throw({problem,Where,What}).
-
-make_warning(Core, Term) ->
- case should_suppress_warning(Core) of
- true ->
- ok;
- false ->
- Anno = cerl:get_ann(Core),
- Line = get_line(Anno),
- File = get_file(Anno),
- {File,[{Line,?MODULE,Term}]}
- end.
-
-should_suppress_warning(Core) ->
- Ann = cerl:get_ann(Core),
- member(compiler_generated, Ann).
-
-get_line([Line|_]) when is_integer(Line) -> Line;
-get_line([_|T]) -> get_line(T);
-get_line([]) -> none.
-
-get_file([{file,File}|_]) -> File;
-get_file([_|T]) -> get_file(T);
-get_file([]) -> "no_file". % should not happen
diff --git a/lib/compiler/src/sys_core_dsetel.erl b/lib/compiler/src/sys_core_dsetel.erl
deleted file mode 100644
index 9ab83c210f..0000000000
--- a/lib/compiler/src/sys_core_dsetel.erl
+++ /dev/null
@@ -1,360 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2002-2018. All 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 : Using dsetelement to make multiple-field record updates
-%% faster.
-
-%% 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 introduces destructive updates in the Core Erlang code, it
-%% must be done as a last stage before going to lower-level code.
-%%
-%% NOTE: Because there are currently 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.
-%%
-%% The rewriting is done as follows:
-%%
-%% let X1 = call 'erlang':'setelement(5, Tuple, Value1)
-%% in call 'erlang':'setelement(3, X1, Value2)
-%% =>
-%% let X1 = call 'erlang':'setelement(5, Tuple, Value1)
-%% in do primop dsetelement(3, X1, Value2)
-%% X1
-%% and
-%% let X1 = call 'erlang':'setelement(5, Tuple, Value1)
-%% in let X2 = call 'erlang':'setelement(3, X1, Value2)
-%% in ...
-%% =>
-%% let X2 = call 'erlang':'setelement(5, Tuple, Value1)
-%% in do primop 'dsetelement(3, X2, Value2)
-%% ...
-%% if X1 is used exactly once.
-%% Thus, we need to track variable usage.
-%%
-
--module(sys_core_dsetel).
-
--export([module/2]).
-
--include("core_parse.hrl").
-
--spec module(cerl:c_module(), [compile:option()]) -> {'ok', cerl:c_module()}.
-
-module(M0, _Options) ->
- M = visit_module(M0),
- {ok,M}.
-
-visit_module(#c_module{defs=Ds0}=R) ->
- Env = #{},
- Ds = visit_module_1(Ds0, Env, []),
- R#c_module{defs=Ds}.
-
-visit_module_1([{Name,F0}|Fs], Env, Acc) ->
- try visit(Env, F0) of
- {F,_} ->
- visit_module_1(Fs, Env, [{Name,F}|Acc])
- catch
- Class:Error:Stack ->
- #c_var{name={Func,Arity}} = Name,
- io:fwrite("Function: ~w/~w\n", [Func,Arity]),
- erlang:raise(Class, Error, Stack)
- end;
-visit_module_1([], _, Acc) ->
- lists:reverse(Acc).
-
-visit(Env, #c_var{name={_,_}}=R) ->
- %% Ignore local function name.
- {R, Env};
-visit(Env0, #c_var{name=X}=R) ->
- %% There should not be any free variables. If there are,
- %% the case will fail with an exception.
- case Env0 of
- #{X:=N} ->
- {R, Env0#{X:=N+1}}
- end;
-visit(Env, #c_literal{}=R) ->
- {R, Env};
-visit(Env0, #c_tuple{es=Es0}=R) ->
- {Es1,Env1} = visit_list(Env0, Es0),
- {R#c_tuple{es=Es1}, Env1};
-visit(Env0, #c_map{es=Es0}=R) ->
- {Es1,Env1} = visit_list(Env0, Es0),
- {R#c_map{es=Es1}, Env1};
-visit(Env0, #c_map_pair{key=K0,val=V0}=R) ->
- {K,Env1} = visit(Env0, K0),
- {V,Env2} = visit(Env1, V0),
- {R#c_map_pair{key=K,val=V}, Env2};
-visit(Env0, #c_cons{hd=H0,tl=T0}=R) ->
- {H1,Env1} = visit(Env0, H0),
- {T1,Env2} = visit(Env1, T0),
- {R#c_cons{hd=H1,tl=T1}, Env2};
-visit(Env0, #c_binary{segments=Segs}=R) ->
- Env = visit_bin_segs(Env0, Segs),
- {R, Env};
-visit(Env0, #c_values{es=Es0}=R) ->
- {Es1,Env1} = visit_list(Env0, Es0),
- {R#c_values{es=Es1}, Env1};
-visit(Env0, #c_fun{vars=Vs, body=B0}=R) ->
- {Xs, Env1} = bind_vars(Vs, Env0),
- {B1,Env2} = visit(Env1, B0),
- {R#c_fun{body=B1}, restore_vars(Xs, Env0, Env2)};
-visit(Env0, #c_let{vars=Vs, arg=A0, body=B0}=R) ->
- {A1,Env1} = visit(Env0, A0),
- {Xs,Env2} = bind_vars(Vs, Env1),
- {B1,Env3} = visit(Env2, B0),
- rewrite(R#c_let{arg=A1,body=B1}, Env3, restore_vars(Xs, Env1, Env3));
-visit(Env0, #c_seq{arg=A0, body=B0}=R) ->
- {A1,Env1} = visit(Env0, A0),
- {B1,Env2} = visit(Env1, B0),
- {R#c_seq{arg=A1,body=B1}, Env2};
-visit(Env0, #c_case{arg=A0,clauses=Cs0}=R) ->
- {A1,Env1} = visit(Env0, A0),
- {Cs1,Env2} = visit_list(Env1, Cs0),
- {R#c_case{arg=A1,clauses=Cs1}, Env2};
-visit(Env0, #c_clause{pats=Ps,guard=G0,body=B0}=R) ->
- {Vs, Env1} = visit_pats(Ps, Env0),
- {G1,Env2} = visit(Env1, G0),
- {B1,Env3} = visit(Env2, B0),
- {R#c_clause{guard=G1,body=B1}, restore_vars(Vs, Env0, Env3)};
-visit(Env0, #c_receive{clauses=Cs0,timeout=T0,action=A0}=R) ->
- {T1,Env1} = visit(Env0, T0),
- {Cs1,Env2} = visit_list(Env1, Cs0),
- {A1,Env3} = visit(Env2, A0),
- {R#c_receive{clauses=Cs1,timeout=T1,action=A1}, Env3};
-visit(Env0, #c_apply{op=Op0, args=As0}=R) ->
- {Op1,Env1} = visit(Env0, Op0),
- {As1,Env2} = visit_list(Env1, As0),
- {R#c_apply{op=Op1,args=As1}, Env2};
-visit(Env0, #c_call{module=M0,name=N0,args=As0}=R) ->
- {M1,Env1} = visit(Env0, M0),
- {N1,Env2} = visit(Env1, N0),
- {As1,Env3} = visit_list(Env2, As0),
- {R#c_call{module=M1,name=N1,args=As1}, Env3};
-visit(Env0, #c_primop{name=N0, args=As0}=R) ->
- {N1,Env1} = visit(Env0, N0),
- {As1,Env2} = visit_list(Env1, As0),
- {R#c_primop{name=N1,args=As1}, Env2};
-visit(Env0, #c_try{arg=E0, vars=Vs, body=B0, evars=Evs, handler=H0}=R) ->
- {E1,Env1} = visit(Env0, E0),
- {Xs, Env2} = bind_vars(Vs, Env1),
- {B1,Env3} = visit(Env2, B0),
- Env4 = restore_vars(Xs, Env1, Env3),
- {Ys, Env5} = bind_vars(Evs, Env4),
- {H1,Env6} = visit(Env5, H0),
- {R#c_try{arg=E1,body=B1,handler=H1}, restore_vars(Ys, Env4, Env6)};
-visit(Env0, #c_catch{body=B0}=R) ->
- {B1,Env1} = visit(Env0, B0),
- {R#c_catch{body=B1}, Env1};
-visit(Env0, #c_letrec{defs=Ds0,body=B0}=R) ->
- {Xs, Env1} = bind_vars([V || {V,_} <- Ds0], Env0),
- {Ds1,Env2} = visit_def_list(Env1, Ds0),
- {B1,Env3} = visit(Env2, B0),
- {R#c_letrec{defs=Ds1,body=B1}, restore_vars(Xs, Env0, Env3)}.
-%% The following general code for handling modules is slow if a module
-%% contains very many functions. There is special code in visit_module/1
-%% which is much faster.
-%% visit(Env0, #c_module{defs=D0}=R) ->
-%% {R1,Env1} = visit(Env0, #c_letrec{defs=D0,body=#c_nil{}}),
-%% {R#c_module{defs=R1#c_letrec.defs}, Env1};
-
-visit_list(Env, L) ->
- lists:mapfoldl(fun (E, A) -> visit(A, E) end, Env, L).
-
-visit_def_list(Env, L) ->
- lists:mapfoldl(fun ({Name,V0}, E0) ->
- {V1,E1} = visit(E0, V0),
- {{Name,V1}, E1}
- end, Env, L).
-
-visit_bin_segs(Env, Segs) ->
- lists:foldl(fun (#c_bitstr{val=Val,size=Sz}, E0) ->
- {_, E1} = visit(E0, Val),
- {_, E2} = visit(E1, Sz),
- E2
- end, Env, Segs).
-
-bind_vars(Vs, Env) ->
- bind_vars(Vs, Env, []).
-
-bind_vars([#c_var{name=X}|Vs], Env0, Xs)->
- bind_vars(Vs, Env0#{X=>0}, [X|Xs]);
-bind_vars([], Env,Xs) ->
- {Xs, Env}.
-
-visit_pats(Ps, Env) ->
- visit_pats(Ps, Env, []).
-
-visit_pats([P|Ps], Env0, Vs0) ->
- {Vs1, Env1} = visit_pat(Env0, P, Vs0),
- visit_pats(Ps, Env1, Vs1);
-visit_pats([], Env, Vs) ->
- {Vs, Env}.
-
-visit_pat(Env0, #c_var{name=V}, Vs) ->
- {[V|Vs], Env0#{V=>0}};
-visit_pat(Env0, #c_tuple{es=Es}, Vs) ->
- visit_pats(Es, Env0, Vs);
-visit_pat(Env0, #c_map{es=Es}, Vs) ->
- visit_pats(Es, Env0, Vs);
-visit_pat(Env0, #c_map_pair{op=#c_literal{val=exact},key=V,val=K}, Vs0) ->
- {Vs1, Env1} = visit_pat(Env0, V, Vs0),
- visit_pat(Env1, K, Vs1);
-visit_pat(Env0, #c_cons{hd=H,tl=T}, Vs0) ->
- {Vs1, Env1} = visit_pat(Env0, H, Vs0),
- visit_pat(Env1, T, Vs1);
-visit_pat(Env0, #c_binary{segments=Segs}, Vs) ->
- visit_pats(Segs, Env0, Vs);
-visit_pat(Env0, #c_bitstr{val=Val,size=Sz}, Vs0) ->
- {Vs1, Env1} =
- case Sz of
- #c_var{name=V} ->
- %% We don't tolerate free variables.
- case Env0 of
- #{V:=N} ->
- {Vs0, Env0#{V:=N+1}}
- end;
- _ ->
- visit_pat(Env0, Sz, Vs0)
- end,
- visit_pat(Env1, Val, Vs1);
-visit_pat(Env0, #c_alias{pat=P,var=#c_var{name=V}}, Vs) ->
- visit_pat(Env0#{V=>0}, P, [V|Vs]);
-visit_pat(Env, #c_literal{}, Vs) ->
- {Vs, Env}.
-
-restore_vars([V|Vs], Env0, Env1) ->
- case Env0 of
- #{V:=N} ->
- restore_vars(Vs, Env0, Env1#{V=>N});
- _ ->
- restore_vars(Vs, Env0, maps:remove(V, Env1))
- end;
-restore_vars([], _, Env1) ->
- Env1.
-
-
-%% let X1 = call 'erlang':'setelement(5, Tuple, Value1)
-%% in call 'erlang':'setelement(3, X1, Value2)
-%% =>
-%% let X1 = call 'erlang':'setelement(5, Tuple, Value1)
-%% in do primop dsetelement(3, X1, Value2)
-%% X1
-
-rewrite(#c_let{vars=[#c_var{name=X}=V]=Vs,
- arg=#c_call{module=#c_literal{val='erlang'},
- name=#c_literal{val='setelement'},
- args=[#c_literal{val=Index1}, _Tuple, _Val1]
- }=A,
- body=#c_call{anno=Banno,module=#c_literal{val='erlang'},
- name=#c_literal{val='setelement'},
- args=[#c_literal{val=Index2},
- #c_var{name=X},
- Val2]
- }
- }=R,
- _BodyEnv, FinalEnv)
- when is_integer(Index1), is_integer(Index2), Index2 > 0, Index1 > Index2 ->
- case is_safe(Val2) of
- true ->
- {R#c_let{vars=Vs,
- arg=A,
- body=#c_seq{arg=#c_primop{
- anno=Banno,
- name=#c_literal{val='dsetelement'},
- args=[#c_literal{val=Index2},
- V,
- Val2]},
- body=V}
- },
- FinalEnv};
- false ->
- {R, FinalEnv}
- end;
-
-%% let X1 = call 'erlang':'setelement(5, Tuple, Value1)
-%% in let X2 = 'erlang':'setelement(3, X1, Value2)
-%% in ...
-%% =>
-%% let X2 = call 'erlang':'setelement(5, Tuple, Value1)
-%% in do primop dsetelement(3, X2, Value2)
-%% ...
-%% if X1 is used exactly once.
-
-rewrite(#c_let{vars=[#c_var{name=X1}],
- arg=#c_call{module=#c_literal{val='erlang'},
- name=#c_literal{val='setelement'},
- args=[#c_literal{val=Index1}, _Tuple, _Val1]
- }=A,
- body=#c_let{vars=[#c_var{}=V]=Vs,
- arg=#c_call{anno=Banno,
- module=#c_literal{val='erlang'},
- name=#c_literal{val='setelement'},
- args=[#c_literal{val=Index2},
- #c_var{name=X1},
- Val2]},
- body=B}
- }=R,
- BodyEnv, FinalEnv)
- when is_integer(Index1), is_integer(Index2), Index2 > 0, Index1 > Index2 ->
- case is_single_use(X1, BodyEnv) andalso is_safe(Val2) of
- true ->
- {R#c_let{vars=Vs,
- arg=A,
- body=#c_seq{arg=#c_primop{
- anno=Banno,
- name=#c_literal{val='dsetelement'},
- args=[#c_literal{val=Index2},
- V,
- Val2]},
- body=B}
- },
- FinalEnv};
- false ->
- {R, FinalEnv}
- end;
-
-rewrite(R, _, FinalEnv) ->
- {R, FinalEnv}.
-
-%% is_safe(CoreExpr) -> true|false
-%% Determines whether the Core expression can cause a GC collection at run-time.
-%% Note: Assumes that the constant pool is turned on.
-
-is_safe(#c_var{}) -> true;
-is_safe(#c_literal{}) -> true;
-is_safe(_) -> false.
-
-is_single_use(V, Env) ->
- case Env of
- #{V:=1} ->
- true;
- _ ->
- false
- end.
diff --git a/lib/compiler/src/sys_core_fold.erl b/lib/compiler/src/sys_core_fold.erl
index 1681d97efb..7e219da0af 100644
--- a/lib/compiler/src/sys_core_fold.erl
+++ b/lib/compiler/src/sys_core_fold.erl
@@ -99,7 +99,7 @@
t=#{} :: map(), %Types
in_guard=false}). %In guard or not.
--type type_info() :: cerl:cerl() | 'bool' | 'integer'.
+-type type_info() :: cerl:cerl() | 'bool' | 'integer' | {'fun', pos_integer()}.
-type yes_no_maybe() :: 'yes' | 'no' | 'maybe'.
-type sub() :: #sub{}.
@@ -478,9 +478,20 @@ expr(#c_try{anno=A,arg=E0,vars=Vs0,body=B0,evars=Evs0,handler=H0}=Try, _, Sub0)
false ->
{Evs1,Sub2} = var_list(Evs0, Sub0),
H1 = body(H0, value, Sub2),
- Try#c_try{arg=E1,vars=Vs1,body=B1,evars=Evs1,handler=H1}
+ H2 = opt_try_handler(H1, lists:last(Evs1)),
+ Try#c_try{arg=E1,vars=Vs1,body=B1,evars=Evs1,handler=H2}
end.
+%% Attempts to convert old erlang:get_stacktrace/0 calls into the new
+%% three-argument catch, with possibility of further optimisations.
+opt_try_handler(#c_call{anno=A,module=#c_literal{val=erlang},name=#c_literal{val=get_stacktrace},args=[]}, Var) ->
+ #c_primop{anno=A,name=#c_literal{val=build_stacktrace},args=[Var]};
+opt_try_handler(#c_case{clauses=Cs0} = Case, Var) ->
+ Cs = [C#c_clause{body=opt_try_handler(B, Var)} || #c_clause{body=B} = C <- Cs0],
+ Case#c_case{clauses=Cs};
+opt_try_handler(#c_let{arg=Arg} = Let, Var) ->
+ Let#c_let{arg=opt_try_handler(Arg, Var)};
+opt_try_handler(X, _) -> X.
%% 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
@@ -561,6 +572,7 @@ ifes_list(FVar, [E|Es], Safe) ->
ifes_list(_FVar, [], _Safe) ->
true.
+
expr_list(Es, Ctxt, Sub) ->
[expr(E, Ctxt, Sub) || E <- Es].
@@ -949,18 +961,16 @@ fold_lit_args(Call, Module, Name, Args0) ->
%%
fold_non_lit_args(Call, erlang, is_boolean, [Arg], Sub) ->
eval_is_boolean(Call, Arg, Sub);
-fold_non_lit_args(Call, erlang, element, [Arg1,Arg2], Sub) ->
- eval_element(Call, Arg1, Arg2, Sub);
fold_non_lit_args(Call, erlang, length, [Arg], _) ->
eval_length(Call, Arg);
fold_non_lit_args(Call, erlang, '++', [Arg1,Arg2], _) ->
eval_append(Call, Arg1, Arg2);
fold_non_lit_args(Call, lists, append, [Arg1,Arg2], _) ->
eval_append(Call, Arg1, Arg2);
-fold_non_lit_args(Call, erlang, setelement, [Arg1,Arg2,Arg3], _) ->
- eval_setelement(Call, Arg1, Arg2, Arg3);
-fold_non_lit_args(Call, erlang, is_record, [Arg1,Arg2,Arg3], Sub) ->
- eval_is_record(Call, Arg1, Arg2, Arg3, Sub);
+fold_non_lit_args(Call, erlang, is_function, [Arg1], Sub) ->
+ eval_is_function_1(Call, Arg1, Sub);
+fold_non_lit_args(Call, erlang, is_function, [Arg1,Arg2], Sub) ->
+ eval_is_function_2(Call, Arg1, Arg2, Sub);
fold_non_lit_args(Call, erlang, N, Args, Sub) ->
NumArgs = length(Args),
case erl_internal:comp_op(N, NumArgs) of
@@ -976,6 +986,22 @@ fold_non_lit_args(Call, erlang, N, Args, Sub) ->
end;
fold_non_lit_args(Call, _, _, _, _) -> Call.
+eval_is_function_1(Call, Arg1, Sub) ->
+ case get_type(Arg1, Sub) of
+ none -> Call;
+ {'fun',_} -> #c_literal{anno=cerl:get_ann(Call),val=true};
+ _ -> #c_literal{anno=cerl:get_ann(Call),val=false}
+ end.
+
+eval_is_function_2(Call, Arg1, #c_literal{val=Arity}, Sub)
+ when is_integer(Arity), Arity > 0 ->
+ case get_type(Arg1, Sub) of
+ none -> Call;
+ {'fun',Arity} -> #c_literal{anno=cerl:get_ann(Call),val=true};
+ _ -> #c_literal{anno=cerl:get_ann(Call),val=false}
+ end;
+eval_is_function_2(Call, _Arg1, _Arg2, _Sub) -> Call.
+
%% Evaluate a relational operation using type information.
eval_rel_op(Call, Op, [#c_var{name=V},#c_var{name=V}], _) ->
Bool = erlang:Op(same, same),
@@ -1109,96 +1135,6 @@ eval_append(Call, #c_cons{anno=Anno,hd=H,tl=T}, List) ->
eval_append(Call, X, Y) ->
Call#c_call{args=[X,Y]}. %Rebuild call arguments.
-%% eval_element(Call, Pos, Tuple, Types) -> Val.
-%% Evaluates element/2 if the position Pos is a literal and
-%% the shape of the tuple Tuple is known.
-%%
-eval_element(Call, #c_literal{val=Pos}, Tuple, Types)
- when is_integer(Pos) ->
- case get_type(Tuple, Types) of
- none ->
- Call;
- Type ->
- Es = case cerl:is_c_tuple(Type) of
- false -> [];
- true -> cerl:tuple_es(Type)
- end,
- if
- 1 =< Pos, Pos =< length(Es) ->
- El = lists:nth(Pos, Es),
- try
- cerl:set_ann(pat_to_expr(El), [compiler_generated])
- catch
- throw:impossible ->
- Call
- end;
- true ->
- %% Index outside tuple or not a tuple.
- eval_failure(Call, badarg)
- end
- end;
-eval_element(Call, Pos, Tuple, Sub) ->
- case is_int_type(Pos, Sub) =:= no orelse
- is_tuple_type(Tuple, Sub) =:= no of
- true ->
- eval_failure(Call, badarg);
- false ->
- Call
- end.
-
-%% eval_is_record(Call, Var, Tag, Size, Types) -> Val.
-%% Evaluates is_record/3 using type information.
-%%
-eval_is_record(Call, Term, #c_literal{val=NeededTag},
- #c_literal{val=Size}, Types) ->
- case get_type(Term, Types) of
- none ->
- Call;
- Type ->
- Es = case cerl:is_c_tuple(Type) of
- false -> [];
- true -> cerl:tuple_es(Type)
- end,
- case Es of
- [#c_literal{val=Tag}|_] ->
- Bool = Tag =:= NeededTag andalso
- length(Es) =:= Size,
- #c_literal{val=Bool};
- _ ->
- #c_literal{val=false}
- end
- end;
-eval_is_record(Call, _, _, _, _) -> Call.
-
-%% eval_setelement(Call, Pos, Tuple, NewVal) -> Core.
-%% Evaluates setelement/3 if position Pos is an integer
-%% and the shape of the tuple Tuple is known.
-%%
-eval_setelement(Call, #c_literal{val=Pos}, Tuple, NewVal)
- when is_integer(Pos) ->
- case cerl:is_data(Tuple) of
- false ->
- Call;
- true ->
- Es0 = case cerl:is_c_tuple(Tuple) of
- false -> [];
- true -> cerl:tuple_es(Tuple)
- end,
- if
- 1 =< Pos, Pos =< length(Es0) ->
- Es = eval_setelement_1(Pos, Es0, NewVal),
- cerl:update_c_tuple(Tuple, Es);
- true ->
- eval_failure(Call, badarg)
- end
- end;
-eval_setelement(Call, _, _, _) -> Call.
-
-eval_setelement_1(1, [_|T], NewVal) ->
- [NewVal|T];
-eval_setelement_1(Pos, [H|T], NewVal) when Pos > 1 ->
- [H|eval_setelement_1(Pos-1, T, NewVal)].
-
%% eval_failure(Call, Reason) -> Core.
%% Warn for a call that will fail and replace the call with
%% a call to erlang:error(Reason).
@@ -1258,16 +1194,15 @@ clause(#c_clause{pats=Ps0}=Cl, Cexpr, Ctxt, Sub0) ->
end.
clause_1(#c_clause{guard=G0,body=B0}=Cl, Ps1, Cexpr, Ctxt, Sub1) ->
- Sub2 = update_types(Cexpr, Ps1, Sub1),
GSub = case {Cexpr,Ps1,G0} of
{_,_,#c_literal{}} ->
%% No need for substitution tricks when the guard
%% does not contain any variables.
- Sub2;
+ Sub1;
{#c_var{name='_'},_,_} ->
%% In a 'receive', Cexpr is the variable '_', which represents the
%% message being matched. We must NOT do any extra substiutions.
- Sub2;
+ Sub1;
{#c_var{},[#c_var{}=Var],_} ->
%% The idea here is to optimize expressions such as
%%
@@ -1289,16 +1224,16 @@ clause_1(#c_clause{guard=G0,body=B0}=Cl, Ps1, Cexpr, Ctxt, Sub1) ->
%%
case cerl:is_c_fname(Cexpr) of
false ->
- sub_set_var(Var, Cexpr, Sub2);
+ sub_set_var(Var, Cexpr, Sub1);
true ->
%% We must not copy funs, and especially not into guards.
- Sub2
+ Sub1
end;
_ ->
- Sub2
+ Sub1
end,
G1 = guard(G0, GSub),
- B1 = body(B0, Ctxt, Sub2),
+ B1 = body(B0, Ctxt, Sub1),
Cl#c_clause{pats=Ps1,guard=G1,body=B1}.
%% let_substs(LetVars, LetArg, Sub) -> {[Var],[Val],Sub}.
@@ -1382,8 +1317,7 @@ pattern(#c_binary{segments=V0}=Pat, Isub, Osub0) ->
{Pat#c_binary{segments=V1},Osub1};
pattern(#c_alias{var=V0,pat=P0}=Pat, Isub, Osub0) ->
{V1,Osub1} = pattern(V0, Isub, Osub0),
- {P1,Osub2} = pattern(P0, Isub, Osub1),
- Osub = update_types(V1, [P1], Osub2),
+ {P1,Osub} = pattern(P0, Isub, Osub1),
{Pat#c_alias{var=V1,pat=P1},Osub}.
map_pair_pattern_list(Ps0, Isub, Osub0) ->
@@ -2105,14 +2039,9 @@ case_expand_var(E, #sub{t=Tdb}) ->
%% encountered.
coerce_to_data(C) ->
- case cerl:is_c_alias(C) of
- false ->
- case cerl:is_data(C) orelse cerl:is_c_var(C) of
- true -> C;
- false -> throw(impossible)
- end;
- true ->
- coerce_to_data(cerl:alias_pat(C))
+ case cerl:is_data(C) orelse cerl:is_c_var(C) of
+ true -> C;
+ false -> throw(impossible)
end.
%% case_opt_nomatch(E, Clauses, LitExpr) -> Clauses'
@@ -3108,14 +3037,6 @@ is_int_type(Var, Sub) ->
C -> yes_no(cerl:is_c_int(C))
end.
--spec is_tuple_type(cerl:cerl(), sub()) -> yes_no_maybe().
-
-is_tuple_type(Var, Sub) ->
- case get_type(Var, Sub) of
- none -> maybe;
- C -> yes_no(cerl:is_c_tuple(C))
- end.
-
yes_no(true) -> yes;
yes_no(false) -> no.
@@ -3177,23 +3098,23 @@ returns_integer(_, _) -> false.
%% update_types(Expr, Pattern, Sub) -> Sub'
%% Update the type database.
--spec update_types(cerl:cerl(), [type_info()], sub()) -> sub().
+-spec update_types(cerl:c_var(), [type_info()], sub()) -> sub().
-update_types(Expr, Pat, #sub{t=Tdb0}=Sub) ->
- Tdb = update_types_1(Expr, Pat, Tdb0),
+update_types(#c_var{name=V}, Pat, #sub{t=Tdb0}=Sub) ->
+ Tdb = update_types_1(V, Pat, Tdb0),
Sub#sub{t=Tdb}.
-update_types_1(#c_var{name=V}, Pat, Types) ->
- update_types_2(V, Pat, Types);
-update_types_1(_, _, Types) -> Types.
-
-update_types_2(V, [#c_tuple{}=P], Types) ->
+update_types_1(V, [#c_tuple{}=P], Types) ->
Types#{V=>P};
-update_types_2(V, [#c_literal{val=Bool}], Types) when is_boolean(Bool) ->
+update_types_1(V, [#c_literal{val=Bool}], Types) when is_boolean(Bool) ->
Types#{V=>bool};
-update_types_2(V, [Type], Types) when is_atom(Type) ->
+update_types_1(V, [#c_fun{vars=Vars}], Types) ->
+ Types#{V=>{'fun',length(Vars)}};
+update_types_1(V, [#c_var{name={_,Arity}}], Types) ->
+ Types#{V=>{'fun',Arity}};
+update_types_1(V, [Type], Types) when is_atom(Type) ->
Types#{V=>Type};
-update_types_2(_, _, Types) -> Types.
+update_types_1(_, _, Types) -> Types.
%% kill_types(V, Tdb) -> Tdb'
%% Kill any entries that references the variable,
@@ -3209,6 +3130,8 @@ kill_types2(V, [{_,#c_tuple{}=Tuple}=Entry|Tdb]) ->
false -> [Entry|kill_types2(V, Tdb)];
true -> kill_types2(V, Tdb)
end;
+kill_types2(V, [{_, {'fun',_}}=Entry|Tdb]) ->
+ [Entry|kill_types2(V, Tdb)];
kill_types2(V, [{_,Atom}=Entry|Tdb]) when is_atom(Atom) ->
[Entry|kill_types2(V, Tdb)];
kill_types2(_, []) -> [].
diff --git a/lib/compiler/src/sys_core_fold_lists.erl b/lib/compiler/src/sys_core_fold_lists.erl
index 9867fab46a..e93b435011 100644
--- a/lib/compiler/src/sys_core_fold_lists.erl
+++ b/lib/compiler/src/sys_core_fold_lists.erl
@@ -37,22 +37,27 @@ call(#c_call{anno=Anno}, lists, all, [Arg1,Arg2]) ->
Xs = #c_var{name='Xs'},
X = #c_var{name='X'},
Err1 = #c_tuple{es=[#c_literal{val='case_clause'}, X]},
- CC1 = #c_clause{pats=[#c_literal{val=true}], guard=#c_literal{val=true},
+ CC1 = #c_clause{anno=Anno,
+ pats=[#c_literal{val=true}], guard=#c_literal{val=true},
body=#c_apply{anno=Anno, op=Loop, args=[Xs]}},
- CC2 = #c_clause{pats=[#c_literal{val=false}], guard=#c_literal{val=true},
+ CC2 = #c_clause{anno=Anno,
+ pats=[#c_literal{val=false}], guard=#c_literal{val=true},
body=#c_literal{val=false}},
- CC3 = #c_clause{pats=[X], guard=#c_literal{val=true},
+ CC3 = #c_clause{anno=Anno,
+ pats=[X], guard=#c_literal{val=true},
body=match_fail(Anno, Err1)},
- C1 = #c_clause{pats=[#c_cons{hd=X, tl=Xs}], guard=#c_literal{val=true},
+ C1 = #c_clause{anno=Anno,
+ pats=[#c_cons{hd=X, tl=Xs}], guard=#c_literal{val=true},
body=#c_case{arg=#c_apply{anno=Anno, op=F, args=[X]},
clauses = [CC1, CC2, CC3]}},
- C2 = #c_clause{pats=[#c_literal{val=[]}],
+ C2 = #c_clause{anno=Anno,
+ pats=[#c_literal{val=[]}],
guard=#c_call{module=#c_literal{val=erlang},
name=#c_literal{val=is_function},
args=[F, #c_literal{val=1}]},
body=#c_literal{val=true}},
Err2 = #c_tuple{es=[#c_literal{val='function_clause'}, F, Xs]},
- C3 = #c_clause{pats=[Xs], guard=#c_literal{val=true},
+ C3 = #c_clause{anno=Anno, pats=[Xs], guard=#c_literal{val=true},
body=match_fail([{function_name,{'lists^all',1}}|Anno], Err2)},
Fun = #c_fun{vars=[Xs],
body=#c_case{arg=Xs, clauses=[C1, C2, C3]}},
@@ -66,16 +71,21 @@ call(#c_call{anno=Anno}, lists, any, [Arg1,Arg2]) ->
Xs = #c_var{name='Xs'},
X = #c_var{name='X'},
Err1 = #c_tuple{es=[#c_literal{val='case_clause'}, X]},
- CC1 = #c_clause{pats=[#c_literal{val=true}], guard=#c_literal{val=true},
+ CC1 = #c_clause{anno=Anno,
+ pats=[#c_literal{val=true}], guard=#c_literal{val=true},
body=#c_literal{val=true}},
- CC2 = #c_clause{pats=[#c_literal{val=false}], guard=#c_literal{val=true},
+ CC2 = #c_clause{anno=Anno,
+ pats=[#c_literal{val=false}], guard=#c_literal{val=true},
body=#c_apply{anno=Anno, op=Loop, args=[Xs]}},
- CC3 = #c_clause{pats=[X], guard=#c_literal{val=true},
+ CC3 = #c_clause{anno=Anno,
+ pats=[X], guard=#c_literal{val=true},
body=match_fail(Anno, Err1)},
- C1 = #c_clause{pats=[#c_cons{hd=X, tl=Xs}], guard=#c_literal{val=true},
+ C1 = #c_clause{anno=Anno,
+ pats=[#c_cons{hd=X, tl=Xs}], guard=#c_literal{val=true},
body=#c_case{arg=#c_apply{anno=Anno, op=F, args=[X]},
clauses = [CC1, CC2, CC3]}},
- C2 = #c_clause{pats=[#c_literal{val=[]}],
+ C2 = #c_clause{anno=Anno,
+ pats=[#c_literal{val=[]}],
guard=#c_call{module=#c_literal{val=erlang},
name=#c_literal{val=is_function},
args=[F, #c_literal{val=1}]},
@@ -94,16 +104,17 @@ call(#c_call{anno=Anno}, lists, foreach, [Arg1,Arg2]) ->
F = #c_var{name='F'},
Xs = #c_var{name='Xs'},
X = #c_var{name='X'},
- C1 = #c_clause{pats=[#c_cons{hd=X, tl=Xs}], guard=#c_literal{val=true},
+ C1 = #c_clause{anno=Anno,
+ pats=[#c_cons{hd=X, tl=Xs}], guard=#c_literal{val=true},
body=#c_seq{arg=#c_apply{anno=Anno, op=F, args=[X]},
body=#c_apply{anno=Anno, op=Loop, args=[Xs]}}},
- C2 = #c_clause{pats=[#c_literal{val=[]}],
+ C2 = #c_clause{anno=Anno, pats=[#c_literal{val=[]}],
guard=#c_call{module=#c_literal{val=erlang},
name=#c_literal{val=is_function},
args=[F, #c_literal{val=1}]},
body=#c_literal{val=ok}},
Err = #c_tuple{es=[#c_literal{val='function_clause'}, F, Xs]},
- C3 = #c_clause{pats=[Xs], guard=#c_literal{val=true},
+ C3 = #c_clause{anno=Anno, pats=[Xs], guard=#c_literal{val=true},
body=match_fail([{function_name,{'lists^foreach',1}}|Anno], Err)},
Fun = #c_fun{vars=[Xs],
body=#c_case{arg=Xs, clauses=[C1, C2, C3]}},
@@ -117,7 +128,8 @@ call(#c_call{anno=Anno}, lists, map, [Arg1,Arg2]) ->
Xs = #c_var{name='Xs'},
X = #c_var{name='X'},
H = #c_var{name='H'},
- C1 = #c_clause{pats=[#c_cons{hd=X, tl=Xs}], guard=#c_literal{val=true},
+ C1 = #c_clause{anno=Anno,
+ pats=[#c_cons{hd=X, tl=Xs}], guard=#c_literal{val=true},
body=#c_let{vars=[H], arg=#c_apply{anno=Anno,
op=F,
args=[X]},
@@ -126,7 +138,7 @@ call(#c_call{anno=Anno}, lists, map, [Arg1,Arg2]) ->
tl=#c_apply{anno=Anno,
op=Loop,
args=[Xs]}}}},
- C2 = #c_clause{pats=[#c_literal{val=[]}],
+ C2 = #c_clause{anno=Anno, pats=[#c_literal{val=[]}],
guard=#c_call{module=#c_literal{val=erlang},
name=#c_literal{val=is_function},
args=[F, #c_literal{val=1}]},
@@ -146,7 +158,8 @@ call(#c_call{anno=Anno}, lists, flatmap, [Arg1,Arg2]) ->
Xs = #c_var{name='Xs'},
X = #c_var{name='X'},
H = #c_var{name='H'},
- C1 = #c_clause{pats=[#c_cons{hd=X, tl=Xs}], guard=#c_literal{val=true},
+ C1 = #c_clause{anno=Anno,
+ pats=[#c_cons{hd=X, tl=Xs}], guard=#c_literal{val=true},
body=#c_let{vars=[H],
arg=#c_apply{anno=Anno, op=F, args=[X]},
body=#c_call{anno=[compiler_generated|Anno],
@@ -156,13 +169,13 @@ call(#c_call{anno=Anno}, lists, flatmap, [Arg1,Arg2]) ->
#c_apply{anno=Anno,
op=Loop,
args=[Xs]}]}}},
- C2 = #c_clause{pats=[#c_literal{val=[]}],
+ C2 = #c_clause{anno=Anno, pats=[#c_literal{val=[]}],
guard=#c_call{module=#c_literal{val=erlang},
name=#c_literal{val=is_function},
args=[F, #c_literal{val=1}]},
body=#c_literal{val=[]}},
Err = #c_tuple{es=[#c_literal{val='function_clause'}, F, Xs]},
- C3 = #c_clause{pats=[Xs], guard=#c_literal{val=true},
+ C3 = #c_clause{anno=Anno, pats=[Xs], guard=#c_literal{val=true},
body=match_fail([{function_name,{'lists^flatmap',1}}|Anno], Err)},
Fun = #c_fun{vars=[Xs],
body=#c_case{arg=Xs, clauses=[C1, C2, C3]}},
@@ -177,11 +190,13 @@ call(#c_call{anno=Anno}, lists, filter, [Arg1,Arg2]) ->
X = #c_var{name='X'},
B = #c_var{name='B'},
Err1 = #c_tuple{es=[#c_literal{val='case_clause'}, X]},
- CC1 = #c_clause{pats=[#c_literal{val=true}], guard=#c_literal{val=true},
+ CC1 = #c_clause{anno=Anno,
+ pats=[#c_literal{val=true}], guard=#c_literal{val=true},
body=#c_cons{anno=[compiler_generated], hd=X, tl=Xs}},
- CC2 = #c_clause{pats=[#c_literal{val=false}], guard=#c_literal{val=true},
+ CC2 = #c_clause{anno=Anno,
+ pats=[#c_literal{val=false}], guard=#c_literal{val=true},
body=Xs},
- CC3 = #c_clause{pats=[X], guard=#c_literal{val=true},
+ CC3 = #c_clause{anno=Anno, pats=[X], guard=#c_literal{val=true},
body=match_fail(Anno, Err1)},
Case = #c_case{arg=B, clauses = [CC1, CC2, CC3]},
C1 = #c_clause{pats=[#c_cons{hd=X, tl=Xs}], guard=#c_literal{val=true},
@@ -192,13 +207,15 @@ call(#c_call{anno=Anno}, lists, filter, [Arg1,Arg2]) ->
op=Loop,
args=[Xs]},
body=Case}}},
- C2 = #c_clause{pats=[#c_literal{val=[]}],
+ C2 = #c_clause{anno=Anno,
+ pats=[#c_literal{val=[]}],
guard=#c_call{module=#c_literal{val=erlang},
name=#c_literal{val=is_function},
args=[F, #c_literal{val=1}]},
body=#c_literal{val=[]}},
Err2 = #c_tuple{es=[#c_literal{val='function_clause'}, F, Xs]},
- C3 = #c_clause{pats=[Xs], guard=#c_literal{val=true},
+ C3 = #c_clause{anno=Anno,
+ pats=[Xs], guard=#c_literal{val=true},
body=match_fail([{function_name,{'lists^filter',1}}|Anno], Err2)},
Fun = #c_fun{vars=[Xs],
body=#c_case{arg=Xs, clauses=[C1, C2, C3]}},
@@ -212,19 +229,20 @@ call(#c_call{anno=Anno}, lists, foldl, [Arg1,Arg2,Arg3]) ->
Xs = #c_var{name='Xs'},
X = #c_var{name='X'},
A = #c_var{name='A'},
- C1 = #c_clause{pats=[#c_cons{hd=X, tl=Xs}], guard=#c_literal{val=true},
+ C1 = #c_clause{anno=Anno,
+ pats=[#c_cons{hd=X, tl=Xs}], guard=#c_literal{val=true},
body=#c_apply{anno=Anno,
op=Loop,
args=[Xs, #c_apply{anno=Anno,
op=F,
args=[X, A]}]}},
- C2 = #c_clause{pats=[#c_literal{val=[]}],
+ C2 = #c_clause{anno=Anno, pats=[#c_literal{val=[]}],
guard=#c_call{module=#c_literal{val=erlang},
name=#c_literal{val=is_function},
args=[F, #c_literal{val=2}]},
body=A},
Err = #c_tuple{es=[#c_literal{val='function_clause'}, F, A, Xs]},
- C3 = #c_clause{pats=[Xs], guard=#c_literal{val=true},
+ C3 = #c_clause{anno=Anno, pats=[Xs], guard=#c_literal{val=true},
body=match_fail([{function_name,{'lists^foldl',2}}|Anno], Err)},
Fun = #c_fun{vars=[Xs, A],
body=#c_case{arg=Xs, clauses=[C1, C2, C3]}},
@@ -238,19 +256,20 @@ call(#c_call{anno=Anno}, lists, foldr, [Arg1,Arg2,Arg3]) ->
Xs = #c_var{name='Xs'},
X = #c_var{name='X'},
A = #c_var{name='A'},
- C1 = #c_clause{pats=[#c_cons{hd=X, tl=Xs}], guard=#c_literal{val=true},
+ C1 = #c_clause{anno=Anno,
+ pats=[#c_cons{hd=X, tl=Xs}], guard=#c_literal{val=true},
body=#c_apply{anno=Anno,
op=F,
args=[X, #c_apply{anno=Anno,
op=Loop,
args=[Xs, A]}]}},
- C2 = #c_clause{pats=[#c_literal{val=[]}],
+ C2 = #c_clause{anno=Anno, pats=[#c_literal{val=[]}],
guard=#c_call{module=#c_literal{val=erlang},
name=#c_literal{val=is_function},
args=[F, #c_literal{val=2}]},
body=A},
Err = #c_tuple{es=[#c_literal{val='function_clause'}, F, A, Xs]},
- C3 = #c_clause{pats=[Xs], guard=#c_literal{val=true},
+ C3 = #c_clause{anno=Anno, pats=[Xs], guard=#c_literal{val=true},
body=match_fail([{function_name,{'lists^foldr',2}}|Anno], Err)},
Fun = #c_fun{vars=[Xs, A],
body=#c_case{arg=Xs, clauses=[C1, C2, C3]}},
@@ -266,13 +285,14 @@ call(#c_call{anno=Anno}, lists, mapfoldl, [Arg1,Arg2,Arg3]) ->
Avar = #c_var{name='A'},
Match =
fun (A, P, E) ->
- C1 = #c_clause{pats=[P], guard=#c_literal{val=true}, body=E},
+ C1 = #c_clause{anno=Anno, pats=[P], guard=#c_literal{val=true}, body=E},
Err = #c_tuple{es=[#c_literal{val='badmatch'}, X]},
- C2 = #c_clause{pats=[X], guard=#c_literal{val=true},
+ C2 = #c_clause{anno=Anno, pats=[X], guard=#c_literal{val=true},
body=match_fail(Anno, Err)},
#c_case{arg=A, clauses=[C1, C2]}
end,
- C1 = #c_clause{pats=[#c_cons{hd=X, tl=Xs}], guard=#c_literal{val=true},
+ C1 = #c_clause{anno=Anno,
+ pats=[#c_cons{hd=X, tl=Xs}], guard=#c_literal{val=true},
body=Match(#c_apply{anno=Anno, op=F, args=[X, Avar]},
#c_tuple{es=[X, Avar]},
%%% Tuple passing version
@@ -292,7 +312,7 @@ call(#c_call{anno=Anno}, lists, mapfoldl, [Arg1,Arg2,Arg3]) ->
%%% body=#c_values{es=[#c_cons{hd=X, tl=Xs},
%%% A]}}
)},
- C2 = #c_clause{pats=[#c_literal{val=[]}],
+ C2 = #c_clause{anno=Anno, pats=[#c_literal{val=[]}],
guard=#c_call{module=#c_literal{val=erlang},
name=#c_literal{val=is_function},
args=[F, #c_literal{val=2}]},
@@ -302,7 +322,7 @@ call(#c_call{anno=Anno}, lists, mapfoldl, [Arg1,Arg2,Arg3]) ->
%%% Multiple-value version
%%% body=#c_values{es=[#c_literal{val=[]}, A]}},
Err = #c_tuple{es=[#c_literal{val='function_clause'}, F, Avar, Xs]},
- C3 = #c_clause{pats=[Xs], guard=#c_literal{val=true},
+ C3 = #c_clause{anno=Anno, pats=[Xs], guard=#c_literal{val=true},
body=match_fail([{function_name,{'lists^mapfoldl',2}}|Anno], Err)},
Fun = #c_fun{vars=[Xs, Avar],
body=#c_case{arg=Xs, clauses=[C1, C2, C3]}},
@@ -326,13 +346,13 @@ call(#c_call{anno=Anno}, lists, mapfoldr, [Arg1,Arg2,Arg3]) ->
Avar = #c_var{name='A'},
Match =
fun (A, P, E) ->
- C1 = #c_clause{pats=[P], guard=#c_literal{val=true}, body=E},
+ C1 = #c_clause{anno=Anno, pats=[P], guard=#c_literal{val=true}, body=E},
Err = #c_tuple{es=[#c_literal{val='badmatch'}, X]},
- C2 = #c_clause{pats=[X], guard=#c_literal{val=true},
+ C2 = #c_clause{anno=Anno, pats=[X], guard=#c_literal{val=true},
body=match_fail(Anno, Err)},
#c_case{arg=A, clauses=[C1, C2]}
end,
- C1 = #c_clause{pats=[#c_cons{hd=X, tl=Xs}], guard=#c_literal{val=true},
+ C1 = #c_clause{anno=Anno, pats=[#c_cons{hd=X, tl=Xs}], guard=#c_literal{val=true},
%%% Tuple passing version
body=Match(#c_apply{anno=Anno,
op=Loop,
@@ -352,7 +372,8 @@ call(#c_call{anno=Anno}, lists, mapfoldr, [Arg1,Arg2,Arg3]) ->
%%% #c_values{es=[#c_cons{hd=X, tl=Xs},
%%% A]})}
},
- C2 = #c_clause{pats=[#c_literal{val=[]}],
+ C2 = #c_clause{anno=Anno,
+ pats=[#c_literal{val=[]}],
guard=#c_call{module=#c_literal{val=erlang},
name=#c_literal{val=is_function},
args=[F, #c_literal{val=2}]},
@@ -362,7 +383,7 @@ call(#c_call{anno=Anno}, lists, mapfoldr, [Arg1,Arg2,Arg3]) ->
%%% Multiple-value version
%%% body=#c_values{es=[#c_literal{val=[]}, A]}},
Err = #c_tuple{es=[#c_literal{val='function_clause'}, F, Avar, Xs]},
- C3 = #c_clause{pats=[Xs], guard=#c_literal{val=true},
+ C3 = #c_clause{anno=Anno, pats=[Xs], guard=#c_literal{val=true},
body=match_fail([{function_name,{'lists^mapfoldr',2}}|Anno], Err)},
Fun = #c_fun{vars=[Xs, Avar],
body=#c_case{arg=Xs, clauses=[C1, C2, C3]}},
diff --git a/lib/compiler/src/sys_core_inline.erl b/lib/compiler/src/sys_core_inline.erl
index 5a6cc45e4a..3380e3f1bd 100644
--- a/lib/compiler/src/sys_core_inline.erl
+++ b/lib/compiler/src/sys_core_inline.erl
@@ -195,6 +195,9 @@ kill_id_anns(Body) ->
cerl_trees:map(fun(#c_fun{anno=A0}=CFun) ->
A = kill_id_anns_1(A0),
CFun#c_fun{anno=A};
+ (#c_var{anno=A0}=Var) ->
+ A = kill_id_anns_1(A0),
+ Var#c_var{anno=A};
(Expr) ->
%% Mark everything as compiler generated to
%% suppress bogus warnings.
diff --git a/lib/compiler/src/v3_codegen.erl b/lib/compiler/src/v3_codegen.erl
deleted file mode 100644
index d7a7778740..0000000000
--- a/lib/compiler/src/v3_codegen.erl
+++ /dev/null
@@ -1,2791 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1999-2018. All 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 : Code generator for Beam.
-
--module(v3_codegen).
-
-%% The main interface.
--export([module/2]).
-
--import(lists, [member/2,keymember/3,keysort/2,keydelete/3,
- append/1,flatmap/2,filter/2,foldl/3,foldr/3,mapfoldl/3,
- sort/1,reverse/1,reverse/2,map/2]).
--import(ordsets, [add_element/2,intersection/2,union/2]).
-
--include("v3_kernel.hrl").
-
-%% These are not defined in v3_kernel.hrl.
-get_kanno(Kthing) -> element(2, Kthing).
-set_kanno(Kthing, Anno) -> setelement(2, Kthing, Anno).
-
-%% Main codegen structure.
--record(cg, {lcount=1, %Label counter
- bfail, %Fail label for BIFs
- break, %Break label
- recv, %Receive label
- is_top_block, %Boolean: top block or not
- functable=#{}, %Map of local functions: {Name,Arity}=>Label
- in_catch=false, %Inside a catch or not.
- need_frame, %Need a stack frame.
- ultimate_failure, %Label for ultimate match failure.
- ctx %Match context.
- }).
-
-%% Stack/register state record.
--record(sr, {reg=[], %Register table
- stk=[], %Stack table
- res=[]}). %Registers to reserve
-
-%% Internal records.
--record(cg_need_heap, {anno=[] :: term(),
- h=0 :: integer()}).
--record(cg_block, {anno=[] :: term(),
- es=[] :: [term()]}).
-
--type vdb_entry() :: {atom(),non_neg_integer(),non_neg_integer()}.
-
--record(l, {i=0 :: non_neg_integer(), %Op number
- vdb=[] :: [vdb_entry()], %Variable database
- a=[] :: [term()]}). %Core annotation
-
--spec module(#k_mdef{}, [compile:option()]) -> {'ok',beam_asm:module_code()}.
-
-module(#k_mdef{name=Mod,exports=Es,attributes=Attr,body=Forms}, _Opts) ->
- {Asm,St} = functions(Forms, {atom,Mod}),
- {ok,{Mod,Es,Attr,Asm,St#cg.lcount}}.
-
-functions(Forms, AtomMod) ->
- mapfoldl(fun (F, St) -> function(F, AtomMod, St) end, #cg{lcount=1}, Forms).
-
-function(#k_fdef{anno=#k{a=Anno},func=Name,arity=Arity,
- vars=As,body=Kb}, AtomMod, St0) ->
- try
- #k_match{} = Kb, %Assertion.
-
- %% Annotate kernel records with variable usage.
- Vdb0 = init_vars(As),
- {Body,_,Vdb} = body(Kb, 1, Vdb0),
-
- %% Generate the BEAM assembly code.
- {Asm,EntryLabel,St} = cg_fun(Body, As, Vdb, AtomMod,
- {Name,Arity}, Anno, St0),
- Func = {function,Name,Arity,EntryLabel,Asm},
- {Func,St}
- catch
- Class:Error:Stack ->
- io:fwrite("Function: ~w/~w\n", [Name,Arity]),
- erlang:raise(Class, Error, Stack)
- end.
-
-%% This pass creates beam format annotated with variable lifetime
-%% information. Each thing is given an index and for each variable we
-%% store the first and last index for its occurrence. The variable
-%% database, VDB, attached to each thing is only relevant internally
-%% for that thing.
-%%
-%% For nested things like matches the numbering continues locally and
-%% the VDB for that thing refers to the variable usage within that
-%% thing. Variables which live through a such a thing are internally
-%% given a very large last index. Internally the indexes continue
-%% after the index of that thing. This creates no problems as the
-%% internal variable info never escapes and externally we only see
-%% variable which are alive both before or after.
-%%
-%% This means that variables never "escape" from a thing and the only
-%% way to get values from a thing is to "return" them, with 'break' or
-%% 'return'. Externally these values become the return values of the
-%% thing. This is no real limitation as most nested things have
-%% multiple threads so working out a common best variable usage is
-%% difficult.
-
-%% body(Kbody, I, Vdb) -> {[Expr],MaxI,Vdb}.
-%% Handle a body.
-
-body(#k_seq{arg=Ke,body=Kb}, I, Vdb0) ->
- %%ok = io:fwrite("life ~w:~p~n", [?LINE,{Ke,I,Vdb0}]),
- A = get_kanno(Ke),
- Vdb1 = use_vars(union(A#k.us, A#k.ns), I, Vdb0),
- {Es,MaxI,Vdb2} = body(Kb, I+1, Vdb1),
- E = expr(Ke, I, Vdb2),
- {[E|Es],MaxI,Vdb2};
-body(Ke, I, Vdb0) ->
- %%ok = io:fwrite("life ~w:~p~n", [?LINE,{Ke,I,Vdb0}]),
- A = get_kanno(Ke),
- Vdb1 = use_vars(union(A#k.us, A#k.ns), I, Vdb0),
- E = expr(Ke, I, Vdb1),
- {[E],I,Vdb1}.
-
-%% expr(Kexpr, I, Vdb) -> Expr.
-
-expr(#k_test{anno=A}=Test, I, _Vdb) ->
- Test#k_test{anno=#l{i=I,a=A#k.a}};
-expr(#k_call{anno=A}=Call, I, _Vdb) ->
- Call#k_call{anno=#l{i=I,a=A#k.a}};
-expr(#k_enter{anno=A}=Enter, I, _Vdb) ->
- Enter#k_enter{anno=#l{i=I,a=A#k.a}};
-expr(#k_bif{anno=A}=Bif, I, _Vdb) ->
- Bif#k_bif{anno=#l{i=I,a=A#k.a}};
-expr(#k_match{anno=A,body=Kb,ret=Rs}, I, Vdb) ->
- %% Work out imported variables which need to be locked.
- Mdb = vdb_sub(I, I+1, Vdb),
- M = match(Kb, A#k.us, I+1, Mdb),
- L = #l{i=I,vdb=use_vars(A#k.us, I+1, Mdb),a=A#k.a},
- #k_match{anno=L,body=M,ret=Rs};
-expr(#k_guard_match{anno=A,body=Kb,ret=Rs}, I, Vdb) ->
- %% Work out imported variables which need to be locked.
- Mdb = vdb_sub(I, I+1, Vdb),
- M = match(Kb, A#k.us, I+1, Mdb),
- L = #l{i=I,vdb=use_vars(A#k.us, I+1, Mdb),a=A#k.a},
- #k_guard_match{anno=L,body=M,ret=Rs};
-expr(#k_protected{}=Protected, I, Vdb) ->
- protected(Protected, I, Vdb);
-expr(#k_try{anno=A,arg=Ka,vars=Vs,body=Kb,evars=Evs,handler=Kh}=Try, I, Vdb) ->
- %% Lock variables that are alive before the catch and used afterwards.
- %% Don't lock variables that are only used inside the try.
- Tdb0 = vdb_sub(I, I+1, Vdb),
- %% This is the tricky bit. Lock variables in Arg that are used in
- %% the body and handler. Add try tag 'variable'.
- Ab = get_kanno(Kb),
- Ah = get_kanno(Kh),
- Tdb1 = use_vars(union(Ab#k.us, Ah#k.us), I+3, Tdb0),
- Tdb2 = vdb_sub(I, I+2, Tdb1),
- Vnames = fun (Kvar) -> Kvar#k_var.name end, %Get the variable names
- {Aes,_,Adb} = body(Ka, I+2, add_var({catch_tag,I+1}, I+1, locked, Tdb2)),
- {Bes,_,Bdb} = body(Kb, I+4, new_vars(sort(map(Vnames, Vs)), I+3, Tdb2)),
- {Hes,_,Hdb} = body(Kh, I+4, new_vars(sort(map(Vnames, Evs)), I+3, Tdb2)),
- L = #l{i=I,vdb=Tdb1,a=A#k.a},
- Try#k_try{anno=L,
- arg=#cg_block{es=Aes,anno=#l{i=I+1,vdb=Adb,a=[]}},
- vars=Vs,body=#cg_block{es=Bes,anno=#l{i=I+3,vdb=Bdb,a=[]}},
- evars=Evs,handler=#cg_block{es=Hes,anno=#l{i=I+3,vdb=Hdb,a=[]}}};
-expr(#k_try_enter{anno=A,arg=Ka,vars=Vs,body=Kb,evars=Evs,handler=Kh}, I, Vdb) ->
- %% Lock variables that are alive before the catch and used afterwards.
- %% Don't lock variables that are only used inside the try.
- Tdb0 = vdb_sub(I, I+1, Vdb),
- %% This is the tricky bit. Lock variables in Arg that are used in
- %% the body and handler. Add try tag 'variable'.
- Ab = get_kanno(Kb),
- Ah = get_kanno(Kh),
- Tdb1 = use_vars(union(Ab#k.us, Ah#k.us), I+3, Tdb0),
- Tdb2 = vdb_sub(I, I+2, Tdb1),
- Vnames = fun (Kvar) -> Kvar#k_var.name end, %Get the variable names
- {Aes,_,Adb} = body(Ka, I+2, add_var({catch_tag,I+1}, I+1, 1000000, Tdb2)),
- {Bes,_,Bdb} = body(Kb, I+4, new_vars(sort(map(Vnames, Vs)), I+3, Tdb2)),
- {Hes,_,Hdb} = body(Kh, I+4, new_vars(sort(map(Vnames, Evs)), I+3, Tdb2)),
- L = #l{i=I,vdb=Tdb1,a=A#k.a},
- #k_try_enter{anno=L,
- arg=#cg_block{es=Aes,anno=#l{i=I+1,vdb=Adb,a=[]}},
- vars=Vs,body=#cg_block{es=Bes,anno=#l{i=I+3,vdb=Bdb,a=[]}},
- evars=Evs,handler=#cg_block{es=Hes,anno=#l{i=I+3,vdb=Hdb,a=[]}}};
-expr(#k_catch{anno=A,body=Kb}=Catch, I, Vdb) ->
- %% Lock variables that are alive before the catch and used afterwards.
- %% Don't lock variables that are only used inside the catch.
- %% Add catch tag 'variable'.
- Cdb0 = vdb_sub(I, I+1, Vdb),
- {Es,_,Cdb1} = body(Kb, I+1, add_var({catch_tag,I}, I, locked, Cdb0)),
- L = #l{i=I,vdb=Cdb1,a=A#k.a},
- Catch#k_catch{anno=L,body=#cg_block{es=Es}};
-expr(#k_receive{anno=A,var=V,body=Kb,action=Ka}=Recv, I, Vdb) ->
- %% Work out imported variables which need to be locked.
- Rdb = vdb_sub(I, I+1, Vdb),
- M = match(Kb, add_element(V#k_var.name, A#k.us), I+1,
- new_vars([V#k_var.name], I, Rdb)),
- {Tes,_,Adb} = body(Ka, I+1, Rdb),
- Le = #l{i=I,vdb=use_vars(A#k.us, I+1, Vdb),a=A#k.a},
- Recv#k_receive{anno=Le,body=M,
- action=#cg_block{anno=#l{i=I+1,vdb=Adb,a=[]},es=Tes}};
-expr(#k_receive_accept{anno=A}, I, _Vdb) ->
- #k_receive_accept{anno=#l{i=I,a=A#k.a}};
-expr(#k_receive_next{anno=A}, I, _Vdb) ->
- #k_receive_next{anno=#l{i=I,a=A#k.a}};
-expr(#k_put{anno=A}=Put, I, _Vdb) ->
- Put#k_put{anno=#l{i=I,a=A#k.a}};
-expr(#k_break{anno=A}=Break, I, _Vdb) ->
- Break#k_break{anno=#l{i=I,a=A#k.a}};
-expr(#k_guard_break{anno=A}=Break, I, _Vdb) ->
- Break#k_guard_break{anno=#l{i=I,a=A#k.a}};
-expr(#k_return{anno=A}=Ret, I, _Vdb) ->
- Ret#k_return{anno=#l{i=I,a=A#k.a}}.
-
-%% protected(Kprotected, I, Vdb) -> Protected.
-%% Only used in guards.
-
-protected(#k_protected{anno=A,arg=Ts}=Prot, I, Vdb) ->
- %% Lock variables that are alive before try and used afterwards.
- %% Don't lock variables that are only used inside the protected
- %% expression.
- Pdb0 = vdb_sub(I, I+1, Vdb),
- {T,MaxI,Pdb1} = body(Ts, I+1, Pdb0),
- Pdb2 = use_vars(A#k.ns, MaxI+1, Pdb1), %Save "return" values
- Prot#k_protected{arg=T,anno=#l{i=I,a=A#k.a,vdb=Pdb2}}.
-
-%% match(Kexpr, [LockVar], I, Vdb) -> Expr.
-%% Convert match tree to old format.
-
-match(#k_alt{anno=A,first=Kf,then=Kt}, Ls, I, Vdb0) ->
- Vdb1 = use_vars(union(A#k.us, Ls), I, Vdb0),
- F = match(Kf, Ls, I+1, Vdb1),
- T = match(Kt, Ls, I+1, Vdb1),
- #k_alt{anno=[],first=F,then=T};
-match(#k_select{anno=A,types=Kts}=Select, Ls, I, Vdb0) ->
- Vdb1 = use_vars(union(A#k.us, Ls), I, Vdb0),
- Ts = [type_clause(Tc, Ls, I+1, Vdb1) || Tc <- Kts],
- Select#k_select{anno=[],types=Ts};
-match(#k_guard{anno=A,clauses=Kcs}, Ls, I, Vdb0) ->
- Vdb1 = use_vars(union(A#k.us, Ls), I, Vdb0),
- Cs = [guard_clause(G, Ls, I+1, Vdb1) || G <- Kcs],
- #k_guard{anno=[],clauses=Cs};
-match(Other, Ls, I, Vdb0) ->
- Vdb1 = use_vars(Ls, I, Vdb0),
- {B,_,Vdb2} = body(Other, I+1, Vdb1),
- Le = #l{i=I,vdb=Vdb2,a=[]},
- #cg_block{anno=Le,es=B}.
-
-type_clause(#k_type_clause{anno=A,type=T,values=Kvs}, Ls, I, Vdb0) ->
- %%ok = io:format("life ~w: ~p~n", [?LINE,{T,Kvs}]),
- Vdb1 = use_vars(union(A#k.us, Ls), I+1, Vdb0),
- Vs = [val_clause(Vc, Ls, I+1, Vdb1) || Vc <- Kvs],
- #k_type_clause{anno=[],type=T,values=Vs}.
-
-val_clause(#k_val_clause{anno=A,val=V,body=Kb}, Ls0, I, Vdb0) ->
- New = (get_kanno(V))#k.ns,
- Bus = (get_kanno(Kb))#k.us,
- %%ok = io:format("Ls0 = ~p, Used=~p\n New=~p, Bus=~p\n", [Ls0,Used,New,Bus]),
- Ls1 = union(intersection(New, Bus), Ls0), %Lock for safety
- Vdb1 = use_vars(union(A#k.us, Ls1), I+1, new_vars(New, I, Vdb0)),
- B = match(Kb, Ls1, I+1, Vdb1),
- Le = #l{i=I,vdb=use_vars(Bus, I+1, Vdb1),a=A#k.a},
- #k_val_clause{anno=Le,val=V,body=B}.
-
-guard_clause(#k_guard_clause{anno=A,guard=Kg,body=Kb}, Ls, I, Vdb0) ->
- Vdb1 = use_vars(union(A#k.us, Ls), I+2, Vdb0),
- Gdb = vdb_sub(I+1, I+2, Vdb1),
- G = protected(Kg, I+1, Gdb),
- B = match(Kb, Ls, I+2, Vdb1),
- Le = #l{i=I,vdb=use_vars((get_kanno(Kg))#k.us, I+2, Vdb1),a=A#k.a},
- #k_guard_clause{anno=Le,guard=G,body=B}.
-
-
-%% Here follows the code generator pass.
-%%
-%% The following assumptions have been made:
-%%
-%% 1. Matches, i.e. things with {match,M,Ret} wrappers, only return
-%% values; no variables are exported. If the match would have returned
-%% extra variables then these have been transformed to multiple return
-%% values.
-%%
-%% 2. All BIF's called in guards are gc-safe so there is no need to
-%% put thing on the stack in the guard. While this would in principle
-%% work it would be difficult to keep track of the stack depth when
-%% trimming.
-%%
-%% The code generation uses variable lifetime information added by
-%% the previous pass to save variables, allocate registers and
-%% move registers to the stack when necessary.
-%%
-%% We try to use a consistent variable name scheme throughout. The
-%% StackReg record is always called Bef,Int<n>,Aft.
-
-%% cg_fun([Lkexpr], [HeadVar], Vdb, State) -> {[Ainstr],State}
-
-cg_fun(Les, Hvs, Vdb, AtomMod, NameArity, Anno, St0) ->
- {Fi,St1} = new_label(St0), %FuncInfo label
- {Fl,St2} = local_func_label(NameArity, St1),
-
- %%
- %% The pattern matching compiler (in v3_kernel) no longer
- %% provides its own catch-all clause, because the
- %% call to erlang:exit/1 caused problem when cases were
- %% used in guards. Therefore, there may be tests that
- %% cannot fail (providing that there is not a bug in a
- %% previous optimzation pass), but still need to provide
- %% a label (there are instructions, such as is_tuple/2,
- %% that do not allow {f,0}).
- %%
- %% We will generate an ultimate failure label and put it
- %% at the end of function, followed by an 'if_end' instruction.
- %% Note that and 'if_end' instruction does not need any
- %% live x registers, so it will always be safe to jump to
- %% it. (We never ever expect the jump to be taken, and in
- %% most functions there will never be any references to
- %% the label in the first place.)
- %%
-
- {UltimateMatchFail,St3} = new_label(St2),
-
- %% Create initial stack/register state, clear unused arguments.
- Bef = clear_dead(#sr{reg=foldl(fun (#k_var{name=V}, Reg) ->
- put_reg(V, Reg)
- end, [], Hvs),
- stk=[]}, 0, Vdb),
- {B,_Aft,St} = cg_list(Les, Vdb, Bef,
- St3#cg{bfail=0,
- ultimate_failure=UltimateMatchFail,
- is_top_block=true}),
- {Name,Arity} = NameArity,
- Asm = [{label,Fi},line(Anno),{func_info,AtomMod,{atom,Name},Arity},
- {label,Fl}|B++[{label,UltimateMatchFail},if_end]],
- {Asm,Fl,St}.
-
-%% cg(Lkexpr, Vdb, StackReg, State) -> {[Ainstr],StackReg,State}.
-%% Generate code for a kexpr.
-
-cg(#cg_block{anno=Le,es=Es}, Vdb, Bef, St) ->
- block_cg(Es, Le, Vdb, Bef, St);
-cg(#k_match{anno=Le,body=M,ret=Rs}, Vdb, Bef, St) ->
- match_cg(M, Rs, Le, Vdb, Bef, St);
-cg(#k_guard_match{anno=Le,body=M,ret=Rs}, Vdb, Bef, St) ->
- guard_match_cg(M, Rs, Le, Vdb, Bef, St);
-cg(#k_call{anno=Le,op=Func,args=As,ret=Rs}, Vdb, Bef, St) ->
- call_cg(Func, As, Rs, Le, Vdb, Bef, St);
-cg(#k_enter{anno=Le,op=Func,args=As}, Vdb, Bef, St) ->
- enter_cg(Func, As, Le, Vdb, Bef, St);
-cg(#k_bif{anno=Le}=Bif, Vdb, Bef, St) ->
- bif_cg(Bif, Le, Vdb, Bef, St);
-cg(#k_receive{anno=Le,timeout=Te,var=Rvar,body=Rm,action=Tes,ret=Rs},
- Vdb, Bef, St) ->
- recv_loop_cg(Te, Rvar, Rm, Tes, Rs, Le, Vdb, Bef, St);
-cg(#k_receive_next{anno=Le}, Vdb, Bef, St) ->
- recv_next_cg(Le, Vdb, Bef, St);
-cg(#k_receive_accept{}, _Vdb, Bef, St) ->
- {[remove_message],Bef,St};
-cg(#k_try{anno=Le,arg=Ta,vars=Vs,body=Tb,evars=Evs,handler=Th,ret=Rs},
- Vdb, Bef, St) ->
- try_cg(Ta, Vs, Tb, Evs, Th, Rs, Le, Vdb, Bef, St);
-cg(#k_try_enter{anno=Le,arg=Ta,vars=Vs,body=Tb,evars=Evs,handler=Th},
- Vdb, Bef, St) ->
- try_enter_cg(Ta, Vs, Tb, Evs, Th, Le, Vdb, Bef, St);
-cg(#k_catch{anno=Le,body=Cb,ret=[R]}, Vdb, Bef, St) ->
- catch_cg(Cb, R, Le, Vdb, Bef, St);
-cg(#k_put{anno=Le,arg=Con,ret=Var}, Vdb, Bef, St) ->
- put_cg(Var, Con, Le, Vdb, Bef, St);
-cg(#k_return{anno=Le,args=Rs}, Vdb, Bef, St) ->
- return_cg(Rs, Le, Vdb, Bef, St);
-cg(#k_break{anno=Le,args=Bs}, Vdb, Bef, St) ->
- break_cg(Bs, Le, Vdb, Bef, St);
-cg(#k_guard_break{anno=Le,args=Bs}, Vdb, Bef, St) ->
- guard_break_cg(Bs, Le, Vdb, Bef, St);
-cg(#cg_need_heap{h=H}, _Vdb, Bef, St) ->
- {[{test_heap,H,max_reg(Bef#sr.reg)}],Bef,St}.
-
-%% cg_list([Kexpr], FirstI, Vdb, StackReg, St) -> {[Ainstr],StackReg,St}.
-
-cg_list(Kes, Vdb, Bef, St0) ->
- {Keis,{Aft,St1}} =
- flatmapfoldl(fun (Ke, {Inta,Sta}) ->
- {Keis,Intb,Stb} = cg(Ke, Vdb, Inta, Sta),
- {Keis,{Intb,Stb}}
- end, {Bef,St0}, need_heap(Kes)),
- {Keis,Aft,St1}.
-
-%% need_heap([Lkexpr], I, St) -> [Lkexpr].
-%% Insert need_heap instructions in Kexpr list. Try to be smart and
-%% collect them together as much as possible.
-
-need_heap(Kes0) ->
- {Kes,H} = need_heap_0(reverse(Kes0), 0, []),
-
- %% Prepend need_heap if necessary.
- need_heap_need(H) ++ Kes.
-
-need_heap_0([Ke|Kes], H0, Acc) ->
- {Ns,H} = need_heap_1(Ke, H0),
- need_heap_0(Kes, H, [Ke|Ns]++Acc);
-need_heap_0([], H, Acc) ->
- {Acc,H}.
-
-need_heap_1(#k_put{arg=#k_binary{}}, H) ->
- {need_heap_need(H),0};
-need_heap_1(#k_put{arg=#k_map{}}, H) ->
- {need_heap_need(H),0};
-need_heap_1(#k_put{arg=Val}, H) ->
- %% Just pass through adding to needed heap.
- {[],H + case Val of
- #k_cons{} -> 2;
- #k_tuple{es=Es} -> 1 + length(Es);
- _Other -> 0
- end};
-need_heap_1(#k_bif{}=Bif, H) ->
- case is_gc_bif(Bif) of
- false ->
- {[],H};
- true ->
- {need_heap_need(H),0}
- end;
-need_heap_1(_Ke, H) ->
- %% Call or call-like instruction such as set_tuple_element/3.
- {need_heap_need(H),0}.
-
-need_heap_need(0) -> [];
-need_heap_need(H) -> [#cg_need_heap{h=H}].
-
-%% is_gc_bif(#k_bif{}) -> true|false.
-%% is_gc_bif(Name, Arity) -> true|false.
-%% Determines whether the BIF Name/Arity might do a GC.
-
-is_gc_bif(#k_bif{op=#k_remote{name=#k_atom{val=Name}},args=Args}) ->
- is_gc_bif(Name, length(Args));
-is_gc_bif(#k_bif{op=#k_internal{}}) ->
- true.
-
-is_gc_bif(hd, 1) -> false;
-is_gc_bif(tl, 1) -> false;
-is_gc_bif(self, 0) -> false;
-is_gc_bif(node, 0) -> false;
-is_gc_bif(node, 1) -> false;
-is_gc_bif(element, 2) -> false;
-is_gc_bif(get, 1) -> false;
-is_gc_bif(tuple_size, 1) -> false;
-is_gc_bif(map_get, 2) -> false;
-is_gc_bif(is_map_key, 2) -> false;
-is_gc_bif(Bif, Arity) ->
- not (erl_internal:bool_op(Bif, Arity) orelse
- erl_internal:new_type_test(Bif, Arity) orelse
- erl_internal:comp_op(Bif, Arity)).
-
-%% match_cg(Matc, [Ret], Le, Vdb, StackReg, State) ->
-%% {[Ainstr],StackReg,State}.
-%% Generate code for a match. First save all variables on the stack
-%% that are to survive after the match. We leave saved variables in
-%% their registers as they might actually be in the right place.
-
-match_cg(M, Rs, Le, Vdb, Bef, St0) ->
- I = Le#l.i,
- {Sis,Int0} = adjust_stack(Bef, I, I+1, Vdb),
- {B,St1} = new_label(St0),
- {Mis,Int1,St2} = match_cg(M, St1#cg.ultimate_failure,
- Int0, St1#cg{break=B}),
- %% Put return values in registers.
- Reg = load_vars(Rs, Int1#sr.reg),
- {Sis ++ Mis ++ [{label,B}],
- clear_dead(Int1#sr{reg=Reg}, I, Vdb),
- St2#cg{break=St1#cg.break}}.
-
-guard_match_cg(M, Rs, Le, Vdb, Bef, St0) ->
- I = Le#l.i,
- {B,St1} = new_label(St0),
- Fail = case St0 of
- #cg{bfail=0,ultimate_failure=Fail0} -> Fail0;
- #cg{bfail=Fail0} -> Fail0
- end,
- {Mis,Aft,St2} = match_cg(M, Fail, Bef, St1#cg{break=B}),
- %% Update the register descriptors for the return registers.
- Reg = guard_match_regs(Aft#sr.reg, Rs),
- {Mis ++ [{label,B}],
- clear_dead(Aft#sr{reg=Reg}, I, Vdb),
- St2#cg{break=St1#cg.break}}.
-
-guard_match_regs([{I,gbreakvar}|Rs], [#k_var{name=V}|Vs]) ->
- [{I,V}|guard_match_regs(Rs, Vs)];
-guard_match_regs([R|Rs], Vs) ->
- [R|guard_match_regs(Rs, Vs)];
-guard_match_regs([], []) -> [].
-
-
-%% match_cg(Match, Fail, StackReg, State) -> {[Ainstr],StackReg,State}.
-%% Generate code for a match tree. N.B. there is no need pass Vdb
-%% down as each level which uses this takes its own internal Vdb not
-%% the outer one.
-
-match_cg(#k_alt{first=F,then=S}, Fail, Bef, St0) ->
- {Tf,St1} = new_label(St0),
- {Fis,Faft,St2} = match_cg(F, Tf, Bef, St1),
- {Sis,Saft,St3} = match_cg(S, Fail, Bef, St2),
- Aft = sr_merge(Faft, Saft),
- {Fis ++ [{label,Tf}] ++ Sis,Aft,St3};
-match_cg(#k_select{var=#k_var{anno=Vanno,name=Vname}=V,types=Scs0}, Fail, Bef, St) ->
- ReuseForContext = member(reuse_for_context, Vanno) andalso
- find_reg(Vname, Bef#sr.reg) =/= error,
- Scs = case ReuseForContext of
- false -> Scs0;
- true -> bsm_rename_ctx(Scs0, Vname)
- end,
- match_fmf(fun (S, F, Sta) ->
- select_cg(S, V, F, Fail, Bef, Sta) end,
- Fail, St, Scs);
-match_cg(#k_guard{clauses=Gcs}, Fail, Bef, St) ->
- match_fmf(fun (G, F, Sta) -> guard_clause_cg(G, F, Bef, Sta) end,
- Fail, St, Gcs);
-match_cg(#cg_block{anno=Le,es=Es}, _Fail, Bef, St) ->
- %% Must clear registers and stack of dead variables.
- Int = clear_dead(Bef, Le#l.i, Le#l.vdb),
- block_cg(Es, Le, Int, St).
-
-%% bsm_rename_ctx([Clause], Var) -> [Clause]
-%% We know from an annotation that the register for a binary can
-%% be reused for the match context because the two are not truly
-%% alive at the same time (even though the life time information
-%% says so).
-%%
-%% The easiest way to have those variables share the same register is
-%% to rename the variable with the shortest life-span (the match
-%% context) to the variable for the binary (which can have a very
-%% long life-time because it is locked during matching). We KNOW that
-%% the match state variable will only be alive during the matching.
-%%
-%% We must also remove all information about the match context
-%% variable from all life-time information databases (Vdb).
-
-bsm_rename_ctx([#k_type_clause{type=k_binary,values=Vcs}=TC|Cs], New) ->
- [#k_val_clause{val=#k_binary{segs=#k_var{name=Old}}=Bin,
- body=Ke0}=VC0] = Vcs,
- Ke = bsm_rename_ctx(Ke0, Old, New, false),
- VC = VC0#k_val_clause{val=Bin#k_binary{segs=#k_var{name=New}},
- body=Ke},
- [TC#k_type_clause{values=[VC]}|bsm_rename_ctx(Cs, New)];
-bsm_rename_ctx([C|Cs], New) ->
- [C|bsm_rename_ctx(Cs, New)];
-bsm_rename_ctx([], _) -> [].
-
-%% bsm_rename_ctx(Ke, OldName, NewName, InProt) -> Ke'
-%% Rename and clear OldName from life-time information. We must
-%% recurse into any block contained in a protected, but it would
-%% only complicatate things to recurse into blocks not in a protected
-%% (the match context variable is not live inside them).
-
-bsm_rename_ctx(#k_select{var=#k_var{name=V},types=Cs0}=Sel,
- Old, New, InProt) ->
- Cs = bsm_rename_ctx_list(Cs0, Old, New, InProt),
- Sel#k_select{var=#k_var{name=bsm_rename_var(V, Old, New)},types=Cs};
-bsm_rename_ctx(#k_type_clause{values=Cs0}=TC, Old, New, InProt) ->
- Cs = bsm_rename_ctx_list(Cs0, Old, New, InProt),
- TC#k_type_clause{values=Cs};
-bsm_rename_ctx(#k_val_clause{body=Ke0}=VC, Old, New, InProt) ->
- Ke = bsm_rename_ctx(Ke0, Old, New, InProt),
- VC#k_val_clause{body=Ke};
-bsm_rename_ctx(#k_alt{first=F0,then=S0}=Alt, Old, New, InProt) ->
- F = bsm_rename_ctx(F0, Old, New, InProt),
- S = bsm_rename_ctx(S0, Old, New, InProt),
- Alt#k_alt{first=F,then=S};
-bsm_rename_ctx(#k_guard{clauses=Gcs0}=Guard, Old, New, InProt) ->
- Gcs = bsm_rename_ctx_list(Gcs0, Old, New, InProt),
- Guard#k_guard{clauses=Gcs};
-bsm_rename_ctx(#k_guard_clause{guard=G0,body=B0}=GC, Old, New, InProt) ->
- G = bsm_rename_ctx(G0, Old, New, InProt),
- B = bsm_rename_ctx(B0, Old, New, InProt),
- %% A guard clause may cause unsaved variables to be saved on the stack.
- %% Since the match state variable Old is an alias for New (uses the
- %% same register), it is neither in the stack nor register descriptor
- %% lists and we would crash when we didn't find it unless we remove
- %% it from the database.
- bsm_forget_var(GC#k_guard_clause{guard=G,body=B}, Old);
-bsm_rename_ctx(#k_protected{arg=Ts0}=Prot, Old, New, _InProt) ->
- InProt = true,
- Ts = bsm_rename_ctx_list(Ts0, Old, New, InProt),
- bsm_forget_var(Prot#k_protected{arg=Ts}, Old);
-bsm_rename_ctx(#k_guard_match{body=Ms0}=Match, Old, New, InProt) ->
- Ms = bsm_rename_ctx(Ms0, Old, New, InProt),
- Match#k_guard_match{body=Ms};
-bsm_rename_ctx(#k_test{}=Test, _, _, _) -> Test;
-bsm_rename_ctx(#k_bif{}=Bif, _, _, _) -> Bif;
-bsm_rename_ctx(#k_put{}=Put, _, _, _) -> Put;
-bsm_rename_ctx(#k_call{}=Call, _, _, _) -> Call;
-bsm_rename_ctx(#cg_block{}=Block, Old, _, false) ->
- %% This block is not inside a protected. The match context variable cannot
- %% possibly be live inside the block.
- bsm_forget_var(Block, Old);
-bsm_rename_ctx(#cg_block{es=Es0}=Block, Old, New, true) ->
- %% A block in a protected. We must recursively rename the variable
- %% inside the block.
- Es = bsm_rename_ctx_list(Es0, Old, New, true),
- bsm_forget_var(Block#cg_block{es=Es}, Old);
-bsm_rename_ctx(#k_guard_break{}=Break, Old, _New, _InProt) ->
- bsm_forget_var(Break, Old).
-
-bsm_rename_ctx_list([C|Cs], Old, New, InProt) ->
- [bsm_rename_ctx(C, Old, New, InProt)|
- bsm_rename_ctx_list(Cs, Old, New, InProt)];
-bsm_rename_ctx_list([], _, _, _) -> [].
-
-bsm_rename_var(Old, Old, New) -> New;
-bsm_rename_var(V, _, _) -> V.
-
-%% bsm_forget_var(#l{}, Variable) -> #l{}
-%% Remove a variable from the variable life-time database.
-
-bsm_forget_var(Ke, V) ->
- #l{vdb=Vdb} = L0 = get_kanno(Ke),
- L = L0#l{vdb=keydelete(V, 1, Vdb)},
- set_kanno(Ke, L).
-
-%% block_cg([Kexpr], Le, Vdb, StackReg, St) -> {[Ainstr],StackReg,St}.
-%% block_cg([Kexpr], Le, StackReg, St) -> {[Ainstr],StackReg,St}.
-
-block_cg(Es, Le, _Vdb, Bef, St) ->
- block_cg(Es, Le, Bef, St).
-
-block_cg(Es, Le, Bef, #cg{is_top_block=false}=St) ->
- cg_block(Es, Le#l.vdb, Bef, St);
-block_cg(Es, Le, Bef, #cg{is_top_block=true}=St0) ->
- %% No stack frame has been established yet. Do we need one?
- case need_stackframe(Es) of
- true ->
- %% We need a stack frame. Generate the code and add the
- %% code for creating and deallocating the stack frame.
- {Is0,Aft,St} = cg_block(Es, Le#l.vdb, Bef,
- St0#cg{is_top_block=false,need_frame=false}),
- Is = top_level_block(Is0, Aft, max_reg(Bef#sr.reg), St),
- {Is,Aft,St#cg{is_top_block=true}};
- false ->
- %% This sequence of instructions ending in a #k_match{} (a
- %% 'case' or 'if') in the Erlang code does not need a
- %% stack frame yet. Delay the creation (if a stack frame
- %% is needed at all, it will be created inside the
- %% #k_match{}).
- cg_list(Es, Le#l.vdb, Bef, St0)
- end.
-
-%% need_stackframe([Kexpr]) -> true|false.
-%% Does this list of instructions need a stack frame?
-%%
-%% A sequence of instructions that don't clobber the X registers
-%% followed by a single #k_match{} doesn't need a stack frame.
-
-need_stackframe([H|T]) ->
- case H of
- #k_bif{op=#k_internal{}} -> true;
- #k_put{arg=#k_binary{}} -> true;
- #k_bif{} -> need_stackframe(T);
- #k_put{} -> need_stackframe(T);
- #k_guard_match{} -> need_stackframe(T);
- #k_match{} when T =:= [] -> false;
- _ -> true
- end;
-need_stackframe([]) -> false.
-
-cg_block([], _Vdb, Bef, St0) ->
- {[],Bef,St0};
-cg_block(Kes0, Vdb, Bef, St0) ->
- {Kes2,Int1,St1} =
- case basic_block(Kes0) of
- {Kes1,LastI,Args,Rest} ->
- cg_basic_block(Kes1, LastI, Args, Vdb, Bef, St0);
- {Kes1,Rest} ->
- cg_list(Kes1, Vdb, Bef, St0)
- end,
- {Kes3,Int2,St2} = cg_block(Rest, Vdb, Int1, St1),
- {Kes2 ++ Kes3,Int2,St2}.
-
-basic_block(Kes) -> basic_block(Kes, []).
-
-basic_block([Ke|Kes], Acc) ->
- case collect_block(Ke) of
- include -> basic_block(Kes, [Ke|Acc]);
- {block_end,As} ->
- case Acc of
- [] ->
- %% If the basic block does not contain any #k_put{} instructions,
- %% it serves no useful purpose to do basic block optimizations.
- {[Ke],Kes};
- _ ->
- #l{i=I} = get_kanno(Ke),
- {reverse(Acc, [Ke]),I,As,Kes}
- end;
- no_block -> {reverse(Acc, [Ke]),Kes}
- end.
-
-collect_block(#k_put{arg=Arg}) ->
- %% #k_put{} instructions that may garbage collect are not allowed
- %% in basic blocks.
- case Arg of
- #k_binary{} -> no_block;
- #k_map{} -> no_block;
- _ -> include
- end;
-collect_block(#k_call{op=Func,args=As}) ->
- {block_end,As++func_vars(Func)};
-collect_block(#k_enter{op=Func,args=As}) ->
- {block_end,As++func_vars(Func)};
-collect_block(#k_return{args=Rs}) ->
- {block_end,Rs};
-collect_block(#k_break{args=Bs}) ->
- {block_end,Bs};
-collect_block(_) -> no_block.
-
-func_vars(#k_var{}=Var) ->
- [Var];
-func_vars(#k_remote{mod=M,name=F})
- when is_record(M, k_var); is_record(F, k_var) ->
- [M,F];
-func_vars(_) -> [].
-
-%% cg_basic_block([Kexpr], FirstI, LastI, Arguments, Vdb, StackReg, State) ->
-%% {[Ainstr],StackReg,State}.
-%%
-%% Do a specialized code generation for a basic block of #put{}
-%% instructions (that don't do any garbage collection) followed by a
-%% call, break, or return.
-%%
-%% 'Arguments' is a list of the variables that must be loaded into
-%% consecutive X registers before the last instruction in the block.
-%% The point of this specialized code generation is to try put the
-%% all of the variables in 'Arguments' into the correct X register
-%% to begin with, instead of putting them into the first available
-%% X register and having to move them to the correct X register
-%% later.
-%%
-%% To achieve that, we attempt to reserve the X registers that the
-%% variables in 'Arguments' will need to be in when the block ends.
-%%
-%% To make it more likely that reservations will be successful, we
-%% will try to save variables that need to be saved to the stack as
-%% early as possible (if an X register needed by a variable in
-%% Arguments is occupied by another variable, the value in the
-%% X register can be evicted if it is saved on the stack).
-%%
-%% We will take care not to increase the size of stack frame compared
-%% to what the standard code generator would have done (that is, to
-%% save all X registers at the last possible moment). We will do that
-%% by extending the stack frame to the minimal size needed to save
-%% all that needs to be saved using extend_stack/4, and use
-%% save_carefully/4 during code generation to only save the variables
-%% that can be saved without growing the stack frame.
-
-cg_basic_block(Kes, Lf, As, Vdb, Bef, St0) ->
- Int0 = reserve_arg_regs(As, Bef),
- Int = extend_stack(Int0, Lf, Lf+1, Vdb),
- {Keis,{Aft,St1}} =
- flatmapfoldl(fun(Ke, St) -> cg_basic_block(Ke, St, Lf, Vdb) end,
- {Int,St0}, need_heap(Kes)),
- {Keis,Aft,St1}.
-
-cg_basic_block(#cg_need_heap{}=Ke, {Bef,St0}, _Lf, Vdb) ->
- {Keis,Aft,St1} = cg(Ke, Vdb, Bef, St0),
- {Keis,{Aft,St1}};
-cg_basic_block(Ke, {Bef,St0}, Lf, Vdb) ->
- #l{i=I} = get_kanno(Ke),
-
- %% Save all we can to increase the possibility that reserving
- %% registers will succeed.
- {Sis,Int0} = save_carefully(Bef, I, Lf+1, Vdb),
- Int1 = reserve(Int0),
- {Keis,Aft,St1} = cg(Ke, Vdb, Int1, St0),
- {Sis ++ Keis,{Aft,St1}}.
-
-%% reserve_arg_regs([Argument], Bef) -> Aft.
-%% Try to reserve the X registers for all arguments. All registers
-%% that we wish to reserve will be saved in Bef#sr.res.
-
-reserve_arg_regs(As, Bef) ->
- Res = reserve_arg_regs_1(As, 0),
- reserve(Bef#sr{res=Res}).
-
-reserve_arg_regs_1([#k_var{name=V}|As], I) ->
- [{I,V}|reserve_arg_regs_1(As, I+1)];
-reserve_arg_regs_1([A|As], I) ->
- [{I,A}|reserve_arg_regs_1(As, I+1)];
-reserve_arg_regs_1([], _) -> [].
-
-%% reserve(Bef) -> Aft.
-%% Try to reserve more registers. The registers we wish to reserve
-%% are found in Bef#sr.res.
-
-reserve(#sr{reg=Regs,stk=Stk,res=Res}=Sr) ->
- Sr#sr{reg=reserve_1(Res, Regs, Stk)}.
-
-reserve_1([{I,V}|Rs], [free|Regs], Stk) ->
- [{reserved,I,V}|reserve_1(Rs, Regs, Stk)];
-reserve_1([{I,V}|Rs], [{I,V}|Regs], Stk) ->
- [{I,V}|reserve_1(Rs, Regs, Stk)];
-reserve_1([{I,V}|Rs], [{I,Var}|Regs], Stk) ->
- case on_stack(Var, Stk) of
- true -> [{reserved,I,V}|reserve_1(Rs, Regs, Stk)];
- false -> [{I,Var}|reserve_1(Rs, Regs, Stk)]
- end;
-reserve_1([{I,V}|Rs], [{reserved,I,_}|Regs], Stk) ->
- [{reserved,I,V}|reserve_1(Rs, Regs, Stk)];
-reserve_1([{I,V}|Rs], [], Stk) ->
- [{reserved,I,V}|reserve_1(Rs, [], Stk)];
-reserve_1([], Regs, _) -> Regs.
-
-%% extend_stack(Bef, FirstBefore, LastFrom, Vdb) -> Aft.
-%% Extend the stack enough to fit all variables alive past LastFrom
-%% and not already on the stack.
-
-extend_stack(#sr{stk=Stk0}=Bef, Fb, Lf, Vdb) ->
- Stk1 = clear_dead_stk(Stk0, Fb, Vdb),
- New = new_not_on_stack(Stk1, Fb, Lf, Vdb),
- Stk2 = foldl(fun ({V,_,_}, Stk) -> put_stack(V, Stk) end, Stk1, New),
- Stk = Stk0 ++ lists:duplicate(length(Stk2) - length(Stk0), free),
- Bef#sr{stk=Stk}.
-
-%% save_carefully(Bef, FirstBefore, LastFrom, Vdb) -> {[SaveVar],Aft}.
-%% Save variables which are used past current point and which are not
-%% already on the stack, but only if the variables can be saved without
-%% growing the stack frame.
-
-save_carefully(#sr{stk=Stk}=Bef, Fb, Lf, Vdb) ->
- New0 = new_not_on_stack(Stk, Fb, Lf, Vdb),
- New = keysort(2, New0),
- save_carefully_1(New, Bef, []).
-
-save_carefully_1([{V,_,_}|Vs], #sr{reg=Regs,stk=Stk0}=Bef, Acc) ->
- case put_stack_carefully(V, Stk0) of
- error ->
- {reverse(Acc),Bef};
- Stk1 ->
- SrcReg = fetch_reg(V, Regs),
- Move = {move,SrcReg,fetch_stack(V, Stk1)},
- {x,_} = SrcReg, %Assertion - must be X register.
- save_carefully_1(Vs, Bef#sr{stk=Stk1}, [Move|Acc])
- end;
-save_carefully_1([], Bef, Acc) ->
- {reverse(Acc),Bef}.
-
-%% top_level_block([Instruction], Bef, MaxRegs, St) -> [Instruction].
-%% For the top-level block, allocate a stack frame a necessary,
-%% adjust Y register numbering and instructions that return
-%% from the function.
-
-top_level_block(Keis, #sr{stk=[]}, _MaxRegs, #cg{need_frame=false}) ->
- Keis;
-top_level_block(Keis, Bef, MaxRegs, _St) ->
- %% This top block needs an allocate instruction before it, and a
- %% deallocate instruction before each return.
- FrameSz = length(Bef#sr.stk),
- MaxY = FrameSz-1,
- Keis1 = flatmap(fun ({call_only,Arity,Func}) ->
- [{call_last,Arity,Func,FrameSz}];
- ({call_ext_only,Arity,Func}) ->
- [{call_ext_last,Arity,Func,FrameSz}];
- ({apply_only,Arity}) ->
- [{apply_last,Arity,FrameSz}];
- (return) ->
- [{deallocate,FrameSz},return];
- (Tuple) when is_tuple(Tuple) ->
- [turn_yregs(Tuple, MaxY)];
- (Other) ->
- [Other]
- end, Keis),
- [{allocate_zero,FrameSz,MaxRegs}|Keis1].
-
-%% turn_yregs(Size, Tuple, MaxY) -> Tuple'
-%% Renumber y register so that {y,0} becomes {y,FrameSize-1},
-%% {y,FrameSize-1} becomes {y,0} and so on. This is to make nested
-%% catches work. The code generation algorithm gives a lower register
-%% number to the outer catch, which is wrong.
-
-turn_yregs({call,_,_}=I, _MaxY) -> I;
-turn_yregs({call_ext,_,_}=I, _MaxY) -> I;
-turn_yregs({jump,_}=I, _MaxY) -> I;
-turn_yregs({label,_}=I, _MaxY) -> I;
-turn_yregs({line,_}=I, _MaxY) -> I;
-turn_yregs({test_heap,_,_}=I, _MaxY) -> I;
-turn_yregs({bif,Op,F,A,B}, MaxY) ->
- {bif,Op,F,turn_yreg(A, MaxY),turn_yreg(B, MaxY)};
-turn_yregs({gc_bif,Op,F,Live,A,B}, MaxY) when is_integer(Live) ->
- {gc_bif,Op,F,Live,turn_yreg(A, MaxY),turn_yreg(B, MaxY)};
-turn_yregs({get_tuple_element,S,N,D}, MaxY) ->
- {get_tuple_element,turn_yreg(S, MaxY),N,turn_yreg(D, MaxY)};
-turn_yregs({put_tuple,Arity,D}, MaxY) ->
- {put_tuple,Arity,turn_yreg(D, MaxY)};
-turn_yregs({select_val,R,F,L}, MaxY) ->
- {select_val,turn_yreg(R, MaxY),F,L};
-turn_yregs({test,Op,F,L}, MaxY) ->
- {test,Op,F,turn_yreg(L, MaxY)};
-turn_yregs({test,Op,F,Live,A,B}, MaxY) when is_integer(Live) ->
- {test,Op,F,Live,turn_yreg(A, MaxY),turn_yreg(B, MaxY)};
-turn_yregs({Op,A}, MaxY) ->
- {Op,turn_yreg(A, MaxY)};
-turn_yregs({Op,A,B}, MaxY) ->
- {Op,turn_yreg(A, MaxY),turn_yreg(B, MaxY)};
-turn_yregs({Op,A,B,C}, MaxY) ->
- {Op,turn_yreg(A, MaxY),turn_yreg(B, MaxY),turn_yreg(C, MaxY)};
-turn_yregs(Tuple, MaxY) ->
- turn_yregs(tuple_size(Tuple), Tuple, MaxY).
-
-turn_yregs(1, Tp, _) ->
- Tp;
-turn_yregs(N, Tp, MaxY) ->
- E = turn_yreg(element(N, Tp), MaxY),
- turn_yregs(N-1, setelement(N, Tp, E), MaxY).
-
-turn_yreg({yy,YY}, MaxY) ->
- {y,MaxY-YY};
-turn_yreg({list,Ls},MaxY) ->
- {list,turn_yreg(Ls, MaxY)};
-turn_yreg([_|_]=Ts, MaxY) ->
- [turn_yreg(T, MaxY) || T <- Ts];
-turn_yreg(Other, _MaxY) ->
- Other.
-
-%% select_cg(Sclause, V, TypeFail, ValueFail, StackReg, State) ->
-%% {Is,StackReg,State}.
-%% Selecting type and value needs two failure labels, TypeFail is the
-%% label to jump to of the next type test when this type fails, and
-%% ValueFail is the label when this type is correct but the value is
-%% wrong. These are different as in the second case there is no need
-%% to try the next type, it will always fail.
-
-select_cg(#k_type_clause{type=Type,values=Vs}, Var, Tf, Vf, Bef, St) ->
- #k_var{name=V} = Var,
- select_cg(Type, Vs, V, Tf, Vf, Bef, St).
-
-select_cg(k_cons, [S], V, Tf, Vf, Bef, St) ->
- select_cons(S, V, Tf, Vf, Bef, St);
-select_cg(k_nil, [S], V, Tf, Vf, Bef, St) ->
- select_nil(S, V, Tf, Vf, Bef, St);
-select_cg(k_binary, [S], V, Tf, Vf, Bef, St) ->
- select_binary(S, V, Tf, Vf, Bef, St);
-select_cg(k_bin_seg, S, V, Tf, _Vf, Bef, St) ->
- select_bin_segs(S, V, Tf, Bef, St);
-select_cg(k_bin_int, S, V, Tf, _Vf, Bef, St) ->
- select_bin_segs(S, V, Tf, Bef, St);
-select_cg(k_bin_end, [S], V, Tf, _Vf, Bef, St) ->
- select_bin_end(S, V, Tf, Bef, St);
-select_cg(k_map, S, V, Tf, Vf, Bef, St) ->
- select_map(S, V, Tf, Vf, Bef, St);
-select_cg(k_literal, S, V, Tf, Vf, Bef, St) ->
- select_literal(S, V, Tf, Vf, Bef, St);
-select_cg(Type, Scs, V, Tf, Vf, Bef, St0) ->
- {Vis,{Aft,St1}} =
- mapfoldl(fun (S, {Int,Sta}) ->
- {Val,Is,Inta,Stb} = select_val(S, V, Vf, Bef, Sta),
- {{Is,[Val]},{sr_merge(Int, Inta),Stb}}
- end, {void,St0}, Scs),
- OptVls = combine(lists:sort(combine(Vis))),
- {Vls,Sis,St2} = select_labels(OptVls, St1, [], []),
- {select_val_cg(Type, fetch_var(V, Bef), Vls, Tf, Vf, Sis), Aft, St2}.
-
-select_val_cg(k_tuple, R, [Arity,{f,Lbl}], Tf, Vf, [{label,Lbl}|Sis]) ->
- [{test,is_tuple,{f,Tf},[R]},{test,test_arity,{f,Vf},[R,Arity]}|Sis];
-select_val_cg(k_tuple, R, Vls, Tf, Vf, Sis) ->
- [{test,is_tuple,{f,Tf},[R]},{select_tuple_arity,R,{f,Vf},{list,Vls}}|Sis];
-select_val_cg(Type, R, [Val, {f,Lbl}], Fail, Fail, [{label,Lbl}|Sis]) ->
- [{test,is_eq_exact,{f,Fail},[R,{type(Type),Val}]}|Sis];
-select_val_cg(Type, R, [Val, {f,Lbl}], Tf, Vf, [{label,Lbl}|Sis]) ->
- [{test,select_type_test(Type),{f,Tf},[R]},
- {test,is_eq_exact,{f,Vf},[R,{type(Type),Val}]}|Sis];
-select_val_cg(Type, R, Vls0, Tf, Vf, Sis) ->
- Vls1 = [case Value of
- {f,_Lbl} -> Value;
- _ -> {type(Type),Value}
- end || Value <- Vls0],
- [{test,select_type_test(Type),{f,Tf},[R]}, {select_val,R,{f,Vf},{list,Vls1}}|Sis].
-
-type(k_atom) -> atom;
-type(k_float) -> float;
-type(k_int) -> integer.
-
-select_type_test(k_int) -> is_integer;
-select_type_test(k_atom) -> is_atom;
-select_type_test(k_float) -> is_float.
-
-combine([{Is,Vs1}, {Is,Vs2}|Vis]) -> combine([{Is,Vs1 ++ Vs2}|Vis]);
-combine([V|Vis]) -> [V|combine(Vis)];
-combine([]) -> [].
-
-select_labels([{Is,Vs}|Vis], St0, Vls, Sis) ->
- {Lbl,St1} = new_label(St0),
- select_labels(Vis, St1, add_vls(Vs, Lbl, Vls), [[{label,Lbl}|Is]|Sis]);
-select_labels([], St, Vls, Sis) ->
- {Vls,append(Sis),St}.
-
-add_vls([V|Vs], Lbl, Acc) ->
- add_vls(Vs, Lbl, [V, {f,Lbl}|Acc]);
-add_vls([], _, Acc) -> Acc.
-
-select_literal(S, V, Tf, Vf, Bef, St) ->
- Reg = fetch_var(V, Bef),
- F = fun(ValClause, Fail, St0) ->
- {Val,Is,Aft,St1} = select_val(ValClause, V, Vf, Bef, St0),
- Test = {test,is_eq_exact,{f,Fail},[Reg,{literal,Val}]},
- {[Test|Is],Aft,St1}
- end,
- match_fmf(F, Tf, St, S).
-
-select_cons(#k_val_clause{val=#k_cons{hd=Hd,tl=Tl},body=B,anno=#l{i=I,vdb=Vdb}},
- V, Tf, Vf, Bef, St0) ->
- Es = [Hd,Tl],
- {Eis,Int,St1} = select_extract_cons(V, Es, I, Vdb, Bef, St0),
- {Bis,Aft,St2} = match_cg(B, Vf, Int, St1),
- {[{test,is_nonempty_list,{f,Tf},[fetch_var(V, Bef)]}] ++ Eis ++ Bis,Aft,St2}.
-
-select_nil(#k_val_clause{val=#k_nil{},body=B}, V, Tf, Vf, Bef, St0) ->
- {Bis,Aft,St1} = match_cg(B, Vf, Bef, St0),
- {[{test,is_nil,{f,Tf},[fetch_var(V, Bef)]}] ++ Bis,Aft,St1}.
-
-select_binary(#k_val_clause{val=#k_binary{segs=#k_var{name=V}},body=B,
- anno=#l{i=I,vdb=Vdb}}, V, Tf, Vf, Bef, St0) ->
- #cg{ctx=OldCtx} = St0,
- Int0 = clear_dead(Bef#sr{reg=Bef#sr.reg}, I, Vdb),
- {Bis0,Aft,St1} = match_cg(B, Vf, Int0, St0#cg{ctx=V}),
- CtxReg = fetch_var(V, Int0),
- Live = max_reg(Bef#sr.reg),
- Bis1 = [{test,bs_start_match2,{f,Tf},Live,[CtxReg,{context,V}],CtxReg},
- {bs_save2,CtxReg,{V,V}}|Bis0],
- Bis = finish_select_binary(Bis1),
- {Bis,Aft,St1#cg{ctx=OldCtx}};
-select_binary(#k_val_clause{val=#k_binary{segs=#k_var{name=Ivar}},body=B,
- anno=#l{i=I,vdb=Vdb}}, V, Tf, Vf, Bef, St0) ->
- #cg{ctx=OldCtx} = St0,
- Regs = put_reg(Ivar, Bef#sr.reg),
- Int0 = clear_dead(Bef#sr{reg=Regs}, I, Vdb),
- {Bis0,Aft,St1} = match_cg(B, Vf, Int0, St0#cg{ctx=Ivar}),
- CtxReg = fetch_var(Ivar, Int0),
- Live = max_reg(Bef#sr.reg),
- Bis1 = [{test,bs_start_match2,{f,Tf},Live,
- [fetch_var(V, Bef),{context,Ivar}],CtxReg},
- {bs_save2,CtxReg,{Ivar,Ivar}}|Bis0],
- Bis = finish_select_binary(Bis1),
- {Bis,Aft,St1#cg{ctx=OldCtx}}.
-
-finish_select_binary([{bs_save2,R,Point}=I,{bs_restore2,R,Point}|Is]) ->
- [I|finish_select_binary(Is)];
-finish_select_binary([{bs_save2,R,Point}=I,{test,is_eq_exact,_,_}=Test,
- {bs_restore2,R,Point}|Is]) ->
- [I,Test|finish_select_binary(Is)];
-finish_select_binary([{test,bs_match_string,F,[Ctx,BinList]}|Is])
- when is_list(BinList) ->
- I = {test,bs_match_string,F,[Ctx,list_to_bitstring(BinList)]},
- [I|finish_select_binary(Is)];
-finish_select_binary([I|Is]) ->
- [I|finish_select_binary(Is)];
-finish_select_binary([]) -> [].
-
-%% New instructions for selection of binary segments.
-
-select_bin_segs(Scs, Ivar, Tf, Bef, St) ->
- match_fmf(fun(S, Fail, Sta) ->
- select_bin_seg(S, Ivar, Fail, Bef, Sta) end,
- Tf, St, Scs).
-
-select_bin_seg(#k_val_clause{val=#k_bin_seg{size=Size,unit=U,type=T,
- seg=Seg,flags=Fs0,next=Next},
- body=B,
- anno=#l{i=I,vdb=Vdb,a=A}}, Ivar, Fail, Bef, St0) ->
- Ctx = St0#cg.ctx,
- Fs = [{anno,A}|Fs0],
- Es = case Next of
- [] -> [Seg];
- _ -> [Seg,Next]
- end,
- {Mis,Int,St1} = select_extract_bin(Es, Size, U, T, Fs, Fail,
- I, Vdb, Bef, Ctx, B, St0),
- {Bis,Aft,St2} = match_cg(B, Fail, Int, St1),
- CtxReg = fetch_var(Ctx, Bef),
- Is = if
- Mis =:= [] ->
- %% No bs_restore2 instruction needed if no match instructions.
- Bis;
- true ->
- [{bs_restore2,CtxReg,{Ctx,Ivar}}|Mis++Bis]
- end,
- {Is,Aft,St2};
-select_bin_seg(#k_val_clause{val=#k_bin_int{size=Sz,unit=U,flags=Fs,
- val=Val,next=Next},
- body=B,
- anno=#l{i=I,vdb=Vdb}}, Ivar, Fail, Bef, St0) ->
- Ctx = St0#cg.ctx,
- {Mis,Int,St1} = select_extract_int(Next, Val, Sz, U, Fs, Fail,
- I, Vdb, Bef, Ctx, St0),
- {Bis,Aft,St2} = match_cg(B, Fail, Int, St1),
- CtxReg = fetch_var(Ctx, Bef),
- Is = case Mis ++ Bis of
- [{test,bs_match_string,F,[OtherCtx,Bin1]},
- {bs_save2,OtherCtx,_},
- {bs_restore2,OtherCtx,_},
- {test,bs_match_string,F,[OtherCtx,Bin2]}|Is0] ->
- %% We used to do this optimization later, but it
- %% turns out that in huge functions with many
- %% bs_match_string instructions, it's a big win
- %% to do the combination now. To avoid copying the
- %% binary data again and again, we'll combine bitstrings
- %% in a list and convert all of it to a bitstring later.
- [{test,bs_match_string,F,[OtherCtx,[Bin1,Bin2]]}|Is0];
- Is0 ->
- Is0
- end,
- {[{bs_restore2,CtxReg,{Ctx,Ivar}}|Is],Aft,St2}.
-
-select_extract_int(#k_var{name=Tl}, Val, #k_int{val=Sz}, U, Fs, Vf,
- I, Vdb, Bef, Ctx, St) ->
- Bits = U*Sz,
- Bin = case member(big, Fs) of
- true ->
- <<Val:Bits>>;
- false ->
- true = member(little, Fs), %Assertion.
- <<Val:Bits/little>>
- end,
- Bits = bit_size(Bin), %Assertion.
- CtxReg = fetch_var(Ctx, Bef),
- Is = if
- Bits =:= 0 ->
- [{bs_save2,CtxReg,{Ctx,Tl}}];
- true ->
- [{test,bs_match_string,{f,Vf},[CtxReg,Bin]},
- {bs_save2,CtxReg,{Ctx,Tl}}]
- end,
- {Is,clear_dead(Bef, I, Vdb),St}.
-
-select_extract_bin([#k_var{name=Hd},#k_var{name=Tl}], Size0, Unit, Type, Flags, Vf,
- I, Vdb, Bef, Ctx, _Body, St) ->
- SizeReg = get_bin_size_reg(Size0, Bef),
- {Es,Aft} =
- case vdb_find(Hd, Vdb) of
- {_,_,Lhd} when Lhd =< I ->
- %% The extracted value will not be used.
- CtxReg = fetch_var(Ctx, Bef),
- Live = max_reg(Bef#sr.reg),
- Skip = build_skip_instr(Type, Vf, CtxReg, Live,
- SizeReg, Unit, Flags),
- {[Skip,{bs_save2,CtxReg,{Ctx,Tl}}],Bef};
- {_,_,_} ->
- Reg = put_reg(Hd, Bef#sr.reg),
- Int1 = Bef#sr{reg=Reg},
- Rhd = fetch_reg(Hd, Reg),
- CtxReg = fetch_reg(Ctx, Reg),
- Live = max_reg(Bef#sr.reg),
- {[build_bs_instr(Type, Vf, CtxReg, Live, SizeReg,
- Unit, Flags, Rhd),
- {bs_save2,CtxReg,{Ctx,Tl}}],Int1}
- end,
- {Es,clear_dead(Aft, I, Vdb),St};
-select_extract_bin([#k_var{name=Hd}], Size, Unit, binary, Flags, Vf,
- I, Vdb, Bef, Ctx, Body, St) ->
- %% Match the last segment of a binary. We KNOW that the size
- %% must be 'all'.
- #k_atom{val=all} = Size, %Assertion.
- {Es,Aft} =
- case vdb_find(Hd, Vdb) of
- {_,_,Lhd} when Lhd =< I ->
- %% The result will not be used. Furthermore, since we
- %% we are at the end of the binary, the position will
- %% not be used again; thus, it is safe to do a cheaper
- %% test of the unit.
- CtxReg = fetch_var(Ctx, Bef),
- {case Unit of
- 1 ->
- [];
- _ ->
- [{test,bs_test_unit,{f,Vf},[CtxReg,Unit]}]
- end,Bef};
- {_,_,_} ->
- case is_context_unused(Body) of
- false ->
- Reg = put_reg(Hd, Bef#sr.reg),
- Int1 = Bef#sr{reg=Reg},
- Rhd = fetch_reg(Hd, Reg),
- CtxReg = fetch_reg(Ctx, Reg),
- Name = bs_get_binary2,
- Live = max_reg(Bef#sr.reg),
- {[{test,Name,{f,Vf},Live,
- [CtxReg,atomic(Size),Unit,{field_flags,Flags}],Rhd}],
- Int1};
- true ->
- %% Since the matching context will not be used again,
- %% we can reuse its register. Reusing the register
- %% opens some interesting optimizations in the
- %% run-time system.
-
- Reg0 = Bef#sr.reg,
- CtxReg = fetch_reg(Ctx, Reg0),
- Reg = replace_reg_contents(Ctx, Hd, Reg0),
- Int1 = Bef#sr{reg=Reg},
- Name = bs_get_binary2,
- Live = max_reg(Int1#sr.reg),
- {[{test,Name,{f,Vf},Live,
- [CtxReg,atomic(Size),Unit,{field_flags,Flags}],CtxReg}],
- Int1}
- end
- end,
- {Es,clear_dead(Aft, I, Vdb),St}.
-
-%% is_context_unused(Ke) -> true | false
-%% Simple heurististic to determine whether the code that follows
-%% will use the current matching context again. (The liveness
-%% information is too conservative to be useful for this purpose.)
-%% 'true' means that the code that follows will definitely not use
-%% the context again (because it is a block, not guard or matching
-%% code); 'false' that we are not sure (there could be more
-%% matching).
-
-is_context_unused(#k_alt{then=Then}) ->
- %% #k_alt{} can be used for different purposes. If the Then part
- %% is a block, it means that matching has finished and is used for a guard
- %% to choose between the matched clauses.
- is_context_unused(Then);
-is_context_unused(#cg_block{}) ->
- true;
-is_context_unused(_) ->
- false.
-
-select_bin_end(#k_val_clause{val=#k_bin_end{},body=B}, Ivar, Tf, Bef, St0) ->
- Ctx = St0#cg.ctx,
- {Bis,Aft,St2} = match_cg(B, Tf, Bef, St0),
- CtxReg = fetch_var(Ctx, Bef),
- {[{bs_restore2,CtxReg,{Ctx,Ivar}},
- {test,bs_test_tail2,{f,Tf},[CtxReg,0]}|Bis],Aft,St2}.
-
-get_bin_size_reg(#k_var{name=V}, Bef) ->
- fetch_var(V, Bef);
-get_bin_size_reg(Literal, _Bef) ->
- atomic(Literal).
-
-build_bs_instr(Type, Vf, CtxReg, Live, SizeReg, Unit, Flags, Rhd) ->
- {Format,Name} = case Type of
- integer -> {plain,bs_get_integer2};
- float -> {plain,bs_get_float2};
- binary -> {plain,bs_get_binary2};
- utf8 -> {utf,bs_get_utf8};
- utf16 -> {utf,bs_get_utf16};
- utf32 -> {utf,bs_get_utf32}
- end,
- case Format of
- plain ->
- {test,Name,{f,Vf},Live,
- [CtxReg,SizeReg,Unit,{field_flags,Flags}],Rhd};
- utf ->
- {test,Name,{f,Vf},Live,
- [CtxReg,{field_flags,Flags}],Rhd}
- end.
-
-build_skip_instr(Type, Vf, CtxReg, Live, SizeReg, Unit, Flags) ->
- {Format,Name} = case Type of
- utf8 -> {utf,bs_skip_utf8};
- utf16 -> {utf,bs_skip_utf16};
- utf32 -> {utf,bs_skip_utf32};
- _ -> {plain,bs_skip_bits2}
- end,
- case Format of
- plain ->
- {test,Name,{f,Vf},[CtxReg,SizeReg,Unit,{field_flags,Flags}]};
- utf ->
- {test,Name,{f,Vf},[CtxReg,Live,{field_flags,Flags}]}
- end.
-
-select_val(#k_val_clause{val=#k_tuple{es=Es},body=B,anno=#l{i=I,vdb=Vdb}},
- V, Vf, Bef, St0) ->
- {Eis,Int,St1} = select_extract_tuple(V, Es, I, Vdb, Bef, St0),
- {Bis,Aft,St2} = match_cg(B, Vf, Int, St1),
- {length(Es),Eis ++ Bis,Aft,St2};
-select_val(#k_val_clause{val=Val0,body=B}, _V, Vf, Bef, St0) ->
- Val = case Val0 of
- #k_atom{val=Lit} -> Lit;
- #k_float{val=Lit} -> Lit;
- #k_int{val=Lit} -> Lit;
- #k_literal{val=Lit} -> Lit
- end,
- {Bis,Aft,St1} = match_cg(B, Vf, Bef, St0),
- {Val,Bis,Aft,St1}.
-
-%% select_extract_tuple(Src, [V], I, Vdb, StackReg, State) ->
-%% {[E],StackReg,State}.
-%% Extract tuple elements, but only if they do not immediately die.
-
-select_extract_tuple(Src, Vs, I, Vdb, Bef, St) ->
- F = fun (#k_var{name=V}, {Int0,Elem}) ->
- case vdb_find(V, Vdb) of
- {V,_,L} when L =< I -> {[], {Int0,Elem+1}};
- _Other ->
- Reg1 = put_reg(V, Int0#sr.reg),
- Int1 = Int0#sr{reg=Reg1},
- Rsrc = fetch_var(Src, Int1),
- {[{get_tuple_element,Rsrc,Elem,fetch_reg(V, Reg1)}],
- {Int1,Elem+1}}
- end
- end,
- {Es,{Aft,_}} = flatmapfoldl(F, {Bef,0}, Vs),
- {Es,Aft,St}.
-
-select_map(Scs, V, Tf, Vf, Bef, St0) ->
- Reg = fetch_var(V, Bef),
- {Is,Aft,St1} =
- match_fmf(fun(#k_val_clause{val=#k_map{op=exact,es=Es},
- body=B,anno=#l{i=I,vdb=Vdb}}, Fail, St1) ->
- select_map_val(V, Es, B, Fail, I, Vdb, Bef, St1)
- end, Vf, St0, Scs),
- {[{test,is_map,{f,Tf},[Reg]}|Is],Aft,St1}.
-
-select_map_val(V, Es, B, Fail, I, Vdb, Bef, St0) ->
- {Eis,Int,St1} = select_extract_map(V, Es, Fail, I, Vdb, Bef, St0),
- {Bis,Aft,St2} = match_cg(B, Fail, Int, St1),
- {Eis++Bis,Aft,St2}.
-
-select_extract_map(_, [], _, _, _, Bef, St) -> {[],Bef,St};
-select_extract_map(Src, Vs, Fail, I, Vdb, Bef, St) ->
- %% First split the instruction flow
- %% We want one set of each
- %% 1) has_map_fields (no target registers)
- %% 2) get_map_elements (with target registers)
- %% Assume keys are term-sorted
- Rsrc = fetch_var(Src, Bef),
-
- {{HasKs,GetVs,HasVarKs,GetVarVs},Aft} =
- foldr(fun(#k_map_pair{key=#k_var{name=K},val=#k_var{name=V}},
- {{HasKsi,GetVsi,HasVarVsi,GetVarVsi},Int0}) ->
- case vdb_find(V, Vdb) of
- {V,_,L} when L =< I ->
- RK = fetch_var(K,Int0),
- {{HasKsi,GetVsi,[RK|HasVarVsi],GetVarVsi},Int0};
- _Other ->
- Reg1 = put_reg(V, Int0#sr.reg),
- Int1 = Int0#sr{reg=Reg1},
- RK = fetch_var(K,Int0),
- RV = fetch_reg(V,Reg1),
- {{HasKsi,GetVsi,HasVarVsi,[[RK,RV]|GetVarVsi]},Int1}
- end;
- (#k_map_pair{key=Key,val=#k_var{name=V}},
- {{HasKsi,GetVsi,HasVarVsi,GetVarVsi},Int0}) ->
- case vdb_find(V, Vdb) of
- {V,_,L} when L =< I ->
- {{[atomic(Key)|HasKsi],GetVsi,HasVarVsi,GetVarVsi},Int0};
- _Other ->
- Reg1 = put_reg(V, Int0#sr.reg),
- Int1 = Int0#sr{reg=Reg1},
- {{HasKsi,[atomic(Key),fetch_reg(V, Reg1)|GetVsi],
- HasVarVsi,GetVarVsi},Int1}
- end
- end, {{[],[],[],[]},Bef}, Vs),
-
- Code = [{test,has_map_fields,{f,Fail},Rsrc,{list,HasKs}} || HasKs =/= []] ++
- [{test,has_map_fields,{f,Fail},Rsrc,{list,[K]}} || K <- HasVarKs] ++
- [{get_map_elements, {f,Fail},Rsrc,{list,GetVs}} || GetVs =/= []] ++
- [{get_map_elements, {f,Fail},Rsrc,{list,[K,V]}} || [K,V] <- GetVarVs],
- {Code, Aft, St}.
-
-
-select_extract_cons(Src, [#k_var{name=Hd},#k_var{name=Tl}], I, Vdb, Bef, St) ->
- Rsrc = fetch_var(Src, Bef),
- Int = clear_dead(Bef, I, Vdb),
- {{_,_,Lhd},{_,_,Ltl}} = {vdb_find(Hd, Vdb),vdb_find(Tl, Vdb)},
- case {Lhd =< I, Ltl =< I} of
- {true,true} ->
- %% Both dead.
- {[],Bef,St};
- {true,false} ->
- %% Head dead.
- Reg0 = put_reg(Tl, Bef#sr.reg),
- Aft = Int#sr{reg=Reg0},
- Rtl = fetch_reg(Tl, Reg0),
- {[{get_tl,Rsrc,Rtl}],Aft,St};
- {false,true} ->
- %% Tail dead.
- Reg0 = put_reg(Hd, Bef#sr.reg),
- Aft = Int#sr{reg=Reg0},
- Rhd = fetch_reg(Hd, Reg0),
- {[{get_hd,Rsrc,Rhd}],Aft,St};
- {false,false} ->
- %% Both used.
- Reg0 = put_reg(Tl, put_reg(Hd, Bef#sr.reg)),
- Aft = Bef#sr{reg=Reg0},
- Rhd = fetch_reg(Hd, Reg0),
- Rtl = fetch_reg(Tl, Reg0),
- {[{get_hd,Rsrc,Rhd},{get_tl,Rsrc,Rtl}],Aft,St}
- end.
-
-guard_clause_cg(#k_guard_clause{anno=#l{vdb=Vdb},guard=G,body=B}, Fail, Bef, St0) ->
- {Gis,Int,St1} = guard_cg(G, Fail, Vdb, Bef, St0),
- {Bis,Aft,St} = match_cg(B, Fail, Int, St1),
- {Gis ++ Bis,Aft,St}.
-
-%% guard_cg(Guard, Fail, Vdb, StackReg, State) ->
-%% {[Ainstr],StackReg,State}.
-%% A guard is a boolean expression of tests. Tests return true or
-%% false. A fault in a test causes the test to return false. Tests
-%% never return the boolean, instead we generate jump code to go to
-%% the correct exit point. Primops and tests all go to the next
-%% instruction on success or jump to a failure label.
-
-guard_cg(#k_protected{arg=Ts,ret=Rs,anno=#l{vdb=Pdb}}, Fail, _Vdb, Bef, St) ->
- protected_cg(Ts, Rs, Fail, Pdb, Bef, St);
-guard_cg(#k_test{anno=#l{i=I},op=Test0,args=As,inverted=Inverted},
- Fail, Vdb, Bef, St0) ->
- #k_remote{mod=#k_atom{val=erlang},name=#k_atom{val=Test}} = Test0,
- case Inverted of
- false ->
- test_cg(Test, As, Fail, I, Vdb, Bef, St0);
- true ->
- {Psucc,St1} = new_label(St0),
- {Is,Aft,St2} = test_cg(Test, As, Psucc, I, Vdb, Bef, St1),
- {Is++[{jump,{f,Fail}},{label,Psucc}],Aft,St2}
- end;
-guard_cg(G, _Fail, Vdb, Bef, St) ->
- %%ok = io:fwrite("cg ~w: ~p~n", [?LINE,{G,Fail,Vdb,Bef}]),
- {Gis,Aft,St1} = cg(G, Vdb, Bef, St),
- %%ok = io:fwrite("cg ~w: ~p~n", [?LINE,{Aft}]),
- {Gis,Aft,St1}.
-
-%% guard_cg_list([Kexpr], Fail, I, Vdb, StackReg, St) ->
-%% {[Ainstr],StackReg,St}.
-
-guard_cg_list(Kes, Fail, Vdb, Bef, St0) ->
- {Keis,{Aft,St1}} =
- flatmapfoldl(fun (Ke, {Inta,Sta}) ->
- {Keis,Intb,Stb} =
- guard_cg(Ke, Fail, Vdb, Inta, Sta),
- {Keis,{Intb,Stb}}
- end, {Bef,St0}, need_heap(Kes)),
- {Keis,Aft,St1}.
-
-%% protected_cg([Kexpr], [Ret], Fail, I, Vdb, Bef, St) -> {[Ainstr],Aft,St}.
-%% Do a protected. Protecteds without return values are just done
-%% for effect, the return value is not checked, success passes on to
-%% the next instruction and failure jumps to Fail. If there are
-%% return values then these must be set to 'false' on failure,
-%% control always passes to the next instruction.
-
-protected_cg(Ts, [], Fail, Vdb, Bef, St0) ->
- %% Protect these calls, revert when done.
- {Tis,Aft,St1} = guard_cg_list(Ts, Fail, Vdb, Bef, St0#cg{bfail=Fail}),
- {Tis,Aft,St1#cg{bfail=St0#cg.bfail}};
-protected_cg(Ts, Rs, _Fail, Vdb, Bef, St0) ->
- {Pfail,St1} = new_label(St0),
- {Psucc,St2} = new_label(St1),
- {Tis,Aft,St3} = guard_cg_list(Ts, Pfail, Vdb, Bef,
- St2#cg{bfail=Pfail}),
- %%ok = io:fwrite("cg ~w: ~p~n", [?LINE,{Rs,I,Vdb,Aft}]),
- %% Set return values to false.
- Mis = [{move,{atom,false},fetch_var(V,Aft)}||#k_var{name=V} <- Rs],
- {Tis ++ [{jump,{f,Psucc}},
- {label,Pfail}] ++ Mis ++ [{label,Psucc}],
- Aft,St3#cg{bfail=St0#cg.bfail}}.
-
-%% test_cg(TestName, Args, Fail, I, Vdb, Bef, St) -> {[Ainstr],Aft,St}.
-%% Generate test instruction. Use explicit fail label here.
-
-test_cg(is_map, [A], Fail, I, Vdb, Bef, St) ->
- %% We must avoid creating code like this:
- %%
- %% move x(0) y(0)
- %% is_map Fail [x(0)]
- %% make_fun => x(0) %% Overwrite x(0)
- %% put_map_assoc y(0) ...
- %%
- %% The code is safe, but beam_validator does not understand that.
- %% Extending beam_validator to handle such (rare) code as the
- %% above would make it slower for all programs. Instead, change
- %% the code generator to always prefer the Y register for is_map()
- %% and put_map_assoc() instructions, ensuring that they use the
- %% same register.
- Arg = cg_reg_arg_prefer_y(A, Bef),
- Aft = clear_dead(Bef, I, Vdb),
- {[{test,is_map,{f,Fail},[Arg]}],Aft,St};
-test_cg(is_boolean, [#k_atom{val=Val}], Fail, I, Vdb, Bef, St) ->
- Aft = clear_dead(Bef, I, Vdb),
- Is = case is_boolean(Val) of
- true -> [];
- false -> [{jump,{f,Fail}}]
- end,
- {Is,Aft,St};
-test_cg(Test, As, Fail, I, Vdb, Bef, St) ->
- Args = cg_reg_args(As, Bef),
- Aft = clear_dead(Bef, I, Vdb),
- {[beam_utils:bif_to_test(Test, Args, {f,Fail})],Aft,St}.
-
-%% match_fmf(Fun, LastFail, State, [Clause]) -> {Is,Aft,State}.
-%% This is a special flatmapfoldl for match code gen where we
-%% generate a "failure" label for each clause. The last clause uses
-%% an externally generated failure label, LastFail. N.B. We do not
-%% know or care how the failure labels are used.
-
-match_fmf(F, LastFail, St, [H]) ->
- F(H, LastFail, St);
-match_fmf(F, LastFail, St0, [H|T]) ->
- {Fail,St1} = new_label(St0),
- {R,Aft1,St2} = F(H, Fail, St1),
- {Rs,Aft2,St3} = match_fmf(F, LastFail, St2, T),
- {R ++ [{label,Fail}] ++ Rs,sr_merge(Aft1, Aft2),St3}.
-
-%% call_cg(Func, [Arg], [Ret], Le, Vdb, StackReg, State) ->
-%% {[Ainstr],StackReg,State}.
-%% enter_cg(Func, [Arg], Le, Vdb, Bef, St) -> {[Ainstr],Aft,St}.
-%% Call and enter first put the arguments into registers and save any
-%% other registers, then clean up and compress the stack and set the
-%% frame size. Finally the actual call is made. Call then needs the
-%% return values filled in.
-
-call_cg(#k_var{}=Var, As, Rs, Le, Vdb, Bef, St0) ->
- {Sis,Int} = cg_setup_call(As++[Var], Bef, Le#l.i, Vdb),
- %% Put return values in registers.
- Reg = load_vars(Rs, clear_regs(Int#sr.reg)),
- %% Build complete code and final stack/register state.
- Arity = length(As),
- {Frees,Aft} = free_dead(clear_dead(Int#sr{reg=Reg}, Le#l.i, Vdb)),
- {Sis ++ Frees ++ [line(Le),{call_fun,Arity}],Aft,
- need_stack_frame(St0)};
-call_cg(#k_remote{mod=Mod,name=Name}, As, Rs, Le, Vdb, Bef, St0)
- when is_record(Mod, k_var); is_record(Name, k_var) ->
- {Sis,Int} = cg_setup_call(As++[Mod,Name], Bef, Le#l.i, Vdb),
- %% Put return values in registers.
- Reg = load_vars(Rs, clear_regs(Int#sr.reg)),
- %% Build complete code and final stack/register state.
- Arity = length(As),
- St = need_stack_frame(St0),
- %%{Call,St1} = build_call(Func, Arity, St0),
- {Frees,Aft} = free_dead(clear_dead(Int#sr{reg=Reg}, Le#l.i, Vdb)),
- {Sis ++ Frees ++ [line(Le),{apply,Arity}],Aft,St};
-call_cg(Func, As, Rs, Le, Vdb, Bef, St0) ->
- case St0 of
- #cg{bfail=Fail} when Fail =/= 0 ->
- %% Inside a guard. The only allowed function call is to
- %% erlang:error/1,2. We will generate the following code:
- %%
- %% move {atom,ok} DestReg
- %% jump FailureLabel
- #k_remote{mod=#k_atom{val=erlang},
- name=#k_atom{val=error}} = Func, %Assertion.
- [#k_var{name=DestVar}] = Rs,
- Int0 = clear_dead(Bef, Le#l.i, Vdb),
- Reg = put_reg(DestVar, Int0#sr.reg),
- Int = Int0#sr{reg=Reg},
- Dst = fetch_reg(DestVar, Reg),
- {[{move,{atom,ok},Dst},{jump,{f,Fail}}],
- clear_dead(Int, Le#l.i, Vdb),St0};
- #cg{} ->
- %% Ordinary function call in a function body.
- {Sis,Int} = cg_setup_call(As, Bef, Le#l.i, Vdb),
- %% Put return values in registers.
- Reg = load_vars(Rs, clear_regs(Int#sr.reg)),
- %% Build complete code and final stack/register state.
- Arity = length(As),
- {Call,St1} = build_call(Func, Arity, St0),
- {Frees,Aft} = free_dead(clear_dead(Int#sr{reg=Reg}, Le#l.i, Vdb)),
- {Sis ++ Frees ++ [line(Le)|Call],Aft,St1}
- end.
-
-build_call(#k_remote{mod=#k_atom{val=erlang},name=#k_atom{val='!'}}, 2, St0) ->
- {[send],need_stack_frame(St0)};
-build_call(#k_remote{mod=#k_atom{val=Mod},name=#k_atom{val=Name}}, Arity, St0) ->
- {[{call_ext,Arity,{extfunc,Mod,Name,Arity}}],need_stack_frame(St0)};
-build_call(#k_local{name=Name}, Arity, St0) when is_atom(Name) ->
- {Lbl,St1} = local_func_label(Name, Arity, need_stack_frame(St0)),
- {[{call,Arity,{f,Lbl}}],St1}.
-
-free_dead(#sr{stk=Stk0}=Aft) ->
- {Instr,Stk} = free_dead(Stk0, 0, [], []),
- {Instr,Aft#sr{stk=Stk}}.
-
-free_dead([dead|Stk], Y, Instr, StkAcc) ->
- %% Note: kill/1 is equivalent to init/1 (translated by beam_asm).
- %% We use kill/1 to help further optimisation passes.
- free_dead(Stk, Y+1, [{kill,{yy,Y}}|Instr], [free|StkAcc]);
-free_dead([Any|Stk], Y, Instr, StkAcc) ->
- free_dead(Stk, Y+1, Instr, [Any|StkAcc]);
-free_dead([], _, Instr, StkAcc) -> {Instr,reverse(StkAcc)}.
-
-enter_cg(#k_var{} = Var, As, Le, Vdb, Bef, St0) ->
- {Sis,Int} = cg_setup_call(As++[Var], Bef, Le#l.i, Vdb),
- %% Build complete code and final stack/register state.
- Arity = length(As),
- {Sis ++ [line(Le),{call_fun,Arity},return],
- clear_dead(Int#sr{reg=clear_regs(Int#sr.reg)}, Le#l.i, Vdb),
- need_stack_frame(St0)};
-enter_cg(#k_remote{mod=Mod,name=Name}, As, Le, Vdb, Bef, St0)
- when is_record(Mod, k_var); is_record(Name, k_var) ->
- {Sis,Int} = cg_setup_call(As++[Mod,Name], Bef, Le#l.i, Vdb),
- %% Build complete code and final stack/register state.
- Arity = length(As),
- St = need_stack_frame(St0),
- {Sis ++ [line(Le),{apply_only,Arity}],
- clear_dead(Int#sr{reg=clear_regs(Int#sr.reg)}, Le#l.i, Vdb),
- St};
-enter_cg(Func, As, Le, Vdb, Bef, St0) ->
- {Sis,Int} = cg_setup_call(As, Bef, Le#l.i, Vdb),
- %% Build complete code and final stack/register state.
- Arity = length(As),
- {Call,St1} = build_enter(Func, Arity, St0),
- Line = enter_line(Func, Arity, Le),
- {Sis ++ Line ++ Call,
- clear_dead(Int#sr{reg=clear_regs(Int#sr.reg)}, Le#l.i, Vdb),
- St1}.
-
-build_enter(#k_remote{mod=#k_atom{val=erlang},name=#k_atom{val='!'}}, 2, St0) ->
- {[send,return],need_stack_frame(St0)};
-build_enter(#k_remote{mod=#k_atom{val=Mod},name=#k_atom{val=Name}}, Arity, St0) ->
- St1 = case trap_bif(Mod, Name, Arity) of
- true -> need_stack_frame(St0);
- false -> St0
- end,
- {[{call_ext_only,Arity,{extfunc,Mod,Name,Arity}}],St1};
-build_enter(#k_local{name=Name}, Arity, St0) when is_atom(Name) ->
- {Lbl,St1} = local_func_label(Name, Arity, St0),
- {[{call_only,Arity,{f,Lbl}}],St1}.
-
-enter_line(#k_remote{mod=#k_atom{val=Mod},name=#k_atom{val=Name}}, Arity, Le) ->
- case erl_bifs:is_safe(Mod, Name, Arity) of
- false ->
- %% Tail-recursive call, possibly to a BIF.
- %% We'll need a line instruction in case the
- %% BIF call fails.
- [line(Le)];
- true ->
- %% Call to a safe BIF. Since it cannot fail,
- %% we don't need any line instruction here.
- []
- end;
-enter_line(_, _, _) ->
- %% Tail-recursive call to a local function. A line
- %% instruction will not be useful.
- [].
-
-%% local_func_label(Name, Arity, State) -> {Label,State'}
-%% local_func_label({Name,Arity}, State) -> {Label,State'}
-%% Get the function entry label for a local function.
-
-local_func_label(Name, Arity, St) ->
- local_func_label({Name,Arity}, St).
-
-local_func_label(Key, #cg{functable=Map}=St0) ->
- case Map of
- #{Key := Label} -> {Label,St0};
- _ ->
- {Label,St} = new_label(St0),
- {Label,St#cg{functable=Map#{Key => Label}}}
- end.
-
-%% need_stack_frame(State) -> State'
-%% Make a note in the state that this function will need a stack frame.
-
-need_stack_frame(#cg{need_frame=true}=St) -> St;
-need_stack_frame(St) -> St#cg{need_frame=true}.
-
-%% trap_bif(Mod, Name, Arity) -> true|false
-%% Trap bifs that need a stack frame.
-
-trap_bif(erlang, link, 1) -> true;
-trap_bif(erlang, unlink, 1) -> true;
-trap_bif(erlang, monitor_node, 2) -> true;
-trap_bif(erlang, group_leader, 2) -> true;
-trap_bif(erlang, exit, 2) -> true;
-trap_bif(_, _, _) -> false.
-
-%% bif_cg(#k_bif{}, Le, Vdb, StackReg, State) ->
-%% {[Ainstr],StackReg,State}.
-%% Generate code a BIF.
-
-bif_cg(#k_bif{op=#k_internal{name=Name},args=As,ret=Rs}, Le, Vdb, Bef, St) ->
- internal_cg(Name, As, Rs, Le, Vdb, Bef, St);
-bif_cg(#k_bif{op=#k_remote{mod=#k_atom{val=erlang},name=#k_atom{val=Name}},
- args=As,ret=Rs}, Le, Vdb, Bef, St) ->
- Ar = length(As),
- case is_gc_bif(Name, Ar) of
- false ->
- bif_cg(Name, As, Rs, Le, Vdb, Bef, St);
- true ->
- gc_bif_cg(Name, As, Rs, Le, Vdb, Bef, St)
- end.
-
-%% internal_cg(Bif, [Arg], [Ret], Le, Vdb, StackReg, State) ->
-%% {[Ainstr],StackReg,State}.
-
-internal_cg(bs_context_to_binary=Instr, [Src0], [], Le, Vdb, Bef, St0) ->
- [Src] = cg_reg_args([Src0], Bef),
- {[{Instr,Src}],clear_dead(Bef, Le#l.i, Vdb), St0};
-internal_cg(dsetelement, [Index0,Tuple0,New0], _Rs, Le, Vdb, Bef, St0) ->
- [New,Tuple,{integer,Index1}] = cg_reg_args([New0,Tuple0,Index0], Bef),
- Index = Index1-1,
- {[{set_tuple_element,New,Tuple,Index}],
- clear_dead(Bef, Le#l.i, Vdb), St0};
-internal_cg(make_fun, [Func0,Arity0|As], Rs, Le, Vdb, Bef, St0) ->
- %% This behaves more like a function call.
- #k_atom{val=Func} = Func0,
- #k_int{val=Arity} = Arity0,
- {Sis,Int} = cg_setup_call(As, Bef, Le#l.i, Vdb),
- Reg = load_vars(Rs, clear_regs(Int#sr.reg)),
- {FuncLbl,St1} = local_func_label(Func, Arity, St0),
- MakeFun = {make_fun2,{f,FuncLbl},0,0,length(As)},
- {Sis ++ [MakeFun],
- clear_dead(Int#sr{reg=Reg}, Le#l.i, Vdb),
- St1};
-internal_cg(bs_init_writable=I, As, Rs, Le, Vdb, Bef, St) ->
- %% This behaves like a function call.
- {Sis,Int} = cg_setup_call(As, Bef, Le#l.i, Vdb),
- Reg = load_vars(Rs, clear_regs(Int#sr.reg)),
- {Sis++[I],clear_dead(Int#sr{reg=Reg}, Le#l.i, Vdb),St};
-internal_cg(build_stacktrace=I, As, Rs, Le, Vdb, Bef, St) ->
- %% This behaves like a function call.
- {Sis,Int} = cg_setup_call(As, Bef, Le#l.i, Vdb),
- Reg = load_vars(Rs, clear_regs(Int#sr.reg)),
- {Sis++[I],clear_dead(Int#sr{reg=Reg}, Le#l.i, Vdb),St};
-internal_cg(raise, As, Rs, Le, Vdb, Bef, St) ->
- %% raise can be treated like a guard BIF.
- bif_cg(raise, As, Rs, Le, Vdb, Bef, St);
-internal_cg(guard_error, [ExitCall], _Rs, Le, Vdb, Bef, St) ->
- %% A call an exit BIF from inside a #k_guard_match{}.
- %% Generate a standard call, but leave the register descriptors
- %% alone, effectively pretending that there was no call.
- #k_call{op=#k_remote{mod=#k_atom{val=Mod},name=#k_atom{val=Name}},
- args=As} = ExitCall,
- Arity = length(As),
- {Ms,_} = cg_call_args(As, Bef, Le#l.i, Vdb),
- Call = {call_ext,Arity,{extfunc,Mod,Name,Arity}},
- Is = Ms++[line(Le),Call],
- {Is,Bef,St};
-internal_cg(raw_raise=I, As, Rs, Le, Vdb, Bef, St) ->
- %% This behaves like a function call.
- {Sis,Int} = cg_setup_call(As, Bef, Le#l.i, Vdb),
- Reg = load_vars(Rs, clear_regs(Int#sr.reg)),
- {Sis++[I],clear_dead(Int#sr{reg=Reg}, Le#l.i, Vdb),St}.
-
-%% bif_cg(Bif, [Arg], [Ret], Le, Vdb, StackReg, State) ->
-%% {[Ainstr],StackReg,State}.
-
-bif_cg(Bif, As, [#k_var{name=V}], Le, Vdb, Bef, St0) ->
- Ars = cg_reg_args(As, Bef),
-
- %% If we are inside a catch and in a body (not in guard) and the
- %% BIF may fail, we must save everything that will be alive after
- %% the catch (because the code after the code assumes that all
- %% variables that are live are stored on the stack).
- %%
- %% Currently, we are somewhat pessimistic in
- %% that we save any variable that will be live after this BIF call.
-
- MayFail = not erl_bifs:is_safe(erlang, Bif, length(As)),
- {Sis,Int0} =
- case MayFail of
- true ->
- maybe_adjust_stack(Bef, Le#l.i, Le#l.i+1, Vdb, St0);
- false ->
- {[],Bef}
- end,
- Int1 = clear_dead(Int0, Le#l.i, Vdb),
- Reg = put_reg(V, Int1#sr.reg),
- Int = Int1#sr{reg=Reg},
- Dst = fetch_reg(V, Reg),
- BifFail = {f,St0#cg.bfail},
- %% We need a line instructions for BIFs that may fail in a body.
- Line = case BifFail of
- {f,0} when MayFail ->
- [line(Le)];
- _ ->
- []
- end,
- {Sis++Line++[{bif,Bif,BifFail,Ars,Dst}],
- clear_dead(Int, Le#l.i, Vdb), St0}.
-
-
-%% gc_bif_cg(Bif, [Arg], [Ret], Le, Vdb, StackReg, State) ->
-%% {[Ainstr],StackReg,State}.
-
-gc_bif_cg(Bif, As, [#k_var{name=V}], Le, Vdb, Bef, St0) ->
- Ars = cg_reg_args(As, Bef),
-
- %% If we are inside a catch and in a body (not in guard) and the
- %% BIF may fail, we must save everything that will be alive after
- %% the catch (because the code after the code assumes that all
- %% variables that are live are stored on the stack).
- %%
- %% Currently, we are somewhat pessimistic in
- %% that we save any variable that will be live after this BIF call.
-
- {Sis,Int0} = maybe_adjust_stack(Bef, Le#l.i, Le#l.i+1, Vdb, St0),
-
- Int1 = clear_dead(Int0, Le#l.i, Vdb),
- Reg = put_reg(V, Int1#sr.reg),
- Int = Int1#sr{reg=Reg},
- Dst = fetch_reg(V, Reg),
- BifFail = {f,St0#cg.bfail},
- Line = case BifFail of
- {f,0} -> [line(Le)];
- {f,_} -> []
- end,
- {Sis++Line++[{gc_bif,Bif,BifFail,max_reg(Bef#sr.reg),Ars,Dst}],
- clear_dead(Int, Le#l.i, Vdb), St0}.
-
-%% recv_loop_cg(TimeOut, ReceiveVar, ReceiveMatch, TimeOutExprs,
-%% [Ret], Le, Vdb, Bef, St) -> {[Ainstr],Aft,St}.
-
-recv_loop_cg(Te, Rvar, Rm, Tes, Rs, Le, Vdb, Bef, St0) ->
- {Sis,Int0} = adjust_stack(Bef, Le#l.i, Le#l.i, Vdb),
- Int1 = Int0#sr{reg=clear_regs(Int0#sr.reg)},
- %% Get labels.
- {Rl,St1} = new_label(St0),
- {Tl,St2} = new_label(St1),
- {Bl,St3} = new_label(St2),
- St4 = St3#cg{break=Bl,recv=Rl}, %Set correct receive labels
- {Ris,Raft,St5} = cg_recv_mesg(Rvar, Rm, Tl, Int1, St4),
- {Wis,Taft,St6} = cg_recv_wait(Te, Tes, Le#l.i, Int1, St5),
- Int2 = sr_merge(Raft, Taft), %Merge stack/registers
- Reg = load_vars(Rs, Int2#sr.reg),
- {Sis ++ [line(Le)] ++ Ris ++ [{label,Tl}] ++ Wis ++ [{label,Bl}],
- clear_dead(Int2#sr{reg=Reg}, Le#l.i, Vdb),
- St6#cg{break=St0#cg.break,recv=St0#cg.recv}}.
-
-%% cg_recv_mesg( ) -> {[Ainstr],Aft,St}.
-
-cg_recv_mesg(#k_var{name=R}, Rm, Tl, Bef, St0) ->
- Int0 = Bef#sr{reg=put_reg(R, Bef#sr.reg)},
- Ret = fetch_reg(R, Int0#sr.reg),
- %% Int1 = clear_dead(Int0, I, Rm#l.vdb),
- Int1 = Int0,
- {Mis,Int2,St1} = match_cg(Rm, none, Int1, St0),
- {[{label,St1#cg.recv},{loop_rec,{f,Tl},Ret}|Mis],Int2,St1}.
-
-%% cg_recv_wait(Te, Tes, I, Vdb, Int2, St3) -> {[Ainstr],Aft,St}.
-
-cg_recv_wait(#k_atom{val=infinity}, #cg_block{anno=Le,es=Tes}, I, Bef, St0) ->
- %% We know that the 'after' body will never be executed.
- %% But to keep the stack and register information up to date,
- %% we will generate the code for the 'after' body, and then discard it.
- Int1 = clear_dead(Bef, I, Le#l.vdb),
- {_,Int2,St1} = cg_block(Tes, Le#l.vdb,
- Int1#sr{reg=clear_regs(Int1#sr.reg)}, St0),
- {[{wait,{f,St1#cg.recv}}],Int2,St1};
-cg_recv_wait(#k_int{val=0}, #cg_block{anno=Le,es=Tes}, _I, Bef, St0) ->
- {Tis,Int,St1} = cg_block(Tes, Le#l.vdb, Bef, St0),
- {[timeout|Tis],Int,St1};
-cg_recv_wait(Te, #cg_block{anno=Le,es=Tes}, I, Bef, St0) ->
- Reg = cg_reg_arg(Te, Bef),
- %% Must have empty registers here! Bug if anything in registers.
- Int0 = clear_dead(Bef, I, Le#l.vdb),
- {Tis,Int,St1} = cg_block(Tes, Le#l.vdb,
- Int0#sr{reg=clear_regs(Int0#sr.reg)}, St0),
- {[{wait_timeout,{f,St1#cg.recv},Reg},timeout] ++ Tis,Int,St1}.
-
-%% recv_next_cg(Le, Vdb, StackReg, St) -> {[Ainstr],StackReg,St}.
-%% Use adjust stack to clear stack, but only need it for Aft.
-
-recv_next_cg(Le, Vdb, Bef, St) ->
- {Sis,Aft} = adjust_stack(Bef, Le#l.i, Le#l.i+1, Vdb),
- {[{loop_rec_end,{f,St#cg.recv}}] ++ Sis,Aft,St}. %Joke
-
-%% try_cg(TryBlock, [BodyVar], TryBody, [ExcpVar], TryHandler, [Ret],
-%% Le, Vdb, StackReg, St) -> {[Ainstr],StackReg,St}.
-
-try_cg(Ta, Vs, Tb, Evs, Th, Rs, Le, Vdb, Bef, St0) ->
- {B,St1} = new_label(St0), %Body label
- {H,St2} = new_label(St1), %Handler label
- {E,St3} = new_label(St2), %End label
- #l{i=TryTag} = get_kanno(Ta),
- Int1 = Bef#sr{stk=put_catch(TryTag, Bef#sr.stk)},
- TryReg = fetch_stack({catch_tag,TryTag}, Int1#sr.stk),
- {Ais,Int2,St4} = cg(Ta, Vdb, Int1, St3#cg{break=B,in_catch=true}),
- Int3 = Int2#sr{stk=drop_catch(TryTag, Int2#sr.stk)},
- St5 = St4#cg{break=E,in_catch=St3#cg.in_catch},
- {Bis,Baft,St6} = cg(Tb, Vdb, Int3#sr{reg=load_vars(Vs, Int3#sr.reg)}, St5),
- {His,Haft,St7} = cg(Th, Vdb, Int3#sr{reg=load_vars(Evs, Int3#sr.reg)}, St6),
- Int4 = sr_merge(Baft, Haft), %Merge stack/registers
- Aft = Int4#sr{reg=load_vars(Rs, Int4#sr.reg)},
- {[{'try',TryReg,{f,H}}] ++ Ais ++
- [{label,B},{try_end,TryReg}] ++ Bis ++
- [{label,H},{try_case,TryReg}] ++ His ++
- [{label,E}],
- clear_dead(Aft, Le#l.i, Vdb),
- St7#cg{break=St0#cg.break}}.
-
-try_enter_cg(Ta, Vs, Tb, Evs, Th, Le, Vdb, Bef, St0) ->
- {B,St1} = new_label(St0), %Body label
- {H,St2} = new_label(St1), %Handler label
- #l{i=TryTag} = get_kanno(Ta),
- Int1 = Bef#sr{stk=put_catch(TryTag, Bef#sr.stk)},
- TryReg = fetch_stack({catch_tag,TryTag}, Int1#sr.stk),
- {Ais,Int2,St3} = cg(Ta, Vdb, Int1, St2#cg{break=B,in_catch=true}),
- Int3 = Int2#sr{stk=drop_catch(TryTag, Int2#sr.stk)},
- St4 = St3#cg{in_catch=St2#cg.in_catch},
- {Bis,Baft,St5} = cg(Tb, Vdb, Int3#sr{reg=load_vars(Vs, Int3#sr.reg)}, St4),
- {His,Haft,St6} = cg(Th, Vdb, Int3#sr{reg=load_vars(Evs, Int3#sr.reg)}, St5),
- Int4 = sr_merge(Baft, Haft), %Merge stack/registers
- Aft = Int4,
- {[{'try',TryReg,{f,H}}] ++ Ais ++
- [{label,B},{try_end,TryReg}] ++ Bis ++
- [{label,H},{try_case,TryReg}] ++ His,
- clear_dead(Aft, Le#l.i, Vdb),
- St6#cg{break=St0#cg.break}}.
-
-%% catch_cg(CatchBlock, Ret, Le, Vdb, Bef, St) -> {[Ainstr],Aft,St}.
-
-catch_cg(#cg_block{es=C}, #k_var{name=R}, Le, Vdb, Bef, St0) ->
- {B,St1} = new_label(St0),
- CatchTag = Le#l.i,
- Int1 = Bef#sr{stk=put_catch(CatchTag, Bef#sr.stk)},
- CatchReg = fetch_stack({catch_tag,CatchTag}, Int1#sr.stk),
- {Cis,Int2,St2} = cg_block(C, Le#l.vdb, Int1,
- St1#cg{break=B,in_catch=true}),
- [] = Int2#sr.reg, %Assertion.
- Aft = Int2#sr{reg=[{0,R}],stk=drop_catch(CatchTag, Int2#sr.stk)},
- {[{'catch',CatchReg,{f,B}}] ++ Cis ++
- [{label,B},{catch_end,CatchReg}],
- clear_dead(Aft, Le#l.i, Vdb),
- St2#cg{break=St1#cg.break,in_catch=St1#cg.in_catch}}.
-
-%% put_cg([Var], Constr, Le, Vdb, Bef, St) -> {[Ainstr],Aft,St}.
-%% We have to be careful how a 'put' works. First the structure is
-%% built, then it is filled and finally things can be cleared. The
-%% annotation must reflect this and make sure that the return
-%% variable is allocated first.
-%%
-%% put_list and put_map are atomic instructions, both of
-%% which can safely resuse one of the source registers as target.
-
-put_cg([#k_var{name=R}], #k_cons{hd=Hd,tl=Tl}, Le, Vdb, Bef, St) ->
- [S1,S2] = cg_reg_args([Hd,Tl], Bef),
- Int0 = clear_dead(Bef, Le#l.i, Vdb),
- Int1 = Int0#sr{reg=put_reg(R, Int0#sr.reg)},
- Ret = fetch_reg(R, Int1#sr.reg),
- {[{put_list,S1,S2,Ret}], Int1, St};
-put_cg([#k_var{name=R}], #k_binary{segs=Segs}, Le, Vdb, Bef,
- #cg{bfail=Bfail}=St) ->
- %% At run-time, binaries are constructed in three stages:
- %% 1) First the size of the binary is calculated.
- %% 2) Then the binary is allocated.
- %% 3) Then each field in the binary is constructed.
- %% For simplicity, we use the target register to also hold the
- %% size of the binary. Therefore the target register must *not*
- %% be one of the source registers.
-
- %% First allocate the target register.
- Int0 = Bef#sr{reg=put_reg(R, Bef#sr.reg)},
- Target = fetch_reg(R, Int0#sr.reg),
-
- %% Also allocate a scratch register for size calculations.
- Temp = find_scratch_reg(Int0#sr.reg),
-
- %% First generate the code that constructs each field.
- Fail = {f,Bfail},
- PutCode = cg_bin_put(Segs, Fail, Bef),
- {Sis,Int1} = maybe_adjust_stack(Int0, Le#l.i, Le#l.i+1, Vdb, St),
- MaxRegs = max_reg(Bef#sr.reg),
- Aft = clear_dead(Int1, Le#l.i, Vdb),
-
- %% Now generate the complete code for constructing the binary.
- Code = cg_binary(PutCode, Target, Temp, Fail, MaxRegs, Le#l.a),
- {Sis++Code,Aft,St};
-
-%% Map: single variable key.
-put_cg([#k_var{name=R}], #k_map{op=Op,var=Map,
- es=[#k_map_pair{key=#k_var{}=K,val=V}]},
- Le, Vdb, Bef, St0) ->
- {Sis,Int0} = maybe_adjust_stack(Bef, Le#l.i, Le#l.i+1, Vdb, St0),
-
- SrcReg = cg_reg_arg_prefer_y(Map, Int0),
- Line = line(Le#l.a),
-
- List = [cg_reg_arg(K,Int0),cg_reg_arg(V,Int0)],
-
- Live = max_reg(Bef#sr.reg),
-
- %% The target register can reuse one of the source registers.
- Aft0 = clear_dead(Int0, Le#l.i, Vdb),
- Aft = Aft0#sr{reg=put_reg(R, Aft0#sr.reg)},
- Target = fetch_reg(R, Aft#sr.reg),
-
- {Is,St1} = put_cg_map(Line, Op, SrcReg, Target, Live, List, St0),
- {Sis++Is,Aft,St1};
-
-%% Map: (possibly) multiple literal keys.
-put_cg([#k_var{name=R}], #k_map{op=Op,var=Map,es=Es}, Le, Vdb, Bef, St0) ->
-
- %% assert key literals
- [] = [Var || #k_map_pair{key=#k_var{}=Var} <- Es],
-
- {Sis,Int0} = maybe_adjust_stack(Bef, Le#l.i, Le#l.i+1, Vdb, St0),
- SrcReg = cg_reg_arg_prefer_y(Map, Int0),
- Line = line(Le#l.a),
-
- %% fetch registers for values to be put into the map
- List = flatmap(fun(#k_map_pair{key=K,val=V}) ->
- [atomic(K),cg_reg_arg(V, Int0)]
- end, Es),
-
- Live = max_reg(Bef#sr.reg),
-
- %% The target register can reuse one of the source registers.
- Aft0 = clear_dead(Int0, Le#l.i, Vdb),
- Aft = Aft0#sr{reg=put_reg(R, Aft0#sr.reg)},
- Target = fetch_reg(R, Aft#sr.reg),
-
- {Is,St1} = put_cg_map(Line, Op, SrcReg, Target, Live, List, St0),
- {Sis++Is,Aft,St1};
-
-%% Everything else.
-put_cg([#k_var{name=R}], Con, Le, Vdb, Bef, St) ->
- %% Find a place for the return register first.
- Int = Bef#sr{reg=put_reg(R, Bef#sr.reg)},
- Ret = fetch_reg(R, Int#sr.reg),
- Ais = case Con of
- #k_tuple{es=Es} ->
- [{put_tuple,length(Es),Ret}] ++ cg_build_args(Es, Bef);
- Other ->
- [{move,cg_reg_arg(Other, Int),Ret}]
- end,
- {Ais,clear_dead(Int, Le#l.i, Vdb),St}.
-
-
-put_cg_map(Line, Op0, SrcReg, Target, Live, List, St0) ->
- Bfail = St0#cg.bfail,
- Fail = {f,St0#cg.bfail},
- Op = case Op0 of
- assoc -> put_map_assoc;
- exact -> put_map_exact
- end,
- {OkLbl,St1} = new_label(St0),
- {BadLbl,St2} = new_label(St1),
- Is = if
- Bfail =:= 0 orelse Op =:= put_map_assoc ->
- [Line,{Op,{f,0},SrcReg,Target,Live,{list,List}}];
- true ->
- %% Ensure that Target is always set, even if
- %% the map update operation fails. That is necessary
- %% because Target may be included in a test_heap
- %% instruction.
- [Line,
- {Op,{f,BadLbl},SrcReg,Target,Live,{list,List}},
- {jump,{f,OkLbl}},
- {label,BadLbl},
- {move,{atom,ok},Target},
- {jump,Fail},
- {label,OkLbl}]
- end,
- {Is,St2}.
-
-%%%
-%%% Code generation for constructing binaries.
-%%%
-
-cg_binary([{bs_put_binary,Fail,{atom,all},U,_Flags,Src}|PutCode],
- Target, Temp, Fail, MaxRegs, Anno) ->
- Line = line(Anno),
- Live = cg_live(Target, MaxRegs),
- SzCode = cg_bitstr_size(PutCode, Target, Temp, Fail, Live),
- BinFlags = {field_flags,[]},
- Code = [Line|SzCode] ++
- [case member(single_use, Anno) of
- true ->
- {bs_private_append,Fail,Target,U,Src,BinFlags,Target};
- false ->
- {bs_append,Fail,Target,0,MaxRegs,U,Src,BinFlags,Target}
- end] ++ PutCode,
- cg_bin_opt(Code);
-cg_binary(PutCode, Target, Temp, Fail, MaxRegs, Anno) ->
- Line = line(Anno),
- Live = cg_live(Target, MaxRegs),
- {InitOp,SzCode} = cg_binary_size(PutCode, Target, Temp, Fail, Live),
-
- Code = [Line|SzCode] ++ [{InitOp,Fail,Target,0,MaxRegs,
- {field_flags,[]},Target}|PutCode],
- cg_bin_opt(Code).
-
-cg_live({x,X}, MaxRegs) when X =:= MaxRegs -> MaxRegs+1;
-cg_live({x,X}, MaxRegs) when X < MaxRegs -> MaxRegs.
-
-%% Generate code that calculate the size of the bitstr to be
-%% built in BITS.
-
-cg_bitstr_size(PutCode, Target, Temp, Fail, Live) ->
- {Bits,Es} = cg_bitstr_size_1(PutCode, 0, []),
- reverse(cg_gen_binsize(Es, Target, Temp, Fail, Live,
- [{move,{integer,Bits},Target}])).
-
-cg_bitstr_size_1([{bs_put_utf8,_,_,Src}|Next], Bits, Acc) ->
- cg_bitstr_size_1(Next, Bits, [{'*',{bs_utf8_size,Src},8}|Acc]);
-cg_bitstr_size_1([{bs_put_utf16,_,_,Src}|Next], Bits, Acc) ->
- cg_bitstr_size_1(Next, Bits, [{'*',{bs_utf16_size,Src},8}|Acc]);
-cg_bitstr_size_1([{bs_put_utf32,_,_,_}|Next], Bits, Acc) ->
- cg_bitstr_size_1(Next, Bits+32, Acc);
-cg_bitstr_size_1([{_,_,S,U,_,Src}|Next], Bits, Acc) ->
- case S of
- {integer,N} -> cg_bitstr_size_1(Next, Bits+N*U, Acc);
- {atom,all} -> cg_bitstr_size_1(Next, Bits, [{bit_size,Src}|Acc]);
- _ when U =:= 1 -> cg_bitstr_size_1(Next, Bits, [S|Acc]);
- _ -> cg_bitstr_size_1(Next, Bits, [{'*',S,U}|Acc])
- end;
-cg_bitstr_size_1([], Bits, Acc) -> {Bits,Acc}.
-
-%% Generate code that calculate the size of the bitstr to be
-%% built in BYTES or BITS (depending on what is easiest).
-
-cg_binary_size(PutCode, Target, Temp, Fail, Live) ->
- {InitInstruction,Szs} = cg_binary_size_1(PutCode, 0, []),
- SizeExpr = reverse(cg_gen_binsize(Szs, Target, Temp, Fail, Live, [{move,{integer,0},Target}])),
- {InitInstruction,SizeExpr}.
-
-cg_binary_size_1([{bs_put_utf8,_Fail,_Flags,Src}|T], Bits, Acc) ->
- cg_binary_size_1(T, Bits, [{8,{bs_utf8_size,Src}}|Acc]);
-cg_binary_size_1([{bs_put_utf16,_Fail,_Flags,Src}|T], Bits, Acc) ->
- cg_binary_size_1(T, Bits, [{8,{bs_utf16_size,Src}}|Acc]);
-cg_binary_size_1([{bs_put_utf32,_Fail,_Flags,_Src}|T], Bits, Acc) ->
- cg_binary_size_1(T, Bits+32, Acc);
-cg_binary_size_1([{_Put,_Fail,S,U,_Flags,Src}|T], Bits, Acc) ->
- cg_binary_size_2(S, U, Src, T, Bits, Acc);
-cg_binary_size_1([], Bits, Acc) ->
- Bytes = Bits div 8,
- RemBits = Bits rem 8,
- Sizes0 = sort([{1,{integer,RemBits}},{8,{integer,Bytes}}|Acc]),
- Sizes = filter(fun({_,{integer,0}}) -> false;
- (_) -> true end, Sizes0),
- case Sizes of
- [{1,_}|_] ->
- {bs_init_bits,cg_binary_bytes_to_bits(Sizes, [])};
- [{8,_}|_] ->
- {bs_init2,[E || {8,E} <- Sizes]};
- [] ->
- {bs_init_bits,[]}
- end.
-
-cg_binary_size_2({integer,N}, U, _, Next, Bits, Acc) ->
- cg_binary_size_1(Next, Bits+N*U, Acc);
-cg_binary_size_2({atom,all}, U, E, Next, Bits, Acc) ->
- if
- U rem 8 =:= 0 ->
- cg_binary_size_1(Next, Bits, [{8,{byte_size,E}}|Acc]);
- true ->
- cg_binary_size_1(Next, Bits, [{1,{bit_size,E}}|Acc])
- end;
-cg_binary_size_2(Reg, 1, _, Next, Bits, Acc) ->
- cg_binary_size_1(Next, Bits, [{1,Reg}|Acc]);
-cg_binary_size_2(Reg, 8, _, Next, Bits, Acc) ->
- cg_binary_size_1(Next, Bits, [{8,Reg}|Acc]);
-cg_binary_size_2(Reg, U, _, Next, Bits, Acc) ->
- cg_binary_size_1(Next, Bits, [{1,{'*',Reg,U}}|Acc]).
-
-cg_binary_bytes_to_bits([{8,{integer,N}}|T], Acc) ->
- cg_binary_bytes_to_bits(T, [{integer,8*N}|Acc]);
-cg_binary_bytes_to_bits([{8,{byte_size,Reg}}|T], Acc) ->
- cg_binary_bytes_to_bits(T, [{bit_size,Reg}|Acc]);
-cg_binary_bytes_to_bits([{8,Reg}|T], Acc) ->
- cg_binary_bytes_to_bits(T, [{'*',Reg,8}|Acc]);
-cg_binary_bytes_to_bits([{1,Sz}|T], Acc) ->
- cg_binary_bytes_to_bits(T, [Sz|Acc]);
-cg_binary_bytes_to_bits([], Acc) ->
- cg_binary_bytes_to_bits_1(sort(Acc)).
-
-cg_binary_bytes_to_bits_1([{integer,I},{integer,J}|T]) ->
- cg_binary_bytes_to_bits_1([{integer,I+J}|T]);
-cg_binary_bytes_to_bits_1([H|T]) ->
- [H|cg_binary_bytes_to_bits_1(T)];
-cg_binary_bytes_to_bits_1([]) -> [].
-
-cg_gen_binsize([{'*',{bs_utf8_size,Src},B}|T], Target, Temp, Fail, Live, Acc) ->
- Size = {bs_utf8_size,Fail,Src,Temp},
- Add = {bs_add,Fail,[Target,Temp,B],Target},
- cg_gen_binsize(T, Target, Temp, Fail, Live,
- [Add,Size|Acc]);
-cg_gen_binsize([{'*',{bs_utf16_size,Src},B}|T], Target, Temp, Fail, Live, Acc) ->
- Size = {bs_utf16_size,Fail,Src,Temp},
- Add = {bs_add,Fail,[Target,Temp,B],Target},
- cg_gen_binsize(T, Target, Temp, Fail, Live,
- [Add,Size|Acc]);
-cg_gen_binsize([{'*',A,B}|T], Target, Temp, Fail, Live, Acc) ->
- cg_gen_binsize(T, Target, Temp, Fail, Live,
- [{bs_add,Fail,[Target,A,B],Target}|Acc]);
-cg_gen_binsize([{bit_size,B}|T], Target, Temp, Fail, Live, Acc) ->
- cg_gen_binsize([Temp|T], Target, Temp, Fail, Live,
- [{gc_bif,bit_size,Fail,Live,[B],Temp}|Acc]);
-cg_gen_binsize([{byte_size,B}|T], Target, Temp, Fail, Live, Acc) ->
- cg_gen_binsize([Temp|T], Target, Temp, Fail, Live,
- [{gc_bif,byte_size,Fail,Live,[B],Temp}|Acc]);
-cg_gen_binsize([{bs_utf8_size,B}|T], Target, Temp, Fail, Live, Acc) ->
- cg_gen_binsize([Temp|T], Target, Temp, Fail, Live,
- [{bs_utf8_size,Fail,B,Temp}|Acc]);
-cg_gen_binsize([{bs_utf16_size,B}|T], Target, Temp, Fail, Live, Acc) ->
- cg_gen_binsize([Temp|T], Target, Temp, Fail, Live,
- [{bs_utf16_size,Fail,B,Temp}|Acc]);
-cg_gen_binsize([E0|T], Target, Temp, Fail, Live, Acc) ->
- cg_gen_binsize(T, Target, Temp, Fail, Live,
- [{bs_add,Fail,[Target,E0,1],Target}|Acc]);
-cg_gen_binsize([], _, _, _, _, Acc) -> Acc.
-
-
-%% cg_bin_opt(Code0) -> Code
-%% Optimize the size calculations for binary construction.
-
-cg_bin_opt([{move,S1,{x,X}=D},{gc_bif,Op,Fail,Live0,As,Dst}|Is]) ->
- Live = if
- X + 1 =:= Live0 -> X;
- true -> Live0
- end,
- [{gc_bif,Op,Fail,Live,As,D}|cg_bin_opt([{move,S1,Dst}|Is])];
-cg_bin_opt([{move,_,_}=I1,{Op,_,_,_}=I2|Is])
- when Op =:= bs_utf8_size orelse Op =:= bs_utf16_size ->
- [I2|cg_bin_opt([I1|Is])];
-cg_bin_opt([{bs_add,_,[{integer,0},Src,1],Dst}|Is]) ->
- cg_bin_opt_1([{move,Src,Dst}|Is]);
-cg_bin_opt([{bs_add,_,[Src,{integer,0},_],Dst}|Is]) ->
- cg_bin_opt_1([{move,Src,Dst}|Is]);
-cg_bin_opt(Is) ->
- cg_bin_opt_1(Is).
-
-cg_bin_opt_1([{move,Size,D},{bs_append,Fail,D,Extra,Regs,U,Bin,Flags,D}|Is]) ->
- [{bs_append,Fail,Size,Extra,Regs,U,Bin,Flags,D}|cg_bin_opt(Is)];
-cg_bin_opt_1([{move,Size,D},{bs_private_append,Fail,D,U,Bin,Flags,D}|Is]) ->
- [{bs_private_append,Fail,Size,U,Bin,Flags,D}|cg_bin_opt(Is)];
-cg_bin_opt_1([{move,Size,D},{Op,Fail,D,Extra,Regs,Flags,D}|Is])
- when Op =:= bs_init2; Op =:= bs_init_bits ->
- Bytes = case Size of
- {integer,Int} -> Int;
- _ -> Size
- end,
- [{Op,Fail,Bytes,Extra,Regs,Flags,D}|cg_bin_opt(Is)];
-cg_bin_opt_1([{move,S1,D},{bs_add,Fail,[D,S2,U],Dst}|Is]) ->
- cg_bin_opt([{bs_add,Fail,[S1,S2,U],Dst}|Is]);
-cg_bin_opt_1([{move,S1,D},{bs_add,Fail,[S2,D,U],Dst}|Is]) ->
- cg_bin_opt([{bs_add,Fail,[S2,S1,U],Dst}|Is]);
-cg_bin_opt_1([I|Is]) ->
- [I|cg_bin_opt(Is)];
-cg_bin_opt_1([]) ->
- [].
-
-cg_bin_put(#k_bin_seg{size=S0,unit=U,type=T,flags=Fs,seg=E0,next=Next},
- Fail, Bef) ->
- S1 = cg_reg_arg(S0, Bef),
- E1 = cg_reg_arg(E0, Bef),
- {Format,Op} = case T of
- integer -> {plain,bs_put_integer};
- utf8 -> {utf,bs_put_utf8};
- utf16 -> {utf,bs_put_utf16};
- utf32 -> {utf,bs_put_utf32};
- binary -> {plain,bs_put_binary};
- float -> {plain,bs_put_float}
- end,
- case Format of
- plain ->
- [{Op,Fail,S1,U,{field_flags,Fs},E1}|cg_bin_put(Next, Fail, Bef)];
- utf ->
- [{Op,Fail,{field_flags,Fs},E1}|cg_bin_put(Next, Fail, Bef)]
- end;
-cg_bin_put(#k_bin_end{}, _, _) -> [].
-
-cg_build_args(As, Bef) ->
- [{put,cg_reg_arg(A, Bef)} || A <- As].
-
-%% return_cg([Val], Le, Vdb, Bef, St) -> {[Ainstr],Aft,St}.
-%% break_cg([Val], Le, Vdb, Bef, St) -> {[Ainstr],Aft,St}.
-%% These are very simple, just put return/break values in registers
-%% from 0, then return/break. Use the call setup to clean up stack,
-%% but must clear registers to ensure sr_merge works correctly.
-
-return_cg(Rs, Le, Vdb, Bef, St) ->
- {Ms,Int} = cg_setup_call(Rs, Bef, Le#l.i, Vdb),
- {Ms ++ [return],Int#sr{reg=clear_regs(Int#sr.reg)},St}.
-
-break_cg(Bs, Le, Vdb, Bef, St) ->
- {Ms,Int} = cg_setup_call(Bs, Bef, Le#l.i, Vdb),
- {Ms ++ [{jump,{f,St#cg.break}}],
- Int#sr{reg=clear_regs(Int#sr.reg)},St}.
-
-guard_break_cg(Bs, #l{i=I}, Vdb, #sr{reg=Reg0}=Bef, St) ->
- #sr{reg=Reg1} = Int = clear_dead(Bef, I, Vdb),
- Reg2 = trim_free(Reg1),
- NumLocked = length(Reg2),
- Moves0 = gen_moves(Bs, Bef, NumLocked, []),
- Moves = order_moves(Moves0, find_scratch_reg(Reg0)),
- {BreakVars,_} = mapfoldl(fun(_, RegNum) ->
- {{RegNum,gbreakvar},RegNum+1}
- end, length(Reg2), Bs),
- Reg = Reg2 ++ BreakVars,
- Aft = Int#sr{reg=Reg},
- {Moves ++ [{jump,{f,St#cg.break}}],Aft,St}.
-
-%% cg_reg_arg(Arg0, Info) -> Arg
-%% cg_reg_args([Arg0], Info) -> [Arg]
-%% Convert argument[s] into registers. Literal values are returned unchanged.
-
-cg_reg_args(As, Bef) -> [cg_reg_arg(A, Bef) || A <- As].
-
-cg_reg_arg(#k_var{name=V}, Bef) -> fetch_var(V, Bef);
-cg_reg_arg(Literal, _) -> atomic(Literal).
-
-cg_reg_arg_prefer_y(#k_var{name=V}, Bef) -> fetch_var_prefer_y(V, Bef);
-cg_reg_arg_prefer_y(Literal, _) -> atomic(Literal).
-
-%% cg_setup_call([Arg], Bef, Cur, Vdb) -> {[Instr],Aft}.
-%% Do the complete setup for a call/enter.
-
-cg_setup_call(As, Bef, I, Vdb) ->
- {Ms,Int0} = cg_call_args(As, Bef, I, Vdb),
- %% Have set up arguments, can now clean up, compress and save to stack.
- Int1 = Int0#sr{stk=clear_dead_stk(Int0#sr.stk, I, Vdb),res=[]},
- {Sis,Int2} = adjust_stack(Int1, I, I+1, Vdb),
- {Ms ++ Sis,Int2}.
-
-%% cg_call_args([Arg], SrState) -> {[Instr],SrState}.
-%% Setup the arguments to a call/enter/bif. Put the arguments into
-%% consecutive registers starting at {x,0} moving any data which
-%% needs to be saved. Return a modified SrState structure with the
-%% new register contents. N.B. the resultant register info will
-%% contain non-variable values when there are non-variable values.
-%%
-%% This routine is complicated by unsaved values in x registers.
-%% We'll move away any unsaved values that are in the registers
-%% to be overwritten by the arguments.
-
-cg_call_args(As, Bef, I, Vdb) ->
- Regs0 = load_arg_regs(Bef#sr.reg, As),
- Unsaved = unsaved_registers(Regs0, Bef#sr.stk, I, I+1, Vdb),
- {UnsavedMoves,Regs} = move_unsaved(Unsaved, Bef#sr.reg, Regs0),
- Moves0 = gen_moves(As, Bef),
- Moves = order_moves(Moves0, find_scratch_reg(Regs)),
- {UnsavedMoves ++ Moves,Bef#sr{reg=Regs}}.
-
-%% load_arg_regs([Reg], Arguments) -> [Reg]
-%% Update the register descriptor to include the arguments (from {x,0}
-%% and upwards). Values in argument register are overwritten.
-%% Values in x registers above the arguments are preserved.
-
-load_arg_regs(Regs, As) -> load_arg_regs(Regs, As, 0).
-
-load_arg_regs([_|Rs], [#k_var{name=V}|As], I) -> [{I,V}|load_arg_regs(Rs, As, I+1)];
-load_arg_regs([_|Rs], [A|As], I) -> [{I,A}|load_arg_regs(Rs, As, I+1)];
-load_arg_regs([], [#k_var{name=V}|As], I) -> [{I,V}|load_arg_regs([], As, I+1)];
-load_arg_regs([], [A|As], I) -> [{I,A}|load_arg_regs([], As, I+1)];
-load_arg_regs(Rs, [], _) -> Rs.
-
-%% Returns the variables must be saved and are currently in the
-%% x registers that are about to be overwritten by the arguments.
-
-unsaved_registers(Regs, Stk, Fb, Lf, Vdb) ->
- [V || {V,F,L} <- Vdb,
- F < Fb,
- L >= Lf,
- not on_stack(V, Stk),
- not in_reg(V, Regs)].
-
-in_reg(V, Regs) -> keymember(V, 2, Regs).
-
-%% Move away unsaved variables from the registers that are to be
-%% overwritten by the arguments.
-move_unsaved(Vs, OrigRegs, NewRegs) ->
- move_unsaved(Vs, OrigRegs, NewRegs, []).
-
-move_unsaved([V|Vs], OrigRegs, NewRegs0, Acc) ->
- NewRegs = put_reg(V, NewRegs0),
- Src = fetch_reg(V, OrigRegs),
- Dst = fetch_reg(V, NewRegs),
- move_unsaved(Vs, OrigRegs, NewRegs, [{move,Src,Dst}|Acc]);
-move_unsaved([], _, Regs, Acc) -> {Acc,Regs}.
-
-%% gen_moves(As, Sr)
-%% Generate the basic move instruction to move the arguments
-%% to their proper registers. The list will be sorted on
-%% destinations. (I.e. the move to {x,0} will be first --
-%% see the comment to order_moves/2.)
-
-gen_moves(As, Sr) -> gen_moves(As, Sr, 0, []).
-
-gen_moves([#k_var{name=V}|As], Sr, I, Acc) ->
- case fetch_var(V, Sr) of
- {x,I} -> gen_moves(As, Sr, I+1, Acc);
- Reg -> gen_moves(As, Sr, I+1, [{move,Reg,{x,I}}|Acc])
- end;
-gen_moves([A0|As], Sr, I, Acc) ->
- A = atomic(A0),
- gen_moves(As, Sr, I+1, [{move,A,{x,I}}|Acc]);
-gen_moves([], _, _, Acc) -> lists:keysort(3, Acc).
-
-%% order_moves([Move], ScratchReg) -> [Move]
-%% Orders move instruction so that source registers are not
-%% destroyed before they are used. If there are cycles
-%% (such as {move,{x,0},{x,1}}, {move,{x,1},{x,1}}),
-%% the scratch register is used to break up the cycle.
-%% If possible, the first move of the input list is placed
-%% last in the result list (to make the move to {x,0} occur
-%% just before the call to allow the Beam loader to coalesce
-%% the instructions).
-
-order_moves(Ms, Scr) -> order_moves(Ms, Scr, []).
-
-order_moves([{move,_,_}=M|Ms0], ScrReg, Acc0) ->
- {Chain,Ms} = collect_chain(Ms0, [M], ScrReg),
- Acc = reverse(Chain, Acc0),
- order_moves(Ms, ScrReg, Acc);
-order_moves([], _, Acc) -> Acc.
-
-collect_chain(Ms, Path, ScrReg) ->
- collect_chain(Ms, Path, [], ScrReg).
-
-collect_chain([{move,Src,Same}=M|Ms0], [{move,Same,_}|_]=Path, Others, ScrReg) ->
- case lists:keyfind(Src, 3, Path) of
- false ->
- collect_chain(reverse(Others, Ms0), [M|Path], [], ScrReg);
- _ -> % We have a cycle.
- {break_up_cycle(M, Path, ScrReg),reverse(Others, Ms0)}
- end;
-collect_chain([M|Ms], Path, Others, ScrReg) ->
- collect_chain(Ms, Path, [M|Others], ScrReg);
-collect_chain([], Path, Others, _) ->
- {Path,Others}.
-
-break_up_cycle({move,Src,_}=M, Path, ScrReg) ->
- [{move,ScrReg,Src},M|break_up_cycle1(Src, Path, ScrReg)].
-
-break_up_cycle1(Dst, [{move,Src,Dst}|Path], ScrReg) ->
- [{move,Src,ScrReg}|Path];
-break_up_cycle1(Dst, [M|Path], LastMove) ->
- [M|break_up_cycle1(Dst, Path, LastMove)].
-
-%% clear_dead(Sr, Until, Vdb) -> Aft.
-%% Remove all variables in Sr which have died AT ALL so far.
-
-clear_dead(#sr{stk=Stk}=Sr0, Until, Vdb) ->
- Sr = Sr0#sr{reg=clear_dead_reg(Sr0, Until, Vdb),
- stk=clear_dead_stk(Stk, Until, Vdb)},
- reserve(Sr).
-
-clear_dead_reg(Sr, Until, Vdb) ->
- [case R of
- {_I,V} = IV ->
- case vdb_find(V, Vdb) of
- {V,_,L} when L > Until -> IV;
- _ -> free %Remove anything else
- end;
- {reserved,_I,_V}=Reserved -> Reserved;
- free -> free
- end || R <- Sr#sr.reg].
-
-clear_dead_stk(Stk, Until, Vdb) ->
- [case S of
- {V} = T ->
- case vdb_find(V, Vdb) of
- {V,_,L} when L > Until -> T;
- _ -> dead %Remove anything else
- end;
- free -> free;
- dead -> dead
- end || S <- Stk].
-
-
-%% sr_merge(Sr1, Sr2) -> Sr.
-%% Merge two stack/register states keeping the longest of both stack
-%% and register. Perform consistency check on both, elements must be
-%% the same. Allow frame size 'void' to make easy creation of
-%% "empty" frame.
-
-sr_merge(#sr{reg=R1,stk=S1,res=[]}, #sr{reg=R2,stk=S2,res=[]}) ->
- #sr{reg=longest(R1, R2),stk=longest(S1, S2),res=[]};
-sr_merge(void, S2) -> S2#sr{res=[]}.
-
-longest([H|T1], [H|T2]) -> [H|longest(T1, T2)];
-longest([dead|T1], [free|T2]) -> [dead|longest(T1, T2)];
-longest([free|T1], [dead|T2]) -> [dead|longest(T1, T2)];
-longest([dead|_] = L, []) -> L;
-longest([], [dead|_] = L) -> L;
-longest([free|_] = L, []) -> L;
-longest([], [free|_] = L) -> L;
-longest([], []) -> [].
-
-trim_free([R|Rs0]) ->
- case {trim_free(Rs0),R} of
- {[],free} -> [];
- {Rs,R} -> [R|Rs]
- end;
-trim_free([]) -> [].
-
-%% maybe_adjust_stack(Bef, FirstBefore, LastFrom, Vdb, St) -> {[Ainstr],Aft}.
-%% Adjust the stack, but only if the code is inside a catch and not
-%% inside a guard. Use this funtion before instructions that may
-%% cause an exception.
-
-maybe_adjust_stack(Bef, Fb, Lf, Vdb, St) ->
- case St of
- #cg{in_catch=true,bfail=0} ->
- adjust_stack(Bef, Fb, Lf, Vdb);
- #cg{} ->
- {[],Bef}
- end.
-
-%% adjust_stack(Bef, FirstBefore, LastFrom, Vdb) -> {[Ainstr],Aft}.
-%% Do complete stack adjustment by compressing stack and adding
-%% variables to be saved. Try to optimise ordering on stack by
-%% having reverse order to their lifetimes.
-%%
-%% In Beam, there is a fixed stack frame and no need to do stack compression.
-
-adjust_stack(Bef, Fb, Lf, Vdb) ->
- Stk0 = Bef#sr.stk,
- {Stk1,Saves} = save_stack(Stk0, Fb, Lf, Vdb),
- {saves(Saves, Bef#sr.reg, Stk1),
- Bef#sr{stk=Stk1}}.
-
-%% save_stack(Stack, FirstBefore, LastFrom, Vdb) -> {[SaveVar],NewStack}.
-%% Save variables which are used past current point and which are not
-%% already on the stack.
-
-save_stack(Stk0, Fb, Lf, Vdb) ->
- %% New variables that are in use but not on stack.
- New = new_not_on_stack(Stk0, Fb, Lf, Vdb),
-
- %% Add new variables that are not just dropped immediately.
- %% N.B. foldr works backwards from the end!!
- Saves = [V || {V,_,_} <- keysort(3, New)],
- Stk1 = foldr(fun (V, Stk) -> put_stack(V, Stk) end, Stk0, Saves),
- {Stk1,Saves}.
-
-%% new_not_on_stack(Stack, FirstBefore, LastFrom, Vdb) ->
-%% [{Variable,First,Last}]
-%% Return information about all variables that are used past current
-%% point and that are not already on the stack.
-
-new_not_on_stack(Stk, Fb, Lf, Vdb) ->
- [VFL || {V,F,L} = VFL <- Vdb,
- F < Fb,
- L >= Lf,
- not on_stack(V, Stk)].
-
-%% saves([SaveVar], Reg, Stk) -> [{move,Reg,Stk}].
-%% Generate move instructions to save variables onto stack. The
-%% stack/reg info used is that after the new stack has been made.
-
-saves(Ss, Reg, Stk) ->
- [{move,fetch_reg(V, Reg),fetch_stack(V, Stk)} || V <- Ss].
-
-%% fetch_var(VarName, StkReg) -> r{R} | sp{Sp}.
-%% find_var(VarName, StkReg) -> ok{r{R} | sp{Sp}} | error.
-%% Fetch/find a variable in either the registers or on the
-%% stack. Fetch KNOWS it's there.
-
-fetch_var(V, Sr) ->
- case find_reg(V, Sr#sr.reg) of
- {ok,R} -> R;
- error -> fetch_stack(V, Sr#sr.stk)
- end.
-
-fetch_var_prefer_y(V, #sr{reg=Reg,stk=Stk}) ->
- case find_stack(V, Stk) of
- {ok,R} -> R;
- error -> fetch_reg(V, Reg)
- end.
-
-load_vars(Vs, Regs) ->
- foldl(fun (#k_var{name=V}, Rs) -> put_reg(V, Rs) end, Regs, Vs).
-
-%% put_reg(Val, Regs) -> Regs.
-%% find_reg(Val, Regs) -> {ok,r{R}} | error.
-%% fetch_reg(Val, Regs) -> r{R}.
-%% Functions to interface the registers.
-
-% put_regs(Vs, Rs) -> foldl(fun put_reg/2, Rs, Vs).
-
-put_reg(V, Rs) -> put_reg_1(V, Rs, 0).
-
-put_reg_1(V, [free|Rs], I) -> [{I,V}|Rs];
-put_reg_1(V, [{reserved,I,V}|Rs], I) -> [{I,V}|Rs];
-put_reg_1(V, [R|Rs], I) -> [R|put_reg_1(V, Rs, I+1)];
-put_reg_1(V, [], I) -> [{I,V}].
-
-fetch_reg(V, [{I,V}|_]) -> {x,I};
-fetch_reg(V, [_|SRs]) -> fetch_reg(V, SRs).
-
-find_reg(V, [{I,V}|_]) -> {ok,{x,I}};
-find_reg(V, [_|SRs]) -> find_reg(V, SRs);
-find_reg(_, []) -> error.
-
-%% For the bit syntax, we need a scratch register if we are constructing
-%% a binary that will not be used.
-
-find_scratch_reg(Rs) -> find_scratch_reg(Rs, 0).
-
-find_scratch_reg([free|_], I) -> {x,I};
-find_scratch_reg([_|Rs], I) -> find_scratch_reg(Rs, I+1);
-find_scratch_reg([], I) -> {x,I}.
-
-replace_reg_contents(Old, New, [{I,Old}|Rs]) -> [{I,New}|Rs];
-replace_reg_contents(Old, New, [R|Rs]) -> [R|replace_reg_contents(Old, New, Rs)].
-
-%%clear_regs(Regs) -> map(fun (R) -> free end, Regs).
-clear_regs(_) -> [].
-
-max_reg(Regs) ->
- foldl(fun ({I,_}, _) -> I;
- (_, Max) -> Max end,
- -1, Regs) + 1.
-
-%% put_stack(Val, [{Val}]) -> [{Val}].
-%% fetch_stack(Var, Stk) -> sp{S}.
-%% find_stack(Var, Stk) -> ok{sp{S}} | error.
-%% Functions to interface the stack.
-
-put_stack(Val, []) -> [{Val}];
-put_stack(Val, [dead|Stk]) -> [{Val}|Stk];
-put_stack(Val, [free|Stk]) -> [{Val}|Stk];
-put_stack(Val, [NotFree|Stk]) -> [NotFree|put_stack(Val, Stk)].
-
-put_stack_carefully(Val, Stk0) ->
- try
- put_stack_carefully1(Val, Stk0)
- catch
- throw:error ->
- error
- end.
-
-put_stack_carefully1(_, []) -> throw(error);
-put_stack_carefully1(Val, [dead|Stk]) -> [{Val}|Stk];
-put_stack_carefully1(Val, [free|Stk]) -> [{Val}|Stk];
-put_stack_carefully1(Val, [NotFree|Stk]) ->
- [NotFree|put_stack_carefully1(Val, Stk)].
-
-fetch_stack(Var, Stk) -> fetch_stack(Var, Stk, 0).
-
-fetch_stack(V, [{V}|_], I) -> {yy,I};
-fetch_stack(V, [_|Stk], I) -> fetch_stack(V, Stk, I+1).
-
-find_stack(Var, Stk) -> find_stack(Var, Stk, 0).
-
-find_stack(V, [{V}|_], I) -> {ok,{yy,I}};
-find_stack(V, [_|Stk], I) -> find_stack(V, Stk, I+1);
-find_stack(_, [], _) -> error.
-
-on_stack(V, Stk) -> keymember(V, 1, Stk).
-
-%% put_catch(CatchTag, Stack) -> Stack'
-%% drop_catch(CatchTag, Stack) -> Stack'
-%% Special interface for putting and removing catch tags, to ensure that
-%% catches nest properly. Also used for try tags.
-
-put_catch(Tag, Stk0) -> put_catch(Tag, reverse(Stk0), []).
-
-put_catch(Tag, [], Stk) ->
- put_stack({catch_tag,Tag}, Stk);
-put_catch(Tag, [{{catch_tag,_}}|_]=RevStk, Stk) ->
- reverse(RevStk, put_stack({catch_tag,Tag}, Stk));
-put_catch(Tag, [Other|Stk], Acc) ->
- put_catch(Tag, Stk, [Other|Acc]).
-
-drop_catch(Tag, [{{catch_tag,Tag}}|Stk]) -> [free|Stk];
-drop_catch(Tag, [Other|Stk]) -> [Other|drop_catch(Tag, Stk)].
-
-%% atomic(Klit) -> Lit.
-%% atomic_list([Klit]) -> [Lit].
-
-atomic(#k_literal{val=V}) -> {literal,V};
-atomic(#k_int{val=I}) -> {integer,I};
-atomic(#k_float{val=F}) -> {float,F};
-atomic(#k_atom{val=A}) -> {atom,A};
-%%atomic(#k_char{val=C}) -> {char,C};
-atomic(#k_nil{}) -> nil.
-
-%% new_label(St) -> {L,St}.
-
-new_label(#cg{lcount=Next}=St) ->
- {Next,St#cg{lcount=Next+1}}.
-
-%% line(Le) -> {line,[] | {location,File,Line}}
-%% Create a line instruction, containing information about
-%% the current filename and line number. A line information
-%% instruction should be placed before any operation that could
-%% cause an exception.
-
-line(#l{a=Anno}) ->
- line(Anno);
-line([Line,{file,Name}]) when is_integer(Line) ->
- line_1(Name, Line);
-line([_|_]=A) ->
- {Name,Line} = find_loc(A, no_file, 0),
- line_1(Name, Line);
-line([]) ->
- {line,[]}.
-
-line_1(no_file, _) ->
- {line,[]};
-line_1(_, 0) ->
- %% Missing line number or line number 0.
- {line,[]};
-line_1(Name, Line) ->
- {line,[{location,Name,Line}]}.
-
-find_loc([Line|T], File, _) when is_integer(Line) ->
- find_loc(T, File, Line);
-find_loc([{file,File}|T], _, Line) ->
- find_loc(T, File, Line);
-find_loc([_|T], File, Line) ->
- find_loc(T, File, Line);
-find_loc([], File, Line) -> {File,Line}.
-
-flatmapfoldl(F, Accu0, [Hd|Tail]) ->
- {R,Accu1} = F(Hd, Accu0),
- {Rs,Accu2} = flatmapfoldl(F, Accu1, Tail),
- {R++Rs,Accu2};
-flatmapfoldl(_, Accu, []) -> {[],Accu}.
-
-%% Keep track of life time for variables.
-%%
-%% init_vars([{var,VarName}]) -> Vdb.
-%% new_vars([VarName], I, Vdb) -> Vdb.
-%% use_vars([VarName], I, Vdb) -> Vdb.
-%% add_var(VarName, F, L, Vdb) -> Vdb.
-%%
-%% The list of variable names for new_vars/3 and use_vars/3
-%% must be sorted.
-
-init_vars(Vs) ->
- vdb_new(Vs).
-
-new_vars([], _, Vdb) -> Vdb;
-new_vars([V], I, Vdb) -> vdb_store_new(V, {V,I,I}, Vdb);
-new_vars(Vs, I, Vdb) -> vdb_update_vars(Vs, Vdb, I).
-
-use_vars([], _, Vdb) ->
- Vdb;
-use_vars([V], I, Vdb) ->
- case vdb_find(V, Vdb) of
- {V,F,L} when I > L -> vdb_update(V, {V,F,I}, Vdb);
- {V,_,_} -> Vdb;
- error -> vdb_store_new(V, {V,I,I}, Vdb)
- end;
-use_vars(Vs, I, Vdb) -> vdb_update_vars(Vs, Vdb, I).
-
-add_var(V, F, L, Vdb) ->
- vdb_store_new(V, {V,F,L}, Vdb).
-
-%% vdb
-
-vdb_new(Vs) ->
- ordsets:from_list([{V,0,0} || #k_var{name=V} <- Vs]).
-
--type var() :: atom().
-
--spec vdb_find(var(), [vdb_entry()]) -> 'error' | vdb_entry().
-
-vdb_find(V, Vdb) ->
- case lists:keyfind(V, 1, Vdb) of
- false -> error;
- Vd -> Vd
- end.
-
-vdb_update(V, Update, [{V,_,_}|Vdb]) ->
- [Update|Vdb];
-vdb_update(V, Update, [Vd|Vdb]) ->
- [Vd|vdb_update(V, Update, Vdb)].
-
-vdb_store_new(V, New, [{V1,_,_}=Vd|Vdb]) when V > V1 ->
- [Vd|vdb_store_new(V, New, Vdb)];
-vdb_store_new(V, New, [{V1,_,_}|_]=Vdb) when V < V1 ->
- [New|Vdb];
-vdb_store_new(_, New, []) -> [New].
-
-vdb_update_vars([V|_]=Vs, [{V1,_,_}=Vd|Vdb], I) when V > V1 ->
- [Vd|vdb_update_vars(Vs, Vdb, I)];
-vdb_update_vars([V|Vs], [{V1,_,_}|_]=Vdb, I) when V < V1 ->
- %% New variable.
- [{V,I,I}|vdb_update_vars(Vs, Vdb, I)];
-vdb_update_vars([V|Vs], [{_,F,L}=Vd|Vdb], I) ->
- %% Existing variable.
- if
- I > L -> [{V,F,I}|vdb_update_vars(Vs, Vdb, I)];
- true -> [Vd|vdb_update_vars(Vs, Vdb, I)]
- end;
-vdb_update_vars([V|Vs], [], I) ->
- %% New variable.
- [{V,I,I}|vdb_update_vars(Vs, [], I)];
-vdb_update_vars([], Vdb, _) -> Vdb.
-
-%% vdb_sub(Min, Max, Vdb) -> Vdb.
-%% Extract variables which are used before and after Min. Lock
-%% variables alive after Max.
-
-vdb_sub(Min, Max, Vdb) ->
- [ if L >= Max -> {V,F,locked};
- true -> Vd
- end || {V,F,L}=Vd <- Vdb,
- F < Min,
- L >= Min ].
diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl
index c9517c3e51..3699c9d22e 100644
--- a/lib/compiler/src/v3_core.erl
+++ b/lib/compiler/src/v3_core.erl
@@ -228,7 +228,8 @@ function({function,_,Name,Arity,Cs0}, Ws0, File, Opts) ->
body(Cs0, Name, Arity, St0) ->
Anno = lineno_anno(element(2, hd(Cs0)), St0),
- {Args,St1} = new_vars(Anno, Arity, St0),
+ {Args0,St1} = new_vars(Anno, Arity, St0),
+ Args = reverse(Args0), %Nicer order
case clauses(Cs0, St1) of
{Cs1,[],St2} ->
{Ps,St3} = new_vars(Arity, St2), %Need new variables here
@@ -329,7 +330,7 @@ gexpr({protect,Line,Arg}, Bools0, St0) ->
{#iprotect{anno=#a{anno=Anno},body=Eps++[E]},[],Bools0,St}
end;
gexpr({op,_,'andalso',_,_}=E0, Bools, St0) ->
- {op,L,'andalso',E1,E2} = right_assoc(E0, 'andalso', St0),
+ {op,L,'andalso',E1,E2} = right_assoc(E0, 'andalso'),
Anno = lineno_anno(L, St0),
{#c_var{name=V0},St} = new_var(Anno, St0),
V = {var,L,V0},
@@ -337,7 +338,7 @@ gexpr({op,_,'andalso',_,_}=E0, Bools, St0) ->
E = make_bool_switch_guard(L, E1, V, E2, False),
gexpr(E, Bools, St);
gexpr({op,_,'orelse',_,_}=E0, Bools, St0) ->
- {op,L,'orelse',E1,E2} = right_assoc(E0, 'orelse', St0),
+ {op,L,'orelse',E1,E2} = right_assoc(E0, 'orelse'),
Anno = lineno_anno(L, St0),
{#c_var{name=V0},St} = new_var(Anno, St0),
V = {var,L,V0},
@@ -766,14 +767,16 @@ expr({op,_,'++',{lc,Llc,E,Qs0},More}, St0) ->
{Qs,St2} = preprocess_quals(Llc, Qs0, St1),
{Y,Yps,St} = lc_tq(Llc, E, Qs, Mc, St2),
{Y,Mps++Yps,St};
-expr({op,L,'andalso',E1,E2}, St0) ->
+expr({op,_,'andalso',_,_}=E0, St0) ->
+ {op,L,'andalso',E1,E2} = right_assoc(E0, 'andalso'),
Anno = lineno_anno(L, St0),
{#c_var{name=V0},St} = new_var(Anno, St0),
V = {var,L,V0},
False = {atom,L,false},
E = make_bool_switch(L, E1, V, E2, False, St0),
expr(E, St);
-expr({op,L,'orelse',E1,E2}, St0) ->
+expr({op,_,'orelse',_,_}=E0, St0) ->
+ {op,L,'orelse',E1,E2} = right_assoc(E0, 'orelse'),
Anno = lineno_anno(L, St0),
{#c_var{name=V0},St} = new_var(Anno, St0),
V = {var,L,V0},
@@ -2057,17 +2060,9 @@ fail_clause(Pats, Anno, Arg) ->
args=[Arg]}]}.
%% Optimization for Dialyzer.
-right_assoc(E, Op, St) ->
- case member(dialyzer, St#core.opts) of
- true ->
- right_assoc2(E, Op);
- false ->
- E
- end.
-
-right_assoc2({op,L1,Op,{op,L2,Op,E1,E2},E3}, Op) ->
- right_assoc2({op,L2,Op,E1,{op,L1,Op,E2,E3}}, Op);
-right_assoc2(E, _Op) -> E.
+right_assoc({op,L1,Op,{op,L2,Op,E1,E2},E3}, Op) ->
+ right_assoc({op,L2,Op,E1,{op,L1,Op,E2,E3}}, Op);
+right_assoc(E, _Op) -> E.
annotate_tuple(A, Es, St) ->
case member(dialyzer, St#core.opts) of
@@ -2626,7 +2621,8 @@ cfun(#ifun{anno=A,id=Id,vars=Args,clauses=Lcs,fc=Lfc}, _As, St0) ->
[],A#a.us,St2}.
c_call_erl(Fun, Args) ->
- cerl:c_call(cerl:c_atom(erlang), cerl:c_atom(Fun), Args).
+ As = [compiler_generated],
+ cerl:ann_c_call(As, cerl:c_atom(erlang), cerl:c_atom(Fun), Args).
%% lit_vars(Literal) -> [Var].
diff --git a/lib/compiler/src/v3_kernel.erl b/lib/compiler/src/v3_kernel.erl
index aef0b6cc9f..e2b8787224 100644
--- a/lib/compiler/src/v3_kernel.erl
+++ b/lib/compiler/src/v3_kernel.erl
@@ -82,8 +82,7 @@
-export([module/2,format_error/1]).
-import(lists, [map/2,foldl/3,foldr/3,mapfoldl/3,splitwith/2,member/2,
- keymember/3,keyfind/3,partition/2,droplast/1,last/1,sort/1,
- reverse/1]).
+ keyfind/3,partition/2,droplast/1,last/1,sort/1,reverse/1]).
-import(ordsets, [add_element/2,del_element/2,union/2,union/1,subtract/2]).
-import(cerl, [c_tuple/1]).
@@ -1415,8 +1414,6 @@ is_remote_bif(_, _, _) -> false.
%% return multiple values. Only used in bodies where a BIF may be
%% called for effect only.
-bif_vals(dsetelement, 3) -> 0;
-bif_vals(bs_context_to_binary, 1) -> 0;
bif_vals(_, _) -> 1.
bif_vals(_, _, _) -> 1.
@@ -1593,19 +1590,12 @@ match_var([U|Us], Cs0, Def, St) ->
%% constructor/constant as first argument. Group the constructors
%% according to type, the order is really irrelevant but tries to be
%% smart.
-
-match_con(Us, Cs0, Def, St) ->
- %% Expand literals at the top level.
- Cs = [expand_pat_lit_clause(C) || C <- Cs0],
- match_con_1(Us, Cs, Def, St).
-
-match_con_1([U|_Us] = L, Cs, Def, St0) ->
+match_con([U|_Us] = L, Cs, Def, St0) ->
%% Extract clauses for different constructors (types).
%%ok = io:format("match_con ~p~n", [Cs]),
- Ttcs0 = select_types([k_binary], Cs) ++ select_bin_con(Cs) ++
- select_types([k_cons,k_tuple,k_map,k_atom,k_float,
- k_int,k_nil], Cs),
- Ttcs = opt_single_valued(Ttcs0),
+ Ttcs0 = select_types(Cs, [], [], [], [], [], [], [], [], []),
+ Ttcs1 = [{T, Types} || {T, [_ | _] = Types} <- Ttcs0],
+ Ttcs = opt_single_valued(Ttcs1),
%%ok = io:format("ttcs = ~p~n", [Ttcs]),
{Scs,St1} =
mapfoldl(fun ({T,Tcs}, St) ->
@@ -1616,8 +1606,41 @@ match_con_1([U|_Us] = L, Cs, Def, St0) ->
St0, Ttcs),
{build_alt_1st_no_fail(build_select(U, Scs), Def),St1}.
-select_types(Types, Cs) ->
- [{T,Tcs} || T <- Types, begin Tcs = select(T, Cs), Tcs =/= [] end].
+select_types([NoExpC | Cs], Bin, BinCon, Cons, Tuple, Map, Atom, Float, Int, Nil) ->
+ C = expand_pat_lit_clause(NoExpC),
+ case clause_con(C) of
+ k_binary ->
+ select_types(Cs, [C |Bin], BinCon, Cons, Tuple, Map, Atom, Float, Int, Nil);
+ k_bin_seg ->
+ select_types(Cs, Bin, [C | BinCon], Cons, Tuple, Map, Atom, Float, Int, Nil);
+ k_bin_end ->
+ select_types(Cs, Bin, [C | BinCon], Cons, Tuple, Map, Atom, Float, Int, Nil);
+ k_cons ->
+ select_types(Cs, Bin, BinCon, [C | Cons], Tuple, Map, Atom, Float, Int, Nil);
+ k_tuple ->
+ select_types(Cs, Bin, BinCon, Cons, [C | Tuple], Map, Atom, Float, Int, Nil);
+ k_map ->
+ select_types(Cs, Bin, BinCon, Cons, Tuple, [C | Map], Atom, Float, Int, Nil);
+ k_atom ->
+ select_types(Cs, Bin, BinCon, Cons, Tuple, Map, [C | Atom], Float, Int, Nil);
+ k_float ->
+ select_types(Cs, Bin, BinCon, Cons, Tuple, Map, Atom, [C | Float], Int, Nil);
+ k_int ->
+ select_types(Cs, Bin, BinCon, Cons, Tuple, Map, Atom, Float, [C | Int], Nil);
+ k_nil ->
+ select_types(Cs, Bin, BinCon, Cons, Tuple, Map, Atom, Float, Int, [C | Nil])
+ end;
+select_types([], Bin, BinCon, Cons, Tuple, Map, Atom, Float, Int, Nil) ->
+ [{k_binary, reverse(Bin)}] ++ handle_bin_con(reverse(BinCon)) ++
+ [
+ {k_cons, reverse(Cons)},
+ {k_tuple, reverse(Tuple)},
+ {k_map, reverse(Map)},
+ {k_atom, reverse(Atom)},
+ {k_float, reverse(Float)},
+ {k_int, reverse(Int)},
+ {k_nil, reverse(Nil)}
+ ].
expand_pat_lit_clause(#iclause{pats=[#ialias{pat=#k_literal{anno=A,val=Val}}=Alias|Ps]}=C) ->
P = expand_pat_lit(Val, A),
@@ -1746,20 +1769,12 @@ combine_bin_segs(#k_bin_end{}) ->
combine_bin_segs(_) ->
throw(not_possible).
-%% select_bin_con([Clause]) -> [{Type,[Clause]}].
-%% Extract clauses for the k_bin_seg constructor. As k_bin_seg
+%% handle_bin_con([Clause]) -> [{Type,[Clause]}].
+%% Handle clauses for the k_bin_seg constructor. As k_bin_seg
%% matching can overlap, the k_bin_seg constructors cannot be
%% reordered, only grouped.
-select_bin_con(Cs0) ->
- Cs1 = lists:filter(fun (C) ->
- Con = clause_con(C),
- (Con =:= k_bin_seg) or (Con =:= k_bin_end)
- end, Cs0),
- select_bin_con_1(Cs1).
-
-
-select_bin_con_1(Cs) ->
+handle_bin_con(Cs) ->
try
%% The usual way to match literals is to first extract the
%% value to a register, and then compare the register to the
@@ -1798,14 +1813,14 @@ select_bin_con_1(Cs) ->
end
catch
throw:not_possible ->
- select_bin_con_2(Cs)
+ handle_bin_con_not_possible(Cs)
end.
-select_bin_con_2([C1|Cs]) ->
+handle_bin_con_not_possible([C1|Cs]) ->
Con = clause_con(C1),
{More,Rest} = splitwith(fun (C) -> clause_con(C) =:= Con end, Cs),
- [{Con,[C1|More]}|select_bin_con_2(Rest)];
-select_bin_con_2([]) -> [].
+ [{Con,[C1|More]}|handle_bin_con_not_possible(Rest)];
+handle_bin_con_not_possible([]) -> [].
%% select_bin_int([Clause]) -> {k_bin_int,[Clause]}
%% If the first pattern in each clause selects the same integer,
@@ -1905,10 +1920,6 @@ select_utf8(Val0) ->
throw(not_possible)
end.
-%% select(Con, [Clause]) -> [Clause].
-
-select(T, Cs) -> [ C || C <- Cs, clause_con(C) =:= T ].
-
%% 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.
@@ -2044,8 +2055,9 @@ get_match(#k_binary{}, St0) ->
{[V]=Mes,St1} = new_vars(1, St0),
{#k_binary{segs=V},Mes,St1};
get_match(#k_bin_seg{size=#k_atom{val=all},next={k_bin_end,[]}}=Seg, St0) ->
- {[S]=Vars,St1} = new_vars(1, St0),
- {Seg#k_bin_seg{seg=S,next=[]},Vars,St1};
+ {[S,N0],St1} = new_vars(2, St0),
+ N = set_kanno(N0, [no_usage]),
+ {Seg#k_bin_seg{seg=S,next=N},[S],St1};
get_match(#k_bin_seg{}=Seg, St0) ->
{[S,N0],St1} = new_vars(2, St0),
N = set_kanno(N0, [no_usage]),
@@ -2343,8 +2355,7 @@ uexpr(#k_bif{anno=A,op=Op,args=As}=Bif, {break,Rs}, St0) ->
{Brs,St1} = bif_returns(Op, Rs, St0),
{Bif#k_bif{anno=#k{us=Used,ns=lit_list_vars(Brs),a=A},ret=Brs},
Used,St1};
-uexpr(#k_match{anno=A,vars=Vs0,body=B0}, Br, St0) ->
- Vs = handle_reuse_annos(Vs0, St0),
+uexpr(#k_match{anno=A,vars=Vs,body=B0}, Br, St0) ->
Rs = break_rets(Br),
{B1,Bu,St1} = umatch(B0, Br, St0),
case is_in_guard(St1) of
@@ -2374,9 +2385,10 @@ uexpr(#k_try{anno=A,arg=A0,vars=Vs,body=B0,evars=Evs,handler=H0},
true ->
{[#k_var{name=X}],#k_var{name=X}} = {Vs,B0}, %Assertion.
#k_atom{val=false} = H0, %Assertion.
- {A1,Bu,St1} = uexpr(A0, Br, St0),
+ {Avs,St1} = new_vars(length(Rs0), St0),
+ {A1,Bu,St} = uexpr(A0, {break,Avs}, St1),
{#k_protected{anno=#k{us=Bu,ns=lit_list_vars(Rs0),a=A},
- arg=A1,ret=Rs0},Bu,St1};
+ arg=A1,ret=Rs0,inner=Avs},Bu,St};
false ->
{Avs,St1} = new_vars(length(Vs), St0),
{A1,Au,St2} = ubody(A0, {break,Avs}, St1),
@@ -2446,33 +2458,6 @@ make_fdef(Anno, Name, Arity, Vs, Body) ->
vars=Vs,body=Body,ret=[]},
#k_fdef{anno=Anno,func=Name,arity=Arity,vars=Vs,body=Match}.
-
-%% handle_reuse_annos([#k_var{}], State) -> State.
-%% In general, it is only safe to reuse a variable for a match context
-%% if the original value of the variable will no longer be needed.
-%%
-%% If a variable has been bound in an outer letrec and is therefore
-%% free in the current function, the variable may still be used.
-%% We don't bother to check whether the variable is actually used,
-%% but simply clears the 'reuse_for_context' annotation for any variable
-%% that is free.
-handle_reuse_annos(Vs, St) ->
- [handle_reuse_anno(V, St) || V <- Vs].
-
-handle_reuse_anno(#k_var{anno=A}=V, St) ->
- case member(reuse_for_context, A) of
- false -> V;
- true -> handle_reuse_anno_1(V, St)
- end.
-
-handle_reuse_anno_1(#k_var{anno=Anno,name=Vname}=V, #kern{ff={F,A}}=St) ->
- FreeVs = get_free(F, A, St),
- case keymember(Vname, #k_var.name, FreeVs) of
- true -> V#k_var{anno=Anno--[reuse_for_context]};
- false -> V
- end;
-handle_reuse_anno_1(V, _St) -> V.
-
%% get_free(Name, Arity, State) -> [Free].
%% store_free(Name, Arity, [Free], State) -> State.
@@ -2516,8 +2501,7 @@ umatch(#k_alt{anno=A,first=F0,then=T0}, Br, St0) ->
Used = union(Fu, Tu),
{#k_alt{anno=#k{us=Used,ns=[],a=A},first=F1,then=T1},
Used,St2};
-umatch(#k_select{anno=A,var=V0,types=Ts0}, Br, St0) ->
- V = handle_reuse_anno(V0, St0),
+umatch(#k_select{anno=A,var=V,types=Ts0}, Br, St0) ->
{Ts1,Tus,St1} = umatch_list(Ts0, Br, St0),
Used = case member(no_usage, get_kanno(V)) of
true -> Tus;
diff --git a/lib/compiler/src/v3_kernel.hrl b/lib/compiler/src/v3_kernel.hrl
index e6f0d3c1f7..e26360a6da 100644
--- a/lib/compiler/src/v3_kernel.hrl
+++ b/lib/compiler/src/v3_kernel.hrl
@@ -66,7 +66,7 @@
-record(k_receive_next, {anno=[]}).
-record(k_try, {anno=[],arg,vars,body,evars,handler,ret=[]}).
-record(k_try_enter, {anno=[],arg,vars,body,evars,handler}).
--record(k_protected, {anno=[],arg,ret=[]}).
+-record(k_protected, {anno=[],arg,ret=[],inner}).
-record(k_catch, {anno=[],body,ret=[]}).
-record(k_guard_match, {anno=[],vars,body,ret=[]}).
diff --git a/lib/compiler/test/Makefile b/lib/compiler/test/Makefile
index da5d207db9..db8eb7e2e1 100644
--- a/lib/compiler/test/Makefile
+++ b/lib/compiler/test/Makefile
@@ -14,6 +14,7 @@ MODULES= \
beam_except_SUITE \
beam_jump_SUITE \
beam_reorder_SUITE \
+ beam_ssa_SUITE \
beam_type_SUITE \
beam_utils_SUITE \
bif_SUITE \
@@ -52,6 +53,7 @@ NO_OPT= \
beam_except \
beam_jump \
beam_reorder \
+ beam_ssa \
beam_type \
beam_utils \
bif \
@@ -75,6 +77,7 @@ INLINE= \
andor \
apply \
beam_block \
+ beam_ssa \
beam_utils \
bif \
bs_bincomp \
@@ -94,16 +97,30 @@ INLINE= \
receive \
record
+R21= \
+ bs_construct \
+ bs_match
+
CORE_MODULES = \
lfe_andor_SUITE \
lfe_guard_SUITE
+NO_MOD_OPT = $(NO_OPT)
+
+NO_SSA_OPT = $(NO_OPT)
+
NO_OPT_MODULES= $(NO_OPT:%=%_no_opt_SUITE)
NO_OPT_ERL_FILES= $(NO_OPT_MODULES:%=%.erl)
POST_OPT_MODULES= $(NO_OPT:%=%_post_opt_SUITE)
POST_OPT_ERL_FILES= $(POST_OPT_MODULES:%=%.erl)
INLINE_MODULES= $(INLINE:%=%_inline_SUITE)
INLINE_ERL_FILES= $(INLINE_MODULES:%=%.erl)
+R21_MODULES= $(R21:%=%_r21_SUITE)
+R21_ERL_FILES= $(R21_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)
+NO_SSA_OPT_ERL_FILES= $(NO_SSA_OPT_MODULES:%=%.erl)
ERL_FILES= $(MODULES:%=%.erl)
CORE_FILES= $(CORE_MODULES:%=%.core)
@@ -124,7 +141,7 @@ RELSYSDIR = $(RELEASE_PATH)/compiler_test
# ----------------------------------------------------
ERL_MAKE_FLAGS +=
-ERL_COMPILE_FLAGS += +clint +clint0
+ERL_COMPILE_FLAGS += +clint +clint0 +ssalint
EBIN = .
@@ -132,15 +149,24 @@ EBIN = .
# Targets
# ----------------------------------------------------
-make_emakefile: $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) $(INLINE_ERL_FILES)
+make_emakefile: $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) $(NO_SSA_OPT_ERL_FILES) \
+ $(INLINE_ERL_FILES) $(R21_ERL_FILES) $(NO_MOD_OPT_ERL_FILES)
$(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES) \
> $(EMAKEFILE)
- $(ERL_TOP)/make/make_emakefile +no_copt +no_postopt $(ERL_COMPILE_FLAGS) \
+ $(ERL_TOP)/make/make_emakefile +no_copt +no_postopt \
+ +no_ssa_opt +no_recv_opt $(ERL_COMPILE_FLAGS) \
-o$(EBIN) $(NO_OPT_MODULES) >> $(EMAKEFILE)
+ $(ERL_TOP)/make/make_emakefile +no_share_opt +no_bsm_opt +no_fun_opt \
+ +no_ssa_opt +no_recv_opt $(ERL_COMPILE_FLAGS) \
+ -o$(EBIN) $(NO_SSA_OPT_MODULES) >> $(EMAKEFILE)
$(ERL_TOP)/make/make_emakefile +no_copt $(ERL_COMPILE_FLAGS) \
-o$(EBIN) $(POST_OPT_MODULES) >> $(EMAKEFILE)
$(ERL_TOP)/make/make_emakefile +inline $(ERL_COMPILE_FLAGS) \
-o$(EBIN) $(INLINE_MODULES) >> $(EMAKEFILE)
+ $(ERL_TOP)/make/make_emakefile +r21 $(ERL_COMPILE_FLAGS) \
+ -o$(EBIN) $(R21_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) \
-o$(EBIN) $(CORE_MODULES) >> $(EMAKEFILE)
@@ -161,12 +187,21 @@ docs:
%_no_opt_SUITE.erl: %_SUITE.erl
sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@
+%_no_ssa_opt_SUITE.erl: %_SUITE.erl
+ sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@
+
%_post_opt_SUITE.erl: %_SUITE.erl
sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@
%_inline_SUITE.erl: %_SUITE.erl
sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@
+%_r21_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 $@));' $< > $@
+
# ----------------------------------------------------
# Release Target
# ----------------------------------------------------
@@ -179,7 +214,9 @@ release_tests_spec: make_emakefile
$(INSTALL_DATA) compiler.spec compiler.cover \
$(EMAKEFILE) $(ERL_FILES) "$(RELSYSDIR)"
$(INSTALL_DATA) $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) \
- $(INLINE_ERL_FILES) "$(RELSYSDIR)"
+ $(INLINE_ERL_FILES) $(R21_ERL_FILES) \
+ $(NO_MOD_OPT_ERL_FILES) \
+ $(NO_SSA_OPT_ERL_FILES) "$(RELSYSDIR)"
$(INSTALL_DATA) $(CORE_FILES) "$(RELSYSDIR)"
for file in $(ERL_DUMMY_FILES); do \
module=`basename $$file .erl`; \
diff --git a/lib/compiler/test/apply_SUITE.erl b/lib/compiler/test/apply_SUITE.erl
index 0f82a56fb7..2ee518b1a0 100644
--- a/lib/compiler/test/apply_SUITE.erl
+++ b/lib/compiler/test/apply_SUITE.erl
@@ -73,6 +73,7 @@ mfa(Config) when is_list(Config) ->
{'EXIT',_} = (catch ?APPLY2(Mod, (id(bazzzzzz)), a, b)),
{'EXIT',_} = (catch ?APPLY2({}, baz, a, b)),
{'EXIT',_} = (catch ?APPLY2(?MODULE, [], a, b)),
+ {'EXIT',_} = (catch bad_literal_call(1)),
ok = apply(Mod, foo, id([])),
{[a,b|c]} = apply(Mod, bar, id([[a,b|c]])),
@@ -92,6 +93,13 @@ mfa(Config) when is_list(Config) ->
apply(Mod, foo, []).
+%% The single call to this function with a literal argument caused type
+%% optimization to swap out the 'mod' field of a #b_remote{}, which was
+%% mishandled during code generation as it assumed that the module would always
+%% be an atom.
+bad_literal_call(I) ->
+ I:foo().
+
foo() ->
ok.
diff --git a/lib/compiler/test/beam_except_SUITE.erl b/lib/compiler/test/beam_except_SUITE.erl
index 2b4a780899..9380fe06c8 100644
--- a/lib/compiler/test/beam_except_SUITE.erl
+++ b/lib/compiler/test/beam_except_SUITE.erl
@@ -21,7 +21,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,coverage/1]).
+ multiple_allocs/1,bs_get_tail/1,coverage/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -31,6 +31,7 @@ all() ->
groups() ->
[{p,[parallel],
[multiple_allocs,
+ bs_get_tail,
coverage]}].
init_per_suite(Config) ->
@@ -63,6 +64,17 @@ place(lee) ->
conditions() ->
(talking = going) = storage + [large = wanted].
+bs_get_tail(Config) ->
+ {<<"abc">>,0,0,Config} = bs_get_tail_1(id(<<0:32, "abc">>), 0, 0, Config),
+ {'EXIT',
+ {function_clause,
+ [{?MODULE,bs_get_tail_1,[<<>>,0,0,Config],_}|_]}} =
+ (catch bs_get_tail_1(id(<<>>), 0, 0, Config)),
+ ok.
+
+bs_get_tail_1(<<_:32, Rest/binary>>, Z1, Z2, F1) ->
+ {Rest,Z1,Z2,F1}.
+
coverage(_) ->
File = {file,"fake.erl"},
ok = fc(a),
@@ -83,8 +95,24 @@ coverage(_) ->
(catch bar(x)),
{'EXIT',{{case_clause,{1}},[{?MODULE,bar,1,[File,{line,9}]}|_]}} =
(catch bar(0)),
+
+ Self = self(),
+ {'EXIT',{{strange,Self},[{?MODULE,foo,[any],[File,{line,14}]}|_]}} =
+ (catch foo(any)),
+
+ {ok,succeed,1,2} = foobar(succeed, 1, 2),
+ {'EXIT',{function_clause,[{?MODULE,foobar,[[fail],1,2],
+ [{file,"fake.erl"},{line,16}]}|_]}} =
+ (catch foobar([fail], 1, 2)),
+ {'EXIT',{function_clause,[{?MODULE,fake_function_clause,[{a,b},42.0],_}|_]}} =
+ (catch fake_function_clause({a,b})),
+
ok.
+fake_function_clause(A) -> error(function_clause, [A,42.0]).
+
+id(I) -> I.
+
-file("fake.erl", 1).
fc(a) -> %Line 2
ok; %Line 3
@@ -96,3 +124,9 @@ bar(X) -> %Line 8
case {X+1} of %Line 9
1 -> ok %Line 10
end. %Line 11
+%% Cover collection code for function_clause exceptions.
+foo(A) -> %Line 13
+ error({strange,self()}, [A]). %Line 14
+%% Cover beam_except:tag_literal/1.
+foobar(A, B, C) when is_atom(A) -> %Line 16
+ {ok,A,B,C}. %Line 17
diff --git a/lib/compiler/test/beam_jump_SUITE.erl b/lib/compiler/test/beam_jump_SUITE.erl
index faedc0c1f1..a456f31d79 100644
--- a/lib/compiler/test/beam_jump_SUITE.erl
+++ b/lib/compiler/test/beam_jump_SUITE.erl
@@ -22,7 +22,8 @@
-export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1,
init_per_group/2,end_per_group/2,
undefined_label/1,ambiguous_catch_try_state/1,
- build_tuple/1]).
+ unsafe_move_elimination/1,build_tuple/1,
+ coverage/1]).
suite() ->
[{ct_hooks,[ts_install_cth]}].
@@ -34,7 +35,9 @@ groups() ->
[{p,[parallel],
[undefined_label,
ambiguous_catch_try_state,
- build_tuple
+ unsafe_move_elimination,
+ build_tuple,
+ coverage
]}].
init_per_suite(Config) ->
@@ -75,6 +78,82 @@ checks(Wanted) ->
%% Must be one line to cause the unsafe optimization.
{catch case river() of sheet -> begin +Wanted, if "da" -> Wanted end end end, catch case river() of sheet -> begin + Wanted, if "da" -> Wanted end end end}.
+unsafe_move_elimination(_Config) ->
+ {{left,right,false},false} = unsafe_move_elimination_1(left, right, false),
+ {{false,right,false},false} = unsafe_move_elimination_1(false, right, true),
+ {{true,right,right},right} = unsafe_move_elimination_1(true, right, true),
+ [ok = unsafe_move_elimination_2(I) || I <- lists:seq(0,16)],
+ ok.
+
+unsafe_move_elimination_1(Left, Right, Simple0) ->
+ id(1),
+
+ %% The move at label 29 would be removed by beam_jump, which is unsafe because
+ %% the two select_val instructions have different source registers.
+ %%
+ %% {select_val,{y,0},{f,25},{list,[{atom,true},{f,27},{atom,false},{f,29}]}}.
+ %% ^^^^^ ^^^^^^^^^^^^^^^^^^^
+ %% {label,27}.
+ %% {kill,{y,0}}.
+ %% {move,{y,2},{x,0}}.
+ %% {line,...}.
+ %% {call,1,{f,31}}.
+ %% {select_val,{x,0},{f,33},{list,[{atom,true},{f,35},{atom,false},{f,29}]}}.
+ %% ^^^^^ ^^^^^^^^^^^^^^^^^^^
+ %% {label,29}.
+ %% {move,{atom,false},{y,0}}. <=== REMOVED (unsafely).
+ %% {jump,{f,37}}.
+
+ Simple = case case Simple0 of
+ false -> false;
+ true -> id(Left)
+ end
+ of
+ false ->
+ false;
+ true ->
+ id(Right)
+ end,
+ {id({Left,Right,Simple}),Simple}.
+
+unsafe_move_elimination_2(Int) ->
+ %% The type optimization pass would recognize that TagInt can only be
+ %% [0 .. 7], so the first 'case' would select_val over [0 .. 6] and swap
+ %% out the fail label with the block for 7.
+ %%
+ %% A later optimization would merge this block with 'expects_h' in the
+ %% second case, as the latter is only reachable from the former.
+ %%
+ %% ... but this broke down when the move elimination optimization didn't
+ %% take the fail label of the first select_val into account. This caused it
+ %% to believe that the only way to reach 'expects_h' was through the second
+ %% case when 'Tag' =:= 'h', which made it remove the move instruction
+ %% added in the first case, passing garbage to expects_h/2.
+ TagInt = Int band 2#111,
+ Tag = case TagInt of
+ 0 -> a;
+ 1 -> b;
+ 2 -> c;
+ 3 -> d;
+ 4 -> e;
+ 5 -> f;
+ 6 -> g;
+ 7 -> h
+ end,
+ case Tag of
+ g -> expects_g(TagInt, Tag);
+ h -> expects_h(TagInt, Tag);
+ _ -> Tag = id(Tag), ok
+ end.
+
+expects_g(6, Atom) ->
+ Atom = id(g),
+ ok.
+
+expects_h(7, Atom) ->
+ Atom = id(h),
+ ok.
+
-record(message2, {id, p1}).
-record(message3, {id, p1, p2}).
@@ -87,3 +166,45 @@ do_build_tuple(Message) ->
Res = {res, rand:uniform(100)},
{Message#message3.id, Res}
end.
+
+coverage(_Config) ->
+ ok = coverage_1(ok),
+ {error,badarg} = coverage_1({error,badarg}),
+
+ gt = coverage_2(100, 42),
+ le = coverage_2(100, 999),
+ le = coverage_2([], []),
+ gt = coverage_2([], xxx),
+
+ ok.
+
+coverage_1(Var) ->
+ case id(Var) of
+ ok -> ok;
+ Error -> Error
+ end.
+
+%% Cover beam_jump:invert_test(is_ne_exact).
+coverage_2(Pre1, Pre2) ->
+ case
+ case Pre1 == [] of
+ false ->
+ false;
+ true ->
+ Pre2 /= []
+ end
+ of
+ true ->
+ gt;
+ false ->
+ case Pre1 > Pre2 of
+ true ->
+ gt;
+ false ->
+ le
+ end
+ end.
+
+
+id(I) ->
+ I.
diff --git a/lib/compiler/test/beam_ssa_SUITE.erl b/lib/compiler/test/beam_ssa_SUITE.erl
new file mode 100644
index 0000000000..15cf9bcbf3
--- /dev/null
+++ b/lib/compiler/test/beam_ssa_SUITE.erl
@@ -0,0 +1,497 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018. All 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_SUITE).
+
+-export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1,
+ init_per_group/2,end_per_group/2,
+ calls/1,tuple_matching/1,recv/1,maps/1,
+ cover_ssa_dead/1,combine_sw/1,share_opt/1]).
+
+suite() -> [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [{group,p}].
+
+groups() ->
+ [{p,test_lib:parallel(),
+ [tuple_matching,
+ calls,
+ recv,
+ maps,
+ cover_ssa_dead,
+ combine_sw,
+ share_opt
+ ]}].
+
+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.
+
+calls(Config) ->
+ Ret = {return,value,Config},
+ Ret = fun_call(fun(42) -> ok end, Ret),
+ Ret = apply_fun(fun(a, b) -> ok end, [a,b], Ret),
+ Ret = apply_mfa(test_lib, id, [anything], Ret),
+ {'EXIT',{badarg,_}} = (catch call_error()),
+ {'EXIT',{badarg,_}} = (catch call_error(42)),
+ 5 = start_it([erlang,length,1,2,3,4,5]),
+ ok.
+
+fun_call(Fun, X0) ->
+ X = id(X0),
+ Fun(42),
+ X.
+
+apply_fun(Fun, Args, X0) ->
+ X = id(X0),
+ apply(Fun, Args),
+ X.
+
+apply_mfa(Mod, Name, Args, X0) ->
+ X = id(X0),
+ apply(Mod, Name, Args),
+ X.
+
+call_error() ->
+ error(badarg),
+ ok.
+
+call_error(I) ->
+ <<I:(-8)>>,
+ ok.
+
+start_it([_|_]=MFA) ->
+ case MFA of
+ [M,F|Args] -> M:F(Args)
+ end.
+
+tuple_matching(_Config) ->
+ do_tuple_matching({tag,42}),
+
+ true = is_two_tuple({a,b}),
+ false = is_two_tuple({a,b,c}),
+ false = is_two_tuple(atom),
+
+ ok.
+
+do_tuple_matching(Arg) ->
+ Res = do_tuple_matching_1(Arg),
+ Res = do_tuple_matching_2(Arg),
+ Res = do_tuple_matching_3(Arg),
+ Res.
+
+do_tuple_matching_1({tag,V}) ->
+ {ok,V}.
+
+do_tuple_matching_2(Tuple) when is_tuple(Tuple) ->
+ Size = tuple_size(Tuple),
+ if
+ Size =:= 2 ->
+ {ok,element(2, Tuple)}
+ end.
+
+do_tuple_matching_3(Tuple) when is_tuple(Tuple) ->
+ Size = tuple_size(Tuple),
+ if
+ Size =:= 2 ->
+ 2 = id(Size),
+ {ok,element(2, Tuple)}
+ end.
+
+is_two_tuple(Arg) ->
+ case is_tuple(Arg) of
+ false -> false;
+ true -> tuple_size(Arg) == 2
+ end.
+
+-record(reporter_state, {res,run_config}).
+-record(run_config, {report_interval=0}).
+
+recv(_Config) ->
+ Parent = self(),
+
+ %% Test sync_wait_mon/2.
+ Succ = fun() -> Parent ! {ack,self(),{result,42}} end,
+ {result,42} = sync_wait_mon(spawn_monitor(Succ), infinity),
+
+ Down = fun() -> exit(down) end,
+ {error,down} = sync_wait_mon(spawn_monitor(Down), infinity),
+
+ Exit = fun() ->
+ Self = self(),
+ spawn(fun() -> exit(Self, kill_me) end),
+ receive _ -> ok end
+ end,
+ {error,kill_me} = sync_wait_mon(spawn_monitor(Exit), infinity),
+
+ Timeout = fun() -> receive _ -> ok end end,
+ {error,timeout} = sync_wait_mon(spawn_monitor(Timeout), 0),
+
+ %% Test reporter_loop/1.
+ {a,Parent} = reporter_loop(#reporter_state{res={a,Parent},
+ run_config=#run_config{}}),
+
+ %% Test bad_sink/0.
+ bad_sink(),
+
+ %% Test tricky_recv_1/0.
+ self() ! 1,
+ a = tricky_recv_1(),
+ self() ! 2,
+ b = tricky_recv_1(),
+
+ %% Test tricky_recv_2/0.
+ self() ! 1,
+ {1,yes} = tricky_recv_2(),
+ self() ! 2,
+ {2,maybe} = tricky_recv_2(),
+
+ %% Test 'receive after infinity' in try/catch.
+ Pid = spawn(fun recv_after_inf_in_try/0),
+ exit(Pid, done),
+
+ %% Test tricky_recv_3().
+ self() ! {{self(),r0},{1,42,"name"}},
+ {Parent,r0,[<<1:32,1:8,42:8>>,"name",0]} = tricky_recv_3(),
+ self() ! {{self(),r1},{2,99,<<"data">>}},
+ {Parent,r1,<<1:32,2:8,99:8,"data">>} = tricky_recv_3(),
+
+ %% Test tricky_recv_4().
+ self() ! {[self(),r0],{1,42,"name"}},
+ {Parent,r0,[<<1:32,1:8,42:8>>,"name",0]} = tricky_recv_4(),
+ self() ! {[self(),r1],{2,99,<<"data">>}},
+ {Parent,r1,<<1:32,2:8,99:8,"data">>} = tricky_recv_4(),
+
+ ok.
+
+sync_wait_mon({Pid, Ref}, Timeout) ->
+ receive
+ {ack,Pid,Return} ->
+ erlang:demonitor(Ref, [flush]),
+ Return;
+ {'DOWN',Ref,_Type,Pid,Reason} ->
+ {error,Reason};
+ {'EXIT',Pid,Reason} ->
+ erlang:demonitor(Ref, [flush]),
+ {error,Reason}
+ after Timeout ->
+ erlang:demonitor(Ref, [flush]),
+ exit(Pid, kill),
+ {error,timeout}
+ end.
+
+reporter_loop(State) ->
+ RC = State#reporter_state.run_config,
+ receive after RC#run_config.report_interval ->
+ State#reporter_state.res
+ end.
+
+bad_sink() ->
+ {ok,Pid} = my_spawn(self()),
+ %% The get_tuple_element instruction for the matching
+ %% above was sinked into the receive loop. That will
+ %% not work (and would be bad for performance if it
+ %% would work).
+ receive
+ {ok,Pid} ->
+ ok;
+ error ->
+ exit(failed)
+ end,
+ exit(Pid, kill).
+
+my_spawn(Parent) ->
+ Pid = spawn(fun() ->
+ Parent ! {ok,self()},
+ receive _ -> ok end
+ end),
+ {ok,Pid}.
+
+tricky_recv_1() ->
+ receive
+ X=1 ->
+ id(42),
+ a;
+ X=2 ->
+ b
+ end,
+ case X of
+ 1 -> a;
+ 2 -> b
+ end.
+
+tricky_recv_2() ->
+ receive
+ X=1 ->
+ Y = case id(X) of
+ 1 -> yes;
+ _ -> no
+ end,
+ a;
+ X=2 ->
+ Y = maybe,
+ b
+ end,
+ {X,Y}.
+
+recv_after_inf_in_try() ->
+ try
+ %% Used to crash beam_kernel_to_ssa.
+ receive after infinity -> ok end
+ catch
+ _A:_B ->
+ receive after infinity -> ok end
+ end.
+
+tricky_recv_3() ->
+ {Pid, R, Request} =
+ receive
+ {{Pid0,R0}, {1, Proto0, Name0}} ->
+ {Pid0, R0,
+ [<<1:32, 1:8, Proto0:8>>,Name0,0]};
+ {{Pid1,R1}, {2, Proto1, Data1}} ->
+ {Pid1, R1,
+ <<1:32, 2:8, Proto1:8, Data1/binary>>}
+ end,
+ id({Pid,R,Request}).
+
+tricky_recv_4() ->
+ {Pid, R, Request} =
+ receive
+ {[Pid0,R0], {1, Proto0, Name0}} ->
+ {Pid0, R0,
+ [<<1:32, 1:8, Proto0:8>>,Name0,0]};
+ {[Pid1,R1], {2, Proto1, Data1}} ->
+ {Pid1, R1,
+ <<1:32, 2:8, Proto1:8, Data1/binary>>}
+ end,
+ id({Pid,R,Request}).
+
+maps(_Config) ->
+ {'EXIT',{{badmatch,#{}},_}} = (catch maps_1(any)),
+ ok.
+
+maps_1(K) ->
+ _ = id(42),
+ #{K:=V} = #{},
+ V.
+
+-record(wx_ref, {type=any_type,ref=any_ref}).
+
+cover_ssa_dead(_Config) ->
+ str = format_str(str, escapable, [], true),
+ [iolist,str] = format_str(str, escapable, iolist, true),
+ bad = format_str(str, not_escapable, [], true),
+ bad = format_str(str, not_escapable, iolist, true),
+ bad = format_str(str, escapable, [], false),
+ bad = format_str(str, escapable, [], bad),
+
+ DefWxRef = #wx_ref{},
+ {DefWxRef,77,9999,[]} = contains(#wx_ref{}, 77, 9999),
+ {DefWxRef,77.0,9999,[]} = contains(#wx_ref{}, 77.0, 9999),
+ {DefWxRef,77,9999.0,[]} = contains(#wx_ref{}, 77, 9999.0),
+ {DefWxRef,77.0,9999.0,[]} = contains(#wx_ref{}, 77.0, 9999.0),
+ {any_type,any_ref,42,43,[option]} = contains(#wx_ref{}, {42,43}, [option]),
+ {any_type,any_ref,42,43,[]} = contains(#wx_ref{}, {42,43}, []),
+ {any_type,any_ref,42.0,43,[]} = contains(#wx_ref{}, {42.0,43}, []),
+ {any_type,any_ref,42,43.0,[]} = contains(#wx_ref{}, {42,43.0}, []),
+ {any_type,any_ref,42.0,43.0,[]} = contains(#wx_ref{}, {42.0,43.0}, []),
+
+ nope = conv_alub(false, '=:='),
+ ok = conv_alub(true, '=:='),
+ ok = conv_alub(true, none),
+ error = conv_alub(false, none),
+
+ {false,false} = eval_alu(false, false, false),
+ {true,false} = eval_alu(false, false, true),
+ {false,true} = eval_alu(false, true, false),
+ {false,false} = eval_alu(false, true, true),
+ {false,true} = eval_alu(true, false, false),
+ {false,false} = eval_alu(true, false, true),
+ {true,true} = eval_alu(true, true, false),
+ {false,true} = eval_alu(true, true, true),
+
+ 100.0 = percentage(1.0, 0.0),
+ 100.0 = percentage(1, 0),
+ 0.0 = percentage(0, 0),
+ 0.0 = percentage(0.0, 0.0),
+ 40.0 = percentage(4.0, 10.0),
+ 60.0 = percentage(6, 10),
+
+ %% Cover '=:=', followed by '=/='.
+ false = 'cover__=:=__=/='(41),
+ true = 'cover__=:=__=/='(42),
+ false = 'cover__=:=__=/='(43),
+
+ %% Cover '<', followed by '=/='.
+ true = 'cover__<__=/='(41),
+ false = 'cover__<__=/='(42),
+ false = 'cover__<__=/='(43),
+
+ %% Cover '=<', followed by '=/='.
+ true = 'cover__=<__=/='(41),
+ true = 'cover__=<__=/='(42),
+ false = 'cover__=<__=/='(43),
+
+ %% Cover '>=', followed by '=/='.
+ false = 'cover__>=__=/='(41),
+ true = 'cover__>=__=/='(42),
+ true = 'cover__>=__=/='(43),
+
+ %% Cover '>', followed by '=/='.
+ false = 'cover__>__=/='(41),
+ false = 'cover__>__=/='(42),
+ true = 'cover__>__=/='(43),
+
+ ok.
+
+'cover__=:=__=/='(X) when X =:= 42 -> X =/= 43;
+'cover__=:=__=/='(_) -> false.
+
+'cover__<__=/='(X) when X < 42 -> X =/= 42;
+'cover__<__=/='(_) -> false.
+
+'cover__=<__=/='(X) when X =< 42 -> X =/= 43;
+'cover__=<__=/='(_) -> false.
+
+'cover__>=__=/='(X) when X >= 42 -> X =/= 41;
+'cover__>=__=/='(_) -> false.
+
+'cover__>__=/='(X) when X > 42 -> X =/= 42;
+'cover__>__=/='(_) -> false.
+
+format_str(Str, FormatData, IoList, EscChars) ->
+ Escapable = FormatData =:= escapable,
+ case id(Str) of
+ IoStr when Escapable, EscChars, IoList == [] ->
+ id(IoStr);
+ IoStr when Escapable, EscChars ->
+ [IoList,id(IoStr)];
+ _ ->
+ bad
+ end.
+
+contains(This, X, Y) when is_record(This, wx_ref), is_number(X), is_number(Y) ->
+ {This,X,Y,[]};
+contains(#wx_ref{type=ThisT,ref=ThisRef}, {CX,CY}, Options)
+ when is_number(CX), is_number(CY), is_list(Options) ->
+ {ThisT,ThisRef,CX,CY,Options}.
+
+conv_alub(HasDst, CmpOp) ->
+ case (not HasDst) andalso CmpOp =/= none of
+ true -> nope;
+ false ->
+ case HasDst of
+ false -> error;
+ true -> ok
+ end
+ end.
+
+eval_alu(Sign1, Sign2, N) ->
+ V = (Sign1 andalso Sign2 andalso (not N))
+ or ((not Sign1) andalso (not Sign2) andalso N),
+ C = (Sign1 andalso Sign2)
+ or ((not N) andalso (Sign1 orelse Sign2)),
+ {V,C}.
+
+percentage(Divident, Divisor) ->
+ if Divisor == 0 andalso Divident /= 0 ->
+ 100.0;
+ Divisor == 0 ->
+ 0.0;
+ true ->
+ Divident / Divisor * 100
+ end.
+
+combine_sw(_Config) ->
+ [a] = do_comb_sw_1(a),
+ [b,b] = do_comb_sw_1(b),
+ [c] = do_comb_sw_1(c),
+ [c] = do_comb_sw_1(c),
+ [] = do_comb_sw_1(z),
+
+ [a] = do_comb_sw_2(a),
+ [b2,b1] = do_comb_sw_2(b),
+ [c] = do_comb_sw_2(c),
+ [c] = do_comb_sw_2(c),
+ [] = do_comb_sw_2(z),
+
+ ok.
+
+do_comb_sw_1(X) ->
+ put(?MODULE, []),
+ if
+ X == a; X == b ->
+ put(?MODULE, [X|get(?MODULE)]);
+ true ->
+ ok
+ end,
+ if
+ X == b; X == c ->
+ put(?MODULE, [X|get(?MODULE)]);
+ true ->
+ ok
+ end,
+ erase(?MODULE).
+
+do_comb_sw_2(X) ->
+ put(?MODULE, []),
+ case X of
+ a ->
+ put(?MODULE, [a|get(?MODULE)]);
+ b ->
+ put(?MODULE, [b1|get(?MODULE)]);
+ _ ->
+ ok
+ end,
+ case X of
+ b ->
+ put(?MODULE, [b2|get(?MODULE)]);
+ c ->
+ put(?MODULE, [c|get(?MODULE)]);
+ _ ->
+ ok
+ end,
+ erase(?MODULE).
+
+share_opt(_Config) ->
+ ok = do_share_opt(0).
+
+do_share_opt(A) ->
+ %% The compiler would be stuck in an infinite loop in beam_ssa_share.
+ case A of
+ 0 -> a;
+ 1 -> b;
+ 2 -> c
+ end,
+ receive after 1 -> ok end.
+
+
+%% The identity function.
+id(I) -> I.
diff --git a/lib/compiler/test/beam_type_SUITE.erl b/lib/compiler/test/beam_type_SUITE.erl
index 061076b3ff..a7ffc3f60a 100644
--- a/lib/compiler/test/beam_type_SUITE.erl
+++ b/lib/compiler/test/beam_type_SUITE.erl
@@ -21,9 +21,10 @@
-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,coverage/1,booleans/1,setelement/1,cons/1,
- tuple/1,record_float/1,binary_float/1,float_compare/1,
- arity_checks/1,elixir_binaries/1,find_best/1]).
+ integers/1,numbers/1,coverage/1,booleans/1,setelement/1,
+ cons/1,tuple/1,record_float/1,binary_float/1,float_compare/1,
+ arity_checks/1,elixir_binaries/1,find_best/1,
+ test_size/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -33,6 +34,7 @@ all() ->
groups() ->
[{p,[parallel],
[integers,
+ numbers,
coverage,
booleans,
setelement,
@@ -43,7 +45,8 @@ groups() ->
float_compare,
arity_checks,
elixir_binaries,
- find_best
+ find_best,
+ test_size
]}].
init_per_suite(Config) ->
@@ -113,8 +116,8 @@ do_integers_4(_, _, Res) ->
Res.
do_integers_5(X0, Y0) ->
- %% X and Y will use the same register.
- X = X0 band 1,
+ %% _X and Y will use the same register.
+ _X = X0 band 1,
Y = Y0 band 3,
case Y of
0 -> zero;
@@ -123,6 +126,59 @@ do_integers_5(X0, Y0) ->
3 -> three
end.
+numbers(_Config) ->
+ Int = id(42),
+ true = is_integer(Int),
+ true = is_number(Int),
+ false = is_float(Int),
+
+ Float = id(42.0),
+ true = is_float(Float),
+ true = is_number(Float),
+ false = is_integer(Float),
+
+ Number = id(1) + id(2),
+ true = is_number(Number),
+ true = is_integer(Number),
+ false = is_float(Number),
+
+ AnotherNumber = id(99.0) + id(1),
+ true = is_float(AnotherNumber),
+ true = is_number(AnotherNumber),
+ false = is_integer(AnotherNumber),
+
+ NotNumber = id(atom),
+ true = is_atom(NotNumber),
+ false = is_number(NotNumber),
+ false = is_integer(NotNumber),
+ false = is_float(NotNumber),
+
+ true = is_number(Int),
+ true = is_number(Float),
+ true = is_number(Number),
+ true = is_number(AnotherNumber),
+
+ %% Cover beam_ssa_type:join/2.
+
+ Join1 = case id(a) of
+ a -> 3 + id(7); %Number.
+ b -> id(5) / id(2) %Float.
+ end,
+ true = is_integer(Join1),
+
+ Join2 = case id(a) of
+ a -> id(5) / 2; %Float.
+ b -> 3 + id(7) %Number.
+ end,
+ true = is_float(Join2),
+
+ %% Cover beam_ssa_type:meet/2.
+
+ Meet1 = id(0) + -10.0, %Float.
+ 10.0 = abs(Meet1), %Number.
+
+ ok.
+
coverage(Config) ->
{'EXIT',{badarith,_}} = (catch id(1) bsl 0.5),
{'EXIT',{badarith,_}} = (catch id(2.0) bsl 2),
@@ -157,18 +213,59 @@ coverage(Config) ->
[_|_] ->
ok
end,
+
+ %% Cover beam_type:verified_type(none).
+ {'EXIT',{badarith,_}} = (catch (id(2) / id(1)) band 16#ff),
+
ok.
booleans(_Config) ->
- {'EXIT',{{case_clause,_},_}} = (catch do_booleans(42)),
+ {'EXIT',{{case_clause,_},_}} = (catch do_booleans_1(42)),
+
+ ok = do_booleans_2(42, 41),
+ error = do_booleans_2(42, 42),
+
+ AnyAtom = id(atom),
+ true = is_atom(AnyAtom),
+ false = is_boolean(AnyAtom),
+
+ MaybeBool = id(maybe),
+ case MaybeBool of
+ true -> ok;
+ maybe -> ok;
+ false -> ok
+ end,
+ false = is_boolean(MaybeBool),
+
+ NotBool = id(a),
+ case NotBool of
+ a -> ok;
+ b -> ok;
+ c -> ok
+ end,
+ false = is_boolean(NotBool),
+
ok.
-do_booleans(B) ->
+do_booleans_1(B) ->
case is_integer(B) of
yes -> yes;
no -> no
end.
+do_booleans_2(A, B) ->
+ Not = not do_booleans_cmp(A, B),
+ case Not of
+ true ->
+ case Not of
+ true -> error;
+ false -> ok
+ end;
+ false -> ok
+ end.
+
+do_booleans_cmp(A, B) -> A > B.
+
setelement(_Config) ->
T0 = id({a,42}),
{a,_} = T0,
@@ -177,13 +274,59 @@ setelement(_Config) ->
cons(_Config) ->
[did] = cons(assigned, did),
+
+ true = cons_is_empty_list([]),
+ false = cons_is_empty_list([a]),
+
+ false = cons_not(true),
+ true = cons_not(false),
+
+ {$a,"bc"} = cons_hdtl(true),
+ {$d,"ef"} = cons_hdtl(false),
ok.
cons(assigned, Instrument) ->
[Instrument] = [did].
+cons_is_empty_list(L) ->
+ Cons = case L of
+ [] -> "true";
+ _ -> "false"
+ end,
+ id(1),
+ case Cons of
+ "true" -> true;
+ "false" -> false
+ end.
+
+cons_not(B) ->
+ Cons = case B of
+ true -> "true";
+ false -> "false"
+ end,
+ id(1),
+ case Cons of
+ "true" -> false;
+ "false" -> true
+ end.
+
+cons_hdtl(B) ->
+ Cons = case B of
+ true -> "abc";
+ false -> "def"
+ end,
+ id(1),
+ {id(hd(Cons)),id(tl(Cons))}.
+
+-record(bird, {a=a,b=id(42)}).
+
tuple(_Config) ->
{'EXIT',{{badmatch,{necessary}},_}} = (catch do_tuple()),
+
+ [] = [X || X <- [], #bird{a = a} == {r,X,foo}],
+ [] = [X || X <- [], #bird{b = b} == {bird,X}],
+ [] = [X || X <- [], 3 == X#bird.a],
+
ok.
do_tuple() ->
@@ -320,6 +463,15 @@ find_best([], <<"a">>) ->
find_best([], nil) ->
{error,<<"should not get here">>}.
+test_size(_Config) ->
+ 2 = do_test_size({a,b}),
+ 4 = do_test_size(<<42:32>>),
+ ok.
+
+do_test_size(Term) when is_tuple(Term) ->
+ size(Term);
+do_test_size(Term) when is_binary(Term) ->
+ size(Term).
id(I) ->
I.
diff --git a/lib/compiler/test/beam_utils_SUITE.erl b/lib/compiler/test/beam_utils_SUITE.erl
index ff0f72d519..eb0af59f9d 100644
--- a/lib/compiler/test/beam_utils_SUITE.erl
+++ b/lib/compiler/test/beam_utils_SUITE.erl
@@ -197,7 +197,7 @@ do_bs_init_4(Arg1, Arg2) ->
id(Rewrite)
end/binary,
"/shared">>);
- Other ->
+ _Other ->
error
end.
@@ -553,7 +553,7 @@ not_used_p(_C, S, K, L) when is_record(K, k) ->
id(K)
end.
-is_used_fr(Config) ->
+is_used_fr(_Config) ->
1 = is_used_fr(self(), self()),
1 = is_used_fr(self(), other),
receive 1 -> ok end,
@@ -572,7 +572,7 @@ is_used_fr(X, Y) ->
X ! 1.
%% ERL-778.
-unsafe_is_function(Config) ->
+unsafe_is_function(_Config) ->
{undefined,any} = unsafe_is_function(undefined, any),
{ok,any} = unsafe_is_function(fun() -> ok end, any),
{'EXIT',{{case_clause,_},_}} = (catch unsafe_is_function(fun(_) -> ok end, any)),
diff --git a/lib/compiler/test/beam_validator_SUITE.erl b/lib/compiler/test/beam_validator_SUITE.erl
index d3e544a9cc..265da43f9d 100644
--- a/lib/compiler/test/beam_validator_SUITE.erl
+++ b/lib/compiler/test/beam_validator_SUITE.erl
@@ -34,7 +34,7 @@
undef_label/1,illegal_instruction/1,failing_gc_guard_bif/1,
map_field_lists/1,cover_bin_opt/1,
val_dsetel/1,bad_tuples/1,bad_try_catch_nesting/1,
- receive_stacked/1]).
+ receive_stacked/1,aliased_types/1]).
-include_lib("common_test/include/ct.hrl").
@@ -63,7 +63,7 @@ groups() ->
undef_label,illegal_instruction,failing_gc_guard_bif,
map_field_lists,cover_bin_opt,val_dsetel,
bad_tuples,bad_try_catch_nesting,
- receive_stacked]}].
+ receive_stacked,aliased_types]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -107,13 +107,12 @@ xrange(Config) when is_list(Config) ->
Errors = do_val(xrange, Config),
[{{t,sum_1,2},
{{bif,'+',{f,0},[{x,-1},{x,1}],{x,0}},4,
- {uninitialized_reg,{x,-1}}}},
+ {bad_register,{x,-1}}}},
{{t,sum_2,2},
- {{bif,'+',{f,0},[{x,0},{x,1023}],{x,0}},4,
- {uninitialized_reg,{x,1023}}}},
+ {{bif,'+',{f,0},[{x,0},{x,1023}],{x,0}},4,limit}},
{{t,sum_3,2},
{{bif,'+',{f,0},[{x,0},{x,1}],{x,-1}},4,
- {invalid_store,{x,-1},number}}},
+ {bad_register,{x,-1}}}},
{{t,sum_4,2},
{{bif,'+',{f,0},[{x,0},{x,1}],{x,1023}},4,limit}}] = Errors,
ok.
@@ -122,15 +121,15 @@ yrange(Config) when is_list(Config) ->
Errors = do_val(yrange, Config),
[{{t,sum_1,2},
{{move,{x,1},{y,-1}},5,
- {invalid_store,{y,-1},term}}},
+ {bad_register,{y,-1}}}},
{{t,sum_2,2},
{{bif,'+',{f,0},[{x,0},{y,1024}],{x,0}},7,
- {uninitialized_reg,{y,1024}}}},
+ limit}},
{{t,sum_3,2},
{{move,{x,1},{y,1024}},5,limit}},
{{t,sum_4,2},
{{move,{x,1},{y,-1}},5,
- {invalid_store,{y,-1},term}}}] = Errors,
+ {bad_register,{y,-1}}}}] = Errors,
ok.
stack(Config) when is_list(Config) ->
@@ -159,7 +158,7 @@ merge_undefined(Config) when is_list(Config) ->
[{{t,handle_call,2},
{{call_ext,1,{extfunc,erlang,exit,1}},
10,
- {uninitialized_reg,{y,0}}}}] = Errors,
+ {uninitialized_reg,{y,_}}}}] = Errors,
ok.
uninit(Config) when is_list(Config) ->
@@ -178,7 +177,7 @@ unsafe_catch(Config) when is_list(Config) ->
Errors = do_val(unsafe_catch, Config),
[{{t,small,2},
{{bs_put_integer,{f,0},{integer,16},1,
- {field_flags,[unsigned,big]},{y,0}},
+ {field_flags,[unsigned,big]},{y,0}},
20,
{unassigned,{y,0}}}}] = Errors,
ok.
@@ -211,19 +210,19 @@ bad_catch_try(Config) when is_list(Config) ->
Errors = do_val(bad_catch_try, Config),
[{{bad_catch_try,bad_1,1},
{{'catch',{x,0},{f,3}},
- 5,{invalid_store,{x,0},{catchtag,[3]}}}},
+ 5,{invalid_tag_register,{x,0}}}},
{{bad_catch_try,bad_2,1},
{{catch_end,{x,9}},
- 8,{source_not_y_reg,{x,9}}}},
+ 8,{invalid_tag_register,{x,9}}}},
{{bad_catch_try,bad_3,1},
- {{catch_end,{y,1}},9,{bad_type,{atom,kalle}}}},
+ {{catch_end,{y,1}},9,{invalid_tag,{y,1},{atom,kalle}}}},
{{bad_catch_try,bad_4,1},
- {{'try',{x,0},{f,15}},5,{invalid_store,{x,0},{trytag,[15]}}}},
+ {{'try',{x,0},{f,15}},5,{invalid_tag_register,{x,0}}}},
{{bad_catch_try,bad_5,1},
- {{try_case,{y,1}},12,{bad_type,term}}},
+ {{try_case,{y,1}},12,{invalid_tag,{y,1},term}}},
{{bad_catch_try,bad_6,1},
{{move,{integer,1},{y,1}},7,
- {invalid_store,{y,1},{integer,1}}}}] = Errors,
+ {invalid_store,{y,1}}}}] = Errors,
ok.
cons_guard(Config) when is_list(Config) ->
@@ -247,7 +246,7 @@ freg_range(Config) when is_list(Config) ->
{{t,sum_3,2},
{{bif,fadd,{f,0},[{fr,0},{fr,1}],{fr,-1}},
7,
- {bad_target,{fr,-1}}}},
+ {bad_register,{fr,-1}}}},
{{t,sum_4,2},
{{bif,fadd,{f,0},[{fr,0},{fr,1}],{fr,1024}},
7,
@@ -539,37 +538,37 @@ receive_stacked(Config) ->
[{{receive_stacked,f1,0},
{{loop_rec_end,{f,3}},
17,
- {fragile_message_reference,{y,0}}}},
+ {fragile_message_reference,{y,_}}}},
{{receive_stacked,f2,0},
- {{test_heap,3,0},10,{fragile_message_reference,{y,1}}}},
+ {{test_heap,3,0},10,{fragile_message_reference,{y,_}}}},
{{receive_stacked,f3,0},
- {{test_heap,3,0},10,{fragile_message_reference,{y,1}}}},
+ {{test_heap,3,0},10,{fragile_message_reference,{y,_}}}},
{{receive_stacked,f4,0},
- {{test_heap,3,0},10,{fragile_message_reference,{y,1}}}},
+ {{test_heap,3,0},10,{fragile_message_reference,{y,_}}}},
{{receive_stacked,f5,0},
{{loop_rec_end,{f,23}},
23,
- {fragile_message_reference,{y,1}}}},
+ {fragile_message_reference,{y,_}}}},
{{receive_stacked,f6,0},
- {{gc_bif,byte_size,{f,29},0,[{y,0}],{x,0}},
+ {{gc_bif,byte_size,{f,29},0,[{y,_}],{x,0}},
12,
- {fragile_message_reference,{y,0}}}},
+ {fragile_message_reference,{y,_}}}},
{{receive_stacked,f7,0},
{{loop_rec_end,{f,33}},
20,
- {fragile_message_reference,{y,0}}}},
+ {fragile_message_reference,{y,_}}}},
{{receive_stacked,f8,0},
{{loop_rec_end,{f,38}},
20,
- {fragile_message_reference,{y,0}}}},
+ {fragile_message_reference,{y,_}}}},
{{receive_stacked,m1,0},
{{loop_rec_end,{f,43}},
19,
- {fragile_message_reference,{y,0}}}},
+ {fragile_message_reference,{y,_}}}},
{{receive_stacked,m2,0},
{{loop_rec_end,{f,48}},
33,
- {fragile_message_reference,{y,0}}}}] = Errors,
+ {fragile_message_reference,{y,_}}}}] = Errors,
%% Compile the original source code as a smoke test.
Data = proplists:get_value(data_dir, Config),
@@ -579,6 +578,58 @@ receive_stacked(Config) ->
ok.
+aliased_types(Config) ->
+ Seq = lists:seq(1, 5),
+ 1 = aliased_types_1(Seq, Config),
+
+ {1,1} = aliased_types_2(Seq),
+ {42,none} = aliased_types_2([]),
+
+ gurka = aliased_types_3([gurka]),
+ gaffel = aliased_types_3([gaffel]),
+
+ ok.
+
+%% ERL-735: validator failed to track types on aliased registers, rejecting
+%% legitimate optimizations.
+%%
+%% move x0 y0
+%% bif hd L1 x0
+%% get_hd y0 %% The validator failed to see that y0 was a list
+%%
+aliased_types_1(Bug, Config) ->
+ if
+ Config =/= [gurka, gaffel] -> %% Pointless branch.
+ _ = hd(Bug),
+ lists:seq(1, 5),
+ hd(Bug)
+ end.
+
+%% ERL-832: validator failed to realize that a Y register was a cons.
+aliased_types_2(Bug) ->
+ Res = case Bug of
+ [] -> id(42);
+ _ -> hd(Bug)
+ end,
+ {Res,case Bug of
+ [] -> none;
+ _ -> hd(Bug)
+ end}.
+
+%% ERL-832 part deux; validator failed to realize that an aliased register was
+%% a cons.
+aliased_types_3(Bug) ->
+ List = [Y || Y <- Bug],
+ case List of
+ [] -> Bug;
+ _ ->
+ if
+ hd(List) -> a:a();
+ true -> ok
+ end,
+ hd(List)
+ end.
+
%%%-------------------------------------------------------------------------
transform_remove(Remove, Module) ->
@@ -637,3 +688,6 @@ night(Turned) ->
ok.
participating(_, _, _, _) -> ok.
+
+id(I) ->
+ I.
diff --git a/lib/compiler/test/beam_validator_SUITE_data/bad_bin_match.S b/lib/compiler/test/beam_validator_SUITE_data/bad_bin_match.S
index a60ca1e89a..c7610971f1 100644
--- a/lib/compiler/test/beam_validator_SUITE_data/bad_bin_match.S
+++ b/lib/compiler/test/beam_validator_SUITE_data/bad_bin_match.S
@@ -11,5 +11,5 @@
{label,1}.
{func_info,{atom,t},{atom,t},1}.
{label,2}.
- {test,bs_start_match2,{f,1},1,[{x,0},0],{x,0}}.
+ {test,bs_start_match3,{f,1},1,[{x,0}],{x,0}}.
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 cca052a9c4..5b974119c6 100644
--- a/lib/compiler/test/beam_validator_SUITE_data/receive_stacked.S
+++ b/lib/compiler/test/beam_validator_SUITE_data/receive_stacked.S
@@ -172,7 +172,7 @@
{allocate_zero,1,0}.
{label,28}.
{loop_rec,{f,30},{x,0}}.
- {test,bs_start_match2,{f,29},1,[{x,0},0],{x,0}}.
+ {test,bs_start_match3,{f,29},1,[{x,0}],{x,0}}.
{test,bs_get_integer2,
{f,29},
1,
@@ -219,7 +219,7 @@
{allocate_zero,1,0}.
{label,33}.
{loop_rec,{f,35},{x,0}}.
- {test,bs_start_match2,{f,34},1,[{x,0},0],{x,0}}.
+ {test,bs_start_match3,{f,34},1,[{x,0}],{x,0}}.
{test,bs_get_integer2,
{f,34},
1,
@@ -262,7 +262,7 @@
{allocate_zero,1,0}.
{label,38}.
{loop_rec,{f,40},{x,0}}.
- {test,bs_start_match2,{f,39},1,[{x,0},0],{x,1}}.
+ {test,bs_start_match3,{f,39},1,[{x,0}],{x,1}}.
{test,bs_get_integer2,
{f,39},
2,
diff --git a/lib/compiler/test/bif_SUITE.erl b/lib/compiler/test/bif_SUITE.erl
index 42ba5d5365..423a7666af 100644
--- a/lib/compiler/test/bif_SUITE.erl
+++ b/lib/compiler/test/bif_SUITE.erl
@@ -23,7 +23,7 @@
-export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1,
init_per_group/2,end_per_group/2,
- beam_validator/1,trunc_and_friends/1,cover_safe_bifs/1]).
+ beam_validator/1,trunc_and_friends/1,cover_safe_and_pure_bifs/1]).
suite() ->
[{ct_hooks,[ts_install_cth]}].
@@ -35,7 +35,7 @@ groups() ->
[{p,[parallel],
[beam_validator,
trunc_and_friends,
- cover_safe_bifs
+ cover_safe_and_pure_bifs
]}].
init_per_suite(Config) ->
@@ -106,7 +106,7 @@ trunc_template(Func, Bif) ->
catch error:badarg -> ok end,
ok.").
-cover_safe_bifs(Config) ->
+cover_safe_and_pure_bifs(Config) ->
_ = get(),
_ = get_keys(a),
_ = group_leader(),
@@ -118,5 +118,6 @@ cover_safe_bifs(Config) ->
_ = processes(),
_ = registered(),
_ = term_to_binary(Config),
+ 42 = list_to_integer("2A", 16),
ok.
diff --git a/lib/compiler/test/bs_construct_SUITE.erl b/lib/compiler/test/bs_construct_SUITE.erl
index ccc49df005..69017d87e7 100644
--- a/lib/compiler/test/bs_construct_SUITE.erl
+++ b/lib/compiler/test/bs_construct_SUITE.erl
@@ -153,6 +153,8 @@ l(I_13, I_big1, I_16, Bin) ->
[0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
16#77,16#FF,16#FF,16#FF,16#FF,16#FF,16#FF,16#FF,16#FF,16#FF,16#FF,
16#FF,16#FF,16#FF,16#FF,16#FF,16#FF]),
+ ?T(<< (<<"abc",7:3>>):3/binary >>,
+ [$a,$b,$c]),
%% Mix different units.
?T(<<37558955:(I_16-12)/unit:8,1:1>>,
@@ -311,6 +313,9 @@ fail(Config) when is_list(Config) ->
{'EXIT',{badarg,_}} = (catch <<0:(-(1 bsl 100))>>),
{'EXIT',{badarg,_}} = (catch <<Bin/binary,0:(-(1 bsl 100))>>),
+ %% Unaligned sizes with literal binaries.
+ {'EXIT',{badarg,_}} = (catch <<0,(<<7777:17>>)/binary>>),
+
ok.
float_bin(Config) when is_list(Config) ->
diff --git a/lib/compiler/test/bs_match_SUITE.erl b/lib/compiler/test/bs_match_SUITE.erl
index a751f6fda5..2cfcb841a7 100644
--- a/lib/compiler/test/bs_match_SUITE.erl
+++ b/lib/compiler/test/bs_match_SUITE.erl
@@ -40,8 +40,10 @@
map_and_binary/1,unsafe_branch_caching/1,
bad_literals/1,good_literals/1,constant_propagation/1,
parse_xml/1,get_payload/1,escape/1,num_slots_different/1,
- beam_bsm/1,guard/1,is_ascii/1,non_opt_eq/1,erl_689/1,
- bs_start_match2_defs/1]).
+ beam_bsm/1,guard/1,is_ascii/1,non_opt_eq/1,
+ expression_before_match/1,erl_689/1,restore_on_call/1,
+ restore_after_catch/1,matches_on_parameter/1,big_positions/1,
+ matching_meets_apply/1,bs_start_match2_defs/1]).
-export([coverage_id/1,coverage_external_ignore/2]).
@@ -57,7 +59,7 @@ all() ->
[{group,p}].
groups() ->
- [{p,[parallel],
+ [{p,[],
[size_shadow,int_float,otp_5269,null_fields,wiger,
bin_tail,save_restore,
partitioned_bs_match,function_clause,unit,
@@ -73,8 +75,10 @@ groups() ->
map_and_binary,unsafe_branch_caching,
bad_literals,good_literals,constant_propagation,parse_xml,
get_payload,escape,num_slots_different,
- beam_bsm,guard,is_ascii,non_opt_eq,erl_689,
- bs_start_match2_defs]}].
+ beam_bsm,guard,is_ascii,non_opt_eq,
+ expression_before_match,erl_689,restore_on_call,
+ matches_on_parameter,big_positions,
+ matching_meets_apply,bs_start_match2_defs]}].
init_per_suite(Config) ->
@@ -250,6 +254,12 @@ bin_tail(Config) when is_list(Config) ->
ok = bin_tail_e(<<2:2,1:1,1:5,42:64>>),
error = bin_tail_e(<<3:2,1:1,1:5,42:64>>),
error = bin_tail_e(<<>>),
+
+ MD5 = erlang:md5(<<42>>),
+ <<"abc">> = bin_tail_f(<<MD5/binary,"abc">>, MD5, 3),
+ error = bin_tail_f(<<MD5/binary,"abc">>, MD5, 999),
+ {'EXIT',{_,_}} = (catch bin_tail_f(<<0:16/unit:8>>, MD5, 0)),
+
ok.
bin_tail_c(Bin, Offset) ->
@@ -306,6 +316,14 @@ bin_tail_e_var(Bin) ->
<<2:2,_:1,1:5,Tail/binary>> -> Tail;
_ -> error
end.
+
+bin_tail_f(Bin, MD5, Size) ->
+ case Bin of
+ <<MD5:16/binary, Tail:Size/binary>> ->
+ Tail;
+ <<MD5:16/binary, _/binary>> ->
+ error
+ end.
save_restore(Config) when is_list(Config) ->
0 = save_restore_1(<<0:2,42:6>>),
@@ -457,6 +475,15 @@ unit(Config) when is_list(Config) ->
127 = peek7(<<127:7>>),
100 = peek7(<<100:7,19:7>>),
fc(peek7, [<<1,2>>], catch peek7(<<1,2>>)),
+
+ 1 = unit_opt(1, -1),
+ 8 = unit_opt(8, -1),
+
+ <<1:32,"abc">> = unit_opt_2(<<1:32,"abc">>),
+ <<"def">> = unit_opt_2(<<2:32,"def">>),
+ {'EXIT',_} = (catch unit_opt_2(<<1:32,33:7>>)),
+ {'EXIT',_} = (catch unit_opt_2(<<2:32,55:7>>)),
+
ok.
peek1(<<B:8,_/bitstring>>) -> B.
@@ -467,6 +494,27 @@ peek8(<<B:8,_/binary>>) -> B.
peek16(<<B:16,_/binary-unit:16>>) -> B.
+unit_opt(U, X) ->
+ %% Cover type analysis in beam_ssa_type.
+ Bin = case U of
+ 1 -> <<X:7>>;
+ 8 -> <<X>>
+ end,
+ %% The type of Bin will be set to {binary,gcd(1, 8)}.
+ case Bin of
+ <<_/binary-unit:8>> -> 8;
+ <<_/binary-unit:1>> -> 1
+ end.
+
+unit_opt_2(<<St:32,KO/binary>> = Bin0) ->
+ Bin = if
+ St =:= 1 ->
+ Bin0;
+ St =:= 2 ->
+ <<KO/binary>>
+ end,
+ id(Bin).
+
shared_sub_bins(Config) when is_list(Config) ->
{15,[<<>>,<<5>>,<<4,5>>,<<3,4,5>>,<<2,3,4,5>>]} = sum(<<1,2,3,4,5>>, [], 0),
ok.
@@ -694,6 +742,20 @@ coverage(Config) when is_list(Config) ->
binary = coverage_bitstring(<<7>>),
bitstring = coverage_bitstring(<<7:4>>),
other = coverage_bitstring([a]),
+
+ %% Cover code in beam_trim.
+
+ {done,<<17,53>>,[253,155,200]} =
+ coverage_trim(<<253,155,200,17,53>>, e0, e1, e2, e3, []),
+
+ <<"(right|linux)">> = coverage_trim_1(<<"">>, <<"right">>, <<"linux">>),
+ <<"/(right|linux)">> = coverage_trim_1(<<"/">>, <<"right">>, <<"linux">>),
+ <<"(left|linux)/(right|linux)">> =
+ coverage_trim_1(<<"left">>, <<"right">>, <<"linux">>),
+
+ {10,<<"-">>,""} = coverage_trim_2(<<"-">>, 10, []),
+ {8,<<"-">>,"aa"} = coverage_trim_2(<<"aa-">>, 10, []),
+
ok.
coverage_fold(Fun, Acc, <<H,T/binary>>) ->
@@ -788,6 +850,37 @@ coverage_bitstring(Bin) when is_binary(Bin) -> binary;
coverage_bitstring(<<_/bitstring>>) -> bitstring;
coverage_bitstring(_) -> other.
+coverage_trim(<<C:8,T/binary>> = Bin, E0, E1, E2, E3, Acc) ->
+ case id(C > 128) of
+ true ->
+ coverage_trim(T, E0, E1, E2, E3, [C|Acc]);
+ false ->
+ {done,Bin,lists:reverse(Acc)}
+ end.
+
+coverage_trim_1(<<>>, Right, OsType) ->
+ do_coverage_trim_1(Right, OsType);
+coverage_trim_1(<<"/">>, Right, OsType) ->
+ <<"/",(do_coverage_trim_1(Right, OsType))/binary>>;
+coverage_trim_1(Left, Right, OsType) ->
+ <<(do_coverage_trim_1(Left, OsType))/binary,
+ "/",
+ (do_coverage_trim_1(Right, OsType))/binary>>.
+
+do_coverage_trim_1(A, OsType) ->
+ <<"(",A/binary,"|",OsType/binary,")">>.
+
+coverage_trim_2(<<C/utf8,R/binary>> = Bin, I, L) ->
+ case printable_char(C) of
+ true ->
+ coverage_trim_2(R, I - 1, [C | L]);
+ false ->
+ {I,Bin,lists:reverse(L)}
+ end.
+
+printable_char($a) -> true;
+printable_char(_) -> false.
+
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>>),
@@ -1754,11 +1847,10 @@ do_erl_689_2b(_, <<Length, Data/binary>>) ->
%% ERL-753
bs_start_match2_defs(_Config) ->
- {<<"http://127.0.0.1:1234/vsaas/hello">>} = api_url(<<"hello">>, dummy),
- {"https://127.0.0.1:4321/vsaas/hello"} = api_url({https, "hello"}, dummy).
+ {<<"http://127.0.0.1:1234/vsaas/hello">>} = api_url(<<"hello">>),
+ {"https://127.0.0.1:4321/vsaas/hello"} = api_url({https, "hello"}).
-api_url(URL, Auth) ->
- Header = [],
+api_url(URL) ->
case URL of
<<_/binary>> -> {<<"http://127.0.0.1:1234/vsaas/",URL/binary>>};
{https, [_|_] = URL1} -> {"https://127.0.0.1:4321/vsaas/"++URL1}
@@ -1767,4 +1859,113 @@ api_url(URL, Auth) ->
check(F, R) ->
R = F().
+%% Make sure that an expression that comes between function start and a match
+%% expression passes validation.
+expression_before_match(Config) when is_list(Config) ->
+ <<_,R/binary>> = id(<<0,1,2,3>>),
+ {1, <<2,3>>} = expression_before_match_1(R),
+ ok.
+
+expression_before_match_1(R) ->
+ A = id(1),
+ case R of
+ <<1,Bar/binary>> -> {A, Bar};
+ <<>> -> {A, baz}
+ end.
+
+%% Make sure that context positions are updated on calls.
+restore_on_call(Config) when is_list(Config) ->
+ ok = restore_on_call_1(<<0, 1, 2>>).
+
+restore_on_call_1(<<0, Rest/binary>>) ->
+ <<2>> = restore_on_call_2(Rest),
+ <<2>> = restore_on_call_2(Rest), %% {badmatch, <<>>} on missing restore.
+ ok.
+
+restore_on_call_2(<<1, Rest/binary>>) -> Rest;
+restore_on_call_2(Other) -> Other.
+
+%% 'catch' must invalidate positions.
+restore_after_catch(Config) when is_list(Config) ->
+ <<0, 1>> = restore_after_catch_1(<<0, 1>>),
+ ok.
+
+restore_after_catch_1(<<A/binary>>) ->
+ try throw_after_byte(A) of
+ _ -> impossible
+ catch
+ throw:_Any ->
+ %% Will equal <<1>> if the bug is present.
+ A
+ end.
+
+throw_after_byte(<<_,_/binary>>) ->
+ throw(away).
+
+matches_on_parameter(Config) when is_list(Config) ->
+ %% This improves coverage for matching on "naked" parameters.
+ {<<"urka">>, <<"a">>} = matches_on_parameter_1(<<"gurka">>),
+ ok = (catch matches_on_parameter_2(<<"10001110101">>, 0)).
+
+matches_on_parameter_1(Bin) ->
+ <<"g", A/binary>> = Bin,
+ <<_,_,"rk", B/binary>> = Bin,
+ {A, B}.
+
+matches_on_parameter_2(Bin, Offset) ->
+ <<_:Offset, Bit:1, Rest/bits>> = Bin,
+ case bit_size(Rest) of
+ 0 -> throw(ok);
+ _ -> [Bit | matches_on_parameter_2(Bin, Offset + 1)]
+ end.
+
+big_positions(Config) when is_list(Config) ->
+ %% This provides coverage for when match context positions no longer fit
+ %% into an immediate on 32-bit platforms.
+
+ A = <<0:((1 bsl 27) - 8), $A, 1:1, "gurka", $A>>,
+ B = <<0:((1 bsl 27) - 8), $B, "hello", $B>>,
+
+ {a,$A} = bp_start_match(A),
+ {b,$B} = bp_start_match(B),
+ {a,$A} = bp_getpos(A),
+ {b,$B} = bp_getpos(B),
+
+ ok.
+
+%% After the first iteration the context's position will no longer fit into an
+%% immediate. To improve performance the bs_start_match3 instruction will
+%% return a new context with an updated base position so that we won't have to
+%% resort to using bigints.
+bp_start_match(<<_:(1 bsl 27),T/bits>>) -> bp_start_match(T);
+bp_start_match(<<1:1,"gurka",A>>) -> {a,A};
+bp_start_match(<<"hello",B>>) -> {b,B}.
+
+%% This is a corner case where the above didn't work perfectly; if the position
+%% was _just_ small enough to fit into an immediate when bs_start_match3 was
+%% hit, but too large at bs_get_position, then it must be saved as a bigint.
+bp_getpos(<<_:((1 bsl 27) - 8),T/bits>>) -> bp_getpos(T);
+bp_getpos(<<A,1:1,"gurka",A>>) -> {a,A};
+bp_getpos(<<B,"hello",B>>) -> {b,B}.
+
+matching_meets_apply(_Config) ->
+ <<"abc">> = do_matching_meets_apply(<<"/abc">>, []),
+ 42 = do_matching_meets_apply(<<"">>, {erlang,-42}),
+ 100 = do_matching_meets_apply(no_binary, {erlang,-100}),
+ ok.
+
+do_matching_meets_apply(<<$/, Rest/binary>>, _Handler) ->
+ id(Rest);
+do_matching_meets_apply(<<_/binary>>=Name, never_matches_a) ->
+ %% Used to crash the compiler because variables in a remote
+ %% were not handled properly by beam_ssa_bsm.
+ Name:foo(gurka);
+do_matching_meets_apply(<<_/binary>>=Name, never_matches_b) ->
+ %% Another case of the above.
+ foo:Name(gurka);
+do_matching_meets_apply(_Bin, {Handler, State}) ->
+ %% Another case of the above.
+ Handler:abs(State).
+
+
id(I) -> I.
diff --git a/lib/compiler/test/compilation_SUITE.erl b/lib/compiler/test/compilation_SUITE.erl
index 139f7af0d4..74f9dbd9b4 100644
--- a/lib/compiler/test/compilation_SUITE.erl
+++ b/lib/compiler/test/compilation_SUITE.erl
@@ -170,7 +170,7 @@ try_it(Module, Conf) ->
atom_to_list(Module)),
Out = proplists:get_value(priv_dir,Conf),
io:format("Compiling: ~s\n", [Src]),
- CompRc0 = compile:file(Src, [clint0,clint,{outdir,Out},report,
+ CompRc0 = compile:file(Src, [clint0,clint,ssalint,{outdir,Out},report,
bin_opt_info|OtherOpts]),
io:format("Result: ~p\n",[CompRc0]),
{ok,_Mod} = CompRc0,
@@ -189,7 +189,7 @@ try_it(Module, Conf) ->
ct:timetrap(Timetrap),
io:format("Compiling (with old inliner): ~s\n", [Src]),
- CompRc2 = compile:file(Src, [clint,
+ CompRc2 = compile:file(Src, [clint,ssalint,
{outdir,Out},report,bin_opt_info,
{inline,1000}|OtherOpts]),
io:format("Result: ~p\n",[CompRc2]),
@@ -355,7 +355,7 @@ compile_compiler(Files, OutDir, Version, InlineOpts) ->
io:format("~ts", [code:which(compile)]),
io:format("Compiling ~s into ~ts", [Version,OutDir]),
Opts = [report,
- clint0,clint,
+ clint0,clint,ssalint,
bin_opt_info,
{outdir,OutDir},
{d,'COMPILER_VSN',"\""++Version++"\""},
diff --git a/lib/compiler/test/compile_SUITE.erl b/lib/compiler/test/compile_SUITE.erl
index 6b230710b3..408af80dd9 100644
--- a/lib/compiler/test/compile_SUITE.erl
+++ b/lib/compiler/test/compile_SUITE.erl
@@ -28,12 +28,12 @@
init_per_group/2,end_per_group/2,
app_test/1,appup_test/1,
debug_info/4, custom_debug_info/1, custom_compile_info/1,
- file_1/1, forms_2/1, module_mismatch/1, big_file/1, outdir/1,
+ file_1/1, forms_2/1, module_mismatch/1, outdir/1,
binary/1, makedep/1, cond_and_ifdef/1, listings/1, listings_big/1,
other_output/1, kernel_listing/1, encrypted_abstr/1,
strict_record/1, utf8_atoms/1, utf8_functions/1, extra_chunks/1,
cover/1, env/1, core_pp/1, tuple_calls/1,
- core_roundtrip/1, asm/1, optimized_guards/1,
+ core_roundtrip/1, asm/1,
sys_pre_attributes/1, dialyzer/1,
warnings/1, pre_load_check/1, env_compiler_options/1,
bc_options/1, deterministic_include/1, deterministic_paths/1
@@ -46,11 +46,11 @@ suite() -> [{ct_hooks,[ts_install_cth]}].
-spec all() -> all_return_type().
all() ->
- [app_test, appup_test, file_1, forms_2, module_mismatch, big_file, outdir,
+ [app_test, appup_test, file_1, forms_2, module_mismatch, outdir,
binary, makedep, cond_and_ifdef, listings, listings_big,
other_output, kernel_listing, encrypted_abstr, tuple_calls,
strict_record, utf8_atoms, utf8_functions, extra_chunks,
- cover, env, core_pp, core_roundtrip, asm, optimized_guards,
+ cover, env, core_pp, core_roundtrip, asm,
sys_pre_attributes, dialyzer, warnings, pre_load_check,
env_compiler_options, custom_debug_info, bc_options,
custom_compile_info, deterministic_include, deterministic_paths].
@@ -104,6 +104,7 @@ file_1(Config) when is_list(Config) ->
compile_and_verify(Simple, Target, []),
compile_and_verify(Simple, Target, [native]),
compile_and_verify(Simple, Target, [debug_info]),
+ compile_and_verify(Simple, Target, [no_postopt]),
{ok,simple} = compile:file(Simple, [no_line_info]), %Coverage
{ok,simple} = compile:file(Simple, [{eprof,beam_z}]), %Coverage
@@ -231,17 +232,6 @@ module_mismatch(Config) when is_list(Config) ->
ok.
-big_file(Config) when is_list(Config) ->
- {Big,Target} = get_files(Config, big, "big_file"),
- ok = file:set_cwd(filename:dirname(Target)),
- compile_and_verify(Big, Target, []),
- compile_and_verify(Big, Target, [debug_info]),
- compile_and_verify(Big, Target, [no_postopt]),
-
- %% Cleanup.
- ok = file:delete(Target),
- ok.
-
%% Tests that the {outdir, Dir} option works.
outdir(Config) when is_list(Config) ->
@@ -370,41 +360,37 @@ do_file_listings(DataDir, PrivDir, [File|Files]) ->
TargetDir = filename:join(PrivDir, listings),
ok = file:make_dir(TargetDir),
- %% Test all dedicated listing options.
- do_listing(Simple, TargetDir, 'S'),
- do_listing(Simple, TargetDir, 'E'),
- do_listing(Simple, TargetDir, 'P'),
- do_listing(Simple, TargetDir, dpp, ".pp"),
- do_listing(Simple, TargetDir, dabstr, ".abstr"),
- do_listing(Simple, TargetDir, dexp, ".expand"),
- do_listing(Simple, TargetDir, dcore, ".core"),
- do_listing(Simple, TargetDir, doldinline, ".oldinline"),
- do_listing(Simple, TargetDir, dinline, ".inline"),
- do_listing(Simple, TargetDir, dcore, ".core"),
- do_listing(Simple, TargetDir, dcopt, ".copt"),
- do_listing(Simple, TargetDir, dcbsm, ".core_bsm"),
- do_listing(Simple, TargetDir, dsetel, ".dsetel"),
- do_listing(Simple, TargetDir, dkern, ".kernel"),
- do_listing(Simple, TargetDir, dcg, ".codegen"),
- do_listing(Simple, TargetDir, dblk, ".block"),
- do_listing(Simple, TargetDir, dexcept, ".except"),
- do_listing(Simple, TargetDir, dbs, ".bs"),
- do_listing(Simple, TargetDir, dtype, ".type"),
- do_listing(Simple, TargetDir, ddead, ".dead"),
- do_listing(Simple, TargetDir, djmp, ".jump"),
- do_listing(Simple, TargetDir, dclean, ".clean"),
- do_listing(Simple, TargetDir, dpeep, ".peep"),
- do_listing(Simple, TargetDir, dopt, ".optimize"),
-
- %% First clean up.
- Listings = filename:join(PrivDir, listings),
- lists:foreach(fun(F) -> ok = file:delete(F) end,
- filelib:wildcard(filename:join(Listings, "*"))),
+ List = [{'S',".S"},
+ {'E',".E"},
+ {'P',".P"},
+ {dpp, ".pp"},
+ {dabstr, ".abstr"},
+ {dexp, ".expand"},
+ {dcore, ".core"},
+ {doldinline, ".oldinline"},
+ {dinline, ".inline"},
+ {dcore, ".core"},
+ {dcopt, ".copt"},
+ {dcbsm, ".core_bsm"},
+ {dkern, ".kernel"},
+ {dssa, ".ssa"},
+ {dssaopt, ".ssaopt"},
+ {dprecg, ".precodegen"},
+ {dcg, ".codegen"},
+ {dblk, ".block"},
+ {dexcept, ".except"},
+ {djmp, ".jump"},
+ {dclean, ".clean"},
+ {dpeep, ".peep"},
+ {dopt, ".optimize"},
+ {diffable, ".S"}],
+ p_listings(List, Simple, TargetDir),
%% Test options that produce a listing file if 'binary' is not given.
do_listing(Simple, TargetDir, to_pp, ".P"),
do_listing(Simple, TargetDir, to_exp, ".E"),
do_listing(Simple, TargetDir, to_core0, ".core"),
+ Listings = filename:join(PrivDir, listings),
ok = file:delete(filename:join(Listings, File ++ ".core")),
do_listing(Simple, TargetDir, to_core, ".core"),
do_listing(Simple, TargetDir, to_kernel, ".kernel"),
@@ -420,21 +406,35 @@ do_file_listings(DataDir, PrivDir, [File|Files]) ->
listings_big(Config) when is_list(Config) ->
{Big,Target} = get_files(Config, big, listings_big),
TargetDir = filename:dirname(Target),
- do_listing(Big, TargetDir, 'S'),
- do_listing(Big, TargetDir, 'E'),
- do_listing(Big, TargetDir, 'P'),
- do_listing(Big, TargetDir, dkern, ".kernel"),
- do_listing(Big, TargetDir, to_dis, ".dis"),
-
- TargetNoext = filename:rootname(Target, code:objfile_extension()),
- {ok,big} = compile:file(TargetNoext, [from_asm,{outdir,TargetDir}]),
-
- %% Cleanup.
- ok = file:delete(Target),
- lists:foreach(fun(F) -> ok = file:delete(F) end,
- filelib:wildcard(filename:join(TargetDir, "*"))),
- ok = file:del_dir(TargetDir),
- ok.
+ List = [{'S',".S"},
+ {'E',".E"},
+ {'P',".P"},
+ {dkern, ".kernel"},
+ {dssa, ".ssa"},
+ {dssaopt, ".ssaopt"},
+ {dprecg, ".precodegen"},
+ {to_dis, ".dis"}],
+ p_listings(List, Big, TargetDir).
+
+p_listings(List, File, BaseDir) ->
+ Run = fun({Option,Extension}) ->
+ Uniq = erlang:unique_integer([positive]),
+ Dir = filename:join(BaseDir, integer_to_list(Uniq)),
+ ok = file:make_dir(Dir),
+ try
+ do_listing(File, Dir, Option, Extension),
+ ok
+ catch
+ Class:Error:Stk ->
+ io:format("~p:~p\n~p\n", [Class,Error,Stk]),
+ error
+ after
+ _ = [ok = file:delete(F) ||
+ F <- filelib:wildcard(filename:join(Dir, "*"))],
+ ok = file:del_dir(Dir)
+ end
+ end,
+ test_lib:p_run(Run, List).
other_output(Config) when is_list(Config) ->
{Simple,_Target} = get_files(Config, simple, "other_output"),
@@ -681,9 +681,6 @@ cover(Config) when is_list(Config) ->
io:format("~p\n", [compile:options()]),
ok.
-do_listing(Source, TargetDir, Type) ->
- do_listing(Source, TargetDir, Type, "." ++ atom_to_list(Type)).
-
do_listing(Source, TargetDir, Type, Ext) ->
io:format("Source: ~p TargetDir: ~p\n Type: ~p Ext: ~p\n",
[Source, TargetDir, Type, Ext]),
@@ -920,7 +917,7 @@ do_core_pp_1(M, A, Outdir) ->
ok = file:delete(CoreFile),
%% Compile as usual (including optimizations).
- compile_forms(M, Core, [clint,from_core,binary]),
+ compile_forms(M, Core, [clint,ssalint,from_core,binary]),
%% Don't optimize to test that we are not dependent
%% on the Core Erlang optmimization passes.
@@ -929,7 +926,7 @@ do_core_pp_1(M, A, Outdir) ->
%% records; if sys_core_fold was run it would fix
%% that; if sys_core_fold was not run v3_kernel would
%% crash.)
- compile_forms(M, Core, [clint,from_core,no_copt,binary]),
+ compile_forms(M, Core, [clint,ssalint,from_core,no_copt,binary]),
ok.
@@ -1170,95 +1167,6 @@ do_asm(Beam, Outdir) ->
error
end.
-%% Make sure that guards are fully optimized. Guards should
-%% should use 'test' instructions, not 'bif' instructions.
-
-optimized_guards(_Config) ->
- TestBeams = get_unique_beam_files(),
- test_lib:p_run(fun(F) -> do_opt_guards(F) end, TestBeams).
-
-do_opt_guards(Beam) ->
- {ok,{M,[{abstract_code,{raw_abstract_v1,A}}]}} =
- beam_lib:chunks(Beam, [abstract_code]),
- try
- {ok,M,Asm} = compile:forms(A, ['S']),
- do_opt_guards_mod(Asm)
- catch Class:Error:Stk ->
- io:format("~p: ~p ~p\n~p\n", [M,Class,Error,Stk]),
- error
- end.
-
-do_opt_guards_mod({Mod,_Exp,_Attr,Asm,_NumLabels}) ->
- case do_opt_guards_fs(Mod, Asm) of
- [] ->
- ok;
- [_|_]=Bifs ->
- io:format("ERRORS FOR ~p:\n~p\n", [Mod,Bifs]),
- error
- end.
-
-do_opt_guards_fs(Mod, [{function,Name,Arity,_,Is}|Fs]) ->
- Bifs0 = do_opt_guards_fun(Is),
-
- %% The compiler does not attempt to optimize 'xor'.
- %% Therefore, ignore all functions that use 'xor' in
- %% a guard.
- Bifs = case lists:any(fun({bif,'xor',_,_,_}) -> true;
- (_) -> false
- end, Bifs0) of
- true -> [];
- false -> Bifs0
- end,
-
- %% Filter out the allowed exceptions.
- FA = {Name,Arity},
- case {Bifs,is_exception(Mod, FA)} of
- {[_|_],true} ->
- io:format("~p:~p/~p IGNORED:\n~p\n",
- [Mod,Name,Arity,Bifs]),
- do_opt_guards_fs(Mod, Fs);
- {[_|_],false} ->
- [{FA,Bifs}|do_opt_guards_fs(Mod, Fs)];
- {[],false} ->
- do_opt_guards_fs(Mod, Fs);
- {[],true} ->
- io:format("Redundant exception for ~p:~p/~p\n",
- [Mod,Name,Arity]),
- error(redundant)
- end;
-do_opt_guards_fs(_, []) -> [].
-
-do_opt_guards_fun([{bif,Name,{f,F},As,_}=I|Is]) when F =/= 0 ->
- Arity = length(As),
- case erl_internal:comp_op(Name, Arity) orelse
- erl_internal:bool_op(Name, Arity) orelse
- erl_internal:new_type_test(Name, Arity) of
- true ->
- [I|do_opt_guards_fun(Is)];
- false ->
- do_opt_guards_fun(Is)
- end;
-do_opt_guards_fun([_|Is]) ->
- do_opt_guards_fun(Is);
-do_opt_guards_fun([]) -> [].
-
-is_exception(bs_match_SUITE, {matching_and_andalso_2,2}) -> true;
-is_exception(bs_match_SUITE, {matching_and_andalso_3,2}) -> true;
-is_exception(guard_SUITE, {'-complex_not/1-fun-4-',1}) -> true;
-is_exception(guard_SUITE, {'-complex_not/1-fun-5-',1}) -> true;
-is_exception(guard_SUITE, {basic_andalso_orelse,1}) -> true;
-is_exception(guard_SUITE, {bad_guards,1}) -> true;
-is_exception(guard_SUITE, {bad_guards_2,2}) -> true;
-is_exception(guard_SUITE, {bad_guards_3,2}) -> true;
-is_exception(guard_SUITE, {cqlc,4}) -> true;
-is_exception(guard_SUITE, {csemi7,3}) -> true;
-is_exception(guard_SUITE, {misc,1}) -> true;
-is_exception(guard_SUITE, {nested_not_2b,4}) -> true;
-is_exception(guard_SUITE, {tricky_1,2}) -> true;
-is_exception(map_SUITE, {map_guard_update,2}) -> true;
-is_exception(map_SUITE, {map_guard_update_variables,3}) -> true;
-is_exception(_, _) -> false.
-
sys_pre_attributes(Config) ->
DataDir = proplists:get_value(data_dir, Config),
File = filename:join(DataDir, "attributes.erl"),
@@ -1475,36 +1383,49 @@ env_compiler_options(_Config) ->
bc_options(Config) ->
DataDir = proplists:get_value(data_dir, Config),
- 101 = highest_opcode(DataDir, small_float, [no_get_hd_tl,no_line_info]),
-
- 103 = highest_opcode(DataDir, big,
- [no_get_hd_tl,no_record_opt,
- no_line_info,no_stack_trimming]),
-
- 125 = highest_opcode(DataDir, small_float,
- [no_get_hd_tl,no_line_info,no_float_opt]),
-
- 132 = highest_opcode(DataDir, small,
- [no_get_hd_tl,no_record_opt,no_float_opt,no_line_info]),
-
- 136 = highest_opcode(DataDir, big, [no_get_hd_tl,no_record_opt,no_line_info]),
-
- 153 = highest_opcode(DataDir, big, [no_get_hd_tl,no_record_opt]),
- 153 = highest_opcode(DataDir, big, [r16]),
- 153 = highest_opcode(DataDir, big, [r17]),
- 153 = highest_opcode(DataDir, big, [r18]),
- 153 = highest_opcode(DataDir, big, [r19]),
- 153 = highest_opcode(DataDir, small_float, [r16]),
- 153 = highest_opcode(DataDir, small_float, []),
-
- 158 = highest_opcode(DataDir, small_maps, [r17]),
- 158 = highest_opcode(DataDir, small_maps, [r18]),
- 158 = highest_opcode(DataDir, small_maps, [r19]),
- 158 = highest_opcode(DataDir, small_maps, [r20]),
- 158 = highest_opcode(DataDir, small_maps, []),
-
- 163 = highest_opcode(DataDir, big, []),
-
+ L = [{101, small_float, [no_get_hd_tl,no_line_info]},
+ {103, big, [no_put_tuple2,no_get_hd_tl,no_ssa_opt_record,
+ no_line_info,no_stack_trimming]},
+ {125, small_float, [no_get_hd_tl,no_line_info,no_ssa_opt_float]},
+
+ {132, small, [no_put_tuple2,no_get_hd_tl,no_ssa_opt_record,
+ no_ssa_opt_float,no_line_info,no_bsm3]},
+
+ {153, small, [r20]},
+ {153, small, [r21]},
+
+ {136, big, [no_put_tuple2,no_get_hd_tl,
+ no_ssa_opt_record,no_line_info]},
+
+ {153, big, [no_put_tuple2,no_get_hd_tl, no_ssa_opt_record]},
+ {153, big, [r16]},
+ {153, big, [r17]},
+ {153, big, [r18]},
+ {153, big, [r19]},
+ {153, small_float, [r16]},
+ {153, small_float, []},
+
+ {158, small_maps, [r17]},
+ {158, small_maps, [r18]},
+ {158, small_maps, [r19]},
+ {158, small_maps, [r20]},
+ {158, small_maps, [r21]},
+
+ {164, small_maps, []},
+ {164, big, []}
+ ],
+
+ Test = fun({Expected,Mod,Options}) ->
+ case highest_opcode(DataDir, Mod, Options) of
+ Expected ->
+ ok;
+ Got ->
+ io:format("*** module ~p, options ~p => got ~p; expected ~p\n",
+ [Mod,Options,Got,Expected]),
+ error
+ end
+ end,
+ test_lib:p_run(Test, L),
ok.
highest_opcode(DataDir, Mod, Opt) ->
diff --git a/lib/compiler/test/compiler.cover b/lib/compiler/test/compiler.cover
index 3fd7fc1937..fac0f9947c 100644
--- a/lib/compiler/test/compiler.cover
+++ b/lib/compiler/test/compiler.cover
@@ -1,5 +1,4 @@
-{incl_app,compiler,details}.
-
%% -*- erlang -*-
+{local_only,compiler,true}.
+{incl_app,compiler,details}.
{excl_mods,compiler,[core_scan,core_parse]}.
-
diff --git a/lib/compiler/test/core_fold_SUITE.erl b/lib/compiler/test/core_fold_SUITE.erl
index 47606014c3..adfebd5158 100644
--- a/lib/compiler/test/core_fold_SUITE.erl
+++ b/lib/compiler/test/core_fold_SUITE.erl
@@ -212,9 +212,14 @@ bifs(Config) when is_list(Config) ->
{ok,#{K:=V}} = id(list_to_tuple([ok,#{K=>V}])),
ok.
--define(CMP_SAME(A0, B), (fun(A) -> true = A == B, false = A /= B end)(id(A0))).
--define(CMP_DIFF(A0, B), (fun(A) -> false = A == B, true = A /= B end)(id(A0))).
-
+-define(CMP_SAME0(A0, B), (fun(A) -> true = A == B, false = A /= B end)(id(A0))).
+-define(CMP_SAME1(A0, B), (fun(A) -> false = A /= B, true = A == B end)(id(A0))).
+-define(CMP_SAME(A0, B), (true = ?CMP_SAME0(A0, B) =:= not ?CMP_SAME1(A0, B))).
+
+-define(CMP_DIFF0(A0, B), (fun(A) -> false = A == B, true = A /= B end)(id(A0))).
+-define(CMP_DIFF1(A0, B), (fun(A) -> true = A /= B, false = A == B end)(id(A0))).
+-define(CMP_DIFF(A0, B), (true = ?CMP_DIFF0(A0, B) =:= not ?CMP_DIFF1(A0, B))).
+
eq(Config) when is_list(Config) ->
?CMP_SAME([a,b,c], [a,b,c]),
?CMP_SAME([42.0], [42.0]),
@@ -278,6 +283,8 @@ coverage(Config) when is_list(Config) ->
a = cover_remove_non_vars_alias({a,b,c}),
error = cover_will_match_lit_list(),
{ok,[a]} = cover_is_safe_bool_expr(a),
+ false = cover_is_safe_bool_expr2(a),
+ ok = cover_eval_is_function(fun id/1),
ok = cover_opt_guard_try(#cover_opt_guard_try{list=[a]}),
error = cover_opt_guard_try(#cover_opt_guard_try{list=[]}),
@@ -341,6 +348,15 @@ cover_is_safe_bool_expr(X) ->
false
end.
+cover_is_safe_bool_expr2(X) ->
+ try
+ V = [X],
+ is_function(V, 1)
+ catch
+ _:_ ->
+ false
+ end.
+
cover_opt_guard_try(Msg) ->
if
length(Msg#cover_opt_guard_try.list) =/= 1 ->
@@ -349,6 +365,12 @@ cover_opt_guard_try(Msg) ->
ok
end.
+cover_eval_is_function(X) ->
+ case X of
+ {a,_} -> is_function(X);
+ _ -> ok
+ end.
+
bsm_an_inlined(<<_:8>>, _) -> ok;
bsm_an_inlined(_, _) -> error.
@@ -356,7 +378,7 @@ unused_multiple_values_error(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
Dir = test_lib:get_data_dir(Config),
Core = filename:join(Dir, "unused_multiple_values_error"),
- Opts = [no_copt,clint,return,from_core,{outdir,PrivDir}
+ Opts = [no_copt,clint,ssalint,return,from_core,{outdir,PrivDir}
|test_lib:opt_opts(?MODULE)],
{error,[{unused_multiple_values_error,
[{none,core_lint,{return_mismatch,{hello,1}}}]}],
@@ -480,7 +502,7 @@ source(true, Activities) ->
Activities
end.
-tim(#{reduction := Emergency}) ->
+tim(#{reduction := _Emergency}) ->
try
fun() -> surgery end
catch
diff --git a/lib/compiler/test/float_SUITE.erl b/lib/compiler/test/float_SUITE.erl
index 012810aba2..831e8279aa 100644
--- a/lib/compiler/test/float_SUITE.erl
+++ b/lib/compiler/test/float_SUITE.erl
@@ -20,7 +20,8 @@
-module(float_SUITE).
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
- pending/1,bif_calls/1,math_functions/1,mixed_float_and_int/1]).
+ pending/1,bif_calls/1,math_functions/1,mixed_float_and_int/1,
+ subtract_number_type/1]).
-include_lib("common_test/include/ct.hrl").
@@ -28,7 +29,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[pending, bif_calls, math_functions,
- mixed_float_and_int].
+ mixed_float_and_int, subtract_number_type].
groups() ->
[].
@@ -176,5 +177,15 @@ mixed_float_and_int(Config) when is_list(Config) ->
pc(Cov, NotCov, X) ->
round(Cov/(Cov+NotCov)*100) + 42 + 2.0*X.
+subtract_number_type(Config) when is_list(Config) ->
+ 120 = fact(5).
+
+fact(N) ->
+ fact(N, 1).
+
+fact(0, P) -> P;
+fact(1, P) -> P;
+fact(N, P) -> fact(N-1, P*N).
+
id(I) -> I.
diff --git a/lib/compiler/test/fun_SUITE.erl b/lib/compiler/test/fun_SUITE.erl
index e00885fcd6..1df0a05275 100644
--- a/lib/compiler/test/fun_SUITE.erl
+++ b/lib/compiler/test/fun_SUITE.erl
@@ -249,6 +249,13 @@ badfun(_Config) ->
expect_badfun(X, catch X(put(?FUNCTION_NAME, of_course))),
of_course = erase(?FUNCTION_NAME),
+ %% A literal as a Fun used to crash the code generator. This only happened
+ %% when type optimization had reduced `Fun` to a literal, hence the match.
+ Literal = fun(literal = Fun) ->
+ Fun()
+ end,
+ expect_badfun(literal, catch Literal(literal)),
+
ok.
expect_badfun(Term, Exit) ->
diff --git a/lib/compiler/test/guard_SUITE.erl b/lib/compiler/test/guard_SUITE.erl
index 6ad73b46f7..ed0a56f064 100644
--- a/lib/compiler/test/guard_SUITE.erl
+++ b/lib/compiler/test/guard_SUITE.erl
@@ -35,8 +35,7 @@
basic_andalso_orelse/1,traverse_dcd/1,
check_qlc_hrl/1,andalso_semi/1,t_tuple_size/1,binary_part/1,
bad_constants/1,bad_guards/1,
- guard_in_catch/1,beam_bool_SUITE/1,
- cover_beam_dead/1]).
+ guard_in_catch/1,beam_bool_SUITE/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -54,8 +53,7 @@ groups() ->
rel_ops,rel_op_combinations,
literal_type_tests,basic_andalso_orelse,traverse_dcd,
check_qlc_hrl,andalso_semi,t_tuple_size,binary_part,
- bad_constants,bad_guards,guard_in_catch,beam_bool_SUITE,
- cover_beam_dead]}].
+ bad_constants,bad_guards,guard_in_catch,beam_bool_SUITE]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -1297,6 +1295,32 @@ rel_ops(Config) when is_list(Config) ->
Empty = id([]),
?T(==, [], Empty),
+ %% Cover beam_ssa_dead:turn_op('/=').
+ ok = (fun(A, B) when is_atom(A) ->
+ X = id(A /= B),
+ if
+ X -> ok;
+ true -> error
+ end
+ end)(a, b),
+ ok = (fun(A, B) when is_atom(A) ->
+ X = id(B /= A),
+ if
+ X -> ok;
+ true -> error
+ end
+ end)(a, b),
+
+ %% Cover beam_ssa_dead.
+ Arrow = fun([T1,T2]) when T1 == $>, T2 == $>;
+ T1 == $<, T2 == $| -> true;
+ (_) -> false
+ end,
+ true = Arrow(">>"),
+ true = Arrow("<|"),
+ false = Arrow("><"),
+ false = Arrow(""),
+
ok.
-undef(TestOp).
@@ -1330,6 +1354,9 @@ rel_op_combinations_1(N, Digits) ->
Bool = is_digit_6(N),
Bool = is_digit_7(N),
Bool = is_digit_8(N),
+ Bool = is_digit_9(42, N),
+ Bool = is_digit_10(N, 0),
+ Bool = is_digit_11(N, 0),
rel_op_combinations_1(N-1, Digits).
is_digit_1(X) when 16#0660 =< X, X =< 16#0669 -> true;
@@ -1373,6 +1400,24 @@ is_digit_8(X) when X =< 16#0669, X > (16#0660-1) -> true;
is_digit_8(16#0670) -> false;
is_digit_8(_) -> false.
+is_digit_9(A, 0) when A =:= 42 -> false;
+is_digit_9(_, X) when X > 16#065F, X < 16#066A -> true;
+is_digit_9(_, X) when 16#0030 =< X, X =< 16#0039 -> true;
+is_digit_9(_, X) when 16#06F0 =< X, X =< 16#06F9 -> true;
+is_digit_9(_, _) -> false.
+
+is_digit_10(0, 0) -> false;
+is_digit_10(X, _) when X < 16#066A, 16#0660 =< X -> true;
+is_digit_10(X, _) when 16#0030 =< X, X =< 16#0039 -> true;
+is_digit_10(X, _) when 16#06F0 =< X, X =< 16#06F9 -> true;
+is_digit_10(_, _) -> false.
+
+is_digit_11(0, 0) -> false;
+is_digit_11(X, _) when X =< 16#0669, 16#0660 =< X -> true;
+is_digit_11(X, _) when 16#0030 =< X, X =< 16#0039 -> true;
+is_digit_11(X, _) when 16#06F0 =< X, X =< 16#06F9 -> true;
+is_digit_11(_, _) -> false.
+
rel_op_combinations_2(0, _) ->
ok;
rel_op_combinations_2(N, Range) ->
@@ -1473,6 +1518,7 @@ rel_op_combinations_3(N, Red) ->
Val = redundant_9(N),
Val = redundant_10(N),
Val = redundant_11(N),
+ Val = redundant_11(N),
rel_op_combinations_3(N-1, Red).
redundant_1(X) when X >= 51, X =< 80 -> 5*X;
@@ -1527,6 +1573,10 @@ redundant_11(X) when X =:= 10 -> 2*X;
redundant_11(X) when X >= 51, X =< 80 -> 5*X;
redundant_11(_) -> none.
+redundant_12(X) when X >= 50, X =< 80 -> 2*X;
+redundant_12(X) when X < 51 -> 5*X;
+redundant_12(_) -> none.
+
%% Test type tests on literal values. (From emulator test suites.)
literal_type_tests(Config) when is_list(Config) ->
case ?MODULE of
@@ -1779,15 +1829,10 @@ t_tuple_size(Config) when is_list(Config) ->
error = ludicrous_tuple_size({a,b,c}),
error = ludicrous_tuple_size([a,b,c]),
- %% Test the "unsafe case" - the register assigned the tuple size is
- %% not killed.
- DataDir = test_lib:get_data_dir(Config),
- File = filename:join(DataDir, "guard_SUITE_tuple_size"),
- {ok,Mod,Code} = compile:file(File, [from_asm,binary]),
- code:load_binary(Mod, File, Code),
- 14 = Mod:t({1,2,3,4}),
- _ = code:delete(Mod),
- _ = code:purge(Mod),
+ good_ip({1,2,3,4}),
+ good_ip({1,2,3,4,5,6,7,8}),
+ error = validate_ip({42,11}),
+ error = validate_ip(atom),
ok.
@@ -1805,6 +1850,16 @@ ludicrous_tuple_size(T)
when tuple_size(T) =:= 16#FFFFFFFFFFFFFFFF -> ok;
ludicrous_tuple_size(_) -> error.
+good_ip(IP) ->
+ IP = validate_ip(IP).
+
+validate_ip(Value) when is_tuple(Value) andalso
+ ((size(Value) =:= 4) orelse (size(Value) =:= 8)) ->
+ %% size/1 (converted to tuple_size) used more than once.
+ Value;
+validate_ip(_) ->
+ error.
+
%%
%% The binary_part/2,3 guard BIFs
%%
@@ -2206,32 +2261,6 @@ maps() ->
evidence(#{0 := Charge}) when 0; #{[] => Charge} == #{[] => 42} ->
ok.
-cover_beam_dead(_Config) ->
- Mod = ?FUNCTION_NAME,
- Attr = [],
- Fs = [{function,test,1,2,
- [{label,1},
- {line,[]},
- {func_info,{atom,Mod},{atom,test},1},
- {label,2},
- %% Cover beam_dead:turn_op/1 using swapped operand order.
- {test,is_ne_exact,{f,3},[{integer,1},{x,0}]},
- {test,is_eq_exact,{f,1},[{atom,a},{x,0}]},
- {label,3},
- {move,{atom,ok},{x,0}},
- return]}],
- Exp = [{test,1}],
- Asm = {Mod,Exp,Attr,Fs,3},
- {ok,Mod,Beam} = compile:forms(Asm, [from_asm,binary,report]),
- {module,Mod} = code:load_binary(Mod, Mod, Beam),
- ok = Mod:test(1),
- ok = Mod:test(a),
- {'EXIT',_} = (catch Mod:test(other)),
- true = code:delete(Mod),
- _ = code:purge(Mod),
-
- ok.
-
%% Call this function to turn off constant propagation.
id(I) -> I.
diff --git a/lib/compiler/test/guard_SUITE_data/guard_SUITE_tuple_size.S b/lib/compiler/test/guard_SUITE_data/guard_SUITE_tuple_size.S
deleted file mode 100644
index cffb792920..0000000000
--- a/lib/compiler/test/guard_SUITE_data/guard_SUITE_tuple_size.S
+++ /dev/null
@@ -1,30 +0,0 @@
-{module, guard_SUITE_tuple_size}. %% version = 0
-
-{exports, [{t,1}]}.
-
-{attributes, []}.
-
-{labels, 5}.
-
-
-{function, t, 1, 2}.
- {label,1}.
- {func_info,{atom,guard_SUITE_tuple_size},{atom,t},1}.
- {label,2}.
- {bif,tuple_size,{f,4},[{x,0}],{x,1}}.
- {test,is_eq_exact,{f,4},[{x,1},{integer,4}]}.
- {test,is_tuple,{f,3},[{x,0}]}.
- {test,test_arity,{f,3},[{x,0},4]}.
- {get_tuple_element,{x,0},0,{x,5}}.
- {get_tuple_element,{x,0},1,{x,2}}.
- {get_tuple_element,{x,0},2,{x,3}}.
- {get_tuple_element,{x,0},3,{x,4}}.
- {gc_bif,'+',{f,0},6,[{x,1},{x,2}],{x,0}}.
- {gc_bif,'+',{f,0},6,[{x,0},{x,3}],{x,0}}.
- {gc_bif,'+',{f,0},6,[{x,0},{x,4}],{x,0}}.
- {gc_bif,'+',{f,0},6,[{x,0},{x,5}],{x,0}}.
- return.
- {label,3}.
- {badmatch,{x,0}}.
- {label,4}.
- {jump,{f,1}}.
diff --git a/lib/compiler/test/inline_SUITE.erl b/lib/compiler/test/inline_SUITE.erl
index dcc703c3e1..aff1a56c47 100644
--- a/lib/compiler/test/inline_SUITE.erl
+++ b/lib/compiler/test/inline_SUITE.erl
@@ -42,13 +42,9 @@ groups() ->
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
- Pa = "-pa " ++ filename:dirname(code:which(?MODULE)),
- {ok,Node} = start_node(compiler, Pa),
- [{testing_node,Node}|Config].
+ Config.
-end_per_suite(Config) ->
- Node = proplists:get_value(testing_node, Config),
- test_server:stop_node(Node),
+end_per_suite(_Config) ->
ok.
init_per_group(_GroupName, Config) ->
@@ -89,26 +85,26 @@ attribute(Config) when is_list(Config) ->
?comp(maps_inline_test).
try_inline(Mod, Config) ->
- Node = proplists:get_value(testing_node, Config),
Src = filename:join(proplists:get_value(data_dir, Config),
atom_to_list(Mod)),
Out = proplists:get_value(priv_dir,Config),
%% Normal compilation.
io:format("Compiling: ~s\n", [Src]),
- {ok,Mod} = compile:file(Src, [{outdir,Out},report,bin_opt_info,clint]),
+ {ok,Mod} = compile:file(Src, [{outdir,Out},report,
+ bin_opt_info,clint,ssalint]),
ct:timetrap({minutes,10}),
- NormalResult = rpc:call(Node, ?MODULE, load_and_call, [Out,Mod]),
+ NormalResult = load_and_call(Out, Mod),
%% Inlining.
io:format("Compiling with old inliner: ~s\n", [Src]),
{ok,Mod} = compile:file(Src, [{outdir,Out},report,bin_opt_info,
- {inline,1000},clint]),
+ {inline,1000},clint,ssalint]),
%% Run inlined code.
ct:timetrap({minutes,10}),
- OldInlinedResult = rpc:call(Node, ?MODULE, load_and_call, [Out,Mod]),
+ OldInlinedResult = load_and_call(Out, Mod),
%% Compare results.
compare(NormalResult, OldInlinedResult),
@@ -117,11 +113,11 @@ try_inline(Mod, Config) ->
%% Inlining.
io:format("Compiling with new inliner: ~s\n", [Src]),
{ok,Mod} = compile:file(Src, [{outdir,Out},report,
- bin_opt_info,inline,clint]),
+ bin_opt_info,inline,clint,ssalint]),
%% Run inlined code.
ct:timetrap({minutes,10}),
- InlinedResult = rpc:call(Node, ?MODULE, load_and_call, [Out,Mod]),
+ InlinedResult = load_and_call(Out, Mod),
%% Compare results.
compare(NormalResult, InlinedResult),
@@ -130,6 +126,11 @@ try_inline(Mod, Config) ->
%% Delete Beam file.
ok = file:delete(filename:join(Out, atom_to_list(Mod)++code:objfile_extension())),
+ %% Delete loaded module.
+ _ = code:purge(Mod),
+ _ = code:delete(Mod),
+ _ = code:purge(Mod),
+
ok.
compare(Same, Same) -> ok;
@@ -143,12 +144,6 @@ compare([H1|_], [H2|_]) ->
ct:fail(different);
compare([], []) -> ok.
-start_node(Name, Args) ->
- case test_server:start_node(Name, slave, [{args,Args}]) of
- {ok,Node} -> {ok, Node};
- Error -> ct:fail(Error)
- end.
-
load_and_call(Out, Module) ->
io:format("Loading...\n",[]),
code:purge(Module),
@@ -349,9 +344,8 @@ otp_7223_2({a}) ->
1.
coverage(Config) when is_list(Config) ->
- Mod = bsdecode,
+ Mod = attribute,
Src = filename:join(proplists:get_value(data_dir, Config), Mod),
- {ok,Mod,_} = compile:file(Src, [binary,report,{inline,0},clint]),
- {ok,Mod,_} = compile:file(Src, [binary,report,{inline,20},
- verbose,clint]),
+ {ok,Mod,_} = compile:file(Src, [binary,report,{inline,0},
+ clint,ssalint]),
ok.
diff --git a/lib/compiler/test/inline_SUITE_data/barnes2.erl b/lib/compiler/test/inline_SUITE_data/barnes2.erl
index a986331060..49e9bdfb6b 100644
--- a/lib/compiler/test/inline_SUITE_data/barnes2.erl
+++ b/lib/compiler/test/inline_SUITE_data/barnes2.erl
@@ -6,7 +6,7 @@
?MODULE() ->
Stars = create_scenario(1000, 1.0),
R = hd(loop(10,1000.0,Stars,0)),
- Str = lists:flatten(io:lib_format("~s", [R])),
+ Str = lists:flatten(io_lib:format("~p", [R])),
{R,Str =:= {1.00000,-1.92269e+4,-1.92269e+4,2.86459e-2,2.86459e-2}}.
create_scenario(N, M) ->
diff --git a/lib/compiler/test/map_SUITE.erl b/lib/compiler/test/map_SUITE.erl
index 3e0ab78390..440b632381 100644
--- a/lib/compiler/test/map_SUITE.erl
+++ b/lib/compiler/test/map_SUITE.erl
@@ -70,7 +70,10 @@
t_bad_update/1,
%% new in OTP 21
- t_reused_key_variable/1
+ t_reused_key_variable/1,
+
+ %% new in OTP 22
+ t_mixed_clause/1,cover_beam_trim/1
]).
suite() -> [].
@@ -124,7 +127,10 @@ all() ->
t_bad_update,
%% new in OTP 21
- t_reused_key_variable
+ t_reused_key_variable,
+
+ %% new in OTP 22
+ t_mixed_clause,cover_beam_trim
].
groups() -> [].
@@ -1373,22 +1379,22 @@ map_usage(Def, Used) ->
t_guard_sequence(Config) when is_list(Config) ->
- {1, "a"} = map_guard_sequence_1(#{seq=>1,val=>id("a")}),
- {2, "b"} = map_guard_sequence_1(#{seq=>2,val=>id("b")}),
- {3, "c"} = map_guard_sequence_1(#{seq=>3,val=>id("c")}),
- {4, "d"} = map_guard_sequence_1(#{seq=>4,val=>id("d")}),
- {5, "e"} = map_guard_sequence_1(#{seq=>5,val=>id("e")}),
-
- {1,M1} = map_guard_sequence_2(M1 = id(#{a=>3})),
- {2,M2} = map_guard_sequence_2(M2 = id(#{a=>4, b=>4})),
- {3,gg,M3} = map_guard_sequence_2(M3 = id(#{a=>gg, b=>4})),
- {4,sc,sc,M4} = map_guard_sequence_2(M4 = id(#{a=>sc, b=>3, c=>sc2})),
- {5,kk,kk,M5} = map_guard_sequence_2(M5 = id(#{a=>kk, b=>other, c=>sc2})),
-
- %% error case
- {'EXIT',{function_clause,_}} = (catch map_guard_sequence_1(#{seq=>6,val=>id("e")})),
- {'EXIT',{function_clause,_}} = (catch map_guard_sequence_2(#{b=>5})),
- ok.
+ {1, "a"} = map_guard_sequence_1(#{seq=>1,val=>id("a")}),
+ {2, "b"} = map_guard_sequence_1(#{seq=>2,val=>id("b")}),
+ {3, "c"} = map_guard_sequence_1(#{seq=>3,val=>id("c")}),
+ {4, "d"} = map_guard_sequence_1(#{seq=>4,val=>id("d")}),
+ {5, "e"} = map_guard_sequence_1(#{seq=>5,val=>id("e")}),
+
+ {1,M1} = map_guard_sequence_2(M1 = id(#{a=>3})),
+ {2,M2} = map_guard_sequence_2(M2 = id(#{a=>4, b=>4})),
+ {3,gg,M3} = map_guard_sequence_2(M3 = id(#{a=>gg, b=>4})),
+ {4,sc,sc,M4} = map_guard_sequence_2(M4 = id(#{a=>sc, b=>3, c=>sc2})),
+ {5,kk,kk,M5} = map_guard_sequence_2(M5 = id(#{a=>kk, b=>other, c=>sc2})),
+
+ %% error case
+ {'EXIT',{function_clause,_}} = (catch map_guard_sequence_1(#{seq=>6,val=>id("e")})),
+ {'EXIT',{function_clause,_}} = (catch map_guard_sequence_2(#{b=>5})),
+ ok.
t_guard_sequence_large(Config) when is_list(Config) ->
M0 = id(#{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00",03]}=>"10",
@@ -1443,21 +1449,21 @@ t_guard_sequence_large(Config) when is_list(Config) ->
18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19" } => "large map key 2" }),
- {1, "a"} = map_guard_sequence_1(M0#{seq=>1,val=>id("a")}),
- {2, "b"} = map_guard_sequence_1(M0#{seq=>2,val=>id("b")}),
- {3, "c"} = map_guard_sequence_1(M0#{seq=>3,val=>id("c")}),
- {4, "d"} = map_guard_sequence_1(M0#{seq=>4,val=>id("d")}),
- {5, "e"} = map_guard_sequence_1(M0#{seq=>5,val=>id("e")}),
+ {1, "a"} = map_guard_sequence_1(M0#{seq=>1,val=>id("a")}),
+ {2, "b"} = map_guard_sequence_1(M0#{seq=>2,val=>id("b")}),
+ {3, "c"} = map_guard_sequence_1(M0#{seq=>3,val=>id("c")}),
+ {4, "d"} = map_guard_sequence_1(M0#{seq=>4,val=>id("d")}),
+ {5, "e"} = map_guard_sequence_1(M0#{seq=>5,val=>id("e")}),
- {1,M1} = map_guard_sequence_2(M1 = id(M0#{a=>3})),
- {2,M2} = map_guard_sequence_2(M2 = id(M0#{a=>4, b=>4})),
- {3,gg,M3} = map_guard_sequence_2(M3 = id(M0#{a=>gg, b=>4})),
- {4,sc,sc,M4} = map_guard_sequence_2(M4 = id(M0#{a=>sc, b=>3, c=>sc2})),
- {5,kk,kk,M5} = map_guard_sequence_2(M5 = id(M0#{a=>kk, b=>other, c=>sc2})),
+ {1,M1} = map_guard_sequence_2(M1 = id(M0#{a=>3})),
+ {2,M2} = map_guard_sequence_2(M2 = id(M0#{a=>4, b=>4})),
+ {3,gg,M3} = map_guard_sequence_2(M3 = id(M0#{a=>gg, b=>4})),
+ {4,sc,sc,M4} = map_guard_sequence_2(M4 = id(M0#{a=>sc, b=>3, c=>sc2})),
+ {5,kk,kk,M5} = map_guard_sequence_2(M5 = id(M0#{a=>kk, b=>other, c=>sc2})),
- {'EXIT',{function_clause,_}} = (catch map_guard_sequence_1(M0#{seq=>6,val=>id("e")})),
- {'EXIT',{function_clause,_}} = (catch map_guard_sequence_2(M0#{b=>5})),
- ok.
+ {'EXIT',{function_clause,_}} = (catch map_guard_sequence_1(M0#{seq=>6,val=>id("e")})),
+ {'EXIT',{function_clause,_}} = (catch map_guard_sequence_2(M0#{b=>5})),
+ ok.
map_guard_sequence_1(#{seq:=1=Seq, val:=Val}) -> {Seq,Val};
map_guard_sequence_1(#{seq:=2=Seq, val:=Val}) -> {Seq,Val};
@@ -2079,7 +2085,7 @@ t_register_corruption(Config) when is_list(Config) ->
{3,wanted,<<"value">>} = register_corruption_foo(wanted,M),
ok.
-register_corruption_foo(A,#{a := V1, b := V2}) ->
+register_corruption_foo(_,#{a := V1, b := V2}) ->
register_corruption_dummy_call(1,V1,V2);
register_corruption_foo(A,#{b := V}) ->
register_corruption_dummy_call(2,A,V);
@@ -2161,6 +2167,31 @@ t_reused_key_variable(Config) when is_list(Config) ->
ok
end.
+t_mixed_clause(_Config) ->
+ put(fool_inliner, x),
+ K = get(fool_inliner),
+ {42,100} = case #{K=>42,y=>100} of
+ #{x:=X,y:=Y} ->
+ {X,Y}
+ end,
+ nomatch = case #{K=>42,y=>100} of
+ #{x:=X,y:=0} ->
+ {X,Y};
+ #{} ->
+ nomatch
+ end,
+ ok.
+
+cover_beam_trim(_Config) ->
+ val = do_cover_beam_trim(id, max, max, id, #{id=>val}),
+ ok.
+
+do_cover_beam_trim(Id, OldMax, Max, Id, M) ->
+ OldMax = id(Max),
+ #{Id:=Val} = id(M),
+ Val.
+
+
%% aux
rand_terms(0) -> [];
diff --git a/lib/compiler/test/match_SUITE.erl b/lib/compiler/test/match_SUITE.erl
index 72e5356a8d..94bfbb0efe 100644
--- a/lib/compiler/test/match_SUITE.erl
+++ b/lib/compiler/test/match_SUITE.erl
@@ -25,7 +25,7 @@
match_in_call/1,untuplify/1,shortcut_boolean/1,letify_guard/1,
selectify/1,deselectify/1,underscore/1,match_map/1,map_vars_used/1,
coverage/1,grab_bag/1,literal_binary/1,
- unary_op/1]).
+ unary_op/1,eq_types/1,match_after_return/1]).
-include_lib("common_test/include/ct.hrl").
@@ -40,7 +40,8 @@ groups() ->
match_in_call,untuplify,
shortcut_boolean,letify_guard,selectify,deselectify,
underscore,match_map,map_vars_used,coverage,
- grab_bag,literal_binary,unary_op]}].
+ grab_bag,literal_binary,unary_op,eq_types,
+ match_after_return]}].
init_per_suite(Config) ->
@@ -254,6 +255,8 @@ non_matching_aliases(_Config) ->
none = mixed_aliases([d]),
none = mixed_aliases({a,42}),
none = mixed_aliases(42),
+ none = mixed_aliases(<<6789:16>>),
+ none = mixed_aliases(#{key=>value}),
{'EXIT',{{badmatch,42},_}} = (catch nomatch_alias(42)),
{'EXIT',{{badmatch,job},_}} = (catch entirely()),
@@ -279,6 +282,16 @@ mixed_aliases(<<X:8>> = x) -> {a,X};
mixed_aliases([b] = <<X:8>>) -> {b,X};
mixed_aliases(<<X:8>> = {a,X}) -> {c,X};
mixed_aliases([X] = <<X:8>>) -> {d,X};
+mixed_aliases(<<X:16>> = X) -> {e,X};
+mixed_aliases(X = <<X:16>>) -> {f,X};
+mixed_aliases(<<X:16,_/binary>> = X) -> {g,X};
+mixed_aliases(X = <<X:16,_/binary>>) -> {h,X};
+mixed_aliases(X = #{key:=X}) -> {i,X};
+mixed_aliases(#{key:=X} = X) -> {j,X};
+mixed_aliases([X] = #{key:=X}) -> {k,X};
+mixed_aliases(#{key:=X} = [X]) -> {l,X};
+mixed_aliases({a,X} = #{key:=X}) -> {m,X};
+mixed_aliases(#{key:=X} = {a,X}) -> {n,X};
mixed_aliases(_) -> none.
nomatch_alias(I) ->
@@ -456,6 +469,7 @@ letify_guard(A, B) ->
selectify(Config) when is_list(Config) ->
integer = sel_different_types({r,42}),
atom = sel_different_types({r,forty_two}),
+ float = sel_different_types({r,100.0}),
none = sel_different_types({r,18}),
{'EXIT',_} = (catch sel_different_types([a,b,c])),
@@ -466,12 +480,15 @@ selectify(Config) when is_list(Config) ->
integer42 = sel_same_value2(42),
integer43 = sel_same_value2(43),
error = sel_same_value2(44),
+
ok.
sel_different_types({r,_}=T) when element(2, T) =:= forty_two ->
atom;
sel_different_types({r,_}=T) when element(2, T) =:= 42 ->
integer;
+sel_different_types({r,_}=T) when element(2, T) =:= 100.0 ->
+ float;
sel_different_types({r,_}) ->
none.
@@ -489,9 +506,8 @@ sel_same_value2(V) when V =:= 42; V =:= 43 ->
sel_same_value2(_) ->
error.
-%% Test deconstruction of select_val instructions in beam_peep into
-%% regular tests with just one possible value left. Hitting proper cases
-%% in beam_peep relies on unification of labels by beam_jump.
+%% Test deconstruction of select_val instructions to regular tests
+%% with zero or one values left.
deselectify(Config) when is_list(Config) ->
one_or_other = desel_tuple_arity({1}),
@@ -512,7 +528,31 @@ deselectify(Config) when is_list(Config) ->
one_or_other = dsel_atom_typecheck(one),
two = dsel_atom_typecheck(two),
- one_or_other = dsel_atom_typecheck(three).
+ one_or_other = dsel_atom_typecheck(three),
+
+ %% Cover deconstruction of select_val instructions in
+ %% beam_peep.
+
+ stop = dsel_peek_0(stop),
+ ignore = dsel_peek_0(ignore),
+ Config = dsel_peek_0(Config),
+
+ stop = dsel_peek_1(stop, any),
+ Config = dsel_peek_1(ignore, Config),
+ other = dsel_peek_1(other, ignored),
+
+ 0 = dsel_peek_2(0, any),
+ Config = dsel_peek_2(1, Config),
+ 2 = dsel_peek_2(2, ignored),
+
+ true = dsel_peek_3(true),
+ false = dsel_peek_3(false),
+ {error,Config} = dsel_peek_3(Config),
+
+ ok.
+
+%% The following will be optimized by the sharing optimizations
+%% in beam_ssa_opt.
desel_tuple_arity(Tuple) when is_tuple(Tuple) ->
case Tuple of
@@ -549,6 +589,39 @@ dsel_atom_typecheck(Val) when is_atom(Val) ->
_ -> one_or_other
end.
+%% The following functions are carefully crafted so that the sharing
+%% optimizations in beam_ssa_opt can't be applied. After applying the
+%% beam_jump:eliminate_moves/1 optimization and beam_clean:clean_labels/1
+%% has unified labels, beam_peep is able to optimize these functions.
+
+dsel_peek_0(A0) ->
+ case id(A0) of
+ stop -> stop;
+ ignore -> ignore;
+ A -> A
+ end.
+
+dsel_peek_1(A0, B) ->
+ case id(A0) of
+ stop -> stop;
+ ignore -> B;
+ A -> A
+ end.
+
+dsel_peek_2(A0, B) ->
+ case id(A0) of
+ 0 -> 0;
+ 1 -> B;
+ A -> A
+ end.
+
+dsel_peek_3(A0) ->
+ case id(A0) of
+ true -> true;
+ false -> false;
+ Other -> {error,Other}
+ end.
+
underscore(Config) when is_list(Config) ->
case Config of
[] ->
@@ -591,13 +664,26 @@ do_map_vars_used(X, Y, Map) ->
Val
end.
+-record(coverage_id, {bool=false,id}).
coverage(Config) when is_list(Config) ->
%% Cover beam_dead.
ok = coverage_1(x, a),
ok = coverage_1(x, b),
%% Cover sys_pre_expand.
- ok = coverage_3("abc").
+ ok = coverage_3("abc"),
+
+ %% Cover beam_ssa_dead.
+ {expr,key} = coverage_4([literal,get], [[expr,key]]),
+ {expr,key} = coverage_4([expr,key], []),
+
+ a = coverage_5([8,8,8], #coverage_id{bool=true}),
+ b = coverage_5([], #coverage_id{bool=true}),
+
+ %% Cover beam_ssa_opt.
+ ok = coverage_6(),
+
+ ok.
coverage_1(B, Tag) ->
case Tag of
@@ -610,6 +696,37 @@ coverage_2(2, b, x) -> ok.
coverage_3([$a]++[]++"bc") -> ok.
+%% Cover beam_ssa_dead:eval_type_test_1(is_nonempty_list, Arg).
+coverage_4([literal,get], [Expr]) ->
+ coverage_4(Expr, []);
+coverage_4([Expr,Key], []) ->
+ {Expr,Key}.
+
+%% Cover beam_ssa_dead:eval_type_test_1(is_tagged_tuple, Arg).
+coverage_5(Config, TermId)
+ when TermId =:= #coverage_id{bool=true},
+ Config =:= [8,8,8] ->
+ a;
+coverage_5(_Config, #coverage_id{bool=true}) ->
+ b.
+
+coverage_6() ->
+ X = 17,
+ case
+ case id(1) > 0 of
+ true ->
+ 17;
+ false ->
+ 42
+ end
+ of
+ X ->
+ ok;
+ V ->
+ %% Cover beam_ssa_opt:make_literal/2.
+ error([error,X,V])
+ end.
+
grab_bag(_Config) ->
[_|T] = id([a,b,c]),
[b,c] = id(T),
@@ -754,5 +871,35 @@ unary_op_1(Vop@1) ->
end
end.
+eq_types(_Config) ->
+ Ref = make_ref(),
+ Ref = eq_types(Ref, any),
+ ok.
+
+eq_types(A, B) ->
+ %% {put_tuple2,{y,0},{list,[{x,0},{x,1}]}}.
+ Term0 = {A, B},
+ Term = id(Term0),
+
+ %% {test,is_eq_exact,{f,3},[{y,0},{x,0}]}.
+ %% Here beam_validator must infer that {x,0} has the
+ %% same type as {y,0}.
+ Term = Term0,
+
+ %% {get_tuple_element,{x,0},0,{x,0}}.
+ {Ref22,_} = Term,
+
+ Ref22.
+
+match_after_return(Config) when is_list(Config) ->
+ %% The return type of the following call will never match the 'wont_happen'
+ %% clauses below, and the beam_ssa_type was clever enough to see that but
+ %% didn't remove the blocks, so it crashed when trying to extract A.
+ ok = case mar_test_tuple(erlang:unique_integer()) of
+ {gurka, never_matches, A} -> {wont_happen, A};
+ _ -> ok
+ end.
+
+mar_test_tuple(I) -> {gurka, I}.
id(I) -> I.
diff --git a/lib/compiler/test/misc_SUITE.erl b/lib/compiler/test/misc_SUITE.erl
index a1d931b994..a0b415ceaa 100644
--- a/lib/compiler/test/misc_SUITE.erl
+++ b/lib/compiler/test/misc_SUITE.erl
@@ -161,17 +161,16 @@ md5_1(Beam) ->
%% Cover some code that handles internal errors.
silly_coverage(Config) when is_list(Config) ->
- %% sys_core_fold, sys_core_alias, sys_core_bsm, sys_core_setel, v3_kernel
+ %% sys_core_fold, sys_core_alias, sys_core_bsm, v3_kernel
BadCoreErlang = {c_module,[],
name,[],[],
[{{c_var,[],{foo,2}},seriously_bad_body}]},
expect_error(fun() -> sys_core_fold:module(BadCoreErlang, []) end),
expect_error(fun() -> sys_core_alias:module(BadCoreErlang, []) end),
expect_error(fun() -> sys_core_bsm:module(BadCoreErlang, []) end),
- expect_error(fun() -> sys_core_dsetel:module(BadCoreErlang, []) end),
expect_error(fun() -> v3_kernel:module(BadCoreErlang, []) end),
- %% v3_codegen
+ %% beam_kernel_to_ssa
BadKernel = {k_mdef,[],?MODULE,
[{foo,0}],
[],
@@ -179,7 +178,38 @@ silly_coverage(Config) when is_list(Config) ->
{k,[],[],[]},
f,0,[],
seriously_bad_body}]},
- expect_error(fun() -> v3_codegen:module(BadKernel, []) end),
+ expect_error(fun() -> beam_kernel_to_ssa:module(BadKernel, []) end),
+
+ %% beam_ssa_lint
+ %% beam_ssa_recv
+ %% beam_ssa_share
+ %% beam_ssa_pre_codegen
+ %% beam_ssa_codegen
+ BadSSA = {b_module,#{},a,b,c,
+ [{b_function,#{func_info=>{mod,foo,0}},args,bad_blocks,0}]},
+ expect_error(fun() -> beam_ssa_lint:module(BadSSA, []) end),
+ expect_error(fun() -> beam_ssa_recv:module(BadSSA, []) end),
+ expect_error(fun() -> beam_ssa_share:module(BadSSA, []) end),
+ expect_error(fun() -> beam_ssa_pre_codegen:module(BadSSA, []) end),
+ expect_error(fun() -> beam_ssa_codegen:module(BadSSA, []) end),
+
+ %% beam_ssa_opt
+ BadSSABlocks = #{0 => {b_blk,#{},[bad_code],{b_ret,#{},arg}}},
+ BadSSAOpt = {b_module,#{},a,[],c,
+ [{b_function,#{func_info=>{mod,foo,0}},[],
+ BadSSABlocks,0}]},
+ expect_error(fun() -> beam_ssa_opt:module(BadSSAOpt, []) end),
+
+ %% beam_ssa_lint, beam_ssa_pp
+ {error,[{_,Errors}]} = beam_ssa_lint:module(bad_ssa_lint_input(), []),
+ _ = [io:put_chars(Mod:format_error(Reason)) ||
+ {Mod,Reason} <- Errors],
+
+ %% Cover printing of annotations in beam_ssa_pp
+ PPAnno = #{func_info=>{mod,foo,0},other_anno=>value,map_anno=>#{k=>v}},
+ PPBlocks = #{0=>{b_blk,#{},[],{b_ret,#{},{b_literal,42}}}},
+ PP = {b_function,PPAnno,[],PPBlocks,0},
+ io:put_chars(beam_ssa_pp:format_function(PP)),
%% beam_a
BeamAInput = {?MODULE,[{foo,0}],[],
@@ -189,14 +219,6 @@ silly_coverage(Config) when is_list(Config) ->
{label,2}|non_proper_list]}],99},
expect_error(fun() -> beam_a:module(BeamAInput, []) end),
- %% beam_reorder
- BlockInput = {?MODULE,[{foo,0}],[],
- [{function,foo,0,2,
- [{label,1},
- {func_info,{atom,?MODULE},{atom,foo},0},
- {label,2}|non_proper_list]}],99},
- expect_error(fun() -> beam_reorder:module(BlockInput, []) end),
-
%% beam_block
BlockInput = {?MODULE,[{foo,0}],[],
[{function,foo,0,2,
@@ -205,19 +227,6 @@ silly_coverage(Config) when is_list(Config) ->
{label,2}|non_proper_list]}],99},
expect_error(fun() -> beam_block:module(BlockInput, []) end),
- %% beam_bs
- BsInput = BlockInput,
- expect_error(fun() -> beam_bs:module(BsInput, []) end),
-
- %% beam_type
- TypeInput = {?MODULE,[{foo,0}],[],
- [{function,foo,0,2,
- [{label,1},
- {line,loc},
- {func_info,{atom,?MODULE},{atom,foo},0},
- {label,2}|non_proper_list]}],99},
- expect_error(fun() -> beam_type:module(TypeInput, []) end),
-
%% beam_except
ExceptInput = {?MODULE,[{foo,0}],[],
[{function,foo,0,2,
@@ -227,15 +236,9 @@ silly_coverage(Config) when is_list(Config) ->
{label,2}|non_proper_list]}],99},
expect_error(fun() -> beam_except:module(ExceptInput, []) end),
- %% beam_dead. This is tricky. Our function must look OK to
- %% beam_utils:clean_labels/1, but must crash beam_dead.
- DeadInput = {?MODULE,[{foo,0}],[],
- [{function,foo,0,2,
- [{label,1},
- {func_info,{atom,?MODULE},{atom,foo},0},
- {label,2},
- {test,is_eq_exact,{f,1},[bad,operands]}]}],99},
- expect_error(fun() -> beam_dead:module(DeadInput, []) end),
+ %% beam_jump
+ JumpInput = BlockInput,
+ expect_error(fun() -> beam_jump:module(JumpInput, []) end),
%% beam_clean
CleanInput = {?MODULE,[{foo,0}],[],
@@ -246,6 +249,10 @@ silly_coverage(Config) when is_list(Config) ->
{jump,{f,42}}]}],99},
expect_error(fun() -> beam_clean:module(CleanInput, []) end),
+ %% beam_jump
+ TrimInput = BlockInput,
+ expect_error(fun() -> beam_trim:module(TrimInput, []) end),
+
%% beam_peep. This is tricky. Use a select instruction with
%% an odd number of elements in the list to crash
%% prune_redundant_values/2 but not beam_clean:clean_labels/1.
@@ -253,48 +260,10 @@ silly_coverage(Config) when is_list(Config) ->
[{function,foo,0,2,
[{label,1},
{func_info,{atom,?MODULE},{atom,foo},0},
- {label,2},{select,op,r,{f,2},[{f,2}]}]}],
+ {label,2},{select,select_val,r,{f,2},[{f,2}]}]}],
2},
expect_error(fun() -> beam_peep:module(PeepInput, []) end),
- %% beam_bsm. This is tricky. Our function must be sane enough to not crash
- %% btb_index/1, but must crash the main optimization pass.
- BsmInput = {?MODULE,[{foo,0}],[],
- [{function,foo,0,2,
- [{label,1},
- {func_info,{atom,?MODULE},{atom,foo},0},
- {label,2},
- {test,bs_get_binary2,{f,99},0,[{x,0},{atom,all},1,[]],{x,0}},
- {block,[a|b]}]}],0},
- expect_error(fun() -> beam_bsm:module(BsmInput, []) end),
-
- %% beam_receive.
- ReceiveInput = {?MODULE,[{foo,0}],[],
- [{function,foo,0,2,
- [{label,1},
- {func_info,{atom,?MODULE},{atom,foo},0},
- {label,2},
- {call_ext,0,{extfunc,erlang,make_ref,0}},
- {block,[a|b]}]}],0},
- expect_error(fun() -> beam_receive:module(ReceiveInput, []) end),
-
- %% beam_record.
- RecordInput = {?MODULE,[{foo,0}],[],
- [{function,foo,1,2,
- [{label,1},
- {func_info,{atom,?MODULE},{atom,foo},1},
- {label,2},
- {test,is_tuple,{f,1},[{x,0}]},
- {test,test_arity,{f,1},[{x,0},3]},
- {block,[{set,[{x,1}],[{x,0}],{get_tuple_element,0}}]},
- {test,is_eq_exact,{f,1},[{x,1},{atom,bar}]},
- {block,[{set,[{x,2}],[{x,0}],{get_tuple_element,1}}|a]},
- {test,is_eq_exact,{f,1},[{x,2},{integer,1}]},
- {block,[{set,[{x,0}],[{atom,ok}],move}]},
- return]}],0},
-
- expect_error(fun() -> beam_record:module(RecordInput, []) end),
-
BeamZInput = {?MODULE,[{foo,0}],[],
[{function,foo,0,2,
[{label,1},
@@ -312,6 +281,31 @@ silly_coverage(Config) when is_list(Config) ->
ok.
+bad_ssa_lint_input() ->
+ {b_module,#{},t,
+ [{foobar,1},{module_info,0},{module_info,1}],
+ [],
+ [{b_function,
+ #{func_info => {t,foobar,1},location => {"t.erl",4}},
+ [{b_var,0}],
+ #{0 => {b_blk,#{},[],{b_ret,#{},{b_var,'@undefined_var'}}}},
+ 3},
+ {b_function,
+ #{func_info => {t,module_info,0}},
+ [],
+ #{0 =>
+ {b_blk,#{},
+ [{b_set,#{},
+ {b_var,{'@ssa_ret',3}},
+ call,
+ [{b_remote,
+ {b_literal,erlang},
+ {b_literal,get_module_info},
+ 1},
+ {b_var,'@unknown_variable'}]}],
+ {b_ret,#{},{b_var,{'@ssa_ret',3}}}}},
+ 4}]}.
+
expect_error(Fun) ->
try Fun() of
Any ->
diff --git a/lib/compiler/test/receive_SUITE.erl b/lib/compiler/test/receive_SUITE.erl
index 4219768d6f..0038eb1a4b 100644
--- a/lib/compiler/test/receive_SUITE.erl
+++ b/lib/compiler/test/receive_SUITE.erl
@@ -25,7 +25,8 @@
init_per_group/2,end_per_group/2,
init_per_testcase/2,end_per_testcase/2,
export/1,recv/1,coverage/1,otp_7980/1,ref_opt/1,
- wait/1,recv_in_try/1,double_recv/1]).
+ wait/1,recv_in_try/1,double_recv/1,receive_var_zero/1,
+ match_built_terms/1]).
-include_lib("common_test/include/ct.hrl").
@@ -45,7 +46,8 @@ all() ->
groups() ->
[{p,test_lib:parallel(),
[recv,coverage,otp_7980,ref_opt,export,wait,
- recv_in_try,double_recv]}].
+ recv_in_try,double_recv,receive_var_zero,
+ match_built_terms]}].
init_per_suite(Config) ->
@@ -378,4 +380,51 @@ do_double_recv(_, Msg) ->
error
end.
+%% Test 'after Z', when Z =:= 0 been propagated as an immediate by the type
+%% optimization pass.
+receive_var_zero(Config) when is_list(Config) ->
+ self() ! x,
+ self() ! y,
+ Z = zero(),
+ timeout = receive
+ z -> ok
+ after Z -> timeout
+ end,
+ timeout = receive
+ after Z -> timeout
+ end,
+ self() ! w,
+ receive
+ x -> ok;
+ Other ->
+ ct:fail({bad_message,Other})
+ end.
+
+zero() -> 0.
+
+%% ERL-862; the validator would explode when a term was constructed in a
+%% receive guard.
+
+-define(MATCH_BUILT_TERM(Ref, Expr),
+ (fun() ->
+ Ref = make_ref(),
+ A = id($a),
+ B = id($b),
+ Built = id(Expr),
+ self() ! {Ref, A, B},
+ receive
+ {Ref, A, B} when Expr =:= Built ->
+ ok
+ after 5000 ->
+ ct:fail("Failed to match message with term built in "
+ "receive guard.")
+ end
+ end)()).
+
+match_built_terms(Config) when is_list(Config) ->
+ ?MATCH_BUILT_TERM(Ref, [A, B]),
+ ?MATCH_BUILT_TERM(Ref, {A, B}),
+ ?MATCH_BUILT_TERM(Ref, <<A, B>>),
+ ?MATCH_BUILT_TERM(Ref, #{ 1 => A, 2 => B}).
+
id(I) -> I.
diff --git a/lib/compiler/test/receive_SUITE_data/ref_opt/no_5.erl b/lib/compiler/test/receive_SUITE_data/ref_opt/no_5.erl
new file mode 100644
index 0000000000..4fbde3a83d
--- /dev/null
+++ b/lib/compiler/test/receive_SUITE_data/ref_opt/no_5.erl
@@ -0,0 +1,38 @@
+-module(no_5).
+-compile([export_all,nowarn_export_all]).
+
+?MODULE() ->
+ ok.
+
+%% Nested receives were not handled properly.
+
+confusing_recv_mark(Pid) ->
+ Ref = make_ref(),
+ %% There would be a recv_mark here.
+ MRef = erlang:monitor(process, Pid),
+ receive
+ Ref ->
+ %% And a recv_set here.
+ receive
+ MRef -> gurka
+ end;
+ MRef ->
+ gaffel
+ end.
+
+%% The optimization could potentially be improved to
+%% handle matching of multiple refs, like this:
+
+proper_recv_mark(Pid) ->
+ %% Place the recv_mark before the creation of both refs.
+ Ref = make_ref(),
+ MRef = erlang:monitor(process, Pid),
+ %% Place the recv_set here.
+ receive
+ Ref ->
+ receive
+ MRef -> gurka
+ end;
+ MRef ->
+ gaffel
+ end.
diff --git a/lib/compiler/test/receive_SUITE_data/ref_opt/yes_14.S b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_14.S
deleted file mode 100644
index fd14228135..0000000000
--- a/lib/compiler/test/receive_SUITE_data/ref_opt/yes_14.S
+++ /dev/null
@@ -1,71 +0,0 @@
-{module, yes_14}. %% version = 0
-
-{exports, [{f,2},{module_info,0},{module_info,1},{yes_14,0}]}.
-
-{attributes, []}.
-
-{labels, 12}.
-
-
-{function, yes_14, 0, 2}.
- {label,1}.
- {func_info,{atom,yes_14},{atom,yes_14},0}.
- {label,2}.
- {move,{atom,ok},{x,0}}.
- return.
-
-
-{function, f, 2, 4}.
- {label,3}.
- {func_info,{atom,yes_14},{atom,f},2}.
- {label,4}.
- {allocate_heap,2,3,2}.
- {move,{x,0},{y,1}}.
- {put_tuple,2,{y,0}}.
- {put,{atom,data}}.
- {put,{x,1}}.
- {call_ext,0,{extfunc,erlang,make_ref,0}}. % Ref in [x0]
- {test_heap,4,1}.
- {put_tuple,3,{x,1}}.
- {put,{atom,request}}.
- {put,{x,0}}.
- {put,{y,0}}.
- {move,{x,0},{y,0}}. % Ref in [x0,y0]
- {move,{y,1},{x,0}}. % Ref in [y0]
- {kill,{y,1}}.
- send.
- {move,{y,0},{x,0}}. % Ref in [x0,y0]
- {move,{x,0},{y,1}}. % Ref in [x0,y0,y1]
- {label,5}.
- {loop_rec,{f,7},{x,0}}. % Ref in [y0,y1]
- {test,is_tuple,{f,6},[{x,0}]}.
- {test,test_arity,{f,6},[{x,0},2]}.
- {get_tuple_element,{x,0},0,{x,1}}.
- {get_tuple_element,{x,0},1,{x,2}}.
- {test,is_eq_exact,{f,6},[{x,1},{atom,reply}]}.
- {test,is_eq_exact,{f,6},[{x,2},{y,1}]}.
- remove_message.
- {move,{atom,ok},{x,0}}.
- {deallocate,2}.
- return.
- {label,6}.
- {loop_rec_end,{f,5}}.
- {label,7}.
- {wait,{f,5}}.
-
-
-{function, module_info, 0, 9}.
- {label,8}.
- {func_info,{atom,yes_14},{atom,module_info},0}.
- {label,9}.
- {move,{atom,yes_14},{x,0}}.
- {call_ext_only,1,{extfunc,erlang,get_module_info,1}}.
-
-
-{function, module_info, 1, 11}.
- {label,10}.
- {func_info,{atom,yes_14},{atom,module_info},1}.
- {label,11}.
- {move,{x,0},{x,1}}.
- {move,{atom,yes_14},{x,0}}.
- {call_ext_only,2,{extfunc,erlang,get_module_info,2}}.
diff --git a/lib/compiler/test/receive_SUITE_data/ref_opt/yes_14.erl b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_14.erl
new file mode 100644
index 0000000000..aa47c02af9
--- /dev/null
+++ b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_14.erl
@@ -0,0 +1,27 @@
+-module(yes_14).
+-compile(export_all).
+
+?MODULE() ->
+ ok.
+
+do_call(Process, Request) ->
+ Mref = erlang:monitor(process, Process),
+ Process ! Request,
+ Local = case node(Process) of
+ Node when Node =:= node() -> true;
+ _Node -> false
+ end,
+ id(Local),
+ receive
+ {X,Y,Z} when Mref =/= X, Z =:= 42, Mref =:= Y ->
+ error;
+ {X,Y,_} when Mref =/= X, Mref =:= Y ->
+ error;
+ {Mref, Reply} ->
+ erlang:demonitor(Mref, [flush]),
+ {ok, Reply};
+ {'DOWN', Mref, _, _, _} ->
+ error
+ end.
+
+id(I) -> I.
diff --git a/lib/compiler/test/regressions_SUITE.erl b/lib/compiler/test/regressions_SUITE.erl
index 9b0b9b0c38..39febf060f 100644
--- a/lib/compiler/test/regressions_SUITE.erl
+++ b/lib/compiler/test/regressions_SUITE.erl
@@ -23,7 +23,7 @@
-export([all/0,groups/0,init_per_testcase/2,end_per_testcase/2,
init_per_group/2,end_per_group/2,
- init_per_testcase/2,end_per_testcase/2,
+ init_per_suite/1,end_per_suite/1,
suite/0]).
-export([maps/1]).
diff --git a/lib/compiler/test/test_lib.erl b/lib/compiler/test/test_lib.erl
index 8954a9f5fb..39c26c6142 100644
--- a/lib/compiler/test/test_lib.erl
+++ b/lib/compiler/test/test_lib.erl
@@ -50,12 +50,8 @@ smoke_disasm(File) when is_list(File) ->
Res = beam_disasm:file(File),
{beam_file,_Mod} = {element(1, Res),element(2, Res)}.
-%% If we are running cover, we don't want to run test cases that
-%% invokes the compiler in parallel, as doing so would probably
-%% be slower than running them sequentially.
-
parallel() ->
- case test_server:is_cover() orelse erlang:system_info(schedulers) =:= 1 of
+ case erlang:system_info(schedulers) =:= 1 of
true -> [];
false -> [parallel]
end.
@@ -70,18 +66,24 @@ uniq() ->
opt_opts(Mod) ->
Comp = Mod:module_info(compile),
{options,Opts} = lists:keyfind(options, 1, Comp),
- lists:filter(fun(no_copt) -> true;
- (no_postopt) -> true;
- (no_float_opt) -> true;
- (no_new_funs) -> true;
- (no_new_binaries) -> true;
- (no_new_apply) -> true;
- (no_gc_bifs) -> true;
- (no_stack_trimming) -> true;
- (debug_info) -> true;
- (inline) -> true;
- (_) -> false
- end, Opts).
+ lists:filter(fun
+ (debug_info) -> true;
+ (inline) -> true;
+ (no_bsm3) -> true;
+ (no_bsm_opt) -> true;
+ (no_copt) -> true;
+ (no_fun_opt) -> true;
+ (no_module_opt) -> true;
+ (no_postopt) -> true;
+ (no_put_tuple2) -> true;
+ (no_recv_opt) -> true;
+ (no_share_opt) -> true;
+ (no_ssa_float) -> true;
+ (no_ssa_opt) -> true;
+ (no_stack_trimming) -> true;
+ (no_type_opt) -> true;
+ (_) -> false
+ end, Opts).
%% Some test suites gets cloned (e.g. to "record_SUITE" to
%% "record_no_opt_SUITE"), but the data directory is not cloned.
@@ -91,17 +93,23 @@ get_data_dir(Config) ->
Data0 = proplists:get_value(data_dir, Config),
Opts = [{return,list}],
Data1 = re:replace(Data0, "_no_opt_SUITE", "_SUITE", Opts),
- Data = re:replace(Data1, "_post_opt_SUITE", "_SUITE", Opts),
- re:replace(Data, "_inline_SUITE", "_SUITE", Opts).
+ Data2 = re:replace(Data1, "_post_opt_SUITE", "_SUITE", Opts),
+ Data3 = re:replace(Data2, "_inline_SUITE", "_SUITE", Opts),
+ Data4 = re:replace(Data3, "_r21_SUITE", "_SUITE", Opts),
+ Data = re:replace(Data4, "_no_module_opt_SUITE", "_SUITE", Opts),
+ re:replace(Data, "_no_ssa_opt_SUITE", "_SUITE", Opts).
is_cloned_mod(Mod) ->
is_cloned_mod_1(atom_to_list(Mod)).
%% Test whether Mod is a cloned module.
-is_cloned_mod_1("no_opt_SUITE") -> true;
-is_cloned_mod_1("post_opt_SUITE") -> true;
-is_cloned_mod_1("inline_SUITE") -> true;
+is_cloned_mod_1("_no_opt_SUITE") -> true;
+is_cloned_mod_1("_no_ssa_opt_SUITE") -> true;
+is_cloned_mod_1("_post_opt_SUITE") -> true;
+is_cloned_mod_1("_inline_SUITE") -> true;
+is_cloned_mod_1("_21_SUITE") -> true;
+is_cloned_mod_1("_no_module_opt_SUITE") -> true;
is_cloned_mod_1([_|T]) -> is_cloned_mod_1(T);
is_cloned_mod_1([]) -> false.
@@ -110,18 +118,7 @@ is_cloned_mod_1([]) -> false.
p_run(Test, List) ->
S = erlang:system_info(schedulers),
- N = case test_server:is_cover() of
- false ->
- S + 1;
- true ->
- %% Cover is running. Using too many processes
- %% could slow us down. Measurements on my computer
- %% showed that using 4 parallel processes was
- %% slightly faster than using 3. Using more than
- %% 4 would not buy us much and could actually be
- %% slower.
- min(S, 4)
- end,
+ N = S + 1,
io:format("p_run: ~p parallel processes\n", [N]),
p_run_loop(Test, List, N, [], 0, 0).
diff --git a/lib/compiler/test/warnings_SUITE.erl b/lib/compiler/test/warnings_SUITE.erl
index 33d55996ad..70b7100451 100644
--- a/lib/compiler/test/warnings_SUITE.erl
+++ b/lib/compiler/test/warnings_SUITE.erl
@@ -42,7 +42,7 @@
comprehensions/1,maps/1,maps_bin_opt_info/1,
redundant_boolean_clauses/1,
latin1_fallback/1,underscore/1,no_warnings/1,
- bit_syntax/1,inlining/1]).
+ bit_syntax/1,inlining/1,tuple_calls/1]).
init_per_testcase(_Case, Config) ->
Config.
@@ -64,7 +64,8 @@ groups() ->
bin_opt_info,bin_construction,comprehensions,maps,
maps_bin_opt_info,
redundant_boolean_clauses,latin1_fallback,
- underscore,no_warnings,bit_syntax,inlining]}].
+ underscore,no_warnings,bit_syntax,inlining,
+ tuple_calls]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -239,19 +240,7 @@ guard(Config) when is_list(Config) ->
{4,sys_core_fold,nomatch_guard},
{6,sys_core_fold,no_clause_match},
{6,sys_core_fold,nomatch_guard},
- {6,sys_core_fold,{eval_failure,badarg}},
- {8,sys_core_fold,no_clause_match},
- {8,sys_core_fold,nomatch_guard},
- {8,sys_core_fold,{eval_failure,badarg}},
- {9,sys_core_fold,no_clause_match},
- {9,sys_core_fold,nomatch_guard},
- {9,sys_core_fold,{eval_failure,badarg}},
- {10,sys_core_fold,no_clause_match},
- {10,sys_core_fold,nomatch_guard},
- {10,sys_core_fold,{eval_failure,badarg}},
- {11,sys_core_fold,no_clause_match},
- {11,sys_core_fold,nomatch_guard},
- {11,sys_core_fold,{eval_failure,badarg}}
+ {6,sys_core_fold,{eval_failure,badarg}}
]}}],
[] = run(Config, Ts),
@@ -522,25 +511,43 @@ bin_opt_info(Config) when is_list(Config) ->
<<>> -> ok
end.
+ %% We use a tail in a BIF instruction, remote call, function
+ %% return, and an optimizable tail call for better coverage.
+ t2(<<A,B,T/bytes>>) ->
+ if
+ A > B -> t2(T);
+ A =< B -> T
+ end;
+ t2(<<_,T/bytes>>) when byte_size(T) < 4 ->
+ foo;
t2(<<_,T/bytes>>) ->
- split_binary(T, 4).
+ split_binary(T, 4).
">>,
- Ts1 = [{bsm1,
- Code,
- [bin_opt_info],
- {warnings,
- [{4,sys_core_bsm,orig_bin_var_used_in_guard},
- {5,beam_bsm,{no_bin_opt,{{t1,1},no_suitable_bs_start_match}}},
- {9,beam_bsm,{no_bin_opt,
- {binary_used_in,{extfunc,erlang,split_binary,2}}}} ]}}],
- [] = run(Config, Ts1),
+
+ Ws = (catch run_test(Config, Code, [bin_opt_info])),
+
+ %% This is an inexact match since the pass reports exact instructions as
+ %% part of the warnings, which may include annotations that vary from run
+ %% to run.
+ {warnings,
+ [{5,beam_ssa_bsm,{unsuitable_call,
+ {{b_local,{b_literal,t1},1},
+ {used_before_match,
+ {b_set,_,_,{bif,byte_size},[_]}}}}},
+ {5,beam_ssa_bsm,{binary_created,_,_}},
+ {11,beam_ssa_bsm,{binary_created,_,_}}, %% A =< B -> T
+ {13,beam_ssa_bsm,context_reused}, %% A > B -> t2(T);
+ {16,beam_ssa_bsm,{binary_created,_,_}}, %% when byte_size(T) < 4 ->
+ {19,beam_ssa_bsm,{remote_call,
+ {b_remote,
+ {b_literal,erlang},
+ {b_literal,split_binary},2}}},
+ {19,beam_ssa_bsm,{binary_created,_,_}} %% split_binary(T, 4)
+ ]} = Ws,
%% For coverage: don't give the bin_opt_info option.
- Ts2 = [{bsm2,
- Code,
- [],
- []}],
- [] = run(Config, Ts2),
+ [] = (catch run_test(Config, Code, [])),
+
ok.
bin_construction(Config) when is_list(Config) ->
@@ -746,7 +753,7 @@ maps_bin_opt_info(Config) when is_list(Config) ->
M.
">>,
[bin_opt_info],
- {warnings,[{2,beam_bsm,bin_opt}]}}],
+ {warnings,[{3,beam_ssa_bsm,context_reused}]}}],
[] = run(Config, Ts),
ok.
@@ -952,6 +959,20 @@ inlining(Config) ->
run(Config, Ts),
ok.
+tuple_calls(Config) ->
+ %% Make sure that no spurious warnings are generated.
+ Ts = [{inlining_1,
+ <<"-compile(tuple_calls).
+ dispatch(X) ->
+ (list_to_atom(\"prefix_\" ++
+ atom_to_list(suffix))):doit(X).
+ ">>,
+ [],
+ []}
+ ],
+ run(Config, Ts),
+ ok.
+
%%%
%%% End of test cases.
%%%
@@ -969,7 +990,6 @@ run(Config, Tests) ->
end,
lists:foldl(F, [], Tests).
-
%% Compiles a test module and returns the list of errors and warnings.
run_test(Conf, Test0, Warnings) ->
diff --git a/lib/configure.in.src b/lib/configure.in.src
deleted file mode 100644
index d507a5c0dd..0000000000
--- a/lib/configure.in.src
+++ /dev/null
@@ -1,62 +0,0 @@
-dnl
-dnl %CopyrightBegin%
-dnl
-dnl Copyright Ericsson AB 1999-2016. 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.
-dnl You may obtain a copy of the License at
-dnl
-dnl http://www.apache.org/licenses/LICENSE-2.0
-dnl
-dnl Unless required by applicable law or agreed to in writing, software
-dnl distributed under the License is distributed on an "AS IS" BASIS,
-dnl WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-dnl See the License for the specific language governing permissions and
-dnl limitations under the License.
-dnl
-dnl %CopyrightEnd%
-dnl
-
-dnl Turn off caching
-define([AC_CACHE_LOAD], )dnl
-define([AC_CACHE_SAVE], )dnl
-
-dnl Process this file with autoconf to produce a configure script.
-AC_INIT
-
-dnl
-dnl This is just to run configure in all applications that need it.
-dnl
-
-if test -z "$ERL_TOP" || test ! -d $ERL_TOP ; then
- AC_MSG_ERROR(You need to set the environment variable ERL_TOP!)
-fi
-erl_top=${ERL_TOP}
-AC_CONFIG_AUX_DIRS($erl_top/erts/autoconf)
-
-AC_ARG_ENABLE(bootstrap-only,
-[ --enable-bootstrap-only enable bootstrap only configuration],
-[ if test "X$enableval" = "Xyes"; then
- bootstrap_only=yes
- else
- bootstrap_only=no
- fi
-],
-bootstrap_only=no)
-
-# Multiple versions of autoconf generates code that
-# don't work on all platforms (e.g. SunOS 5.8) if
-# sub directories are soft links. Internally at Ericsson
-# some OTP application directories are soft links.
-# An added "/." solves this problem.
-
-@BOOTSTRAP_CONFIGURE_APPS@
-
-if test $bootstrap_only = no; then
-
-@NON_BOOTSTRAP_CONFIGURE_APPS@
-
-fi
-
-AC_OUTPUT
diff --git a/lib/crypto/c_src/Makefile.in b/lib/crypto/c_src/Makefile.in
index 31124ba477..e1e7f71538 100644
--- a/lib/crypto/c_src/Makefile.in
+++ b/lib/crypto/c_src/Makefile.in
@@ -19,7 +19,6 @@
#
include $(ERL_TOP)/make/target.mk
include $(ERL_TOP)/make/$(TARGET)/otp.mk
-include $(ERL_TOP)/make/$(TARGET)/otp_ded.mk
# ----------------------------------------------------
# Application version
@@ -31,23 +30,20 @@ VSN=$(CRYPTO_VSN)
# The following variables differ between systems.
# Set by configure.
# ----------------------------------------------------
-CC = $(DED_CC)
-LD = $(DED_LD)
+CC = @DED_CC@
+LD = @DED_LD@
SHELL = /bin/sh
-LIBS = $(DED_LIBS)
-LDFLAGS += $(DED_LDFLAGS)
-CFLAGS = $(DED_CFLAGS)
+LIBS = @DED_LIBS@
+LDFLAGS += @DED_LDFLAGS@
+CFLAGS = @DED_CFLAGS@ @SSL_FLAGS@
# From erts/configure
SSL_LIBDIR = @SSL_LIBDIR@
SSL_INCLUDE = @SSL_INCLUDE@
SSL_CRYPTO_LIBNAME = @SSL_CRYPTO_LIBNAME@
SSL_SSL_LIBNAME = @SSL_SSL_LIBNAME@
-SSL_FLAGS = @SSL_FLAGS@
-
-INCLUDES = $(SSL_INCLUDE) $(DED_INCLUDES)
-CFLAGS += $(SSL_FLAGS)
+INCLUDES = $(SSL_INCLUDE) @DED_INCLUDE@
ifeq ($(TYPE),debug)
TYPEMARKER = .debug
@@ -70,7 +66,41 @@ RELSYSDIR = $(RELEASE_PATH)/lib/crypto-$(VSN)
# ----------------------------------------------------
# Misc Macros
# ----------------------------------------------------
-CRYPTO_OBJS = $(OBJDIR)/crypto$(TYPEMARKER).o
+
+PRIVDIR = ../priv
+OBJDIR = $(PRIVDIR)/obj/$(TARGET)
+LIBDIR = $(PRIVDIR)/lib/$(TARGET)
+
+CRYPTO_OBJS = $(OBJDIR)/crypto$(TYPEMARKER).o \
+ $(OBJDIR)/aead$(TYPEMARKER).o \
+ $(OBJDIR)/aes$(TYPEMARKER).o \
+ $(OBJDIR)/algorithms$(TYPEMARKER).o \
+ $(OBJDIR)/api_ng$(TYPEMARKER).o \
+ $(OBJDIR)/atoms$(TYPEMARKER).o \
+ $(OBJDIR)/block$(TYPEMARKER).o \
+ $(OBJDIR)/bn$(TYPEMARKER).o \
+ $(OBJDIR)/chacha20$(TYPEMARKER).o \
+ $(OBJDIR)/cipher$(TYPEMARKER).o \
+ $(OBJDIR)/cmac$(TYPEMARKER).o \
+ $(OBJDIR)/dh$(TYPEMARKER).o \
+ $(OBJDIR)/digest$(TYPEMARKER).o \
+ $(OBJDIR)/dss$(TYPEMARKER).o \
+ $(OBJDIR)/ec$(TYPEMARKER).o \
+ $(OBJDIR)/ecdh$(TYPEMARKER).o \
+ $(OBJDIR)/eddsa$(TYPEMARKER).o \
+ $(OBJDIR)/engine$(TYPEMARKER).o \
+ $(OBJDIR)/evp$(TYPEMARKER).o \
+ $(OBJDIR)/fips$(TYPEMARKER).o \
+ $(OBJDIR)/hash$(TYPEMARKER).o \
+ $(OBJDIR)/hmac$(TYPEMARKER).o \
+ $(OBJDIR)/info$(TYPEMARKER).o \
+ $(OBJDIR)/math$(TYPEMARKER).o \
+ $(OBJDIR)/pkey$(TYPEMARKER).o \
+ $(OBJDIR)/poly1305$(TYPEMARKER).o \
+ $(OBJDIR)/rand$(TYPEMARKER).o \
+ $(OBJDIR)/rc4$(TYPEMARKER).o \
+ $(OBJDIR)/rsa$(TYPEMARKER).o \
+ $(OBJDIR)/srp$(TYPEMARKER).o
CALLBACK_OBJS = $(OBJDIR)/crypto_callback$(TYPEMARKER).o
NIF_MAKEFILE = $(PRIVDIR)/Makefile
CRYPTO_STATIC_OBJS = $(OBJDIR)/crypto_static$(TYPEMARKER).o\
@@ -80,19 +110,10 @@ NIF_ARCHIVE = $(LIBDIR)/crypto$(TYPEMARKER).a
TEST_ENGINE_OBJS = $(OBJDIR)/otp_test_engine$(TYPEMARKER).o
-ifeq ($(findstring win32,$(TARGET)), win32)
-NIF_LIB = $(LIBDIR)/crypto$(TYPEMARKER).dll
-CALLBACK_LIB = $(LIBDIR)/crypto_callback$(TYPEMARKER).dll
-TEST_ENGINE_LIB = $(LIBDIR)/otp_test_engine$(TYPEMARKER).dll
-else
-NIF_LIB = $(LIBDIR)/crypto$(TYPEMARKER).so
-CALLBACK_LIB = $(LIBDIR)/crypto_callback$(TYPEMARKER).so
-TEST_ENGINE_LIB = $(LIBDIR)/otp_test_engine$(TYPEMARKER).so
-endif
+NIF_LIB = $(LIBDIR)/crypto$(TYPEMARKER).@DED_EXT@
+CALLBACK_LIB = $(LIBDIR)/crypto_callback$(TYPEMARKER).@DED_EXT@
+TEST_ENGINE_LIB = $(LIBDIR)/otp_test_engine$(TYPEMARKER).@DED_EXT@
-ifeq ($(HOST_OS),)
-HOST_OS := $(shell $(ERL_TOP)/erts/autoconf/config.guess)
-endif
DYNAMIC_CRYPTO_LIB=@SSL_DYNAMIC_ONLY@
ifeq ($(DYNAMIC_CRYPTO_LIB),yes)
@@ -125,7 +146,7 @@ RANLIB=true
endif
ALL_CFLAGS = $(TYPE_FLAGS) $(EXTRA_FLAGS) $(INCLUDES)
-ALL_STATIC_CFLAGS = $(DED_STATIC_CFLAGS) $(INCLUDES)
+ALL_STATIC_CFLAGS = @DED_STATIC_CFLAGS@ $(INCLUDES)
# ----------------------------------------------------
# Targets
@@ -180,32 +201,21 @@ $(LIBDIR)/crypto_callback$(TYPEMARKER).dll: $(CALLBACK_OBJS)
endif
-clean:
-ifeq ($(findstring win32,$(TARGET)), win32)
- rm -f $(LIBDIR)/crypto.dll
- rm -f $(LIBDIR)/crypto.debug.dll
- rm -f $(LIBDIR)/crypto_callback.dll
- rm -f $(LIBDIR)/crypto_callback.debug.dll
- rm -f $(LIBDIR)/otp_test_engine.dll
-else
- rm -f $(LIBDIR)/crypto.so
- rm -f $(LIBDIR)/crypto.debug.so
- rm -f $(LIBDIR)/crypto.valgrind.so
- rm -f $(LIBDIR)/crypto_callback.so
- rm -f $(LIBDIR)/crypto_callback.debug.so
- rm -f $(LIBDIR)/crypto_callback.valgrind.so
- rm -f $(LIBDIR)/otp_test_engine.so
-endif
- rm -f $(OBJDIR)/crypto.o
- rm -f $(OBJDIR)/crypto_static.o
- rm -f $(OBJDIR)/crypto.debug.o
- rm -f $(OBJDIR)/crypto_static.debug.o
- rm -f $(OBJDIR)/crypto.valgrind.o
- rm -f $(OBJDIR)/crypto_static.valgrind.o
- rm -f $(OBJDIR)/crypto_callback.o
- rm -f $(OBJDIR)/crypto_callback.debug.o
- rm -f $(OBJDIR)/crypto_callback.valgrind.o
- rm -f $(OBJDIR)/otp_test_engine.o
+CLEAN_OBJS_RAW = $(CRYPTO_OBJS) $(CALLBACK_OBJS) $(CRYPTO_STATIC_OBJS) $(TEST_ENGINE_OBJS)
+CLEAN_OBJS_O = $(patsubst %.debug.o,%.o,$(CLEAN_OBJS_RAW:.valgrind.o=.o))
+
+CLEAN_LIBS_RAW = $(NIF_LIB) $(CALLBACK_LIB) $(TEST_ENGINE_LIB)
+CLEAN_LIBS_SO = $(patsubst %.debug.@DED_EXT@,%.@DED_EXT@,$(CLEAN_LIBS_RAW:.valgrind.@DED_EXT@=.@DED_EXT@))
+
+clean_dynamic_libs:
+ rm -f $(CLEAN_LIBS_SO)
+ rm -f $(foreach T,.valgrind.@DED_EXT@ .debug.@DED_EXT@,$(CLEAN_LIBS_SO:.@DED_EXT@=$T))
+
+clean_objs:
+ rm -f $(CLEAN_OBJS_O)
+ rm -f $(foreach T,.valgrind.o .debug.o,$(CLEAN_OBJS_O:.o=$T))
+
+clean: clean_objs clean_dynamic_libs
rm -f core *~
docs:
diff --git a/lib/crypto/c_src/aead.c b/lib/crypto/c_src/aead.c
new file mode 100644
index 0000000000..3ee04f1be9
--- /dev/null
+++ b/lib/crypto/c_src/aead.c
@@ -0,0 +1,243 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "aead.h"
+#include "aes.h"
+#include "cipher.h"
+
+ERL_NIF_TERM aead_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Type,Key,Iv,AAD,In) */
+#if defined(HAVE_AEAD)
+ const struct cipher_type_t *cipherp;
+ EVP_CIPHER_CTX *ctx = NULL;
+ const EVP_CIPHER *cipher = NULL;
+ ErlNifBinary key, iv, aad, in;
+ unsigned int tag_len;
+ unsigned char *outp, *tagp;
+ ERL_NIF_TERM type, out, out_tag, ret;
+ int len, ctx_ctrl_set_ivlen, ctx_ctrl_get_tag, ctx_ctrl_set_tag;
+
+ type = argv[0];
+
+ ASSERT(argc == 6);
+
+ if (!enif_is_atom(env, type))
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, argv[1], &key))
+ goto bad_arg;
+ if (!enif_inspect_binary(env, argv[2], &iv))
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, argv[3], &aad))
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, argv[4], &in))
+ goto bad_arg;
+ if (!enif_get_uint(env, argv[5], &tag_len))
+ goto bad_arg;
+
+ if (tag_len > INT_MAX
+ || iv.size > INT_MAX
+ || in.size > INT_MAX
+ || aad.size > INT_MAX)
+ goto bad_arg;
+
+ if ((cipherp = get_cipher_type(type, key.size)) == NULL)
+ goto bad_arg;
+ if (cipherp->flags & NON_EVP_CIPHER)
+ goto bad_arg;
+ if (! (cipherp->flags & AEAD_CIPHER) )
+ goto bad_arg;
+ if ((cipher = cipherp->cipher.p) == NULL)
+ return enif_raise_exception(env, atom_notsup);
+
+ ctx_ctrl_set_ivlen = cipherp->extra.aead.ctx_ctrl_set_ivlen;
+ ctx_ctrl_get_tag = cipherp->extra.aead.ctx_ctrl_get_tag;
+ ctx_ctrl_set_tag = cipherp->extra.aead.ctx_ctrl_set_tag;
+
+ if ((ctx = EVP_CIPHER_CTX_new()) == NULL)
+ goto err;
+
+ if (EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1)
+ goto err;
+ if (EVP_CIPHER_CTX_ctrl(ctx, ctx_ctrl_set_ivlen, (int)iv.size, NULL) != 1)
+ goto err;
+
+#if defined(HAVE_CCM)
+ if (type == atom_aes_ccm) {
+ if (EVP_CIPHER_CTX_ctrl(ctx, ctx_ctrl_set_tag, (int)tag_len, NULL) != 1)
+ goto err;
+ if (EVP_EncryptInit_ex(ctx, NULL, NULL, key.data, iv.data) != 1)
+ goto err;
+ if (EVP_EncryptUpdate(ctx, NULL, &len, NULL, (int)in.size) != 1)
+ goto err;
+ } else
+#endif
+ {
+ if (EVP_EncryptInit_ex(ctx, NULL, NULL, key.data, iv.data) != 1)
+ goto err;
+ }
+
+ if (EVP_EncryptUpdate(ctx, NULL, &len, aad.data, (int)aad.size) != 1)
+ goto err;
+
+ if ((outp = enif_make_new_binary(env, in.size, &out)) == NULL)
+ goto err;
+
+ if (EVP_EncryptUpdate(ctx, outp, &len, in.data, (int)in.size) != 1)
+ goto err;
+ if (EVP_EncryptFinal_ex(ctx, outp/*+len*/, &len) != 1)
+ goto err;
+
+ if ((tagp = enif_make_new_binary(env, tag_len, &out_tag)) == NULL)
+ goto err;
+
+ if (EVP_CIPHER_CTX_ctrl(ctx, ctx_ctrl_get_tag, (int)tag_len, tagp) != 1)
+ goto err;
+
+ CONSUME_REDS(env, in);
+ ret = enif_make_tuple2(env, out, out_tag);
+ goto done;
+
+ bad_arg:
+ ret = enif_make_badarg(env);
+ goto done;
+
+ err:
+ ret = atom_error;
+
+ done:
+ if (ctx)
+ EVP_CIPHER_CTX_free(ctx);
+ return ret;
+
+#else
+ return enif_raise_exception(env, atom_notsup);
+#endif
+}
+
+ERL_NIF_TERM aead_decrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Type,Key,Iv,AAD,In,Tag) */
+#if defined(HAVE_AEAD)
+ const struct cipher_type_t *cipherp;
+ EVP_CIPHER_CTX *ctx = NULL;
+ const EVP_CIPHER *cipher = NULL;
+ ErlNifBinary key, iv, aad, in, tag;
+ unsigned char *outp;
+ ERL_NIF_TERM type, out, ret;
+ int len, ctx_ctrl_set_ivlen, ctx_ctrl_set_tag;
+
+ ASSERT(argc == 6);
+
+ type = argv[0];
+#if defined(HAVE_GCM_EVP_DECRYPT_BUG)
+ if (type == atom_aes_gcm)
+ return aes_gcm_decrypt_NO_EVP(env, argc, argv);
+#endif
+
+ if (!enif_is_atom(env, type))
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, argv[1], &key))
+ goto bad_arg;
+ if (!enif_inspect_binary(env, argv[2], &iv))
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, argv[3], &aad))
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, argv[4], &in))
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, argv[5], &tag))
+ goto bad_arg;
+
+ if (tag.size > INT_MAX
+ || key.size > INT_MAX
+ || iv.size > INT_MAX
+ || in.size > INT_MAX
+ || aad.size > INT_MAX)
+ goto bad_arg;
+
+ if ((cipherp = get_cipher_type(type, key.size)) == NULL)
+ goto bad_arg;
+ if (cipherp->flags & NON_EVP_CIPHER)
+ goto bad_arg;
+ if ( !(cipherp->flags & AEAD_CIPHER) )
+ goto bad_arg;
+ if ((cipher = cipherp->cipher.p) == NULL)
+ return enif_raise_exception(env, atom_notsup);
+
+ ctx_ctrl_set_ivlen = cipherp->extra.aead.ctx_ctrl_set_ivlen;
+ ctx_ctrl_set_tag = cipherp->extra.aead.ctx_ctrl_set_tag;
+
+ if ((outp = enif_make_new_binary(env, in.size, &out)) == NULL)
+ goto err;
+
+ if ((ctx = EVP_CIPHER_CTX_new()) == NULL)
+ goto err;
+ if (EVP_DecryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1)
+ goto err;
+ if (EVP_CIPHER_CTX_ctrl(ctx, ctx_ctrl_set_ivlen, (int)iv.size, NULL) != 1)
+ goto err;
+
+#if defined(HAVE_CCM)
+ if (type == atom_aes_ccm) {
+ if (EVP_CIPHER_CTX_ctrl(ctx, ctx_ctrl_set_tag, (int)tag.size, tag.data) != 1)
+ goto err;
+ if (EVP_DecryptInit_ex(ctx, NULL, NULL, key.data, iv.data) != 1)
+ goto err;
+ if (EVP_DecryptUpdate(ctx, NULL, &len, NULL, (int)in.size) != 1)
+ goto err;
+ }
+ else
+#endif
+ {
+ if (EVP_DecryptInit_ex(ctx, NULL, NULL, key.data, iv.data) != 1)
+ goto err;
+ }
+
+ if (EVP_DecryptUpdate(ctx, NULL, &len, aad.data, (int)aad.size) != 1)
+ goto err;
+ if (EVP_DecryptUpdate(ctx, outp, &len, in.data, (int)in.size) != 1)
+ goto err;
+
+#if defined(HAVE_GCM)
+ if (type == atom_aes_gcm) {
+ if (EVP_CIPHER_CTX_ctrl(ctx, ctx_ctrl_set_tag, (int)tag.size, tag.data) != 1)
+ goto err;
+ if (EVP_DecryptFinal_ex(ctx, outp+len, &len) != 1)
+ goto err;
+ }
+#endif
+ CONSUME_REDS(env, in);
+ ret = out;
+ goto done;
+
+ bad_arg:
+ ret = enif_make_badarg(env);
+ goto done;
+
+ err:
+ ret = atom_error;
+
+ done:
+ if (ctx)
+ EVP_CIPHER_CTX_free(ctx);
+ return ret;
+
+#else
+ return enif_raise_exception(env, atom_notsup);
+#endif
+}
diff --git a/lib/crypto/c_src/aead.h b/lib/crypto/c_src/aead.h
new file mode 100644
index 0000000000..54c0711535
--- /dev/null
+++ b/lib/crypto/c_src/aead.h
@@ -0,0 +1,29 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_AEAD_H__
+#define E_AEAD_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM aead_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM aead_decrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_AEAD_H__ */
diff --git a/lib/crypto/c_src/aes.c b/lib/crypto/c_src/aes.c
new file mode 100644
index 0000000000..ee2bb70fb7
--- /dev/null
+++ b/lib/crypto/c_src/aes.c
@@ -0,0 +1,451 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "aes.h"
+#include "cipher.h"
+
+ERL_NIF_TERM aes_cfb_8_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key, IVec, Data, IsEncrypt) */
+ ErlNifBinary key, ivec, text;
+ AES_KEY aes_key;
+ unsigned char ivec_clone[16]; /* writable copy */
+ int new_ivlen = 0;
+ ERL_NIF_TERM ret;
+ unsigned char *outp;
+
+ CHECK_NO_FIPS_MODE();
+
+ ASSERT(argc == 4);
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &key))
+ goto bad_arg;
+ if (key.size != 16 && key.size != 24 && key.size != 32)
+ goto bad_arg;
+ if (!enif_inspect_binary(env, argv[1], &ivec))
+ goto bad_arg;
+ if (ivec.size != 16)
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, argv[2], &text))
+ goto bad_arg;
+
+ memcpy(ivec_clone, ivec.data, 16);
+
+ /* NOTE: This function returns 0 on success unlike most OpenSSL functions */
+ if (AES_set_encrypt_key(key.data, (int)key.size * 8, &aes_key) != 0)
+ goto err;
+ if ((outp = enif_make_new_binary(env, text.size, &ret)) == NULL)
+ goto err;
+ AES_cfb8_encrypt((unsigned char *) text.data,
+ outp,
+ text.size, &aes_key, ivec_clone, &new_ivlen,
+ (argv[3] == atom_true));
+ CONSUME_REDS(env,text);
+ return ret;
+
+ bad_arg:
+ err:
+ return enif_make_badarg(env);
+}
+
+ERL_NIF_TERM aes_cfb_128_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key, IVec, Data, IsEncrypt) */
+ ErlNifBinary key, ivec, text;
+ AES_KEY aes_key;
+ unsigned char ivec_clone[16]; /* writable copy */
+ int new_ivlen = 0;
+ ERL_NIF_TERM ret;
+ unsigned char *outp;
+
+ ASSERT(argc == 4);
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &key))
+ goto bad_arg;
+ if (key.size != 16 && key.size != 24 && key.size != 32)
+ goto bad_arg;
+ if (!enif_inspect_binary(env, argv[1], &ivec))
+ goto bad_arg;
+ if (ivec.size != 16)
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, argv[2], &text))
+ goto bad_arg;
+
+ memcpy(ivec_clone, ivec.data, 16);
+
+ /* NOTE: This function returns 0 on success unlike most OpenSSL functions */
+ if (AES_set_encrypt_key(key.data, (int)key.size * 8, &aes_key) != 0)
+ goto err;
+
+ if ((outp = enif_make_new_binary(env, text.size, &ret)) == NULL)
+ goto err;
+ AES_cfb128_encrypt((unsigned char *) text.data,
+ outp,
+ text.size, &aes_key, ivec_clone, &new_ivlen,
+ (argv[3] == atom_true));
+ CONSUME_REDS(env,text);
+ return ret;
+
+ bad_arg:
+ err:
+ return enif_make_badarg(env);
+}
+
+ERL_NIF_TERM aes_ige_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key, IVec, Data, IsEncrypt) */
+#ifdef HAVE_AES_IGE
+ ErlNifBinary key_bin, ivec_bin, data_bin;
+ AES_KEY aes_key;
+ unsigned char ivec[32];
+ int type;
+ unsigned char* ret_ptr;
+ ERL_NIF_TERM ret;
+
+ CHECK_NO_FIPS_MODE();
+
+ ASSERT(argc == 4);
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin))
+ goto bad_arg;
+ if (key_bin.size != 16 && key_bin.size != 32)
+ goto bad_arg;
+ if (!enif_inspect_binary(env, argv[1], &ivec_bin))
+ goto bad_arg;
+ if (ivec_bin.size != 32)
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, argv[2], &data_bin))
+ goto bad_arg;
+ if (data_bin.size % 16 != 0)
+ goto bad_arg;
+
+ if (argv[3] == atom_true) {
+ type = AES_ENCRYPT;
+ /* NOTE: This function returns 0 on success unlike most OpenSSL functions */
+ if (AES_set_encrypt_key(key_bin.data, (int)key_bin.size * 8, &aes_key) != 0)
+ goto err;
+ }
+ else {
+ type = AES_DECRYPT;
+ /* NOTE: This function returns 0 on success unlike most OpenSSL functions */
+ if (AES_set_decrypt_key(key_bin.data, (int)key_bin.size * 8, &aes_key) != 0)
+ goto err;
+ }
+
+ if ((ret_ptr = enif_make_new_binary(env, data_bin.size, &ret)) == NULL)
+ goto err;
+
+ memcpy(ivec, ivec_bin.data, 32); /* writable copy */
+
+ AES_ige_encrypt(data_bin.data, ret_ptr, data_bin.size, &aes_key, ivec, type);
+
+ CONSUME_REDS(env,data_bin);
+ return ret;
+
+ bad_arg:
+ err:
+ return enif_make_badarg(env);
+
+#else
+ return atom_notsup;
+#endif
+}
+
+
+#ifdef HAVE_EVP_AES_CTR
+ERL_NIF_TERM aes_ctr_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key, IVec) */
+ ErlNifBinary key_bin, ivec_bin;
+ struct evp_cipher_ctx *ctx = NULL;
+ const EVP_CIPHER *cipher;
+ ERL_NIF_TERM ret;
+
+ ASSERT(argc == 2);
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin))
+ goto bad_arg;
+ if (!enif_inspect_binary(env, argv[1], &ivec_bin))
+ goto bad_arg;
+ if (ivec_bin.size != 16)
+ goto bad_arg;
+
+ switch (key_bin.size)
+ {
+ case 16:
+ cipher = EVP_aes_128_ctr();
+ break;
+ case 24:
+ cipher = EVP_aes_192_ctr();
+ break;
+ case 32:
+ cipher = EVP_aes_256_ctr();
+ break;
+ default:
+ goto bad_arg;
+ }
+
+ if ((ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx))) == NULL)
+ goto err;
+ if ((ctx->ctx = EVP_CIPHER_CTX_new()) == NULL)
+ goto err;
+
+ if (EVP_CipherInit_ex(ctx->ctx, cipher, NULL,
+ key_bin.data, ivec_bin.data, 1) != 1)
+ goto err;
+
+ if (EVP_CIPHER_CTX_set_padding(ctx->ctx, 0) != 1)
+ goto err;
+
+ ret = enif_make_resource(env, ctx);
+ goto done;
+
+ bad_arg:
+ return enif_make_badarg(env);
+
+ err:
+ ret = enif_make_badarg(env);
+
+ done:
+ if (ctx)
+ enif_release_resource(ctx);
+ return ret;
+}
+
+ERL_NIF_TERM aes_ctr_stream_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Context, Data) */
+ struct evp_cipher_ctx *ctx = NULL, *new_ctx = NULL;
+ ErlNifBinary data_bin;
+ ERL_NIF_TERM ret, cipher_term;
+ unsigned char *out;
+ int outl = 0;
+
+ ASSERT(argc == 2);
+
+ if (!enif_get_resource(env, argv[0], evp_cipher_ctx_rtype, (void**)&ctx))
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, argv[1], &data_bin))
+ goto bad_arg;
+ if (data_bin.size > INT_MAX)
+ goto bad_arg;
+
+ if ((new_ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx))) == NULL)
+ goto err;
+ if ((new_ctx->ctx = EVP_CIPHER_CTX_new()) == NULL)
+ goto err;
+
+ if (EVP_CIPHER_CTX_copy(new_ctx->ctx, ctx->ctx) != 1)
+ goto err;
+
+ if ((out = enif_make_new_binary(env, data_bin.size, &cipher_term)) == NULL)
+ goto err;
+
+ if (EVP_CipherUpdate(new_ctx->ctx, out, &outl, data_bin.data, (int)data_bin.size) != 1)
+ goto err;
+ ASSERT(outl >= 0 && (size_t)outl == data_bin.size);
+
+ ret = enif_make_tuple2(env, enif_make_resource(env, new_ctx), cipher_term);
+ CONSUME_REDS(env,data_bin);
+ goto done;
+
+ bad_arg:
+ return enif_make_badarg(env);
+
+ err:
+ ret = enif_make_badarg(env);
+
+ done:
+ if (new_ctx)
+ enif_release_resource(new_ctx);
+ return ret;
+}
+
+#else /* if not HAVE_EVP_AES_CTR */
+
+ERL_NIF_TERM aes_ctr_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key, IVec) */
+ ASSERT(argc == 2);
+
+ return aes_ctr_stream_init_compat(env, argv[0], argv[1]);
+}
+
+
+ERL_NIF_TERM aes_ctr_stream_init_compat(ErlNifEnv* env, const ERL_NIF_TERM key_term, const ERL_NIF_TERM iv_term)
+{
+ ErlNifBinary key_bin, ivec_bin;
+ ERL_NIF_TERM ecount_bin;
+ unsigned char *outp;
+
+ if (!enif_inspect_iolist_as_binary(env, key_term, &key_bin))
+ goto bad_arg;
+ if (key_bin.size != 16 && key_bin.size != 24 && key_bin.size != 32)
+ goto bad_arg;
+ if (!enif_inspect_binary(env, iv_term, &ivec_bin))
+ goto bad_arg;
+ if (ivec_bin.size != 16)
+ goto bad_arg;
+ if ((outp = enif_make_new_binary(env, AES_BLOCK_SIZE, &ecount_bin)) == NULL)
+ goto err;
+ memset(outp, 0, AES_BLOCK_SIZE);
+
+ return enif_make_tuple4(env, key_term, iv_term, ecount_bin, enif_make_int(env, 0));
+
+ bad_arg:
+ err:
+ return enif_make_badarg(env);
+}
+
+ERL_NIF_TERM aes_ctr_stream_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ ASSERT(argc == 2);
+
+ return aes_ctr_stream_encrypt_compat(env, argv[0], argv[1]);
+}
+
+
+ERL_NIF_TERM aes_ctr_stream_encrypt_compat(ErlNifEnv* env, const ERL_NIF_TERM state_arg, const ERL_NIF_TERM data_arg)
+{/* ({Key, IVec, ECount, Num}, Data) */
+ ErlNifBinary key_bin, ivec_bin, text_bin, ecount_bin;
+ AES_KEY aes_key;
+ unsigned int num;
+ ERL_NIF_TERM ret, num2_term, cipher_term, ivec2_term, ecount2_term, new_state_term;
+ int state_arity;
+ const ERL_NIF_TERM *state_term;
+ unsigned char * ivec2_buf;
+ unsigned char * ecount2_buf;
+ unsigned char *outp;
+
+ if (!enif_get_tuple(env, state_arg, &state_arity, &state_term))
+ goto bad_arg;
+ if (state_arity != 4)
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, state_term[0], &key_bin))
+ goto bad_arg;
+ if (key_bin.size > INT_MAX / 8)
+ goto bad_arg;
+ if (!enif_inspect_binary(env, state_term[1], &ivec_bin))
+ goto bad_arg;
+ if (ivec_bin.size != 16)
+ goto bad_arg;
+ if (!enif_inspect_binary(env, state_term[2], &ecount_bin))
+ goto bad_arg;
+ if (ecount_bin.size != AES_BLOCK_SIZE)
+ goto bad_arg;
+ if (!enif_get_uint(env, state_term[3], &num))
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, data_arg, &text_bin))
+ goto bad_arg;
+
+ /* NOTE: This function returns 0 on success unlike most OpenSSL functions */
+ if (AES_set_encrypt_key(key_bin.data, (int)key_bin.size * 8, &aes_key) != 0)
+ goto bad_arg;
+
+ if ((ivec2_buf = enif_make_new_binary(env, ivec_bin.size, &ivec2_term)) == NULL)
+ goto err;
+ if ((ecount2_buf = enif_make_new_binary(env, ecount_bin.size, &ecount2_term)) == NULL)
+ goto err;
+
+ memcpy(ivec2_buf, ivec_bin.data, 16);
+ memcpy(ecount2_buf, ecount_bin.data, ecount_bin.size);
+
+ if ((outp = enif_make_new_binary(env, text_bin.size, &cipher_term)) == NULL)
+ goto err;
+
+ AES_ctr128_encrypt((unsigned char *) text_bin.data,
+ outp,
+ text_bin.size, &aes_key, ivec2_buf, ecount2_buf, &num);
+
+ num2_term = enif_make_uint(env, num);
+ new_state_term = enif_make_tuple4(env, state_term[0], ivec2_term, ecount2_term, num2_term);
+ ret = enif_make_tuple2(env, new_state_term, cipher_term);
+ CONSUME_REDS(env,text_bin);
+ return ret;
+
+ bad_arg:
+ err:
+ return enif_make_badarg(env);
+}
+#endif /* !HAVE_EVP_AES_CTR */
+
+#ifdef HAVE_GCM_EVP_DECRYPT_BUG
+ERL_NIF_TERM aes_gcm_decrypt_NO_EVP(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Type,Key,Iv,AAD,In,Tag) */
+ GCM128_CONTEXT *ctx = NULL;
+ ErlNifBinary key, iv, aad, in, tag;
+ AES_KEY aes_key;
+ unsigned char *outp;
+ ERL_NIF_TERM out, ret;
+
+ ASSERT(argc == 6);
+
+ if (!enif_inspect_iolist_as_binary(env, argv[1], &key))
+ goto bad_arg;
+ if (key.size > INT_MAX / 8)
+ goto bad_arg;
+ if (!enif_inspect_binary(env, argv[2], &iv))
+ goto bad_arg;
+ if (iv.size == 0)
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, argv[3], &aad))
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, argv[4], &in))
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, argv[5], &tag))
+ goto bad_arg;
+
+ /* NOTE: This function returns 0 on success unlike most OpenSSL functions */
+ if (AES_set_encrypt_key(key.data, (int)key.size * 8, &aes_key) != 0)
+ goto bad_arg;
+
+ if ((ctx = CRYPTO_gcm128_new(&aes_key, (block128_f)AES_encrypt)) == NULL)
+ goto err;
+
+ CRYPTO_gcm128_setiv(ctx, iv.data, iv.size);
+
+ /* NOTE: This function returns 0 on success unlike most OpenSSL functions */
+ if (CRYPTO_gcm128_aad(ctx, aad.data, aad.size) != 0)
+ goto err;
+
+ if ((outp = enif_make_new_binary(env, in.size, &out)) == NULL)
+ goto err;
+
+ /* NOTE: This function returns 0 on success unlike most OpenSSL functions */
+ if (CRYPTO_gcm128_decrypt(ctx, in.data, outp, in.size) != 0)
+ goto err;
+
+ /* calculate and check the tag */
+ /* NOTE: This function returns 0 on success unlike most OpenSSL functions */
+ if (CRYPTO_gcm128_finish(ctx, tag.data, tag.size) != 0)
+ goto err;
+
+ CONSUME_REDS(env, in);
+ ret = out;
+ goto done;
+
+ bad_arg:
+ ret = enif_make_badarg(env);
+ goto done;
+
+ err:
+ ret = atom_error;
+
+ done:
+ if (ctx)
+ CRYPTO_gcm128_release(ctx);
+ return ret;
+}
+#endif /* HAVE_GCM_EVP_DECRYPT_BUG */
+
diff --git a/lib/crypto/c_src/aes.h b/lib/crypto/c_src/aes.h
new file mode 100644
index 0000000000..527d041410
--- /dev/null
+++ b/lib/crypto/c_src/aes.h
@@ -0,0 +1,41 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_AES_H__
+#define E_AES_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM aes_cfb_8_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM aes_cfb_128_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM aes_ige_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+ERL_NIF_TERM aes_ctr_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM aes_ctr_stream_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+#if !defined(HAVE_EVP_AES_CTR)
+ERL_NIF_TERM aes_ctr_stream_init_compat(ErlNifEnv* env, const ERL_NIF_TERM key_term, const ERL_NIF_TERM iv_term);
+ERL_NIF_TERM aes_ctr_stream_encrypt_compat(ErlNifEnv* env, const ERL_NIF_TERM state_arg, const ERL_NIF_TERM data_arg);
+#endif
+
+#ifdef HAVE_GCM_EVP_DECRYPT_BUG
+ERL_NIF_TERM aes_gcm_decrypt_NO_EVP(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+#endif
+
+#endif /* E_AES_H__ */
diff --git a/lib/crypto/c_src/algorithms.c b/lib/crypto/c_src/algorithms.c
new file mode 100644
index 0000000000..06cd109fc1
--- /dev/null
+++ b/lib/crypto/c_src/algorithms.c
@@ -0,0 +1,273 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "algorithms.h"
+#include "cipher.h"
+
+static unsigned int algo_hash_cnt, algo_hash_fips_cnt;
+static ERL_NIF_TERM algo_hash[14]; /* increase when extending the list */
+static unsigned int algo_pubkey_cnt, algo_pubkey_fips_cnt;
+static ERL_NIF_TERM algo_pubkey[12]; /* increase when extending the list */
+static unsigned int algo_mac_cnt, algo_mac_fips_cnt;
+static ERL_NIF_TERM algo_mac[3]; /* increase when extending the list */
+static unsigned int algo_curve_cnt, algo_curve_fips_cnt;
+static ERL_NIF_TERM algo_curve[89]; /* increase when extending the list */
+static unsigned int algo_rsa_opts_cnt, algo_rsa_opts_fips_cnt;
+static ERL_NIF_TERM algo_rsa_opts[11]; /* increase when extending the list */
+
+void init_algorithms_types(ErlNifEnv* env)
+{
+ // Validated algorithms first
+ algo_hash_cnt = 0;
+ algo_hash[algo_hash_cnt++] = atom_sha;
+#ifdef HAVE_SHA224
+ algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha224");
+#endif
+#ifdef HAVE_SHA256
+ algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha256");
+#endif
+#ifdef HAVE_SHA384
+ algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha384");
+#endif
+#ifdef HAVE_SHA512
+ algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha512");
+#endif
+#ifdef HAVE_SHA3_224
+ algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha3_224");
+#endif
+#ifdef HAVE_SHA3_256
+ algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha3_256");
+#endif
+#ifdef HAVE_SHA3_384
+ algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha3_384");
+#endif
+#ifdef HAVE_SHA3_512
+ algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha3_512");
+#endif
+#ifdef HAVE_BLAKE2
+ algo_hash[algo_hash_cnt++] = enif_make_atom(env, "blake2b");
+ algo_hash[algo_hash_cnt++] = enif_make_atom(env, "blake2s");
+#endif
+
+ // Non-validated algorithms follow
+ algo_hash_fips_cnt = algo_hash_cnt;
+ algo_hash[algo_hash_cnt++] = enif_make_atom(env, "md4");
+ algo_hash[algo_hash_cnt++] = enif_make_atom(env, "md5");
+ algo_hash[algo_hash_cnt++] = enif_make_atom(env, "ripemd160");
+
+ algo_pubkey_cnt = 0;
+ algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "rsa");
+ algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "dss");
+ algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "dh");
+#if defined(HAVE_EC)
+#if !defined(OPENSSL_NO_EC2M)
+ algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "ec_gf2m");
+#endif
+ algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "ecdsa");
+ algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "ecdh");
+#endif
+ // Non-validated algorithms follow
+ algo_pubkey_fips_cnt = algo_pubkey_cnt;
+ // Don't know if Edward curves are fips validated
+#if defined(HAVE_EDDSA)
+ algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "eddsa");
+#endif
+ algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "srp");
+
+
+ // Validated algorithms first
+ algo_mac_cnt = 0;
+ algo_mac[algo_mac_cnt++] = enif_make_atom(env,"hmac");
+#ifdef HAVE_CMAC
+ algo_mac[algo_mac_cnt++] = enif_make_atom(env,"cmac");
+#endif
+#ifdef HAVE_POLY1305
+ algo_mac[algo_mac_cnt++] = enif_make_atom(env,"poly1305");
+#endif
+ // Non-validated algorithms follow
+ algo_mac_fips_cnt = algo_mac_cnt;
+
+ // Validated algorithms first
+ algo_curve_cnt = 0;
+#if defined(HAVE_EC)
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp160k1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp160r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp160r2");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp192r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp192k1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp224k1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp224r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp256k1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp256r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp384r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp521r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime192v1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime192v2");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime192v3");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime239v1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime239v2");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime239v3");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime256v1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls7");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls9");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls12");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP160r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP160t1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP192r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP192t1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP224r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP224t1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP256r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP256t1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP320r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP320t1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP384r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP384t1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP512r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP512t1");
+#if !defined(OPENSSL_NO_EC2M)
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect163k1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect163r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect163r2");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect193r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect193r2");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect233k1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect233r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect239k1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect283k1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect283r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect409k1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect409r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect571k1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect571r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb163v1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb163v2");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb163v3");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb176v1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb191v1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb191v2");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb191v3");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb208w1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb239v1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb239v2");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb239v3");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb272w1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb304w1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb359v1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb368w1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb431r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls3");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls5");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls10");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls11");
+#endif
+#endif
+ // Non-validated algorithms follow
+ algo_curve_fips_cnt = algo_curve_cnt;
+#if defined(HAVE_EC)
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp112r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp112r2");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp128r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp128r2");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls6");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls8");
+#if !defined(OPENSSL_NO_EC2M)
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect113r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect113r2");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect131r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect131r2");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls4");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ipsec3");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ipsec4");
+#endif
+#endif
+ //--
+#ifdef HAVE_EDDSA
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ed25519");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ed448");
+#endif
+#ifdef HAVE_ED_CURVE_DH
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"x25519");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"x448");
+#endif
+
+ // Validated algorithms first
+ algo_rsa_opts_cnt = 0;
+#ifdef HAS_EVP_PKEY_CTX
+# ifdef HAVE_RSA_PKCS1_PSS_PADDING
+ algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_pkcs1_pss_padding");
+ algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_pss_saltlen");
+# endif
+# ifdef HAVE_RSA_MGF1_MD
+ algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_mgf1_md");
+# endif
+# ifdef HAVE_RSA_OAEP_PADDING
+ algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_pkcs1_oaep_padding");
+# endif
+# ifdef HAVE_RSA_OAEP_MD
+ algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_oaep_label");
+ algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_oaep_md");
+# endif
+ algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"signature_md");
+#endif
+ algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_pkcs1_padding");
+ algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_x931_padding");
+#ifdef HAVE_RSA_SSLV23_PADDING
+ algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_sslv23_padding");
+#endif
+ algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_no_padding");
+ algo_rsa_opts_fips_cnt = algo_rsa_opts_cnt;
+
+
+ // Check that the max number of algos is updated
+ ASSERT(algo_hash_cnt <= sizeof(algo_hash)/sizeof(ERL_NIF_TERM));
+ ASSERT(algo_pubkey_cnt <= sizeof(algo_pubkey)/sizeof(ERL_NIF_TERM));
+ ASSERT(algo_mac_cnt <= sizeof(algo_mac)/sizeof(ERL_NIF_TERM));
+ ASSERT(algo_curve_cnt <= sizeof(algo_curve)/sizeof(ERL_NIF_TERM));
+ ASSERT(algo_rsa_opts_cnt <= sizeof(algo_rsa_opts)/sizeof(ERL_NIF_TERM));
+}
+
+ERL_NIF_TERM algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+#ifdef FIPS_SUPPORT
+ int fips_mode = FIPS_mode();
+
+ unsigned int hash_cnt = fips_mode ? algo_hash_fips_cnt : algo_hash_cnt;
+ unsigned int pubkey_cnt = fips_mode ? algo_pubkey_fips_cnt : algo_pubkey_cnt;
+ unsigned int mac_cnt = fips_mode ? algo_mac_fips_cnt : algo_mac_cnt;
+ unsigned int curve_cnt = fips_mode ? algo_curve_fips_cnt : algo_curve_cnt;
+ unsigned int rsa_opts_cnt = fips_mode ? algo_rsa_opts_fips_cnt : algo_rsa_opts_cnt;
+#else
+ unsigned int hash_cnt = algo_hash_cnt;
+ unsigned int pubkey_cnt = algo_pubkey_cnt;
+ unsigned int mac_cnt = algo_mac_cnt;
+ unsigned int curve_cnt = algo_curve_cnt;
+ unsigned int rsa_opts_cnt = algo_rsa_opts_cnt;
+#endif
+ return enif_make_tuple6(env,
+ enif_make_list_from_array(env, algo_hash, hash_cnt),
+ enif_make_list_from_array(env, algo_pubkey, pubkey_cnt),
+ cipher_types_as_list(env),
+ enif_make_list_from_array(env, algo_mac, mac_cnt),
+ enif_make_list_from_array(env, algo_curve, curve_cnt),
+ enif_make_list_from_array(env, algo_rsa_opts, rsa_opts_cnt)
+ );
+}
diff --git a/lib/crypto/c_src/algorithms.h b/lib/crypto/c_src/algorithms.h
new file mode 100644
index 0000000000..068fb661ec
--- /dev/null
+++ b/lib/crypto/c_src/algorithms.h
@@ -0,0 +1,30 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_ALGORITHMS_H__
+#define E_ALGORITHMS_H__ 1
+
+#include "common.h"
+
+void init_algorithms_types(ErlNifEnv* env);
+
+ERL_NIF_TERM algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_ALGORITHMS_H__ */
diff --git a/lib/crypto/c_src/api_ng.c b/lib/crypto/c_src/api_ng.c
new file mode 100644
index 0000000000..c4114d1626
--- /dev/null
+++ b/lib/crypto/c_src/api_ng.c
@@ -0,0 +1,223 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "api_ng.h"
+#include "aes.h"
+#include "cipher.h"
+
+/*
+ * A unified set of functions for encryption/decryption.
+ *
+ * EXPERIMENTAL!!
+ *
+ */
+ERL_NIF_TERM ng_crypto_update(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+
+
+/* Try better error messages in new functions */
+#define ERROR_Term(Env, ReasonTerm) enif_make_tuple2((Env), atom_error, (ReasonTerm))
+#define ERROR_Str(Env, ReasonString) ERROR_Term((Env), enif_make_string((Env),(ReasonString),(ERL_NIF_LATIN1)))
+
+/* Initializes state for (de)encryption
+ */
+ERL_NIF_TERM ng_crypto_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Cipher, Key, IVec, Encrypt) % if no IV for the Cipher, set IVec = <<>>
+ */
+ ErlNifBinary key_bin, ivec_bin;
+ unsigned char *iv = NULL;
+ struct evp_cipher_ctx *ctx;
+ const struct cipher_type_t *cipherp;
+ const EVP_CIPHER *cipher;
+ ERL_NIF_TERM enc_flg_arg, ret;
+ int enc;
+ unsigned iv_len;
+
+ enc_flg_arg = argv[argc-1];
+ if (enc_flg_arg == atom_true)
+ enc = 1;
+ else if (enc_flg_arg == atom_false)
+ enc = 0;
+ else if (enc_flg_arg == atom_undefined)
+ /* For compat funcs in crypto.erl */
+ enc = -1;
+ else
+ return ERROR_Str(env, "Bad enc flag");
+
+ if (!enif_inspect_binary(env, argv[1], &key_bin))
+ return ERROR_Str(env, "Bad key");
+
+ if (!(cipherp = get_cipher_type(argv[0], key_bin.size)))
+ return ERROR_Str(env, "Unknown cipher or bad key size");
+
+ if (FORBIDDEN_IN_FIPS(cipherp))
+ return enif_raise_exception(env, atom_notsup);
+
+ if (enc == -1)
+ return atom_undefined;
+
+ if (!(cipher = cipherp->cipher.p)) {
+#if !defined(HAVE_EVP_AES_CTR)
+ if (cipherp->flags & AES_CTR_COMPAT)
+ return aes_ctr_stream_init_compat(env, argv[1], argv[2]);
+ else
+#endif
+ return enif_raise_exception(env, atom_notsup);
+ }
+
+#ifdef HAVE_ECB_IVEC_BUG
+ if (cipherp->flags & ECB_BUG_0_9_8L)
+ iv_len = 0; /* <= 0.9.8l returns faulty ivec length */
+ else
+#endif
+ iv_len = EVP_CIPHER_iv_length(cipher);
+
+ if (iv_len) {
+ if (!enif_inspect_binary(env, argv[2], &ivec_bin))
+ return ERROR_Str(env, "Bad iv type");
+
+ if (iv_len != ivec_bin.size)
+ return ERROR_Str(env, "Bad iv size");
+
+ iv = ivec_bin.data;
+ }
+
+ if ((ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx))) == NULL)
+ return ERROR_Str(env, "Can't allocate resource");
+
+ ctx->ctx = EVP_CIPHER_CTX_new();
+ if (! ctx->ctx)
+ return ERROR_Str(env, "Can't allocate context");
+
+ if (!EVP_CipherInit_ex(ctx->ctx, cipher, NULL, NULL, NULL, enc)) {
+ enif_release_resource(ctx);
+ return ERROR_Str(env, "Can't initialize context, step 1");
+ }
+
+ if (!EVP_CIPHER_CTX_set_key_length(ctx->ctx, (int)key_bin.size)) {
+ enif_release_resource(ctx);
+ return ERROR_Str(env, "Can't initialize context, key_length");
+ }
+
+ if (EVP_CIPHER_type(cipher) == NID_rc2_cbc) {
+ if (key_bin.size > INT_MAX / 8) {
+ enif_release_resource(ctx);
+ return ERROR_Str(env, "To large rc2_cbc key");
+ }
+ if (!EVP_CIPHER_CTX_ctrl(ctx->ctx, EVP_CTRL_SET_RC2_KEY_BITS, (int)key_bin.size * 8, NULL)) {
+ enif_release_resource(ctx);
+ return ERROR_Str(env, "ctrl rc2_cbc key");
+ }
+ }
+
+ if (!EVP_CipherInit_ex(ctx->ctx, NULL, NULL, key_bin.data, iv, enc)) {
+ enif_release_resource(ctx);
+ return ERROR_Str(env, "Can't initialize key and/or iv");
+ }
+
+ EVP_CIPHER_CTX_set_padding(ctx->ctx, 0);
+
+ ret = enif_make_resource(env, ctx);
+ enif_release_resource(ctx);
+ return ret;
+}
+
+ERL_NIF_TERM ng_crypto_update(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Context, Data)
+ (Context, Data, IV) */
+ struct evp_cipher_ctx *ctx;
+ ErlNifBinary in_data_bin, ivec_bin, out_data_bin;
+ int out_len, block_size;
+
+#if !defined(HAVE_EVP_AES_CTR)
+ const ERL_NIF_TERM *state_term;
+ int state_arity;
+
+ if (enif_get_tuple(env, argv[0], &state_arity, &state_term) && (state_arity == 4)) {
+ return aes_ctr_stream_encrypt_compat(env, argv[0], argv[1]);
+ }
+#endif
+
+ if (!enif_get_resource(env, argv[0], evp_cipher_ctx_rtype, (void**)&ctx))
+ return ERROR_Str(env, "Bad 1:st arg");
+
+ if (!enif_inspect_binary(env, argv[1], &in_data_bin) )
+ return ERROR_Str(env, "Bad 2:nd arg");
+
+ /* arg[1] was checked by the caller */
+ ASSERT(in_data_bin.size =< INT_MAX);
+
+ block_size = EVP_CIPHER_CTX_block_size(ctx->ctx);
+ if (in_data_bin.size % (size_t)block_size != 0)
+ return ERROR_Str(env, "Data not a multiple of block size");
+
+ if (argc==3) {
+ if (!enif_inspect_iolist_as_binary(env, argv[2], &ivec_bin))
+ return ERROR_Str(env, "Not binary IV");
+
+ if (ivec_bin.size > INT_MAX)
+ return ERROR_Str(env, "Too big IV");
+
+ if (!EVP_CipherInit_ex(ctx->ctx, NULL, NULL, NULL, ivec_bin.data, -1))
+ return ERROR_Str(env, "Can't set IV");
+ }
+
+ if (!enif_alloc_binary((size_t)in_data_bin.size+block_size, &out_data_bin))
+ return ERROR_Str(env, "Can't allocate outdata");
+
+ if (!EVP_CipherUpdate(ctx->ctx, out_data_bin.data, &out_len, in_data_bin.data, in_data_bin.size))
+ return ERROR_Str(env, "Can't update");
+
+ if (!enif_realloc_binary(&out_data_bin, (size_t)out_len))
+ return ERROR_Str(env, "Can't reallocate");
+
+ CONSUME_REDS(env, in_data_bin);
+ return enif_make_binary(env, &out_data_bin);
+}
+
+
+ERL_NIF_TERM ng_crypto_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Context, Data)
+ (Context, Data, IV) */
+ int i;
+ ErlNifBinary data_bin;
+ ERL_NIF_TERM new_argv[3];
+
+ ASSERT(argc =< 3);
+
+ if (!enif_inspect_iolist_as_binary(env, argv[1], &data_bin))
+ return ERROR_Str(env, "iodata expected as data");
+
+ if (data_bin.size > INT_MAX)
+ return ERROR_Str(env, "to long data");
+
+ for (i=0; i<argc; i++) new_argv[i] = argv[i];
+ new_argv[1] = enif_make_binary(env, &data_bin);
+
+ /* Run long jobs on a dirty scheduler to not block the current emulator thread */
+ if (data_bin.size > MAX_BYTES_TO_NIF) {
+ return enif_schedule_nif(env, "ng_crypto_update",
+ ERL_NIF_DIRTY_JOB_CPU_BOUND,
+ ng_crypto_update, argc, new_argv);
+ }
+
+ return ng_crypto_update(env, argc, new_argv);
+}
+
diff --git a/lib/crypto/c_src/api_ng.h b/lib/crypto/c_src/api_ng.h
new file mode 100644
index 0000000000..a3b40fe7fc
--- /dev/null
+++ b/lib/crypto/c_src/api_ng.h
@@ -0,0 +1,29 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_API_NG_H__
+#define E_API_NG_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM ng_crypto_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM ng_crypto_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_AES_H__ */
diff --git a/lib/crypto/c_src/atoms.c b/lib/crypto/c_src/atoms.c
new file mode 100644
index 0000000000..2e417da7f4
--- /dev/null
+++ b/lib/crypto/c_src/atoms.c
@@ -0,0 +1,212 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "atoms.h"
+
+ERL_NIF_TERM atom_true;
+ERL_NIF_TERM atom_false;
+ERL_NIF_TERM atom_sha;
+ERL_NIF_TERM atom_error;
+ERL_NIF_TERM atom_rsa_pkcs1_padding;
+ERL_NIF_TERM atom_rsa_pkcs1_oaep_padding;
+ERL_NIF_TERM atom_rsa_no_padding;
+ERL_NIF_TERM atom_signature_md;
+ERL_NIF_TERM atom_undefined;
+
+ERL_NIF_TERM atom_ok;
+ERL_NIF_TERM atom_none;
+ERL_NIF_TERM atom_notsup;
+ERL_NIF_TERM atom_digest;
+#ifdef FIPS_SUPPORT
+ERL_NIF_TERM atom_enabled;
+ERL_NIF_TERM atom_not_enabled;
+#else
+ERL_NIF_TERM atom_not_supported;
+#endif
+
+#if defined(HAVE_EC)
+ERL_NIF_TERM atom_prime_field;
+ERL_NIF_TERM atom_characteristic_two_field;
+ERL_NIF_TERM atom_tpbasis;
+ERL_NIF_TERM atom_ppbasis;
+ERL_NIF_TERM atom_onbasis;
+#endif
+
+ERL_NIF_TERM atom_aes_cfb8;
+ERL_NIF_TERM atom_aes_cfb128;
+#ifdef HAVE_GCM
+ERL_NIF_TERM atom_aes_gcm;
+#endif
+#ifdef HAVE_CCM
+ERL_NIF_TERM atom_aes_ccm;
+#endif
+
+ERL_NIF_TERM atom_rsa;
+ERL_NIF_TERM atom_dss;
+ERL_NIF_TERM atom_ecdsa;
+
+#ifdef HAVE_ED_CURVE_DH
+ERL_NIF_TERM atom_x25519;
+ERL_NIF_TERM atom_x448;
+#endif
+
+ERL_NIF_TERM atom_eddsa;
+#ifdef HAVE_EDDSA
+ERL_NIF_TERM atom_ed25519;
+ERL_NIF_TERM atom_ed448;
+#endif
+
+ERL_NIF_TERM atom_rsa_mgf1_md;
+ERL_NIF_TERM atom_rsa_oaep_label;
+ERL_NIF_TERM atom_rsa_oaep_md;
+ERL_NIF_TERM atom_rsa_pad; /* backwards compatibility */
+ERL_NIF_TERM atom_rsa_padding;
+ERL_NIF_TERM atom_rsa_pkcs1_pss_padding;
+#ifdef HAVE_RSA_SSLV23_PADDING
+ERL_NIF_TERM atom_rsa_sslv23_padding;
+#endif
+ERL_NIF_TERM atom_rsa_x931_padding;
+ERL_NIF_TERM atom_rsa_pss_saltlen;
+
+#ifdef HAVE_BLAKE2
+ERL_NIF_TERM atom_blake2b;
+ERL_NIF_TERM atom_blake2s;
+#endif
+
+#ifdef HAS_ENGINE_SUPPORT
+
+ERL_NIF_TERM atom_engine_method_rsa;
+ERL_NIF_TERM atom_engine_method_dsa;
+ERL_NIF_TERM atom_engine_method_dh;
+ERL_NIF_TERM atom_engine_method_rand;
+ERL_NIF_TERM atom_engine_method_ecdh;
+ERL_NIF_TERM atom_engine_method_ecdsa;
+ERL_NIF_TERM atom_engine_method_ciphers;
+ERL_NIF_TERM atom_engine_method_digests;
+ERL_NIF_TERM atom_engine_method_store;
+ERL_NIF_TERM atom_engine_method_pkey_meths;
+ERL_NIF_TERM atom_engine_method_pkey_asn1_meths;
+ERL_NIF_TERM atom_engine_method_ec;
+
+ERL_NIF_TERM atom_engine;
+ERL_NIF_TERM atom_key_id;
+ERL_NIF_TERM atom_password;
+#endif
+
+int init_atoms(ErlNifEnv *env, const ERL_NIF_TERM fips_mode, const ERL_NIF_TERM load_info) {
+ atom_true = enif_make_atom(env,"true");
+ atom_false = enif_make_atom(env,"false");
+ /* Enter FIPS mode */
+ if (fips_mode == atom_true) {
+#ifdef FIPS_SUPPORT
+ if (!FIPS_mode_set(1)) {
+#else
+ {
+#endif
+ PRINTF_ERR0("CRYPTO: Could not setup FIPS mode");
+ return 0;
+ }
+ } else if (fips_mode != atom_false) {
+ PRINTF_ERR1("CRYPTO: Invalid load_info '%T'", load_info);
+ return 0;
+ }
+
+ atom_sha = enif_make_atom(env,"sha");
+ atom_error = enif_make_atom(env,"error");
+ atom_rsa_pkcs1_padding = enif_make_atom(env,"rsa_pkcs1_padding");
+ atom_rsa_pkcs1_oaep_padding = enif_make_atom(env,"rsa_pkcs1_oaep_padding");
+ atom_rsa_no_padding = enif_make_atom(env,"rsa_no_padding");
+ atom_signature_md = enif_make_atom(env,"signature_md");
+ atom_undefined = enif_make_atom(env,"undefined");
+ atom_ok = enif_make_atom(env,"ok");
+ atom_none = enif_make_atom(env,"none");
+ atom_notsup = enif_make_atom(env,"notsup");
+ atom_digest = enif_make_atom(env,"digest");
+
+#if defined(HAVE_EC)
+ atom_prime_field = enif_make_atom(env,"prime_field");
+ atom_characteristic_two_field = enif_make_atom(env,"characteristic_two_field");
+ atom_tpbasis = enif_make_atom(env,"tpbasis");
+ atom_ppbasis = enif_make_atom(env,"ppbasis");
+ atom_onbasis = enif_make_atom(env,"onbasis");
+#endif
+
+ atom_aes_cfb8 = enif_make_atom(env, "aes_cfb8");
+ atom_aes_cfb128 = enif_make_atom(env, "aes_cfb128");
+#ifdef HAVE_GCM
+ atom_aes_gcm = enif_make_atom(env, "aes_gcm");
+#endif
+#ifdef HAVE_CCM
+ atom_aes_ccm = enif_make_atom(env, "aes_ccm");
+#endif
+
+#ifdef FIPS_SUPPORT
+ atom_enabled = enif_make_atom(env,"enabled");
+ atom_not_enabled = enif_make_atom(env,"not_enabled");
+#else
+ atom_not_supported = enif_make_atom(env,"not_supported");
+#endif
+ atom_rsa = enif_make_atom(env,"rsa");
+ atom_dss = enif_make_atom(env,"dss");
+ atom_ecdsa = enif_make_atom(env,"ecdsa");
+
+#ifdef HAVE_ED_CURVE_DH
+ atom_x25519 = enif_make_atom(env,"x25519");
+ atom_x448 = enif_make_atom(env,"x448");
+#endif
+ atom_eddsa = enif_make_atom(env,"eddsa");
+#ifdef HAVE_EDDSA
+ atom_ed25519 = enif_make_atom(env,"ed25519");
+ atom_ed448 = enif_make_atom(env,"ed448");
+#endif
+ atom_rsa_mgf1_md = enif_make_atom(env,"rsa_mgf1_md");
+ atom_rsa_oaep_label = enif_make_atom(env,"rsa_oaep_label");
+ atom_rsa_oaep_md = enif_make_atom(env,"rsa_oaep_md");
+ atom_rsa_pad = enif_make_atom(env,"rsa_pad"); /* backwards compatibility */
+ atom_rsa_padding = enif_make_atom(env,"rsa_padding");
+ atom_rsa_pkcs1_pss_padding = enif_make_atom(env,"rsa_pkcs1_pss_padding");
+#ifdef HAVE_RSA_SSLV23_PADDING
+ atom_rsa_sslv23_padding = enif_make_atom(env,"rsa_sslv23_padding");
+#endif
+ atom_rsa_x931_padding = enif_make_atom(env,"rsa_x931_padding");
+ atom_rsa_pss_saltlen = enif_make_atom(env,"rsa_pss_saltlen");
+
+#ifdef HAS_ENGINE_SUPPORT
+
+ atom_engine_method_rsa = enif_make_atom(env,"engine_method_rsa");
+ atom_engine_method_dsa = enif_make_atom(env,"engine_method_dsa");
+ atom_engine_method_dh = enif_make_atom(env,"engine_method_dh");
+ atom_engine_method_rand = enif_make_atom(env,"engine_method_rand");
+ atom_engine_method_ecdh = enif_make_atom(env,"engine_method_ecdh");
+ atom_engine_method_ecdsa = enif_make_atom(env,"engine_method_ecdsa");
+ atom_engine_method_store = enif_make_atom(env,"engine_method_store");
+ atom_engine_method_ciphers = enif_make_atom(env,"engine_method_ciphers");
+ atom_engine_method_digests = enif_make_atom(env,"engine_method_digests");
+ atom_engine_method_pkey_meths = enif_make_atom(env,"engine_method_pkey_meths");
+ atom_engine_method_pkey_asn1_meths = enif_make_atom(env,"engine_method_pkey_asn1_meths");
+ atom_engine_method_ec = enif_make_atom(env,"engine_method_ec");
+
+ atom_engine = enif_make_atom(env,"engine");
+ atom_key_id = enif_make_atom(env,"key_id");
+ atom_password = enif_make_atom(env,"password");
+#endif
+
+ return 1;
+}
diff --git a/lib/crypto/c_src/atoms.h b/lib/crypto/c_src/atoms.h
new file mode 100644
index 0000000000..f15523d865
--- /dev/null
+++ b/lib/crypto/c_src/atoms.h
@@ -0,0 +1,114 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_ATOMS_H__
+#define E_ATOMS_H__ 1
+
+#include <erl_nif.h>
+#include "openssl_config.h"
+
+extern ERL_NIF_TERM atom_true;
+extern ERL_NIF_TERM atom_false;
+extern ERL_NIF_TERM atom_sha;
+extern ERL_NIF_TERM atom_error;
+extern ERL_NIF_TERM atom_rsa_pkcs1_padding;
+extern ERL_NIF_TERM atom_rsa_pkcs1_oaep_padding;
+extern ERL_NIF_TERM atom_rsa_no_padding;
+extern ERL_NIF_TERM atom_signature_md;
+extern ERL_NIF_TERM atom_undefined;
+
+extern ERL_NIF_TERM atom_ok;
+extern ERL_NIF_TERM atom_none;
+extern ERL_NIF_TERM atom_notsup;
+extern ERL_NIF_TERM atom_digest;
+#ifdef FIPS_SUPPORT
+extern ERL_NIF_TERM atom_enabled;
+extern ERL_NIF_TERM atom_not_enabled;
+#else
+extern ERL_NIF_TERM atom_not_supported;
+#endif
+
+#if defined(HAVE_EC)
+extern ERL_NIF_TERM atom_prime_field;
+extern ERL_NIF_TERM atom_characteristic_two_field;
+extern ERL_NIF_TERM atom_tpbasis;
+extern ERL_NIF_TERM atom_ppbasis;
+extern ERL_NIF_TERM atom_onbasis;
+#endif
+
+extern ERL_NIF_TERM atom_aes_cfb8;
+extern ERL_NIF_TERM atom_aes_cfb128;
+#ifdef HAVE_GCM
+extern ERL_NIF_TERM atom_aes_gcm;
+#endif
+#ifdef HAVE_CCM
+extern ERL_NIF_TERM atom_aes_ccm;
+#endif
+
+extern ERL_NIF_TERM atom_rsa;
+extern ERL_NIF_TERM atom_dss;
+extern ERL_NIF_TERM atom_ecdsa;
+
+#ifdef HAVE_ED_CURVE_DH
+extern ERL_NIF_TERM atom_x25519;
+extern ERL_NIF_TERM atom_x448;
+#endif
+
+extern ERL_NIF_TERM atom_eddsa;
+#ifdef HAVE_EDDSA
+extern ERL_NIF_TERM atom_ed25519;
+extern ERL_NIF_TERM atom_ed448;
+#endif
+
+extern ERL_NIF_TERM atom_rsa_mgf1_md;
+extern ERL_NIF_TERM atom_rsa_oaep_label;
+extern ERL_NIF_TERM atom_rsa_oaep_md;
+extern ERL_NIF_TERM atom_rsa_pad; /* backwards compatibility */
+extern ERL_NIF_TERM atom_rsa_padding;
+extern ERL_NIF_TERM atom_rsa_pkcs1_pss_padding;
+#ifdef HAVE_RSA_SSLV23_PADDING
+extern ERL_NIF_TERM atom_rsa_sslv23_padding;
+#endif
+extern ERL_NIF_TERM atom_rsa_x931_padding;
+extern ERL_NIF_TERM atom_rsa_pss_saltlen;
+
+#ifdef HAS_ENGINE_SUPPORT
+
+extern ERL_NIF_TERM atom_engine_method_rsa;
+extern ERL_NIF_TERM atom_engine_method_dsa;
+extern ERL_NIF_TERM atom_engine_method_dh;
+extern ERL_NIF_TERM atom_engine_method_rand;
+extern ERL_NIF_TERM atom_engine_method_ecdh;
+extern ERL_NIF_TERM atom_engine_method_ecdsa;
+extern ERL_NIF_TERM atom_engine_method_ciphers;
+extern ERL_NIF_TERM atom_engine_method_digests;
+extern ERL_NIF_TERM atom_engine_method_store;
+extern ERL_NIF_TERM atom_engine_method_pkey_meths;
+extern ERL_NIF_TERM atom_engine_method_pkey_asn1_meths;
+extern ERL_NIF_TERM atom_engine_method_ec;
+
+extern ERL_NIF_TERM atom_engine;
+extern ERL_NIF_TERM atom_key_id;
+extern ERL_NIF_TERM atom_password;
+#endif
+
+int init_atoms(ErlNifEnv *env, const ERL_NIF_TERM fips_mode, const ERL_NIF_TERM load_info);
+
+#endif /* E_ATOMS_H__ */
diff --git a/lib/crypto/c_src/block.c b/lib/crypto/c_src/block.c
new file mode 100644
index 0000000000..0a4fd72623
--- /dev/null
+++ b/lib/crypto/c_src/block.c
@@ -0,0 +1,149 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "block.h"
+#include "aes.h"
+#include "cipher.h"
+
+ERL_NIF_TERM block_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Type, Key, Ivec, Text, IsEncrypt) or (Type, Key, Text, IsEncrypt) */
+ const struct cipher_type_t *cipherp;
+ const EVP_CIPHER *cipher;
+ ErlNifBinary key, ivec, text;
+ EVP_CIPHER_CTX *ctx = NULL;
+ ERL_NIF_TERM ret;
+ unsigned char *out;
+ int ivec_size, out_size = 0;
+ int cipher_len;
+
+ ASSERT(argc == 4 || argc == 5);
+
+ if (!enif_inspect_iolist_as_binary(env, argv[1], &key))
+ goto bad_arg;
+ if (key.size > INT_MAX)
+ goto bad_arg;
+ if ((cipherp = get_cipher_type(argv[0], key.size)) == NULL)
+ goto bad_arg;
+ if (cipherp->flags & (NON_EVP_CIPHER | AEAD_CIPHER))
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, argv[argc - 2], &text))
+ goto bad_arg;
+ if (text.size > INT_MAX)
+ goto bad_arg;
+
+ if (FORBIDDEN_IN_FIPS(cipherp))
+ return enif_raise_exception(env, atom_notsup);
+ if ((cipher = cipherp->cipher.p) == NULL)
+ return enif_raise_exception(env, atom_notsup);
+
+ if (cipherp->flags & AES_CFBx) {
+ if (argv[0] == atom_aes_cfb8
+ && (key.size == 24 || key.size == 32)) {
+ /* Why do EVP_CIPHER_CTX_set_key_length() fail on these key sizes?
+ * Fall back on low level API
+ */
+ return aes_cfb_8_crypt(env, argc-1, argv+1);
+ }
+ else if (argv[0] == atom_aes_cfb128
+ && (key.size == 24 || key.size == 32)) {
+ /* Why do EVP_CIPHER_CTX_set_key_length() fail on these key sizes?
+ * Fall back on low level API
+ */
+ return aes_cfb_128_crypt_nif(env, argc-1, argv+1);
+ }
+ }
+
+ ivec_size = EVP_CIPHER_iv_length(cipher);
+
+#ifdef HAVE_ECB_IVEC_BUG
+ if (cipherp->flags & ECB_BUG_0_9_8L)
+ ivec_size = 0; /* 0.9.8l returns faulty ivec_size */
+#endif
+
+ if (ivec_size < 0)
+ goto bad_arg;
+
+ if ((cipher_len = EVP_CIPHER_block_size(cipher)) < 0)
+ goto bad_arg;
+ if (text.size % (size_t)cipher_len != 0)
+ goto bad_arg;
+
+ if (ivec_size == 0) {
+ if (argc != 4)
+ goto bad_arg;
+ } else {
+ if (argc != 5)
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, argv[2], &ivec))
+ goto bad_arg;
+ if (ivec.size != (size_t)ivec_size)
+ goto bad_arg;
+ }
+
+ if ((out = enif_make_new_binary(env, text.size, &ret)) == NULL)
+ goto err;
+ if ((ctx = EVP_CIPHER_CTX_new()) == NULL)
+ goto err;
+
+ if (!EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL,
+ (argv[argc - 1] == atom_true)))
+ goto err;
+ if (!EVP_CIPHER_CTX_set_key_length(ctx, (int)key.size))
+ goto err;
+
+ if (EVP_CIPHER_type(cipher) == NID_rc2_cbc) {
+ if (key.size > INT_MAX / 8)
+ goto err;
+ if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_SET_RC2_KEY_BITS, (int)key.size * 8, NULL))
+ goto err;
+ }
+
+ if (!EVP_CipherInit_ex(ctx, NULL, NULL, key.data,
+ ivec_size ? ivec.data : NULL, -1))
+ goto err;
+ if (!EVP_CIPHER_CTX_set_padding(ctx, 0))
+ goto err;
+
+ /* OpenSSL 0.9.8h asserts text.size > 0 */
+ if (text.size > 0) {
+ if (!EVP_CipherUpdate(ctx, out, &out_size, text.data, (int)text.size))
+ goto err;
+ if (ASSERT(out_size == text.size), 0)
+ goto err;
+ if (!EVP_CipherFinal_ex(ctx, out + out_size, &out_size))
+ goto err;
+ }
+
+ ASSERT(out_size == 0);
+ CONSUME_REDS(env, text);
+ goto done;
+
+ bad_arg:
+ ret = enif_make_badarg(env);
+ goto done;
+
+ err:
+ ret = enif_raise_exception(env, atom_notsup);
+
+ done:
+ if (ctx)
+ EVP_CIPHER_CTX_free(ctx);
+ return ret;
+}
diff --git a/lib/crypto/c_src/block.h b/lib/crypto/c_src/block.h
new file mode 100644
index 0000000000..cc5e78ce12
--- /dev/null
+++ b/lib/crypto/c_src/block.h
@@ -0,0 +1,28 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_BLOCK_H__
+#define E_BLOCK_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM block_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_BLOCK_H__ */
diff --git a/lib/crypto/c_src/bn.c b/lib/crypto/c_src/bn.c
new file mode 100644
index 0000000000..34ed4f7ebc
--- /dev/null
+++ b/lib/crypto/c_src/bn.c
@@ -0,0 +1,186 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "bn.h"
+
+
+int get_bn_from_mpint(ErlNifEnv* env, ERL_NIF_TERM term, BIGNUM** bnp)
+{
+ BIGNUM *ret;
+ ErlNifBinary bin;
+ int sz;
+
+ if (!enif_inspect_binary(env, term, &bin))
+ goto err;
+ if (bin.size > INT_MAX - 4)
+ goto err;
+
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(bin.data, bin.size);
+
+ if (bin.size < 4)
+ goto err;
+ sz = (int)bin.size - 4;
+ if (get_int32(bin.data) != sz)
+ goto err;
+
+ if ((ret = BN_bin2bn(bin.data+4, sz, NULL)) == NULL)
+ goto err;
+
+ *bnp = ret;
+ return 1;
+
+ err:
+ return 0;
+}
+
+int get_bn_from_bin(ErlNifEnv* env, ERL_NIF_TERM term, BIGNUM** bnp)
+{
+ BIGNUM *ret;
+ ErlNifBinary bin;
+
+ if (!enif_inspect_binary(env, term, &bin))
+ goto err;
+ if (bin.size > INT_MAX)
+ goto err;
+
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(bin.data, bin.size);
+
+ if ((ret = BN_bin2bn(bin.data, (int)bin.size, NULL)) == NULL)
+ goto err;
+
+ *bnp = ret;
+ return 1;
+
+ err:
+ return 0;
+}
+
+ERL_NIF_TERM bin_from_bn(ErlNifEnv* env, const BIGNUM *bn)
+{
+ int bn_len;
+ unsigned char *bin_ptr;
+ ERL_NIF_TERM term;
+
+ /* Copy the bignum into an erlang binary. */
+ if ((bn_len = BN_num_bytes(bn)) < 0)
+ goto err;
+ if ((bin_ptr = enif_make_new_binary(env, (size_t)bn_len, &term)) == NULL)
+ goto err;
+
+ if (BN_bn2bin(bn, bin_ptr) < 0)
+ goto err;
+
+ return term;
+
+ err:
+ return atom_error;
+}
+
+ERL_NIF_TERM mod_exp_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Base,Exponent,Modulo,bin_hdr) */
+ BIGNUM *bn_base = NULL, *bn_exponent = NULL, *bn_modulo = NULL, *bn_result = NULL;
+ BN_CTX *bn_ctx = NULL;
+ unsigned char* ptr;
+ int dlen;
+ unsigned bin_hdr; /* return type: 0=plain binary, 4: mpint */
+ unsigned extra_byte;
+ ERL_NIF_TERM ret;
+
+ ASSERT(argc == 4);
+
+ if (!get_bn_from_bin(env, argv[0], &bn_base))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, argv[1], &bn_exponent))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, argv[2], &bn_modulo))
+ goto bad_arg;
+ if (!enif_get_uint(env, argv[3], &bin_hdr))
+ goto bad_arg;
+ if (bin_hdr != 0 && bin_hdr != 4)
+ goto bad_arg;
+
+ if ((bn_result = BN_new()) == NULL)
+ goto err;
+ if ((bn_ctx = BN_CTX_new()) == NULL)
+ goto err;
+
+ if (!BN_mod_exp(bn_result, bn_base, bn_exponent, bn_modulo, bn_ctx))
+ goto err;
+
+ dlen = BN_num_bytes(bn_result);
+ if (dlen < 0 || dlen > INT_MAX / 8)
+ goto bad_arg;
+ extra_byte = bin_hdr && BN_is_bit_set(bn_result, dlen * 8 - 1);
+
+ if ((ptr = enif_make_new_binary(env, bin_hdr + extra_byte + (unsigned int)dlen, &ret)) == NULL)
+ goto err;
+
+ if (bin_hdr) {
+ put_uint32(ptr, extra_byte + (unsigned int)dlen);
+ ptr[4] = 0; /* extra zeroed byte to ensure a positive mpint */
+ ptr += bin_hdr + extra_byte;
+ }
+
+ BN_bn2bin(bn_result, ptr);
+ goto done;
+
+ bad_arg:
+ err:
+ ret = enif_make_badarg(env);
+
+ done:
+ if (bn_base)
+ BN_free(bn_base);
+ if (bn_exponent)
+ BN_free(bn_exponent);
+ if (bn_modulo)
+ BN_free(bn_modulo);
+ if (bn_result)
+ BN_free(bn_result);
+ if (bn_ctx)
+ BN_CTX_free(bn_ctx);
+ return ret;
+}
+
+#ifdef HAVE_EC
+ERL_NIF_TERM bn2term(ErlNifEnv* env, const BIGNUM *bn)
+{
+ int dlen;
+ unsigned char* ptr;
+ ERL_NIF_TERM ret;
+
+ if (bn == NULL)
+ return atom_undefined;
+
+ dlen = BN_num_bytes(bn);
+ if (dlen < 0)
+ goto err;
+ if ((ptr = enif_make_new_binary(env, (size_t)dlen, &ret)) == NULL)
+ goto err;
+
+ BN_bn2bin(bn, ptr);
+
+ ERL_VALGRIND_MAKE_MEM_DEFINED(ptr, dlen);
+ return ret;
+
+ err:
+ return enif_make_badarg(env);
+}
+#endif
diff --git a/lib/crypto/c_src/bn.h b/lib/crypto/c_src/bn.h
new file mode 100644
index 0000000000..332b06e79d
--- /dev/null
+++ b/lib/crypto/c_src/bn.h
@@ -0,0 +1,36 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_BN_H__
+#define E_BN_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM bin_from_bn(ErlNifEnv* env, const BIGNUM *bn);
+ERL_NIF_TERM mod_exp_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#ifdef HAVE_EC
+ERL_NIF_TERM bn2term(ErlNifEnv* env, const BIGNUM *bn);
+#endif
+
+int get_bn_from_mpint(ErlNifEnv* env, ERL_NIF_TERM term, BIGNUM** bnp);
+int get_bn_from_bin(ErlNifEnv* env, ERL_NIF_TERM term, BIGNUM** bnp);
+
+#endif /* E_BN_H__ */
diff --git a/lib/crypto/c_src/chacha20.c b/lib/crypto/c_src/chacha20.c
new file mode 100644
index 0000000000..cfcc395dca
--- /dev/null
+++ b/lib/crypto/c_src/chacha20.c
@@ -0,0 +1,124 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "chacha20.h"
+#include "cipher.h"
+
+ERL_NIF_TERM chacha20_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key, IV) */
+#if defined(HAVE_CHACHA20)
+ ErlNifBinary key_bin, ivec_bin;
+ struct evp_cipher_ctx *ctx = NULL;
+ const EVP_CIPHER *cipher;
+ ERL_NIF_TERM ret;
+
+ ASSERT(argc == 2);
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin))
+ goto bad_arg;
+ if (key_bin.size != 32)
+ goto bad_arg;
+ if (!enif_inspect_binary(env, argv[1], &ivec_bin))
+ goto bad_arg;
+ if (ivec_bin.size != 16)
+ goto bad_arg;
+
+ cipher = EVP_chacha20();
+
+ if ((ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx))) == NULL)
+ goto err;
+ if ((ctx->ctx = EVP_CIPHER_CTX_new()) == NULL)
+ goto err;
+
+ if (EVP_CipherInit_ex(ctx->ctx, cipher, NULL,
+ key_bin.data, ivec_bin.data, 1) != 1)
+ goto err;
+ if (EVP_CIPHER_CTX_set_padding(ctx->ctx, 0) != 1)
+ goto err;
+
+ ret = enif_make_resource(env, ctx);
+ goto done;
+
+ bad_arg:
+ return enif_make_badarg(env);
+
+ err:
+ ret = enif_make_badarg(env);
+
+ done:
+ if (ctx)
+ enif_release_resource(ctx);
+ return ret;
+
+#else
+ return enif_raise_exception(env, atom_notsup);
+#endif
+}
+
+ERL_NIF_TERM chacha20_stream_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (State, Data) */
+#if defined(HAVE_CHACHA20)
+ struct evp_cipher_ctx *ctx = NULL, *new_ctx = NULL;
+ ErlNifBinary data_bin;
+ ERL_NIF_TERM ret, cipher_term;
+ unsigned char *out;
+ int outl = 0;
+
+ ASSERT(argc == 2);
+
+ if (!enif_get_resource(env, argv[0], evp_cipher_ctx_rtype, (void**)&ctx))
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, argv[1], &data_bin))
+ goto bad_arg;
+ if (data_bin.size > INT_MAX)
+ goto bad_arg;
+
+ if ((new_ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx))) == NULL)
+ goto err;
+ if ((new_ctx->ctx = EVP_CIPHER_CTX_new()) == NULL)
+ goto err;
+
+ if (EVP_CIPHER_CTX_copy(new_ctx->ctx, ctx->ctx) != 1)
+ goto err;
+ if ((out = enif_make_new_binary(env, data_bin.size, &cipher_term)) == NULL)
+ goto err;
+ if (EVP_CipherUpdate(new_ctx->ctx, out, &outl, data_bin.data, (int)data_bin.size) != 1)
+ goto err;
+ ASSERT(outl >= 0 && (size_t)outl == data_bin.size);
+
+ ret = enif_make_tuple2(env, enif_make_resource(env, new_ctx), cipher_term);
+ CONSUME_REDS(env, data_bin);
+ goto done;
+
+ bad_arg:
+ return enif_make_badarg(env);
+
+ err:
+ ret = enif_make_badarg(env);
+
+ done:
+ if (new_ctx)
+ enif_release_resource(new_ctx);
+ return ret;
+
+#else
+ return enif_raise_exception(env, atom_notsup);
+#endif
+}
diff --git a/lib/crypto/c_src/chacha20.h b/lib/crypto/c_src/chacha20.h
new file mode 100644
index 0000000000..7e2ccae2bb
--- /dev/null
+++ b/lib/crypto/c_src/chacha20.h
@@ -0,0 +1,29 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_CHACHA20_H__
+#define E_CHACHA20_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM chacha20_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM chacha20_stream_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_CHACHA20_H__ */
diff --git a/lib/crypto/c_src/check_erlang.cocci b/lib/crypto/c_src/check_erlang.cocci
new file mode 100644
index 0000000000..b2a981f2ac
--- /dev/null
+++ b/lib/crypto/c_src/check_erlang.cocci
@@ -0,0 +1,196 @@
+// %CopyrightBegin%
+//
+// Copyright Doug Hogan 2019. All 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%
+
+// Coccinelle script to help verify Erlang calls.
+// http://coccinelle.lip6.fr
+// https://github.com/coccinelle/coccinelle
+//
+// These work with the Erlang code because it has a rigid coding pattern.
+// $ spatch.opt --all-includes -sp_file check_erlang.cocci -dir .
+
+// Make sure resources are cleaned up properly in all paths.
+// Need 'strict' so it's also checked in error handling paths.
+@enif_alloc_resource@
+type T;
+identifier CTX, L;
+identifier virtual.enif_alloc_resource, virtual.enif_release_resource;
+position p, pr;
+@@
+
+ T *CTX = NULL;
+
+ ...
+ if ((CTX = enif_alloc_resource(...)@p) == NULL)
+ goto L;
+
+ ... when strict, forall
+ if (CTX)
+ enif_release_resource(CTX)@pr;
+
+
+// After calling enif_alloc_binary(), you must either release it with
+// enif_release_binary() or transfer ownership to Erlang via enif_make_binary().
+@enif_alloc_binary@
+expression SZ;
+identifier BIN, RET, ENV, X, L;
+identifier TUPLE =~ "^enif_make_tuple[0-9]+$";
+identifier virtual.enif_alloc_binary, virtual.enif_make_binary;
+identifier virtual.enif_release_binary;
+position pa, pm, pr;
+@@
+
+// This construct is used in engine.c
+(
+ if (!enif_alloc_binary(SZ, &BIN)@pa)
+ goto L;
+
+ ... when strict, forall
+ return
+(
+ enif_make_binary(ENV, &BIN)@pm
+|
+ TUPLE(..., enif_make_binary(ENV, &BIN)@pm)@pm
+);
+
+|
+// This is the typical way we allocate and use binaries.
+ int X = 0;
+
+ ...
+ if (!enif_alloc_binary(SZ, &BIN)@pa)
+ goto L;
+ X = 1;
+
+ ... when strict, forall
+(
+ RET = enif_make_binary(ENV, &BIN)@pm;
+ X = 0;
+|
+ if (X)
+ enif_release_binary(&BIN)@pr;
+|
+ return enif_make_binary(ENV, &BIN)@pm;
+)
+)
+
+// TODO: These don't have single checks that handle all cases.
+//
+// enif_consume_timeslice returns 1 if exhausted or else 0
+// enif_has_pending_exception returns true if exception pending
+
+@erlang_check_void@
+identifier FUNCVOID =~ "^(enif_mutex_destroy|enif_mutex_lock|enif_mutex_unlock|enif_rwlock_destroy|enif_rwlock_rlock|enif_rwlock_runlock|enif_rwlock_rwlock|enif_rwlock_rwunlock|enif_system_info)$";
+position p;
+@@
+
+ FUNCVOID(...)@p;
+
+
+@erlang_check_null@
+expression X;
+identifier L;
+identifier FUNCNULL =~ "^(enif_alloc|enif_alloc_resource|enif_dlopen|enif_dlsym|enif_make_new_binary|enif_mutex_create|enif_open_resource_type|enif_realloc|enif_rwlock_create)$";
+position p;
+@@
+
+(
+ if ((X = FUNCNULL(...)@p) == NULL)
+ goto L;
+|
+ X = FUNCNULL(...)@p;
+ if (X == NULL)
+ goto L;
+|
+ return FUNCNULL(...)@p;
+)
+
+
+@erlang_check_not@
+identifier L;
+identifier FUNCNOT =~ "^(enif_alloc_binary|enif_get_int|enif_get_list_cell|enif_get_list_length|enif_get_long|enif_get_map_value|enif_get_resource|enif_get_tuple|enif_get_uint|enif_get_ulong|enif_inspect_binary|enif_inspect_iolist_as_binary|enif_is_atom|enif_is_binary|enif_is_current_process_alive|enif_is_empty_list|enif_is_list|enif_is_map|enif_is_tuple|enif_realloc_binary)$";
+position p;
+@@
+
+(
+ if (!FUNCNOT(...)@p)
+ goto L;
+|
+ return FUNCNOT(...)@p;
+)
+
+
+@erlang_check_null_free@
+expression X;
+identifier FUNCFREE =~ "^(enif_free|enif_free_env|enif_free_iovec|enif_release_binary|enif_release_resource)$";
+position p;
+@@
+
+ if (
+(
+ X
+|
+ X != NULL
+)
+ )
+ FUNCFREE(X)@p;
+
+
+@erlang_check_new@
+expression RET;
+identifier FUNCNEW =~ "^(enif_make_atom|enif_make_badarg|enif_make_binary|enif_make_int|enif_make_list|enif_make_list_from_array|enif_make_resource|enif_make_tuple|enif_raise_exception|enif_schedule_nif|enif_thread_self)$";
+position p;
+@@
+
+(
+ RET = FUNCNEW(...)@p;
+|
+ return FUNCNEW(...)@p;
+)
+
+
+// Flag any calls that aren't part of the above pattern.
+@enif_alloc_not_free@
+
+identifier FUNCVOID =~ "^(enif_mutex_destroy|enif_mutex_lock|enif_mutex_unlock|enif_rwlock_destroy|enif_rwlock_rlock|enif_rwlock_runlock|enif_rwlock_rwlock|enif_rwlock_rwunlock|enif_system_info)$";
+position pvoid != {erlang_check_void.p,enif_alloc_binary.pr};
+
+identifier FUNCNULL =~ "^(enif_alloc|enif_alloc_resource|enif_dlopen|enif_dlsym|enif_make_new_binary|enif_mutex_create|enif_open_resource_type|enif_realloc|enif_rwlock_create)$";
+position pnull != {erlang_check_null.p,enif_alloc_resource.p};
+
+identifier FUNCNOT =~ "^(enif_alloc_binary|enif_get_int|enif_get_list_cell|enif_get_list_length|enif_get_long|enif_get_map_value|enif_get_resource|enif_get_tuple|enif_get_uint|enif_get_ulong|enif_inspect_binary|enif_inspect_iolist_as_binary|enif_is_atom|enif_is_binary|enif_is_current_process_alive|enif_is_empty_list|enif_is_list|enif_is_map|enif_is_tuple|enif_realloc_binary)$";
+position pnot != {erlang_check_not.p,enif_alloc_binary.pa};
+
+identifier FUNCNEW =~ "^(enif_make_atom|enif_make_badarg|enif_make_binary|enif_make_int|enif_make_list|enif_make_list_from_array|enif_make_resource|enif_make_tuple|enif_raise_exception|enif_schedule_nif|enif_thread_self)$";
+position pnew != {erlang_check_new.p,enif_alloc_binary.pm};
+
+identifier FUNCFREE =~ "^(enif_free|enif_free_env|enif_free_iovec|enif_release_binary|enif_release_resource)$";
+position pfree != {enif_alloc_resource.pr,enif_alloc_binary.pr,erlang_check_null_free.p};
+
+@@
+
+(
+* FUNCVOID(...)@pvoid
+|
+* FUNCNULL(...)@pnull
+|
+* FUNCNOT(...)@pnot
+|
+* FUNCNEW(...)@pnew
+|
+* FUNCFREE(...)@pfree
+)
diff --git a/lib/crypto/c_src/check_openssl.cocci b/lib/crypto/c_src/check_openssl.cocci
new file mode 100644
index 0000000000..75d1a6e44b
--- /dev/null
+++ b/lib/crypto/c_src/check_openssl.cocci
@@ -0,0 +1,281 @@
+// %CopyrightBegin%
+//
+// Copyright Doug Hogan 2019. All 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%
+
+// Coccinelle script to help verify the subset of OpenSSL calls used by Erlang.
+// http://coccinelle.lip6.fr
+// https://github.com/coccinelle/coccinelle
+//
+// These work with the Erlang code because it has a rigid coding pattern.
+// $ spatch.opt --all-includes -sp_file check_openssl.cocci -dir .
+
+// TODO: These APIs may not have a single check that covers all cases
+// or may not be necessary to check.
+//
+// BN_GENCB_get_arg
+// BN_bn2bin
+// BN_cmp
+// BN_is_bit_set
+// BN_is_negative
+// BN_is_zero
+// BN_num_bits
+// DH_get0_key
+// DH_size
+// EC_GROUP_get_degree
+// EC_KEY_get0_group
+// EC_KEY_get0_private_key
+// EC_KEY_get0_public_key
+// EC_KEY_get_conv_form
+// EVP_CIPHER_block_size
+// EVP_CIPHER_iv_length
+// EVP_CIPHER_type
+// EVP_MD_CTX_md
+// EVP_MD_size
+// EVP_aes_128_cbc
+// EVP_aes_128_ccm
+// EVP_aes_128_cfb128
+// EVP_aes_128_cfb8
+// EVP_aes_128_ctr
+// EVP_aes_128_ecb
+// EVP_aes_128_gcm
+// EVP_aes_192_cbc
+// EVP_aes_192_ccm
+// EVP_aes_192_ctr
+// EVP_aes_192_ecb
+// EVP_aes_192_gcm
+// EVP_aes_256_cbc
+// EVP_aes_256_ccm
+// EVP_aes_256_ctr
+// EVP_aes_256_ecb
+// EVP_aes_256_gcm
+// EVP_bf_cbc
+// EVP_bf_cfb64
+// EVP_bf_ecb
+// EVP_bf_ofb
+// EVP_chacha20
+// EVP_chacha20_poly1305
+// EVP_des_cbc
+// EVP_des_cfb8
+// EVP_des_ecb
+// EVP_des_ede3_cbc
+// EVP_des_ede3_cfb8
+// EVP_md4
+// EVP_md5
+// EVP_rc2_cbc
+// EVP_ripemd160
+// EVP_sha1
+// EVP_sha224
+// EVP_sha256
+// EVP_sha384
+// EVP_sha3_224
+// EVP_sha3_256
+// EVP_sha3_384
+// EVP_sha3_512
+// EVP_sha512
+// OpenSSL_version
+// OpenSSL_version_num
+// PEM_read_PrivateKey
+// PEM_read_PUBKEY
+// RSA_size
+
+// Unusual API for OpenSSL: 0 or positive on success and negative value(s) on error.
+@openssl_check_negative@
+identifier FUNCNEG =~ "^(DH_compute_key|RSA_padding_check_SSLv23)$";
+expression X;
+identifier L;
+position p;
+@@
+
+ if (
+(
+ FUNCNEG(...)@p < 0
+|
+ (X = FUNCNEG(...)@p) < 0
+)
+ )
+ goto L;
+
+// Unusual API for OpenSSL: positive on success or else error
+@openssl_check_positive@
+identifier FUNCPOS =~ "^(ECDH_compute_key|EVP_CIPHER_asn1_to_param|EVP_CIPHER_param_to_asn1|EVP_PKEY_CTX_ctrl|RSA_pkey_ctx_ctrl)$";
+identifier L;
+expression X;
+position p;
+@@
+
+ if (
+(
+ FUNCPOS(...)@p < 1
+|
+ (X = FUNCPOS(...)@p) < 1
+)
+ )
+ goto L;
+
+// Unusual API for OpenSSL: 0=success.
+@openssl_check_0@
+identifier L;
+expression X;
+identifier FUNC0 =~ "^(AES_set_decrypt_key|AES_set_encrypt_key|CRYPTO_gcm128_aad|CRYPTO_gcm128_decrypt|CRYPTO_gcm128_finish)$";
+position p;
+@@
+
+ if (
+(
+ FUNC0(...)@p != 0
+|
+ (X = FUNC0(...)@p) != 0
+)
+ )
+ goto L;
+
+// These do not necessarily allocate resources but they may return NULL.
+@openssl_check_null@
+expression X;
+identifier L;
+identifier FUNCNULL =~ "^(BN_CTX_new|BN_GENCB_new|BN_MONT_CTX_new|BN_bin2bn|BN_dup|BN_generate_prime|BN_new|CMAC_CTX_new|CRYPTO_clear_realloc|CRYPTO_gcm128_new|CRYPTO_malloc|CRYPTO_realloc|CRYPTO_zalloc|DH_generate_parameters|DH_new|DSA_new|EC_GROUP_dup|EC_GROUP_get0_generator|EC_GROUP_method_of|EC_GROUP_new_curve_GFm|EC_GROUP_new_curve_GFp|EC_KEY_copy|EC_KEY_dup|EC_KEY_get0_engine|EC_KEY_new|EC_KEY_new_by_curve_name|EC_POINT_bn2point|EC_POINT_dup|EC_POINT_new|EC_POINT_point2bn|ENGINE_by_id|ENGINE_get_cipher_engine|ENGINE_get_default_DH|ENGINE_get_default_DSA|ENGINE_get_default_RAND|ENGINE_get_default_RSA|ENGINE_get_digest_engine|ENGINE_get_first|ENGINE_get_id|ENGINE_get_last|ENGINE_get_name|ENGINE_get_next|ENGINE_get_prev|ENGINE_load_private_key|ENGINE_load_public_key|ENGINE_new|EVP_CIPHER_CTX_new|EVP_MAC_CTX_new|EVP_MAC_CTX_new_id|EVP_MD_CTX_new|EVP_MD_meth_new|EVP_PKEY_CTX_new|EVP_PKEY_CTX_new_id|EVP_PKEY_get1_DH|EVP_PKEY_get1_DSA|EVP_PKEY_get1_EC_KEY|EVP_PKEY_get1_RSA|EVP_PKEY_new|EVP_PKEY_new_raw_private_key|EVP_PKEY_new_raw_public_key|EVP_get_cipherbyname|EVP_get_cipherbynid|EVP_get_cipherbyobj|EVP_get_macbyname|EVP_get_macbynid|EVP_get_macbyobj|HMAC|HMAC_CTX_new|OPENSSL_buf2hexstr|OPENSSL_clear_realloc|OPENSSL_hexstr2buf|OPENSSL_malloc|OPENSSL_realloc|OPENSSL_strdup|OPENSSL_strndup|OPENSSL_zalloc|RSA_meth_dup|RSA_meth_new|RSA_new)$";
+position p;
+@@
+
+(
+ if ((X = FUNCNULL(...)@p) == NULL)
+ goto L;
+|
+ X = FUNCNULL(...)@p;
+ if (X == NULL)
+ goto L;
+)
+
+// non-zero=success, 0=failure. These can be safely used with !
+@openssl_check_not@
+expression X;
+identifier L;
+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 p;
+@@
+
+ if (
+(
+ !FUNCNOT(...)@p
+|
+ !(X = FUNCNOT)@p
+)
+ )
+ goto L;
+
+// 1=success. These may have == 0 or <= 0 or non-one failure so we explicitly check for success.
+// Since some EVP_* functions use failure == 0 and others use <= 0, we consolidate all
+// EVP_* calls into here so it's less error prone. In such cases, they all use 1 for success.
+@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)$";
+position p;
+@@
+
+ if (
+(
+ FUNC1(...)@p != 1
+|
+ (X = FUNC1(...)@p) != 1
+)
+ )
+ goto L;
+
+
+// These are void but here for completeness
+@openssl_void@
+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)$";
+position p;
+@@
+
+ FUNCVOID(...)@p;
+
+
+// Traditionally, OpenSSL didn't adhere to the semantics of free() calls
+// allowing for NULL. However, they have been changing it over time.
+// Since Erlang allows for unmaintained versions of OpenSSL, be conservative
+// and assume the worst.
+@openssl_free@
+expression X;
+identifier FUNCFREE =~ "^(BN_CTX_free|BN_GENCB_free|BN_clear_free|BN_free|CMAC_CTX_free|CRYPTO_free|DH_free|DSA_free|EC_GROUP_free|EC_KEY_free|EC_POINT_free|EVP_CIPHER_CTX_free|EVP_MD_CTX_free|EVP_PKEY_CTX_free|EVP_PKEY_free|HMAC_CTX_free|RSA_free|RSA_meth_free)$";
+position p;
+@@
+
+ if (
+(
+ X
+|
+ X != NULL
+)
+ )
+ FUNCFREE(X)@p;
+
+
+// NOTE: Keep these in sync with the above definitions!
+//
+// Find all of the cases that we haven't marked safe positions of.
+//
+// This will flag a few false positives because the code isn't using the
+// standard pattern.
+//
+// NOTE: You have to copy the regexps because there doesn't appear to be a way in
+// coccinelle to reference a regexp identifier from another rule properly.
+@openssl_check_NOT_SAFE@
+
+identifier FUNCNEG =~ "^(DH_compute_key|RSA_padding_check_SSLv23)$";
+position pneg != openssl_check_negative.p;
+
+identifier FUNCPOS =~ "^(ECDH_compute_key|EVP_CIPHER_asn1_to_param|EVP_CIPHER_param_to_asn1|EVP_PKEY_CTX_ctrl|RSA_pkey_ctx_ctrl)$";
+position ppos != openssl_check_positive.p;
+
+identifier FUNC0 =~ "^(AES_set_decrypt_key|AES_set_encrypt_key|CRYPTO_gcm128_aad|CRYPTO_gcm128_decrypt|CRYPTO_gcm128_finish)$";
+position p0 != openssl_check_0.p;
+
+identifier FUNCNULL =~ "^(BN_CTX_new|BN_GENCB_new|BN_MONT_CTX_new|BN_bin2bn|BN_dup|BN_generate_prime|BN_new|CMAC_CTX_new|CRYPTO_clear_realloc|CRYPTO_gcm128_new|CRYPTO_malloc|CRYPTO_realloc|CRYPTO_zalloc|DH_generate_parameters|DH_new|DSA_new|EC_GROUP_dup|EC_GROUP_get0_generator|EC_GROUP_method_of|EC_GROUP_new_curve_GFm|EC_GROUP_new_curve_GFp|EC_KEY_copy|EC_KEY_dup|EC_KEY_get0_engine|EC_KEY_new|EC_KEY_new_by_curve_name|EC_POINT_bn2point|EC_POINT_dup|EC_POINT_new|EC_POINT_point2bn|ENGINE_by_id|ENGINE_get_cipher_engine|ENGINE_get_default_DH|ENGINE_get_default_DSA|ENGINE_get_default_RAND|ENGINE_get_default_RSA|ENGINE_get_digest_engine|ENGINE_get_first|ENGINE_get_id|ENGINE_get_last|ENGINE_get_name|ENGINE_get_next|ENGINE_get_prev|ENGINE_load_private_key|ENGINE_load_public_key|ENGINE_new|EVP_CIPHER_CTX_new|EVP_MAC_CTX_new|EVP_MAC_CTX_new_id|EVP_MD_CTX_new|EVP_MD_meth_new|EVP_PKEY_CTX_new|EVP_PKEY_CTX_new_id|EVP_PKEY_get1_DH|EVP_PKEY_get1_DSA|EVP_PKEY_get1_EC_KEY|EVP_PKEY_get1_RSA|EVP_PKEY_new|EVP_PKEY_new_raw_private_key|EVP_PKEY_new_raw_public_key|EVP_get_cipherbyname|EVP_get_cipherbynid|EVP_get_cipherbyobj|EVP_get_macbyname|EVP_get_macbynid|EVP_get_macbyobj|HMAC|HMAC_CTX_new|OPENSSL_buf2hexstr|OPENSSL_clear_realloc|OPENSSL_hexstr2buf|OPENSSL_malloc|OPENSSL_realloc|OPENSSL_strdup|OPENSSL_strndup|OPENSSL_zalloc|RSA_meth_dup|RSA_meth_new|RSA_new)$";
+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)$";
+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)$";
+position pvoid != openssl_void.p;
+
+identifier FUNCFREE =~ "^(BN_CTX_free|BN_GENCB_free|BN_clear_free|BN_free|CMAC_CTX_free|CRYPTO_free|DH_free|DSA_free|EC_GROUP_free|EC_KEY_free|EC_POINT_free|EVP_CIPHER_CTX_free|EVP_MD_CTX_free|EVP_PKEY_CTX_free|EVP_PKEY_free|HMAC_CTX_free|RSA_free|RSA_meth_free)$";
+position pfree != openssl_free.p;
+@@
+
+(
+* FUNCNEG(...)@pneg
+|
+* FUNCPOS(...)@ppos
+|
+* FUNCNULL(...)@pnull
+|
+* FUNC0(...)@p0
+|
+* FUNC1(...)@p1
+|
+* FUNCNOT(...)@pnot
+|
+* FUNCVOID(...)@pvoid
+|
+* FUNCFREE(...)@pfree
+)
diff --git a/lib/crypto/c_src/cipher.c b/lib/crypto/c_src/cipher.c
new file mode 100644
index 0000000000..f8e44b228a
--- /dev/null
+++ b/lib/crypto/c_src/cipher.c
@@ -0,0 +1,229 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "cipher.h"
+
+#ifdef OPENSSL_NO_DES
+#define COND_NO_DES_PTR(Ptr) (NULL)
+#else
+#define COND_NO_DES_PTR(Ptr) (Ptr)
+#endif
+
+static struct cipher_type_t cipher_types[] =
+{
+#ifndef OPENSSL_NO_RC2
+ {{"rc2_cbc"}, {&EVP_rc2_cbc}, 0, NO_FIPS_CIPHER},
+#else
+ {{"rc2_cbc"}, {NULL}, 0, NO_FIPS_CIPHER},
+#endif
+#ifndef OPENSSL_NO_RC4
+ {{"rc4"}, {&EVP_rc4}, 0, NO_FIPS_CIPHER},
+#else
+ {{"rc4"}, {NULL}, 0, NO_FIPS_CIPHER},
+#endif
+ {{"des_cbc"}, {COND_NO_DES_PTR(&EVP_des_cbc)}, 0, NO_FIPS_CIPHER},
+ {{"des_cfb"}, {COND_NO_DES_PTR(&EVP_des_cfb8)}, 0, NO_FIPS_CIPHER},
+ {{"des_ecb"}, {COND_NO_DES_PTR(&EVP_des_ecb)}, 0, NO_FIPS_CIPHER | ECB_BUG_0_9_8L},
+
+ {{"des_ede3_cbc"}, {COND_NO_DES_PTR(&EVP_des_ede3_cbc)}, 0, 0},
+
+#ifdef HAVE_DES_ede3_cfb_encrypt
+ {{"des_ede3_cfb"}, {COND_NO_DES_PTR(&EVP_des_ede3_cfb8)}, 0, 0},
+#else
+ {{"des_ede3_cfb"}, {NULL}, 0, 0},
+#endif
+
+ {{"blowfish_cbc"}, {&EVP_bf_cbc}, 0, NO_FIPS_CIPHER},
+ {{"blowfish_cfb64"}, {&EVP_bf_cfb64}, 0, NO_FIPS_CIPHER},
+ {{"blowfish_ofb64"}, {&EVP_bf_ofb}, 0, NO_FIPS_CIPHER},
+ {{"blowfish_ecb"}, {&EVP_bf_ecb}, 0, NO_FIPS_CIPHER | ECB_BUG_0_9_8L},
+
+ {{"aes_cbc"}, {&EVP_aes_128_cbc}, 16, 0},
+ {{"aes_cbc"}, {&EVP_aes_192_cbc}, 24, 0},
+ {{"aes_cbc"}, {&EVP_aes_256_cbc}, 32, 0},
+
+ {{"aes_128_cbc"}, {&EVP_aes_128_cbc}, 16, 0},
+ {{"aes_192_cbc"}, {&EVP_aes_192_cbc}, 24, 0},
+ {{"aes_256_cbc"}, {&EVP_aes_256_cbc}, 32, 0},
+
+ {{"aes_cfb8"}, {&EVP_aes_128_cfb8}, 16, NO_FIPS_CIPHER | AES_CFBx},
+ {{"aes_cfb8"}, {&EVP_aes_192_cfb8}, 24, NO_FIPS_CIPHER | AES_CFBx},
+ {{"aes_cfb8"}, {&EVP_aes_256_cfb8}, 32, NO_FIPS_CIPHER | AES_CFBx},
+
+ {{"aes_cfb128"}, {&EVP_aes_128_cfb128}, 16, NO_FIPS_CIPHER | AES_CFBx},
+ {{"aes_cfb128"}, {&EVP_aes_192_cfb128}, 24, NO_FIPS_CIPHER | AES_CFBx},
+ {{"aes_cfb128"}, {&EVP_aes_256_cfb128}, 32, NO_FIPS_CIPHER | AES_CFBx},
+
+ {{"aes_ecb"}, {&EVP_aes_128_ecb}, 16, ECB_BUG_0_9_8L},
+ {{"aes_ecb"}, {&EVP_aes_192_ecb}, 24, ECB_BUG_0_9_8L},
+ {{"aes_ecb"}, {&EVP_aes_256_ecb}, 32, ECB_BUG_0_9_8L},
+
+#if defined(HAVE_EVP_AES_CTR)
+ {{"aes_128_ctr"}, {&EVP_aes_128_ctr}, 16, 0},
+ {{"aes_192_ctr"}, {&EVP_aes_192_ctr}, 24, 0},
+ {{"aes_256_ctr"}, {&EVP_aes_256_ctr}, 32, 0},
+ {{"aes_ctr"}, {&EVP_aes_128_ctr}, 16, 0},
+ {{"aes_ctr"}, {&EVP_aes_192_ctr}, 24, 0},
+ {{"aes_ctr"}, {&EVP_aes_256_ctr}, 32, 0},
+#else
+ {{"aes_128_ctr"}, {NULL}, 16, AES_CTR_COMPAT},
+ {{"aes_192_ctr"}, {NULL}, 24, AES_CTR_COMPAT},
+ {{"aes_256_ctr"}, {NULL}, 32, AES_CTR_COMPAT},
+ {{"aes_ctr"}, {NULL}, 0, AES_CTR_COMPAT},
+#endif
+
+#if defined(HAVE_CHACHA20)
+ {{"chacha20"}, {&EVP_chacha20}, 32, NO_FIPS_CIPHER},
+#else
+ {{"chacha20"}, {NULL}, 0, NO_FIPS_CIPHER},
+#endif
+
+ /*==== AEAD ciphers ====*/
+#if defined(HAVE_CHACHA20_POLY1305)
+ {{"chacha20_poly1305"}, {&EVP_chacha20_poly1305}, 0, NO_FIPS_CIPHER | AEAD_CIPHER, {{EVP_CTRL_AEAD_SET_IVLEN,EVP_CTRL_AEAD_GET_TAG,EVP_CTRL_AEAD_SET_TAG}}},
+#else
+ {{"chacha20_poly1305"}, {NULL}, 0, NO_FIPS_CIPHER | AEAD_CIPHER, {{0,0,0}}},
+#endif
+
+#if defined(HAVE_GCM)
+ {{"aes_gcm"}, {&EVP_aes_128_gcm}, 16, AEAD_CIPHER, {{EVP_CTRL_GCM_SET_IVLEN,EVP_CTRL_GCM_GET_TAG,EVP_CTRL_GCM_SET_TAG}}},
+ {{"aes_gcm"}, {&EVP_aes_192_gcm}, 24, AEAD_CIPHER, {{EVP_CTRL_GCM_SET_IVLEN,EVP_CTRL_GCM_GET_TAG,EVP_CTRL_GCM_SET_TAG}}},
+ {{"aes_gcm"}, {&EVP_aes_256_gcm}, 32, AEAD_CIPHER, {{EVP_CTRL_GCM_SET_IVLEN,EVP_CTRL_GCM_GET_TAG,EVP_CTRL_GCM_SET_TAG}}},
+ {{"aes_128_gcm"}, {&EVP_aes_128_gcm}, 16, AEAD_CIPHER, {{EVP_CTRL_GCM_SET_IVLEN,EVP_CTRL_GCM_GET_TAG,EVP_CTRL_GCM_SET_TAG}}},
+ {{"aes_192_gcm"}, {&EVP_aes_192_gcm}, 24, AEAD_CIPHER, {{EVP_CTRL_GCM_SET_IVLEN,EVP_CTRL_GCM_GET_TAG,EVP_CTRL_GCM_SET_TAG}}},
+ {{"aes_256_gcm"}, {&EVP_aes_256_gcm}, 32, AEAD_CIPHER, {{EVP_CTRL_GCM_SET_IVLEN,EVP_CTRL_GCM_GET_TAG,EVP_CTRL_GCM_SET_TAG}}},
+#else
+ {{"aes_gcm"}, {NULL}, 0, AEAD_CIPHER, {{0,0,0}}},
+ {{"aes_128_gcm"}, {NULL}, 16, AEAD_CIPHER, {{0,0,0}}},
+ {{"aes_192_gcm"}, {NULL}, 24, AEAD_CIPHER, {{0,0,0}}},
+ {{"aes_256_gcm"}, {NULL}, 32, AEAD_CIPHER, {{0,0,0}}},
+#endif
+
+#if defined(HAVE_CCM)
+ {{"aes_ccm"}, {&EVP_aes_128_ccm}, 16, AEAD_CIPHER, {{EVP_CTRL_CCM_SET_IVLEN,EVP_CTRL_CCM_GET_TAG,EVP_CTRL_CCM_SET_TAG}}},
+ {{"aes_ccm"}, {&EVP_aes_192_ccm}, 24, AEAD_CIPHER, {{EVP_CTRL_CCM_SET_IVLEN,EVP_CTRL_CCM_GET_TAG,EVP_CTRL_CCM_SET_TAG}}},
+ {{"aes_ccm"}, {&EVP_aes_256_ccm}, 32, AEAD_CIPHER, {{EVP_CTRL_CCM_SET_IVLEN,EVP_CTRL_CCM_GET_TAG,EVP_CTRL_CCM_SET_TAG}}},
+ {{"aes_128_ccm"}, {&EVP_aes_128_ccm}, 16, AEAD_CIPHER, {{EVP_CTRL_CCM_SET_IVLEN,EVP_CTRL_CCM_GET_TAG,EVP_CTRL_CCM_SET_TAG}}},
+ {{"aes_192_ccm"}, {&EVP_aes_192_ccm}, 24, AEAD_CIPHER, {{EVP_CTRL_CCM_SET_IVLEN,EVP_CTRL_CCM_GET_TAG,EVP_CTRL_CCM_SET_TAG}}},
+ {{"aes_256_ccm"}, {&EVP_aes_256_ccm}, 32, AEAD_CIPHER, {{EVP_CTRL_CCM_SET_IVLEN,EVP_CTRL_CCM_GET_TAG,EVP_CTRL_CCM_SET_TAG}}},
+#else
+ {{"aes_ccm"}, {NULL}, 0, AEAD_CIPHER, {{0,0,0}}},
+ {{"aes_128_ccm"}, {NULL}, 16, AEAD_CIPHER, {{0,0,0}}},
+ {{"aes_192_ccm"}, {NULL}, 24, AEAD_CIPHER, {{0,0,0}}},
+ {{"aes_256_ccm"}, {NULL}, 32, AEAD_CIPHER, {{0,0,0}}},
+#endif
+
+ /*==== Specialy handled ciphers, only for inclusion in algorithm's list ====*/
+#ifdef HAVE_AES_IGE
+ {{"aes_ige256"}, {NULL}, 0, NO_FIPS_CIPHER | NON_EVP_CIPHER},
+#endif
+
+ /*==== End of list ==== */
+
+ {{NULL},{NULL},0,0}
+};
+
+ErlNifResourceType* evp_cipher_ctx_rtype;
+
+static size_t num_cipher_types = 0;
+
+static void evp_cipher_ctx_dtor(ErlNifEnv* env, struct evp_cipher_ctx* ctx) {
+ if (ctx == NULL)
+ return;
+
+ if (ctx->ctx)
+ EVP_CIPHER_CTX_free(ctx->ctx);
+}
+
+int init_cipher_ctx(ErlNifEnv *env) {
+ evp_cipher_ctx_rtype = enif_open_resource_type(env, NULL, "EVP_CIPHER_CTX",
+ (ErlNifResourceDtor*) evp_cipher_ctx_dtor,
+ ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER,
+ NULL);
+ if (evp_cipher_ctx_rtype == NULL)
+ goto err;
+
+ return 1;
+
+ err:
+ PRINTF_ERR0("CRYPTO: Could not open resource type 'EVP_CIPHER_CTX'");
+ return 0;
+}
+
+void init_cipher_types(ErlNifEnv* env)
+{
+ struct cipher_type_t* p = cipher_types;
+
+ num_cipher_types = 0;
+ for (p = cipher_types; p->type.str; p++) {
+ num_cipher_types++;
+ p->type.atom = enif_make_atom(env, p->type.str);
+ if (p->cipher.funcp)
+ p->cipher.p = p->cipher.funcp();
+ }
+ p->type.atom = atom_false; /* end marker */
+
+ qsort(cipher_types, num_cipher_types, sizeof(cipher_types[0]), cmp_cipher_types);
+}
+
+const struct cipher_type_t* get_cipher_type(ERL_NIF_TERM type, size_t key_len)
+{
+ struct cipher_type_t key;
+
+ key.type.atom = type;
+ key.key_len = key_len;
+
+ return bsearch(&key, cipher_types, num_cipher_types, sizeof(cipher_types[0]), cmp_cipher_types);
+}
+
+
+int cmp_cipher_types(const void *keyp, const void *elemp) {
+ const struct cipher_type_t *key = keyp;
+ const struct cipher_type_t *elem = elemp;
+
+ if (key->type.atom < elem->type.atom) return -1;
+ else if (key->type.atom > elem->type.atom) return 1;
+ else /* key->type.atom == elem->type.atom */
+ if (!elem->key_len || key->key_len == elem->key_len) return 0;
+ else if (key->key_len < elem->key_len) return -1;
+ else return 1;
+}
+
+
+ERL_NIF_TERM cipher_types_as_list(ErlNifEnv* env)
+{
+ struct cipher_type_t* p;
+ ERL_NIF_TERM prev, hd;
+
+ hd = enif_make_list(env, 0);
+ prev = atom_undefined;
+
+ for (p = cipher_types; (p->type.atom & (p->type.atom != atom_false)); p++) {
+ if ((prev != p->type.atom) &&
+ ((p->cipher.p != NULL) ||
+ (p->flags & (NON_EVP_CIPHER|AES_CTR_COMPAT)) ) && /* Special handling. Bad indeed... */
+ ! FORBIDDEN_IN_FIPS(p)
+ )
+ hd = enif_make_list_cell(env, p->type.atom, hd);
+ prev = p->type.atom;
+ }
+
+ return hd;
+}
diff --git a/lib/crypto/c_src/cipher.h b/lib/crypto/c_src/cipher.h
new file mode 100644
index 0000000000..6b43afea99
--- /dev/null
+++ b/lib/crypto/c_src/cipher.h
@@ -0,0 +1,73 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_CIPHER_H__
+#define E_CIPHER_H__ 1
+
+#include "common.h"
+
+struct cipher_type_t {
+ union {
+ const char* str; /* before init */
+ ERL_NIF_TERM atom; /* after init */
+ }type;
+ union {
+ const EVP_CIPHER* (*funcp)(void); /* before init, NULL if notsup */
+ const EVP_CIPHER* p; /* after init, NULL if notsup */
+ }cipher;
+ size_t key_len; /* != 0 to also match on key_len */
+ unsigned flags;
+ union {
+ struct aead_ctrl {int ctx_ctrl_set_ivlen, ctx_ctrl_get_tag, ctx_ctrl_set_tag;} aead;
+ } extra;
+};
+
+/* masks in the flags field if cipher_type_t */
+#define NO_FIPS_CIPHER 1
+#define AES_CFBx 2
+#define ECB_BUG_0_9_8L 4
+#define AEAD_CIPHER 8
+#define NON_EVP_CIPHER 16
+#define AES_CTR_COMPAT 32
+
+
+#ifdef FIPS_SUPPORT
+/* May have FIPS support, must check dynamically if it is enabled */
+# define FORBIDDEN_IN_FIPS(P) (((P)->flags & NO_FIPS_CIPHER) && FIPS_mode())
+#else
+/* No FIPS support since the symbol FIPS_SUPPORT is undefined */
+# define FORBIDDEN_IN_FIPS(P) 0
+#endif
+
+extern ErlNifResourceType* evp_cipher_ctx_rtype;
+struct evp_cipher_ctx {
+ EVP_CIPHER_CTX* ctx;
+};
+
+int init_cipher_ctx(ErlNifEnv *env);
+
+void init_cipher_types(ErlNifEnv* env);
+const struct cipher_type_t* get_cipher_type(ERL_NIF_TERM type, size_t key_len);
+
+int cmp_cipher_types(const void *keyp, const void *elemp);
+
+ERL_NIF_TERM cipher_types_as_list(ErlNifEnv* env);
+
+#endif /* E_CIPHER_H__ */
diff --git a/lib/crypto/c_src/cmac.c b/lib/crypto/c_src/cmac.c
new file mode 100644
index 0000000000..49e67ccf29
--- /dev/null
+++ b/lib/crypto/c_src/cmac.c
@@ -0,0 +1,88 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "cmac.h"
+#include "cipher.h"
+
+ERL_NIF_TERM cmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Type, Key, Data) */
+#if defined(HAVE_CMAC)
+ const struct cipher_type_t *cipherp;
+ const EVP_CIPHER *cipher;
+ CMAC_CTX *ctx = NULL;
+ ErlNifBinary key;
+ ErlNifBinary data;
+ ERL_NIF_TERM ret;
+ size_t ret_size;
+ unsigned char *outp;
+ int cipher_len;
+
+ ASSERT(argc == 3);
+
+ if (!enif_inspect_iolist_as_binary(env, argv[1], &key))
+ goto bad_arg;
+ if ((cipherp = get_cipher_type(argv[0], key.size)) == NULL)
+ goto bad_arg;
+ if (cipherp->flags & (NON_EVP_CIPHER | AEAD_CIPHER))
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, argv[2], &data))
+ goto bad_arg;
+
+ if (FORBIDDEN_IN_FIPS(cipherp))
+ return enif_raise_exception(env, atom_notsup);
+ if ((cipher = cipherp->cipher.p) == NULL)
+ return enif_raise_exception(env, atom_notsup);
+
+ if ((ctx = CMAC_CTX_new()) == NULL)
+ goto err;
+ if (!CMAC_Init(ctx, key.data, key.size, cipher, NULL))
+ goto err;
+ if (!CMAC_Update(ctx, data.data, data.size))
+ goto err;
+ if ((cipher_len = EVP_CIPHER_block_size(cipher)) < 0)
+ goto err;
+ if ((outp = enif_make_new_binary(env, (size_t)cipher_len, &ret)) == NULL)
+ goto err;
+ if (!CMAC_Final(ctx, outp, &ret_size))
+ goto err;
+
+ ASSERT(ret_size == (unsigned)EVP_CIPHER_block_size(cipher));
+ CONSUME_REDS(env, data);
+ goto done;
+
+ bad_arg:
+ return enif_make_badarg(env);
+
+ err:
+ ret = atom_notsup;
+
+ done:
+ if (ctx)
+ CMAC_CTX_free(ctx);
+ return ret;
+
+#else
+ /* The CMAC functionality was introduced in OpenSSL 1.0.1
+ * Although OTP requires at least version 0.9.8, the versions 0.9.8 and 1.0.0 are
+ * no longer maintained. */
+ return atom_notsup;
+#endif
+}
+
diff --git a/lib/crypto/c_src/cmac.h b/lib/crypto/c_src/cmac.h
new file mode 100644
index 0000000000..14488def58
--- /dev/null
+++ b/lib/crypto/c_src/cmac.h
@@ -0,0 +1,28 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_CMAC_H__
+#define E_CMAC_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM cmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_CMAC_H__ */
diff --git a/lib/crypto/c_src/common.h b/lib/crypto/c_src/common.h
new file mode 100644
index 0000000000..2bc8bdd73c
--- /dev/null
+++ b/lib/crypto/c_src/common.h
@@ -0,0 +1,38 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_COMMON_H__
+#define E_COMMON_H__ 1
+
+#ifdef __WIN32__
+# include <windows.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+#include <stdint.h>
+
+#include <erl_nif.h>
+#include "openssl_config.h"
+#include "atoms.h"
+
+#endif /* E_COMMON_H__ */
diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c
index df607732bf..06439c34b2 100644
--- a/lib/crypto/c_src/crypto.c
+++ b/lib/crypto/c_src/crypto.c
@@ -23,982 +23,123 @@
* Based on OpenSSL.
*/
-#ifdef __WIN32__
- #include <windows.h>
-#endif
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-
-#include <erl_nif.h>
-
-#define OPENSSL_THREAD_DEFINES
-#include <openssl/opensslconf.h>
-
-#include <openssl/crypto.h>
-#ifndef OPENSSL_NO_DES
-#include <openssl/des.h>
-#endif /* #ifndef OPENSSL_NO_DES */
-/* #include <openssl/idea.h> This is not supported on the openssl OTP requires */
-#include <openssl/dsa.h>
-#include <openssl/rsa.h>
-#include <openssl/aes.h>
-#include <openssl/md5.h>
-#include <openssl/md4.h>
-#include <openssl/sha.h>
-#include <openssl/ripemd.h>
-#include <openssl/bn.h>
-#include <openssl/objects.h>
-#ifndef OPENSSL_NO_RC4
- #include <openssl/rc4.h>
-#endif /* OPENSSL_NO_RC4 */
-#ifndef OPENSSL_NO_RC2
- #include <openssl/rc2.h>
-#endif
-#include <openssl/blowfish.h>
-#include <openssl/rand.h>
-#include <openssl/evp.h>
-#include <openssl/hmac.h>
-#include <openssl/err.h>
-
-/* Helper macro to construct a OPENSSL_VERSION_NUMBER.
- * See openssl/opensslv.h
- */
-#define PACKED_OPENSSL_VERSION(MAJ, MIN, FIX, P) \
- ((((((((MAJ << 8) | MIN) << 8 ) | FIX) << 8) | (P-'a'+1)) << 4) | 0xf)
-
-#define PACKED_OPENSSL_VERSION_PLAIN(MAJ, MIN, FIX) \
- PACKED_OPENSSL_VERSION(MAJ,MIN,FIX,('a'-1))
-
-
-/* LibreSSL was cloned from OpenSSL 1.0.1g and claims to be API and BPI compatible
- * with 1.0.1.
- *
- * LibreSSL has the same names on include files and symbols as OpenSSL, but defines
- * the OPENSSL_VERSION_NUMBER to be >= 2.0.0
- *
- * Therefor works tests like this as intendend:
- * OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
- * (The test is for example "2.4.2" >= "1.0.0" although the test
- * with the cloned OpenSSL test would be "1.0.1" >= "1.0.0")
- *
- * But tests like this gives wrong result:
- * OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
- * (The test is false since "2.4.2" < "1.1.0". It should have been
- * true because the LibreSSL API version is "1.0.1")
- *
- */
-
-#ifdef LIBRESSL_VERSION_NUMBER
-/* A macro to test on in this file */
-#define HAS_LIBRESSL
-#endif
-
-#ifdef HAS_LIBRESSL
-/* LibreSSL dislikes FIPS */
-# ifdef FIPS_SUPPORT
-# undef FIPS_SUPPORT
-# endif
-
-# if LIBRESSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(2,7,0)
-/* LibreSSL wants the 1.0.1 API */
-# define NEED_EVP_COMPATIBILITY_FUNCTIONS
-# endif
-#endif
-
-
-#if OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
-# define NEED_EVP_COMPATIBILITY_FUNCTIONS
-#endif
-
-
-#ifndef HAS_LIBRESSL
-# if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
-# define HAS_EVP_PKEY_CTX
-# endif
-#endif
-
-
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
-#include <openssl/modes.h>
-#endif
-
-#include "crypto_callback.h"
-
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(0,9,8) \
- && !defined(OPENSSL_NO_SHA224) && defined(NID_sha224) \
- && !defined(OPENSSL_NO_SHA256) /* disabled like this in my sha.h (?) */
-# define HAVE_SHA224
-#endif
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(0,9,8) \
- && !defined(OPENSSL_NO_SHA256) && defined(NID_sha256)
-# define HAVE_SHA256
-#endif
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(0,9,8) \
- && !defined(OPENSSL_NO_SHA384) && defined(NID_sha384)\
- && !defined(OPENSSL_NO_SHA512) /* disabled like this in my sha.h (?) */
-# define HAVE_SHA384
-#endif
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(0,9,8) \
- && !defined(OPENSSL_NO_SHA512) && defined(NID_sha512)
-# define HAVE_SHA512
-#endif
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(0,9,7,'e')
-# define HAVE_DES_ede3_cfb_encrypt
-#endif
-
-// SHA3:
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,1,1)
-// An error in beta releases of 1.1.1 fixed in production release
-# ifdef NID_sha3_224
-# define HAVE_SHA3_224
-# endif
-# ifdef NID_sha3_256
-# define HAVE_SHA3_256
-# endif
-#endif
-# ifdef NID_sha3_384
-# define HAVE_SHA3_384
-# endif
-# ifdef NID_sha3_512
-# define HAVE_SHA3_512
-# endif
-
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(0,9,8,'o') \
- && !defined(OPENSSL_NO_EC) \
- && !defined(OPENSSL_NO_ECDH) \
- && !defined(OPENSSL_NO_ECDSA)
-# define HAVE_EC
-#endif
-
-// (test for >= 1.1.1pre8)
-#if OPENSSL_VERSION_NUMBER >= (PACKED_OPENSSL_VERSION_PLAIN(1,1,1) -7) \
- && !defined(HAS_LIBRESSL) \
- && defined(HAVE_EC)
-# define HAVE_ED_CURVE_DH
-# if OPENSSL_VERSION_NUMBER >= (PACKED_OPENSSL_VERSION_PLAIN(1,1,1))
-# define HAVE_EDDSA
-# endif
-#endif
-
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(0,9,8,'c')
-# define HAVE_AES_IGE
-#endif
-
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,1)
-# define HAVE_EVP_AES_CTR
-# define HAVE_AEAD
-# define HAVE_GCM
-# define HAVE_CCM
-# define HAVE_CMAC
-# if defined(RSA_PKCS1_OAEP_PADDING)
-# define HAVE_RSA_OAEP_PADDING
-# endif
-# define HAVE_RSA_MGF1_MD
-# if OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION(1,0,1,'d')
-# define HAVE_GCM_EVP_DECRYPT_BUG
-# endif
-#endif
-
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
-# ifndef HAS_LIBRESSL
-# define HAVE_CHACHA20
-# define HAVE_CHACHA20_POLY1305
-# define HAVE_RSA_OAEP_MD
-# endif
-#endif
-
-// OPENSSL_VERSION_NUMBER >= 1.1.1-pre8
-#if OPENSSL_VERSION_NUMBER >= (PACKED_OPENSSL_VERSION_PLAIN(1,1,1)-7)
-# ifndef HAS_LIBRESSL
-# define HAVE_POLY1305
-# endif
-#endif
-
-#if OPENSSL_VERSION_NUMBER <= PACKED_OPENSSL_VERSION(0,9,8,'l')
-# define HAVE_ECB_IVEC_BUG
-#endif
-
-#ifndef HAS_LIBRESSL
-# ifdef RSA_SSLV23_PADDING
-# define HAVE_RSA_SSLV23_PADDING
-# endif
-#endif
-
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
-# ifdef RSA_PKCS1_PSS_PADDING
-# define HAVE_RSA_PKCS1_PSS_PADDING
-# endif
-#endif
-
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(0,9,8,'h') \
- && defined(HAVE_EC)
-/* If OPENSSL_NO_EC is set, there will be an error in ec.h included from engine.h
- So if EC is disabled, you can't use Engine either....
-*/
-# define HAS_ENGINE_SUPPORT
-#endif
-
-
-#if defined(HAS_ENGINE_SUPPORT)
-# include <openssl/engine.h>
-#endif
-
-#if defined(HAVE_CMAC)
-#include <openssl/cmac.h>
-#endif
-
-#if defined(HAVE_EC)
-#include <openssl/ec.h>
-#include <openssl/ecdh.h>
-#include <openssl/ecdsa.h>
-#endif
-
-#ifdef VALGRIND
- # include <valgrind/memcheck.h>
-
-/* libcrypto mixes supplied buffer contents into its entropy pool,
- which makes valgrind complain about the use of uninitialized data.
- We use this valgrind "request" to make sure that no such seemingly
- undefined data is returned.
-*/
- # define ERL_VALGRIND_MAKE_MEM_DEFINED(ptr,size) \
- VALGRIND_MAKE_MEM_DEFINED(ptr,size)
-
- # define ERL_VALGRIND_ASSERT_MEM_DEFINED(Ptr,Size) \
- do { \
- int __erl_valgrind_mem_defined = VALGRIND_CHECK_MEM_IS_DEFINED((Ptr),(Size)); \
- if (__erl_valgrind_mem_defined != 0) { \
- fprintf(stderr,"\r\n####### VALGRIND_ASSSERT(%p,%ld) failed at %s:%d\r\n", \
- (Ptr),(long)(Size), __FILE__, __LINE__); \
- abort(); \
- } \
- } while (0)
-
-#else
- # define ERL_VALGRIND_MAKE_MEM_DEFINED(ptr,size)
- # define ERL_VALGRIND_ASSERT_MEM_DEFINED(ptr,size)
-#endif
-
-#ifdef DEBUG
- # define ASSERT(e) \
- ((void) ((e) ? 1 : (fprintf(stderr,"Assert '%s' failed at %s:%d\n",\
- #e, __FILE__, __LINE__), abort(), 0)))
-#else
- # define ASSERT(e) ((void) 1)
-#endif
-
-#ifdef __GNUC__
- # define INLINE __inline__
-#elif defined(__WIN32__)
- # define INLINE __forceinline
-#else
- # define INLINE
-#endif
-
-
-#define get_int32(s) ((((unsigned char*) (s))[0] << 24) | \
- (((unsigned char*) (s))[1] << 16) | \
- (((unsigned char*) (s))[2] << 8) | \
- (((unsigned char*) (s))[3]))
-
-#define put_int32(s,i) \
-{ (s)[0] = (char)(((i) >> 24) & 0xff);\
- (s)[1] = (char)(((i) >> 16) & 0xff);\
- (s)[2] = (char)(((i) >> 8) & 0xff);\
- (s)[3] = (char)((i) & 0xff);\
-}
-
-/* This shall correspond to the similar macro in crypto.erl */
-/* Current value is: erlang:system_info(context_reductions) * 10 */
-#define MAX_BYTES_TO_NIF 20000
-
-#define CONSUME_REDS(NifEnv, Ibin) \
-do { \
- int _cost = ((Ibin).size * 100) / MAX_BYTES_TO_NIF;\
- if (_cost) { \
- (void) enif_consume_timeslice((NifEnv), \
- (_cost > 100) ? 100 : _cost); \
- } \
- } while (0)
-
-
-#ifdef NEED_EVP_COMPATIBILITY_FUNCTIONS
-/*
- * In OpenSSL 1.1.0, most structs are opaque. That means that
- * the structs cannot be allocated as automatic variables on the
- * C stack (because the size is unknown) and that it is necessary
- * to use access functions.
- *
- * For backward compatibility to previous versions of OpenSSL, define
- * on our versions of the new functions defined in 1.1.0 here, so that
- * we don't have to sprinkle ifdefs throughout the code.
- */
-
-static HMAC_CTX *HMAC_CTX_new(void);
-static void HMAC_CTX_free(HMAC_CTX *ctx);
-
-static HMAC_CTX *HMAC_CTX_new()
-{
- HMAC_CTX *ctx = CRYPTO_malloc(sizeof(HMAC_CTX), __FILE__, __LINE__);
- HMAC_CTX_init(ctx);
- return ctx;
-}
-
-static void HMAC_CTX_free(HMAC_CTX *ctx)
-{
- HMAC_CTX_cleanup(ctx);
- CRYPTO_free(ctx);
-}
-
-#define EVP_MD_CTX_new() EVP_MD_CTX_create()
-#define EVP_MD_CTX_free(ctx) EVP_MD_CTX_destroy(ctx)
-
-static INLINE void *BN_GENCB_get_arg(BN_GENCB *cb);
-
-static INLINE void *BN_GENCB_get_arg(BN_GENCB *cb)
-{
- return cb->arg;
-}
-
-static INLINE int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d);
-static INLINE void RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d);
-static INLINE int RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q);
-static INLINE void RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q);
-static INLINE int RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp);
-static INLINE void RSA_get0_crt_params(const RSA *r, const BIGNUM **dmp1, const BIGNUM **dmq1, const BIGNUM **iqmp);
-
-static INLINE int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d)
-{
- r->n = n;
- r->e = e;
- r->d = d;
- return 1;
-}
-
-static INLINE void RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d)
-{
- *n = r->n;
- *e = r->e;
- *d = r->d;
-}
-
-static INLINE int RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q)
-{
- r->p = p;
- r->q = q;
- return 1;
-}
-
-static INLINE void RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q)
-{
- *p = r->p;
- *q = r->q;
-}
-
-static INLINE int RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp)
-{
- r->dmp1 = dmp1;
- r->dmq1 = dmq1;
- r->iqmp = iqmp;
- return 1;
-}
-
-static INLINE void RSA_get0_crt_params(const RSA *r, const BIGNUM **dmp1, const BIGNUM **dmq1, const BIGNUM **iqmp)
-{
- *dmp1 = r->dmp1;
- *dmq1 = r->dmq1;
- *iqmp = r->iqmp;
-}
-
-static INLINE int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key);
-static INLINE int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g);
-static INLINE void DSA_get0_pqg(const DSA *dsa,
- const BIGNUM **p, const BIGNUM **q, const BIGNUM **g);
-static INLINE void DSA_get0_key(const DSA *dsa,
- const BIGNUM **pub_key, const BIGNUM **priv_key);
-
-static INLINE int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key)
-{
- d->pub_key = pub_key;
- d->priv_key = priv_key;
- return 1;
-}
-
-static INLINE int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g)
-{
- d->p = p;
- d->q = q;
- d->g = g;
- return 1;
-}
-
-static INLINE void
-DSA_get0_pqg(const DSA *dsa, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g)
-{
- *p = dsa->p;
- *q = dsa->q;
- *g = dsa->g;
-}
-
-static INLINE void
-DSA_get0_key(const DSA *dsa, const BIGNUM **pub_key, const BIGNUM **priv_key)
-{
- if (pub_key) *pub_key = dsa->pub_key;
- if (priv_key) *priv_key = dsa->priv_key;
-}
-
-
-
-static INLINE int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key);
-static INLINE int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g);
-static INLINE int DH_set_length(DH *dh, long length);
-static INLINE void DH_get0_pqg(const DH *dh,
- const BIGNUM **p, const BIGNUM **q, const BIGNUM **g);
-static INLINE void DH_get0_key(const DH *dh,
- const BIGNUM **pub_key, const BIGNUM **priv_key);
-
-static INLINE int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key)
-{
- dh->pub_key = pub_key;
- dh->priv_key = priv_key;
- return 1;
-}
-
-static INLINE int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g)
-{
- dh->p = p;
- dh->q = q;
- dh->g = g;
- return 1;
-}
-
-static INLINE int DH_set_length(DH *dh, long length)
-{
- dh->length = length;
- return 1;
-}
-
-
-
-static INLINE void
-DH_get0_pqg(const DH *dh, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g)
-{
- *p = dh->p;
- *q = dh->q;
- *g = dh->g;
-}
-
-static INLINE void
-DH_get0_key(const DH *dh, const BIGNUM **pub_key, const BIGNUM **priv_key)
-{
- if (pub_key) *pub_key = dh->pub_key;
- if (priv_key) *priv_key = dh->priv_key;
-}
-
-#else /* End of compatibility definitions. */
-
-#define HAVE_OPAQUE_BN_GENCB
-
-#endif
+#include "common.h"
+
+#include "aead.h"
+#include "aes.h"
+#include "algorithms.h"
+#include "api_ng.h"
+#include "block.h"
+#include "bn.h"
+#include "chacha20.h"
+#include "cipher.h"
+#include "cmac.h"
+#include "dh.h"
+#include "digest.h"
+#include "dss.h"
+#include "ec.h"
+#include "ecdh.h"
+#include "eddsa.h"
+#include "engine.h"
+#include "evp.h"
+#include "fips.h"
+#include "hash.h"
+#include "hmac.h"
+#include "info.h"
+#include "math.h"
+#include "pkey.h"
+#include "poly1305.h"
+#include "rand.h"
+#include "rc4.h"
+#include "rsa.h"
+#include "srp.h"
/* 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);
-/* The NIFs: */
-static ERL_NIF_TERM info_lib(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM info_fips(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM enable_fips_mode(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM hash_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM hash_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM hash_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM hash_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM hmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM hmac_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM hmac_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM hmac_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM cmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM block_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM aes_cfb_8_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM aes_cfb_128_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM aes_ige_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM aes_ctr_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM aes_ctr_stream_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM strong_rand_bytes_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM strong_rand_range_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM rand_uniform_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM mod_exp_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM do_exor(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM rc4_set_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM rc4_encrypt_with_state(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM pkey_sign_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM pkey_verify_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM pkey_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM rsa_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM dh_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM dh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM privkey_to_pubkey_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM srp_value_B_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM srp_user_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM srp_host_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-
-static ERL_NIF_TERM ec_key_generate(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM ecdh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-
-static ERL_NIF_TERM evp_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM evp_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-
-static ERL_NIF_TERM rand_seed_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-
-static ERL_NIF_TERM aead_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM aead_decrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-#ifdef HAVE_GCM_EVP_DECRYPT_BUG
-static ERL_NIF_TERM aes_gcm_decrypt_NO_EVP(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-#endif
-
-static ERL_NIF_TERM chacha20_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM chacha20_stream_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-
-static ERL_NIF_TERM poly1305_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-
-static ERL_NIF_TERM engine_by_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_finish_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_free_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_load_dynamic_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_ctrl_cmd_strings_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_register_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_unregister_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_add_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_remove_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_get_first_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_get_next_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_get_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_get_name_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_get_all_methods_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-
-/* helpers */
-static void init_algorithms_types(ErlNifEnv*);
-static void init_digest_types(ErlNifEnv* env);
-static void init_cipher_types(ErlNifEnv* env);
-#ifdef HAVE_EC
-static EC_KEY* ec_key_new(ErlNifEnv* env, ERL_NIF_TERM curve_arg);
-static int term2point(ErlNifEnv* env, ERL_NIF_TERM term,
- EC_GROUP *group, EC_POINT **pptr);
-#endif
-static ERL_NIF_TERM bin_from_bn(ErlNifEnv* env, const BIGNUM *bn);
-
-#ifdef HAS_ENGINE_SUPPORT
-static int get_engine_load_cmd_list(ErlNifEnv* env, const ERL_NIF_TERM term, char **cmds, int i);
-static int zero_terminate(ErlNifBinary bin, char **buf);
-#endif
-
static int library_refc = 0; /* number of users of this dynamic library */
static int library_initialized = 0;
static ErlNifFunc nif_funcs[] = {
- {"info_lib", 0, info_lib},
- {"info_fips", 0, info_fips},
- {"enable_fips_mode", 1, enable_fips_mode},
- {"algorithms", 0, algorithms},
- {"hash_nif", 2, hash_nif},
- {"hash_init_nif", 1, hash_init_nif},
- {"hash_update_nif", 2, hash_update_nif},
- {"hash_final_nif", 1, hash_final_nif},
- {"hmac_nif", 3, hmac_nif},
- {"hmac_nif", 4, hmac_nif},
- {"hmac_init_nif", 2, hmac_init_nif},
- {"hmac_update_nif", 2, hmac_update_nif},
- {"hmac_final_nif", 1, hmac_final_nif},
- {"hmac_final_nif", 2, hmac_final_nif},
- {"cmac_nif", 3, cmac_nif},
- {"block_crypt_nif", 5, block_crypt_nif},
- {"block_crypt_nif", 4, block_crypt_nif},
- {"aes_ige_crypt_nif", 4, aes_ige_crypt_nif},
- {"aes_ctr_stream_init", 2, aes_ctr_stream_init},
- {"aes_ctr_stream_encrypt", 2, aes_ctr_stream_encrypt},
- {"aes_ctr_stream_decrypt", 2, aes_ctr_stream_encrypt},
- {"strong_rand_bytes_nif", 1, strong_rand_bytes_nif},
- {"strong_rand_range_nif", 1, strong_rand_range_nif},
- {"rand_uniform_nif", 2, rand_uniform_nif},
- {"mod_exp_nif", 4, mod_exp_nif},
- {"do_exor", 2, do_exor},
- {"rc4_set_key", 1, rc4_set_key},
- {"rc4_encrypt_with_state", 2, rc4_encrypt_with_state},
- {"pkey_sign_nif", 5, pkey_sign_nif},
- {"pkey_verify_nif", 6, pkey_verify_nif},
- {"pkey_crypt_nif", 6, pkey_crypt_nif},
- {"rsa_generate_key_nif", 2, rsa_generate_key_nif},
- {"dh_generate_key_nif", 4, dh_generate_key_nif},
- {"dh_compute_key_nif", 3, dh_compute_key_nif},
- {"evp_compute_key_nif", 3, evp_compute_key_nif},
- {"evp_generate_key_nif", 1, evp_generate_key_nif},
- {"privkey_to_pubkey_nif", 2, privkey_to_pubkey_nif},
- {"srp_value_B_nif", 5, srp_value_B_nif},
- {"srp_user_secret_nif", 7, srp_user_secret_nif},
- {"srp_host_secret_nif", 5, srp_host_secret_nif},
-
- {"ec_key_generate", 2, ec_key_generate},
- {"ecdh_compute_key_nif", 3, ecdh_compute_key_nif},
-
- {"rand_seed_nif", 1, rand_seed_nif},
-
- {"aead_encrypt", 6, aead_encrypt},
- {"aead_decrypt", 6, aead_decrypt},
-
- {"chacha20_stream_init", 2, chacha20_stream_init},
- {"chacha20_stream_encrypt", 2, chacha20_stream_crypt},
- {"chacha20_stream_decrypt", 2, chacha20_stream_crypt},
-
- {"poly1305_nif", 2, poly1305_nif},
-
- {"engine_by_id_nif", 1, engine_by_id_nif},
- {"engine_init_nif", 1, engine_init_nif},
- {"engine_finish_nif", 1, engine_finish_nif},
- {"engine_free_nif", 1, engine_free_nif},
- {"engine_load_dynamic_nif", 0, engine_load_dynamic_nif},
- {"engine_ctrl_cmd_strings_nif", 3, engine_ctrl_cmd_strings_nif},
- {"engine_register_nif", 2, engine_register_nif},
- {"engine_unregister_nif", 2, engine_unregister_nif},
- {"engine_add_nif", 1, engine_add_nif},
- {"engine_remove_nif", 1, engine_remove_nif},
- {"engine_get_first_nif", 0, engine_get_first_nif},
- {"engine_get_next_nif", 1, engine_get_next_nif},
- {"engine_get_id_nif", 1, engine_get_id_nif},
- {"engine_get_name_nif", 1, engine_get_name_nif},
- {"engine_get_all_methods_nif", 0, engine_get_all_methods_nif}
-
+ {"info_lib", 0, info_lib, 0},
+ {"info_fips", 0, info_fips, 0},
+ {"enable_fips_mode", 1, enable_fips_mode, 0},
+ {"algorithms", 0, algorithms, 0},
+ {"hash_nif", 2, hash_nif, 0},
+ {"hash_init_nif", 1, hash_init_nif, 0},
+ {"hash_update_nif", 2, hash_update_nif, 0},
+ {"hash_final_nif", 1, hash_final_nif, 0},
+ {"hmac_nif", 3, hmac_nif, 0},
+ {"hmac_nif", 4, hmac_nif, 0},
+ {"hmac_init_nif", 2, hmac_init_nif, 0},
+ {"hmac_update_nif", 2, hmac_update_nif, 0},
+ {"hmac_final_nif", 1, hmac_final_nif, 0},
+ {"hmac_final_nif", 2, hmac_final_nif, 0},
+ {"cmac_nif", 3, cmac_nif, 0},
+ {"block_crypt_nif", 5, block_crypt_nif, 0},
+ {"block_crypt_nif", 4, block_crypt_nif, 0},
+ {"aes_ige_crypt_nif", 4, aes_ige_crypt_nif, 0},
+ {"aes_ctr_stream_init", 2, aes_ctr_stream_init, 0},
+ {"aes_ctr_stream_encrypt", 2, aes_ctr_stream_encrypt, 0},
+ {"aes_ctr_stream_decrypt", 2, aes_ctr_stream_encrypt, 0},
+ {"ng_crypto_init_nif", 4, ng_crypto_init_nif, 0},
+ {"ng_crypto_update_nif", 2, ng_crypto_update_nif, 0},
+ {"ng_crypto_update_nif", 3, ng_crypto_update_nif, 0},
+ {"strong_rand_bytes_nif", 1, strong_rand_bytes_nif, 0},
+ {"strong_rand_range_nif", 1, strong_rand_range_nif, 0},
+ {"rand_uniform_nif", 2, rand_uniform_nif, 0},
+ {"mod_exp_nif", 4, mod_exp_nif, 0},
+ {"do_exor", 2, do_exor, 0},
+ {"rc4_set_key", 1, rc4_set_key, 0},
+ {"rc4_encrypt_with_state", 2, rc4_encrypt_with_state, 0},
+ {"pkey_sign_nif", 5, pkey_sign_nif, 0},
+ {"pkey_verify_nif", 6, pkey_verify_nif, 0},
+ {"pkey_crypt_nif", 6, pkey_crypt_nif, 0},
+ {"rsa_generate_key_nif", 2, rsa_generate_key_nif, 0},
+ {"dh_generate_key_nif", 4, dh_generate_key_nif, 0},
+ {"dh_compute_key_nif", 3, dh_compute_key_nif, 0},
+ {"evp_compute_key_nif", 3, evp_compute_key_nif, 0},
+ {"evp_generate_key_nif", 1, evp_generate_key_nif, 0},
+ {"privkey_to_pubkey_nif", 2, privkey_to_pubkey_nif, 0},
+ {"srp_value_B_nif", 5, srp_value_B_nif, 0},
+ {"srp_user_secret_nif", 7, srp_user_secret_nif, 0},
+ {"srp_host_secret_nif", 5, srp_host_secret_nif, 0},
+
+ {"ec_key_generate", 2, ec_key_generate, 0},
+ {"ecdh_compute_key_nif", 3, ecdh_compute_key_nif, 0},
+
+ {"rand_seed_nif", 1, rand_seed_nif, 0},
+
+ {"aead_encrypt", 6, aead_encrypt, 0},
+ {"aead_decrypt", 6, aead_decrypt, 0},
+
+ {"chacha20_stream_init", 2, chacha20_stream_init, 0},
+ {"chacha20_stream_encrypt", 2, chacha20_stream_crypt, 0},
+ {"chacha20_stream_decrypt", 2, chacha20_stream_crypt, 0},
+
+ {"poly1305_nif", 2, poly1305_nif, 0},
+
+ {"engine_by_id_nif", 1, engine_by_id_nif, 0},
+ {"engine_init_nif", 1, engine_init_nif, 0},
+ {"engine_finish_nif", 1, engine_finish_nif, 0},
+ {"engine_free_nif", 1, engine_free_nif, 0},
+ {"engine_load_dynamic_nif", 0, engine_load_dynamic_nif, 0},
+ {"engine_ctrl_cmd_strings_nif", 3, engine_ctrl_cmd_strings_nif, 0},
+ {"engine_register_nif", 2, engine_register_nif, 0},
+ {"engine_unregister_nif", 2, engine_unregister_nif, 0},
+ {"engine_add_nif", 1, engine_add_nif, 0},
+ {"engine_remove_nif", 1, engine_remove_nif, 0},
+ {"engine_get_first_nif", 0, engine_get_first_nif, 0},
+ {"engine_get_next_nif", 1, engine_get_next_nif, 0},
+ {"engine_get_id_nif", 1, engine_get_id_nif, 0},
+ {"engine_get_name_nif", 1, engine_get_name_nif, 0},
+ {"engine_get_all_methods_nif", 0, engine_get_all_methods_nif, 0}
};
ERL_NIF_INIT(crypto,nif_funcs,load,NULL,upgrade,unload)
-#define MD5_CTX_LEN (sizeof(MD5_CTX))
-#define MD4_CTX_LEN (sizeof(MD4_CTX))
-#define RIPEMD160_CTX_LEN (sizeof(RIPEMD160_CTX))
-
-
-static ERL_NIF_TERM atom_true;
-static ERL_NIF_TERM atom_false;
-static ERL_NIF_TERM atom_sha;
-static ERL_NIF_TERM atom_error;
-static ERL_NIF_TERM atom_rsa_pkcs1_padding;
-static ERL_NIF_TERM atom_rsa_pkcs1_oaep_padding;
-static ERL_NIF_TERM atom_rsa_no_padding;
-static ERL_NIF_TERM atom_signature_md;
-static ERL_NIF_TERM atom_undefined;
-
-static ERL_NIF_TERM atom_ok;
-static ERL_NIF_TERM atom_not_prime;
-static ERL_NIF_TERM atom_not_strong_prime;
-static ERL_NIF_TERM atom_unable_to_check_generator;
-static ERL_NIF_TERM atom_not_suitable_generator;
-static ERL_NIF_TERM atom_check_failed;
-static ERL_NIF_TERM atom_unknown;
-static ERL_NIF_TERM atom_none;
-static ERL_NIF_TERM atom_notsup;
-static ERL_NIF_TERM atom_digest;
-#ifdef FIPS_SUPPORT
-static ERL_NIF_TERM atom_enabled;
-static ERL_NIF_TERM atom_not_enabled;
-#else
-static ERL_NIF_TERM atom_not_supported;
-#endif
-
-#if defined(HAVE_EC)
-static ERL_NIF_TERM atom_ec;
-static ERL_NIF_TERM atom_prime_field;
-static ERL_NIF_TERM atom_characteristic_two_field;
-static ERL_NIF_TERM atom_tpbasis;
-static ERL_NIF_TERM atom_ppbasis;
-static ERL_NIF_TERM atom_onbasis;
-#endif
-
-static ERL_NIF_TERM atom_aes_cfb8;
-static ERL_NIF_TERM atom_aes_cfb128;
-#ifdef HAVE_GCM
-static ERL_NIF_TERM atom_aes_gcm;
-#endif
-#ifdef HAVE_CCM
-static ERL_NIF_TERM atom_aes_ccm;
-#endif
-#ifdef HAVE_CHACHA20_POLY1305
-static ERL_NIF_TERM atom_chacha20_poly1305;
-#endif
-#ifdef HAVE_ECB_IVEC_BUG
-static ERL_NIF_TERM atom_aes_ecb;
-static ERL_NIF_TERM atom_des_ecb;
-static ERL_NIF_TERM atom_blowfish_ecb;
-#endif
-
-static ERL_NIF_TERM atom_rsa;
-static ERL_NIF_TERM atom_dss;
-static ERL_NIF_TERM atom_ecdsa;
-
-#ifdef HAVE_ED_CURVE_DH
-static ERL_NIF_TERM atom_x25519;
-static ERL_NIF_TERM atom_x448;
-#endif
-
-static ERL_NIF_TERM atom_eddsa;
-#ifdef HAVE_EDDSA
-static ERL_NIF_TERM atom_ed25519;
-static ERL_NIF_TERM atom_ed448;
-#endif
-
-static ERL_NIF_TERM atom_rsa_mgf1_md;
-static ERL_NIF_TERM atom_rsa_oaep_label;
-static ERL_NIF_TERM atom_rsa_oaep_md;
-static ERL_NIF_TERM atom_rsa_pad; /* backwards compatibility */
-static ERL_NIF_TERM atom_rsa_padding;
-static ERL_NIF_TERM atom_rsa_pkcs1_pss_padding;
-#ifdef HAVE_RSA_SSLV23_PADDING
-static ERL_NIF_TERM atom_rsa_sslv23_padding;
-#endif
-static ERL_NIF_TERM atom_rsa_x931_padding;
-static ERL_NIF_TERM atom_rsa_pss_saltlen;
-static ERL_NIF_TERM atom_sha224;
-static ERL_NIF_TERM atom_sha256;
-static ERL_NIF_TERM atom_sha384;
-static ERL_NIF_TERM atom_sha512;
-static ERL_NIF_TERM atom_sha3_224;
-static ERL_NIF_TERM atom_sha3_256;
-static ERL_NIF_TERM atom_sha3_384;
-static ERL_NIF_TERM atom_sha3_512;
-static ERL_NIF_TERM atom_md5;
-static ERL_NIF_TERM atom_ripemd160;
-
-#ifdef HAS_ENGINE_SUPPORT
-static ERL_NIF_TERM atom_bad_engine_method;
-static ERL_NIF_TERM atom_bad_engine_id;
-static ERL_NIF_TERM atom_ctrl_cmd_failed;
-static ERL_NIF_TERM atom_engine_init_failed;
-static ERL_NIF_TERM atom_register_engine_failed;
-static ERL_NIF_TERM atom_add_engine_failed;
-static ERL_NIF_TERM atom_remove_engine_failed;
-static ERL_NIF_TERM atom_engine_method_not_supported;
-
-static ERL_NIF_TERM atom_engine_method_rsa;
-static ERL_NIF_TERM atom_engine_method_dsa;
-static ERL_NIF_TERM atom_engine_method_dh;
-static ERL_NIF_TERM atom_engine_method_rand;
-static ERL_NIF_TERM atom_engine_method_ecdh;
-static ERL_NIF_TERM atom_engine_method_ecdsa;
-static ERL_NIF_TERM atom_engine_method_ciphers;
-static ERL_NIF_TERM atom_engine_method_digests;
-static ERL_NIF_TERM atom_engine_method_store;
-static ERL_NIF_TERM atom_engine_method_pkey_meths;
-static ERL_NIF_TERM atom_engine_method_pkey_asn1_meths;
-static ERL_NIF_TERM atom_engine_method_ec;
-
-static ERL_NIF_TERM atom_engine;
-static ERL_NIF_TERM atom_key_id;
-static ERL_NIF_TERM atom_password;
-#endif
-
-static ErlNifResourceType* hmac_context_rtype;
-struct hmac_context
-{
- ErlNifMutex* mtx;
- int alive;
- HMAC_CTX* ctx;
-};
-static void hmac_context_dtor(ErlNifEnv* env, struct hmac_context*);
-
-struct digest_type_t {
- union {
- const char* str; /* before init, NULL for end-of-table */
- ERL_NIF_TERM atom; /* after init, 'false' for end-of-table */
- }type;
- union {
- const EVP_MD* (*funcp)(void); /* before init, NULL if notsup */
- const EVP_MD* p; /* after init, NULL if notsup */
- }md;
-};
-
-static struct digest_type_t digest_types[] =
-{
- {{"md4"}, {&EVP_md4}},
- {{"md5"}, {&EVP_md5}},
- {{"ripemd160"}, {&EVP_ripemd160}},
- {{"sha"}, {&EVP_sha1}},
- {{"sha224"},
-#ifdef HAVE_SHA224
- {&EVP_sha224}
-#else
- {NULL}
-#endif
- },
- {{"sha256"},
-#ifdef HAVE_SHA256
- {&EVP_sha256}
-#else
- {NULL}
-#endif
- },
- {{"sha384"},
-#ifdef HAVE_SHA384
- {&EVP_sha384}
-#else
- {NULL}
-#endif
- },
- {{"sha512"},
-#ifdef HAVE_SHA512
- {&EVP_sha512}
-#else
- {NULL}
-#endif
- },
- {{"sha3_224"},
-#ifdef HAVE_SHA3_224
- {&EVP_sha3_224}
-#else
- {NULL}
-#endif
- },
- {{"sha3_256"},
-#ifdef HAVE_SHA3_256
- {&EVP_sha3_256}
-#else
- {NULL}
-#endif
- },
- {{"sha3_384"},
-#ifdef HAVE_SHA3_384
- {&EVP_sha3_384}
-#else
- {NULL}
-#endif
- },
- {{"sha3_512"},
-#ifdef HAVE_SHA3_512
- {&EVP_sha3_512}
-#else
- {NULL}
-#endif
- },
-
- {{NULL}}
-};
-
-static struct digest_type_t* get_digest_type(ERL_NIF_TERM type);
-
-struct cipher_type_t {
- union {
- const char* str; /* before init */
- ERL_NIF_TERM atom; /* after init */
- }type;
- union {
- const EVP_CIPHER* (*funcp)(void); /* before init, NULL if notsup */
- const EVP_CIPHER* p; /* after init, NULL if notsup */
- }cipher;
- const size_t key_len; /* != 0 to also match on key_len */
-};
-
-#ifdef OPENSSL_NO_DES
-#define COND_NO_DES_PTR(Ptr) (NULL)
-#else
-#define COND_NO_DES_PTR(Ptr) (Ptr)
-#endif
-
-static struct cipher_type_t cipher_types[] =
-{
- {{"rc2_cbc"},
-#ifndef OPENSSL_NO_RC2
- {&EVP_rc2_cbc}
-#else
- {NULL}
-#endif
- },
- {{"des_cbc"}, {COND_NO_DES_PTR(&EVP_des_cbc)}},
- {{"des_cfb"}, {COND_NO_DES_PTR(&EVP_des_cfb8)}},
- {{"des_ecb"}, {COND_NO_DES_PTR(&EVP_des_ecb)}},
- {{"des_ede3_cbc"}, {COND_NO_DES_PTR(&EVP_des_ede3_cbc)}},
- {{"des_ede3_cbf"}, /* Misspelled, retained */
-#ifdef HAVE_DES_ede3_cfb_encrypt
- {COND_NO_DES_PTR(&EVP_des_ede3_cfb8)}
-#else
- {NULL}
-#endif
- },
- {{"des_ede3_cfb"},
-#ifdef HAVE_DES_ede3_cfb_encrypt
- {COND_NO_DES_PTR(&EVP_des_ede3_cfb8)}
-#else
- {NULL}
-#endif
- },
- {{"blowfish_cbc"}, {&EVP_bf_cbc}},
- {{"blowfish_cfb64"}, {&EVP_bf_cfb64}},
- {{"blowfish_ofb64"}, {&EVP_bf_ofb}},
- {{"blowfish_ecb"}, {&EVP_bf_ecb}},
- {{"aes_cbc"}, {&EVP_aes_128_cbc}, 16},
- {{"aes_cbc"}, {&EVP_aes_192_cbc}, 24},
- {{"aes_cbc"}, {&EVP_aes_256_cbc}, 32},
- {{"aes_cbc128"}, {&EVP_aes_128_cbc}},
- {{"aes_cbc256"}, {&EVP_aes_256_cbc}},
- {{"aes_cfb8"}, {&EVP_aes_128_cfb8}},
- {{"aes_cfb128"}, {&EVP_aes_128_cfb128}},
- {{"aes_ecb"}, {&EVP_aes_128_ecb}, 16},
- {{"aes_ecb"}, {&EVP_aes_192_ecb}, 24},
- {{"aes_ecb"}, {&EVP_aes_256_ecb}, 32},
- {{NULL}}
-};
-
-static struct cipher_type_t* get_cipher_type(ERL_NIF_TERM type, size_t key_len);
-
-
-/*
-#define PRINTF_ERR0(FMT) enif_fprintf(stderr, FMT "\n")
-#define PRINTF_ERR1(FMT, A1) enif_fprintf(stderr, FMT "\n", A1)
-#define PRINTF_ERR2(FMT, A1, A2) enif_fprintf(stderr, FMT "\n", A1, A2)
-*/
-
-#define PRINTF_ERR0(FMT)
-#define PRINTF_ERR1(FMT,A1)
-#define PRINTF_ERR2(FMT,A1,A2)
-
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
-/* Define resource types for OpenSSL context structures. */
-static ErlNifResourceType* evp_md_ctx_rtype;
-struct evp_md_ctx {
- EVP_MD_CTX* ctx;
-};
-static void evp_md_ctx_dtor(ErlNifEnv* env, struct evp_md_ctx *ctx) {
- EVP_MD_CTX_free(ctx->ctx);
-}
-#endif
-
-#ifdef HAVE_EVP_AES_CTR
-static ErlNifResourceType* evp_cipher_ctx_rtype;
-struct evp_cipher_ctx {
- EVP_CIPHER_CTX* ctx;
-};
-static void evp_cipher_ctx_dtor(ErlNifEnv* env, struct evp_cipher_ctx* ctx) {
- EVP_CIPHER_CTX_free(ctx->ctx);
-}
-#endif
-
-// Engine
-#ifdef HAS_ENGINE_SUPPORT
-static ErlNifResourceType* engine_ctx_rtype;
-struct engine_ctx {
- ENGINE *engine;
- char *id;
-};
-static void engine_ctx_dtor(ErlNifEnv* env, struct engine_ctx* ctx) {
- PRINTF_ERR0("engine_ctx_dtor");
- if(ctx->id) {
- PRINTF_ERR1(" non empty ctx->id=%s", ctx->id);
- enif_free(ctx->id);
- } else
- PRINTF_ERR0(" empty ctx->id=NULL");
-}
-#endif
static int verify_lib_version(void)
{
@@ -1015,46 +156,6 @@ static int verify_lib_version(void)
return 1;
}
-#ifdef FIPS_SUPPORT
-/* In FIPS mode non-FIPS algorithms are disabled and return badarg. */
-#define CHECK_NO_FIPS_MODE() { if (FIPS_mode()) return atom_notsup; }
-#else
-#define CHECK_NO_FIPS_MODE()
-#endif
-
-#ifdef HAVE_DYNAMIC_CRYPTO_LIB
-
-# if defined(DEBUG)
-static char crypto_callback_name[] = "crypto_callback.debug";
-# elif defined(VALGRIND)
-static char crypto_callback_name[] = "crypto_callback.valgrind";
-# else
-static char crypto_callback_name[] = "crypto_callback";
-# endif
-
-static int change_basename(ErlNifBinary* bin, char* buf, int bufsz, const char* newfile)
-{
- int i;
-
- for (i = bin->size; i > 0; i--) {
- if (bin->data[i-1] == '/')
- break;
- }
- if (i + strlen(newfile) >= bufsz) {
- PRINTF_ERR0("CRYPTO: lib name too long");
- return 0;
- }
- memcpy(buf, bin->data, i);
- strcpy(buf+i, newfile);
- return 1;
-}
-
-static void error_handler(void* null, const char* errstr)
-{
- PRINTF_ERR1("CRYPTO LOADING ERROR: '%s'", errstr);
-}
-#endif /* HAVE_DYNAMIC_CRYPTO_LIB */
-
static int initialize(ErlNifEnv* env, ERL_NIF_TERM load_info)
{
#ifdef OPENSSL_THREADS
@@ -1068,59 +169,37 @@ static int initialize(ErlNifEnv* env, ERL_NIF_TERM load_info)
int vernum;
ErlNifBinary lib_bin;
char lib_buf[1000];
+#ifdef HAVE_DYNAMIC_CRYPTO_LIB
+ void *handle;
+#endif
if (!verify_lib_version())
return __LINE__;
/* load_info: {302, <<"/full/path/of/this/library">>,true|false} */
- if (!enif_get_tuple(env, load_info, &tpl_arity, &tpl_array)
- || tpl_arity != 3
- || !enif_get_int(env, tpl_array[0], &vernum)
- || vernum != 302
- || !enif_inspect_binary(env, tpl_array[1], &lib_bin)) {
-
- PRINTF_ERR1("CRYPTO: Invalid load_info '%T'", load_info);
- return __LINE__;
- }
+ if (!enif_get_tuple(env, load_info, &tpl_arity, &tpl_array))
+ return __LINE__;
+ if (tpl_arity != 3)
+ return __LINE__;
+ if (!enif_get_int(env, tpl_array[0], &vernum))
+ return __LINE__;
+ if (vernum != 302)
+ return __LINE__;
+ if (!enif_inspect_binary(env, tpl_array[1], &lib_bin))
+ return __LINE__;
- hmac_context_rtype = enif_open_resource_type(env, NULL, "hmac_context",
- (ErlNifResourceDtor*) hmac_context_dtor,
- ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER,
- NULL);
- if (!hmac_context_rtype) {
- PRINTF_ERR0("CRYPTO: Could not open resource type 'hmac_context'");
+ if (!init_hmac_ctx(env)) {
return __LINE__;
}
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
- evp_md_ctx_rtype = enif_open_resource_type(env, NULL, "EVP_MD_CTX",
- (ErlNifResourceDtor*) evp_md_ctx_dtor,
- ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER,
- NULL);
- if (!evp_md_ctx_rtype) {
- PRINTF_ERR0("CRYPTO: Could not open resource type 'EVP_MD_CTX'");
+ if (!init_hash_ctx(env)) {
return __LINE__;
}
-#endif
-#ifdef HAVE_EVP_AES_CTR
- evp_cipher_ctx_rtype = enif_open_resource_type(env, NULL, "EVP_CIPHER_CTX",
- (ErlNifResourceDtor*) evp_cipher_ctx_dtor,
- ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER,
- NULL);
- if (!evp_cipher_ctx_rtype) {
- PRINTF_ERR0("CRYPTO: Could not open resource type 'EVP_CIPHER_CTX'");
+ if (!init_cipher_ctx(env)) {
return __LINE__;
}
-#endif
-#ifdef HAS_ENGINE_SUPPORT
- engine_ctx_rtype = enif_open_resource_type(env, NULL, "ENGINE_CTX",
- (ErlNifResourceDtor*) engine_ctx_dtor,
- ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER,
- NULL);
- if (!engine_ctx_rtype) {
- PRINTF_ERR0("CRYPTO: Could not open resource type 'ENGINE_CTX'");
+ if (!init_engine_ctx(env)) {
return __LINE__;
}
-#endif
if (library_initialized) {
/* Repeated loading of this library (module upgrade).
@@ -1129,149 +208,18 @@ static int initialize(ErlNifEnv* env, ERL_NIF_TERM load_info)
return 0;
}
- atom_true = enif_make_atom(env,"true");
- atom_false = enif_make_atom(env,"false");
- /* Enter FIPS mode */
- if (tpl_array[2] == atom_true) {
-#ifdef FIPS_SUPPORT
- if (!FIPS_mode_set(1)) {
-#else
- {
-#endif
- PRINTF_ERR0("CRYPTO: Could not setup FIPS mode");
- return 0;
- }
- } else if (tpl_array[2] != atom_false) {
- PRINTF_ERR1("CRYPTO: Invalid load_info '%T'", load_info);
- return 0;
+ if (!init_atoms(env, tpl_array[2], load_info)) {
+ return __LINE__;
}
- atom_sha = enif_make_atom(env,"sha");
- atom_error = enif_make_atom(env,"error");
- atom_rsa_pkcs1_padding = enif_make_atom(env,"rsa_pkcs1_padding");
- atom_rsa_pkcs1_oaep_padding = enif_make_atom(env,"rsa_pkcs1_oaep_padding");
- atom_rsa_no_padding = enif_make_atom(env,"rsa_no_padding");
- atom_signature_md = enif_make_atom(env,"signature_md");
- atom_undefined = enif_make_atom(env,"undefined");
- atom_ok = enif_make_atom(env,"ok");
- atom_not_prime = enif_make_atom(env,"not_prime");
- atom_not_strong_prime = enif_make_atom(env,"not_strong_prime");
- atom_unable_to_check_generator = enif_make_atom(env,"unable_to_check_generator");
- atom_not_suitable_generator = enif_make_atom(env,"not_suitable_generator");
- atom_check_failed = enif_make_atom(env,"check_failed");
- atom_unknown = enif_make_atom(env,"unknown");
- atom_none = enif_make_atom(env,"none");
- atom_notsup = enif_make_atom(env,"notsup");
- atom_digest = enif_make_atom(env,"digest");
-
-#if defined(HAVE_EC)
- atom_ec = enif_make_atom(env,"ec");
- atom_prime_field = enif_make_atom(env,"prime_field");
- atom_characteristic_two_field = enif_make_atom(env,"characteristic_two_field");
- atom_tpbasis = enif_make_atom(env,"tpbasis");
- atom_ppbasis = enif_make_atom(env,"ppbasis");
- atom_onbasis = enif_make_atom(env,"onbasis");
-#endif
-
- atom_aes_cfb8 = enif_make_atom(env, "aes_cfb8");
- atom_aes_cfb128 = enif_make_atom(env, "aes_cfb128");
-#ifdef HAVE_GCM
- atom_aes_gcm = enif_make_atom(env, "aes_gcm");
-#endif
-#ifdef HAVE_CCM
- atom_aes_ccm = enif_make_atom(env, "aes_ccm");
-#endif
-#ifdef HAVE_CHACHA20_POLY1305
- atom_chacha20_poly1305 = enif_make_atom(env,"chacha20_poly1305");
-#endif
-#ifdef HAVE_ECB_IVEC_BUG
- atom_aes_ecb = enif_make_atom(env, "aes_ecb");
- atom_des_ecb = enif_make_atom(env, "des_ecb");
- atom_blowfish_ecb = enif_make_atom(env, "blowfish_ecb");
-#endif
-
-#ifdef FIPS_SUPPORT
- atom_enabled = enif_make_atom(env,"enabled");
- atom_not_enabled = enif_make_atom(env,"not_enabled");
-#else
- atom_not_supported = enif_make_atom(env,"not_supported");
-#endif
- atom_rsa = enif_make_atom(env,"rsa");
- atom_dss = enif_make_atom(env,"dss");
- atom_ecdsa = enif_make_atom(env,"ecdsa");
-#ifdef HAVE_ED_CURVE_DH
- atom_x25519 = enif_make_atom(env,"x25519");
- atom_x448 = enif_make_atom(env,"x448");
-#endif
- atom_eddsa = enif_make_atom(env,"eddsa");
-#ifdef HAVE_EDDSA
- atom_ed25519 = enif_make_atom(env,"ed25519");
- atom_ed448 = enif_make_atom(env,"ed448");
-#endif
- atom_rsa_mgf1_md = enif_make_atom(env,"rsa_mgf1_md");
- atom_rsa_oaep_label = enif_make_atom(env,"rsa_oaep_label");
- atom_rsa_oaep_md = enif_make_atom(env,"rsa_oaep_md");
- atom_rsa_pad = enif_make_atom(env,"rsa_pad"); /* backwards compatibility */
- atom_rsa_padding = enif_make_atom(env,"rsa_padding");
- atom_rsa_pkcs1_pss_padding = enif_make_atom(env,"rsa_pkcs1_pss_padding");
-#ifdef HAVE_RSA_SSLV23_PADDING
- atom_rsa_sslv23_padding = enif_make_atom(env,"rsa_sslv23_padding");
-#endif
- atom_rsa_x931_padding = enif_make_atom(env,"rsa_x931_padding");
- atom_rsa_pss_saltlen = enif_make_atom(env,"rsa_pss_saltlen");
- atom_sha224 = enif_make_atom(env,"sha224");
- atom_sha256 = enif_make_atom(env,"sha256");
- atom_sha384 = enif_make_atom(env,"sha384");
- atom_sha512 = enif_make_atom(env,"sha512");
- atom_sha3_224 = enif_make_atom(env,"sha3_224");
- atom_sha3_256 = enif_make_atom(env,"sha3_256");
- atom_sha3_384 = enif_make_atom(env,"sha3_384");
- atom_sha3_512 = enif_make_atom(env,"sha3_512");
- atom_md5 = enif_make_atom(env,"md5");
- atom_ripemd160 = enif_make_atom(env,"ripemd160");
-
-#ifdef HAS_ENGINE_SUPPORT
- atom_bad_engine_method = enif_make_atom(env,"bad_engine_method");
- atom_bad_engine_id = enif_make_atom(env,"bad_engine_id");
- atom_ctrl_cmd_failed = enif_make_atom(env,"ctrl_cmd_failed");
- atom_engine_init_failed = enif_make_atom(env,"engine_init_failed");
- atom_engine_method_not_supported = enif_make_atom(env,"engine_method_not_supported");
- atom_add_engine_failed = enif_make_atom(env,"add_engine_failed");
- atom_remove_engine_failed = enif_make_atom(env,"remove_engine_failed");
-
- atom_engine_method_rsa = enif_make_atom(env,"engine_method_rsa");
- atom_engine_method_dsa = enif_make_atom(env,"engine_method_dsa");
- atom_engine_method_dh = enif_make_atom(env,"engine_method_dh");
- atom_engine_method_rand = enif_make_atom(env,"engine_method_rand");
- atom_engine_method_ecdh = enif_make_atom(env,"engine_method_ecdh");
- atom_engine_method_ecdsa = enif_make_atom(env,"engine_method_ecdsa");
- atom_engine_method_store = enif_make_atom(env,"engine_method_store");
- atom_engine_method_ciphers = enif_make_atom(env,"engine_method_ciphers");
- atom_engine_method_digests = enif_make_atom(env,"engine_method_digests");
- atom_engine_method_pkey_meths = enif_make_atom(env,"engine_method_pkey_meths");
- atom_engine_method_pkey_asn1_meths = enif_make_atom(env,"engine_method_pkey_asn1_meths");
- atom_engine_method_ec = enif_make_atom(env,"engine_method_ec");
-
- atom_engine = enif_make_atom(env,"engine");
- atom_key_id = enif_make_atom(env,"key_id");
- atom_password = enif_make_atom(env,"password");
-#endif
-
-
#ifdef HAVE_DYNAMIC_CRYPTO_LIB
- {
- void* handle;
- if (!change_basename(&lib_bin, lib_buf, sizeof(lib_buf), crypto_callback_name)) {
- return __LINE__;
- }
- if (!(handle = enif_dlopen(lib_buf, &error_handler, NULL))) {
- return __LINE__;
- }
- if (!(funcp = (get_crypto_callbacks_t*) enif_dlsym(handle, "get_crypto_callbacks",
- &error_handler, NULL))) {
- return __LINE__;
- }
- }
+ if (!change_basename(&lib_bin, lib_buf, sizeof(lib_buf), crypto_callback_name))
+ return __LINE__;
+ if ((handle = enif_dlopen(lib_buf, &error_handler, NULL)) == NULL)
+ return __LINE__;
+ if ((funcp = (get_crypto_callbacks_t*) enif_dlsym(handle, "get_crypto_callbacks",
+ &error_handler, NULL)) == NULL)
+ return __LINE__;
#else /* !HAVE_DYNAMIC_CRYPTO_LIB */
funcp = &get_crypto_callbacks;
#endif
@@ -1291,7 +239,10 @@ static int initialize(ErlNifEnv* env, ERL_NIF_TERM load_info)
return __LINE__;
}
- CRYPTO_set_mem_functions(ccb->crypto_alloc, ccb->crypto_realloc, ccb->crypto_free);
+#ifdef HAS_CRYPTO_MEM_FUNCTIONS
+ if (!CRYPTO_set_mem_functions(ccb->crypto_alloc, ccb->crypto_realloc, ccb->crypto_free))
+ return __LINE__;
+#endif
#ifdef OPENSSL_THREADS
if (nlocks > 0) {
@@ -1345,4806 +296,3 @@ static void unload(ErlNifEnv* env, void* priv_data)
{
--library_refc;
}
-
-static int algo_hash_cnt, algo_hash_fips_cnt;
-static ERL_NIF_TERM algo_hash[12]; /* increase when extending the list */
-static int algo_pubkey_cnt, algo_pubkey_fips_cnt;
-static ERL_NIF_TERM algo_pubkey[12]; /* increase when extending the list */
-static int algo_cipher_cnt, algo_cipher_fips_cnt;
-static ERL_NIF_TERM algo_cipher[25]; /* increase when extending the list */
-static int algo_mac_cnt, algo_mac_fips_cnt;
-static ERL_NIF_TERM algo_mac[3]; /* increase when extending the list */
-static int algo_curve_cnt, algo_curve_fips_cnt;
-static ERL_NIF_TERM algo_curve[89]; /* increase when extending the list */
-static int algo_rsa_opts_cnt, algo_rsa_opts_fips_cnt;
-static ERL_NIF_TERM algo_rsa_opts[11]; /* increase when extending the list */
-
-static void init_algorithms_types(ErlNifEnv* env)
-{
- // Validated algorithms first
- algo_hash_cnt = 0;
- algo_hash[algo_hash_cnt++] = atom_sha;
-#ifdef HAVE_SHA224
- algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha224");
-#endif
-#ifdef HAVE_SHA256
- algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha256");
-#endif
-#ifdef HAVE_SHA384
- algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha384");
-#endif
-#ifdef HAVE_SHA512
- algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha512");
-#endif
-#ifdef HAVE_SHA3_224
- algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha3_224");
-#endif
-#ifdef HAVE_SHA3_256
- algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha3_256");
-#endif
-#ifdef HAVE_SHA3_384
- algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha3_384");
-#endif
-#ifdef HAVE_SHA3_512
- algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha3_512");
-#endif
- // Non-validated algorithms follow
- algo_hash_fips_cnt = algo_hash_cnt;
- algo_hash[algo_hash_cnt++] = enif_make_atom(env, "md4");
- algo_hash[algo_hash_cnt++] = enif_make_atom(env, "md5");
- algo_hash[algo_hash_cnt++] = enif_make_atom(env, "ripemd160");
-
- algo_pubkey_cnt = 0;
- algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "rsa");
- algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "dss");
- algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "dh");
-#if defined(HAVE_EC)
-#if !defined(OPENSSL_NO_EC2M)
- algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "ec_gf2m");
-#endif
- algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "ecdsa");
- algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "ecdh");
-#endif
- // Non-validated algorithms follow
- algo_pubkey_fips_cnt = algo_pubkey_cnt;
- // Don't know if Edward curves are fips validated
-#if defined(HAVE_EDDSA)
- algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "eddsa");
-#endif
- algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "srp");
-
- // Validated algorithms first
- algo_cipher_cnt = 0;
-#ifndef OPENSSL_NO_DES
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "des3_cbc");
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "des_ede3");
-#ifdef HAVE_DES_ede3_cfb_encrypt
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "des3_cbf");
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "des3_cfb");
-#endif
-#endif
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_cbc");
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_cbc128");
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_cfb8");
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_cfb128");
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_cbc256");
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_ctr");
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_ecb");
-#if defined(HAVE_GCM)
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"aes_gcm");
-#endif
-#if defined(HAVE_CCM)
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"aes_ccm");
-#endif
- // Non-validated algorithms follow
- algo_cipher_fips_cnt = algo_cipher_cnt;
-#ifdef HAVE_AES_IGE
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"aes_ige256");
-#endif
-#ifndef OPENSSL_NO_DES
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"des_cbc");
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"des_cfb");
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"des_ecb");
-#endif
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"blowfish_cbc");
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"blowfish_cfb64");
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"blowfish_ofb64");
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"blowfish_ecb");
-#ifndef OPENSSL_NO_RC2
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"rc2_cbc");
-#endif
-#ifndef OPENSSL_NO_RC4
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"rc4");
-#endif
-#if defined(HAVE_CHACHA20_POLY1305)
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"chacha20_poly1305");
-#endif
-#if defined(HAVE_CHACHA20)
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"chacha20");
-#endif
-
- // Validated algorithms first
- algo_mac_cnt = 0;
- algo_mac[algo_mac_cnt++] = enif_make_atom(env,"hmac");
-#ifdef HAVE_CMAC
- algo_mac[algo_mac_cnt++] = enif_make_atom(env,"cmac");
-#endif
-#ifdef HAVE_POLY1305
- algo_mac[algo_mac_cnt++] = enif_make_atom(env,"poly1305");
-#endif
- // Non-validated algorithms follow
- algo_mac_fips_cnt = algo_mac_cnt;
-
- // Validated algorithms first
- algo_curve_cnt = 0;
-#if defined(HAVE_EC)
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp160k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp160r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp160r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp192r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp192k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp224k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp224r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp256k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp256r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp384r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp521r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime192v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime192v2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime192v3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime239v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime239v2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime239v3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime256v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls7");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls9");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls12");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP160r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP160t1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP192r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP192t1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP224r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP224t1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP256r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP256t1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP320r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP320t1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP384r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP384t1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP512r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP512t1");
-#if !defined(OPENSSL_NO_EC2M)
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect163k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect163r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect163r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect193r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect193r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect233k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect233r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect239k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect283k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect283r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect409k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect409r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect571k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect571r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb163v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb163v2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb163v3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb176v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb191v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb191v2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb191v3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb208w1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb239v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb239v2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb239v3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb272w1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb304w1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb359v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb368w1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb431r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls5");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls10");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls11");
-#endif
-#endif
- // Non-validated algorithms follow
- algo_curve_fips_cnt = algo_curve_cnt;
-#if defined(HAVE_EC)
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp112r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp112r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp128r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp128r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls6");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls8");
-#if !defined(OPENSSL_NO_EC2M)
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect113r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect113r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect131r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect131r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls4");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ipsec3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ipsec4");
-#endif
-#endif
- //--
-#ifdef HAVE_EDDSA
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ed25519");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ed448");
-#endif
-#ifdef HAVE_ED_CURVE_DH
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"x25519");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"x448");
-#endif
-
- // Validated algorithms first
- algo_rsa_opts_cnt = 0;
-#ifdef HAS_EVP_PKEY_CTX
-# ifdef HAVE_RSA_PKCS1_PSS_PADDING
- algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_pkcs1_pss_padding");
- algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_pss_saltlen");
-# endif
-# ifdef HAVE_RSA_MGF1_MD
- algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_mgf1_md");
-# endif
-# ifdef HAVE_RSA_OAEP_PADDING
- algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_pkcs1_oaep_padding");
-# endif
-# ifdef HAVE_RSA_OAEP_MD
- algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_oaep_label");
- algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_oaep_md");
-# endif
- algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"signature_md");
-#endif
- algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_pkcs1_padding");
- algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_x931_padding");
-#ifdef HAVE_RSA_SSLV23_PADDING
- algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_sslv23_padding");
-#endif
- algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_no_padding");
- algo_rsa_opts_fips_cnt = algo_rsa_opts_cnt;
-
-
- // Check that the max number of algos is updated
- ASSERT(algo_hash_cnt <= sizeof(algo_hash)/sizeof(ERL_NIF_TERM));
- ASSERT(algo_pubkey_cnt <= sizeof(algo_pubkey)/sizeof(ERL_NIF_TERM));
- ASSERT(algo_cipher_cnt <= sizeof(algo_cipher)/sizeof(ERL_NIF_TERM));
- ASSERT(algo_mac_cnt <= sizeof(algo_mac)/sizeof(ERL_NIF_TERM));
- ASSERT(algo_curve_cnt <= sizeof(algo_curve)/sizeof(ERL_NIF_TERM));
- ASSERT(algo_rsa_opts_cnt <= sizeof(algo_rsa_opts)/sizeof(ERL_NIF_TERM));
-}
-
-static ERL_NIF_TERM algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{
-#ifdef FIPS_SUPPORT
- int fips_mode = FIPS_mode();
- int hash_cnt = fips_mode ? algo_hash_fips_cnt : algo_hash_cnt;
- int pubkey_cnt = fips_mode ? algo_pubkey_fips_cnt : algo_pubkey_cnt;
- int cipher_cnt = fips_mode ? algo_cipher_fips_cnt : algo_cipher_cnt;
- int mac_cnt = fips_mode ? algo_mac_fips_cnt : algo_mac_cnt;
- int curve_cnt = fips_mode ? algo_curve_fips_cnt : algo_curve_cnt;
- int rsa_opts_cnt = fips_mode ? algo_rsa_opts_fips_cnt : algo_rsa_opts_cnt;
-#else
- int hash_cnt = algo_hash_cnt;
- int pubkey_cnt = algo_pubkey_cnt;
- int cipher_cnt = algo_cipher_cnt;
- int mac_cnt = algo_mac_cnt;
- int curve_cnt = algo_curve_cnt;
- int rsa_opts_cnt = algo_rsa_opts_cnt;
-#endif
- return enif_make_tuple6(env,
- enif_make_list_from_array(env, algo_hash, hash_cnt),
- enif_make_list_from_array(env, algo_pubkey, pubkey_cnt),
- enif_make_list_from_array(env, algo_cipher, cipher_cnt),
- enif_make_list_from_array(env, algo_mac, mac_cnt),
- enif_make_list_from_array(env, algo_curve, curve_cnt),
- enif_make_list_from_array(env, algo_rsa_opts, rsa_opts_cnt)
- );
-}
-
-static ERL_NIF_TERM info_lib(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{
- /* [{<<"OpenSSL">>,9470143,<<"OpenSSL 0.9.8k 25 Mar 2009">>}] */
-
- static const char libname[] = "OpenSSL";
- unsigned name_sz = strlen(libname);
- const char* ver = SSLeay_version(SSLEAY_VERSION);
- unsigned ver_sz = strlen(ver);
- ERL_NIF_TERM name_term, ver_term;
- int ver_num = OPENSSL_VERSION_NUMBER;
- /* R16:
- * Ignore library version number from SSLeay() and instead show header
- * version. Otherwise user might try to call a function that is implemented
- * by a newer library but not supported by the headers used at compile time.
- * Example: DES_ede3_cfb_encrypt in 0.9.7i but not in 0.9.7d.
- *
- * Version string is still from library though.
- */
-
- memcpy(enif_make_new_binary(env, name_sz, &name_term), libname, name_sz);
- memcpy(enif_make_new_binary(env, ver_sz, &ver_term), ver, ver_sz);
-
- return enif_make_list1(env, enif_make_tuple3(env, name_term,
- enif_make_int(env, ver_num),
- ver_term));
-}
-
-static ERL_NIF_TERM info_fips(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{
-#ifdef FIPS_SUPPORT
- return FIPS_mode() ? atom_enabled : atom_not_enabled;
-#else
- return atom_not_supported;
-#endif
-}
-
-static ERL_NIF_TERM enable_fips_mode(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Boolean) */
- if (argv[0] == atom_true) {
-#ifdef FIPS_SUPPORT
- if (FIPS_mode_set(1)) {
- return atom_true;
- }
-#endif
- PRINTF_ERR0("CRYPTO: Could not setup FIPS mode");
- return atom_false;
- } else if (argv[0] == atom_false) {
-#ifdef FIPS_SUPPORT
- if (!FIPS_mode_set(0)) {
- return atom_false;
- }
-#endif
- return atom_true;
- } else {
- return enif_make_badarg(env);
- }
-}
-
-
-#if defined(HAVE_EC)
-static ERL_NIF_TERM make_badarg_maybe(ErlNifEnv* env)
-{
- ERL_NIF_TERM reason;
- if (enif_has_pending_exception(env, &reason))
- return reason; /* dummy return value ignored */
- else
- return enif_make_badarg(env);
-}
-#endif
-
-static ERL_NIF_TERM hash_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Type, Data) */
- struct digest_type_t *digp = NULL;
- const EVP_MD *md;
- ErlNifBinary data;
- ERL_NIF_TERM ret;
- unsigned ret_size;
-
- digp = get_digest_type(argv[0]);
- if (!digp ||
- !enif_inspect_iolist_as_binary(env, argv[1], &data)) {
- return enif_make_badarg(env);
- }
- md = digp->md.p;
- if (!md) {
- return atom_notsup;
- }
-
- ret_size = (unsigned)EVP_MD_size(md);
- ASSERT(0 < ret_size && ret_size <= EVP_MAX_MD_SIZE);
- if (!EVP_Digest(data.data, data.size,
- enif_make_new_binary(env, ret_size, &ret), &ret_size,
- md, NULL)) {
- return atom_notsup;
- }
- ASSERT(ret_size == (unsigned)EVP_MD_size(md));
-
- CONSUME_REDS(env, data);
- return ret;
-}
-
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
-
-static ERL_NIF_TERM hash_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Type) */
- struct digest_type_t *digp = NULL;
- struct evp_md_ctx *ctx;
- ERL_NIF_TERM ret;
-
- digp = get_digest_type(argv[0]);
- if (!digp) {
- return enif_make_badarg(env);
- }
- if (!digp->md.p) {
- return atom_notsup;
- }
-
- ctx = enif_alloc_resource(evp_md_ctx_rtype, sizeof(struct evp_md_ctx));
- ctx->ctx = EVP_MD_CTX_new();
- if (!EVP_DigestInit(ctx->ctx, digp->md.p)) {
- enif_release_resource(ctx);
- return atom_notsup;
- }
- ret = enif_make_resource(env, ctx);
- enif_release_resource(ctx);
- return ret;
-}
-static ERL_NIF_TERM hash_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Context, Data) */
- struct evp_md_ctx *ctx, *new_ctx;
- ErlNifBinary data;
- ERL_NIF_TERM ret;
-
- if (!enif_get_resource(env, argv[0], evp_md_ctx_rtype, (void**)&ctx) ||
- !enif_inspect_iolist_as_binary(env, argv[1], &data)) {
- return enif_make_badarg(env);
- }
-
- new_ctx = enif_alloc_resource(evp_md_ctx_rtype, sizeof(struct evp_md_ctx));
- new_ctx->ctx = EVP_MD_CTX_new();
- if (!EVP_MD_CTX_copy(new_ctx->ctx, ctx->ctx) ||
- !EVP_DigestUpdate(new_ctx->ctx, data.data, data.size)) {
- enif_release_resource(new_ctx);
- return atom_notsup;
- }
-
- ret = enif_make_resource(env, new_ctx);
- enif_release_resource(new_ctx);
- CONSUME_REDS(env, data);
- return ret;
-}
-static ERL_NIF_TERM hash_final_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 ret_size;
-
- if (!enif_get_resource(env, argv[0], evp_md_ctx_rtype, (void**)&ctx)) {
- return enif_make_badarg(env);
- }
-
- ret_size = (unsigned)EVP_MD_CTX_size(ctx->ctx);
- ASSERT(0 < ret_size && ret_size <= EVP_MAX_MD_SIZE);
-
- new_ctx = EVP_MD_CTX_new();
- if (!EVP_MD_CTX_copy(new_ctx, ctx->ctx) ||
- !EVP_DigestFinal(new_ctx,
- enif_make_new_binary(env, ret_size, &ret),
- &ret_size)) {
- EVP_MD_CTX_free(new_ctx);
- return atom_notsup;
- }
- EVP_MD_CTX_free(new_ctx);
- ASSERT(ret_size == (unsigned)EVP_MD_CTX_size(ctx->ctx));
-
- return ret;
-}
-
-#else /* if OPENSSL_VERSION_NUMBER < 1.0 */
-
-static ERL_NIF_TERM hash_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Type) */
- typedef int (*init_fun)(unsigned char*);
- struct digest_type_t *digp = NULL;
- ERL_NIF_TERM ctx;
- size_t ctx_size = 0;
- init_fun ctx_init = 0;
-
- digp = get_digest_type(argv[0]);
- if (!digp) {
- return enif_make_badarg(env);
- }
- if (!digp->md.p) {
- return atom_notsup;
- }
-
- switch (EVP_MD_type(digp->md.p))
- {
- case NID_md4:
- ctx_size = MD4_CTX_LEN;
- ctx_init = (init_fun)(&MD4_Init);
- break;
- case NID_md5:
- ctx_size = MD5_CTX_LEN;
- ctx_init = (init_fun)(&MD5_Init);
- break;
- case NID_ripemd160:
- ctx_size = RIPEMD160_CTX_LEN;
- ctx_init = (init_fun)(&RIPEMD160_Init);
- break;
- case NID_sha1:
- ctx_size = sizeof(SHA_CTX);
- ctx_init = (init_fun)(&SHA1_Init);
- break;
-#ifdef HAVE_SHA224
- case NID_sha224:
- ctx_size = sizeof(SHA256_CTX);
- ctx_init = (init_fun)(&SHA224_Init);
- break;
-#endif
-#ifdef HAVE_SHA256
- case NID_sha256:
- ctx_size = sizeof(SHA256_CTX);
- ctx_init = (init_fun)(&SHA256_Init);
- break;
-#endif
-#ifdef HAVE_SHA384
- case NID_sha384:
- ctx_size = sizeof(SHA512_CTX);
- ctx_init = (init_fun)(&SHA384_Init);
- break;
-#endif
-#ifdef HAVE_SHA512
- case NID_sha512:
- ctx_size = sizeof(SHA512_CTX);
- ctx_init = (init_fun)(&SHA512_Init);
- break;
-#endif
- default:
- return atom_notsup;
- }
- ASSERT(ctx_size);
- ASSERT(ctx_init);
-
- ctx_init(enif_make_new_binary(env, ctx_size, &ctx));
- return enif_make_tuple2(env, argv[0], ctx);
-}
-static ERL_NIF_TERM hash_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* ({Type, Context}, Data) */
- typedef int (*update_fun)(unsigned char*, const unsigned char*, size_t);
- ERL_NIF_TERM new_ctx;
- ErlNifBinary ctx, data;
- const ERL_NIF_TERM *tuple;
- int arity;
- struct digest_type_t *digp = NULL;
- unsigned char *ctx_buff;
- size_t ctx_size = 0;
- update_fun ctx_update = 0;
-
- if (!enif_get_tuple(env, argv[0], &arity, &tuple) ||
- arity != 2 ||
- !(digp = get_digest_type(tuple[0])) ||
- !enif_inspect_binary(env, tuple[1], &ctx) ||
- !enif_inspect_iolist_as_binary(env, argv[1], &data)) {
- return enif_make_badarg(env);
- }
- if (!digp->md.p) {
- return atom_notsup;
- }
-
- switch (EVP_MD_type(digp->md.p))
- {
- case NID_md4:
- ctx_size = MD4_CTX_LEN;
- ctx_update = (update_fun)(&MD4_Update);
- break;
- case NID_md5:
- ctx_size = MD5_CTX_LEN;
- ctx_update = (update_fun)(&MD5_Update);
- break;
- case NID_ripemd160:
- ctx_size = RIPEMD160_CTX_LEN;
- ctx_update = (update_fun)(&RIPEMD160_Update);
- break;
- case NID_sha1:
- ctx_size = sizeof(SHA_CTX);
- ctx_update = (update_fun)(&SHA1_Update);
- break;
-#ifdef HAVE_SHA224
- case NID_sha224:
- ctx_size = sizeof(SHA256_CTX);
- ctx_update = (update_fun)(&SHA224_Update);
- break;
-#endif
-#ifdef HAVE_SHA256
- case NID_sha256:
- ctx_size = sizeof(SHA256_CTX);
- ctx_update = (update_fun)(&SHA256_Update);
- break;
-#endif
-#ifdef HAVE_SHA384
- case NID_sha384:
- ctx_size = sizeof(SHA512_CTX);
- ctx_update = (update_fun)(&SHA384_Update);
- break;
-#endif
-#ifdef HAVE_SHA512
- case NID_sha512:
- ctx_size = sizeof(SHA512_CTX);
- ctx_update = (update_fun)(&SHA512_Update);
- break;
-#endif
- default:
- return atom_notsup;
- }
- ASSERT(ctx_size);
- ASSERT(ctx_update);
-
- if (ctx.size != ctx_size) {
- return enif_make_badarg(env);
- }
-
- ctx_buff = enif_make_new_binary(env, ctx_size, &new_ctx);
- memcpy(ctx_buff, ctx.data, ctx_size);
- ctx_update(ctx_buff, data.data, data.size);
-
- CONSUME_REDS(env, data);
- return enif_make_tuple2(env, tuple[0], new_ctx);
-}
-static ERL_NIF_TERM hash_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* ({Type, Context}) */
- typedef int (*final_fun)(unsigned char*, void*);
- ERL_NIF_TERM ret;
- ErlNifBinary ctx;
- const ERL_NIF_TERM *tuple;
- int arity;
- struct digest_type_t *digp = NULL;
- const EVP_MD *md;
- void *new_ctx;
- size_t ctx_size = 0;
- final_fun ctx_final = 0;
-
- if (!enif_get_tuple(env, argv[0], &arity, &tuple) ||
- arity != 2 ||
- !(digp = get_digest_type(tuple[0])) ||
- !enif_inspect_binary(env, tuple[1], &ctx)) {
- return enif_make_badarg(env);
- }
- md = digp->md.p;
- if (!md) {
- return atom_notsup;
- }
-
-
- switch (EVP_MD_type(md))
- {
- case NID_md4:
- ctx_size = MD4_CTX_LEN;
- ctx_final = (final_fun)(&MD4_Final);
- break;
- case NID_md5:
- ctx_size = MD5_CTX_LEN;
- ctx_final = (final_fun)(&MD5_Final);
- break;
- case NID_ripemd160:
- ctx_size = RIPEMD160_CTX_LEN;
- ctx_final = (final_fun)(&RIPEMD160_Final);
- break;
- case NID_sha1:
- ctx_size = sizeof(SHA_CTX);
- ctx_final = (final_fun)(&SHA1_Final);
- break;
-#ifdef HAVE_SHA224
- case NID_sha224:
- ctx_size = sizeof(SHA256_CTX);
- ctx_final = (final_fun)(&SHA224_Final);
- break;
-#endif
-#ifdef HAVE_SHA256
- case NID_sha256:
- ctx_size = sizeof(SHA256_CTX);
- ctx_final = (final_fun)(&SHA256_Final);
- break;
-#endif
-#ifdef HAVE_SHA384
- case NID_sha384:
- ctx_size = sizeof(SHA512_CTX);
- ctx_final = (final_fun)(&SHA384_Final);
- break;
-#endif
-#ifdef HAVE_SHA512
- case NID_sha512:
- ctx_size = sizeof(SHA512_CTX);
- ctx_final = (final_fun)(&SHA512_Final);
- break;
-#endif
- default:
- return atom_notsup;
- }
- ASSERT(ctx_size);
- ASSERT(ctx_final);
-
- if (ctx.size != ctx_size) {
- return enif_make_badarg(env);
- }
-
- new_ctx = enif_alloc(ctx_size);
- memcpy(new_ctx, ctx.data, ctx_size);
- ctx_final(enif_make_new_binary(env, (size_t)EVP_MD_size(md), &ret),
- new_ctx);
- enif_free(new_ctx);
-
- return ret;
-}
-#endif /* OPENSSL_VERSION_NUMBER < 1.0 */
-
-
-static ERL_NIF_TERM hmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Type, Key, Data) or (Type, Key, Data, MacSize) */
- struct digest_type_t *digp = NULL;
- ErlNifBinary key, data;
- unsigned char buff[EVP_MAX_MD_SIZE];
- unsigned size = 0, req_size = 0;
- ERL_NIF_TERM ret;
-
- digp = get_digest_type(argv[0]);
- if (!digp ||
- !enif_inspect_iolist_as_binary(env, argv[1], &key) ||
- !enif_inspect_iolist_as_binary(env, argv[2], &data) ||
- (argc == 4 && !enif_get_uint(env, argv[3], &req_size))) {
- return enif_make_badarg(env);
- }
-
- if (!digp->md.p ||
- !HMAC(digp->md.p,
- key.data, key.size,
- data.data, data.size,
- buff, &size)) {
- return atom_notsup;
- }
- ASSERT(0 < size && size <= EVP_MAX_MD_SIZE);
- CONSUME_REDS(env, data);
-
- if (argc == 4) {
- if (req_size <= size) {
- size = req_size;
- }
- else {
- return enif_make_badarg(env);
- }
- }
- memcpy(enif_make_new_binary(env, size, &ret), buff, size);
- return ret;
-}
-
-static void hmac_context_dtor(ErlNifEnv* env, struct hmac_context *obj)
-{
- if (obj->alive) {
- HMAC_CTX_free(obj->ctx);
- obj->alive = 0;
- }
- enif_mutex_destroy(obj->mtx);
-}
-
-static ERL_NIF_TERM hmac_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Type, Key) */
- struct digest_type_t *digp = NULL;
- ErlNifBinary key;
- ERL_NIF_TERM ret;
- struct hmac_context *obj;
-
- digp = get_digest_type(argv[0]);
- if (!digp ||
- !enif_inspect_iolist_as_binary(env, argv[1], &key)) {
- return enif_make_badarg(env);
- }
- if (!digp->md.p) {
- return atom_notsup;
- }
-
- obj = enif_alloc_resource(hmac_context_rtype, sizeof(struct hmac_context));
- obj->mtx = enif_mutex_create("crypto.hmac");
- obj->alive = 1;
- obj->ctx = HMAC_CTX_new();
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
- // Check the return value of HMAC_Init: it may fail in FIPS mode
- // for disabled algorithms
- if (!HMAC_Init_ex(obj->ctx, key.data, key.size, digp->md.p, NULL)) {
- enif_release_resource(obj);
- return atom_notsup;
- }
-#else
- HMAC_Init_ex(obj->ctx, key.data, key.size, digp->md.p, NULL);
-#endif
-
- ret = enif_make_resource(env, obj);
- enif_release_resource(obj);
- return ret;
-}
-
-static ERL_NIF_TERM hmac_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Context, Data) */
- ErlNifBinary data;
- struct hmac_context* obj;
-
- if (!enif_get_resource(env, argv[0], hmac_context_rtype, (void**)&obj)
- || !enif_inspect_iolist_as_binary(env, argv[1], &data)) {
- return enif_make_badarg(env);
- }
- enif_mutex_lock(obj->mtx);
- if (!obj->alive) {
- enif_mutex_unlock(obj->mtx);
- return enif_make_badarg(env);
- }
- HMAC_Update(obj->ctx, data.data, data.size);
- enif_mutex_unlock(obj->mtx);
-
- CONSUME_REDS(env,data);
- return argv[0];
-}
-
-static ERL_NIF_TERM hmac_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Context) or (Context, HashLen) */
- ERL_NIF_TERM ret;
- struct hmac_context* obj;
- unsigned char mac_buf[EVP_MAX_MD_SIZE];
- unsigned char * mac_bin;
- unsigned int req_len = 0;
- unsigned int mac_len;
-
- if (!enif_get_resource(env,argv[0],hmac_context_rtype, (void**)&obj)
- || (argc == 2 && !enif_get_uint(env, argv[1], &req_len))) {
- return enif_make_badarg(env);
- }
-
- enif_mutex_lock(obj->mtx);
- if (!obj->alive) {
- enif_mutex_unlock(obj->mtx);
- return enif_make_badarg(env);
- }
-
- HMAC_Final(obj->ctx, mac_buf, &mac_len);
- HMAC_CTX_free(obj->ctx);
- obj->alive = 0;
- enif_mutex_unlock(obj->mtx);
-
- if (argc == 2 && req_len < mac_len) {
- /* Only truncate to req_len bytes if asked. */
- mac_len = req_len;
- }
- mac_bin = enif_make_new_binary(env, mac_len, &ret);
- memcpy(mac_bin, mac_buf, mac_len);
-
- return ret;
-}
-
-static ERL_NIF_TERM cmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Type, Key, Data) */
-#if defined(HAVE_CMAC)
- struct cipher_type_t *cipherp = NULL;
- const EVP_CIPHER *cipher;
- CMAC_CTX *ctx;
- ErlNifBinary key;
- ErlNifBinary data;
- ERL_NIF_TERM ret;
- size_t ret_size;
-
- if (!enif_inspect_iolist_as_binary(env, argv[1], &key)
- || !(cipherp = get_cipher_type(argv[0], key.size))
- || !enif_inspect_iolist_as_binary(env, argv[2], &data)) {
- return enif_make_badarg(env);
- }
- cipher = cipherp->cipher.p;
- if (!cipher) {
- return enif_raise_exception(env, atom_notsup);
- }
-
- ctx = CMAC_CTX_new();
- if (!CMAC_Init(ctx, key.data, key.size, cipher, NULL)) {
- CMAC_CTX_free(ctx);
- return atom_notsup;
- }
-
- if (!CMAC_Update(ctx, data.data, data.size) ||
- !CMAC_Final(ctx,
- enif_make_new_binary(env, EVP_CIPHER_block_size(cipher), &ret),
- &ret_size)) {
- CMAC_CTX_free(ctx);
- return atom_notsup;
- }
- ASSERT(ret_size == (unsigned)EVP_CIPHER_block_size(cipher));
-
- CMAC_CTX_free(ctx);
- CONSUME_REDS(env, data);
- return ret;
-#else
- /* The CMAC functionality was introduced in OpenSSL 1.0.1
- * Although OTP requires at least version 0.9.8, the versions 0.9.8 and 1.0.0 are
- * no longer maintained. */
- return atom_notsup;
-#endif
-}
-
-/* For OpenSSL >= 1.1.1 the hmac_nif and cmac_nif could be integrated into poly1305 (with 'type' as parameter) */
-static ERL_NIF_TERM poly1305_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Key, Text) */
-#ifdef HAVE_POLY1305
- ErlNifBinary key_bin, text, ret_bin;
- ERL_NIF_TERM ret = atom_error;
- EVP_PKEY *key = NULL;
- EVP_MD_CTX *mctx = NULL;
- EVP_PKEY_CTX *pctx = NULL;
- const EVP_MD *md = NULL;
- size_t size;
- int type;
-
- type = EVP_PKEY_POLY1305;
-
- if (!enif_inspect_binary(env, argv[0], &key_bin) ||
- !(key_bin.size == 32) ) {
- return enif_make_badarg(env);
- }
-
- if (!enif_inspect_binary(env, argv[1], &text) ) {
- return enif_make_badarg(env);
- }
-
- key = EVP_PKEY_new_raw_private_key(type, /*engine*/ NULL, key_bin.data, key_bin.size);
-
- if (!key ||
- !(mctx = EVP_MD_CTX_new()) ||
- !EVP_DigestSignInit(mctx, &pctx, md, /*engine*/ NULL, key) ||
- !EVP_DigestSignUpdate(mctx, text.data, text.size)) {
- goto err;
- }
-
- if (!EVP_DigestSignFinal(mctx, NULL, &size) ||
- !enif_alloc_binary(size, &ret_bin) ||
- !EVP_DigestSignFinal(mctx, ret_bin.data, &size)) {
- goto err;
- }
-
- if ((size != ret_bin.size) &&
- !enif_realloc_binary(&ret_bin, size)) {
- goto err;
- }
-
- ret = enif_make_binary(env, &ret_bin);
-
- err:
- EVP_MD_CTX_free(mctx);
- EVP_PKEY_free(key);
- return ret;
-
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM block_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Type, Key, Ivec, Text, IsEncrypt) or (Type, Key, Text, IsEncrypt) */
- struct cipher_type_t *cipherp = NULL;
- const EVP_CIPHER *cipher;
- ErlNifBinary key, ivec, text;
- EVP_CIPHER_CTX* ctx;
- ERL_NIF_TERM ret;
- unsigned char *out;
- int ivec_size, out_size = 0;
-
- if (!enif_inspect_iolist_as_binary(env, argv[1], &key)
- || !(cipherp = get_cipher_type(argv[0], key.size))
- || !enif_inspect_iolist_as_binary(env, argv[argc - 2], &text)) {
- return enif_make_badarg(env);
- }
- cipher = cipherp->cipher.p;
- if (!cipher) {
- return enif_raise_exception(env, atom_notsup);
- }
-
- if (argv[0] == atom_aes_cfb8
- && (key.size == 24 || key.size == 32)) {
- /* Why do EVP_CIPHER_CTX_set_key_length() fail on these key sizes?
- * Fall back on low level API
- */
- return aes_cfb_8_crypt(env, argc-1, argv+1);
- }
- else if (argv[0] == atom_aes_cfb128
- && (key.size == 24 || key.size == 32)) {
- /* Why do EVP_CIPHER_CTX_set_key_length() fail on these key sizes?
- * Fall back on low level API
- */
- return aes_cfb_128_crypt_nif(env, argc-1, argv+1);
- }
-
- ivec_size = EVP_CIPHER_iv_length(cipher);
-
-#ifdef HAVE_ECB_IVEC_BUG
- if (argv[0] == atom_aes_ecb || argv[0] == atom_blowfish_ecb ||
- argv[0] == atom_des_ecb)
- ivec_size = 0; /* 0.9.8l returns faulty ivec_size */
-#endif
-
- if (text.size % EVP_CIPHER_block_size(cipher) != 0 ||
- (ivec_size == 0 ? argc != 4
- : (argc != 5 ||
- !enif_inspect_iolist_as_binary(env, argv[2], &ivec) ||
- ivec.size != ivec_size))) {
- return enif_make_badarg(env);
- }
-
- out = enif_make_new_binary(env, text.size, &ret);
-
- ctx = EVP_CIPHER_CTX_new();
- if (!EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL,
- (argv[argc - 1] == atom_true)) ||
- !EVP_CIPHER_CTX_set_key_length(ctx, key.size) ||
- !(EVP_CIPHER_type(cipher) != NID_rc2_cbc ||
- EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_SET_RC2_KEY_BITS, key.size * 8, NULL)) ||
- !EVP_CipherInit_ex(ctx, NULL, NULL,
- key.data, ivec_size ? ivec.data : NULL, -1) ||
- !EVP_CIPHER_CTX_set_padding(ctx, 0)) {
-
- EVP_CIPHER_CTX_free(ctx);
- return enif_raise_exception(env, atom_notsup);
- }
-
- if (text.size > 0 && /* OpenSSL 0.9.8h asserts text.size > 0 */
- (!EVP_CipherUpdate(ctx, out, &out_size, text.data, text.size)
- || (ASSERT(out_size == text.size), 0)
- || !EVP_CipherFinal_ex(ctx, out + out_size, &out_size))) {
-
- EVP_CIPHER_CTX_free(ctx);
- return enif_raise_exception(env, atom_notsup);
- }
- ASSERT(out_size == 0);
- EVP_CIPHER_CTX_free(ctx);
- CONSUME_REDS(env, text);
-
- return ret;
-}
-
-static ERL_NIF_TERM aes_cfb_8_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Key, IVec, Data, IsEncrypt) */
- ErlNifBinary key, ivec, text;
- AES_KEY aes_key;
- unsigned char ivec_clone[16]; /* writable copy */
- int new_ivlen = 0;
- ERL_NIF_TERM ret;
-
- CHECK_NO_FIPS_MODE();
-
- if (!enif_inspect_iolist_as_binary(env, argv[0], &key)
- || !(key.size == 16 || key.size == 24 || key.size == 32)
- || !enif_inspect_binary(env, argv[1], &ivec) || ivec.size != 16
- || !enif_inspect_iolist_as_binary(env, argv[2], &text)) {
- return enif_make_badarg(env);
- }
-
- memcpy(ivec_clone, ivec.data, 16);
- AES_set_encrypt_key(key.data, key.size * 8, &aes_key);
- AES_cfb8_encrypt((unsigned char *) text.data,
- enif_make_new_binary(env, text.size, &ret),
- text.size, &aes_key, ivec_clone, &new_ivlen,
- (argv[3] == atom_true));
- CONSUME_REDS(env,text);
- return ret;
-}
-
-static ERL_NIF_TERM aes_cfb_128_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Key, IVec, Data, IsEncrypt) */
- ErlNifBinary key, ivec, text;
- AES_KEY aes_key;
- unsigned char ivec_clone[16]; /* writable copy */
- int new_ivlen = 0;
- ERL_NIF_TERM ret;
-
- if (!enif_inspect_iolist_as_binary(env, argv[0], &key)
- || !(key.size == 16 || key.size == 24 || key.size == 32)
- || !enif_inspect_binary(env, argv[1], &ivec) || ivec.size != 16
- || !enif_inspect_iolist_as_binary(env, argv[2], &text)) {
- return enif_make_badarg(env);
- }
-
- memcpy(ivec_clone, ivec.data, 16);
- AES_set_encrypt_key(key.data, key.size * 8, &aes_key);
- AES_cfb128_encrypt((unsigned char *) text.data,
- enif_make_new_binary(env, text.size, &ret),
- text.size, &aes_key, ivec_clone, &new_ivlen,
- (argv[3] == atom_true));
- CONSUME_REDS(env,text);
- return ret;
-}
-
-static ERL_NIF_TERM aes_ige_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Key, IVec, Data, IsEncrypt) */
-#ifdef HAVE_AES_IGE
- ErlNifBinary key_bin, ivec_bin, data_bin;
- AES_KEY aes_key;
- unsigned char ivec[32];
- int i;
- unsigned char* ret_ptr;
- ERL_NIF_TERM ret;
-
- CHECK_NO_FIPS_MODE();
-
- if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin)
- || (key_bin.size != 16 && key_bin.size != 32)
- || !enif_inspect_binary(env, argv[1], &ivec_bin)
- || ivec_bin.size != 32
- || !enif_inspect_iolist_as_binary(env, argv[2], &data_bin)
- || data_bin.size % 16 != 0) {
-
- return enif_make_badarg(env);
- }
-
- if (argv[3] == atom_true) {
- i = AES_ENCRYPT;
- AES_set_encrypt_key(key_bin.data, key_bin.size*8, &aes_key);
- }
- else {
- i = AES_DECRYPT;
- AES_set_decrypt_key(key_bin.data, key_bin.size*8, &aes_key);
- }
-
- ret_ptr = enif_make_new_binary(env, data_bin.size, &ret);
- memcpy(ivec, ivec_bin.data, 32); /* writable copy */
- AES_ige_encrypt(data_bin.data, ret_ptr, data_bin.size, &aes_key, ivec, i);
- CONSUME_REDS(env,data_bin);
- return ret;
-#else
- return atom_notsup;
-#endif
-}
-
-
-/* Initializes state for ctr streaming (de)encryption
-*/
-#ifdef HAVE_EVP_AES_CTR
-static ERL_NIF_TERM aes_ctr_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Key, IVec) */
- ErlNifBinary key_bin, ivec_bin;
- struct evp_cipher_ctx *ctx;
- const EVP_CIPHER *cipher;
- ERL_NIF_TERM ret;
-
- if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin)
- || !enif_inspect_binary(env, argv[1], &ivec_bin)
- || ivec_bin.size != 16) {
- return enif_make_badarg(env);
- }
-
- switch (key_bin.size)
- {
- case 16: cipher = EVP_aes_128_ctr(); break;
- case 24: cipher = EVP_aes_192_ctr(); break;
- case 32: cipher = EVP_aes_256_ctr(); break;
- default: return enif_make_badarg(env);
- }
-
- ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx));
- ctx->ctx = EVP_CIPHER_CTX_new();
- EVP_CipherInit_ex(ctx->ctx, cipher, NULL,
- key_bin.data, ivec_bin.data, 1);
- EVP_CIPHER_CTX_set_padding(ctx->ctx, 0);
- ret = enif_make_resource(env, ctx);
- enif_release_resource(ctx);
- return ret;
-}
-static ERL_NIF_TERM aes_ctr_stream_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Context, Data) */
- struct evp_cipher_ctx *ctx, *new_ctx;
- ErlNifBinary data_bin;
- ERL_NIF_TERM ret, cipher_term;
- unsigned char *out;
- int outl = 0;
-
- if (!enif_get_resource(env, argv[0], evp_cipher_ctx_rtype, (void**)&ctx)
- || !enif_inspect_iolist_as_binary(env, argv[1], &data_bin)) {
- return enif_make_badarg(env);
- }
- new_ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx));
- new_ctx->ctx = EVP_CIPHER_CTX_new();
- EVP_CIPHER_CTX_copy(new_ctx->ctx, ctx->ctx);
- out = enif_make_new_binary(env, data_bin.size, &cipher_term);
- EVP_CipherUpdate(new_ctx->ctx, out, &outl, data_bin.data, data_bin.size);
- ASSERT(outl == data_bin.size);
-
- ret = enif_make_tuple2(env, enif_make_resource(env, new_ctx), cipher_term);
- enif_release_resource(new_ctx);
- CONSUME_REDS(env,data_bin);
- return ret;
-}
-
-#else /* if not HAVE_EVP_AES_CTR */
-
-static ERL_NIF_TERM aes_ctr_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Key, IVec) */
- ErlNifBinary key_bin, ivec_bin;
- ERL_NIF_TERM ecount_bin;
-
- if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin)
- || !enif_inspect_binary(env, argv[1], &ivec_bin)
- || !(key_bin.size == 16 || key_bin.size == 24 || key_bin.size ==32)
- || ivec_bin.size != 16) {
- return enif_make_badarg(env);
- }
-
- memset(enif_make_new_binary(env, AES_BLOCK_SIZE, &ecount_bin),
- 0, AES_BLOCK_SIZE);
- return enif_make_tuple4(env, argv[0], argv[1], ecount_bin, enif_make_int(env, 0));
-}
-
-static ERL_NIF_TERM aes_ctr_stream_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* ({Key, IVec, ECount, Num}, Data) */
- ErlNifBinary key_bin, ivec_bin, text_bin, ecount_bin;
- AES_KEY aes_key;
- unsigned int num;
- ERL_NIF_TERM ret, num2_term, cipher_term, ivec2_term, ecount2_term, new_state_term;
- int state_arity;
- const ERL_NIF_TERM *state_term;
- unsigned char * ivec2_buf;
- unsigned char * ecount2_buf;
-
- if (!enif_get_tuple(env, argv[0], &state_arity, &state_term)
- || state_arity != 4
- || !enif_inspect_iolist_as_binary(env, state_term[0], &key_bin)
- || AES_set_encrypt_key(key_bin.data, key_bin.size*8, &aes_key) != 0
- || !enif_inspect_binary(env, state_term[1], &ivec_bin) || ivec_bin.size != 16
- || !enif_inspect_binary(env, state_term[2], &ecount_bin) || ecount_bin.size != AES_BLOCK_SIZE
- || !enif_get_uint(env, state_term[3], &num)
- || !enif_inspect_iolist_as_binary(env, argv[1], &text_bin)) {
- return enif_make_badarg(env);
- }
-
- ivec2_buf = enif_make_new_binary(env, ivec_bin.size, &ivec2_term);
- ecount2_buf = enif_make_new_binary(env, ecount_bin.size, &ecount2_term);
-
- memcpy(ivec2_buf, ivec_bin.data, 16);
- memcpy(ecount2_buf, ecount_bin.data, ecount_bin.size);
-
- AES_ctr128_encrypt((unsigned char *) text_bin.data,
- enif_make_new_binary(env, text_bin.size, &cipher_term),
- text_bin.size, &aes_key, ivec2_buf, ecount2_buf, &num);
-
- num2_term = enif_make_uint(env, num);
- new_state_term = enif_make_tuple4(env, state_term[0], ivec2_term, ecount2_term, num2_term);
- ret = enif_make_tuple2(env, new_state_term, cipher_term);
- CONSUME_REDS(env,text_bin);
- return ret;
-}
-#endif /* !HAVE_EVP_AES_CTR */
-
-static ERL_NIF_TERM aead_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Type,Key,Iv,AAD,In) */
-#if defined(HAVE_AEAD)
- EVP_CIPHER_CTX *ctx;
- const EVP_CIPHER *cipher = NULL;
- ErlNifBinary key, iv, aad, in;
- unsigned int tag_len;
- unsigned char *outp, *tagp;
- ERL_NIF_TERM type, out, out_tag;
- int len, ctx_ctrl_set_ivlen, ctx_ctrl_get_tag;
-
- type = argv[0];
-
- if (!enif_is_atom(env, type)
- || !enif_inspect_iolist_as_binary(env, argv[1], &key)
- || !enif_inspect_binary(env, argv[2], &iv)
- || !enif_inspect_iolist_as_binary(env, argv[3], &aad)
- || !enif_inspect_iolist_as_binary(env, argv[4], &in)
- || !enif_get_uint(env, argv[5], &tag_len)) {
- return enif_make_badarg(env);
- }
-
- /* Use cipher_type some day. Must check block_encrypt|decrypt first */
-#if defined(HAVE_GCM)
- if (type == atom_aes_gcm) {
- if ((iv.size > 0)
- && (1 <= tag_len && tag_len <= 16)) {
- ctx_ctrl_set_ivlen = EVP_CTRL_GCM_SET_IVLEN;
- ctx_ctrl_get_tag = EVP_CTRL_GCM_GET_TAG;
- if (key.size == 16) cipher = EVP_aes_128_gcm();
- else if (key.size == 24) cipher = EVP_aes_192_gcm();
- else if (key.size == 32) cipher = EVP_aes_256_gcm();
- else enif_make_badarg(env);
- } else
- enif_make_badarg(env);
- } else
-#endif
-#if defined(HAVE_CCM)
- if (type == atom_aes_ccm) {
- if ((7 <= iv.size && iv.size <= 13)
- && (4 <= tag_len && tag_len <= 16)
- && ((tag_len & 1) == 0)
- ) {
- ctx_ctrl_set_ivlen = EVP_CTRL_CCM_SET_IVLEN;
- ctx_ctrl_get_tag = EVP_CTRL_CCM_GET_TAG;
- if (key.size == 16) cipher = EVP_aes_128_ccm();
- else if (key.size == 24) cipher = EVP_aes_192_ccm();
- else if (key.size == 32) cipher = EVP_aes_256_ccm();
- else enif_make_badarg(env);
- } else
- enif_make_badarg(env);
- } else
-#endif
-#if defined(HAVE_CHACHA20_POLY1305)
- if (type == atom_chacha20_poly1305) {
- if ((key.size == 32)
- && (1 <= iv.size && iv.size <= 16)
- && (tag_len == 16)
- ) {
- ctx_ctrl_set_ivlen = EVP_CTRL_AEAD_SET_IVLEN;
- ctx_ctrl_get_tag = EVP_CTRL_AEAD_GET_TAG,
- cipher = EVP_chacha20_poly1305();
- } else enif_make_badarg(env);
- } else
-#endif
- return enif_raise_exception(env, atom_notsup);
-
- ctx = EVP_CIPHER_CTX_new();
- if (EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) goto out_err;
- if (EVP_CIPHER_CTX_ctrl(ctx, ctx_ctrl_set_ivlen, iv.size, NULL) != 1) goto out_err;
-
-#if defined(HAVE_CCM)
- if (type == atom_aes_ccm) {
- if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, tag_len, NULL) != 1) goto out_err;
- if (EVP_EncryptInit_ex(ctx, NULL, NULL, key.data, iv.data) != 1) goto out_err;
- if (EVP_EncryptUpdate(ctx, NULL, &len, NULL, in.size) != 1) goto out_err;
- } else
-#endif
- if (EVP_EncryptInit_ex(ctx, NULL, NULL, key.data, iv.data) != 1) goto out_err;
-
- if (EVP_EncryptUpdate(ctx, NULL, &len, aad.data, aad.size) != 1) goto out_err;
-
- outp = enif_make_new_binary(env, in.size, &out);
-
- if (EVP_EncryptUpdate(ctx, outp, &len, in.data, in.size) != 1) goto out_err;
- if (EVP_EncryptFinal_ex(ctx, outp/*+len*/, &len) != 1) goto out_err;
-
- tagp = enif_make_new_binary(env, tag_len, &out_tag);
-
- if (EVP_CIPHER_CTX_ctrl(ctx, ctx_ctrl_get_tag, tag_len, tagp) != 1) goto out_err;
-
- EVP_CIPHER_CTX_free(ctx);
- CONSUME_REDS(env, in);
- return enif_make_tuple2(env, out, out_tag);
-
-out_err:
- EVP_CIPHER_CTX_free(ctx);
- return atom_error;
-
-#else
- return enif_raise_exception(env, atom_notsup);
-#endif
-}
-
-static ERL_NIF_TERM aead_decrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Type,Key,Iv,AAD,In,Tag) */
-#if defined(HAVE_AEAD)
- EVP_CIPHER_CTX *ctx;
- const EVP_CIPHER *cipher = NULL;
- ErlNifBinary key, iv, aad, in, tag;
- unsigned char *outp;
- ERL_NIF_TERM type, out;
- int len, ctx_ctrl_set_ivlen, ctx_ctrl_set_tag;
-
- type = argv[0];
-#if defined(HAVE_GCM_EVP_DECRYPT_BUG)
- if (type == atom_aes_gcm)
- return aes_gcm_decrypt_NO_EVP(env, argc, argv);
-#endif
-
- if (!enif_is_atom(env, type)
- || !enif_inspect_iolist_as_binary(env, argv[1], &key)
- || !enif_inspect_binary(env, argv[2], &iv)
- || !enif_inspect_iolist_as_binary(env, argv[3], &aad)
- || !enif_inspect_iolist_as_binary(env, argv[4], &in)
- || !enif_inspect_iolist_as_binary(env, argv[5], &tag)) {
- return enif_make_badarg(env);
- }
-
- /* Use cipher_type some day. Must check block_encrypt|decrypt first */
-#if defined(HAVE_GCM)
- if (type == atom_aes_gcm) {
- if (iv.size > 0) {
- ctx_ctrl_set_ivlen = EVP_CTRL_GCM_SET_IVLEN;
- ctx_ctrl_set_tag = EVP_CTRL_GCM_SET_TAG;
- if (key.size == 16) cipher = EVP_aes_128_gcm();
- else if (key.size == 24) cipher = EVP_aes_192_gcm();
- else if (key.size == 32) cipher = EVP_aes_256_gcm();
- else enif_make_badarg(env);
- } else
- enif_make_badarg(env);
- } else
-#endif
-#if defined(HAVE_CCM)
- if (type == atom_aes_ccm) {
- if (iv.size > 0) {
- ctx_ctrl_set_ivlen = EVP_CTRL_CCM_SET_IVLEN;
- if (key.size == 16) cipher = EVP_aes_128_ccm();
- else if (key.size == 24) cipher = EVP_aes_192_ccm();
- else if (key.size == 32) cipher = EVP_aes_256_ccm();
- else enif_make_badarg(env);
- } else
- enif_make_badarg(env);
- } else
-#endif
-#if defined(HAVE_CHACHA20_POLY1305)
- if (type == atom_chacha20_poly1305) {
- if ((key.size == 32)
- && (1 <= iv.size && iv.size <= 16)
- && tag.size == 16
- ) {
- ctx_ctrl_set_ivlen = EVP_CTRL_AEAD_SET_IVLEN;
- ctx_ctrl_set_tag = EVP_CTRL_AEAD_SET_TAG;
- cipher = EVP_chacha20_poly1305();
- } else enif_make_badarg(env);
- } else
-#endif
- return enif_raise_exception(env, atom_notsup);
-
- outp = enif_make_new_binary(env, in.size, &out);
-
- ctx = EVP_CIPHER_CTX_new();
- if (EVP_DecryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) goto out_err;
- if (EVP_CIPHER_CTX_ctrl(ctx, ctx_ctrl_set_ivlen, iv.size, NULL) != 1) goto out_err;
-
-#if defined(HAVE_CCM)
- if (type == atom_aes_ccm) {
- if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, tag.size, tag.data) != 1) goto out_err;
- }
-#endif
-
- if (EVP_DecryptInit_ex(ctx, NULL, NULL, key.data, iv.data) != 1) goto out_err;
-
-#if defined(HAVE_CCM)
- if (type == atom_aes_ccm) {
- if (1 != EVP_DecryptUpdate(ctx, NULL, &len, NULL, in.size)) goto out_err;
- }
-#endif
-
- if (EVP_DecryptUpdate(ctx, NULL, &len, aad.data, aad.size) != 1) goto out_err;
- if (EVP_DecryptUpdate(ctx, outp, &len, in.data, in.size) != 1) goto out_err;
-
-#if defined(HAVE_GCM) || defined(HAVE_CHACHA20_POLY1305)
- if (type == atom_aes_gcm) {
- if (EVP_CIPHER_CTX_ctrl(ctx, ctx_ctrl_set_tag, tag.size, tag.data) != 1) goto out_err;
- if (EVP_DecryptFinal_ex(ctx, outp+len, &len) != 1) goto out_err;
- }
-#endif
- EVP_CIPHER_CTX_free(ctx);
-
- CONSUME_REDS(env, in);
- return out;
-
-out_err:
- EVP_CIPHER_CTX_free(ctx);
- return atom_error;
-#else
- return enif_raise_exception(env, atom_notsup);
-#endif
-}
-
-#ifdef HAVE_GCM_EVP_DECRYPT_BUG
-static ERL_NIF_TERM aes_gcm_decrypt_NO_EVP(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Type,Key,Iv,AAD,In,Tag) */
- GCM128_CONTEXT *ctx;
- ErlNifBinary key, iv, aad, in, tag;
- AES_KEY aes_key;
- unsigned char *outp;
- ERL_NIF_TERM out;
-
- if (!enif_inspect_iolist_as_binary(env, argv[1], &key)
- || AES_set_encrypt_key(key.data, key.size*8, &aes_key) != 0
- || !enif_inspect_binary(env, argv[2], &iv) || iv.size == 0
- || !enif_inspect_iolist_as_binary(env, argv[3], &aad)
- || !enif_inspect_iolist_as_binary(env, argv[4], &in)
- || !enif_inspect_iolist_as_binary(env, argv[5], &tag)) {
- return enif_make_badarg(env);
- }
-
- if (!(ctx = CRYPTO_gcm128_new(&aes_key, (block128_f)AES_encrypt)))
- return atom_error;
-
- CRYPTO_gcm128_setiv(ctx, iv.data, iv.size);
-
- if (CRYPTO_gcm128_aad(ctx, aad.data, aad.size))
- goto out_err;
-
- outp = enif_make_new_binary(env, in.size, &out);
-
- /* decrypt */
- if (CRYPTO_gcm128_decrypt(ctx, in.data, outp, in.size))
- goto out_err;
-
- /* calculate and check the tag */
- if (CRYPTO_gcm128_finish(ctx, tag.data, tag.size))
- goto out_err;
-
- CRYPTO_gcm128_release(ctx);
- CONSUME_REDS(env, in);
-
- return out;
-
-out_err:
- CRYPTO_gcm128_release(ctx);
- return atom_error;
-}
-#endif /* HAVE_GCM_EVP_DECRYPT_BUG */
-
-
-static ERL_NIF_TERM chacha20_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Key, IV) */
-#if defined(HAVE_CHACHA20)
- ErlNifBinary key_bin, ivec_bin;
- struct evp_cipher_ctx *ctx;
- const EVP_CIPHER *cipher;
- ERL_NIF_TERM ret;
-
- if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin)
- || !enif_inspect_binary(env, argv[1], &ivec_bin)
- || key_bin.size != 32
- || ivec_bin.size != 16) {
- return enif_make_badarg(env);
- }
-
- cipher = EVP_chacha20();
-
- ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx));
- ctx->ctx = EVP_CIPHER_CTX_new();
-
-
- EVP_CipherInit_ex(ctx->ctx, cipher, NULL,
- key_bin.data, ivec_bin.data, 1);
- EVP_CIPHER_CTX_set_padding(ctx->ctx, 0);
- ret = enif_make_resource(env, ctx);
- enif_release_resource(ctx);
- return ret;
-#else
- return enif_raise_exception(env, atom_notsup);
-#endif
-};
-
-static ERL_NIF_TERM chacha20_stream_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (State, Data) */
-#if defined(HAVE_CHACHA20)
- struct evp_cipher_ctx *ctx, *new_ctx;
- ErlNifBinary data_bin;
- ERL_NIF_TERM ret, cipher_term;
- unsigned char *out;
- int outl = 0;
-
- if (!enif_get_resource(env, argv[0], evp_cipher_ctx_rtype, (void**)&ctx)
- || !enif_inspect_iolist_as_binary(env, argv[1], &data_bin)) {
- return enif_make_badarg(env);
- }
- new_ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx));
- new_ctx->ctx = EVP_CIPHER_CTX_new();
- EVP_CIPHER_CTX_copy(new_ctx->ctx, ctx->ctx);
- out = enif_make_new_binary(env, data_bin.size, &cipher_term);
- EVP_CipherUpdate(new_ctx->ctx, out, &outl, data_bin.data, data_bin.size);
- ASSERT(outl == data_bin.size);
-
- ret = enif_make_tuple2(env, enif_make_resource(env, new_ctx), cipher_term);
- enif_release_resource(new_ctx);
- CONSUME_REDS(env,data_bin);
- return ret;
-#else
- return enif_raise_exception(env, atom_notsup);
-#endif
-};
-
-
-static ERL_NIF_TERM strong_rand_bytes_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Bytes) */
- unsigned bytes;
- unsigned char* data;
- ERL_NIF_TERM ret;
-
- if (!enif_get_uint(env, argv[0], &bytes)) {
- return enif_make_badarg(env);
- }
- data = enif_make_new_binary(env, bytes, &ret);
- if ( RAND_bytes(data, bytes) != 1) {
- return atom_false;
- }
- ERL_VALGRIND_MAKE_MEM_DEFINED(data, bytes);
- return ret;
-}
-
-
-static int get_bn_from_mpint(ErlNifEnv* env, ERL_NIF_TERM term, BIGNUM** bnp)
-{
- ErlNifBinary bin;
- int sz;
- if (!enif_inspect_binary(env,term,&bin)) {
- return 0;
- }
- ERL_VALGRIND_ASSERT_MEM_DEFINED(bin.data, bin.size);
- sz = bin.size - 4;
- if (sz < 0 || get_int32(bin.data) != sz) {
- return 0;
- }
- *bnp = BN_bin2bn(bin.data+4, sz, NULL);
- return 1;
-}
-
-static int get_bn_from_bin(ErlNifEnv* env, ERL_NIF_TERM term, BIGNUM** bnp)
-{
- ErlNifBinary bin;
- if (!enif_inspect_binary(env,term,&bin)) {
- return 0;
- }
- ERL_VALGRIND_ASSERT_MEM_DEFINED(bin.data, bin.size);
- *bnp = BN_bin2bn(bin.data, bin.size, NULL);
- return 1;
-}
-
-static ERL_NIF_TERM bin_from_bn(ErlNifEnv* env, const BIGNUM *bn)
-{
- int bn_len;
- unsigned char *bin_ptr;
- ERL_NIF_TERM term;
-
- /* Copy the bignum into an erlang binary. */
- bn_len = BN_num_bytes(bn);
- bin_ptr = enif_make_new_binary(env, bn_len, &term);
- BN_bn2bin(bn, bin_ptr);
-
- return term;
-}
-
-static ERL_NIF_TERM strong_rand_range_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Range) */
- BIGNUM *bn_range, *bn_rand;
- ERL_NIF_TERM ret;
-
- if(!get_bn_from_bin(env, argv[0], &bn_range)) {
- return enif_make_badarg(env);
- }
-
- bn_rand = BN_new();
- if (BN_rand_range(bn_rand, bn_range) != 1) {
- ret = atom_false;
- }
- else {
- ret = bin_from_bn(env, bn_rand);
- }
- BN_free(bn_rand);
- BN_free(bn_range);
- return ret;
-}
-
-static ERL_NIF_TERM rand_uniform_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Lo,Hi) */
- BIGNUM *bn_from = NULL, *bn_to, *bn_rand;
- unsigned char* data;
- unsigned dlen;
- ERL_NIF_TERM ret;
-
- if (!get_bn_from_mpint(env, argv[0], &bn_from)
- || !get_bn_from_mpint(env, argv[1], &bn_rand)) {
- if (bn_from) BN_free(bn_from);
- return enif_make_badarg(env);
- }
-
- bn_to = BN_new();
- BN_sub(bn_to, bn_rand, bn_from);
- BN_pseudo_rand_range(bn_rand, bn_to);
- BN_add(bn_rand, bn_rand, bn_from);
- dlen = BN_num_bytes(bn_rand);
- data = enif_make_new_binary(env, dlen+4, &ret);
- put_int32(data, dlen);
- BN_bn2bin(bn_rand, data+4);
- ERL_VALGRIND_MAKE_MEM_DEFINED(data+4, dlen);
- BN_free(bn_rand);
- BN_free(bn_from);
- BN_free(bn_to);
- return ret;
-}
-
-static ERL_NIF_TERM mod_exp_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Base,Exponent,Modulo,bin_hdr) */
- BIGNUM *bn_base=NULL, *bn_exponent=NULL, *bn_modulo=NULL, *bn_result;
- BN_CTX *bn_ctx;
- unsigned char* ptr;
- unsigned dlen;
- unsigned bin_hdr; /* return type: 0=plain binary, 4: mpint */
- unsigned extra_byte;
- ERL_NIF_TERM ret;
-
- if (!get_bn_from_bin(env, argv[0], &bn_base)
- || !get_bn_from_bin(env, argv[1], &bn_exponent)
- || !get_bn_from_bin(env, argv[2], &bn_modulo)
- || !enif_get_uint(env,argv[3],&bin_hdr) || (bin_hdr & ~4)) {
-
- if (bn_base) BN_free(bn_base);
- if (bn_exponent) BN_free(bn_exponent);
- if (bn_modulo) BN_free(bn_modulo);
- return enif_make_badarg(env);
- }
- bn_result = BN_new();
- bn_ctx = BN_CTX_new();
- BN_mod_exp(bn_result, bn_base, bn_exponent, bn_modulo, bn_ctx);
- dlen = BN_num_bytes(bn_result);
- extra_byte = bin_hdr && BN_is_bit_set(bn_result, dlen*8-1);
- ptr = enif_make_new_binary(env, bin_hdr+extra_byte+dlen, &ret);
- if (bin_hdr) {
- put_int32(ptr, extra_byte+dlen);
- ptr[4] = 0; /* extra zeroed byte to ensure a positive mpint */
- ptr += bin_hdr + extra_byte;
- }
- BN_bn2bin(bn_result, ptr);
- BN_free(bn_result);
- BN_CTX_free(bn_ctx);
- BN_free(bn_modulo);
- BN_free(bn_exponent);
- BN_free(bn_base);
- return ret;
-}
-
-static void init_digest_types(ErlNifEnv* env)
-{
- struct digest_type_t* p = digest_types;
-
- for (p = digest_types; p->type.str; p++) {
- p->type.atom = enif_make_atom(env, p->type.str);
- if (p->md.funcp)
- p->md.p = p->md.funcp();
- }
- p->type.atom = atom_false; /* end marker */
-}
-
-static void init_cipher_types(ErlNifEnv* env)
-{
- struct cipher_type_t* p = cipher_types;
-
- for (p = cipher_types; p->type.str; p++) {
- p->type.atom = enif_make_atom(env, p->type.str);
- if (p->cipher.funcp)
- p->cipher.p = p->cipher.funcp();
- }
- p->type.atom = atom_false; /* end marker */
-}
-
-static struct digest_type_t* get_digest_type(ERL_NIF_TERM type)
-{
- struct digest_type_t* p = NULL;
- for (p = digest_types; p->type.atom != atom_false; p++) {
- if (type == p->type.atom) {
- return p;
- }
- }
- return NULL;
-}
-
-static struct cipher_type_t* get_cipher_type(ERL_NIF_TERM type, size_t key_len)
-{
- struct cipher_type_t* p = NULL;
- for (p = cipher_types; p->type.atom != atom_false; p++) {
- if (type == p->type.atom && (!p->key_len || key_len == p->key_len)) {
- return p;
- }
- }
- return NULL;
-}
-
-
-static ERL_NIF_TERM do_exor(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Data1, Data2) */
- ErlNifBinary d1, d2;
- unsigned char* ret_ptr;
- int i;
- ERL_NIF_TERM ret;
-
- if (!enif_inspect_iolist_as_binary(env,argv[0], &d1)
- || !enif_inspect_iolist_as_binary(env,argv[1], &d2)
- || d1.size != d2.size) {
- return enif_make_badarg(env);
- }
- ret_ptr = enif_make_new_binary(env, d1.size, &ret);
-
- for (i=0; i<d1.size; i++) {
- ret_ptr[i] = d1.data[i] ^ d2.data[i];
- }
- CONSUME_REDS(env,d1);
- return ret;
-}
-
-static ERL_NIF_TERM rc4_set_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Key) */
-#ifndef OPENSSL_NO_RC4
- ErlNifBinary key;
- ERL_NIF_TERM ret;
-
- CHECK_NO_FIPS_MODE();
-
- if (!enif_inspect_iolist_as_binary(env,argv[0], &key)) {
- return enif_make_badarg(env);
- }
- RC4_set_key((RC4_KEY*)enif_make_new_binary(env, sizeof(RC4_KEY), &ret),
- key.size, key.data);
- return ret;
-#else
- return enif_raise_exception(env, atom_notsup);
-#endif
-}
-
-static ERL_NIF_TERM rc4_encrypt_with_state(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (State, Data) */
-#ifndef OPENSSL_NO_RC4
- ErlNifBinary state, data;
- RC4_KEY* rc4_key;
- ERL_NIF_TERM new_state, new_data;
-
- CHECK_NO_FIPS_MODE();
-
- if (!enif_inspect_iolist_as_binary(env,argv[0], &state)
- || state.size != sizeof(RC4_KEY)
- || !enif_inspect_iolist_as_binary(env,argv[1], &data)) {
- return enif_make_badarg(env);
- }
- rc4_key = (RC4_KEY*)enif_make_new_binary(env, sizeof(RC4_KEY), &new_state);
- memcpy(rc4_key, state.data, sizeof(RC4_KEY));
- RC4(rc4_key, data.size, data.data,
- enif_make_new_binary(env, data.size, &new_data));
- CONSUME_REDS(env,data);
- return enif_make_tuple2(env,new_state,new_data);
-#else
- return enif_raise_exception(env, atom_notsup);
-#endif
-}
-
-static int get_rsa_private_key(ErlNifEnv* env, ERL_NIF_TERM key, RSA *rsa)
-{
- /* key=[E,N,D]|[E,N,D,P1,P2,E1,E2,C] */
- ERL_NIF_TERM head, tail;
- BIGNUM *e, *n, *d;
- BIGNUM *p, *q;
- BIGNUM *dmp1, *dmq1, *iqmp;
-
- if (!enif_get_list_cell(env, key, &head, &tail)
- || !get_bn_from_bin(env, head, &e)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &n)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &d)) {
- return 0;
- }
- (void) RSA_set0_key(rsa, n, e, d);
- if (enif_is_empty_list(env, tail)) {
- return 1;
- }
- if (!enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &p)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &q)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &dmp1)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &dmq1)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &iqmp)
- || !enif_is_empty_list(env, tail)) {
- return 0;
- }
- (void) RSA_set0_factors(rsa, p, q);
- (void) RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp);
- return 1;
-}
-
-
-static int get_rsa_public_key(ErlNifEnv* env, ERL_NIF_TERM key, RSA *rsa)
-{
- /* key=[E,N] */
- ERL_NIF_TERM head, tail;
- BIGNUM *e, *n;
-
- if (!enif_get_list_cell(env, key, &head, &tail)
- || !get_bn_from_bin(env, head, &e)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &n)
- || !enif_is_empty_list(env, tail)) {
- return 0;
- }
-
- (void) RSA_set0_key(rsa, n, e, NULL);
- return 1;
-}
-
-#ifdef HAVE_EDDSA
- static int get_eddsa_key(ErlNifEnv* env, int public, ERL_NIF_TERM key, EVP_PKEY **pkey)
-{
- /* key=[K] */
- ERL_NIF_TERM head, tail, tail2, algo;
- ErlNifBinary bin;
- int type;
-
- if (!enif_get_list_cell(env, key, &head, &tail)
- || !enif_inspect_binary(env, head, &bin)
- || !enif_get_list_cell(env, tail, &algo, &tail2)
- || !enif_is_empty_list(env, tail2)) {
- return 0;
- }
- if (algo == atom_ed25519) type = EVP_PKEY_ED25519;
- else if (algo == atom_ed448) type = EVP_PKEY_ED448;
- else
- return 0;
-
- if (public)
- *pkey = EVP_PKEY_new_raw_public_key(type, NULL, bin.data, bin.size);
- else
- *pkey = EVP_PKEY_new_raw_private_key(type, NULL, bin.data, bin.size);
-
- if (!pkey)
- return 0;
- return 1;
-}
-#endif
-
-static int get_dss_private_key(ErlNifEnv* env, ERL_NIF_TERM key, DSA *dsa)
-{
- /* key=[P,Q,G,KEY] */
- ERL_NIF_TERM head, tail;
- BIGNUM *dsa_p = NULL, *dsa_q = NULL, *dsa_g = NULL;
- BIGNUM *dummy_pub_key, *priv_key = NULL;
-
- if (!enif_get_list_cell(env, key, &head, &tail)
- || !get_bn_from_bin(env, head, &dsa_p)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &dsa_q)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &dsa_g)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &priv_key)
- || !enif_is_empty_list(env,tail)) {
- if (dsa_p) BN_free(dsa_p);
- if (dsa_q) BN_free(dsa_q);
- if (dsa_g) BN_free(dsa_g);
- if (priv_key) BN_free(priv_key);
- return 0;
- }
-
- /* Note: DSA_set0_key() does not allow setting only the
- * private key, although DSA_sign() does not use the
- * public key. Work around this limitation by setting
- * the public key to a copy of the private key.
- */
- dummy_pub_key = BN_dup(priv_key);
-
- DSA_set0_pqg(dsa, dsa_p, dsa_q, dsa_g);
- DSA_set0_key(dsa, dummy_pub_key, priv_key);
- return 1;
-}
-
-
-static int get_dss_public_key(ErlNifEnv* env, ERL_NIF_TERM key, DSA *dsa)
-{
- /* key=[P, Q, G, Y] */
- ERL_NIF_TERM head, tail;
- BIGNUM *dsa_p = NULL, *dsa_q = NULL, *dsa_g = NULL, *dsa_y = NULL;
-
- if (!enif_get_list_cell(env, key, &head, &tail)
- || !get_bn_from_bin(env, head, &dsa_p)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &dsa_q)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &dsa_g)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &dsa_y)
- || !enif_is_empty_list(env,tail)) {
- if (dsa_p) BN_free(dsa_p);
- if (dsa_q) BN_free(dsa_q);
- if (dsa_g) BN_free(dsa_g);
- if (dsa_y) BN_free(dsa_y);
- return 0;
- }
-
- DSA_set0_pqg(dsa, dsa_p, dsa_q, dsa_g);
- DSA_set0_key(dsa, dsa_y, NULL);
- return 1;
-}
-
-/* Creates a term which can be parsed by get_rsa_private_key(). This is a list of plain integer binaries (not mpints). */
-static ERL_NIF_TERM put_rsa_private_key(ErlNifEnv* env, const RSA *rsa)
-{
- ERL_NIF_TERM result[8];
- const BIGNUM *n, *e, *d, *p, *q, *dmp1, *dmq1, *iqmp;
-
- /* Return at least [E,N,D] */
- n = NULL; e = NULL; d = NULL;
- RSA_get0_key(rsa, &n, &e, &d);
-
- result[0] = bin_from_bn(env, e); // Exponent E
- result[1] = bin_from_bn(env, n); // Modulus N = p*q
- result[2] = bin_from_bn(env, d); // Exponent D
-
- /* Check whether the optional additional parameters are available */
- p = NULL; q = NULL;
- RSA_get0_factors(rsa, &p, &q);
- dmp1 = NULL; dmq1 = NULL; iqmp = NULL;
- RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp);
-
- if (p && q && dmp1 && dmq1 && iqmp) {
- result[3] = bin_from_bn(env, p); // Factor p
- result[4] = bin_from_bn(env, q); // Factor q
- result[5] = bin_from_bn(env, dmp1); // D mod (p-1)
- result[6] = bin_from_bn(env, dmq1); // D mod (q-1)
- result[7] = bin_from_bn(env, iqmp); // (1/q) mod p
-
- return enif_make_list_from_array(env, result, 8);
- } else {
- return enif_make_list_from_array(env, result, 3);
- }
-}
-
-static int check_erlang_interrupt(int maj, int min, BN_GENCB *ctxt)
-{
- ErlNifEnv *env = BN_GENCB_get_arg(ctxt);
-
- if (!enif_is_current_process_alive(env)) {
- return 0;
- } else {
- return 1;
- }
-}
-
-static ERL_NIF_TERM rsa_generate_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (ModulusSize, PublicExponent) */
- int modulus_bits;
- BIGNUM *pub_exp, *three;
- RSA *rsa;
- int success;
- ERL_NIF_TERM result;
- BN_GENCB *intr_cb;
-#ifndef HAVE_OPAQUE_BN_GENCB
- BN_GENCB intr_cb_buf;
-#endif
-
- if (!enif_get_int(env, argv[0], &modulus_bits) || modulus_bits < 256) {
- return enif_make_badarg(env);
- }
-
- if (!get_bn_from_bin(env, argv[1], &pub_exp)) {
- return enif_make_badarg(env);
- }
-
- /* Make sure the public exponent is large enough (at least 3).
- * Without this, RSA_generate_key_ex() can run forever. */
- three = BN_new();
- BN_set_word(three, 3);
- success = BN_cmp(pub_exp, three);
- BN_free(three);
- if (success < 0) {
- BN_free(pub_exp);
- return enif_make_badarg(env);
- }
-
- /* For large keys, prime generation can take many seconds. Set up
- * the callback which we use to test whether the process has been
- * interrupted. */
-#ifdef HAVE_OPAQUE_BN_GENCB
- intr_cb = BN_GENCB_new();
-#else
- intr_cb = &intr_cb_buf;
-#endif
- BN_GENCB_set(intr_cb, check_erlang_interrupt, env);
-
- rsa = RSA_new();
- success = RSA_generate_key_ex(rsa, modulus_bits, pub_exp, intr_cb);
- BN_free(pub_exp);
-
-#ifdef HAVE_OPAQUE_BN_GENCB
- BN_GENCB_free(intr_cb);
-#endif
-
- if (!success) {
- RSA_free(rsa);
- return atom_error;
- }
-
- result = put_rsa_private_key(env, rsa);
- RSA_free(rsa);
-
- return result;
-}
-
-static ERL_NIF_TERM rsa_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{
- /* RSA key generation can take a long time (>1 sec for a large
- * modulus), so schedule it as a CPU-bound operation. */
- return enif_schedule_nif(env, "rsa_generate_key",
- ERL_NIF_DIRTY_JOB_CPU_BOUND,
- rsa_generate_key, argc, argv);
-}
-
-static ERL_NIF_TERM dh_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (PrivKey|undefined, DHParams=[P,G], Mpint, Len|0) */
- DH *dh_params = NULL;
- int mpint; /* 0 or 4 */
-
- {
- ERL_NIF_TERM head, tail;
- BIGNUM
- *dh_p = NULL,
- *dh_g = NULL,
- *priv_key_in = NULL;
- unsigned long
- len = 0;
-
- if (!(get_bn_from_bin(env, argv[0], &priv_key_in)
- || argv[0] == atom_undefined)
- || !enif_get_list_cell(env, argv[1], &head, &tail)
- || !get_bn_from_bin(env, head, &dh_p)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &dh_g)
- || !enif_is_empty_list(env, tail)
- || !enif_get_int(env, argv[2], &mpint) || (mpint & ~4)
- || !enif_get_ulong(env, argv[3], &len)
-
- /* Load dh_params with values to use by the generator.
- Mem mgmnt transfered from dh_p etc to dh_params */
- || !(dh_params = DH_new())
- || (priv_key_in && !DH_set0_key(dh_params, NULL, priv_key_in))
- || !DH_set0_pqg(dh_params, dh_p, NULL, dh_g)
- ) {
- if (priv_key_in) BN_free(priv_key_in);
- if (dh_p) BN_free(dh_p);
- if (dh_g) BN_free(dh_g);
- if (dh_params) DH_free(dh_params);
- return enif_make_badarg(env);
- }
-
- if (len) {
- if (len < BN_num_bits(dh_p))
- DH_set_length(dh_params, len);
- else {
- if (priv_key_in) BN_free(priv_key_in);
- if (dh_p) BN_free(dh_p);
- if (dh_g) BN_free(dh_g);
- if (dh_params) DH_free(dh_params);
- return enif_make_badarg(env);
- }
- }
- }
-
-#ifdef HAS_EVP_PKEY_CTX
- {
- EVP_PKEY_CTX *ctx;
- EVP_PKEY *dhkey, *params;
- int success;
-
- params = EVP_PKEY_new();
- success = EVP_PKEY_set1_DH(params, dh_params); /* set the key referenced by params to dh_params... */
- DH_free(dh_params); /* ...dh_params (and params) must be freed */
- if (!success) return atom_error;
-
- ctx = EVP_PKEY_CTX_new(params, NULL);
- EVP_PKEY_free(params);
- if (!ctx) {
- return atom_error;
- }
-
- if (!EVP_PKEY_keygen_init(ctx)) {
- /* EVP_PKEY_CTX_free(ctx); */
- return atom_error;
- }
-
- dhkey = EVP_PKEY_new();
- if (!EVP_PKEY_keygen(ctx, &dhkey)) { /* "performs a key generation operation, the ... */
- /*... generated key is written to ppkey." (=last arg) */
- /* EVP_PKEY_CTX_free(ctx); */
- /* EVP_PKEY_free(dhkey); */
- return atom_error;
- }
-
- dh_params = EVP_PKEY_get1_DH(dhkey); /* return the referenced key. dh_params and dhkey must be freed */
- EVP_PKEY_free(dhkey);
- if (!dh_params) {
- /* EVP_PKEY_CTX_free(ctx); */
- return atom_error;
- }
- EVP_PKEY_CTX_free(ctx);
- }
-#else
- if (!DH_generate_key(dh_params)) return atom_error;
-#endif
- {
- unsigned char *pub_ptr, *prv_ptr;
- int pub_len, prv_len;
- ERL_NIF_TERM ret_pub, ret_prv;
- const BIGNUM *pub_key_gen, *priv_key_gen;
-
- DH_get0_key(dh_params,
- &pub_key_gen, &priv_key_gen); /* Get pub_key_gen and priv_key_gen.
- "The values point to the internal representation of
- the public key and private key values. This memory
- should not be freed directly." says man */
- pub_len = BN_num_bytes(pub_key_gen);
- prv_len = BN_num_bytes(priv_key_gen);
- pub_ptr = enif_make_new_binary(env, pub_len+mpint, &ret_pub);
- prv_ptr = enif_make_new_binary(env, prv_len+mpint, &ret_prv);
- if (mpint) {
- put_int32(pub_ptr, pub_len); pub_ptr += 4;
- put_int32(prv_ptr, prv_len); prv_ptr += 4;
- }
- BN_bn2bin(pub_key_gen, pub_ptr);
- BN_bn2bin(priv_key_gen, prv_ptr);
- ERL_VALGRIND_MAKE_MEM_DEFINED(pub_ptr, pub_len);
- ERL_VALGRIND_MAKE_MEM_DEFINED(prv_ptr, prv_len);
-
- DH_free(dh_params);
-
- return enif_make_tuple2(env, ret_pub, ret_prv);
- }
-}
-
-static ERL_NIF_TERM dh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (OthersPublicKey, MyPrivateKey, DHParams=[P,G]) */
- BIGNUM *other_pub_key = NULL,
- *dh_p = NULL,
- *dh_g = NULL;
- DH *dh_priv = DH_new();
-
- /* Check the arguments and get
- my private key (dh_priv),
- the peer's public key (other_pub_key),
- the parameters p & q
- */
-
- {
- BIGNUM *dummy_pub_key = NULL,
- *priv_key = NULL;
- ERL_NIF_TERM head, tail;
-
- if (!get_bn_from_bin(env, argv[0], &other_pub_key)
- || !get_bn_from_bin(env, argv[1], &priv_key)
- || !enif_get_list_cell(env, argv[2], &head, &tail)
- || !get_bn_from_bin(env, head, &dh_p)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &dh_g)
- || !enif_is_empty_list(env, tail)
-
- /* Note: DH_set0_key() does not allow setting only the
- * private key, although DH_compute_key() does not use the
- * public key. Work around this limitation by setting
- * the public key to a copy of the private key.
- */
- || !(dummy_pub_key = BN_dup(priv_key))
- || !DH_set0_key(dh_priv, dummy_pub_key, priv_key)
- || !DH_set0_pqg(dh_priv, dh_p, NULL, dh_g)
- ) {
- if (dh_p) BN_free(dh_p);
- if (dh_g) BN_free(dh_g);
- if (other_pub_key) BN_free(other_pub_key);
- if (dummy_pub_key) BN_free(dummy_pub_key);
- if (priv_key) BN_free(priv_key);
- return enif_make_badarg(env);
- }
- }
- {
- ErlNifBinary ret_bin;
- int size;
-
- enif_alloc_binary(DH_size(dh_priv), &ret_bin);
- size = DH_compute_key(ret_bin.data, other_pub_key, dh_priv);
- BN_free(other_pub_key);
- DH_free(dh_priv);
- if (size<=0) {
- enif_release_binary(&ret_bin);
- return atom_error;
- }
-
- if (size != ret_bin.size) enif_realloc_binary(&ret_bin, size);
- return enif_make_binary(env, &ret_bin);
- }
-}
-
-
-static ERL_NIF_TERM srp_value_B_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Multiplier, Verifier, Generator, Exponent, Prime) */
- BIGNUM *bn_verifier = NULL;
- BIGNUM *bn_exponent = NULL, *bn_generator = NULL, *bn_prime = NULL, *bn_multiplier = NULL, *bn_result;
- BN_CTX *bn_ctx;
- unsigned char* ptr;
- unsigned dlen;
- ERL_NIF_TERM ret;
-
- CHECK_NO_FIPS_MODE();
-
- if (!get_bn_from_bin(env, argv[0], &bn_multiplier)
- || !get_bn_from_bin(env, argv[1], &bn_verifier)
- || !get_bn_from_bin(env, argv[2], &bn_generator)
- || !get_bn_from_bin(env, argv[3], &bn_exponent)
- || !get_bn_from_bin(env, argv[4], &bn_prime)) {
- if (bn_multiplier) BN_free(bn_multiplier);
- if (bn_verifier) BN_free(bn_verifier);
- if (bn_generator) BN_free(bn_generator);
- if (bn_exponent) BN_free(bn_exponent);
- if (bn_prime) BN_free(bn_prime);
- return enif_make_badarg(env);
- }
-
- bn_result = BN_new();
- bn_ctx = BN_CTX_new();
-
- /* B = k*v + g^b % N */
-
- /* k * v */
- BN_mod_mul(bn_multiplier, bn_multiplier, bn_verifier, bn_prime, bn_ctx);
-
- /* g^b % N */
- BN_mod_exp(bn_result, bn_generator, bn_exponent, bn_prime, bn_ctx);
-
- /* k*v + g^b % N */
- BN_mod_add(bn_result, bn_result, bn_multiplier, bn_prime, bn_ctx);
-
- /* check that B % N != 0, reuse bn_multiplier */
- BN_nnmod(bn_multiplier, bn_result, bn_prime, bn_ctx);
- if (BN_is_zero(bn_multiplier)) {
- ret = atom_error;
- } else {
- dlen = BN_num_bytes(bn_result);
- ptr = enif_make_new_binary(env, dlen, &ret);
- BN_bn2bin(bn_result, ptr);
- }
- BN_free(bn_result);
- BN_CTX_free(bn_ctx);
- BN_free(bn_prime);
- BN_free(bn_generator);
- BN_free(bn_multiplier);
- BN_free(bn_exponent);
- BN_free(bn_verifier);
- return ret;
-}
-
-static ERL_NIF_TERM srp_user_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (a, u, B, Multiplier, Prime, Exponent, Generator) */
-/*
- <premaster secret> = (B - (k * g^x)) ^ (a + (u * x)) % N
-*/
- BIGNUM *bn_exponent = NULL, *bn_a = NULL;
- BIGNUM *bn_u = NULL, *bn_multiplier = NULL, *bn_exp2,
- *bn_base, *bn_prime = NULL, *bn_generator = NULL,
- *bn_B = NULL, *bn_result;
- BN_CTX *bn_ctx;
- unsigned char* ptr;
- unsigned dlen;
- ERL_NIF_TERM ret;
-
- CHECK_NO_FIPS_MODE();
-
- if (!get_bn_from_bin(env, argv[0], &bn_a)
- || !get_bn_from_bin(env, argv[1], &bn_u)
- || !get_bn_from_bin(env, argv[2], &bn_B)
- || !get_bn_from_bin(env, argv[3], &bn_multiplier)
- || !get_bn_from_bin(env, argv[4], &bn_generator)
- || !get_bn_from_bin(env, argv[5], &bn_exponent)
- || !get_bn_from_bin(env, argv[6], &bn_prime))
- {
- if (bn_exponent) BN_free(bn_exponent);
- if (bn_a) BN_free(bn_a);
- if (bn_u) BN_free(bn_u);
- if (bn_B) BN_free(bn_B);
- if (bn_multiplier) BN_free(bn_multiplier);
- if (bn_generator) BN_free(bn_generator);
- if (bn_prime) BN_free(bn_prime);
- return enif_make_badarg(env);
- }
-
- bn_ctx = BN_CTX_new();
- bn_result = BN_new();
-
- /* check that B % N != 0 */
- BN_nnmod(bn_result, bn_B, bn_prime, bn_ctx);
- if (BN_is_zero(bn_result)) {
- BN_free(bn_exponent);
- BN_free(bn_a);
- BN_free(bn_generator);
- BN_free(bn_prime);
- BN_free(bn_u);
- BN_free(bn_B);
- BN_CTX_free(bn_ctx);
-
- return atom_error;
- }
-
- /* (B - (k * g^x)) */
- bn_base = BN_new();
- BN_mod_exp(bn_result, bn_generator, bn_exponent, bn_prime, bn_ctx);
- BN_mod_mul(bn_result, bn_multiplier, bn_result, bn_prime, bn_ctx);
- BN_mod_sub(bn_base, bn_B, bn_result, bn_prime, bn_ctx);
-
- /* a + (u * x) */
- bn_exp2 = BN_new();
- BN_mul(bn_result, bn_u, bn_exponent, bn_ctx);
- BN_add(bn_exp2, bn_a, bn_result);
-
- /* (B - (k * g^x)) ^ (a + (u * x)) % N */
- BN_mod_exp(bn_result, bn_base, bn_exp2, bn_prime, bn_ctx);
-
- dlen = BN_num_bytes(bn_result);
- ptr = enif_make_new_binary(env, dlen, &ret);
- BN_bn2bin(bn_result, ptr);
- BN_free(bn_result);
- BN_CTX_free(bn_ctx);
-
- BN_free(bn_multiplier);
- BN_free(bn_exp2);
- BN_free(bn_u);
- BN_free(bn_exponent);
- BN_free(bn_a);
- BN_free(bn_B);
- BN_free(bn_base);
- BN_free(bn_generator);
- BN_free(bn_prime);
- return ret;
-}
-
-static ERL_NIF_TERM srp_host_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Verifier, b, u, A, Prime) */
-/*
- <premaster secret> = (A * v^u) ^ b % N
-*/
- BIGNUM *bn_b = NULL, *bn_verifier = NULL;
- BIGNUM *bn_prime = NULL, *bn_A = NULL, *bn_u = NULL, *bn_base, *bn_result;
- BN_CTX *bn_ctx;
- unsigned char* ptr;
- unsigned dlen;
- ERL_NIF_TERM ret;
-
- CHECK_NO_FIPS_MODE();
-
- if (!get_bn_from_bin(env, argv[0], &bn_verifier)
- || !get_bn_from_bin(env, argv[1], &bn_b)
- || !get_bn_from_bin(env, argv[2], &bn_u)
- || !get_bn_from_bin(env, argv[3], &bn_A)
- || !get_bn_from_bin(env, argv[4], &bn_prime))
- {
- if (bn_verifier) BN_free(bn_verifier);
- if (bn_b) BN_free(bn_b);
- if (bn_u) BN_free(bn_u);
- if (bn_A) BN_free(bn_A);
- if (bn_prime) BN_free(bn_prime);
- return enif_make_badarg(env);
- }
-
- bn_ctx = BN_CTX_new();
- bn_result = BN_new();
-
- /* check that A % N != 0 */
- BN_nnmod(bn_result, bn_A, bn_prime, bn_ctx);
- if (BN_is_zero(bn_result)) {
- BN_free(bn_b);
- BN_free(bn_verifier);
- BN_free(bn_prime);
- BN_free(bn_A);
- BN_CTX_free(bn_ctx);
-
- return atom_error;
- }
-
- /* (A * v^u) */
- bn_base = BN_new();
- BN_mod_exp(bn_base, bn_verifier, bn_u, bn_prime, bn_ctx);
- BN_mod_mul(bn_base, bn_A, bn_base, bn_prime, bn_ctx);
-
- /* (A * v^u) ^ b % N */
- BN_mod_exp(bn_result, bn_base, bn_b, bn_prime, bn_ctx);
-
- dlen = BN_num_bytes(bn_result);
- ptr = enif_make_new_binary(env, dlen, &ret);
- BN_bn2bin(bn_result, ptr);
- BN_free(bn_result);
- BN_CTX_free(bn_ctx);
-
- BN_free(bn_u);
- BN_free(bn_base);
- BN_free(bn_verifier);
- BN_free(bn_prime);
- BN_free(bn_A);
- BN_free(bn_b);
- return ret;
-}
-
-#if defined(HAVE_EC)
-static EC_KEY* ec_key_new(ErlNifEnv* env, ERL_NIF_TERM curve_arg)
-{
- EC_KEY *key = NULL;
- int c_arity = -1;
- const ERL_NIF_TERM* curve;
- ErlNifBinary seed;
- BIGNUM *p = NULL;
- BIGNUM *a = NULL;
- BIGNUM *b = NULL;
- BIGNUM *bn_order = NULL;
- BIGNUM *cofactor = NULL;
- EC_GROUP *group = NULL;
- EC_POINT *point = NULL;
-
- /* {Field, Prime, Point, Order, CoFactor} = Curve */
- if (enif_get_tuple(env,curve_arg,&c_arity,&curve)
- && c_arity == 5
- && get_bn_from_bin(env, curve[3], &bn_order)
- && (curve[4] != atom_none && get_bn_from_bin(env, curve[4], &cofactor))) {
-
- int f_arity = -1;
- const ERL_NIF_TERM* field;
- int p_arity = -1;
- const ERL_NIF_TERM* prime;
-
- long field_bits;
-
- /* {A, B, Seed} = Prime */
- if (!enif_get_tuple(env,curve[1],&p_arity,&prime)
- || !get_bn_from_bin(env, prime[0], &a)
- || !get_bn_from_bin(env, prime[1], &b))
- goto out_err;
-
- if (!enif_get_tuple(env,curve[0],&f_arity,&field))
- goto out_err;
-
- if (f_arity == 2 && field[0] == atom_prime_field) {
- /* {prime_field, Prime} */
-
- if (!get_bn_from_bin(env, field[1], &p))
- goto out_err;
-
- if (BN_is_negative(p) || BN_is_zero(p))
- goto out_err;
-
- field_bits = BN_num_bits(p);
- if (field_bits > OPENSSL_ECC_MAX_FIELD_BITS)
- goto out_err;
-
- /* create the EC_GROUP structure */
- group = EC_GROUP_new_curve_GFp(p, a, b, NULL);
-
- } else if (f_arity == 3 && field[0] == atom_characteristic_two_field) {
-#if defined(OPENSSL_NO_EC2M)
- enif_raise_exception(env, atom_notsup);
- goto out_err;
-#else
- /* {characteristic_two_field, M, Basis} */
-
- int b_arity = -1;
- const ERL_NIF_TERM* basis;
- unsigned int k1, k2, k3;
-
- if ((p = BN_new()) == NULL)
- goto out_err;
-
- if (!enif_get_long(env, field[1], &field_bits)
- || field_bits > OPENSSL_ECC_MAX_FIELD_BITS)
- goto out_err;
-
- if (enif_get_tuple(env,field[2],&b_arity,&basis)) {
- if (b_arity == 2
- && basis[0] == atom_tpbasis
- && enif_get_uint(env, basis[1], &k1)) {
- /* {tpbasis, k} = Basis */
-
- if (!(field_bits > k1 && k1 > 0))
- goto out_err;
-
- /* create the polynomial */
- if (!BN_set_bit(p, (int)field_bits)
- || !BN_set_bit(p, (int)k1)
- || !BN_set_bit(p, 0))
- goto out_err;
-
- } else if (b_arity == 4
- && basis[0] == atom_ppbasis
- && enif_get_uint(env, basis[1], &k1)
- && enif_get_uint(env, basis[2], &k2)
- && enif_get_uint(env, basis[3], &k3)) {
- /* {ppbasis, k1, k2, k3} = Basis */
-
- if (!(field_bits > k3 && k3 > k2 && k2 > k1 && k1 > 0))
- goto out_err;
-
- /* create the polynomial */
- if (!BN_set_bit(p, (int)field_bits)
- || !BN_set_bit(p, (int)k1)
- || !BN_set_bit(p, (int)k2)
- || !BN_set_bit(p, (int)k3)
- || !BN_set_bit(p, 0))
- goto out_err;
-
- } else
- goto out_err;
- } else if (field[2] == atom_onbasis) {
- /* onbasis = Basis */
- /* no parameters */
- goto out_err;
-
- } else
- goto out_err;
-
- group = EC_GROUP_new_curve_GF2m(p, a, b, NULL);
-#endif
- } else
- goto out_err;
-
- if (!group)
- goto out_err;
-
- if (enif_inspect_binary(env, prime[2], &seed)) {
- EC_GROUP_set_seed(group, seed.data, seed.size);
- }
-
- if (!term2point(env, curve[2], group, &point))
- goto out_err;
-
- if (BN_is_negative(bn_order)
- || BN_is_zero(bn_order)
- || BN_num_bits(bn_order) > (int)field_bits + 1)
- goto out_err;
-
- if (!EC_GROUP_set_generator(group, point, bn_order, cofactor))
- goto out_err;
-
- EC_GROUP_set_asn1_flag(group, 0x0);
-
- key = EC_KEY_new();
- if (!key)
- goto out_err;
- EC_KEY_set_group(key, group);
- }
- else {
- goto out_err;
- }
-
-
- goto out;
-
-out_err:
- if (key) EC_KEY_free(key);
- key = NULL;
-
-out:
- /* some OpenSSL structures are mem-dup'ed into the key,
- so we have to free our copies here */
- if (p) BN_free(p);
- if (a) BN_free(a);
- if (b) BN_free(b);
- if (bn_order) BN_free(bn_order);
- if (cofactor) BN_free(cofactor);
- if (group) EC_GROUP_free(group);
- if (point) EC_POINT_free(point);
-
- return key;
-}
-
-
-static ERL_NIF_TERM bn2term(ErlNifEnv* env, const BIGNUM *bn)
-{
- unsigned dlen;
- unsigned char* ptr;
- ERL_NIF_TERM ret;
-
- if (!bn)
- return atom_undefined;
-
- dlen = BN_num_bytes(bn);
- ptr = enif_make_new_binary(env, dlen, &ret);
- BN_bn2bin(bn, ptr);
- ERL_VALGRIND_MAKE_MEM_DEFINED(ptr, dlen);
- return ret;
-}
-
-static ERL_NIF_TERM point2term(ErlNifEnv* env,
- const EC_GROUP *group,
- const EC_POINT *point,
- point_conversion_form_t form)
-{
- unsigned dlen;
- ErlNifBinary bin;
-
- dlen = EC_POINT_point2oct(group, point, form, NULL, 0, NULL);
- if (dlen == 0)
- return atom_undefined;
-
- if (!enif_alloc_binary(dlen, &bin))
- return enif_make_badarg(env);
-
- if (!EC_POINT_point2oct(group, point, form, bin.data, bin.size, NULL)) {
- enif_release_binary(&bin);
- return enif_make_badarg(env);
- }
- ERL_VALGRIND_MAKE_MEM_DEFINED(bin.data, bin.size);
- return enif_make_binary(env, &bin);
-}
-
-static int term2point(ErlNifEnv* env, ERL_NIF_TERM term,
- EC_GROUP *group, EC_POINT **pptr)
-{
- int ret = 0;
- ErlNifBinary bin;
- EC_POINT *point;
-
- if (!enif_inspect_binary(env,term,&bin)) {
- return 0;
- }
-
- if ((*pptr = point = EC_POINT_new(group)) == NULL) {
- return 0;
- }
-
- /* set the point conversion form */
- EC_GROUP_set_point_conversion_form(group, (point_conversion_form_t)(bin.data[0] & ~0x01));
-
- /* extract the ec point */
- if (!EC_POINT_oct2point(group, point, bin.data, bin.size, NULL)) {
- EC_POINT_free(point);
- *pptr = NULL;
- } else
- ret = 1;
-
- return ret;
-}
-
-static int get_ec_key(ErlNifEnv* env,
- ERL_NIF_TERM curve, ERL_NIF_TERM priv, ERL_NIF_TERM pub,
- EC_KEY** res)
-{
- EC_KEY *key = NULL;
- BIGNUM *priv_key = NULL;
- EC_POINT *pub_key = NULL;
- EC_GROUP *group = NULL;
-
- if (!(priv == atom_undefined || get_bn_from_bin(env, priv, &priv_key))
- || !(pub == atom_undefined || enif_is_binary(env, pub))) {
- goto out_err;
- }
-
- key = ec_key_new(env, curve);
-
- if (!key) {
- goto out_err;
- }
-
- if (!group)
- group = EC_GROUP_dup(EC_KEY_get0_group(key));
-
- if (term2point(env, pub, group, &pub_key)) {
- if (!EC_KEY_set_public_key(key, pub_key)) {
- goto out_err;
- }
- }
- if (priv != atom_undefined
- && !BN_is_zero(priv_key)) {
- if (!EC_KEY_set_private_key(key, priv_key))
- goto out_err;
-
- /* calculate public key (if necessary) */
- if (EC_KEY_get0_public_key(key) == NULL)
- {
- /* the public key was not included in the SEC1 private
- * key => calculate the public key */
- pub_key = EC_POINT_new(group);
- if (pub_key == NULL
- || !EC_POINT_copy(pub_key, EC_GROUP_get0_generator(group))
- || !EC_POINT_mul(group, pub_key, priv_key, NULL, NULL, NULL)
- || !EC_KEY_set_public_key(key, pub_key))
- goto out_err;
- }
- }
-
- goto out;
-
-out_err:
- if (key) EC_KEY_free(key);
- key = NULL;
-
-out:
- /* some OpenSSL structures are mem-dup'ed into the key,
- so we have to free our copies here */
- if (priv_key) BN_clear_free(priv_key);
- if (pub_key) EC_POINT_free(pub_key);
- if (group) EC_GROUP_free(group);
- if (!key)
- return 0;
- *res = key;
- return 1;
-}
-#endif /* HAVE_EC */
-
-static ERL_NIF_TERM ec_key_generate(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{
-#if defined(HAVE_EC)
- EC_KEY *key = NULL;
- const EC_GROUP *group;
- const EC_POINT *public_key;
- ERL_NIF_TERM priv_key;
- ERL_NIF_TERM pub_key = atom_undefined;
-
- if (!get_ec_key(env, argv[0], argv[1], atom_undefined, &key))
- goto badarg;
-
- if (argv[1] == atom_undefined) {
- if (!EC_KEY_generate_key(key))
- goto badarg;
- }
-
- group = EC_KEY_get0_group(key);
- public_key = EC_KEY_get0_public_key(key);
-
- if (group && public_key) {
- pub_key = point2term(env, group, public_key,
- EC_KEY_get_conv_form(key));
- }
- priv_key = bn2term(env, EC_KEY_get0_private_key(key));
- EC_KEY_free(key);
- return enif_make_tuple2(env, pub_key, priv_key);
-
-badarg:
- if (key)
- EC_KEY_free(key);
- return make_badarg_maybe(env);
-#else
- return atom_notsup;
-#endif
-}
-
-/*
- (_OthersPublicKey, _MyPrivateKey)
- (_OthersPublicKey, _MyEC_Point)
-*/
-static ERL_NIF_TERM ecdh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-/* (OtherPublicKey, Curve, My) */
-{
-#if defined(HAVE_EC)
- ERL_NIF_TERM ret;
- unsigned char *p;
- EC_KEY* key = NULL;
- int field_size = 0;
- int i;
- EC_GROUP *group;
- const BIGNUM *priv_key;
- EC_POINT *my_ecpoint = NULL;
- EC_KEY *other_ecdh = NULL;
-
- if (!get_ec_key(env, argv[1], argv[2], atom_undefined, &key))
- return make_badarg_maybe(env);
-
- group = EC_GROUP_dup(EC_KEY_get0_group(key));
- priv_key = EC_KEY_get0_private_key(key);
-
- if (!term2point(env, argv[0], group, &my_ecpoint)) {
- goto out_err;
- }
-
- if ((other_ecdh = EC_KEY_new()) == NULL
- || !EC_KEY_set_group(other_ecdh, group)
- || !EC_KEY_set_private_key(other_ecdh, priv_key))
- goto out_err;
-
- field_size = EC_GROUP_get_degree(group);
- if (field_size <= 0)
- goto out_err;
-
- p = enif_make_new_binary(env, (field_size+7)/8, &ret);
- i = ECDH_compute_key(p, (field_size+7)/8, my_ecpoint, other_ecdh, NULL);
-
- if (i < 0)
- goto out_err;
-out:
- if (group) EC_GROUP_free(group);
- if (my_ecpoint) EC_POINT_free(my_ecpoint);
- if (other_ecdh) EC_KEY_free(other_ecdh);
- if (key) EC_KEY_free(key);
-
- return ret;
-
-out_err:
- ret = enif_make_badarg(env);
- goto out;
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM evp_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
- /* (Curve, PeerBin, MyBin) */
-{
-#ifdef HAVE_ED_CURVE_DH
- int type;
- EVP_PKEY_CTX *ctx = NULL;
- ErlNifBinary peer_bin, my_bin, key_bin;
- EVP_PKEY *peer_key = NULL, *my_key = NULL;
- size_t max_size;
-
- if (argv[0] == atom_x25519) type = EVP_PKEY_X25519;
- else if (argv[0] == atom_x448) type = EVP_PKEY_X448;
- else return enif_make_badarg(env);
-
- if (!enif_inspect_binary(env, argv[1], &peer_bin) ||
- !enif_inspect_binary(env, argv[2], &my_bin))
- goto return_badarg;
-
- if (!(my_key = EVP_PKEY_new_raw_private_key(type, NULL, my_bin.data, my_bin.size)) ||
- !(ctx = EVP_PKEY_CTX_new(my_key, NULL)))
- goto return_badarg;
-
- if (!EVP_PKEY_derive_init(ctx))
- goto return_badarg;
-
- if (!(peer_key = EVP_PKEY_new_raw_public_key(type, NULL, peer_bin.data, peer_bin.size)) ||
- !EVP_PKEY_derive_set_peer(ctx, peer_key))
- goto return_badarg;
-
- if (!EVP_PKEY_derive(ctx, NULL, &max_size))
- goto return_badarg;
-
- if (!enif_alloc_binary(max_size, &key_bin) ||
- !EVP_PKEY_derive(ctx, key_bin.data, &key_bin.size))
- goto return_badarg;
-
- if (key_bin.size < max_size) {
- size_t actual_size = key_bin.size;
- if (!enif_realloc_binary(&key_bin, actual_size))
- goto return_badarg;
- }
-
- EVP_PKEY_free(my_key);
- EVP_PKEY_free(peer_key);
- EVP_PKEY_CTX_free(ctx);
- return enif_make_binary(env, &key_bin);
-
-return_badarg:
- if (my_key) EVP_PKEY_free(my_key);
- if (peer_key) EVP_PKEY_free(peer_key);
- if (ctx) EVP_PKEY_CTX_free(ctx);
- return enif_make_badarg(env);
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM evp_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-/* (Curve) */
-{
-#ifdef HAVE_ED_CURVE_DH
- int type;
- EVP_PKEY_CTX *ctx = NULL;
- EVP_PKEY *pkey = NULL;
- ERL_NIF_TERM ret_pub, ret_prv;
- size_t key_len;
-
- if (argv[0] == atom_x25519) type = EVP_PKEY_X25519;
- else if (argv[0] == atom_x448) type = EVP_PKEY_X448;
- else return enif_make_badarg(env);
-
- if (!(ctx = EVP_PKEY_CTX_new_id(type, NULL))) return enif_make_badarg(env);
-
- if (!EVP_PKEY_keygen_init(ctx)) goto return_error;
- if (!EVP_PKEY_keygen(ctx, &pkey)) goto return_error;
-
- if (!EVP_PKEY_get_raw_public_key(pkey, NULL, &key_len)) goto return_error;
- if (!EVP_PKEY_get_raw_public_key(pkey,
- enif_make_new_binary(env, key_len, &ret_pub),
- &key_len))
- goto return_error;
-
- if (!EVP_PKEY_get_raw_private_key(pkey, NULL, &key_len)) goto return_error;
- if (!EVP_PKEY_get_raw_private_key(pkey,
- enif_make_new_binary(env, key_len, &ret_prv),
- &key_len))
- goto return_error;
-
- EVP_PKEY_free(pkey);
- EVP_PKEY_CTX_free(ctx);
- return enif_make_tuple2(env, ret_pub, ret_prv);
-
-return_error:
- if (pkey) EVP_PKEY_free(pkey);
- if (ctx) EVP_PKEY_CTX_free(ctx);
- return atom_error;
-
-#else
- return atom_notsup;
-#endif
-}
-
-/*================================================================*/
-#define PKEY_BADARG -1
-#define PKEY_NOTSUP 0
-#define PKEY_OK 1
-
-typedef struct PKeyCryptOptions {
- const EVP_MD *rsa_mgf1_md;
- ErlNifBinary rsa_oaep_label;
- const EVP_MD *rsa_oaep_md;
- int rsa_padding;
- const EVP_MD *signature_md;
-} PKeyCryptOptions;
-
-typedef struct PKeySignOptions {
- const EVP_MD *rsa_mgf1_md;
- int rsa_padding;
- int rsa_pss_saltlen;
-} PKeySignOptions;
-
-static int get_pkey_digest_type(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM type,
- const EVP_MD **md)
-{
- struct digest_type_t *digp = NULL;
- *md = NULL;
-
- if (type == atom_none && algorithm == atom_rsa) return PKEY_OK;
-#ifdef HAVE_EDDSA
- if (algorithm == atom_eddsa) return PKEY_OK;
-#endif
- digp = get_digest_type(type);
- if (!digp) return PKEY_BADARG;
- if (!digp->md.p) return PKEY_NOTSUP;
-
- *md = digp->md.p;
- return PKEY_OK;
-}
-
-
-static int get_pkey_sign_digest(ErlNifEnv *env, ERL_NIF_TERM algorithm,
- ERL_NIF_TERM type, ERL_NIF_TERM data,
- unsigned char *md_value, const EVP_MD **mdp,
- unsigned char **tbsp, size_t *tbslenp)
-{
- int i;
- const ERL_NIF_TERM *tpl_terms;
- int tpl_arity;
- ErlNifBinary tbs_bin;
- EVP_MD_CTX *mdctx;
- const EVP_MD *md = *mdp;
- unsigned char *tbs = *tbsp;
- size_t tbslen = *tbslenp;
- unsigned int tbsleni;
-
- if ((i = get_pkey_digest_type(env, algorithm, type, &md)) != PKEY_OK) {
- return i;
- }
- if (enif_get_tuple(env, data, &tpl_arity, &tpl_terms)) {
- if (tpl_arity != 2 || tpl_terms[0] != atom_digest
- || !enif_inspect_binary(env, tpl_terms[1], &tbs_bin)
- || (md != NULL && tbs_bin.size != EVP_MD_size(md))) {
- return PKEY_BADARG;
- }
- /* We have a digest (= hashed text) in tbs_bin */
- tbs = tbs_bin.data;
- tbslen = tbs_bin.size;
- } else if (md == NULL) {
- if (!enif_inspect_binary(env, data, &tbs_bin)) {
- return PKEY_BADARG;
- }
- /* md == NULL, that is no hashing because DigestType argument was atom_none */
- tbs = tbs_bin.data;
- tbslen = tbs_bin.size;
- } else {
- if (!enif_inspect_binary(env, data, &tbs_bin)) {
- return PKEY_BADARG;
- }
- /* We have the cleartext in tbs_bin and the hash algo info in md */
- tbs = md_value;
- mdctx = EVP_MD_CTX_create();
- if (!mdctx) {
- return PKEY_BADARG;
- }
- /* Looks well, now hash the plain text into a digest according to md */
- if (EVP_DigestInit_ex(mdctx, md, NULL) <= 0) {
- EVP_MD_CTX_destroy(mdctx);
- return PKEY_BADARG;
- }
- if (EVP_DigestUpdate(mdctx, tbs_bin.data, tbs_bin.size) <= 0) {
- EVP_MD_CTX_destroy(mdctx);
- return PKEY_BADARG;
- }
- if (EVP_DigestFinal_ex(mdctx, tbs, &tbsleni) <= 0) {
- EVP_MD_CTX_destroy(mdctx);
- return PKEY_BADARG;
- }
- tbslen = (size_t)(tbsleni);
- EVP_MD_CTX_destroy(mdctx);
- }
-
- *mdp = md;
- *tbsp = tbs;
- *tbslenp = tbslen;
-
- return PKEY_OK;
-}
-
-
-static int get_pkey_sign_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM options,
- const EVP_MD *md, PKeySignOptions *opt)
-{
- ERL_NIF_TERM head, tail;
- const ERL_NIF_TERM *tpl_terms;
- int tpl_arity;
- const EVP_MD *opt_md;
- int i;
-
- if (!enif_is_list(env, options)) {
- return PKEY_BADARG;
- }
-
- /* defaults */
- if (algorithm == atom_rsa) {
- opt->rsa_mgf1_md = NULL;
- opt->rsa_padding = RSA_PKCS1_PADDING;
- opt->rsa_pss_saltlen = -2;
- }
-
- if (enif_is_empty_list(env, options)) {
- return PKEY_OK;
- }
-
- if (algorithm == atom_rsa) {
- tail = options;
- while (enif_get_list_cell(env, tail, &head, &tail)) {
- if (enif_get_tuple(env, head, &tpl_arity, &tpl_terms) && tpl_arity == 2) {
- if (tpl_terms[0] == atom_rsa_mgf1_md && enif_is_atom(env, tpl_terms[1])) {
- i = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md);
- if (i != PKEY_OK) {
- return i;
- }
- opt->rsa_mgf1_md = opt_md;
- } else if (tpl_terms[0] == atom_rsa_padding) {
- if (tpl_terms[1] == atom_rsa_pkcs1_padding) {
- opt->rsa_padding = RSA_PKCS1_PADDING;
- } else if (tpl_terms[1] == atom_rsa_pkcs1_pss_padding) {
-#ifdef HAVE_RSA_PKCS1_PSS_PADDING
- opt->rsa_padding = RSA_PKCS1_PSS_PADDING;
- if (opt->rsa_mgf1_md == NULL) {
- opt->rsa_mgf1_md = md;
- }
-#else
- return PKEY_NOTSUP;
-#endif
- } else if (tpl_terms[1] == atom_rsa_x931_padding) {
- opt->rsa_padding = RSA_X931_PADDING;
- } else if (tpl_terms[1] == atom_rsa_no_padding) {
- opt->rsa_padding = RSA_NO_PADDING;
- } else {
- return PKEY_BADARG;
- }
- } else if (tpl_terms[0] == atom_rsa_pss_saltlen) {
- if (!enif_get_int(env, tpl_terms[1], &(opt->rsa_pss_saltlen))
- || opt->rsa_pss_saltlen < -2) {
- return PKEY_BADARG;
- }
- } else {
- return PKEY_BADARG;
- }
- } else {
- return PKEY_BADARG;
- }
- }
- } else {
- return PKEY_BADARG;
- }
-
- return PKEY_OK;
-}
-
-
-#ifdef HAS_ENGINE_SUPPORT
-static int get_engine_and_key_id(ErlNifEnv *env, ERL_NIF_TERM key, char ** id, ENGINE **e)
-{
- ERL_NIF_TERM engine_res, key_id_term;
- struct engine_ctx *ctx;
- ErlNifBinary key_id_bin;
-
- if (!enif_get_map_value(env, key, atom_engine, &engine_res) ||
- !enif_get_resource(env, engine_res, engine_ctx_rtype, (void**)&ctx) ||
- !enif_get_map_value(env, key, atom_key_id, &key_id_term) ||
- !enif_inspect_binary(env, key_id_term, &key_id_bin)) {
- return 0;
- }
- else {
- *e = ctx->engine;
- return zero_terminate(key_id_bin, id);
- }
-}
-
-
-static char *get_key_password(ErlNifEnv *env, ERL_NIF_TERM key) {
- ERL_NIF_TERM tmp_term;
- ErlNifBinary pwd_bin;
- char *pwd = NULL;
- if (enif_get_map_value(env, key, atom_password, &tmp_term) &&
- enif_inspect_binary(env, tmp_term, &pwd_bin) &&
- zero_terminate(pwd_bin, &pwd)
- ) return pwd;
-
- return NULL;
-}
-
-static int zero_terminate(ErlNifBinary bin, char **buf) {
- *buf = enif_alloc(bin.size+1);
- if (!*buf)
- return 0;
- memcpy(*buf, bin.data, bin.size);
- *(*buf+bin.size) = 0;
- return 1;
-}
-#endif
-
-static int get_pkey_private_key(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM key, EVP_PKEY **pkey)
-{
- if (enif_is_map(env, key)) {
-#ifdef HAS_ENGINE_SUPPORT
- /* Use key stored in engine */
- ENGINE *e;
- char *id = NULL;
- char *password;
-
- if (!get_engine_and_key_id(env, key, &id, &e))
- return PKEY_BADARG;
- password = get_key_password(env, key);
- *pkey = ENGINE_load_private_key(e, id, NULL, password);
- if (password) enif_free(password);
- enif_free(id);
- if (!*pkey)
- return PKEY_BADARG;
-#else
- return PKEY_BADARG;
-#endif
- }
- else if (algorithm == atom_rsa) {
- RSA *rsa = RSA_new();
-
- if (!get_rsa_private_key(env, key, rsa)) {
- RSA_free(rsa);
- return PKEY_BADARG;
- }
-
- *pkey = EVP_PKEY_new();
- if (!EVP_PKEY_assign_RSA(*pkey, rsa)) {
- EVP_PKEY_free(*pkey);
- RSA_free(rsa);
- return PKEY_BADARG;
- }
- } else if (algorithm == atom_ecdsa) {
-#if defined(HAVE_EC)
- EC_KEY *ec = NULL;
- const ERL_NIF_TERM *tpl_terms;
- int tpl_arity;
-
- if (enif_get_tuple(env, key, &tpl_arity, &tpl_terms) && tpl_arity == 2
- && enif_is_tuple(env, tpl_terms[0]) && enif_is_binary(env, tpl_terms[1])
- && get_ec_key(env, tpl_terms[0], tpl_terms[1], atom_undefined, &ec)) {
-
- *pkey = EVP_PKEY_new();
- if (!EVP_PKEY_assign_EC_KEY(*pkey, ec)) {
- EVP_PKEY_free(*pkey);
- EC_KEY_free(ec);
- return PKEY_BADARG;
- }
- } else {
- return PKEY_BADARG;
- }
-#else
- return PKEY_NOTSUP;
-#endif
- } else if (algorithm == atom_eddsa) {
-#if defined(HAVE_EDDSA)
- if (!get_eddsa_key(env, 0, key, pkey)) {
- return PKEY_BADARG;
- }
-#else
- return PKEY_NOTSUP;
-#endif
- } else if (algorithm == atom_dss) {
- DSA *dsa = DSA_new();
-
- if (!get_dss_private_key(env, key, dsa)) {
- DSA_free(dsa);
- return PKEY_BADARG;
- }
-
- *pkey = EVP_PKEY_new();
- if (!EVP_PKEY_assign_DSA(*pkey, dsa)) {
- EVP_PKEY_free(*pkey);
- DSA_free(dsa);
- return PKEY_BADARG;
- }
- } else {
- return PKEY_BADARG;
- }
-
- return PKEY_OK;
-}
-
-
-static int get_pkey_public_key(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM key,
- EVP_PKEY **pkey)
-{
- if (enif_is_map(env, key)) {
-#ifdef HAS_ENGINE_SUPPORT
- /* Use key stored in engine */
- ENGINE *e;
- char *id = NULL;
- char *password;
-
- if (!get_engine_and_key_id(env, key, &id, &e))
- return PKEY_BADARG;
- password = get_key_password(env, key);
- *pkey = ENGINE_load_public_key(e, id, NULL, password);
- if (password) enif_free(password);
- enif_free(id);
- if (!pkey)
- return PKEY_BADARG;
-#else
- return PKEY_BADARG;
-#endif
- } else if (algorithm == atom_rsa) {
- RSA *rsa = RSA_new();
-
- if (!get_rsa_public_key(env, key, rsa)) {
- RSA_free(rsa);
- return PKEY_BADARG;
- }
-
- *pkey = EVP_PKEY_new();
- if (!EVP_PKEY_assign_RSA(*pkey, rsa)) {
- EVP_PKEY_free(*pkey);
- RSA_free(rsa);
- return PKEY_BADARG;
- }
- } else if (algorithm == atom_ecdsa) {
-#if defined(HAVE_EC)
- EC_KEY *ec = NULL;
- const ERL_NIF_TERM *tpl_terms;
- int tpl_arity;
-
- if (enif_get_tuple(env, key, &tpl_arity, &tpl_terms) && tpl_arity == 2
- && enif_is_tuple(env, tpl_terms[0]) && enif_is_binary(env, tpl_terms[1])
- && get_ec_key(env, tpl_terms[0], atom_undefined, tpl_terms[1], &ec)) {
-
- *pkey = EVP_PKEY_new();
- if (!EVP_PKEY_assign_EC_KEY(*pkey, ec)) {
- EVP_PKEY_free(*pkey);
- EC_KEY_free(ec);
- return PKEY_BADARG;
- }
- } else {
- return PKEY_BADARG;
- }
-#else
- return PKEY_NOTSUP;
-#endif
- } else if (algorithm == atom_eddsa) {
-#if defined(HAVE_EDDSA)
- if (!get_eddsa_key(env, 1, key, pkey)) {
- return PKEY_BADARG;
- }
-#else
- return PKEY_NOTSUP;
-#endif
- } else if (algorithm == atom_dss) {
- DSA *dsa = DSA_new();
-
- if (!get_dss_public_key(env, key, dsa)) {
- DSA_free(dsa);
- return PKEY_BADARG;
- }
-
- *pkey = EVP_PKEY_new();
- if (!EVP_PKEY_assign_DSA(*pkey, dsa)) {
- EVP_PKEY_free(*pkey);
- DSA_free(dsa);
- return PKEY_BADARG;
- }
- } else {
- return PKEY_BADARG;
- }
-
- return PKEY_OK;
-}
-
-static ERL_NIF_TERM pkey_sign_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
-{/* (Algorithm, Type, Data|{digest,Digest}, Key|#{}, Options) */
- int i;
- const EVP_MD *md = NULL;
- unsigned char md_value[EVP_MAX_MD_SIZE];
- EVP_PKEY *pkey;
-#ifdef HAS_EVP_PKEY_CTX
- EVP_PKEY_CTX *ctx;
- size_t siglen;
-#else
- unsigned len, siglen;
-#endif
- PKeySignOptions sig_opt;
- ErlNifBinary sig_bin; /* signature */
- unsigned char *tbs; /* data to be signed */
- size_t tbslen;
-/*char buf[1024];
-enif_get_atom(env,argv[0],buf,1024,ERL_NIF_LATIN1); printf("algo=%s ",buf);
-enif_get_atom(env,argv[1],buf,1024,ERL_NIF_LATIN1); printf("hash=%s ",buf);
-printf("\r\n");
-*/
-
-#ifndef HAS_ENGINE_SUPPORT
- if (enif_is_map(env, argv[3])) {
- return atom_notsup;
- }
-#endif
-
- i = get_pkey_sign_digest(env, argv[0], argv[1], argv[2], md_value, &md, &tbs, &tbslen);
- if (i != PKEY_OK) {
- if (i == PKEY_NOTSUP)
- return atom_notsup;
- else
- return enif_make_badarg(env);
- }
-
- i = get_pkey_sign_options(env, argv[0], argv[4], md, &sig_opt);
- if (i != PKEY_OK) {
- if (i == PKEY_NOTSUP)
- return atom_notsup;
- else
- return enif_make_badarg(env);
- }
-
- if (get_pkey_private_key(env, argv[0], argv[3], &pkey) != PKEY_OK) {
- return enif_make_badarg(env);
- }
-
-#ifdef HAS_EVP_PKEY_CTX
- ctx = EVP_PKEY_CTX_new(pkey, NULL);
- if (!ctx) goto badarg;
-
- if (argv[0] != atom_eddsa) {
- if (EVP_PKEY_sign_init(ctx) <= 0) goto badarg;
- if (md != NULL && EVP_PKEY_CTX_set_signature_md(ctx, md) <= 0) goto badarg;
- }
-
- if (argv[0] == atom_rsa) {
- if (EVP_PKEY_CTX_set_rsa_padding(ctx, sig_opt.rsa_padding) <= 0) goto badarg;
-# ifdef HAVE_RSA_PKCS1_PSS_PADDING
- if (sig_opt.rsa_padding == RSA_PKCS1_PSS_PADDING) {
- if (sig_opt.rsa_mgf1_md != NULL) {
-# ifdef HAVE_RSA_MGF1_MD
- if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, sig_opt.rsa_mgf1_md) <= 0) goto badarg;
-# else
- EVP_PKEY_CTX_free(ctx);
- EVP_PKEY_free(pkey);
- return atom_notsup;
-# endif
- }
- if (sig_opt.rsa_pss_saltlen > -2
- && EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, sig_opt.rsa_pss_saltlen) <= 0)
- goto badarg;
- }
-#endif
- }
-
- if (argv[0] == atom_eddsa) {
-#ifdef HAVE_EDDSA
- EVP_MD_CTX* mdctx = EVP_MD_CTX_new();
- if (!EVP_DigestSignInit(mdctx, NULL, NULL, NULL, pkey)) {
- if (mdctx) EVP_MD_CTX_free(mdctx);
- goto badarg;
- }
-
- if (!EVP_DigestSign(mdctx, NULL, &siglen, tbs, tbslen)) {
- EVP_MD_CTX_free(mdctx);
- goto badarg;
- }
- enif_alloc_binary(siglen, &sig_bin);
-
- if (!EVP_DigestSign(mdctx, sig_bin.data, &siglen, tbs, tbslen)) {
- EVP_MD_CTX_free(mdctx);
- goto badarg;
- }
- EVP_MD_CTX_free(mdctx);
-#else
- goto badarg;
-#endif
- }
- else
- {
- if (EVP_PKEY_sign(ctx, NULL, &siglen, tbs, tbslen) <= 0) goto badarg;
- enif_alloc_binary(siglen, &sig_bin);
-
- if (md != NULL) {
- ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, EVP_MD_size(md));
- }
- i = EVP_PKEY_sign(ctx, sig_bin.data, &siglen, tbs, tbslen);
- }
-
- EVP_PKEY_CTX_free(ctx);
-#else
-/*printf("Old interface\r\n");
- */
- if (argv[0] == atom_rsa) {
- RSA *rsa = EVP_PKEY_get1_RSA(pkey);
- enif_alloc_binary(RSA_size(rsa), &sig_bin);
- len = EVP_MD_size(md);
- ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, len);
- i = RSA_sign(md->type, tbs, len, sig_bin.data, &siglen, rsa);
- RSA_free(rsa);
- } else if (argv[0] == atom_dss) {
- DSA *dsa = EVP_PKEY_get1_DSA(pkey);
- enif_alloc_binary(DSA_size(dsa), &sig_bin);
- len = EVP_MD_size(md);
- ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, len);
- i = DSA_sign(md->type, tbs, len, sig_bin.data, &siglen, dsa);
- DSA_free(dsa);
- } else if (argv[0] == atom_ecdsa) {
-#if defined(HAVE_EC)
- EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pkey);
- enif_alloc_binary(ECDSA_size(ec), &sig_bin);
- len = EVP_MD_size(md);
- ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, len);
- i = ECDSA_sign(md->type, tbs, len, sig_bin.data, &siglen, ec);
- EC_KEY_free(ec);
-#else
- EVP_PKEY_free(pkey);
- return atom_notsup;
-#endif
- } else {
- goto badarg;
- }
-#endif
-
- EVP_PKEY_free(pkey);
- if (i == 1) {
- ERL_VALGRIND_MAKE_MEM_DEFINED(sig_bin.data, siglen);
- if (siglen != sig_bin.size) {
- enif_realloc_binary(&sig_bin, siglen);
- ERL_VALGRIND_ASSERT_MEM_DEFINED(sig_bin.data, siglen);
- }
- return enif_make_binary(env, &sig_bin);
- } else {
- enif_release_binary(&sig_bin);
- return atom_error;
- }
-
- badarg:
-#ifdef HAS_EVP_PKEY_CTX
- EVP_PKEY_CTX_free(ctx);
-#endif
- EVP_PKEY_free(pkey);
- return enif_make_badarg(env);
-}
-
-
-static ERL_NIF_TERM pkey_verify_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
-{/* (Algorithm, Type, Data|{digest,Digest}, Signature, Key, Options) */
- int i;
- const EVP_MD *md = NULL;
- unsigned char md_value[EVP_MAX_MD_SIZE];
- EVP_PKEY *pkey;
-#ifdef HAS_EVP_PKEY_CTX
- EVP_PKEY_CTX *ctx;
-#else
-#endif
- PKeySignOptions sig_opt;
- ErlNifBinary sig_bin; /* signature */
- unsigned char *tbs; /* data to be signed */
- size_t tbslen;
-
-#ifndef HAS_ENGINE_SUPPORT
- if (enif_is_map(env, argv[4])) {
- return atom_notsup;
- }
-#endif
-
- if (!enif_inspect_binary(env, argv[3], &sig_bin)) {
- return enif_make_badarg(env);
- }
-
- i = get_pkey_sign_digest(env, argv[0], argv[1], argv[2], md_value, &md, &tbs, &tbslen);
- if (i != PKEY_OK) {
- if (i == PKEY_NOTSUP)
- return atom_notsup;
- else
- return enif_make_badarg(env);
- }
-
- i = get_pkey_sign_options(env, argv[0], argv[5], md, &sig_opt);
- if (i != PKEY_OK) {
- if (i == PKEY_NOTSUP)
- return atom_notsup;
- else
- return enif_make_badarg(env);
- }
-
- if (get_pkey_public_key(env, argv[0], argv[4], &pkey) != PKEY_OK) {
- return enif_make_badarg(env);
- }
-
-#ifdef HAS_EVP_PKEY_CTX
-/* printf("EVP interface\r\n");
- */
- ctx = EVP_PKEY_CTX_new(pkey, NULL);
- if (!ctx) goto badarg;
-
- if (argv[0] != atom_eddsa) {
- if (EVP_PKEY_verify_init(ctx) <= 0) goto badarg;
- if (md != NULL && EVP_PKEY_CTX_set_signature_md(ctx, md) <= 0) goto badarg;
- }
-
- if (argv[0] == atom_rsa) {
- if (EVP_PKEY_CTX_set_rsa_padding(ctx, sig_opt.rsa_padding) <= 0) goto badarg;
- if (sig_opt.rsa_padding == RSA_PKCS1_PSS_PADDING) {
- if (sig_opt.rsa_mgf1_md != NULL) {
-# ifdef HAVE_RSA_MGF1_MD
- if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, sig_opt.rsa_mgf1_md) <= 0) goto badarg;
-# else
- EVP_PKEY_CTX_free(ctx);
- EVP_PKEY_free(pkey);
- return atom_notsup;
-# endif
- }
- if (sig_opt.rsa_pss_saltlen > -2
- && EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, sig_opt.rsa_pss_saltlen) <= 0)
- goto badarg;
- }
- }
-
- if (argv[0] == atom_eddsa) {
-#ifdef HAVE_EDDSA
- EVP_MD_CTX* mdctx = EVP_MD_CTX_create();
-
- if (!EVP_DigestVerifyInit(mdctx, NULL, NULL, NULL, pkey)) {
- if (mdctx) EVP_MD_CTX_destroy(mdctx);
- goto badarg;
- }
-
- i = EVP_DigestVerify(mdctx, sig_bin.data, sig_bin.size, tbs, tbslen);
- EVP_MD_CTX_destroy(mdctx);
-#else
- goto badarg;
-#endif
- }
- else
- {
- if (md != NULL) {
- ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, EVP_MD_size(md));
- }
- i = EVP_PKEY_verify(ctx, sig_bin.data, sig_bin.size, tbs, tbslen);
- }
-
- EVP_PKEY_CTX_free(ctx);
-#else
-/*printf("Old interface\r\n");
-*/
- if (argv[0] == atom_rsa) {
- RSA *rsa = EVP_PKEY_get1_RSA(pkey);
- i = RSA_verify(md->type, tbs, tbslen, sig_bin.data, sig_bin.size, rsa);
- RSA_free(rsa);
- } else if (argv[0] == atom_dss) {
- DSA *dsa = EVP_PKEY_get1_DSA(pkey);
- i = DSA_verify(0, tbs, tbslen, sig_bin.data, sig_bin.size, dsa);
- DSA_free(dsa);
- } else if (argv[0] == atom_ecdsa) {
-#if defined(HAVE_EC)
- EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pkey);
- i = ECDSA_verify(EVP_MD_type(md), tbs, tbslen, sig_bin.data, sig_bin.size, ec);
- EC_KEY_free(ec);
-#else
- EVP_PKEY_free(pkey);
- return atom_notsup;
-#endif
- } else {
- goto badarg;
- }
-#endif
-
- EVP_PKEY_free(pkey);
- if (i == 1) {
- return atom_true;
- } else {
- return atom_false;
- }
-
- badarg:
-#ifdef HAS_EVP_PKEY_CTX
- EVP_PKEY_CTX_free(ctx);
-#endif
- EVP_PKEY_free(pkey);
- return enif_make_badarg(env);
-}
-
-
-/*--------------------------------*/
-
-static int get_pkey_crypt_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM options,
- PKeyCryptOptions *opt)
-{
- ERL_NIF_TERM head, tail;
- const ERL_NIF_TERM *tpl_terms;
- int tpl_arity;
- const EVP_MD *opt_md;
- int i;
-
- if (!enif_is_list(env, options)) {
- return PKEY_BADARG;
- }
-
- /* defaults */
- if (algorithm == atom_rsa) {
- opt->rsa_mgf1_md = NULL;
- opt->rsa_oaep_label.data = NULL;
- opt->rsa_oaep_label.size = 0;
- opt->rsa_oaep_md = NULL;
- opt->rsa_padding = RSA_PKCS1_PADDING;
- opt->signature_md = NULL;
- }
-
- if (enif_is_empty_list(env, options)) {
- return PKEY_OK;
- }
-
- if (algorithm == atom_rsa) {
- tail = options;
- while (enif_get_list_cell(env, tail, &head, &tail)) {
- if (enif_get_tuple(env, head, &tpl_arity, &tpl_terms) && tpl_arity == 2) {
- if (tpl_terms[0] == atom_rsa_padding
- || tpl_terms[0] == atom_rsa_pad /* Compatibility */
- ) {
- if (tpl_terms[1] == atom_rsa_pkcs1_padding) {
- opt->rsa_padding = RSA_PKCS1_PADDING;
-#ifdef HAVE_RSA_OAEP_PADDING
- } else if (tpl_terms[1] == atom_rsa_pkcs1_oaep_padding) {
- opt->rsa_padding = RSA_PKCS1_OAEP_PADDING;
-#endif
-#ifdef HAVE_RSA_SSLV23_PADDING
- } else if (tpl_terms[1] == atom_rsa_sslv23_padding) {
- opt->rsa_padding = RSA_SSLV23_PADDING;
-#endif
- } else if (tpl_terms[1] == atom_rsa_x931_padding) {
- opt->rsa_padding = RSA_X931_PADDING;
- } else if (tpl_terms[1] == atom_rsa_no_padding) {
- opt->rsa_padding = RSA_NO_PADDING;
- } else {
- return PKEY_BADARG;
- }
- } else if (tpl_terms[0] == atom_signature_md && enif_is_atom(env, tpl_terms[1])) {
- i = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md);
- if (i != PKEY_OK) {
- return i;
- }
- opt->signature_md = opt_md;
- } else if (tpl_terms[0] == atom_rsa_mgf1_md && enif_is_atom(env, tpl_terms[1])) {
-#ifndef HAVE_RSA_MGF1_MD
- if (tpl_terms[1] != atom_sha)
- return PKEY_NOTSUP;
-#endif
- i = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md);
- if (i != PKEY_OK) {
- return i;
- }
- opt->rsa_mgf1_md = opt_md;
- } else if (tpl_terms[0] == atom_rsa_oaep_label
- && enif_inspect_binary(env, tpl_terms[1], &(opt->rsa_oaep_label))) {
-#ifdef HAVE_RSA_OAEP_MD
- continue;
-#else
- return PKEY_NOTSUP;
-#endif
- } else if (tpl_terms[0] == atom_rsa_oaep_md && enif_is_atom(env, tpl_terms[1])) {
-#ifndef HAVE_RSA_OAEP_MD
- if (tpl_terms[1] != atom_sha)
- return PKEY_NOTSUP;
-#endif
- i = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md);
- if (i != PKEY_OK) {
- return i;
- }
- opt->rsa_oaep_md = opt_md;
- } else {
- return PKEY_BADARG;
- }
- } else {
- return PKEY_BADARG;
- }
- }
- } else {
- return PKEY_BADARG;
- }
-
- return PKEY_OK;
-}
-
-static size_t size_of_RSA(EVP_PKEY *pkey) {
- size_t tmplen;
- RSA *rsa = EVP_PKEY_get1_RSA(pkey);
- if (rsa == NULL) return 0;
- tmplen = RSA_size(rsa);
- RSA_free(rsa);
- return tmplen;
-}
-
-static ERL_NIF_TERM pkey_crypt_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
-{/* (Algorithm, Data, PublKey=[E,N]|[E,N,D]|[E,N,D,P1,P2,E1,E2,C], Options, IsPrivate, IsEncrypt) */
- int i;
- EVP_PKEY *pkey;
-#ifdef HAS_EVP_PKEY_CTX
- EVP_PKEY_CTX *ctx;
-#else
- RSA *rsa;
-#endif
- PKeyCryptOptions crypt_opt;
- ErlNifBinary in_bin, out_bin, tmp_bin;
- size_t outlen;
-#ifdef HAVE_RSA_SSLV23_PADDING
- size_t tmplen;
-#endif
- int is_private = (argv[4] == atom_true),
- is_encrypt = (argv[5] == atom_true);
- int algo_init = 0;
-
-/* char algo[1024]; */
-
-#ifndef HAS_ENGINE_SUPPORT
- if (enif_is_map(env, argv[2])) {
- return atom_notsup;
- }
-#endif
-
- if (!enif_inspect_binary(env, argv[1], &in_bin)) {
- return enif_make_badarg(env);
- }
-
- i = get_pkey_crypt_options(env, argv[0], argv[3], &crypt_opt);
- if (i != PKEY_OK) {
- if (i == PKEY_NOTSUP)
- return atom_notsup;
- else
- return enif_make_badarg(env);
- }
-
- if (is_private) {
- if (get_pkey_private_key(env, argv[0], argv[2], &pkey) != PKEY_OK) {
- return enif_make_badarg(env);
- }
- } else {
- if (get_pkey_public_key(env, argv[0], argv[2], &pkey) != PKEY_OK) {
- return enif_make_badarg(env);
- }
- }
-
- out_bin.data = NULL;
- out_bin.size = 0;
- tmp_bin.data = NULL;
- tmp_bin.size = 0;
-
-#ifdef HAS_EVP_PKEY_CTX
- ctx = EVP_PKEY_CTX_new(pkey, NULL);
- if (!ctx) goto badarg;
-
-/* enif_get_atom(env,argv[0],algo,1024,ERL_NIF_LATIN1); */
-
- if (is_private) {
- if (is_encrypt) {
- /* private encrypt */
- if ((algo_init=EVP_PKEY_sign_init(ctx)) <= 0) {
- /* fprintf(stderr,"BADARG %s private encrypt algo_init=%d %s:%d\r\n", algo, algo_init, __FILE__, __LINE__); */
- goto badarg;
- }
- } else {
- /* private decrypt */
- if ((algo_init=EVP_PKEY_decrypt_init(ctx)) <= 0) {
- /* fprintf(stderr,"BADARG %s private decrypt algo_init=%d %s:%d\r\n", algo, algo_init, __FILE__, __LINE__); */
- goto badarg;
- }
- }
- } else {
- if (is_encrypt) {
- /* public encrypt */
- if ((algo_init=EVP_PKEY_encrypt_init(ctx)) <= 0) {
- /* fprintf(stderr,"BADARG %s public encrypt algo_init=%d %s:%d\r\n", algo,algo_init,__FILE__, __LINE__); */
- goto badarg;
- }
- } else {
- /* public decrypt */
- if ((algo_init=EVP_PKEY_verify_recover_init(ctx)) <= 0) {
- /* fprintf(stderr,"BADARG %s public decrypt algo_init=%d %s:%d\r\n", algo,algo_init,__FILE__, __LINE__); */
- goto badarg;
- }
- }
- }
-
- if (argv[0] == atom_rsa) {
- if (crypt_opt.signature_md != NULL
- && EVP_PKEY_CTX_set_signature_md(ctx, crypt_opt.signature_md) <= 0)
- goto badarg;
-#ifdef HAVE_RSA_SSLV23_PADDING
- if (crypt_opt.rsa_padding == RSA_SSLV23_PADDING) {
- if (is_encrypt) {
- tmplen = size_of_RSA(pkey);
- if (tmplen == 0) goto badarg;
- if (!enif_alloc_binary(tmplen, &tmp_bin)) goto badarg;
- if (RSA_padding_add_SSLv23(tmp_bin.data, tmplen, in_bin.data, in_bin.size) <= 0)
- goto badarg;
- in_bin = tmp_bin;
- }
- if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_NO_PADDING) <= 0) goto badarg;
- } else
-#endif
- {
- if (EVP_PKEY_CTX_set_rsa_padding(ctx, crypt_opt.rsa_padding) <= 0) goto badarg;
- }
-#ifdef HAVE_RSA_OAEP_MD
- if (crypt_opt.rsa_padding == RSA_PKCS1_OAEP_PADDING) {
- if (crypt_opt.rsa_oaep_md != NULL
- && EVP_PKEY_CTX_set_rsa_oaep_md(ctx, crypt_opt.rsa_oaep_md) <= 0)
- goto badarg;
- if (crypt_opt.rsa_mgf1_md != NULL
- && EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, crypt_opt.rsa_mgf1_md) <= 0) goto badarg;
- if (crypt_opt.rsa_oaep_label.data != NULL && crypt_opt.rsa_oaep_label.size > 0) {
- unsigned char *label_copy = NULL;
- label_copy = OPENSSL_malloc(crypt_opt.rsa_oaep_label.size);
- if (label_copy == NULL) goto badarg;
- memcpy((void *)(label_copy), (const void *)(crypt_opt.rsa_oaep_label.data),
- crypt_opt.rsa_oaep_label.size);
- if (EVP_PKEY_CTX_set0_rsa_oaep_label(ctx, label_copy,
- crypt_opt.rsa_oaep_label.size) <= 0) {
- OPENSSL_free(label_copy);
- label_copy = NULL;
- goto badarg;
- }
- }
- }
-#endif
- }
-
- if (is_private) {
- if (is_encrypt) {
- /* private_encrypt */
- i = EVP_PKEY_sign(ctx, NULL, &outlen, in_bin.data, in_bin.size);
- } else {
- /* private_decrypt */
- i = EVP_PKEY_decrypt(ctx, NULL, &outlen, in_bin.data, in_bin.size);
- }
- } else {
- if (is_encrypt) {
- /* public_encrypt */
- i = EVP_PKEY_encrypt(ctx, NULL, &outlen, in_bin.data, in_bin.size);
- } else {
- /* public_decrypt */
- i = EVP_PKEY_verify_recover(ctx, NULL, &outlen, in_bin.data, in_bin.size);
- }
- }
- /* fprintf(stderr,"i = %d %s:%d\r\n", i, __FILE__, __LINE__); */
-
- if (i != 1) goto badarg;
-
- enif_alloc_binary(outlen, &out_bin);
-
- if (is_private) {
- if (is_encrypt) {
- /* private_encrypt */
- i = EVP_PKEY_sign(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size);
- } else {
- /* private_decrypt */
- i = EVP_PKEY_decrypt(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size);
- }
- } else {
- if (is_encrypt) {
- /* public_encrypt */
- i = EVP_PKEY_encrypt(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size);
- } else {
- /* public_decrypt */
- i = EVP_PKEY_verify_recover(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size);
- }
- }
-
-#else
- /* Non-EVP cryptolib. Only support RSA */
-
- if (argv[0] != atom_rsa) {
- algo_init = -2; /* exitcode: notsup */
- goto badarg;
- }
- rsa = EVP_PKEY_get1_RSA(pkey);
- enif_alloc_binary(RSA_size(rsa), &out_bin);
-
- if (is_private) {
- if (is_encrypt) {
- /* non-evp rsa private encrypt */
- ERL_VALGRIND_ASSERT_MEM_DEFINED(in_bin.data,in_bin.size);
- i = RSA_private_encrypt(in_bin.size, in_bin.data,
- out_bin.data, rsa, crypt_opt.rsa_padding);
- if (i > 0) {
- ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, i);
- }
- } else {
- /* non-evp rsa private decrypt */
- i = RSA_private_decrypt(in_bin.size, in_bin.data,
- out_bin.data, rsa, crypt_opt.rsa_padding);
- if (i > 0) {
- ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, i);
- enif_realloc_binary(&out_bin, i);
- }
- }
- } else {
- if (is_encrypt) {
- /* non-evp rsa public encrypt */
- ERL_VALGRIND_ASSERT_MEM_DEFINED(in_bin.data,in_bin.size);
- i = RSA_public_encrypt(in_bin.size, in_bin.data,
- out_bin.data, rsa, crypt_opt.rsa_padding);
- if (i > 0) {
- ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, i);
- }
- } else {
- /* non-evp rsa public decrypt */
- i = RSA_public_decrypt(in_bin.size, in_bin.data,
- out_bin.data, rsa, crypt_opt.rsa_padding);
- if (i > 0) {
- ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, i);
- enif_realloc_binary(&out_bin, i);
- }
- }
- }
-
- outlen = i;
- RSA_free(rsa);
-#endif
-
- if ((i > 0) && argv[0] == atom_rsa && !is_encrypt) {
-#ifdef HAVE_RSA_SSLV23_PADDING
- if (crypt_opt.rsa_padding == RSA_SSLV23_PADDING) {
- unsigned char *p;
- tmplen = size_of_RSA(pkey);
- if (tmplen == 0) goto badarg;
- if (!enif_alloc_binary(tmplen, &tmp_bin))
- goto badarg;
- p = out_bin.data;
- p++;
- i = RSA_padding_check_SSLv23(tmp_bin.data, tmplen, p, out_bin.size - 1, tmplen);
- if (i >= 0) {
- outlen = i;
- in_bin = out_bin;
- out_bin = tmp_bin;
- tmp_bin = in_bin;
- i = 1;
- }
- }
-#endif
- }
-
- if (tmp_bin.data != NULL) {
- enif_release_binary(&tmp_bin);
- }
-
-#ifdef HAS_EVP_PKEY_CTX
- EVP_PKEY_CTX_free(ctx);
-#else
-#endif
- EVP_PKEY_free(pkey);
- if (i > 0) {
- ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, outlen);
- if (outlen != out_bin.size) {
- enif_realloc_binary(&out_bin, outlen);
- ERL_VALGRIND_ASSERT_MEM_DEFINED(out_bin.data, outlen);
- }
- return enif_make_binary(env, &out_bin);
- } else {
- enif_release_binary(&out_bin);
- return atom_error;
- }
-
- badarg:
- if (out_bin.data != NULL) {
- enif_release_binary(&out_bin);
- }
- if (tmp_bin.data != NULL) {
- enif_release_binary(&tmp_bin);
- }
-#ifdef HAS_EVP_PKEY_CTX
- EVP_PKEY_CTX_free(ctx);
-#else
-#endif
- EVP_PKEY_free(pkey);
- if (algo_init == -2)
- return atom_notsup;
- else
- return enif_make_badarg(env);
-}
-
-
-
-/*--------------------------------*/
-static ERL_NIF_TERM privkey_to_pubkey_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{ /* (Algorithm, PrivKey | KeyMap) */
- EVP_PKEY *pkey;
- ERL_NIF_TERM alg = argv[0];
- ERL_NIF_TERM result[8];
- if (get_pkey_private_key(env, alg, argv[1], &pkey) != PKEY_OK) {
- return enif_make_badarg(env);
- }
-
- if (alg == atom_rsa) {
- const BIGNUM *n = NULL, *e = NULL, *d = NULL;
- RSA *rsa = EVP_PKEY_get1_RSA(pkey);
- if (rsa) {
- RSA_get0_key(rsa, &n, &e, &d);
- result[0] = bin_from_bn(env, e); // Exponent E
- result[1] = bin_from_bn(env, n); // Modulus N = p*q
- RSA_free(rsa);
- EVP_PKEY_free(pkey);
- return enif_make_list_from_array(env, result, 2);
- }
-
- } else if (argv[0] == atom_dss) {
- const BIGNUM *p = NULL, *q = NULL, *g = NULL, *pub_key = NULL;
- DSA *dsa = EVP_PKEY_get1_DSA(pkey);
- if (dsa) {
- DSA_get0_pqg(dsa, &p, &q, &g);
- DSA_get0_key(dsa, &pub_key, NULL);
- result[0] = bin_from_bn(env, p);
- result[1] = bin_from_bn(env, q);
- result[2] = bin_from_bn(env, g);
- result[3] = bin_from_bn(env, pub_key);
- DSA_free(dsa);
- EVP_PKEY_free(pkey);
- return enif_make_list_from_array(env, result, 4);
- }
-
- } else if (argv[0] == atom_ecdsa) {
-#if defined(HAVE_EC)
- /* not yet implemented
- EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pkey);
- if (ec) {
- / * Example of result:
- {
- Curve = {Field, Prime, Point, Order, CoFactor} =
- {
- Field = {prime_field,<<255,...,255>>},
- Prime = {<<255,...,252>>,
- <<90,...,75>>,
- <<196,...,144>>
- },
- Point = <<4,...,245>>,
- Order = <<255,...,81>>,
- CoFactor = <<1>>
- },
- Key = <<151,...,62>>
- }
- or
- {
- Curve =
- {characteristic_two_field,
- M,
- Basis = {tpbasis, _}
- | {ppbasis, k1, k2, k3}
- },
- Key
- }
- * /
- EVP_PKEY_free(pkey);
- return enif_make_list_from_array(env, ..., ...);
- */
-#endif
- }
-
- if (pkey) EVP_PKEY_free(pkey);
- return enif_make_badarg(env);
-}
-
-/*================================================================*/
-
-static ERL_NIF_TERM rand_seed_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{
- ErlNifBinary seed_bin;
-
- if (!enif_inspect_binary(env, argv[0], &seed_bin))
- return enif_make_badarg(env);
- RAND_seed(seed_bin.data,seed_bin.size);
- return atom_ok;
-}
-
-/*================================================================*/
-/* Engine */
-/*================================================================*/
-static ERL_NIF_TERM engine_by_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (EngineId) */
-#ifdef HAS_ENGINE_SUPPORT
- ERL_NIF_TERM ret;
- ErlNifBinary engine_id_bin;
- char *engine_id;
- ENGINE *engine;
- struct engine_ctx *ctx;
-
- // Get Engine Id
- if(!enif_inspect_binary(env, argv[0], &engine_id_bin)) {
- PRINTF_ERR0("engine_by_id_nif Leaved: badarg");
- return enif_make_badarg(env);
- } else {
- engine_id = enif_alloc(engine_id_bin.size+1);
- (void) memcpy(engine_id, engine_id_bin.data, engine_id_bin.size);
- engine_id[engine_id_bin.size] = '\0';
- }
-
- engine = ENGINE_by_id(engine_id);
- if(!engine) {
- enif_free(engine_id);
- PRINTF_ERR0("engine_by_id_nif Leaved: {error, bad_engine_id}");
- return enif_make_tuple2(env, atom_error, atom_bad_engine_id);
- }
-
- ctx = enif_alloc_resource(engine_ctx_rtype, sizeof(struct engine_ctx));
- ctx->engine = engine;
- ctx->id = engine_id;
-
- ret = enif_make_resource(env, ctx);
- enif_release_resource(ctx);
-
- return enif_make_tuple2(env, atom_ok, ret);
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM engine_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Engine) */
-#ifdef HAS_ENGINE_SUPPORT
- ERL_NIF_TERM ret = atom_ok;
- struct engine_ctx *ctx;
-
- // Get Engine
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
- PRINTF_ERR0("engine_init_nif Leaved: Parameter not an engine resource object");
- return enif_make_badarg(env);
- }
- if (!ENGINE_init(ctx->engine)) {
- //ERR_print_errors_fp(stderr);
- PRINTF_ERR0("engine_init_nif Leaved: {error, engine_init_failed}");
- return enif_make_tuple2(env, atom_error, atom_engine_init_failed);
- }
-
- return ret;
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM engine_free_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Engine) */
-#ifdef HAS_ENGINE_SUPPORT
- struct engine_ctx *ctx;
-
- // Get Engine
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
- PRINTF_ERR0("engine_free_nif Leaved: Parameter not an engine resource object");
- return enif_make_badarg(env);
- }
-
- ENGINE_free(ctx->engine);
- return atom_ok;
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM engine_finish_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Engine) */
-#ifdef HAS_ENGINE_SUPPORT
- struct engine_ctx *ctx;
-
- // Get Engine
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
- PRINTF_ERR0("engine_finish_nif Leaved: Parameter not an engine resource object");
- return enif_make_badarg(env);
- }
-
- ENGINE_finish(ctx->engine);
- return atom_ok;
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM engine_load_dynamic_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* () */
-#ifdef HAS_ENGINE_SUPPORT
- ENGINE_load_dynamic();
- return atom_ok;
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM engine_ctrl_cmd_strings_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Engine, Commands) */
-#ifdef HAS_ENGINE_SUPPORT
- ERL_NIF_TERM ret = atom_ok;
- unsigned int cmds_len = 0;
- char **cmds = NULL;
- struct engine_ctx *ctx;
- int i, optional = 0;
-
- // Get Engine
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
- PRINTF_ERR0("engine_ctrl_cmd_strings_nif Leaved: Parameter not an engine resource object");
- return enif_make_badarg(env);
- }
-
- PRINTF_ERR1("Engine Id: %s\r\n", ENGINE_get_id(ctx->engine));
-
- // Get Command List
- if(!enif_get_list_length(env, argv[1], &cmds_len)) {
- PRINTF_ERR0("engine_ctrl_cmd_strings_nif Leaved: Bad Command List");
- return enif_make_badarg(env);
- } else {
- cmds_len *= 2; // Key-Value list from erlang
- cmds = enif_alloc((cmds_len+1)*sizeof(char*));
- if(get_engine_load_cmd_list(env, argv[1], cmds, 0)) {
- PRINTF_ERR0("engine_ctrl_cmd_strings_nif Leaved: Couldn't read Command List");
- ret = enif_make_badarg(env);
- goto error;
- }
- }
-
- if(!enif_get_int(env, argv[2], &optional)) {
- PRINTF_ERR0("engine_ctrl_cmd_strings_nif Leaved: Parameter optional not an integer");
- return enif_make_badarg(env);
- }
-
- for(i = 0; i < cmds_len; i+=2) {
- PRINTF_ERR2("Cmd: %s:%s\r\n",
- cmds[i] ? cmds[i] : "(NULL)",
- cmds[i+1] ? cmds[i+1] : "(NULL)");
- if(!ENGINE_ctrl_cmd_string(ctx->engine, cmds[i], cmds[i+1], optional)) {
- PRINTF_ERR2("Command failed: %s:%s\r\n",
- cmds[i] ? cmds[i] : "(NULL)",
- cmds[i+1] ? cmds[i+1] : "(NULL)");
- //ENGINE_free(ctx->engine);
- ret = enif_make_tuple2(env, atom_error, atom_ctrl_cmd_failed);
- PRINTF_ERR0("engine_ctrl_cmd_strings_nif Leaved: {error, ctrl_cmd_failed}");
- goto error;
- }
- }
-
- error:
- for(i = 0; cmds != NULL && cmds[i] != NULL; i++)
- enif_free(cmds[i]);
- enif_free(cmds);
- return ret;
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM engine_add_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Engine) */
-#ifdef HAS_ENGINE_SUPPORT
- struct engine_ctx *ctx;
-
- // Get Engine
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
- PRINTF_ERR0("engine_add_nif Leaved: Parameter not an engine resource object");
- return enif_make_badarg(env);
- }
-
- if (!ENGINE_add(ctx->engine)) {
- PRINTF_ERR0("engine_add_nif Leaved: {error, add_engine_failed}");
- return enif_make_tuple2(env, atom_error, atom_add_engine_failed);
- }
- return atom_ok;
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM engine_remove_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Engine) */
-#ifdef HAS_ENGINE_SUPPORT
- struct engine_ctx *ctx;
-
- // Get Engine
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
- PRINTF_ERR0("engine_remove_nif Leaved: Parameter not an engine resource object");
- return enif_make_badarg(env);
- }
-
- if (!ENGINE_remove(ctx->engine)) {
- PRINTF_ERR0("engine_remove_nif Leaved: {error, remove_engine_failed}");
- return enif_make_tuple2(env, atom_error, atom_remove_engine_failed);
- }
- return atom_ok;
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM engine_register_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Engine, EngineMethod) */
-#ifdef HAS_ENGINE_SUPPORT
- struct engine_ctx *ctx;
- unsigned int method;
-
- // Get Engine
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
- PRINTF_ERR0("engine_register_nif Leaved: Parameter not an engine resource object");
- return enif_make_badarg(env);
- }
- // Get Method
- if (!enif_get_uint(env, argv[1], &method)) {
- PRINTF_ERR0("engine_register_nif Leaved: Parameter Method not an uint");
- return enif_make_badarg(env);
- }
-
- switch(method)
- {
-#ifdef ENGINE_METHOD_RSA
- case ENGINE_METHOD_RSA:
- if (!ENGINE_register_RSA(ctx->engine))
- return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
- break;
-#endif
-#ifdef ENGINE_METHOD_DSA
- case ENGINE_METHOD_DSA:
- if (!ENGINE_register_DSA(ctx->engine))
- return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
- break;
-#endif
-#ifdef ENGINE_METHOD_DH
- case ENGINE_METHOD_DH:
- if (!ENGINE_register_DH(ctx->engine))
- return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
- break;
-#endif
-#ifdef ENGINE_METHOD_RAND
- case ENGINE_METHOD_RAND:
- if (!ENGINE_register_RAND(ctx->engine))
- return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
- break;
-#endif
-#ifdef ENGINE_METHOD_ECDH
- case ENGINE_METHOD_ECDH:
- if (!ENGINE_register_ECDH(ctx->engine))
- return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
- break;
-#endif
-#ifdef ENGINE_METHOD_ECDSA
- case ENGINE_METHOD_ECDSA:
- if (!ENGINE_register_ECDSA(ctx->engine))
- return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
- break;
-#endif
-#ifdef ENGINE_METHOD_STORE
- case ENGINE_METHOD_STORE:
- if (!ENGINE_register_STORE(ctx->engine))
- return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
- break;
-#endif
-#ifdef ENGINE_METHOD_CIPHERS
- case ENGINE_METHOD_CIPHERS:
- if (!ENGINE_register_ciphers(ctx->engine))
- return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
- break;
-#endif
-#ifdef ENGINE_METHOD_DIGESTS
- case ENGINE_METHOD_DIGESTS:
- if (!ENGINE_register_digests(ctx->engine))
- return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
- break;
-#endif
-#ifdef ENGINE_METHOD_PKEY_METHS
- case ENGINE_METHOD_PKEY_METHS:
- if (!ENGINE_register_pkey_meths(ctx->engine))
- return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
- break;
-#endif
-#ifdef ENGINE_METHOD_PKEY_ASN1_METHS
- case ENGINE_METHOD_PKEY_ASN1_METHS:
- if (!ENGINE_register_pkey_asn1_meths(ctx->engine))
- return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
- break;
-#endif
-#ifdef ENGINE_METHOD_EC
- case ENGINE_METHOD_EC:
- if (!ENGINE_register_EC(ctx->engine))
- return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
- break;
-#endif
- default:
- return enif_make_tuple2(env, atom_error, atom_engine_method_not_supported);
- break;
- }
- return atom_ok;
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM engine_unregister_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Engine, EngineMethod) */
-#ifdef HAS_ENGINE_SUPPORT
- struct engine_ctx *ctx;
- unsigned int method;
-
- // Get Engine
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
- PRINTF_ERR0("engine_unregister_nif Leaved: Parameter not an engine resource object");
- return enif_make_badarg(env);
- }
- // Get Method
- if (!enif_get_uint(env, argv[1], &method)) {
- PRINTF_ERR0("engine_unregister_nif Leaved: Parameter Method not an uint");
- return enif_make_badarg(env);
- }
-
- switch(method)
- {
-#ifdef ENGINE_METHOD_RSA
- case ENGINE_METHOD_RSA:
- ENGINE_unregister_RSA(ctx->engine);
- break;
-#endif
-#ifdef ENGINE_METHOD_DSA
- case ENGINE_METHOD_DSA:
- ENGINE_unregister_DSA(ctx->engine);
- break;
-#endif
-#ifdef ENGINE_METHOD_DH
- case ENGINE_METHOD_DH:
- ENGINE_unregister_DH(ctx->engine);
- break;
-#endif
-#ifdef ENGINE_METHOD_RAND
- case ENGINE_METHOD_RAND:
- ENGINE_unregister_RAND(ctx->engine);
- break;
-#endif
-#ifdef ENGINE_METHOD_ECDH
- case ENGINE_METHOD_ECDH:
- ENGINE_unregister_ECDH(ctx->engine);
- break;
-#endif
-#ifdef ENGINE_METHOD_ECDSA
- case ENGINE_METHOD_ECDSA:
- ENGINE_unregister_ECDSA(ctx->engine);
- break;
-#endif
-#ifdef ENGINE_METHOD_STORE
- case ENGINE_METHOD_STORE:
- ENGINE_unregister_STORE(ctx->engine);
- break;
-#endif
-#ifdef ENGINE_METHOD_CIPHERS
- case ENGINE_METHOD_CIPHERS:
- ENGINE_unregister_ciphers(ctx->engine);
- break;
-#endif
-#ifdef ENGINE_METHOD_DIGESTS
- case ENGINE_METHOD_DIGESTS:
- ENGINE_unregister_digests(ctx->engine);
- break;
-#endif
-#ifdef ENGINE_METHOD_PKEY_METHS
- case ENGINE_METHOD_PKEY_METHS:
- ENGINE_unregister_pkey_meths(ctx->engine);
- break;
-#endif
-#ifdef ENGINE_METHOD_PKEY_ASN1_METHS
- case ENGINE_METHOD_PKEY_ASN1_METHS:
- ENGINE_unregister_pkey_asn1_meths(ctx->engine);
- break;
-#endif
-#ifdef ENGINE_METHOD_EC
- case ENGINE_METHOD_EC:
- ENGINE_unregister_EC(ctx->engine);
- break;
-#endif
- default:
- break;
- }
- return atom_ok;
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM engine_get_first_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Engine) */
-#ifdef HAS_ENGINE_SUPPORT
- ERL_NIF_TERM ret;
- ENGINE *engine;
- ErlNifBinary engine_bin;
- struct engine_ctx *ctx;
-
- engine = ENGINE_get_first();
- if(!engine) {
- enif_alloc_binary(0, &engine_bin);
- engine_bin.size = 0;
- return enif_make_tuple2(env, atom_ok, enif_make_binary(env, &engine_bin));
- }
-
- ctx = enif_alloc_resource(engine_ctx_rtype, sizeof(struct engine_ctx));
- ctx->engine = engine;
- ctx->id = NULL;
-
- ret = enif_make_resource(env, ctx);
- enif_release_resource(ctx);
-
- return enif_make_tuple2(env, atom_ok, ret);
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM engine_get_next_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Engine) */
-#ifdef HAS_ENGINE_SUPPORT
- ERL_NIF_TERM ret;
- ENGINE *engine;
- ErlNifBinary engine_bin;
- struct engine_ctx *ctx, *next_ctx;
-
- // Get Engine
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
- PRINTF_ERR0("engine_get_next_nif Leaved: Parameter not an engine resource object");
- return enif_make_badarg(env);
- }
- engine = ENGINE_get_next(ctx->engine);
- if (!engine) {
- enif_alloc_binary(0, &engine_bin);
- engine_bin.size = 0;
- return enif_make_tuple2(env, atom_ok, enif_make_binary(env, &engine_bin));
- }
-
- next_ctx = enif_alloc_resource(engine_ctx_rtype, sizeof(struct engine_ctx));
- next_ctx->engine = engine;
- next_ctx->id = NULL;
-
- ret = enif_make_resource(env, next_ctx);
- enif_release_resource(next_ctx);
-
- return enif_make_tuple2(env, atom_ok, ret);
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM engine_get_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Engine) */
-#ifdef HAS_ENGINE_SUPPORT
- ErlNifBinary engine_id_bin;
- const char *engine_id;
- int size;
- struct engine_ctx *ctx;
-
- // Get Engine
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
- PRINTF_ERR0("engine_get_id_nif Leaved: Parameter not an engine resource object");
- return enif_make_badarg(env);
- }
-
- engine_id = ENGINE_get_id(ctx->engine);
- if (!engine_id) {
- enif_alloc_binary(0, &engine_id_bin);
- engine_id_bin.size = 0;
- return enif_make_binary(env, &engine_id_bin);
- }
-
- size = strlen(engine_id);
- enif_alloc_binary(size, &engine_id_bin);
- engine_id_bin.size = size;
- memcpy(engine_id_bin.data, engine_id, size);
-
- return enif_make_binary(env, &engine_id_bin);
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM engine_get_name_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Engine) */
-#ifdef HAS_ENGINE_SUPPORT
- ErlNifBinary engine_name_bin;
- const char *engine_name;
- int size;
- struct engine_ctx *ctx;
-
- // Get Engine
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
- PRINTF_ERR0("engine_get_id_nif Leaved: Parameter not an engine resource object");
- return enif_make_badarg(env);
- }
-
- engine_name = ENGINE_get_name(ctx->engine);
- if (!engine_name) {
- enif_alloc_binary(0, &engine_name_bin);
- engine_name_bin.size = 0;
- return enif_make_binary(env, &engine_name_bin);
- }
-
- size = strlen(engine_name);
- enif_alloc_binary(size, &engine_name_bin);
- engine_name_bin.size = size;
- memcpy(engine_name_bin.data, engine_name, size);
-
- return enif_make_binary(env, &engine_name_bin);
-#else
- return atom_notsup;
-#endif
-}
-
-#ifdef HAS_ENGINE_SUPPORT
-static int get_engine_load_cmd_list(ErlNifEnv* env, const ERL_NIF_TERM term, char **cmds, int i)
-{
- ERL_NIF_TERM head, tail;
- const ERL_NIF_TERM *tmp_tuple;
- ErlNifBinary tmpbin;
- int arity;
- char* tmpstr;
-
- if(!enif_is_empty_list(env, term)) {
- if(!enif_get_list_cell(env, term, &head, &tail)) {
- cmds[i] = NULL;
- return -1;
- } else {
- if(!enif_get_tuple(env, head, &arity, &tmp_tuple) || arity != 2) {
- cmds[i] = NULL;
- return -1;
- } else {
- if(!enif_inspect_binary(env, tmp_tuple[0], &tmpbin)) {
- cmds[i] = NULL;
- return -1;
- } else {
- tmpstr = enif_alloc(tmpbin.size+1);
- (void) memcpy(tmpstr, tmpbin.data, tmpbin.size);
- tmpstr[tmpbin.size] = '\0';
- cmds[i++] = tmpstr;
- }
- if(!enif_inspect_binary(env, tmp_tuple[1], &tmpbin)) {
- cmds[i] = NULL;
- return -1;
- } else {
- if(tmpbin.size == 0)
- cmds[i++] = NULL;
- else {
- tmpstr = enif_alloc(tmpbin.size+1);
- (void) memcpy(tmpstr, tmpbin.data, tmpbin.size);
- tmpstr[tmpbin.size] = '\0';
- cmds[i++] = tmpstr;
- }
- }
- return get_engine_load_cmd_list(env, tail, cmds, i);
- }
- }
- } else {
- cmds[i] = NULL;
- return 0;
- }
-}
-#endif
-
-static ERL_NIF_TERM engine_get_all_methods_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* () */
-#ifdef HAS_ENGINE_SUPPORT
- ERL_NIF_TERM method_array[12];
- int i = 0;
-
-#ifdef ENGINE_METHOD_RSA
- method_array[i++] = atom_engine_method_rsa;
-#endif
-#ifdef ENGINE_METHOD_DSA
- method_array[i++] = atom_engine_method_dsa;
-#endif
-#ifdef ENGINE_METHOD_DH
- method_array[i++] = atom_engine_method_dh;
-#endif
-#ifdef ENGINE_METHOD_RAND
- method_array[i++] = atom_engine_method_rand;
-#endif
-#ifdef ENGINE_METHOD_ECDH
- method_array[i++] = atom_engine_method_ecdh;
-#endif
-#ifdef ENGINE_METHOD_ECDSA
- method_array[i++] = atom_engine_method_ecdsa;
-#endif
-#ifdef ENGINE_METHOD_STORE
- method_array[i++] = atom_engine_method_store;
-#endif
-#ifdef ENGINE_METHOD_CIPHERS
- method_array[i++] = atom_engine_method_ciphers;
-#endif
-#ifdef ENGINE_METHOD_DIGESTS
- method_array[i++] = atom_engine_method_digests;
-#endif
-#ifdef ENGINE_METHOD_PKEY_METHS
- method_array[i++] = atom_engine_method_pkey_meths;
-#endif
-#ifdef ENGINE_METHOD_PKEY_ASN1_METHS
- method_array[i++] = atom_engine_method_pkey_asn1_meths;
-#endif
-#ifdef ENGINE_METHOD_EC
- method_array[i++] = atom_engine_method_ec;
-#endif
-
- return enif_make_list_from_array(env, method_array, i);
-#else
- return atom_notsup;
-#endif
-}
diff --git a/lib/crypto/c_src/crypto_callback.c b/lib/crypto/c_src/crypto_callback.c
index 0cc7dd609d..0141ccd840 100644
--- a/lib/crypto/c_src/crypto_callback.c
+++ b/lib/crypto/c_src/crypto_callback.c
@@ -21,6 +21,7 @@
#include <stdio.h>
#include <string.h>
#include <openssl/opensslconf.h>
+#include <stdint.h>
#include <erl_nif.h>
#include "crypto_callback.h"
@@ -64,22 +65,36 @@ static void nomem(size_t size, const char* op)
static void* crypto_alloc(size_t size CCB_FILE_LINE_ARGS)
{
- void *ret = enif_alloc(size);
+ void *ret;
- if (!ret && size)
- nomem(size, "allocate");
+ if ((ret = enif_alloc(size)) == NULL)
+ goto err;
return ret;
+
+ err:
+ if (size)
+ nomem(size, "allocate");
+ return NULL;
}
static void* crypto_realloc(void* ptr, size_t size CCB_FILE_LINE_ARGS)
{
- void* ret = enif_realloc(ptr, size);
+ void* ret;
- if (!ret && size)
- nomem(size, "reallocate");
+ if ((ret = enif_realloc(ptr, size)) == NULL)
+ goto err;
return ret;
+
+ err:
+ if (size)
+ nomem(size, "reallocate");
+ return NULL;
}
+
static void crypto_free(void* ptr CCB_FILE_LINE_ARGS)
{
+ if (ptr == NULL)
+ return;
+
enif_free(ptr);
}
@@ -160,19 +175,26 @@ DLLEXPORT struct crypto_callbacks* get_crypto_callbacks(int nlocks)
#ifdef OPENSSL_THREADS
if (nlocks > 0) {
int i;
- lock_vec = enif_alloc(nlocks*sizeof(*lock_vec));
- if (lock_vec==NULL) return NULL;
- memset(lock_vec, 0, nlocks*sizeof(*lock_vec));
-
+
+ if ((size_t)nlocks > SIZE_MAX / sizeof(*lock_vec))
+ goto err;
+ if ((lock_vec = enif_alloc((size_t)nlocks * sizeof(*lock_vec))) == NULL)
+ goto err;
+
+ memset(lock_vec, 0, (size_t)nlocks * sizeof(*lock_vec));
+
for (i=nlocks-1; i>=0; --i) {
- lock_vec[i] = enif_rwlock_create("crypto_stat");
- if (lock_vec[i]==NULL) return NULL;
+ if ((lock_vec[i] = enif_rwlock_create("crypto_stat")) == NULL)
+ goto err;
}
}
#endif
is_initialized = 1;
}
return &the_struct;
+
+ err:
+ return NULL;
}
#ifdef HAVE_DYNAMIC_CRYPTO_LIB
diff --git a/lib/crypto/c_src/dh.c b/lib/crypto/c_src/dh.c
new file mode 100644
index 0000000000..38eb534d99
--- /dev/null
+++ b/lib/crypto/c_src/dh.c
@@ -0,0 +1,294 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "dh.h"
+#include "bn.h"
+
+ERL_NIF_TERM dh_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (PrivKey|undefined, DHParams=[P,G], Mpint, Len|0) */
+ DH *dh_params = NULL;
+ unsigned int mpint; /* 0 or 4 */
+ ERL_NIF_TERM head, tail;
+ BIGNUM *dh_p = NULL;
+ BIGNUM *dh_p_shared;
+ BIGNUM *dh_g = NULL;
+ BIGNUM *priv_key_in = NULL;
+ unsigned long len = 0;
+ unsigned char *pub_ptr, *prv_ptr;
+ int pub_len, prv_len;
+ ERL_NIF_TERM ret_pub, ret_prv, ret;
+ const BIGNUM *pub_key_gen, *priv_key_gen;
+#ifdef HAS_EVP_PKEY_CTX
+ EVP_PKEY_CTX *ctx = NULL;
+ EVP_PKEY *dhkey = NULL, *params = NULL;
+#endif
+
+ ASSERT(argc == 4);
+
+ if (argv[0] != atom_undefined) {
+ if (!get_bn_from_bin(env, argv[0], &priv_key_in))
+ goto bad_arg;
+ }
+ if (!enif_get_list_cell(env, argv[1], &head, &tail))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, head, &dh_p))
+ goto bad_arg;
+
+ if (!enif_get_list_cell(env, tail, &head, &tail))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, head, &dh_g))
+ goto bad_arg;
+
+ if (!enif_is_empty_list(env, tail))
+ goto bad_arg;
+
+ if (!enif_get_uint(env, argv[2], &mpint))
+ goto bad_arg;
+ if (mpint != 0 && mpint != 4)
+ goto bad_arg;
+
+ if (!enif_get_ulong(env, argv[3], &len))
+ goto bad_arg;
+ if (len > LONG_MAX)
+ goto bad_arg;
+
+ /* Load dh_params with values to use by the generator.
+ Mem mgmnt transfered from dh_p etc to dh_params */
+ if ((dh_params = DH_new()) == NULL)
+ goto bad_arg;
+ if (priv_key_in) {
+ if (!DH_set0_key(dh_params, NULL, priv_key_in))
+ goto bad_arg;
+ /* On success, dh_params owns priv_key_in */
+ priv_key_in = NULL;
+ }
+ if (!DH_set0_pqg(dh_params, dh_p, NULL, dh_g))
+ goto bad_arg;
+ dh_p_shared = dh_p; /* Don't free this because dh_params owns it */
+ /* On success, dh_params owns dh_p and dh_g */
+ dh_p = NULL;
+ dh_g = NULL;
+
+ if (len) {
+ int bn_len;
+
+ if ((bn_len = BN_num_bits(dh_p_shared)) < 0)
+ goto bad_arg;
+ dh_p_shared = NULL; /* dh_params owns the reference */
+ if (len >= (size_t)bn_len)
+ goto bad_arg;
+
+ if (!DH_set_length(dh_params, (long)len))
+ goto bad_arg;
+ }
+
+#ifdef HAS_EVP_PKEY_CTX
+ if ((params = EVP_PKEY_new()) == NULL)
+ goto err;
+
+ /* set the key referenced by params to dh_params... */
+ if (EVP_PKEY_set1_DH(params, dh_params) != 1)
+ goto err;
+
+ if ((ctx = EVP_PKEY_CTX_new(params, NULL)) == NULL)
+ goto err;
+
+ if (EVP_PKEY_keygen_init(ctx) != 1)
+ goto err;
+
+ if ((dhkey = EVP_PKEY_new()) == NULL)
+ goto err;
+
+ /* key gen op, key written to ppkey (=last arg) */
+ if (EVP_PKEY_keygen(ctx, &dhkey) != 1)
+ goto err;
+
+ DH_free(dh_params);
+ if ((dh_params = EVP_PKEY_get1_DH(dhkey)) == NULL)
+ goto err;
+
+#else
+ if (!DH_generate_key(dh_params))
+ goto err;
+#endif
+
+ DH_get0_key(dh_params, &pub_key_gen, &priv_key_gen);
+
+ if ((pub_len = BN_num_bytes(pub_key_gen)) < 0)
+ goto err;
+ if ((prv_len = BN_num_bytes(priv_key_gen)) < 0)
+ goto err;
+
+ if ((pub_ptr = enif_make_new_binary(env, (size_t)pub_len+mpint, &ret_pub)) == NULL)
+ goto err;
+ if ((prv_ptr = enif_make_new_binary(env, (size_t)prv_len+mpint, &ret_prv)) == NULL)
+ goto err;
+
+ if (mpint) {
+ put_uint32(pub_ptr, (unsigned int)pub_len);
+ pub_ptr += 4;
+
+ put_uint32(prv_ptr, (unsigned int)prv_len);
+ prv_ptr += 4;
+ }
+
+ if (BN_bn2bin(pub_key_gen, pub_ptr) < 0)
+ goto err;
+ if (BN_bn2bin(priv_key_gen, prv_ptr) < 0)
+ goto err;
+
+ ERL_VALGRIND_MAKE_MEM_DEFINED(pub_ptr, pub_len);
+ ERL_VALGRIND_MAKE_MEM_DEFINED(prv_ptr, prv_len);
+
+ ret = enif_make_tuple2(env, ret_pub, ret_prv);
+ goto done;
+
+ bad_arg:
+ ret = enif_make_badarg(env);
+ goto done;
+
+ err:
+ ret = atom_error;
+
+ done:
+ if (priv_key_in)
+ BN_free(priv_key_in);
+ if (dh_p)
+ BN_free(dh_p);
+ if (dh_g)
+ BN_free(dh_g);
+ if (dh_params)
+ DH_free(dh_params);
+
+#ifdef HAS_EVP_PKEY_CTX
+ if (ctx)
+ EVP_PKEY_CTX_free(ctx);
+ if (dhkey)
+ EVP_PKEY_free(dhkey);
+ if (params)
+ EVP_PKEY_free(params);
+#endif
+
+ return ret;
+}
+
+ERL_NIF_TERM dh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (OthersPublicKey, MyPrivateKey, DHParams=[P,G]) */
+ BIGNUM *other_pub_key = NULL;
+ BIGNUM *dh_p = NULL;
+ BIGNUM *dh_g = NULL;
+ BIGNUM *dummy_pub_key = NULL;
+ BIGNUM *priv_key = NULL;
+ DH *dh_priv = NULL;
+ ERL_NIF_TERM head, tail, ret;
+ ErlNifBinary ret_bin;
+ int size;
+ int ret_bin_alloc = 0;
+ int dh_size;
+
+ /* Check the arguments and get
+ my private key (dh_priv),
+ the peer's public key (other_pub_key),
+ the parameters p & q
+ */
+ ASSERT(argc == 3);
+
+ if (!get_bn_from_bin(env, argv[0], &other_pub_key))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, argv[1], &priv_key))
+ goto bad_arg;
+
+ if (!enif_get_list_cell(env, argv[2], &head, &tail))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, head, &dh_p))
+ goto bad_arg;
+
+ if (!enif_get_list_cell(env, tail, &head, &tail))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, head, &dh_g))
+ goto bad_arg;
+
+ if (!enif_is_empty_list(env, tail))
+ goto bad_arg;
+
+ /* Note: DH_set0_key() does not allow setting only the
+ * private key, although DH_compute_key() does not use the
+ * public key. Work around this limitation by setting
+ * the public key to a copy of the private key.
+ */
+ if ((dummy_pub_key = BN_dup(priv_key)) == NULL)
+ goto err;
+ if ((dh_priv = DH_new()) == NULL)
+ goto err;
+
+ if (!DH_set0_key(dh_priv, dummy_pub_key, priv_key))
+ goto err;
+ /* dh_priv owns dummy_pub_key and priv_key now */
+ dummy_pub_key = NULL;
+ priv_key = NULL;
+
+ if (!DH_set0_pqg(dh_priv, dh_p, NULL, dh_g))
+ goto err;
+ /* dh_priv owns dh_p and dh_g now */
+ dh_p = NULL;
+ dh_g = NULL;
+
+ if ((dh_size = DH_size(dh_priv)) < 0)
+ goto err;
+ if (!enif_alloc_binary((size_t)dh_size, &ret_bin))
+ goto err;
+ ret_bin_alloc = 1;
+
+ if ((size = DH_compute_key(ret_bin.data, other_pub_key, dh_priv)) < 0)
+ goto err;
+ if (size == 0)
+ goto err;
+
+ if ((size_t)size != ret_bin.size) {
+ if (!enif_realloc_binary(&ret_bin, (size_t)size))
+ goto err;
+ }
+
+ ret = enif_make_binary(env, &ret_bin);
+ ret_bin_alloc = 0;
+ goto done;
+
+ bad_arg:
+ err:
+ if (ret_bin_alloc)
+ enif_release_binary(&ret_bin);
+ ret = enif_make_badarg(env);
+
+ done:
+ if (other_pub_key)
+ BN_free(other_pub_key);
+ if (priv_key)
+ BN_free(priv_key);
+ if (dh_p)
+ BN_free(dh_p);
+ if (dh_g)
+ BN_free(dh_g);
+ if (dummy_pub_key)
+ BN_free(dummy_pub_key);
+ if (dh_priv)
+ DH_free(dh_priv);
+
+ return ret;
+}
diff --git a/lib/crypto/c_src/dh.h b/lib/crypto/c_src/dh.h
new file mode 100644
index 0000000000..a996b0ea28
--- /dev/null
+++ b/lib/crypto/c_src/dh.h
@@ -0,0 +1,29 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_DH_H__
+#define E_DH_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM dh_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM dh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_DH_H__ */
diff --git a/lib/crypto/c_src/digest.c b/lib/crypto/c_src/digest.c
new file mode 100644
index 0000000000..fec286c000
--- /dev/null
+++ b/lib/crypto/c_src/digest.c
@@ -0,0 +1,125 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "digest.h"
+
+static struct digest_type_t digest_types[] =
+{
+ {{"md4"}, {&EVP_md4}},
+ {{"md5"}, {&EVP_md5}},
+ {{"ripemd160"}, {&EVP_ripemd160}},
+ {{"sha"}, {&EVP_sha1}},
+ {{"sha224"},
+#ifdef HAVE_SHA224
+ {&EVP_sha224}
+#else
+ {NULL}
+#endif
+ },
+ {{"sha256"},
+#ifdef HAVE_SHA256
+ {&EVP_sha256}
+#else
+ {NULL}
+#endif
+ },
+ {{"sha384"},
+#ifdef HAVE_SHA384
+ {&EVP_sha384}
+#else
+ {NULL}
+#endif
+ },
+ {{"sha512"},
+#ifdef HAVE_SHA512
+ {&EVP_sha512}
+#else
+ {NULL}
+#endif
+ },
+ {{"sha3_224"},
+#ifdef HAVE_SHA3_224
+ {&EVP_sha3_224}
+#else
+ {NULL}
+#endif
+ },
+ {{"sha3_256"},
+#ifdef HAVE_SHA3_256
+ {&EVP_sha3_256}
+#else
+ {NULL}
+#endif
+ },
+ {{"sha3_384"},
+#ifdef HAVE_SHA3_384
+ {&EVP_sha3_384}
+#else
+ {NULL}
+#endif
+ },
+ {{"sha3_512"},
+#ifdef HAVE_SHA3_512
+ {&EVP_sha3_512}
+#else
+ {NULL}
+#endif
+ },
+ {{"blake2b"},
+#ifdef HAVE_BLAKE2
+ {&EVP_blake2b512}
+#else
+ {NULL}
+#endif
+ },
+ {{"blake2s"},
+#ifdef HAVE_BLAKE2
+ {&EVP_blake2s256}
+#else
+ {NULL}
+#endif
+ },
+
+ {{NULL}, {NULL}}
+};
+
+void init_digest_types(ErlNifEnv* env)
+{
+ struct digest_type_t* p = digest_types;
+
+ for (p = digest_types; p->type.str; p++) {
+ p->type.atom = enif_make_atom(env, p->type.str);
+ if (p->md.funcp)
+ p->md.p = p->md.funcp();
+ }
+ p->type.atom = atom_false; /* end marker */
+}
+
+struct digest_type_t* get_digest_type(ERL_NIF_TERM type)
+{
+ struct digest_type_t* p = NULL;
+ for (p = digest_types; p->type.atom != atom_false; p++) {
+ if (type == p->type.atom) {
+ return p;
+ }
+ }
+ return NULL;
+}
+
diff --git a/lib/crypto/c_src/digest.h b/lib/crypto/c_src/digest.h
new file mode 100644
index 0000000000..06852416cf
--- /dev/null
+++ b/lib/crypto/c_src/digest.h
@@ -0,0 +1,40 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_DIGEST_H__
+#define E_DIGEST_H__ 1
+
+#include "common.h"
+
+struct digest_type_t {
+ union {
+ const char* str; /* before init, NULL for end-of-table */
+ ERL_NIF_TERM atom; /* after init, 'false' for end-of-table */
+ }type;
+ union {
+ const EVP_MD* (*funcp)(void); /* before init, NULL if notsup */
+ const EVP_MD* p; /* after init, NULL if notsup */
+ }md;
+};
+
+void init_digest_types(ErlNifEnv* env);
+struct digest_type_t* get_digest_type(ERL_NIF_TERM type);
+
+#endif /* E_DIGEST_H__ */
diff --git a/lib/crypto/c_src/dss.c b/lib/crypto/c_src/dss.c
new file mode 100644
index 0000000000..9bf8eb3ce0
--- /dev/null
+++ b/lib/crypto/c_src/dss.c
@@ -0,0 +1,144 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "dss.h"
+#include "bn.h"
+
+int get_dss_private_key(ErlNifEnv* env, ERL_NIF_TERM key, DSA *dsa)
+{
+ /* key=[P,Q,G,KEY] */
+ ERL_NIF_TERM head, tail;
+ BIGNUM *dsa_p = NULL, *dsa_q = NULL, *dsa_g = NULL;
+ BIGNUM *dummy_pub_key = NULL, *priv_key = NULL;
+
+ if (!enif_get_list_cell(env, key, &head, &tail))
+ goto err;
+ if (!get_bn_from_bin(env, head, &dsa_p))
+ goto err;
+
+ if (!enif_get_list_cell(env, tail, &head, &tail))
+ goto err;
+ if (!get_bn_from_bin(env, head, &dsa_q))
+ goto err;
+
+ if (!enif_get_list_cell(env, tail, &head, &tail))
+ goto err;
+ if (!get_bn_from_bin(env, head, &dsa_g))
+ goto err;
+
+ if (!enif_get_list_cell(env, tail, &head, &tail))
+ goto err;
+ if (!get_bn_from_bin(env, head, &priv_key))
+ goto err;
+
+ if (!enif_is_empty_list(env, tail))
+ goto err;
+
+ /* Note: DSA_set0_key() does not allow setting only the
+ * private key, although DSA_sign() does not use the
+ * public key. Work around this limitation by setting
+ * the public key to a copy of the private key.
+ */
+ if ((dummy_pub_key = BN_dup(priv_key)) == NULL)
+ goto err;
+
+ if (!DSA_set0_pqg(dsa, dsa_p, dsa_q, dsa_g))
+ goto err;
+ /* dsa takes ownership on success */
+ dsa_p = NULL;
+ dsa_q = NULL;
+ dsa_g = NULL;
+
+ if (!DSA_set0_key(dsa, dummy_pub_key, priv_key))
+ goto err;
+ /* dsa takes ownership on success */
+ dummy_pub_key = NULL;
+ priv_key = NULL;
+
+ return 1;
+
+ err:
+ if (dsa_p)
+ BN_free(dsa_p);
+ if (dsa_q)
+ BN_free(dsa_q);
+ if (dsa_g)
+ BN_free(dsa_g);
+ if (priv_key)
+ BN_free(priv_key);
+ if (dummy_pub_key)
+ BN_free(dummy_pub_key);
+ return 0;
+}
+
+int get_dss_public_key(ErlNifEnv* env, ERL_NIF_TERM key, DSA *dsa)
+{
+ /* key=[P, Q, G, Y] */
+ ERL_NIF_TERM head, tail;
+ BIGNUM *dsa_p = NULL, *dsa_q = NULL, *dsa_g = NULL, *dsa_y = NULL;
+
+ if (!enif_get_list_cell(env, key, &head, &tail))
+ goto err;
+ if (!get_bn_from_bin(env, head, &dsa_p))
+ goto err;
+
+ if (!enif_get_list_cell(env, tail, &head, &tail))
+ goto err;
+ if (!get_bn_from_bin(env, head, &dsa_q))
+ goto err;
+
+ if (!enif_get_list_cell(env, tail, &head, &tail))
+ goto err;
+ if (!get_bn_from_bin(env, head, &dsa_g))
+ goto err;
+
+ if (!enif_get_list_cell(env, tail, &head, &tail))
+ goto err;
+ if (!get_bn_from_bin(env, head, &dsa_y))
+ goto err;
+
+ if (!enif_is_empty_list(env,tail))
+ goto err;
+
+ if (!DSA_set0_pqg(dsa, dsa_p, dsa_q, dsa_g))
+ goto err;
+ /* dsa takes ownership on success */
+ dsa_p = NULL;
+ dsa_q = NULL;
+ dsa_g = NULL;
+
+ if (!DSA_set0_key(dsa, dsa_y, NULL))
+ goto err;
+ /* dsa takes ownership on success */
+ dsa_y = NULL;
+
+ return 1;
+
+ err:
+ if (dsa_p)
+ BN_free(dsa_p);
+ if (dsa_q)
+ BN_free(dsa_q);
+ if (dsa_g)
+ BN_free(dsa_g);
+ if (dsa_y)
+ BN_free(dsa_y);
+ return 0;
+}
diff --git a/lib/crypto/c_src/dss.h b/lib/crypto/c_src/dss.h
new file mode 100644
index 0000000000..3275657e98
--- /dev/null
+++ b/lib/crypto/c_src/dss.h
@@ -0,0 +1,29 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_DSS_H__
+#define E_DSS_H__ 1
+
+#include "common.h"
+
+int get_dss_private_key(ErlNifEnv* env, ERL_NIF_TERM key, DSA *dsa);
+int get_dss_public_key(ErlNifEnv* env, ERL_NIF_TERM key, DSA *dsa);
+
+#endif /* E_DSS_H__ */
diff --git a/lib/crypto/c_src/ec.c b/lib/crypto/c_src/ec.c
new file mode 100644
index 0000000000..51a3547694
--- /dev/null
+++ b/lib/crypto/c_src/ec.c
@@ -0,0 +1,414 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "ec.h"
+#include "bn.h"
+
+#ifdef HAVE_EC
+static EC_KEY* ec_key_new(ErlNifEnv* env, ERL_NIF_TERM curve_arg);
+static ERL_NIF_TERM point2term(ErlNifEnv* env,
+ const EC_GROUP *group,
+ const EC_POINT *point,
+ point_conversion_form_t form);
+
+ERL_NIF_TERM make_badarg_maybe(ErlNifEnv* env)
+{
+ ERL_NIF_TERM reason;
+ if (enif_has_pending_exception(env, &reason))
+ return reason; /* dummy return value ignored */
+ else
+ return enif_make_badarg(env);
+}
+
+static EC_KEY* ec_key_new(ErlNifEnv* env, ERL_NIF_TERM curve_arg)
+{
+ EC_KEY *key = NULL;
+ int c_arity = -1;
+ const ERL_NIF_TERM* curve;
+ ErlNifBinary seed;
+ BIGNUM *p = NULL;
+ BIGNUM *a = NULL;
+ BIGNUM *b = NULL;
+ BIGNUM *bn_order = NULL;
+ BIGNUM *cofactor = NULL;
+ EC_GROUP *group = NULL;
+ EC_POINT *point = NULL;
+ int f_arity = -1;
+ const ERL_NIF_TERM *field;
+ int p_arity = -1;
+ const ERL_NIF_TERM *prime;
+ long field_bits;
+
+ /* {Field, Prime, Point, Order, CoFactor} = Curve */
+ if (!enif_get_tuple(env, curve_arg, &c_arity, &curve))
+ goto err;
+ if (c_arity != 5)
+ goto err;
+ if (!get_bn_from_bin(env, curve[3], &bn_order))
+ goto err;
+ if (curve[4] != atom_none) {
+ if (!get_bn_from_bin(env, curve[4], &cofactor))
+ goto err;
+ }
+
+ /* {A, B, Seed} = Prime */
+ if (!enif_get_tuple(env, curve[1], &p_arity, &prime))
+ goto err;
+ if (!get_bn_from_bin(env, prime[0], &a))
+ goto err;
+ if (!get_bn_from_bin(env, prime[1], &b))
+ goto err;
+
+ if (!enif_get_tuple(env, curve[0], &f_arity, &field))
+ goto err;
+
+ if (f_arity == 2 && field[0] == atom_prime_field) {
+ /* {prime_field, Prime} */
+ if (!get_bn_from_bin(env, field[1], &p))
+ goto err;
+ if (BN_is_negative(p))
+ goto err;
+ if (BN_is_zero(p))
+ goto err;
+
+ field_bits = BN_num_bits(p);
+ if (field_bits > OPENSSL_ECC_MAX_FIELD_BITS)
+ goto err;
+
+ /* create the EC_GROUP structure */
+ if ((group = EC_GROUP_new_curve_GFp(p, a, b, NULL)) == NULL)
+ goto err;
+
+ } else if (f_arity == 3 && field[0] == atom_characteristic_two_field) {
+#if defined(OPENSSL_NO_EC2M)
+ enif_raise_exception(env, atom_notsup);
+ goto err;
+#else
+ /* {characteristic_two_field, M, Basis} */
+ int b_arity = -1;
+ const ERL_NIF_TERM* basis;
+
+ if ((p = BN_new()) == NULL)
+ goto err;
+ if (!enif_get_long(env, field[1], &field_bits))
+ goto err;
+ if (field_bits > OPENSSL_ECC_MAX_FIELD_BITS || field_bits > INT_MAX)
+ goto err;
+
+ if (enif_get_tuple(env, field[2], &b_arity, &basis)) {
+ if (b_arity == 2) {
+ unsigned int k1;
+
+ if (basis[0] != atom_tpbasis)
+ goto err;
+ if (!enif_get_uint(env, basis[1], &k1))
+ goto err;
+
+ /* {tpbasis, k} = Basis */
+ if (field_bits <= k1 || k1 == 0 || k1 > INT_MAX)
+ goto err;
+
+ /* create the polynomial */
+ if (!BN_set_bit(p, (int)field_bits))
+ goto err;
+ if (!BN_set_bit(p, (int)k1))
+ goto err;
+ if (!BN_set_bit(p, 0))
+ goto err;
+
+ } else if (b_arity == 4) {
+ unsigned int k1, k2, k3;
+
+ if (basis[0] != atom_ppbasis)
+ goto err;
+ if (!enif_get_uint(env, basis[1], &k1))
+ goto err;
+ if (!enif_get_uint(env, basis[2], &k2))
+ goto err;
+ if (!enif_get_uint(env, basis[3], &k3))
+ goto err;
+
+ /* {ppbasis, k1, k2, k3} = Basis */
+ if (field_bits <= k3 || k3 <= k2 || k2 <= k1 || k1 == 0 || k3 > INT_MAX || k2 > INT_MAX || k1 > INT_MAX)
+ goto err;
+
+ /* create the polynomial */
+ if (!BN_set_bit(p, (int)field_bits))
+ goto err;
+ if (!BN_set_bit(p, (int)k1))
+ goto err;
+ if (!BN_set_bit(p, (int)k2))
+ goto err;
+ if (!BN_set_bit(p, (int)k3))
+ goto err;
+ if (!BN_set_bit(p, 0))
+ goto err;
+
+ } else
+ goto err;
+ } else if (field[2] == atom_onbasis) {
+ /* onbasis = Basis */
+ /* no parameters */
+ goto err;
+
+ } else
+ goto err;
+
+ if ((group = EC_GROUP_new_curve_GF2m(p, a, b, NULL)) == NULL)
+ goto err;
+#endif
+ } else
+ goto err;
+
+ if (enif_inspect_binary(env, prime[2], &seed)) {
+ if (!EC_GROUP_set_seed(group, seed.data, seed.size))
+ goto err;
+ }
+
+ if (!term2point(env, curve[2], group, &point))
+ goto err;
+
+ if (BN_is_negative(bn_order))
+ goto err;
+ if (BN_is_zero(bn_order))
+ goto err;
+ if (BN_num_bits(bn_order) > (int)field_bits + 1)
+ goto err;
+
+ if (!EC_GROUP_set_generator(group, point, bn_order, cofactor))
+ goto err;
+
+ EC_GROUP_set_asn1_flag(group, 0x0);
+
+ if ((key = EC_KEY_new()) == NULL)
+ goto err;
+
+ if (!EC_KEY_set_group(key, group))
+ goto err;
+
+ goto done;
+
+ err:
+ if (key)
+ EC_KEY_free(key);
+ key = NULL;
+
+ done:
+ /* some OpenSSL structures are mem-dup'ed into the key,
+ so we have to free our copies here */
+ if (bn_order)
+ BN_free(bn_order);
+ if (cofactor)
+ BN_free(cofactor);
+ if (a)
+ BN_free(a);
+ if (b)
+ BN_free(b);
+ if (p)
+ BN_free(p);
+ if (group)
+ EC_GROUP_free(group);
+ if (point)
+ EC_POINT_free(point);
+
+ return key;
+}
+
+static ERL_NIF_TERM point2term(ErlNifEnv* env,
+ const EC_GROUP *group,
+ const EC_POINT *point,
+ point_conversion_form_t form)
+{
+ ERL_NIF_TERM ret;
+ size_t dlen;
+ ErlNifBinary bin;
+ int bin_alloc = 0;
+
+ if ((dlen = EC_POINT_point2oct(group, point, form, NULL, 0, NULL)) == 0)
+ return atom_undefined;
+
+ if (!enif_alloc_binary(dlen, &bin))
+ goto err;
+ bin_alloc = 1;
+
+ if (!EC_POINT_point2oct(group, point, form, bin.data, bin.size, NULL))
+ goto err;
+
+ ERL_VALGRIND_MAKE_MEM_DEFINED(bin.data, bin.size);
+
+ ret = enif_make_binary(env, &bin);
+ bin_alloc = 0;
+ goto done;
+
+ err:
+ if (bin_alloc)
+ enif_release_binary(&bin);
+ ret = enif_make_badarg(env);
+
+ done:
+ return ret;
+}
+
+int term2point(ErlNifEnv* env, ERL_NIF_TERM term, EC_GROUP *group, EC_POINT **pptr)
+{
+ ErlNifBinary bin;
+ EC_POINT *point = NULL;
+
+ if (!enif_inspect_binary(env, term, &bin))
+ goto err;
+
+ if ((point = EC_POINT_new(group)) == NULL)
+ goto err;
+
+ /* set the point conversion form */
+ EC_GROUP_set_point_conversion_form(group, (point_conversion_form_t)(bin.data[0] & ~0x01));
+
+ /* extract the ec point */
+ if (!EC_POINT_oct2point(group, point, bin.data, bin.size, NULL))
+ goto err;
+
+ *pptr = point;
+ return 1;
+
+ err:
+ if (point)
+ EC_POINT_free(point);
+ return 0;
+}
+
+int get_ec_key(ErlNifEnv* env,
+ ERL_NIF_TERM curve, ERL_NIF_TERM priv, ERL_NIF_TERM pub,
+ EC_KEY** res)
+{
+ EC_KEY *key = NULL;
+ BIGNUM *priv_key = NULL;
+ EC_POINT *pub_key = NULL;
+ EC_GROUP *group = NULL;
+
+ if (priv != atom_undefined) {
+ if (!get_bn_from_bin(env, priv, &priv_key))
+ goto err;
+ }
+ if (pub != atom_undefined) {
+ if (!enif_is_binary(env, pub))
+ goto err;
+ }
+
+ if ((key = ec_key_new(env, curve)) == NULL)
+ goto err;
+
+ if ((group = EC_GROUP_dup(EC_KEY_get0_group(key))) == NULL)
+ goto err;
+
+ if (term2point(env, pub, group, &pub_key)) {
+ if (!EC_KEY_set_public_key(key, pub_key))
+ goto err;
+ }
+
+ if (priv != atom_undefined && !BN_is_zero(priv_key)) {
+ if (!EC_KEY_set_private_key(key, priv_key))
+ goto err;
+
+ /* calculate public key (if necessary) */
+ if (EC_KEY_get0_public_key(key) == NULL) {
+ /* the public key was not included in the SEC1 private
+ * key => calculate the public key */
+ if ((pub_key = EC_POINT_new(group)) == NULL)
+ goto err;
+ if (!EC_POINT_copy(pub_key, EC_GROUP_get0_generator(group)))
+ goto err;
+ if (!EC_POINT_mul(group, pub_key, priv_key, NULL, NULL, NULL))
+ goto err;
+ if (!EC_KEY_set_public_key(key, pub_key))
+ goto err;
+ }
+ }
+ goto done;
+
+ err:
+ if (key)
+ EC_KEY_free(key);
+ key = NULL;
+
+ done:
+ /* some OpenSSL structures are mem-dup'ed into the key,
+ so we have to free our copies here */
+ if (priv_key)
+ BN_clear_free(priv_key);
+ if (group)
+ EC_GROUP_free(group);
+ if (pub_key)
+ EC_POINT_free(pub_key);
+
+ if (key == NULL)
+ return 0;
+
+ *res = key;
+ return 1;
+}
+
+#endif /* HAVE_EC */
+
+ERL_NIF_TERM ec_key_generate(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+#if defined(HAVE_EC)
+ EC_KEY *key = NULL;
+ const EC_GROUP *group;
+ const EC_POINT *public_key;
+ ERL_NIF_TERM priv_key;
+ ERL_NIF_TERM pub_key;
+ ERL_NIF_TERM ret;
+
+ if (!get_ec_key(env, argv[0], argv[1], atom_undefined, &key))
+ goto bad_arg;
+
+ if (argv[1] == atom_undefined) {
+ if (!EC_KEY_generate_key(key))
+ goto err;
+ }
+
+ group = EC_KEY_get0_group(key);
+ public_key = EC_KEY_get0_public_key(key);
+
+ if (group == NULL || public_key == NULL) {
+ pub_key = atom_undefined;
+
+ } else {
+ pub_key = point2term(env, group, public_key,
+ EC_KEY_get_conv_form(key));
+ }
+
+ priv_key = bn2term(env, EC_KEY_get0_private_key(key));
+ ret = enif_make_tuple2(env, pub_key, priv_key);
+ goto done;
+
+ err:
+ bad_arg:
+ ret = make_badarg_maybe(env);
+
+ done:
+ if (key)
+ EC_KEY_free(key);
+ return ret;
+
+#else
+ return atom_notsup;
+#endif
+}
diff --git a/lib/crypto/c_src/ec.h b/lib/crypto/c_src/ec.h
new file mode 100644
index 0000000000..b7e1cc5a46
--- /dev/null
+++ b/lib/crypto/c_src/ec.h
@@ -0,0 +1,35 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_EC_H__
+#define E_EC_H__ 1
+
+#include "common.h"
+
+#if defined(HAVE_EC)
+int get_ec_key(ErlNifEnv* env, ERL_NIF_TERM curve, ERL_NIF_TERM priv, ERL_NIF_TERM pub,
+ EC_KEY** res);
+int term2point(ErlNifEnv* env, ERL_NIF_TERM term, EC_GROUP *group, EC_POINT **pptr);
+ERL_NIF_TERM make_badarg_maybe(ErlNifEnv* env);
+#endif
+
+ERL_NIF_TERM ec_key_generate(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_EC_H__ */
diff --git a/lib/crypto/c_src/ecdh.c b/lib/crypto/c_src/ecdh.c
new file mode 100644
index 0000000000..9e3f460519
--- /dev/null
+++ b/lib/crypto/c_src/ecdh.c
@@ -0,0 +1,94 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "ecdh.h"
+#include "ec.h"
+
+/*
+ (_OthersPublicKey, _MyPrivateKey)
+ (_OthersPublicKey, _MyEC_Point)
+*/
+ERL_NIF_TERM ecdh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+/* (OtherPublicKey, Curve, My) */
+{
+#if defined(HAVE_EC)
+ ERL_NIF_TERM ret;
+ unsigned char *p;
+ EC_KEY* key = NULL;
+ int degree;
+ size_t field_size;
+ EC_GROUP *group = NULL;
+ const BIGNUM *priv_key;
+ EC_POINT *my_ecpoint = NULL;
+ EC_KEY *other_ecdh = NULL;
+
+ ASSERT(argc == 3);
+
+ if (!get_ec_key(env, argv[1], argv[2], atom_undefined, &key))
+ goto bad_arg;
+ if ((group = EC_GROUP_dup(EC_KEY_get0_group(key))) == NULL)
+ goto bad_arg;
+ priv_key = EC_KEY_get0_private_key(key);
+
+ if (!term2point(env, argv[0], group, &my_ecpoint)) {
+ goto err;
+ }
+
+ if ((other_ecdh = EC_KEY_new()) == NULL)
+ goto err;
+ if (!EC_KEY_set_group(other_ecdh, group))
+ goto err;
+ if (!EC_KEY_set_private_key(other_ecdh, priv_key))
+ goto err;
+
+ if ((degree = EC_GROUP_get_degree(group)) <= 0)
+ goto err;
+
+ field_size = (size_t)degree;
+ if ((p = enif_make_new_binary(env, (field_size+7)/8, &ret)) == NULL)
+ goto err;
+ if (ECDH_compute_key(p, (field_size+7)/8, my_ecpoint, other_ecdh, NULL) < 1)
+ goto err;
+
+ goto done;
+
+ bad_arg:
+ ret = make_badarg_maybe(env);
+ goto done;
+
+ err:
+ ret = enif_make_badarg(env);
+
+ done:
+ if (group)
+ EC_GROUP_free(group);
+ if (my_ecpoint)
+ EC_POINT_free(my_ecpoint);
+ if (other_ecdh)
+ EC_KEY_free(other_ecdh);
+ if (key)
+ EC_KEY_free(key);
+
+ return ret;
+
+#else
+ return atom_notsup;
+#endif
+}
diff --git a/lib/crypto/c_src/ecdh.h b/lib/crypto/c_src/ecdh.h
new file mode 100644
index 0000000000..5ed331e676
--- /dev/null
+++ b/lib/crypto/c_src/ecdh.h
@@ -0,0 +1,28 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_ECDH_H__
+#define E_ECDH_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM ecdh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_ECDH_H__ */
diff --git a/lib/crypto/c_src/eddsa.c b/lib/crypto/c_src/eddsa.c
new file mode 100644
index 0000000000..0c89f9f6db
--- /dev/null
+++ b/lib/crypto/c_src/eddsa.c
@@ -0,0 +1,63 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "eddsa.h"
+
+#ifdef HAVE_EDDSA
+int get_eddsa_key(ErlNifEnv* env, int public, ERL_NIF_TERM key, EVP_PKEY **pkey)
+{
+ /* key=[K] */
+ EVP_PKEY *result;
+ ERL_NIF_TERM head, tail, tail2, algo;
+ ErlNifBinary bin;
+ int type;
+
+ if (!enif_get_list_cell(env, key, &head, &tail))
+ goto err;
+ if (!enif_inspect_binary(env, head, &bin))
+ goto err;
+ if (!enif_get_list_cell(env, tail, &algo, &tail2))
+ goto err;
+ if (!enif_is_empty_list(env, tail2))
+ goto err;
+
+ if (algo == atom_ed25519) {
+ type = EVP_PKEY_ED25519;
+ } else if (algo == atom_ed448) {
+ type = EVP_PKEY_ED448;
+ } else {
+ goto err;
+ }
+
+ if (public)
+ result = EVP_PKEY_new_raw_public_key(type, NULL, bin.data, bin.size);
+ else
+ result = EVP_PKEY_new_raw_private_key(type, NULL, bin.data, bin.size);
+
+ if (result == NULL)
+ goto err;
+
+ *pkey = result;
+ return 1;
+
+ err:
+ return 0;
+}
+#endif
diff --git a/lib/crypto/c_src/eddsa.h b/lib/crypto/c_src/eddsa.h
new file mode 100644
index 0000000000..4b30247cab
--- /dev/null
+++ b/lib/crypto/c_src/eddsa.h
@@ -0,0 +1,30 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_EDDSA_H__
+#define E_EDDSA_H__ 1
+
+#include "common.h"
+
+#ifdef HAVE_EDDSA
+int get_eddsa_key(ErlNifEnv* env, int public, ERL_NIF_TERM key, EVP_PKEY **pkey);
+#endif
+
+#endif /* E_EDDSA_H__ */
diff --git a/lib/crypto/c_src/engine.c b/lib/crypto/c_src/engine.c
new file mode 100644
index 0000000000..7ffbb9e70d
--- /dev/null
+++ b/lib/crypto/c_src/engine.c
@@ -0,0 +1,842 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "engine.h"
+
+#ifdef HAS_ENGINE_SUPPORT
+struct engine_ctx {
+ ENGINE *engine;
+ char *id;
+};
+
+#define ERROR_Term(Env, ReasonTerm) enif_make_tuple2((Env), atom_error, (ReasonTerm))
+#define ERROR_Atom(Env, ReasonString) ERROR_Term((Env), enif_make_atom((Env),(ReasonString)))
+
+static ErlNifResourceType* engine_ctx_rtype;
+
+static int get_engine_load_cmd_list(ErlNifEnv* env, const ERL_NIF_TERM term, char **cmds, int i);
+static int zero_terminate(ErlNifBinary bin, char **buf);
+
+static void engine_ctx_dtor(ErlNifEnv* env, struct engine_ctx* ctx) {
+ if (ctx == NULL)
+ return;
+
+ PRINTF_ERR0("engine_ctx_dtor");
+ if(ctx->id) {
+ PRINTF_ERR1(" non empty ctx->id=%s", ctx->id);
+ enif_free(ctx->id);
+ } else
+ PRINTF_ERR0(" empty ctx->id=NULL");
+}
+
+int get_engine_and_key_id(ErlNifEnv *env, ERL_NIF_TERM key, char ** id, ENGINE **e)
+{
+ ERL_NIF_TERM engine_res, key_id_term;
+ struct engine_ctx *ctx;
+ ErlNifBinary key_id_bin;
+
+ if (!enif_get_map_value(env, key, atom_engine, &engine_res))
+ goto err;
+ if (!enif_get_resource(env, engine_res, engine_ctx_rtype, (void**)&ctx))
+ goto err;
+ if (!enif_get_map_value(env, key, atom_key_id, &key_id_term))
+ goto err;
+ if (!enif_inspect_binary(env, key_id_term, &key_id_bin))
+ goto err;
+
+ *e = ctx->engine;
+ return zero_terminate(key_id_bin, id);
+
+ err:
+ return 0;
+}
+
+char *get_key_password(ErlNifEnv *env, ERL_NIF_TERM key) {
+ ERL_NIF_TERM tmp_term;
+ ErlNifBinary pwd_bin;
+ char *pwd = NULL;
+
+ if (!enif_get_map_value(env, key, atom_password, &tmp_term))
+ goto err;
+ if (!enif_inspect_binary(env, tmp_term, &pwd_bin))
+ goto err;
+ if (!zero_terminate(pwd_bin, &pwd))
+ goto err;
+
+ return pwd;
+
+ err:
+ return NULL;
+}
+
+static int zero_terminate(ErlNifBinary bin, char **buf) {
+ if ((*buf = enif_alloc(bin.size + 1)) == NULL)
+ goto err;
+
+ memcpy(*buf, bin.data, bin.size);
+ *(*buf + bin.size) = 0;
+
+ return 1;
+
+ err:
+ return 0;
+}
+#endif /* HAS_ENGINE_SUPPORT */
+
+int init_engine_ctx(ErlNifEnv *env) {
+#ifdef HAS_ENGINE_SUPPORT
+ engine_ctx_rtype = enif_open_resource_type(env, NULL, "ENGINE_CTX",
+ (ErlNifResourceDtor*) engine_ctx_dtor,
+ ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER,
+ NULL);
+ if (engine_ctx_rtype == NULL)
+ goto err;
+#endif
+
+ return 1;
+
+ err:
+ PRINTF_ERR0("CRYPTO: Could not open resource type 'ENGINE_CTX'");
+ return 0;
+}
+
+ERL_NIF_TERM engine_by_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (EngineId) */
+#ifdef HAS_ENGINE_SUPPORT
+ ERL_NIF_TERM ret, result;
+ ErlNifBinary engine_id_bin;
+ char *engine_id = NULL;
+ ENGINE *engine;
+ struct engine_ctx *ctx = NULL;
+
+ // Get Engine Id
+ ASSERT(argc == 1);
+
+ if (!enif_inspect_binary(env, argv[0], &engine_id_bin))
+ goto bad_arg;
+
+ if ((engine_id = enif_alloc(engine_id_bin.size+1)) == NULL)
+ goto err;
+ (void) memcpy(engine_id, engine_id_bin.data, engine_id_bin.size);
+ engine_id[engine_id_bin.size] = '\0';
+
+ if ((engine = ENGINE_by_id(engine_id)) == NULL) {
+ PRINTF_ERR0("engine_by_id_nif Leaved: {error, bad_engine_id}");
+ ret = ERROR_Atom(env, "bad_engine_id");
+ goto done;
+ }
+
+ if ((ctx = enif_alloc_resource(engine_ctx_rtype, sizeof(struct engine_ctx))) == NULL)
+ goto err;
+ ctx->engine = engine;
+ ctx->id = engine_id;
+ /* ctx now owns engine_id */
+ engine_id = NULL;
+
+ result = enif_make_resource(env, ctx);
+ ret = enif_make_tuple2(env, atom_ok, result);
+ goto done;
+
+ bad_arg:
+ err:
+ ret = enif_make_badarg(env);
+
+ done:
+ if (engine_id)
+ enif_free(engine_id);
+ if (ctx)
+ enif_release_resource(ctx);
+ return ret;
+
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM engine_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Engine) */
+#ifdef HAS_ENGINE_SUPPORT
+ struct engine_ctx *ctx;
+
+ // Get Engine
+ ASSERT(argc == 1);
+
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx))
+ goto bad_arg;
+
+ if (!ENGINE_init(ctx->engine))
+ return ERROR_Atom(env, "engine_init_failed");
+
+ return atom_ok;
+
+ bad_arg:
+ return enif_make_badarg(env);
+
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM engine_free_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Engine) */
+#ifdef HAS_ENGINE_SUPPORT
+ struct engine_ctx *ctx;
+
+ // Get Engine
+ ASSERT(argc == 1);
+
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx))
+ goto bad_arg;
+
+ if (!ENGINE_free(ctx->engine))
+ goto err;
+ return atom_ok;
+
+ bad_arg:
+ err:
+ return enif_make_badarg(env);
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM engine_finish_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Engine) */
+#ifdef HAS_ENGINE_SUPPORT
+ struct engine_ctx *ctx;
+
+ // Get Engine
+ ASSERT(argc == 1);
+
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx))
+ goto bad_arg;
+
+ if (!ENGINE_finish(ctx->engine))
+ goto err;
+ return atom_ok;
+
+ bad_arg:
+ err:
+ return enif_make_badarg(env);
+
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM engine_load_dynamic_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* () */
+#ifdef HAS_ENGINE_SUPPORT
+ ASSERT(argc == 0);
+
+ ENGINE_load_dynamic();
+ return atom_ok;
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM engine_ctrl_cmd_strings_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Engine, Commands, Optional) */
+#ifdef HAS_ENGINE_SUPPORT
+ ERL_NIF_TERM ret;
+ unsigned int cmds_len = 0;
+ char **cmds = NULL;
+ struct engine_ctx *ctx;
+ unsigned int i;
+ int optional = 0;
+ int cmds_loaded = 0;
+
+ // Get Engine
+ ASSERT(argc == 3);
+
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx))
+ goto bad_arg;
+
+ PRINTF_ERR1("Engine Id: %s\r\n", ENGINE_get_id(ctx->engine));
+ // Get Command List
+ if (!enif_get_list_length(env, argv[1], &cmds_len))
+ goto bad_arg;
+
+ if (cmds_len > (UINT_MAX / 2) - 1)
+ goto err;
+ cmds_len *= 2; // Key-Value list from erlang
+
+ if ((size_t)cmds_len + 1 > SIZE_MAX / sizeof(char*))
+ goto err;
+ if ((cmds = enif_alloc((cmds_len + 1) * sizeof(char*))) == NULL)
+ goto err;
+ if (get_engine_load_cmd_list(env, argv[1], cmds, 0))
+ goto err;
+ cmds_loaded = 1;
+ if (!enif_get_int(env, argv[2], &optional))
+ goto err;
+
+ for(i = 0; i < cmds_len; i+=2) {
+ PRINTF_ERR2("Cmd: %s:%s\r\n",
+ cmds[i] ? cmds[i] : "(NULL)",
+ cmds[i+1] ? cmds[i+1] : "(NULL)");
+ if(!ENGINE_ctrl_cmd_string(ctx->engine, cmds[i], cmds[i+1], optional)) {
+ PRINTF_ERR2("Command failed: %s:%s\r\n",
+ cmds[i] ? cmds[i] : "(NULL)",
+ cmds[i+1] ? cmds[i+1] : "(NULL)");
+ goto cmd_failed;
+ }
+ }
+ ret = atom_ok;
+ goto done;
+
+ bad_arg:
+ err:
+ ret = enif_make_badarg(env);
+ goto done;
+
+ cmd_failed:
+ ret = ERROR_Atom(env, "ctrl_cmd_failed");
+
+ done:
+ if (cmds_loaded) {
+ for (i = 0; cmds != NULL && cmds[i] != NULL; i++)
+ enif_free(cmds[i]);
+ }
+
+ if (cmds != NULL)
+ enif_free(cmds);
+
+ return ret;
+
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM engine_add_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Engine) */
+#ifdef HAS_ENGINE_SUPPORT
+ struct engine_ctx *ctx;
+
+ // Get Engine
+ ASSERT(argc == 1);
+
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx))
+ goto bad_arg;
+
+ if (!ENGINE_add(ctx->engine))
+ goto failed;
+
+ return atom_ok;
+
+ bad_arg:
+ return enif_make_badarg(env);
+
+ failed:
+ return ERROR_Atom(env, "add_engine_failed");
+
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM engine_remove_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Engine) */
+#ifdef HAS_ENGINE_SUPPORT
+ struct engine_ctx *ctx;
+
+ // Get Engine
+ ASSERT(argc == 1);
+
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx))
+ goto bad_arg;
+
+ if (!ENGINE_remove(ctx->engine))
+ goto failed;
+
+ return atom_ok;
+
+ bad_arg:
+ return enif_make_badarg(env);
+
+ failed:
+ return ERROR_Atom(env, "remove_engine_failed");
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM engine_register_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Engine, EngineMethod) */
+#ifdef HAS_ENGINE_SUPPORT
+ struct engine_ctx *ctx;
+ unsigned int method;
+
+ // Get Engine
+ ASSERT(argc == 2);
+
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx))
+ goto bad_arg;
+ if (!enif_get_uint(env, argv[1], &method))
+ goto bad_arg;
+
+ switch(method)
+ {
+#ifdef ENGINE_METHOD_RSA
+ case ENGINE_METHOD_RSA:
+ if (!ENGINE_register_RSA(ctx->engine))
+ goto failed;
+ break;
+#endif
+#ifdef ENGINE_METHOD_DSA
+ case ENGINE_METHOD_DSA:
+ if (!ENGINE_register_DSA(ctx->engine))
+ goto failed;
+ break;
+#endif
+#ifdef ENGINE_METHOD_DH
+ case ENGINE_METHOD_DH:
+ if (!ENGINE_register_DH(ctx->engine))
+ goto failed;
+ break;
+#endif
+#ifdef ENGINE_METHOD_RAND
+ case ENGINE_METHOD_RAND:
+ if (!ENGINE_register_RAND(ctx->engine))
+ goto failed;
+ break;
+#endif
+#ifdef ENGINE_METHOD_ECDH
+ case ENGINE_METHOD_ECDH:
+ if (!ENGINE_register_ECDH(ctx->engine))
+ goto failed;
+ break;
+#endif
+#ifdef ENGINE_METHOD_ECDSA
+ case ENGINE_METHOD_ECDSA:
+ if (!ENGINE_register_ECDSA(ctx->engine))
+ goto failed;
+ break;
+#endif
+#ifdef ENGINE_METHOD_STORE
+ case ENGINE_METHOD_STORE:
+ if (!ENGINE_register_STORE(ctx->engine))
+ goto failed;
+ break;
+#endif
+#ifdef ENGINE_METHOD_CIPHERS
+ case ENGINE_METHOD_CIPHERS:
+ if (!ENGINE_register_ciphers(ctx->engine))
+ goto failed;
+ break;
+#endif
+#ifdef ENGINE_METHOD_DIGESTS
+ case ENGINE_METHOD_DIGESTS:
+ if (!ENGINE_register_digests(ctx->engine))
+ goto failed;
+ break;
+#endif
+#ifdef ENGINE_METHOD_PKEY_METHS
+ case ENGINE_METHOD_PKEY_METHS:
+ if (!ENGINE_register_pkey_meths(ctx->engine))
+ goto failed;
+ break;
+#endif
+#ifdef ENGINE_METHOD_PKEY_ASN1_METHS
+ case ENGINE_METHOD_PKEY_ASN1_METHS:
+ if (!ENGINE_register_pkey_asn1_meths(ctx->engine))
+ goto failed;
+ break;
+#endif
+#ifdef ENGINE_METHOD_EC
+ case ENGINE_METHOD_EC:
+ if (!ENGINE_register_EC(ctx->engine))
+ goto failed;
+ break;
+#endif
+ default:
+ return ERROR_Atom(env, "engine_method_not_supported");
+ }
+
+ return atom_ok;
+
+ bad_arg:
+ return enif_make_badarg(env);
+
+ failed:
+ return ERROR_Atom(env, "register_engine_failed");
+
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM engine_unregister_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Engine, EngineMethod) */
+#ifdef HAS_ENGINE_SUPPORT
+ struct engine_ctx *ctx;
+ unsigned int method;
+
+ // Get Engine
+ ASSERT(argc == 2);
+
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx))
+ goto bad_arg;
+ if (!enif_get_uint(env, argv[1], &method))
+ goto bad_arg;
+
+ switch(method)
+ {
+#ifdef ENGINE_METHOD_RSA
+ case ENGINE_METHOD_RSA:
+ ENGINE_unregister_RSA(ctx->engine);
+ break;
+#endif
+#ifdef ENGINE_METHOD_DSA
+ case ENGINE_METHOD_DSA:
+ ENGINE_unregister_DSA(ctx->engine);
+ break;
+#endif
+#ifdef ENGINE_METHOD_DH
+ case ENGINE_METHOD_DH:
+ ENGINE_unregister_DH(ctx->engine);
+ break;
+#endif
+#ifdef ENGINE_METHOD_RAND
+ case ENGINE_METHOD_RAND:
+ ENGINE_unregister_RAND(ctx->engine);
+ break;
+#endif
+#ifdef ENGINE_METHOD_ECDH
+ case ENGINE_METHOD_ECDH:
+ ENGINE_unregister_ECDH(ctx->engine);
+ break;
+#endif
+#ifdef ENGINE_METHOD_ECDSA
+ case ENGINE_METHOD_ECDSA:
+ ENGINE_unregister_ECDSA(ctx->engine);
+ break;
+#endif
+#ifdef ENGINE_METHOD_STORE
+ case ENGINE_METHOD_STORE:
+ ENGINE_unregister_STORE(ctx->engine);
+ break;
+#endif
+#ifdef ENGINE_METHOD_CIPHERS
+ case ENGINE_METHOD_CIPHERS:
+ ENGINE_unregister_ciphers(ctx->engine);
+ break;
+#endif
+#ifdef ENGINE_METHOD_DIGESTS
+ case ENGINE_METHOD_DIGESTS:
+ ENGINE_unregister_digests(ctx->engine);
+ break;
+#endif
+#ifdef ENGINE_METHOD_PKEY_METHS
+ case ENGINE_METHOD_PKEY_METHS:
+ ENGINE_unregister_pkey_meths(ctx->engine);
+ break;
+#endif
+#ifdef ENGINE_METHOD_PKEY_ASN1_METHS
+ case ENGINE_METHOD_PKEY_ASN1_METHS:
+ ENGINE_unregister_pkey_asn1_meths(ctx->engine);
+ break;
+#endif
+#ifdef ENGINE_METHOD_EC
+ case ENGINE_METHOD_EC:
+ ENGINE_unregister_EC(ctx->engine);
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return atom_ok;
+
+ bad_arg:
+ return enif_make_badarg(env);
+
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM engine_get_first_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* () */
+#ifdef HAS_ENGINE_SUPPORT
+ ERL_NIF_TERM ret, result;
+ ENGINE *engine;
+ ErlNifBinary engine_bin;
+ struct engine_ctx *ctx = NULL;
+
+ ASSERT(argc == 0);
+
+ if ((engine = ENGINE_get_first()) == NULL) {
+ if (!enif_alloc_binary(0, &engine_bin))
+ goto err;
+ engine_bin.size = 0;
+ return enif_make_tuple2(env, atom_ok, enif_make_binary(env, &engine_bin));
+ }
+
+ if ((ctx = enif_alloc_resource(engine_ctx_rtype, sizeof(struct engine_ctx))) == NULL)
+ goto err;
+ ctx->engine = engine;
+ ctx->id = NULL;
+
+ result = enif_make_resource(env, ctx);
+ ret = enif_make_tuple2(env, atom_ok, result);
+ goto done;
+
+ err:
+ ret = enif_make_badarg(env);
+
+ done:
+ if (ctx)
+ enif_release_resource(ctx);
+ return ret;
+
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM engine_get_next_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Engine) */
+#ifdef HAS_ENGINE_SUPPORT
+ ERL_NIF_TERM ret, result;
+ ENGINE *engine;
+ ErlNifBinary engine_bin;
+ struct engine_ctx *ctx, *next_ctx = NULL;
+
+ // Get Engine
+ ASSERT(argc == 1);
+
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx))
+ goto bad_arg;
+
+ if ((engine = ENGINE_get_next(ctx->engine)) == NULL) {
+ if (!enif_alloc_binary(0, &engine_bin))
+ goto err;
+ engine_bin.size = 0;
+ return enif_make_tuple2(env, atom_ok, enif_make_binary(env, &engine_bin));
+ }
+
+ if ((next_ctx = enif_alloc_resource(engine_ctx_rtype, sizeof(struct engine_ctx))) == NULL)
+ goto err;
+ next_ctx->engine = engine;
+ next_ctx->id = NULL;
+
+ result = enif_make_resource(env, next_ctx);
+ ret = enif_make_tuple2(env, atom_ok, result);
+ goto done;
+
+ bad_arg:
+ err:
+ ret = enif_make_badarg(env);
+
+ done:
+ if (next_ctx)
+ enif_release_resource(next_ctx);
+ return ret;
+
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM engine_get_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Engine) */
+#ifdef HAS_ENGINE_SUPPORT
+ ErlNifBinary engine_id_bin;
+ const char *engine_id;
+ size_t size;
+ struct engine_ctx *ctx = NULL;
+
+ // Get Engine
+ ASSERT(argc == 1);
+
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx))
+ goto bad_arg;
+
+ if ((engine_id = ENGINE_get_id(ctx->engine)) == NULL) {
+ if (!enif_alloc_binary(0, &engine_id_bin))
+ goto err;
+ engine_id_bin.size = 0;
+ return enif_make_binary(env, &engine_id_bin);
+ }
+
+ size = strlen(engine_id);
+ if (!enif_alloc_binary(size, &engine_id_bin))
+ goto err;
+ engine_id_bin.size = size;
+ memcpy(engine_id_bin.data, engine_id, size);
+
+ return enif_make_binary(env, &engine_id_bin);
+
+ bad_arg:
+ err:
+ return enif_make_badarg(env);
+
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM engine_get_name_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Engine) */
+#ifdef HAS_ENGINE_SUPPORT
+ ErlNifBinary engine_name_bin;
+ const char *engine_name;
+ size_t size;
+ struct engine_ctx *ctx;
+
+ // Get Engine
+ ASSERT(argc == 1);
+
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx))
+ goto bad_arg;
+
+ if ((engine_name = ENGINE_get_name(ctx->engine)) == NULL) {
+ if (!enif_alloc_binary(0, &engine_name_bin))
+ goto err;
+ engine_name_bin.size = 0;
+ return enif_make_binary(env, &engine_name_bin);
+ }
+
+ size = strlen(engine_name);
+ if (!enif_alloc_binary(size, &engine_name_bin))
+ goto err;
+ engine_name_bin.size = size;
+ memcpy(engine_name_bin.data, engine_name, size);
+
+ return enif_make_binary(env, &engine_name_bin);
+
+ bad_arg:
+ err:
+ return enif_make_badarg(env);
+
+#else
+ return atom_notsup;
+#endif
+}
+
+#ifdef HAS_ENGINE_SUPPORT
+static int get_engine_load_cmd_list(ErlNifEnv* env, const ERL_NIF_TERM term, char **cmds, int i)
+{
+ ERL_NIF_TERM head, tail;
+ const ERL_NIF_TERM *tmp_tuple;
+ ErlNifBinary tmpbin;
+ int arity;
+ char *tuple1 = NULL, *tuple2 = NULL;
+
+ if (enif_is_empty_list(env, term)) {
+ cmds[i] = NULL;
+ return 0;
+ }
+
+ if (!enif_get_list_cell(env, term, &head, &tail))
+ goto err;
+ if (!enif_get_tuple(env, head, &arity, &tmp_tuple))
+ goto err;
+ if (arity != 2)
+ goto err;
+ if (!enif_inspect_binary(env, tmp_tuple[0], &tmpbin))
+ goto err;
+
+ if ((tuple1 = enif_alloc(tmpbin.size + 1)) == NULL)
+ goto err;
+
+ (void) memcpy(tuple1, tmpbin.data, tmpbin.size);
+ tuple1[tmpbin.size] = '\0';
+ cmds[i] = tuple1;
+ i++;
+
+ if (!enif_inspect_binary(env, tmp_tuple[1], &tmpbin))
+ goto err;
+
+ if (tmpbin.size == 0) {
+ cmds[i] = NULL;
+ } else {
+ if ((tuple2 = enif_alloc(tmpbin.size + 1)) == NULL)
+ goto err;
+ (void) memcpy(tuple2, tmpbin.data, tmpbin.size);
+ tuple2[tmpbin.size] = '\0';
+ cmds[i] = tuple2;
+ }
+ i++;
+ return get_engine_load_cmd_list(env, tail, cmds, i);
+
+ err:
+ if (tuple1 != NULL) {
+ i--;
+ enif_free(tuple1);
+ }
+ cmds[i] = NULL;
+ return -1;
+}
+#endif /* HAS_ENGINE_SUPPORT */
+
+ERL_NIF_TERM engine_get_all_methods_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* () */
+#ifdef HAS_ENGINE_SUPPORT
+ ERL_NIF_TERM method_array[12];
+ unsigned int i = 0;
+
+ ASSERT(argc == 0);
+
+#ifdef ENGINE_METHOD_RSA
+ method_array[i++] = atom_engine_method_rsa;
+#endif
+#ifdef ENGINE_METHOD_DSA
+ method_array[i++] = atom_engine_method_dsa;
+#endif
+#ifdef ENGINE_METHOD_DH
+ method_array[i++] = atom_engine_method_dh;
+#endif
+#ifdef ENGINE_METHOD_RAND
+ method_array[i++] = atom_engine_method_rand;
+#endif
+#ifdef ENGINE_METHOD_ECDH
+ method_array[i++] = atom_engine_method_ecdh;
+#endif
+#ifdef ENGINE_METHOD_ECDSA
+ method_array[i++] = atom_engine_method_ecdsa;
+#endif
+#ifdef ENGINE_METHOD_STORE
+ method_array[i++] = atom_engine_method_store;
+#endif
+#ifdef ENGINE_METHOD_CIPHERS
+ method_array[i++] = atom_engine_method_ciphers;
+#endif
+#ifdef ENGINE_METHOD_DIGESTS
+ method_array[i++] = atom_engine_method_digests;
+#endif
+#ifdef ENGINE_METHOD_PKEY_METHS
+ method_array[i++] = atom_engine_method_pkey_meths;
+#endif
+#ifdef ENGINE_METHOD_PKEY_ASN1_METHS
+ method_array[i++] = atom_engine_method_pkey_asn1_meths;
+#endif
+#ifdef ENGINE_METHOD_EC
+ method_array[i++] = atom_engine_method_ec;
+#endif
+
+ return enif_make_list_from_array(env, method_array, i);
+#else
+ return atom_notsup;
+#endif
+}
diff --git a/lib/crypto/c_src/engine.h b/lib/crypto/c_src/engine.h
new file mode 100644
index 0000000000..4a2eed9672
--- /dev/null
+++ b/lib/crypto/c_src/engine.h
@@ -0,0 +1,49 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_ENGINE_H__
+#define E_ENGINE_H__ 1
+
+#include "common.h"
+
+#ifdef HAS_ENGINE_SUPPORT
+int get_engine_and_key_id(ErlNifEnv *env, ERL_NIF_TERM key, char ** id, ENGINE **e);
+char *get_key_password(ErlNifEnv *env, ERL_NIF_TERM key);
+#endif /* HAS_ENGINE_SUPPORT */
+
+int init_engine_ctx(ErlNifEnv *env);
+
+ERL_NIF_TERM engine_by_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_finish_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_free_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_load_dynamic_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_ctrl_cmd_strings_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_register_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_unregister_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_add_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_remove_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_get_first_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_get_next_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_get_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_get_name_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_get_all_methods_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_ENGINE_H__ */
diff --git a/lib/crypto/c_src/evp.c b/lib/crypto/c_src/evp.c
new file mode 100644
index 0000000000..3bf66bfffe
--- /dev/null
+++ b/lib/crypto/c_src/evp.c
@@ -0,0 +1,164 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "evp.h"
+
+ERL_NIF_TERM evp_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+ /* (Curve, PeerBin, MyBin) */
+{
+#ifdef HAVE_ED_CURVE_DH
+ ERL_NIF_TERM ret;
+ int type;
+ EVP_PKEY_CTX *ctx = NULL;
+ ErlNifBinary peer_bin, my_bin, key_bin;
+ EVP_PKEY *peer_key = NULL, *my_key = NULL;
+ size_t max_size;
+ int key_bin_alloc = 0;
+
+ ASSERT(argc == 3);
+
+ if (argv[0] == atom_x25519)
+ type = EVP_PKEY_X25519;
+ else if (argv[0] == atom_x448)
+ type = EVP_PKEY_X448;
+ else
+ goto bad_arg;
+
+ if (!enif_inspect_binary(env, argv[1], &peer_bin))
+ goto bad_arg;
+ if (!enif_inspect_binary(env, argv[2], &my_bin))
+ goto bad_arg;
+
+ if ((my_key = EVP_PKEY_new_raw_private_key(type, NULL, my_bin.data, my_bin.size)) == NULL)
+ goto err;
+ if ((ctx = EVP_PKEY_CTX_new(my_key, NULL)) == NULL)
+ goto err;
+
+ if (EVP_PKEY_derive_init(ctx) != 1)
+ goto err;
+
+ if ((peer_key = EVP_PKEY_new_raw_public_key(type, NULL, peer_bin.data, peer_bin.size)) == NULL)
+ goto err;
+ if (EVP_PKEY_derive_set_peer(ctx, peer_key) != 1)
+ goto err;
+
+ if (EVP_PKEY_derive(ctx, NULL, &max_size) != 1)
+ goto err;
+
+ if (!enif_alloc_binary(max_size, &key_bin))
+ goto err;
+ key_bin_alloc = 1;
+ if (EVP_PKEY_derive(ctx, key_bin.data, &key_bin.size) != 1)
+ goto err;
+
+ if (key_bin.size < max_size) {
+ if (!enif_realloc_binary(&key_bin, (size_t)key_bin.size))
+ goto err;
+ }
+
+ ret = enif_make_binary(env, &key_bin);
+ key_bin_alloc = 0;
+ goto done;
+
+ bad_arg:
+ err:
+ if (key_bin_alloc)
+ enif_release_binary(&key_bin);
+ ret = enif_make_badarg(env);
+
+ done:
+ if (my_key)
+ EVP_PKEY_free(my_key);
+ if (peer_key)
+ EVP_PKEY_free(peer_key);
+ if (ctx)
+ EVP_PKEY_CTX_free(ctx);
+
+ return ret;
+
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM evp_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+/* (Curve) */
+{
+#ifdef HAVE_ED_CURVE_DH
+ int type;
+ EVP_PKEY_CTX *ctx = NULL;
+ EVP_PKEY *pkey = NULL;
+ ERL_NIF_TERM ret_pub, ret_prv, ret;
+ size_t key_len;
+ unsigned char *out_pub = NULL, *out_priv = NULL;
+
+ ASSERT(argc == 1);
+
+ if (argv[0] == atom_x25519)
+ type = EVP_PKEY_X25519;
+ else if (argv[0] == atom_x448)
+ type = EVP_PKEY_X448;
+ else
+ goto bad_arg;
+
+ if ((ctx = EVP_PKEY_CTX_new_id(type, NULL)) == NULL)
+ goto bad_arg;
+
+ if (EVP_PKEY_keygen_init(ctx) != 1)
+ goto err;
+ if (EVP_PKEY_keygen(ctx, &pkey) != 1)
+ goto err;
+
+ if (EVP_PKEY_get_raw_public_key(pkey, NULL, &key_len) != 1)
+ goto err;
+ if ((out_pub = enif_make_new_binary(env, key_len, &ret_pub)) == NULL)
+ goto err;
+ if (EVP_PKEY_get_raw_public_key(pkey, out_pub, &key_len) != 1)
+ goto err;
+
+ if (EVP_PKEY_get_raw_private_key(pkey, NULL, &key_len) != 1)
+ goto err;
+ if ((out_priv = enif_make_new_binary(env, key_len, &ret_prv)) == NULL)
+ goto err;
+ if (EVP_PKEY_get_raw_private_key(pkey, out_priv, &key_len) != 1)
+ goto err;
+
+ ret = enif_make_tuple2(env, ret_pub, ret_prv);
+ goto done;
+
+ bad_arg:
+ ret = enif_make_badarg(env);
+ goto done;
+
+ err:
+ ret = atom_error;
+
+ done:
+ if (pkey)
+ EVP_PKEY_free(pkey);
+ if (ctx)
+ EVP_PKEY_CTX_free(ctx);
+ return ret;
+
+#else
+ return atom_notsup;
+#endif
+}
+
diff --git a/lib/crypto/c_src/evp.h b/lib/crypto/c_src/evp.h
new file mode 100644
index 0000000000..d767260262
--- /dev/null
+++ b/lib/crypto/c_src/evp.h
@@ -0,0 +1,29 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_EVP_H__
+#define E_EVP_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM evp_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM evp_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_EVP_H__ */
diff --git a/lib/crypto/c_src/evp_compat.h b/lib/crypto/c_src/evp_compat.h
new file mode 100644
index 0000000000..dc94a61d8e
--- /dev/null
+++ b/lib/crypto/c_src/evp_compat.h
@@ -0,0 +1,210 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_EVP_COMPAT_H__
+#define E_EVP_COMPAT_H__ 1
+
+/*
+ * In OpenSSL 1.1.0, most structs are opaque. That means that
+ * the structs cannot be allocated as automatic variables on the
+ * C stack (because the size is unknown) and that it is necessary
+ * to use access functions.
+ *
+ * For backward compatibility to previous versions of OpenSSL, define
+ * on our versions of the new functions defined in 1.1.0 here, so that
+ * we don't have to sprinkle ifdefs throughout the code.
+ */
+
+static INLINE HMAC_CTX *HMAC_CTX_new(void);
+static INLINE void HMAC_CTX_free(HMAC_CTX *ctx);
+
+static INLINE HMAC_CTX *HMAC_CTX_new()
+{
+ HMAC_CTX *ctx;
+
+ if ((ctx = CRYPTO_malloc(sizeof(HMAC_CTX), __FILE__, __LINE__)) == NULL)
+ return NULL;
+
+ HMAC_CTX_init(ctx);
+ return ctx;
+}
+
+static INLINE void HMAC_CTX_free(HMAC_CTX *ctx)
+{
+ if (ctx == NULL)
+ return;
+
+ HMAC_CTX_cleanup(ctx);
+ CRYPTO_free(ctx);
+}
+
+/* Renamed in 1.1.0 */
+#define EVP_MD_CTX_new() EVP_MD_CTX_create()
+#define EVP_MD_CTX_free(ctx) EVP_MD_CTX_destroy((ctx))
+
+static INLINE void *BN_GENCB_get_arg(BN_GENCB *cb);
+
+static INLINE void *BN_GENCB_get_arg(BN_GENCB *cb)
+{
+ return cb->arg;
+}
+
+static INLINE int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d);
+static INLINE void RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d);
+static INLINE int RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q);
+static INLINE void RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q);
+static INLINE int RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp);
+static INLINE void RSA_get0_crt_params(const RSA *r, const BIGNUM **dmp1, const BIGNUM **dmq1, const BIGNUM **iqmp);
+
+static INLINE int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d)
+{
+ r->n = n;
+ r->e = e;
+ r->d = d;
+ return 1;
+}
+
+static INLINE void RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d)
+{
+ *n = r->n;
+ *e = r->e;
+ *d = r->d;
+}
+
+static INLINE int RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q)
+{
+ r->p = p;
+ r->q = q;
+ return 1;
+}
+
+static INLINE void RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q)
+{
+ *p = r->p;
+ *q = r->q;
+}
+
+static INLINE int RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp)
+{
+ r->dmp1 = dmp1;
+ r->dmq1 = dmq1;
+ r->iqmp = iqmp;
+ return 1;
+}
+
+static INLINE void RSA_get0_crt_params(const RSA *r, const BIGNUM **dmp1, const BIGNUM **dmq1, const BIGNUM **iqmp)
+{
+ *dmp1 = r->dmp1;
+ *dmq1 = r->dmq1;
+ *iqmp = r->iqmp;
+}
+
+static INLINE int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key);
+static INLINE int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g);
+static INLINE void DSA_get0_pqg(const DSA *dsa,
+ const BIGNUM **p, const BIGNUM **q, const BIGNUM **g);
+static INLINE void DSA_get0_key(const DSA *dsa,
+ const BIGNUM **pub_key, const BIGNUM **priv_key);
+
+static INLINE int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key)
+{
+ d->pub_key = pub_key;
+ d->priv_key = priv_key;
+ return 1;
+}
+
+static INLINE int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g)
+{
+ d->p = p;
+ d->q = q;
+ d->g = g;
+ return 1;
+}
+
+static INLINE void
+DSA_get0_pqg(const DSA *dsa, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g)
+{
+ *p = dsa->p;
+ *q = dsa->q;
+ *g = dsa->g;
+}
+
+static INLINE void
+DSA_get0_key(const DSA *dsa, const BIGNUM **pub_key, const BIGNUM **priv_key)
+{
+ if (pub_key)
+ *pub_key = dsa->pub_key;
+
+ if (priv_key)
+ *priv_key = dsa->priv_key;
+}
+
+
+
+static INLINE int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key);
+static INLINE int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g);
+static INLINE int DH_set_length(DH *dh, long length);
+static INLINE void DH_get0_pqg(const DH *dh,
+ const BIGNUM **p, const BIGNUM **q, const BIGNUM **g);
+static INLINE void DH_get0_key(const DH *dh,
+ const BIGNUM **pub_key, const BIGNUM **priv_key);
+
+static INLINE int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key)
+{
+ dh->pub_key = pub_key;
+ dh->priv_key = priv_key;
+ return 1;
+}
+
+static INLINE int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g)
+{
+ dh->p = p;
+ dh->q = q;
+ dh->g = g;
+ return 1;
+}
+
+static INLINE int DH_set_length(DH *dh, long length)
+{
+ dh->length = length;
+ return 1;
+}
+
+
+
+static INLINE void
+DH_get0_pqg(const DH *dh, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g)
+{
+ *p = dh->p;
+ *q = dh->q;
+ *g = dh->g;
+}
+
+static INLINE void
+DH_get0_key(const DH *dh, const BIGNUM **pub_key, const BIGNUM **priv_key)
+{
+ if (pub_key)
+ *pub_key = dh->pub_key;
+
+ if (priv_key)
+ *priv_key = dh->priv_key;
+}
+
+#endif /* E_EVP_COMPAT_H__ */
diff --git a/lib/crypto/c_src/fips.c b/lib/crypto/c_src/fips.c
new file mode 100644
index 0000000000..b2d892d00b
--- /dev/null
+++ b/lib/crypto/c_src/fips.c
@@ -0,0 +1,52 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "fips.h"
+
+ERL_NIF_TERM info_fips(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+#ifdef FIPS_SUPPORT
+ return FIPS_mode() ? atom_enabled : atom_not_enabled;
+#else
+ return atom_not_supported;
+#endif
+}
+
+ERL_NIF_TERM enable_fips_mode(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Boolean) */
+ if (argv[0] == atom_true) {
+#ifdef FIPS_SUPPORT
+ if (FIPS_mode_set(1)) {
+ return atom_true;
+ }
+#endif
+ PRINTF_ERR0("CRYPTO: Could not setup FIPS mode");
+ return atom_false;
+ } else if (argv[0] == atom_false) {
+#ifdef FIPS_SUPPORT
+ if (!FIPS_mode_set(0)) {
+ return atom_false;
+ }
+#endif
+ return atom_true;
+ } else {
+ return enif_make_badarg(env);
+ }
+}
diff --git a/lib/crypto/c_src/fips.h b/lib/crypto/c_src/fips.h
new file mode 100644
index 0000000000..9a436bd202
--- /dev/null
+++ b/lib/crypto/c_src/fips.h
@@ -0,0 +1,29 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_FIPS_H__
+#define E_FIPS_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM info_fips(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM enable_fips_mode(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_FIPS_H__ */
diff --git a/lib/crypto/c_src/hash.c b/lib/crypto/c_src/hash.c
new file mode 100644
index 0000000000..457e9d071a
--- /dev/null
+++ b/lib/crypto/c_src/hash.c
@@ -0,0 +1,499 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "hash.h"
+#include "digest.h"
+
+#define MD5_CTX_LEN (sizeof(MD5_CTX))
+#define MD4_CTX_LEN (sizeof(MD4_CTX))
+#define RIPEMD160_CTX_LEN (sizeof(RIPEMD160_CTX))
+
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
+struct evp_md_ctx {
+ EVP_MD_CTX* ctx;
+};
+
+/* Define resource types for OpenSSL context structures. */
+static ErlNifResourceType* evp_md_ctx_rtype;
+
+static void evp_md_ctx_dtor(ErlNifEnv* env, struct evp_md_ctx *ctx) {
+ if (ctx == NULL)
+ return;
+
+ if (ctx->ctx)
+ EVP_MD_CTX_free(ctx->ctx);
+}
+#endif
+
+int init_hash_ctx(ErlNifEnv* env) {
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
+ evp_md_ctx_rtype = enif_open_resource_type(env, NULL, "EVP_MD_CTX",
+ (ErlNifResourceDtor*) evp_md_ctx_dtor,
+ ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER,
+ NULL);
+ if (evp_md_ctx_rtype == NULL)
+ goto err;
+#endif
+
+ return 1;
+
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
+ err:
+ PRINTF_ERR0("CRYPTO: Could not open resource type 'EVP_MD_CTX'");
+ return 0;
+#endif
+}
+
+ERL_NIF_TERM hash_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Type, Data) */
+ struct digest_type_t *digp = NULL;
+ const EVP_MD *md;
+ ErlNifBinary data;
+ ERL_NIF_TERM ret;
+ unsigned ret_size;
+ unsigned char *outp;
+
+ ASSERT(argc == 2);
+
+ if ((digp = get_digest_type(argv[0])) == NULL)
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, argv[1], &data))
+ goto bad_arg;
+
+ if ((md = digp->md.p) == NULL)
+ goto err;
+
+ ret_size = (unsigned)EVP_MD_size(md);
+ ASSERT(0 < ret_size && ret_size <= EVP_MAX_MD_SIZE);
+
+ if ((outp = enif_make_new_binary(env, ret_size, &ret)) == NULL)
+ goto err;
+ if (EVP_Digest(data.data, data.size, outp, &ret_size, md, NULL) != 1)
+ goto err;
+
+ ASSERT(ret_size == (unsigned)EVP_MD_size(md));
+
+ CONSUME_REDS(env, data);
+ return ret;
+
+ bad_arg:
+ return enif_make_badarg(env);
+
+ err:
+ return atom_notsup;
+}
+
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
+
+ERL_NIF_TERM hash_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Type) */
+ struct digest_type_t *digp = NULL;
+ struct evp_md_ctx *ctx = NULL;
+ ERL_NIF_TERM ret;
+
+ ASSERT(argc == 1);
+
+ if ((digp = get_digest_type(argv[0])) == NULL)
+ goto bad_arg;
+ if (digp->md.p == NULL)
+ goto err;
+
+ if ((ctx = enif_alloc_resource(evp_md_ctx_rtype, sizeof(struct evp_md_ctx))) == NULL)
+ goto err;
+ if ((ctx->ctx = EVP_MD_CTX_new()) == NULL)
+ goto err;
+ if (EVP_DigestInit(ctx->ctx, digp->md.p) != 1)
+ goto err;
+
+ ret = enif_make_resource(env, ctx);
+ goto done;
+
+ bad_arg:
+ return enif_make_badarg(env);
+
+ err:
+ ret = atom_notsup;
+
+ done:
+ if (ctx)
+ enif_release_resource(ctx);
+ return ret;
+}
+
+ERL_NIF_TERM hash_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Context, Data) */
+ struct evp_md_ctx *ctx, *new_ctx = NULL;
+ ErlNifBinary data;
+ ERL_NIF_TERM ret;
+
+ ASSERT(argc == 2);
+
+ if (!enif_get_resource(env, argv[0], evp_md_ctx_rtype, (void**)&ctx))
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, argv[1], &data))
+ goto bad_arg;
+
+ if ((new_ctx = enif_alloc_resource(evp_md_ctx_rtype, sizeof(struct evp_md_ctx))) == NULL)
+ goto err;
+ if ((new_ctx->ctx = EVP_MD_CTX_new()) == NULL)
+ goto err;
+ if (EVP_MD_CTX_copy(new_ctx->ctx, ctx->ctx) != 1)
+ goto err;
+ if (EVP_DigestUpdate(new_ctx->ctx, data.data, data.size) != 1)
+ goto err;
+
+ ret = enif_make_resource(env, new_ctx);
+ CONSUME_REDS(env, data);
+ goto done;
+
+ bad_arg:
+ return enif_make_badarg(env);
+
+ err:
+ ret = atom_notsup;
+
+ done:
+ if (new_ctx)
+ enif_release_resource(new_ctx);
+ return ret;
+}
+
+ERL_NIF_TERM hash_final_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 ret_size;
+ unsigned char *outp;
+
+ ASSERT(argc == 1);
+
+ if (!enif_get_resource(env, argv[0], evp_md_ctx_rtype, (void**)&ctx))
+ goto bad_arg;
+
+ ret_size = (unsigned)EVP_MD_CTX_size(ctx->ctx);
+ ASSERT(0 < ret_size && ret_size <= EVP_MAX_MD_SIZE);
+
+ if ((new_ctx = EVP_MD_CTX_new()) == NULL)
+ goto err;
+ if (EVP_MD_CTX_copy(new_ctx, ctx->ctx) != 1)
+ goto err;
+ if ((outp = enif_make_new_binary(env, ret_size, &ret)) == NULL)
+ goto err;
+ if (EVP_DigestFinal(new_ctx, outp, &ret_size) != 1)
+ goto err;
+
+ ASSERT(ret_size == (unsigned)EVP_MD_CTX_size(ctx->ctx));
+ goto done;
+
+ bad_arg:
+ return enif_make_badarg(env);
+
+ err:
+ ret = atom_notsup;
+
+ done:
+ if (new_ctx)
+ EVP_MD_CTX_free(new_ctx);
+ return ret;
+}
+
+#else /* if OPENSSL_VERSION_NUMBER < 1.0 */
+
+ERL_NIF_TERM hash_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Type) */
+ typedef int (*init_fun)(unsigned char*);
+ struct digest_type_t *digp = NULL;
+ ERL_NIF_TERM ctx;
+ size_t ctx_size = 0;
+ init_fun ctx_init = 0;
+ unsigned char *outp;
+
+ ASSERT(argc == 1);
+
+ if ((digp = get_digest_type(argv[0])) == NULL)
+ goto bad_arg;
+ if (digp->md.p == NULL)
+ goto err;
+
+ switch (EVP_MD_type(digp->md.p))
+ {
+ case NID_md4:
+ ctx_size = MD4_CTX_LEN;
+ ctx_init = (init_fun)(&MD4_Init);
+ break;
+ case NID_md5:
+ ctx_size = MD5_CTX_LEN;
+ ctx_init = (init_fun)(&MD5_Init);
+ break;
+ case NID_ripemd160:
+ ctx_size = RIPEMD160_CTX_LEN;
+ ctx_init = (init_fun)(&RIPEMD160_Init);
+ break;
+ case NID_sha1:
+ ctx_size = sizeof(SHA_CTX);
+ ctx_init = (init_fun)(&SHA1_Init);
+ break;
+#ifdef HAVE_SHA224
+ case NID_sha224:
+ ctx_size = sizeof(SHA256_CTX);
+ ctx_init = (init_fun)(&SHA224_Init);
+ break;
+#endif
+#ifdef HAVE_SHA256
+ case NID_sha256:
+ ctx_size = sizeof(SHA256_CTX);
+ ctx_init = (init_fun)(&SHA256_Init);
+ break;
+#endif
+#ifdef HAVE_SHA384
+ case NID_sha384:
+ ctx_size = sizeof(SHA512_CTX);
+ ctx_init = (init_fun)(&SHA384_Init);
+ break;
+#endif
+#ifdef HAVE_SHA512
+ case NID_sha512:
+ ctx_size = sizeof(SHA512_CTX);
+ ctx_init = (init_fun)(&SHA512_Init);
+ break;
+#endif
+ default:
+ goto err;
+ }
+ ASSERT(ctx_size);
+ ASSERT(ctx_init);
+
+ if ((outp = enif_make_new_binary(env, ctx_size, &ctx)) == NULL)
+ goto err;
+
+ if (ctx_init(outp) != 1)
+ goto err;
+
+ return enif_make_tuple2(env, argv[0], ctx);
+
+ bad_arg:
+ return enif_make_badarg(env);
+
+ err:
+ return atom_notsup;
+}
+
+ERL_NIF_TERM hash_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* ({Type, Context}, Data) */
+ typedef int (*update_fun)(unsigned char*, const unsigned char*, size_t);
+ ERL_NIF_TERM new_ctx;
+ ErlNifBinary ctx, data;
+ const ERL_NIF_TERM *tuple;
+ int arity;
+ struct digest_type_t *digp = NULL;
+ unsigned char *ctx_buff;
+ size_t ctx_size = 0;
+ update_fun ctx_update = 0;
+
+ ASSERT(argc == 2);
+
+ if (!enif_get_tuple(env, argv[0], &arity, &tuple))
+ goto bad_arg;
+ if (arity != 2)
+ goto bad_arg;
+ if ((digp = get_digest_type(tuple[0])) == NULL)
+ goto bad_arg;
+ if (!enif_inspect_binary(env, tuple[1], &ctx))
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, argv[1], &data))
+ goto bad_arg;
+
+ if (digp->md.p == NULL)
+ goto err;
+
+ switch (EVP_MD_type(digp->md.p))
+ {
+ case NID_md4:
+ ctx_size = MD4_CTX_LEN;
+ ctx_update = (update_fun)(&MD4_Update);
+ break;
+ case NID_md5:
+ ctx_size = MD5_CTX_LEN;
+ ctx_update = (update_fun)(&MD5_Update);
+ break;
+ case NID_ripemd160:
+ ctx_size = RIPEMD160_CTX_LEN;
+ ctx_update = (update_fun)(&RIPEMD160_Update);
+ break;
+ case NID_sha1:
+ ctx_size = sizeof(SHA_CTX);
+ ctx_update = (update_fun)(&SHA1_Update);
+ break;
+#ifdef HAVE_SHA224
+ case NID_sha224:
+ ctx_size = sizeof(SHA256_CTX);
+ ctx_update = (update_fun)(&SHA224_Update);
+ break;
+#endif
+#ifdef HAVE_SHA256
+ case NID_sha256:
+ ctx_size = sizeof(SHA256_CTX);
+ ctx_update = (update_fun)(&SHA256_Update);
+ break;
+#endif
+#ifdef HAVE_SHA384
+ case NID_sha384:
+ ctx_size = sizeof(SHA512_CTX);
+ ctx_update = (update_fun)(&SHA384_Update);
+ break;
+#endif
+#ifdef HAVE_SHA512
+ case NID_sha512:
+ ctx_size = sizeof(SHA512_CTX);
+ ctx_update = (update_fun)(&SHA512_Update);
+ break;
+#endif
+ default:
+ goto err;
+ }
+ ASSERT(ctx_size);
+ ASSERT(ctx_update);
+
+ if (ctx.size != ctx_size)
+ goto bad_arg;
+
+ if ((ctx_buff = enif_make_new_binary(env, ctx_size, &new_ctx)) == NULL)
+ goto err;
+ memcpy(ctx_buff, ctx.data, ctx_size);
+
+ if (ctx_update(ctx_buff, data.data, data.size) != 1)
+ goto err;
+
+ CONSUME_REDS(env, data);
+ return enif_make_tuple2(env, tuple[0], new_ctx);
+
+ bad_arg:
+ return enif_make_badarg(env);
+
+ err:
+ return atom_notsup;
+}
+
+ERL_NIF_TERM hash_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* ({Type, Context}) */
+ typedef int (*final_fun)(unsigned char*, void*);
+ ERL_NIF_TERM ret;
+ ErlNifBinary ctx;
+ const ERL_NIF_TERM *tuple;
+ int arity;
+ struct digest_type_t *digp = NULL;
+ const EVP_MD *md;
+ void *new_ctx = NULL;
+ size_t ctx_size = 0;
+ final_fun ctx_final = 0;
+ unsigned char *outp;
+
+ ASSERT(argc == 1);
+
+ if (!enif_get_tuple(env, argv[0], &arity, &tuple))
+ goto bad_arg;
+ if (arity != 2)
+ goto bad_arg;
+ if ((digp = get_digest_type(tuple[0])) == NULL)
+ goto bad_arg;
+ if (!enif_inspect_binary(env, tuple[1], &ctx))
+ goto bad_arg;
+
+ if ((md = digp->md.p) == NULL)
+ goto err;
+
+ switch (EVP_MD_type(md))
+ {
+ case NID_md4:
+ ctx_size = MD4_CTX_LEN;
+ ctx_final = (final_fun)(&MD4_Final);
+ break;
+ case NID_md5:
+ ctx_size = MD5_CTX_LEN;
+ ctx_final = (final_fun)(&MD5_Final);
+ break;
+ case NID_ripemd160:
+ ctx_size = RIPEMD160_CTX_LEN;
+ ctx_final = (final_fun)(&RIPEMD160_Final);
+ break;
+ case NID_sha1:
+ ctx_size = sizeof(SHA_CTX);
+ ctx_final = (final_fun)(&SHA1_Final);
+ break;
+#ifdef HAVE_SHA224
+ case NID_sha224:
+ ctx_size = sizeof(SHA256_CTX);
+ ctx_final = (final_fun)(&SHA224_Final);
+ break;
+#endif
+#ifdef HAVE_SHA256
+ case NID_sha256:
+ ctx_size = sizeof(SHA256_CTX);
+ ctx_final = (final_fun)(&SHA256_Final);
+ break;
+#endif
+#ifdef HAVE_SHA384
+ case NID_sha384:
+ ctx_size = sizeof(SHA512_CTX);
+ ctx_final = (final_fun)(&SHA384_Final);
+ break;
+#endif
+#ifdef HAVE_SHA512
+ case NID_sha512:
+ ctx_size = sizeof(SHA512_CTX);
+ ctx_final = (final_fun)(&SHA512_Final);
+ break;
+#endif
+ default:
+ goto err;
+ }
+ ASSERT(ctx_size);
+ ASSERT(ctx_final);
+
+ if (ctx.size != ctx_size)
+ goto bad_arg;
+
+ if ((new_ctx = enif_alloc(ctx_size)) == NULL)
+ goto err;
+
+ memcpy(new_ctx, ctx.data, ctx_size);
+
+ if ((outp = enif_make_new_binary(env, (size_t)EVP_MD_size(md), &ret)) == NULL)
+ goto err;
+
+ if (ctx_final(outp, new_ctx) != 1)
+ goto err;
+
+ goto done;
+
+ bad_arg:
+ return enif_make_badarg(env);
+
+ err:
+ ret = atom_notsup;
+
+ done:
+ if (new_ctx)
+ enif_free(new_ctx);
+ return ret;
+}
+
+#endif /* OPENSSL_VERSION_NUMBER < 1.0 */
diff --git a/lib/crypto/c_src/hash.h b/lib/crypto/c_src/hash.h
new file mode 100644
index 0000000000..8bae07f39a
--- /dev/null
+++ b/lib/crypto/c_src/hash.h
@@ -0,0 +1,33 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_HASH_H__
+#define E_HASH_H__ 1
+
+#include "common.h"
+
+int init_hash_ctx(ErlNifEnv *env);
+
+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[]);
+
+#endif /* E_HASH_H__ */
diff --git a/lib/crypto/c_src/hmac.c b/lib/crypto/c_src/hmac.c
new file mode 100644
index 0000000000..c41e50eb35
--- /dev/null
+++ b/lib/crypto/c_src/hmac.c
@@ -0,0 +1,270 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "hmac.h"
+#include "digest.h"
+
+struct hmac_context
+{
+ ErlNifMutex* mtx;
+ int alive;
+ HMAC_CTX* ctx;
+};
+
+static ErlNifResourceType* hmac_context_rtype;
+
+static void hmac_context_dtor(ErlNifEnv* env, struct hmac_context*);
+
+int init_hmac_ctx(ErlNifEnv *env) {
+ hmac_context_rtype = enif_open_resource_type(env, NULL, "hmac_context",
+ (ErlNifResourceDtor*) hmac_context_dtor,
+ ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER,
+ NULL);
+ if (hmac_context_rtype == NULL)
+ goto err;
+
+ return 1;
+
+ err:
+ PRINTF_ERR0("CRYPTO: Could not open resource type 'hmac_context'");
+ return 0;
+}
+
+ERL_NIF_TERM hmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Type, Key, Data) or (Type, Key, Data, MacSize) */
+ struct digest_type_t *digp = NULL;
+ ErlNifBinary key, data;
+ unsigned char buff[EVP_MAX_MD_SIZE];
+ unsigned size = 0, req_size = 0;
+ ERL_NIF_TERM ret;
+ unsigned char *outp;
+
+ ASSERT(argc == 3 || argc == 4);
+
+ if ((digp = get_digest_type(argv[0])) == NULL)
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, argv[1], &key))
+ goto bad_arg;
+ if (key.size > INT_MAX)
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, argv[2], &data))
+ goto bad_arg;
+ if (argc == 4) {
+ if (!enif_get_uint(env, argv[3], &req_size))
+ goto bad_arg;
+ }
+
+ if (digp->md.p == NULL)
+ goto err;
+ if (HMAC(digp->md.p,
+ key.data, (int)key.size,
+ data.data, data.size,
+ buff, &size) == NULL)
+ goto err;
+
+ ASSERT(0 < size && size <= EVP_MAX_MD_SIZE);
+ CONSUME_REDS(env, data);
+
+ if (argc == 4) {
+ if (req_size > size)
+ goto bad_arg;
+
+ size = req_size;
+ }
+
+ if ((outp = enif_make_new_binary(env, size, &ret)) == NULL)
+ goto err;
+
+ memcpy(outp, buff, size);
+ return ret;
+
+ bad_arg:
+ return enif_make_badarg(env);
+
+ err:
+ return atom_notsup;
+}
+
+static void hmac_context_dtor(ErlNifEnv* env, struct hmac_context *obj)
+{
+ if (obj == NULL)
+ return;
+
+ if (obj->alive) {
+ if (obj->ctx)
+ HMAC_CTX_free(obj->ctx);
+ obj->alive = 0;
+ }
+
+ if (obj->mtx != NULL)
+ enif_mutex_destroy(obj->mtx);
+}
+
+ERL_NIF_TERM hmac_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Type, Key) */
+ struct digest_type_t *digp = NULL;
+ ErlNifBinary key;
+ ERL_NIF_TERM ret;
+ struct hmac_context *obj = NULL;
+
+ ASSERT(argc == 2);
+
+ if ((digp = get_digest_type(argv[0])) == NULL)
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, argv[1], &key))
+ goto bad_arg;
+ if (key.size > INT_MAX)
+ goto bad_arg;
+
+ if (digp->md.p == NULL)
+ goto err;
+
+ if ((obj = enif_alloc_resource(hmac_context_rtype, sizeof(struct hmac_context))) == NULL)
+ goto err;
+ obj->ctx = NULL;
+ obj->mtx = NULL;
+ obj->alive = 0;
+
+ if ((obj->ctx = HMAC_CTX_new()) == NULL)
+ goto err;
+ obj->alive = 1;
+ if ((obj->mtx = enif_mutex_create("crypto.hmac")) == NULL)
+ goto err;
+
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
+ // Check the return value of HMAC_Init: it may fail in FIPS mode
+ // for disabled algorithms
+ if (!HMAC_Init_ex(obj->ctx, key.data, (int)key.size, digp->md.p, NULL))
+ goto err;
+#else
+ // In ancient versions of OpenSSL, this was a void function.
+ HMAC_Init_ex(obj->ctx, key.data, (int)key.size, digp->md.p, NULL);
+#endif
+
+ ret = enif_make_resource(env, obj);
+ goto done;
+
+ bad_arg:
+ return enif_make_badarg(env);
+
+ err:
+ ret = atom_notsup;
+
+ done:
+ if (obj)
+ enif_release_resource(obj);
+ return ret;
+}
+
+ERL_NIF_TERM hmac_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Context, Data) */
+ ERL_NIF_TERM ret;
+ ErlNifBinary data;
+ struct hmac_context *obj = NULL;
+
+ ASSERT(argc == 2);
+
+ if (!enif_get_resource(env, argv[0], hmac_context_rtype, (void**)&obj))
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, argv[1], &data))
+ goto bad_arg;
+
+ enif_mutex_lock(obj->mtx);
+ if (!obj->alive)
+ goto err;
+
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
+ if (!HMAC_Update(obj->ctx, data.data, data.size))
+ goto err;
+#else
+ // In ancient versions of OpenSSL, this was a void function.
+ HMAC_Update(obj->ctx, data.data, data.size);
+#endif
+
+ CONSUME_REDS(env,data);
+ ret = argv[0];
+ goto done;
+
+ bad_arg:
+ return enif_make_badarg(env);
+
+ err:
+ ret = enif_make_badarg(env);
+
+ done:
+ enif_mutex_unlock(obj->mtx);
+ return ret;
+}
+
+ERL_NIF_TERM hmac_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Context) or (Context, HashLen) */
+ ERL_NIF_TERM ret;
+ struct hmac_context* obj;
+ unsigned char mac_buf[EVP_MAX_MD_SIZE];
+ unsigned char * mac_bin;
+ unsigned int req_len = 0;
+ unsigned int mac_len;
+
+ ASSERT(argc == 1 || argc == 2);
+
+ if (!enif_get_resource(env, argv[0], hmac_context_rtype, (void**)&obj))
+ goto bad_arg;
+ if (argc == 2) {
+ if (!enif_get_uint(env, argv[1], &req_len))
+ goto bad_arg;
+ }
+
+ enif_mutex_lock(obj->mtx);
+ if (!obj->alive)
+ goto err;
+
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
+ if (!HMAC_Final(obj->ctx, mac_buf, &mac_len))
+ goto err;
+#else
+ // In ancient versions of OpenSSL, this was a void function.
+ HMAC_Final(obj->ctx, mac_buf, &mac_len);
+#endif
+
+ if (obj->ctx)
+ HMAC_CTX_free(obj->ctx);
+ obj->alive = 0;
+
+ if (argc == 2 && req_len < mac_len) {
+ /* Only truncate to req_len bytes if asked. */
+ mac_len = req_len;
+ }
+ if ((mac_bin = enif_make_new_binary(env, mac_len, &ret)) == NULL)
+ goto err;
+
+ memcpy(mac_bin, mac_buf, mac_len);
+ goto done;
+
+ bad_arg:
+ return enif_make_badarg(env);
+
+ err:
+ ret = enif_make_badarg(env);
+
+ done:
+ enif_mutex_unlock(obj->mtx);
+ return ret;
+}
+
diff --git a/lib/crypto/c_src/hmac.h b/lib/crypto/c_src/hmac.h
new file mode 100644
index 0000000000..1f0e0ca632
--- /dev/null
+++ b/lib/crypto/c_src/hmac.h
@@ -0,0 +1,33 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_HMAC_H__
+#define E_HMAC_H__ 1
+
+#include "common.h"
+
+int init_hmac_ctx(ErlNifEnv *env);
+
+ERL_NIF_TERM hmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM hmac_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM hmac_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM hmac_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_HMAC_H__ */
diff --git a/lib/crypto/c_src/info.c b/lib/crypto/c_src/info.c
new file mode 100644
index 0000000000..42f477fead
--- /dev/null
+++ b/lib/crypto/c_src/info.c
@@ -0,0 +1,107 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "info.h"
+
+#ifdef HAVE_DYNAMIC_CRYPTO_LIB
+
+# if defined(DEBUG)
+char *crypto_callback_name = "crypto_callback.debug";
+# elif defined(VALGRIND)
+char *crypto_callback_name = "crypto_callback.valgrind";
+# else
+char *crypto_callback_name = "crypto_callback";
+# endif
+
+int change_basename(ErlNifBinary* bin, char* buf, size_t bufsz, const char* newfile)
+{
+ size_t i;
+ size_t newlen;
+
+ for (i = bin->size; i > 0; i--) {
+ if (bin->data[i-1] == '/')
+ break;
+ }
+
+ newlen = strlen(newfile);
+ if (i > SIZE_MAX - newlen)
+ goto err;
+
+ if (i + newlen >= bufsz)
+ goto err;
+
+ memcpy(buf, bin->data, i);
+ strcpy(buf+i, newfile);
+
+ return 1;
+
+ err:
+ return 0;
+}
+
+void error_handler(void* null, const char* errstr)
+{
+ PRINTF_ERR1("CRYPTO LOADING ERROR: '%s'", errstr);
+}
+#endif /* HAVE_DYNAMIC_CRYPTO_LIB */
+
+ERL_NIF_TERM info_lib(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
+{/* () */
+ /* [{<<"OpenSSL">>,9470143,<<"OpenSSL 0.9.8k 25 Mar 2009">>}] */
+
+ ERL_NIF_TERM name_term, ver_term;
+ static const char libname[] = "OpenSSL";
+ size_t name_sz;
+ const char* ver;
+ size_t ver_sz;
+ int ver_num;
+ unsigned char *out_name, *out_ver;
+
+ ASSERT(argc == 0);
+
+ name_sz = strlen(libname);
+ ver = SSLeay_version(SSLEAY_VERSION);
+ ver_sz = strlen(ver);
+ ver_num = OPENSSL_VERSION_NUMBER;
+
+ /* R16:
+ * Ignore library version number from SSLeay() and instead show header
+ * version. Otherwise user might try to call a function that is implemented
+ * by a newer library but not supported by the headers used at compile time.
+ * Example: DES_ede3_cfb_encrypt in 0.9.7i but not in 0.9.7d.
+ *
+ * Version string is still from library though.
+ */
+
+ if ((out_name = enif_make_new_binary(env, name_sz, &name_term)) == NULL)
+ goto err;
+ if ((out_ver = enif_make_new_binary(env, ver_sz, &ver_term)) == NULL)
+ goto err;
+
+ memcpy(out_name, libname, name_sz);
+ memcpy(out_ver, ver, ver_sz);
+
+ return enif_make_list1(env, enif_make_tuple3(env, name_term,
+ enif_make_int(env, ver_num),
+ ver_term));
+
+ err:
+ return enif_make_badarg(env);
+}
diff --git a/lib/crypto/c_src/info.h b/lib/crypto/c_src/info.h
new file mode 100644
index 0000000000..67690625c9
--- /dev/null
+++ b/lib/crypto/c_src/info.h
@@ -0,0 +1,35 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_INFO_H__
+#define E_INFO_H__ 1
+
+#include "common.h"
+
+#ifdef HAVE_DYNAMIC_CRYPTO_LIB
+extern char *crypto_callback_name;
+
+int change_basename(ErlNifBinary* bin, char* buf, size_t bufsz, const char* newfile);
+void error_handler(void* null, const char* errstr);
+#endif
+
+ERL_NIF_TERM info_lib(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_INFO_H__ */
diff --git a/lib/crypto/c_src/math.c b/lib/crypto/c_src/math.c
new file mode 100644
index 0000000000..85494bbc93
--- /dev/null
+++ b/lib/crypto/c_src/math.c
@@ -0,0 +1,53 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "math.h"
+
+ERL_NIF_TERM do_exor(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Data1, Data2) */
+ ErlNifBinary d1, d2;
+ unsigned char* ret_ptr;
+ size_t i;
+ ERL_NIF_TERM ret;
+
+ ASSERT(argc == 2);
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &d1))
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, argv[1], &d2))
+ goto bad_arg;
+ if (d1.size != d2.size)
+ goto bad_arg;
+
+ if ((ret_ptr = enif_make_new_binary(env, d1.size, &ret)) == NULL)
+ goto err;
+
+ for (i=0; i<d1.size; i++) {
+ ret_ptr[i] = d1.data[i] ^ d2.data[i];
+ }
+
+ CONSUME_REDS(env,d1);
+ return ret;
+
+ bad_arg:
+ err:
+ return enif_make_badarg(env);
+}
+
diff --git a/lib/crypto/c_src/math.h b/lib/crypto/c_src/math.h
new file mode 100644
index 0000000000..b8d68ea654
--- /dev/null
+++ b/lib/crypto/c_src/math.h
@@ -0,0 +1,28 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_MATH_H__
+#define E_MATH_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM do_exor(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_MATH_H__ */
diff --git a/lib/crypto/c_src/openssl_config.h b/lib/crypto/c_src/openssl_config.h
new file mode 100644
index 0000000000..45144a0c25
--- /dev/null
+++ b/lib/crypto/c_src/openssl_config.h
@@ -0,0 +1,354 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_OPENSSL_CONFIG_H__
+#define E_OPENSSL_CONFIG_H__ 1
+
+#define OPENSSL_THREAD_DEFINES
+#include <openssl/opensslconf.h>
+
+#include <openssl/crypto.h>
+#ifndef OPENSSL_NO_DES
+#include <openssl/des.h>
+#endif /* #ifndef OPENSSL_NO_DES */
+/* #include <openssl/idea.h> This is not supported on the openssl OTP requires */
+#include <openssl/dsa.h>
+#include <openssl/rsa.h>
+#include <openssl/aes.h>
+#include <openssl/md5.h>
+#include <openssl/md4.h>
+#include <openssl/sha.h>
+#include <openssl/ripemd.h>
+#include <openssl/bn.h>
+#include <openssl/objects.h>
+#ifndef OPENSSL_NO_RC4
+ #include <openssl/rc4.h>
+#endif /* OPENSSL_NO_RC4 */
+#ifndef OPENSSL_NO_RC2
+ #include <openssl/rc2.h>
+#endif
+#include <openssl/blowfish.h>
+#include <openssl/rand.h>
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#include <openssl/err.h>
+
+/* Helper macro to construct a OPENSSL_VERSION_NUMBER.
+ * See openssl/opensslv.h
+ */
+#define PACKED_OPENSSL_VERSION(MAJ, MIN, FIX, P) \
+ ((((((((MAJ << 8) | MIN) << 8 ) | FIX) << 8) | (P-'a'+1)) << 4) | 0xf)
+
+#define PACKED_OPENSSL_VERSION_PLAIN(MAJ, MIN, FIX) \
+ PACKED_OPENSSL_VERSION(MAJ,MIN,FIX,('a'-1))
+
+
+/* LibreSSL was cloned from OpenSSL 1.0.1g and claims to be API and BPI compatible
+ * with 1.0.1.
+ *
+ * LibreSSL has the same names on include files and symbols as OpenSSL, but defines
+ * the OPENSSL_VERSION_NUMBER to be >= 2.0.0
+ *
+ * Therefor works tests like this as intendend:
+ * OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
+ * (The test is for example "2.4.2" >= "1.0.0" although the test
+ * with the cloned OpenSSL test would be "1.0.1" >= "1.0.0")
+ *
+ * But tests like this gives wrong result:
+ * OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
+ * (The test is false since "2.4.2" < "1.1.0". It should have been
+ * true because the LibreSSL API version is "1.0.1")
+ *
+ */
+
+#ifdef LIBRESSL_VERSION_NUMBER
+/* A macro to test on in this file */
+#define HAS_LIBRESSL
+#endif
+
+#ifdef HAS_LIBRESSL
+/* LibreSSL dislikes FIPS */
+# ifdef FIPS_SUPPORT
+# undef FIPS_SUPPORT
+# endif
+
+/* LibreSSL has never supported the custom mem functions */
+#ifndef HAS_LIBRESSL
+# define HAS_CRYPTO_MEM_FUNCTIONS
+#endif
+
+# if LIBRESSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(2,7,0)
+/* LibreSSL wants the 1.0.1 API */
+# define NEED_EVP_COMPATIBILITY_FUNCTIONS
+# endif
+#endif
+
+
+#if OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
+# define NEED_EVP_COMPATIBILITY_FUNCTIONS
+#endif
+
+
+#ifndef HAS_LIBRESSL
+# if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
+# define HAS_EVP_PKEY_CTX
+# endif
+#endif
+
+
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
+#include <openssl/modes.h>
+#endif
+
+#include "crypto_callback.h"
+
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(0,9,8) \
+ && !defined(OPENSSL_NO_SHA224) && defined(NID_sha224) \
+ && !defined(OPENSSL_NO_SHA256) /* disabled like this in my sha.h (?) */
+# define HAVE_SHA224
+#endif
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(0,9,8) \
+ && !defined(OPENSSL_NO_SHA256) && defined(NID_sha256)
+# define HAVE_SHA256
+#endif
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(0,9,8) \
+ && !defined(OPENSSL_NO_SHA384) && defined(NID_sha384)\
+ && !defined(OPENSSL_NO_SHA512) /* disabled like this in my sha.h (?) */
+# define HAVE_SHA384
+#endif
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(0,9,8) \
+ && !defined(OPENSSL_NO_SHA512) && defined(NID_sha512)
+# define HAVE_SHA512
+#endif
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(0,9,7,'e')
+# define HAVE_DES_ede3_cfb_encrypt
+#endif
+
+// SHA3:
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,1,1)
+// An error in beta releases of 1.1.1 fixed in production release
+# ifdef NID_sha3_224
+# define HAVE_SHA3_224
+# endif
+# ifdef NID_sha3_256
+# define HAVE_SHA3_256
+# endif
+#endif
+# ifdef NID_sha3_384
+# define HAVE_SHA3_384
+# endif
+# ifdef NID_sha3_512
+# define HAVE_SHA3_512
+# endif
+
+// BLAKE2:
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,1,1) \
+ && !defined(HAS_LIBRESSL) \
+ && !defined(OPENSSL_NO_BLAKE2)
+# define HAVE_BLAKE2
+#endif
+
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(0,9,8,'o') \
+ && !defined(OPENSSL_NO_EC) \
+ && !defined(OPENSSL_NO_ECDH) \
+ && !defined(OPENSSL_NO_ECDSA)
+# define HAVE_EC
+#endif
+
+// (test for >= 1.1.1pre8)
+#if OPENSSL_VERSION_NUMBER >= (PACKED_OPENSSL_VERSION_PLAIN(1,1,1) -7) \
+ && !defined(HAS_LIBRESSL) \
+ && defined(HAVE_EC)
+# define HAVE_ED_CURVE_DH
+# if OPENSSL_VERSION_NUMBER >= (PACKED_OPENSSL_VERSION_PLAIN(1,1,1))
+# define HAVE_EDDSA
+# endif
+#endif
+
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(0,9,8,'c')
+# define HAVE_AES_IGE
+#endif
+
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,1)
+# define HAVE_EVP_AES_CTR
+# define HAVE_AEAD
+# define HAVE_GCM
+# define HAVE_CCM
+# define HAVE_CMAC
+# if defined(RSA_PKCS1_OAEP_PADDING)
+# define HAVE_RSA_OAEP_PADDING
+# endif
+# define HAVE_RSA_MGF1_MD
+# if OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION(1,0,1,'d')
+# define HAVE_GCM_EVP_DECRYPT_BUG
+# endif
+#endif
+
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
+# ifndef HAS_LIBRESSL
+# define HAVE_CHACHA20
+# define HAVE_CHACHA20_POLY1305
+# define HAVE_RSA_OAEP_MD
+# endif
+#endif
+
+// OPENSSL_VERSION_NUMBER >= 1.1.1-pre8
+#if OPENSSL_VERSION_NUMBER >= (PACKED_OPENSSL_VERSION_PLAIN(1,1,1)-7)
+# ifndef HAS_LIBRESSL
+# define HAVE_POLY1305
+# endif
+#endif
+
+#if OPENSSL_VERSION_NUMBER <= PACKED_OPENSSL_VERSION(0,9,8,'l')
+# define HAVE_ECB_IVEC_BUG
+#endif
+
+#ifndef HAS_LIBRESSL
+# ifdef RSA_SSLV23_PADDING
+# define HAVE_RSA_SSLV23_PADDING
+# endif
+#endif
+
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
+# ifdef RSA_PKCS1_PSS_PADDING
+# define HAVE_RSA_PKCS1_PSS_PADDING
+# endif
+#endif
+
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(0,9,8,'h') \
+ && defined(HAVE_EC)
+/* If OPENSSL_NO_EC is set, there will be an error in ec.h included from engine.h
+ So if EC is disabled, you can't use Engine either....
+*/
+# define HAS_ENGINE_SUPPORT
+#endif
+
+
+#if defined(HAS_ENGINE_SUPPORT)
+# include <openssl/engine.h>
+#endif
+
+#if defined(HAVE_CMAC)
+#include <openssl/cmac.h>
+#endif
+
+#if defined(HAVE_EC)
+#include <openssl/ec.h>
+#include <openssl/ecdh.h>
+#include <openssl/ecdsa.h>
+#endif
+
+#ifdef VALGRIND
+ # include <valgrind/memcheck.h>
+
+/* libcrypto mixes supplied buffer contents into its entropy pool,
+ which makes valgrind complain about the use of uninitialized data.
+ We use this valgrind "request" to make sure that no such seemingly
+ undefined data is returned.
+*/
+ # define ERL_VALGRIND_MAKE_MEM_DEFINED(ptr,size) \
+ VALGRIND_MAKE_MEM_DEFINED(ptr,size)
+
+ # define ERL_VALGRIND_ASSERT_MEM_DEFINED(Ptr,Size) \
+ do { \
+ int __erl_valgrind_mem_defined = VALGRIND_CHECK_MEM_IS_DEFINED((Ptr),(Size)); \
+ if (__erl_valgrind_mem_defined != 0) { \
+ fprintf(stderr,"\r\n####### VALGRIND_ASSSERT(%p,%ld) failed at %s:%d\r\n", \
+ (Ptr),(long)(Size), __FILE__, __LINE__); \
+ abort(); \
+ } \
+ } while (0)
+
+#else
+ # define ERL_VALGRIND_MAKE_MEM_DEFINED(ptr,size)
+ # define ERL_VALGRIND_ASSERT_MEM_DEFINED(ptr,size)
+#endif
+
+#ifdef DEBUG
+ # define ASSERT(e) \
+ ((void) ((e) ? 1 : (fprintf(stderr,"Assert '%s' failed at %s:%d\n",\
+ #e, __FILE__, __LINE__), abort(), 0)))
+#else
+ # define ASSERT(e) ((void) 1)
+#endif
+
+#ifdef __GNUC__
+ # define INLINE __inline__
+#elif defined(__WIN32__)
+ # define INLINE __forceinline
+#else
+ # define INLINE
+#endif
+
+
+#define get_int32(s) ((((unsigned char*) (s))[0] << 24) | \
+ (((unsigned char*) (s))[1] << 16) | \
+ (((unsigned char*) (s))[2] << 8) | \
+ (((unsigned char*) (s))[3]))
+
+#define put_uint32(s,i) \
+{ (s)[0] = (unsigned char)(((i) >> 24) & 0xff);\
+ (s)[1] = (unsigned char)(((i) >> 16) & 0xff);\
+ (s)[2] = (unsigned char)(((i) >> 8) & 0xff);\
+ (s)[3] = (unsigned char)((i) & 0xff);\
+}
+
+/* This shall correspond to the similar macro in crypto.erl */
+/* Current value is: erlang:system_info(context_reductions) * 10 */
+#define MAX_BYTES_TO_NIF 20000
+
+#define CONSUME_REDS(NifEnv, Ibin) \
+do { \
+ size_t _cost = (Ibin).size; \
+ if (_cost > SIZE_MAX / 100) \
+ _cost = 100; \
+ else \
+ _cost = (_cost * 100) / MAX_BYTES_TO_NIF; \
+ \
+ if (_cost) { \
+ (void) enif_consume_timeslice((NifEnv), \
+ (_cost > 100) ? 100 : (int)_cost); \
+ } \
+ } while (0)
+
+#ifdef NEED_EVP_COMPATIBILITY_FUNCTIONS
+# include "evp_compat.h"
+#else
+# define HAVE_OPAQUE_BN_GENCB
+#endif
+
+#if 0
+# define PRINTF_ERR0(FMT) enif_fprintf(stderr, FMT "\n")
+# define PRINTF_ERR1(FMT, A1) enif_fprintf(stderr, FMT "\n", A1)
+# define PRINTF_ERR2(FMT, A1, A2) enif_fprintf(stderr, FMT "\n", A1, A2)
+#else
+# define PRINTF_ERR0(FMT)
+# define PRINTF_ERR1(FMT,A1)
+# define PRINTF_ERR2(FMT,A1,A2)
+#endif
+
+#ifdef FIPS_SUPPORT
+/* In FIPS mode non-FIPS algorithms are disabled and return badarg. */
+#define CHECK_NO_FIPS_MODE() { if (FIPS_mode()) return atom_notsup; }
+#else
+#define CHECK_NO_FIPS_MODE()
+#endif
+
+#endif /* E_OPENSSL_CONFIG_H__ */
diff --git a/lib/crypto/c_src/otp_test_engine.c b/lib/crypto/c_src/otp_test_engine.c
index 2c8cce094e..fd26b7cb5d 100644
--- a/lib/crypto/c_src/otp_test_engine.c
+++ b/lib/crypto/c_src/otp_test_engine.c
@@ -21,8 +21,11 @@
#ifdef _WIN32
#define OPENSSL_OPT_WINDLL
#endif
+
#include <stdio.h>
#include <string.h>
+#include <limits.h>
+#include <stdint.h>
#include <openssl/md5.h>
#include <openssl/rsa.h>
@@ -87,13 +90,12 @@ static int test_init(ENGINE *e) {
printf("OTP Test Engine Initializatzion!\r\n");
#if defined(FAKE_RSA_IMPL)
- if ( !RSA_meth_set_finish(test_rsa_method, test_rsa_free)
- || !RSA_meth_set_sign(test_rsa_method, test_rsa_sign)
- || !RSA_meth_set_verify(test_rsa_method, test_rsa_verify)
- ) {
- fprintf(stderr, "Setup RSA_METHOD failed\r\n");
- return 0;
- }
+ if (!RSA_meth_set_finish(test_rsa_method, test_rsa_free))
+ goto err;
+ if (!RSA_meth_set_sign(test_rsa_method, test_rsa_sign))
+ goto err;
+ if (!RSA_meth_set_verify(test_rsa_method, test_rsa_verify))
+ goto err;
#endif /* if defined(FAKE_RSA_IMPL) */
/* Load all digest and cipher algorithms. Needed for password protected private keys */
@@ -101,6 +103,12 @@ static int test_init(ENGINE *e) {
OpenSSL_add_all_digests();
return 111;
+
+#if defined(FAKE_RSA_IMPL)
+err:
+ fprintf(stderr, "Setup RSA_METHOD failed\r\n");
+ return 0;
+#endif
}
static void add_test_data(unsigned char *md, unsigned int len)
@@ -152,15 +160,15 @@ static int test_engine_md5_update(EVP_MD_CTX *ctx,const void *data, size_t count
static int test_engine_md5_final(EVP_MD_CTX *ctx,unsigned char *md) {
#ifdef OLD
- int ret;
-
fprintf(stderr, "MD5 final size of EVP_MD: %lu\r\n", sizeof(EVP_MD));
- ret = MD5_Final(md, data(ctx));
+ if (!MD5_Final(md, data(ctx)))
+ goto err;
- if (ret > 0) {
- add_test_data(md, MD5_DIGEST_LENGTH);
- }
- return ret;
+ add_test_data(md, MD5_DIGEST_LENGTH);
+ return 1;
+
+ err:
+ return 0;
#else
fprintf(stderr, "MD5 final\r\n");
add_test_data(md, MD5_DIGEST_LENGTH);
@@ -190,7 +198,6 @@ static int test_digest_ids[] = {NID_md5};
static int test_engine_digest_selector(ENGINE *e, const EVP_MD **digest,
const int **nids, int nid) {
- int ok = 1;
if (!digest) {
*nids = test_digest_ids;
fprintf(stderr, "Digest is empty! Nid:%d\r\n", nid);
@@ -201,64 +208,82 @@ static int test_engine_digest_selector(ENGINE *e, const EVP_MD **digest,
#ifdef OLD
*digest = &test_engine_md5_method;
#else
- EVP_MD *md = EVP_MD_meth_new(NID_md5, NID_undef);
- if (!md ||
- !EVP_MD_meth_set_result_size(md, MD5_DIGEST_LENGTH) ||
- !EVP_MD_meth_set_flags(md, 0) ||
- !EVP_MD_meth_set_init(md, test_engine_md5_init) ||
- !EVP_MD_meth_set_update(md, test_engine_md5_update) ||
- !EVP_MD_meth_set_final(md, test_engine_md5_final) ||
- !EVP_MD_meth_set_copy(md, NULL) ||
- !EVP_MD_meth_set_cleanup(md, NULL) ||
- !EVP_MD_meth_set_input_blocksize(md, MD5_CBLOCK) ||
- !EVP_MD_meth_set_app_datasize(md, sizeof(EVP_MD *) + sizeof(MD5_CTX)) ||
- !EVP_MD_meth_set_ctrl(md, NULL))
- {
- ok = 0;
- *digest = NULL;
- } else
- {
- *digest = md;
- }
+ EVP_MD *md;
+
+ if ((md = EVP_MD_meth_new(NID_md5, NID_undef)) == NULL)
+ goto err;
+ if (EVP_MD_meth_set_result_size(md, MD5_DIGEST_LENGTH) != 1)
+ goto err;
+ if (EVP_MD_meth_set_flags(md, 0) != 1)
+ goto err;
+ if (EVP_MD_meth_set_init(md, test_engine_md5_init) != 1)
+ goto err;
+ if (EVP_MD_meth_set_update(md, test_engine_md5_update) != 1)
+ goto err;
+ if (EVP_MD_meth_set_final(md, test_engine_md5_final) != 1)
+ goto err;
+ if (EVP_MD_meth_set_copy(md, NULL) != 1)
+ goto err;
+ if (EVP_MD_meth_set_cleanup(md, NULL) != 1)
+ goto err;
+ if (EVP_MD_meth_set_input_blocksize(md, MD5_CBLOCK) != 1)
+ goto err;
+ if (EVP_MD_meth_set_app_datasize(md, sizeof(EVP_MD *) + sizeof(MD5_CTX)) != 1)
+ goto err;
+ if (EVP_MD_meth_set_ctrl(md, NULL) != 1)
+ goto err;
+
+ *digest = md;
#endif
}
else {
- ok = 0;
- *digest = NULL;
+ goto err;
}
- return ok;
+ return 1;
+
+ err:
+ *digest = NULL;
+ return 0;
}
static int bind_helper(ENGINE * e, const char *id)
{
#if defined(FAKE_RSA_IMPL)
- test_rsa_method = RSA_meth_new("OTP test RSA method", 0);
- if (test_rsa_method == NULL) {
+ if ((test_rsa_method = RSA_meth_new("OTP test RSA method", 0)) == NULL) {
fprintf(stderr, "RSA_meth_new failed\r\n");
- return 0;
+ goto err;
}
#endif /* if defined(FAKE_RSA_IMPL) */
- if (!ENGINE_set_id(e, test_engine_id)
- || !ENGINE_set_name(e, test_engine_name)
- || !ENGINE_set_init_function(e, test_init)
- || !ENGINE_set_digests(e, &test_engine_digest_selector)
- /* For testing of key storage in an Engine: */
- || !ENGINE_set_load_privkey_function(e, &test_privkey_load)
- || !ENGINE_set_load_pubkey_function(e, &test_pubkey_load)
- )
- return 0;
+ if (!ENGINE_set_id(e, test_engine_id))
+ goto err;
+ if (!ENGINE_set_name(e, test_engine_name))
+ goto err;
+ if (!ENGINE_set_init_function(e, test_init))
+ goto err;
+ if (!ENGINE_set_digests(e, &test_engine_digest_selector))
+ goto err;
+ /* For testing of key storage in an Engine: */
+ if (!ENGINE_set_load_privkey_function(e, &test_privkey_load))
+ goto err;
+ if (!ENGINE_set_load_pubkey_function(e, &test_pubkey_load))
+ goto err;
#if defined(FAKE_RSA_IMPL)
- if ( !ENGINE_set_RSA(e, test_rsa_method) ) {
- RSA_meth_free(test_rsa_method);
- test_rsa_method = NULL;
- return 0;
- }
+ if (!ENGINE_set_RSA(e, test_rsa_method))
+ goto err;
#endif /* if defined(FAKE_RSA_IMPL) */
return 1;
+
+ err:
+#if defined(FAKE_RSA_IMPL)
+ if (test_rsa_method)
+ RSA_meth_free(test_rsa_method);
+ test_rsa_method = NULL;
+#endif
+ return 0;
}
IMPLEMENT_DYNAMIC_CHECK_FN();
@@ -304,7 +329,7 @@ EVP_PKEY* test_key_load(ENGINE *eng, const char *id, UI_METHOD *ui_method, void
fprintf(stderr, "Contents of file \"%s\":\r\n",id);
f = fopen(id, "r");
{ /* Print the contents of the key file */
- char c;
+ int c;
while (!feof(f)) {
switch (c=fgetc(f)) {
case '\n':
@@ -324,23 +349,28 @@ EVP_PKEY* test_key_load(ENGINE *eng, const char *id, UI_METHOD *ui_method, void
int pem_passwd_cb_fun(char *buf, int size, int rwflag, void *password)
{
- int i;
+ size_t i;
+
+ if (size < 0)
+ return 0;
fprintf(stderr, "In pem_passwd_cb_fun\r\n");
if (!password)
return 0;
i = strlen(password);
- if (i < size) {
- /* whole pwd (incl terminating 0) fits */
- fprintf(stderr, "Got FULL pwd %d(%d) chars\r\n", i, size);
- memcpy(buf, (char*)password, i+1);
- return i+1;
- } else {
- fprintf(stderr, "Got TO LONG pwd %d(%d) chars\r\n", i, size);
- /* meaningless with a truncated password */
- return 0;
- }
+ if (i >= (size_t)size || i > INT_MAX - 1)
+ goto err;
+
+ /* whole pwd (incl terminating 0) fits */
+ fprintf(stderr, "Got FULL pwd %zu(%d) chars\r\n", i, size);
+ memcpy(buf, (char*)password, i+1);
+ return (int)i+1;
+
+ err:
+ fprintf(stderr, "Got TO LONG pwd %zu(%d) chars\r\n", i, size);
+ /* meaningless with a truncated password */
+ return 0;
}
#endif
@@ -349,7 +379,7 @@ int pem_passwd_cb_fun(char *buf, int size, int rwflag, void *password)
/* RSA sign. This returns a fixed string so the test case can test that it was called
instead of the cryptolib default RSA sign */
-unsigned char fake_flag[] = {255,3,124,180,35,10,180,151,101,247,62,59,80,122,220,
+static unsigned char fake_flag[] = {255,3,124,180,35,10,180,151,101,247,62,59,80,122,220,
142,24,180,191,34,51,150,112,27,43,142,195,60,245,213,80,179};
int test_rsa_sign(int dtype,
@@ -360,11 +390,10 @@ int test_rsa_sign(int dtype,
/* The key */
const RSA *rsa)
{
- int slen;
fprintf(stderr, "test_rsa_sign (dtype=%i) called m_len=%u *siglen=%u\r\n", dtype, m_len, *siglen);
if (!sigret) {
fprintf(stderr, "sigret = NULL\r\n");
- return -1;
+ goto err;
}
/* {int i;
@@ -376,14 +405,20 @@ int test_rsa_sign(int dtype,
if ((sizeof(fake_flag) == m_len)
&& bcmp(m,fake_flag,m_len) == 0) {
+ int slen;
+
printf("To be faked\r\n");
/* To be faked */
- slen = RSA_size(rsa);
- add_test_data(sigret, slen); /* The signature is 0,1,2...255,0,1... */
- *siglen = slen; /* Must set this. Why? */
+ if ((slen = RSA_size(rsa)) < 0)
+ goto err;
+ add_test_data(sigret, (unsigned int)slen); /* The signature is 0,1,2...255,0,1... */
+ *siglen = (unsigned int)slen; /* Must set this. Why? */
return 1; /* 1 = success */
}
return 0;
+
+ err:
+ return -1;
}
int test_rsa_verify(int dtype,
@@ -398,8 +433,13 @@ int test_rsa_verify(int dtype,
if ((sizeof(fake_flag) == m_len)
&& bcmp(m,fake_flag,m_len) == 0) {
+ int size;
+
+ if ((size = RSA_size(rsa)) < 0)
+ return 0;
+
printf("To be faked\r\n");
- return (siglen == RSA_size(rsa))
+ return (siglen == (unsigned int)size)
&& chk_test_data(sigret, siglen);
}
return 0;
diff --git a/lib/crypto/c_src/pkey.c b/lib/crypto/c_src/pkey.c
new file mode 100644
index 0000000000..393358d173
--- /dev/null
+++ b/lib/crypto/c_src/pkey.c
@@ -0,0 +1,1456 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "pkey.h"
+#include "bn.h"
+#include "digest.h"
+#include "dss.h"
+#include "ec.h"
+#include "eddsa.h"
+#include "engine.h"
+#include "rsa.h"
+
+#define PKEY_BADARG -1
+#define PKEY_NOTSUP 0
+#define PKEY_OK 1
+
+typedef struct PKeyCryptOptions {
+ const EVP_MD *rsa_mgf1_md;
+ ErlNifBinary rsa_oaep_label;
+ const EVP_MD *rsa_oaep_md;
+ int rsa_padding;
+ const EVP_MD *signature_md;
+} PKeyCryptOptions;
+
+typedef struct PKeySignOptions {
+ const EVP_MD *rsa_mgf1_md;
+ int rsa_padding;
+ int rsa_pss_saltlen;
+} PKeySignOptions;
+
+
+static int get_pkey_digest_type(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM type,
+ const EVP_MD **md);
+static int get_pkey_sign_digest(ErlNifEnv *env, ERL_NIF_TERM algorithm,
+ ERL_NIF_TERM type, ERL_NIF_TERM data,
+ unsigned char *md_value, const EVP_MD **mdp,
+ unsigned char **tbsp, size_t *tbslenp);
+static int get_pkey_sign_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM options,
+ const EVP_MD *md, PKeySignOptions *opt);
+static int get_pkey_private_key(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM key, EVP_PKEY **pkey);
+static int get_pkey_public_key(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM key,
+ EVP_PKEY **pkey);
+static int get_pkey_crypt_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM options,
+ PKeyCryptOptions *opt);
+static size_t size_of_RSA(EVP_PKEY *pkey);
+
+
+static int get_pkey_digest_type(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM type,
+ const EVP_MD **md)
+{
+ struct digest_type_t *digp = NULL;
+ *md = NULL;
+
+ if (type == atom_none && algorithm == atom_rsa)
+ return PKEY_OK;
+ if (algorithm == atom_eddsa) {
+#ifdef HAVE_EDDSA
+ if (!FIPS_mode()) return PKEY_OK;
+#else
+ return PKEY_NOTSUP;
+#endif
+ }
+ if ((digp = get_digest_type(type)) == NULL)
+ return PKEY_BADARG;
+ if (digp->md.p == NULL)
+ return PKEY_NOTSUP;
+
+ *md = digp->md.p;
+ return PKEY_OK;
+}
+
+static int get_pkey_sign_digest(ErlNifEnv *env, ERL_NIF_TERM algorithm,
+ ERL_NIF_TERM type, ERL_NIF_TERM data,
+ unsigned char *md_value, const EVP_MD **mdp,
+ unsigned char **tbsp, size_t *tbslenp)
+{
+ int i, ret;
+ const ERL_NIF_TERM *tpl_terms;
+ int tpl_arity;
+ ErlNifBinary tbs_bin;
+ EVP_MD_CTX *mdctx = NULL;
+ const EVP_MD *md;
+ unsigned char *tbs;
+ size_t tbslen;
+ unsigned int tbsleni;
+
+ md = *mdp;
+ tbs = *tbsp;
+ tbslen = *tbslenp;
+
+ if ((i = get_pkey_digest_type(env, algorithm, type, &md)) != PKEY_OK)
+ return i;
+
+ if (enif_get_tuple(env, data, &tpl_arity, &tpl_terms)) {
+ if (tpl_arity != 2)
+ goto bad_arg;
+ if (tpl_terms[0] != atom_digest)
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, tpl_terms[1], &tbs_bin))
+ goto bad_arg;
+ if (tbs_bin.size > INT_MAX)
+ goto bad_arg;
+ if (md != NULL) {
+ if ((int)tbs_bin.size != EVP_MD_size(md))
+ goto bad_arg;
+ }
+
+ /* We have a digest (= hashed text) in tbs_bin */
+ tbs = tbs_bin.data;
+ tbslen = tbs_bin.size;
+ } else if (md == NULL) {
+ if (!enif_inspect_iolist_as_binary(env, data, &tbs_bin))
+ goto bad_arg;
+
+ /* md == NULL, that is no hashing because DigestType argument was atom_none */
+ tbs = tbs_bin.data;
+ tbslen = tbs_bin.size;
+ } else {
+ if (!enif_inspect_iolist_as_binary(env, data, &tbs_bin))
+ goto bad_arg;
+
+ /* We have the cleartext in tbs_bin and the hash algo info in md */
+ tbs = md_value;
+
+ if ((mdctx = EVP_MD_CTX_create()) == NULL)
+ goto err;
+
+ /* Looks well, now hash the plain text into a digest according to md */
+ if (EVP_DigestInit_ex(mdctx, md, NULL) != 1)
+ goto err;
+ if (EVP_DigestUpdate(mdctx, tbs_bin.data, tbs_bin.size) != 1)
+ goto err;
+ if (EVP_DigestFinal_ex(mdctx, tbs, &tbsleni) != 1)
+ goto err;
+
+ tbslen = (size_t)tbsleni;
+ }
+
+ *mdp = md;
+ *tbsp = tbs;
+ *tbslenp = tbslen;
+
+ ret = PKEY_OK;
+ goto done;
+
+ bad_arg:
+ err:
+ ret = PKEY_BADARG;
+
+ done:
+ if (mdctx)
+ EVP_MD_CTX_destroy(mdctx);
+ return ret;
+}
+
+static int get_pkey_sign_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM options,
+ const EVP_MD *md, PKeySignOptions *opt)
+{
+ ERL_NIF_TERM head, tail;
+ const ERL_NIF_TERM *tpl_terms;
+ int tpl_arity;
+ const EVP_MD *opt_md;
+
+ if (!enif_is_list(env, options))
+ goto bad_arg;
+
+ /* defaults */
+ if (algorithm == atom_rsa) {
+ opt->rsa_mgf1_md = NULL;
+ opt->rsa_padding = RSA_PKCS1_PADDING;
+ opt->rsa_pss_saltlen = -2;
+ }
+
+ if (enif_is_empty_list(env, options))
+ return PKEY_OK;
+
+ if (algorithm != atom_rsa)
+ goto bad_arg;
+
+ tail = options;
+ while (enif_get_list_cell(env, tail, &head, &tail)) {
+ if (!enif_get_tuple(env, head, &tpl_arity, &tpl_terms))
+ goto bad_arg;
+ if (tpl_arity != 2)
+ goto bad_arg;
+
+ if (tpl_terms[0] == atom_rsa_mgf1_md && enif_is_atom(env, tpl_terms[1])) {
+ int result;
+
+ result = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md);
+ if (result != PKEY_OK)
+ return result;
+
+ opt->rsa_mgf1_md = opt_md;
+
+ } else if (tpl_terms[0] == atom_rsa_padding) {
+ if (tpl_terms[1] == atom_rsa_pkcs1_padding) {
+ opt->rsa_padding = RSA_PKCS1_PADDING;
+
+ } else if (tpl_terms[1] == atom_rsa_pkcs1_pss_padding) {
+#ifdef HAVE_RSA_PKCS1_PSS_PADDING
+ opt->rsa_padding = RSA_PKCS1_PSS_PADDING;
+ if (opt->rsa_mgf1_md == NULL)
+ opt->rsa_mgf1_md = md;
+#else
+ return PKEY_NOTSUP;
+#endif
+
+ } else if (tpl_terms[1] == atom_rsa_x931_padding) {
+ opt->rsa_padding = RSA_X931_PADDING;
+
+ } else if (tpl_terms[1] == atom_rsa_no_padding) {
+ opt->rsa_padding = RSA_NO_PADDING;
+
+ } else {
+ goto bad_arg;
+ }
+
+ } else if (tpl_terms[0] == atom_rsa_pss_saltlen) {
+ if (!enif_get_int(env, tpl_terms[1], &(opt->rsa_pss_saltlen)))
+ goto bad_arg;
+ if (opt->rsa_pss_saltlen < -2)
+ goto bad_arg;
+
+ } else {
+ goto bad_arg;
+ }
+ }
+
+ return PKEY_OK;
+
+ bad_arg:
+ return PKEY_BADARG;
+}
+
+static int get_pkey_private_key(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM key, EVP_PKEY **pkey)
+{
+ EVP_PKEY *result = NULL;
+ RSA *rsa = NULL;
+ DSA *dsa = NULL;
+#if defined(HAVE_EC)
+ EC_KEY *ec = NULL;
+#endif
+ char *id = NULL;
+ char *password = NULL;
+
+ if (enif_is_map(env, key)) {
+#ifdef HAS_ENGINE_SUPPORT
+ /* Use key stored in engine */
+ ENGINE *e;
+
+ if (!get_engine_and_key_id(env, key, &id, &e))
+ goto err;
+
+ password = get_key_password(env, key);
+ result = ENGINE_load_private_key(e, id, NULL, password);
+
+#else
+ return PKEY_BADARG;
+#endif
+ } else if (algorithm == atom_rsa) {
+ if ((rsa = RSA_new()) == NULL)
+ goto err;
+
+ if (!get_rsa_private_key(env, key, rsa))
+ goto err;
+ if ((result = EVP_PKEY_new()) == NULL)
+ goto err;
+ if (EVP_PKEY_assign_RSA(result, rsa) != 1)
+ goto err;
+ /* On success, result owns rsa */
+ rsa = NULL;
+
+ } else if (algorithm == atom_ecdsa) {
+#if defined(HAVE_EC)
+ const ERL_NIF_TERM *tpl_terms;
+ int tpl_arity;
+
+ if (!enif_get_tuple(env, key, &tpl_arity, &tpl_terms))
+ goto err;
+ if (tpl_arity != 2)
+ goto err;
+ if (!enif_is_tuple(env, tpl_terms[0]))
+ goto err;
+ if (!enif_is_binary(env, tpl_terms[1]))
+ goto err;
+ if (!get_ec_key(env, tpl_terms[0], tpl_terms[1], atom_undefined, &ec))
+ goto err;
+
+ if ((result = EVP_PKEY_new()) == NULL)
+ goto err;
+ if (EVP_PKEY_assign_EC_KEY(result, ec) != 1)
+ goto err;
+ /* On success, result owns ec */
+ ec = NULL;
+
+#else
+ return PKEY_NOTSUP;
+#endif
+ } else if (algorithm == atom_eddsa) {
+#ifdef HAVE_EDDSA
+ if (!FIPS_mode())
+ {
+ if (!get_eddsa_key(env, 0, key, &result))
+ goto err;
+ else
+ goto done; // Not nice....
+ }
+#else
+ return PKEY_NOTSUP;
+#endif
+ } else if (algorithm == atom_dss) {
+ if ((dsa = DSA_new()) == NULL)
+ goto err;
+ if (!get_dss_private_key(env, key, dsa))
+ goto err;
+
+ if ((result = EVP_PKEY_new()) == NULL)
+ goto err;
+ if (EVP_PKEY_assign_DSA(result, dsa) != 1)
+ goto err;
+ /* On success, result owns dsa */
+ dsa = NULL;
+
+ } else {
+ return PKEY_BADARG;
+ }
+
+ goto done;
+
+ err:
+ if (result)
+ EVP_PKEY_free(result);
+ result = NULL;
+
+ done:
+ if (password)
+ enif_free(password);
+ if (id)
+ enif_free(id);
+ if (rsa)
+ RSA_free(rsa);
+ if (dsa)
+ DSA_free(dsa);
+#ifdef HAVE_EC
+ if (ec)
+ EC_KEY_free(ec);
+#endif
+
+ if (result == NULL) {
+ return PKEY_BADARG;
+ } else {
+ *pkey = result;
+ return PKEY_OK;
+ }
+}
+
+static int get_pkey_public_key(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM key,
+ EVP_PKEY **pkey)
+{
+ EVP_PKEY *result = NULL;
+ RSA *rsa = NULL;
+ DSA *dsa = NULL;
+#if defined(HAVE_EC)
+ EC_KEY *ec = NULL;
+#endif
+ char *id = NULL;
+ char *password = NULL;
+
+ if (enif_is_map(env, key)) {
+#ifdef HAS_ENGINE_SUPPORT
+ /* Use key stored in engine */
+ ENGINE *e;
+
+ if (!get_engine_and_key_id(env, key, &id, &e))
+ goto err;
+
+ password = get_key_password(env, key);
+ result = ENGINE_load_public_key(e, id, NULL, password);
+
+#else
+ return PKEY_BADARG;
+#endif
+ } else if (algorithm == atom_rsa) {
+ if ((rsa = RSA_new()) == NULL)
+ goto err;
+
+ if (!get_rsa_public_key(env, key, rsa))
+ goto err;
+
+ if ((result = EVP_PKEY_new()) == NULL)
+ goto err;
+ if (EVP_PKEY_assign_RSA(result, rsa) != 1)
+ goto err;
+ /* On success, result owns rsa */
+ rsa = NULL;
+
+ } else if (algorithm == atom_ecdsa) {
+#if defined(HAVE_EC)
+ const ERL_NIF_TERM *tpl_terms;
+ int tpl_arity;
+
+ if (!enif_get_tuple(env, key, &tpl_arity, &tpl_terms))
+ goto err;
+ if (tpl_arity != 2)
+ goto err;
+ if (!enif_is_tuple(env, tpl_terms[0]))
+ goto err;
+ if (!enif_is_binary(env, tpl_terms[1]))
+ goto err;
+ if (!get_ec_key(env, tpl_terms[0], atom_undefined, tpl_terms[1], &ec))
+ goto err;
+
+ if ((result = EVP_PKEY_new()) == NULL)
+ goto err;
+
+ if (EVP_PKEY_assign_EC_KEY(result, ec) != 1)
+ goto err;
+ /* On success, result owns ec */
+ ec = NULL;
+
+#else
+ return PKEY_NOTSUP;
+#endif
+ } else if (algorithm == atom_eddsa) {
+#ifdef HAVE_EDDSA
+ if (!FIPS_mode()) {
+ if (!get_eddsa_key(env, 1, key, &result))
+ goto err;
+ }
+#else
+ return PKEY_NOTSUP;
+#endif
+ } else if (algorithm == atom_dss) {
+ if ((dsa = DSA_new()) == NULL)
+ goto err;
+
+ if (!get_dss_public_key(env, key, dsa))
+ goto err;
+
+ if ((result = EVP_PKEY_new()) == NULL)
+ goto err;
+ if (EVP_PKEY_assign_DSA(result, dsa) != 1)
+ goto err;
+ /* On success, result owns dsa */
+ dsa = NULL;
+
+ } else {
+ return PKEY_BADARG;
+ }
+
+ goto done;
+
+ err:
+ if (result)
+ EVP_PKEY_free(result);
+ result = NULL;
+
+ done:
+ if (password)
+ enif_free(password);
+ if (id)
+ enif_free(id);
+ if (rsa)
+ RSA_free(rsa);
+ if (dsa)
+ DSA_free(dsa);
+#ifdef HAVE_EC
+ if (ec)
+ EC_KEY_free(ec);
+#endif
+
+ if (result == NULL) {
+ return PKEY_BADARG;
+ } else {
+ *pkey = result;
+ return PKEY_OK;
+ }
+}
+
+ERL_NIF_TERM pkey_sign_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
+{/* (Algorithm, Type, Data|{digest,Digest}, Key|#{}, Options) */
+ int i;
+ int sig_bin_alloc = 0;
+ ERL_NIF_TERM ret;
+ const EVP_MD *md = NULL;
+ unsigned char md_value[EVP_MAX_MD_SIZE];
+ EVP_PKEY *pkey = NULL;
+#ifdef HAVE_EDDSA
+ EVP_MD_CTX *mdctx = NULL;
+#endif
+#ifdef HAS_EVP_PKEY_CTX
+ EVP_PKEY_CTX *ctx = NULL;
+ size_t siglen;
+#else
+ int len;
+ unsigned int siglen;
+#endif
+ PKeySignOptions sig_opt;
+ ErlNifBinary sig_bin; /* signature */
+ unsigned char *tbs; /* data to be signed */
+ size_t tbslen;
+ RSA *rsa = NULL;
+ DSA *dsa = NULL;
+#if defined(HAVE_EC)
+ EC_KEY *ec = NULL;
+#endif
+/*char buf[1024];
+enif_get_atom(env,argv[0],buf,1024,ERL_NIF_LATIN1); printf("algo=%s ",buf);
+enif_get_atom(env,argv[1],buf,1024,ERL_NIF_LATIN1); printf("hash=%s ",buf);
+*/
+
+#ifndef HAS_ENGINE_SUPPORT
+ if (enif_is_map(env, argv[3]))
+ return atom_notsup;
+#endif
+
+ i = get_pkey_sign_digest(env, argv[0], argv[1], argv[2], md_value, &md, &tbs, &tbslen);
+ switch (i) {
+ case PKEY_OK:
+ break;
+ case PKEY_NOTSUP:
+ goto notsup;
+ default:
+ goto bad_arg;
+ }
+
+ i = get_pkey_sign_options(env, argv[0], argv[4], md, &sig_opt);
+ switch (i) {
+ case PKEY_OK:
+ break;
+ case PKEY_NOTSUP:
+ goto notsup;
+ default:
+ goto bad_arg;
+ }
+
+ if (get_pkey_private_key(env, argv[0], argv[3], &pkey) != PKEY_OK)
+ goto bad_arg;
+
+#ifdef HAS_EVP_PKEY_CTX
+ if ((ctx = EVP_PKEY_CTX_new(pkey, NULL)) == NULL)
+ goto err;
+
+ if (argv[0] != atom_eddsa) {
+ if (EVP_PKEY_sign_init(ctx) != 1)
+ goto err;
+ if (md != NULL) {
+ if (EVP_PKEY_CTX_set_signature_md(ctx, md) != 1)
+ goto err;
+ }
+ }
+
+ if (argv[0] == atom_rsa) {
+ if (EVP_PKEY_CTX_set_rsa_padding(ctx, sig_opt.rsa_padding) != 1)
+ goto err;
+# ifdef HAVE_RSA_PKCS1_PSS_PADDING
+ if (sig_opt.rsa_padding == RSA_PKCS1_PSS_PADDING) {
+ if (sig_opt.rsa_mgf1_md != NULL) {
+# ifdef HAVE_RSA_MGF1_MD
+ if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, sig_opt.rsa_mgf1_md) != 1)
+ goto err;
+# else
+ goto notsup;
+# endif
+ }
+ if (sig_opt.rsa_pss_saltlen > -2) {
+ if (EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, sig_opt.rsa_pss_saltlen) != 1)
+ goto err;
+ }
+ }
+#endif
+ }
+
+ if (argv[0] == atom_eddsa) {
+#ifdef HAVE_EDDSA
+ if (!FIPS_mode()) {
+ if ((mdctx = EVP_MD_CTX_new()) == NULL)
+ goto err;
+
+ if (EVP_DigestSignInit(mdctx, NULL, NULL, NULL, pkey) != 1)
+ goto err;
+ if (EVP_DigestSign(mdctx, NULL, &siglen, tbs, tbslen) != 1)
+ goto err;
+ if (!enif_alloc_binary(siglen, &sig_bin))
+ goto err;
+ sig_bin_alloc = 1;
+
+ if (EVP_DigestSign(mdctx, sig_bin.data, &siglen, tbs, tbslen) != 1)
+ goto bad_key;
+ }
+ else
+#endif
+ goto notsup;
+ } else {
+ if (EVP_PKEY_sign(ctx, NULL, &siglen, tbs, tbslen) != 1)
+ goto err;
+ if (!enif_alloc_binary(siglen, &sig_bin))
+ goto err;
+ sig_bin_alloc = 1;
+
+ if (md != NULL) {
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, EVP_MD_size(md));
+ }
+ if (EVP_PKEY_sign(ctx, sig_bin.data, &siglen, tbs, tbslen) != 1)
+ goto bad_key;
+ }
+#else
+/*printf("Old interface\r\n");
+ */
+ if (argv[0] == atom_rsa) {
+ if ((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL)
+ goto err;
+ if ((len = RSA_size(rsa)) < 0)
+ goto err;
+ if (!enif_alloc_binary((size_t)len, &sig_bin))
+ goto err;
+ sig_bin_alloc = 1;
+
+ if ((len = EVP_MD_size(md)) < 0)
+ goto err;
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, len);
+
+ if (RSA_sign(md->type, tbs, (unsigned int)len, sig_bin.data, &siglen, rsa) != 1)
+ goto bad_key;
+ } else if (argv[0] == atom_dss) {
+ if ((dsa = EVP_PKEY_get1_DSA(pkey)) == NULL)
+ goto err;
+ if ((len = DSA_size(dsa)) < 0)
+ goto err;
+ if (!enif_alloc_binary((size_t)len, &sig_bin))
+ goto err;
+ sig_bin_alloc = 1;
+
+ if ((len = EVP_MD_size(md)) < 0)
+ goto err;
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, len);
+
+ if (DSA_sign(md->type, tbs, len, sig_bin.data, &siglen, dsa) != 1)
+ goto bad_key;
+ } else if (argv[0] == atom_ecdsa) {
+#if defined(HAVE_EC)
+ if ((ec = EVP_PKEY_get1_EC_KEY(pkey)) == NULL)
+ goto err;
+ if ((len = ECDSA_size(ec)) < 0)
+ goto err;
+ if (!enif_alloc_binary((size_t)len, &sig_bin))
+ goto err;
+ sig_bin_alloc = 1;
+
+ len = EVP_MD_size(md);
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, len);
+
+ if (ECDSA_sign(md->type, tbs, len, sig_bin.data, &siglen, ec) != 1)
+ goto bad_key;
+#else
+ goto notsup;
+#endif
+ } else {
+ goto bad_arg;
+ }
+#endif
+
+ ERL_VALGRIND_MAKE_MEM_DEFINED(sig_bin.data, siglen);
+ if (siglen != sig_bin.size) {
+ if (!enif_realloc_binary(&sig_bin, siglen))
+ goto err;
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(sig_bin.data, siglen);
+ }
+ ret = enif_make_binary(env, &sig_bin);
+ sig_bin_alloc = 0;
+ goto done;
+
+ bad_key:
+ ret = atom_error;
+ goto done;
+
+ notsup:
+ ret = atom_notsup;
+ goto done;
+
+ bad_arg:
+ err:
+ ret = enif_make_badarg(env);
+ goto done;
+
+ done:
+ if (sig_bin_alloc)
+ enif_release_binary(&sig_bin);
+ if (rsa)
+ RSA_free(rsa);
+ if (dsa)
+ DSA_free(dsa);
+#ifdef HAVE_EC
+ if (ec)
+ EC_KEY_free(ec);
+#endif
+#ifdef HAS_EVP_PKEY_CTX
+ if (ctx)
+ EVP_PKEY_CTX_free(ctx);
+#endif
+ if (pkey)
+ EVP_PKEY_free(pkey);
+
+ return ret;
+}
+
+ERL_NIF_TERM pkey_verify_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
+{/* (Algorithm, Type, Data|{digest,Digest}, Signature, Key, Options) */
+ int i;
+ int result;
+ const EVP_MD *md = NULL;
+ unsigned char md_value[EVP_MAX_MD_SIZE];
+ EVP_PKEY *pkey = NULL;
+#ifdef HAS_EVP_PKEY_CTX
+ EVP_PKEY_CTX *ctx = NULL;
+#else
+#endif
+ PKeySignOptions sig_opt;
+ ErlNifBinary sig_bin; /* signature */
+ unsigned char *tbs; /* data to be signed */
+ size_t tbslen;
+ ERL_NIF_TERM ret;
+ RSA *rsa = NULL;
+ DSA *dsa = NULL;
+#ifdef HAVE_EC
+ EC_KEY *ec = NULL;
+#endif
+#ifdef HAVE_EDDSA
+ EVP_MD_CTX *mdctx = NULL;
+#endif
+
+#ifndef HAS_ENGINE_SUPPORT
+ if (enif_is_map(env, argv[4]))
+ return atom_notsup;
+#endif
+
+ if (!enif_inspect_binary(env, argv[3], &sig_bin))
+ return enif_make_badarg(env);
+
+ i = get_pkey_sign_digest(env, argv[0], argv[1], argv[2], md_value, &md, &tbs, &tbslen);
+ switch (i) {
+ case PKEY_OK:
+ break;
+ case PKEY_NOTSUP:
+ goto notsup;
+ default:
+ goto bad_arg;
+ }
+
+ i = get_pkey_sign_options(env, argv[0], argv[5], md, &sig_opt);
+ switch (i) {
+ case PKEY_OK:
+ break;
+ case PKEY_NOTSUP:
+ goto notsup;
+ default:
+ goto bad_arg;
+ }
+
+ if (get_pkey_public_key(env, argv[0], argv[4], &pkey) != PKEY_OK) {
+ goto bad_arg;
+ }
+
+#ifdef HAS_EVP_PKEY_CTX
+/* printf("EVP interface\r\n");
+ */
+ if ((ctx = EVP_PKEY_CTX_new(pkey, NULL)) == NULL)
+ goto err;
+
+ if (argv[0] != atom_eddsa) {
+ if (EVP_PKEY_verify_init(ctx) != 1)
+ goto err;
+ if (md != NULL) {
+ if (EVP_PKEY_CTX_set_signature_md(ctx, md) != 1)
+ goto err;
+ }
+ }
+
+ if (argv[0] == atom_rsa) {
+ if (EVP_PKEY_CTX_set_rsa_padding(ctx, sig_opt.rsa_padding) != 1)
+ goto err;
+ if (sig_opt.rsa_padding == RSA_PKCS1_PSS_PADDING) {
+ if (sig_opt.rsa_mgf1_md != NULL) {
+# ifdef HAVE_RSA_MGF1_MD
+ if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, sig_opt.rsa_mgf1_md) != 1)
+ goto err;
+# else
+ goto notsup;
+# endif
+ }
+ if (sig_opt.rsa_pss_saltlen > -2) {
+ if (EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, sig_opt.rsa_pss_saltlen) != 1)
+ goto err;
+ }
+ }
+ }
+
+ if (argv[0] == atom_eddsa) {
+#ifdef HAVE_EDDSA
+ if (!FIPS_mode()) {
+ if ((mdctx = EVP_MD_CTX_new()) == NULL)
+ goto err;
+
+ if (EVP_DigestVerifyInit(mdctx, NULL, NULL, NULL, pkey) != 1)
+ goto err;
+
+ result = EVP_DigestVerify(mdctx, sig_bin.data, sig_bin.size, tbs, tbslen);
+ }
+ else
+#endif
+ goto notsup;
+ } else {
+ if (md != NULL) {
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, EVP_MD_size(md));
+ }
+ result = EVP_PKEY_verify(ctx, sig_bin.data, sig_bin.size, tbs, tbslen);
+ }
+#else
+/*printf("Old interface\r\n");
+*/
+ if (tbslen > INT_MAX)
+ goto bad_arg;
+ if (sig_bin.size > INT_MAX)
+ goto bad_arg;
+ if (argv[0] == atom_rsa) {
+ if ((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL)
+ goto err;
+ result = RSA_verify(md->type, tbs, (unsigned int)tbslen, sig_bin.data, (unsigned int)sig_bin.size, rsa);
+ } else if (argv[0] == atom_dss) {
+ if ((dsa = EVP_PKEY_get1_DSA(pkey)) == NULL)
+ goto err;
+ result = DSA_verify(0, tbs, (int)tbslen, sig_bin.data, (int)sig_bin.size, dsa);
+ } else if (argv[0] == atom_ecdsa) {
+#if defined(HAVE_EC)
+ if ((ec = EVP_PKEY_get1_EC_KEY(pkey)) == NULL)
+ goto err;
+ result = ECDSA_verify(EVP_MD_type(md), tbs, (int)tbslen, sig_bin.data, (int)sig_bin.size, ec);
+#else
+ goto notsup;
+#endif
+ } else {
+ goto bad_arg;
+ }
+#endif
+
+ ret = (result == 1 ? atom_true : atom_false);
+ goto done;
+
+ bad_arg:
+ err:
+ ret = enif_make_badarg(env);
+ goto done;
+
+ notsup:
+ ret = atom_notsup;
+
+ done:
+#ifdef HAS_EVP_PKEY_CTX
+ if (ctx)
+ EVP_PKEY_CTX_free(ctx);
+#endif
+#ifdef HAVE_EDDSA
+ if (mdctx)
+ EVP_MD_CTX_free(mdctx);
+#endif
+ if (pkey)
+ EVP_PKEY_free(pkey);
+ if (rsa)
+ RSA_free(rsa);
+ if (dsa)
+ DSA_free(dsa);
+#ifdef HAVE_EC
+ if (ec)
+ EC_KEY_free(ec);
+#endif
+
+ return ret;
+}
+
+static int get_pkey_crypt_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM options,
+ PKeyCryptOptions *opt)
+{
+ ERL_NIF_TERM head, tail;
+ const ERL_NIF_TERM *tpl_terms;
+ int tpl_arity;
+ const EVP_MD *opt_md;
+
+ if (!enif_is_list(env, options))
+ goto bad_arg;
+
+ /* defaults */
+ if (algorithm == atom_rsa) {
+ opt->rsa_mgf1_md = NULL;
+ opt->rsa_oaep_label.data = NULL;
+ opt->rsa_oaep_label.size = 0;
+ opt->rsa_oaep_md = NULL;
+ opt->rsa_padding = RSA_PKCS1_PADDING;
+ opt->signature_md = NULL;
+ }
+
+ if (enif_is_empty_list(env, options))
+ return PKEY_OK;
+
+ if (algorithm != atom_rsa)
+ goto bad_arg;
+
+ tail = options;
+ while (enif_get_list_cell(env, tail, &head, &tail)) {
+ if (!enif_get_tuple(env, head, &tpl_arity, &tpl_terms))
+ goto bad_arg;
+ if (tpl_arity != 2)
+ goto bad_arg;
+
+ if (tpl_terms[0] == atom_rsa_padding
+ || tpl_terms[0] == atom_rsa_pad /* Compatibility */
+ ) {
+ if (tpl_terms[1] == atom_rsa_pkcs1_padding) {
+ opt->rsa_padding = RSA_PKCS1_PADDING;
+
+#ifdef HAVE_RSA_OAEP_PADDING
+ } else if (tpl_terms[1] == atom_rsa_pkcs1_oaep_padding) {
+ opt->rsa_padding = RSA_PKCS1_OAEP_PADDING;
+#endif
+
+#ifdef HAVE_RSA_SSLV23_PADDING
+ } else if (tpl_terms[1] == atom_rsa_sslv23_padding) {
+ opt->rsa_padding = RSA_SSLV23_PADDING;
+#endif
+
+ } else if (tpl_terms[1] == atom_rsa_x931_padding) {
+ opt->rsa_padding = RSA_X931_PADDING;
+
+ } else if (tpl_terms[1] == atom_rsa_no_padding) {
+ opt->rsa_padding = RSA_NO_PADDING;
+
+ } else {
+ goto bad_arg;
+ }
+
+ } else if (tpl_terms[0] == atom_signature_md && enif_is_atom(env, tpl_terms[1])) {
+ int i;
+ i = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md);
+ if (i != PKEY_OK) {
+ return i;
+ }
+ opt->signature_md = opt_md;
+
+ } else if (tpl_terms[0] == atom_rsa_mgf1_md && enif_is_atom(env, tpl_terms[1])) {
+ int i;
+#ifndef HAVE_RSA_MGF1_MD
+ if (tpl_terms[1] != atom_sha)
+ return PKEY_NOTSUP;
+#endif
+ i = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md);
+ if (i != PKEY_OK) {
+ return i;
+ }
+ opt->rsa_mgf1_md = opt_md;
+
+ } else if (tpl_terms[0] == atom_rsa_oaep_label
+ && enif_inspect_binary(env, tpl_terms[1], &(opt->rsa_oaep_label))) {
+#ifdef HAVE_RSA_OAEP_MD
+ continue;
+#else
+ return PKEY_NOTSUP;
+#endif
+
+ } else if (tpl_terms[0] == atom_rsa_oaep_md && enif_is_atom(env, tpl_terms[1])) {
+ int i;
+#ifndef HAVE_RSA_OAEP_MD
+ if (tpl_terms[1] != atom_sha)
+ return PKEY_NOTSUP;
+#endif
+ i = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md);
+ if (i != PKEY_OK) {
+ return i;
+ }
+ opt->rsa_oaep_md = opt_md;
+
+ } else {
+ goto bad_arg;
+ }
+ }
+
+ return PKEY_OK;
+
+ bad_arg:
+ return PKEY_BADARG;
+}
+
+static size_t size_of_RSA(EVP_PKEY *pkey) {
+ int ret = 0;
+ RSA *rsa = NULL;
+
+ if ((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL)
+ goto err;
+ ret = RSA_size(rsa);
+
+ err:
+ if (rsa)
+ RSA_free(rsa);
+
+ return (ret < 0) ? 0 : (size_t)ret;
+}
+
+ERL_NIF_TERM pkey_crypt_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
+{/* (Algorithm, Data, PublKey=[E,N]|[E,N,D]|[E,N,D,P1,P2,E1,E2,C], Options, IsPrivate, IsEncrypt) */
+ ERL_NIF_TERM ret;
+ int i;
+ int result = 0;
+ int tmp_bin_alloc = 0;
+ int out_bin_alloc = 0;
+ EVP_PKEY *pkey = NULL;
+#ifdef HAS_EVP_PKEY_CTX
+ EVP_PKEY_CTX *ctx = NULL;
+#else
+ int len;
+ RSA *rsa = NULL;
+#endif
+ PKeyCryptOptions crypt_opt;
+ ErlNifBinary in_bin, out_bin, tmp_bin;
+ size_t outlen;
+#ifdef HAVE_RSA_SSLV23_PADDING
+ size_t tmplen;
+#endif
+ int is_private, is_encrypt;
+ int algo_init = 0;
+ unsigned char *label_copy = NULL;
+
+ ASSERT(argc == 6);
+
+ is_private = (argv[4] == atom_true);
+ is_encrypt = (argv[5] == atom_true);
+
+/* char algo[1024]; */
+
+#ifndef HAS_ENGINE_SUPPORT
+ if (enif_is_map(env, argv[2]))
+ return atom_notsup;
+#endif
+
+ if (!enif_inspect_binary(env, argv[1], &in_bin))
+ goto bad_arg;
+
+ i = get_pkey_crypt_options(env, argv[0], argv[3], &crypt_opt);
+ switch (i) {
+ case PKEY_OK:
+ break;
+ case PKEY_NOTSUP:
+ goto notsup;
+ default:
+ goto bad_arg;
+ }
+
+ if (is_private) {
+ if (get_pkey_private_key(env, argv[0], argv[2], &pkey) != PKEY_OK)
+ goto bad_arg;
+ } else {
+ if (get_pkey_public_key(env, argv[0], argv[2], &pkey) != PKEY_OK)
+ goto bad_arg;
+ }
+
+#ifdef HAS_EVP_PKEY_CTX
+ if ((ctx = EVP_PKEY_CTX_new(pkey, NULL)) == NULL)
+ goto err;
+
+/* enif_get_atom(env,argv[0],algo,1024,ERL_NIF_LATIN1); */
+
+ if (is_private) {
+ if (is_encrypt) {
+ /* private encrypt */
+ if ((algo_init = EVP_PKEY_sign_init(ctx)) != 1)
+ goto bad_arg;
+ } else {
+ /* private decrypt */
+ if ((algo_init = EVP_PKEY_decrypt_init(ctx)) != 1)
+ goto bad_arg;
+ }
+ } else {
+ if (is_encrypt) {
+ /* public encrypt */
+ if ((algo_init = EVP_PKEY_encrypt_init(ctx)) != 1)
+ goto bad_arg;
+ } else {
+ /* public decrypt */
+ if ((algo_init = EVP_PKEY_verify_recover_init(ctx)) != 1)
+ goto bad_arg;
+ }
+ }
+
+ if (argv[0] == atom_rsa) {
+ if (crypt_opt.signature_md != NULL) {
+ if (EVP_PKEY_CTX_set_signature_md(ctx, crypt_opt.signature_md) != 1)
+ goto bad_arg;
+ }
+
+#ifdef HAVE_RSA_SSLV23_PADDING
+ if (crypt_opt.rsa_padding == RSA_SSLV23_PADDING) {
+ if (is_encrypt) {
+ tmplen = size_of_RSA(pkey);
+ if (tmplen < 1 || tmplen > INT_MAX)
+ goto err;
+ if (!enif_alloc_binary(tmplen, &tmp_bin))
+ goto err;
+ tmp_bin_alloc = 1;
+ if (in_bin.size > INT_MAX)
+ goto err;
+ if (!RSA_padding_add_SSLv23(tmp_bin.data, (int)tmplen, in_bin.data, (int)in_bin.size))
+ goto err;
+ in_bin = tmp_bin;
+ }
+ if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_NO_PADDING) != 1)
+ goto err;
+ } else
+#endif
+ {
+ if (EVP_PKEY_CTX_set_rsa_padding(ctx, crypt_opt.rsa_padding) != 1)
+ goto err;
+ }
+
+#ifdef HAVE_RSA_OAEP_MD
+ if (crypt_opt.rsa_padding == RSA_PKCS1_OAEP_PADDING) {
+ if (crypt_opt.rsa_oaep_md != NULL) {
+ if (EVP_PKEY_CTX_set_rsa_oaep_md(ctx, crypt_opt.rsa_oaep_md) != 1)
+ goto err;
+ }
+
+ if (crypt_opt.rsa_mgf1_md != NULL) {
+ if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, crypt_opt.rsa_mgf1_md) != 1)
+ goto err;
+ }
+
+ if (crypt_opt.rsa_oaep_label.data != NULL && crypt_opt.rsa_oaep_label.size > 0) {
+ if (crypt_opt.rsa_oaep_label.size > INT_MAX)
+ goto err;
+ if ((label_copy = OPENSSL_malloc(crypt_opt.rsa_oaep_label.size)) == NULL)
+ goto err;
+
+ memcpy((void *)(label_copy), (const void *)(crypt_opt.rsa_oaep_label.data),
+ crypt_opt.rsa_oaep_label.size);
+
+ if (EVP_PKEY_CTX_set0_rsa_oaep_label(ctx, label_copy,
+ (int)crypt_opt.rsa_oaep_label.size) != 1)
+ goto err;
+ /* On success, label_copy is owned by ctx */
+ label_copy = NULL;
+ }
+ }
+#endif
+ }
+
+ if (is_private) {
+ if (is_encrypt) {
+ /* private_encrypt */
+ result = EVP_PKEY_sign(ctx, NULL, &outlen, in_bin.data, in_bin.size);
+ } else {
+ /* private_decrypt */
+ result = EVP_PKEY_decrypt(ctx, NULL, &outlen, in_bin.data, in_bin.size);
+ }
+ } else {
+ if (is_encrypt) {
+ /* public_encrypt */
+ result = EVP_PKEY_encrypt(ctx, NULL, &outlen, in_bin.data, in_bin.size);
+ } else {
+ /* public_decrypt */
+ result = EVP_PKEY_verify_recover(ctx, NULL, &outlen, in_bin.data, in_bin.size);
+ }
+ }
+ /* fprintf(stderr,"i = %d %s:%d\r\n", i, __FILE__, __LINE__); */
+
+ if (result != 1)
+ goto err;
+
+ if (!enif_alloc_binary(outlen, &out_bin))
+ goto err;
+ out_bin_alloc = 1;
+
+ if (is_private) {
+ if (is_encrypt) {
+ /* private_encrypt */
+ result = EVP_PKEY_sign(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size);
+ } else {
+ /* private_decrypt */
+ result = EVP_PKEY_decrypt(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size);
+ }
+ } else {
+ if (is_encrypt) {
+ /* public_encrypt */
+ result = EVP_PKEY_encrypt(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size);
+ } else {
+ /* public_decrypt */
+ result = EVP_PKEY_verify_recover(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size);
+ }
+ }
+
+#else
+ /* Non-EVP cryptolib. Only support RSA */
+
+ if (argv[0] != atom_rsa) {
+ algo_init = -2; /* exitcode: notsup */
+ goto bad_arg;
+ }
+
+ if ((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL)
+ goto err;
+ if ((len = RSA_size(rsa)) < 0)
+ goto err;
+ if (!enif_alloc_binary((size_t)len, &out_bin))
+ goto err;
+ out_bin_alloc = 1;
+
+ if (in_bin.size > INT_MAX)
+ goto err;
+ if (is_private) {
+ if (is_encrypt) {
+ /* non-evp rsa private encrypt */
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(in_bin.data,in_bin.size);
+ result = RSA_private_encrypt((int)in_bin.size, in_bin.data,
+ out_bin.data, rsa, crypt_opt.rsa_padding);
+ if (result > 0) {
+ ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, result);
+ }
+ } else {
+ /* non-evp rsa private decrypt */
+ result = RSA_private_decrypt((int)in_bin.size, in_bin.data,
+ out_bin.data, rsa, crypt_opt.rsa_padding);
+ if (result > 0) {
+ ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, result);
+ if (!enif_realloc_binary(&out_bin, (size_t)result))
+ goto err;
+ }
+ }
+ } else {
+ if (is_encrypt) {
+ /* non-evp rsa public encrypt */
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(in_bin.data,in_bin.size);
+ result = RSA_public_encrypt((int)in_bin.size, in_bin.data,
+ out_bin.data, rsa, crypt_opt.rsa_padding);
+ if (result > 0) {
+ ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, result);
+ }
+ } else {
+ /* non-evp rsa public decrypt */
+ result = RSA_public_decrypt((int)in_bin.size, in_bin.data,
+ out_bin.data, rsa, crypt_opt.rsa_padding);
+ if (result > 0) {
+ ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, result);
+ if (!enif_realloc_binary(&out_bin, (size_t)result))
+ goto err;
+ }
+ }
+ }
+
+ outlen = (size_t)result;
+#endif
+
+ if ((result > 0) && argv[0] == atom_rsa && !is_encrypt) {
+#ifdef HAVE_RSA_SSLV23_PADDING
+ if (crypt_opt.rsa_padding == RSA_SSLV23_PADDING) {
+ unsigned char *p;
+
+ tmplen = size_of_RSA(pkey);
+ if (tmplen < 1 || tmplen > INT_MAX)
+ goto err;
+ if (!enif_alloc_binary(tmplen, &tmp_bin))
+ goto err;
+ tmp_bin_alloc = 1;
+ if (out_bin.size > INT_MAX)
+ goto err;
+
+ p = out_bin.data;
+ p++;
+
+ result = RSA_padding_check_SSLv23(tmp_bin.data, (int)tmplen, p, (int)out_bin.size - 1, (int)tmplen);
+ if (result >= 0) {
+ outlen = (size_t)result;
+ in_bin = out_bin;
+ out_bin = tmp_bin;
+ tmp_bin = in_bin;
+ result = 1;
+ }
+ }
+#endif
+ }
+
+ if (result > 0) {
+ ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, outlen);
+ if (outlen != out_bin.size) {
+ if (!enif_realloc_binary(&out_bin, outlen))
+ goto err;
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(out_bin.data, outlen);
+ }
+ ret = enif_make_binary(env, &out_bin);
+ out_bin_alloc = 0;
+ } else {
+ ret = atom_error;
+ }
+ goto done;
+
+ notsup:
+ ret = atom_notsup;
+ goto done;
+
+ bad_arg:
+ err:
+ if (algo_init == -2)
+ ret = atom_notsup;
+ else
+ ret = enif_make_badarg(env);
+
+ done:
+ if (out_bin_alloc)
+ enif_release_binary(&out_bin);
+ if (tmp_bin_alloc)
+ enif_release_binary(&tmp_bin);
+
+#ifdef HAS_EVP_PKEY_CTX
+ if (ctx)
+ EVP_PKEY_CTX_free(ctx);
+#else
+ if (rsa)
+ RSA_free(rsa);
+#endif
+ if (pkey)
+ EVP_PKEY_free(pkey);
+
+ if (label_copy)
+ OPENSSL_free(label_copy);
+
+ return ret;
+}
+
+ERL_NIF_TERM privkey_to_pubkey_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{ /* (Algorithm, PrivKey | KeyMap) */
+ ERL_NIF_TERM ret;
+ EVP_PKEY *pkey = NULL;
+ RSA *rsa = NULL;
+ DSA *dsa = NULL;
+ ERL_NIF_TERM result[8];
+
+ ASSERT(argc == 2);
+
+ if (get_pkey_private_key(env, argv[0], argv[1], &pkey) != PKEY_OK)
+ goto bad_arg;
+
+ if (argv[0] == atom_rsa) {
+ const BIGNUM *n = NULL, *e = NULL, *d = NULL;
+
+ if ((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL)
+ goto err;
+
+ RSA_get0_key(rsa, &n, &e, &d);
+
+ // Exponent E
+ if ((result[0] = bin_from_bn(env, e)) == atom_error)
+ goto err;
+ // Modulus N = p*q
+ if ((result[1] = bin_from_bn(env, n)) == atom_error)
+ goto err;
+
+ ret = enif_make_list_from_array(env, result, 2);
+
+ } else if (argv[0] == atom_dss) {
+ const BIGNUM *p = NULL, *q = NULL, *g = NULL, *pub_key = NULL;
+
+ if ((dsa = EVP_PKEY_get1_DSA(pkey)) == NULL)
+ goto err;
+
+ DSA_get0_pqg(dsa, &p, &q, &g);
+ DSA_get0_key(dsa, &pub_key, NULL);
+
+ if ((result[0] = bin_from_bn(env, p)) == atom_error)
+ goto err;
+ if ((result[1] = bin_from_bn(env, q)) == atom_error)
+ goto err;
+ if ((result[2] = bin_from_bn(env, g)) == atom_error)
+ goto err;
+ if ((result[3] = bin_from_bn(env, pub_key)) == atom_error)
+ goto err;
+
+ ret = enif_make_list_from_array(env, result, 4);
+
+ } else if (argv[0] == atom_ecdsa) {
+#if defined(HAVE_EC)
+ /* not yet implemented
+ EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pkey);
+ if (ec) {
+ / * Example of result:
+ {
+ Curve = {Field, Prime, Point, Order, CoFactor} =
+ {
+ Field = {prime_field,<<255,...,255>>},
+ Prime = {<<255,...,252>>,
+ <<90,...,75>>,
+ <<196,...,144>>
+ },
+ Point = <<4,...,245>>,
+ Order = <<255,...,81>>,
+ CoFactor = <<1>>
+ },
+ Key = <<151,...,62>>
+ }
+ or
+ {
+ Curve =
+ {characteristic_two_field,
+ M,
+ Basis = {tpbasis, _}
+ | {ppbasis, k1, k2, k3}
+ },
+ Key
+ }
+ * /
+ EVP_PKEY_free(pkey);
+ return enif_make_list_from_array(env, ..., ...);
+ */
+#endif
+ goto bad_arg;
+ } else {
+ goto bad_arg;
+ }
+
+ goto done;
+
+ bad_arg:
+ err:
+ ret = enif_make_badarg(env);
+
+ done:
+ if (rsa)
+ RSA_free(rsa);
+ if (dsa)
+ DSA_free(dsa);
+ if (pkey)
+ EVP_PKEY_free(pkey);
+
+ return ret;
+}
diff --git a/lib/crypto/c_src/pkey.h b/lib/crypto/c_src/pkey.h
new file mode 100644
index 0000000000..f647a4a160
--- /dev/null
+++ b/lib/crypto/c_src/pkey.h
@@ -0,0 +1,31 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_PKEY_H__
+#define E_PKEY_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM pkey_sign_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM pkey_verify_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM pkey_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM privkey_to_pubkey_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_PKEY_H__ */
diff --git a/lib/crypto/c_src/poly1305.c b/lib/crypto/c_src/poly1305.c
new file mode 100644
index 0000000000..db3433dce3
--- /dev/null
+++ b/lib/crypto/c_src/poly1305.c
@@ -0,0 +1,90 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "poly1305.h"
+
+/* For OpenSSL >= 1.1.1 the hmac_nif and cmac_nif could be integrated into poly1305 (with 'type' as parameter) */
+ERL_NIF_TERM poly1305_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key, Text) */
+#ifdef HAVE_POLY1305
+ ErlNifBinary key_bin, text, ret_bin;
+ ERL_NIF_TERM ret;
+ EVP_PKEY *key = NULL;
+ EVP_MD_CTX *mctx = NULL;
+ EVP_PKEY_CTX *pctx = NULL;
+ const EVP_MD *md = NULL;
+ size_t size;
+ int ret_bin_alloc = 0;
+
+ ASSERT(argc == 2);
+
+ if (!enif_inspect_binary(env, argv[0], &key_bin))
+ goto bad_arg;
+ if (key_bin.size != 32)
+ goto bad_arg;
+ if (!enif_inspect_binary(env, argv[1], &text))
+ goto bad_arg;
+
+ if ((key = EVP_PKEY_new_raw_private_key(EVP_PKEY_POLY1305, /*engine*/ NULL, key_bin.data, key_bin.size)) == NULL)
+ goto err;
+
+ if ((mctx = EVP_MD_CTX_new()) == NULL)
+ goto err;
+ if (EVP_DigestSignInit(mctx, &pctx, md, /*engine*/ NULL, key) != 1)
+ goto err;
+ if (EVP_DigestSignUpdate(mctx, text.data, text.size) != 1)
+ goto err;
+
+ if (EVP_DigestSignFinal(mctx, NULL, &size) != 1)
+ goto err;
+ if (!enif_alloc_binary(size, &ret_bin))
+ goto err;
+ ret_bin_alloc = 1;
+ if (EVP_DigestSignFinal(mctx, ret_bin.data, &size) != 1)
+ goto err;
+
+ if (size != ret_bin.size) {
+ if (!enif_realloc_binary(&ret_bin, size))
+ goto err;
+ }
+
+ ret = enif_make_binary(env, &ret_bin);
+ ret_bin_alloc = 0;
+ goto done;
+
+ bad_arg:
+ return enif_make_badarg(env);
+
+ err:
+ if (ret_bin_alloc)
+ enif_release_binary(&ret_bin);
+ ret = atom_error;
+
+ done:
+ if (mctx)
+ EVP_MD_CTX_free(mctx);
+ if (key)
+ EVP_PKEY_free(key);
+ return ret;
+
+#else
+ return atom_notsup;
+#endif
+}
diff --git a/lib/crypto/c_src/poly1305.h b/lib/crypto/c_src/poly1305.h
new file mode 100644
index 0000000000..4bf45e6218
--- /dev/null
+++ b/lib/crypto/c_src/poly1305.h
@@ -0,0 +1,28 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_POLY1305_H__
+#define E_POLY1305_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM poly1305_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_POLY1305_H__ */
diff --git a/lib/crypto/c_src/rand.c b/lib/crypto/c_src/rand.c
new file mode 100644
index 0000000000..3812ae0991
--- /dev/null
+++ b/lib/crypto/c_src/rand.c
@@ -0,0 +1,149 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "rand.h"
+#include "bn.h"
+
+ERL_NIF_TERM strong_rand_bytes_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Bytes) */
+ unsigned bytes;
+ unsigned char* data;
+ ERL_NIF_TERM ret;
+
+ ASSERT(argc == 1);
+
+ if (!enif_get_uint(env, argv[0], &bytes))
+ goto bad_arg;
+ if (bytes > INT_MAX)
+ goto bad_arg;
+
+ if ((data = enif_make_new_binary(env, bytes, &ret)) == NULL)
+ goto err;
+ if (RAND_bytes(data, (int)bytes) != 1)
+ goto err;
+
+ ERL_VALGRIND_MAKE_MEM_DEFINED(data, bytes);
+ return ret;
+
+ bad_arg:
+ return enif_make_badarg(env);
+
+ err:
+ return atom_false;
+}
+
+ERL_NIF_TERM strong_rand_range_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Range) */
+ BIGNUM *bn_range = NULL, *bn_rand = NULL;
+ ERL_NIF_TERM ret;
+
+ ASSERT(argc == 1);
+
+ if (!get_bn_from_bin(env, argv[0], &bn_range))
+ goto bad_arg;
+
+ if ((bn_rand = BN_new()) == NULL)
+ goto err;
+ if (!BN_rand_range(bn_rand, bn_range))
+ goto err;
+
+ if ((ret = bin_from_bn(env, bn_rand)) == atom_error)
+ goto err;
+ goto done;
+
+ bad_arg:
+ return enif_make_badarg(env);
+
+ err:
+ ret = atom_false;
+
+ done:
+ if (bn_rand)
+ BN_free(bn_rand);
+ if (bn_range)
+ BN_free(bn_range);
+ return ret;
+}
+
+ERL_NIF_TERM rand_uniform_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Lo,Hi) */
+ BIGNUM *bn_from = NULL, *bn_to = NULL, *bn_rand = NULL;
+ unsigned char* data;
+ int dlen;
+ ERL_NIF_TERM ret;
+
+ ASSERT(argc == 2);
+
+ if (!get_bn_from_mpint(env, argv[0], &bn_from))
+ goto bad_arg;
+ if (!get_bn_from_mpint(env, argv[1], &bn_rand))
+ goto bad_arg;
+
+ if ((bn_to = BN_new()) == NULL)
+ goto err;
+
+ if (!BN_sub(bn_to, bn_rand, bn_from))
+ goto err;
+ if (!BN_pseudo_rand_range(bn_rand, bn_to))
+ goto err;
+ if (!BN_add(bn_rand, bn_rand, bn_from))
+ goto err;
+
+ if ((dlen = BN_num_bytes(bn_rand)) < 0)
+ goto err;
+ if ((data = enif_make_new_binary(env, (size_t)dlen+4, &ret)) == NULL)
+ goto err;
+
+ put_uint32(data, (unsigned int)dlen);
+ BN_bn2bin(bn_rand, data+4);
+ ERL_VALGRIND_MAKE_MEM_DEFINED(data+4, dlen);
+ goto done;
+
+ bad_arg:
+ err:
+ ret = enif_make_badarg(env);
+
+ done:
+ if (bn_rand)
+ BN_free(bn_rand);
+ if (bn_from)
+ BN_free(bn_from);
+ if (bn_to)
+ BN_free(bn_to);
+ return ret;
+}
+
+ERL_NIF_TERM rand_seed_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Seed) */
+ ErlNifBinary seed_bin;
+
+ ASSERT(argc == 1);
+
+ if (!enif_inspect_binary(env, argv[0], &seed_bin))
+ goto bad_arg;
+ if (seed_bin.size > INT_MAX)
+ goto bad_arg;
+
+ RAND_seed(seed_bin.data, (int)seed_bin.size);
+ return atom_ok;
+
+ bad_arg:
+ return enif_make_badarg(env);
+}
diff --git a/lib/crypto/c_src/rand.h b/lib/crypto/c_src/rand.h
new file mode 100644
index 0000000000..9c23d343ec
--- /dev/null
+++ b/lib/crypto/c_src/rand.h
@@ -0,0 +1,31 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_RAND_H__
+#define E_RAND_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM strong_rand_bytes_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM strong_rand_range_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM rand_uniform_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM rand_seed_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_RAND_H__ */
diff --git a/lib/crypto/c_src/rc4.c b/lib/crypto/c_src/rc4.c
new file mode 100644
index 0000000000..e423661097
--- /dev/null
+++ b/lib/crypto/c_src/rc4.c
@@ -0,0 +1,92 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "rc4.h"
+
+ERL_NIF_TERM rc4_set_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key) */
+#ifndef OPENSSL_NO_RC4
+ ErlNifBinary key;
+ ERL_NIF_TERM ret;
+ RC4_KEY *rc4_key;
+
+ CHECK_NO_FIPS_MODE();
+
+ ASSERT(argc == 1);
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &key))
+ goto bad_arg;
+ if (key.size > INT_MAX)
+ goto bad_arg;
+
+ if ((rc4_key = (RC4_KEY*)enif_make_new_binary(env, sizeof(RC4_KEY), &ret)) == NULL)
+ goto err;
+
+ RC4_set_key(rc4_key, (int)key.size, key.data);
+ return ret;
+
+ bad_arg:
+ err:
+ return enif_make_badarg(env);
+
+#else
+ return enif_raise_exception(env, atom_notsup);
+#endif
+}
+
+ERL_NIF_TERM rc4_encrypt_with_state(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (State, Data) */
+#ifndef OPENSSL_NO_RC4
+ ErlNifBinary state, data;
+ RC4_KEY* rc4_key;
+ ERL_NIF_TERM new_state, new_data;
+ unsigned char *outp;
+
+ CHECK_NO_FIPS_MODE();
+
+ ASSERT(argc == 2);
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &state))
+ goto bad_arg;
+ if (state.size != sizeof(RC4_KEY))
+ goto bad_arg;
+ if (!enif_inspect_iolist_as_binary(env, argv[1], &data))
+ goto bad_arg;
+
+ if ((rc4_key = (RC4_KEY*)enif_make_new_binary(env, sizeof(RC4_KEY), &new_state)) == NULL)
+ goto err;
+ if ((outp = enif_make_new_binary(env, data.size, &new_data)) == NULL)
+ goto err;
+
+ memcpy(rc4_key, state.data, sizeof(RC4_KEY));
+ RC4(rc4_key, data.size, data.data, outp);
+
+ CONSUME_REDS(env, data);
+ return enif_make_tuple2(env, new_state, new_data);
+
+ bad_arg:
+ err:
+ return enif_make_badarg(env);
+
+#else
+ return enif_raise_exception(env, atom_notsup);
+#endif
+}
+
diff --git a/lib/crypto/c_src/rc4.h b/lib/crypto/c_src/rc4.h
new file mode 100644
index 0000000000..28bf674253
--- /dev/null
+++ b/lib/crypto/c_src/rc4.h
@@ -0,0 +1,29 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_RC4_H__
+#define E_RC4_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM rc4_set_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM rc4_encrypt_with_state(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_RC4_H__ */
diff --git a/lib/crypto/c_src/rsa.c b/lib/crypto/c_src/rsa.c
new file mode 100644
index 0000000000..e9f29aa496
--- /dev/null
+++ b/lib/crypto/c_src/rsa.c
@@ -0,0 +1,282 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "rsa.h"
+#include "bn.h"
+
+static ERL_NIF_TERM rsa_generate_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM put_rsa_private_key(ErlNifEnv* env, const RSA *rsa);
+static int check_erlang_interrupt(int maj, int min, BN_GENCB *ctxt);
+
+int get_rsa_private_key(ErlNifEnv* env, ERL_NIF_TERM key, RSA *rsa)
+{
+ /* key=[E,N,D]|[E,N,D,P1,P2,E1,E2,C] */
+ ERL_NIF_TERM head, tail;
+ BIGNUM *e = NULL, *n = NULL, *d = NULL;
+ BIGNUM *p = NULL, *q = NULL;
+ BIGNUM *dmp1 = NULL, *dmq1 = NULL, *iqmp = NULL;
+
+ if (!enif_get_list_cell(env, key, &head, &tail))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, head, &e))
+ goto bad_arg;
+ if (!enif_get_list_cell(env, tail, &head, &tail))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, head, &n))
+ goto bad_arg;
+ if (!enif_get_list_cell(env, tail, &head, &tail))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, head, &d))
+ goto bad_arg;
+
+ if (!RSA_set0_key(rsa, n, e, d))
+ goto err;
+ /* rsa now owns n, e, and d */
+ n = NULL;
+ e = NULL;
+ d = NULL;
+
+ if (enif_is_empty_list(env, tail))
+ return 1;
+
+ if (!enif_get_list_cell(env, tail, &head, &tail))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, head, &p))
+ goto bad_arg;
+ if (!enif_get_list_cell(env, tail, &head, &tail))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, head, &q))
+ goto bad_arg;
+ if (!enif_get_list_cell(env, tail, &head, &tail))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, head, &dmp1))
+ goto bad_arg;
+ if (!enif_get_list_cell(env, tail, &head, &tail))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, head, &dmq1))
+ goto bad_arg;
+ if (!enif_get_list_cell(env, tail, &head, &tail))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, head, &iqmp))
+ goto bad_arg;
+ if (!enif_is_empty_list(env, tail))
+ goto bad_arg;
+
+ if (!RSA_set0_factors(rsa, p, q))
+ goto err;
+ /* rsa now owns p and q */
+ p = NULL;
+ q = NULL;
+
+ if (!RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp))
+ goto err;
+ /* rsa now owns dmp1, dmq1, and iqmp */
+ dmp1 = NULL;
+ dmq1 = NULL;
+ iqmp = NULL;
+
+ return 1;
+
+ bad_arg:
+ err:
+ if (e)
+ BN_free(e);
+ if (n)
+ BN_free(n);
+ if (d)
+ BN_free(d);
+ if (p)
+ BN_free(p);
+ if (q)
+ BN_free(q);
+ if (dmp1)
+ BN_free(dmp1);
+ if (dmq1)
+ BN_free(dmq1);
+ if (iqmp)
+ BN_free(iqmp);
+
+ return 0;
+}
+
+int get_rsa_public_key(ErlNifEnv* env, ERL_NIF_TERM key, RSA *rsa)
+{
+ /* key=[E,N] */
+ ERL_NIF_TERM head, tail;
+ BIGNUM *e = NULL, *n = NULL;
+
+ if (!enif_get_list_cell(env, key, &head, &tail))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, head, &e))
+ goto bad_arg;
+ if (!enif_get_list_cell(env, tail, &head, &tail))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, head, &n))
+ goto bad_arg;
+ if (!enif_is_empty_list(env, tail))
+ goto bad_arg;
+
+ if (!RSA_set0_key(rsa, n, e, NULL))
+ goto err;
+ /* rsa now owns n and e */
+ n = NULL;
+ e = NULL;
+
+ return 1;
+
+ bad_arg:
+ err:
+ if (e)
+ BN_free(e);
+ if (n)
+ BN_free(n);
+
+ return 0;
+}
+
+/* Creates a term which can be parsed by get_rsa_private_key(). This is a list of plain integer binaries (not mpints). */
+static ERL_NIF_TERM put_rsa_private_key(ErlNifEnv* env, const RSA *rsa)
+{
+ ERL_NIF_TERM result[8];
+ const BIGNUM *n = NULL, *e = NULL, *d = NULL, *p = NULL, *q = NULL, *dmp1 = NULL, *dmq1 = NULL, *iqmp = NULL;
+
+ /* Return at least [E,N,D] */
+ RSA_get0_key(rsa, &n, &e, &d);
+
+ if ((result[0] = bin_from_bn(env, e)) == atom_error) // Exponent E
+ goto err;
+ if ((result[1] = bin_from_bn(env, n)) == atom_error) // Modulus N = p*q
+ goto err;
+ if ((result[2] = bin_from_bn(env, d)) == atom_error) // Exponent D
+ goto err;
+
+ /* Check whether the optional additional parameters are available */
+ RSA_get0_factors(rsa, &p, &q);
+ RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp);
+
+ if (p && q && dmp1 && dmq1 && iqmp) {
+ if ((result[3] = bin_from_bn(env, p)) == atom_error) // Factor p
+ goto err;
+ if ((result[4] = bin_from_bn(env, q)) == atom_error) // Factor q
+ goto err;
+ if ((result[5] = bin_from_bn(env, dmp1)) == atom_error) // D mod (p-1)
+ goto err;
+ if ((result[6] = bin_from_bn(env, dmq1)) == atom_error) // D mod (q-1)
+ goto err;
+ if ((result[7] = bin_from_bn(env, iqmp)) == atom_error) // (1/q) mod p
+ goto err;
+
+ return enif_make_list_from_array(env, result, 8);
+ } else {
+ return enif_make_list_from_array(env, result, 3);
+ }
+
+ err:
+ return enif_make_badarg(env);
+}
+
+static int check_erlang_interrupt(int maj, int min, BN_GENCB *ctxt)
+{
+ ErlNifEnv *env = BN_GENCB_get_arg(ctxt);
+
+ if (!enif_is_current_process_alive(env)) {
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+static ERL_NIF_TERM rsa_generate_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (ModulusSize, PublicExponent) */
+ ERL_NIF_TERM ret;
+ int modulus_bits;
+ BIGNUM *pub_exp = NULL, *three = NULL;
+ RSA *rsa = NULL;
+ BN_GENCB *intr_cb = NULL;
+#ifndef HAVE_OPAQUE_BN_GENCB
+ BN_GENCB intr_cb_buf;
+#endif
+
+ ASSERT(argc == 2);
+
+ if (!enif_get_int(env, argv[0], &modulus_bits))
+ goto bad_arg;
+ if (modulus_bits < 256)
+ goto bad_arg;
+ if (!get_bn_from_bin(env, argv[1], &pub_exp))
+ goto bad_arg;
+
+ /* Make sure the public exponent is large enough (at least 3).
+ * Without this, RSA_generate_key_ex() can run forever. */
+ if ((three = BN_new()) == NULL)
+ goto err;
+ if (!BN_set_word(three, 3))
+ goto err;
+ if (BN_cmp(pub_exp, three) < 0)
+ goto err;
+
+ /* For large keys, prime generation can take many seconds. Set up
+ * the callback which we use to test whether the process has been
+ * interrupted. */
+#ifdef HAVE_OPAQUE_BN_GENCB
+ if ((intr_cb = BN_GENCB_new()) == NULL)
+ goto err;
+#else
+ intr_cb = &intr_cb_buf;
+#endif
+ BN_GENCB_set(intr_cb, check_erlang_interrupt, env);
+
+ if ((rsa = RSA_new()) == NULL)
+ goto err;
+
+ if (!RSA_generate_key_ex(rsa, modulus_bits, pub_exp, intr_cb))
+ goto err;
+
+ ret = put_rsa_private_key(env, rsa);
+ goto done;
+
+ bad_arg:
+ return enif_make_badarg(env);
+
+ err:
+ ret = atom_error;
+
+ done:
+ if (pub_exp)
+ BN_free(pub_exp);
+ if (three)
+ BN_free(three);
+#ifdef HAVE_OPAQUE_BN_GENCB
+ if (intr_cb)
+ BN_GENCB_free(intr_cb);
+#endif
+ if (rsa)
+ RSA_free(rsa);
+ return ret;
+}
+
+ERL_NIF_TERM rsa_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ /* RSA key generation can take a long time (>1 sec for a large
+ * modulus), so schedule it as a CPU-bound operation. */
+ return enif_schedule_nif(env, "rsa_generate_key",
+ ERL_NIF_DIRTY_JOB_CPU_BOUND,
+ rsa_generate_key, argc, argv);
+}
diff --git a/lib/crypto/c_src/rsa.h b/lib/crypto/c_src/rsa.h
new file mode 100644
index 0000000000..69c02aa2cb
--- /dev/null
+++ b/lib/crypto/c_src/rsa.h
@@ -0,0 +1,31 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_RSA_H__
+#define E_RSA_H__ 1
+
+#include "common.h"
+
+int get_rsa_public_key(ErlNifEnv* env, ERL_NIF_TERM key, RSA *rsa);
+int get_rsa_private_key(ErlNifEnv* env, ERL_NIF_TERM key, RSA *rsa);
+
+ERL_NIF_TERM rsa_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_RSA_H__ */
diff --git a/lib/crypto/c_src/srp.c b/lib/crypto/c_src/srp.c
new file mode 100644
index 0000000000..2979048006
--- /dev/null
+++ b/lib/crypto/c_src/srp.c
@@ -0,0 +1,307 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 "srp.h"
+#include "bn.h"
+
+ERL_NIF_TERM srp_value_B_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Multiplier, Verifier, Generator, Exponent, Prime) */
+ BIGNUM *bn_verifier = NULL;
+ BIGNUM *bn_exponent = NULL, *bn_generator = NULL, *bn_prime = NULL, *bn_multiplier = NULL, *bn_result = NULL;
+ BN_CTX *bn_ctx = NULL;
+ unsigned char* ptr;
+ int dlen;
+ ERL_NIF_TERM ret;
+
+ CHECK_NO_FIPS_MODE();
+
+ ASSERT(argc == 5);
+
+ if (!get_bn_from_bin(env, argv[0], &bn_multiplier))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, argv[1], &bn_verifier))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, argv[2], &bn_generator))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, argv[3], &bn_exponent))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, argv[4], &bn_prime))
+ goto bad_arg;
+
+ if ((bn_result = BN_new()) == NULL)
+ goto err;
+ if ((bn_ctx = BN_CTX_new()) == NULL)
+ goto err;
+
+ /* B = k*v + g^b % N */
+
+ /* k * v */
+ if (!BN_mod_mul(bn_multiplier, bn_multiplier, bn_verifier, bn_prime, bn_ctx))
+ goto err;
+
+ /* g^b % N */
+ if (!BN_mod_exp(bn_result, bn_generator, bn_exponent, bn_prime, bn_ctx))
+ goto err;
+
+ /* k*v + g^b % N */
+ if (!BN_mod_add(bn_result, bn_result, bn_multiplier, bn_prime, bn_ctx))
+ goto err;
+
+ /* check that B % N != 0, reuse bn_multiplier */
+ if (!BN_nnmod(bn_multiplier, bn_result, bn_prime, bn_ctx))
+ goto err;
+
+ if (BN_is_zero(bn_multiplier))
+ goto err;
+
+ if ((dlen = BN_num_bytes(bn_result)) < 0)
+ goto err;
+ if ((ptr = enif_make_new_binary(env, (size_t)dlen, &ret)) == NULL)
+ goto err;
+
+ if (BN_bn2bin(bn_result, ptr) < 0)
+ goto err;
+
+ goto done;
+
+ bad_arg:
+ ret = enif_make_badarg(env);
+ goto done;
+
+ err:
+ ret = atom_error;
+
+ done:
+ if (bn_multiplier)
+ BN_free(bn_multiplier);
+ if (bn_verifier)
+ BN_free(bn_verifier);
+ if (bn_generator)
+ BN_free(bn_generator);
+ if (bn_exponent)
+ BN_free(bn_exponent);
+ if (bn_prime)
+ BN_free(bn_prime);
+ if (bn_result)
+ BN_free(bn_result);
+ if (bn_ctx)
+ BN_CTX_free(bn_ctx);
+
+ return ret;
+}
+
+ERL_NIF_TERM srp_user_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (a, u, B, Multiplier, Prime, Exponent, Generator) */
+/*
+ <premaster secret> = (B - (k * g^x)) ^ (a + (u * x)) % N
+*/
+ BIGNUM *bn_exponent = NULL, *bn_a = NULL;
+ BIGNUM *bn_u = NULL, *bn_multiplier = NULL, *bn_exp2 = NULL;
+ BIGNUM *bn_base = NULL, *bn_prime = NULL, *bn_generator = NULL;
+ BIGNUM *bn_B = NULL, *bn_result = NULL;
+ BN_CTX *bn_ctx = NULL;
+ unsigned char *ptr;
+ int dlen;
+ ERL_NIF_TERM ret;
+
+ CHECK_NO_FIPS_MODE();
+
+ ASSERT(argc == 7);
+
+ if (!get_bn_from_bin(env, argv[0], &bn_a))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, argv[1], &bn_u))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, argv[2], &bn_B))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, argv[3], &bn_multiplier))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, argv[4], &bn_generator))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, argv[5], &bn_exponent))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, argv[6], &bn_prime))
+ goto bad_arg;
+
+ if ((bn_ctx = BN_CTX_new()) == NULL)
+ goto err;
+ if ((bn_result = BN_new()) == NULL)
+ goto err;
+
+ /* check that B % N != 0 */
+ if (!BN_nnmod(bn_result, bn_B, bn_prime, bn_ctx))
+ goto err;
+ if (BN_is_zero(bn_result))
+ goto err;
+
+ /* (B - (k * g^x)) */
+ if ((bn_base = BN_new()) == NULL)
+ goto err;
+ if (!BN_mod_exp(bn_result, bn_generator, bn_exponent, bn_prime, bn_ctx))
+ goto err;
+ if (!BN_mod_mul(bn_result, bn_multiplier, bn_result, bn_prime, bn_ctx))
+ goto err;
+ if (!BN_mod_sub(bn_base, bn_B, bn_result, bn_prime, bn_ctx))
+ goto err;
+
+ /* a + (u * x) */
+ if ((bn_exp2 = BN_new()) == NULL)
+ goto err;
+ if (!BN_mul(bn_result, bn_u, bn_exponent, bn_ctx))
+ goto err;
+ if (!BN_add(bn_exp2, bn_a, bn_result))
+ goto err;
+
+ /* (B - (k * g^x)) ^ (a + (u * x)) % N */
+ if (!BN_mod_exp(bn_result, bn_base, bn_exp2, bn_prime, bn_ctx))
+ goto err;
+
+ if ((dlen = BN_num_bytes(bn_result)) < 0)
+ goto err;
+ if ((ptr = enif_make_new_binary(env, (size_t)dlen, &ret)) == NULL)
+ goto err;
+
+ if (BN_bn2bin(bn_result, ptr) < 0)
+ goto err;
+
+ goto done;
+
+ bad_arg:
+ ret = enif_make_badarg(env);
+ goto done;
+
+ err:
+ ret = atom_error;
+
+ done:
+ if (bn_a)
+ BN_free(bn_a);
+ if (bn_u)
+ BN_free(bn_u);
+ if (bn_B)
+ BN_free(bn_B);
+ if (bn_multiplier)
+ BN_free(bn_multiplier);
+ if (bn_generator)
+ BN_free(bn_generator);
+ if (bn_exponent)
+ BN_free(bn_exponent);
+ if (bn_prime)
+ BN_free(bn_prime);
+ if (bn_ctx)
+ BN_CTX_free(bn_ctx);
+ if (bn_result)
+ BN_free(bn_result);
+ if (bn_base)
+ BN_free(bn_base);
+ if (bn_exp2)
+ BN_free(bn_exp2);
+
+ return ret;
+}
+
+ERL_NIF_TERM srp_host_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Verifier, b, u, A, Prime) */
+/*
+ <premaster secret> = (A * v^u) ^ b % N
+*/
+ BIGNUM *bn_b = NULL, *bn_verifier = NULL;
+ BIGNUM *bn_prime = NULL, *bn_A = NULL, *bn_u = NULL, *bn_base = NULL, *bn_result = NULL;
+ BN_CTX *bn_ctx = NULL;
+ unsigned char *ptr;
+ int dlen;
+ ERL_NIF_TERM ret;
+
+ CHECK_NO_FIPS_MODE();
+
+ ASSERT(argc == 5);
+
+ if (!get_bn_from_bin(env, argv[0], &bn_verifier))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, argv[1], &bn_b))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, argv[2], &bn_u))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, argv[3], &bn_A))
+ goto bad_arg;
+ if (!get_bn_from_bin(env, argv[4], &bn_prime))
+ goto bad_arg;
+
+ if ((bn_ctx = BN_CTX_new()) == NULL)
+ goto err;
+ if ((bn_result = BN_new()) == NULL)
+ goto err;
+
+ /* check that A % N != 0 */
+ if (!BN_nnmod(bn_result, bn_A, bn_prime, bn_ctx))
+ goto err;
+ if (BN_is_zero(bn_result))
+ goto err;
+
+ /* (A * v^u) */
+ if ((bn_base = BN_new()) == NULL)
+ goto err;
+ if (!BN_mod_exp(bn_base, bn_verifier, bn_u, bn_prime, bn_ctx))
+ goto err;
+ if (!BN_mod_mul(bn_base, bn_A, bn_base, bn_prime, bn_ctx))
+ goto err;
+
+ /* (A * v^u) ^ b % N */
+ if (!BN_mod_exp(bn_result, bn_base, bn_b, bn_prime, bn_ctx))
+ goto err;
+
+ if ((dlen = BN_num_bytes(bn_result)) < 0)
+ goto err;
+ if ((ptr = enif_make_new_binary(env, (size_t)dlen, &ret)) == NULL)
+ goto err;
+
+ if (BN_bn2bin(bn_result, ptr) < 0)
+ goto err;
+
+ goto done;
+
+ bad_arg:
+ ret = enif_make_badarg(env);
+ goto done;
+
+ err:
+ ret = atom_error;
+
+ done:
+ if (bn_verifier)
+ BN_free(bn_verifier);
+ if (bn_b)
+ BN_free(bn_b);
+ if (bn_u)
+ BN_free(bn_u);
+ if (bn_A)
+ BN_free(bn_A);
+ if (bn_prime)
+ BN_free(bn_prime);
+ if (bn_ctx)
+ BN_CTX_free(bn_ctx);
+ if (bn_result)
+ BN_free(bn_result);
+ if (bn_base)
+ BN_free(bn_base);
+
+ return ret;
+}
+
diff --git a/lib/crypto/c_src/srp.h b/lib/crypto/c_src/srp.h
new file mode 100644
index 0000000000..c356690470
--- /dev/null
+++ b/lib/crypto/c_src/srp.h
@@ -0,0 +1,30 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-2018. All 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 E_SRP_H__
+#define E_SRP_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM srp_value_B_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM srp_user_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM srp_host_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_SRP_H__ */
diff --git a/lib/crypto/configure.in b/lib/crypto/configure.in
new file mode 100644
index 0000000000..a3b6673f29
--- /dev/null
+++ b/lib/crypto/configure.in
@@ -0,0 +1,775 @@
+dnl Process this file with autoconf to produce a configure script. -*-m4-*-
+dnl
+dnl %CopyrightBegin%
+dnl
+dnl Copyright Ericsson AB 2018. 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.
+dnl You may obtain a copy of the License at
+dnl
+dnl http://www.apache.org/licenses/LICENSE-2.0
+dnl
+dnl Unless required by applicable law or agreed to in writing, software
+dnl distributed under the License is distributed on an "AS IS" BASIS,
+dnl WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+dnl See the License for the specific language governing permissions and
+dnl limitations under the License.
+dnl
+dnl %CopyrightEnd%
+dnl
+
+dnl define([AC_CACHE_LOAD], )dnl
+dnl define([AC_CACHE_SAVE], )dnl
+
+
+AC_INIT(vsn.mk)
+
+if test -z "$ERL_TOP" || test ! -d "$ERL_TOP" ; then
+ AC_CONFIG_AUX_DIRS(autoconf)
+else
+ erl_top=${ERL_TOP}
+ AC_CONFIG_AUX_DIRS($erl_top/erts/autoconf)
+fi
+
+if test "X$host" != "Xfree_source" -a "X$host" != "Xwin32"; then
+ AC_CANONICAL_HOST
+else
+ host_os=win32
+fi
+
+LM_PRECIOUS_VARS
+
+if test "$cross_compiling" = "yes"; then
+ CROSS_COMPILING=yes
+else
+ CROSS_COMPILING=no
+fi
+AC_SUBST(CROSS_COMPILING)
+
+ERL_XCOMP_SYSROOT_INIT
+
+AC_PROG_CC
+LM_WINDOWS_ENVIRONMENT
+
+ERL_DED
+
+dnl
+dnl SSL, SSH and CRYPTO need the OpenSSL libraries
+dnl
+dnl Check flags --with-ssl, --without-ssl --with-ssl=PATH.
+dnl If no option is given or --with-ssl is set without a path then we
+dnl search for OpenSSL libraries and header files in the standard locations.
+dnl If set to --without-ssl we disable the use of SSL, SSH and CRYPTO.
+dnl If set to --with-ssl=PATH we use that path as the prefix, i.e. we
+dnl use "PATH/include" and "PATH/lib".
+
+AC_CHECK_SIZEOF(void *)
+
+std_ssl_locations="/usr/local /usr/sfw /usr /opt/local /usr/pkg /usr/local/openssl /usr/lib/openssl /usr/openssl /usr/local/ssl /usr/lib/ssl /usr/ssl /"
+
+AC_ARG_WITH(ssl-zlib,
+AS_HELP_STRING([--with-ssl-zlib=PATH],
+ [specify location of ZLib to be used by OpenSSL])
+AS_HELP_STRING([--with-ssl-zlib],
+ [link SSL with Zlib (default if found)])
+AS_HELP_STRING([--without-ssl-zlib],
+ [don't link SSL with ZLib]))
+
+if test "x$with_ssl_zlib" = "xno"; then
+ SSL_LINK_WITH_ZLIB=no
+ STATIC_ZLIB_LIBS=
+elif test "x$with_ssl_zlib" = "xyes" || test "x$with_ssl_zlib" = "x"; then
+ if test $erl_xcomp_without_sysroot = yes; then
+ AC_MSG_WARN([Cannot search for zlib; missing cross system root (erl_xcomp_sysroot).])
+ SSL_LINK_WITH_ZLIB=no
+ STATIC_ZLIB_LIBS=
+ elif test "x$MIXED_CYGWIN" = "xyes" -o "x$MIXED_MSYS" = "xyes"; then
+ SSL_LINK_WITH_ZLIB=no
+ STATIC_ZLIB_LIBS=
+ else
+ SSL_LINK_WITH_ZLIB=no
+ STATIC_ZLIB_LIBS=
+ AC_MSG_CHECKING(for static ZLib to be used by SSL in standard locations)
+ for rdir in $std_ssl_locations; do
+ dir="$erl_xcomp_sysroot$rdir"
+ if test "x$ac_cv_sizeof_void_p" = "x8"; then
+ if test -f "$dir/lib64/libz.a"; then
+ SSL_LINK_WITH_ZLIB=yes
+ STATIC_ZLIB_LIBS="$dir/lib64/libz.a"
+ break
+ elif test -f "$dir/lib/64/libz.a"; then
+ SSL_LINK_WITH_ZLIB=yes
+ STATIC_ZLIB_LIBS="$dir/lib/64/libz.a"
+ break
+ fi
+ fi
+ if test -f "$dir/lib/libz.a"; then
+ SSL_LINK_WITH_ZLIB=yes
+ STATIC_ZLIB_LIBS="$dir/lib/libz.a"
+ break
+ fi
+ done
+ if test "x$SSL_LINK_WITH_ZLIB" = "xno"; then
+ AC_MSG_RESULT([no])
+ else
+ AC_MSG_RESULT([$STATIC_ZLIB_LIBS])
+ fi
+ fi
+else
+ SSL_LINK_WITH_ZLIB=no
+ STATIC_ZLIB_LIBS=
+ if test -f "$with_ssl_zlib/libz.a"; then
+ SSL_LINK_WITH_ZLIB=yes
+ STATIC_ZLIB_LIBS=$with_ssl_zlib/libz.a
+ elif test -f "$with_ssl_zlib/lib/libz.a"; then
+ SSL_LINK_WITH_ZLIB=yes
+ STATIC_ZLIB_LIBS=$with_ssl_zlib/lib/libz.a
+ fi
+ if test "x$ac_cv_sizeof_void_p" = "x8"; then
+ if test -f "$with_ssl_zlib/lib64/libz.a"; then
+ SSL_LINK_WITH_ZLIB=yes
+ STATIC_ZLIB_LIBS=$with_ssl_zlib/lib64/libz.a
+ elif test -f "$with_ssl_zlib/lib/64/libz.a"; then
+ SSL_LINK_WITH_ZLIB=yes
+ STATIC_ZLIB_LIBS=$with_ssl_zlib/lib/64/libz.a
+ fi
+ fi
+ if test "x$SSL_LINK_WITH_ZLIB" = "xno"; then
+ AC_MSG_ERROR(Invalid path to option --with-ssl-zlib=PATH)
+ fi
+fi
+
+
+AC_ARG_WITH(ssl,
+AS_HELP_STRING([--with-ssl=PATH], [specify location of OpenSSL include and lib])
+AS_HELP_STRING([--with-ssl], [use SSL (default)])
+AS_HELP_STRING([--without-ssl], [don't use SSL]))
+
+AC_ARG_WITH(ssl-incl,
+AS_HELP_STRING([--with-ssl-incl=PATH], [location of OpenSSL include dir, if different than specified by --with-ssl=PATH]),
+[
+case X$with_ssl in
+ X | Xyes | Xno) AC_MSG_ERROR([--with-ssl-incl=PATH set without --with-ssl=PATH]);;
+esac
+],
+[with_ssl_incl=$with_ssl]) #default
+
+AC_ARG_WITH(ssl-rpath,
+AS_HELP_STRING([--with-ssl-rpath=yes|no|PATHS],
+ [runtime library path for OpenSSL. Default is "yes", which equates to a
+ number of standard locations. If "no", then no runtime
+ library paths will be used. Anything else should be a
+ comma separated list of paths.]),
+[
+case X$with_ssl in
+ Xno) AC_MSG_ERROR([--with-ssl-rpath set without --with-ssl]);;
+esac
+],
+[with_ssl_rpath=yes]) #default
+
+
+AC_ARG_ENABLE(dynamic-ssl-lib,
+AS_HELP_STRING([--disable-dynamic-ssl-lib],
+ [disable using dynamic openssl libraries]),
+[ case "$enableval" in
+ no) enable_dynamic_ssl=no ;;
+ *) enable_dynamic_ssl=yes ;;
+ esac ], enable_dynamic_ssl=yes)
+
+#----------------------------------------------------------------------
+# We actually might do the SSL tests twice due to late discovery of
+# kerberos problems with static linking, in case we redo it all trying
+# dynamic SSL libraries instead.
+#----------------------------------------------------------------------
+
+ssl_done=no
+
+while test "x$ssl_done" != "xyes"; do
+
+ssl_done=yes # Default only one run
+
+# Remove all SKIP files from previous runs
+for a in ssl crypto ssh; do
+ rm -f "$ERL_TOP/lib/$a/SKIP"
+done
+
+SSL_DYNAMIC_ONLY=$enable_dynamic_ssl
+SSL_STATIC_ONLY=no
+
+case "$erl_xcomp_without_sysroot-$with_ssl" in
+ yes-* | no-no)
+ SSL_APP=
+ CRYPTO_APP=
+ SSH_APP=
+ if test "$with_ssl" = "no"; then
+ skip="User gave --without-ssl option"
+ else
+ skip="Cannot search for ssl; missing cross system root (erl_xcomp_sysroot)."
+ fi
+ for a in ssl crypto ssh; do
+ echo "$skip" > $ERL_TOP/lib/$a/SKIP
+ done
+ ;;
+ no-yes | no- )
+ # On windows, we could try to find the installation
+ # of Shining Light OpenSSL, which can be found by poking in
+ # the uninstall section in the registry, it's worth a try...
+ extra_dir=""
+ if test "x$MIXED_CYGWIN" = "xyes"; then
+ AC_CHECK_PROG(REGTOOL, regtool, regtool, false)
+ if test "$ac_cv_prog_REGTOOL" != false; then
+ wrp="/machine/software/microsoft/windows/currentversion/"
+ if test "x$ac_cv_sizeof_void_p" = "x8"; then
+ urp="uninstall/openssl (64-bit)_is1/inno setup: app path"
+ regtool_subsystem=-w
+ else
+ urp="uninstall/openssl (32-bit)_is1/inno setup: app path"
+ regtool_subsystem=-W
+ fi
+ rp="$wrp$urp"
+ if regtool -q $regtool_subsystem get "$rp" > /dev/null; then
+ true
+ else
+ # Fallback to unspecified wordlength
+ urp="uninstall/openssl_is1/inno setup: app path"
+ rp="$wrp$urp"
+ fi
+ if regtool -q $regtool_subsystem get "$rp" > /dev/null; then
+ ssl_install_dir=`regtool -q $regtool_subsystem get "$rp"`
+ # Try hard to get rid of spaces...
+ if cygpath -d "$ssl_install_dir" > /dev/null 2>&1; then
+ ssl_install_dir=`cygpath -d "$ssl_install_dir"`
+ fi
+ extra_dir=`cygpath $ssl_install_dir`
+ fi
+ fi
+ elif test "x$MIXED_MSYS" = "xyes"; then
+ AC_CHECK_PROG(REGTOOL, reg_query.sh, reg_query.sh, false)
+ if test "$ac_cv_prog_REGTOOL" != false; then
+ if test "x$ac_cv_sizeof_void_p" = "x8"; then
+ rp="HKLM/SOFTWARE/Microsoft/Windows/CurrentVersion/Uninstall/OpenSSL (64-bit)_is1"
+ else
+ rp="HKLM/SOFTWARE/Microsoft/Windows/CurrentVersion/Uninstall/OpenSSL_is1"
+ fi
+ key="Inno Setup: App Path"
+ if "$ac_cv_prog_REGTOOL" "$rp" "$key" > /dev/null; then
+ ssl_install_dir=`"$ac_cv_prog_REGTOOL" "$rp" "$key"`
+ extra_dir=`win2msys_path.sh "$ssl_install_dir"`
+ fi
+ fi
+ fi
+ # We search for OpenSSL in the common OS standard locations.
+ SSL_APP=ssl
+ CRYPTO_APP=crypto
+ SSH_APP=ssh
+
+ SSL_CRYPTO_LIBNAME=crypto
+ SSL_SSL_LIBNAME=ssl
+
+ if test "x$MIXED_CYGWIN" = "xyes" -o "x$MIXED_MSYS" = "xyes"; then
+ if test "x$ac_cv_sizeof_void_p" = "x8"; then
+ std_win_ssl_locations="/cygdrive/c/OpenSSL-Win64 /c/OpenSSL-Win64 /opt/local64/pgm/OpenSSL"
+ else
+ std_win_ssl_locations="/cygdrive/c/OpenSSL-Win32 /c/OpenSSL-Win32 /cygdrive/c/OpenSSL /c/OpenSSL /opt/local/pgm/OpenSSL"
+ fi
+ else
+ std_win_ssl_locations=""
+ fi
+
+
+ AC_MSG_CHECKING(for OpenSSL >= 0.9.8c in standard locations)
+ for rdir in $extra_dir $std_win_ssl_locations $std_ssl_locations; do
+ dir="$erl_xcomp_sysroot$rdir"
+ if test -f "$erl_xcomp_isysroot$rdir/include/openssl/opensslv.h"; then
+ is_real_ssl=yes
+ SSL_INCDIR="$dir"
+ if test "x$MIXED_CYGWIN" = "xyes" -o "x$MIXED_MSYS" = "xyes"; then
+ if test -f "$dir/lib/VC/libeay32.lib"; then
+ SSL_RUNTIME_LIBDIR="$rdir/lib/VC"
+ SSL_LIBDIR="$dir/lib/VC"
+ SSL_CRYPTO_LIBNAME=libeay32
+ SSL_SSL_LIBNAME=ssleay32
+ elif test -f "$dir/lib/VC/openssl.lib"; then
+ SSL_RUNTIME_LIBDIR="$rdir/lib/VC"
+ SSL_LIBDIR="$dir/lib/VC"
+ elif test -f $dir/lib/VC/libeay32MD.lib; then
+ SSL_CRYPTO_LIBNAME=libeay32MD
+ SSL_SSL_LIBNAME=ssleay32MD
+ if test "x$enable_dynamic_ssl" = "xno" && \
+ test -f $dir/lib/VC/static/libeay32MD.lib; then
+ SSL_RUNTIME_LIBDIR="$rdir/lib/VC/static"
+ SSL_LIBDIR="$dir/lib/VC/static"
+ else
+ SSL_RUNTIME_LIBDIR="$rdir/lib/VC"
+ SSL_LIBDIR="$dir/lib/VC"
+ fi
+ elif test -f "$dir/lib/libeay32.lib"; then
+ SSL_RUNTIME_LIBDIR="$rdir/lib"
+ SSL_LIBDIR="$dir/lib"
+ SSL_CRYPTO_LIBNAME=libeay32
+ SSL_SSL_LIBNAME=ssleay32
+ elif test -f "$dir/lib/openssl.lib"; then
+ SSL_RUNTIME_LIBDIR="$rdir/lib"
+ SSL_LIBDIR="$dir/lib"
+ else
+ is_real_ssl=no
+ fi
+ elif test -f "$dir/lib/powerpc/libsslcrypto.a"; then
+ SSL_CRYPTO_LIBNAME=sslcrypto
+ SSL_LIBDIR="$dir/lib/powerpc/"
+ SSL_RUNTIME_LIBDIR="$rdir/lib/powerpc/"
+ else
+ if test "x$ac_cv_sizeof_void_p" = "x8"; then
+ if test -f "$dir/lib64/libcrypto.a"; then
+ SSL_RUNTIME_LIBDIR="$rdir/lib64"
+ SSL_LIBDIR="$dir/lib64"
+ elif test -f "$dir/lib/64/libcrypto.a"; then
+ SSL_RUNTIME_LIBDIR="$rdir/lib/64"
+ SSL_LIBDIR="$dir/lib/64"
+ elif test -f "$dir/lib64/libcrypto.so"; then
+ SSL_RUNTIME_LIBDIR="$rdir/lib64"
+ SSL_LIBDIR="$dir/lib64"
+ elif test -f "$dir/lib/64/libcrypto.so"; then
+ SSL_RUNTIME_LIBDIR="$rdir/lib/64"
+ SSL_LIBDIR="$dir/lib/64"
+ else
+ SSL_RUNTIME_LIBDIR="$rdir/lib"
+ SSL_LIBDIR="$dir/lib"
+ fi
+ else
+ SSL_RUNTIME_LIBDIR="$rdir/lib"
+ SSL_LIBDIR="$dir/lib"
+ fi
+ fi
+ if test '!' -f "$SSL_LIBDIR/lib${SSL_CRYPTO_LIBNAME}.a"; then
+ SSL_DYNAMIC_ONLY=yes
+ elif test '!' -f "$SSL_LIBDIR/lib${SSL_CRYPTO_LIBNAME}.so" -a '!' -f "$SSL_LIBDIR/lib${SSL_CRYPTO_LIBNAME}.dylib"; then
+ SSL_STATIC_ONLY=yes
+ fi
+ SSL_BINDIR="$rdir/bin"
+ if test "x$is_real_ssl" = "xyes" ; then
+ SSL_INCLUDE="-I$dir/include"
+ old_CPPFLAGS=$CPPFLAGS
+ CPPFLAGS=$SSL_INCLUDE
+ AC_EGREP_CPP(^yes$,[
+#include <openssl/opensslv.h>
+#if OPENSSL_VERSION_NUMBER >= 0x0090803fL
+yes
+#endif
+ ],[
+ ssl_found=yes
+ ],[
+ SSL_APP=
+ ssl_found=no
+ ])
+ CPPFLAGS=$old_CPPFLAGS
+ if test "x$ssl_found" = "xyes"; then
+ if test "x$MIXED_CYGWIN" = "xyes" -o "x$MIXED_MSYS" = "xyes"; then
+ ssl_linkable=yes
+ elif test "x${SSL_CRYPTO_LIBNAME}" = "xsslcrypto"; then
+ # This should only be triggered seen OSE
+ ssl_linkable=yes
+ else
+ saveCFLAGS="$CFLAGS"
+ saveLDFLAGS="$LDFLAGS"
+ saveLIBS="$LIBS"
+ CFLAGS="$CFLAGS $SSL_INCLUDE"
+ if test "x$SSL_STATIC_ONLY" = "xyes"; then
+ LIBS="${SSL_LIBDIR}/lib${SSL_CRYPTO_LIBNAME}.a"
+ else
+ LDFLAGS="$LDFLAGS -L$SSL_LIBDIR"
+ LIBS="$LIBS -l${SSL_CRYPTO_LIBNAME}"
+ fi
+ AC_TRY_LINK([
+ #include <stdio.h>
+ #include <openssl/hmac.h>],
+ [
+ HMAC(0, 0, 0, 0, 0, 0, 0);
+ ],
+ [ssl_linkable=yes],
+ [ssl_linkable=no])
+ CFLAGS="$saveCFLAGS"
+ LDFLAGS="$saveLDFLAGS"
+ LIBS="$saveLIBS"
+ fi
+ fi
+ if test "x$ssl_found" = "xyes" && test "x$ssl_linkable" = "xyes"; then
+ AC_MSG_RESULT([$dir])
+ break;
+ fi
+ fi
+ fi
+ done
+
+ if test "x$ssl_found" != "xyes" ; then
+ dnl
+ dnl If no SSL found above, check whether we are running on OpenBSD.
+ dnl
+ case $host_os in
+ openbsd*)
+ if test -f "$erl_xcomp_isysroot/usr/include/openssl/opensslv.h"; then
+ # Trust OpenBSD to have everything the in the correct locations.
+ ssl_found=yes
+ ssl_linkable=yes
+ SSL_INCDIR="$erl_xcomp_sysroot/usr"
+ AC_MSG_RESULT([$SSL_INCDIR])
+ SSL_RUNTIME_LIB="/usr/lib"
+ SSL_LIB="$erl_xcomp_sysroot/usr/lib"
+ SSL_BINDIR="/usr/sbin"
+ dnl OpenBSD requires us to link with -L and -l
+ SSL_DYNAMIC_ONLY="yes"
+ fi
+ ;;
+ esac
+ fi
+dnl Now, certain linuxes have a 64bit libcrypto
+dnl that cannot build shared libraries (i.e. not PIC)
+dnl One could argue that this is wrong, but
+dnl so it is - be adoptable
+ if test "$ssl_found" = "yes" && test "$ssl_linkable" = "yes" && test "$SSL_DYNAMIC_ONLY" != "yes"; then
+ case $host_os in
+ linux*)
+ saveCFLAGS="$CFLAGS"
+ saveLDFLAGS="$LDFLAGS"
+ saveLIBS="$LIBS"
+ CFLAGS="$DED_CFLAGS $SSL_INCLUDE"
+ LDFLAGS="$DED_LDFLAGS"
+ LIBS="$SSL_LIBDIR/libcrypto.a $STATIC_ZLIB_LIBS"
+ AC_TRY_LINK([
+ #include <stdio.h>
+ #include <openssl/hmac.h>],
+ [
+ HMAC(0, 0, 0, 0, 0, 0, 0);
+ ],
+ [ssl_dyn_linkable=yes],
+ [ssl_dyn_linkable=no])
+ CFLAGS="$saveCFLAGS"
+ LDFLAGS="$saveLDFLAGS"
+ LIBS="$saveLIBS"
+ if test "x$ssl_dyn_linkable" != "xyes"; then
+ SSL_DYNAMIC_ONLY=yes
+ AC_MSG_WARN([SSL will be linked against dynamic lib as static lib is not purely relocatable])
+ fi
+ ;;
+ esac
+ fi
+
+
+
+
+ if test "x$ssl_found" != "xyes" || test "x$ssl_linkable" != "xyes"; then
+ if test "x$ssl_found" = "xyes"; then
+ AC_MSG_RESULT([found; but not usable])
+ else
+ AC_MSG_RESULT([no])
+ fi
+ SSL_APP=
+ CRYPTO_APP=
+ SSH_APP=
+ AC_MSG_WARN([No (usable) OpenSSL found, skipping ssl, ssh and crypto applications])
+
+ for a in ssl crypto ssh; do
+ echo "No usable OpenSSL found" > $ERL_TOP/lib/$a/SKIP
+ done
+ fi
+ ;;
+ *)
+ # Option given with PATH to package
+ if test ! -d "$with_ssl" ; then
+ AC_MSG_ERROR(Invalid path to option --with-ssl=PATH)
+ fi
+ if test ! -d "$with_ssl_incl" ; then
+ AC_MSG_ERROR(Invalid path to option --with-ssl-incl=PATH)
+ fi
+ SSL_INCDIR="$with_ssl_incl"
+ SSL_CRYPTO_LIBNAME=crypto
+ SSL_SSL_LIBNAME=ssl
+ if test "x$MIXED_CYGWIN" = "xyes" -o "x$MIXED_MSYS" = "xyes" && test -d "$with_ssl/lib/VC"; then
+ if test -f "$with_ssl/lib/VC/libeay32.lib"; then
+ SSL_LIBDIR="$with_ssl/lib/VC"
+ SSL_CRYPTO_LIBNAME=libeay32
+ SSL_SSL_LIBNAME=ssleay32
+ elif test -f "$with_ssl/lib/VC/openssl.lib"; then
+ SSL_LIBDIR="$with_ssl/lib/VC"
+ elif test -f $with_ssl/lib/VC/libeay32MD.lib; then
+ SSL_CRYPTO_LIBNAME=libeay32MD
+ SSL_SSL_LIBNAME=ssleay32MD
+ if test "x$enable_dynamic_ssl" = "xno" && \
+ test -f $with_ssl/lib/VC/static/libeay32MD.lib; then
+ SSL_LIBDIR="$with_ssl/lib/VC/static"
+ else
+ SSL_LIBDIR="$with_ssl/lib/VC"
+ fi
+ elif test -f "$with_ssl/lib/libeay32.lib"; then
+ SSL_LIBDIR="$with_ssl/lib"
+ SSL_CRYPTO_LIBNAME=libeay32
+ SSL_SSL_LIBNAME=ssleay32
+ else
+ # This probably wont work, but that's what the user said, so...
+ SSL_LIBDIR="$with_ssl/lib"
+ fi
+ elif test -f "$dir/lib/powerpc/libsslcrypto.a"; then
+ SSL_CRYPTO_LIBNAME=sslcrypto
+ SSL_LIBDIR="$with_ssl/lib/powerpc/"
+ elif test "x$ac_cv_sizeof_void_p" = "x8"; then
+ if test -f "$with_ssl/lib64/libcrypto.a"; then
+ SSL_LIBDIR="$with_ssl/lib64"
+ elif test -f "$with_ssl/lib/64/libcrypto.a"; then
+ SSL_LIBDIR="$with_ssl/lib/64"
+ elif test -f "$with_ssl/lib64/libcrypto.so"; then
+ SSL_LIBDIR="$with_ssl/lib64"
+ elif test -f "$with_ssl/lib/64/libcrypto.so"; then
+ SSL_LIBDIR="$with_ssl/lib/64"
+ else
+ SSL_LIBDIR="$with_ssl/lib"
+ fi
+ else
+ SSL_LIBDIR="$with_ssl/lib"
+ fi
+ if test '!' -f "${SSL_LIBDIR}/lib${SSL_CRYPTO_LIBNAME}.a"; then
+ SSL_DYNAMIC_ONLY=yes
+ elif test '!' -f ${SSL_LIBDIR}/lib${SSL_CRYPTO_LIBNAME}.so -a '!' -f "$SSL_LIBDIR/lib${SSL_CRYPTO_LIBNAME}.dylib"; then
+ SSL_STATIC_ONLY=yes
+ fi
+ SSL_INCLUDE="-I$with_ssl_incl/include"
+ SSL_APP=ssl
+ CRYPTO_APP=crypto
+ SSH_APP=ssh
+ if test "$cross_compiling" = "yes"; then
+ SSL_RUNTIME_LIBDIR=`echo "$SSL_LIBDIR" | sed -n "s|^$erl_xcomp_sysroot\(/*\)\(.*\)\$|/\2|p"`
+ else
+ SSL_RUNTIME_LIBDIR="$SSL_LIBDIR"
+ fi
+esac
+
+if test "x$SSL_APP" != "x" ; then
+ dnl We found openssl, now check if we use kerberos 5 support
+ dnl FIXME: Do we still support platforms that have Kerberos?
+ AC_MSG_CHECKING(for OpenSSL kerberos 5 support)
+ old_CPPFLAGS=$CPPFLAGS
+ CPPFLAGS=$SSL_INCLUDE
+ AC_EGREP_CPP(^yes$,[
+#include <openssl/opensslv.h>
+#include <openssl/opensslconf.h>
+#if OPENSSL_VERSION_NUMBER < 0x1010000fL && !defined(OPENSSL_NO_KRB5)
+yes
+#endif
+ ],[
+ AC_MSG_RESULT([yes])
+ ssl_krb5_enabled=yes
+ if test "x$SSL_DYNAMIC_ONLY" != "xyes"; then
+ if test -f "$SSL_LIBDIR/libkrb5.a"; then
+ SSL_LINK_WITH_KERBEROS=yes
+ STATIC_KERBEROS_LIBS="$SSL_LIBDIR/libkrb5.a"
+ if test -f "$SSL_LIBDIR/libkrb5support.a"; then
+ STATIC_KERBEROS_LIBS="$STATIC_KERBEROS_LIBS $SSL_LIBDIR/libkrb5support.a"
+ fi
+ if test -f "$SSL_LIBDIR/libk5crypto.a"; then
+ STATIC_KERBEROS_LIBS="$STATIC_KERBEROS_LIBS $SSL_LIBDIR/libk5crypto.a"
+ fi
+ if test -f "$SSL_LIBDIR/libresolv.a"; then
+ STATIC_KERBEROS_LIBS="$STATIC_KERBEROS_LIBS $SSL_LIBDIR/libresolv.a"
+ fi
+ if test -f "$SSL_LIBDIR/libcom_err.a"; then
+ STATIC_KERBEROS_LIBS="$STATIC_KERBEROS_LIBS $SSL_LIBDIR/libcom_err.a"
+ fi
+ else
+ AC_MSG_WARN([Kerberos needed but no kerberos static libraries found])
+ AC_MSG_WARN([Rescanning for dynamic SSL libraries])
+ enable_dynamic_ssl=yes
+ ssl_done=no
+ SSL_LINK_WITH_KERBEROS=no
+ STATIC_KERBEROS_LIBS=""
+ ssl_krb5_enabled=no
+ SSL_WITH_KERBEROS=no
+ fi
+ else
+ SSL_LINK_WITH_KERBEROS=no
+ STATIC_KERBEROS_LIBS=""
+ fi
+ ],[
+ AC_MSG_RESULT([no])
+ ssl_krb5_enabled=no
+ SSL_WITH_KERBEROS=no
+ ])
+ CPPFLAGS=$old_CPPFLAGS
+ SSL_KRB5_INCLUDE=
+ if test "x$ssl_krb5_enabled" = "xyes" ; then
+ AC_MSG_CHECKING(for krb5.h in standard locations)
+ for dir in $extra_dir "$SSL_INCDIR/include" "$SSL_INCDIR/include/openssl" \
+ "$SSL_INCDIR/include/kerberos" \
+ "$erl_xcomp_isysroot/cygdrive/c/kerberos/include" \
+ "$erl_xcomp_isysroot/usr/local/kerberos/include" \
+ "$erl_xcomp_isysroot/usr/kerberos/include" \
+ "$erl_xcomp_isysroot/usr/include"
+ do
+ if test -f "$dir/krb5.h" ; then
+ SSL_KRB5_INCLUDE="$dir"
+ break
+ fi
+ done
+ if test "x$SSL_KRB5_INCLUDE" = "x" ; then
+ AC_MSG_RESULT([not found])
+ SSL_APP=
+ CRYPTO_APP=
+ SSH_APP=
+ AC_MSG_WARN([OpenSSL is configured for kerberos but no krb5.h found])
+ for a in ssl crypto ssh ; do
+ echo "OpenSSL is configured for kerberos but no krb5.h found" > $ERL_TOP/lib/$a/SKIP
+ done
+ else
+ AC_MSG_RESULT([found in $SSL_KRB5_INCLUDE])
+ SSL_INCLUDE="$SSL_INCLUDE -I$SSL_KRB5_INCLUDE"
+ fi
+ fi
+fi
+
+done # while test ssl_done != yes
+
+SSL_DED_LD_RUNTIME_LIBRARY_PATH=
+ded_ld_rflg="$DED_LD_FLAG_RUNTIME_LIBRARY_PATH"
+
+
+case "$with_ssl_rpath" in
+
+yes) # Use standard lib locations for ssl runtime library path
+
+ if test "$SSL_APP" != "" && test "$SSL_DYNAMIC_ONLY" = "yes" && test "$ded_ld_rflg" != ""; then
+
+ AC_MSG_CHECKING(for ssl runtime library path to use)
+
+ libdirs="/lib"
+
+ if test "$ac_cv_sizeof_void_p" = "8"; then
+ dir_lib64=no
+ dir_lib_64=no
+
+ case "$SSL_RUNTIME_LIBDIR" in
+ */lib/64 | */lib/64/ ) dir_lib_64=yes;;
+ */lib64 | */lib64/ ) dir_lib64=yes;;
+ *) ;;
+ esac
+
+ for dir in $std_ssl_locations; do
+ test $dir_lib_64 = no &&
+ test -d "$erl_xcomp_sysroot$dir/lib/64" &&
+ dir_lib_64=yes
+ test $dir_lib64 = no &&
+ test -d "$erl_xcomp_sysroot$dir/lib64" &&
+ dir_lib64=yes
+ done
+
+ test $dir_lib_64 = yes && libdirs="/lib/64 $libdirs"
+ test $dir_lib64 = yes && libdirs="/lib64 $libdirs"
+ fi
+
+ for type in std x_std curr; do
+
+ ded_ld_rpath="$ded_ld_rflg$SSL_RUNTIME_LIBDIR"
+ rpath="$SSL_RUNTIME_LIBDIR"
+
+ if test $type != curr; then
+ for ldir in $libdirs; do
+ for dir in $std_ssl_locations; do
+ test "$SSL_LIBDIR" != "$dir$ldir" || continue
+ test $type != x_std || test -d "$dir$ldir" || continue
+ if test "$dir" = "/"; then
+ libdir="$ldir"
+ else
+ libdir="$dir$ldir"
+ fi
+ ded_ld_rpath="$ded_ld_rpath $ded_ld_rflg$libdir"
+ rpath="$rpath:$libdir"
+ done
+ done
+ fi
+
+ saveCFLAGS="$CFLAGS"
+ saveLDFLAGS="$LDFLAGS"
+ saveLIBS="$LIBS"
+ CFLAGS="$CFLAGS $SSL_INCLUDE"
+ LDFLAGS="$LDFLAGS $ld_rpath -L$SSL_LIBDIR"
+ LIBS="-lcrypto"
+ AC_TRY_LINK([
+ #include <stdio.h>
+ #include <openssl/hmac.h>
+ ],
+ [
+ HMAC(0, 0, 0, 0, 0, 0, 0);
+ ],
+ [rpath_success=yes],
+ [rpath_success=no])
+ CFLAGS="$saveCFLAGS"
+ LDFLAGS="$saveLDFLAGS"
+ LIBS="$saveLIBS"
+
+ test "$rpath_success" = "yes" && break
+ done
+
+ test "$rpath_success" = "yes" || { ded_ld_rpath=; rpath=; }
+
+ SSL_DED_LD_RUNTIME_LIBRARY_PATH="$ded_ld_rpath"
+
+ AC_MSG_RESULT([$rpath])
+ test "$rpath" != "" || AC_MSG_WARN([Cannot set run path during linking])
+ fi
+ ;;
+
+no) # Use no ssl runtime library path
+ SSL_DED_LD_RUNTIME_LIBRARY_PATH=
+ ;;
+
+*) # Use ssl runtime library paths set by --with-ssl-rpath (without any check)
+ ded_ld_rpath=
+ delimit=
+ for dir in `echo $with_ssl_rpath | sed "s/,/ /g"`; do
+ ded_ld_rpath="$ded_ld_rpath$delimit$ded_ld_rflg$dir"
+ delimit=" "
+ done
+ SSL_DED_LD_RUNTIME_LIBRARY_PATH="$ded_ld_rpath"
+ ;;
+
+esac
+
+
+AC_ARG_ENABLE(fips,
+AS_HELP_STRING([--enable-fips], [enable OpenSSL FIPS mode support])
+AS_HELP_STRING([--disable-fips], [disable OpenSSL FIPS mode support (default)]),
+[ case "$enableval" in
+ yes) enable_fips_support=yes ;;
+ *) enable_fips_support=no ;;
+ esac ], enable_fips_support=no)
+
+if test "x$enable_fips_support" = "xyes" && test "$CRYPTO_APP" != ""; then
+ saveCFLAGS="$CFLAGS"
+ saveLDFLAGS="$LDFLAGS"
+ saveLIBS="$LIBS"
+ CFLAGS="$CFLAGS $SSL_INCLUDE"
+ LDFLAGS="$LDFLAGS $ded_ld_rpath -L$SSL_LIBDIR"
+ LIBS="-lcrypto"
+ AC_CHECK_FUNC([FIPS_mode_set],
+ [SSL_FLAGS="-DFIPS_SUPPORT"],
+ [SSL_FLAGS=])
+ CFLAGS="$saveCFLAGS"
+ LDFLAGS="$saveLDFLAGS"
+ LIBS="$saveLIBS"
+else
+ SSL_FLAGS=
+fi
+
+AC_SUBST(SSL_INCLUDE)
+AC_SUBST(SSL_INCDIR)
+AC_SUBST(SSL_LIBDIR)
+AC_SUBST(SSL_FLAGS)
+AC_SUBST(SSL_CRYPTO_LIBNAME)
+AC_SUBST(SSL_SSL_LIBNAME)
+AC_SUBST(SSL_DED_LD_RUNTIME_LIBRARY_PATH)
+AC_SUBST(SSL_DYNAMIC_ONLY)
+AC_SUBST(SSL_LINK_WITH_KERBEROS)
+AC_SUBST(STATIC_KERBEROS_LIBS)
+AC_SUBST(SSL_LINK_WITH_ZLIB)
+AC_SUBST(STATIC_ZLIB_LIBS)
+
+AC_OUTPUT(c_src/$host/Makefile:c_src/Makefile.in)
+
diff --git a/lib/crypto/doc/src/crypto.xml b/lib/crypto/doc/src/crypto.xml
index b33db0d6e4..83e10c4c78 100644
--- a/lib/crypto/doc/src/crypto.xml
+++ b/lib/crypto/doc/src/crypto.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE erlref SYSTEM "erlref.dtd">
<erlref>
@@ -23,7 +22,7 @@
<title>crypto</title>
</header>
- <module>crypto</module>
+ <module since="">crypto</module>
<modulesummary>Crypto Functions</modulesummary>
<description>
<p>This module provides a set of cryptographic functions.
@@ -45,6 +44,10 @@
SHA-3 Standard: Permutation-Based Hash and Extendable-Output Functions [FIPS PUB 202]
</url>
</item>
+ <tag>BLAKE2</tag>
+ <item>
+ <url href="https://blake2.net/">BLAKE2 — fast secure hashing</url>
+ </item>
<tag>MD5</tag>
<item>
<url href="http://www.ietf.org/rfc/rfc1321.txt">The MD5 Message Digest Algorithm [RFC 1321]</url>
@@ -190,10 +193,12 @@
<datatype_title>Ciphers</datatype_title>
<datatype>
<name name="stream_cipher"/>
+ <name name="stream_cipher_iv"/>
+ <name name="stream_cipher_no_iv"/>
<desc>
<p>Stream ciphers for
- <seealso marker="#stream_encrypt-2">stream_encrypt/2</seealso> and
- <seealso marker="#stream_decrypt-2">stream_decrypt/2</seealso> .
+ <seealso marker="#stream_init-3">stream_init/3</seealso> and
+ <seealso marker="#stream_init-2">stream_init/2</seealso> .
</p>
</desc>
</datatype>
@@ -211,6 +216,18 @@
</datatype>
<datatype>
+ <name name="alias_cfb"/>
+ <name name="alias_cbc"/>
+ <desc>
+ <p>Names that are replaced by more common names. They may deprecated in futer releases.</p>
+ <p><c>des3_cbc</c> and <c>des_ede3</c> should be replaced by <c>des_ede3_cbc</c></p>
+ <p><c>des_ede3_cbf</c>, <c>des3_cbf</c> and <c>des3_cfb</c> should be replaced by <c>des_ede3_cfb</c>.</p>
+ <p><c>aes_cbc128</c> should be replaced by <c>aes_128_cbc</c>.</p>
+ <p><c>aes_cbc256</c> should be replaced by <c>aes_256_cbc</c>.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
<name name="block_cipher_without_iv"/>
<name name="ecb_cipher"/>
<desc>
@@ -236,6 +253,7 @@
<name name="sha1"/>
<name name="sha2"/>
<name name="sha3"/>
+ <name name="blake2"/>
<desc>
</desc>
</datatype>
@@ -524,7 +542,7 @@
<!--================ FUNCTIONS ================-->
<funcs>
<func>
- <name name="block_encrypt" arity="3"/>
+ <name name="block_encrypt" arity="3" since="OTP 18.0"/>
<fsummary>Encrypt <c>PlainText</c> according to <c>Type</c> block cipher</fsummary>
<desc>
<p>Encrypt <c>PlainText</c> according to <c>Type</c> block cipher.</p>
@@ -537,7 +555,7 @@
</func>
<func>
- <name name="block_decrypt" arity="3"/>
+ <name name="block_decrypt" arity="3" since="OTP 18.0"/>
<fsummary>Decrypt <c>CipherText</c> according to <c>Type</c> block cipher</fsummary>
<desc>
<p>Decrypt <c>CipherText</c> according to <c>Type</c> block cipher.</p>
@@ -550,9 +568,9 @@
</func>
<func>
- <name>block_encrypt(Type, Key, Ivec, PlainText) -> CipherText</name>
- <name>block_encrypt(AeadType, Key, Ivec, {AAD, PlainText}) -> {CipherText, CipherTag}</name>
- <name>block_encrypt(aes_gcm | aes_ccm, Key, Ivec, {AAD, PlainText, TagLength}) -> {CipherText, CipherTag}</name>
+ <name since="OTP R16B01">block_encrypt(Type, Key, Ivec, PlainText) -> CipherText</name>
+ <name since="OTP R16B01">block_encrypt(AeadType, Key, Ivec, {AAD, PlainText}) -> {CipherText, CipherTag}</name>
+ <name since="OTP R16B01">block_encrypt(aes_gcm | aes_ccm, Key, Ivec, {AAD, PlainText, TagLength}) -> {CipherText, CipherTag}</name>
<fsummary>Encrypt <c>PlainText</c> according to <c>Type</c> block cipher</fsummary>
<type>
<v>Type = <seealso marker="#type-block_cipher_with_iv">block_cipher_with_iv()</seealso></v>
@@ -577,8 +595,8 @@
</func>
<func>
- <name>block_decrypt(Type, Key, Ivec, CipherText) -> PlainText</name>
- <name>block_decrypt(AeadType, Key, Ivec, {AAD, CipherText, CipherTag}) -> PlainText | error</name>
+ <name since="OTP R16B01">block_decrypt(Type, Key, Ivec, CipherText) -> PlainText</name>
+ <name since="OTP R16B01">block_decrypt(AeadType, Key, Ivec, {AAD, CipherText, CipherTag}) -> PlainText | error</name>
<fsummary>Decrypt <c>CipherText</c> according to <c>Type</c> block cipher</fsummary>
<type>
<v>Type = <seealso marker="#type-block_cipher_with_iv">block_cipher_with_iv()</seealso></v>
@@ -603,7 +621,7 @@
</func>
<func>
- <name name="bytes_to_integer" arity="1"/>
+ <name name="bytes_to_integer" arity="1" since="OTP R16B01"/>
<fsummary>Convert binary representation, of an integer, to an Erlang integer.</fsummary>
<desc>
<p>Convert binary representation, of an integer, to an Erlang integer.
@@ -612,7 +630,7 @@
</func>
<func>
- <name name="compute_key" arity="4"/>
+ <name name="compute_key" arity="4" since="OTP R16B01"/>
<fsummary>Computes the shared secret</fsummary>
<desc>
<p>Computes the shared secret from the private key and the other party's public key.
@@ -622,7 +640,7 @@
</func>
<func>
- <name name="exor" arity="2"/>
+ <name name="exor" arity="2" since=""/>
<fsummary>XOR data</fsummary>
<desc>
<p>Performs bit-wise XOR (exclusive or) on the data supplied.</p>
@@ -631,8 +649,8 @@
<func>
- <name name="generate_key" arity="2"/>
- <name name="generate_key" arity="3"/>
+ <name name="generate_key" arity="2" since="OTP R16B01"/>
+ <name name="generate_key" arity="3" since="OTP R16B01"/>
<fsummary>Generates a public key of type <c>Type</c></fsummary>
<desc>
<p>Generates a public key of type <c>Type</c>.
@@ -653,7 +671,7 @@
</func>
<func>
- <name name="hash" arity="2"/>
+ <name name="hash" arity="2" since="OTP R15B02"/>
<fsummary></fsummary>
<desc>
<p>Computes a message digest of type <c>Type</c> from <c>Data</c>.</p>
@@ -663,7 +681,7 @@
</func>
<func>
- <name name="hash_init" arity="1"/>
+ <name name="hash_init" arity="1" since="OTP R15B02"/>
<fsummary></fsummary>
<desc>
<p>Initializes the context for streaming hash operations. <c>Type</c> determines
@@ -675,7 +693,7 @@
</func>
<func>
- <name name="hash_update" arity="2"/>
+ <name name="hash_update" arity="2" since="OTP R15B02"/>
<fsummary></fsummary>
<desc>
<p>Updates the digest represented by <c>Context</c> using the given <c>Data</c>. <c>Context</c>
@@ -687,7 +705,7 @@
</func>
<func>
- <name name="hash_final" arity="1"/>
+ <name name="hash_final" arity="1" since="OTP R15B02"/>
<fsummary></fsummary>
<desc>
<p>Finalizes the hash operation referenced by <c>Context</c> returned
@@ -698,8 +716,8 @@
</func>
<func>
- <name name="hmac" arity="3"/>
- <name name="hmac" arity="4"/>
+ <name name="hmac" arity="3" since="OTP R16B"/>
+ <name name="hmac" arity="4" since="OTP R16B"/>
<fsummary></fsummary>
<desc>
<p>Computes a HMAC of type <c>Type</c> from <c>Data</c> using
@@ -709,7 +727,7 @@
</func>
<func>
- <name name="hmac_init" arity="2"/>
+ <name name="hmac_init" arity="2" since="OTP R14B03"/>
<fsummary></fsummary>
<desc>
<p>Initializes the context for streaming HMAC operations. <c>Type</c> determines
@@ -719,7 +737,7 @@
</func>
<func>
- <name name="hmac_update" arity="2"/>
+ <name name="hmac_update" arity="2" since="OTP R14B03"/>
<fsummary></fsummary>
<desc>
<p>Updates the HMAC represented by <c>Context</c> using the given <c>Data</c>. <c>Context</c>
@@ -738,7 +756,7 @@
</func>
<func>
- <name name="hmac_final" arity="1"/>
+ <name name="hmac_final" arity="1" since="OTP R14B03"/>
<fsummary></fsummary>
<desc>
<p>Finalizes the HMAC operation referenced by <c>Context</c>. The size of the resultant MAC is
@@ -747,7 +765,7 @@
</func>
<func>
- <name name="hmac_final_n" arity="2"/>
+ <name name="hmac_final_n" arity="2" since="OTP R14B03"/>
<fsummary></fsummary>
<desc>
<p>Finalizes the HMAC operation referenced by <c>Context</c>. <c>HashLen</c> must be greater than
@@ -756,8 +774,8 @@
</func>
<func>
- <name name="cmac" arity="3"/>
- <name name="cmac" arity="4"/>
+ <name name="cmac" arity="3" since="OTP 20.0"/>
+ <name name="cmac" arity="4" since="OTP 20.0"/>
<fsummary>Calculates the Cipher-based Message Authentication Code.</fsummary>
<desc>
<p>Computes a CMAC of type <c>Type</c> from <c>Data</c> using
@@ -767,7 +785,7 @@
</func>
<func>
- <name name="info_fips" arity="0"/>
+ <name name="info_fips" arity="0" since="OTP 20.0"/>
<fsummary>Provides information about the FIPS operating status.</fsummary>
<desc>
<p>Provides information about the FIPS operating status of
@@ -790,7 +808,7 @@
</func>
<func>
- <name name="enable_fips_mode" arity="1"/>
+ <name name="enable_fips_mode" arity="1" since="OTP 21.1"/>
<fsummary>Change FIPS mode.</fsummary>
<desc>
<p>Enables (<c>Enable = true</c>) or disables (<c>Enable = false</c>) FIPS mode. Returns <c>true</c> if
@@ -805,7 +823,7 @@
</func>
<func>
- <name name="info_lib" arity="0"/>
+ <name name="info_lib" arity="0" since=""/>
<fsummary>Provides information about the libraries used by crypto.</fsummary>
<desc>
<p>Provides the name and version of the libraries used by crypto.</p>
@@ -826,7 +844,7 @@
</func>
<func>
- <name name="mod_pow" arity="3"/>
+ <name name="mod_pow" arity="3" since="OTP R16B01"/>
<fsummary>Computes the function: N^P mod M</fsummary>
<desc>
<p>Computes the function <c>N^P mod M</c>.</p>
@@ -834,8 +852,8 @@
</func>
<func>
- <name name="next_iv" arity="2"/>
- <name name="next_iv" arity="3"/>
+ <name name="next_iv" arity="2" since="OTP R16B01"/>
+ <name name="next_iv" arity="3" since="OTP R16B01"/>
<fsummary></fsummary>
<desc>
<p>Returns the initialization vector to be used in the next
@@ -847,7 +865,7 @@
</func>
<func>
- <name name="poly1305" arity="2"/>
+ <name name="poly1305" arity="2" since="OTP 21.1"/>
<fsummary></fsummary>
<desc>
<p>Computes a POLY1305 message authentication code (<c>Mac</c>) from <c>Data</c> using
@@ -856,7 +874,7 @@
</func>
<func>
- <name name="private_decrypt" arity="4"/>
+ <name name="private_decrypt" arity="4" since="OTP R16B01"/>
<fsummary>Decrypts CipherText using the private Key.</fsummary>
<desc>
<p>Decrypts the <c>CipherText</c>, encrypted with
@@ -870,7 +888,7 @@
</func>
<func>
- <name name="private_encrypt" arity="4"/>
+ <name name="private_encrypt" arity="4" since="OTP R16B01"/>
<fsummary>Encrypts PlainText using the private Key.</fsummary>
<desc>
<p>Encrypts the <c>PlainText</c> using the <c>PrivateKey</c>
@@ -883,7 +901,7 @@
</func>
<func>
- <name name="public_decrypt" arity="4"/>
+ <name name="public_decrypt" arity="4" since="OTP R16B01"/>
<fsummary>Decrypts CipherText using the public Key.</fsummary>
<desc>
<p>Decrypts the <c>CipherText</c>, encrypted with
@@ -897,7 +915,7 @@
</func>
<func>
- <name name="public_encrypt" arity="4"/>
+ <name name="public_encrypt" arity="4" since="OTP R16B01"/>
<fsummary>Encrypts PlainText using the public Key.</fsummary>
<desc>
<p>Encrypts the <c>PlainText</c> (message digest) using the <c>PublicKey</c>
@@ -909,7 +927,7 @@
</func>
<func>
- <name name="rand_seed" arity="1"/>
+ <name name="rand_seed" arity="1" since="OTP 17.0"/>
<fsummary>Set the seed for random bytes generation</fsummary>
<desc>
<p>Set the seed for PRNG to the given binary. This calls the
@@ -922,7 +940,7 @@
</func>
<func>
- <name>rand_uniform(Lo, Hi) -> N</name>
+ <name since="">rand_uniform(Lo, Hi) -> N</name>
<fsummary>Generate a random number</fsummary>
<type>
<v>Lo, Hi, N = integer()</v>
@@ -935,7 +953,7 @@
</func>
<func>
- <name name="start" arity="0"/>
+ <name name="start" arity="0" since=""/>
<fsummary> Equivalent to application:start(crypto). </fsummary>
<desc>
<p> Equivalent to application:start(crypto).</p>
@@ -943,7 +961,7 @@
</func>
<func>
- <name name="stop" arity="0"/>
+ <name name="stop" arity="0" since=""/>
<fsummary> Equivalent to application:stop(crypto).</fsummary>
<desc>
<p> Equivalent to application:stop(crypto).</p>
@@ -951,7 +969,7 @@
</func>
<func>
- <name name="strong_rand_bytes" arity="1"/>
+ <name name="strong_rand_bytes" arity="1" since="OTP R14B03"/>
<fsummary>Generate a binary of random bytes</fsummary>
<desc>
<p>Generates N bytes randomly uniform 0..255, and returns the
@@ -964,7 +982,7 @@
</func>
<func>
- <name name="rand_seed" arity="0"/>
+ <name name="rand_seed" arity="0" since="OTP 20.0"/>
<fsummary>Strong random number generation plugin state</fsummary>
<desc>
<p>
@@ -992,7 +1010,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name name="rand_seed_s" arity="0"/>
+ <name name="rand_seed_s" arity="0" since="OTP 20.0"/>
<fsummary>Strong random number generation plugin state</fsummary>
<desc>
<p>
@@ -1011,7 +1029,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</p>
<note>
<p>
- The state returned from this function can not be used
+ The state returned from this function cannot be used
to get a reproducable random sequence as from
the other
<seealso marker="stdlib:rand">rand</seealso>
@@ -1027,7 +1045,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name>rand_seed_alg(Alg) -> rand:state()</name>
+ <name since="OTP 21.0">rand_seed_alg(Alg) -> rand:state()</name>
<fsummary>Strong random number generation plugin state</fsummary>
<type>
<v>Alg = crypto | crypto_cache</v>
@@ -1037,7 +1055,8 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
<p>
Creates state object for
<seealso marker="stdlib:rand">random number generation</seealso>,
- in order to generate cryptographically strong random numbers.
+ in order to generate cryptographically strong random numbers,
+ and saves it in the process dictionary before returning it as well.
See also
<seealso marker="stdlib:rand#seed-1">rand:seed/1</seealso> and
<seealso marker="#rand_seed_alg_s-1">rand_seed_alg_s/1</seealso>.
@@ -1048,12 +1067,6 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
may raise exception <c>error:low_entropy</c> in case the random generator
failed due to lack of secure "randomness".
</p>
- <p>
- The cache size can be changed from its default value using the
- <seealso marker="crypto_app">
- crypto app's
- </seealso> configuration parameter <c>rand_cache_size</c>.
- </p>
<p><em>Example</em></p>
<pre>
_ = crypto:rand_seed_alg(crypto_cache),
@@ -1063,7 +1076,35 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name>rand_seed_alg_s(Alg) -> rand:state()</name>
+ <name since="OTP-22.0">rand_seed_alg(Alg, Seed) -> rand:state()</name>
+ <fsummary>Strong random number generation plugin state</fsummary>
+ <type>
+ <v>Alg = crypto_aes</v>
+ </type>
+ <desc>
+ <marker id="rand_seed_alg-2" />
+ <p>
+ Creates a state object for
+ <seealso marker="stdlib:rand">random number generation</seealso>,
+ in order to generate cryptographically unpredictable random numbers,
+ and saves it in the process dictionary before returning it as well.
+ See also
+ <seealso marker="#rand_seed_alg_s-2">rand_seed_alg_s/2</seealso>.
+ </p>
+ <p><em>Example</em></p>
+ <pre>
+_ = crypto:rand_seed_alg(crypto_aes, "my seed"),
+IntegerValue = rand:uniform(42), % [1; 42]
+FloatValue = rand:uniform(), % [0.0; 1.0[
+_ = crypto:rand_seed_alg(crypto_aes, "my seed"),
+IntegerValue = rand:uniform(42), % Same values
+FloatValue = rand:uniform(). % again
+ </pre>
+ </desc>
+ </func>
+
+ <func>
+ <name since="OTP 21.0">rand_seed_alg_s(Alg) -> rand:state()</name>
<fsummary>Strong random number generation plugin state</fsummary>
<type>
<v>Alg = crypto | crypto_cache</v>
@@ -1099,9 +1140,15 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
crypto app's
</seealso> configuration parameter <c>rand_cache_size</c>.
</p>
+ <p>
+ When using the state object from this function the
+ <seealso marker="stdlib:rand">rand</seealso> functions using it
+ may throw exception <c>low_entropy</c> in case the random generator
+ failed due to lack of secure "randomness".
+ </p>
<note>
<p>
- The state returned from this function can not be used
+ The state returned from this function cannot be used
to get a reproducable random sequence as from
the other
<seealso marker="stdlib:rand">rand</seealso>
@@ -1121,7 +1168,73 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name name="stream_init" arity="2"/>
+ <name since="OTP 22.0">rand_seed_alg_s(Alg, Seed) -> rand:state()</name>
+ <fsummary>Strong random number generation plugin state</fsummary>
+ <type>
+ <v>Alg = crypto_aes</v>
+ </type>
+ <desc>
+ <marker id="rand_seed_alg_s-2" />
+ <p>
+ Creates a state object for
+ <seealso marker="stdlib:rand">random number generation</seealso>,
+ in order to generate cryptographically unpredictable random numbers.
+ See also
+ <seealso marker="#rand_seed_alg-1">rand_seed_alg/1</seealso>.
+ </p>
+ <p>
+ To get a long period the Xoroshiro928 generator from the
+ <seealso marker="stdlib:rand">rand</seealso>
+ module is used as a counter (with period 2^928 - 1)
+ and the generator states are scrambled through AES
+ to create 58-bit pseudo random values.
+ </p>
+ <p>
+ The result should be statistically completely unpredictable
+ random values, since the scrambling is cryptographically strong
+ and the period is ridiculously long. But the generated numbers
+ are not to be regarded as cryptographically strong since
+ there is no re-keying schedule.
+ </p>
+ <list type="bulleted">
+ <item>
+ <p>
+ If you need cryptographically strong random numbers use
+ <seealso marker="#rand_seed_alg_s-1">rand_seed_alg_s/1</seealso>
+ with <c>Alg =:= crypto</c> or <c>Alg =:= crypto_cache</c>.
+ </p>
+ </item>
+ <item>
+ <p>
+ If you need to be able to repeat the sequence use this function.
+ </p>
+ </item>
+ <item>
+ <p>
+ If you do not need the statistical quality of this function,
+ there are faster algorithms in the
+ <seealso marker="stdlib:rand">rand</seealso>
+ module.
+ </p>
+ </item>
+ </list>
+ <p>
+ Thanks to the used generator the state object supports the
+ <seealso marker="stdlib:rand#jump-0"><c>rand:jump/0,1</c></seealso>
+ function with distance 2^512.
+ </p>
+ <p>
+ Numbers are generated in batches and cached for speed reasons.
+ The cache size can be changed from its default value using the
+ <seealso marker="crypto_app">
+ crypto app's
+ </seealso> configuration parameter <c>rand_cache_size</c>.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="stream_init" arity="2" since="OTP R16B01"/>
<fsummary></fsummary>
<desc>
<p>Initializes the state for use in RC4 stream encryption
@@ -1134,7 +1247,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name name="stream_init" arity="3"/>
+ <name name="stream_init" arity="3" since="OTP R16B01"/>
<fsummary></fsummary>
<desc>
<p>Initializes the state for use in streaming AES encryption using Counter mode (CTR).
@@ -1149,7 +1262,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name name="stream_encrypt" arity="2"/>
+ <name name="stream_encrypt" arity="2" since="OTP R16B01"/>
<fsummary></fsummary>
<desc>
<p>Encrypts <c>PlainText</c> according to the stream cipher <c>Type</c> specified in stream_init/3.
@@ -1160,7 +1273,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name name="stream_decrypt" arity="2"/>
+ <name name="stream_decrypt" arity="2" since="OTP R16B01"/>
<fsummary></fsummary>
<desc>
<p>Decrypts <c>CipherText</c> according to the stream cipher <c>Type</c> specified in stream_init/3.
@@ -1171,7 +1284,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name name="supports" arity="0"/>
+ <name name="supports" arity="0" since="OTP R16B01"/>
<fsummary>Provide a list of available crypto algorithms.</fsummary>
<desc>
<p> Can be used to determine which crypto algorithms that are supported
@@ -1183,7 +1296,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name name="ec_curves" arity="0"/>
+ <name name="ec_curves" arity="0" since="OTP 17.0"/>
<fsummary>Provide a list of available named elliptic curves.</fsummary>
<desc>
<p>Can be used to determine which named elliptic curves are supported.</p>
@@ -1191,7 +1304,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name name="ec_curve" arity="1"/>
+ <name name="ec_curve" arity="1" since="OTP 17.0"/>
<fsummary>Get the defining parameters of a elliptic curve.</fsummary>
<desc>
<p>Return the defining parameters of a elliptic curve.</p>
@@ -1199,8 +1312,8 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name name="sign" arity="4"/>
- <name name="sign" arity="5"/>
+ <name name="sign" arity="4" since="OTP R16B01"/>
+ <name name="sign" arity="5" since="OTP 20.1"/>
<fsummary> Create digital signature.</fsummary>
<desc>
<p>Creates a digital signature.</p>
@@ -1214,8 +1327,8 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name name="verify" arity="5"/>
- <name name="verify" arity="6"/>
+ <name name="verify" arity="5" since="OTP R16B01"/>
+ <name name="verify" arity="6" since="OTP 20.1"/>
<fsummary>Verifies a digital signature.</fsummary>
<desc>
<p>Verifies a digital signature</p>
@@ -1231,7 +1344,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
<!-- Engine functions -->
<func>
- <name name="privkey_to_pubkey" arity="2"/>
+ <name name="privkey_to_pubkey" arity="2" since="OTP 20.2"/>
<fsummary>Fetches a public key from an Engine stored private key.</fsummary>
<desc>
<p>Fetches the corresponding public key from a private key stored in an Engine.
@@ -1241,7 +1354,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name name="engine_get_all_methods" arity="0"/>
+ <name name="engine_get_all_methods" arity="0" since="OTP 20.2"/>
<fsummary>Return list of all possible engine methods</fsummary>
<desc>
<p>
@@ -1259,7 +1372,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name name="engine_load" arity="3"/>
+ <name name="engine_load" arity="3" since="OTP 20.2"/>
<fsummary>Dynamical load an encryption engine</fsummary>
<desc>
<p>
@@ -1281,7 +1394,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name name="engine_load" arity="4"/>
+ <name name="engine_load" arity="4" since="OTP 20.2"/>
<fsummary>Dynamical load an encryption engine</fsummary>
<desc>
<p>
@@ -1301,7 +1414,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name name="engine_unload" arity="1"/>
+ <name name="engine_unload" arity="1" since="OTP 20.2"/>
<fsummary>Dynamical load an encryption engine</fsummary>
<desc>
<p>
@@ -1321,7 +1434,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name name="engine_by_id" arity="1"/>
+ <name name="engine_by_id" arity="1" since="OTP 21.0.6"/>
<fsummary>Get a reference to an already loaded engine</fsummary>
<desc>
<p>
@@ -1341,7 +1454,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name name="engine_ctrl_cmd_string" arity="3"/>
+ <name name="engine_ctrl_cmd_string" arity="3" since="OTP 20.2"/>
<fsummary>Sends ctrl commands to an OpenSSL engine</fsummary>
<desc>
<p>
@@ -1358,7 +1471,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name name="engine_ctrl_cmd_string" arity="4"/>
+ <name name="engine_ctrl_cmd_string" arity="4" since="OTP 20.2"/>
<fsummary>Sends ctrl commands to an OpenSSL engine</fsummary>
<desc>
<p>
@@ -1379,7 +1492,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name name="engine_add" arity="1"/>
+ <name name="engine_add" arity="1" since="OTP 21.0.6"/>
<fsummary>Add engine to OpenSSL internal list</fsummary>
<desc>
<p>Add the engine to OpenSSL's internal list.</p>
@@ -1392,7 +1505,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name name="engine_remove" arity="1"/>
+ <name name="engine_remove" arity="1" since="OTP 21.0.6"/>
<fsummary>Remove engine to OpenSSL internal list</fsummary>
<desc>
<p>Remove the engine from OpenSSL's internal list.</p>
@@ -1405,7 +1518,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name name="engine_get_id" arity="1"/>
+ <name name="engine_get_id" arity="1" since="OTP 21.0.6"/>
<fsummary>Fetch engine ID</fsummary>
<desc>
<p>Return the ID for the engine, or an empty binary if there is no id set.</p>
@@ -1418,7 +1531,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name name="engine_get_name" arity="1"/>
+ <name name="engine_get_name" arity="1" since="OTP 21.0.6"/>
<fsummary>Fetch engine name</fsummary>
<desc>
<p>Return the name (eg a description) for the engine, or an empty binary if there is no name set.</p>
@@ -1431,7 +1544,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name name="engine_list" arity="0"/>
+ <name name="engine_list" arity="0" since="OTP 20.2"/>
<fsummary>List the known engine ids</fsummary>
<desc>
<p>List the id's of all engines in OpenSSL's internal list.</p>
@@ -1451,7 +1564,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name name="ensure_engine_loaded" arity="2"/>
+ <name name="ensure_engine_loaded" arity="2" since="OTP 21.0.6"/>
<fsummary>Ensure encryption engine just loaded once</fsummary>
<desc>
<p>
@@ -1473,7 +1586,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name name="ensure_engine_loaded" arity="3"/>
+ <name name="ensure_engine_loaded" arity="3" since="OTP 21.0.6"/>
<fsummary>Ensure encryption engine just loaded once</fsummary>
<desc>
<p>
@@ -1496,7 +1609,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name name="ensure_engine_unloaded" arity="1"/>
+ <name name="ensure_engine_unloaded" arity="1" since="OTP 21.0.6"/>
<fsummary>Unload an engine loaded with the ensure function</fsummary>
<desc>
<p>
@@ -1519,7 +1632,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name name="ensure_engine_unloaded" arity="2"/>
+ <name name="ensure_engine_unloaded" arity="2" since="OTP 21.0.6"/>
<fsummary>Unload an engine loaded with the ensure function</fsummary>
<desc>
<p>
diff --git a/lib/crypto/doc/src/engine_keys.xml b/lib/crypto/doc/src/engine_keys.xml
index feeb353d1e..f78bb81bba 100644
--- a/lib/crypto/doc/src/engine_keys.xml
+++ b/lib/crypto/doc/src/engine_keys.xml
@@ -40,7 +40,7 @@
</p>
<p>
An engine could among other tasks provide a storage for
- private or public keys. Such a storage could be made safer than the normal file system. Thoose techniques are not
+ private or public keys. Such a storage could be made safer than the normal file system. Those techniques are not
described in this User's Guide. Here we concentrate on how to use private or public keys stored in
such an engine.
</p>
@@ -51,7 +51,7 @@
<p>
OTP/Crypto requires that the user provides two or three items of information about the key. The application used
by the user is usually on a higher level, for example in
- <seealso marker="ssl:ssl#key_option_def">SSL</seealso>. If using
+ <seealso marker="ssl:ssl#type-key">SSL</seealso>. If using
the crypto application directly, it is required that:
</p>
<list>
diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl
index 72cb9aabfd..fe8390c5b8 100644
--- a/lib/crypto/src/crypto.erl
+++ b/lib/crypto/src/crypto.erl
@@ -31,9 +31,10 @@
-export([cmac/3, cmac/4]).
-export([poly1305/2]).
-export([exor/2, strong_rand_bytes/1, mod_pow/3]).
--export([rand_seed/0, rand_seed_alg/1]).
--export([rand_seed_s/0, rand_seed_alg_s/1]).
+-export([rand_seed/0, rand_seed_alg/1, rand_seed_alg/2]).
+-export([rand_seed_s/0, rand_seed_alg_s/1, rand_seed_alg_s/2]).
-export([rand_plugin_next/1]).
+-export([rand_plugin_aes_next/1, rand_plugin_aes_jump/1]).
-export([rand_plugin_uniform/1]).
-export([rand_plugin_uniform/2]).
-export([rand_cache_plugin_next/1]).
@@ -46,6 +47,19 @@
-export([privkey_to_pubkey/2]).
-export([ec_curve/1, ec_curves/0]).
-export([rand_seed/1]).
+
+%% Experiment
+-export([crypto_init/4,
+ crypto_update/2, crypto_update/3,
+ %% Emulates old api:
+ crypto_stream_init/2, crypto_stream_init/3,
+ crypto_stream_encrypt/2,
+ crypto_stream_decrypt/2,
+ crypto_block_encrypt/3, crypto_block_encrypt/4,
+ crypto_block_decrypt/3, crypto_block_decrypt/4
+ ]).
+
+
%% Engine
-export([
engine_get_all_methods/0,
@@ -88,11 +102,14 @@
-export_type([
stream_state/0,
hmac_state/0,
- hash_state/0
+ hash_state/0,
+ crypto_state/0
]).
%% Private. For tests.
--export([packed_openssl_version/4, engine_methods_convert_to_bitmask/2, get_test_engine/0]).
+-export([packed_openssl_version/4, engine_methods_convert_to_bitmask/2,
+ get_test_engine/0]).
+-export([rand_plugin_aes_jump_2pow20/1]).
-deprecated({rand_uniform, 2, next_major_release}).
@@ -258,17 +275,36 @@
%%%
-type block_cipher_with_iv() :: cbc_cipher()
| cfb_cipher()
- | aes_cbc128
- | aes_cbc256
| aes_ige256
| blowfish_ofb64
- | des3_cbf % cfb misspelled
- | des_ede3
| rc2_cbc .
--type cbc_cipher() :: des_cbc | des3_cbc | aes_cbc | blowfish_cbc .
--type aead_cipher() :: aes_gcm | aes_ccm | chacha20_poly1305 .
--type cfb_cipher() :: aes_cfb128 | aes_cfb8 | blowfish_cfb64 | des3_cfb | des_cfb .
+-type cbc_cipher() :: des_cbc | des_ede3_cbc
+ | blowfish_cbc
+ | aes_cbc | aes_128_cbc | aes_192_cbc | aes_256_cbc
+ | alias_cbc() .
+-type alias_cbc() :: des3_cbc | des_ede3
+ | aes_cbc128 | aes_cbc256 .
+
+-type aead_cipher() :: aes_gcm
+ | aes_128_gcm
+ | aes_192_gcm
+ | aes_256_gcm
+ | aes_ccm
+ | aes_128_ccm
+ | aes_192_ccm
+ | aes_256_ccm
+ | chacha20_poly1305 .
+
+-type cfb_cipher() :: aes_cfb8
+ | aes_cfb128
+ | blowfish_cfb64
+ | des_cfb
+ | des_ede3_cfb
+ | alias_cfb() .
+-type alias_cfb() :: des_ede3_cbf | des3_cbf
+ | des3_cfb .
+
-type block_cipher_without_iv() :: ecb_cipher() .
-type ecb_cipher() :: des_ecb | blowfish_ecb | aes_ecb .
@@ -284,6 +320,7 @@
-type sha1() :: sha .
-type sha2() :: sha224 | sha256 | sha384 | sha512 .
-type sha3() :: sha3_224 | sha3_256 | sha3_384 | sha3_512 .
+-type blake2() :: blake2b | blake2s .
-type compatibility_only_hash() :: md5 | md4 .
@@ -326,11 +363,11 @@ stop() ->
| {macs, Macs}
| {curves, Curves}
| {rsa_opts, RSAopts},
- Hashs :: [sha1() | sha2() | sha3() | ripemd160 | compatibility_only_hash()],
+ Hashs :: [sha1() | sha2() | sha3() | blake2() | ripemd160 | compatibility_only_hash()],
Ciphers :: [stream_cipher()
| block_cipher_with_iv() | block_cipher_without_iv()
| aead_cipher()
- ],
+ ],
PKs :: [rsa | dss | ecdsa | dh | ecdh | ec_gf2m],
Macs :: [hmac | cmac | poly1305],
Curves :: [ec_named_curve() | edwards_curve_dh() | edwards_curve_ed()],
@@ -338,7 +375,7 @@ stop() ->
supports()->
{Hashs, PubKeys, Ciphers, Macs, Curves, RsaOpts} = algorithms(),
[{hashs, Hashs},
- {ciphers, Ciphers},
+ {ciphers, prepend_cipher_aliases(Ciphers)},
{public_keys, PubKeys},
{macs, Macs},
{curves, Curves},
@@ -364,7 +401,7 @@ enable_fips_mode(_) -> ?nif_stub.
%%%
%%%================================================================
--define(HASH_HASH_ALGORITHM, sha1() | sha2() | sha3() | ripemd160 | compatibility_only_hash() ).
+-define(HASH_HASH_ALGORITHM, sha1() | sha2() | sha3() | blake2() | ripemd160 | compatibility_only_hash() ).
-spec hash(Type, Data) -> Digest when Type :: ?HASH_HASH_ALGORITHM,
Data :: iodata(),
@@ -469,7 +506,7 @@ hmac_final_n(Context, HashLen) ->
Data :: iodata(),
Mac :: binary().
cmac(Type, Key, Data) ->
- notsup_to_error(cmac_nif(Type, Key, Data)).
+ notsup_to_error(cmac_nif(alias(Type), Key, Data)).
-spec cmac(Type, Key, Data, MacLength) ->
Mac when Type :: ?CMAC_CIPHER_ALGORITHM,
@@ -477,8 +514,9 @@ cmac(Type, Key, Data) ->
Data :: iodata(),
MacLength :: integer(),
Mac :: binary().
+
cmac(Type, Key, Data, MacLength) ->
- erlang:binary_part(cmac(Type, Key, Data), 0, MacLength).
+ erlang:binary_part(cmac(alias(Type), Key, Data), 0, MacLength).
%%%---- POLY1305
@@ -495,91 +533,80 @@ poly1305(Key, Data) ->
%%%---- Block ciphers
+%%%----------------------------------------------------------------
-spec block_encrypt(Type::block_cipher_with_iv(), Key::key()|des3_key(), Ivec::binary(), PlainText::iodata()) -> binary();
(Type::aead_cipher(), Key::iodata(), Ivec::binary(), {AAD::binary(), PlainText::iodata()}) ->
{binary(), binary()};
(aes_gcm | aes_ccm, Key::iodata(), Ivec::binary(), {AAD::binary(), PlainText::iodata(), TagLength::1..16}) ->
{binary(), binary()}.
-block_encrypt(Type, Key, Ivec, PlainText) when Type =:= des_cbc;
- Type =:= des_cfb;
- Type =:= blowfish_cbc;
- Type =:= blowfish_cfb64;
- Type =:= blowfish_ofb64;
- Type =:= aes_cbc128;
- Type =:= aes_cfb8;
- Type =:= aes_cfb128;
- Type =:= aes_cbc256;
- Type =:= aes_cbc;
- Type =:= rc2_cbc ->
- block_crypt_nif(Type, Key, Ivec, PlainText, true);
-block_encrypt(Type, Key0, Ivec, PlainText) when Type =:= des3_cbc;
- Type =:= des_ede3 ->
- Key = check_des3_key(Key0),
- block_crypt_nif(des_ede3_cbc, Key, Ivec, PlainText, true);
-block_encrypt(des3_cbf, Key0, Ivec, PlainText) -> % cfb misspelled
- Key = check_des3_key(Key0),
- block_crypt_nif(des_ede3_cbf, Key, Ivec, PlainText, true);
-block_encrypt(des3_cfb, Key0, Ivec, PlainText) ->
+
+block_encrypt(Type, Key, Ivec, Data) ->
+ do_block_encrypt(alias(Type), Key, Ivec, Data).
+
+do_block_encrypt(Type, Key0, Ivec, Data) when Type =:= des_ede3_cbc;
+ Type =:= des_ede3_cfb ->
Key = check_des3_key(Key0),
- block_crypt_nif(des_ede3_cfb, Key, Ivec, PlainText, true);
-block_encrypt(aes_ige256, Key, Ivec, PlainText) ->
+ block_crypt_nif(Type, Key, Ivec, Data, true);
+
+do_block_encrypt(Type, Key, Ivec, PlainText) when Type =:= aes_ige256 ->
notsup_to_error(aes_ige_crypt_nif(Key, Ivec, PlainText, true));
-block_encrypt(Type, Key, Ivec, {AAD, PlainText}) when Type =:= aes_gcm;
- Type =:= aes_ccm ->
- aead_encrypt(Type, Key, Ivec, AAD, PlainText);
-block_encrypt(Type, Key, Ivec, {AAD, PlainText, TagLength}) when Type =:= aes_gcm;
- Type =:= aes_ccm ->
- aead_encrypt(Type, Key, Ivec, AAD, PlainText, TagLength);
-block_encrypt(chacha20_poly1305=Type, Key, Ivec, {AAD, PlainText}) ->
- aead_encrypt(Type, Key, Ivec, AAD, PlainText, 16).
+do_block_encrypt(Type, Key, Ivec, {AAD, PlainText}) when Type =:= chacha20_poly1305 ->
+ aead_encrypt(Type, Key, Ivec, AAD, PlainText, 16);
+
+do_block_encrypt(Type, Key, Ivec, Data) when Type =:= aes_gcm;
+ Type =:= aes_ccm ->
+ case Data of
+ {AAD, PlainText} ->
+ aead_encrypt(Type, Key, Ivec, AAD, PlainText);
+ {AAD, PlainText, TagLength} ->
+ aead_encrypt(Type, Key, Ivec, AAD, PlainText, TagLength)
+ end;
+
+do_block_encrypt(Type, Key, Ivec, PlainText) ->
+ block_crypt_nif(Type, Key, Ivec, PlainText, true).
+
+
+-spec block_encrypt(Type::block_cipher_without_iv(), Key::key(), PlainText::iodata()) -> binary().
+
+block_encrypt(Type, Key, PlainText) ->
+ block_crypt_nif(alias(Type), Key, PlainText, true).
+
+%%%----------------------------------------------------------------
+%%%----------------------------------------------------------------
-spec block_decrypt(Type::block_cipher_with_iv(), Key::key()|des3_key(), Ivec::binary(), Data::iodata()) -> binary();
(Type::aead_cipher(), Key::iodata(), Ivec::binary(),
{AAD::binary(), Data::iodata(), Tag::binary()}) -> binary() | error.
-block_decrypt(Type, Key, Ivec, Data) when Type =:= des_cbc;
- Type =:= des_cfb;
- Type =:= blowfish_cbc;
- Type =:= blowfish_cfb64;
- Type =:= blowfish_ofb64;
- Type =:= aes_cbc;
- Type =:= aes_cbc128;
- Type =:= aes_cfb8;
- Type =:= aes_cfb128;
- Type =:= aes_cbc256;
- Type =:= rc2_cbc ->
- block_crypt_nif(Type, Key, Ivec, Data, false);
-block_decrypt(Type, Key0, Ivec, Data) when Type =:= des3_cbc;
- Type =:= des_ede3 ->
- Key = check_des3_key(Key0),
- block_crypt_nif(des_ede3_cbc, Key, Ivec, Data, false);
-block_decrypt(des3_cbf, Key0, Ivec, Data) -> % cfb misspelled
- Key = check_des3_key(Key0),
- block_crypt_nif(des_ede3_cbf, Key, Ivec, Data, false);
-block_decrypt(des3_cfb, Key0, Ivec, Data) ->
+
+block_decrypt(Type, Key, Ivec, Data) ->
+ do_block_decrypt(alias(Type), Key, Ivec, Data).
+
+do_block_decrypt(Type, Key0, Ivec, Data) when Type =:= des_ede3_cbc;
+ Type =:= des_ede3_cfb ->
Key = check_des3_key(Key0),
- block_crypt_nif(des_ede3_cfb, Key, Ivec, Data, false);
-block_decrypt(aes_ige256, Key, Ivec, Data) ->
+ block_crypt_nif(Type, Key, Ivec, Data, false);
+
+do_block_decrypt(aes_ige256, Key, Ivec, Data) ->
notsup_to_error(aes_ige_crypt_nif(Key, Ivec, Data, false));
-block_decrypt(Type, Key, Ivec, {AAD, Data, Tag}) when Type =:= aes_gcm;
+
+do_block_decrypt(Type, Key, Ivec, {AAD, Data, Tag}) when Type =:= aes_gcm;
Type =:= aes_ccm;
Type =:= chacha20_poly1305 ->
- aead_decrypt(Type, Key, Ivec, AAD, Data, Tag).
-
+ aead_decrypt(Type, Key, Ivec, AAD, Data, Tag);
--spec block_encrypt(Type::block_cipher_without_iv(), Key::key(), PlainText::iodata()) -> binary().
+do_block_decrypt(Type, Key, Ivec, Data) ->
+ block_crypt_nif(Type, Key, Ivec, Data, false).
-block_encrypt(Type, Key, PlainText) ->
- block_crypt_nif(Type, Key, PlainText, true).
-spec block_decrypt(Type::block_cipher_without_iv(), Key::key(), Data::iodata()) -> binary().
block_decrypt(Type, Key, Data) ->
- block_crypt_nif(Type, Key, Data, false).
-
+ block_crypt_nif(alias(Type), Key, Data, false).
+%%%----------------------------------------------------------------
-spec next_iv(Type:: cbc_cipher(), Data) -> NextIVec when % Type :: cbc_cipher(), %des_cbc | des3_cbc | aes_cbc | aes_ige,
Data :: iodata(),
NextIVec :: binary().
@@ -610,18 +637,30 @@ next_iv(Type, Data, _Ivec) ->
-opaque stream_state() :: {stream_cipher(), reference()}.
--type stream_cipher() :: rc4 | aes_ctr | chacha20 .
+-type stream_cipher() :: stream_cipher_iv() | stream_cipher_no_iv() .
+-type stream_cipher_no_iv() :: rc4 .
+-type stream_cipher_iv() :: aes_ctr
+ | aes_128_ctr
+ | aes_192_ctr
+ | aes_256_ctr
+ | chacha20 .
--spec stream_init(Type, Key, IVec) -> State when Type :: aes_ctr | chacha20,
+-spec stream_init(Type, Key, IVec) -> State when Type :: stream_cipher_iv(),
Key :: iodata(),
IVec :: binary(),
State :: stream_state() .
stream_init(aes_ctr, Key, Ivec) ->
{aes_ctr, aes_ctr_stream_init(Key, Ivec)};
+stream_init(aes_128_ctr, Key, Ivec) ->
+ {aes_ctr, aes_ctr_stream_init(Key, Ivec)};
+stream_init(aes_192_ctr, Key, Ivec) ->
+ {aes_ctr, aes_ctr_stream_init(Key, Ivec)};
+stream_init(aes_256_ctr, Key, Ivec) ->
+ {aes_ctr, aes_ctr_stream_init(Key, Ivec)};
stream_init(chacha20, Key, Ivec) ->
{chacha20, chacha20_stream_init(Key,Ivec)}.
--spec stream_init(Type, Key) -> State when Type :: rc4,
+-spec stream_init(Type, Key) -> State when Type :: stream_cipher_no_iv(),
Key :: iodata(),
State :: stream_state() .
stream_init(rc4, Key) ->
@@ -679,34 +718,73 @@ rand_seed_s() ->
rand_seed_alg(Alg) ->
rand:seed(rand_seed_alg_s(Alg)).
+-spec rand_seed_alg(Alg :: atom(), Seed :: term()) ->
+ {rand:alg_handler(),
+ atom() | rand_cache_seed()}.
+rand_seed_alg(Alg, Seed) ->
+ rand:seed(rand_seed_alg_s(Alg, Seed)).
+
-define(CRYPTO_CACHE_BITS, 56).
+-define(CRYPTO_AES_BITS, 58).
+
-spec rand_seed_alg_s(Alg :: atom()) ->
{rand:alg_handler(),
atom() | rand_cache_seed()}.
-rand_seed_alg_s(?MODULE) ->
- {#{ type => ?MODULE,
- bits => 64,
- next => fun ?MODULE:rand_plugin_next/1,
- uniform => fun ?MODULE:rand_plugin_uniform/1,
- uniform_n => fun ?MODULE:rand_plugin_uniform/2},
- no_seed};
-rand_seed_alg_s(crypto_cache) ->
+rand_seed_alg_s({AlgHandler, _AlgState} = State) when is_map(AlgHandler) ->
+ State;
+rand_seed_alg_s({Alg, AlgState}) when is_atom(Alg) ->
+ {mk_alg_handler(Alg),AlgState};
+ rand_seed_alg_s(Alg) when is_atom(Alg) ->
+ {mk_alg_handler(Alg),mk_alg_state(Alg)}.
+%%
+-spec rand_seed_alg_s(Alg :: atom(), Seed :: term()) ->
+ {rand:alg_handler(),
+ atom() | rand_cache_seed()}.
+rand_seed_alg_s(Alg, Seed) when is_atom(Alg) ->
+ {mk_alg_handler(Alg),mk_alg_state({Alg,Seed})}.
+
+mk_alg_handler(?MODULE = Alg) ->
+ #{ type => Alg,
+ bits => 64,
+ next => fun ?MODULE:rand_plugin_next/1,
+ uniform => fun ?MODULE:rand_plugin_uniform/1,
+ uniform_n => fun ?MODULE:rand_plugin_uniform/2};
+mk_alg_handler(crypto_cache = Alg) ->
+ #{ type => Alg,
+ bits => ?CRYPTO_CACHE_BITS,
+ next => fun ?MODULE:rand_cache_plugin_next/1};
+mk_alg_handler(crypto_aes = Alg) ->
+ #{ type => Alg,
+ bits => ?CRYPTO_AES_BITS,
+ next => fun ?MODULE:rand_plugin_aes_next/1,
+ jump => fun ?MODULE:rand_plugin_aes_jump/1}.
+
+mk_alg_state(?MODULE) ->
+ no_seed;
+mk_alg_state(crypto_cache) ->
CacheBits = ?CRYPTO_CACHE_BITS,
- EnvCacheSize =
- application:get_env(
- crypto, rand_cache_size, CacheBits * 16), % Cache 16 * 8 words
- Bytes = (CacheBits + 7) div 8,
+ BytesPerWord = (CacheBits + 7) div 8,
+ GenBytes =
+ ((rand_cache_size() + (2*BytesPerWord - 1)) div BytesPerWord)
+ * BytesPerWord,
+ {CacheBits, GenBytes, <<>>};
+mk_alg_state({crypto_aes,Seed}) ->
+ %% 16 byte words (128 bit crypto blocks)
+ GenWords = (rand_cache_size() + 31) div 16,
+ Key = crypto:hash(sha256, Seed),
+ {F,Count} = longcount_seed(Seed),
+ {Key,GenWords,F,Count}.
+
+rand_cache_size() ->
+ DefaultCacheSize = 1024,
CacheSize =
- case ((EnvCacheSize + (Bytes - 1)) div Bytes) * Bytes of
- Sz when is_integer(Sz), Bytes =< Sz ->
- Sz;
- _ ->
- Bytes
- end,
- {#{ type => crypto_cache,
- bits => CacheBits,
- next => fun ?MODULE:rand_cache_plugin_next/1},
- {CacheBits, CacheSize, <<>>}}.
+ application:get_env(crypto, rand_cache_size, DefaultCacheSize),
+ if
+ is_integer(CacheSize), 0 =< CacheSize ->
+ CacheSize;
+ true ->
+ DefaultCacheSize
+ end.
rand_plugin_next(Seed) ->
{bytes_to_integer(strong_rand_range(1 bsl 64)), Seed}.
@@ -717,12 +795,97 @@ rand_plugin_uniform(State) ->
rand_plugin_uniform(Max, State) ->
{bytes_to_integer(strong_rand_range(Max)) + 1, State}.
-rand_cache_plugin_next({CacheBits, CacheSize, <<>>}) ->
+
+rand_cache_plugin_next({CacheBits, GenBytes, <<>>}) ->
rand_cache_plugin_next(
- {CacheBits, CacheSize, strong_rand_bytes(CacheSize)});
-rand_cache_plugin_next({CacheBits, CacheSize, Cache}) ->
+ {CacheBits, GenBytes, strong_rand_bytes(GenBytes)});
+rand_cache_plugin_next({CacheBits, GenBytes, Cache}) ->
<<I:CacheBits, NewCache/binary>> = Cache,
- {I, {CacheBits, CacheSize, NewCache}}.
+ {I, {CacheBits, GenBytes, NewCache}}.
+
+
+%% Encrypt 128 bit counter values and use the 58 lowest
+%% encrypted bits as random numbers.
+%%
+%% The 128 bit counter is handled as 4 32 bit words
+%% to avoid bignums. Generate a bunch of numbers
+%% at the time and cache them.
+%%
+-dialyzer({no_improper_lists, rand_plugin_aes_next/1}).
+rand_plugin_aes_next([V|Cache]) ->
+ {V,Cache};
+rand_plugin_aes_next({Key,GenWords,F,Count}) ->
+ rand_plugin_aes_next(Key, GenWords, F, Count);
+rand_plugin_aes_next({Key,GenWords,F,_JumpBase,Count}) ->
+ rand_plugin_aes_next(Key, GenWords, F, Count).
+%%
+rand_plugin_aes_next(Key, GenWords, F, Count) ->
+ {Cleartext,NewCount} = aes_cleartext(<<>>, F, Count, GenWords),
+ Encrypted = crypto:block_encrypt(aes_ecb, Key, Cleartext),
+ [V|Cache] = aes_cache(Encrypted, {Key,GenWords,F,Count,NewCount}),
+ {V,Cache}.
+
+%% A jump advances the counter 2^512 steps; the jump function
+%% is applied to the jump base and then the number of used
+%% numbers from the cache has to be wasted for the jump to be correct
+%%
+rand_plugin_aes_jump({#{type := crypto_aes} = Alg, Cache}) ->
+ {Alg,rand_plugin_aes_jump(fun longcount_jump/1, 0, Cache)}.
+%% Count cached words and subtract their number from jump
+-dialyzer({no_improper_lists, rand_plugin_aes_jump/3}).
+rand_plugin_aes_jump(Jump, J, [_|Cache]) ->
+ rand_plugin_aes_jump(Jump, J + 1, Cache);
+rand_plugin_aes_jump(Jump, J, {Key,GenWords,F,JumpBase, _Count}) ->
+ rand_plugin_aes_jump(Jump, GenWords - J, Key, GenWords, F, JumpBase);
+rand_plugin_aes_jump(Jump, 0, {Key,GenWords,F,JumpBase}) ->
+ rand_plugin_aes_jump(Jump, 0, Key, GenWords, F, JumpBase).
+%%
+rand_plugin_aes_jump(Jump, Skip, Key, GenWords, F, JumpBase) ->
+ Count = longcount_next_count(Skip, Jump(JumpBase)),
+ {Key,GenWords,F,Count}.
+
+rand_plugin_aes_jump_2pow20(Cache) ->
+ rand_plugin_aes_jump(fun longcount_jump_2pow20/1, 0, Cache).
+
+
+longcount_seed(Seed) ->
+ <<X:64, _:6, F:12, S2:58, S1:58, S0:58>> =
+ crypto:hash(sha256, [Seed,<<"Xoroshiro928">>]),
+ {F,rand:exro928_seed([S0,S1,S2|rand:seed58(13, X)])}.
+
+longcount_next_count(0, Count) ->
+ Count;
+longcount_next_count(N, Count) ->
+ longcount_next_count(N - 1, rand:exro928_next_state(Count)).
+
+longcount_next(Count) ->
+ rand:exro928_next(Count).
+
+longcount_jump(Count) ->
+ rand:exro928_jump_2pow512(Count).
+
+longcount_jump_2pow20(Count) ->
+ rand:exro928_jump_2pow20(Count).
+
+
+%% Build binary with counter values to cache
+aes_cleartext(Cleartext, _F, Count, 0) ->
+ {Cleartext,Count};
+aes_cleartext(Cleartext, F, Count, GenWords) ->
+ {{S0,S1}, NewCount} = longcount_next(Count),
+ aes_cleartext(
+ <<Cleartext/binary, F:12, S1:58, S0:58>>,
+ F, NewCount, GenWords - 1).
+
+%% Parse and cache encrypted counter values aka random numbers
+-dialyzer({no_improper_lists, aes_cache/2}).
+aes_cache(<<>>, Cache) ->
+ Cache;
+aes_cache(
+ <<_:(128 - ?CRYPTO_AES_BITS), V:?CRYPTO_AES_BITS, Encrypted/binary>>,
+ Cache) ->
+ [V|aes_cache(Encrypted, Cache)].
+
strong_rand_range(Range) when is_integer(Range), Range > 0 ->
BinRange = int_to_bin(Range),
@@ -787,7 +950,8 @@ rand_seed_nif(_Seed) -> ?nif_stub.
-type pk_sign_verify_opts() :: [ rsa_sign_verify_opt() ] .
-type rsa_sign_verify_opt() :: {rsa_padding, rsa_sign_verify_padding()}
- | {rsa_pss_saltlen, integer()} .
+ | {rsa_pss_saltlen, integer()}
+ | {rsa_mgf1_md, sha2()}.
-type rsa_sign_verify_padding() :: rsa_pkcs1_padding | rsa_pkcs1_pss_padding
| rsa_x931_padding | rsa_no_padding
@@ -803,7 +967,7 @@ rand_seed_nif(_Seed) -> ?nif_stub.
DigestType :: rsa_digest_type()
| dss_digest_type()
| ecdsa_digest_type(),
- Msg :: binary() | {digest,binary()},
+ Msg :: iodata() | {digest,iodata()},
Key :: rsa_private()
| dss_private()
| [ecdsa_private() | ecdsa_params()]
@@ -822,7 +986,7 @@ sign(Algorithm, Type, Data, Key) ->
| dss_digest_type()
| ecdsa_digest_type()
| none,
- Msg :: binary() | {digest,binary()},
+ Msg :: iodata() | {digest,iodata()},
Key :: rsa_private()
| dss_private()
| [ecdsa_private() | ecdsa_params()]
@@ -851,7 +1015,7 @@ pkey_sign_nif(_Algorithm, _Type, _Digest, _Key, _Options) -> ?nif_stub.
| dss_digest_type()
| ecdsa_digest_type()
| none,
- Msg :: binary() | {digest,binary()},
+ Msg :: iodata() | {digest,iodata()},
Signature :: binary(),
Key :: rsa_public()
| dss_public()
@@ -869,7 +1033,7 @@ verify(Algorithm, Type, Data, Signature, Key) ->
DigestType :: rsa_digest_type()
| dss_digest_type()
| ecdsa_digest_type(),
- Msg :: binary() | {digest,binary()},
+ Msg :: iodata() | {digest,iodata()},
Signature :: binary(),
Key :: rsa_public()
| dss_public()
@@ -2051,3 +2215,178 @@ check_otp_test_engine(LibDir) ->
{error, notexist}
end
end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%
+%%% Experimental NG
+%%%
+
+%%% -> {ok,State::ref()} | {error,Reason}
+
+-opaque crypto_state() :: reference() | {any(),any(),any(),any()}.
+
+
+%%%----------------------------------------------------------------
+%%%
+%%% Create and initialize a new state for encryption or decryption
+%%%
+
+-spec crypto_init(Cipher, Key, IV, EncryptFlag) -> {ok,State} | {error,term()} | undefined
+ when Cipher :: stream_cipher()
+ | block_cipher_with_iv()
+ | block_cipher_without_iv() ,
+ Key :: iodata(),
+ IV :: binary(),
+ EncryptFlag :: boolean() | undefined,
+ State :: crypto_state() .
+
+crypto_init(Cipher, Key, IV, EncryptFlag) when is_atom(Cipher),
+ is_binary(Key),
+ is_binary(IV),
+ is_atom(EncryptFlag) ->
+ case ng_crypto_init_nif(alias(Cipher), Key, IV, EncryptFlag) of
+ {error,Error} ->
+ {error,Error};
+ undefined -> % For compatibility function crypto_stream_init/3
+ undefined;
+ Ref when is_reference(Ref) ->
+ {ok,Ref};
+ State when is_tuple(State),
+ size(State)==4 ->
+ {ok,State} % compatibility with old cryptolibs < 1.0.1
+ end.
+
+
+%%%----------------------------------------------------------------
+%%%
+%%% Encrypt/decrypt a sequence of bytes. The sum of the sizes
+%%% of all blocks must be an integer multiple of the crypto's
+%%% blocksize.
+%%%
+
+-spec crypto_update(State, Data) -> {ok,Result} | {error,term()}
+ when State :: crypto_state(),
+ Data :: iodata(),
+ Result :: binary() | {crypto_state(),binary()}.
+crypto_update(State, Data) ->
+ mk_ret(ng_crypto_update_nif(State, Data)).
+
+%%%----------------------------------------------------------------
+%%%
+%%% Encrypt/decrypt a sequence of bytes but change the IV first.
+%%% Not applicable for all modes.
+%%%
+
+-spec crypto_update(State, Data, IV) -> {ok,Result} | {error,term()}
+ when State :: crypto_state(),
+ Data :: iodata(),
+ IV :: binary(),
+ Result :: binary() | {crypto_state(),binary()}.
+crypto_update(State, Data, IV) ->
+ mk_ret(ng_crypto_update_nif(State, Data, IV)).
+
+%%%----------------------------------------------------------------
+%%% Helpers
+mk_ret(R) -> mk_ret(R, []).
+
+mk_ret({error,Error}, _) ->
+ {error,Error};
+mk_ret(Bin, Acc) when is_binary(Bin) ->
+ {ok, iolist_to_binary(lists:reverse([Bin|Acc]))};
+mk_ret({State1,Bin}, Acc) when is_tuple(State1),
+ size(State1) == 4,
+ is_binary(Bin) ->
+ %% compatibility with old cryptolibs < 1.0.1
+ {ok, {State1, iolist_to_binary(lists:reverse([Bin|Acc]))}}.
+
+%%%----------------------------------------------------------------
+%%% NIFs
+ng_crypto_init_nif(_Cipher, _Key, _IVec, _EncryptFlg) -> ?nif_stub.
+ng_crypto_update_nif(_State, _Data) -> ?nif_stub.
+ng_crypto_update_nif(_State, _Data, _IV) -> ?nif_stub.
+
+%%%================================================================
+%%% Compatibility functions to be called by "old" api functions.
+
+%%%--------------------------------
+%%%---- block encrypt/decrypt
+crypto_block_encrypt(Cipher, Key, Data) -> crypto_block_encrypt(Cipher, Key, <<>>, Data).
+crypto_block_decrypt(Cipher, Key, Data) -> crypto_block_decrypt(Cipher, Key, <<>>, Data).
+
+crypto_block_encrypt(Cipher, Key, Ivec, Data) -> crypto_block(Cipher, Key, Ivec, Data, true).
+crypto_block_decrypt(Cipher, Key, Ivec, Data) -> crypto_block(Cipher, Key, Ivec, Data, false).
+
+%% AEAD: use old funcs
+
+%%%---- helper
+crypto_block(Cipher, Key, IV, Data, EncryptFlag) ->
+ case crypto_init(Cipher, iolist_to_binary(Key), iolist_to_binary(IV), EncryptFlag) of
+ {ok, Ref} ->
+ case crypto_update(Ref, Data) of
+ {ok, {_,Bin}} when is_binary(Bin) -> Bin;
+ {ok, Bin} when is_binary(Bin) -> Bin;
+ {error,_} -> error(badarg)
+ end;
+
+ {error,_} -> error(badarg)
+ end.
+
+%%%--------------------------------
+%%%---- stream init, encrypt/decrypt
+
+crypto_stream_init(Cipher, Key) ->
+ crypto_stream_init(Cipher, Key, <<>>).
+
+crypto_stream_init(Cipher, Key0, IV0) ->
+ Key = iolist_to_binary(Key0),
+ IV = iolist_to_binary(IV0),
+ %% First check the argumensts:
+ case crypto_init(Cipher, Key, IV, undefined) of
+ undefined ->
+ {Cipher, {Key, IV}};
+ {error,_} ->
+ {error,badarg}
+ end.
+
+crypto_stream_encrypt(State, PlainText) ->
+ crypto_stream_emulate(State, PlainText, true).
+
+crypto_stream_decrypt(State, CryptoText) ->
+ crypto_stream_emulate(State, CryptoText, false).
+
+
+%%%---- helper
+crypto_stream_emulate({Cipher,{Key,IV}}, Data, EncryptFlag) ->
+ case crypto_init(Cipher, Key, IV, EncryptFlag) of
+ {ok,State} ->
+ crypto_stream_emulate({Cipher,State}, Data, EncryptFlag);
+ {error,_} ->
+ error(badarg)
+ end;
+crypto_stream_emulate({Cipher,State}, Data, _) ->
+ case crypto_update(State, Data) of
+ {ok, {State1,Bin}} when is_binary(Bin) -> {{Cipher,State1},Bin};
+ {ok,Bin} when is_binary(Bin) -> {{Cipher,State},Bin};
+ {error,_} -> error(badarg)
+ end.
+
+
+%%%================================================================
+
+prepend_cipher_aliases(L) ->
+ [des3_cbc, des_ede3, des_ede3_cbf, des3_cbf, des3_cfb, aes_cbc128, aes_cbc256 | L].
+
+
+%%%---- des_ede3_cbc
+alias(des3_cbc) -> des_ede3_cbc;
+alias(des_ede3) -> des_ede3_cbc;
+%%%---- des_ede3_cfb
+alias(des_ede3_cbf) -> des_ede3_cfb;
+alias(des3_cbf) -> des_ede3_cfb;
+alias(des3_cfb) -> des_ede3_cfb;
+%%%---- aes_*_cbc
+alias(aes_cbc128) -> aes_128_cbc;
+alias(aes_cbc256) -> aes_256_cbc;
+
+alias(Alg) -> Alg.
diff --git a/lib/crypto/test/Makefile b/lib/crypto/test/Makefile
index e046a25338..988d95a8bc 100644
--- a/lib/crypto/test/Makefile
+++ b/lib/crypto/test/Makefile
@@ -6,7 +6,7 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
# ----------------------------------------------------
MODULES = \
- blowfish_SUITE \
+ crypto_bench_SUITE \
crypto_SUITE \
engine_SUITE
@@ -77,7 +77,7 @@ release_spec:
release_tests_spec: $(TEST_TARGET)
$(INSTALL_DIR) "$(RELSYSDIR)"
- $(INSTALL_DATA) crypto.spec crypto.cover $(RELTEST_FILES) "$(RELSYSDIR)"
+ $(INSTALL_DATA) crypto.spec crypto_bench.spec crypto.cover $(RELTEST_FILES) "$(RELSYSDIR)"
@tar cfh - *_SUITE_data | (cd "$(RELSYSDIR)"; tar xf -)
chmod -R u+w "$(RELSYSDIR)"
diff --git a/lib/crypto/test/blowfish_SUITE.erl b/lib/crypto/test/blowfish_SUITE.erl
deleted file mode 100644
index a931ebb47e..0000000000
--- a/lib/crypto/test/blowfish_SUITE.erl
+++ /dev/null
@@ -1,300 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2009-2018. All 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(blowfish_SUITE).
-
-%% Note: This directive should only be used in test suites.
--compile(export_all).
-
--include_lib("common_test/include/ct.hrl").
-
--define(TIMEOUT, 120000). % 2 min
-
--define(KEY, to_bin("0123456789ABCDEFF0E1D2C3B4A59687")).
--define(IVEC, to_bin("FEDCBA9876543210")).
-%% "7654321 Now is the time for " (includes trailing '\0')
--define(DATA, to_bin("37363534333231204E6F77206973207468652074696D6520666F722000")).
--define(DATA_PADDED, to_bin("37363534333231204E6F77206973207468652074696D6520666F722000000000")).
-
-%% Test server callback functions
-%%--------------------------------------------------------------------
-%% Function: init_per_suite(Config) -> Config
-%% Config - [tuple()]
-%% A list of key/value pairs, holding the test case configuration.
-%% Description: Initialization before the whole suite
-%%
-%% Note: This function is free to add any key/value pairs to the Config
-%% variable, but should NOT alter/remove any existing entries.
-%%--------------------------------------------------------------------
-init_per_suite(Config) ->
- case catch crypto:start() of
- ok ->
- catch ct:comment("~s",[element(3,hd(crypto:info_lib()))]),
- catch ct:log("crypto:info_lib() -> ~p~n"
- "crypto:supports() -> ~p~n"
- "crypto:version() -> ~p~n"
- ,[crypto:info_lib(), crypto:supports(), crypto:version()]),
- Config;
- _Else ->
- {skip,"Could not start crypto!"}
- end.
-
-%%--------------------------------------------------------------------
-%% Function: end_per_suite(Config) -> _
-%% Config - [tuple()]
-%% A list of key/value pairs, holding the test case configuration.
-%% Description: Cleanup after the whole suite
-%%--------------------------------------------------------------------
-end_per_suite(_Config) ->
- crypto:stop().
-
-%%--------------------------------------------------------------------
-%% Function: init_per_testcase(TestCase, Config) -> Config
-%% Case - atom()
-%% Name of the test case that is about to be run.
-%% Config - [tuple()]
-%% A list of key/value pairs, holding the test case configuration.
-%%
-%% Description: Initialization before each test case
-%%
-%% Note: This function is free to add any key/value pairs to the Config
-%% variable, but should NOT alter/remove any existing entries.
-%% Description: Initialization before each test case
-%%--------------------------------------------------------------------
-init_per_testcase(_TestCase, Config0) ->
- Config = lists:keydelete(watchdog, 1, Config0),
- Dog = test_server:timetrap(?TIMEOUT),
- [{watchdog, Dog} | Config].
-
-%%--------------------------------------------------------------------
-%% Function: end_per_testcase(TestCase, Config) -> _
-%% Case - atom()
-%% Name of the test case that is about to be run.
-%% Config - [tuple()]
-%% A list of key/value pairs, holding the test case configuration.
-%% Description: Cleanup after each test case
-%%--------------------------------------------------------------------
-end_per_testcase(_TestCase, Config) ->
- Dog = ?config(watchdog, Config),
- case Dog of
- undefined ->
- ok;
- _ ->
- test_server:timetrap_cancel(Dog)
- end.
-
-%%--------------------------------------------------------------------
-%% Function: all(Clause) -> TestCases
-%% Clause - atom() - suite | doc
-%% TestCases - [Case]
-%% Case - atom()
-%% Name of a test case.
-%% Description: Returns a list of all test cases in this test suite
-%%--------------------------------------------------------------------
-suite() -> [{ct_hooks,[ts_install_cth]}].
-
-all() ->
-[{group, fips},
- {group, non_fips}].
-
-groups() ->
- [{fips, [], [no_ecb, no_cbc, no_cfb64, no_ofb64]},
- {non_fips, [], [ecb, cbc, cfb64, ofb64]}].
-
-init_per_group(fips, Config) ->
- case crypto:info_fips() of
- enabled ->
- Config;
- not_enabled ->
- case crypto:enable_fips_mode(true) of
- true ->
- enabled = crypto:info_fips(),
- Config;
- false ->
- {skip, "Failed to enable FIPS mode"}
- end;
- not_supported ->
- {skip, "FIPS mode not supported"}
- end;
-init_per_group(non_fips, Config) ->
- case crypto:info_fips() of
- enabled ->
- true = crypto:enable_fips_mode(false),
- not_enabled = crypto:info_fips(),
- Config;
- _NotEnabled ->
- Config
- end;
-init_per_group(_GroupName, Config) ->
- Config.
-
-end_per_group(_GroupName, Config) ->
- Config.
-
-
-%% Test cases start here.
-%%--------------------------------------------------------------------
-
-ecb_test(KeyBytes, ClearBytes, CipherBytes) ->
- {Key, Clear, Cipher} =
- {to_bin(KeyBytes), to_bin(ClearBytes), to_bin(CipherBytes)},
- ?line m(crypto:block_encrypt(blowfish_ecb, Key, Clear), Cipher),
- true.
-
-ecb(doc) ->
- "Test that ECB mode is OK";
-ecb(suite) ->
- [];
-ecb(Config) when is_list(Config) ->
- true = ecb_test("0000000000000000", "0000000000000000", "4EF997456198DD78"),
- true = ecb_test("FFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", "51866FD5B85ECB8A"),
- true = ecb_test("3000000000000000", "1000000000000001", "7D856F9A613063F2"),
- true = ecb_test("1111111111111111", "1111111111111111", "2466DD878B963C9D"),
- true = ecb_test("0123456789ABCDEF", "1111111111111111", "61F9C3802281B096"),
- true = ecb_test("1111111111111111", "0123456789ABCDEF", "7D0CC630AFDA1EC7"),
- true = ecb_test("0000000000000000", "0000000000000000", "4EF997456198DD78"),
- true = ecb_test("FEDCBA9876543210", "0123456789ABCDEF", "0ACEAB0FC6A0A28D"),
- true = ecb_test("7CA110454A1A6E57", "01A1D6D039776742", "59C68245EB05282B"),
- true = ecb_test("0131D9619DC1376E", "5CD54CA83DEF57DA", "B1B8CC0B250F09A0"),
- true = ecb_test("07A1133E4A0B2686", "0248D43806F67172", "1730E5778BEA1DA4"),
- true = ecb_test("3849674C2602319E", "51454B582DDF440A", "A25E7856CF2651EB"),
- true = ecb_test("04B915BA43FEB5B6", "42FD443059577FA2", "353882B109CE8F1A"),
- true = ecb_test("0113B970FD34F2CE", "059B5E0851CF143A", "48F4D0884C379918"),
- true = ecb_test("0170F175468FB5E6", "0756D8E0774761D2", "432193B78951FC98"),
- true = ecb_test("43297FAD38E373FE", "762514B829BF486A", "13F04154D69D1AE5"),
- true = ecb_test("07A7137045DA2A16", "3BDD119049372802", "2EEDDA93FFD39C79"),
- true = ecb_test("04689104C2FD3B2F", "26955F6835AF609A", "D887E0393C2DA6E3"),
- true = ecb_test("37D06BB516CB7546", "164D5E404F275232", "5F99D04F5B163969"),
- true = ecb_test("1F08260D1AC2465E", "6B056E18759F5CCA", "4A057A3B24D3977B"),
- true = ecb_test("584023641ABA6176", "004BD6EF09176062", "452031C1E4FADA8E"),
- true = ecb_test("025816164629B007", "480D39006EE762F2", "7555AE39F59B87BD"),
- true = ecb_test("49793EBC79B3258F", "437540C8698F3CFA", "53C55F9CB49FC019"),
- true = ecb_test("4FB05E1515AB73A7", "072D43A077075292", "7A8E7BFA937E89A3"),
- true = ecb_test("49E95D6D4CA229BF", "02FE55778117F12A", "CF9C5D7A4986ADB5"),
- true = ecb_test("018310DC409B26D6", "1D9D5C5018F728C2", "D1ABB290658BC778"),
- true = ecb_test("1C587F1C13924FEF", "305532286D6F295A", "55CB3774D13EF201"),
- true = ecb_test("0101010101010101", "0123456789ABCDEF", "FA34EC4847B268B2"),
- true = ecb_test("1F1F1F1F0E0E0E0E", "0123456789ABCDEF", "A790795108EA3CAE"),
- true = ecb_test("E0FEE0FEF1FEF1FE", "0123456789ABCDEF", "C39E072D9FAC631D"),
- true = ecb_test("0000000000000000", "FFFFFFFFFFFFFFFF", "014933E0CDAFF6E4"),
- true = ecb_test("FFFFFFFFFFFFFFFF", "0000000000000000", "F21E9A77B71C49BC"),
- true = ecb_test("0123456789ABCDEF", "0000000000000000", "245946885754369A"),
- true = ecb_test("FEDCBA9876543210", "FFFFFFFFFFFFFFFF", "6B5C5A9C5D9E0A5A"),
- ok.
-
-cbc(doc) ->
- "Test that CBC mode is OK";
-cbc(suite) ->
- [];
-cbc(Config) when is_list(Config) ->
- true = crypto:block_encrypt(blowfish_cbc, ?KEY, ?IVEC, ?DATA_PADDED) =:=
- to_bin("6B77B4D63006DEE605B156E27403979358DEB9E7154616D959F1652BD5FF92CC"),
- ok.
-
-cfb64(doc) ->
- "Test that CFB64 mode is OK";
-cfb64(suite) ->
- [];
-cfb64(Config) when is_list(Config) ->
- true = crypto:block_encrypt(blowfish_cfb64, ?KEY, ?IVEC, ?DATA) =:=
- to_bin("E73214A2822139CAF26ECF6D2EB9E76E3DA3DE04D1517200519D57A6C3"),
- ok.
-
-ofb64(doc) ->
- "Test that OFB64 mode is OK";
-ofb64(suite) ->
- [];
-ofb64(Config) when is_list(Config) ->
- true = crypto:block_encrypt(blowfish_ofb64, ?KEY, ?IVEC, ?DATA) =:=
- to_bin("E73214A2822139CA62B343CC5B65587310DD908D0C241B2263C2CF80DA"),
- ok.
-
-no_ecb(doc) ->
- "Test that ECB mode is disabled";
-no_ecb(suite) ->
- [];
-no_ecb(Config) when is_list(Config) ->
- notsup(fun crypto:block_encrypt/3,
- [blowfish_ecb,
- to_bin("0000000000000000"),
- to_bin("FFFFFFFFFFFFFFFF")]).
-
-no_cbc(doc) ->
- "Test that CBC mode is disabled";
-no_cbc(suite) ->
- [];
-no_cbc(Config) when is_list(Config) ->
- notsup(fun crypto:block_encrypt/4,
- [blowfish_cbc, ?KEY, ?IVEC, ?DATA_PADDED]).
-
-no_cfb64(doc) ->
- "Test that CFB64 mode is disabled";
-no_cfb64(suite) ->
- [];
-no_cfb64(Config) when is_list(Config) ->
- notsup(fun crypto:block_encrypt/4,
- [blowfish_cfb64, ?KEY, ?IVEC, ?DATA]),
- ok.
-
-no_ofb64(doc) ->
- "Test that OFB64 mode is disabled";
-no_ofb64(suite) ->
- [];
-no_ofb64(Config) when is_list(Config) ->
- notsup(fun crypto:block_encrypt/4,
- [blowfish_ofb64, ?KEY, ?IVEC, ?DATA]).
-
-%% Helper functions
-
-%% Assert function fails with notsup error
-notsup(Fun, Args) ->
- ok = try
- {error, {return, apply(Fun, Args)}}
- catch
- error:notsup ->
- ok;
- Class:Error ->
- {error, {Class, Error}}
- end.
-
-
-%% Convert a hexadecimal string to a binary.
--spec(to_bin(L::string()) -> binary()).
-to_bin(L) ->
- to_bin(L, []).
-
-%% @spec dehex(char()) -> integer()
-%% @doc Convert a hex digit to its integer value.
--spec(dehex(char()) -> integer()).
-dehex(C) when C >= $0, C =< $9 ->
- C - $0;
-dehex(C) when C >= $a, C =< $f ->
- C - $a + 10;
-dehex(C) when C >= $A, C =< $F ->
- C - $A + 10.
-
--spec(to_bin(L::string(), list()) -> binary()).
-to_bin([], Acc) ->
- iolist_to_binary(lists:reverse(Acc));
-to_bin([C1, C2 | Rest], Acc) ->
- to_bin(Rest, [(dehex(C1) bsl 4) bor dehex(C2) | Acc]).
-
-m(X,X) -> ok.
diff --git a/lib/crypto/test/crypto.spec b/lib/crypto/test/crypto.spec
index cc09970cb3..4a95275687 100644
--- a/lib/crypto/test/crypto.spec
+++ b/lib/crypto/test/crypto.spec
@@ -1 +1,6 @@
{suites,"../crypto_test",all}.
+
+{skip_suites, "../crypto_test", [crypto_bench_SUITE
+ ],
+ "Benchmarks run separately"}.
+
diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl
index 6c6188f775..ab6d88deb2 100644
--- a/lib/crypto/test/crypto_SUITE.erl
+++ b/lib/crypto/test/crypto_SUITE.erl
@@ -56,6 +56,8 @@ groups() ->
{group, sha3_256},
{group, sha3_384},
{group, sha3_512},
+ {group, blake2b},
+ {group, blake2s},
{group, rsa},
{group, dss},
{group, ecdsa},
@@ -99,6 +101,8 @@ groups() ->
{group, rsa},
{group, dss},
{group, ecdsa},
+ {group, no_ed25519},
+ {group, no_ed448},
{group, dh},
{group, ecdh},
{group, no_srp},
@@ -113,8 +117,8 @@ groups() ->
{group, no_blowfish_cfb64},
{group, no_blowfish_ofb64},
{group, aes_cbc128},
- {group, aes_cfb8},
- {group, aes_cfb128},
+ {group, no_aes_cfb8},
+ {group, no_aes_cfb128},
{group, aes_cbc256},
{group, no_aes_ige256},
{group, no_rc2_cbc},
@@ -137,6 +141,8 @@ groups() ->
{sha3_256, [], [hash, hmac]},
{sha3_384, [], [hash, hmac]},
{sha3_512, [], [hash, hmac]},
+ {blake2b, [], [hash, hmac]},
+ {blake2s, [], [hash, hmac]},
{rsa, [], [sign_verify,
public_encrypt,
private_encrypt,
@@ -156,7 +162,7 @@ groups() ->
]},
{dh, [], [generate_compute,
compute_bug]},
- {ecdh, [], [generate_all_supported, compute, generate]},
+ {ecdh, [], [use_all_elliptic_curves, compute, generate]},
{srp, [], [generate_compute]},
{des_cbc, [], [block]},
{des_cfb, [], [block]},
@@ -183,8 +189,16 @@ groups() ->
{chacha20, [], [stream]},
{poly1305, [], [poly1305]},
{aes_cbc, [], [block]},
+ {no_aes_cfb8,[], [no_support, no_block]},
+ {no_aes_cfb128,[], [no_support, no_block]},
{no_md4, [], [no_support, no_hash]},
{no_md5, [], [no_support, no_hash, no_hmac]},
+ {no_ed25519, [], [no_support, no_sign_verify
+ %% Does not work yet: ,public_encrypt, private_encrypt
+ ]},
+ {no_ed448, [], [no_support, no_sign_verify
+ %% Does not work yet: ,public_encrypt, private_encrypt
+ ]},
{no_ripemd160, [], [no_support, no_hash]},
{no_srp, [], [no_support, no_generate_compute]},
{no_des_cbc, [], [no_support, no_block]},
@@ -251,7 +265,7 @@ init_per_group(fips, Config) ->
enabled = crypto:info_fips(),
FIPSConfig;
false ->
- {skip, "Failed to enable FIPS mode"}
+ {fail, "Failed to enable FIPS mode"}
end;
not_supported ->
{skip, "FIPS mode not supported"}
@@ -401,17 +415,6 @@ block() ->
block(Config) when is_list(Config) ->
Fips = proplists:get_bool(fips, Config),
Type = ?config(type, Config),
- %% See comment about EVP_CIPHER_CTX_set_key_length in
- %% block_crypt_nif in crypto.c.
- case {Fips, Type} of
- {true, aes_cfb8} ->
- throw({skip, "Cannot test aes_cfb8 in FIPS mode because of key length issue"});
- {true, aes_cfb128} ->
- throw({skip, "Cannot test aes_cfb128 in FIPS mode because of key length issue"});
- _ ->
- ok
- end,
-
Blocks = lazy_eval(proplists:get_value(block, Config)),
lists:foreach(fun block_cipher/1, Blocks),
lists:foreach(fun block_cipher/1, block_iolistify(Blocks)),
@@ -500,6 +503,13 @@ sign_verify(Config) when is_list(Config) ->
SignVerify = proplists:get_value(sign_verify, Config),
lists:foreach(fun do_sign_verify/1, SignVerify).
+%%--------------------------------------------------------------------
+no_sign_verify() ->
+ [{doc, "Test disabled sign/verify digital signatures"}].
+no_sign_verify(Config) when is_list(Config) ->
+ [SignVerifyHd|_] = proplists:get_value(sign_verify, Config),
+ notsup(fun do_sign_verify/1, [SignVerifyHd]).
+
%%--------------------------------------------------------------------
public_encrypt() ->
[{doc, "Test public_encrypt/decrypt "}].
@@ -563,32 +573,43 @@ compute(Config) when is_list(Config) ->
Gen = proplists:get_value(compute, Config),
lists:foreach(fun do_compute/1, Gen).
%%--------------------------------------------------------------------
-generate_all_supported() ->
- [{doc, " Test that all curves from crypto:ec_curves/0 returns two binaries"}].
-generate_all_supported(_Config) ->
+use_all_elliptic_curves() ->
+ [{doc, " Test that all curves from crypto:ec_curves/0"}].
+use_all_elliptic_curves(_Config) ->
+ Msg = <<"hello world!">>,
+ Sups = crypto:supports(),
+ Curves = proplists:get_value(curves, Sups),
+ Hashs = proplists:get_value(hashs, Sups),
+ ct:log("Lib: ~p~nFIPS: ~p~nCurves:~n~p~nHashs: ~p", [crypto:info_lib(),
+ crypto:info_fips(),
+ Curves,
+ Hashs]),
Results =
- [try
- crypto:generate_key(ecdh, C)
- of
- {B1,B2} when is_binary(B1) and is_binary(B2) ->
- %% That is, seems like it works as expected.
- {ok,C};
- Err ->
- ct:log("ERROR: Curve ~p generated ~p", [C,Err]),
- {error,{C,Err}}
- catch
- Cls:Err:Stack ->
- ct:log("ERROR: Curve ~p exception ~p:~p~n~p", [C,Cls,Err,Stack]),
- {error,{C,{Cls,Err}}}
- end
- || C <- crypto:ec_curves(),
- not lists:member(C, [ed25519, ed448])
+ [{{Curve,Hash},
+ try
+ {Pub,Priv} = crypto:generate_key(ecdh, Curve),
+ true = is_binary(Pub),
+ true = is_binary(Priv),
+ Sig = crypto:sign(ecdsa, Hash, Msg, [Priv, Curve]),
+ crypto:verify(ecdsa, Hash, Msg, Sig, [Pub, Curve])
+ catch
+ C:E ->
+ {C,E}
+ end}
+ || Curve <- Curves -- [ed25519, ed448, x25519, x448, ipsec3, ipsec4],
+ Hash <- Hashs -- [md4, md5, ripemd160, sha3_224, sha3_256, sha3_384, sha3_512, blake2b, blake2s]
],
- OK = [C || {ok,C} <- Results],
- ct:log("Ok (len=~p): ~p", [length(OK), OK]),
- false = lists:any(fun({error,_}) -> true;
- (_) -> false
- end, Results).
+ Fails =
+ lists:filter(fun({_,true}) -> false;
+ (_) -> true
+ end, Results),
+ case Fails of
+ [] ->
+ ok;
+ _ ->
+ ct:log("Fails:~n~p",[Fails]),
+ ct:fail("Bad curve(s)",[])
+ end.
%%--------------------------------------------------------------------
generate() ->
@@ -970,7 +991,7 @@ do_sign_verify({Type, Hash, Public, Private, Msg, Options}) ->
error:notsup when NotSupLow == true,
is_integer(LibVer),
LibVer < 16#10001000 ->
- %% Thoose opts where introduced in 1.0.1
+ %% Those opts where introduced in 1.0.1
ct:log("notsup but OK in old cryptolib crypto:sign(~p, ~p, ..., ..., ..., ~p)",
[Type,Hash,Options]),
true;
@@ -1427,6 +1448,12 @@ group_config(sha3_384 = Type, Config) ->
group_config(sha3_512 = Type, Config) ->
{Msgs,Digests} = sha3_test_vectors(Type),
[{hash, {Type, Msgs, Digests}}, {hmac, hmac_sha3(Type)} | Config];
+group_config(blake2b = Type, Config) ->
+ {Msgs, Digests} = blake2_test_vectors(Type),
+ [{hash, {Type, Msgs, Digests}}, {hmac, blake2_hmac(Type)} | Config];
+group_config(blake2s = Type, Config) ->
+ {Msgs, Digests} = blake2_test_vectors(Type),
+ [{hash, {Type, Msgs, Digests}}, {hmac, blake2_hmac(Type)} | Config];
group_config(rsa, Config) ->
Msg = rsa_plain(),
Public = rsa_public(),
@@ -1693,6 +1720,71 @@ rfc_1321_md5_digests() ->
hexstr2bin("d174ab98d277d9f5a5611c2c9f419d9f"),
hexstr2bin("57edf4a22be3c955ac49da2e2107b67a")].
+
+%% BLAKE2 re-use SHA3 test vectors.
+blake2_test_vectors(blake2b) ->
+ {sha3_msgs(),
+ [ <<186,128,165,63,152,28,77,13,106,39,151,182,159,18,246,233,76,33,47,20,104,90,196,183,75,18,187,111,219,255,162,209,125,135,197,57,42,171,121,45,194,82,213,222,69,51,204,149,24,211,138,168,219,241,146,90,185,35,134,237,212,0,153,35>>
+ , <<120,106,2,247,66,1,89,3,198,198,253,133,37,82,210,114,145,47,71,64,225,88,71,97,138,134,226,23,247,31,84,25,210,94,16,49,175,238,88,83,19,137,100,68,147,78,176,75,144,58,104,91,20,72,183,85,213,111,112,26,254,155,226,206>>
+ , <<114,133,255,62,139,215,104,214,155,230,43,59,241,135,101,163,37,145,127,169,116,74,194,245,130,162,8,80,188,43,17,65,237,27,62,69,40,89,90,204,144,119,43,223,45,55,220,138,71,19,11,68,243,58,2,232,115,14,90,216,225,102,232,136>>
+ , <<206,116,26,197,147,15,227,70,129,17,117,197,34,123,183,191,205,71,244,38,18,250,228,108,8,9,81,79,158,14,58,17,238,23,115,40,113,71,205,234,238,223,245,7,9,170,113,99,65,254,101,36,15,74,214,119,125,107,250,249,114,110,94,82>>
+ , <<152,251,62,251,114,6,253,25,235,246,155,111,49,44,247,182,78,59,148,219,225,161,113,7,145,57,117,167,147,241,119,225,208,119,96,157,127,186,54,60,187,160,13,5,247,170,78,79,168,113,93,100,40,16,76,10,117,100,59,15,243,253,62,175>>
+ ]};
+blake2_test_vectors(blake2s) ->
+ {sha3_msgs(),
+ [ <<80,140,94,140,50,124,20,226,225,167,43,163,78,235,69,47,55,69,139,32,158,214,58,41,77,153,155,76,134,103,89,130>>
+ , <<105,33,122,48,121,144,128,148,225,17,33,208,66,53,74,124,31,85,182,72,44,161,165,30,27,37,13,253,30,208,238,249>>
+ , <<111,77,245,17,106,111,51,46,218,177,217,225,14,232,125,246,85,123,234,182,37,157,118,99,243,188,213,114,44,19,241,137>>
+ , <<53,141,210,237,7,128,212,5,78,118,203,111,58,91,206,40,65,232,226,245,71,67,29,77,9,219,33,182,109,148,31,199>>
+ , <<190,192,192,230,205,229,182,122,203,115,184,31,121,166,122,64,121,174,28,96,218,201,210,102,26,241,142,159,139,80,223,165>>
+ ]}.
+
+blake2_hmac(Type) ->
+ {Ks, Ds, Hs} = lists:unzip3(
+ [ {hexstr2bin(K), hexstr2bin(D), H}
+ || {{K, D}, H} <- lists:zip(blake2_hmac_key_data(), blake2_hmac_hmac(Type)) ]),
+ {Type, Ks, Ds, Hs}.
+
+blake2_hmac_key_data() ->
+ [ {"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b 0b0b0b0b",
+ "4869205468657265"}
+ , {"4a656665",
+ "7768617420646f2079612077616e7420 666f72206e6f7468696e673f"}
+ , {"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaa",
+ "dddddddddddddddddddddddddddddddd dddddddddddddddddddddddddddddddd dddddddddddddddddddddddddddddddd dddd"}
+ , {"0102030405060708090a0b0c0d0e0f10 111213141516171819",
+ "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd cdcd"}
+ , {"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaa",
+ "54657374205573696e67204c61726765 72205468616e20426c6f636b2d53697a 65204b6579202d2048617368204b6579 204669727374"}
+ , {"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaa",
+ "54657374205573696e67204c61726765 72205468616e20426c6f636b2d53697a 65204b6579202d2048617368204b6579 204669727374"}
+ , {"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaa",
+ "54686973206973206120746573742075 73696e672061206c6172676572207468 616e20626c6f636b2d73697a65206b65 7920616e642061206c61726765722074 68616e20626c6f636b2d73697a652064 6174612e20546865206b6579206e6565 647320746f2062652068617368656420 6265666f7265206265696e6720757365 642062792074686520484d414320616c 676f726974686d2e"}
+ , {"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaa",
+ "54686973206973206120746573742075 73696e672061206c6172676572207468 616e20626c6f636b2d73697a65206b65 7920616e642061206c61726765722074 68616e20626c6f636b2d73697a652064 6174612e20546865206b6579206e6565 647320746f2062652068617368656420 6265666f7265206265696e6720757365 642062792074686520484d414320616c 676f726974686d2e"}
+ ].
+
+blake2_hmac_hmac(blake2b) ->
+ [ <<53,138,106,24,73,36,137,79,195,75,238,86,128,238,223,87,216,74,55,187,56,131,47,40,142,59,39,220,99,169,140,200,201,30,118,218,71,107,80,139,198,178,212,8,162,72,133,116,82,144,110,74,32,180,140,107,75,85,210,223,15,225,221,36>>
+ , <<111,248,132,248,221,194,166,88,107,60,152,164,205,110,189,241,78,193,2,4,182,113,0,115,235,88,101,173,227,122,38,67,184,128,124,19,53,209,7,236,219,159,254,174,182,130,140,70,37,186,23,44,102,55,158,252,210,34,194,222,17,114,122,180>>
+ , <<244,59,198,44,122,153,53,60,59,44,96,232,239,36,251,189,66,233,84,120,102,220,156,91,228,237,198,244,167,212,188,10,198,32,194,198,0,52,208,64,240,219,175,134,249,233,205,120,145,160,149,89,94,237,85,226,169,150,33,95,12,21,192,24>>
+ , <<229,219,182,222,47,238,66,161,202,160,110,78,123,132,206,64,143,250,92,74,157,226,99,46,202,118,156,222,136,117,1,76,114,208,114,15,234,245,63,118,230,161,128,53,127,82,141,123,244,132,250,58,20,232,204,31,15,59,173,167,23,180,52,145>>
+ , <<165,75,41,67,178,162,2,39,212,28,164,108,9,69,175,9,188,31,174,251,47,73,137,76,35,174,188,85,127,183,156,72,137,220,167,68,8,220,134,80,134,102,122,237,238,74,49,133,197,58,73,200,11,129,76,76,88,19,234,12,139,56,168,248>>
+ , <<180,214,140,139,182,82,151,170,52,132,168,110,29,51,183,138,70,159,33,234,170,158,212,218,159,236,145,218,71,23,34,61,44,15,163,134,170,47,209,241,255,207,89,23,178,103,84,96,53,237,48,238,164,178,19,162,133,148,211,211,169,179,140,170>>
+ , <<171,52,121,128,166,75,94,130,93,209,14,125,50,253,67,160,26,142,109,234,38,122,185,173,125,145,53,36,82,102,24,146,83,17,175,188,176,196,149,25,203,235,221,112,149,64,168,215,37,251,145,26,194,174,233,178,163,170,67,215,150,18,51,147>>
+ , <<97,220,242,140,166,12,169,92,130,89,147,39,171,215,169,161,152,111,242,219,211,199,73,69,198,227,35,186,203,76,159,26,94,103,82,93,20,186,141,98,36,177,98,229,102,23,21,37,83,3,69,169,178,86,8,178,125,251,163,180,146,115,213,6>>
+ ];
+blake2_hmac_hmac(blake2s) ->
+ [ <<101,168,183,197,204,145,54,212,36,232,44,55,226,112,126,116,233,19,192,101,91,153,199,95,64,237,243,135,69,58,50,96>>
+ , <<144,182,40,30,47,48,56,201,5,106,240,180,167,231,99,202,230,254,93,158,180,56,106,14,201,82,55,137,12,16,79,240>>
+ , <<252,196,245,149,41,80,46,52,195,216,218,63,253,171,130,150,106,44,182,55,255,94,155,215,1,19,92,46,148,105,231,144>>
+ , <<70,68,52,220,190,206,9,93,69,106,29,98,214,236,86,248,152,230,37,163,158,92,82,189,249,77,175,17,27,173,131,170>>
+ , <<210,61,121,57,79,83,213,54,160,150,230,81,68,71,238,170,187,5,222,208,27,227,44,25,55,218,106,143,113,3,188,78>>
+ , <<92,76,83,46,110,69,89,83,133,78,21,16,149,38,110,224,127,213,88,129,190,223,139,57,8,217,95,13,190,54,159,234>>
+ , <<203,96,246,167,145,241,64,191,138,162,229,31,243,88,205,178,204,92,3,51,4,91,127,183,122,186,122,179,176,207,178,55>>
+ , <<190,53,233,217,99,171,215,108,1,184,171,181,22,36,240,209,16,96,16,92,213,22,16,58,114,241,117,214,211,189,30,202>>
+ ].
+
%%% https://www.di-mgt.com.au/sha_testvectors.html
sha3_msgs() ->
["abc",
diff --git a/lib/crypto/test/crypto_bench.spec b/lib/crypto/test/crypto_bench.spec
new file mode 100644
index 0000000000..b9a26d94db
--- /dev/null
+++ b/lib/crypto/test/crypto_bench.spec
@@ -0,0 +1,3 @@
+{suites, "../crypto_test", [
+ crypto_bench_SUITE
+ ]}.
diff --git a/lib/crypto/test/crypto_bench_SUITE.erl b/lib/crypto/test/crypto_bench_SUITE.erl
new file mode 100644
index 0000000000..c66a27f0c8
--- /dev/null
+++ b/lib/crypto/test/crypto_bench_SUITE.erl
@@ -0,0 +1,400 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009-2018. All 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(crypto_bench_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include_lib("common_test/include/ct_event.hrl").
+-include_lib("common_test/include/ct.hrl").
+
+suite() -> [%%{ct_hooks,[{ts_install_cth,[{nodenames,2}]}]},
+ {timetrap,{minutes,2}}
+ ].
+
+all() ->
+ [
+ {group, textblock_256}
+ ].
+
+groups() ->
+ [
+ {textblock_256, [], [
+ {group, ciphers_128},
+ {group, ciphers_256}
+ ]},
+
+ {ciphers_128, [{repeat, 5}], [
+ block,
+ stream
+ ]},
+
+ {ciphers_256, [{repeat, 5}], [
+ block,
+ stream,
+ chacha
+ ]}
+ ].
+
+%%%----------------------------------------------------------------
+%%%
+init_per_suite(Config0) ->
+ try crypto:start() of
+ _ ->
+ [{_,_,Info}] = crypto:info_lib(),
+ ct:comment("~s",[Info]),
+ ct:pal("Crypto version: ~p~n~n~p",[Info,crypto:supports()]),
+ Config1 = measure_openssl_aes_cbc([128,256], Config0),
+ calibrate([{sec_goal,10} | Config1])
+
+ catch _:_ ->
+ {fail, "Crypto did not start"}
+ end.
+
+end_per_suite(_Config) ->
+ application:stop(crypto).
+
+%%%----------------------------------------------------------------
+%%%
+init_per_group(Group, Config) ->
+ case atom_to_list(Group) of
+ "ciphers_"++KeySizeStr ->
+ KeySize = list_to_integer(KeySizeStr),
+ [{key_size,KeySize} | Config];
+
+ "textblock_"++BlockSizeStr ->
+ BlockSize = list_to_integer(BlockSizeStr),
+ [{block_size,BlockSize} | Config];
+
+ _ ->
+ Config
+ end.
+
+end_per_group(_Group, Config) ->
+ Config.
+
+
+measure_openssl_aes_cbc(KeySizes, Config) ->
+ BLno_acc = [baseline(aes_cbc, KeySize, false) || KeySize <- KeySizes],
+ ct:pal("Non-accelerated baseline encryption time [µs/block]:~n~p", [BLno_acc]),
+ BLacc = [baseline(aes_cbc, KeySize, true) || KeySize <- KeySizes],
+ ct:pal("Possibly accelerated baseline encryption time [µs/block]:~n~p", [BLacc]),
+ [{acc,BLacc},
+ {no_acc,BLno_acc} | Config].
+
+calibrate(Config) ->
+ Secs = proplists:get_value(sec_goal, Config, 10),
+ {_,Empty} = data(empty, 0, 0),
+ {Ne,Te} = run1(Secs*3000, Empty),
+ report(["Overhead"], Te/Ne),
+ [{overhead,Te/Ne} | Config].
+
+%%%================================================================
+%%%
+%%%
+block(Config) ->
+ run_cryptos([aes_cbc, aes_gcm, aes_ccm],
+ Config).
+
+stream(Config) ->
+ run_cryptos([aes_ctr],
+ Config).
+
+chacha(Config) ->
+ run_cryptos([chacha20, chacha20_poly1305],
+ Config).
+
+
+%%%================================================================
+%%%
+%%%
+
+run_cryptos(Cryptos, Config) ->
+ KeySize = proplists:get_value(key_size, Config),
+ BlockSize = proplists:get_value(block_size, Config),
+ MilliSecGoal = 1000*proplists:get_value(sec_goal,Config),
+ OverHead = proplists:get_value(overhead, Config, 0),
+ [try
+ TimePerOpBrutto = run(Crypto,KeySize,BlockSize,MilliSecGoal),
+ %% ct:pal("Brutto: ~p Overhead: ~p (~.2f %) Netto: ~p",
+ %% [TimePerOpBrutto, OverHead, 100*OverHead/TimePerOpBrutto,TimePerOpBrutto - OverHead]),
+ TimePerOpBrutto - OverHead
+ of
+ TimePerOp -> % µs
+ %% First, Report speed of encrypting blocks of 1000. [blocks/sec]
+ ReportUnit = 1000,
+ Label = [fmt(Crypto)," key:",KeySize," block:",BlockSize],
+ report(Label,
+ (BlockSize/ReportUnit)*1000000/TimePerOp
+ ),
+
+ EffCrypto = case Crypto of
+ X -> X
+ end,
+ %% Percent of accelerated speed
+ case find_value([acc,{EffCrypto,KeySize},BlockSize], Config) of
+ undefined ->
+ ok;
+ TimePerOpBaseAcc ->
+ report(["Percent of acc OpenSSL "|Label],
+ 100*TimePerOpBaseAcc/TimePerOp % Percent of base *speed*
+ )
+ end,
+
+ %% Percent of non-accelerated speed
+ case find_value([no_acc,{EffCrypto,KeySize},BlockSize], Config) of
+ undefined ->
+ ok;
+ TimePerOpBaseNoAcc ->
+ report(["Percent of noacc OpenSSL "|Label],
+ 100*TimePerOpBaseNoAcc/TimePerOp % Percent of base *speed*
+ )
+ end
+ catch
+ _:_ ->
+ ct:pal("~p unsupported",[{Crypto,KeySize,BlockSize}])
+ end
+ || Crypto <- Cryptos,
+ supported(Crypto)
+ ].
+
+
+run(Crypto, KeySize, BlockSize, MilliSecGoal) ->
+ {_Type, Funs} = data(Crypto, KeySize, BlockSize),
+ {Nc,Tc} = run1(MilliSecGoal, Funs),
+ Tc/Nc.
+
+fmt(X) -> X.
+
+
+find_value(KeyPath, PropList, Default) ->
+ try find_value(KeyPath, PropList)
+ of
+ undefined -> Default
+ catch
+ error:function_clause -> Default
+ end.
+
+find_value(KeyPath, PropList) ->
+ lists:foldl(fun(K, L) when is_list(L) -> proplists:get_value(K,L);
+ (_, _) -> undefined
+ end, PropList, KeyPath).
+
+%%%================================================================
+%%%
+%%%
+funs({block, {Type, Key, IV, Block}}) ->
+ {fun() -> ok end,
+ fun(_) -> crypto:block_encrypt(Type, Key, IV, Block) end,
+ fun(_) -> ok end};
+
+funs({stream, {Type, Key, IV, Block}}) ->
+ {fun() -> {crypto:stream_init(Type, Key, IV),ok} end,
+ fun({Ctx,_}) -> crypto:stream_encrypt(Ctx, Block) end,
+ fun(_) -> ok end}.
+
+
+data(aes_cbc, KeySize, BlockSize) ->
+ Type = case KeySize of
+ 128 -> aes_cbc128;
+ 256 -> aes_cbc256
+ end,
+ Key = mk_bin(KeySize div 8),
+ IV = mk_bin(16),
+ Block = mk_bin(BlockSize),
+ {Type, funs({block, {Type, Key, IV, Block}})};
+
+data(aes_gcm, KeySize, BlockSize) ->
+ Type = aes_gcm,
+ Key = mk_bin(KeySize div 8),
+ IV = mk_bin(12),
+ Block = mk_bin(BlockSize),
+ AAD = <<01,02,03,04>>,
+ {Type, funs({block, {Type, Key, IV, {AAD,Block,16}}})};
+
+data(aes_ccm, KeySize, BlockSize) ->
+ Type = aes_ccm,
+ Key = mk_bin(KeySize div 8),
+ IV = mk_bin(12),
+ Block = mk_bin(BlockSize),
+ AAD = <<01,02,03,04>>,
+ {Type, funs({block, {Type, Key, IV, {AAD,Block,12}}})};
+
+data(aes_ctr, KeySize, BlockSize) ->
+ Type = aes_ctr,
+ Key = mk_bin(KeySize div 8),
+ IV = mk_bin(16),
+ Block = mk_bin(BlockSize),
+ {Type, funs({stream, {Type, Key, IV, Block}})};
+
+data(chacha20_poly1305, 256=KeySize, BlockSize) ->
+ Type = chacha20_poly1305,
+ Key = mk_bin(KeySize div 8),
+ IV = mk_bin(16),
+ AAD = <<01,02,03,04>>,
+ Block = mk_bin(BlockSize),
+ {Type, funs({block, {Type, Key, IV, {AAD,Block}}})};
+
+data(chacha20, 256=KeySize, BlockSize) ->
+ Type = chacha20,
+ Key = mk_bin(KeySize div 8),
+ IV = mk_bin(16),
+ Block = mk_bin(BlockSize),
+ {Type, funs({stream, {Type, Key, IV, Block}})};
+
+data(empty, 0, 0) ->
+ {undefined,
+ {fun() -> ok end,
+ fun(X) -> X end,
+ fun(_) -> ok end}}.
+
+%%%================================================================
+%%%
+%%%
+run1(MilliSecGoal, Funs) ->
+ Parent = self(),
+ Pid = spawn(fun() ->
+ {Fi,Fu,Ff} = Funs,
+ Ctx0 = Fi(),
+ erlang:garbage_collect(),
+ T0 = start_time(),
+ {N,Ctx} = loop(Fu, Ctx0, 0),
+ T = elapsed_time(T0),
+ Ff(Ctx),
+ Parent ! {result,N,microseconds(T)}
+ end),
+ Pid ! go,
+ receive
+ after MilliSecGoal ->
+ Pid ! stop
+ end,
+ receive
+ {result,N,MicroSecs} ->
+ {N,MicroSecs}
+ end.
+
+
+loop(F, Ctx, N) ->
+ receive
+ stop ->
+ {N, Ctx}
+ after 0 ->
+ loop(F, F(Ctx), N+1)
+ end.
+
+%%%----------------------------------------------------------------
+report(LabelList, Value) ->
+ Label = report_chars(lists:concat(LabelList)),
+ ct:pal("ct_event:notify ~p: ~p", [Label, Value]),
+ ct_event:notify(
+ #event{name = benchmark_data,
+ data = [{name, Label},
+ {value,Value}]}).
+
+report_chars(Cs) ->
+ [case C of
+ $- -> $_;
+ _ -> C
+ end || C <- Cs].
+
+%%%----------------------------------------------------------------
+supported(Algorithm) ->
+ lists:member(Algorithm,
+ [A || {_,As} <- crypto:supports(), A <- As]
+ ).
+
+%%%----------------------------------------------------------------
+start_time() ->
+ erlang:system_time().
+
+elapsed_time(StartTime) ->
+ erlang:system_time() - StartTime.
+
+microseconds(Time) ->
+ erlang:convert_time_unit(Time, native, microsecond).
+
+%%%----------------------------------------------------------------
+
+%% Example output:
+%% +DT:aes-128-cbc:3:16
+%% +R:135704772:aes-128-cbc:2.980000
+%% +DT:aes-128-cbc:3:64
+%% +R:36835089:aes-128-cbc:3.000000
+%% +DT:aes-128-cbc:3:256
+%% +R:9398616:aes-128-cbc:3.000000
+%% +DT:aes-128-cbc:3:1024
+%% +R:2355683:aes-128-cbc:2.990000
+%% +DT:aes-128-cbc:3:8192
+%% +R:294508:aes-128-cbc:2.990000
+%% +H:16:64:256:1024:8192
+%% +F:22:aes-128-cbc:728616225.50:785815232.00:802015232.00:806762338.46:806892821.40
+
+baseline(Crypto, KeySize, EVP) ->
+ Spec=
+ case {Crypto,KeySize} of
+ {aes_cbc, 128} -> "aes-128-cbc";
+ {aes_cbc, 256} -> "aes-256-cbc"
+ end,
+ {{Crypto,KeySize}, baseline(Spec, EVP)}.
+
+baseline(Spec, EVP) ->
+ Cmd =
+ case EVP of
+ true -> "openssl speed -mr -evp " ++ Spec;
+ false-> "openssl speed -mr " ++ Spec
+ end,
+ get_base_values(string:tokens(os:cmd(Cmd),"\n"), Spec, []).
+
+
+get_base_values(["+DT:"++Sdt,
+ "+R:"++Sr
+ |T], Crypto, Acc) ->
+ [Crypto0,_GoalSecs0,BlockSize0] = string:tokens(Sdt, ":"),
+ [Nblocks0,Crypto0,RealSecs0] = string:tokens(Sr, ":"),
+ Crypto = fix_possible_space_bug(Crypto0),
+ RealSecs = list_to_float(RealSecs0),
+ BlockSize = list_to_integer(BlockSize0),
+ Nblocks = list_to_integer(Nblocks0),
+ get_base_values(T, Crypto, [{BlockSize, 1000000*RealSecs/Nblocks} | Acc]);
+
+get_base_values([_|T], Crypto, Acc) ->
+ get_base_values(T, Crypto, Acc);
+
+get_base_values([], _, Acc) ->
+ lists:sort(Acc).
+
+fix_possible_space_bug(S) -> lists:concat(lists:join("-",string:tokens(S,"- "))).
+
+%%%----------------------------------------------------------------
+mk_bin(Size) when Size =< 256 ->
+ list_to_binary(lists:seq(0,Size-1));
+
+mk_bin(Size) when 1024 =< Size ->
+ B = mk_bin(Size div 4),
+ Brest = mk_bin(Size rem 4),
+ <<B/binary, B/binary, B/binary, B/binary, Brest/binary>>;
+
+mk_bin(Size) when 256 < Size ->
+ B = mk_bin(Size div 2),
+ Brest = mk_bin(Size rem 2),
+ <<B/binary, B/binary, Brest/binary>>.
+
diff --git a/lib/crypto/test/engine_SUITE.erl b/lib/crypto/test/engine_SUITE.erl
index 8a45fc9076..3416fbd78d 100644
--- a/lib/crypto/test/engine_SUITE.erl
+++ b/lib/crypto/test/engine_SUITE.erl
@@ -345,13 +345,13 @@ engine_list(Config) when is_list(Config) ->
{skip, "OTP Test engine not found"};
{ok, Engine} ->
try
- EngineList0 = crypto:engine_list(),
case crypto:engine_load(<<"dynamic">>,
[{<<"SO_PATH">>, Engine},
<<"LOAD">>],
[]) of
{ok, E} ->
EngineList0 = crypto:engine_list(),
+ false = lists:member(<<"MD5">>, EngineList0),
ok = crypto:engine_add(E),
[<<"MD5">>] = lists:subtract(crypto:engine_list(), EngineList0),
ok = crypto:engine_remove(E),
@@ -674,7 +674,7 @@ ensure_load(Config) when is_list(Config) ->
end.
%%%----------------------------------------------------------------
-%%% Pub/priv key storage tests. Thoose are for testing the crypto.erl
+%%% Pub/priv key storage tests. Those are for testing the crypto.erl
%%% support for using priv/pub keys stored in an engine.
sign_verify_rsa(Config) ->
diff --git a/lib/debugger/doc/src/debugger.xml b/lib/debugger/doc/src/debugger.xml
index 1ecdbcd064..77285095e7 100644
--- a/lib/debugger/doc/src/debugger.xml
+++ b/lib/debugger/doc/src/debugger.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>debugger</module>
+ <module since="">debugger</module>
<modulesummary>Erlang Debugger.</modulesummary>
<description>
<p>Erlang Debugger for debugging and testing of Erlang programs.</p>
@@ -36,10 +36,10 @@
<funcs>
<func>
- <name>start()</name>
- <name>start(File)</name>
- <name>start(Mode)</name>
- <name>start(Mode, File)</name>
+ <name since="">start()</name>
+ <name since="">start(File)</name>
+ <name since="">start(Mode)</name>
+ <name since="">start(Mode, File)</name>
<fsummary>Start Debugger.</fsummary>
<type>
<v>Mode = local | global</v>
@@ -60,7 +60,7 @@
</func>
<func>
- <name>quick(Module, Name, Args)</name>
+ <name since="">quick(Module, Name, Args)</name>
<fsummary>Debug a process.</fsummary>
<type>
<v>Module = Name = atom()</v>
diff --git a/lib/debugger/doc/src/i.xml b/lib/debugger/doc/src/i.xml
index 628b91e9e4..06b0eb876a 100644
--- a/lib/debugger/doc/src/i.xml
+++ b/lib/debugger/doc/src/i.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>i</module>
+ <module since="">i</module>
<modulesummary>Debugger/Interpreter Interface.</modulesummary>
<description>
<p>The <c>i</c> module provides short forms for some of
@@ -51,7 +51,7 @@
<funcs>
<func>
- <name>im() -> pid()</name>
+ <name since="">im() -> pid()</name>
<fsummary>Start a graphical monitor.</fsummary>
<desc>
<p>Starts a new graphical monitor. This is the Monitor window,
@@ -63,10 +63,10 @@
</func>
<func>
- <name>ii(AbsModules) -> ok</name>
- <name>ii(AbsModule) -> {module, Module} | error</name>
- <name>ini(AbsModules) -> ok</name>
- <name>ini(AbsModule) -> {module, Module} | error</name>
+ <name since="">ii(AbsModules) -> ok</name>
+ <name since="">ii(AbsModule) -> {module, Module} | error</name>
+ <name since="">ini(AbsModules) -> ok</name>
+ <name since="">ini(AbsModule) -> {module, Module} | error</name>
<fsummary>Interpret a module.</fsummary>
<type>
<v>AbsModules = [AbsModule]</v>
@@ -84,8 +84,8 @@
</func>
<func>
- <name>iq(AbsModule) -> ok</name>
- <name>inq(AbsModule) -> ok</name>
+ <name since="">iq(AbsModule) -> ok</name>
+ <name since="">inq(AbsModule) -> ok</name>
<fsummary>Stop interpreting a module.</fsummary>
<type>
<v>AbsModule = Module | File</v>
@@ -100,7 +100,7 @@
</func>
<func>
- <name>il() -> ok</name>
+ <name since="">il() -> ok</name>
<fsummary>Make a printout of all interpreted modules</fsummary>
<desc>
<p>Makes a printout of all interpreted modules.
@@ -110,7 +110,7 @@
</func>
<func>
- <name>ip() -> ok</name>
+ <name since="">ip() -> ok</name>
<fsummary>Print the current status of all interpreted
processes.</fsummary>
<desc>
@@ -119,7 +119,7 @@
</func>
<func>
- <name>ic() -> ok</name>
+ <name since="">ic() -> ok</name>
<fsummary>Clear information about processes executing interpreted
code.</fsummary>
<desc>
@@ -129,8 +129,8 @@
</func>
<func>
- <name>iaa(Flags) -> true</name>
- <name>iaa(Flags, Function) -> true</name>
+ <name since="">iaa(Flags) -> true</name>
+ <name since="">iaa(Flags, Function) -> true</name>
<fsummary>Set when and how to attach to a process.</fsummary>
<type>
<v>Flags = [init | break | exit]</v>
@@ -148,7 +148,7 @@
</func>
<func>
- <name>ist(Flag) -> true</name>
+ <name since="">ist(Flag) -> true</name>
<fsummary>Set how to save call frames.</fsummary>
<type>
<v>Flag = all | no_tail | false</v>
@@ -160,7 +160,7 @@
</func>
<func>
- <name>ia(Pid) -> ok | no_proc</name>
+ <name since="">ia(Pid) -> ok | no_proc</name>
<fsummary>Attache to a process.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -172,7 +172,7 @@
</func>
<func>
- <name>ia(X,Y,Z) -> ok | no_proc</name>
+ <name since="">ia(X,Y,Z) -> ok | no_proc</name>
<fsummary>Attache to a process.</fsummary>
<type>
<v>X = Y = Z = int()</v>
@@ -184,7 +184,7 @@
</func>
<func>
- <name>ia(Pid, Function) -> ok | no_proc</name>
+ <name since="">ia(Pid, Function) -> ok | no_proc</name>
<fsummary>Attache to a process.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -199,7 +199,7 @@
</func>
<func>
- <name>ia(X,Y,Z, Function) -> ok | no_proc</name>
+ <name since="">ia(X,Y,Z, Function) -> ok | no_proc</name>
<fsummary>Attache to a process.</fsummary>
<type>
<v>X = Y = Z = int()</v>
@@ -217,7 +217,7 @@
</func>
<func>
- <name>ib(Module, Line) -> ok | {error, break_exists}</name>
+ <name since="">ib(Module, Line) -> ok | {error, break_exists}</name>
<fsummary>Create a breakpoint.</fsummary>
<type>
<v>Module = atom()</v>
@@ -229,7 +229,7 @@
</func>
<func>
- <name>ib(Module, Name, Arity) -> ok | {error, function_not_found}
+ <name since="">ib(Module, Name, Arity) -> ok | {error, function_not_found}
</name>
<fsummary>Create breakpoints in the specified function.</fsummary>
<type>
@@ -243,7 +243,7 @@
</func>
<func>
- <name>ir() -> ok</name>
+ <name since="">ir() -> ok</name>
<fsummary>Delete all breakpoints.</fsummary>
<desc>
<p>Deletes all breakpoints.</p>
@@ -251,7 +251,7 @@
</func>
<func>
- <name>ir(Module) -> ok</name>
+ <name since="">ir(Module) -> ok</name>
<fsummary>Delete all breakpoints in a module.</fsummary>
<type>
<v>Module = atom()</v>
@@ -262,7 +262,7 @@
</func>
<func>
- <name>ir(Module, Line) -> ok</name>
+ <name since="">ir(Module, Line) -> ok</name>
<fsummary>Delete a breakpoint.</fsummary>
<type>
<v>Module = atom()</v>
@@ -274,7 +274,7 @@
</func>
<func>
- <name>ir(Module, Name, Arity) -> ok | {error, function_not_found}
+ <name since="">ir(Module, Name, Arity) -> ok | {error, function_not_found}
</name>
<fsummary>Delete breakpoints from the specified function.</fsummary>
<type>
@@ -288,7 +288,7 @@
</func>
<func>
- <name>ibd(Module, Line) -> ok</name>
+ <name since="">ibd(Module, Line) -> ok</name>
<fsummary>Make a breakpoint inactive.</fsummary>
<type>
<v>Module = atom()</v>
@@ -300,7 +300,7 @@
</func>
<func>
- <name>ibe(Module, Line) -> ok</name>
+ <name since="">ibe(Module, Line) -> ok</name>
<fsummary>Make a breakpoint active.</fsummary>
<type>
<v>Module = atom()</v>
@@ -312,7 +312,7 @@
</func>
<func>
- <name>iba(Module, Line, Action) -> ok</name>
+ <name since="">iba(Module, Line, Action) -> ok</name>
<fsummary>Set the trigger action of a breakpoint.</fsummary>
<type>
<v>Module = atom()</v>
@@ -326,7 +326,7 @@
</func>
<func>
- <name>ibc(Module, Line, Function) -> ok</name>
+ <name since="">ibc(Module, Line, Function) -> ok</name>
<fsummary>Set the conditional test of a breakpoint.</fsummary>
<type>
<v>Module = atom()</v>
@@ -348,7 +348,7 @@
</func>
<func>
- <name>ipb() -> ok</name>
+ <name since="">ipb() -> ok</name>
<fsummary>Print all existing breakpoints.</fsummary>
<desc>
<p>Prints all existing breakpoints.</p>
@@ -356,7 +356,7 @@
</func>
<func>
- <name>ipb(Module) -> ok</name>
+ <name since="">ipb(Module) -> ok</name>
<fsummary>Print all existing breakpoints in a module.</fsummary>
<type>
<v>Module = atom()</v>
@@ -367,7 +367,7 @@
</func>
<func>
- <name>iv() -> atom()</name>
+ <name since="">iv() -> atom()</name>
<fsummary>Return the current version number of the interpreter.
</fsummary>
<desc>
@@ -377,7 +377,7 @@
</func>
<func>
- <name>help() -> ok</name>
+ <name since="">help() -> ok</name>
<fsummary>Print help text.</fsummary>
<desc>
<p>Prints help text.</p>
diff --git a/lib/debugger/doc/src/int.xml b/lib/debugger/doc/src/int.xml
index 31e9dfe923..a0078714e6 100644
--- a/lib/debugger/doc/src/int.xml
+++ b/lib/debugger/doc/src/int.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>int</module>
+ <module since="">int</module>
<modulesummary>Interpreter Interface.</modulesummary>
<description>
<p>The Erlang interpreter provides mechanisms for breakpoints and
@@ -94,10 +94,10 @@
<funcs>
<func>
- <name>i(AbsModule) -> {module,Module} | error</name>
- <name>i(AbsModules) -> ok</name>
- <name>ni(AbsModule) -> {module,Module} | error</name>
- <name>ni(AbsModules) -> ok</name>
+ <name since="">i(AbsModule) -> {module,Module} | error</name>
+ <name since="">i(AbsModules) -> ok</name>
+ <name since="">ni(AbsModule) -> {module,Module} | error</name>
+ <name since="">ni(AbsModules) -> ok</name>
<fsummary>Interpret a module.</fsummary>
<type>
<v>AbsModules = [AbsModule]</v>
@@ -144,8 +144,8 @@
</func>
<func>
- <name>n(AbsModule) -> ok</name>
- <name>nn(AbsModule) -> ok</name>
+ <name since="">n(AbsModule) -> ok</name>
+ <name since="">nn(AbsModule) -> ok</name>
<fsummary>Stop interpreting a module.</fsummary>
<type>
<v>AbsModule = Module | File | [Module | File]</v>
@@ -163,7 +163,7 @@
</func>
<func>
- <name>interpreted() -> [Module]</name>
+ <name since="">interpreted() -> [Module]</name>
<fsummary>Get all interpreted modules.</fsummary>
<type>
<v>Module = atom()</v>
@@ -174,7 +174,7 @@
</func>
<func>
- <name>file(Module) -> File | {error,not_loaded}</name>
+ <name since="">file(Module) -> File | {error,not_loaded}</name>
<fsummary>Get the filename for an interpreted module.</fsummary>
<type>
<v>Module = atom()</v>
@@ -187,7 +187,7 @@
</func>
<func>
- <name>interpretable(AbsModule) -> true | {error,Reason}</name>
+ <name since="">interpretable(AbsModule) -> true | {error,Reason}</name>
<fsummary>Check if a module can be interpreted.</fsummary>
<type>
<v>AbsModule = Module | File</v>
@@ -255,9 +255,9 @@
</func>
<func>
- <name>auto_attach() -> false | {Flags,Function}</name>
- <name>auto_attach(false)</name>
- <name>auto_attach(Flags, Function)</name>
+ <name since="">auto_attach() -> false | {Flags,Function}</name>
+ <name since="">auto_attach(false)</name>
+ <name since="">auto_attach(Flags, Function)</name>
<fsummary>Get and set when and how to attach to a process.</fsummary>
<type>
<v>Flags = [init | break | exit]</v>
@@ -290,8 +290,8 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>stack_trace() -> Flag</name>
- <name>stack_trace(Flag)</name>
+ <name since="">stack_trace() -> Flag</name>
+ <name since="">stack_trace(Flag)</name>
<fsummary>Get and set if and how to save call frames.</fsummary>
<type>
<v>Flag = all | no_tail | false</v>
@@ -322,7 +322,7 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>break(Module, Line) -> ok | {error,break_exists}</name>
+ <name since="">break(Module, Line) -> ok | {error,break_exists}</name>
<fsummary>Create a breakpoint.</fsummary>
<type>
<v>Module = atom()</v>
@@ -334,7 +334,7 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>delete_break(Module, Line) -> ok</name>
+ <name since="">delete_break(Module, Line) -> ok</name>
<fsummary>Delete a breakpoint.</fsummary>
<type>
<v>Module = atom()</v>
@@ -346,7 +346,7 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>break_in(Module, Name, Arity) -> ok
+ <name since="">break_in(Module, Name, Arity) -> ok
| {error,function_not_found}</name>
<fsummary>Create breakpoints in the specified function.</fsummary>
<type>
@@ -360,7 +360,7 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>del_break_in(Module, Name, Arity) -> ok
+ <name since="">del_break_in(Module, Name, Arity) -> ok
| {error,function_not_found}</name>
<fsummary>Delete breakpoints from the specified function.</fsummary>
<type>
@@ -374,8 +374,8 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>no_break() -> ok</name>
- <name>no_break(Module) -> ok</name>
+ <name since="">no_break() -> ok</name>
+ <name since="">no_break(Module) -> ok</name>
<fsummary>Delete all breakpoints.</fsummary>
<desc>
<p>Deletes all breakpoints, or all breakpoints in <c>Module</c>.</p>
@@ -383,7 +383,7 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>disable_break(Module, Line) -> ok</name>
+ <name since="">disable_break(Module, Line) -> ok</name>
<fsummary>Make a breakpoint inactive.</fsummary>
<type>
<v>Module = atom()</v>
@@ -395,7 +395,7 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>enable_break(Module, Line) -> ok</name>
+ <name since="">enable_break(Module, Line) -> ok</name>
<fsummary>Make a breakpoint active.</fsummary>
<type>
<v>Module = atom()</v>
@@ -407,7 +407,7 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>action_at_break(Module, Line, Action) -> ok</name>
+ <name since="">action_at_break(Module, Line, Action) -> ok</name>
<fsummary>Set the trigger action of a breakpoint.</fsummary>
<type>
<v>Module = atom()</v>
@@ -421,7 +421,7 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>test_at_break(Module, Line, Function) -> ok</name>
+ <name since="">test_at_break(Module, Line, Function) -> ok</name>
<fsummary>Set the conditional test of a breakpoint.</fsummary>
<type>
<v>Module = atom()</v>
@@ -438,7 +438,7 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>get_binding(Var, Bindings) -> {value,Value} | unbound</name>
+ <name since="">get_binding(Var, Bindings) -> {value,Value} | unbound</name>
<fsummary>Retrieve a variable binding.</fsummary>
<type>
<v>Var = atom()</v>
@@ -453,8 +453,8 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>all_breaks() -> [Break]</name>
- <name>all_breaks(Module) -> [Break]</name>
+ <name since="">all_breaks() -> [Break]</name>
+ <name since="">all_breaks(Module) -> [Break]</name>
<fsummary>Get all breakpoints.</fsummary>
<type>
<v>Break = {Point,Options}</v>
@@ -474,7 +474,7 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>snapshot() -> [Snapshot]</name>
+ <name since="">snapshot() -> [Snapshot]</name>
<fsummary>Get information about all processes executing interpreted
code.</fsummary>
<type>
@@ -519,7 +519,7 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>clear() -> ok</name>
+ <name since="">clear() -> ok</name>
<fsummary>Clear information about processes executing interpreted
code.</fsummary>
<desc>
@@ -529,8 +529,8 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>continue(Pid) -> ok | {error,not_interpreted}</name>
- <name>continue(X,Y,Z) -> ok | {error,not_interpreted}</name>
+ <name since="">continue(Pid) -> ok | {error,not_interpreted}</name>
+ <name since="">continue(X,Y,Z) -> ok | {error,not_interpreted}</name>
<fsummary>Resume process execution.</fsummary>
<type>
<v>Pid = pid()</v>
diff --git a/lib/debugger/src/dbg_icmd.erl b/lib/debugger/src/dbg_icmd.erl
index ac901c5469..0eb258567f 100644
--- a/lib/debugger/src/dbg_icmd.erl
+++ b/lib/debugger/src/dbg_icmd.erl
@@ -248,7 +248,7 @@ handle_int_msg({attached, AttPid}, Status, _Bs,
tell_attached({attached, M, Line, get(trace)}),
%% Give info about status and call level as well
- %% In this case, Status can not be exit_at
+ %% In this case, Status cannot be exit_at
Msg = case Status of
idle -> {func_at,M,Line,Le};
break -> {break_at,M,Line,Le};
diff --git a/lib/debugger/test/int_eval_SUITE.erl b/lib/debugger/test/int_eval_SUITE.erl
index 0542e45142..324a44bad8 100644
--- a/lib/debugger/test/int_eval_SUITE.erl
+++ b/lib/debugger/test/int_eval_SUITE.erl
@@ -285,7 +285,10 @@ do_eval(Config, Mod) ->
DataDir = proplists:get_value(data_dir, Config),
ok = file:set_cwd(DataDir),
- {ok,Mod} = compile:file(Mod, [report,debug_info]),
+ %% Turn off type-based optimizations across function calls, as it
+ %% would turn many body-recursive calls into tail-recursive calls,
+ %% which would change the stacktrace.
+ {ok,Mod} = compile:file(Mod, [no_module_opt,report,debug_info]),
{module,Mod} = code:load_file(Mod),
CompiledRes = Mod:Mod(),
ok = io:format("Compiled:\n~p", [CompiledRes]),
diff --git a/lib/dialyzer/doc/src/dialyzer.xml b/lib/dialyzer/doc/src/dialyzer.xml
index e34ffd6def..f5e8337eb1 100644
--- a/lib/dialyzer/doc/src/dialyzer.xml
+++ b/lib/dialyzer/doc/src/dialyzer.xml
@@ -29,7 +29,7 @@
<rev></rev>
<file>dialyzer.xml</file>
</header>
- <module>dialyzer</module>
+ <module since="">dialyzer</module>
<modulesummary>Dialyzer, a DIscrepancy AnaLYZer for ERlang programs.
</modulesummary>
<description>
@@ -472,7 +472,7 @@ dialyzer --plts plt_1 ... plt_n -- files_to_analyze</code>
<funcs>
<func>
- <name>format_warning(Msg) -> string()</name>
+ <name since="">format_warning(Msg) -> string()</name>
<fsummary>Get the string version of a warning message.</fsummary>
<type>
<v>Msg = {Tag, Id, msg()}</v>
@@ -485,8 +485,8 @@ dialyzer --plts plt_1 ... plt_n -- files_to_analyze</code>
</func>
<func>
- <name>gui() -> ok | {error, Msg}</name>
- <name>gui(OptList) -> ok | {error, Msg}</name>
+ <name since="">gui() -> ok | {error, Msg}</name>
+ <name since="">gui(OptList) -> ok | {error, Msg}</name>
<fsummary>Dialyzer GUI version.</fsummary>
<type>
<v>OptList</v>
@@ -539,7 +539,7 @@ WarnOpts :: error_handling
</func>
<func>
- <name>plt_info(string()) -> {'ok', [{atom(), any()}]} | {'error', atom()}</name>
+ <name since="">plt_info(string()) -> {'ok', [{atom(), any()}]} | {'error', atom()}</name>
<fsummary>Return information about the specified PLT.</fsummary>
<desc>
<p>Returns information about the specified PLT.</p>
@@ -547,7 +547,7 @@ WarnOpts :: error_handling
</func>
<func>
- <name>run(OptList) -> Warnings</name>
+ <name since="">run(OptList) -> Warnings</name>
<fsummary>Dialyzer command-line version.</fsummary>
<type>
<v>OptList</v>
diff --git a/lib/dialyzer/src/dialyzer_codeserver.erl b/lib/dialyzer/src/dialyzer_codeserver.erl
index 5587cf2bdf..c4e3c322e5 100644
--- a/lib/dialyzer/src/dialyzer_codeserver.erl
+++ b/lib/dialyzer/src/dialyzer_codeserver.erl
@@ -347,13 +347,11 @@ get_file_contract(Key, ContDict) ->
lookup_mfa_contract(MFA, #codeserver{contracts = ContDict}) ->
ets_dict_find(MFA, ContDict).
--spec lookup_meta_info(module() | mfa(), codeserver()) -> meta_info().
+-spec lookup_meta_info(module() | mfa(), codeserver()) ->
+ {'ok', meta_info()} | 'error'.
lookup_meta_info(MorMFA, #codeserver{fun_meta_info = FunMetaInfo}) ->
- case ets_dict_find(MorMFA, FunMetaInfo) of
- error -> [];
- {ok, PropList} -> PropList
- end.
+ ets_dict_find(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 af7f4385ad..9c36d745c3 100644
--- a/lib/dialyzer/src/dialyzer_contracts.erl
+++ b/lib/dialyzer/src/dialyzer_contracts.erl
@@ -25,7 +25,7 @@
%% get_contract_signature/1,
is_overloaded/1,
process_contract_remote_types/1,
- store_tmp_contract/5]).
+ store_tmp_contract/6]).
-export_type([file_contract/0, plt_contracts/0]).
@@ -146,18 +146,18 @@ process_contract_remote_types(CodeServer) ->
Mods = dialyzer_codeserver:all_temp_modules(CodeServer),
RecordTable = dialyzer_codeserver:get_records_table(CodeServer),
ExpTypes = dialyzer_codeserver:get_exported_types(CodeServer),
- ContractFun =
- fun({{_M, _F, _A}=MFA, {File, TmpContract, Xtra}}, C0) ->
- #tmp_contract{contract_funs = CFuns, forms = Forms} = TmpContract,
- {NewCs, C2} = lists:mapfoldl(fun(CFun, C1) ->
- CFun(ExpTypes, RecordTable, C1)
- end, C0, CFuns),
- Args = general_domain(NewCs),
- Contract = #contract{contracts = NewCs, args = Args, forms = Forms},
- {{MFA, {File, Contract, Xtra}}, C2}
- end,
ModuleFun =
fun(ModuleName) ->
+ ContractFun =
+ 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)
+ end, C0, CFuns),
+ Args = general_domain(NewCs),
+ Contract = #contract{contracts = NewCs, args = Args, forms = Forms},
+ {{MFA, {File, Contract, Xtra}}, C2}
+ end,
Cache = erl_types:cache__new(),
{ContractMap, CallbackMap} =
dialyzer_codeserver:get_temp_contracts(ModuleName, CodeServer),
@@ -474,26 +474,29 @@ insert_constraints([], Map) -> Map.
-type spec_data() :: {TypeSpec :: [_], Xtra:: [_]}.
--spec store_tmp_contract(mfa(), file_line(), spec_data(), contracts(), types()) ->
- contracts().
+-spec store_tmp_contract(module(), mfa(), file_line(), spec_data(),
+ contracts(), types()) -> contracts().
-store_tmp_contract(MFA, FileLine, {TypeSpec, Xtra}, SpecMap, RecordsDict) ->
+store_tmp_contract(Module, MFA, FileLine, {TypeSpec, Xtra}, SpecMap,
+ RecordsDict) ->
%% io:format("contract from form: ~tp\n", [TypeSpec]),
- TmpContract = contract_from_form(TypeSpec, MFA, RecordsDict, FileLine),
+ TmpContract = contract_from_form(TypeSpec, Module, MFA, RecordsDict, FileLine),
%% io:format("contract: ~tp\n", [TmpContract]),
maps:put(MFA, {FileLine, TmpContract, Xtra}, SpecMap).
-contract_from_form(Forms, MFA, RecDict, FileLine) ->
- {CFuns, Forms1} = contract_from_form(Forms, MFA, RecDict, FileLine, [], []),
+contract_from_form(Forms, Module, MFA, RecDict, FileLine) ->
+ {CFuns, Forms1} =
+ contract_from_form(Forms, Module, MFA, RecDict, FileLine, [], []),
#tmp_contract{contract_funs = CFuns, forms = Forms1}.
-contract_from_form([{type, _, 'fun', [_, _]} = Form | Left], MFA, RecDict,
- FileLine, TypeAcc, FormAcc) ->
+contract_from_form([{type, _, 'fun', [_, _]} = Form | Left], Module, MFA,
+ RecDict, FileLine, TypeAcc, FormAcc) ->
TypeFun =
fun(ExpTypes, RecordTable, Cache) ->
{NewType, NewCache} =
try
- from_form_with_check(Form, ExpTypes, MFA, RecordTable, Cache)
+ from_form_with_check(Form, ExpTypes, Module, MFA, RecordTable,
+ Cache)
catch
throw:{error, Msg} ->
{File, Line} = FileLine,
@@ -506,68 +509,74 @@ contract_from_form([{type, _, 'fun', [_, _]} = Form | Left], MFA, RecDict,
end,
NewTypeAcc = [TypeFun | TypeAcc],
NewFormAcc = [{Form, []} | FormAcc],
- contract_from_form(Left, MFA, RecDict, FileLine, NewTypeAcc, NewFormAcc);
+ contract_from_form(Left, Module, MFA, RecDict, FileLine, NewTypeAcc,
+ NewFormAcc);
contract_from_form([{type, _L1, bounded_fun,
[{type, _L2, 'fun', [_, _]} = Form, Constr]}| Left],
- MFA, RecDict, FileLine, TypeAcc, FormAcc) ->
+ Module, MFA, RecDict, FileLine, TypeAcc, FormAcc) ->
TypeFun =
fun(ExpTypes, RecordTable, Cache) ->
{Constr1, VarTable, Cache1} =
- process_constraints(Constr, MFA, RecDict, ExpTypes, RecordTable,
- Cache),
+ process_constraints(Constr, Module, MFA, RecDict, ExpTypes,
+ RecordTable, Cache),
{NewType, NewCache} =
- from_form_with_check(Form, ExpTypes, MFA, RecordTable,
+ from_form_with_check(Form, ExpTypes, Module, MFA, RecordTable,
VarTable, Cache1),
NewTypeNoVars = erl_types:subst_all_vars_to_any(NewType),
{{NewTypeNoVars, Constr1}, NewCache}
end,
NewTypeAcc = [TypeFun | TypeAcc],
NewFormAcc = [{Form, Constr} | FormAcc],
- contract_from_form(Left, MFA, RecDict, FileLine, NewTypeAcc, NewFormAcc);
-contract_from_form([], _MFA, _RecDict, _FileLine, TypeAcc, FormAcc) ->
+ contract_from_form(Left, Module, MFA, RecDict, FileLine, NewTypeAcc,
+ NewFormAcc);
+contract_from_form([], _Mod, _MFA, _RecDict, _FileLine, TypeAcc, FormAcc) ->
{lists:reverse(TypeAcc), lists:reverse(FormAcc)}.
-process_constraints(Constrs, MFA, RecDict, ExpTypes, RecordTable, Cache) ->
- {Init0, NewCache} = initialize_constraints(Constrs, MFA, RecDict, ExpTypes,
- RecordTable, Cache),
+process_constraints(Constrs, Module, MFA, RecDict, ExpTypes, RecordTable,
+ Cache) ->
+ {Init0, NewCache} = initialize_constraints(Constrs, Module, MFA, RecDict,
+ ExpTypes, RecordTable, Cache),
Init = remove_cycles(Init0),
- constraints_fixpoint(Init, MFA, RecDict, ExpTypes, RecordTable, NewCache).
+ constraints_fixpoint(Init, Module, MFA, RecDict, ExpTypes, RecordTable,
+ NewCache).
-initialize_constraints(Constrs, MFA, RecDict, ExpTypes, RecordTable, Cache) ->
- initialize_constraints(Constrs, MFA, RecDict, ExpTypes, RecordTable,
+initialize_constraints(Constrs, Module, MFA, RecDict, ExpTypes, RecordTable,
+ Cache) ->
+ initialize_constraints(Constrs, Module, MFA, RecDict, ExpTypes, RecordTable,
Cache, []).
-initialize_constraints([], _MFA, _RecDict, _ExpTypes, _RecordTable,
+initialize_constraints([], _Module, _MFA, _RecDict, _ExpTypes, _RecordTable,
Cache, Acc) ->
{Acc, Cache};
-initialize_constraints([Constr|Rest], MFA, RecDict, ExpTypes, RecordTable,
- Cache, Acc) ->
+initialize_constraints([Constr|Rest], Module, MFA, RecDict, ExpTypes,
+ RecordTable, Cache, Acc) ->
case Constr of
{type, _, constraint, [{atom, _, is_subtype}, [Type1, Type2]]} ->
VarTable = erl_types:var_table__new(),
{T1, NewCache} =
- final_form(Type1, ExpTypes, MFA, RecordTable, VarTable, Cache),
+ final_form(Type1, ExpTypes, Module, MFA, RecordTable, VarTable, Cache),
Entry = {T1, Type2},
- initialize_constraints(Rest, MFA, RecDict, ExpTypes, RecordTable,
- NewCache, [Entry|Acc]);
+ initialize_constraints(Rest, Module, MFA, RecDict, ExpTypes,
+ RecordTable, NewCache, [Entry|Acc]);
{type, _, constraint, [{atom,_,Name}, List]} ->
N = length(List),
throw({error,
io_lib:format("Unsupported type guard ~tw/~w\n", [Name, N])})
end.
-constraints_fixpoint(Constrs, MFA, RecDict, ExpTypes, RecordTable, Cache) ->
+constraints_fixpoint(Constrs, Module, MFA, RecDict, ExpTypes, RecordTable,
+ Cache) ->
VarTable = erl_types:var_table__new(),
{VarTab, NewCache} =
- constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, RecordTable,
+ constraints_to_dict(Constrs, Module, MFA, RecDict, ExpTypes, RecordTable,
VarTable, Cache),
- constraints_fixpoint(VarTab, MFA, Constrs, RecDict, ExpTypes,
+ constraints_fixpoint(VarTab, Module, MFA, Constrs, RecDict, ExpTypes,
RecordTable, NewCache).
-constraints_fixpoint(OldVarTab, MFA, Constrs, RecDict, ExpTypes,
+constraints_fixpoint(OldVarTab, Module, MFA, Constrs, RecDict, ExpTypes,
RecordTable, Cache) ->
{NewVarTab, NewCache} =
- constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, RecordTable,
+ constraints_to_dict(Constrs, Module, MFA, RecDict, ExpTypes, RecordTable,
OldVarTab, Cache),
case NewVarTab of
OldVarTab ->
@@ -578,19 +587,23 @@ constraints_fixpoint(OldVarTab, MFA, Constrs, RecDict, ExpTypes,
FinalConstrs = maps:fold(Fun, [], NewVarTab),
{FinalConstrs, NewVarTab, NewCache};
_Other ->
- constraints_fixpoint(NewVarTab, MFA, Constrs, RecDict, ExpTypes,
+ constraints_fixpoint(NewVarTab, Module, MFA, Constrs, RecDict, ExpTypes,
RecordTable, NewCache)
end.
-final_form(Form, ExpTypes, MFA, RecordTable, VarTable, Cache) ->
- from_form_with_check(Form, ExpTypes, MFA, RecordTable, VarTable, Cache).
+final_form(Form, ExpTypes, Module, MFA, RecordTable, VarTable, Cache) ->
+ from_form_with_check(Form, ExpTypes, Module, MFA, RecordTable, VarTable,
+ Cache).
-from_form_with_check(Form, ExpTypes, MFA, RecordTable, Cache) ->
+from_form_with_check(Form, ExpTypes, Module, MFA, RecordTable, Cache) ->
VarTable = erl_types:var_table__new(),
- from_form_with_check(Form, ExpTypes, MFA, RecordTable, VarTable, Cache).
+ from_form_with_check(Form, ExpTypes, Module, MFA, RecordTable, VarTable,
+ Cache).
-from_form_with_check(Form, ExpTypes, MFA, RecordTable, VarTable, Cache) ->
- Site = {spec, MFA},
+from_form_with_check(Form, ExpTypes, Module, MFA, RecordTable, VarTable,
+ Cache) ->
+ {_, F, A} = MFA,
+ Site = {spec, {Module, F, A}},
C1 = erl_types:t_check_record_fields(Form, ExpTypes, Site, RecordTable,
VarTable, Cache),
%% The check costs some time, and with the assumption that contracts
@@ -598,22 +611,22 @@ from_form_with_check(Form, ExpTypes, MFA, RecordTable, VarTable, Cache) ->
%% erl_types:t_from_form_check_remote(Form, ExpTypes, MFA, RecordTable),
erl_types:t_from_form(Form, ExpTypes, Site, RecordTable, VarTable, C1).
-constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, RecordTable,
+constraints_to_dict(Constrs, Module, MFA, RecDict, ExpTypes, RecordTable,
VarTab, Cache) ->
{Subtypes, NewCache} =
- constraints_to_subs(Constrs, MFA, RecDict, ExpTypes, RecordTable,
+ constraints_to_subs(Constrs, Module, MFA, RecDict, ExpTypes, RecordTable,
VarTab, Cache, []),
{insert_constraints(Subtypes), NewCache}.
-constraints_to_subs([], _MFA, _RecDict, _ExpTypes, _RecordTable,
+constraints_to_subs([], _Module, _MFA, _RecDict, _ExpTypes, _RecordTable,
_VarTab, Cache, Acc) ->
{Acc, Cache};
-constraints_to_subs([{T1, Form2}|Rest], MFA, RecDict, ExpTypes, RecordTable,
- VarTab, Cache, Acc) ->
+constraints_to_subs([{T1, Form2}|Rest], Module, MFA, RecDict, ExpTypes,
+ RecordTable, VarTab, Cache, Acc) ->
{T2, NewCache} =
- final_form(Form2, ExpTypes, MFA, RecordTable, VarTab, Cache),
+ final_form(Form2, ExpTypes, Module, MFA, RecordTable, VarTab, Cache),
NewAcc = [{subtype, T1, T2}|Acc],
- constraints_to_subs(Rest, MFA, RecDict, ExpTypes, RecordTable,
+ constraints_to_subs(Rest, Module, MFA, RecDict, ExpTypes, RecordTable,
VarTab, NewCache, NewAcc).
%% Replaces variables with '_' when necessary to break up cycles among
@@ -898,6 +911,7 @@ is_remote_types_related(Contract, CSig, Sig, MFA, RecDict) ->
t_from_forms_without_remote([{FType, []}], MFA, RecDict) ->
Site = {spec, MFA},
+ %% FIXME
Type1 = erl_types:t_from_form_without_remote(FType, Site, RecDict),
{ok, erl_types:subst_all_vars_to_any(Type1)};
t_from_forms_without_remote([{_FType, _Constrs}], _MFA, _RecDict) ->
diff --git a/lib/dialyzer/src/dialyzer_utils.erl b/lib/dialyzer/src/dialyzer_utils.erl
index abd89034f3..3fe026b096 100644
--- a/lib/dialyzer/src/dialyzer_utils.erl
+++ b/lib/dialyzer/src/dialyzer_utils.erl
@@ -450,8 +450,9 @@ get_spec_info([{Contract, Ln, [{Id, TypeSpec}]}|Left],
error ->
SpecData = {TypeSpec, Xtra},
NewActiveMap =
- dialyzer_contracts:store_tmp_contract(MFA, {File, Ln}, SpecData,
- ActiveMap, RecordsMap),
+ dialyzer_contracts:store_tmp_contract(ModName, MFA, {File, Ln},
+ SpecData, ActiveMap,
+ RecordsMap),
{NewSpecMap, NewCallbackMap} =
case Contract of
spec -> {NewActiveMap, CallbackMap};
@@ -599,24 +600,32 @@ collect_attribute([], _Tag, _File) ->
-spec is_suppressed_fun(mfa(), codeserver()) -> boolean().
is_suppressed_fun(MFA, CodeServer) ->
- lookup_fun_property(MFA, nowarn_function, CodeServer).
+ lookup_fun_property(MFA, nowarn_function, CodeServer, false).
-spec is_suppressed_tag(mfa() | module(), dial_warn_tag(), codeserver()) ->
boolean().
is_suppressed_tag(MorMFA, Tag, Codeserver) ->
- not lookup_fun_property(MorMFA, Tag, Codeserver).
-
-lookup_fun_property({M, _F, _A}=MFA, Property, CodeServer) ->
- MFAPropList = dialyzer_codeserver:lookup_meta_info(MFA, CodeServer),
- case proplists:get_value(Property, MFAPropList, no) of
- mod -> false; % suppressed in function
- func -> true; % requested in function
- no -> lookup_fun_property(M, Property, CodeServer)
+ not lookup_fun_property(MorMFA, Tag, Codeserver, true).
+
+lookup_fun_property({M, _F, _A}=MFA, Property, CodeServer, NoInfoReturn) ->
+ case dialyzer_codeserver:lookup_meta_info(MFA, CodeServer) of
+ error ->
+ lookup_fun_property(M, Property, CodeServer, NoInfoReturn);
+ {ok, MFAPropList} ->
+ case proplists:get_value(Property, MFAPropList, no) of
+ mod -> false; % suppressed in function
+ func -> true; % requested in function
+ no -> lookup_fun_property(M, Property, CodeServer, NoInfoReturn)
+ end
end;
-lookup_fun_property(M, Property, CodeServer) when is_atom(M) ->
- MPropList = dialyzer_codeserver:lookup_meta_info(M, CodeServer),
- proplists:is_defined(Property, MPropList).
+lookup_fun_property(M, Property, CodeServer, NoInfoReturn) when is_atom(M) ->
+ case dialyzer_codeserver:lookup_meta_info(M, CodeServer) of
+ error ->
+ NoInfoReturn;
+ {ok, MPropList} ->
+ proplists:is_defined(Property, MPropList)
+ end.
%% ============================================================================
%%
@@ -643,7 +652,7 @@ sets_filter([Mod|Mods], ExpTypes) ->
src_compiler_opts() ->
[no_copt, to_core, binary, return_errors,
no_inline, strict_record_tests, strict_record_updates,
- dialyzer].
+ dialyzer, no_spawn_compiler_process].
-spec format_errors([{module(), string()}]) -> [string()].
diff --git a/lib/dialyzer/src/typer.erl b/lib/dialyzer/src/typer.erl
index 4b99f5f72e..5a1b8619c9 100644
--- a/lib/dialyzer/src/typer.erl
+++ b/lib/dialyzer/src/typer.erl
@@ -980,7 +980,7 @@ fatal_error(Slogan) ->
mode_error(OldMode, NewMode) ->
Msg = io_lib:format("Mode was previously set to '~s'; "
- "can not set it to '~s' now",
+ "cannot set it to '~s' now",
[OldMode, NewMode]),
fatal_error(Msg).
diff --git a/lib/dialyzer/test/opaque_SUITE_data/results/para b/lib/dialyzer/test/opaque_SUITE_data/results/para
index 37b5b7b44e..eca445315c 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/results/para
+++ b/lib/dialyzer/test/opaque_SUITE_data/results/para
@@ -29,5 +29,6 @@ para4.erl:74: Attempt to test for equality between a term of type para4_adt:int(
para4.erl:79: Attempt to test for equality between a term of type para4_adt:int(2 | 3 | 4) and a term of opaque type para4_adt:int(5 | 6 | 7)
para4.erl:84: Attempt to test for equality between a term of type para4_adt:un(3 | 4) and a term of opaque type para4_adt:un(1 | 2)
para4.erl:89: Attempt to test for equality between a term of type para4_adt:tup({_,_}) and a term of opaque type para4_adt:tup(tuple())
+para4.erl:94: Attempt to test for equality between a term of type para4_adt:t(#{1=>'a'}) and a term of opaque type para4_adt:t(#{2=>'b'})
para5.erl:13: Attempt to test for inequality between a term of type para5_adt:dd(atom()) and a term of opaque type para5_adt:d()
para5.erl:8: The test para5_adt:d() =:= para5_adt:d() can never evaluate to 'true'
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/para/para4.erl b/lib/dialyzer/test/opaque_SUITE_data/src/para/para4.erl
index b9794672a9..8cd049169d 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/src/para/para4.erl
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/para/para4.erl
@@ -88,6 +88,11 @@ adt_tt13() ->
I2 = adt_tup2(),
I1 =:= I2. % opaque attempt
+adt_tt14() ->
+ I1 = adt_map(),
+ I2 = adt_map2(),
+ I1 =:= I2.
+
y3() ->
{a, 3}.
@@ -132,3 +137,9 @@ adt_tup() ->
adt_tup2() ->
para4_adt:tup2().
+
+adt_map() ->
+ para4_adt:map().
+
+adt_map2() ->
+ para4_adt:map2().
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/para/para4_adt.erl b/lib/dialyzer/test/opaque_SUITE_data/src/para/para4_adt.erl
index 407dd198a7..06a6c22677 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/src/para/para4_adt.erl
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/para/para4_adt.erl
@@ -8,6 +8,8 @@
-export([tup/0, tup2/0]).
+-export([map/0, map2/0]).
+
-export_type([t/1, y/1, int/1, tup/1, un/1]).
-type ai() :: atom() | integer().
@@ -106,3 +108,13 @@ tup() ->
tup2() ->
foo:tup2().
+
+-spec map() -> t(#{2 => b}).
+
+map() ->
+ foo:map().
+
+-spec map2() -> t(#{1 => a}).
+
+map2() ->
+ foo:map2().
diff --git a/lib/dialyzer/test/options1_SUITE_data/src/compiler/beam_validator.erl b/lib/dialyzer/test/options1_SUITE_data/src/compiler/beam_validator.erl
index ea92613781..3606b21932 100644
--- a/lib/dialyzer/test/options1_SUITE_data/src/compiler/beam_validator.erl
+++ b/lib/dialyzer/test/options1_SUITE_data/src/compiler/beam_validator.erl
@@ -272,7 +272,7 @@ valfun_1(if_end, Vst) ->
valfun_1({try_case_end,Src}, Vst) ->
assert_term(Src, Vst),
kill_state(Vst);
-%% Instructions that can not cause exceptions
+%% Instructions that cannot cause exceptions
valfun_1({move,Src,Dst}, Vst) ->
Type = get_term_type(Src, Vst),
set_type_reg(Type, Dst, Vst);
diff --git a/lib/dialyzer/test/r9c_SUITE_data/src/asn1/asn1ct_gen_ber.erl b/lib/dialyzer/test/r9c_SUITE_data/src/asn1/asn1ct_gen_ber.erl
index 1d065b3723..88b464a721 100644
--- a/lib/dialyzer/test/r9c_SUITE_data/src/asn1/asn1ct_gen_ber.erl
+++ b/lib/dialyzer/test/r9c_SUITE_data/src/asn1/asn1ct_gen_ber.erl
@@ -300,7 +300,7 @@ gen_encode_prim(_Erules,D,DoTag,Value) when record(D,type) ->
'ASN1_OPEN_TYPE' ->
emit_encode_func('open_type', Value,DoTag);
XX ->
- exit({'can not encode' ,XX})
+ exit({'cannot encode' ,XX})
end.
@@ -562,7 +562,7 @@ gen_dec_prim(Erules,Att,BytesVar,DoTag,TagIn,Length,_Form,OptOrMand) ->
BytesVar,","]),
false;
Other ->
- exit({'can not decode' ,Other})
+ exit({'cannot decode' ,Other})
end,
NewLength = case DoLength of
diff --git a/lib/dialyzer/test/r9c_SUITE_data/src/asn1/asn1ct_gen_ber_bin_v2.erl b/lib/dialyzer/test/r9c_SUITE_data/src/asn1/asn1ct_gen_ber_bin_v2.erl
index 9164ec6551..573bb15c92 100644
--- a/lib/dialyzer/test/r9c_SUITE_data/src/asn1/asn1ct_gen_ber_bin_v2.erl
+++ b/lib/dialyzer/test/r9c_SUITE_data/src/asn1/asn1ct_gen_ber_bin_v2.erl
@@ -290,7 +290,7 @@ gen_encode_prim(_Erules,D,DoTag,Value) when record(D,type) ->
'ASN1_OPEN_TYPE' ->
emit_encode_func('open_type', Value,DoTag);
XX ->
- exit({'can not encode' ,XX})
+ exit({'cannot encode' ,XX})
end.
@@ -602,7 +602,7 @@ gen_dec_prim(_Erules,Att,BytesVar,DoTag,_TagIn,_Form,_OptOrMand) ->
BytesVar,","]),
add_func({decode_open_type_as_binary,2});
Other ->
- exit({'can not decode' ,Other})
+ exit({'cannot decode' ,Other})
end,
case {DoTag,NewTypeName} of
diff --git a/lib/dialyzer/test/r9c_SUITE_data/src/mnesia/mnesia_lib.erl b/lib/dialyzer/test/r9c_SUITE_data/src/mnesia/mnesia_lib.erl
index 4008f8d789..9e0cdac2dc 100644
--- a/lib/dialyzer/test/r9c_SUITE_data/src/mnesia/mnesia_lib.erl
+++ b/lib/dialyzer/test/r9c_SUITE_data/src/mnesia/mnesia_lib.erl
@@ -779,7 +779,7 @@ error_desc(no_transaction) -> "Operation not allowed outside transactions";
error_desc(combine_error) -> "Table options were ilegally combined";
error_desc(bad_index) -> "Index already exists or was out of bounds";
error_desc(already_exists) -> "Some schema option we try to set is already on";
-error_desc(index_exists)-> "Some ops can not be performed on tabs with index";
+error_desc(index_exists)-> "Some ops cannot be performed on tabs with index";
error_desc(no_exists)-> "Tried to perform op on non-existing (non alive) item";
error_desc(system_limit) -> "Some system_limit was exhausted";
error_desc(mnesia_down) -> "A transaction involving objects at some remote "
diff --git a/lib/dialyzer/test/r9c_SUITE_data/src/mnesia/mnesia_tm.erl b/lib/dialyzer/test/r9c_SUITE_data/src/mnesia/mnesia_tm.erl
index af49ceff72..b4e9edb7ce 100644
--- a/lib/dialyzer/test/r9c_SUITE_data/src/mnesia/mnesia_tm.erl
+++ b/lib/dialyzer/test/r9c_SUITE_data/src/mnesia/mnesia_tm.erl
@@ -1529,7 +1529,7 @@ commit_participant(Coord, Tid, Bin, C0, DiscNs, _RamNs) ->
?eval_debug_fun({?MODULE, commit_participant, pre}, [{tid, Tid}]),
case catch mnesia_schema:prepare_commit(Tid, C0, {part, Coord}) of
{Modified, C, DumperMode} when record(C, commit) ->
- %% If we can not find any local unclear decision
+ %% If we cannot find any local unclear decision
%% we should presume abort at startup recovery
case lists:member(node(), DiscNs) of
false ->
diff --git a/lib/dialyzer/test/small_SUITE_data/results/maps_sum b/lib/dialyzer/test/small_SUITE_data/results/maps_sum
index b29ac77d88..daa099e490 100644
--- a/lib/dialyzer/test/small_SUITE_data/results/maps_sum
+++ b/lib/dialyzer/test/small_SUITE_data/results/maps_sum
@@ -1,4 +1,4 @@
-maps_sum.erl:15: Invalid type specification for function maps_sum:wrong1/1. The success typing is (maps:iterator() | map()) -> any()
+maps_sum.erl:15: Invalid type specification for function maps_sum:wrong1/1. The success typing is (maps:iterator(_,_) | map()) -> any()
maps_sum.erl:26: Function wrong2/1 has no local return
maps_sum.erl:27: 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/spec_other_module b/lib/dialyzer/test/small_SUITE_data/results/spec_other_module
new file mode 100644
index 0000000000..ab2e35cf55
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/results/spec_other_module
@@ -0,0 +1,2 @@
+
+spec_other_module.erl:7: Contract for function that does not exist: lists:flatten/1
diff --git a/lib/dialyzer/test/small_SUITE_data/src/lists_key_bug.erl b/lib/dialyzer/test/small_SUITE_data/src/lists_key_bug.erl
new file mode 100644
index 0000000000..ad5cf3c503
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/src/lists_key_bug.erl
@@ -0,0 +1,75 @@
+-module(lists_key_bug).
+
+%% OTP-15570
+
+-export([is_1/1, is_2/1, i/1, t1/0, t2/0, im/0]).
+
+%% int_set([3])
+is_1(V) ->
+ K = ikey(V),
+ case lists:keyfind(K, 1, [{<<"foo">>, bar}]) of
+ false ->
+ a;
+ {_, _} ->
+ b
+ end.
+
+ikey(1) ->
+ 3;
+ikey(2) ->
+ <<"foo">>.
+
+%% int_set([3, 5])
+is_2(V) ->
+ K = iskey(V),
+ case lists:keyfind(K, 1, [{<<"foo">>, bar}]) of
+ false ->
+ a;
+ {_, _} ->
+ b
+ end.
+
+iskey(1) ->
+ 12;
+iskey(2) ->
+ 14;
+iskey(3) ->
+ <<"foo">>.
+
+%% integer()
+i(V) ->
+ K = intkey(V),
+ case lists:keyfind(K, 1, [{9.0, foo}]) of
+ false ->
+ a;
+ {_, _} ->
+ b
+ end.
+
+intkey(K) when is_integer(K) ->
+ K + 9999.
+
+t1() ->
+ case lists:keyfind({17}, 1, [{{17.0}, true}]) of
+ false ->
+ a;
+ {_, _} ->
+ b
+ end.
+
+t2() ->
+ case lists:keyfind({17.0}, 1, [{{17}, true}]) of
+ false ->
+ a;
+ {_, _} ->
+ b
+ end.
+
+%% Note: #{1.0 => a} =/= #{1 => a}.
+im() ->
+ case lists:keyfind(#{1.0 => a}, 1, [{#{1 => a}, foo}]) of
+ false ->
+ a;
+ {_, _} ->
+ b
+ end.
diff --git a/lib/dialyzer/test/small_SUITE_data/src/spec_other_module.erl b/lib/dialyzer/test/small_SUITE_data/src/spec_other_module.erl
new file mode 100644
index 0000000000..b36742b1bd
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/src/spec_other_module.erl
@@ -0,0 +1,7 @@
+-module(spec_other_module).
+
+%% OTP-15562 and ERL-845. Example provided by Kostis.
+
+-type deep_list(A) :: [A | deep_list(A)].
+
+-spec lists:flatten(deep_list(A)) -> [A].
diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml
index dfa4c803ed..0a0194af2d 100644
--- a/lib/diameter/doc/src/diameter.xml
+++ b/lib/diameter/doc/src/diameter.xml
@@ -55,7 +55,7 @@ limitations under the License.
<!-- ===================================================================== -->
<!-- ===================================================================== -->
-<module>diameter</module>
+<module since="OTP R14B03">diameter</module>
<modulesummary>Main API of the diameter application.</modulesummary>
<description>
@@ -1574,7 +1574,7 @@ identifies the configuration.</p>
<!-- ===================================================================== -->
<func>
-<name>add_transport(SvcName, {connect|listen, [Opt]})
+<name since="OTP R14B03">add_transport(SvcName, {connect|listen, [Opt]})
-> {ok, Ref} | {error, Reason}</name>
<fsummary>Add transport capability to a service.</fsummary>
<type>
@@ -1624,7 +1624,7 @@ its transports.</p>
<!-- ===================================================================== -->
<func>
-<name>call(SvcName, App, Request, [Opt]) -> Answer | ok | {error, Reason}</name>
+<name since="OTP R14B03">call(SvcName, App, Request, [Opt]) -> Answer | ok | {error, Reason}</name>
<fsummary>Send a Diameter request message.</fsummary>
<type>
<v>SvcName = &service_name;</v>
@@ -1730,7 +1730,7 @@ transport connection.</p>
<!-- ===================================================================== -->
<func>
-<name>origin_state_id() -> &dict_Unsigned32;</name>
+<name since="OTP R14B03">origin_state_id() -> &dict_Unsigned32;</name>
<fsummary>Returns a reasonable Origin-State-Id.</fsummary>
<desc>
<p>
@@ -1748,7 +1748,7 @@ at the time the diameter application was started.</p>
<!-- ===================================================================== -->
<func>
-<name>remove_transport(SvcName, Pred) -> ok | {error, Reason}</name>
+<name since="OTP R14B03">remove_transport(SvcName, Pred) -> ok | {error, Reason}</name>
<fsummary>Remove previously added transports.</fsummary>
<type>
<v>SvcName = &service_name;</v>
@@ -1795,7 +1795,7 @@ configured on the transport.</p>
<!-- ===================================================================== -->
<func>
-<name>service_info(SvcName, Info) -> term()</name>
+<name since="OTP R14B03">service_info(SvcName, Info) -> term()</name>
<fsummary>Return information about a started service.</fsummary>
<type>
<v>SvcName = &service_name;</v>
@@ -2114,7 +2114,7 @@ For example:</p>
<!-- ===================================================================== -->
<func>
-<name>services() -> [SvcName]</name>
+<name since="OTP R14B03">services() -> [SvcName]</name>
<fsummary>Return the list of started services.</fsummary>
<type>
<v>SvcName = &service_name;</v>
@@ -2129,7 +2129,7 @@ Return the list of started services.</p>
<!-- ===================================================================== -->
<func>
-<name>session_id(Ident) -> &dict_OctetString;</name>
+<name since="OTP R14B03">session_id(Ident) -> &dict_OctetString;</name>
<fsummary>Return a value for a Session-Id AVP.</fsummary>
<type>
<v>Ident = &dict_DiameterIdentity;</v>
@@ -2148,7 +2148,7 @@ the message containing the returned value will be sent.</p>
<!-- ===================================================================== -->
<func>
-<name>start() -> ok | {error, Reason}</name>
+<name since="OTP R14B03">start() -> ok | {error, Reason}</name>
<fsummary>Start the diameter application.</fsummary>
<desc>
<p>
@@ -2164,7 +2164,7 @@ file, not by calling <c>start/0</c> explicitly.</p>
<!-- ===================================================================== -->
<func>
-<name>start_service(SvcName, Options) -> ok | {error, Reason}</name>
+<name since="OTP R14B03">start_service(SvcName, Options) -> ok | {error, Reason}</name>
<fsummary>Start a Diameter service.</fsummary>
<type>
<v>SvcName = &service_name;</v>
@@ -2194,7 +2194,7 @@ necessarily the case.</p>
<!-- ===================================================================== -->
<func>
-<name>stop() -> ok | {error, Reason}</name>
+<name since="OTP R14B03">stop() -> ok | {error, Reason}</name>
<fsummary>Stop the diameter application.</fsummary>
<desc>
<p>
@@ -2208,7 +2208,7 @@ Stop the diameter application.</p>
<!-- ===================================================================== -->
<func>
-<name>stop_service(SvcName) -> ok | {error, Reason}</name>
+<name since="OTP R14B03">stop_service(SvcName) -> ok | {error, Reason}</name>
<fsummary>Stop a Diameter service.</fsummary>
<type>
<v>SvcName = &service_name;</v>
@@ -2236,7 +2236,7 @@ be called to remove transport configuration.</p>
<!-- ===================================================================== -->
<func>
-<name>subscribe(SvcName) -> true</name>
+<name since="OTP R14B03">subscribe(SvcName) -> true</name>
<fsummary>Subscribe to event messages.</fsummary>
<type>
<v>SvcName = &service_name;</v>
@@ -2258,7 +2258,7 @@ reception of all transport-related events.</p>
<!-- ===================================================================== -->
<func>
-<name>unsubscribe(SvcName) -> true</name>
+<name since="OTP R14B03">unsubscribe(SvcName) -> true</name>
<fsummary>Unsubscribe to event messages.</fsummary>
<type>
<v>SvcName = &service_name;</v>
diff --git a/lib/diameter/doc/src/diameter_app.xml b/lib/diameter/doc/src/diameter_app.xml
index aa334beb21..82e3d449ef 100644
--- a/lib/diameter/doc/src/diameter_app.xml
+++ b/lib/diameter/doc/src/diameter_app.xml
@@ -44,7 +44,7 @@ limitations under the License.
</header>
-<module>diameter_app</module>
+<module since="OTP R14B03">diameter_app</module>
<modulesummary>
Callback module of a Diameter application.</modulesummary>
@@ -180,7 +180,7 @@ process.</p>
<funcs>
<func>
-<name>Mod:peer_up(SvcName, Peer, State) -> NewState</name>
+<name since="OTP R14B03">Mod:peer_up(SvcName, Peer, State) -> NewState</name>
<fsummary>Invoked when a transport connection has been established</fsummary>
<type>
<v>SvcName = &mod_service_name;</v>
@@ -215,7 +215,7 @@ handled independently of &peer_up; and &peer_down;.</p>
</func>
<func>
-<name>Mod:peer_down(SvcName, Peer, State) -> NewState</name>
+<name since="OTP R14B03">Mod:peer_down(SvcName, Peer, State) -> NewState</name>
<fsummary>Invoked when a transport connection has been lost.</fsummary>
<type>
<v>SvcName = &mod_service_name;</v>
@@ -234,7 +234,7 @@ candidate in &pick_peer; callbacks.</p>
</func>
<func>
-<name>Mod:pick_peer(LocalCandidates, RemoteCandidates, SvcName, State)
+<name since="OTP R14B03">Mod:pick_peer(LocalCandidates, RemoteCandidates, SvcName, State)
-> Selection | false</name>
<fsummary>Select a target peer for an outgoing request.</fsummary>
<type>
@@ -311,7 +311,7 @@ or &peer_down; callback.</p>
</func>
<func>
-<name>Mod:prepare_request(Packet, SvcName, Peer) -> Action</name>
+<name since="OTP R14B03">Mod:prepare_request(Packet, SvcName, Peer) -> Action</name>
<fsummary>Return a request for encoding and transport.</fsummary>
<type>
<v>Packet = &packet;</v>
@@ -363,7 +363,7 @@ discarded}</c>.</p>
</func>
<func>
-<name>Mod:prepare_retransmit(Packet, SvcName, Peer) -> Action</name>
+<name since="OTP R14B03">Mod:prepare_retransmit(Packet, SvcName, Peer) -> Action</name>
<fsummary>Return a request for encoding and retransmission.</fsummary>
<type>
<v>Packet = &packet;</v>
@@ -393,7 +393,7 @@ discarded}</c>.</p>
</func>
<func>
-<name>Mod:handle_answer(Packet, Request, SvcName, Peer) -> Result</name>
+<name since="OTP R14B03">Mod:handle_answer(Packet, Request, SvcName, Peer) -> Result</name>
<fsummary>Receive an answer message from a peer.</fsummary>
<type>
<v>Packet = &packet;</v>
@@ -437,7 +437,7 @@ The &mod_application_opt;
</func>
<func>
-<name>Mod:handle_error(Reason, Request, SvcName, Peer) -> Result</name>
+<name since="OTP R14B03">Mod:handle_error(Reason, Request, SvcName, Peer) -> Result</name>
<fsummary>Return an error from a outgoing request.</fsummary>
<type>
<v>Reason = timeout | failover | term()</v>
@@ -465,7 +465,7 @@ not selected.</p>
</func>
<func>
-<name>Mod:handle_request(Packet, SvcName, Peer) -> Action</name>
+<name since="OTP R14B03">Mod:handle_request(Packet, SvcName, Peer) -> Action</name>
<fsummary>Receive an incoming request.</fsummary>
<type>
<v>Packet = &packet;</v>
diff --git a/lib/diameter/doc/src/diameter_codec.xml b/lib/diameter/doc/src/diameter_codec.xml
index 0a34dd7ec7..0384ad2913 100644
--- a/lib/diameter/doc/src/diameter_codec.xml
+++ b/lib/diameter/doc/src/diameter_codec.xml
@@ -46,7 +46,7 @@ limitations under the License.
<file>diameter_codec.xml</file>
</header>
-<module>diameter_codec</module>
+<module since="OTP R15B03">diameter_codec</module>
<modulesummary>Decode and encode of Diameter messages.</modulesummary>
<description>
@@ -346,7 +346,7 @@ question, as documented in &man_transport;.</p>
<funcs>
<func>
-<name>decode(Mod, Bin) -> Pkt</name>
+<name since="OTP R15B03">decode(Mod, Bin) -> Pkt</name>
<fsummary>Decode a Diameter message.</fsummary>
<type>
<v>Mod = &dictionary;</v>
@@ -362,7 +362,7 @@ Decode a Diameter message.</p>
</func>
<func>
-<name>encode(Mod, Msg) -> Pkt</name>
+<name since="OTP R15B03">encode(Mod, Msg) -> Pkt</name>
<fsummary>Encode a Diameter message.</fsummary>
<type>
<v>Mod = &dictionary;</v>
diff --git a/lib/diameter/doc/src/diameter_make.xml b/lib/diameter/doc/src/diameter_make.xml
index 112355816f..57e83bbca1 100644
--- a/lib/diameter/doc/src/diameter_make.xml
+++ b/lib/diameter/doc/src/diameter_make.xml
@@ -45,7 +45,7 @@ limitations under the License.
<file>diameter_make.xml</file>
</header>
-<module>diameter_make</module>
+<module since="OTP R14B03">diameter_make</module>
<modulesummary>Diameter dictionary compilation.</modulesummary>
<description>
@@ -67,7 +67,7 @@ interface.</p>
<funcs>
<func>
-<name>codec(File :: iolist() | binary(), [Opt]) -> ok
+<name since="OTP R15B">codec(File :: iolist() | binary(), [Opt]) -> ok
| {ok, [Out]}
| {error, Reason}</name>
<fsummary>Compile a dictionary file into Erlang source.</fsummary>
@@ -186,7 +186,7 @@ A returned error reason can be converted into a readable string using
<!-- ===================================================================== -->
<func>
-<name>format(Parsed) -> iolist()</name>
+<name since="OTP R16B03">format(Parsed) -> iolist()</name>
<fsummary>Format a parsed dictionary.</fsummary>
<desc>
<p>
@@ -198,7 +198,7 @@ dictionary format.</p>
<!-- ===================================================================== -->
<func>
-<name>flatten(Parsed) -> term()</name>
+<name since="OTP R16B03">flatten(Parsed) -> term()</name>
<fsummary>Flatten a parsed dictionary.</fsummary>
<desc>
@@ -214,7 +214,7 @@ The return value is also a parsed dictionary.</p>
<!-- ===================================================================== -->
<func>
-<name>format_error(Reason) -> string()</name>
+<name since="OTP 17.0">format_error(Reason) -> string()</name>
<fsummary>Turn an error reason into a readable string.</fsummary>
<desc>
diff --git a/lib/diameter/doc/src/diameter_sctp.xml b/lib/diameter/doc/src/diameter_sctp.xml
index 62e958870e..2cf924ee6b 100644
--- a/lib/diameter/doc/src/diameter_sctp.xml
+++ b/lib/diameter/doc/src/diameter_sctp.xml
@@ -47,7 +47,7 @@ limitations under the License.
<file>diameter_sctp.xml</file>
</header>
-<module>diameter_sctp</module>
+<module since="OTP R14B03">diameter_sctp</module>
<modulesummary>Diameter transport over SCTP.</modulesummary>
<description>
@@ -67,7 +67,7 @@ and implements the behaviour documented in
<funcs>
<func>
-<name>start({Type, Ref}, Svc, [Opt])
+<name since="OTP R14B03">start({Type, Ref}, Svc, [Opt])
-> {ok, Pid, [LAddr]} | {error, Reason}</name>
<fsummary>Start a transport process.</fsummary>
<type>
diff --git a/lib/diameter/doc/src/diameter_tcp.xml b/lib/diameter/doc/src/diameter_tcp.xml
index 9f84eeb9fd..fe0cb2d067 100644
--- a/lib/diameter/doc/src/diameter_tcp.xml
+++ b/lib/diameter/doc/src/diameter_tcp.xml
@@ -57,7 +57,7 @@ limitations under the License.
<file>diameter_tcp.xml</file>
</header>
-<module>diameter_tcp</module>
+<module since="OTP R14B03">diameter_tcp</module>
<modulesummary>Diameter transport over TCP.</modulesummary>
<description>
@@ -83,7 +83,7 @@ before configuring TLS capability on diameter transports.</p>
<funcs>
<func>
-<name>start({Type, Ref}, Svc, [Opt])
+<name since="OTP R14B03">start({Type, Ref}, Svc, [Opt])
-> {ok, Pid}
| {ok, Pid, [LAddr]}
| {error, Reason}</name>
diff --git a/lib/diameter/doc/src/diameter_transport.xml b/lib/diameter/doc/src/diameter_transport.xml
index 294e8a8864..67fd54bc56 100644
--- a/lib/diameter/doc/src/diameter_transport.xml
+++ b/lib/diameter/doc/src/diameter_transport.xml
@@ -43,7 +43,7 @@ limitations under the License.
<file>diameter_transport.xml</file>
</header>
-<module>diameter_transport</module>
+<module since="OTP R14B03">diameter_transport</module>
<modulesummary>Diameter transport interface.</modulesummary>
<description>
@@ -94,7 +94,7 @@ and has the binary() to send in its <c>bin</c> field.</p>
<funcs>
<func>
-<name>Mod:start({Type, Ref}, Svc, Config)
+<name since="OTP R14B03">Mod:start({Type, Ref}, Svc, Config)
-> {ok, Pid}
| {ok, Pid, LAddrs}
| {error, Reason}</name>
diff --git a/lib/diameter/doc/src/notes.xml b/lib/diameter/doc/src/notes.xml
index 4bfc98de40..cc92bd99f0 100644
--- a/lib/diameter/doc/src/notes.xml
+++ b/lib/diameter/doc/src/notes.xml
@@ -78,6 +78,24 @@ first.</p>
</section>
+<section><title>diameter 2.1.4.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix failure of incoming answer message with faulty
+ Experimental-Result-Code. Failure to decode the AVP
+ resulted in an uncaught exception, with no no
+ handle_answer/error callback as a consequence.</p>
+ <p>
+ Own Id: OTP-15569 Aux Id: ERIERL-302 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>diameter 2.1.4</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/diameter/src/base/diameter_gen.erl b/lib/diameter/src/base/diameter_gen.erl
index d110a3015e..564448de48 100644
--- a/lib/diameter/src/base/diameter_gen.erl
+++ b/lib/diameter/src/base/diameter_gen.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2019. All 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/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl
index d2856ae530..2d3e4a2ac9 100644
--- a/lib/diameter/src/base/diameter_traffic.erl
+++ b/lib/diameter/src/base/diameter_traffic.erl
@@ -1925,6 +1925,8 @@ get_avp(Dict, Name, [#diameter_header{} | Avps]) ->
A = find_avp(Code, Vid, Avps),
avp_decode(Dict, Name, ungroup(A))
catch
+ {diameter_gen, _} -> %% faulty Grouped AVP
+ undefined;
error: _ ->
undefined
end;
diff --git a/lib/diameter/src/diameter.appup.src b/lib/diameter/src/diameter.appup.src
index 51830f5276..4e6b983bac 100644
--- a/lib/diameter/src/diameter.appup.src
+++ b/lib/diameter/src/diameter.appup.src
@@ -2,7 +2,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2019. All 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,6 +59,7 @@
{"2.1.2", [{restart_application, diameter}]}, %% 20.1.3
{"2.1.3", [{restart_application, diameter}]}, %% 20.2
{"2.1.4", [{restart_application, diameter}]}, %% 20.3
+ {"2.1.4.1", [{restart_application, diameter}]}, %% 20.3.8.19
{"2.1.5", [{update, diameter_peer_fsm}]} %% 21.0
],
[
@@ -100,6 +101,7 @@
{"2.1.2", [{restart_application, diameter}]},
{"2.1.3", [{restart_application, diameter}]},
{"2.1.4", [{restart_application, diameter}]},
+ {"2.1.4.1", [{restart_application, diameter}]},
{"2.1.5", [{update, diameter_peer_fsm}]}
]
}.
diff --git a/lib/diameter/src/transport/diameter_tcp.erl b/lib/diameter/src/transport/diameter_tcp.erl
index da059fa7d6..e5e766d2a0 100644
--- a/lib/diameter/src/transport/diameter_tcp.erl
+++ b/lib/diameter/src/transport/diameter_tcp.erl
@@ -92,9 +92,9 @@
-type connect_option() :: {raddr, inet:ip_address()}
| {rport, pos_integer()}
- | {ssl_options, true | [ssl:connect_option()]}
+ | {ssl_options, true | [ssl:tls_client_option()]}
| option()
- | ssl:connect_option()
+ | ssl:tls_client_option()
| gen_tcp:connect_option().
-type match() :: inet:ip_address()
@@ -102,9 +102,9 @@
| [match()].
-type listen_option() :: {accept, match()}
- | {ssl_options, true | [ssl:listen_option()]}
+ | {ssl_options, true | [ssl:tls_server_option()]}
| option()
- | ssl:listen_option()
+ | ssl:tls_server_option()
| gen_tcp:listen_option().
-type option() :: {port, non_neg_integer()}
diff --git a/lib/edoc/src/edoc.erl b/lib/edoc/src/edoc.erl
index b641118c5d..e9d62d3283 100644
--- a/lib/edoc/src/edoc.erl
+++ b/lib/edoc/src/edoc.erl
@@ -578,7 +578,7 @@ read_source(Name, Opts0) ->
Opts = expand_opts(Opts0),
case read_source_1(Name, Opts) of
{ok, Forms} ->
- check_forms(Forms, Name),
+ check_forms(Forms, Name, Opts),
Forms;
{error, R} ->
edoc_report:error({"error reading file '~ts'.",
@@ -692,13 +692,19 @@ fll([T | L], LastLine, Ts) ->
fll(L, _LastLine, Ts) ->
lists:reverse(L, Ts).
-check_forms(Fs, Name) ->
+check_forms(Fs, Name, Opts) ->
Fun = fun (F) ->
case erl_syntax:type(F) of
error_marker ->
case erl_syntax:error_marker_info(F) of
{L, M, D} ->
- edoc_report:error(L, Name, {format_error, M, D});
+ edoc_report:error(L, Name, {format_error, M, D}),
+ case proplists:get_bool(preprocess, Opts) of
+ true ->
+ ok;
+ false ->
+ helpful_message(Name)
+ end;
Other ->
edoc_report:report(Name, "unknown error in "
"source code: ~w.", [Other])
@@ -710,6 +716,11 @@ check_forms(Fs, Name) ->
end,
lists:foreach(Fun, Fs).
+helpful_message(Name) ->
+ Ms = ["If the error is caused by too exotic macro",
+ "definitions or uses of macros, adding option",
+ "{preprocess, true} can help. See also edoc(3)."],
+ lists:foreach(fun(M) -> edoc_report:report(Name, M, []) end, Ms).
%% @spec get_doc(File::filename()) -> {ModuleName, edoc_module()}
%% @equiv get_doc(File, [])
diff --git a/lib/eldap/README b/lib/eldap/README
index e1bde9d658..238f140e93 100644
--- a/lib/eldap/README
+++ b/lib/eldap/README
@@ -23,7 +23,7 @@ system has been configured with SSL.
In the test directory there are some hints and examples
on how to test the code and how to setup and populate
an OpenLDAP server. The 'eldap' code has been tested
-agains OpenLDAP, IPlanet and ActiveDirectory servers.
+against OpenLDAP, IPlanet and ActiveDirectory servers.
If you plan to incorporate this code into your system
I suggest that you build a server/supervisor harnesk
diff --git a/lib/eldap/doc/src/eldap.xml b/lib/eldap/doc/src/eldap.xml
index f2c7889e58..790a2f4e26 100644
--- a/lib/eldap/doc/src/eldap.xml
+++ b/lib/eldap/doc/src/eldap.xml
@@ -28,7 +28,7 @@
<date>2000-06-20</date>
<rev>B</rev>
</header>
- <module>eldap</module>
+ <module since="OTP R15B01">eldap</module>
<modulesummary>LDAP Client</modulesummary>
<description>
<p>This module provides a client api to the Lightweight Directory Access Protocol (LDAP).
@@ -103,7 +103,7 @@
<funcs>
<func>
- <name>open([Host]) -> {ok, Handle} | {error, Reason}</name>
+ <name since="OTP R15B01">open([Host]) -> {ok, Handle} | {error, Reason}</name>
<fsummary>Open a connection to an LDAP server.</fsummary>
<type>
<v>Handle = handle()</v>
@@ -113,7 +113,7 @@
</desc>
</func>
<func>
- <name>open([Host], [Option]) -> {ok, Handle} | {error, Reason}</name>
+ <name since="OTP R15B01">open([Host], [Option]) -> {ok, Handle} | {error, Reason}</name>
<fsummary>Open a connection to an LDAP server.</fsummary>
<type>
<v>Handle = handle()</v>
@@ -129,7 +129,7 @@
</desc>
</func>
<func>
- <name>close(Handle) -> ok</name>
+ <name since="OTP R15B01">close(Handle) -> ok</name>
<fsummary>Shutdown the connection.</fsummary>
<type>
<v>Handle = handle()</v>
@@ -140,14 +140,14 @@
</desc>
</func>
<func>
- <name>start_tls(Handle, Options) -> return_value()</name>
+ <name since="OTP R16B03">start_tls(Handle, Options) -> return_value()</name>
<fsummary>Upgrade a connection to TLS.</fsummary>
<desc>
<p>Same as start_tls(Handle, Options, infinity)</p>
</desc>
</func>
<func>
- <name>start_tls(Handle, Options, Timeout) -> return_value()</name>
+ <name since="OTP R16B03">start_tls(Handle, Options, Timeout) -> return_value()</name>
<fsummary>Upgrade a connection to TLS.</fsummary>
<type>
<v>Handle = handle()</v>
@@ -176,7 +176,7 @@
</desc>
</func>
<func>
- <name>simple_bind(Handle, Dn, Password) -> return_value()</name>
+ <name since="OTP R15B01">simple_bind(Handle, Dn, Password) -> return_value()</name>
<fsummary>Authenticate the connection.</fsummary>
<type>
<v>Handle = handle()</v>
@@ -188,7 +188,7 @@
</desc>
</func>
<func>
- <name>add(Handle, Dn, [Attribute]) -> return_value()</name>
+ <name since="OTP R15B01">add(Handle, Dn, [Attribute]) -> return_value()</name>
<fsummary>Add an entry.</fsummary>
<type>
<v>Handle = handle()</v>
@@ -209,7 +209,7 @@
</desc>
</func>
<func>
- <name>delete(Handle, Dn) -> return_value()</name>
+ <name since="OTP R15B01">delete(Handle, Dn) -> return_value()</name>
<fsummary>Delete an entry.</fsummary>
<type>
<v>Dn = string()</v>
@@ -223,7 +223,7 @@
</func>
<func>
- <name>mod_add(Type, [Value]) -> modify_op()</name>
+ <name since="OTP R15B01">mod_add(Type, [Value]) -> modify_op()</name>
<fsummary>Create a modification operation.</fsummary>
<type>
<v>Type = string()</v>
@@ -232,7 +232,7 @@
<desc> <p> Create an add modification operation.</p> </desc>
</func>
<func>
- <name>mod_delete(Type, [Value]) -> modify_op()</name>
+ <name since="OTP R15B01">mod_delete(Type, [Value]) -> modify_op()</name>
<fsummary>Create a modification operation.</fsummary>
<type>
<v>Type = string()</v>
@@ -241,7 +241,7 @@
<desc> <p> Create a delete modification operation.</p> </desc>
</func>
<func>
- <name>mod_replace(Type, [Value]) -> modify_op()</name>
+ <name since="OTP R15B01">mod_replace(Type, [Value]) -> modify_op()</name>
<fsummary>Create a modification operation.</fsummary>
<type>
<v>Type = string()</v>
@@ -251,7 +251,7 @@
</func>
<func>
- <name>modify(Handle, Dn, [ModifyOp]) -> return_value()</name>
+ <name since="OTP R15B01">modify(Handle, Dn, [ModifyOp]) -> return_value()</name>
<fsummary>Modify an entry.</fsummary>
<type>
<v>Dn = string()</v>
@@ -267,7 +267,7 @@
</desc>
</func>
<func>
- <name>modify_password(Handle, Dn, NewPasswd) -> return_value() | {ok, GenPasswd}</name>
+ <name since="OTP 18.0">modify_password(Handle, Dn, NewPasswd) -> return_value() | {ok, GenPasswd}</name>
<fsummary>Modify the password of a user.</fsummary>
<type>
<v>Dn = string()</v>
@@ -278,7 +278,7 @@
</desc>
</func>
<func>
- <name>modify_password(Handle, Dn, NewPasswd, OldPasswd) -> return_value() | {ok, GenPasswd}</name>
+ <name since="OTP 18.0">modify_password(Handle, Dn, NewPasswd, OldPasswd) -> return_value() | {ok, GenPasswd}</name>
<fsummary>Modify the password of a user.</fsummary>
<type>
<v>Dn = string()</v>
@@ -307,7 +307,7 @@
</desc>
</func>
<func>
- <name>modify_dn(Handle, Dn, NewRDN, DeleteOldRDN, NewSupDN) -> return_value()</name>
+ <name since="OTP R15B01">modify_dn(Handle, Dn, NewRDN, DeleteOldRDN, NewSupDN) -> return_value()</name>
<fsummary>Modify the DN of an entry.</fsummary>
<type>
<v>Dn = string()</v>
@@ -327,7 +327,7 @@
</desc>
</func>
<func>
- <name>search(Handle, SearchOptions) -> {ok, #eldap_search_result{}} | {ok, {referral,referrals()}} | {error, Reason}</name>
+ <name since="OTP R15B01">search(Handle, SearchOptions) -> {ok, #eldap_search_result{}} | {ok, {referral,referrals()}} | {error, Reason}</name>
<fsummary>Search the Directory</fsummary>
<type>
<v>SearchOptions = #eldap_search{} | [SearchOption]</v>
@@ -354,44 +354,44 @@
</func>
<func>
- <name>baseObject() -> scope()</name>
+ <name since="OTP R15B01">baseObject() -> scope()</name>
<fsummary>Create search scope.</fsummary>
<desc> <p> Search baseobject only.</p> </desc>
</func>
<func>
- <name>singleLevel() -> scope()</name>
+ <name since="OTP R15B01">singleLevel() -> scope()</name>
<fsummary>Create search scope.</fsummary>
<desc> <p> Search the specified level only, i.e. do not recurse.</p> </desc>
</func>
<func>
- <name>wholeSubtree() -> scope()</name>
+ <name since="OTP R15B01">wholeSubtree() -> scope()</name>
<fsummary>Create search scope.</fsummary>
<desc> <p> Search the entire subtree.</p> </desc>
</func>
<func>
- <name>neverDerefAliases() -> dereference()</name>
+ <name since="OTP R15B01">neverDerefAliases() -> dereference()</name>
<fsummary>Create search option.</fsummary>
<desc> <p>Never derefrence aliases, treat aliases as entries.</p> </desc>
</func>
<func>
- <name>derefAlways() -> dereference()</name>
+ <name since="OTP R15B01">derefAlways() -> dereference()</name>
<fsummary>Create search option.</fsummary>
<desc> <p>Always derefrence aliases.</p> </desc>
</func>
<func>
- <name>derefInSearching() -> dereference()</name>
+ <name since="OTP R15B01">derefInSearching() -> dereference()</name>
<fsummary>Create search option.</fsummary>
<desc> <p>Derefrence aliases only when searching.</p> </desc>
</func>
<func>
- <name>derefFindingBaseObj() -> dereference()</name>
+ <name since="OTP R15B01">derefFindingBaseObj() -> dereference()</name>
<fsummary>Create search option.</fsummary>
<desc> <p>Derefrence aliases only in finding the base.</p> </desc>
</func>
<func>
- <name>present(Type) -> filter()</name>
+ <name since="OTP R15B01">present(Type) -> filter()</name>
<fsummary>Create search filter option.</fsummary>
<type>
<v>Type = string()</v>
@@ -399,7 +399,7 @@
<desc> <p>Create a filter which filters on attribute type presence.</p> </desc>
</func>
<func>
- <name>substrings(Type, [SubString]) -> filter()</name>
+ <name since="OTP R15B01">substrings(Type, [SubString]) -> filter()</name>
<fsummary>Create search filter option.</fsummary>
<type>
<v>Type = string()</v>
@@ -409,7 +409,7 @@
<desc> <p>Create a filter which filters on substrings.</p> </desc>
</func>
<func>
- <name>equalityMatch(Type, Value) -> filter()</name>
+ <name since="OTP R15B01">equalityMatch(Type, Value) -> filter()</name>
<fsummary>Create search filter option.</fsummary>
<type>
<v>Type = string()</v>
@@ -418,7 +418,7 @@
<desc> <p>Create a equality filter.</p> </desc>
</func>
<func>
- <name>greaterOrEqual(Type, Value) -> filter()</name>
+ <name since="OTP R15B01">greaterOrEqual(Type, Value) -> filter()</name>
<fsummary>Create search filter option.</fsummary>
<type>
<v>Type = string()</v>
@@ -427,7 +427,7 @@
<desc> <p>Create a greater or equal filter.</p> </desc>
</func>
<func>
- <name>lessOrEqual(Type, Value) -> filter()</name>
+ <name since="OTP R15B01">lessOrEqual(Type, Value) -> filter()</name>
<fsummary>Create search filter option.</fsummary>
<type>
<v>Type = string()</v>
@@ -436,7 +436,7 @@
<desc> <p>Create a less or equal filter.</p> </desc>
</func>
<func>
- <name>approxMatch(Type, Value) -> filter()</name>
+ <name since="OTP R15B01">approxMatch(Type, Value) -> filter()</name>
<fsummary>Create search filter option.</fsummary>
<type>
<v>Type = string()</v>
@@ -445,7 +445,7 @@
<desc> <p>Create a approximation match filter.</p> </desc>
</func>
<func>
- <name>extensibleMatch(MatchValue, OptionalAttrs) -> filter()</name>
+ <name since="OTP 17.4">extensibleMatch(MatchValue, OptionalAttrs) -> filter()</name>
<fsummary>Create search filter option.</fsummary>
<type>
<v>MatchValue = string()</v>
@@ -459,7 +459,7 @@
<p>creates a filter which performs a <c>caseExactMatch</c> on the attribute <c>sn</c> and matches with the value <c>"Bar"</c>. The default value of <c>dnAttributes</c> is <c>false</c>.</p> </desc>
</func>
<func>
- <name>'and'([Filter]) -> filter()</name>
+ <name since="OTP R15B01">'and'([Filter]) -> filter()</name>
<fsummary>Create search filter option.</fsummary>
<type>
<v>Filter = filter()</v>
@@ -467,7 +467,7 @@
<desc> <p>Creates a filter where all <c>Filter</c> must be true.</p> </desc>
</func>
<func>
- <name>'or'([Filter]) -> filter()</name>
+ <name since="OTP R15B01">'or'([Filter]) -> filter()</name>
<fsummary>Create search filter option.</fsummary>
<type>
<v>Filter = filter()</v>
@@ -475,7 +475,7 @@
<desc> <p>Create a filter where at least one of the <c>Filter</c> must be true.</p> </desc>
</func>
<func>
- <name>'not'(Filter) -> filter()</name>
+ <name since="OTP R15B01">'not'(Filter) -> filter()</name>
<fsummary>Create search filter option.</fsummary>
<type>
<v>Filter = filter()</v>
diff --git a/lib/eldap/test/make_certs.erl b/lib/eldap/test/make_certs.erl
index cfa43289e1..e8a13ae113 100644
--- a/lib/eldap/test/make_certs.erl
+++ b/lib/eldap/test/make_certs.erl
@@ -348,7 +348,7 @@ req_cnf(C) ->
"default_bits = ", integer_to_list(C#config.default_bits), "\n"
"RANDFILE = $ROOTDIR/RAND\n"
"encrypt_key = no\n"
- "default_md = md5\n"
+ "default_md = sha1\n"
"#string_mask = pkix\n"
"x509_extensions = ca_ext\n"
"prompt = no\n"
@@ -394,7 +394,7 @@ ca_cnf(C) ->
["crl_extensions = crl_ext\n" || C#config.v2_crls],
"unique_subject = no\n"
"default_days = 3600\n"
- "default_md = md5\n"
+ "default_md = sha1\n"
"preserve = no\n"
"policy = policy_match\n"
"\n"
diff --git a/lib/erl_docgen/doc/src/doc-build.xml b/lib/erl_docgen/doc/src/doc-build.xml
index 3ea8798639..17e13bff81 100644
--- a/lib/erl_docgen/doc/src/doc-build.xml
+++ b/lib/erl_docgen/doc/src/doc-build.xml
@@ -178,7 +178,7 @@
</section>
<section>
- <title>Upcomming changes</title>
+ <title>Upcoming changes</title>
<p>
The output from the <c>erl_docgen</c> documentation build process is now just the OTP style.
But in a near future we will for example add the possibility to change logo, color in the PDF and
diff --git a/lib/erl_docgen/doc/src/docgen_xml_check.xml b/lib/erl_docgen/doc/src/docgen_xml_check.xml
index 68253edef7..8d6dceef43 100644
--- a/lib/erl_docgen/doc/src/docgen_xml_check.xml
+++ b/lib/erl_docgen/doc/src/docgen_xml_check.xml
@@ -30,7 +30,7 @@
<date></date>
<rev></rev>
</header>
- <module>docgen_xml_check</module>
+ <module since="OTP R15B">docgen_xml_check</module>
<modulesummary>Validate XML documentation source code</modulesummary>
<description>
<p><c>docgen_xml_check</c> contains functions for validating XML
@@ -39,7 +39,7 @@
<funcs>
<func>
- <name>validate(File) -> ok | error | {error, badfile}</name>
+ <name since="OTP R15B">validate(File) -> ok | error | {error, badfile}</name>
<fsummary>Validate XML source code.</fsummary>
<type>
<v>File = string()</v>
diff --git a/lib/erl_docgen/priv/css/otp_doc.css b/lib/erl_docgen/priv/css/otp_doc.css
index 34c6befb0e..17d9f8dd56 100644
--- a/lib/erl_docgen/priv/css/otp_doc.css
+++ b/lib/erl_docgen/priv/css/otp_doc.css
@@ -71,6 +71,39 @@ a:visited { color: #1b6ec2; text-decoration: none }
}
.bold_code { font-family: mono, Courier, monospace; font-weight: bold }
+
+/* Invisible table for function specs,
+ * just to get since-version out in right margin */
+.func-table, .func-tr, .func-td, .cfunc-td, .func-since-td {
+ width: 200%;
+ border: 0;
+ padding: 0;
+ margin: 0;
+}
+
+.func-tr:nth-child(n) {
+ background: inherit /* turn off zebra striped rows */
+}
+
+.func-td {
+ width: 50%;
+}
+
+.cfunc-td {
+ width: 50%;
+ padding-left: 100px;
+ text-indent: -100px;
+}
+
+.func-since-td {
+ width: 50%;
+ padding-left: 1em
+}
+
+.func-td:hover {
+ background-color: #f5f5f5;
+}
+
.code {
font-family: mono, Courier, monospace;
font-weight: normal;
@@ -283,3 +316,9 @@ a > .code {
.func-types-title{
font-size: 1em;
}
+
+.since{
+ color: gray;
+ font-weight: normal;
+ font-size: small;
+} \ No newline at end of file
diff --git a/lib/erl_docgen/priv/dtd/cref.dtd b/lib/erl_docgen/priv/dtd/cref.dtd
index 5ccd98ed89..d392081807 100644
--- a/lib/erl_docgen/priv/dtd/cref.dtd
+++ b/lib/erl_docgen/priv/dtd/cref.dtd
@@ -30,6 +30,8 @@
<!-- `name' is used in common.refs.dtd and must therefore
be defined in each *ref. dtd -->
<!ELEMENT name (ret,nametext) >
+<!ATTLIST name since CDATA #IMPLIED>
+
<!ELEMENT ret (#PCDATA) >
<!ELEMENT nametext (#PCDATA) >
diff --git a/lib/erl_docgen/priv/dtd/erlref.dtd b/lib/erl_docgen/priv/dtd/erlref.dtd
index 78d6771f52..8202ea5a4d 100644
--- a/lib/erl_docgen/priv/dtd/erlref.dtd
+++ b/lib/erl_docgen/priv/dtd/erlref.dtd
@@ -25,6 +25,7 @@
<!ELEMENT erlref (header,module,modulesummary,description,
(section|funcs|datatypes)*,authors?) >
<!ELEMENT module (#PCDATA) >
+<!ATTLIST module since CDATA #IMPLIED>
<!ELEMENT modulesummary (#PCDATA) >
<!-- `name' is used in common.refs.dtd and must therefore
@@ -34,4 +35,5 @@
arity CDATA #IMPLIED
clause_i CDATA #IMPLIED
anchor CDATA #IMPLIED
+ since CDATA #IMPLIED
n_vars CDATA #IMPLIED>
diff --git a/lib/erl_docgen/priv/xsl/db_html.xsl b/lib/erl_docgen/priv/xsl/db_html.xsl
index a0a922216b..c9be926e1e 100644
--- a/lib/erl_docgen/priv/xsl/db_html.xsl
+++ b/lib/erl_docgen/priv/xsl/db_html.xsl
@@ -191,6 +191,7 @@
<xsl:variable name="name" select="@name"/>
<xsl:variable name="arity" select="@arity"/>
<xsl:variable name="anchor" select="@anchor"/>
+ <xsl:variable name="since" select="@since"/>
<xsl:variable name="spec0">
<xsl:call-template name="find_spec"/>
</xsl:variable>
@@ -225,11 +226,12 @@
<xsl:variable name="global_types" select="ancestor::erlref/datatypes"/>
<xsl:variable name="local_types"
select="../type[string-length(@name) > 0]"/>
- <xsl:apply-templates select="$spec/contract/clause/head">
+ <xsl:apply-templates select="$spec/contract/clause/head">
<xsl:with-param name="ghlink" select="ancestor-or-self::*[@ghlink]/@ghlink"/>
<xsl:with-param name="local_types" select="$local_types"/>
<xsl:with-param name="global_types" select="$global_types"/>
- </xsl:apply-templates>
+ <xsl:with-param name="since" select="$since"/>
+ </xsl:apply-templates>
</xsl:when>
</xsl:choose>
</xsl:template>
@@ -238,19 +240,32 @@
<xsl:param name="ghlink"/>
<xsl:param name="local_types"/>
<xsl:param name="global_types"/>
+ <xsl:param name="since"/>
<xsl:variable name="id" select="concat(concat(concat(concat(../../../name,'-'),../../../arity),'-'),generate-id(.))"/>
- <div class="bold_code func-head"
- onMouseOver="document.getElementById('ghlink-{$id}').style.visibility = 'visible';"
- onMouseOut="document.getElementById('ghlink-{$id}').style.visibility = 'hidden';">
- <xsl:call-template name="ghlink">
- <xsl:with-param name="ghlink" select="$ghlink"/>
- <xsl:with-param name="id" select="$id"/>
- </xsl:call-template>
- <xsl:apply-templates mode="local_type">
- <xsl:with-param name="local_types" select="$local_types"/>
- <xsl:with-param name="global_types" select="$global_types"/>
- </xsl:apply-templates>
- </div>
+ <table class="func-table">
+ <tr class="func-tr">
+ <td class="func-td">
+ <div class="bold_code func-head"
+ onMouseOver="document.getElementById('ghlink-{$id}').style.visibility = 'visible';"
+ onMouseOut="document.getElementById('ghlink-{$id}').style.visibility = 'hidden';">
+ <xsl:call-template name="ghlink">
+ <xsl:with-param name="ghlink" select="$ghlink"/>
+ <xsl:with-param name="id" select="$id"/>
+ </xsl:call-template>
+ <xsl:apply-templates mode="local_type">
+ <xsl:with-param name="local_types" select="$local_types"/>
+ <xsl:with-param name="global_types" select="$global_types"/>
+ </xsl:apply-templates>
+ </div>
+ </td>
+ <td class="func-since-td">
+ <xsl:if test="string-length($since) > 0">
+ <span class="since"><xsl:value-of select="$since"/>
+ </span>
+ </xsl:if>
+ </td>
+ </tr>
+ </table>
</xsl:template>
<!-- The *last* <name name="..." arity=".."/> -->
@@ -1884,6 +1899,16 @@
<xsl:with-param name="partnum" select="$partnum"/>
</xsl:apply-templates>
</div>
+ <!-- Since -->
+ <xsl:if test="string-length(../module/@since) > 0">
+ <xsl:call-template name="h3_title_link">
+ <xsl:with-param name="title">Since</xsl:with-param>
+ </xsl:call-template>
+ <div class="REFBODY module-since">
+ Module <xsl:value-of select="../module"/> was introduced in
+ <xsl:value-of select="../module/@since"/>.
+ </div>
+ </xsl:if>
</xsl:template>
<!-- Lib -->
@@ -2030,11 +2055,10 @@
<xsl:template match="func">
<xsl:param name="partnum"/>
- <p><xsl:apply-templates select="name"/>
- <xsl:apply-templates
- select="name[string-length(@arity) > 0 and position()=last()]"
- mode="types"/>
- </p>
+ <xsl:apply-templates select="name"/>
+ <xsl:apply-templates
+ select="name[string-length(@arity) > 0 and position()=last()]"
+ mode="types"/>
<xsl:apply-templates select="fsummary|type|desc">
<xsl:with-param name="partnum" select="$partnum"/>
@@ -2093,19 +2117,22 @@
<xsl:choose>
<xsl:when test="ancestor::cref">
- <span class="bold_code bc-7">
- <xsl:call-template name="title_link">
- <xsl:with-param name="link" select="substring-before(nametext, '(')"/>
- <xsl:with-param name="title">
- <xsl:value-of select="ret"/>
- <xsl:call-template name="maybe-space-after-ret">
- <xsl:with-param name="s" select="ret"/>
- </xsl:call-template>
- <xsl:value-of select="nametext"/>
- </xsl:with-param>
- </xsl:call-template>
- </span>
- <br/>
+ <table class="func-table">
+ <tr class="func-tr">
+ <td class="cfunc-td">
+ <span class="bold_code bc-7">
+ <xsl:call-template name="title_link">
+ <xsl:with-param name="link" select="substring-before(nametext, '(')"/>
+ </xsl:call-template>
+ </span>
+ </td>
+ <td class="func-since-td">
+ <xsl:if test="string-length(@since) > 0">
+ <span class="since"><xsl:value-of select="@since"/></span>
+ </xsl:if>
+ </td>
+ </tr>
+ </table>
</xsl:when>
<xsl:when test="ancestor::erlref">
<xsl:variable name="fname">
@@ -2136,14 +2163,25 @@
</div>
</xsl:when>
<xsl:otherwise>
- <div class="bold_code fun-type">
- <xsl:call-template name="title_link">
- <xsl:with-param name="link" select="concat(concat($fname,'-'),$arity)"/>
- <xsl:with-param name="title">
- <xsl:apply-templates/>
- </xsl:with-param>
- </xsl:call-template>
- </div>
+ <table class="func-table">
+ <tr class="func-tr">
+ <td class="func-td">
+ <div class="bold_code fun-type">
+ <xsl:call-template name="title_link">
+ <xsl:with-param name="link" select="concat(concat($fname,'-'),$arity)"/>
+ <xsl:with-param name="title">
+ <xsl:apply-templates/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </div>
+ </td>
+ <td class="func-since-td">
+ <xsl:if test="string-length(@since) > 0">
+ <span class="since"><xsl:value-of select="@since"/></span>
+ </xsl:if>
+ </td>
+ </tr>
+ </table>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
@@ -2154,18 +2192,6 @@
</xsl:template>
- <xsl:template name="maybe-space-after-ret">
- <xsl:param name="s"/>
- <xsl:variable name="last_char"
- select="substring($s, string-length($s), 1)"/>
- <xsl:choose>
- <xsl:when test="$last_char != '*'">
- <xsl:text> </xsl:text>
- </xsl:when>
- </xsl:choose>
- </xsl:template>
-
-
<!-- Type -->
<xsl:template match="type">
<xsl:param name="partnum"/>
@@ -2219,7 +2245,7 @@
</xsl:template>
<xsl:template name="title_link">
- <xsl:param name="title"/>
+ <xsl:param name="title" select="'APPLY'"/>
<xsl:param name="link" select="erl:to-link(title)"/>
<xsl:param name="ghlink" select="ancestor-or-self::*[@ghlink][position() = 1]/@ghlink"/>
<xsl:variable name="id" select="concat(concat($link,'-'), generate-id(.))"/>
@@ -2229,7 +2255,16 @@
<xsl:with-param name="id" select="$id"/>
<xsl:with-param name="ghlink" select="$ghlink"/>
</xsl:call-template>
- <a class="title_link" name="{$link}" href="#{$link}"><xsl:value-of select="$title"/></a>
+ <a class="title_link" name="{$link}" href="#{$link}">
+ <xsl:choose>
+ <xsl:when test="$title = 'APPLY'">
+ <xsl:apply-templates/> <!-- like <ret> and <nametext> -->
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$title"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </a>
</span>
</xsl:template>
@@ -2659,4 +2694,46 @@
<xsl:value-of select="normalize-space(.)"/>
</xsl:template>
+ <xsl:template match="ret">
+ <xsl:value-of select="."/>
+ <xsl:variable name="last_char" select="substring(., string-length(.), 1)"/>
+ <xsl:if test="$last_char != '*'">
+ <xsl:text> </xsl:text>
+ </xsl:if>
+ </xsl:template>
+
+ <xsl:template match="nametext">
+ <xsl:value-of select="substring-before(.,'(')"/>
+ <xsl:text>(</xsl:text>
+ <xsl:variable name="arglist" select="substring-after(.,'(')"/>
+ <xsl:choose>
+ <xsl:when test="$arglist = ')' or $arglist = 'void)'">
+ <xsl:value-of select="$arglist"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <br/>
+ <xsl:call-template name="cfunc-arglist">
+ <xsl:with-param name="text" select="$arglist"/>
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+ <!-- Format C function argument list with <br> after comma -->
+ <xsl:template name="cfunc-arglist">
+ <xsl:param name="text"/>
+ <xsl:variable name="line" select="normalize-space($text)"/>
+ <xsl:choose>
+ <xsl:when test="contains($line,',')">
+ <xsl:value-of select="substring-before($line,',')"/>,<br/>
+ <xsl:call-template name="cfunc-arglist">
+ <xsl:with-param name="text" select="substring-after($line,',')"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$line"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
</xsl:stylesheet>
diff --git a/lib/erl_docgen/src/docgen_edoc_xml_cb.erl b/lib/erl_docgen/src/docgen_edoc_xml_cb.erl
index b065c18cda..d562cfddcc 100644
--- a/lib/erl_docgen/src/docgen_edoc_xml_cb.erl
+++ b/lib/erl_docgen/src/docgen_edoc_xml_cb.erl
@@ -182,7 +182,7 @@ chapter_title(#xmlElement{content=Es}) -> % name = h3 | h4
%% otp_xmlify(Es1) -> Es2
%% Es1 = Es2 = [#xmlElement{} | #xmlText{}]
%% Fix things that are allowed in XHTML but not in chapter/erlref DTDs.
-%% 1) lists (<ul>, <ol>, <dl>) and code snippets (<pre>) can not occur
+%% 1) lists (<ul>, <ol>, <dl>) and code snippets (<pre>) cannot occur
%% within a <p>, such a <p> must be splitted into a sequence of <p>,
%% <ul>, <ol>, <dl> and <pre>.
%% 2) <a> must only have either a href attribute (corresponds to a
diff --git a/lib/erl_interface/configure.in b/lib/erl_interface/configure.in
index a155ceef7e..53c2f7069c 100644
--- a/lib/erl_interface/configure.in
+++ b/lib/erl_interface/configure.in
@@ -29,11 +29,6 @@ dnl m4_define(EI_VERSION,regexp(m4_include(VERSION),[version \([-.0-9A-Za-z]+\)]
AC_INIT()
-if test "x$no_recursion" != "xyes" -a "x$OVERRIDE_CONFIG_CACHE" = "x"; then
- # We do not want to use a common cache!
- cache_file=/dev/null
-fi
-
dnl How to set srcdir absolute is taken from the GNU Emacs distribution
#### Make srcdir absolute, if it isn't already. It's important to
#### avoid running the path through pwd unnecessary, since pwd can
@@ -66,7 +61,7 @@ fi
TARGET=$host
AC_SUBST(TARGET)
-AC_CONFIG_HEADER([src/$host/config.h:config.h.in])
+AC_CONFIG_HEADER([src/$host/config.h:config.h.in include/$host/ei_config.h:include/ei_config.h.in])
dnl ----------------------------------------------------------------------
dnl Optional features
@@ -82,6 +77,15 @@ AC_ARG_ENABLE(threads,
esac ],
[ threads_disabled=maybe ])
+AC_ARG_ENABLE(mask-real-errno,
+[ --disable-mask-real-errno do not mask real 'errno'],
+[ case "$enableval" in
+ no) mask_real_errno=no ;;
+ *) mask_real_errno=yes ;;
+ esac ],
+[ mask_real_errno=yes ])
+
+
dnl ----------------------------------------------------------------------
dnl Checks for programs
dnl ----------------------------------------------------------------------
@@ -100,6 +104,10 @@ AC_CHECK_SIZEOF(long)
AC_CHECK_SIZEOF(void *)
AC_CHECK_SIZEOF(long long)
+if test $mask_real_errno = yes; then
+ AC_DEFINE(EI_HIDE_REAL_ERRNO, 1, [Define if 'errno' should not be exposed as is in 'erl_errno'])
+fi
+
dnl We set EI_64BIT mode when long is 8 bytes, this makes things
dnl work on windows and unix correctly
if test $ac_cv_sizeof_long = 8; then
@@ -158,7 +166,7 @@ AC_CHECK_LIB([socket], [getpeername])
# Checks for header files.
AC_HEADER_STDC
AC_HEADER_SYS_WAIT
-AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h malloc.h netdb.h netinet/in.h stddef.h stdlib.h string.h sys/param.h sys/socket.h sys/select.h sys/time.h unistd.h sys/types.h])
+AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h malloc.h netdb.h netinet/in.h stddef.h stdlib.h string.h sys/param.h sys/socket.h sys/select.h sys/time.h unistd.h sys/types.h sys/uio.h])
# Checks for typedefs, structures, and compiler characteristics.
# fixme AC_C_CONST & AC_C_VOLATILE needed for Windows?
@@ -176,6 +184,14 @@ AC_TRY_COMPILE([#include <sys/types.h>
AC_DEFINE(HAVE_SOCKLEN_T, [], [Define if you have the `socklen_t' type])],
[AC_MSG_RESULT(no)])
+AC_MSG_CHECKING([for deprecated attribute])
+AC_TRY_COMPILE([],
+[void my_function(void) __attribute__((deprecated));],
+[AC_MSG_RESULT(yes)
+ AC_DEFINE(HAVE_DEPRECATED_ATTRIBUTE, [], [Define to 1 if you have the `deprecated' attribute])],
+[AC_MSG_RESULT(no)])
+
+
# Checks for library functions.
AC_FUNC_ALLOCA
dnl AC_FUNC_FORK
@@ -193,7 +209,7 @@ AC_CHECK_FUNCS([dup2 gethostbyaddr gethostbyname \
gethostbyaddr_r \
gethostbyname_r gethostname writev \
gethrtime gettimeofday inet_ntoa memchr memmove memset select \
- socket strchr strerror strrchr strstr uname])
+ socket strchr strerror strrchr strstr uname sysconf])
AC_CHECK_FUNC(res_gethostbyname, [],
AC_CHECK_LIB(resolv, res_gethostbyname)
)
@@ -255,6 +271,7 @@ AC_SUBST(EI_THREADS)
case "$threads_disabled" in
no|maybe)
LM_CHECK_THR_LIB
+ ETHR_CHK_GCC_ATOMIC_OPS([])
case "$THR_LIB_NAME" in
"")
@@ -268,7 +285,7 @@ case "$threads_disabled" in
EI_THREADS="true"
THR_DEFS="$THR_DEFS -D_WIN32_WINNT=0x0600 -DWINVER=0x0600"
;;
- pthread)
+ pthread)
EI_THREADS="true"
;;
*)
@@ -351,6 +368,7 @@ LDFLAGS="$LDFLAGS $sanitizers"
# XXX
# ---------------------------------------------------------------------------
+
AC_OUTPUT(
src/$host/Makefile:src/Makefile.in
src/$host/eidefs.mk:src/eidefs.mk.in
diff --git a/lib/erl_interface/doc/src/ei.xml b/lib/erl_interface/doc/src/ei.xml
index 9502fb1ee7..2bdb390644 100644
--- a/lib/erl_interface/doc/src/ei.xml
+++ b/lib/erl_interface/doc/src/ei.xml
@@ -35,6 +35,9 @@
<lib>ei</lib>
<libsummary>Routines for handling the Erlang binary term format.</libsummary>
<description>
+ <note><p>The support for VxWorks is deprecated as of OTP 22, and
+ will be removed in OTP 23.</p></note>
+
<p>The library <c>ei</c> contains macros and functions to encode
and decode the Erlang binary term format.</p>
@@ -124,7 +127,7 @@ typedef enum {
<funcs>
<func>
- <name><ret>int</ret><nametext>ei_decode_atom(const char *buf, int *index, char *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_atom(const char *buf, int *index, char *p)</nametext></name>
<fsummary>Decode an atom.</fsummary>
<desc>
<p>Decodes an atom from the binary format. The <c>NULL</c>-terminated
@@ -134,7 +137,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_atom_as(const char *buf, int *index, char *p, int plen, erlang_char_encoding want, erlang_char_encoding* was, erlang_char_encoding* result)</nametext></name>
+ <name since="OTP R16B"><ret>int</ret><nametext>ei_decode_atom_as(const char *buf, int *index, char *p, int plen, erlang_char_encoding want, erlang_char_encoding* was, erlang_char_encoding* result)</nametext></name>
<fsummary>Decode an atom.</fsummary>
<desc>
<p>Decodes an atom from the binary format. The <c>NULL</c>-terminated
@@ -158,7 +161,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_bignum(const char *buf, int *index, mpz_t obj)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_bignum(const char *buf, int *index, mpz_t obj)</nametext></name>
<fsummary>Decode a GMP arbitrary precision integer.</fsummary>
<desc>
<p>Decodes an integer in the binary format to a GMP
@@ -168,7 +171,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_binary(const char *buf, int *index, void *p, long *len)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_binary(const char *buf, int *index, void *p, long *len)</nametext></name>
<fsummary>Decode a binary.</fsummary>
<desc>
<p>Decodes a binary from the binary format. Parameter
@@ -180,7 +183,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_boolean(const char *buf, int *index, int *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_boolean(const char *buf, int *index, int *p)</nametext></name>
<fsummary>Decode a boolean.</fsummary>
<desc>
<p>Decodes a boolean value from the binary format.
@@ -190,7 +193,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_char(const char *buf, int *index, char *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_char(const char *buf, int *index, char *p)</nametext></name>
<fsummary>Decode an 8-bit integer between 0-255.</fsummary>
<desc>
<p>Decodes a char (8-bit) integer between 0-255 from the binary format.
@@ -203,7 +206,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_double(const char *buf, int *index, double *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_double(const char *buf, int *index, double *p)</nametext></name>
<fsummary>Decode a double.</fsummary>
<desc>
<p>Decodes a double-precision (64-bit) floating
@@ -212,7 +215,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_ei_term(const char* buf, int* index, ei_term* term)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_ei_term(const char* buf, int* index, ei_term* term)</nametext></name>
<fsummary>Decode a term, without previous knowledge of type.</fsummary>
<desc>
<p>Decodes any term, or at least tries to. If the term
@@ -233,8 +236,8 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_fun(const char *buf, int *index, erlang_fun *p)</nametext></name>
- <name><ret>void</ret><nametext>free_fun(erlang_fun* f)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_fun(const char *buf, int *index, erlang_fun *p)</nametext></name>
+ <name since=""><ret>void</ret><nametext>free_fun(erlang_fun* f)</nametext></name>
<fsummary>Decode a fun.</fsummary>
<desc>
<p>Decodes a fun from the binary format. Parameter
@@ -248,7 +251,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_list_header(const char *buf, int *index, int *arity)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_list_header(const char *buf, int *index, int *arity)</nametext></name>
<fsummary>Decode a list.</fsummary>
<desc>
<p>Decodes a list header from the binary
@@ -265,7 +268,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_long(const char *buf, int *index, long *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_long(const char *buf, int *index, long *p)</nametext></name>
<fsummary>Decode integer.</fsummary>
<desc>
<p>Decodes a long integer from the binary format.
@@ -275,7 +278,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_longlong(const char *buf, int *index, long long *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_longlong(const char *buf, int *index, long long *p)</nametext></name>
<fsummary>Decode integer.</fsummary>
<desc>
<p>Decodes a GCC <c>long long</c> or Visual C++
@@ -286,7 +289,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_map_header(const char *buf, int *index, int *arity)</nametext></name>
+ <name since="OTP 17.0"><ret>int</ret><nametext>ei_decode_map_header(const char *buf, int *index, int *arity)</nametext></name>
<fsummary>Decode a map.</fsummary>
<desc>
<p>Decodes a map header from the binary
@@ -299,7 +302,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_pid(const char *buf, int *index, erlang_pid *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_pid(const char *buf, int *index, erlang_pid *p)</nametext></name>
<fsummary>Decode a <c>pid</c>.</fsummary>
<desc>
<p>Decodes a process identifier (pid) from the binary format.</p>
@@ -307,7 +310,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_port(const char *buf, int *index, erlang_port *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_port(const char *buf, int *index, erlang_port *p)</nametext></name>
<fsummary>Decode a port.</fsummary>
<desc>
<p>Decodes a port identifier from the binary format.</p>
@@ -315,7 +318,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_ref(const char *buf, int *index, erlang_ref *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_ref(const char *buf, int *index, erlang_ref *p)</nametext></name>
<fsummary>Decode a reference.</fsummary>
<desc>
<p>Decodes a reference from the binary format.</p>
@@ -323,7 +326,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_string(const char *buf, int *index, char *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_string(const char *buf, int *index, char *p)</nametext></name>
<fsummary>Decode a string.</fsummary>
<desc>
<p>Decodes a string from the binary format. A
@@ -338,7 +341,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_term(const char *buf, int *index, void *t)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_term(const char *buf, int *index, void *t)</nametext></name>
<fsummary>Decode a <c>ETERM</c>.</fsummary>
<desc>
<p>Decodes a term from the binary format. The term
@@ -352,7 +355,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_trace(const char *buf, int *index, erlang_trace *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_trace(const char *buf, int *index, erlang_trace *p)</nametext></name>
<fsummary>Decode a trace token.</fsummary>
<desc>
<p>Decodes an Erlang trace token from the binary format.</p>
@@ -360,7 +363,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_tuple_header(const char *buf, int *index, int *arity)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_tuple_header(const char *buf, int *index, int *arity)</nametext></name>
<fsummary>Decode a tuple.</fsummary>
<desc>
<p>Decodes a tuple header, the number of elements
@@ -370,7 +373,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_ulong(const char *buf, int *index, unsigned long *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_ulong(const char *buf, int *index, unsigned long *p)</nametext></name>
<fsummary>Decode unsigned integer.</fsummary>
<desc>
<p>Decodes an unsigned long integer from the binary format.
@@ -380,7 +383,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_ulonglong(const char *buf, int *index, unsigned long long *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_ulonglong(const char *buf, int *index, unsigned long long *p)</nametext></name>
<fsummary>Decode unsigned integer.</fsummary>
<desc>
<p>Decodes a GCC <c>unsigned long long</c> or Visual C++
@@ -390,7 +393,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_version(const char *buf, int *index, int *version)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_version(const char *buf, int *index, int *version)</nametext></name>
<fsummary>Decode an empty list (<c>nil</c>).</fsummary>
<desc>
<p>Decodes the version magic number for the
@@ -400,10 +403,10 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_atom(char *buf, int *index, const char *p)</nametext></name>
- <name><ret>int</ret><nametext>ei_encode_atom_len(char *buf, int *index, const char *p, int len)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_atom(ei_x_buff* x, const char *p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_atom_len(ei_x_buff* x, const char *p, int len)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_atom(char *buf, int *index, const char *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_atom_len(char *buf, int *index, const char *p, int len)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_encode_atom(ei_x_buff* x, const char *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_encode_atom_len(ei_x_buff* x, const char *p, int len)</nametext></name>
<fsummary>Encode an atom.</fsummary>
<desc>
<p>Encodes an atom in the binary format. Parameter <c>p</c>
@@ -415,10 +418,10 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_atom_as(char *buf, int *index, const char *p, erlang_char_encoding from_enc, erlang_char_encoding to_enc)</nametext></name>
- <name><ret>int</ret><nametext>ei_encode_atom_len_as(char *buf, int *index, const char *p, int len, erlang_char_encoding from_enc, erlang_char_encoding to_enc)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_atom_as(ei_x_buff* x, const char *p, erlang_char_encoding from_enc, erlang_char_encoding to_enc)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_atom_len_as(ei_x_buff* x, const char *p, int len, erlang_char_encoding from_enc, erlang_char_encoding to_enc)</nametext></name>
+ <name since="OTP R16B"><ret>int</ret><nametext>ei_encode_atom_as(char *buf, int *index, const char *p, erlang_char_encoding from_enc, erlang_char_encoding to_enc)</nametext></name>
+ <name since="OTP R16B"><ret>int</ret><nametext>ei_encode_atom_len_as(char *buf, int *index, const char *p, int len, erlang_char_encoding from_enc, erlang_char_encoding to_enc)</nametext></name>
+ <name since="OTP R16B"><ret>int</ret><nametext>ei_x_encode_atom_as(ei_x_buff* x, const char *p, erlang_char_encoding from_enc, erlang_char_encoding to_enc)</nametext></name>
+ <name since="OTP R16B"><ret>int</ret><nametext>ei_x_encode_atom_len_as(ei_x_buff* x, const char *p, int len, erlang_char_encoding from_enc, erlang_char_encoding to_enc)</nametext></name>
<fsummary>Encode an atom.</fsummary>
<desc>
<p>Encodes an atom in the binary format. Parameter <c>p</c> is the name of the atom with
@@ -435,8 +438,8 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_bignum(char *buf, int *index, mpz_t obj)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_bignum(ei_x_buff *x, mpz_t obj)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_bignum(char *buf, int *index, mpz_t obj)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_encode_bignum(ei_x_buff *x, mpz_t obj)</nametext></name>
<fsummary>Encode an arbitrary precision integer.</fsummary>
<desc>
<p>Encodes a GMP <c>mpz_t</c> integer to binary format.
@@ -446,8 +449,8 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_binary(char *buf, int *index, const void *p, long len)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_binary(ei_x_buff* x, const void *p, long len)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_binary(char *buf, int *index, const void *p, long len)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_encode_binary(ei_x_buff* x, const void *p, long len)</nametext></name>
<fsummary>Encode a binary.</fsummary>
<desc>
<p>Encodes a binary in the binary format. The data is at
@@ -456,8 +459,8 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_boolean(char *buf, int *index, int p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_boolean(ei_x_buff* x, int p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_boolean(char *buf, int *index, int p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_encode_boolean(ei_x_buff* x, int p)</nametext></name>
<fsummary>Encode a boolean.</fsummary>
<desc>
<p>Encodes a boolean value as the atom <c>true</c> if
@@ -467,8 +470,8 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_char(char *buf, int *index, char p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_char(ei_x_buff* x, char p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_char(char *buf, int *index, char p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_encode_char(ei_x_buff* x, char p)</nametext></name>
<fsummary>Encode an 8-bit integer between 0-255.</fsummary>
<desc>
<p>Encodes a char (8-bit) as an integer between 0-255 in the binary
@@ -481,8 +484,8 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_double(char *buf, int *index, double p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_double(ei_x_buff* x, double p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_double(char *buf, int *index, double p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_encode_double(ei_x_buff* x, double p)</nametext></name>
<fsummary>Encode a double float.</fsummary>
<desc>
<p>Encodes a double-precision (64-bit) floating point number in
@@ -493,8 +496,8 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_empty_list(char* buf, int* index)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_empty_list(ei_x_buff* x)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_empty_list(char* buf, int* index)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_encode_empty_list(ei_x_buff* x)</nametext></name>
<fsummary>Encode an empty list (<c>nil</c>).</fsummary>
<desc>
<p>Encodes an empty list. It is often used at the tail of a list.</p>
@@ -502,8 +505,8 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_fun(char *buf, int *index, const erlang_fun *p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_fun(ei_x_buff* x, const erlang_fun* fun)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_fun(char *buf, int *index, const erlang_fun *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_encode_fun(ei_x_buff* x, const erlang_fun* fun)</nametext></name>
<fsummary>Encode a fun.</fsummary>
<desc>
<p>Encodes a fun in the binary format. Parameter <c>p</c>
@@ -515,8 +518,8 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_list_header(char *buf, int *index, int arity)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_list_header(ei_x_buff* x, int arity)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_list_header(char *buf, int *index, int arity)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_encode_list_header(ei_x_buff* x, int arity)</nametext></name>
<fsummary>Encode a list.</fsummary>
<desc>
<p>Encodes a list header, with a specified
@@ -552,8 +555,8 @@ ei_x_encode_empty_list(&amp;x);</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_long(char *buf, int *index, long p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_long(ei_x_buff* x, long p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_long(char *buf, int *index, long p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_encode_long(ei_x_buff* x, long p)</nametext></name>
<fsummary>Encode integer.</fsummary>
<desc>
<p>Encodes a long integer in the binary format.
@@ -563,8 +566,8 @@ ei_x_encode_empty_list(&amp;x);</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_longlong(char *buf, int *index, long long p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_longlong(ei_x_buff* x, long long p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_longlong(char *buf, int *index, long long p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_encode_longlong(ei_x_buff* x, long long p)</nametext></name>
<fsummary>Encode integer.</fsummary>
<desc>
<p>Encodes a GCC <c>long long</c> or Visual C++
@@ -574,8 +577,8 @@ ei_x_encode_empty_list(&amp;x);</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_map_header(char *buf, int *index, int arity)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_map_header(ei_x_buff* x, int arity)</nametext></name>
+ <name since="OTP 17.0"><ret>int</ret><nametext>ei_encode_map_header(char *buf, int *index, int arity)</nametext></name>
+ <name since="OTP 17.0"><ret>int</ret><nametext>ei_x_encode_map_header(ei_x_buff* x, int arity)</nametext></name>
<fsummary>Encode a map.</fsummary>
<desc>
<p>Encodes a map header, with a specified arity. The next
@@ -595,8 +598,8 @@ ei_x_encode_string(&amp;x, "Banana");</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_pid(char *buf, int *index, const erlang_pid *p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_pid(ei_x_buff* x, const erlang_pid *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_pid(char *buf, int *index, const erlang_pid *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_encode_pid(ei_x_buff* x, const erlang_pid *p)</nametext></name>
<fsummary>Encode a pid.</fsummary>
<desc>
<p>Encodes an Erlang process identifier (pid) in the binary
@@ -607,8 +610,8 @@ ei_x_encode_string(&amp;x, "Banana");</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_port(char *buf, int *index, const erlang_port *p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_port(ei_x_buff* x, const erlang_port *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_port(char *buf, int *index, const erlang_port *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_encode_port(ei_x_buff* x, const erlang_port *p)</nametext></name>
<fsummary>Encode a port.</fsummary>
<desc>
<p>Encodes an Erlang port in the binary format. Parameter
@@ -619,8 +622,8 @@ ei_x_encode_string(&amp;x, "Banana");</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_ref(char *buf, int *index, const erlang_ref *p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_ref(ei_x_buff* x, const erlang_ref *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_ref(char *buf, int *index, const erlang_ref *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_encode_ref(ei_x_buff* x, const erlang_ref *p)</nametext></name>
<fsummary>Encode a ref.</fsummary>
<desc>
<p>Encodes an Erlang reference in the binary format. Parameter
@@ -631,10 +634,10 @@ ei_x_encode_string(&amp;x, "Banana");</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_string(char *buf, int *index, const char *p)</nametext></name>
- <name><ret>int</ret><nametext>ei_encode_string_len(char *buf, int *index, const char *p, int len)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_string(ei_x_buff* x, const char *p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_string_len(ei_x_buff* x, const char* s, int len)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_string(char *buf, int *index, const char *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_string_len(char *buf, int *index, const char *p, int len)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_encode_string(ei_x_buff* x, const char *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_encode_string_len(ei_x_buff* x, const char* s, int len)</nametext></name>
<fsummary>Encode a string.</fsummary>
<desc>
<p>Encodes a string in the binary format. (A string in Erlang
@@ -645,8 +648,8 @@ ei_x_encode_string(&amp;x, "Banana");</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_term(char *buf, int *index, void *t)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_term(ei_x_buff* x, void *t)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_term(char *buf, int *index, void *t)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_encode_term(ei_x_buff* x, void *t)</nametext></name>
<fsummary>Encode an <c>erl_interface</c> term.</fsummary>
<desc>
<p>Encodes an <c>ETERM</c>, as obtained from
@@ -656,8 +659,8 @@ ei_x_encode_string(&amp;x, "Banana");</pre>
</desc>
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_trace(char *buf, int *index, const erlang_trace *p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_trace(ei_x_buff* x, const erlang_trace *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_trace(char *buf, int *index, const erlang_trace *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_encode_trace(ei_x_buff* x, const erlang_trace *p)</nametext></name>
<fsummary>Encode a trace token.</fsummary>
<desc>
<p>Encodes an Erlang trace token in the binary format.
@@ -668,8 +671,8 @@ ei_x_encode_string(&amp;x, "Banana");</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_tuple_header(char *buf, int *index, int arity)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_tuple_header(ei_x_buff* x, int arity)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_tuple_header(char *buf, int *index, int arity)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_encode_tuple_header(ei_x_buff* x, int arity)</nametext></name>
<fsummary>Encode a tuple.</fsummary>
<desc>
<p>Encodes a tuple header, with a specified
@@ -687,8 +690,8 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_ulong(char *buf, int *index, unsigned long p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_ulong(ei_x_buff* x, unsigned long p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_ulong(char *buf, int *index, unsigned long p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_encode_ulong(ei_x_buff* x, unsigned long p)</nametext></name>
<fsummary>Encode unsigned integer.</fsummary>
<desc>
<p>Encodes an unsigned long integer in the binary format.
@@ -698,8 +701,8 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_ulonglong(char *buf, int *index, unsigned long long p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_ulonglong(ei_x_buff* x, unsigned long long p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_ulonglong(char *buf, int *index, unsigned long long p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_encode_ulonglong(ei_x_buff* x, unsigned long long p)</nametext></name>
<fsummary>Encode unsigned integer.</fsummary>
<desc>
<p>Encodes a GCC <c>unsigned long long</c> or Visual C++
@@ -709,8 +712,8 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_version(char *buf, int *index)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_version(ei_x_buff* x)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_version(char *buf, int *index)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_encode_version(ei_x_buff* x)</nametext></name>
<fsummary>Encode version.</fsummary>
<desc>
<p>Encodes a version magic number for the binary format. Must
@@ -719,7 +722,7 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_get_type(const char *buf, const int *index, int *type, int *size)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_get_type(const char *buf, const int *index, int *type, int *size)</nametext></name>
<fsummary>Fetch the type and size of an encoded term.</fsummary>
<desc>
<p>Returns the type in <c>type</c> and size in
@@ -733,8 +736,23 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_print_term(FILE* fp, const char* buf, int* index)</nametext></name>
- <name><ret>int</ret><nametext>ei_s_print_term(char** s, const char* buf, int* index)</nametext></name>
+ <name since="OTP @OTP-15442@"><ret>int</ret><nametext>ei_init(void)</nametext></name>
+ <fsummary>Initialize the ei library.</fsummary>
+ <desc>
+ <p>Initialize the <c>ei</c> library. This function should be called once
+ (and only once) before calling any other functionality in the <c>ei</c>
+ library. However, note the exception below.</p>
+ <p>If the <c>ei</c> library is used together with the <c>erl_interface</c>
+ library, this function should <em>not</em> be called directly. It will be
+ called by the <c>erl_init()</c> function which should be used to initialize
+ the combination of the two libraries instead.</p>
+ <p>On success zero is returned. On failure a posix error code is returned.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since=""><ret>int</ret><nametext>ei_print_term(FILE* fp, const char* buf, int* index)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_s_print_term(char** s, const char* buf, int* index)</nametext></name>
<fsummary>Print a term in clear text.</fsummary>
<desc>
<p>Prints a term, in clear text, to the file
@@ -759,7 +777,7 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
</func>
<func>
- <name><ret>void</ret><nametext>ei_set_compat_rel(release_number)</nametext></name>
+ <name since=""><ret>void</ret><nametext>ei_set_compat_rel(release_number)</nametext></name>
<fsummary>Set the ei library in compatibility mode.</fsummary>
<type>
<v>unsigned release_number;</v>
@@ -794,7 +812,7 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_skip_term(const char* buf, int* index)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_skip_term(const char* buf, int* index)</nametext></name>
<fsummary>Skip a term.</fsummary>
<desc>
<p>Skips a term in the specified buffer;
@@ -815,8 +833,8 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_x_append(ei_x_buff* x, const ei_x_buff* x2)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_append_buf(ei_x_buff* x, const char* buf, int len)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_append(ei_x_buff* x, const ei_x_buff* x2)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_append_buf(ei_x_buff* x, const char* buf, int len)</nametext></name>
<fsummary>Append a buffer at the end.</fsummary>
<desc>
<p>Appends data at the end of buffer <c>x</c>.</p>
@@ -824,8 +842,8 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_x_format(ei_x_buff* x, const char* fmt, ...)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_format_wo_ver(ei_x_buff* x, const char *fmt, ... )</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_format(ei_x_buff* x, const char* fmt, ...)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_format_wo_ver(ei_x_buff* x, const char *fmt, ... )</nametext></name>
<fsummary>Format a term from a format string and parameters.</fsummary>
<desc>
<p>Formats a term, given as a string, to a buffer.
@@ -853,7 +871,7 @@ encodes the tuple {numbers,12,3.14159}</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_x_free(ei_x_buff* x)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_free(ei_x_buff* x)</nametext></name>
<fsummary>Free a buffer.</fsummary>
<desc>
<p>Frees an <c>ei_x_buff</c> buffer.
@@ -862,8 +880,8 @@ encodes the tuple {numbers,12,3.14159}</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_x_new(ei_x_buff* x)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_new_with_version(ei_x_buff* x)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_new(ei_x_buff* x)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_new_with_version(ei_x_buff* x)</nametext></name>
<fsummary>Allocate a new buffer.</fsummary>
<desc>
<p>Allocates a new <c>ei_x_buff</c> buffer. The
diff --git a/lib/erl_interface/doc/src/ei_connect.xml b/lib/erl_interface/doc/src/ei_connect.xml
index 607a7cbff4..df40973270 100644
--- a/lib/erl_interface/doc/src/ei_connect.xml
+++ b/lib/erl_interface/doc/src/ei_connect.xml
@@ -34,6 +34,9 @@
<lib>ei_connect</lib>
<libsummary>Communicate with distributed Erlang.</libsummary>
<description>
+ <note><p>The support for VxWorks is deprecated as of OTP 22, and
+ will be removed in OTP 23.</p></note>
+
<p>This module enables C-programs to communicate with Erlang nodes,
using the Erlang distribution over TCP/IP.</p>
@@ -85,20 +88,288 @@
the <c>_tmo</c> suffix.</p>
</section>
+ <section>
+ <marker id="ussi"/>
+ <title>User Supplied Socket Implementation</title>
+ <p>By default <c>ei</c> supplies a TCP/IPv4 socket interface
+ that is used when communicating. The user can however plug in
+ his/her own IPv4 socket implementation. This, for example, in order
+ to communicate over TLS. A user supplied socket implementation
+ is plugged in by passing a
+ <seealso marker="#ei_socket_callbacks">callback structure</seealso>
+ to either
+ <seealso marker="#ei_connect_init"><c>ei_connect_init_ussi()</c></seealso>
+ or
+ <seealso marker="#ei_connect_init"><c>ei_connect_xinit_ussi()</c></seealso>.</p>
+
+ <p>All callbacks in the <c>ei_socket_callbacks</c> structure
+ <em>should</em> return zero on success; and a posix error
+ code on failure.</p>
+
+ <p>The <c>addr</c> argument of the <c>listen</c>, <c>accept</c>,
+ and <c>connect</c> callbacks refer to appropriate address
+ structure for currently used protocol. Currently <c>ei</c>
+ only supports IPv4. That is, at this time <c>addr</c> always
+ points to a <c>struct sockaddr_in</c> structure.</p>
+
+ <p>The <c>ei_socket_callbacks</c> structure may be enlarged in
+ the future. All fields not set, <em>needs</em> to be zeroed out.</p>
+
+ <marker id="ei_socket_callbacks"/>
+ <code type="none"><![CDATA[
+typedef struct {
+ int flags;
+ int (*socket)(void **ctx, void *setup_ctx);
+ int (*close)(void *ctx);
+ int (*listen)(void *ctx, void *addr, int *len, int backlog);
+ int (*accept)(void **ctx, void *addr, int *len, unsigned tmo);
+ int (*connect)(void *ctx, void *addr, int len, unsigned tmo);
+ int (*writev)(void *ctx, const void *iov, int iovcnt, ssize_t *len, unsigned tmo);
+ int (*write)(void *ctx, const char *buf, ssize_t *len, unsigned tmo);
+ int (*read)(void *ctx, char *buf, ssize_t *len, unsigned tmo);
+ int (*handshake_packet_header_size)(void *ctx, int *sz);
+ int (*connect_handshake_complete)(void *ctx);
+ int (*accept_handshake_complete)(void *ctx);
+ int (*get_fd)(void *ctx, int *fd);
+} ei_socket_callbacks;
+ ]]></code>
+
+ <taglist>
+
+ <tag><c>flags</c></tag>
+ <item>
+ <p>Flags informing <c>ei</c> about the behaviour of the
+ callbacks. Flags should be bitwise or:ed together. If no flag,
+ is set, the <c>flags</c> field should contain <c>0</c>. Currently,
+ supported flags:</p>
+ <taglist>
+ <tag><c>EI_SCLBK_FLG_FULL_IMPL</c></tag>
+ <item>
+ <p>
+ If set, the <c>accept()</c>, <c>connect()</c>,
+ <c>writev()</c>, <c>write()</c>, and <c>read()</c> callbacks
+ implements timeouts. The timeout is passed in the <c>tmo</c>
+ argument and is given in milli seconds. Note that the
+ <c>tmo</c> argument to these callbacks differ from the
+ timeout arguments in the <c>ei</c> API. Zero means a zero
+ timeout. That is, poll and timeout immediately unless the
+ operation is successful. <c>EI_SCLBK_INF_TMO</c>
+ (max <c>unsigned</c>) means infinite timeout. The file
+ descriptor is in blocking mode when a callback is called,
+ and it must be in blocking mode when the callback returns.
+ </p>
+ <p>
+ If not set, <c>ei</c> will implement the timeout using
+ <c>select()</c> in order to determine when to call the
+ callbacks and when to time out. The <c>tmo</c> arguments
+ of the <c>accept()</c>, <c>connect()</c>, <c>writev()</c>,
+ <c>write()</c>, and <c>read()</c> callbacks should be
+ ignored. The callbacks may be called in non-blocking mode.
+ The callbacks are not allowed to change between blocking
+ and non-blocking mode. In order for this to work,
+ <c>select()</c> needs to interact with the socket primitives
+ used the same way as it interacts with the ordinary socket
+ primitives. If this is not the case, the callbacks
+ <em>need</em> to implement timeouts and this flag should
+ be set.
+ </p>
+ </item>
+ </taglist>
+ <p>More flags may be introduced in the future.</p>
+ </item>
+
+ <tag><c>int (*socket)(void **ctx, void *setup_ctx)</c></tag>
+ <item>
+ <p>Create a socket and a context for the socket.</p>
+
+ <p>On success it should set <c>*ctx</c> to point to a context for
+ the created socket. This context will be passed to all other
+ socket callbacks. This function will be passed the same
+ <c>setup_context</c> as passed to the preceeding
+ <seealso marker="#ei_connect_init"><c>ei_connect_init_ussi()</c></seealso>
+ or
+ <seealso marker="#ei_connect_init"><c>ei_connect_xinit_ussi()</c></seealso>
+ call.</p>
+
+ <note><p>During the lifetime of a socket, the pointer <c>*ctx</c>
+ <em>has</em> to remain the same. That is, it cannot later be
+ relocated.</p></note>
+
+ <p>This callback is mandatory.</p>
+ </item>
+
+ <tag><c>int (*close)(void *ctx)</c></tag>
+ <item>
+ <p>Close the socket identified by <c>ctx</c> and destroy the context.</p>
+
+ <p>This callback is mandatory.</p>
+ </item>
+
+ <tag><c>int (*listen)(void *ctx, void *addr, int *len, int backlog)</c></tag>
+ <item>
+ <p>Bind the socket identified by <c>ctx</c> to a local interface
+ and then listen on it.</p>
+
+ <p>The <c>addr</c> and <c>len</c> arguments are both input and output
+ arguments. When called <c>addr</c> points to an address structure of
+ lenght <c>*len</c> containing information on how to bind the socket.
+ Uppon return this callback should have updated the structure referred
+ by <c>addr</c> with information on how the socket actually was bound.
+ <c>*len</c> should be updated to reflect the size of <c>*addr</c>
+ updated. <c>backlog</c> identifies the size of the backlog for the
+ listen socket.</p>
+
+ <p>This callback is mandatory.</p>
+ </item>
+
+ <tag><c>int (*accept)(void **ctx, void *addr, int *len, unsigned tmo)</c></tag>
+ <item>
+ <p>Accept connections on the listen socket identified by
+ <c>*ctx</c>.</p>
+
+ <p>When a connection is accepted, a new context for the accepted
+ connection should be created and <c>*ctx</c> should be updated
+ to point to the new context for the accepted connection. When
+ called <c>addr</c> points to an uninitialized address structure
+ of lenght <c>*len</c>. Uppon return this callback should have
+ updated this structure with information about the client address.
+ <c>*len</c> should be updated to reflect the size of <c>*addr</c>
+ updated.
+ </p>
+
+ <p>If the <c>EI_SCLBK_FLG_FULL_IMPL</c> flag has been set,
+ <c>tmo</c> contains timeout time in milliseconds.</p>
+
+ <note><p>During the lifetime of a socket, the pointer <c>*ctx</c>
+ <em>has</em> to remain the same. That is, it cannot later be
+ relocated.</p></note>
+
+ <p>This callback is mandatory.</p>
+ </item>
+
+ <tag><c>int (*connect)(void *ctx, void *addr, int len, unsigned tmo)</c></tag>
+ <item>
+ <p>Connect the socket identified by <c>ctx</c> to the address
+ identified by <c>addr</c>.</p>
+
+ <p>When called <c>addr</c> points to an address structure of
+ lenght <c>len</c> containing information on where to connect.</p>
+
+ <p>If the <c>EI_SCLBK_FLG_FULL_IMPL</c> flag has been set,
+ <c>tmo</c> contains timeout time in milliseconds.</p>
+
+ <p>This callback is mandatory.</p>
+ </item>
+
+ <tag><c>int (*writev)(void *ctx, const void *iov, long iovcnt, ssize_t *len, unsigned tmo)</c></tag>
+ <item>
+ <p>Write data on the connected socket identified by <c>ctx</c>.</p>
+
+ <p><c>iov</c> points to an array of <c>struct iovec</c> structures of
+ length <c>iovcnt</c> containing data to write to the socket. On success,
+ this callback should set <c>*len</c> to the amount of bytes successfully
+ written on the socket.</p>
+
+ <p>If the <c>EI_SCLBK_FLG_FULL_IMPL</c> flag has been set,
+ <c>tmo</c> contains timeout time in milliseconds.</p>
+
+ <p>This callback is optional. Set the <c>writev</c> field
+ in the the <c>ei_socket_callbacks</c> structure to <c>NULL</c> if not
+ implemented.</p>
+ </item>
+
+ <tag><c>int (*write)(void *ctx, const char *buf, ssize_t *len, unsigned tmo)</c></tag>
+ <item>
+ <p>Write data on the connected socket identified by <c>ctx</c>.</p>
+
+ <p>When called <c>buf</c> points to a buffer of length <c>*len</c>
+ containing the data to write on the socket. On success, this callback
+ should set <c>*len</c> to the amount of bytes successfully written on
+ the socket.</p>
+
+ <p>If the <c>EI_SCLBK_FLG_FULL_IMPL</c> flag has been set,
+ <c>tmo</c> contains timeout time in milliseconds.</p>
+
+ <p>This callback is mandatory.</p>
+ </item>
+
+ <tag><c>int (*read)(void *ctx, char *buf, ssize_t *len, unsigned tmo)</c></tag>
+ <item>
+ <p>Read data on the connected socket identified by <c>ctx</c>.</p>
+
+ <p><c>buf</c> points to a buffer of length <c>*len</c> where the
+ read data should be placed. On success, this callback should update
+ <c>*len</c> to the amount of bytes successfully read on the socket.</p>
+
+ <p>If the <c>EI_SCLBK_FLG_FULL_IMPL</c> flag has been set,
+ <c>tmo</c> contains timeout time in milliseconds.</p>
+
+ <p>This callback is mandatory.</p>
+ </item>
+
+ <tag><c>int (*handshake_packet_header_size)(void *ctx, int *sz)</c></tag>
+ <item>
+ <p>Inform about handshake packet header size to use during the Erlang
+ distribution handshake.</p>
+
+ <p>On success, <c>*sz</c> should be set to the handshake packet header
+ size to use. Valid values are <c>2</c> and <c>4</c>. Erlang TCP
+ distribution use a handshake packet size of <c>2</c> and Erlang TLS
+ distribution use a handshake packet size of <c>4</c>.</p>
+
+ <p>This callback is mandatory.</p>
+ </item>
+
+ <tag><c>int (*connect_handshake_complete)(void *ctx)</c></tag>
+ <item>
+ <p>Called when a locally started handshake has completed successfully.</p>
+
+ <p>This callback is optional. Set the <c>connect_handshake_complete</c> field
+ in the <c>ei_socket_callbacks</c> structure to <c>NULL</c> if not implemented.</p>
+ </item>
+
+ <tag><c>int (*accept_handshake_complete)(void *ctx)</c></tag>
+ <item>
+ <p>Called when a remotely started handshake has completed successfully.</p>
+
+ <p>This callback is optional. Set the <c>accept_handshake_complete</c> field in
+ the <c>ei_socket_callbacks</c> structure to <c>NULL</c> if not implemented.</p>
+ </item>
+
+ <tag><c>int (*get_fd)(void *ctx, int *fd)</c></tag>
+ <item>
+ <p>Inform about file descriptor used by the socket which is identified
+ by <c>ctx</c>.</p>
+
+ <note><p>During the lifetime of a socket, the file descriptor
+ <em>has</em> to remain the same. That is, repeated calls to this
+ callback with the same context <c>should</c> always report the same
+ file descriptor.</p>
+ <p>The file descriptor <em>has</em> to be a real file descriptor.
+ That is, no other operation should be able to get the same file
+ descriptor until it has been released by the <c>close()</c>
+ callback.</p>
+ </note>
+
+ <p>This callback is mandatory.</p>
+ </item>
+ </taglist>
+ </section>
<funcs>
<func>
- <name><ret>struct hostent</ret><nametext>*ei_gethostbyaddr(const char *addr, int len, int type)</nametext></name>
- <name><ret>struct hostent</ret><nametext>*ei_gethostbyaddr_r(const char *addr, int length, int type, struct hostent *hostp, char *buffer, int buflen, int *h_errnop)</nametext></name>
- <name><ret>struct hostent</ret><nametext>*ei_gethostbyname(const char *name)</nametext></name>
- <name><ret>struct hostent</ret><nametext>*ei_gethostbyname_r(const char *name, struct hostent *hostp, char *buffer, int buflen, int *h_errnop)</nametext></name>
+ <name since=""><ret>struct hostent *</ret><nametext>ei_gethostbyaddr(const char *addr, int len, int type)</nametext></name>
+ <name since=""><ret>struct hostent *</ret><nametext>ei_gethostbyaddr_r(const char *addr, int length, int type, struct hostent *hostp, char *buffer, int buflen, int *h_errnop)</nametext></name>
+ <name since=""><ret>struct hostent *</ret><nametext>ei_gethostbyname(const char *name)</nametext></name>
+ <name since=""><ret>struct hostent *</ret><nametext>ei_gethostbyname_r(const char *name, struct hostent *hostp, char *buffer, int buflen, int *h_errnop)</nametext></name>
<fsummary>Name lookup functions.</fsummary>
<desc>
<p>Convenience functions for some common name lookup functions.</p>
</desc>
</func>
+
<func>
- <name><ret>int</ret><nametext>ei_accept(ei_cnode *ec, int listensock, ErlConnect *conp)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_accept(ei_cnode *ec, int listensock, ErlConnect *conp)</nametext></name>
<fsummary>Accept a connection from another node.</fsummary>
<desc>
<p>Used by a server process to accept a
@@ -130,7 +401,7 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret><nametext>ei_accept_tmo(ei_cnode *ec, int listensock, ErlConnect *conp, unsigned timeout_ms)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_accept_tmo(ei_cnode *ec, int listensock, ErlConnect *conp, unsigned timeout_ms)</nametext></name>
<fsummary>Accept a connection from another node with optional
time-out.</fsummary>
<desc>
@@ -141,8 +412,16 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret><nametext>ei_connect(ei_cnode* ec, char *nodename)</nametext></name>
- <name><ret>int</ret><nametext>ei_xconnect(ei_cnode* ec, Erl_IpAddr adr, char *alivename)</nametext></name>
+ <name since="OTP @OTP-15442@"><ret>int</ret><nametext>ei_close_connection(int fd)</nametext></name>
+ <fsummary>Close a connection.</fsummary>
+ <desc>
+ <p>Closes a previously opened connection or listen socket.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since=""><ret>int</ret><nametext>ei_connect(ei_cnode* ec, char *nodename)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_xconnect(ei_cnode* ec, Erl_IpAddr adr, char *alivename)</nametext></name>
<fsummary>Establish a connection to an Erlang node.</fsummary>
<desc>
<p>Sets up a connection to an Erlang node.</p>
@@ -192,8 +471,10 @@ fd = ei_xconnect(&ec, &addr, ALIVE);
</func>
<func>
- <name><ret>int</ret><nametext>ei_connect_init(ei_cnode* ec, const char* this_node_name, const char *cookie, short creation)</nametext></name>
- <name><ret>int</ret><nametext>ei_connect_xinit(ei_cnode* ec, const char *thishostname, const char *thisalivename, const char *thisnodename, Erl_IpAddr thisipaddr, const char *cookie, short creation)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_connect_init(ei_cnode* ec, const char* this_node_name, const char *cookie, short creation)</nametext></name>
+ <name since="OTP @OTP-15442@"><ret>int</ret><nametext>ei_connect_init_ussi(ei_cnode* ec, const char* this_node_name, const char *cookie, short creation, ei_socket_callbacks *cbs, int cbs_sz, void *setup_context)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_connect_xinit(ei_cnode* ec, const char *thishostname, const char *thisalivename, const char *thisnodename, Erl_IpAddr thisipaddr, const char *cookie, short creation)</nametext></name>
+ <name since="OTP @OTP-15442@"><ret>int</ret><nametext>ei_connect_xinit_ussi(ei_cnode* ec, const char *thishostname, const char *thisalivename, const char *thisnodename, Erl_IpAddr thisipaddr, const char *cookie, short creation, ei_socket_callbacks *cbs, int cbs_sz, void *setup_context)</nametext></name>
<fsummary>Initialize for a connection.</fsummary>
<desc>
<p>Initializes the <c>ec</c> structure, to
@@ -236,6 +517,21 @@ fd = ei_xconnect(&ec, &addr, ALIVE);
<item>
<p><c>thispaddr</c> if the IP address of the host.</p>
</item>
+ <item>
+ <p><c>cbs</c> is a pointer to a
+ <seealso marker="#ei_socket_callbacks">callback structure</seealso>
+ implementing and alternative socket interface.</p>
+ </item>
+ <item>
+ <p><c>cbs_sz</c> is the size of the structure
+ pointed to by <c>cbs</c>.</p>
+ </item>
+ <item>
+ <p><c>setup_context</c> is a pointer to a structure that
+ will be passed as second argument to the <c>socket</c> callback
+ in the <c>cbs</c> structure.</p>
+ </item>
+
</list>
<p>A C-node acting as a server is assigned a creation
number when it calls <c>ei_publish()</c>.</p>
@@ -273,8 +569,8 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
</func>
<func>
- <name><ret>int</ret><nametext>ei_connect_tmo(ei_cnode* ec, char *nodename, unsigned timeout_ms)</nametext></name>
- <name><ret>int</ret><nametext>ei_xconnect_tmo(ei_cnode* ec, Erl_IpAddr adr, char *alivename, unsigned timeout_ms)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_connect_tmo(ei_cnode* ec, char *nodename, unsigned timeout_ms)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_xconnect_tmo(ei_cnode* ec, Erl_IpAddr adr, char *alivename, unsigned timeout_ms)</nametext></name>
<fsummary>Establish a connection to an Erlang node with optional
time-out.</fsummary>
<desc>
@@ -286,8 +582,8 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
</func>
<func>
- <name><ret>int</ret><nametext>ei_get_tracelevel(void)</nametext></name>
- <name><ret>void</ret><nametext>ei_set_tracelevel(int level)</nametext></name>
+ <name since="OTP R13B04"><ret>int</ret><nametext>ei_get_tracelevel(void)</nametext></name>
+ <name since="OTP R13B04"><ret>void</ret><nametext>ei_set_tracelevel(int level)</nametext></name>
<fsummary>Get and set functions for tracing.</fsummary>
<desc>
<p>Used to set tracing on the distribution. The levels are different
@@ -299,7 +595,46 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
</func>
<func>
- <name><ret>int</ret><nametext>ei_publish(ei_cnode *ec, int port)</nametext></name>
+ <name since="OTP @OTP-15442@"><ret>int</ret><nametext>ei_listen(ei_cnode *ec, int *port, int backlog)</nametext></name>
+ <name since="OTP @OTP-15442@"><ret>int</ret><nametext>ei_xlisten(ei_cnode *ec, Erl_IpAddr adr, int *port, int backlog)</nametext></name>
+ <fsummary>Create a listen socket.</fsummary>
+ <desc>
+ <p>Used by a server process to setup a listen socket which
+ later can be used for accepting connections from client processes.
+ </p>
+ <list type="bulleted">
+ <item>
+ <p><c>ec</c> is the C-node structure.</p>
+ </item>
+ <item>
+ <p><c>adr</c> is local interface to bind to.</p>
+ </item>
+ <item>
+ <p><c>port</c> is a pointer to an integer containing the
+ port number to bind to. If <c>*port</c> equals <c>0</c>
+ when calling <c>ei_listen()</c>, the socket will be bound to
+ an ephemeral port. On success, <c>ei_listen()</c> will update
+ the value of <c>*port</c> to the port actually bound to.
+ </p>
+ </item>
+ <item>
+ <p><c>backlog</c> is maximum backlog of pending connections.</p>
+ </item>
+ </list>
+ <p><c>ei_listen</c> will create a socket, bind to a port on the
+ local interface identified by <c>adr</c> (or all local interfaces if
+ <c>ei_listen()</c> is called), and mark the socket as a passive socket
+ (that is, a socket that will be used for accepting incoming connections).
+ </p>
+ <p>
+ On success, a file descriptor is returned which can be used in a call to
+ <c>ei_accept()</c>. On failure, <c>ERL_ERROR</c> is returned and
+ <c>erl_errno</c> is set to <c>EIO</c>.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since=""><ret>int</ret><nametext>ei_publish(ei_cnode *ec, int port)</nametext></name>
<fsummary>Publish a node name.</fsummary>
<desc>
<p>Used by a server process to register
@@ -336,7 +671,7 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
</func>
<func>
- <name><ret>int</ret><nametext>ei_publish_tmo(ei_cnode *ec, int port, unsigned timeout_ms)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_publish_tmo(ei_cnode *ec, int port, unsigned timeout_ms)</nametext></name>
<fsummary>Publish a node name with optional time-out.</fsummary>
<desc>
<p>Equivalent to
@@ -346,7 +681,7 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
</func>
<func>
- <name><ret>int</ret><nametext>ei_receive(int fd, unsigned char* bufp, int bufsize)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_receive(int fd, unsigned char* bufp, int bufsize)</nametext></name>
<fsummary>Receive a message.</fsummary>
<desc>
<p>Receives a message consisting of a sequence
@@ -387,7 +722,7 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
</func>
<func>
- <name><ret>int</ret><nametext>ei_receive_encoded(int fd, char **mbufp, int *bufsz, erlang_msg *msg, int *msglen)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_receive_encoded(int fd, char **mbufp, int *bufsz, erlang_msg *msg, int *msglen)</nametext></name>
<fsummary>Obsolete function for receiving a message.</fsummary>
<desc>
<p>This function is retained for compatibility with code
@@ -417,7 +752,7 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
</func>
<func>
- <name><ret>int</ret><nametext>ei_receive_encoded_tmo(int fd, char **mbufp, int *bufsz, erlang_msg *msg, int *msglen, unsigned timeout_ms)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_receive_encoded_tmo(int fd, char **mbufp, int *bufsz, erlang_msg *msg, int *msglen, unsigned timeout_ms)</nametext></name>
<fsummary>Obsolete function for receiving a message with time-out.
</fsummary>
<desc>
@@ -428,8 +763,8 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
</func>
<func>
- <name><ret>int</ret><nametext>ei_receive_msg(int fd, erlang_msg* msg, ei_x_buff* x)</nametext></name>
- <name><ret>int</ret><nametext>ei_xreceive_msg(int fd, erlang_msg* msg, ei_x_buff* x)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_receive_msg(int fd, erlang_msg* msg, ei_x_buff* x)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_xreceive_msg(int fd, erlang_msg* msg, ei_x_buff* x)</nametext></name>
<fsummary>Receive a message.</fsummary>
<desc>
<p>Receives a message to the buffer in <c>x</c>.
@@ -493,8 +828,8 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret><nametext>ei_receive_msg_tmo(int fd, erlang_msg* msg, ei_x_buff* x, unsigned imeout_ms)</nametext></name>
- <name><ret>int</ret><nametext>ei_xreceive_msg_tmo(int fd, erlang_msg* msg, ei_x_buff* x, unsigned timeout_ms)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_receive_msg_tmo(int fd, erlang_msg* msg, ei_x_buff* x, unsigned imeout_ms)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_xreceive_msg_tmo(int fd, erlang_msg* msg, ei_x_buff* x, unsigned timeout_ms)</nametext></name>
<fsummary>Receive a message with optional time-out.</fsummary>
<desc>
<p>Equivalent to <c>ei_receive_msg</c> and <c>ei_xreceive_msg</c>
@@ -504,7 +839,7 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret><nametext>ei_receive_tmo(int fd, unsigned char* bufp, int bufsize, unsigned timeout_ms)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_receive_tmo(int fd, unsigned char* bufp, int bufsize, unsigned timeout_ms)</nametext></name>
<fsummary>Receive a message with optional time-out.</fsummary>
<desc>
<p>Equivalent to
@@ -514,7 +849,7 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_send(ei_cnode* ec, int fd, char* server_name, char* buf, int len)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_send(ei_cnode* ec, int fd, char* server_name, char* buf, int len)</nametext></name>
<fsummary>Send a message to a registered name.</fsummary>
<desc>
<p>Sends an Erlang term to a registered process.</p>
@@ -546,7 +881,7 @@ if (ei_reg_send(&ec, fd, x.buff, x.index) < 0)
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_send_tmo(ei_cnode* ec, int fd, char* server_name, char* buf, int len, unsigned timeout_ms)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_send_tmo(ei_cnode* ec, int fd, char* server_name, char* buf, int len, unsigned timeout_ms)</nametext></name>
<fsummary>Send a message to a registered name with optional time-out
</fsummary>
<desc>
@@ -557,9 +892,9 @@ if (ei_reg_send(&ec, fd, x.buff, x.index) < 0)
</func>
<func>
- <name><ret>int</ret><nametext>ei_rpc(ei_cnode *ec, int fd, char *mod, char *fun, const char *argbuf, int argbuflen, ei_x_buff *x)</nametext></name>
- <name><ret>int</ret><nametext>ei_rpc_to(ei_cnode *ec, int fd, char *mod, char *fun, const char *argbuf, int argbuflen)</nametext></name>
- <name><ret>int</ret><nametext>ei_rpc_from(ei_cnode *ec, int fd, int timeout, erlang_msg *msg, ei_x_buff *x)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_rpc(ei_cnode *ec, int fd, char *mod, char *fun, const char *argbuf, int argbuflen, ei_x_buff *x)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_rpc_to(ei_cnode *ec, int fd, char *mod, char *fun, const char *argbuf, int argbuflen)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_rpc_from(ei_cnode *ec, int fd, int timeout, erlang_msg *msg, ei_x_buff *x)</nametext></name>
<fsummary>Remote Procedure Call from C to Erlang.</fsummary>
<desc>
<p>Supports calling Erlang functions on remote nodes.
@@ -658,7 +993,7 @@ if (ei_decode_version(result.buff, &index) < 0
</func>
<func>
- <name><ret>erlang_pid *</ret><nametext>ei_self(ei_cnode *ec)</nametext></name>
+ <name since=""><ret>erlang_pid *</ret><nametext>ei_self(ei_cnode *ec)</nametext></name>
<fsummary>Retrieve the pid of the C-node.</fsummary>
<desc>
<p>Retrieves the pid of the C-node. Every C-node
@@ -671,7 +1006,7 @@ if (ei_decode_version(result.buff, &index) < 0
</func>
<func>
- <name><ret>int</ret><nametext>ei_send(int fd, erlang_pid* to, char* buf, int len)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_send(int fd, erlang_pid* to, char* buf, int len)</nametext></name>
<fsummary>Send a message.</fsummary>
<desc>
<p>Sends an Erlang term to a process.</p>
@@ -692,7 +1027,7 @@ if (ei_decode_version(result.buff, &index) < 0
</func>
<func>
- <name><ret>int</ret><nametext>ei_send_encoded(int fd, erlang_pid* to, char* buf, int len)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_send_encoded(int fd, erlang_pid* to, char* buf, int len)</nametext></name>
<fsummary>Obsolete function to send a message.</fsummary>
<desc>
<p>Works exactly as <c>ei_send</c>, the alternative name is retained for
@@ -702,7 +1037,7 @@ if (ei_decode_version(result.buff, &index) < 0
</func>
<func>
- <name><ret>int</ret><nametext>ei_send_encoded_tmo(int fd, erlang_pid* to, char* buf, int len, unsigned timeout_ms)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_send_encoded_tmo(int fd, erlang_pid* to, char* buf, int len, unsigned timeout_ms)</nametext></name>
<fsummary>Obsolete function to send a message with optional time-out.
</fsummary>
<desc>
@@ -713,7 +1048,7 @@ if (ei_decode_version(result.buff, &index) < 0
</func>
<func>
- <name><ret>int</ret><nametext>ei_send_reg_encoded(int fd, const erlang_pid *from, const char *to, const char *buf, int len)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_send_reg_encoded(int fd, const erlang_pid *from, const char *to, const char *buf, int len)</nametext></name>
<fsummary>Obsolete function to send a message to a registered name.
</fsummary>
<desc>
@@ -741,7 +1076,7 @@ self->num = fd;
</func>
<func>
- <name><ret>int</ret><nametext>ei_send_reg_encoded_tmo(int fd, const erlang_pid *from, const char *to, const char *buf, int len)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_send_reg_encoded_tmo(int fd, const erlang_pid *from, const char *to, const char *buf, int len)</nametext></name>
<fsummary>Obsolete function to send a message to a registered name with
time-out.</fsummary>
<desc>
@@ -752,7 +1087,7 @@ self->num = fd;
</func>
<func>
- <name><ret>int</ret><nametext>ei_send_tmo(int fd, erlang_pid* to, char* buf, int len, unsigned timeout_ms)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_send_tmo(int fd, erlang_pid* to, char* buf, int len, unsigned timeout_ms)</nametext></name>
<fsummary>Send a message with optional time-out.</fsummary>
<desc>
<p>Equivalent to
@@ -762,9 +1097,9 @@ self->num = fd;
</func>
<func>
- <name><ret>const char *</ret><nametext>ei_thisnodename(ei_cnode *ec)</nametext></name>
- <name><ret>const char *</ret><nametext>ei_thishostname(ei_cnode *ec)</nametext></name>
- <name><ret>const char *</ret><nametext>ei_thisalivename(ei_cnode *ec)</nametext></name>
+ <name since=""><ret>const char *</ret><nametext>ei_thisnodename(ei_cnode *ec)</nametext></name>
+ <name since=""><ret>const char *</ret><nametext>ei_thishostname(ei_cnode *ec)</nametext></name>
+ <name since=""><ret>const char *</ret><nametext>ei_thisalivename(ei_cnode *ec)</nametext></name>
<fsummary>Retrieve some values.</fsummary>
<desc>
<p>Can be used to retrieve information about
@@ -779,7 +1114,7 @@ self->num = fd;
</func>
<func>
- <name><ret>int</ret><nametext>ei_unpublish(ei_cnode *ec)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_unpublish(ei_cnode *ec)</nametext></name>
<fsummary>Forcefully unpublish a node name.</fsummary>
<desc>
<p>Can be called by a process to unregister a
@@ -802,7 +1137,7 @@ self->num = fd;
</func>
<func>
- <name><ret>int</ret><nametext>ei_unpublish_tmo(ei_cnode *ec, unsigned timeout_ms)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_unpublish_tmo(ei_cnode *ec, unsigned timeout_ms)</nametext></name>
<fsummary>Unpublish a node name with optional time-out.</fsummary>
<desc>
<p>Equivalent to
diff --git a/lib/erl_interface/doc/src/ei_users_guide.xml b/lib/erl_interface/doc/src/ei_users_guide.xml
index 0eed50b50b..7ca10d1a99 100644
--- a/lib/erl_interface/doc/src/ei_users_guide.xml
+++ b/lib/erl_interface/doc/src/ei_users_guide.xml
@@ -34,6 +34,18 @@
</header>
<section>
+ <title>Deprecation and Removal</title>
+ <note><p>The support for VxWorks is deprecated as of OTP 22, and
+ will be removed in OTP 23.</p></note>
+ <note><p>The old legacy <c>erl_interface</c> library (functions
+ with prefix <c>erl_</c>) is deprecated as of OTP 22, and will be
+ removed in OTP 23. This does not apply to the <c>ei</c>
+ library. Reasonably new <c>gcc</c> compilers will issue deprecation
+ warnings. In order to disable these warnings, define the macro
+ <c>EI_NO_DEPR_WARN</c>.</p></note>
+ </section>
+
+ <section>
<title>Introduction</title>
<p>The <c>Erl_Interface</c> library contains functions that help you
integrate programs written in C and Erlang. The functions in
@@ -162,12 +174,20 @@ $ ld -L/usr/local/otp/lib/erl_interface-3.2.3/
</section>
<section>
- <title>Initializing the Erl_Interface Library</title>
- <p>Before calling any of the other <c>Erl_Interface</c> functions, call
- <c>erl_init()</c> exactly once to initialize the library.
+ <title>Initializing the Libraries</title>
+ <p>
+ Before calling any of the other functions in the <c>erl_interface</c>
+ and <c>ei</c> libraries, call <c>erl_init()</c> exactly once to initialize
+ both libraries.
<c>erl_init()</c> takes two arguments. However, the arguments
- are no longer used by <c>Erl_Interface</c> and are therefore to be
- specified as <c>erl_init(NULL,0)</c>.</p>
+ are no longer used by <c>erl_interface</c> and are therefore to be
+ specified as <c>erl_init(NULL,0)</c>.
+ </p>
+ <p>
+ If you only use the <c>ei</c> library, instead initialize it by calling
+ <c>ei_init()</c> exactly once before calling any other functions in
+ the <c>ei</c> library.
+ </p>
</section>
<section>
diff --git a/lib/erl_interface/doc/src/erl_connect.xml b/lib/erl_interface/doc/src/erl_connect.xml
index 76ef6588c2..9492a82864 100644
--- a/lib/erl_interface/doc/src/erl_connect.xml
+++ b/lib/erl_interface/doc/src/erl_connect.xml
@@ -35,6 +35,15 @@
<lib>erl_connect</lib>
<libsummary>Communicate with distributed Erlang.</libsummary>
<description>
+ <note><p>The support for VxWorks is deprecated as of OTP 22, and
+ will be removed in OTP 23.</p></note>
+ <note><p>The old legacy <c>erl_interface</c> library (functions
+ with prefix <c>erl_</c>) is deprecated as of OTP 22, and will be
+ removed in OTP 23. This does not apply to the <c>ei</c>
+ library. Reasonably new <c>gcc</c> compilers will issue deprecation
+ warnings. In order to disable these warnings, define the macro
+ <c>EI_NO_DEPR_WARN</c>.</p></note>
+
<p>This module provides support for communication between distributed
Erlang nodes and C-nodes, in a manner that is transparent to Erlang
processes.</p>
@@ -49,7 +58,7 @@
<funcs>
<func>
- <name><ret>int</ret><nametext>erl_accept(listensock, conp)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_accept(listensock, conp)</nametext></name>
<fsummary>Accept a connection.</fsummary>
<type>
<v>int listensock;</v>
@@ -78,7 +87,7 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret><nametext>erl_close_connection(fd)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_close_connection(fd)</nametext></name>
<fsummary>Close a connection to an Erlang node.</fsummary>
<type>
<v>int fd;</v>
@@ -95,8 +104,8 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret><nametext>erl_connect(node)</nametext></name>
- <name><ret>int</ret><nametext>erl_xconnect(addr, alive)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_connect(node)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_xconnect(addr, alive)</nametext></name>
<fsummary>Establish a connection to an Erlang node.</fsummary>
<type>
<v>char *node, *alive;</v>
@@ -149,8 +158,8 @@ erl_xconnect( &addr , ALIVE );
</func>
<func>
- <name><ret>int</ret><nametext>erl_connect_init(number, cookie, creation)</nametext></name>
- <name><ret>int</ret><nametext>erl_connect_xinit(host, alive, node, addr, cookie, creation)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_connect_init(number, cookie, creation)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_connect_xinit(host, alive, node, addr, cookie, creation)</nametext></name>
<fsummary>Initialize communication.</fsummary>
<type>
<v>int number;</v>
@@ -246,7 +255,7 @@ if (!erl_connect_init(17, "samplecookiestring...", 0))
</func>
<func>
- <name><ret>int</ret><nametext>erl_publish(port)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_publish(port)</nametext></name>
<fsummary>Publish a node name.</fsummary>
<type>
<v>int port;</v>
@@ -277,7 +286,7 @@ if (!erl_connect_init(17, "samplecookiestring...", 0))
</func>
<func>
- <name><ret>int</ret><nametext>erl_receive(fd, bufp, bufsize)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_receive(fd, bufp, bufsize)</nametext></name>
<fsummary>Receive a message.</fsummary>
<type>
<v>int fd;</v>
@@ -316,7 +325,7 @@ if (!erl_connect_init(17, "samplecookiestring...", 0))
</func>
<func>
- <name><ret>int</ret><nametext>erl_receive_msg(fd, bufp, bufsize, emsg)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_receive_msg(fd, bufp, bufsize, emsg)</nametext></name>
<fsummary>Receive and decode a message.</fsummary>
<type>
<v>int fd;</v>
@@ -411,7 +420,7 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret><nametext>erl_reg_send(fd, to, msg)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_reg_send(fd, to, msg)</nametext></name>
<fsummary>Send a message to a registered name.</fsummary>
<type>
<v>int fd;</v>
@@ -439,9 +448,9 @@ typedef struct {
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_rpc(fd, mod, fun, args)</nametext></name>
- <name><ret>int</ret><nametext>erl_rpc_from(fd, timeout, emsg)</nametext></name>
- <name><ret>int</ret><nametext>erl_rpc_to(fd, mod, fun, args)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_rpc(fd, mod, fun, args)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_rpc_from(fd, timeout, emsg)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_rpc_to(fd, mod, fun, args)</nametext></name>
<fsummary>Remote Procedure Call.</fsummary>
<type>
<v>int fd, timeout;</v>
@@ -511,7 +520,7 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret><nametext>erl_send(fd, to, msg)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_send(fd, to, msg)</nametext></name>
<fsummary>Send a message.</fsummary>
<type>
<v>int fd;</v>
@@ -541,11 +550,11 @@ typedef struct {
</func>
<func>
- <name><ret>const char *</ret><nametext>erl_thisalivename()</nametext></name>
- <name><ret>const char *</ret><nametext>erl_thiscookie()</nametext></name>
- <name><ret>short</ret><nametext>erl_thiscreation()</nametext></name>
- <name><ret>const char *</ret><nametext>erl_thishostname()</nametext></name>
- <name><ret>const char *</ret><nametext>erl_thisnodename()</nametext></name>
+ <name since=""><ret>const char *</ret><nametext>erl_thisalivename()</nametext></name>
+ <name since=""><ret>const char *</ret><nametext>erl_thiscookie()</nametext></name>
+ <name since=""><ret>short</ret><nametext>erl_thiscreation()</nametext></name>
+ <name since=""><ret>const char *</ret><nametext>erl_thishostname()</nametext></name>
+ <name since=""><ret>const char *</ret><nametext>erl_thisnodename()</nametext></name>
<fsummary>Retrieve some values.</fsummary>
<desc>
<p>Retrieves information about
@@ -556,7 +565,7 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret><nametext>erl_unpublish(alive)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_unpublish(alive)</nametext></name>
<fsummary>Forcefully unpublish a node name.</fsummary>
<type>
<v>char *alive;</v>
@@ -583,7 +592,7 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret><nametext>erl_xreceive_msg(fd, bufpp, bufsizep, emsg)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_xreceive_msg(fd, bufpp, bufsizep, emsg)</nametext></name>
<fsummary>Receive and decode a message.</fsummary>
<type>
<v>int fd;</v>
@@ -616,10 +625,10 @@ typedef struct {
</func>
<func>
- <name><ret>struct hostent</ret><nametext>*erl_gethostbyaddr(addr, length, type)</nametext></name>
- <name><ret>struct hostent</ret><nametext>*erl_gethostbyaddr_r(addr, length, type, hostp, buffer, buflen, h_errnop)</nametext></name>
- <name><ret>struct hostent</ret><nametext>*erl_gethostbyname(name)</nametext></name>
- <name><ret>struct hostent</ret><nametext>*erl_gethostbyname_r(name, hostp, buffer, buflen, h_errnop)</nametext></name>
+ <name since=""><ret>struct hostent *</ret><nametext>erl_gethostbyaddr(addr, length, type)</nametext></name>
+ <name since=""><ret>struct hostent *</ret><nametext>erl_gethostbyaddr_r(addr, length, type, hostp, buffer, buflen, h_errnop)</nametext></name>
+ <name since=""><ret>struct hostent *</ret><nametext>erl_gethostbyname(name)</nametext></name>
+ <name since=""><ret>struct hostent *</ret><nametext>erl_gethostbyname_r(name, hostp, buffer, buflen, h_errnop)</nametext></name>
<fsummary>Name lookup functions.</fsummary>
<type>
diff --git a/lib/erl_interface/doc/src/erl_error.xml b/lib/erl_interface/doc/src/erl_error.xml
index 8139c9b343..6fac94e442 100644
--- a/lib/erl_interface/doc/src/erl_error.xml
+++ b/lib/erl_interface/doc/src/erl_error.xml
@@ -47,7 +47,7 @@
<funcs>
<func>
- <name><ret>void</ret><nametext>erl_err_msg(FormatStr, ... )</nametext></name>
+ <name since=""><ret>void</ret><nametext>erl_err_msg(FormatStr, ... )</nametext></name>
<fsummary>Non-fatal error, and not system call error.</fsummary>
<type>
<v>const char *FormatStr;</v>
@@ -59,7 +59,7 @@
</func>
<func>
- <name><ret>void</ret><nametext>erl_err_quit(FormatStr, ... )</nametext></name>
+ <name since=""><ret>void</ret><nametext>erl_err_quit(FormatStr, ... )</nametext></name>
<fsummary>Fatal error, but not system call error.</fsummary>
<type>
<v>const char *FormatStr;</v>
@@ -73,7 +73,7 @@
</func>
<func>
- <name><ret>void</ret><nametext>erl_err_ret(FormatStr, ... )</nametext></name>
+ <name since=""><ret>void</ret><nametext>erl_err_ret(FormatStr, ... )</nametext></name>
<fsummary>Non-fatal system call error.</fsummary>
<type>
<v>const char *FormatStr;</v>
@@ -86,7 +86,7 @@
</func>
<func>
- <name><ret>void</ret><nametext>erl_err_sys(FormatStr, ... )</nametext></name>
+ <name since=""><ret>void</ret><nametext>erl_err_sys(FormatStr, ... )</nametext></name>
<fsummary>Fatal system call error.</fsummary>
<type>
<v>const char *FormatStr;</v>
@@ -113,7 +113,7 @@
<funcs>
<func>
- <name><ret>volatile int</ret><nametext>erl_errno</nametext></name>
+ <name since=""><ret>volatile int</ret><nametext>erl_errno</nametext></name>
<fsummary>Variable <c>erl_errno</c> contains the
Erl_Interface error number. You can change the value if you wish.
</fsummary>
diff --git a/lib/erl_interface/doc/src/erl_eterm.xml b/lib/erl_interface/doc/src/erl_eterm.xml
index 9a05196a70..295760b4e6 100644
--- a/lib/erl_interface/doc/src/erl_eterm.xml
+++ b/lib/erl_interface/doc/src/erl_eterm.xml
@@ -35,6 +35,15 @@
<lib>erl_eterm</lib>
<libsummary>Functions for Erlang term construction.</libsummary>
<description>
+ <note><p>The support for VxWorks is deprecated as of OTP 22, and
+ will be removed in OTP 23.</p></note>
+ <note><p>The old legacy <c>erl_interface</c> library (functions
+ with prefix <c>erl_</c>) is deprecated as of OTP 22, and will be
+ removed in OTP 23. This does not apply to the <c>ei</c>
+ library. Reasonably new <c>gcc</c> compilers will issue deprecation
+ warnings. In order to disable these warnings, define the macro
+ <c>EI_NO_DEPR_WARN</c>.</p></note>
+
<p>This module provides functions for creating and manipulating
Erlang terms.</p>
@@ -142,7 +151,7 @@
<funcs>
<func>
- <name><ret>ETERM *</ret><nametext>erl_cons(head, tail)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_cons(head, tail)</nametext></name>
<fsummary>Prepend a term to the head of a list.</fsummary>
<type>
<v>ETERM *head;</v>
@@ -181,7 +190,7 @@ erl_free_compound(list);
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_copy_term(term)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_copy_term(term)</nametext></name>
<fsummary>Create a copy of an Erlang term.</fsummary>
<type>
<v>ETERM *term;</v>
@@ -193,7 +202,7 @@ erl_free_compound(list);
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_element(position, tuple)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_element(position, tuple)</nametext></name>
<fsummary>Extract an element from an Erlang tuple.</fsummary>
<type>
<v>int position;</v>
@@ -215,7 +224,7 @@ erl_free_compound(list);
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_hd(list)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_hd(list)</nametext></name>
<fsummary>Extract the first element from a list.</fsummary>
<type>
<v>ETERM *list;</v>
@@ -230,7 +239,7 @@ erl_free_compound(list);
</func>
<func>
- <name><ret>void</ret><nametext>erl_init(NULL, 0)</nametext></name>
+ <name since=""><ret>void</ret><nametext>erl_init(NULL, 0)</nametext></name>
<fsummary>Initialization routine.</fsummary>
<type>
<v>void *NULL;</v>
@@ -245,7 +254,7 @@ erl_free_compound(list);
</func>
<func>
- <name><ret>int</ret><nametext>erl_iolist_length(list)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_iolist_length(list)</nametext></name>
<fsummary>Return the length of an I/O list.</fsummary>
<type>
<v>ETERM *list;</v>
@@ -262,7 +271,7 @@ erl_free_compound(list);
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_iolist_to_binary(term)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_iolist_to_binary(term)</nametext></name>
<fsummary>Convert an I/O list to a binary.</fsummary>
<type>
<v>ETERM *list;</v>
@@ -289,7 +298,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>char *</ret><nametext>erl_iolist_to_string(list)</nametext></name>
+ <name since=""><ret>char *</ret><nametext>erl_iolist_to_string(list)</nametext></name>
<fsummary>Convert an I/O list to a <c>NULL</c>-terminated string.</fsummary>
<type>
<v>ETERM *list;</v>
@@ -312,7 +321,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>int</ret><nametext>erl_length(list)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_length(list)</nametext></name>
<fsummary>Determine the length of a list.</fsummary>
<type>
<v>ETERM *list;</v>
@@ -328,7 +337,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_atom(string)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_atom(string)</nametext></name>
<fsummary>Create an atom.</fsummary>
<type>
<v>const char *string;</v>
@@ -355,7 +364,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_binary(bptr, size)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_binary(bptr, size)</nametext></name>
<fsummary>Create a binary object.</fsummary>
<type>
<v>char *bptr;</v>
@@ -378,7 +387,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_empty_list()</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_empty_list()</nametext></name>
<fsummary>Create an empty Erlang list.</fsummary>
<desc>
<p>Creates and returns an empty Erlang list.
@@ -388,7 +397,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_estring(string, len)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_estring(string, len)</nametext></name>
<fsummary>Create an Erlang string.</fsummary>
<type>
<v>char *string;</v>
@@ -408,7 +417,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_float(f)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_float(f)</nametext></name>
<fsummary>Create an Erlang float.</fsummary>
<type>
<v>double f;</v>
@@ -426,7 +435,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_int(n)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_int(n)</nametext></name>
<fsummary>Create an Erlang integer.</fsummary>
<type>
<v>int n;</v>
@@ -443,7 +452,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_list(array, arrsize)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_list(array, arrsize)</nametext></name>
<fsummary>Create a list from an array.</fsummary>
<type>
<v>ETERM **array;</v>
@@ -465,7 +474,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_long_ref(node, n1, n2, n3, creation)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_long_ref(node, n1, n2, n3, creation)</nametext></name>
<fsummary>Create an Erlang reference.</fsummary>
<type>
<v>const char *node;</v>
@@ -495,7 +504,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_pid(node, number, serial, creation)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_pid(node, number, serial, creation)</nametext></name>
<fsummary>Create a process identifier.</fsummary>
<type>
<v>const char *node;</v>
@@ -525,7 +534,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_port(node, number, creation)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_port(node, number, creation)</nametext></name>
<fsummary>Create a port identifier.</fsummary>
<type>
<v>const char *node;</v>
@@ -550,7 +559,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_ref(node, number, creation)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_ref(node, number, creation)</nametext></name>
<fsummary>Create an old Erlang reference.</fsummary>
<type>
<v>const char *node;</v>
@@ -578,7 +587,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_string(string)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_string(string)</nametext></name>
<fsummary>Create a string.</fsummary>
<type>
<v>char *string;</v>
@@ -593,7 +602,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_tuple(array, arrsize)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_tuple(array, arrsize)</nametext></name>
<fsummary>Create an Erlang tuple from an array.</fsummary>
<type>
<v>ETERM **array;</v>
@@ -621,7 +630,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_uint(n)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_uint(n)</nametext></name>
<fsummary>Create an unsigned integer.</fsummary>
<type>
<v>unsigned int n;</v>
@@ -638,7 +647,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_var(name)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_var(name)</nametext></name>
<fsummary>Create an Erlang variable.</fsummary>
<type>
<v>char *name;</v>
@@ -653,7 +662,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>int</ret><nametext>erl_print_term(stream, term)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_print_term(stream, term)</nametext></name>
<fsummary>Print an Erlang term.</fsummary>
<type>
<v>FILE *stream;</v>
@@ -672,7 +681,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>void</ret><nametext>erl_set_compat_rel(release_number)</nametext></name>
+ <name since=""><ret>void</ret><nametext>erl_set_compat_rel(release_number)</nametext></name>
<fsummary>Set the Erl_Interface library in compatibility mode.</fsummary>
<type>
<v>unsigned release_number;</v>
@@ -706,7 +715,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>int</ret><nametext>erl_size(term)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_size(term)</nametext></name>
<fsummary>Return the arity of a tuple or binary.</fsummary>
<type>
<v>ETERM *term;</v>
@@ -723,7 +732,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_tl(list)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_tl(list)</nametext></name>
<fsummary>Extract the tail from a list.</fsummary>
<type>
<v>ETERM *list;</v>
@@ -738,7 +747,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_var_content(term, name)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_var_content(term, name)</nametext></name>
<fsummary>Extract the content of a variable.</fsummary>
<type>
<v>ETERM *term;</v>
diff --git a/lib/erl_interface/doc/src/erl_format.xml b/lib/erl_interface/doc/src/erl_format.xml
index 5b8b7b5e78..b5e895c720 100644
--- a/lib/erl_interface/doc/src/erl_format.xml
+++ b/lib/erl_interface/doc/src/erl_format.xml
@@ -41,7 +41,7 @@
<funcs>
<func>
- <name><ret>ETERM *</ret><nametext>erl_format(FormatStr, ...)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_format(FormatStr, ...)</nametext></name>
<fsummary>Create an Erlang term.</fsummary>
<type>
<v>char *FormatStr;</v>
@@ -81,7 +81,7 @@ erl_format("[{name,~a},{age,~i},{data,~w}]",
</func>
<func>
- <name><ret>int</ret><nametext>erl_match(Pattern, Term)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_match(Pattern, Term)</nametext></name>
<fsummary>Perform pattern matching.</fsummary>
<type>
<v>ETERM *Pattern,*Term;</v>
diff --git a/lib/erl_interface/doc/src/erl_global.xml b/lib/erl_interface/doc/src/erl_global.xml
index 2fa0045adf..39085b46f0 100644
--- a/lib/erl_interface/doc/src/erl_global.xml
+++ b/lib/erl_interface/doc/src/erl_global.xml
@@ -35,6 +35,15 @@
<lib>erl_global</lib>
<libsummary>Access globally registered names.</libsummary>
<description>
+ <note><p>The support for VxWorks is deprecated as of OTP 22, and
+ will be removed in OTP 23.</p></note>
+ <note><p>The old legacy <c>erl_interface</c> library (functions
+ with prefix <c>erl_</c>) is deprecated as of OTP 22, and will be
+ removed in OTP 23. This does not apply to the <c>ei</c>
+ library. Reasonably new <c>gcc</c> compilers will issue deprecation
+ warnings. In order to disable these warnings, define the macro
+ <c>EI_NO_DEPR_WARN</c>.</p></note>
+
<p>This module provides support for registering, looking
up, and unregistering names in the <c>global</c> module.
For more information, see
@@ -48,7 +57,7 @@
<funcs>
<func>
- <name><ret>char **</ret><nametext>erl_global_names(fd,count)</nametext></name>
+ <name since=""><ret>char **</ret><nametext>erl_global_names(fd,count)</nametext></name>
<fsummary>Obtain list of global names.</fsummary>
<type>
<v>int fd;</v>
@@ -79,7 +88,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>erl_global_register(fd,name,pid)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_global_register(fd,name,pid)</nametext></name>
<fsummary>Register a name in global.</fsummary>
<type>
<v>int fd;</v>
@@ -103,7 +112,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>erl_global_unregister(fd,name)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_global_unregister(fd,name)</nametext></name>
<fsummary>Unregister a name from global.</fsummary>
<type>
<v>int fd;</v>
@@ -122,7 +131,7 @@
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_global_whereis(fd,name,node)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_global_whereis(fd,name,node)</nametext></name>
<fsummary>Look up a name in global.</fsummary>
<type>
<v>int fd;</v>
diff --git a/lib/erl_interface/doc/src/erl_interface.xml b/lib/erl_interface/doc/src/erl_interface.xml
index 4e66655b39..decd66046a 100644
--- a/lib/erl_interface/doc/src/erl_interface.xml
+++ b/lib/erl_interface/doc/src/erl_interface.xml
@@ -58,6 +58,18 @@
</list>
<section>
+ <title>Deprecation and Removal</title>
+ <note><p>The support for VxWorks is deprecated as of OTP 22, and
+ will be removed in OTP 23.</p></note>
+ <note><p>The old legacy <c>erl_interface</c> library (functions
+ with prefix <c>erl_</c>) is deprecated as of OTP 22, and will be
+ removed in OTP 23. This does not apply to the <c>ei</c>
+ library. Reasonably new <c>gcc</c> compilers will issue deprecation
+ warnings. In order to disable these warnings, define the macro
+ <c>EI_NO_DEPR_WARN</c>.</p></note>
+ </section>
+
+ <section>
<title>Compiling and Linking Your Code</title>
<p>In order to use any of the Erl_Interface functions, include the
following lines in your code:</p>
diff --git a/lib/erl_interface/doc/src/erl_malloc.xml b/lib/erl_interface/doc/src/erl_malloc.xml
index c0eebc29e9..6650620064 100644
--- a/lib/erl_interface/doc/src/erl_malloc.xml
+++ b/lib/erl_interface/doc/src/erl_malloc.xml
@@ -35,13 +35,21 @@
<lib>erl_malloc</lib>
<libsummary>Memory allocation functions.</libsummary>
<description>
+ <note><p>The support for VxWorks is deprecated as of OTP 22, and
+ will be removed in OTP 23.</p></note>
+ <note><p>The old legacy <c>erl_interface</c> library (functions
+ with prefix <c>erl_</c>) is deprecated as of OTP 22, and will be
+ removed in OTP 23. This does not apply to the <c>ei</c>
+ library. Reasonably new <c>gcc</c> compilers will issue deprecation
+ warnings. In order to disable these warnings, define the macro
+ <c>EI_NO_DEPR_WARN</c>.</p></note>
<p>This module provides functions for allocating and deallocating
memory.</p>
</description>
<funcs>
<func>
- <name><ret>ETERM *</ret><nametext>erl_alloc_eterm(etype)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_alloc_eterm(etype)</nametext></name>
<fsummary>Allocate an ETERM structure.</fsummary>
<type>
<v>unsigned char etype;</v>
@@ -89,7 +97,7 @@
</func>
<func>
- <name><ret>void</ret><nametext>erl_eterm_release(void)</nametext></name>
+ <name since=""><ret>void</ret><nametext>erl_eterm_release(void)</nametext></name>
<fsummary>Clear the ETERM freelist.</fsummary>
<desc>
<p>Clears the freelist, where blocks are placed when they are
@@ -99,7 +107,7 @@
</func>
<func>
- <name><ret>void</ret><nametext>erl_eterm_statistics(allocated, freed)</nametext></name>
+ <name since=""><ret>void</ret><nametext>erl_eterm_statistics(allocated, freed)</nametext></name>
<fsummary>Report term allocation statistics.</fsummary>
<type>
<v>long *allocated;</v>
@@ -127,7 +135,7 @@
</func>
<func>
- <name><ret>void</ret><nametext>erl_free(ptr)</nametext></name>
+ <name since=""><ret>void</ret><nametext>erl_free(ptr)</nametext></name>
<fsummary>Free some memory.</fsummary>
<type>
<v>void *ptr;</v>
@@ -139,7 +147,7 @@
</func>
<func>
- <name><ret>void</ret><nametext>erl_free_array(array, size)</nametext></name>
+ <name since=""><ret>void</ret><nametext>erl_free_array(array, size)</nametext></name>
<fsummary>Free an array of ETERM structures.</fsummary>
<type>
<v>ETERM **array;</v>
@@ -156,7 +164,7 @@
</func>
<func>
- <name><ret>void</ret><nametext>erl_free_compound(t)</nametext></name>
+ <name since=""><ret>void</ret><nametext>erl_free_compound(t)</nametext></name>
<fsummary>Free an array of ETERM structures.</fsummary>
<type>
<v>ETERM *t;</v>
@@ -179,7 +187,7 @@
</func>
<func>
- <name><ret>void</ret><nametext>erl_free_term(t)</nametext></name>
+ <name since=""><ret>void</ret><nametext>erl_free_term(t)</nametext></name>
<fsummary>Free an ETERM structure.</fsummary>
<type>
<v>ETERM *t;</v>
@@ -190,7 +198,7 @@
</func>
<func>
- <name><ret>void</ret><nametext>erl_malloc(size)</nametext></name>
+ <name since=""><ret>void</ret><nametext>erl_malloc(size)</nametext></name>
<fsummary>Allocate some memory.</fsummary>
<type>
<v>long size;</v>
diff --git a/lib/erl_interface/doc/src/erl_marshal.xml b/lib/erl_interface/doc/src/erl_marshal.xml
index 2ad658f78b..33d359d871 100644
--- a/lib/erl_interface/doc/src/erl_marshal.xml
+++ b/lib/erl_interface/doc/src/erl_marshal.xml
@@ -35,6 +35,14 @@
<lib>erl_marshal</lib>
<libsummary>Encoding and decoding of Erlang terms.</libsummary>
<description>
+ <note><p>The support for VxWorks is deprecated as of OTP 22, and
+ will be removed in OTP 23.</p></note>
+ <note><p>The old legacy <c>erl_interface</c> library (functions
+ with prefix <c>erl_</c>) is deprecated as of OTP 22, and will be
+ removed in OTP 23. This does not apply to the <c>ei</c>
+ library. Reasonably new <c>gcc</c> compilers will issue deprecation
+ warnings. In order to disable these warnings, define the macro
+ <c>EI_NO_DEPR_WARN</c>.</p></note>
<p>This module contains functions for encoding Erlang terms into
a sequence of bytes, and for decoding Erlang terms from a
sequence of bytes.</p>
@@ -42,7 +50,7 @@
<funcs>
<func>
- <name><ret>int</ret><nametext>erl_compare_ext(bufp1, bufp2)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_compare_ext(bufp1, bufp2)</nametext></name>
<fsummary>Compare encoded byte sequences.</fsummary>
<type>
<v>unsigned char *bufp1,*bufp2;</v>
@@ -62,8 +70,8 @@
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_decode(bufp)</nametext></name>
- <name><ret>ETERM *</ret><nametext>erl_decode_buf(bufpp)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_decode(bufp)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_decode_buf(bufpp)</nametext></name>
<fsummary>Convert a term from Erlang external format.</fsummary>
<type>
<v>unsigned char *bufp;</v>
@@ -102,8 +110,8 @@
</func>
<func>
- <name><ret>int</ret><nametext>erl_encode(term, bufp)</nametext></name>
- <name><ret>int</ret><nametext>erl_encode_buf(term, bufpp)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_encode(term, bufp)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_encode_buf(term, bufpp)</nametext></name>
<fsummary>Convert a term into Erlang external format.</fsummary>
<type>
<v>ETERM *term;</v>
@@ -179,7 +187,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>erl_ext_size(bufp)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_ext_size(bufp)</nametext></name>
<fsummary>Count elements in encoded term.</fsummary>
<type>
<v>unsigned char *bufp;</v>
@@ -190,7 +198,7 @@
</func>
<func>
- <name><ret>unsigned char</ret><nametext>erl_ext_type(bufp)</nametext></name>
+ <name since=""><ret>unsigned char</ret><nametext>erl_ext_type(bufp)</nametext></name>
<fsummary>Determine type of an encoded byte sequence.</fsummary>
<type>
<v>unsigned char *bufp;</v>
@@ -228,7 +236,7 @@
</func>
<func>
- <name><ret>unsigned char *</ret><nametext>erl_peek_ext(bufp, pos)</nametext></name>
+ <name since=""><ret>unsigned char *</ret><nametext>erl_peek_ext(bufp, pos)</nametext></name>
<fsummary>Step over encoded term.</fsummary>
<type>
<v>unsigned char *bufp;</v>
@@ -252,7 +260,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>erl_term_len(t)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_term_len(t)</nametext></name>
<fsummary>Determine encoded size of term.</fsummary>
<type>
<v>ETERM *t;</v>
diff --git a/lib/erl_interface/doc/src/ref_man.xml b/lib/erl_interface/doc/src/ref_man.xml
index 1e20637cb7..a4f947c79f 100644
--- a/lib/erl_interface/doc/src/ref_man.xml
+++ b/lib/erl_interface/doc/src/ref_man.xml
@@ -29,6 +29,14 @@
<file>ref_man.xml</file>
</header>
<description>
+ <note><p>The support for VxWorks is deprecated as of OTP 22, and
+ will be removed in OTP 23.</p></note>
+ <note><p>The old legacy <c>erl_interface</c> library (functions
+ with prefix <c>erl_</c>) is deprecated as of OTP 22, and will be
+ removed in OTP 23. This does not apply to the <c>ei</c>
+ library. Reasonably new <c>gcc</c> compilers will issue deprecation
+ warnings. In order to disable these warnings, define the macro
+ <c>EI_NO_DEPR_WARN</c>.</p></note>
</description>
<xi:include href="ei.xml"/>
<xi:include href="ei_connect.xml"/>
diff --git a/lib/erl_interface/doc/src/ref_man_ei.xml b/lib/erl_interface/doc/src/ref_man_ei.xml
index 92ff9ed328..d8d1deaea1 100644
--- a/lib/erl_interface/doc/src/ref_man_ei.xml
+++ b/lib/erl_interface/doc/src/ref_man_ei.xml
@@ -30,8 +30,14 @@
<file>ref_man_ei.xml</file>
</header>
<description>
- <p>The <c>ei</c> library is a <c>C</c> interface library for
- communication with <c>Erlang</c>.</p>
+ <note><p>The support for VxWorks is deprecated as of OTP 22, and
+ will be removed in OTP 23.</p></note>
+ <note><p>The old legacy <c>erl_interface</c> library (functions
+ with prefix <c>erl_</c>) is deprecated as of OTP 22, and will be
+ removed in OTP 23. This does not apply to the <c>ei</c>
+ library. Reasonably new <c>gcc</c> compilers will issue deprecation
+ warnings. In order to disable these warnings, define the macro
+ <c>EI_NO_DEPR_WARN</c>.</p></note>
<note>
<p>By default, the <c>ei</c> library is only guaranteed
to be compatible with other Erlang/OTP components from the same
diff --git a/lib/erl_interface/doc/src/ref_man_erl_interface.xml b/lib/erl_interface/doc/src/ref_man_erl_interface.xml
index 4b1d0e9981..2b69d0fa74 100644
--- a/lib/erl_interface/doc/src/ref_man_erl_interface.xml
+++ b/lib/erl_interface/doc/src/ref_man_erl_interface.xml
@@ -30,6 +30,14 @@
<file>ref_man_erl_interface.xml</file>
</header>
<description>
+ <note><p>The support for VxWorks is deprecated as of OTP 22, and
+ will be removed in OTP 23.</p></note>
+ <note><p>The old legacy <c>erl_interface</c> library (functions
+ with prefix <c>erl_</c>) is deprecated as of OTP 22, and will be
+ removed in OTP 23. This does not apply to the <c>ei</c>
+ library. Reasonably new <c>gcc</c> compilers will issue deprecation
+ warnings. In order to disable these warnings, define the macro
+ <c>EI_NO_DEPR_WARN</c>.</p></note>
<p>The <c>erl_interface</c> library is a <c>C</c> interface library
for communication with <c>Erlang</c>.</p>
<note>
diff --git a/lib/erl_interface/doc/src/registry.xml b/lib/erl_interface/doc/src/registry.xml
index 6d70fb3475..1c90c5c9dd 100644
--- a/lib/erl_interface/doc/src/registry.xml
+++ b/lib/erl_interface/doc/src/registry.xml
@@ -44,7 +44,7 @@
<funcs>
<func>
- <name><ret>int</ret><nametext>ei_reg_close(reg)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_close(reg)</nametext></name>
<fsummary>Close a registry.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -59,7 +59,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_delete(reg,key)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_delete(reg,key)</nametext></name>
<fsummary>Delete an object from the registry.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -85,7 +85,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_dump(fd,reg,mntab,flags)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_dump(fd,reg,mntab,flags)</nametext></name>
<fsummary>Back up a registry to Mnesia.</fsummary>
<type>
<v>int fd;</v>
@@ -125,7 +125,7 @@
</func>
<func>
- <name><ret>double</ret><nametext>ei_reg_getfval(reg,key)</nametext></name>
+ <name since=""><ret>double</ret><nametext>ei_reg_getfval(reg,key)</nametext></name>
<fsummary>Get a floating point object.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -151,7 +151,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_getival(reg,key)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_getival(reg,key)</nametext></name>
<fsummary>Get an integer object.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -177,7 +177,7 @@
</func>
<func>
- <name><ret>const void *</ret><nametext>ei_reg_getpval(reg,key,size)</nametext></name>
+ <name since=""><ret>const void *</ret><nametext>ei_reg_getpval(reg,key,size)</nametext></name>
<fsummary>Get a binary object.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -207,7 +207,7 @@
</func>
<func>
- <name><ret>const char *</ret><nametext>ei_reg_getsval(reg,key)</nametext></name>
+ <name since=""><ret>const char *</ret><nametext>ei_reg_getsval(reg,key)</nametext></name>
<fsummary>Get a string object.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -232,7 +232,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_getval(reg,key,flags,v,...)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_getval(reg,key,flags,v,...)</nametext></name>
<fsummary>Get any object.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -278,7 +278,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_markdirty(reg,key)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_markdirty(reg,key)</nametext></name>
<fsummary>Mark an object as dirty.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -305,7 +305,7 @@
</func>
<func>
- <name><ret>ei_reg *</ret><nametext>ei_reg_open(size)</nametext></name>
+ <name since=""><ret>ei_reg *</ret><nametext>ei_reg_open(size)</nametext></name>
<fsummary>Create and open a registry.</fsummary>
<type>
<v>int size;</v>
@@ -326,7 +326,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_purge(reg)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_purge(reg)</nametext></name>
<fsummary>Remove deleted objects.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -346,7 +346,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_resize(reg,newsize)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_resize(reg,newsize)</nametext></name>
<fsummary>Resize a registry.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -363,7 +363,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_restore(fd,reg,mntab)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_restore(fd,reg,mntab)</nametext></name>
<fsummary>Restore a registry from Mnesia.</fsummary>
<type>
<v>int fd;</v>
@@ -399,7 +399,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_setfval(reg,key,f)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_setfval(reg,key,f)</nametext></name>
<fsummary>Assign a floating point object.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -424,7 +424,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_setival(reg,key,i)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_setival(reg,key,i)</nametext></name>
<fsummary>Assign an integer object.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -448,7 +448,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_setpval(reg,key,p,size)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_setpval(reg,key,p,size)</nametext></name>
<fsummary>Assign a binary object.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -479,7 +479,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_setsval(reg,key,s)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_setsval(reg,key,s)</nametext></name>
<fsummary>Assign a string object.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -507,7 +507,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_setval(reg,key,flags,v,...)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_setval(reg,key,flags,v,...)</nametext></name>
<fsummary>Assign a value to any object type.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -552,7 +552,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_stat(reg,key,obuf)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_stat(reg,key,obuf)</nametext></name>
<fsummary>Get object information.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -590,7 +590,7 @@ struct ei_reg_stat {
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_tabstat(reg,obuf)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_tabstat(reg,obuf)</nametext></name>
<fsummary>Get registry information.</fsummary>
<type>
<v>ei_reg *reg;</v>
diff --git a/lib/erl_interface/include/ei.h b/lib/erl_interface/include/ei.h
index 948f89be85..10a6e5bf50 100644
--- a/lib/erl_interface/include/ei.h
+++ b/lib/erl_interface/include/ei.h
@@ -35,6 +35,9 @@
#include <winsock2.h>
#include <windows.h>
#include <winbase.h>
+typedef LONG_PTR ssize_t; /* Sigh... */
+#else
+#include <sys/types.h> /* ssize_t */
#endif
#include <stdio.h> /* Need type FILE */
@@ -44,6 +47,20 @@
# include <netdb.h>
#endif
+#ifndef EI_INCLUDED_CONFIG_H__
+#include "ei_config.h"
+#endif
+
+#if defined(HAVE_DEPRECATED_ATTRIBUTE) && !defined(EI_NO_DEPR_WARN)
+#define EI_HAVE_DEPRECATED_ATTR__ 1
+#define EI_DEPRECATED_ATTR_NAME deprecated
+#define EI_DEPRECATED_ATTR __attribute__((EI_DEPRECATED_ATTR_NAME))
+#else
+#undef EI_HAVE_DEPRECATED_ATTR__
+#define EI_DEPRECATED_ATTR_NAME
+#define EI_DEPRECATED_ATTR
+#endif
+
/* -------------------------------------------------------------------- */
/* Defines part of API */
@@ -286,6 +303,31 @@ typedef struct {
char nodename[MAXNODELEN+1];
} ErlConnect;
+#define EI_SCLBK_INF_TMO (~((unsigned) 0))
+
+#define EI_SCLBK_FLG_FULL_IMPL (1 << 0)
+
+typedef struct {
+ int flags;
+
+ int (*socket)(void **ctx, void *setup_ctx);
+ int (*close)(void *ctx);
+ int (*listen)(void *ctx, void *addr, int *len, int backlog);
+ int (*accept)(void **ctx, void *addr, int *len, unsigned tmo);
+ int (*connect)(void *ctx, void *addr, int len, unsigned tmo);
+ int (*writev)(void *ctx, const void *iov, int iovcnt, ssize_t *len, unsigned tmo);
+ int (*write)(void *ctx, const char *buf, ssize_t *len, unsigned tmo);
+ int (*read)(void *ctx, char *buf, ssize_t *len, unsigned tmo);
+
+ int (*handshake_packet_header_size)(void *ctx, int *sz);
+ int (*connect_handshake_complete)(void *ctx);
+ int (*accept_handshake_complete)(void *ctx);
+ int (*get_fd)(void *ctx, int *fd);
+
+ /* end of version 1 */
+
+} ei_socket_callbacks;
+
typedef struct ei_cnode_s {
char thishostname[EI_MAXHOSTNAMELEN+1];
char thisnodename[MAXNODELEN+1];
@@ -295,6 +337,8 @@ typedef struct ei_cnode_s {
char ei_connect_cookie[EI_MAX_COOKIE_SIZE+1];
short creation;
erlang_pid self;
+ ei_socket_callbacks *cbs;
+ void *setup_context;
} ei_cnode;
typedef struct in_addr *Erl_IpAddr;
@@ -308,7 +352,6 @@ typedef struct ei_x_buff_TAG {
int index;
} ei_x_buff;
-
/* -------------------------------------------------------------------- */
/* Function definitions (listed in same order as documentation) */
/* -------------------------------------------------------------------- */
@@ -322,6 +365,16 @@ int ei_connect_xinit (ei_cnode* ec, const char *thishostname,
Erl_IpAddr thisipaddr, const char *cookie,
const short creation);
+int ei_connect_init_ussi(ei_cnode* ec, const char* this_node_name,
+ const char *cookie, short creation,
+ ei_socket_callbacks *cbs, int cbs_sz,
+ void *setup_context);
+int ei_connect_xinit_ussi(ei_cnode* ec, const char *thishostname,
+ const char *thisalivename, const char *thisnodename,
+ Erl_IpAddr thisipaddr, const char *cookie,
+ const short creation, ei_socket_callbacks *cbs,
+ int cbs_sz, void *setup_context);
+
int ei_connect(ei_cnode* ec, char *nodename);
int ei_connect_tmo(ei_cnode* ec, char *nodename, unsigned ms);
int ei_xconnect(ei_cnode* ec, Erl_IpAddr adr, char *alivename);
@@ -348,11 +401,15 @@ int ei_rpc_from(ei_cnode* ec, int fd, int timeout, erlang_msg* msg,
int ei_publish(ei_cnode* ec, int port);
int ei_publish_tmo(ei_cnode* ec, int port, unsigned ms);
+int ei_listen(ei_cnode *ec, int *port, int backlog);
+int ei_xlisten(ei_cnode *ec, Erl_IpAddr adr, int *port, int backlog);
int ei_accept(ei_cnode* ec, int lfd, ErlConnect *conp);
int ei_accept_tmo(ei_cnode* ec, int lfd, ErlConnect *conp, unsigned ms);
int ei_unpublish(ei_cnode* ec);
int ei_unpublish_tmo(const char *alive, unsigned ms);
+int ei_close_connection(int fd);
+
const char *ei_thisnodename(const ei_cnode* ec);
const char *ei_thishostname(const ei_cnode* ec);
const char *ei_thisalivename(const ei_cnode* ec);
@@ -626,6 +683,8 @@ struct ei_reg_tabstat {
};
+int ei_init(void);
+
/* -------------------------------------------------------------------- */
/* XXXXXXXXXXX */
/* -------------------------------------------------------------------- */
diff --git a/lib/erl_interface/include/ei_config.h.in b/lib/erl_interface/include/ei_config.h.in
new file mode 100644
index 0000000000..dfcf676bed
--- /dev/null
+++ b/lib/erl_interface/include/ei_config.h.in
@@ -0,0 +1,3 @@
+
+/* Define to 1 if you have the `deprecated' attribute */
+#undef HAVE_DEPRECATED_ATTRIBUTE
diff --git a/lib/erl_interface/include/erl_interface.h b/lib/erl_interface/include/erl_interface.h
index c22f21af2b..7c87223a38 100644
--- a/lib/erl_interface/include/erl_interface.h
+++ b/lib/erl_interface/include/erl_interface.h
@@ -25,8 +25,6 @@
/* Note: the 'ei' interface is the prefered C API. */
/************************************************************************/
-/* FIXME only include if needed? */
-
#include "ei.h" /* ei is the base */
/* -------------------------------------------------------------------- */
@@ -195,11 +193,11 @@ typedef struct {
int lenL;
} Erl_Atom_data;
-char* erl_atom_ptr_latin1(Erl_Atom_data*);
-char* erl_atom_ptr_utf8(Erl_Atom_data*);
-int erl_atom_size_latin1(Erl_Atom_data*);
-int erl_atom_size_utf8(Erl_Atom_data*);
-char* erl_atom_init_latin1(Erl_Atom_data*, const char*);
+char* erl_atom_ptr_latin1(Erl_Atom_data*) EI_DEPRECATED_ATTR;
+char* erl_atom_ptr_utf8(Erl_Atom_data*) EI_DEPRECATED_ATTR;
+int erl_atom_size_latin1(Erl_Atom_data*) EI_DEPRECATED_ATTR;
+int erl_atom_size_utf8(Erl_Atom_data*) EI_DEPRECATED_ATTR;
+char* erl_atom_init_latin1(Erl_Atom_data*, const char*) EI_DEPRECATED_ATTR;
typedef struct {
Erl_Header h;
@@ -324,110 +322,117 @@ typedef unsigned char Erl_Heap;
/* The functions */
/* -------------------------------------------------------------------- */
-void erl_init(void *x, long y);
-void erl_set_compat_rel(unsigned);
-int erl_connect_init(int, char*,short);
-int erl_connect_xinit(char*,char*,char*,struct in_addr*,char*,short);
-int erl_connect(char*);
-int erl_xconnect(struct in_addr*,char *);
-int erl_close_connection(int);
-int erl_receive(int, unsigned char*, int);
-int erl_receive_msg(int, unsigned char*, int, ErlMessage*);
-int erl_xreceive_msg(int, unsigned char**, int*, ErlMessage*);
-int erl_send(int, ETERM*, ETERM*);
-int erl_reg_send(int, char*, ETERM*);
-ETERM *erl_rpc(int,char*,char*,ETERM*);
-int erl_rpc_to(int,char*,char*,ETERM*);
-int erl_rpc_from(int,int,ErlMessage*);
+void erl_init(void *x, long y) EI_DEPRECATED_ATTR;
+void erl_set_compat_rel(unsigned) EI_DEPRECATED_ATTR;
+int erl_connect_init(int, char*,short) EI_DEPRECATED_ATTR;
+int erl_connect_xinit(char*,char*,char*,struct in_addr*,char*,short) EI_DEPRECATED_ATTR;
+int erl_connect(char*) EI_DEPRECATED_ATTR;
+int erl_xconnect(struct in_addr*,char *) EI_DEPRECATED_ATTR;
+int erl_close_connection(int) EI_DEPRECATED_ATTR;
+int erl_receive(int, unsigned char*, int) EI_DEPRECATED_ATTR;
+int erl_receive_msg(int, unsigned char*, int, ErlMessage*) EI_DEPRECATED_ATTR;
+int erl_xreceive_msg(int, unsigned char**, int*, ErlMessage*) EI_DEPRECATED_ATTR;
+int erl_send(int, ETERM*, ETERM*) EI_DEPRECATED_ATTR;
+int erl_reg_send(int, char*, ETERM*) EI_DEPRECATED_ATTR;
+ETERM *erl_rpc(int,char*,char*,ETERM*) EI_DEPRECATED_ATTR;
+int erl_rpc_to(int,char*,char*,ETERM*) EI_DEPRECATED_ATTR;
+int erl_rpc_from(int,int,ErlMessage*) EI_DEPRECATED_ATTR;
/* erl_publish returns open descriptor on success, or -1 */
-int erl_publish(int port);
-int erl_accept(int,ErlConnect*);
+int erl_publish(int port) EI_DEPRECATED_ATTR;
+int erl_accept(int,ErlConnect*) EI_DEPRECATED_ATTR;
-const char *erl_thiscookie(void);
-const char *erl_thisnodename(void);
-const char *erl_thishostname(void);
-const char *erl_thisalivename(void);
-short erl_thiscreation(void);
+const char *erl_thiscookie(void) EI_DEPRECATED_ATTR;
+const char *erl_thisnodename(void) EI_DEPRECATED_ATTR;
+const char *erl_thishostname(void) EI_DEPRECATED_ATTR;
+const char *erl_thisalivename(void) EI_DEPRECATED_ATTR;
+short erl_thiscreation(void) EI_DEPRECATED_ATTR;
/* returns 0 on success, -1 if node not known to epmd or epmd not reached */
-int erl_unpublish(const char *alive);
+int erl_unpublish(const char *alive) EI_DEPRECATED_ATTR;
+
+#ifdef EI_HAVE_DEPRECATED_ATTR__
+#define EI_DEPR_ATTR_EXTRA , EI_DEPRECATED_ATTR_NAME
+#else
+#define EI_DEPR_ATTR_EXTRA
+#endif
+
/* Report generic error to stderr. */
void erl_err_msg(const char * __template, ...)
- __attribute__ ((__format__ (printf, 1, 2)));
+ __attribute__ ((__format__ (printf, 1, 2) EI_DEPR_ATTR_EXTRA)) ;
/* Report generic error to stderr and die. */
void erl_err_quit(const char * __template, ...)
- __attribute__ ((__format__ (printf, 1, 2), __noreturn__));
+ __attribute__ ((__format__ (printf, 1, 2), __noreturn__ EI_DEPR_ATTR_EXTRA));
/* Report system/libc error to stderr. */
void erl_err_ret(const char * __template, ...)
- __attribute__ ((__format__ (printf, 1, 2)));
+ __attribute__ ((__format__ (printf, 1, 2) EI_DEPR_ATTR_EXTRA));
/* Report system/libc error to stderr and die. */
void erl_err_sys(const char * __template, ...)
- __attribute__ ((__format__ (printf, 1, 2), __noreturn__));
-
-ETERM *erl_cons(ETERM*,ETERM*);
-ETERM *erl_copy_term(const ETERM*);
-ETERM *erl_element(int,const ETERM*);
-
-ETERM *erl_hd(const ETERM*);
-ETERM* erl_iolist_to_binary(const ETERM* term);
-char* erl_iolist_to_string(const ETERM* term);
-int erl_iolist_length(const ETERM*);
-int erl_length(const ETERM*);
-ETERM *erl_mk_atom(const char*);
-ETERM *erl_mk_binary(const char*,int);
-ETERM *erl_mk_empty_list(void);
-ETERM *erl_mk_estring(const char*, int);
-ETERM *erl_mk_float(double);
-ETERM *erl_mk_int(int);
-ETERM *erl_mk_longlong(long long);
-ETERM *erl_mk_list(ETERM**,int);
-ETERM *erl_mk_pid(const char*,unsigned int,unsigned int,unsigned char);
-ETERM *erl_mk_port(const char*,unsigned int,unsigned char);
-ETERM *erl_mk_ref(const char*,unsigned int,unsigned char);
+ __attribute__ ((__format__ (printf, 1, 2), __noreturn__ EI_DEPR_ATTR_EXTRA));
+
+ETERM *erl_cons(ETERM*,ETERM*) EI_DEPRECATED_ATTR;
+ETERM *erl_copy_term(const ETERM*) EI_DEPRECATED_ATTR;
+ETERM *erl_element(int,const ETERM*) EI_DEPRECATED_ATTR;
+
+ETERM *erl_hd(const ETERM*) EI_DEPRECATED_ATTR;
+ETERM* erl_iolist_to_binary(const ETERM* term) EI_DEPRECATED_ATTR;
+char* erl_iolist_to_string(const ETERM* term) EI_DEPRECATED_ATTR;
+int erl_iolist_length(const ETERM*) EI_DEPRECATED_ATTR;
+int erl_length(const ETERM*) EI_DEPRECATED_ATTR;
+ETERM *erl_mk_atom(const char*) EI_DEPRECATED_ATTR;
+ETERM *erl_mk_binary(const char*,int) EI_DEPRECATED_ATTR;
+ETERM *erl_mk_empty_list(void) EI_DEPRECATED_ATTR;
+ETERM *erl_mk_estring(const char*, int) EI_DEPRECATED_ATTR;
+ETERM *erl_mk_float(double) EI_DEPRECATED_ATTR;
+ETERM *erl_mk_int(int) EI_DEPRECATED_ATTR;
+ETERM *erl_mk_longlong(long long) EI_DEPRECATED_ATTR;
+ETERM *erl_mk_list(ETERM**,int) EI_DEPRECATED_ATTR;
+ETERM *erl_mk_pid(const char*,unsigned int,unsigned int,unsigned char) EI_DEPRECATED_ATTR;
+ETERM *erl_mk_port(const char*,unsigned int,unsigned char) EI_DEPRECATED_ATTR;
+ETERM *erl_mk_ref(const char*,unsigned int,unsigned char) EI_DEPRECATED_ATTR;
ETERM *erl_mk_long_ref(const char*,unsigned int,unsigned int,
- unsigned int,unsigned char);
-ETERM *erl_mk_string(const char*);
-ETERM *erl_mk_tuple(ETERM**,int);
-ETERM *erl_mk_uint(unsigned int);
-ETERM *erl_mk_ulonglong(unsigned long long);
-ETERM *erl_mk_var(const char*);
-int erl_print_term(FILE*,const ETERM*);
-/* int erl_sprint_term(char*,const ETERM*); */
-int erl_size(const ETERM*);
-ETERM *erl_tl(const ETERM*);
-ETERM *erl_var_content(const ETERM*, const char*);
-
-ETERM *erl_format(char*, ... );
-int erl_match(ETERM*, ETERM*);
-
-char **erl_global_names(int fd, int *count);
-int erl_global_register(int fd, const char *name, ETERM *pid);
-int erl_global_unregister(int fd, const char *name);
-ETERM *erl_global_whereis(int fd, const char *name, char *node);
-
-void erl_init_malloc(Erl_Heap*,long);
-ETERM *erl_alloc_eterm(unsigned char);
-void erl_eterm_release(void);
-void erl_eterm_statistics(unsigned long*,unsigned long*);
-void erl_free_array(ETERM**,int);
-void erl_free_term(ETERM*);
-void erl_free_compound(ETERM*);
-void *erl_malloc(long);
-void erl_free(void*);
-
-int erl_compare_ext(unsigned char*, unsigned char*);
-ETERM *erl_decode(unsigned char*);
-ETERM *erl_decode_buf(unsigned char**);
-int erl_encode(ETERM*,unsigned char*t);
-int erl_encode_buf(ETERM*,unsigned char**);
-int erl_ext_size(unsigned char*);
-unsigned char erl_ext_type(unsigned char*); /* Note: returned 'char' before R9C */
-unsigned char *erl_peek_ext(unsigned char*,int);
-int erl_term_len(ETERM*);
-
-int cmp_latin1_vs_utf8(const char* sL, int lenL, const char* sU, int lenU);
+ unsigned int,unsigned char) EI_DEPRECATED_ATTR;
+ETERM *erl_mk_string(const char*) EI_DEPRECATED_ATTR;
+ETERM *erl_mk_tuple(ETERM**,int) EI_DEPRECATED_ATTR;
+ETERM *erl_mk_uint(unsigned int) EI_DEPRECATED_ATTR;
+ETERM *erl_mk_ulonglong(unsigned long long) EI_DEPRECATED_ATTR;
+ETERM *erl_mk_var(const char*) EI_DEPRECATED_ATTR;
+int erl_print_term(FILE*,const ETERM*) EI_DEPRECATED_ATTR;
+/* int erl_sprint_term(char*,const ETERM*) EI_DEPRECATED_ATTR; */
+int erl_size(const ETERM*) EI_DEPRECATED_ATTR;
+ETERM *erl_tl(const ETERM*) EI_DEPRECATED_ATTR;
+ETERM *erl_var_content(const ETERM*, const char*) EI_DEPRECATED_ATTR;
+
+ETERM *erl_format(char*, ... ) EI_DEPRECATED_ATTR;
+int erl_match(ETERM*, ETERM*) EI_DEPRECATED_ATTR;
+
+char **erl_global_names(int fd, int *count) EI_DEPRECATED_ATTR;
+int erl_global_register(int fd, const char *name, ETERM *pid) EI_DEPRECATED_ATTR;
+int erl_global_unregister(int fd, const char *name) EI_DEPRECATED_ATTR;
+ETERM *erl_global_whereis(int fd, const char *name, char *node) EI_DEPRECATED_ATTR;
+
+void erl_init_malloc(Erl_Heap*,long) EI_DEPRECATED_ATTR;
+ETERM *erl_alloc_eterm(unsigned char) EI_DEPRECATED_ATTR;
+void erl_eterm_release(void) EI_DEPRECATED_ATTR;
+void erl_eterm_statistics(unsigned long*,unsigned long*) EI_DEPRECATED_ATTR;
+void erl_free_array(ETERM**,int) EI_DEPRECATED_ATTR;
+void erl_free_term(ETERM*) EI_DEPRECATED_ATTR;
+void erl_free_compound(ETERM*) EI_DEPRECATED_ATTR;
+void *erl_malloc(long) EI_DEPRECATED_ATTR;
+void erl_free(void*) EI_DEPRECATED_ATTR;
+
+int erl_compare_ext(unsigned char*, unsigned char*) EI_DEPRECATED_ATTR;
+ETERM *erl_decode(unsigned char*) EI_DEPRECATED_ATTR;
+ETERM *erl_decode_buf(unsigned char**) EI_DEPRECATED_ATTR;
+int erl_encode(ETERM*,unsigned char*t) EI_DEPRECATED_ATTR;
+int erl_encode_buf(ETERM*,unsigned char**) EI_DEPRECATED_ATTR;
+int erl_ext_size(unsigned char*) EI_DEPRECATED_ATTR;
+unsigned char erl_ext_type(unsigned char*) EI_DEPRECATED_ATTR; /* Note: returned 'char' before R9C */
+unsigned char *erl_peek_ext(unsigned char*,int) EI_DEPRECATED_ATTR;
+int erl_term_len(ETERM*) EI_DEPRECATED_ATTR;
+
+int cmp_latin1_vs_utf8(const char* sL, int lenL, const char* sU, int lenU) EI_DEPRECATED_ATTR;
/* -------------------------------------------------------------------- */
/* Wrappers around ei functions */
@@ -437,29 +442,29 @@ int cmp_latin1_vs_utf8(const char* sL, int lenL, const char* sU, int lenU);
* Undocumented before R9C, included for compatibility with old code
*/
-struct hostent *erl_gethostbyname(const char *name);
-struct hostent *erl_gethostbyaddr(const char *addr, int len, int type);
+struct hostent *erl_gethostbyname(const char *name) EI_DEPRECATED_ATTR;
+struct hostent *erl_gethostbyaddr(const char *addr, int len, int type) EI_DEPRECATED_ATTR;
struct hostent *erl_gethostbyname_r(const char *name,
struct hostent *hostp,
char *buffer,
int buflen,
- int *h_errnop);
+ int *h_errnop) EI_DEPRECATED_ATTR;
struct hostent *erl_gethostbyaddr_r(const char *addr,
int length,
int type,
struct hostent *hostp,
char *buffer,
int buflen,
- int *h_errnop);
+ int *h_errnop) EI_DEPRECATED_ATTR;
/*
* Undocumented, included for compatibility with old code
*/
-void erl_init_resolve(void);
-int erl_distversion(int fd);
-int erl_epmd_connect(struct in_addr *inaddr);
-int erl_epmd_port(struct in_addr *inaddr, const char *alive, int *dist);
+void erl_init_resolve(void) EI_DEPRECATED_ATTR;
+int erl_distversion(int fd) EI_DEPRECATED_ATTR;
+int erl_epmd_connect(struct in_addr *inaddr) EI_DEPRECATED_ATTR;
+int erl_epmd_port(struct in_addr *inaddr, const char *alive, int *dist) EI_DEPRECATED_ATTR;
#ifdef __cplusplus
}
diff --git a/lib/erl_interface/src/INSTALL b/lib/erl_interface/src/INSTALL
index b42a17ac46..bf3ca8b6a5 100644
--- a/lib/erl_interface/src/INSTALL
+++ b/lib/erl_interface/src/INSTALL
@@ -122,10 +122,10 @@ you can use the `configure' options `--x-includes=DIR' and
Specifying the System Type
==========================
- There may be some features `configure' can not figure out
+ There may be some features `configure' cannot figure out
automatically, but needs to determine by the type of host the package
will run on. Usually `configure' can figure that out, but if it prints
-a message saying it can not guess the host type, give it the
+a message saying it cannot guess the host type, give it the
`--host=TYPE' option. TYPE can either be a short name for the system
type, such as `sun4', or a canonical name with three fields:
CPU-COMPANY-SYSTEM
diff --git a/lib/erl_interface/src/Makefile.in b/lib/erl_interface/src/Makefile.in
index 614e7325a9..1b19a85f1e 100644
--- a/lib/erl_interface/src/Makefile.in
+++ b/lib/erl_interface/src/Makefile.in
@@ -31,12 +31,13 @@
.PHONY : debug opt release clean distclean depend
-TARGET = @TARGET@
+include $(ERL_TOP)/make/target.mk
# ----------------------------------------------------
# Application version and release dir specification
# ----------------------------------------------------
include ../vsn.mk
+include $(ERL_TOP)/make/target.mk
include $(TARGET)/eidefs.mk
include $(ERL_TOP)/make/output.mk
@@ -117,7 +118,7 @@ else
AR_FLAGS=rcv
endif
-INCFLAGS = -I. -I../include -Iconnect -Iencode -Idecode -Imisc -Iepmd \
+INCFLAGS = -I. -I../include -I../include/$(TARGET) -Iconnect -Iencode -Idecode -Imisc -Iepmd \
-Iregistry -I$(TARGET)
ifeq ($(USING_VC),yes)
@@ -126,6 +127,8 @@ else
WARNFLAGS = @WFLAGS@
endif
+WARNFLAGS += -DEI_NO_DEPR_WARN
+
CFLAGS = @LIB_CFLAGS@ $(WARNFLAGS) $(INCFLAGS) $(TYPE_FLAGS)
PROG_CFLAGS = @CFLAGS@ $(WARNFLAGS) $(INCFLAGS) $(TYPE_FLAGS) -Ilegacy
@@ -319,7 +322,8 @@ HEADERS = \
../include/ei.h \
../include/ei_connect.h \
../include/eicode.h \
- ../include/erl_interface.h
+ ../include/erl_interface.h \
+ ../include/$(TARGET)/ei_config.h
EISOURCES = \
$(CONNECTSRC) \
@@ -417,7 +421,8 @@ MISCSRC = \
misc/eimd5.c \
misc/get_type.c \
misc/show_msg.c \
- misc/ei_compat.c
+ misc/ei_compat.c \
+ misc/ei_init.c
REGISTRYSRC = \
registry/hash_dohash.c \
diff --git a/lib/erl_interface/src/README.internal b/lib/erl_interface/src/README.internal
index c1f2d6863f..42c45b46a9 100644
--- a/lib/erl_interface/src/README.internal
+++ b/lib/erl_interface/src/README.internal
@@ -167,12 +167,12 @@ NOTE!!!! Sending a "char" to macros like isupper(), isalpha() where
the character is > 127 will cause serios problems on some
machines/OS. The reason is that
- 'char' may be unsigned, i.e. the Swedish char '�' will
+ 'char' may be unsigned, i.e. the Swedish char 'ä' will
as a number be negativ.
The implementation of isupper() and others will on some
machines use an array that is indexed with the incoming
- character code. The Swedish '�' will then create an access
+ character code. The Swedish 'ä' will then create an access
on memory outside the array!
This may give a random value as a result or a segmentation
@@ -219,7 +219,7 @@ There are some functions in the 'ei' library that uses the GCC and
VC++ "long long" type. Unfortunately this can lead to some trouble.
When user code is linked with the "libei.a" the linker will extract
-all objects files needed for resolving all symbol referenses
+all objects files needed for resolving all symbol references
found. This means that you want to follow the rule that
* To reduce executable code size we use resonably small C source
@@ -252,7 +252,7 @@ example is that in plain R9C the ei_x_encode_longlong() function is
located in the file "ei_x_encode.c". So if any "long long" ei_x
function is used we have an unessesary dependency on
"ei_encode_longlong.o" and then need to link with GNU ld on with the
-user code or explicitely link with "libgcc.a". The situation can be
+user code or explicitly link with "libgcc.a". The situation can be
visible in in plain R9C using
% nm -A erl_interface-3.4/lib/libei.a | \
diff --git a/lib/erl_interface/src/connect/ei_connect.c b/lib/erl_interface/src/connect/ei_connect.c
index 9df4fa3b6c..7a304e6d4f 100644
--- a/lib/erl_interface/src/connect/ei_connect.c
+++ b/lib/erl_interface/src/connect/ei_connect.c
@@ -42,10 +42,8 @@
#include <inetLib.h>
#include <unistd.h>
-#include <sys/types.h>
#include <sys/times.h>
#include <unistd.h>
-#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
@@ -55,7 +53,6 @@
#else /* some other unix */
#include <unistd.h>
-#include <sys/types.h>
#include <sys/times.h>
#if TIME_WITH_SYS_TIME
@@ -84,6 +81,7 @@
#include <string.h>
#include <errno.h>
#include <ctype.h>
+#include <stddef.h>
#include "eiext.h"
#include "ei_portio.h"
@@ -98,11 +96,16 @@
#include "ei_epmd.h"
#include "ei_internal.h"
+static int ei_connect_initialized = 0;
int ei_tracelevel = 0;
#define COOKIE_FILE "/.erlang.cookie"
#define EI_MAX_HOME_PATH 1024
+#define EI_SOCKET_CALLBACKS_SZ_V1 \
+ (offsetof(ei_socket_callbacks, get_fd) \
+ + sizeof(int (*)(void *)))
+
/* FIXME why not macro? */
static char *null_cookie = "";
@@ -113,35 +116,51 @@ static int get_home(char *buf, int size);
static unsigned gen_challenge(void);
static void gen_digest(unsigned challenge, char cookie[],
unsigned char digest[16]);
-static int send_status(int fd, char *status, unsigned ms);
-static int recv_status(int fd, unsigned ms);
-static int send_challenge(int fd, char *nodename,
- unsigned challenge, unsigned version, unsigned ms);
-static int recv_challenge(int fd, unsigned *challenge,
- unsigned *version,
- unsigned *flags, ErlConnect *namebuf, unsigned ms);
-static int send_challenge_reply(int fd, unsigned char digest[16],
+static int send_status(ei_socket_callbacks *cbs, void *ctx,
+ int pkt_sz, char *status, unsigned ms);
+static int recv_status(ei_socket_callbacks *cbs, void *ctx,
+ int pkt_sz, unsigned ms);
+static int send_challenge(ei_socket_callbacks *cbs, void *ctx, int pkt_sz,
+ char *nodename, unsigned challenge,
+ unsigned version, unsigned ms);
+static int recv_challenge(ei_socket_callbacks *cbs, void *ctx, int pkt_sz,
+ unsigned *challenge, unsigned *version,
+ unsigned *flags, char *namebuf, unsigned ms);
+static int send_challenge_reply(ei_socket_callbacks *cbs, void *ctx,
+ int pkt_sz, unsigned char digest[16],
unsigned challenge, unsigned ms);
-static int recv_challenge_reply(int fd,
- unsigned our_challenge,
+static int recv_challenge_reply(ei_socket_callbacks *cbs, void *ctx,
+ int pkt_sz, unsigned our_challenge,
char cookie[],
unsigned *her_challenge, unsigned ms);
-static int send_challenge_ack(int fd, unsigned char digest[16], unsigned ms);
-static int recv_challenge_ack(int fd,
- unsigned our_challenge,
+static int send_challenge_ack(ei_socket_callbacks *cbs, void *ctx,
+ int pkt_sz, unsigned char digest[16],
+ unsigned ms);
+static int recv_challenge_ack(ei_socket_callbacks *cbs, void *ctx,
+ int pkt_sz, unsigned our_challenge,
char cookie[], unsigned ms);
-static int send_name(int fd, char *nodename,
- unsigned version, unsigned ms);
+static int send_name(ei_socket_callbacks *cbs, void *ctx, int pkt_sz,
+ char *nodename, unsigned version, unsigned ms);
-/* Common for both handshake types */
-static int recv_name(int fd,
- unsigned *version,
- unsigned *flags, ErlConnect *namebuf, unsigned ms);
+static int recv_name(ei_socket_callbacks *cbs, void *ctx, int pkt_sz,
+ unsigned *version, unsigned *flags, char *namebuf,
+ unsigned ms);
static struct hostent*
dyn_gethostbyname_r(const char *name, struct hostent *hostp, char **buffer_p,
int buflen, int *h_errnop);
+static void abort_connection(ei_socket_callbacks *cbs, void *ctx);
+static int close_connection(ei_socket_callbacks *cbs, void *ctx, int fd);
+
+static char *
+estr(int e)
+{
+ char *str = strerror(e);
+ if (!str)
+ return "unknown error";
+ return str;
+}
/***************************************************************************
@@ -154,25 +173,208 @@ dyn_gethostbyname_r(const char *name, struct hostent *hostp, char **buffer_p,
typedef struct ei_socket_info_s {
int socket;
+ ei_socket_callbacks *cbs;
+ void *ctx;
int dist_version;
ei_cnode cnode; /* A copy, not a pointer. We don't know when freed */
char cookie[EI_MAX_COOKIE_SIZE+1];
} ei_socket_info;
+/***************************************************************************
+ *
+ * XXX
+ *
+ ***************************************************************************/
+
+#ifndef ETHR_HAVE___atomic_compare_exchange_n
+# define ETHR_HAVE___atomic_compare_exchange_n 0
+#endif
+#ifndef ETHR_HAVE___atomic_load_n
+# define ETHR_HAVE___atomic_load_n 0
+#endif
+#ifndef ETHR_HAVE___atomic_store_n
+# define ETHR_HAVE___atomic_store_n 0
+#endif
+
+#if defined(_REENTRANT) \
+ && (!(ETHR_HAVE___atomic_compare_exchange_n & SIZEOF_VOID_P) \
+ || !(ETHR_HAVE___atomic_load_n & SIZEOF_VOID_P) \
+ || !(ETHR_HAVE___atomic_store_n & SIZEOF_VOID_P))
+# undef EI_DISABLE_SEQ_SOCKET_INFO
+# define EI_DISABLE_SEQ_SOCKET_INFO
+#endif
+
+#ifdef __WIN32__
+# undef EI_DISABLE_SEQ_SOCKET_INFO
+# define EI_DISABLE_SEQ_SOCKET_INFO
+#endif
+
+#ifndef EI_DISABLE_SEQ_SOCKET_INFO
+
+#ifdef _REENTRANT
+
+#define EI_ATOMIC_CMPXCHG_ACQ_REL(VARP, XCHGP, NEW) \
+ __atomic_compare_exchange_n((VARP), (XCHGP), (NEW), 0, \
+ __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)
+#define EI_ATOMIC_LOAD_ACQ(VARP) \
+ __atomic_load_n((VARP), __ATOMIC_ACQUIRE)
+#define EI_ATOMIC_STORE_REL(VARP, NEW) \
+ __atomic_store_n((VARP), (NEW), __ATOMIC_RELEASE)
+
+#else /* ! _REENTRANT */
+
+#define EI_ATOMIC_CMPXCHG_ACQ_REL(VARP, XCHGP, NEW) \
+ (*(VARP) == *(XCHGP) \
+ ? ((*(VARP) = (NEW)), !0) \
+ : ((*(XCHGP) = *(VARP)), 0))
+#define EI_ATOMIC_LOAD_ACQ(VARP) (*(VARP))
+#define EI_ATOMIC_STORE_REL(VARP, NEW) (*(VARP) = (NEW))
+
+#endif /* ! _REENTRANT */
+
+#define EI_SOCKET_INFO_SEG_BITS 5
+#define EI_SOCKET_INFO_SEG_SIZE (1 << EI_SOCKET_INFO_SEG_BITS)
+#define EI_SOCKET_INFO_SEG_MASK (EI_SOCKET_INFO_SEG_SIZE - 1)
+
+typedef struct {
+ int max_fds;
+ ei_socket_info *segments[1]; /* Larger in reality... */
+} ei_socket_info_data__;
+
+static ei_socket_info_data__ *socket_info_data = NULL;
+
+static int init_socket_info(int late)
+{
+ int max_fds;
+ int i;
+ size_t segments_len;
+ ei_socket_info_data__ *info_data, *xchg;
+
+ if (EI_ATOMIC_LOAD_ACQ(&socket_info_data) != NULL)
+ return 0; /* Already initialized... */
+
+#if defined(HAVE_SYSCONF) && defined(_SC_OPEN_MAX)
+ max_fds = sysconf(_SC_OPEN_MAX);
+#else
+ max_fds = 1024;
+#endif
+
+ if (max_fds < 0)
+ return EIO;
+
+ segments_len = ((max_fds-1)/EI_SOCKET_INFO_SEG_SIZE + 1);
+
+ info_data = malloc(sizeof(ei_socket_info_data__)
+ + (sizeof(ei_socket_info *)*(segments_len-1)));
+ if (!info_data)
+ return ENOMEM;
+
+ info_data->max_fds = max_fds;
+ for (i = 0; i < segments_len; i++)
+ info_data->segments[i] = NULL;
+
+ xchg = NULL;
+ if (!EI_ATOMIC_CMPXCHG_ACQ_REL(&socket_info_data, &xchg, info_data))
+ free(info_data); /* Already initialized... */
+
+ return 0;
+}
+
+static int put_ei_socket_info(int fd, int dist_version, char* cookie, ei_cnode *ec,
+ ei_socket_callbacks *cbs, void *ctx)
+{
+ int six;
+ ei_socket_info *seg, *si;
+ int socket;
+
+ if (fd < 0 || socket_info_data->max_fds <= fd)
+ return -1;
+
+ socket = fd;
+ six = fd >> EI_SOCKET_INFO_SEG_BITS;
+ seg = EI_ATOMIC_LOAD_ACQ(&socket_info_data->segments[six]);
+
+ if (!seg) {
+ ei_socket_info *xchg;
+ int i;
+ seg = malloc(sizeof(ei_socket_info)*EI_SOCKET_INFO_SEG_SIZE);
+ if (!seg)
+ return -1;
+ for (i = 0; i < EI_SOCKET_INFO_SEG_SIZE; i++) {
+ seg[i].socket = -1;
+ }
+
+ xchg = NULL;
+ if (!EI_ATOMIC_CMPXCHG_ACQ_REL(&socket_info_data->segments[six], &xchg, seg)) {
+ free(seg);
+ seg = xchg;
+ }
+ }
+
+ si = &seg[fd & EI_SOCKET_INFO_SEG_MASK];
+
+ if (dist_version < 0) {
+ socket = -1;
+ si->cbs = NULL;
+ si->ctx = NULL;
+ }
+ else {
+ si->dist_version = dist_version;
+ si->cnode = *ec;
+ si->cbs = cbs;
+ si->ctx = ctx;
+ strcpy(si->cookie, cookie);
+ }
+
+ EI_ATOMIC_STORE_REL(&si->socket, socket);
+
+ return 0;
+}
+
+static ei_socket_info* get_ei_socket_info(int fd)
+{
+ int six, socket;
+ ei_socket_info *seg, *si;
+
+ if (fd < 0 || socket_info_data->max_fds <= fd)
+ return NULL;
+
+ six = fd >> EI_SOCKET_INFO_SEG_BITS;
+ seg = EI_ATOMIC_LOAD_ACQ(&socket_info_data->segments[six]);
+
+ if (!seg)
+ return NULL;
+
+ si = &seg[fd & EI_SOCKET_INFO_SEG_MASK];
+ socket = EI_ATOMIC_LOAD_ACQ(&si->socket);
+ if (socket != fd)
+ return NULL;
+ return si;
+}
+
+#else /* EI_DISABLE_SEQ_SOCKET_INFO */
+
int ei_n_sockets = 0, ei_sz_sockets = 0;
ei_socket_info *ei_sockets = NULL;
+
#ifdef _REENTRANT
ei_mutex_t* ei_sockets_lock = NULL;
#endif /* _REENTRANT */
+static int init_socket_info(int late)
+{
+#ifdef _REENTRANT
+ if (late)
+ return ENOTSUP; /* Refuse doing unsafe initialization... */
+ ei_sockets_lock = ei_mutex_create();
+ if (!ei_sockets_lock)
+ return ENOMEM;
+#endif /* _REENTRANT */
+ return 0;
+}
-/***************************************************************************
- *
- * XXX
- *
- ***************************************************************************/
-
-static int put_ei_socket_info(int fd, int dist_version, char* cookie, ei_cnode *ec)
+static int put_ei_socket_info(int fd, int dist_version, char* cookie, ei_cnode *ec,
+ ei_socket_callbacks *cbs, void *ctx)
{
int i;
@@ -182,11 +384,13 @@ static int put_ei_socket_info(int fd, int dist_version, char* cookie, ei_cnode *
for (i = 0; i < ei_n_sockets; ++i) {
if (ei_sockets[i].socket == fd) {
if (dist_version == -1) {
- memmove(&ei_sockets[i], &ei_sockets[i+1],
+ memmove(&ei_sockets[i], &ei_sockets[i+1],
sizeof(ei_sockets[0])*(ei_n_sockets-i-1));
} else {
ei_sockets[i].dist_version = dist_version;
/* Copy the content, see ei_socket_info */
+ ei_sockets[i].cbs = cbs;
+ ei_sockets[i].ctx = ctx;
ei_sockets[i].cnode = *ec;
strcpy(ei_sockets[i].cookie, cookie);
}
@@ -209,7 +413,9 @@ static int put_ei_socket_info(int fd, int dist_version, char* cookie, ei_cnode *
}
ei_sockets[ei_n_sockets].socket = fd;
ei_sockets[ei_n_sockets].dist_version = dist_version;
- ei_sockets[i].cnode = *ec;
+ ei_sockets[ei_n_sockets].cnode = *ec;
+ ei_sockets[ei_n_sockets].cbs = cbs;
+ ei_sockets[ei_n_sockets].ctx = ctx;
strcpy(ei_sockets[ei_n_sockets].cookie, cookie);
++ei_n_sockets;
}
@@ -219,14 +425,6 @@ static int put_ei_socket_info(int fd, int dist_version, char* cookie, ei_cnode *
return 0;
}
-#if 0
-/* FIXME not used ?! */
-static int remove_ei_socket_info(int fd, int dist_version, char* cookie)
-{
- return put_ei_socket_info(fd, -1, NULL);
-}
-#endif
-
static ei_socket_info* get_ei_socket_info(int fd)
{
int i;
@@ -248,6 +446,13 @@ static ei_socket_info* get_ei_socket_info(int fd)
return NULL;
}
+#endif /* EI_DISABLE_SEQ_SOCKET_INFO */
+
+static int remove_ei_socket_info(int fd)
+{
+ return put_ei_socket_info(fd, -1, NULL, NULL, NULL, NULL);
+}
+
ei_cnode *ei_fd_to_cnode(int fd)
{
ei_socket_info *sockinfo = get_ei_socket_info(fd);
@@ -255,6 +460,19 @@ ei_cnode *ei_fd_to_cnode(int fd)
return &sockinfo->cnode;
}
+int ei_get_cbs_ctx__(ei_socket_callbacks **cbs, void **ctx, int fd)
+{
+ ei_socket_info *sockinfo = get_ei_socket_info(fd);
+ if (sockinfo) {
+ *cbs = sockinfo->cbs;
+ *ctx = sockinfo->ctx;
+ return 0;
+ }
+
+ *cbs = NULL;
+ *ctx = NULL;
+ return EBADF;
+}
/***************************************************************************
* Get/Set tracelevel
@@ -333,21 +551,6 @@ const char *ei_getfdcookie(int fd)
return r;
}
-/* call with cookie to set value to use on descriptor fd,
-* or specify NULL to use default
-*/
-/* FIXME why defined but not used? */
-#if 0
-static int ei_setfdcookie(ei_cnode* ec, int fd, char *cookie)
-{
- int dist_version = ei_distversion(fd);
-
- if (cookie == NULL)
- cookie = ec->ei_connect_cookie;
- return put_ei_socket_info(fd, dist_version, cookie);
-}
-#endif
-
static int get_int32(unsigned char *s)
{
return ((s[0] << 24) | (s[1] << 16) | (s[2] << 8) | (s[3] ));
@@ -400,34 +603,62 @@ static int initWinSock(void)
}
#endif
+static int init_connect(int late)
+{
+ int error;
+
+ /*
+ * 'late' is non-zero when not called via ei_init(). Such a
+ * call is not supported, but we for now save the day if
+ * it easy to do so; otherwise, return ENOTSUP.
+ */
+
+#ifdef __WIN32__
+ if (!initWinSock()) {
+ EI_TRACE_ERR0("ei_init_connect","can't initiate winsock");
+ return EIO;
+ }
+#endif /* win32 */
+
+ error = init_socket_info(late);
+ if (error) {
+ EI_TRACE_ERR0("ei_init_connect","can't initiate socket info");
+ return error;
+ }
+
+ ei_connect_initialized = !0;
+ return 0;
+}
+
+int ei_init_connect(void)
+{
+ return init_connect(0);
+}
+
/*
* Perhaps run this routine instead of ei_connect_init/2 ?
* Initailize by setting:
* thishostname, thisalivename, thisnodename and thisipaddr
*/
-int ei_connect_xinit(ei_cnode* ec, const char *thishostname,
- const char *thisalivename, const char *thisnodename,
- Erl_IpAddr thisipaddr, const char *cookie,
- const short creation)
+int ei_connect_xinit_ussi(ei_cnode* ec, const char *thishostname,
+ const char *thisalivename, const char *thisnodename,
+ Erl_IpAddr thisipaddr, const char *cookie,
+ const short creation, ei_socket_callbacks *cbs,
+ int cbs_sz, void *setup_context)
{
char *dbglevel;
-
-/* FIXME this code was enabled for 'erl'_connect_xinit(), why not here? */
-#if 0
-#ifdef __WIN32__
- if (!initWinSock()) {
- EI_TRACE_ERR0("ei_connect_xinit","can't initiate winsock");
- return ERL_ERROR;
- }
-#endif
-#endif
-#ifdef _REENTRANT
- if (ei_sockets_lock == NULL) {
- ei_sockets_lock = ei_mutex_create();
- }
-#endif /* _REENTRANT */
+ if (!ei_connect_initialized)
+ init_connect(!0);
+ if (cbs != &ei_default_socket_callbacks)
+ EI_SET_HAVE_PLUGIN_SOCKET_IMPL__;
+
+ if (cbs_sz < EI_SOCKET_CALLBACKS_SZ_V1) {
+ EI_TRACE_ERR0("ei_connect_xinit","invalid size of ei_socket_callbacks struct");
+ return ERL_ERROR;
+ }
+
ec->creation = creation & 0x3; /* 2 bits */
if (cookie) {
@@ -469,6 +700,9 @@ int ei_connect_xinit(ei_cnode* ec, const char *thishostname,
ec->self.serial = 0;
ec->self.creation = creation & 0x3; /* 2 bits */
+ ec->cbs = cbs;
+ ec->setup_context = setup_context;
+
if ((dbglevel = getenv("EI_TRACELEVEL")) != NULL ||
(dbglevel = getenv("ERL_DEBUG_DIST")) != NULL)
ei_tracelevel = atoi(dbglevel);
@@ -476,14 +710,27 @@ int ei_connect_xinit(ei_cnode* ec, const char *thishostname,
return 0;
}
+int ei_connect_xinit(ei_cnode* ec, const char *thishostname,
+ const char *thisalivename, const char *thisnodename,
+ Erl_IpAddr thisipaddr, const char *cookie,
+ const short creation)
+{
+ return ei_connect_xinit_ussi(ec, thishostname, thisalivename, thisnodename,
+ thisipaddr, cookie, creation,
+ &ei_default_socket_callbacks,
+ sizeof(ei_default_socket_callbacks),
+ NULL);
+}
/*
* Initialize by set: thishostname, thisalivename,
* thisnodename and thisipaddr. At success return 0,
* otherwise return -1.
*/
-int ei_connect_init(ei_cnode* ec, const char* this_node_name,
- const char *cookie, short creation)
+int ei_connect_init_ussi(ei_cnode* ec, const char* this_node_name,
+ const char *cookie, short creation,
+ ei_socket_callbacks *cbs, int cbs_sz,
+ void *setup_context)
{
char thishostname[EI_MAXHOSTNAMELEN+1];
char thisnodename[MAXNODELEN+1];
@@ -494,17 +741,8 @@ int ei_connect_init(ei_cnode* ec, const char* this_node_name,
int ei_h_errno;
int res;
-#ifdef __WIN32__
- if (!initWinSock()) {
- EI_TRACE_ERR0("ei_connect_xinit","can't initiate winsock");
- return ERL_ERROR;
- }
-#endif /* win32 */
-#ifdef _REENTRANT
- if (ei_sockets_lock == NULL) {
- ei_sockets_lock = ei_mutex_create();
- }
-#endif /* _REENTRANT */
+ if (!ei_connect_initialized)
+ init_connect(!0);
/* gethostname requires len to be max(hostname) + 1 */
if (gethostname(thishostname, EI_MAXHOSTNAMELEN+1) == -1) {
@@ -561,43 +799,22 @@ int ei_connect_init(ei_cnode* ec, const char* this_node_name,
sprintf(thisnodename, "%s@%s", this_node_name, hp->h_name);
}
}
- res = ei_connect_xinit(ec, thishostname, thisalivename, thisnodename,
- (struct in_addr *)*hp->h_addr_list, cookie, creation);
+ res = ei_connect_xinit_ussi(ec, thishostname, thisalivename, thisnodename,
+ (struct in_addr *)*hp->h_addr_list, cookie, creation,
+ cbs, cbs_sz, setup_context);
if (buf != buffer)
free(buf);
return res;
}
-
-/* connects to port at ip-address ip_addr
-* and returns fd to socket
-* port has to be in host byte order
-*/
-static int cnct(uint16 port, struct in_addr *ip_addr, int addr_len, unsigned ms)
+int ei_connect_init(ei_cnode* ec, const char* this_node_name,
+ const char *cookie, short creation)
{
- int s, res;
- struct sockaddr_in iserv_addr;
-
- if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
- erl_errno = errno;
- return ERL_ERROR;
- }
-
- memset((char*)&iserv_addr, 0, sizeof(struct sockaddr_in));
- memcpy((char*)&iserv_addr.sin_addr, (char*)ip_addr, addr_len);
- iserv_addr.sin_family = AF_INET;
- iserv_addr.sin_port = htons(port);
-
- if ((res = ei_connect_t(s, (struct sockaddr*)&iserv_addr,
- sizeof(iserv_addr),ms)) < 0) {
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- closesocket(s);
- return ERL_ERROR;
- }
-
- return s;
-} /* cnct */
-
+ return ei_connect_init_ussi(ec, this_node_name, cookie, creation,
+ &ei_default_socket_callbacks,
+ sizeof(ei_default_socket_callbacks),
+ NULL);
+}
/*
* Same as ei_gethostbyname_r, but also handles ERANGE error
@@ -758,91 +975,218 @@ int ei_connect(ei_cnode* ec, char *nodename)
* the node through epmd at that host
*
*/
-int ei_xconnect_tmo(ei_cnode* ec, Erl_IpAddr adr, char *alivename, unsigned ms)
+int ei_xconnect_tmo(ei_cnode* ec, Erl_IpAddr ip_addr, char *alivename, unsigned ms)
{
- struct in_addr *ip_addr=(struct in_addr *) adr;
+ ei_socket_callbacks *cbs = ec->cbs;
+ void *ctx;
int rport = 0; /*uint16 rport = 0;*/
int sockd;
- int one = 1;
int dist = 0;
- ErlConnect her_name;
unsigned her_flags, her_version;
-
+ unsigned our_challenge, her_challenge;
+ unsigned char our_digest[16];
+ int err;
+ int pkt_sz;
+ struct sockaddr_in addr;
+ unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
+
erl_errno = EIO; /* Default error code */
EI_TRACE_CONN1("ei_xconnect","-> CONNECT attempt to connect to %s",
alivename);
- if ((rport = ei_epmd_port_tmo(ip_addr,alivename,&dist, ms)) < 0) {
+ if ((rport = ei_epmd_port_tmo(ip_addr,alivename,&dist, tmo)) < 0) {
EI_TRACE_ERR0("ei_xconnect","-> CONNECT can't get remote port");
/* ei_epmd_port_tmo() has set erl_errno */
return ERL_NO_PORT;
}
-
- /* we now have port number to enode, try to connect */
- if((sockd = cnct((uint16)rport, ip_addr, sizeof(struct in_addr),ms)) < 0) {
- EI_TRACE_ERR0("ei_xconnect","-> CONNECT socket connect failed");
- /* cnct() has set erl_errno */
- return ERL_CONNECT_FAIL;
- }
-
- EI_TRACE_CONN0("ei_xconnect","-> CONNECT connected to remote");
- /* FIXME why connect before checking 'dist' output from ei_epmd_port() ?! */
if (dist <= 4) {
EI_TRACE_ERR0("ei_xconnect","-> CONNECT remote version not compatible");
- goto error;
+ return ERL_ERROR;
}
- else {
- unsigned our_challenge, her_challenge;
- unsigned char our_digest[16];
-
- if (send_name(sockd, ec->thisnodename, (unsigned) dist, ms))
- goto error;
- if (recv_status(sockd, ms))
- goto error;
- if (recv_challenge(sockd, &her_challenge, &her_version,
- &her_flags, &her_name, ms))
- goto error;
- our_challenge = gen_challenge();
- gen_digest(her_challenge, ec->ei_connect_cookie, our_digest);
- if (send_challenge_reply(sockd, our_digest, our_challenge, ms))
- goto error;
- if (recv_challenge_ack(sockd, our_challenge,
- ec->ei_connect_cookie, ms))
- goto error;
- put_ei_socket_info(sockd, dist, null_cookie, ec); /* FIXME check == 0 */
+
+ err = ei_socket_ctx__(cbs, &ctx, ec->setup_context);
+ if (err) {
+ EI_TRACE_ERR2("ei_xconnect","-> SOCKET failed: %s (%d)",
+ estr(err), err);
+ erl_errno = err;
+ return ERL_CONNECT_FAIL;
+ }
+
+ memset((void *) &addr, 0, sizeof(struct sockaddr_in));
+ memcpy((void *) &addr.sin_addr, (void *) ip_addr, sizeof(addr.sin_addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(rport);
+
+ err = ei_connect_ctx_t__(cbs, ctx, (void *) &addr, sizeof(addr), tmo);
+ if (err) {
+ EI_TRACE_ERR2("ei_xconnect","-> CONNECT socket connect failed: %s (%d)",
+ estr(err), err);
+ abort_connection(cbs, ctx);
+ erl_errno = err;
+ return ERL_CONNECT_FAIL;
}
- setsockopt(sockd, IPPROTO_TCP, TCP_NODELAY, (char *)&one, sizeof(one));
- setsockopt(sockd, SOL_SOCKET, SO_KEEPALIVE, (char *)&one, sizeof(one));
+ EI_TRACE_CONN0("ei_xconnect","-> CONNECT connected to remote");
- EI_TRACE_CONN1("ei_xconnect","-> CONNECT (ok) remote = %s",alivename);
+ err = EI_GET_FD__(cbs, ctx, &sockd);
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ goto error;
+ }
+
+ err = cbs->handshake_packet_header_size(ctx, &pkt_sz);
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ goto error;
+ }
+
+ if (send_name(cbs, ctx, pkt_sz, ec->thisnodename, (unsigned) dist, tmo))
+ goto error;
+ if (recv_status(cbs, ctx, pkt_sz, tmo))
+ goto error;
+ if (recv_challenge(cbs, ctx, pkt_sz, &her_challenge,
+ &her_version, &her_flags, NULL, tmo))
+ goto error;
+ our_challenge = gen_challenge();
+ gen_digest(her_challenge, ec->ei_connect_cookie, our_digest);
+ if (send_challenge_reply(cbs, ctx, pkt_sz, our_digest, our_challenge, tmo))
+ goto error;
+ if (recv_challenge_ack(cbs, ctx, pkt_sz, our_challenge,
+ ec->ei_connect_cookie, tmo))
+ goto error;
+ if (put_ei_socket_info(sockd, dist, null_cookie, ec, cbs, ctx) != 0)
+ goto error;
+
+ if (cbs->connect_handshake_complete) {
+ err = cbs->connect_handshake_complete(ctx);
+ if (err) {
+ EI_TRACE_ERR2("ei_xconnect","-> CONNECT failed: %s (%d)",
+ estr(err), err);
+ close_connection(cbs, ctx, sockd);
+ EI_CONN_SAVE_ERRNO__(err);
+ return ERL_ERROR;
+ }
+ }
+ EI_TRACE_CONN1("ei_xconnect","-> CONNECT (ok) remote = %s",alivename);
+
erl_errno = 0;
return sockd;
error:
EI_TRACE_ERR0("ei_xconnect","-> CONNECT failed");
- closesocket(sockd);
+ abort_connection(cbs, ctx);
return ERL_ERROR;
} /* ei_xconnect */
-int ei_xconnect(ei_cnode* ec, Erl_IpAddr adr, char *alivename)
+int ei_xconnect(ei_cnode* ec, Erl_IpAddr ip_addr, char *alivename)
{
- return ei_xconnect_tmo(ec, adr, alivename, 0);
+ return ei_xconnect_tmo(ec, ip_addr, alivename, 0);
}
+int ei_listen(ei_cnode *ec, int *port, int backlog)
+{
+ struct in_addr ip_addr;
+ ip_addr.s_addr = htonl(INADDR_ANY);
+ return ei_xlisten(ec, &ip_addr, port, backlog);
+}
+
+int ei_xlisten(ei_cnode *ec, struct in_addr *ip_addr, int *port, int backlog)
+{
+ ei_socket_callbacks *cbs = ec->cbs;
+ struct sockaddr_in sock_addr;
+ void *ctx;
+ int fd, err, len;
+
+ err = ei_socket_ctx__(cbs, &ctx, ec->setup_context);
+ if (err) {
+ EI_TRACE_ERR2("ei_xlisten","-> SOCKET failed: %s (%d)",
+ estr(err), err);
+ erl_errno = err;
+ return ERL_ERROR;
+ }
+
+ memset((void *) &sock_addr, 0, sizeof(struct sockaddr_in));
+ memcpy((void *) &sock_addr.sin_addr, (void *) ip_addr, sizeof(*ip_addr));
+ sock_addr.sin_family = AF_INET;
+ sock_addr.sin_port = htons((short) *port);
+
+ len = sizeof(sock_addr);
+ err = ei_listen_ctx__(cbs, ctx, (void *) &sock_addr, &len, backlog);
+ if (err) {
+ EI_TRACE_ERR2("ei_xlisten","-> listen failed: %s (%d)",
+ estr(err), err);
+ erl_errno = err;
+ goto error;
+ }
+
+ if (len != sizeof(sock_addr)) {
+ if (len < offsetof(struct sockaddr_in, sin_addr) + sizeof(sock_addr.sin_addr)
+ || len < offsetof(struct sockaddr_in, sin_port) + sizeof(sock_addr.sin_port)) {
+ erl_errno = EIO;
+ EI_TRACE_ERR0("ei_xlisten","-> get info failed");
+ goto error;
+ }
+ }
+
+ memcpy((void *) ip_addr, (void *) &sock_addr.sin_addr, sizeof(*ip_addr));
+ *port = (int) ntohs(sock_addr.sin_port);
+
+ err = EI_GET_FD__(cbs, ctx, &fd);
+ if (err) {
+ erl_errno = err;
+ goto error;
+ }
+
+ if (put_ei_socket_info(fd, 0, null_cookie, ec, cbs, ctx) != 0) {
+ EI_TRACE_ERR0("ei_xlisten","-> save socket info failed");
+ erl_errno = EIO;
+ goto error;
+ }
+
+ erl_errno = 0;
+
+ return fd;
+
+error:
+ abort_connection(cbs, ctx);
+ return ERL_ERROR;
+}
+
+static int close_connection(ei_socket_callbacks *cbs, void *ctx, int fd)
+{
+ int err;
+ remove_ei_socket_info(fd);
+ err = ei_close_ctx__(cbs, ctx);
+ if (err) {
+ erl_errno = err;
+ return ERL_ERROR;
+ }
+ return 0;
+}
- /*
- * For symmetry reasons
-*/
-#if 0
int ei_close_connection(int fd)
{
- return closesocket(fd);
+ ei_socket_callbacks *cbs;
+ void *ctx;
+ int err = EI_GET_CBS_CTX__(&cbs, &ctx, fd);
+ if (err)
+ erl_errno = err;
+ else {
+ if (close_connection(cbs, ctx, fd) == 0)
+ return 0;
+ }
+ EI_TRACE_ERR2("ei_close_connection","<- CLOSE socket close failed: %s (%d)",
+ estr(erl_errno), erl_errno);
+ return ERL_ERROR;
} /* ei_close_connection */
-#endif
+
+static void abort_connection(ei_socket_callbacks *cbs, void *ctx)
+{
+ (void) ei_close_ctx__(cbs, ctx);
+}
/*
* Accept and initiate a connection from another
@@ -857,25 +1201,71 @@ int ei_accept(ei_cnode* ec, int lfd, ErlConnect *conp)
int ei_accept_tmo(ei_cnode* ec, int lfd, ErlConnect *conp, unsigned ms)
{
int fd;
- struct sockaddr_in cli_addr;
- int cli_addr_len=sizeof(struct sockaddr_in);
unsigned her_version, her_flags;
- ErlConnect her_name;
+ char tmp_nodename[MAXNODELEN+1];
+ char *her_name;
+ int pkt_sz, err;
+ struct sockaddr_in addr;
+ int addr_len = sizeof(struct sockaddr_in);
+ ei_socket_callbacks *cbs;
+ void *ctx;
+ unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
erl_errno = EIO; /* Default error code */
+
+ err = EI_GET_CBS_CTX__(&cbs, &ctx, lfd);
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return ERL_ERROR;
+ }
+
EI_TRACE_CONN0("ei_accept","<- ACCEPT waiting for connection");
+
+ if (conp) {
+ her_name = &conp->nodename[0];
+ }
+ else {
+ her_name = &tmp_nodename[0];
+ }
- if ((fd = ei_accept_t(lfd, (struct sockaddr*) &cli_addr,
- &cli_addr_len, ms )) < 0) {
- EI_TRACE_ERR0("ei_accept","<- ACCEPT socket accept failed");
- erl_errno = (fd == -2) ? ETIMEDOUT : EIO;
- goto error;
+ /*
+ * ei_accept_ctx_t__() replaces the pointer to the listen context
+ * with a pointer to the accepted connection context on success.
+ */
+ err = ei_accept_ctx_t__(cbs, &ctx, (void *) &addr, &addr_len, tmo);
+ if (err) {
+ EI_TRACE_ERR2("ei_accept","<- ACCEPT socket accept failed: %s (%d)",
+ estr(err), err);
+ EI_CONN_SAVE_ERRNO__(err);
+ return ERL_ERROR;
+ }
+
+ err = EI_GET_FD__(cbs, ctx, &fd);
+ if (err) {
+ EI_TRACE_ERR2("ei_accept","<- ACCEPT get fd failed: %s (%d)",
+ estr(err), err);
+ EI_CONN_SAVE_ERRNO__(err);
+ }
+
+ if (addr_len != sizeof(struct sockaddr_in)) {
+ if (addr_len < (offsetof(struct sockaddr_in, sin_addr)
+ + sizeof(addr.sin_addr))) {
+ EI_TRACE_ERR0("ei_accept","<- ACCEPT get addr failed");
+ goto error;
+ }
+ }
+
+ err = cbs->handshake_packet_header_size(ctx, &pkt_sz);
+ if (err) {
+ EI_TRACE_ERR2("ei_accept","<- ACCEPT get packet size failed: %s (%d)",
+ estr(err), err);
+ EI_CONN_SAVE_ERRNO__(err);
}
EI_TRACE_CONN0("ei_accept","<- ACCEPT connected to remote");
- if (recv_name(fd, &her_version, &her_flags, &her_name, ms)) {
+ if (recv_name(cbs, ctx, pkt_sz, &her_version, &her_flags, her_name, tmo)) {
EI_TRACE_ERR0("ei_accept","<- ACCEPT initial ident failed");
goto error;
}
@@ -888,34 +1278,45 @@ int ei_accept_tmo(ei_cnode* ec, int lfd, ErlConnect *conp, unsigned ms)
unsigned our_challenge;
unsigned her_challenge;
unsigned char our_digest[16];
-
- if (send_status(fd,"ok", ms))
+
+ if (send_status(cbs, ctx, pkt_sz, "ok", tmo))
goto error;
our_challenge = gen_challenge();
- if (send_challenge(fd, ec->thisnodename,
- our_challenge, her_version, ms))
+ if (send_challenge(cbs, ctx, pkt_sz, ec->thisnodename,
+ our_challenge, her_version, tmo))
goto error;
- if (recv_challenge_reply(fd, our_challenge,
- ec->ei_connect_cookie,
- &her_challenge, ms))
+ if (recv_challenge_reply(cbs, ctx, pkt_sz, our_challenge,
+ ec->ei_connect_cookie, &her_challenge, tmo))
goto error;
gen_digest(her_challenge, ec->ei_connect_cookie, our_digest);
- if (send_challenge_ack(fd, our_digest, ms))
+ if (send_challenge_ack(cbs, ctx, pkt_sz, our_digest, tmo))
goto error;
- put_ei_socket_info(fd, her_version, null_cookie, ec);
+ if (put_ei_socket_info(fd, her_version, null_cookie, ec, cbs, ctx) != 0)
+ goto error;
+ }
+ if (conp) {
+ memcpy((void *) conp->ipadr, (void *) &addr.sin_addr, sizeof(conp->ipadr));
+ }
+
+ if (cbs->accept_handshake_complete) {
+ err = cbs->accept_handshake_complete(ctx);
+ if (err) {
+ EI_TRACE_ERR2("ei_xconnect","-> ACCEPT handshake failed: %s (%d)",
+ estr(err), err);
+ close_connection(cbs, ctx, fd);
+ EI_CONN_SAVE_ERRNO__(err);
+ return ERL_ERROR;
+ }
}
- if (conp)
- *conp = her_name;
- EI_TRACE_CONN1("ei_accept","<- ACCEPT (ok) remote = %s",her_name.nodename);
+ EI_TRACE_CONN1("ei_accept","<- ACCEPT (ok) remote = %s",her_name);
erl_errno = 0; /* No error */
return fd;
error:
EI_TRACE_ERR0("ei_accept","<- ACCEPT failed");
- if (fd>=0)
- closesocket(fd);
+ abort_connection(cbs, ctx);
return ERL_ERROR;
} /* ei_accept */
@@ -927,36 +1328,57 @@ error:
*/
int ei_receive_tmo(int fd, unsigned char *bufp, int bufsize, unsigned ms)
{
- int len;
+ ssize_t len;
unsigned char fourbyte[4]={0,0,0,0};
- int res;
-
- if ((res = ei_read_fill_t(fd, (char *) bufp, 4, ms)) != 4) {
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
+ int err;
+ ei_socket_callbacks *cbs;
+ void *ctx;
+ unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
+
+ err = EI_GET_CBS_CTX__(&cbs, &ctx, fd);
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return ERL_ERROR;
+ }
+
+ len = (ssize_t) 4;
+ err = ei_read_fill_ctx_t__(cbs, ctx, (char *) bufp, &len, tmo);
+ if (!err && len != (ssize_t) 4)
+ err = EIO;
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
return ERL_ERROR;
}
/* Tick handling */
- if ((len = get_int32(bufp)) == ERL_TICK)
- {
- ei_write_fill_t(fd, (char *) fourbyte, 4, ms);
+ len = get_int32(bufp);
+ if (len == ERL_TICK) {
+ len = 4;
+ ei_write_fill_ctx_t__(cbs, ctx, (char *) fourbyte, &len, tmo);
/* FIXME ok to ignore error or timeout? */
erl_errno = EAGAIN;
return ERL_TICK;
}
- else if (len > bufsize)
- {
+
+ if (len > bufsize) {
/* FIXME: We should drain the message. */
erl_errno = EMSGSIZE;
return ERL_ERROR;
}
- else if ((res = ei_read_fill_t(fd, (char *) bufp, len, ms)) != len)
- {
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- return ERL_ERROR;
+ else {
+ ssize_t need = len;
+ err = ei_read_fill_ctx_t__(cbs, ctx, (char *) bufp, &len, tmo);
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return ERL_ERROR;
+ }
+ if (len != need) {
+ erl_errno = EIO;
+ return ERL_ERROR;
+ }
}
- return len;
+ return (int) len;
}
@@ -1112,36 +1534,11 @@ int ei_rpc_to(ei_cnode *ec, int fd, char *mod, char *fun,
int ei_rpc_from(ei_cnode *ec, int fd, int timeout, erlang_msg *msg,
ei_x_buff *x)
{
- fd_set readmask;
- struct timeval tv;
- struct timeval *t = NULL;
-
- if (timeout >= 0) {
- tv.tv_sec = timeout / 1000;
- tv.tv_usec = (timeout % 1000) * 1000;
- t = &tv;
- }
-
- FD_ZERO(&readmask);
- FD_SET(fd,&readmask);
-
- switch (select(fd+1, &readmask, NULL, NULL, t)) {
- case -1:
- erl_errno = EIO;
- return ERL_ERROR;
-
- case 0:
- erl_errno = ETIMEDOUT;
- return ERL_TIMEOUT;
-
- default:
- if (FD_ISSET(fd, &readmask)) {
- return ei_xreceive_msg(fd, msg, x);
- } else {
- erl_errno = EIO;
- return ERL_ERROR;
- }
- }
+ unsigned tmo = timeout < 0 ? EI_SCLBK_INF_TMO : (unsigned) timeout;
+ int res = ei_xreceive_msg_tmo(fd, msg, x, tmo);
+ if (res < 0 && erl_errno == ETIMEDOUT)
+ return ERL_TIMEOUT;
+ return res;
} /* rpc_from */
/*
@@ -1295,19 +1692,34 @@ static char *hex(char digest[16], char buff[33])
return buff;
}
-static int read_2byte_package(int fd, char **buf, int *buflen,
- int *is_static, unsigned ms)
+static int read_hs_package(ei_socket_callbacks *cbs, void *ctx,
+ int pkt_sz, char **buf, int *buflen,
+ int *is_static, unsigned ms)
{
- unsigned char nbuf[2];
+ unsigned char nbuf[4];
unsigned char *x = nbuf;
- unsigned len;
- int res;
-
- if((res = ei_read_fill_t(fd, (char *)nbuf, 2, ms)) != 2) {
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
+ ssize_t len, need;
+ int err;
+
+ len = (ssize_t) pkt_sz;
+ err = ei_read_fill_ctx_t__(cbs, ctx, (char *)nbuf, &len, ms);
+ if (!err && len != (ssize_t) pkt_sz)
+ err = EIO;
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
return -1;
}
- len = get16be(x);
+
+ switch (pkt_sz) {
+ case 2:
+ len = get16be(x);
+ break;
+ case 4:
+ len = get32be(x);
+ break;
+ default:
+ return -1;
+ }
if (len > *buflen) {
if (*is_static) {
@@ -1329,20 +1741,26 @@ static int read_2byte_package(int fd, char **buf, int *buflen,
*buflen = len;
}
}
- if ((res = ei_read_fill_t(fd, *buf, len, ms)) != len) {
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
+ need = len;
+ err = ei_read_fill_ctx_t__(cbs, ctx, *buf, &len, ms);
+ if (!err && len != need)
+ err = EIO;
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
return -1;
}
return len;
}
-static int send_status(int fd, char *status, unsigned ms)
+static int send_status(ei_socket_callbacks *cbs, void *ctx,
+ int pkt_sz, char *status, unsigned ms)
{
char *buf, *s;
char dbuf[DEFBUF_SIZ];
- int siz = strlen(status) + 1 + 2;
- int res;
+ int siz = strlen(status) + 1 + pkt_sz;
+ int err;
+ ssize_t len;
buf = (siz > DEFBUF_SIZ) ? malloc(siz) : dbuf;
if (!buf) {
@@ -1350,14 +1768,28 @@ static int send_status(int fd, char *status, unsigned ms)
return -1;
}
s = buf;
- put16be(s,siz - 2);
+ switch (pkt_sz) {
+ case 2:
+ put16be(s,siz - 2);
+ break;
+ case 4:
+ put32be(s,siz - 4);
+ break;
+ default:
+ return -1;
+ }
put8(s, 's');
memcpy(s, status, strlen(status));
- if ((res = ei_write_fill_t(fd, buf, siz, ms)) != siz) {
- EI_TRACE_ERR0("send_status","-> SEND_STATUS socket write failed");
+ len = (ssize_t) siz;
+ err = ei_write_fill_ctx_t__(cbs, ctx, buf, &len, ms);
+ if (!err && len != (ssize_t) siz)
+ err = EIO;
+ if (err) {
+ EI_TRACE_ERR2("send_status","-> SEND_STATUS socket write failed: %s (%d)",
+ estr(err), err);
if (buf != dbuf)
- free(buf);
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
+ free(buf);
+ EI_CONN_SAVE_ERRNO__(err);
return -1;
}
EI_TRACE_CONN1("send_status","-> SEND_STATUS (%s)",status);
@@ -1367,7 +1799,8 @@ static int send_status(int fd, char *status, unsigned ms)
return 0;
}
-static int recv_status(int fd, unsigned ms)
+static int recv_status(ei_socket_callbacks *cbs, void *ctx,
+ int pkt_sz, unsigned ms)
{
char dbuf[DEFBUF_SIZ];
char *buf = dbuf;
@@ -1375,7 +1808,8 @@ static int recv_status(int fd, unsigned ms)
int buflen = DEFBUF_SIZ;
int rlen;
- if ((rlen = read_2byte_package(fd, &buf, &buflen, &is_static, ms)) <= 0) {
+ if ((rlen = read_hs_package(cbs, ctx, pkt_sz,
+ &buf, &buflen, &is_static, ms)) <= 0) {
EI_TRACE_ERR1("recv_status",
"<- RECV_STATUS socket read failed (%d)", rlen);
goto error;
@@ -1396,7 +1830,10 @@ error:
return -1;
}
-static int send_name_or_challenge(int fd, char *nodename,
+static int send_name_or_challenge(ei_socket_callbacks *cbs,
+ void *ctx,
+ int pkt_sz,
+ char *nodename,
int f_chall,
unsigned challenge,
unsigned version,
@@ -1405,9 +1842,10 @@ static int send_name_or_challenge(int fd, char *nodename,
char *buf;
unsigned char *s;
char dbuf[DEFBUF_SIZ];
- int siz = 2 + 1 + 2 + 4 + strlen(nodename);
+ int siz = pkt_sz + 1 + 2 + 4 + strlen(nodename);
const char* function[] = {"SEND_NAME", "SEND_CHALLENGE"};
- int res;
+ int err;
+ ssize_t len;
if (f_chall)
siz += 4;
@@ -1417,7 +1855,16 @@ static int send_name_or_challenge(int fd, char *nodename,
return -1;
}
s = (unsigned char *)buf;
- put16be(s,siz - 2);
+ switch (pkt_sz) {
+ case 2:
+ put16be(s,siz - 2);
+ break;
+ case 4:
+ put32be(s,siz - 4);
+ break;
+ default:
+ return -1;
+ }
put8(s, 'n');
put16be(s, version);
put32be(s, (DFLAG_EXTENDED_REFERENCES
@@ -1433,13 +1880,16 @@ static int send_name_or_challenge(int fd, char *nodename,
if (f_chall)
put32be(s, challenge);
memcpy(s, nodename, strlen(nodename));
-
- if ((res = ei_write_fill_t(fd, buf, siz, ms)) != siz) {
+ len = (ssize_t) siz;
+ err = ei_write_fill_ctx_t__(cbs, ctx, buf, &len, ms);
+ if (!err && len != (ssize_t) siz)
+ err = EIO;
+ if (err) {
EI_TRACE_ERR1("send_name_or_challenge",
"-> %s socket write failed", function[f_chall]);
if (buf != dbuf)
free(buf);
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
+ EI_CONN_SAVE_ERRNO__(err);
return -1;
}
@@ -1448,9 +1898,9 @@ static int send_name_or_challenge(int fd, char *nodename,
return 0;
}
-static int recv_challenge(int fd, unsigned *challenge,
- unsigned *version,
- unsigned *flags, ErlConnect *namebuf, unsigned ms)
+static int recv_challenge(ei_socket_callbacks *cbs, void *ctx,
+ int pkt_sz, unsigned *challenge, unsigned *version,
+ unsigned *flags, char *namebuf, unsigned ms)
{
char dbuf[DEFBUF_SIZ];
char *buf = dbuf;
@@ -1458,13 +1908,13 @@ static int recv_challenge(int fd, unsigned *challenge,
int buflen = DEFBUF_SIZ;
int rlen;
char *s;
- struct sockaddr_in sin;
- socklen_t sin_len = sizeof(sin);
char tag;
-
+ char tmp_nodename[MAXNODELEN+1];
+
erl_errno = EIO; /* Default */
- if ((rlen = read_2byte_package(fd, &buf, &buflen, &is_static, ms)) <= 0) {
+ if ((rlen = read_hs_package(cbs, ctx, pkt_sz, &buf, &buflen,
+ &is_static, ms)) <= 0) {
EI_TRACE_ERR1("recv_challenge",
"<- RECV_CHALLENGE socket read failed (%d)",rlen);
goto error;
@@ -1505,22 +1955,19 @@ static int recv_challenge(int fd, unsigned *challenge,
goto error;
}
- if (getpeername(fd, (struct sockaddr *) &sin, &sin_len) < 0) {
- EI_TRACE_ERR0("recv_challenge","<- RECV_CHALLENGE can't get peername");
- erl_errno = errno;
- goto error;
- }
- memcpy(namebuf->ipadr, &(sin.sin_addr.s_addr),
- sizeof(sin.sin_addr.s_addr));
- memcpy(namebuf->nodename, s, rlen - 11);
- namebuf->nodename[rlen - 11] = '\0';
+ if (!namebuf)
+ namebuf = &tmp_nodename[0];
+
+ memcpy(namebuf, s, rlen - 11);
+ namebuf[rlen - 11] = '\0';
+
if (!is_static)
free(buf);
EI_TRACE_CONN4("recv_challenge","<- RECV_CHALLENGE (ok) node = %s, "
"version = %u, "
"flags = %u, "
"challenge = %d",
- namebuf->nodename,
+ namebuf,
*version,
*flags,
*challenge
@@ -1533,24 +1980,40 @@ error:
return -1;
}
-static int send_challenge_reply(int fd, unsigned char digest[16],
+static int send_challenge_reply(ei_socket_callbacks *cbs, void *ctx,
+ int pkt_sz, unsigned char digest[16],
unsigned challenge, unsigned ms)
{
char *s;
char buf[DEFBUF_SIZ];
- int siz = 2 + 1 + 4 + 16;
- int res;
+ int siz = pkt_sz + 1 + 4 + 16;
+ int err;
+ ssize_t len;
s = buf;
- put16be(s,siz - 2);
+ switch (pkt_sz) {
+ case 2:
+ put16be(s,siz - 2);
+ break;
+ case 4:
+ put32be(s,siz - 4);
+ break;
+ default:
+ return -1;
+ }
put8(s, 'r');
put32be(s, challenge);
memcpy(s, digest, 16);
-
- if ((res = ei_write_fill_t(fd, buf, siz, ms)) != siz) {
- EI_TRACE_ERR0("send_challenge_reply",
- "-> SEND_CHALLENGE_REPLY socket write failed");
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
+
+ len = (ssize_t) siz;
+ err = ei_write_fill_ctx_t__(cbs, ctx, buf, &len, ms);
+ if (!err && len != (ssize_t) siz)
+ err = EIO;
+ if (err) {
+ EI_TRACE_ERR2("send_challenge_reply",
+ "-> SEND_CHALLENGE_REPLY socket write failed: %s (%d)",
+ estr(err), err);
+ EI_CONN_SAVE_ERRNO__(err);
return -1;
}
@@ -1563,11 +2026,13 @@ static int send_challenge_reply(int fd, unsigned char digest[16],
return 0;
}
-static int recv_challenge_reply (int fd,
- unsigned our_challenge,
- char cookie[],
- unsigned *her_challenge,
- unsigned ms)
+static int recv_challenge_reply(ei_socket_callbacks *cbs,
+ void *ctx,
+ int pkt_sz,
+ unsigned our_challenge,
+ char cookie[],
+ unsigned *her_challenge,
+ unsigned ms)
{
char dbuf[DEFBUF_SIZ];
char *buf = dbuf;
@@ -1580,7 +2045,7 @@ static int recv_challenge_reply (int fd,
erl_errno = EIO; /* Default */
- if ((rlen = read_2byte_package(fd, &buf, &buflen, &is_static, ms)) != 21) {
+ if ((rlen = read_hs_package(cbs, ctx, pkt_sz, &buf, &buflen, &is_static, ms)) != 21) {
EI_TRACE_ERR1("recv_challenge_reply",
"<- RECV_CHALLENGE_REPLY socket read failed (%d)",rlen);
goto error;
@@ -1620,23 +2085,38 @@ error:
return -1;
}
-static int send_challenge_ack(int fd, unsigned char digest[16], unsigned ms)
+static int send_challenge_ack(ei_socket_callbacks *cbs, void *ctx, int pkt_sz,
+ unsigned char digest[16], unsigned ms)
{
char *s;
char buf[DEFBUF_SIZ];
- int siz = 2 + 1 + 16;
- int res;
+ int siz = pkt_sz + 1 + 16;
+ int err;
+ ssize_t len;
s = buf;
-
- put16be(s,siz - 2);
+ switch (pkt_sz) {
+ case 2:
+ put16be(s,siz - 2);
+ break;
+ case 4:
+ put32be(s,siz - 4);
+ break;
+ default:
+ return -1;
+ }
put8(s, 'a');
memcpy(s, digest, 16);
- if ((res = ei_write_fill_t(fd, buf, siz, ms)) != siz) {
- EI_TRACE_ERR0("recv_challenge_reply",
- "-> SEND_CHALLENGE_ACK socket write failed");
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
+ len = (ssize_t) siz;
+ err = ei_write_fill_ctx_t__(cbs, ctx, buf, &len, ms);
+ if (!err && len != (ssize_t) siz)
+ err = EIO;
+ if (err) {
+ EI_TRACE_ERR2("recv_challenge_reply",
+ "-> SEND_CHALLENGE_ACK socket write failed: %s (%d)",
+ estr(err), err);
+ EI_CONN_SAVE_ERRNO__(err);
return -1;
}
@@ -1649,8 +2129,8 @@ static int send_challenge_ack(int fd, unsigned char digest[16], unsigned ms)
return 0;
}
-static int recv_challenge_ack(int fd,
- unsigned our_challenge,
+static int recv_challenge_ack(ei_socket_callbacks *cbs, void *ctx,
+ int pkt_sz, unsigned our_challenge,
char cookie[], unsigned ms)
{
char dbuf[DEFBUF_SIZ];
@@ -1664,7 +2144,7 @@ static int recv_challenge_ack(int fd,
erl_errno = EIO; /* Default */
- if ((rlen = read_2byte_package(fd, &buf, &buflen, &is_static, ms)) != 17) {
+ if ((rlen = read_hs_package(cbs, ctx, pkt_sz, &buf, &buflen, &is_static, ms)) != 17) {
EI_TRACE_ERR1("recv_challenge_ack",
"<- RECV_CHALLENGE_ACK socket read failed (%d)",rlen);
goto error;
@@ -1701,20 +2181,24 @@ error:
return -1;
}
-static int send_name(int fd, char *nodename, unsigned version, unsigned ms)
+static int send_name(ei_socket_callbacks *cbs, void *ctx, int pkt_sz,
+ char *nodename, unsigned version, unsigned ms)
{
- return send_name_or_challenge(fd, nodename, 0, 0, version, ms);
+ return send_name_or_challenge(cbs, ctx, pkt_sz, nodename, 0,
+ 0, version, ms);
}
-static int send_challenge(int fd, char *nodename,
- unsigned challenge, unsigned version, unsigned ms)
+static int send_challenge(ei_socket_callbacks *cbs, void *ctx, int pkt_sz,
+ char *nodename, unsigned challenge, unsigned version,
+ unsigned ms)
{
- return send_name_or_challenge(fd, nodename, 1, challenge, version, ms);
+ return send_name_or_challenge(cbs, ctx, pkt_sz, nodename, 1,
+ challenge, version, ms);
}
-static int recv_name(int fd,
- unsigned *version,
- unsigned *flags, ErlConnect *namebuf, unsigned ms)
+static int recv_name(ei_socket_callbacks *cbs, void *ctx,
+ int pkt_sz, unsigned *version,
+ unsigned *flags, char *namebuf, unsigned ms)
{
char dbuf[DEFBUF_SIZ];
char *buf = dbuf;
@@ -1722,13 +2206,13 @@ static int recv_name(int fd,
int buflen = DEFBUF_SIZ;
int rlen;
char *s;
- struct sockaddr_in sin;
- socklen_t sin_len = sizeof(sin);
+ char tmp_nodename[MAXNODELEN+1];
char tag;
erl_errno = EIO; /* Default */
- if ((rlen = read_2byte_package(fd, &buf, &buflen, &is_static, ms)) <= 0) {
+ if ((rlen = read_hs_package(cbs, ctx, pkt_sz, &buf, &buflen,
+ &is_static, ms)) <= 0) {
EI_TRACE_ERR1("recv_name","<- RECV_NAME socket read failed (%d)",rlen);
goto error;
}
@@ -1759,21 +2243,18 @@ static int recv_name(int fd,
erl_errno = EIO;
goto error;
}
-
- if (getpeername(fd, (struct sockaddr *) &sin, &sin_len) < 0) {
- EI_TRACE_ERR0("recv_name","<- RECV_NAME can't get peername");
- erl_errno = errno;
- goto error;
- }
- memcpy(namebuf->ipadr, &(sin.sin_addr.s_addr),
- sizeof(sin.sin_addr.s_addr));
- memcpy(namebuf->nodename, s, rlen - 7);
- namebuf->nodename[rlen - 7] = '\0';
+
+ if (!namebuf)
+ namebuf = &tmp_nodename[0];
+
+ memcpy(namebuf, s, rlen - 7);
+ namebuf[rlen - 7] = '\0';
+
if (!is_static)
free(buf);
EI_TRACE_CONN3("recv_name",
"<- RECV_NAME (ok) node = %s, version = %u, flags = %u",
- namebuf->nodename,*version,*flags);
+ namebuf,*version,*flags);
erl_errno = 0;
return 0;
@@ -1867,3 +2348,4 @@ static int get_cookie(char *buf, int bufsize)
return 1; /* Success! */
}
+
diff --git a/lib/erl_interface/src/connect/ei_resolve.c b/lib/erl_interface/src/connect/ei_resolve.c
index 022a43d255..225fddc784 100644
--- a/lib/erl_interface/src/connect/ei_resolve.c
+++ b/lib/erl_interface/src/connect/ei_resolve.c
@@ -57,9 +57,9 @@
#ifdef HAVE_GETHOSTBYNAME_R
-void ei_init_resolve(void)
+int ei_init_resolve(void)
{
- return; /* Do nothing */
+ return 0; /* Do nothing */
}
#else /* !HAVE_GETHOSTBYNAME_R */
@@ -103,7 +103,7 @@ static int verify_dns_configuration(void);
* our own, which are just wrappers around hostGetByName() and
* hostGetByAddr(). Here we look up the functions.
*/
-void ei_init_resolve(void)
+int ei_init_resolve(void)
{
#ifdef VXWORKS
@@ -134,9 +134,12 @@ void ei_init_resolve(void)
#ifdef _REENTRANT
ei_gethost_sem = ei_mutex_create();
+ if (!ei_gethost_sem)
+ return ENOMEM;
#endif /* _REENTRANT */
ei_resolve_initialized = 1;
+ return 0;
}
#ifdef VXWORKS
@@ -312,9 +315,11 @@ static struct hostent *my_gethostbyname_r(const char *name,
struct hostent *src;
struct hostent *rval = NULL;
- /* FIXME this should have been done in 'erl'_init()? */
- if (!ei_resolve_initialized) ei_init_resolve();
-
+ if (!ei_resolve_initialized) {
+ *h_errnop = NO_RECOVERY;
+ return NULL;
+ }
+
#ifdef _REENTRANT
/* === BEGIN critical section === */
if (ei_mutex_lock(ei_gethost_sem,0) != 0) {
@@ -377,7 +382,10 @@ static struct hostent *my_gethostbyaddr_r(const char *addr,
struct hostent *rval = NULL;
/* FIXME this should have been done in 'erl'_init()? */
- if (!ei_resolve_initialized) ei_init_resolve();
+ if (!ei_resolve_initialized) {
+ *h_errnop = NO_RECOVERY;
+ return NULL;
+ }
#ifdef _REENTRANT
/* === BEGIN critical section === */
diff --git a/lib/erl_interface/src/connect/ei_resolve.h b/lib/erl_interface/src/connect/ei_resolve.h
index 10a49ffbc6..5711d7da76 100644
--- a/lib/erl_interface/src/connect/ei_resolve.h
+++ b/lib/erl_interface/src/connect/ei_resolve.h
@@ -20,6 +20,6 @@
#ifndef _EI_RESOLVE_H
#define _EI_RESOLVE_H
-void ei_init_resolve(void);
+int ei_init_resolve(void);
#endif /* _EI_RESOLVE_H */
diff --git a/lib/erl_interface/src/connect/eirecv.c b/lib/erl_interface/src/connect/eirecv.c
index 7b9dbfc387..47eea06ced 100644
--- a/lib/erl_interface/src/connect/eirecv.c
+++ b/lib/erl_interface/src/connect/eirecv.c
@@ -60,22 +60,36 @@ ei_recv_internal (int fd,
int arity;
int version;
int index = 0;
- int i = 0;
- int res;
+ int err;
int show_this_msg = 0;
+ ei_socket_callbacks *cbs;
+ void *ctx;
+ ssize_t rlen;
+ unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
+
+ err = EI_GET_CBS_CTX__(&cbs, &ctx, fd);
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
+ }
/* get length field */
- if ((res = ei_read_fill_t(fd, header, 4, ms)) != 4)
- {
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
+ rlen = 4;
+ err = ei_read_fill_ctx_t__(cbs, ctx, header, &rlen, tmo);
+ if (!err && rlen != 4)
+ err = EIO;
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
return -1;
}
+
len = get32be(s);
/* got tick - respond and return */
if (!len) {
char tock[] = {0,0,0,0};
- ei_write_fill_t(fd, tock, sizeof(tock), ms); /* Failure no problem */
+ ssize_t wlen = sizeof(tock);
+ ei_write_fill_ctx_t__(cbs, ctx, tock, &wlen, tmo); /* Failure no problem */
*msglenp = 0;
return 0; /* maybe flag ERL_EAGAIN [sverkerw] */
}
@@ -86,9 +100,12 @@ ei_recv_internal (int fd,
ei_trace(-1,NULL);
/* read enough to get at least entire header */
- bytesread = (len > EIRECVBUF ? EIRECVBUF : len);
- if ((i = ei_read_fill_t(fd,header,bytesread,ms)) != bytesread) {
- erl_errno = (i == -2) ? ETIMEDOUT : EIO;
+ rlen = bytesread = (len > EIRECVBUF ? EIRECVBUF : len);
+ err = ei_read_fill_ctx_t__(cbs, ctx, header, &rlen, tmo);
+ if (!err && rlen != bytesread)
+ err = EIO;
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
return -1;
}
@@ -212,12 +229,17 @@ ei_recv_internal (int fd,
*/
if (msglen > *bufsz) {
if (staticbufp) {
- int sz = EIRECVBUF;
/* flush in rest of packet */
while (remain > 0) {
- if (remain < sz) sz = remain;
- if ((i=ei_read_fill_t(fd,header,sz,ms)) <= 0) break;
- remain -= i;
+ rlen = remain > EIRECVBUF ? EIRECVBUF : remain;
+ err = ei_read_fill_ctx_t__(cbs, ctx, header, &rlen, tmo);
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
+ }
+ if (rlen == 0)
+ break;
+ remain -= rlen;
}
erl_errno = EMSGSIZE;
return -1;
@@ -247,11 +269,15 @@ ei_recv_internal (int fd,
/* read the rest of the message into callers buffer */
if (remain > 0) {
- if ((i = ei_read_fill_t(fd,mbuf+bytesread-index,remain,ms)) != remain) {
- *msglenp = bytesread-index+1; /* actual bytes in users buffer */
- erl_errno = (i == -2) ? ETIMEDOUT : EIO;
- return -1;
- }
+ rlen = remain;
+ err = ei_read_fill_ctx_t__(cbs, ctx, mbuf+bytesread-index, &rlen, tmo);
+ if (!err && rlen != remain)
+ err = EIO;
+ if (err) {
+ *msglenp = bytesread-index+1; /* actual bytes in users buffer */
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
+ }
}
if (show_this_msg)
diff --git a/lib/erl_interface/src/connect/send.c b/lib/erl_interface/src/connect/send.c
index 37d7db6d68..d97532d123 100644
--- a/lib/erl_interface/src/connect/send.c
+++ b/lib/erl_interface/src/connect/send.c
@@ -58,10 +58,17 @@ int ei_send_encoded_tmo(int fd, const erlang_pid *to,
char *s, header[1200]; /* see size calculation below */
erlang_trace *token = NULL;
int index = 5; /* reserve 5 bytes for control message */
- int res;
-#ifdef HAVE_WRITEV
- struct iovec v[2];
-#endif
+ int err;
+ ei_socket_callbacks *cbs;
+ void *ctx;
+ ssize_t len, tot_len;
+ unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
+
+ err = EI_GET_CBS_CTX__(&cbs, &ctx, fd);
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return ERL_ERROR;
+ }
/* are we tracing? */
/* check that he can receive trace tokens first */
@@ -91,30 +98,47 @@ int ei_send_encoded_tmo(int fd, const erlang_pid *to,
if (ei_tracelevel >= 4)
ei_show_sendmsg(stderr,header,msg);
-#ifdef HAVE_WRITEV
-
- v[0].iov_base = (char *)header;
- v[0].iov_len = index;
- v[1].iov_base = (char *)msg;
- v[1].iov_len = msglen;
-
- if ((res = ei_writev_fill_t(fd,v,2,ms)) != index+msglen) {
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- return -1;
- }
-
-#else /* !HAVE_WRITEV */
-
- if ((res = ei_write_fill_t(fd,header,index,ms)) != index) {
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- return -1;
+
+#ifdef EI_HAVE_STRUCT_IOVEC__
+ if (ei_socket_callbacks_have_writev__(cbs)) {
+ struct iovec v[2];
+
+ v[0].iov_base = (char *)header;
+ v[0].iov_len = index;
+ v[1].iov_base = (char *)msg;
+ v[1].iov_len = msglen;
+
+ len = tot_len = (ssize_t) index+msglen;
+ err = ei_writev_fill_ctx_t__(cbs, ctx, v, 2, &len, tmo);
+ if (!err && len != tot_len)
+ err = EIO;
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
+ }
+
+ return 0;
}
- if ((res = ei_write_fill_t(fd,msg,msglen,ms)) != msglen) {
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- return -1;
+#endif /* EI_HAVE_STRUCT_IOVEC__ */
+
+ /* no writev() */
+ len = tot_len = (ssize_t) index;
+ err = ei_write_fill_ctx_t__(cbs, ctx, header, &len, tmo);
+ if (!err && len != tot_len)
+ err = EIO;
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
}
-#endif /* !HAVE_WRITEV */
+ len = tot_len = (ssize_t) msglen;
+ err = ei_write_fill_ctx_t__(cbs, ctx, msg, &len, tmo);
+ if (!err && len != tot_len)
+ err = EIO;
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
+ }
return 0;
}
diff --git a/lib/erl_interface/src/connect/send_exit.c b/lib/erl_interface/src/connect/send_exit.c
index 2e298e3221..b4f7e14c7f 100644
--- a/lib/erl_interface/src/connect/send_exit.c
+++ b/lib/erl_interface/src/connect/send_exit.c
@@ -55,6 +55,17 @@ int ei_send_exit_tmo(int fd, const erlang_pid *from, const erlang_pid *to,
char *s;
int index = 0;
int len = strlen(reason) + 1080; /* see below */
+ ei_socket_callbacks *cbs;
+ void *ctx;
+ int err;
+ ssize_t wlen;
+ unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
+
+ err = EI_GET_CBS_CTX__(&cbs, &ctx, fd);
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return ERL_ERROR;
+ }
if (len > EISMALLBUF)
if (!(dbuf = malloc(len)))
@@ -92,10 +103,16 @@ int ei_send_exit_tmo(int fd, const erlang_pid *from, const erlang_pid *to,
if (ei_tracelevel >= 4)
ei_show_sendmsg(stderr,msgbuf,NULL);
- ei_write_fill_t(fd,msgbuf,index,ms);
- /* FIXME ignore timeout etc? erl_errno?! */
-
- if (dbuf) free(dbuf);
+ wlen = (ssize_t) index;
+ err = ei_write_fill_ctx_t__(cbs, ctx, msgbuf, &wlen, tmo);
+ if (!err && wlen != (ssize_t) index)
+ err = EIO;
+ if (dbuf)
+ free(dbuf);
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return ERL_ERROR;
+ }
return 0;
}
diff --git a/lib/erl_interface/src/connect/send_reg.c b/lib/erl_interface/src/connect/send_reg.c
index 62478f042d..80d61e57b5 100644
--- a/lib/erl_interface/src/connect/send_reg.c
+++ b/lib/erl_interface/src/connect/send_reg.c
@@ -51,11 +51,17 @@ int ei_send_reg_encoded_tmo(int fd, const erlang_pid *from,
char *s, header[1400]; /* see size calculation below */
erlang_trace *token = NULL;
int index = 5; /* reserve 5 bytes for control message */
- int res;
+ int err;
+ ei_socket_callbacks *cbs;
+ void *ctx;
+ ssize_t len, tot_len;
+ unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
-#ifdef HAVE_WRITEV
- struct iovec v[2];
-#endif
+ err = EI_GET_CBS_CTX__(&cbs, &ctx, fd);
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return ERL_ERROR;
+ }
/* are we tracing? */
/* check that he can receive trace tokens first */
@@ -86,29 +92,45 @@ int ei_send_reg_encoded_tmo(int fd, const erlang_pid *from,
if (ei_tracelevel >= 4)
ei_show_sendmsg(stderr,header,msg);
-#ifdef HAVE_WRITEV
+#ifdef EI_HAVE_STRUCT_IOVEC__
+ if (ei_socket_callbacks_have_writev__(cbs)) {
+ struct iovec v[2];
- v[0].iov_base = (char *)header;
- v[0].iov_len = index;
- v[1].iov_base = (char *)msg;
- v[1].iov_len = msglen;
+ v[0].iov_base = (char *)header;
+ v[0].iov_len = index;
+ v[1].iov_base = (char *)msg;
+ v[1].iov_len = msglen;
- if ((res = ei_writev_fill_t(fd,v,2,ms)) != index+msglen) {
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- return -1;
+ len = tot_len = (ssize_t) index+msglen;
+ err = ei_writev_fill_ctx_t__(cbs, ctx, v, 2, &len, tmo);
+ if (!err && len != tot_len)
+ err = EIO;
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
+ }
+ return 0;
}
-#else
-
+#endif /* EI_HAVE_STRUCT_IOVEC__ */
+
/* no writev() */
- if ((res = ei_write_fill_t(fd,header,index,ms)) != index) {
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- return -1;
+ len = tot_len = (ssize_t) index;
+ err = ei_write_fill_ctx_t__(cbs, ctx, header, &len, tmo);
+ if (!err && len != tot_len)
+ err = EIO;
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
}
- if ((res = ei_write_fill_t(fd,msg,msglen,ms)) != msglen) {
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- return -1;
+
+ len = tot_len = (ssize_t) msglen;
+ err = ei_write_fill_ctx_t__(cbs, ctx, msg, &len, tmo);
+ if (!err && len != tot_len)
+ err = EIO;
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
}
-#endif
return 0;
}
diff --git a/lib/erl_interface/src/epmd/epmd_port.c b/lib/erl_interface/src/epmd/epmd_port.c
index 2ec418b24a..492c3fb3aa 100644
--- a/lib/erl_interface/src/epmd/epmd_port.c
+++ b/lib/erl_interface/src/epmd/epmd_port.c
@@ -62,31 +62,38 @@
int ei_epmd_connect_tmo(struct in_addr *inaddr, unsigned ms)
{
static unsigned int epmd_port = 0;
- struct sockaddr_in saddr;
- int sd;
- int res;
+ int port, sd, err;
+ struct in_addr ip_addr;
+ struct sockaddr_in addr;
+ unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
+
+ err = ei_socket__(&sd);
+ if (err) {
+ erl_errno = err;
+ return -1;
+ }
if (epmd_port == 0) {
char* port_str = getenv("ERL_EPMD_PORT");
epmd_port = (port_str != NULL) ? atoi(port_str) : EPMD_PORT;
}
- memset(&saddr, 0, sizeof(saddr));
- saddr.sin_port = htons(epmd_port);
- saddr.sin_family = AF_INET;
- if (!inaddr) saddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
- else memmove(&saddr.sin_addr,inaddr,sizeof(saddr.sin_addr));
+ port = (int) epmd_port;
- if (((sd = socket(PF_INET, SOCK_STREAM, 0)) < 0))
- {
- erl_errno = errno;
- return -1;
+ if (!inaddr) {
+ ip_addr.s_addr = htonl(INADDR_LOOPBACK);
+ inaddr = &ip_addr;
}
+
+ memset((void *) &addr, 0, sizeof(struct sockaddr_in));
+ memcpy((void *) &addr.sin_addr, (void *) inaddr, sizeof(addr.sin_addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port);
- if ((res = ei_connect_t(sd,(struct sockaddr *)&saddr,sizeof(saddr),ms)) < 0)
- {
- erl_errno = (res == -2) ? ETIMEDOUT : errno;
- closesocket(sd);
+ err = ei_connect_t__(sd, (void *) &addr, sizeof(addr), tmo);
+ if (err) {
+ erl_errno = err;
+ ei_close__(sd);
return -1;
}
@@ -104,6 +111,9 @@ static int ei_epmd_r4_port (struct in_addr *addr, const char *alive,
int port;
int dist_high, dist_low, proto;
int res;
+ int err;
+ ssize_t dlen;
+ unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
#if defined(VXWORKS)
char ntoabuf[32];
#endif
@@ -124,10 +134,14 @@ static int ei_epmd_r4_port (struct in_addr *addr, const char *alive,
return -1;
}
- if ((res = ei_write_fill_t(fd, buf, len+2, ms)) != len+2) {
- closesocket(fd);
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- return -1;
+ dlen = len + 2;
+ err = ei_write_fill_t__(fd, buf, &dlen, tmo);
+ if (!err && dlen != (ssize_t) len + 2)
+ erl_errno = EIO;
+ if (err) {
+ ei_close__(fd);
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
}
#ifdef VXWORKS
@@ -142,12 +156,15 @@ static int ei_epmd_r4_port (struct in_addr *addr, const char *alive,
"-> PORT2_REQ alive=%s ip=%s",alive,inet_ntoa(*addr));
#endif
- /* read first two bytes (response type, response) */
- if ((res = ei_read_fill_t(fd, buf, 2, ms)) != 2) {
- EI_TRACE_ERR0("ei_epmd_r4_port","<- CLOSE");
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- closesocket(fd);
- return -2; /* version mismatch */
+ dlen = (ssize_t) 2;
+ err = ei_read_fill_t__(fd, buf, &dlen, tmo);
+ if (!err && dlen != (ssize_t) 2)
+ erl_errno = EIO;
+ if (err) {
+ EI_TRACE_ERR0("ei_epmd_r4_port","<- CLOSE");
+ ei_close__(fd);
+ EI_CONN_SAVE_ERRNO__(err);
+ return -2;
}
s = buf;
@@ -156,7 +173,7 @@ static int ei_epmd_r4_port (struct in_addr *addr, const char *alive,
if (res != EI_EPMD_PORT2_RESP) { /* response type */
EI_TRACE_ERR1("ei_epmd_r4_port","<- unknown (%d)",res);
EI_TRACE_ERR0("ei_epmd_r4_port","-> CLOSE");
- closesocket(fd);
+ ei_close__(fd);
erl_errno = EIO;
return -1;
}
@@ -167,7 +184,7 @@ static int ei_epmd_r4_port (struct in_addr *addr, const char *alive,
if ((res = get8(s))) {
/* got negative response */
EI_TRACE_ERR1("ei_epmd_r4_port","<- PORT2_RESP result=%d (failure)",res);
- closesocket(fd);
+ ei_close__(fd);
erl_errno = EIO;
return -1;
}
@@ -175,14 +192,18 @@ static int ei_epmd_r4_port (struct in_addr *addr, const char *alive,
EI_TRACE_CONN1("ei_epmd_r4_port","<- PORT2_RESP result=%d (ok)",res);
/* expecting remaining 8 bytes */
- if ((res = ei_read_fill_t(fd,buf,8,ms)) != 8) {
+ dlen = (ssize_t) 8;
+ err = ei_read_fill_t__(fd, buf, &dlen, tmo);
+ if (!err && dlen != (ssize_t) 8)
+ err = EIO;
+ if (err) {
EI_TRACE_ERR0("ei_epmd_r4_port","<- CLOSE");
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- closesocket(fd);
+ ei_close__(fd);
+ EI_CONN_SAVE_ERRNO__(err);
return -1;
}
- closesocket(fd);
+ ei_close__(fd);
s = buf;
port = get16be(s);
diff --git a/lib/erl_interface/src/epmd/epmd_publish.c b/lib/erl_interface/src/epmd/epmd_publish.c
index 47d68a6db0..20b8e867e8 100644
--- a/lib/erl_interface/src/epmd/epmd_publish.c
+++ b/lib/erl_interface/src/epmd/epmd_publish.c
@@ -68,8 +68,10 @@ static int ei_epmd_r4_publish (int port, const char *alive, unsigned ms)
int nlen = strlen(alive);
int len = elen + nlen + 13; /* hard coded: be careful! */
int n;
- int res, creation;
-
+ int err, res, creation;
+ ssize_t dlen;
+ unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
+
if (len > sizeof(buf)-2)
{
erl_errno = ERANGE;
@@ -93,29 +95,39 @@ static int ei_epmd_r4_publish (int port, const char *alive, unsigned ms)
if ((fd = ei_epmd_connect_tmo(NULL,ms)) < 0) return fd;
- if ((res = ei_write_fill_t(fd, buf, len+2, ms)) != len+2) {
- closesocket(fd);
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- return -1;
+ dlen = (ssize_t) len+2;
+ err = ei_write_fill_t__(fd, buf, &dlen, tmo);
+ if (!err && dlen != (ssize_t) len + 2)
+ erl_errno = EIO;
+ if (err) {
+ ei_close__(fd);
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
}
EI_TRACE_CONN6("ei_epmd_r4_publish",
"-> ALIVE2_REQ alive=%s port=%d ntype=%d "
"proto=%d dist-high=%d dist-low=%d",
alive,port,'H',EI_MYPROTO,EI_DIST_HIGH,EI_DIST_LOW);
-
- if ((n = ei_read_fill_t(fd, buf, 4, ms)) != 4) {
+
+ dlen = (ssize_t) 4;
+ err = ei_read_fill_t__(fd, buf, &dlen, tmo);
+ n = (int) dlen;
+ if (!err && n != 4)
+ err = EIO;
+ if (err) {
EI_TRACE_ERR0("ei_epmd_r4_publish","<- CLOSE");
- closesocket(fd);
- erl_errno = (n == -2) ? ETIMEDOUT : EIO;
+ ei_close__(fd);
+ EI_CONN_SAVE_ERRNO__(err);
return -2; /* version mismatch */
}
+
/* Don't close fd here! It keeps us registered with epmd */
s = buf;
if (((res=get8(s)) != EI_EPMD_ALIVE2_RESP)) { /* response */
EI_TRACE_ERR1("ei_epmd_r4_publish","<- unknown (%d)",res);
EI_TRACE_ERR0("ei_epmd_r4_publish","-> CLOSE");
- closesocket(fd);
+ ei_close__(fd);
erl_errno = EIO;
return -1;
}
@@ -124,7 +136,7 @@ static int ei_epmd_r4_publish (int port, const char *alive, unsigned ms)
if (((res=get8(s)) != 0)) { /* 0 == success */
EI_TRACE_ERR1("ei_epmd_r4_publish"," result=%d (fail)",res);
- closesocket(fd);
+ ei_close__(fd);
erl_errno = EIO;
return -1;
}
diff --git a/lib/erl_interface/src/epmd/epmd_unpublish.c b/lib/erl_interface/src/epmd/epmd_unpublish.c
index 255d0ffb59..c112f74147 100644
--- a/lib/erl_interface/src/epmd/epmd_unpublish.c
+++ b/lib/erl_interface/src/epmd/epmd_unpublish.c
@@ -58,7 +58,9 @@ int ei_unpublish_tmo(const char *alive, unsigned ms)
char buf[EPMDBUF];
char *s = (char*)buf;
int len = 1 + strlen(alive);
- int fd, res;
+ int fd, err;
+ ssize_t dlen;
+ unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
if (len > sizeof(buf)-3) {
erl_errno = ERANGE;
@@ -72,20 +74,29 @@ int ei_unpublish_tmo(const char *alive, unsigned ms)
/* FIXME can't connect, return success?! At least commen whats up */
if ((fd = ei_epmd_connect_tmo(NULL,ms)) < 0) return fd;
- if ((res = ei_write_fill_t(fd, buf, len+2,ms)) != len+2) {
- closesocket(fd);
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- return -1;
+ dlen = (ssize_t) len+2;
+ err = ei_write_fill_t__(fd, buf, &dlen, tmo);
+ if (!err && dlen != (ssize_t) len + 2)
+ erl_errno = EIO;
+ if (err) {
+ ei_close__(fd);
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
}
EI_TRACE_CONN1("ei_unpublish_tmo","-> STOP %s",alive);
-
- if ((res = ei_read_fill_t(fd, buf, 7, ms)) != 7) {
- closesocket(fd);
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- return -1;
+
+ dlen = (ssize_t) 7;
+ err = ei_read_fill_t__(fd, buf, &dlen, tmo);
+ if (!err && dlen != (ssize_t) 7)
+ erl_errno = EIO;
+ if (err) {
+ ei_close__(fd);
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
}
- closesocket(fd);
+
+ ei_close__(fd);
buf[7]=(char)0; /* terminate the string */
if (!strcmp("STOPPED",(char *)buf)) {
diff --git a/lib/erl_interface/src/legacy/erl_connect.c b/lib/erl_interface/src/legacy/erl_connect.c
index 7ffd545d3e..e2fd4611c0 100644
--- a/lib/erl_interface/src/legacy/erl_connect.c
+++ b/lib/erl_interface/src/legacy/erl_connect.c
@@ -179,15 +179,13 @@ int erl_xconnect(Erl_IpAddr addr, char *alivename)
*
* API: erl_close_connection()
*
- * Close a connection. FIXME call ei_close_connection() later.
- *
* Returns 0 on success and -1 on failure.
*
***************************************************************************/
int erl_close_connection(int fd)
{
- return closesocket(fd);
+ return ei_close_connection(fd);
}
/*
@@ -220,7 +218,10 @@ int erl_reg_send(int fd, char *server_name, ETERM *msg)
ei_x_buff x;
int r;
- ei_x_new_with_version(&x);
+ if (ei_x_new_with_version(&x) < 0) {
+ erl_errno = ENOMEM;
+ return 0;
+ }
if (ei_x_encode_term(&x, msg) < 0) {
erl_errno = EINVAL;
r = 0;
diff --git a/lib/erl_interface/src/legacy/erl_eterm.c b/lib/erl_interface/src/legacy/erl_eterm.c
index 9ad92121f4..7ed2bdbc93 100644
--- a/lib/erl_interface/src/legacy/erl_eterm.c
+++ b/lib/erl_interface/src/legacy/erl_eterm.c
@@ -65,7 +65,7 @@ void erl_init(void *hp,long heap_size)
{
erl_init_malloc(hp, heap_size);
erl_init_marshal();
- ei_init_resolve();
+ (void) ei_init();
}
void erl_set_compat_rel(unsigned rel)
diff --git a/lib/erl_interface/src/misc/ei_init.c b/lib/erl_interface/src/misc/ei_init.c
new file mode 100644
index 0000000000..5357968657
--- /dev/null
+++ b/lib/erl_interface/src/misc/ei_init.c
@@ -0,0 +1,32 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2019. All 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 "ei.h"
+#include "ei_resolve.h"
+#include "ei_internal.h"
+
+int
+ei_init(void)
+{
+ int error = ei_init_connect();
+ if (error)
+ return error;
+ return ei_init_resolve();
+}
diff --git a/lib/erl_interface/src/misc/ei_internal.h b/lib/erl_interface/src/misc/ei_internal.h
index aa6aacd703..f28dd6d668 100644
--- a/lib/erl_interface/src/misc/ei_internal.h
+++ b/lib/erl_interface/src/misc/ei_internal.h
@@ -22,19 +22,20 @@
#ifndef _EI_INTERNAL_H
#define _EI_INTERNAL_H
+#ifdef EI_HIDE_REAL_ERRNO
+# define EI_CONN_SAVE_ERRNO__(E) \
+ ((E) == ETIMEDOUT ? (erl_errno = ETIMEDOUT) : (erl_errno = EIO))
+#else
+# define EI_CONN_SAVE_ERRNO__(E) \
+ (erl_errno = (E))
+#endif
+
/*
* Some useful stuff not to be exported to users.
*/
#ifdef __WIN32__
#define MAXPATHLEN 256
-#define writesocket(sock,buf,nbyte) send(sock,buf,nbyte,0)
-#define readsocket(sock,buf,nbyte) recv(sock,buf,nbyte,0)
-#else /* not __WIN32__ */
-#define writesocket write
-#define readsocket read
-#define closesocket close
-#define ioctlsocket ioctl
#endif
/*
@@ -152,7 +153,12 @@
extern int ei_tracelevel;
+int ei_init_connect(void);
+
void ei_trace_printf(const char *name, int level, const char *format, ...);
int ei_internal_use_r9_pids_ports(void);
+
+int ei_get_cbs_ctx__(ei_socket_callbacks **cbs, void **ctx, int fd);
+
#endif /* _EI_INTERNAL_H */
diff --git a/lib/erl_interface/src/misc/ei_portio.c b/lib/erl_interface/src/misc/ei_portio.c
index 8cd35bf2e5..bccc86c1b1 100644
--- a/lib/erl_interface/src/misc/ei_portio.c
+++ b/lib/erl_interface/src/misc/ei_portio.c
@@ -19,9 +19,13 @@
*
*/
+
+#include "eidef.h"
+
#ifdef __WIN32__
#include <winsock2.h>
#include <windows.h>
+#include <winbase.h>
#include <process.h>
#include <stdio.h>
#include <stdlib.h>
@@ -35,10 +39,6 @@ static unsigned long param_one = 1;
#define SET_BLOCKING(Sock) ioctlsocket((Sock),FIONBIO,&param_zero)
#define SET_NONBLOCKING(Sock) ioctlsocket((Sock),FIONBIO,&param_one)
-#define ERROR_WOULDBLOCK WSAEWOULDBLOCK
-#define ERROR_TIMEDOUT WSAETIMEDOUT
-#define ERROR_INPROGRESS WSAEINPROGRESS
-#define GET_SOCKET_ERROR() WSAGetLastError()
#define MEANS_SOCKET_ERROR(Ret) ((Ret == SOCKET_ERROR))
#define IS_INVALID_SOCKET(Sock) ((Sock) == INVALID_SOCKET)
@@ -50,125 +50,414 @@ static unsigned long param_one = 1;
#include <taskLib.h>
#include <inetLib.h>
#include <selectLib.h>
-#include <sys/types.h>
#include <ioLib.h>
#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <timers.h>
static unsigned long param_zero = 0;
static unsigned long param_one = 1;
#define SET_BLOCKING(Sock) ioctl((Sock),FIONBIO,(int)&param_zero)
#define SET_NONBLOCKING(Sock) ioctl((Sock),FIONBIO,(int)&param_one)
-#define ERROR_WOULDBLOCK EWOULDBLOCK
-#define ERROR_TIMEDOUT ETIMEDOUT
-#define ERROR_INPROGRESS EINPROGRESS
-#define GET_SOCKET_ERROR() (errno)
#define MEANS_SOCKET_ERROR(Ret) ((Ret) == ERROR)
#define IS_INVALID_SOCKET(Sock) ((Sock) < 0)
#else /* other unix */
#include <stdlib.h>
-#include <sys/types.h>
#include <sys/socket.h>
-#include <sys/uio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
-#ifndef EWOULDBLOCK
-#define ERROR_WOULDBLOCK EAGAIN
-#else
-#define ERROR_WOULDBLOCK EWOULDBLOCK
-#endif
#define SET_BLOCKING(fd) fcntl((fd), F_SETFL, \
fcntl((fd), F_GETFL, 0) & ~O_NONBLOCK)
#define SET_NONBLOCKING(fd) fcntl((fd), F_SETFL, \
fcntl((fd), F_GETFL, 0) | O_NONBLOCK)
-#define ERROR_TIMEDOUT ETIMEDOUT
-#define ERROR_INPROGRESS EINPROGRESS
-#define GET_SOCKET_ERROR() (errno)
#define MEANS_SOCKET_ERROR(Ret) ((Ret) < 0)
#define IS_INVALID_SOCKET(Sock) ((Sock) < 0)
#endif
/* common includes */
-#include "eidef.h"
+#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "ei_portio.h"
-#include "ei_internal.h"
-
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#else
#include <time.h>
#endif
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+#include "ei_portio.h"
+#include "ei_internal.h"
+
+#ifdef __WIN32__
-#ifdef HAVE_WRITEV
-static int ei_writev_t(int fd, struct iovec *iov, int iovcnt, unsigned ms)
+#define writesocket(sock,buf,nbyte) send(sock,buf,nbyte,0)
+#define readsocket(sock,buf,nbyte) recv(sock,buf,nbyte,0)
+
+static int get_error(void)
{
- int res;
- if (ms != 0) {
- fd_set writemask;
- struct timeval tv;
- tv.tv_sec = (time_t) (ms / 1000U);
- ms %= 1000U;
- tv.tv_usec = (time_t) (ms * 1000U);
- FD_ZERO(&writemask);
- FD_SET(fd,&writemask);
- switch (select(fd+1, NULL, &writemask, NULL, &tv)) {
- case -1 :
- return -1; /* i/o error */
- case 0:
- return -2; /* timeout */
- default:
- if (!FD_ISSET(fd, &writemask)) {
- return -1; /* Other error */
- }
- }
+ switch (WSAGetLastError()) {
+ case WSAEWOULDBLOCK: return EWOULDBLOCK;
+ case WSAETIMEDOUT: return ETIMEDOUT;
+ case WSAEINPROGRESS: return EINPROGRESS;
+ case WSA_NOT_ENOUGH_MEMORY: return ENOMEM;
+ case WSA_INVALID_PARAMETER: return EINVAL;
+ case WSAEBADF: return EBADF;
+ case WSAEINVAL: return EINVAL;
+ case WSAEADDRINUSE: return EADDRINUSE;
+ case WSAENETUNREACH: return ENETUNREACH;
+ case WSAECONNABORTED: return ECONNABORTED;
+ case WSAECONNRESET: return ECONNRESET;
+ case WSAECONNREFUSED: return ECONNREFUSED;
+ case WSAEHOSTUNREACH: return EHOSTUNREACH;
+ case WSAEMFILE: return EMFILE;
+ case WSAEALREADY: return EALREADY;
+ default: return EIO;
}
+}
+
+#else /* not __WIN32__ */
+
+#define writesocket write
+#define readsocket read
+#define closesocket close
+#define ioctlsocket ioctl
+
+static int get_error(void)
+{
+ int err = errno;
+ if (err == 0)
+ return EIO; /* Make sure never to return 0 as error code... */
+ return err;
+}
+
+#endif
+
+int ei_plugin_socket_impl__ = 0;
+
+/*
+ * Callbacks for communication over TCP/IPv4
+ */
+
+static int tcp_get_fd(void *ctx, int *fd)
+{
+ return EI_DFLT_CTX_TO_FD__(ctx, fd);
+}
+
+static int tcp_hs_packet_header_size(void *ctx, int *sz)
+{
+ int fd;
+ *sz = 2;
+ return EI_DFLT_CTX_TO_FD__(ctx, &fd);
+}
+
+static int tcp_handshake_complete(void *ctx)
+{
+ int res, fd, one = 1;
+
+ res = EI_DFLT_CTX_TO_FD__(ctx, &fd);
+ if (res)
+ return res;
+
+ res = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&one, sizeof(one));
+ if (MEANS_SOCKET_ERROR(res))
+ return get_error();
+
+ res = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char *)&one, sizeof(one));
+ if (MEANS_SOCKET_ERROR(res))
+ return get_error();
+
+ return 0;
+}
+
+static int tcp_socket(void **ctx, void *setup_ctx)
+{
+ int fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (MEANS_SOCKET_ERROR(fd))
+ return get_error();
+
+ *ctx = EI_FD_AS_CTX__(fd);
+ return 0;
+}
+
+static int tcp_close(void *ctx)
+{
+ int fd, res;
+
+ res = EI_DFLT_CTX_TO_FD__(ctx, &fd);
+ if (res)
+ return res;
+
+ res = closesocket(fd);
+ if (MEANS_SOCKET_ERROR(res))
+ return get_error();
+
+ return 0;
+}
+
+static int tcp_listen(void *ctx, void *addr, int *len, int backlog)
+{
+ int res, fd;
+ socklen_t sz = (socklen_t) *len;
+ int on = 1;
+
+ res = EI_DFLT_CTX_TO_FD__(ctx, &fd);
+ if (res)
+ return res;
+
+ res = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on));
+ if (MEANS_SOCKET_ERROR(res))
+ return get_error();
+
+ res = bind(fd, (struct sockaddr *) addr, sz);
+ if (MEANS_SOCKET_ERROR(res))
+ return get_error();
+
+ res = getsockname(fd, (struct sockaddr *) addr, (socklen_t *) &sz);
+ if (MEANS_SOCKET_ERROR(res))
+ return get_error();
+ *len = (int) sz;
+
+ res = listen(fd, backlog);
+ if (MEANS_SOCKET_ERROR(res))
+ return get_error();
+
+ return 0;
+}
+
+static int tcp_accept(void **ctx, void *addr, int *len, unsigned unused)
+{
+ int fd, res;
+ socklen_t addr_len = (socklen_t) *len;
+
+ if (!ctx)
+ return EINVAL;
+
+ res = EI_DFLT_CTX_TO_FD__(*ctx, &fd);
+ if (res)
+ return res;
+
+ res = accept(fd, (struct sockaddr*) addr, &addr_len);
+ if (MEANS_SOCKET_ERROR(res))
+ return get_error();
+
+ *len = (int) addr_len;
+
+ *ctx = EI_FD_AS_CTX__(res);
+ return 0;
+}
+
+static int tcp_connect(void *ctx, void *addr, int len, unsigned unused)
+{
+ int res, fd;
+
+ res = EI_DFLT_CTX_TO_FD__(ctx, &fd);
+ if (res)
+ return res;
+
+ res = connect(fd, (struct sockaddr *) addr, len);
+ if (MEANS_SOCKET_ERROR(res))
+ return get_error();
+
+ return 0;
+}
+
+#if defined(EI_HAVE_STRUCT_IOVEC__) && defined(HAVE_WRITEV)
+
+static int tcp_writev(void *ctx, const void *viov, int iovcnt, ssize_t *len, unsigned unused)
+{
+ const struct iovec *iov = (const struct iovec *) viov;
+ int fd, error;
+ ssize_t res;
+
+ error = EI_DFLT_CTX_TO_FD__(ctx, &fd);
+ if (error)
+ return error;
+
res = writev(fd, iov, iovcnt);
- return (res < 0) ? -1 : res;
+ if (MEANS_SOCKET_ERROR(res))
+ return get_error();
+ *len = res;
+ return 0;
+}
+
+#endif
+
+static int tcp_write(void *ctx, const char* buf, ssize_t *len, unsigned unused)
+{
+ int error, fd;
+ ssize_t res;
+
+ error = EI_DFLT_CTX_TO_FD__(ctx, &fd);
+ if (error)
+ return error;
+
+ res = writesocket(fd, buf, *len);
+ if (MEANS_SOCKET_ERROR(res))
+ return get_error();
+ *len = res;
+ return 0;
+}
+
+static int tcp_read(void *ctx, char* buf, ssize_t *len, unsigned unused)
+{
+ int error, fd;
+ ssize_t res;
+
+ error = EI_DFLT_CTX_TO_FD__(ctx, &fd);
+ if (error)
+ return error;
+
+ res = readsocket(fd, buf, *len);
+ if (MEANS_SOCKET_ERROR(res))
+ return get_error();
+ *len = res;
+ return 0;
+}
+
+ei_socket_callbacks ei_default_socket_callbacks = {
+ 0, /* flags */
+ tcp_socket,
+ tcp_close,
+ tcp_listen,
+ tcp_accept,
+ tcp_connect,
+#if defined(EI_HAVE_STRUCT_IOVEC__) && defined(HAVE_WRITEV)
+ tcp_writev,
+#else
+ NULL,
+#endif
+ tcp_write,
+ tcp_read,
+
+ tcp_hs_packet_header_size,
+ tcp_handshake_complete,
+ tcp_handshake_complete,
+ tcp_get_fd
+
+};
+
+
+/*
+ *
+ */
+
+#if defined(EI_HAVE_STRUCT_IOVEC__)
+
+int ei_socket_callbacks_have_writev__(ei_socket_callbacks *cbs)
+{
+ return !!cbs->writev;
}
-int ei_writev_fill_t(int fd, const struct iovec *iov, int iovcnt, unsigned ms)
+static int writev_ctx_t__(ei_socket_callbacks *cbs, void *ctx,
+ const struct iovec *iov, int iovcnt,
+ ssize_t *len,
+ unsigned ms)
{
- int i;
- int done;
+ int error;
+
+ if (!(cbs->flags & EI_SCLBK_FLG_FULL_IMPL) && ms != EI_SCLBK_INF_TMO) {
+ int fd;
+
+ error = EI_GET_FD__(cbs, ctx, &fd);
+ if (error)
+ return error;
+
+ do {
+ fd_set writemask;
+ struct timeval tv;
+
+ tv.tv_sec = (time_t) (ms / 1000U);
+ ms %= 1000U;
+ tv.tv_usec = (time_t) (ms * 1000U);
+ FD_ZERO(&writemask);
+ FD_SET(fd,&writemask);
+ switch (select(fd+1, NULL, &writemask, NULL, &tv)) {
+ case -1 :
+ error = get_error();
+ if (error != EINTR)
+ return error;
+ break;
+ case 0:
+ return ETIMEDOUT; /* timeout */
+ default:
+ if (!FD_ISSET(fd, &writemask)) {
+ return EIO; /* Other error */
+ }
+ error = 0;
+ break;
+ }
+ } while (error == EINTR);
+ }
+ do {
+ error = cbs->writev(ctx, (const void *) iov, iovcnt, len, ms);
+ } while (error == EINTR);
+ return error;
+}
+
+int ei_writev_fill_ctx_t__(ei_socket_callbacks *cbs, void *ctx,
+ const struct iovec *iov, int iovcnt,
+ ssize_t *len,
+ unsigned ms)
+{
+ ssize_t i, done, sum;
struct iovec *iov_base = NULL;
struct iovec *current_iov;
int current_iovcnt;
- int sum;
+ int fd, error;
+ int basic;
+
+ if (!cbs->writev)
+ return ENOTSUP;
+
+ error = EI_GET_FD__(cbs, ctx, &fd);
+ if (error)
+ return error;
+ basic = !(cbs->flags & EI_SCLBK_FLG_FULL_IMPL);
+
for (sum = 0, i = 0; i < iovcnt; ++i) {
sum += iov[i].iov_len;
}
- if (ms != 0U) {
+ if (basic && ms != 0U) {
SET_NONBLOCKING(fd);
}
current_iovcnt = iovcnt;
current_iov = (struct iovec *) iov;
done = 0;
for (;;) {
- i = ei_writev_t(fd, current_iov, current_iovcnt, ms);
- if (i <= 0) { /* ei_writev_t should always return at least 1 */
+
+ error = writev_ctx_t__(cbs, ctx, current_iov, current_iovcnt, &i, ms);
+ if (error) {
+ *len = done;
if (ms != 0U) {
SET_BLOCKING(fd);
}
if (iov_base != NULL) {
free(iov_base);
}
- return (i);
- }
+ return error;
+ }
done += i;
if (done < sum) {
if (iov_base == NULL) {
iov_base = malloc(sizeof(struct iovec) * iovcnt);
if (iov_base == NULL) {
- return -1;
+ *len = done;
+ return ENOMEM;
}
memcpy(iov_base, iov, sizeof(struct iovec) * iovcnt);
current_iov = iov_base;
@@ -189,195 +478,383 @@ int ei_writev_fill_t(int fd, const struct iovec *iov, int iovcnt, unsigned
break;
}
}
- if (ms != 0U) {
+ if (basic && ms != 0U) {
SET_BLOCKING(fd);
}
if (iov_base != NULL) {
free(iov_base);
}
- return (sum);
+ *len = done;
+ return 0;
}
+#endif /* defined(EI_HAVE_STRUCT_IOVEC__) */
-#endif
-
-int ei_connect_t(int fd, void *sinp, int sin_siz, unsigned ms)
+int ei_socket_ctx__(ei_socket_callbacks *cbs, void **ctx, void *setup_ctx)
{
int res;
- int error;
- int s_res;
- struct timeval tv;
- fd_set writefds;
- fd_set exceptfds;
-
- if (ms == 0) {
- res = connect(fd, sinp, sin_siz);
- return (res < 0) ? -1 : res;
- } else {
- SET_NONBLOCKING(fd);
- res = connect(fd, sinp, sin_siz);
- error = GET_SOCKET_ERROR();
- SET_BLOCKING(fd);
- if (!MEANS_SOCKET_ERROR(res)) {
- return (res < 0) ? -1 : res;
- } else {
- if (error != ERROR_WOULDBLOCK &&
- error != ERROR_INPROGRESS) {
- return -1;
- } else {
- tv.tv_sec = (long) (ms/1000U);
- ms %= 1000U;
- tv.tv_usec = (long) (ms * 1000U);
- FD_ZERO(&writefds);
- FD_SET(fd,&writefds);
- FD_ZERO(&exceptfds);
- FD_SET(fd,&exceptfds);
- s_res = select(fd + 1, NULL, &writefds, &exceptfds, &tv);
- switch (s_res) {
- case 0:
- return -2;
- case 1:
- if (FD_ISSET(fd, &exceptfds)) {
- return -1;
- } else {
- return 0; /* Connect completed */
- }
- default:
- return -1;
- }
- }
- }
- }
+
+ do {
+ res = cbs->socket(ctx, setup_ctx);
+ } while (res == EINTR);
+
+ return res;
}
-int ei_accept_t(int fd, void *addr, void *addrlen, unsigned ms)
+int ei_close_ctx__(ei_socket_callbacks *cbs, void *ctx)
{
- int res;
- if (ms != 0) {
- fd_set readmask;
- struct timeval tv;
- tv.tv_sec = (time_t) (ms / 1000U);
- ms %= 1000U;
- tv.tv_usec = (time_t) (ms * 1000U);
- FD_ZERO(&readmask);
- FD_SET(fd,&readmask);
- switch (select(fd+1, &readmask, NULL, NULL, &tv)) {
- case -1 :
- return -1; /* i/o error */
- case 0:
- return -2; /* timeout */
- default:
- if (!FD_ISSET(fd, &readmask)) {
- return -1; /* Other error */
- }
- }
- }
- res = (int) accept(fd,addr,addrlen);
- return (res < 0) ? -1 : res;
+ return cbs->close(ctx);
}
+
+int ei_connect_ctx_t__(ei_socket_callbacks *cbs, void *ctx,
+ void *addr, int len, unsigned ms)
+{
+ int res, fd;
+
+ if ((cbs->flags & EI_SCLBK_FLG_FULL_IMPL) || ms == EI_SCLBK_INF_TMO) {
+ do {
+ res = cbs->connect(ctx, addr, len, ms);
+ } while (res == EINTR);
+ return res;
+ }
+
+ res = EI_GET_FD__(cbs, ctx, &fd);
+ if (res)
+ return res;
+ SET_NONBLOCKING(fd);
+ do {
+ res = cbs->connect(ctx, addr, len, 0);
+ } while (res == EINTR);
+ SET_BLOCKING(fd);
+ switch (res) {
+ case EINPROGRESS:
+ case EAGAIN:
+#ifdef EWOULDBLOCK
+#if EWOULDBLOCK != EAGAIN
+ case EWOULDBLOCK:
+#endif
+#endif
+ break;
+ default:
+ return res;
+ }
-static int ei_read_t(int fd, char* buf, int len, unsigned ms)
+ while (1) {
+ struct timeval tv;
+ fd_set writefds;
+ fd_set exceptfds;
+
+ tv.tv_sec = (long) (ms/1000U);
+ ms %= 1000U;
+ tv.tv_usec = (long) (ms * 1000U);
+ FD_ZERO(&writefds);
+ FD_SET(fd,&writefds);
+ FD_ZERO(&exceptfds);
+ FD_SET(fd,&exceptfds);
+ res = select(fd + 1, NULL, &writefds, &exceptfds, &tv);
+ switch (res) {
+ case -1:
+ res = get_error();
+ if (res != EINTR)
+ return res;
+ break;
+ case 0:
+ return ETIMEDOUT;
+ case 1:
+ if (!FD_ISSET(fd, &exceptfds))
+ return 0; /* Connect completed */
+ /* fall through... */
+ default:
+ return EIO;
+ }
+ }
+}
+
+int ei_listen_ctx__(ei_socket_callbacks *cbs, void *ctx,
+ void *adr, int *len, int backlog)
{
int res;
- if (ms != 0) {
- fd_set readmask;
- struct timeval tv;
- tv.tv_sec = (time_t) (ms / 1000U);
- ms %= 1000U;
- tv.tv_usec = (time_t) (ms * 1000U);
- FD_ZERO(&readmask);
- FD_SET(fd,&readmask);
- switch (select(fd+1, &readmask, NULL, NULL, &tv)) {
- case -1 :
- return -1; /* i/o error */
- case 0:
- return -2; /* timeout */
- default:
- if (!FD_ISSET(fd, &readmask)) {
- return -1; /* Other error */
- }
- }
+
+ do {
+ res = cbs->listen(ctx, adr, len, backlog);
+ } while (res == EINTR);
+ return res;
+}
+
+int ei_accept_ctx_t__(ei_socket_callbacks *cbs, void **ctx,
+ void *addr, int *len, unsigned ms)
+{
+ int error;
+
+ if (!(cbs->flags & EI_SCLBK_FLG_FULL_IMPL) && ms != EI_SCLBK_INF_TMO) {
+ int fd;
+
+ error = EI_GET_FD__(cbs, *ctx, &fd);
+ if (error)
+ return error;
+
+ do {
+ fd_set readmask;
+ struct timeval tv;
+
+ tv.tv_sec = (time_t) (ms / 1000U);
+ ms %= 1000U;
+ tv.tv_usec = (time_t) (ms * 1000U);
+ FD_ZERO(&readmask);
+ FD_SET(fd,&readmask);
+ switch (select(fd+1, &readmask, NULL, NULL, &tv)) {
+ case -1 :
+ error = get_error();
+ if (error != EINTR)
+ return error;
+ break;
+ case 0:
+ return ETIMEDOUT; /* timeout */
+ default:
+ if (!FD_ISSET(fd, &readmask)) {
+ return EIO; /* Other error */
+ }
+ error = 0;
+ break;
+ }
+ } while (error == EINTR);
}
- res = readsocket(fd, buf, len);
- return (res < 0) ? -1 : res;
+ do {
+ error = cbs->accept(ctx, addr, len, ms);
+ } while (error == EINTR);
+ return error;
}
-static int ei_write_t(int fd, const char* buf, int len, unsigned ms)
+static int read_ctx_t__(ei_socket_callbacks *cbs, void *ctx,
+ char* buf, ssize_t *len, unsigned ms)
{
- int res;
- if (ms != 0) {
- fd_set writemask;
- struct timeval tv;
- tv.tv_sec = (time_t) (ms / 1000U);
- ms %= 1000U;
- tv.tv_usec = (time_t) (ms * 1000U);
- FD_ZERO(&writemask);
- FD_SET(fd,&writemask);
- switch (select(fd+1, NULL, &writemask, NULL, &tv)) {
- case -1 :
- return -1; /* i/o error */
- case 0:
- return -2; /* timeout */
- default:
- if (!FD_ISSET(fd, &writemask)) {
- return -1; /* Other error */
- }
- }
+ int error;
+
+ if (!(cbs->flags & EI_SCLBK_FLG_FULL_IMPL) && ms != EI_SCLBK_INF_TMO) {
+ int fd;
+
+ error = EI_GET_FD__(cbs, ctx, &fd);
+ if (error)
+ return error;
+
+ do {
+ fd_set readmask;
+ struct timeval tv;
+
+ tv.tv_sec = (time_t) (ms / 1000U);
+ ms %= 1000U;
+ tv.tv_usec = (time_t) (ms * 1000U);
+ FD_ZERO(&readmask);
+ FD_SET(fd,&readmask);
+ switch (select(fd+1, &readmask, NULL, NULL, &tv)) {
+ case -1 :
+ error = get_error();
+ if (error != EINTR)
+ return error;
+ break;
+ case 0:
+ return ETIMEDOUT; /* timeout */
+ default:
+ if (!FD_ISSET(fd, &readmask)) {
+ return EIO; /* Other error */
+ }
+ error = 0;
+ break;
+ }
+ } while (error == EINTR);
+ }
+ do {
+ error = cbs->read(ctx, buf, len, ms);
+ } while (error == EINTR);
+ return error;
+}
+
+static int write_ctx_t__(ei_socket_callbacks *cbs, void *ctx, const char* buf, ssize_t *len, unsigned ms)
+{
+ int error;
+
+ if (!(cbs->flags & EI_SCLBK_FLG_FULL_IMPL) && ms != EI_SCLBK_INF_TMO) {
+ int fd;
+
+ error = EI_GET_FD__(cbs, ctx, &fd);
+ if (error)
+ return error;
+
+ do {
+ fd_set writemask;
+ struct timeval tv;
+
+ tv.tv_sec = (time_t) (ms / 1000U);
+ ms %= 1000U;
+ tv.tv_usec = (time_t) (ms * 1000U);
+ FD_ZERO(&writemask);
+ FD_SET(fd,&writemask);
+ switch (select(fd+1, NULL, &writemask, NULL, &tv)) {
+ case -1 :
+ error = get_error();
+ if (error != EINTR)
+ return error;
+ break;
+ case 0:
+ return ETIMEDOUT; /* timeout */
+ default:
+ if (!FD_ISSET(fd, &writemask)) {
+ return EIO; /* Other error */
+ }
+ error = 0;
+ break;
+ }
+ } while (error == EINTR);
}
- res = writesocket(fd, buf, len);
- return (res < 0) ? -1 : res;
+ do {
+ error = cbs->write(ctx, buf, len, ms);
+ } while (error == EINTR);
+ return error;
}
/*
* Fill buffer, return buffer length, 0 for EOF, < 0 (and sets errno)
* for error. */
-int ei_read_fill_t(int fd, char* buf, int len, unsigned ms)
+int ei_read_fill_ctx_t__(ei_socket_callbacks *cbs, void *ctx, char* buf, ssize_t *len, unsigned ms)
{
- int i,got=0;
+ ssize_t got = 0;
+ ssize_t want = *len;
do {
- i = ei_read_t(fd, buf+got, len-got, ms);
- if (i <= 0)
- return (i);
- got += i;
- } while (got < len);
- return (len);
-
+ ssize_t read_len = want-got;
+ int error;
+
+ do {
+ error = read_ctx_t__(cbs, ctx, buf+got, &read_len, ms);
+ } while (error == EINTR);
+ if (error)
+ return error;
+ if (read_len == 0) {
+ *len = got;
+ return 0;
+ }
+ got += read_len;
+ } while (got < want);
+
+ *len = got;
+ return 0;
} /* read_fill */
-int ei_read_fill(int fd, char* buf, int len)
+int ei_read_fill_ctx__(ei_socket_callbacks *cbs, void *ctx, char* buf, ssize_t *len)
{
- return ei_read_fill_t(fd, buf, len, 0);
+ return ei_read_fill_ctx_t__(cbs, ctx, buf, len, 0);
}
/* write entire buffer on fd or fail (setting errno)
*/
-int ei_write_fill_t(int fd, const char *buf, int len, unsigned ms)
+int ei_write_fill_ctx_t__(ei_socket_callbacks *cbs, void *ctx, const char *buf, ssize_t *len, unsigned ms)
{
- int i,done=0;
- if (ms != 0U) {
+ ssize_t tot = *len, done = 0;
+ int error, fd = -1, basic = !(cbs->flags & EI_SCLBK_FLG_FULL_IMPL);
+
+ if (basic && ms != 0U) {
+ error = EI_GET_FD__(cbs, ctx, &fd);
+ if (error)
+ return error;
SET_NONBLOCKING(fd);
}
do {
- i = ei_write_t(fd, buf+done, len-done, ms);
- if (i <= 0) {
- if (ms != 0U) {
+ ssize_t write_len = tot-done;
+ error = write_ctx_t__(cbs, ctx, buf+done, &write_len, ms);
+ if (error) {
+ *len = done;
+ if (basic && ms != 0U) {
SET_BLOCKING(fd);
}
- return (i);
+ return error;
}
- done += i;
- } while (done < len);
- if (ms != 0U) {
+ done += write_len;
+ } while (done < tot);
+ if (basic && ms != 0U) {
SET_BLOCKING(fd);
}
- return (len);
+ *len = done;
+ return 0;
+}
+
+int ei_write_fill_ctx__(ei_socket_callbacks *cbs, void *ctx, const char *buf, ssize_t *len)
+{
+ return ei_write_fill_ctx_t__(cbs, ctx, buf, len, 0);
+}
+
+/*
+ * Internal API for TCP/IPv4
+ */
+
+int ei_connect_t__(int fd, void *addr, int len, unsigned ms)
+{
+ return ei_connect_ctx_t__(&ei_default_socket_callbacks, EI_FD_AS_CTX__(fd),
+ addr, len, ms);
}
-int ei_write_fill(int fd, const char *buf, int len)
+int ei_socket__(int *fd)
{
- return ei_write_fill_t(fd, buf, len, 0);
+ void *ctx;
+ int error = ei_socket_ctx__(&ei_default_socket_callbacks, &ctx, NULL);
+ if (error)
+ return error;
+ return EI_GET_FD__(&ei_default_socket_callbacks, ctx, fd);
}
+int ei_close__(int fd)
+{
+ return ei_close_ctx__(&ei_default_socket_callbacks, EI_FD_AS_CTX__(fd));
+}
+
+int ei_listen__(int fd, void *adr, int *len, int backlog)
+{
+ return ei_listen_ctx__(&ei_default_socket_callbacks, EI_FD_AS_CTX__(fd),
+ adr, len, backlog);
+}
+
+int ei_accept_t__(int *fd, void *addr, int *len, unsigned ms)
+{
+ void *ctx = EI_FD_AS_CTX__(*fd);
+ int error = ei_accept_ctx_t__(&ei_default_socket_callbacks, &ctx,
+ addr, len, ms);
+ if (error)
+ return error;
+ return EI_GET_FD__(&ei_default_socket_callbacks, ctx, fd);
+}
+
+int ei_read_fill_t__(int fd, char* buf, ssize_t *len, unsigned ms)
+{
+ return ei_read_fill_ctx_t__(&ei_default_socket_callbacks, EI_FD_AS_CTX__(fd),
+ buf, len, ms);
+}
+
+int ei_read_fill__(int fd, char* buf, ssize_t *len)
+{
+ return ei_read_fill_ctx_t__(&ei_default_socket_callbacks, EI_FD_AS_CTX__(fd),
+ buf, len, 0);
+}
+
+int ei_write_fill_t__(int fd, const char *buf, ssize_t *len, unsigned ms)
+{
+ return ei_write_fill_ctx_t__(&ei_default_socket_callbacks, EI_FD_AS_CTX__(fd),
+ buf, len, ms);
+}
+
+int ei_write_fill__(int fd, const char *buf, ssize_t *len)
+{
+ return ei_write_fill_ctx_t__(&ei_default_socket_callbacks, EI_FD_AS_CTX__(fd),
+ buf, len, 0);
+}
+
+#if defined(EI_HAVE_STRUCT_IOVEC__) && defined(HAVE_WRITEV)
+
+int ei_writev_fill_t__(int fd, const struct iovec *iov, int iovcnt, ssize_t *len, unsigned ms)
+{
+ return ei_writev_fill_ctx_t__(&ei_default_socket_callbacks, EI_FD_AS_CTX__(fd),
+ iov, iovcnt, len, ms);
+}
+
+#endif
+
diff --git a/lib/erl_interface/src/misc/ei_portio.h b/lib/erl_interface/src/misc/ei_portio.h
index bded811a35..a84b5ca09c 100644
--- a/lib/erl_interface/src/misc/ei_portio.h
+++ b/lib/erl_interface/src/misc/ei_portio.h
@@ -21,21 +21,94 @@
*/
#ifndef _EI_PORTIO_H
#define _EI_PORTIO_H
-#if !defined(__WIN32__) && !defined(VXWORKS)
-#ifdef HAVE_WRITEV
+
+#undef EI_HAVE_STRUCT_IOVEC__
+#if !defined(__WIN32__) && !defined(VXWORKS) && defined(HAVE_SYS_UIO_H)
/* Declaration of struct iovec *iov should be visible in this scope. */
-#include <sys/uio.h>
+# include <sys/uio.h>
+# define EI_HAVE_STRUCT_IOVEC__
#endif
+
+/*
+ * Internal API. Should not be used outside of the erl_interface application...
+ */
+
+int ei_socket_ctx__(ei_socket_callbacks *cbs, void **ctx, void *setup);
+int ei_close_ctx__(ei_socket_callbacks *cbs, void *ctx);
+int ei_listen_ctx__(ei_socket_callbacks *cbs, void *ctx, void *adr, int *len, int backlog);
+int ei_accept_ctx_t__(ei_socket_callbacks *cbs, void **ctx, void *addr, int *len, unsigned ms);
+int ei_connect_ctx_t__(ei_socket_callbacks *cbs, void *ctx, void *addr, int len, unsigned ms);
+int ei_read_fill_ctx__(ei_socket_callbacks *cbs, void *ctx, char* buf, ssize_t *len);
+int ei_write_fill_ctx__(ei_socket_callbacks *cbs, void *ctx, const char *buf, ssize_t *len);
+int ei_read_fill_ctx_t__(ei_socket_callbacks *cbs, void *ctx, char* buf, ssize_t *len, unsigned ms);
+int ei_write_fill_ctx_t__(ei_socket_callbacks *cbs, void *ctx, const char *buf, ssize_t *len, unsigned ms);
+#if defined(EI_HAVE_STRUCT_IOVEC__)
+int ei_writev_fill_ctx_t__(ei_socket_callbacks *cbs, void *ctx, const struct iovec *iov, int iovcnt, ssize_t *len, unsigned ms);
+int ei_socket_callbacks_have_writev__(ei_socket_callbacks *cbs);
#endif
-int ei_accept_t(int fd, void *addr, void *addrlen, unsigned ms);
-int ei_connect_t(int fd, void *sinp, int sin_siz, unsigned ms);
-int ei_read_fill(int fd, char* buf, int len);
-int ei_write_fill(int fd, const char *buf, int len);
-int ei_read_fill_t(int fd, char* buf, int len, unsigned ms);
-int ei_write_fill_t(int fd, const char *buf, int len, unsigned ms);
-#ifdef HAVE_WRITEV
-int ei_writev_fill_t(int fd, const struct iovec *iov, int iovcnt, unsigned ms);
+ei_socket_callbacks ei_default_socket_callbacks;
+
+#define EI_FD_AS_CTX__(FD) \
+ ((void *) (long) (FD))
+
+#define EI_DFLT_CTX_TO_FD__(CTX, FD) \
+ ((int) (long) (CTX) < 0 \
+ ? EBADF \
+ : (*(FD) = (int) (long) (CTX), 0))
+
+#define EI_GET_FD__(CBS, CTX, FD) \
+ ((CBS) == &ei_default_socket_callbacks \
+ ? EI_DFLT_CTX_TO_FD__((CTX), FD) \
+ : (CBS)->get_fd((CTX), (FD)))
+
+extern int ei_plugin_socket_impl__;
+
+#if !defined(_REENTRANT)
+
+#define EI_HAVE_PLUGIN_SOCKET_IMPL__ \
+ ei_plugin_socket_impl__
+#define EI_SET_HAVE_PLUGIN_SOCKET_IMPL__ \
+ ei_plugin_socket_impl__ = 1
+
+#elif ((ETHR_HAVE___atomic_load_n & SIZEOF_INT) \
+ && (ETHR_HAVE___atomic_store_n & SIZEOF_INT))
+
+#define EI_HAVE_PLUGIN_SOCKET_IMPL__ \
+ __atomic_load_n(&ei_plugin_socket_impl__, __ATOMIC_ACQUIRE)
+#define EI_SET_HAVE_PLUGIN_SOCKET_IMPL__ \
+ __atomic_store_n(&ei_plugin_socket_impl__, 1, __ATOMIC_RELEASE)
+
+#else
+
+/* No gcc atomics; always lookup using ei_get_cbs_ctx()... */
+#define EI_HAVE_PLUGIN_SOCKET_IMPL__ 0
+#define EI_SET_HAVE_PLUGIN_SOCKET_IMPL__ (void) 0
+
+#endif
+
+#define EI_GET_CBS_CTX__(CBS, CTX, FD) \
+ (EI_HAVE_PLUGIN_SOCKET_IMPL__ \
+ ? ei_get_cbs_ctx__((CBS), (CTX), (FD)) \
+ : ((FD) < 0 \
+ ? EBADF \
+ : (*(CBS) = &ei_default_socket_callbacks, \
+ *(CTX) = EI_FD_AS_CTX__((FD)), \
+ 0)))
+/*
+ * The following uses our own TCP/IPv4 socket implementation...
+ */
+int ei_socket__(int *fd);
+int ei_close__(int fd);
+int ei_listen__(int fd, void *adr, int *len, int backlog);
+int ei_accept_t__(int *fd, void *addr, int *len, unsigned ms);
+int ei_connect_t__(int fd, void *addr, int len, unsigned ms);
+int ei_read_fill__(int fd, char* buf, ssize_t *len);
+int ei_write_fill__(int fd, const char *buf, ssize_t *len);
+int ei_read_fill_t__(int fd, char* buf, ssize_t *len, unsigned ms);
+int ei_write_fill_t__(int fd, const char *buf, ssize_t *len, unsigned ms);
+#if defined(EI_HAVE_STRUCT_IOVEC__) && defined(HAVE_WRITEV)
+int ei_writev_fill_t__(int fd, const struct iovec *iov, int iovcnt, ssize_t *len, unsigned ms);
#endif
#endif /* _EI_PORTIO_H */
diff --git a/lib/erl_interface/src/misc/ei_pthreads.c b/lib/erl_interface/src/misc/ei_pthreads.c
index ec1c8d956f..c6d07a9a0a 100644
--- a/lib/erl_interface/src/misc/ei_pthreads.c
+++ b/lib/erl_interface/src/misc/ei_pthreads.c
@@ -78,7 +78,7 @@ static void tls_init_once(void)
errno_tls_index = TlsAlloc();
if (errno_tls_index == TLS_OUT_OF_INDEXES) {
fprintf(stderr,
- "FATAL ERROR: can not allocate TLS index for "
+ "FATAL ERROR: cannot allocate TLS index for "
"erl_errno (error code = %d)!\n",GetLastError());
exit(1);
}
diff --git a/lib/erl_interface/src/misc/eidef.h b/lib/erl_interface/src/misc/eidef.h
index f38824d826..083814c6e9 100644
--- a/lib/erl_interface/src/misc/eidef.h
+++ b/lib/erl_interface/src/misc/eidef.h
@@ -25,6 +25,7 @@
/* Common definitions used in ei user interface */
+#define EI_INCLUDED_CONFIG_H__
#include "config.h" /* Central include of config.h */
/* vxWorks.h needs to be before stddef.h */
diff --git a/lib/erl_interface/src/not_used/send_link.c b/lib/erl_interface/src/not_used/send_link.c
index 7be476fd93..38fae27df4 100644
--- a/lib/erl_interface/src/not_used/send_link.c
+++ b/lib/erl_interface/src/not_used/send_link.c
@@ -50,6 +50,7 @@ static int link_unlink(int fd, const erlang_pid *from, const erlang_pid *to,
char *s;
int index = 0;
int n;
+ unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
index = 5; /* max sizes: */
ei_encode_version(msgbuf,&index); /* 1 */
@@ -69,7 +70,7 @@ static int link_unlink(int fd, const erlang_pid *from, const erlang_pid *to,
if (ei_trace_distribution > 1) ei_show_sendmsg(stderr,msgbuf,NULL);
#endif
- n = ei_write_fill_t(fd,msgbuf,index,ms);
+ n = ei_write_fill_t__(fd,msgbuf,index,tmo);
return (n==index ? 0 : -1);
}
diff --git a/lib/erl_interface/src/prog/erl_start.c b/lib/erl_interface/src/prog/erl_start.c
index 670a5900c9..ba495ac818 100644
--- a/lib/erl_interface/src/prog/erl_start.c
+++ b/lib/erl_interface/src/prog/erl_start.c
@@ -97,7 +97,7 @@
#endif
#ifndef RSH
-#define RSH "/usr/bin/rsh"
+#define RSH "/usr/bin/ssh"
#endif
#ifndef HAVE_SOCKLEN_T
diff --git a/lib/erl_interface/test/all_SUITE_data/Makefile.src b/lib/erl_interface/test/all_SUITE_data/Makefile.src
index 4f27b097c8..57e522fd3e 100644
--- a/lib/erl_interface/test/all_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/all_SUITE_data/Makefile.src
@@ -21,8 +21,8 @@ include @erl_interface_mk_include@
CC0 = @CC@
CC = .@DS@gccifier@exe@ -CC"$(CC0)"
-CFLAGS0 = @CFLAGS@ -I@erl_interface_include@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@
+CFLAGS0 = @CFLAGS@ -I@erl_interface_include@ @erl_interface_target_include@
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@
EI_COMMON_OBJS = runner@obj@ ei_runner@obj@
ALL_OBJS = gccifier@exe@ $(EI_COMMON_OBJS)
diff --git a/lib/erl_interface/test/ei_accept_SUITE.erl b/lib/erl_interface/test/ei_accept_SUITE.erl
index 78a433d21b..9c9c3f86b6 100644
--- a/lib/erl_interface/test/ei_accept_SUITE.erl
+++ b/lib/erl_interface/test/ei_accept_SUITE.erl
@@ -81,12 +81,10 @@ ei_accept(Config) when is_list(Config) ->
ei_threaded_accept(Config) when is_list(Config) ->
Einode = filename:join(proplists:get_value(data_dir, Config), "eiaccnode"),
- N = 1, % 3,
+ N = 3,
Host = atom_to_list(node()),
- Port = 6767,
- start_einode(Einode, N, Host, Port),
+ start_einode(Einode, N, Host),
io:format("started eiaccnode"),
- %%spawn_link(fun() -> start_einode(Einode, N, Host, Port) end),
TestServerPid = self(),
[spawn_link(fun() -> send_rec_einode(I, TestServerPid) end) || I <- lists:seq(0, N-1)],
[receive I -> ok end || I <- lists:seq(0, N-1) ],
@@ -159,10 +157,9 @@ send_rec_einode(N, TestServerPid) ->
ct:fail(EINode)
end.
-start_einode(Einode, N, Host, Port) ->
+start_einode(Einode, N, Host) ->
Einodecmd = Einode ++ " " ++ atom_to_list(erlang:get_cookie())
- ++ " " ++ integer_to_list(N) ++ " " ++ Host ++ " "
- ++ integer_to_list(Port) ++ " nothreads",
+ ++ " " ++ integer_to_list(N) ++ " " ++ Host,
io:format("Einodecmd ~p ~n", [Einodecmd]),
open_port({spawn, Einodecmd}, []),
ok.
diff --git a/lib/erl_interface/test/ei_accept_SUITE_data/Makefile.src b/lib/erl_interface/test/ei_accept_SUITE_data/Makefile.src
index 10ef437f8b..76dc84221f 100644
--- a/lib/erl_interface/test/ei_accept_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/ei_accept_SUITE_data/Makefile.src
@@ -27,7 +27,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/ei_runner@obj@ \
$(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
EI_ACCEPT_OBJS = ei_accept_test@obj@ ei_accept_test_decl@obj@
EIACCNODE_OBJS = eiaccnode@obj@
diff --git a/lib/erl_interface/test/ei_accept_SUITE_data/ei_accept_test.c b/lib/erl_interface/test/ei_accept_SUITE_data/ei_accept_test.c
index 50df848b69..c209f506b1 100644
--- a/lib/erl_interface/test/ei_accept_SUITE_data/ei_accept_test.c
+++ b/lib/erl_interface/test/ei_accept_SUITE_data/ei_accept_test.c
@@ -74,6 +74,8 @@ TESTCASE(interpret)
int i;
ei_term term;
+ ei_init();
+
ei_x_new(&x);
while (get_bin_term(&x, &term) == 0) {
char* buf = x.buff, func[MAXATOMLEN];
@@ -125,45 +127,26 @@ static void cmd_ei_connect_init(char* buf, int len)
ei_x_free(&res);
}
-static int my_listen(int port)
-{
- int listen_fd;
- struct sockaddr_in addr;
- const char *on = "1";
-
- if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
- return -1;
-
- setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, on, sizeof(on));
-
- memset((void*) &addr, 0, (size_t) sizeof(addr));
- addr.sin_family = AF_INET;
- addr.sin_port = htons(port);
- addr.sin_addr.s_addr = htonl(INADDR_ANY);
-
- if (bind(listen_fd, (struct sockaddr*) &addr, sizeof(addr)) < 0)
- return -1;
-
- listen(listen_fd, 5);
- return listen_fd;
-}
-
static void cmd_ei_publish(char* buf, int len)
{
int index = 0;
- int listen, r;
- long port;
+ int iport, lfd, r;
+ long lport;
ei_x_buff x;
int i;
/* get port */
- if (ei_decode_long(buf, &index, &port) < 0)
+ if (ei_decode_long(buf, &index, &lport) < 0)
fail("expected int (port)");
/* Make a listen socket */
- if ((listen = my_listen(port)) <= 0)
+
+ iport = (int) lport;
+ lfd = ei_listen(&ec, &iport, 5);
+ if (lfd < 0)
fail("listen");
+ lport = (long) iport;
- if ((i = ei_publish(&ec, port)) == -1)
+ if ((i = ei_publish(&ec, lport)) == -1)
fail("ei_publish");
#ifdef VXWORKS
save_fd(i);
@@ -171,7 +154,7 @@ static void cmd_ei_publish(char* buf, int len)
/* send listen-fd, result and errno */
ei_x_new_with_version(&x);
ei_x_encode_tuple_header(&x, 3);
- ei_x_encode_long(&x, listen);
+ ei_x_encode_long(&x, (long) lfd);
ei_x_encode_long(&x, i);
ei_x_encode_long(&x, erl_errno);
send_bin_term(&x);
diff --git a/lib/erl_interface/test/ei_accept_SUITE_data/eiaccnode.c b/lib/erl_interface/test/ei_accept_SUITE_data/eiaccnode.c
index 308f843530..90c7a2259f 100644
--- a/lib/erl_interface/test/ei_accept_SUITE_data/eiaccnode.c
+++ b/lib/erl_interface/test/ei_accept_SUITE_data/eiaccnode.c
@@ -47,8 +47,6 @@
#define MAIN main
#endif
-static int my_listen(int port);
-
/*
A small einode.
To be called from the test case ei_accept_SUITE:multi_thread
@@ -64,7 +62,6 @@ static int my_listen(int port);
*/
static const char* cookie, * desthost;
-static int port; /* actually base port */
#ifndef SD_SEND
#ifdef SHUTWR
@@ -74,10 +71,6 @@ static int port; /* actually base port */
#endif
#endif
-#ifndef __WIN32__
-#define closesocket(fd) close(fd)
-#endif
-
#ifdef __WIN32__
static DWORD WINAPI
#else
@@ -86,26 +79,32 @@ static void*
einode_thread(void* num)
{
int n = (int)num;
+ int port;
ei_cnode ec;
- char myname[100], destname[100];
+ char myname[100], destname[100], filename[100];
int r, fd, listen;
ErlConnect conn;
erlang_msg msg;
-/* FILE* f;*/
+ FILE* file;
- sprintf(myname, "eiacc%d", n);
- printf("thread %d (%s) listening\n", n, myname, destname);
+ sprintf(filename, "eiacc%d_trace.txt", n);
+ file = fopen(filename, "w");
+
+ sprintf(myname, "eiacc%d", n); fflush(file);
r = ei_connect_init(&ec, myname, cookie, 0);
- if ((listen = my_listen(port+n)) <= 0) {
- printf("listen err\n");
+ port = 0;
+ listen = ei_listen(&ec, &port, 5);
+ if (listen <= 0) {
+ fprintf(file, "listen err\n"); fflush(file);
exit(7);
}
- if (ei_publish(&ec, port + n) == -1) {
- printf("ei_publish port %d\n", port+n);
+ fprintf(file, "thread %d (%s:%s) listening on port %d\n", n, myname, destname, port);
+ if (ei_publish(&ec, port) == -1) {
+ fprintf(file, "ei_publish port %d\n", port+n); fflush(file);
exit(8);
}
fd = ei_accept(&ec, listen, &conn);
- printf("ei_accept %d\n", fd);
+ fprintf(file, "ei_accept %d\n", fd); fflush(file);
if (fd >= 0) {
ei_x_buff x, xs;
int index, version;
@@ -117,37 +116,38 @@ static void*
if (got == ERL_TICK)
continue;
if (got == ERL_ERROR) {
- printf("receive error %d\n", n);
+ fprintf(file, "receive error %d\n", n); fflush(file);
return 0;
}
- printf("received %d\n", got);
+ fprintf(file, "received %d\n", got); fflush(file);
break;
}
index = 0;
if (ei_decode_version(x.buff, &index, &version) != 0) {
- printf("ei_decode_version %d\n", n);
+ fprintf(file, "ei_decode_version %d\n", n); fflush(file);
return 0;
}
if (ei_decode_pid(x.buff, &index, &pid) != 0) {
- printf("ei_decode_pid %d\n", n);
+ fprintf(file, "ei_decode_pid %d\n", n); fflush(file);
return 0;
}
-/* fprintf(f, "got pid from %s \n", pid.node);*/
+ fprintf(file, "got pid from %s \n", pid.node); fflush(file);
ei_x_new_with_version(&xs);
ei_x_encode_tuple_header(&xs, 2);
ei_x_encode_long(&xs, n);
ei_x_encode_pid(&xs, &pid);
r = ei_send(fd, &pid, xs.buff, xs.index);
-/* fprintf(f, "sent %d bytes %d\n", xs.index, r);*/
+ fprintf(file, "sent %d bytes %d\n", xs.index, r); fflush(file);
shutdown(fd, SD_SEND);
- closesocket(fd);
+ ei_close_connection(fd);
ei_x_free(&x);
ei_x_free(&xs);
} else {
- printf("coudn't connect fd %d r %d\n", fd, r);
+ fprintf(file, "coudn't connect fd %d r %d\n", fd, r); fflush(file);
}
- printf("done thread %d\n", n);
-/* fclose(f);*/
+ ei_close_connection(listen);
+ fprintf(file, "done thread %d\n", n);
+ fclose(file);
return 0;
}
@@ -170,12 +170,16 @@ MAIN(int argc, char *argv[])
if (n > 100)
exit(2);
desthost = argv[3];
- port = atoi(argv[4]);
-#ifndef VXWORKS
- no_threads = argv[5] != NULL && strcmp(argv[5], "nothreads") == 0;
-#else
+ if (argc == 3)
+ no_threads = 0;
+ else
+ no_threads = argv[4] != NULL && strcmp(argv[4], "nothreads") == 0;
+#ifdef VXWORKS
no_threads = 1;
#endif
+
+ ei_init();
+
for (i = 0; i < n; ++i) {
if (!no_threads) {
#ifndef VXWORKS
@@ -209,27 +213,3 @@ MAIN(int argc, char *argv[])
printf("ok\n");
return 0;
}
-
-static int my_listen(int port)
-{
- int listen_fd;
- struct sockaddr_in addr;
- const char *on = "1";
-
- if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
- return -1;
-
- setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, on, sizeof(on));
-
- memset((void*) &addr, 0, (size_t) sizeof(addr));
- addr.sin_family = AF_INET;
- addr.sin_port = htons(port);
- addr.sin_addr.s_addr = htonl(INADDR_ANY);
-
- if (bind(listen_fd, (struct sockaddr*) &addr, sizeof(addr)) < 0)
- return -1;
-
- listen(listen_fd, 5);
- return listen_fd;
-}
-
diff --git a/lib/erl_interface/test/ei_connect_SUITE_data/Makefile.src b/lib/erl_interface/test/ei_connect_SUITE_data/Makefile.src
index c2d8261dd8..d1694e607d 100644
--- a/lib/erl_interface/test/ei_connect_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/ei_connect_SUITE_data/Makefile.src
@@ -28,7 +28,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/ei_runner@obj@ \
$(LIBERL) $(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
EI_CONNECT_OBJS = ei_connect_test@obj@ ei_connect_test_decl@obj@
EINODE_OBJS = einode@obj@
diff --git a/lib/erl_interface/test/ei_connect_SUITE_data/ei_connect_test.c b/lib/erl_interface/test/ei_connect_SUITE_data/ei_connect_test.c
index 29c03d7604..58c0c7f8d8 100644
--- a/lib/erl_interface/test/ei_connect_SUITE_data/ei_connect_test.c
+++ b/lib/erl_interface/test/ei_connect_SUITE_data/ei_connect_test.c
@@ -73,6 +73,8 @@ TESTCASE(interpret)
int i;
ei_term term;
+ ei_init();
+
ei_x_new(&x);
while (get_bin_term(&x, &term) == 0) {
char* buf = x.buff, func[MAXATOMLEN];
diff --git a/lib/erl_interface/test/ei_decode_SUITE_data/Makefile.src b/lib/erl_interface/test/ei_decode_SUITE_data/Makefile.src
index e678914a40..76591d893c 100644
--- a/lib/erl_interface/test/ei_decode_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/ei_decode_SUITE_data/Makefile.src
@@ -27,7 +27,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/ei_runner@obj@ \
$(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
EI_DECODE_OBJS = ei_decode_test@obj@ ei_decode_test_decl@obj@
all: ei_decode_test@exe@
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 f945a7d378..e516f310b6 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
@@ -321,6 +321,8 @@ int ei_decode_my_string(const char *buf, int *index, char *to,
TESTCASE(test_ei_decode_long)
{
+ ei_init();
+
EI_DECODE_2 (decode_long, 2, long, 0);
EI_DECODE_2 (decode_long, 2, long, 255);
EI_DECODE_2 (decode_long, 5, long, 256);
@@ -363,6 +365,8 @@ TESTCASE(test_ei_decode_long)
TESTCASE(test_ei_decode_ulong)
{
+ ei_init();
+
EI_DECODE_2 (decode_ulong, 2, unsigned long, 0);
EI_DECODE_2 (decode_ulong, 2, unsigned long, 255);
EI_DECODE_2 (decode_ulong, 5, unsigned long, 256);
@@ -409,6 +413,8 @@ TESTCASE(test_ei_decode_ulong)
TESTCASE(test_ei_decode_longlong)
{
+ ei_init();
+
#ifndef VXWORKS
EI_DECODE_2 (decode_longlong, 2, EI_LONGLONG, 0);
EI_DECODE_2 (decode_longlong, 2, EI_LONGLONG, 255);
@@ -443,6 +449,8 @@ TESTCASE(test_ei_decode_longlong)
TESTCASE(test_ei_decode_ulonglong)
{
+ ei_init();
+
#ifndef VXWORKS
EI_DECODE_2 (decode_ulonglong, 2, EI_ULONGLONG, 0);
EI_DECODE_2 (decode_ulonglong, 2, EI_ULONGLONG, 255);
@@ -478,6 +486,8 @@ TESTCASE(test_ei_decode_ulonglong)
TESTCASE(test_ei_decode_char)
{
+ ei_init();
+
EI_DECODE_2(decode_char, 2, char, 0);
EI_DECODE_2(decode_char, 2, char, 0x7f);
EI_DECODE_2(decode_char, 2, char, 0xff);
@@ -491,6 +501,8 @@ TESTCASE(test_ei_decode_char)
TESTCASE(test_ei_decode_nonoptimal)
{
+ ei_init();
+
EI_DECODE_2(decode_char, 2, char, 42);
EI_DECODE_2(decode_char, 5, char, 42);
EI_DECODE_2(decode_char, 4, char, 42);
@@ -612,6 +624,8 @@ TESTCASE(test_ei_decode_nonoptimal)
TESTCASE(test_ei_decode_misc)
{
+ ei_init();
+
/*
EI_DECODE_0(decode_version);
*/
@@ -647,6 +661,7 @@ TESTCASE(test_ei_decode_misc)
TESTCASE(test_ei_decode_utf8_atom)
{
+ ei_init();
EI_DECODE_STRING_4(decode_my_atom_as, 4, P99({229,0}), /* LATIN1 "�" */
P99({ERLANG_ANY,ERLANG_LATIN1,ERLANG_LATIN1}));
diff --git a/lib/erl_interface/test/ei_decode_encode_SUITE_data/Makefile.src b/lib/erl_interface/test/ei_decode_encode_SUITE_data/Makefile.src
index 853fe9ddeb..3f5fa4f295 100644
--- a/lib/erl_interface/test/ei_decode_encode_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/ei_decode_encode_SUITE_data/Makefile.src
@@ -27,7 +27,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/ei_runner@obj@ \
$(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
EI_DECODE_ENCODE_OBJS = ei_decode_encode_test@obj@ ei_decode_encode_test_decl@obj@
all: ei_decode_encode_test@exe@
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 9977683d59..55d9ed1b1a 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
@@ -477,6 +477,8 @@ TESTCASE(test_ei_decode_encode)
{
int i;
+ ei_init();
+
decode_encode_one(&fun_type);
decode_encode_one(&pid_type);
decode_encode_one(&port_type);
diff --git a/lib/erl_interface/test/ei_encode_SUITE_data/Makefile.src b/lib/erl_interface/test/ei_encode_SUITE_data/Makefile.src
index 3b2cab7af4..489382d85e 100644
--- a/lib/erl_interface/test/ei_encode_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/ei_encode_SUITE_data/Makefile.src
@@ -27,7 +27,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/ei_runner@obj@ \
$(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
EI_ENCODE_OBJS = ei_encode_test@obj@ ei_encode_test_decl@obj@
all: ei_encode_test@exe@
diff --git a/lib/erl_interface/test/ei_encode_SUITE_data/ei_encode_test.c b/lib/erl_interface/test/ei_encode_SUITE_data/ei_encode_test.c
index 32811fdf22..6f63cc5d7e 100644
--- a/lib/erl_interface/test/ei_encode_SUITE_data/ei_encode_test.c
+++ b/lib/erl_interface/test/ei_encode_SUITE_data/ei_encode_test.c
@@ -403,6 +403,8 @@
TESTCASE(test_ei_encode_long)
{
+ ei_init();
+
EI_ENCODE_1(encode_long, 0);
EI_ENCODE_1(encode_long, 255);
@@ -430,6 +432,8 @@ TESTCASE(test_ei_encode_long)
TESTCASE(test_ei_encode_ulong)
{
+ ei_init();
+
EI_ENCODE_1(encode_ulong, 0);
EI_ENCODE_1(encode_ulong, 255);
@@ -454,6 +458,7 @@ TESTCASE(test_ei_encode_ulong)
TESTCASE(test_ei_encode_longlong)
{
+ ei_init();
#ifndef VXWORKS
@@ -494,6 +499,7 @@ TESTCASE(test_ei_encode_longlong)
TESTCASE(test_ei_encode_ulonglong)
{
+ ei_init();
#ifndef VXWORKS
@@ -527,6 +533,8 @@ TESTCASE(test_ei_encode_ulonglong)
TESTCASE(test_ei_encode_char)
{
+ ei_init();
+
EI_ENCODE_1(encode_char, 0);
EI_ENCODE_1(encode_char, 0x7f);
@@ -540,6 +548,8 @@ TESTCASE(test_ei_encode_char)
TESTCASE(test_ei_encode_misc)
{
+ ei_init();
+
EI_ENCODE_0(encode_version);
EI_ENCODE_1(encode_double, 0.0);
@@ -594,6 +604,8 @@ TESTCASE(test_ei_encode_fails)
char buf[1024];
int index;
+ ei_init();
+
/* FIXME the ei_x versions are not tested */
index = 0;
@@ -660,6 +672,7 @@ TESTCASE(test_ei_encode_fails)
TESTCASE(test_ei_encode_utf8_atom)
{
+ ei_init();
EI_ENCODE_3(encode_atom_as, "�", ERLANG_LATIN1, ERLANG_UTF8);
EI_ENCODE_3(encode_atom_as, "�", ERLANG_LATIN1, ERLANG_LATIN1);
@@ -686,6 +699,7 @@ TESTCASE(test_ei_encode_utf8_atom)
TESTCASE(test_ei_encode_utf8_atom_len)
{
+ ei_init();
EI_ENCODE_4(encode_atom_len_as, "���", 1, ERLANG_LATIN1, ERLANG_UTF8);
EI_ENCODE_4(encode_atom_len_as, "���", 2, ERLANG_LATIN1, ERLANG_LATIN1);
diff --git a/lib/erl_interface/test/ei_format_SUITE_data/Makefile.src b/lib/erl_interface/test/ei_format_SUITE_data/Makefile.src
index b89dcae45a..9e5a271db6 100644
--- a/lib/erl_interface/test/ei_format_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/ei_format_SUITE_data/Makefile.src
@@ -27,7 +27,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/ei_runner@obj@ \
$(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
EI_FORMAT_OBJS = ei_format_test@obj@ ei_format_test_decl@obj@
all: ei_format_test@exe@
diff --git a/lib/erl_interface/test/ei_format_SUITE_data/ei_format_test.c b/lib/erl_interface/test/ei_format_SUITE_data/ei_format_test.c
index 8450332b28..1c0443c0f4 100644
--- a/lib/erl_interface/test/ei_format_SUITE_data/ei_format_test.c
+++ b/lib/erl_interface/test/ei_format_SUITE_data/ei_format_test.c
@@ -48,6 +48,8 @@ send_format(char* format)
TESTCASE(atoms)
{
+ ei_init();
+
send_format("''");
send_format("'a'");
send_format("'A'");
@@ -82,6 +84,8 @@ TESTCASE(atoms)
TESTCASE(tuples)
{
+ ei_init();
+
send_format("{}");
send_format("{a}");
send_format("{a, b}");
@@ -108,6 +112,8 @@ TESTCASE(lists)
ei_x_buff x;
static char str[65537];
+ ei_init();
+
send_format("[]");
send_format("[a]");
send_format("[a, b]");
@@ -177,6 +183,8 @@ TESTCASE(format_wo_ver) {
*/
ei_x_buff x;
+ ei_init();
+
ei_x_new (&x);
ei_x_format(&x, "[-1, +2, ~c, {~a,~s},{~a,~i}]", 'c', "a", "b", "c", 10);
send_bin_term(&x);
diff --git a/lib/erl_interface/test/ei_print_SUITE_data/Makefile.src b/lib/erl_interface/test/ei_print_SUITE_data/Makefile.src
index 150c11b99c..354011f1a5 100644
--- a/lib/erl_interface/test/ei_print_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/ei_print_SUITE_data/Makefile.src
@@ -27,7 +27,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/ei_runner@obj@ \
$(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
EI_PRINT_OBJS = ei_print_test@obj@ ei_print_test_decl@obj@
all: ei_print_test@exe@
diff --git a/lib/erl_interface/test/ei_print_SUITE_data/ei_print_test.c b/lib/erl_interface/test/ei_print_SUITE_data/ei_print_test.c
index 15cfbcae34..80be3016e6 100644
--- a/lib/erl_interface/test/ei_print_SUITE_data/ei_print_test.c
+++ b/lib/erl_interface/test/ei_print_SUITE_data/ei_print_test.c
@@ -84,6 +84,8 @@ static void send_printed3f(char* format, float f1, float f2)
TESTCASE(atoms)
{
+ ei_init();
+
send_printed("''");
send_printed("'a'");
send_printed("'A'");
@@ -118,6 +120,8 @@ TESTCASE(atoms)
TESTCASE(tuples)
{
+ ei_init();
+
send_printed("{}");
send_printed("{a}");
send_printed("{a, b}");
@@ -138,6 +142,8 @@ TESTCASE(lists)
{
ei_x_buff x;
+ ei_init();
+
send_printed("[]");
send_printed("[a]");
send_printed("[a, b]");
@@ -164,6 +170,8 @@ TESTCASE(strings)
{
ei_x_buff x;
+ ei_init();
+
send_printed("\"\n\"");
send_printed("\"\r\n\"");
send_printed("\"a\"");
diff --git a/lib/erl_interface/test/ei_tmo_SUITE_data/Makefile.src b/lib/erl_interface/test/ei_tmo_SUITE_data/Makefile.src
index b4ee361939..76a9c6a606 100644
--- a/lib/erl_interface/test/ei_tmo_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/ei_tmo_SUITE_data/Makefile.src
@@ -27,7 +27,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/ei_runner@obj@ \
$(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
EI_TMO_OBJS = ei_tmo_test@obj@ ei_tmo_test_decl@obj@
all: ei_tmo_test@exe@
diff --git a/lib/erl_interface/test/ei_tmo_SUITE_data/ei_tmo_test.c b/lib/erl_interface/test/ei_tmo_SUITE_data/ei_tmo_test.c
index 39846e4a58..693e405f75 100644
--- a/lib/erl_interface/test/ei_tmo_SUITE_data/ei_tmo_test.c
+++ b/lib/erl_interface/test/ei_tmo_SUITE_data/ei_tmo_test.c
@@ -96,6 +96,8 @@ TESTCASE(framework_check)
int i;
#endif
+ ei_init();
+
OPEN_DEBUGFILE(1);
DEBUGF(("B�rjar... \n"));
@@ -340,6 +342,7 @@ TESTCASE(recv_tmo)
int com_sock = -1;
ei_cnode nodeinfo;
+ ei_init();
OPEN_DEBUGFILE(5);
@@ -450,6 +453,7 @@ TESTCASE(send_tmo)
int com_sock = -1;
ei_cnode nodeinfo;
+ ei_init();
OPEN_DEBUGFILE(4);
@@ -591,7 +595,7 @@ TESTCASE(connect_tmo)
int com_sock = -1;
ei_cnode nodeinfo;
-
+ ei_init();
OPEN_DEBUGFILE(3);
@@ -680,7 +684,7 @@ TESTCASE(accept_tmo)
ErlConnect peer;
ei_cnode nodeinfo;
-
+ ei_init();
OPEN_DEBUGFILE(2);
diff --git a/lib/erl_interface/test/erl_connect_SUITE_data/Makefile.src b/lib/erl_interface/test/erl_connect_SUITE_data/Makefile.src
index ff4c382c97..19b076794a 100644
--- a/lib/erl_interface/test/erl_connect_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/erl_connect_SUITE_data/Makefile.src
@@ -28,7 +28,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/runner@obj@ \
$(LIBERL) $(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
OBJS = erl_connect_test@obj@ erl_connect_test_decl@obj@
all: erl_connect_test@exe@
diff --git a/lib/erl_interface/test/erl_eterm_SUITE_data/Makefile.src b/lib/erl_interface/test/erl_eterm_SUITE_data/Makefile.src
index 4b1ddf77b6..cd4de56d7c 100644
--- a/lib/erl_interface/test/erl_eterm_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/erl_eterm_SUITE_data/Makefile.src
@@ -28,7 +28,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/runner@obj@ \
$(LIBERL) $(LIBEI) @erl_interface_sock_libs@ @LIBS@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
ETERM_OBJS = eterm_test@obj@ eterm_test_decl@obj@
CNODE_OBJS = cnode@obj@
PRINT_OBJS = print_term@obj@
diff --git a/lib/erl_interface/test/erl_eterm_SUITE_data/cnode.c b/lib/erl_interface/test/erl_eterm_SUITE_data/cnode.c
index bead0f8413..b87feb9dfc 100644
--- a/lib/erl_interface/test/erl_eterm_SUITE_data/cnode.c
+++ b/lib/erl_interface/test/erl_eterm_SUITE_data/cnode.c
@@ -20,7 +20,7 @@
#include <stdlib.h>
#include <stdio.h>
-
+#include <string.h>
#include "ei.h"
#include "erl_interface.h"
@@ -68,6 +68,7 @@ MAIN(int argc, char **argv)
char host[80];
int number;
ETERM *ref, *ref1, *ref2;
+ FILE *dfile = fopen("cnode_debug_printout", "w");
erl_init(NULL, 0);
@@ -80,28 +81,30 @@ MAIN(int argc, char **argv)
gethostname(host, sizeof(host));
sprintf(node, "c%d@%s", number, host);
- printf("s = %d\n", s);
+ fprintf(dfile, "s = %d\n", s); fflush(dfile);
sprintf(server, "test_server@%s", host);
fd = erl_connect(server);
- printf("fd = %d\n", fd);
+ fprintf(dfile, "fd = %d\n", fd);
-/* printf("dist = %d\n", erl_distversion(fd)); */
+/* fprintf(dfile, "dist = %d\n", erl_distversion(fd)); */
#if 1
ref = erl_mk_long_ref(node, 4711, 113, 98, 0);
#else
ref = erl_mk_ref(node, 4711, 0);
#endif
- printf("ref = %d\n", ref);
+ fprintf(dfile, "ref = %p\n", ref); fflush(dfile);
s = erl_reg_send(fd, "mip", ref);
- printf("s = %d\n", s);
+ fprintf(dfile, "s = %d\n", s); fflush(dfile);
{
ETERM* emsg;
emsg = SELF(fd);
- erl_reg_send(fd,"mip",emsg);
+ fprintf(dfile, "pid = %p\n", emsg); fflush(dfile);
+ s = erl_reg_send(fd,"mip",emsg);
+ fprintf(dfile, "s2 = %d\n", s); fflush(dfile);
erl_free_term(emsg);
}
@@ -116,28 +119,29 @@ MAIN(int argc, char **argv)
#endif
switch (s) {
case ERL_TICK:
- printf("tick\n");
+ fprintf(dfile, "tick\n");
break;
case ERL_ERROR:
- printf("error\n");
+ fprintf(dfile, "error: %s (%d)\n", strerror(erl_errno), erl_errno);
break;
case ERL_MSG:
- printf("msg %d\n", msgsize);
+ fprintf(dfile, "msg %d\n", msgsize);
break;
default:
- printf("unknown result %d\n", s);
+ fprintf(dfile, "unknown result %d\n", s);
break;
}
+ fflush(dfile);
} while (s == ERL_TICK);
s = erl_reg_send(fd, "mip", msg.msg);
- printf("s = %d\n", s);
+ fprintf(dfile, "s = %d\n", s); fflush(dfile);
s = erl_reg_send(fd, "mip", msg.to);
- printf("s = %d\n", s);
+ fprintf(dfile, "s = %d\n", s); fflush(dfile);
#if 0
/* from = NULL! */
s = erl_reg_send(fd, "mip", msg.from);
- printf("s = %d\n", s);
+ fprintf(dfile, "s = %d\n", s); fflush(dfile);
#endif
#if 0
@@ -150,17 +154,19 @@ MAIN(int argc, char **argv)
ref1 = erl_mk_long_ref(node, 4711, 113, 98, 0);
ref2 = erl_mk_ref(node, 4711, 0);
s = erl_encode(ref1, buf1);
- printf("enc1 s = %d\n", s);
+ fprintf(dfile, "enc1 s = %d\n", s); fflush(dfile);
s = erl_encode(ref2, buf2);
- printf("enc2 s = %d\n", s);
+ fprintf(dfile, "enc2 s = %d\n", s); fflush(dfile);
s = erl_compare_ext(buf1, buf2);
- printf("comp s = %d\n", s);
+ fprintf(dfile, "comp s = %d\n", s); fflush(dfile);
/* Compare, in another way */
s = erl_match(ref1, ref2);
- printf("match s = %d\n", s);
+ fprintf(dfile, "match s = %d\n", s); fflush(dfile);
#endif
+ fclose(dfile);
+
erl_close_connection(fd);
return 0;
diff --git a/lib/erl_interface/test/erl_ext_SUITE_data/Makefile.src b/lib/erl_interface/test/erl_ext_SUITE_data/Makefile.src
index fe8caebbd6..50b60637cd 100644
--- a/lib/erl_interface/test/erl_ext_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/erl_ext_SUITE_data/Makefile.src
@@ -28,7 +28,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/runner@obj@ \
$(LIBERL) $(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
EXT_OBJS = ext_test@obj@ ext_test_decl@obj@
all: ext_test@exe@
diff --git a/lib/erl_interface/test/erl_format_SUITE_data/Makefile.src b/lib/erl_interface/test/erl_format_SUITE_data/Makefile.src
index 2ba59ab651..7d51cd6007 100644
--- a/lib/erl_interface/test/erl_format_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/erl_format_SUITE_data/Makefile.src
@@ -28,7 +28,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/runner@obj@ \
$(LIBERL) $(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
FORMAT_OBJS = format_test@obj@ format_test_decl@obj@
all: format_test@exe@
diff --git a/lib/erl_interface/test/erl_global_SUITE_data/Makefile.src b/lib/erl_interface/test/erl_global_SUITE_data/Makefile.src
index 1c1530d1b6..9f2a8619ac 100644
--- a/lib/erl_interface/test/erl_global_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/erl_global_SUITE_data/Makefile.src
@@ -28,7 +28,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/runner@obj@ \
$(LIBERL) $(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
OBJS = erl_global_test@obj@ erl_global_test_decl@obj@
all: erl_global_test@exe@
diff --git a/lib/erl_interface/test/erl_match_SUITE_data/Makefile.src b/lib/erl_interface/test/erl_match_SUITE_data/Makefile.src
index 156214a269..56ed1df203 100644
--- a/lib/erl_interface/test/erl_match_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/erl_match_SUITE_data/Makefile.src
@@ -28,7 +28,7 @@ LIBEI = @erl_interface_eilib@
LIBFLAGS = ../all_SUITE_data/runner@obj@ \
$(LIBERL) $(LIBEI) @LIBS@ @erl_interface_sock_libs@ \
@erl_interface_threadlib@
-CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ -I../all_SUITE_data
+CFLAGS = @EI_CFLAGS@ $(THR_DEFS) -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
MATCH_OBJS = match_test@obj@ match_test_decl@obj@
all: match_test@exe@
diff --git a/lib/erl_interface/test/port_call_SUITE_data/Makefile.src b/lib/erl_interface/test/port_call_SUITE_data/Makefile.src
index 0f97ce9f70..e09e0fe175 100644
--- a/lib/erl_interface/test/port_call_SUITE_data/Makefile.src
+++ b/lib/erl_interface/test/port_call_SUITE_data/Makefile.src
@@ -27,7 +27,7 @@ LIBERL = @erl_interface_lib_drv@
LIBEI = @erl_interface_eilib_drv@
SHLIB_EXTRA_LDLIBS = $(LIBERL) $(LIBEI) @erl_interface_threadlib@
-SHLIB_EXTRA_CFLAGS = -I@erl_interface_include@ -I../all_SUITE_data
+SHLIB_EXTRA_CFLAGS = -I@erl_interface_include@ @erl_interface_target_include@ -I../all_SUITE_data
all: port_call_drv@dll@
diff --git a/lib/et/doc/src/et.xml b/lib/et/doc/src/et.xml
index 3009b559e1..a362d00b3e 100644
--- a/lib/et/doc/src/et.xml
+++ b/lib/et/doc/src/et.xml
@@ -32,14 +32,14 @@
<rev>%VSN%</rev>
<file>et</file>
</header>
- <module>et</module>
+ <module since="">et</module>
<modulesummary>Main API of the Event Trace (ET) application</modulesummary>
<description>
<p>Interface module for the Event Trace (ET) application</p>
</description>
<funcs>
<func>
- <name>trace_me(DetailLevel, From, To, Label, Contents) -> hopefully_traced</name>
+ <name since="OTP R13B04">trace_me(DetailLevel, From, To, Label, Contents) -> hopefully_traced</name>
<fsummary>A function that is intended to be traced.</fsummary>
<type>
<v>DetailLevel = integer(X) when X =&lt; 0, X >= 100</v>
@@ -70,7 +70,7 @@
</func>
<func>
- <name>trace_me(DetailLevel, FromTo, Label, Contents) -> hopefully_traced</name>
+ <name since="OTP R13B04">trace_me(DetailLevel, FromTo, Label, Contents) -> hopefully_traced</name>
<fsummary>A function that is intended to be traced.</fsummary>
<desc>
<p>Invokes <c>et:trace_me/5</c> with both <c>From</c> and <c>To</c>
@@ -79,8 +79,8 @@
</func>
<func>
- <name>phone_home(DetailLevel, FromTo, Label, Contents) -> hopefully_traced</name>
- <name>phone_home(DetailLevel, From, To, Label, Contents) -> hopefully_traced</name>
+ <name since="">phone_home(DetailLevel, FromTo, Label, Contents) -> hopefully_traced</name>
+ <name since="">phone_home(DetailLevel, From, To, Label, Contents) -> hopefully_traced</name>
<fsummary>Send a signal to the outer space</fsummary>
<desc>
<p>These functions sends a signal to the outer space and the
@@ -90,8 +90,8 @@
</desc>
</func>
<func>
- <name>report_event(DetailLevel, FromTo, Label, Contents) -> hopefully_traced</name>
- <name>report_event(DetailLevel, From, To, Label, Contents) -> hopefully_traced</name>
+ <name since="">report_event(DetailLevel, FromTo, Label, Contents) -> hopefully_traced</name>
+ <name since="">report_event(DetailLevel, From, To, Label, Contents) -> hopefully_traced</name>
<fsummary>Deprecated functions</fsummary>
<desc>
<p>Deprecated functions which for the time being are kept for
diff --git a/lib/et/doc/src/et_collector.xml b/lib/et/doc/src/et_collector.xml
index fd90ecfc41..f908612797 100644
--- a/lib/et/doc/src/et_collector.xml
+++ b/lib/et/doc/src/et_collector.xml
@@ -32,14 +32,14 @@
<rev>%VSN%</rev>
<file>et_collector.xml</file>
</header>
- <module>et_collector</module>
+ <module since="">et_collector</module>
<modulesummary>Collect trace events and provide a backing storage appropriate for iteration </modulesummary>
<description>
<p>Interface module for the Event Trace (ET) application</p>
</description>
<funcs>
<func>
- <name>start_link(Options) -> {ok, CollectorPid} | {error, Reason}</name>
+ <name since="">start_link(Options) -> {ok, CollectorPid} | {error, Reason}</name>
<fsummary>Start a collector process</fsummary>
<type>
<v>Options = [option()]</v>
@@ -105,7 +105,7 @@
</desc>
</func>
<func>
- <name>stop(CollectorPid) -> ok</name>
+ <name since="">stop(CollectorPid) -> ok</name>
<fsummary>Stop a collector process</fsummary>
<type>
<v>CollectorPid = pid()</v>
@@ -115,7 +115,7 @@
</desc>
</func>
<func>
- <name>save_event_file(CollectorPid, FileName, Options) -> ok | {error, Reason}</name>
+ <name since="">save_event_file(CollectorPid, FileName, Options) -> ok | {error, Reason}</name>
<fsummary>Save the events to a file</fsummary>
<type>
<v>CollectorPid = pid()</v>
@@ -139,7 +139,7 @@
</desc>
</func>
<func>
- <name>load_event_file(CollectorPid, FileName) -> {ok, BadBytes} | exit(Reason)</name>
+ <name since="">load_event_file(CollectorPid, FileName) -> {ok, BadBytes} | exit(Reason)</name>
<fsummary>Load the event table from a file</fsummary>
<type>
<v>CollectorPid = pid()</v>
@@ -152,9 +152,9 @@
</desc>
</func>
<func>
- <name>report(Handle, TraceOrEvent) -> {ok, Continuation} | exit(Reason)</name>
- <name>report_event(Handle, DetailLevel, FromTo, Label, Contents) -> {ok, Continuation} | exit(Reason)</name>
- <name>report_event(Handle, DetailLevel, From, To, Label, Contents) -> {ok, Continuation} | exit(Reason)</name>
+ <name since="">report(Handle, TraceOrEvent) -> {ok, Continuation} | exit(Reason)</name>
+ <name since="">report_event(Handle, DetailLevel, FromTo, Label, Contents) -> {ok, Continuation} | exit(Reason)</name>
+ <name since="">report_event(Handle, DetailLevel, From, To, Label, Contents) -> {ok, Continuation} | exit(Reason)</name>
<fsummary>Report an event to the collector</fsummary>
<type>
<v>Handle = Initial | Continuation</v>
@@ -181,7 +181,7 @@
</desc>
</func>
<func>
- <name>make_key(Type, Stuff) -> Key</name>
+ <name since="">make_key(Type, Stuff) -> Key</name>
<fsummary>Make a key out of an event record or an old key</fsummary>
<type>
<v>Type = record(table_handle) | trace_ts | event_ts</v>
@@ -193,7 +193,7 @@
</desc>
</func>
<func>
- <name>get_table_handle(CollectorPid) -> Handle</name>
+ <name since="">get_table_handle(CollectorPid) -> Handle</name>
<fsummary>Return a table handle</fsummary>
<type>
<v>CollectorPid = pid()</v>
@@ -204,7 +204,7 @@
</desc>
</func>
<func>
- <name>get_global_pid() -> CollectorPid | exit(Reason)</name>
+ <name since="">get_global_pid() -> CollectorPid | exit(Reason)</name>
<fsummary>Return a the identity of the globally registered collector if there is any</fsummary>
<type>
<v>CollectorPid = pid()</v>
@@ -216,7 +216,7 @@
</desc>
</func>
<func>
- <name>change_pattern(CollectorPid, RawPattern) -> {old_pattern, TracePattern}</name>
+ <name since="">change_pattern(CollectorPid, RawPattern) -> {old_pattern, TracePattern}</name>
<fsummary>Change active trace pattern globally on all trace nodes</fsummary>
<type>
<v>CollectorPid = pid()</v>
@@ -232,9 +232,9 @@
</desc>
</func>
<func>
- <name>dict_insert(CollectorPid, {filter, collector}, FilterFun) -> ok</name>
- <name>dict_insert(CollectorPid, {subscriber, SubscriberPid}, Void) -> ok</name>
- <name>dict_insert(CollectorPid, Key, Val) -> ok</name>
+ <name since="">dict_insert(CollectorPid, {filter, collector}, FilterFun) -> ok</name>
+ <name since="">dict_insert(CollectorPid, {subscriber, SubscriberPid}, Void) -> ok</name>
+ <name since="">dict_insert(CollectorPid, Key, Val) -> ok</name>
<fsummary>Insert a dictionary entry and send a {et, {dict_insert, Key, Val}} tuple to all registered subscribers.</fsummary>
<type>
<v>CollectorPid = pid()</v>
@@ -259,7 +259,7 @@
</desc>
</func>
<func>
- <name>dict_lookup(CollectorPid, Key) -> [Val]</name>
+ <name since="">dict_lookup(CollectorPid, Key) -> [Val]</name>
<fsummary>Lookup a dictionary entry and return zero or one value</fsummary>
<type>
<v>CollectorPid = pid()</v>
@@ -273,7 +273,7 @@
</desc>
</func>
<func>
- <name>dict_delete(CollectorPid, Key) -> ok</name>
+ <name since="">dict_delete(CollectorPid, Key) -> ok</name>
<fsummary>Delete a dictionary entry and send a {et, {dict_delete, Key}} tuple to all registered subscribers.</fsummary>
<type>
<v>CollectorPid = pid()</v>
@@ -290,7 +290,7 @@
</desc>
</func>
<func>
- <name>dict_match(CollectorPid, Pattern) -> [Match]</name>
+ <name since="">dict_match(CollectorPid, Pattern) -> [Match]</name>
<fsummary>Match some dictionary entries</fsummary>
<type>
<v>CollectorPid = pid()</v>
@@ -306,7 +306,7 @@
</desc>
</func>
<func>
- <name>multicast(_CollectorPid, Msg) -> ok</name>
+ <name since="">multicast(_CollectorPid, Msg) -> ok</name>
<fsummary>Sends a message to all registered subscribers</fsummary>
<type>
<v>CollectorPid = pid()</v>
@@ -318,7 +318,7 @@
</desc>
</func>
<func>
- <name>start_trace_client(CollectorPid, Type, Parameters) -> file_loaded | {trace_client_pid, pid()} | exit(Reason)</name>
+ <name since="">start_trace_client(CollectorPid, Type, Parameters) -> file_loaded | {trace_client_pid, pid()} | exit(Reason)</name>
<fsummary>Load raw Erlang trace from a file, port or process.</fsummary>
<type>
<v>Type = dbg_trace_client_type()</v>
@@ -330,14 +330,14 @@
</desc>
</func>
<func>
- <name>iterate(Handle, Prev, Limit) -> NewAcc</name>
+ <name since="">iterate(Handle, Prev, Limit) -> NewAcc</name>
<fsummary>Iterates over the currently stored events</fsummary>
<desc>
<p>Short for iterate(Handle, Prev, Limit, undefined, Prev) -&gt; NewAcc</p>
</desc>
</func>
<func>
- <name>iterate(Handle, Prev, Limit, Fun, Acc) -> NewAcc</name>
+ <name since="">iterate(Handle, Prev, Limit, Fun, Acc) -> NewAcc</name>
<fsummary>Iterate over the currently stored events</fsummary>
<type>
<v>Handle = collector_pid() | table_handle()</v>
@@ -361,7 +361,7 @@
</desc>
</func>
<func>
- <name>clear_table(Handle) -> ok</name>
+ <name since="">clear_table(Handle) -> ok</name>
<fsummary>Clear the event table</fsummary>
<type>
<v>Handle = collector_pid() | table_handle()</v>
diff --git a/lib/et/doc/src/et_selector.xml b/lib/et/doc/src/et_selector.xml
index 30ca74c872..3c766cafb7 100644
--- a/lib/et/doc/src/et_selector.xml
+++ b/lib/et/doc/src/et_selector.xml
@@ -32,7 +32,7 @@
<rev>%VSN%</rev>
<file>et_selector.xml</file>
</header>
- <module>et_selector</module>
+ <module since="">et_selector</module>
<modulesummary>Define event transforms and trace patterns</modulesummary>
<description>
<p></p>
@@ -40,7 +40,7 @@
<funcs>
<func>
- <name>make_pattern(RawPattern) -> TracePattern</name>
+ <name since="">make_pattern(RawPattern) -> TracePattern</name>
<fsummary>Makes a trace pattern suitable to feed change_pattern/1</fsummary>
<type>
@@ -61,7 +61,7 @@
</func>
<func>
- <name>change_pattern(Pattern) -> ok</name>
+ <name since="">change_pattern(Pattern) -> ok</name>
<fsummary>Activates/deactivates tracing by changing the current trace pattern</fsummary>
@@ -85,7 +85,7 @@
</desc>
</func>
<func>
- <name>parse_event(Mod, ValidTraceData) -> false | true | {true, Event}</name>
+ <name since="">parse_event(Mod, ValidTraceData) -> false | true | {true, Event}</name>
<fsummary>Transforms trace data and makes an event record out of it</fsummary>
diff --git a/lib/et/doc/src/et_viewer.xml b/lib/et/doc/src/et_viewer.xml
index e0b39636e9..9d59eef668 100644
--- a/lib/et/doc/src/et_viewer.xml
+++ b/lib/et/doc/src/et_viewer.xml
@@ -32,14 +32,14 @@
<rev>%VSN%</rev>
<file>et_viewer.xml</file>
</header>
- <module>et_viewer</module>
+ <module since="">et_viewer</module>
<modulesummary>Displays a sequence chart for trace events (messages/actions)</modulesummary>
<description>
<p></p>
</description>
<funcs>
<func>
- <name>file(FileName) -> {ok, ViewerPid} | {error, Reason}</name>
+ <name since="">file(FileName) -> {ok, ViewerPid} | {error, Reason}</name>
<fsummary>Start a new event viewer and a corresponding collector and load them with trace events from a trace file.</fsummary>
<type>
<v>FileName() = string()</v>
@@ -52,7 +52,7 @@
</desc>
</func>
<func>
- <name>start() -> ok</name>
+ <name since="">start() -> ok</name>
<fsummary>Simplified start of a sequence chart viewer with global tracing activated.</fsummary>
<desc>
<p>Simplified start of a sequence chart viewer with
@@ -62,7 +62,7 @@
</desc>
</func>
<func>
- <name>start(Options) -> ok</name>
+ <name since="">start(Options) -> ok</name>
<fsummary>Start of a sequence chart viewer without linking to the parent process.</fsummary>
<desc>
<p>Start of a sequence chart viewer without linking
@@ -70,7 +70,7 @@
</desc>
</func>
<func>
- <name>start_link(Options) -> {ok, ViewerPid} | {error, Reason}</name>
+ <name since="">start_link(Options) -> {ok, ViewerPid} | {error, Reason}</name>
<fsummary>Start a sequence chart viewer for trace events (messages/actions)</fsummary>
<type>
<v>Options = [option() | collector_option()]</v>
@@ -125,7 +125,7 @@
</desc>
</func>
<func>
- <name>get_collector_pid(ViewerPid) -> CollectorPid</name>
+ <name since="">get_collector_pid(ViewerPid) -> CollectorPid</name>
<fsummary>Returns the identifier of the collector process</fsummary>
<type>
<v>ViewerPid = pid()</v>
@@ -136,7 +136,7 @@
</desc>
</func>
<func>
- <name>stop(ViewerPid) -> ok</name>
+ <name since="">stop(ViewerPid) -> ok</name>
<fsummary>Stops a viewer</fsummary>
<type>
<v>ViewerPid = pid()</v>
diff --git a/lib/ftp/doc/src/ftp.xml b/lib/ftp/doc/src/ftp.xml
index 34e3ff84b0..9645b03364 100644
--- a/lib/ftp/doc/src/ftp.xml
+++ b/lib/ftp/doc/src/ftp.xml
@@ -29,7 +29,7 @@
<rev>B</rev>
<file>ftp.xml</file>
</header>
- <module>ftp</module>
+ <module since="">ftp</module>
<modulesummary>A File Transfer Protocol client.</modulesummary>
<description>
@@ -272,7 +272,7 @@
<funcs>
<func>
- <name>account(Pid, Account) -> ok | {error, Reason}</name>
+ <name since="">account(Pid, Account) -> ok | {error, Reason}</name>
<fsummary>Specifies which account to use.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -289,8 +289,8 @@
</func>
<func>
- <name>append(Pid, LocalFile) -> </name>
- <name>append(Pid, LocalFile, RemoteFile) -> ok | {error, Reason}</name>
+ <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
<c>Remotefile</c>.</fsummary>
<type>
@@ -310,7 +310,7 @@
</func>
<func>
- <name>append_bin(Pid, Bin, RemoteFile) -> ok | {error, Reason}</name>
+ <name since="">append_bin(Pid, Bin, RemoteFile) -> ok | {error, Reason}</name>
<fsummary>Transfers a binary into a remote file.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -328,7 +328,7 @@
</func>
<func>
- <name>append_chunk(Pid, Bin) -> ok | {error, Reason}</name>
+ <name since="">append_chunk(Pid, Bin) -> ok | {error, Reason}</name>
<fsummary>Appends a chunk to the remote file.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -348,7 +348,7 @@
</func>
<func>
- <name>append_chunk_start(Pid, File) -> ok | {error, Reason}</name>
+ <name since="">append_chunk_start(Pid, File) -> ok | {error, Reason}</name>
<fsummary>Starts transfer of file chunks for appending to <c>File</c>.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -365,7 +365,7 @@
</func>
<func>
- <name>append_chunk_end(Pid) -> ok | {error, Reason}</name>
+ <name since="">append_chunk_end(Pid) -> ok | {error, Reason}</name>
<fsummary>Stops transfer of chunks for appending.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -381,7 +381,7 @@
</func>
<func>
- <name>cd(Pid, Dir) -> ok | {error, Reason}</name>
+ <name since="">cd(Pid, Dir) -> ok | {error, Reason}</name>
<fsummary>Changes remote working directory.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -397,7 +397,7 @@
</func>
<func>
- <name>close(Pid) -> ok</name>
+ <name since="">close(Pid) -> ok</name>
<fsummary>Ends the FTP session.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -411,7 +411,7 @@
</func>
<func>
- <name>delete(Pid, File) -> ok | {error, Reason}</name>
+ <name since="">delete(Pid, File) -> ok | {error, Reason}</name>
<fsummary>Deletes a file at the remote server.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -426,7 +426,7 @@
</func>
<func>
- <name>formaterror(Tag) -> string()</name>
+ <name since="">formaterror(Tag) -> string()</name>
<fsummary>Returns error diagnostics.</fsummary>
<type>
<v>Tag = {error, atom()} | atom()</v>
@@ -440,7 +440,7 @@
</func>
<func>
- <name>lcd(Pid, Dir) -> ok | {error, Reason}</name>
+ <name since="">lcd(Pid, Dir) -> ok | {error, Reason}</name>
<fsummary>Changes local working directory.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -455,7 +455,7 @@
</func>
<func>
- <name>lpwd(Pid) -> {ok, Dir}</name>
+ <name since="">lpwd(Pid) -> {ok, Dir}</name>
<fsummary>Gets local current working directory.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -470,8 +470,8 @@
</func>
<func>
- <name>ls(Pid) -> </name>
- <name>ls(Pid, Pathname) -> {ok, Listing} | {error, Reason}</name>
+ <name since="">ls(Pid) -> </name>
+ <name since="">ls(Pid, Pathname) -> {ok, Listing} | {error, Reason}</name>
<fsummary>List of files.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -493,7 +493,7 @@
</func>
<func>
- <name>mkdir(Pid, Dir) -> ok | {error, Reason}</name>
+ <name since="">mkdir(Pid, Dir) -> ok | {error, Reason}</name>
<fsummary>Creates a remote directory.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -510,8 +510,8 @@
</func>
<func>
- <name>nlist(Pid) -> </name>
- <name>nlist(Pid, Pathname) -> {ok, Listing} | {error, Reason}</name>
+ <name since="">nlist(Pid) -> </name>
+ <name since="">nlist(Pid, Pathname) -> {ok, Listing} | {error, Reason}</name>
<fsummary>List of files.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -535,8 +535,8 @@
</func>
<func>
- <name>open(Host) -> {ok, Pid} | {error, Reason}</name>
- <name>open(Host, Opts) -> {ok, Pid} | {error, Reason}</name>
+ <name since="">open(Host) -> {ok, Pid} | {error, Reason}</name>
+ <name since="">open(Host, Opts) -> {ok, Pid} | {error, Reason}</name>
<fsummary>Starts a standalone FTP client.</fsummary>
<type>
<v>Host = string() | ip_address()</v>
@@ -550,7 +550,7 @@
<v>ipfamily() = inet | inet6 | inet6fb4 (default is inet)</v>
<v>port() = integer() > 0 (default is 21)</v>
<v>mode() = active | passive (default is passive)</v>
- <v>tls_options() = [<seealso marker="ssl:ssl#type-ssloption">ssl:ssloption()</seealso>]</v>
+ <v>tls_options() = [<seealso marker="ssl:ssl#type-tls_option">ssl:tls_option()</seealso>]</v>
<v>sock_opts() = [<seealso marker="kernel:gen_tcp#type-option">gen_tcp:option()</seealso> 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>
@@ -587,7 +587,7 @@
</func>
<func>
- <name>pwd(Pid) -> {ok, Dir} | {error, Reason}</name>
+ <name since="">pwd(Pid) -> {ok, Dir} | {error, Reason}</name>
<fsummary>Gets the remote current working directory.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -603,8 +603,8 @@
</func>
<func>
- <name>recv(Pid, RemoteFile) -> </name>
- <name>recv(Pid, RemoteFile, LocalFile) -> ok | {error, Reason}</name>
+ <name since="">recv(Pid, RemoteFile) -> </name>
+ <name since="">recv(Pid, RemoteFile, LocalFile) -> ok | {error, Reason}</name>
<fsummary>Transfers a file from remote server.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -627,7 +627,7 @@
</func>
<func>
- <name>recv_bin(Pid, RemoteFile) -> {ok, Bin} | {error, Reason}</name>
+ <name since="">recv_bin(Pid, RemoteFile) -> {ok, Bin} | {error, Reason}</name>
<fsummary>Transfers a file from remote server as a binary.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -644,7 +644,7 @@
</func>
<func>
- <name>recv_chunk_start(Pid, RemoteFile) -> ok | {error, Reason}</name>
+ <name since="">recv_chunk_start(Pid, RemoteFile) -> ok | {error, Reason}</name>
<fsummary>Starts chunk-reading of the remote file.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -660,7 +660,7 @@
</func>
<func>
- <name>recv_chunk(Pid) -> ok | {ok, Bin} | {error, Reason}</name>
+ <name since="">recv_chunk(Pid) -> ok | {ok, Bin} | {error, Reason}</name>
<fsummary>Receives a chunk of the remote file.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -682,7 +682,7 @@
</func>
<func>
- <name>rename(Pid, Old, New) -> ok | {error, Reason}</name>
+ <name since="">rename(Pid, Old, New) -> ok | {error, Reason}</name>
<fsummary>Renames a file at the remote server.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -697,7 +697,7 @@
</func>
<func>
- <name>rmdir(Pid, Dir) -> ok | {error, Reason}</name>
+ <name since="">rmdir(Pid, Dir) -> ok | {error, Reason}</name>
<fsummary>Removes a remote directory.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -714,8 +714,8 @@
</func>
<func>
- <name>send(Pid, LocalFile) -></name>
- <name>send(Pid, LocalFile, RemoteFile) -> ok | {error, Reason}</name>
+ <name since="">send(Pid, LocalFile) -></name>
+ <name since="">send(Pid, LocalFile, RemoteFile) -> ok | {error, Reason}</name>
<fsummary>Transfers a file to the remote server.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -732,7 +732,7 @@
</func>
<func>
- <name>send_bin(Pid, Bin, RemoteFile) -> ok | {error, Reason}</name>
+ <name since="">send_bin(Pid, Bin, RemoteFile) -> ok | {error, Reason}</name>
<fsummary>Transfers a binary into a remote file.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -749,7 +749,7 @@
</func>
<func>
- <name>send_chunk(Pid, Bin) -> ok | {error, Reason}</name>
+ <name since="">send_chunk(Pid, Bin) -> ok | {error, Reason}</name>
<fsummary>Writes a chunk to the remote file.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -769,7 +769,7 @@
</func>
<func>
- <name>send_chunk_start(Pid, File) -> ok | {error, Reason}</name>
+ <name since="">send_chunk_start(Pid, File) -> ok | {error, Reason}</name>
<fsummary>Starts transfer of file chunks.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -785,7 +785,7 @@
</func>
<func>
- <name>send_chunk_end(Pid) -> ok | {error, Reason}</name>
+ <name since="">send_chunk_end(Pid) -> ok | {error, Reason}</name>
<fsummary>Stops transfer of chunks.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -801,7 +801,7 @@
</func>
<func>
- <name>start_service(ServiceConfig) -> {ok, Pid} | {error, Reason}</name>
+ <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>
@@ -820,7 +820,7 @@
</func>
<func>
- <name>stop_service(Reference) -> ok | {error, Reason} </name>
+ <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>
@@ -832,7 +832,7 @@
</func>
<func>
- <name>type(Pid, Type) -> ok | {error, Reason}</name>
+ <name since="">type(Pid, Type) -> ok | {error, Reason}</name>
<fsummary>Sets transfer type to <c>ascii</c>or <c>binary</c>.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -849,7 +849,7 @@
</func>
<func>
- <name>user(Pid, User, Password) -> ok | {error, Reason}</name>
+ <name since="">user(Pid, User, Password) -> ok | {error, Reason}</name>
<fsummary>User login.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -864,7 +864,7 @@
</func>
<func>
- <name>user(Pid, User, Password, Account) -> ok | {error, Reason}</name>
+ <name since="">user(Pid, User, Password, Account) -> ok | {error, Reason}</name>
<fsummary>User login.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -880,7 +880,7 @@
</func>
<func>
- <name>quote(Pid, Command) -> [FTPLine]</name>
+ <name since="">quote(Pid, Command) -> [FTPLine]</name>
<fsummary>Sends an arbitrary FTP command.</fsummary>
<type>
<v>Pid = pid()</v>
diff --git a/lib/hipe/cerl/erl_bif_types.erl b/lib/hipe/cerl/erl_bif_types.erl
index 48ce641ab9..8ae1cd4ab7 100644
--- a/lib/hipe/cerl/erl_bif_types.erl
+++ b/lib/hipe/cerl/erl_bif_types.erl
@@ -2225,10 +2225,7 @@ type_order() ->
t_map(), t_list(), t_bitstr()].
key_comparisons_fail(X0, KeyPos, TupleList, Opaques) ->
- X = case t_is_number(t_inf(X0, t_number(), Opaques), Opaques) of
- false -> X0;
- true -> t_number()
- end,
+ X = erl_types:t_widen_to_number(X0),
lists:all(fun(Tuple) ->
Key = type(erlang, element, 2, [KeyPos, Tuple]),
t_is_none(t_inf(Key, X, Opaques))
diff --git a/lib/hipe/cerl/erl_types.erl b/lib/hipe/cerl/erl_types.erl
index 9abb4d31d9..d61cd8664c 100644
--- a/lib/hipe/cerl/erl_types.erl
+++ b/lib/hipe/cerl/erl_types.erl
@@ -66,7 +66,6 @@
t_find_opaque_mismatch/3,
t_find_unknown_opaque/3,
t_fixnum/0,
- t_map/2,
t_non_neg_fixnum/0,
t_pos_fixnum/0,
t_float/0,
@@ -205,6 +204,7 @@
t_unopaque/1, t_unopaque/2,
t_var/1,
t_var_name/1,
+ t_widen_to_number/1,
%% t_assign_variables_to_subtype/2,
type_is_defined/4,
record_field_diffs_to_string/2,
@@ -1594,6 +1594,50 @@ lift_list_to_pos_empty(?nil) -> ?nil;
lift_list_to_pos_empty(?list(Content, Termination, _)) ->
?list(Content, Termination, ?unknown_qual).
+-spec t_widen_to_number(erl_type()) -> erl_type().
+
+%% Widens integers and floats to t_number().
+%% Used by erl_bif_types:key_comparison_fail().
+
+t_widen_to_number(?any) -> ?any;
+t_widen_to_number(?none) -> ?none;
+t_widen_to_number(?unit) -> ?unit;
+t_widen_to_number(?atom(_Set) = T) -> T;
+t_widen_to_number(?bitstr(_Unit, _Base) = T) -> T;
+t_widen_to_number(?float) -> t_number();
+t_widen_to_number(?function(Domain, Range)) ->
+ ?function(t_widen_to_number(Domain), t_widen_to_number(Range));
+t_widen_to_number(?identifier(_Types) = T) -> T;
+t_widen_to_number(?int_range(_From, _To)) -> t_number();
+t_widen_to_number(?int_set(_Set)) -> t_number();
+t_widen_to_number(?integer(_Types)) -> t_number();
+t_widen_to_number(?list(Type, Tail, Size)) ->
+ ?list(t_widen_to_number(Type), t_widen_to_number(Tail), Size);
+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(ordsets:from_list(L));
+t_widen_to_number(?product(Types)) ->
+ ?product(list_widen_to_number(Types));
+t_widen_to_number(?tuple(?any, _, _) = T) -> T;
+t_widen_to_number(?tuple(Types, Arity, Tag)) ->
+ ?tuple(list_widen_to_number(Types), Arity, Tag);
+t_widen_to_number(?tuple_set(_) = Tuples) ->
+ t_sup([t_widen_to_number(T) || T <- t_tuple_subtypes(Tuples)]);
+t_widen_to_number(?union(List)) ->
+ ?union(list_widen_to_number(List));
+t_widen_to_number(?var(_Id)= T) -> T.
+
+list_widen_to_number(List) ->
+ [t_widen_to_number(E) || E <- List].
+
%%-----------------------------------------------------------------------------
%% Maps
%%
@@ -3104,9 +3148,18 @@ is_compat_arg(?list(Contents1, Termination1, Size1),
is_compat_arg(?product(Types1), ?product(Types2)) ->
is_compat_list(Types1, Types2);
is_compat_arg(?map(Pairs1, DefK1, DefV1), ?map(Pairs2, DefK2, DefV2)) ->
- (is_compat_list(Pairs1, Pairs2) andalso
- is_compat_arg(DefK1, DefK2) andalso
- is_compat_arg(DefV1, DefV2));
+ {Ks1, _, Vs1} = lists:unzip3(Pairs1),
+ {Ks2, _, Vs2} = lists:unzip3(Pairs2),
+ Key1 = t_sup([DefK1 | Ks1]),
+ Key2 = t_sup([DefK2 | Ks2]),
+ case is_compat_arg(Key1, Key2) of
+ true ->
+ Value1 = t_sup([DefV1 | Vs1]),
+ Value2 = t_sup([DefV2 | Vs2]),
+ is_compat_arg(Value1, Value2);
+ false ->
+ false
+ end;
is_compat_arg(?tuple(?any, ?any, ?any), ?tuple(_, _, _)) -> false;
is_compat_arg(?tuple(_, _, _), ?tuple(?any, ?any, ?any)) -> false;
is_compat_arg(?tuple(Elements1, Arity, _),
@@ -4156,39 +4209,6 @@ t_abstract_records(?opaque(_)=Type, RecDict) ->
t_abstract_records(T, _RecDict) ->
T.
-%% Map over types. Depth first. Used by the contract checker. ?list is
-%% not fully implemented so take care when changing the type in Termination.
-
--spec t_map(fun((erl_type()) -> erl_type()), erl_type()) -> erl_type().
-
-t_map(Fun, ?list(Contents, Termination, Size)) ->
- Fun(?list(t_map(Fun, Contents), t_map(Fun, Termination), Size));
-t_map(Fun, ?function(Domain, Range)) ->
- Fun(?function(t_map(Fun, Domain), t_map(Fun, Range)));
-t_map(Fun, ?product(Types)) ->
- Fun(?product([t_map(Fun, T) || T <- Types]));
-t_map(Fun, ?union(Types)) ->
- Fun(t_sup([t_map(Fun, T) || T <- Types]));
-t_map(Fun, ?tuple(?any, ?any, ?any) = T) ->
- Fun(T);
-t_map(Fun, ?tuple(Elements, _Arity, _Tag)) ->
- Fun(t_tuple([t_map(Fun, E) || E <- Elements]));
-t_map(Fun, ?tuple_set(_) = Tuples) ->
- Fun(t_sup([t_map(Fun, T) || T <- t_tuple_subtypes(Tuples)]));
-t_map(Fun, ?opaque(Set)) ->
- L = [Opaque#opaque{struct = NewS} ||
- #opaque{struct = S} = Opaque <- set_to_list(Set),
- not t_is_none(NewS = t_map(Fun, S))],
- Fun(case L of
- [] -> ?none;
- _ -> ?opaque(ordsets:from_list(L))
- end);
-t_map(Fun, ?map(Pairs,DefK,DefV)) ->
- %% TODO:
- Fun(t_map(Pairs, Fun(DefK), Fun(DefV)));
-t_map(Fun, T) ->
- Fun(T).
-
%%=============================================================================
%%
%% Prettyprinter
diff --git a/lib/hipe/doc/src/hipe_app.xml b/lib/hipe/doc/src/hipe_app.xml
index 63bc6ea2d7..480290cd9e 100644
--- a/lib/hipe/doc/src/hipe_app.xml
+++ b/lib/hipe/doc/src/hipe_app.xml
@@ -62,6 +62,13 @@
and the runtime system that have limited or no support for HiPE compiled modules.
</p>
<taglist>
+ <tag>Binary matching</tag>
+ <item><p>The HiPE compiler will crash on modules containing binary
+ matching unless they have been compiled with the <c>+no_bsm3</c> flag.
+ Note that this will disable all related optimizations done by the BEAM
+ compiler.</p>
+ </item>
+
<tag>Stack traces</tag>
<item><p>Stack traces returned from <seealso marker="erts:erlang#get_stacktrace/0">
<c>erlang:get_stacktrace/0</c></seealso> or as part of <c>'EXIT'</c> terms
@@ -78,12 +85,12 @@
</item>
<tag>NIFs</tag>
- <item><p>Modules compiled with HiPE can not call <seealso marker="erts:erlang#load_nif-2">
+ <item><p>Modules compiled with HiPE cannot call <seealso marker="erts:erlang#load_nif-2">
<c>erlang:load_nif/2</c></seealso> to load NIFs.</p>
</item>
<tag>-on_load</tag>
- <item><p>Modules compiled with HiPE can not use
+ <item><p>Modules compiled with HiPE cannot use
<seealso marker="doc/reference_manual:code_loading#on_load"><c>-on_load()</c></seealso>
directives.</p>
</item>
diff --git a/lib/hipe/icode/hipe_beam_to_icode.erl b/lib/hipe/icode/hipe_beam_to_icode.erl
index f429d40272..ffe81ef9b8 100644
--- a/lib/hipe/icode/hipe_beam_to_icode.erl
+++ b/lib/hipe/icode/hipe_beam_to_icode.erl
@@ -647,6 +647,13 @@ trans_fun([{put_tuple,_Size,Reg}|Instructions], Env) ->
Primop = hipe_icode:mk_primop(Dest,mktuple,Src),
Moves ++ [Primop | trans_fun(Instructions2,Env2)];
%%--- put --- SHOULD NOT REALLY EXIST HERE; put INSTRUCTIONS ARE HANDLED ABOVE.
+%%--- put_tuple2 ---
+trans_fun([{put_tuple2,Reg,{list,Elements}}|Instructions], Env) ->
+ Dest = [mk_var(Reg)],
+ {Moves,Vars,Env2} = trans_elements(Elements, [], [], Env),
+ Src = lists:reverse(Vars),
+ Primop = hipe_icode:mk_primop(Dest, mktuple, Src),
+ Moves ++ [Primop | trans_fun(Instructions, Env2)];
%%--- badmatch ---
trans_fun([{badmatch,Arg}|Instructions], Env) ->
BadVar = trans_arg(Arg),
@@ -1139,9 +1146,10 @@ trans_fun([{test,has_map_fields,{f,Lbl},Map,{list,Keys}}|Instructions], Env) ->
lists:flatten([[K, {r, 0}] || K <- Keys])),
[MapMove, TestInstructions | trans_fun(Instructions, Env2)];
trans_fun([{get_map_elements,{f,Lbl},Map,{list,KVPs}}|Instructions], Env) ->
+ KVPs1 = overwrite_map_last(Map, KVPs),
{MapMove, MapVar, Env1} = mk_move_and_var(Map, Env),
{TestInstructions, GetInstructions, Env2} =
- trans_map_query(MapVar, map_label(Lbl), Env1, KVPs),
+ trans_map_query(MapVar, map_label(Lbl), Env1, KVPs1),
[MapMove, TestInstructions, GetInstructions | trans_fun(Instructions, Env2)];
%%--- put_map_assoc ---
trans_fun([{put_map_assoc,{f,Lbl},Map,Dst,_N,{list,Pairs}}|Instructions], Env) ->
@@ -1563,6 +1571,21 @@ trans_type_test2(function2, Lbl, Arg, Arity, Env) ->
hipe_icode:label_name(True), map_label(Lbl)),
{[Move1,Move2,I,True],Env2}.
+
+%%
+%% Makes sure that if a get_map_elements instruction will overwrite
+%% the map source, it will be done last.
+%%
+overwrite_map_last(Map, KVPs) ->
+ overwrite_map_last2(Map, KVPs, []).
+
+overwrite_map_last2(Map, [Key,Map|KVPs], _Last) ->
+ overwrite_map_last2(Map, KVPs, [Key,Map]);
+overwrite_map_last2(Map, [Key,Val|KVPs], Last) ->
+ [Key,Val|overwrite_map_last2(Map, KVPs, Last)];
+overwrite_map_last2(_Map, [], Last) ->
+ Last.
+
%%
%% Handles the get_map_elements instruction and the has_map_fields
%% test instruction.
@@ -1683,6 +1706,19 @@ trans_puts([{put,X}|Code], Vars, Moves, Env) ->
trans_puts(Code, Vars, Moves, Env) -> %% No more put operations
{Moves, Code, Vars, Env}.
+trans_elements([X|Code], Vars, Moves, Env) ->
+ case type(X) of
+ var ->
+ Var = mk_var(X),
+ trans_elements(Code, [Var|Vars], Moves, Env);
+ #beam_const{value=C} ->
+ Var = mk_var(new),
+ Move = hipe_icode:mk_move(Var, hipe_icode:mk_const(C)),
+ trans_elements(Code, [Var|Vars], [Move|Moves], Env)
+ end;
+trans_elements([], Vars, Moves, Env) ->
+ {Moves, Vars, Env}.
+
%%-----------------------------------------------------------------------
%% The code for this instruction is a bit large because we are treating
%% different cases differently. We want to use the icode `type'
diff --git a/lib/hipe/icode/hipe_icode_range.erl b/lib/hipe/icode/hipe_icode_range.erl
index 34b18acccd..098a7a8d8c 100644
--- a/lib/hipe/icode/hipe_icode_range.erl
+++ b/lib/hipe/icode/hipe_icode_range.erl
@@ -1633,7 +1633,7 @@ inf_bsl(_, pos_inf) -> neg_inf;
inf_bsl(Number, neg_inf) when is_integer(Number), Number >= 0 -> 0;
inf_bsl(_Number, neg_inf) -> -1;
inf_bsl(Number1, Number2) when is_integer(Number1), is_integer(Number2) ->
- %% We can not shift left with a number which is not a fixnum. We
+ %% We cannot shift left with a number which is not a fixnum. We
%% don't have enough memory.
Bits = ?BITS,
if Number2 > (Bits bsl 1) -> inf_bsl(Number1, pos_inf);
diff --git a/lib/hipe/llvm/hipe_llvm_main.erl b/lib/hipe/llvm/hipe_llvm_main.erl
index 54c435c127..44f0566379 100644
--- a/lib/hipe/llvm/hipe_llvm_main.erl
+++ b/lib/hipe/llvm/hipe_llvm_main.erl
@@ -526,8 +526,8 @@ unique_folder(FunName, Arity, Options) ->
case proplists:get_bool(llvm_save_temps, Options) of
true -> %% Store folder in current directory
DirName;
- false -> %% Temporarily store folder in tempfs (/dev/shm/)
- "/dev/shm/" ++ DirName
+ false -> %% Temporarily store folder in tempfs or tmp dir
+ tmpfs_folder() ++ DirName
end,
%% Make sure it does not exist
case dir_exists(Dir) of
@@ -537,6 +537,14 @@ unique_folder(FunName, Arity, Options) ->
Dir
end.
+tmpfs_folder() ->
+ case os:type() of
+ {unix, linux} ->
+ "/dev/shm/";
+ {unix, _} -> %% Fallback to tmp dir. e.g. FreeBSD
+ "/tmp/"
+ end.
+
%% @doc Function that checks that a given Filename is an existing Directory
%% Name (from http://rosettacode.org/wiki/Ensure_that_a_file_exists#Erlang)
dir_exists(Filename) ->
diff --git a/lib/hipe/main/hipe.erl b/lib/hipe/main/hipe.erl
index e2cb9c0f0b..2348e9b1f6 100644
--- a/lib/hipe/main/hipe.erl
+++ b/lib/hipe/main/hipe.erl
@@ -196,7 +196,7 @@
file/1,
file/2,
get_llvm_version/0,
- llvm_support_available/0,
+ erllvm_is_supported/0,
load/1,
help/0,
help_hiper/0,
@@ -218,12 +218,11 @@
%% Basic type declaration for exported functions of the 'hipe' module
%%-------------------------------------------------------------------
--type mod() :: atom().
--type f_unit() :: mod() | binary().
+-type mod() :: module().
+-type file_or_bin() :: file:filename() | binary().
-type ret_rtl() :: [_].
-type c_ret() :: {'ok', mod()} | {'error', term()} |
{'ok', mod(), ret_rtl()}. %% The last for debugging only
--type compile_file() :: atom() | string() | binary().
-type compile_ret() :: {hipe_architecture(), binary()} | list().
%%-------------------------------------------------------------------
@@ -233,26 +232,26 @@
%%-------------------------------------------------------------------
-%% @spec load(Mod) -> {module, Mod} | {error, Reason}
-%% Mod = mod()
+%% @spec load(Module) -> {module, Module} | {error, Reason}
+%% Module = mod()
%% Reason = term()
%%
%% @doc Like load/2, but tries to locate a BEAM file automatically.
%%
%% @see load/2
--spec load(Mod) -> {'module', Mod} | {'error', term()} when Mod :: mod().
+-spec load(Module) -> {'module', Module} | {'error', Reason :: term()}
+ when Module :: mod().
-load(Mod) ->
- load(Mod, beam_file(Mod)).
+load(Module) ->
+ load(Module, beam_file(Module)).
-%% @spec load(Mod, BeamFileName) -> {module, Mod} | {error, Reason}
-%% Mod = mod()
+%% @spec load(Module, BeamFileName) -> {module, Module} | {error, Reason}
+%% Module = mod()
+%% BeamFileName = file:filename()
%% Reason = term()
-%% BeamFileName = string()
-%% filename() = term()
%%
-%% @type mod() = atom(). A module name.
+%% @type mod() = module(). A module name.
%%
%% @doc User interface for loading code into memory. The code can be
%% given as a native code binary or as the file name of a BEAM file
@@ -262,8 +261,8 @@ load(Mod) ->
%%
%% @see load/1
--spec load(Mod, string()) -> {'module', Mod} | {'error', term()}
- when Mod :: mod().
+-spec load(Module, file:filename()) -> {'module', Module} | {'error', term()}
+ when Module :: mod().
load(Mod, BeamFileName) when is_list(BeamFileName) ->
Architecture = erlang:system_info(hipe_architecture),
@@ -273,26 +272,22 @@ load(Mod, BeamFileName) when is_list(BeamFileName) ->
Error -> {error, Error}
end.
-%% @spec c(Name) -> {ok, Name} | {error, Reason}
-%% Name = mod()
+%% @spec c(Mod) -> {ok, Mod} | {error, Reason}
+%% Mod = mod()
%% Reason = term()
%%
-%% @equiv c(Name, [])
+%% @equiv c(Mod, [])
-spec c(mod()) -> c_ret().
-c(Name) ->
- c(Name, []).
+c(Mod) ->
+ c(Mod, []).
-%% @spec c(Name, options()) -> {ok, Name} | {error, Reason}
-%% Name = mod()
+%% @spec c(Module, options()) -> {ok, Module} | {error, Reason}
+%% Module = mod()
%% options() = [option()]
%% option() = term()
%% Reason = term()
-%%
-%% @type fun() = atom(). A function identifier.
-%%
-%% @type arity() = integer(). A function arity; always nonnegative.
%%
%% @doc User-friendly native code compiler interface. Reads BEAM code
%% from the corresponding "Module<code>.beam</code>" file in the
@@ -307,12 +302,12 @@ c(Name) ->
-spec c(mod(), comp_options()) -> c_ret().
-c(Name, Options) ->
- c(Name, beam_file(Name), Options).
+c(Module, Options) ->
+ c(Module, beam_file(Module), Options).
-%% @spec c(Name, File, options()) -> {ok, Name} | {error, Reason}
-%% Name = mod()
-%% File = filename() | binary()
+%% @spec c(Module, File, options()) -> {ok, Module} | {error, Reason}
+%% Module = mod()
+%% File = file:filename() | binary()
%% Reason = term()
%%
%% @doc Like <code>c/2</code>, but reads BEAM code from the specified
@@ -321,32 +316,32 @@ c(Name, Options) ->
%% @see c/2
%% @see f/2
-c(Name, File, Opts) ->
+c(Module, File, Opts) ->
Opts1 = user_compile_opts(Opts),
- case compile(Name, File, Opts1) of
+ case compile(Module, File, Opts1) of
{ok, Res} ->
case proplists:get_bool(to_rtl, Opts1) of
- true -> {ok, Name, Res};
- false -> {ok, Name}
+ true -> {ok, Module, Res};
+ false -> {ok, Module}
end;
Other ->
Other
end.
%% @spec f(File) -> {ok, Name} | {error, Reason}
-%% File = filename() | binary()
+%% File = file:filename() | binary()
%% Name = mod()
%% Reason = term()
%%
%% @equiv f(File, [])
--spec f(f_unit()) -> {'ok', mod()} | {'error', term()}.
+-spec f(file_or_bin()) -> {'ok', mod()} | {'error', term()}.
f(File) ->
f(File, []).
%% @spec f(File, options()) -> {ok, Name} | {error, Reason}
-%% File = filename() | binary()
+%% File = file:filename() | binary()
%% Name = mod()
%% Reason = term()
%%
@@ -355,7 +350,7 @@ f(File) ->
%%
%% @see c/3
--spec f(f_unit(), comp_options()) -> {'ok', mod()} | {'error', term()}.
+-spec f(file_or_bin(), comp_options()) -> {'ok', mod()} | {'error', term()}.
f(File, Opts) ->
case file(File, user_compile_opts(Opts)) of
@@ -371,20 +366,20 @@ user_compile_opts(Opts) ->
Opts ++ ?USER_DEFAULTS.
-%% @spec compile(Name) -> {ok, {Target,Binary}} | {error, Reason}
-%% Name = mod()
+%% @spec compile(Module) -> {ok, {Target,Binary}} | {error, Reason}
+%% Module = mod()
%% Binary = binary()
%% Reason = term()
%%
-%% @equiv compile(Name, [])
+%% @equiv compile(Module, [])
-spec compile(mod()) -> {'ok', compile_ret()} | {'error', term()}.
-compile(Name) ->
- compile(Name, []).
+compile(Module) ->
+ compile(Module, []).
-%% @spec compile(Name, options()) -> {ok, {Target,Binary}} | {error, Reason}
-%% Name = mod()
+%% @spec compile(Module, options()) -> {ok, {Target,Binary}} | {error, Reason}
+%% Module = mod()
%% Binary = binary()
%% Reason = term()
%%
@@ -403,26 +398,26 @@ compile(Name) ->
%% @see file/2
%% @see load/2
--spec compile(mod(), comp_options()) -> {'ok', compile_ret()} | {'error', _}.
+-spec compile(mod(), comp_options()) -> {'ok', compile_ret()} | {'error', term()}.
-compile(Name, Options) ->
- compile(Name, beam_file(Name), Options).
+compile(Module, Options) ->
+ compile(Module, beam_file(Module), Options).
--spec beam_file(mod()) -> string().
+-spec beam_file(mod()) -> file:filename().
beam_file(Module) when is_atom(Module) ->
case code:which(Module) of
non_existing ->
- ?error_msg("Cannot find ~w.beam file.",[Module]),
+ ?error_msg("Cannot find ~w.beam file.", [Module]),
?EXIT({cant_find_beam_file,Module});
- File -> % string()
+ File when is_list(File) ->
File
end.
%% @spec compile(Name, File, options()) ->
%% {ok, {Target, Binary}} | {error, Reason}
%% Name = mod()
-%% File = filename() | binary()
+%% File = file:filename() | binary()
%% Binary = binary()
%% Reason = term()
%%
@@ -431,7 +426,7 @@ beam_file(Module) when is_atom(Module) ->
%%
%% @see compile/2
--spec compile(mod(), compile_file(), comp_options()) ->
+-spec compile(mod(), file_or_bin(), comp_options()) ->
{'ok', compile_ret()} | {'error', term()}.
compile(Name, File, Opts0) when is_atom(Name) ->
@@ -475,18 +470,18 @@ compile(Name, File, Opts0) when is_atom(Name) ->
run_compiler(Name, DisasmFun, IcodeFun, Opts)
end.
--spec compile_core(mod(), cerl:c_module(), compile_file(), comp_options()) ->
+-spec compile_core(mod(), cerl:c_module(), file_or_bin(), comp_options()) ->
{'ok', compile_ret()} | {'error', term()}.
compile_core(Name, Core0, File, Opts) ->
Core = cerl:from_records(Core0),
compile(Name, Core, File, Opts).
-%% @spec compile(Name, Core, File, options()) ->
+%% @spec compile(Module, Core, File, options()) ->
%% {ok, {Target, Binary}} | {error, Reason}
-%% Name = mod()
+%% Module = mod()
%% Core = coreErlang() | []
-%% File = filename() | binary()
+%% File = file:filename() | binary()
%% Binary = binary()
%% Reason = term()
%%
@@ -499,7 +494,7 @@ compile_core(Name, Core0, File, Opts) ->
%%
%% @see compile/3
--spec compile(mod(), cerl:c_module() | [], compile_file(), comp_options()) ->
+-spec compile(mod(), cerl:c_module() | [], file_or_bin(), comp_options()) ->
{'ok', compile_ret()} | {'error', term()}.
compile(Name, [], File, Opts) ->
@@ -511,38 +506,36 @@ compile(Name, Core, File, Opts) when is_atom(Name) ->
end,
run_compiler(Name, DisasmFun, IcodeFun, Opts).
-%% @spec file(File) -> {ok, Name, {Target, Binary}} | {error, Reason}
-%% File = filename() | binary()
-%% Name = mod() | mfa()
+%% @spec file(File) -> {ok, Mod, {Target, Binary}} | {error, Reason}
+%% File = file:filename()
+%% Mod = mod()
%% Binary = binary()
%% Reason = term()
%%
%% @equiv file(File, [])
--spec file(Mod) -> {'ok', Mod, compile_ret()} | {'error', term()}
- when Mod :: mod().
+-spec file(file:filename()) -> {'ok', mod(), compile_ret()} | {'error', term()}.
file(File) ->
file(File, []).
-%% @spec file(File, options()) -> {ok, Name, {Target,Binary}} | {error, Reason}
-%% File = filename()
-%% Name = mod() | mfa()
+%% @spec file(File, options()) -> {ok, Mod, {Target, Binary}} | {error, Reason}
+%% File = file:filename()
+%% Mod = mod()
%% Binary = binary()
%% Reason = term()
%%
%% @doc Like <code>compile/2</code>, but takes the module name from the
-%% specified <code>File</code>. Returns both the name and the final
+%% specified <code>File</code>. Returns both the module name and the final
%% binary if successful.
%%
%% @see file/1
%% @see compile/2
--spec file(Mod, comp_options()) -> {'ok', Mod, compile_ret()}
- | {'error', term()}
- when Mod :: mod().
-file(File, Options) when is_atom(File) ->
- case beam_lib:info(atom_to_list(File)) of
+-spec file(file:filename(), comp_options()) -> {'ok', mod(), compile_ret()}
+ | {'error', Reason :: term()}.
+file(File, Options) when is_list(File) ->
+ case beam_lib:info(File) of
L when is_list(L) ->
{module, Mod} = lists:keyfind(module, 1, L),
case compile(Mod, File, Options) of
@@ -653,7 +646,7 @@ run_compiler_1(Name, DisasmFun, IcodeFun, Options) ->
get(hipe_target_arch)),
Opts =
case proplists:get_bool(to_llvm, Opts0) andalso
- not llvm_support_available() of
+ not llvm_version_is_OK() of
true ->
?error_msg("No LLVM version 3.9 or greater "
"found in $PATH; aborting "
@@ -1607,9 +1600,15 @@ check_options(Opts) ->
ok
end.
--spec llvm_support_available() -> boolean().
+-spec erllvm_is_supported() -> boolean().
+erllvm_is_supported() ->
+ %% XXX: The test should really check the _target_ architecture,
+ %% (hipe_target_arch), but there's no guarantee it's set.
+ Arch = erlang:system_info(hipe_architecture),
+ lists:member(Arch, [amd64, x86]) andalso llvm_version_is_OK().
-llvm_support_available() ->
+-spec llvm_version_is_OK() -> boolean().
+llvm_version_is_OK() ->
get_llvm_version() >= {3,9}.
-type llvm_version() :: {Major :: integer(), Minor :: integer()}.
diff --git a/lib/hipe/rtl/hipe_icode2rtl.erl b/lib/hipe/rtl/hipe_icode2rtl.erl
index 6da8a76d34..1ab41f4deb 100644
--- a/lib/hipe/rtl/hipe_icode2rtl.erl
+++ b/lib/hipe/rtl/hipe_icode2rtl.erl
@@ -215,7 +215,7 @@ gen_enter(I, VarMap, ConstTab) ->
{Code1, ConstTab2} =
case hipe_icode:enter_type(I) of
primop ->
- IsGuard = false, % enter can not happen in a guard
+ IsGuard = false, % enter cannot happen in a guard
hipe_rtl_primops:gen_enter_primop({Fun, Args}, IsGuard, ConstTab1);
Type ->
Call = gen_enter_1(Fun, Args, Type),
diff --git a/lib/hipe/rtl/hipe_rtl_arith.inc b/lib/hipe/rtl/hipe_rtl_arith.inc
index c05b7aa160..575f10b542 100644
--- a/lib/hipe/rtl/hipe_rtl_arith.inc
+++ b/lib/hipe/rtl/hipe_rtl_arith.inc
@@ -118,8 +118,8 @@ eval_alu(Op, Arg1, Arg2) ->
%% Björn & Bjarni:
%% We need to be able to do evaluations based only on the bits, since
-%% there are cases where we can evaluate a subset of the bits, but can
-%% not do a full eval-alub call (eg. a + 0 gives no carry)
+%% there are cases where we can evaluate a subset of the bits, but
+%% cannot do a full eval-alub call (eg. a + 0 gives no carry)
%%
-spec eval_cond_bits(hipe_rtl:alub_cond(), boolean(),
boolean(), boolean(), boolean()) -> boolean().
diff --git a/lib/hipe/rtl/hipe_rtl_ssa_const_prop.erl b/lib/hipe/rtl/hipe_rtl_ssa_const_prop.erl
index cad43e2df5..72373e536d 100644
--- a/lib/hipe/rtl/hipe_rtl_ssa_const_prop.erl
+++ b/lib/hipe/rtl/hipe_rtl_ssa_const_prop.erl
@@ -31,11 +31,11 @@
%%
%% Some things to note:
%%
-%% 1. All precoloured registers are assumed to contain bottom. We can not
+%% 1. All precoloured registers are assumed to contain bottom. We cannot
%% do anything with them since they are not in SSA-form. This might be
%% possible to resolve in some way, but we decided to not go there.
%%
-%% 2. const_labels are assumed to be bottom, we can not find the address
+%% 2. const_labels are assumed to be bottom, we cannot find the address
%% in any nice way (that I know of, maybe someone can help ?). I
%% suppose they don't get a value until linking (or some step that
%% resembles it). They are only affecting bignums and floats (at least
@@ -579,7 +579,7 @@ visit_multimove(Inst, Env) ->
%% Procedure : visit_call/2
%% Purpose : execute a call-instruction. All calls return bottom. We make
%% this assumption since the icode-leel have taken care of BIF's
-%% and we belive that we are left with the things that can not be
+%% and we belive that we are left with the things that cannot be
%% done att compile time.
%% Arguments : Inst - The instruction
%% Env - The environment
diff --git a/lib/hipe/test/Makefile b/lib/hipe/test/Makefile
index efeb0887ab..3650cda663 100644
--- a/lib/hipe/test/Makefile
+++ b/lib/hipe/test/Makefile
@@ -13,7 +13,6 @@ MODULES= \
# .erl files for these modules are automatically generated
GEN_MODULES= \
basic_SUITE \
- bs_SUITE \
maps_SUITE \
sanity_SUITE
diff --git a/lib/hipe/test/basic_SUITE_data/basic_inline_function.erl b/lib/hipe/test/basic_SUITE_data/basic_inline_function.erl
deleted file mode 100644
index 4c08064670..0000000000
--- a/lib/hipe/test/basic_SUITE_data/basic_inline_function.erl
+++ /dev/null
@@ -1,73 +0,0 @@
-%%% -*- erlang-indent-level: 2 -*-
-%%%-------------------------------------------------------------------
-%%% Author: Kostis Sagonas
-%%%
-%%% Contains tests that depend on the compiler inliner being turned on.
-%%%-------------------------------------------------------------------
--module(basic_inline_function).
-
--export([test/0]).
-
--compile({inline, [{to_objects, 3}]}).
-
-test() ->
- ok = test_inline_match(),
- ok.
-
-%%--------------------------------------------------------------------
-
-test_inline_match() ->
- bad_object = test1(a, {binary, foo, set}, c),
- bad_object = test2(a, {binary, foo, set}, c),
- bad_object = test3(a, {binary, foo, set}, c),
- ok.
-
-%% Inlined
-test1(KeysObjs, C, Ts) ->
- case catch to_objects(KeysObjs, C, Ts) of
- {'EXIT', _} ->
- bad_object;
- ok ->
- ok
- end.
-
-%% "Inlined" by hand
-test2(KeysObjs, C, _Ts) ->
- case catch (case C of
- {binary, _, set} ->
- <<_ObjSz0:32, _T/binary>> = KeysObjs;
- _ -> ok
- end) of
- {'EXIT', _} ->
- bad_object;
- ok ->
- ok
- end.
-
-%% Not inlined
-test3(KeysObjs, C, Ts) ->
- case catch fto_objects(KeysObjs, C, Ts) of
- {'EXIT', _} ->
- bad_object;
- ok ->
- ok
- end.
-
-%% Inlined.
-to_objects(Bin, {binary, _, set}, _Ts) ->
- <<_ObjSz0:32, _T/binary>> = Bin,
- ok;
-to_objects(<<_ObjSz0:32, _T/binary>> ,_, _) ->
- ok;
-to_objects(_Bin, _, _Ts) ->
- ok.
-
-%% Not Inlined.
-fto_objects(Bin, {binary, _, set}, _Ts) ->
- <<_ObjSz0:32, _T/binary>> = Bin,
- ok;
-fto_objects(<<_ObjSz0:32, _T/binary>> ,_,_) ->
- ok;
-fto_objects(_Bin, _, _Ts) ->
- ok.
-
diff --git a/lib/hipe/test/hipe_testsuite_driver.erl b/lib/hipe/test/hipe_testsuite_driver.erl
index 8813af5dfc..c506dd5e1d 100644
--- a/lib/hipe/test/hipe_testsuite_driver.erl
+++ b/lib/hipe/test/hipe_testsuite_driver.erl
@@ -170,7 +170,7 @@ run(TestCase, Dir, _OutDir) ->
{ok, TestCase} = hipe:c(TestCase, [o0|HiPEOpts]),
ok = TestCase:test(),
ToLLVM = try TestCase:to_llvm() catch error:undef -> true end,
- case ToLLVM andalso hipe:llvm_support_available() of
+ case ToLLVM andalso hipe:erllvm_is_supported() of
true ->
{ok, TestCase} = hipe:c(TestCase, [to_llvm|HiPEOpts]),
ok = TestCase:test();
diff --git a/lib/inets/doc/src/http_uri.xml b/lib/inets/doc/src/http_uri.xml
index 2dec5acbf9..6d3547f4fe 100644
--- a/lib/inets/doc/src/http_uri.xml
+++ b/lib/inets/doc/src/http_uri.xml
@@ -30,7 +30,7 @@
<rev></rev>
</header>
- <module>http_uri</module>
+ <module since="OTP R15B01">http_uri</module>
<modulesummary>URI utility module</modulesummary>
<description>
@@ -79,7 +79,7 @@
<funcs>
<func>
- <name>decode(HexEncodedURI) -> URI</name>
+ <name since="OTP R15B01">decode(HexEncodedURI) -> URI</name>
<fsummary>Decodes a hexadecimal encoded URI.</fsummary>
<type>
@@ -93,7 +93,7 @@
</desc>
</func>
<func>
- <name>encode(URI) -> HexEncodedURI</name>
+ <name since="OTP R15B01">encode(URI) -> HexEncodedURI</name>
<fsummary>Encodes a hexadecimal encoded URI.</fsummary>
<type>
@@ -109,8 +109,8 @@
</func>
<func>
- <name>parse(URI) -> {ok, Result} | {error, Reason}</name>
- <name>parse(URI, Options) -> {ok, Result} | {error, Reason}</name>
+ <name since="OTP R15B01">parse(URI) -> {ok, Result} | {error, Reason}</name>
+ <name since="OTP R15B01">parse(URI, Options) -> {ok, Result} | {error, Reason}</name>
<fsummary>Parses a URI.</fsummary>
<type>
<v>URI = uri()</v>
@@ -165,7 +165,7 @@ fun(SchemeStr :: string() | binary()) ->
</func>
<func>
- <name>scheme_defaults() -> SchemeDefaults</name>
+ <name since="OTP R15B01">scheme_defaults() -> SchemeDefaults</name>
<fsummary>A list of the scheme and their default ports.</fsummary>
<type>
<v>SchemeDefaults = [{scheme(), default_scheme_port_number()}] </v>
diff --git a/lib/inets/doc/src/httpc.xml b/lib/inets/doc/src/httpc.xml
index a2871f3b95..7451b314ec 100644
--- a/lib/inets/doc/src/httpc.xml
+++ b/lib/inets/doc/src/httpc.xml
@@ -30,7 +30,7 @@
<rev></rev>
</header>
- <module>httpc</module>
+ <module since="OTP R13B04">httpc</module>
<modulesummary>An HTTP/1.1 client</modulesummary>
<description>
@@ -151,8 +151,8 @@
<funcs>
<func>
- <name>cancel_request(RequestId) -></name>
- <name>cancel_request(RequestId, Profile) -> ok</name>
+ <name since="OTP R13B04">cancel_request(RequestId) -></name>
+ <name since="OTP R13B04">cancel_request(RequestId, Profile) -> ok</name>
<fsummary>Cancels an asynchronous HTTP request.</fsummary>
<type>
<v>RequestId = request_id() - A unique identifier as returned
@@ -169,9 +169,9 @@
</func>
<func>
- <name>cookie_header(Url) -> </name>
- <name>cookie_header(Url, Profile | Opts) -> header() | {error, Reason}</name>
- <name>cookie_header(Url, Opts, Profile) -> header() | {error, Reason}</name>
+ <name since="OTP R13B04">cookie_header(Url) -> </name>
+ <name since="OTP R13B04">cookie_header(Url, Profile | Opts) -> header() | {error, Reason}</name>
+ <name since="OTP R15B">cookie_header(Url, Opts, Profile) -> header() | {error, Reason}</name>
<fsummary>Returns the cookie header that would have been sent when
making a request to URL using the profile <c>Profile</c>.</fsummary>
<type>
@@ -193,8 +193,8 @@
</func>
<func>
- <name>get_options(OptionItems) -> {ok, Values} | {error, Reason}</name>
- <name>get_options(OptionItems, Profile) -> {ok, Values} | {error, Reason}</name>
+ <name since="OTP R15B01">get_options(OptionItems) -> {ok, Values} | {error, Reason}</name>
+ <name since="OTP R15B01">get_options(OptionItems, Profile) -> {ok, Values} | {error, Reason}</name>
<fsummary>Gets the currently used options.</fsummary>
<type>
<v>OptionItems = all | [option_item()]</v>
@@ -223,8 +223,8 @@
</func>
<func>
- <name>info() -> list()</name>
- <name>info(Profile) -> list()</name>
+ <name since="OTP R15B02">info() -> list()</name>
+ <name since="OTP R15B02">info(Profile) -> list()</name>
<fsummary>Produces a list of miscellaneous information.</fsummary>
<type>
<v>Profile = profile() | pid()</v>
@@ -239,8 +239,8 @@
<func>
- <name>reset_cookies() -> void()</name>
- <name>reset_cookies(Profile) -> void()</name>
+ <name since="OTP R13B04">reset_cookies() -> void()</name>
+ <name since="OTP R13B04">reset_cookies(Profile) -> void()</name>
<fsummary>Resets the cookie database.</fsummary>
<type>
<v>Profile = profile() | pid()</v>
@@ -254,8 +254,8 @@
</func>
<func>
- <name>request(Url) -> </name>
- <name>request(Url, Profile) -> {ok, Result} | {error, Reason}</name>
+ <name since="OTP R13B04">request(Url) -> </name>
+ <name since="OTP R13B04">request(Url, Profile) -> {ok, Result} | {error, Reason}</name>
<fsummary>Sends a get HTTP request.</fsummary>
<type>
<v>Url = url()</v>
@@ -272,8 +272,8 @@
</func>
<func>
- <name>request(Method, Request, HTTPOptions, Options) -></name>
- <name>request(Method, Request, HTTPOptions, Options, Profile) -> {ok, Result} | {ok, saved_to_file} | {error, Reason}</name>
+ <name since="OTP R13B04">request(Method, Request, HTTPOptions, Options) -></name>
+ <name since="OTP R13B04">request(Method, Request, HTTPOptions, Options, Profile) -> {ok, Result} | {ok, saved_to_file} | {error, Reason}</name>
<fsummary>Sends an HTTP request.</fsummary>
<type>
@@ -521,8 +521,8 @@
<func>
- <name>set_options(Options) -> </name>
- <name>set_options(Options, Profile) -> ok | {error, Reason}</name>
+ <name since="OTP R13B04">set_options(Options) -> </name>
+ <name since="OTP R13B04">set_options(Options, Profile) -> ok | {error, Reason}</name>
<fsummary>Sets options to be used for subsequent requests.</fsummary>
<type>
<v>Options = [Option]</v>
@@ -639,8 +639,8 @@
</func>
<func>
- <name>store_cookies(SetCookieHeaders, Url) -> </name>
- <name>store_cookies(SetCookieHeaders, Url, Profile) -> ok | {error, Reason}</name>
+ <name since="OTP R14B02">store_cookies(SetCookieHeaders, Url) -> </name>
+ <name since="OTP R14B02">store_cookies(SetCookieHeaders, Url, Profile) -> ok | {error, Reason}</name>
<fsummary>Saves the cookies defined in <c>SetCookieHeaders</c> in the
client profile cookie database.</fsummary>
<type>
@@ -658,7 +658,7 @@
</func>
<func>
- <name>stream_next(Pid) -> ok</name>
+ <name since="OTP R13B04">stream_next(Pid) -> ok</name>
<fsummary>Triggers the next message to be streamed, that is,
the same behavior as active one for sockets.
</fsummary>
@@ -676,8 +676,8 @@
</func>
<func>
- <name>which_cookies() -> cookies()</name>
- <name>which_cookies(Profile) -> cookies()</name>
+ <name since="OTP R13B04">which_cookies() -> cookies()</name>
+ <name since="OTP R13B04">which_cookies(Profile) -> cookies()</name>
<fsummary>Dumps the entire cookie database.</fsummary>
<type>
<v>Profile = profile() | pid()</v>
@@ -695,8 +695,8 @@
</func>
<func>
- <name>which_sessions() -> session_info()</name>
- <name>which_sessions(Profile) -> session_info()</name>
+ <name since="OTP R15B02">which_sessions() -> session_info()</name>
+ <name since="OTP R15B02">which_sessions(Profile) -> session_info()</name>
<fsummary>Produces a slightly processed dump of the sessions database.</fsummary>
<type>
<v>Profile = profile() | pid()</v>
diff --git a/lib/inets/doc/src/httpd.xml b/lib/inets/doc/src/httpd.xml
index 2c70c2b050..1750078db0 100644
--- a/lib/inets/doc/src/httpd.xml
+++ b/lib/inets/doc/src/httpd.xml
@@ -29,7 +29,7 @@
<rev>2.2</rev>
<file>httpd.sgml</file>
</header>
- <module>httpd</module>
+ <module since="">httpd</module>
<modulesummary>
HTTP server API
</modulesummary>
@@ -173,7 +173,7 @@
<item>
<p>For <c>ip_comm</c> configuration options, see
<seealso marker="kernel:gen_tcp#listen-2">gen_tcp:listen/2</seealso>, some options
- that are used internally by httpd can not be set.</p>
+ that are used internally by httpd cannot be set.</p>
<p>For <c>SSL</c> configuration options, see
<seealso marker="ssl:ssl#listen-2">ssl:listen/2</seealso>.</p>
<p>Default is <c>ip_comm</c>.</p>
@@ -874,8 +874,8 @@ text/plain asc txt</pre>
<funcs>
<func>
- <name>info(Pid) -></name>
- <name>info(Pid, Properties) -> [{Option, Value}]</name>
+ <name since="">info(Pid) -></name>
+ <name since="">info(Pid, Properties) -> [{Option, Value}]</name>
<fsummary>Fetches information about the HTTP server.</fsummary>
<type>
<v>Properties = [property()]</v>
@@ -899,10 +899,10 @@ text/plain asc txt</pre>
</func>
<func>
- <name>info(Address, Port) -> </name>
- <name>info(Address, Port, Profile) -> </name>
- <name>info(Address, Port, Profile, Properties) -> [{Option, Value}] </name>
- <name>info(Address, Port, Properties) -> [{Option, Value}] </name>
+ <name since="">info(Address, Port) -> </name>
+ <name since="">info(Address, Port, Profile) -> </name>
+ <name since="OTP 18.0">info(Address, Port, Profile, Properties) -> [{Option, Value}] </name>
+ <name since="">info(Address, Port, Properties) -> [{Option, Value}] </name>
<fsummary>Fetches information about the HTTP server.</fsummary>
<type>
<v>Address = ip_address()</v>
@@ -927,7 +927,7 @@ text/plain asc txt</pre>
</func>
<func>
- <name>reload_config(Config, Mode) -> ok | {error, Reason}</name>
+ <name since="">reload_config(Config, Mode) -> ok | {error, Reason}</name>
<fsummary>Reloads the HTTP server configuration without
restarting the server.</fsummary>
<type>
@@ -1051,7 +1051,7 @@ text/plain asc txt</pre>
</section>
<funcs>
<func>
- <name>Module:do(ModData)-> {proceed, OldData} | {proceed, NewData} | {break, NewData} | done</name>
+ <name since="">Module:do(ModData)-> {proceed, OldData} | {proceed, NewData} | {break, NewData} | done</name>
<fsummary>Called for each request to the web server.</fsummary>
<type>
<v>OldData = list()</v>
@@ -1105,7 +1105,7 @@ text/plain asc txt</pre>
</func>
<func>
- <name>Module:load(Line, AccIn)-> eof | ok | {ok, AccOut} | {ok, AccOut, {Option, Value}} | {ok, AccOut, [{Option, Value}]} | {error, Reason}</name>
+ <name since="">Module:load(Line, AccIn)-> eof | ok | {ok, AccOut} | {ok, AccOut, {Option, Value}} | {ok, AccOut, [{Option, Value}]} | {error, Reason}</name>
<fsummary>Converts a line in an Apache-like config
file to an <c>{Option, Value}</c> tuple.</fsummary>
<type>
@@ -1128,7 +1128,7 @@ text/plain asc txt</pre>
</func>
<func>
- <name>Module:remove(ConfigDB) -> ok | {error, Reason} </name>
+ <name since="">Module:remove(ConfigDB) -> ok | {error, Reason} </name>
<fsummary>Callback function that is called when the web server is closed.</fsummary>
<type>
<v>ConfigDB = ets_table()</v>
@@ -1143,7 +1143,7 @@ text/plain asc txt</pre>
</func>
<func>
- <name>Module:store({Option, Value}, Config)-> {ok, {Option, NewValue}} | {error, Reason}</name>
+ <name since="">Module:store({Option, Value}, Config)-> {ok, {Option, NewValue}} | {error, Reason}</name>
<fsummary>Checks the validity of the configuration options.</fsummary>
<type>
<v>Line = string()</v>
@@ -1171,7 +1171,7 @@ text/plain asc txt</pre>
</section>
<funcs>
<func>
- <name>parse_query(QueryString) -> [{Key,Value}]</name>
+ <name since="">parse_query(QueryString) -> [{Key,Value}]</name>
<fsummary>Parses incoming data to <c>erl</c> and <c>eval</c> scripts.</fsummary>
<type>
<v>QueryString = string()</v>
diff --git a/lib/inets/doc/src/httpd_custom_api.xml b/lib/inets/doc/src/httpd_custom_api.xml
index d2e5441895..2c0f92ff83 100644
--- a/lib/inets/doc/src/httpd_custom_api.xml
+++ b/lib/inets/doc/src/httpd_custom_api.xml
@@ -25,7 +25,7 @@
<title>httpd_custom_api</title>
<file>httpd_custom_api.xml</file>
</header>
- <module>httpd_custom_api</module>
+ <module since="OTP 17.5.6">httpd_custom_api</module>
<modulesummary>Behaviour with optional callbacks to customize the inets HTTP server.</modulesummary>
<description>
<p> The module implementing this behaviour shall be supplied to to the servers
@@ -34,7 +34,7 @@
</description>
<funcs>
<func>
- <name>response_default_headers() -> [Header] </name>
+ <name since="OTP 18.1.1">response_default_headers() -> [Header] </name>
<fsummary>Provide default headers for the HTTP servers responses.</fsummary>
<type>
<v>Header = {HeaderName :: string(), HeaderValue::string()}</v>
@@ -48,7 +48,7 @@
</func>
<func>
- <name>response_header({HeaderName, HeaderValue}) -> {true, Header} | false </name>
+ <name since="OTP 17.5.6">response_header({HeaderName, HeaderValue}) -> {true, Header} | false </name>
<fsummary>Filter and possible alter HTTP response headers.</fsummary>
<type>
<v>Header = {HeaderName :: string(), HeaderValue::string()}</v>
@@ -61,7 +61,7 @@
</func>
<func>
- <name>request_header({HeaderName, HeaderValue}) -> {true, Header} | false </name>
+ <name since="OTP 17.5.6">request_header({HeaderName, HeaderValue}) -> {true, Header} | false </name>
<fsummary>Filter and possible alter HTTP request headers.</fsummary>
<type>
<v>Header = {HeaderName :: string(), HeaderValue::string()}</v>
diff --git a/lib/inets/doc/src/httpd_socket.xml b/lib/inets/doc/src/httpd_socket.xml
index d3aa82a540..22ead06f38 100644
--- a/lib/inets/doc/src/httpd_socket.xml
+++ b/lib/inets/doc/src/httpd_socket.xml
@@ -29,7 +29,7 @@
<rev>2.2</rev>
<file>httpd_socket.sgml</file>
</header>
- <module>httpd_socket</module>
+ <module since="">httpd_socket</module>
<modulesummary>Communication utility functions to be used by the Erlang
web server API programmer.</modulesummary>
<description>
@@ -43,7 +43,7 @@
<funcs>
<func>
- <name>deliver(SocketType, Socket, Data) -> Result</name>
+ <name since="">deliver(SocketType, Socket, Data) -> Result</name>
<fsummary>Sends binary data over socket.</fsummary>
<type>
<v>SocketType = socket_type()</v>
@@ -63,7 +63,7 @@
</func>
<func>
- <name>peername(SocketType,Socket) -> {Port,IPAddress}</name>
+ <name since="">peername(SocketType,Socket) -> {Port,IPAddress}</name>
<fsummary>Returns the port and IP address of the remote socket.</fsummary>
<type>
<v>SocketType = socket_type()</v>
@@ -81,7 +81,7 @@
</func>
<func>
- <name>resolve() -> HostName</name>
+ <name since="">resolve() -> HostName</name>
<fsummary>Returns the official name of the current host.</fsummary>
<type>
<v>HostName = string()</v>
diff --git a/lib/inets/doc/src/httpd_util.xml b/lib/inets/doc/src/httpd_util.xml
index 220a2ede35..e0f947f860 100644
--- a/lib/inets/doc/src/httpd_util.xml
+++ b/lib/inets/doc/src/httpd_util.xml
@@ -29,7 +29,7 @@
<rev>2.2</rev>
<file>httpd_util.sgml</file>
</header>
- <module>httpd_util</module>
+ <module since="">httpd_util</module>
<modulesummary>Miscellaneous utility functions to be used when implementing
Erlang web server API modules.</modulesummary>
<description>
@@ -41,12 +41,11 @@
<funcs>
<func>
- <name>convert_request_date(DateString) -> ErlDate|bad_date</name>
+ <name since="">convert_request_date(DateString) -> ErlDate|bad_date</name>
<fsummary>Converts the date to the Erlang date format.</fsummary>
<type>
<v>DateString = string()</v>
- <v>ErlDate = {{Year,Month,Date},{Hour,Min,Sec}}</v>
- <v>Year = Month = Date = Hour = Min = Sec = integer()</v>
+ <v>ErlDate = calendar:datetime() </v>
</type>
<desc>
<p><c>convert_request_date/1</c> converts <c>DateString</c> to
@@ -57,7 +56,7 @@
</func>
<func>
- <name>create_etag(FileInfo) -> Etag</name>
+ <name since="">create_etag(FileInfo) -> Etag</name>
<fsummary>Calculates the Etag for a file.</fsummary>
<type>
<v>FileInfo = file_info()</v>
@@ -71,7 +70,7 @@
</func>
<func>
- <name>day(NthDayOfWeek) -> DayOfWeek</name>
+ <name since="">day(NthDayOfWeek) -> DayOfWeek</name>
<fsummary>Converts the day of the week
(integer [1-7]) to an abbreviated string.</fsummary>
<type>
@@ -87,7 +86,7 @@
</func>
<func>
- <name>decode_hex(HexValue) -> DecValue</name>
+ <name since="">decode_hex(HexValue) -> DecValue</name>
<fsummary>Converts a hexadecimal value into its decimal equivalent.</fsummary>
<type>
<v>HexValue = DecValue = string()</v>
@@ -99,7 +98,7 @@
</func>
<func>
- <name>flatlength(NestedList) -> Size</name>
+ <name since="">flatlength(NestedList) -> Size</name>
<fsummary>Computes the size of a possibly nested list.</fsummary>
<type>
<v>NestedList = list()</v>
@@ -112,7 +111,7 @@
</func>
<func>
- <name>hexlist_to_integer(HexString) -> Number</name>
+ <name since="">hexlist_to_integer(HexString) -> Number</name>
<fsummary>Converts a hexadecimal string to an integer.</fsummary>
<type>
<v>Number = integer()</v>
@@ -125,7 +124,7 @@
</func>
<func>
- <name>integer_to_hexlist(Number) -> HexString</name>
+ <name since="">integer_to_hexlist(Number) -> HexString</name>
<fsummary>Converts an integer to a hexadecimal string.</fsummary>
<type>
<v>Number = integer()</v>
@@ -138,8 +137,8 @@
</func>
<func>
- <name>lookup(ETSTable,Key) -> Result</name>
- <name>lookup(ETSTable,Key,Undefined) -> Result</name>
+ <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>
in an ETS table.</fsummary>
<type>
@@ -160,8 +159,8 @@
</func>
<func>
- <name>lookup_mime(ConfigDB,Suffix)</name>
- <name>lookup_mime(ConfigDB,Suffix,Undefined) -> MimeType</name>
+ <name since="">lookup_mime(ConfigDB,Suffix)</name>
+ <name since="">lookup_mime(ConfigDB,Suffix,Undefined) -> MimeType</name>
<fsummary>Returns the MIME type associated with a specific file suffix.</fsummary>
<type>
<v>ConfigDB = ets_table()</v>
@@ -179,8 +178,8 @@
</func>
<func>
- <name>lookup_mime_default(ConfigDB,Suffix)</name>
- <name>lookup_mime_default(ConfigDB,Suffix,Undefined) -> MimeType</name>
+ <name since="">lookup_mime_default(ConfigDB,Suffix)</name>
+ <name since="">lookup_mime_default(ConfigDB,Suffix,Undefined) -> MimeType</name>
<fsummary>Returns the MIME type associated with a specific file suffix
or the value of the DefaultType.</fsummary>
<type>
@@ -201,7 +200,7 @@
</func>
<func>
- <name>message(StatusCode,PhraseArgs,ConfigDB) -> Message</name>
+ <name since="">message(StatusCode,PhraseArgs,ConfigDB) -> Message</name>
<fsummary>Returns an informative HTTP 1.1 status string in HTML.</fsummary>
<type>
<v>StatusCode = 301 | 400 | 403 | 404 | 500 | 501 | 504</v>
@@ -236,7 +235,7 @@
</func>
<func>
- <name>month(NthMonth) -> Month</name>
+ <name since="">month(NthMonth) -> Month</name>
<fsummary>Converts the month as an integer (1-12) to an abbreviated string.</fsummary>
<type>
<v>NthMonth = 1-12</v>
@@ -250,7 +249,7 @@
</func>
<func>
- <name>multi_lookup(ETSTable,Key) -> Result</name>
+ <name since="">multi_lookup(ETSTable,Key) -> Result</name>
<fsummary>Extracts the values associated with a key in an ETS table.</fsummary>
<type>
<v>ETSTable = ets_table()</v>
@@ -265,7 +264,7 @@
</func>
<func>
- <name>reason_phrase(StatusCode) -> Description</name>
+ <name since="">reason_phrase(StatusCode) -> Description</name>
<fsummary>Returns the description of an HTTP 1.1 status code.</fsummary>
<type>
<v>StatusCode = 100| 200 | 201 | 202 | 204 | 205 | 206 | 300 | 301 | 302 | 303 | 304 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 410 411 | 412 | 413 | 414 415 | 416 | 417 | 500 | 501 | 502 | 503 | 504 | 505</v>
@@ -280,11 +279,11 @@
</func>
<func>
- <name>rfc1123_date() -> RFC1123Date</name>
- <name>rfc1123_date({{YYYY,MM,DD},{Hour,Min,Sec}}) -> RFC1123Date</name>
+ <name since="">rfc1123_date() -> RFC1123Date</name>
+ <name since="">rfc1123_date(Date) -> RFC1123Date</name>
<fsummary>Returns the current date in RFC 1123 format.</fsummary>
<type>
- <v>YYYY = MM = DD = Hour = Min = Sec = integer()</v>
+ <v> Date = calendar:datetime()</v>
<v>RFC1123Date = string()</v>
</type>
<desc>
@@ -295,7 +294,7 @@
</func>
<func>
- <name>split(String,RegExp,N) -> SplitRes</name>
+ <name since="">split(String,RegExp,N) -> SplitRes</name>
<fsummary>Splits a string in N chunks using a regular expression.</fsummary>
<type>
<v>String = RegExp = string()</v>
@@ -313,7 +312,7 @@
</func>
<func>
- <name>split_script_path(RequestLine) -> Splitted</name>
+ <name since="">split_script_path(RequestLine) -> Splitted</name>
<fsummary>Splits a <c>RequestLine</c> in a file reference to an executable,
and a <c>QueryString</c> or a <c>PathInfo</c>string.</fsummary>
<type>
@@ -330,7 +329,7 @@
</func>
<func>
- <name>split_path(RequestLine) -> {Path,QueryStringOrPathInfo}</name>
+ <name since="">split_path(RequestLine) -> {Path,QueryStringOrPathInfo}</name>
<fsummary>Splits a <c>RequestLine</c> in a file reference, and a
<c>QueryString</c> or a <c>PathInfo</c> string.</fsummary>
<type>
@@ -356,7 +355,7 @@
</func>
<func>
- <name>strip(String) -> Stripped</name>
+ <name since="">strip(String) -> Stripped</name>
<fsummary>Returns <c>String</c> where the leading and trailing space
tabs are removed.</fsummary>
<type>
@@ -370,7 +369,7 @@
</func>
<func>
- <name>suffix(FileName) -> Suffix</name>
+ <name since="">suffix(FileName) -> Suffix</name>
<fsummary>Extracts the file suffix from a given filename.</fsummary>
<type>
<v>FileName = Suffix = string()</v>
diff --git a/lib/inets/doc/src/inets.xml b/lib/inets/doc/src/inets.xml
index 9b0ffaad5e..176af3137a 100644
--- a/lib/inets/doc/src/inets.xml
+++ b/lib/inets/doc/src/inets.xml
@@ -29,7 +29,7 @@
<date></date>
<rev></rev>
</header>
- <module>inets</module>
+ <module since="">inets</module>
<modulesummary>The Inets services API.</modulesummary>
<description>
<p>This module provides the most basic API to the
@@ -51,7 +51,7 @@
<funcs>
<func>
- <name>services() -> [{Service, Pid}]</name>
+ <name since="">services() -> [{Service, Pid}]</name>
<fsummary>Returns a list of currently running services.</fsummary>
<type>
<v>Service = service()</v>
@@ -68,7 +68,7 @@
</func>
<func>
- <name>services_info() -> [{Service, Pid, Info}]</name>
+ <name since="">services_info() -> [{Service, Pid, Info}]</name>
<fsummary>Returns a list of currently running services where
each service is described by an <c>[{Option, Value}]</c>
list.</fsummary>
@@ -91,7 +91,7 @@
</func>
<func>
- <name>service_names() -> [Service] </name>
+ <name since="">service_names() -> [Service] </name>
<fsummary>Returns a list of available service names.</fsummary>
<type>
<v>Service = service()</v>
@@ -104,8 +104,8 @@
</func>
<func>
- <name>start() -> </name>
- <name>start(Type) -> ok | {error, Reason}</name>
+ <name since="">start() -> </name>
+ <name since="">start(Type) -> ok | {error, Reason}</name>
<fsummary>Starts the <c>Inets</c> application.</fsummary>
<type>
<v>Type = permanent | transient | temporary</v>
@@ -120,8 +120,8 @@
</func>
<func>
- <name>start(Service, ServiceConfig) -> {ok, Pid} | {error, Reason}</name>
- <name>start(Service, ServiceConfig, How) -> {ok, Pid} | {error, Reason}</name>
+ <name since="">start(Service, ServiceConfig) -> {ok, Pid} | {error, Reason}</name>
+ <name since="">start(Service, ServiceConfig, How) -> {ok, Pid} | {error, Reason}</name>
<fsummary>Dynamically starts an <c>Inets</c>
service after the <c>Inets</c> application has been started.</fsummary>
<type>
@@ -156,7 +156,7 @@
</func>
<func>
- <name>stop() -> ok </name>
+ <name since="">stop() -> ok </name>
<fsummary>Stops the <c>Inets</c> application.</fsummary>
<desc>
<p>Stops the <c>Inets</c> application. See also
@@ -167,7 +167,7 @@
</func>
<func>
- <name>stop(Service, Reference) -> ok | {error, Reason} </name>
+ <name since="">stop(Service, Reference) -> ok | {error, Reason} </name>
<fsummary>Stops a started service of the <c>Inets</c> application or takes
down a <c>stand_alone </c>service gracefully.</fsummary>
<type>
diff --git a/lib/inets/doc/src/mod_alias.xml b/lib/inets/doc/src/mod_alias.xml
index 6ae19700a5..ff57d49d08 100644
--- a/lib/inets/doc/src/mod_alias.xml
+++ b/lib/inets/doc/src/mod_alias.xml
@@ -29,7 +29,7 @@
<rev>2.2</rev>
<file>mod_alias.sgml</file>
</header>
- <module>mod_alias</module>
+ <module since="">mod_alias</module>
<modulesummary>URL aliasing.</modulesummary>
<description>
<p>Erlang web server internal API for handling of, for example,
@@ -40,7 +40,7 @@
<funcs>
<func>
- <name>default_index(ConfigDB, Path) -> NewPath</name>
+ <name since="">default_index(ConfigDB, Path) -> NewPath</name>
<fsummary>Returns a new path with the default resource or file appended.</fsummary>
<type>
<v>ConfigDB = config_db()</v>
@@ -64,7 +64,7 @@
</func>
<func>
- <name>path(PathData, ConfigDB, RequestURI) -> Path</name>
+ <name since="">path(PathData, ConfigDB, RequestURI) -> Path</name>
<fsummary>Returns the file path to a URL.</fsummary>
<type>
<v>PathData = interaction_data()</v>
@@ -89,7 +89,7 @@
</func>
<func>
- <name>real_name(ConfigDB, RequestURI, Aliases) -> Ret</name>
+ <name since="">real_name(ConfigDB, RequestURI, Aliases) -> Ret</name>
<fsummary>Expands a request URI using <c>Aliases</c> config directives.</fsummary>
<type>
<v>ConfigDB = config_db()</v>
@@ -120,7 +120,7 @@
</func>
<func>
- <name>real_script_name(ConfigDB, RequestURI, ScriptAliases) -> Ret</name>
+ <name since="">real_script_name(ConfigDB, RequestURI, ScriptAliases) -> Ret</name>
<fsummary>Expands a request URI using <c>ScriptAliases</c>
config directives.</fsummary>
<type>
diff --git a/lib/inets/doc/src/mod_auth.xml b/lib/inets/doc/src/mod_auth.xml
index c4f844622b..ad864ca4d1 100644
--- a/lib/inets/doc/src/mod_auth.xml
+++ b/lib/inets/doc/src/mod_auth.xml
@@ -29,7 +29,7 @@
<rev>2.3</rev>
<file>mod_auth.sgml</file>
</header>
- <module>mod_auth</module>
+ <module since="">mod_auth</module>
<modulesummary>User authentication using text files, Dets, or Mnesia database.</modulesummary>
<description>
<p>This module provides for basic user authentication using
@@ -38,9 +38,9 @@
<funcs>
<func>
- <name>add_group_member(GroupName, UserName, Options) -> true | {error, Reason}</name>
- <name>add_group_member(GroupName, UserName, Port, Dir) -> true | {error, Reason}</name>
- <name>add_group_member(GroupName, UserName, Address, Port, Dir) -> true | {error, Reason}</name>
+ <name since="">add_group_member(GroupName, UserName, Options) -> true | {error, Reason}</name>
+ <name since="">add_group_member(GroupName, UserName, Port, Dir) -> true | {error, Reason}</name>
+ <name since="">add_group_member(GroupName, UserName, Address, Port, Dir) -> true | {error, Reason}</name>
<fsummary>Adds a user to a group.</fsummary>
<type>
<v>GroupName = string()</v>
@@ -65,9 +65,9 @@
</func>
<func>
- <name>add_user(UserName, Options) -> true| {error, Reason}</name>
- <name>add_user(UserName, Password, UserData, Port, Dir) -> true | {error, Reason}</name>
- <name>add_user(UserName, Password, UserData, Address, Port, Dir) -> true | {error, Reason}</name>
+ <name since="">add_user(UserName, Options) -> true| {error, Reason}</name>
+ <name since="">add_user(UserName, Password, UserData, Port, Dir) -> true | {error, Reason}</name>
+ <name since="">add_user(UserName, Password, UserData, Address, Port, Dir) -> true | {error, Reason}</name>
<fsummary>Adds a user to the user database.</fsummary>
<type>
<v>UserName = string()</v>
@@ -92,8 +92,8 @@
</func>
<func>
- <name>delete_group(GroupName, Options) -> true | {error,Reason} &lt;name>delete_group(GroupName, Port, Dir) -> true | {error, Reason}</name>
- <name>delete_group(GroupName, Address, Port, Dir) -> true | {error, Reason}</name>
+ <name since="">delete_group(GroupName, Options) -> true | {error,Reason} &lt;name>delete_group(GroupName, Port, Dir) -> true | {error, Reason}</name>
+ <name since="">delete_group(GroupName, Address, Port, Dir) -> true | {error, Reason}</name>
<fsummary>Deletes a group.</fsummary>
<type>
<v>Options = [Option]</v>
@@ -115,9 +115,9 @@
</func>
<func>
- <name>delete_group_member(GroupName, UserName, Options) -> true | {error, Reason}</name>
- <name>delete_group_member(GroupName, UserName, Port, Dir) -> true | {error, Reason}</name>
- <name>delete_group_member(GroupName, UserName, Address, Port, Dir) -> true | {error, Reason}</name>
+ <name since="">delete_group_member(GroupName, UserName, Options) -> true | {error, Reason}</name>
+ <name since="">delete_group_member(GroupName, UserName, Port, Dir) -> true | {error, Reason}</name>
+ <name since="">delete_group_member(GroupName, UserName, Address, Port, Dir) -> true | {error, Reason}</name>
<fsummary>Removes a user from a group.</fsummary>
<type>
<v>GroupName = string()</v>
@@ -141,9 +141,9 @@
</func>
<func>
- <name>delete_user(UserName,Options) -> true | {error, Reason}</name>
- <name>delete_user(UserName, Port, Dir) -> true | {error, Reason}</name>
- <name>delete_user(UserName, Address, Port, Dir) -> true | {error, Reason}</name>
+ <name since="">delete_user(UserName,Options) -> true | {error, Reason}</name>
+ <name since="">delete_user(UserName, Port, Dir) -> true | {error, Reason}</name>
+ <name since="">delete_user(UserName, Address, Port, Dir) -> true | {error, Reason}</name>
<fsummary>Deletes a user from the user database.</fsummary>
<type>
<v>UserName = string()</v>
@@ -166,9 +166,9 @@
</func>
<func>
- <name>get_user(UserName,Options) -> {ok, #httpd_user} |{error, Reason}</name>
- <name>get_user(UserName, Port, Dir) -> {ok, #httpd_user} | {error, Reason}</name>
- <name>get_user(UserName, Address, Port, Dir) -> {ok, #httpd_user} | {error, Reason}</name>
+ <name since="">get_user(UserName,Options) -> {ok, #httpd_user} |{error, Reason}</name>
+ <name since="">get_user(UserName, Port, Dir) -> {ok, #httpd_user} | {error, Reason}</name>
+ <name since="">get_user(UserName, Address, Port, Dir) -> {ok, #httpd_user} | {error, Reason}</name>
<fsummary>Returns a user from the user database.</fsummary>
<type>
<v>UserName = string()</v>
@@ -190,9 +190,9 @@
</func>
<func>
- <name>list_groups(Options) -> {ok, Groups} | {error, Reason}</name>
- <name>list_groups(Port, Dir) -> {ok, Groups} | {error, Reason}</name>
- <name>list_groups(Address, Port, Dir) -> {ok, Groups} | {error, Reason}</name>
+ <name since="">list_groups(Options) -> {ok, Groups} | {error, Reason}</name>
+ <name since="">list_groups(Port, Dir) -> {ok, Groups} | {error, Reason}</name>
+ <name since="">list_groups(Address, Port, Dir) -> {ok, Groups} | {error, Reason}</name>
<fsummary>Lists all the groups.</fsummary>
<type>
<v>Options = [Option]</v>
@@ -214,9 +214,9 @@
</func>
<func>
- <name>list_group_members(GroupName, Options) -> {ok, Users} | {error, Reason}</name>
- <name>list_group_members(GroupName, Port, Dir) -> {ok, Users} | {error, Reason}</name>
- <name>list_group_members(GroupName, Address, Port, Dir) -> {ok, Users} | {error, Reason}</name>
+ <name since="">list_group_members(GroupName, Options) -> {ok, Users} | {error, Reason}</name>
+ <name since="">list_group_members(GroupName, Port, Dir) -> {ok, Users} | {error, Reason}</name>
+ <name since="">list_group_members(GroupName, Address, Port, Dir) -> {ok, Users} | {error, Reason}</name>
<fsummary>Lists the members of a group.</fsummary>
<type>
<v>GroupName = string()</v>
@@ -240,9 +240,9 @@
</func>
<func>
- <name>list_users(Options) -> {ok, Users} | {error, Reason}</name>
- <name>list_users(Port, Dir) -> {ok, Users} | {error, Reason}</name>
- <name>list_users(Address, Port, Dir) -> {ok, Users} | {error, Reason}</name>
+ <name since="">list_users(Options) -> {ok, Users} | {error, Reason}</name>
+ <name since="OTP R14B01">list_users(Port, Dir) -> {ok, Users} | {error, Reason}</name>
+ <name since="">list_users(Address, Port, Dir) -> {ok, Users} | {error, Reason}</name>
<fsummary>Lists users in the user database.</fsummary>
<type>
<v>Options = [Option]</v>
@@ -264,8 +264,8 @@
</func>
<func>
- <name>update_password(Port, Dir, OldPassword, NewPassword, NewPassword) -> ok | {error, Reason}</name>
- <name>update_password(Address,Port, Dir, OldPassword, NewPassword, NewPassword) -> ok | {error, Reason}</name>
+ <name since="">update_password(Port, Dir, OldPassword, NewPassword, NewPassword) -> ok | {error, Reason}</name>
+ <name since="">update_password(Address,Port, Dir, OldPassword, NewPassword, NewPassword) -> ok | {error, Reason}</name>
<fsummary>Changes <c>AuthAcessPassword</c>.</fsummary>
<type>
<v>Port = integer()</v>
diff --git a/lib/inets/doc/src/mod_esi.xml b/lib/inets/doc/src/mod_esi.xml
index ede7dc8f7d..bc5f98068f 100644
--- a/lib/inets/doc/src/mod_esi.xml
+++ b/lib/inets/doc/src/mod_esi.xml
@@ -25,7 +25,7 @@
<title>mod_esi</title>
<file>mod_esi.sgml</file>
</header>
- <module>mod_esi</module>
+ <module since="">mod_esi</module>
<modulesummary>Erlang Server Interface</modulesummary>
<description>
<p>This module defines the Erlang Server Interface (ESI) API.
@@ -88,7 +88,7 @@
<funcs>
<func>
- <name>deliver(SessionID, Data) -> ok | {error, Reason}</name>
+ <name since="">deliver(SessionID, Data) -> ok | {error, Reason}</name>
<fsummary>Sends <c>Data</c> back to client.</fsummary>
<type>
<v>SessionID = term()</v>
@@ -121,7 +121,7 @@
<funcs>
<func>
- <name>Module:Function(SessionID, Env, Input)-> {continue, State} | _ </name>
+ <name since="">Module:Function(SessionID, Env, Input)-> {continue, State} | _ </name>
<fsummary>Creates a dynamic web page and returns it chunk by chunk
to the server process by calling <c>mod_esi:deliver/2</c>.</fsummary>
<type>
@@ -179,7 +179,7 @@
</func>
<func>
- <name>Module:Function(Env, Input)-> Response </name>
+ <name since="">Module:Function(Env, Input)-> Response </name>
<fsummary>Creates a dynamic web page and returns it as a list.
This function is deprecated and is only kept for backwards compatibility.</fsummary>
<type>
diff --git a/lib/inets/doc/src/mod_security.xml b/lib/inets/doc/src/mod_security.xml
index 6f3f3c048a..c26d7468c2 100644
--- a/lib/inets/doc/src/mod_security.xml
+++ b/lib/inets/doc/src/mod_security.xml
@@ -29,7 +29,7 @@
<rev>1.0</rev>
<file>mod_security.sgml</file>
</header>
- <module>mod_security</module>
+ <module since="">mod_security</module>
<modulesummary>Security Audit and Trailing Functionality</modulesummary>
<description>
<p>Security Audit and Trailing Functionality</p>
@@ -37,8 +37,8 @@
<funcs>
<func>
- <name>block_user(User, Port, Dir, Seconds) -> true | {error, Reason}</name>
- <name>block_user(User, Address, Port, Dir, Seconds) -> true | {error, Reason}</name>
+ <name since="">block_user(User, Port, Dir, Seconds) -> true | {error, Reason}</name>
+ <name since="">block_user(User, Address, Port, Dir, Seconds) -> true | {error, Reason}</name>
<fsummary>Blocks a user from access to a directory for a certain amount of time.</fsummary>
<type>
<v>User = string()</v>
@@ -56,10 +56,10 @@
</func>
<func>
- <name>list_auth_users(Port) -> Users | []</name>
- <name>list_auth_users(Address, Port) -> Users | []</name>
- <name>list_auth_users(Port, Dir) -> Users | []</name>
- <name>list_auth_users(Address, Port, Dir) -> Users | []</name>
+ <name since="">list_auth_users(Port) -> Users | []</name>
+ <name since="">list_auth_users(Address, Port) -> Users | []</name>
+ <name since="">list_auth_users(Port, Dir) -> Users | []</name>
+ <name since="">list_auth_users(Address, Port, Dir) -> Users | []</name>
<fsummary>Lists users that have authenticated within the <c>SecurityAuthTimeout</c>
time for a given address (if specified), port number, and directory
(if specified).</fsummary>
@@ -77,10 +77,10 @@
</desc>
</func>
<func>
- <name>list_blocked_users(Port) -> Users | []</name>
- <name>list_blocked_users(Address, Port) -> Users | []</name>
- <name>list_blocked_users(Port, Dir) -> Users | []</name>
- <name>list_blocked_users(Address, Port, Dir) -> Users | []</name>
+ <name since="">list_blocked_users(Port) -> Users | []</name>
+ <name since="">list_blocked_users(Address, Port) -> Users | []</name>
+ <name since="">list_blocked_users(Port, Dir) -> Users | []</name>
+ <name since="">list_blocked_users(Address, Port, Dir) -> Users | []</name>
<fsummary>Lists users that are currently blocked from access to a
specified port number, for a given address (if specified).</fsummary>
<type>
@@ -97,10 +97,10 @@
</func>
<func>
- <name>unblock_user(User, Port) -> true | {error, Reason}</name>
- <name>unblock_user(User, Address, Port) -> true | {error, Reason}</name>
- <name>unblock_user(User, Port, Dir) -> true | {error, Reason}</name>
- <name>unblock_user(User, Address, Port, Dir) -> true | {error, Reason}</name>
+ <name since="">unblock_user(User, Port) -> true | {error, Reason}</name>
+ <name since="">unblock_user(User, Address, Port) -> true | {error, Reason}</name>
+ <name since="">unblock_user(User, Port, Dir) -> true | {error, Reason}</name>
+ <name since="">unblock_user(User, Address, Port, Dir) -> true | {error, Reason}</name>
<fsummary>Removes a blocked user from the block list.</fsummary>
<type>
<v>User = string()</v>
@@ -129,8 +129,8 @@
<funcs>
<func>
- <name>Module:event(What, Port, Dir, Data) -> ignored</name>
- <name>Module:event(What, Address, Port, Dir, Data) -> ignored</name>
+ <name since="OTP 18.1">Module:event(What, Port, Dir, Data) -> ignored</name>
+ <name since="OTP 18.1">Module:event(What, Address, Port, Dir, Data) -> ignored</name>
<fsummary>Called whenever an event occurs in <c>mod_security</c>.</fsummary>
<type>
<v>What = atom()</v>
diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml
index 3624c6e3d7..31dae6317e 100644
--- a/lib/inets/doc/src/notes.xml
+++ b/lib/inets/doc/src/notes.xml
@@ -1341,7 +1341,7 @@
Add option {ftp_extension, boolean} to enable use of
extended commands EPSV and EPRT, as specified in RFC
2428, for IPv4 instead of using the legacy commands. Ipv6
- can not be supported without the extended commands.</p>
+ cannot be supported without the extended commands.</p>
<p>
Own Id: OTP-12255</p>
</item>
diff --git a/lib/inets/doc/src/notes_history.xml b/lib/inets/doc/src/notes_history.xml
index c12899e614..1523827db9 100644
--- a/lib/inets/doc/src/notes_history.xml
+++ b/lib/inets/doc/src/notes_history.xml
@@ -1149,8 +1149,8 @@
<list type="bulleted">
<item>
<p>When further testing the functionality of https requests
- that goes through a proxy. We realised that alas this can
- not currently be supported as it requires features from
+ that goes through a proxy. We realised that alas this
+ cannot currently be supported as it requires features from
the ssl implementation that is not currently available.
So for now an error message will be returned when trying
to use this functionality.</p>
diff --git a/lib/inets/examples/httpd_load_test/hdlt_slave.erl b/lib/inets/examples/httpd_load_test/hdlt_slave.erl
index 5ee005629d..30ddfd76af 100644
--- a/lib/inets/examples/httpd_load_test/hdlt_slave.erl
+++ b/lib/inets/examples/httpd_load_test/hdlt_slave.erl
@@ -44,18 +44,17 @@
%% this to work is that the 'erl' program can be found in PATH.
%%
%% If the master and slave are on different hosts, start/N uses
-%% the 'rsh' program to spawn an Erlang node on the other host.
+%% the 'ssh' program to spawn an Erlang node on the other host.
%% Alternative, if the master was started as
%% 'erl -sname xxx -rsh my_rsh...', then 'my_rsh' will be used instead
-%% of 'rsh' (this is useful for systems where the rsh program is named
-%% 'remsh').
+%% of 'ssh' (this is useful for systems still using rsh or remsh).
%%
%% For this to work, the following conditions must be fulfilled:
%%
-%% 1. There must be an Rsh program on computer; if not an error
+%% 1. There must be an ssh program on computer; if not an error
%% is returned.
%%
-%% 2. The hosts must be configured to allowed 'rsh' access without
+%% 2. The hosts must be configured to allow 'ssh' access without
%% prompts for password.
%%
%% The slave node will have its filer and user server redirected
@@ -244,7 +243,7 @@ register_unique_name(Number) ->
%% Makes up the command to start the nodes.
%% If the node should run on the local host, there is
-%% no need to use rsh.
+%% no need to use ssh.
mk_cmd(Host, Name, Paths, Args, Waiter, Prog) ->
PaPaths = [[" -pa ", Path] || Path <- Paths],
diff --git a/lib/inets/src/http_client/httpc_cookie.erl b/lib/inets/src/http_client/httpc_cookie.erl
index cbf428ab3e..2e647a1438 100644
--- a/lib/inets/src/http_client/httpc_cookie.erl
+++ b/lib/inets/src/http_client/httpc_cookie.erl
@@ -271,7 +271,7 @@ lookup_cookies(CookieDb, Host, Path) ->
lookup_domain_cookies(_CookieDb, [], AccCookies) ->
lists:flatten(AccCookies);
-%% Top domains can not have cookies
+%% Top domains cannot have cookies
lookup_domain_cookies(_CookieDb, [_], AccCookies) ->
lists:flatten(AccCookies);
diff --git a/lib/inets/src/http_client/httpc_manager.erl b/lib/inets/src/http_client/httpc_manager.erl
index 0333442bf2..0dc0483fa9 100644
--- a/lib/inets/src/http_client/httpc_manager.erl
+++ b/lib/inets/src/http_client/httpc_manager.erl
@@ -863,7 +863,7 @@ select_session(Candidates, _, Max, pipeline) ->
select_session(Candidates, Max).
select_session([] = _Candidates, _Max) ->
- ?hcrd("select session - no candicate", []),
+ ?hcrd("select session - no candidate", []),
no_connection;
select_session(Candidates, Max) ->
NewCandidates =
diff --git a/lib/inets/src/http_client/httpc_response.erl b/lib/inets/src/http_client/httpc_response.erl
index 78d6b4ed24..bb6b76da89 100644
--- a/lib/inets/src/http_client/httpc_response.erl
+++ b/lib/inets/src/http_client/httpc_response.erl
@@ -398,7 +398,7 @@ redirect(Response = {_, Headers, _}, Request) ->
THost = http_util:maybe_add_brackets(maps:get(host, URIMap), Brackets),
TPort = maps:get(port, URIMap),
TPath = maps:get(path, URIMap),
- TQuery = maps:get(query, URIMap, ""),
+ TQuery = add_question_mark(maps:get(query, URIMap, "")),
NewURI = uri_string:normalize(
uri_string:recompose(URIMap)),
HostPort = http_request:normalize_host(TScheme, THost, TPort),
@@ -417,6 +417,14 @@ redirect(Response = {_, Headers, _}, Request) ->
end
end.
+add_question_mark(<<>>) ->
+ <<>>;
+add_question_mark([]) ->
+ [];
+add_question_mark(Comp) when is_binary(Comp) ->
+ <<$?, Comp/binary>>;
+add_question_mark(Comp) when is_list(Comp) ->
+ [$?|Comp].
%% RFC3986 - 5.2.2. Transform References
resolve_uri(Scheme, Host, Port, Path, Query, URI) ->
diff --git a/lib/inets/test/http_format_SUITE.erl b/lib/inets/test/http_format_SUITE.erl
index d6b0e5f9f5..0a5aed67d5 100644
--- a/lib/inets/test/http_format_SUITE.erl
+++ b/lib/inets/test/http_format_SUITE.erl
@@ -435,7 +435,7 @@ http_request(Config) when is_list(Config) ->
[<<>>, Length1], HttpBody1)).
%%-------------------------------------------------------------------------
validate_request_line() ->
- [{doc, "Test httpd_request:validate/3. Makes sure you can not get past"
+ [{doc, "Test httpd_request:validate/3. Makes sure you cannot get past"
" the server_root and that the request is recognized by the server"
" and protcol version."}].
validate_request_line(Config) when is_list(Config) ->
diff --git a/lib/inets/test/httpc_proxy_SUITE.erl b/lib/inets/test/httpc_proxy_SUITE.erl
index 198b245399..3ee7981660 100644
--- a/lib/inets/test/httpc_proxy_SUITE.erl
+++ b/lib/inets/test/httpc_proxy_SUITE.erl
@@ -534,7 +534,7 @@ init_local_proxy(Config) ->
ct:fail({local_proxy_start_failed,Error})
end;
_ ->
- {skip,"Platform can not run local proxy start script"}
+ {skip,"Platform cannot run local proxy start script"}
end.
init_local_proxy_string(String, Config) ->
diff --git a/lib/inets/test/httpd_bench_SUITE.erl b/lib/inets/test/httpd_bench_SUITE.erl
index 4b549dcb5b..087516f56c 100644
--- a/lib/inets/test/httpd_bench_SUITE.erl
+++ b/lib/inets/test/httpd_bench_SUITE.erl
@@ -37,7 +37,8 @@
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
suite() ->
- [{ct_hooks,[{ts_install_cth,[{nodenames,2}]}]}].
+ [{timetrap, {minutes, 1}},
+ {ct_hooks,[{ts_install_cth,[{nodenames,2}]}]}].
all() ->
[
@@ -499,8 +500,8 @@ start_dummy("http"= Protocol, Config) ->
Conf = [
%%{big, filename:join(DataDir, "1M_file")},
%%{small, filename:join(DataDir, "1k_file")},
- {big, {gen, crypto:rand_bytes(1000000)}},
- {small, {gen, crypto:rand_bytes(1000)}},
+ {big, {gen, crypto:strong_rand_bytes(1000000)}},
+ {small, {gen, crypto:strong_rand_bytes(1000)}},
{http_version, HTTPVersion},
{keep_alive, ?config(keep_alive, Config)}
],
@@ -519,8 +520,8 @@ start_dummy("https" = Protocol, Config) ->
Opts = [{active, true}, {nodelay, true}, {reuseaddr, true} | SSLOpts],
Conf = [%%{big, filename:join(DataDir, "1M_file")},
%%{small, filename:join(DataDir, "1k_file")},
- {big, {gen, crypto:rand_bytes(1000000)}},
- {small, {gen, crypto:rand_bytes(1000)}},
+ {big, {gen, crypto:strong_rand_bytes(1000000)}},
+ {small, {gen, crypto:strong_rand_bytes(1000)}},
{http_version, HTTPVersion},
{keep_alive, ?config(keep_alive, Config)}
],
diff --git a/lib/jinterface/doc/src/notes.xml b/lib/jinterface/doc/src/notes.xml
index 75a2364384..e4bfddcd17 100644
--- a/lib/jinterface/doc/src/notes.xml
+++ b/lib/jinterface/doc/src/notes.xml
@@ -720,7 +720,7 @@
<item>
<p><c>OtpMbox.receive()</c> and <c>OtpMbox.receive(long timeout)</c> can now throw
<c>OtpErlangDecodeException</c> if the received message
- can not be decoded. <c>null</c> is now only returned from
+ cannot be decoded. <c>null</c> is now only returned from
<c>OtpMbox.receive(long timeout)</c> if a timeout occurs.
<c>OtpMbox.receive()</c> will never return <c>null</c>.</p>
<p>*** POTENTIAL INCOMPATIBILITY ***</p>
diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpEpmd.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpEpmd.java
index 363fdb950a..fffb8475d3 100644
--- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpEpmd.java
+++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpEpmd.java
@@ -102,7 +102,7 @@ public class OtpEpmd {
/**
* Set the port number to be used to contact the epmd process. Only needed
* when the default port is not desired and system environment variable
- * ERL_EPMD_PORT can not be read (applet).
+ * ERL_EPMD_PORT cannot be read (applet).
*/
public static void useEpmdPort(final int port) {
EpmdPort.set(port);
diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpInputStream.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpInputStream.java
index ded8f6e1e5..6d81ce630b 100644
--- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpInputStream.java
+++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpInputStream.java
@@ -601,7 +601,7 @@ public class OtpInputStream extends ByteArrayInputStream {
* @return the integer value.
*
* @exception OtpErlangDecodeException
- * if the next term in the stream can not be represented as a
+ * if the next term in the stream cannot be represented as a
* positive integer.
*/
public int read_uint() throws OtpErlangDecodeException {
@@ -622,7 +622,7 @@ public class OtpInputStream extends ByteArrayInputStream {
* @return the integer value.
*
* @exception OtpErlangDecodeException
- * if the next term in the stream can not be represented as
+ * if the next term in the stream cannot be represented as
* an integer.
*/
public int read_int() throws OtpErlangDecodeException {
@@ -643,7 +643,7 @@ public class OtpInputStream extends ByteArrayInputStream {
* @return the short value.
*
* @exception OtpErlangDecodeException
- * if the next term in the stream can not be represented as a
+ * if the next term in the stream cannot be represented as a
* positive short.
*/
public short read_ushort() throws OtpErlangDecodeException {
@@ -664,7 +664,7 @@ public class OtpInputStream extends ByteArrayInputStream {
* @return the short value.
*
* @exception OtpErlangDecodeException
- * if the next term in the stream can not be represented as a
+ * if the next term in the stream cannot be represented as a
* short.
*/
public short read_short() throws OtpErlangDecodeException {
@@ -685,7 +685,7 @@ public class OtpInputStream extends ByteArrayInputStream {
* @return the long value.
*
* @exception OtpErlangDecodeException
- * if the next term in the stream can not be represented as a
+ * if the next term in the stream cannot be represented as a
* positive long.
*/
public long read_ulong() throws OtpErlangDecodeException {
@@ -698,7 +698,7 @@ public class OtpInputStream extends ByteArrayInputStream {
* @return the long value.
*
* @exception OtpErlangDecodeException
- * if the next term in the stream can not be represented as a
+ * if the next term in the stream cannot be represented as a
* long.
*/
public long read_long() throws OtpErlangDecodeException {
diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMbox.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMbox.java
index 42e178c3f6..29a8bc1540 100644
--- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMbox.java
+++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMbox.java
@@ -156,7 +156,7 @@ public class OtpMbox {
* of the next message waiting in this mailbox.
*
* @exception OtpErlangDecodeException
- * if the message can not be decoded.
+ * if the message cannot be decoded.
*
* @exception OtpErlangExit
* if a linked {@link OtpErlangPid pid} has exited or has
@@ -184,7 +184,7 @@ public class OtpMbox {
* of the next message waiting in this mailbox.
*
* @exception OtpErlangDecodeException
- * if the message can not be decoded.
+ * if the message cannot be decoded.
*
* @exception OtpErlangExit
* if a linked {@link OtpErlangPid pid} has exited or has
diff --git a/lib/kernel/doc/src/application.xml b/lib/kernel/doc/src/application.xml
index 38c7b5acf1..5170502581 100644
--- a/lib/kernel/doc/src/application.xml
+++ b/lib/kernel/doc/src/application.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>application</module>
+ <module since="">application</module>
<modulesummary>Generic OTP application functions</modulesummary>
<description>
<p>In OTP, <em>application</em> denotes a component implementing
@@ -67,8 +67,8 @@
</datatypes>
<funcs>
<func>
- <name name="ensure_all_started" arity="1"/>
- <name name="ensure_all_started" arity="2"/>
+ <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>
<desc>
<p>Equivalent to calling
@@ -85,8 +85,8 @@
</desc>
</func>
<func>
- <name name="ensure_started" arity="1"/>
- <name name="ensure_started" arity="2"/>
+ <name name="ensure_started" arity="1" since="OTP R16B01"/>
+ <name name="ensure_started" arity="2" since="OTP R16B01"/>
<fsummary>Load and start an application.</fsummary>
<desc>
<p>Equivalent to
@@ -95,8 +95,8 @@
</desc>
</func>
<func>
- <name name="get_all_env" arity="0"/>
- <name name="get_all_env" arity="1"/>
+ <name name="get_all_env" arity="0" since=""/>
+ <name name="get_all_env" arity="1" since=""/>
<fsummary>Get the configuration parameters for an application.</fsummary>
<desc>
<p>Returns the configuration parameters and their values for
@@ -108,8 +108,8 @@
</desc>
</func>
<func>
- <name name="get_all_key" arity="0"/>
- <name name="get_all_key" arity="1"/>
+ <name name="get_all_key" arity="0" since=""/>
+ <name name="get_all_key" arity="1" since=""/>
<fsummary>Get the application specification keys.</fsummary>
<desc>
<p>Returns the application specification keys and their values
@@ -122,8 +122,8 @@
</desc>
</func>
<func>
- <name name="get_application" arity="0"/>
- <name name="get_application" arity="1"/>
+ <name name="get_application" arity="0" since=""/>
+ <name name="get_application" arity="1" since=""/>
<fsummary>Get the name of an application containing a certain process or module.</fsummary>
<desc>
<p>Returns the name of the application to which the process
@@ -136,8 +136,8 @@
</desc>
</func>
<func>
- <name name="get_env" arity="1"/>
- <name name="get_env" arity="2"/>
+ <name name="get_env" arity="1" since=""/>
+ <name name="get_env" arity="2" since=""/>
<fsummary>Get the value of a configuration parameter.</fsummary>
<desc>
<p>Returns the value of configuration parameter <c><anno>Par</anno></c>
@@ -153,7 +153,7 @@
</desc>
</func>
<func>
- <name name="get_env" arity="3"/>
+ <name name="get_env" arity="3" since="OTP R16B"/>
<fsummary>Get the value of a configuration parameter using a default.</fsummary>
<desc>
<p>Works like <seealso marker="#get_env/2"><c>get_env/2</c></seealso> but returns
@@ -162,8 +162,8 @@
</desc>
</func>
<func>
- <name name="get_key" arity="1"/>
- <name name="get_key" arity="2"/>
+ <name name="get_key" arity="1" since=""/>
+ <name name="get_key" arity="2" since=""/>
<fsummary>Get the value of an application specification key.</fsummary>
<desc>
<p>Returns the value of the application specification key
@@ -180,8 +180,8 @@
</desc>
</func>
<func>
- <name name="load" arity="1"/>
- <name name="load" arity="2"/>
+ <name name="load" arity="1" since=""/>
+ <name name="load" arity="2" since=""/>
<fsummary>Load an application.</fsummary>
<type name="application_spec"/>
<type name="application_opt"/>
@@ -226,7 +226,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name name="loaded_applications" arity="0"/>
+ <name name="loaded_applications" arity="0" since=""/>
<fsummary>Get the currently loaded applications.</fsummary>
<desc>
<p>Returns a list with information about the applications, and included
@@ -238,7 +238,42 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name name="permit" arity="2"/>
+ <name name="set_env" arity="1" since="OTP @OTP-15642@"/>
+ <name name="set_env" arity="2" since="OTP @OTP-15642@"/>
+ <fsummary>Sets the configuration parameters of multiple applications.</fsummary>
+ <desc>
+ <p>Sets the configuration <c><anno>Config</anno></c> for multiple
+ applications. It is equivalent to calling <c>set_env/4</c> on
+ each application individially, except it is more efficient.
+ The given <c><anno>Config</anno></c> is validated before the
+ configuration is set.</p>
+ <p><c>set_env/2</c> uses the standard <c>gen_server</c> time-out
+ value (5000 ms). Option <c>timeout</c> can be specified
+ if another time-out value is useful, for example, in situations
+ where the application controller is heavily loaded.</p>
+ <p>Option <c>persistent</c> can be set to <c>true</c>
+ to guarantee that parameters set with <c>set_env/2</c>
+ are not overridden by those defined in the application resource
+ file on load. This means that persistent values will stick after the application
+ is loaded and also on application reload.</p>
+ <p>If an application is given more than once or if an application
+ has the same key given more than once, the behaviour is undefined
+ and a warning message will be logged. In future releases, an error
+ will be raised.</p>
+ <p><c>set_env/1</c> is equivalent to <c>set_env(Config, [])</c>.</p>
+ <warning>
+ <p>Use this function only if you know what you are doing,
+ that is, on your own applications. It is very
+ application-dependent and
+ configuration parameter-dependent when and how often
+ the value is read by the application. Careless use
+ of this function can put the application in a
+ weird, inconsistent, and malfunctioning state.</p>
+ </warning>
+ </desc>
+ </func>
+ <func>
+ <name name="permit" arity="2" since=""/>
<fsummary>Change the permission for an application to run at a node.</fsummary>
<desc>
<p>Changes the permission for <c><anno>Application</anno></c> to run at
@@ -271,8 +306,8 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name name="set_env" arity="3"/>
- <name name="set_env" arity="4"/>
+ <name name="set_env" arity="3" since=""/>
+ <name name="set_env" arity="4" since=""/>
<fsummary>Set the value of a configuration parameter.</fsummary>
<desc>
<p>Sets the value of configuration parameter <c><anno>Par</anno></c> for
@@ -302,8 +337,8 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name name="start" arity="1"/>
- <name name="start" arity="2"/>
+ <name name="start" arity="1" since=""/>
+ <name name="start" arity="2" since=""/>
<fsummary>Load and start an application.</fsummary>
<desc>
<p>Starts <c><anno>Application</anno></c>. If it is not loaded,
@@ -353,7 +388,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name name="start_type" arity="0"/>
+ <name name="start_type" arity="0" since=""/>
<fsummary>Get the start type of an ongoing application startup.</fsummary>
<desc>
<p>This function is intended to be called by a process belonging
@@ -370,7 +405,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name name="stop" arity="1"/>
+ <name name="stop" arity="1" since=""/>
<fsummary>Stop an application.</fsummary>
<desc>
<p>Stops <c><anno>Application</anno></c>. The application master calls
@@ -399,7 +434,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name name="takeover" arity="2"/>
+ <name name="takeover" arity="2" since=""/>
<fsummary>Take over a distributed application.</fsummary>
<desc>
<p>Takes over the distributed application
@@ -424,7 +459,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name name="unload" arity="1"/>
+ <name name="unload" arity="1" since=""/>
<fsummary>Unload an application.</fsummary>
<desc>
<p>Unloads the application specification for <c><anno>Application</anno></c>
@@ -435,8 +470,8 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name name="unset_env" arity="2"/>
- <name name="unset_env" arity="3"/>
+ <name name="unset_env" arity="2" since=""/>
+ <name name="unset_env" arity="3" since=""/>
<fsummary>Unset the value of a configuration parameter.</fsummary>
<desc>
<p>Removes the configuration parameter <c><anno>Par</anno></c> and its value
@@ -459,8 +494,8 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name name="which_applications" arity="0"/>
- <name name="which_applications" arity="1"/>
+ <name name="which_applications" arity="0" since=""/>
+ <name name="which_applications" arity="1" since=""/>
<fsummary>Get the currently running applications.</fsummary>
<desc>
<p>Returns a list with information about the applications that
@@ -484,7 +519,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</section>
<funcs>
<func>
- <name>Module:start(StartType, StartArgs) -> {ok, Pid} | {ok, Pid, State} | {error, Reason}</name>
+ <name since="">Module:start(StartType, StartArgs) -> {ok, Pid} | {ok, Pid, State} | {error, Reason}</name>
<fsummary>Start an application.</fsummary>
<type>
<v>StartType = <seealso marker="#type-start_type"><c>start_type()</c></seealso></v>
@@ -526,7 +561,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name>Module:start_phase(Phase, StartType, PhaseArgs) -> ok | {error, Reason}</name>
+ <name since="">Module:start_phase(Phase, StartType, PhaseArgs) -> ok | {error, Reason}</name>
<fsummary>Extended start of an application.</fsummary>
<type>
<v>Phase = atom()</v>
@@ -551,7 +586,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name>Module:prep_stop(State) -> NewState</name>
+ <name since="">Module:prep_stop(State) -> NewState</name>
<fsummary>Prepare an application for termination.</fsummary>
<type>
<v>State = NewState = term()</v>
@@ -569,7 +604,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name>Module:stop(State)</name>
+ <name since="">Module:stop(State)</name>
<fsummary>Clean up after termination of an application.</fsummary>
<type>
<v>State = term()</v>
@@ -585,7 +620,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name>Module:config_change(Changed, New, Removed) -> ok</name>
+ <name since="">Module:config_change(Changed, New, Removed) -> ok</name>
<fsummary>Update the configuration parameters for an application.</fsummary>
<type>
<v>Changed = [{Par,Val}]</v>
diff --git a/lib/kernel/doc/src/auth.xml b/lib/kernel/doc/src/auth.xml
index 5901446960..a57da18de9 100644
--- a/lib/kernel/doc/src/auth.xml
+++ b/lib/kernel/doc/src/auth.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>auth</module>
+ <module since="">auth</module>
<modulesummary>Erlang network authentication server.</modulesummary>
<description>
<p>This module is deprecated. For a description of the Magic
@@ -42,7 +42,7 @@
</datatypes>
<funcs>
<func>
- <name name="cookie" arity="0"/>
+ <name name="cookie" arity="0" since=""/>
<fsummary>Magic cookie for local node (deprecated).</fsummary>
<desc>
<p>Use
@@ -51,7 +51,7 @@
</desc>
</func>
<func>
- <name name="cookie" arity="1"/>
+ <name name="cookie" arity="1" since=""/>
<fsummary>Set the magic for the local node (deprecated).</fsummary>
<type_desc variable="TheCookie">
The cookie can also be specified as a list with a single atom element.
@@ -63,7 +63,7 @@
</desc>
</func>
<func>
- <name name="is_auth" arity="1"/>
+ <name name="is_auth" arity="1" since=""/>
<fsummary>Status of communication authorization (deprecated).</fsummary>
<desc>
<p>Returns <c>yes</c> if communication with <c><anno>Node</anno></c> is
@@ -76,7 +76,7 @@
</desc>
</func>
<func>
- <name>node_cookie([Node, Cookie]) -> yes | no</name>
+ <name since="">node_cookie([Node, Cookie]) -> yes | no</name>
<fsummary>Set the magic cookie for a node and verify authorization (deprecated).</fsummary>
<type>
<v>Node = node()</v>
@@ -88,7 +88,7 @@
</desc>
</func>
<func>
- <name name="node_cookie" arity="2"/>
+ <name name="node_cookie" arity="2" since=""/>
<fsummary>Set the magic cookie for a node and verify authorization (deprecated).</fsummary>
<desc>
<p>Sets the magic cookie of <c><anno>Node</anno></c> to
diff --git a/lib/kernel/doc/src/code.xml b/lib/kernel/doc/src/code.xml
index aff3e8133c..4aa9e8b9d2 100644
--- a/lib/kernel/doc/src/code.xml
+++ b/lib/kernel/doc/src/code.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>code</module>
+ <module since="">code</module>
<modulesummary>Erlang code server.</modulesummary>
<description>
<p>This module contains the interface to the Erlang
@@ -322,7 +322,7 @@ zip:create("mnesia-4.4.7.ez",
<funcs>
<func>
- <name name="set_path" arity="1"/>
+ <name name="set_path" arity="1" since=""/>
<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>
@@ -336,15 +336,15 @@ zip:create("mnesia-4.4.7.ez",
</desc>
</func>
<func>
- <name name="get_path" arity="0"/>
+ <name name="get_path" arity="0" since=""/>
<fsummary>Return the code server search path.</fsummary>
<desc>
<p>Returns the code path.</p>
</desc>
</func>
<func>
- <name name="add_path" arity="1"/>
- <name name="add_pathz" arity="1"/>
+ <name name="add_path" arity="1" since=""/>
+ <name name="add_pathz" arity="1" since=""/>
<fsummary>Add a directory to the end of the code path.</fsummary>
<type name="add_path_ret"/>
<desc>
@@ -357,7 +357,7 @@ zip:create("mnesia-4.4.7.ez",
</desc>
</func>
<func>
- <name name="add_patha" arity="1"/>
+ <name name="add_patha" arity="1" since=""/>
<fsummary>Add a directory to the beginning of the code path.</fsummary>
<type name="add_path_ret"/>
<desc>
@@ -370,8 +370,8 @@ zip:create("mnesia-4.4.7.ez",
</desc>
</func>
<func>
- <name name="add_paths" arity="1"/>
- <name name="add_pathsz" arity="1"/>
+ <name name="add_paths" arity="1" since=""/>
+ <name name="add_pathsz" arity="1" since=""/>
<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
@@ -381,7 +381,7 @@ zip:create("mnesia-4.4.7.ez",
</desc>
</func>
<func>
- <name name="add_pathsa" arity="1"/>
+ <name name="add_pathsa" arity="1" since=""/>
<fsummary>Add directories to the beginning of the code path.</fsummary>
<desc>
<p>Traverses <c><anno>Dirs</anno></c> and adds
@@ -397,7 +397,7 @@ zip:create("mnesia-4.4.7.ez",
</desc>
</func>
<func>
- <name name="del_path" arity="1"/>
+ <name name="del_path" arity="1" since=""/>
<fsummary>Delete a directory from the code path.</fsummary>
<desc>
<p>Deletes a directory from the code path. The argument can be
@@ -417,7 +417,7 @@ zip:create("mnesia-4.4.7.ez",
</desc>
</func>
<func>
- <name name="replace_path" arity="2"/>
+ <name name="replace_path" arity="2" since=""/>
<fsummary>Replace a directory with another in the code path.</fsummary>
<desc>
<p>Replaces an old occurrence of a directory
@@ -441,7 +441,7 @@ zip:create("mnesia-4.4.7.ez",
</desc>
</func>
<func>
- <name name="load_file" arity="1"/>
+ <name name="load_file" arity="1" since=""/>
<fsummary>Load a module.</fsummary>
<type name="load_ret"/>
<desc>
@@ -460,7 +460,7 @@ zip:create("mnesia-4.4.7.ez",
</desc>
</func>
<func>
- <name name="load_abs" arity="1"/>
+ <name name="load_abs" arity="1" since=""/>
<fsummary>Load a module, residing in a specified file.</fsummary>
<type name="load_ret"/>
<type name="loaded_filename"/>
@@ -477,7 +477,7 @@ zip:create("mnesia-4.4.7.ez",
</desc>
</func>
<func>
- <name name="ensure_loaded" arity="1"/>
+ <name name="ensure_loaded" arity="1" since=""/>
<fsummary>Ensure that a module is loaded.</fsummary>
<desc>
<p>Tries to load a module in the same way as
@@ -489,7 +489,7 @@ zip:create("mnesia-4.4.7.ez",
</desc>
</func>
<func>
- <name name="load_binary" arity="3"/>
+ <name name="load_binary" arity="3" since=""/>
<fsummary>Load object code for a module.</fsummary>
<type name="loaded_filename"/>
<type name="loaded_ret_atoms"/>
@@ -507,7 +507,7 @@ zip:create("mnesia-4.4.7.ez",
</desc>
</func>
<func>
- <name name="atomic_load" arity="1"/>
+ <name name="atomic_load" arity="1" since="OTP 19.0"/>
<fsummary>Load a list of modules atomically</fsummary>
<desc>
<p>Tries to load all of the modules in the list
@@ -538,7 +538,7 @@ zip:create("mnesia-4.4.7.ez",
</item>
<tag><c>not_purged</c></tag>
<item>
- <p>The object code can not be loaded because an old version
+ <p>The object code cannot be loaded because an old version
of the code already exists.</p>
</item>
<tag><c>sticky_directory</c></tag>
@@ -566,7 +566,7 @@ ok = code:finish_loading(Prepared),
</desc>
</func>
<func>
- <name name="prepare_loading" arity="1"/>
+ <name name="prepare_loading" arity="1" since="OTP 19.0"/>
<fsummary>Prepare a list of modules atomically</fsummary>
<desc>
<p>Prepares to load the modules in the list
@@ -598,7 +598,7 @@ ok = code:finish_loading(Prepared),
</desc>
</func>
<func>
- <name name="finish_loading" arity="1"/>
+ <name name="finish_loading" arity="1" since="OTP 19.0"/>
<fsummary>Finish loading a list of prepared modules atomically</fsummary>
<desc>
<p>Tries to load code for all modules that have been previously
@@ -611,7 +611,7 @@ ok = code:finish_loading(Prepared),
<taglist>
<tag><c>not_purged</c></tag>
<item>
- <p>The object code can not be loaded because an old version
+ <p>The object code cannot be loaded because an old version
of the code already exists.</p>
</item>
<tag><c>sticky_directory</c></tag>
@@ -627,7 +627,7 @@ ok = code:finish_loading(Prepared),
</desc>
</func>
<func>
- <name name="ensure_modules_loaded" arity="1"/>
+ <name name="ensure_modules_loaded" arity="1" since="OTP 19.0"/>
<fsummary>Ensure that a list of modules is loaded</fsummary>
<desc>
<p>Tries to load any modules not already loaded in the list
@@ -639,7 +639,7 @@ ok = code:finish_loading(Prepared),
</desc>
</func>
<func>
- <name name="delete" arity="1"/>
+ <name name="delete" arity="1" since=""/>
<fsummary>Remove current code for a module.</fsummary>
<desc>
<p>Removes the current code for <c><anno>Module</anno></c>, that is,
@@ -652,7 +652,7 @@ ok = code:finish_loading(Prepared),
</desc>
</func>
<func>
- <name name="purge" arity="1"/>
+ <name name="purge" arity="1" since=""/>
<fsummary>Remove old code for a module.</fsummary>
<desc>
<p>Purges the code for <c><anno>Module</anno></c>, that is, removes code
@@ -668,7 +668,7 @@ ok = code:finish_loading(Prepared),
</desc>
</func>
<func>
- <name name="soft_purge" arity="1"/>
+ <name name="soft_purge" arity="1" since=""/>
<fsummary>Remove old code for a module, unless no process uses it.</fsummary>
<desc>
<p>Purges the code for <c><anno>Module</anno></c>, that is, removes code
@@ -683,7 +683,7 @@ ok = code:finish_loading(Prepared),
</desc>
</func>
<func>
- <name name="is_loaded" arity="1"/>
+ <name name="is_loaded" arity="1" since=""/>
<fsummary>Check if a module is loaded.</fsummary>
<type name="loaded_filename"/>
<type name="loaded_ret_atoms"/>
@@ -702,7 +702,7 @@ ok = code:finish_loading(Prepared),
</desc>
</func>
<func>
- <name name="all_loaded" arity="0"/>
+ <name name="all_loaded" arity="0" since=""/>
<fsummary>Get all loaded modules.</fsummary>
<type name="loaded_filename"/>
<type name="loaded_ret_atoms"/>
@@ -716,7 +716,7 @@ ok = code:finish_loading(Prepared),
</desc>
</func>
<func>
- <name name="which" arity="1"/>
+ <name name="which" arity="1" since=""/>
<fsummary>The object code file of a module.</fsummary>
<type name="loaded_ret_atoms"/>
<desc>
@@ -731,7 +731,7 @@ ok = code:finish_loading(Prepared),
</desc>
</func>
<func>
- <name name="get_object_code" arity="1"/>
+ <name name="get_object_code" arity="1" since=""/>
<fsummary>Gets the object code for a module.</fsummary>
<desc>
<p>Searches the code path for the object code of module
@@ -750,7 +750,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="root_dir" arity="0"/>
+ <name name="root_dir" arity="0" since=""/>
<fsummary>Root directory of Erlang/OTP.</fsummary>
<desc>
<p>Returns the root directory of Erlang/OTP, which is
@@ -762,7 +762,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="lib_dir" arity="0"/>
+ <name name="lib_dir" arity="0" since=""/>
<fsummary>Library directory of Erlang/OTP.</fsummary>
<desc>
<p>Returns the library directory, <c>$OTPROOT/lib</c>, where
@@ -774,7 +774,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="lib_dir" arity="1"/>
+ <name name="lib_dir" arity="1" since=""/>
<fsummary>Library directory for an application.</fsummary>
<desc>
<p>Returns the path
@@ -807,7 +807,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="lib_dir" arity="2"/>
+ <name name="lib_dir" arity="2" since=""/>
<fsummary>Subdirectory for an application.</fsummary>
<desc>
<p>Returns the path to a subdirectory directly under the top
@@ -827,7 +827,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="compiler_dir" arity="0"/>
+ <name name="compiler_dir" arity="0" since=""/>
<fsummary>Library directory for the compiler.</fsummary>
<desc>
<p>Returns the compiler library directory. Equivalent to
@@ -835,7 +835,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="priv_dir" arity="1"/>
+ <name name="priv_dir" arity="1" since=""/>
<fsummary>Priv directory for an application.</fsummary>
<desc>
<p>Returns the path to the <c>priv</c> directory in an
@@ -846,7 +846,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="objfile_extension" arity="0"/>
+ <name name="objfile_extension" arity="0" since=""/>
<fsummary>Object code file extension.</fsummary>
<desc>
<p>Returns the object code file extension corresponding to
@@ -854,7 +854,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="stick_dir" arity="1"/>
+ <name name="stick_dir" arity="1" since=""/>
<fsummary>Mark a directory as sticky.</fsummary>
<desc>
<p>Marks <c><anno>Dir</anno></c> as sticky.</p>
@@ -862,7 +862,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="unstick_dir" arity="1"/>
+ <name name="unstick_dir" arity="1" since=""/>
<fsummary>Remove a sticky directory mark.</fsummary>
<desc>
<p>Unsticks a directory that is marked as
@@ -871,7 +871,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="is_sticky" arity="1"/>
+ <name name="is_sticky" arity="1" since=""/>
<fsummary>Test if a module is sticky.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Module</anno></c> is the
@@ -882,7 +882,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="where_is_file" arity="1"/>
+ <name name="where_is_file" arity="1" since=""/>
<fsummary>Full name of a file located in the code path.</fsummary>
<desc>
<p>Searches the code path for <c><anno>Filename</anno></c>, a file of
@@ -893,7 +893,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="clash" arity="0"/>
+ <name name="clash" arity="0" since=""/>
<fsummary>Search for modules with identical names.</fsummary>
<desc>
<p>Searches all directories in the code path for module names with
@@ -901,7 +901,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="module_status" arity="1"/>
+ <name name="module_status" arity="1" since="OTP 20.0"/>
<fsummary>Return the status of the module in relation to object file on disk.</fsummary>
<desc>
<p>Returns:</p>
@@ -934,7 +934,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="modified_modules" arity="0"/>
+ <name name="modified_modules" arity="0" since="OTP 20.0"/>
<fsummary>Return a list of all modules modified on disk.</fsummary>
<desc>
<p>Returns the list of all currently loaded modules for which
@@ -943,7 +943,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="is_module_native" arity="1"/>
+ <name name="is_module_native" arity="1" since=""/>
<fsummary>Test if a module has native code.</fsummary>
<desc>
<p>Returns:</p>
@@ -961,7 +961,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</func>
<func>
- <name name="get_mode" arity="0"/>
+ <name name="get_mode" arity="0" since="OTP R16B"/>
<fsummary>The mode of the code server.</fsummary>
<desc>
<p>Returns an atom describing the mode of the code server:
diff --git a/lib/kernel/doc/src/disk_log.xml b/lib/kernel/doc/src/disk_log.xml
index 884cb32c0c..e308b06f3c 100644
--- a/lib/kernel/doc/src/disk_log.xml
+++ b/lib/kernel/doc/src/disk_log.xml
@@ -34,7 +34,7 @@
<rev>D</rev>
<file>disk_log.sgml</file>
</header>
- <module>disk_log</module>
+ <module since="">disk_log</module>
<modulesummary>A disk-based term logging facility.</modulesummary>
<description>
<p><c>disk_log</c> is a disk-based term logger that enables
@@ -238,7 +238,7 @@
</datatypes>
<funcs>
<func>
- <name name="accessible_logs" arity="0"/>
+ <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.
@@ -248,8 +248,8 @@
</desc>
</func>
<func>
- <name name="alog" arity="2"/>
- <name name="balog" arity="2"/>
+ <name name="alog" arity="2" since=""/>
+ <name name="balog" arity="2" since=""/>
<fsummary>Asynchronously log an item on to a disk log.</fsummary>
<type variable="Log"/>
<type variable="Term" name_i="1"/>
@@ -275,8 +275,8 @@
</desc>
</func>
<func>
- <name name="alog_terms" arity="2"/>
- <name name="balog_terms" arity="2"/>
+ <name name="alog_terms" arity="2" since=""/>
+ <name name="balog_terms" arity="2" since=""/>
<fsummary>Asynchronously log many items on to a disk log.</fsummary>
<type variable="Log"/>
<type variable="TermList" name_i="1"/>
@@ -303,8 +303,8 @@
</desc>
</func>
<func>
- <name name="block" arity="1"/>
- <name name="block" arity="2"/>
+ <name name="block" arity="1" since=""/>
+ <name name="block" arity="2" since=""/>
<fsummary>Block a disk log.</fsummary>
<type name="block_error_rsn"/>
<desc>
@@ -330,21 +330,21 @@
</desc>
</func>
<func>
- <name name="change_header" arity="2"/>
+ <name name="change_header" arity="2" since=""/>
<fsummary>Change option head or head_func for an owner of a disk log.</fsummary>
<desc>
<p>Changes the value of option <c>head</c> or <c>head_func</c> for an owner of a disk log.</p>
</desc>
</func>
<func>
- <name name="change_notify" arity="3"/>
+ <name name="change_notify" arity="3" since=""/>
<fsummary>Change option notify for an owner of a disk log.</fsummary>
<desc>
<p>Changes the value of option <c>notify</c> for an owner of a disk log. </p>
</desc>
</func>
<func>
- <name name="change_size" arity="2"/>
+ <name name="change_size" arity="2" since=""/>
<fsummary>Change the size of an open disk log.</fsummary>
<desc>
<p>Changes the size of an open log.
@@ -384,10 +384,10 @@
</desc>
</func>
<func>
- <name name="chunk" arity="2"/>
- <name name="chunk" arity="3"/>
- <name name="bchunk" arity="2"/>
- <name name="bchunk" arity="3"/>
+ <name name="chunk" arity="2" since=""/>
+ <name name="chunk" arity="3" since=""/>
+ <name name="bchunk" arity="2" since=""/>
+ <name name="bchunk" arity="3" since=""/>
<fsummary>Read a chunk of items written to a disk log.</fsummary>
<type variable="Log"/>
<type variable="Continuation"/>
@@ -447,7 +447,7 @@
</desc>
</func>
<func>
- <name name="chunk_info" arity="1"/>
+ <name name="chunk_info" arity="1" since=""/>
<fsummary>Return information about a chunk continuation of a disk log.</fsummary>
<desc>
<p>Returns the pair <c>{node, <anno>Node</anno>}</c>,
@@ -457,7 +457,7 @@
</desc>
</func>
<func>
- <name name="chunk_step" arity="3"/>
+ <name name="chunk_step" arity="3" since=""/>
<fsummary>Step forward or backward among the wrap log files of a disk log.</fsummary>
<desc>
<p>Can be used with <c>chunk/2,3</c> and <c>bchunk/2,3</c>
@@ -480,7 +480,7 @@
</desc>
</func>
<func>
- <name name="close" arity="1"/>
+ <name name="close" arity="1" since=""/>
<fsummary>Close a disk log.</fsummary>
<type name="close_error_rsn"/>
<desc>
@@ -505,7 +505,7 @@
</desc>
</func>
<func>
- <name name="format_error" arity="1"/>
+ <name name="format_error" arity="1" since=""/>
<fsummary>Return an English description of a disk log error reply.</fsummary>
<desc>
<p>Given the error returned by any function in this module,
@@ -517,7 +517,7 @@
</desc>
</func>
<func>
- <name name="inc_wrap_file" arity="1"/>
+ <name name="inc_wrap_file" arity="1" since=""/>
<fsummary>Change to the next wrap log file of a disk log.</fsummary>
<type name="inc_wrap_error_rsn"/>
<type name="invalid_header"/>
@@ -534,7 +534,7 @@
</desc>
</func>
<func>
- <name name="info" arity="1"/>
+ <name name="info" arity="1" since=""/>
<fsummary>Return information about a disk log.</fsummary>
<type name="dlog_info"/>
<desc>
@@ -685,8 +685,8 @@
</desc>
</func>
<func>
- <name name="lclose" arity="1"/>
- <name name="lclose" arity="2"/>
+ <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>
@@ -704,8 +704,8 @@
</desc>
</func>
<func>
- <name name="log" arity="2"/>
- <name name="blog" arity="2"/>
+ <name name="log" arity="2" since=""/>
+ <name name="blog" arity="2" since=""/>
<fsummary>Log an item onto a disk log.</fsummary>
<type variable="Log"/>
<type variable="Term" name_i="1"/>
@@ -739,8 +739,8 @@
</desc>
</func>
<func>
- <name name="log_terms" arity="2"/>
- <name name="blog_terms" arity="2"/>
+ <name name="log_terms" arity="2" since=""/>
+ <name name="blog_terms" arity="2" since=""/>
<fsummary>Log many items onto a disk log.</fsummary>
<type variable="Log"/>
<type variable="TermList" name_i="1"/>
@@ -768,7 +768,7 @@
</desc>
</func>
<func>
- <name name="open" arity="1"/>
+ <name name="open" arity="1" since=""/>
<fsummary>Open a disk log file.</fsummary>
<type name="dlog_options"/>
<type name="dlog_option"/>
@@ -1041,7 +1041,7 @@
</desc>
</func>
<func>
- <name name="pid2name" arity="1"/>
+ <name name="pid2name" arity="1" since=""/>
<fsummary>Return the name of the disk log handled by a pid.</fsummary>
<desc>
<p>Returns the log name
@@ -1053,9 +1053,9 @@
</desc>
</func>
<func>
- <name name="reopen" arity="2"/>
- <name name="reopen" arity="3"/>
- <name name="breopen" arity="3"/>
+ <name name="reopen" arity="2" since=""/>
+ <name name="reopen" arity="3" since=""/>
+ <name name="breopen" arity="3" since=""/>
<fsummary>Reopen a disk log and save the old log.</fsummary>
<type variable="Log"/>
<type variable="File" name_i="1"/>
@@ -1087,7 +1087,7 @@
</desc>
</func>
<func>
- <name name="sync" arity="1"/>
+ <name name="sync" arity="1" since=""/>
<fsummary>Flush the contents of a disk log to the disk.</fsummary>
<type name="sync_error_rsn"/>
<desc>
@@ -1097,9 +1097,9 @@
</desc>
</func>
<func>
- <name name="truncate" arity="1"/>
- <name name="truncate" arity="2"/>
- <name name="btruncate" arity="2"/>
+ <name name="truncate" arity="1" since=""/>
+ <name name="truncate" arity="2" since=""/>
+ <name name="btruncate" arity="2" since=""/>
<fsummary>Truncate a disk log.</fsummary>
<type variable="Log"/>
<type variable="Head" name_i="2"/>
@@ -1129,7 +1129,7 @@
</desc>
</func>
<func>
- <name name="unblock" arity="1"/>
+ <name name="unblock" arity="1" since=""/>
<fsummary>Unblock a disk log.</fsummary>
<type name="unblock_error_rsn"/>
<desc>
diff --git a/lib/kernel/doc/src/erl_boot_server.xml b/lib/kernel/doc/src/erl_boot_server.xml
index 4109251387..89f9855c49 100644
--- a/lib/kernel/doc/src/erl_boot_server.xml
+++ b/lib/kernel/doc/src/erl_boot_server.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>erl_boot_server</module>
+ <module since="">erl_boot_server</module>
<modulesummary>Boot server for other Erlang machines.</modulesummary>
<description>
<p>This server is used to assist diskless Erlang nodes that fetch
@@ -52,14 +52,14 @@
</description>
<funcs>
<func>
- <name name="add_slave" arity="1"/>
+ <name name="add_slave" arity="1" since=""/>
<fsummary>Add a slave to the list of allowed slaves.</fsummary>
<desc>
<p>Adds a <c><anno>Slave</anno></c> node to the list of allowed slave hosts.</p>
</desc>
</func>
<func>
- <name name="delete_slave" arity="1"/>
+ <name name="delete_slave" arity="1" since=""/>
<fsummary>Delete a slave from the list of allowed slaves.</fsummary>
<desc>
<p>Deletes a <c><anno>Slave</anno></c> node from the list of allowed slave
@@ -67,7 +67,7 @@
</desc>
</func>
<func>
- <name name="start" arity="1"/>
+ <name name="start" arity="1" since=""/>
<fsummary>Start the boot server.</fsummary>
<desc>
<p>Starts the boot server. <c><anno>Slaves</anno></c> is a list of
@@ -76,7 +76,7 @@
</desc>
</func>
<func>
- <name name="start_link" arity="1"/>
+ <name name="start_link" arity="1" since=""/>
<fsummary>Start the boot server and link to the the caller.</fsummary>
<desc>
<p>Starts the boot server and links to the caller. This function
@@ -85,7 +85,7 @@
</desc>
</func>
<func>
- <name name="which_slaves" arity="0"/>
+ <name name="which_slaves" arity="0" since=""/>
<fsummary>Return the current list of allowed slave hosts.</fsummary>
<desc>
<p>Returns the current list of allowed slave hosts.</p>
diff --git a/lib/kernel/doc/src/erl_ddll.xml b/lib/kernel/doc/src/erl_ddll.xml
index 75114e015c..f2d5e1b397 100644
--- a/lib/kernel/doc/src/erl_ddll.xml
+++ b/lib/kernel/doc/src/erl_ddll.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>erl_ddll</module>
+ <module since="">erl_ddll</module>
<modulesummary>Dynamic driver loader and linker.</modulesummary>
<description>
<p>This module provides an interface for loading
@@ -196,7 +196,7 @@
</datatypes>
<funcs>
<func>
- <name name="demonitor" arity="1"/>
+ <name name="demonitor" arity="1" since=""/>
<fsummary>Remove a monitor for a driver.</fsummary>
<desc>
<p>Removes a driver monitor in much the same way as
@@ -212,7 +212,7 @@
</desc>
</func>
<func>
- <name name="format_error" arity="1"/>
+ <name name="format_error" arity="1" since=""/>
<fsummary>Format an error descriptor.</fsummary>
<desc>
<p>Takes an <c><anno>ErrorDesc</anno></c> returned by load, unload, or
@@ -229,7 +229,7 @@
</desc>
</func>
<func>
- <name name="info" arity="0"/>
+ <name name="info" arity="0" since=""/>
<fsummary>Retrieve information about all drivers.</fsummary>
<desc>
<p>Returns a list of tuples <c>{<anno>DriverName</anno>, <anno>InfoList</anno>}</c>,
@@ -240,7 +240,7 @@
</desc>
</func>
<func>
- <name name="info" arity="1"/>
+ <name name="info" arity="1" since=""/>
<fsummary>Retrieve information about one driver.</fsummary>
<desc>
<p>Returns a list of tuples <c>{<anno>Tag</anno>, <anno>Value</anno>}</c>,
@@ -266,7 +266,7 @@
</desc>
</func>
<func>
- <name name="info" arity="2"/>
+ <name name="info" arity="2" since=""/>
<fsummary>Retrieve specific information about one driver.</fsummary>
<desc>
<p>Returns specific information about one aspect of a driver.
@@ -328,7 +328,7 @@
</desc>
</func>
<func>
- <name name="load" arity="2"/>
+ <name name="load" arity="2" since=""/>
<fsummary>Load a driver.</fsummary>
<desc>
<p>Loads and links the dynamic driver <c><anno>Name</anno></c>.
@@ -390,7 +390,7 @@
</desc>
</func>
<func>
- <name name="load_driver" arity="2"/>
+ <name name="load_driver" arity="2" since=""/>
<fsummary>Load a driver.</fsummary>
<desc>
<p>Works essentially as <c>load/2</c>, but loads the driver
@@ -413,7 +413,7 @@
</desc>
</func>
<func>
- <name name="loaded_drivers" arity="0"/>
+ <name name="loaded_drivers" arity="0" since=""/>
<fsummary>List loaded drivers.</fsummary>
<desc>
<p>Returns a list of all the available drivers, both
@@ -425,7 +425,7 @@
</desc>
</func>
<func>
- <name name="monitor" arity="2"/>
+ <name name="monitor" arity="2" since=""/>
<fsummary>Create a monitor for a driver.</fsummary>
<desc>
<p>Creates a driver monitor and works in many
@@ -588,7 +588,7 @@
</desc>
</func>
<func>
- <name name="reload" arity="2"/>
+ <name name="reload" arity="2" since=""/>
<fsummary>Replace a driver.</fsummary>
<desc>
<p>Reloads the driver named <c><anno>Name</anno></c> from a possibly
@@ -626,7 +626,7 @@
</desc>
</func>
<func>
- <name name="reload_driver" arity="2"/>
+ <name name="reload_driver" arity="2" since=""/>
<fsummary>Replace a driver.</fsummary>
<desc>
<p>Works exactly as <seealso marker="#reload/2"><c>reload/2</c></seealso>,
@@ -644,7 +644,7 @@
</desc>
</func>
<func>
- <name name="try_load" arity="3"/>
+ <name name="try_load" arity="3" since=""/>
<fsummary>Load a driver.</fsummary>
<desc>
<p>Provides more control than the
@@ -931,7 +931,7 @@
</desc>
</func>
<func>
- <name name="try_unload" arity="2"/>
+ <name name="try_unload" arity="2" since=""/>
<fsummary>Unload a driver.</fsummary>
<desc>
<p>This is the low-level function to unload (or decrement
@@ -1116,7 +1116,7 @@
</desc>
</func>
<func>
- <name name="unload" arity="1"/>
+ <name name="unload" arity="1" since=""/>
<fsummary>Unload a driver.</fsummary>
<desc>
<p>Unloads, or at least dereferences the driver named
@@ -1143,7 +1143,7 @@
</desc>
</func>
<func>
- <name name="unload_driver" arity="1"/>
+ <name name="unload_driver" arity="1" since=""/>
<fsummary>Unload a driver.</fsummary>
<desc>
<p>Unloads, or at least dereferences the driver named
diff --git a/lib/kernel/doc/src/erl_epmd.xml b/lib/kernel/doc/src/erl_epmd.xml
index 8b076cd2d7..2adbf11a28 100644
--- a/lib/kernel/doc/src/erl_epmd.xml
+++ b/lib/kernel/doc/src/erl_epmd.xml
@@ -28,7 +28,7 @@
<date>2018-02-19</date>
<rev>A</rev>
</header>
- <module>erl_epmd</module>
+ <module since="OTP R14B">erl_epmd</module>
<modulesummary>
Erlang interface towards epmd
</modulesummary>
@@ -41,7 +41,7 @@
<funcs>
<func>
- <name name="start_link" arity="0"/>
+ <name name="start_link" arity="0" since="OTP 21.0"/>
<fsummary>Callback for erl_distribution supervisor.</fsummary>
<desc>
<p>This function is invoked as this module is added as a child of the
@@ -50,8 +50,8 @@
</func>
<func>
- <name name="register_node" arity="2"/>
- <name name="register_node" arity="3"/>
+ <name name="register_node" arity="2" since="OTP 21.0"/>
+ <name name="register_node" arity="3" since="OTP 21.0"/>
<fsummary>Registers the node with <c>epmd</c>.</fsummary>
<desc>
<p>Registers the node with <c>epmd</c> and tells epmd what port will be
@@ -62,8 +62,8 @@
</func>
<func>
- <name name="port_please" arity="2"/>
- <name name="port_please" arity="3"/>
+ <name name="port_please" arity="2" since="OTP 21.0"/>
+ <name name="port_please" arity="3" since="OTP 21.0"/>
<fsummary>Returns the port number for a given node.</fsummary>
<desc>
<p>Requests the distribution port for the given node of an EPMD
@@ -73,7 +73,7 @@
</func>
<func>
- <name name="address_please" arity="3"/>
+ <name name="address_please" arity="3" since="OTP 21.0"/>
<fsummary>Returns address and port.</fsummary>
<desc>
<p>Called by the distribution module. Resolves the <c>Host</c> to an IP
@@ -84,7 +84,7 @@
</func>
<func>
- <name name="names" arity="1"/>
+ <name name="names" arity="1" since="OTP 21.0"/>
<fsummary>Names of Erlang nodes at a host.</fsummary>
<desc>
<p>Called by <seealso marker="net_adm"><c>net_adm:names/0</c></seealso>.
diff --git a/lib/kernel/doc/src/error_handler.xml b/lib/kernel/doc/src/error_handler.xml
index e5639487dc..eb01e87aee 100644
--- a/lib/kernel/doc/src/error_handler.xml
+++ b/lib/kernel/doc/src/error_handler.xml
@@ -30,7 +30,7 @@
<date></date>
<rev></rev>
</header>
- <module>error_handler</module>
+ <module since="">error_handler</module>
<modulesummary>Default system error handler.</modulesummary>
<description>
<p>This module defines what happens when certain types
@@ -38,7 +38,7 @@
</description>
<funcs>
<func>
- <name name="raise_undef_exception" arity="3"/>
+ <name name="raise_undef_exception" arity="3" since="OTP R16B"/>
<fsummary>Raise an undef exception.</fsummary>
<type_desc variable="Args">
A (possibly empty) list of arguments <c>Arg1,..,ArgN</c>
@@ -51,7 +51,7 @@
</desc>
</func>
<func>
- <name name="undefined_function" arity="3"/>
+ <name name="undefined_function" arity="3" since=""/>
<fsummary>Called when an undefined function is encountered.</fsummary>
<type_desc variable="Args">
A (possibly empty) list of arguments <c>Arg1,..,ArgN</c>
@@ -93,7 +93,7 @@
</desc>
</func>
<func>
- <name name="undefined_lambda" arity="3"/>
+ <name name="undefined_lambda" arity="3" since=""/>
<fsummary>Called when an undefined lambda (fun) is encountered.</fsummary>
<type_desc variable="Args">
A (possibly empty) list of arguments <c>Arg1,..,ArgN</c>
diff --git a/lib/kernel/doc/src/error_logger.xml b/lib/kernel/doc/src/error_logger.xml
index c3d68fd79f..c170b4fa34 100644
--- a/lib/kernel/doc/src/error_logger.xml
+++ b/lib/kernel/doc/src/error_logger.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>error_logger</module>
+ <module since="">error_logger</module>
<modulesummary>Erlang error logger.</modulesummary>
<description>
@@ -76,8 +76,8 @@
</datatypes>
<funcs>
<func>
- <name name="add_report_handler" arity="1"/>
- <name name="add_report_handler" arity="2"/>
+ <name name="add_report_handler" arity="1" since=""/>
+ <name name="add_report_handler" arity="2" since=""/>
<fsummary>Add an event handler to the error logger.</fsummary>
<desc>
<p>Adds a new event handler to the error logger. The event
@@ -96,7 +96,7 @@
</desc>
</func>
<func>
- <name name="delete_report_handler" arity="1"/>
+ <name name="delete_report_handler" arity="1" since=""/>
<fsummary>Delete an event handler from the error logger.</fsummary>
<desc>
<p>Deletes an event handler from the error logger by calling
@@ -108,9 +108,9 @@
</desc>
</func>
<func>
- <name name="error_msg" arity="1"/>
- <name name="error_msg" arity="2"/>
- <name name="format" arity="2"/>
+ <name name="error_msg" arity="1" since=""/>
+ <name name="error_msg" arity="2" since=""/>
+ <name name="format" arity="2" since=""/>
<fsummary>Log a standard error event.</fsummary>
<desc>
<p>Log a standard error event. The <c><anno>Format</anno></c>
@@ -142,7 +142,7 @@ ok</pre>
</desc>
</func>
<func>
- <name name="error_report" arity="1"/>
+ <name name="error_report" arity="1" since=""/>
<fsummary>Log a standard error event.</fsummary>
<desc>
<p>Log a standard error event. Error logger forwards the event
@@ -169,7 +169,7 @@ ok</pre>
</desc>
</func>
<func>
- <name name="error_report" arity="2"/>
+ <name name="error_report" arity="2" since=""/>
<fsummary>Log a user-defined error event.</fsummary>
<desc>
<p>Log a user-defined error event. Error logger forwards the
@@ -191,7 +191,7 @@ ok</pre>
</desc>
</func>
<func>
- <name name="get_format_depth" arity="0"/>
+ <name name="get_format_depth" arity="0" since="OTP 20.0"/>
<fsummary>Get the value of the Kernel application variable
<c>error_logger_format_depth</c>.</fsummary>
<desc>
@@ -211,8 +211,8 @@ ok</pre>
</desc>
</func>
<func>
- <name name="info_msg" arity="1"/>
- <name name="info_msg" arity="2"/>
+ <name name="info_msg" arity="1" since=""/>
+ <name name="info_msg" arity="2" since=""/>
<fsummary>Log a standard information event.</fsummary>
<desc>
<p>Log a standard information event. The <c><anno>Format</anno></c>
@@ -244,7 +244,7 @@ ok</pre>
</desc>
</func>
<func>
- <name name="info_report" arity="1"/>
+ <name name="info_report" arity="1" since=""/>
<fsummary>Log a standard information event.</fsummary>
<desc>
<p>Log a standard information event. Error logger forwards the
@@ -271,7 +271,7 @@ ok</pre>
</desc>
</func>
<func>
- <name name="info_report" arity="2"/>
+ <name name="info_report" arity="2" since=""/>
<fsummary>Log a user-defined information event.</fsummary>
<desc>
<p>Log a user-defined information event. Error logger forwards
@@ -294,9 +294,9 @@ ok</pre>
</desc>
</func>
<func>
- <name name="logfile" arity="1" clause_i="1"/>
- <name name="logfile" arity="1" clause_i="2"/>
- <name name="logfile" arity="1" clause_i="3"/>
+ <name name="logfile" arity="1" clause_i="1" since=""/>
+ <name name="logfile" arity="1" clause_i="2" since=""/>
+ <name name="logfile" arity="1" clause_i="3" since=""/>
<fsummary>Enable or disable error printouts to a file.</fsummary>
<type variable="Filename"/>
<type variable="OpenReason" name_i="1"/>
@@ -346,7 +346,7 @@ ok</pre>
</desc>
</func>
<func>
- <name name="tty" arity="1"/>
+ <name name="tty" arity="1" since=""/>
<fsummary>Enable or disable printouts to the terminal.</fsummary>
<desc>
<p>Enables (<c><anno>Flag</anno> == true</c>) or disables
@@ -363,7 +363,7 @@ ok</pre>
</desc>
</func>
<func>
- <name name="warning_map" arity="0"/>
+ <name name="warning_map" arity="0" since=""/>
<fsummary>Return the current mapping for warning events.</fsummary>
<desc>
<p>Returns the current mapping for warning events. Events sent
@@ -400,8 +400,8 @@ ok</pre>
</desc>
</func>
<func>
- <name name="warning_msg" arity="1"/>
- <name name="warning_msg" arity="2"/>
+ <name name="warning_msg" arity="1" since=""/>
+ <name name="warning_msg" arity="2" since=""/>
<fsummary>Log a standard warning event.</fsummary>
<desc>
<p>Log a standard warning event. The <c><anno>Format</anno></c>
@@ -429,7 +429,7 @@ ok</pre>
</desc>
</func>
<func>
- <name name="warning_report" arity="1"/>
+ <name name="warning_report" arity="1" since=""/>
<fsummary>Log a standard warning event.</fsummary>
<desc>
<p>Log a standard warning event. Error logger forwards the event
@@ -446,7 +446,7 @@ ok</pre>
</desc>
</func>
<func>
- <name name="warning_report" arity="2"/>
+ <name name="warning_report" arity="2" since=""/>
<fsummary>Log a user-defined warning event.</fsummary>
<desc>
<p>Log a user-defined warning event. Error logger forwards the
diff --git a/lib/kernel/doc/src/file.xml b/lib/kernel/doc/src/file.xml
index 9acaf6b41e..fc25e83d40 100644
--- a/lib/kernel/doc/src/file.xml
+++ b/lib/kernel/doc/src/file.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>file</module>
+ <module since="">file</module>
<modulesummary>File interface module.</modulesummary>
<description>
<p>This module provides an interface to the file system.</p>
@@ -186,7 +186,7 @@
<funcs>
<func>
- <name name="advise" arity="4"/>
+ <name name="advise" arity="4" since="OTP R14B"/>
<fsummary>Predeclare an access pattern for file data.</fsummary>
<type name="posix_file_advise"/>
<desc>
@@ -197,7 +197,7 @@
</desc>
</func>
<func>
- <name name="allocate" arity="3"/>
+ <name name="allocate" arity="3" since="OTP R16B"/>
<fsummary>Allocate file space.</fsummary>
<desc>
<p><c>allocate/3</c> can be used to preallocate space for a file.</p>
@@ -209,7 +209,7 @@
</desc>
</func>
<func>
- <name name="change_group" arity="2"/>
+ <name name="change_group" arity="2" since=""/>
<fsummary>Change group of a file.</fsummary>
<desc>
<p>Changes group of a file. See
@@ -217,7 +217,7 @@
</desc>
</func>
<func>
- <name name="change_mode" arity="2"/>
+ <name name="change_mode" arity="2" since="OTP R14B"/>
<fsummary>Change permissions of a file.</fsummary>
<desc>
<p>Changes permissions of a file. See
@@ -225,7 +225,7 @@
</desc>
</func>
<func>
- <name name="change_owner" arity="2"/>
+ <name name="change_owner" arity="2" since=""/>
<fsummary>Change owner of a file.</fsummary>
<desc>
<p>Changes owner of a file. See
@@ -233,7 +233,7 @@
</desc>
</func>
<func>
- <name name="change_owner" arity="3"/>
+ <name name="change_owner" arity="3" since=""/>
<fsummary>Change owner and group of a file.</fsummary>
<desc>
<p>Changes owner and group of a file. See
@@ -241,7 +241,7 @@
</desc>
</func>
<func>
- <name name="change_time" arity="2"/>
+ <name name="change_time" arity="2" since=""/>
<fsummary>Change the modification time of a file.</fsummary>
<desc>
<p>Changes the modification and access times of a file. See
@@ -249,7 +249,7 @@
</desc>
</func>
<func>
- <name name="change_time" arity="3"/>
+ <name name="change_time" arity="3" since=""/>
<fsummary>Change the modification and last access time of a file.</fsummary>
<desc>
<p>Changes the modification and last access times of a file. See
@@ -257,7 +257,7 @@
</desc>
</func>
<func>
- <name name="close" arity="1"/>
+ <name name="close" arity="1" since=""/>
<fsummary>Close a file.</fsummary>
<desc>
<p>Closes the file referenced by <c><anno>IoDevice</anno></c>. It mostly
@@ -270,7 +270,7 @@
</desc>
</func>
<func>
- <name name="consult" arity="1"/>
+ <name name="consult" arity="1" since=""/>
<fsummary>Read Erlang terms from a file.</fsummary>
<desc>
<p>Reads Erlang terms, separated by '.', from
@@ -308,8 +308,8 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="copy" arity="2"/>
- <name name="copy" arity="3"/>
+ <name name="copy" arity="2" since=""/>
+ <name name="copy" arity="3" since=""/>
<fsummary>Copy file contents.</fsummary>
<desc>
<p>Copies <c><anno>ByteCount</anno></c> bytes from
@@ -346,7 +346,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="datasync" arity="1"/>
+ <name name="datasync" arity="1" since="OTP R14B"/>
<fsummary>Synchronize the in-memory data of a file, ignoring most of its metadata, with that on the physical medium.</fsummary>
<desc>
<p>Ensures that any buffers kept by the operating system
@@ -369,7 +369,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="del_dir" arity="1"/>
+ <name name="del_dir" arity="1" since=""/>
<fsummary>Delete a directory.</fsummary>
<desc>
<p>Tries to delete directory <c><anno>Dir</anno></c>.
@@ -405,7 +405,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="delete" arity="1"/>
+ <name name="delete" arity="1" since=""/>
<fsummary>Delete a file.</fsummary>
<desc>
<p>Tries to delete file <c><anno>Filename</anno></c>.
@@ -442,7 +442,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="eval" arity="1"/>
+ <name name="eval" arity="1" since=""/>
<fsummary>Evaluate Erlang expressions in a file.</fsummary>
<desc>
<p>Reads and evaluates Erlang expressions, separated by '.' (or
@@ -476,7 +476,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="eval" arity="2"/>
+ <name name="eval" arity="2" since=""/>
<fsummary>Evaluate Erlang expressions in a file.</fsummary>
<desc>
<p>The same as <c>eval/1</c>, but the variable bindings
@@ -486,7 +486,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="format_error" arity="1"/>
+ <name name="format_error" arity="1" since=""/>
<fsummary>Return a descriptive string for an error reason.</fsummary>
<desc>
<p>Given the error reason returned by any function in this
@@ -494,7 +494,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="get_cwd" arity="0"/>
+ <name name="get_cwd" arity="0" since=""/>
<fsummary>Get the current working directory.</fsummary>
<desc>
<p>Returns <c>{ok, <anno>Dir</anno>}</c>, where <c><anno>Dir</anno></c>
@@ -516,7 +516,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="get_cwd" arity="1"/>
+ <name name="get_cwd" arity="1" since=""/>
<fsummary>Get the current working directory for the specified drive.</fsummary>
<desc>
<p>Returns <c>{ok, <anno>Dir</anno>}</c> or
@@ -547,7 +547,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="list_dir" arity="1"/>
+ <name name="list_dir" arity="1" since=""/>
<fsummary>List files in a directory.</fsummary>
<desc>
<p>Lists all files in a directory, <em>except</em> files
@@ -578,7 +578,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="list_dir_all" arity="1"/>
+ <name name="list_dir_all" arity="1" since="OTP R16B"/>
<fsummary>List all files in a directory.</fsummary>
<desc>
<p><marker id="list_dir_all"/>Lists all the files in a directory,
@@ -603,7 +603,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="make_dir" arity="1"/>
+ <name name="make_dir" arity="1" since=""/>
<fsummary>Make a directory.</fsummary>
<desc>
<p>Tries to create directory <c><anno>Dir</anno></c>. Missing parent
@@ -637,7 +637,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="make_link" arity="2"/>
+ <name name="make_link" arity="2" since=""/>
<fsummary>Make a hard link to a file.</fsummary>
<desc>
<p>Makes a hard link from <c><anno>Existing</anno></c> to
@@ -666,7 +666,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="make_symlink" arity="2"/>
+ <name name="make_symlink" arity="2" since=""/>
<fsummary>Make a symbolic link to a file or directory.</fsummary>
<desc>
<p>Creates a symbolic link <c><anno>New</anno></c> to
@@ -702,7 +702,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="native_name_encoding" arity="0"/>
+ <name name="native_name_encoding" arity="0" since="OTP R14B01"/>
<fsummary>Return the configured filename encoding of the VM.</fsummary>
<desc>
<p><marker id="native_name_encoding"/>Returns
@@ -714,7 +714,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="open" arity="2"/>
+ <name name="open" arity="2" since=""/>
<fsummary>Open a file.</fsummary>
<desc>
<p>Opens file <c><anno>File</anno></c> in the mode determined
@@ -997,7 +997,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="path_consult" arity="2"/>
+ <name name="path_consult" arity="2" since=""/>
<fsummary>Read Erlang terms from a file.</fsummary>
<desc>
<p>Searches the path <c><anno>Path</anno></c> (a list of directory
@@ -1039,7 +1039,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="path_eval" arity="2"/>
+ <name name="path_eval" arity="2" since=""/>
<fsummary>Evaluate Erlang expressions in a file.</fsummary>
<desc>
<p>Searches the path <c><anno>Path</anno></c> (a list of directory
@@ -1085,7 +1085,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="path_open" arity="3"/>
+ <name name="path_open" arity="3" since=""/>
<fsummary>Open a file.</fsummary>
<desc>
<p>Searches the path <c><anno>Path</anno></c> (a list of directory
@@ -1114,7 +1114,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="path_script" arity="2"/>
+ <name name="path_script" arity="2" since=""/>
<fsummary>Evaluate and return the value of Erlang expressions in a file.</fsummary>
<desc>
<p>Searches the path <c><anno>Path</anno></c> (a list of directory
@@ -1158,7 +1158,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="path_script" arity="3"/>
+ <name name="path_script" arity="3" since=""/>
<fsummary>Evaluate and return the value of Erlang expressions in a file.</fsummary>
<desc>
<p>The same as <c>path_script/2</c> but the variable bindings
@@ -1168,7 +1168,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="pid2name" arity="1"/>
+ <name name="pid2name" arity="1" since=""/>
<fsummary>Return the name of the file handled by a pid.</fsummary>
<desc>
<p>If <c><anno>Pid</anno></c> is an I/O device, that is, a pid returned from
@@ -1193,7 +1193,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="position" arity="2"/>
+ <name name="position" arity="2" since=""/>
<fsummary>Set position in a file.</fsummary>
<desc>
<p>Sets the position of the file referenced by <c><anno>IoDevice</anno></c>
@@ -1245,7 +1245,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="pread" arity="2"/>
+ <name name="pread" arity="2" since=""/>
<fsummary>Read from a file at certain positions.</fsummary>
<desc>
<p>Performs a sequence of <c>pread/3</c> in one operation,
@@ -1263,7 +1263,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="pread" arity="3"/>
+ <name name="pread" arity="3" since=""/>
<fsummary>Read from a file at a certain position.</fsummary>
<desc>
<p>Combines <c>position/2</c> and <c>read/2</c> in one
@@ -1283,7 +1283,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="pwrite" arity="2"/>
+ <name name="pwrite" arity="2" since=""/>
<fsummary>Write to a file at certain positions.</fsummary>
<desc>
<p>Performs a sequence of <c>pwrite/3</c> in one operation,
@@ -1298,7 +1298,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="pwrite" arity="3"/>
+ <name name="pwrite" arity="3" since=""/>
<fsummary>Write to a file at a certain position.</fsummary>
<desc>
<p>Combines <c>position/2</c> and <c>write/2</c> in one
@@ -1317,7 +1317,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="read" arity="2"/>
+ <name name="read" arity="2" since=""/>
<fsummary>Read from a file.</fsummary>
<desc>
<p>Reads <c><anno>Number</anno></c> bytes/characters from the file
@@ -1371,7 +1371,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="read_file" arity="1"/>
+ <name name="read_file" arity="1" since=""/>
<fsummary>Read a file.</fsummary>
<desc>
<p>Returns <c>{ok, <anno>Binary</anno>}</c>, where
@@ -1407,8 +1407,8 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="read_file_info" arity="1"/>
- <name name="read_file_info" arity="2"/>
+ <name name="read_file_info" arity="1" since=""/>
+ <name name="read_file_info" arity="2" since="OTP R15B"/>
<fsummary>Retrieve information about a file.</fsummary>
<desc>
<p>Retrieves information about a file. Returns
@@ -1562,7 +1562,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="read_line" arity="1"/>
+ <name name="read_line" arity="1" since=""/>
<fsummary>Read a line from a file.</fsummary>
<desc>
<p>Reads a line of bytes/characters from the file referenced by
@@ -1619,7 +1619,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="read_link" arity="1"/>
+ <name name="read_link" arity="1" since=""/>
<fsummary>See what a link is pointing to.</fsummary>
<desc>
<p><marker id="read_link_all"/>Returns
@@ -1649,7 +1649,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="read_link_all" arity="1"/>
+ <name name="read_link_all" arity="1" since="OTP R16B"/>
<fsummary>See what a link is pointing to.</fsummary>
<desc>
<p>Returns <c>{ok, <anno>Filename</anno>}</c> if
@@ -1677,8 +1677,8 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="read_link_info" arity="1"/>
- <name name="read_link_info" arity="2"/>
+ <name name="read_link_info" arity="1" since=""/>
+ <name name="read_link_info" arity="2" since="OTP R15B"/>
<fsummary>Retrieve information about a link or file.</fsummary>
<desc>
<p>Works like
@@ -1699,7 +1699,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="rename" arity="2"/>
+ <name name="rename" arity="2" since=""/>
<fsummary>Rename a file.</fsummary>
<desc>
<p>Tries to rename the file <c><anno>Source</anno></c> to
@@ -1762,7 +1762,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="script" arity="1"/>
+ <name name="script" arity="1" since=""/>
<fsummary>Evaluate and return the value of Erlang expressions in a file.</fsummary>
<desc>
<p>Reads and evaluates Erlang expressions, separated by '.' (or
@@ -1797,7 +1797,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="script" arity="2"/>
+ <name name="script" arity="2" since=""/>
<fsummary>Evaluate and return the value of Erlang expressions in a file.</fsummary>
<desc>
<p>The same as <c>script/1</c> but the variable bindings
@@ -1807,7 +1807,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="sendfile" arity="2"/>
+ <name name="sendfile" arity="2" since="OTP R15B"/>
<fsummary>Send a file to a socket.</fsummary>
<desc>
<p>Sends the file <c>Filename</c> to <c>Socket</c>.
@@ -1816,7 +1816,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="sendfile" arity="5"/>
+ <name name="sendfile" arity="5" since="OTP R15B"/>
<fsummary>Send a file to a socket.</fsummary>
<type name="sendfile_option"/>
<desc>
@@ -1843,7 +1843,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="set_cwd" arity="1"/>
+ <name name="set_cwd" arity="1" since=""/>
<fsummary>Set the current working directory.</fsummary>
<desc>
<p>Sets the current working directory of the file server to
@@ -1890,7 +1890,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="sync" arity="1"/>
+ <name name="sync" arity="1" since=""/>
<fsummary>Synchronize the in-memory state of a file with that on the physical medium.</fsummary>
<desc>
<p>Ensures that any buffers kept by the operating system
@@ -1906,7 +1906,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="truncate" arity="1"/>
+ <name name="truncate" arity="1" since=""/>
<fsummary>Truncate a file.</fsummary>
<desc>
<p>Truncates the file referenced by <c><anno>IoDevice</anno></c> at
@@ -1915,7 +1915,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="write" arity="2"/>
+ <name name="write" arity="2" since=""/>
<fsummary>Write to a file.</fsummary>
<desc>
<p>Writes <c><anno>Bytes</anno></c> to the file referenced by
@@ -1941,7 +1941,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="write_file" arity="2"/>
+ <name name="write_file" arity="2" since=""/>
<fsummary>Write a file.</fsummary>
<desc>
<p>Writes the contents of the <c>iodata</c> term <c><anno>Bytes</anno></c>
@@ -1978,7 +1978,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="write_file" arity="3"/>
+ <name name="write_file" arity="3" since=""/>
<fsummary>Write a file.</fsummary>
<desc>
<p>Same as <c>write_file/2</c>, but takes a third argument
@@ -1989,8 +1989,8 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="write_file_info" arity="2"/>
- <name name="write_file_info" arity="3"/>
+ <name name="write_file_info" arity="2" since=""/>
+ <name name="write_file_info" arity="3" since="OTP R15B"/>
<fsummary>Change file information.</fsummary>
<desc>
<p>Changes file information. Returns <c>ok</c> if successful,
diff --git a/lib/kernel/doc/src/gen_sctp.xml b/lib/kernel/doc/src/gen_sctp.xml
index 1e08b25f66..f70d6c24db 100644
--- a/lib/kernel/doc/src/gen_sctp.xml
+++ b/lib/kernel/doc/src/gen_sctp.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>gen_sctp.xml</file>
</header>
- <module>gen_sctp</module>
+ <module since="">gen_sctp</module>
<modulesummary>Functions for communicating with sockets using the SCTP
protocol.</modulesummary>
<description>
@@ -100,7 +100,7 @@
<funcs>
<func>
- <name name="abort" arity="2"/>
+ <name name="abort" arity="2" since=""/>
<fsummary>Abnormally terminate the association specified by
<c>Assoc</c>, without flushing of unsent data.</fsummary>
<desc>
@@ -113,7 +113,7 @@
</func>
<func>
- <name name="close" arity="1"/>
+ <name name="close" arity="1" since=""/>
<fsummary>Close the socket and all associations on it.</fsummary>
<desc>
<p>Closes the socket and all associations on it. The unsent
@@ -128,7 +128,7 @@
</func>
<func>
- <name name="connect" arity="4"/>
+ <name name="connect" arity="4" since=""/>
<fsummary>Same as <c>connect(Socket, Addr, Port, Opts, infinity)</c>.</fsummary>
<desc>
<p>Same as <c>connect(<anno>Socket</anno>, <anno>Addr</anno>,
@@ -137,7 +137,7 @@
</func>
<func>
- <name name="connect" arity="5"/>
+ <name name="connect" arity="5" since=""/>
<fsummary>Establish a new association for socket <c>Socket</c>, with a
peer (SCTP server socket).</fsummary>
<desc>
@@ -213,7 +213,7 @@ connect(Socket, Ip, Port>,
</func>
<func>
- <name name="connect_init" arity="4"/>
+ <name name="connect_init" arity="4" since="OTP R13B04"/>
<fsummary>Same as <c>connect_init(Socket, Addr, Port, Opts, infinity)</c>..</fsummary>
<desc>
<p>Same as <c>connect_init(<anno>Socket</anno>, <anno>Addr</anno>,
@@ -222,7 +222,7 @@ connect(Socket, Ip, Port>,
</func>
<func>
- <name name="connect_init" arity="5"/>
+ <name name="connect_init" arity="5" since="OTP R13B04"/>
<fsummary>Initiate a new association for socket <c>Socket</c>, with a
peer (SCTP server socket).</fsummary>
<desc>
@@ -248,7 +248,7 @@ connect(Socket, Ip, Port>,
</func>
<func>
- <name name="controlling_process" arity="2"/>
+ <name name="controlling_process" arity="2" since=""/>
<fsummary>Assign a new controlling process pid to the socket.</fsummary>
<desc>
<p>Assigns a new controlling process <c><anno>Pid</anno></c> to
@@ -259,7 +259,7 @@ connect(Socket, Ip, Port>,
</func>
<func>
- <name name="eof" arity="2"/>
+ <name name="eof" arity="2" since=""/>
<fsummary>Gracefully terminate the association specified by <c>Assoc</c>,
with flushing of all unsent data.</fsummary>
<desc>
@@ -272,7 +272,7 @@ connect(Socket, Ip, Port>,
</func>
<func>
- <name name="error_string" arity="1"/>
+ <name name="error_string" arity="1" since=""/>
<fsummary>Translate an SCTP error number into a string.</fsummary>
<desc>
<p>Translates an SCTP error number from, for example,
@@ -283,8 +283,8 @@ connect(Socket, Ip, Port>,
</func>
<func>
- <name name="listen" arity="2" clause_i="1"/>
- <name name="listen" arity="2" clause_i="2"/>
+ <name name="listen" arity="2" clause_i="1" since=""/>
+ <name name="listen" arity="2" clause_i="2" since="OTP R15B"/>
<fsummary>Set up a socket to listen.</fsummary>
<desc>
<p>Sets up a socket to listen on the IP address and port number
@@ -300,10 +300,10 @@ connect(Socket, Ip, Port>,
</func>
<func>
- <name name="open" arity="0"/>
- <name name="open" arity="1" clause_i="1"/>
- <name name="open" arity="1" clause_i="2"/>
- <name name="open" arity="2"/>
+ <name name="open" arity="0" since=""/>
+ <name name="open" arity="1" clause_i="1" since=""/>
+ <name name="open" arity="1" clause_i="2" since=""/>
+ <name name="open" arity="2" since=""/>
<fsummary>Create an SCTP socket and binds it to local addresses.</fsummary>
<desc>
<p>Creates an SCTP socket and binds it to the local addresses
@@ -366,7 +366,7 @@ connect(Socket, Ip, Port>,
</func>
<func>
- <name name="peeloff" arity="2"/>
+ <name name="peeloff" arity="2" since="OTP R15B"/>
<fsummary>Peel off a type <c>stream</c> socket from a type
<c>seqpacket</c> one.</fsummary>
<desc>
@@ -387,8 +387,8 @@ connect(Socket, Ip, Port>,
</func>
<func>
- <name name="recv" arity="1"/>
- <name name="recv" arity="2"/>
+ <name name="recv" arity="1" since=""/>
+ <name name="recv" arity="2" since=""/>
<fsummary>Receive a message from a socket.</fsummary>
<desc>
<p>Receives the <c><anno>Data</anno></c> message from any association
@@ -532,7 +532,7 @@ connect(Socket, Ip, Port>,
</func>
<func>
- <name name="send" arity="3"/>
+ <name name="send" arity="3" since=""/>
<fsummary>Send a message using an <c>#sctp_sndrcvinfo{}</c>record.</fsummary>
<desc>
<p>Sends the <c><anno>Data</anno></c> message with all sending
@@ -547,7 +547,7 @@ connect(Socket, Ip, Port>,
</func>
<func>
- <name name="send" arity="4"/>
+ <name name="send" arity="4" since=""/>
<fsummary>Send a message over an existing association and specified
stream.</fsummary>
<desc>
diff --git a/lib/kernel/doc/src/gen_tcp.xml b/lib/kernel/doc/src/gen_tcp.xml
index 24d63693fd..fc16473393 100644
--- a/lib/kernel/doc/src/gen_tcp.xml
+++ b/lib/kernel/doc/src/gen_tcp.xml
@@ -27,7 +27,7 @@
<date>1997-10-24</date>
<rev>A</rev>
</header>
- <module>gen_tcp</module>
+ <module since="">gen_tcp</module>
<modulesummary>Interface to TCP/IP sockets.</modulesummary>
<description>
<p>This module provides functions for communicating
@@ -116,8 +116,8 @@ do_recv(Sock, Bs) ->
<funcs>
<func>
- <name name="accept" arity="1"/>
- <name name="accept" arity="2"/>
+ <name name="accept" arity="1" since=""/>
+ <name name="accept" arity="2" since=""/>
<fsummary>Accept an incoming connection request on a listening socket.</fsummary>
<type_desc variable="ListenSocket">Returned by
<seealso marker="#listen/2"><c>listen/2</c></seealso>.
@@ -163,7 +163,7 @@ do_recv(Sock, Bs) ->
</func>
<func>
- <name name="close" arity="1"/>
+ <name name="close" arity="1" since=""/>
<fsummary>Close a TCP socket.</fsummary>
<desc>
<p>Closes a TCP socket.</p>
@@ -188,8 +188,8 @@ do_recv(Sock, Bs) ->
</func>
<func>
- <name name="connect" arity="3"/>
- <name name="connect" arity="4"/>
+ <name name="connect" arity="3" since=""/>
+ <name name="connect" arity="4" since=""/>
<fsummary>Connect to a TCP port.</fsummary>
<desc>
<p>Connects to a server on TCP port <c><anno>Port</anno></c> on the host
@@ -268,7 +268,7 @@ do_recv(Sock, Bs) ->
</func>
<func>
- <name name="controlling_process" arity="2"/>
+ <name name="controlling_process" arity="2" since=""/>
<fsummary>Change controlling process of a socket.</fsummary>
<desc>
<p>Assigns a new controlling process <c><anno>Pid</anno></c> to
@@ -292,7 +292,7 @@ do_recv(Sock, Bs) ->
</func>
<func>
- <name name="listen" arity="2"/>
+ <name name="listen" arity="2" since=""/>
<fsummary>Set up a socket to listen on a port.</fsummary>
<desc>
<p>Sets up a socket to listen on port <c><anno>Port</anno></c> on
@@ -349,8 +349,8 @@ do_recv(Sock, Bs) ->
</func>
<func>
- <name name="recv" arity="2"/>
- <name name="recv" arity="3"/>
+ <name name="recv" arity="2" since=""/>
+ <name name="recv" arity="3" since=""/>
<fsummary>Receive a packet from a passive socket.</fsummary>
<type_desc variable="HttpPacket">See the description of
<c>HttpPacket</c> in
@@ -375,7 +375,7 @@ do_recv(Sock, Bs) ->
</func>
<func>
- <name name="send" arity="2"/>
+ <name name="send" arity="2" since=""/>
<fsummary>Send a packet.</fsummary>
<desc>
<p>Sends a packet on a socket.</p>
@@ -386,7 +386,7 @@ do_recv(Sock, Bs) ->
</func>
<func>
- <name name="shutdown" arity="2"/>
+ <name name="shutdown" arity="2" since=""/>
<fsummary>Asynchronously close a socket.</fsummary>
<desc>
<p>Closes a socket in one or two directions.</p>
diff --git a/lib/kernel/doc/src/gen_udp.xml b/lib/kernel/doc/src/gen_udp.xml
index 840ca3c188..d20fc1fdfd 100644
--- a/lib/kernel/doc/src/gen_udp.xml
+++ b/lib/kernel/doc/src/gen_udp.xml
@@ -28,7 +28,7 @@
<date>1997-12-03</date>
<rev>A</rev>
</header>
- <module>gen_udp</module>
+ <module since="">gen_udp</module>
<modulesummary>Interface to UDP sockets.</modulesummary>
<description>
<p>This module provides functions for communicating
@@ -53,7 +53,7 @@
<funcs>
<func>
- <name name="close" arity="1"/>
+ <name name="close" arity="1" since=""/>
<fsummary>Close a UDP socket.</fsummary>
<desc>
<p>Closes a UDP socket.</p>
@@ -61,7 +61,7 @@
</func>
<func>
- <name name="controlling_process" arity="2"/>
+ <name name="controlling_process" arity="2" since=""/>
<fsummary>Change controlling process of a socket.</fsummary>
<desc>
<p>Assigns a new controlling process <c><anno>Pid</anno></c> to
@@ -77,8 +77,8 @@
</func>
<func>
- <name name="open" arity="1"/>
- <name name="open" arity="2"/>
+ <name name="open" arity="1" since=""/>
+ <name name="open" arity="2" since=""/>
<fsummary>Associate a UDP port number with the process calling it.</fsummary>
<desc>
<p>Associates a UDP port number (<c><anno>Port</anno></c>) with the
@@ -189,8 +189,8 @@
</func>
<func>
- <name name="recv" arity="2"/>
- <name name="recv" arity="3"/>
+ <name name="recv" arity="2" since=""/>
+ <name name="recv" arity="3" since=""/>
<fsummary>Receive a packet from a passive socket.</fsummary>
<desc>
<p>
@@ -213,7 +213,7 @@
</func>
<func>
- <name name="send" arity="4"/>
+ <name name="send" arity="4" since=""/>
<fsummary>Send a packet.</fsummary>
<desc>
<p>
diff --git a/lib/kernel/doc/src/global.xml b/lib/kernel/doc/src/global.xml
index 4442741f54..dfe71de5ce 100644
--- a/lib/kernel/doc/src/global.xml
+++ b/lib/kernel/doc/src/global.xml
@@ -28,7 +28,7 @@
<date>1997-11-17</date>
<rev></rev>
</header>
- <module>global</module>
+ <module since="">global</module>
<modulesummary>A global name registration facility.</modulesummary>
<description>
<p>This module consists of the following services:</p>
@@ -100,8 +100,8 @@
<funcs>
<func>
- <name name="del_lock" arity="1"/>
- <name name="del_lock" arity="2"/>
+ <name name="del_lock" arity="1" since=""/>
+ <name name="del_lock" arity="2" since=""/>
<fsummary>Delete a lock.</fsummary>
<desc>
<p>Deletes the lock <c><anno>Id</anno></c> synchronously.</p>
@@ -109,7 +109,7 @@
</func>
<func>
- <name name="notify_all_name" arity="3"/>
+ <name name="notify_all_name" arity="3" since=""/>
<fsummary>Name resolving function that notifies both pids.</fsummary>
<desc>
<p>Can be used as a name resolving function for
@@ -123,7 +123,7 @@
</func>
<func>
- <name name="random_exit_name" arity="3"/>
+ <name name="random_exit_name" arity="3" since=""/>
<fsummary>Name resolving function that kills one pid.</fsummary>
<desc>
<p>Can be used as a name resolving function for
@@ -136,7 +136,7 @@
</func>
<func>
- <name name="random_notify_name" arity="3"/>
+ <name name="random_notify_name" arity="3" since=""/>
<fsummary>Name resolving function that notifies one pid.</fsummary>
<desc>
<p>Can be used as a name resolving function for
@@ -150,8 +150,8 @@
</func>
<func>
- <name name="re_register_name" arity="2"/>
- <name name="re_register_name" arity="3"/>
+ <name name="re_register_name" arity="2" since=""/>
+ <name name="re_register_name" arity="3" since=""/>
<fsummary>Atomically re-register a name.</fsummary>
<type name="method"/>
<type_desc name="method">{<c>Module</c>, <c>Function</c>}
@@ -167,8 +167,8 @@
</func>
<func>
- <name name="register_name" arity="2"/>
- <name name="register_name" arity="3"/>
+ <name name="register_name" arity="2" since=""/>
+ <name name="register_name" arity="3" since=""/>
<fsummary>Globally register a name for a pid.</fsummary>
<type name="method"/>
<type_desc name="method">{<c>Module</c>, <c>Function</c>} is also
@@ -221,7 +221,7 @@
</func>
<func>
- <name name="registered_names" arity="0"/>
+ <name name="registered_names" arity="0" since=""/>
<fsummary>All globally registered names.</fsummary>
<desc>
<p>Returns a list of all globally registered names.</p>
@@ -229,7 +229,7 @@
</func>
<func>
- <name name="send" arity="2"/>
+ <name name="send" arity="2" since=""/>
<fsummary>Send a message to a globally registered pid.</fsummary>
<desc>
<p>Sends message <c><anno>Msg</anno></c> to the pid globally registered
@@ -241,9 +241,9 @@
</func>
<func>
- <name name="set_lock" arity="1"/>
- <name name="set_lock" arity="2"/>
- <name name="set_lock" arity="3"/>
+ <name name="set_lock" arity="1" since=""/>
+ <name name="set_lock" arity="2" since=""/>
+ <name name="set_lock" arity="3" since=""/>
<fsummary>Set a lock on the specified nodes.</fsummary>
<type name="id"/>
<type name="retries"/>
@@ -287,7 +287,7 @@
</func>
<func>
- <name name="sync" arity="0"/>
+ <name name="sync" arity="0" since=""/>
<fsummary>Synchronize the global name server.</fsummary>
<desc>
<p>Synchronizes the global name server with all nodes known to
@@ -302,9 +302,9 @@
</func>
<func>
- <name name="trans" arity="2"/>
- <name name="trans" arity="3"/>
- <name name="trans" arity="4"/>
+ <name name="trans" arity="2" since=""/>
+ <name name="trans" arity="3" since=""/>
+ <name name="trans" arity="4" since=""/>
<fsummary>Micro transaction facility.</fsummary>
<type name="retries"/>
<type name="trans_fun"/>
@@ -322,7 +322,7 @@
</func>
<func>
- <name name="unregister_name" arity="1"/>
+ <name name="unregister_name" arity="1" since=""/>
<fsummary>Remove a globally registered name for a pid.</fsummary>
<desc>
<p>Removes the globally registered name <c><anno>Name</anno></c> from
@@ -331,7 +331,7 @@
</func>
<func>
- <name name="whereis_name" arity="1"/>
+ <name name="whereis_name" arity="1" since=""/>
<fsummary>Get the pid with a specified globally registered name.</fsummary>
<desc>
<p>Returns the pid with the globally registered name
diff --git a/lib/kernel/doc/src/global_group.xml b/lib/kernel/doc/src/global_group.xml
index 8f947b9adf..74d15cd476 100644
--- a/lib/kernel/doc/src/global_group.xml
+++ b/lib/kernel/doc/src/global_group.xml
@@ -28,7 +28,7 @@
<date>1998-12-18</date>
<rev>B</rev>
</header>
- <module>global_group</module>
+ <module since="">global_group</module>
<modulesummary>Grouping nodes to global name registration groups.</modulesummary>
<description>
<p>This module makes it possible to partition the nodes of a
@@ -105,7 +105,7 @@
<funcs>
<func>
- <name name="global_groups" arity="0"/>
+ <name name="global_groups" arity="0" since=""/>
<fsummary>Return the global group names.</fsummary>
<desc>
<p>Returns a tuple containing the name of the global group that
@@ -116,7 +116,7 @@
</func>
<func>
- <name name="info" arity="0"/>
+ <name name="info" arity="0" since=""/>
<fsummary>Information about global groups.</fsummary>
<type name="info_item"/>
<type name="sync_state"/>
@@ -173,7 +173,7 @@
</func>
<func>
- <name name="monitor_nodes" arity="1"/>
+ <name name="monitor_nodes" arity="1" since=""/>
<fsummary>Subscribe to node status changes.</fsummary>
<desc>
<p>Depending on <c><anno>Flag</anno></c>, the calling process
@@ -187,7 +187,7 @@
</func>
<func>
- <name name="own_nodes" arity="0"/>
+ <name name="own_nodes" arity="0" since=""/>
<fsummary>Return the group nodes.</fsummary>
<desc>
<p>Returns the names of all group nodes, regardless of their
@@ -196,7 +196,7 @@
</func>
<func>
- <name name="registered_names" arity="1"/>
+ <name name="registered_names" arity="1" since=""/>
<fsummary>Return globally registered names.</fsummary>
<desc>
<p>Returns a list of all names that are globally registered
@@ -205,8 +205,8 @@
</func>
<func>
- <name name="send" arity="2"/>
- <name name="send" arity="3"/>
+ <name name="send" arity="2" since=""/>
+ <name name="send" arity="3" since=""/>
<fsummary>Send a message to a globally registered pid.</fsummary>
<desc>
<p>Searches for <c><anno>Name</anno></c>, globally registered on
@@ -224,7 +224,7 @@
</func>
<func>
- <name name="sync" arity="0"/>
+ <name name="sync" arity="0" since=""/>
<fsummary>Synchronize the group nodes.</fsummary>
<desc>
<p>Synchronizes the group nodes, that is, the global name
@@ -242,8 +242,8 @@
</func>
<func>
- <name name="whereis_name" arity="1"/>
- <name name="whereis_name" arity="2"/>
+ <name name="whereis_name" arity="1" since=""/>
+ <name name="whereis_name" arity="2" since=""/>
<fsummary>Get the pid with a specified globally registered name.</fsummary>
<desc>
<p>Searches for <c><anno>Name</anno></c>, globally registered on
diff --git a/lib/kernel/doc/src/heart.xml b/lib/kernel/doc/src/heart.xml
index ad1a2ffeb9..4243b1ffe8 100644
--- a/lib/kernel/doc/src/heart.xml
+++ b/lib/kernel/doc/src/heart.xml
@@ -28,7 +28,7 @@
<date>1998-01-28</date>
<rev>A</rev>
</header>
- <module>heart</module>
+ <module since="">heart</module>
<modulesummary>Heartbeat monitoring of an Erlang runtime system.</modulesummary>
<description>
<p>This modules contains the interface to the <c>heart</c> process.
@@ -119,7 +119,7 @@
<funcs>
<func>
- <name name="set_cmd" arity="1"/>
+ <name name="set_cmd" arity="1" since=""/>
<fsummary>Set a temporary reboot command.</fsummary>
<desc>
<p>Sets a temporary reboot command. This command is used if
@@ -136,7 +136,7 @@
</func>
<func>
- <name name="clear_cmd" arity="0"/>
+ <name name="clear_cmd" arity="0" since=""/>
<fsummary>Clear the temporary boot command.</fsummary>
<desc>
<p>Clears the temporary boot command. If the system terminates,
@@ -145,7 +145,7 @@
</func>
<func>
- <name name="get_cmd" arity="0"/>
+ <name name="get_cmd" arity="0" since=""/>
<fsummary>Get the temporary reboot command.</fsummary>
<desc>
<p>Gets the temporary reboot command. If the command is cleared,
@@ -154,7 +154,7 @@
</func>
<func>
- <name name="set_callback" arity="2"/>
+ <name name="set_callback" arity="2" since="OTP 18.3"/>
<fsummary>Set a validation callback</fsummary>
<desc>
<p> This validation callback will be executed before any
@@ -166,14 +166,14 @@
</desc>
</func>
<func>
- <name name="clear_callback" arity="0"/>
+ <name name="clear_callback" arity="0" since="OTP 18.3"/>
<fsummary>Clear the validation callback</fsummary>
<desc>
<p>Removes the validation callback call before heartbeats.</p>
</desc>
</func>
<func>
- <name name="get_callback" arity="0"/>
+ <name name="get_callback" arity="0" since="OTP 18.3"/>
<fsummary>Get the validation callback</fsummary>
<desc>
<p>Get the validation callback. If the callback is cleared, <c>none</c> will be returned.</p>
@@ -181,7 +181,7 @@
</func>
<func>
- <name name="set_options" arity="1"/>
+ <name name="set_options" arity="1" since="OTP 18.3"/>
<fsummary>Set a list of options</fsummary>
<desc>
<p> Valid options <c>set_options</c> are: </p>
@@ -199,7 +199,7 @@
</desc>
</func>
<func>
- <name name="get_options" arity="0"/>
+ <name name="get_options" arity="0" since="OTP 18.3"/>
<fsummary>Get the temporary reboot command</fsummary>
<desc>
<p>Returns <c>{ok, Options}</c> where <c>Options</c> is a list of current options enabled for heart.
diff --git a/lib/kernel/doc/src/inet.xml b/lib/kernel/doc/src/inet.xml
index 87b08e4e36..709ba8e8fd 100644
--- a/lib/kernel/doc/src/inet.xml
+++ b/lib/kernel/doc/src/inet.xml
@@ -28,7 +28,7 @@
<date>1998-02-04</date>
<rev>A</rev>
</header>
- <module>inet</module>
+ <module since="">inet</module>
<modulesummary>Access to TCP/IP protocols.</modulesummary>
<description>
<p>This module provides access to TCP/IP protocols.</p>
@@ -298,7 +298,7 @@ fe80::204:acff:fe17:bf38
<funcs>
<func>
- <name name="close" arity="1"/>
+ <name name="close" arity="1" since=""/>
<fsummary>Close a socket of any type.</fsummary>
<desc>
<p>Closes a socket of any type.</p>
@@ -306,7 +306,7 @@ fe80::204:acff:fe17:bf38
</func>
<func>
- <name name="format_error" arity="1"/>
+ <name name="format_error" arity="1" since=""/>
<fsummary>Return a descriptive string for an error reason.</fsummary>
<desc>
<p>Returns a diagnostic error string. For possible POSIX values and
@@ -316,7 +316,7 @@ fe80::204:acff:fe17:bf38
</func>
<func>
- <name name="get_rc" arity="0"/>
+ <name name="get_rc" arity="0" since=""/>
<fsummary>Return a list of IP configuration parameters.</fsummary>
<desc>
<p>
@@ -335,7 +335,7 @@ fe80::204:acff:fe17:bf38
</func>
<func>
- <name name="getaddr" arity="2"/>
+ <name name="getaddr" arity="2" since=""/>
<fsummary>Return the IP address for a host.</fsummary>
<desc>
<p>Returns the IP address for <c><anno>Host</anno></c> as a tuple of
@@ -345,7 +345,7 @@ fe80::204:acff:fe17:bf38
</func>
<func>
- <name name="getaddrs" arity="2"/>
+ <name name="getaddrs" arity="2" since=""/>
<fsummary>Return the IP addresses for a host.</fsummary>
<desc>
<p>Returns a list of all IP addresses for <c><anno>Host</anno></c>.
@@ -355,7 +355,7 @@ fe80::204:acff:fe17:bf38
</func>
<func>
- <name name="gethostbyaddr" arity="1"/>
+ <name name="gethostbyaddr" arity="1" since=""/>
<fsummary>Return a hostent record for the host with the specified
address.</fsummary>
<desc>
@@ -364,7 +364,7 @@ fe80::204:acff:fe17:bf38
</func>
<func>
- <name name="gethostbyname" arity="1"/>
+ <name name="gethostbyname" arity="1" since=""/>
<fsummary>Return a hostent record for the host with the specified name.
</fsummary>
<desc>
@@ -376,7 +376,7 @@ fe80::204:acff:fe17:bf38
</func>
<func>
- <name name="gethostbyname" arity="2"/>
+ <name name="gethostbyname" arity="2" since=""/>
<fsummary>Return a hostent record for the host with the specified name.
</fsummary>
<desc>
@@ -386,7 +386,7 @@ fe80::204:acff:fe17:bf38
</func>
<func>
- <name name="gethostname" arity="0"/>
+ <name name="gethostname" arity="0" since=""/>
<fsummary>Return the local hostname.</fsummary>
<desc>
<p>Returns the local hostname. Never fails.</p>
@@ -394,7 +394,7 @@ fe80::204:acff:fe17:bf38
</func>
<func>
- <name name="getifaddrs" arity="0"/>
+ <name name="getifaddrs" arity="0" since="OTP R14B01"/>
<fsummary>Return a list of interfaces and their addresses.</fsummary>
<desc>
<p>
@@ -416,7 +416,7 @@ fe80::204:acff:fe17:bf38
</func>
<func>
- <name>getifaddrs(Opts) ->
+ <name since="OTP 21.2">getifaddrs(Opts) ->
{ok, [{Ifname, Ifopts}]} | {error, Posix}
</name>
<fsummary>Return a list of interfaces and their addresses.</fsummary>
@@ -459,7 +459,7 @@ fe80::204:acff:fe17:bf38
</func>
<func>
- <name name="getopts" arity="2"/>
+ <name name="getopts" arity="2" since=""/>
<fsummary>Get one or more options for a socket.</fsummary>
<desc>
<p>Gets one or more options for a socket. For a list of available
@@ -529,8 +529,8 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="getstat" arity="1"/>
- <name name="getstat" arity="2"/>
+ <name name="getstat" arity="1" since=""/>
+ <name name="getstat" arity="2" since=""/>
<fsummary>Get one or more statistic options for a socket.</fsummary>
<type name="stat_option"/>
<desc>
@@ -586,9 +586,9 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="i" arity="0" />
- <name name="i" arity="1" />
- <name name="i" arity="2" />
+ <name name="i" arity="0" since="OTP 21.0"/>
+ <name name="i" arity="1" since="OTP 21.0"/>
+ <name name="i" arity="2" since="OTP 21.0"/>
<fsummary>Displays information and statistics about sockets on the terminal</fsummary>
<desc>
<p>
@@ -641,7 +641,7 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="ntoa" arity="1" />
+ <name name="ntoa" arity="1" since="OTP R16B02"/>
<fsummary>Convert IPv6/IPV4 address to ASCII.</fsummary>
<desc>
<p>Parses an
@@ -651,7 +651,7 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="parse_address" arity="1" />
+ <name name="parse_address" arity="1" since="OTP R16B"/>
<fsummary>Parse an IPv4 or IPv6 address.</fsummary>
<desc>
<p>Parses an IPv4 or IPv6 address string and returns an
@@ -662,7 +662,7 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="parse_ipv4_address" arity="1" />
+ <name name="parse_ipv4_address" arity="1" since="OTP R16B"/>
<fsummary>Parse an IPv4 address.</fsummary>
<desc>
<p>Parses an IPv4 address string and returns an
@@ -672,7 +672,7 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="parse_ipv4strict_address" arity="1" />
+ <name name="parse_ipv4strict_address" arity="1" since="OTP R16B"/>
<fsummary>Parse an IPv4 address strict.</fsummary>
<desc>
<p>Parses an IPv4 address string containing four fields, that is,
@@ -683,7 +683,7 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="parse_ipv6_address" arity="1" />
+ <name name="parse_ipv6_address" arity="1" since="OTP R16B"/>
<fsummary>Parse an IPv6 address.</fsummary>
<desc>
<p>Parses an IPv6 address string and returns an
@@ -694,7 +694,7 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="parse_ipv6strict_address" arity="1" />
+ <name name="parse_ipv6strict_address" arity="1" since="OTP R16B"/>
<fsummary>Parse an IPv6 address strict.</fsummary>
<desc>
<p>Parses an IPv6 address string and returns an
@@ -704,7 +704,7 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="ipv4_mapped_ipv6_address" arity="1" />
+ <name name="ipv4_mapped_ipv6_address" arity="1" since="OTP 21.0"/>
<fsummary>Convert to and from IPv4-mapped IPv6 address.</fsummary>
<desc>
<p>
@@ -717,7 +717,7 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="parse_strict_address" arity="1" />
+ <name name="parse_strict_address" arity="1" since="OTP R16B"/>
<fsummary>Parse an IPv4 or IPv6 address strict.</fsummary>
<desc>
<p>Parses an IPv4 or IPv6 address string and returns an
@@ -728,7 +728,7 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="peername" arity="1"/>
+ <name name="peername" arity="1" since=""/>
<fsummary>Return the address and port for the other end of a connection.
</fsummary>
<desc>
@@ -741,7 +741,7 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="peernames" arity="1"/>
+ <name name="peernames" arity="1" since="OTP R16B03"/>
<fsummary>Return all address/port numbers for the other end of a
connection.</fsummary>
<desc>
@@ -755,7 +755,7 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="peernames" arity="2"/>
+ <name name="peernames" arity="2" since="OTP R16B03"/>
<fsummary>Return all address/port numbers for the other end of a
connection.</fsummary>
<desc>
@@ -774,7 +774,7 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="port" arity="1"/>
+ <name name="port" arity="1" since=""/>
<fsummary>Return the local port number for a socket.</fsummary>
<desc>
<p>Returns the local port number for a socket.</p>
@@ -782,7 +782,7 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="setopts" arity="2"/>
+ <name name="setopts" arity="2" since=""/>
<fsummary>Set one or more options for a socket.</fsummary>
<desc>
<p>Sets one or more options for a socket.</p>
@@ -1007,13 +1007,34 @@ get_tcpi_sacked(Sock) ->
<marker id="option-linger"></marker>
</item>
<tag><c>{linger, {true|false, Seconds}}</c></tag>
- <item>
+ <item>
<p>Determines the time-out, in seconds, for flushing unsent data
- in the <c>close/1</c> socket call. If the first component of
- the value tuple is <c>false</c>, the second is ignored. This
- means that <c>close/1</c> returns immediately, not waiting
- for data to be flushed. Otherwise, the second component is
- the flushing time-out, in seconds.</p>
+ in the <c>close/1</c> socket call. </p>
+ <p>The first component is if linger is enabled, the second component
+ is the flushing time-out, in seconds. There are 3 alternatives:</p>
+ <taglist>
+ <tag><c>{false, _}</c></tag>
+ <item>
+ <p>close/1 or shutdown/2 returns immediately,
+ not waiting for data to be flushed, with closing
+ happening in the background.</p>
+ </item>
+ <tag><c>{true, 0}</c></tag>
+ <item>
+ <p>Aborts the connection when it is closed.
+ Discards any data still remaining in the send buffers
+ and sends RST to the peer.</p>
+ <p>This avoids TCP's TIME_WAIT state, but leaves open
+ the possibility that another "incarnation" of this connection
+ being created.</p>
+ </item>
+ <tag><c>{true, Time} when Time > 0</c></tag>
+ <item>
+ <p>close/1 or shutdown/2 will not return until
+ all queued messages for the socket have been successfully
+ sent or the linger timeout (Time) has been reached.</p>
+ </item>
+ </taglist>
</item>
<tag><c>{low_msgq_watermark, Size}</c></tag>
<item>
@@ -1486,7 +1507,7 @@ inet:setopts(Sock,[{raw,6,8,<<30:32/native>>}]),]]></code>
</func>
<func>
- <name name="sockname" arity="1"/>
+ <name name="sockname" arity="1" since=""/>
<fsummary>Return the local address and port number for a socket.
</fsummary>
<desc>
@@ -1499,7 +1520,7 @@ inet:setopts(Sock,[{raw,6,8,<<30:32/native>>}]),]]></code>
</func>
<func>
- <name name="socknames" arity="1"/>
+ <name name="socknames" arity="1" since="OTP R16B03"/>
<fsummary>Return all local address/port numbers for a socket.</fsummary>
<desc>
<p>Equivalent to
@@ -1509,7 +1530,7 @@ inet:setopts(Sock,[{raw,6,8,<<30:32/native>>}]),]]></code>
</func>
<func>
- <name name="socknames" arity="2"/>
+ <name name="socknames" arity="2" since="OTP R16B03"/>
<fsummary>Return all local address/port numbers for a socket.</fsummary>
<desc>
<p>Returns a list of all local address/port number pairs for a socket
diff --git a/lib/kernel/doc/src/inet_res.xml b/lib/kernel/doc/src/inet_res.xml
index 351d86a93a..1904e371f7 100644
--- a/lib/kernel/doc/src/inet_res.xml
+++ b/lib/kernel/doc/src/inet_res.xml
@@ -28,7 +28,7 @@
<date>2009-09-11</date>
<rev>A</rev>
</header>
- <module>inet_res</module>
+ <module since="">inet_res</module>
<modulesummary>A rudimentary DNS client.</modulesummary>
<description>
<p>This module performs DNS name resolving to recursive name servers.</p>
@@ -185,8 +185,8 @@ inet_dns:record_type(_) -> undefined.</pre>
<funcs>
<func>
- <name name="getbyname" arity="2"/>
- <name name="getbyname" arity="3"/>
+ <name name="getbyname" arity="2" since=""/>
+ <name name="getbyname" arity="3" since=""/>
<fsummary>Resolve a DNS record of the specified type for the specified
host.</fsummary>
<desc>
@@ -205,8 +205,8 @@ inet_dns:record_type(_) -> undefined.</pre>
</func>
<func>
- <name name="gethostbyaddr" arity="1"/>
- <name name="gethostbyaddr" arity="2"/>
+ <name name="gethostbyaddr" arity="1" since=""/>
+ <name name="gethostbyaddr" arity="2" since=""/>
<fsummary>Return a hostent record for the host with the specified
address.</fsummary>
<desc>
@@ -217,9 +217,9 @@ inet_dns:record_type(_) -> undefined.</pre>
</func>
<func>
- <name name="gethostbyname" arity="1"/>
- <name name="gethostbyname" arity="2"/>
- <name name="gethostbyname" arity="3"/>
+ <name name="gethostbyname" arity="1" since=""/>
+ <name name="gethostbyname" arity="2" since=""/>
+ <name name="gethostbyname" arity="3" since=""/>
<fsummary>Return a hostent record for the host with the specified name.
</fsummary>
<desc>
@@ -235,9 +235,9 @@ inet_dns:record_type(_) -> undefined.</pre>
</func>
<func>
- <name name="lookup" arity="3"/>
- <name name="lookup" arity="4"/>
- <name name="lookup" arity="5"/>
+ <name name="lookup" arity="3" since=""/>
+ <name name="lookup" arity="4" since=""/>
+ <name name="lookup" arity="5" since=""/>
<fsummary>Resolve the DNS data for the record of the specified type
and class for the specified name.</fsummary>
<desc>
@@ -257,9 +257,9 @@ inet_dns:record_type(_) -> undefined.</pre>
</func>
<func>
- <name name="resolve" arity="3"/>
- <name name="resolve" arity="4"/>
- <name name="resolve" arity="5"/>
+ <name name="resolve" arity="3" since=""/>
+ <name name="resolve" arity="4" since=""/>
+ <name name="resolve" arity="5" since=""/>
<fsummary>Resolve a DNS record of the specified type and class
for the specified name.</fsummary>
<desc>
@@ -326,9 +326,9 @@ example_lookup(Name, Class, Type) ->
<funcs>
<func>
- <name name="nslookup" arity="3"/>
- <name name="nslookup" arity="4" clause_i="1"/>
- <name name="nslookup" arity="4" clause_i="2"/>
+ <name name="nslookup" arity="3" since=""/>
+ <name name="nslookup" arity="4" clause_i="1" since=""/>
+ <name name="nslookup" arity="4" clause_i="2" since=""/>
<fsummary>Resolve a DNS record of the specified type and class for the
specified name.</fsummary>
<type variable="Name"/>
@@ -344,8 +344,8 @@ example_lookup(Name, Class, Type) ->
</func>
<func>
- <name name="nnslookup" arity="4"/>
- <name name="nnslookup" arity="5"/>
+ <name name="nnslookup" arity="4" since=""/>
+ <name name="nnslookup" arity="5" since=""/>
<fsummary>Resolve a DNS record of the specified type and class
for the specified name.</fsummary>
<desc>
diff --git a/lib/kernel/doc/src/logger.xml b/lib/kernel/doc/src/logger.xml
index 556f25f96a..e6448e144e 100644
--- a/lib/kernel/doc/src/logger.xml
+++ b/lib/kernel/doc/src/logger.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>logger.xml</file>
</header>
- <module>logger</module>
+ <module since="OTP 21.0">logger</module>
<modulesummary>API module for Logger, the standard logging facility
in Erlang/OTP.</modulesummary>
@@ -190,7 +190,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
<list>
<item><c>pid => self()</c></item>
<item><c>gl => group_leader()</c></item>
- <item><c>time => erlang:system_time(microsecond)</c></item>
+ <item><c>time => logger:timestamp()</c></item>
</list>
<p>When a log macro is used, Logger also inserts location
information:</p>
@@ -245,6 +245,12 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</desc>
</datatype>
<datatype>
+ <name name="olp_config"/>
+ <desc>
+ <p></p>
+ </desc>
+ </datatype>
+ <datatype>
<name name="primary_config"/>
<desc>
<p>Primary configuration data for Logger. The following
@@ -282,8 +288,8 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
<name name="timestamp"/>
<desc>
<p>A timestamp produced
- with <seealso marker="erts:erlang#system_time-1">
- <c>erlang:system_time(microsecond)</c></seealso>.</p>
+ with <seealso marker="#timestamp-0">
+ <c>logger:timestamp()</c></seealso>.</p>
</desc>
</datatype>
</datatypes>
@@ -334,9 +340,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</section>
<funcs>
<func>
- <name>emergency(StringOrReport[,Metadata])</name>
- <name>emergency(Format,Args[,Metadata])</name>
- <name>emergency(Fun,FunArgs[,Metadata])</name>
+ <name since="OTP 21.0">emergency(StringOrReport[,Metadata])</name>
+ <name since="OTP 21.0">emergency(Format,Args[,Metadata])</name>
+ <name since="OTP 21.0">emergency(Fun,FunArgs[,Metadata])</name>
<fsummary>Logs the given message as level <c>emergency</c>.</fsummary>
<desc>
<p>Equivalent to
@@ -345,9 +351,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</func>
<func>
- <name>alert(StringOrReport[,Metadata])</name>
- <name>alert(Format,Args[,Metadata])</name>
- <name>alert(Fun,FunArgs[,Metadata])</name>
+ <name since="OTP 21.0">alert(StringOrReport[,Metadata])</name>
+ <name since="OTP 21.0">alert(Format,Args[,Metadata])</name>
+ <name since="OTP 21.0">alert(Fun,FunArgs[,Metadata])</name>
<fsummary>Logs the given message as level <c>alert</c>.</fsummary>
<desc>
<p>Equivalent to
@@ -356,9 +362,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</func>
<func>
- <name>critical(StringOrReport[,Metadata])</name>
- <name>critical(Format,Args[,Metadata])</name>
- <name>critical(Fun,FunArgs[,Metadata])</name>
+ <name since="OTP 21.0">critical(StringOrReport[,Metadata])</name>
+ <name since="OTP 21.0">critical(Format,Args[,Metadata])</name>
+ <name since="OTP 21.0">critical(Fun,FunArgs[,Metadata])</name>
<fsummary>Logs the given message as level <c>critical</c>.</fsummary>
<desc>
<p>Equivalent to
@@ -367,9 +373,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</func>
<func>
- <name>error(StringOrReport[,Metadata])</name>
- <name>error(Format,Args[,Metadata])</name>
- <name>error(Fun,FunArgs[,Metadata])</name>
+ <name since="OTP 21.0">error(StringOrReport[,Metadata])</name>
+ <name since="OTP 21.0">error(Format,Args[,Metadata])</name>
+ <name since="OTP 21.0">error(Fun,FunArgs[,Metadata])</name>
<fsummary>Logs the given message as level <c>error</c>.</fsummary>
<desc>
<p>Equivalent to
@@ -378,9 +384,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</func>
<func>
- <name>warning(StringOrReport[,Metadata])</name>
- <name>warning(Format,Args[,Metadata])</name>
- <name>warning(Fun,FunArgs[,Metadata])</name>
+ <name since="OTP 21.0">warning(StringOrReport[,Metadata])</name>
+ <name since="OTP 21.0">warning(Format,Args[,Metadata])</name>
+ <name since="OTP 21.0">warning(Fun,FunArgs[,Metadata])</name>
<fsummary>Logs the given message as level <c>warning</c>.</fsummary>
<desc>
<p>Equivalent to
@@ -389,9 +395,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</func>
<func>
- <name>notice(StringOrReport[,Metadata])</name>
- <name>notice(Format,Args[,Metadata])</name>
- <name>notice(Fun,FunArgs[,Metadata])</name>
+ <name since="OTP 21.0">notice(StringOrReport[,Metadata])</name>
+ <name since="OTP 21.0">notice(Format,Args[,Metadata])</name>
+ <name since="OTP 21.0">notice(Fun,FunArgs[,Metadata])</name>
<fsummary>Logs the given message as level <c>notice</c>.</fsummary>
<desc>
<p>Equivalent to
@@ -400,9 +406,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</func>
<func>
- <name>info(StringOrReport[,Metadata])</name>
- <name>info(Format,Args[,Metadata])</name>
- <name>info(Fun,FunArgs[,Metadata])</name>
+ <name since="OTP 21.0">info(StringOrReport[,Metadata])</name>
+ <name since="OTP 21.0">info(Format,Args[,Metadata])</name>
+ <name since="OTP 21.0">info(Fun,FunArgs[,Metadata])</name>
<fsummary>Logs the given message as level <c>info</c>.</fsummary>
<desc>
<p>Equivalent to
@@ -411,9 +417,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</func>
<func>
- <name>debug(StringOrReport[,Metadata])</name>
- <name>debug(Format,Args[,Metadata])</name>
- <name>debug(Fun,FunArgs[,Metadata])</name>
+ <name since="OTP 21.0">debug(StringOrReport[,Metadata])</name>
+ <name since="OTP 21.0">debug(Format,Args[,Metadata])</name>
+ <name since="OTP 21.0">debug(Fun,FunArgs[,Metadata])</name>
<fsummary>Logs the given message as level <c>debug</c>.</fsummary>
<desc>
<p>Equivalent to
@@ -422,12 +428,12 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</func>
<func>
- <name name="log" arity="2"/>
- <name name="log" arity="3" clause_i="1"/>
- <name name="log" arity="3" clause_i="2"/>
- <name name="log" arity="3" clause_i="3"/>
- <name name="log" arity="4" clause_i="1"/>
- <name name="log" arity="4" clause_i="2"/>
+ <name name="log" arity="2" since="OTP 21.0"/>
+ <name name="log" arity="3" clause_i="1" since="OTP 21.0"/>
+ <name name="log" arity="3" clause_i="2" since="OTP 21.0"/>
+ <name name="log" arity="3" clause_i="3" since="OTP 21.0"/>
+ <name name="log" arity="4" clause_i="1" since="OTP 21.0"/>
+ <name name="log" arity="4" clause_i="2" since="OTP 21.0"/>
<fsummary>Logs the given message.</fsummary>
<type variable="Level"/>
<type variable="StringOrReport" name_i="1"/>
@@ -448,7 +454,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</section>
<funcs>
<func>
- <name name="add_handler" arity="3"/>
+ <name name="add_handler" arity="3" since="OTP 21.0"/>
<fsummary>Add a handler with the given configuration.</fsummary>
<desc>
<p>Add a handler with the given configuration.</p>
@@ -459,7 +465,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</func>
<func>
- <name name="add_handler_filter" arity="3"/>
+ <name name="add_handler_filter" arity="3" since="OTP 21.0"/>
<fsummary>Add a filter to the specified handler.</fsummary>
<desc>
<p>Add a filter to the specified handler.</p>
@@ -500,7 +506,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</func>
<func>
- <name name="add_handlers" arity="1" clause_i="1"/>
+ <name name="add_handlers" arity="1" clause_i="1" since="OTP 21.0"/>
<fsummary>Set up log handlers from the application's
configuration parameters.</fsummary>
<desc>
@@ -510,7 +516,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</func>
<func>
- <name name="add_handlers" arity="1" clause_i="2"/>
+ <name name="add_handlers" arity="1" clause_i="2" since="OTP 21.0"/>
<fsummary>Setup logger handlers.</fsummary>
<type name="config_handler"/>
<desc>
@@ -553,7 +559,7 @@ start(_, []) ->
</func>
<func>
- <name name="add_primary_filter" arity="2"/>
+ <name name="add_primary_filter" arity="2" since="OTP 21.0"/>
<fsummary>Add a primary filter to Logger.</fsummary>
<desc>
<p>Add a primary filter to Logger.</p>
@@ -594,16 +600,16 @@ start(_, []) ->
</func>
<func>
- <name name="get_config" arity="0"/>
+ <name name="get_config" arity="0" since="OTP 21.0"/>
<fsummary>Look up the current Logger configuration</fsummary>
<desc>
- <p>Look up all current Logger configuration, including primary
- and handler configuration, and module level settings.</p>
+ <p>Look up all current Logger configuration, including primary,
+ handler, and proxy configuration, and module level settings.</p>
</desc>
</func>
<func>
- <name name="get_handler_config" arity="0"/>
+ <name name="get_handler_config" arity="0" since="OTP 21.0"/>
<fsummary>Look up the current configuration for all handlers.</fsummary>
<desc>
<p>Look up the current configuration for all handlers.</p>
@@ -611,7 +617,7 @@ start(_, []) ->
</func>
<func>
- <name name="get_handler_config" arity="1"/>
+ <name name="get_handler_config" arity="1" since="OTP 21.0"/>
<fsummary>Look up the current configuration for the given
handler.</fsummary>
<desc>
@@ -620,7 +626,7 @@ start(_, []) ->
</func>
<func>
- <name name="get_handler_ids" arity="0"/>
+ <name name="get_handler_ids" arity="0" since="OTP 21.0"/>
<fsummary>Look up the identities for all installed handlers.</fsummary>
<desc>
<p>Look up the identities for all installed handlers.</p>
@@ -628,7 +634,7 @@ start(_, []) ->
</func>
<func>
- <name name="get_primary_config" arity="0"/>
+ <name name="get_primary_config" arity="0" since="OTP 21.0"/>
<fsummary>Look up the current primary configuration for Logger.</fsummary>
<desc>
<p>Look up the current primary configuration for Logger.</p>
@@ -636,7 +642,18 @@ start(_, []) ->
</func>
<func>
- <name name="get_module_level" arity="0"/>
+ <name name="get_proxy_config" arity="0" since="OTP 21.3"/>
+ <fsummary>Look up the current configuration for the Logger proxy.</fsummary>
+ <desc>
+ <p>Look up the current configuration for the Logger proxy.</p>
+ <p>For more information about the proxy, see
+ section <seealso marker="logger_chapter#proxy">Logger
+ Proxy</seealso> in the Kernel User's Guide.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="get_module_level" arity="0" since="OTP 21.0"/>
<fsummary>Look up all current module levels.</fsummary>
<desc>
<p>Look up all current module levels. Returns a list
@@ -648,7 +665,7 @@ start(_, []) ->
</func>
<func>
- <name name="get_module_level" arity="1"/>
+ <name name="get_module_level" arity="1" since="OTP 21.0"/>
<fsummary>Look up the current level for the given modules.</fsummary>
<desc>
<p>Look up the current level for the given modules. Returns a
@@ -660,7 +677,7 @@ start(_, []) ->
</func>
<func>
- <name name="get_process_metadata" arity="0"/>
+ <name name="get_process_metadata" arity="0" since="OTP 21.0"/>
<fsummary>Retrieve data set with set_process_metadata/1.</fsummary>
<desc>
<p>Retrieve data set
@@ -672,7 +689,16 @@ start(_, []) ->
</func>
<func>
- <name name="remove_handler" arity="1"/>
+ <name name="i" arity="0" since="OTP 21.3"/>
+ <name name="i" arity="1" since="OTP 21.3"/>
+ <fsummary>Pretty print the Logger configuration.</fsummary>
+ <desc>
+ <p>Pretty print the Logger configuration.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="remove_handler" arity="1" since="OTP 21.0"/>
<fsummary>Remove the handler with the specified identity.</fsummary>
<desc>
<p>Remove the handler identified by <c><anno>HandlerId</anno></c>.</p>
@@ -680,7 +706,7 @@ start(_, []) ->
</func>
<func>
- <name name="remove_handler_filter" arity="2"/>
+ <name name="remove_handler_filter" arity="2" since="OTP 21.0"/>
<fsummary>Remove a filter from the specified handler.</fsummary>
<desc>
<p>Remove the filter identified
@@ -690,7 +716,7 @@ start(_, []) ->
</func>
<func>
- <name name="remove_primary_filter" arity="1"/>
+ <name name="remove_primary_filter" arity="1" since="OTP 21.0"/>
<fsummary>Remove a primary filter from Logger.</fsummary>
<desc>
<p>Remove the primary filter identified
@@ -699,7 +725,7 @@ start(_, []) ->
</func>
<func>
- <name name="set_application_level" arity="2"/>
+ <name name="set_application_level" arity="2" since="OTP 21.1"/>
<fsummary>Set the log level for all modules in the specified application.</fsummary>
<desc>
<p>Set the log level for all the modules of the specified application.</p>
@@ -710,7 +736,7 @@ start(_, []) ->
</func>
<func>
- <name name="set_handler_config" arity="2"/>
+ <name name="set_handler_config" arity="2" since="OTP 21.0"/>
<fsummary>Set configuration data for the specified handler.</fsummary>
<desc>
<p>Set configuration data for the specified handler. This
@@ -731,11 +757,11 @@ start(_, []) ->
</func>
<func>
- <name name="set_handler_config" arity="3" clause_i="1"/>
- <name name="set_handler_config" arity="3" clause_i="2"/>
- <name name="set_handler_config" arity="3" clause_i="3"/>
- <name name="set_handler_config" arity="3" clause_i="4"/>
- <name name="set_handler_config" arity="3" clause_i="5"/>
+ <name name="set_handler_config" arity="3" clause_i="1" since="OTP 21.0"/>
+ <name name="set_handler_config" arity="3" clause_i="2" since="OTP 21.0"/>
+ <name name="set_handler_config" arity="3" clause_i="3" since="OTP 21.0"/>
+ <name name="set_handler_config" arity="3" clause_i="4" since="OTP 21.0"/>
+ <name name="set_handler_config" arity="3" clause_i="5" since="OTP 21.0"/>
<fsummary>Add or update configuration data for the specified
handler.</fsummary>
<type variable="HandlerId"/>
@@ -767,7 +793,7 @@ start(_, []) ->
</func>
<func>
- <name name="set_primary_config" arity="1"/>
+ <name name="set_primary_config" arity="1" since="OTP 21.0"/>
<fsummary>Set primary configuration data for Logger.</fsummary>
<desc>
<p>Set primary configuration data for Logger. This
@@ -785,9 +811,9 @@ start(_, []) ->
</func>
<func>
- <name name="set_primary_config" arity="2" clause_i="1"/>
- <name name="set_primary_config" arity="2" clause_i="2"/>
- <name name="set_primary_config" arity="2" clause_i="3"/>
+ <name name="set_primary_config" arity="2" clause_i="1" since="OTP 21.0"/>
+ <name name="set_primary_config" arity="2" clause_i="2" since="OTP 21.0"/>
+ <name name="set_primary_config" arity="2" clause_i="3" since="OTP 21.0"/>
<fsummary>Add or update primary configuration data for Logger.</fsummary>
<type variable="Level" name_i="1"/>
<type variable="FilterDefault" name_i="2"/>
@@ -801,7 +827,28 @@ start(_, []) ->
</func>
<func>
- <name name="set_module_level" arity="2"/>
+ <name name="set_proxy_config" arity="1" since="OTP 21.3"/>
+ <fsummary>Set configuration data for the Logger proxy.</fsummary>
+ <desc>
+ <p>Set configuration data for the Logger proxy. This
+ overwrites the current proxy configuration. Keys that are not
+ specified in the <c><anno>Config</anno></c> map gets default
+ values.</p>
+ <p>To modify the existing configuration,
+ use <seealso marker="#update_proxy_config-1">
+ <c>update_proxy_config/1</c></seealso>, or, if a more
+ complex merge is needed, read the current configuration
+ with <seealso marker="#get_proxy_config-0"><c>get_proxy_config/0</c>
+ </seealso>, then do the merge before writing the new
+ configuration back with this function.</p>
+ <p>For more information about the proxy, see
+ section <seealso marker="logger_chapter#proxy">Logger
+ Proxy</seealso> in the Kernel User's Guide.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="set_module_level" arity="2" since="OTP 21.0"/>
<fsummary>Set the log level for the specified modules.</fsummary>
<desc>
<p>Set the log level for the specified modules.</p>
@@ -841,7 +888,7 @@ start(_, []) ->
</func>
<func>
- <name name="set_process_metadata" arity="1"/>
+ <name name="set_process_metadata" arity="1" since="OTP 21.0"/>
<fsummary>Set metadata to use when logging from current process.</fsummary>
<desc>
<p>Set metadata which Logger shall automatically insert in
@@ -860,7 +907,7 @@ start(_, []) ->
</func>
<func>
- <name name="unset_application_level" arity="1"/>
+ <name name="unset_application_level" arity="1" since="OTP 21.1"/>
<fsummary>Unset the log level for all modules in the specified application.</fsummary>
<desc>
<p>Unset the log level for all the modules of the specified application.</p>
@@ -871,7 +918,7 @@ start(_, []) ->
</func>
<func>
- <name name="unset_module_level" arity="0"/>
+ <name name="unset_module_level" arity="0" since="OTP 21.0"/>
<fsummary>Remove module specific log settings for all modules.</fsummary>
<desc>
<p>Remove module specific log settings. After this, the
@@ -880,7 +927,7 @@ start(_, []) ->
</func>
<func>
- <name name="unset_module_level" arity="1"/>
+ <name name="unset_module_level" arity="1" since="OTP 21.0"/>
<fsummary>Remove module specific log settings for the given
modules.</fsummary>
<desc>
@@ -890,7 +937,7 @@ start(_, []) ->
</func>
<func>
- <name name="unset_process_metadata" arity="0"/>
+ <name name="unset_process_metadata" arity="0" since="OTP 21.0"/>
<fsummary>Delete data set with set_process_metadata/1.</fsummary>
<desc>
<p>Delete data set
@@ -902,7 +949,7 @@ start(_, []) ->
</func>
<func>
- <name name="update_formatter_config" arity="2"/>
+ <name name="update_formatter_config" arity="2" since="OTP 21.0"/>
<fsummary>Update the formatter configuration for the specified handler.</fsummary>
<desc>
<p>Update the formatter configuration for the specified handler.</p>
@@ -917,7 +964,7 @@ start(_, []) ->
</func>
<func>
- <name name="update_formatter_config" arity="3"/>
+ <name name="update_formatter_config" arity="3" since="OTP 21.0"/>
<fsummary>Update the formatter configuration for the specified handler.</fsummary>
<desc>
<p>Update the formatter configuration for the specified handler.</p>
@@ -928,7 +975,7 @@ start(_, []) ->
</func>
<func>
- <name name="update_handler_config" arity="2"/>
+ <name name="update_handler_config" arity="2" since="OTP 21.0"/>
<fsummary>Update configuration data for the specified handler.</fsummary>
<desc>
<p>Update configuration data for the specified handler. This function
@@ -944,11 +991,11 @@ logger:set_handler_config(HandlerId, maps:merge(Old, Config)).
</func>
<func>
- <name name="update_handler_config" arity="3" clause_i="1"/>
- <name name="update_handler_config" arity="3" clause_i="2"/>
- <name name="update_handler_config" arity="3" clause_i="3"/>
- <name name="update_handler_config" arity="3" clause_i="4"/>
- <name name="update_handler_config" arity="3" clause_i="5"/>
+ <name name="update_handler_config" arity="3" clause_i="1" since="OTP 21.2"/>
+ <name name="update_handler_config" arity="3" clause_i="2" since="OTP 21.2"/>
+ <name name="update_handler_config" arity="3" clause_i="3" since="OTP 21.2"/>
+ <name name="update_handler_config" arity="3" clause_i="4" since="OTP 21.2"/>
+ <name name="update_handler_config" arity="3" clause_i="5" since="OTP 21.2"/>
<fsummary>Add or update configuration data for the specified
handler.</fsummary>
<type variable="HandlerId"/>
@@ -980,7 +1027,7 @@ logger:set_handler_config(HandlerId, maps:merge(Old, Config)).
</func>
<func>
- <name name="update_primary_config" arity="1"/>
+ <name name="update_primary_config" arity="1" since="OTP 21.0"/>
<fsummary>Update primary configuration data for Logger.</fsummary>
<desc>
<p>Update primary configuration data for Logger. This function
@@ -996,7 +1043,7 @@ logger:set_primary_config(maps:merge(Old, Config)).
</func>
<func>
- <name name="update_process_metadata" arity="1"/>
+ <name name="update_process_metadata" arity="1" since="OTP 21.0"/>
<fsummary>Set or update metadata to use when logging from
current process.</fsummary>
<desc>
@@ -1013,6 +1060,25 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)).
</seealso>.</p>
</desc>
</func>
+
+ <func>
+ <name name="update_proxy_config" arity="1" since="OTP 21.3"/>
+ <fsummary>Update configuration data for the Logger proxy.</fsummary>
+ <desc>
+ <p>Update configuration data for the Logger proxy. This function
+ behaves as if it was implemented as follows:</p>
+ <code type="erl">
+Old = logger:get_proxy_config(),
+logger:set_proxy_config(maps:merge(Old, Config)).
+ </code>
+ <p>To overwrite the existing configuration without any merge,
+ use <seealso marker="#set_proxy_config-1"><c>set_proxy_config/1</c>
+ </seealso>.</p>
+ <p>For more information about the proxy, see
+ section <seealso marker="logger_chapter#proxy">Logger
+ Proxy</seealso> in the Kernel User's Guide.</p>
+ </desc>
+ </func>
</funcs>
<section>
@@ -1021,7 +1087,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)).
</section>
<funcs>
<func>
- <name name="compare_levels" arity="2"/>
+ <name name="compare_levels" arity="2" since="OTP 21.0"/>
<fsummary>Compare the severity of two log levels.</fsummary>
<desc>
<p>Compare the severity of two log levels. Returns <c>gt</c>
@@ -1032,7 +1098,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)).
</func>
<func>
- <name name="format_report" arity="1"/>
+ <name name="format_report" arity="1" since="OTP 21.0"/>
<fsummary>Convert a log message on report form to {Format, Args}.</fsummary>
<desc>
<p>Convert a log message on report form to <c>{Format,
@@ -1051,6 +1117,24 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)).
a key-value list before formatting as such.</p>
</desc>
</func>
+
+ <func>
+ <name name="timestamp" arity="0" since="OTP @OTP-15625@"/>
+ <fsummary>Return a timestamp to insert in meta data for a log
+ event.</fsummary>
+ <desc>
+ <p>Return a timestamp that can be inserted as the <c>time</c>
+ field in the meta data for a log event. It is produced with
+ <seealso marker="kernel:os#system_time-1">
+ <c>os:system_time(microsecond)</c></seealso>.</p>
+ <p>Notice that Logger automatically inserts a timestamp in the
+ meta data unless it already exists. This function is
+ exported for the rare case when the timestamp must be taken
+ at a different point in time than when the log event is
+ issued.</p>
+ </desc>
+ </func>
+
</funcs>
<section>
@@ -1062,7 +1146,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)).
<funcs>
<func>
- <name>HModule:adding_handler(Config1) -> {ok, Config2} | {error,
+ <name since="OTP 21.0">HModule:adding_handler(Config1) -> {ok, Config2} | {error,
Reason}</name>
<fsummary>An instance of this handler is about to be added.</fsummary>
<type>
@@ -1088,7 +1172,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)).
</func>
<func>
- <name>HModule:changing_config(SetOrUpdate, OldConfig, NewConfig) -> {ok, Config} | {error, Reason}</name>
+ <name since="OTP 21.2">HModule:changing_config(SetOrUpdate, OldConfig, NewConfig) -> {ok, Config} | {error, Reason}</name>
<fsummary>The configuration for this handler is about to change.</fsummary>
<type>
<v>SetOrUpdate = set | update</v>
@@ -1126,7 +1210,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)).
</func>
<func>
- <name>HModule:filter_config(Config) -> FilteredConfig</name>
+ <name since="OTP 21.2">HModule:filter_config(Config) -> FilteredConfig</name>
<fsummary>Remove internal data from configuration.</fsummary>
<type>
<v>Config = FilteredConfig =
@@ -1146,7 +1230,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)).
</func>
<func>
- <name>HModule:log(LogEvent, Config) -> void()</name>
+ <name since="OTP 21.0">HModule:log(LogEvent, Config) -> void()</name>
<fsummary>Log the given log event.</fsummary>
<type>
<v>LogEvent =
@@ -1169,7 +1253,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)).
</func>
<func>
- <name>HModule:removing_handler(Config) -> ok</name>
+ <name since="OTP 21.0">HModule:removing_handler(Config) -> ok</name>
<fsummary>The given handler is about to be removed.</fsummary>
<type>
<v>Config =
@@ -1197,7 +1281,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)).
<funcs>
<func>
- <name>FModule:check_config(FConfig) -> ok | {error, Reason}</name>
+ <name since="OTP 21.0">FModule:check_config(FConfig) -> ok | {error, Reason}</name>
<fsummary>Validate the given formatter configuration.</fsummary>
<type>
<v>FConfig =
@@ -1228,7 +1312,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)).
</desc>
</func>
<func>
- <name>FModule:format(LogEvent, FConfig) -> FormattedLogEntry</name>
+ <name since="OTP 21.0">FModule:format(LogEvent, FConfig) -> FormattedLogEntry</name>
<fsummary>Format the given log event.</fsummary>
<type>
<v>LogEvent =
diff --git a/lib/kernel/doc/src/logger_chapter.xml b/lib/kernel/doc/src/logger_chapter.xml
index 458e61cef5..03b9edcf8f 100644
--- a/lib/kernel/doc/src/logger_chapter.xml
+++ b/lib/kernel/doc/src/logger_chapter.xml
@@ -212,13 +212,13 @@
<pre>fun((<seealso marker="logger#type-report"><c>logger:report()</c></seealso>,<seealso marker="logger#type-report_cb_config"><c>logger:report_cb_config()</c></seealso>) -> <seealso marker="stdlib:unicode#type-chardata"><c>unicode:chardata()</c></seealso>)
</pre>
<p>The fun must obey the <c>depth</c> and <c>chars_limit</c>
- parameters provided in the second argument, as the formatter can
- not do anything useful of these parameters with the returned
- string. The extra data also contains a field named
- <c>single_line</c>, indicating if the printed log message may
- contain line breaks or not. This variant is used when the
- formatting of the report depends on the size or single line
- parameters.</p>
+ parameters provided in the second argument, as the formatter
+ cannot do anything useful of these parameters with the
+ returned string. The extra data also contains a field named
+ <c>single_line</c>, indicating if the printed log message may
+ contain line breaks or not. This variant is used when the
+ formatting of the report depends on the size or single line
+ parameters.</p>
<p>Example, format string and arguments:</p>
<code>logger:error("The file does not exist: ~ts",[Filename])</code>
<p>Example, string:</p>
@@ -693,8 +693,10 @@ logger:debug(#{got => connection_request, id => Id, state => State},
with <seealso marker="#logger_sasl_compatible">
<c>logger_sasl_compatible</c></seealso>.</p>
<p>With this parameter, you can modify or disable the default
- handler, add custom handlers and primary logger filters, and
- set log levels per module.</p>
+ handler, add custom handlers and primary logger filters, set
+ log levels per module, and modify
+ the <seealso marker="#proxy">proxy</seealso>
+ configuration.</p>
<p><c>Config</c> is any (zero or more) of the following:</p>
<taglist>
<tag><c>{handler, default, undefined}</c></tag>
@@ -746,6 +748,14 @@ logger:debug(#{got => connection_request, id => Id, state => State},
<p>for each <c>Module</c>.</p>
<p>Multiple entries of this type are allowed.</p>
</item>
+ <tag><c>{proxy, ProxyConfig}</c></tag>
+ <item>
+ <p>Sets the proxy configuration, equivalent to calling</p>
+ <pre><seealso marker="logger#set_proxy_config/1">
+ logger:set_proxy_config(ProxyConfig)
+ </seealso></pre>
+ <p>Only one entry of this type is allowed.</p>
+ </item>
</taglist>
<p>See
section <seealso marker="#config_examples">Configuration
@@ -1334,9 +1344,50 @@ logger:add_handler(my_disk_log_h, logger_disk_log_h,
</section>
<section>
+ <marker id="proxy"/>
+ <title>Logger Proxy</title>
+ <p>The Logger proxy is an Erlang process which is part of the
+ Kernel application's supervision tree. During startup, the proxy
+ process registers itself as the <c>system_logger</c>, meaning
+ that log events produced by the emulator are sent to this
+ process.</p>
+ <p>When a log event is issued on a process which has its group
+ leader on a remote node, Logger automatically forwards the log
+ event to the group leader's node. To achieve this, it first
+ sends the log event as an Erlang message from the original
+ client process to the proxy on the local node, and the proxy in
+ turn forwards the event to the proxy on the remote node.</p>
+ <p>When receiving a log event, either from the emulator or from a
+ remote node, the proxy calls the Logger API to log the event.</p>
+ <p>The proxy process is overload protected in the same way as
+ described in
+ section <seealso marker="#overload_protection">Protecting the
+ Handler from Overload</seealso>, but with the following default
+ values:</p>
+ <code>
+ #{sync_mode_qlen => 500,
+ drop_mode_qlen => 1000,
+ flush_qlen => 5000,
+ burst_limit_enable => false,
+ overload_kill_enable => false}</code>
+ <p>For log events from the emulator, synchronous message passing
+ mode is not applicable, since all messages are passed
+ asynchronously by the emulator. Drop mode is achieved by setting
+ the <c>system_logger</c> to <c>undefined</c>, forcing the
+ emulator to drop events until it is set back to the proxy pid
+ again.</p>
+ <p>The proxy uses <seealso marker="erts:erlang#send_nosuspend/2">
+ <c>erlang:send_nosuspend/2</c></seealso> when sending log
+ events to a remote node. If the message could not be sent
+ without suspending the sender, it is dropped. This is to avoid
+ blocking the proxy process.</p>
+ </section>
+
+ <section>
<title>See Also</title>
<p>
<seealso marker="disk_log"><c>disk_log(3)</c></seealso>,
+ <seealso marker="erts:erlang"><c>erlang(3)</c></seealso>,
<seealso marker="error_logger"><c>error_logger(3)</c></seealso>,
<seealso marker="logger"><c>logger(3)</c></seealso>,
<seealso marker="logger_disk_log_h"><c>logger_disk_log_h(3)</c></seealso>,
diff --git a/lib/kernel/doc/src/logger_disk_log_h.xml b/lib/kernel/doc/src/logger_disk_log_h.xml
index d9b941a0a9..5b2374690e 100644
--- a/lib/kernel/doc/src/logger_disk_log_h.xml
+++ b/lib/kernel/doc/src/logger_disk_log_h.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>logger_disk_log_h.xml</file>
</header>
- <module>logger_disk_log_h</module>
+ <module since="OTP 21.0">logger_disk_log_h</module>
<modulesummary>A disk_log based handler for Logger</modulesummary>
<description>
@@ -148,7 +148,7 @@ erl -kernel logger '[{handler,default,logger_disk_log_h,
<funcs>
<func>
- <name name="filesync" arity="1" clause_i="1"/>
+ <name name="filesync" arity="1" clause_i="1" since="OTP 21.0"/>
<fsummary>Writes buffered data to disk.</fsummary>
<desc>
<p>Write buffered data to disk.</p>
diff --git a/lib/kernel/doc/src/logger_filters.xml b/lib/kernel/doc/src/logger_filters.xml
index 90f1fcc270..0a02342864 100644
--- a/lib/kernel/doc/src/logger_filters.xml
+++ b/lib/kernel/doc/src/logger_filters.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>logger_filters.xml</file>
</header>
- <module>logger_filters</module>
+ <module since="OTP 21.0">logger_filters</module>
<modulesummary>Filters to use with Logger.</modulesummary>
<description>
@@ -51,7 +51,7 @@
<funcs>
<func>
- <name name="domain" arity="2"/>
+ <name name="domain" arity="2" since="OTP 21.0"/>
<fsummary>Filter log events based on the domain field in
metadata.</fsummary>
<desc>
@@ -152,7 +152,7 @@ ok</code>
</func>
<func>
- <name name="level" arity="2"/>
+ <name name="level" arity="2" since="OTP 21.0"/>
<fsummary>Filter log events based on the log level.</fsummary>
<desc>
<p>This filter provides a way of filtering log events based
@@ -212,7 +212,7 @@ ok</code>
</func>
<func>
- <name name="progress" arity="2"/>
+ <name name="progress" arity="2" since="OTP 21.0"/>
<fsummary>Filter progress reports from supervisor and application_controller.</fsummary>
<desc>
<p>This filter matches all progress reports
@@ -227,7 +227,7 @@ ok</code>
</func>
<func>
- <name name="remote_gl" arity="2"/>
+ <name name="remote_gl" arity="2" since="OTP 21.0"/>
<fsummary>Filter events with group leader on remote node.</fsummary>
<desc>
<p>This filter matches all events originating from a process
diff --git a/lib/kernel/doc/src/logger_formatter.xml b/lib/kernel/doc/src/logger_formatter.xml
index d066e263df..6dc83d24e1 100644
--- a/lib/kernel/doc/src/logger_formatter.xml
+++ b/lib/kernel/doc/src/logger_formatter.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>logger_formatter.xml</file>
</header>
- <module>logger_formatter</module>
+ <module since="OTP 21.0">logger_formatter</module>
<modulesummary>Default formatter for Logger.</modulesummary>
<description>
@@ -289,7 +289,7 @@ exit_reason: "It crashed"</code>
<funcs>
<func>
- <name name="check_config" arity="1"/>
+ <name name="check_config" arity="1" since="OTP 21.0"/>
<fsummary>Validates the given formatter configuration.</fsummary>
<desc>
<p>The function is called by Logger when the formatter
@@ -310,7 +310,7 @@ exit_reason: "It crashed"</code>
</desc>
</func>
<func>
- <name name="format" arity="2"/>
+ <name name="format" arity="2" since="OTP 21.0"/>
<fsummary>Formats the given message.</fsummary>
<desc>
<p>This the formatter callback function to be called from
diff --git a/lib/kernel/doc/src/logger_std_h.xml b/lib/kernel/doc/src/logger_std_h.xml
index e156f5719b..fcd180abd6 100644
--- a/lib/kernel/doc/src/logger_std_h.xml
+++ b/lib/kernel/doc/src/logger_std_h.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>logger_std_h.xml</file>
</header>
- <module>logger_std_h</module>
+ <module since="OTP 21.0">logger_std_h</module>
<modulesummary>Standard handler for Logger.</modulesummary>
<description>
@@ -121,7 +121,7 @@ erl -kernel logger '[{handler,default,logger_std_h,
<funcs>
<func>
- <name name="filesync" arity="1" clause_i="1"/>
+ <name name="filesync" arity="1" clause_i="1" since="OTP 21.0"/>
<fsummary>Writes buffered data to disk.</fsummary>
<desc>
<p>Write buffered data to disk.</p>
diff --git a/lib/kernel/doc/src/net_adm.xml b/lib/kernel/doc/src/net_adm.xml
index 6957a3b5e4..c3e1619f1b 100644
--- a/lib/kernel/doc/src/net_adm.xml
+++ b/lib/kernel/doc/src/net_adm.xml
@@ -28,7 +28,7 @@
<date>1996-09-10</date>
<rev>A</rev>
</header>
- <module>net_adm</module>
+ <module since="">net_adm</module>
<modulesummary>Various Erlang net administration routines.</modulesummary>
<description>
<p>This module contains various network utility functions.</p>
@@ -36,7 +36,7 @@
<funcs>
<func>
- <name name="dns_hostname" arity="1"/>
+ <name name="dns_hostname" arity="1" since=""/>
<fsummary>Official name of a host.</fsummary>
<desc>
<p>Returns the official name of <c><anno>Host</anno></c>, or
@@ -46,7 +46,7 @@
</func>
<func>
- <name name="host_file" arity="0"/>
+ <name name="host_file" arity="0" since=""/>
<fsummary>Read file <c>.hosts.erlang</c>.</fsummary>
<desc>
<p>Reads file <c>.hosts.erlang</c>, see section
@@ -58,7 +58,7 @@
</func>
<func>
- <name name="localhost" arity="0"/>
+ <name name="localhost" arity="0" since=""/>
<fsummary>Name of the local host.</fsummary>
<desc>
<p>Returns the name of the local host. If Erlang was started
@@ -68,8 +68,8 @@
</func>
<func>
- <name name="names" arity="0"/>
- <name name="names" arity="1"/>
+ <name name="names" arity="0" since=""/>
+ <name name="names" arity="1" since=""/>
<fsummary>Names of Erlang nodes at a host.</fsummary>
<desc>
<p>Similar to <c>epmd -names</c>, see
@@ -86,7 +86,7 @@
</func>
<func>
- <name name="ping" arity="1"/>
+ <name name="ping" arity="1" since=""/>
<fsummary>Set up a connection to a node.</fsummary>
<desc>
<p>Sets up a connection to <c><anno>Node</anno></c>. Returns
@@ -95,8 +95,8 @@
</func>
<func>
- <name name="world" arity="0"/>
- <name name="world" arity="1"/>
+ <name name="world" arity="0" since=""/>
+ <name name="world" arity="1" since=""/>
<fsummary>Lookup and connect to all nodes at all hosts in
<c>.hosts.erlang</c>.</fsummary>
<type name="verbosity"/>
@@ -117,8 +117,8 @@
</func>
<func>
- <name name="world_list" arity="1"/>
- <name name="world_list" arity="2"/>
+ <name name="world_list" arity="1" since=""/>
+ <name name="world_list" arity="2" since=""/>
<fsummary>Lookup and connect to all nodes at specified hosts.</fsummary>
<type name="verbosity"/>
<desc>
diff --git a/lib/kernel/doc/src/net_kernel.xml b/lib/kernel/doc/src/net_kernel.xml
index bfbe7a6470..419d3cad84 100644
--- a/lib/kernel/doc/src/net_kernel.xml
+++ b/lib/kernel/doc/src/net_kernel.xml
@@ -28,7 +28,7 @@
<date>1996-09-10</date>
<rev>A</rev>
</header>
- <module>net_kernel</module>
+ <module since="">net_kernel</module>
<modulesummary>Erlang networking kernel.</modulesummary>
<description>
<p>The net kernel is a system process, registered as
@@ -81,7 +81,7 @@ $ <input>erl -sname foobar</input></pre>
<funcs>
<func>
- <name name="allow" arity="1"/>
+ <name name="allow" arity="1" since=""/>
<fsummary>Permit access to a specified set of nodes</fsummary>
<desc>
<p>Permits access to the specified set of nodes.</p>
@@ -98,7 +98,7 @@ $ <input>erl -sname foobar</input></pre>
</func>
<func>
- <name name="connect_node" arity="1"/>
+ <name name="connect_node" arity="1" since=""/>
<fsummary>Establish a connection to a node.</fsummary>
<desc>
<p>Establishes a connection to <c><anno>Node</anno></c>. Returns
@@ -110,7 +110,7 @@ $ <input>erl -sname foobar</input></pre>
</func>
<func>
- <name name="get_net_ticktime" arity="0"/>
+ <name name="get_net_ticktime" arity="0" since=""/>
<fsummary>Get <c>net_ticktime</c>.</fsummary>
<desc>
<p>Gets <c>net_ticktime</c> (see
@@ -131,7 +131,7 @@ $ <input>erl -sname foobar</input></pre>
</func>
<func>
- <name name="getopts" arity="2"/>
+ <name name="getopts" arity="2" since="OTP 19.1"/>
<fsummary>Get distribution socket options.</fsummary>
<desc>
<p>Get one or more options for the distribution socket
@@ -146,8 +146,8 @@ $ <input>erl -sname foobar</input></pre>
</func>
<func>
- <name name="monitor_nodes" arity="1"/>
- <name name="monitor_nodes" arity="2"/>
+ <name name="monitor_nodes" arity="1" since=""/>
+ <name name="monitor_nodes" arity="2" since=""/>
<fsummary>Subscribe to node status change messages.</fsummary>
<desc>
<p>The calling process subscribes or unsubscribes to node
@@ -267,8 +267,8 @@ $ <input>erl -sname foobar</input></pre>
</func>
<func>
- <name name="set_net_ticktime" arity="1"/>
- <name name="set_net_ticktime" arity="2"/>
+ <name name="set_net_ticktime" arity="1" since=""/>
+ <name name="set_net_ticktime" arity="2" since=""/>
<fsummary>Set <c>net_ticktime</c>.</fsummary>
<desc>
<p>Sets <c>net_ticktime</c> (see
@@ -324,7 +324,7 @@ $ <input>erl -sname foobar</input></pre>
</func>
<func>
- <name name="setopts" arity="2"/>
+ <name name="setopts" arity="2" since="OTP 19.1"/>
<fsummary>Set distribution socket options.</fsummary>
<desc>
<p>Set one or more options for distribution sockets.
@@ -345,9 +345,9 @@ $ <input>erl -sname foobar</input></pre>
</func>
<func>
- <name>start([Name]) -> {ok, pid()} | {error, Reason}</name>
- <name>start([Name, NameType]) -> {ok, pid()} | {error, Reason}</name>
- <name>start([Name, NameType, Ticktime]) -> {ok, pid()} | {error, Reason}</name>
+ <name since="">start([Name]) -> {ok, pid()} | {error, Reason}</name>
+ <name since="">start([Name, NameType]) -> {ok, pid()} | {error, Reason}</name>
+ <name since="">start([Name, NameType, Ticktime]) -> {ok, pid()} | {error, Reason}</name>
<fsummary>Turn an Erlang runtime system into a distributed node.</fsummary>
<type>
<v>Name = atom()</v>
@@ -364,7 +364,7 @@ $ <input>erl -sname foobar</input></pre>
</func>
<func>
- <name name="stop" arity="0"/>
+ <name name="stop" arity="0" since=""/>
<fsummary>Turn a node into a non-distributed Erlang runtime system.</fsummary>
<desc>
<p>Turns a distributed node into a non-distributed node. For
diff --git a/lib/kernel/doc/src/os.xml b/lib/kernel/doc/src/os.xml
index c95e615c6b..0500e4cfb3 100644
--- a/lib/kernel/doc/src/os.xml
+++ b/lib/kernel/doc/src/os.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>os</module>
+ <module since="">os</module>
<modulesummary>Operating system-specific functions.</modulesummary>
<description>
<p>The functions in this module are operating system-specific.
@@ -134,8 +134,8 @@
<funcs>
<func>
- <name name="cmd" arity="1"/>
- <name name="cmd" arity="2"/>
+ <name name="cmd" arity="1" since=""/>
+ <name name="cmd" arity="2" since="OTP 20.2.3"/>
<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
@@ -173,8 +173,8 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
</func>
<func>
- <name name="find_executable" arity="1"/>
- <name name="find_executable" arity="2"/>
+ <name name="find_executable" arity="1" since=""/>
+ <name name="find_executable" arity="2" since=""/>
<fsummary>Absolute filename of a program.</fsummary>
<desc>
<p>These two functions look up an executable program, with the
@@ -190,7 +190,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
</func>
<func>
- <name name="getenv" arity="0"/>
+ <name name="getenv" arity="0" since=""/>
<fsummary>List all environment variables.</fsummary>
<desc>
<p>Returns a list of all environment variables.
@@ -205,7 +205,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
</func>
<func>
- <name name="getenv" arity="1"/>
+ <name name="getenv" arity="1" since=""/>
<fsummary>Get the value of an environment variable.</fsummary>
<desc>
<p>Returns the <c><anno>Value</anno></c> of the environment variable
@@ -220,7 +220,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
</func>
<func>
- <name name="getenv" arity="2"/>
+ <name name="getenv" arity="2" since="OTP 18.0"/>
<fsummary>Get the value of an environment variable.</fsummary>
<desc>
<p>Returns the <c><anno>Value</anno></c> of the environment variable
@@ -235,7 +235,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
</func>
<func>
- <name name="getpid" arity="0"/>
+ <name name="getpid" arity="0" since=""/>
<fsummary>Return the process identifier of the emulator
process.</fsummary>
<desc>
@@ -251,7 +251,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
</func>
<func>
- <name name="putenv" arity="2"/>
+ <name name="putenv" arity="2" since=""/>
<fsummary>Set a new value for an environment variable.</fsummary>
<desc>
<p>Sets a new <c><anno>Value</anno></c> for environment variable
@@ -277,7 +277,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
</func>
<func>
- <name name="set_signal" arity="2"/>
+ <name name="set_signal" arity="2" since="OTP 20.0"/>
<fsummary>Enables or disables handling of OS signals.</fsummary>
<desc>
<p>Enables or disables OS signals.</p>
@@ -304,7 +304,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
</func>
<func>
- <name name="system_time" arity="0"/>
+ <name name="system_time" arity="0" since="OTP 18.0"/>
<fsummary>Current OS system time.</fsummary>
<desc>
<p>Returns the current
@@ -317,7 +317,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
</func>
<func>
- <name name="system_time" arity="1"/>
+ <name name="system_time" arity="1" since="OTP 18.0"/>
<fsummary>Current OS system time.</fsummary>
<desc>
<p>Returns the current
@@ -332,7 +332,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
</func>
<func>
- <name name="timestamp" arity="0"/>
+ <name name="timestamp" arity="0" since=""/>
<fsummary>Current OS system time on the <c>erlang:timestamp/0</c> format.</fsummary>
<type_desc variable="Timestamp">Timestamp = {MegaSecs, Secs, MicroSecs}</type_desc>
<desc>
@@ -373,7 +373,7 @@ calendar:now_to_universal_time(TS),
</func>
<func>
- <name name="perf_counter" arity="0"/>
+ <name name="perf_counter" arity="0" since="OTP 19.0"/>
<fsummary>Returns a performance counter</fsummary>
<desc>
<p>Returns the current performance counter value in <c>perf_counter</c>
@@ -383,7 +383,7 @@ calendar:now_to_universal_time(TS),
</desc>
</func>
<func>
- <name name="perf_counter" arity="1"/>
+ <name name="perf_counter" arity="1" since="OTP 19.0"/>
<fsummary>Returns a performance counter</fsummary>
<desc><p>Returns a performance counter that can be used as a very fast and
high resolution timestamp. This counter is read directly from the hardware or operating
@@ -397,7 +397,7 @@ calendar:now_to_universal_time(TS),
</desc>
</func>
<func>
- <name name="type" arity="0"/>
+ <name name="type" arity="0" since=""/>
<fsummary>Return the OS family and, in some cases, the OS name of the
current OS.</fsummary>
<desc>
@@ -417,7 +417,7 @@ calendar:now_to_universal_time(TS),
</func>
<func>
- <name name="unsetenv" arity="1"/>
+ <name name="unsetenv" arity="1" since="OTP R16B03"/>
<fsummary>Delete an environment variable.</fsummary>
<desc>
<p>Deletes the environment variable <c><anno>VarName</anno></c>.</p>
@@ -429,7 +429,7 @@ calendar:now_to_universal_time(TS),
</func>
<func>
- <name name="version" arity="0"/>
+ <name name="version" arity="0" since=""/>
<fsummary>Return the OS versions.</fsummary>
<desc>
<p>Returns the OS version.
diff --git a/lib/kernel/doc/src/pg2.xml b/lib/kernel/doc/src/pg2.xml
index 0631b317b4..058d711756 100644
--- a/lib/kernel/doc/src/pg2.xml
+++ b/lib/kernel/doc/src/pg2.xml
@@ -32,7 +32,7 @@
<rev>A2</rev>
<file>pg2.xml</file>
</header>
- <module>pg2</module>
+ <module since="">pg2</module>
<modulesummary>Distributed named process groups.</modulesummary>
<description>
<p>This module implements process groups. Each message can be sent
@@ -66,7 +66,7 @@
<funcs>
<func>
- <name name="create" arity="1"/>
+ <name name="create" arity="1" since=""/>
<fsummary>Create a new, empty process group.</fsummary>
<desc>
<p>Creates a new, empty process group. The group is globally
@@ -75,7 +75,7 @@
</func>
<func>
- <name name="delete" arity="1"/>
+ <name name="delete" arity="1" since=""/>
<fsummary>Delete a process group.</fsummary>
<desc>
<p>Deletes a process group.</p>
@@ -83,7 +83,7 @@
</func>
<func>
- <name name="get_closest_pid" arity="1"/>
+ <name name="get_closest_pid" arity="1" since=""/>
<fsummary>Common dispatch function.</fsummary>
<desc>
<p>A useful dispatch function that can be used from
@@ -93,7 +93,7 @@
</func>
<func>
- <name name="get_local_members" arity="1"/>
+ <name name="get_local_members" arity="1" since=""/>
<fsummary>Return all local processes in a group.</fsummary>
<desc>
<p>Returns all processes running on the local node in the
@@ -104,7 +104,7 @@
</func>
<func>
- <name name="get_members" arity="1"/>
+ <name name="get_members" arity="1" since=""/>
<fsummary>Return all processes in a group.</fsummary>
<desc>
<p>Returns all processes in the group <c>Name</c>. This
@@ -114,7 +114,7 @@
</func>
<func>
- <name name="join" arity="2"/>
+ <name name="join" arity="2" since=""/>
<fsummary>Join a process to a group.</fsummary>
<desc>
<p>Joins the process <c>Pid</c> to the group <c>Name</c>.
@@ -124,7 +124,7 @@
</func>
<func>
- <name name="leave" arity="2"/>
+ <name name="leave" arity="2" since=""/>
<fsummary>Make a process leave a group.</fsummary>
<desc>
<p>Makes the process <c>Pid</c> leave the group <c>Name</c>.
@@ -134,8 +134,8 @@
</func>
<func>
- <name name="start" arity="0"/>
- <name name="start_link" arity="0"/>
+ <name name="start" arity="0" since=""/>
+ <name name="start_link" arity="0" since=""/>
<fsummary>Start the <c>pg2</c> server.</fsummary>
<desc>
<p>Starts the <c>pg2</c> server. Normally, the server does not need
@@ -149,7 +149,7 @@
</func>
<func>
- <name name="which_groups" arity="0"/>
+ <name name="which_groups" arity="0" since=""/>
<fsummary>Return a list of all known groups.</fsummary>
<desc>
<p>Returns a list of all known groups.</p>
diff --git a/lib/kernel/doc/src/rpc.xml b/lib/kernel/doc/src/rpc.xml
index fab616e630..c55454506e 100644
--- a/lib/kernel/doc/src/rpc.xml
+++ b/lib/kernel/doc/src/rpc.xml
@@ -28,7 +28,7 @@
<date>1996-09-10</date>
<rev>A</rev>
</header>
- <module>rpc</module>
+ <module since="">rpc</module>
<modulesummary>Remote Procedure Call services.</modulesummary>
<description>
<p>This module contains services similar to Remote
@@ -51,7 +51,7 @@
<funcs>
<func>
- <name name="abcast" arity="2"/>
+ <name name="abcast" arity="2" since=""/>
<fsummary>Broadcast a message asynchronously to a registered process on
all nodes.</fsummary>
<desc>
@@ -61,7 +61,7 @@
</func>
<func>
- <name name="abcast" arity="3"/>
+ <name name="abcast" arity="3" since=""/>
<fsummary>Broadcast a message asynchronously to a registered process on
specific nodes.</fsummary>
<desc>
@@ -72,7 +72,7 @@
</func>
<func>
- <name name="async_call" arity="4"/>
+ <name name="async_call" arity="4" since=""/>
<fsummary>Evaluate a function call on a node, asynchronous
version.</fsummary>
<desc>
@@ -98,7 +98,7 @@
</func>
<func>
- <name name="block_call" arity="4"/>
+ <name name="block_call" arity="4" since=""/>
<fsummary>Evaluate a function call on a node in the RPC server's
context.</fsummary>
<desc>
@@ -115,7 +115,7 @@
</func>
<func>
- <name name="block_call" arity="5"/>
+ <name name="block_call" arity="5" since=""/>
<fsummary>Evaluate a function call on a node in the RPC server's
context.</fsummary>
<desc>
@@ -127,7 +127,7 @@
</func>
<func>
- <name name="call" arity="4"/>
+ <name name="call" arity="4" since=""/>
<fsummary>Evaluate a function call on a node.</fsummary>
<desc>
<p>Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>,
@@ -138,7 +138,7 @@
</func>
<func>
- <name name="call" arity="5"/>
+ <name name="call" arity="5" since=""/>
<fsummary>Evaluate a function call on a node.</fsummary>
<desc>
<p>Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>,
@@ -158,7 +158,7 @@
</func>
<func>
- <name name="cast" arity="4"/>
+ <name name="cast" arity="4" since=""/>
<fsummary>Run a function on a node ignoring the result.</fsummary>
<desc>
<p>Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>,
@@ -171,7 +171,7 @@
</func>
<func>
- <name name="eval_everywhere" arity="3"/>
+ <name name="eval_everywhere" arity="3" since=""/>
<fsummary>Run a function on all nodes, ignoring the result.</fsummary>
<desc>
<p>Equivalent to <c>eval_everywhere([node()|nodes()],
@@ -181,7 +181,7 @@
</func>
<func>
- <name name="eval_everywhere" arity="4"/>
+ <name name="eval_everywhere" arity="4" since=""/>
<fsummary>Run a function on specific nodes, ignoring the
result.</fsummary>
<desc>
@@ -192,7 +192,7 @@
</func>
<func>
- <name name="multi_server_call" arity="2"/>
+ <name name="multi_server_call" arity="2" since=""/>
<fsummary>Interact with the servers on a number of nodes.</fsummary>
<desc>
<p>Equivalent to <c>multi_server_call([node()|nodes()],
@@ -201,7 +201,7 @@
</func>
<func>
- <name name="multi_server_call" arity="3"/>
+ <name name="multi_server_call" arity="3" since=""/>
<fsummary>Interact with the servers on a number of nodes.</fsummary>
<desc>
<p>Can be used when interacting with servers called
@@ -224,7 +224,7 @@
</func>
<func>
- <name name="multicall" arity="3"/>
+ <name name="multicall" arity="3" since=""/>
<fsummary>Evaluate a function call on a number of nodes.</fsummary>
<desc>
<p>Equivalent to <c>multicall([node()|nodes()], <anno>Module</anno>,
@@ -233,7 +233,7 @@
</func>
<func>
- <name name="multicall" arity="4" clause_i="1"/>
+ <name name="multicall" arity="4" clause_i="1" since=""/>
<fsummary>Evaluate a function call on a number of nodes.</fsummary>
<desc>
<p>Equivalent to <c>multicall(<anno>Nodes</anno>, <anno>Module</anno>,
@@ -242,7 +242,7 @@
</func>
<func>
- <name name="multicall" arity="4" clause_i="2"/>
+ <name name="multicall" arity="4" clause_i="2" since=""/>
<fsummary>Evaluate a function call on a number of nodes.</fsummary>
<desc>
<p>Equivalent to <c>multicall([node()|nodes()], <anno>Module</anno>,
@@ -252,7 +252,7 @@
</func>
<func>
- <name name="multicall" arity="5"/>
+ <name name="multicall" arity="5" since=""/>
<fsummary>Evaluate a function call on a number of nodes.</fsummary>
<desc>
<p>In contrast to an RPC, a multicall is an RPC that is sent
@@ -288,7 +288,7 @@
</func>
<func>
- <name name="nb_yield" arity="1"/>
+ <name name="nb_yield" arity="1" since=""/>
<fsummary>Deliver the result of evaluating a function call on a node
(non-blocking).</fsummary>
<desc>
@@ -297,7 +297,7 @@
</func>
<func>
- <name name="nb_yield" arity="2"/>
+ <name name="nb_yield" arity="2" since=""/>
<fsummary>Deliver the result of evaluating a function call on a node
(non-blocking).</fsummary>
<desc>
@@ -315,7 +315,7 @@
</func>
<func>
- <name name="parallel_eval" arity="1"/>
+ <name name="parallel_eval" arity="1" since=""/>
<fsummary>Evaluate many function calls on all nodes in
parallel.</fsummary>
<desc>
@@ -328,7 +328,7 @@
</func>
<func>
- <name name="pinfo" arity="1"/>
+ <name name="pinfo" arity="1" since=""/>
<fsummary>Information about a process.</fsummary>
<desc>
<p>Location transparent version of the BIF
@@ -337,8 +337,8 @@
</func>
<func>
- <name name="pinfo" arity="2" clause_i="1"/>
- <name name="pinfo" arity="2" clause_i="2"/>
+ <name name="pinfo" arity="2" clause_i="1" since=""/>
+ <name name="pinfo" arity="2" clause_i="2" since=""/>
<fsummary>Information about a process.</fsummary>
<desc>
<p>Location transparent version of the BIF
@@ -347,7 +347,7 @@
</func>
<func>
- <name name="pmap" arity="3"/>
+ <name name="pmap" arity="3" since=""/>
<fsummary>Parallel evaluation of mapping a function over a
list.</fsummary>
<desc>
@@ -360,7 +360,7 @@
</func>
<func>
- <name name="sbcast" arity="2"/>
+ <name name="sbcast" arity="2" since=""/>
<fsummary>Broadcast a message synchronously to a registered process on
all nodes.</fsummary>
<desc>
@@ -370,7 +370,7 @@
</func>
<func>
- <name name="sbcast" arity="3"/>
+ <name name="sbcast" arity="3" since=""/>
<fsummary>Broadcast a message synchronously to a registered process on
specific nodes.</fsummary>
<desc>
@@ -391,7 +391,7 @@
</func>
<func>
- <name name="server_call" arity="4"/>
+ <name name="server_call" arity="4" since=""/>
<fsummary>Interact with a server on a node.</fsummary>
<desc>
<p>Can be used when interacting with a server called
@@ -410,7 +410,7 @@
</func>
<func>
- <name name="yield" arity="1"/>
+ <name name="yield" arity="1" since=""/>
<fsummary>Deliver the result of evaluating a function call on a node
(blocking).</fsummary>
<desc>
diff --git a/lib/kernel/doc/src/seq_trace.xml b/lib/kernel/doc/src/seq_trace.xml
index 1a4a74419a..aa29223dd0 100644
--- a/lib/kernel/doc/src/seq_trace.xml
+++ b/lib/kernel/doc/src/seq_trace.xml
@@ -28,7 +28,7 @@
<date>1998-04-16</date>
<rev>A</rev>
</header>
- <module>seq_trace</module>
+ <module since="">seq_trace</module>
<modulesummary>Sequential tracing of messages.</modulesummary>
<description>
<p>Sequential tracing makes it possible to trace all messages
@@ -51,7 +51,7 @@
</datatypes>
<funcs>
<func>
- <name name="set_token" arity="1"/>
+ <name name="set_token" arity="1" since=""/>
<fsummary>Set the trace token</fsummary>
<desc>
<p>Sets the trace token for the calling process to <c><anno>Token</anno></c>.
@@ -71,7 +71,7 @@ seq_trace:set_token(OldToken), % activate the trace token again
</desc>
</func>
<func>
- <name name="set_token" arity="2"/>
+ <name name="set_token" arity="2" since=""/>
<fsummary>Set a component of the trace token</fsummary>
<type name="component"/>
<type name="flag"/>
@@ -158,7 +158,7 @@ seq_trace:set_token(OldToken), % activate the trace token again
</desc>
</func>
<func>
- <name name="get_token" arity="0"/>
+ <name name="get_token" arity="0" since=""/>
<fsummary>Return the value of the trace token</fsummary>
<desc>
<p>Returns the value of the trace token for the calling process.
@@ -169,7 +169,7 @@ seq_trace:set_token(OldToken), % activate the trace token again
</desc>
</func>
<func>
- <name name="get_token" arity="1"/>
+ <name name="get_token" arity="1" since=""/>
<fsummary>Return the value of a trace token component</fsummary>
<type name="component"/>
<type name="flag"/>
@@ -182,7 +182,7 @@ seq_trace:set_token(OldToken), % activate the trace token again
</desc>
</func>
<func>
- <name name="print" arity="1"/>
+ <name name="print" arity="1" since=""/>
<fsummary>Put the Erlang term <c>TraceInfo</c>into the sequential trace output</fsummary>
<desc>
<p>Puts the Erlang term <c><anno>TraceInfo</anno></c> into the sequential
@@ -192,7 +192,7 @@ seq_trace:set_token(OldToken), % activate the trace token again
</desc>
</func>
<func>
- <name name="print" arity="2"/>
+ <name name="print" arity="2" since=""/>
<fsummary>Put the Erlang term <c>TraceInfo</c>into the sequential trace output</fsummary>
<desc>
<p>Same as <c>print/1</c> with the additional condition that
@@ -201,7 +201,7 @@ seq_trace:set_token(OldToken), % activate the trace token again
</desc>
</func>
<func>
- <name name="reset_trace" arity="0"/>
+ <name name="reset_trace" arity="0" since=""/>
<fsummary>Stop all sequential tracing on the local node</fsummary>
<desc>
<p>Sets the trace token to empty for all processes on the
@@ -213,7 +213,7 @@ seq_trace:set_token(OldToken), % activate the trace token again
</desc>
</func>
<func>
- <name name="set_system_tracer" arity="1"/>
+ <name name="set_system_tracer" arity="1" since=""/>
<fsummary>Set the system tracer</fsummary>
<type name="tracer"/>
<desc>
@@ -227,7 +227,7 @@ seq_trace:set_token(OldToken), % activate the trace token again
</desc>
</func>
<func>
- <name name="get_system_tracer" arity="0"/>
+ <name name="get_system_tracer" arity="0" since=""/>
<fsummary>Return the pid() or port() of the current system tracer.</fsummary>
<type name="tracer"/>
<desc>
diff --git a/lib/kernel/doc/src/wrap_log_reader.xml b/lib/kernel/doc/src/wrap_log_reader.xml
index 7fb9c1c023..5f37e7ec5f 100644
--- a/lib/kernel/doc/src/wrap_log_reader.xml
+++ b/lib/kernel/doc/src/wrap_log_reader.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>wrap_log_reader.sgml</file>
</header>
- <module>wrap_log_reader</module>
+ <module since="">wrap_log_reader</module>
<modulesummary>A service to read internally formatted wrap disk logs.
</modulesummary>
<description>
@@ -65,8 +65,8 @@
<funcs>
<func>
- <name name="chunk" arity="1"/>
- <name name="chunk" arity="2"/>
+ <name name="chunk" arity="1" since=""/>
+ <name name="chunk" arity="2" since=""/>
<fsummary>Read a chunk of objects written to a wrap log.</fsummary>
<type name="chunk_ret"/>
<desc>
@@ -105,7 +105,7 @@
</func>
<func>
- <name name="close" arity="1"/>
+ <name name="close" arity="1" since=""/>
<fsummary>Close a log.</fsummary>
<desc>
<p>Closes a log file properly.</p>
@@ -113,8 +113,8 @@
</func>
<func>
- <name name="open" arity="1"/>
- <name name="open" arity="2"/>
+ <name name="open" arity="1" since=""/>
+ <name name="open" arity="2" since=""/>
<fsummary>Open a log file.</fsummary>
<type name="open_ret"/>
<desc>
diff --git a/lib/kernel/include/dist.hrl b/lib/kernel/include/dist.hrl
index 003852f1b0..f06fc328d7 100644
--- a/lib/kernel/include/dist.hrl
+++ b/lib/kernel/include/dist.hrl
@@ -42,6 +42,9 @@
-define(DFLAG_BIG_CREATION, 16#40000).
-define(DFLAG_SEND_SENDER, 16#80000).
-define(DFLAG_BIG_SEQTRACE_LABELS, 16#100000).
+%% -define(DFLAG_NO_MAGIC, 16#200000). %% Used internally only
+-define(DFLAG_EXIT_PAYLOAD, 16#400000).
+-define(DFLAG_FRAGMENTS, 16#800000).
%% Also update dflag2str() in ../src/dist_util.erl
%% when adding flags...
diff --git a/lib/kernel/src/Makefile b/lib/kernel/src/Makefile
index 57f17defc8..43b776f37e 100644
--- a/lib/kernel/src/Makefile
+++ b/lib/kernel/src/Makefile
@@ -118,10 +118,11 @@ MODULES = \
logger_h_common \
logger_filters \
logger_formatter \
+ logger_olp \
+ logger_proxy \
logger_server \
logger_simple_h \
logger_sup \
- net \
net_adm \
net_kernel \
os \
@@ -151,7 +152,7 @@ INTERNAL_HRL_FILES= application_master.hrl disk_log.hrl \
inet_dns.hrl inet_res.hrl \
inet_boot.hrl inet_config.hrl inet_int.hrl \
inet_dns_record_adts.hrl \
- logger_internal.hrl logger_h_common.hrl
+ logger_internal.hrl logger_olp.hrl logger_h_common.hrl
ERL_FILES= $(MODULES:%=%.erl)
@@ -279,6 +280,8 @@ $(EBIN)/logger_config.beam: logger_internal.hrl ../include/logger.hrl
$(EBIN)/logger_disk_log_h.beam: logger_h_common.hrl logger_internal.hrl ../include/logger.hrl ../include/file.hrl
$(EBIN)/logger_filters.beam: logger_internal.hrl ../include/logger.hrl
$(EBIN)/logger_formatter.beam: logger_internal.hrl ../include/logger.hrl
+$(EBIN)/logger_olp.beam: logger_olp.hrl logger_internal.hrl
+$(EBIN)/logger_proxy.beam: logger_internal.hrl
$(EBIN)/logger_server.beam: logger_internal.hrl ../include/logger.hrl
$(EBIN)/logger_simple_h.beam: logger_internal.hrl ../include/logger.hrl
$(EBIN)/logger_std_h.beam: logger_h_common.hrl logger_internal.hrl ../include/logger.hrl ../include/file.hrl
diff --git a/lib/kernel/src/application.erl b/lib/kernel/src/application.erl
index bc6be2f8f5..5c2e981e4b 100644
--- a/lib/kernel/src/application.erl
+++ b/lib/kernel/src/application.erl
@@ -25,7 +25,7 @@
which_applications/0, which_applications/1,
loaded_applications/0, permit/2]).
-export([ensure_started/1, ensure_started/2]).
--export([set_env/3, set_env/4, unset_env/2, unset_env/3]).
+-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]).
@@ -279,6 +279,26 @@ loaded_applications() ->
info() ->
application_controller:info().
+-spec set_env(Config) -> 'ok' when
+ Config :: [{Application, Env}],
+ Application :: atom(),
+ Env :: [{Par :: atom(), Val :: term()}].
+
+set_env(Config) when is_list(Config) ->
+ set_env(Config, []).
+
+-spec set_env(Config, Opts) -> 'ok' when
+ Config :: [{Application, Env}],
+ Application :: atom(),
+ Env :: [{Par :: atom(), Val :: term()}],
+ Opts :: [{timeout, timeout()} | {persistent, boolean()}].
+
+set_env(Config, Opts) when is_list(Config), is_list(Opts) ->
+ case application_controller:set_env(Config, Opts) of
+ ok -> ok;
+ {error, Msg} -> erlang:error({badarg, Msg}, [Config, Opts])
+ end.
+
-spec set_env(Application, Par, Val) -> 'ok' when
Application :: atom(),
Par :: atom(),
diff --git a/lib/kernel/src/application_controller.erl b/lib/kernel/src/application_controller.erl
index a074d2e74b..9a8091fb2e 100644
--- a/lib/kernel/src/application_controller.erl
+++ b/lib/kernel/src/application_controller.erl
@@ -26,7 +26,7 @@
control_application/1,
change_application_data/2, prep_config_change/0, config_change/1,
which_applications/0, which_applications/1,
- loaded_applications/0, info/0,
+ 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_key/2, get_key/2, get_pid_all_key/1, get_all_key/1,
get_master/1, get_application/1, get_application_module/1,
@@ -345,9 +345,6 @@ get_all_env(AppName) ->
map(fun([Key, Val]) -> {Key, Val} end,
ets:match(ac_tab, {{env, AppName, '$1'}, '$2'})).
-
-
-
get_pid_key(Master, Key) ->
case ets:match(ac_tab, {{application_master, '$1'}, Master}) of
[[AppName]] -> get_key(AppName, Key);
@@ -461,6 +458,15 @@ permit_application(ApplName, Flag) ->
{permit_application, ApplName, Flag},
infinity).
+set_env(Config, Opts) ->
+ case check_conf_data(Config) of
+ ok ->
+ Timeout = proplists:get_value(timeout, Opts, 5000),
+ gen_server:call(?AC, {set_env, Config, Opts}, Timeout);
+
+ {error, _} = Error ->
+ Error
+ end.
set_env(AppName, Key, Val) ->
gen_server:call(?AC, {set_env, AppName, Key, Val, []}).
@@ -528,19 +534,17 @@ check_conf_data([]) ->
check_conf_data(ConfData) when is_list(ConfData) ->
[Application | ConfDataRem] = ConfData,
case Application of
- {kernel, List} when is_list(List) ->
- case check_para_kernel(List) of
- ok ->
- check_conf_data(ConfDataRem);
- Error1 ->
- Error1
- end;
{AppName, List} when is_atom(AppName), is_list(List) ->
- case check_para(List, atom_to_list(AppName)) of
- ok ->
- check_conf_data(ConfDataRem);
- Error2 ->
- Error2
+ case lists:keymember(AppName, 1, ConfDataRem) of
+ true ->
+ ?LOG_WARNING("duplicate application config: " ++ atom_to_list(AppName));
+ false ->
+ ok
+ end,
+
+ case check_para(List, AppName) of
+ ok -> check_conf_data(ConfDataRem);
+ Error -> Error
end;
{AppName, List} when is_list(List) ->
ErrMsg = "application: "
@@ -553,36 +557,40 @@ check_conf_data(ConfData) when is_list(ConfData) ->
++ "; parameters must be a list",
{error, ErrMsg};
Else ->
- ErrMsg = "invalid application name: " ++
- lists:flatten(io_lib:format(" ~tp",[Else])),
+ ErrMsg = "invalid application config: "
+ ++ lists:flatten(io_lib:format("~tp",[Else])),
{error, ErrMsg}
end;
check_conf_data(_ConfData) ->
- {error, 'configuration must be a list ended by <dot><whitespace>'}.
-
+ {error, "configuration must be a list ended by <dot><whitespace>"}.
-%% Special check of distributed parameter for kernel
-check_para_kernel([]) ->
+
+check_para([], _AppName) ->
ok;
-check_para_kernel([{distributed, Apps} | ParaList]) when is_list(Apps) ->
- case check_distributed(Apps) of
- {error, _ErrorMsg} = Error ->
- Error;
- _ ->
- check_para_kernel(ParaList)
+check_para([{Para, Val} | ParaList], AppName) when is_atom(Para) ->
+ case lists:keymember(Para, 1, ParaList) of
+ true ->
+ ?LOG_WARNING("application: " ++ atom_to_list(AppName) ++
+ "; duplicate parameter: " ++ atom_to_list(Para));
+ false ->
+ ok
+ end,
+
+ case check_para_value(Para, Val, AppName) of
+ ok -> check_para(ParaList, AppName);
+ {error, _} = Error -> Error
end;
-check_para_kernel([{distributed, _Apps} | _ParaList]) ->
- {error, "application: kernel; erroneous parameter: distributed"};
-check_para_kernel([{Para, _Val} | ParaList]) when is_atom(Para) ->
- check_para_kernel(ParaList);
-check_para_kernel([{Para, _Val} | _ParaList]) ->
- {error, "application: kernel; invalid parameter: " ++
+check_para([{Para, _Val} | _ParaList], AppName) ->
+ {error, "application: " ++ atom_to_list(AppName) ++ "; invalid parameter name: " ++
lists:flatten(io_lib:format("~tp",[Para]))};
-check_para_kernel(Else) ->
- {error, "application: kernel; invalid parameter list: " ++
+check_para([Else | _ParaList], AppName) ->
+ {error, "application: " ++ atom_to_list(AppName) ++ "; invalid parameter: " ++
lists:flatten(io_lib:format("~tp",[Else]))}.
-
+check_para_value(distributed, Apps, kernel) -> check_distributed(Apps);
+check_para_value(_Para, _Val, _AppName) -> ok.
+
+%% Special check of distributed parameter for kernel
check_distributed([]) ->
ok;
check_distributed([{App, List} | Apps]) when is_atom(App), is_list(List) ->
@@ -595,18 +603,6 @@ check_distributed(_Else) ->
{error, "application: kernel; erroneous parameter: distributed"}.
-check_para([], _AppName) ->
- ok;
-check_para([{Para, _Val} | ParaList], AppName) when is_atom(Para) ->
- check_para(ParaList, AppName);
-check_para([{Para, _Val} | _ParaList], AppName) ->
- {error, "application: " ++ AppName ++ "; invalid parameter: " ++
- lists:flatten(io_lib:format("~tp",[Para]))};
-check_para([Else | _ParaList], AppName) ->
- {error, "application: " ++ AppName ++ "; invalid parameter: " ++
- lists:flatten(io_lib:format("~tp",[Else]))}.
-
-
-type calls() :: 'info' | 'prep_config_change' | 'which_applications'
| {'config_change' | 'control_application' |
'load_application' | 'start_type' | 'stop_application' |
@@ -863,6 +859,16 @@ handle_call(which_applications, _From, S) ->
end, S#state.running),
{reply, Reply, S};
+handle_call({set_env, Config, Opts}, _From, S) ->
+ _ = [add_env(AppName, Env) || {AppName, Env} <- Config],
+
+ case proplists:get_value(persistent, Opts, false) of
+ true ->
+ {reply, ok, S#state{conf_data = merge_env(S#state.conf_data, Config)}};
+ false ->
+ {reply, ok, S}
+ end;
+
handle_call({set_env, AppName, Key, Val, Opts}, _From, S) ->
ets:insert(ac_tab, {{env, AppName, Key}, Val}),
case proplists:get_value(persistent, Opts, false) of
diff --git a/lib/kernel/src/code_server.erl b/lib/kernel/src/code_server.erl
index 1b4a67ecb7..68e1205301 100644
--- a/lib/kernel/src/code_server.erl
+++ b/lib/kernel/src/code_server.erl
@@ -1434,19 +1434,25 @@ all_loaded(Db) ->
-spec error_msg(io:format(), [term()]) -> 'ok'.
error_msg(Format, Args) ->
+ %% This is equal to calling logger:error/3 which we don't want to
+ %% do from code_server. We don't want to call logger:timestamp()
+ %% either.
logger ! {log,error,Format,Args,
#{pid=>self(),
gl=>group_leader(),
- time=>erlang:system_time(microsecond),
+ time=>os:system_time(microsecond),
error_logger=>#{tag=>error}}},
ok.
-spec info_msg(io:format(), [term()]) -> 'ok'.
info_msg(Format, Args) ->
+ %% This is equal to calling logger:info/3 which we don't want to
+ %% do from code_server. We don't want to call logger:timestamp()
+ %% either.
logger ! {log,info,Format,Args,
#{pid=>self(),
gl=>group_leader(),
- time=>erlang:system_time(microsecond),
+ time=>os:system_time(microsecond),
error_logger=>#{tag=>info_msg}}},
ok.
diff --git a/lib/kernel/src/dist_util.erl b/lib/kernel/src/dist_util.erl
index ecc022b28d..09ed31f10c 100644
--- a/lib/kernel/src/dist_util.erl
+++ b/lib/kernel/src/dist_util.erl
@@ -116,6 +116,10 @@ dflag2str(?DFLAG_SEND_SENDER) ->
"SEND_SENDER";
dflag2str(?DFLAG_BIG_SEQTRACE_LABELS) ->
"BIG_SEQTRACE_LABELS";
+dflag2str(?DFLAG_EXIT_PAYLOAD) ->
+ "EXIT_PAYLOAD";
+dflag2str(?DFLAG_FRAGMENTS) ->
+ "FRAGMENTS";
dflag2str(_) ->
"UNKNOWN".
diff --git a/lib/kernel/src/erl_epmd.erl b/lib/kernel/src/erl_epmd.erl
index b7e8868911..7a14e2635c 100644
--- a/lib/kernel/src/erl_epmd.erl
+++ b/lib/kernel/src/erl_epmd.erl
@@ -77,8 +77,8 @@ stop() ->
%%
-spec port_please(Name, Host) -> {ok, Port, Version} | noport when
- Name :: string(),
- Host :: inet:ip_address(),
+ Name :: atom() | string(),
+ Host :: atom() | string() | inet:ip_address(),
Port :: non_neg_integer(),
Version :: non_neg_integer().
@@ -86,8 +86,8 @@ port_please(Node, Host) ->
port_please(Node, Host, infinity).
-spec port_please(Name, Host, Timeout) -> {ok, Port, Version} | noport when
- Name :: string(),
- Host :: inet:ip_address(),
+ Name :: atom() | string(),
+ Host :: atom() | string() | inet:ip_address(),
Timeout :: non_neg_integer() | infinity,
Port :: non_neg_integer(),
Version :: non_neg_integer().
diff --git a/lib/kernel/src/erts_debug.erl b/lib/kernel/src/erts_debug.erl
index 1270de4144..c4d276f9e8 100644
--- a/lib/kernel/src/erts_debug.erl
+++ b/lib/kernel/src/erts_debug.erl
@@ -36,7 +36,8 @@
map_info/1, same/2, set_internal_state/2,
size_shared/1, copy_shared/1, dirty_cpu/2, dirty_io/2, dirty/3,
lcnt_control/1, lcnt_control/2, lcnt_collect/0, lcnt_clear/0,
- lc_graph/0, lc_graph_to_dot/2, lc_graph_merge/2]).
+ lc_graph/0, lc_graph_to_dot/2, lc_graph_merge/2,
+ alloc_blocks_size/1]).
-spec breakpoint(MFA, Flag) -> non_neg_integer() when
MFA :: {Module :: module(),
@@ -495,3 +496,58 @@ lcg_print_locks(Out, [LastLock]) ->
lcg_print_locks(Out, [Lock | Rest]) ->
io:format(Out, "~w,\n", [Lock]),
lcg_print_locks(Out, Rest).
+
+
+%% Returns the amount of memory allocated by the given allocator type.
+-spec alloc_blocks_size(Type) -> non_neg_integer() | undefined when
+ Type :: atom().
+
+alloc_blocks_size(Type) ->
+ Allocs = erlang:system_info(alloc_util_allocators),
+ Sizes = erlang:system_info({allocator_sizes, Allocs}),
+ alloc_blocks_size_1(Sizes, Type, 0).
+
+alloc_blocks_size_1([], _Type, 0) ->
+ undefined;
+alloc_blocks_size_1([{_Type, false} | Rest], Type, Acc) ->
+ alloc_blocks_size_1(Rest, Type, Acc);
+alloc_blocks_size_1([{Type, Instances} | Rest], Type, Acc0) ->
+ F = fun ({instance, _, L}, Acc) ->
+ MBCSPool = case lists:keyfind(mbcs_pool, 1, L) of
+ {_, Pool} -> Pool;
+ false -> []
+ end,
+ {_,MBCS} = lists:keyfind(mbcs, 1, L),
+ {_,SBCS} = lists:keyfind(sbcs, 1, L),
+ Acc +
+ sum_block_sizes(MBCSPool) +
+ sum_block_sizes(MBCS) +
+ sum_block_sizes(SBCS)
+ end,
+ alloc_blocks_size_1(Rest, Type, lists:foldl(F, Acc0, Instances));
+alloc_blocks_size_1([{_Type, Instances} | Rest], Type, Acc0) ->
+ F = fun ({instance, _, L}, Acc) ->
+ Acc + sum_foreign_sizes(Type, L)
+ end,
+ alloc_blocks_size_1(Rest, Type, lists:foldl(F, Acc0, Instances));
+alloc_blocks_size_1([], _Type, Acc) ->
+ Acc.
+
+sum_foreign_sizes(Type, L) ->
+ case lists:keyfind(mbcs_pool, 1, L) of
+ {_,Pool} ->
+ {_,ForeignBlocks} = lists:keyfind(foreign_blocks, 1, Pool),
+ case lists:keyfind(Type, 1, ForeignBlocks) of
+ {_,TypeSizes} -> sum_block_sizes(TypeSizes);
+ false -> 0
+ end;
+ _ ->
+ 0
+ end.
+
+sum_block_sizes(Blocks) ->
+ lists:foldl(
+ fun({blocks_size, Sz,_,_}, Sz0) -> Sz0+Sz;
+ ({blocks_size, Sz}, Sz0) -> Sz0+Sz;
+ (_, Sz) -> Sz
+ end, 0, Blocks).
diff --git a/lib/kernel/src/gen_tcp.erl b/lib/kernel/src/gen_tcp.erl
index 7f7833ec23..5d4764f8ff 100644
--- a/lib/kernel/src/gen_tcp.erl
+++ b/lib/kernel/src/gen_tcp.erl
@@ -156,7 +156,7 @@ connect(Address, Port, Opts) ->
Options :: [connect_option()],
Timeout :: timeout(),
Socket :: socket(),
- Reason :: inet:posix().
+ Reason :: timeout | inet:posix().
connect(Address, Port, Opts, Time) ->
Timer = inet:start_timer(Time),
@@ -220,7 +220,7 @@ listen(Port, Opts0) ->
-spec accept(ListenSocket) -> {ok, Socket} | {error, Reason} when
ListenSocket :: socket(),
Socket :: socket(),
- Reason :: closed | timeout | system_limit | inet:posix().
+ Reason :: closed | system_limit | inet:posix().
accept(S) ->
case inet_db:lookup_socket(S) of
@@ -312,7 +312,7 @@ recv(S, Length) when is_port(S) ->
Length :: non_neg_integer(),
Timeout :: timeout(),
Packet :: string() | binary() | HttpPacket,
- Reason :: closed | inet:posix(),
+ Reason :: closed | timeout | inet:posix(),
HttpPacket :: term().
recv(S, Length, Time) when is_port(S) ->
diff --git a/lib/kernel/src/gen_udp.erl b/lib/kernel/src/gen_udp.erl
index d6e8652e77..fad7b2f887 100644
--- a/lib/kernel/src/gen_udp.erl
+++ b/lib/kernel/src/gen_udp.erl
@@ -95,7 +95,7 @@
-spec open(Port) -> {ok, Socket} | {error, Reason} when
Port :: inet:port_number(),
Socket :: socket(),
- Reason :: inet:posix().
+ Reason :: system_limit | inet:posix().
open(Port) ->
open(Port, []).
@@ -112,7 +112,7 @@ open(Port) ->
| {bind_to_device, binary()}
| option(),
Socket :: socket(),
- Reason :: inet:posix().
+ Reason :: system_limit | inet:posix().
open(Port, Opts0) ->
{Mod, Opts} = inet:udp_module(Opts0),
@@ -186,7 +186,7 @@ recv(S,Len) when is_port(S), is_integer(Len) ->
Port :: inet:port_number(),
AncData :: inet:ancillary_data(),
Packet :: string() | binary(),
- Reason :: not_owner | inet:posix().
+ Reason :: not_owner | timeout | inet:posix().
recv(S,Len,Time) when is_port(S) ->
case inet_db:lookup_socket(S) of
diff --git a/lib/kernel/src/inet_config.erl b/lib/kernel/src/inet_config.erl
index 9f76360b8b..e771461b65 100644
--- a/lib/kernel/src/inet_config.erl
+++ b/lib/kernel/src/inet_config.erl
@@ -98,7 +98,7 @@ init() ->
{win32,WinType} ->
win32_load_from_registry(WinType);
_ ->
- error("can not read win32 system registry~n", [])
+ error("cannot read win32 system registry~n", [])
end
end, CfgFiles),
diff --git a/lib/kernel/src/inet_db.erl b/lib/kernel/src/inet_db.erl
index 6cbb6ac2da..3f5a2ea5ee 100644
--- a/lib/kernel/src/inet_db.erl
+++ b/lib/kernel/src/inet_db.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -1223,7 +1223,10 @@ handle_set_file(Option, Fname, TagTm, TagInfo, ParseFun, From,
{ok, B, _} -> B;
_ -> <<>>
end;
- _ -> <<>>
+ _ ->
+ ets:insert(Db, {TagInfo, undefined}),
+ TimeZero = - (?RES_FILE_UPDATE_TM + 1), % Early enough
+ ets:insert(Db, {TagTm, TimeZero})
end,
handle_set_file(ParseFun, Bin, From, State);
false -> {reply,error,State}
diff --git a/lib/kernel/src/inet_dns.erl b/lib/kernel/src/inet_dns.erl
index f1f58bc872..6c98d2aab7 100644
--- a/lib/kernel/src/inet_dns.erl
+++ b/lib/kernel/src/inet_dns.erl
@@ -699,7 +699,7 @@ encode_labels(Bin, Comp0, Pos, [L|Ls]=Labels)
none ->
Comp = if Pos < (3 bsl 14) ->
%% Just in case - compression
- %% pointers can not reach further
+ %% pointers cannot reach further
gb_trees:insert(Labels, Pos, Comp0);
true -> Comp0
end,
diff --git a/lib/kernel/src/inet_tcp_dist.erl b/lib/kernel/src/inet_tcp_dist.erl
index c37212b0f9..c5a114a9ef 100644
--- a/lib/kernel/src/inet_tcp_dist.erl
+++ b/lib/kernel/src/inet_tcp_dist.erl
@@ -212,6 +212,7 @@ do_accept(Driver, Kernel, AcceptPid, Socket, MyNode, Allowed, SetupTime) ->
[{active, true},
{deliver, port},
{packet, 4},
+ binary,
nodelay()])
end,
f_getll = fun(S) ->
diff --git a/lib/kernel/src/kernel.app.src b/lib/kernel/src/kernel.app.src
index fe073621c8..4b48f6cd1d 100644
--- a/lib/kernel/src/kernel.app.src
+++ b/lib/kernel/src/kernel.app.src
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All 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,11 +68,12 @@
logger_formatter,
logger_h_common,
logger_handler_watcher,
+ logger_olp,
+ logger_proxy,
logger_server,
logger_simple_h,
logger_std_h,
logger_sup,
- net,
net_adm,
net_kernel,
os,
diff --git a/lib/kernel/src/logger.erl b/lib/kernel/src/logger.erl
index 6762998d4f..38bd2f481c 100644
--- a/lib/kernel/src/logger.erl
+++ b/lib/kernel/src/logger.erl
@@ -43,11 +43,14 @@
get_module_level/0, get_module_level/1,
set_primary_config/1, set_primary_config/2,
set_handler_config/2, set_handler_config/3,
+ set_proxy_config/1,
update_primary_config/1,
update_handler_config/2, update_handler_config/3,
+ update_proxy_config/1,
update_formatter_config/2, update_formatter_config/3,
get_primary_config/0, get_handler_config/1,
get_handler_config/0, get_handler_ids/0, get_config/0,
+ get_proxy_config/0,
add_handlers/1]).
%% Private configuration
@@ -57,6 +60,8 @@
-export([compare_levels/2]).
-export([set_process_metadata/1, update_process_metadata/1,
unset_process_metadata/0, get_process_metadata/0]).
+-export([i/0, i/1]).
+-export([timestamp/0]).
%% Basic report formatting
-export([format_report/1, format_otp_report/1]).
@@ -122,6 +127,18 @@
{filters,log | stop,[{filter_id(),filter()}]} |
{module_level,level(),[module()]}].
+-type olp_config() :: #{sync_mode_qlen => non_neg_integer(),
+ drop_mode_qlen => pos_integer(),
+ flush_qlen => pos_integer(),
+ burst_limit_enable => boolean(),
+ burst_limit_max_count => pos_integer(),
+ burst_limit_window_time => pos_integer(),
+ overload_kill_enable => boolean(),
+ overload_kill_qlen => pos_integer(),
+ overload_kill_mem_size => pos_integer(),
+ overload_kill_restart_after =>
+ non_neg_integer() | infinity}.
+
-export_type([log_event/0,
level/0,
report/0,
@@ -137,7 +154,9 @@
filter_arg/0,
filter_return/0,
config_handler/0,
- formatter_config/0]).
+ formatter_config/0,
+ olp_config/0,
+ timestamp/0]).
%%%-----------------------------------------------------------------
%%% API
@@ -337,6 +356,10 @@ internal_log(Level,Term) when is_atom(Level) ->
erlang:display_string("Logger - "++ atom_to_list(Level) ++ ": "),
erlang:display(Term).
+-spec timestamp() -> timestamp().
+timestamp() ->
+ os:system_time(microsecond).
+
%%%-----------------------------------------------------------------
%%% Configuration
-spec add_primary_filter(FilterId,Filter) -> ok | {error,term()} when
@@ -390,6 +413,7 @@ set_primary_config(Key,Value) ->
set_primary_config(Config) ->
logger_server:set_config(primary,Config).
+
-spec set_handler_config(HandlerId,level,Level) -> Return when
HandlerId :: handler_id(),
Level :: level() | all | none,
@@ -419,6 +443,11 @@ set_handler_config(HandlerId,Key,Value) ->
set_handler_config(HandlerId,Config) ->
logger_server:set_config(HandlerId,Config).
+-spec set_proxy_config(Config) -> ok | {error,term()} when
+ Config :: olp_config().
+set_proxy_config(Config) ->
+ logger_server:set_config(proxy,Config).
+
-spec update_primary_config(Config) -> ok | {error,term()} when
Config :: primary_config().
update_primary_config(Config) ->
@@ -453,6 +482,11 @@ update_handler_config(HandlerId,Key,Value) ->
update_handler_config(HandlerId,Config) ->
logger_server:update_config(HandlerId,Config).
+-spec update_proxy_config(Config) -> ok | {error,term()} when
+ Config :: olp_config().
+update_proxy_config(Config) ->
+ logger_server:update_config(proxy,Config).
+
-spec get_primary_config() -> Config when
Config :: primary_config().
get_primary_config() ->
@@ -486,6 +520,12 @@ get_handler_ids() ->
{ok,#{handlers:=HandlerIds}} = logger_config:get(?LOGGER_TABLE,primary),
HandlerIds.
+-spec get_proxy_config() -> Config when
+ Config :: olp_config().
+get_proxy_config() ->
+ {ok,Config} = logger_config:get(?LOGGER_TABLE,proxy),
+ Config.
+
-spec update_formatter_config(HandlerId,FormatterConfig) ->
ok | {error,term()} when
HandlerId :: handler_id(),
@@ -606,12 +646,150 @@ unset_process_metadata() ->
-spec get_config() -> #{primary=>primary_config(),
handlers=>[handler_config()],
+ proxy=>olp_config(),
module_levels=>[{module(),level() | all | none}]}.
get_config() ->
#{primary=>get_primary_config(),
handlers=>get_handler_config(),
+ proxy=>get_proxy_config(),
module_levels=>lists:keysort(1,get_module_level())}.
+-spec i() -> ok.
+i() ->
+ #{primary := Primary,
+ handlers := HandlerConfigs,
+ proxy := Proxy,
+ module_levels := Modules} = get_config(),
+ M = modifier(),
+ i_primary(Primary,M),
+ i_handlers(HandlerConfigs,M),
+ i_proxy(Proxy,M),
+ i_modules(Modules,M).
+
+-spec i(What) -> ok when
+ What :: primary | handlers | proxy | modules | handler_id().
+i(primary) ->
+ i_primary(get_primary_config(),modifier());
+i(handlers) ->
+ i_handlers(get_handler_config(),modifier());
+i(proxy) ->
+ i_proxy(get_proxy_config(),modifier());
+i(modules) ->
+ i_modules(get_module_level(),modifier());
+i(HandlerId) when is_atom(HandlerId) ->
+ case get_handler_config(HandlerId) of
+ {ok,HandlerConfig} ->
+ i_handlers([HandlerConfig],modifier());
+ Error ->
+ Error
+ end;
+i(What) ->
+ erlang:error(badarg,[What]).
+
+
+i_primary(#{level := Level,
+ filters := Filters,
+ filter_default := FilterDefault},
+ M) ->
+ io:format("Primary configuration: ~n",[]),
+ io:format(" Level: ~p~n",[Level]),
+ io:format(" Filter Default: ~p~n", [FilterDefault]),
+ io:format(" Filters: ~n", []),
+ print_filters(" ",Filters,M).
+
+i_handlers(HandlerConfigs,M) ->
+ io:format("Handler configuration: ~n", []),
+ print_handlers(HandlerConfigs,M).
+
+i_proxy(Proxy,M) ->
+ io:format("Proxy configuration: ~n", []),
+ print_custom(" ",Proxy,M).
+
+i_modules(Modules,M) ->
+ io:format("Level set per module: ~n", []),
+ print_module_levels(Modules,M).
+
+encoding() ->
+ case lists:keyfind(encoding, 1, io:getopts()) of
+ false -> latin1;
+ {encoding, Enc} -> Enc
+ end.
+
+modifier() ->
+ modifier(encoding()).
+
+modifier(latin1) -> "";
+modifier(_) -> "t".
+
+print_filters(Indent, {Id, {Fun, Arg}}, M) ->
+ io:format("~sId: ~"++M++"p~n"
+ "~s Fun: ~"++M++"p~n"
+ "~s Arg: ~"++M++"p~n",
+ [Indent, Id, Indent, Fun, Indent, Arg]);
+print_filters(Indent,[],_M) ->
+ io:format("~s(none)~n",[Indent]);
+print_filters(Indent,Filters,M) ->
+ [print_filters(Indent,Filter,M) || Filter <- Filters],
+ ok.
+
+print_handlers(#{id := Id,
+ module := Module,
+ level := Level,
+ filters := Filters, filter_default := FilterDefault,
+ formatter := {FormatterModule,FormatterConfig}} = Config, M) ->
+ io:format(" Id: ~"++M++"p~n"
+ " Module: ~p~n"
+ " Level: ~p~n"
+ " Formatter:~n"
+ " Module: ~p~n"
+ " Config:~n",
+ [Id, Module, Level, FormatterModule]),
+ print_custom(" ",FormatterConfig,M),
+ io:format(" Filter Default: ~p~n"
+ " Filters:~n",
+ [FilterDefault]),
+ print_filters(" ",Filters,M),
+ case maps:find(config,Config) of
+ {ok,HandlerConfig} ->
+ io:format(" Handler Config:~n"),
+ print_custom(" ",HandlerConfig,M);
+ error ->
+ ok
+ end,
+ MyKeys = [filter_default, filters, formatter, level, module, id, config],
+ case maps:without(MyKeys,Config) of
+ Empty when Empty==#{} ->
+ ok;
+ Unhandled ->
+ io:format(" Custom Config:~n"),
+ print_custom(" ",Unhandled,M)
+ end;
+print_handlers([], _M) ->
+ io:format(" (none)~n");
+print_handlers(HandlerConfigs, M) ->
+ [print_handlers(HandlerConfig, M) || HandlerConfig <- HandlerConfigs],
+ ok.
+
+print_custom(Indent, {Key, Value}, M) ->
+ io:format("~s~"++M++"p: ~"++M++"p~n",[Indent,Key,Value]);
+print_custom(Indent, Map, M) when is_map(Map) ->
+ print_custom(Indent,lists:keysort(1,maps:to_list(Map)), M);
+print_custom(Indent, List, M) when is_list(List), is_tuple(hd(List)) ->
+ [print_custom(Indent, X, M) || X <- List],
+ ok;
+print_custom(Indent, Value, M) ->
+ io:format("~s~"++M++"p~n",[Indent,Value]).
+
+print_module_levels({Module,Level},M) ->
+ io:format(" Module: ~"++M++"p~n"
+ " Level: ~p~n",
+ [Module,Level]);
+print_module_levels([],_M) ->
+ io:format(" (none)~n");
+print_module_levels(Modules,M) ->
+ [print_module_levels(Module,M) || Module <- Modules],
+ ok.
+
-spec internal_init_logger() -> ok | {error,term()}.
%% This function is responsible for config of the logger
%% This is done before add_handlers because we want the
@@ -672,6 +850,17 @@ init_kernel_handlers(Env) ->
%% This function is responsible for resolving the handler config
%% and then starting the correct handlers. This is done after the
%% kernel supervisor tree has been started as it needs the logger_sup.
+add_handlers(kernel) ->
+ Env = get_logger_env(kernel),
+ case get_proxy_opts(Env) of
+ undefined ->
+ add_handlers(kernel,Env);
+ Opts ->
+ case set_proxy_config(Opts) of
+ ok -> add_handlers(kernel,Env);
+ {error, Reason} -> {error,{bad_proxy_config,Reason}}
+ end
+ end;
add_handlers(App) when is_atom(App) ->
add_handlers(App,get_logger_env(App));
add_handlers(HandlerConfig) ->
@@ -729,6 +918,8 @@ check_logger_config(kernel,[{filters,_,_}|Env]) ->
check_logger_config(kernel,Env);
check_logger_config(kernel,[{module_level,_,_}|Env]) ->
check_logger_config(kernel,Env);
+check_logger_config(kernel,[{proxy,_}|Env]) ->
+ check_logger_config(kernel,Env);
check_logger_config(_,Bad) ->
throw(Bad).
@@ -784,6 +975,13 @@ get_primary_filters(Env) ->
_ -> throw({multiple_filters,Env})
end.
+get_proxy_opts(Env) ->
+ case [P || P={proxy,_} <- Env] of
+ [{proxy,Opts}] -> Opts;
+ [] -> undefined;
+ _ -> throw({multiple_proxies,Env})
+ end.
+
%% This function looks at the kernel logger environment
%% and updates it so that the correct logger is configured
init_default_config(Type,Env) when Type==standard_io;
@@ -880,30 +1078,30 @@ log_allowed(Location,Level,Msg,Meta0) when is_map(Meta0) ->
maps:merge(Location,maps:merge(proc_meta(),Meta0))),
case node(maps:get(gl,Meta)) of
Node when Node=/=node() ->
- log_remote(Node,Level,Msg,Meta),
- do_log_allowed(Level,Msg,Meta);
+ log_remote(Node,Level,Msg,Meta);
_ ->
- do_log_allowed(Level,Msg,Meta)
- end.
+ ok
+ end,
+ do_log_allowed(Level,Msg,Meta,tid()).
-do_log_allowed(Level,{Format,Args}=Msg,Meta)
+do_log_allowed(Level,{Format,Args}=Msg,Meta,Tid)
when ?IS_LEVEL(Level),
is_list(Format),
is_list(Args),
is_map(Meta) ->
- logger_backend:log_allowed(#{level=>Level,msg=>Msg,meta=>Meta},tid());
-do_log_allowed(Level,Report,Meta)
+ logger_backend:log_allowed(#{level=>Level,msg=>Msg,meta=>Meta},Tid);
+do_log_allowed(Level,Report,Meta,Tid)
when ?IS_LEVEL(Level),
?IS_REPORT(Report),
is_map(Meta) ->
logger_backend:log_allowed(#{level=>Level,msg=>{report,Report},meta=>Meta},
- tid());
-do_log_allowed(Level,String,Meta)
+ Tid);
+do_log_allowed(Level,String,Meta,Tid)
when ?IS_LEVEL(Level),
?IS_STRING(String),
is_map(Meta) ->
logger_backend:log_allowed(#{level=>Level,msg=>{string,String},meta=>Meta},
- tid()).
+ Tid).
tid() ->
ets:whereis(?LOGGER_TABLE).
@@ -913,7 +1111,7 @@ log_remote(Node,Level,Msg,Meta) ->
log_remote(Node,{log,Level,Msg,Meta}).
log_remote(Node,Request) ->
- {logger,Node} ! Request,
+ logger_proxy:log({remote,Node,Request}),
ok.
add_default_metadata(Meta) ->
@@ -937,7 +1135,7 @@ proc_meta() ->
default(pid) -> self();
default(gl) -> group_leader();
-default(time) -> erlang:system_time(microsecond).
+default(time) -> timestamp().
%% Remove everything upto and including this module from the stacktrace
filter_stacktrace(Module,[{Module,_,_,_}|_]) ->
diff --git a/lib/kernel/src/logger_config.erl b/lib/kernel/src/logger_config.erl
index 5e9faf332c..5024d20cfe 100644
--- a/lib/kernel/src/logger_config.erl
+++ b/lib/kernel/src/logger_config.erl
@@ -66,6 +66,8 @@ get(Tid,What) ->
case ets:lookup(Tid,table_key(What)) of
[{_,_,Config}] ->
{ok,Config};
+ [{_,Config}] when What=:=proxy ->
+ {ok,Config};
[] ->
{error,{not_found,What}}
end.
@@ -79,10 +81,15 @@ get(Tid,What,Level) ->
[Data] -> {ok,Data}
end.
+create(Tid,proxy,Config) ->
+ ets:insert(Tid,{table_key(proxy),Config});
create(Tid,What,Config) ->
LevelInt = level_to_int(maps:get(level,Config)),
ets:insert(Tid,{table_key(What),LevelInt,Config}).
+set(Tid,proxy,Config) ->
+ ets:insert(Tid,{table_key(proxy),Config}),
+ ok;
set(Tid,What,Config) ->
LevelInt = level_to_int(maps:get(level,Config)),
%% Should do this only if the level has actually changed. Possibly
@@ -148,5 +155,6 @@ int_to_level(?LOG_ALL) -> all.
%%%-----------------------------------------------------------------
%%% Internal
+table_key(proxy) -> ?PROXY_KEY;
table_key(primary) -> ?PRIMARY_KEY;
table_key(HandlerId) -> {?HANDLER_KEY,HandlerId}.
diff --git a/lib/kernel/src/logger_disk_log_h.erl b/lib/kernel/src/logger_disk_log_h.erl
index 41e0d51a9d..47b39da900 100644
--- a/lib/kernel/src/logger_disk_log_h.erl
+++ b/lib/kernel/src/logger_disk_log_h.erl
@@ -24,7 +24,7 @@
-include("logger_h_common.hrl").
%%% API
--export([info/1, filesync/1, reset/1]).
+-export([filesync/1]).
%% logger_h_common callbacks
-export([init/2, check_config/4, reset_state/2,
@@ -47,25 +47,6 @@
filesync(Name) ->
logger_h_common:filesync(?MODULE,Name).
-%%%-----------------------------------------------------------------
-%%%
--spec info(Name) -> Info | {error,Reason} when
- Name :: atom(),
- Info :: term(),
- Reason :: handler_busy | {badarg,term()}.
-
-info(Name) ->
- logger_h_common:info(?MODULE,Name).
-
-%%%-----------------------------------------------------------------
-%%%
--spec reset(Name) -> ok | {error,Reason} when
- Name :: atom(),
- Reason :: handler_busy | {badarg,term()}.
-
-reset(Name) ->
- logger_h_common:reset(?MODULE,Name).
-
%%%===================================================================
%%% logger callbacks
%%%===================================================================
diff --git a/lib/kernel/src/logger_h_common.erl b/lib/kernel/src/logger_h_common.erl
index 74a2d158fc..4b5e0a7dd0 100644
--- a/lib/kernel/src/logger_h_common.erl
+++ b/lib/kernel/src/logger_h_common.erl
@@ -24,11 +24,11 @@
-include("logger_internal.hrl").
%% API
--export([start_link/1, info/2, filesync/2, reset/2]).
+-export([filesync/2]).
-%% gen_server and proc_lib callbacks
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+%% logger_olp callbacks
+-export([init/1, handle_load/2, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3, notify/2, reset_state/1]).
%% logger callbacks
-export([log/2, adding_handler/1, removing_handler/1, changing_config/3,
@@ -37,52 +37,45 @@
%% Library functions for handlers
-export([error_notify/1]).
-%%%-----------------------------------------------------------------
--define(CONFIG_KEYS,[sync_mode_qlen,
- drop_mode_qlen,
- flush_qlen,
- burst_limit_enable,
- burst_limit_max_count,
- burst_limit_window_time,
- overload_kill_enable,
- overload_kill_qlen,
- overload_kill_mem_size,
- overload_kill_restart_after,
- filesync_repeat_interval]).
--define(READ_ONLY_KEYS,[handler_pid,mode_tab]).
+-define(OLP_KEYS,[sync_mode_qlen,
+ drop_mode_qlen,
+ flush_qlen,
+ burst_limit_enable,
+ burst_limit_max_count,
+ burst_limit_window_time,
+ overload_kill_enable,
+ overload_kill_qlen,
+ overload_kill_mem_size,
+ overload_kill_restart_after]).
+
+-define(COMMON_KEYS,[filesync_repeat_interval]).
+
+-define(READ_ONLY_KEYS,[olp]).
%%%-----------------------------------------------------------------
%%% API
%% This function is called by the logger_sup supervisor
-start_link(Args) ->
- proc_lib:start_link(?MODULE,init,[Args]).
-
filesync(Module, Name) ->
call(Module, Name, filesync).
-info(Module, Name) ->
- call(Module, Name, info).
-
-reset(Module, Name) ->
- call(Module, Name, reset).
-
%%%-----------------------------------------------------------------
%%% Handler being added
adding_handler(#{id:=Name,module:=Module}=Config) ->
HConfig0 = maps:get(config, Config, #{}),
- HandlerConfig0 = maps:without(?CONFIG_KEYS,HConfig0),
+ HandlerConfig0 = maps:without(?OLP_KEYS++?COMMON_KEYS,HConfig0),
case Module:check_config(Name,set,undefined,HandlerConfig0) of
{ok,HandlerConfig} ->
- ModifiedCommon = maps:with(?CONFIG_KEYS,HandlerConfig),
- CommonConfig0 = maps:with(?CONFIG_KEYS,HConfig0),
+ ModifiedCommon = maps:with(?COMMON_KEYS,HandlerConfig),
+ CommonConfig0 = maps:with(?COMMON_KEYS,HConfig0),
CommonConfig = maps:merge(
maps:merge(get_default_config(), CommonConfig0),
ModifiedCommon),
case check_config(CommonConfig) of
ok ->
HConfig = maps:merge(CommonConfig,HandlerConfig),
- start(Config#{config => HConfig});
+ OlpOpts = maps:with(?OLP_KEYS,HConfig0),
+ start(OlpOpts, Config#{config => HConfig});
{error,Faulty} ->
{error,{invalid_config,Module,Faulty}}
end;
@@ -92,11 +85,11 @@ adding_handler(#{id:=Name,module:=Module}=Config) ->
%%%-----------------------------------------------------------------
%%% Handler being removed
-removing_handler(#{id:=Name, module:=Module}) ->
+removing_handler(#{id:=Name, module:=Module, config:=#{olp:=Olp}}) ->
case whereis(?name_to_reg_name(Module,Name)) of
undefined ->
ok;
- Pid ->
+ _Pid ->
%% We don't want to do supervisor:terminate_child here
%% since we need to distinguish this explicit stop from a
%% system termination in order to avoid circular attempts
@@ -106,7 +99,7 @@ removing_handler(#{id:=Name, module:=Module}) ->
%% the restart type is temporary, which means that the
%% child specification is automatically removed from the
%% supervisor when the process dies.
- _ = gen_server:call(Pid, stop),
+ _ = logger_olp:stop(Olp),
ok
end.
@@ -116,34 +109,52 @@ changing_config(SetOrUpdate,
#{id:=Name,config:=OldHConfig,module:=Module},
NewConfig0) ->
NewHConfig0 = maps:get(config, NewConfig0, #{}),
- OldHandlerConfig = maps:without(?CONFIG_KEYS++?READ_ONLY_KEYS,OldHConfig),
- NewHandlerConfig0 = maps:without(?CONFIG_KEYS++?READ_ONLY_KEYS,NewHConfig0),
+ NoHandlerKeys = ?OLP_KEYS++?COMMON_KEYS++?READ_ONLY_KEYS,
+ OldHandlerConfig = maps:without(NoHandlerKeys,OldHConfig),
+ NewHandlerConfig0 = maps:without(NoHandlerKeys,NewHConfig0),
case Module:check_config(Name, SetOrUpdate,
OldHandlerConfig,NewHandlerConfig0) of
{ok, NewHandlerConfig} ->
- ModifiedCommon = maps:with(?CONFIG_KEYS,NewHandlerConfig),
- NewCommonConfig0 = maps:with(?CONFIG_KEYS,NewHConfig0),
+ ModifiedCommon = maps:with(?COMMON_KEYS,NewHandlerConfig),
+ NewCommonConfig0 = maps:with(?COMMON_KEYS,NewHConfig0),
+ OldCommonConfig = maps:with(?COMMON_KEYS,OldHConfig),
CommonDefault =
case SetOrUpdate of
set ->
get_default_config();
update ->
- maps:with(?CONFIG_KEYS,OldHConfig)
+ OldCommonConfig
end,
NewCommonConfig = maps:merge(
maps:merge(CommonDefault,NewCommonConfig0),
ModifiedCommon),
case check_config(NewCommonConfig) of
ok ->
- ReadOnly = maps:with(?READ_ONLY_KEYS,OldHConfig),
- NewHConfig = maps:merge(
- maps:merge(NewCommonConfig,NewHandlerConfig),
- ReadOnly),
- NewConfig = NewConfig0#{config=>NewHConfig},
- HPid = maps:get(handler_pid,OldHConfig),
- case call(HPid, {change_config,NewConfig}) of
- ok -> {ok,NewConfig};
- Error -> Error
+ OlpDefault =
+ case SetOrUpdate of
+ set ->
+ logger_olp:get_default_opts();
+ update ->
+ maps:with(?OLP_KEYS,OldHConfig)
+ end,
+ Olp = maps:get(olp,OldHConfig),
+ NewOlpOpts = maps:merge(OlpDefault,
+ maps:with(?OLP_KEYS,NewHConfig0)),
+ case logger_olp:set_opts(Olp,NewOlpOpts) of
+ ok ->
+ maybe_set_repeated_filesync(Olp,OldCommonConfig,
+ NewCommonConfig),
+ ReadOnly = maps:with(?READ_ONLY_KEYS,OldHConfig),
+ NewHConfig =
+ maps:merge(
+ maps:merge(
+ maps:merge(NewCommonConfig,NewHandlerConfig),
+ ReadOnly),
+ NewOlpOpts),
+ NewConfig = NewConfig0#{config=>NewHConfig},
+ {ok,NewConfig};
+ Error ->
+ Error
end;
{error,Faulty} ->
{error,{invalid_config,Module,Faulty}}
@@ -158,14 +169,12 @@ changing_config(SetOrUpdate,
LogEvent :: logger:log_event(),
Config :: logger:handler_config().
-log(LogEvent, Config = #{id := Name,
- config := #{handler_pid := HPid,
- mode_tab := ModeTab}}) ->
+log(LogEvent, Config = #{config := #{olp:=Olp}}) ->
%% if the handler has crashed, we must drop this event
%% and hope the handler restarts so we can try again
- true = is_process_alive(HPid),
+ true = is_process_alive(logger_olp:get_pid(Olp)),
Bin = log_to_binary(LogEvent, Config),
- call_cast_or_drop(Name, HPid, ModeTab, Bin).
+ logger_olp:load(Olp,Bin).
%%%-----------------------------------------------------------------
%%% Remove internal fields from configuration
@@ -180,18 +189,23 @@ filter_config(#{config:=HConfig}=Config) ->
%%%
%%% The handler process is linked to logger_sup, which is part of the
%%% kernel application's supervision tree.
-start(#{id := Name} = Config0) ->
+start(OlpOpts0, #{id := Name, module:=Module, config:=HConfig} = Config0) ->
+ RegName = ?name_to_reg_name(Module,Name),
ChildSpec =
#{id => Name,
- start => {?MODULE, start_link, [Config0]},
+ start => {logger_olp, start_link, [RegName,?MODULE,
+ Config0, OlpOpts0]},
restart => temporary,
shutdown => 2000,
type => worker,
modules => [?MODULE]},
case supervisor:start_child(logger_sup, ChildSpec) of
- {ok,Pid,Config} ->
+ {ok,Pid,Olp} ->
ok = logger_handler_watcher:register_handler(Name,Pid),
- {ok,Config};
+ OlpOpts = logger_olp:get_opts(Olp),
+ {ok,Config0#{config=>(maps:merge(HConfig,OlpOpts))#{olp=>Olp}}};
+ {error,{Reason,Ch}} when is_tuple(Ch), element(1,Ch)==child ->
+ {error,Reason};
Error ->
Error
end.
@@ -200,103 +214,50 @@ start(#{id := Name} = Config0) ->
%%% gen_server callbacks
%%%===================================================================
-init(#{id := Name, module := Module,
- formatter := Formatter, config := HConfig0} = Config0) ->
- RegName = ?name_to_reg_name(Module,Name),
- register(RegName, self()),
+init(#{id := Name, module := Module, config := HConfig}) ->
process_flag(trap_exit, true),
- process_flag(message_queue_data, off_heap),
?init_test_hooks(),
- ?start_observation(Name),
- case Module:init(Name, HConfig0) of
+ case Module:init(Name, HConfig) of
{ok,HState} ->
- try ets:new(Name, [public]) of
- ModeTab ->
- ?set_mode(ModeTab, async),
- T0 = ?timestamp(),
- HConfig = HConfig0#{handler_pid => self(),
- mode_tab => ModeTab},
- Config = Config0#{config => HConfig},
- proc_lib:init_ack({ok,self(),Config}),
- %% Storing common config in state to avoid copying
- %% (sending) the config data for each log message
- CommonConfig = maps:with(?CONFIG_KEYS,HConfig),
- State =
- ?merge_with_stats(
- CommonConfig#{id => Name,
- module => Module,
- mode_tab => ModeTab,
- mode => async,
- ctrl_sync_count =>
- ?CONTROLLER_SYNC_INTERVAL,
- last_qlen => 0,
- last_log_ts => T0,
- last_op => sync,
- burst_win_ts => T0,
- burst_msg_count => 0,
- formatter => Formatter,
- handler_state => HState}),
- State1 = set_repeated_filesync(State),
- unset_restart_flag(State1),
- gen_server:enter_loop(?MODULE, [], State1)
- catch
- _:Error ->
- unregister(RegName),
- error_notify({init_handler,Name,Error}),
- proc_lib:init_ack(Error)
- end;
+ %% Storing common config in state to avoid copying
+ %% (sending) the config data for each log message
+ CommonConfig = maps:with(?COMMON_KEYS,HConfig),
+ State = CommonConfig#{id => Name,
+ module => Module,
+ ctrl_sync_count =>
+ ?CONTROLLER_SYNC_INTERVAL,
+ last_op => sync,
+ handler_state => HState},
+ State1 = set_repeated_filesync(State),
+ {ok,State1};
Error ->
- unregister(RegName),
- error_notify({init_handler,Name,Error}),
- proc_lib:init_ack(Error)
+ Error
end.
-%% This is the synchronous log event.
-handle_call({log, Bin}, _From, State) ->
- {Result,State1} = do_log(Bin, call, State),
- %% Result == ok | dropped
- {reply,Result, State1};
+%% This is the log event.
+handle_load(Bin, #{id:=Name,
+ module:=Module,
+ handler_state:=HandlerState,
+ ctrl_sync_count := CtrlSync}=State) ->
+ if CtrlSync==0 ->
+ {_,HS1} = Module:write(Name, sync, Bin, HandlerState),
+ State#{handler_state => HS1,
+ ctrl_sync_count => ?CONTROLLER_SYNC_INTERVAL,
+ last_op=>write};
+ true ->
+ {_,HS1} = Module:write(Name, async, Bin, HandlerState),
+ State#{handler_state => HS1,
+ ctrl_sync_count => CtrlSync-1,
+ last_op=>write}
+ end.
handle_call(filesync, _From, State = #{id := Name,
module := Module,
handler_state := HandlerState}) ->
{Result,HandlerState1} = Module:filesync(Name,sync,HandlerState),
- {reply, Result, State#{handler_state=>HandlerState1, last_op=>sync}};
-
-handle_call({change_config, #{formatter:=Formatter, config:=NewHConfig}}, _From,
- State = #{filesync_repeat_interval := FSyncInt0}) ->
- %% In the future, if handler_state must be updated due to config
- %% change, then we need to add a callback to Module here.
- CommonConfig = maps:with(?CONFIG_KEYS,NewHConfig),
- State1 = maps:merge(State, CommonConfig),
- State2 =
- case maps:get(filesync_repeat_interval, NewHConfig) of
- FSyncInt0 ->
- State1;
- _FSyncInt1 ->
- set_repeated_filesync(cancel_repeated_filesync(State1))
- end,
- {reply, ok, State2#{formatter:=Formatter}};
-
-handle_call(info, _From, State) ->
- {reply, State, State};
-
-handle_call(reset, _From,
- #{id:=Name,module:=Module,handler_state:=HandlerState}=State) ->
- State1 = ?merge_with_stats(State),
- {reply, ok, State1#{last_qlen => 0,
- last_log_ts => ?timestamp(),
- handler_state => Module:reset_state(Name,HandlerState)}};
-
-handle_call(stop, _From, State) ->
- {stop, {shutdown,stopped}, ok, State}.
-
-%% This is the asynchronous log event.
-handle_cast({log, Bin}, State) ->
- {_,State1} = do_log(Bin, cast, State),
- {noreply, State1};
+ {reply, Result, State#{handler_state=>HandlerState1, last_op=>sync}}.
%% If FILESYNC_REPEAT_INTERVAL is set to a millisec value, this
%% clause gets called repeatedly by the handler. In order to
@@ -319,168 +280,83 @@ handle_cast(repeated_filesync,
{_,HS} = Module:filesync(Name, async, HandlerState),
State#{handler_state => HS, last_op => sync}
end,
- {noreply,set_repeated_filesync(State1)}.
+ {noreply,set_repeated_filesync(State1)};
+
+handle_cast({set_repeated_filesync,FSyncInt},State) ->
+ State1 = State#{filesync_repeat_interval=>FSyncInt},
+ State2 = set_repeated_filesync(cancel_repeated_filesync(State1)),
+ {noreply, State2}.
handle_info(Info, #{id := Name, module := Module,
handler_state := HandlerState} = State) ->
{noreply,State#{handler_state => Module:handle_info(Name,Info,HandlerState)}}.
-terminate(Reason, State = #{id := Name,
- module := Module,
- handler_state := HandlerState}) ->
+terminate(overloaded=Reason, #{id:=Name}=State) ->
+ _ = log_handler_info(Name,"Handler ~p overloaded and stopping",[Name],State),
+ do_terminate(Reason,State),
+ ConfigResult = logger:get_handler_config(Name),
+ case ConfigResult of
+ {ok,#{module:=Module}=HConfig0} ->
+ spawn(fun() -> logger:remove_handler(Name) end),
+ HConfig = try Module:filter_config(HConfig0)
+ catch _:_ -> HConfig0
+ end,
+ {ok,fun() -> logger:add_handler(Name,Module,HConfig) end};
+ Error ->
+ error_notify({Name,restart_impossible,Error}),
+ Error
+ end;
+terminate(Reason, State) ->
+ do_terminate(Reason, State).
+
+do_terminate(Reason, State = #{id := Name,
+ module := Module,
+ handler_state := HandlerState}) ->
_ = cancel_repeated_filesync(State),
_ = Module:terminate(Name, Reason, HandlerState),
- ok = stop_or_restart(Name, Reason, State),
- unregister(?name_to_reg_name(Module, Name)),
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
+reset_state(#{id:=Name, module:=Module, handler_state:=HandlerState} = State) ->
+ State#{handler_state=>Module:reset_state(Name, HandlerState)}.
%%%-----------------------------------------------------------------
%%% Internal functions
call(Module, Name, Op) when is_atom(Name) ->
- call(?name_to_reg_name(Module,Name), Op);
+ case logger_olp:call(?name_to_reg_name(Module,Name), Op) of
+ {error,busy} -> {error,handler_busy};
+ Other -> Other
+ end;
call(_, Name, Op) ->
{error,{badarg,{Op,[Name]}}}.
-call(Server, Msg) ->
- try
- gen_server:call(Server, Msg, ?DEFAULT_CALL_TIMEOUT)
- catch
- _:{timeout,_} -> {error,handler_busy}
- end.
-
-%% check for overload between every event (and set Mode to async,
-%% sync or drop accordingly), but never flush the whole mailbox
-%% before LogWindowSize events have been handled
-do_log(Bin, CallOrCast, State = #{id:=Name, mode:=Mode0}) ->
- T1 = ?timestamp(),
-
- %% check if the handler is getting overloaded, or if it's
- %% recovering from overload (the check must be done for each
- %% event to react quickly to large bursts of events and
- %% to ensure that the handler can never end up in drop mode
- %% with an empty mailbox, which would stop operation)
- {Mode1,QLen,Mem,State1} = check_load(State),
-
- if (Mode1 == drop) andalso (Mode0 =/= drop) ->
- log_handler_info(Name, "Handler ~p switched to drop mode",
- [Name], State);
- (Mode0 == drop) andalso ((Mode1 == async) orelse (Mode1 == sync)) ->
- log_handler_info(Name, "Handler ~p switched to ~w mode",
- [Name,Mode1], State);
- true ->
- ok
- end,
-
- %% kill the handler if it can't keep up with the load
- kill_if_choked(Name, QLen, Mem, State),
-
- if Mode1 == flush ->
- flush(Name, QLen, T1, State1);
- true ->
- write(Name, Mode1, T1, Bin, CallOrCast, State1)
- end.
-
-%% this clause is called by do_log/3 after an overload check
-%% has been performed, where QLen > FlushQLen
-flush(Name, _QLen0, T1, State=#{last_log_ts := _T0, mode_tab := ModeTab}) ->
- %% flush messages in the mailbox (a limited number in
- %% order to not cause long delays)
- NewFlushed = flush_log_events(?FLUSH_MAX_N),
-
- %% write info in log about flushed messages
+notify({mode_change,Mode0,Mode1},#{id:=Name}=State) ->
+ 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",
- [Name,NewFlushed], State),
-
- %% because of the receive loop when flushing messages, the
- %% handler will be scheduled out often and the mailbox could
- %% grow very large, so we'd better check the queue again here
- {_,_QLen1} = process_info(self(), message_queue_len),
- ?observe(Name,{max_qlen,_QLen1}),
-
- %% Add 1 for the current log event
- ?observe(Name,{flushed,NewFlushed+1}),
-
- State1 = ?update_max_time(?diff_time(T1,_T0),State),
- State2 = ?update_max_qlen(_QLen1,State1),
- {dropped,?update_other(flushed,FLUSHED,NewFlushed,
- State2#{mode => ?set_mode(ModeTab,async),
- last_qlen => 0,
- last_log_ts => T1})}.
-
-%% this clause is called to write to file
-write(Name, Mode, T1, Bin, _CallOrCast,
- State = #{module := Module,
- handler_state := HandlerState,
- mode_tab := ModeTab,
- ctrl_sync_count := CtrlSync,
- last_qlen := LastQLen,
- last_log_ts := T0}) ->
- %% check if we need to limit the number of writes
- %% during a burst of log events
- {DoWrite,State1} = limit_burst(State),
-
- %% only log synhrounously every ?CONTROLLER_SYNC_INTERVAL time, to
- %% give the handler time between writes so it can keep up with
- %% incoming messages
- {Result,LastQLen1,HandlerState1} =
- if DoWrite, CtrlSync == 0 ->
- ?observe(Name,{_CallOrCast,1}),
- {_,HS1} = Module:write(Name, sync, Bin, HandlerState),
- {ok,element(2, process_info(self(), message_queue_len)),HS1};
- DoWrite ->
- ?observe(Name,{_CallOrCast,1}),
- {_,HS1} = Module:write(Name, async, Bin, HandlerState),
- {ok,LastQLen,HS1};
- not DoWrite ->
- ?observe(Name,{flushed,1}),
- {dropped,LastQLen,HandlerState}
- end,
-
- %% Check if the time since the previous log event is long enough -
- %% and the queue length small enough - to assume the mailbox has
- %% been emptied, and if so, do filesync operation and reset mode to
- %% async. Note that this is the best we can do to detect an idle
- %% handler without setting a timer after each log call/cast. If the
- %% time between two consecutive log events is fast and no new
- %% event comes in after the last one, idle state won't be detected!
- Time = ?diff_time(T1,T0),
- State2 =
- if (LastQLen1 < ?FILESYNC_OK_QLEN) andalso
- (Time > ?IDLE_DETECT_TIME_USEC) ->
- {_,HS2} = Module:filesync(Name,async,HandlerState),
- State1#{mode => ?change_mode(ModeTab, Mode, async),
- burst_msg_count => 0,
- handler_state => HS2};
- true ->
- State1#{mode => Mode, handler_state => HandlerState1}
- end,
- State3 = ?update_calls_or_casts(_CallOrCast,1,State2),
- State4 = ?update_max_qlen(LastQLen1,State3),
- State5 =
- ?update_max_time(Time,
- State4#{last_qlen := LastQLen1,
- last_log_ts => T1,
- last_op => write,
- ctrl_sync_count =>
- if CtrlSync==0 -> ?CONTROLLER_SYNC_INTERVAL;
- true -> CtrlSync-1
- end}),
- {Result,State5}.
+ [Name,Flushed], State);
+notify(restart,#{id:=Name}=State) ->
+ log_handler_info(Name, "Handler ~p restarted", [Name], State);
+notify(idle,#{id:=Name,module:=Module,handler_state:=HandlerState}=State) ->
+ {_,HS} = Module:filesync(Name,async,HandlerState),
+ State#{handler_state=>HS, last_op=>sync}.
log_handler_info(Name, Format, Args, #{module:=Module,
- formatter:=Formatter,
- handler_state:=HandlerState}) ->
- Config = #{formatter=>Formatter},
- Meta = #{time=>erlang:system_time(microsecond)},
+ handler_state:=HandlerState}=State) ->
+ Config =
+ case logger:get_handler_config(Name) of
+ {ok,Conf} -> Conf;
+ _ -> #{formatter=>{?DEFAULT_FORMATTER,?DEFAULT_FORMAT_CONFIG}}
+ end,
+ Meta = #{time=>logger:timestamp()},
Bin = log_to_binary(#{level => notice,
msg => {Format,Args},
meta => Meta}, Config),
- _ = Module:write(Name, async, Bin, HandlerState),
- ok.
+ {_,HS} = Module:write(Name, async, Bin, HandlerState),
+ State#{handler_state=>HS, last_op=>write}.
%%%-----------------------------------------------------------------
%%% Convert log data on any form to binary
@@ -540,42 +416,8 @@ string_to_binary(String) ->
%%%-----------------------------------------------------------------
%%% Check that the configuration term is valid
check_config(Config) when is_map(Config) ->
- case check_common_config(maps:to_list(Config)) of
- ok ->
- case overload_levels_ok(Config) of
- true ->
- ok;
- false ->
- Faulty = maps:with([sync_mode_qlen,
- drop_mode_qlen,
- flush_qlen],Config),
- {error,{invalid_levels,Faulty}}
- end;
- Error ->
- Error
- end.
+ check_common_config(maps:to_list(Config)).
-check_common_config([{sync_mode_qlen,N}|Config]) when is_integer(N) ->
- check_common_config(Config);
-check_common_config([{drop_mode_qlen,N}|Config]) when is_integer(N) ->
- check_common_config(Config);
-check_common_config([{flush_qlen,N}|Config]) when is_integer(N) ->
- check_common_config(Config);
-check_common_config([{burst_limit_enable,Bool}|Config]) when is_boolean(Bool) ->
- check_common_config(Config);
-check_common_config([{burst_limit_max_count,N}|Config]) when is_integer(N) ->
- check_common_config(Config);
-check_common_config([{burst_limit_window_time,N}|Config]) when is_integer(N) ->
- check_common_config(Config);
-check_common_config([{overload_kill_enable,Bool}|Config]) when is_boolean(Bool) ->
- check_common_config(Config);
-check_common_config([{overload_kill_qlen,N}|Config]) when is_integer(N) ->
- check_common_config(Config);
-check_common_config([{overload_kill_mem_size,N}|Config]) when is_integer(N) ->
- check_common_config(Config);
-check_common_config([{overload_kill_restart_after,NorA}|Config])
- when is_integer(NorA); NorA == infinity ->
- check_common_config(Config);
check_common_config([{filesync_repeat_interval,NorA}|Config])
when is_integer(NorA); NorA == no_repeat ->
check_common_config(Config);
@@ -585,156 +427,7 @@ check_common_config([]) ->
ok.
get_default_config() ->
- #{sync_mode_qlen => ?SYNC_MODE_QLEN,
- drop_mode_qlen => ?DROP_MODE_QLEN,
- flush_qlen => ?FLUSH_QLEN,
- burst_limit_enable => ?BURST_LIMIT_ENABLE,
- burst_limit_max_count => ?BURST_LIMIT_MAX_COUNT,
- burst_limit_window_time => ?BURST_LIMIT_WINDOW_TIME,
- overload_kill_enable => ?OVERLOAD_KILL_ENABLE,
- overload_kill_qlen => ?OVERLOAD_KILL_QLEN,
- overload_kill_mem_size => ?OVERLOAD_KILL_MEM_SIZE,
- overload_kill_restart_after => ?OVERLOAD_KILL_RESTART_AFTER,
- filesync_repeat_interval => ?FILESYNC_REPEAT_INTERVAL}.
-
-%%%-----------------------------------------------------------------
-%%% Overload Protection
-call_cast_or_drop(_Name, HandlerPid, ModeTab, Bin) ->
- %% If the handler process is getting overloaded, the log event
- %% will be synchronous instead of asynchronous (slows down the
- %% logging tempo of a process doing lots of logging. If the
- %% handler is choked, drop mode is set and no event will be sent.
- try ?get_mode(ModeTab) of
- async ->
- gen_server:cast(HandlerPid, {log,Bin});
- sync ->
- case call(HandlerPid, {log,Bin}) of
- ok ->
- ok;
- _Other ->
- %% dropped or {error,handler_busy}
- ?observe(_Name,{dropped,1}),
- ok
- end;
- drop ->
- ?observe(_Name,{dropped,1})
- catch
- %% if the ETS table doesn't exist (maybe because of a
- %% handler restart), we can only drop the event
- _:_ -> ?observe(_Name,{dropped,1})
- end,
- ok.
-
-set_restart_flag(#{id := Name, module := Module} = State) ->
- log_handler_info(Name, "Handler ~p overloaded and stopping", [Name], State),
- Flag = list_to_atom(lists:concat([Module,"_",Name,"_restarting"])),
- spawn(fun() ->
- register(Flag, self()),
- timer:sleep(infinity)
- end),
- ok.
-
-unset_restart_flag(#{id := Name, module := Module} = State) ->
- Flag = list_to_atom(lists:concat([Module,"_",Name,"_restarting"])),
- case whereis(Flag) of
- undefined ->
- ok;
- Pid ->
- exit(Pid, kill),
- log_handler_info(Name, "Handler ~p restarted", [Name], State)
- end.
-
-check_load(State = #{id:=_Name, mode_tab := ModeTab, mode := Mode,
- sync_mode_qlen := SyncModeQLen,
- drop_mode_qlen := DropModeQLen,
- flush_qlen := FlushQLen}) ->
- {_,Mem} = process_info(self(), memory),
- ?observe(_Name,{max_mem,Mem}),
- {_,QLen} = process_info(self(), message_queue_len),
- ?observe(_Name,{max_qlen,QLen}),
- %% When the handler process gets scheduled in, it's impossible
- %% to predict the QLen. We could jump "up" arbitrarily from say
- %% async to sync, async to drop, sync to flush, etc. However, when
- %% the handler process manages the log events (without flushing),
- %% one after the other, we will move "down" from drop to sync and
- %% from sync to async. This way we don't risk getting stuck in
- %% drop or sync mode with an empty mailbox.
- {Mode1,_NewDrops,_NewFlushes} =
- if
- QLen >= FlushQLen ->
- {flush, 0,1};
- QLen >= DropModeQLen ->
- %% Note that drop mode will force log events to
- %% be dropped on the client side (never sent get to
- %% the handler).
- IncDrops = if Mode == drop -> 0; true -> 1 end,
- {?change_mode(ModeTab, Mode, drop), IncDrops,0};
- QLen >= SyncModeQLen ->
- {?change_mode(ModeTab, Mode, sync), 0,0};
- true ->
- {?change_mode(ModeTab, Mode, async), 0,0}
- end,
- State1 = ?update_other(drops,DROPS,_NewDrops,State),
- {Mode1, QLen, Mem,
- ?update_other(flushes,FLUSHES,_NewFlushes,
- State1#{last_qlen => QLen})}.
-
-limit_burst(#{burst_limit_enable := false}=State) ->
- {true,State};
-limit_burst(#{burst_win_ts := BurstWinT0,
- burst_msg_count := BurstMsgCount,
- burst_limit_window_time := BurstLimitWinTime,
- burst_limit_max_count := BurstLimitMaxCnt} = State) ->
- if (BurstMsgCount >= BurstLimitMaxCnt) ->
- %% the limit for allowed messages has been reached
- BurstWinT1 = ?timestamp(),
- case ?diff_time(BurstWinT1,BurstWinT0) of
- BurstCheckTime when BurstCheckTime < (BurstLimitWinTime*1000) ->
- %% we're still within the burst time frame
- {false,?update_other(burst_drops,BURSTS,1,State)};
- _BurstCheckTime ->
- %% burst time frame passed, reset counters
- {true,State#{burst_win_ts => BurstWinT1,
- burst_msg_count => 0}}
- end;
- true ->
- %% the limit for allowed messages not yet reached
- {true,State#{burst_win_ts => BurstWinT0,
- burst_msg_count => BurstMsgCount+1}}
- end.
-
-kill_if_choked(Name, QLen, Mem, State = #{overload_kill_enable := KillIfOL,
- overload_kill_qlen := OLKillQLen,
- overload_kill_mem_size := OLKillMem}) ->
- if KillIfOL andalso
- ((QLen > OLKillQLen) orelse (Mem > OLKillMem)) ->
- set_restart_flag(State),
- exit({shutdown,{overloaded,Name,QLen,Mem}});
- true ->
- ok
- end.
-
-flush_log_events(Limit) ->
- process_flag(priority, high),
- Flushed = flush_log_events(0, Limit),
- process_flag(priority, normal),
- Flushed.
-
-flush_log_events(Limit, Limit) ->
- Limit;
-flush_log_events(N, Limit) ->
- %% flush log events but leave other events, such as
- %% filesync, info and change_config, so that these
- %% have a chance to be processed even under heavy load
- receive
- {'$gen_cast',{log,_}} ->
- flush_log_events(N+1, Limit);
- {'$gen_call',{Pid,MRef},{log,_}} ->
- Pid ! {MRef, dropped},
- flush_log_events(N+1, Limit)
- after
- 0 -> N
- end.
+ #{filesync_repeat_interval => ?FILESYNC_REPEAT_INTERVAL}.
set_repeated_filesync(#{filesync_repeat_interval:=FSyncInt} = State)
when is_integer(FSyncInt) ->
@@ -752,51 +445,12 @@ cancel_repeated_filesync(State) ->
error ->
State
end.
-
-stop_or_restart(Name, {shutdown,Reason={overloaded,_Name,_QLen,_Mem}},
- #{overload_kill_restart_after := RestartAfter}) ->
- %% If we're terminating because of an overload situation (see
- %% kill_if_choked/4), we need to remove the handler and set a
- %% restart timer. A separate process must perform this in order to
- %% avoid deadlock.
- HandlerPid = self(),
- ConfigResult = logger:get_handler_config(Name),
- RemoveAndRestart =
- fun() ->
- MRef = erlang:monitor(process, HandlerPid),
- receive
- {'DOWN',MRef,_,_,_} ->
- ok
- after 30000 ->
- error_notify(Reason),
- exit(HandlerPid, kill)
- end,
- case ConfigResult of
- {ok,#{module:=HMod}=HConfig0} when is_integer(RestartAfter) ->
- _ = logger:remove_handler(Name),
- HConfig = try HMod:filter_config(HConfig0)
- catch _:_ -> HConfig0
- end,
- _ = timer:apply_after(RestartAfter, logger, add_handler,
- [Name,HMod,HConfig]);
- {ok,_} ->
- _ = logger:remove_handler(Name);
- {error,CfgReason} when is_integer(RestartAfter) ->
- error_notify({Name,restart_impossible,CfgReason});
- {error,_} ->
- ok
- end
- end,
- spawn(RemoveAndRestart),
- ok;
-stop_or_restart(_Name, _Reason, _State) ->
- ok.
-
-overload_levels_ok(HandlerConfig) ->
- SMQL = maps:get(sync_mode_qlen, HandlerConfig, ?SYNC_MODE_QLEN),
- DMQL = maps:get(drop_mode_qlen, HandlerConfig, ?DROP_MODE_QLEN),
- FQL = maps:get(flush_qlen, HandlerConfig, ?FLUSH_QLEN),
- (DMQL > 1) andalso (SMQL =< DMQL) andalso (DMQL =< FQL).
-
error_notify(Term) ->
?internal_log(error, Term).
+
+maybe_set_repeated_filesync(_Olp,
+ #{filesync_repeat_interval:=FSyncInt},
+ #{filesync_repeat_interval:=FSyncInt}) ->
+ ok;
+maybe_set_repeated_filesync(Olp,_,#{filesync_repeat_interval:=FSyncInt}) ->
+ logger_olp:cast(Olp,{set_repeated_filesync,FSyncInt}).
diff --git a/lib/kernel/src/logger_h_common.hrl b/lib/kernel/src/logger_h_common.hrl
index 261b0a6246..004a61d9d9 100644
--- a/lib/kernel/src/logger_h_common.hrl
+++ b/lib/kernel/src/logger_h_common.hrl
@@ -1,50 +1,22 @@
-
-%%%-----------------------------------------------------------------
-%%% Overload protection configuration
-
-%%! *** NOTE ***
-%%! It's important that:
-%%! SYNC_MODE_QLEN =< DROP_MODE_QLEN =< FLUSH_QLEN
-%%! and that DROP_MODE_QLEN >= 2.
-%%! Otherwise the handler could end up in drop mode with no new
-%%! log requests to process. This would cause all future requests
-%%! to be dropped (no switch to async mode would ever take place).
-
-%% This specifies the message_queue_len value where the log
-%% requests switch from asynchronous casts to synchronous calls.
--define(SYNC_MODE_QLEN, 10).
-%% Above this message_queue_len, log requests will be dropped,
-%% i.e. no log requests get sent to the handler process.
--define(DROP_MODE_QLEN, 200).
-%% Above this message_queue_len, the handler process will flush
-%% its mailbox and only leave this number of messages in it.
--define(FLUSH_QLEN, 1000).
-
-%% Never flush more than this number of messages in one go,
-%% or the handler will be unresponsive for seconds (keep this
-%% number as large as possible or the mailbox could grow large).
--define(FLUSH_MAX_N, 5000).
-
-%% BURST_LIMIT_MAX_COUNT is the max number of log requests allowed
-%% to be written within a BURST_LIMIT_WINDOW_TIME time frame.
--define(BURST_LIMIT_ENABLE, true).
--define(BURST_LIMIT_MAX_COUNT, 500).
--define(BURST_LIMIT_WINDOW_TIME, 1000).
-
-%% This enables/disables the feature to automatically get the
-%% handler terminated if it gets too loaded (and can't keep up).
--define(OVERLOAD_KILL_ENABLE, false).
-%% If the message_queue_len goes above this size even after
-%% flushing has been performed, the handler is terminated.
--define(OVERLOAD_KILL_QLEN, 20000).
-%% If the memory usage exceeds this level
--define(OVERLOAD_KILL_MEM_SIZE, 3000000).
-
-%% This is the default time that the handler will wait before
-%% restarting and accepting new requests. The value 'infinity'
-%% disables restarts.
--define(OVERLOAD_KILL_RESTART_AFTER, 5000).
-%%-define(OVERLOAD_KILL_RESTART_AFTER, infinity).
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2015. All 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%
+%%
%% The handler sends asynchronous write requests to the process
%% controlling the i/o device, but every once in this interval
@@ -65,12 +37,6 @@
-define(FILESYNC_REPEAT_INTERVAL, 5000).
%%-define(FILESYNC_REPEAT_INTERVAL, no_repeat).
-%% This is the time after last message received that we think/hope
-%% that the handler has an empty mailbox (no new log request has
-%% come in).
--define(IDLE_DETECT_TIME_MSEC, 100).
--define(IDLE_DETECT_TIME_USEC, 100000).
-
%% Default disk log option values
-define(DISK_LOG_TYPE, wrap).
-define(DISK_LOG_MAX_NO_FILES, 10).
@@ -83,43 +49,6 @@
list_to_atom(lists:concat([MODULE,"_",Name]))).
%%%-----------------------------------------------------------------
-%%% Overload protection macros
-
--define(timestamp(), erlang:monotonic_time(microsecond)).
-
--define(get_mode(Tid),
- case ets:lookup(Tid, mode) of
- [{mode,M}] -> M;
- _ -> async
- end).
-
--define(set_mode(Tid, M),
- begin ets:insert(Tid, {mode,M}), M end).
-
--define(change_mode(Tid, M0, M1),
- if M0 == M1 ->
- M0;
- true ->
- ets:insert(Tid, {mode,M1}),
- M1
- end).
-
--define(min(X1, X2),
- if X2 == undefined -> X1;
- X2 < X1 -> X2;
- true -> X1
- end).
-
--define(max(X1, X2),
- if
- X2 == undefined -> X1;
- X2 > X1 -> X2;
- true -> X1
- end).
-
--define(diff_time(OS_T1, OS_T0), OS_T1-OS_T0).
-
-%%%-----------------------------------------------------------------
%%% The test hook macros make it possible to observe and manipulate
%%% internal handler functionality. When enabled, these macros will
%%% slow down execution and therefore should not be include in code
@@ -183,7 +112,6 @@
[{_,ERROR}] -> ERROR
catch _:_ -> disk_log:sync(LOG) end).
- -define(DEFAULT_CALL_TIMEOUT, 5000).
-else. % DEFAULTS!
-define(TEST_HOOKS_TAB, undefined).
@@ -196,68 +124,4 @@
-define(file_datasync(DEVICE), file:datasync(DEVICE)).
-define(disk_log_write(LOG, MODE, DATA), disk_log_write(LOG, MODE, DATA)).
-define(disk_log_sync(LOG), disk_log:sync(LOG)).
- -define(DEFAULT_CALL_TIMEOUT, 10000).
--endif.
-
-%%%-----------------------------------------------------------------
-%%% These macros enable statistics counters in the state of the
-%%% handler which is useful for analysing the overload protection
-%%% behaviour. These counters should not be included in code to be
-%%% officially released (as some counters will grow very large
-%%% over time).
-
-%%-define(SAVE_STATS, true).
--ifdef(SAVE_STATS).
- -define(merge_with_stats(STATE),
- STATE#{flushes => 0, flushed => 0, drops => 0,
- burst_drops => 0, casts => 0, calls => 0,
- max_qlen => 0, max_time => 0}).
-
- -define(update_max_qlen(QLEN, STATE),
- begin #{max_qlen := QLEN0} = STATE,
- STATE#{max_qlen => ?max(QLEN0,QLEN)} end).
-
- -define(update_calls_or_casts(CALL_OR_CAST, INC, STATE),
- case CALL_OR_CAST of
- cast ->
- #{casts := CASTS0} = STATE,
- STATE#{casts => CASTS0+INC};
- call ->
- #{calls := CALLS0} = STATE,
- STATE#{calls => CALLS0+INC}
- end).
-
- -define(update_max_time(TIME, STATE),
- begin #{max_time := TIME0} = STATE,
- STATE#{max_time => ?max(TIME0,TIME)} end).
-
- -define(update_other(OTHER, VAR, INCVAL, STATE),
- begin #{OTHER := VAR} = STATE,
- STATE#{OTHER => VAR+INCVAL} end).
-
--else. % DEFAULT!
- -define(merge_with_stats(STATE), STATE).
- -define(update_max_qlen(_QLEN, STATE), STATE).
- -define(update_calls_or_casts(_CALL_OR_CAST, _INC, STATE), STATE).
- -define(update_max_time(_TIME, STATE), STATE).
- -define(update_other(_OTHER, _VAR, _INCVAL, STATE), STATE).
--endif.
-
-%%%-----------------------------------------------------------------
-%%% These macros enable callbacks that make it possible to analyse
-%%% the overload protection behaviour from outside the handler
-%%% process (including dropped requests on the client side).
-%%% An external callback module (?OBSERVER_MOD) is required which
-%%% is not part of the kernel application. For this reason, these
-%%% callbacks should not be included in code to be officially released.
-
-%%-define(OBSERVER_MOD, logger_test).
--ifdef(OBSERVER_MOD).
- -define(start_observation(NAME), ?OBSERVER:start_observation(NAME)).
- -define(observe(NAME,EVENT), ?OBSERVER:observe(NAME,EVENT)).
-
--else. % DEFAULT!
- -define(start_observation(_NAME), ok).
- -define(observe(_NAME,_EVENT), ok).
-endif.
-%%! <---
diff --git a/lib/kernel/src/logger_internal.hrl b/lib/kernel/src/logger_internal.hrl
index d96a4ac78b..e53922e5d3 100644
--- a/lib/kernel/src/logger_internal.hrl
+++ b/lib/kernel/src/logger_internal.hrl
@@ -19,6 +19,7 @@
%%
-include_lib("kernel/include/logger.hrl").
-define(LOGGER_TABLE,logger).
+-define(PROXY_KEY,'$proxy_config$').
-define(PRIMARY_KEY,'$primary_config$').
-define(HANDLER_KEY,'$handler_config$').
-define(LOGGER_META_KEY,'$logger_metadata$').
@@ -40,12 +41,14 @@
-define(DEFAULT_LOGGER_CALL_TIMEOUT, infinity).
--define(LOG_INTERNAL(Level,Report),
+-define(LOG_INTERNAL(Level,Report),?DO_LOG_INTERNAL(Level,[Report])).
+-define(LOG_INTERNAL(Level,Format,Args),?DO_LOG_INTERNAL(Level,[Format,Args])).
+-define(DO_LOG_INTERNAL(Level,Data),
case logger:allow(Level,?MODULE) of
true ->
%% Spawn this to avoid deadlocks
- _ = spawn(logger,macro_log,[?LOCATION,Level,Report,
- logger:add_default_metadata(#{})]),
+ _ = spawn(logger,macro_log,[?LOCATION,Level|Data]++
+ [logger:add_default_metadata(#{})]),
ok;
false ->
ok
diff --git a/lib/kernel/src/logger_olp.erl b/lib/kernel/src/logger_olp.erl
new file mode 100644
index 0000000000..009280a9c9
--- /dev/null
+++ b/lib/kernel/src/logger_olp.erl
@@ -0,0 +1,626 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2017-2018. All 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(logger_olp).
+-behaviour(gen_server).
+
+-include("logger_olp.hrl").
+-include("logger_internal.hrl").
+
+%% API
+-export([start_link/4, load/2, info/1, reset/1, stop/1, restart/1,
+ set_opts/2, get_opts/1, get_default_opts/0, get_pid/1,
+ call/2, cast/2, get_ref/0, get_ref/1]).
+
+%% gen_server and proc_lib callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-define(OPT_KEYS,[sync_mode_qlen,
+ drop_mode_qlen,
+ flush_qlen,
+ burst_limit_enable,
+ burst_limit_max_count,
+ burst_limit_window_time,
+ overload_kill_enable,
+ overload_kill_qlen,
+ overload_kill_mem_size,
+ overload_kill_restart_after]).
+
+-export_type([olp_ref/0, options/0]).
+
+-opaque olp_ref() :: {atom(),pid(),ets:tid()}.
+
+-type options() :: logger:olp_config().
+
+%%%-----------------------------------------------------------------
+%%% API
+
+-spec start_link(Name,Module,Args,Options) -> {ok,Pid,Olp} | {error,Reason} when
+ Name :: atom(),
+ Module :: module(),
+ Args :: term(),
+ Options :: options(),
+ Pid :: pid(),
+ Olp :: olp_ref(),
+ Reason :: term().
+start_link(Name,Module,Args,Options0) when is_map(Options0) ->
+ Options = maps:merge(get_default_opts(),Options0),
+ case check_opts(Options) of
+ ok ->
+ proc_lib:start_link(?MODULE,init,[[Name,Module,Args,Options]]);
+ Error ->
+ Error
+ end.
+
+-spec load(Olp, Msg) -> ok when
+ Olp :: olp_ref(),
+ Msg :: term().
+load({_Name,Pid,ModeRef},Msg) ->
+ %% If the process is getting overloaded, the message will be
+ %% synchronous instead of asynchronous (slows down the tempo of a
+ %% process causing much load). If the process is choked, drop mode
+ %% is set and no message is sent.
+ try ?get_mode(ModeRef) of
+ async ->
+ gen_server:cast(Pid, {'$olp_load',Msg});
+ sync ->
+ case call(Pid, {'$olp_load',Msg}) of
+ ok ->
+ ok;
+ _Other ->
+ %% dropped or {error,busy}
+ ?observe(_Name,{dropped,1}),
+ ok
+ end;
+ drop ->
+ ?observe(_Name,{dropped,1})
+ catch
+ %% if the ETS table doesn't exist (maybe because of a
+ %% process restart), we can only drop the event
+ _:_ -> ?observe(_Name,{dropped,1})
+ end,
+ ok.
+
+-spec info(Olp) -> map() | {error, busy} when
+ Olp :: atom() | pid() | olp_ref().
+info(Olp) ->
+ call(Olp, info).
+
+-spec reset(Olp) -> ok | {error, busy} when
+ Olp :: atom() | pid() | olp_ref().
+reset(Olp) ->
+ call(Olp, reset).
+
+-spec stop(Olp) -> ok when
+ Olp :: atom() | pid() | olp_ref().
+stop({_Name,Pid,_ModRef}) ->
+ stop(Pid);
+stop(Pid) ->
+ _ = gen_server:call(Pid, stop),
+ ok.
+
+-spec set_opts(Olp, Opts) -> ok | {error,term()} | {error, busy} when
+ Olp :: atom() | pid() | olp_ref(),
+ Opts :: options().
+set_opts(Olp, Opts) ->
+ call(Olp, {set_opts,Opts}).
+
+-spec get_opts(Olp) -> options() | {error, busy} when
+ Olp :: atom() | pid() | olp_ref().
+get_opts(Olp) ->
+ call(Olp, get_opts).
+
+-spec get_default_opts() -> options().
+get_default_opts() ->
+ #{sync_mode_qlen => ?SYNC_MODE_QLEN,
+ drop_mode_qlen => ?DROP_MODE_QLEN,
+ flush_qlen => ?FLUSH_QLEN,
+ burst_limit_enable => ?BURST_LIMIT_ENABLE,
+ burst_limit_max_count => ?BURST_LIMIT_MAX_COUNT,
+ burst_limit_window_time => ?BURST_LIMIT_WINDOW_TIME,
+ overload_kill_enable => ?OVERLOAD_KILL_ENABLE,
+ overload_kill_qlen => ?OVERLOAD_KILL_QLEN,
+ overload_kill_mem_size => ?OVERLOAD_KILL_MEM_SIZE,
+ overload_kill_restart_after => ?OVERLOAD_KILL_RESTART_AFTER}.
+
+-spec restart(fun(() -> any())) -> ok.
+restart(Fun) ->
+ Result =
+ try Fun()
+ catch C:R:S ->
+ {error,{restart_failed,Fun,C,R,S}}
+ end,
+ ?LOG_INTERNAL(debug,[{logger_olp,restart},
+ {result,Result}]),
+ ok.
+
+-spec get_ref() -> olp_ref().
+get_ref() ->
+ get(olp_ref).
+
+-spec get_ref(PidOrName) -> olp_ref() | {error, busy} when
+ PidOrName :: pid() | atom().
+get_ref(PidOrName) ->
+ call(PidOrName,get_ref).
+
+-spec get_pid(olp_ref()) -> pid().
+get_pid({_Name,Pid,_ModeRef}) ->
+ Pid.
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+init([Name,Module,Args,Options]) ->
+ register(Name, self()),
+ process_flag(message_queue_data, off_heap),
+
+ ?start_observation(Name),
+
+ try ets:new(Name, [public]) of
+ ModeRef ->
+ OlpRef = {Name,self(),ModeRef},
+ put(olp_ref,OlpRef),
+ try Module:init(Args) of
+ {ok,CBState} ->
+ ?set_mode(ModeRef, async),
+ T0 = ?timestamp(),
+ proc_lib:init_ack({ok,self(),OlpRef}),
+ %% Storing options in state to avoid copying
+ %% (sending) the option data with each message
+ State0 = ?merge_with_stats(
+ Options#{id => Name,
+ idle=> true,
+ module => Module,
+ mode_ref => ModeRef,
+ mode => async,
+ last_qlen => 0,
+ last_load_ts => T0,
+ burst_win_ts => T0,
+ burst_msg_count => 0,
+ cb_state => CBState}),
+ State = reset_restart_flag(State0),
+ gen_server:enter_loop(?MODULE, [], State);
+ Error ->
+ _ = ets:delete(ModeRef),
+ unregister(Name),
+ proc_lib:init_ack(Error)
+ catch
+ _:Error ->
+ _ = ets:delete(ModeRef),
+ unregister(Name),
+ proc_lib:init_ack(Error)
+ end
+ catch
+ _:Error ->
+ unregister(Name),
+ proc_lib:init_ack(Error)
+ end.
+
+%% This is the synchronous load event.
+handle_call({'$olp_load', Msg}, _From, State) ->
+ {Result,State1} = do_load(Msg, call, State#{idle=>false}),
+ %% Result == ok | dropped
+ reply_return(Result,State1);
+
+handle_call(get_ref,_From,#{id:=Name,mode_ref:=ModeRef}=State) ->
+ reply_return({Name,self(),ModeRef},State);
+
+handle_call({set_opts,Opts0},_From,State) ->
+ Opts = maps:merge(maps:with(?OPT_KEYS,State),Opts0),
+ case check_opts(Opts) of
+ ok ->
+ reply_return(ok, maps:merge(State,Opts));
+ Error ->
+ reply_return(Error, State)
+ end;
+
+handle_call(get_opts,_From,State) ->
+ reply_return(maps:with(?OPT_KEYS,State), State);
+
+handle_call(info, _From, State) ->
+ reply_return(State, State);
+
+handle_call(reset, _From, #{module:=Module,cb_state:=CBState}=State) ->
+ State1 = ?merge_with_stats(State),
+ CBState1 = try_callback_call(Module,reset_state,[CBState],CBState),
+ reply_return(ok, State1#{idle => true,
+ last_qlen => 0,
+ last_load_ts => ?timestamp(),
+ cb_state => CBState1});
+
+handle_call(stop, _From, State) ->
+ {stop, {shutdown,stopped}, ok, State};
+
+handle_call(Msg, From, #{module:=Module,cb_state:=CBState}=State) ->
+ case try_callback_call(Module,handle_call,[Msg, From, CBState]) of
+ {reply,Reply,CBState1} ->
+ reply_return(Reply,State#{cb_state=>CBState1});
+ {noreply,CBState1} ->
+ noreply_return(State#{cb_state=>CBState1});
+ {stop, Reason, Reply, CBState1} ->
+ {stop, Reason, Reply, State#{cb_state=>CBState1}};
+ {stop, Reason, CBState1} ->
+ {stop, Reason, State#{cb_state=>CBState1}}
+ end.
+
+%% This is the asynchronous load event.
+handle_cast({'$olp_load', Msg}, State) ->
+ {_Result,State1} = do_load(Msg, cast, State#{idle=>false}),
+ noreply_return(State1);
+
+handle_cast(Msg, #{module:=Module, cb_state:=CBState} = State) ->
+ case try_callback_call(Module,handle_cast,[Msg, CBState]) of
+ {noreply,CBState1} ->
+ noreply_return(State#{cb_state=>CBState1});
+ {stop, Reason, CBState1} ->
+ {stop, Reason, State#{cb_state=>CBState1}}
+ end.
+
+handle_info(timeout, #{mode_ref:=_ModeRef, mode:=Mode} = State) ->
+ State1 = notify(idle,State),
+ State2 = maybe_notify_mode_change(async,State1),
+ {noreply, State2#{idle => true,
+ mode => ?change_mode(_ModeRef, Mode, async),
+ burst_msg_count => 0}};
+handle_info(Msg, #{module := Module, cb_state := CBState} = State) ->
+ case try_callback_call(Module,handle_info,[Msg, CBState]) of
+ {noreply,CBState1} ->
+ noreply_return(State#{cb_state=>CBState1});
+ {stop, Reason, CBState1} ->
+ {stop, Reason, State#{cb_state=>CBState1}};
+ {load,CBState1} ->
+ {_,State1} = do_load(Msg, cast, State#{idle=>false,
+ cb_state=>CBState1}),
+ noreply_return(State1)
+ end.
+
+terminate({shutdown,{overloaded,_QLen,_Mem}},
+ #{id:=Name, module := Module, cb_state := CBState,
+ overload_kill_restart_after := RestartAfter} = State) ->
+ %% We're terminating because of an overload situation (see
+ %% kill_if_choked/3).
+ unregister(Name), %%!!!! to avoid error printout of callback crashed on stop
+ case try_callback_call(Module,terminate,[overloaded,CBState],ok) of
+ {ok,Fun} when is_function(Fun,0), is_integer(RestartAfter) ->
+ set_restart_flag(State),
+ _ = timer:apply_after(RestartAfter,?MODULE,restart,[Fun]),
+ ok;
+ _ ->
+ ok
+ end;
+terminate(Reason, #{id:=Name, module:=Module, cb_state:=CBState}) ->
+ _ = try_callback_call(Module,terminate,[Reason,CBState],ok),
+ unregister(Name),
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+
+%%%-----------------------------------------------------------------
+%%% Internal functions
+-spec call(Olp, term()) -> term() | {error,busy} when
+ Olp :: atom() | pid() | olp_ref().
+call({_Name, Pid, _ModeRef},Msg) ->
+ call(Pid, Msg);
+call(Server, Msg) ->
+ try
+ gen_server:call(Server, Msg)
+ catch
+ _:{timeout,_} -> {error,busy}
+ end.
+
+-spec cast(olp_ref(),term()) -> ok.
+cast({_Name, Pid, _ModeRef},Msg) ->
+ gen_server:cast(Pid, Msg).
+
+%% check for overload between every event (and set Mode to async,
+%% sync or drop accordingly), but never flush the whole mailbox
+%% before LogWindowSize events have been handled
+do_load(Msg, CallOrCast, State) ->
+ T1 = ?timestamp(),
+ State1 = ?update_time(T1,State),
+
+ %% check if the process is getting overloaded, or if it's
+ %% recovering from overload (the check must be done for each
+ %% event to react quickly to large bursts of events and
+ %% to ensure that the handler can never end up in drop mode
+ %% with an empty mailbox, which would stop operation)
+ {Mode1,QLen,Mem,State2} = check_load(State1),
+
+ %% kill the handler if it can't keep up with the load
+ kill_if_choked(QLen, Mem, State2),
+
+ if Mode1 == flush ->
+ flush(T1, State2);
+ true ->
+ handle_load(Mode1, T1, Msg, CallOrCast, State2)
+ end.
+
+%% this function is called by do_load/3 after an overload check
+%% has been performed, where QLen > FlushQLen
+flush(T1, State=#{id := _Name, mode := Mode, last_load_ts := _T0, mode_ref := ModeRef}) ->
+ %% flush load messages in the mailbox (a limited number in order
+ %% to not cause long delays)
+ NewFlushed = flush_load(?FLUSH_MAX_N),
+
+ %% write info in log about flushed messages
+ State1=notify({flushed,NewFlushed},State),
+
+ %% because of the receive loop when flushing messages, the
+ %% handler will be scheduled out often and the mailbox could
+ %% grow very large, so we'd better check the queue again here
+ {_,QLen1} = process_info(self(), message_queue_len),
+ ?observe(_Name,{max_qlen,QLen1}),
+
+ %% Add 1 for the current log event
+ ?observe(_Name,{flushed,NewFlushed+1}),
+
+ State2 = ?update_max_time(?diff_time(T1,_T0),State1),
+ State3 = ?update_max_qlen(QLen1,State2),
+ State4 = maybe_notify_mode_change(async,State3),
+ {dropped,?update_other(flushed,FLUSHED,NewFlushed,
+ State4#{mode => ?change_mode(ModeRef,Mode,async),
+ last_qlen => QLen1,
+ last_load_ts => T1})}.
+
+%% this function is called to actually handle the message
+handle_load(Mode, T1, Msg, _CallOrCast,
+ State = #{id := _Name,
+ module := Module,
+ cb_state := CBState,
+ last_qlen := LastQLen,
+ last_load_ts := _T0}) ->
+ %% check if we need to limit the number of writes
+ %% during a burst of log events
+ {DoWrite,State1} = limit_burst(State),
+
+ {Result,LastQLen1,CBState1} =
+ if DoWrite ->
+ ?observe(_Name,{_CallOrCast,1}),
+ CBS = try_callback_call(Module,handle_load,[Msg,CBState]),
+ {ok,element(2, process_info(self(), message_queue_len)),CBS};
+ true ->
+ ?observe(_Name,{flushed,1}),
+ {dropped,LastQLen,CBState}
+ end,
+ State2 = State1#{cb_state=>CBState1},
+
+ State3 = State2#{mode => Mode},
+ State4 = ?update_calls_or_casts(_CallOrCast,1,State3),
+ State5 = ?update_max_qlen(LastQLen1,State4),
+ State6 =
+ ?update_max_time(?diff_time(T1,_T0),
+ State5#{last_qlen := LastQLen1,
+ last_load_ts => T1}),
+ State7 = case Result of
+ ok ->
+ S = ?update_freq(T1,State6),
+ ?update_other(writes,WRITES,1,S);
+ _ ->
+ State6
+ end,
+ {Result,State7}.
+
+
+%%%-----------------------------------------------------------------
+%%% Check that the options are valid
+check_opts(Options) when is_map(Options) ->
+ case do_check_opts(maps:to_list(Options)) of
+ ok ->
+ case overload_levels_ok(Options) of
+ true ->
+ ok;
+ false ->
+ Faulty = maps:with([sync_mode_qlen,
+ drop_mode_qlen,
+ flush_qlen],Options),
+ {error,{invalid_olp_levels,Faulty}}
+ end;
+ {error,Key,Value} ->
+ {error,{invalid_olp_config,#{Key=>Value}}}
+ end.
+
+do_check_opts([{sync_mode_qlen,N}|Options]) when is_integer(N) ->
+ do_check_opts(Options);
+do_check_opts([{drop_mode_qlen,N}|Options]) when is_integer(N) ->
+ do_check_opts(Options);
+do_check_opts([{flush_qlen,N}|Options]) when is_integer(N) ->
+ do_check_opts(Options);
+do_check_opts([{burst_limit_enable,Bool}|Options]) when is_boolean(Bool) ->
+ do_check_opts(Options);
+do_check_opts([{burst_limit_max_count,N}|Options]) when is_integer(N) ->
+ do_check_opts(Options);
+do_check_opts([{burst_limit_window_time,N}|Options]) when is_integer(N) ->
+ do_check_opts(Options);
+do_check_opts([{overload_kill_enable,Bool}|Options]) when is_boolean(Bool) ->
+ do_check_opts(Options);
+do_check_opts([{overload_kill_qlen,N}|Options]) when is_integer(N) ->
+ do_check_opts(Options);
+do_check_opts([{overload_kill_mem_size,N}|Options]) when is_integer(N) ->
+ do_check_opts(Options);
+do_check_opts([{overload_kill_restart_after,NorA}|Options])
+ when is_integer(NorA); NorA == infinity ->
+ do_check_opts(Options);
+do_check_opts([{Key,Value}|_]) ->
+ {error,Key,Value};
+do_check_opts([]) ->
+ ok.
+
+set_restart_flag(#{id := Name, module := Module}) ->
+ Flag = list_to_atom(lists:concat([Module,"_",Name,"_restarting"])),
+ spawn(fun() ->
+ register(Flag, self()),
+ timer:sleep(infinity)
+ end),
+ ok.
+
+reset_restart_flag(#{id := Name, module := Module} = State) ->
+ Flag = list_to_atom(lists:concat([Module,"_",Name,"_restarting"])),
+ case whereis(Flag) of
+ undefined ->
+ State;
+ Pid ->
+ exit(Pid, kill),
+ notify(restart,State)
+ end.
+
+check_load(State = #{id:=_Name, mode_ref := ModeRef, mode := Mode,
+ sync_mode_qlen := SyncModeQLen,
+ drop_mode_qlen := DropModeQLen,
+ flush_qlen := FlushQLen}) ->
+ {_,Mem} = process_info(self(), memory),
+ ?observe(_Name,{max_mem,Mem}),
+ {_,QLen} = process_info(self(), message_queue_len),
+ ?observe(_Name,{max_qlen,QLen}),
+ %% When the handler process gets scheduled in, it's impossible
+ %% to predict the QLen. We could jump "up" arbitrarily from say
+ %% async to sync, async to drop, sync to flush, etc. However, when
+ %% the handler process manages the log events (without flushing),
+ %% one after the other, we will move "down" from drop to sync and
+ %% from sync to async. This way we don't risk getting stuck in
+ %% drop or sync mode with an empty mailbox.
+ {Mode1,_NewDrops,_NewFlushes} =
+ if
+ QLen >= FlushQLen ->
+ {flush, 0,1};
+ QLen >= DropModeQLen ->
+ %% Note that drop mode will force load messages to
+ %% be dropped on the client side (never sent to
+ %% the olp process).
+ IncDrops = if Mode == drop -> 0; true -> 1 end,
+ {?change_mode(ModeRef, Mode, drop), IncDrops,0};
+ QLen >= SyncModeQLen ->
+ {?change_mode(ModeRef, Mode, sync), 0,0};
+ true ->
+ {?change_mode(ModeRef, Mode, async), 0,0}
+ end,
+ State1 = ?update_other(drops,DROPS,_NewDrops,State),
+ State2 = ?update_max_qlen(QLen,State1),
+ State3 = maybe_notify_mode_change(Mode1,State2),
+ {Mode1, QLen, Mem,
+ ?update_other(flushes,FLUSHES,_NewFlushes,
+ State3#{last_qlen => QLen})}.
+
+limit_burst(#{burst_limit_enable := false}=State) ->
+ {true,State};
+limit_burst(#{burst_win_ts := BurstWinT0,
+ burst_msg_count := BurstMsgCount,
+ burst_limit_window_time := BurstLimitWinTime,
+ burst_limit_max_count := BurstLimitMaxCnt} = State) ->
+ if (BurstMsgCount >= BurstLimitMaxCnt) ->
+ %% the limit for allowed messages has been reached
+ BurstWinT1 = ?timestamp(),
+ case ?diff_time(BurstWinT1,BurstWinT0) of
+ BurstCheckTime when BurstCheckTime < (BurstLimitWinTime*1000) ->
+ %% we're still within the burst time frame
+ {false,?update_other(burst_drops,BURSTS,1,State)};
+ _BurstCheckTime ->
+ %% burst time frame passed, reset counters
+ {true,State#{burst_win_ts => BurstWinT1,
+ burst_msg_count => 0}}
+ end;
+ true ->
+ %% the limit for allowed messages not yet reached
+ {true,State#{burst_win_ts => BurstWinT0,
+ burst_msg_count => BurstMsgCount+1}}
+ end.
+
+kill_if_choked(QLen, Mem, #{overload_kill_enable := KillIfOL,
+ overload_kill_qlen := OLKillQLen,
+ overload_kill_mem_size := OLKillMem}) ->
+ if KillIfOL andalso
+ ((QLen > OLKillQLen) orelse (Mem > OLKillMem)) ->
+ exit({shutdown,{overloaded,QLen,Mem}});
+ true ->
+ ok
+ end.
+
+flush_load(Limit) ->
+ process_flag(priority, high),
+ Flushed = flush_load(0, Limit),
+ process_flag(priority, normal),
+ Flushed.
+
+flush_load(Limit, Limit) ->
+ Limit;
+flush_load(N, Limit) ->
+ %% flush log events but leave other events, such as info, reset
+ %% and stop, so that these have a chance to be processed even
+ %% under heavy load
+ receive
+ {'$gen_cast',{'$olp_load',_}} ->
+ flush_load(N+1, Limit);
+ {'$gen_call',{Pid,MRef},{'$olp_load',_}} ->
+ Pid ! {MRef, dropped},
+ flush_load(N+1, Limit);
+ {log,_,_,_,_} ->
+ flush_load(N+1, Limit);
+ {log,_,_,_} ->
+ flush_load(N+1, Limit)
+ after
+ 0 -> N
+ end.
+
+overload_levels_ok(Options) ->
+ SMQL = maps:get(sync_mode_qlen, Options, ?SYNC_MODE_QLEN),
+ DMQL = maps:get(drop_mode_qlen, Options, ?DROP_MODE_QLEN),
+ FQL = maps:get(flush_qlen, Options, ?FLUSH_QLEN),
+ (DMQL > 1) andalso (SMQL =< DMQL) andalso (DMQL =< FQL).
+
+maybe_notify_mode_change(drop,#{mode:=Mode0}=State)
+ when Mode0=/=drop ->
+ notify({mode_change,Mode0,drop},State);
+maybe_notify_mode_change(Mode1,#{mode:=drop}=State)
+ when Mode1==async; Mode1==sync ->
+ notify({mode_change,drop,Mode1},State);
+maybe_notify_mode_change(_,State) ->
+ State.
+
+notify(Note,#{module:=Module,cb_state:=CBState}=State) ->
+ CBState1 = try_callback_call(Module,notify,[Note,CBState],CBState),
+ State#{cb_state=>CBState1}.
+
+try_callback_call(Module, Function, Args) ->
+ try_callback_call(Module, Function, Args, '$no_default_return').
+
+try_callback_call(Module, Function, Args, DefRet) ->
+ try apply(Module, Function, Args)
+ catch
+ throw:R -> R;
+ error:undef:S when DefRet=/='$no_default_return' ->
+ case S of
+ [{Module,Function,Args,_}|_] ->
+ DefRet;
+ _ ->
+ erlang:raise(error,undef,S)
+ end
+ end.
+
+noreply_return(#{idle:=true}=State) ->
+ {noreply,State};
+noreply_return(#{idle:=false}=State) ->
+ {noreply,State,?IDLE_DETECT_TIME}.
+
+reply_return(Reply,#{idle:=true}=State) ->
+ {reply,Reply,State};
+reply_return(Reply,#{idle:=false}=State) ->
+ {reply,Reply,State,?IDLE_DETECT_TIME}.
diff --git a/lib/kernel/src/logger_olp.hrl b/lib/kernel/src/logger_olp.hrl
new file mode 100644
index 0000000000..9b4f5ebf27
--- /dev/null
+++ b/lib/kernel/src/logger_olp.hrl
@@ -0,0 +1,180 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2015. All 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%
+%%
+
+%%%-----------------------------------------------------------------
+%%% Overload protection configuration
+
+%%! *** NOTE ***
+%%! It's important that:
+%%! SYNC_MODE_QLEN =< DROP_MODE_QLEN =< FLUSH_QLEN
+%%! and that DROP_MODE_QLEN >= 2.
+%%! Otherwise the process could end up in drop mode with no new
+%%! log requests to process. This would cause all future requests
+%%! to be dropped (no switch to async mode would ever take place).
+
+%% This specifies the message_queue_len value where the log
+%% requests switch from asynchronous casts to synchronous calls.
+-define(SYNC_MODE_QLEN, 10).
+%% Above this message_queue_len, log requests will be dropped,
+%% i.e. no log requests get sent to the process.
+-define(DROP_MODE_QLEN, 200).
+%% Above this message_queue_len, the process will flush its mailbox
+%% and only leave this number of messages in it.
+-define(FLUSH_QLEN, 1000).
+
+%% Never flush more than this number of messages in one go, or the
+%% process will be unresponsive for seconds (keep this number as large
+%% as possible or the mailbox could grow large).
+-define(FLUSH_MAX_N, 5000).
+
+%% BURST_LIMIT_MAX_COUNT is the max number of log requests allowed
+%% to be written within a BURST_LIMIT_WINDOW_TIME time frame.
+-define(BURST_LIMIT_ENABLE, true).
+-define(BURST_LIMIT_MAX_COUNT, 500).
+-define(BURST_LIMIT_WINDOW_TIME, 1000).
+
+%% This enables/disables the feature to automatically terminate the
+%% process if it gets too loaded (and can't keep up).
+-define(OVERLOAD_KILL_ENABLE, false).
+%% If the message_queue_len goes above this size even after
+%% flushing has been performed, the process is terminated.
+-define(OVERLOAD_KILL_QLEN, 20000).
+%% If the memory usage exceeds this level, the process is terminated.
+-define(OVERLOAD_KILL_MEM_SIZE, 3000000).
+
+%% This is the default time to wait before restarting and accepting
+%% new requests. The value 'infinity' disables restarts.
+-define(OVERLOAD_KILL_RESTART_AFTER, 5000).
+
+%% This is the time in milliseconds after last load message received
+%% that we notify the callback about being idle.
+-define(IDLE_DETECT_TIME, 100).
+
+%%%-----------------------------------------------------------------
+%%% Overload protection macros
+
+-define(timestamp(), erlang:monotonic_time(microsecond)).
+
+-define(get_mode(Tid),
+ case ets:lookup(Tid, mode) of
+ [{mode,M}] -> M;
+ _ -> async
+ end).
+
+-define(set_mode(Tid, M),
+ begin ets:insert(Tid, {mode,M}), M end).
+
+-define(change_mode(Tid, M0, M1),
+ if M0 == M1 ->
+ M0;
+ true ->
+ ets:insert(Tid, {mode,M1}),
+ M1
+ end).
+
+-define(max(X1, X2),
+ if
+ X2 == undefined -> X1;
+ X2 > X1 -> X2;
+ true -> X1
+ end).
+
+-define(diff_time(OS_T1, OS_T0), OS_T1-OS_T0).
+
+%%%-----------------------------------------------------------------
+%%% These macros enable statistics counters in the state of the
+%%% process, which is useful for analysing the overload protection
+%%% behaviour. These counters should not be included in code to be
+%%% officially released (as some counters will grow very large over
+%%% time).
+
+%% -define(SAVE_STATS, true).
+-ifdef(SAVE_STATS).
+ -define(merge_with_stats(STATE),
+ begin
+ TIME = ?timestamp(),
+ STATE#{start => TIME, time => {TIME,0},
+ flushes => 0, flushed => 0, drops => 0,
+ burst_drops => 0, casts => 0, calls => 0,
+ writes => 0, max_qlen => 0, max_time => 0,
+ freq => {TIME,0,0}} end).
+
+ -define(update_max_qlen(QLEN, STATE),
+ begin #{max_qlen := QLEN0} = STATE,
+ STATE#{max_qlen => ?max(QLEN0,QLEN)} end).
+
+ -define(update_calls_or_casts(CALL_OR_CAST, INC, STATE),
+ case CALL_OR_CAST of
+ cast ->
+ #{casts := CASTS0} = STATE,
+ STATE#{casts => CASTS0+INC};
+ call ->
+ #{calls := CALLS0} = STATE,
+ STATE#{calls => CALLS0+INC}
+ end).
+
+ -define(update_max_time(TIME, STATE),
+ begin #{max_time := TIME0} = STATE,
+ STATE#{max_time => ?max(TIME0,TIME)} end).
+
+ -define(update_other(OTHER, VAR, INCVAL, STATE),
+ begin #{OTHER := VAR} = STATE,
+ STATE#{OTHER => VAR+INCVAL} end).
+
+ -define(update_freq(TIME,STATE),
+ begin
+ case STATE of
+ #{freq := {START, 49, _}} ->
+ STATE#{freq => {TIME, 0, trunc(1000000*50/(?diff_time(TIME,START)))}};
+ #{freq := {START, N, FREQ}} ->
+ STATE#{freq => {START, N+1, FREQ}}
+ end end).
+
+ -define(update_time(TIME,STATE),
+ begin #{start := START} = STATE,
+ STATE#{time => {TIME,trunc((?diff_time(TIME,START))/1000000)}} end).
+
+-else. % DEFAULT!
+ -define(merge_with_stats(STATE), STATE).
+ -define(update_max_qlen(_QLEN, STATE), STATE).
+ -define(update_calls_or_casts(_CALL_OR_CAST, _INC, STATE), STATE).
+ -define(update_max_time(_TIME, STATE), STATE).
+ -define(update_other(_OTHER, _VAR, _INCVAL, STATE), STATE).
+ -define(update_freq(_TIME, STATE), STATE).
+ -define(update_time(_TIME, STATE), STATE).
+-endif.
+
+%%%-----------------------------------------------------------------
+%%% These macros enable callbacks that make it possible to analyse the
+%%% overload protection behaviour from outside the process (including
+%%% dropped requests on the client side). An external callback module
+%%% (?OBSERVER_MOD) is required which is not part of the kernel
+%%% application. For this reason, these callbacks should not be
+%%% included in code to be officially released.
+
+%%-define(OBSERVER_MOD, logger_test).
+-ifdef(OBSERVER_MOD).
+ -define(start_observation(NAME), ?OBSERVER:start_observation(NAME)).
+ -define(observe(NAME,EVENT), ?OBSERVER:observe(NAME,EVENT)).
+
+-else. % DEFAULT!
+ -define(start_observation(_NAME), ok).
+ -define(observe(_NAME,_EVENT), ok).
+-endif.
diff --git a/lib/kernel/src/logger_proxy.erl b/lib/kernel/src/logger_proxy.erl
new file mode 100644
index 0000000000..24b293805c
--- /dev/null
+++ b/lib/kernel/src/logger_proxy.erl
@@ -0,0 +1,165 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2017-2018. All 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(logger_proxy).
+
+%% API
+-export([start_link/0, restart/0, log/1, child_spec/0, get_default_config/0]).
+
+%% logger_olp callbacks
+-export([init/1, handle_load/2, handle_info/2, terminate/2,
+ notify/2]).
+
+-include("logger_internal.hrl").
+
+-define(SERVER,?MODULE).
+
+%%%-----------------------------------------------------------------
+%%% API
+-spec log(RemoteLog) -> ok when
+ RemoteLog :: {remote,node(),LogEvent},
+ LogEvent :: {log,Level,Format,Args,Meta} |
+ {log,Level,StringOrReport,Meta},
+ Level :: logger:level(),
+ Format :: io:format(),
+ Args :: list(term()),
+ StringOrReport :: unicode:chardata() | logger:report(),
+ Meta :: logger:metadata().
+log(RemoteLog) ->
+ Olp = persistent_term:get(?MODULE),
+ case logger_olp:get_pid(Olp) =:= self() of
+ true ->
+ %% This happens when the log event comes from the
+ %% emulator, and the group leader is on a remote node.
+ _ = handle_load(RemoteLog, no_state),
+ ok;
+ false ->
+ logger_olp:load(Olp, RemoteLog)
+ end.
+
+%% Called by supervisor
+-spec start_link() -> {ok,pid(),logger_olp:olp_ref()} | {error,term()}.
+start_link() ->
+ %% Notice that sync_mode is only used when logging to remote node,
+ %% i.e. when the log/2 API function is called.
+ %%
+ %% When receiving log events from the emulator or from a remote
+ %% node, the log event is sent as a message to this process, and
+ %% thus received directly in handle_info/2. This means that the
+ %% mode (async/sync/drop) is not read before the message is
+ %% sent. Thus sync mode is never entered, and drop mode is
+ %% implemented by setting the system_logger flag to undefined (see
+ %% notify/2)
+ %%
+ %% Burst limit is disabled, since this is only a proxy and we
+ %% don't want to limit bursts twice (here and in the handler).
+ logger_olp:start_link(?SERVER,?MODULE,[],logger:get_proxy_config()).
+
+%% Fun used for restarting this process after it has been killed due
+%% to overload (must set overload_kill_enable=>true in opts)
+restart() ->
+ case supervisor:start_child(logger_sup, child_spec()) of
+ {ok,_Pid,Olp} ->
+ {ok,Olp};
+ {error,{Reason,Ch}} when is_tuple(Ch), element(1,Ch)==child ->
+ {error,Reason};
+ Error ->
+ Error
+ end.
+
+%% Called internally and by logger_sup
+child_spec() ->
+ Name = ?SERVER,
+ #{id => Name,
+ start => {?MODULE, start_link, []},
+ restart => temporary,
+ shutdown => 2000,
+ type => worker,
+ modules => [?MODULE]}.
+
+get_default_config() ->
+ OlpDefault = logger_olp:get_default_opts(),
+ OlpDefault#{sync_mode_qlen=>500,
+ drop_mode_qlen=>1000,
+ flush_qlen=>5000,
+ burst_limit_enable=>false}.
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+init([]) ->
+ process_flag(trap_exit, true),
+ _ = erlang:system_flag(system_logger,self()),
+ persistent_term:put(?MODULE,logger_olp:get_ref()),
+ {ok,no_state}.
+
+%% Log event to send to the node where the group leader of it's client resides
+handle_load({remote,Node,Log},State) ->
+ %% If the connection is overloaded (send_nosuspend returns false),
+ %% we drop the message.
+ _ = erlang:send_nosuspend({?SERVER,Node},Log),
+ State;
+%% Log event to log on this node
+handle_load({log,Level,Format,Args,Meta},State) ->
+ try_log([Level,Format,Args,Meta]),
+ State;
+handle_load({log,Level,Report,Meta},State) ->
+ try_log([Level,Report,Meta]),
+ State.
+
+%% Log event sent to this process e.g. from the emulator - it is really load
+handle_info(Log,State) when is_tuple(Log), element(1,Log)==log ->
+ {load,State}.
+
+terminate(overloaded, _State) ->
+ _ = erlang:system_flag(system_logger,undefined),
+ {ok,fun ?MODULE:restart/0};
+terminate(_Reason, _State) ->
+ _ = erlang:system_flag(system_logger,whereis(logger)),
+ ok.
+
+notify({mode_change,Mode0,Mode1},State) ->
+ _ = if Mode1=:=drop -> % entering drop mode
+ erlang:system_flag(system_logger,undefined);
+ Mode0=:=drop -> % leaving drop mode
+ erlang:system_flag(system_logger,self());
+ true ->
+ ok
+ end,
+ ?LOG_INTERNAL(notice,"~w switched from ~w to ~w mode",[?MODULE,Mode0,Mode1]),
+ State;
+notify({flushed,Flushed},State) ->
+ ?LOG_INTERNAL(notice, "~w flushed ~w log events",[?MODULE,Flushed]),
+ State;
+notify(restart,State) ->
+ ?LOG_INTERNAL(notice, "~w restarted", [?MODULE]),
+ State;
+notify(_Note,State) ->
+ State.
+
+%%%-----------------------------------------------------------------
+%%% Internal functions
+try_log(Args) ->
+ try apply(logger,log,Args)
+ catch C:R:S ->
+ ?LOG_INTERNAL(debug,[{?MODULE,log_failed},
+ {log,Args},
+ {reason,{C,R,S}}])
+ end.
diff --git a/lib/kernel/src/logger_server.erl b/lib/kernel/src/logger_server.erl
index b7735dbcf7..722246e82c 100644
--- a/lib/kernel/src/logger_server.erl
+++ b/lib/kernel/src/logger_server.erl
@@ -22,8 +22,7 @@
-behaviour(gen_server).
%% API
--export([start_link/0,
- add_handler/3, remove_handler/1,
+-export([start_link/0, add_handler/3, remove_handler/1,
add_filter/2, remove_filter/2,
set_module_level/2, unset_module_level/0,
unset_module_level/1, cache_module_level/1,
@@ -43,7 +42,7 @@
-define(SERVER, logger).
-define(LOGGER_SERVER_TAG, '$logger_cb_process').
--record(state, {tid, async_req, async_req_queue}).
+-record(state, {tid, async_req, async_req_queue, remote_logger}).
%%%===================================================================
%%% API
@@ -155,6 +154,8 @@ init([]) ->
process_flag(trap_exit, true),
put(?LOGGER_SERVER_TAG,true),
Tid = logger_config:new(?LOGGER_TABLE),
+ %% Store initial proxy config. logger_proxy reads config from here at startup.
+ logger_config:create(Tid,proxy,logger_proxy:get_default_config()),
PrimaryConfig = maps:merge(default_config(primary),
#{handlers=>[simple]}),
logger_config:create(Tid,primary,PrimaryConfig),
@@ -221,6 +222,24 @@ handle_call({add_filter,Id,Filter}, _From,#state{tid=Tid}=State) ->
handle_call({remove_filter,Id,FilterId}, _From, #state{tid=Tid}=State) ->
Reply = do_remove_filter(Tid,Id,FilterId),
{reply,Reply,State};
+handle_call({change_config,SetOrUpd,proxy,Config0},_From,#state{tid=Tid}=State) ->
+ Default =
+ case SetOrUpd of
+ set ->
+ logger_proxy:get_default_config();
+ update ->
+ {ok,OldConfig} = logger_config:get(Tid,proxy),
+ OldConfig
+ end,
+ Config = maps:merge(Default,Config0),
+ Reply =
+ case logger_olp:set_opts(logger_proxy,Config) of
+ ok ->
+ logger_config:set(Tid,proxy,Config);
+ Error ->
+ Error
+ end,
+ {reply,Reply,State};
handle_call({change_config,SetOrUpd,primary,Config0}, _From,
#state{tid=Tid}=State) ->
{ok,#{handlers:=Handlers}=OldConfig} = logger_config:get(Tid,primary),
@@ -357,7 +376,7 @@ terminate(_Reason, _State) ->
%%%===================================================================
%%% Internal functions
%%%===================================================================
-call(Request) ->
+call(Request) when is_tuple(Request) ->
Action = element(1,Request),
case get(?LOGGER_SERVER_TAG) of
true when
@@ -369,6 +388,7 @@ call(Request) ->
gen_server:call(?SERVER,Request,?DEFAULT_LOGGER_CALL_TIMEOUT)
end.
+
do_add_filter(Tid,Id,{FId,_} = Filter) ->
case logger_config:get(Tid,Id) of
{ok,Config} ->
@@ -413,11 +433,13 @@ default_config(Id,Module) ->
sanity_check(Owner,Key,Value) ->
sanity_check_1(Owner,[{Key,Value}]).
-sanity_check(HandlerId,Config) when is_map(Config) ->
- sanity_check_1(HandlerId,maps:to_list(Config));
+sanity_check(Owner,Config) when is_map(Config) ->
+ sanity_check_1(Owner,maps:to_list(Config));
sanity_check(_,Config) ->
{error,{invalid_config,Config}}.
+sanity_check_1(proxy,_Config) ->
+ ok; % Details are checked by logger_olp:set_opts/2
sanity_check_1(Owner,Config) when is_list(Config) ->
try
Type = get_type(Owner),
diff --git a/lib/kernel/src/logger_simple_h.erl b/lib/kernel/src/logger_simple_h.erl
index fe181722f3..a0d51dba25 100644
--- a/lib/kernel/src/logger_simple_h.erl
+++ b/lib/kernel/src/logger_simple_h.erl
@@ -69,7 +69,7 @@ log(#{msg:=_,meta:=#{time:=_}}=Log,_Config) ->
do_log(
#{level=>error,
msg=>{report,{error,simple_handler_process_dead}},
- meta=>#{time=>erlang:system_time(microsecond)}}),
+ meta=>#{time=>logger:timestamp()}}),
do_log(Log);
_ ->
?MODULE ! {log,Log}
@@ -129,7 +129,7 @@ drop_msg(0) ->
drop_msg(N) ->
[#{level=>info,
msg=>{"Simple handler buffer full, dropped ~w messages",[N]},
- meta=>#{time=>erlang:system_time(microsecond)}}].
+ meta=>#{time=>logger:timestamp()}}].
%%%-----------------------------------------------------------------
%%% Internal
diff --git a/lib/kernel/src/logger_std_h.erl b/lib/kernel/src/logger_std_h.erl
index 63d1dbaba2..65f5b3876e 100644
--- a/lib/kernel/src/logger_std_h.erl
+++ b/lib/kernel/src/logger_std_h.erl
@@ -26,7 +26,7 @@
-include_lib("kernel/include/file.hrl").
%% API
--export([info/1, filesync/1, reset/1]).
+-export([filesync/1]).
%% logger_h_common callbacks
-export([init/2, check_config/4, reset_state/2,
@@ -36,6 +36,8 @@
-export([log/2, adding_handler/1, removing_handler/1, changing_config/3,
filter_config/1]).
+-define(DEFAULT_CALL_TIMEOUT, 5000).
+
%%%===================================================================
%%% API
%%%===================================================================
@@ -49,25 +51,6 @@
filesync(Name) ->
logger_h_common:filesync(?MODULE,Name).
-%%%-----------------------------------------------------------------
-%%%
--spec info(Name) -> Info | {error,Reason} when
- Name :: atom(),
- Info :: term(),
- Reason :: handler_busy | {badarg,term()}.
-
-info(Name) ->
- logger_h_common:info(?MODULE,Name).
-
-%%%-----------------------------------------------------------------
-%%%
--spec reset(Name) -> ok | {error,Reason} when
- Name :: atom(),
- Reason :: handler_busy | {badarg,term()}.
-
-reset(Name) ->
- logger_h_common:reset(?MODULE,Name).
-
%%%===================================================================
%%% logger callbacks - just forward to logger_h_common
%%%===================================================================
@@ -234,17 +217,24 @@ open_log_file(HandlerName, FileInfo) ->
Error -> Error
end.
-do_open_log_file({file,File}) ->
- do_open_log_file({file,File,[raw,append,delayed_write]});
+do_open_log_file({file,FileName}) ->
+ do_open_log_file({file,FileName,[raw,append,delayed_write]});
-do_open_log_file({file,File,[]}) ->
- do_open_log_file({file,File,[raw,append,delayed_write]});
+do_open_log_file({file,FileName,[]}) ->
+ do_open_log_file({file,FileName,[raw,append,delayed_write]});
-do_open_log_file({file,File,Modes}) ->
+do_open_log_file({file,FileName,Modes}) ->
try
- case filelib:ensure_dir(File) of
+ case filelib:ensure_dir(FileName) of
ok ->
- file:open(File, Modes);
+ case file:open(FileName, Modes) of
+ {ok, Fd} ->
+ {ok,#file_info{inode=INode}} =
+ file:read_file_info(FileName),
+ {ok, {Fd, INode}};
+ Error ->
+ Error
+ end;
Error ->
Error
end
@@ -254,7 +244,7 @@ do_open_log_file({file,File,Modes}) ->
close_log_file(Std) when Std == standard_io; Std == standard_error ->
ok;
-close_log_file(Fd) ->
+close_log_file({Fd,_}) ->
_ = file:datasync(Fd),
_ = file:close(Fd).
@@ -313,9 +303,9 @@ file_ctrl_init(HandlerName, FileInfo, Starter) when is_tuple(FileInfo) ->
process_flag(message_queue_data, off_heap),
FileName = element(2, FileInfo),
case do_open_log_file(FileInfo) of
- {ok,Fd} ->
+ {ok,File} ->
Starter ! {self(),ok},
- file_ctrl_loop(Fd, FileName, false, ok, ok, HandlerName);
+ file_ctrl_loop(File, FileName, false, ok, ok, HandlerName);
{error,Reason} ->
Starter ! {self(),{error,{open_failed,FileName,Reason}}}
end;
@@ -323,39 +313,43 @@ file_ctrl_init(HandlerName, StdDev, Starter) ->
Starter ! {self(),ok},
file_ctrl_loop(StdDev, StdDev, false, ok, ok, HandlerName).
-file_ctrl_loop(Fd, DevName, Synced,
+file_ctrl_loop(File, DevName, Synced,
PrevWriteResult, PrevSyncResult, HandlerName) ->
receive
%% asynchronous event
{log,Bin} ->
- Fd1 = ensure(Fd, DevName),
- Result = write_to_dev(Fd1, Bin, DevName, PrevWriteResult, HandlerName),
- file_ctrl_loop(Fd1, DevName, false,
+ File1 = ensure(File, DevName),
+ Result = write_to_dev(File1, Bin, DevName,
+ PrevWriteResult, HandlerName),
+ file_ctrl_loop(File1, DevName, false,
Result, PrevSyncResult, HandlerName);
%% synchronous event
{{log,Bin},{From,MRef}} ->
- Fd1 = ensure(Fd, DevName),
- Result = write_to_dev(Fd1, Bin, DevName, PrevWriteResult, HandlerName),
+ File1 = ensure(File, DevName),
+ Result = write_to_dev(File1, Bin, DevName,
+ PrevWriteResult, HandlerName),
From ! {MRef,ok},
- file_ctrl_loop(Fd1, DevName, false,
+ file_ctrl_loop(File1, DevName, false,
Result, PrevSyncResult, HandlerName);
filesync ->
- Fd1 = ensure(Fd, DevName),
- Result = sync_dev(Fd1, DevName, Synced, PrevSyncResult, HandlerName),
- file_ctrl_loop(Fd1, DevName, true,
+ File1 = ensure(File, DevName),
+ Result = sync_dev(File1, DevName, Synced,
+ PrevSyncResult, HandlerName),
+ file_ctrl_loop(File1, DevName, true,
PrevWriteResult, Result, HandlerName);
{filesync,{From,MRef}} ->
- Fd1 = ensure(Fd, DevName),
- Result = sync_dev(Fd1, DevName, Synced, PrevSyncResult, HandlerName),
+ File1 = ensure(File, DevName),
+ Result = sync_dev(File1, DevName, Synced,
+ PrevSyncResult, HandlerName),
From ! {MRef,ok},
- file_ctrl_loop(Fd1, DevName, true,
+ file_ctrl_loop(File1, DevName, true,
PrevWriteResult, Result, HandlerName);
stop ->
- _ = close_log_file(Fd),
+ _ = close_log_file(File),
stopped
end.
@@ -364,16 +358,16 @@ file_ctrl_loop(Fd, DevName, Synced,
%% logrotate)
ensure(Fd,DevName) when is_atom(DevName) ->
Fd;
-ensure(Fd,FileName) ->
+ensure({Fd,INode},FileName) ->
case file:read_file_info(FileName) of
- {ok,_} ->
- Fd;
+ {ok,#file_info{inode=INode}} ->
+ {Fd,INode};
_ ->
_ = file:close(Fd),
_ = file:close(Fd), % delayed_write cause close not to close
case do_open_log_file({file,FileName}) of
- {ok,Fd1} ->
- Fd1;
+ {ok,File} ->
+ File;
Error ->
exit({could_not_reopen_file,Error})
end
@@ -382,13 +376,13 @@ ensure(Fd,FileName) ->
write_to_dev(DevName, Bin, _DevName, _PrevWriteResult, _HandlerName)
when is_atom(DevName) ->
io:put_chars(DevName, Bin);
-write_to_dev(Fd, Bin, FileName, PrevWriteResult, HandlerName) ->
+write_to_dev({Fd,_}, Bin, FileName, PrevWriteResult, HandlerName) ->
Result = ?file_write(Fd, Bin),
maybe_notify_error(write,Result,PrevWriteResult,FileName,HandlerName).
-sync_dev(_Fd, _FileName, true, PrevSyncResult, _HandlerName) ->
+sync_dev(_, _FileName, true, PrevSyncResult, _HandlerName) ->
PrevSyncResult;
-sync_dev(Fd, FileName, false, PrevSyncResult, HandlerName) ->
+sync_dev({Fd,_}, FileName, false, PrevSyncResult, HandlerName) ->
Result = ?file_datasync(Fd),
maybe_notify_error(filesync,Result,PrevSyncResult,FileName,HandlerName).
diff --git a/lib/kernel/src/logger_sup.erl b/lib/kernel/src/logger_sup.erl
index 3d6f482e20..9ea8558a16 100644
--- a/lib/kernel/src/logger_sup.erl
+++ b/lib/kernel/src/logger_sup.erl
@@ -50,7 +50,9 @@ init([]) ->
start => {logger_handler_watcher, start_link, []},
shutdown => brutal_kill},
- {ok, {SupFlags, [Watcher]}}.
+ Proxy = logger_proxy:child_spec(),
+
+ {ok, {SupFlags, [Watcher,Proxy]}}.
%%%===================================================================
%%% Internal functions
diff --git a/lib/kernel/src/net_kernel.erl b/lib/kernel/src/net_kernel.erl
index a9dc77837e..83d3b4b5e1 100644
--- a/lib/kernel/src/net_kernel.erl
+++ b/lib/kernel/src/net_kernel.erl
@@ -1126,14 +1126,22 @@ do_disconnect(Node, State) ->
{false, State}
end.
-
disconnect_pid(Pid, State) ->
exit(Pid, disconnect),
+
+ %% This code used to only use exit + recv 'EXIT' to sync,
+ %% but since OTP-22 links are no longer broken atomically
+ %% so the exit message below can arrive before any remaining
+ %% exit messages have killed the distribution port
+ Ref = erlang:monitor(process, Pid),
%% Sync wait for connection to die!!!
receive
- {'EXIT',Pid,Reason} ->
- {_,State1} = handle_exit(Pid, Reason, State),
- {true, State1}
+ {'DOWN',Ref,_,_,_} ->
+ receive
+ {'EXIT',Pid,Reason} ->
+ {_,State1} = handle_exit(Pid, Reason, State),
+ {true, State1}
+ end
end.
%%
@@ -1433,7 +1441,7 @@ validate_hostname([$@|HostPart] = Host) ->
end.
valid_name_head(Head) ->
- {ok, MP} = re:compile("^[0-9A-Za-z_\\-]*$", [unicode]),
+ {ok, MP} = re:compile("^[0-9A-Za-z_\\-]+$", [unicode]),
case re:run(Head, MP) of
{match, _} ->
true;
diff --git a/lib/kernel/src/seq_trace.erl b/lib/kernel/src/seq_trace.erl
index 14fe21e9de..4f9d7b3e5c 100644
--- a/lib/kernel/src/seq_trace.erl
+++ b/lib/kernel/src/seq_trace.erl
@@ -98,7 +98,7 @@ print(Label, Term) ->
-spec reset_trace() -> 'true'.
reset_trace() ->
- erlang:system_flag(1, 0).
+ erlang:system_flag(reset_seq_trace, true).
%% reset_trace(Pid) -> % this might be a useful function too
diff --git a/lib/kernel/src/standard_error.erl b/lib/kernel/src/standard_error.erl
index 5d649e5f94..ef5b532960 100644
--- a/lib/kernel/src/standard_error.erl
+++ b/lib/kernel/src/standard_error.erl
@@ -27,7 +27,8 @@
-define(PROCNAME_SUP, standard_error_sup).
%% Defines for control ops
--define(CTRL_OP_GET_WINSIZE,100).
+-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.
diff --git a/lib/kernel/src/user.erl b/lib/kernel/src/user.erl
index 872e63ab53..0c9e1ea303 100644
--- a/lib/kernel/src/user.erl
+++ b/lib/kernel/src/user.erl
@@ -28,7 +28,8 @@
-define(NAME, user).
%% Defines for control ops
--define(CTRL_OP_GET_WINSIZE,100).
+-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.
diff --git a/lib/kernel/src/user_drv.erl b/lib/kernel/src/user_drv.erl
index 9f914aa222..08286dd476 100644
--- a/lib/kernel/src/user_drv.erl
+++ b/lib/kernel/src/user_drv.erl
@@ -32,9 +32,10 @@
-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).
+-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)
diff --git a/lib/kernel/test/Makefile b/lib/kernel/test/Makefile
index 4a86265a4a..d203597fc2 100644
--- a/lib/kernel/test/Makefile
+++ b/lib/kernel/test/Makefile
@@ -76,8 +76,11 @@ MODULES= \
logger_filters_SUITE \
logger_formatter_SUITE \
logger_legacy_SUITE \
+ logger_olp_SUITE \
+ logger_proxy_SUITE \
logger_simple_h_SUITE \
logger_std_h_SUITE \
+ logger_stress_SUITE \
logger_test_lib \
os_SUITE \
pg2_SUITE \
@@ -127,6 +130,9 @@ ERL_COMPILE_FLAGS +=
EBIN = .
+TARGETS = $(MODULES:%=$(EBIN)/%.$(EMULATOR))
+
+
# ----------------------------------------------------
# Targets
# ----------------------------------------------------
@@ -147,6 +153,9 @@ clean:
docs:
+targets: $(TARGETS)
+
+
# ----------------------------------------------------
# Release Target
# ----------------------------------------------------
diff --git a/lib/kernel/test/application_SUITE.erl b/lib/kernel/test/application_SUITE.erl
index 5c35b82207..94d7c17712 100644
--- a/lib/kernel/test/application_SUITE.erl
+++ b/lib/kernel/test/application_SUITE.erl
@@ -31,6 +31,7 @@
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,
+ set_env/1, set_env_persistent/1, set_env_errors/1,
permit_false_start_local/1, permit_false_start_dist/1, script_start/1,
nodedown_start/1, init2973/0, loop2973/0, loop5606/1]).
@@ -55,6 +56,7 @@ 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,
{group, distr_changed}, config_change, shutdown_func, shutdown_timeout,
shutdown_deadlock, config_relative_paths,
persistent_env].
@@ -1944,6 +1946,101 @@ get_appls([_ | T], Res) ->
get_appls([], Res) ->
Res.
+%% Test set_env/1.
+set_env(Conf) when is_list(Conf) ->
+ ok = application:set_env([{appinc, [{own2, persist}, {not_in_app, persist}]},
+ {unknown_app, [{key, persist}]}]),
+
+ %% own_env1 and own2 are set in appinc
+ undefined = application:get_env(appinc, own_env1),
+ {ok, persist} = application:get_env(appinc, own2),
+ {ok, persist} = application:get_env(appinc, not_in_app),
+ {ok, persist} = application:get_env(unknown_app, key),
+
+ ok = application:load(appinc()),
+ {ok, value1} = application:get_env(appinc, own_env1),
+ {ok, val2} = application:get_env(appinc, own2),
+ {ok, persist} = application:get_env(appinc, not_in_app),
+ {ok, persist} = application:get_env(unknown_app, key),
+
+ %% On reload, values are lost
+ ok = application:unload(appinc),
+ ok = application:load(appinc()),
+ {ok, value1} = application:get_env(appinc, own_env1),
+ {ok, val2} = application:get_env(appinc, own2),
+ undefined = application:get_env(appinc, not_in_app),
+
+ %% Clean up
+ ok = application:unload(appinc).
+
+%% Test set_env/2 with persistent true.
+set_env_persistent(Conf) when is_list(Conf) ->
+ Opts = [{persistent, true}],
+ ok = application:set_env([{appinc, [{own2, persist}, {not_in_app, persist}]},
+ {unknown_app, [{key, persist}]}], Opts),
+
+ %% own_env1 and own2 are set in appinc
+ undefined = application:get_env(appinc, own_env1),
+ {ok, persist} = application:get_env(appinc, own2),
+ {ok, persist} = application:get_env(appinc, not_in_app),
+ {ok, persist} = application:get_env(unknown_app, key),
+
+ ok = application:load(appinc()),
+ {ok, value1} = application:get_env(appinc, own_env1),
+ {ok, persist} = application:get_env(appinc, own2),
+ {ok, persist} = application:get_env(appinc, not_in_app),
+ {ok, persist} = application:get_env(unknown_app, key),
+
+ %% On reload, values are not lost
+ ok = application:unload(appinc),
+ ok = application:load(appinc()),
+ {ok, value1} = application:get_env(appinc, own_env1),
+ {ok, persist} = application:get_env(appinc, own2),
+ {ok, persist} = application:get_env(appinc, not_in_app),
+
+ %% Clean up
+ ok = application:unload(appinc).
+
+set_env_errors(Conf) when is_list(Conf) ->
+ "application: 1; application name must be an atom" =
+ badarg_msg(fun() -> application:set_env([{1, []}]) end),
+
+ "application: foo; parameters must be a list" =
+ badarg_msg(fun() -> application:set_env([{foo, bar}]) end),
+
+ "invalid application config: foo_bar" =
+ badarg_msg(fun() -> application:set_env([foo_bar]) end),
+
+ "application: foo; invalid parameter name: 1" =
+ badarg_msg(fun() -> application:set_env([{foo, [{1, 2}]}]) end),
+
+ "application: foo; invalid parameter: config" =
+ badarg_msg(fun() -> application:set_env([{foo, [config]}]) end),
+
+ "application: kernel; erroneous parameter: distributed" =
+ badarg_msg(fun() -> application:set_env([{kernel, [{distributed, config}]}]) end),
+
+ %% This will raise in the future
+ ct:capture_start(),
+ _ = application:set_env([{foo, []}, {foo, []}]),
+ timer:sleep(100),
+ ct:capture_stop(),
+ [_ | _] = string:find(ct:capture_get(), "duplicate application config: foo"),
+
+ ct:capture_start(),
+ _ = application:set_env([{foo, [{bar, baz}, {bar, bat}]}]),
+ timer:sleep(100),
+ ct:capture_stop(),
+ [_ | _] = string:find(ct:capture_get(), "application: foo; duplicate parameter: bar"),
+
+ ok.
+
+badarg_msg(Fun) ->
+ try Fun() of
+ _ -> ct:fail(try_succeeded)
+ catch
+ error:{badarg, Msg} -> Msg
+ end.
%% Test set_env/4 and unset_env/3 with persistent true.
persistent_env(Conf) when is_list(Conf) ->
diff --git a/lib/kernel/test/code_SUITE.erl b/lib/kernel/test/code_SUITE.erl
index 1314316c13..64e0b9d8dd 100644
--- a/lib/kernel/test/code_SUITE.erl
+++ b/lib/kernel/test/code_SUITE.erl
@@ -525,7 +525,7 @@ upgrade(Config) ->
T = [beam, hipe],
[upgrade_do(DataDir, Client, T) || Client <- T],
- case hipe:llvm_support_available() of
+ case hipe:erllvm_is_supported() of
false -> ok;
true ->
T2 = [beam, hipe_llvm],
@@ -1021,6 +1021,13 @@ mult_lib_remove_prefix([H|T1], [H|T2]) ->
mult_lib_remove_prefix([$/|T], []) -> T.
bad_erl_libs(Config) when is_list(Config) ->
+ %% Preserve ERL_LIBS if set.
+ BadLibs0 = "/no/such/dir",
+ BadLibs =
+ case os:getenv("ERL_LIBS") of
+ false -> BadLibs0;
+ Libs -> BadLibs0 ++ ":" ++ Libs
+ end,
{ok,Node} =
test_server:start_node(bad_erl_libs, slave, []),
Code = rpc:call(Node,code,get_path,[]),
@@ -1028,10 +1035,9 @@ bad_erl_libs(Config) when is_list(Config) ->
{ok,Node2} =
test_server:start_node(bad_erl_libs, slave,
- [{args,"-env ERL_LIBS /no/such/dir"}]),
+ [{args,"-env ERL_LIBS " ++ BadLibs}]),
Code2 = rpc:call(Node,code,get_path,[]),
test_server:stop_node(Node2),
-
%% Test that code path is not affected by the faulty ERL_LIBS
Code = Code2,
diff --git a/lib/kernel/test/inet_SUITE.erl b/lib/kernel/test/inet_SUITE.erl
index f436eafad3..44ec7e7076 100644
--- a/lib/kernel/test/inet_SUITE.erl
+++ b/lib/kernel/test/inet_SUITE.erl
@@ -21,6 +21,7 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/inet.hrl").
+-include_lib("kernel/src/inet_res.hrl").
-include_lib("kernel/src/inet_dns.hrl").
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
@@ -34,7 +35,7 @@
ipv4_to_ipv6/0, ipv4_to_ipv6/1,
host_and_addr/0, host_and_addr/1,
t_gethostnative/1,
- gethostnative_parallell/1, cname_loop/1,
+ gethostnative_parallell/1, cname_loop/1, missing_hosts_reload/1,
gethostnative_soft_restart/0, gethostnative_soft_restart/1,
gethostnative_debug_level/0, gethostnative_debug_level/1,
lookup_bad_search_option/1,
@@ -56,7 +57,7 @@ all() ->
[t_gethostbyaddr, t_gethostbyname, t_getaddr,
t_gethostbyaddr_v6, t_gethostbyname_v6, t_getaddr_v6,
ipv4_to_ipv6, host_and_addr, {group, parse},
- t_gethostnative, gethostnative_parallell, cname_loop,
+ t_gethostnative, gethostnative_parallell, cname_loop, missing_hosts_reload,
gethostnative_debug_level, gethostnative_soft_restart,
lookup_bad_search_option,
getif, getif_ifr_name_overflow, getservbyname_overflow,
@@ -97,7 +98,7 @@ end_per_group(_GroupName, Config) ->
init_per_testcase(lookup_bad_search_option, Config) ->
Db = inet_db,
Key = res_lookup,
- %% The bad option can not enter through inet_db:set_lookup/1,
+ %% The bad option cannot enter through inet_db:set_lookup/1,
%% but through e.g .inetrc.
Prev = ets:lookup(Db, Key),
ets:delete(Db, Key),
@@ -840,6 +841,32 @@ cname_loop(Config) when is_list(Config) ->
ok.
+%% Test that hosts file gets reloaded correctly in case when it
+% was missing during initial startup
+missing_hosts_reload(Config) when is_list(Config) ->
+ RootDir = proplists:get_value(priv_dir,Config),
+ HostsFile = filename:join(RootDir, atom_to_list(?MODULE) ++ ".hosts"),
+ InetRc = filename:join(RootDir, "inetrc"),
+ ok = file:write_file(InetRc, "{hosts_file, \"" ++ HostsFile ++ "\"}.\n"),
+ {error, enoent} = file:read_file_info(HostsFile),
+ % start a node
+ Pa = filename:dirname(code:which(?MODULE)),
+ {ok, TestNode} = test_server:start_node(?MODULE, slave,
+ [{args, "-pa " ++ Pa ++ " -kernel inetrc '\"" ++ InetRc ++ "\"'"}]),
+ % ensure it has our RC
+ Rc = rpc:call(TestNode, inet_db, get_rc, []),
+ {hosts_file, HostsFile} = lists:keyfind(hosts_file, 1, Rc),
+ % ensure it does not resolve
+ {error, nxdomain} = rpc:call(TestNode, inet_hosts, gethostbyname, ["somehost"]),
+ % write hosts file
+ ok = file:write_file(HostsFile, "1.2.3.4 somehost"),
+ % wait for cached timestamp to expire
+ timer:sleep(?RES_FILE_UPDATE_TM * 1000 + 100),
+ % ensure it DOES resolve
+ {ok,{hostent,"somehost",[],inet,4,[{1,2,3,4}]}} =
+ rpc:call(TestNode, inet_hosts, gethostbyname, ["somehost"]),
+ % cleanup
+ true = test_server:stop_node(TestNode).
%% These must be run in the whole suite since they need
%% the host list and require inet_gethost_native to be started.
diff --git a/lib/kernel/test/inet_res_SUITE.erl b/lib/kernel/test/inet_res_SUITE.erl
index df6e48abae..cbec8d430c 100644
--- a/lib/kernel/test/inet_res_SUITE.erl
+++ b/lib/kernel/test/inet_res_SUITE.erl
@@ -531,7 +531,7 @@ edns0(Config) when is_list(Config) ->
case os:version() of
{M,V,_} when M < 5; M == 5, V =< 8 ->
%% In our test park only known platform
- %% with an DNS resolver that can not do
+ %% with an DNS resolver that cannot do
%% EDNS0.
{comment,"No EDNS0"}
end
diff --git a/lib/kernel/test/init_SUITE.erl b/lib/kernel/test/init_SUITE.erl
index 4f90260f98..a0154b2694 100644
--- a/lib/kernel/test/init_SUITE.erl
+++ b/lib/kernel/test/init_SUITE.erl
@@ -459,7 +459,7 @@ find_system_procs([], SysProcs) ->
SysProcs#sys_procs.prim_file];
find_system_procs([P|Ps], SysProcs) ->
case process_info(P, [initial_call, priority]) of
- [{initial_call,{otp_ring0,start,2}},_] ->
+ [{initial_call,{erl_init,start,2}},_] ->
undefined = SysProcs#sys_procs.init,
find_system_procs(Ps, SysProcs#sys_procs{init = P});
[{initial_call,{erts_code_purger,start,0}},_] ->
diff --git a/lib/kernel/test/kernel_bench.spec b/lib/kernel/test/kernel_bench.spec
index 4de133f21b..898ceb59e0 100644
--- a/lib/kernel/test/kernel_bench.spec
+++ b/lib/kernel/test/kernel_bench.spec
@@ -1,2 +1,3 @@
{groups,"../kernel_test",zlib_SUITE,[bench]}.
{groups,"../kernel_test",file_SUITE,[bench]}.
+{suites,"../kernel_test",[logger_stress_SUITE]}.
diff --git a/lib/kernel/test/logger.cover b/lib/kernel/test/logger.cover
index 960bc0abff..9691aa295e 100644
--- a/lib/kernel/test/logger.cover
+++ b/lib/kernel/test/logger.cover
@@ -4,9 +4,12 @@
logger_backend,
logger_config,
logger_disk_log_h,
- logger_h_common,
logger_filters,
logger_formatter,
+ logger_handler_watcher,
+ logger_h_common,
+ logger_olp,
+ logger_proxy,
logger_server,
logger_simple_h,
logger_std_h,
diff --git a/lib/kernel/test/logger.spec b/lib/kernel/test/logger.spec
index 1ab90b3e93..3aec37951d 100644
--- a/lib/kernel/test/logger.spec
+++ b/lib/kernel/test/logger.spec
@@ -7,5 +7,7 @@
logger_filters_SUITE,
logger_formatter_SUITE,
logger_legacy_SUITE,
+ logger_olp_SUITE,
+ logger_proxy_SUITE,
logger_simple_h_SUITE,
logger_std_h_SUITE]}.
diff --git a/lib/kernel/test/logger_SUITE.erl b/lib/kernel/test/logger_SUITE.erl
index d831d0d108..70bb775db8 100644
--- a/lib/kernel/test/logger_SUITE.erl
+++ b/lib/kernel/test/logger_SUITE.erl
@@ -101,7 +101,8 @@ all() ->
compare_levels,
process_metadata,
app_config,
- kernel_config].
+ kernel_config,
+ pretty_print].
start_stop(_Config) ->
S = whereis(logger),
@@ -898,14 +899,14 @@ process_metadata(_Config) ->
undefined = logger:get_process_metadata(),
{error,badarg} = ?TRY(logger:set_process_metadata(bad)),
ok = logger:add_handler(h1,?MODULE,#{level=>notice,filter_default=>log}),
- Time = erlang:system_time(microsecond),
+ Time = logger:timestamp(),
ProcMeta = #{time=>Time,line=>0,custom=>proc},
ok = logger:set_process_metadata(ProcMeta),
S1 = ?str,
?LOG_NOTICE(S1,#{custom=>macro}),
check_logged(notice,S1,#{time=>Time,line=>0,custom=>macro}),
- Time2 = erlang:system_time(microsecond),
+ Time2 = logger:timestamp(),
S2 = ?str,
?LOG_NOTICE(S2,#{time=>Time2,line=>1,custom=>macro}),
check_logged(notice,S2,#{time=>Time2,line=>1,custom=>macro}),
@@ -1141,6 +1142,61 @@ kernel_config(Config) ->
ok.
+pretty_print(Config) ->
+ ok = logger:add_handler(?FUNCTION_NAME,logger_std_h,#{}),
+ ok = logger:set_module_level([module1,module2],debug),
+
+ ct:capture_start(),
+ logger:i(),
+ ct:capture_stop(),
+ I0 = ct:capture_get(),
+
+ ct:capture_start(),
+ logger:i(primary),
+ ct:capture_stop(),
+ IPrim = ct:capture_get(),
+
+ ct:capture_start(),
+ logger:i(handlers),
+ ct:capture_stop(),
+ IHs = ct:capture_get(),
+
+ ct:capture_start(),
+ logger:i(proxy),
+ ct:capture_stop(),
+ IProxy = ct:capture_get(),
+
+ ct:capture_start(),
+ logger:i(modules),
+ ct:capture_stop(),
+ IMs = ct:capture_get(),
+
+ I02 = lists:append([IPrim,IHs,IProxy,IMs]),
+ %% ct:log("~p~n",[I0]),
+ %% ct:log("~p~n",[I02]),
+ I0 = I02,
+
+ ct:capture_start(),
+ logger:i(handlers),
+ ct:capture_stop(),
+ IHs = ct:capture_get(),
+
+ Ids = logger:get_handler_ids(),
+ IHs2 =
+ lists:append(
+ [begin
+ ct:capture_start(),
+ logger:i(Id),
+ ct:capture_stop(),
+ [_|IH] = ct:capture_get(),
+ IH
+ end || Id <- Ids]),
+
+ %% ct:log("~p~n",[IHs]),
+ %% ct:log("~p~n",[["Handler configuration: \n"|IHs2]]),
+ IHs = ["Handler configuration: \n"|IHs2],
+ ok.
+
%%%-----------------------------------------------------------------
%%% Internal
check_logged(Level,Format,Args,Meta) ->
diff --git a/lib/kernel/test/logger_disk_log_h_SUITE.erl b/lib/kernel/test/logger_disk_log_h_SUITE.erl
index 87b8250781..13b30835a1 100644
--- a/lib/kernel/test/logger_disk_log_h_SUITE.erl
+++ b/lib/kernel/test/logger_disk_log_h_SUITE.erl
@@ -24,6 +24,7 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/logger.hrl").
-include_lib("kernel/src/logger_internal.hrl").
+-include_lib("kernel/src/logger_olp.hrl").
-include_lib("kernel/src/logger_h_common.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
-include_lib("kernel/include/file.hrl").
@@ -97,7 +98,6 @@ all() ->
formatter_fail,
config_fail,
bad_input,
- info_and_reset,
reconfig,
sync,
disk_log_full,
@@ -293,7 +293,7 @@ logging(Config) ->
ok = start_and_add(Name, #{filter_default=>log,
formatter=>{?MODULE,self()}},
#{file => LogFile}),
- MsgFormatter = fun(Term) -> {io_lib:format("Term:~p",[Term]),[]} end,
+ MsgFormatter = fun(Term) -> {"Term:~p",[Term]} end,
logger:notice([{x,y}], #{report_cb => MsgFormatter}),
logger:notice([{x,y}], #{}),
ct:pal("Checking contents of ~p", [?log_no(LogFile,1)]),
@@ -306,9 +306,9 @@ logging(cleanup, _Config) ->
filter_config(_Config) ->
ok = logger:add_handler(?MODULE,logger_disk_log_h,#{}),
{ok,#{config:=HConfig}=Config} = logger:get_handler_config(?MODULE),
- HConfig = maps:without([handler_pid,mode_tab],HConfig),
+ HConfig = maps:without([olp],HConfig),
- FakeFullHConfig = HConfig#{handler_pid=>self(),mode_tab=>erlang:make_ref()},
+ FakeFullHConfig = HConfig#{olp=>{regname,self(),erlang:make_ref()}},
#{config:=HConfig} =
logger_disk_log_h:filter_config(Config#{config=>FakeFullHConfig}),
ok.
@@ -351,9 +351,7 @@ errors(Config) ->
%% Read-only fields may (accidentially) be included in the change,
%% but it won't take effect
{ok,C} = logger:get_handler_config(Name1),
- ok = logger:set_handler_config(Name1,config,
- #{handler_pid=>self(),
- mode_tab=>erlang:make_ref()}),
+ ok = logger:set_handler_config(Name1,config,#{olp=>dummyvalue}),
{ok,C} = logger:get_handler_config(Name1),
@@ -419,19 +417,16 @@ config_fail(_Config) ->
filter_default=>log,
formatter=>{?MODULE,self()}}),
- {error,{handler_not_added,{invalid_config,logger_disk_log_h,
- {invalid_levels,#{drop_mode_qlen:=1}}}}} =
+ {error,{handler_not_added,{invalid_olp_levels,#{drop_mode_qlen:=1}}}} =
logger:add_handler(?MODULE,logger_disk_log_h,
#{config => #{drop_mode_qlen=>1}}),
- {error,{handler_not_added,{invalid_config,logger_disk_log_h,
- {invalid_levels,#{sync_mode_qlen:=43,
- drop_mode_qlen:=42}}}}} =
+ {error,{handler_not_added,{invalid_olp_levels,#{sync_mode_qlen:=43,
+ drop_mode_qlen:=42}}}} =
logger:add_handler(?MODULE,logger_disk_log_h,
#{config => #{sync_mode_qlen=>43,
drop_mode_qlen=>42}}),
- {error,{handler_not_added,{invalid_config,logger_disk_log_h,
- {invalid_levels,#{drop_mode_qlen:=43,
- flush_qlen:=42}}}}} =
+ {error,{handler_not_added,{invalid_olp_levels,#{drop_mode_qlen:=43,
+ flush_qlen:=42}}}} =
logger:add_handler(?MODULE,logger_disk_log_h,
#{config => #{drop_mode_qlen=>43,
flush_qlen=>42}}),
@@ -445,7 +440,7 @@ config_fail(_Config) ->
#{max_no_files=>2}),
%% incorrect values of OP params
{ok,#{config := HConfig}} = logger:get_handler_config(?MODULE),
- {error,{invalid_config,logger_disk_log_h,{invalid_levels,_}}} =
+ {error,{invalid_olp_levels,_}} =
logger:update_handler_config(?MODULE,config,
HConfig#{sync_mode_qlen=>100,
flush_qlen=>99}),
@@ -459,18 +454,7 @@ config_fail(cleanup,_Config) ->
bad_input(_Config) ->
{error,{badarg,{filesync,["BadType"]}}} =
- logger_disk_log_h:filesync("BadType"),
- {error,{badarg,{info,["BadType"]}}} = logger_disk_log_h:info("BadType"),
- {error,{badarg,{reset,["BadType"]}}} = logger_disk_log_h:reset("BadType").
-
-info_and_reset(_Config) ->
- ok = logger:add_handler(?MODULE,logger_disk_log_h,
- #{filter_default=>log,
- formatter=>{?MODULE,self()}}),
- #{id := ?MODULE} = logger_disk_log_h:info(?MODULE),
- ok = logger_disk_log_h:reset(?MODULE).
-info_and_reset(cleanup,_Config) ->
- logger:remove_handler(?MODULE).
+ logger_disk_log_h:filesync("BadType").
reconfig(Config) ->
Dir = ?config(priv_dir,Config),
@@ -479,7 +463,7 @@ reconfig(Config) ->
#{filter_default=>log,
filters=>?DEFAULT_HANDLER_FILTERS([?MODULE]),
formatter=>{?MODULE,self()}}),
- #{id := ?MODULE,
+ #{%id := ?MODULE,
sync_mode_qlen := ?SYNC_MODE_QLEN,
drop_mode_qlen := ?DROP_MODE_QLEN,
flush_qlen := ?FLUSH_QLEN,
@@ -490,13 +474,14 @@ reconfig(Config) ->
overload_kill_qlen := ?OVERLOAD_KILL_QLEN,
overload_kill_mem_size := ?OVERLOAD_KILL_MEM_SIZE,
overload_kill_restart_after := ?OVERLOAD_KILL_RESTART_AFTER,
- filesync_repeat_interval := ?FILESYNC_REPEAT_INTERVAL,
- handler_state :=
- #{log_opts := #{type := ?DISK_LOG_TYPE,
- max_no_files := ?DISK_LOG_MAX_NO_FILES,
- max_no_bytes := ?DISK_LOG_MAX_NO_BYTES,
- file := DiskLogFile}}} =
- logger_disk_log_h:info(?MODULE),
+ cb_state :=
+ #{handler_state :=
+ #{log_opts := #{type := ?DISK_LOG_TYPE,
+ max_no_files := ?DISK_LOG_MAX_NO_FILES,
+ max_no_bytes := ?DISK_LOG_MAX_NO_BYTES,
+ file := DiskLogFile}},
+ filesync_repeat_interval := ?FILESYNC_REPEAT_INTERVAL}} =
+ logger_olp:info(h_proc_name()),
{ok,#{config :=
#{sync_mode_qlen := ?SYNC_MODE_QLEN,
drop_mode_qlen := ?DROP_MODE_QLEN,
@@ -527,7 +512,7 @@ reconfig(Config) ->
overload_kill_restart_after => infinity,
filesync_repeat_interval => no_repeat},
ok = logger:set_handler_config(?MODULE, config, HConfig1),
- #{id := ?MODULE,
+ #{%id := ?MODULE,
sync_mode_qlen := 1,
drop_mode_qlen := 2,
flush_qlen := 3,
@@ -538,8 +523,8 @@ reconfig(Config) ->
overload_kill_qlen := 100000,
overload_kill_mem_size := 10000000,
overload_kill_restart_after := infinity,
- filesync_repeat_interval := no_repeat} =
- logger_disk_log_h:info(?MODULE),
+ cb_state := #{filesync_repeat_interval := no_repeat}} =
+ logger_olp:info(h_proc_name()),
{ok,#{config:=HConfig1}} = logger:get_handler_config(?MODULE),
ok = logger:update_handler_config(?MODULE, config,
@@ -577,12 +562,13 @@ reconfig(Config) ->
max_no_files => 1,
max_no_bytes => 1024,
file => File}}),
- #{handler_state :=
- #{log_opts := #{type := halt,
- max_no_files := 1,
- max_no_bytes := 1024,
- file := File}}} =
- logger_disk_log_h:info(?MODULE),
+ #{cb_state :=
+ #{handler_state :=
+ #{log_opts := #{type := halt,
+ max_no_files := 1,
+ max_no_bytes := 1024,
+ file := File}}}} =
+ logger_olp:info(h_proc_name()),
{ok,#{config :=
#{type := halt,
max_no_files := 1,
@@ -650,13 +636,8 @@ sync(Config) ->
{ok,#{config := HConfig}} = logger:get_handler_config(?MODULE),
HConfig1 = HConfig#{filesync_repeat_interval => no_repeat},
ok = logger:update_handler_config(?MODULE, config, HConfig1),
-
no_repeat = maps:get(filesync_repeat_interval,
- logger_disk_log_h:info(?MODULE)),
- %% The following timer is to make sure the time from last log
- %% ("first") to next ("second") is long enough, so the a flush is
- %% triggered by the idle timeout between "fourth" and "fifth".
- timer:sleep(?IDLE_DETECT_TIME_MSEC*2),
+ maps:get(cb_state,logger_olp:info(h_proc_name()))),
start_tracer([{logger_disk_log_h,disk_log_write,3},
{disk_log,sync,1}],
@@ -666,10 +647,10 @@ sync(Config) ->
{disk_log,sync}]),
logger:notice("second", ?domain),
- timer:sleep(?IDLE_DETECT_TIME_MSEC*2),
+ timer:sleep(?IDLE_DETECT_TIME*2),
logger:notice("third", ?domain),
%% wait for automatic disk_log_sync
- check_tracer(?IDLE_DETECT_TIME_MSEC*2),
+ check_tracer(?IDLE_DETECT_TIME*2),
try_read_file(Log, {ok,<<"first\nsecond\nthird\n">>}, 1000),
@@ -678,14 +659,15 @@ sync(Config) ->
WaitT = 4500,
OneSync = {logger_h_common,handle_cast,repeated_filesync},
%% receive 1 repeated_filesync per sec
- start_tracer([{logger_h_common,handle_cast,2}],
+ start_tracer([{{logger_h_common,handle_cast,2},
+ [{[repeated_filesync,'_'],[],[{message,{caller}}]}]}],
[OneSync || _ <- lists:seq(1, trunc(WaitT/SyncInt))]),
HConfig2 = HConfig#{filesync_repeat_interval => SyncInt},
ok = logger:update_handler_config(?MODULE, config, HConfig2),
SyncInt = maps:get(filesync_repeat_interval,
- logger_disk_log_h:info(?MODULE)),
+ maps:get(cb_state,logger_olp:info(h_proc_name()))),
timer:sleep(WaitT),
HConfig3 = HConfig#{filesync_repeat_interval => no_repeat},
ok = logger:update_handler_config(?MODULE, config, HConfig3),
@@ -803,7 +785,7 @@ disk_log_full(cleanup, _Config) ->
dbg:stop_clear(),
logger:remove_handler(?MODULE).
-disk_log_events(Config) ->
+disk_log_events(_Config) ->
Node = node(),
Log = ?MODULE,
ok = logger:add_handler(?MODULE,
@@ -860,10 +842,12 @@ write_failure(Config) ->
rpc:call(Node, ets, insert, [?TEST_HOOKS_TAB,{tester,self()}]),
rpc:call(Node, ?MODULE, set_internal_log, [?MODULE,internal_log]),
rpc:call(Node, ?MODULE, set_result, [disk_log_write,ok]),
- HState = rpc:call(Node, logger_disk_log_h, info, [?STANDARD_HANDLER]),
- ct:pal("LogOpts = ~p", [LogOpts = maps:get(log_opts,
- maps:get(handler_state,HState))]),
-
+ HState = rpc:call(Node, logger_olp, info, [h_proc_name(?STANDARD_HANDLER)]),
+ LogOpts = maps:get(log_opts,
+ maps:get(handler_state,
+ maps:get(cb_state,HState))),
+ ct:pal("LogOpts = ~p", [LogOpts]),
+
%% ?check and ?check_no_log in this test only check for internal log events
ok = log_on_remote_node(Node, "Logged1"),
rpc:call(Node, logger_disk_log_h, filesync, [?STANDARD_HANDLER]),
@@ -914,15 +898,16 @@ sync_failure(Config) ->
rpc:call(Node, ets, insert, [?TEST_HOOKS_TAB,{tester,self()}]),
rpc:call(Node, ?MODULE, set_internal_log, [?MODULE,internal_log]),
rpc:call(Node, ?MODULE, set_result, [disk_log_sync,ok]),
- HState = rpc:call(Node, logger_disk_log_h, info, [?STANDARD_HANDLER]),
- LogOpts = maps:get(log_opts, maps:get(handler_state,HState)),
+ HState = rpc:call(Node, logger_olp, info, [h_proc_name(?STANDARD_HANDLER)]),
+ LogOpts = maps:get(log_opts, maps:get(handler_state,
+ maps:get(cb_state,HState))),
SyncInt = 500,
ok = rpc:call(Node, logger, update_handler_config,
[?STANDARD_HANDLER, config,
#{filesync_repeat_interval => SyncInt}]),
- Info = rpc:call(Node, logger_disk_log_h, info, [?STANDARD_HANDLER]),
- SyncInt = maps:get(filesync_repeat_interval, Info),
+ Info = rpc:call(Node, logger_olp, info, [h_proc_name(?STANDARD_HANDLER)]),
+ SyncInt = maps:get(filesync_repeat_interval, maps:get(cb_state, Info)),
ok = log_on_remote_node(Node, "Logged1"),
?check_no_log,
@@ -1198,7 +1183,7 @@ qlen_kill_new(Config) ->
receive
{'DOWN', MRef, _, _, Info} ->
case Info of
- {shutdown,{overloaded,?MODULE,QLen,Mem}} ->
+ {shutdown,{overloaded,QLen,Mem}} ->
ct:pal("Terminated with qlen = ~w, mem = ~w", [QLen,Mem]);
killed ->
ct:pal("Slow shutdown, handler process was killed!", [])
@@ -1208,7 +1193,7 @@ qlen_kill_new(Config) ->
ok
after
5000 ->
- Info = logger_disk_log_h:info(?MODULE),
+ Info = logger_olp:info(h_proc_name()),
ct:pal("Handler state = ~p", [Info]),
ct:fail("Handler not dead! It should not have survived this!")
end.
@@ -1235,7 +1220,7 @@ mem_kill_new(Config) ->
receive
{'DOWN', MRef, _, _, Info} ->
case Info of
- {shutdown,{overloaded,?MODULE,QLen,Mem}} ->
+ {shutdown,{overloaded,QLen,Mem}} ->
ct:pal("Terminated with qlen = ~w, mem = ~w", [QLen,Mem]);
killed ->
ct:pal("Slow shutdown, handler process was killed!", [])
@@ -1245,7 +1230,7 @@ mem_kill_new(Config) ->
ok
after
5000 ->
- Info = logger_disk_log_h:info(?MODULE),
+ Info = logger_olp:info(h_proc_name()),
ct:pal("Handler state = ~p", [Info]),
ct:fail("Handler not dead! It should not have survived this!")
end.
@@ -1271,7 +1256,7 @@ restart_after(Config) ->
ok
after
5000 ->
- Info1 = logger_std_h:info(?MODULE),
+ Info1 = logger_olp:info(h_proc_name()),
ct:pal("Handler state = ~p", [Info1]),
ct:fail("Handler not dead! It should not have survived this!")
end,
@@ -1295,7 +1280,7 @@ restart_after(Config) ->
ok
after
5000 ->
- Info2 = logger_std_h:info(?MODULE),
+ Info2 = logger_olp:info(h_proc_name()),
ct:pal("Handler state = ~p", [Info2]),
ct:fail("Handler not dead! It should not have survived this!")
end,
@@ -1316,11 +1301,15 @@ handler_requests_under_load(Config) ->
flush_qlen => 2000,
burst_limit_enable => false}},
ok = logger:update_handler_config(?MODULE, NewHConfig),
- Pid = spawn_link(fun() -> send_requests(?MODULE, 1, [{filesync,[]},
- {info,[]},
- {reset,[]},
- {change_config,[]}])
- end),
+ Pid = spawn_link(
+ fun() -> send_requests(1,[{logger_disk_log_h,filesync,[?MODULE],[]},
+ {logger_olp,info,[h_proc_name()],[]},
+ {logger_olp,reset,[h_proc_name()],[]},
+ {logger,update_handler_config,
+ [?MODULE, config,
+ #{overload_kill_enable => false}],
+ []}])
+ end),
Procs = 100,
Sent = Procs * send_burst({n,5000}, {spawn,Procs,10}, {chars,79}, notice),
Pid ! {self(),finish},
@@ -1332,29 +1321,22 @@ handler_requests_under_load(Config) ->
[E || E <- Res,
is_tuple(E) andalso (element(1,E) == error)]
end,
- Errors = [{Req,FindError(Res)} || {Req,Res} <- ReqResult],
- NoOfReqs = lists:foldl(fun({_,Res}, N) -> N + length(Res) end, 0, ReqResult),
+ Errors = [{Func,FindError(Res)} || {_,Func,_,Res} <- ReqResult],
+ NoOfReqs = lists:foldl(fun({_,_,_,Res}, N) -> N + length(Res) end,
+ 0, ReqResult),
ct:pal("~w requests made. Errors: ~n~p", [NoOfReqs,Errors]),
ok = file_delete(Log).
handler_requests_under_load(cleanup, _Config) ->
ok = stop_handler(?MODULE).
-send_requests(HName, TO, Reqs = [{Req,Res}|Rs]) ->
+send_requests(TO, Reqs = [{Mod,Func,Args,Res}|Rs]) ->
receive
{From,finish} ->
From ! {self(),Reqs}
after
TO ->
- Result =
- case Req of
- change_config ->
- logger:update_handler_config(HName, logger_disk_log_h,
- #{overload_kill_enable =>
- false});
- Func ->
- logger_disk_log_h:Func(HName)
- end,
- send_requests(HName, TO, Rs ++ [{Req,[Result|Res]}])
+ Result = apply(Mod,Func,Args),
+ send_requests(TO, Rs ++ [{Mod,Func,Args,[Result|Res]}])
end.
%%%-----------------------------------------------------------------
@@ -1472,15 +1454,6 @@ format(Msg,Tag) ->
erlang:display(Error),
exit(Error).
-remove(Handler, LogName) ->
- logger_disk_log_h:remove(Handler, LogName),
- HState = #{log_names := Logs} = logger_disk_log_h:info(),
- false = maps:is_key(LogName, HState),
- false = lists:member(LogName, Logs),
- false = logger_config:exist(?LOGGER_TABLE, LogName),
- {error,no_such_log} = disk_log:info(LogName),
- ok.
-
start_and_add(Name, Config, LogOpts) ->
HConfig = maps:get(config, Config, #{}),
HConfig1 = maps:merge(HConfig, LogOpts),
@@ -1607,7 +1580,9 @@ start_tracer(Trace,Expected) ->
ok.
tpl([{M,F,A}|Trace]) ->
- {ok,Match} = dbg:tpl(M,F,A,c),
+ tpl([{{M,F,A},c}|Trace]);
+tpl([{{M,F,A},MS}|Trace]) ->
+ {ok,Match} = dbg:tpl(M,F,A,MS),
case lists:keyfind(matched,1,Match) of
{_,_,1} ->
ok;
diff --git a/lib/kernel/test/logger_env_var_SUITE.erl b/lib/kernel/test/logger_env_var_SUITE.erl
index e8d1a313dc..9d2ad11be8 100644
--- a/lib/kernel/test/logger_env_var_SUITE.erl
+++ b/lib/kernel/test/logger_env_var_SUITE.erl
@@ -59,7 +59,8 @@ groups() ->
logger_undefined,
logger_many_handlers_default_first,
logger_many_handlers_default_last,
- logger_many_handlers_default_last_broken_filter
+ logger_many_handlers_default_last_broken_filter,
+ logger_proxy
]},
{bad,[],[bad_error_logger,
bad_level,
@@ -541,6 +542,19 @@ logger_many_handlers(Config, Env, LogErr, LogInfo, NumProgress) ->
ok.
+logger_proxy(Config) ->
+ %% assume current node runs with default settings
+ DefOpts = logger_olp:get_opts(logger_proxy),
+ {ok,_,Node} = setup(Config,
+ [{logger,[{proxy,#{sync_mode_qlen=>0,
+ drop_mode_qlen=>2}}]}]),
+ Expected = DefOpts#{sync_mode_qlen:=0,
+ drop_mode_qlen:=2},
+ Expected = rpc:call(Node,logger_olp,get_opts,[logger_proxy]),
+ Expected = rpc:call(Node,logger,get_proxy_config,[]),
+
+ ok.
+
sasl_compatible_false(Config) ->
Log = file(Config,?FUNCTION_NAME),
{ok,_,Node} = setup(Config,
diff --git a/lib/kernel/test/logger_formatter_SUITE.erl b/lib/kernel/test/logger_formatter_SUITE.erl
index 8c13f0f908..83e3e6c40a 100644
--- a/lib/kernel/test/logger_formatter_SUITE.erl
+++ b/lib/kernel/test/logger_formatter_SUITE.erl
@@ -867,7 +867,7 @@ my_try(Fun) ->
try Fun() catch C:R:S -> {C,R,hd(S)} end.
timestamp() ->
- erlang:system_time(microsecond).
+ logger:timestamp().
%% necessary?
add_time(#{time:=_}=Meta) ->
diff --git a/lib/kernel/test/logger_olp_SUITE.erl b/lib/kernel/test/logger_olp_SUITE.erl
new file mode 100644
index 0000000000..ea3eec89f5
--- /dev/null
+++ b/lib/kernel/test/logger_olp_SUITE.erl
@@ -0,0 +1,90 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018. All 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(logger_olp_SUITE).
+
+-compile(export_all).
+
+-include_lib("kernel/src/logger_olp.hrl").
+
+suite() ->
+ [{timetrap,{seconds,30}}].
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_group(_Group, Config) ->
+ Config.
+
+end_per_group(_Group, _Config) ->
+ ok.
+
+init_per_testcase(_TestCase, Config) ->
+ Config.
+
+end_per_testcase(Case, Config) ->
+ try apply(?MODULE,Case,[cleanup,Config])
+ catch error:undef -> ok
+ end,
+ ok.
+
+groups() ->
+ [].
+
+all() ->
+ [idle_timer].
+
+%%%-----------------------------------------------------------------
+%%% Test cases
+idle_timer(_Config) ->
+ {ok,_Pid,Olp} = logger_olp:start_link(?MODULE,?MODULE,self(),#{}),
+ [logger_olp:load(Olp,{msg,N}) || N<-lists:seq(1,3)],
+ timer:sleep(?IDLE_DETECT_TIME*2),
+ [{load,{msg,1}},
+ {load,{msg,2}},
+ {load,{msg,3}},
+ {notify,idle}] = test_server:messages_get(),
+ logger_olp:cast(Olp,hello),
+ timer:sleep(?IDLE_DETECT_TIME*2),
+ [{cast,hello}] = test_server:messages_get(),
+ ok.
+idle_timer(cleanup,_Config) ->
+ unlink(whereis(?MODULE)),
+ logger_olp:stop(?MODULE),
+ ok.
+
+%%%-----------------------------------------------------------------
+%%% Olp callbacks
+init(P) ->
+ {ok,P}.
+
+handle_load(M,P) ->
+ P ! {load,M},
+ P.
+
+handle_cast(M,P) ->
+ P ! {cast,M},
+ {noreply,P}.
+
+notify(N,P) ->
+ P ! {notify,N},
+ P.
diff --git a/lib/kernel/test/logger_proxy_SUITE.erl b/lib/kernel/test/logger_proxy_SUITE.erl
new file mode 100644
index 0000000000..777531e4ed
--- /dev/null
+++ b/lib/kernel/test/logger_proxy_SUITE.erl
@@ -0,0 +1,274 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018. All 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(logger_proxy_SUITE).
+
+-compile(export_all).
+
+%% -include_lib("common_test/include/ct.hrl").
+%% -include_lib("kernel/include/logger.hrl").
+%% -include_lib("kernel/src/logger_internal.hrl").
+
+%% -define(str,"Log from "++atom_to_list(?FUNCTION_NAME)++
+%% ":"++integer_to_list(?LINE)).
+%% -define(map_rep,#{function=>?FUNCTION_NAME, line=>?LINE}).
+%% -define(keyval_rep,[{function,?FUNCTION_NAME}, {line,?LINE}]).
+
+%% -define(MY_LOC(N),#{mfa=>{?MODULE,?FUNCTION_NAME,?FUNCTION_ARITY},
+%% file=>?FILE, line=>?LINE-N}).
+
+%% -define(TRY(X), my_try(fun() -> X end)).
+
+
+-define(HNAME,list_to_atom(lists:concat([?MODULE,"_",?FUNCTION_NAME]))).
+-define(LOC,#{mfa=>{?MODULE,?FUNCTION_NAME,?FUNCTION_ARITY},line=>?LINE}).
+-define(ENSURE_TIME,5000).
+
+suite() ->
+ [{timetrap,{seconds,30}},
+ {ct_hooks,[logger_test_lib]}].
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_group(_Group, Config) ->
+ Config.
+
+end_per_group(_Group, _Config) ->
+ ok.
+
+init_per_testcase(_TestCase, Config) ->
+ Config.
+
+end_per_testcase(Case, Config) ->
+ try apply(?MODULE,Case,[cleanup,Config])
+ catch error:undef -> ok
+ end,
+ ok.
+
+groups() ->
+ [].
+
+all() ->
+ [basic,
+ emulator,
+ remote,
+ remote_emulator,
+ config,
+ restart_after,
+ terminate].
+
+%%%-----------------------------------------------------------------
+%%% Test cases
+basic(_Config) ->
+ ok = logger:add_handler(?HNAME,?MODULE,#{config=>self()}),
+ logger_proxy ! {log,notice,"Log from: ~p; ~p",[?FUNCTION_NAME,?LINE],L1=?LOC},
+ ok = ensure(L1),
+ logger_proxy ! {log,notice,[{test_case,?FUNCTION_NAME},{line,?LINE}],L2=?LOC},
+ ok = ensure(L2),
+ logger_proxy:log({remote,node(),{log,notice,
+ "Log from: ~p; ~p",
+ [?FUNCTION_NAME,?LINE],
+ L3=?LOC}}),
+ ok = ensure(L3),
+ logger_proxy:log({remote,node(),{log,notice,
+ [{test_case,?FUNCTION_NAME},
+ {line,?LINE}],
+ L4=?LOC}}),
+ ok = ensure(L4),
+ ok.
+basic(cleanup,_Config) ->
+ ok = logger:remove_handler(?HNAME).
+
+emulator(_Config) ->
+ ok = logger:add_handler(?HNAME,?MODULE,#{config=>self()}),
+ Pid = spawn(fun() -> erlang:error(some_reason) end),
+ ok = ensure(#{pid=>Pid}),
+ ok.
+emulator(cleanup,_Config) ->
+ ok = logger:remove_handler(?HNAME).
+
+remote(Config) ->
+ {ok,_,Node} = logger_test_lib:setup(Config,[{logger,[{proxy,#{}}]}]),
+ ok = logger:add_handler(?HNAME,?MODULE,#{config=>self()}),
+ L1 = ?LOC, spawn(Node,fun() -> logger:notice("Log from ~p; ~p",[?FUNCTION_NAME,?LINE],L1) end),
+ ok = ensure(L1),
+ L2 = ?LOC, spawn(Node,fun() -> logger:notice([{test_case,?FUNCTION_NAME},{line,?LINE}],L2) end),
+ ok = ensure(L2),
+ ok.
+remote(cleanup,_Config) ->
+ ok = logger:remove_handler(?HNAME).
+
+remote_emulator(Config) ->
+ {ok,_,Node} = logger_test_lib:setup(Config,[{logger,[{proxy,#{}}]}]),
+ ok = logger:add_handler(?HNAME,?MODULE,#{config=>self()}),
+ Pid = spawn(Node,fun() -> erlang:error(some_reason) end),
+ ok = ensure(#{pid=>Pid}),
+ ok.
+remote_emulator(cleanup,_Config) ->
+ ok = logger:remove_handler(?HNAME).
+
+config(_Config) ->
+ C1 = #{sync_mode_qlen:=SQ,
+ drop_mode_qlen:=DQ} = logger:get_proxy_config(),
+ C1 = logger_olp:get_opts(logger_proxy),
+
+ %% Update the existing config with these two values
+ SQ1 = SQ+1,
+ DQ1 = DQ+1,
+ ok = logger:update_proxy_config(#{sync_mode_qlen=>SQ1,
+ drop_mode_qlen=>DQ1}),
+ C2 = logger:get_proxy_config(), % reads from ets table
+ C2 = logger_olp:get_opts(logger_proxy), % ensure consistency with process opts
+ C2 = C1#{sync_mode_qlen:=SQ1,
+ drop_mode_qlen:=DQ1},
+
+ %% Update the existing again with only one value
+ SQ2 = SQ+2,
+ ok = logger:update_proxy_config(#{sync_mode_qlen=>SQ2}),
+ C3 = logger:get_proxy_config(),
+ C3 = logger_olp:get_opts(logger_proxy),
+ C3 = C2#{sync_mode_qlen:=SQ2},
+
+ %% Set the config, i.e. merge with defaults
+ ok = logger:set_proxy_config(#{sync_mode_qlen=>SQ1}),
+ C4 = logger:get_proxy_config(),
+ C4 = logger_olp:get_opts(logger_proxy),
+ C4 = C1#{sync_mode_qlen:=SQ1},
+
+ %% Reset to default
+ ok = logger:set_proxy_config(#{}),
+ C5 = logger:get_proxy_config(),
+ C5 = logger_olp:get_opts(logger_proxy),
+ C5 = logger_proxy:get_default_config(),
+
+ %% Errors
+ {error,{invalid_olp_config,_}} =
+ logger:set_proxy_config(#{faulty_key=>1}),
+ {error,{invalid_olp_config,_}} =
+ logger:set_proxy_config(#{sync_mode_qlen=>infinity}),
+ {error,{invalid_config,[]}} = logger:set_proxy_config([]),
+
+ {error,{invalid_olp_config,_}} =
+ logger:update_proxy_config(#{faulty_key=>1}),
+ {error,{invalid_olp_config,_}} =
+ logger:update_proxy_config(#{sync_mode_qlen=>infinity}),
+ {error,{invalid_config,[]}} = logger:update_proxy_config([]),
+
+ C5 = logger:get_proxy_config(),
+ C5 = logger_olp:get_opts(logger_proxy),
+
+ ok.
+config(cleanup,_Config) ->
+ _ = logger:set_logger_proxy(logger_proxy:get_default_config()),
+ ok.
+
+restart_after(_Config) ->
+ Restart = 3000,
+ ok = logger:update_proxy_config(#{overload_kill_enable => true,
+ overload_kill_qlen => 10,
+ overload_kill_restart_after => Restart}),
+ Proxy = whereis(logger_proxy),
+ Proxy = erlang:system_info(system_logger),
+ ProxyConfig = logger:get_proxy_config(),
+ ProxyConfig = logger_olp:get_opts(logger_proxy),
+
+ Ref = erlang:monitor(process,Proxy),
+ spawn(fun() ->
+ [logger_proxy ! {log,debug,
+ [{test_case,?FUNCTION_NAME},
+ {line,?LINE}],
+ ?LOC} || _ <- lists:seq(1,100)]
+ end),
+ receive
+ {'DOWN',Ref,_,_,_Reason} ->
+ undefined = erlang:system_info(system_logger),
+ timer:sleep(Restart),
+ poll_restarted(10)
+ after 5000 ->
+ ct:fail(proxy_not_terminated)
+ end,
+
+ Proxy1 = whereis(logger_proxy),
+ Proxy1 = erlang:system_info(system_logger),
+ ProxyConfig = logger:get_proxy_config(),
+ ProxyConfig = logger_olp:get_opts(logger_proxy),
+
+ ok.
+restart_after(cleanup,_Config) ->
+ _ = logger:set_logger_proxy(logger_proxy:get_default_config()),
+ ok.
+
+%% Test that system_logger flag is set to logger process if
+%% logger_proxy terminates for other reason than overloaded.
+terminate(_Config) ->
+ Logger = whereis(logger),
+ Proxy = whereis(logger_proxy),
+ Proxy = erlang:system_info(system_logger),
+ ProxyConfig = logger:get_proxy_config(),
+ ProxyConfig = logger_olp:get_opts(logger_proxy),
+
+ Ref = erlang:monitor(process,Proxy),
+ ok = logger_olp:stop(Proxy),
+ receive
+ {'DOWN',Ref,_,_,_Reason} ->
+ Logger = erlang:system_info(system_logger),
+ logger_proxy:restart(),
+ poll_restarted(10)
+ after 5000 ->
+ ct:fail(proxy_not_terminated)
+ end,
+
+ Proxy1 = whereis(logger_proxy),
+ Proxy1 = erlang:system_info(system_logger),
+ ProxyConfig = logger:get_proxy_config(),
+ ProxyConfig = logger_olp:get_opts(logger_proxy),
+
+ ok.
+
+%%%-----------------------------------------------------------------
+%%% Internal functions
+
+poll_restarted(0) ->
+ ct:fail(proxy_not_restarted);
+poll_restarted(N) ->
+ timer:sleep(1000),
+ case whereis(logger_proxy) of
+ undefined ->
+ poll_restarted(N-1);
+ _Pid ->
+ ok
+ end.
+
+%% Logger handler callback
+log(#{meta:=Meta},#{config:=Pid}) ->
+ Pid ! {logged,Meta}.
+
+%% Check that the log from the logger callback function log/2 is received
+ensure(Match) ->
+ receive {logged,Meta} ->
+ case maps:with(maps:keys(Match),Meta) of
+ Match -> ok;
+ _NoMatch -> {error,Match,Meta,test_server:messages_get()}
+ end
+ after ?ENSURE_TIME -> {error,Match,test_server:messages_get()}
+ end.
diff --git a/lib/kernel/test/logger_std_h_SUITE.erl b/lib/kernel/test/logger_std_h_SUITE.erl
index eb17a6d857..b2c2c8ba67 100644
--- a/lib/kernel/test/logger_std_h_SUITE.erl
+++ b/lib/kernel/test/logger_std_h_SUITE.erl
@@ -25,10 +25,15 @@
-include_lib("kernel/include/logger.hrl").
-include_lib("kernel/src/logger_internal.hrl").
-include_lib("kernel/src/logger_h_common.hrl").
+-include_lib("kernel/src/logger_olp.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
-include_lib("kernel/include/file.hrl").
--define(check_no_log, [] = test_server:messages_get()).
+-define(check_no_log,
+ begin
+ timer:sleep(?IDLE_DETECT_TIME*2),
+ [] = test_server:messages_get()
+ end).
-define(check(Expected),
receive
{log,Expected} ->
@@ -115,7 +120,6 @@ all() ->
crash_std_h_to_file,
crash_std_h_to_disk_log,
bad_input,
- info_and_reset,
reconfig,
file_opts,
sync,
@@ -137,7 +141,8 @@ all() ->
mem_kill_std,
restart_after,
handler_requests_under_load,
- recreate_deleted_log
+ recreate_deleted_log,
+ reopen_changed_log
].
add_remove_instance_tty(_Config) ->
@@ -209,9 +214,9 @@ default_formatter(_Config) ->
filter_config(_Config) ->
ok = logger:add_handler(?MODULE,logger_std_h,#{}),
{ok,#{config:=HConfig}=Config} = logger:get_handler_config(?MODULE),
- HConfig = maps:without([handler_pid,mode_tab],HConfig),
+ HConfig = maps:without([olp],HConfig),
- FakeFullHConfig = HConfig#{handler_pid=>self(),mode_tab=>erlang:make_ref()},
+ FakeFullHConfig = HConfig#{olp=>{regname,self(),erlang:make_ref()}},
#{config:=HConfig} =
logger_std_h:filter_config(Config#{config=>FakeFullHConfig}),
ok.
@@ -246,13 +251,13 @@ errors(Config) ->
_ ->
NoDir = lists:concat(["/",?MODULE,"_dir"]),
{error,
- {handler_not_added,{{open_failed,NoDir,eacces},_}}} =
+ {handler_not_added,{open_failed,NoDir,eacces}}} =
logger:add_handler(myh2,logger_std_h,
#{config=>#{type=>{file,NoDir}}})
end,
{error,
- {handler_not_added,{{open_failed,Log,_},_}}} =
+ {handler_not_added,{open_failed,Log,_}}} =
logger:add_handler(myh3,logger_std_h,
#{config=>#{type=>{file,Log,[bad_file_opt]}}}),
@@ -320,19 +325,16 @@ config_fail(_Config) ->
#{config => #{restart_type => bad},
filter_default=>log,
formatter=>{?MODULE,self()}}),
- {error,{handler_not_added,{invalid_config,logger_std_h,
- {invalid_levels,#{drop_mode_qlen:=1}}}}} =
+ {error,{handler_not_added,{invalid_olp_levels,#{drop_mode_qlen:=1}}}} =
logger:add_handler(?MODULE,logger_std_h,
#{config => #{drop_mode_qlen=>1}}),
- {error,{handler_not_added,{invalid_config,logger_std_h,
- {invalid_levels,#{sync_mode_qlen:=43,
- drop_mode_qlen:=42}}}}} =
+ {error,{handler_not_added,{invalid_olp_levels,#{sync_mode_qlen:=43,
+ drop_mode_qlen:=42}}}} =
logger:add_handler(?MODULE,logger_std_h,
#{config => #{sync_mode_qlen=>43,
drop_mode_qlen=>42}}),
- {error,{handler_not_added,{invalid_config,logger_std_h,
- {invalid_levels,#{drop_mode_qlen:=43,
- flush_qlen:=42}}}}} =
+ {error,{handler_not_added,{invalid_olp_levels,#{drop_mode_qlen:=43,
+ flush_qlen:=42}}}} =
logger:add_handler(?MODULE,logger_std_h,
#{config => #{drop_mode_qlen=>43,
flush_qlen=>42}}),
@@ -344,7 +346,7 @@ config_fail(_Config) ->
logger:set_handler_config(?MODULE,config,
#{type=>{file,"file"}}),
- {error,{invalid_config,logger_std_h,{invalid_levels,_}}} =
+ {error,{invalid_olp_levels,_}} =
logger:set_handler_config(?MODULE,config,
#{sync_mode_qlen=>100,
flush_qlen=>99}),
@@ -355,9 +357,7 @@ config_fail(_Config) ->
%% Read-only fields may (accidentially) be included in the change,
%% but it won't take effect
{ok,C} = logger:get_handler_config(?MODULE),
- ok = logger:set_handler_config(?MODULE,config,
- #{handler_pid=>self(),
- mode_tab=>erlang:make_ref()}),
+ ok = logger:set_handler_config(?MODULE,config,#{olp=>dummyvalue}),
{ok,C} = logger:get_handler_config(?MODULE),
ok.
@@ -425,10 +425,13 @@ crash_std_h(Config,Func,Var,Type,Log) ->
%% logger would send the log event to the logger process here instead
%% of logging it itself.
log_on_remote_node(Node,Msg) ->
+ Pid = self(),
_ = spawn_link(Node,
fun() -> erlang:group_leader(whereis(user),self()),
- logger:notice(Msg)
+ logger:notice(Msg),
+ Pid ! done
end),
+ receive done -> ok end,
ok.
@@ -456,14 +459,7 @@ sync_and_read(Node,file,Log) ->
end.
bad_input(_Config) ->
- {error,{badarg,{filesync,["BadType"]}}} = logger_std_h:filesync("BadType"),
- {error,{badarg,{info,["BadType"]}}} = logger_std_h:info("BadType"),
- {error,{badarg,{reset,["BadType"]}}} = logger_std_h:reset("BadType").
-
-
-info_and_reset(_Config) ->
- #{id := ?STANDARD_HANDLER} = logger_std_h:info(?STANDARD_HANDLER),
- ok = logger_std_h:reset(?STANDARD_HANDLER).
+ {error,{badarg,{filesync,["BadType"]}}} = logger_std_h:filesync("BadType").
reconfig(Config) ->
Dir = ?config(priv_dir,Config),
@@ -473,9 +469,10 @@ reconfig(Config) ->
filter_default=>log,
filters=>?DEFAULT_HANDLER_FILTERS([?MODULE]),
formatter=>{?MODULE,self()}}),
- #{id := ?MODULE,
- handler_state := #{type := standard_io,
- file_ctrl_pid := FileCtrlPid},
+ #{%id := ?MODULE,
+ cb_state:=#{handler_state := #{type := standard_io,
+ file_ctrl_pid := FileCtrlPid},
+ filesync_repeat_interval := no_repeat},
sync_mode_qlen := ?SYNC_MODE_QLEN,
drop_mode_qlen := ?DROP_MODE_QLEN,
flush_qlen := ?FLUSH_QLEN,
@@ -485,9 +482,8 @@ reconfig(Config) ->
overload_kill_enable := ?OVERLOAD_KILL_ENABLE,
overload_kill_qlen := ?OVERLOAD_KILL_QLEN,
overload_kill_mem_size := ?OVERLOAD_KILL_MEM_SIZE,
- overload_kill_restart_after := ?OVERLOAD_KILL_RESTART_AFTER,
- filesync_repeat_interval := no_repeat} = DefaultInfo =
- logger_std_h:info(?MODULE),
+ overload_kill_restart_after := ?OVERLOAD_KILL_RESTART_AFTER} =
+ logger_olp:info(h_proc_name()),
{ok,
#{config:=
@@ -518,9 +514,10 @@ reconfig(Config) ->
overload_kill_mem_size => 10000000,
overload_kill_restart_after => infinity,
filesync_repeat_interval => 5000}),
- #{id := ?MODULE,
- handler_state := #{type := standard_io,
- file_ctrl_pid := FileCtrlPid},
+ #{%id := ?MODULE,
+ cb_state := #{handler_state := #{type := standard_io,
+ file_ctrl_pid := FileCtrlPid},
+ filesync_repeat_interval := no_repeat},
sync_mode_qlen := 1,
drop_mode_qlen := 2,
flush_qlen := 3,
@@ -530,8 +527,7 @@ reconfig(Config) ->
overload_kill_enable := true,
overload_kill_qlen := 100000,
overload_kill_mem_size := 10000000,
- overload_kill_restart_after := infinity,
- filesync_repeat_interval := no_repeat} = Info = logger_std_h:info(?MODULE),
+ overload_kill_restart_after := infinity} = logger_olp:info(h_proc_name()),
{ok,#{config :=
#{type := standard_io,
@@ -613,7 +609,7 @@ file_opts(Config) ->
Log = filename:join(Dir, lists:concat([?FUNCTION_NAME,".log"])),
BadFileOpts = [raw],
BadType = {file,Log,BadFileOpts},
- {error,{handler_not_added,{{open_failed,Log,enoent},_}}} =
+ {error,{handler_not_added,{open_failed,Log,enoent}}} =
logger:add_handler(?MODULE, logger_std_h,
#{config => #{type => BadType}}),
@@ -626,7 +622,9 @@ file_opts(Config) ->
filters=>?DEFAULT_HANDLER_FILTERS([?MODULE]),
formatter=>{?MODULE,self()}}),
- #{handler_state := #{type := OkType}} = logger_std_h:info(?MODULE),
+ #{cb_state := #{handler_state := #{type := OkType}}} =
+ logger_olp:info(h_proc_name()),
+ {ok,#{config := #{type := OkType}}} = logger:get_handler_config(?MODULE),
logger:notice(M1=?msg,?domain),
?check(M1),
B1 = ?bin(M1),
@@ -675,11 +673,8 @@ sync(Config) ->
%% a filesync is still performed when handler goes idle
ok = logger:update_handler_config(?MODULE, config,
#{filesync_repeat_interval => no_repeat}),
- no_repeat = maps:get(filesync_repeat_interval, logger_std_h:info(?MODULE)),
- %% The following timer is to make sure the time from last log
- %% ("second") to next ("third") is long enough, so the a flush is
- %% triggered by the idle timeout between "thrid" and "fourth".
- timer:sleep(?IDLE_DETECT_TIME_MSEC*2),
+ no_repeat = maps:get(filesync_repeat_interval,
+ maps:get(cb_state, logger_olp:info(h_proc_name()))),
start_tracer([{logger_std_h, write_to_dev, 5},
{file, datasync, 1}],
[{logger_std_h, write_to_dev, <<"third\n">>},
@@ -688,22 +683,24 @@ sync(Config) ->
{file,datasync}]),
logger:notice("third", ?domain),
%% wait for automatic filesync
- timer:sleep(?IDLE_DETECT_TIME_MSEC*2),
+ timer:sleep(?IDLE_DETECT_TIME*2),
logger:notice("fourth", ?domain),
%% wait for automatic filesync
- check_tracer(?IDLE_DETECT_TIME_MSEC*2),
+ check_tracer(?IDLE_DETECT_TIME*2),
%% switch repeated filesync on and verify that the looping works
SyncInt = 1000,
WaitT = 4500,
OneSync = {logger_h_common,handle_cast,repeated_filesync},
%% receive 1 repeated_filesync per sec
- start_tracer([{logger_h_common,handle_cast,2}],
+ start_tracer([{{logger_h_common,handle_cast,2},
+ [{[repeated_filesync,'_'],[],[]}]}],
[OneSync || _ <- lists:seq(1, trunc(WaitT/SyncInt))]),
ok = logger:update_handler_config(?MODULE, config,
#{filesync_repeat_interval => SyncInt}),
- SyncInt = maps:get(filesync_repeat_interval, logger_std_h:info(?MODULE)),
+ SyncInt = maps:get(filesync_repeat_interval,
+ maps:get(cb_state,logger_olp:info(h_proc_name()))),
timer:sleep(WaitT),
ok = logger:update_handler_config(?MODULE, config,
#{filesync_repeat_interval => no_repeat}),
@@ -764,8 +761,6 @@ sync_failure(Config) ->
ok = rpc:call(Node, logger, update_handler_config,
[?STANDARD_HANDLER, config,
#{filesync_repeat_interval => SyncInt}]),
- Info = rpc:call(Node, logger_std_h, info, [?STANDARD_HANDLER]),
- SyncInt = maps:get(filesync_repeat_interval, Info),
ok = log_on_remote_node(Node, "Logged1"),
?check_no_log,
@@ -1095,7 +1090,7 @@ qlen_kill_new(Config) ->
receive
{'DOWN', MRef, _, _, Info} ->
case Info of
- {shutdown,{overloaded,?MODULE,QLen,Mem}} ->
+ {shutdown,{overloaded,QLen,Mem}} ->
ct:pal("Terminated with qlen = ~w, mem = ~w", [QLen,Mem]);
killed ->
ct:pal("Slow shutdown, handler process was killed!", [])
@@ -1105,7 +1100,7 @@ qlen_kill_new(Config) ->
ok
after
5000 ->
- Info = logger_std_h:info(?MODULE),
+ Info = logger_olp:info(h_proc_name()),
ct:pal("Handler state = ~p", [Info]),
ct:fail("Handler not dead! It should not have survived this!")
end.
@@ -1146,7 +1141,7 @@ mem_kill_new(Config) ->
receive
{'DOWN', MRef, _, _, Info} ->
case Info of
- {shutdown,{overloaded,?MODULE,QLen,Mem}} ->
+ {shutdown,{overloaded,QLen,Mem}} ->
ct:pal("Terminated with qlen = ~w, mem = ~w", [QLen,Mem]);
killed ->
ct:pal("Slow shutdown, handler process was killed!", [])
@@ -1156,7 +1151,7 @@ mem_kill_new(Config) ->
ok
after
5000 ->
- Info = logger_std_h:info(?MODULE),
+ Info = logger_olp:info(h_proc_name()),
ct:pal("Handler state = ~p", [Info]),
ct:fail("Handler not dead! It should not have survived this!")
end.
@@ -1187,7 +1182,7 @@ restart_after(Config) ->
ok
after
5000 ->
- Info1 = logger_std_h:info(?MODULE),
+ Info1 = logger_olp:info(h_proc_name()),
ct:pal("Handler state = ~p", [Info1]),
ct:fail("Handler not dead! It should not have survived this!")
end,
@@ -1212,7 +1207,7 @@ restart_after(Config) ->
ok
after
5000 ->
- Info2 = logger_std_h:info(?MODULE),
+ Info2 = logger_olp:info(h_proc_name()),
ct:pal("Handler state = ~p", [Info2]),
ct:fail("Handler not dead! It should not have survived this!")
end,
@@ -1234,11 +1229,15 @@ handler_requests_under_load(Config) ->
flush_qlen => 2000,
burst_limit_enable => false}},
ok = logger:update_handler_config(?MODULE, NewHConfig),
- Pid = spawn_link(fun() -> send_requests(?MODULE, 1, [{filesync,[]},
- {info,[]},
- {reset,[]},
- {change_config,[]}])
- end),
+ Pid = spawn_link(
+ fun() -> send_requests(1,[{logger_std_h,filesync,[?MODULE],[]},
+ {logger_olp,info,[h_proc_name()],[]},
+ {logger_olp,reset,[h_proc_name()],[]},
+ {logger,update_handler_config,
+ [?MODULE, config,
+ #{overload_kill_enable => false}],
+ []}])
+ end),
Sent = send_burst({t,10000}, seq, {chars,79}, notice),
Pid ! {self(),finish},
ReqResult = receive {Pid,Result} -> Result end,
@@ -1249,8 +1248,9 @@ handler_requests_under_load(Config) ->
[E || E <- Res,
is_tuple(E) andalso (element(1,E) == error)]
end,
- Errors = [{Req,FindError(Res)} || {Req,Res} <- ReqResult],
- NoOfReqs = lists:foldl(fun({_,Res}, N) -> N + length(Res) end, 0, ReqResult),
+ Errors = [{Func,FindError(Res)} || {_,Func,_,Res} <- ReqResult],
+ NoOfReqs = lists:foldl(fun({_,_,_,Res}, N) -> N + length(Res) end,
+ 0, ReqResult),
ct:pal("~w requests made. Errors: ~n~p", [NoOfReqs,Errors]),
ok = file_delete(Log).
handler_requests_under_load(cleanup, _Config) ->
@@ -1270,24 +1270,31 @@ recreate_deleted_log(Config) ->
recreate_deleted_log(cleanup, _Config) ->
ok = stop_handler(?MODULE).
+reopen_changed_log(Config) ->
+ {Log,_HConfig,_StdHConfig} =
+ start_handler(?MODULE, ?FUNCTION_NAME, Config),
+ logger:notice("first",?domain),
+ logger_std_h:filesync(?MODULE),
+ ok = file:rename(Log,Log++".old"),
+ ok = file:write_file(Log,""),
+ logger:notice("second",?domain),
+ logger_std_h:filesync(?MODULE),
+ {ok,<<"first\n">>} = file:read_file(Log++".old"),
+ {ok,<<"second\n">>} = file:read_file(Log),
+ ok.
+reopen_changed_log(cleanup, _Config) ->
+ ok = stop_handler(?MODULE).
+
%%%-----------------------------------------------------------------
%%%
-send_requests(HName, TO, Reqs = [{Req,Res}|Rs]) ->
+send_requests(TO, Reqs = [{Mod,Func,Args,Res}|Rs]) ->
receive
{From,finish} ->
From ! {self(),Reqs}
after
TO ->
- Result =
- case Req of
- change_config ->
- logger:update_handler_config(HName, config,
- #{overload_kill_enable =>
- false});
- Func ->
- logger_std_h:Func(HName)
- end,
- send_requests(HName, TO, Rs ++ [{Req,[Result|Res]}])
+ Result = apply(Mod,Func,Args),
+ send_requests(TO, Rs ++ [{Mod,Func,Args,[Result|Res]}])
end.
@@ -1624,7 +1631,8 @@ start_tracer(Trace,Expected) ->
Pid = self(),
FileCtrlPid = maps:get(file_ctrl_pid,
maps:get(handler_state,
- logger_std_h:info(?MODULE))),
+ maps:get(cb_state,
+ logger_olp:info(h_proc_name())))),
dbg:tracer(process,{fun tracer/2,{Pid,Expected}}),
dbg:p(whereis(h_proc_name()),[c]),
dbg:p(FileCtrlPid,[c]),
@@ -1632,7 +1640,9 @@ start_tracer(Trace,Expected) ->
ok.
tpl([{M,F,A}|Trace]) ->
- {ok,Match} = dbg:tpl(M,F,A,[]),
+ tpl([{{M,F,A},[]}|Trace]);
+tpl([{{M,F,A},MS}|Trace]) ->
+ {ok,Match} = dbg:tpl(M,F,A,MS),
case lists:keyfind(matched,1,Match) of
{_,_,1} ->
ok;
diff --git a/lib/kernel/test/logger_stress_SUITE.erl b/lib/kernel/test/logger_stress_SUITE.erl
new file mode 100644
index 0000000000..4072e8c86a
--- /dev/null
+++ b/lib/kernel/test/logger_stress_SUITE.erl
@@ -0,0 +1,456 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018. All 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(logger_stress_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct_event.hrl").
+-include_lib("kernel/include/logger.hrl").
+-include_lib("kernel/src/logger_h_common.hrl").
+
+-ifdef(SAVE_STATS).
+ -define(COLLECT_STATS(_All_,_Procs_),
+ ct:pal("~p",[stats(_All_,_Procs_)])).
+-else.
+ -define(COLLECT_STATS(_All_,_Procs__), ok).
+-endif.
+
+-define(TEST_DURATION,120). % seconds
+
+suite() ->
+ [{timetrap,{minutes,3}},
+ {ct_hooks,[logger_test_lib]}].
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_group(_Group, Config) ->
+ Config.
+
+end_per_group(_Group, _Config) ->
+ ok.
+
+init_per_testcase(_TestCase, Config) ->
+ Config.
+
+end_per_testcase(Case, Config) ->
+ try apply(?MODULE,Case,[cleanup,Config])
+ catch error:undef -> ok
+ end,
+ ok.
+
+groups() ->
+ [].
+
+all() ->
+ [allow_events,
+ reject_events,
+ std_handler,
+ disk_log_handler,
+ emulator_events,
+ remote_events,
+ remote_to_disk_log,
+ remote_emulator_events,
+ remote_emulator_to_disk_log].
+
+%%%-----------------------------------------------------------------
+%%% Test cases
+%%%-----------------------------------------------------------------
+%% Time from log macro call to handler callback
+allow_events(Config) ->
+ {ok,_,Node} =
+ logger_test_lib:setup(Config,
+ [{logger,
+ [{handler,default,?MODULE,#{}}]},
+ {logger_level,notice}]),
+ N = 100000,
+ {T,_} = timer:tc(fun() -> rpc:call(Node,?MODULE,nlogs,[N]) end),
+ IOPS = N * 1000/T, % log events allowed per millisecond
+ ct_event:notify(#event{name = benchmark_data,
+ data = [{value,IOPS}]}),
+ {comment,io_lib:format("~.2f accepted events pr millisecond",
+ [IOPS])}.
+
+%% Time from log macro call to reject (log level)
+reject_events(Config) ->
+ {ok,_,Node} =
+ logger_test_lib:setup(Config,
+ [{logger,
+ [{handler,default,?MODULE,#{}}]},
+ {logger_level,error}]),
+ N = 1000000,
+ {T,_} = timer:tc(fun() -> rpc:call(Node,?MODULE,nlogs,[N]) end),
+ IOPS = N * 1000/T, % log events rejected per millisecond
+ ct_event:notify(#event{name = benchmark_data,
+ data = [{value,IOPS}]}),
+ {comment,io_lib:format("~.2f rejected events pr millisecond",
+ [IOPS])}.
+
+%% Cascading failure that produce gen_server and proc_lib reports -
+%% how many of the produced log events are actually written to a log
+%% with logger_std_h file handler.
+std_handler(Config) ->
+ {ok,_,Node} =
+ logger_test_lib:setup(Config,
+ [{logger,
+ [{handler,default,logger_std_h,
+ #{config=>#{type=>{file,"default.log"}}}}]}]),
+
+ cascade({Node,{logger_backend,log_allowed,2},[]},
+ {Node,{logger_std_h,write,4},[{default,logger_std_h_default}]},
+ fun otp_cascading/0).
+std_handler(cleanup,_Config) ->
+ _ = file:delete("default.log"),
+ ok.
+
+%% Cascading failure that produce gen_server and proc_lib reports -
+%% how many of the produced log events are actually written to a log
+%% with logger_disk_log_h wrap file handler.
+disk_log_handler(Config) ->
+ {ok,_,Node} =
+ logger_test_lib:setup(Config,
+ [{logger,
+ [{handler,default,logger_disk_log_h,#{}}]}]),
+ cascade({Node,{logger_backend,log_allowed,2},[]},
+ {Node,{logger_disk_log_h,write,4},
+ [{default,logger_disk_log_h_default}]},
+ fun otp_cascading/0).
+disk_log_handler(cleanup,_Config) ->
+ Files = filelib:wildcard("default.log.*"),
+ [_ = file:delete(F) || F <- Files],
+ ok.
+
+%% Cascading failure that produce log events from the emulator - how
+%% many of the produced log events pass through the proxy.
+emulator_events(Config) ->
+ {ok,_,Node} =
+ logger_test_lib:setup(Config,
+ [{logger,
+ [{handler,default,?MODULE,#{}}]}]),
+ cascade({Node,{?MODULE,producer,0},[]},
+ {Node,{?MODULE,log,2},[{proxy,logger_proxy}]},
+ fun em_cascading/0).
+
+%% Cascading failure that produce gen_server and proc_lib reports on
+%% remote node - how many of the produced log events pass through the
+%% proxy.
+remote_events(Config) ->
+ {ok,_,Node1} =
+ logger_test_lib:setup([{postfix,1}|Config],
+ [{logger,
+ [{handler,default,?MODULE,#{}}]}]),
+ {ok,_,Node2} =
+ logger_test_lib:setup([{postfix,2}|Config],[]),
+ cascade({Node2,{logger_backend,log_allowed,2},[{remote_proxy,logger_proxy}]},
+ {Node1,{?MODULE,log,2},[{local_proxy,logger_proxy}]},
+ fun otp_cascading/0).
+
+%% Cascading failure that produce gen_server and proc_lib reports on
+%% remote node - how many of the produced log events are actually
+%% written to a log with logger_disk_log_h wrap file handler.
+remote_to_disk_log(Config) ->
+ {ok,_,Node1} =
+ logger_test_lib:setup([{postfix,1}|Config],
+ [{logger,
+ [{handler,default,logger_disk_log_h,#{}}]}]),
+ {ok,_,Node2} =
+ logger_test_lib:setup([{postfix,2}|Config],[]),
+ cascade({Node2,{logger_backend,log_allowed,2},[{remote_proxy,logger_proxy}]},
+ {Node1,{logger_disk_log_h,write,4},
+ [{local_proxy,logger_proxy},
+ {local_default,logger_disk_log_h_default}]},
+ fun otp_cascading/0).
+remote_to_disk_log(cleanup,_Config) ->
+ Files = filelib:wildcard("default.log.*"),
+ [_ = file:delete(F) || F <- Files],
+ ok.
+
+%% Cascading failure that produce log events from the emulator on
+%% remote node - how many of the produced log events pass through the
+%% proxy.
+remote_emulator_events(Config) ->
+ {ok,_,Node1} =
+ logger_test_lib:setup([{postfix,1}|Config],
+ [{logger,
+ [{handler,default,?MODULE,#{}}]}]),
+ {ok,_,Node2} =
+ logger_test_lib:setup([{postfix,2}|Config],[]),
+ cascade({Node2,{?MODULE,producer,0},[{remote_proxy,logger_proxy}]},
+ {Node1,{?MODULE,log,2},[{local_proxy,logger_proxy}]},
+ fun em_cascading/0).
+
+%% Cascading failure that produce log events from the emulator on
+%% remote node - how many of the produced log events are actually
+%% written to a log with logger_disk_log_h wrap file handler.
+remote_emulator_to_disk_log(Config) ->
+ {ok,_,Node1} =
+ logger_test_lib:setup([{postfix,1}|Config],
+ [{logger,
+ [{handler,default,logger_disk_log_h,#{}}]}]),
+ {ok,_,Node2} =
+ logger_test_lib:setup([{postfix,2}|Config],[]),
+ cascade({Node2,{?MODULE,producer,0},[{remote_proxy,logger_proxy}]},
+ {Node1,{logger_disk_log_h,write,4},
+ [{local_proxy,logger_proxy},
+ {local_default,logger_disk_log_h_default}]},
+ fun em_cascading/0).
+remote_emulator_to_disk_log(cleanup,_Config) ->
+ Files = filelib:wildcard("default.log.*"),
+ [_ = file:delete(F) || F <- Files],
+ ok.
+
+%%%-----------------------------------------------------------------
+%%% Internal functions
+nlogs(N) ->
+ group_leader(whereis(user),self()),
+ Str = "\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "[\\]^_`abcdefghijklmnopqr",
+ [?LOG_NOTICE(Str) || _ <- lists:seq(1,N)],
+ ok.
+
+%% cascade(ProducerInfo,ConsumerInfo,TestFun)
+cascade({PNode,PMFA,_PStatProcs},{CNode,CMFA,_CStatProcs},TestFun) ->
+ Tab = ets:new(counter,[set,public]),
+ ets:insert(Tab,{producer,0}),
+ ets:insert(Tab,{consumer,0}),
+ dbg:tracer(process,{fun tracer/2,{Tab,PNode,CNode}}),
+ dbg:n(PNode),
+ dbg:n(CNode),
+ dbg:cn(node()),
+ dbg:p(all,[call,arity]),
+ dbg:tpl(PMFA,[]),
+ dbg:tpl(CMFA,[]),
+
+ Pid = rpc:call(CNode,?MODULE,wrap_test,[PNode,TestFun]),
+ MRef = erlang:monitor(process,Pid),
+ TO = ?TEST_DURATION*1000,
+ receive {'DOWN',MRef,_,_,Reason} ->
+ ct:fail({remote_pid_down,Reason})
+ after TO ->
+ All = ets:lookup_element(Tab,producer,2),
+ Written = ets:lookup_element(Tab,consumer,2),
+ dbg:stop_clear(),
+ ?COLLECT_STATS(All,
+ [{PNode,P,Id} || {Id,P} <- _PStatProcs] ++
+ [{CNode,P,Id} || {Id,P} <- _CStatProcs]),
+ Ratio = Written/All * 100,
+ ct_event:notify(#event{name = benchmark_data,
+ data = [{value,Ratio}]}),
+ {comment,io_lib:format("~p % (~p written, ~p produced)",
+ [round(Ratio),Written,All])}
+ end.
+
+wrap_test(Fun) ->
+ wrap_test(node(),Fun).
+wrap_test(Node,Fun) ->
+ reset(),
+ group_leader(whereis(user),self()),
+ rpc:call(Node,?MODULE,do_fun,[Fun]).
+
+do_fun(Fun) ->
+ reset(),
+ Fun().
+
+reset() ->
+ reset([logger_std_h_default, logger_disk_log_h_default, logger_proxy]).
+reset([P|Ps]) ->
+ is_pid(whereis(P)) andalso logger_olp:reset(P),
+ reset(Ps);
+reset([]) ->
+ ok.
+
+
+tracer({trace,_,call,{?MODULE,producer,_}},{Tab,_PNode,_CNode}=S) ->
+ ets:update_counter(Tab,producer,1),
+ S;
+tracer({trace,Pid,call,{logger_backend,log_allowed,_}},{Tab,PNode,_CNode}=S) when node(Pid)=:=PNode ->
+ ets:update_counter(Tab,producer,1),
+ S;
+tracer({trace,_,call,{?MODULE,log,_}},{Tab,_PNode,_CNode}=S) ->
+ ets:update_counter(Tab,consumer,1),
+ S;
+tracer({trace,_,call,{_,write,_}},{Tab,_PNode,_CNode}=S) ->
+ ets:update_counter(Tab,consumer,1),
+ S;
+tracer(_,S) ->
+ S.
+
+
+%%%-----------------------------------------------------------------
+%%% Collect statistics
+-define(STAT_KEYS,
+ [burst_drops,
+ calls,
+ casts,
+ drops,
+ flushed,
+ flushes,
+ freq,
+ last_qlen,
+ max_qlen,
+ time,
+ writes]).
+-define(EVENT_KEYS,
+ [calls,casts,flushed]).
+
+stats(All,Procs) ->
+ NI = [{Id,rpc:call(N,logger_olp,info,[P])} || {N,P,Id}<-Procs],
+ [{all,All}|[stats(Id,I,All) || {Id,I} <- NI]].
+
+stats(Id,Info,All) ->
+ S = maps:with(?STAT_KEYS,Info),
+ AllOnProc = lists:sum(maps:values(maps:with(?EVENT_KEYS,S))),
+ if All>0 ->
+ Writes = maps:get(writes,S),
+ {_,ActiveTime} = maps:get(time,S),
+ Rate = round(100*Writes/All),
+ RateOnProc =
+ if AllOnProc>0 ->
+ round(100*Writes/AllOnProc);
+ true ->
+ 0
+ end,
+ AvFreq =
+ if ActiveTime>0 ->
+ round(Writes/ActiveTime);
+ true ->
+ 0
+ end,
+ {Id,
+ {stats,S},
+ {rate,Rate},
+ {rate_on_proc,RateOnProc},
+ {av_freq,AvFreq}};
+ true ->
+ {Id,none}
+ end.
+
+%%%-----------------------------------------------------------------
+%%% Spawn a lot of processes that crash repeatedly, causing a lot of
+%%% error reports from the emulator.
+em_cascading() ->
+ spawn(fun() -> super() end).
+
+super() ->
+ process_flag(trap_exit,true),
+ spawn_link(fun server/0),
+ [spawn_link(fun client/0) || _<-lists:seq(1,10000)],
+ super_loop().
+
+super_loop() ->
+ receive
+ {'EXIT',_,server} ->
+ spawn_link(fun server/0),
+ super_loop();
+ {'EXIT',_,_} ->
+ _L = lists:sum(lists:seq(1,10000)),
+ spawn_link(fun client/0),
+ super_loop()
+ end.
+
+client() ->
+ receive
+ after 1 ->
+ case whereis(server) of
+ Pid when is_pid(Pid) ->
+ ok;
+ undefined ->
+ producer(),
+ erlang:error(some_exception)
+ end
+ end,
+ client().
+
+server() ->
+ register(server,self()),
+ receive
+ after 3000 ->
+ exit(server)
+ end.
+
+
+%%%-----------------------------------------------------------------
+%%% Create a supervisor tree with processes that crash repeatedly,
+%%% causing a lot of supervisor reports and crashreports
+otp_cascading() ->
+ {ok,Pid} = supervisor:start_link({local,otp_super}, ?MODULE, [otp_super]),
+ unlink(Pid),
+ Pid.
+
+otp_server_sup() ->
+ supervisor:start_link({local,otp_server_sup},?MODULE,[otp_server_sup]).
+
+otp_client_sup(N) ->
+ supervisor:start_link({local,otp_client_sup},?MODULE,[otp_client_sup,N]).
+
+otp_server() ->
+ gen_server:start_link({local,otp_server},?MODULE,[otp_server],[]).
+
+otp_client() ->
+ gen_server:start_link(?MODULE,[otp_client],[]).
+
+init([otp_super]) ->
+ {ok, {{one_for_one, 200, 10},
+ [{client_sup,
+ {?MODULE, otp_client_sup, [10000]},
+ permanent, 1000, supervisor, [?MODULE]},
+ {server_sup,
+ {?MODULE, otp_server_sup, []},
+ permanent, 1000, supervisor, [?MODULE]}
+ ]}};
+init([otp_server_sup]) ->
+ {ok, {{one_for_one, 2, 10},
+ [{server,
+ {?MODULE, otp_server, []},
+ permanent, 1000, worker, [?MODULE]}
+ ]}};
+init([otp_client_sup,N]) ->
+ spawn(fun() ->
+ [supervisor:start_child(otp_client_sup,[])
+ || _ <- lists:seq(1,N)]
+ end),
+ {ok, {{simple_one_for_one, N*10, 1},
+ [{client,
+ {?MODULE, otp_client, []},
+ permanent, 1000, worker, [?MODULE]}
+ ]}};
+init([otp_server]) ->
+ {ok, server, 10000};
+init([otp_client]) ->
+ {ok, client,1}.
+
+handle_info(timeout, client) ->
+ true = is_pid(whereis(otp_server)),
+ {noreply,client,1};
+handle_info(timeout, server) ->
+ exit(self(), some_error).
+
+%%%-----------------------------------------------------------------
+%%% Logger callbacks
+log(_LogEvent,_Config) ->
+ ok.
+
+%%%-----------------------------------------------------------------
+%%% Function to trace on for counting produced emulator messages
+producer() ->
+ ok.
diff --git a/lib/kernel/test/logger_test_lib.erl b/lib/kernel/test/logger_test_lib.erl
index 81eb9ce5eb..be4bc427fb 100644
--- a/lib/kernel/test/logger_test_lib.erl
+++ b/lib/kernel/test/logger_test_lib.erl
@@ -28,11 +28,17 @@
post_end_per_testcase/5, post_end_per_suite/3]).
setup(Config,Vars) ->
+ Postfix = case proplists:get_value(postfix, Config) of
+ undefined -> "";
+ P -> ["_",P]
+ end,
FuncStr = lists:concat([proplists:get_value(suite, Config), "_",
- proplists:get_value(tc, Config)]),
+ proplists:get_value(tc, Config)|
+ Postfix]),
ConfigFileName = filename:join(proplists:get_value(priv_dir, Config), FuncStr),
file:write_file(ConfigFileName ++ ".config", io_lib:format("[{kernel, ~p}].",[Vars])),
- case test_server:start_node(proplists:get_value(tc, Config), slave,
+ Sname = lists:concat([proplists:get_value(tc,Config)|Postfix]),
+ case test_server:start_node(Sname, slave,
[{args, ["-pa ",filename:dirname(code:which(?MODULE)),
" -boot start_sasl -kernel start_timer true "
"-config ",ConfigFileName]}]) of
diff --git a/lib/kernel/test/seq_trace_SUITE.erl b/lib/kernel/test/seq_trace_SUITE.erl
index ee8f4e94f8..663f910751 100644
--- a/lib/kernel/test/seq_trace_SUITE.erl
+++ b/lib/kernel/test/seq_trace_SUITE.erl
@@ -19,6 +19,9 @@
%%
-module(seq_trace_SUITE).
+%% label_capability_mismatch needs to run a part of the test on an OTP 20 node.
+-compile(r20).
+
-export([all/0, suite/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]).
@@ -358,7 +361,7 @@ do_incompatible_labels(Rel) ->
Mdir = filename:dirname(Dir),
true = rpc:call(Node,code,add_patha,[Mdir]),
seq_trace:reset_trace(),
- rpc:call(Node,?MODULE,start_tracer,[]),
+ true = is_pid(rpc:call(Node,?MODULE,start_tracer,[])),
Receiver = spawn(Node,?MODULE,one_time_receiver,[]),
%% This node does not support arbitrary labels, so it must fail with a
@@ -385,7 +388,7 @@ do_compatible_labels(Rel) ->
Mdir = filename:dirname(Dir),
true = rpc:call(Node,code,add_patha,[Mdir]),
seq_trace:reset_trace(),
- rpc:call(Node,?MODULE,start_tracer,[]),
+ true = is_pid(rpc:call(Node,?MODULE,start_tracer,[])),
Receiver = spawn(Node,?MODULE,one_time_receiver,[]),
%% This node does not support arbitrary labels, but small integers should
diff --git a/lib/megaco/configure.in b/lib/megaco/configure.in
index eaa875d0a3..bae6144abe 100644
--- a/lib/megaco/configure.in
+++ b/lib/megaco/configure.in
@@ -22,10 +22,6 @@ dnl
dnl define([AC_CACHE_LOAD], )dnl
dnl define([AC_CACHE_SAVE], )dnl
-if test "x$no_recursion" != "xyes" -a "x$OVERRIDE_CONFIG_CACHE" = "x"; then
- # We do not want to use a common cache!
- cache_file=/dev/null
-fi
AC_INIT(vsn.mk)
@@ -42,11 +38,14 @@ else
host_os=win32
fi
-
dnl ----------------------------------------------------------------------
dnl Checks for programs.
dnl ----------------------------------------------------------------------
+AC_PROG_CC
+
+LM_WINDOWS_ENVIRONMENT
+
AC_DEFUN(ERL_REENTRANT_FLEX,
[flex_compile='$LEX -R -Pconftest -oconftest.c conftest.flex 1>&AC_FD_CC'
changequote(253, 273)dnl
@@ -188,111 +187,7 @@ CFLAGS="$CFLAGS $sanitizers"
LDFLAGS="$LDFLAGS $sanitizers"
])
-dnl
-dnl If ${ERL_TOP}/make/otp_ded.mk.in exists and contains DED_MK_VSN > 0,
-dnl every thing releted to compiling Dynamic Erlang Drivers can be found
-dnl in $(ERL_TOP)/make/$(TARGET)/ded.mk at compile time. If not, try to
-dnl figure these things out.
-dnl
-
-AC_MSG_CHECKING([for usable Dynamic Erlang Driver configuration])
-[
- ded_mk_in="${ERL_TOP}/make/otp_ded.mk.in"
- ded_mk_vsn=
- test -r "$ded_mk_in" &&
- ded_mk_vsn=`sed -n "s/^DED_MK_VSN[ ]*=[ ]*\(.*\)/\1/p" < "$ded_mk_in"`
- test "$ded_mk_vsn" != "" || ded_mk_vsn=0
-]
-
-if test $ded_mk_vsn -gt 0; then
-
-HAVE_USABLE_OTP_DED_MK=yes
-AC_MSG_RESULT([yes])
-
-CC=false
-AC_SUBST(CC)
-DED_LD=false
-AC_SUBST(DED_LD)
-
-else dnl --- begin no usable otp_ded.mk.in ---
-
-HAVE_USABLE_OTP_DED_MK=no
-AC_MSG_RESULT([no])
-
-dnl
-dnl C compiler (related) defs
-dnl
-
-AC_PROG_CC
-
-dnl
-dnl Flags to the C compiler
-dnl
-
-if test "X$host" = "Xwin32"; then
- DED_CFLAGS="$CFLAGS"
-else
- case $host_os in
- darwin*)
- CFLAGS="$CFLAGS -fno-common"
- ;;
- esac
-
- if test "x$GCC" = xyes; then
- DED_CFLAGS="$CFLAGS -fPIC $DED_CFLAGS"
- else
- DED_CFLAGS="$CFLAGS $DED_CFLAGS"
- fi
-fi
-
-dnl emulator includes needed
-DED_INCLUDES="-I${ERL_TOP}/erts/emulator/beam -I${ERL_TOP}/erts/include -I${ERL_TOP}/erts/include/$host -I${ERL_TOP}/erts/include/internal -I${ERL_TOP}/erts/include/internal/$host -I${ERL_TOP}/erts/emulator/sys/$ERLANG_OSTYPE"
-
-DED_THR_DEFS="-D_THREAD_SAFE -D_REENTRANT"
-
-case $host_os in
- win32)
- DED_LDFLAGS="-dll"
- ;;
- solaris2*|sysv4*)
- DED_LDFLAGS="-G"
- ;;
- aix4*)
- DED_LDFLAGS="-G -bnoentry -bexpall"
- ;;
- freebsd2*)
- # Non-ELF GNU linker
- DED_LDFLAGS="-Bshareable"
- ;;
- darwin*)
- # Mach-O linker, a shared lib and a loadable
- # object file is not the same thing.
- DED_LDFLAGS="-bundle -flat_namespace -undefined suppress"
- DED_LD="$CC"
- ;;
- *)
- # assume GNU linker and ELF
- DED_LDFLAGS="-shared"
- ;;
-esac
-
-AC_CHECK_PROGS(DED_LD, [$LD ld.sh])
-AC_CHECK_TOOL(DED_LD, ld, no_ld)
-if test "$DED_LD" = no_ld; then
- AC_MSG_ERROR([ld is required to build the flex scanner!])
-fi
-
-AC_MSG_CHECKING(for linker flags for loadable drivers)
-DED_LDFLAGS="$LDFLAGS $DED_LDFLAGS"
-AC_MSG_RESULT([$DED_LDFLAGS])
-
-fi dnl --- end no usable otp_ded.mk.in ---
-
-AC_SUBST(HAVE_USABLE_OTP_DED_MK)
-AC_SUBST(DED_CFLAGS)
-AC_SUBST(DED_INCLUDES)
-AC_SUBST(DED_THR_DEFS)
-AC_SUBST(DED_LDFLAGS)
+ERL_DED
AC_CHECK_PROG(PERL, perl, perl, no_perl)
if test "$PERL" = no_perl; then
diff --git a/lib/megaco/doc/src/megaco.xml b/lib/megaco/doc/src/megaco.xml
index d4a7451bfc..c7bcdfcd6f 100644
--- a/lib/megaco/doc/src/megaco.xml
+++ b/lib/megaco/doc/src/megaco.xml
@@ -32,7 +32,7 @@
<rev>%VSN%</rev>
<file>megaco.xml</file>
</header>
- <module>megaco</module>
+ <module since="">megaco</module>
<modulesummary>Main API of the Megaco application</modulesummary>
<description>
<p>Interface module for the Megaco application</p>
@@ -135,7 +135,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
<funcs>
<func>
- <name>start() -> ok | {error, Reason}</name>
+ <name since="">start() -> ok | {error, Reason}</name>
<fsummary>Starts the Megaco application</fsummary>
<type>
<v>Reason = term()</v>
@@ -153,7 +153,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>stop() -> ok | {error, Reason}</name>
+ <name since="">stop() -> ok | {error, Reason}</name>
<fsummary>Stops the Megaco application</fsummary>
<type>
<v>Reason = term()</v>
@@ -166,7 +166,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>start_user(UserMid, Config) -> ok | {error, Reason}</name>
+ <name since="">start_user(UserMid, Config) -> ok | {error, Reason}</name>
<fsummary>Initial configuration of a user</fsummary>
<type>
<v>UserMid = megaco_mid()</v>
@@ -188,7 +188,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>stop_user(UserMid) -> ok | {error, Reason}</name>
+ <name since="">stop_user(UserMid) -> ok | {error, Reason}</name>
<fsummary>Delete the configuration of a user</fsummary>
<type>
<v>UserMid = megaco_mid()</v>
@@ -203,8 +203,8 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>user_info(UserMid) -> [{Item, Value}]</name>
- <name>user_info(UserMid, Item) -> Value | exit(Reason)</name>
+ <name since="">user_info(UserMid) -> [{Item, Value}]</name>
+ <name since="">user_info(UserMid, Item) -> Value | exit(Reason)</name>
<fsummary>Lookup user information</fsummary>
<type>
<v>Handle = user_info_handle()</v>
@@ -703,7 +703,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>update_user_info(UserMid, Item, Value) -> ok | {error, Reason}</name>
+ <name since="">update_user_info(UserMid, Item, Value) -> ok | {error, Reason}</name>
<fsummary>Update information about a user</fsummary>
<type>
<v>UserMid = megaco_mid() </v>
@@ -721,8 +721,8 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>conn_info(ConnHandle) -> [{Item, Value}]</name>
- <name>conn_info(ConnHandle, Item) -> Value | exit(Reason)</name>
+ <name since="">conn_info(ConnHandle) -> [{Item, Value}]</name>
+ <name since="">conn_info(ConnHandle, Item) -> Value | exit(Reason)</name>
<fsummary>Lookup information about an active connection</fsummary>
<type>
<v>ConnHandle = #megaco_conn_handle{}</v>
@@ -1222,7 +1222,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>update_conn_info(ConnHandle, Item, Value) -> ok | {error, Reason}</name>
+ <name since="">update_conn_info(ConnHandle, Item, Value) -> ok | {error, Reason}</name>
<fsummary>Update information about an active connection</fsummary>
<type>
<v>ConnHandle = #megaco_conn_handle{}</v>
@@ -1241,8 +1241,8 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>system_info() -> [{Item, Value}] | exit(Reason)</name>
- <name>system_info(Item) -> Value | exit(Reason)</name>
+ <name since="">system_info() -> [{Item, Value}] | exit(Reason)</name>
+ <name since="">system_info(Item) -> Value | exit(Reason)</name>
<fsummary>Lookup system information</fsummary>
<type>
<v>Item = system_info_item()</v>
@@ -1289,7 +1289,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>info() -> Info</name>
+ <name since="">info() -> Info</name>
<fsummary>All the information of the application</fsummary>
<type>
<v>Info = [{Key, Value}]</v>
@@ -1311,8 +1311,8 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>connect(ReceiveHandle, RemoteMid, SendHandle, ControlPid) -> {ok, ConnHandle} | {error, Reason}</name>
- <name>connect(ReceiveHandle, RemoteMid, SendHandle, ControlPid, Extra) -> {ok, ConnHandle} | {error, Reason}</name>
+ <name since="">connect(ReceiveHandle, RemoteMid, SendHandle, ControlPid) -> {ok, ConnHandle} | {error, Reason}</name>
+ <name since="">connect(ReceiveHandle, RemoteMid, SendHandle, ControlPid, Extra) -> {ok, ConnHandle} | {error, Reason}</name>
<fsummary>Establish a "virtual" connection</fsummary>
<type>
<v>ReceiveHandle = #megaco_receive_handle{}</v>
@@ -1436,7 +1436,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>disconnect(ConnHandle, DiscoReason) -> ok | {error, ErrReason}</name>
+ <name since="">disconnect(ConnHandle, DiscoReason) -> ok | {error, ErrReason}</name>
<fsummary>Tear down a "virtual" connection</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -1454,7 +1454,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>call(ConnHandle, Actions, Options) -> {ProtocolVersion, UserReply}</name>
+ <name since="">call(ConnHandle, Actions, Options) -> {ProtocolVersion, UserReply}</name>
<fsummary>Sends one or more transaction request(s) and waits for the reply</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -1545,7 +1545,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>cast(ConnHandle, Actions, Options) -> ok | {error, Reason}</name>
+ <name since="">cast(ConnHandle, Actions, Options) -> ok | {error, Reason}</name>
<fsummary>Sends one or more transaction request(s) but does NOT wait for a reply</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -1582,7 +1582,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>encode_actions(ConnHandle, Actions, Options) -> {ok, BinOrBins} | {error, Reason}</name>
+ <name since="">encode_actions(ConnHandle, Actions, Options) -> {ok, BinOrBins} | {error, Reason}</name>
<fsummary>Encode action requests for one or more transaction request(s)</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -1607,9 +1607,9 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>token_tag2string(Tag) -> Result</name>
- <name>token_tag2string(Tag, EncoderMod) -> Result</name>
- <name>token_tag2string(Tag, EncoderMod, Version) -> Result</name>
+ <name since="">token_tag2string(Tag) -> Result</name>
+ <name since="">token_tag2string(Tag, EncoderMod) -> Result</name>
+ <name since="">token_tag2string(Tag, EncoderMod, Version) -> Result</name>
<fsummary>Convert a token tag to a string</fsummary>
<type>
<v>Tag = atom()</v>
@@ -1635,7 +1635,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>cancel(ConnHandle, CancelReason) -> ok | {error, ErrReason}</name>
+ <name since="">cancel(ConnHandle, CancelReason) -> ok | {error, ErrReason}</name>
<fsummary>Cancel all outstanding messages for this connection</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -1655,8 +1655,8 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>process_received_message(ReceiveHandle, ControlPid, SendHandle, BinMsg) -> ok</name>
- <name>process_received_message(ReceiveHandle, ControlPid, SendHandle, BinMsg, Extra) -> ok</name>
+ <name since="">process_received_message(ReceiveHandle, ControlPid, SendHandle, BinMsg) -> ok</name>
+ <name since="">process_received_message(ReceiveHandle, ControlPid, SendHandle, BinMsg, Extra) -> ok</name>
<fsummary>Process a received message</fsummary>
<type>
<v>ReceiveHandle = #megaco_receive_handle{}</v>
@@ -1755,8 +1755,8 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>receive_message(ReceiveHandle, ControlPid, SendHandle, BinMsg) -> ok</name>
- <name>receive_message(ReceiveHandle, ControlPid, SendHandle, BinMsg, Extra) -> ok</name>
+ <name since="">receive_message(ReceiveHandle, ControlPid, SendHandle, BinMsg) -> ok</name>
+ <name since="">receive_message(ReceiveHandle, ControlPid, SendHandle, BinMsg, Extra) -> ok</name>
<fsummary>Process a received message</fsummary>
<type>
<v>ReceiveHandle = #megaco_receive_handle{}</v>
@@ -1783,7 +1783,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>parse_digit_map(DigitMapBody) -> {ok, ParsedDigitMap} | {error, Reason}</name>
+ <name since="">parse_digit_map(DigitMapBody) -> {ok, ParsedDigitMap} | {error, Reason}</name>
<fsummary>Parses a digit map body</fsummary>
<type>
<v>DigitMapBody = string()</v>
@@ -1802,8 +1802,8 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>eval_digit_map(DigitMap) -> {ok, MatchResult} | {error, Reason}</name>
- <name>eval_digit_map(DigitMap, Timers) -> {ok, MatchResult} | {error, Reason}</name>
+ <name since="">eval_digit_map(DigitMap) -> {ok, MatchResult} | {error, Reason}</name>
+ <name since="">eval_digit_map(DigitMap, Timers) -> {ok, MatchResult} | {error, Reason}</name>
<fsummary>Collect digit map letters according to the digit map</fsummary>
<type>
<v>DigitMap = #'DigitMapValue'{} | parsed_digit_map()</v>
@@ -1839,7 +1839,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>report_digit_event(DigitMapEvalPid, Events) -> ok | {error, Reason}</name>
+ <name since="">report_digit_event(DigitMapEvalPid, Events) -> ok | {error, Reason}</name>
<fsummary>Send one or more events to the event collector process</fsummary>
<type>
<v>DigitMapEvalPid = pid()</v>
@@ -1866,7 +1866,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>test_digit_event(DigitMap, Events) -> {ok, Kind, Letters} | {error, Reason}</name>
+ <name since="">test_digit_event(DigitMap, Events) -> {ok, Kind, Letters} | {error, Reason}</name>
<fsummary>Feed digit map collector with events and return the result</fsummary>
<type>
<v>DigitMap = #'DigitMapValue'{} | parsed_digit_map()</v>
@@ -1900,7 +1900,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>encode_sdp(SDP) -> {ok, PP} | {error, Reason}</name>
+ <name since="">encode_sdp(SDP) -> {ok, PP} | {error, Reason}</name>
<fsummary>Encode an SDP construct</fsummary>
<type>
<v>SDP = sdp_property_parm() | sdp_property_group() | sdp_property_groups() | asn1_NOVALUE</v>
@@ -1929,7 +1929,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>decode_sdp(PP) -> {ok, SDP} | {error, Reason}</name>
+ <name since="">decode_sdp(PP) -> {ok, SDP} | {error, Reason}</name>
<fsummary>Decode an property parameter construct</fsummary>
<type>
<v>PP = property_parm() | property_group() | property_groups() | asn1_NOVALUE</v>
@@ -1969,7 +1969,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>get_sdp_record_from_PropertGroup(Type, PG) -> [sdp()]</name>
+ <name since="">get_sdp_record_from_PropertGroup(Type, PG) -> [sdp()]</name>
<fsummary>Get all sdp records of a certain type from a property group</fsummary>
<type>
<v>Type = v | c | m | o | a | b | t | r | z | k | s | i | u | e | p</v>
@@ -1986,8 +1986,8 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>versions1() -> {ok, VersionInfo} | {error, Reason}</name>
- <name>versions2() -> {ok, Info} | {error, Reason}</name>
+ <name since="">versions1() -> {ok, VersionInfo} | {error, Reason}</name>
+ <name since="">versions2() -> {ok, Info} | {error, Reason}</name>
<fsummary>Retreive various system and application info</fsummary>
<type>
<v>VersionInfo = [version_info()]</v>
@@ -2007,8 +2007,8 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>print_version_info() -> void()</name>
- <name>print_version_info(VersionInfo) -> void()</name>
+ <name since="">print_version_info() -> void()</name>
+ <name since="">print_version_info(VersionInfo) -> void()</name>
<fsummary>Formated print of result of the versions functions</fsummary>
<type>
<v>VersionInfo = [version_info()]</v>
@@ -2029,7 +2029,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>enable_trace(Level, Destination) -> void()</name>
+ <name since="">enable_trace(Level, Destination) -> void()</name>
<fsummary>Start megaco tracing</fsummary>
<type>
<v>Level = max | min | 0 &lt;= integer() &lt;= 100</v>
@@ -2057,7 +2057,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>disable_trace() -> void()</name>
+ <name since="">disable_trace() -> void()</name>
<fsummary>Stop megaco tracing</fsummary>
<desc>
<p>This function is used to stop megaco tracing.</p>
@@ -2065,7 +2065,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</desc>
</func>
<func>
- <name>set_trace(Level) -> void()</name>
+ <name since="">set_trace(Level) -> void()</name>
<fsummary>Change megaco trace level</fsummary>
<type>
<v>Level = max | min | 0 &lt;= integer() &lt;= 100</v>
@@ -2081,10 +2081,10 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>get_stats() -> {ok, TotalStats} | {error, Reason}</name>
- <name>get_stats(GlobalCounter) -> {ok, CounterStats} | {error, Reason}</name>
- <name>get_stats(ConnHandle) -> {ok, ConnHandleStats} | {error, Reason}</name>
- <name>get_stats(ConnHandle, Counter) -> {ok, integer()} | {error, Reason}</name>
+ <name since="">get_stats() -> {ok, TotalStats} | {error, Reason}</name>
+ <name since="">get_stats(GlobalCounter) -> {ok, CounterStats} | {error, Reason}</name>
+ <name since="">get_stats(ConnHandle) -> {ok, ConnHandleStats} | {error, Reason}</name>
+ <name since="">get_stats(ConnHandle, Counter) -> {ok, integer()} | {error, Reason}</name>
<fsummary></fsummary>
<type>
<v>TotalStats = [total_stats()]</v>
@@ -2110,8 +2110,8 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>reset_stats() -> void()</name>
- <name>reset_stats(ConnHandle) -> void()</name>
+ <name since="">reset_stats() -> void()</name>
+ <name since="">reset_stats(ConnHandle) -> void()</name>
<fsummary></fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -2123,7 +2123,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>test_request(ConnHandle, Version, EncodingMod, EncodingConfig, Actions) -> {MegaMsg, EncodeRes}</name>
+ <name since="">test_request(ConnHandle, Version, EncodingMod, EncodingConfig, Actions) -> {MegaMsg, EncodeRes}</name>
<fsummary>Tests if the Actions argument is correct</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -2150,7 +2150,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>test_reply(ConnHandle, Version, EncodingMod, EncodingConfig, Reply) -> {MegaMsg, EncodeRes}</name>
+ <name since="">test_reply(ConnHandle, Version, EncodingMod, EncodingConfig, Reply) -> {MegaMsg, EncodeRes}</name>
<fsummary>Tests if the Reply argument is correct</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
diff --git a/lib/megaco/doc/src/megaco_codec_meas.xml b/lib/megaco/doc/src/megaco_codec_meas.xml
index 13cc3eb834..5184fe392e 100644
--- a/lib/megaco/doc/src/megaco_codec_meas.xml
+++ b/lib/megaco/doc/src/megaco_codec_meas.xml
@@ -32,7 +32,7 @@
<rev>%VSN%</rev>
<file>megaco_codec_meas.xml</file>
</header>
- <module>megaco_codec_meas</module>
+ <module since="">megaco_codec_meas</module>
<modulesummary>This module implements a simple megaco codec measurement tool.</modulesummary>
<description>
<p>This module implements a simple megaco codec measurement tool.</p>
@@ -43,8 +43,8 @@
<funcs>
<func>
- <name>start() -> void()</name>
- <name>start(MessagePackage) -> void()</name>
+ <name since="">start() -> void()</name>
+ <name since="">start(MessagePackage) -> void()</name>
<fsummary></fsummary>
<type>
<v>MessagePackageRaw = message_package()</v>
diff --git a/lib/megaco/doc/src/megaco_codec_mstone1.xml b/lib/megaco/doc/src/megaco_codec_mstone1.xml
index 2ff959a648..507a790c71 100644
--- a/lib/megaco/doc/src/megaco_codec_mstone1.xml
+++ b/lib/megaco/doc/src/megaco_codec_mstone1.xml
@@ -32,7 +32,7 @@
<rev>%VSN%</rev>
<file>megaco_codec_mstone1.xml</file>
</header>
- <module>megaco_codec_mstone1</module>
+ <module since="">megaco_codec_mstone1</module>
<modulesummary>This module implements a simple megaco codec-based performance tool.</modulesummary>
<description>
<p>This module implements the <em>mstone1</em> tool,
@@ -44,9 +44,9 @@
<funcs>
<func>
- <name>start() -> void()</name>
- <name>start(MessagePackage) -> void()</name>
- <name>start(MessagePackage, Factor) -> void()</name>
+ <name since="">start() -> void()</name>
+ <name since="">start(MessagePackage) -> void()</name>
+ <name since="">start(MessagePackage, Factor) -> void()</name>
<fsummary></fsummary>
<type>
<v>MessagePackage = message_package()</v>
@@ -63,9 +63,9 @@
</func>
<func>
- <name>start_flex() -> void()</name>
- <name>start_flex(MessagePackage) -> void()</name>
- <name>start_flex(MessagePackage, Factor) -> void()</name>
+ <name since="">start_flex() -> void()</name>
+ <name since="">start_flex(MessagePackage) -> void()</name>
+ <name since="">start_flex(MessagePackage, Factor) -> void()</name>
<fsummary></fsummary>
<type>
<v>MessagePackage = message_package()</v>
@@ -83,9 +83,9 @@
</func>
<func>
- <name>start_only_drv() -> void()</name>
- <name>start_only_drv(MessagePackage) -> void()</name>
- <name>start_only_drv(MessagePackage, Factor) -> void()</name>
+ <name since="">start_only_drv() -> void()</name>
+ <name since="">start_only_drv(MessagePackage) -> void()</name>
+ <name since="">start_only_drv(MessagePackage, Factor) -> void()</name>
<fsummary></fsummary>
<type>
<v>MessagePackage = message_package()</v>
@@ -105,9 +105,9 @@
</func>
<func>
- <name>start_no_drv() -> void()</name>
- <name>start_no_drv(MessagePackage) -> void()</name>
- <name>start_no_drv(MessagePackage, Factor) -> void()</name>
+ <name since="">start_no_drv() -> void()</name>
+ <name since="">start_no_drv(MessagePackage) -> void()</name>
+ <name since="">start_no_drv(MessagePackage, Factor) -> void()</name>
<fsummary></fsummary>
<type>
<v>MessagePackage = message_package()</v>
diff --git a/lib/megaco/doc/src/megaco_codec_mstone2.xml b/lib/megaco/doc/src/megaco_codec_mstone2.xml
index 3da30d4f99..03990f5c3d 100644
--- a/lib/megaco/doc/src/megaco_codec_mstone2.xml
+++ b/lib/megaco/doc/src/megaco_codec_mstone2.xml
@@ -32,7 +32,7 @@
<rev>%VSN%</rev>
<file>megaco_codec_mstone2.xml</file>
</header>
- <module>megaco_codec_mstone2</module>
+ <module since="">megaco_codec_mstone2</module>
<modulesummary>This module implements a simple megaco codec-based performance tool.</modulesummary>
<description>
<p>This module implements the <em>mstone2</em> tool,
@@ -44,8 +44,8 @@
<funcs>
<func>
- <name>start() -> void()</name>
- <name>start(MessagePackage) -> void()</name>
+ <name since="">start() -> void()</name>
+ <name since="">start(MessagePackage) -> void()</name>
<fsummary></fsummary>
<type>
<v>MessagePackage = message_package()</v>
diff --git a/lib/megaco/doc/src/megaco_codec_transform.xml b/lib/megaco/doc/src/megaco_codec_transform.xml
index 26b83c3799..392868fdfa 100644
--- a/lib/megaco/doc/src/megaco_codec_transform.xml
+++ b/lib/megaco/doc/src/megaco_codec_transform.xml
@@ -32,7 +32,7 @@
<rev>%VSN%</rev>
<file>megaco_codec_transform.xml</file>
</header>
- <module>megaco_codec_transform</module>
+ <module since="">megaco_codec_transform</module>
<modulesummary>Megaco message transformation utility.</modulesummary>
<description>
@@ -45,8 +45,8 @@
<funcs>
<func>
- <name>export_messages() -> void()</name>
- <name>export_messages(MessagePackage) -> void()</name>
+ <name since="">export_messages() -> void()</name>
+ <name since="">export_messages(MessagePackage) -> void()</name>
<fsummary></fsummary>
<type>
<v>MessagePackage = atom()</v>
diff --git a/lib/megaco/doc/src/megaco_edist_compress.xml b/lib/megaco/doc/src/megaco_edist_compress.xml
index d5c7c7224d..16443e469c 100644
--- a/lib/megaco/doc/src/megaco_edist_compress.xml
+++ b/lib/megaco/doc/src/megaco_edist_compress.xml
@@ -32,7 +32,7 @@
<rev>%VSN%</rev>
<file>megaco_edist_compress.xml</file>
</header>
- <module>megaco_edist_compress</module>
+ <module since="">megaco_edist_compress</module>
<modulesummary>Megaco erlang dist compress behaviour.</modulesummary>
<description>
<p>The following functions should be exported from a
@@ -40,7 +40,7 @@
</description>
<funcs>
<func>
- <name>Module:encode(R, Version) -> T</name>
+ <name since="">Module:encode(R, Version) -> T</name>
<fsummary>Encode (compress) a megaco component.</fsummary>
<type>
<v>R = megaco_message() | transaction() | action_reply() | action_request() | command_request()</v>
@@ -53,7 +53,7 @@
</desc>
</func>
<func>
- <name>Module:decode(T, Version) -> R</name>
+ <name since="">Module:decode(T, Version) -> R</name>
<fsummary>Decode (decompress) a megaco component.</fsummary>
<type>
<v>T = term()</v>
diff --git a/lib/megaco/doc/src/megaco_encoder.xml b/lib/megaco/doc/src/megaco_encoder.xml
index 13c6ed324b..cc8270440b 100644
--- a/lib/megaco/doc/src/megaco_encoder.xml
+++ b/lib/megaco/doc/src/megaco_encoder.xml
@@ -32,7 +32,7 @@
<rev>%VSN%</rev>
<file>megaco_encoder.xml</file>
</header>
- <module>megaco_encoder</module>
+ <module since="">megaco_encoder</module>
<modulesummary>Megaco encoder behaviour.</modulesummary>
<description>
<p>The following functions should be exported from a
@@ -64,7 +64,7 @@ action_reply() = #'ActionReply'{}
<funcs>
<func>
- <name>Module:encode_message(EncodingConfig, Version, Message) -> {ok, Bin} | Error</name>
+ <name since="">Module:encode_message(EncodingConfig, Version, Message) -> {ok, Bin} | Error</name>
<fsummary>Encode a megaco message.</fsummary>
<type>
<v>EncodingConfig = list()</v>
@@ -81,7 +81,7 @@ action_reply() = #'ActionReply'{}
</func>
<func>
- <name>Module:decode_message(EncodingConfig, Version, Bin) -> {ok, Message} | Error</name>
+ <name since="">Module:decode_message(EncodingConfig, Version, Bin) -> {ok, Message} | Error</name>
<fsummary>Decode a megaco message.</fsummary>
<type>
<v>EncodingConfig = list()</v>
@@ -104,7 +104,7 @@ action_reply() = #'ActionReply'{}
</func>
<func>
- <name>Module:decode_mini_message(EncodingConfig, Version, Bin) -> {ok, Message} | Error</name>
+ <name since="">Module:decode_mini_message(EncodingConfig, Version, Bin) -> {ok, Message} | Error</name>
<fsummary>Perform a minimal decode of a megaco message.</fsummary>
<type>
<v>EncodingConfig = list()</v>
@@ -129,7 +129,7 @@ action_reply() = #'ActionReply'{}
</func>
<func>
- <name>Module:encode_transaction(EncodingConfig, Version, Transaction) -> OK | Error</name>
+ <name since="">Module:encode_transaction(EncodingConfig, Version, Transaction) -> OK | Error</name>
<fsummary>Encode a megaco transaction.</fsummary>
<type>
<v>EncodingConfig = list()</v>
@@ -155,7 +155,7 @@ action_reply() = #'ActionReply'{}
</func>
<func>
- <name>Module:encode_action_requests(EncodingConfig, Version, ARs) -> OK | Error</name>
+ <name since="">Module:encode_action_requests(EncodingConfig, Version, ARs) -> OK | Error</name>
<fsummary>Encode megaco action requests.</fsummary>
<type>
<v>EncodingConfig = list()</v>
@@ -181,7 +181,7 @@ action_reply() = #'ActionReply'{}
</func>
<func>
- <name>Module:encode_action_reply(EncodingConfig, Version, AR) -> OK | Error</name>
+ <name since="">Module:encode_action_reply(EncodingConfig, Version, AR) -> OK | Error</name>
<fsummary>Encode a megaco action reply.</fsummary>
<type>
<v>EncodingConfig = list()</v>
diff --git a/lib/megaco/doc/src/megaco_flex_scanner.xml b/lib/megaco/doc/src/megaco_flex_scanner.xml
index 0856f3f429..121a7fbcff 100644
--- a/lib/megaco/doc/src/megaco_flex_scanner.xml
+++ b/lib/megaco/doc/src/megaco_flex_scanner.xml
@@ -32,7 +32,7 @@
<rev>%VSN%</rev>
<file>megaco_flex_scanner.xml</file>
</header>
- <module>megaco_flex_scanner</module>
+ <module since="">megaco_flex_scanner</module>
<modulesummary>Interface module to the flex scanner linked in driver.</modulesummary>
<description>
<p>This module contains the public interface to the flex scanner
@@ -72,7 +72,7 @@ megaco_version() = integer() >= 1
<funcs>
<func>
- <name>start() -> {ok, PortOrPorts} | {error, Reason}</name>
+ <name since="">start() -> {ok, PortOrPorts} | {error, Reason}</name>
<fsummary></fsummary>
<type>
<v>PortOrPorts = megaco_ports()</v>
@@ -94,7 +94,7 @@ megaco_version() = integer() >= 1
</func>
<func>
- <name>stop(PortOrPorts) -> stopped</name>
+ <name since="">stop(PortOrPorts) -> stopped</name>
<fsummary></fsummary>
<type>
<v>PortOrPorts = megaco_ports()</v>
@@ -108,7 +108,7 @@ megaco_version() = integer() >= 1
</func>
<func>
- <name>is_reentrant_enabled() -> Boolean</name>
+ <name since="">is_reentrant_enabled() -> Boolean</name>
<fsummary></fsummary>
<type>
<v>Boolean = boolean()</v>
@@ -121,7 +121,7 @@ megaco_version() = integer() >= 1
</func>
<func>
- <name>is_scanner_port(Port, PortOrPorts) -> Boolean</name>
+ <name since="">is_scanner_port(Port, PortOrPorts) -> Boolean</name>
<fsummary></fsummary>
<type>
<v>Port = port()</v>
@@ -137,7 +137,7 @@ megaco_version() = integer() >= 1
</func>
<func>
- <name>scan(Binary, PortOrPorts) -> {ok, Tokens, Version, LatestLine} | {error, Reason, LatestLine} </name>
+ <name since="">scan(Binary, PortOrPorts) -> {ok, Tokens, Version, LatestLine} | {error, Reason, LatestLine} </name>
<fsummary></fsummary>
<type>
<v>Binary = binary()</v>
diff --git a/lib/megaco/doc/src/megaco_tcp.xml b/lib/megaco/doc/src/megaco_tcp.xml
index 77aee32f6c..63713b2c56 100644
--- a/lib/megaco/doc/src/megaco_tcp.xml
+++ b/lib/megaco/doc/src/megaco_tcp.xml
@@ -32,7 +32,7 @@
<rev>%VSN%</rev>
<file>megaco_tcp.xml</file>
</header>
- <module>megaco_tcp</module>
+ <module since="">megaco_tcp</module>
<modulesummary>Interface module to TPKT transport protocol for Megaco/H.248.</modulesummary>
<description>
<p>This module contains the public interface to the TPKT (TCP/IP) version
@@ -40,7 +40,7 @@
</description>
<funcs>
<func>
- <name>start_transport() -> {ok, TransportRef}</name>
+ <name since="">start_transport() -> {ok, TransportRef}</name>
<fsummary></fsummary>
<type>
<v>TransportRef = pid()</v>
@@ -51,7 +51,7 @@
</desc>
</func>
<func>
- <name>listen(TransportRef, ListenPortSpecList) -> ok</name>
+ <name since="">listen(TransportRef, ListenPortSpecList) -> ok</name>
<fsummary></fsummary>
<type>
<v>TransportRef = pid() | regname()</v>
@@ -65,7 +65,7 @@
</desc>
</func>
<func>
- <name>connect(TransportRef, OptionList) -> {ok, Handle, ControlPid} | {error, Reason}</name>
+ <name since="">connect(TransportRef, OptionList) -> {ok, Handle, ControlPid} | {error, Reason}</name>
<fsummary></fsummary>
<type>
<v>TransportRef = pid() | regname()</v>
@@ -86,7 +86,7 @@
</desc>
</func>
<func>
- <name>close(Handle) -> ok</name>
+ <name since="">close(Handle) -> ok</name>
<fsummary></fsummary>
<type>
<v>Handle = socket_handle()</v>
@@ -96,7 +96,7 @@
</desc>
</func>
<func>
- <name>socket(Handle) -> Socket</name>
+ <name since="">socket(Handle) -> Socket</name>
<fsummary></fsummary>
<type>
<v>Handle = socket_handle()</v>
@@ -109,7 +109,7 @@
</desc>
</func>
<func>
- <name>send_message(Handle, Message) -> ok</name>
+ <name since="">send_message(Handle, Message) -> ok</name>
<fsummary></fsummary>
<type>
<v>Handle = socket_handle()</v>
@@ -120,7 +120,7 @@
</desc>
</func>
<func>
- <name>block(Handle) -> ok</name>
+ <name since="">block(Handle) -> ok</name>
<fsummary></fsummary>
<type>
<v>Handle = socket_handle()</v>
@@ -130,7 +130,7 @@
</desc>
</func>
<func>
- <name>unblock(Handle) -> ok</name>
+ <name since="">unblock(Handle) -> ok</name>
<fsummary></fsummary>
<type>
<v>Handle = socket_handle()</v>
@@ -141,7 +141,7 @@
</desc>
</func>
<func>
- <name>upgrade_receive_handle(ControlPid) -> ok</name>
+ <name since="">upgrade_receive_handle(ControlPid) -> ok</name>
<fsummary></fsummary>
<type>
<v>ControlPid = pid()</v>
@@ -153,9 +153,9 @@
</desc>
</func>
<func>
- <name>get_stats() -> {ok, TotalStats} | {error, Reason}</name>
- <name>get_stats(SendHandle) -> {ok, SendHandleStats} | {error, Reason}</name>
- <name>get_stats(SendHandle, Counter) -> {ok, CounterStats} | {error, Reason}</name>
+ <name since="">get_stats() -> {ok, TotalStats} | {error, Reason}</name>
+ <name since="">get_stats(SendHandle) -> {ok, SendHandleStats} | {error, Reason}</name>
+ <name since="">get_stats(SendHandle, Counter) -> {ok, CounterStats} | {error, Reason}</name>
<fsummary></fsummary>
<type>
<v>TotalStats = [send_handle_stats()]</v>
@@ -173,8 +173,8 @@
</desc>
</func>
<func>
- <name>reset_stats() -> void()</name>
- <name>reset_stats(SendHandle) -> void()</name>
+ <name since="">reset_stats() -> void()</name>
+ <name since="">reset_stats(SendHandle) -> void()</name>
<fsummary></fsummary>
<type>
<v>SendHandle = send_handle()</v>
diff --git a/lib/megaco/doc/src/megaco_transport.xml b/lib/megaco/doc/src/megaco_transport.xml
index 3002e9b74e..ba8c794750 100644
--- a/lib/megaco/doc/src/megaco_transport.xml
+++ b/lib/megaco/doc/src/megaco_transport.xml
@@ -32,7 +32,7 @@
<rev>%VSN%</rev>
<file>megaco_transport.xml</file>
</header>
- <module>megaco_transport</module>
+ <module since="">megaco_transport</module>
<modulesummary>Megaco transport behaviour.</modulesummary>
<description>
<p>The following functions should be exported from a
@@ -54,8 +54,8 @@
</description>
<funcs>
<func>
- <name>Module:send_message(Handle, Msg) -> ok | {cancel, Reason} | Error</name>
- <name>Module:send_message(Handle, Msg, Resend) -> ok | {cancel, Reason} | Error</name>
+ <name since="">Module:send_message(Handle, Msg) -> ok | {cancel, Reason} | Error</name>
+ <name since="">Module:send_message(Handle, Msg, Resend) -> ok | {cancel, Reason} | Error</name>
<fsummary>Send a megaco message.</fsummary>
<type>
<v>Handle = term()</v>
@@ -99,7 +99,7 @@
</func>
<func>
- <name>Module:resend_message(Handle, Msg) -> ok | {cancel, Reason} | Error</name>
+ <name since="">Module:resend_message(Handle, Msg) -> ok | {cancel, Reason} | Error</name>
<fsummary>Re-send a megaco message.</fsummary>
<type>
<v>Handle = term()</v>
diff --git a/lib/megaco/doc/src/megaco_udp.xml b/lib/megaco/doc/src/megaco_udp.xml
index b2559c77d5..3d776c19b6 100644
--- a/lib/megaco/doc/src/megaco_udp.xml
+++ b/lib/megaco/doc/src/megaco_udp.xml
@@ -32,7 +32,7 @@
<rev>%VSN%</rev>
<file>megaco_udp.xml</file>
</header>
- <module>megaco_udp</module>
+ <module since="">megaco_udp</module>
<modulesummary>Interface module to UDP transport protocol for Megaco/H.248.</modulesummary>
<description>
<p>This module contains the public interface to the UDP/IP version
@@ -40,7 +40,7 @@
</description>
<funcs>
<func>
- <name>start_transport() -> {ok, TransportRef}</name>
+ <name since="">start_transport() -> {ok, TransportRef}</name>
<fsummary></fsummary>
<type>
<v>TransportRef = pid()</v>
@@ -51,7 +51,7 @@
</desc>
</func>
<func>
- <name>open(TransportRef, OptionList) -> {ok, Handle, ControlPid} | {error, Reason}</name>
+ <name since="">open(TransportRef, OptionList) -> {ok, Handle, ControlPid} | {error, Reason}</name>
<fsummary></fsummary>
<type>
<v>TransportRef = pid() | regname()</v>
@@ -73,7 +73,7 @@
</desc>
</func>
<func>
- <name>close(Handle, Msg) -> ok</name>
+ <name since="">close(Handle, Msg) -> ok</name>
<fsummary></fsummary>
<type>
<v>Handle = socket_handle()</v>
@@ -84,7 +84,7 @@
</desc>
</func>
<func>
- <name>socket(Handle) -> Socket</name>
+ <name since="">socket(Handle) -> Socket</name>
<fsummary></fsummary>
<type>
<v>Handle = socket_handle()</v>
@@ -97,7 +97,7 @@
</desc>
</func>
<func>
- <name>create_send_handle(Handle, Host, Port) -> send_handle()</name>
+ <name since="">create_send_handle(Handle, Host, Port) -> send_handle()</name>
<fsummary></fsummary>
<type>
<v>Handle = socket_handle()</v>
@@ -110,7 +110,7 @@
</desc>
</func>
<func>
- <name>send_message(SendHandle, Msg) -> ok</name>
+ <name since="">send_message(SendHandle, Msg) -> ok</name>
<fsummary></fsummary>
<type>
<v>SendHandle = send_handle()</v>
@@ -125,7 +125,7 @@
</desc>
</func>
<func>
- <name>block(Handle) -> ok</name>
+ <name since="">block(Handle) -> ok</name>
<fsummary></fsummary>
<type>
<v>Handle = socket_handle()</v>
@@ -135,7 +135,7 @@
</desc>
</func>
<func>
- <name>unblock(Handle) -> ok</name>
+ <name since="">unblock(Handle) -> ok</name>
<fsummary></fsummary>
<type>
<v>Handle = socket_handle()</v>
@@ -146,7 +146,7 @@
</desc>
</func>
<func>
- <name>upgrade_receive_handle(ControlPid, NewHandle) -> ok</name>
+ <name since="">upgrade_receive_handle(ControlPid, NewHandle) -> ok</name>
<fsummary></fsummary>
<type>
<v>ControlPid = pid()</v>
@@ -160,9 +160,9 @@
</desc>
</func>
<func>
- <name>get_stats() -> {ok, TotalStats} | {error, Reason}</name>
- <name>get_stats(SendHandle) -> {ok, SendHandleStats} | {error, Reason}</name>
- <name>get_stats(SendHandle, Counter) -> {ok, CounterStats} | {error, Reason}</name>
+ <name since="">get_stats() -> {ok, TotalStats} | {error, Reason}</name>
+ <name since="">get_stats(SendHandle) -> {ok, SendHandleStats} | {error, Reason}</name>
+ <name since="">get_stats(SendHandle, Counter) -> {ok, CounterStats} | {error, Reason}</name>
<fsummary></fsummary>
<type>
<v>TotalStats = [total_stats()]</v>
@@ -180,8 +180,8 @@
</desc>
</func>
<func>
- <name>reset_stats() -> void()</name>
- <name>reset_stats(SendHandle) -> void()</name>
+ <name since="">reset_stats() -> void()</name>
+ <name since="">reset_stats(SendHandle) -> void()</name>
<fsummary></fsummary>
<type>
<v>SendHandle = send_handle()</v>
diff --git a/lib/megaco/doc/src/megaco_user.xml b/lib/megaco/doc/src/megaco_user.xml
index 067be15fe0..198f2aa24c 100644
--- a/lib/megaco/doc/src/megaco_user.xml
+++ b/lib/megaco/doc/src/megaco_user.xml
@@ -32,7 +32,7 @@
<rev>%VSN%</rev>
<file>megaco_user.xml</file>
</header>
- <module>megaco_user</module>
+ <module since="">megaco_user</module>
<modulesummary>Callback module for users of the Megaco application</modulesummary>
<description>
<p>This module defines the callback behaviour of Megaco users. A
@@ -164,8 +164,8 @@ protocol_version() = integer() ]]></code>
<funcs>
<func>
- <name>handle_connect(ConnHandle, ProtocolVersion) -> ok | error | {error,ErrorDescr}</name>
- <name>handle_connect(ConnHandle, ProtocolVersion, Extra]) -> ok | error | {error,ErrorDescr}</name>
+ <name since="">handle_connect(ConnHandle, ProtocolVersion) -> ok | error | {error,ErrorDescr}</name>
+ <name since="">handle_connect(ConnHandle, ProtocolVersion, Extra]) -> ok | error | {error,ErrorDescr}</name>
<fsummary>Invoked when a new connection is established</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -202,7 +202,7 @@ protocol_version() = integer() ]]></code>
</func>
<func>
- <name>handle_disconnect(ConnHandle, ProtocolVersion, Reason) -> ok</name>
+ <name since="">handle_disconnect(ConnHandle, ProtocolVersion, Reason) -> ok</name>
<fsummary>Invoked when a connection is teared down</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -220,8 +220,8 @@ protocol_version() = integer() ]]></code>
</func>
<func>
- <name>handle_syntax_error(ReceiveHandle, ProtocolVersion, DefaultED) -> reply | {reply, ED} | no_reply | {no_reply, ED} </name>
- <name>handle_syntax_error(ReceiveHandle, ProtocolVersion, DefaultED, Extra) -> reply | {reply, ED} | no_reply | {no_reply, ED} </name>
+ <name since="">handle_syntax_error(ReceiveHandle, ProtocolVersion, DefaultED) -> reply | {reply, ED} | no_reply | {no_reply, ED} </name>
+ <name since="">handle_syntax_error(ReceiveHandle, ProtocolVersion, DefaultED, Extra) -> reply | {reply, ED} | no_reply | {no_reply, ED} </name>
<fsummary>Invoked when a received message had syntax errors</fsummary>
<type>
<v>ReceiveHandle = receive_handle()</v>
@@ -258,8 +258,8 @@ protocol_version() = integer() ]]></code>
</func>
<func>
- <name>handle_message_error(ConnHandle, ProtocolVersion, ErrorDescr) -> ok</name>
- <name>handle_message_error(ConnHandle, ProtocolVersion, ErrorDescr, Extra) -> ok</name>
+ <name since="">handle_message_error(ConnHandle, ProtocolVersion, ErrorDescr) -> ok</name>
+ <name since="">handle_message_error(ConnHandle, ProtocolVersion, ErrorDescr, Extra) -> ok</name>
<fsummary>Invoked when a received message just contains an error</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -291,8 +291,8 @@ protocol_version() = integer() ]]></code>
<!--
<func>
- <name>handle_segment_error(ConnHandle, ProtocolVersion, TransId, SegmentError) -> ok</name>
- <name>handle_segment_error(ConnHandle, ProtocolVersion, TransId, SegmentError, Extra) -> ok</name>
+ <name since="">handle_segment_error(ConnHandle, ProtocolVersion, TransId, SegmentError) -> ok</name>
+ <name since="">handle_segment_error(ConnHandle, ProtocolVersion, TransId, SegmentError, Extra) -> ok</name>
<fsummary>Invoked when a segment error has been detected</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -316,8 +316,8 @@ protocol_version() = integer() ]]></code>
-->
<func>
- <name>handle_trans_request(ConnHandle, ProtocolVersion, ActionRequests) -> pending() | reply() | ignore_trans_request</name>
- <name>handle_trans_request(ConnHandle, ProtocolVersion, ActionRequests, Extra) -> pending() | reply() | ignore_trans_request</name>
+ <name since="">handle_trans_request(ConnHandle, ProtocolVersion, ActionRequests) -> pending() | reply() | ignore_trans_request</name>
+ <name since="">handle_trans_request(ConnHandle, ProtocolVersion, ActionRequests, Extra) -> pending() | reply() | ignore_trans_request</name>
<fsummary>Invoked for each transaction request</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -420,8 +420,8 @@ protocol_version() = integer() ]]></code>
</func>
<func>
- <name>handle_trans_long_request(ConnHandle, ProtocolVersion, ReqData) -> reply()</name>
- <name>handle_trans_long_request(ConnHandle, ProtocolVersion, ReqData, Extra) -> reply()</name>
+ <name since="">handle_trans_long_request(ConnHandle, ProtocolVersion, ReqData) -> reply()</name>
+ <name since="">handle_trans_long_request(ConnHandle, ProtocolVersion, ReqData, Extra) -> reply()</name>
<fsummary>Optionally invoked for a time consuming transaction request</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -460,8 +460,8 @@ protocol_version() = integer() ]]></code>
</func>
<func>
- <name>handle_trans_reply(ConnHandle, ProtocolVersion, UserReply, ReplyData) -> ok</name>
- <name>handle_trans_reply(ConnHandle, ProtocolVersion, UserReply, ReplyData, Extra) -> ok</name>
+ <name since="">handle_trans_reply(ConnHandle, ProtocolVersion, UserReply, ReplyData) -> ok</name>
+ <name since="">handle_trans_reply(ConnHandle, ProtocolVersion, UserReply, ReplyData, Extra) -> ok</name>
<fsummary>Optionally invoked for a transaction reply</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -593,8 +593,8 @@ protocol_version() = integer() ]]></code>
</func>
<func>
- <name>handle_trans_ack(ConnHandle, ProtocolVersion, AckStatus, AckData) -> ok</name>
- <name>handle_trans_ack(ConnHandle, ProtocolVersion, AckStatus, AckData, Extra) -> ok</name>
+ <name since="">handle_trans_ack(ConnHandle, ProtocolVersion, AckStatus, AckData) -> ok</name>
+ <name since="">handle_trans_ack(ConnHandle, ProtocolVersion, AckStatus, AckData, Extra) -> ok</name>
<fsummary>Optionally invoked for a transaction acknowledgement</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -658,8 +658,8 @@ protocol_version() = integer() ]]></code>
</func>
<func>
- <name>handle_unexpected_trans(ConnHandle, ProtocolVersion, Trans) -> ok</name>
- <name>handle_unexpected_trans(ConnHandle, ProtocolVersion, Trans, Extra) -> ok</name>
+ <name since="">handle_unexpected_trans(ConnHandle, ProtocolVersion, Trans) -> ok</name>
+ <name since="">handle_unexpected_trans(ConnHandle, ProtocolVersion, Trans, Extra) -> ok</name>
<fsummary>Invoked when an unexpected message is received</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -685,8 +685,8 @@ protocol_version() = integer() ]]></code>
</func>
<func>
- <name>handle_trans_request_abort(ConnHandle, ProtocolVersion, TransNo, Pid) -> ok</name>
- <name>handle_trans_request_abort(ConnHandle, ProtocolVersion, TransNo, Pid, Extra) -> ok</name>
+ <name since="">handle_trans_request_abort(ConnHandle, ProtocolVersion, TransNo, Pid) -> ok</name>
+ <name since="">handle_trans_request_abort(ConnHandle, ProtocolVersion, TransNo, Pid, Extra) -> ok</name>
<fsummary>Invoked when an transaction request has been aborted</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -710,8 +710,8 @@ protocol_version() = integer() ]]></code>
</func>
<func>
- <name>handle_segment_reply(ConnHandle, ProtocolVersion, TransNo, SegNo, SegCompl) -> ok</name>
- <name>handle_segment_reply(ConnHandle, ProtocolVersion, TransNo, SegNo, SegCompl, Extra) -> ok</name>
+ <name since="">handle_segment_reply(ConnHandle, ProtocolVersion, TransNo, SegNo, SegCompl) -> ok</name>
+ <name since="">handle_segment_reply(ConnHandle, ProtocolVersion, TransNo, SegNo, SegCompl, Extra) -> ok</name>
<fsummary>Segment Reply Indication</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
diff --git a/lib/megaco/src/flex/Makefile.in b/lib/megaco/src/flex/Makefile.in
index c37ad4d702..26d2ddd44c 100644
--- a/lib/megaco/src/flex/Makefile.in
+++ b/lib/megaco/src/flex/Makefile.in
@@ -31,25 +31,6 @@ include ../../vsn.mk
VSN=$(MEGACO_VSN)
# ----------------------------------------------------
-# Dynamic Erlang Driver
-# ----------------------------------------------------
-HAVE_USABLE_OTP_DED_MK = @HAVE_USABLE_OTP_DED_MK@
-
-ifeq ($(HAVE_USABLE_OTP_DED_MK),yes)
-# otp_ded.mk will be used on R13B04 and later
-include $(ERL_TOP)/make/$(TARGET)/otp_ded.mk
-else
-# megacos configure provide the info instead
-DED_CC = @CC@
-DED__NOWARN_NOTHR_CFLAGS = @DED_CFLAGS@
-DED_THR_DEFS = @DED_THR_DEFS@
-DED_LD = @DED_LD@
-DED_LDFLAGS = @DED_LDFLAGS@
-DED_INCLUDES = @DED_INCLUDES@
-DED_EXT = so
-endif
-
-# ----------------------------------------------------
# The following variables differ on different systems, we set
# reasonable defaults, if something different is needed it should
# be set for that system only.
@@ -57,20 +38,19 @@ endif
FLEX_VSN = $(shell flex --version)
-TMP_CFLAGS = $(DED__NOWARN_NOTHR_CFLAGS) @OTP_EXTRA_FLAGS@
+TMP_CFLAGS = @DED_BASIC_CFLAGS@ @OTP_EXTRA_FLAGS@
ifeq ($(TYPE),valgrind)
CFLAGS = $(subst -O2, , $(TMP_CFLAGS)) -DVALGRIND
else
CFLAGS = $(TMP_CFLAGS)
endif
-CC = $(DED_CC)
-CFLAGS_MT = $(CFLAGS) $(DED_THR_DEFS)
-LD = $(DED_LD)
-LDFLAGS = $(DED_LDFLAGS)
+CC = @DED_CC@
+CFLAGS_MT = $(CFLAGS) @DED_THR_DEFS@
+LD = @DED_LD@
+LDFLAGS = @DED_LDFLAGS@
LEX = @LEX@
LEXLIB = @LEXLIB@
PERL = @PERL@
-ERLANG_OSTYPE = @ERLANG_OSTYPE@
# Shall we build the flex scanner or not.
# We assume that it does not exist on windows...
@@ -143,8 +123,8 @@ ifeq ($(findstring win32,$(TARGET)), win32)
FLEX_SCANNER_SO =
SOLIBS = $(FLEX_SCANNER_SO)
else
-FLEX_SCANNER_SO = $(LIBDIR)/$(STD_DRV).$(DED_EXT)
-FLEX_SCANNER_MT_SO = $(LIBDIR)/$(MT_DRV).$(DED_EXT)
+FLEX_SCANNER_SO = $(LIBDIR)/$(STD_DRV).@DED_EXT@
+FLEX_SCANNER_MT_SO = $(LIBDIR)/$(MT_DRV).@DED_EXT@
SOLIBS = $(FLEX_SCANNER_SO) $(FLEX_SCANNER_MT_SO)
endif
@@ -179,7 +159,7 @@ else
CFLAGS += -DMFS_FLEX_DEBUG=0
endif
-CFLAGS += $(DED_INCLUDES) -I$(ERL_TOP)/erts/$(TARGET) $(DRV_FLAGS) -funroll-loops -Wall
+CFLAGS += @DED_INCLUDE@ -I$(ERL_TOP)/erts/$(TARGET) $(DRV_FLAGS) -funroll-loops -Wall
#ifneq ($(FLEX_VSN),)
#CFLAGS += -DFLEX_VERSION="$(FLEX_VSN)"
@@ -398,10 +378,10 @@ $(OBJDIR)/$(MT_DRV).o: $(MT_DRV).c
# No need to link with -lfl as we have also defined %option noyywrap -
# and having -lfl doesn't work under Darwin for some reason. - Sean
-$(LIBDIR)/$(STD_DRV).$(DED_EXT): $(OBJDIR)/$(STD_DRV).o
+$(LIBDIR)/$(STD_DRV).@DED_EXT@: $(OBJDIR)/$(STD_DRV).o
$(V_colon)@echo "linking std driver:"
$(V_LD) $(LDFLAGS) -o $@ $<
-$(LIBDIR)/$(MT_DRV).$(DED_EXT): $(OBJDIR)/$(MT_DRV).o
+$(LIBDIR)/$(MT_DRV).@DED_EXT@: $(OBJDIR)/$(MT_DRV).o
$(V_colon)@echo "linking multi-threaded driver:"
$(V_LD) $(LDFLAGS) -o $@ $<
diff --git a/lib/mnesia/doc/src/mnesia.xml b/lib/mnesia/doc/src/mnesia.xml
index 621b6047ee..11b0b8e987 100644
--- a/lib/mnesia/doc/src/mnesia.xml
+++ b/lib/mnesia/doc/src/mnesia.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file></file>
</header>
- <module>mnesia</module>
+ <module since="">mnesia</module>
<modulesummary>A distributed telecommunications DBMS</modulesummary>
<description>
@@ -183,7 +183,7 @@
<funcs>
<func>
- <name>abort(Reason) -> transaction abort</name>
+ <name since="">abort(Reason) -> transaction abort</name>
<fsummary>Terminates the current transaction.</fsummary>
<desc>
<p>Makes the transaction silently
@@ -195,7 +195,7 @@
</desc>
</func>
<func>
- <name>activate_checkpoint(Args) -> {ok,Name,Nodes} | {error,Reason}</name>
+ <name since="">activate_checkpoint(Args) -> {ok,Name,Nodes} | {error,Reason}</name>
<fsummary>Activates a checkpoint.</fsummary>
<desc>
<marker id="activate_checkpoint"></marker>
@@ -259,7 +259,7 @@
</desc>
</func>
<func>
- <name>activity(AccessContext, Fun [, Args]) -> ResultOfFun | exit(Reason)</name>
+ <name since="">activity(AccessContext, Fun [, Args]) -> ResultOfFun | exit(Reason)</name>
<fsummary>Executes <c>Fun</c> in <c>AccessContext</c>.</fsummary>
<desc>
<marker id="activity_2_3"></marker>
@@ -271,7 +271,7 @@
</desc>
</func>
<func>
- <name>activity(AccessContext, Fun, Args, AccessMod) -> ResultOfFun | exit(Reason)</name>
+ <name since="">activity(AccessContext, Fun, Args, AccessMod) -> ResultOfFun | exit(Reason)</name>
<fsummary>Executes <c>Fun</c> in <c>AccessContext</c>.</fsummary>
<desc>
<marker id="activity_4"></marker>
@@ -403,7 +403,7 @@
</desc>
</func>
<func>
- <name>add_table_copy(Tab, Node, Type) -> {aborted, R} | {atomic, ok}</name>
+ <name since="">add_table_copy(Tab, Node, Type) -> {aborted, R} | {atomic, ok}</name>
<fsummary>Copies a table to a remote node.</fsummary>
<desc>
<marker id="add_table_copy"></marker>
@@ -420,7 +420,7 @@ mnesia:add_table_copy(person, Node, disc_copies)</code>
</desc>
</func>
<func>
- <name>add_table_index(Tab, AttrName) -> {aborted, R} | {atomic, ok}</name>
+ <name since="">add_table_index(Tab, AttrName) -> {aborted, R} | {atomic, ok}</name>
<fsummary>Creates an index for a table.</fsummary>
<desc>
<marker id="add_table_index"></marker>
@@ -441,7 +441,7 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name>all_keys(Tab) -> KeyList | transaction abort</name>
+ <name since="">all_keys(Tab) -> KeyList | transaction abort</name>
<fsummary>Returns all keys in a table.</fsummary>
<desc>
<marker id="all_keys"></marker>
@@ -453,7 +453,7 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name>async_dirty(Fun, [, Args]) -> ResultOfFun | exit(Reason)</name>
+ <name since="">async_dirty(Fun, [, Args]) -> ResultOfFun | exit(Reason)</name>
<fsummary>Calls the <c>Fun</c> in a context that is not protected by a transaction.</fsummary>
<desc>
<marker id="async_dirty"></marker>
@@ -493,7 +493,7 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name>backup(Opaque [, BackupMod]) -> ok | {error,Reason}</name>
+ <name since="">backup(Opaque [, BackupMod]) -> ok | {error,Reason}</name>
<fsummary>Backs up all tables in the database.</fsummary>
<desc>
<marker id="backup"></marker>
@@ -505,7 +505,7 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name>backup_checkpoint(Name, Opaque [, BackupMod]) -> ok | {error,Reason}</name>
+ <name since="">backup_checkpoint(Name, Opaque [, BackupMod]) -> ok | {error,Reason}</name>
<fsummary>Backs up all tables in a checkpoint.</fsummary>
<desc>
<marker id="backup_checkpoint"></marker>
@@ -520,7 +520,7 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name>change_config(Config, Value) -> {error, Reason} | {ok, ReturnValue}</name>
+ <name since="">change_config(Config, Value) -> {error, Reason} | {ok, ReturnValue}</name>
<fsummary>Changes a configuration parameter.</fsummary>
<desc>
<marker id="change_config"></marker>
@@ -554,7 +554,7 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name>change_table_access_mode(Tab, AccessMode) -> {aborted, R} | {atomic, ok}</name>
+ <name since="">change_table_access_mode(Tab, AccessMode) -> {aborted, R} | {atomic, ok}</name>
<fsummary>Changes the access mode for the table.</fsummary>
<desc>
<marker id="change_table_access_mode"></marker>
@@ -568,7 +568,7 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name>change_table_copy_type(Tab, Node, To) -> {aborted, R} | {atomic, ok}</name>
+ <name since="">change_table_copy_type(Tab, Node, To) -> {aborted, R} | {atomic, ok}</name>
<fsummary>Changes the storage type of a table.</fsummary>
<desc>
<marker id="change_table_copy_type"></marker>
@@ -585,7 +585,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code>
</desc>
</func>
<func>
- <name>change_table_load_order(Tab, LoadOrder) -> {aborted, R} | {atomic, ok}</name>
+ <name since="">change_table_load_order(Tab, LoadOrder) -> {aborted, R} | {atomic, ok}</name>
<fsummary>Changes the load order priority for the table.</fsummary>
<desc>
<marker id="change_table_load_order"></marker>
@@ -595,7 +595,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code>
</desc>
</func>
<func>
- <name>change_table_majority(Tab, Majority) -> {aborted, R} | {atomic, ok}</name>
+ <name since="OTP R14B03">change_table_majority(Tab, Majority) -> {aborted, R} | {atomic, ok}</name>
<fsummary>Changes the majority check setting for the table.</fsummary>
<desc>
<p><c>Majority</c> must be a boolean. Default is <c>false</c>.
@@ -607,7 +607,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code>
</desc>
</func>
<func>
- <name>clear_table(Tab) -> {aborted, R} | {atomic, ok}</name>
+ <name since="">clear_table(Tab) -> {aborted, R} | {atomic, ok}</name>
<fsummary>Deletes all entries in a table.</fsummary>
<desc>
<marker id="clear_table"></marker>
@@ -615,7 +615,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code>
</desc>
</func>
<func>
- <name>create_schema(DiscNodes) -> ok | {error,Reason}</name>
+ <name since="">create_schema(DiscNodes) -> ok | {error,Reason}</name>
<fsummary>Creates a new schema on the specified nodes.</fsummary>
<desc>
<marker id="create_schema"></marker>
@@ -637,7 +637,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code>
</desc>
</func>
<func>
- <name>create_table(Name, TabDef) -> {atomic, ok} | {aborted, Reason}</name>
+ <name since="">create_table(Name, TabDef) -> {atomic, ok} | {aborted, Reason}</name>
<fsummary>Creates a Mnesia table called <c>Name</c>with properties as described by argument <c>TabDef</c>.</fsummary>
<desc>
<marker id="create_table"></marker>
@@ -799,7 +799,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>deactivate_checkpoint(Name) -> ok | {error, Reason}</name>
+ <name since="">deactivate_checkpoint(Name) -> ok | {error, Reason}</name>
<fsummary>Deactivates a checkpoint.</fsummary>
<desc>
<marker id="deactivate_checkpoint"></marker>
@@ -811,7 +811,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>del_table_copy(Tab, Node) -> {aborted, R} | {atomic, ok}</name>
+ <name since="">del_table_copy(Tab, Node) -> {aborted, R} | {atomic, ok}</name>
<fsummary>Deletes the replica of table <c>Tab</c> at node <c>Node</c>.</fsummary>
<desc>
<marker id="del_table_copy"></marker>
@@ -825,7 +825,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>del_table_index(Tab, AttrName) -> {aborted, R} | {atomic, ok}</name>
+ <name since="">del_table_index(Tab, AttrName) -> {aborted, R} | {atomic, ok}</name>
<fsummary>Deletes an index in a table.</fsummary>
<desc>
<marker id="del_table_index"></marker>
@@ -834,7 +834,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>delete({Tab, Key}) -> transaction abort | ok</name>
+ <name since="">delete({Tab, Key}) -> transaction abort | ok</name>
<fsummary>Deletes all records in table <c>Tab</c> with the key <c>Key</c>.</fsummary>
<desc>
<marker id="delete_2"></marker>
@@ -842,7 +842,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>delete(Tab, Key, LockKind) -> transaction abort | ok</name>
+ <name since="">delete(Tab, Key, LockKind) -> transaction abort | ok</name>
<fsummary>Deletes all records in table <c>Tab</c>with the key <c>Key</c>.</fsummary>
<desc>
<marker id="delete_3"></marker>
@@ -857,7 +857,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>delete_object(Record) -> transaction abort | ok</name>
+ <name since="">delete_object(Record) -> transaction abort | ok</name>
<fsummary>Delete a record.</fsummary>
<desc>
<marker id="delete_object_1"></marker>
@@ -866,7 +866,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>delete_object(Tab, Record, LockKind) -> transaction abort | ok</name>
+ <name since="">delete_object(Tab, Record, LockKind) -> transaction abort | ok</name>
<fsummary>Deletes a record.</fsummary>
<desc>
<marker id="delete_object_3"></marker>
@@ -883,7 +883,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>delete_schema(DiscNodes) -> ok | {error,Reason}</name>
+ <name since="">delete_schema(DiscNodes) -> ok | {error,Reason}</name>
<fsummary>Deletes the schema on the given nodes.</fsummary>
<desc>
<marker id="delete_schema"></marker>
@@ -904,7 +904,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>delete_table(Tab) -> {aborted, Reason} | {atomic, ok}</name>
+ <name since="">delete_table(Tab) -> {aborted, Reason} | {atomic, ok}</name>
<fsummary>Deletes permanently all replicas of table <c>Tab</c>.</fsummary>
<desc>
<marker id="delete_table"></marker>
@@ -912,7 +912,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_all_keys(Tab) -> KeyList | exit({aborted, Reason})</name>
+ <name since="">dirty_all_keys(Tab) -> KeyList | exit({aborted, Reason})</name>
<fsummary>Dirty search for all record keys in table.</fsummary>
<desc>
<marker id="delete_all_keys"></marker>
@@ -920,7 +920,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_delete({Tab, Key}) -> ok | exit({aborted, Reason})</name>
+ <name since="">dirty_delete({Tab, Key}) -> ok | exit({aborted, Reason})</name>
<fsummary>Dirty delete of a record.</fsummary>
<desc>
<marker id="dirty_delete"></marker>
@@ -928,14 +928,14 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_delete(Tab, Key) -> ok | exit({aborted, Reason})</name>
+ <name since="">dirty_delete(Tab, Key) -> ok | exit({aborted, Reason})</name>
<fsummary>Dirty delete of a record.</fsummary>
<desc>
<p>Dirty equivalent of the function <c>mnesia:delete/3</c>.</p>
</desc>
</func>
<func>
- <name>dirty_delete_object(Record)</name>
+ <name since="">dirty_delete_object(Record)</name>
<fsummary>Dirty delete of a record.</fsummary>
<desc>
<marker id="dirty_delete_object_1"></marker>
@@ -944,14 +944,14 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_delete_object(Tab, Record)</name>
+ <name since="">dirty_delete_object(Tab, Record)</name>
<fsummary>Dirty delete of a record.</fsummary>
<desc>
<p>Dirty equivalent of the function <c>mnesia:delete_object/3</c>.</p>
</desc>
</func>
<func>
- <name>dirty_first(Tab) -> Key | exit({aborted, Reason})</name>
+ <name since="">dirty_first(Tab) -> Key | exit({aborted, Reason})</name>
<fsummary>Returns the key for the first record in a table.</fsummary>
<desc>
<marker id="dirty_first"></marker>
@@ -967,7 +967,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_index_match_object(Pattern, Pos)</name>
+ <name since="">dirty_index_match_object(Pattern, Pos)</name>
<fsummary>Dirty pattern match using index.</fsummary>
<desc>
<marker id="dirty_index_match_object_2"></marker>
@@ -977,7 +977,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_index_match_object(Tab, Pattern, Pos)</name>
+ <name since="">dirty_index_match_object(Tab, Pattern, Pos)</name>
<fsummary>Dirty pattern match using index.</fsummary>
<desc>
<p>Dirty equivalent of the function
@@ -985,7 +985,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_index_read(Tab, SecondaryKey, Pos)</name>
+ <name since="">dirty_index_read(Tab, SecondaryKey, Pos)</name>
<fsummary>Dirty read using index.</fsummary>
<desc>
<marker id="dirty_index_read"></marker>
@@ -994,7 +994,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_last(Tab) -> Key | exit({aborted, Reason})</name>
+ <name since="">dirty_last(Tab) -> Key | exit({aborted, Reason})</name>
<fsummary>Returns the key for the last record in a table.</fsummary>
<desc>
<marker id="dirty_last"></marker>
@@ -1006,7 +1006,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_match_object(Pattern) -> RecordList | exit({aborted, Reason})</name>
+ <name since="">dirty_match_object(Pattern) -> RecordList | exit({aborted, Reason})</name>
<fsummary>Dirty pattern match pattern.</fsummary>
<desc>
<marker id="dirty_match_object_1"></marker>
@@ -1015,7 +1015,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_match_object(Tab, Pattern) -> RecordList | exit({aborted, Reason})</name>
+ <name since="">dirty_match_object(Tab, Pattern) -> RecordList | exit({aborted, Reason})</name>
<fsummary>Dirty pattern match pattern.</fsummary>
<desc>
<p>Dirty equivalent of the function
@@ -1023,7 +1023,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_next(Tab, Key) -> Key | exit({aborted, Reason})</name>
+ <name since="">dirty_next(Tab, Key) -> Key | exit({aborted, Reason})</name>
<fsummary>Return the next key in a table.</fsummary>
<desc>
<marker id="dirty_next"></marker>
@@ -1038,7 +1038,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_prev(Tab, Key) -> Key | exit({aborted, Reason})</name>
+ <name since="">dirty_prev(Tab, Key) -> Key | exit({aborted, Reason})</name>
<fsummary>Returns the previous key in a table.</fsummary>
<desc>
<marker id="dirty_prev"></marker>
@@ -1050,7 +1050,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_read({Tab, Key}) -> ValueList | exit({aborted, Reason}</name>
+ <name since="">dirty_read({Tab, Key}) -> ValueList | exit({aborted, Reason}</name>
<fsummary>Dirty read of records.</fsummary>
<desc>
<marker id="dirty_read"></marker>
@@ -1058,14 +1058,14 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_read(Tab, Key) -> ValueList | exit({aborted, Reason}</name>
+ <name since="">dirty_read(Tab, Key) -> ValueList | exit({aborted, Reason}</name>
<fsummary>Dirty read of records.</fsummary>
<desc>
<p>Dirty equivalent of the function <c>mnesia:read/3</c>.</p>
</desc>
</func>
<func>
- <name>dirty_select(Tab, MatchSpec) -> ValueList | exit({aborted, Reason}</name>
+ <name since="">dirty_select(Tab, MatchSpec) -> ValueList | exit({aborted, Reason}</name>
<fsummary>Dirty matches the objects in <c>Tab</c> against <c>MatchSpec</c>.</fsummary>
<desc>
<marker id="dirty_select"></marker>
@@ -1073,7 +1073,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_slot(Tab, Slot) -> RecordList | exit({aborted, Reason})</name>
+ <name since="">dirty_slot(Tab, Slot) -> RecordList | exit({aborted, Reason})</name>
<fsummary>Returns the list of records that are associated with <c>Slot</c> in a table.</fsummary>
<desc>
<marker id="dirty_slot"></marker>
@@ -1089,7 +1089,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_update_counter({Tab, Key}, Incr) -> NewVal | exit({aborted, Reason})</name>
+ <name since="">dirty_update_counter({Tab, Key}, Incr) -> NewVal | exit({aborted, Reason})</name>
<fsummary>Dirty update of a counter record.</fsummary>
<desc>
<marker id="dirty_update_counter"></marker>
@@ -1097,7 +1097,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_update_counter(Tab, Key, Incr) -> NewVal | exit({aborted, Reason})</name>
+ <name since="">dirty_update_counter(Tab, Key, Incr) -> NewVal | exit({aborted, Reason})</name>
<fsummary>Dirty update of a counter record.</fsummary>
<desc>
<p>Mnesia has no special counter records. However,
@@ -1126,7 +1126,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_write(Record) -> ok | exit({aborted, Reason})</name>
+ <name since="">dirty_write(Record) -> ok | exit({aborted, Reason})</name>
<fsummary>Dirty write of a record.</fsummary>
<desc>
<marker id="dirty_write_1"></marker>
@@ -1135,14 +1135,14 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_write(Tab, Record) -> ok | exit({aborted, Reason})</name>
+ <name since="">dirty_write(Tab, Record) -> ok | exit({aborted, Reason})</name>
<fsummary>Dirty write of a record.</fsummary>
<desc>
<p>Dirty equivalent of the function <c>mnesia:write/3</c>.</p>
</desc>
</func>
<func>
- <name>dump_log() -> dumped</name>
+ <name since="">dump_log() -> dumped</name>
<fsummary>Performs a user-initiated dump of the local log file.</fsummary>
<desc>
<marker id="dump_log"></marker>
@@ -1156,7 +1156,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dump_tables(TabList) -> {atomic, ok} | {aborted, Reason}</name>
+ <name since="">dump_tables(TabList) -> {atomic, ok} | {aborted, Reason}</name>
<fsummary>Dumps all RAM tables to disc.</fsummary>
<desc>
<marker id="dump_tables"></marker>
@@ -1168,7 +1168,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dump_to_textfile(Filename)</name>
+ <name since="">dump_to_textfile(Filename)</name>
<fsummary>Dumps local tables into a text file.</fsummary>
<desc>
<marker id="dump_to_textfile"></marker>
@@ -1181,7 +1181,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>error_description(Error) -> String</name>
+ <name since="">error_description(Error) -> String</name>
<fsummary>Returns a string describing a particular Mnesia error.</fsummary>
<desc>
<marker id="error_description"></marker>
@@ -1259,7 +1259,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>ets(Fun, [, Args]) -> ResultOfFun | exit(Reason)</name>
+ <name since="">ets(Fun, [, Args]) -> ResultOfFun | exit(Reason)</name>
<fsummary>Calls the <c>Fun</c> in a raw context that is not protected by a transaction.</fsummary>
<desc>
<marker id="ets"></marker>
@@ -1278,7 +1278,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>first(Tab) -> Key | transaction abort</name>
+ <name since="">first(Tab) -> Key | transaction abort</name>
<fsummary>Returns the key for the first record in a table.</fsummary>
<desc>
<marker id="first"></marker>
@@ -1293,7 +1293,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>foldl(Function, Acc, Table) -> NewAcc | transaction abort</name>
+ <name since="">foldl(Function, Acc, Table) -> NewAcc | transaction abort</name>
<fsummary>Calls <c>Function</c> for each record in <c>Table</c>.</fsummary>
<desc>
<marker id="foldl"></marker>
@@ -1306,7 +1306,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>foldr(Function, Acc, Table) -> NewAcc | transaction abort</name>
+ <name since="">foldr(Function, Acc, Table) -> NewAcc | transaction abort</name>
<fsummary>Calls <c>Function</c> for each record in <c>Table</c>.</fsummary>
<desc>
<marker id="foldr"></marker>
@@ -1317,7 +1317,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>force_load_table(Tab) -> yes | ErrorDescription</name>
+ <name since="">force_load_table(Tab) -> yes | ErrorDescription</name>
<fsummary>Forces a table to be loaded into the system.</fsummary>
<desc>
<marker id="force_load_table"></marker>
@@ -1335,7 +1335,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>index_match_object(Pattern, Pos) -> transaction abort | ObjList</name>
+ <name since="">index_match_object(Pattern, Pos) -> transaction abort | ObjList</name>
<fsummary>Matches records and uses index information.</fsummary>
<desc>
<marker id="index_match_object_2"></marker>
@@ -1345,7 +1345,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>index_match_object(Tab, Pattern, Pos, LockKind) -> transaction abort | ObjList</name>
+ <name since="">index_match_object(Tab, Pattern, Pos, LockKind) -> transaction abort | ObjList</name>
<fsummary>Matches records and uses index information.</fsummary>
<desc>
<marker id="index_match_object_4"></marker>
@@ -1377,7 +1377,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>index_read(Tab, SecondaryKey, Pos) -> transaction abort | RecordList</name>
+ <name since="">index_read(Tab, SecondaryKey, Pos) -> transaction abort | RecordList</name>
<fsummary>Reads records through index table.</fsummary>
<desc>
<marker id="index_read"></marker>
@@ -1397,7 +1397,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>info() -> ok</name>
+ <name since="">info() -> ok</name>
<fsummary>Prints system information on the terminal.</fsummary>
<desc>
<marker id="info"></marker>
@@ -1408,7 +1408,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>install_fallback(Opaque) -> ok | {error,Reason}</name>
+ <name since="">install_fallback(Opaque) -> ok | {error,Reason}</name>
<fsummary>Installs a backup as fallback.</fsummary>
<desc>
<marker id="install_fallback_1"></marker>
@@ -1417,7 +1417,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>install_fallback(Opaque), BackupMod) -> ok | {error,Reason}</name>
+ <name since="">install_fallback(Opaque), BackupMod) -> ok | {error,Reason}</name>
<fsummary>Installs a backup as fallback.</fsummary>
<desc>
<p>Calls <c>mnesia:install_fallback(Opaque, Args)</c>, where
@@ -1425,7 +1425,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>install_fallback(Opaque, Args) -> ok | {error,Reason}</name>
+ <name since="">install_fallback(Opaque, Args) -> ok | {error,Reason}</name>
<fsummary>Installs a backup as fallback.</fsummary>
<desc>
<p>Installs a backup as fallback. The fallback is used to
@@ -1483,7 +1483,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>is_transaction() -> boolean</name>
+ <name since="">is_transaction() -> boolean</name>
<fsummary>Checks if code is running in a transaction.</fsummary>
<desc>
<marker id="is_transaction"></marker>
@@ -1492,7 +1492,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>last(Tab) -> Key | transaction abort</name>
+ <name since="">last(Tab) -> Key | transaction abort</name>
<fsummary>Returns the key for the last record in a table.</fsummary>
<desc>
<p>Works exactly like
@@ -1503,7 +1503,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>load_textfile(Filename)</name>
+ <name since="">load_textfile(Filename)</name>
<fsummary>Loads tables from a text file.</fsummary>
<desc>
<marker id="load_textfile"></marker>
@@ -1516,7 +1516,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>lock(LockItem, LockKind) -> Nodes | ok | transaction abort</name>
+ <name since="">lock(LockItem, LockKind) -> Nodes | ok | transaction abort</name>
<fsummary>Explicit grab lock.</fsummary>
<desc>
<marker id="lock"></marker>
@@ -1605,7 +1605,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>match_object(Pattern) -> transaction abort | RecList</name>
+ <name since="">match_object(Pattern) -> transaction abort | RecList</name>
<fsummary>Matches <c>Pattern</c> for records.</fsummary>
<desc>
<marker id="match_object_1"></marker>
@@ -1614,7 +1614,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>match_object(Tab, Pattern, LockKind) -> transaction abort | RecList</name>
+ <name since="">match_object(Tab, Pattern, LockKind) -> transaction abort | RecList</name>
<fsummary>Matches <c>Pattern</c> for records.</fsummary>
<desc>
<marker id="match_object_3"></marker>
@@ -1639,7 +1639,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>move_table_copy(Tab, From, To) -> {aborted, Reason} | {atomic, ok}</name>
+ <name since="">move_table_copy(Tab, From, To) -> {aborted, Reason} | {atomic, ok}</name>
<fsummary>Moves the copy of table <c>Tab</c> from node <c>From</c> to node <c>To</c>.</fsummary>
<desc>
<marker id="move_table_copy"></marker>
@@ -1653,7 +1653,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>next(Tab, Key) -> Key | transaction abort</name>
+ <name since="">next(Tab, Key) -> Key | transaction abort</name>
<fsummary>Returns the next key in a table.</fsummary>
<desc>
<marker id="next"></marker>
@@ -1665,7 +1665,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>prev(Tab, Key) -> Key | transaction abort</name>
+ <name since="">prev(Tab, Key) -> Key | transaction abort</name>
<fsummary>Returns the previous key in a table.</fsummary>
<desc>
<p>Works exactly like
@@ -1676,7 +1676,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>read({Tab, Key}) -> transaction abort | RecordList</name>
+ <name since="">read({Tab, Key}) -> transaction abort | RecordList</name>
<fsummary>Reads records(s) with a given key.</fsummary>
<desc>
<marker id="read_2"></marker>
@@ -1684,14 +1684,14 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>read(Tab, Key) -> transaction abort | RecordList</name>
+ <name since="">read(Tab, Key) -> transaction abort | RecordList</name>
<fsummary>Reads records(s) with a given key.</fsummary>
<desc>
<p>Calls function <c>mnesia:read(Tab, Key, read)</c>.</p>
</desc>
</func>
<func>
- <name>read(Tab, Key, LockKind) -> transaction abort | RecordList</name>
+ <name since="">read(Tab, Key, LockKind) -> transaction abort | RecordList</name>
<fsummary>Reads records(s) with a given key.</fsummary>
<desc>
<marker id="read_3"></marker>
@@ -1716,7 +1716,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>read_lock_table(Tab) -> ok | transaction abort</name>
+ <name since="">read_lock_table(Tab) -> ok | transaction abort</name>
<fsummary>Sets a read lock on an entire table.</fsummary>
<desc>
<marker id="read_lock_table"></marker>
@@ -1725,7 +1725,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>report_event(Event) -> ok</name>
+ <name since="">report_event(Event) -> ok</name>
<fsummary>Reports a user event to the Mnesia event handler.</fsummary>
<desc>
<marker id="report_event"></marker>
@@ -1743,7 +1743,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>restore(Opaque, Args) -> {atomic, RestoredTabs} |{aborted, Reason}</name>
+ <name since="">restore(Opaque, Args) -> {atomic, RestoredTabs} |{aborted, Reason}</name>
<fsummary>Online restore of backup.</fsummary>
<desc>
<marker id="restore"></marker>
@@ -1803,7 +1803,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>s_delete({Tab, Key}) -> ok | transaction abort</name>
+ <name since="">s_delete({Tab, Key}) -> ok | transaction abort</name>
<fsummary>Sets sticky lock and delete records.</fsummary>
<desc>
<marker id="s_delete"></marker>
@@ -1812,7 +1812,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>s_delete_object(Record) -> ok | transaction abort</name>
+ <name since="">s_delete_object(Record) -> ok | transaction abort</name>
<fsummary>Sets sticky lock and delete record.</fsummary>
<desc>
<marker id="s_delete_object"></marker>
@@ -1822,7 +1822,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>s_write(Record) -> ok | transaction abort</name>
+ <name since="">s_write(Record) -> ok | transaction abort</name>
<fsummary>Writes <c>Record</c> and sets sticky lock.</fsummary>
<desc>
<marker id="s_write"></marker>
@@ -1832,21 +1832,21 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>schema() -> ok</name>
+ <name since="">schema() -> ok</name>
<fsummary>Prints information about all table definitions on the terminal.</fsummary>
<desc>
<p>Prints information about all table definitions on the terminal.</p>
</desc>
</func>
<func>
- <name>schema(Tab) -> ok</name>
+ <name since="">schema(Tab) -> ok</name>
<fsummary>Prints information about one table definition on the terminal.</fsummary>
<desc>
<p>Prints information about one table definition on the terminal.</p>
</desc>
</func>
<func>
- <name>select(Tab, MatchSpec [, Lock]) -> transaction abort | [Object]</name>
+ <name since="">select(Tab, MatchSpec [, Lock]) -> transaction abort | [Object]</name>
<fsummary>Matches the objects in <c>Tab</c> against <c>MatchSpec</c>.</fsummary>
<desc>
<marker id="select_2_3"></marker>
@@ -1884,7 +1884,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name>select(Tab, MatchSpec, NObjects, Lock) -> transaction abort | {[Object],Cont} | '$end_of_table'</name>
+ <name since="">select(Tab, MatchSpec, NObjects, Lock) -> transaction abort | {[Object],Cont} | '$end_of_table'</name>
<fsummary>Matches the objects in <c>Tab</c> against <c>MatchSpec</c>.</fsummary>
<desc>
<marker id="select_4"></marker>
@@ -1907,7 +1907,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name>select(Cont) -> transaction abort | {[Object],Cont} | '$end_of_table'</name>
+ <name since="">select(Cont) -> transaction abort | {[Object],Cont} | '$end_of_table'</name>
<fsummary>Continues selecting objects.</fsummary>
<desc>
<p>Selects more objects with the match specification initiated
@@ -1919,7 +1919,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name>set_debug_level(Level) -> OldLevel</name>
+ <name since="">set_debug_level(Level) -> OldLevel</name>
<fsummary>Changes the internal debug level of Mnesia.</fsummary>
<desc>
<marker id="set_debug_level"></marker>
@@ -1930,7 +1930,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name>set_master_nodes(MasterNodes) -> ok | {error, Reason}</name>
+ <name since="">set_master_nodes(MasterNodes) -> ok | {error, Reason}</name>
<fsummary>Sets the master nodes for all tables.</fsummary>
<desc>
<marker id="set_master_nodes_1"></marker>
@@ -1943,7 +1943,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name>set_master_nodes(Tab, MasterNodes) -> ok | {error, Reason}</name>
+ <name since="">set_master_nodes(Tab, MasterNodes) -> ok | {error, Reason}</name>
<fsummary>Sets the master nodes for a table.</fsummary>
<desc>
<marker id="set_master_nodes_2"></marker>
@@ -1968,14 +1968,14 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name>snmp_close_table(Tab) -> {aborted, R} | {atomic, ok}</name>
+ <name since="">snmp_close_table(Tab) -> {aborted, R} | {atomic, ok}</name>
<fsummary>Removes the possibility for SNMP to manipulate the table.</fsummary>
<desc>
<p>Removes the possibility for SNMP to manipulate the table.</p>
</desc>
</func>
<func>
- <name>snmp_get_mnesia_key(Tab, RowIndex) -> {ok, Key} | undefined</name>
+ <name since="">snmp_get_mnesia_key(Tab, RowIndex) -> {ok, Key} | undefined</name>
<fsummary>Gets the corresponding Mnesia key from an SNMP index.</fsummary>
<type>
<v>Tab ::= atom()</v>
@@ -1990,7 +1990,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name>snmp_get_next_index(Tab, RowIndex) -> {ok, NextIndex} | endOfTable</name>
+ <name since="">snmp_get_next_index(Tab, RowIndex) -> {ok, NextIndex} | endOfTable</name>
<fsummary>Gets the index of the next lexicographical row.</fsummary>
<type>
<v>Tab ::= atom()</v>
@@ -2006,7 +2006,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name>snmp_get_row(Tab, RowIndex) -> {ok, Row} | undefined</name>
+ <name since="">snmp_get_row(Tab, RowIndex) -> {ok, Row} | undefined</name>
<fsummary>Retrieves a row indexed by an SNMP index.</fsummary>
<type>
<v>Tab ::= atom()</v>
@@ -2019,7 +2019,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name>snmp_open_table(Tab, SnmpStruct) -> {aborted, R} | {atomic, ok}</name>
+ <name since="">snmp_open_table(Tab, SnmpStruct) -> {aborted, R} | {atomic, ok}</name>
<fsummary>Organizes a Mnesia table as an SNMP table.</fsummary>
<type>
<v>Tab ::= atom()</v>
@@ -2073,10 +2073,17 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name>start() -> ok | {error, Reason}</name>
+ <name since="">start() -> ok | {error, Reason}</name>
<fsummary>Starts a local Mnesia system.</fsummary>
<desc>
<marker id="start"></marker>
+ <p>Mnesia startup is asynchronous. The function call
+ <c>mnesia:start()</c> returns the atom <c>ok</c> and then
+ starts to initialize the different tables. Depending on the
+ size of the database, this can take some time, and the
+ application programmer must wait for the tables that the
+ application needs before they can be used. This is achieved
+ by using the function <c>mnesia:wait_for_tables/2</c>.</p>
<p>The startup procedure for a set of Mnesia nodes is a
fairly complicated operation. A Mnesia system consists
of a set of nodes, with Mnesia started locally on all
@@ -2108,7 +2115,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name>stop() -> stopped</name>
+ <name since="">stop() -> stopped</name>
<fsummary>Stops Mnesia locally.</fsummary>
<desc>
<marker id="stop"></marker>
@@ -2117,7 +2124,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name>subscribe(EventCategory) -> {ok, Node} | {error, Reason}</name>
+ <name since="">subscribe(EventCategory) -> {ok, Node} | {error, Reason}</name>
<fsummary>Subscribes to events of type <c>EventCategory</c>.</fsummary>
<desc>
<marker id="subscribe"></marker>
@@ -2127,7 +2134,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name>sync_dirty(Fun, [, Args]) -> ResultOfFun | exit(Reason)</name>
+ <name since="">sync_dirty(Fun, [, Args]) -> ResultOfFun | exit(Reason)</name>
<fsummary>Calls the <c>Fun</c> in a context that is not protected by a transaction.</fsummary>
<desc>
<marker id="sync_dirty"></marker>
@@ -2143,7 +2150,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name>sync_log() -> ok | {error, Reason}</name>
+ <name since="OTP 17.0">sync_log() -> ok | {error, Reason}</name>
<fsummary>Performs a file sync of the local log file.</fsummary>
<desc>
<p>Ensures that the local transaction log file is synced to disk.
@@ -2153,7 +2160,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name>sync_transaction(Fun, [[, Args], Retries]) -> {aborted, Reason} | {atomic, ResultOfFun}</name>
+ <name since="">sync_transaction(Fun, [[, Args], Retries]) -> {aborted, Reason} | {atomic, ResultOfFun}</name>
<fsummary>Synchronously executes a transaction.</fsummary>
<desc>
<marker id="sync_transaction"></marker>
@@ -2166,7 +2173,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name>system_info(InfoKey) -> Info | exit({aborted, Reason})</name>
+ <name since="">system_info(InfoKey) -> Info | exit({aborted, Reason})</name>
<fsummary>Returns information about the Mnesia system.</fsummary>
<desc>
<marker id="system_info"></marker>
@@ -2353,7 +2360,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name>table(Tab [,[Option]]) -> QueryHandle</name>
+ <name since="">table(Tab [,[Option]]) -> QueryHandle</name>
<fsummary>Return a QLC query handle.</fsummary>
<desc>
<marker id="table"></marker>
@@ -2408,7 +2415,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name>table_info(Tab, InfoKey) -> Info | exit({aborted, Reason})</name>
+ <name since="">table_info(Tab, InfoKey) -> Info | exit({aborted, Reason})</name>
<fsummary>Returns local information about table.</fsummary>
<desc>
<marker id="table_info"></marker>
@@ -2560,7 +2567,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name>transaction(Fun [[, Args], Retries]) -> {aborted, Reason} | {atomic, ResultOfFun}</name>
+ <name since="">transaction(Fun [[, Args], Retries]) -> {aborted, Reason} | {atomic, ResultOfFun}</name>
<fsummary>Executes a transaction.</fsummary>
<desc>
<marker id="transaction"></marker>
@@ -2628,7 +2635,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name>transform_table(Tab, Fun, NewAttributeList, NewRecordName) -> {aborted, R} | {atomic, ok}</name>
+ <name since="">transform_table(Tab, Fun, NewAttributeList, NewRecordName) -> {aborted, R} | {atomic, ok}</name>
<fsummary>Changes format on all records in table <c>Tab</c>.</fsummary>
<desc>
<marker id="transform_table_4"></marker>
@@ -2649,7 +2656,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name>transform_table(Tab, Fun, NewAttributeList) -> {aborted, R} | {atomic, ok}</name>
+ <name since="">transform_table(Tab, Fun, NewAttributeList) -> {aborted, R} | {atomic, ok}</name>
<fsummary>Changes format on all records in table <c>Tab</c>.</fsummary>
<desc>
<p>Calls <c>mnesia:transform_table(Tab, Fun,
@@ -2658,7 +2665,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name>traverse_backup(Source, [SourceMod,] Target, [TargetMod,] Fun, Acc) -> {ok, LastAcc} | {error, Reason}</name>
+ <name since="">traverse_backup(Source, [SourceMod,] Target, [TargetMod,] Fun, Acc) -> {ok, LastAcc} | {error, Reason}</name>
<fsummary>Traversal of a backup.</fsummary>
<desc>
<marker id="traverse_backup"></marker>
@@ -2689,7 +2696,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name>uninstall_fallback() -> ok | {error,Reason}</name>
+ <name since="">uninstall_fallback() -> ok | {error,Reason}</name>
<fsummary>Uninstalls a fallback.</fsummary>
<desc>
<marker id="uninstall_fallback_0"></marker>
@@ -2698,7 +2705,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name>uninstall_fallback(Args) -> ok | {error,Reason}</name>
+ <name since="">uninstall_fallback(Args) -> ok | {error,Reason}</name>
<fsummary>Uninstalls a fallback.</fsummary>
<desc>
<p>Deinstalls a fallback before it
@@ -2725,7 +2732,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name>unsubscribe(EventCategory) -> {ok, Node} | {error, Reason}</name>
+ <name since="">unsubscribe(EventCategory) -> {ok, Node} | {error, Reason}</name>
<fsummary>Subscribes to events of type <c>EventCategory</c>.</fsummary>
<desc>
<marker id="unsubscribe"></marker>
@@ -2735,7 +2742,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name>wait_for_tables(TabList, Timeout) -> ok | {timeout, BadTabList} | {error, Reason}</name>
+ <name since="">wait_for_tables(TabList, Timeout) -> ok | {timeout, BadTabList} | {error, Reason}</name>
<fsummary>Waits for tables to be accessible.</fsummary>
<desc>
<marker id="wait_for_tables"></marker>
@@ -2746,7 +2753,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name>wread({Tab, Key}) -> transaction abort | RecordList</name>
+ <name since="">wread({Tab, Key}) -> transaction abort | RecordList</name>
<fsummary>Reads records with given key.</fsummary>
<desc>
<marker id="wread"></marker>
@@ -2754,7 +2761,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name>write(Record) -> transaction abort | ok</name>
+ <name since="">write(Record) -> transaction abort | ok</name>
<fsummary>Writes a record into the database.</fsummary>
<desc>
<marker id="write_1"></marker>
@@ -2763,7 +2770,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name>write(Tab, Record, LockKind) -> transaction abort | ok</name>
+ <name since="">write(Tab, Record, LockKind) -> transaction abort | ok</name>
<fsummary>Writes a record into the database.</fsummary>
<desc>
<marker id="write_3"></marker>
@@ -2779,7 +2786,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name>write_lock_table(Tab) -> ok | transaction abort</name>
+ <name since="">write_lock_table(Tab) -> ok | transaction abort</name>
<fsummary>Sets write lock on an entire table.</fsummary>
<desc>
<marker id="write_lock_table"></marker>
diff --git a/lib/mnesia/doc/src/mnesia_frag_hash.xml b/lib/mnesia/doc/src/mnesia_frag_hash.xml
index c233acdb05..ccaba412d0 100644
--- a/lib/mnesia/doc/src/mnesia_frag_hash.xml
+++ b/lib/mnesia/doc/src/mnesia_frag_hash.xml
@@ -34,7 +34,7 @@
<rev>A</rev>
<file>mnesia_frag_hash.sgml</file>
</header>
- <module>mnesia_frag_hash</module>
+ <module since="">mnesia_frag_hash</module>
<modulesummary>Defines mnesia_frag_hash callback behavior</modulesummary>
<description>
<p>This module defines a callback behavior for user-defined hash
@@ -50,7 +50,7 @@
<funcs>
<func>
- <name>init_state(Tab, State) -> NewState | abort(Reason)</name>
+ <name since="">init_state(Tab, State) -> NewState | abort(Reason)</name>
<fsummary>Initiates the hash state for a new table.</fsummary>
<type>
<v>Tab = atom()</v>
@@ -72,7 +72,7 @@
</desc>
</func>
<func>
- <name>add_frag(State) -> {NewState, IterFrags, AdditionalLockFrags} | abort(Reason)</name>
+ <name since="">add_frag(State) -> {NewState, IterFrags, AdditionalLockFrags} | abort(Reason)</name>
<fsummary>Starts when a new fragment is added to a fragmented table.</fsummary>
<type>
<v>State = term()</v>
@@ -100,7 +100,7 @@
</desc>
</func>
<func>
- <name>del_frag(State) -> {NewState, IterFrags, AdditionalLockFrags} | abort(Reason)</name>
+ <name since="">del_frag(State) -> {NewState, IterFrags, AdditionalLockFrags} | abort(Reason)</name>
<fsummary>Starts when a fragment is deleted from a fragmented table.</fsummary>
<type>
<v>State = term()</v>
@@ -127,7 +127,7 @@
</desc>
</func>
<func>
- <name>key_to_frag_number(State, Key) -> FragNum | abort(Reason)</name>
+ <name since="">key_to_frag_number(State, Key) -> FragNum | abort(Reason)</name>
<fsummary>Resolves the key of a record into a fragment number.</fsummary>
<type>
<v>FragNum = integer()</v>
@@ -140,7 +140,7 @@
</desc>
</func>
<func>
- <name>match_spec_to_frag_numbers(State, MatchSpec) -> FragNums | abort(Reason)</name>
+ <name since="">match_spec_to_frag_numbers(State, MatchSpec) -> FragNums | abort(Reason)</name>
<fsummary>Resolves a <c>MatchSpec</c> into a list of fragment numbers.</fsummary>
<type>
<v>MatcSpec = ets_select_match_spec()</v>
diff --git a/lib/mnesia/doc/src/mnesia_registry.xml b/lib/mnesia/doc/src/mnesia_registry.xml
index a76f716981..18ddc4ab9e 100644
--- a/lib/mnesia/doc/src/mnesia_registry.xml
+++ b/lib/mnesia/doc/src/mnesia_registry.xml
@@ -34,7 +34,7 @@
<rev>A</rev>
<file>mnesia_registry.sgml</file>
</header>
- <module>mnesia_registry</module>
+ <module since="">mnesia_registry</module>
<modulesummary>Dump support for registries in erl_interface.</modulesummary>
<description>
<p>This module is usually part of the <c>erl_interface</c>
@@ -57,7 +57,7 @@
<funcs>
<func>
- <name>create_table(Tab) -> ok | exit(Reason)</name>
+ <name since="">create_table(Tab) -> ok | exit(Reason)</name>
<fsummary>Creates a registry table in Mnesia.</fsummary>
<desc>
<p>A wrapper function for <c>mnesia:create_table/2</c>,
@@ -73,7 +73,7 @@
</desc>
</func>
<func>
- <name>create_table(Tab, TabDef) -> ok | exit(Reason)</name>
+ <name since="">create_table(Tab, TabDef) -> ok | exit(Reason)</name>
<fsummary>Creates a customized registry table in Mnesia.</fsummary>
<desc>
<p>A wrapper function for <c>mnesia:create_table/2</c>,
diff --git a/lib/mnesia/doc/src/notes.xml b/lib/mnesia/doc/src/notes.xml
index 29c35d221c..8fc3610bb6 100644
--- a/lib/mnesia/doc/src/notes.xml
+++ b/lib/mnesia/doc/src/notes.xml
@@ -737,7 +737,7 @@
<p>
Returns the same value for
mnesia_loader:disc_load_table/2 as
- mnesia_loader:net_load_table/4 if a table copy can not be
+ mnesia_loader:net_load_table/4 if a table copy cannot be
found. (Thanks to Uwe Dauernheim)</p>
<p>
Own Id: OTP-10015</p>
diff --git a/lib/mnesia/examples/bench/README b/lib/mnesia/examples/bench/README
index 5d31b5ba25..b8209b19b8 100644
--- a/lib/mnesia/examples/bench/README
+++ b/lib/mnesia/examples/bench/README
@@ -46,7 +46,7 @@ you need to:
- put the $ERL_TOP/bin directory in your path on all nodes
- bind IP adresses to hostnames (e.g via DNS or /etc/hosts)
- - enable usage of rsh so it does not prompt for password
+ - enable usage of ssh so it does not prompt for password
If you cannot achieve this, it is possible to run the benchmark
anyway, but it requires more manual work to be done for each
@@ -141,7 +141,7 @@ statistics_detail
following atoms: normal, debug and debug2. debug enables a
finer grain of statistics to be reported, but since it requires
more counters, to be updated by the generator processes it may
- cause slightly worse benchmark performace figures than the brief
+ cause slightly worse benchmark performance figures than the brief
default case, that is normal. debug2 prints out the debug info
and formats it according to LMC's benchmark program.
@@ -160,7 +160,7 @@ n_fragments
Defines how many fragments each table should be divided in.
Default is 100. The fragments are evenly distributed over
- all table nodes. The group table not devided in fragments.
+ all table nodes. The group table not divided in fragments.
n_replicas
diff --git a/lib/mnesia/src/mnesia.erl b/lib/mnesia/src/mnesia.erl
index 223dba3f90..77afb8250c 100644
--- a/lib/mnesia/src/mnesia.erl
+++ b/lib/mnesia/src/mnesia.erl
@@ -838,18 +838,20 @@ read(Tid, Ts, Tab, Key, LockKind)
tid ->
Store = Ts#tidstore.store,
Oid = {Tab, Key},
- Objs =
- case LockKind of
- read ->
- mnesia_locker:rlock(Tid, Store, Oid);
- write ->
- mnesia_locker:rwlock(Tid, Store, Oid);
- sticky_write ->
- mnesia_locker:sticky_rwlock(Tid, Store, Oid);
- _ ->
- abort({bad_type, Tab, LockKind})
- end,
- add_written(?ets_lookup(Store, Oid), Tab, Objs);
+ ObjsFun =
+ fun() ->
+ case LockKind of
+ read ->
+ mnesia_locker:rlock(Tid, Store, Oid);
+ write ->
+ mnesia_locker:rwlock(Tid, Store, Oid);
+ sticky_write ->
+ mnesia_locker:sticky_rwlock(Tid, Store, Oid);
+ _ ->
+ abort({bad_type, Tab, LockKind})
+ end
+ end,
+ add_written(?ets_lookup(Store, Oid), Tab, ObjsFun, LockKind);
_Protocol ->
dirty_read(Tab, Key)
end;
@@ -1202,14 +1204,20 @@ add_previous(_Tid, Ts, _Type, Tab) ->
%% This routine fixes up the return value from read/1 so that
%% it is correct with respect to what this particular transaction
%% has already written, deleted .... etc
+%% The actual read from the table is not done if not needed due to local
+%% transaction context, and if so, no extra read lock is needed either.
-add_written([], _Tab, Objs) ->
- Objs; % standard normal fast case
-add_written(Written, Tab, Objs) ->
+add_written([], _Tab, ObjsFun, _LockKind) ->
+ ObjsFun(); % standard normal fast case
+add_written(Written, Tab, ObjsFun, LockKind) ->
case val({Tab, setorbag}) of
bag ->
- add_written_to_bag(Written, Objs, []);
+ add_written_to_bag(Written, ObjsFun(), []);
+ _ when LockKind == read;
+ LockKind == write ->
+ add_written_to_set(Written);
_ ->
+ _ = ObjsFun(), % Fall back to request new lock and read from source
add_written_to_set(Written)
end.
diff --git a/lib/mnesia/src/mnesia_dumper.erl b/lib/mnesia/src/mnesia_dumper.erl
index a2880d6cf4..8ab11be2d3 100644
--- a/lib/mnesia/src/mnesia_dumper.erl
+++ b/lib/mnesia/src/mnesia_dumper.erl
@@ -67,10 +67,10 @@ get_log_writes() ->
incr_log_writes() ->
Left = mnesia_lib:incr_counter(trans_log_writes_left, -1),
if
- Left > 0 ->
- ignore;
+ Left =:= 0 ->
+ adjust_log_writes(true);
true ->
- adjust_log_writes(true)
+ ignore
end.
adjust_log_writes(DoCast) ->
@@ -272,17 +272,12 @@ do_insert_rec(Tid, Rec, InPlace, InitBy, LogV) ->
end
end,
D = Rec#commit.disc_copies,
- ExtOps = commit_ext(Rec),
insert_ops(Tid, disc_copies, D, InPlace, InitBy, LogV),
- [insert_ops(Tid, Ext, Ops, InPlace, InitBy, LogV) ||
- {Ext, Ops} <- ExtOps,
- storage_semantics(Ext) == disc_copies],
+ insert_ext_ops(Tid, commit_ext(Rec), InPlace, InitBy),
case InitBy of
startup ->
DO = Rec#commit.disc_only_copies,
- insert_ops(Tid, disc_only_copies, DO, InPlace, InitBy, LogV),
- [insert_ops(Tid, Ext, Ops, InPlace, InitBy, LogV) ||
- {Ext, Ops} <- ExtOps, storage_semantics(Ext) == disc_only_copies];
+ insert_ops(Tid, disc_only_copies, DO, InPlace, InitBy, LogV);
_ ->
ignore
end.
@@ -290,11 +285,8 @@ do_insert_rec(Tid, Rec, InPlace, InitBy, LogV) ->
commit_ext(#commit{ext = []}) -> [];
commit_ext(#commit{ext = Ext}) ->
case lists:keyfind(ext_copies, 1, Ext) of
- {_, C} ->
- lists:foldl(fun({Ext0, Op}, D) ->
- orddict:append(Ext0, Op, D)
- end, orddict:new(), C);
- false -> []
+ {_, C} -> C;
+ false -> []
end.
update(_Tid, [], _DumperMode) ->
@@ -330,6 +322,21 @@ perform_update(Tid, SchemaOps, _DumperMode, _UseDir) ->
fatal("Schema update error ~tp ~tp", [{Reason,ST}, SchemaOps])
end.
+insert_ext_ops(Tid, ExtOps, InPlace, InitBy) ->
+ %% Note: ext ops cannot be part of pre-4.3 logs, so there's no need
+ %% to support the old operation order, as in `insert_ops'
+ lists:foreach(
+ fun ({Ext, Op}) ->
+ case storage_semantics(Ext) of
+ Semantics when Semantics == disc_copies;
+ Semantics == disc_only_copies, InitBy == startup ->
+ insert_op(Tid, Ext, Op, InPlace, InitBy);
+ _Other ->
+ ok
+ end
+ end,
+ ExtOps).
+
insert_ops(_Tid, _Storage, [], _InPlace, _InitBy, _) -> ok;
insert_ops(Tid, Storage, [Op], InPlace, InitBy, Ver) when Ver >= "4.3"->
insert_op(Tid, Storage, Op, InPlace, InitBy),
diff --git a/lib/mnesia/src/mnesia_lib.erl b/lib/mnesia/src/mnesia_lib.erl
index a884b8e086..6abc05fade 100644
--- a/lib/mnesia/src/mnesia_lib.erl
+++ b/lib/mnesia/src/mnesia_lib.erl
@@ -929,7 +929,7 @@ error_desc(no_transaction) -> "Operation not allowed outside transactions";
error_desc(combine_error) -> "Table options were ilegally combined";
error_desc(bad_index) -> "Index already exists or was out of bounds";
error_desc(already_exists) -> "Some schema option we try to set is already on";
-error_desc(index_exists)-> "Some ops can not be performed on tabs with index";
+error_desc(index_exists)-> "Some ops cannot be performed on tabs with index";
error_desc(no_exists)-> "Tried to perform op on non-existing (non alive) item";
error_desc(system_limit) -> "Some system_limit was exhausted";
error_desc(mnesia_down) -> "A transaction involving objects at some remote "
diff --git a/lib/mnesia/src/mnesia_tm.erl b/lib/mnesia/src/mnesia_tm.erl
index 4b3fffc735..cbf7db28f0 100644
--- a/lib/mnesia/src/mnesia_tm.erl
+++ b/lib/mnesia/src/mnesia_tm.erl
@@ -1661,7 +1661,7 @@ commit_participant(Coord, Tid, Bin, C0, DiscNs, _RamNs) ->
?eval_debug_fun({?MODULE, commit_participant, pre}, [{tid, Tid}]),
try mnesia_schema:prepare_commit(Tid, C0, {part, Coord}) of
{Modified, C = #commit{}, DumperMode} ->
- %% If we can not find any local unclear decision
+ %% If we cannot find any local unclear decision
%% we should presume abort at startup recovery
case lists:member(node(), DiscNs) of
false ->
diff --git a/lib/mnesia/test/README b/lib/mnesia/test/README
index e0ced7399d..30a0d2fd64 100644
--- a/lib/mnesia/test/README
+++ b/lib/mnesia/test/README
@@ -51,7 +51,7 @@ stated as test suite configuration parameters, but by default
the extra node names are generated. In this example the names
will be: a, a1 and a2. It is enough to start the first node
manually, the extra nodes will automatically be started if
-neccessary.
+necessary.
The attached UNIX shell script mt, does not work on all
platforms, but it may be used as a source for inspiration. It
@@ -63,7 +63,7 @@ test cases (i.e. test cases that encountered an error).
During development we want to be able to run the test cases
in the debugger. This demands a little bit of preparations:
- - Start the neccessary number of nodes (normally 3).
+ - Start the necessary number of nodes (normally 3).
This may either be done by running the mt script or
by starting the main node and then invoke mt:start_nodes()
to start the extra nodes with slave.
@@ -73,7 +73,7 @@ in the debugger. This demands a little bit of preparations:
- Load all files that needs to be interpreted. This is typically
all Mnesia files plus the test case. By invoking mnesia:ni()
- and mnesia:ni([TestModule]) the neccessary modules will be
+ and mnesia:ni([TestModule]) the necessary modules will be
loaded on all CONNECTED nodes.
The test case execution is supervised in order to ensure that no test
diff --git a/lib/observer/doc/src/crashdump.xml b/lib/observer/doc/src/crashdump.xml
index 48f944cbce..62c6ff1f25 100644
--- a/lib/observer/doc/src/crashdump.xml
+++ b/lib/observer/doc/src/crashdump.xml
@@ -34,7 +34,7 @@
<rev>PA1</rev>
<file>crashdump.xml</file>
</header>
- <module>crashdump_viewer</module>
+ <module since="">crashdump_viewer</module>
<modulesummary>A WxWidgets based tool for browsing Erlang
crashdumps.</modulesummary>
<description>
@@ -46,8 +46,8 @@
</description>
<funcs>
<func>
- <name>start() -> ok</name>
- <name>start(File) -> ok</name>
+ <name since="">start() -> ok</name>
+ <name since="OTP 17.0">start(File) -> ok</name>
<fsummary>Start the Crashdump Viewer.</fsummary>
<type>
<v>File = string()</v>
@@ -62,7 +62,7 @@
</desc>
</func>
<func>
- <name>stop() -> ok</name>
+ <name since="">stop() -> ok</name>
<fsummary>Terminate the Crashdump Viewer.</fsummary>
<desc>
<p>Terminates the Crashdump Viewer and closes
diff --git a/lib/observer/doc/src/etop.xml b/lib/observer/doc/src/etop.xml
index e7a83d0514..f0acc7b5d8 100644
--- a/lib/observer/doc/src/etop.xml
+++ b/lib/observer/doc/src/etop.xml
@@ -34,7 +34,7 @@
<rev></rev>
<file></file>
</header>
- <module>etop</module>
+ <module since="">etop</module>
<modulesummary>Erlang Top is a tool for presenting information about Erlang
processes similar to the information presented by "top" in UNIX.</modulesummary>
<description>
@@ -98,7 +98,7 @@
</description>
<funcs>
<func>
- <name>start() -> ok</name>
+ <name since="OTP R15B01">start() -> ok</name>
<fsummary>Start etop.</fsummary>
<desc>
<p>Starts <c>etop</c>.
@@ -106,7 +106,7 @@
</desc>
</func>
<func>
- <name>start(Options) -> ok</name>
+ <name since="OTP R15B01">start(Options) -> ok</name>
<fsummary>Start etop.</fsummary>
<type>
<v>Options = [Option]</v>
@@ -120,7 +120,7 @@
</desc>
</func>
<func>
- <name>help() -> ok</name>
+ <name since="OTP R15B01">help() -> ok</name>
<fsummary>Display the etop help.</fsummary>
<desc>
<p>Displays the help of <c>etop</c> and
@@ -128,7 +128,7 @@
</desc>
</func>
<func>
- <name>config(Key,Value) -> Result</name>
+ <name since="">config(Key,Value) -> Result</name>
<fsummary>Change the configuration of the tool.</fsummary>
<type>
<v>Result = ok | {error,Reason}</v>
@@ -142,7 +142,7 @@
</desc>
</func>
<func>
- <name>dump(File) -> Result</name>
+ <name since="">dump(File) -> Result</name>
<fsummary>Dump the current display to a file.</fsummary>
<type>
<v>Result = ok | {error,Reason}</v>
@@ -153,7 +153,7 @@
</desc>
</func>
<func>
- <name>stop() -> stop</name>
+ <name since="">stop() -> stop</name>
<fsummary>Terminate etop.</fsummary>
<desc>
<p>Terminates <c>etop</c>.</p>
diff --git a/lib/observer/doc/src/observer.xml b/lib/observer/doc/src/observer.xml
index 843be26ee1..7fb1dd044e 100644
--- a/lib/observer/doc/src/observer.xml
+++ b/lib/observer/doc/src/observer.xml
@@ -33,7 +33,7 @@
<rev>PA1</rev>
<file>observer.xml</file>
</header>
- <module>observer</module>
+ <module since="OTP R15B">observer</module>
<modulesummary>A GUI tool for observing an Erlang system.</modulesummary>
<description>
<p>Observer is a graphical tool for observing the characteristics of
@@ -48,7 +48,7 @@
</description>
<funcs>
<func>
- <name>start() -> ok</name>
+ <name since="OTP R15B">start() -> ok</name>
<fsummary>Start the Observer GUI.</fsummary>
<desc>
<p>Starts the Observer GUI.
diff --git a/lib/observer/doc/src/ttb.xml b/lib/observer/doc/src/ttb.xml
index 7cd15e15d3..fee95e0b21 100644
--- a/lib/observer/doc/src/ttb.xml
+++ b/lib/observer/doc/src/ttb.xml
@@ -33,7 +33,7 @@
<rev>PA1</rev>
<file>ttb.xml</file>
</header>
- <module>ttb</module>
+ <module since="">ttb</module>
<modulesummary>A base for building trace tools for distributed systems.</modulesummary>
<description>
<p>The Trace Tool Builder, <c>ttb</c>, is a base for building trace
@@ -44,7 +44,7 @@
</description>
<funcs>
<func>
- <name>start_trace(Nodes, Patterns, FlagSpec, Opts) -> Result</name>
+ <name since="OTP R15B">start_trace(Nodes, Patterns, FlagSpec, Opts) -> Result</name>
<fsummary>Start a trace port on each specified node.</fsummary>
<type>
<v>Result = see p/2</v>
@@ -76,7 +76,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>tracer() -> Result</name>
+ <name since="">tracer() -> Result</name>
<fsummary>Equivalent to tracer(node()).</fsummary>
<desc>
<p>Equivalent to <c>tracer(node())</c>.</p>
@@ -84,7 +84,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>tracer(Shortcut) -> Result</name>
+ <name since="">tracer(Shortcut) -> Result</name>
<fsummary>Handy shortcuts for common tracing settings.</fsummary>
<type>
<v>Shortcut = shell | dbg</v>
@@ -97,7 +97,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>tracer(Nodes) -> Result</name>
+ <name since="">tracer(Nodes) -> Result</name>
<fsummary>Equivalent to tracer(Nodes,[]).</fsummary>
<desc>
<p>Equivalent to <c>tracer(Nodes,[])</c>.</p>
@@ -105,7 +105,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>tracer(Nodes,Opts) -> Result</name>
+ <name since="">tracer(Nodes,Opts) -> Result</name>
<fsummary>Start a trace port on each specified node.</fsummary>
<type>
<v>Result = {ok, ActivatedNodes} | {error,Reason}</v>
@@ -243,7 +243,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>p(Item,Flags) -> Return</name>
+ <name since="">p(Item,Flags) -> Return</name>
<fsummary>Set the specified trace flags on the specified processes or ports.</fsummary>
<type>
<v>Return = {ok,[{Item,MatchDesc}]}</v>
@@ -277,7 +277,8 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>tp, tpl, tpe, ctp, ctpl, ctpg, ctpe</name>
+ <name since="">tp, tpl, ctp, ctpl, ctpg</name>
+ <name since="OTP 19.0">tpe, ctpe</name>
<fsummary>Set and clear trace patterns.</fsummary>
<desc>
<p>These functions are to be used with trace
@@ -338,7 +339,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>list_history() -> History</name>
+ <name since="">list_history() -> History</name>
<fsummary>Return all calls stored in history.</fsummary>
<type>
<v>History = [{N,Func,Args}]</v>
@@ -352,7 +353,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>run_history(N) -> ok | {error, Reason}</name>
+ <name since="">run_history(N) -> ok | {error, Reason}</name>
<fsummary>Execute one entry of the history.</fsummary>
<type>
<v>N = integer() | [integer()]</v>
@@ -364,7 +365,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>write_config(ConfigFile,Config)</name>
+ <name since="">write_config(ConfigFile,Config)</name>
<fsummary>Equivalent to write_config(ConfigFile,Config,[]).</fsummary>
<desc>
<p>Equivalent to <c>write_config(ConfigFile,Config,[])</c>.</p>
@@ -372,7 +373,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>write_config(ConfigFile,Config,Opts) -> ok | {error,Reason}</name>
+ <name since="">write_config(ConfigFile,Config,Opts) -> ok | {error,Reason}</name>
<fsummary>Create a configuration file.</fsummary>
<type>
<v>ConfigFile = string()</v>
@@ -405,7 +406,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>run_config(ConfigFile) -> ok | {error,Reason}</name>
+ <name since="">run_config(ConfigFile) -> ok | {error,Reason}</name>
<fsummary>Execute all entries in a configuration file.</fsummary>
<type>
<v>ConfigFile = string()</v>
@@ -418,7 +419,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>run_config(ConfigFile,NumList) -> ok | {error,Reason}</name>
+ <name since="">run_config(ConfigFile,NumList) -> ok | {error,Reason}</name>
<fsummary>Execute selected entries from a configuration file.</fsummary>
<type>
<v>ConfigFile = string()</v>
@@ -437,7 +438,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>list_config(ConfigFile) -> Config | {error,Reason}</name>
+ <name since="">list_config(ConfigFile) -> Config | {error,Reason}</name>
<fsummary>List all entries in a configuration file.</fsummary>
<type>
<v>ConfigFile = string()</v>
@@ -449,7 +450,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>write_trace_info(Key,Info) -> ok</name>
+ <name since="">write_trace_info(Key,Info) -> ok</name>
<fsummary>Write any information to file <c>.ti</c>.</fsummary>
<type>
<v>Key = term()</v>
@@ -465,7 +466,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>seq_trigger_ms() -> MatchSpec</name>
+ <name since="">seq_trigger_ms() -> MatchSpec</name>
<fsummary>Equivalent to seq_trigger_ms(all).</fsummary>
<desc>
<p>Equivalent to <c>seq_trigger_ms(all)</c>.</p>
@@ -473,7 +474,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>seq_trigger_ms(Flags) -> MatchSpec</name>
+ <name since="">seq_trigger_ms(Flags) -> MatchSpec</name>
<fsummary>Return a match_spec() which starts sequential tracing.</fsummary>
<type>
<v>MatchSpec = match_spec()</v>
@@ -521,7 +522,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>stop()</name>
+ <name since="">stop()</name>
<fsummary>Equivalent to stop([]).</fsummary>
<desc>
<p>Equivalent to <c>stop([])</c>.</p>
@@ -529,7 +530,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>stop(Opts) -> stopped | {stopped, Dir}</name>
+ <name since="">stop(Opts) -> stopped | {stopped, Dir}</name>
<fsummary>Stop tracing and fetch/format logs from all nodes.</fsummary>
<type>
<v>Opts = Opt | [Opt]</v>
@@ -573,7 +574,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>get_et_handler()</name>
+ <name since="OTP R15B">get_et_handler()</name>
<fsummary>Return the <c>et</c> handler.</fsummary>
<desc>
<p>Returns the <c>et</c> handler, which can be used with <c>format/2</c>
@@ -583,7 +584,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>format(File)</name>
+ <name since="">format(File)</name>
<fsummary>Equivalent to <c>format(File,[])</c>.</fsummary>
<desc>
<p>Equivalent to <c>format(File,[])</c>.</p>
@@ -591,7 +592,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>format(File,Options) -> ok | {error, Reason}</name>
+ <name since="">format(File,Options) -> ok | {error, Reason}</name>
<fsummary>Format a binary trace log.</fsummary>
<type>
<v>File = string() | [string()]</v>
diff --git a/lib/observer/src/cdv_bin_cb.erl b/lib/observer/src/cdv_bin_cb.erl
index a4a542297c..91d33474c8 100644
--- a/lib/observer/src/cdv_bin_cb.erl
+++ b/lib/observer/src/cdv_bin_cb.erl
@@ -49,7 +49,7 @@ format_bin_fun(Format,Bin) ->
try io_lib:format(Format,[Bin]) of
Str -> plain_html(lists:flatten(Str))
catch error:badarg ->
- Warning = "This binary can not be formatted with " ++ Format,
+ Warning = "This binary cannot be formatted with " ++ Format,
observer_html_lib:warning(Warning)
end
end.
@@ -59,7 +59,7 @@ binary_to_term_fun(Bin) ->
try binary_to_term(Bin) of
Term -> plain_html(io_lib:format("~tp",[Term]))
catch error:badarg ->
- Warning = "This binary can not be converted to an Erlang term",
+ Warning = "This binary cannot be converted to an Erlang term",
observer_html_lib:warning(Warning)
end
end.
diff --git a/lib/observer/src/cdv_detail_wx.erl b/lib/observer/src/cdv_detail_wx.erl
index 4b1984c394..5e1137511a 100644
--- a/lib/observer/src/cdv_detail_wx.erl
+++ b/lib/observer/src/cdv_detail_wx.erl
@@ -84,8 +84,9 @@ destroy_progress(_) ->
ok.
init(Id,ParentFrame,Callback,App,Parent,{Title,Info,TW}) ->
+ Scale = observer_wx:get_scale(),
Frame=wxFrame:new(ParentFrame, ?wxID_ANY, [Title],
- [{style, ?wxDEFAULT_FRAME_STYLE}, {size, {850,600}}]),
+ [{style, ?wxDEFAULT_FRAME_STYLE}, {size, {Scale*850,Scale*600}}]),
MenuBar = wxMenuBar:new(),
create_menus(MenuBar),
wxFrame:setMenuBar(Frame, MenuBar),
diff --git a/lib/observer/src/cdv_html_wx.erl b/lib/observer/src/cdv_html_wx.erl
index ffef83227c..8956173c93 100644
--- a/lib/observer/src/cdv_html_wx.erl
+++ b/lib/observer/src/cdv_html_wx.erl
@@ -79,14 +79,14 @@ handle_info(active, #state{panel=HtmlWin,delayed_fetch=Callback}=State)
observer_lib:sync_destroy_progress_dialog(),
wx_misc:beginBusyCursor(),
wxHtmlWindow:setPage(HtmlWin,HtmlText),
- cdv_wx:set_status(TW),
+ cdv_wx_set_status(State, TW),
wx_misc:endBusyCursor(),
{noreply, State#state{expand_table=Tab,
delayed_fetch=undefined,
trunc_warn=TW}};
handle_info(active, State) ->
- cdv_wx:set_status(State#state.trunc_warn),
+ cdv_wx_set_status(State, State#state.trunc_warn),
{noreply, State};
handle_info(Info, State) ->
@@ -164,3 +164,10 @@ expand(Id,Callback,#state{expand_wins=Opened0, app=App}=State) ->
Opened0
end,
State#state{expand_wins=Opened}.
+
+cdv_wx_set_status(#state{app = cdv}, Status) ->
+ %% this module is used by the observer when cdw_wx isn't started
+ %% only try to set status when used by cdv
+ cdv_wx:set_status(Status);
+cdv_wx_set_status(_, _) ->
+ ok.
diff --git a/lib/observer/src/cdv_table_wx.erl b/lib/observer/src/cdv_table_wx.erl
index 0f28a51017..0cad272262 100644
--- a/lib/observer/src/cdv_table_wx.erl
+++ b/lib/observer/src/cdv_table_wx.erl
@@ -50,11 +50,12 @@ init([ParentWin, {ColumnSpec,Info,TW}]) ->
end,
Grid = wxListCtrl:new(ParentWin, [{style, Style}]),
Li = wxListItem:new(),
+ Scale = observer_wx:get_scale(),
AddListEntry = fun({Name, Align, DefSize}, Col) ->
wxListItem:setText(Li, Name),
wxListItem:setAlign(Li, Align),
wxListCtrl:insertColumn(Grid, Col, Li),
- wxListCtrl:setColumnWidth(Grid, Col, DefSize),
+ wxListCtrl:setColumnWidth(Grid, Col, DefSize*Scale),
Col + 1
end,
lists:foldl(AddListEntry, 0, ColumnSpec),
diff --git a/lib/observer/src/cdv_term_cb.erl b/lib/observer/src/cdv_term_cb.erl
index 91de6449c4..85da1d227a 100644
--- a/lib/observer/src/cdv_term_cb.erl
+++ b/lib/observer/src/cdv_term_cb.erl
@@ -51,7 +51,7 @@ format_term_fun(Format,Term,Tab) ->
try io_lib:format(Format,[Term]) of
Str -> {expand, plain_html(Str), Tab}
catch error:badarg ->
- Warning = "This term can not be formatted with " ++ Format,
+ Warning = "This term cannot be formatted with " ++ Format,
observer_html_lib:warning(Warning)
after
observer_lib:report_progress({ok,stop_pulse})
diff --git a/lib/observer/src/cdv_virtual_list_wx.erl b/lib/observer/src/cdv_virtual_list_wx.erl
index 2702301021..14877b7eab 100644
--- a/lib/observer/src/cdv_virtual_list_wx.erl
+++ b/lib/observer/src/cdv_virtual_list_wx.erl
@@ -132,11 +132,12 @@ create_list_box(Panel, Holder, Callback, Owner) ->
end}
]),
Li = wxListItem:new(),
+ Scale = observer_wx:get_scale(),
AddListEntry = fun({Name, Align, DefSize}, Col) ->
wxListItem:setText(Li, Name),
wxListItem:setAlign(Li, Align),
wxListCtrl:insertColumn(ListCtrl, Col, Li),
- wxListCtrl:setColumnWidth(ListCtrl, Col, DefSize),
+ wxListCtrl:setColumnWidth(ListCtrl, Col, DefSize*Scale),
Col + 1
end,
ListItems = Callback:col_spec(),
diff --git a/lib/observer/src/cdv_wx.erl b/lib/observer/src/cdv_wx.erl
index 1e9cef8952..7100cc8790 100644
--- a/lib/observer/src/cdv_wx.erl
+++ b/lib/observer/src/cdv_wx.erl
@@ -101,8 +101,9 @@ init(File0) ->
{ok,CdvServer} = crashdump_viewer:start_link(),
catch wxSystemOptions:setOption("mac.listctrl.always_use_generic", 1),
+ Scale = observer_wx:get_scale(),
Frame = wxFrame:new(wx:null(), ?wxID_ANY, "Crashdump Viewer",
- [{size, {850, 600}}, {style, ?wxDEFAULT_FRAME_STYLE}]),
+ [{size, {Scale*850, Scale*600}}, {style, ?wxDEFAULT_FRAME_STYLE}]),
IconFile = filename:join(code:priv_dir(observer), "erlang_observer.png"),
Icon = wxIcon:new(IconFile, [{type,?wxBITMAP_TYPE_PNG}]),
wxFrame:setIcon(Frame, Icon),
@@ -458,10 +459,7 @@ maybe_warn_filename(FileName) ->
true ->
continue;
false ->
- DumpName = case os:getenv("ERL_CRASH_DUMP") of
- false -> filename:absname("erl_crash.dump");
- Name -> filename:absname(Name)
- end,
+ DumpName = filename:absname(os:getenv("ERL_CRASH_DUMP", "erl_crash.dump")),
case filename:absname(FileName) of
DumpName ->
Warning =
diff --git a/lib/observer/src/observer_alloc_wx.erl b/lib/observer/src/observer_alloc_wx.erl
index 54e246f247..da47a30fb1 100644
--- a/lib/observer/src/observer_alloc_wx.erl
+++ b/lib/observer/src/observer_alloc_wx.erl
@@ -282,11 +282,12 @@ create_mem_info(Parent) ->
Grid = wxListCtrl:new(Parent, [{style, Style}]),
Li = wxListItem:new(),
+ Scale = observer_wx:get_scale(),
AddListEntry = fun({Name, Align, DefSize}, Col) ->
wxListItem:setText(Li, Name),
wxListItem:setAlign(Li, Align),
wxListCtrl:insertColumn(Grid, Col, Li),
- wxListCtrl:setColumnWidth(Grid, Col, DefSize),
+ wxListCtrl:setColumnWidth(Grid, Col, DefSize*Scale),
Col + 1
end,
ListItems = [{"Allocator Type", ?wxLIST_FORMAT_LEFT, 200},
diff --git a/lib/observer/src/observer_app_wx.erl b/lib/observer/src/observer_app_wx.erl
index 2a481966da..8c3eef5411 100644
--- a/lib/observer/src/observer_app_wx.erl
+++ b/lib/observer/src/observer_app_wx.erl
@@ -117,16 +117,19 @@ init([Notebook, Parent, _Config]) ->
UseGC = haveGC(),
Version28 = ?wxMAJOR_VERSION =:= 2 andalso ?wxMINOR_VERSION =:= 8,
+ Scale = observer_wx:get_scale(),
Font = case os:type() of
{unix,_} when UseGC, Version28 ->
- wxFont:new(12,?wxFONTFAMILY_DECORATIVE,?wxFONTSTYLE_NORMAL,?wxFONTWEIGHT_NORMAL);
+ wxFont:new(Scale * 12,?wxFONTFAMILY_DECORATIVE,?wxFONTSTYLE_NORMAL,?wxFONTWEIGHT_NORMAL);
_ ->
- wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT)
+ Font0 = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT),
+ wxFont:setPointSize(Font0, Scale * wxFont:getPointSize(Font0)),
+ Font0
end,
SelCol = wxSystemSettings:getColour(?wxSYS_COLOUR_HIGHLIGHT),
GreyBrush = wxBrush:new({230,230,240}),
SelBrush = wxBrush:new(SelCol),
- LinkPen = wxPen:new(SelCol, [{width, 2}]),
+ LinkPen = wxPen:new(SelCol, [{width, Scale * 2}]),
process_flag(trap_exit, true),
{Panel, #state{parent=Parent,
panel =Panel,
@@ -134,7 +137,7 @@ init([Notebook, Parent, _Config]) ->
app_w =DrawingArea,
usegc = UseGC,
paint=#paint{font = Font,
- pen = wxPen:new({80,80,80}, [{width, 2}]),
+ pen = wxPen:new({80,80,80}, [{width, Scale * 2}]),
brush= GreyBrush,
sel = SelBrush,
links= LinkPen
diff --git a/lib/observer/src/observer_perf_wx.erl b/lib/observer/src/observer_perf_wx.erl
index 21c6d26f49..79271addf2 100644
--- a/lib/observer/src/observer_perf_wx.erl
+++ b/lib/observer/src/observer_perf_wx.erl
@@ -110,25 +110,26 @@ setup_graph_drawing(Panels) ->
_ = [Do(Panel) || Panel <- Panels],
UseGC = haveGC(),
Version28 = ?wxMAJOR_VERSION =:= 2 andalso ?wxMINOR_VERSION =:= 8,
+ Scale = observer_wx:get_scale(),
{Font, SmallFont}
= if UseGC, Version28 ->
%% Def font is really small when using Graphics contexts in 2.8
%% Hardcode it
- F = wxFont:new(12,?wxFONTFAMILY_DECORATIVE,?wxFONTSTYLE_NORMAL,?wxFONTWEIGHT_BOLD),
- SF = wxFont:new(10, ?wxFONTFAMILY_DECORATIVE, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_NORMAL),
+ F = wxFont:new(Scale * 12,?wxFONTFAMILY_DECORATIVE,?wxFONTSTYLE_NORMAL,?wxFONTWEIGHT_BOLD),
+ SF = wxFont:new(Scale * 10, ?wxFONTFAMILY_DECORATIVE, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_NORMAL),
{F, SF};
true ->
DefFont = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT),
DefSize = wxFont:getPointSize(DefFont),
DefFamily = wxFont:getFamily(DefFont),
- F = wxFont:new(DefSize-1, DefFamily, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_BOLD),
- SF = wxFont:new(DefSize-2, DefFamily, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_NORMAL),
+ F = wxFont:new(Scale * (DefSize-1), DefFamily, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_BOLD),
+ SF = wxFont:new(Scale * (DefSize-2), DefFamily, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_NORMAL),
{F, SF}
end,
- BlackPen = wxPen:new({0,0,0}, [{width, 1}]),
- Pens = [wxPen:new(Col, [{width, 1}, {style, ?wxSOLID}])
+ BlackPen = wxPen:new({0,0,0}, [{width, Scale}]),
+ Pens = [wxPen:new(Col, [{width, Scale}, {style, ?wxSOLID}])
|| Col <- tuple_to_list(colors())],
- DotPens = [wxPen:new(Col, [{width, 1}, {style, ?wxDOT}])
+ DotPens = [wxPen:new(Col, [{width, Scale}, {style, ?wxDOT}])
|| Col <- tuple_to_list(colors())],
#paint{usegc = UseGC,
font = Font,
diff --git a/lib/observer/src/observer_port_wx.erl b/lib/observer/src/observer_port_wx.erl
index 445f3dd6b1..00cf1b5fba 100644
--- a/lib/observer/src/observer_port_wx.erl
+++ b/lib/observer/src/observer_port_wx.erl
@@ -96,11 +96,12 @@ init([Notebook, Parent, Config]) ->
wxListCtrl:setColumnWidth(Grid, Col, DefSize),
Col + 1
end,
- ListItems = [{"Id", ?wxLIST_FORMAT_LEFT, 150},
- {"Connected", ?wxLIST_FORMAT_LEFT, 150},
- {"Name", ?wxLIST_FORMAT_LEFT, 150},
- {"Controls", ?wxLIST_FORMAT_LEFT, 200},
- {"Slot", ?wxLIST_FORMAT_RIGHT, 50}],
+ Scale = observer_wx:get_scale(),
+ ListItems = [{"Id", ?wxLIST_FORMAT_LEFT, Scale*150},
+ {"Connected", ?wxLIST_FORMAT_LEFT, Scale*150},
+ {"Name", ?wxLIST_FORMAT_LEFT, Scale*150},
+ {"Controls", ?wxLIST_FORMAT_LEFT, Scale*200},
+ {"Slot", ?wxLIST_FORMAT_RIGHT, Scale*50}],
lists:foldl(AddListEntry, 0, ListItems),
wxListItem:destroy(Li),
@@ -461,10 +462,11 @@ display_port_info(Parent, PortRec, Opened) ->
do_display_port_info(Parent0, PortRec) ->
Parent = observer_lib:get_wx_parent(Parent0),
Title = "Port Info: " ++ PortRec#port.id_str,
+ Scale = observer_wx:get_scale(),
Frame = wxMiniFrame:new(Parent, ?wxID_ANY, Title,
[{style, ?wxSYSTEM_MENU bor ?wxCAPTION
bor ?wxCLOSE_BOX bor ?wxRESIZE_BORDER},
- {size,{600,400}}]),
+ {size,{Scale * 600, Scale * 400}}]),
ScrolledWin = wxScrolledWindow:new(Frame,[{style,?wxHSCROLL bor ?wxVSCROLL}]),
wxScrolledWindow:enableScrolling(ScrolledWin,true,true),
wxScrolledWindow:setScrollbars(ScrolledWin,20,20,0,0),
diff --git a/lib/observer/src/observer_pro_wx.erl b/lib/observer/src/observer_pro_wx.erl
index 04e654a37e..4ab4a78462 100644
--- a/lib/observer/src/observer_pro_wx.erl
+++ b/lib/observer/src/observer_pro_wx.erl
@@ -163,13 +163,14 @@ create_list_box(Panel, Holder) ->
wxListCtrl:setColumnWidth(ListCtrl, Col, DefSize),
Col + 1
end,
- ListItems = [{"Pid", ?wxLIST_FORMAT_CENTRE, 120},
- {"Name or Initial Func", ?wxLIST_FORMAT_LEFT, 200},
-%% {"Time", ?wxLIST_FORMAT_CENTRE, 50},
- {"Reds", ?wxLIST_FORMAT_RIGHT, 100},
- {"Memory", ?wxLIST_FORMAT_RIGHT, 100},
- {"MsgQ", ?wxLIST_FORMAT_RIGHT, 50},
- {"Current Function", ?wxLIST_FORMAT_LEFT, 200}],
+ Scale = observer_wx:get_scale(),
+ ListItems = [{"Pid", ?wxLIST_FORMAT_CENTRE, Scale*120},
+ {"Name or Initial Func", ?wxLIST_FORMAT_LEFT, Scale*200},
+%% {"Time", ?wxLIST_FORMAT_CENTRE, Scale*50},
+ {"Reds", ?wxLIST_FORMAT_RIGHT, Scale*100},
+ {"Memory", ?wxLIST_FORMAT_RIGHT, Scale*100},
+ {"MsgQ", ?wxLIST_FORMAT_RIGHT, Scale*50},
+ {"Current Function", ?wxLIST_FORMAT_LEFT, Scale*200}],
lists:foldl(AddListEntry, 0, ListItems),
wxListItem:destroy(Li),
diff --git a/lib/observer/src/observer_procinfo.erl b/lib/observer/src/observer_procinfo.erl
index f436886735..bd5fed0951 100644
--- a/lib/observer/src/observer_procinfo.erl
+++ b/lib/observer/src/observer_procinfo.erl
@@ -59,8 +59,9 @@ init([Pid, ParentFrame, Parent]) ->
{registered_name, Registered} -> io_lib:format("~tp (~p)",[Registered, Pid]);
undefined -> throw(process_undefined)
end,
+ Scale = observer_wx:get_scale(),
Frame=wxFrame:new(ParentFrame, ?wxID_ANY, [atom_to_list(node(Pid)), $:, Title],
- [{style, ?wxDEFAULT_FRAME_STYLE}, {size, {850,600}}]),
+ [{style, ?wxDEFAULT_FRAME_STYLE}, {size, {Scale * 850, Scale * 600}}]),
MenuBar = wxMenuBar:new(),
create_menus(MenuBar),
wxFrame:setMenuBar(Frame, MenuBar),
@@ -245,12 +246,13 @@ init_dict_page(Parent, Pid, Table) ->
init_stack_page(Parent, Pid) ->
LCtrl = wxListCtrl:new(Parent, [{style, ?wxLC_REPORT bor ?wxLC_HRULES}]),
Li = wxListItem:new(),
+ Scale = observer_wx:get_scale(),
wxListItem:setText(Li, "Module:Function/Arg"),
wxListCtrl:insertColumn(LCtrl, 0, Li),
- wxListCtrl:setColumnWidth(LCtrl, 0, 300),
+ wxListCtrl:setColumnWidth(LCtrl, 0, Scale * 300),
wxListItem:setText(Li, "File:LineNumber"),
wxListCtrl:insertColumn(LCtrl, 1, Li),
- wxListCtrl:setColumnWidth(LCtrl, 1, 300),
+ wxListCtrl:setColumnWidth(LCtrl, 1, Scale * 300),
wxListItem:destroy(Li),
Update = fun() ->
case observer_wx:try_rpc(node(Pid), erlang, process_info,
diff --git a/lib/observer/src/observer_trace_wx.erl b/lib/observer/src/observer_trace_wx.erl
index 2c3b46a3a1..f458c8c34a 100644
--- a/lib/observer/src/observer_trace_wx.erl
+++ b/lib/observer/src/observer_trace_wx.erl
@@ -188,8 +188,9 @@ create_proc_port_view(Parent) ->
wxListCtrl:setColumnWidth(Procs, Col, DefSize),
Col + 1
end,
- ProcListItems = [{"Process Id", ?wxLIST_FORMAT_CENTER, 120},
- {"Trace Options", ?wxLIST_FORMAT_LEFT, 300}],
+ Scale = observer_wx:get_scale(),
+ ProcListItems = [{"Process Id", ?wxLIST_FORMAT_CENTER, Scale*120},
+ {"Trace Options", ?wxLIST_FORMAT_LEFT, Scale*300}],
lists:foldl(AddProc, 0, ProcListItems),
AddPort = fun({Name, Align, DefSize}, Col) ->
@@ -199,8 +200,8 @@ create_proc_port_view(Parent) ->
wxListCtrl:setColumnWidth(Ports, Col, DefSize),
Col + 1
end,
- PortListItems = [{"Port Id", ?wxLIST_FORMAT_CENTER, 120},
- {"Trace Options", ?wxLIST_FORMAT_LEFT, 300}],
+ PortListItems = [{"Port Id", ?wxLIST_FORMAT_CENTER, Scale*120},
+ {"Trace Options", ?wxLIST_FORMAT_LEFT, Scale*300}],
lists:foldl(AddPort, 0, PortListItems),
wxListItem:destroy(Li),
@@ -242,14 +243,15 @@ create_matchspec_view(Parent) ->
Funcs = wxListCtrl:new(Splitter, [{winid, ?FUNCS_WIN}, {style, Style}]),
Li = wxListItem:new(),
+ Scale = observer_wx:get_scale(),
wxListItem:setText(Li, "Modules"),
wxListCtrl:insertColumn(Modules, 0, Li),
wxListItem:setText(Li, "Functions"),
wxListCtrl:insertColumn(Funcs, 0, Li),
- wxListCtrl:setColumnWidth(Funcs, 0, 150),
+ wxListCtrl:setColumnWidth(Funcs, 0, Scale*150),
wxListItem:setText(Li, "Match Spec"),
wxListCtrl:insertColumn(Funcs, 1, Li),
- wxListCtrl:setColumnWidth(Funcs, 1, 300),
+ wxListCtrl:setColumnWidth(Funcs, 1, Scale*300),
wxListItem:destroy(Li),
wxSplitterWindow:setSashGravity(Splitter, 0.0),
@@ -969,7 +971,8 @@ output_file(true, true, Opts) ->
create_logwindow(_Parent, false) -> {false, false};
create_logwindow(Parent, true) ->
- LogWin = wxFrame:new(Parent, ?LOG_WIN, "Trace Log", [{size, {750, 800}}]),
+ Scale = observer_wx:get_scale(),
+ LogWin = wxFrame:new(Parent, ?LOG_WIN, "Trace Log", [{size, {750*Scale, 800*Scale}}]),
MB = wxMenuBar:new(),
File = wxMenu:new(),
wxMenu:append(File, ?LOG_CLEAR, "Clear Log\tCtrl-C"),
diff --git a/lib/observer/src/observer_traceoptions_wx.erl b/lib/observer/src/observer_traceoptions_wx.erl
index ea292b92af..514d55ff24 100644
--- a/lib/observer/src/observer_traceoptions_wx.erl
+++ b/lib/observer/src/observer_traceoptions_wx.erl
@@ -167,9 +167,10 @@ select_nodes(Parent, Nodes) ->
check_selector(Parent, Choices).
module_selector(Parent, Node) ->
+ Scale = observer_wx:get_scale(),
Dialog = wxDialog:new(Parent, ?wxID_ANY, "Select Module or Event",
[{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER},
- {size, {400, 400}}]),
+ {size, {400*Scale, 400*Scale}}]),
Panel = wxPanel:new(Dialog),
PanelSz = wxBoxSizer:new(?wxVERTICAL),
MainSz = wxBoxSizer:new(?wxVERTICAL),
@@ -237,9 +238,10 @@ function_selector(Parent, Node, Module) ->
end.
check_selector(Parent, ParsedChoices) ->
+ Scale = observer_wx:get_scale(),
Dialog = wxDialog:new(Parent, ?wxID_ANY, "Trace Functions",
[{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER},
- {size, {400, 400}}]),
+ {size, {400*Scale, 400*Scale}}]),
Panel = wxPanel:new(Dialog),
PanelSz = wxBoxSizer:new(?wxVERTICAL),
@@ -331,9 +333,10 @@ select_matchspec(Pid, Parent, AllMatchSpecs, Key) ->
{value,{Key,MSs0},Rest} -> {MSs0,Rest};
false -> {[],AllMatchSpecs}
end,
+ Scale = observer_wx:get_scale(),
Dialog = wxDialog:new(Parent, ?wxID_ANY, "Trace Match Specifications",
[{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER},
- {size, {400, 400}}]),
+ {size, {400*Scale, 400*Scale}}]),
Panel = wxPanel:new(Dialog),
PanelSz = wxBoxSizer:new(?wxVERTICAL),
diff --git a/lib/observer/src/observer_tv_table.erl b/lib/observer/src/observer_tv_table.erl
index d6dcee2cda..7bd67a0f0b 100644
--- a/lib/observer/src/observer_tv_table.erl
+++ b/lib/observer/src/observer_tv_table.erl
@@ -99,7 +99,8 @@ init([Parent, Opts]) ->
ets -> "TV Ets: " ++ Title0;
mnesia -> "TV Mnesia: " ++ Title0
end,
- Frame = wxFrame:new(Parent, ?wxID_ANY, Title, [{size, {800, 600}}]),
+ Scale = observer_wx:get_scale(),
+ Frame = wxFrame:new(Parent, ?wxID_ANY, Title, [{size, {Scale * 800, Scale * 600}}]),
IconFile = filename:join(code:priv_dir(observer), "erlang_observer.png"),
Icon = wxIcon:new(IconFile, [{type,?wxBITMAP_TYPE_PNG}]),
wxFrame:setIcon(Frame, Icon),
diff --git a/lib/observer/src/observer_tv_wx.erl b/lib/observer/src/observer_tv_wx.erl
index 814f3a1260..9743a6ed42 100644
--- a/lib/observer/src/observer_tv_wx.erl
+++ b/lib/observer/src/observer_tv_wx.erl
@@ -87,12 +87,13 @@ init([Notebook, Parent, Config]) ->
wxListCtrl:setColumnWidth(Grid, Col, DefSize),
Col + 1
end,
- ListItems = [{"Table Name", ?wxLIST_FORMAT_LEFT, 200},
- {"Objects", ?wxLIST_FORMAT_RIGHT, 100},
- {"Size (kB)", ?wxLIST_FORMAT_RIGHT, 100},
- {"Owner Pid", ?wxLIST_FORMAT_CENTER, 150},
- {"Owner Name", ?wxLIST_FORMAT_LEFT, 200},
- {"Table Id", ?wxLIST_FORMAT_LEFT, 250}
+ Scale = observer_wx:get_scale(),
+ ListItems = [{"Table Name", ?wxLIST_FORMAT_LEFT, Scale*200},
+ {"Objects", ?wxLIST_FORMAT_RIGHT, Scale*100},
+ {"Size (kB)", ?wxLIST_FORMAT_RIGHT, Scale*100},
+ {"Owner Pid", ?wxLIST_FORMAT_CENTER, Scale*150},
+ {"Owner Name", ?wxLIST_FORMAT_LEFT, Scale*200},
+ {"Table Id", ?wxLIST_FORMAT_LEFT, Scale*250}
],
lists:foldl(AddListEntry, 0, ListItems),
wxListItem:destroy(Li),
@@ -147,7 +148,7 @@ handle_event(#wx{event=#wxList{type=command_list_item_activated,
State=#state{holder=Holder, node=Node, opts=#opts{type=Type}, grid=Grid}) ->
case get_table(Holder, Index) of
#tab{protection=private} ->
- self() ! {error, "Table has 'private' protection and can not be read"};
+ self() ! {error, "Table has 'private' protection and cannot be read"};
#tab{}=Table ->
observer_tv_table:start_link(Grid, [{node,Node}, {type,Type}, {table,Table}]);
_ -> ignore
@@ -187,7 +188,7 @@ handle_event(#wx{id=?ID_SHOW_TABLE},
R when is_integer(R) ->
case get_table(Holder, R) of
#tab{protection=private} ->
- self() ! {error, "Table has 'private' protection and can not be read"};
+ self() ! {error, "Table has 'private' protection and cannot be read"};
#tab{}=Table ->
observer_tv_table:start_link(Grid, [{node,Node}, {type,Type}, {table,Table}]);
_ -> ignore
diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl
index 453e3bdc2d..de7d821030 100644
--- a/lib/observer/src/observer_wx.erl
+++ b/lib/observer/src/observer_wx.erl
@@ -22,7 +22,7 @@
-export([start/0, stop/0]).
-export([create_menus/2, get_attrib/1, get_tracer/0, get_active_node/0, get_menubar/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]).
-export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3,
handle_call/3, handle_info/2, check_page_title/1]).
@@ -91,14 +91,24 @@ get_active_node() ->
get_menubar() ->
wx_object:call(observer, get_menubar).
+get_scale() ->
+ ScaleStr = os:getenv("OBSERVER_SCALE", "1"),
+ try list_to_integer(ScaleStr) of
+ Scale when Scale < 1 -> 1;
+ Scale -> Scale
+ catch _:_ ->
+ 1
+ end.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init(_Args) ->
register(observer, self()),
wx:new(),
catch wxSystemOptions:setOption("mac.listctrl.always_use_generic", 1),
+ Scale = get_scale(),
Frame = wxFrame:new(wx:null(), ?wxID_ANY, "Observer",
- [{size, {850, 600}}, {style, ?wxDEFAULT_FRAME_STYLE}]),
+ [{size, {Scale * 850, Scale * 600}}, {style, ?wxDEFAULT_FRAME_STYLE}]),
IconFile = filename:join(code:priv_dir(observer), "erlang_observer.png"),
Icon = wxIcon:new(IconFile, [{type,?wxBITMAP_TYPE_PNG}]),
wxFrame:setIcon(Frame, Icon),
@@ -771,7 +781,11 @@ ensure_sasl_started(Node) ->
ensure_mf_h_handler_used(Node) ->
%% is log_mf_h used ?
- Handlers = rpc:block_call(Node, gen_event, which_handlers, [error_logger]),
+ Handlers =
+ case rpc:block_call(Node, gen_event, which_handlers, [error_logger]) of
+ {badrpc,{'EXIT',noproc}} -> []; % OTP-21+ and no event handler exists
+ Hs -> Hs
+ end,
case lists:any(fun(L)-> L == log_mf_h end, Handlers) of
false -> throw("Error: log_mf_h handler not used in sasl."),
error;
@@ -806,7 +820,7 @@ is_rb_compatible(Node) ->
is_rb_server_running(Node, LogState) ->
%% If already started, somebody else may use it.
- %% We can not use it too, as far log file would be overriden. Not fair.
+ %% We cannot use it too, as far log file would be overriden. Not fair.
case rpc:block_call(Node, erlang, whereis, [rb_server]) of
Pid when is_pid(Pid), (LogState == false) ->
throw("Error: rb_server is already started and maybe used by someone.");
diff --git a/lib/observer/test/crashdump_helper.erl b/lib/observer/test/crashdump_helper.erl
index d5d3649525..84ed99afa5 100644
--- a/lib/observer/test/crashdump_helper.erl
+++ b/lib/observer/test/crashdump_helper.erl
@@ -24,7 +24,7 @@
create_binaries/0,create_sub_binaries/1,
dump_persistent_terms/0,
create_persistent_terms/0]).
--compile(r18).
+-compile(r20).
-include_lib("common_test/include/ct.hrl").
n1_proc(N2,Creator) ->
@@ -204,5 +204,4 @@ dump_persistent_terms() ->
create_persistent_terms() ->
persistent_term:put({?MODULE,first}, {pid,42.0}),
persistent_term:put({?MODULE,second}, [1,2,3]),
- persistent_term:get().
-
+ {persistent_term:get({?MODULE,first}),persistent_term:get({?MODULE,second})}.
diff --git a/lib/observer/test/crashdump_viewer_SUITE.erl b/lib/observer/test/crashdump_viewer_SUITE.erl
index 8c5e618f4a..31cf7011d4 100644
--- a/lib/observer/test/crashdump_viewer_SUITE.erl
+++ b/lib/observer/test/crashdump_viewer_SUITE.erl
@@ -615,9 +615,8 @@ special(File,Procs) ->
#proc{dict=Dict} = ProcDetails,
%% io:format("~p\n", [Dict]),
- Pts1 = crashdump_helper:create_persistent_terms(),
- Pts2 = proplists:get_value(pts,Dict),
- true = lists:sort(Pts1) =:= lists:sort(Pts2),
+ Pts = crashdump_helper:create_persistent_terms(),
+ Pts = proplists:get_value(pts,Dict),
io:format(" persistent terms ok",[]),
ok;
_ ->
diff --git a/lib/observer/test/crashdump_viewer_SUITE_data/old_format.dump b/lib/observer/test/crashdump_viewer_SUITE_data/old_format.dump
index 2c8944fa9d..0b30fdf2f1 100644
--- a/lib/observer/test/crashdump_viewer_SUITE_data/old_format.dump
+++ b/lib/observer/test/crashdump_viewer_SUITE_data/old_format.dump
@@ -10,7 +10,7 @@ Compiled on Wed Dec 4 11:11:21 2002
Process Information
--------------------------------------------------
<0.0.0> Waiting. Registered as: init
-Spawned as: otp_ring0:start/2
+Spawned as: erl_init:start/2
Message buffer data: 4 words
Link list: [<0.5.0>,<0.4.0>,<0.2.0>]
Reductions 3125 stack+heap 610 old_heap_sz=233
@@ -68,7 +68,7 @@ y(3) [gen_event,<0.1.0>,<0.1.0>,{local,error_logger},[],[],[]]
Spawned as: proc_lib:init_p/5
Message buffer data: 4 words
Link list: [<0.7.0>,<0.0.0>]
-Dictionary: [{'$initial_call',{gen,init_it,[gen_server,<0.1.0>,<0.1.0>,{local,application_controller},application_controller,{application,kernel,[{description,"ERTS CXC 138 10"},{vsn,"2.6.3.15"},{id,[]},{modules,[application,application_controller,application_master,application_starter,auth,code,code_aux,code_server,code_server_int,dist_util,erl_boot_server,erl_distribution,erl_open_port,erl_prim_loader,erl_reply,erlang,error_handler,error_logger,file,global,global_group,global_search,group,heart,inet6_tcp,inet6_tcp_dist,inet6_udp,inet_config,inet_hosts,inet_gethost_native,inet_tcp_dist,otp_pre_init,init,kernel,kernel_config,net,net_adm,net_kernel,os,ram_file,rpc,user,user_drv,user_sup,disk_log,disk_log_1,disk_log_server,disk_log_sup,dist_ac,erl_atom_cache,erl_ddll,erl_epmd,erl_external,erts_debug,fixtable_server,gen_tcp,gen_udp,prim_inet,inet,inet_db,inet_dns,inet_parse,inet_res,inet_tcp,inet_udp,pg2,seq_trace,socks5,socks5_auth,socks5_tcp,socks5_udp,wrap_log_reader,otp_ring0]},{registered,[application_controller,erl_reply,auth,boot_server,code_server,disk_log_server,disk_log_sup,erl_prim_loader,error_logger,file_server,fixtable_server,global_group,global_name_server,heart,init,kernel_config,kernel_sup,net_kernel,net_sup,rex,user,os_server,ddll_server,erl_epmd,inet_db,pg2]},{applications,[]},{included_applications,[]},{env,[{error_logger,tty}]},{start_phases,undefined},{maxT,infinity},{maxP,infinity},{mod,{kernel,[]}}]},[]]}},{'$ancestors',[<0.1.0>]}]
+Dictionary: [{'$initial_call',{gen,init_it,[gen_server,<0.1.0>,<0.1.0>,{local,application_controller},application_controller,{application,kernel,[{description,"ERTS CXC 138 10"},{vsn,"2.6.3.15"},{id,[]},{modules,[application,application_controller,application_master,application_starter,auth,code,code_aux,code_server,code_server_int,dist_util,erl_boot_server,erl_distribution,erl_open_port,erl_prim_loader,erl_reply,erlang,error_handler,error_logger,file,global,global_group,global_search,group,heart,inet6_tcp,inet6_tcp_dist,inet6_udp,inet_config,inet_hosts,inet_gethost_native,inet_tcp_dist,otp_pre_init,init,kernel,kernel_config,net,net_adm,net_kernel,os,ram_file,rpc,user,user_drv,user_sup,disk_log,disk_log_1,disk_log_server,disk_log_sup,dist_ac,erl_atom_cache,erl_ddll,erl_epmd,erl_external,erts_debug,fixtable_server,gen_tcp,gen_udp,prim_inet,inet,inet_db,inet_dns,inet_parse,inet_res,inet_tcp,inet_udp,pg2,seq_trace,socks5,socks5_auth,socks5_tcp,socks5_udp,wrap_log_reader,erl_init]},{registered,[application_controller,erl_reply,auth,boot_server,code_server,disk_log_server,disk_log_sup,erl_prim_loader,error_logger,file_server,fixtable_server,global_group,global_name_server,heart,init,kernel_config,kernel_sup,net_kernel,net_sup,rex,user,os_server,ddll_server,erl_epmd,inet_db,pg2]},{applications,[]},{included_applications,[]},{env,[{error_logger,tty}]},{start_phases,undefined},{maxT,infinity},{maxP,infinity},{mod,{kernel,[]}}]},[]]}},{'$ancestors',[<0.1.0>]}]
Reductions 527 stack+heap 1597 old_heap_sz=0
Heap unused=833 OldHeap unused=0
Stack dump:
@@ -88,13 +88,13 @@ y(5) <0.1.0>
y(0) Catch 0x33480C (proc_lib:init_p/5 + 164)
y(1) gen
y(2) init_it
-y(3) [gen_server,<0.1.0>,<0.1.0>,{local,application_controller},application_controller,{application,kernel,[{description,"ERTS CXC 138 10"},{vsn,"2.6.3.15"},{id,[]},{modules,[application,application_controller,application_master,application_starter,auth,code,code_aux,code_server,code_server_int,dist_util,erl_boot_server,erl_distribution,erl_open_port,erl_prim_loader,erl_reply,erlang,error_handler,error_logger,file,global,global_group,global_search,group,heart,inet6_tcp,inet6_tcp_dist,inet6_udp,inet_config,inet_hosts,inet_gethost_native,inet_tcp_dist,otp_pre_init,init,kernel,kernel_config,net,net_adm,net_kernel,os,ram_file,rpc,user,user_drv,user_sup,disk_log,disk_log_1,disk_log_server,disk_log_sup,dist_ac,erl_atom_cache,erl_ddll,erl_epmd,erl_external,erts_debug,fixtable_server,gen_tcp,gen_udp,prim_inet,inet,inet_db,inet_dns,inet_parse,inet_res,inet_tcp,inet_udp,pg2,seq_trace,socks5,socks5_auth,socks5_tcp,socks5_udp,wrap_log_reader,otp_ring0]},{registered,[application_controller,erl_reply,auth,boot_server,code_server,disk_log_server,disk_log_sup,erl_prim_loader,error_logger,file_server,fixtable_server,global_group,global_name_server,heart,init,kernel_config,kernel_sup,net_kernel,net_sup,rex,user,os_server,ddll_server,erl_epmd,inet_db,pg2]},{applications,[]},{included_applications,[]},{env,[{error_logger,tty}]},{start_phases,undefined},{maxT,infinity},{maxP,infinity},{mod,{kernel,[]}}]},[]]
+y(3) [gen_server,<0.1.0>,<0.1.0>,{local,application_controller},application_controller,{application,kernel,[{description,"ERTS CXC 138 10"},{vsn,"2.6.3.15"},{id,[]},{modules,[application,application_controller,application_master,application_starter,auth,code,code_aux,code_server,code_server_int,dist_util,erl_boot_server,erl_distribution,erl_open_port,erl_prim_loader,erl_reply,erlang,error_handler,error_logger,file,global,global_group,global_search,group,heart,inet6_tcp,inet6_tcp_dist,inet6_udp,inet_config,inet_hosts,inet_gethost_native,inet_tcp_dist,otp_pre_init,init,kernel,kernel_config,net,net_adm,net_kernel,os,ram_file,rpc,user,user_drv,user_sup,disk_log,disk_log_1,disk_log_server,disk_log_sup,dist_ac,erl_atom_cache,erl_ddll,erl_epmd,erl_external,erts_debug,fixtable_server,gen_tcp,gen_udp,prim_inet,inet,inet_db,inet_dns,inet_parse,inet_res,inet_tcp,inet_udp,pg2,seq_trace,socks5,socks5_auth,socks5_tcp,socks5_udp,wrap_log_reader,erl_init]},{registered,[application_controller,erl_reply,auth,boot_server,code_server,disk_log_server,disk_log_sup,erl_prim_loader,error_logger,file_server,fixtable_server,global_group,global_name_server,heart,init,kernel_config,kernel_sup,net_kernel,net_sup,rex,user,os_server,ddll_server,erl_epmd,inet_db,pg2]},{applications,[]},{included_applications,[]},{env,[{error_logger,tty}]},{start_phases,undefined},{maxT,infinity},{maxP,infinity},{mod,{kernel,[]}}]},[]]
--------------------------------------------------
<0.7.0> Waiting.
Spawned as: proc_lib:init_p/5
Message buffer data: 0 words
Link list: [<0.8.0>,<0.5.0>]
-Dictionary: [{'$initial_call',{application_master,init,[<0.5.0>,<0.6.0>,{appl_data,kernel,[application_controller,erl_reply,auth,boot_server,code_server,disk_log_server,disk_log_sup,erl_prim_loader,error_logger,file_server,fixtable_server,global_group,global_name_server,heart,init,kernel_config,kernel_sup,net_kernel,net_sup,rex,user,os_server,ddll_server,erl_epmd,inet_db,pg2],undefined,{kernel,[]},[application,application_controller,application_master,application_starter,auth,code,code_aux,code_server,code_server_int,dist_util,erl_boot_server,erl_distribution,erl_open_port,erl_prim_loader,erl_reply,erlang,error_handler,error_logger,file,global,global_group,global_search,group,heart,inet6_tcp,inet6_tcp_dist,inet6_udp,inet_config,inet_hosts,inet_gethost_native,inet_tcp_dist,otp_pre_init,init,kernel,kernel_config,net,net_adm,net_kernel,os,ram_file,rpc,user,user_drv,user_sup,disk_log,disk_log_1,disk_log_server,disk_log_sup,dist_ac,erl_atom_cache,erl_ddll,erl_epmd,erl_external,erts_debug,fixtable_server,gen_tcp,gen_udp,prim_inet,inet,inet_db,inet_dns,inet_parse,inet_res,inet_tcp,inet_udp,pg2,seq_trace,socks5,socks5_auth,socks5_tcp,socks5_udp,wrap_log_reader,otp_ring0],[],infinity,infinity},normal]}},{'$ancestors',[<0.6.0>]}]
+Dictionary: [{'$initial_call',{application_master,init,[<0.5.0>,<0.6.0>,{appl_data,kernel,[application_controller,erl_reply,auth,boot_server,code_server,disk_log_server,disk_log_sup,erl_prim_loader,error_logger,file_server,fixtable_server,global_group,global_name_server,heart,init,kernel_config,kernel_sup,net_kernel,net_sup,rex,user,os_server,ddll_server,erl_epmd,inet_db,pg2],undefined,{kernel,[]},[application,application_controller,application_master,application_starter,auth,code,code_aux,code_server,code_server_int,dist_util,erl_boot_server,erl_distribution,erl_open_port,erl_prim_loader,erl_reply,erlang,error_handler,error_logger,file,global,global_group,global_search,group,heart,inet6_tcp,inet6_tcp_dist,inet6_udp,inet_config,inet_hosts,inet_gethost_native,inet_tcp_dist,otp_pre_init,init,kernel,kernel_config,net,net_adm,net_kernel,os,ram_file,rpc,user,user_drv,user_sup,disk_log,disk_log_1,disk_log_server,disk_log_sup,dist_ac,erl_atom_cache,erl_ddll,erl_epmd,erl_external,erts_debug,fixtable_server,gen_tcp,gen_udp,prim_inet,inet,inet_db,inet_dns,inet_parse,inet_res,inet_tcp,inet_udp,pg2,seq_trace,socks5,socks5_auth,socks5_tcp,socks5_udp,wrap_log_reader,erl_init],[],infinity,infinity},normal]}},{'$ancestors',[<0.6.0>]}]
Reductions 45 stack+heap 377 old_heap_sz=377
Heap unused=306 OldHeap unused=377
Stack dump:
@@ -103,14 +103,14 @@ cp = 0x33480c (proc_lib:init_p/5 + 164)
arity = 0
347174 Return addr 0x33480C (proc_lib:init_p/5 + 164)
-y(0) {state,<0.8.0>,{appl_data,kernel,[application_controller,erl_reply,auth,boot_server,code_server,disk_log_server,disk_log_sup,erl_prim_loader,error_logger,file_server,fixtable_server,global_group,global_name_server,heart,init,kernel_config,kernel_sup,net_kernel,net_sup,rex,user,os_server,ddll_server,erl_epmd,inet_db,pg2],undefined,{kernel,[]},[application,application_controller,application_master,application_starter,auth,code,code_aux,code_server,code_server_int,dist_util,erl_boot_server,erl_distribution,erl_open_port,erl_prim_loader,erl_reply,erlang,error_handler,error_logger,file,global,global_group,global_search,group,heart,inet6_tcp,inet6_tcp_dist,inet6_udp,inet_config,inet_hosts,inet_gethost_native,inet_tcp_dist,otp_pre_init,init,kernel,kernel_config,net,net_adm,net_kernel,os,ram_file,rpc,user,user_drv,user_sup,disk_log,disk_log_1,disk_log_server,disk_log_sup,dist_ac,erl_atom_cache,erl_ddll,erl_epmd,erl_external,erts_debug,fixtable_server,gen_tcp,gen_udp,prim_inet,inet,inet_db,inet_dns,inet_parse,inet_res,inet_tcp,inet_udp,pg2,seq_trace,socks5,socks5_auth,socks5_tcp,socks5_udp,wrap_log_reader,otp_ring0],[],infinity,infinity},[],0,<0.0.0>}
+y(0) {state,<0.8.0>,{appl_data,kernel,[application_controller,erl_reply,auth,boot_server,code_server,disk_log_server,disk_log_sup,erl_prim_loader,error_logger,file_server,fixtable_server,global_group,global_name_server,heart,init,kernel_config,kernel_sup,net_kernel,net_sup,rex,user,os_server,ddll_server,erl_epmd,inet_db,pg2],undefined,{kernel,[]},[application,application_controller,application_master,application_starter,auth,code,code_aux,code_server,code_server_int,dist_util,erl_boot_server,erl_distribution,erl_open_port,erl_prim_loader,erl_reply,erlang,error_handler,error_logger,file,global,global_group,global_search,group,heart,inet6_tcp,inet6_tcp_dist,inet6_udp,inet_config,inet_hosts,inet_gethost_native,inet_tcp_dist,otp_pre_init,init,kernel,kernel_config,net,net_adm,net_kernel,os,ram_file,rpc,user,user_drv,user_sup,disk_log,disk_log_1,disk_log_server,disk_log_sup,dist_ac,erl_atom_cache,erl_ddll,erl_epmd,erl_external,erts_debug,fixtable_server,gen_tcp,gen_udp,prim_inet,inet,inet_db,inet_dns,inet_parse,inet_res,inet_tcp,inet_udp,pg2,seq_trace,socks5,socks5_auth,socks5_tcp,socks5_udp,wrap_log_reader,erl_init],[],infinity,infinity},[],0,<0.0.0>}
y(1) <0.5.0>
347180 Return addr 0xFCEC8 (<terminate process normally>)
y(0) Catch 0x33480C (proc_lib:init_p/5 + 164)
y(1) application_master
y(2) init
-y(3) [<0.5.0>,<0.6.0>,{appl_data,kernel,[application_controller,erl_reply,auth,boot_server,code_server,disk_log_server,disk_log_sup,erl_prim_loader,error_logger,file_server,fixtable_server,global_group,global_name_server,heart,init,kernel_config,kernel_sup,net_kernel,net_sup,rex,user,os_server,ddll_server,erl_epmd,inet_db,pg2],undefined,{kernel,[]},[application,application_controller,application_master,application_starter,auth,code,code_aux,code_server,code_server_int,dist_util,erl_boot_server,erl_distribution,erl_open_port,erl_prim_loader,erl_reply,erlang,error_handler,error_logger,file,global,global_group,global_search,group,heart,inet6_tcp,inet6_tcp_dist,inet6_udp,inet_config,inet_hosts,inet_gethost_native,inet_tcp_dist,otp_pre_init,init,kernel,kernel_config,net,net_adm,net_kernel,os,ram_file,rpc,user,user_drv,user_sup,disk_log,disk_log_1,disk_log_server,disk_log_sup,dist_ac,erl_atom_cache,erl_ddll,erl_epmd,erl_external,erts_debug,fixtable_server,gen_tcp,gen_udp,prim_inet,inet,inet_db,inet_dns,inet_parse,inet_res,inet_tcp,inet_udp,pg2,seq_trace,socks5,socks5_auth,socks5_tcp,socks5_udp,wrap_log_reader,otp_ring0],[],infinity,infinity},normal]
+y(3) [<0.5.0>,<0.6.0>,{appl_data,kernel,[application_controller,erl_reply,auth,boot_server,code_server,disk_log_server,disk_log_sup,erl_prim_loader,error_logger,file_server,fixtable_server,global_group,global_name_server,heart,init,kernel_config,kernel_sup,net_kernel,net_sup,rex,user,os_server,ddll_server,erl_epmd,inet_db,pg2],undefined,{kernel,[]},[application,application_controller,application_master,application_starter,auth,code,code_aux,code_server,code_server_int,dist_util,erl_boot_server,erl_distribution,erl_open_port,erl_prim_loader,erl_reply,erlang,error_handler,error_logger,file,global,global_group,global_search,group,heart,inet6_tcp,inet6_tcp_dist,inet6_udp,inet_config,inet_hosts,inet_gethost_native,inet_tcp_dist,otp_pre_init,init,kernel,kernel_config,net,net_adm,net_kernel,os,ram_file,rpc,user,user_drv,user_sup,disk_log,disk_log_1,disk_log_server,disk_log_sup,dist_ac,erl_atom_cache,erl_ddll,erl_epmd,erl_external,erts_debug,fixtable_server,gen_tcp,gen_udp,prim_inet,inet,inet_db,inet_dns,inet_parse,inet_res,inet_tcp,inet_udp,pg2,seq_trace,socks5,socks5_auth,socks5_tcp,socks5_udp,wrap_log_reader,erl_init],[],infinity,infinity},normal]
--------------------------------------------------
<0.8.0> Waiting.
Spawned as: application_master:start_it/4
@@ -652,7 +652,7 @@ Not alive
Loaded Modules Information
--------------------------------------------------
-otp_ring0 448
+erl_init 448
init 28000
prim_inet 34800
erl_prim_loader 14187
@@ -1202,7 +1202,7 @@ udp_error
inet_async
inet_reply
empty_out_q
-otp_ring0
+erl_init
boot
init
run
@@ -1285,7 +1285,7 @@ sleep
start_prim_loader
erl_prim_loader
set_path
-'can not start loader'
+'cannot start loader'
add_to_kernel
prim_load_flags
'-loader'
@@ -1304,7 +1304,7 @@ path_flags
'-pz'
get_boot
not_found
-'can not get bootfile'
+'cannot get bootfile'
'bootfile format error'
get_file
script
@@ -1349,7 +1349,7 @@ extension
'-boot/1-fun-0-'
'-bs2ss/1-fun-0-'
'-bs2as/1-fun-0-'
-'can not load'
+'cannot load'
'unexpected command in bootfile'
prim_inet
open
@@ -3071,7 +3071,7 @@ change_group
change_time
master
start_slave
-'can not get remote filer '
+'cannot get remote filer '
start_relay
relay
'Port controlling ~w terminated in file_server'
diff --git a/lib/observer/test/observer_SUITE.erl b/lib/observer/test/observer_SUITE.erl
index 40f5d44847..75336cedcc 100644
--- a/lib/observer/test/observer_SUITE.erl
+++ b/lib/observer/test/observer_SUITE.erl
@@ -301,7 +301,7 @@ table_win(Config) when is_list(Config) ->
Notebook = setup_whitebox_testing(),
Parent = get_top_level_parent(Notebook),
TObj = observer_tv_table:start_link(Parent, [{node,node()}, {type,ets}, {table,#tab{name=foo, id=Table}}]),
- %% Modal can not test edit..
+ %% Modal cannot test edit..
%% TPid = wx_object:get_pid(TObj),
%% TPid ! #wx{event=#wxList{type=command_list_item_activated, itemIndex=12}},
timer:sleep(3000),
diff --git a/lib/odbc/c_src/Makefile.in b/lib/odbc/c_src/Makefile.in
index 784e73c47e..294d832797 100644
--- a/lib/odbc/c_src/Makefile.in
+++ b/lib/odbc/c_src/Makefile.in
@@ -52,7 +52,7 @@ RELSYSDIR = $(RELEASE_PATH)/lib/odbc-$(VSN)
# Target Specs
# ----------------------------------------------------
EI_ROOT = $(ERL_TOP)/lib/erl_interface
-EI_INCLUDE = -I$(EI_ROOT)/include
+EI_INCLUDE = -I$(EI_ROOT)/include -I$(EI_ROOT)/include/$(TARGET)
ifeq ($(findstring win32,$(TARGET)),win32)
EI_LIB = -lerl_interface_md -lei_md
ENTRY_OBJ=$(ERL_TOP)/erts/obj/$(TARGET)/port_entry.o
diff --git a/lib/odbc/c_src/odbcserver.c b/lib/odbc/c_src/odbcserver.c
index 8c799f6ff1..fb4f61417e 100644
--- a/lib/odbc/c_src/odbcserver.c
+++ b/lib/odbc/c_src/odbcserver.c
@@ -2749,6 +2749,11 @@ static diagnos get_diagnos(SQLSMALLINT handleType, SQLHANDLE handle, Boolean ext
errmsg_buffer_size = errmsg_buffer_size - errmsg_size;
acc_errmsg_size = acc_errmsg_size + errmsg_size;
current_errmsg_pos = current_errmsg_pos + errmsg_size;
+ } else if(result == SQL_SUCCESS_WITH_INFO && errmsg_size >= errmsg_buffer_size) {
+ memcpy(diagnos.sqlState, current_sql_state, SQL_STATE_SIZE);
+ diagnos.nativeError = nativeError;
+ acc_errmsg_size = errmsg_buffer_size;
+ break;
} else {
break;
}
diff --git a/lib/odbc/configure.in b/lib/odbc/configure.in
index 2dec6e5abf..c5cf2786ca 100644
--- a/lib/odbc/configure.in
+++ b/lib/odbc/configure.in
@@ -21,12 +21,6 @@ dnl
dnl define([AC_CACHE_LOAD], )dnl
dnl define([AC_CACHE_SAVE], )dnl
-if test "x$no_recursion" != "xyes" -a "x$OVERRIDE_CONFIG_CACHE" = "x"; then
- # We do not want to use a common cache!
- cache_file=/dev/null
-fi
-
-
dnl Process this file with autoconf to produce a configure script.
AC_INIT(c_src/odbcserver.c)
diff --git a/lib/odbc/doc/src/databases.xml b/lib/odbc/doc/src/databases.xml
index a060f87e46..614c327a46 100644
--- a/lib/odbc/doc/src/databases.xml
+++ b/lib/odbc/doc/src/databases.xml
@@ -65,7 +65,7 @@
<p>Another obstacle is that some drivers do not support scrollable
cursors which has the effect that the only way to traverse the
result set is sequentially, with next, from the first row to the
- last, and once you pass a row you can not go back. This means
+ last, and once you pass a row you cannot go back. This means
that some functions in the interface will not work together with
certain drivers. A similar problem is that not all drivers
support "row count" for select queries, hence resulting in that
diff --git a/lib/odbc/doc/src/error_handling.xml b/lib/odbc/doc/src/error_handling.xml
index 4c80cff12f..8d794d2c99 100644
--- a/lib/odbc/doc/src/error_handling.xml
+++ b/lib/odbc/doc/src/error_handling.xml
@@ -138,7 +138,7 @@
should be run through the erlang port was not compiled for
your platform.</item>
<item>Errors discovered by the ODBC driver - If calls to the
- ODBC-driver fails due to circumstances that can not be
+ ODBC-driver fails due to circumstances that cannot be
controlled by the Erlang ODBC application programmer, an
error string will be dug up from the driver. This string
will be the <c>Reason</c> in the <c>{error, Reason} </c>
diff --git a/lib/odbc/doc/src/getting_started.xml b/lib/odbc/doc/src/getting_started.xml
index f2ff8b8993..b27754ff22 100644
--- a/lib/odbc/doc/src/getting_started.xml
+++ b/lib/odbc/doc/src/getting_started.xml
@@ -50,7 +50,7 @@
and paths to appropriate values. This may differ a lot
between different os's, databases and ODBC drivers. This
is a configuration problem related to the third party product
- and hence we can not give you a standard solution in this guide.</item>
+ and hence we cannot give you a standard solution in this guide.</item>
<item>The Erlang ODBC application consists of both <c>Erlang</c>
and <c>C</c> code. The <c>C</c> code is delivered as a
precompiled executable for windows, solaris and linux (SLES10) in the commercial
diff --git a/lib/odbc/doc/src/notes_history.xml b/lib/odbc/doc/src/notes_history.xml
index 22a92f67cd..d0c0a472e7 100644
--- a/lib/odbc/doc/src/notes_history.xml
+++ b/lib/odbc/doc/src/notes_history.xml
@@ -63,7 +63,7 @@
<list type="bulleted">
<item>
<p>The erlang odbc process will now die normally if a
- connection can not be established. No connection no
+ connection cannot be established. No connection no
process it is expected. And as the client has already
received the error message that would be the reason with
which the erlang process would be stopped, the supervisor
@@ -193,7 +193,7 @@
</item>
<item>
<p>The erlang odbc process will now die normally if a
- connection can not be established. No connection no
+ connection cannot be established. No connection no
process it is expected. And as the client has already
received the error message that would be the reason with
which the erlang process would be stopped, the supervisor
diff --git a/lib/odbc/doc/src/odbc.xml b/lib/odbc/doc/src/odbc.xml
index 4bb1f035f9..1a3063e6ce 100644
--- a/lib/odbc/doc/src/odbc.xml
+++ b/lib/odbc/doc/src/odbc.xml
@@ -29,7 +29,7 @@
<date></date>
<rev></rev>
</header>
- <module>odbc</module>
+ <module since="">odbc</module>
<modulesummary>Erlang ODBC application</modulesummary>
<description>
<p>This application provides an Erlang interface to communicate
@@ -130,8 +130,8 @@
</section>
<funcs>
<func>
- <name>commit(Ref, CommitMode) -></name>
- <name>commit(Ref, CommitMode, TimeOut) -> ok | {error, Reason} </name>
+ <name since="">commit(Ref, CommitMode) -></name>
+ <name since="">commit(Ref, CommitMode, TimeOut) -> ok | {error, Reason} </name>
<fsummary>Commits or rollbacks a transaction. </fsummary>
<type>
<v>Ref = connection_reference() </v>
@@ -145,7 +145,7 @@
</desc>
</func>
<func>
- <name>connect(ConnectStr, Options) -> {ok, Ref} | {error, Reason} </name>
+ <name since="">connect(ConnectStr, Options) -> {ok, Ref} | {error, Reason} </name>
<fsummary>Opens a connection to the database. </fsummary>
<type>
<v>ConnectStr = string()</v>
@@ -236,7 +236,7 @@
</desc>
</func>
<func>
- <name>disconnect(Ref) -> ok | {error, Reason} </name>
+ <name since="">disconnect(Ref) -> ok | {error, Reason} </name>
<fsummary>Closes a connection to a database. </fsummary>
<type>
<v>Ref = connection_reference()</v>
@@ -246,7 +246,7 @@
<p>Closes a connection to a database. This will also
terminate all processes that may have been spawned
when the connection was opened. This call will always succeed.
- If the connection can not be disconnected gracefully it will
+ If the connection cannot be disconnected gracefully it will
be brutally killed. However you may receive an error message
as result if you try to disconnect a connection started by another
process.
@@ -255,8 +255,8 @@
</desc>
</func>
<func>
- <name>describe_table(Ref, Table) -> </name>
- <name>describe_table(Ref, Table, Timeout) -> {ok, Description} | {error, Reason} </name>
+ <name since="">describe_table(Ref, Table) -> </name>
+ <name since="">describe_table(Ref, Table, Timeout) -> {ok, Description} | {error, Reason} </name>
<fsummary>Queries the database to find out the data types of the columns of the table <c>Table</c>. </fsummary>
<type>
<v>Ref = connection_reference()</v>
@@ -271,8 +271,8 @@
</desc>
</func>
<func>
- <name>first(Ref) -></name>
- <name>first(Ref, Timeout) -> {selected, ColNames, Rows} | {error, Reason} </name>
+ <name since="">first(Ref) -></name>
+ <name since="">first(Ref, Timeout) -> {selected, ColNames, Rows} | {error, Reason} </name>
<fsummary>Returns the first row of the result set and positions a cursor at this row.</fsummary>
<type>
<v>Ref = connection_reference()</v>
@@ -287,8 +287,8 @@
</desc>
</func>
<func>
- <name>last(Ref) -></name>
- <name>last(Ref, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name>
+ <name since="">last(Ref) -></name>
+ <name since="">last(Ref, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name>
<fsummary>Returns the last row of the result set and positions a cursor at this row. </fsummary>
<type>
<v>Ref = connection_reference()</v>
@@ -303,8 +303,8 @@
</desc>
</func>
<func>
- <name>next(Ref) -> </name>
- <name>next(Ref, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name>
+ <name since="">next(Ref) -> </name>
+ <name since="">next(Ref, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name>
<fsummary>Returns the next row of the result set relative the current cursor position and positions the cursor at this row. </fsummary>
<type>
<v>Ref = connection_reference()</v>
@@ -325,8 +325,8 @@
</desc>
</func>
<func>
- <name>param_query(Ref, SQLQuery, Params) -> </name>
- <name>param_query(Ref, SQLQuery, Params, TimeOut) -> ResultTuple | {error, Reason} </name>
+ <name since="">param_query(Ref, SQLQuery, Params) -> </name>
+ <name since="">param_query(Ref, SQLQuery, Params, TimeOut) -> ResultTuple | {error, Reason} </name>
<fsummary>Executes a parameterized SQL query.</fsummary>
<type>
<v>Ref = connection_reference()</v>
@@ -353,8 +353,8 @@
</desc>
</func>
<func>
- <name>prev(Ref) -> </name>
- <name>prev(ConnectionReference, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name>
+ <name since="">prev(Ref) -> </name>
+ <name since="">prev(ConnectionReference, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name>
<fsummary>Returns the previous row of the result set relative the current cursor position and positions the cursor at this row. </fsummary>
<type>
<v>Ref = connection_reference()</v>
@@ -371,8 +371,8 @@
</func>
<func>
- <name>start() -> </name>
- <name>start(Type) -> ok | {error, Reason}</name>
+ <name since="">start() -> </name>
+ <name since="">start(Type) -> ok | {error, Reason}</name>
<fsummary>Starts the odb application. </fsummary>
<type>
@@ -389,7 +389,7 @@
</func>
<func>
- <name>stop() -> ok </name>
+ <name since="">stop() -> ok </name>
<fsummary> Stops the odbc application.</fsummary>
<desc>
@@ -400,8 +400,8 @@
</func>
<func>
- <name>sql_query(Ref, SQLQuery) -> </name>
- <name>sql_query(Ref, SQLQuery, TimeOut) -> ResultTuple | [ResultTuple] |{error, Reason}</name>
+ <name since="">sql_query(Ref, SQLQuery) -> </name>
+ <name since="">sql_query(Ref, SQLQuery, TimeOut) -> ResultTuple | [ResultTuple] |{error, Reason}</name>
<fsummary>Executes a SQL query or a batch of SQL queries. If it is a SELECT query the result set is returned, on the format<c>{selected, ColNames, Rows}</c>. For other query types the tuple <c>{updated, NRows}</c>is returned, and for batched queries, if the driver supports them, this function can also return a list of result tuples.</fsummary>
<type>
<v>Ref = connection_reference()</v>
@@ -429,8 +429,8 @@
</desc>
</func>
<func>
- <name>select_count(Ref, SelectQuery) -> </name>
- <name>select_count(Ref, SelectQuery, TimeOut) -> {ok, NrRows} | {error, Reason} </name>
+ <name since="">select_count(Ref, SelectQuery) -> </name>
+ <name since="">select_count(Ref, SelectQuery, TimeOut) -> {ok, NrRows} | {error, Reason} </name>
<fsummary>Executes a SQL SELECT query and associates the result set with the connection. A cursor is positioned before the first row in the result set and the tuple <c>{ok, NrRows}</c>is returned. </fsummary>
<type>
<v>Ref = connection_reference()</v>
@@ -453,8 +453,8 @@
</desc>
</func>
<func>
- <name>select(Ref, Position, N) -></name>
- <name>select(Ref, Position, N, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name>
+ <name since="">select(Ref, Position, N) -></name>
+ <name since="">select(Ref, Position, N, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name>
<fsummary>Selects <c>N</c>consecutive rows of the result set.</fsummary>
<type>
<v>Ref = connection_reference()</v>
diff --git a/lib/odbc/test/odbc_connect_SUITE.erl b/lib/odbc/test/odbc_connect_SUITE.erl
index 261dfc6f20..94ca62b3fb 100644
--- a/lib/odbc/test/odbc_connect_SUITE.erl
+++ b/lib/odbc/test/odbc_connect_SUITE.erl
@@ -259,7 +259,7 @@ not_exist_db(_Config) ->
%%-------------------------------------------------------------------------
no_c_executable() ->
- [{doc,"Test what happens if the port-program can not be found"}].
+ [{doc,"Test what happens if the port-program cannot be found"}].
no_c_executable(_Config) ->
process_flag(trap_exit, true),
Dir = filename:nativename(filename:join(code:priv_dir(odbc),
diff --git a/lib/os_mon/Makefile b/lib/os_mon/Makefile
index 1eff8a785a..40ce94e0c7 100644
--- a/lib/os_mon/Makefile
+++ b/lib/os_mon/Makefile
@@ -23,11 +23,7 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
#
# Macros
#
-ifeq ($(findstring win32,$(TARGET)),win32)
-SUB_DIRECTORIES = src c_src mibs doc/src
-else
-SUB_DIRECTORIES = src c_src mibs doc/src
-endif
+SUB_DIRECTORIES = src c_src doc/src
include vsn.mk
VSN = $(OS_MON_VSN)
diff --git a/lib/os_mon/doc/src/Makefile b/lib/os_mon/doc/src/Makefile
index 354f8ed26b..8e9a4c333c 100644
--- a/lib/os_mon/doc/src/Makefile
+++ b/lib/os_mon/doc/src/Makefile
@@ -39,7 +39,6 @@ XML_APPLICATION_FILES = ref_man.xml
XML_REF3_FILES = cpu_sup.xml \
disksup.xml \
memsup.xml \
- os_mon_mib.xml \
os_sup.xml \
nteventlog.xml
diff --git a/lib/os_mon/doc/src/cpu_sup.xml b/lib/os_mon/doc/src/cpu_sup.xml
index bada165a06..b7adb2bcd2 100644
--- a/lib/os_mon/doc/src/cpu_sup.xml
+++ b/lib/os_mon/doc/src/cpu_sup.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>cpu_sup</module>
+ <module since="">cpu_sup</module>
<modulesummary>A CPU Load and CPU Utilization Supervisor Process</modulesummary>
<description>
<p><c>cpu_sup</c> is a process which supervises the CPU load
@@ -76,7 +76,7 @@
</description>
<funcs>
<func>
- <name>nprocs() -> UnixProcesses | {error, Reason}</name>
+ <name since="">nprocs() -> UnixProcesses | {error, Reason}</name>
<fsummary>Get the number of UNIX processes running on this host</fsummary>
<type>
<v>UnixProcesses = int()</v>
@@ -90,7 +90,7 @@
</desc>
</func>
<func>
- <name>avg1() -> SystemLoad | {error, Reason}</name>
+ <name since="">avg1() -> SystemLoad | {error, Reason}</name>
<fsummary>Get the system load average for the last minute</fsummary>
<type>
<v>SystemLoad = int()</v>
@@ -104,7 +104,7 @@
</desc>
</func>
<func>
- <name>avg5() -> SystemLoad | {error, Reason}</name>
+ <name since="">avg5() -> SystemLoad | {error, Reason}</name>
<fsummary>Get the system load average for the last five minutes</fsummary>
<type>
<v>SystemLoad = int()</v>
@@ -118,7 +118,7 @@
</desc>
</func>
<func>
- <name>avg15() -> SystemLoad | {error, Reason}</name>
+ <name since="">avg15() -> SystemLoad | {error, Reason}</name>
<fsummary>Get the system load average for the last fifteen minutes</fsummary>
<type>
<v>SystemLoad = int()</v>
@@ -132,7 +132,7 @@
</desc>
</func>
<func>
- <name>util() -> CpuUtil | {error, Reason}</name>
+ <name since="">util() -> CpuUtil | {error, Reason}</name>
<fsummary>Get the CPU utilization</fsummary>
<type>
<v>CpuUtil = float()</v>
@@ -156,7 +156,7 @@
</desc>
</func>
<func>
- <name>util(Opts) -> UtilSpec | {error, Reason}</name>
+ <name since="">util(Opts) -> UtilSpec | {error, Reason}</name>
<fsummary>Get the CPU utilization</fsummary>
<type>
<v>Opts = [detailed | per_cpu]</v>
diff --git a/lib/os_mon/doc/src/disksup.xml b/lib/os_mon/doc/src/disksup.xml
index 610ef2c907..116a6dfd19 100644
--- a/lib/os_mon/doc/src/disksup.xml
+++ b/lib/os_mon/doc/src/disksup.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>disksup</module>
+ <module since="">disksup</module>
<modulesummary>A Disk Supervisor Process</modulesummary>
<description>
<p><c>disksup</c> is a process which supervises the available disk
@@ -92,7 +92,7 @@
</section>
<funcs>
<func>
- <name>get_disk_data() -> [DiskData]</name>
+ <name since="">get_disk_data() -> [DiskData]</name>
<fsummary>Get data for the disks in the system</fsummary>
<type>
<v>DiskData = {Id, KByte, Capacity}</v>
@@ -112,7 +112,7 @@
</desc>
</func>
<func>
- <name>get_check_interval() -> MS</name>
+ <name since="">get_check_interval() -> MS</name>
<fsummary>Get time interval, in milliseconds, for the periodic disk space check</fsummary>
<type>
<v>MS = int()</v>
@@ -123,7 +123,7 @@
</desc>
</func>
<func>
- <name>set_check_interval(Minutes) -> ok</name>
+ <name since="">set_check_interval(Minutes) -> ok</name>
<fsummary>Set time interval, in minutes, for the periodic disk space check</fsummary>
<type>
<v>Minutes = int()>=1</v>
@@ -138,7 +138,7 @@
</desc>
</func>
<func>
- <name>get_almost_full_threshold() -> Percent</name>
+ <name since="">get_almost_full_threshold() -> Percent</name>
<fsummary>Get threshold, in percent, for disk space utilization</fsummary>
<type>
<v>Percent = int()</v>
@@ -148,7 +148,7 @@
</desc>
</func>
<func>
- <name>set_almost_full_threshold(Float) -> ok</name>
+ <name since="">set_almost_full_threshold(Float) -> ok</name>
<fsummary>Set threshold, as percentage represented by a float, for disk space utilization</fsummary>
<type>
<v>Float = float(), 0=&lt;Float=&lt;1</v>
diff --git a/lib/os_mon/doc/src/memsup.xml b/lib/os_mon/doc/src/memsup.xml
index c669e4670a..51c78b07c2 100644
--- a/lib/os_mon/doc/src/memsup.xml
+++ b/lib/os_mon/doc/src/memsup.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>memsup</module>
+ <module since="">memsup</module>
<modulesummary>A Memory Supervisor Process</modulesummary>
<description>
<p><c>memsup</c> is a process which supervises the memory usage for
@@ -127,7 +127,7 @@
</section>
<funcs>
<func>
- <name>get_memory_data() -> {Total,Allocated,Worst}</name>
+ <name since="">get_memory_data() -> {Total,Allocated,Worst}</name>
<fsummary>Get data for the memory in the system</fsummary>
<type>
<v>Total = Allocated = int()</v>
@@ -155,7 +155,7 @@
</desc>
</func>
<func>
- <name>get_system_memory_data() -> MemDataList</name>
+ <name since="">get_system_memory_data() -> MemDataList</name>
<fsummary>Get system dependent memory data</fsummary>
<type>
<v>MemDataList = [{Tag, Size}]</v>
@@ -216,7 +216,7 @@
</desc>
</func>
<func>
- <name>get_os_wordsize() -> Wordsize</name>
+ <name since="">get_os_wordsize() -> Wordsize</name>
<fsummary>Get the wordsize of running os.</fsummary>
<type>
<v>Wordsize = 32 | 64 | unsupported_os</v>
@@ -226,7 +226,7 @@
</desc>
</func>
<func>
- <name>get_check_interval() -> MS</name>
+ <name since="">get_check_interval() -> MS</name>
<fsummary>Get time interval, in milliseconds, for the periodic memory check</fsummary>
<type>
<v>MS = int()</v>
@@ -237,7 +237,7 @@
</desc>
</func>
<func>
- <name>set_check_interval(Minutes) -> ok</name>
+ <name since="">set_check_interval(Minutes) -> ok</name>
<fsummary>Set time interval, in minutes, for the periodic memory check</fsummary>
<type>
<v>Minutes = int()>0</v>
@@ -252,7 +252,7 @@
</desc>
</func>
<func>
- <name>get_procmem_high_watermark() -> int()</name>
+ <name since="">get_procmem_high_watermark() -> int()</name>
<fsummary>Get threshold, in percent, for process memory allocation</fsummary>
<desc>
<p>Returns the threshold, in percent, for process memory
@@ -260,7 +260,7 @@
</desc>
</func>
<func>
- <name>set_procmem_high_watermark(Float) -> ok</name>
+ <name since="">set_procmem_high_watermark(Float) -> ok</name>
<fsummary>Set threshold, as percentage represented by a float, for process memory allocation</fsummary>
<desc>
<p>Changes the threshold, given as a float, for process memory
@@ -273,7 +273,7 @@
</desc>
</func>
<func>
- <name>get_sysmem_high_watermark() -> int()</name>
+ <name since="">get_sysmem_high_watermark() -> int()</name>
<fsummary>Get threshold, in percent, for system memory allocation</fsummary>
<desc>
<p>Returns the threshold, in percent, for system memory
@@ -281,7 +281,7 @@
</desc>
</func>
<func>
- <name>set_sysmem_high_watermark(Float) -> ok</name>
+ <name since="">set_sysmem_high_watermark(Float) -> ok</name>
<fsummary>Set threshold, given as a float, for system memory allocation</fsummary>
<desc>
<p>Changes the threshold, given as a float, for system memory
@@ -294,7 +294,7 @@
</desc>
</func>
<func>
- <name>get_helper_timeout() -> Seconds</name>
+ <name since="">get_helper_timeout() -> Seconds</name>
<fsummary>Get the timeout value, in seconds, for memory checks</fsummary>
<type>
<v>Seconds = int()</v>
@@ -304,7 +304,7 @@
</desc>
</func>
<func>
- <name>set_helper_timeout(Seconds) -> ok</name>
+ <name since="">set_helper_timeout(Seconds) -> ok</name>
<fsummary>Set the timeout value, in seconds, for memory checks</fsummary>
<type>
<v>Seconds = int() (>= 1)</v>
diff --git a/lib/os_mon/doc/src/nteventlog.xml b/lib/os_mon/doc/src/nteventlog.xml
index d32427227c..08cf165a24 100644
--- a/lib/os_mon/doc/src/nteventlog.xml
+++ b/lib/os_mon/doc/src/nteventlog.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>nteventlog</module>
+ <module since="">nteventlog</module>
<modulesummary>Interface to Windows Event Log</modulesummary>
<description>
<p><c>nteventlog</c> provides a generic interface to the Windows
@@ -61,8 +61,8 @@
</description>
<funcs>
<func>
- <name>start(Identifier, MFA) -> Result</name>
- <name>start_link(Identifier, MFA) -> Result</name>
+ <name since="">start(Identifier, MFA) -> Result</name>
+ <name since="">start_link(Identifier, MFA) -> Result</name>
<fsummary>Start the NT eventlog server</fsummary>
<type>
<v>Identifier = string() | atom()</v>
@@ -82,7 +82,7 @@
</desc>
</func>
<func>
- <name>stop() -> stopped</name>
+ <name since="">stop() -> stopped</name>
<fsummary>Stop the NT eventlog server</fsummary>
<type>
<v>Result = stopped</v>
diff --git a/lib/os_mon/doc/src/os_mon_app.xml b/lib/os_mon/doc/src/os_mon_app.xml
index 99492a2021..c77a9d0411 100644
--- a/lib/os_mon/doc/src/os_mon_app.xml
+++ b/lib/os_mon/doc/src/os_mon_app.xml
@@ -88,33 +88,6 @@
</section>
<section>
- <title>SNMP MIBs</title>
- <p>The following MIBs are defined in OS_Mon:</p>
- <taglist>
- <tag>OTP-OS-MON-MIB</tag>
- <item>
- <p>This MIB contains objects for instrumentation of disk,
- memory and CPU usage of the nodes in the system.</p>
- </item>
- </taglist>
- <p>The MIB is stored in the <c>mibs</c> directory. It is defined
- in SNMPv2 SMI syntax. An SNMPv1 version of the MIB is delivered
- in the <c>mibs/v1</c> directory.</p>
- <p>The compiled MIB is located under <c>priv/mibs</c>, and
- the generated <c>.hrl</c> file under the <c>include</c> directory.
- To compile a MIB that IMPORTS the <c>OTP-OS-MON-MIB</c>, give
- the option <c>{il, ["os_mon/priv/mibs"]}</c> to the MIB compiler.</p>
- <p>If the MIB should be used in a system, it should be loaded into
- an agent with a call to <c>os_mon_mib:load(Agent)</c>, where
- <c>Agent</c> is the pid or registered name of an SNMP agent. Use
- <c>os_mon_mib:unload(Agent)</c> to unload the MIB.
- The implementation of this MIB uses Mnesia to store a cache with
- data needed, which implicates that Mnesia must be up and running.
- The MIB also use functions defined for the <c>OTP-MIB</c>, thus
- that MIB must be loaded as well.</p>
- </section>
-
- <section>
<title>See Also</title>
<p><seealso marker="cpu_sup">cpu_sup(3)</seealso>,
<seealso marker="disksup">disksup(3)</seealso>,
diff --git a/lib/os_mon/doc/src/os_mon_mib.xml b/lib/os_mon/doc/src/os_mon_mib.xml
deleted file mode 100644
index e995bf3de1..0000000000
--- a/lib/os_mon/doc/src/os_mon_mib.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE erlref SYSTEM "erlref.dtd">
-
-<erlref>
- <header>
- <copyright>
- <year>2004</year><year>2018</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>os_mon_mib</title>
- <prepared>Ingela Andin</prepared>
- <responsible></responsible>
- <docno></docno>
- <date></date>
- <rev></rev>
- </header>
- <module>os_mon_mib</module>
- <modulesummary>Loading and Unloading of OTP-OS-MON-MIB</modulesummary>
- <description>
- <p>Functions for loading and unloading the OTP-OS-MON-MIB into/from
- an SNMP agent. The instrumentation of the OTP-OS-MON-MIB uses
- Mnesia, hence Mnesia must be started prior to loading
- the OTP-OS-MON-MIB.</p>
- <warning>
- <p>This module has been deprecated and will be removed in a furture release.</p>
- </warning>
- </description>
- <funcs>
- <func>
- <name>load(Agent) -> ok | {error, Reason}</name>
- <fsummary>Load the OTP-OS-MON-MIB</fsummary>
- <type>
- <v>Agent = pid() | atom()</v>
- <v>Reason = term()</v>
- </type>
- <desc>
- <p>Loads the OTP-OS-MON-MIB.</p>
- </desc>
- </func>
- <func>
- <name>unload(Agent) -> ok | {error, Reason}</name>
- <fsummary>Unload the OTP-OS-MON-MIB</fsummary>
- <type>
- <v>Agent = pid() | atom() </v>
- <v>Reason = term()</v>
- </type>
- <desc>
- <p>Unloads the OTP-OS-MON-MIB.</p>
- </desc>
- </func>
- </funcs>
-
- <section>
- <title>See Also</title>
- <p><seealso marker="os_mon_app">os_mon(6)</seealso>,
- <seealso marker="snmp:snmp">snmp(3)</seealso></p>
- </section>
-</erlref>
-
diff --git a/lib/os_mon/doc/src/os_sup.xml b/lib/os_mon/doc/src/os_sup.xml
index d517f387b4..4a84165a6c 100644
--- a/lib/os_mon/doc/src/os_sup.xml
+++ b/lib/os_mon/doc/src/os_sup.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>os_sup</module>
+ <module since="">os_sup</module>
<modulesummary>Interface to OS System Messages</modulesummary>
<description>
<p><c>os_sup</c> is a process providing a message passing service
@@ -159,8 +159,8 @@
</section>
<funcs>
<func>
- <name>enable() -> ok | {error, Res}</name>
- <name>enable(Dir, Conf) -> ok | {error, Error}</name>
+ <name since="">enable() -> ok | {error, Res}</name>
+ <name since="">enable(Dir, Conf) -> ok | {error, Error}</name>
<fsummary>Enable the service (Solaris only)</fsummary>
<type>
<v>Dir = Conf = Res = string()</v>
@@ -194,8 +194,8 @@
</desc>
</func>
<func>
- <name>disable() -> ok | {error, Res}</name>
- <name>disable(Dir, Conf) -> ok | {error, Error}</name>
+ <name since="">disable() -> ok | {error, Res}</name>
+ <name since="">disable(Dir, Conf) -> ok | {error, Error}</name>
<fsummary>Disable the service (Solaris only)</fsummary>
<type>
<v>Dir = Conf = Res = string()</v>
diff --git a/lib/os_mon/doc/src/ref_man.xml b/lib/os_mon/doc/src/ref_man.xml
index a8f847a8ba..57dd5c5f0b 100644
--- a/lib/os_mon/doc/src/ref_man.xml
+++ b/lib/os_mon/doc/src/ref_man.xml
@@ -36,7 +36,6 @@
<xi:include href="cpu_sup.xml"/>
<xi:include href="disksup.xml"/>
<xi:include href="memsup.xml"/>
- <xi:include href="os_mon_mib.xml"/>
<xi:include href="os_sup.xml"/>
<xi:include href="nteventlog.xml"/>
</application>
diff --git a/lib/os_mon/mibs/Makefile b/lib/os_mon/mibs/Makefile
deleted file mode 100644
index dbc105ee3d..0000000000
--- a/lib/os_mon/mibs/Makefile
+++ /dev/null
@@ -1,101 +0,0 @@
-#
-# %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%
-#
-include $(ERL_TOP)/make/target.mk
-include $(ERL_TOP)/make/$(TARGET)/otp.mk
-
-# ----------------------------------------------------
-# Application version
-# ----------------------------------------------------
-include ../vsn.mk
-VSN=$(OS_MON_VSN)
-
-# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/os_mon-$(VSN)
-
-# ----------------------------------------------------
-# Target Specs
-# ----------------------------------------------------
-
-MIB_FILES= OTP-OS-MON-MIB.mib
-FUNCS_FILES = OTP-OS-MON-MIB.funcs
-
-BIN_TARGETS= $(MIB_FILES:%.mib=$(SNMP_BIN_TARGET_DIR)/%.bin)
-HRL_TARGETS= $(MIB_FILES:%.mib=$(SNMP_HRL_TARGET_DIR)/%.hrl)
-V1_MIB_FILES= $(MIB_FILES:%.mib=v1/%.mib.v1)
-
-TARGET_FILES= $(SNMP_BIN_TARGET_DIR)/OTP-REG.bin \
- $(SNMP_BIN_TARGET_DIR)/OTP-TC.bin \
- $(SNMP_BIN_TARGET_DIR)/OTP-MIB.bin \
- $(BIN_TARGETS) $(HRL_TARGETS) $(V1_MIB_FILES)
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-SNMP_FLAGS = -I $(SNMP_BIN_TARGET_DIR)
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-
-debug opt: $(TARGET_FILES)
-
-clean:
- rm -f $(TARGET_FILES)
- rm -f core
-
-docs:
-
-OTP_MIBDIR = $(shell if test -d ../../otp_mibs; then echo otp_mibs; \
- else echo sasl; fi)
-
-$(SNMP_BIN_TARGET_DIR)/OTP-REG.bin: $(ERL_TOP)/lib/$(OTP_MIBDIR)/mibs/OTP-REG.mib
- $(snmp_verbose)$(ERLC) -pa $(SNMP_TOOLKIT)/ebin -I $(SNMP_TOOLKIT)/priv/mibs $(SNMP_FLAGS) -o $(SNMP_BIN_TARGET_DIR) $<
-
-$(SNMP_BIN_TARGET_DIR)/OTP-TC.bin: $(ERL_TOP)/lib/$(OTP_MIBDIR)/mibs/OTP-TC.mib
- $(snmp_verbose)$(ERLC) -pa $(SNMP_TOOLKIT)/ebin -I $(SNMP_TOOLKIT)/priv/mibs $(SNMP_FLAGS) -o $(SNMP_BIN_TARGET_DIR) $<
-
-$(SNMP_BIN_TARGET_DIR)/OTP-MIB.bin: $(ERL_TOP)/lib/$(OTP_MIBDIR)/mibs/OTP-MIB.mib
- $(snmp_verbose)$(ERLC) -pa $(SNMP_TOOLKIT)/ebin -I $(SNMP_TOOLKIT)/priv/mibs $(SNMP_FLAGS) -o $(SNMP_BIN_TARGET_DIR) $<
-
-v1/%.mib.v1: %.mib
- $(gen_verbose)$(ERL_TOP)/lib/snmp/bin/snmp-v2tov1 -o $@ $<
-
-$(SNMP_BIN_TARGET_DIR)/OTP-OS-MON-MIB.bin: \
- $(SNMP_BIN_TARGET_DIR)/OTP-REG.bin \
- $(SNMP_BIN_TARGET_DIR)/OTP-MIB.bin \
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_spec: opt
- $(INSTALL_DIR) "$(RELSYSDIR)/mibs"
- $(INSTALL_DIR) "$(RELSYSDIR)/mibs/v1"
- $(INSTALL_DATA) $(MIB_FILES) $(FUNCS_FILES) "$(RELSYSDIR)/mibs"
- $(INSTALL_DATA) $(V1_MIB_FILES) "$(RELSYSDIR)/mibs/v1"
- $(INSTALL_DIR) "$(RELSYSDIR)/include"
- $(INSTALL_DATA) $(HRL_TARGETS) "$(RELSYSDIR)/include"
- $(INSTALL_DIR) "$(RELSYSDIR)/priv/mibs"
- $(INSTALL_DATA) $(BIN_TARGETS) "$(RELSYSDIR)/priv/mibs"
-
-release_docs_spec:
diff --git a/lib/os_mon/mibs/OTP-OS-MON-MIB.funcs b/lib/os_mon/mibs/OTP-OS-MON-MIB.funcs
deleted file mode 100644
index 7ed76517b9..0000000000
--- a/lib/os_mon/mibs/OTP-OS-MON-MIB.funcs
+++ /dev/null
@@ -1,5 +0,0 @@
-{loadMemorySystemWatermark, {os_mon_mib, mem_sys_mark, []}}.
-{loadMemoryErlProcWatermark, {os_mon_mib, mem_proc_mark, []}}.
-{loadTable, {os_mon_mib, load_table, []}}.
-{diskAlmostFullThreshold, {os_mon_mib, disk_threshold, []}}.
-{diskTable, {os_mon_mib, disk_table, []}}.
diff --git a/lib/os_mon/mibs/OTP-OS-MON-MIB.mib b/lib/os_mon/mibs/OTP-OS-MON-MIB.mib
deleted file mode 100644
index e027e96154..0000000000
--- a/lib/os_mon/mibs/OTP-OS-MON-MIB.mib
+++ /dev/null
@@ -1,423 +0,0 @@
---
--- %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%
---
-
-OTP-OS-MON-MIB DEFINITIONS ::= BEGIN
-
-IMPORTS
- MODULE-IDENTITY, NOTIFICATION-TYPE, OBJECT-TYPE,
- Counter32, Gauge32, Integer32, Unsigned32, Counter64
- FROM SNMPv2-SMI
- TEXTUAL-CONVENTION, DisplayString
- FROM SNMPv2-TC
- MODULE-COMPLIANCE, NOTIFICATION-GROUP, OBJECT-GROUP
- FROM SNMPv2-CONF
- otpModules, otpApplications
- FROM OTP-REG
- erlNodeId
- FROM OTP-MIB
- ;
-
-
-otpOsMonModule MODULE-IDENTITY
- LAST-UPDATED "0305090900Z"
- ORGANIZATION "Ericsson"
- CONTACT-INFO
- "Contact: Erlang Support see license agreement for Erlang/OTP."
-
- DESCRIPTION
- "This MIB is part of the OTP MIB. It defines MIB objects
- for the os_mon application in OTP."
-
- REVISION "0508260900Z"
- DESCRIPTION
- "Removed dependeny on EVA."
- REVISION "0305090900Z"
- DESCRIPTION
- "Changed CONTACT-INFO as it was outdated, made it more generic
- to avoid such changes in the future."
-
- REVISION "9807080900Z"
- DESCRIPTION
- "Changed MAX-ACCESS for diskDescr from not-accessible to
- read-only."
-
- REVISION "9801270900Z"
- DESCRIPTION
- "Changed erroneous name of this module to otpOsMonModule."
-
- REVISION "9712010900Z"
- DESCRIPTION
- "Converted to v2 SMI and placed in the OTP tree."
-
- REVISION "9608191700Z"
- DESCRIPTION
- "The initial revision of MIB module OTP-OS-MON-MIB."
- ::= { otpModules 4 }
-
-OTPCounterBasedGauge64 ::= TEXTUAL-CONVENTION
- STATUS current
- DESCRIPTION
- "The CounterBasedGauge64 type represents a non-negative
- integer, which may increase or decrease, but shall never
- exceed a maximum value, nor fall below a minimum value. The
- maximum value can not be greater than 2^64-1
- (18446744073709551615 decimal), and the minimum value can
-
- not be smaller than 0. The value of a CounterBasedGauge64
- has its maximum value whenever the information being modeled
- is greater than or equal to its maximum value, and has its
- minimum value whenever the information being modeled is
- smaller than or equal to its minimum value. If the
- information being modeled subsequently decreases below
- (increases above) the maximum (minimum) value, the
- CounterBasedGauge64 also decreases (increases).
-
- Note that this TC is not strictly supported in SMIv2,
- because the 'always increasing' and 'counter wrap' semantics
- associated with the Counter64 base type are not preserved.
- It is possible that management applications which rely
- solely upon the (Counter64) ASN.1 tag to determine object
- semantics will mistakenly operate upon objects of this type
- as they would for Counter64 objects.
-
- This textual convention represents a limited and short-term
- solution, and may be deprecated as a long term solution is
- defined and deployed to replace it."
- SYNTAX Counter64
-
-otpOsMonMIB OBJECT IDENTIFIER ::= { otpApplications 2 }
-otpOsMonMIBConformance
- OBJECT IDENTIFIER ::= { otpOsMonMIB 1 }
-otpOsMonMIBObjects
- OBJECT IDENTIFIER ::= { otpOsMonMIB 2 }
-otpOsMonMIBAlarms
- OBJECT IDENTIFIER ::= { otpOsMonMIB 4 }
-otpOsMonMIBAlarmsV2
- OBJECT IDENTIFIER ::= { otpOsMonMIBAlarms 0 }
-
-
--- Datatypes
-
--- Managed Objects
-
-load OBJECT IDENTIFIER ::= { otpOsMonMIBObjects 1 }
-disk OBJECT IDENTIFIER ::= { otpOsMonMIBObjects 2 }
-
-loadMemorySystemWatermark OBJECT-TYPE
- SYNTAX Integer32 (0..100)
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "Threshold in percent of the total available system
- memory, which specifies how much memory can be allocated
- by the system before an alarm is sent."
- ::= { load 1 }
-
-loadMemoryErlProcWatermark OBJECT-TYPE
- SYNTAX Integer32 (0..100)
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "Threshold in percent of the total available system
- memory, which specifies how much memory can be allocated
- by one Erlang process before an alarm is sent."
- ::= { load 2 }
-
-loadTable OBJECT-TYPE
- SYNTAX SEQUENCE OF LoadEntry
- MAX-ACCESS not-accessible
- STATUS current
- DESCRIPTION
- "A table with load and memory information
- for each node."
- ::= { load 3 }
-
-loadEntry OBJECT-TYPE
- SYNTAX LoadEntry
- MAX-ACCESS not-accessible
- STATUS current
- DESCRIPTION
- "A conceptual row in the loadTable."
- INDEX { loadErlNodeName }
- ::= { loadTable 1 }
-
-LoadEntry ::= SEQUENCE {
- loadErlNodeName DisplayString,
- loadSystemTotalMemory Gauge32,
- loadSystemUsedMemory Gauge32,
- loadLargestErlProcess DisplayString,
- loadLargestErlProcessUsedMemory Gauge32,
- loadCpuLoad Integer32,
- loadCpuLoad5 Integer32,
- loadCpuLoad15 Integer32,
- loadOsWordsize Unsigned32,
- loadSystemTotalMemory64 OTPCounterBasedGauge64,
- loadSystemUsedMemory64 OTPCounterBasedGauge64,
- loadLargestErlProcessUsedMemory64 OTPCounterBasedGauge64
- }
-
-loadErlNodeName OBJECT-TYPE
- SYNTAX DisplayString
- MAX-ACCESS not-accessible
- STATUS current
- DESCRIPTION
- "The name of the erlang node, e.g. erlnode@host1."
- ::= { loadEntry 1 }
-
-loadSystemTotalMemory OBJECT-TYPE
- SYNTAX Gauge32
- UNITS "bytes"
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "The amount of total memory in the system."
- ::= { loadEntry 2 }
-
-loadSystemUsedMemory OBJECT-TYPE
- SYNTAX Gauge32
- UNITS "bytes"
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "The amount of used memory."
- ::= { loadEntry 3 }
-
-loadLargestErlProcess OBJECT-TYPE
- SYNTAX DisplayString
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "The process identifier (Pid) of the largest Erlang
- process."
- ::= { loadEntry 4 }
-
-loadLargestErlProcessUsedMemory OBJECT-TYPE
- SYNTAX Gauge32
- UNITS "bytes"
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "The amount of memory used by the largest Erlang
- process."
- ::= { loadEntry 5 }
-
-loadCpuLoad OBJECT-TYPE
- SYNTAX Integer32 (0..100)
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "The average load the last minute in percent of the CPU
- where the Erlang node runs."
- ::= { loadEntry 6 }
-
-loadCpuLoad5 OBJECT-TYPE
- SYNTAX Integer32 (0..100)
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "The average load the last 5 minutes in percent of the CPU
- where the Erlang node runs."
- ::= { loadEntry 7}
-
-loadCpuLoad15 OBJECT-TYPE
- SYNTAX Integer32 (0..100)
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "The average load the last 15 minutes in percent of the CPU
- where the Erlang node runs."
- ::= { loadEntry 8}
-
-loadOsWordsize OBJECT-TYPE
- SYNTAX Unsigned32
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "The wordsize of the operating operating system."
- ::= { loadEntry 9 }
-
-loadSystemTotalMemory64 OBJECT-TYPE
- SYNTAX OTPCounterBasedGauge64
- UNITS "bytes"
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "The amount of total memory in the system for 64-bit operating system."
- ::= { loadEntry 10 }
-
-loadSystemUsedMemory64 OBJECT-TYPE
- SYNTAX OTPCounterBasedGauge64
- UNITS "bytes"
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "The amount of used memory for 64-bit operating system."
- ::= { loadEntry 11 }
-
-loadLargestErlProcessUsedMemory64 OBJECT-TYPE
- SYNTAX OTPCounterBasedGauge64
- UNITS "bytes"
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "The amount of memory used by the largest Erlang
- process for 64-bit operating system.."
- ::= { loadEntry 12 }
-
-diskAlmostFullThreshold OBJECT-TYPE
- SYNTAX Integer32 (0..100)
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "Threshold in percent of the available disk space,
- which specifies how much disk space can be used by
- a disk or partition before an alarm is sent."
- ::= { disk 1 }
-
-diskTable OBJECT-TYPE
- SYNTAX SEQUENCE OF DiskEntry
- MAX-ACCESS not-accessible
- STATUS current
- DESCRIPTION
- "A table with all local disks or partitions on each
- node."
- ::= { disk 2 }
-
-diskEntry OBJECT-TYPE
- SYNTAX DiskEntry
- MAX-ACCESS not-accessible
- STATUS current
- DESCRIPTION
- "A conceptual row in the diskTable."
- INDEX { erlNodeId, diskId }
- ::= { diskTable 1 }
-
-DiskEntry ::= SEQUENCE {
- diskId Integer32,
- diskDescr DisplayString,
- diskKBytes Gauge32,
- diskCapacity Integer32
- }
-
-diskId OBJECT-TYPE
- SYNTAX Integer32
- MAX-ACCESS not-accessible
- STATUS current
- DESCRIPTION
- "An integer that uniquely identifies the disk
- or partition."
- ::= { diskEntry 1 }
-
-diskDescr OBJECT-TYPE
- SYNTAX DisplayString
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "A string that identifies the disk or partition."
- ::= { diskEntry 2 }
-
-diskKBytes OBJECT-TYPE
- SYNTAX Gauge32
- UNITS "kbytes"
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "The amount of total disk/partition space. "
- ::= { diskEntry 3 }
-
-diskCapacity OBJECT-TYPE
- SYNTAX Integer32 (0..100)
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "How much of the disk's/partition's total capacity has
- been used, in percent."
- ::= { diskEntry 4 }
-
-
--- conformance information
-
-otpOsMonMIBCompliances
- OBJECT IDENTIFIER ::= { otpOsMonMIBConformance 1 }
-otpOsMonMIBGroups
- OBJECT IDENTIFIER ::= { otpOsMonMIBConformance 2 }
-
-
--- compliance statements
-
-otpOsMonBasicCompliance MODULE-COMPLIANCE
- STATUS current
- DESCRIPTION
- "The compliance statement for SNMPv2 entities which
- implement the OTP-OS-MON-MIB."
- MODULE -- this module
- GROUP loadGroup
- DESCRIPTION
- "This group is mandatory for systems implementing the
- load supervison functionality."
- GROUP loadAlarmsGroup
- DESCRIPTION
- "This group is optional for systems implementing the
- load supervison functionality."
- GROUP diskGroup
- DESCRIPTION
- "This group is mandatory for system implementing the
- disk supervison functionality."
- GROUP diskAlarmsGroup
- DESCRIPTION
- "This group is optional for systems implementing the
- disk supervison functionality."
- ::= { otpOsMonMIBCompliances 1 }
-
-
--- units of conformance
-
-loadGroup OBJECT-GROUP
- OBJECTS { loadMemorySystemWatermark,
- loadMemoryErlProcWatermark,
- loadSystemTotalMemory,
- loadSystemUsedMemory,
- loadLargestErlProcess,
- loadLargestErlProcessUsedMemory,
- loadCpuLoad,
- loadCpuLoad5,
- loadCpuLoad15,
- loadOsWordsize,
- loadSystemTotalMemory64,
- loadSystemUsedMemory64,
- loadLargestErlProcessUsedMemory64}
- STATUS current
- DESCRIPTION
- "A collection of objects providing basic instrumentation
- of the load of the OTP system."
- ::= { otpOsMonMIBGroups 1 }
-
-diskGroup OBJECT-GROUP
- OBJECTS { diskAlmostFullThreshold,
- diskDescr,
- diskKBytes,
- diskCapacity }
- STATUS current
- DESCRIPTION
- "A collection of objects providing basic instrumentation
- of the disks in the OTP system."
- ::= { otpOsMonMIBGroups 3 }
-
-END
diff --git a/lib/os_mon/src/Makefile b/lib/os_mon/src/Makefile
index fc2eb22393..923a31f290 100644
--- a/lib/os_mon/src/Makefile
+++ b/lib/os_mon/src/Makefile
@@ -34,8 +34,7 @@ RELSYSDIR = $(RELEASE_PATH)/lib/os_mon-$(VSN)
# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
-MODULES= disksup memsup cpu_sup os_mon os_mon_mib os_sup os_mon_sysinfo \
- nteventlog
+MODULES= disksup memsup cpu_sup os_mon os_sup os_mon_sysinfo nteventlog
INCLUDE=../include
CSRC=../c_src
diff --git a/lib/os_mon/src/memsup.erl b/lib/os_mon/src/memsup.erl
index 9d6447430d..b69d657aa7 100644
--- a/lib/os_mon/src/memsup.erl
+++ b/lib/os_mon/src/memsup.erl
@@ -701,6 +701,7 @@ get_os_wordsize_with_uname() ->
"sparc64" -> 64;
"amd64" -> 64;
"ppc64" -> 64;
+ "ppc64le" -> 64;
"s390x" -> 64;
_ -> 32
end.
diff --git a/lib/os_mon/src/os_mon.app.src b/lib/os_mon/src/os_mon.app.src
index 8be94f65d4..6c9b0d7576 100644
--- a/lib/os_mon/src/os_mon.app.src
+++ b/lib/os_mon/src/os_mon.app.src
@@ -21,7 +21,7 @@
{application, os_mon,
[{description, "CPO CXC 138 46"},
{vsn, "%VSN%"},
- {modules, [os_mon, os_mon_mib, os_sup,
+ {modules, [os_mon, os_sup,
disksup, memsup, cpu_sup, os_mon_sysinfo, nteventlog]},
{registered, [os_mon_sup, os_mon_sysinfo, disksup, memsup, cpu_sup,
os_sup_server]},
@@ -31,6 +31,4 @@
{start_memsup, true},
{start_os_sup, false}]},
{mod, {os_mon, []}},
- {runtime_dependencies, ["stdlib-2.0","snmp-4.25.1","sasl-2.4",
- "otp_mibs-1.0.9","mnesia-4.12","kernel-3.0",
- "erts-6.0"]}]}.
+ {runtime_dependencies, ["stdlib-2.0","sasl-2.4","kernel-3.0","erts-6.0"]}]}.
diff --git a/lib/os_mon/src/os_mon_mib.erl b/lib/os_mon/src/os_mon_mib.erl
deleted file mode 100644
index 9b5d2fbba6..0000000000
--- a/lib/os_mon/src/os_mon_mib.erl
+++ /dev/null
@@ -1,251 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1996-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(os_mon_mib).
-%%%-----------------------------------------------------------------
-%%% Description: This module implements the OS-MON-MIB.
-%%% The tables are implemented as shadow tables with the module
-%%% snmp_shadow_table. Here the update functions are implemented.
-%%%-----------------------------------------------------------------
-
--include("../../otp_mibs/include/OTP-MIB.hrl").
-
-%% API
--export([load/1, unload/1]).
-
-%% Deprecated API
--export([init/1, stop/1]).
-
--deprecated([{init,1,eventually},
- {stop,1,eventually}]).
-
-%% SNMP instrumentation
--export([load_table/1, load_table/3, disk_table/1, disk_table/3,
- mem_sys_mark/1, mem_proc_mark/1, disk_threshold/1]).
-
-%% SNMP shadow functions
--export([update_load_table/0, update_disk_table/0]).
-
-%% Exported for internal use via rpc
--export([get_load/1, get_disks/1]).
-
-%% Shadow tables
--record(loadTable, {
- loadErlNodeName,
- loadSystemTotalMemory,
- loadSystemUsedMemory,
- loadLargestErlProcess,
- loadLargestErlProcessUsedMemory,
- loadCpuLoad,
- loadCpuLoad5,
- loadCpuLoad15,
- loadOsWordsize,
- loadSystemTotalMemory64,
- loadSystemUsedMemory64,
- loadLargestErlProcessUsedMemory64}).
-
--record(diskTable,
- {key, diskDescr, diskKBytes, diskCapacity}).
-
-%% Shadow argument macros
--define(loadShadowArgs,
- {loadTable, string, record_info(fields, loadTable), 5000,
- fun os_mon_mib:update_load_table/0}).
-
--define(diskShadowArgs,
- {diskTable, {integer, integer}, record_info(fields, diskTable), 5000,
- fun os_mon_mib:update_disk_table/0}).
-
-%% Misc
--record(diskAlloc, {diskDescr, diskId}).
-
-%%%=========================================================================
-%%% API
-%%%=========================================================================
-
-%%-------------------------------------------------------------------------
-%% load(Agent) -> ok | {error, Reason}
-%% Agent - pid() | atom()
-%% Reason - term()
-%% Description: Loads the OTP-OS-MON-MIB
-%%-------------------------------------------------------------------------
-load(Agent) ->
- MibDir = filename:join(code:priv_dir(os_mon), "mibs"),
- snmpa:load_mibs(Agent, [filename:join(MibDir, "OTP-OS-MON-MIB")]).
-
-%%-------------------------------------------------------------------------
-%% unload(Agent) -> ok | {error, Reason}
-%% Agent - pid() | atom()
-%% Reason - term()
-%% Description: Unloads the OTP-OS-MON-MIB
-%%-------------------------------------------------------------------------
-unload(Agent) ->
- snmpa:unload_mibs(Agent, ["OTP-OS-MON-MIB"]).
-
-%% To be backwards compatible
-init(Agent) ->
- load(Agent).
-stop(Agent) ->
- unload(Agent).
-
-%%%=========================================================================
-%%% SNMP instrumentation
-%%%=========================================================================
-load_table(Op) ->
- snmp_shadow_table:table_func(Op, ?loadShadowArgs).
-load_table(Op, RowIndex, Cols) ->
- snmp_shadow_table:table_func(Op, RowIndex, Cols, ?loadShadowArgs).
-
-disk_table(new) ->
- Tab = diskAlloc,
- Storage = ram_copies,
- case lists:member(Tab, mnesia:system_info(tables)) of
- true ->
- case mnesia:table_info(Tab, storage_type) of
- unknown ->
- {atomic, ok}=mnesia:add_table_copy(Tab, node(), Storage);
- Storage ->
- catch delete_all(Tab)
- end;
- false ->
- Nodes = [node()],
- Props = [{type, set},
- {attributes, record_info(fields, diskAlloc)},
- {local_content, true},
- {Storage, Nodes}],
- {atomic, ok} = mnesia:create_table(Tab, Props)
-
- end,
- Rec = #diskAlloc{diskDescr = next_index, diskId = 1},
- ok = mnesia:dirty_write(Rec),
- snmp_shadow_table:table_func(new, ?diskShadowArgs).
-
-disk_table(Op, RowIndex, Cols) ->
- snmp_shadow_table:table_func(Op, RowIndex, Cols, ?diskShadowArgs).
-
-mem_sys_mark(get) ->
- {value, memsup:get_sysmem_high_watermark()};
-mem_sys_mark(_) ->
- ok.
-
-mem_proc_mark(get) ->
- {value, memsup:get_procmem_high_watermark()};
-mem_proc_mark(_) ->
- ok.
-
-disk_threshold(get) ->
- {value, disksup:get_almost_full_threshold()};
-disk_threshold(_) ->
- ok.
-
-%%%=========================================================================
-%%% SNMP shadow functions
-%%%=========================================================================
-update_load_table() ->
- delete_all(loadTable),
- lists:foreach(
- fun(Node) ->
- case rpc:call(Node, os_mon_mib, get_load, [Node]) of
- Load when is_record(Load,loadTable) ->
- ok = mnesia:dirty_write(Load);
- _Else ->
- ok
- end
- end, [node() | nodes()]).
-
-
-update_disk_table() ->
- delete_all(diskTable),
- node_update_disk_table(
- otp_mib:erl_node_table(get_next, [], [?erlNodeName,?erlNodeOutBytes])).
-
-%%%========================================================================
-%%% Exported for internal use via rpc
-%%%========================================================================
-get_load(Node) ->
- {Total, Allocated, PidString, PidAllocated} = case memsup:get_memory_data() of
- {MemTot, MemAlloc, undefined} -> {MemTot, MemAlloc, "undefined", 0};
- {MemTot, MemAlloc, {Pid, PidMem}} -> {MemTot, MemAlloc, pid_to_str(Pid), PidMem}
- end,
- OsWordsize = case memsup:get_os_wordsize() of
- WS when is_integer(WS) -> WS;
- _ -> 0
- end,
- #loadTable{
- loadErlNodeName = atom_to_list(Node),
- loadSystemTotalMemory = mask_int32(Total),
- loadSystemUsedMemory = mask_int32(Allocated),
- loadLargestErlProcess = PidString,
- loadLargestErlProcessUsedMemory = mask_int32(PidAllocated),
- loadCpuLoad = get_cpu_load(avg1),
- loadCpuLoad5 = get_cpu_load(avg5),
- loadCpuLoad15 = get_cpu_load(avg15),
- loadOsWordsize = OsWordsize,
- loadSystemTotalMemory64 = Total,
- loadSystemUsedMemory64 = Allocated,
- loadLargestErlProcessUsedMemory64 = PidAllocated
- }.
-
-mask_int32(Value) -> Value band ((1 bsl 32) - 1).
-
-get_disks(NodeId) ->
- element(1,
- lists:mapfoldl(
- fun({Descr, KByte, Capacity}, DiskId) ->
- {#diskTable{key = {NodeId, DiskId},
- diskDescr = Descr,
- diskKBytes = KByte,
- diskCapacity = Capacity},
- DiskId + 1}
- end, 1, disksup:get_disk_data())).
-
-
-%%%========================================================================
-%%% Internal functions
-%%%========================================================================
-node_update_disk_table([_, endOfTable]) ->
- ok;
-
-node_update_disk_table([{[?erlNodeName | IndexList], NodeStr}, _]) ->
- Disks = rpc:call(list_to_atom(NodeStr), os_mon_mib, get_disks,
- IndexList),
- lists:foreach(fun(Disk) ->
- mnesia:dirty_write(Disk)
- end, Disks),
- node_update_disk_table(otp_mib:erl_node_table(get_next,
- IndexList,
- [?erlNodeName,
- ?erlNodeOutBytes])).
-
-get_cpu_load(X) when X == avg1; X == avg5; X == avg15 ->
- case erlang:round(apply(cpu_sup, X, [])/2.56) of
- Large when Large > 100 ->
- 100;
- Load ->
- Load
- end.
-
-delete_all(Name) -> delete_all(mnesia:dirty_first(Name), Name).
-delete_all('$end_of_table', _Name) -> done;
-delete_all(Key, Name) ->
- Next = mnesia:dirty_next(Name, Key),
- ok = mnesia:dirty_delete({Name, Key}),
- delete_all(Next, Name).
-
-pid_to_str(Pid) -> lists:flatten(io_lib:format("~w", [Pid])).
diff --git a/lib/os_mon/test/Makefile b/lib/os_mon/test/Makefile
index 6ac67e6bae..03c73b95ec 100644
--- a/lib/os_mon/test/Makefile
+++ b/lib/os_mon/test/Makefile
@@ -30,7 +30,6 @@ MODULES= \
disksup_SUITE \
memsup_SUITE \
cpu_sup_SUITE \
- os_mon_mib_SUITE \
os_sup_SUITE \
os_mon_conf
@@ -87,7 +86,6 @@ release_tests_spec: make_emakefile
$(INSTALL_DIR) "$(RELSYSDIR)"
$(INSTALL_DATA) os_mon.spec os_mon.cover os_mon_smoke.spec \
$(EMAKEFILE) $(SOURCE) "$(RELSYSDIR)"
- $(INSTALL_DATA) os_mon_mib_SUITE.cfg "$(RELSYSDIR)"
## tar chf - *_SUITE_data | (cd "$(RELSYSDIR)"; tar xf -)
diff --git a/lib/os_mon/test/os_mon.spec b/lib/os_mon/test/os_mon.spec
index 4b4286b313..d292b258f3 100644
--- a/lib/os_mon/test/os_mon.spec
+++ b/lib/os_mon/test/os_mon.spec
@@ -1,2 +1 @@
{suites,"../os_mon_test",all}.
-{config,"os_mon_mib_SUITE.cfg"}. \ No newline at end of file
diff --git a/lib/os_mon/test/os_mon_mib_SUITE.cfg b/lib/os_mon/test/os_mon_mib_SUITE.cfg
deleted file mode 100644
index a33c23530b..0000000000
--- a/lib/os_mon/test/os_mon_mib_SUITE.cfg
+++ /dev/null
@@ -1,8 +0,0 @@
-%% -*- erlang -*-
-{snmp, [{start_agent,true},
- {users,[{os_mon_mib_test,[snmpm_user_default,[]]}]},
- {managed_agents,[{os_mon_mib_test,
- [os_mon_mib_test, {127,0,0,1}, 4000, []]}]},
- {agent_sysname,"Test os_mon_mibs"},
- {mgr_port,5001}
- ]}.
diff --git a/lib/os_mon/test/os_mon_mib_SUITE.erl b/lib/os_mon/test/os_mon_mib_SUITE.erl
deleted file mode 100644
index f40d5f442c..0000000000
--- a/lib/os_mon/test/os_mon_mib_SUITE.erl
+++ /dev/null
@@ -1,578 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-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(os_mon_mib_SUITE).
-
-%%-----------------------------------------------------------------
-%% This suite can no longer be executed standalone, i.e. it must be
-%% executed with common test. The reason is that ct_snmp is used
-%% instead of the snmp application directly. The suite requires a
-%% config file, os_mon_mib_SUITE.cfg, found in the same directory as
-%% the suite.
-%%
-%% Execute with:
-%% > ct_run -suite os_mon_mib_SUITE -config os_mon_mib_SUITE.cfg
-%%-----------------------------------------------------------------
-
--include_lib("common_test/include/ct.hrl").
--include_lib("os_mon/include/OTP-OS-MON-MIB.hrl").
--include_lib("snmp/include/snmp_types.hrl").
-
-% Test server specific exports
--export([all/0, suite/0, groups/0,
- init_per_suite/1, end_per_suite/1]).
-
-
-% Test cases must be exported.
--export([update_load_table/1]).
-
--export([get_mem_sys_mark/1, get_mem_proc_mark/1, get_disk_threshold/1,
- get_load_table/1, get_disk_table/1,
- real_snmp_request/1, load_unload/1]).
-
--export([sys_tot_mem/1, sys_used_mem/1, large_erl_process/1,
- large_erl_process_mem/1, cpu_load/1, cpu_load5/1, cpu_load15/1,
- os_wordsize/1, sys_tot_mem64/1, sys_used_mem64/1,
- large_erl_process_mem64/1, disk_descr/1, disk_kbytes/1,
- disk_capacity/1]).
-
--export([otp_6351/1, otp_7441/1]).
-
--define(TRAP_UDP, 5000).
--define(AGENT_UDP, 4000).
--define(CONF_FILE_VER, [v2]).
--define(SYS_NAME, "Test os_mon_mibs").
--define(MAX_MSG_SIZE, 484).
--define(ENGINE_ID, "mgrEngine").
--define(MGR_PORT, 5001).
-
-%%---------------------------------------------------------------------
-
-suite() ->
- [{ct_hooks,[ts_install_cth]},
- {timetrap,{minutes,6}},
- {require, snmp_mgr_agent, snmp}].
-
-all() ->
- [load_unload, get_mem_sys_mark, get_mem_proc_mark,
- get_disk_threshold, get_load_table,
- {group, get_next_load_table}, get_disk_table,
- {group, get_next_disk_table}, real_snmp_request,
- update_load_table, {group, tickets}].
-
-groups() ->
- [{tickets, [], [otp_6351, otp_7441]},
- {get_next_load_table, [],
- [sys_tot_mem, sys_used_mem, large_erl_process,
- large_erl_process_mem, cpu_load, cpu_load5, cpu_load15,
- os_wordsize, sys_tot_mem64, sys_used_mem64,
- large_erl_process_mem64]},
- {get_next_disk_table, [],
- [disk_descr, disk_kbytes, disk_capacity]}].
-
-
-%%---------------------------------------------------------------------
-%%--------------------------------------------------------------------
-%% Function: init_per_suite(Config) -> Config
-%% Config - [tuple()]
-%% A list of key/value pairs, holding the test case configuration.
-%% Description: Initiation before the whole suite
-%%
-%% Note: This function is free to add any key/value pairs to the Config
-%% variable, but should NOT alter/remove any existing entries.
-%%--------------------------------------------------------------------
-init_per_suite(Config) ->
- application:start(sasl),
- application:start(mnesia),
- application:start(os_mon),
-
- ok = ct_snmp:start(Config,snmp_mgr_agent),
-
- %% Load the mibs that should be tested
- otp_mib:load(snmp_master_agent),
- os_mon_mib:load(snmp_master_agent),
-
- Config.
-%%--------------------------------------------------------------------
-%% Function: end_per_suite(Config) -> _
-%% Config - [tuple()]
-%% A list of key/value pairs, holding the test case configuration.
-%% Description: Cleanup after the whole suite
-%%--------------------------------------------------------------------
-end_per_suite(Config) ->
- PrivDir = proplists:get_value(priv_dir, Config),
- ConfDir = filename:join(PrivDir,"conf"),
- DbDir = filename:join(PrivDir,"db"),
- MgrDir = filename:join(PrivDir, "mgr"),
-
- %% Uload mibs
- snmpa:unload_mibs(snmp_master_agent,["OTP-OS-MON-MIB"]),
- otp_mib:unload(snmp_master_agent),
-
- %% Clean up
- application:stop(snmp),
- application:stop(mnesia),
- application:stop(os_mon),
-
- del_dir(ConfDir),
- del_dir(DbDir),
- (catch del_dir(MgrDir)),
- ok.
-
-%%---------------------------------------------------------------------
-%% Test cases
-%%---------------------------------------------------------------------
-
-%% Test to unload and the reload the OTP.mib
-load_unload(Config) when is_list(Config) ->
- os_mon_mib:unload(snmp_master_agent),
- os_mon_mib:load(snmp_master_agent),
- ok.
-%%---------------------------------------------------------------------
-
-%% check os_mon_mib:update_load_table error handling
-update_load_table(Config) when is_list(Config) ->
- Node = start_node(),
- ok = rpc:call(Node,application,start,[sasl]),
- ok = rpc:call(Node,application,start,[os_mon]),
- ok = os_mon_mib:update_load_table(),
- rpc:call(Node,application,stop,[os_mon]),
- ok = os_mon_mib:update_load_table(),
- stop_node(Node),
- ok.
-
-%% like update_load_table, when memsup_system_only==true
-otp_6351(Config) when is_list(Config) ->
- Node = start_node(),
- ok = rpc:call(Node,application,start,[sasl]),
- ok = rpc:call(Node,application,load,[os_mon]),
- ok = rpc:call(Node,application,set_env,
- [os_mon,memsup_system_only,true]),
- ok = rpc:call(Node,application,start,[os_mon]),
- Res = rpc:call(Node,os_mon_mib,get_load,[Node]),
- if
- is_tuple(Res), element(1, Res)==loadTable ->
- ok;
- true ->
- ct:fail(Res)
- end,
- rpc:call(Node,application,stop,[os_mon]),
- stop_node(Node),
- ok.
-
-
-%%---------------------------------------------------------------------
-%% Simulates a get call to test the instrumentation function
-%% for the loadMemorySystemWatermark variable.
-get_mem_sys_mark(Config) when is_list(Config) ->
- case os_mon_mib:mem_sys_mark(get) of
- {value, SysMark} when is_integer(SysMark) ->
- ok;
- _ ->
- ct:fail(sys_mark_value_not_integer)
- end.
-%%---------------------------------------------------------------------
-%% Simulates a get call to test the instrumentation function
-%% for the loadMemoryErlProcWatermark variable.
-get_mem_proc_mark(Config) when is_list(Config) ->
- case os_mon_mib:mem_proc_mark(get) of
- {value, ProcMark} when is_integer(ProcMark) ->
- ok;
- _ ->
- ct:fail(proc_mark_value_not_integer)
- end.
-%%---------------------------------------------------------------------
-%% Simulates a get call to test the instrumentation function
-%% for the diskAlmostFullThreshold variable.
-get_disk_threshold(Config) when is_list(Config) ->
- case os_mon_mib:disk_threshold(get) of
- {value, ProcMark} when is_integer(ProcMark) ->
- ok;
- _ ->
- ct:fail(disk_threshold_value_not_integer)
- end.
-%%---------------------------------------------------------------------
-
-%%% Note that when we have a string key, as in loadTable, the
-%%% instrumentation will deal with the [length(String), String]. We
-%%% have to know about this, when short cutting SNMP and calling
-%%% instrumentation functions directly as done in most test cases in
-%%% this test suite
-
-%% Simulates get calls to test the instrumentation function
-%% for the loadTable
-get_load_table(Config) when is_list(Config) ->
-
- NodeStr = atom_to_list(node()),
- NodeLen = length(NodeStr),
-
- {_, _, {Pid, _}} = memsup:get_memory_data(),
- PidStr = lists:flatten(io_lib:format("~w", [Pid])),
- [{value, NodeStr},{value, PidStr}] =
- os_mon_mib:load_table(get, [NodeLen | NodeStr],
- [?loadErlNodeName, ?loadLargestErlProcess]),
-
- Values = os_mon_mib:load_table(get, [NodeLen | NodeStr] ,
- [?loadSystemTotalMemory,
- ?loadSystemUsedMemory,
- ?loadLargestErlProcessUsedMemory,
- ?loadCpuLoad,
- ?loadCpuLoad5,
- ?loadCpuLoad15,
- ?loadOsWordsize,
- ?loadSystemTotalMemory64,
- ?loadSystemUsedMemory64,
- ?loadLargestErlProcessUsedMemory64]),
-
- IsInt = fun({value, Val}) when is_integer(Val) ->
- true;
- (_) ->
- false
- end,
-
- NewValues = lists:filter(IsInt, Values),
-
- case length(NewValues) of
- 10 ->
- ok;
- _ ->
- ct:fail(value_not_integer)
- end,
-
- [{noValue,noSuchInstance}, {noValue,noSuchInstance},
- {noValue,noSuchInstance}, {noValue,noSuchInstance},
- {noValue,noSuchInstance}, {noValue,noSuchInstance},
- {noValue,noSuchInstance}, {noValue,noSuchInstance},
- {noValue,noSuchInstance}, {noValue,noSuchInstance},
- {noValue,noSuchInstance}, {noValue,noSuchInstance}] =
- os_mon_mib:load_table(get, [3, 102, 111, 111],
- [?loadErlNodeName,
- ?loadSystemTotalMemory,
- ?loadSystemUsedMemory,
- ?loadLargestErlProcess,
- ?loadLargestErlProcessUsedMemory,
- ?loadCpuLoad,
- ?loadCpuLoad5,
- ?loadCpuLoad15,
- ?loadOsWordsize,
- ?loadSystemTotalMemory64,
- ?loadSystemUsedMemory64,
- ?loadLargestErlProcessUsedMemory64]),
-
- ok.
-%%---------------------------------------------------------------------
-
-sys_tot_mem(Config) when is_list(Config) ->
- [{[?loadSystemTotalMemory, Len | NodeStr], Mem}] =
- os_mon_mib:load_table(get_next, [], [?loadSystemTotalMemory]),
- Len = length(NodeStr),
- true = lists:member(list_to_atom(NodeStr), [node() | nodes()]),
-
- case Mem of
- Mem when is_integer(Mem) ->
- ok;
- _ ->
- ct:fail(sys_tot_mem_value_not_integer)
- end.
-
-sys_used_mem(Config) when is_list(Config) ->
- [{[?loadSystemUsedMemory, Len | NodeStr], Mem}] =
- os_mon_mib:load_table(get_next,[], [?loadSystemUsedMemory]),
- Len = length(NodeStr),
- true = lists:member(list_to_atom(NodeStr), [node() | nodes()]),
-
- case Mem of
- Mem when is_integer(Mem) ->
- ok;
- _ ->
- ct:fail(sys_used_mem_value_not_integer)
- end.
-
-large_erl_process(Config) when is_list(Config) ->
- {_, _, {Pid, _}} = memsup:get_memory_data(),
- PidStr = lists:flatten(io_lib:format("~w", [Pid])),
- [{[?loadLargestErlProcess, Len | NodeStr], PidStr}] =
- os_mon_mib:load_table(get_next,[], [?loadLargestErlProcess]),
- Len = length(NodeStr),
- true = lists:member(list_to_atom(NodeStr), [node() | nodes()]),
- ok.
-
-large_erl_process_mem(Config) when is_list(Config) ->
-
- [{[?loadLargestErlProcessUsedMemory, Len | NodeStr], Mem}] =
- os_mon_mib:load_table(get_next,[],
- [?loadLargestErlProcessUsedMemory]),
- Len = length(NodeStr),
- true = lists:member(list_to_atom(NodeStr), [node() | nodes()]),
-
- case Mem of
- Mem when is_integer(Mem) ->
- ok;
- _ ->
- ct:fail(erl_pid_mem_value_not_integer)
- end.
-
-cpu_load(Config) when is_list(Config) ->
- [{[?loadCpuLoad, Len | NodeStr], Load}] =
- os_mon_mib:load_table(get_next,[], [?loadCpuLoad]),
- Len = length(NodeStr),
- true = lists:member(list_to_atom(NodeStr), [node() | nodes()]),
-
- case Load of
- Load when is_integer(Load) ->
- ok;
- _ ->
- ct:fail(cpu_load_value_not_integer)
- end.
-
-cpu_load5(Config) when is_list(Config) ->
- [{[?loadCpuLoad5, Len | NodeStr], Load}] =
- os_mon_mib:load_table(get_next,[], [?loadCpuLoad5]),
- Len = length(NodeStr),
- true = lists:member(list_to_atom(NodeStr), [node() | nodes()]),
-
- case Load of
- Load when is_integer(Load) ->
- ok;
- _ ->
- ct:fail(cpu_load5_value_not_integer)
- end.
-
-cpu_load15(Config) when is_list(Config) ->
- [{[?loadCpuLoad15, Len | NodeStr], Load}] =
- os_mon_mib:load_table(get_next,[], [?loadCpuLoad15]),
- Len = length(NodeStr),
- true = lists:member(list_to_atom(NodeStr), [node() | nodes()]),
-
- case Load of
- Load when is_integer(Load) ->
- ok;
- _ ->
- ct:fail(cpu_load15_value_not_integer)
- end.
-
-os_wordsize(Config) when is_list(Config) ->
- [{[?loadOsWordsize, Len | NodeStr], Wordsize}] =
- os_mon_mib:load_table(get_next,[], [?loadOsWordsize]),
- Len = length(NodeStr),
- true = lists:member(list_to_atom(NodeStr), [node() | nodes()]),
-
- case Wordsize of
- Wordsize when is_integer(Wordsize) ->
- ok;
- _ ->
- ct:fail(os_wordsize_value_not_integer)
- end.
-
-sys_tot_mem64(Config) when is_list(Config) ->
- [{[?loadSystemTotalMemory64, Len | NodeStr], Mem}] =
- os_mon_mib:load_table(get_next, [], [?loadSystemTotalMemory64]),
- Len = length(NodeStr),
- true = lists:member(list_to_atom(NodeStr), [node() | nodes()]),
-
- case Mem of
- Mem when is_integer(Mem) ->
- ok;
- _ ->
- ct:fail(sys_tot_mem_value_not_integer)
- end.
-
-sys_used_mem64(Config) when is_list(Config) ->
- [{[?loadSystemUsedMemory64, Len | NodeStr], Mem}] =
- os_mon_mib:load_table(get_next,[], [?loadSystemUsedMemory64]),
- Len = length(NodeStr),
- true = lists:member(list_to_atom(NodeStr), [node() | nodes()]),
-
- case Mem of
- Mem when is_integer(Mem) ->
- ok;
- _ ->
- ct:fail(sys_used_mem_value_not_integer)
- end.
-
-large_erl_process_mem64(Config) when is_list(Config) ->
-
- [{[?loadLargestErlProcessUsedMemory64, Len | NodeStr], Mem}] =
- os_mon_mib:load_table(get_next,[],
- [?loadLargestErlProcessUsedMemory64]),
- Len = length(NodeStr),
- true = lists:member(list_to_atom(NodeStr), [node() | nodes()]),
-
- case Mem of
- Mem when is_integer(Mem) ->
- ok;
- _ ->
- ct:fail(erl_pid_mem_value_not_integer)
- end.
-%%---------------------------------------------------------------------
-%% Simulates get calls to test the instrumentation function
-%% for the diskTable.
-get_disk_table(Config) when is_list(Config) ->
-
- DiskData = disksup:get_disk_data(),
- DiskDataLen = length(DiskData),
-
- if
- DiskDataLen > 0 ->
- [{value, Value}] =
- os_mon_mib:disk_table(get, [1,1], [?diskDescr]),
-
- case is_list(Value) of
- true ->
- ok;
- false ->
- ct:fail(value_not_a_string)
- end,
-
- Values = os_mon_mib:disk_table(get, [1,1],
- [?diskId,
- ?diskKBytes,
- ?diskCapacity]),
-
- IsInt = fun({value, Val}) when is_integer(Val) ->
- true;
- (_) ->
- false
- end,
-
- NewValues = lists:filter(IsInt, Values),
-
- case length(NewValues) of
- 3 ->
- ok;
- _ ->
- ct:fail(value_not_integer)
- end
- end,
-
- [{noValue,noSuchInstance}, {noValue,noSuchInstance},
- {noValue,noSuchInstance}, {noValue,noSuchInstance}] =
- os_mon_mib:disk_table(get, [1, DiskDataLen + 1], [?diskId,
- ?diskDescr,
- ?diskKBytes,
- ?diskCapacity]),
-
- ok.
-
-%%---------------------------------------------------------------------
-
-disk_descr(Config) when is_list(Config) ->
- [{[?diskDescr, 1,1], Descr}] =
- os_mon_mib:disk_table(get_next, [], [?diskDescr]),
-
- case Descr of
- Descr when is_list(Descr) ->
- ok;
- _ ->
- ct:fail(disk_descr_value_not_a_string)
- end.
-
-disk_kbytes(Config) when is_list(Config) ->
- [{[?diskKBytes, 1,1], Kbytes}] =
- os_mon_mib:disk_table(get_next,[], [?diskKBytes]),
-
- case Kbytes of
- Kbytes when is_integer(Kbytes) ->
- ok;
- _ ->
- ct:fail(disk_kbytes_value_not_integer)
- end.
-
-
-disk_capacity(Config) when is_list(Config) ->
- [{[?diskCapacity, 1,1], Capacity}] =
- os_mon_mib:disk_table(get_next,[], [?diskCapacity]),
-
- case Capacity of
- Capacity when is_integer(Capacity) ->
- ok;
- _ ->
- ct:fail(disk_capacity_value_not_integer)
- end.
-
-%%---------------------------------------------------------------------
-%% Starts an snmp manager and sends a real snmp-request. i.e.
-%% sends a udp message on the correct format.
-real_snmp_request(Config) when is_list(Config) ->
- NodStr = atom_to_list(node()),
- Len = length(NodStr),
- {_, _, {Pid, _}} = memsup:get_memory_data(),
- PidStr = lists:flatten(io_lib:format("~w", [Pid])),
- io:format("FOO: ~p~n", [PidStr]),
- ok = snmp_get([?loadEntry ++
- [?loadLargestErlProcess, Len | NodStr]],
- PidStr),
- ok = snmp_get_next([?loadEntry ++
- [?loadSystemUsedMemory, Len | NodStr]],
- ?loadEntry ++ [?loadSystemUsedMemory + 1, Len
- | NodStr], PidStr),
- ok = snmp_set([?loadEntry ++ [?loadLargestErlProcess, Len | NodStr]],
- s, "<0.101.0>", Config),
- ok.
-
-%% Starts an snmp manager and requests total memory. Was previously
-%% integer32 which was errornous on 64 bit machines.
-otp_7441(Config) when is_list(Config) ->
- NodStr = atom_to_list(node()),
- Len = length(NodStr),
- Oids = [Oid|_] = [?loadEntry ++ [?loadSystemTotalMemory, Len | NodStr]],
- {noError,0,[#varbind{oid = Oid, variabletype = 'Unsigned32'}]} =
- ct_snmp:get_values(os_mon_mib_test, Oids, snmp_mgr_agent),
-
- ok.
-
-%%---------------------------------------------------------------------
-%% Internal functions
-%%---------------------------------------------------------------------
-start_node() ->
- Pa = filename:dirname(code:which(?MODULE)),
- {ok,Node} = test_server:start_node(testnisse, slave, [{args, " -pa " ++ Pa}]),
- Node.
-
-stop_node(Node) ->
- test_server:stop_node(Node).
-
-del_dir(Dir) ->
- io:format("Deleting: ~s~n",[Dir]),
- {ok, Files} = file:list_dir(Dir),
- FullPathFiles = lists:map(fun(File) -> filename:join(Dir, File) end,
- Files),
- lists:foreach(fun file:delete/1, FullPathFiles),
- file:del_dir(Dir).
-
-%%---------------------------------------------------------------------
-snmp_get(Oids = [Oid |_], Result) ->
- {noError,0,[#varbind{oid = Oid,
- variabletype = 'OCTET STRING',
- value = Result}]} =
- ct_snmp:get_values(os_mon_mib_test, Oids, snmp_mgr_agent),
- ok.
-
-snmp_get_next(Oids, NextOid, Result) ->
- {noError,0,[#varbind{oid = NextOid,
- variabletype = 'OCTET STRING',
- value = Result}]} =
- ct_snmp:get_next_values(os_mon_mib_test, Oids, snmp_mgr_agent),
- ok.
-
-snmp_set(Oid, ValuType, Value, Config) ->
- {notWritable, _, _} =
- ct_snmp:set_values(os_mon_mib_test, [{Oid, ValuType, Value}],
- snmp_mgr_agent, Config),
- ok.
diff --git a/lib/otp_mibs/AUTHORS b/lib/otp_mibs/AUTHORS
deleted file mode 100644
index 3f570082f4..0000000000
--- a/lib/otp_mibs/AUTHORS
+++ /dev/null
@@ -1,8 +0,0 @@
-Original Authors and Contributors:
-
-Martin Bj�rklund
-Lars Thorsen
-Claes Wikstr�m
-Kent Boortz
-Bj�rn Gustavsson
-Ingela Anderton - Created otp_mibs app. to eliminate SASL's SNMP dependence.
diff --git a/lib/otp_mibs/doc/src/Makefile b/lib/otp_mibs/doc/src/Makefile
deleted file mode 100644
index 22c3c127ac..0000000000
--- a/lib/otp_mibs/doc/src/Makefile
+++ /dev/null
@@ -1,116 +0,0 @@
-#
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2003-2018. All 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_TOP)/make/target.mk
-include $(ERL_TOP)/make/$(TARGET)/otp.mk
-
-# ----------------------------------------------------
-# Application version
-# ----------------------------------------------------
-include ../../vsn.mk
-VSN=$(OTP_MIBS_VSN)
-APPLICATION=otp_mibs
-
-# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-# ----------------------------------------------------
-# Target Specs
-# ----------------------------------------------------
-XML_APPLICATION_FILES = ref_man.xml
-XML_REF3_FILES = otp_mib.xml
-
-XML_PART_FILES = part.xml
-XML_CHAPTER_FILES = \
- introduction.xml \
- mibs.xml \
- notes.xml
-
-BOOK_FILES = book.xml
-
-XML_FILES = \
- $(BOOK_FILES) $(XML_CHAPTER_FILES) \
- $(XML_PART_FILES) $(XML_REF3_FILES) $(XML_APPLICATION_FILES)
-
-GIF_FILES =
-
-# ----------------------------------------------------
-
-HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
- $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
-
-INFO_FILE = ../../info
-
-MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
-
-HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
-
-TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-XML_FLAGS +=
-DVIPS_FLAGS +=
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
- $(INSTALL_DATA) $< $@
-
-docs: pdf html man
-
-$(TOP_PDF_FILE): $(XML_FILES)
-
-pdf: $(TOP_PDF_FILE)
-
-html: gifs $(HTML_REF_MAN_FILE)
-
-man: $(MAN3_FILES)
-
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
-
-debug opt:
-
-clean clean_docs:
- rm -rf $(HTMLDIR)/*
- rm -rf $(XMLDIR)
- rm -f $(MAN3DIR)/*
- rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
- rm -f errs core *~
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_docs_spec: docs
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf"
- $(INSTALL_DIR) "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(HTMLDIR)/* \
- "$(RELSYSDIR)/doc/html"
- $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
- $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
- $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
-
-release_spec:
diff --git a/lib/otp_mibs/doc/src/introduction.xml b/lib/otp_mibs/doc/src/introduction.xml
deleted file mode 100644
index 7046cbb8ae..0000000000
--- a/lib/otp_mibs/doc/src/introduction.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE chapter SYSTEM "chapter.dtd">
-
-<chapter>
- <header>
- <copyright>
- <year>2003</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.
-
- </legalnotice>
-
- <title>Introduction</title>
- <prepared>Ingela Anderton</prepared>
- <docno></docno>
- <date>2003-05-19</date>
- <rev>A</rev>
- <file>introduction.xml</file>
- </header>
-
- <section>
- <title>Purpose</title>
- <p>The purpose of the OTP_Mibs application is to provide an SNMP
- management information base for Erlang nodes.</p>
- </section>
-
- <section>
- <title>Pre-requisites</title>
- <p>It is assumed that the reader is familiar with the Erlang
- programming language, concepts of OTP and has a basic knowledge
- of SNMP.</p>
- </section>
-</chapter>
-
-
diff --git a/lib/otp_mibs/doc/src/mibs.xml b/lib/otp_mibs/doc/src/mibs.xml
deleted file mode 100644
index a32d5ea5f5..0000000000
--- a/lib/otp_mibs/doc/src/mibs.xml
+++ /dev/null
@@ -1,71 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE chapter SYSTEM "chapter.dtd">
-
-<chapter>
- <header>
- <copyright>
- <year>2003</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.
-
- </legalnotice>
-
- <title>Mibs</title>
- <prepared>Ingela Anderton</prepared>
- <docno></docno>
- <date>2003-05-19</date>
- <rev>A</rev>
- <file>mibs.xml</file>
- </header>
-
- <section>
- <title>Structure</title>
- <p>The OTP mibs are stored in the
- <c>$OTP_ROOT/lib/otp_mibs/mibs/</c> directory. They
- are defined in SNMPv2 SMI syntax. An SNMPv1 version of the mib is
- delivered in the <c>mibs/v1</c> directory. The compiled MIB is
- located under <c>priv/mibs</c>, and the generated <c>.hrl</c>
- file under the <c>include</c> directory. To compile a MIB that
- IMPORTS a MIB in the OTP_Mibs application, give the option
- <c>{il, ["otp_mibs/priv/mibs"]}</c> to the MIB compiler.</p>
- </section>
-
- <section>
- <title>OTP-MIB</title>
- <p>The OTP-MIB mib represents information about Erlang nodes such as
- node name, number of running processes, virtual machine version
- etc. If the MIB should be used in a system, it should be
- loaded into an SNMP agent by using the API function
- <c>otp_mib:load/1</c>.</p>
- </section>
-
- <section>
- <title>OTP-REG</title>
- <p>The OTP-REG mib defines the unique OTP subtree of object
- identifiers under the Ericsson subtree. Under the OTP subtree
- several object identifiers are defined. This module is typically
- included by OTP applications defining their own mibs, or ASN.1
- modules in general, that require unique object identifiers under
- the OTP subtree.</p>
- </section>
-
- <section>
- <title>OTP-TC</title>
- <p>The OTP-TC mib provides the textual convention datatype
- <c>OwnerString</c>.</p>
- </section>
-</chapter>
-
-
diff --git a/lib/otp_mibs/doc/src/notes.xml b/lib/otp_mibs/doc/src/notes.xml
deleted file mode 100644
index 443f08f1e1..0000000000
--- a/lib/otp_mibs/doc/src/notes.xml
+++ /dev/null
@@ -1,327 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE chapter SYSTEM "chapter.dtd">
-
-<chapter>
- <header>
- <copyright>
- <year>2004</year><year>2018</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>OTP_Mibs Release Notes</title>
- <prepared>otp_appnotes</prepared>
- <docno>nil</docno>
- <date>nil</date>
- <rev>nil</rev>
- <file>notes.xml</file>
- </header>
- <p>This document describes the changes made to the OTP_Mibs
- application.</p>
-
-<section><title>Otp_Mibs 1.2.1</title>
-
- <section><title>Fixed Bugs and Malfunctions</title>
- <list>
- <item>
- <p>
- Improved documentation.</p>
- <p>
- Own Id: OTP-15190</p>
- </item>
- </list>
- </section>
-
-</section>
-
-<section><title>Otp_Mibs 1.2</title>
-
- <section><title>Improvements and New Features</title>
- <list>
- <item>
- <p>
- The otp_mibs application has been deprecated and will be
- removed in a future release.</p>
- <p>
- Own Id: OTP-15141</p>
- </item>
- </list>
- </section>
-
-</section>
-
-<section><title>Otp_Mibs 1.1.2</title>
-
- <section><title>Fixed Bugs and Malfunctions</title>
- <list>
- <item>
- <p> Removed all old unused files in the documentation.
- </p>
- <p>
- Own Id: OTP-14475 Aux Id: ERL-409, PR-1493 </p>
- </item>
- </list>
- </section>
-
-</section>
-
-<section><title>Otp_Mibs 1.1.1</title>
-
- <section><title>Improvements and New Features</title>
- <list>
- <item>
- <p>
- Internal changes</p>
- <p>
- Own Id: OTP-13551</p>
- </item>
- </list>
- </section>
-
-</section>
-
-<section><title>Otp_Mibs 1.1</title>
-
- <section><title>Improvements and New Features</title>
- <list>
- <item>
- <p>
- Change license text from Erlang Public License to Apache
- Public License v2</p>
- <p>
- Own Id: OTP-12845</p>
- </item>
- </list>
- </section>
-
-</section>
-
-<section><title>Otp_Mibs 1.0.10</title>
-
- <section><title>Fixed Bugs and Malfunctions</title>
- <list>
- <item>
- <p>
- Make sure the clean rule for ssh, ssl, eunit and otp_mibs
- actually removes generated files.</p>
- <p>
- Own Id: OTP-12200</p>
- </item>
- </list>
- </section>
-
-</section>
-
-<section><title>Otp_Mibs 1.0.9</title>
-
- <section><title>Fixed Bugs and Malfunctions</title>
- <list>
- <item>
- <p>
- Add type based integer value truncation/reset.</p>
- <p>
- This fixes errors when querying e.g. the
- erlNodeReductions, erlNodeInBytes and erlNodeOutBytes
- objects in long-running Erlang/OTP systems.</p>
- <p>
- Update types of applicable MIB objects to 64bit based
- types.</p>
- <p>
- Potential incompatibility: Type change of Counter32 to
- Counter64 in OTP-MIB.mib</p>
- <p>
- (Thanks to Tobias Schlager)</p>
- <p>
- *** POTENTIAL INCOMPATIBILITY ***</p>
- <p>
- Own Id: OTP-11203</p>
- </item>
- <item>
- <p>
- Application upgrade (appup) files are corrected for the
- following applications: </p>
- <p>
- <c>asn1, common_test, compiler, crypto, debugger,
- dialyzer, edoc, eldap, erl_docgen, et, eunit, gs, hipe,
- inets, observer, odbc, os_mon, otp_mibs, parsetools,
- percept, public_key, reltool, runtime_tools, ssh,
- syntax_tools, test_server, tools, typer, webtool, wx,
- xmerl</c></p>
- <p>
- A new test utility for testing appup files is added to
- test_server. This is now used by most applications in
- OTP.</p>
- <p>
- (Thanks to Tobias Schlager)</p>
- <p>
- Own Id: OTP-11744</p>
- </item>
- </list>
- </section>
-
-</section>
-
-<section><title>Otp_Mibs 1.0.8</title>
-
- <section><title>Improvements and New Features</title>
- <list>
- <item>
- <p>
- Misc build updates</p>
- <p>
- Own Id: OTP-10784</p>
- </item>
- </list>
- </section>
-
-</section>
-
-<section><title>Otp_Mibs 1.0.7</title>
-
- <section><title>Improvements and New Features</title>
- <list>
- <item>
- <p>
- Tuple funs (a two-element tuple with a module name and a
- function) are now officially deprecated and will be
- removed in R16. Use '<c>fun M:F/A</c>' instead. To make
- you aware that your system uses tuple funs, the very
- first time a tuple fun is applied, a warning will be sent
- to the error logger.</p>
- <p>
- Own Id: OTP-9649</p>
- </item>
- </list>
- </section>
-
-</section>
-
-<section><title>Otp_Mibs 1.0.6</title>
-
- <section><title>Improvements and New Features</title>
- <list>
- <item>
- <p>
- The documentation is now built with open source tools
- (xsltproc and fop) that exists on most platforms. One
- visible change is that the frames are removed.</p>
- <p>
- Own Id: OTP-8201</p>
- </item>
- </list>
- </section>
-
-</section>
-
-<section><title>Otp_Mibs 1.0.5</title>
-
- <section><title>Improvements and New Features</title>
- <list>
- <item>
- <p>The copyright notices have been updated.</p>
- <p>
- Own Id: OTP-7851</p>
- </item>
- </list>
- </section>
-
-</section>
-
- <section>
- <title>Otp_Mibs 1.0.4.1</title>
-
- <section>
- <title>Improvements and New Features</title>
- <list type="bulleted">
- <item>
- <p>Minor Makefile changes.</p>
- <p>Own Id: OTP-6689</p>
- </item>
- </list>
- </section>
- </section>
-
- <section>
- <title>OTP_Mibs 1.0.4</title>
-
- <section>
- <title>Improvements and New Features</title>
- <list type="bulleted">
- <item>
- <p>Replaced calls to deprecated functions in <c>snmp</c>
- with calls to the equivalent functions in <c>snmpa</c>.</p>
- <p>Own Id: OTP-6112</p>
- </item>
- </list>
- </section>
- </section>
-
- <section>
- <title>OTP_Mibs 1.0.3</title>
-
- <section>
- <title>Improvements and New Features</title>
- <list type="bulleted">
- <item>
- <p>The <c>otp_mib</c> module has been cleaned up to improve the
- maintainability. It should have no effect on the
- functionality of the OTP_Mibs application.</p>
- <p>Own Id: OTP-4982</p>
- </item>
- </list>
- </section>
- </section>
-
- <section>
- <title>OTP_Mibs 1.0.2</title>
-
- <section>
- <title>Fixed Bugs and Malfunctions</title>
- <list type="bulleted">
- <item>
- <p>Incorrect <c>.app</c> file (missing mandatory
- <c>registered</c>).</p>
- <p>Own Id: OTP-4823 Aux Id: Seq8145, OTP-4801 </p>
- </item>
- </list>
- </section>
- </section>
-
- <section>
- <title>OTP_Mibs 1.0.1</title>
-
- <section>
- <title>Fixed Bugs and Malfunctions</title>
- <list type="bulleted">
- <item>
- <p>Missing <c>.app</c> and <c>appup</c> files in <c>ebin</c>.</p>
- <p>Own Id: OTP-4801 Aux Id: Seq8145 </p>
- </item>
- </list>
- </section>
- </section>
-
- <section>
- <title>OTP_Mibs 1.0</title>
- <p>The OTP mibs that where included in the SASL application
- have been moved to this new application OTP_Mibs. The OTP
- mibs had no real connection to SASL and it is desirable that
- the core of Erlang/OTP is not dependent on SNMP.</p>
- <p>Own Id: OTP-4686</p>
- </section>
-</chapter>
-
-
diff --git a/lib/otp_mibs/doc/src/otp_mib.xml b/lib/otp_mibs/doc/src/otp_mib.xml
deleted file mode 100644
index 530c529c69..0000000000
--- a/lib/otp_mibs/doc/src/otp_mib.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE erlref SYSTEM "erlref.dtd">
-
-<erlref>
- <header>
- <copyright>
- <year>2003</year><year>2018</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>otp_mib</title>
- <prepared>Ingela Anderton</prepared>
- <docno></docno>
- <date></date>
- <rev></rev>
- </header>
- <module>otp_mib</module>
- <modulesummary>Handles the OTP-MIB</modulesummary>
- <description>
- <p>The SNMP application should be used to start an SNMP agent. Then
- the API functions below can be used to load/unload the OTP-MIB
- into/from the agent. The instrumentation of the OTP-MIB uses
- Mnesia, hence Mnesia must be started prior to loading the OTP-MIB.</p>
- <warning>
- <p>This application has been deprecated and will be removed in a furture release.</p>
- </warning>
- </description>
- <funcs>
- <func>
- <name>load(Agent) -> ok | {error, Reason}</name>
- <fsummary>Load the OTP-MIB</fsummary>
- <type>
- <v>Agent = pid() | atom()</v>
- <v>Reason = term()</v>
- </type>
- <desc>
- <p>Loads the OTP-MIB.</p>
- </desc>
- </func>
- <func>
- <name>unload(Agent) -> ok | {error, Reason}</name>
- <fsummary>Unload the OTP-MIB</fsummary>
- <type>
- <v>Agent = pid() | atom()</v>
- <v>Reason = term()</v>
- </type>
- <desc>
- <p>Unloads the OTP-MIB.</p>
- </desc>
- </func>
- </funcs>
-
- <section>
- <title>See Also</title>
- <p>snmp(3)</p>
- </section>
-</erlref>
-
-
diff --git a/lib/otp_mibs/doc/src/ref_man.xml b/lib/otp_mibs/doc/src/ref_man.xml
deleted file mode 100644
index 06c5aadcd9..0000000000
--- a/lib/otp_mibs/doc/src/ref_man.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE application SYSTEM "application.dtd">
-
-<application xmlns:xi="http://www.w3.org/2001/XInclude">
- <header>
- <copyright>
- <year>2003</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.
-
- </legalnotice>
-
- <title>OTP_Mibs Reference Manual</title>
- <prepared>Ingela Anderton</prepared>
- <docno></docno>
- <date>2002-09-13</date>
- <rev>A</rev>
- </header>
- <description>
- <p>The <em>OTP_Mibs</em> application provides an SNMP management
- information base for Erlang nodes.</p>
- </description>
- <xi:include href="otp_mib.xml"/>
-</application>
-
-
diff --git a/lib/otp_mibs/ebin/.gitignore b/lib/otp_mibs/ebin/.gitignore
deleted file mode 100644
index e69de29bb2..0000000000
--- a/lib/otp_mibs/ebin/.gitignore
+++ /dev/null
diff --git a/lib/otp_mibs/include/.gitignore b/lib/otp_mibs/include/.gitignore
deleted file mode 100644
index e69de29bb2..0000000000
--- a/lib/otp_mibs/include/.gitignore
+++ /dev/null
diff --git a/lib/otp_mibs/info b/lib/otp_mibs/info
deleted file mode 100644
index aedd1c883b..0000000000
--- a/lib/otp_mibs/info
+++ /dev/null
@@ -1,2 +0,0 @@
-group: oam Operation & Maintenance Applications
-short: SNMP management information base for Erlang/OTP nodes.
diff --git a/lib/otp_mibs/mibs/OTP-EVA-MIB.mib b/lib/otp_mibs/mibs/OTP-EVA-MIB.mib
deleted file mode 100644
index 4d0c53ed95..0000000000
--- a/lib/otp_mibs/mibs/OTP-EVA-MIB.mib
+++ /dev/null
@@ -1,569 +0,0 @@
---
--- %CopyrightBegin%
---
--- Copyright Ericsson AB 2004-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%
---
-
-OTP-EVA-MIB DEFINITIONS ::= BEGIN
-
-IMPORTS
- MODULE-IDENTITY, OBJECT-TYPE, NOTIFICATION-TYPE,
- Counter32, Gauge32, Integer32
- FROM SNMPv2-SMI
- TEXTUAL-CONVENTION, DisplayString, DateAndTime
- FROM SNMPv2-TC
- MODULE-COMPLIANCE, OBJECT-GROUP, NOTIFICATION-GROUP
- FROM SNMPv2-CONF
- otpModules, otpApplications
- FROM OTP-REG
- OwnerString
- FROM OTP-TC
- ;
-
-otpEvaModule MODULE-IDENTITY
- LAST-UPDATED "200305090900Z"
- ORGANIZATION "Ericsson"
- CONTACT-INFO
- "Contact: Erlang Support see license agreement for Erlang/OTP."
- DESCRIPTION
- "This MIB is part of the OTP MIB. It defines MIB objects
- for the eva application in OTP."
-
- REVISION "200305090900Z"
- DESCRIPTION
- "Changed CONTACT-INFO as it was outdated, made it more generic
- to avoid such changes in the future."
-
- REVISION "199801270900Z"
- DESCRIPTION
- "Changed erroneous defintion of alarmCleared notification.
- Changed erroneous name of this module to otpEvaModule."
- REVISION "199712010900Z"
- DESCRIPTION
- "Converted to v2 SMI and placed in the OTP tree."
- REVISION "199705020900Z"
- DESCRIPTION
- "The initial version of this MIB module. It is very much
- inspired by the ANS-ALM-MIB and Axd301Eva-OMS mibs."
- ::= { otpModules 6 }
-
-otpEvaMIB OBJECT IDENTIFIER ::= { otpApplications 4 }
-otpEvaMIBConformance
- OBJECT IDENTIFIER ::= { otpEvaMIB 1 }
-otpEvaMIBObjects
- OBJECT IDENTIFIER ::= { otpEvaMIB 2 }
-otpEvaMIBEvents OBJECT IDENTIFIER ::= { otpEvaMIB 3 }
-otpEvaMIBEventsV2
- OBJECT IDENTIFIER ::= { otpEvaMIBEvents 0 }
-
-
--- Datatypes
-
-AlarmSeverity ::= TEXTUAL-CONVENTION
- STATUS current
- DESCRIPTION
- "The AlarmSeverity defines six severity levels,
- which provide an indication of how it is perceived that the
- capability of the managed object has been affected. Those
- severity levels which represent service affecting conditions
- ordered from most severe to least severe are critical,
- major, minor and warning. The levels used are as defined
- in X.733, ITU Alarm Reporting Function:
-
- o The Indeterminate severity level indicates that the
- severity level cannot be determined.
-
- o The Critical severity level indicates that a service
- affecting condition has occurred and an immediate
- corrective action is required. Such a severity can be
- reported, for example, when a managed object becomes
- totally out of service and its capability must be restored.
-
- o The Major severity level indicates that a service
- affecting condition has developed and an urgent corrective
- action is required. Such a severity can be reported, for
- example, when there is a severe degradation in the
- capability of the managed object and its full capability
- must be restored.
-
- o The Minor severity level indicates the existence of a
- non-service affecting fault condition and that corrective
- action should be taken in order to prevent a more serious
- (for example, service affecting) fault. Such a severity
- can be reported, for example, when the detected alarm
- condition is not currently degrading the capacity of the
- managed object.
-
- o The Warning severity level indicates the detection of a
- potential or impending service affecting fault, before any
- significant effects have been felt. Action should be taken
- to further diagnose (if necessary) and correct the problem
- in order to prevent it from becoming a more serious service
- affecting fault.
-
- When an alarm is cleared, an alarmCleared event is generated.
- This event clears the alarm with the currentAlarmFaultId
- contained in the event. It is not required that the clearing
- of previously reported alarms are reported. Therefore, a managing
- system cannot assume that the absence of an alarmedCleared event
- for a fault means that the condition that caused the generation
- of previous alarms is still present. Managed object definers
- shall state if, and under which conditions, the alarmedCleared
- event is used.
-
- The clear value of AlarmSeverity is an action which is used when
- a management station wants to clear an active alarm. This is not
- possible on all systems, and thus an agent does not have support
- write access for this value."
- REFERENCE
- "X.733, ITU Alarm Reporting Function"
- SYNTAX INTEGER {
- indeterminate (0),
- critical (1),
- major (2),
- minor (3),
- warning (4),
- clear (5) -- Written, not read
- }
-
-AlarmClass ::= TEXTUAL-CONVENTION
- STATUS current
- DESCRIPTION
- "The AlarmClass type categorizes the alarm, and is
- defined when the alarm is registered. It is as defined in
- X.733, ITU Alarm Reporting Function:
-
- o communications. An alarm of this class is principally
- associated with the procedures or processes required
- to convey information from one point to another.
-
- o qos. An alarm of this class is principally associated
- with a degradation in the quality of service.
-
- o processing. An alarm of this class is principally
- associated with a software or processing fault.
-
- o equipment. An alarm of this class is principally
- associated with an equipment fault.
-
- o environmental. An alarm of this class is principally
- associated with a condition relating to an enclosure in
- with equipment resides."
- REFERENCE
- "X.733, ITU Alarm Reporting Function"
- SYNTAX INTEGER {
- unknown (0),
- communications (1),
- qos (2),
- processing (3),
- equipment (4),
- environmental (5)
- }
-
-
--- Managed Objects
-
-event OBJECT IDENTIFIER ::= { otpEvaMIBObjects 1 }
-alarm OBJECT IDENTIFIER ::= { otpEvaMIBObjects 2 }
-currentAlarm OBJECT IDENTIFIER ::= { otpEvaMIBObjects 3 }
-
--- The Event group
---
--- The Event group controls the generation of notifications of
--- events from the system.
-
-eventTable OBJECT-TYPE
- SYNTAX SEQUENCE OF EventEntry
- MAX-ACCESS not-accessible
- STATUS current
- DESCRIPTION
- "A list of events defined by the system. This table is used
- to control the sending of traps and to whom the traps are
- sent."
- ::= { event 1 }
-
-eventEntry OBJECT-TYPE
- SYNTAX EventEntry
- MAX-ACCESS not-accessible
- STATUS current
- DESCRIPTION
- "A set of parameters that describe an event to be
- generated when certain conditions are met."
- INDEX { eventIndex }
- ::= { eventTable 1 }
-
-EventEntry ::= SEQUENCE {
- eventIndex Integer32 (1..2147483647),
- eventTrapName DisplayString,
- eventTreatment INTEGER,
- eventCommunity OCTET STRING (SIZE (0..127)),
- eventSentTraps Counter32,
- eventOwner OwnerString
- }
-
-eventIndex OBJECT-TYPE
- SYNTAX Integer32 (1..2147483647)
- MAX-ACCESS not-accessible
- STATUS current
- DESCRIPTION
- "An index that uniquely identifies an entry in the
- event table. Each such entry defines one event that
- is to be generated when the appropriate conditions
- occur. The value for each eventIndex must remain
- constant, at least from one re-initialization of the
- entity's network management system to the next
- re-initialization."
- ::= { eventEntry 1 }
-
-eventTrapName OBJECT-TYPE
- SYNTAX DisplayString
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "The identifier of the corresponding trap.
- NOTE: this should be an OID in SNMPv2, but must be
- a string in v1."
- ::= { eventEntry 2 }
-
-eventTreatment OBJECT-TYPE
- SYNTAX INTEGER {
- none(1),
- log(2),
- snmpTrap(3),
- logAndTrap(4)
- }
- MAX-ACCESS read-write
- STATUS current
- DESCRIPTION
- "Defines how the system shall treat this event. In the
- case of snmpTrap, an SNMP trap is sent to one or more
- management stations. In the case of log, the event is
- guaranteed to be logged in a log according to some log
- strategy. Each such log strategy may define a MIB module
- for control and examination of logs."
- ::= { eventEntry 3 }
-
-eventCommunity OBJECT-TYPE
- SYNTAX OCTET STRING (SIZE (0..127))
- MAX-ACCESS read-write
- STATUS current
- DESCRIPTION
- "If an SNMP trap is to be sent, it will be sent to
- the SNMP community specified by this octet string."
- ::= { eventEntry 4 }
-
-eventSentTraps OBJECT-TYPE
- SYNTAX Counter32
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "The number of times this event has been sent as a trap."
- ::= { eventEntry 5 }
-
-eventOwner OBJECT-TYPE
- SYNTAX OwnerString
- MAX-ACCESS read-write
- STATUS current
- DESCRIPTION
- "The manager entity that 'owns' this event entry, and is
- therefore responsible for its contents."
- ::= { eventEntry 6 }
-
-eventTime OBJECT-TYPE
- SYNTAX DateAndTime
- MAX-ACCESS accessible-for-notify
- STATUS current
- DESCRIPTION
- "This object may be included in a trap definition for an event.
- It specifies the time the event was generated."
- ::= { event 2 }
-
-
--- The Alarm group
---
--- The Alarm group extends the Event group with objects for alarms.
-
-alarmTable OBJECT-TYPE
- SYNTAX SEQUENCE OF AlarmEntry
- MAX-ACCESS not-accessible
- STATUS current
- DESCRIPTION
- "Contains additional information for alarm events."
- ::= { alarm 1 }
-
-alarmEntry OBJECT-TYPE
- SYNTAX AlarmEntry
- MAX-ACCESS not-accessible
- STATUS current
- DESCRIPTION
- "A set of parameters for alarms."
- INDEX { eventIndex }
- ::= { alarmTable 1 }
-
-AlarmEntry ::= SEQUENCE {
- alarmClass AlarmClass,
- alarmSeverity AlarmSeverity
- }
-
-alarmClass OBJECT-TYPE
- SYNTAX AlarmClass
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "The class of this alarm."
- ::= { alarmEntry 1 }
-
-alarmSeverity OBJECT-TYPE
- SYNTAX AlarmSeverity
- MAX-ACCESS read-write
- STATUS current
- DESCRIPTION
- "The perceived severity that shall apply to the
- associated alarms."
- ::= { alarmEntry 2 }
-
-
--- The CurrentAlarm group
---
--- The CurrentAlarm group is a collection of objects for monitoring of
--- active alarms in the system.
-
-numberOfCurrentAlarms OBJECT-TYPE
- SYNTAX Gauge32
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "Number of currently active alarms in the system."
- ::= { currentAlarm 1 }
-
-currentAlarmLastTimeChanged OBJECT-TYPE
- SYNTAX DateAndTime
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "The time an entry in the currentAlarmTable was changed.
- It may be used by a management station as a value to
- poll. If the value is changed, the management station
- knows that the currentAlarmTable has been updated."
- ::= { currentAlarm 2 }
-
-currentAlarmTable OBJECT-TYPE
- SYNTAX SEQUENCE OF CurrentAlarmEntry
- MAX-ACCESS not-accessible
- STATUS current
- DESCRIPTION
- "A list of currently active alarms in the system."
- ::= { currentAlarm 3 }
-
-currentAlarmEntry OBJECT-TYPE
- SYNTAX CurrentAlarmEntry
- MAX-ACCESS not-accessible
- STATUS current
- DESCRIPTION
- "A set of parameters that describe a currently active
- alarm."
- INDEX { currentAlarmFaultId }
- ::= { currentAlarmTable 1 }
-
-CurrentAlarmEntry ::= SEQUENCE {
- currentAlarmFaultId Integer32 (1..2147483647),
- currentAlarmEventIndex Integer32 (1..2147483647),
- currentAlarmObject OBJECT IDENTIFIER,
- currentAlarmCause OBJECT IDENTIFIER,
- currentAlarmSeverity AlarmSeverity,
- currentAlarmTime DateAndTime,
- currentAlarmInformation DisplayString,
- currentAlarmExtra1 OBJECT IDENTIFIER,
- currentAlarmExtra2 OBJECT IDENTIFIER
- }
-
-currentAlarmFaultId OBJECT-TYPE
- SYNTAX Integer32 (1..2147483647)
- MAX-ACCESS not-accessible
- STATUS current
- DESCRIPTION
- "An id that uniquely identifies a fault. Each fault is
- represented as one an entry in the currentAlarmTable."
- ::= { currentAlarmEntry 1 }
-
-currentAlarmEventIndex OBJECT-TYPE
- SYNTAX Integer32 (1..2147483647)
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "A pointer into the eventTable. Points to the event
- corresponding to this alarm."
- ::= { currentAlarmEntry 2 }
-
-currentAlarmObject OBJECT-TYPE
- SYNTAX OBJECT IDENTIFIER
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "The alarming object."
- ::= { currentAlarmEntry 3 }
-
-currentAlarmCause OBJECT-TYPE
- SYNTAX OBJECT IDENTIFIER
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "The probable cause of the alarm."
- ::= { currentAlarmEntry 4 }
-
-currentAlarmSeverity OBJECT-TYPE
- SYNTAX AlarmSeverity
- MAX-ACCESS read-write
- STATUS current
- DESCRIPTION
- "The perceived severity of the fault. A manager can set
- this value to clear only. When set to clear, the alarm
- is removed from this table, and a 'clearAlarm' event is
- generated."
- ::= { currentAlarmEntry 5 }
-
-currentAlarmTime OBJECT-TYPE
- SYNTAX DateAndTime
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "The time the fault was detected."
- ::= { currentAlarmEntry 6 }
-
-currentAlarmInformation OBJECT-TYPE
- SYNTAX DisplayString
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "Additional information pin-pointing the problem."
- ::= { currentAlarmEntry 7 }
-
-currentAlarmExtra1 OBJECT-TYPE
- SYNTAX OBJECT IDENTIFIER
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "An extra parameter used for some alarms at their own
- discretion. Can be used for example to identify
- additional objects in the alarm, or instead of
- currentAlarmInformation to pin-point the problem, if the
- additional information is defined in some MIB."
- ::= { currentAlarmEntry 8 }
-
-currentAlarmExtra2 OBJECT-TYPE
- SYNTAX OBJECT IDENTIFIER
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "An extra parameter used for some alarms at their own
- discretion. Can be used for example to identify
- additional objects in the alarm, or instead of
- currentAlarmInformation to pin-point the problem, if the
- additional information is defined in some MIB."
- ::= { currentAlarmEntry 9 }
-
-
--- Events
-
-alarmCleared NOTIFICATION-TYPE
- OBJECTS {
- currentAlarmEventIndex,
- eventTime
- }
- STATUS current
- DESCRIPTION
- "This event is sent when an alarm has been cleared,
- either by the application or by an operator. Note that the
- currentAlarmFaultId is implicitly sent as the instance identifier
- for currentAlarmEventIndex."
- ::= { otpEvaMIBEventsV2 1 }
-
-
--- conformance information
-
-otpEvaMIBCompliances
- OBJECT IDENTIFIER ::= { otpEvaMIBConformance 1 }
-otpEvaMIBGroups
- OBJECT IDENTIFIER ::= { otpEvaMIBConformance 2 }
-
-
--- compliance statements
-
-otpEvaBasicCompliance MODULE-COMPLIANCE
- STATUS current
- DESCRIPTION
- "The compliance statement for SNMPv2 entities which
- implement the OTP-EVA-MIB."
- MODULE -- this module
- MANDATORY-GROUPS { eventGroup,
- alarmGroup,
- currentAlarmGroup,
- evaEventsGroup }
-
- ::= { otpEvaMIBCompliances 1 }
-
-
--- units of conformance
-
-eventGroup OBJECT-GROUP
- OBJECTS { eventTrapName,
- eventTreatment,
- eventCommunity,
- eventSentTraps,
- eventOwner,
- eventTime }
- STATUS current
- DESCRIPTION
- "A collection of objects providing basic instrumentation
- and control of the events defined in the OTP system."
- ::= { otpEvaMIBGroups 1 }
-
-alarmGroup OBJECT-GROUP
- OBJECTS { alarmClass,
- alarmSeverity }
- STATUS current
- DESCRIPTION
- "A collection of objects providing basic instrumentation
- and control of the alarms defined the OTP system."
- ::= { otpEvaMIBGroups 2 }
-
-currentAlarmGroup OBJECT-GROUP
- OBJECTS { numberOfCurrentAlarms,
- currentAlarmLastTimeChanged,
- currentAlarmEventIndex,
- currentAlarmObject,
- currentAlarmCause,
- currentAlarmSeverity,
- currentAlarmTime,
- currentAlarmInformation,
- currentAlarmExtra1,
- currentAlarmExtra2 }
- STATUS current
- DESCRIPTION
- "A collection of objects providing basic instrumentation
- of the activa alarm list in the OTP system."
- ::= { otpEvaMIBGroups 3 }
-
-evaEventsGroup NOTIFICATION-GROUP
- NOTIFICATIONS { alarmCleared }
- STATUS current
- DESCRIPTION
- "The notification which is generated from EVA."
- ::= { otpEvaMIBGroups 4 }
-
-
-END
diff --git a/lib/otp_mibs/mibs/OTP-MIB.funcs b/lib/otp_mibs/mibs/OTP-MIB.funcs
deleted file mode 100644
index 9f9d69c3d1..0000000000
--- a/lib/otp_mibs/mibs/OTP-MIB.funcs
+++ /dev/null
@@ -1,2 +0,0 @@
-{erlNodeTable, {otp_mib, erl_node_table, []}}.
-{applTable, {otp_mib, appl_table, []}}.
diff --git a/lib/otp_mibs/mibs/OTP-MIB.mib b/lib/otp_mibs/mibs/OTP-MIB.mib
deleted file mode 100644
index 693319eae4..0000000000
--- a/lib/otp_mibs/mibs/OTP-MIB.mib
+++ /dev/null
@@ -1,318 +0,0 @@
---
--- %CopyrightBegin%
---
--- Copyright Ericsson AB 1996-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%
---
-
-OTP-MIB DEFINITIONS ::= BEGIN
-
-IMPORTS
- MODULE-IDENTITY, OBJECT-TYPE,
- Counter64, Gauge32, Integer32
- FROM SNMPv2-SMI
- TEXTUAL-CONVENTION, DisplayString
- FROM SNMPv2-TC
- MODULE-COMPLIANCE, OBJECT-GROUP
- FROM SNMPv2-CONF
- otpModules, otpApplications
- FROM OTP-REG
- ;
-
-otpModule MODULE-IDENTITY
- LAST-UPDATED "201307160700Z"
- ORGANIZATION "Ericsson"
- CONTACT-INFO
- "Contact: Erlang Support see license agreement for Erlang/OTP."
- DESCRIPTION
- "This is a MIB for a distributed OTP system, with one SNMP
- agent executing at one node only. Each Erlang node in the
- OTP system is represented by one row in the erlNodeTable."
-
- REVISION "201307160700Z"
- DESCRIPTION
- "Updated various types to be able to reflect larger values.
- The objects erlNodeReductions, erlNodeInBytes, erlNodeOutBytes as well
- as the type MilliSeconds have been updated to Counter64."
-
- REVISION "200305090900Z"
- DESCRIPTION
- "Changed CONTACT-INFO as it was outdated, made it more generic
- to avoid such changes in the future."
-
- REVISION "199712010900Z"
- DESCRIPTION
- "Converted to v2 SMI and placed in the OTP tree."
-
- REVISION "199608191700Z"
- DESCRIPTION
- "The initial revision of MIB module OTP-MIB."
-
- ::= { otpModules 3 }
-
-otpMIB OBJECT IDENTIFIER ::= { otpApplications 1 }
-otpMIBConformance
- OBJECT IDENTIFIER ::= { otpMIB 1 }
-otpMIBObjects OBJECT IDENTIFIER ::= { otpMIB 2 }
-
--- Datatypes
-
-MilliSeconds ::= TEXTUAL-CONVENTION
- STATUS current
- DESCRIPTION
- "The MilliSeconds type represents a Counter which represents
- the time, in milliseconds between two epochs. When objects
- are defined which use this type, the description of the object
- identifies both of the reference epochs."
- SYNTAX Counter64
-
-
--- Managed Objects
-
-erlang OBJECT IDENTIFIER ::= { otpMIBObjects 1 }
-appls OBJECT IDENTIFIER ::= { otpMIBObjects 2 }
-
-erlNodeTable OBJECT-TYPE
- SYNTAX SEQUENCE OF ErlNodeEntry
- MAX-ACCESS not-accessible
- STATUS current
- DESCRIPTION
- "A table with info on each erlang node in the system."
- ::= { erlang 1 }
-
-erlNodeEntry OBJECT-TYPE
- SYNTAX ErlNodeEntry
- MAX-ACCESS not-accessible
- STATUS current
- DESCRIPTION
- "A conceptual row in the erlNodeTable."
- INDEX { erlNodeId }
- ::= { erlNodeTable 1 }
-
-ErlNodeEntry ::= SEQUENCE {
- erlNodeId Integer32,
- erlNodeName DisplayString,
- erlNodeMachine DisplayString,
- erlNodeVersion DisplayString,
- erlNodeRunQueue Gauge32,
- erlNodeRunTime MilliSeconds,
- erlNodeWallClock MilliSeconds,
- erlNodeReductions Counter64,
- erlNodeProcesses Gauge32,
- erlNodeInBytes Counter64,
- erlNodeOutBytes Counter64
-}
-
-erlNodeId OBJECT-TYPE
- SYNTAX Integer32
- MAX-ACCESS not-accessible
- STATUS current
- DESCRIPTION
- "An integer that uniquely identifies the erlang node."
- ::= { erlNodeEntry 1 }
-
-erlNodeName OBJECT-TYPE
- SYNTAX DisplayString
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "The symbolic name of the erlang node."
- ::= { erlNodeEntry 2 }
-
-erlNodeMachine OBJECT-TYPE
- SYNTAX DisplayString
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "The virtual machine executing the erlang node"
- ::= { erlNodeEntry 3 }
-
-erlNodeVersion OBJECT-TYPE
- SYNTAX DisplayString
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "The version number of the virtual machine"
- ::= { erlNodeEntry 4 }
-
-erlNodeRunQueue OBJECT-TYPE
- SYNTAX Gauge32
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "Number of processes scheduled to run"
- ::= { erlNodeEntry 5 }
-
-erlNodeRunTime OBJECT-TYPE
- SYNTAX MilliSeconds
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "Total cpu time in milliseconds since the
- system started"
- ::= { erlNodeEntry 6 }
-
-erlNodeWallClock OBJECT-TYPE
- SYNTAX MilliSeconds
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "Total real time in milliseconds since the
- system started"
- ::= { erlNodeEntry 7 }
-
-erlNodeReductions OBJECT-TYPE
- SYNTAX Counter64
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "Number of function calls since the system started"
- ::= { erlNodeEntry 8 }
-
-erlNodeProcesses OBJECT-TYPE
- SYNTAX Gauge32
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "Number of running processes in the system."
- ::= { erlNodeEntry 9 }
-
-erlNodeInBytes OBJECT-TYPE
- SYNTAX Counter64
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "The total number of bytes delivered to the system"
- ::= { erlNodeEntry 10 }
-
-erlNodeOutBytes OBJECT-TYPE
- SYNTAX Counter64
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "The total number of bytes delivered from the system"
- ::= { erlNodeEntry 11 }
-
-
-applTable OBJECT-TYPE
- SYNTAX SEQUENCE OF ApplEntry
- MAX-ACCESS not-accessible
- STATUS current
- DESCRIPTION
- "A table with all currently running applications
- for each node."
- ::= { appls 1 }
-
-applEntry OBJECT-TYPE
- SYNTAX ApplEntry
- MAX-ACCESS not-accessible
- STATUS current
- DESCRIPTION
- "A conceptual row in the applTable."
- INDEX { erlNodeId, applId }
- ::= { applTable 1 }
-
-ApplEntry ::= SEQUENCE {
- applId Integer32,
- applName DisplayString,
- applDescr DisplayString,
- applVsn DisplayString
-}
-
-applId OBJECT-TYPE
- SYNTAX Integer32
- MAX-ACCESS not-accessible
- STATUS current
- DESCRIPTION
- "An integer that uniquely identifies the application."
- ::= { applEntry 1 }
-
-applName OBJECT-TYPE
- SYNTAX DisplayString
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "The name of the application."
- ::= { applEntry 2 }
-
-applDescr OBJECT-TYPE
- SYNTAX DisplayString
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "A short description of the application."
- ::= { applEntry 3 }
-
-applVsn OBJECT-TYPE
- SYNTAX DisplayString
- MAX-ACCESS read-only
- STATUS current
- DESCRIPTION
- "The version of the application."
- ::= { applEntry 4 }
-
-
--- conformance information
-
-otpMIBCompliances
- OBJECT IDENTIFIER ::= { otpMIBConformance 1 }
-otpMIBGroups OBJECT IDENTIFIER ::= { otpMIBConformance 2 }
-
-
--- compliance statements
-
-otpBasicCompliance MODULE-COMPLIANCE
- STATUS current
- DESCRIPTION
- "The compliance statement for SNMPv2 entities which
- implement the OTP-MIB."
- MODULE -- this module
- MANDATORY-GROUPS { erlGroup, applGroup }
-
- ::= { otpMIBCompliances 1 }
-
-
--- units of conformance
-
-erlGroup OBJECT-GROUP
- OBJECTS { erlNodeName,
- erlNodeMachine,
- erlNodeVersion,
- erlNodeRunQueue,
- erlNodeRunTime,
- erlNodeWallClock,
- erlNodeReductions,
- erlNodeProcesses,
- erlNodeInBytes,
- erlNodeOutBytes }
- STATUS current
- DESCRIPTION
- "A collection of objects providing basic instrumentation
- of the Erlang runtime system."
- ::= { otpMIBGroups 1 }
-
-applGroup OBJECT-GROUP
- OBJECTS { applName,
- applDescr,
- applVsn }
- STATUS current
- DESCRIPTION
- "A collection of objects providing basic instrumentation
- of the applications in the OTP system."
- ::= { otpMIBGroups 2 }
-
-
-END
diff --git a/lib/otp_mibs/mibs/v1/.gitignore b/lib/otp_mibs/mibs/v1/.gitignore
deleted file mode 100644
index e69de29bb2..0000000000
--- a/lib/otp_mibs/mibs/v1/.gitignore
+++ /dev/null
diff --git a/lib/otp_mibs/priv/bin/.gitignore b/lib/otp_mibs/priv/bin/.gitignore
deleted file mode 100644
index e69de29bb2..0000000000
--- a/lib/otp_mibs/priv/bin/.gitignore
+++ /dev/null
diff --git a/lib/otp_mibs/priv/mibs/.gitignore b/lib/otp_mibs/priv/mibs/.gitignore
deleted file mode 100644
index e69de29bb2..0000000000
--- a/lib/otp_mibs/priv/mibs/.gitignore
+++ /dev/null
diff --git a/lib/otp_mibs/priv/obj/.gitignore b/lib/otp_mibs/priv/obj/.gitignore
deleted file mode 100644
index e69de29bb2..0000000000
--- a/lib/otp_mibs/priv/obj/.gitignore
+++ /dev/null
diff --git a/lib/otp_mibs/src/Makefile b/lib/otp_mibs/src/Makefile
deleted file mode 100644
index 5c7af39c3f..0000000000
--- a/lib/otp_mibs/src/Makefile
+++ /dev/null
@@ -1,106 +0,0 @@
-#
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2003-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%
-#
-include $(ERL_TOP)/make/target.mk
-
-ifeq ($(TYPE),debug)
-ERL_COMPILE_FLAGS += -Ddebug -W
-endif
-
-include $(ERL_TOP)/make/$(TARGET)/otp.mk
-
-# ----------------------------------------------------
-# Application version
-# ----------------------------------------------------
-include ../vsn.mk
-VSN=$(OTP_MIBS_VSN)
-
-# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/otp_mibs-$(VSN)
-# ----------------------------------------------------
-# Target Specs
-# ----------------------------------------------------
-MODULES = \
- otp_mib
-
-INCLUDE=../include
-
-HRL_FILES =
-
-INTERNAL_HRL_FILES =
-
-ERL_FILES = $(MODULES:%=%.erl)
-
-APP_FILE = otp_mibs.app
-APP_SRC = $(APP_FILE).src
-APP_TARGET = $(EBIN)/$(APP_FILE)
-
-APPUP_FILE = otp_mibs.appup
-APPUP_SRC = $(APPUP_FILE).src
-APPUP_TARGET = $(EBIN)/$(APPUP_FILE)
-
-TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR))
-
-TARGETS = $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET)
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-ERL_COMPILE_FLAGS += -I$(INCLUDE)
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-
-debug opt: $(TARGETS)
-
-clean:
- rm -f $(TARGET_FILES)
- rm -f $(APP_TARGET)
- rm -f $(APPUP_TARGET)
- rm -f core
-
-docs:
-
-# ----------------------------------------------------
-# Special Build Targets
-# ----------------------------------------------------
-
-$(APP_TARGET): $(APP_SRC) ../vsn.mk
- $(vsn_verbose)sed -e 's;%VSN%;$(VSN);' $< > $@
-
-$(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk
- $(vsn_verbose)sed -e 's;%VSN%;$(VSN);' $< > $@
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_spec: opt
- $(INSTALL_DIR) "$(RELSYSDIR)/src"
- $(INSTALL_DATA) $(ERL_FILES) "$(RELSYSDIR)/src"
- $(INSTALL_DIR) "$(RELSYSDIR)/ebin"
- $(INSTALL_DATA) $(TARGETS) "$(RELSYSDIR)/ebin"
-
-release_docs_spec:
-
-
diff --git a/lib/otp_mibs/src/otp_mib.erl b/lib/otp_mibs/src/otp_mib.erl
deleted file mode 100644
index ca868f2817..0000000000
--- a/lib/otp_mibs/src/otp_mib.erl
+++ /dev/null
@@ -1,219 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1996-2018. All 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(otp_mib).
-%%%-----------------------------------------------------------------
-%%% Description: This module implements the OTP-MIB.
-%%% The tables are implemented as shadow tables with the module
-%%% snmp_shadow_table.
-%%%-----------------------------------------------------------------
-
-%% API
--export([load/1, unload/1]).
-
-%% SNMP instrumentation
--export([erl_node_table/1, erl_node_table/3, appl_table/1, appl_table/3]).
-
-%% SNMP shadow functions
--export([update_erl_node_table/0, update_appl_table/0]).
-
-%% Exported for internal use via rpc
--export([get_erl_node/1, get_appls/1]).
-
--deprecated([{load,1,eventually},
- {unload,1,eventually}]).
-
-%% Shadow tables
--record(erlNodeTable,
- {erlNodeId, erlNodeName, erlNodeMachine, erlNodeVersion,
- erlNodeRunQueue,
- erlNodeRunTime, erlNodeWallClock, erlNodeReductions,
- erlNodeProcesses, erlNodeInBytes, erlNodeOutBytes}).
-
--record(applTable, {key = '_', applName = '_', applDescr = '_',
- applVsn = '_'}).
-
-%% Shadow argument macros
--define(erlNodeShadowArgs,
- {erlNodeTable, integer, record_info(fields, erlNodeTable), 5000,
- fun otp_mib:update_erl_node_table/0}).
-
--define(applShadowArgs,
- {applTable, {integer, integer}, record_info(fields, applTable),
- 5000, fun otp_mib:update_appl_table/0}).
-
-%% Misc
--record(erlNodeAlloc, {nodeName, nodeId}).
-
-%%%=========================================================================
-%%% API
-%%%=========================================================================
-
-%%-------------------------------------------------------------------------
-%% load(Agent) -> ok | {error, Reason}
-%% Agent - pid() | atom()
-%% Reason - term()
-%% Description: Loads the OTP-MIB
-%%-------------------------------------------------------------------------
-load(Agent) ->
- MibDir = code:priv_dir(otp_mibs) ++ "/mibs",
- snmpa:load_mibs(Agent, [MibDir ++ "/OTP-MIB"]).
-
-%%-------------------------------------------------------------------------
-%% unload(Agent) -> ok | {error, Reason}
-%% Agent - pid() | atom()
-%% Reason - term()
-%% Description: Loads the OTP-MIB
-%%-------------------------------------------------------------------------
-unload(Agent) ->
- snmpa:unload_mibs(Agent, ["OTP-MIB"]).
-
-
-%%%=========================================================================
-%%% SNMP instrumentation
-%%%=========================================================================
-erl_node_table(new) ->
- Tab = erlNodeAlloc,
- Storage = ram_copies,
- case lists:member(Tab, mnesia:system_info(tables)) of
- true ->
- case mnesia:table_info(Tab, storage_type) of
- unknown ->
- {atomic, ok} = mnesia:add_table_copy(Tab, node(), Storage);
- Storage ->
- catch delete_all(Tab)
- end;
- false ->
- Nodes = [node()],
- Props = [{type, set},
- {attributes, record_info(fields, erlNodeAlloc)},
- {local_content, true},
- {Storage, Nodes}],
- {atomic, ok} = mnesia:create_table(Tab, Props)
- end,
- ok = mnesia:dirty_write({erlNodeAlloc, next_index, 1}),
- update_node_alloc([node() | nodes()]),
- snmp_shadow_table:table_func(new, ?erlNodeShadowArgs).
-
-erl_node_table(Op, RowIndex, Cols) ->
- snmp_shadow_table:table_func(Op, RowIndex, Cols, ?erlNodeShadowArgs).
-
-
-appl_table(Op) ->
- snmp_shadow_table:table_func(Op, ?applShadowArgs).
-appl_table(Op, RowIndex, Cols) ->
- snmp_shadow_table:table_func(Op, RowIndex, Cols, ?applShadowArgs).
-
-
-%%%=========================================================================
-%%% SNMP shadow functions
-%%%=========================================================================
-update_erl_node_table() ->
- delete_all(erlNodeTable),
- Nodes = [node() | nodes()],
- update_node_alloc(Nodes),
- lists:foreach(
- fun(Node) ->
- [{_,_,Idx}] = mnesia:dirty_read({erlNodeAlloc, Node}),
- ErlNode = rpc:call(Node, otp_mib, get_erl_node, [Idx]),
- ok = mnesia:dirty_write(ErlNode)
- end, Nodes).
-
-update_appl_table() ->
- delete_all(applTable),
- Nodes = [node() | nodes()],
- update_node_alloc(Nodes),
- lists:foreach(
- fun(Node) ->
- [{_,_,Idx}] = mnesia:dirty_read({erlNodeAlloc, Node}),
- Appls = rpc:call(Node, otp_mib, get_appls, [Idx]),
- lists:foreach(fun(Appl) ->
- ok = mnesia:dirty_write(Appl)
- end, Appls)
- end, Nodes).
-
-%%%========================================================================
-%%% Exported for internal use via rpc
-%%%========================================================================
-get_erl_node(Id) ->
- RunQueue = erlang:statistics(run_queue),
- RunTime = element(1, erlang:statistics(runtime)),
- WallClock = element(1, erlang:statistics(wall_clock)),
- Reductions = element(1, erlang:statistics(reductions)),
- Processes = length(processes()),
- IO = erlang:statistics(io),
- InBytes = element(2, element(1, IO)),
- OutBytes = element(2, element(2, IO)),
- #erlNodeTable{erlNodeId = truncate_int('Integer32', Id),
- erlNodeName = atom_to_list(node()),
- erlNodeVersion = erlang:system_info(version),
- erlNodeMachine = erlang:system_info(machine),
- erlNodeRunQueue = truncate_int('Unsigned32', RunQueue),
- erlNodeRunTime = truncate_int('Counter64', RunTime),
- erlNodeWallClock = truncate_int('Counter64', WallClock),
- erlNodeReductions = truncate_int('Counter64', Reductions),
- erlNodeProcesses = truncate_int('Unsigned32', Processes),
- erlNodeInBytes = truncate_int('Counter64', InBytes),
- erlNodeOutBytes = truncate_int('Counter64', OutBytes)}.
-
-get_appls(NodeId) ->
- element(1,
- lists:mapfoldl(
- fun({ApplName, ApplDescr, ApplVsn}, ApplId) ->
- {#applTable{key = {NodeId, ApplId},
- applName = atom_to_list(ApplName),
- applDescr = ApplDescr,
- applVsn = ApplVsn},
- ApplId + 1}
- end, 1, application:which_applications())).
-
-%%%========================================================================
-%%% Internal functions
-%%%========================================================================
-update_node_alloc([Node | T]) ->
- case mnesia:dirty_read({erlNodeAlloc, Node}) of
- [] ->
- [{_, _, Idx}] = mnesia:dirty_read({erlNodeAlloc, next_index}),
- ok = mnesia:dirty_write(#erlNodeAlloc{nodeName = Node,
- nodeId = Idx}),
- ok = mnesia:dirty_write({erlNodeAlloc, next_index, Idx + 1});
- _ ->
- ok
- end,
- update_node_alloc(T);
-update_node_alloc([]) -> ok.
-
-delete_all(Name) -> delete_all(mnesia:dirty_first(Name), Name).
-delete_all('$end_of_table', _Name) -> done;
-delete_all(Key, Name) ->
- Next = mnesia:dirty_next(Name, Key),
- ok = mnesia:dirty_delete({Name, Key}),
- delete_all(Next, Name).
-
-%% This will return a value limited to fit into the specified type.
-%% While counter types will be resetted, other integer types will
-%% only be restricted to the valid range.
-truncate_int('Counter64', Value) when Value < 0 -> 0;
-truncate_int('Counter64', Value) -> Value rem 18446744073709551615;
-truncate_int('Unsigned32', Value) when Value < 0 -> 0;
-truncate_int('Unsigned32', Value) when Value > 4294967295 -> 4294967295;
-truncate_int('Unsigned32', Value) -> Value;
-truncate_int('Integer32', Value) when Value < -2147483648 -> -2147483648;
-truncate_int('Integer32', Value) when Value > 2147483647 -> 2147483647;
-truncate_int('Integer32', Value) -> Value.
diff --git a/lib/otp_mibs/src/otp_mibs.app.src b/lib/otp_mibs/src/otp_mibs.app.src
deleted file mode 100644
index 75ef25c366..0000000000
--- a/lib/otp_mibs/src/otp_mibs.app.src
+++ /dev/null
@@ -1,30 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2003-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%
-%%
-
-{application, otp_mibs,
- [{description, "SNMP managment information base for Erlang/OTP nodes."},
- {vsn, "%VSN%"},
- {modules, [otp_mib]},
- {registered, []},
- {applications, [kernel, stdlib, snmp]},
- {env,[]},
- {runtime_dependencies, ["stdlib-2.0","snmp-4.25.1","mnesia-4.12",
- "kernel-3.0","erts-6.0"]}]}.
-
diff --git a/lib/otp_mibs/test/Makefile b/lib/otp_mibs/test/Makefile
deleted file mode 100644
index 9736cf8bce..0000000000
--- a/lib/otp_mibs/test/Makefile
+++ /dev/null
@@ -1,85 +0,0 @@
-#
-# %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%
-#
-include $(ERL_TOP)/make/target.mk
-
-include $(ERL_TOP)/make/$(TARGET)/otp.mk
-
-# ----------------------------------------------------
-# Target Specs
-# ----------------------------------------------------
-
-MODULES= otp_mibs_SUITE
-
-EBIN = .
-
-HRL_FILES=
-
-ERL_FILES= $(MODULES:%=%.erl)
-
-TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR))
-
-SOURCE = $(ERL_FILES) $(HRL_FILES)
-
-EMAKEFILE=Emakefile
-
-# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/otp_mibs_test
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-ERL_MAKE_FLAGS +=
-ERL_COMPILE_FLAGS += \
- -I$(ERL_TOP)/lib/snmp/include
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-
-make_emakefile:
- $(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES)\
- > $(EMAKEFILE)
-
-tests debug opt: make_emakefile
- erl $(ERL_MAKE_FLAGS) -make
-
-clean:
- rm -f $(EMAKEFILE)
- rm -f $(TARGET_FILES)
- rm -f core *~
-
-docs:
-
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_spec:
-
-release_tests_spec: make_emakefile
- $(INSTALL_DIR) "$(RELSYSDIR)"
- $(INSTALL_DATA) $(EMAKEFILE) $(SOURCE) "$(RELSYSDIR)"
- $(INSTALL_DATA) otp_mibs_SUITE.cfg "$(RELSYSDIR)"
-
-release_docs_spec:
diff --git a/lib/otp_mibs/test/otp_mibs_SUITE.cfg b/lib/otp_mibs/test/otp_mibs_SUITE.cfg
deleted file mode 100644
index d01cf92104..0000000000
--- a/lib/otp_mibs/test/otp_mibs_SUITE.cfg
+++ /dev/null
@@ -1,15 +0,0 @@
-%% -*- erlang -*-
-{snmp,
- [
- {start_agent,true},
- {users,
- [
- {otp_mibs_test,[snmpm_user_default,[]]}
- ]},
- {managed_agents,
- [
- {otp_mibs_test, [otp_mibs_test, {127,0,0,1}, 4000, []]}
- ]},
- {agent_sysname,"Test otp_mibs"},
- {mgr_port,5001}
- ]}.
diff --git a/lib/otp_mibs/test/otp_mibs_SUITE.erl b/lib/otp_mibs/test/otp_mibs_SUITE.erl
deleted file mode 100644
index cb3cd28200..0000000000
--- a/lib/otp_mibs/test/otp_mibs_SUITE.erl
+++ /dev/null
@@ -1,255 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-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(otp_mibs_SUITE).
-
-%%-----------------------------------------------------------------
-%% This suite can no longer be executed standalone, i.e. it must be
-%% executed with common test. The reason is that ct_snmp is used
-%% instead of the snmp application directly. The suite requires a
-%% config file, otp_mibs_SUITE.cfg, found in the same directory as
-%% the suite.
-%%
-%% Execute with:
-%% > ct_run -suite otp_mibs_SUITE -config otp_mibs_SUITE.cfg
-%%-----------------------------------------------------------------
-
--include_lib("common_test/include/ct.hrl").
--include_lib("otp_mibs/include/OTP-MIB.hrl").
--include_lib("snmp/include/snmp_types.hrl").
-
-% Test server specific exports
--export([all/0,
- suite/0,
- groups/0,
- init_per_group/2,
- end_per_group/2,
- init_per_suite/1,
- end_per_suite/1,
- init_per_testcase/2,
- end_per_testcase/2]).
-
-% Test cases must be exported.
--export([app/1, appup/1, nt_basic_types/1, nt_high_reduction_count/1]).
-
--define(TRAP_UDP, 5000).
--define(AGENT_UDP, 4000).
--define(CONF_FILE_VER, [v2]).
--define(SYS_NAME, "Test otp_mibs").
--define(MAX_MSG_SIZE, 484).
--define(ENGINE_ID, "mgrEngine").
--define(MGR_PORT, 5001).
-
-%% Since some cases are only interested in single entries of the OTP-MIB's
-%% node table, one row must be chosen. The first row should be sufficient
-%% for this.
--define(NODE_ENTRY, 1).
-
-%%---------------------------------------------------------------------
-%% CT setup
-%%---------------------------------------------------------------------
-
-init_per_testcase(_Case, Config) when is_list(Config) ->
- Dog = test_server:timetrap(test_server:minutes(6)),
- [{watchdog, Dog}|Config].
-
-end_per_testcase(_Case, Config) when is_list(Config) ->
- Dog = ?config(watchdog, Config),
- test_server:timetrap_cancel(Dog),
- Config.
-
-suite() -> [{ct_hooks,[ts_install_cth]}, {require, snmp_mgr_agent, snmp}].
-
-all() -> [{group, app}, {group, node_table}].
-
-groups() -> [{app, [], [app, appup]},
- {node_table, [], [nt_basic_types, nt_high_reduction_count]}].
-
-init_per_group(_GroupName, Config) -> Config.
-
-end_per_group(_GroupName, Config) -> Config.
-
-init_per_suite(Config) ->
- ?line application:start(sasl),
- ?line application:start(mnesia),
- ?line application:start(otp_mibs),
-
- ok = ct_snmp:start(Config,snmp_mgr_agent),
-
- %% Load the mibs that should be tested
- otp_mib:load(snmp_master_agent),
-
- Config.
-
-end_per_suite(Config) ->
- PrivDir = ?config(priv_dir, Config),
- ConfDir = filename:join(PrivDir,"conf"),
- DbDir = filename:join(PrivDir,"db"),
- MgrDir = filename:join(PrivDir, "mgr"),
-
- %% Uload mibs
- otp_mib:unload(snmp_master_agent),
-
- %% Clean up
- application:stop(snmp),
- application:stop(mnesia),
- application:stop(otp_mibs),
-
- del_dir(ConfDir),
- del_dir(DbDir),
- (catch del_dir(MgrDir)),
- ok.
-
-%%---------------------------------------------------------------------
-%% Test cases
-%%---------------------------------------------------------------------
-
-%% Test that the otp_mibs app file is ok
-app(Config) when is_list(Config) ->
- ok = ?t:app_test(otp_mibs).
-
-%% Test that the otp_mibs appup file is ok
-appup(Config) when is_list(Config) ->
- ok = ?t:appup_test(otp_mibs).
-
-nt_basic_types(suite) ->
- [];
-nt_basic_types(doc) ->
- ["Query every item of the node table and check its variable "
- "type and content for sensible values."];
-nt_basic_types(Config) when is_list(Config) ->
- ok = otp_mib:update_erl_node_table(),
-
- NodeNameId = ?erlNodeEntry ++ [?erlNodeName, ?NODE_ENTRY],
- {noError, 0, [NodeNameVal]} = snmp_get([NodeNameId]),
- #varbind{variabletype = 'OCTET STRING'} = NodeNameVal,
- true = is_list(NodeNameVal#varbind.value),
-
- NodeMachineId = ?erlNodeEntry ++ [?erlNodeMachine, ?NODE_ENTRY],
- {noError, 0, [NodeMachineVal]} = snmp_get([NodeMachineId]),
- #varbind{variabletype = 'OCTET STRING'} = NodeMachineVal,
- true = is_list(NodeMachineVal#varbind.value),
-
- NodeVersionId = ?erlNodeEntry ++ [?erlNodeVersion, ?NODE_ENTRY],
- {noError, 0, [NodeVersionVal]} = snmp_get([NodeVersionId]),
- #varbind{variabletype = 'OCTET STRING'} = NodeVersionVal,
- true = is_list(NodeVersionVal#varbind.value),
-
- NodeRunQueueId = ?erlNodeEntry ++ [?erlNodeRunQueue, ?NODE_ENTRY],
- {noError, 0, [NodeRunQueueVal]} = snmp_get([NodeRunQueueId]),
- #varbind{variabletype = 'Unsigned32'} = NodeRunQueueVal,
- true = is_integer(NodeRunQueueVal#varbind.value),
- NodeRunQueueVal#varbind.value >= 0,
- NodeRunQueueVal#varbind.value =< 4294967295,
-
- NodeRunTimeId = ?erlNodeEntry ++ [?erlNodeRunTime, ?NODE_ENTRY],
- {noError, 0, [NodeRunTimeVal]} = snmp_get([NodeRunTimeId]),
- #varbind{variabletype = 'Counter64'} = NodeRunTimeVal,
- true = is_integer(NodeRunTimeVal#varbind.value),
- NodeRunTimeVal#varbind.value >= 0,
- NodeRunTimeVal#varbind.value =< 18446744073709551615,
-
- NodeWallClockId = ?erlNodeEntry ++ [?erlNodeWallClock, ?NODE_ENTRY],
- {noError, 0, [NodeWallClockVal]} = snmp_get([NodeWallClockId]),
- #varbind{variabletype = 'Counter64'} = NodeWallClockVal,
- true = is_integer(NodeWallClockVal#varbind.value),
- NodeWallClockVal#varbind.value >= 0,
- NodeWallClockVal#varbind.value =< 18446744073709551615,
-
- NodeReductionsId = ?erlNodeEntry ++ [?erlNodeReductions, ?NODE_ENTRY],
- {noError, 0, [NodeReductionsVal]} = snmp_get([NodeReductionsId]),
- #varbind{variabletype = 'Counter64'} = NodeReductionsVal,
- true = is_integer(NodeReductionsVal#varbind.value),
- NodeReductionsVal#varbind.value >= 0,
- NodeReductionsVal#varbind.value =< 18446744073709551615,
-
- NodeProcessesId = ?erlNodeEntry ++ [?erlNodeProcesses, ?NODE_ENTRY],
- {noError, 0, [NodeProcessesVal]} = snmp_get([NodeProcessesId]),
- #varbind{variabletype = 'Unsigned32'} = NodeProcessesVal,
- true = is_integer(NodeProcessesVal#varbind.value),
- NodeProcessesVal#varbind.value >= 0,
- NodeProcessesVal#varbind.value =< 4294967295,
-
- NodeInBytesId = ?erlNodeEntry ++ [?erlNodeInBytes, ?NODE_ENTRY],
- {noError, 0, [NodeInBytesVal]} = snmp_get([NodeInBytesId]),
- #varbind{variabletype = 'Counter64'} = NodeInBytesVal,
- true = is_integer(NodeInBytesVal#varbind.value),
- NodeInBytesVal#varbind.value >= 0,
- NodeInBytesVal#varbind.value =< 18446744073709551615,
-
- NodeOutBytesId = ?erlNodeEntry ++ [?erlNodeOutBytes, ?NODE_ENTRY],
- {noError, 0, [NodeOutBytesVal]} = snmp_get([NodeOutBytesId]),
- #varbind{variabletype = 'Counter64'} = NodeOutBytesVal,
- true = is_integer(NodeOutBytesVal#varbind.value),
- NodeOutBytesVal#varbind.value >= 0,
- NodeOutBytesVal#varbind.value =< 18446744073709551615,
-
- ok.
-
-nt_high_reduction_count(suite) ->
- [];
-nt_high_reduction_count(doc) ->
- ["Check that no error occurs when the erlNodeReductions field"
- "exceeds the 32bit boundary, this may take about 10min."];
-nt_high_reduction_count(Config) when is_list(Config) ->
- NodeReductions = ?erlNodeEntry ++ [?erlNodeReductions, ?NODE_ENTRY],
-
- BumpFun = fun(F, Limit) ->
- case erlang:statistics(reductions) of
- {Total, _} when Total < Limit ->
- F(F, Limit);
- _ ->
- ok
- end
- end,
-
- ok = otp_mib:update_erl_node_table(),
-
- {noError, 0, [StartVal]} = snmp_get([NodeReductions]),
- #varbind{variabletype = 'Counter64'} = StartVal,
- true = is_integer(StartVal#varbind.value),
- StartVal#varbind.value >= 0,
- case StartVal#varbind.value =< 4294967295 of
- true ->
- ok = otp_mib:update_erl_node_table(),
- BumpFun(BumpFun, 4294967295),
- {noError, 0, [EndVal]} = snmp_get([NodeReductions]),
- #varbind{variabletype = 'Counter64'} = EndVal,
- true = is_integer(EndVal#varbind.value),
- EndVal#varbind.value >= 4294967295,
- EndVal#varbind.value =< 18446744073709551615;
- false ->
- %% no need to bump more reductions, since the initial get
- %% command already returned successfully with a large value
- ok
- end.
-
-%%---------------------------------------------------------------------
-%% Internal functions
-%%---------------------------------------------------------------------
-
-snmp_get(OIdList) ->
- ct_snmp:get_values(otp_mibs_test, OIdList, snmp_mgr_agent).
-
-del_dir(Dir) ->
- io:format("Deleting: ~s~n",[Dir]),
- {ok, Files} = file:list_dir(Dir),
- FullPathFiles = lists:map(fun(File) -> filename:join(Dir, File) end, Files),
- lists:foreach(fun file:delete/1, FullPathFiles),
- file:del_dir(Dir).
diff --git a/lib/otp_mibs/vsn.mk b/lib/otp_mibs/vsn.mk
deleted file mode 100644
index 1b0444afcd..0000000000
--- a/lib/otp_mibs/vsn.mk
+++ /dev/null
@@ -1,5 +0,0 @@
-OTP_MIBS_VSN = 1.2.1
-
-# Note: The branch 'otp_mibs' is defunct as of otp_mibs-1.0.4 and
-# should NOT be used again.
-
diff --git a/lib/parsetools/doc/src/leex.xml b/lib/parsetools/doc/src/leex.xml
index 1227625287..3b82f60201 100644
--- a/lib/parsetools/doc/src/leex.xml
+++ b/lib/parsetools/doc/src/leex.xml
@@ -21,7 +21,7 @@
<rev>A</rev>
<file>leex.xml</file>
</header>
- <module>leex</module>
+ <module since="">leex</module>
<modulesummary>Lexical analyzer generator for Erlang</modulesummary>
<description>
<p>A regular expression based lexical analyzer generator for
@@ -38,7 +38,8 @@ Token = tuple()</code>
</section>
<funcs>
<func>
- <name>file(FileName, [, Options]) -> LeexRet</name>
+ <name since="">file(FileName) -> LeexRet</name>
+ <name since="OTP R16B02">file(FileName, Options) -> LeexRet</name>
<fsummary>Generate a lexical analyzer</fsummary>
<type>
<v>FileName = filename()</v>
@@ -124,7 +125,7 @@ Token = tuple()</code>
</desc>
</func>
<func>
- <name>format_error(ErrorInfo) -> Chars</name>
+ <name since="">format_error(ErrorInfo) -> Chars</name>
<fsummary>Return an English description of a an error tuple.</fsummary>
<type>
<v>Chars = [char() | Chars]</v>
@@ -145,8 +146,8 @@ Token = tuple()</code>
<funcs>
<func>
- <name>string(String) -> StringRet</name>
- <name>string(String, StartLine) -> StringRet</name>
+ <name since="">string(String) -> StringRet</name>
+ <name since="">string(String, StartLine) -> StringRet</name>
<fsummary>Generated by Leex</fsummary>
<type>
<v>String = string()</v>
@@ -163,9 +164,9 @@ Token = tuple()</code>
</func>
<func>
- <name>token(Cont, Chars) -> {more,Cont1} | {done,TokenRet,RestChars}
+ <name since="">token(Cont, Chars) -> {more,Cont1} | {done,TokenRet,RestChars}
</name>
- <name>token(Cont, Chars, StartLine) -> {more,Cont1}
+ <name since="">token(Cont, Chars, StartLine) -> {more,Cont1}
| {done,TokenRet,RestChars}
</name>
<fsummary>Generated by Leex</fsummary>
@@ -198,9 +199,9 @@ io:request(InFile, {get_until,Prompt,Module,token,[Line]})
</func>
<func>
- <name>tokens(Cont, Chars) -> {more,Cont1} | {done,TokensRet,RestChars}
+ <name since="">tokens(Cont, Chars) -> {more,Cont1} | {done,TokensRet,RestChars}
</name>
- <name>tokens(Cont, Chars, StartLine) ->
+ <name since="">tokens(Cont, Chars, StartLine) ->
{more,Cont1} | {done,TokensRet,RestChars}
</name>
<fsummary>Generated by Leex</fsummary>
diff --git a/lib/parsetools/doc/src/yecc.xml b/lib/parsetools/doc/src/yecc.xml
index 5f95b5c150..67a2c95c25 100644
--- a/lib/parsetools/doc/src/yecc.xml
+++ b/lib/parsetools/doc/src/yecc.xml
@@ -32,7 +32,7 @@
<rev>B</rev>
<file>yecc.sgml</file>
</header>
- <module>yecc</module>
+ <module since="">yecc</module>
<modulesummary>LALR-1 Parser Generator</modulesummary>
<description>
<p>An LALR-1 parser generator for Erlang, similar to <c>yacc</c>.
@@ -46,7 +46,7 @@
</description>
<funcs>
<func>
- <name>file(Grammarfile [, Options]) -> YeccRet</name>
+ <name since="">file(Grammarfile [, Options]) -> YeccRet</name>
<fsummary>Give information about resolved and unresolved parse action conflicts.</fsummary>
<type>
<v>Grammarfile = filename()</v>
@@ -137,7 +137,7 @@
</desc>
</func>
<func>
- <name>format_error(Reason) -> Chars</name>
+ <name since="">format_error(Reason) -> Chars</name>
<fsummary>Return an English description of a an error tuple.</fsummary>
<type>
<v>Reason =&nbsp;-&nbsp;as returned by yecc:file/1,2&nbsp;-</v>
diff --git a/lib/public_key/doc/src/public_key.xml b/lib/public_key/doc/src/public_key.xml
index ee3877ddd0..9fcedf6ef9 100644
--- a/lib/public_key/doc/src/public_key.xml
+++ b/lib/public_key/doc/src/public_key.xml
@@ -31,7 +31,7 @@
<date></date>
<rev></rev>
</header>
- <module>public_key</module>
+ <module since="">public_key</module>
<modulesummary>API module for public-key infrastructure.</modulesummary>
<description>
<p>Provides functions to handle public-key infrastructure,
@@ -176,7 +176,7 @@
<funcs>
<func>
- <name name="compute_key" arity="2"/>
+ <name name="compute_key" arity="2" since="OTP R16B01"/>
<fsummary>Computes shared secret.</fsummary>
<desc>
<p>Computes shared secret.</p>
@@ -184,7 +184,7 @@
</func>
<func>
- <name name="compute_key" arity="3"/>
+ <name name="compute_key" arity="3" since="OTP R16B01"/>
<fsummary>Computes shared secret.</fsummary>
<desc>
<p>Computes shared secret.</p>
@@ -192,8 +192,8 @@
</func>
<func>
- <name name="decrypt_private" arity="2"/>
- <name name="decrypt_private" arity="3"/>
+ <name name="decrypt_private" arity="2" since="OTP R14B"/>
+ <name name="decrypt_private" arity="3" since="OTP R14B"/>
<fsummary>Public-key decryption.</fsummary>
<desc>
<p>Public-key decryption using the private key. See also <seealso
@@ -202,8 +202,8 @@
</func>
<func>
- <name name="decrypt_public" arity="2"/>
- <name name="decrypt_public" arity="3"/>
+ <name name="decrypt_public" arity="2" since="OTP R14B"/>
+ <name name="decrypt_public" arity="3" since="OTP R14B"/>
<fsummary>Public-key decryption.</fsummary>
<desc>
<p>Public-key decryption using the public key. See also <seealso
@@ -212,7 +212,7 @@
</func>
<func>
- <name name="der_decode" arity="2"/>
+ <name name="der_decode" arity="2" since="OTP R14B"/>
<fsummary>Decodes a public-key ASN.1 DER encoded entity.</fsummary>
<desc>
<p>Decodes a public-key ASN.1 DER encoded entity.</p>
@@ -220,7 +220,7 @@
</func>
<func>
- <name name="der_encode" arity="2"/>
+ <name name="der_encode" arity="2" since="OTP R14B"/>
<fsummary>Encodes a public-key entity with ASN.1 DER encoding.</fsummary>
<desc>
<p>Encodes a public-key entity with ASN.1 DER encoding.</p>
@@ -228,7 +228,7 @@
</func>
<func>
- <name name="dh_gex_group" arity="4"/>
+ <name name="dh_gex_group" arity="4" since="OTP 18.2"/>
<fsummary>Selects a group for Diffie-Hellman key exchange</fsummary>
<desc>
<p>Selects a group for Diffie-Hellman key exchange with the key size in the range <c>MinSize...MaxSize</c>
@@ -249,8 +249,8 @@
</func>
<func>
- <name name="encrypt_private" arity="2"/>
- <name name="encrypt_private" arity="3"/>
+ <name name="encrypt_private" arity="2" since="OTP R14B"/>
+ <name name="encrypt_private" arity="3" since="OTP 21.1"/>
<fsummary>Public-key encryption using the private key.</fsummary>
<desc>
<p>Public-key encryption using the private key.
@@ -260,8 +260,8 @@
</func>
<func>
- <name name="encrypt_public" arity="2"/>
- <name name="encrypt_public" arity="3"/>
+ <name name="encrypt_public" arity="2" since="OTP R14B"/>
+ <name name="encrypt_public" arity="3" since="OTP 21.1"/>
<fsummary>Public-key encryption using the public key.</fsummary>
<desc>
<p>Public-key encryption using the public key. See also <seealso
@@ -270,7 +270,7 @@
</func>
<func>
- <name name="generate_key" arity="1"/>
+ <name name="generate_key" arity="1" since="OTP R16B01"/>
<fsummary>Generates a new keypair.</fsummary>
<desc>
<p>Generates a new keypair. Note that except for Diffie-Hellman
@@ -281,7 +281,7 @@
</func>
<func>
- <name name="pem_decode" arity="1"/>
+ <name name="pem_decode" arity="1" since="OTP R14B"/>
<fsummary>Decodes PEM binary data and returns
entries as ASN.1 DER encoded entities.</fsummary>
<desc>
@@ -291,7 +291,7 @@
</func>
<func>
- <name name="pem_encode" arity="1"/>
+ <name name="pem_encode" arity="1" since="OTP R14B"/>
<fsummary>Creates a PEM binary.</fsummary>
<desc>
<p>Creates a PEM binary.</p>
@@ -299,8 +299,8 @@
</func>
<func>
- <name name="pem_entry_decode" arity="1"/>
- <name name="pem_entry_decode" arity="2"/>
+ <name name="pem_entry_decode" arity="1" since="OTP R14B"/>
+ <name name="pem_entry_decode" arity="2" since="OTP R14B"/>
<fsummary>Decodes a PEM entry.</fsummary>
<desc>
<p>Decodes a PEM entry. <c>pem_decode/1</c> returns a list of PEM
@@ -311,8 +311,8 @@
</func>
<func>
- <name name="pem_entry_encode" arity="2"/>
- <name name="pem_entry_encode" arity="3"/>
+ <name name="pem_entry_encode" arity="2" since="OTP R14B"/>
+ <name name="pem_entry_encode" arity="3" since="OTP R14B"/>
<fsummary>Creates a PEM entry that can be fed to <c>pem_encode/1</c>.</fsummary>
<desc>
<p>Creates a PEM entry that can be feed to <c>pem_encode/1</c>.</p>
@@ -326,7 +326,7 @@
</func>
<func>
- <name name="pkix_decode_cert" arity="2"/>
+ <name name="pkix_decode_cert" arity="2" since=""/>
<fsummary>Decodes an ASN.1 DER-encoded PKIX x509 certificate.</fsummary>
<desc>
<p>Decodes an ASN.1 DER-encoded PKIX certificate. Option <c>otp</c>
@@ -337,7 +337,7 @@
</func>
<func>
- <name name="pkix_encode" arity="3"/>
+ <name name="pkix_encode" arity="3" since="OTP R14B"/>
<fsummary>DER encodes a PKIX x509 certificate or part of such a
certificate.</fsummary>
<desc>
@@ -349,7 +349,7 @@
</func>
<func>
- <name name="pkix_is_issuer" arity="2"/>
+ <name name="pkix_is_issuer" arity="2" since="OTP R14B"/>
<fsummary>Checks if <c>IssuerCert</c> issued <c>Cert</c>.</fsummary>
<desc>
<p>Checks if <c>IssuerCert</c> issued <c>Cert</c>.</p>
@@ -357,7 +357,7 @@
</func>
<func>
- <name name="pkix_is_fixed_dh_cert" arity="1"/>
+ <name name="pkix_is_fixed_dh_cert" arity="1" since="OTP R14B"/>
<fsummary>Checks if a certificate is a fixed Diffie-Hellman certificate.</fsummary>
<desc>
<p>Checks if a certificate is a fixed Diffie-Hellman certificate.</p>
@@ -365,7 +365,7 @@
</func>
<func>
- <name name="pkix_is_self_signed" arity="1"/>
+ <name name="pkix_is_self_signed" arity="1" since="OTP R14B"/>
<fsummary>Checks if a certificate is self-signed.</fsummary>
<desc>
<p>Checks if a certificate is self-signed.</p>
@@ -373,7 +373,7 @@
</func>
<func>
- <name name="pkix_issuer_id" arity="2"/>
+ <name name="pkix_issuer_id" arity="2" since="OTP R14B"/>
<fsummary>Returns the issuer id.</fsummary>
<desc>
<p>Returns the issuer id.</p>
@@ -381,7 +381,7 @@
</func>
<func>
- <name name="pkix_normalize_name" arity="1"/>
+ <name name="pkix_normalize_name" arity="1" since="OTP R14B"/>
<fsummary>Normalizes an issuer name so that it can be easily
compared to another issuer name.</fsummary>
<desc>
@@ -391,7 +391,7 @@
</func>
<func>
- <name>pkix_path_validation(TrustedCert, CertChain, Options) -> {ok, {PublicKeyInfo, PolicyTree}} | {error, {bad_cert, Reason}} </name>
+ <name since="OTP R16B">pkix_path_validation(TrustedCert, CertChain, Options) -> {ok, {PublicKeyInfo, PolicyTree}} | {error, {bad_cert, Reason}} </name>
<fsummary>Performs a basic path validation according to RFC 5280.</fsummary>
<type>
<v>TrustedCert = #'OTPCertificate'{} | der_encoded() | atom()</v>
@@ -491,7 +491,7 @@ fun(OtpCert :: #'OTPCertificate'{},
</func>
<func>
- <name name="pkix_crl_issuer" arity="1"/>
+ <name name="pkix_crl_issuer" arity="1" since="OTP 17.5"/>
<fsummary>Returns the issuer of the <c>CRL</c>.</fsummary>
<desc>
<p>Returns the issuer of the <c>CRL</c>.</p>
@@ -499,7 +499,7 @@ fun(OtpCert :: #'OTPCertificate'{},
</func>
<func>
- <name name="pkix_crls_validate" arity="3"/>
+ <name name="pkix_crls_validate" arity="3" since="OTP R16B"/>
<fsummary>Performs CRL validation.</fsummary>
<desc>
<p>Performs CRL validation. It is intended to be called from
@@ -541,7 +541,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
<tag>{undetermined_details, boolean()}</tag>
<item>
- <p>Defaults to false. When revocation status can not be
+ <p>Defaults to false. When revocation status cannot be
determined, and this option is set to true, details of why no
CRLs where accepted are included in the return value.</p>
</item>
@@ -551,7 +551,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
</func>
<func>
- <name name="pkix_crl_verify" arity="2"/>
+ <name name="pkix_crl_verify" arity="2" since="OTP 17.5"/>
<fsummary> Verify that <c>Cert</c> is the <c> CRL</c> signer. </fsummary>
<desc>
<p>Verify that <c>Cert</c> is the <c>CRL</c> signer.</p>
@@ -559,7 +559,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
</func>
<func>
- <name name="pkix_dist_point" arity="1"/>
+ <name name="pkix_dist_point" arity="1" since="OTP 17.5"/>
<fsummary>Creates a distribution point for CRLs issued by the same issuer as <c>Cert</c>.</fsummary>
<desc>
<p>Creates a distribution point for CRLs issued by the same issuer as <c>Cert</c>.
@@ -570,7 +570,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
</func>
<func>
- <name name="pkix_dist_points" arity="1"/>
+ <name name="pkix_dist_points" arity="1" since="OTP 17.5"/>
<fsummary> Extracts distribution points from the certificates extensions.</fsummary>
<desc>
<p> Extracts distribution points from the certificates extensions.</p>
@@ -578,7 +578,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
</func>
<func>
- <name name="pkix_match_dist_point" arity="2"/>
+ <name name="pkix_match_dist_point" arity="2" since="OTP 19.0"/>
<fsummary>Checks whether the given distribution point matches the
Issuing Distribution Point of the CRL.</fsummary>
<desc>
@@ -590,7 +590,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
</func>
<func>
- <name name="pkix_sign" arity="2"/>
+ <name name="pkix_sign" arity="2" since="OTP R14B"/>
<fsummary>Signs certificate.</fsummary>
<desc>
<p>Signs an 'OTPTBSCertificate'. Returns the corresponding
@@ -599,7 +599,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
</func>
<func>
- <name name="pkix_sign_types" arity="1"/>
+ <name name="pkix_sign_types" arity="1" since="OTP R16B01"/>
<fsummary>Translates signature algorithm OID to Erlang digest and signature algorithm types.</fsummary>
<desc>
<p>Translates signature algorithm OID to Erlang digest and signature types.
@@ -609,8 +609,8 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
</func>
<func>
- <name>pkix_test_data(Options) -> Config </name>
- <name>pkix_test_data([chain_opts()]) -> [conf_opt()]</name>
+ <name since="OTP 20.1">pkix_test_data(Options) -> Config </name>
+ <name since="OTP 20.1">pkix_test_data([chain_opts()]) -> [conf_opt()]</name>
<fsummary>Creates certificate test data.</fsummary>
<type>
<v>Options = #{chain_type() := chain_opts()} </v>
@@ -644,7 +644,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
<v>conf_opt() = {cert, der_encoded()} | {key, PrivateKey} |{cacerts, [der_encoded()]}</v>
<d>
This is a subset of the type
- <seealso marker="ssl:ssl#type-ssloption"> ssl:ssl_option()</seealso>.
+ <seealso marker="ssl:ssl#type-tls_option"> ssl:tls_option()</seealso>.
<c>PrivateKey</c> is what
<seealso marker="#generate_key-1">generate_key/1</seealso>
returns.
@@ -736,13 +736,13 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
<note><p>
Note that the generated certificates and keys does not provide a formally correct PKIX-trust-chain
- and they can not be used to achieve real security. This function is provided for testing purposes only.
+ and they cannot be used to achieve real security. This function is provided for testing purposes only.
</p></note>
</desc>
</func>
<func>
- <name>pkix_test_root_cert(Name, Options) -> RootCert</name>
+ <name since="OTP 20.2">pkix_test_root_cert(Name, Options) -> RootCert</name>
<fsummary>Generates a test data root cert.</fsummary>
<type>
<v>Name = string()</v>
@@ -772,7 +772,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
</func>
<func>
- <name name="pkix_verify" arity="2"/>
+ <name name="pkix_verify" arity="2" since="OTP R14B"/>
<fsummary>Verifies PKIX x.509 certificate signature.</fsummary>
<desc>
<p>Verifies PKIX x.509 certificate signature.</p>
@@ -780,8 +780,8 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
</func>
<func>
- <name>pkix_verify_hostname(Cert, ReferenceIDs) -> boolean()</name>
- <name>pkix_verify_hostname(Cert, ReferenceIDs, Opts) -> boolean()</name>
+ <name since="OTP 19.3">pkix_verify_hostname(Cert, ReferenceIDs) -> boolean()</name>
+ <name since="OTP 19.3">pkix_verify_hostname(Cert, ReferenceIDs, Opts) -> boolean()</name>
<fsummary>Verifies that a PKIX x.509 certificate <i>presented identifier</i> (e.g hostname) is
an expected one.</fsummary>
<type>
@@ -864,7 +864,7 @@ end
</func>
<func>
- <name>pkix_verify_hostname_match_fun(Protcol) -> fun(RefId | FQDN::string(), PresentedID) -> boolean() | default</name>
+ <name since="OTP 21.0">pkix_verify_hostname_match_fun(Protcol) -> fun(RefId | FQDN::string(), PresentedID) -> boolean() | default</name>
<fsummary>Returns a fun that is intendended as argument to the match_fun option in pkix_verify_hostname/3.
</fsummary>
<type>
@@ -889,8 +889,8 @@ end
<func>
- <name name="sign" arity="3"/>
- <name name="sign" arity="4"/>
+ <name name="sign" arity="3" since=""/>
+ <name name="sign" arity="4" since="OTP 20.1"/>
<fsummary>Creates a digital signature.</fsummary>
<desc>
<p>Creates a digital signature.</p>
@@ -901,7 +901,7 @@ end
</func>
<func>
- <name name="ssh_decode" arity="2"/>
+ <name name="ssh_decode" arity="2" since="OTP R14B03"/>
<fsummary>Decodes an SSH file-binary.</fsummary>
<desc>
<p>Decodes an SSH file-binary. In the case of <c>known_hosts</c> or
@@ -933,7 +933,7 @@ end
</func>
<func>
- <name name="ssh_encode" arity="2"/>
+ <name name="ssh_encode" arity="2" since="OTP R14B03"/>
<fsummary>Encodes a list of SSH file entries to a binary.</fsummary>
<desc>
<p>Encodes a list of SSH file entries (public keys and attributes) to a binary. Possible
@@ -947,9 +947,9 @@ end
</func>
<func>
- <name>ssh_hostkey_fingerprint(HostKey) -> string()</name>
- <name>ssh_hostkey_fingerprint(DigestType, HostKey) -> string()</name>
- <name>ssh_hostkey_fingerprint([DigestType], HostKey) -> [string()]</name>
+ <name since="OTP 19.2">ssh_hostkey_fingerprint(HostKey) -> string()</name>
+ <name since="OTP 19.2">ssh_hostkey_fingerprint(DigestType, HostKey) -> string()</name>
+ <name since="OTP 19.2">ssh_hostkey_fingerprint([DigestType], HostKey) -> [string()]</name>
<fsummary>Calculates a ssh fingerprint for a hostkey.</fsummary>
<type>
<v>HostKey = <seealso marker="#type-public_key">public_key()</seealso></v>
@@ -982,8 +982,8 @@ end
</func>
<func>
- <name name="verify" arity="4"/>
- <name name="verify" arity="5"/>
+ <name name="verify" arity="4" since="OTP R14B"/>
+ <name name="verify" arity="5" since="OTP 20.1"/>
<fsummary>Verifies a digital signature.</fsummary>
<desc>
<p>Verifies a digital signature.</p>
@@ -993,7 +993,7 @@ end
</func>
<func>
- <name name="short_name_hash" arity="1"/>
+ <name name="short_name_hash" arity="1" since="OTP 19.0"/>
<fsummary>Generates a short hash of an issuer name.</fsummary>
<desc>
<p>Generates a short hash of an issuer name. The hash is
diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl
index 75d40d2e8a..47c5dbb95a 100644
--- a/lib/public_key/src/public_key.erl
+++ b/lib/public_key/src/public_key.erl
@@ -66,7 +66,7 @@
-export_type([public_key/0, private_key/0, pem_entry/0,
pki_asn1_type/0, asn1_type/0, ssh_file/0, der_encoded/0,
- key_params/0, digest_type/0]).
+ key_params/0, digest_type/0, issuer_name/0, oid/0]).
-type public_key() :: rsa_public_key() | dsa_public_key() | ec_public_key() | ed_public_key() .
-type private_key() :: rsa_private_key() | dsa_private_key() | ec_private_key() | ed_private_key() .
diff --git a/lib/reltool/doc/src/reltool.xml b/lib/reltool/doc/src/reltool.xml
index 874cda8369..136ec2ed69 100644
--- a/lib/reltool/doc/src/reltool.xml
+++ b/lib/reltool/doc/src/reltool.xml
@@ -33,7 +33,7 @@
<date></date>
<rev>%VSN%</rev>
</header>
- <module>reltool</module>
+ <module since="">reltool</module>
<modulesummary>Main API of the Reltool application</modulesummary>
<description>
<p>This is an interface module for the Reltool application.</p>
@@ -503,6 +503,7 @@ sys() = {root_dir, root_dir()}
| {incl_cond, incl_cond()}
| {boot_rel, boot_rel()}
| {rel, rel_name(), rel_vsn(), [rel_app()]}
+ | {rel, rel_name(), rel_vsn(), [rel_app()], [rel_opt()]}
| {relocatable, relocatable()}
| {app_file, app_file()}
| {debug_info, debug_info()}
@@ -534,6 +535,7 @@ rel_app() = app_name()
| {app_name(), app_type()}
| {app_name(), [incl_app()]}
| {app_name(), app_type(), [incl_app()]}
+rel_opt() = {load_dot_erlang, boolean()}
app_name() = atom()
app_type() = permanent | transient | temporary | load | none
app_vsn() = string()
@@ -591,7 +593,7 @@ target_spec() = [target_spec()]
<funcs>
<func>
- <name>create_target(Server, TargetDir) -> ok | {error, Reason}</name>
+ <name since="">create_target(Server, TargetDir) -> ok | {error, Reason}</name>
<fsummary>Create a target system</fsummary>
<type>
<v>Server = server()</v>
@@ -604,7 +606,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>eval_target_spec(TargetSpec, RootDir, TargetDir) -> ok | {error, Reason}</name>
+ <name since="">eval_target_spec(TargetSpec, RootDir, TargetDir) -> ok | {error, Reason}</name>
<fsummary>Create a target system</fsummary>
<type>
<v>TargetSpec = target_spec()</v>
@@ -655,7 +657,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>get_config(Server) -> {ok, Config} | {error, Reason}</name>
+ <name since="">get_config(Server) -> {ok, Config} | {error, Reason}</name>
<fsummary>Get reltool configuration</fsummary>
<type>
<v>Server = server()</v>
@@ -667,7 +669,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>get_config(Server, InclDefaults, InclDerived) -> {ok, Config} | {error, Reason}</name>
+ <name since="">get_config(Server, InclDefaults, InclDerived) -> {ok, Config} | {error, Reason}</name>
<fsummary>Get reltool configuration</fsummary>
<type>
<v>Server = server()</v>
@@ -685,7 +687,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>get_rel(Server, Relname) -> {ok, RelFile} | {error, Reason}</name>
+ <name since="">get_rel(Server, Relname) -> {ok, RelFile} | {error, Reason}</name>
<fsummary>Get contents of a release file</fsummary>
<type>
<v>Server = server()</v>
@@ -698,7 +700,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>get_script(Server, Relname) -> {ok, ScriptFile | {error, Reason}</name>
+ <name since="">get_script(Server, Relname) -> {ok, ScriptFile | {error, Reason}</name>
<fsummary>Get contents of a boot script file</fsummary>
<type>
<v>Server = server()</v>
@@ -711,7 +713,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>get_status(Server) -> {ok, [Warning]} | {error, Reason}</name>
+ <name since="OTP R14B">get_status(Server) -> {ok, [Warning]} | {error, Reason}</name>
<fsummary>Get contents of a release file</fsummary>
<type>
<v>Server = server()</v>
@@ -722,7 +724,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>get_server(WindowPid) -> {ok, ServerPid} | {error, Reason}</name>
+ <name since="">get_server(WindowPid) -> {ok, ServerPid} | {error, Reason}</name>
<fsummary>Start server process with options</fsummary>
<type>
<v>WindowPid = window_pid()</v>
@@ -733,7 +735,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>get_target_spec(Server) -> {ok, TargetSpec} | {error, Reason}</name>
+ <name since="">get_target_spec(Server) -> {ok, TargetSpec} | {error, Reason}</name>
<fsummary>Return a specification of the target system</fsummary>
<type>
<v>Server = server()</v>
@@ -747,7 +749,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>install(RelName, TargetDir) -> ok | {error, Reason}</name>
+ <name since="">install(RelName, TargetDir) -> ok | {error, Reason}</name>
<fsummary>Install a target system</fsummary>
<type>
<v>RelName = rel_name()</v>
@@ -758,7 +760,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>start() -> {ok, WindowPid} | {error, Reason}</name>
+ <name since="">start() -> {ok, WindowPid} | {error, Reason}</name>
<fsummary>Start main window process</fsummary>
<type>
<v>WindowPid = window_pid()</v>
@@ -768,7 +770,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>start(Options) -> {ok, WindowPid} | {error, Reason}</name>
+ <name since="">start(Options) -> {ok, WindowPid} | {error, Reason}</name>
<fsummary>Start main window process with options</fsummary>
<type>
<v>Options = options()</v>
@@ -779,7 +781,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>start_link(Options) -> {ok, WindowPid} | {error, Reason}</name>
+ <name since="">start_link(Options) -> {ok, WindowPid} | {error, Reason}</name>
<fsummary>Start main window process with options</fsummary>
<type>
<v>Options = options()</v>
@@ -790,7 +792,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>start_server(Options) -> {ok, ServerPid} | {error, Reason}</name>
+ <name since="">start_server(Options) -> {ok, ServerPid} | {error, Reason}</name>
<fsummary>Start server process with options</fsummary>
<type>
<v>Options = options()</v>
@@ -803,7 +805,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>stop(Pid) -> ok | {error, Reason}</name>
+ <name since="">stop(Pid) -> ok | {error, Reason}</name>
<fsummary>Stop a server or window process</fsummary>
<type>
<v>Pid = server_pid() | window_pid()</v>
diff --git a/lib/reltool/doc/src/reltool_examples.xml b/lib/reltool/doc/src/reltool_examples.xml
index 2a103119e6..3888b643a2 100644
--- a/lib/reltool/doc/src/reltool_examples.xml
+++ b/lib/reltool/doc/src/reltool_examples.xml
@@ -160,7 +160,7 @@ Eshell V9.0 (abort with ^G)
{mod,erts_internal,[]},
{mod,erts_literal_area_collector,[]},
{mod,init,[]},
- {mod,otp_ring0,...},
+ {mod,erl_init,...},
{mod,...},
{...}|...]}]},
{app,compiler,
@@ -313,9 +313,12 @@ Erlang/OTP 20 [erts-10.0] [source-c13b302] [64-bit] [smp:4:4] [ds:4:4:10] [async
[hipe] [kernel-poll:false]
Eshell V10.0 (abort with ^G)
1&gt;
-1&gt; {ok, Server} = reltool:start_server([{config, {sys, [{boot_rel, "NAME"},
- {rel, "NAME", "VSN",
- [sasl]}]}}]).
+1&gt; {ok, Server} = reltool:start_server([{config,
+ {sys,
+ [{boot_rel, "NAME"},
+ {rel, "NAME", "VSN",
+ [sasl],
+ [{load_dot_erlang, false}]}]}}]).
{ok,&lt;0.1288.0&gt;}
2&gt;
2&gt; reltool:get_config(Server).
@@ -331,7 +334,7 @@ Eshell V10.0 (abort with ^G)
{ok,{script,{"NAME","VSN"},
[{preLoaded,[erl_prim_loader,erl_tracer,erlang,
erts_code_purger,erts_dirty_process_signal_handler,
- erts_internal,erts_literal_area_collector,init,otp_ring0,
+ erts_internal,erts_literal_area_collector,init,erl_init,
prim_eval,prim_file,prim_inet,prim_zip,zlib]},
{progress,preloaded},
{path,["$ROOT/lib/kernel-5.2/ebin",
diff --git a/lib/reltool/src/reltool.hrl b/lib/reltool/src/reltool.hrl
index d133762818..892aaf8649 100644
--- a/lib/reltool/src/reltool.hrl
+++ b/lib/reltool/src/reltool.hrl
@@ -61,6 +61,7 @@
| {app_name(), app_type()}
| {app_name(), [incl_app()]}
| {app_name(), app_type(), [incl_app()]}.
+-type rel_opt() :: {load_dot_erlang, boolean()}.
-type mod() :: {incl_cond, incl_cond()}
| {debug_info, debug_info()}.
-type app() :: {vsn, app_vsn()}
@@ -92,6 +93,8 @@
| {lib_dirs, [lib_dir()]}
| {boot_rel, boot_rel()}
| {rel, rel_name(), rel_vsn(), [rel_app()]}
+ | {rel, rel_name(), rel_vsn(),
+ [rel_app()], [rel_opt()]}
| {relocatable, relocatable()}
| {erts, app()}
| {escript, escript_file(), [escript()]}
diff --git a/lib/reltool/src/reltool_server.erl b/lib/reltool/src/reltool_server.erl
index af71b0cf2a..2de8000fd8 100644
--- a/lib/reltool/src/reltool_server.erl
+++ b/lib/reltool/src/reltool_server.erl
@@ -526,7 +526,7 @@ analyse(#state{sys=Sys} = S, Apps, Status) ->
%% is included in a release (rel spec - see apps_in_rels above).
%% Then initiate the same for each module, and check that there
%% are no duplicated module names (in different applications)
- %% where we can not decide which one to use.
+ %% where we cannot decide which one to use.
%% Write all #app to app_tab and all #mod to mod_tab.
Status2 = apps_init_is_included(S, Apps, RelApps, Status),
@@ -1483,6 +1483,18 @@ decode(#sys{rels = Rels} = Sys, [{rel, Name, Vsn, RelApps} | SysKeyVals])
Rel = #rel{name = Name, vsn = Vsn, rel_apps = []},
Rel2 = decode(Rel, RelApps),
decode(Sys#sys{rels = [Rel2 | Rels]}, SysKeyVals);
+decode(#sys{rels = Rels} = Sys, [{rel, Name, Vsn, RelApps, Opts} | SysKeyVals])
+ when is_list(Name), is_list(Vsn), is_list(RelApps), is_list(Opts) ->
+ Rel1 = lists:foldl(fun(Opt, Rel0) ->
+ case Opt of
+ {load_dot_erlang, Value} when is_boolean(Value) ->
+ Rel0#rel{load_dot_erlang = Value};
+ _ ->
+ reltool_utils:throw_error("Illegal rel option: ~tp", [Opt])
+ end
+ end, #rel{name = Name, vsn = Vsn, rel_apps = []}, Opts),
+ Rel2 = decode(Rel1, RelApps),
+ decode(Sys#sys{rels = [Rel2 | Rels]}, SysKeyVals);
decode(#sys{} = Sys, [{Key, Val} | KeyVals]) ->
Sys3 =
case Key of
diff --git a/lib/reltool/src/reltool_target.erl b/lib/reltool/src/reltool_target.erl
index 64834ecc1d..dfa62479a0 100644
--- a/lib/reltool/src/reltool_target.erl
+++ b/lib/reltool/src/reltool_target.erl
@@ -108,12 +108,8 @@ do_gen_config(#sys{root_dir = RootDir,
emit(incl_cond, A#app.incl_cond, undefined, InclDefs)}
|| A <- Apps, A#app.is_escript],
DefaultRels = reltool_utils:default_rels(),
- RelsItems =
- [{rel, R#rel.name, R#rel.vsn, do_gen_config(R, InclDefs)} ||
- R <- Rels],
- DefaultRelsItems =
- [{rel, R#rel.name, R#rel.vsn, do_gen_config(R, InclDefs)} ||
- R <- DefaultRels],
+ RelsItems = [do_gen_config(R, InclDefs) || R <- Rels],
+ DefaultRelsItems = [do_gen_config(R, InclDefs) || R <- DefaultRels],
RelsItems2 =
case InclDefs of
true -> RelsItems;
@@ -201,11 +197,20 @@ do_gen_config(#mod{name = Name,
_ ->
[]
end;
-do_gen_config(#rel{name = _Name,
- vsn = _Vsn,
- rel_apps = RelApps},
- InclDefs) ->
- [do_gen_config(RA, InclDefs) || RA <- RelApps];
+do_gen_config(#rel{name = Name,
+ vsn = Vsn,
+ rel_apps = RelApps,
+ load_dot_erlang = LoadDotErlang},
+ InclDefs) ->
+ RelAppsConfig = [do_gen_config(RA, InclDefs) || RA <- RelApps],
+ if
+ LoadDotErlang =:= false ->
+ {rel, Name, Vsn, RelAppsConfig, [{load_dot_erlang, false}]};
+ InclDefs =:= true ->
+ {rel, Name, Vsn, RelAppsConfig, [{load_dot_erlang, true}]};
+ LoadDotErlang =:= true ->
+ {rel, Name, Vsn, RelAppsConfig}
+ end;
do_gen_config(#rel_app{name = Name,
app_type = Type,
incl_apps = InclApps},
diff --git a/lib/reltool/test/reltool_server_SUITE.erl b/lib/reltool/test/reltool_server_SUITE.erl
index 9ee61b595d..bb092e8bbf 100644
--- a/lib/reltool/test/reltool_server_SUITE.erl
+++ b/lib/reltool/test/reltool_server_SUITE.erl
@@ -102,6 +102,7 @@ all() ->
create_release,
create_release_sort,
create_script,
+ create_script_without_dot_erlang,
create_script_sort,
create_target,
create_target_unicode,
@@ -171,7 +172,7 @@ break(_Config) ->
start_server(_Config) ->
{ok, Pid} = ?msym({ok, _}, reltool:start_server([])),
- Libs = lists:sort(erl_libs()),
+ Libs = reltool_test_lib:erl_libs(),
StrippedDefault =
case Libs of
[] -> {sys, []};
@@ -185,7 +186,7 @@ start_server(_Config) ->
%% Start a server process and check that it does not crash
set_config(_Config) ->
- Libs = lists:sort(erl_libs()),
+ Libs = reltool_test_lib:erl_libs(),
Default =
{sys,
[
@@ -219,7 +220,15 @@ get_config(_Config) ->
StdLibDir = filename:join(LibDir,"stdlib-"++StdVsn),
SaslLibDir = filename:join(LibDir,"sasl-"++SaslVsn),
- Sys = {sys,[{incl_cond, exclude},
+ Libs = reltool_test_lib:erl_libs(),
+ LibDirs =
+ case Libs of
+ [] -> [];
+ _ -> [{lib_dirs,Libs}]
+ end,
+
+ Sys = {sys,LibDirs ++
+ [{incl_cond, exclude},
{app,kernel,[{incl_cond,include}]},
{app,sasl,[{incl_cond,include},{vsn,SaslVsn}]},
{app,stdlib,[{incl_cond,include},{lib_dir,StdLibDir}]}]},
@@ -228,13 +237,27 @@ get_config(_Config) ->
?m({ok, Sys}, reltool:get_config(Pid,false,false)),
%% Include derived info
- ?msym({ok,{sys,[{incl_cond, exclude},
- {erts,[]},
- {app,kernel,[{incl_cond,include},{mod,_,[]}|_]},
- {app,sasl,[{incl_cond,include},{vsn,SaslVsn},{mod,_,[]}|_]},
- {app,stdlib,[{incl_cond,include},{lib_dir,StdLibDir},
- {mod,_,[]}|_]}]}},
- reltool:get_config(Pid,false,true)),
+ case Libs of
+ [] ->
+ ?msym({ok,{sys,[{incl_cond, exclude},
+ {erts,[]},
+ {app,kernel,[{incl_cond,include},{mod,_,[]}|_]},
+ {app,sasl,[{incl_cond,include},{vsn,SaslVsn},
+ {mod,_,[]}|_]},
+ {app,stdlib,[{incl_cond,include},{lib_dir,StdLibDir},
+ {mod,_,[]}|_]}]}},
+ reltool:get_config(Pid,false,true));
+ _ ->
+ ?msym({ok,{sys,[{lib_dirs,Libs},
+ {incl_cond, exclude},
+ {erts,[]},
+ {app,kernel,[{incl_cond,include},{mod,_,[]}|_]},
+ {app,sasl,[{incl_cond,include},{vsn,SaslVsn},
+ {mod,_,[]}|_]},
+ {app,stdlib,[{incl_cond,include},{lib_dir,StdLibDir},
+ {mod,_,[]}|_]}]}},
+ reltool:get_config(Pid,false,true))
+ end,
%% Include defaults
?msym({ok,{sys,[{root_dir,_},
@@ -248,9 +271,9 @@ get_config(_Config) ->
{app,stdlib,[{incl_cond,include},{vsn,undefined},
{lib_dir,StdLibDir}]},
{boot_rel,"start_clean"},
- {rel,"no_dot_erlang","1.0",[]},
- {rel,"start_clean","1.0",[]},
- {rel,"start_sasl","1.0",[sasl]},
+ {rel,"no_dot_erlang","1.0",[],[{load_dot_erlang,false}]},
+ {rel,"start_clean","1.0",[],[{load_dot_erlang,true}]},
+ {rel,"start_sasl","1.0",[sasl],[{load_dot_erlang,true}]},
{emu_name,"beam"},
{relocatable,true},
{profile,development},
@@ -279,9 +302,9 @@ get_config(_Config) ->
{app,stdlib,[{incl_cond,include},{vsn,StdVsn},
{lib_dir,StdLibDir},{mod,_,[]}|_]},
{boot_rel,"start_clean"},
- {rel,"no_dot_erlang","1.0",[]},
- {rel,"start_clean","1.0",[]},
- {rel,"start_sasl","1.0",[sasl]},
+ {rel,"no_dot_erlang","1.0",[],[{load_dot_erlang,false}]},
+ {rel,"start_clean","1.0",[],[{load_dot_erlang,true}]},
+ {rel,"start_sasl","1.0",[sasl],[{load_dot_erlang,true}]},
{emu_name,"beam"},
{relocatable,true},
{profile,development},
@@ -305,11 +328,11 @@ get_config(_Config) ->
%% OTP-9135, test that app_file option can be set to all | keep | strip
otp_9135(_Config) ->
- Libs = lists:sort(erl_libs()),
+ Libs = reltool_test_lib:erl_libs(),
StrippedDefaultSys =
case Libs of
[] -> [];
- _ -> {lib_dirs, Libs}
+ _ -> [{lib_dirs, Libs}]
end,
Config1 = {sys,[{app_file, keep}]}, % this is the default
@@ -549,6 +572,32 @@ create_script(_Config) ->
?m(equal, diff_script(OrigScript, Script)),
+ %% A release defaults to load_dot_erlang == true
+ {script, {RelName, RelVsn}, ScriptInstructions} = Script,
+ ?m(true, lists:member({apply,{c,erlangrc,[]}}, ScriptInstructions)),
+
+ %% Stop server
+ ?m(ok, reltool:stop(Pid)),
+ ok.
+
+create_script_without_dot_erlang(_Config) ->
+ %% Configure the server
+ RelName = "Just testing",
+ RelVsn = "1.0",
+ Config =
+ {sys,
+ [
+ {lib_dirs, []},
+ {boot_rel, RelName},
+ {rel, RelName, RelVsn, [stdlib, kernel], [{load_dot_erlang, false}]}
+ ]},
+ {ok, Pid} = ?msym({ok, _}, reltool:start_server([{config, Config}])),
+
+ %% Confirm that load_dot_erlang == false was used
+ {ok, Script} = ?msym({ok, _}, reltool:get_script(Pid, RelName)),
+ {script, {RelName, RelVsn}, ScriptInstructions} = Script,
+ ?m(false, lists:member({apply,{c,erlangrc,[]}}, ScriptInstructions)),
+
%% Stop server
?m(ok, reltool:stop(Pid)),
ok.
@@ -1039,7 +1088,7 @@ create_standalone_app(Config) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Generate standalone system with inlined archived application
-%% Check that the inlined app can not be explicitly configured
+%% Check that the inlined app cannot be explicitly configured
create_standalone_app_clash(Config) ->
%% Create archive
@@ -1659,7 +1708,7 @@ set_apps_inlined(Config) ->
?m(true, Someapp1#app.is_included),
?m(true, Someapp1#app.is_pre_included),
- %% Check that inlined app can not be configured
+ %% Check that inlined app cannot be configured
Someapp2 = Someapp1#app{incl_cond=exclude},
?msym({error,
"Application someapp is inlined in '*escript* someapp'. "
@@ -1719,13 +1768,19 @@ set_sys_and_undo(Config) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
load_config_and_undo(Config) ->
- Sys1 = {sys,[{incl_cond, exclude},
- {app,kernel,[{incl_cond,include}]},
- {app,sasl,[{incl_cond,include}]},
- {app,stdlib,[{incl_cond,include}]},
- {app,tools,[{incl_cond,include}]}]},
+ Sys1 = {sys,Cfg1=[{incl_cond, exclude},
+ {app,kernel,[{incl_cond,include}]},
+ {app,sasl,[{incl_cond,include}]},
+ {app,stdlib,[{incl_cond,include}]},
+ {app,tools,[{incl_cond,include}]}]},
{ok, Pid} = ?msym({ok, _}, reltool:start_server([{config, Sys1}])),
- ?m({ok, Sys1}, reltool:get_config(Pid)),
+ Libs = reltool_test_lib:erl_libs(),
+ Sys11 =
+ case Libs of
+ [] -> Sys1;
+ _ -> {sys, [{lib_dirs, Libs}|Cfg1]}
+ end,
+ ?m({ok, Sys11}, reltool:get_config(Pid)),
?m({ok,[]}, reltool_server:get_status(Pid)),
%% Get app and mod
@@ -1780,13 +1835,19 @@ load_config_and_undo(Config) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Test that load_config is properly rolled back if it fails
load_config_fail(_Config) ->
- Sys1 = {sys,[{incl_cond, exclude},
- {app,kernel,[{incl_cond,include}]},
- {app,sasl,[{incl_cond,include}]},
- {app,stdlib,[{incl_cond,include}]},
- {app,tools,[{incl_cond,include}]}]},
+ Sys1 = {sys,Cfg1=[{incl_cond, exclude},
+ {app,kernel,[{incl_cond,include}]},
+ {app,sasl,[{incl_cond,include}]},
+ {app,stdlib,[{incl_cond,include}]},
+ {app,tools,[{incl_cond,include}]}]},
{ok, Pid} = ?msym({ok, _}, reltool:start_server([{config, Sys1}])),
- ?m({ok, Sys1}, reltool:get_config(Pid)),
+ Libs = reltool_test_lib:erl_libs(),
+ Sys11 =
+ case Libs of
+ [] -> Sys1;
+ _ -> {sys, [{lib_dirs, Libs}|Cfg1]}
+ end,
+ ?m({ok, Sys11}, reltool:get_config(Pid)),
?m({ok,[]}, reltool_server:get_status(Pid)),
%% Get app and mod
@@ -1804,7 +1865,7 @@ load_config_fail(_Config) ->
reltool_server:load_config(Pid,Sys2)),
%% Check that a rollback is done to the old configuration
- ?m({ok, Sys1}, reltool:get_config(Pid,false,false)),
+ ?m({ok, Sys11}, reltool:get_config(Pid,false,false)),
%% and that tools is not changed (i.e. that the new configuration
%% is not applied)
@@ -2074,25 +2135,42 @@ gen_rel_files(_Config) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
save_config(Config) ->
PrivDir = ?config(priv_dir,Config),
- Sys = {sys,[{incl_cond, exclude},
- {app,kernel,[{incl_cond,include}]},
- {app,sasl,[{incl_cond,include}]},
- {app,stdlib,[{incl_cond,include}]}]},
+ Sys = {sys,Cfg=[{incl_cond, exclude},
+ {app,kernel,[{incl_cond,include}]},
+ {app,sasl,[{incl_cond,include}]},
+ {app,stdlib,[{incl_cond,include}]}]},
{ok, Pid} = ?msym({ok, _}, reltool:start_server([{config, Sys}])),
- ?m({ok, Sys}, reltool:get_config(Pid)),
+ Libs = reltool_test_lib:erl_libs(),
+ Sys1 =
+ case Libs of
+ [] -> Sys;
+ _ -> {sys, [{lib_dirs, Libs}|Cfg]}
+ end,
+ ?m({ok, Sys1}, reltool:get_config(Pid)),
Simple = filename:join(PrivDir,"save_simple.reltool"),
?m(ok, reltool_server:save_config(Pid,Simple,false,false)),
- ?m({ok,[Sys]}, file:consult(Simple)),
+ ?m({ok,[Sys1]}, file:consult(Simple)),
Derivates = filename:join(PrivDir,"save_derivates.reltool"),
?m(ok, reltool_server:save_config(Pid,Derivates,false,true)),
- ?msym({ok,[{sys,[{incl_cond, exclude},
- {erts,[]},
- {app,kernel,[{incl_cond,include},{mod,_,[]}|_]},
- {app,sasl,[{incl_cond,include},{mod,_,[]}|_]},
- {app,stdlib,[{incl_cond,include},{mod,_,[]}|_]}]}]},
- file:consult(Derivates)),
+ case Libs of
+ [] ->
+ ?msym({ok,[{sys,[{incl_cond, exclude},
+ {erts,[]},
+ {app,kernel,[{incl_cond,include},{mod,_,[]}|_]},
+ {app,sasl,[{incl_cond,include},{mod,_,[]}|_]},
+ {app,stdlib,[{incl_cond,include},{mod,_,[]}|_]}]}]},
+ file:consult(Derivates));
+ _ ->
+ ?msym({ok,[{sys,[{lib_dirs,Libs},
+ {incl_cond, exclude},
+ {erts,[]},
+ {app,kernel,[{incl_cond,include},{mod,_,[]}|_]},
+ {app,sasl,[{incl_cond,include},{mod,_,[]}|_]},
+ {app,stdlib,[{incl_cond,include},{mod,_,[]}|_]}]}]},
+ file:consult(Derivates))
+ end,
Defaults = filename:join(PrivDir,"save_defaults.reltool"),
?m(ok, reltool_server:save_config(Pid,Defaults,true,false)),
@@ -2107,9 +2185,9 @@ save_config(Config) ->
{app,stdlib,[{incl_cond,include},{vsn,undefined},
{lib_dir,undefined}]},
{boot_rel,"start_clean"},
- {rel,"no_dot_erlang","1.0",[]},
- {rel,"start_clean","1.0",[]},
- {rel,"start_sasl","1.0",[sasl]},
+ {rel,"no_dot_erlang","1.0",[],[{load_dot_erlang,false}]},
+ {rel,"start_clean","1.0",[],[{load_dot_erlang,true}]},
+ {rel,"start_sasl","1.0",[sasl],[{load_dot_erlang,true}]},
{emu_name,"beam"},
{relocatable,true},
{profile,development},
@@ -2148,9 +2226,9 @@ save_config(Config) ->
{app,stdlib,[{incl_cond,include},{vsn,StdVsn},
{lib_dir,StdLibDir},{mod,_,[]}|_]},
{boot_rel,"start_clean"},
- {rel,"no_dot_erlang","1.0",[]},
- {rel,"start_clean","1.0",[]},
- {rel,"start_sasl","1.0",[sasl]},
+ {rel,"no_dot_erlang","1.0",[],[{load_dot_erlang,false}]},
+ {rel,"start_clean","1.0",[],[{load_dot_erlang,true}]},
+ {rel,"start_sasl","1.0",[sasl],[{load_dot_erlang,true}]},
{emu_name,"beam"},
{relocatable,true},
{profile,development},
@@ -2560,9 +2638,6 @@ windows_erl_libs(_Config) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Library functions
-erl_libs() ->
- reltool_utils:erl_libs().
-
datadir(Config) ->
%% Removes the trailing slash...
filename:nativename(?config(data_dir,Config)).
diff --git a/lib/reltool/test/reltool_test_lib.erl b/lib/reltool/test/reltool_test_lib.erl
index be48ea4726..033d952d0a 100644
--- a/lib/reltool/test/reltool_test_lib.erl
+++ b/lib/reltool/test/reltool_test_lib.erl
@@ -237,7 +237,8 @@ wait_for_close() ->
wait_for_close()
end.
-
+erl_libs() ->
+ lists:sort([filename:absname(P) || P<-reltool_utils:erl_libs()]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% A small test server, which can be run standalone in a shell
diff --git a/lib/reltool/test/reltool_wx_SUITE.erl b/lib/reltool/test/reltool_wx_SUITE.erl
index f6f7721762..983c8f6c52 100644
--- a/lib/reltool/test/reltool_wx_SUITE.erl
+++ b/lib/reltool/test/reltool_wx_SUITE.erl
@@ -74,7 +74,12 @@ start_all_windows(_Config) ->
%% Test that server pid can be fetched, and that server is alive
{ok, Server} = ?msym({ok,_}, reltool:get_server(SysPid)),
?m(true, erlang:is_process_alive(Server)),
- ?m({ok,{sys,[]}}, reltool:get_config(Server)),
+ Sys =
+ case reltool_test_lib:erl_libs() of
+ [] -> [];
+ Libs -> [{lib_dirs,Libs}]
+ end,
+ ?m({ok,{sys,Sys}}, reltool:get_config(Server)),
%% Terminate
check_no_win_crash(),
diff --git a/lib/runtime_tools/c_src/Makefile.in b/lib/runtime_tools/c_src/Makefile.in
index 4530a83aee..75b3a98d56 100644
--- a/lib/runtime_tools/c_src/Makefile.in
+++ b/lib/runtime_tools/c_src/Makefile.in
@@ -36,7 +36,7 @@ CC = $(DED_CC)
CFLAGS = $(DED_CFLAGS) -I./
LD = $(DED_LD)
SHELL = /bin/sh
-LIBS = $(DED_LIBS)
+LIBS = $(DED_LIBS) @LIBS@
LDFLAGS += $(DED_LDFLAGS)
TRACE_LIBNAME = dyntrace trace_file_drv trace_ip_drv
@@ -58,7 +58,7 @@ TYPE_FLAGS = $(CFLAGS)
endif
endif
-ALL_CFLAGS = @DEFS@ $(TYPE_FLAGS) $(TRACE_DRV_INCLUDES) \
+ALL_CFLAGS = @DEFS@ @ERTS_CONFIG_H_IDIR@ $(TYPE_FLAGS) $(TRACE_DRV_INCLUDES) \
-I$(OBJDIR) -I$(ERL_TOP)/erts/emulator/$(TARGET)
ROOTDIR = $(ERL_TOP)/lib
@@ -75,7 +75,7 @@ RELSYSDIR = $(RELEASE_PATH)/lib/runtime_tools-$(VSN)
# Misc Macros
# ----------------------------------------------------
-TRACE_LIBS = $(foreach LIB, $(TRACE_LIBNAME), $(LIBDIR)/$(LIB)$(TYPEMARKER).@DED_EXT@)
+TRACE_LIBS = $(foreach LIB, $(TRACE_LIBNAME), $(LIBDIR)/$(LIB)$(TYPEMARKER).$(DED_EXT))
# ----------------------------------------------------
# Targets
@@ -94,7 +94,7 @@ $(LIBDIR):
$(OBJDIR)/%$(TYPEMARKER).o: %.c dyntrace_lttng.h
$(V_CC) -c -o $@ $(ALL_CFLAGS) $<
-$(LIBDIR)/%$(TYPEMARKER).@DED_EXT@: $(OBJDIR)/%$(TYPEMARKER).o
+$(LIBDIR)/%$(TYPEMARKER).$(DED_EXT): $(OBJDIR)/%$(TYPEMARKER).o
$(V_LD) $(LDFLAGS) -o $@ $^ $(LIBS)
clean:
diff --git a/lib/runtime_tools/doc/src/dbg.xml b/lib/runtime_tools/doc/src/dbg.xml
index 3262cafefc..e15fc3efe6 100644
--- a/lib/runtime_tools/doc/src/dbg.xml
+++ b/lib/runtime_tools/doc/src/dbg.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>dbg.sgml</file>
</header>
- <module>dbg</module>
+ <module since="">dbg</module>
<modulesummary>The Text Based Trace Facility</modulesummary>
<description>
<p>This module implements a text based interface to the
@@ -68,7 +68,7 @@
</description>
<funcs>
<func>
- <name>fun2ms(LiteralFun) -> MatchSpec</name>
+ <name since="">fun2ms(LiteralFun) -> MatchSpec</name>
<fsummary>Pseudo function that transforms fun syntax to match_spec.</fsummary>
<type>
<v>LiteralFun = fun() literal</v>
@@ -113,7 +113,7 @@
<p>The imported variables will be replaced by match_spec
<c>const</c> expressions, which is consistent with the
static scoping for Erlang <c>fun()</c>s. Local or global
- function calls can not be in the guard or body of the fun
+ function calls cannot be in the guard or body of the fun
however. Calls to builtin match_spec functions of course is
allowed:</p>
<pre>
@@ -145,14 +145,14 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>h() -> ok </name>
+ <name since="">h() -> ok </name>
<fsummary>Give a list of available help items on standard output.</fsummary>
<desc>
<p>Gives a list of items for brief online help.</p>
</desc>
</func>
<func>
- <name>h(Item) -> ok </name>
+ <name since="">h(Item) -> ok </name>
<fsummary>Give brief help for an item.</fsummary>
<type>
<v>Item = atom()</v>
@@ -163,14 +163,14 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>p(Item) -> {ok, MatchDesc} | {error, term()} </name>
+ <name since="">p(Item) -> {ok, MatchDesc} | {error, term()} </name>
<fsummary>Trace messages to and from Item.</fsummary>
<desc>
<p>Equivalent to <c>p(Item, [m])</c>.</p>
</desc>
</func>
<func>
- <name>p(Item, Flags) -> {ok, MatchDesc} | {error, term()}</name>
+ <name since="">p(Item, Flags) -> {ok, MatchDesc} | {error, term()}</name>
<fsummary>Trace Item according to Flags.</fsummary>
<type>
<v>MatchDesc = [MatchNum]</v>
@@ -303,14 +303,14 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>c(Mod, Fun, Args)</name>
+ <name since="">c(Mod, Fun, Args)</name>
<fsummary>Evaluate <c>apply(M,F,Args)</c>with <c>all</c>trace flags set.</fsummary>
<desc>
<p>Equivalent to <c>c(Mod, Fun, Args, all)</c>.</p>
</desc>
</func>
<func>
- <name>c(Mod, Fun, Args, Flags)</name>
+ <name since="">c(Mod, Fun, Args, Flags)</name>
<fsummary>Evaluate <c>apply(M,F,Args)</c>with <c>Flags</c>trace flags set.</fsummary>
<desc>
<p>Evaluates the expression <c>apply(Mod, Fun, Args)</c> with the trace
@@ -319,35 +319,35 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>i() -> ok</name>
+ <name since="">i() -> ok</name>
<fsummary>Display information about all traced processes and ports.</fsummary>
<desc>
<p>Displays information about all traced processes and ports.</p>
</desc>
</func>
<func>
- <name>tp(Module,MatchSpec)</name>
+ <name since="">tp(Module,MatchSpec)</name>
<fsummary>Set pattern for traced global function calls</fsummary>
<desc>
<p>Same as tp({Module, '_', '_'}, MatchSpec)</p>
</desc>
</func>
<func>
- <name>tp(Module,Function,MatchSpec)</name>
+ <name since="">tp(Module,Function,MatchSpec)</name>
<fsummary>Set pattern for traced global function calls</fsummary>
<desc>
<p>Same as tp({Module, Function, '_'}, MatchSpec)</p>
</desc>
</func>
<func>
- <name>tp(Module, Function, Arity, MatchSpec)</name>
+ <name since="">tp(Module, Function, Arity, MatchSpec)</name>
<fsummary>Set pattern for traced global function calls</fsummary>
<desc>
<p>Same as tp({Module, Function, Arity}, MatchSpec)</p>
</desc>
</func>
<func>
- <name>tp({Module, Function, Arity}, MatchSpec) -> {ok, MatchDesc} | {error, term()}</name>
+ <name since="">tp({Module, Function, Arity}, MatchSpec) -> {ok, MatchDesc} | {error, term()}</name>
<fsummary>Set pattern for traced global function calls</fsummary>
<type>
<v>Module = atom() | '_'</v>
@@ -410,28 +410,28 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>tpl(Module,MatchSpec)</name>
+ <name since="">tpl(Module,MatchSpec)</name>
<fsummary>Set pattern for traced local (as well as global) function calls</fsummary>
<desc>
<p>Same as tpl({Module, '_', '_'}, MatchSpec)</p>
</desc>
</func>
<func>
- <name>tpl(Module,Function,MatchSpec)</name>
+ <name since="">tpl(Module,Function,MatchSpec)</name>
<fsummary>Set pattern for traced local (as well as global) function calls</fsummary>
<desc>
<p>Same as tpl({Module, Function, '_'}, MatchSpec)</p>
</desc>
</func>
<func>
- <name>tpl(Module, Function, Arity, MatchSpec)</name>
+ <name since="">tpl(Module, Function, Arity, MatchSpec)</name>
<fsummary>Set pattern for traced local (as well as global) function calls</fsummary>
<desc>
<p>Same as tpl({Module, Function, Arity}, MatchSpec)</p>
</desc>
</func>
<func>
- <name>tpl({Module, Function, Arity}, MatchSpec) -> {ok, MatchDesc} | {error, term()}</name>
+ <name since="">tpl({Module, Function, Arity}, MatchSpec) -> {ok, MatchDesc} | {error, term()}</name>
<fsummary>Set pattern for traced local (as well as global) function calls</fsummary>
<desc>
<p>This function works as <seealso marker="#tp-2"><c>tp/2</c></seealso>, but enables
@@ -442,7 +442,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
<func>
- <name>tpe(Event, MatchSpec) -> {ok, MatchDesc} | {error, term()}</name>
+ <name since="OTP 19.0">tpe(Event, MatchSpec) -> {ok, MatchDesc} | {error, term()}</name>
<fsummary>Set pattern for traced event</fsummary>
<type>
<v>Event = send | 'receive'</v>
@@ -484,35 +484,35 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>ctp()</name>
+ <name since="">ctp()</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>Same as ctp({'_', '_', '_'})</p>
</desc>
</func>
<func>
- <name>ctp(Module)</name>
+ <name since="">ctp(Module)</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>Same as ctp({Module, '_', '_'})</p>
</desc>
</func>
<func>
- <name>ctp(Module, Function)</name>
+ <name since="">ctp(Module, Function)</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>Same as ctp({Module, Function, '_'})</p>
</desc>
</func>
<func>
- <name>ctp(Module, Function, Arity)</name>
+ <name since="">ctp(Module, Function, Arity)</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>Same as ctp({Module, Function, Arity})</p>
</desc>
</func>
<func>
- <name>ctp({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}</name>
+ <name since="">ctp({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<type>
<v>Module = atom() | '_'</v>
@@ -533,35 +533,35 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>ctpl()</name>
+ <name since="">ctpl()</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>Same as ctpl({'_', '_', '_'})</p>
</desc>
</func>
<func>
- <name>ctpl(Module)</name>
+ <name since="">ctpl(Module)</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>Same as ctpl({Module, '_', '_'})</p>
</desc>
</func>
<func>
- <name>ctpl(Module, Function)</name>
+ <name since="">ctpl(Module, Function)</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>Same as ctpl({Module, Function, '_'})</p>
</desc>
</func>
<func>
- <name>ctpl(Module, Function, Arity)</name>
+ <name since="">ctpl(Module, Function, Arity)</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>Same as ctpl({Module, Function, Arity})</p>
</desc>
</func>
<func>
- <name>ctpl({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}</name>
+ <name since="">ctpl({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>This function works as <seealso marker="#ctp-1"><c>ctp/1</c></seealso>, but only disables
@@ -570,35 +570,35 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>ctpg()</name>
+ <name since="">ctpg()</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>Same as ctpg({'_', '_', '_'})</p>
</desc>
</func>
<func>
- <name>ctpg(Module)</name>
+ <name since="">ctpg(Module)</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>Same as ctpg({Module, '_', '_'})</p>
</desc>
</func>
<func>
- <name>ctpg(Module, Function)</name>
+ <name since="">ctpg(Module, Function)</name>
<fsummary>>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>Same as ctpg({Module, Function, '_'})</p>
</desc>
</func>
<func>
- <name>ctpg(Module, Function, Arity)</name>
+ <name since="">ctpg(Module, Function, Arity)</name>
<fsummary>>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>Same as ctpg({Module, Function, Arity})</p>
</desc>
</func>
<func>
- <name>ctpg({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}</name>
+ <name since="">ctpg({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>This function works as <seealso marker="#ctp-1"><c>ctp/1</c></seealso>, but only disables
@@ -607,7 +607,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>ctpe(Event) -> {ok, MatchDesc} | {error, term()}</name>
+ <name since="OTP 19.0">ctpe(Event) -> {ok, MatchDesc} | {error, term()}</name>
<fsummary>Clear trace pattern for the specified event</fsummary>
<type>
<v>Event = send | 'receive'</v>
@@ -623,7 +623,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>ltp() -> ok</name>
+ <name since="">ltp() -> ok</name>
<fsummary>List saved and built-in match specifications on the console.</fsummary>
<desc>
<p>Use this function to recall all match specifications previously
@@ -654,7 +654,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>dtp() -> ok</name>
+ <name since="">dtp() -> ok</name>
<fsummary>Delete all saved match specifications.</fsummary>
<desc>
<p>Use this function to "forget" all match specifications
@@ -665,7 +665,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>dtp(N) -> ok</name>
+ <name since="">dtp(N) -> ok</name>
<fsummary>Delete a specific saved match_spec.</fsummary>
<type>
<v>N = integer()</v>
@@ -676,7 +676,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>wtp(Name) -> ok | {error, IOError}</name>
+ <name since="">wtp(Name) -> ok | {error, IOError}</name>
<fsummary>Write all saved and built-in match specifications to a file</fsummary>
<type>
<v>Name = string()</v>
@@ -699,7 +699,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>rtp(Name) -> ok | {error, Error}</name>
+ <name since="">rtp(Name) -> ok | {error, Error}</name>
<fsummary>Read saved match specifications from file.</fsummary>
<type>
<v>Name = string()</v>
@@ -728,7 +728,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>n(Nodename) -> {ok, Nodename} | {error, Reason}</name>
+ <name since="">n(Nodename) -> {ok, Nodename} | {error, Reason}</name>
<fsummary>Add a remote node to the list of traced nodes</fsummary>
<type>
<v>Nodename = atom()</v>
@@ -756,7 +756,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
<c>cant_add_local_node</c> is returned.
</p>
<p>If a trace port (see <seealso marker="#trace_port-2"><c>trace_port/2</c></seealso>) is
- running on the local node, remote nodes can not be traced with
+ running on the local node, remote nodes cannot be traced with
a tracer process. The error reason
<c>cant_trace_remote_pid_to_local_port</c> is returned. A
trace port can however be started on the remote node with the
@@ -767,7 +767,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>cn(Nodename) -> ok</name>
+ <name since="">cn(Nodename) -> ok</name>
<fsummary>Clear a node from the list of traced nodes.</fsummary>
<type>
<v>Nodename = atom()</v>
@@ -782,14 +782,14 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>ln() -> ok</name>
+ <name since="">ln() -> ok</name>
<fsummary>Show the list of traced nodes on the console.</fsummary>
<desc>
<p>Shows the list of traced nodes on the console.</p>
</desc>
</func>
<func>
- <name>tracer() -> {ok, pid()} | {error, already_started}</name>
+ <name since="">tracer() -> {ok, pid()} | {error, already_started}</name>
<fsummary>Start a tracer server that handles trace messages.</fsummary>
<desc>
<p>This function starts a server on the local node that will
@@ -805,7 +805,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>tracer(Type, Data) -> {ok, pid()} | {error, Error}</name>
+ <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>
@@ -859,7 +859,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>tracer(Nodename, Type, Data) -> {ok, Nodename} | {error, Reason}</name>
+ <name since="">tracer(Nodename, Type, Data) -> {ok, Nodename} | {error, Reason}</name>
<fsummary>Start a tracer server on given node with additional parameters</fsummary>
<type>
<v>Nodename = atom()</v>
@@ -881,7 +881,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>trace_port(Type, Parameters) -> fun()</name>
+ <name since="">trace_port(Type, Parameters) -> fun()</name>
<fsummary>Create and returns a trace port generating<em>fun</em></fsummary>
<type>
<v>Type = ip | file</v>
@@ -958,28 +958,28 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>flush_trace_port()</name>
+ <name since="">flush_trace_port()</name>
<fsummary>Equivalent to flush_trace_port(node()).</fsummary>
<desc>
<p>Equivalent to <c>flush_trace_port(node())</c>.</p>
</desc>
</func>
<func>
- <name>flush_trace_port(Nodename) -> ok | {error, Reason}</name>
+ <name since="">flush_trace_port(Nodename) -> ok | {error, Reason}</name>
<fsummary>Flush internal data buffers in a trace driver on the given node.</fsummary>
<desc>
<p>Equivalent to <c>trace_port_control(Nodename,flush)</c>.</p>
</desc>
</func>
<func>
- <name>trace_port_control(Operation)</name>
+ <name since="">trace_port_control(Operation)</name>
<fsummary>Equivalent to trace_port_control(node(),Operation).</fsummary>
<desc>
<p>Equivalent to <c>trace_port_control(node(),Operation)</c>.</p>
</desc>
</func>
<func>
- <name>trace_port_control(Nodename,Operation) -> ok | {ok, Result} | {error, Reason}</name>
+ <name since="">trace_port_control(Nodename,Operation) -> ok | {ok, Result} | {error, Reason}</name>
<fsummary>Perform a control operation on the active trace port driver on the given node.</fsummary>
<type>
<v>Nodename = atom()</v>
@@ -1013,7 +1013,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>trace_client(Type, Parameters) -> pid()</name>
+ <name since="">trace_client(Type, Parameters) -> pid()</name>
<fsummary>Start a trace client that reads messages created by a trace port driver</fsummary>
<type>
<v>Type = ip | file | follow_file</v>
@@ -1080,7 +1080,7 @@ hello</pre>
</desc>
</func>
<func>
- <name>trace_client(Type, Parameters, HandlerSpec) -> pid()</name>
+ <name since="">trace_client(Type, Parameters, HandlerSpec) -> pid()</name>
<fsummary>Start a trace client that reads messages created by a trace port driver, with a user defined handler</fsummary>
<type>
<v>Type = ip | file | follow_file</v>
@@ -1110,7 +1110,7 @@ hello</pre>
</desc>
</func>
<func>
- <name>stop_trace_client(Pid) -> ok</name>
+ <name since="">stop_trace_client(Pid) -> ok</name>
<fsummary>Stop a trace client gracefully.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -1123,14 +1123,14 @@ hello</pre>
</desc>
</func>
<func>
- <name>get_tracer()</name>
+ <name since="">get_tracer()</name>
<fsummary>Equivalent to get_tracer(node()).</fsummary>
<desc>
<p>Equivalent to <c>get_tracer(node())</c>.</p>
</desc>
</func>
<func>
- <name>get_tracer(Nodename) -> {ok, Tracer}</name>
+ <name since="">get_tracer(Nodename) -> {ok, Tracer}</name>
<fsummary>Return the process or port to which all trace messages are sent.</fsummary>
<type>
<v>Nodename = atom()</v>
@@ -1142,7 +1142,7 @@ hello</pre>
</desc>
</func>
<func>
- <name>stop() -> ok</name>
+ <name since="">stop() -> ok</name>
<fsummary>Stop the <c>dbg</c>server and the tracing of all processes.</fsummary>
<desc>
<p>Stops the <c>dbg</c> server and clears all trace flags for
@@ -1153,7 +1153,7 @@ hello</pre>
</desc>
</func>
<func>
- <name>stop_clear() -> ok</name>
+ <name since="">stop_clear() -> ok</name>
<fsummary>Stop the <c>dbg</c>server and the tracing of all processes, and clears trace patterns.</fsummary>
<desc>
<p>Same as stop/0, but also clears all trace patterns on global functions calls.</p>
diff --git a/lib/runtime_tools/doc/src/dyntrace.xml b/lib/runtime_tools/doc/src/dyntrace.xml
index 0cdcecab68..4935dfcd71 100644
--- a/lib/runtime_tools/doc/src/dyntrace.xml
+++ b/lib/runtime_tools/doc/src/dyntrace.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>dyntrace.xml</file>
</header>
- <module>dyntrace</module>
+ <module since="OTP R15B01">dyntrace</module>
<modulesummary>Interface to dynamic tracing</modulesummary>
<description>
<p>This module implements interfaces to dynamic tracing, should such be compiled into the virtual machine. For a standard and/or commercial build, no dynamic tracing is available, in which case none of the functions in this module is usable or give any effect.</p>
@@ -47,7 +47,7 @@
</description>
<funcs>
<func>
- <name>available() -> boolean()</name>
+ <name since="OTP R15B01">available() -> boolean()</name>
<fsummary>Check if dynamic tracing is available</fsummary>
<desc>
<p>This function uses the NIF library to determine if dynamic
@@ -59,42 +59,42 @@
</desc>
</func>
<func>
- <name>p() -> true | false | error | badarg</name>
+ <name since="OTP R15B01">p() -> true | false | error | badarg</name>
<fsummary>Trigger the user trace probe.</fsummary>
<desc>
<p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message only containing the user tag and zeroes/empty strings in all other fields.</p>
</desc>
</func>
<func>
- <name>p(integer() | string()) -> true | false | error | badarg</name>
+ <name since="OTP R15B01">p(integer() | string()) -> true | false | error | badarg</name>
<fsummary>Trigger the user trace probe.</fsummary>
<desc>
<p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer or string parameter in the first integer/string field.</p>
</desc>
</func>
<func>
- <name>p(integer() | string(), integer() | string()) -> true | false | error | badarg</name>
+ <name since="OTP R15B01">p(integer() | string(), integer() | string()) -> true | false | error | badarg</name>
<fsummary>Trigger the user trace probe.</fsummary>
<desc>
<p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters. I.e. <c>p(1,"Hello")</c> is ok, as is <c>p(1,1)</c> and <c>p("Hello","Again")</c>, but not <c>p("Hello",1)</c>.</p>
</desc>
</func>
<func>
- <name>p(integer() | string(), integer() | string(), integer() | string()) -> true | false | error | badarg</name>
+ <name since="OTP R15B01">p(integer() | string(), integer() | string(), integer() | string()) -> true | false | error | badarg</name>
<fsummary>Trigger the user trace probe.</fsummary>
<desc>
<p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters, as in <seealso marker="#p/2">p/2</seealso>.</p>
</desc>
</func>
<func>
- <name>p(integer() | string(), integer() | string(), integer() | string(), integer() | string()) -> true | false | error | badarg</name>
+ <name since="OTP R15B01">p(integer() | string(), integer() | string(), integer() | string(), integer() | string()) -> true | false | error | badarg</name>
<fsummary>Trigger the user trace probe.</fsummary>
<desc>
<p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters, as in <seealso marker="#p/2">p/2</seealso>.</p>
</desc>
</func>
<func>
- <name>p(integer(), integer() | string(), integer() | string(), integer() | string(), string()) -> true | false | error | badarg</name>
+ <name since="OTP R15B01">p(integer(), integer() | string(), integer() | string(), integer() | string(), string()) -> true | false | error | badarg</name>
<fsummary>Trigger the user trace probe.</fsummary>
<desc>
<p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters, as in <seealso marker="#p/2">p/2</seealso>.</p>
@@ -102,7 +102,7 @@
</desc>
</func>
<func>
- <name>p(integer(), integer(), integer() | string(), integer() | string(), string(), string()) -> true | false | error | badarg</name>
+ <name since="OTP R15B01">p(integer(), integer(), integer() | string(), integer() | string(), string(), string()) -> true | false | error | badarg</name>
<fsummary>Trigger the user trace probe.</fsummary>
<desc>
<p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters, as in <seealso marker="#p/2">p/2</seealso>.</p>
@@ -110,7 +110,7 @@
</desc>
</func>
<func>
- <name>p(integer(), integer(), integer(), integer() | string(), string(), string(), string()) -> true | false | error | badarg</name>
+ <name since="OTP R15B01">p(integer(), integer(), integer(), integer() | string(), string(), string(), string()) -> true | false | error | badarg</name>
<fsummary>Trigger the user trace probe.</fsummary>
<desc>
<p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters, as in <seealso marker="#p/2">p/2</seealso>.</p>
@@ -118,14 +118,14 @@
</desc>
</func>
<func>
- <name>p(integer(), integer(), integer(), integer(), string(), string(), string(), string()) -> true | false | error | badarg</name>
+ <name since="OTP R15B01">p(integer(), integer(), integer(), integer(), string(), string(), string(), string()) -> true | false | error | badarg</name>
<fsummary>Trigger the user trace probe.</fsummary>
<desc>
<p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing all the integer()'s and string()'s provided, as well as any user tag set in the current process.</p>
</desc>
</func>
<func>
- <name>get_tag() -> binary() | undefined</name>
+ <name since="OTP R15B01">get_tag() -> binary() | undefined</name>
<fsummary>Get the user tag set in the process.</fsummary>
<desc>
<p>This function returns the user tag set in the current
@@ -134,7 +134,7 @@
</desc>
</func>
<func>
- <name>get_tag() -> binary() | undefined</name>
+ <name since="OTP R15B01">get_tag() -> binary() | undefined</name>
<fsummary>Get the user tag set in the process or sent to the process.</fsummary>
<desc>
<p>This function returns the user tag set in the current
@@ -151,7 +151,7 @@
</func>
<func>
- <name>put_tag(Item) -> binary() | undefined </name>
+ <name since="OTP R15B01">put_tag(Item) -> binary() | undefined </name>
<fsummary>Set the user tag of the current process.</fsummary>
<type>
<v>Item = iodata()</v>
@@ -163,7 +163,7 @@
</desc>
</func>
<func>
- <name>spread_tag(boolean()) -> TagData</name>
+ <name since="OTP R15B01">spread_tag(boolean()) -> TagData</name>
<fsummary>Start or stop spreading dynamic trace user tags with the next message.</fsummary>
<type>
<v>TagData = opaque data that can be used as parameter to <seealso marker="#restore_tag/1">restore_tag/1</seealso></v>
@@ -185,7 +185,7 @@ f() ->
</desc>
</func>
<func>
- <name>restore_tag(TagData) -> true</name>
+ <name since="OTP R15B01">restore_tag(TagData) -> true</name>
<fsummary>Restore to a previous state of user tag spreading.</fsummary>
<type>
<v>TagData = opaque data returned by <seealso marker="#spread_tag/1">spread_tag/1</seealso></v>
diff --git a/lib/runtime_tools/doc/src/erts_alloc_config.xml b/lib/runtime_tools/doc/src/erts_alloc_config.xml
index ffc4ec5285..5bcce1b5e3 100644
--- a/lib/runtime_tools/doc/src/erts_alloc_config.xml
+++ b/lib/runtime_tools/doc/src/erts_alloc_config.xml
@@ -29,7 +29,7 @@
<rev>1</rev>
<file>erts_alloc_config.sgml</file>
</header>
- <module>erts_alloc_config</module>
+ <module since="">erts_alloc_config</module>
<modulesummary>Configuration tool for erts_alloc</modulesummary>
<description>
<note>
@@ -136,7 +136,7 @@
</description>
<funcs>
<func>
- <name>save_scenario() -> ok | {error, Error}</name>
+ <name since="">save_scenario() -> ok | {error, Error}</name>
<fsummary>Saves information about current runtime scenario</fsummary>
<type>
<v>Error = term()</v>
@@ -154,7 +154,7 @@
</desc>
</func>
<func>
- <name>make_config() -> ok | {error, Error}</name>
+ <name since="">make_config() -> ok | {error, Error}</name>
<fsummary>Creates an erts_alloc configuration</fsummary>
<type>
<v>Error = term()</v>
@@ -165,7 +165,7 @@
</desc>
</func>
<func>
- <name>make_config(FileNameOrIODev) -> ok | {error, Error}</name>
+ <name since="">make_config(FileNameOrIODev) -> ok | {error, Error}</name>
<fsummary>Creates an erts_alloc configuration</fsummary>
<type>
<v>FileNameOrIODev = string() | io_device()</v>
@@ -190,7 +190,7 @@
</desc>
</func>
<func>
- <name>stop() -> ok | {error, Error}</name>
+ <name since="">stop() -> ok | {error, Error}</name>
<fsummary></fsummary>
<type>
<v>Error = term()</v>
diff --git a/lib/runtime_tools/doc/src/msacc.xml b/lib/runtime_tools/doc/src/msacc.xml
index 129da3d230..ae089de8d0 100644
--- a/lib/runtime_tools/doc/src/msacc.xml
+++ b/lib/runtime_tools/doc/src/msacc.xml
@@ -31,7 +31,7 @@
<rev>A</rev>
<file>msacc.xml</file>
</header>
- <module>msacc</module>
+ <module since="OTP 19.0">msacc</module>
<modulesummary>Convenience functions for microstate accounting</modulesummary>
<description>
<p>This module implements some convenience functions for analyzing
@@ -146,7 +146,7 @@ ok
</datatypes>
<funcs>
<func>
- <name name="available" arity="0"/>
+ <name name="available" arity="0" since="OTP 19.0"/>
<fsummary>Check if microstate accounting is available</fsummary>
<desc>
<p>This function checks whether microstate accounting
@@ -154,7 +154,7 @@ ok
</desc>
</func>
<func>
- <name name="start" arity="0"/>
+ <name name="start" arity="0" since="OTP 19.0"/>
<fsummary>Start microstate accounting.</fsummary>
<desc>
<p>Start microstate accounting. Returns whether it was
@@ -162,7 +162,7 @@ ok
</desc>
</func>
<func>
- <name name="start" arity="1"/>
+ <name name="start" arity="1" since="OTP 19.0"/>
<fsummary>Start microstate accounting for a time.</fsummary>
<desc>
<p>Resets all counters and then starts microstate accounting
@@ -170,7 +170,7 @@ ok
</desc>
</func>
<func>
- <name name="stop" arity="0"/>
+ <name name="stop" arity="0" since="OTP 19.0"/>
<fsummary>Stop microstate accounting.</fsummary>
<desc>
<p>Stop microstate accounting.
@@ -178,7 +178,7 @@ ok
</desc>
</func>
<func>
- <name name="reset" arity="0"/>
+ <name name="reset" arity="0" since="OTP 19.0"/>
<fsummary>Reset microstate accounting counters</fsummary>
<desc>
<p>Reset microstate accounting counters.
@@ -186,7 +186,7 @@ ok
</desc>
</func>
<func>
- <name name="print" arity="0"/>
+ <name name="print" arity="0" since="OTP 19.0"/>
<fsummary>Print microstate statistics</fsummary>
<desc>
<p>
@@ -199,7 +199,7 @@ ok
</desc>
</func>
<func>
- <name name="print" arity="1"/>
+ <name name="print" arity="1" since="OTP 19.0"/>
<fsummary>Print microstate statistics</fsummary>
<desc>
<p>Print the given microstate statistics values to stdout.
@@ -211,7 +211,7 @@ ok
</desc>
</func>
<func>
- <name name="print" arity="2"/>
+ <name name="print" arity="2" since="OTP 19.0"/>
<fsummary>Print microstate statistics</fsummary>
<desc>
<p>Print the given microstate statistics values to standard out.
@@ -234,7 +234,7 @@ ok
</desc>
</func>
<func>
- <name name="print" arity="3"/>
+ <name name="print" arity="3" since="OTP 19.0"/>
<fsummary>Print microstate statistics</fsummary>
<desc>
<p>Print the given microstate statistics values to the given file
@@ -243,7 +243,7 @@ ok
</desc>
</func>
<func>
- <name name="stats" arity="0"/>
+ <name name="stats" arity="0" since="OTP 19.0"/>
<fsummary></fsummary>
<desc>
<p>Returns a runtime system independent version of the microstate
@@ -254,7 +254,7 @@ ok
</desc>
</func>
<func>
- <name name="stats" arity="2" clause_i="1"/>
+ <name name="stats" arity="2" clause_i="1" since="OTP 19.0"/>
<fsummary></fsummary>
<desc>
<p>Returns the system time for the given microstate statistics values.
@@ -269,7 +269,7 @@ ok
</desc>
</func>
<func>
- <name name="stats" arity="2" clause_i="2"/>
+ <name name="stats" arity="2" clause_i="2" since="OTP 19.0"/>
<fsummary></fsummary>
<desc>
<p>Returns fractions of real-time or run-time spent in the various
@@ -277,7 +277,7 @@ ok
</desc>
</func>
<func>
- <name name="stats" arity="2" clause_i="3"/>
+ <name name="stats" arity="2" clause_i="3" since="OTP 19.0"/>
<fsummary></fsummary>
<desc>
<p>Returns a list of microstate statistics values where the values
@@ -285,7 +285,7 @@ ok
</desc>
</func>
<func>
- <name name="to_file" arity="1"/>
+ <name name="to_file" arity="1" since="OTP 19.0"/>
<fsummary></fsummary>
<desc>
<p>Dumps the current microstate statistics counters to a file that can
@@ -294,7 +294,7 @@ ok
</desc>
</func>
<func>
- <name name="from_file" arity="1"/>
+ <name name="from_file" arity="1" since="OTP 19.0"/>
<fsummary></fsummary>
<desc>
<p>Read a file dump produced by <seealso marker="#to_file/1">
diff --git a/lib/runtime_tools/doc/src/scheduler.xml b/lib/runtime_tools/doc/src/scheduler.xml
index dd8bf73bae..b033430183 100644
--- a/lib/runtime_tools/doc/src/scheduler.xml
+++ b/lib/runtime_tools/doc/src/scheduler.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>scheduler.xml</file>
</header>
- <module>scheduler</module>
+ <module since="OTP 21.0">scheduler</module>
<modulesummary>Measure scheduler utilization</modulesummary>
<description>
<p>This module contains utility functions for easier measurement and
@@ -84,7 +84,7 @@
<funcs>
<func>
- <name name="sample" arity="0"/>
+ <name name="sample" arity="0" since="OTP 21.0"/>
<fsummary>Get scheduler utilization sample.</fsummary>
<desc>
<p>Return a scheduler utilization sample for normal and dirty-cpu
@@ -93,7 +93,7 @@
</func>
<func>
- <name name="sample_all" arity="0"/>
+ <name name="sample_all" arity="0" since="OTP 21.0"/>
<fsummary>Get scheduler utilization sample.</fsummary>
<desc>
<p>Return a scheduler utilization sample for all schedulers,
@@ -102,7 +102,7 @@
</func>
<func>
- <name name="utilization" arity="1" clause_i="1"/>
+ <name name="utilization" arity="1" clause_i="1" since="OTP 21.0"/>
<fsummary>Measure scheduler utilizations during a period of time.</fsummary>
<desc>
<p>Measure utilization for normal and dirty-cpu schedulers during
@@ -111,7 +111,7 @@
</func>
<func>
- <name name="utilization" arity="1" clause_i="2"/>
+ <name name="utilization" arity="1" clause_i="2" since="OTP 21.0"/>
<fsummary>Measure scheduler utilizations since sample.</fsummary>
<desc>
<p>Calculate scheduler utilizations for the time interval from when
@@ -121,7 +121,7 @@
</func>
<func>
- <name name="utilization" arity="2"/>
+ <name name="utilization" arity="2" since="OTP 21.0"/>
<fsummary>Measure scheduler utilizations between two samples.</fsummary>
<desc>
<p>Calculates scheduler utilizations for the time interval between
diff --git a/lib/runtime_tools/doc/src/system_information.xml b/lib/runtime_tools/doc/src/system_information.xml
index 53dc595e64..a356b5c6f8 100644
--- a/lib/runtime_tools/doc/src/system_information.xml
+++ b/lib/runtime_tools/doc/src/system_information.xml
@@ -32,14 +32,14 @@
<rev></rev>
<file>system_information.xml</file>
</header>
- <module>system_information</module>
+ <module since="OTP 17.0">system_information</module>
<modulesummary>System Information</modulesummary>
<description>
<p></p>
</description>
<funcs>
<func>
- <name name="sanity_check" arity="0"/>
+ <name name="sanity_check" arity="0" since="OTP 17.0"/>
<fsummary>Perform a sanity check</fsummary>
<desc>
<p>Performs a sanity check on the system. If no issues
@@ -88,7 +88,7 @@
</desc>
</func>
<func>
- <name name="to_file" arity="1"/>
+ <name name="to_file" arity="1" since="OTP 17.0"/>
<fsummary>Write miscellaneous system information to file</fsummary>
<desc><p>Writes miscellaneous system information to file. This
information will typically be requested by the Erlang/OTP team
diff --git a/lib/runtime_tools/examples/dist.systemtap b/lib/runtime_tools/examples/dist.systemtap
index bb20d617e1..4102a5243c 100644
--- a/lib/runtime_tools/examples/dist.systemtap
+++ b/lib/runtime_tools/examples/dist.systemtap
@@ -19,18 +19,18 @@
* %CopyrightEnd%
*/
/*
- * Note: This file assumes that you're using the non-SMP-enabled Erlang
- * virtual machine, "beam". The SMP-enabled VM is called "beam.smp".
+ * Note: This file assumes that you're using the SMP-enabled Erlang
+ * virtual machine, "beam.smp".
* Note that other variations of the virtual machine also have
* different names, e.g. the debug build of the SMP-enabled VM
* is "beam.debug.smp".
*
* To use a different virtual machine, replace each instance of
- * "beam" with "beam.smp" or the VM name appropriate to your
- * environment.
+ * "beam.smp" with "beam.debug.smp" or the VM name appropriate
+ * to your environment.
*/
-probe process("beam").mark("dist-monitor")
+probe process("beam.smp").mark("dist-monitor")
{
printf("monitor: pid %d, who %s, what %s, node %s, type %s, reason %s\n",
pid(),
@@ -38,38 +38,38 @@ probe process("beam").mark("dist-monitor")
user_string($arg5));
}
-probe process("beam").mark("dist-port_busy")
+probe process("beam.smp").mark("dist-port_busy")
{
printf("dist port_busy: node %s, port %s, remote_node %s, blocked pid %s\n",
user_string($arg1), user_string($arg2), user_string($arg3), user_string($arg4));
- blocked_procs[user_string($arg4)] = timestamp;
+ blocked_procs[user_string($arg4)] = local_clock_ns();
}
-probe process("beam").mark("dist-port_busy")
+probe process("beam.smp").mark("dist-port_busy")
{
printf("dist port_busy: node %s, port %s, remote_node %s, blocked pid %s\n",
user_string($arg1), user_string($arg2), user_string($arg3), user_string($arg4));
- blocked_procs[user_string($arg4)] = timestamp;
+ blocked_procs[user_string($arg4)] = local_clock_ns();
}
-probe process("beam").mark("dist-output")
+probe process("beam.smp").mark("dist-output")
{
printf("dist output: node %s, port %s, remote_node %s bytes %d\n",
user_string($arg1), user_string($arg2), user_string($arg3), $arg4);
}
-probe process("beam").mark("dist-outputv")
+probe process("beam.smp").mark("dist-outputv")
{
printf("port outputv: node %s, port %s, remote_node %s bytes %d\n",
user_string($arg1), user_string($arg2), user_string($arg3), $arg4);
}
-probe process("beam").mark("process-scheduled")
+probe process("beam.smp").mark("process-scheduled")
{
pidstr = user_string($arg1);
if (pidstr in blocked_procs) {
printf("blocked pid %s scheduled now, waited %d microseconds\n",
- pidstr, (timestamp - blocked_procs[pidstr]) / 1000);
+ pidstr, (local_clock_ns() - blocked_procs[pidstr]) / 1000);
delete blocked_procs[pidstr];
}
}
diff --git a/lib/runtime_tools/examples/driver1.systemtap b/lib/runtime_tools/examples/driver1.systemtap
index e1ee8ecffc..f5bc28b42d 100644
--- a/lib/runtime_tools/examples/driver1.systemtap
+++ b/lib/runtime_tools/examples/driver1.systemtap
@@ -19,108 +19,102 @@
* %CopyrightEnd%
*/
/*
- * Note: This file assumes that you're using the non-SMP-enabled Erlang
- * virtual machine, "beam". The SMP-enabled VM is called "beam.smp".
+ * Note: This file assumes that you're using the SMP-enabled Erlang
+ * virtual machine, "beam.smp".
* Note that other variations of the virtual machine also have
* different names, e.g. the debug build of the SMP-enabled VM
* is "beam.debug.smp".
*
* To use a different virtual machine, replace each instance of
- * "beam" with "beam.smp" or the VM name appropriate to your
- * environment.
+ * "beam.smp" with "beam.debug.smp" or the VM name appropriate
+ * to your environment.
*/
-probe process("beam").mark("driver-init")
+probe process("beam.smp").mark("driver__init")
{
printf("driver init name %s major %d minor %d flags %d\n",
user_string($arg1), $arg2, $arg3, $arg4);
}
-probe process("beam").mark("driver-start")
+probe process("beam.smp").mark("driver__start")
{
printf("driver start pid %s driver name %s port %s\n",
user_string($arg1), user_string($arg2), user_string($arg3));
}
-probe process("beam").mark("driver-stop")
+probe process("beam.smp").mark("driver__stop")
{
printf("driver stop pid %s driver name %s port %s\n",
user_string($arg1), user_string($arg2), user_string($arg3));
}
-probe process("beam").mark("driver-finish")
+probe process("beam.smp").mark("driver__finish")
{
printf("driver finish driver name %s\n",
user_string($arg1));
}
-probe process("beam").mark("driver-flush")
+probe process("beam.smp").mark("driver__flush")
{
printf("driver flush pid %s port %s port name %s\n",
user_string($arg1), user_string($arg2), user_string($arg3));
}
-probe process("beam").mark("driver-output")
+probe process("beam.smp").mark("driver__output")
{
printf("driver output pid %s port %s port name %s bytes %d\n",
user_string($arg1), user_string($arg2), user_string($arg3), $arg4);
}
-probe process("beam").mark("driver-outputv")
+probe process("beam.smp").mark("driver__outputv")
{
printf("driver outputv pid %s port %s port name %s bytes %d\n",
user_string($arg1), user_string($arg2), user_string($arg3), $arg4);
}
-probe process("beam").mark("driver-control")
+probe process("beam.smp").mark("driver__control")
{
printf("driver control pid %s port %s port name %s command %d bytes %d\n",
user_string($arg1), user_string($arg2), user_string($arg3), $arg4, $arg5);
}
-probe process("beam").mark("driver-call")
+probe process("beam.smp").mark("driver__call")
{
printf("driver call pid %s port %s port name %s command %d bytes %d\n",
user_string($arg1), user_string($arg2), user_string($arg3), $arg4, $arg5);
}
-probe process("beam").mark("driver-event")
-{
- printf("driver event pid %s port %s port name %s\n",
- user_string($arg1), user_string($arg2), user_string($arg3));
-}
-
-probe process("beam").mark("driver-ready_input")
+probe process("beam.smp").mark("driver__ready_input")
{
printf("driver ready_input pid %s port %s port name %s\n",
user_string($arg1), user_string($arg2), user_string($arg3));
}
-probe process("beam").mark("driver-ready_output")
+probe process("beam.smp").mark("driver__ready_output")
{
printf("driver ready_output pid %s port %s port name %s\n",
user_string($arg1), user_string($arg2), user_string($arg3));
}
-probe process("beam").mark("driver-timeout")
+probe process("beam.smp").mark("driver__timeout")
{
printf("driver timeout pid %s port %s port name %s\n",
user_string($arg1), user_string($arg2), user_string($arg3));
}
-probe process("beam").mark("driver-ready_async")
+probe process("beam.smp").mark("driver__ready_async")
{
printf("driver ready_async pid %s port %s port name %s\n",
user_string($arg1), user_string($arg2), user_string($arg3));
}
-probe process("beam").mark("driver-process_exit")
+probe process("beam.smp").mark("driver__process_exit")
{
printf("driver process_exit pid %s port %s port name %s\n",
user_string($arg1), user_string($arg2), user_string($arg3));
}
-probe process("beam").mark("driver-stop_select")
+probe process("beam.smp").mark("driver__stop_select")
{
printf("driver stop_select driver name %s\n", user_string($arg1));
}
diff --git a/lib/runtime_tools/examples/function-calls.systemtap b/lib/runtime_tools/examples/function-calls.systemtap
index 9c44b2d014..6bb173b3ec 100644
--- a/lib/runtime_tools/examples/function-calls.systemtap
+++ b/lib/runtime_tools/examples/function-calls.systemtap
@@ -18,51 +18,51 @@
* %CopyrightEnd%
*/
/*
- * Note: This file assumes that you're using the non-SMP-enabled Erlang
- * virtual machine, "beam". The SMP-enabled VM is called "beam.smp".
+ * Note: This file assumes that you're using the SMP-enabled Erlang
+ * virtual machine, "beam.smp".
* Note that other variations of the virtual machine also have
* different names, e.g. the debug build of the SMP-enabled VM
* is "beam.debug.smp".
*
* To use a different virtual machine, replace each instance of
- * "beam" with "beam.smp" or the VM name appropriate to your
- * environment.
+ * "beam.smp" with "beam.debug.smp" or the VM name appropriate
+ * to your environment.
*/
-probe process("beam").mark("local-function-entry")
+probe process("beam.smp").mark("local-function-entry")
{
printf("pid %s enter (local) %s depth %d\n",
user_string($arg1), user_string($arg2), $arg3);
}
-probe process("beam").mark("global-function-entry")
+probe process("beam.smp").mark("global-function-entry")
{
printf("pid %s enter (global) %s depth %d\n",
user_string($arg1), user_string($arg2), $arg3);
}
-probe process("beam").mark("function-return")
+probe process("beam.smp").mark("function-return")
{
printf("pid %s return %s depth %d\n",
user_string($arg1), user_string($arg2), $arg3);
}
-probe process("beam").mark("bif-entry")
+probe process("beam.smp").mark("bif-entry")
{
printf("pid %s BIF entry mfa %s\n", user_string($arg1), user_string($arg2));
}
-probe process("beam").mark("bif-return")
+probe process("beam.smp").mark("bif-return")
{
printf("pid %s BIF return mfa %s\n", user_string($arg1), user_string($arg2));
}
-probe process("beam").mark("nif-entry")
+probe process("beam.smp").mark("nif-entry")
{
printf("pid %s NIF entry mfa %s\n", user_string($arg1), user_string($arg2));
}
-probe process("beam").mark("nif-return")
+probe process("beam.smp").mark("nif-return")
{
printf("pid %s NIF return mfa %s\n", user_string($arg1), user_string($arg2));
}
diff --git a/lib/runtime_tools/examples/garbage-collection.systemtap b/lib/runtime_tools/examples/garbage-collection.systemtap
index e414eea821..14f0d6851c 100644
--- a/lib/runtime_tools/examples/garbage-collection.systemtap
+++ b/lib/runtime_tools/examples/garbage-collection.systemtap
@@ -18,33 +18,33 @@
* %CopyrightEnd%
*/
/*
- * Note: This file assumes that you're using the non-SMP-enabled Erlang
- * virtual machine, "beam". The SMP-enabled VM is called "beam.smp".
+ * Note: This file assumes that you're using the SMP-enabled Erlang
+ * virtual machine, "beam.smp".
* Note that other variations of the virtual machine also have
* different names, e.g. the debug build of the SMP-enabled VM
* is "beam.debug.smp".
*
* To use a different virtual machine, replace each instance of
- * "beam" with "beam.smp" or the VM name appropriate to your
- * environment.
+ * "beam.smp" with "beam.debug.smp" or the VM name appropriate
+ * to your environment.
*/
-probe process("beam").mark("gc_major-start")
+probe process("beam.smp").mark("gc_major-start")
{
printf("GC major start pid %s need %d words\n", user_string($arg1), $arg2);
}
-probe process("beam").mark("gc_minor-start")
+probe process("beam.smp").mark("gc_minor-start")
{
printf("GC minor start pid %s need %d words\n", user_string($arg1), $arg2);
}
-probe process("beam").mark("gc_major-end")
+probe process("beam.smp").mark("gc_major-end")
{
printf("GC major end pid %s reclaimed %d words\n", user_string($arg1), $arg2);
}
-probe process("beam").mark("gc_minor-start")
+probe process("beam.smp").mark("gc_minor-start")
{
printf("GC minor end pid %s reclaimed %d words\n", user_string($arg1), $arg2);
}
diff --git a/lib/runtime_tools/examples/memory1.systemtap b/lib/runtime_tools/examples/memory1.systemtap
index 04df4d64c4..2fdc5a796c 100644
--- a/lib/runtime_tools/examples/memory1.systemtap
+++ b/lib/runtime_tools/examples/memory1.systemtap
@@ -18,34 +18,34 @@
* %CopyrightEnd%
*/
/*
- * Note: This file assumes that you're using the non-SMP-enabled Erlang
- * virtual machine, "beam". The SMP-enabled VM is called "beam.smp".
+ * Note: This file assumes that you're using the SMP-enabled Erlang
+ * virtual machine, "beam.smp".
* Note that other variations of the virtual machine also have
* different names, e.g. the debug build of the SMP-enabled VM
* is "beam.debug.smp".
*
* To use a different virtual machine, replace each instance of
- * "beam" with "beam.smp" or the VM name appropriate to your
- * environment.
+ * "beam.smp" with "beam.debug.smp" or the VM name appropriate
+ * to your environment.
*/
-probe process("beam").mark("copy-struct")
+probe process("beam.smp").mark("copy-struct")
{
printf("copy_struct %d bytes\n", $arg1);
}
-probe process("beam").mark("copy-object")
+probe process("beam.smp").mark("copy-object")
{
printf("copy_object pid %s %d bytes\n", user_string($arg1), $arg2);
}
-probe process("beam").mark("process-heap_grow")
+probe process("beam.smp").mark("process-heap_grow")
{
printf("proc heap grow pid %s %d -> %d bytes\n", user_string($arg1),
$arg2, $arg3);
}
-probe process("beam").mark("process-heap_shrink")
+probe process("beam.smp").mark("process-heap_shrink")
{
printf("proc heap shrink pid %s %d -> %d bytes\n", user_string($arg1),
$arg2, $arg3);
diff --git a/lib/runtime_tools/examples/messages.systemtap b/lib/runtime_tools/examples/messages.systemtap
index f2ef56a22b..49b7f46d69 100644
--- a/lib/runtime_tools/examples/messages.systemtap
+++ b/lib/runtime_tools/examples/messages.systemtap
@@ -18,15 +18,15 @@
* %CopyrightEnd%
*/
/*
- * Note: This file assumes that you're using the non-SMP-enabled Erlang
- * virtual machine, "beam". The SMP-enabled VM is called "beam.smp".
+ * Note: This file assumes that you're using the SMP-enabled Erlang
+ * virtual machine, "beam.smp".
* Note that other variations of the virtual machine also have
* different names, e.g. the debug build of the SMP-enabled VM
* is "beam.debug.smp".
*
* To use a different virtual machine, replace each instance of
- * "beam" with "beam.smp" or the VM name appropriate to your
- * environment.
+ * "beam.smp" with "beam.debug.smp" or the VM name appropriate
+ * to your environment.
*/
probe begin
@@ -38,7 +38,7 @@ probe begin
printf("\n");
}
-probe process("beam").mark("message-send")
+probe process("beam.smp").mark("message-send")
{
if ($arg4 == 0 && $arg5 == 0 && $arg6 == 0) {
printf("send: %s -> %s: %d words\n",
@@ -51,7 +51,7 @@ probe process("beam").mark("message-send")
}
}
-probe process("beam").mark("message-send-remote")
+probe process("beam.smp").mark("message-send-remote")
{
if ($arg5 == 0 && $arg6 == 0 && $arg7 == 0) {
printf("send : %s -> %s %s: %d words\n",
@@ -64,7 +64,7 @@ probe process("beam").mark("message-send-remote")
}
}
-probe process("beam").mark("message-queued")
+probe process("beam.smp").mark("message-queued")
{
if ($arg4 == 0 && $arg5 == 0 && $arg6 == 0) {
printf("queued: %s: %d words, queue len %d\n", user_string($arg1), $arg2, $arg3);
@@ -75,7 +75,7 @@ probe process("beam").mark("message-queued")
}
}
-probe process("beam").mark("message-receive")
+probe process("beam.smp").mark("message-receive")
{
if ($arg4 == 0 && $arg5 == 0 && $arg6 == 0) {
printf("receive: %s: %d words, queue len %d\n",
diff --git a/lib/runtime_tools/examples/port1.systemtap b/lib/runtime_tools/examples/port1.systemtap
index f7ce03a65e..235581b0b1 100644
--- a/lib/runtime_tools/examples/port1.systemtap
+++ b/lib/runtime_tools/examples/port1.systemtap
@@ -18,15 +18,15 @@
* %CopyrightEnd%
*/
/*
- * Note: This file assumes that you're using the non-SMP-enabled Erlang
- * virtual machine, "beam". The SMP-enabled VM is called "beam.smp".
+ * Note: This file assumes that you're using the SMP-enabled Erlang
+ * virtual machine, "beam.smp".
* Note that other variations of the virtual machine also have
* different names, e.g. the debug build of the SMP-enabled VM
* is "beam.debug.smp".
*
* To use a different virtual machine, replace each instance of
- * "beam" with "beam.smp" or the VM name appropriate to your
- * environment.
+ * "beam.smp" with "beam.debug.smp" or the VM name appropriate
+ * to your environment.
*/
probe begin
@@ -96,19 +96,19 @@ probe begin
driver_map["udp_inet", 62] = "BINDX";
}
-probe process("beam").mark("port-open")
+probe process("beam.smp").mark("port-open")
{
printf("port open pid %s port name %s port %s\n",
user_string($arg1), user_string($arg2), user_string($arg3));
}
-probe process("beam").mark("port-command")
+probe process("beam.smp").mark("port-command")
{
printf("port command pid %s port %s port name %s command type %s\n",
user_string($arg1), user_string($arg2), user_string($arg3), user_string($arg4));
}
-probe process("beam").mark("port-control")
+probe process("beam.smp").mark("port-control")
{
cmd = driver_map[user_string($arg3), $arg4];
cmd_str = (cmd == "") ? "unknown" : cmd;
@@ -118,36 +118,36 @@ probe process("beam").mark("port-control")
/* port-exit is fired as a result of port_close() or exit signal */
-probe process("beam").mark("port-exit")
+probe process("beam.smp").mark("port-exit")
{
printf("port exit pid %s port %s port name %s reason %s\n",
user_string($arg1), user_string($arg2), user_string($arg3), user_string($arg4));
}
-probe process("beam").mark("port-connect")
+probe process("beam.smp").mark("port-connect")
{
printf("port connect pid %s port %s port name %s new pid %s\n",
user_string($arg1), user_string($arg2), user_string($arg3), user_string($arg4));
}
-probe process("beam").mark("port-busy")
+probe process("beam.smp").mark("port-busy")
{
printf("port busy %s\n", user_string($arg1));
}
-probe process("beam").mark("port-not_busy")
+probe process("beam.smp").mark("port-not_busy")
{
printf("port not busy %s\n", user_string($arg1));
}
-probe process("beam").mark("aio_pool-add")
+probe process("beam.smp").mark("aio_pool-add")
{
printf("async I/O pool add thread %d queue len %d\n", $arg1, $arg2);
}
-probe process("beam").mark("aio_pool-get")
+probe process("beam.smp").mark("aio_pool-get")
{
printf("async I/O pool get thread %d queue len %d\n", $arg1, $arg2);
}
-global driver_map; \ No newline at end of file
+global driver_map;
diff --git a/lib/runtime_tools/examples/process-scheduling.systemtap b/lib/runtime_tools/examples/process-scheduling.systemtap
index b0b74257b3..231c589f64 100644
--- a/lib/runtime_tools/examples/process-scheduling.systemtap
+++ b/lib/runtime_tools/examples/process-scheduling.systemtap
@@ -18,28 +18,28 @@
* %CopyrightEnd%
*/
/*
- * Note: This file assumes that you're using the non-SMP-enabled Erlang
- * virtual machine, "beam". The SMP-enabled VM is called "beam.smp".
+ * Note: This file assumes that you're using the SMP-enabled Erlang
+ * virtual machine, "beam.smp".
* Note that other variations of the virtual machine also have
* different names, e.g. the debug build of the SMP-enabled VM
* is "beam.debug.smp".
*
* To use a different virtual machine, replace each instance of
- * "beam" with "beam.smp" or the VM name appropriate to your
- * environment.
+ * "beam.smp" with "beam.debug.smp" or the VM name appropriate
+ * to your environment.
*/
-probe process("beam").mark("process-scheduled")
+probe process("beam.smp").mark("process-scheduled")
{
printf(" Schedule pid %s mfa %s\n", user_string($arg1), user_string($arg2));
}
-probe process("beam").mark("process-unscheduled")
+probe process("beam.smp").mark("process-unscheduled")
{
printf("Unschedule pid %s\n", user_string($arg1));
}
-probe process("beam").mark("process-hibernate")
+probe process("beam.smp").mark("process-hibernate")
{
printf(" Hibernate pid %s resume mfa %s\n",
user_string($arg1), user_string($arg2));
diff --git a/lib/runtime_tools/examples/spawn-exit.systemtap b/lib/runtime_tools/examples/spawn-exit.systemtap
index 89bca14496..a7b4a0a3ea 100644
--- a/lib/runtime_tools/examples/spawn-exit.systemtap
+++ b/lib/runtime_tools/examples/spawn-exit.systemtap
@@ -18,34 +18,34 @@
* %CopyrightEnd%
*/
/*
- * Note: This file assumes that you're using the non-SMP-enabled Erlang
- * virtual machine, "beam". The SMP-enabled VM is called "beam.smp".
+ * Note: This file assumes that you're using the SMP-enabled Erlang
+ * virtual machine, "beam.smp".
* Note that other variations of the virtual machine also have
* different names, e.g. the debug build of the SMP-enabled VM
* is "beam.debug.smp".
*
* To use a different virtual machine, replace each instance of
- * "beam" with "beam.smp" or the VM name appropriate to your
- * environment.
+ * "beam.smp" with "beam.debug.smp" or the VM name appropriate
+ * to your environment.
*/
-probe process("beam").mark("process-spawn")
+probe process("beam.smp").mark("process-spawn")
{
printf("pid %s mfa %s\n", user_string($arg1), user_string($arg2));
}
-probe process("beam").mark("process-exit")
+probe process("beam.smp").mark("process-exit")
{
printf("pid %s reason %s\n", user_string($arg1), user_string($arg2));
}
-probe process("beam").mark("process-exit_signal")
+probe process("beam.smp").mark("process-exit_signal")
{
printf("sender %s -> pid %s reason %s\n",
user_string($arg1), user_string($arg2), user_string($arg3));
}
-probe process("beam").mark("process-exit_signal-remote")
+probe process("beam.smp").mark("process-exit_signal-remote")
{
printf("sender %s -> node %s pid %s reason %s\n",
user_string($arg1), user_string($arg2), user_string($arg3), user_string($arg4));
diff --git a/lib/runtime_tools/examples/user-probe-n.systemtap b/lib/runtime_tools/examples/user-probe-n.systemtap
index 25f7503283..8a0a89c931 100644
--- a/lib/runtime_tools/examples/user-probe-n.systemtap
+++ b/lib/runtime_tools/examples/user-probe-n.systemtap
@@ -18,18 +18,19 @@
* %CopyrightEnd%
*/
/*
- * Note: This file assumes that you're using the non-SMP-enabled Erlang
- * virtual machine, "beam". The SMP-enabled VM is called "beam.smp".
+ * Note: This file assumes that you're using the SMP-enabled Erlang
+ * virtual machine, "beam.smp".
* Note that other variations of the virtual machine also have
* different names, e.g. the debug build of the SMP-enabled VM
* is "beam.debug.smp".
*
* To use a different virtual machine, replace each instance of
- * "beam" with "beam.smp" or the VM name appropriate to your
- * environment.
+ * "beam.smp" with "beam.debug.smp" or the VM name appropriate
+ * to your environment.
*/
-probe process("beam").mark("user_trace-n0")
+
+probe process("beam.smp").mark("user_trace-n0")
{
printf("probe n0: %s %s %d %d %d %d '%s' '%s' '%s' '%s'\n",
user_string($arg1),
@@ -41,7 +42,7 @@ probe process("beam").mark("user_trace-n0")
$arg9 == NULL ? "" : user_string($arg9));
}
-probe process("beam").mark("user_trace-n1")
+probe process("beam.smp").mark("user_trace-n1")
{
printf("probe n1: %s %s %d %d %d %d '%s' '%s' '%s' '%s'\n",
user_string($arg1),
diff --git a/lib/runtime_tools/examples/user-probe.systemtap b/lib/runtime_tools/examples/user-probe.systemtap
index 1777476e54..ce9dde30f8 100644
--- a/lib/runtime_tools/examples/user-probe.systemtap
+++ b/lib/runtime_tools/examples/user-probe.systemtap
@@ -18,23 +18,23 @@
* %CopyrightEnd%
*/
/*
- * Note: This file assumes that you're using the non-SMP-enabled Erlang
- * virtual machine, "beam". The SMP-enabled VM is called "beam.smp".
+ * Note: This file assumes that you're using the SMP-enabled Erlang
+ * virtual machine, "beam.smp".
* Note that other variations of the virtual machine also have
* different names, e.g. the debug build of the SMP-enabled VM
* is "beam.debug.smp".
*
* To use a different virtual machine, replace each instance of
- * "beam" with "beam.smp" or the VM name appropriate to your
- * environment.
+ * "beam.smp" with "beam.debug.smp" or the VM name appropriate
+ * to your environment.
*/
-probe process("beam").mark("user_trace-s1")
+probe process("beam.smp").mark("user_trace-s1")
{
printf("%s\n", user_string($arg1));
}
-probe process("beam").mark("user_trace-i4s4")
+probe process("beam.smp").mark("user_trace-i4s4")
{
printf("%s %s %d %d %d %d '%s' '%s' '%s' '%s'\n",
user_string($arg1),
diff --git a/lib/runtime_tools/src/system_information.erl b/lib/runtime_tools/src/system_information.erl
index 136ee55b54..8f7bfa195b 100644
--- a/lib/runtime_tools/src/system_information.erl
+++ b/lib/runtime_tools/src/system_information.erl
@@ -400,7 +400,6 @@ os_getenv_erts_specific() ->
"ERL_MALLOC_LIB",
"ERL_MAX_PORTS",
"ERL_MAX_ETS_TABLES",
- "ERL_NO_VFORK",
"ERL_NO_KERNEL_POLL",
"ERL_THREAD_POOL_SIZE",
"ERLC_EMULATOR",
diff --git a/lib/runtime_tools/test/system_information_SUITE_data/information_test_report.dat b/lib/runtime_tools/test/system_information_SUITE_data/information_test_report.dat
index a0e3806981..a9399a5d53 100644
--- a/lib/runtime_tools/test/system_information_SUITE_data/information_test_report.dat
+++ b/lib/runtime_tools/test/system_information_SUITE_data/information_test_report.dat
@@ -6829,7 +6829,7 @@
{native,false},
{compiler,"4.9.1"},
{md5,"55bb9fddcdf820938be2efee15eccd82"}]},
- {otp_ring0,
+ {erl_init,
[{loaded,true},
{native,false},
{compiler,"4.9.1"},
diff --git a/lib/sasl/doc/src/alarm_handler.xml b/lib/sasl/doc/src/alarm_handler.xml
index 4160757164..6e74f833cd 100644
--- a/lib/sasl/doc/src/alarm_handler.xml
+++ b/lib/sasl/doc/src/alarm_handler.xml
@@ -34,7 +34,7 @@
<rev>A</rev>
<file>alarm_handler.sgml.t1</file>
</header>
- <module>alarm_handler</module>
+ <module since="">alarm_handler</module>
<modulesummary>An Alarm Handling Process</modulesummary>
<description>
<p>The alarm handler process is a
@@ -81,7 +81,7 @@
<funcs>
<func>
- <name>clear_alarm(AlarmId) -> void()</name>
+ <name since="">clear_alarm(AlarmId) -> void()</name>
<fsummary>Clears the specified alarms.</fsummary>
<type>
<v>AlarmId = term()</v>
@@ -94,7 +94,7 @@
</func>
<func>
- <name>get_alarms() -> [alarm()]</name>
+ <name since="">get_alarms() -> [alarm()]</name>
<fsummary>Gets all active alarms.</fsummary>
<desc>
<p>Returns a list of all active alarms. This function can only
@@ -103,7 +103,7 @@
</func>
<func>
- <name>set_alarm(alarm())</name>
+ <name since="">set_alarm(alarm())</name>
<fsummary>Sets an alarm with an id.</fsummary>
<type>
<v>alarm() = {AlarmId, AlarmDescription}</v>
diff --git a/lib/sasl/doc/src/rb.xml b/lib/sasl/doc/src/rb.xml
index d5df4fd345..0ed7e91c11 100644
--- a/lib/sasl/doc/src/rb.xml
+++ b/lib/sasl/doc/src/rb.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>rb.sgml</file>
</header>
- <module>rb</module>
+ <module since="">rb</module>
<modulesummary>The Report Browser Tool</modulesummary>
<description>
<p>The Report Browser (RB) tool is used to browse and
@@ -43,8 +43,8 @@
<funcs>
<func>
- <name>filter(Filters)</name>
- <name>filter(Filters, Dates)</name>
+ <name since="OTP R13B04">filter(Filters)</name>
+ <name since="OTP R13B04">filter(Filters, Dates)</name>
<fsummary>Filters reports and displays them on the screen.</fsummary>
<type>
<v>Filters = [filter()]</v>
@@ -86,7 +86,7 @@
</func>
<func>
- <name>grep(RegExp)</name>
+ <name since="">grep(RegExp)</name>
<fsummary>Searches the reports for a regular expression.</fsummary>
<type>
<v>RegExp = string() | {string(), Options} | re:mp() | {re:mp(), Options}</v>
@@ -109,8 +109,8 @@
</func>
<func>
- <name>h()</name>
- <name>help()</name>
+ <name since="">h()</name>
+ <name since="">help()</name>
<fsummary>Displays help information.</fsummary>
<desc>
<p>Displays online help information.</p>
@@ -118,8 +118,8 @@
</func>
<func>
- <name>list()</name>
- <name>list(Type)</name>
+ <name since="">list()</name>
+ <name since="">list(Type)</name>
<fsummary>Lists all reports.</fsummary>
<type>
<v>Type = type()</v>
@@ -137,8 +137,8 @@
</func>
<func>
- <name>log_list()</name>
- <name>log_list(Type)</name>
+ <name since="OTP R16B02">log_list()</name>
+ <name since="OTP R16B02">log_list(Type)</name>
<fsummary>Logs report lists.</fsummary>
<type>
<v>Type = type()</v>
@@ -157,8 +157,8 @@
</func>
<func>
- <name>rescan()</name>
- <name>rescan(Options)</name>
+ <name since="">rescan()</name>
+ <name since="">rescan(Options)</name>
<fsummary>Rescans the report directory.</fsummary>
<type>
<v>Options = [opt()]</v>
@@ -171,8 +171,8 @@
</func>
<func>
- <name>show()</name>
- <name>show(Report)</name>
+ <name since="">show()</name>
+ <name since="">show(Report)</name>
<fsummary>Displays reports.</fsummary>
<type>
<v>Report = integer() | type()</v>
@@ -186,8 +186,8 @@
</func>
<func>
- <name>start()</name>
- <name>start(Options)</name>
+ <name since="">start()</name>
+ <name since="">start(Options)</name>
<fsummary>Starts the <c>rb_server</c>.</fsummary>
<type>
<v>Options = [opt()]</v>
@@ -256,7 +256,7 @@
</func>
<func>
- <name>start_log(FileName)</name>
+ <name since="">start_log(FileName)</name>
<fsummary>Redirects all output to <c>FileName</c>.</fsummary>
<type>
<v>FileName = string() | atom() | pid()</v>
@@ -268,7 +268,7 @@
</func>
<func>
- <name>stop()</name>
+ <name since="">stop()</name>
<fsummary>Stops the <c>rb_server</c>.</fsummary>
<desc>
<p>Stops <c>rb_server</c>.</p>
@@ -276,7 +276,7 @@
</func>
<func>
- <name>stop_log()</name>
+ <name since="">stop_log()</name>
<fsummary>Stops logging to file.</fsummary>
<desc>
<p>Closes the log file. The output from the RB tool is
diff --git a/lib/sasl/doc/src/release_handler.xml b/lib/sasl/doc/src/release_handler.xml
index 9ba276aeac..f8ee0306d8 100644
--- a/lib/sasl/doc/src/release_handler.xml
+++ b/lib/sasl/doc/src/release_handler.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>release_handler</module>
+ <module since="">release_handler</module>
<modulesummary>Unpacking and Installation of Release Packages</modulesummary>
<description>
<p>The <em>release handler</em> process belongs to the SASL
@@ -168,8 +168,8 @@
<funcs>
<func>
- <name>check_install_release(Vsn) -> {ok, OtherVsn, Descr} | {error, Reason}</name>
- <name>check_install_release(Vsn,Opts) -> {ok, OtherVsn, Descr} | {error, Reason}</name>
+ <name since="">check_install_release(Vsn) -> {ok, OtherVsn, Descr} | {error, Reason}</name>
+ <name since="OTP R14B04">check_install_release(Vsn,Opts) -> {ok, OtherVsn, Descr} | {error, Reason}</name>
<fsummary>Checks installation of a release in the system.</fsummary>
<type>
<v>Vsn = OtherVsn = string()</v>
@@ -202,7 +202,7 @@
</func>
<func>
- <name>create_RELEASES(Root, RelDir, RelFile, AppDirs) -> ok | {error, Reason}</name>
+ <name since="">create_RELEASES(Root, RelDir, RelFile, AppDirs) -> ok | {error, Reason}</name>
<fsummary>Creates an initial <c>RELEASES</c> file.</fsummary>
<type>
<v>Root = RelDir = RelFile = string()</v>
@@ -233,7 +233,7 @@
</func>
<func>
- <name>install_file(Vsn, File) -> ok | {error, Reason}</name>
+ <name since="">install_file(Vsn, File) -> ok | {error, Reason}</name>
<fsummary>Installs a release file in the release structure.</fsummary>
<type>
<v>Vsn = File = string()</v>
@@ -252,8 +252,8 @@
</func>
<func>
- <name>install_release(Vsn) -> {ok, OtherVsn, Descr} | {error, Reason}</name>
- <name>install_release(Vsn, [Opt]) -> {ok, OtherVsn, Descr} | {continue_after_restart, OtherVsn, Descr} | {error, Reason}</name>
+ <name since="">install_release(Vsn) -> {ok, OtherVsn, Descr} | {error, Reason}</name>
+ <name since="">install_release(Vsn, [Opt]) -> {ok, OtherVsn, Descr} | {continue_after_restart, OtherVsn, Descr} | {error, Reason}</name>
<fsummary>Installs a release in the system.</fsummary>
<type>
<v>Vsn = OtherVsn = string()</v>
@@ -383,7 +383,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
</func>
<func>
- <name>make_permanent(Vsn) -> ok | {error, Reason}</name>
+ <name since="">make_permanent(Vsn) -> ok | {error, Reason}</name>
<fsummary>Makes the specified release version permanent.</fsummary>
<type>
<v>Vsn = string()</v>
@@ -396,7 +396,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
</func>
<func>
- <name>remove_release(Vsn) -> ok | {error, Reason}</name>
+ <name since="">remove_release(Vsn) -> ok | {error, Reason}</name>
<fsummary>Removes a release from the system.</fsummary>
<type>
<v>Vsn = string()</v>
@@ -410,7 +410,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
</func>
<func>
- <name>reboot_old_release(Vsn) -> ok | {error, Reason}</name>
+ <name since="">reboot_old_release(Vsn) -> ok | {error, Reason}</name>
<fsummary>Reboots the system from an old release.</fsummary>
<type>
<v>Vsn = string()</v>
@@ -425,7 +425,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
</func>
<func>
- <name>set_removed(Vsn) -> ok | {error, Reason}</name>
+ <name since="">set_removed(Vsn) -> ok | {error, Reason}</name>
<fsummary>Marks a release as removed.</fsummary>
<type>
<v>Vsn = string()</v>
@@ -440,7 +440,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
</func>
<func>
- <name>set_unpacked(RelFile, AppDirs) -> {ok, Vsn} | {error, Reason}</name>
+ <name since="">set_unpacked(RelFile, AppDirs) -> {ok, Vsn} | {error, Reason}</name>
<fsummary>Marks a release as unpacked.</fsummary>
<type>
<v>RelFile = string()</v>
@@ -466,7 +466,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
</func>
<func>
- <name>unpack_release(Name) -> {ok, Vsn} | {error, Reason}</name>
+ <name since="">unpack_release(Name) -> {ok, Vsn} | {error, Reason}</name>
<fsummary>Unpacks a release package.</fsummary>
<type>
<v>Name = Vsn = string()</v>
@@ -482,7 +482,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
</func>
<func>
- <name>which_releases() -> [{Name, Vsn, Apps, Status}]</name>
+ <name since="">which_releases() -> [{Name, Vsn, Apps, Status}]</name>
<fsummary>Returns all known releases.</fsummary>
<type>
<v>Name = Vsn = string()</v>
@@ -495,7 +495,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
</func>
<func>
- <name>which_releases(Status) -> [{Name, Vsn, Apps, Status}]</name>
+ <name since="OTP R15B">which_releases(Status) -> [{Name, Vsn, Apps, Status}]</name>
<fsummary>Returns all known releases of a specific status.</fsummary>
<type>
<v>Name = Vsn = string()</v>
@@ -537,7 +537,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
<funcs>
<func>
- <name>upgrade_app(App, Dir) -> {ok, Unpurged} | restart_emulator | {error, Reason}</name>
+ <name since="">upgrade_app(App, Dir) -> {ok, Unpurged} | restart_emulator | {error, Reason}</name>
<fsummary>Upgrades to a new application version.</fsummary>
<type>
<v>App = atom()</v>
@@ -586,8 +586,8 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
</func>
<func>
- <name>downgrade_app(App, Dir) -></name>
- <name>downgrade_app(App, OldVsn, Dir) -> {ok, Unpurged} | restart_emulator | {error, Reason}</name>
+ <name since="">downgrade_app(App, Dir) -></name>
+ <name since="">downgrade_app(App, OldVsn, Dir) -> {ok, Unpurged} | restart_emulator | {error, Reason}</name>
<fsummary>Downgrades to a previous application version.</fsummary>
<type>
<v>App = atom()</v>
@@ -633,7 +633,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
</func>
<func>
- <name>upgrade_script(App, Dir) -> {ok, NewVsn, Script}</name>
+ <name since="">upgrade_script(App, Dir) -> {ok, NewVsn, Script}</name>
<fsummary>Finds an application upgrade script.</fsummary>
<type>
<v>App = atom()</v>
@@ -671,7 +671,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
</func>
<func>
- <name>downgrade_script(App, OldVsn, Dir) -> {ok, Script}</name>
+ <name since="">downgrade_script(App, OldVsn, Dir) -> {ok, Script}</name>
<fsummary>Finds an application downgrade script.</fsummary>
<type>
<v>App = atom()</v>
@@ -710,7 +710,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
</func>
<func>
- <name>eval_appup_script(App, ToVsn, ToDir, Script) -> {ok, Unpurged} | restart_emulator | {error, Reason}</name>
+ <name since="">eval_appup_script(App, ToVsn, ToDir, Script) -> {ok, Unpurged} | restart_emulator | {error, Reason}</name>
<fsummary>Evaluates an application upgrade or downgrade script.</fsummary>
<type>
<v>App = atom()</v>
diff --git a/lib/sasl/doc/src/systools.xml b/lib/sasl/doc/src/systools.xml
index 4842c732b1..6facb8ddae 100644
--- a/lib/sasl/doc/src/systools.xml
+++ b/lib/sasl/doc/src/systools.xml
@@ -30,7 +30,7 @@
<date></date>
<rev></rev>
</header>
- <module>systools</module>
+ <module since="">systools</module>
<modulesummary>A Set of Release Handling Tools</modulesummary>
<description>
<p>This module contains functions to generate boot scripts
@@ -40,8 +40,8 @@
<funcs>
<func>
- <name>make_relup(Name, UpFrom, DownTo) -> Result</name>
- <name>make_relup(Name, UpFrom, DownTo, [Opt]) -> Result</name>
+ <name since="">make_relup(Name, UpFrom, DownTo) -> Result</name>
+ <name since="">make_relup(Name, UpFrom, DownTo, [Opt]) -> Result</name>
<fsummary>Generates a release upgrade file <c>relup</c>.</fsummary>
<type>
<v>Name = string()</v>
@@ -136,8 +136,8 @@
</func>
<func>
- <name>make_script(Name) -> Result</name>
- <name>make_script(Name, [Opt]) -> Result</name>
+ <name since="">make_script(Name) -> Result</name>
+ <name since="">make_script(Name, [Opt]) -> Result</name>
<fsummary>Generates a boot script <c>.script/.boot</c>.</fsummary>
<type>
<v>Name = string()</v>
@@ -263,8 +263,8 @@
</func>
<func>
- <name>make_tar(Name) -> Result</name>
- <name>make_tar(Name, [Opt]) -> Result</name>
+ <name since="">make_tar(Name) -> Result</name>
+ <name since="">make_tar(Name, [Opt]) -> Result</name>
<fsummary>Creates a release package.</fsummary>
<type>
<v>Name = string()</v>
@@ -369,7 +369,7 @@ myapp-1/ebin/myapp.app
</func>
<func>
- <name>script2boot(File) -> ok | error</name>
+ <name since="">script2boot(File) -> ok | error</name>
<fsummary>Generates a binary version of a boot script.</fsummary>
<type>
<v>File = string()</v>
diff --git a/lib/sasl/src/rb.erl b/lib/sasl/src/rb.erl
index 28829132a1..bef4268d3a 100644
--- a/lib/sasl/src/rb.erl
+++ b/lib/sasl/src/rb.erl
@@ -890,7 +890,7 @@ read_rep(Fd, FilePosition, Device, Abort, Log) ->
handle_bad_form(Date, Msg, Device, Abort, Log) ->
io:format("rb: ERROR! A report on bad form was encountered. " ++
- "It can not be printed to the log.~n~n"),
+ "It cannot be printed to the log.~n~n"),
io:format("Details:~n~p ~tp~n~n", [Date,Msg]),
case {Abort,Device,open_log_file(Log)} of
{true,standard_io,standard_io} ->
diff --git a/lib/sasl/src/release_handler.erl b/lib/sasl/src/release_handler.erl
index 7570b74c1a..48feac1a21 100644
--- a/lib/sasl/src/release_handler.erl
+++ b/lib/sasl/src/release_handler.erl
@@ -1120,7 +1120,7 @@ new_emulator_make_hybrid_config(CurrentVsn,ToVsn,TmpVsn,RelDir,Masters) ->
{ok,[FC]} ->
FC;
{error,Error1} ->
- io:format("Warning: ~w can not read ~tp: ~tp~n",
+ io:format("Warning: ~w cannot read ~tp: ~tp~n",
[?MODULE,FromFile,Error1]),
[]
end,
@@ -1130,7 +1130,7 @@ new_emulator_make_hybrid_config(CurrentVsn,ToVsn,TmpVsn,RelDir,Masters) ->
{ok,[ToConfig]} ->
[lists:keyfind(App,1,ToConfig) || App <- [kernel,stdlib,sasl]];
{error,Error2} ->
- io:format("Warning: ~w can not read ~tp: ~tp~n",
+ io:format("Warning: ~w cannot read ~tp: ~tp~n",
[?MODULE,ToFile,Error2]),
[false,false,false]
end,
diff --git a/lib/sasl/src/release_handler_1.erl b/lib/sasl/src/release_handler_1.erl
index ca97515299..bf18691687 100644
--- a/lib/sasl/src/release_handler_1.erl
+++ b/lib/sasl/src/release_handler_1.erl
@@ -147,7 +147,7 @@ split_instructions([], Before) ->
%% If PrePurgeMethod == soft_purge, the function will succeed
%% only if there is no process running old code of any of the
%% modules. Else it will throw {error,Mod}, where Mod is the
-%% first module found that can not be soft_purged.
+%% first module found that cannot be soft_purged.
%%
%% If PrePurgeMethod == brutal_purge, the function will
%% always succeed and return a list of all modules that are
diff --git a/lib/sasl/src/systools.erl b/lib/sasl/src/systools.erl
index dd1a58c3c1..34eca6679f 100644
--- a/lib/sasl/src/systools.erl
+++ b/lib/sasl/src/systools.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All 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,7 +73,7 @@ make_tar(RelName, Opt) ->
script2boot(File) ->
case systools_lib:file_term2binary(File ++ ".script", File ++ ".boot") of
{error,Error} ->
- io:format(systools_make:format_error(Error)),
+ io:format("~ts", [systools_make:format_error(Error)]),
error;
_ ->
ok
@@ -84,7 +84,7 @@ script2boot(File, Output0, _Opt) ->
Output = Output0++".boot",
case systools_lib:file_term2binary(Input, Output) of
{error,Error} ->
- io:format(systools_make:format_error(Error)),
+ io:format("~ts", [systools_make:format_error(Error)]),
error;
_ ->
ok
diff --git a/lib/sasl/src/systools_make.erl b/lib/sasl/src/systools_make.erl
index f085246924..c2c91fd667 100644
--- a/lib/sasl/src/systools_make.erl
+++ b/lib/sasl/src/systools_make.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -1562,11 +1562,11 @@ mandatory_modules() ->
preloaded() ->
%% Sorted
- [atomics, counters, erl_prim_loader,erl_tracer,erlang,
+ [atomics,counters,erl_init,erl_prim_loader,erl_tracer,erlang,
erts_code_purger,erts_dirty_process_signal_handler,
erts_internal,erts_literal_area_collector,
- init,otp_ring0,persistent_term,prim_buffer,prim_eval,prim_file,
- prim_inet,prim_zip,zlib].
+ init,net,persistent_term,prim_buffer,prim_eval,prim_file,
+ prim_inet,prim_zip,socket,zlib].
%%______________________________________________________________________
%% Kernel processes; processes that are specially treated by the init
diff --git a/lib/sasl/src/systools_relup.erl b/lib/sasl/src/systools_relup.erl
index e836d57670..5f1176ec69 100644
--- a/lib/sasl/src/systools_relup.erl
+++ b/lib/sasl/src/systools_relup.erl
@@ -587,7 +587,7 @@ default(warnings_as_errors) -> false.
print_error({error, Mod, Error}) ->
S = apply(Mod, format_error, [Error]),
- io:format(S, []);
+ io:format("~ts", [S]);
print_error(Other) ->
io:format("Error: ~tp~n", [Other]).
diff --git a/lib/sasl/test/installer.erl b/lib/sasl/test/installer.erl
index e38d0cfa7b..5429008a5f 100644
--- a/lib/sasl/test/installer.erl
+++ b/lib/sasl/test/installer.erl
@@ -905,7 +905,7 @@ start_client(TestNode,Client,Sname) ->
wait_started(TestNode,Node)
after 30000 ->
?print([{start_client,failed,Node},net_adm:ping(Node)]),
- ?fail({"can not start", Node})
+ ?fail({"cannot start", Node})
end.
start_client_unix(TestNode,Sname,Node) ->
diff --git a/lib/sasl/test/rb_SUITE.erl b/lib/sasl/test/rb_SUITE.erl
index 2b6e452d14..e5ca1775d5 100644
--- a/lib/sasl/test/rb_SUITE.erl
+++ b/lib/sasl/test/rb_SUITE.erl
@@ -423,7 +423,7 @@ start_stop_log(Config) ->
StdioResult2 = capture(fun() -> rb:log_list() end),
{ok,<<>>} = file:read_file(OutFile),
- %% Test that standard_io is used if log file can not be opened
+ %% Test that standard_io is used if log file cannot be opened
ok = rb:start_log(filename:join(nonexistingdir,"newfile.txt")),
StdioResult = capture(fun() -> rb:show(1) end),
{ok,<<>>} = file:read_file(OutFile),
diff --git a/lib/sasl/test/systools_SUITE.erl b/lib/sasl/test/systools_SUITE.erl
index 1827974866..6c913850b9 100644
--- a/lib/sasl/test/systools_SUITE.erl
+++ b/lib/sasl/test/systools_SUITE.erl
@@ -1654,7 +1654,7 @@ abnormal_relup(Config) when is_list(Config) ->
ok.
-%% make_relup: Check relup can not be created is sasl is not in rel file.
+%% make_relup: Check relup cannot be created is sasl is not in rel file.
no_sasl_relup(Config) when is_list(Config) ->
{ok, OldDir} = file:get_cwd(),
{Dir1,Name1} = create_script(latest1_no_sasl,Config),
diff --git a/lib/sasl/test/test_lib.hrl b/lib/sasl/test/test_lib.hrl
index f5210d4f27..7867d3da39 100644
--- a/lib/sasl/test/test_lib.hrl
+++ b/lib/sasl/test/test_lib.hrl
@@ -1,3 +1,3 @@
-define(ertsvsn,"4.4").
--define(kernelvsn,"5.3").
--define(stdlibvsn,"3.4").
+-define(kernelvsn,"6.0").
+-define(stdlibvsn,"3.5").
diff --git a/lib/snmp/doc/src/snmp.xml b/lib/snmp/doc/src/snmp.xml
index 480ed2e825..d20f1a8d06 100644
--- a/lib/snmp/doc/src/snmp.xml
+++ b/lib/snmp/doc/src/snmp.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmp.xml</file>
</header>
- <module>snmp</module>
+ <module since="">snmp</module>
<modulesummary>Interface functions to the SNMP toolkit</modulesummary>
<description>
<p>The module <c>snmp</c> contains interface functions to the
@@ -56,7 +56,7 @@
<funcs>
<func>
- <name>config() -> ok | {error, Reason}</name>
+ <name since="">config() -> ok | {error, Reason}</name>
<fsummary>Configure with a simple interactive tool</fsummary>
<desc>
<p>A simple interactive configuration tool. Simple
@@ -78,8 +78,8 @@
</func>
<func>
- <name>start() -> ok | {error, Reason}</name>
- <name>start(Type) -> ok | {error, Reason}</name>
+ <name since="">start() -> ok | {error, Reason}</name>
+ <name since="">start(Type) -> ok | {error, Reason}</name>
<fsummary>Start the SNMP application</fsummary>
<type>
<v>Type = start_type()</v>
@@ -93,8 +93,8 @@
</func>
<func>
- <name>start_agent() -> ok | {error, Reason}</name>
- <name>start_agent(Type) -> ok | {error, Reason}</name>
+ <name since="">start_agent() -> ok | {error, Reason}</name>
+ <name since="">start_agent(Type) -> ok | {error, Reason}</name>
<fsummary>Start the agent part of the SNMP application</fsummary>
<type>
<v>Type = start_type()</v>
@@ -117,8 +117,8 @@
</func>
<func>
- <name>start_manager() -> ok | {error, Reason}</name>
- <name>start_manager(Type) -> ok | {error, Reason}</name>
+ <name since="">start_manager() -> ok | {error, Reason}</name>
+ <name since="">start_manager(Type) -> ok | {error, Reason}</name>
<fsummary>Start the manager part of the SNMP application</fsummary>
<type>
<v>Type = start_type()</v>
@@ -141,7 +141,7 @@
</func>
<func>
- <name>date_and_time() -> DateAndTime</name>
+ <name since="">date_and_time() -> DateAndTime</name>
<fsummary>Return the current date and time as an OCTET STRING</fsummary>
<type>
<v>DateAndTime = [int()]</v>
@@ -155,7 +155,7 @@
</func>
<func>
- <name>date_and_time_to_universal_time_dst(DateAndTime) -> [utc()]</name>
+ <name since="">date_and_time_to_universal_time_dst(DateAndTime) -> [utc()]</name>
<fsummary>Convert a DateAndTime value to a list of possible utc()</fsummary>
<type>
<v>DateAndTime = [int()]</v>
@@ -171,8 +171,8 @@
</func>
<func>
- <name>date_and_time_to_string(DateAndTime) -> string()</name>
- <name>date_and_time_to_string(DateAndTime, Validate) -> string()</name>
+ <name since="">date_and_time_to_string(DateAndTime) -> string()</name>
+ <name since="">date_and_time_to_string(DateAndTime, Validate) -> string()</name>
<fsummary>Convert a DateAndTime value to a string</fsummary>
<type>
<v>DateAndTime = [int()]</v>
@@ -194,7 +194,7 @@
</func>
<func>
- <name>date_and_time_to_string2(DateAndTime) -> string()</name>
+ <name since="">date_and_time_to_string2(DateAndTime) -> string()</name>
<fsummary>Convert a DateAndTime value to a string</fsummary>
<type>
<v>DateAndTime = [int()]</v>
@@ -210,7 +210,7 @@
</func>
<func>
- <name>local_time_to_date_and_time_dst(Local) -> [DateAndTime]</name>
+ <name since="">local_time_to_date_and_time_dst(Local) -> [DateAndTime]</name>
<fsummary>Convert a Local time value to a list of possible DateAndTime(s)</fsummary>
<type>
<v>Local = {{Y,Mo,D},{H,M,S}}</v>
@@ -226,7 +226,7 @@
</func>
<func>
- <name>universal_time_to_date_and_time(UTC) -> DateAndTime</name>
+ <name since="">universal_time_to_date_and_time(UTC) -> DateAndTime</name>
<fsummary>Convert a UTC value to DateAndTime</fsummary>
<type>
<v>UTC = {{Y,Mo,D},{H,M,S}}</v>
@@ -241,8 +241,8 @@
</func>
<func>
- <name>validate_date_and_time(DateAndTime) -> bool()</name>
- <name>validate_date_and_time(DateAndTime, Validate) -> bool()</name>
+ <name since="">validate_date_and_time(DateAndTime) -> bool()</name>
+ <name since="">validate_date_and_time(DateAndTime, Validate) -> bool()</name>
<fsummary>Check if a DateAndTime value is correct</fsummary>
<type>
<v>DateAndTime = term()</v>
@@ -279,7 +279,7 @@
</func>
<func>
- <name>passwd2localized_key(Alg, Passwd, EngineID) -> Key</name>
+ <name since="">passwd2localized_key(Alg, Passwd, EngineID) -> Key</name>
<fsummary>Generates an localized key</fsummary>
<type>
<v>Alg = algorithm()</v>
@@ -298,7 +298,7 @@
</func>
<func>
- <name>octet_string_to_bits(S) -> Val</name>
+ <name since="">octet_string_to_bits(S) -> Val</name>
<fsummary>Convert an OCTET-STRING to BITS</fsummary>
<type>
<v>Val = bits()</v>
@@ -312,7 +312,7 @@
</func>
<func>
- <name>bits_to_octet_string(B) -> Val</name>
+ <name since="">bits_to_octet_string(B) -> Val</name>
<fsummary>Convert an OCTET-STRING to BITS</fsummary>
<type>
<v>Val = octet_string()</v>
@@ -326,7 +326,7 @@
</func>
<func>
- <name>read_mib(FileName) -> {ok, mib()} | {error, Reason}</name>
+ <name since="">read_mib(FileName) -> {ok, mib()} | {error, Reason}</name>
<fsummary></fsummary>
<type>
<v>FileName = string()</v>
@@ -341,10 +341,10 @@
</func>
<func>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start, Block | Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start, Stop, Block) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start, Block | Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R16B03">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start, Stop, Block) -> ok | {ok, Cnt} | {error, Reason}</name>
<fsummary>Convert an Audit Trail Log to text format</fsummary>
<type>
<v>LogDir = string()</v>
@@ -412,10 +412,10 @@
</func>
<func>
- <name>log_to_io(LogDir, Mibs, LogName, LogFile) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, LogName, LogFile, Start, Block | Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, LogName, LogFile, Start, Stop, Block) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, LogFile) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, LogFile, Start, Block | Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R16B03">log_to_io(LogDir, Mibs, LogName, LogFile, Start, Stop, Block) -> ok | {ok, Cnt} | {error, Reason}</name>
<fsummary>Convert an Audit Trail Log to text format</fsummary>
<type>
<v>LogDir = string()</v>
@@ -440,7 +440,7 @@
</func>
<func>
- <name>change_log_size(LogName, NewSize) -> ok | {error, Reason}</name>
+ <name since="">change_log_size(LogName, NewSize) -> ok | {error, Reason}</name>
<fsummary>Change the size of the Audit Trail Log</fsummary>
<type>
<v>LogName = string()</v>
@@ -463,8 +463,8 @@
</func>
<func>
- <name>print_version_info() -> void()</name>
- <name>print_version_info(Prefix) -> void()</name>
+ <name since="">print_version_info() -> void()</name>
+ <name since="">print_version_info(Prefix) -> void()</name>
<fsummary>Formatted print of result of the versions functions</fsummary>
<type>
<v>Prefix = string() | integer()</v>
@@ -484,8 +484,8 @@
</func>
<func>
- <name>versions1() -> {ok, Info} | {error, Reason}</name>
- <name>versions2() -> {ok, Info} | {error, Reason}</name>
+ <name since="">versions1() -> {ok, Info} | {error, Reason}</name>
+ <name since="">versions2() -> {ok, Info} | {error, Reason}</name>
<fsummary>Retrieve various system and application info</fsummary>
<type>
<v>Info = [info()]</v>
@@ -504,8 +504,8 @@
</func>
<func>
- <name>print_versions(VersionInfo) -> void()</name>
- <name>print_versions(Prefix, VersionInfo) -> void()</name>
+ <name since="">print_versions(VersionInfo) -> void()</name>
+ <name since="">print_versions(Prefix, VersionInfo) -> void()</name>
<fsummary>Formatted print of result of the versions functions</fsummary>
<type>
<v>VersionInfo = [version_info()]</v>
@@ -527,7 +527,7 @@
</func>
<func>
- <name>enable_trace() -> void()</name>
+ <name since="">enable_trace() -> void()</name>
<fsummary>Starts a tracer</fsummary>
<!--
<type>
@@ -543,7 +543,7 @@
</func>
<func>
- <name>disable_trace() -> void()</name>
+ <name since="">disable_trace() -> void()</name>
<fsummary>Stop the tracer</fsummary>
<!--
<type>
@@ -558,7 +558,7 @@
</func>
<func>
- <name>set_trace(Targets) -> void()</name>
+ <name since="">set_trace(Targets) -> void()</name>
<fsummary>Set trace target</fsummary>
<type>
<v>Targets = target() | targets()</v>
@@ -582,7 +582,7 @@
</func>
<func>
- <name>reset_trace(Targets) -> void()</name>
+ <name since="">reset_trace(Targets) -> void()</name>
<fsummary>Reset trace target</fsummary>
<type>
<v>Targets = module() | modules()</v>
@@ -598,7 +598,7 @@
</func>
<func>
- <name>set_trace(Targets, Opts) -> void()</name>
+ <name since="">set_trace(Targets, Opts) -> void()</name>
<fsummary>Set trace target</fsummary>
<type>
<v>Targets = target() | targets()</v>
diff --git a/lib/snmp/doc/src/snmp_community_mib.xml b/lib/snmp/doc/src/snmp_community_mib.xml
index 61dea05950..9800fb6c00 100644
--- a/lib/snmp/doc/src/snmp_community_mib.xml
+++ b/lib/snmp/doc/src/snmp_community_mib.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmp_community_mib.xml</file>
</header>
- <module>snmp_community_mib</module>
+ <module since="">snmp_community_mib</module>
<modulesummary>Instrumentation Functions for SNMP-COMMUNITY-MIB</modulesummary>
<description>
<p>The module <c>snmp_community_mib</c> implements the instrumentation
@@ -45,7 +45,7 @@
<funcs>
<func>
- <name>configure(ConfDir) -> void()</name>
+ <name since="">configure(ConfDir) -> void()</name>
<fsummary>Configure the SNMP-COMMUNITY-MIB</fsummary>
<type>
<v>ConfDir = string()</v>
@@ -77,7 +77,7 @@
</func>
<func>
- <name>reconfigure(ConfDir) -> void()</name>
+ <name since="">reconfigure(ConfDir) -> void()</name>
<fsummary>Configure the SNMP-COMMUNITY-MIB</fsummary>
<type>
<v>ConfDir = string()</v>
@@ -108,8 +108,8 @@
</func>
<func>
- <name>add_community(Idx, CommName, SecName, CtxName, TransportTag) -> Ret</name>
- <name>add_community(Idx, CommName, SecName, EngineId, CtxName, TransportTag) -> Ret</name>
+ <name since="">add_community(Idx, CommName, SecName, CtxName, TransportTag) -> Ret</name>
+ <name since="OTP R14B03">add_community(Idx, CommName, SecName, EngineId, CtxName, TransportTag) -> Ret</name>
<fsummary>Added one community</fsummary>
<type>
<v>Idx = string()</v>
@@ -132,7 +132,7 @@
</func>
<func>
- <name>delete_community(Key) -> Ret</name>
+ <name since="">delete_community(Key) -> Ret</name>
<fsummary>Delete one community</fsummary>
<type>
<v>Key = term()</v>
diff --git a/lib/snmp/doc/src/snmp_framework_mib.xml b/lib/snmp/doc/src/snmp_framework_mib.xml
index 64e5df6ff5..d84327d4d5 100644
--- a/lib/snmp/doc/src/snmp_framework_mib.xml
+++ b/lib/snmp/doc/src/snmp_framework_mib.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmp_framework_mib.xml</file>
</header>
- <module>snmp_framework_mib</module>
+ <module since="">snmp_framework_mib</module>
<modulesummary>Instrumentation Functions for SNMP-FRAMEWORK-MIB</modulesummary>
<description>
<p>The module <c>snmp_framework_mib</c> implements instrumentation
@@ -44,7 +44,7 @@
</description>
<funcs>
<func>
- <name>configure(ConfDir) -> void()</name>
+ <name since="">configure(ConfDir) -> void()</name>
<fsummary>Configure the SNMP-FRAMEWORK-MIB</fsummary>
<type>
<v>ConfDir = string()</v>
@@ -75,7 +75,7 @@
</desc>
</func>
<func>
- <name>init() -> void()</name>
+ <name since="">init() -> void()</name>
<fsummary>Initialize the SNMP-FRAMEWORK-MIB</fsummary>
<desc>
<p>This function is called from the supervisor at system
@@ -88,7 +88,7 @@
</desc>
</func>
<func>
- <name>add_context(Ctx) -> Ret</name>
+ <name since="">add_context(Ctx) -> Ret</name>
<fsummary>Added one context</fsummary>
<type>
<v>Ctx = string()</v>
@@ -103,7 +103,7 @@
</desc>
</func>
<func>
- <name>delete_context(Key) -> Ret</name>
+ <name since="">delete_context(Key) -> Ret</name>
<fsummary>Delete one context</fsummary>
<type>
<v>Key = term()</v>
diff --git a/lib/snmp/doc/src/snmp_generic.xml b/lib/snmp/doc/src/snmp_generic.xml
index 44762dec59..6fb714907c 100644
--- a/lib/snmp/doc/src/snmp_generic.xml
+++ b/lib/snmp/doc/src/snmp_generic.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmp_generic.xml</file>
</header>
- <module>snmp_generic</module>
+ <module since="">snmp_generic</module>
<modulesummary>Generic Functions for Implementing SNMP Objects in a Database</modulesummary>
<description>
<marker id="description"></marker>
@@ -127,8 +127,8 @@ value() = term()
<funcs>
<func>
- <name>get_status_col(Name, Cols)</name>
- <name>get_status_col(NameDb, Cols) -> {ok, StatusVal} | false</name>
+ <name since="">get_status_col(Name, Cols)</name>
+ <name since="">get_status_col(NameDb, Cols) -> {ok, StatusVal} | false</name>
<fsummary>Get the value of the status column from <c>Cols</c></fsummary>
<type>
<v>Name = name()</v>
@@ -148,7 +148,7 @@ value() = term()
</func>
<func>
- <name>get_index_types(Name)</name>
+ <name since="">get_index_types(Name)</name>
<fsummary>Get the index types of <c>Name</c></fsummary>
<type>
<v>Name = name()</v>
@@ -163,7 +163,7 @@ value() = term()
</func>
<func>
- <name>get_table_info(Name, Item) -> table_info_result()</name>
+ <name since="OTP R15B01">get_table_info(Name, Item) -> table_info_result()</name>
<fsummary>Get table info item of MIB table <c>Name</c></fsummary>
<type>
<v>Name = name()</v>
@@ -187,8 +187,8 @@ value() = term()
</func>
<func>
- <name>table_func(Op1, NameDb)</name>
- <name>table_func(Op2, RowIndex, Cols, NameDb) -> Ret</name>
+ <name since="">table_func(Op1, NameDb)</name>
+ <name since="">table_func(Op2, RowIndex, Cols, NameDb) -> Ret</name>
<fsummary>Default instrumentation function for tables</fsummary>
<type>
<v>Op1 = new | delete </v>
@@ -232,7 +232,7 @@ value() = term()
</func>
<func>
- <name>table_get_elements(NameDb, RowIndex, Cols) -> Values</name>
+ <name since="">table_get_elements(NameDb, RowIndex, Cols) -> Values</name>
<fsummary>Get elements in a table row</fsummary>
<type>
<v>NameDb = name_db()</v>
@@ -249,7 +249,7 @@ value() = term()
</func>
<func>
- <name>table_next(NameDb, RestOid) -> RowIndex | endOfTable</name>
+ <name since="">table_next(NameDb, RestOid) -> RowIndex | endOfTable</name>
<fsummary>Find the next row in the table</fsummary>
<type>
<v>NameDb = name_db()</v>
@@ -265,7 +265,7 @@ value() = term()
</func>
<func>
- <name>table_row_exists(NameDb, RowIndex) -> bool()</name>
+ <name since="">table_row_exists(NameDb, RowIndex) -> bool()</name>
<fsummary>Check if a row in a table exists</fsummary>
<type>
<v>NameDb = name_db()</v>
@@ -279,7 +279,7 @@ value() = term()
</func>
<func>
- <name>table_set_elements(NameDb, RowIndex, Cols) -> bool()</name>
+ <name since="">table_set_elements(NameDb, RowIndex, Cols) -> bool()</name>
<fsummary>Set elements in a table row</fsummary>
<type>
<v>NameDb = name_db()</v>
@@ -300,8 +300,8 @@ value() = term()
</func>
<func>
- <name>variable_func(Op1, NameDb)</name>
- <name>variable_func(Op2, Val, NameDb) -> Ret</name>
+ <name since="">variable_func(Op1, NameDb)</name>
+ <name since="">variable_func(Op2, Val, NameDb) -> Ret</name>
<fsummary>Default instrumentation function for tables</fsummary>
<type>
<v>Op1 = new | delete | get</v>
@@ -325,7 +325,7 @@ value() = term()
</func>
<func>
- <name>variable_get(NameDb) -> {value, Value} | undefined</name>
+ <name since="">variable_get(NameDb) -> {value, Value} | undefined</name>
<fsummary>Get the value of a variable</fsummary>
<type>
<v>NameDb = name_db()</v>
@@ -339,7 +339,7 @@ value() = term()
</func>
<func>
- <name>variable_set(NameDb, NewVal) -> true | false</name>
+ <name since="">variable_set(NameDb, NewVal) -> true | false</name>
<fsummary>Set a value for a variable</fsummary>
<type>
<v>NameDb = name_db()</v>
diff --git a/lib/snmp/doc/src/snmp_index.xml b/lib/snmp/doc/src/snmp_index.xml
index 646e9661a3..1497f4cf67 100644
--- a/lib/snmp/doc/src/snmp_index.xml
+++ b/lib/snmp/doc/src/snmp_index.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmp_index.xml</file>
</header>
- <module>snmp_index</module>
+ <module since="">snmp_index</module>
<modulesummary>Abstract Data Type for SNMP Indexing</modulesummary>
<description>
<p>The module <c>snmp_index</c> implements an Abstract
@@ -159,7 +159,7 @@ get_next_pid(Oid, SnmpIndex) ->
</section>
<funcs>
<func>
- <name>delete(Index) -> true</name>
+ <name since="">delete(Index) -> true</name>
<fsummary>Delete an index table</fsummary>
<type>
<v>Index = NewIndex = index()</v>
@@ -173,7 +173,7 @@ get_next_pid(Oid, SnmpIndex) ->
</desc>
</func>
<func>
- <name>delete(Index, Key) -> NewIndex</name>
+ <name since="">delete(Index, Key) -> NewIndex</name>
<fsummary>Delete an item from the index</fsummary>
<type>
<v>Index = NewIndex = index()</v>
@@ -185,7 +185,7 @@ get_next_pid(Oid, SnmpIndex) ->
</desc>
</func>
<func>
- <name>get(Index, KeyOid) -> {ok, {KeyOid, Value}} | undefined</name>
+ <name since="">get(Index, KeyOid) -> {ok, {KeyOid, Value}} | undefined</name>
<fsummary>Get the item with <c>KeyOid</c></fsummary>
<type>
<v>Index = index()</v>
@@ -198,7 +198,7 @@ get_next_pid(Oid, SnmpIndex) ->
</desc>
</func>
<func>
- <name>get_last(Index) -> {ok, {KeyOid, Value}} | undefined</name>
+ <name since="">get_last(Index) -> {ok, {KeyOid, Value}} | undefined</name>
<fsummary>Get the last item in the index structure</fsummary>
<type>
<v>Index = index()</v>
@@ -210,7 +210,7 @@ get_next_pid(Oid, SnmpIndex) ->
</desc>
</func>
<func>
- <name>get_next(Index, KeyOid) -> {ok, {NextKeyOid, Value}} | undefined</name>
+ <name since="">get_next(Index, KeyOid) -> {ok, {NextKeyOid, Value}} | undefined</name>
<fsummary>Get the next item</fsummary>
<type>
<v>Index = index()</v>
@@ -224,7 +224,7 @@ get_next_pid(Oid, SnmpIndex) ->
</desc>
</func>
<func>
- <name>insert(Index, Key, Value) -> NewIndex</name>
+ <name since="">insert(Index, Key, Value) -> NewIndex</name>
<fsummary>Insert an item into the index</fsummary>
<type>
<v>Index = NewIndex = index()</v>
@@ -238,7 +238,7 @@ get_next_pid(Oid, SnmpIndex) ->
</desc>
</func>
<func>
- <name>key_to_oid(Index, Key) -> KeyOid</name>
+ <name since="">key_to_oid(Index, Key) -> KeyOid</name>
<fsummary>Convert a key to an OBJECT IDENTIFIER</fsummary>
<type>
<v>Index = index()</v>
@@ -250,7 +250,7 @@ get_next_pid(Oid, SnmpIndex) ->
</desc>
</func>
<func>
- <name>new(KeyTypes) -> Index</name>
+ <name since="">new(KeyTypes) -> Index</name>
<fsummary>Create a new snmp index structure</fsummary>
<type>
<v>KeyTypes = key_types()</v>
diff --git a/lib/snmp/doc/src/snmp_notification_mib.xml b/lib/snmp/doc/src/snmp_notification_mib.xml
index d2e288ec15..9395edf155 100644
--- a/lib/snmp/doc/src/snmp_notification_mib.xml
+++ b/lib/snmp/doc/src/snmp_notification_mib.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmp_notification_mib.xml</file>
</header>
- <module>snmp_notification_mib</module>
+ <module since="">snmp_notification_mib</module>
<modulesummary>Instrumentation Functions for SNMP-NOTIFICATION-MIB</modulesummary>
<description>
<p>The module <c>snmp_notification_mib</c> implements the
@@ -43,7 +43,7 @@
</description>
<funcs>
<func>
- <name>configure(ConfDir) -> void()</name>
+ <name since="">configure(ConfDir) -> void()</name>
<fsummary>Configure the SNMP-NOTIFICATION-MIB</fsummary>
<type>
<v>ConfDir = string()</v>
@@ -70,7 +70,7 @@
</desc>
</func>
<func>
- <name>reconfigure(ConfDir) -> void()</name>
+ <name since="">reconfigure(ConfDir) -> void()</name>
<fsummary>Configure the SNMP-NOTIFICATION-MIB</fsummary>
<type>
<v>ConfDir = string()</v>
@@ -98,7 +98,7 @@
</desc>
</func>
<func>
- <name>add_notify(Name, Tag, Type) -> Ret</name>
+ <name since="">add_notify(Name, Tag, Type) -> Ret</name>
<fsummary>Added one notify definition</fsummary>
<type>
<v>Name = string()</v>
@@ -115,7 +115,7 @@
</desc>
</func>
<func>
- <name>delete_notify(Key) -> Ret</name>
+ <name since="">delete_notify(Key) -> Ret</name>
<fsummary>Delete one notify definition</fsummary>
<type>
<v>Key = term()</v>
diff --git a/lib/snmp/doc/src/snmp_pdus.xml b/lib/snmp/doc/src/snmp_pdus.xml
index 1d086e6f48..f403b6edf4 100644
--- a/lib/snmp/doc/src/snmp_pdus.xml
+++ b/lib/snmp/doc/src/snmp_pdus.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmp_pdus.xml</file>
</header>
- <module>snmp_pdus</module>
+ <module since="">snmp_pdus</module>
<modulesummary>Encode and Decode Functions for SNMP PDUs</modulesummary>
<description>
<p>RFC1157, RFC1905 and/or RFC2272 should be studied carefully
@@ -55,7 +55,7 @@
</description>
<funcs>
<func>
- <name>dec_message([byte()]) -> Message</name>
+ <name since="">dec_message([byte()]) -> Message</name>
<fsummary>Decode an SNMP Message</fsummary>
<type>
<v>Message = #message</v>
@@ -71,7 +71,7 @@
</desc>
</func>
<func>
- <name>dec_message_only([byte()]) -> Message</name>
+ <name since="">dec_message_only([byte()]) -> Message</name>
<fsummary>Decode an SNMP Message, but not the data part</fsummary>
<type>
<v>Message = #message</v>
@@ -84,7 +84,7 @@
</desc>
</func>
<func>
- <name>dec_pdu([byte()]) -> Pdu</name>
+ <name since="">dec_pdu([byte()]) -> Pdu</name>
<fsummary>Decode an SNMP Pdu</fsummary>
<type>
<v>Pdu = #pdu</v>
@@ -94,7 +94,7 @@
</desc>
</func>
<func>
- <name>dec_scoped_pdu([byte()]) -> ScopedPdu</name>
+ <name since="">dec_scoped_pdu([byte()]) -> ScopedPdu</name>
<fsummary>Decode an SNMP ScopedPdu</fsummary>
<type>
<v>ScopedPdu = #scoped_pdu</v>
@@ -104,7 +104,7 @@
</desc>
</func>
<func>
- <name>dec_scoped_pdu_data([byte()]) -> ScopedPduData</name>
+ <name since="">dec_scoped_pdu_data([byte()]) -> ScopedPduData</name>
<fsummary>Decode an SNMP ScopedPduData</fsummary>
<type>
<v>ScopedPduData = #scoped_pdu | EncryptedPDU</v>
@@ -116,7 +116,7 @@
</desc>
</func>
<func>
- <name>dec_usm_security_parameters([byte()]) -> UsmSecParams</name>
+ <name since="">dec_usm_security_parameters([byte()]) -> UsmSecParams</name>
<fsummary>Decode SNMP UsmSecurityParameters</fsummary>
<type>
<v>UsmSecParams = #usmSecurityParameters</v>
@@ -126,7 +126,7 @@
</desc>
</func>
<func>
- <name>enc_encrypted_scoped_pdu(EncryptedScopedPdu) -> [byte()]</name>
+ <name since="">enc_encrypted_scoped_pdu(EncryptedScopedPdu) -> [byte()]</name>
<fsummary>Encode an encrypted SNMP scopedPDU</fsummary>
<type>
<v>EncryptedScopedPdu = [byte()]</v>
@@ -142,7 +142,7 @@
</desc>
</func>
<func>
- <name>enc_message(Message) -> [byte()]</name>
+ <name since="">enc_message(Message) -> [byte()]</name>
<fsummary>Encode an SNMP Message</fsummary>
<type>
<v>Message = #message</v>
@@ -152,7 +152,7 @@
</desc>
</func>
<func>
- <name>enc_message_only(Message) -> [byte()]</name>
+ <name since="">enc_message_only(Message) -> [byte()]</name>
<fsummary>Encode an SNMP Message, but not the data part</fsummary>
<type>
<v>Message = #message</v>
@@ -166,7 +166,7 @@
</desc>
</func>
<func>
- <name>enc_pdu(Pd) -> [byte()]</name>
+ <name since="">enc_pdu(Pd) -> [byte()]</name>
<fsummary>Encode an SNMP Pdu</fsummary>
<type>
<v>Pdu = #pdu</v>
@@ -176,7 +176,7 @@
</desc>
</func>
<func>
- <name>enc_scoped_pdu(ScopedPdu) -> [byte()]</name>
+ <name since="">enc_scoped_pdu(ScopedPdu) -> [byte()]</name>
<fsummary>Encode an SNMP scopedPDU</fsummary>
<type>
<v>ScopedPdu = #scoped_pdu</v>
@@ -190,7 +190,7 @@
</desc>
</func>
<func>
- <name>enc_usm_security_parameters(UsmSecParams) -> [byte()]</name>
+ <name since="">enc_usm_security_parameters(UsmSecParams) -> [byte()]</name>
<fsummary>Encode SNMP UsmSecurityParameters</fsummary>
<type>
<v>UsmSecParams = #usmSecurityParameters</v>
diff --git a/lib/snmp/doc/src/snmp_standard_mib.xml b/lib/snmp/doc/src/snmp_standard_mib.xml
index 35efbba483..eb4e2fd097 100644
--- a/lib/snmp/doc/src/snmp_standard_mib.xml
+++ b/lib/snmp/doc/src/snmp_standard_mib.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmp_standard_mib.xml</file>
</header>
- <module>snmp_standard_mib</module>
+ <module since="">snmp_standard_mib</module>
<modulesummary>Instrumentation Functions for STANDARD-MIB and SNMPv2-MIB</modulesummary>
<description>
<p>The module <c>snmp_standard_mib</c> implements the instrumentation functions for the
@@ -42,7 +42,7 @@
</description>
<funcs>
<func>
- <name>configure(ConfDir) -> void()</name>
+ <name since="">configure(ConfDir) -> void()</name>
<fsummary>Configure the STANDARD-MIB and SNMPv2-MIB</fsummary>
<type>
<v>ConfDir = string()</v>
@@ -71,8 +71,8 @@
</desc>
</func>
<func>
- <name>inc(Name) -> void()</name>
- <name>inc(Name, N) -> void()</name>
+ <name since="">inc(Name) -> void()</name>
+ <name since="">inc(Name, N) -> void()</name>
<fsummary>Increment a variable in the MIB</fsummary>
<type>
<v>Name = atom()</v>
@@ -84,7 +84,7 @@
</desc>
</func>
<func>
- <name>reconfigure(ConfDir) -> void()</name>
+ <name since="">reconfigure(ConfDir) -> void()</name>
<fsummary>Configure the STANDARD-MIB and SNMPv2-MIB</fsummary>
<type>
<v>ConfDir = string()</v>
@@ -114,14 +114,14 @@
</desc>
</func>
<func>
- <name>reset() -> void()</name>
+ <name since="">reset() -> void()</name>
<fsummary>Reset all <c>snmp</c>counters to 0</fsummary>
<desc>
<p>Resets all <c>snmp</c> counters to 0.</p>
</desc>
</func>
<func>
- <name>sys_up_time() -> Time</name>
+ <name since="">sys_up_time() -> Time</name>
<fsummary>Get the system up time</fsummary>
<type>
<v>Time = int()</v>
diff --git a/lib/snmp/doc/src/snmp_target_mib.xml b/lib/snmp/doc/src/snmp_target_mib.xml
index c3bcd3b4e3..c46edb810d 100644
--- a/lib/snmp/doc/src/snmp_target_mib.xml
+++ b/lib/snmp/doc/src/snmp_target_mib.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmp_target_mib.xml</file>
</header>
- <module>snmp_target_mib</module>
+ <module since="">snmp_target_mib</module>
<modulesummary>Instrumentation Functions for SNMP-TARGET-MIB</modulesummary>
<description>
<p>The module <c>snmp_target_mib</c> implements the instrumentation
@@ -57,7 +57,7 @@
<funcs>
<func>
- <name>configure(ConfDir) -> void()</name>
+ <name since="">configure(ConfDir) -> void()</name>
<fsummary>Configure the SNMP-TARGET-MIB</fsummary>
<type>
<v>ConfDir = string()</v>
@@ -85,7 +85,7 @@
</func>
<func>
- <name>reconfigure(ConfDir) -> void()</name>
+ <name since="">reconfigure(ConfDir) -> void()</name>
<fsummary>Configure the SNMP-TARGET-MIB</fsummary>
<type>
<v>ConfDir = string()</v>
@@ -113,7 +113,7 @@
</func>
<func>
- <name>set_target_engine_id(TargetAddrName, EngineId) -> boolean()</name>
+ <name since="">set_target_engine_id(TargetAddrName, EngineId) -> boolean()</name>
<fsummary>Set the engine id for a targetAddr row.</fsummary>
<type>
<v>TargetAddrName = string()</v>
@@ -130,7 +130,7 @@
</func>
<func>
- <name>add_addr(Name, Domain, Addr, Timeout, Retry, TagList, Params, EngineId, TMask, MMS) -> Ret</name>
+ <name since="">add_addr(Name, Domain, Addr, Timeout, Retry, TagList, Params, EngineId, TMask, MMS) -> Ret</name>
<fsummary>Add one target address definition</fsummary>
<type>
<v>Name = string()</v>
@@ -156,7 +156,7 @@
</func>
<func>
- <name>delete_addr(Key) -> Ret</name>
+ <name since="">delete_addr(Key) -> Ret</name>
<fsummary>Delete one target address definition</fsummary>
<type>
<v>Key = term()</v>
@@ -171,7 +171,7 @@
</func>
<func>
- <name>add_params(Name, MPModel, SecModel, SecName, SecLevel) -> Ret</name>
+ <name since="">add_params(Name, MPModel, SecModel, SecName, SecLevel) -> Ret</name>
<fsummary>Add one target parameter definition</fsummary>
<type>
<v>Name = string()</v>
@@ -191,7 +191,7 @@
</desc>
</func>
<func>
- <name>delete_params(Key) -> Ret</name>
+ <name since="">delete_params(Key) -> Ret</name>
<fsummary>Delete one target parameter definition</fsummary>
<type>
<v>Key = term()</v>
diff --git a/lib/snmp/doc/src/snmp_user_based_sm_mib.xml b/lib/snmp/doc/src/snmp_user_based_sm_mib.xml
index cc376ac118..6c2203ed22 100644
--- a/lib/snmp/doc/src/snmp_user_based_sm_mib.xml
+++ b/lib/snmp/doc/src/snmp_user_based_sm_mib.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmp_user_based_sm_mib.xml</file>
</header>
- <module>snmp_user_based_sm_mib</module>
+ <module since="">snmp_user_based_sm_mib</module>
<modulesummary>Instrumentation Functions for SNMP-USER-BASED-SM-MIB</modulesummary>
<description>
<p>The module <c>snmp_user_based_sm_mib</c> implements the instrumentation
@@ -43,7 +43,7 @@
</description>
<funcs>
<func>
- <name>configure(ConfDir) -> void()</name>
+ <name since="">configure(ConfDir) -> void()</name>
<fsummary>Configure the SNMP-USER-BASED-SM-MIB</fsummary>
<type>
<v>ConfDir = string()</v>
@@ -72,7 +72,7 @@
</desc>
</func>
<func>
- <name>reconfigure(ConfDir) -> void()</name>
+ <name since="">reconfigure(ConfDir) -> void()</name>
<fsummary>Configure the SNMP-USER-BASED-SM-MIB</fsummary>
<type>
<v>ConfDir = string()</v>
@@ -103,7 +103,7 @@
</desc>
</func>
<func>
- <name>add_user(EngineID, Name, SecName, Clone, AuthP, AuthKeyC, OwnAuthKeyC, PrivP, PrivKeyC, OwnPrivKeyC, Public, AuthKey, PrivKey) -> Ret</name>
+ <name since="">add_user(EngineID, Name, SecName, Clone, AuthP, AuthKeyC, OwnAuthKeyC, PrivP, PrivKeyC, OwnPrivKeyC, Public, AuthKey, PrivKey) -> Ret</name>
<fsummary>Add one user</fsummary>
<type>
<v>EngineID = string()</v>
@@ -130,7 +130,7 @@
</desc>
</func>
<func>
- <name>delete_user(Key) -> Ret</name>
+ <name since="">delete_user(Key) -> Ret</name>
<fsummary>Delete one user</fsummary>
<type>
<v>Key = term()</v>
diff --git a/lib/snmp/doc/src/snmp_view_based_acm_mib.xml b/lib/snmp/doc/src/snmp_view_based_acm_mib.xml
index fdad735e71..c5e98a3eb5 100644
--- a/lib/snmp/doc/src/snmp_view_based_acm_mib.xml
+++ b/lib/snmp/doc/src/snmp_view_based_acm_mib.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmp_view_based_acm_mib.xml</file>
</header>
- <module>snmp_view_based_acm_mib</module>
+ <module since="">snmp_view_based_acm_mib</module>
<modulesummary>Instrumentation Functions for SNMP-VIEW-BASED-ACM-MIB</modulesummary>
<description>
<p>The module <c>snmp_view_based_acm_mib</c> implements the instrumentation functions for the
@@ -45,7 +45,7 @@
<funcs>
<func>
- <name>configure(ConfDir) -> void()</name>
+ <name since="">configure(ConfDir) -> void()</name>
<fsummary>Configure the SNMP-VIEW-BASED-ACM-MIB</fsummary>
<type>
<v>ConfDir = string()</v>
@@ -71,7 +71,7 @@
</func>
<func>
- <name>reconfigure(ConfDir) -> void()</name>
+ <name since="">reconfigure(ConfDir) -> void()</name>
<fsummary>Configure the SNMP-VIEW-BASED-ACM-MIB</fsummary>
<type>
<v>ConfDir = string()</v>
@@ -104,7 +104,7 @@
</func>
<func>
- <name>add_sec2group(SecModel, SecName, GroupName) -> Ret</name>
+ <name since="">add_sec2group(SecModel, SecName, GroupName) -> Ret</name>
<fsummary>Add one security to group definition</fsummary>
<type>
<v>SecModel = v1 | v2c | usm</v>
@@ -124,7 +124,7 @@
</func>
<func>
- <name>delete_sec2group(Key) -> Ret</name>
+ <name since="">delete_sec2group(Key) -> Ret</name>
<fsummary>Delete one security to group definition</fsummary>
<type>
<v>Key = term()</v>
@@ -139,7 +139,7 @@
</func>
<func>
- <name>add_access(GroupName, Prefix, SecModel, SecLevel, Match, RV, WV, NV) -> Ret</name>
+ <name since="">add_access(GroupName, Prefix, SecModel, SecLevel, Match, RV, WV, NV) -> Ret</name>
<fsummary>Add one access definition</fsummary>
<type>
<v>GroupName = string()</v>
@@ -163,7 +163,7 @@
</func>
<func>
- <name>delete_access(Key) -> Ret</name>
+ <name since="">delete_access(Key) -> Ret</name>
<fsummary>Delete one access definition</fsummary>
<type>
<v>Key = term()</v>
@@ -178,7 +178,7 @@
</func>
<func>
- <name>add_view_tree_fam(ViewIndex, SubTree, Status, Mask) -> Ret</name>
+ <name since="">add_view_tree_fam(ViewIndex, SubTree, Status, Mask) -> Ret</name>
<fsummary>Add one view tree family definition</fsummary>
<type>
<v>ViewIndex = integer()</v>
@@ -199,7 +199,7 @@
</func>
<func>
- <name>delete_view_tree_fam(Key) -> Ret</name>
+ <name since="">delete_view_tree_fam(Key) -> Ret</name>
<fsummary>Delete one view tree family definition</fsummary>
<type>
<v>Key = term()</v>
diff --git a/lib/snmp/doc/src/snmpa.xml b/lib/snmp/doc/src/snmpa.xml
index b78f14da01..dc2f4e6d66 100644
--- a/lib/snmp/doc/src/snmpa.xml
+++ b/lib/snmp/doc/src/snmpa.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpa.xml</file>
</header>
- <module>snmpa</module>
+ <module since="">snmpa</module>
<modulesummary>Interface Functions to the SNMP toolkit agent</modulesummary>
<description>
<p>The module <c>snmpa</c> contains interface functions to the
@@ -77,7 +77,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
<funcs>
<func>
- <name>add_agent_caps(SysORID, SysORDescr) -> SysORIndex</name>
+ <name since="">add_agent_caps(SysORID, SysORDescr) -> SysORIndex</name>
<fsummary>Add an AGENT-CAPABILITY definition to the agent</fsummary>
<type>
<v>SysORID = oid()</v>
@@ -93,7 +93,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>del_agent_caps(SysORIndex) -> void()</name>
+ <name since="">del_agent_caps(SysORIndex) -> void()</name>
<fsummary>Delete an AGENT-CAPABILITY definition from the agent</fsummary>
<type>
<v>SysORIndex = integer()</v>
@@ -108,7 +108,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>get_agent_caps() -> [[SysORIndex, SysORID, SysORDescr, SysORUpTime]]</name>
+ <name since="">get_agent_caps() -> [[SysORIndex, SysORID, SysORDescr, SysORUpTime]]</name>
<fsummary>Return all AGENT-CAPABILITY definitions in the agent</fsummary>
<type>
<v>SysORIndex = integer()</v>
@@ -125,8 +125,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>get(Agent, Vars) -> Values | {error, Reason}</name>
- <name>get(Agent, Vars, Context) -> Values | {error, Reason}</name>
+ <name since="">get(Agent, Vars) -> Values | {error, Reason}</name>
+ <name since="">get(Agent, Vars, Context) -> Values | {error, Reason}</name>
<fsummary>Perform a get operation on the agent</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -150,8 +150,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>get_next(Agent, Vars) -> Values | {error, Reason}</name>
- <name>get_next(Agent, Vars, Context) -> Values | {error, Reason}</name>
+ <name since="">get_next(Agent, Vars) -> Values | {error, Reason}</name>
+ <name since="">get_next(Agent, Vars, Context) -> Values | {error, Reason}</name>
<fsummary>Perform a get-next operation on the agent</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -176,7 +176,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
<!--
<func>
- <name>get_symbolic_store_db() -> Db</name>
+ <name since="">get_symbolic_store_db() -> Db</name>
<fsummary>Retrieve the symbolic store database reference</fsummary>
<type>
<v>Db = term()</v>
@@ -193,8 +193,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
-->
<func>
- <name>backup(BackupDir) -> ok | {error, Reason}</name>
- <name>backup(Agent, BackupDir) -> ok | {error, Reason}</name>
+ <name since="">backup(BackupDir) -> ok | {error, Reason}</name>
+ <name since="">backup(Agent, BackupDir) -> ok | {error, Reason}</name>
<fsummary>Backup agent data</fsummary>
<type>
<v>BackupDir = string()</v>
@@ -216,8 +216,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</desc>
</func>
<func>
- <name>info() -> [{Key, Value}]</name>
- <name>info(Agent) -> [{Key, Value}]</name>
+ <name since="">info() -> [{Key, Value}]</name>
+ <name since="">info(Agent) -> [{Key, Value}]</name>
<fsummary>Return information about the agent</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -236,7 +236,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>old_info_format(NewInfo) -> OldInfo</name>
+ <name since="">old_info_format(NewInfo) -> OldInfo</name>
<fsummary>Return information about the agent</fsummary>
<type>
<v>OldInfo = NewInfo = [{Key, Value}]</v>
@@ -251,8 +251,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>load_mib(Mib) -> ok | {error, Reason}</name>
- <name>load_mib(Agent, Mib) -> ok | {error, Reason}</name>
+ <name since="OTP R16B02">load_mib(Mib) -> ok | {error, Reason}</name>
+ <name since="OTP R16B02">load_mib(Agent, Mib) -> ok | {error, Reason}</name>
<fsummary>Load single MIB into the agent</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -273,10 +273,10 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>load_mibs(Mibs) -> ok | {error, Reason}</name>
- <name>load_mibs(Mibs, Force) -> ok | {error, Reason}</name>
- <name>load_mibs(Agent, Mibs) -> ok | {error, Reason}</name>
- <name>load_mibs(Agent, Mibs, Force) -> ok | {error, Reason}</name>
+ <name since="">load_mibs(Mibs) -> ok | {error, Reason}</name>
+ <name since="">load_mibs(Mibs, Force) -> ok | {error, Reason}</name>
+ <name since="">load_mibs(Agent, Mibs) -> ok | {error, Reason}</name>
+ <name since="OTP R16B02">load_mibs(Agent, Mibs, Force) -> ok | {error, Reason}</name>
<fsummary>Load MIBs into the agent</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -305,8 +305,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>unload_mib(Mib) -> ok | {error, Reason}</name>
- <name>unload_mib(Agent, Mib) -> ok | {error, Reason}</name>
+ <name since="OTP R16B02">unload_mib(Mib) -> ok | {error, Reason}</name>
+ <name since="OTP R16B02">unload_mib(Agent, Mib) -> ok | {error, Reason}</name>
<fsummary>Unload single MIB from the agent</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -321,10 +321,10 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>unload_mibs(Mibs) -> ok | {error, Reason}</name>
- <name>unload_mibs(Mibs, Force) -> ok | {error, Reason}</name>
- <name>unload_mibs(Agent, Mibs) -> ok | {error, Reason}</name>
- <name>unload_mibs(Agent, Mibs, Force) -> ok | {error, Reason}</name>
+ <name since="">unload_mibs(Mibs) -> ok | {error, Reason}</name>
+ <name since="">unload_mibs(Mibs, Force) -> ok | {error, Reason}</name>
+ <name since="">unload_mibs(Agent, Mibs) -> ok | {error, Reason}</name>
+ <name since="OTP R16B02">unload_mibs(Agent, Mibs, Force) -> ok | {error, Reason}</name>
<fsummary>Unload MIBs from the agent</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -347,8 +347,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>which_mibs() -> Mibs</name>
- <name>which_mibs(Agent) -> Mibs</name>
+ <name since="">which_mibs() -> Mibs</name>
+ <name since="">which_mibs(Agent) -> Mibs</name>
<fsummary>Get a list of all the loaded mibs</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -365,8 +365,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>whereis_mib(MibName) -> {ok, MibFile} | {error, Reason}</name>
- <name>whereis_mib(Agent, MibName) -> {ok, MibFile} | {error, Reason}</name>
+ <name since="">whereis_mib(MibName) -> {ok, MibFile} | {error, Reason}</name>
+ <name since="">whereis_mib(Agent, MibName) -> {ok, MibFile} | {error, Reason}</name>
<fsummary>Get the path to the mib file</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -385,10 +385,10 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>current_request_id() -> {value, RequestId} | false</name>
- <name>current_context() -> {value, Context} | false</name>
- <name>current_community() -> {value, Community} | false</name>
- <name>current_address() -> {value, Address} | false</name>
+ <name since="">current_request_id() -> {value, RequestId} | false</name>
+ <name since="">current_context() -> {value, Context} | false</name>
+ <name since="">current_community() -> {value, Community} | false</name>
+ <name since="">current_address() -> {value, Address} | false</name>
<fsummary>Get the request-id, context, community and address of the current request</fsummary>
<type>
<v>RequestId = integer()</v>
@@ -409,8 +409,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>enum_to_int(Name, Enum) -> {value, Int} | false</name>
- <name>enum_to_int(Db, Name, Enum) -> {value, Int} | false</name>
+ <name since="">enum_to_int(Name, Enum) -> {value, Int} | false</name>
+ <name since="">enum_to_int(Db, Name, Enum) -> {value, Int} | false</name>
<fsummary>Convert an enum value to an integer</fsummary>
<type>
<v>Db = term()</v>
@@ -435,8 +435,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>int_to_enum(Name, Int) -> {value, Enum} | false</name>
- <name>int_to_enum(Db, Name, Int) -> {value, Enum} | false</name>
+ <name since="">int_to_enum(Name, Int) -> {value, Enum} | false</name>
+ <name since="">int_to_enum(Db, Name, Int) -> {value, Enum} | false</name>
<fsummary>Convert an integer to an enum value</fsummary>
<type>
<v>Db = term()</v>
@@ -461,8 +461,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>name_to_oid(Name) -> {value, oid()} | false</name>
- <name>name_to_oid(Db, Name) -> {value, oid()} | false</name>
+ <name since="">name_to_oid(Name) -> {value, oid()} | false</name>
+ <name since="">name_to_oid(Db, Name) -> {value, oid()} | false</name>
<fsummary>Convert a symbolic name to an OID</fsummary>
<type>
<v>Db = term()</v>
@@ -482,8 +482,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>oid_to_name(OID) -> {value, Name} | false</name>
- <name>oid_to_name(Db, OID) -> {value, Name} | false</name>
+ <name since="">oid_to_name(OID) -> {value, Name} | false</name>
+ <name since="">oid_to_name(Db, OID) -> {value, Name} | false</name>
<fsummary>Convert an OID to a symbolic name</fsummary>
<type>
<v>Db = term()</v>
@@ -503,7 +503,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>which_aliasnames() -> Result</name>
+ <name since="">which_aliasnames() -> Result</name>
<fsummary>Get all alias-names known to the agent</fsummary>
<type>
<v>Result = [atom()]</v>
@@ -515,7 +515,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>which_tables() -> Result</name>
+ <name since="">which_tables() -> Result</name>
<fsummary>Get all tables known to the agent</fsummary>
<type>
<v>Result = [atom()]</v>
@@ -528,7 +528,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>which_variables() -> Result</name>
+ <name since="">which_variables() -> Result</name>
<fsummary>Get all variables known to the agent</fsummary>
<type>
<v>Result = [atom()]</v>
@@ -541,7 +541,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>which_notifications() -> Result</name>
+ <name since="">which_notifications() -> Result</name>
<fsummary>Get all notifications known to the agent</fsummary>
<type>
<v>Result = [{Name, MibName, Info}]</v>
@@ -557,15 +557,15 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>log_to_txt(LogDir)</name>
- <name>log_to_txt(LogDir, Block | Mibs)</name>
- <name>log_to_txt(LogDir, Mibs, Block | OutFile) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, Block | LogName) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, Block | LogFile) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block, Start) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_txt(LogDir)</name>
+ <name since="">log_to_txt(LogDir, Block | Mibs)</name>
+ <name since="">log_to_txt(LogDir, Mibs, Block | OutFile) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="">log_to_txt(LogDir, Mibs, OutFile, Block | LogName) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, Block | LogFile) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block, Start) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R16B03">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
<fsummary>Convert an Audit Trail Log to text format</fsummary>
<type>
<v>LogDir = string()</v>
@@ -600,14 +600,14 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>log_to_io(LogDir) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Block | Mibs) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, Block | LogName) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, LogName, Block | LogFile) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, LogName, LogFile, Block, Start) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, LogName, LogFile, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, LogName, LogFile, Block, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Block | Mibs) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs, Block | LogName) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, Block | LogFile) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, LogFile, Block, Start) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, LogFile, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R16B03">log_to_io(LogDir, Mibs, LogName, LogFile, Block, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
<fsummary>Convert an Audit Trail Log to text format</fsummary>
<type>
<v>LogDir = string()</v>
@@ -641,7 +641,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>change_log_size(NewSize) -> ok | {error, Reason}</name>
+ <name since="">change_log_size(NewSize) -> ok | {error, Reason}</name>
<fsummary>Change the size of the Audit Trail Log</fsummary>
<type>
<v>NewSize = {MaxBytes, MaxFiles}</v>
@@ -662,8 +662,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>set_log_type(NewType) -> {ok, OldType} | {error, Reason}</name>
- <name>set_log_type(Agent, NewType) -> {ok, OldType} | {error, Reason}</name>
+ <name since="">set_log_type(NewType) -> {ok, OldType} | {error, Reason}</name>
+ <name since="">set_log_type(Agent, NewType) -> {ok, OldType} | {error, Reason}</name>
<fsummary>Change the type of the Audit Trail Log</fsummary>
<type>
<v>NewType = OldType = atl_type()</v>
@@ -684,8 +684,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>mib_of(Oid) -> {ok, MibName} | {error, Reason}</name>
- <name>mib_of(Agent, Oid) -> {ok, MibName} | {error, Reason}</name>
+ <name since="">mib_of(Oid) -> {ok, MibName} | {error, Reason}</name>
+ <name since="">mib_of(Agent, Oid) -> {ok, MibName} | {error, Reason}</name>
<fsummary>Which mib an Oid belongs to</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -704,8 +704,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>me_of(Oid) -> {ok, Me} | {error, Reason}</name>
- <name>me_of(Agent, Oid) -> {ok, Me} | {error, Reason}</name>
+ <name since="">me_of(Oid) -> {ok, Me} | {error, Reason}</name>
+ <name since="">me_of(Agent, Oid) -> {ok, Me} | {error, Reason}</name>
<fsummary>Retrieve the mib-entry of an Oid</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -724,8 +724,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>invalidate_mibs_cache() -> void()</name>
- <name>invalidate_mibs_cache(Agent) -> void()</name>
+ <name since="">invalidate_mibs_cache() -> void()</name>
+ <name since="">invalidate_mibs_cache(Agent) -> void()</name>
<fsummary>Invalidate the mib server cache</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -739,8 +739,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>enable_mibs_cache() -> void()</name>
- <name>enable_mibs_cache(Agent) -> void()</name>
+ <name since="">enable_mibs_cache() -> void()</name>
+ <name since="">enable_mibs_cache(Agent) -> void()</name>
<fsummary>Enable the mib server cache</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -753,8 +753,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>disable_mibs_cache() -> void()</name>
- <name>disable_mibs_cache(Agent) -> void()</name>
+ <name since="">disable_mibs_cache() -> void()</name>
+ <name since="">disable_mibs_cache(Agent) -> void()</name>
<fsummary>Disable the mib server cache</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -767,8 +767,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>which_mibs_cache_size() -> void()</name>
- <name>which_mibs_cache_size(Agent) -> void()</name>
+ <name since="OTP R14B">which_mibs_cache_size() -> void()</name>
+ <name since="OTP R14B">which_mibs_cache_size(Agent) -> void()</name>
<fsummary>The size of the mib server cache</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -781,12 +781,12 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>gc_mibs_cache() -> {ok, NumElementsGCed} | {error, Reason}</name>
- <name>gc_mibs_cache(Agent) -> {ok, NumElementsGCed} | {error, Reason}</name>
- <name>gc_mibs_cache(Age) -> {ok, NumElementsGCed} | {error, Reason}</name>
- <name>gc_mibs_cache(Agent, Age) -> {ok, NumElementsGCed} | {error, Reason}</name>
- <name>gc_mibs_cache(Age, GcLimit) -> {ok, NumElementsGCed} | {error, Reason}</name>
- <name>gc_mibs_cache(Agent, Age, GcLimit) -> {ok, NumElementsGCed} | {error, Reason}</name>
+ <name since="">gc_mibs_cache() -> {ok, NumElementsGCed} | {error, Reason}</name>
+ <name since="">gc_mibs_cache(Agent) -> {ok, NumElementsGCed} | {error, Reason}</name>
+ <name since="">gc_mibs_cache(Age) -> {ok, NumElementsGCed} | {error, Reason}</name>
+ <name since="">gc_mibs_cache(Agent, Age) -> {ok, NumElementsGCed} | {error, Reason}</name>
+ <name since="">gc_mibs_cache(Age, GcLimit) -> {ok, NumElementsGCed} | {error, Reason}</name>
+ <name since="">gc_mibs_cache(Agent, Age, GcLimit) -> {ok, NumElementsGCed} | {error, Reason}</name>
<fsummary>Perform mib server cache gc</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -808,8 +808,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>enable_mibs_cache_autogc() -> void()</name>
- <name>enable_mibs_cache_autogc(Agent) -> void()</name>
+ <name since="">enable_mibs_cache_autogc() -> void()</name>
+ <name since="">enable_mibs_cache_autogc(Agent) -> void()</name>
<fsummary>Enable automatic gc of the mib server cache</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -822,8 +822,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>disable_mibs_cache_autogc() -> void()</name>
- <name>disable_mibs_cache_autogc(Agent) -> void()</name>
+ <name since="">disable_mibs_cache_autogc() -> void()</name>
+ <name since="">disable_mibs_cache_autogc(Agent) -> void()</name>
<fsummary>Disable automatic gc of the mib server cache</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -836,8 +836,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>update_mibs_cache_age(NewAge) -> ok | {error, Reason}</name>
- <name>update_mibs_cache_age(Agent, NewAge) -> ok | {error, Reason}</name>
+ <name since="">update_mibs_cache_age(NewAge) -> ok | {error, Reason}</name>
+ <name since="">update_mibs_cache_age(Agent, NewAge) -> ok | {error, Reason}</name>
<fsummary>Change the mib server cache age property</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -852,8 +852,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>update_mibs_cache_gclimit(NewGcLimit) -> ok | {error, Reason}</name>
- <name>update_mibs_cache_gclimit(Agent, NewGCLimit) -> ok | {error, Reason}</name>
+ <name since="">update_mibs_cache_gclimit(NewGcLimit) -> ok | {error, Reason}</name>
+ <name since="">update_mibs_cache_gclimit(Agent, NewGCLimit) -> ok | {error, Reason}</name>
<fsummary>Change the mib server cache gclimit property</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -869,10 +869,10 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
<func>
- <name>register_notification_filter(Id, Mod, Data) -> ok | {error, Reason}</name>
- <name>register_notification_filter(Agent, Id, Mod, Data) -> ok | {error, Reason}</name>
- <name>register_notification_filter(Id, Mod, Data, Where) -> ok | {error, Reason}</name>
- <name>register_notification_filter(Agent, Id, Mod, Data, Where) -> ok | {error, Reason}</name>
+ <name since="">register_notification_filter(Id, Mod, Data) -> ok | {error, Reason}</name>
+ <name since="">register_notification_filter(Agent, Id, Mod, Data) -> ok | {error, Reason}</name>
+ <name since="">register_notification_filter(Id, Mod, Data, Where) -> ok | {error, Reason}</name>
+ <name since="">register_notification_filter(Agent, Id, Mod, Data, Where) -> ok | {error, Reason}</name>
<fsummary>Register a notification filter</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -897,8 +897,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>unregister_notification_filter(Id) -> ok | {error, Reason}</name>
- <name>unregister_notification_filter(Agent, Id) -> ok | {error, Reason}</name>
+ <name since="">unregister_notification_filter(Id) -> ok | {error, Reason}</name>
+ <name since="">unregister_notification_filter(Agent, Id) -> ok | {error, Reason}</name>
<fsummary>Unregister a notification filter</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -913,8 +913,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>which_notification_filter() -> Filters</name>
- <name>which_notification_filter(Agent) -> Filters</name>
+ <name since="">which_notification_filter() -> Filters</name>
+ <name since="">which_notification_filter(Agent) -> Filters</name>
<fsummary>Which notification filter</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -929,8 +929,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>set_request_limit(NewLimit) -> {ok, OldLimit} | {error, Reason}</name>
- <name>set_request_limit(Agent, NewLimit) -> {ok, OldLimit} | {error, Reason}</name>
+ <name since="">set_request_limit(NewLimit) -> {ok, OldLimit} | {error, Reason}</name>
+ <name since="">set_request_limit(Agent, NewLimit) -> {ok, OldLimit} | {error, Reason}</name>
<fsummary>Change the request limit</fsummary>
<type>
<v>NewLimit = OldLimit = infinity | integer() >= 0</v>
@@ -950,7 +950,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>register_subagent(Agent, SubTreeOid, Subagent) -> ok | {error, Reason}</name>
+ <name since="">register_subagent(Agent, SubTreeOid, Subagent) -> ok | {error, Reason}</name>
<fsummary>Register a sub-agent under a sub-tree</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -975,7 +975,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
</func>
<func>
- <name>unregister_subagent(Agent, SubagentOidOrPid) -> ok | {ok, SubAgentPid} | {error, Reason}</name>
+ <name since="">unregister_subagent(Agent, SubagentOidOrPid) -> ok | {ok, SubAgentPid} | {error, Reason}</name>
<fsummary>Unregister a sub-agent</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -992,7 +992,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
<func>
- <name>send_notification2(Agent, Notification, SendOpts) -> void()</name>
+ <name since="OTP R14B03">send_notification2(Agent, Notification, SendOpts) -> void()</name>
<fsummary>Send notification</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -1119,11 +1119,11 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
<func>
- <name>send_notification(Agent, Notification, Receiver)</name>
- <name>send_notification(Agent, Notification, Receiver, Varbinds)</name>
- <name>send_notification(Agent, Notification, Receiver, NotifyName, Varbinds)</name>
- <name>send_notification(Agent, Notification, Receiver, NotifyName, ContextName, Varbinds) -> void() </name>
- <name>send_notification(Agent, Notification, Receiver, NotifyName, ContextName, Varbinds, LocalEngineID) -> void() </name>
+ <name since="">send_notification(Agent, Notification, Receiver)</name>
+ <name since="">send_notification(Agent, Notification, Receiver, Varbinds)</name>
+ <name since="">send_notification(Agent, Notification, Receiver, NotifyName, Varbinds)</name>
+ <name since="">send_notification(Agent, Notification, Receiver, NotifyName, ContextName, Varbinds) -> void() </name>
+ <name since="OTP R14B">send_notification(Agent, Notification, Receiver, NotifyName, ContextName, Varbinds, LocalEngineID) -> void() </name>
<fsummary>Send a notification</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -1324,13 +1324,13 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
<func>
- <name>discovery(TargetName, Notification) -> {ok, ManagerEngineID} | {error, Reason}</name>
- <name>discovery(TargetName, Notification, Varbinds) -> {ok, ManagerEngineID} | {error, Reason}</name>
- <name>discovery(TargetName, Notification, DiscoHandler) -> {ok, ManagerEngineID} | {error, Reason}</name>
- <name>discovery(TargetName, Notification, ContextName, Varbinds) -> {ok, ManagerEngineID} | {error, Reason}</name>
- <name>discovery(TargetName, Notification, Varbinds, DiscoHandler) -> {ok, ManagerEngineID} | {error, Reason}</name>
- <name>discovery(TargetName, Notification, ContextName, Varbinds, DiscoHandler) -> {ok, ManagerEngineID} | {error, Reason}</name>
- <name>discovery(TargetName, Notification, ContextName, Varbinds, DiscoHandler, ExtraInfo) -> {ok, ManagerEngineID} | {error, Reason}</name>
+ <name since="">discovery(TargetName, Notification) -> {ok, ManagerEngineID} | {error, Reason}</name>
+ <name since="">discovery(TargetName, Notification, Varbinds) -> {ok, ManagerEngineID} | {error, Reason}</name>
+ <name since="">discovery(TargetName, Notification, DiscoHandler) -> {ok, ManagerEngineID} | {error, Reason}</name>
+ <name since="">discovery(TargetName, Notification, ContextName, Varbinds) -> {ok, ManagerEngineID} | {error, Reason}</name>
+ <name since="">discovery(TargetName, Notification, Varbinds, DiscoHandler) -> {ok, ManagerEngineID} | {error, Reason}</name>
+ <name since="">discovery(TargetName, Notification, ContextName, Varbinds, DiscoHandler) -> {ok, ManagerEngineID} | {error, Reason}</name>
+ <name since="">discovery(TargetName, Notification, ContextName, Varbinds, DiscoHandler, ExtraInfo) -> {ok, ManagerEngineID} | {error, Reason}</name>
<fsummary>Initiate the discovery process with a manager</fsummary>
<type>
<v>TargetName = string()</v>
@@ -1379,7 +1379,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
</func>
<func>
- <name>convert_config(OldConfig) -> AgentConfig</name>
+ <name since="">convert_config(OldConfig) -> AgentConfig</name>
<fsummary>Convert old snmp config to new agent config</fsummary>
<type>
<v>OldConfig = list()</v>
@@ -1403,8 +1403,8 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
</func>
<func>
- <name>restart_worker() -> void()</name>
- <name>restart_worker(Agent) -> void()</name>
+ <name since="">restart_worker() -> void()</name>
+ <name since="">restart_worker(Agent) -> void()</name>
<fsummary>Restart the worker process of a multi-threaded agent</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -1419,8 +1419,8 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
</func>
<func>
- <name>restart_set_worker() -> void()</name>
- <name>restart_set_worker(Agent) -> void()</name>
+ <name since="">restart_set_worker() -> void()</name>
+ <name since="">restart_set_worker(Agent) -> void()</name>
<fsummary>Restart the set worker process of a multi-threaded agent</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -1435,7 +1435,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
</func>
<func>
- <name>print_mib_info() -> void()</name>
+ <name since="OTP R14B02">print_mib_info() -> void()</name>
<fsummary>Print mib info</fsummary>
<desc>
<p>Prints the content of all the (snmp) tables and variables
@@ -1446,7 +1446,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
</func>
<func>
- <name>print_mib_tables() -> void()</name>
+ <name since="OTP R14B02">print_mib_tables() -> void()</name>
<fsummary>Print mib tables</fsummary>
<desc>
<p>Prints the content of all the (snmp) tables
@@ -1457,7 +1457,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
</func>
<func>
- <name>print_mib_variables() -> void()</name>
+ <name since="OTP R14B02">print_mib_variables() -> void()</name>
<fsummary>Print mib variables</fsummary>
<desc>
<p>Prints the content of all the (snmp) variables
@@ -1468,7 +1468,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
</func>
<func>
- <name>verbosity(Ref,Verbosity) -> void()</name>
+ <name since="">verbosity(Ref,Verbosity) -> void()</name>
<fsummary>Assign a new verbosity for the process</fsummary>
<type>
<v>Ref = pid() | sub_agents | master_agent | net_if | mib_server | symbolic_store | note_store | local_db</v>
diff --git a/lib/snmp/doc/src/snmpa_conf.xml b/lib/snmp/doc/src/snmpa_conf.xml
index 503e44a6a2..4134a81c0c 100644
--- a/lib/snmp/doc/src/snmpa_conf.xml
+++ b/lib/snmp/doc/src/snmpa_conf.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpa_conf.xml</file>
</header>
- <module>snmpa_conf</module>
+ <module since="">snmpa_conf</module>
<modulesummary>Utility functions for handling the agent config files.</modulesummary>
<description>
<p>The module <c>snmpa_conf</c> contains various utility functions to
@@ -92,7 +92,7 @@ word() = 0..65535
<funcs>
<func>
- <name>agent_entry(Tag, Val) -> agent_entry()</name>
+ <name since="">agent_entry(Tag, Val) -> agent_entry()</name>
<fsummary>Create an agent entry</fsummary>
<type>
<v>Tag = intAgentTransports | intAgentUDPPort | intAgentMaxPacketSize | snmpEngineMaxMessageSize | snmpEngineID</v>
@@ -111,8 +111,8 @@ word() = 0..65535
</func>
<func>
- <name>write_agent_config(Dir, Conf) -> ok</name>
- <name>write_agent_config(Dir, Hdr, Conf) -> ok</name>
+ <name since="">write_agent_config(Dir, Conf) -> ok</name>
+ <name since="">write_agent_config(Dir, Hdr, Conf) -> ok</name>
<fsummary>Write the agent config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -134,7 +134,7 @@ word() = 0..65535
</func>
<func>
- <name>append_agent_config(Dir, Conf) -> ok</name>
+ <name since="">append_agent_config(Dir, Conf) -> ok</name>
<fsummary>Append the agent config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -153,7 +153,7 @@ word() = 0..65535
</func>
<func>
- <name>read_agent_config(Dir) -> Conf</name>
+ <name since="">read_agent_config(Dir) -> Conf</name>
<fsummary>Read the agent config from the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -172,7 +172,7 @@ word() = 0..65535
</func>
<func>
- <name>standard_entry(Tag, Val) -> standard_entry()</name>
+ <name since="">standard_entry(Tag, Val) -> standard_entry()</name>
<fsummary>Create an standard entry</fsummary>
<type>
<v>Tag = sysDescr | sysObjectID | sysContact | sysName | sysLocation | sysServices | snmpEnableAuthenTraps</v>
@@ -192,8 +192,8 @@ word() = 0..65535
</func>
<func>
- <name>write_standard_config(Dir, Conf) -> ok</name>
- <name>write_standard_config(Dir, Hdr, Conf) -> ok</name>
+ <name since="">write_standard_config(Dir, Conf) -> ok</name>
+ <name since="">write_standard_config(Dir, Hdr, Conf) -> ok</name>
<fsummary>Write the agent standard config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -216,7 +216,7 @@ word() = 0..65535
</func>
<func>
- <name>append_standard_config(Dir, Conf) -> ok</name>
+ <name since="">append_standard_config(Dir, Conf) -> ok</name>
<fsummary>Append the agent standard config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -236,7 +236,7 @@ word() = 0..65535
</func>
<func>
- <name>read_standard_config(Dir) -> Conf</name>
+ <name since="">read_standard_config(Dir) -> Conf</name>
<fsummary>Read the agent standard config from the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -255,7 +255,7 @@ word() = 0..65535
</func>
<func>
- <name>context_entry(Context) -> context_entry()</name>
+ <name since="">context_entry(Context) -> context_entry()</name>
<fsummary>Create an context entry</fsummary>
<type>
<v>Context = string()</v>
@@ -273,8 +273,8 @@ word() = 0..65535
</func>
<func>
- <name>write_context_config(Dir, Conf) -> ok</name>
- <name>write_context_config(Dir, Hdr, Conf) -> ok</name>
+ <name since="">write_context_config(Dir, Conf) -> ok</name>
+ <name since="">write_context_config(Dir, Hdr, Conf) -> ok</name>
<fsummary>Write the agent context(s) to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -297,7 +297,7 @@ word() = 0..65535
</func>
<func>
- <name>append_context_config(Dir, Conf) -> ok</name>
+ <name since="">append_context_config(Dir, Conf) -> ok</name>
<fsummary>Append the agent context(s) to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -317,7 +317,7 @@ word() = 0..65535
</func>
<func>
- <name>read_context_config(Dir) -> Conf</name>
+ <name since="">read_context_config(Dir) -> Conf</name>
<fsummary>Read the agent context config from the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -336,8 +336,8 @@ word() = 0..65535
</func>
<func>
- <name>community_entry(CommunityIndex) -> community_entry()</name>
- <name>community_entry(CommunityIndex, CommunityName, SecName, ContextName, TransportTag) -> community_entry()</name>
+ <name since="">community_entry(CommunityIndex) -> community_entry()</name>
+ <name since="">community_entry(CommunityIndex, CommunityName, SecName, ContextName, TransportTag) -> community_entry()</name>
<fsummary>Create an community entry</fsummary>
<type>
<v>CommunityIndex = string()</v>
@@ -364,8 +364,8 @@ word() = 0..65535
</func>
<func>
- <name>write_community_config(Dir, Conf) -> ok</name>
- <name>write_community_config(Dir, Hdr, Conf) -> ok</name>
+ <name since="">write_community_config(Dir, Conf) -> ok</name>
+ <name since="">write_community_config(Dir, Hdr, Conf) -> ok</name>
<fsummary>Write the agent community config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -388,7 +388,7 @@ word() = 0..65535
</func>
<func>
- <name>append_community_config(Dir, Conf) -> ok</name>
+ <name since="">append_community_config(Dir, Conf) -> ok</name>
<fsummary>Append the agent community config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -408,7 +408,7 @@ word() = 0..65535
</func>
<func>
- <name>read_community_config(Dir) -> Conf</name>
+ <name since="">read_community_config(Dir) -> Conf</name>
<fsummary>Read the agent community config from the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -427,10 +427,10 @@ word() = 0..65535
</func>
<func>
- <name>target_addr_entry(Name, Domain, Addr, TagList, ParamsName, EngineId) -> target_addr_entry()</name>
- <name>target_addr_entry(Name, Domain, Addr, TagList, ParamsName, EngineId, TMask) -> target_addr_entry()</name>
- <name>target_addr_entry(Name, Domain, Addr, TagList, ParamsName, EngineId, TMask, MaxMessageSize) -> target_addr_entry()</name>
- <name>target_addr_entry(Name, Domain, Addr, Timeout, RetryCount, TagList, ParamsName, EngineId, TMask, MaxMessageSize) -> target_addr_entry()</name>
+ <name since="">target_addr_entry(Name, Domain, Addr, TagList, ParamsName, EngineId) -> target_addr_entry()</name>
+ <name since="OTP 17.3">target_addr_entry(Name, Domain, Addr, TagList, ParamsName, EngineId, TMask) -> target_addr_entry()</name>
+ <name since="">target_addr_entry(Name, Domain, Addr, TagList, ParamsName, EngineId, TMask, MaxMessageSize) -> target_addr_entry()</name>
+ <name since="">target_addr_entry(Name, Domain, Addr, Timeout, RetryCount, TagList, ParamsName, EngineId, TMask, MaxMessageSize) -> target_addr_entry()</name>
<fsummary>Create an target_addr entry</fsummary>
<type>
<v>Name = string()</v>
@@ -464,8 +464,8 @@ word() = 0..65535
</func>
<func>
- <name>write_target_addr_config(Dir, Conf) -> ok</name>
- <name>write_target_addr_config(Dir, Hdr, Conf) -> ok</name>
+ <name since="">write_target_addr_config(Dir, Conf) -> ok</name>
+ <name since="">write_target_addr_config(Dir, Hdr, Conf) -> ok</name>
<fsummary>Write the agent target_addr config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -488,7 +488,7 @@ word() = 0..65535
</func>
<func>
- <name>append_target_addr_config(Dir, Conf) -> ok</name>
+ <name since="">append_target_addr_config(Dir, Conf) -> ok</name>
<fsummary>Append the agent target_addr config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -508,7 +508,7 @@ word() = 0..65535
</func>
<func>
- <name>read_target_addr_config(Dir) -> Conf</name>
+ <name since="">read_target_addr_config(Dir) -> Conf</name>
<fsummary>Read the agent target_addr config from the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -527,9 +527,9 @@ word() = 0..65535
</func>
<func>
- <name>target_params_entry(Name, Vsn) -> target_params_entry()</name>
- <name>target_params_entry(Name, Vsn, SecName, SecLevel) -> target_params_entry()</name>
- <name>target_params_entry(Name, MPModel, SecModel, SecName, SecLevel) -> target_params_entry()</name>
+ <name since="">target_params_entry(Name, Vsn) -> target_params_entry()</name>
+ <name since="">target_params_entry(Name, Vsn, SecName, SecLevel) -> target_params_entry()</name>
+ <name since="">target_params_entry(Name, MPModel, SecModel, SecName, SecLevel) -> target_params_entry()</name>
<fsummary>Create an target_params entry</fsummary>
<type>
<v>Name = string()</v>
@@ -564,8 +564,8 @@ word() = 0..65535
</func>
<func>
- <name>write_target_params_config(Dir, Conf) -> ok</name>
- <name>write_target_params_config(Dir, Hdr, Conf) -> ok</name>
+ <name since="">write_target_params_config(Dir, Conf) -> ok</name>
+ <name since="">write_target_params_config(Dir, Hdr, Conf) -> ok</name>
<fsummary>Write the agent target_params config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -588,7 +588,7 @@ word() = 0..65535
</func>
<func>
- <name>append_target_params_config(Dir, Conf) -> ok</name>
+ <name since="">append_target_params_config(Dir, Conf) -> ok</name>
<fsummary>Append the agent target_params config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -608,7 +608,7 @@ word() = 0..65535
</func>
<func>
- <name>read_target_params_config(Dir) -> Conf</name>
+ <name since="">read_target_params_config(Dir) -> Conf</name>
<fsummary>Read the agent target_params config from the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -627,10 +627,10 @@ word() = 0..65535
</func>
<func>
- <name>vacm_s2g_entry(SecModel, SecName, GroupName) -> vacm_s2g_entry()</name>
- <name>vacm_acc_entry(GroupName, Prefix, SecModel, SecLevel, Match, ReadView, WriteView, NotifyView) -> vacm_acc_entry()</name>
- <name>vacm_vtf_entry(ViewIndex, ViewSubtree) -> vacm_vtf_entry()</name>
- <name>vacm_vtf_entry(ViewIndex, ViewSubtree, ViewStatus, ViewMask) -> vacm_vtf_entry()</name>
+ <name since="">vacm_s2g_entry(SecModel, SecName, GroupName) -> vacm_s2g_entry()</name>
+ <name since="">vacm_acc_entry(GroupName, Prefix, SecModel, SecLevel, Match, ReadView, WriteView, NotifyView) -> vacm_acc_entry()</name>
+ <name since="">vacm_vtf_entry(ViewIndex, ViewSubtree) -> vacm_vtf_entry()</name>
+ <name since="">vacm_vtf_entry(ViewIndex, ViewSubtree, ViewStatus, ViewMask) -> vacm_vtf_entry()</name>
<fsummary>Create an vacm entry</fsummary>
<type>
<v>SecModel = v1 | v2c | usm</v>
@@ -665,8 +665,8 @@ word() = 0..65535
</func>
<func>
- <name>write_vacm_config(Dir, Conf) -> ok</name>
- <name>write_vacm_config(Dir, Hdr, Conf) -> ok</name>
+ <name since="">write_vacm_config(Dir, Conf) -> ok</name>
+ <name since="">write_vacm_config(Dir, Hdr, Conf) -> ok</name>
<fsummary>Write the agent vacm config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -690,7 +690,7 @@ word() = 0..65535
</func>
<func>
- <name>append_vacm_config(Dir, Conf) -> ok</name>
+ <name since="">append_vacm_config(Dir, Conf) -> ok</name>
<fsummary>Append the agent vacm config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -710,7 +710,7 @@ word() = 0..65535
</func>
<func>
- <name>read_vacm_config(Dir) -> Conf</name>
+ <name since="">read_vacm_config(Dir) -> Conf</name>
<fsummary>Read the agent vacm config from the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -729,8 +729,8 @@ word() = 0..65535
</func>
<func>
- <name>usm_entry(EngineId) -> usm_entry()</name>
- <name>usm_entry(EngineID, UserName, SecName, Clone, AuthP, AuthKeyC, OwnAuthKeyC, PrivP, PrivKeyC, OwnPrivKeyC, Public, AuthKey, PrivKey) -> usm_entry()</name>
+ <name since="">usm_entry(EngineId) -> usm_entry()</name>
+ <name since="">usm_entry(EngineID, UserName, SecName, Clone, AuthP, AuthKeyC, OwnAuthKeyC, PrivP, PrivKeyC, OwnPrivKeyC, Public, AuthKey, PrivKey) -> usm_entry()</name>
<fsummary>Create an usm entry</fsummary>
<type>
<v>EngineId = string()</v>
@@ -762,8 +762,8 @@ word() = 0..65535
</func>
<func>
- <name>write_usm_config(Dir, Conf) -> ok</name>
- <name>write_usm_config(Dir, Hdr, Conf) -> ok</name>
+ <name since="">write_usm_config(Dir, Conf) -> ok</name>
+ <name since="">write_usm_config(Dir, Hdr, Conf) -> ok</name>
<fsummary>Write the agent usm config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -786,7 +786,7 @@ word() = 0..65535
</func>
<func>
- <name>append_usm_config(Dir, Conf) -> ok</name>
+ <name since="">append_usm_config(Dir, Conf) -> ok</name>
<fsummary>Append the agent usm config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -805,7 +805,7 @@ word() = 0..65535
</func>
<func>
- <name>read_usm_config(Dir) -> Conf</name>
+ <name since="">read_usm_config(Dir) -> Conf</name>
<fsummary>Read the agent usm config from the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -824,7 +824,7 @@ word() = 0..65535
</func>
<func>
- <name>notify_entry(Name, Tag, Type) -> notify_entry()</name>
+ <name since="">notify_entry(Name, Tag, Type) -> notify_entry()</name>
<fsummary>Create an notify entry</fsummary>
<type>
<v>Name = string()</v>
@@ -845,8 +845,8 @@ word() = 0..65535
</func>
<func>
- <name>write_notify_config(Dir, Conf) -> ok</name>
- <name>write_notify_config(Dir, Hdr, Conf) -> ok</name>
+ <name since="">write_notify_config(Dir, Conf) -> ok</name>
+ <name since="">write_notify_config(Dir, Hdr, Conf) -> ok</name>
<fsummary>Write the agent notify config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -869,7 +869,7 @@ word() = 0..65535
</func>
<func>
- <name>append_notify_config(Dir, Conf) -> ok</name>
+ <name since="">append_notify_config(Dir, Conf) -> ok</name>
<fsummary>Append the agent notify config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -889,7 +889,7 @@ word() = 0..65535
</func>
<func>
- <name>read_notify_config(Dir) -> Conf</name>
+ <name since="">read_notify_config(Dir) -> Conf</name>
<fsummary>Read the agent notify config from the config file</fsummary>
<type>
<v>Dir = string()</v>
diff --git a/lib/snmp/doc/src/snmpa_discovery_handler.xml b/lib/snmp/doc/src/snmpa_discovery_handler.xml
index 0ea72a880c..21b8746c11 100644
--- a/lib/snmp/doc/src/snmpa_discovery_handler.xml
+++ b/lib/snmp/doc/src/snmpa_discovery_handler.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpa_discovery_handler.xml</file>
</header>
- <module>snmpa_discovery_handler</module>
+ <module since="">snmpa_discovery_handler</module>
<modulesummary>Behaviour module for the SNMP agent discovery handler.</modulesummary>
<description>
<p>This module defines the behaviour of the agent discovery
@@ -51,7 +51,7 @@
<funcs>
<func>
- <name>stage1_finish(TargetName, ManagerEngineID, ExtraInfo) -> ignore | {ok, usm_entry() | [usm_entry()]} | {ok, usm_entry() | [usm_entry()], NewExtraInfo}</name>
+ <name since="">stage1_finish(TargetName, ManagerEngineID, ExtraInfo) -> ignore | {ok, usm_entry() | [usm_entry()]} | {ok, usm_entry() | [usm_entry()], NewExtraInfo}</name>
<fsummary>Discovery stage 1 finish</fsummary>
<type>
<v>TargetName = string()</v>
diff --git a/lib/snmp/doc/src/snmpa_error.xml b/lib/snmp/doc/src/snmpa_error.xml
index 7cc4a3513d..6e6761b7a5 100644
--- a/lib/snmp/doc/src/snmpa_error.xml
+++ b/lib/snmp/doc/src/snmpa_error.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpa_error.xml</file>
</header>
- <module>snmpa_error</module>
+ <module since="">snmpa_error</module>
<modulesummary>Functions for Reporting SNMP Errors</modulesummary>
<description>
<marker id="desc"></marker>
@@ -57,7 +57,7 @@
</description>
<funcs>
<func>
- <name>config_err(Format, Args) -> void()</name>
+ <name since="">config_err(Format, Args) -> void()</name>
<fsummary>Called if a configuration error occurs</fsummary>
<type>
<v>Format = string()</v>
@@ -76,7 +76,7 @@
</func>
<func>
- <name>user_err(Format, Args) -> void()</name>
+ <name since="">user_err(Format, Args) -> void()</name>
<fsummary>Called if a user related error occurs</fsummary>
<type>
<v>Format = string()</v>
diff --git a/lib/snmp/doc/src/snmpa_error_io.xml b/lib/snmp/doc/src/snmpa_error_io.xml
index bcb2688646..d78e09ff13 100644
--- a/lib/snmp/doc/src/snmpa_error_io.xml
+++ b/lib/snmp/doc/src/snmpa_error_io.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpa_error_io.xml</file>
</header>
- <module>snmpa_error_io</module>
+ <module since="">snmpa_error_io</module>
<modulesummary>Functions for Reporting SNMP Errors on stdio</modulesummary>
<description>
<p>The module <c>snmpa_error_io</c> implements the
@@ -52,7 +52,7 @@
</description>
<funcs>
<func>
- <name>config_err(Format, Args) -> void()</name>
+ <name since="">config_err(Format, Args) -> void()</name>
<fsummary>Called if a configuration error occurs</fsummary>
<type>
<v>Format = string()</v>
@@ -68,7 +68,7 @@
</desc>
</func>
<func>
- <name>user_err(Format, Args) -> void()</name>
+ <name since="">user_err(Format, Args) -> void()</name>
<fsummary>Called if a user related error occurs</fsummary>
<type>
<v>Format = string()</v>
diff --git a/lib/snmp/doc/src/snmpa_error_logger.xml b/lib/snmp/doc/src/snmpa_error_logger.xml
index 4feb2e7f32..b0565a6839 100644
--- a/lib/snmp/doc/src/snmpa_error_logger.xml
+++ b/lib/snmp/doc/src/snmpa_error_logger.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpa_error_logger.xml</file>
</header>
- <module>snmpa_error_logger</module>
+ <module since="">snmpa_error_logger</module>
<modulesummary>Functions for Reporting SNMP Errors through the error_logger</modulesummary>
<description>
<p>The module <c>snmpa_error_logger</c> implements the
@@ -54,7 +54,7 @@
</description>
<funcs>
<func>
- <name>config_err(Format, Args) -> void()</name>
+ <name since="">config_err(Format, Args) -> void()</name>
<fsummary>Called if a configuration error occurs</fsummary>
<type>
<v>Format = string()</v>
@@ -70,7 +70,7 @@
</desc>
</func>
<func>
- <name>user_err(Format, Args) -> void()</name>
+ <name since="">user_err(Format, Args) -> void()</name>
<fsummary>Called if a user related error occurs</fsummary>
<type>
<v>Format = string()</v>
diff --git a/lib/snmp/doc/src/snmpa_error_report.xml b/lib/snmp/doc/src/snmpa_error_report.xml
index 282d9b4e59..f08dc1df23 100644
--- a/lib/snmp/doc/src/snmpa_error_report.xml
+++ b/lib/snmp/doc/src/snmpa_error_report.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpa_error_report.xml</file>
</header>
- <module>snmpa_error_report</module>
+ <module since="">snmpa_error_report</module>
<modulesummary>Behaviour module for reporting SNMP agent errors</modulesummary>
<description>
<marker id="desc"></marker>
@@ -52,7 +52,7 @@
</description>
<funcs>
<func>
- <name>config_err(Format, Args) -> void()</name>
+ <name since="">config_err(Format, Args) -> void()</name>
<fsummary>Called if a configuration error occurs</fsummary>
<type>
<v>Format = string()</v>
@@ -68,7 +68,7 @@
</desc>
</func>
<func>
- <name>user_err(Format, Args) -> void()</name>
+ <name since="">user_err(Format, Args) -> void()</name>
<fsummary>Called if a user related error occurs</fsummary>
<type>
<v>Format = string()</v>
diff --git a/lib/snmp/doc/src/snmpa_local_db.xml b/lib/snmp/doc/src/snmpa_local_db.xml
index ac8d466ab3..229f22ab70 100644
--- a/lib/snmp/doc/src/snmpa_local_db.xml
+++ b/lib/snmp/doc/src/snmpa_local_db.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpa_local_db.xml</file>
</header>
- <module>snmpa_local_db</module>
+ <module since="">snmpa_local_db</module>
<modulesummary>The SNMP built-in database</modulesummary>
<description>
<p>The module <c>snmpa_local_db</c> contains functions for
@@ -86,7 +86,7 @@
</section>
<funcs>
<func>
- <name>dump() -> ok | {error, Reason}</name>
+ <name since="">dump() -> ok | {error, Reason}</name>
<fsummary>Dump the database to disk</fsummary>
<type>
<v>Reason = term()</v>
@@ -97,7 +97,7 @@
</desc>
</func>
<func>
- <name>match(NameDb, Pattern)</name>
+ <name since="">match(NameDb, Pattern)</name>
<fsummary>Perform a match on the table</fsummary>
<desc>
<p>Performs an ets/dets matching on the table.
@@ -106,9 +106,9 @@
</desc>
</func>
<func>
- <name>print()</name>
- <name>print(TableName)</name>
- <name>print(TableName, Db)</name>
+ <name since="">print()</name>
+ <name since="">print(TableName)</name>
+ <name since="">print(TableName, Db)</name>
<fsummary>Print the database to screen</fsummary>
<type>
<v>TableName = atom()</v>
@@ -124,7 +124,7 @@
</desc>
</func>
<func>
- <name>table_create(NameDb) -> bool()</name>
+ <name since="">table_create(NameDb) -> bool()</name>
<fsummary>Create a table</fsummary>
<desc>
<p>Creates a table. If the table already exist, the old copy
@@ -135,7 +135,7 @@
</desc>
</func>
<func>
- <name>table_create_row(NameDb, RowIndex, Row) -> bool()</name>
+ <name since="">table_create_row(NameDb, RowIndex, Row) -> bool()</name>
<fsummary>Create a row in a table</fsummary>
<type>
<v>Row = {Val1, Val2, ..., ValN}</v>
@@ -147,28 +147,28 @@
</desc>
</func>
<func>
- <name>table_delete(NameDb) -> void()</name>
+ <name since="">table_delete(NameDb) -> void()</name>
<fsummary>Delete a table</fsummary>
<desc>
<p>Deletes a table.</p>
</desc>
</func>
<func>
- <name>table_delete_row(NameDb, RowIndex) -> bool()</name>
+ <name since="">table_delete_row(NameDb, RowIndex) -> bool()</name>
<fsummary>Delete the row in the table</fsummary>
<desc>
<p>Deletes the row in the table.</p>
</desc>
</func>
<func>
- <name>table_exists(NameDb) -> bool()</name>
+ <name since="">table_exists(NameDb) -> bool()</name>
<fsummary>Check if a table exists</fsummary>
<desc>
<p>Checks if a table exists.</p>
</desc>
</func>
<func>
- <name>table_get_row(NameDb, RowIndex) -> Row | undefined</name>
+ <name since="">table_get_row(NameDb, RowIndex) -> Row | undefined</name>
<fsummary>Get a row from the table</fsummary>
<type>
<v>Row = {Val1, Val2, ..., ValN}</v>
diff --git a/lib/snmp/doc/src/snmpa_mib_data.xml b/lib/snmp/doc/src/snmpa_mib_data.xml
index 1a73c9b634..b849a2826a 100644
--- a/lib/snmp/doc/src/snmpa_mib_data.xml
+++ b/lib/snmp/doc/src/snmpa_mib_data.xml
@@ -30,7 +30,7 @@
<file>snmpa_mib_data.xml</file>
</header>
- <module>snmpa_mib_data</module>
+ <module since="OTP R16B01">snmpa_mib_data</module>
<modulesummary>Behaviour module for the SNMP agent mib-server
data module.</modulesummary>
<description>
@@ -108,7 +108,7 @@
<funcs>
<func>
- <name>Module:new(Storage) -> State</name>
+ <name since="OTP R16B01">Module:new(Storage) -> State</name>
<fsummary>Create new (mib-server) data instance</fsummary>
<type>
<v>Storage = mib_storage()</v>
@@ -122,7 +122,7 @@
</func>
<func>
- <name>Module:close(State) -> void()</name>
+ <name since="OTP R16B01">Module:close(State) -> void()</name>
<fsummary>Close the mib-server data instance</fsummary>
<type>
<v>State = term()</v>
@@ -135,7 +135,7 @@
</func>
<func>
- <name>Module:sync(State) -> void()</name>
+ <name since="OTP R16B01">Module:sync(State) -> void()</name>
<fsummary>Synchronize to disc</fsummary>
<type>
<v>State = term()</v>
@@ -151,7 +151,7 @@
</func>
<func>
- <name>Module:load_mib(State, Filename, MeOverride, TeOverride) -> {ok, NewState} | {error, Reason}</name>
+ <name since="OTP R16B01">Module:load_mib(State, Filename, MeOverride, TeOverride) -> {ok, NewState} | {error, Reason}</name>
<fsummary>Load a mib into the mib-server</fsummary>
<type>
<v>State = NewState = term()</v>
@@ -172,7 +172,7 @@
</func>
<func>
- <name>Module:unload_mib(State, Filename) -> {ok, NewState} | {error, Reason}</name>
+ <name since="OTP R16B01">Module:unload_mib(State, Filename) -> {ok, NewState} | {error, Reason}</name>
<fsummary>Unload mib from the mib-server</fsummary>
<type>
<v>State = NewState = term()</v>
@@ -188,7 +188,7 @@
</func>
<func>
- <name>Module:lookup(State, Oid) -> Reply</name>
+ <name since="OTP R16B01">Module:lookup(State, Oid) -> Reply</name>
<fsummary>Find the mib-entry corresponding to the Oid</fsummary>
<type>
<v>State = term()</v>
@@ -210,7 +210,7 @@
</func>
<func>
- <name>Module:next(State, Oid, MibView) -> Reply</name>
+ <name since="OTP R16B01">Module:next(State, Oid, MibView) -> Reply</name>
<fsummary>Finds the lexicographically next oid</fsummary>
<type>
<v>State = term()</v>
@@ -227,7 +227,7 @@
</func>
<func>
- <name>Module:register_subagent(State, Oid, Pid) -> Reply</name>
+ <name since="OTP R16B01">Module:register_subagent(State, Oid, Pid) -> Reply</name>
<fsummary>Register the subagent</fsummary>
<type>
<v>State = NewState = term()</v>
@@ -245,7 +245,7 @@
</func>
<func>
- <name>Module:unregister_subagent(State, PidOrOid) -> Reply</name>
+ <name since="OTP R16B01">Module:unregister_subagent(State, PidOrOid) -> Reply</name>
<fsummary>Unregister the subagent</fsummary>
<type>
<v>State = NewState = term()</v>
@@ -266,7 +266,7 @@
</func>
<func>
- <name>Module:dump(State, Destination) -> Reply</name>
+ <name since="OTP R16B01">Module:dump(State, Destination) -> Reply</name>
<fsummary>Unregister the subagent</fsummary>
<type>
<v>State = term()</v>
@@ -284,7 +284,7 @@
</func>
<func>
- <name>Module:which_mib(State, Oid) -> Reply</name>
+ <name since="OTP R16B01">Module:which_mib(State, Oid) -> Reply</name>
<fsummary>Retrieve the mib file for an oid()</fsummary>
<type>
<v>State = term()</v>
@@ -301,7 +301,7 @@
</func>
<func>
- <name>Module:which_mibs(State) -> Reply</name>
+ <name since="OTP R16B01">Module:which_mibs(State) -> Reply</name>
<fsummary>Retrieve all loaded mib files</fsummary>
<type>
<v>State = term()</v>
@@ -317,7 +317,7 @@
</func>
<func>
- <name>Module:whereis_mib(State, MibName) -> Reply</name>
+ <name since="OTP R16B01">Module:whereis_mib(State, MibName) -> Reply</name>
<fsummary>Retrieve the mib file for the mib</fsummary>
<type>
<v>State = term()</v>
@@ -334,7 +334,7 @@
</func>
<func>
- <name>Module:info(State) -> Reply</name>
+ <name since="OTP R16B01">Module:info(State) -> Reply</name>
<fsummary>Retrieve misc info for the mib data</fsummary>
<type>
<v>State = term()</v>
@@ -352,7 +352,7 @@
</func>
<func>
- <name>Module:backup(State, BackupDir) -> Reply</name>
+ <name since="OTP R16B01">Module:backup(State, BackupDir) -> Reply</name>
<fsummary>Perform a backup of the mib-server data</fsummary>
<type>
<v>State = term()</v>
@@ -370,7 +370,7 @@
</func>
<func>
- <name>Module:code_change(Destination, Vsn, Extra, State) -> NewState</name>
+ <name since="OTP R16B01">Module:code_change(Destination, Vsn, Extra, State) -> NewState</name>
<fsummary>Perform a code-change</fsummary>
<type>
<v>Destination = up | down</v>
diff --git a/lib/snmp/doc/src/snmpa_mib_storage.xml b/lib/snmp/doc/src/snmpa_mib_storage.xml
index 58ce2167ec..ee2b009e77 100644
--- a/lib/snmp/doc/src/snmpa_mib_storage.xml
+++ b/lib/snmp/doc/src/snmpa_mib_storage.xml
@@ -30,7 +30,7 @@
<file>snmpa_mib_storage.xml</file>
</header>
- <module>snmpa_mib_storage</module>
+ <module since="OTP R16B01">snmpa_mib_storage</module>
<modulesummary>
Behaviour module for the SNMP agent mib storage.
</modulesummary>
@@ -96,7 +96,7 @@
<funcs>
<func>
- <name>Module:open(Name, RecordName, Fields, Type, Options) -> {ok, TabId} | {error, Reason}</name>
+ <name since="OTP R16B01">Module:open(Name, RecordName, Fields, Type, Options) -> {ok, TabId} | {error, Reason}</name>
<fsummary>Create new (mib-server) data instance</fsummary>
<type>
<v>Name = atom()</v>
@@ -122,7 +122,7 @@
</func>
<func>
- <name>Module:close(TabId) -> void()</name>
+ <name since="OTP R16B01">Module:close(TabId) -> void()</name>
<fsummary>Close the mib-storage table</fsummary>
<type>
<v>State = term()</v>
@@ -135,7 +135,7 @@
</func>
<func>
- <name>Module:read(TabId, Key) -> false | {value, Record}</name>
+ <name since="OTP R16B01">Module:read(TabId, Key) -> false | {value, Record}</name>
<fsummary>Read a record from the mib-storage table</fsummary>
<type>
<v>TabId = term()</v>
@@ -150,7 +150,7 @@
</func>
<func>
- <name>Module:write(TabId, Record) -> ok | {error, Reason}</name>
+ <name since="OTP R16B01">Module:write(TabId, Record) -> ok | {error, Reason}</name>
<fsummary>Write a record to the mib-storage table</fsummary>
<type>
<v>TabId = term()</v>
@@ -165,7 +165,7 @@
</func>
<func>
- <name>Module:delete(TabId) -> void()</name>
+ <name since="OTP R16B01">Module:delete(TabId) -> void()</name>
<fsummary>Delete an entire mib-storage table</fsummary>
<type>
<v>TabId = term()</v>
@@ -178,7 +178,7 @@
</func>
<func>
- <name>Module:delete(TabId, Key) -> ok | {error, Reason}</name>
+ <name since="OTP R16B01">Module:delete(TabId, Key) -> ok | {error, Reason}</name>
<fsummary>Delete a record from the mib-storage table</fsummary>
<type>
<v>TabId = term()</v>
@@ -193,7 +193,7 @@
</func>
<func>
- <name>Module:match_object(TabId, Pattern) -> {ok, Recs} | {error, Reason}</name>
+ <name since="OTP R16B01">Module:match_object(TabId, Pattern) -> {ok, Recs} | {error, Reason}</name>
<fsummary>Search the mib-storage table for record matching pattern</fsummary>
<type>
<v>TabId = term()</v>
@@ -210,7 +210,7 @@
</func>
<func>
- <name>Module:match_delete(TabId, Pattern) -> {ok, Recs} | {error, Reason}</name>
+ <name since="OTP R16B01">Module:match_delete(TabId, Pattern) -> {ok, Recs} | {error, Reason}</name>
<fsummary>Delete records in the mib-storage table matching pattern</fsummary>
<type>
<v>TabId = term()</v>
@@ -228,7 +228,7 @@
</func>
<func>
- <name>Module:tab2list(TabId) -> Recs</name>
+ <name since="OTP R16B01">Module:tab2list(TabId) -> Recs</name>
<fsummary>Return all records of the mib-storage table</fsummary>
<type>
<v>TabId = term()</v>
@@ -243,7 +243,7 @@
</func>
<func>
- <name>Module:info(TabId) -> {ok, Info} | {error, Reason}</name>
+ <name since="OTP R16B01">Module:info(TabId) -> {ok, Info} | {error, Reason}</name>
<fsummary>Returns information about the mib-storage table. </fsummary>
<type>
<v>TabId = term()</v>
@@ -259,7 +259,7 @@
</func>
<func>
- <name>Module:sync(TabId) -> void()</name>
+ <name since="OTP R16B01">Module:sync(TabId) -> void()</name>
<fsummary>Synchronize mib-storage table</fsummary>
<type>
<v>TabId = term()</v>
@@ -273,7 +273,7 @@
</func>
<func>
- <name>Module:backup(TabId, BackupDir) -> ok | {error, Reason}</name>
+ <name since="OTP R16B01">Module:backup(TabId, BackupDir) -> ok | {error, Reason}</name>
<fsummary>Perform a backup of the mib-storage table</fsummary>
<type>
<v>TabId = term()</v>
diff --git a/lib/snmp/doc/src/snmpa_mpd.xml b/lib/snmp/doc/src/snmpa_mpd.xml
index a39c087c20..d76a9c4a94 100644
--- a/lib/snmp/doc/src/snmpa_mpd.xml
+++ b/lib/snmp/doc/src/snmpa_mpd.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpa_mpd.xml</file>
</header>
- <module>snmpa_mpd</module>
+ <module since="">snmpa_mpd</module>
<modulesummary>Message Processing and Dispatch module for the SNMP agent</modulesummary>
<description>
<p>The module <c>snmpa_mpd</c> implements the version independent
@@ -52,7 +52,7 @@
<funcs>
<func>
- <name>init(Vsns) -> mpd_state()</name>
+ <name since="">init(Vsns) -> mpd_state()</name>
<fsummary>Initialize the MPD module</fsummary>
<type>
<v>Vsns = [Vsn]</v>
@@ -70,8 +70,8 @@
</func>
<func>
- <name>process_packet(Packet, From, State, NoteStore, Log) -> {ok, Vsn, Pdu, PduMS, ACMData} | {discarded, Reason} | {discovery, DiscoPacket}</name>
- <name>process_packet(Packet, From, LocalEngineID, State, NoteStore, Log) -> {ok, Vsn, Pdu, PduMS, ACMData} | {discarded, Reason} | {discovery, DiscoPacket}</name>
+ <name since="OTP 17.3">process_packet(Packet, From, State, NoteStore, Log) -> {ok, Vsn, Pdu, PduMS, ACMData} | {discarded, Reason} | {discovery, DiscoPacket}</name>
+ <name since="OTP R14B">process_packet(Packet, From, LocalEngineID, State, NoteStore, Log) -> {ok, Vsn, Pdu, PduMS, ACMData} | {discarded, Reason} | {discovery, DiscoPacket}</name>
<fsummary>Process a packet received from the network</fsummary>
<type>
<v>Packet = binary()</v>
@@ -108,8 +108,8 @@
</func>
<func>
- <name>generate_response_msg(Vsn, RePdu, Type, ACMData, Log) -> {ok, Packet} | {discarded, Reason}</name>
- <name>generate_response_msg(Vsn, RePdu, Type, ACMData, LocalEngineID, Log) -> {ok, Packet} | {discarded, Reason}</name>
+ <name since="OTP R14B">generate_response_msg(Vsn, RePdu, Type, ACMData, Log) -> {ok, Packet} | {discarded, Reason}</name>
+ <name since="OTP R14B">generate_response_msg(Vsn, RePdu, Type, ACMData, LocalEngineID, Log) -> {ok, Packet} | {discarded, Reason}</name>
<fsummary>Generate a response packet to be sent to the network</fsummary>
<type>
<v>Vsn = 'version-1' | 'version-2' | 'version-3'</v>
@@ -136,8 +136,8 @@
</func>
<func>
- <name>generate_msg(Vsn, NoteStore, Pdu, MsgData, To) -> {ok, PacketsAndAddresses} | {discarded, Reason}</name>
- <name>generate_msg(Vsn, NoteStore, Pdu, MsgData, LocalEngineID, To) -> {ok, PacketsAndAddresses} | {discarded, Reason}</name>
+ <name since="OTP R14B">generate_msg(Vsn, NoteStore, Pdu, MsgData, To) -> {ok, PacketsAndAddresses} | {discarded, Reason}</name>
+ <name since="OTP R14B">generate_msg(Vsn, NoteStore, Pdu, MsgData, LocalEngineID, To) -> {ok, PacketsAndAddresses} | {discarded, Reason}</name>
<fsummary>Generate a request message to be sent to the network</fsummary>
<type>
<v>Vsn = 'version-1' | 'version-2' | 'version-3'</v>
@@ -185,7 +185,7 @@
</func>
<func>
- <name>process_taddrs(TDests) -> Dests</name>
+ <name since="OTP 17.3">process_taddrs(TDests) -> Dests</name>
<fsummary>Transform addresses from internal MIB format to a less internal
</fsummary>
<type>
@@ -211,7 +211,7 @@
</func>
<func>
- <name>discarded_pdu(Variable) -> void()</name>
+ <name since="">discarded_pdu(Variable) -> void()</name>
<fsummary>Increment the variable associated with a discarded pdu</fsummary>
<type>
<v>Variable = atom()</v>
diff --git a/lib/snmp/doc/src/snmpa_network_interface.xml b/lib/snmp/doc/src/snmpa_network_interface.xml
index d4d4989e90..3e79df11b1 100644
--- a/lib/snmp/doc/src/snmpa_network_interface.xml
+++ b/lib/snmp/doc/src/snmpa_network_interface.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpa_network_interface.xml</file>
</header>
- <module>snmpa_network_interface</module>
+ <module since="">snmpa_network_interface</module>
<modulesummary>Behaviour module for the SNMP agent network interface.</modulesummary>
<description>
<p>This module defines the behaviour of the agent network
@@ -68,7 +68,7 @@
<funcs>
<func>
- <name>start_link(Prio, NoteStore, MasterAgent, Opts) -> {ok, Pid} | {error, Reason}</name>
+ <name since="">start_link(Prio, NoteStore, MasterAgent, Opts) -> {ok, Pid} | {error, Reason}</name>
<fsummary>Start-link the network interface process</fsummary>
<type>
<v>Prio = priority()</v>
@@ -93,7 +93,7 @@
</func>
<func>
- <name>info(Pid) -> [{Key, Value}]</name>
+ <name since="">info(Pid) -> [{Key, Value}]</name>
<fsummary>Return information about the running network interface process</fsummary>
<type>
<v>Pid = pid()</v>
@@ -112,7 +112,7 @@
</func>
<func>
- <name>verbosity(Pid, Verbosity) -> void()</name>
+ <name since="">verbosity(Pid, Verbosity) -> void()</name>
<fsummary>Change the verbosity of a running network interface process</fsummary>
<type>
<v>Pid = pid()</v>
@@ -126,7 +126,7 @@
</func>
<func>
- <name>get_log_type(Pid) -> {ok, LogType} | {error, Reason}</name>
+ <name since="">get_log_type(Pid) -> {ok, LogType} | {error, Reason}</name>
<fsummary>Get the Audit Trail Log type</fsummary>
<type>
<v>Pid = pid()</v>
@@ -147,7 +147,7 @@
</func>
<func>
- <name>set_log_type(Pid, NewType) -> {ok, OldType} | {error, Reason}</name>
+ <name since="">set_log_type(Pid, NewType) -> {ok, OldType} | {error, Reason}</name>
<fsummary>Change the Audit Trail Log type</fsummary>
<type>
<v>Pid = pid()</v>
diff --git a/lib/snmp/doc/src/snmpa_network_interface_filter.xml b/lib/snmp/doc/src/snmpa_network_interface_filter.xml
index 7cd08f8935..02c7d291dd 100644
--- a/lib/snmp/doc/src/snmpa_network_interface_filter.xml
+++ b/lib/snmp/doc/src/snmpa_network_interface_filter.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpa_network_interface_filter.xml</file>
</header>
- <module>snmpa_network_interface_filter</module>
+ <module since="">snmpa_network_interface_filter</module>
<modulesummary>Behaviour module for the SNMP agent network-interface filter.</modulesummary>
<description>
<p>This module defines the behaviour of the agent network interface
@@ -101,7 +101,7 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' |
</section>
<funcs>
<func>
- <name>accept_recv(Domain, Addr) -> boolean()</name>
+ <name since="">accept_recv(Domain, Addr) -> boolean()</name>
<fsummary>Shall the received message be accepted</fsummary>
<type>
<v>Domain = transportDomain()</v>
@@ -116,7 +116,7 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' |
</desc>
</func>
<func>
- <name>accept_send(Domain, Addr) -> boolean()</name>
+ <name since="">accept_send(Domain, Addr) -> boolean()</name>
<fsummary>Shall the message be sent</fsummary>
<type>
<v>Domain = transportDomain()</v>
@@ -131,7 +131,7 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' |
</desc>
</func>
<func>
- <name>accept_recv_pdu(Domain, Addr, PduType) -> boolean()</name>
+ <name since="">accept_recv_pdu(Domain, Addr, PduType) -> boolean()</name>
<fsummary>Shall the received pdu be accepted</fsummary>
<type>
<v>Domain = transportDomain()</v>
@@ -148,7 +148,7 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' |
</desc>
</func>
<func>
- <name>accept_send_pdu(Targets, PduType) -> Reply</name>
+ <name since="">accept_send_pdu(Targets, PduType) -> Reply</name>
<fsummary>Shall the pdu be sent</fsummary>
<type>
<v>Targets = targets()</v>
diff --git a/lib/snmp/doc/src/snmpa_notification_delivery_info_receiver.xml b/lib/snmp/doc/src/snmpa_notification_delivery_info_receiver.xml
index cbae158544..5dad372710 100644
--- a/lib/snmp/doc/src/snmpa_notification_delivery_info_receiver.xml
+++ b/lib/snmp/doc/src/snmpa_notification_delivery_info_receiver.xml
@@ -34,7 +34,7 @@
<rev></rev>
<file>snmpa_notification_delivery_info_receiver.xml</file>
</header>
- <module>snmpa_notification_delivery_info_receiver</module>
+ <module since="">snmpa_notification_delivery_info_receiver</module>
<modulesummary>
Behaviour module for the SNMP agent notification delivery
information receiver.
@@ -76,7 +76,7 @@
<funcs>
<func>
- <name>delivery_targets(Tag, Targets, Extra) -> void()</name>
+ <name since="">delivery_targets(Tag, Targets, Extra) -> void()</name>
<fsummary>Inform about target addresses</fsummary>
<type>
<v>Tag = term()</v>
@@ -97,7 +97,7 @@
</func>
<func>
- <name>delivery_info(Tag, Target, DeliveryResult, Extra) -> void()</name>
+ <name since="">delivery_info(Tag, Target, DeliveryResult, Extra) -> void()</name>
<fsummary>Inform about delivery result</fsummary>
<type>
<v>Tag = term()</v>
diff --git a/lib/snmp/doc/src/snmpa_notification_filter.xml b/lib/snmp/doc/src/snmpa_notification_filter.xml
index 0f16ba4440..902412ccc5 100644
--- a/lib/snmp/doc/src/snmpa_notification_filter.xml
+++ b/lib/snmp/doc/src/snmpa_notification_filter.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpa_notification_filter.xml</file>
</header>
- <module>snmpa_notification_filter</module>
+ <module since="">snmpa_notification_filter</module>
<modulesummary>Behaviour module for the SNMP agent notification filters.</modulesummary>
<description>
<p>This module defines the behaviour of the agent notification
@@ -51,7 +51,7 @@
</description>
<funcs>
<func>
- <name>handle_notification(Notif, Data) -> Reply</name>
+ <name since="">handle_notification(Notif, Data) -> Reply</name>
<fsummary>Handle a notification</fsummary>
<type>
<v>Reply = send | {send, NewNotif} | dont_send</v>
diff --git a/lib/snmp/doc/src/snmpa_supervisor.xml b/lib/snmp/doc/src/snmpa_supervisor.xml
index 86c6fbc350..e11cde390f 100644
--- a/lib/snmp/doc/src/snmpa_supervisor.xml
+++ b/lib/snmp/doc/src/snmpa_supervisor.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpa_supervisor.xml</file>
</header>
- <module>snmpa_supervisor</module>
+ <module since="">snmpa_supervisor</module>
<modulesummary>A supervisor for the SNMP agent Processes</modulesummary>
<description>
<p>This is the top supervisor for the agent part of the SNMP
@@ -42,7 +42,7 @@
</description>
<funcs>
<func>
- <name>start_sub_sup(Opts) -> {ok, pid()} | {error, {already_started, pid()}} | {error, Reason}</name>
+ <name since="">start_sub_sup(Opts) -> {ok, pid()} | {error, {already_started, pid()}} | {error, Reason}</name>
<fsummary>Start the SNMP supervisor for sub-agents only</fsummary>
<type>
<v>Opts = [opt()]</v>
@@ -60,7 +60,7 @@
</desc>
</func>
<func>
- <name>start_master_sup(Opts) -> {ok, pid()} | {error, {already_started, pid()}} | {error, Reason}</name>
+ <name since="">start_master_sup(Opts) -> {ok, pid()} | {error, {already_started, pid()}} | {error, Reason}</name>
<fsummary>Start the SNMP supervisor for all agents</fsummary>
<type>
<v>Opts = [opt()]</v>
@@ -82,7 +82,7 @@
</desc>
</func>
<func>
- <name>start_sub_agent(ParentAgent,Subtree,Mibs) -> {ok, pid()} | {error, Reason}</name>
+ <name since="">start_sub_agent(ParentAgent,Subtree,Mibs) -> {ok, pid()} | {error, Reason}</name>
<fsummary>Start a sub-agent</fsummary>
<type>
<v>ParentAgent = pid()</v>
@@ -99,7 +99,7 @@
</desc>
</func>
<func>
- <name>stop_sub_agent(SubAgent) -> ok | no_such_child</name>
+ <name since="">stop_sub_agent(SubAgent) -> ok | no_such_child</name>
<fsummary>Stop a sub-agent</fsummary>
<type>
<v>SubAgent = pid()</v>
diff --git a/lib/snmp/doc/src/snmpc.xml b/lib/snmp/doc/src/snmpc.xml
index aba51bb500..b22b32a133 100644
--- a/lib/snmp/doc/src/snmpc.xml
+++ b/lib/snmp/doc/src/snmpc.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpc.xml</file>
</header>
- <module>snmpc</module>
+ <module since="">snmpc</module>
<modulesummary>Interface Functions to the SNMP toolkit MIB compiler</modulesummary>
<description>
<p>The module <c>snmpc</c> contains interface functions to the
@@ -43,8 +43,8 @@
<funcs>
<func>
- <name>compile(File)</name>
- <name>compile(File, Options) -> {ok, BinFileName} | {error, Reason}</name>
+ <name since="">compile(File)</name>
+ <name since="">compile(File, Options) -> {ok, BinFileName} | {error, Reason}</name>
<fsummary>Compile the specified MIB</fsummary>
<type>
<v>File = string()</v>
@@ -236,7 +236,7 @@
</func>
<func>
- <name>is_consistent(Mibs) -> ok | {error, Reason}</name>
+ <name since="">is_consistent(Mibs) -> ok | {error, Reason}</name>
<fsummary>Check for OID conflicts between MIBs</fsummary>
<type>
<v>Mibs = [MibName]</v>
@@ -252,7 +252,7 @@
</func>
<func>
- <name>mib_to_hrl(MibName) -> ok | {error, Reason}</name>
+ <name since="">mib_to_hrl(MibName) -> ok | {error, Reason}</name>
<fsummary>Generate constants for the objects in the MIB</fsummary>
<type>
<v>MibName = string()</v>
diff --git a/lib/snmp/doc/src/snmpm.xml b/lib/snmp/doc/src/snmpm.xml
index be4cd58a1a..c45df98ee0 100644
--- a/lib/snmp/doc/src/snmpm.xml
+++ b/lib/snmp/doc/src/snmpm.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpm.xml</file>
</header>
- <module>snmpm</module>
+ <module since="">snmpm</module>
<modulesummary>Interface functions to the SNMP toolkit manager</modulesummary>
<description>
<p>The module <c>snmpm</c> contains interface functions to the
@@ -77,7 +77,7 @@ sec_level() = noAuthNoPriv | authNoPriv | authPriv
</section>
<funcs>
<func>
- <name>monitor() -> Ref</name>
+ <name since="">monitor() -> Ref</name>
<fsummary>Monitor the snmp manager</fsummary>
<type>
<v>Ref = reference()</v>
@@ -92,7 +92,7 @@ sec_level() = noAuthNoPriv | authNoPriv | authPriv
</func>
<func>
- <name>demonitor(Ref) -> void()</name>
+ <name since="">demonitor(Ref) -> void()</name>
<fsummary>Turn off monitoring of the snmp manager</fsummary>
<type>
<v>Ref = reference()</v>
@@ -105,7 +105,7 @@ sec_level() = noAuthNoPriv | authNoPriv | authPriv
</func>
<func>
- <name>notify_started(Timeout) -> Pid</name>
+ <name since="">notify_started(Timeout) -> Pid</name>
<fsummary>Request to be notified when manager started</fsummary>
<type>
<v>Timeout = integer()</v>
@@ -148,7 +148,7 @@ sec_level() = noAuthNoPriv | authNoPriv | authPriv
</func>
<func>
- <name>cancel_notify_started(Pid) -> void()</name>
+ <name since="">cancel_notify_started(Pid) -> void()</name>
<fsummary>Cancel request to be notified when manager started</fsummary>
<type>
<v>Pid = pid()</v>
@@ -161,8 +161,8 @@ sec_level() = noAuthNoPriv | authNoPriv | authPriv
</func>
<func>
- <name>register_user(Id, Module, Data) -> ok | {error, Reason}</name>
- <name>register_user(Id, Module, Data, DefaultAgentConfig) -> ok | {error, Reason}</name>
+ <name since="">register_user(Id, Module, Data) -> ok | {error, Reason}</name>
+ <name since="">register_user(Id, Module, Data, DefaultAgentConfig) -> ok | {error, Reason}</name>
<fsummary>Register a user of the manager</fsummary>
<type>
<v>Id = term()</v>
@@ -204,8 +204,8 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv
</func>
<func>
- <name>register_user_monitor(Id, Module, Data) -> ok | {error, Reason}</name>
- <name>register_user_monitor(Id, Module, Data, DefaultAgentConfig) -> ok | {error, Reason}</name>
+ <name since="">register_user_monitor(Id, Module, Data) -> ok | {error, Reason}</name>
+ <name since="">register_user_monitor(Id, Module, Data, DefaultAgentConfig) -> ok | {error, Reason}</name>
<fsummary>Register a monitored user of the manager</fsummary>
<type>
<v>Id = term()</v>
@@ -252,7 +252,7 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv
</func>
<func>
- <name>unregister_user(Id) -> ok | {error, Reason}</name>
+ <name since="">unregister_user(Id) -> ok | {error, Reason}</name>
<fsummary>Unregister the user</fsummary>
<type>
<v>Id = term()</v>
@@ -265,7 +265,7 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv
</func>
<func>
- <name>which_users() -> Users</name>
+ <name since="">which_users() -> Users</name>
<fsummary>Get a list of all users</fsummary>
<type>
<v>Users = [UserId]</v>
@@ -279,7 +279,7 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv
</func>
<func>
- <name>register_agent(UserId, TargetName, Config) -> ok | {error, Reason}</name>
+ <name since="">register_agent(UserId, TargetName, Config) -> ok | {error, Reason}</name>
<fsummary>Register this agent</fsummary>
<type>
<v>UserId = term()</v>
@@ -325,7 +325,7 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv
</func>
<func>
- <name>unregister_agent(UserId, TargetName) -> ok | {error, Reason}</name>
+ <name since="">unregister_agent(UserId, TargetName) -> ok | {error, Reason}</name>
<fsummary>Unregister the user</fsummary>
<type>
<v>UserId = term()</v>
@@ -339,7 +339,7 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv
</func>
<func>
- <name>agent_info(TargetName, Item) -> {ok, Val} | {error, Reason}</name>
+ <name since="">agent_info(TargetName, Item) -> {ok, Val} | {error, Reason}</name>
<fsummary>Retrieve agent config</fsummary>
<type>
<v>TargetName = target_name()</v>
@@ -354,8 +354,8 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv
</func>
<func>
- <name>update_agent_info(UserId, TargetName, Info) -> ok | {error, Reason}</name>
- <name>update_agent_info(UserId, TargetName, Item, Val) -> ok | {error, Reason}</name>
+ <name since="OTP R14B04">update_agent_info(UserId, TargetName, Info) -> ok | {error, Reason}</name>
+ <name since="">update_agent_info(UserId, TargetName, Item, Val) -> ok | {error, Reason}</name>
<fsummary>Update agent config</fsummary>
<type>
<v>UserId = term()</v>
@@ -379,8 +379,8 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv
</func>
<func>
- <name>which_agents() -> Agents</name>
- <name>which_agents(UserId) -> Agents</name>
+ <name since="">which_agents() -> Agents</name>
+ <name since="">which_agents(UserId) -> Agents</name>
<fsummary>List the registered agents</fsummary>
<type>
<v>UserId = term()</v>
@@ -396,7 +396,7 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv
</func>
<func>
- <name>register_usm_user(EngineID, UserName, Conf) -> ok | {error, Reason}</name>
+ <name since="">register_usm_user(EngineID, UserName, Conf) -> ok | {error, Reason}</name>
<fsummary>Register this USM user</fsummary>
<type>
<v>EngineID = string()</v>
@@ -427,7 +427,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>unregister_usm_user(EngineID, UserName) -> ok | {error, Reason}</name>
+ <name since="">unregister_usm_user(EngineID, UserName) -> ok | {error, Reason}</name>
<fsummary>Unregister this USM user</fsummary>
<type>
<v>EngineID = string()</v>
@@ -442,7 +442,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>usm_user_info(EngineID, UserName, Item) -> {ok, Val} | {error, Reason}</name>
+ <name since="">usm_user_info(EngineID, UserName, Item) -> {ok, Val} | {error, Reason}</name>
<fsummary>Retrieve usm user config</fsummary>
<type>
<v>EngineID = string()</v>
@@ -458,7 +458,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>update_usm_user_info(EngineID, UserName, Item, Val) -> ok | {error, Reason}</name>
+ <name since="">update_usm_user_info(EngineID, UserName, Item, Val) -> ok | {error, Reason}</name>
<fsummary>Update agent config</fsummary>
<type>
<v>EngineID = string()</v>
@@ -475,7 +475,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>which_usm_users() -> UsmUsers</name>
+ <name since="">which_usm_users() -> UsmUsers</name>
<fsummary>List all the registered usm users</fsummary>
<type>
<v>UsmUsers = [{EngineID,UserName}]</v>
@@ -490,7 +490,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>which_usm_users(EngineID) -> UsmUsers</name>
+ <name since="">which_usm_users(EngineID) -> UsmUsers</name>
<fsummary>List the registered usm users</fsummary>
<type>
<v>UsmUsers = [UserName]</v>
@@ -505,8 +505,8 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>sync_get2(UserId, TargetName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get2(UserId, TargetName, Oids, SendOpts) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="OTP R14B03">sync_get2(UserId, TargetName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="OTP R14B03">sync_get2(UserId, TargetName, Oids, SendOpts) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
<fsummary>Synchronous <c>get-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -559,11 +559,11 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>sync_get(UserId, TargetName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get(UserId, TargetName, ContextName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get(UserId, TargetName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get(UserId, TargetName, ContextName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get(UserId, TargetName, ContextName, Oids, Timeout, ExtraInfo) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get(UserId, TargetName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get(UserId, TargetName, ContextName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get(UserId, TargetName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get(UserId, TargetName, ContextName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get(UserId, TargetName, ContextName, Oids, Timeout, ExtraInfo) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
<fsummary>Synchronous <c>get-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -605,8 +605,8 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>async_get2(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get2(UserId, TargetName, Oids, SendOpts) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="OTP R14B03">async_get2(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="OTP R14B03">async_get2(UserId, TargetName, Oids, SendOpts) -> {ok, ReqId} | {error, Reason}</name>
<fsummary>Asynchronous <c>get-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -647,11 +647,11 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>async_get(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get(UserId, TargetName, ContextName, Oids) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get(UserId, TargetName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get(UserId, TargetName, ContextName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get(UserId, TargetName, ContextName, Oids, Expire, ExtraInfo) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get(UserId, TargetName, ContextName, Oids) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get(UserId, TargetName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get(UserId, TargetName, ContextName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get(UserId, TargetName, ContextName, Oids, Expire, ExtraInfo) -> {ok, ReqId} | {error, Reason}</name>
<fsummary>Asynchronous <c>get-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -683,8 +683,8 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>sync_get_next2(UserId, TargetName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get_next2(UserId, TargetName, Oids, SendOpts) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="OTP R14B03">sync_get_next2(UserId, TargetName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="OTP R14B03">sync_get_next2(UserId, TargetName, Oids, SendOpts) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
<fsummary>Synchronous <c>get-next-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -737,11 +737,11 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>sync_get_next(UserId, TargetName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get_next(UserId, TargetName, ContextName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get_next(UserId, TargetName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get_next(UserId, TargetName, ContextName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get_next(UserId, TargetName, ContextName, Oids, Timeout, ExtraInfo) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get_next(UserId, TargetName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get_next(UserId, TargetName, ContextName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get_next(UserId, TargetName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get_next(UserId, TargetName, ContextName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get_next(UserId, TargetName, ContextName, Oids, Timeout, ExtraInfo) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
<fsummary>Synchronous <c>get-next-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -775,8 +775,8 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>async_get_next2(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get_next2(UserId, TargetName, Oids, SendOpts) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="OTP R14B03">async_get_next2(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="OTP R14B03">async_get_next2(UserId, TargetName, Oids, SendOpts) -> {ok, ReqId} | {error, Reason}</name>
<fsummary>Asynchronous <c>get-next-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -814,11 +814,11 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>async_get_next(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get_next(UserId, TargetName, ContextName, Oids) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get_next(UserId, TargetName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get_next(UserId, TargetName, ContextName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get_next(UserId, TargetName, ContextName, Oids, Expire, ExtraInfo) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get_next(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get_next(UserId, TargetName, ContextName, Oids) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get_next(UserId, TargetName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get_next(UserId, TargetName, ContextName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get_next(UserId, TargetName, ContextName, Oids, Expire, ExtraInfo) -> {ok, ReqId} | {error, Reason}</name>
<fsummary>Asynchronous <c>get-next-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -849,8 +849,8 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>sync_set2(UserId, TargetName, VarsAndVals) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_set2(UserId, TargetName, VarsAndVals, SendOpts) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="OTP R14B03">sync_set2(UserId, TargetName, VarsAndVals) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="OTP R14B03">sync_set2(UserId, TargetName, VarsAndVals, SendOpts) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
<fsummary>Synchronous <c>set-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -906,11 +906,11 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>sync_set(UserId, TargetName, VarsAndVals) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_set(UserId, TargetName, ContextName, VarsAndVals) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_set(UserId, TargetName, VarsAndVals, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_set(UserId, TargetName, ContextName, VarsAndVals, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_set(UserId, TargetName, ContextName, VarsAndVals, Timeout, ExtraInfo) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_set(UserId, TargetName, VarsAndVals) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_set(UserId, TargetName, ContextName, VarsAndVals) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_set(UserId, TargetName, VarsAndVals, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_set(UserId, TargetName, ContextName, VarsAndVals, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_set(UserId, TargetName, ContextName, VarsAndVals, Timeout, ExtraInfo) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
<fsummary>Synchronous <c>set-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -946,8 +946,8 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>async_set2(UserId, TargetName, VarsAndVals) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_set2(UserId, TargetName, VarsAndVals, SendOpts) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="OTP R14B03">async_set2(UserId, TargetName, VarsAndVals) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="OTP R14B03">async_set2(UserId, TargetName, VarsAndVals, SendOpts) -> {ok, ReqId} | {error, Reason}</name>
<fsummary>Asynchronous <c>set-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -990,11 +990,11 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>async_set(UserId, TargetName, VarsAndVals) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_set(UserId, TargetName, ContextName, VarsAndVals) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_set(UserId, TargetName, VarsAndVals, Expire) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_set(UserId, TargetName, ContextName, VarsAndVals, Expire) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_set(UserId, TargetName, ContextName, VarsAndVals, Expire, ExtraInfo) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_set(UserId, TargetName, VarsAndVals) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_set(UserId, TargetName, ContextName, VarsAndVals) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_set(UserId, TargetName, VarsAndVals, Expire) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_set(UserId, TargetName, ContextName, VarsAndVals, Expire) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_set(UserId, TargetName, ContextName, VarsAndVals, Expire, ExtraInfo) -> {ok, ReqId} | {error, Reason}</name>
<fsummary>Asynchronous <c>set-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -1026,8 +1026,8 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>sync_get_bulk2(UserId, TragetName, NonRep, MaxRep, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get_bulk2(UserId, TragetName, NonRep, MaxRep, Oids, SendOpts) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="OTP R14B03">sync_get_bulk2(UserId, TragetName, NonRep, MaxRep, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="OTP R14B03">sync_get_bulk2(UserId, TragetName, NonRep, MaxRep, Oids, SendOpts) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
<fsummary>Synchronous <c>get-bulk-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -1082,11 +1082,11 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>sync_get_bulk(UserId, TragetName, NonRep, MaxRep, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get_bulk(UserId, TragetName, NonRep, MaxRep, ContextName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get_bulk(UserId, TragetName, NonRep, MaxRep, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get_bulk(UserId, TragetName, NonRep, MaxRep, ContextName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get_bulk(UserId, TragetName, NonRep, MaxRep, ContextName, Oids, Timeout, ExtraInfo) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get_bulk(UserId, TragetName, NonRep, MaxRep, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get_bulk(UserId, TragetName, NonRep, MaxRep, ContextName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get_bulk(UserId, TragetName, NonRep, MaxRep, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get_bulk(UserId, TragetName, NonRep, MaxRep, ContextName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get_bulk(UserId, TragetName, NonRep, MaxRep, ContextName, Oids, Timeout, ExtraInfo) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
<fsummary>Synchronous <c>get-bulk-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -1121,8 +1121,8 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>async_get_bulk2(UserId, TargetName, NonRep, MaxRep, Oids) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get_bulk2(UserId, TargetName, NonRep, MaxRep, Oids, SendOpts) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="OTP R14B03">async_get_bulk2(UserId, TargetName, NonRep, MaxRep, Oids) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="OTP R14B03">async_get_bulk2(UserId, TargetName, NonRep, MaxRep, Oids, SendOpts) -> {ok, ReqId} | {error, Reason}</name>
<fsummary>Asynchronous <c>get-bulk-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -1162,11 +1162,11 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>async_get_bulk(UserId, TargetName, NonRep, MaxRep, Oids) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get_bulk(UserId, TargetName, NonRep, MaxRep, ContextName, Oids) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get_bulk(UserId, TargetName, NonRep, MaxRep, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get_bulk(UserId, TargetName, NonRep, MaxRep, ContextName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get_bulk(UserId, TargetName, NonRep, MaxRep, ContextName, Oids, Expire, ExtraInfo) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get_bulk(UserId, TargetName, NonRep, MaxRep, Oids) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get_bulk(UserId, TargetName, NonRep, MaxRep, ContextName, Oids) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get_bulk(UserId, TargetName, NonRep, MaxRep, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get_bulk(UserId, TargetName, NonRep, MaxRep, ContextName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get_bulk(UserId, TargetName, NonRep, MaxRep, ContextName, Oids, Expire, ExtraInfo) -> {ok, ReqId} | {error, Reason}</name>
<fsummary>Asynchronous <c>get-bulk-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -1199,7 +1199,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>cancel_async_request(UserId, ReqId) -> ok | {error, Reason}</name>
+ <name since="">cancel_async_request(UserId, ReqId) -> ok | {error, Reason}</name>
<fsummary>Cancel a asynchronous request</fsummary>
<type>
<v>UserId = term()</v>
@@ -1214,15 +1214,15 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>log_to_txt(LogDir)</name>
- <name>log_to_txt(LogDir, Block | Mibs)</name>
- <name>log_to_txt(LogDir, Mibs, Block | OutFile) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, Block | LogName) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, Block | LogFile) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block, Start) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R16B03">log_to_txt(LogDir)</name>
+ <name since="">log_to_txt(LogDir, Block | Mibs)</name>
+ <name since="">log_to_txt(LogDir, Mibs, Block | OutFile) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="">log_to_txt(LogDir, Mibs, OutFile, Block | LogName) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, Block | LogFile) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block, Start) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R16B03">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
<fsummary>Convert an Audit Trail Log to text format</fsummary>
<type>
<v>LogDir = string()</v>
@@ -1257,15 +1257,15 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>log_to_io(LogDir) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Block | Mibs) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs) -> ok | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, Block | LogName) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, LogName, Block | LogFile) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, LogName, LogFile, Block, Start) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, LogName, LogFile, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, LogName, LogFile, Block, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Block | Mibs) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs) -> ok | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs, Block | LogName) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, Block | LogFile) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, LogFile, Block, Start) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, LogFile, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R16B03">log_to_io(LogDir, Mibs, LogName, LogFile, Block, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
<fsummary>Convert an Audit Trail Log to text format</fsummary>
<type>
<v>LogDir = string()</v>
@@ -1299,7 +1299,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>change_log_size(NewSize) -> ok | {error, Reason}</name>
+ <name since="">change_log_size(NewSize) -> ok | {error, Reason}</name>
<fsummary>Change the size of the Audit Trail Log</fsummary>
<type>
<v>NewSize = {MaxBytes, MaxFiles}</v>
@@ -1321,7 +1321,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>set_log_type(NewType) -> {ok, OldType} | {error, Reason}</name>
+ <name since="">set_log_type(NewType) -> {ok, OldType} | {error, Reason}</name>
<fsummary>Change the Audit Trail Log type</fsummary>
<type>
<v>NewType = OldType = atl_type()</v>
@@ -1340,7 +1340,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>load_mib(Mib) -> ok | {error, Reason}</name>
+ <name since="">load_mib(Mib) -> ok | {error, Reason}</name>
<fsummary>Load a MIB into the manager</fsummary>
<type>
<v>Mib = MibName</v>
@@ -1361,7 +1361,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>unload_mib(Mib) -> ok | {error, Reason}</name>
+ <name since="">unload_mib(Mib) -> ok | {error, Reason}</name>
<fsummary>Unload a MIB from the manager</fsummary>
<type>
<v>Mib = MibName</v>
@@ -1382,7 +1382,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>which_mibs() -> Mibs</name>
+ <name since="">which_mibs() -> Mibs</name>
<fsummary>Which mibs are loaded into the manager</fsummary>
<type>
<v>Mibs = [{MibName, MibFile}]</v>
@@ -1397,7 +1397,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>name_to_oid(Name) -> {ok, Oids} | {error, Reason}</name>
+ <name since="">name_to_oid(Name) -> {ok, Oids} | {error, Reason}</name>
<fsummary>Get all the possible oid's for an alias-name</fsummary>
<type>
<v>Name = atom()</v>
@@ -1414,7 +1414,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>oid_to_name(Oid) -> {ok, Name} | {error, Reason}</name>
+ <name since="">oid_to_name(Oid) -> {ok, Name} | {error, Reason}</name>
<fsummary>Get the alias-name of the oid </fsummary>
<type>
<v>Oid = oid()</v>
@@ -1429,7 +1429,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>oid_to_type(Oid) -> {ok, Type} | {error, Reason}</name>
+ <name since="">oid_to_type(Oid) -> {ok, Type} | {error, Reason}</name>
<fsummary>Get the type of the the oid</fsummary>
<type>
<v>Oid = oid()</v>
@@ -1444,7 +1444,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>backup(BackupDir) -> ok | {error, Reason}</name>
+ <name since="">backup(BackupDir) -> ok | {error, Reason}</name>
<fsummary>Backup manager data</fsummary>
<type>
<v>BackupDir = string()</v>
@@ -1458,7 +1458,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>info() -> [{Key, Value}]</name>
+ <name since="">info() -> [{Key, Value}]</name>
<fsummary>Return information about the manager</fsummary>
<type>
<v>Key = atom()</v>
@@ -1475,7 +1475,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>verbosity(Ref, Verbosity) -> void()</name>
+ <name since="">verbosity(Ref, Verbosity) -> void()</name>
<fsummary>Assign a new verbosity for the process</fsummary>
<type>
<v>Ref = server | config | net_if | note_store | all</v>
@@ -1492,8 +1492,8 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>format_reason(Reason) -> string()</name>
- <name>format_reason(Prefix, Reason) -> string()</name>
+ <name since="">format_reason(Reason) -> string()</name>
+ <name since="">format_reason(Prefix, Reason) -> string()</name>
<fsummary>Assign a new verbosity for the process</fsummary>
<type>
<v>Reason = term()</v>
diff --git a/lib/snmp/doc/src/snmpm_conf.xml b/lib/snmp/doc/src/snmpm_conf.xml
index a3097e5f7e..17ecf2df7c 100644
--- a/lib/snmp/doc/src/snmpm_conf.xml
+++ b/lib/snmp/doc/src/snmpm_conf.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpm_conf.xml</file>
</header>
- <module>snmpm_conf</module>
+ <module since="">snmpm_conf</module>
<modulesummary>Utility functions for handling the manager config files.</modulesummary>
<description>
<p>The module <c>snmpm_conf</c> contains various utility functions to
@@ -42,7 +42,7 @@
</description>
<funcs>
<func>
- <name>manager_entry(Tag, Val) -> manager_entry()</name>
+ <name since="">manager_entry(Tag, Val) -> manager_entry()</name>
<fsummary>Create an manager entry</fsummary>
<type>
<v>Tag = address | port | engine_id | max_message_size</v>
@@ -60,8 +60,8 @@
</desc>
</func>
<func>
- <name>write_manager_config(Dir, Conf) -> ok</name>
- <name>write_manager_config(Dir, Hdr, Conf) -> ok</name>
+ <name since="">write_manager_config(Dir, Conf) -> ok</name>
+ <name since="">write_manager_config(Dir, Hdr, Conf) -> ok</name>
<fsummary>Write the manager config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -81,7 +81,7 @@
</desc>
</func>
<func>
- <name>append_manager_config(Dir, Conf) -> ok</name>
+ <name since="">append_manager_config(Dir, Conf) -> ok</name>
<fsummary>Append the manager config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -98,7 +98,7 @@
</desc>
</func>
<func>
- <name>read_manager_config(Dir) -> Conf</name>
+ <name since="">read_manager_config(Dir) -> Conf</name>
<fsummary>Read the manager config from the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -115,9 +115,9 @@
</desc>
</func>
<func>
- <name>users_entry(UserId) -> users_entry()</name>
- <name>users_entry(UserId, UserMod) -> users_entry()</name>
- <name>users_entry(UserId, UserMod, UserData) -> users_entry()</name>
+ <name since="">users_entry(UserId) -> users_entry()</name>
+ <name since="">users_entry(UserId, UserMod) -> users_entry()</name>
+ <name since="">users_entry(UserId, UserMod, UserData) -> users_entry()</name>
<fsummary>Create an users entry</fsummary>
<type>
<v>UserId = term()</v>
@@ -139,8 +139,8 @@
</desc>
</func>
<func>
- <name>write_users_config(Dir, Conf) -> ok</name>
- <name>write_users_config(Dir, Hdr, Conf) -> ok</name>
+ <name since="">write_users_config(Dir, Conf) -> ok</name>
+ <name since="">write_users_config(Dir, Hdr, Conf) -> ok</name>
<fsummary>Write the manager users config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -161,7 +161,7 @@
</desc>
</func>
<func>
- <name>append_users_config(Dir, Conf) -> ok</name>
+ <name since="">append_users_config(Dir, Conf) -> ok</name>
<fsummary>Append the manager users config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -179,7 +179,7 @@
</desc>
</func>
<func>
- <name>read_users_config(Dir) -> Conf</name>
+ <name since="">read_users_config(Dir) -> Conf</name>
<fsummary>Read the manager users config from the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -196,7 +196,7 @@
</desc>
</func>
<func>
- <name>agents_entry(UserId, TargetName, Comm, Domain, Addr, EngineID, Timeout, MaxMessageSize, Version, SecModel, SecName, SecLevel) -> agents_entry()</name>
+ <name since="">agents_entry(UserId, TargetName, Comm, Domain, Addr, EngineID, Timeout, MaxMessageSize, Version, SecModel, SecName, SecLevel) -> agents_entry()</name>
<fsummary>Create an agents entry</fsummary>
<type>
<v>UserId = term()</v>
@@ -223,8 +223,8 @@
</desc>
</func>
<func>
- <name>write_agents_config(Dir, Conf) -> ok</name>
- <name>write_agents_config(Dir, Hdr, Conf) -> ok</name>
+ <name since="">write_agents_config(Dir, Conf) -> ok</name>
+ <name since="">write_agents_config(Dir, Hdr, Conf) -> ok</name>
<fsummary>Write the manager agents to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -245,7 +245,7 @@
</desc>
</func>
<func>
- <name>append_agents_config(Dir, Conf) -> ok</name>
+ <name since="">append_agents_config(Dir, Conf) -> ok</name>
<fsummary>Append the manager agents to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -263,7 +263,7 @@
</desc>
</func>
<func>
- <name>read_agents_config(Dir) -> Conf</name>
+ <name since="">read_agents_config(Dir) -> Conf</name>
<fsummary>Read the manager agents config from the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -280,8 +280,8 @@
</desc>
</func>
<func>
- <name>usm_entry(EngineID, UserName, AuthP, AuthKey, PrivP, PrivKey) -> usm_entry()</name>
- <name>usm_entry(EngineID, UserName, SecName, AuthP, AuthKey, PrivP, PrivKey) -> usm_entry()</name>
+ <name since="">usm_entry(EngineID, UserName, AuthP, AuthKey, PrivP, PrivKey) -> usm_entry()</name>
+ <name since="">usm_entry(EngineID, UserName, SecName, AuthP, AuthKey, PrivP, PrivKey) -> usm_entry()</name>
<fsummary>Create an usm entry</fsummary>
<type>
<v>EngineID = string()</v>
@@ -303,8 +303,8 @@
</desc>
</func>
<func>
- <name>write_usm_config(Dir, Conf) -> ok</name>
- <name>write_usm_config(Dir, Hdr, Conf) -> ok</name>
+ <name since="">write_usm_config(Dir, Conf) -> ok</name>
+ <name since="">write_usm_config(Dir, Hdr, Conf) -> ok</name>
<fsummary>Write the manager usm config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -325,7 +325,7 @@
</desc>
</func>
<func>
- <name>append_usm_config(Dir, Conf) -> ok</name>
+ <name since="">append_usm_config(Dir, Conf) -> ok</name>
<fsummary>Append the manager usm config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -343,7 +343,7 @@
</desc>
</func>
<func>
- <name>read_usm_config(Dir) -> Conf</name>
+ <name since="">read_usm_config(Dir) -> Conf</name>
<fsummary>Read the manager usm config from the config file</fsummary>
<type>
<v>Dir = string()</v>
diff --git a/lib/snmp/doc/src/snmpm_mpd.xml b/lib/snmp/doc/src/snmpm_mpd.xml
index 08276e4b30..b024f8d294 100644
--- a/lib/snmp/doc/src/snmpm_mpd.xml
+++ b/lib/snmp/doc/src/snmpm_mpd.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpm_mpd.xml</file>
</header>
- <module>snmpm_mpd</module>
+ <module since="">snmpm_mpd</module>
<modulesummary>Message Processing and Dispatch module for the SNMP manager</modulesummary>
<description>
<p>The module <c>snmpm_mpd</c> implements the version independent
@@ -48,7 +48,7 @@
<funcs>
<func>
- <name>init_mpd(Vsns) -> mpd_state()</name>
+ <name since="">init_mpd(Vsns) -> mpd_state()</name>
<fsummary>Initialize the MPD module</fsummary>
<type>
<v>Vsns = [Vsn]</v>
@@ -64,7 +64,7 @@
</desc>
</func>
<func>
- <name>process_msg(Msg, Domain, Addr, State, NoteStore, Logger) -> {ok, Vsn, Pdu, PduMS, MsgData} | {discarded, Reason}</name>
+ <name since="OTP 17.3">process_msg(Msg, Domain, Addr, State, NoteStore, Logger) -> {ok, Vsn, Pdu, PduMS, MsgData} | {discarded, Reason}</name>
<fsummary>Process a message received from the network</fsummary>
<type>
<v>Msg = binary()</v>
@@ -92,7 +92,7 @@
</desc>
</func>
<func>
- <name>generate_msg(Vsn, NoteStore, Pdu, MsgData, Logger) -> {ok, Packet} | {discarded, Reason}</name>
+ <name since="">generate_msg(Vsn, NoteStore, Pdu, MsgData, Logger) -> {ok, Packet} | {discarded, Reason}</name>
<fsummary>Generate a request message to be sent to the network</fsummary>
<type>
<v>Vsn = 'version-1' | 'version-2' | 'version-3'</v>
@@ -117,7 +117,7 @@
</desc>
</func>
<func>
- <name>generate_response_msg(Vsn, Pdu, MsgData, Logger) -> {ok, Packet} | {discarded, Reason}</name>
+ <name since="">generate_response_msg(Vsn, Pdu, MsgData, Logger) -> {ok, Packet} | {discarded, Reason}</name>
<fsummary>Generate a response packet to be sent to the network</fsummary>
<type>
<v>Vsn = 'version-1' | 'version-2' | 'version-3'</v>
diff --git a/lib/snmp/doc/src/snmpm_network_interface.xml b/lib/snmp/doc/src/snmpm_network_interface.xml
index 73892aa2e8..e4a66ceef2 100644
--- a/lib/snmp/doc/src/snmpm_network_interface.xml
+++ b/lib/snmp/doc/src/snmpm_network_interface.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpm_network_interface.xml</file>
</header>
- <module>snmpm_network_interface</module>
+ <module since="">snmpm_network_interface</module>
<modulesummary>Behaviour module for the SNMP manager network interface.</modulesummary>
<description>
<p>This module defines the behaviour of the manager network
@@ -79,7 +79,7 @@
<funcs>
<func>
- <name>start_link(Server, NoteStore) -> {ok, Pid} | {error, Reason}</name>
+ <name since="">start_link(Server, NoteStore) -> {ok, Pid} | {error, Reason}</name>
<fsummary>Start-link the network interface process</fsummary>
<type>
<v>Server = pid()</v>
@@ -95,7 +95,7 @@
</func>
<func>
- <name>stop(Pid) -> void()</name>
+ <name since="">stop(Pid) -> void()</name>
<fsummary>Stop the network interface process</fsummary>
<type>
<v>Pid = pid()</v>
@@ -108,7 +108,7 @@
</func>
<func>
- <name>send_pdu(Pid, Pdu, Vsn, MsgData, Domain, Addr, ExtraInfo) -> void()</name>
+ <name since="">send_pdu(Pid, Pdu, Vsn, MsgData, Domain, Addr, ExtraInfo) -> void()</name>
<fsummary>Request the network interface process to send this pdu</fsummary>
<type>
<v>Pid = pid()</v>
@@ -142,7 +142,7 @@
</func>
<func>
- <name>inform_response(Pid, Ref, Addr, Port) -> void()</name>
+ <name since="">inform_response(Pid, Ref, Addr, Port) -> void()</name>
<fsummary>Send the inform-request ack</fsummary>
<type>
<v>Pid = pid()</v>
@@ -163,7 +163,7 @@
</func>
<func>
- <name>note_store(Pid, NoteStore) -> void()</name>
+ <name since="">note_store(Pid, NoteStore) -> void()</name>
<fsummary>Change the verbosity of the network interface process</fsummary>
<type>
<v>Pid = pid()</v>
@@ -179,7 +179,7 @@
</func>
<func>
- <name>info(Pid) -> [{Key, Value}]</name>
+ <name since="">info(Pid) -> [{Key, Value}]</name>
<fsummary>Return information about the running network interface process</fsummary>
<type>
<v>Pid = pid()</v>
@@ -198,7 +198,7 @@
</func>
<func>
- <name>verbosity(Pid, Verbosity) -> void()</name>
+ <name since="">verbosity(Pid, Verbosity) -> void()</name>
<fsummary>Change the verbosity of the network interface process</fsummary>
<type>
<v>Pid = pid()</v>
@@ -212,7 +212,7 @@
</func>
<func>
- <name>get_log_type(Pid) -> {ok, LogType} | {error, Reason}</name>
+ <name since="">get_log_type(Pid) -> {ok, LogType} | {error, Reason}</name>
<fsummary>Get the Audit Trail Log type</fsummary>
<type>
<v>Pid = pid()</v>
@@ -233,7 +233,7 @@
</func>
<func>
- <name>set_log_type(Pid, NewType) -> {ok, OldType} | {error, Reason}</name>
+ <name since="">set_log_type(Pid, NewType) -> {ok, OldType} | {error, Reason}</name>
<fsummary>Change the Audit Trail Log type</fsummary>
<type>
<v>Pid = pid()</v>
diff --git a/lib/snmp/doc/src/snmpm_network_interface_filter.xml b/lib/snmp/doc/src/snmpm_network_interface_filter.xml
index 742cd53fc6..a50572da51 100644
--- a/lib/snmp/doc/src/snmpm_network_interface_filter.xml
+++ b/lib/snmp/doc/src/snmpm_network_interface_filter.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpm_network_interface_filter.xml</file>
</header>
- <module>snmpm_network_interface_filter</module>
+ <module since="">snmpm_network_interface_filter</module>
<modulesummary>Behaviour module for the SNMP manager network-interface filter.</modulesummary>
<description>
<p>This module defines the behaviour of the manager network interface
@@ -100,7 +100,7 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' |
<funcs>
<func>
- <name>accept_recv(Domain, Addr) -> boolean()</name>
+ <name since="">accept_recv(Domain, Addr) -> boolean()</name>
<fsummary>Shall the received message be accepted</fsummary>
<type>
<v>Domain = transportDomain()</v>
@@ -116,7 +116,7 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' |
</func>
<func>
- <name>accept_send(Domain, Addr) -> boolean()</name>
+ <name since="">accept_send(Domain, Addr) -> boolean()</name>
<fsummary>Shall the message be sent</fsummary>
<type>
<v>Domain = transportDomain()</v>
@@ -132,7 +132,7 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' |
</func>
<func>
- <name>accept_recv_pdu(Domain, Addr, PduType) -> boolean()</name>
+ <name since="">accept_recv_pdu(Domain, Addr, PduType) -> boolean()</name>
<fsummary>Shall the received pdu be accepted</fsummary>
<type>
<v>Domain = transportDomain()</v>
@@ -150,7 +150,7 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' |
</func>
<func>
- <name>accept_send_pdu(Domain, Addr, PduType) -> boolean()</name>
+ <name since="">accept_send_pdu(Domain, Addr, PduType) -> boolean()</name>
<fsummary>Shall the pdu be sent</fsummary>
<type>
<v>Domain = transportDomain()</v>
diff --git a/lib/snmp/doc/src/snmpm_user.xml b/lib/snmp/doc/src/snmpm_user.xml
index 87ae1d224a..9abf596c83 100644
--- a/lib/snmp/doc/src/snmpm_user.xml
+++ b/lib/snmp/doc/src/snmpm_user.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpm_user.xml</file>
</header>
- <module>snmpm_user</module>
+ <module since="">snmpm_user</module>
<modulesummary>Behaviour module for the SNMP manager user.</modulesummary>
<description>
<p>This module defines the behaviour of the manager user.
@@ -93,7 +93,7 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(),
<funcs>
<func>
- <name>handle_error(ReqId, Reason, UserData) -> void()</name>
+ <name since="">handle_error(ReqId, Reason, UserData) -> void()</name>
<fsummary>Handle error</fsummary>
<type>
<v>ReqId = integer()</v>
@@ -122,7 +122,7 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(),
</func>
<func>
- <name>handle_agent(Domain, Addr, Type, SnmpInfo, UserData) -> Reply</name>
+ <name since="">handle_agent(Domain, Addr, Type, SnmpInfo, UserData) -> Reply</name>
<fsummary>Handle agent</fsummary>
<type>
<v>Domain = transportDomainUdpIpv4 | transportDomainUdpIpv6</v>
@@ -181,7 +181,7 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(),
</func>
<func>
- <name>handle_pdu(TargetName, ReqId, SnmpPduInfo, UserData) -> void()</name>
+ <name since="">handle_pdu(TargetName, ReqId, SnmpPduInfo, UserData) -> void()</name>
<fsummary>Handle the reply to an asynchronous request</fsummary>
<type>
<v>TargetName = target_name()</v>
@@ -202,7 +202,7 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(),
</func>
<func>
- <name>handle_trap(TargetName, SnmpTrapInfo, UserData) -> Reply</name>
+ <name since="">handle_trap(TargetName, SnmpTrapInfo, UserData) -> Reply</name>
<fsummary>Handle a trap/notification message</fsummary>
<type>
<v>TargetName = TargetName2 = target_name()</v>
@@ -225,7 +225,7 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(),
</func>
<func>
- <name>handle_inform(TargetName, SnmpInformInfo, UserData) -> Reply</name>
+ <name since="">handle_inform(TargetName, SnmpInformInfo, UserData) -> Reply</name>
<fsummary>Handle a inform message</fsummary>
<type>
<v>TargetName = TargetName2 = target_name()</v>
@@ -253,7 +253,7 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(),
</func>
<func>
- <name>handle_report(TargetName, SnmpReportInfo, UserData) -> Reply</name>
+ <name since="">handle_report(TargetName, SnmpReportInfo, UserData) -> Reply</name>
<fsummary>Handle a report message</fsummary>
<type>
<v>TargetName = TargetName2 = target_name()</v>
@@ -278,7 +278,7 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(),
</func>
<func>
- <name>handle_invalid_result(IN, OUT) -> void()</name>
+ <name since="OTP R16B03">handle_invalid_result(IN, OUT) -> void()</name>
<fsummary>Handle a report message</fsummary>
<type>
<v>IN = {Func, Args}</v>
diff --git a/lib/snmp/mibs/Makefile.in b/lib/snmp/mibs/Makefile.in
index 2350194077..77893cbdc8 100644
--- a/lib/snmp/mibs/Makefile.in
+++ b/lib/snmp/mibs/Makefile.in
@@ -41,14 +41,7 @@ RELSYSDIR = $(RELEASE_PATH)/lib/snmp-$(VSN)
# Common macros
# ----------------------------------------------------
-# NOTE:
-# The OTP-REG mib actually belongs to another
-# application (otp_mibs), and is exported by this
-# app. But since that app is built later, we have
-# to built it here in order to be able to build
-# OTP-SNMPEA-MIB (that needs otpModules and
-# otpApplications).
-MIBS_A = \
+MIBS = \
RFC1213-MIB \
STANDARD-MIB \
SNMPv2-TM \
@@ -62,16 +55,10 @@ MIBS_A = \
SNMP-VIEW-BASED-ACM-MIB \
SNMP-USM-AES-MIB \
INET-ADDRESS-MIB \
- TRANSPORT-ADDRESS-MIB
-
-MIBS_B = OTP-SNMPEA-MIB
-
-BUILD_MIBS = \
- $(MIBS_A) \
+ TRANSPORT-ADDRESS-MIB \
OTP-REG \
- $(MIBS_B)
-
-MIBS = $(MIBS_A) $(MIBS_B)
+ OTP-TC \
+ OTP-SNMPEA-MIB
STD_v1_MIB_FILES = \
RFC1155-SMI.mib \
@@ -100,8 +87,8 @@ HRL_FILES = $(SNMP_HRL_TARGET_DIR)/SNMPv2-TC.hrl \
TARGET_FILES = \
$(ERL_TOP)/lib/snmp/bin/snmp-v2tov1 \
- $(BUILD_MIBS:%=$(SNMP_BIN_TARGET_DIR)/%.bin) \
- $(HRL_TARGETS) \
+ $(MIBS:%=$(SNMP_BIN_TARGET_DIR)/%.bin) \
+ $(HRL_TARGETS) \
$(V1_MIB_FILES)
@@ -136,21 +123,18 @@ endif
# Targets
# ----------------------------------------------------
-OTP_MIBDIR = $(shell if test -d ../../otp_mibs; then echo otp_mibs; \
- else echo sasl; fi)
-
debug opt: $(TARGET_FILES)
$(ERL_TOP)/lib/snmp/bin/snmp-v2tov1: $(ERL_TOP)/lib/snmp/bin/snmp-v2tov1.src
$(gen_verbose)$(PERL) -p -e 's?%PERL%?$(PERL)? ' < $< > $@
$(V_at)chmod 755 $@
-$(SNMP_BIN_TARGET_DIR)/OTP-REG.bin: $(ERL_TOP)/lib/$(OTP_MIBDIR)/mibs/OTP-REG.mib
- $(snmp_verbose)$(ERLC) -pa $(SNMP_TOOLKIT)/ebin -I $(SNMP_TOOLKIT)/priv/mibs $(SNMP_FLAGS) -o $(SNMP_BIN_TARGET_DIR) $<
-
# To support parallel make, we'll need explicit dependencies
# to ensure that an imported MIB has been compiled when it's needed.
+$(SNMP_BIN_TARGET_DIR)/OTP-TC.bin: \
+ $(SNMP_BIN_TARGET_DIR)/OTP-REG.bin
+
$(SNMP_BIN_TARGET_DIR)/STANDARD-MIB.bin: \
$(SNMP_BIN_TARGET_DIR)/RFC1213-MIB.bin
@@ -208,8 +192,6 @@ info:
@echo ""
@echo "TARGET_FILES = $(TARGET_FILES)"
@echo ""
- @echo "OTP_MIBDIR = $(OTP_MIBDIR)"
- @echo ""
@echo "SNMP_VSN = $(SNMP_VSN)"
@echo "VSN = $(VSN)"
@echo "RELSYSDIR = "$(RELSYSDIR)""
diff --git a/lib/otp_mibs/mibs/OTP-REG.mib b/lib/snmp/mibs/OTP-REG.mib
index bf1585061c..bf1585061c 100644
--- a/lib/otp_mibs/mibs/OTP-REG.mib
+++ b/lib/snmp/mibs/OTP-REG.mib
diff --git a/lib/otp_mibs/mibs/OTP-TC.mib b/lib/snmp/mibs/OTP-TC.mib
index efe5451f0c..efe5451f0c 100644
--- a/lib/otp_mibs/mibs/OTP-TC.mib
+++ b/lib/snmp/mibs/OTP-TC.mib
diff --git a/lib/snmp/src/agent/snmpa_set_lib.erl b/lib/snmp/src/agent/snmpa_set_lib.erl
index 57507a36e8..97b8ddf7c4 100644
--- a/lib/snmp/src/agent/snmpa_set_lib.erl
+++ b/lib/snmp/src/agent/snmpa_set_lib.erl
@@ -46,9 +46,9 @@
%%* 6) IF value is outside the acceptable range THEN wrongValue.
%% 7) IF variable does not exist and could not ever be created
%% THEN noCreation.
-%% 8) IF variable can not be created now THEN inconsistentName.
-%% 9) IF value can not be set now THEN inconsistentValue.
-%%* 9) IF instances of the variable can not be modified THEN notWritable.
+%% 8) IF variable cannot be created now THEN inconsistentName.
+%% 9) IF value cannot be set now THEN inconsistentValue.
+%%* 9) IF instances of the variable cannot be modified THEN notWritable.
%% 10) IF an unavailable resource is needed THEN resourceUnavailable.
%% 11) IF any other error THEN genErr.
%% 12) Otherwise ok!
diff --git a/lib/snmp/src/agent/snmpa_trap.erl b/lib/snmp/src/agent/snmpa_trap.erl
index e75016f7ec..293d1f3ccf 100644
--- a/lib/snmp/src/agent/snmpa_trap.erl
+++ b/lib/snmp/src/agent/snmpa_trap.erl
@@ -830,11 +830,11 @@ do_send_v1_trap(Enter, Spec, V1Res, NVbs, ExtraInfo, NetIf, SysUpTime) ->
case lists:keyfind(transportDomainUdpIpv4, 1, Transports) of
false ->
?vtrace(
- "snmpa_trap: can not send v1 trap "
+ "snmpa_trap: cannot send v1 trap "
"without IPv4 domain: ~p",
[Transports]),
user_err(
- "snmpa_trap: can not send v1 trap "
+ "snmpa_trap: cannot send v1 trap "
"without IPv4 domain: ~p",
[Transports]);
DomainAddr ->
diff --git a/lib/snmp/test/snmp_compiler_test.erl b/lib/snmp/test/snmp_compiler_test.erl
index 0a7b729d1f..2e48d5134d 100644
--- a/lib/snmp/test/snmp_compiler_test.erl
+++ b/lib/snmp/test/snmp_compiler_test.erl
@@ -234,14 +234,14 @@ agent_capabilities(Config) when is_list(Config) ->
AcMib = join(Dir,"AC-TEST-MIB.mib"),
?line {ok, MibFile1} = snmpc:compile(AcMib, [options,
version,
- {i, [SnmpMibsDir, OtpMibsMibsDir]},
+ {i, [SnmpMibsDir]},
{outdir, Dir},
{verbosity, trace}]),
?line {ok, Mib1} = snmp_misc:read_mib(MibFile1),
?line {ok, MibFile2} = snmpc:compile(AcMib, [options,
version,
agent_capabilities,
- {i, [SnmpMibsDir, OtpMibsMibsDir]},
+ {i, [SnmpMibsDir]},
{outdir, Dir},
{verbosity, trace}]),
?line {ok, Mib2} = snmp_misc:read_mib(MibFile2),
@@ -290,7 +290,7 @@ module_compliance(Config) when is_list(Config) ->
?line {ok, Mib2} = snmp_misc:read_mib(MibFile2),
MEDiff = Mib2#mib.mes -- Mib1#mib.mes,
%% This is a rather pathetic test, but it is somthing...
- io:format("agent_capabilities -> "
+ io:format("module_compliance -> "
"~n MEDiff: ~p"
"~n Mib1: ~p"
"~n Mib2: ~p"
diff --git a/lib/snmp/test/test-mibs/ALARM-MIB.mib b/lib/snmp/test/test-mibs/ALARM-MIB.mib
index 18e43d4b4b..10d2adbff9 100644
--- a/lib/snmp/test/test-mibs/ALARM-MIB.mib
+++ b/lib/snmp/test/test-mibs/ALARM-MIB.mib
@@ -330,7 +330,7 @@ alarmModelRowStatus OBJECT-TYPE
cannot be used as an alarm suppression mechanism. Entries
that are notInService will disappear as described in RFC2579.
- This row can not be modified while it is being
+ This row cannot be modified while it is being
referenced by a value of alarmActiveModelPointer. In these
cases, an error of `inconsistentValue' will be returned to
the manager.
diff --git a/lib/snmp/test/test-mibs/SNMPv2-TC.mib b/lib/snmp/test/test-mibs/SNMPv2-TC.mib
index fd6a728ab5..1d75c4bbd8 100644
--- a/lib/snmp/test/test-mibs/SNMPv2-TC.mib
+++ b/lib/snmp/test/test-mibs/SNMPv2-TC.mib
@@ -454,7 +454,7 @@ value | | see 1| ->C| ->D
associated with this column or that there is no
conceptual row for which this column would be
accessible in the MIB view used by the retrieval. As
- such, the management station can not issue any
+ such, the management station cannot issue any
management protocol set operations to create an
instance of this column.
@@ -576,7 +576,7 @@ value | | see 1| ->C| ->D
associated with this column or that there is no
conceptual row for which this column would be
accessible in the MIB view used by the retrieval. As
- such, the management station can not issue any
+ such, the management station cannot issue any
management protocol set operations to create an
instance of this column.
diff --git a/lib/snmp/vsn.mk b/lib/snmp/vsn.mk
index 4d5a0fbce8..f305497cd3 100644
--- a/lib/snmp/vsn.mk
+++ b/lib/snmp/vsn.mk
@@ -19,6 +19,6 @@
# %CopyrightEnd%
APPLICATION = snmp
-SNMP_VSN = 5.2.12
+SNMP_VSN = 5.3
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(SNMP_VSN)$(PRE_VSN)"
diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml
index e8988ce487..0bc4baf5eb 100644
--- a/lib/ssh/doc/src/notes.xml
+++ b/lib/ssh/doc/src/notes.xml
@@ -308,6 +308,21 @@
</section>
</section>
+<section><title>Ssh 4.6.9.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed port leakage if a ssh:daemon call failed.</p>
+ <p>
+ Own Id: OTP-15397 Aux Id: ERL-801 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Ssh 4.6.9.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -2898,7 +2913,7 @@
</item>
<item>
<p>
- Fixed internal error on when client and server can not
+ Fixed internal error on when client and server cannot
agree o which authmethod to use.</p>
<p>
Own Id: OTP-10731 Aux Id: seq12237 </p>
diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml
index 8435fced11..3fd6eae423 100644
--- a/lib/ssh/doc/src/ssh.xml
+++ b/lib/ssh/doc/src/ssh.xml
@@ -28,7 +28,7 @@
<date>2007-10-06</date>
<rev></rev>
</header>
- <module>ssh</module>
+ <module since="">ssh</module>
<modulesummary>Main API of the ssh application</modulesummary>
<description>
<p>This is the interface module for the <c>SSH</c> application.
@@ -46,7 +46,7 @@
In that encrypted connection one or more channels could be opened with
<seealso marker="ssh_connection#session_channel/2">ssh_connection:session_channel/2,4</seealso>.
</p>
- <p>Each channel is an isolated "pipe" between a client-side process and a server-side process. Thoose process
+ <p>Each channel is an isolated "pipe" between a client-side process and a server-side process. Those process
pairs could handle for example file transfers (sftp) or remote command execution (shell, exec and/or cli).
If a custom shell is implemented, the user of the client could execute the special commands remotely. Note that
the user is not necessarily a human but probably a system interfacing the SSH app.
@@ -1059,17 +1059,17 @@
<!-- CLOSE/1 -->
<func>
- <name name="close" arity="1"/>
+ <name name="close" arity="1" since=""/>
<fsummary>Closes an SSH connection.</fsummary>
<desc><p>Closes an SSH connection.</p></desc>
</func>
<!-- CONNECT/2 etc -->
<func>
- <name>connect(Host, Port, Options) -> Result </name>
- <name>connect(Host, Port, Options, NegotiationTimeout) -> Result </name>
- <name>connect(TcpSocket, Options) -> Result</name>
- <name>connect(TcpSocket, Options, NegotiationTimeout) -> Result</name>
+ <name since="">connect(Host, Port, Options) -> Result </name>
+ <name since="">connect(Host, Port, Options, NegotiationTimeout) -> Result </name>
+ <name since="OTP 19.0">connect(TcpSocket, Options) -> Result</name>
+ <name since="">connect(TcpSocket, Options, NegotiationTimeout) -> Result</name>
<fsummary>Connects to an SSH server.</fsummary>
<type>
<v>Host = <seealso marker="#type-host">host()</seealso></v>
@@ -1098,7 +1098,7 @@
<!-- CONNECTION_INFO/1, CONNECTION_INFO/2 -->
<func>
- <name name="connection_info" arity="2"/>
+ <name name="connection_info" arity="2" since=""/>
<fsummary>Retrieves information about a connection.</fsummary>
<desc>
<p>Retrieves information about a connection. The list <c>Keys</c> defines which information that
@@ -1108,9 +1108,9 @@
<!-- DEAMON/1,2,3 -->
<func>
- <name>daemon(Port | TcpSocket) -> Result</name>
- <name>daemon(Port | TcpSocket, Options) -> Result</name>
- <name>daemon(HostAddress, Port, Options) -> Result</name>
+ <name since="">daemon(Port | TcpSocket) -> Result</name>
+ <name since="">daemon(Port | TcpSocket, Options) -> Result</name>
+ <name since="">daemon(HostAddress, Port, Options) -> Result</name>
<fsummary>Starts a server listening for SSH connections.</fsummary>
<type>
<v>Port = integer()</v>
@@ -1154,7 +1154,7 @@
<!-- DAEMON_INFO/1 -->
<func>
- <name name="daemon_info" arity="1"/>
+ <name name="daemon_info" arity="1" since="OTP 19.0"/>
<fsummary>Get info about a daemon</fsummary>
<desc>
<p>Returns a key-value list with information about the daemon.</p>
@@ -1164,7 +1164,7 @@
<!-- DEFAULT_ALGORITHMS/0 -->
<func>
- <name name="default_algorithms" arity="0"/>
+ <name name="default_algorithms" arity="0" since="OTP 18.0"/>
<fsummary>Get a list declaring the supported algorithms</fsummary>
<desc>
<p>Returns a key-value list, where the keys are the different types of algorithms and the values are the
@@ -1176,9 +1176,9 @@
<!-- SHELL/1,2,3 -->
<func>
- <name>shell(Host | TcpSocket) -> Result </name>
- <name>shell(Host | TcpSocket, Options) -> Result </name>
- <name>shell(Host, Port, Options) -> Result </name>
+ <name since="">shell(Host | TcpSocket) -> Result </name>
+ <name since="">shell(Host | TcpSocket, Options) -> Result </name>
+ <name since="">shell(Host, Port, Options) -> Result </name>
<fsummary>Starts an interactive shell on a remote SSH server.</fsummary>
<type>
<v>Host = <seealso marker="#type-host">host()</seealso></v>
@@ -1203,8 +1203,8 @@
</func>
<func>
- <name name="start" arity="0"/>
- <name name="start" arity="1"/>
+ <name name="start" arity="0" since=""/>
+ <name name="start" arity="1" since=""/>
<fsummary>Starts the SSH application.</fsummary>
<desc>
<p>Utility function that starts the applications <c>crypto</c>, <c>public_key</c>,
@@ -1215,7 +1215,7 @@
</func>
<func>
- <name name="stop" arity="0"/>
+ <name name="stop" arity="0" since=""/>
<fsummary>Stops the <c>ssh</c> application.</fsummary>
<desc>
<p>Stops the <c>ssh</c> application.
@@ -1225,9 +1225,9 @@
</func>
<func>
- <name name="stop_daemon" arity="1"/>
- <name name="stop_daemon" arity="2"/>
- <name name="stop_daemon" arity="3"/>
+ <name name="stop_daemon" arity="1" since=""/>
+ <name name="stop_daemon" arity="2" since=""/>
+ <name name="stop_daemon" arity="3" since="OTP 21.0"/>
<fsummary>Stops the listener and all connections started by the listener.</fsummary>
<desc>
<p>Stops the listener and all connections started by the listener.</p>
@@ -1235,9 +1235,9 @@
</func>
<func>
- <name name="stop_listener" arity="1"/>
- <name name="stop_listener" arity="2"/>
- <name name="stop_listener" arity="3"/>
+ <name name="stop_listener" arity="1" since=""/>
+ <name name="stop_listener" arity="2" since=""/>
+ <name name="stop_listener" arity="3" since="OTP 21.0"/>
<fsummary>Stops the listener, but leaves existing connections started by the listener operational.</fsummary>
<desc>
<p>Stops the listener, but leaves existing connections started by the listener operational.</p>
diff --git a/lib/ssh/doc/src/ssh_client_channel.xml b/lib/ssh/doc/src/ssh_client_channel.xml
index 9be4007c68..cd28b95fd3 100644
--- a/lib/ssh/doc/src/ssh_client_channel.xml
+++ b/lib/ssh/doc/src/ssh_client_channel.xml
@@ -29,7 +29,7 @@
<date></date>
<rev></rev>
</header>
- <module>ssh_client_channel</module>
+ <module since="OTP 21.0">ssh_client_channel</module>
<modulesummary>-behaviour(ssh_client_channel). (Replaces ssh_channel)
</modulesummary>
<description>
@@ -68,8 +68,8 @@
<funcs>
<func>
- <name>call(ChannelRef, Msg) -></name>
- <name>call(ChannelRef, Msg, Timeout) -> Reply | {error, Reason}</name>
+ <name since="OTP 21.0">call(ChannelRef, Msg) -></name>
+ <name since="OTP 21.0">call(ChannelRef, Msg, Timeout) -> Reply | {error, Reason}</name>
<fsummary>Makes a synchronous call to a channel.</fsummary>
<type>
<v>ChannelRef = pid() </v>
@@ -92,7 +92,7 @@
</func>
<func>
- <name>cast(ChannelRef, Msg) -> ok </name>
+ <name since="OTP 21.0">cast(ChannelRef, Msg) -> ok </name>
<fsummary>Sends an asynchronous message to the channel
ChannelRef and returns ok.</fsummary>
<type>
@@ -111,7 +111,7 @@
</func>
<func>
- <name>enter_loop(State) -> _ </name>
+ <name since="OTP 21.0">enter_loop(State) -> _ </name>
<fsummary>Makes an existing process an ssh_client_channel (replaces ssh_channel) process.</fsummary>
<type>
<v>State = term()</v>
@@ -131,7 +131,7 @@
</func>
<func>
- <name>init(Options) -> {ok, State} | {ok, State, Timeout} | {stop, Reason} </name>
+ <name since="OTP 21.0">init(Options) -> {ok, State} | {ok, State, Timeout} | {stop, Reason} </name>
<fsummary>Initiates an <c>ssh_client_channel</c> process.</fsummary>
<type>
<v>Options = [{Option, Value}]</v>
@@ -173,7 +173,7 @@
</func>
<func>
- <name>reply(Client, Reply) -> _</name>
+ <name since="OTP 21.0">reply(Client, Reply) -> _</name>
<fsummary>Sends a reply to a client.</fsummary>
<type>
<v>Client = opaque()</v>
@@ -193,8 +193,8 @@
</func>
<func>
- <name>start(SshConnection, ChannelId, ChannelCb, CbInitArgs) -> </name>
- <name>start_link(SshConnection, ChannelId, ChannelCb, CbInitArgs) ->
+ <name since="OTP 21.0">start(SshConnection, ChannelId, ChannelCb, CbInitArgs) -> </name>
+ <name since="OTP 21.0">start_link(SshConnection, ChannelId, ChannelCb, CbInitArgs) ->
{ok, ChannelRef} | {error, Reason}</name>
<fsummary>Starts a process that handles an SSH channel.</fsummary>
<type>
@@ -244,7 +244,7 @@
<funcs>
<func>
- <name>Module:code_change(OldVsn, State, Extra) -> {ok,
+ <name since="OTP 21.0">Module:code_change(OldVsn, State, Extra) -> {ok,
NewState}</name>
<fsummary>Converts process state when code is changed.</fsummary>
<type>
@@ -287,7 +287,7 @@
</func>
<func>
- <name>Module:init(Args) -> {ok, State} | {ok, State, timeout()} |
+ <name since="OTP 21.0">Module:init(Args) -> {ok, State} | {ok, State, timeout()} |
{stop, Reason}</name>
<fsummary>Makes necessary initializations and returns the
initial channel state if the initializations succeed.</fsummary>
@@ -307,7 +307,7 @@
</func>
<func>
- <name>Module:handle_call(Msg, From, State) -> Result</name>
+ <name since="OTP 21.0">Module:handle_call(Msg, From, State) -> Result</name>
<fsummary>Handles messages sent by calling
<c>call/[2,3]</c>.</fsummary>
<type>
@@ -334,7 +334,7 @@
</func>
<func>
- <name>Module:handle_cast(Msg, State) -> Result</name>
+ <name since="OTP 21.0">Module:handle_cast(Msg, State) -> Result</name>
<fsummary>Handles messages sent by calling
<c>cast/2</c>.</fsummary>
<type>
@@ -355,7 +355,7 @@
</func>
<func>
- <name>Module:handle_msg(Msg, State) -> {ok, State} |
+ <name since="OTP 21.0">Module:handle_msg(Msg, State) -> {ok, State} |
{stop, ChannelId, State}</name>
<fsummary>Handles other messages than SSH connection protocol,
@@ -389,7 +389,7 @@
</func>
<func>
- <name>Module:handle_ssh_msg(Msg, State) -> {ok, State} | {stop,
+ <name since="OTP 21.0">Module:handle_ssh_msg(Msg, State) -> {ok, State} | {stop,
ChannelId, State}</name>
<fsummary>Handles <c>ssh</c> connection protocol messages.</fsummary>
<type>
@@ -416,7 +416,7 @@
</func>
<func>
- <name>Module:terminate(Reason, State) -> _</name>
+ <name since="OTP 21.0">Module:terminate(Reason, State) -> _</name>
<fsummary>Does cleaning up before channel process termination.
</fsummary>
<type>
diff --git a/lib/ssh/doc/src/ssh_client_key_api.xml b/lib/ssh/doc/src/ssh_client_key_api.xml
index bc77756147..9f2f3013e5 100644
--- a/lib/ssh/doc/src/ssh_client_key_api.xml
+++ b/lib/ssh/doc/src/ssh_client_key_api.xml
@@ -29,7 +29,7 @@
<date></date>
<rev></rev>
</header>
- <module>ssh_client_key_api</module>
+ <module since="OTP R16B">ssh_client_key_api</module>
<modulesummary>
-behaviour(ssh_client_key_api).
</modulesummary>
@@ -86,7 +86,7 @@
<funcs>
<func>
- <name>Module:add_host_key(HostNames, PublicHostKey, ConnectOptions) -> ok | {error, Reason}</name>
+ <name since="OTP R16B">Module:add_host_key(HostNames, PublicHostKey, ConnectOptions) -> ok | {error, Reason}</name>
<fsummary>Adds a host key to the set of trusted host keys.</fsummary>
<type>
<v>HostNames = string()</v>
@@ -103,7 +103,7 @@
</func>
<func>
- <name>Module:is_host_key(Key, Host, Algorithm, ConnectOptions) -> Result</name>
+ <name since="OTP R16B">Module:is_host_key(Key, Host, Algorithm, ConnectOptions) -> Result</name>
<fsummary>Checks if a host key is trusted.</fsummary>
<type>
<v>Key = <seealso marker="public_key:public_key#type-public_key">public_key:public_key()</seealso></v>
@@ -125,7 +125,7 @@
</func>
<func>
- <name>Module:user_key(Algorithm, ConnectOptions) ->
+ <name since="OTP R16B">Module:user_key(Algorithm, ConnectOptions) ->
{ok, PrivateKey} | {error, Reason}</name>
<fsummary>Fetches the users <em>public key</em> matching the <c>Algorithm</c>.</fsummary>
<type>
diff --git a/lib/ssh/doc/src/ssh_connection.xml b/lib/ssh/doc/src/ssh_connection.xml
index 8e1cf156a8..2a701929f6 100644
--- a/lib/ssh/doc/src/ssh_connection.xml
+++ b/lib/ssh/doc/src/ssh_connection.xml
@@ -30,7 +30,7 @@
<date></date>
<rev></rev>
</header>
- <module>ssh_connection</module>
+ <module since="">ssh_connection</module>
<modulesummary>
This module provides API functions to send SSH Connection Protocol
events to the other side of an SSH channel.
@@ -201,7 +201,7 @@
<funcs>
<func>
- <name>adjust_window(ConnectionRef, ChannelId, NumOfBytes) -> ok</name>
+ <name since="">adjust_window(ConnectionRef, ChannelId, NumOfBytes) -> ok</name>
<fsummary>Adjusts the SSH flow control window.</fsummary>
<type>
<v>ConnectionRef = connection_ref()</v>
@@ -221,7 +221,7 @@
</func>
<func>
- <name>close(ConnectionRef, ChannelId) -> ok</name>
+ <name since="">close(ConnectionRef, ChannelId) -> ok</name>
<fsummary>Sends a close message on the channel <c>ChannelId</c>.</fsummary>
<type>
<v>ConnectionRef = connection_ref()</v>
@@ -240,7 +240,7 @@
</func>
<func>
- <name>exec(ConnectionRef, ChannelId, Command, TimeOut) -> ssh_request_status() |
+ <name since="">exec(ConnectionRef, ChannelId, Command, TimeOut) -> ssh_request_status() |
{error, reason()}</name>
<fsummary>Requests that the server starts the execution of the given command.</fsummary>
<type>
@@ -284,7 +284,7 @@
</func>
<func>
- <name>exit_status(ConnectionRef, ChannelId, Status) -> ok</name>
+ <name since="">exit_status(ConnectionRef, ChannelId, Status) -> ok</name>
<fsummary>Sends the exit status of a command to the client.</fsummary>
<type>
<v>ConnectionRef = connection_ref() </v>
@@ -298,8 +298,8 @@
</func>
<func>
- <name>ptty_alloc(ConnectionRef, ChannelId, Options) -></name>
- <name>ptty_alloc(ConnectionRef, ChannelId, Options, Timeout) -> > ssh_request_status() |
+ <name since="OTP 17.5">ptty_alloc(ConnectionRef, ChannelId, Options) -></name>
+ <name since="OTP 17.4">ptty_alloc(ConnectionRef, ChannelId, Options, Timeout) -> > ssh_request_status() |
{error, reason()}</name>
<fsummary>Sends an SSH Connection Protocol <c>pty_req</c>,
to allocate a pseudo-terminal.</fsummary>
@@ -339,7 +339,7 @@
</func>
<func>
- <name>reply_request(ConnectionRef, WantReply, Status, ChannelId) -> ok</name>
+ <name since="">reply_request(ConnectionRef, WantReply, Status, ChannelId) -> ok</name>
<fsummary>Sends status replies to requests that want such replies.</fsummary>
<type>
<v>ConnectionRef = connection_ref()</v>
@@ -357,10 +357,10 @@
</func>
<func>
- <name>send(ConnectionRef, ChannelId, Data) -></name>
- <name>send(ConnectionRef, ChannelId, Data, Timeout) -></name>
- <name>send(ConnectionRef, ChannelId, Type, Data) -></name>
- <name>send(ConnectionRef, ChannelId, Type, Data, TimeOut) ->
+ <name since="">send(ConnectionRef, ChannelId, Data) -></name>
+ <name since="">send(ConnectionRef, ChannelId, Data, Timeout) -></name>
+ <name since="">send(ConnectionRef, ChannelId, Type, Data) -></name>
+ <name since="">send(ConnectionRef, ChannelId, Type, Data, TimeOut) ->
ok | {error, timeout} | {error, closed}</name>
<fsummary>Sends channel data.</fsummary>
<type>
@@ -380,7 +380,7 @@
</func>
<func>
- <name>send_eof(ConnectionRef, ChannelId) -> ok | {error, closed}</name>
+ <name since="">send_eof(ConnectionRef, ChannelId) -> ok | {error, closed}</name>
<fsummary>Sends EOF on channel <c>ChannelId</c>.</fsummary>
<type>
<v>ConnectionRef = connection_ref()</v>
@@ -392,8 +392,8 @@
</func>
<func>
- <name>session_channel(ConnectionRef, Timeout) -></name>
- <name>session_channel(ConnectionRef, InitialWindowSize,
+ <name since="">session_channel(ConnectionRef, Timeout) -></name>
+ <name since="">session_channel(ConnectionRef, InitialWindowSize,
MaxPacketSize, Timeout) -> {ok, channel_id()} | {error, reason()}</name>
<fsummary>Opens a channel for an SSH session.</fsummary>
<type>
@@ -410,7 +410,7 @@
</func>
<func>
- <name>setenv(ConnectionRef, ChannelId, Var, Value, TimeOut) -> ssh_request_status() |
+ <name since="">setenv(ConnectionRef, ChannelId, Var, Value, TimeOut) -> ssh_request_status() |
{error, reason()}</name>
<fsummary>Environment variables can be passed to the
shell/command to be started later.</fsummary>
@@ -428,7 +428,7 @@
</func>
<func>
- <name>shell(ConnectionRef, ChannelId) -> ok | failure | {error, closed}
+ <name since="">shell(ConnectionRef, ChannelId) -> ok | failure | {error, closed}
</name>
<fsummary>Requests that the user default shell (typically defined in
/etc/passwd in Unix systems) is to be executed at the server end.</fsummary>
@@ -448,7 +448,7 @@
</func>
<func>
- <name>subsystem(ConnectionRef, ChannelId, Subsystem, Timeout) -> ssh_request_status() |
+ <name since="">subsystem(ConnectionRef, ChannelId, Subsystem, Timeout) -> ssh_request_status() |
{error, reason()}</name>
<fsummary>Requests to execute a predefined subsystem on the server.</fsummary>
<type>
diff --git a/lib/ssh/doc/src/ssh_file.xml b/lib/ssh/doc/src/ssh_file.xml
index 6681d9c306..f1fef09083 100644
--- a/lib/ssh/doc/src/ssh_file.xml
+++ b/lib/ssh/doc/src/ssh_file.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>ssh_file</module>
+ <module since="OTP 21.2">ssh_file</module>
<modulesummary>Default callback module for the client's and server's database operations in the ssh application</modulesummary>
<description>
<p>This module is the default callback handler for the client's and the server's user and host "database" operations.
@@ -169,7 +169,7 @@
<funcs>
<func>
- <name>host_key(Algorithm, DaemonOptions) -> {ok, Key} | {error, Reason}</name>
+ <name since="OTP 21.2">host_key(Algorithm, DaemonOptions) -> {ok, Key} | {error, Reason}</name>
<fsummary></fsummary>
<desc>
<p><strong>Types and description</strong></p>
@@ -195,7 +195,7 @@
</func>
<func>
- <name>is_auth_key(PublicUserKey, User, DaemonOptions) -> Result</name>
+ <name since="OTP 21.2">is_auth_key(PublicUserKey, User, DaemonOptions) -> Result</name>
<fsummary></fsummary>
<desc>
<p><strong>Types and description</strong></p>
@@ -216,7 +216,7 @@
</func>
<func>
- <name>add_host_key(HostNames, PublicHostKey, ConnectOptions) -> ok | {error, Reason}</name>
+ <name since="OTP 21.2">add_host_key(HostNames, PublicHostKey, ConnectOptions) -> ok | {error, Reason}</name>
<fsummary></fsummary>
<desc>
<p><strong>Types and description</strong></p>
@@ -235,7 +235,7 @@
</func>
<func>
- <name>is_host_key(Key, Host, Algorithm, ConnectOptions) -> Result</name>
+ <name since="OTP 21.2">is_host_key(Key, Host, Algorithm, ConnectOptions) -> Result</name>
<fsummary></fsummary>
<desc>
<p><strong>Types and description</strong></p>
@@ -254,7 +254,7 @@
</func>
<func>
- <name>user_key(Algorithm, ConnectOptions) -> {ok, PrivateKey} | {error, Reason}</name>
+ <name since="OTP 21.2">user_key(Algorithm, ConnectOptions) -> {ok, PrivateKey} | {error, Reason}</name>
<fsummary></fsummary>
<desc>
<p><strong>Types and description</strong></p>
diff --git a/lib/ssh/doc/src/ssh_server_channel.xml b/lib/ssh/doc/src/ssh_server_channel.xml
index 31ba9a3231..a4e18bbfbf 100644
--- a/lib/ssh/doc/src/ssh_server_channel.xml
+++ b/lib/ssh/doc/src/ssh_server_channel.xml
@@ -29,7 +29,7 @@
<date></date>
<rev></rev>
</header>
- <module>ssh_server_channel</module>
+ <module since="OTP 21.0">ssh_server_channel</module>
<modulesummary>-behaviour(ssh_server_channel). (Replaces ssh_daemon_channel)
</modulesummary>
<description>
@@ -70,7 +70,7 @@
<funcs>
<func>
- <name>Module:init(Args) -> {ok, State} | {ok, State, timeout()} |
+ <name since="OTP 21.0">Module:init(Args) -> {ok, State} | {ok, State, timeout()} |
{stop, Reason}</name>
<fsummary>Makes necessary initializations and returns the
initial channel state if the initializations succeed.</fsummary>
@@ -93,7 +93,7 @@
</func>
<func>
- <name>Module:handle_msg(Msg, State) -> {ok, State} |
+ <name since="OTP 21.0">Module:handle_msg(Msg, State) -> {ok, State} |
{stop, ChannelId, State}</name>
<fsummary>Handles other messages than SSH connection protocol,
@@ -125,7 +125,7 @@
</func>
<func>
- <name>Module:handle_ssh_msg(Msg, State) -> {ok, State} | {stop,
+ <name since="OTP 21.0">Module:handle_ssh_msg(Msg, State) -> {ok, State} | {stop,
ChannelId, State}</name>
<fsummary>Handles <c>ssh</c> connection protocol messages.</fsummary>
<type>
@@ -152,7 +152,7 @@
</func>
<func>
- <name>Module:terminate(Reason, State) -> _</name>
+ <name since="OTP 21.0">Module:terminate(Reason, State) -> _</name>
<fsummary>Does cleaning up before channel process termination.
</fsummary>
<type>
diff --git a/lib/ssh/doc/src/ssh_server_key_api.xml b/lib/ssh/doc/src/ssh_server_key_api.xml
index e2a31bd5f5..013a788a4a 100644
--- a/lib/ssh/doc/src/ssh_server_key_api.xml
+++ b/lib/ssh/doc/src/ssh_server_key_api.xml
@@ -29,7 +29,7 @@
<date></date>
<rev></rev>
</header>
- <module>ssh_server_key_api</module>
+ <module since="OTP R16B">ssh_server_key_api</module>
<modulesummary>
-behaviour(ssh_server_key_api).
</modulesummary>
@@ -87,7 +87,7 @@
<funcs>
<func>
- <name>Module:host_key(Algorithm, DaemonOptions) ->
+ <name since="OTP R16B">Module:host_key(Algorithm, DaemonOptions) ->
{ok, Key} | {error, Reason}</name>
<fsummary>Fetches the host’s private key.</fsummary>
<type>
@@ -111,7 +111,7 @@
</func>
<func>
- <name>Module:is_auth_key(PublicUserKey, User, DaemonOptions) -> Result</name>
+ <name since="OTP R16B">Module:is_auth_key(PublicUserKey, User, DaemonOptions) -> Result</name>
<fsummary>Checks if the user key is authorized.</fsummary>
<type>
<v>PublicUserKey = <seealso marker="public_key:public_key#type-public_key">public_key:public_key()</seealso></v>
diff --git a/lib/ssh/doc/src/ssh_sftp.xml b/lib/ssh/doc/src/ssh_sftp.xml
index 8c105147d6..c89092798d 100644
--- a/lib/ssh/doc/src/ssh_sftp.xml
+++ b/lib/ssh/doc/src/ssh_sftp.xml
@@ -29,7 +29,7 @@
<rev></rev>
<file>ssh_sftp.sgml</file>
</header>
- <module>ssh_sftp</module>
+ <module since="">ssh_sftp</module>
<modulesummary>SFTP client.</modulesummary>
<description>
<p>This module implements an SSH FTP (SFTP) client. SFTP is a
@@ -82,7 +82,7 @@
<funcs>
<func>
- <name>apread(ChannelPid, Handle, Position, Len) -> {async, N} | {error, reason()}</name>
+ <name since="">apread(ChannelPid, Handle, Position, Len) -> {async, N} | {error, reason()}</name>
<fsummary>Reads asynchronously from an open file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -98,7 +98,7 @@
</func>
<func>
- <name>apwrite(ChannelPid, Handle, Position, Data) -> {async, N} | {error, reason()}</name>
+ <name since="">apwrite(ChannelPid, Handle, Position, Data) -> {async, N} | {error, reason()}</name>
<fsummary>Writes asynchronously to an open file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -116,7 +116,7 @@
</func>
<func>
- <name>aread(ChannelPid, Handle, Len) -> {async, N} | {error, reason()}</name>
+ <name since="">aread(ChannelPid, Handle, Len) -> {async, N} | {error, reason()}</name>
<fsummary>Reads asynchronously from an open file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -137,7 +137,7 @@
</func>
<func>
- <name>awrite(ChannelPid, Handle, Data) -> {async, N} | {error, reason()}</name>
+ <name since="">awrite(ChannelPid, Handle, Data) -> {async, N} | {error, reason()}</name>
<fsummary>Writes asynchronously to an open file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -159,8 +159,8 @@
</func>
<func>
- <name>close(ChannelPid, Handle) -></name>
- <name>close(ChannelPid, Handle, Timeout) -> ok | {error, reason()}</name>
+ <name since="">close(ChannelPid, Handle) -></name>
+ <name since="">close(ChannelPid, Handle, Timeout) -> ok | {error, reason()}</name>
<fsummary>Closes an open handle.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -173,8 +173,8 @@
</func>
<func>
- <name>delete(ChannelPid, Name) -></name>
- <name>delete(ChannelPid, Name, Timeout) -> ok | {error, reason()}</name>
+ <name since="">delete(ChannelPid, Name) -></name>
+ <name since="">delete(ChannelPid, Name, Timeout) -> ok | {error, reason()}</name>
<fsummary>Deletes a file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -188,8 +188,8 @@
</func>
<func>
- <name>del_dir(ChannelPid, Name) -></name>
- <name>del_dir(ChannelPid, Name, Timeout) -> ok | {error, reason()}</name>
+ <name since="">del_dir(ChannelPid, Name) -></name>
+ <name since="">del_dir(ChannelPid, Name, Timeout) -> ok | {error, reason()}</name>
<fsummary>Deletes an empty directory.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -204,8 +204,8 @@
</func>
<func>
- <name>list_dir(ChannelPid, Path) -></name>
- <name>list_dir(ChannelPid, Path, Timeout) -> {ok, Filenames} | {error, reason()}</name>
+ <name since="">list_dir(ChannelPid, Path) -></name>
+ <name since="">list_dir(ChannelPid, Path, Timeout) -> {ok, Filenames} | {error, reason()}</name>
<fsummary>Lists the directory.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -221,8 +221,8 @@
</func>
<func>
- <name>make_dir(ChannelPid, Name) -></name>
- <name>make_dir(ChannelPid, Name, Timeout) -> ok | {error, reason()}</name>
+ <name since="">make_dir(ChannelPid, Name) -></name>
+ <name since="">make_dir(ChannelPid, Name, Timeout) -> ok | {error, reason()}</name>
<fsummary>Creates a directory.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -237,8 +237,8 @@
</func>
<func>
- <name>make_symlink(ChannelPid, Name, Target) -></name>
- <name>make_symlink(ChannelPid, Name, Target, Timeout) -> ok | {error, reason()}</name>
+ <name since="">make_symlink(ChannelPid, Name, Target) -></name>
+ <name since="">make_symlink(ChannelPid, Name, Target, Timeout) -> ok | {error, reason()}</name>
<fsummary>Creates a symbolic link.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -253,8 +253,8 @@
</func>
<func>
- <name>open(ChannelPid, File, Mode) -></name>
- <name>open(ChannelPid, File, Mode, Timeout) -> {ok, Handle} | {error, reason()}</name>
+ <name since="">open(ChannelPid, File, Mode) -></name>
+ <name since="">open(ChannelPid, File, Mode, Timeout) -> {ok, Handle} | {error, reason()}</name>
<fsummary>Opens a file and returns a handle.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -270,8 +270,8 @@
</desc>
</func>
<func>
- <name>opendir(ChannelPid, Path) -></name>
- <name>opendir(ChannelPid, Path, Timeout) -> {ok, Handle} | {error, reason()}</name>
+ <name since="">opendir(ChannelPid, Path) -></name>
+ <name since="">opendir(ChannelPid, Path, Timeout) -> {ok, Handle} | {error, reason()}</name>
<fsummary>Opens a directory and returns a handle.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -285,8 +285,8 @@
</func>
<func>
- <name>open_tar(ChannelPid, Path, Mode) -></name>
- <name>open_tar(ChannelPid, Path, Mode, Timeout) -> {ok, Handle} | {error, reason()}</name>
+ <name since="OTP 17.4">open_tar(ChannelPid, Path, Mode) -></name>
+ <name since="OTP 17.4">open_tar(ChannelPid, Path, Mode, Timeout) -> {ok, Handle} | {error, reason()}</name>
<fsummary>Opens a tar file on the server to which <c>ChannelPid</c>
is connected and returns a handle.</fsummary>
<type>
@@ -339,8 +339,8 @@
</func>
<func>
- <name>position(ChannelPid, Handle, Location) -></name>
- <name>position(ChannelPid, Handle, Location, Timeout) -> {ok, NewPosition | {error, reason()}</name>
+ <name since="">position(ChannelPid, Handle, Location) -></name>
+ <name since="">position(ChannelPid, Handle, Location, Timeout) -> {ok, NewPosition | {error, reason()}</name>
<fsummary>Sets the file position of a file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -384,8 +384,8 @@
</func>
<func>
- <name>pread(ChannelPid, Handle, Position, Len) -></name>
- <name>pread(ChannelPid, Handle, Position, Len, Timeout) -> {ok, Data} | eof | {error, reason()}</name>
+ <name since="">pread(ChannelPid, Handle, Position, Len) -></name>
+ <name since="">pread(ChannelPid, Handle, Position, Len, Timeout) -> {ok, Data} | eof | {error, reason()}</name>
<fsummary>Reads from an open file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -402,8 +402,8 @@
</func>
<func>
- <name>pwrite(ChannelPid, Handle, Position, Data) -> ok</name>
- <name>pwrite(ChannelPid, Handle, Position, Data, Timeout) -> ok | {error, reason()}</name>
+ <name since="">pwrite(ChannelPid, Handle, Position, Data) -> ok</name>
+ <name since="">pwrite(ChannelPid, Handle, Position, Data, Timeout) -> ok | {error, reason()}</name>
<fsummary>Writes to an open file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -419,8 +419,8 @@
</func>
<func>
- <name>read(ChannelPid, Handle, Len) -></name>
- <name>read(ChannelPid, Handle, Len, Timeout) -> {ok, Data} | eof | {error, reason()}</name>
+ <name since="">read(ChannelPid, Handle, Len) -></name>
+ <name since="">read(ChannelPid, Handle, Len, Timeout) -> {ok, Data} | eof | {error, reason()}</name>
<fsummary>Reads from an open file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -441,8 +441,8 @@
</func>
<func>
- <name>read_file(ChannelPid, File) -></name>
- <name>read_file(ChannelPid, File, Timeout) -> {ok, Data} | {error, reason()}</name>
+ <name since="">read_file(ChannelPid, File) -></name>
+ <name since="">read_file(ChannelPid, File, Timeout) -> {ok, Data} | {error, reason()}</name>
<fsummary>Reads a file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -456,8 +456,8 @@
</func>
<func>
- <name>read_file_info(ChannelPid, Name) -></name>
- <name>read_file_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | {error, reason()}</name>
+ <name since="">read_file_info(ChannelPid, Name) -></name>
+ <name since="">read_file_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | {error, reason()}</name>
<fsummary>Gets information about a file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -481,8 +481,8 @@
</func>
<func>
- <name>read_link(ChannelPid, Name) -></name>
- <name>read_link(ChannelPid, Name, Timeout) -> {ok, Target} | {error, reason()}</name>
+ <name since="">read_link(ChannelPid, Name) -></name>
+ <name since="">read_link(ChannelPid, Name, Timeout) -> {ok, Target} | {error, reason()}</name>
<fsummary>Reads symbolic link.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -496,8 +496,8 @@
</func>
<func>
- <name>read_link_info(ChannelPid, Name) -> {ok, FileInfo} | {error, reason()}</name>
- <name>read_link_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | {error, reason()}</name>
+ <name since="">read_link_info(ChannelPid, Name) -> {ok, FileInfo} | {error, reason()}</name>
+ <name since="">read_link_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | {error, reason()}</name>
<fsummary>Gets information about a symbolic link.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -517,8 +517,8 @@
</func>
<func>
- <name>rename(ChannelPid, OldName, NewName) -> </name>
- <name>rename(ChannelPid, OldName, NewName, Timeout) -> ok | {error, reason()}</name>
+ <name since="">rename(ChannelPid, OldName, NewName) -> </name>
+ <name since="">rename(ChannelPid, OldName, NewName, Timeout) -> ok | {error, reason()}</name>
<fsummary>Renames a file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -534,16 +534,16 @@
</func>
<func>
- <name>start_channel(ConnectionRef) -></name>
- <name>start_channel(ConnectionRef, Options) ->
+ <name since="">start_channel(ConnectionRef) -></name>
+ <name since="">start_channel(ConnectionRef, Options) ->
{ok, Pid} | {error, reason()|term()}</name>
- <name>start_channel(Host, Options) -></name>
- <name>start_channel(Host, Port, Options) ->
+ <name since="">start_channel(Host, Options) -></name>
+ <name since="">start_channel(Host, Port, Options) ->
{ok, Pid, ConnectionRef} | {error, reason()|term()}</name>
- <name>start_channel(TcpSocket) -></name>
- <name>start_channel(TcpSocket, Options) ->
+ <name since="">start_channel(TcpSocket) -></name>
+ <name since="">start_channel(TcpSocket, Options) ->
{ok, Pid, ConnectionRef} | {error, reason()|term()}</name>
<fsummary>Starts an SFTP client.</fsummary>
@@ -594,7 +594,7 @@
</func>
<func>
- <name>stop_channel(ChannelPid) -> ok</name>
+ <name since="">stop_channel(ChannelPid) -> ok</name>
<fsummary>Stops the SFTP client channel.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -606,8 +606,8 @@
</func>
<func>
- <name>write(ChannelPid, Handle, Data) -></name>
- <name>write(ChannelPid, Handle, Data, Timeout) -> ok | {error, reason()}</name>
+ <name since="">write(ChannelPid, Handle, Data) -></name>
+ <name since="">write(ChannelPid, Handle, Data, Timeout) -> ok | {error, reason()}</name>
<fsummary>Writes to an open file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -625,8 +625,8 @@
</func>
<func>
- <name>write_file(ChannelPid, File, Iolist) -></name>
- <name>write_file(ChannelPid, File, Iolist, Timeout) -> ok | {error, reason()}</name>
+ <name since="">write_file(ChannelPid, File, Iolist) -></name>
+ <name since="">write_file(ChannelPid, File, Iolist, Timeout) -> ok | {error, reason()}</name>
<fsummary>Writes a file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -641,8 +641,8 @@
</func>
<func>
- <name>write_file_info(ChannelPid, Name, Info) -></name>
- <name>write_file_info(ChannelPid, Name, Info, Timeout) -> ok | {error, reason()}</name>
+ <name since="">write_file_info(ChannelPid, Name, Info) -></name>
+ <name since="">write_file_info(ChannelPid, Name, Info, Timeout) -> ok | {error, reason()}</name>
<fsummary>Writes information for a file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
diff --git a/lib/ssh/doc/src/ssh_sftpd.xml b/lib/ssh/doc/src/ssh_sftpd.xml
index 3b34150e98..ee72784add 100644
--- a/lib/ssh/doc/src/ssh_sftpd.xml
+++ b/lib/ssh/doc/src/ssh_sftpd.xml
@@ -29,7 +29,7 @@
<rev></rev>
<file>ssh_sftpd.sgml</file>
</header>
- <module>ssh_sftpd</module>
+ <module since="">ssh_sftpd</module>
<modulesummary>Specifies the channel process to handle an SFTP subsystem.</modulesummary>
<description>
<p>Specifies a channel process to handle an SFTP subsystem.</p>
@@ -51,7 +51,7 @@
</section>
<funcs>
<func>
- <name>subsystem_spec(Options) -> subsystem_spec()</name>
+ <name since="">subsystem_spec(Options) -> subsystem_spec()</name>
<fsummary>Returns the subsystem specification that allows an SSH daemon to handle the subsystem "sftp".</fsummary>
<type>
<v>Options = [{Option, Value}]</v>
diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl
index 278f6a9780..aa9ba0f9bb 100644
--- a/lib/ssh/src/ssh_sftpd.erl
+++ b/lib/ssh/src/ssh_sftpd.erl
@@ -508,11 +508,8 @@ close_our_file({_,Fd}, FileMod, FS0) ->
FS1.
%%% stat: do the stat
-stat(Vsn, ReqId, Data, State, F) when Vsn =< 3->
- <<?UINT32(BLen), BPath:BLen/binary>> = Data,
- stat(ReqId, unicode:characters_to_list(BPath), State, F);
-stat(Vsn, ReqId, Data, State, F) when Vsn >= 4->
- <<?UINT32(BLen), BPath:BLen/binary, ?UINT32(_Flags)>> = Data,
+stat(Vsn, ReqId, Data, State, F) ->
+ <<?UINT32(BLen), BPath:BLen/binary, _/binary>> = Data,
stat(ReqId, unicode:characters_to_list(BPath), State, F).
fstat(Vsn, ReqId, Data, State) when Vsn =< 3->
diff --git a/lib/ssh/test/ssh_bench_SUITE.erl b/lib/ssh/test/ssh_bench_SUITE.erl
index 764c52b624..2ac4e5636a 100644
--- a/lib/ssh/test/ssh_bench_SUITE.erl
+++ b/lib/ssh/test/ssh_bench_SUITE.erl
@@ -109,11 +109,10 @@ connect(Config) ->
lists:foreach(
fun(KexAlg) ->
PrefAlgs = preferred_algorithms(KexAlg),
- report([{value, measure_connect(Config,
- [{preferred_algorithms,PrefAlgs}])},
- {suite, ?MODULE},
- {name, mk_name(["Connect erlc erld ",KexAlg," [µs]"])}
- ])
+ TimeMicroSec = measure_connect(Config,
+ [{preferred_algorithms,PrefAlgs}]),
+ report(["Connect erlc erld ",KexAlg," [connects per sec]"],
+ 1000000 / TimeMicroSec)
end, KexAlgs).
@@ -130,7 +129,7 @@ measure_connect(Config, Opts) ->
[begin
{Time, {ok,Pid}} = timer:tc(ssh,connect,["localhost", Port, ConnectOptions]),
ssh:close(Pid),
- Time
+ Time % in µs
end || _ <- lists:seq(1,?Nruns)]).
%%%----------------------------------------------------------------
@@ -178,10 +177,6 @@ gen_data(DataSz) ->
<<Data0/binary, Data1/binary>>.
-%% connect_measure(Port, Cipher, Mac, Data, Options) ->
-%% report([{value, 1},
-%% {suite, ?MODULE},
-%% {name, mk_name(["Transfer 1M bytes ",Cipher,"/",Mac," [µs]"])}]);
connect_measure(Port, Cipher, Mac, Data, Options) ->
AES_GCM = {cipher,
[]},
@@ -220,10 +215,8 @@ connect_measure(Port, Cipher, Mac, Data, Options) ->
ssh:close(C),
Time
end || _ <- lists:seq(1,?Nruns)],
-
- report([{value, median(Times)},
- {suite, ?MODULE},
- {name, mk_name(["Transfer 1M bytes ",Cipher,"/",Mac," [µs]"])}]).
+ report(["Transfer ",Cipher,"/",Mac," [Mbyte per sec]"],
+ 1000000 / median(Times)).
send_wait_acc(C, Ch, Data) ->
ssh_connection:send(C, Ch, Data),
@@ -238,12 +231,6 @@ send_wait_acc(C, Ch, Data) ->
%%%
%%%----------------------------------------------------------------
-mk_name(Name) -> [char(C) || C <- lists:concat(Name)].
-
-char($-) -> $_;
-char(C) -> C.
-
-%%%----------------------------------------------------------------
preferred_algorithms(KexAlg) ->
[{kex, [KexAlg]},
{public_key, ['ssh-rsa']},
@@ -265,11 +252,22 @@ median(Data) when is_list(Data) ->
1 ->
lists:nth(N div 2 + 1, SortedData)
end,
- ct:log("median(~p) = ~p",[SortedData,Median]),
+ ct:pal("median(~p) = ~p",[SortedData,Median]),
Median.
+%%%----------------------------------------------------------------
+report(LabelList, Value) ->
+ Label = report_chars(lists:concat(LabelList)),
+ ct:pal("ct_event:notify ~p: ~p", [Label, Value]),
+ ct_event:notify(
+ #event{name = benchmark_data,
+ data = [{suite, ?MODULE},
+ {name, Label},
+ {value, Value}]}).
+
+report_chars(Cs) ->
+ [case C of
+ $- -> $_;
+ _ -> C
+ end || C <- Cs].
-report(Data) ->
- ct:log("EventData = ~p",[Data]),
- ct_event:notify(#event{name = benchmark_data,
- data = Data}).
diff --git a/lib/ssh/test/ssh_compat_SUITE.erl b/lib/ssh/test/ssh_compat_SUITE.erl
index f4eef2dc77..8e82527c6e 100644
--- a/lib/ssh/test/ssh_compat_SUITE.erl
+++ b/lib/ssh/test/ssh_compat_SUITE.erl
@@ -1126,7 +1126,24 @@ prepare_local_directory(ServerRootDir) ->
"chmod 222 unreadable_file",
"exit"].
+
check_local_directory(ServerRootDir) ->
+ TimesToTry = 3, % sleep 0.5, 1, 2 and then 4 secs (7.5s in total)
+ check_local_directory(ServerRootDir, 500, TimesToTry-1).
+
+check_local_directory(ServerRootDir, SleepTime, N) ->
+ case do_check_local_directory(ServerRootDir) of
+ {error,Error} when N>0 ->
+ %% Could be that the erlang side is faster and the docker's operations
+ %% are not yet finalized.
+ %% Sleep for a while and retry a few times:
+ timer:sleep(SleepTime),
+ check_local_directory(ServerRootDir, 2*SleepTime, N-1);
+ Other ->
+ Other
+ end.
+
+do_check_local_directory(ServerRootDir) ->
case lists:sort(ok(file:list_dir(ServerRootDir)) -- [".",".."]) of
["ex_tst1","mydir","tst2"] ->
{ok,Expect} = file:read_file(filename:join(ServerRootDir,"ex_tst1")),
@@ -1161,6 +1178,7 @@ check_local_directory(ServerRootDir) ->
{error,{bad_dir_contents,"/"}}
end.
+
call_sftp_in_docker(Config, ServerIP, ServerPort, Cmnds, UserDir) ->
{DockerIP,DockerPort} = ip_port(Config),
{ok,C} = ssh:connect(DockerIP, DockerPort,
diff --git a/lib/ssh/test/ssh_connection_SUITE.erl b/lib/ssh/test/ssh_connection_SUITE.erl
index 778e4a5fc8..6aa587dc7f 100644
--- a/lib/ssh/test/ssh_connection_SUITE.erl
+++ b/lib/ssh/test/ssh_connection_SUITE.erl
@@ -1124,12 +1124,12 @@ start_our_shell(_User, _Peer) ->
ssh_exec_echo(Cmd) ->
spawn(fun() ->
- io:format("echo "++Cmd ++ "\n")
+ io:format("echo ~s\n", [Cmd])
end).
ssh_exec_echo(Cmd, User) ->
spawn(fun() ->
- io:format(io_lib:format("echo ~s ~s\n",[User,Cmd]))
+ io:format("echo ~s ~s\n",[User,Cmd])
end).
ssh_exec_echo(Cmd, User, _PeerAddr) ->
ssh_exec_echo(Cmd,User).
diff --git a/lib/ssh/test/ssh_trpt_test_lib.erl b/lib/ssh/test/ssh_trpt_test_lib.erl
index 8de550af15..f2c9892f95 100644
--- a/lib/ssh/test/ssh_trpt_test_lib.erl
+++ b/lib/ssh/test/ssh_trpt_test_lib.erl
@@ -41,15 +41,20 @@
opts = [],
timeout = 5000, % ms
seen_hello = false,
- enc = <<>>,
ssh = #ssh{}, % #ssh{}
alg_neg = {undefined,undefined}, % {own_kexinit, peer_kexinit}
alg, % #alg{}
vars = dict:new(),
reply = [], % Some repy msgs are generated hidden in ssh_transport :[
prints = [],
- return_value
- }).
+ return_value,
+
+ %% Packet retrival and decryption
+ decrypted_data_buffer = <<>>,
+ encrypted_data_buffer = <<>>,
+ aead_data = <<>>,
+ undecrypted_packet_length
+ }).
-define(role(S), ((S#s.ssh)#ssh.role) ).
@@ -475,11 +480,11 @@ recv(S0 = #s{}) ->
%%%================================================================
try_find_crlf(Seen, S0) ->
- case erlang:decode_packet(line,S0#s.enc,[]) of
+ case erlang:decode_packet(line,S0#s.encrypted_data_buffer,[]) of
{more,_} ->
- Line = <<Seen/binary,(S0#s.enc)/binary>>,
+ Line = <<Seen/binary,(S0#s.encrypted_data_buffer)/binary>>,
S0#s{seen_hello = {more,Line},
- enc = <<>>, % didn't find a complete line
+ encrypted_data_buffer = <<>>, % didn't find a complete line
% -> no more characters to test
return_value = {more,Line}
};
@@ -490,13 +495,13 @@ try_find_crlf(Seen, S0) ->
S = opt(print_messages, S0,
fun(X) when X==true;X==detail -> {"Recv info~n~p~n",[Line]} end),
S#s{seen_hello = false,
- enc = Rest,
+ encrypted_data_buffer = Rest,
return_value = {info,Line}};
S1=#s{} ->
S = opt(print_messages, S1,
fun(X) when X==true;X==detail -> {"Recv hello~n~p~n",[Line]} end),
S#s{seen_hello = true,
- enc = Rest,
+ encrypted_data_buffer = Rest,
return_value = {hello,Line}}
end
end.
@@ -511,19 +516,73 @@ handle_hello(Bin, S=#s{ssh=C}) ->
{{Vp,Vs}, server} -> S#s{ssh = C#ssh{c_vsn=Vp, c_version=Vs}}
end.
-receive_binary_msg(S0=#s{ssh=C0=#ssh{decrypt_block_size = BlockSize,
+receive_binary_msg(S0=#s{}) ->
+ case ssh_transport:handle_packet_part(
+ S0#s.decrypted_data_buffer,
+ S0#s.encrypted_data_buffer,
+ S0#s.aead_data,
+ S0#s.undecrypted_packet_length,
+ S0#s.ssh)
+ of
+ {packet_decrypted, DecryptedBytes, EncryptedDataRest, Ssh1} ->
+ S1 = S0#s{ssh = Ssh1#ssh{recv_sequence = ssh_transport:next_seqnum(Ssh1#ssh.recv_sequence)},
+ decrypted_data_buffer = <<>>,
+ undecrypted_packet_length = undefined,
+ aead_data = <<>>,
+ encrypted_data_buffer = EncryptedDataRest},
+ case
+ catch ssh_message:decode(set_prefix_if_trouble(DecryptedBytes,S1))
+ of
+ {'EXIT',_} -> fail(decode_failed,S1);
+
+ Msg ->
+ Ssh2 = case Msg of
+ #ssh_msg_kexinit{} ->
+ ssh_transport:key_init(opposite_role(Ssh1), Ssh1, DecryptedBytes);
+ _ ->
+ Ssh1
+ end,
+ S2 = opt(print_messages, S1,
+ fun(X) when X==true;X==detail -> {"Recv~n~s~n",[format_msg(Msg)]} end),
+ S3 = opt(print_messages, S2,
+ fun(detail) -> {"decrypted bytes ~p~n",[DecryptedBytes]} end),
+ S3#s{ssh = inc_recv_seq_num(Ssh2),
+ return_value = Msg
+ }
+ end;
+
+ {get_more, DecryptedBytes, EncryptedDataRest, AeadData, TotalNeeded, Ssh1} ->
+ %% Here we know that there are not enough bytes in
+ %% EncryptedDataRest to use. We must wait for more.
+ Remaining = case TotalNeeded of
+ undefined -> 8;
+ _ -> TotalNeeded - size(DecryptedBytes) - size(EncryptedDataRest)
+ end,
+ receive_binary_msg(
+ receive_wait(Remaining,
+ S0#s{encrypted_data_buffer = EncryptedDataRest,
+ decrypted_data_buffer = DecryptedBytes,
+ undecrypted_packet_length = TotalNeeded,
+ aead_data = AeadData,
+ ssh = Ssh1}
+ ))
+ end.
+
+
+
+old_receive_binary_msg(S0=#s{ssh=C0=#ssh{decrypt_block_size = BlockSize,
recv_mac_size = MacSize
}
}) ->
- case size(S0#s.enc) >= max(8,BlockSize) of
+ case size(S0#s.encrypted_data_buffer) >= max(8,BlockSize) of
false ->
%% Need more bytes to decode the packet_length field
- Remaining = max(8,BlockSize) - size(S0#s.enc),
+ Remaining = max(8,BlockSize) - size(S0#s.encrypted_data_buffer),
receive_binary_msg( receive_wait(Remaining, S0) );
true ->
%% Has enough bytes to decode the packet_length field
{_, <<?UINT32(PacketLen), _/binary>>, _} =
- ssh_transport:decrypt_blocks(S0#s.enc, BlockSize, C0), % FIXME: BlockSize should be at least 4
+ ssh_transport:decrypt_blocks(S0#s.encrypted_data_buffer, BlockSize, C0), % FIXME: BlockSize should be at least 4
%% FIXME: Check that ((4+PacketLen) rem BlockSize) == 0 ?
@@ -534,19 +593,19 @@ receive_binary_msg(S0=#s{ssh=C0=#ssh{decrypt_block_size = BlockSize,
((4+PacketLen) rem BlockSize) =/= 0 ->
fail(bad_packet_length_modulo, S0); % FIXME: disconnect
- size(S0#s.enc) >= (4 + PacketLen + MacSize) ->
+ size(S0#s.encrypted_data_buffer) >= (4 + PacketLen + MacSize) ->
%% has the whole packet
S0;
true ->
%% need more bytes to get have the whole packet
- Remaining = (4 + PacketLen + MacSize) - size(S0#s.enc),
+ Remaining = (4 + PacketLen + MacSize) - size(S0#s.encrypted_data_buffer),
receive_wait(Remaining, S0)
end,
%% Decrypt all, including the packet_length part (re-use the initial #ssh{})
{C1, SshPacket = <<?UINT32(_),?BYTE(PadLen),Tail/binary>>, EncRest} =
- ssh_transport:decrypt_blocks(S1#s.enc, PacketLen+4, C0),
+ ssh_transport:decrypt_blocks(S1#s.encrypted_data_buffer, PacketLen+4, C0),
PayloadLen = PacketLen - 1 - PadLen,
<<CompressedPayload:PayloadLen/binary, _Padding:PadLen/binary>> = Tail,
@@ -573,7 +632,7 @@ receive_binary_msg(S0=#s{ssh=C0=#ssh{decrypt_block_size = BlockSize,
S3 = opt(print_messages, S2,
fun(detail) -> {"decrypted bytes ~p~n",[SshPacket]} end),
S3#s{ssh = inc_recv_seq_num(C3),
- enc = Rest,
+ encrypted_data_buffer = Rest,
return_value = Msg
}
end
@@ -602,7 +661,7 @@ receive_poll(S=#s{socket=Sock}) ->
inet:setopts(Sock, [{active,once}]),
receive
{tcp,Sock,Data} ->
- receive_poll( S#s{enc = <<(S#s.enc)/binary,Data/binary>>} );
+ receive_poll( S#s{encrypted_data_buffer = <<(S#s.encrypted_data_buffer)/binary,Data/binary>>} );
{tcp_closed,Sock} ->
throw({tcp,tcp_closed});
{tcp_error, Sock, Reason} ->
@@ -616,7 +675,7 @@ receive_wait(S=#s{socket=Sock,
inet:setopts(Sock, [{active,once}]),
receive
{tcp,Sock,Data} ->
- S#s{enc = <<(S#s.enc)/binary,Data/binary>>};
+ S#s{encrypted_data_buffer = <<(S#s.encrypted_data_buffer)/binary,Data/binary>>};
{tcp_closed,Sock} ->
throw({tcp,tcp_closed});
{tcp_error, Sock, Reason} ->
@@ -627,11 +686,11 @@ receive_wait(S=#s{socket=Sock,
receive_wait(N, S=#s{socket=Sock,
timeout=Timeout,
- enc=Enc0}) when N>0 ->
+ encrypted_data_buffer=Enc0}) when N>0 ->
inet:setopts(Sock, [{active,once}]),
receive
{tcp,Sock,Data} ->
- receive_wait(N-size(Data), S#s{enc = <<Enc0/binary,Data/binary>>});
+ receive_wait(N-size(Data), S#s{encrypted_data_buffer = <<Enc0/binary,Data/binary>>});
{tcp_closed,Sock} ->
throw({tcp,tcp_closed});
{tcp_error, Sock, Reason} ->
diff --git a/lib/ssl/doc/specs/.gitignore b/lib/ssl/doc/specs/.gitignore
new file mode 100644
index 0000000000..322eebcb06
--- /dev/null
+++ b/lib/ssl/doc/specs/.gitignore
@@ -0,0 +1 @@
+specs_*.xml
diff --git a/lib/ssl/doc/src/Makefile b/lib/ssl/doc/src/Makefile
index c72b6d6cc4..7cf251d8f9 100644
--- a/lib/ssl/doc/src/Makefile
+++ b/lib/ssl/doc/src/Makefile
@@ -80,11 +80,16 @@ HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
+SPECS_FILES = $(XML_REF3_FILES:%.xml=$(SPECDIR)/specs_%.xml)
+
+TOP_SPECS_FILE = specs.xml
+
# ----------------------------------------------------
# FLAGS
# ----------------------------------------------------
XML_FLAGS +=
DVIPS_FLAGS +=
+SPECS_FLAGS = -I../../../public_key/include -I../../../public_key/src -I../../..
# ----------------------------------------------------
# Targets
@@ -92,7 +97,7 @@ DVIPS_FLAGS +=
$(HTMLDIR)/%.gif: %.gif
$(INSTALL_DATA) $< $@
-docs: pdf html man
+docs: html pdf man
$(TOP_PDF_FILE): $(XML_FILES)
@@ -105,6 +110,7 @@ clean clean_docs:
rm -rf $(XMLDIR)
rm -f $(MAN3DIR)/*
rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
+ rm -f $(SPECS_FILES)
rm -f errs core *~
man: $(MAN3_FILES) $(MAN6_FILES)
diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml
index 674e38b054..82eb8ff700 100644
--- a/lib/ssl/doc/src/notes.xml
+++ b/lib/ssl/doc/src/notes.xml
@@ -715,7 +715,7 @@
<p>
TLS sessions must be registered with SNI if provided, so
that sessions where client hostname verification would
- fail can not connect reusing a session created when the
+ fail cannot connect reusing a session created when the
server name verification succeeded.</p>
<p>
Own Id: OTP-14632</p>
@@ -893,7 +893,7 @@
public_key:pkix_verify_hostname/2 to verify the hostname
of the connection with the server certificates specified
hostname during certificate path validation. The user may
- explicitly disables it. Also if the hostname can not be
+ explicitly disables it. Also if the hostname cannot be
derived from the first argument to connect or is not
supplied by the server name indication option, the check
will not be performed.</p>
diff --git a/lib/ssl/doc/src/specs.xml b/lib/ssl/doc/src/specs.xml
new file mode 100644
index 0000000000..50e9428fec
--- /dev/null
+++ b/lib/ssl/doc/src/specs.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<specs xmlns:xi="http://www.w3.org/2001/XInclude">
+ <xi:include href="../specs/specs_ssl_crl_cache_api.xml"/>
+ <xi:include href="../specs/specs_ssl_crl_cache.xml"/>
+ <xi:include href="../specs/specs_ssl_session_cache_api.xml"/>
+ <xi:include href="../specs/specs_ssl.xml"/>
+</specs>
+
+
diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml
index 3029977745..b145aac6ab 100644
--- a/lib/ssl/doc/src/ssl.xml
+++ b/lib/ssl/doc/src/ssl.xml
@@ -28,7 +28,7 @@
<rev></rev>
<file>ssl.xml</file>
</header>
- <module>ssl</module>
+ <module since="">ssl</module>
<modulesummary>Interface Functions for Secure Socket Layer</modulesummary>
<description>
<p>
@@ -37,277 +37,364 @@
<seealso marker="ssl_app">ssl(6)</seealso>.
</p>
</description>
-
- <section>
- <title>DATA TYPES</title>
- <p>The following data types are used in the functions for SSL/TLS/DTLS:</p>
-
- <taglist>
-
- <tag><c>boolean() =</c></tag>
- <item><p><c>true | false</c></p></item>
-
- <tag><c>option() =</c></tag>
- <item><p><c>socketoption() | ssl_option() | transport_option()</c></p>
- </item>
-
- <tag><c>socketoption() =</c></tag>
- <item><p><c>proplists:property()</c></p>
- <p>The default socket options are
- <c>[{mode,list},{packet, 0},{header, 0},{active, true}]</c>.</p>
- <p>For valid options, see the
- <seealso marker="kernel:inet">inet(3)</seealso>,
- <seealso marker="kernel:gen_tcp">gen_tcp(3)</seealso> and
- <seealso marker="kernel:gen_tcp">gen_udp(3)</seealso>
- manual pages
- in Kernel. Note that stream oriented options such as packet are only relevant for SSL/TLS and not DTLS</p></item>
-
- <tag><marker id="type-ssloption"/><c>ssl_option() =</c></tag>
- <item>
- <p><c>{verify, verify_type()}</c></p>
- <p><c>| {verify_fun, {fun(), term()}}</c></p>
- <p><c>| {fail_if_no_peer_cert, boolean()}</c></p>
- <p><c>| {depth, integer()}</c></p>
- <p><c>| {cert, public_key:der_encoded()}</c></p>
- <p><c>| {certfile, path()}</c></p>
- <p><c>| {key, {'RSAPrivateKey'| 'DSAPrivateKey' | 'ECPrivateKey'
- | 'PrivateKeyInfo', public_key:der_encoded()} |
- #{algorithm := rsa | dss | ecdsa,
- engine := crypto:engine_ref(), key_id := crypto:key_id(), password => crypto:password()}</c></p>
- <p><c>| {keyfile, path()}</c></p>
- <p><c>| {password, string()}</c></p>
- <p><c>| {cacerts, [public_key:der_encoded()]}</c></p>
- <p><c>| {cacertfile, path()}</c></p>
- <p><c>| {dh, public_key:der_encoded()}</c></p>
- <p><c>| {dhfile, path()}</c></p>
- <p><c>| {ciphers, ciphers()}</c></p>
- <p><c>| {user_lookup_fun, {fun(), term()}}, {psk_identity, string()},
- {srp_identity, {string(), string()}}</c></p>
- <p><c>| {reuse_sessions, boolean()}</c></p>
- <p><c>| {reuse_session, fun()} {next_protocols_advertised, [binary()]}</c></p>
- <p><c>| {client_preferred_next_protocols, {client | server,
- [binary()]} | {client | server, [binary()], binary()}}</c></p>
- <p><c>| {log_alert, boolean()}</c></p>
- <p><c>| {server_name_indication, hostname() | disable}</c></p>
- <p><c>| {customize_hostname_check, list()}</c></p>
- <p><c>| {sni_hosts, [{hostname(), [ssl_option()]}]}</c></p>
- <p><c>| {sni_fun, SNIfun::fun()}</c></p>
- </item>
-
- <tag><c>transport_option() =</c></tag>
- <item><p><c>{cb_info, {CallbackModule::atom(), DataTag::atom(),
-
- ClosedTag::atom(), ErrTag:atom()}}</c></p>
- <p>Defaults to <c>{gen_tcp, tcp, tcp_closed, tcp_error}</c> for TLS
- and <c>{gen_udp, udp, udp_closed, udp_error}</c> for DTLS. Can be used
- to customize the transport layer. For TLS the callback module must implement a
- reliable transport protocol, behave as <c>gen_tcp</c>, and have functions
- corresponding to <c>inet:setopts/2</c>, <c>inet:getopts/2</c>,
- <c>inet:peername/1</c>, <c>inet:sockname/1</c>, and <c>inet:port/1</c>.
- The callback <c>gen_tcp</c> is treated specially and calls <c>inet</c>
- directly. For DTLS this feature must be considered exprimental.</p>
- <taglist>
- <tag><c>CallbackModule =</c></tag>
- <item><p><c>atom()</c></p></item>
- <tag><c>DataTag =</c></tag>
- <item><p><c>atom()</c></p>
- <p>Used in socket data message.</p></item>
- <tag><c>ClosedTag =</c></tag>
- <item><p><c>atom()</c></p>
- <p>Used in socket close message.</p></item>
- </taglist>
- </item>
-
- <tag><c>verify_type() =</c></tag>
- <item><p><c>verify_none | verify_peer</c></p></item>
-
- <tag><c>path() =</c></tag>
- <item><p><c>string()</c></p>
- <p>Represents a file path.</p></item>
- <tag><c>public_key:der_encoded() =</c></tag>
- <item><p><c>binary()</c></p>
- <p>ASN.1 DER-encoded entity as an Erlang binary.</p></item>
+ <!--
+ ================================================================
+ = Data types =
+ ================================================================
+ -->
- <tag><c>host() =</c></tag>
- <item><p><c>hostname() | ipaddress()</c></p></item>
+ <datatypes>
+ <datatype_title>Types used in SSL/TLS/DTLS</datatype_title>
- <tag><c>hostname() =</c></tag>
- <item><p><c>string() - DNS hostname</c></p></item>
-
- <tag><c>ip_address() =</c></tag>
- <item><p><c>{N1,N2,N3,N4} % IPv4 | {K1,K2,K3,K4,K5,K6,K7,K8} % IPv6
- </c></p></item>
+
+ <datatype>
+ <name name="socket"/>
+ </datatype>
+
+ <datatype>
+ <name name="sslsocket"/>
+ <desc>
+ <p>An opaque reference to the TLS/DTLS connection, may be used for equality matching.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="tls_option"/>
+ </datatype>
+
+ <datatype>
+ <name name="tls_client_option"/>
+ </datatype>
+
+ <datatype>
+ <name name="tls_server_option"/>
+ </datatype>
+
+
+ <datatype>
+ <name name="socket_option"/>
+ <desc>
+ <p>The default socket options are
+ <c>[{mode,list},{packet, 0},{header, 0},{active, true}]</c>.</p>
+ <p>For valid options, see the
+ <seealso marker="kernel:inet">inet(3)</seealso>,
+ <seealso marker="kernel:gen_tcp">gen_tcp(3)</seealso> and
+ <seealso marker="kernel:gen_tcp">gen_udp(3)</seealso>
+ manual pages in Kernel. Note that stream oriented options such as packet
+ are only relevant for SSL/TLS and not DTLS</p>
+ </desc>
+ </datatype>
- <tag><c>sslsocket() =</c></tag>
- <item><p>opaque()</p></item>
-
- <tag><marker id="type-protocol"/><c> protocol_version() =</c></tag>
- <item><p><c> ssl_tls_protocol() | dtls_protocol() </c></p></item>
-
- <item><p><c>sslv3 | tlsv1 | 'tlsv1.1' | 'tlsv1.2'</c></p></item>
-
- <tag><marker id="type-protocol"/><c> dtls_protocol() =</c></tag>
- <item><p><c>'dtlsv1' | 'dtlsv1.2'</c></p></item>
-
- <tag><c>ciphers() =</c></tag>
- <item><p><c>= [ciphersuite()]</c></p>
- <p>Tuples and string formats accepted by versions
- before ssl-8.2.4 will be converted for backwards compatibility</p></item>
-
- <tag><c>ciphersuite() =</c></tag>
- <item><p><c>
- #{key_exchange := key_exchange(),
- cipher := cipher(),
- mac := MAC::hash() | aead,
- prf := PRF::hash() | default_prf} </c></p></item>
-
- <tag><c>key_exchange()=</c></tag>
- <item><p><c>rsa | dhe_dss | dhe_rsa | dh_anon | psk | dhe_psk
- | rsa_psk | srp_anon | srp_dss | srp_rsa | ecdh_anon | ecdh_ecdsa
- | ecdhe_ecdsa | ecdh_rsa | ecdhe_rsa</c></p></item>
-
- <tag><c>cipher() =</c></tag>
- <item><p><c>rc4_128 | des_cbc | '3des_ede_cbc'
- | aes_128_cbc | aes_256_cbc | aes_128_gcm | aes_256_gcm | chacha20_poly1305</c></p></item>
-
- <tag><c>hash() =</c></tag>
- <item><p><c>md5 | sha | sha224 | sha256 | sha348 | sha512</c></p></item>
-
- <tag><c>prf_random() =</c></tag>
- <item><p><c>client_random | server_random</c></p></item>
-
- <tag><c>cipher_filters() =</c></tag>
- <item><p><c> [{key_exchange | cipher | mac | prf, algo_filter()}])</c></p></item>
-
- <tag><c>algo_filter() =</c></tag>
- <item><p>fun(key_exchange() | cipher() | hash() | aead | default_prf) -> true | false </p></item>
-
- <tag><c>srp_param_type() =</c></tag>
- <item><p><c>srp_1024 | srp_1536 | srp_2048 | srp_3072
- | srp_4096 | srp_6144 | srp_8192</c></p></item>
-
- <tag><c>SNIfun::fun()</c></tag>
- <item><p><c>= fun(ServerName :: string()) -> [ssl_option()]</c></p></item>
-
- <tag><c>named_curve() =</c></tag>
- <item><p><c>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</c></p></item>
-
- <tag><c>hello_extensions() =</c></tag>
- <item><p><c>#{renegotiation_info => binary() | undefined,
- signature_algs => [{hash(), ecsda| rsa| dsa}] | undefined
- alpn => binary() | undefined,
- next_protocol_negotiation => binary() | undefined,
- srp => string() | undefined,
- ec_point_formats => list() | undefined,
- elliptic_curves => [oid] | undefined,
- sni => string() | undefined}
- }</c></p></item>
+ <datatype>
+ <name name="active_msgs"/>
+ <desc>
+ <p>When a TLS/DTLS socket is in active mode (the default), data from the
+ socket is delivered to the owner of the socket in the form of
+ messages as described above.</p>
+ <p>The <c>ssl_passive</c> message is sent only when the socket is in
+ <c>{active, N}</c> mode and the counter dropped to 0. It indicates
+ that the socket has transitioned to passive (<c>{active, false}</c>) mode.</p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="transport_option"/>
+ <desc>
+ <p>Defaults to <c>{gen_tcp, tcp, tcp_closed, tcp_error}</c>
+ for TLS and <c>{gen_udp, udp, udp_closed, udp_error}</c> for
+ DTLS. Can be used to customize the transport layer. The tag
+ values should be the values used by the underlying transport
+ in its active mode messages. For TLS the callback module must implement a
+ reliable transport protocol, behave as <c>gen_tcp</c>, and have functions
+ corresponding to <c>inet:setopts/2</c>, <c>inet:getopts/2</c>,
+ <c>inet:peername/1</c>, <c>inet:sockname/1</c>, and <c>inet:port/1</c>.
+ The callback <c>gen_tcp</c> is treated specially and calls <c>inet</c>
+ directly. For DTLS this feature must be considered exprimental.
+ </p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="host"/>
+ </datatype>
+
+ <datatype>
+ <name name="hostname"/>
+ </datatype>
+
+ <datatype>
+ <name name="ip_address"/>
+ </datatype>
+
+ <datatype>
+ <name name="protocol_version"/>
+ </datatype>
+
+ <datatype>
+ <name name="tls_version"/>
+ </datatype>
+
+ <datatype>
+ <name name="dtls_version"/>
+ </datatype>
+
+ <datatype>
+ <name name="legacy_version"/>
+ </datatype>
+
+ <datatype>
+ <name name="prf_random"/>
+ </datatype>
+
+ <datatype>
+ <name name="verify_type"/>
+ </datatype>
+
+ <datatype>
+ <name name="ciphers"/>
+ </datatype>
+
+ <datatype>
+ <name name="erl_cipher_suite"/>
+ </datatype>
+
+ <datatype>
+ <name name="cipher"/>
+ </datatype>
+
+ <datatype>
+ <name name="legacy_cipher"/>
+ </datatype>
+
+ <datatype>
+ <name name="cipher_filters"/>
+ </datatype>
+
+ <datatype>
+ <name name="hash"/>
+ </datatype>
+
+ <datatype>
+ <name name="sha2"/>
+ </datatype>
+
+ <datatype>
+ <name name="legacy_hash"/>
+ </datatype>
+
+ <datatype>
+ <name name="old_cipher_suite"/>
+ </datatype>
+
+ <datatype>
+ <name name="signature_algs"/>
+ </datatype>
+
+ <datatype>
+ <name name="sign_algo"/>
+ </datatype>
+
+ <datatype>
+ <name name="sign_scheme"/>
+ </datatype>
+
+ <datatype>
+ <name name="kex_algo"/>
+ </datatype>
+
+ <datatype>
+ <name name="algo_filter"/>
+ </datatype>
+
+ <datatype>
+ <name name="eccs"/>
+ </datatype>
+
+ <datatype>
+ <name name="named_curve"/>
+ </datatype>
+
+ <datatype>
+ <name name="psk_identity"/>
+ </datatype>
+
+ <datatype>
+ <name name="srp_identity"/>
+ </datatype>
+
+ <datatype>
+ <name name="srp_param_type"/>
+ </datatype>
+
+ <datatype>
+ <name name="app_level_protocol"/>
+ </datatype>
+
+ <datatype>
+ <name name="protocol_extensions"/>
+ </datatype>
+
+ <datatype>
+ <name name="error_alert"/>
+ </datatype>
+
+ <datatype>
+ <name name="tls_alert"/>
+ </datatype>
+
+ <datatype_title>TLS/DTLS OPTION DESCRIPTIONS - COMMON for SERVER and CLIENT</datatype_title>
+
+ <datatype>
+ <name name="common_option"/>
+ </datatype>
+
+ <datatype>
+ <name since="OTP 20" name="protocol"/>
+ <desc>
+ <p>Choose TLS or DTLS protocol for the transport layer security.
+ Defaults to <c>tls</c>. For DTLS other transports than UDP are not yet supported.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="handshake_completion"/>
+ <desc>
+ <p>Defaults to <c>full</c>. If hello is specified the handshake will
+ pause after the hello message and give the user a possibility make decisions
+ based on hello extensions before continuing or aborting the handshake by calling
+ <seealso marker="#handshake_continue-3"> handshake_continue/3</seealso> or
+ <seealso marker="#handshake_cancel-1"> handshake_cancel/1</seealso></p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="cert"/>
+ <desc>
+ <p>The DER-encoded users certificate. If this option
+ is supplied, it overrides option <c>certfile</c>.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="cert_pem"/>
+ <desc>
+ <p>Path to a file containing the user certificate on PEM format.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="key"/>
+ <desc>
+ <p>The DER-encoded user's private key or a map refering to a crypto
+ engine and its key reference that optionally can be password protected,
+ seealso <seealso marker="crypto:crypto#engine_load-4"> crypto:engine_load/4
+ </seealso> and <seealso marker="crypto:engine_load"> Crypto's Users Guide</seealso>. If this option
+ is supplied, it overrides option <c>keyfile</c>.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="key_pem"/>
+ <desc>
+ <p>Path to the file containing the user's
+ private PEM-encoded key. As PEM-files can contain several
+ entries, this option defaults to the same file as given by
+ option <c>certfile</c>.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="key_password"/>
+ <desc>
+ <p>String containing the user's password. Only used if the
+ private keyfile is password-protected.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="cipher_suites"/>
+ <desc>
+ <p>Supported cipher suites. The function
+ <c>cipher_suites/2</c> can be used to find all ciphers that
+ are supported by default. <c>cipher_suites(all, 'tlsv1.2')</c> can be
+ called to find all available cipher suites. Pre-Shared Key
+ (<url href="http://www.ietf.org/rfc/rfc4279.txt">RFC
+ 4279</url> and <url
+ href="http://www.ietf.org/rfc/rfc5487.txt">RFC 5487</url>),
+ Secure Remote Password (<url
+ href="http://www.ietf.org/rfc/rfc5054.txt">RFC 5054</url>),
+ RC4, 3DES, DES cipher suites, and anonymous cipher suites only work if
+ explicitly enabled by this option; they are supported/enabled
+ by the peer also. Anonymous cipher suites are supported for
+ testing purposes only and are not be used when security
+ matters.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="eccs"/>
+ <desc><p> Allows to specify the order of preference for named curves
+ and to restrict their usage when using a cipher suite supporting them.</p>
+ </desc>
+ </datatype>
- </taglist>
- </section>
-
- <section>
- <title>TLS/DTLS OPTION DESCRIPTIONS - COMMON for SERVER and CLIENT</title>
-
- <p>The following options have the same meaning in the client and
- the server:</p>
+ <datatype>
+ <name name="signature_schemes"/>
+ <desc>
+ <p>
+ In addition to the signature_algorithms extension from TLS 1.2,
+ <url href="http://www.ietf.org/rfc/rfc8446.txt#section-4.2.3">TLS 1.3
+ (RFC 5246 Section 4.2.3)</url>adds the signature_algorithms_cert extension
+ which enables having special requirements on the signatures used in the
+ certificates that differs from the requirements on digital signatures as a whole.
+ If this is not required this extension is not needed.
+ </p>
+ <p>
+ The client will send a signature_algorithms_cert extension (ClientHello),
+ if TLS version 1.3 or later is used, and the signature_algs_cert option is
+ explicitly specified. By default, only the signature_algs extension is sent.
+ </p>
+ <p>
+ The signature schemes shall be ordered according to the client's preference
+ (favorite choice first).
+ </p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="secure_renegotiation"/>
+ <desc><p>Specifies if to reject renegotiation attempt that does
+ not live up to <url
+ href="http://www.ietf.org/rfc/rfc5746.txt">RFC 5746</url>. By
+ default <c>secure_renegotiate</c> is set to <c>true</c>, that
+ is, secure renegotiation is enforced. If set to <c>false</c>
+ secure renegotiation will still be used if possible, but it
+ falls back to insecure renegotiation if the peer does not
+ support <url href="http://www.ietf.org/rfc/rfc5746.txt">RFC
+ 5746</url>.</p>
+ </desc>
+ </datatype>
- <taglist>
-
- <tag><c>{protocol, tls | dtls}</c></tag>
- <item><p>Choose TLS or DTLS protocol for the transport layer security.
- Defaults to <c>tls</c> Introduced in OTP 20, DTLS support is considered
- experimental in this release. Other transports than UDP are not yet supported.</p></item>
-
- <tag><c>{handshake, hello | full}</c></tag>
- <item><p> Defaults to <c>full</c>. If hello is specified the handshake will
- pause after the hello message and give the user a possibility make decisions
- based on hello extensions before continuing or aborting the handshake by calling
- <seealso marker="#handshake_continue-3"> handshake_continue/3</seealso> or
- <seealso marker="#handshake_cancel-1"> handshake_cancel/1</seealso>
- </p></item>
-
- <tag><c>{cert, public_key:der_encoded()}</c></tag>
- <item><p>The DER-encoded users certificate. If this option
- is supplied, it overrides option <c>certfile</c>.</p></item>
-
- <tag><c>{certfile, path()}</c></tag>
- <item><p>Path to a file containing the user certificate.</p></item>
-
- <tag>
- <marker id="key_option_def"/>
- <c>{key, {'RSAPrivateKey'| 'DSAPrivateKey' | 'ECPrivateKey'
- |'PrivateKeyInfo', public_key:der_encoded()} | #{algorithm := rsa | dss | ecdsa,
- engine := crypto:engine_ref(), key_id := crypto:key_id(), password => crypto:password()}</c></tag>
- <item><p>The DER-encoded user's private key or a map refering to a crypto
- engine and its key reference that optionally can be password protected,
- seealso <seealso marker="crypto:crypto#engine_load-4"> crypto:engine_load/4
- </seealso> and <seealso marker="crypto:engine_load"> Crypto's Users Guide</seealso>. If this option
- is supplied, it overrides option <c>keyfile</c>.</p></item>
-
- <tag><c>{keyfile, path()}</c></tag>
- <item><p>Path to the file containing the user's
- private PEM-encoded key. As PEM-files can contain several
- entries, this option defaults to the same file as given by
- option <c>certfile</c>.</p></item>
-
- <tag><c>{password, string()}</c></tag>
- <item><p>String containing the user's password. Only used if the
- private keyfile is password-protected.</p></item>
-
- <tag><c>{ciphers, ciphers()}</c></tag>
- <item><p>Supported cipher suites. The function
- <c>cipher_suites/0</c> can be used to find all ciphers that are
- supported by default. <c>cipher_suites(all)</c> can be called
- to find all available cipher suites. Pre-Shared Key
- (<url href="http://www.ietf.org/rfc/rfc4279.txt">RFC 4279</url> and
- <url href="http://www.ietf.org/rfc/rfc5487.txt">RFC 5487</url>),
- Secure Remote Password
- (<url href="http://www.ietf.org/rfc/rfc5054.txt">RFC 5054</url>), RC4 cipher suites,
- and anonymous cipher suites only work if explicitly enabled by
- this option; they are supported/enabled by the peer also.
- Anonymous cipher suites are supported for testing purposes
- only and are not be used when security matters.</p></item>
-
- <tag><c>{eccs, [named_curve()]}</c></tag>
- <item><p> Allows to specify the order of preference for named curves
- and to restrict their usage when using a cipher suite supporting them.
- </p></item>
-
- <tag><c>{secure_renegotiate, boolean()}</c></tag>
- <item><p>Specifies if to reject renegotiation attempt that does
- not live up to
- <url href="http://www.ietf.org/rfc/rfc5746.txt">RFC 5746</url>.
- By default <c>secure_renegotiate</c> is set to <c>true</c>,
- that is, secure renegotiation is enforced. If set to <c>false</c> secure renegotiation
- will still be used if possible,
- but it falls back to insecure renegotiation if the peer
- does not support
- <url href="http://www.ietf.org/rfc/rfc5746.txt">RFC 5746</url>.</p>
- </item>
-
- <tag><c>{depth, integer()}</c></tag>
- <item><p>Maximum number of non-self-issued
+ <datatype>
+ <name name="allowed_cert_chain_length"/>
+ <desc><p>Maximum number of non-self-issued
intermediate certificates that can follow the peer certificate
in a valid certification path. So, if depth is 0 the PEER must
be signed by the trusted ROOT-CA directly; if 1 the path can
be PEER, CA, ROOT-CA; if 2 the path can be PEER, CA, CA,
- ROOT-CA, and so on. The default value is 1.</p></item>
-
- <tag><marker id="verify_fun"/><c>{verify_fun, {Verifyfun :: fun(), InitialUserState ::
- term()}}</c></tag>
- <item><p>The verification fun is to be defined as follows:</p>
+ ROOT-CA, and so on. The default value is 1.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="custom_verify"/>
+ <desc>
+ <p>The verification fun is to be defined as follows:</p>
<code>
-fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom() | {revoked,
-atom()}} |
+fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom() |
+ {revoked, atom()}} |
{extension, #'Extension'{}}, InitialUserState :: term()) ->
{valid, UserState :: term()} | {valid_peer, UserState :: term()} |
{fail, Reason :: term()} | {unknown, UserState :: term()}.
@@ -315,20 +402,21 @@ atom()}} |
<p>The verification fun is called during the X509-path
validation when an error or an extension unknown to the SSL
- application is encountered. It is also called
- when a certificate is considered valid by the path validation
- to allow access to each certificate in the path to the user
- application. It differentiates between the peer
- certificate and the CA certificates by using <c>valid_peer</c> or
- <c>valid</c> as second argument to the verification fun. See the
- <seealso marker="public_key:public_key_records">public_key User's
- Guide</seealso> for definition of <c>#'OTPCertificate'{}</c> and
- <c>#'Extension'{}</c>.</p>
+ application is encountered. It is also called when a
+ certificate is considered valid by the path validation to
+ allow access to each certificate in the path to the user
+ application. It differentiates between the peer certificate
+ and the CA certificates by using <c>valid_peer</c> or
+ <c>valid</c> as second argument to the verification fun. See
+ the <seealso marker="public_key:public_key_records">public_key
+ User's Guide</seealso> for definition of
+ <c>#'OTPCertificate'{}</c> and <c>#'Extension'{}</c>.</p>
<list type="bulleted">
- <item><p>If the verify callback fun returns <c>{fail, Reason}</c>,
- the verification process is immediately stopped, an alert is
- sent to the peer, and the TLS/DTLS handshake terminates.</p></item>
+ <item><p>If the verify callback fun returns <c>{fail,
+ Reason}</c>, the verification process is immediately
+ stopped, an alert is sent to the peer, and the TLS/DTLS
+ handshake terminates.</p></item>
<item><p>If the verify callback fun returns <c>{valid, UserState}</c>,
the verification process continues.</p></item>
<item><p>If the verify callback fun always returns
@@ -378,10 +466,12 @@ atom()}} |
<taglist>
<tag><c>unknown_ca</c></tag>
- <item><p>No trusted CA was found in the trusted store. The trusted CA is
- normally a so called ROOT CA, which is a self-signed certificate. Trust can
- be claimed for an intermediate CA (trusted anchor does not have to be
- self-signed according to X-509) by using option <c>partial_chain</c>.</p>
+ <item><p>No trusted CA was found in the trusted store. The
+ trusted CA is normally a so called ROOT CA, which is a
+ self-signed certificate. Trust can be claimed for an
+ intermediate CA (trusted anchor does not have to be
+ self-signed according to X-509) by using option
+ <c>partial_chain</c>.</p>
</item>
<tag><c>selfsigned_peer</c></tag>
@@ -392,15 +482,17 @@ atom()}} |
marker="public_key:public_key#pkix_path_validation-3">public_key:pkix_path_validation/3</seealso>
</p></item>
</taglist>
- </item>
-
- <tag><c>{crl_check, boolean() | peer | best_effort }</c></tag>
- <item>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="crl_check"/>
+ <desc>
<p>Perform CRL (Certificate Revocation List) verification
<seealso marker="public_key:public_key#pkix_crls_validate-3">
- (public_key:pkix_crls_validate/3)</seealso> on all the certificates during the path validation
- <seealso
- marker="public_key:public_key#pkix_path_validation-3">(public_key:pkix_path_validation/3)
+ (public_key:pkix_crls_validate/3)</seealso> on all the
+ certificates during the path validation <seealso
+ marker="public_key:public_key#pkix_path_validation-3">(public_key:pkix_path_validation/3)
</seealso>
of the certificate chain. Defaults to <c>false</c>.</p>
@@ -409,115 +501,114 @@ marker="public_key:public_key#pkix_path_validation-3">public_key:pkix_path_valid
<item>check is only performed on the peer certificate.</item>
<tag><c>best_effort</c></tag>
- <item>if certificate revocation status can not be determined
+ <item>if certificate revocation status cannot be determined
it will be accepted as valid.</item>
</taglist>
-
+
<p>The CA certificates specified for the connection will be used to
construct the certificate chain validating the CRLs.</p>
<p>The CRLs will be fetched from a local or external cache. See
<seealso marker="ssl:ssl_crl_cache_api">ssl_crl_cache_api(3)</seealso>.</p>
- </item>
-
- <tag><c>{crl_cache, {Module :: atom(), {DbHandle :: internal | term(), Args :: list()}}}</c></tag>
- <item>
- <p>Specify how to perform lookup and caching of certificate revocation lists.
- <c>Module</c> defaults to <seealso marker="ssl:ssl_crl_cache">ssl_crl_cache</seealso>
- with <c> DbHandle </c> being <c>internal</c> and an
- empty argument list.</p>
-
- <p>There are two implementations available:</p>
-
- <taglist>
- <tag><c>ssl_crl_cache</c></tag>
- <item>
- <p>This module maintains a cache of CRLs. CRLs can be
- added to the cache using the function <seealso
- marker="ssl:ssl_crl_cache#insert-1">ssl_crl_cache:insert/1</seealso>,
- and optionally automatically fetched through HTTP if the
- following argument is specified:</p>
-
- <taglist>
- <tag><c>{http, timeout()}</c></tag>
- <item><p>
- Enables fetching of CRLs specified as http URIs in<seealso
- marker="public_key:public_key_records">X509 certificate extensions</seealso>.
- Requires the OTP inets application.</p>
- </item>
- </taglist>
- </item>
-
- <tag><c>ssl_crl_hash_dir</c></tag>
- <item>
- <p>This module makes use of a directory where CRLs are
- stored in files named by the hash of the issuer name.</p>
-
- <p>The file names consist of eight hexadecimal digits
- followed by <c>.rN</c>, where <c>N</c> is an integer,
- e.g. <c>1a2b3c4d.r0</c>. For the first version of the
- CRL, <c>N</c> starts at zero, and for each new version,
- <c>N</c> is incremented by one. The OpenSSL utility
- <c>c_rehash</c> creates symlinks according to this
- pattern.</p>
-
- <p>For a given hash value, this module finds all
- consecutive <c>.r*</c> files starting from zero, and those
- files taken together make up the revocation list. CRL
- files whose <c>nextUpdate</c> fields are in the past, or
- that are issued by a different CA that happens to have the
- same name hash, are excluded.</p>
-
- <p>The following argument is required:</p>
-
- <taglist>
- <tag><c>{dir, string()}</c></tag>
- <item><p>Specifies the directory in which the CRLs can be found.</p></item>
- </taglist>
-
- </item>
-
- <tag><c>max_handshake_size</c></tag>
- <item>
- <p>Integer (24 bits unsigned). Used to limit the size of
- valid TLS handshake packets to avoid DoS attacks.
- Defaults to 256*1024.</p>
- </item>
-
- </taglist>
-
- </item>
+ </desc>
+ </datatype>
- <tag><c>{partial_chain, fun(Chain::[DerCert]) -> {trusted_ca, DerCert} |
- unknown_ca }</c></tag>
- <item><p>Claim an intermediate CA in the chain as trusted. TLS then
- performs <seealso
- marker="public_key:public_key#pkix_path_validation-3">public_key:pkix_path_validation/3</seealso>
- with the selected CA as trusted anchor and the rest of the chain.</p></item>
+ <datatype>
+ <name name="crl_cache_opts"/>
+ <desc>
+ <p>Specify how to perform lookup and caching of certificate revocation lists.
+ <c>Module</c> defaults to <seealso marker="ssl:ssl_crl_cache">ssl_crl_cache</seealso>
+ with <c> DbHandle </c> being <c>internal</c> and an
+ empty argument list.</p>
+
+ <p>There are two implementations available:</p>
+
+ <taglist>
+ <tag><c>ssl_crl_cache</c></tag>
+ <item>
+ <p>This module maintains a cache of CRLs. CRLs can be
+ added to the cache using the function <seealso
+ marker="ssl:ssl_crl_cache#insert-1">ssl_crl_cache:insert/1</seealso>,
+ and optionally automatically fetched through HTTP if the
+ following argument is specified:</p>
+
+ <taglist>
+ <tag><c>{http, timeout()}</c></tag>
+ <item><p>
+ Enables fetching of CRLs specified as http URIs in<seealso
+ marker="public_key:public_key_records">X509 certificate extensions</seealso>.
+ Requires the OTP inets application.</p>
+ </item>
+ </taglist>
+ </item>
+
+ <tag><c>ssl_crl_hash_dir</c></tag>
+ <item>
+ <p>This module makes use of a directory where CRLs are
+ stored in files named by the hash of the issuer name.</p>
+
+ <p>The file names consist of eight hexadecimal digits
+ followed by <c>.rN</c>, where <c>N</c> is an integer,
+ e.g. <c>1a2b3c4d.r0</c>. For the first version of the
+ CRL, <c>N</c> starts at zero, and for each new version,
+ <c>N</c> is incremented by one. The OpenSSL utility
+ <c>c_rehash</c> creates symlinks according to this
+ pattern.</p>
+
+ <p>For a given hash value, this module finds all
+ consecutive <c>.r*</c> files starting from zero, and those
+ files taken together make up the revocation list. CRL
+ files whose <c>nextUpdate</c> fields are in the past, or
+ that are issued by a different CA that happens to have the
+ same name hash, are excluded.</p>
+
+ <p>The following argument is required:</p>
+
+ <taglist>
+ <tag><c>{dir, string()}</c></tag>
+ <item><p>Specifies the directory in which the CRLs can be found.</p></item>
+ </taglist>
+ </item>
+ </taglist>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="root_fun"/>
+ <desc>
+ <code>
+fun(Chain::[public_key:der_encoded()]) ->
+ {trusted_ca, DerCert::public_key:der_encoded()} | unknown_ca}
+ </code>
+ <p>Claim an intermediate CA in the chain as trusted. TLS then
+ performs <seealso
+ marker="public_key:public_key#pkix_path_validation-3">public_key:pkix_path_validation/3</seealso>
+ with the selected CA as trusted anchor and the rest of the chain.</p>
+ </desc>
+ </datatype>
- <tag><c>{versions, [protocol_version()]}</c></tag>
- <item><p>TLS protocol versions supported by started clients and servers.
+ <datatype>
+ <name name="protocol_versions"/>
+ <desc><p>TLS protocol versions supported by started clients and servers.
This option overrides the application environment option
<c>protocol_version</c> and <c>dtls_protocol_version</c>. If the environment option is not set, it defaults
to all versions, except SSL-3.0, supported by the SSL application.
- See also <seealso marker="ssl:ssl_app">ssl(6).</seealso></p></item>
+ See also <seealso marker="ssl:ssl_app">ssl(6).</seealso></p>
+ </desc>
+ </datatype>
- <tag><c>{hibernate_after, integer()|undefined}</c></tag>
- <item><p>When an integer-value is specified, <c>TLS/DTLS-connection</c>
- goes into hibernation after the specified number of milliseconds
- of inactivity, thus reducing its memory footprint. When
- <c>undefined</c> is specified (this is the default), the process
- never goes into hibernation.</p></item>
- <tag><c>{user_lookup_fun, {Lookupfun :: fun(), UserState :: term()}}</c></tag>
- <item><p>The lookup fun is to defined as follows:</p>
+ <datatype>
+ <name name="custom_user_lookup"/>
+ <desc><p>The lookup fun is to defined as follows:</p>
<code>
fun(psk, PSKIdentity ::string(), UserState :: term()) ->
{ok, SharedSecret :: binary()} | error;
fun(srp, Username :: string(), UserState :: term()) ->
- {ok, {SRPParams :: srp_param_type(), Salt :: binary(), DerivedKey :: binary()}} | error.
+ {ok, {SRPParams :: srp_param_type(), Salt :: binary(),
+ DerivedKey :: binary()}} | error.
</code>
<p>For Pre-Shared Key (PSK) cipher suites, the lookup fun is
@@ -533,20 +624,63 @@ fun(srp, Username :: string(), UserState :: term()) ->
<url href="http://tools.ietf.org/html/rfc5054#section-2.4"> RFC 5054</url>:
<c>crypto:sha([Salt, crypto:sha([Username, &lt;&lt;$:&gt;&gt;, Password])])</c>
</p>
- </item>
+ </desc>
+ </datatype>
- <tag><c>{padding_check, boolean()}</c></tag>
- <item><p>Affects TLS-1.0 connections only.
+ <datatype>
+ <name name="session_id"/>
+ <desc>
+ <p>Identifies a TLS session.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="log_alert"/>
+ <desc><p>If set to <c>false</c>, error reports are not displayed.
+ Deprecated in OTP 22, use {log_level, <seealso marker="#type-logging_level">logging_level()</seealso>} instead.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="logging_level"/>
+ <desc><p>Specifies the log level for TLS/DTLS. At verbosity level <c>notice</c> and above error reports are
+ displayed in TLS/DTLS. The level <c>debug</c> triggers verbose logging of TLS/DTLS protocol
+ messages.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="hibernate_after"/>
+ <desc><p>When an integer-value is specified, <c>TLS/DTLS-connection</c>
+ goes into hibernation after the specified number of milliseconds
+ of inactivity, thus reducing its memory footprint. When
+ <c>undefined</c> is specified (this is the default), the process
+ never goes into hibernation.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="handshake_size"/>
+ <desc>
+ <p>Integer (24 bits unsigned). Used to limit the size of
+ valid TLS handshake packets to avoid DoS attacks.
+ Defaults to 256*1024.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="padding_check"/>
+ <desc><p>Affects TLS-1.0 connections only.
If set to <c>false</c>, it disables the block cipher padding check
to be able to interoperate with legacy software.</p>
<warning><p>Using <c>{padding_check, boolean()}</c> makes TLS
vulnerable to the Poodle attack.</p></warning>
- </item>
-
-
+ </desc>
+ </datatype>
- <tag><c>{beast_mitigation, one_n_minus_one | zero_n | disabled}</c></tag>
- <item><p>Affects SSL-3.0 and TLS-1.0 connections only. Used to change the BEAST
+ <datatype>
+ <name name="beast_mitigation"/>
+ <desc><p>Affects SSL-3.0 and TLS-1.0 connections only. Used to change the BEAST
mitigation strategy to interoperate with legacy software.
Defaults to <c>one_n_minus_one</c>.</p>
@@ -556,127 +690,170 @@ fun(srp, Username :: string(), UserState :: term()) ->
<p><c>disabled</c> - Disable BEAST mitigation.</p>
- <warning><p>Using <c>{beast_mitigation, disabled}</c> makes SSL or TLS
+ <warning><p>Using <c>{beast_mitigation, disabled}</c> makes SSL-3.0 or TLS-1.0
vulnerable to the BEAST attack.</p></warning>
- </item>
- </taglist>
-
- </section>
-
- <section>
- <title>TLS/DTLS OPTION DESCRIPTIONS - CLIENT SIDE</title>
-
- <p>The following options are client-specific or have a slightly different
- meaning in the client than in the server:</p>
-
- <taglist>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="ssl_imp"/>
+ <desc><p>Deprecated since OTP-17, has no affect.</p></desc>
+ </datatype>
+
+ <datatype_title>TLS/DTLS OPTION DESCRIPTIONS - CLIENT</datatype_title>
+
+ <datatype>
+ <name name="client_option"/>
+ </datatype>
+
+ <datatype>
+ <name name="client_verify_type"/>
+ <desc><p>In mode <c>verify_none</c> the default behavior is to allow
+ all x509-path validation errors. See also option <seealso marker="#type-custom_verify">verify_fun</seealso>.</p>
+ </desc>
+ </datatype>
- <tag><c>{verify, verify_type()}</c></tag>
- <item><p>In mode <c>verify_none</c> the default behavior is to allow
- all x509-path validation errors. See also option <c>verify_fun</c>.</p>
- </item>
+ <datatype>
+ <name name="client_reuse_session"/>
+ <desc>
+ <p>Reuses a specific session earlier saved with the option
+ <c>{reuse_sessions, save} since OTP-21.3 </c>
+ </p>
+ </desc>
+ </datatype>
- <tag><c>{reuse_sessions, boolean()}</c></tag>
- <item><p>Specifies if the client is to try to reuse sessions
- when possible.</p></item>
+ <datatype>
+ <name name="client_reuse_sessions"/>
+ <desc>
+ <p>When <c>save</c> is specified a new connection will be negotiated
+ and saved for later reuse. The session ID can be fetched with
+ <seealso marker="#connection_information-2">connection_information/2</seealso>
+ and used with the client option <seealso marker="#type-client_reuse_session">reuse_session</seealso>
+ The boolean value true specifies that if possible, automatized session reuse will
+ be performed. If a new session is created, and is unique in regard
+ to previous stored sessions, it will be saved for possible later reuse. Since OTP-21.3</p>
+ </desc>
+ </datatype>
- <tag><c>{cacerts, [public_key:der_encoded()]}</c></tag>
- <item><p>The DER-encoded trusted certificates. If this option
- is supplied it overrides option <c>cacertfile</c>.</p></item>
-
- <tag><c>{cacertfile, path()}</c></tag>
- <item><p>Path to a file containing PEM-encoded CA certificates. The CA
+ <datatype>
+ <name name="client_cacerts"/>
+ <desc>
+ <p>The DER-encoded trusted certificates. If this option
+ is supplied it overrides option <c>cacertfile</c>.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="client_cafile"/>
+ <desc>
+ <p>Path to a file containing PEM-encoded CA certificates. The CA
certificates are used during server authentication and when building the
client certificate chain.</p>
- </item>
-
- <tag><c>{alpn_advertised_protocols, [binary()]}</c></tag>
- <item>
- <p>The list of protocols supported by the client to be sent to the
- server to be used for an Application-Layer Protocol Negotiation (ALPN).
- If the server supports ALPN then it will choose a protocol from this
- list; otherwise it will fail the connection with a "no_application_protocol"
- alert. A server that does not support ALPN will ignore this value.</p>
-
- <p>The list of protocols must not contain an empty binary.</p>
-
- <p>The negotiated protocol can be retrieved using the <c>negotiated_protocol/1</c> function.</p>
- </item>
-
- <tag><c>{client_preferred_next_protocols, {Precedence :: server | client, ClientPrefs :: [binary()]}}</c><br/>
- <c>{client_preferred_next_protocols, {Precedence :: server | client, ClientPrefs :: [binary()], Default :: binary()}}</c></tag>
- <item>
- <p>Indicates that the client is to try to perform Next Protocol
- Negotiation.</p>
-
- <p>If precedence is server, the negotiated protocol is the
- first protocol to be shown on the server advertised list, which is
- also on the client preference list.</p>
-
- <p>If precedence is client, the negotiated protocol is the
- first protocol to be shown on the client preference list, which is
- also on the server advertised list.</p>
-
- <p>If the client does not support any of the server advertised
- protocols or the server does not advertise any protocols, the
- client falls back to the first protocol in its list or to the
- default protocol (if a default is supplied). If the
- server does not support Next Protocol Negotiation, the
- connection terminates if no default protocol is supplied.</p>
- </item>
-
- <tag><c>{psk_identity, string()}</c></tag>
- <item><p>Specifies the identity the client presents to the server.
- The matching secret is found by calling <c>user_lookup_fun</c>.</p>
- </item>
-
- <tag><c>{srp_identity, {Username :: string(), Password :: string()}
- </c></tag>
- <item><p>Specifies the username and password to use to authenticate
- to the server.</p></item>
-
- <tag><c>{server_name_indication, HostName :: hostname()}</c></tag>
- <item><p>Specify the hostname to be used in TLS Server Name Indication extension.
- If not specified it will default to the <c>Host</c> argument of <seealso marker="#connect-3">connect/[3,4]</seealso>
- unless it is of type inet:ipaddress().</p>
- <p>
- The <c>HostName</c> will also be used in the hostname verification of the peer certificate using
- <seealso marker="public_key:public_key#pkix_verify_hostname-2">public_key:pkix_verify_hostname/2</seealso>.
- </p>
- </item>
- <tag><c>{server_name_indication, disable}</c></tag>
- <item>
- <p> Prevents the Server Name Indication extension from being sent and
- disables the hostname verification check
- <seealso marker="public_key:public_key#pkix_verify_hostname-2">public_key:pkix_verify_hostname/2</seealso> </p>
- </item>
-
- <tag><c>{customize_hostname_check, Options::list()}</c></tag>
- <item>
- <p> Customizes the hostname verification of the peer certificate, as different protocols that use
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="client_alpn"/>
+ <desc>
+ <p>The list of protocols supported by the client to be sent to the
+ server to be used for an Application-Layer Protocol Negotiation (ALPN).
+ If the server supports ALPN then it will choose a protocol from this
+ list; otherwise it will fail the connection with a "no_application_protocol"
+ alert. A server that does not support ALPN will ignore this value.</p>
+
+ <p>The list of protocols must not contain an empty binary.</p>
+
+ <p>The negotiated protocol can be retrieved using the <c>negotiated_protocol/1</c> function.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="client_preferred_next_protocols"/>
+ <desc>
+ <p>Indicates that the client is to try to perform Next Protocol
+ Negotiation.</p>
+
+ <p>If precedence is server, the negotiated protocol is the
+ first protocol to be shown on the server advertised list, which is
+ also on the client preference list.</p>
+
+ <p>If precedence is client, the negotiated protocol is the
+ first protocol to be shown on the client preference list, which is
+ also on the server advertised list.</p>
+
+ <p>If the client does not support any of the server advertised
+ protocols or the server does not advertise any protocols, the
+ client falls back to the first protocol in its list or to the
+ default protocol (if a default is supplied). If the
+ server does not support Next Protocol Negotiation, the
+ connection terminates if no default protocol is supplied.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="client_psk_identity"/>
+ <desc>
+ <p>Specifies the identity the client presents to the server.
+ The matching secret is found by calling <c>user_lookup_fun</c></p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="client_srp_identity"/>
+ <desc>
+ <p>Specifies the username and password to use to authenticate
+ to the server.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="sni"/>
+ <desc>
+ <p>Specify the hostname to be used in TLS Server Name Indication extension.
+ If not specified it will default to the <c>Host</c> argument of <seealso marker="#connect-3">connect/[3,4]</seealso>
+ unless it is of type inet:ipaddress().</p>
+ <p>
+ The <c>HostName</c> will also be used in the hostname verification of the peer certificate using
+ <seealso marker="public_key:public_key#pkix_verify_hostname-2">public_key:pkix_verify_hostname/2</seealso>.
+ </p>
+ <p> The special value <c>disable</c> prevents the Server Name Indication extension from being sent and
+ disables the hostname verification check
+ <seealso marker="public_key:public_key#pkix_verify_hostname-2">public_key:pkix_verify_hostname/2</seealso> </p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <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
<seealso marker="public_key:public_key#pkix_verify_hostname-3">public_key:pkix_verify_hostname/3</seealso> </p>
- </item>
-
- <tag><c>{fallback, boolean()}</c></tag>
- <item>
- <p> Send special cipher suite TLS_FALLBACK_SCSV to avoid undesired TLS version downgrade.
- Defaults to false</p>
- <warning><p>Note this option is not needed in normal TLS usage and should not be used
- to implement new clients. But legacy clients that retries connections in the following manner</p>
-
- <p><c> ssl:connect(Host, Port, [...{versions, ['tlsv2', 'tlsv1.1', 'tlsv1', 'sslv3']}])</c></p>
- <p><c> ssl:connect(Host, Port, [...{versions, [tlsv1.1', 'tlsv1', 'sslv3']}, {fallback, true}])</c></p>
- <p><c> ssl:connect(Host, Port, [...{versions, ['tlsv1', 'sslv3']}, {fallback, true}]) </c></p>
- <p><c> ssl:connect(Host, Port, [...{versions, ['sslv3']}, {fallback, true}]) </c></p>
-
- <p>may use it to avoid undesired TLS version downgrade. Note that TLS_FALLBACK_SCSV must also
- be supported by the server for the prevention to work.
- </p></warning>
- </item>
- <tag><marker id="client_signature_algs"/><c>{signature_algs, [{hash(), ecdsa | rsa | dsa}]}</c></tag>
- <item>
- <p>In addition to the algorithms negotiated by the cipher
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="fallback"/>
+ <desc>
+ <p> Send special cipher suite TLS_FALLBACK_SCSV to avoid undesired TLS version downgrade.
+ Defaults to false</p>
+ <warning><p>Note this option is not needed in normal TLS usage and should not be used
+ to implement new clients. But legacy clients that retries connections in the following manner</p>
+
+ <p><c> ssl:connect(Host, Port, [...{versions, ['tlsv2', 'tlsv1.1', 'tlsv1', 'sslv3']}])</c></p>
+ <p><c> ssl:connect(Host, Port, [...{versions, [tlsv1.1', 'tlsv1', 'sslv3']}, {fallback, true}])</c></p>
+ <p><c> ssl:connect(Host, Port, [...{versions, ['tlsv1', 'sslv3']}, {fallback, true}]) </c></p>
+ <p><c> ssl:connect(Host, Port, [...{versions, ['sslv3']}, {fallback, true}]) </c></p>
+
+ <p>may use it to avoid undesired TLS version downgrade. Note that TLS_FALLBACK_SCSV must also
+ be supported by the server for the prevention to work.
+ </p></warning>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="client_signature_algs"/>
+ <desc>
+ <p>In addition to the algorithms negotiated by the cipher
suite used for key exchange, payload encryption, message
authentication and pseudo random calculation, the TLS signature
algorithm extension <url
@@ -707,177 +884,227 @@ fun(srp, Username :: string(), UserState :: term()) ->
Selected signature algorithm can restrict which hash functions
that may be selected. Default support for {md5, rsa} removed in ssl-8.0
</p>
- </item>
- </taglist>
- </section>
-
- <section>
- <title>TLS/DTLS OPTION DESCRIPTIONS - SERVER SIDE</title>
-
- <p>The following options are server-specific or have a slightly different
- meaning in the server than in the client:</p>
-
- <taglist>
-
- <tag><c>{cacerts, [public_key:der_encoded()]}</c></tag>
- <item><p>The DER-encoded trusted certificates. If this option
- is supplied it overrides option <c>cacertfile</c>.</p></item>
+ </desc>
+ </datatype>
- <tag><c>{cacertfile, path()}</c></tag>
- <item><p>Path to a file containing PEM-encoded CA
- certificates. The CA certificates are used to build the server
- certificate chain and for client authentication. The CAs are
- also used in the list of acceptable client CAs passed to the
- client when a certificate is requested. Can be omitted if there
- is no need to verify the client and if there are no
- intermediate CAs for the server certificate.</p></item>
-
- <tag><c>{dh, public_key:der_encoded()}</c></tag>
- <item><p>The DER-encoded Diffie-Hellman parameters. If specified,
- it overrides option <c>dhfile</c>.</p></item>
-
- <tag><c>{dhfile, path()}</c></tag>
- <item><p>Path to a file containing PEM-encoded Diffie Hellman parameters
- to be used by the server if a cipher suite using Diffie Hellman key
- exchange is negotiated. If not specified, default parameters are used.
- </p></item>
-
- <tag><c>{verify, verify_type()}</c></tag>
- <item><p>A server only does x509-path validation in mode <c>verify_peer</c>,
- as it then sends a certificate request to the client
- (this message is not sent if the verify option is <c>verify_none</c>).
- You can then also want to specify option <c>fail_if_no_peer_cert</c>.
- </p></item>
-
- <tag><c>{fail_if_no_peer_cert, boolean()}</c></tag>
- <item><p>Used together with <c>{verify, verify_peer}</c> by an TLS/DTLS server.
- If set to <c>true</c>, the server fails if the client does not have
- a certificate to send, that is, sends an empty certificate. If set to
- <c>false</c>, it fails only if the client sends an invalid
- certificate (an empty certificate is considered valid). Defaults to false.</p>
- </item>
-
- <tag><c>{reuse_sessions, boolean()}</c></tag>
- <item><p>Specifies if the server is to agree to reuse sessions
- when requested by the clients. See also option <c>reuse_session</c>.
- </p></item>
-
- <tag><c>{reuse_session, fun(SuggestedSessionId,
- PeerCert, Compression, CipherSuite) -> boolean()}</c></tag>
- <item><p>Enables the TLS/DTLS server to have a local policy
- for deciding if a session is to be reused or not.
- Meaningful only if <c>reuse_sessions</c> is set to <c>true</c>.
- <c>SuggestedSessionId</c> is a <c>binary()</c>, <c>PeerCert</c> is
- a DER-encoded certificate, <c>Compression</c> is an enumeration integer,
- and <c>CipherSuite</c> is of type <c>ciphersuite()</c>.</p></item>
-
- <tag><c>{alpn_preferred_protocols, [binary()]}</c></tag>
- <item>
- <p>Indicates the server will try to perform Application-Layer
- Protocol Negotiation (ALPN).</p>
-
- <p>The list of protocols is in order of preference. The protocol
- negotiated will be the first in the list that matches one of the
- protocols advertised by the client. If no protocol matches, the
- server will fail the connection with a "no_application_protocol" alert.</p>
-
- <p>The negotiated protocol can be retrieved using the <c>negotiated_protocol/1</c> function.</p>
- </item>
-
- <tag><c>{next_protocols_advertised, Protocols :: [binary()]}</c></tag>
- <item><p>List of protocols to send to the client if the client indicates that
- it supports the Next Protocol extension. The client can select a protocol
- that is not on this list. The list of protocols must not contain an empty
- binary. If the server negotiates a Next Protocol, it can be accessed
- using the <c>negotiated_next_protocol/1</c> method.</p></item>
-
- <tag><c>{psk_identity, string()}</c></tag>
- <item><p>Specifies the server identity hint, which the server presents to
- the client.</p></item>
-
- <tag><c>{log_alert, boolean()}</c></tag>
- <item><p>If set to <c>false</c>, error reports are not displayed.</p></item>
-
- <tag><c>{honor_cipher_order, boolean()}</c></tag>
- <item><p>If set to <c>true</c>, use the server preference for cipher
- selection. If set to <c>false</c> (the default), use the client
- preference.</p></item>
-
- <tag><c>{sni_hosts, [{hostname(), [ssl_option()]}]}</c></tag>
- <item><p>If the server receives a SNI (Server Name Indication) from the client
- matching a host listed in the <c>sni_hosts</c> option, the specific options for
- that host will override previously specified options.
-
- The option <c>sni_fun</c>, and <c>sni_hosts</c> are mutually exclusive.</p></item>
-
- <tag><c>{sni_fun, SNIfun::fun()}</c></tag>
- <item><p>If the server receives a SNI (Server Name Indication) from the client,
- the given function will be called to retrieve <c>[ssl_option()]</c> for the indicated server.
- These options will be merged into predefined <c>[ssl_option()]</c>.
-
- The function should be defined as:
- <c>fun(ServerName :: string()) -> [ssl_option()]</c>
- and can be specified as a fun or as named <c>fun module:function/1</c>
-
- The option <c>sni_fun</c>, and <c>sni_hosts</c> are mutually exclusive.</p></item>
-
- <tag><c>{client_renegotiation, boolean()}</c></tag>
- <item>In protocols that support client-initiated renegotiation, the cost
- of resources of such an operation is higher for the server than the
- client. This can act as a vector for denial of service attacks. The SSL
- application already takes measures to counter-act such attempts,
- but client-initiated renegotiation can be strictly disabled by setting
- this option to <c>false</c>. The default value is <c>true</c>.
- Note that disabling renegotiation can result in long-lived connections
- becoming unusable due to limits on the number of messages the underlying
- cipher suite can encipher.
- </item>
-
- <tag><c>{honor_cipher_order, boolean()}</c></tag>
- <item>If true, use the server's preference for cipher selection. If false
- (the default), use the client's preference.
- </item>
+ <datatype_title>TLS/DTLS OPTION DESCRIPTIONS - SERVER </datatype_title>
+
+
+ <datatype>
+ <name name="server_option"/>
+ </datatype>
+
+ <datatype>
+ <name name="server_cacerts"/>
+ <desc><p>The DER-encoded trusted certificates. If this option
+ is supplied it overrides option <c>cacertfile</c>.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="server_cafile"/>
+ <desc><p>Path to a file containing PEM-encoded CA
+ certificates. The CA certificates are used to build the server
+ certificate chain and for client authentication. The CAs are
+ also used in the list of acceptable client CAs passed to the
+ client when a certificate is requested. Can be omitted if
+ there is no need to verify the client and if there are no
+ intermediate CAs for the server certificate.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="dh_der"/>
+ <desc><p>The DER-encoded Diffie-Hellman parameters. If
+ specified, it overrides option <c>dhfile</c>.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="dh_file"/>
+ <desc><p>Path to a file containing PEM-encoded Diffie Hellman
+ parameters to be used by the server if a cipher suite using
+ Diffie Hellman key exchange is negotiated. If not specified,
+ default parameters are used.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="server_verify_type"/>
+ <desc><p>A server only does x509-path validation in mode
+ <c>verify_peer</c>, as it then sends a certificate request to
+ the client (this message is not sent if the verify option is
+ <c>verify_none</c>). You can then also want to specify option
+ <c>fail_if_no_peer_cert</c>. </p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="fail_if_no_peer_cert"/>
+ <desc><p>Used together with <c>{verify, verify_peer}</c> by an
+ TLS/DTLS server. If set to <c>true</c>, the server fails if
+ the client does not have a certificate to send, that is, sends
+ an empty certificate. If set to <c>false</c>, it fails only if
+ the client sends an invalid certificate (an empty certificate
+ is considered valid). Defaults to false.</p>
+ </desc>
+ </datatype>
- <tag><c>{honor_ecc_order, boolean()}</c></tag>
- <item>If true, use the server's preference for ECC curve selection. If false
- (the default), use the client's preference.
- </item>
-
- <tag><c>{signature_algs, [{hash(), ecdsa | rsa | dsa}]}</c></tag>
- <item><p> The algorithms specified by
- this option will be the ones accepted by the server in a signature algorithm
- negotiation, introduced in TLS-1.2. The algorithms will also be offered to the client if a
- client certificate is requested. For more details see the <seealso marker="#client_signature_algs">corresponding client option</seealso>.
- </p> </item>
-
- </taglist>
- </section>
-
- <section>
- <title>General</title>
+ <datatype>
+ <name name="server_reuse_sessions"/>
+ <desc><p>The boolean value true specifies that the server will
+ agree to reuse sessions. Setting it to false will result in an empty
+ session table, that is no sessions will be reused.
+ See also option <seealso marker="#type-server_reuse_session">reuse_session</seealso>
+ </p>
+ </desc>
+ </datatype>
- <p>When an TLS/DTLS socket is in active mode (the default), data from the
- socket is delivered to the owner of the socket in the form of
- messages:</p>
+ <datatype>
+ <name name="server_reuse_session"/>
+ <desc><p>Enables the TLS/DTLS server to have a local policy
+ for deciding if a session is to be reused or not. Meaningful
+ only if <c>reuse_sessions</c> is set to <c>true</c>.
+ <c>SuggestedSessionId</c> is a <c>binary()</c>,
+ <c>PeerCert</c> is a DER-encoded certificate,
+ <c>Compression</c> is an enumeration integer, and
+ <c>CipherSuite</c> is of type <c>ciphersuite()</c>.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="server_alpn"/>
+ <desc>
+ <p>Indicates the server will try to perform
+ Application-Layer Protocol Negotiation (ALPN).</p>
+
+ <p>The list of protocols is in order of preference. The
+ protocol negotiated will be the first in the list that
+ matches one of the protocols advertised by the client. If no
+ protocol matches, the server will fail the connection with a
+ "no_application_protocol" alert.</p>
+
+ <p>The negotiated protocol can be retrieved using the
+ <c>negotiated_protocol/1</c> function.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="server_next_protocol"/>
+ <desc><p>List of protocols to send to the client if the client
+ indicates that it supports the Next Protocol extension. The
+ client can select a protocol that is not on this list. The
+ list of protocols must not contain an empty binary. If the
+ server negotiates a Next Protocol, it can be accessed using
+ the <c>negotiated_next_protocol/1</c> method.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="server_psk_identity"/>
+ <desc>
+ <p>Specifies the server identity hint, which the server presents to
+ the client.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="honor_cipher_order"/>
+ <desc>
+ <p>If set to <c>true</c>, use the server preference for cipher
+ selection. If set to <c>false</c> (the default), use the client
+ preference.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="sni_hosts"/>
+ <desc><p>If the server receives a SNI (Server Name Indication) from the client
+ matching a host listed in the <c>sni_hosts</c> option, the specific options for
+ that host will override previously specified options.
+
+ The option <c>sni_fun</c>, and <c>sni_hosts</c> are mutually exclusive.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="sni_fun"/>
+ <desc>
+ <p>If the server receives a SNI (Server Name Indication)
+ from the client, the given function will be called to
+ retrieve <seealso marker="#type-server_option">[server_option()] </seealso> for the indicated server.
+ These options will be merged into predefined
+ <seealso marker="#type-server_option">[server_option()] </seealso> list.
+
+ The function should be defined as:
+ fun(ServerName :: string()) -> <seealso marker="#type-server_option">[server_option()] </seealso>
+ and can be specified as a fun or as named <c>fun module:function/1</c>
+
+ The option <c>sni_fun</c>, and <c>sni_hosts</c> are mutually exclusive.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="client_renegotiation"/>
+ <desc><p>In protocols that support client-initiated
+ renegotiation, the cost of resources of such an operation is
+ higher for the server than the client. This can act as a
+ vector for denial of service attacks. The SSL application
+ already takes measures to counter-act such attempts, but
+ client-initiated renegotiation can be strictly disabled by
+ setting this option to <c>false</c>. The default value is
+ <c>true</c>. Note that disabling renegotiation can result in
+ long-lived connections becoming unusable due to limits on the
+ number of messages the underlying cipher suite can
+ encipher.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="honor_cipher_order"/>
+ <desc><p>If true, use the server's preference for cipher
+ selection. If false (the default), use the client's
+ preference.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="honor_ecc_order"/>
+ <desc><p>If true, use the server's preference for ECC curve
+ selection. If false (the default), use the client's
+ preference.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="server_signature_algs"/>
+ <desc><p> The algorithms specified by this option will be the
+ ones accepted by the server in a signature algorithm
+ negotiation, introduced in TLS-1.2. The algorithms will also
+ be offered to the client if a client certificate is
+ requested. For more details see the <seealso
+ marker="#type-client_signature_algs">corresponding client
+ option</seealso>.
+ </p>
+ </desc>
+ </datatype>
+ </datatypes>
- <list type="bulleted">
- <item><p><c>{ssl, Socket, Data}</c></p></item>
- <item><p><c>{ssl_closed, Socket}</c></p></item>
- <item><p><c>{ssl_error, Socket, Reason}</c></p></item>
- </list>
+<!--
+ ================================================================
+ = Function definitions =
+ ================================================================
+-->
- <p>A <c>Timeout</c> argument specifies a time-out in milliseconds. The
- default value for argument <c>Timeout</c> is <c>infinity</c>.</p>
- </section>
-
<funcs>
<func>
- <name>append_cipher_suites(Deferred, Suites) -> ciphers() </name>
+ <name since="OTP 20.3">append_cipher_suites(Deferred, Suites) -> ciphers() </name>
<fsummary></fsummary>
<type>
- <v>Deferred = ciphers() | cipher_filters() </v>
- <v>Suites = ciphers() </v>
+ <v>Deferred = <seealso marker="#type-ciphers">ciphers()</seealso> |
+ <seealso marker="#type-cipher_filters">cipher_filters()</seealso></v>
+ <v>Suites = <seealso marker="#type-ciphers">ciphers()</seealso></v>
</type>
<desc><p>Make <c>Deferred</c> suites become the least preferred
suites, that is put them at the end of the cipher suite list
@@ -889,8 +1116,8 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>cipher_suites() -></name>
- <name>cipher_suites(Type) -> old_ciphers()</name>
+ <name since="OTP R14B">cipher_suites() -></name>
+ <name since="OTP R14B">cipher_suites(Type) -> [old_cipher_suite()]</name>
<fsummary>Returns a list of supported cipher suites.</fsummary>
<type>
<v>Type = erlang | openssl | all</v>
@@ -901,12 +1128,12 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>cipher_suites(Supported, Version) -> ciphers()</name>
+ <name since="OTP 20.3">cipher_suites(Supported, Version) -> ciphers()</name>
<fsummary>Returns a list of all default or
all supported cipher suites.</fsummary>
<type>
<v> Supported = default | all | anonymous </v>
- <v> Version = protocol_version() </v>
+ <v> Version = <seealso marker="#type-protocol_version">protocol_version() </seealso></v>
</type>
<desc><p>Returns all default or all supported (except anonymous),
or all anonymous cipher suites for a
@@ -915,10 +1142,16 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>eccs() -></name>
- <name>eccs(protocol_version()) -> [named_curve()]</name>
+ <name since="OTP 19.2">eccs() -></name>
+ <name since="OTP 19.2">eccs(Version) -> NamedCurves</name>
<fsummary>Returns a list of supported ECCs.</fsummary>
+ <type>
+ <v> Version = <seealso marker="#type-protocol_version">protocol_version() </seealso></v>
+ <v> NamedCurves = <seealso marker="#type-named_curve">[named_curve()] </seealso></v>
+
+ </type>
+
<desc><p>Returns a list of supported ECCs. <c>eccs()</c>
is equivalent to calling <c>eccs(Protocol)</c> with all
supported protocols and then deduplicating the output.</p>
@@ -926,7 +1159,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>clear_pem_cache() -> ok </name>
+ <name since="OTP 17.5">clear_pem_cache() -> ok </name>
<fsummary> Clears the pem cache</fsummary>
<desc><p>PEM files, used by ssl API-functions, are cached. The
@@ -938,61 +1171,68 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>connect(Socket, SslOptions) -> </name>
- <name>connect(Socket, SslOptions, Timeout) -> {ok, SslSocket} | {ok, SslSocket, Ext}
+ <name since="OTP R14B">connect(Socket, Options) -> </name>
+ <name since="">connect(Socket, Options, Timeout) -> {ok, SslSocket} | {ok, SslSocket, Ext}
| {error, Reason}</name>
<fsummary>Upgrades a <c>gen_tcp</c>, or
equivalent, connected socket to an TLS socket.</fsummary>
<type>
- <v>Socket = socket()</v>
- <v>SslOptions = [{handshake, hello| full} | ssl_option()]</v>
- <v>Timeout = integer() | infinity</v>
- <v>SslSocket = sslsocket()</v>
- <v>Ext = hello_extensions()</v>
- <v>Reason = term()</v>
+ <v>Socket = <seealso marker="#type-socket"> socket() </seealso></v>
+ <v>Options = <seealso marker="#type-tls_client_option"> [tls_client_option()] </seealso></v>
+ <v>Timeout = timeout()</v>
+ <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v>
+ <v>Ext = <seealso marker="#type-protocol_extensions">protocol_extensions()</seealso></v>
+ <v>Reason = closed | timeout | <seealso marker="#type-error_alert"> error_alert() </seealso></v>
</type>
<desc><p>Upgrades a <c>gen_tcp</c>, or equivalent,
connected socket to an TLS socket, that is, performs the
client-side TLS handshake.</p>
- <note><p>If the option <c>verify</c> is set to <c>verify_peer</c>
- the option <c>server_name_indication</c> shall also be specified,
- if it is not no Server Name Indication extension will be sent,
- and <seealso marker="public_key:public_key#pkix_verify_hostname-2">public_key:pkix_verify_hostname/2</seealso>
- will be called with the IP-address of the connection as <c>ReferenceID</c>, which is proably not what you want.</p>
+ <note><p>If the option <c>verify</c> is set to
+ <c>verify_peer</c> the option <c>server_name_indication</c>
+ shall also be specified, if it is not no Server Name
+ Indication extension will be sent, and <seealso
+ marker="public_key:public_key#pkix_verify_hostname-2">public_key:pkix_verify_hostname/2</seealso>
+ will be called with the IP-address of the connection as
+ <c>ReferenceID</c>, which is proably not what you want.</p>
</note>
<p> If the option <c>{handshake, hello}</c> is used the
handshake is paused after receiving the server hello message
and the success response is <c>{ok, SslSocket, Ext}</c>
- instead of <c>{ok, SslSocket}</c>. Thereafter the handshake is continued or
- canceled by calling <seealso marker="#handshake_continue-3">
+ instead of <c>{ok, SslSocket}</c>. Thereafter the handshake
+ is continued or canceled by calling <seealso
+ marker="#handshake_continue-3">
<c>handshake_continue/3</c></seealso> or <seealso
- marker="#handshake_cancel-1"><c>handshake_cancel/1</c></seealso>.
+ marker="#handshake_cancel-1"><c>handshake_cancel/1</c></seealso>.
</p>
+ <p> If the option <c>active</c> is set to <c>once</c>, <c>true</c> or an integer value,
+ the process owning the sslsocket will receive messages of type
+ <seealso marker="#type-active_msgs"> active_msgs() </seealso>
+ </p>
</desc>
</func>
<func>
- <name>connect(Host, Port, Options) -></name>
- <name>connect(Host, Port, Options, Timeout) ->
+ <name since="">connect(Host, Port, Options) -></name>
+ <name since="">connect(Host, Port, Options, Timeout) ->
{ok, SslSocket}| {ok, SslSocket, Ext} | {error, Reason}</name>
<fsummary>Opens an TLS/DTLS connection to <c>Host</c>, <c>Port</c>.</fsummary>
<type>
- <v>Host = host()</v>
- <v>Port = integer()</v>
- <v>Options = [option()]</v>
- <v>Timeout = integer() | infinity</v>
- <v>SslSocket = sslsocket()</v>
- <v>Reason = term()</v>
+ <v>Host =<seealso marker="#type-host"> host() </seealso> </v>
+ <v>Port = <seealso marker="kernel:inet#type-port_number">inet:port_number()</seealso></v>
+ <v>Options = <seealso marker="#type-tls_client_option"> [tls_client_option()]</seealso></v>
+ <v>Timeout = timeout()</v>
+ <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v>
+ <v>Reason = closed | timeout | <seealso marker="#type-error_alert"> error_alert() </seealso></v>
</type>
<desc><p>Opens an TLS/DTLS connection to <c>Host</c>, <c>Port</c>.</p>
<p> When the option <c>verify</c> is set to <c>verify_peer</c> the check
<seealso marker="public_key:public_key#pkix_verify_hostname-2">public_key:pkix_verify_hostname/2</seealso>
will be performed in addition to the usual x509-path validation checks. If the check fails the error {bad_cert, hostname_check_failed} will
- be propagated to the path validation fun <seealso marker="#verify_fun">verify_fun</seealso>, where it is possible to do customized
+ be propagated to the path validation fun <seealso marker="#type-custom_verify">verify_fun</seealso>, where it is possible to do customized
checks by using the full possibilities of the <seealso marker="public_key:public_key#pkix_verify_hostname-3">public_key:pkix_verify_hostname/3</seealso> API.
When the option <c>server_name_indication</c> is provided, its value (the DNS name) will be used as <c>ReferenceID</c>
@@ -1014,14 +1254,19 @@ fun(srp, Username :: string(), UserState :: term()) ->
<c>handshake_continue/3</c></seealso> or <seealso
marker="#handshake_cancel-1"><c>handshake_cancel/1</c></seealso>.
</p>
+
+ <p> If the option <c>active</c> is set to <c>once</c>, <c>true</c> or an integer value,
+ the process owning the sslsocket will receive messages of type
+ <seealso marker="#type-active_msgs"> active_msgs() </seealso>
+ </p>
</desc>
</func>
<func>
- <name>close(SslSocket) -> ok | {error, Reason}</name>
+ <name since="">close(SslSocket) -> ok | {error, Reason}</name>
<fsummary>Closes an TLS/DTLS connection.</fsummary>
<type>
- <v>SslSocket = sslsocket()</v>
+ <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v>
<v>Reason = term()</v>
</type>
<desc><p>Closes an TLS/DTLS connection.</p>
@@ -1029,10 +1274,10 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>close(SslSocket, How) -> ok | {ok, port()} | {error, Reason}</name>
+ <name since="OTP 18.1">close(SslSocket, How) -> ok | {ok, port()} | {error, Reason}</name>
<fsummary>Closes an TLS connection.</fsummary>
<type>
- <v>SslSocket = sslsocket()</v>
+ <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v>
<v>How = timeout() | {NewController::pid(), timeout()} </v>
<v>Reason = term()</v>
</type>
@@ -1044,12 +1289,12 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>controlling_process(SslSocket, NewOwner) ->
+ <name since="">controlling_process(SslSocket, NewOwner) ->
ok | {error, Reason}</name>
<fsummary>Assigns a new controlling process to the
TLS/DTLS socket.</fsummary>
<type>
- <v>SslSocket = sslsocket()</v>
+ <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v>
<v>NewOwner = pid()</v>
<v>Reason = term()</v>
</type>
@@ -1060,12 +1305,12 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>connection_information(SslSocket) ->
+ <name since="OTP 18.0">connection_information(SslSocket) ->
{ok, Result} | {error, Reason} </name>
<fsummary>Returns all the connection information.
</fsummary>
<type>
- <v>SslSocket = sslsocket()</v>
+ <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v>
<v>Item = protocol | selected_cipher_suite | sni_hostname | ecc | session_id | atom()</v>
<d>Meaningful atoms, not specified above, are the ssl option names.</d>
<v>Result = [{Item::atom(), Value::term()}]</v>
@@ -1081,12 +1326,12 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>connection_information(SslSocket, Items) ->
+ <name since="OTP 18.0">connection_information(SslSocket, Items) ->
{ok, Result} | {error, Reason} </name>
<fsummary>Returns the requested connection information.
</fsummary>
<type>
- <v>SslSocket = sslsocket()</v>
+ <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v>
<v>Items = [Item]</v>
<v>Item = protocol | cipher_suite | sni_hostname | ecc | session_id | client_random
| server_random | master_secret | atom()</v>
@@ -1103,11 +1348,11 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>filter_cipher_suites(Suites, Filters) -> ciphers()</name>
+ <name since="OTP 20.3">filter_cipher_suites(Suites, Filters) -> ciphers()</name>
<fsummary></fsummary>
<type>
- <v> Suites = ciphers()</v>
- <v> Filters = cipher_filters()</v>
+ <v> Suites = <seealso marker="#type-ciphers"> ciphers() </seealso></v>
+ <v> Filters = <seealso marker="#type-cipher_filters"> cipher_filters() </seealso></v>
</type>
<desc><p>Removes cipher suites if any of the filter functions
returns false for any part of the cipher suite. This function
@@ -1118,7 +1363,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>format_error(Reason) -> string()</name>
+ <name since="">format_error(Reason) -> string()</name>
<fsummary>Returns an error string.</fsummary>
<type>
<v>Reason = term()</v>
@@ -1129,11 +1374,11 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>getopts(SslSocket, OptionNames) ->
+ <name since="">getopts(SslSocket, OptionNames) ->
{ok, [socketoption()]} | {error, Reason}</name>
<fsummary>Gets the values of the specified options.</fsummary>
<type>
- <v>Socket = sslsocket()</v>
+ <v>Socket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v>
<v>OptionNames = [atom()]</v>
</type>
<desc>
@@ -1143,13 +1388,13 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>getstat(SslSocket) ->
+ <name since="OTP 19.0">getstat(SslSocket) ->
{ok, OptionValues} | {error, inet:posix()}</name>
- <name>getstat(SslSocket, OptionNames) ->
+ <name since="OTP 19.0">getstat(SslSocket, OptionNames) ->
{ok, OptionValues} | {error, inet:posix()}</name>
<fsummary>Get one or more statistic options for a socket</fsummary>
<type>
- <v>SslSocket = sslsocket()</v>
+ <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v>
<v>OptionNames = [atom()]</v>
<v>OptionValues = [{inet:stat_option(), integer()}]</v>
</type>
@@ -1160,31 +1405,36 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>handshake(HsSocket) -> </name>
- <name>handshake(HsSocket, Timeout) -> {ok, SslSocket} | {error, Reason}</name>
+ <name since="OTP 21.0">handshake(HsSocket) -> </name>
+ <name since="OTP 21.0">handshake(HsSocket, Timeout) -> {ok, SslSocket} | {error, Reason}</name>
<fsummary>Performs server-side SSL/TLS handshake.</fsummary>
<type>
- <v>HsSocket = SslSocket = sslsocket()</v>
- <v>Timeout = integer()</v>
- <v>Reason = term()</v>
+ <v>HsSocket = SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v>
+ <v>Timeout = timeout()</v>
+ <v>Reason = closed | timeout | <seealso marker="#type-error_alert"> error_alert() </seealso></v>
</type>
<desc>
<p>Performs the SSL/TLS/DTLS server-side handshake.</p>
<p>Returns a new TLS/DTLS socket if the handshake is successful.</p>
+
+ <p> If the option <c>active</c> is set to <c>once</c>, <c>true</c> or an integer value,
+ the process owning the sslsocket will receive messages of type
+ <seealso marker="#type-active_msgs"> active_msgs() </seealso>
+ </p>
</desc>
</func>
<func>
- <name>handshake(Socket, SslOptions) -> </name>
- <name>handshake(Socket, SslOptions, Timeout) -> {ok, SslSocket} | {ok, SslSocket, Ext} | {error, Reason}</name>
+ <name since="OTP 21.0">handshake(Socket, Options) -> </name>
+ <name since="OTP 21.0">handshake(Socket, Options, Timeout) -> {ok, SslSocket} | {ok, SslSocket, Ext} | {error, Reason}</name>
<fsummary>Performs server-side SSL/TLS/DTLS handshake.</fsummary>
<type>
- <v>Socket = socket() | sslsocket() </v>
- <v>SslSocket = sslsocket() </v>
- <v>Ext = hello_extensions()</v>
- <v>SslOptions = [{handshake, hello| full} | ssl_option()]</v>
- <v>Timeout = integer()</v>
- <v>Reason = term()</v>
+ <v>Socket = socket() | <seealso marker="#type-sslsocket"> socket() </seealso> </v>
+ <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso> </v>
+ <v>Ext = <seealso marker="#type-protocol_extensions">protocol_extensions()</seealso></v>
+ <v>Options = <seealso marker="#type-tls_server_option"> [server_option()] </seealso> </v>
+ <v>Timeout = timeout()</v>
+ <v>Reason = closed | timeout | <seealso marker="#type-error_alert"> error_alert() </seealso></v>
</type>
<desc>
<p>If <c>Socket</c> is a ordinary <c>socket()</c>: upgrades a <c>gen_tcp</c>,
@@ -1196,7 +1446,8 @@ fun(srp, Username :: string(), UserState :: term()) ->
is undefined.
</p></warning>
- <p>If <c>Socket</c> is an <c>sslsocket()</c>: provides extra SSL/TLS/DTLS
+ <p>If <c>Socket</c> is an
+ <seealso marker="#type-sslsocket"> sslsocket() </seealso>: provides extra SSL/TLS/DTLS
options to those specified in
<seealso marker="#listen-2">listen/2 </seealso> and then performs
the SSL/TLS/DTLS handshake. Returns a new TLS/DTLS socket if the handshake is successful.</p>
@@ -1210,14 +1461,20 @@ fun(srp, Username :: string(), UserState :: term()) ->
<c>handshake_continue/3</c></seealso> or <seealso
marker="#handshake_cancel-1"><c>handshake_cancel/1</c></seealso>.
</p>
+
+ <p> If the option <c>active</c> is set to <c>once</c>, <c>true</c> or an integer value,
+ the process owning the sslsocket will receive messages of type
+ <seealso marker="#type-active_msgs"> active_msgs() </seealso>
+ </p>
+
</desc>
</func>
<func>
- <name>handshake_cancel(SslSocket) -> ok </name>
+ <name since="OTP 21.0">handshake_cancel(SslSocket) -> ok </name>
<fsummary>Cancel handshake with a fatal alert</fsummary>
<type>
- <v>SslSocket = sslsocket()</v>
+ <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v>
</type>
<desc>
<p>Cancel the handshake with a fatal <c>USER_CANCELED</c> alert.</p>
@@ -1225,14 +1482,14 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>handshake_continue(HsSocket, SSLOptions) -> {ok, SslSocket} | {error, Reason}</name>
- <name>handshake_continue(HsSocket, SSLOptions, Timeout) -> {ok, SslSocket} | {error, Reason}</name>
+ <name since="OTP 21.0">handshake_continue(HsSocket, Options) -> {ok, SslSocket} | {error, Reason}</name>
+ <name since="OTP 21.0">handshake_continue(HsSocket, Options, Timeout) -> {ok, SslSocket} | {error, Reason}</name>
<fsummary>Continue the SSL/TLS handshake.</fsummary>
<type>
- <v>HsSocket = SslSocket = sslsocket()</v>
- <v>SslOptions = [ssl_option()]</v>
- <v>Timeout = integer()</v>
- <v>Reason = term()</v>
+ <v>HsSocket = SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v>
+ <v>Options = <seealso marker="#type-tls_option"> tls_option() </seealso> </v>
+ <v>Timeout = timeout()</v>
+ <v>Reason = closed | timeout | <seealso marker="#type-error_alert"> error_alert() </seealso></v>
</type>
<desc>
<p>Continue the SSL/TLS handshake possiby with new, additional or changed options.</p>
@@ -1240,13 +1497,13 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>listen(Port, Options) ->
+ <name since="">listen(Port, Options) ->
{ok, ListenSocket} | {error, Reason}</name>
<fsummary>Creates an SSL listen socket.</fsummary>
<type>
- <v>Port = integer()</v>
- <v>Options = options()</v>
- <v>ListenSocket = sslsocket()</v>
+ <v>Port = <seealso marker="kernel:inet#type-port_number">inet:port_number()</seealso></v>
+ <v>Options = <seealso marker="#type-tls_server_option"> [server_option()] </seealso></v>
+ <v>ListenSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v>
</type>
<desc>
<p>Creates an SSL listen socket.</p>
@@ -1254,10 +1511,10 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>negotiated_protocol(SslSocket) -> {ok, Protocol} | {error, protocol_not_negotiated}</name>
+ <name since="OTP 18.0">negotiated_protocol(SslSocket) -> {ok, Protocol} | {error, protocol_not_negotiated}</name>
<fsummary>Returns the protocol negotiated through ALPN or NPN extensions.</fsummary>
<type>
- <v>SslSocket = sslsocket()</v>
+ <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v>
<v>Protocol = binary()</v>
</type>
<desc>
@@ -1268,10 +1525,10 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>peercert(SslSocket) -> {ok, Cert} | {error, Reason}</name>
+ <name since="">peercert(SslSocket) -> {ok, Cert} | {error, Reason}</name>
<fsummary>Returns the peer certificate.</fsummary>
<type>
- <v>SslSocket = sslsocket()</v>
+ <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v>
<v>Cert = binary()</v>
</type>
<desc>
@@ -1283,13 +1540,13 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>peername(SslSocket) -> {ok, {Address, Port}} |
+ <name since="">peername(SslSocket) -> {ok, {Address, Port}} |
{error, Reason}</name>
<fsummary>Returns the peer address and port.</fsummary>
<type>
- <v>SslSocket = sslsocket()</v>
+ <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v>
<v>Address = ipaddress()</v>
- <v>Port = integer()</v>
+ <v>Port = <seealso marker="kernel:inet#type-port_number">inet:port_number()</seealso></v>
</type>
<desc>
<p>Returns the address and port number of the peer.</p>
@@ -1297,11 +1554,12 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>prepend_cipher_suites(Preferred, Suites) -> ciphers()</name>
+ <name since="OTP 20.3">prepend_cipher_suites(Preferred, Suites) -> ciphers()</name>
<fsummary></fsummary>
<type>
- <v>Preferred = ciphers() | cipher_filters() </v>
- <v>Suites = ciphers() </v>
+ <v>Preferred = <seealso marker="#type-ciphers">ciphers()</seealso> |
+ <seealso marker="#type-cipher_filters">cipher_filters()</seealso></v>
+ <v>Suites = <seealso marker="#type-ciphers">ciphers()</seealso></v>
</type>
<desc><p>Make <c>Preferred</c> suites become the most preferred
suites that is put them at the head of the cipher suite list
@@ -1313,13 +1571,13 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>prf(Socket, Secret, Label, Seed, WantedLength) -> {ok, binary()} | {error, reason()}</name>
+ <name since="OTP R15B01">prf(Socket, Secret, Label, Seed, WantedLength) -> {ok, binary()} | {error, reason()}</name>
<fsummary>Uses a session Pseudo-Random Function to generate key material.</fsummary>
<type>
- <v>Socket = sslsocket()</v>
+ <v>Socket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v>
<v>Secret = binary() | master_secret</v>
<v>Label = binary()</v>
- <v>Seed = [binary() | prf_random()]</v>
+ <v>Seed = [binary() | <seealso marker="#type-prf_random"> prf_random()</seealso>]</v>
<v>WantedLength = non_neg_integer()</v>
</type>
<desc>
@@ -1333,14 +1591,14 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>recv(SslSocket, Length) -> </name>
- <name>recv(SslSocket, Length, Timeout) -> {ok, Data} | {error,
+ <name since="">recv(SslSocket, Length) -> </name>
+ <name since="">recv(SslSocket, Length, Timeout) -> {ok, Data} | {error,
Reason}</name>
<fsummary>Receives data on a socket.</fsummary>
<type>
- <v>SslSocket = sslsocket()</v>
+ <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v>
<v>Length = integer()</v>
- <v>Timeout = integer()</v>
+ <v>Timeout = timeout()</v>
<v>Data = [char()] | binary()</v>
</type>
<desc>
@@ -1360,10 +1618,10 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>renegotiate(SslSocket) -> ok | {error, Reason}</name>
+ <name since="OTP R14B">renegotiate(SslSocket) -> ok | {error, Reason}</name>
<fsummary>Initiates a new handshake.</fsummary>
<type>
- <v>SslSocket = sslsocket()</v>
+ <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v>
</type>
<desc><p>Initiates a new handshake. A notable return value is
<c>{error, renegotiation_rejected}</c> indicating that the peer
@@ -1373,10 +1631,10 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>send(SslSocket, Data) -> ok | {error, Reason}</name>
+ <name since="">send(SslSocket, Data) -> ok | {error, Reason}</name>
<fsummary>Writes data to a socket.</fsummary>
<type>
- <v>SslSocket = sslsocket()</v>
+ <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v>
<v>Data = iodata()</v>
</type>
<desc>
@@ -1387,11 +1645,11 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>setopts(SslSocket, Options) -> ok | {error, Reason}</name>
+ <name since="">setopts(SslSocket, Options) -> ok | {error, Reason}</name>
<fsummary>Sets socket options.</fsummary>
<type>
- <v>SslSocket = sslsocket()</v>
- <v>Options = [socketoption]()</v>
+ <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v>
+ <v>Options = <seealso marker="#type-socket_option"> [socket_option()] </seealso></v>
</type>
<desc>
<p>Sets options according to <c>Options</c> for socket
@@ -1400,10 +1658,21 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>shutdown(SslSocket, How) -> ok | {error, Reason}</name>
+ <name since="OTP 22.0">set_log_level(Level) -> ok | {error, Reason}</name>
+ <fsummary>Sets log level for the SSL application.</fsummary>
+ <type>
+ <v>Level = atom()</v>
+ </type>
+ <desc>
+ <p>Sets log level for the SSL application.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="OTP R14B">shutdown(SslSocket, How) -> ok | {error, Reason}</name>
<fsummary>Immediately closes a socket.</fsummary>
<type>
- <v>SslSocket = sslsocket()</v>
+ <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v>
<v>How = read | write | read_write</v>
<v>Reason = reason()</v>
</type>
@@ -1418,13 +1687,13 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>ssl_accept(SslSocket) -> </name>
- <name>ssl_accept(SslSocket, Timeout) -> ok | {error, Reason}</name>
+ <name since="">ssl_accept(SslSocket) -> </name>
+ <name since="">ssl_accept(SslSocket, Timeout) -> ok | {error, Reason}</name>
<fsummary>Performs server-side SSL/TLS handshake.</fsummary>
<type>
- <v>SslSocket = sslsocket()</v>
- <v>Timeout = integer()</v>
- <v>Reason = term()</v>
+ <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v>
+ <v>Timeout = timeout()</v>
+ <v>Reason = closed | timeout | <seealso marker="#type-error_alert"> error_alert() </seealso></v>
</type>
<desc>
<p>Deprecated in OTP 21, use <seealso marker="#handshake-1">handshake/[1,2]</seealso> instead.</p>
@@ -1433,14 +1702,14 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>ssl_accept(Socket, SslOptions) -> </name>
- <name>ssl_accept(Socket, SslOptions, Timeout) -> {ok, Socket} | ok | {error, Reason}</name>
+ <name since="">ssl_accept(Socket, Options) -> </name>
+ <name since="OTP R14B">ssl_accept(Socket, Options, Timeout) -> {ok, Socket} | ok | {error, Reason}</name>
<fsummary>Performs server-side SSL/TLS/DTLS handshake.</fsummary>
<type>
- <v>Socket = socket() | sslsocket() </v>
- <v>SslOptions = [ssl_option()]</v>
- <v>Timeout = integer()</v>
- <v>Reason = term()</v>
+ <v>Socket = socket() | <seealso marker="#type-sslsocket"> sslsocket() </seealso> </v>
+ <v>Options = <seealso marker="#type-tls_server_option"> [server_option()] </seealso> </v>
+ <v>Timeout = timeout()</v>
+ <v>Reason = closed | timeout | <seealso marker="#type-error_alert"> error_alert() </seealso></v>
</type>
<desc>
<p>Deprecated in OTP 21, use <seealso marker="#handshake-3">handshake/[2,3]</seealso> instead.</p>
@@ -1449,13 +1718,13 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>sockname(SslSocket) -> {ok, {Address, Port}} |
+ <name since="">sockname(SslSocket) -> {ok, {Address, Port}} |
{error, Reason}</name>
<fsummary>Returns the local address and port.</fsummary>
<type>
- <v>SslSocket = sslsocket()</v>
- <v>Address = ipaddress()</v>
- <v>Port = integer()</v>
+ <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v>
+ <v>Address = <seealso marker="#type-ip_address">ip_address()</seealso></v>
+ <v>Port = <seealso marker="kernel:inet#type-port_number">inet:port_number()</seealso></v>
</type>
<desc>
<p>Returns the local address and port number of socket
@@ -1464,8 +1733,8 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>start() -> </name>
- <name>start(Type) -> ok | {error, Reason}</name>
+ <name since="OTP R14B">start() -> </name>
+ <name since="OTP R14B">start(Type) -> ok | {error, Reason}</name>
<fsummary>Starts the SSL application.</fsummary>
<type>
<v>Type = permanent | transient | temporary</v>
@@ -1477,7 +1746,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>stop() -> ok </name>
+ <name since="OTP R14B">stop() -> ok </name>
<fsummary>Stops the SSL application.</fsummary>
<desc>
<p>Stops the SSL application.</p>
@@ -1485,10 +1754,10 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>suite_to_str(CipherSuite) -> String</name>
+ <name since="OTP 21.0">suite_to_str(CipherSuite) -> String</name>
<fsummary>Returns the string representation of a cipher suite.</fsummary>
<type>
- <v>CipherSuite = erl_cipher_suite()</v>
+ <v>CipherSuite = <seealso marker="#type-erl_cipher_suite"> erl_cipher_suite() </seealso></v>
<v>String = string()</v>
</type>
<desc>
@@ -1497,14 +1766,14 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>transport_accept(ListenSocket) -></name>
- <name>transport_accept(ListenSocket, Timeout) ->
+ <name since="">transport_accept(ListenSocket) -></name>
+ <name since="">transport_accept(ListenSocket, Timeout) ->
{ok, SslSocket} | {error, Reason}</name>
<fsummary>Accepts an incoming connection and
prepares for <c>ssl_accept</c>.</fsummary>
<type>
- <v>ListenSocket = SslSocket = sslsocket()</v>
- <v>Timeout = integer()</v>
+ <v>ListenSocket = SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v>
+ <v>Timeout = timeout()</v>
<v>Reason = reason()</v>
</type>
<desc>
@@ -1532,7 +1801,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>versions() -> [versions_info()]</name>
+ <name since="OTP R14B">versions() -> [versions_info()]</name>
<fsummary>Returns version information relevant for the
SSL application.</fsummary>
<type>
diff --git a/lib/ssl/doc/src/ssl_crl_cache.xml b/lib/ssl/doc/src/ssl_crl_cache.xml
index 71c6d5e49e..a33aec62a7 100644
--- a/lib/ssl/doc/src/ssl_crl_cache.xml
+++ b/lib/ssl/doc/src/ssl_crl_cache.xml
@@ -24,7 +24,7 @@
<file>ssl_crl_cache.xml</file>
</header>
- <module>ssl_crl_cache</module>
+ <module since="OTP 18.0">ssl_crl_cache</module>
<modulesummary>CRL cache </modulesummary>
<description>
<p>
@@ -34,32 +34,43 @@
the following functions are available.
</p>
</description>
+
+ <datatypes>
+ <datatype_title>DATA TYPES</datatype_title>
+
+ <datatype>
+ <name name="crl_src"/>
+ </datatype>
+
+ <datatype>
+ <name name="uri"/>
+ </datatype>
+
+ </datatypes>
<funcs>
<func>
- <name>delete(Entries) -> ok | {error, Reason} </name>
+ <name since="OTP 18.0">delete(Entries) -> ok | {error, Reason} </name>
<fsummary> </fsummary>
<type>
- <v> Entries = <seealso marker="stdlib:uri_string">uri_string:uri_string()</seealso> | {file, string()} | {der, [<seealso
- marker="public_key:public_key"> public_key:der_encoded() </seealso>]}</v>
- <v> Reason = term()</v>
+ <v> Entries = <seealso marker="#type-crl_src">crl_src()</seealso>]}</v>
+ <v> Reason = crl_reason()</v>
</type>
<desc>
<p>Delete CRLs from the ssl applications local cache. </p>
</desc>
</func>
<func>
- <name>insert(CRLSrc) -> ok | {error, Reason}</name>
- <name>insert(URI, CRLSrc) -> ok | {error, Reason}</name>
+ <name since="OTP 18.0">insert(CRLSrc) -> ok | {error, Reason}</name>
+ <name since="OTP 18.0">insert(URI, CRLSrc) -> ok | {error, Reason}</name>
<fsummary> </fsummary>
<type>
- <v> CRLSrc = {file, string()} | {der, [ <seealso
- marker="public_key:public_key"> public_key:der_encoded() </seealso> ]}</v>
- <v> URI = <seealso marker="stdlib:uri_string">uri_string:uri_string() </seealso> </v>
+ <v> CRLSrc = <seealso marker="#type-crl_src">crl_src()</seealso>]}</v>
+ <v> URI = <seealso marker="#type-uri">uri()</seealso> </v>
<v> Reason = term()</v>
</type>
<desc>
- <p>Insert CRLs into the ssl applications local cache. </p>
+ <p>Insert CRLs, available to fetch on DER format from <c>URI</c>, into the ssl applications local cache. </p>
</desc>
</func>
</funcs>
diff --git a/lib/ssl/doc/src/ssl_crl_cache_api.xml b/lib/ssl/doc/src/ssl_crl_cache_api.xml
index c6774b4df6..4cba4e1de1 100644
--- a/lib/ssl/doc/src/ssl_crl_cache_api.xml
+++ b/lib/ssl/doc/src/ssl_crl_cache_api.xml
@@ -24,7 +24,7 @@
<file>ssl_crl_cache_api.xml</file>
</header>
- <module>ssl_crl_cache_api</module>
+ <module since="OTP 18.0">ssl_crl_cache_api</module>
<modulesummary>API for a SSL/TLS CRL (Certificate Revocation List) cache.</modulesummary>
<description>
<p>
@@ -39,35 +39,44 @@
a CRL cache.
</p>
</description>
-
- <section>
- <title>DATA TYPES</title>
-
- <p>The following data types are used in the functions below:
- </p>
-
- <taglist>
-
- <tag><c>cache_ref() =</c></tag>
- <item>opaque()</item>
- <tag><c>dist_point() =</c></tag>
- <item><p>#'DistributionPoint'{} see <seealso
- marker="public_key:public_key_records"> X509 certificates records</seealso></p></item>
-
- </taglist>
+
+
+ <!--
+ ================================================================
+ = Data types =
+ ================================================================
+ -->
+
+ <datatypes>
- </section>
+ <datatype>
+ <name name="crl_cache_ref"/>
+ <desc>
+ <p>Reference to the CRL cache.</p>
+ </desc>
+ </datatype>
+
+
+ <datatype>
+ <name name="dist_point"/>
+ <desc>
+ <p>For description see <seealso
+ marker="public_key:public_key_records"> X509 certificates records</seealso></p>
+ </desc>
+ </datatype>
+ </datatypes>
+
<funcs>
<func>
- <name>fresh_crl(DistributionPoint, CRL) -> FreshCRL</name>
+ <name since="OTP 18.0">fresh_crl(DistributionPoint, CRL) -> FreshCRL</name>
<fsummary> <c>fun fresh_crl/2 </c> will be used as input option <c>update_crl</c> to
public_key:pkix_crls_validate/3 </fsummary>
<type>
- <v> DistributionPoint = dist_point() </v>
+ <v> DistributionPoint = <seealso marker="#type-dist_point"> dist_point() </seealso> </v>
<v> CRL = [<seealso
- marker="public_key:public_key">public_key:der_encoded()</seealso>] </v>
+ marker="public_key:public_key#type-der_encoded">public_key:der_encoded()</seealso>] </v>
<v> FreshCRL = [<seealso
- marker="public_key:public_key">public_key:der_encoded()</seealso>] </v>
+ marker="public_key:public_key#type-der_encoded">public_key:der_encoded()</seealso>] </v>
</type>
<desc>
<p> <c>fun fresh_crl/2 </c> will be used as input option <c>update_crl</c> to
@@ -76,16 +85,16 @@
</func>
<func>
- <name>lookup(DistributionPoint, Issuer, DbHandle) -> not_available | CRLs </name>
- <name>lookup(DistributionPoint, DbHandle) -> not_available | CRLs </name>
+ <name since="OTP 19.0">lookup(DistributionPoint, Issuer, DbHandle) -> not_available | CRLs </name>
+ <name since="OTP 18.0">lookup(DistributionPoint, DbHandle) -> not_available | CRLs </name>
<fsummary> </fsummary>
<type>
- <v> DistributionPoint = dist_point() </v>
+ <v> DistributionPoint = <seealso marker="#type-dist_point"> dist_point() </seealso> </v>
<v> Issuer = <seealso
- marker="public_key:public_key">public_key:issuer_name()</seealso> </v>
- <v> DbHandle = cache_ref() </v>
+ marker="public_key:public_key#type-issuer_name">public_key:issuer_name()</seealso> </v>
+ <v> DbHandle = <seealso marker="#type-crl_cache_ref"> crl_cache_ref() </seealso></v>
<v> CRLs = [<seealso
- marker="public_key:public_key">public_key:der_encoded()</seealso>] </v>
+ marker="public_key:public_key#type-der_encoded">public_key:der_encoded()</seealso>] </v>
</type>
<desc> <p>Lookup the CRLs belonging to the distribution point <c> Distributionpoint</c>.
This function may choose to only look in the cache or to follow distribution point
@@ -106,12 +115,12 @@
</func>
<func>
- <name>select(Issuer, DbHandle) -> CRLs </name>
+ <name since="OTP 18.0">select(Issuer, DbHandle) -> CRLs </name>
<fsummary>Select the CRLs in the cache that are issued by <c>Issuer</c></fsummary>
<type>
<v> Issuer = <seealso
- marker="public_key:public_key">public_key:issuer_name()</seealso></v>
- <v> DbHandle = cache_ref() </v>
+ marker="public_key:public_key#type-issuer_name">public_key:issuer_name()</seealso></v>
+ <v> DbHandle = <seealso marker="#type-crl_cache_ref"> cache_ref() </seealso></v>
</type>
<desc>
<p>Select the CRLs in the cache that are issued by <c>Issuer</c> </p>
diff --git a/lib/ssl/doc/src/ssl_distribution.xml b/lib/ssl/doc/src/ssl_distribution.xml
index e14f3f90dc..1774bd8f77 100644
--- a/lib/ssl/doc/src/ssl_distribution.xml
+++ b/lib/ssl/doc/src/ssl_distribution.xml
@@ -191,7 +191,7 @@ Eshell V5.0 (abort with ^G)
Any available SSL/TLS option can be specified in an options file,
but note that options that take a <c>fun()</c> has to use
the syntax <c>fun Mod:Func/Arity</c> since a function
- body can not be compiled when consulting a file.
+ body cannot be compiled when consulting a file.
</p>
<p>
Do not tamper with the socket options
diff --git a/lib/ssl/doc/src/ssl_session_cache_api.xml b/lib/ssl/doc/src/ssl_session_cache_api.xml
index a84a3dfce9..e841729e57 100644
--- a/lib/ssl/doc/src/ssl_session_cache_api.xml
+++ b/lib/ssl/doc/src/ssl_session_cache_api.xml
@@ -28,7 +28,7 @@
<rev></rev>
<file>ssl_session_cache_api.xml</file>
</header>
- <module>ssl_session_cache_api</module>
+ <module since="OTP R14B">ssl_session_cache_api</module>
<modulesummary>TLS session cache API</modulesummary>
<description>
@@ -38,39 +38,50 @@
defining a new callback module implementing this API.
</p>
</description>
- <section>
- <title>DATA TYPES</title>
- <p>The following data types are used in the functions for
- <c>ssl_session_cache_api</c>:</p>
-
- <taglist>
- <tag><c>cache_ref() =</c></tag>
- <item><p><c>opaque()</c></p></item>
-
- <tag><c>key() =</c></tag>
- <item><p><c>{partialkey(), session_id()}</c></p></item>
-
- <tag><c>partialkey() =</c></tag>
- <item><p><c>opaque()</c></p></item>
-
- <tag><c>session_id() =</c></tag>
- <item><p><c>binary()</c></p></item>
-
- <tag><c>session()</c> =</tag>
- <item><p><c>opaque()</c></p></item>
- </taglist>
-
- </section>
+ <!--
+ ================================================================
+ = Data types =
+ ================================================================
+ -->
+
+ <datatypes>
+
+ <datatype>
+ <name name="session_cache_ref"/>
+ </datatype>
+
+ <datatype>
+ <name name="session_cache_key"/>
+ <desc>
+ <p>A key to an entry in the session cache.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="partial_key"/>
+ <desc>
+ <p>The opaque part of the key. Does not need to be handled
+ by the callback.</p>
+ </desc>
+ </datatype>
+
+ <datatype>
+ <name name="session"/>
+ <desc>
+ <p>The session data that is stored for each session.</p>
+ </desc>
+ </datatype>
+ </datatypes>
<funcs>
<func>
- <name>delete(Cache, Key) -> _</name>
+ <name since="OTP R14B">delete(Cache, Key) -> _</name>
<fsummary>Deletes a cache entry.</fsummary>
<type>
- <v>Cache = cache_ref()</v>
- <v>Key = key()</v>
+ <v>Cache = <seealso marker="#type-session_cache_ref"> session_cache_ref() </seealso></v>
+ <v>Key = <seealso marker="#type-session_cache_key">session_cache_key() </seealso> </v>
</type>
<desc>
<p>Deletes a cache entry. Is only called from the cache
@@ -80,10 +91,12 @@
</func>
<func>
- <name>foldl(Fun, Acc0, Cache) -> Acc</name>
+ <name since="OTP R14B">foldl(Fun, Acc0, Cache) -> Acc</name>
<fsummary></fsummary>
<type>
- <v></v>
+ <v>Fun = fun()</v>
+ <v>Acc0 = Acc = term()</v>
+ <v>Cache = <seealso marker="#type-session_cache_ref"> session_cache_ref() </seealso></v>
</type>
<desc>
<p>Calls <c>Fun(Elem, AccIn)</c> on successive elements of the
@@ -96,10 +109,11 @@
</func>
<func>
- <name>init(Args) -> opaque() </name>
+ <name since="OTP 18.0">init(Args) -> Cache </name>
<fsummary>Returns cache reference.</fsummary>
<type>
- <v>Args = proplists:proplist()</v>
+ <v>Cache = <seealso marker="#type-session_cache_ref"> session_cache_ref() </seealso></v>
+ <v>Args = <seealso marker="stdlib:proplists#type-proplist">proplists:proplist()</seealso></v>
</type>
<desc>
<p>Includes property <c>{role, client | server}</c>.
@@ -121,12 +135,12 @@
</func>
<func>
- <name>lookup(Cache, Key) -> Entry</name>
+ <name since="OTP R14B">lookup(Cache, Key) -> Entry</name>
<fsummary>Looks up a cache entry.</fsummary>
<type>
- <v>Cache = cache_ref()</v>
- <v>Key = key()</v>
- <v>Entry = session() | undefined</v>
+ <v>Cache = <seealso marker="#type-session_cache_ref"> session_cache_ref() </seealso></v>
+ <v>Key = <seealso marker="#type-session_cache_key">session_cache_key()</seealso> </v>
+ <v>Session = <seealso marker="#type-session">session()</seealso> | undefined</v>
</type>
<desc>
<p>Looks up a cache entry. Is to be callable from any
@@ -136,12 +150,12 @@
</func>
<func>
- <name>select_session(Cache, PartialKey) -> [session()]</name>
+ <name since="OTP R14B">select_session(Cache, PartialKey) -> [Session]</name>
<fsummary>Selects sessions that can be reused.</fsummary>
<type>
- <v>Cache = cache_ref()</v>
- <v>PartialKey = partialkey()</v>
- <v>Session = session()</v>
+ <v>Cache = <seealso marker="#type-session_cache_ref"> session_cache_ref() </seealso></v>
+ <v>PartialKey = <seealso marker="#type-partial_key"> partial_key() </seealso></v>
+ <v>Session = <seealso marker="#type-session">session()</seealso></v>
</type>
<desc>
<p>Selects sessions that can be reused. Is to be callable
@@ -151,10 +165,10 @@
</func>
<func>
- <name>size(Cache) -> integer()</name>
+ <name since="OTP 19.3">size(Cache) -> integer()</name>
<fsummary>Returns the number of sessions in the cache.</fsummary>
<type>
- <v>Cache = cache_ref()</v>
+ <v>Cache = <seealso marker="#type-session_cache_ref"> session_cache_ref() </seealso></v>
</type>
<desc>
<p>Returns the number of sessions in the cache. If size
@@ -166,11 +180,12 @@
</func>
<func>
- <name>terminate(Cache) -> _</name>
+ <name since="OTP R14B">terminate(Cache) -> _</name>
<fsummary>Called by the process that handles the cache when it
is about to terminate.</fsummary>
<type>
- <v>Cache = term() - as returned by init/0</v>
+ <v>Cache = <seealso marker="#type-session_cache_ref"> session_cache_ref() </seealso></v>
+ <d>As returned by init/0</d>
</type>
<desc>
<p>Takes care of possible cleanup that is needed when the
@@ -180,12 +195,12 @@
</func>
<func>
- <name>update(Cache, Key, Session) -> _</name>
+ <name since="OTP R14B">update(Cache, Key, Session) -> _</name>
<fsummary>Caches a new session or updates an already cached one.</fsummary>
<type>
- <v>Cache = cache_ref()</v>
- <v>Key = key()</v>
- <v>Session = session()</v>
+ <v>Cache = <seealso marker="#type-session_cache_ref"> session_cache_ref() </seealso></v>
+ <v>Key = <seealso marker="#type-session_cache_key">session_cache_key()</seealso> </v>
+ <v>Session = <seealso marker="#type-session">session()</seealso></v>
</type>
<desc>
<p>Caches a new session or updates an already cached one. Is
diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile
index 8d1341f594..8dc76f2638 100644
--- a/lib/ssl/src/Makefile
+++ b/lib/ssl/src/Makefile
@@ -39,60 +39,80 @@ RELSYSDIR = $(RELEASE_PATH)/lib/ssl-$(VSN)
# ----------------------------------------------------
BEHAVIOUR_MODULES= \
- ssl_session_cache_api \
- ssl_crl_cache_api
+ ssl_crl_cache_api \
+ ssl_session_cache_api
+
MODULES= \
- ssl \
- ssl_alert \
- ssl_app \
- ssl_sup \
- ssl_admin_sup\
- tls_connection_sup \
- ssl_connection_sup \
- ssl_listen_tracker_sup\
+ dtls_connection \
dtls_connection_sup \
- dtls_packet_demux \
+ dtls_handshake \
dtls_listener_sup \
- ssl_dist_sup\
- ssl_dist_admin_sup\
- ssl_dist_connection_sup\
+ dtls_packet_demux \
+ dtls_record \
+ dtls_socket \
+ dtls_v1 \
inet_tls_dist \
inet6_tls_dist \
- ssl_certificate\
- ssl_pkix_db\
+ ssl \
+ ssl_admin_sup \
+ ssl_alert \
+ ssl_app \
+ ssl_certificate \
ssl_cipher \
ssl_cipher_format \
- ssl_srp_primes \
- tls_connection \
- dtls_connection \
- tls_sender\
ssl_config \
ssl_connection \
- tls_handshake \
- dtls_handshake\
- ssl_handshake\
- ssl_manager \
- ssl_session \
- ssl_session_cache \
- ssl_pem_cache \
- ssl_crl\
+ ssl_connection_sup \
+ ssl_crl \
ssl_crl_cache \
ssl_crl_hash_dir \
- tls_socket \
- dtls_socket \
- tls_record \
- dtls_record \
+ ssl_dh_groups \
+ ssl_dist_admin_sup \
+ ssl_dist_connection_sup \
+ ssl_dist_sup \
+ ssl_handshake \
+ ssl_listen_tracker_sup \
+ ssl_logger \
+ ssl_manager \
+ ssl_pem_cache \
+ ssl_pkix_db \
ssl_record \
+ ssl_session \
+ ssl_session_cache \
+ ssl_srp_primes \
+ ssl_sup \
ssl_v3 \
- tls_v1 \
- dtls_v1
+ tls_connection \
+ tls_connection_sup \
+ tls_connection_1_3 \
+ tls_handshake \
+ tls_handshake_1_3 \
+ tls_record \
+ tls_record_1_3 \
+ tls_sender \
+ tls_socket \
+ tls_v1
+
INTERNAL_HRL_FILES = \
- ssl_alert.hrl ssl_cipher.hrl \
- tls_connection.hrl dtls_connection.hrl ssl_connection.hrl \
- ssl_handshake.hrl tls_handshake.hrl dtls_handshake.hrl ssl_api.hrl ssl_internal.hrl \
- ssl_record.hrl tls_record.hrl dtls_record.hrl ssl_srp.hrl
+ dtls_connection.hrl \
+ dtls_handshake.hrl \
+ dtls_record.hrl \
+ ssl_alert.hrl \
+ ssl_api.hrl \
+ ssl_cipher.hrl \
+ ssl_connection.hrl \
+ ssl_handshake.hrl \
+ ssl_internal.hrl \
+ ssl_record.hrl \
+ ssl_srp.hrl \
+ tls_connection.hrl \
+ tls_handshake.hrl \
+ tls_handshake_1_3.hrl \
+ tls_record.hrl \
+ tls_record_1_3.hrl
+
ERL_FILES= \
$(MODULES:%=%.erl) \
@@ -111,6 +131,10 @@ APP_TARGET= $(EBIN)/$(APP_FILE)
APPUP_SRC= $(APPUP_FILE).src
APPUP_TARGET= $(EBIN)/$(APPUP_FILE)
+DEPDIR=$(ERL_TOP)/lib/ssl/src/deps
+DEP_FILE=$(DEPDIR)/ssl.d
+$(shell mkdir -p $(dir $(DEP_FILE)) >/dev/null)
+
# ----------------------------------------------------
# FLAGS
# ----------------------------------------------------
@@ -118,7 +142,7 @@ EXTRA_ERLC_FLAGS = +warn_unused_vars
ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/kernel/src \
-pz $(EBIN) \
-pz $(ERL_TOP)/lib/public_key/ebin \
- $(EXTRA_ERLC_FLAGS) -DVSN=\"$(VSN)\"
+ $(EXTRA_ERLC_FLAGS)
# ----------------------------------------------------
@@ -127,11 +151,22 @@ ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/kernel/src \
$(TARGET_FILES): $(BEHAVIOUR_TARGET_FILES)
-debug opt: $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET)
+$(DEP_FILE): $(ERL_FILES)
+ $(gen_verbose)erlc -M $(ERL_FILES) \
+ | sed "s@$(ERL_TOP)@../../..@g" \
+ | sed "s/\.$(EMULATOR)/\.$$\(EMULATOR\)/" \
+ | sed 's@^dtls_@$$(EBIN)/dtls_@' \
+ | sed 's@^inet_@$$(EBIN)/inet_@' \
+ | sed 's@^ssl_@$$(EBIN)/ssl_@' \
+ | sed 's@^tls_@$$(EBIN)/tls_@' \
+ > $(DEP_FILE)
+
+debug opt: $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) $(DEP_FILE)
clean:
rm -f $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) $(BEHAVIOUR_TARGET_FILES)
rm -f errs core *~
+ rm -rf $(DEPDIR)
$(APP_TARGET): $(APP_SRC) ../vsn.mk
$(vsn_verbose)sed -e 's;%VSN%;$(VSN);' $< > $@
@@ -141,7 +176,6 @@ $(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk
docs:
-
# ----------------------------------------------------
# Release Target
# ----------------------------------------------------
@@ -159,22 +193,4 @@ release_docs_spec:
# ----------------------------------------------------
# Dependencies
# ----------------------------------------------------
-$(EBIN)/inet_tls_dist.$(EMULATOR): ../../kernel/include/net_address.hrl ../../kernel/include/dist.hrl ../../kernel/include/dist_util.hrl
-$(EBIN)/tls.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl ssl_handshake.hrl ../../public_key/include/public_key.hrl
-$(EBIN)/ssl_alert.$(EMULATOR): ssl_alert.hrl ssl_record.hrl
-$(EBIN)/ssl_certificate.$(EMULATOR): ssl_internal.hrl ssl_alert.hrl ssl_handshake.hrl ../../public_key/include/public_key.hrl
-$(EBIN)/ssl_certificate_db.$(EMULATOR): ssl_internal.hrl ../../public_key/include/public_key.hrl ../../kernel/include/file.hrl
-$(EBIN)/ssl_cipher.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl ssl_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl
-$(EBIN)/tls_connection.$(EMULATOR): ssl_internal.hrl tls_connection.hrl tls_record.hrl ssl_cipher.hrl tls_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl
-$(EBIN)/dtls_connection.$(EMULATOR): ssl_internal.hrl dtls_connection.hrl dtls_record.hrl ssl_cipher.hrl dtls_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl
-$(EBIN)/tls_handshake.$(EMULATOR): ssl_internal.hrl tls_record.hrl ssl_cipher.hrl tls_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl
-$(EBIN)/tls_handshake.$(EMULATOR): ssl_internal.hrl ssl_connection.hrl ssl_record.hrl ssl_cipher.hrl ssl_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl
-$(EBIN)/ssl_manager.$(EMULATOR): ssl_internal.hrl ssl_handshake.hrl ../../kernel/include/file.hrl
-$(EBIN)/ssl_record.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl ssl_handshake.hrl ssl_alert.hrl
-$(EBIN)/ssl_session.$(EMULATOR): ssl_internal.hrl ssl_handshake.hrl
-$(EBIN)/ssl_session_cache.$(EMULATOR): ssl_internal.hrl ssl_handshake.hrl
-$(EBIN)/ssl_session_cache_api.$(EMULATOR): ssl_internal.hrl ssl_handshake.hrl
-$(EBIN)/ssl_ssl3.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl
-$(EBIN)/ssl_tls1.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl
-$(EBIN)/ssl_cache.$(EMULATOR): ssl_cache.erl ssl_internal.hrl ../../public_key/include/public_key.hrl
-
+-include $(DEP_FILE)
diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl
index 2583667fa2..ed47980a69 100644
--- a/lib/ssl/src/dtls_connection.erl
+++ b/lib/ssl/src/dtls_connection.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2019. All 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,6 +32,7 @@
-include("ssl_internal.hrl").
-include("ssl_srp.hrl").
-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/logger.hrl").
%% Internal application API
@@ -50,8 +51,7 @@
-export([encode_alert/3, send_alert/2, send_alert_in_connection/2, close/5, protocol_name/0]).
%% Data handling
--export([encode_data/3, next_record/1,
- send/3, socket/5, setopts/3, getopts/3]).
+-export([next_record/1, socket/4, setopts/3, getopts/3]).
%% gen_statem state functions
-export([init/3, error/3, downgrade/3, %% Initiation and take down states
@@ -80,7 +80,7 @@ start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = false},_, Tracker}
end.
%%--------------------------------------------------------------------
--spec start_link(atom(), host(), inet:port_number(), port(), list(), pid(), tuple()) ->
+-spec start_link(atom(), ssl:host(), inet:port_number(), port(), list(), pid(), tuple()) ->
{ok, pid()} | ignore | {error, reason()}.
%%
%% Description: Creates a gen_statem process which calls Module:init/1 to
@@ -107,9 +107,11 @@ pids(_) ->
%%====================================================================
%% State transition handling
%%====================================================================
-next_record(#state{unprocessed_handshake_events = N} = State) when N > 0 ->
- {no_record, State#state{unprocessed_handshake_events = N-1}};
-
+next_record(#state{handshake_env =
+ #handshake_env{unprocessed_handshake_events = N} = HsEnv}
+ = State) when N > 0 ->
+ {no_record, State#state{handshake_env =
+ HsEnv#handshake_env{unprocessed_handshake_events = N-1}}};
next_record(#state{protocol_buffers =
#protocol_buffers{dtls_cipher_texts = [#ssl_tls{epoch = Epoch} = CT | Rest]}
= Buffers,
@@ -248,27 +250,31 @@ handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, Stat
handle_protocol_record(#ssl_tls{type = ?HANDSHAKE,
fragment = Data},
StateName,
- #state{protocol_buffers = Buffers0,
- negotiated_version = Version} = State0) ->
+ #state{protocol_buffers = Buffers0,
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = Options} = State) ->
try
- case dtls_handshake:get_dtls_handshake(Version, Data, Buffers0) of
+ case dtls_handshake:get_dtls_handshake(Version, Data, Buffers0, Options) of
{[], Buffers} ->
- next_event(StateName, no_record, State0#state{protocol_buffers = Buffers});
+ next_event(StateName, no_record, State#state{protocol_buffers = Buffers});
{Packets, Buffers} ->
- State = State0#state{protocol_buffers = Buffers},
+ HsEnv = State#state.handshake_env,
Events = dtls_handshake_events(Packets),
{next_state, StateName,
- State#state{unprocessed_handshake_events = unprocessed_events(Events)}, Events}
+ State#state{protocol_buffers = Buffers,
+ handshake_env =
+ HsEnv#handshake_env{unprocessed_handshake_events
+ = unprocessed_events(Events)}}, Events}
end
catch throw:#alert{} = Alert ->
- handle_own_alert(Alert, Version, StateName, State0)
+ handle_own_alert(Alert, Version, StateName, State)
end;
%%% DTLS record protocol level change cipher messages
handle_protocol_record(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) ->
{next_state, StateName, State, [{next_event, internal, #change_cipher_spec{type = Data}}]};
%%% DTLS record protocol level Alert messages
handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName,
- #state{negotiated_version = Version} = State) ->
+ #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
case decode_alerts(EncAlerts) of
Alerts = [_|_] ->
handle_alerts(Alerts, {next_state, StateName, State});
@@ -299,42 +305,51 @@ send_handshake(Handshake, #state{connection_states = ConnectionStates} = State)
#{epoch := Epoch} = ssl_record:current_connection_state(ConnectionStates, write),
send_handshake_flight(queue_handshake(Handshake, State), Epoch).
-queue_handshake(Handshake0, #state{tls_handshake_history = Hist0,
- negotiated_version = Version,
+queue_handshake(Handshake0, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
flight_buffer = #{handshakes := HsBuffer0,
change_cipher_spec := undefined,
- next_sequence := Seq} = Flight0} = State) ->
+ next_sequence := Seq} = Flight0,
+ ssl_options = SslOpts} = State) ->
Handshake = dtls_handshake:encode_handshake(Handshake0, Version, Seq),
Hist = update_handshake_history(Handshake0, Handshake, Hist0),
+ ssl_logger:debug(SslOpts#ssl_options.log_level, outbound, 'handshake', Handshake0),
+
State#state{flight_buffer = Flight0#{handshakes => [Handshake | HsBuffer0],
next_sequence => Seq +1},
- tls_handshake_history = Hist};
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}};
-queue_handshake(Handshake0, #state{tls_handshake_history = Hist0,
- negotiated_version = Version,
+queue_handshake(Handshake0, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
flight_buffer = #{handshakes_after_change_cipher_spec := Buffer0,
- next_sequence := Seq} = Flight0} = State) ->
+ next_sequence := Seq} = Flight0,
+ ssl_options = SslOpts} = State) ->
Handshake = dtls_handshake:encode_handshake(Handshake0, Version, Seq),
Hist = update_handshake_history(Handshake0, Handshake, Hist0),
+ ssl_logger:debug(SslOpts#ssl_options.log_level, outbound, 'handshake', Handshake0),
+
State#state{flight_buffer = Flight0#{handshakes_after_change_cipher_spec => [Handshake | Buffer0],
next_sequence => Seq +1},
- tls_handshake_history = Hist}.
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}}.
queue_change_cipher(ChangeCipher, #state{flight_buffer = Flight,
connection_states = ConnectionStates0} = State) ->
ConnectionStates =
- dtls_record:next_epoch(ConnectionStates0, write),
+ dtls_record:next_epoch(ConnectionStates0, write),
State#state{flight_buffer = Flight#{change_cipher_spec => ChangeCipher},
connection_states = ConnectionStates}.
reinit(State) ->
%% To be API compatible with TLS NOOP here
reinit_handshake_data(State).
-reinit_handshake_data(#state{protocol_buffers = Buffers} = State) ->
- State#state{premaster_secret = undefined,
- public_key_info = undefined,
- tls_handshake_history = ssl_handshake:init_handshake_history(),
- flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT},
+reinit_handshake_data(#state{static_env = #static_env{data_tag = DataTag},
+ protocol_buffers = Buffers,
+ protocol_specific = PS,
+ handshake_env = HsEnv} = State) ->
+ State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = ssl_handshake:init_handshake_history(),
+ public_key_info = undefined,
+ premaster_secret = undefined},
+ protocol_specific = PS#{flight_state => initial_flight_state(DataTag)},
flight_buffer = new_flight(),
protocol_buffers =
Buffers#protocol_buffers{
@@ -343,8 +358,8 @@ reinit_handshake_data(#state{protocol_buffers = Buffers} = State) ->
dtls_handshake_later_fragments = []
}}.
-select_sni_extension(#client_hello{extensions = HelloExtensions}) ->
- HelloExtensions#hello_extensions.sni;
+select_sni_extension(#client_hello{extensions = #{sni := SNI}}) ->
+ SNI;
select_sni_extension(_) ->
undefined.
@@ -358,13 +373,16 @@ empty_connection_state(ConnectionEnd, BeastMitigation) ->
encode_alert(#alert{} = Alert, Version, ConnectionStates) ->
dtls_record:encode_alert_record(Alert, Version, ConnectionStates).
-send_alert(Alert, #state{negotiated_version = Version,
- static_env = #static_env{socket = Socket,
+send_alert(Alert, #state{static_env = #static_env{socket = Socket,
transport_cb = Transport},
- connection_states = ConnectionStates0} = State0) ->
+
+ connection_env = #connection_env{negotiated_version = Version},
+ connection_states = ConnectionStates0,
+ ssl_options = SslOpts} = State0) ->
{BinMsg, ConnectionStates} =
encode_alert(Alert, Version, ConnectionStates0),
send(Transport, Socket, BinMsg),
+ ssl_logger:debug(SslOpts#ssl_options.log_level, outbound, 'record', BinMsg),
State0#state{connection_states = ConnectionStates}.
send_alert_in_connection(Alert, State) ->
@@ -384,16 +402,13 @@ protocol_name() ->
%% Data handling
%%====================================================================
-encode_data(Data, Version, ConnectionStates0)->
- dtls_record:encode_data(Data, Version, ConnectionStates0).
-
-send(Transport, {_, {{_,_}, _} = Socket}, Data) ->
- send(Transport, Socket, Data);
-send(Transport, Socket, Data) ->
- dtls_socket:send(Transport, Socket, Data).
+send(Transport, {Listener, Socket}, Data) when is_pid(Listener) -> % Server socket
+ dtls_socket:send(Transport, Socket, Data);
+send(Transport, Socket, Data) -> % Client socket
+ dtls_socket:send(Transport, Socket, Data).
-socket(Pid, Transport, Socket, Connection, _) ->
- dtls_socket:socket(Pid, Transport, Socket, Connection).
+socket(Pid, Transport, Socket, _Tracker) ->
+ dtls_socket:socket(Pid, Transport, Socket, ?MODULE).
setopts(Transport, Socket, Other) ->
dtls_socket:setopts(Transport, Socket, Other).
@@ -417,41 +432,34 @@ init({call, From}, {start, Timeout},
role = client,
session_cache = Cache,
session_cache_cb = CacheCb},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
+ connection_env = CEnv,
ssl_options = SslOpts,
session = #session{own_certificate = Cert} = Session0,
- connection_states = ConnectionStates0,
- renegotiation = {Renegotiation, _}
+ connection_states = ConnectionStates0
} = State0) ->
- Timer = ssl_connection:start_or_recv_cancel_timer(Timeout, From),
Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
Cache, CacheCb, Renegotiation, Cert),
Version = Hello#client_hello.client_version,
HelloVersion = dtls_record:hello_version(Version, SslOpts#ssl_options.versions),
- State1 = prepare_flight(State0#state{negotiated_version = Version}),
- {State2, Actions} = send_handshake(Hello, State1#state{negotiated_version = HelloVersion}),
- State3 = State2#state{negotiated_version = Version, %% Requested version
+ State1 = prepare_flight(State0#state{connection_env = CEnv#connection_env{negotiated_version = Version}}),
+ {State2, Actions} = send_handshake(Hello, State1#state{connection_env = CEnv#connection_env{negotiated_version = HelloVersion}}),
+ State3 = State2#state{connection_env = CEnv#connection_env{negotiated_version = Version}, %% RequestedVersion
session =
Session0#session{session_id = Hello#client_hello.session_id},
- start_or_recv_from = From,
- timer = Timer,
- flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}
- },
+ start_or_recv_from = From},
{Record, State} = next_record(State3),
- next_event(hello, Record, State, Actions);
-init({call, _} = Type, Event, #state{static_env = #static_env{role = server,
- data_tag = udp}} = State) ->
+ next_event(hello, Record, State, [{{timeout, handshake}, Timeout, close} | Actions]);
+init({call, _} = Type, Event, #state{static_env = #static_env{role = server},
+ protocol_specific = PS} = State) ->
Result = gen_handshake(?FUNCTION_NAME, Type, Event,
- State#state{flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT},
- protocol_specific = #{current_cookie_secret => dtls_v1:cookie_secret(),
- previous_cookie_secret => <<>>,
- ignored_alerts => 0,
- max_ignored_alerts => 10}}),
+ State#state{protocol_specific = PS#{current_cookie_secret => dtls_v1:cookie_secret(),
+ previous_cookie_secret => <<>>,
+ ignored_alerts => 0,
+ max_ignored_alerts => 10}}),
erlang:send_after(dtls_v1:cookie_timeout(), self(), new_cookie_secret),
Result;
-init({call, _} = Type, Event, #state{static_env = #static_env{role = server}} = State) ->
- %% I.E. DTLS over sctp
- gen_handshake(?FUNCTION_NAME, Type, Event, State#state{flight_state = reliable});
init(Type, Event, State) ->
gen_handshake(?FUNCTION_NAME, Type, Event, State).
@@ -487,6 +495,8 @@ hello(internal, #client_hello{cookie = <<>>,
#state{static_env = #static_env{role = server,
transport_cb = Transport,
socket = Socket},
+ handshake_env = HsEnv,
+ connection_env = CEnv,
protocol_specific = #{current_cookie_secret := Secret}} = State0) ->
{ok, {IP, Port}} = dtls_socket:peername(Transport, Socket),
Cookie = dtls_handshake:cookie(Secret, IP, Port, Hello),
@@ -497,46 +507,55 @@ hello(internal, #client_hello{cookie = <<>>,
%% version 1.0 regardless of the version of TLS that is expected to be
%% negotiated.
VerifyRequest = dtls_handshake:hello_verify_request(Cookie, ?HELLO_VERIFY_REQUEST_VERSION),
- State1 = prepare_flight(State0#state{negotiated_version = Version}),
+ State1 = prepare_flight(State0#state{connection_env = CEnv#connection_env{negotiated_version = Version}}),
{State2, Actions} = send_handshake(VerifyRequest, State1),
{Record, State} = next_record(State2),
- next_event(?FUNCTION_NAME, Record, State#state{tls_handshake_history = ssl_handshake:init_handshake_history()}, Actions);
+ next_event(?FUNCTION_NAME, Record,
+ State#state{handshake_env = HsEnv#handshake_env{
+ tls_handshake_history =
+ ssl_handshake:init_handshake_history()}},
+ Actions);
hello(internal, #hello_verify_request{cookie = Cookie}, #state{static_env = #static_env{role = client,
host = Host,
port = Port,
session_cache = Cache,
session_cache_cb = CacheCb},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv,
+ connection_env = CEnv,
ssl_options = SslOpts,
session = #session{own_certificate = OwnCert}
= Session0,
- connection_states = ConnectionStates0,
- renegotiation = {Renegotiation, _}
+ connection_states = ConnectionStates0
} = State0) ->
Hello = dtls_handshake:client_hello(Host, Port, Cookie, ConnectionStates0,
SslOpts,
Cache, CacheCb, Renegotiation, OwnCert),
Version = Hello#client_hello.client_version,
- State1 = prepare_flight(State0#state{tls_handshake_history = ssl_handshake:init_handshake_history()}),
+ State1 = prepare_flight(State0#state{handshake_env =
+ HsEnv#handshake_env{tls_handshake_history
+ = ssl_handshake:init_handshake_history()}}),
{State2, Actions} = send_handshake(Hello, State1),
- State = State2#state{negotiated_version = Version, %% Requested version
+ State = State2#state{connection_env = CEnv#connection_env{negotiated_version = Version}, %% Requested version
session =
Session0#session{session_id =
Hello#client_hello.session_id}},
next_event(?FUNCTION_NAME, no_record, State, Actions);
hello(internal, #client_hello{extensions = Extensions} = Hello,
#state{ssl_options = #ssl_options{handshake = hello},
+ handshake_env = HsEnv,
start_or_recv_from = From} = State) ->
{next_state, user_hello, State#state{start_or_recv_from = undefined,
- hello = Hello},
- [{reply, From, {ok, ssl_connection:map_extensions(Extensions)}}]};
+ handshake_env = HsEnv#handshake_env{hello = Hello}},
+ [{reply, From, {ok, Extensions}}]};
hello(internal, #server_hello{extensions = Extensions} = Hello,
#state{ssl_options = #ssl_options{handshake = hello},
+ handshake_env = HsEnv,
start_or_recv_from = From} = State) ->
{next_state, user_hello, State#state{start_or_recv_from = undefined,
- hello = Hello},
- [{reply, From, {ok, ssl_connection:map_extensions(Extensions)}}]};
+ handshake_env = HsEnv#handshake_env{hello = Hello}},
+ [{reply, From, {ok, Extensions}}]};
hello(internal, #client_hello{cookie = Cookie} = Hello, #state{static_env = #static_env{role = server,
transport_cb = Transport,
@@ -560,9 +579,9 @@ hello(internal, #client_hello{cookie = Cookie} = Hello, #state{static_env = #sta
hello(internal, #server_hello{} = Hello,
#state{
static_env = #static_env{role = client},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
+ connection_env = #connection_env{negotiated_version = ReqVersion},
connection_states = ConnectionStates0,
- negotiated_version = ReqVersion,
- renegotiation = {Renegotiation, _},
ssl_options = SslOptions} = State) ->
case dtls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of
#alert{} = Alert ->
@@ -609,10 +628,11 @@ abbreviated(internal = Type,
ConnectionStates1 = dtls_record:save_current_connection_state(ConnectionStates0, read),
ConnectionStates = dtls_record:next_epoch(ConnectionStates1, read),
gen_handshake(?FUNCTION_NAME, Type, Event, State#state{connection_states = ConnectionStates});
-abbreviated(internal = Type, #finished{} = Event, #state{connection_states = ConnectionStates} = State) ->
+abbreviated(internal = Type, #finished{} = Event, #state{connection_states = ConnectionStates,
+ protocol_specific = PS} = State) ->
gen_handshake(?FUNCTION_NAME, Type, Event,
prepare_flight(State#state{connection_states = ConnectionStates,
- flight_state = connection}));
+ protocol_specific = PS#{flight_state => connection}}));
abbreviated(state_timeout, Event, State) ->
handle_state_timeout(Event, ?FUNCTION_NAME, State);
abbreviated(Type, Event, State) ->
@@ -652,10 +672,11 @@ cipher(internal = Type, #change_cipher_spec{type = <<1>>} = Event,
ConnectionStates1 = dtls_record:save_current_connection_state(ConnectionStates0, read),
ConnectionStates = dtls_record:next_epoch(ConnectionStates1, read),
ssl_connection:?FUNCTION_NAME(Type, Event, State#state{connection_states = ConnectionStates}, ?MODULE);
-cipher(internal = Type, #finished{} = Event, #state{connection_states = ConnectionStates} = State) ->
+cipher(internal = Type, #finished{} = Event, #state{connection_states = ConnectionStates,
+ protocol_specific = PS} = State) ->
ssl_connection:?FUNCTION_NAME(Type, Event,
prepare_flight(State#state{connection_states = ConnectionStates,
- flight_state = connection}),
+ protocol_specific = PS#{flight_state => connection}}),
?MODULE);
cipher(state_timeout, Event, State) ->
handle_state_timeout(Event, ?FUNCTION_NAME, State);
@@ -673,39 +694,43 @@ connection(info, Event, State) ->
gen_info(Event, ?FUNCTION_NAME, State);
connection(internal, #hello_request{}, #state{static_env = #static_env{host = Host,
port = Port,
+ data_tag = DataTag,
session_cache = Cache,
session_cache_cb = CacheCb
},
+ handshake_env = #handshake_env{ renegotiation = {Renegotiation, _}},
+ connection_env = CEnv,
session = #session{own_certificate = Cert} = Session0,
-
ssl_options = SslOpts,
connection_states = ConnectionStates0,
- renegotiation = {Renegotiation, _}} = State0) ->
+ protocol_specific = PS
+ } = State0) ->
Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
Cache, CacheCb, Renegotiation, Cert),
Version = Hello#client_hello.client_version,
HelloVersion = dtls_record:hello_version(Version, SslOpts#ssl_options.versions),
State1 = prepare_flight(State0),
- {State2, Actions} = send_handshake(Hello, State1#state{negotiated_version = HelloVersion}),
+ {State2, Actions} = send_handshake(Hello, State1#state{connection_env = CEnv#connection_env{negotiated_version = HelloVersion}}),
{Record, State} =
next_record(
- State2#state{flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT},
+ State2#state{protocol_specific = PS#{flight_state => initial_flight_state(DataTag)},
session = Session0#session{session_id
- = Hello#client_hello.session_id}}),
+ = Hello#client_hello.session_id}}),
next_event(hello, Record, State, Actions);
connection(internal, #client_hello{} = Hello, #state{static_env = #static_env{role = server},
- allow_renegotiate = true} = State) ->
+ handshake_env = #handshake_env{allow_renegotiate = true} = HsEnv} = State) ->
%% Mitigate Computational DoS attack
%% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html
%% http://www.thc.org/thc-ssl-dos/ Rather than disabling client
%% initiated renegotiation we will disallow many client initiated
%% renegotiations immediately after each other.
erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate),
- {next_state, hello, State#state{allow_renegotiate = false, renegotiation = {true, peer}},
+ {next_state, hello, State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, peer},
+ allow_renegotiate = false}},
[{next_event, internal, Hello}]};
connection(internal, #client_hello{}, #state{static_env = #static_env{role = server},
- allow_renegotiate = false} = State0) ->
+ handshake_env = #handshake_env{allow_renegotiate = false}} = State0) ->
Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION),
State1 = send_alert(Alert, State0),
{Record, State} = ssl_connection:prepare_connection(State1, ?MODULE),
@@ -774,6 +799,12 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, _}, User,
},
#state{static_env = InitStatEnv,
+ handshake_env = #handshake_env{
+ tls_handshake_history = ssl_handshake:init_handshake_history(),
+ renegotiation = {false, first},
+ allow_renegotiate = SSLOptions#ssl_options.client_renegotiation
+ },
+ 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.
@@ -781,21 +812,24 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, _}, User,
session = #session{is_resumable = new},
connection_states = ConnectionStates,
protocol_buffers = #protocol_buffers{},
- user_application = {Monitor, User},
- user_data_buffer = <<>>,
- renegotiation = {false, first},
- allow_renegotiate = SSLOptions#ssl_options.client_renegotiation,
+ user_data_buffer = {[],0,[]},
start_or_recv_from = undefined,
flight_buffer = new_flight(),
- flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}
+ protocol_specific = #{flight_state => initial_flight_state(DataTag)}
}.
+initial_flight_state(udp)->
+ {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT};
+initial_flight_state(_) ->
+ reliable.
+
next_dtls_record(Data, StateName, #state{protocol_buffers = #protocol_buffers{
dtls_record_buffer = Buf0,
- dtls_cipher_texts = CT0} = Buffers} = State0) ->
+ dtls_cipher_texts = CT0} = Buffers,
+ ssl_options = SslOpts} = State0) ->
case dtls_record:get_dtls_records(Data,
acceptable_record_versions(StateName, State0),
- Buf0) of
+ Buf0, SslOpts) of
{Records, Buf1} ->
CT1 = CT0 ++ Records,
next_record(State0#state{protocol_buffers =
@@ -807,7 +841,7 @@ next_dtls_record(Data, StateName, #state{protocol_buffers = #protocol_buffers{
acceptable_record_versions(hello, _) ->
[dtls_record:protocol_version(Vsn) || Vsn <- ?ALL_DATAGRAM_SUPPORTED_VERSIONS];
-acceptable_record_versions(_, #state{negotiated_version = Version}) ->
+acceptable_record_versions(_, #state{connection_env = #connection_env{negotiated_version = Version}}) ->
[Version].
dtls_handshake_events(Packets) ->
@@ -826,8 +860,9 @@ decode_cipher_text(#state{protocol_buffers = #protocol_buffers{dtls_cipher_texts
{Alert, State}
end.
-dtls_version(hello, Version, #state{static_env = #static_env{role = server}} = State) ->
- State#state{negotiated_version = Version}; %%Inital version
+dtls_version(hello, Version, #state{static_env = #static_env{role = server},
+ connection_env = CEnv} = State) ->
+ State#state{connection_env = CEnv#connection_env{negotiated_version = Version}}; %%Inital version
dtls_version(_,_, State) ->
State.
@@ -836,11 +871,11 @@ handle_client_hello(#client_hello{client_version = ClientVersion} = Hello,
static_env = #static_env{port = Port,
session_cache = Cache,
session_cache_cb = CacheCb},
+ handshake_env = #handshake_env{kex_algorithm = KeyExAlg,
+ renegotiation = {Renegotiation, _},
+ negotiated_protocol = CurrentProtocol} = HsEnv,
+ connection_env = CEnv,
session = #session{own_certificate = Cert} = Session0,
- renegotiation = {Renegotiation, _},
-
- negotiated_protocol = CurrentProtocol,
- key_algorithm = KeyExAlg,
ssl_options = SslOpts} = State0) ->
case dtls_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb,
@@ -855,11 +890,12 @@ handle_client_hello(#client_hello{client_version = ClientVersion} = Hello,
end,
State = prepare_flight(State0#state{connection_states = ConnectionStates,
- negotiated_version = Version,
- hashsign_algorithm = HashSign,
- client_hello_version = ClientVersion,
- session = Session,
- negotiated_protocol = Protocol}),
+ connection_env = CEnv#connection_env{negotiated_version = Version},
+ handshake_env = HsEnv#handshake_env{
+ hashsign_algorithm = HashSign,
+ client_hello_version = ClientVersion,
+ negotiated_protocol = Protocol},
+ session = Session}),
ssl_connection:hello(internal, {common_client_hello, Type, ServerHelloExt},
State, ?MODULE)
@@ -879,9 +915,9 @@ handle_info({Protocol, _, _, _, Data}, StateName,
handle_info({CloseTag, Socket}, StateName,
#state{static_env = #static_env{socket = Socket,
close_tag = CloseTag},
+ connection_env = #connection_env{negotiated_version = Version},
socket_options = #socket_options{active = Active},
- protocol_buffers = #protocol_buffers{dtls_cipher_texts = CTs},
- negotiated_version = Version} = State) ->
+ protocol_buffers = #protocol_buffers{dtls_cipher_texts = CTs}} = State) ->
%% Note that as of DTLS 1.2 (TLS 1.1),
%% failure to properly close a connection no longer requires that a
%% session not be resumed. This is a change from DTLS 1.0 to conform
@@ -917,9 +953,10 @@ handle_info(Msg, StateName, State) ->
ssl_connection:StateName(info, Msg, State, ?MODULE).
handle_state_timeout(flight_retransmission_timeout, StateName,
- #state{flight_state = {retransmit, NextTimeout}} = State0) ->
- {State1, Actions0} = send_handshake_flight(State0#state{flight_state = {retransmit, NextTimeout}},
- retransmit_epoch(StateName, State0)),
+ #state{protocol_specific =
+ #{flight_state := {retransmit, _NextTimeout}}} = State0) ->
+ {State1, Actions0} = send_handshake_flight(State0,
+ retransmit_epoch(StateName, State0)),
{next_state, StateName, State, Actions} = next_event(StateName, no_record, State1, Actions0),
%% This will reset the retransmission timer by repeating the enter state event
{repeat_state, State, Actions}.
@@ -938,7 +975,7 @@ handle_own_alert(Alert, Version, StateName, #state{static_env = #static_env{data
ssl_options = Options} = State0) ->
case ignore_alert(Alert, State0) of
{true, State} ->
- log_ignore_alert(Options#ssl_options.log_alert, StateName, Alert, Role),
+ log_ignore_alert(Options#ssl_options.log_level, StateName, Alert, Role),
{next_state, StateName, State};
{false, State} ->
ssl_connection:handle_own_alert(Alert, Version, StateName, State)
@@ -959,7 +996,7 @@ decode_alerts(Bin) ->
ssl_alert:decode(Bin).
gen_handshake(StateName, Type, Event,
- #state{negotiated_version = Version} = State) ->
+ #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
try ssl_connection:StateName(Type, Event, State, ?MODULE) of
Result ->
Result
@@ -970,7 +1007,7 @@ gen_handshake(StateName, Type, Event,
Version, StateName, State)
end.
-gen_info(Event, connection = StateName, #state{negotiated_version = Version} = State) ->
+gen_info(Event, connection = StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
try handle_info(Event, StateName, State) of
Result ->
Result
@@ -981,7 +1018,7 @@ gen_info(Event, connection = StateName, #state{negotiated_version = Version} =
Version, StateName, State)
end;
-gen_info(Event, StateName, #state{negotiated_version = Version} = State) ->
+gen_info(Event, StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
try handle_info(Event, StateName, State) of
Result ->
Result
@@ -1025,17 +1062,17 @@ next_flight(Flight) ->
handshakes_after_change_cipher_spec => []}.
handle_flight_timer(#state{static_env = #static_env{data_tag = udp},
- flight_state = {retransmit, Timeout}} = State) ->
+ protocol_specific = #{flight_state := {retransmit, Timeout}}} = State) ->
start_retransmision_timer(Timeout, State);
handle_flight_timer(#state{static_env = #static_env{data_tag = udp},
- flight_state = connection} = State) ->
+ protocol_specific = #{flight_state := connection}} = State) ->
{State, []};
-handle_flight_timer(State) ->
+handle_flight_timer(#state{protocol_specific = #{flight_state := reliable}} = State) ->
%% No retransmision needed i.e DTLS over SCTP
- {State#state{flight_state = reliable}, []}.
+ {State, []}.
-start_retransmision_timer(Timeout, State) ->
- {State#state{flight_state = {retransmit, new_timeout(Timeout)}},
+start_retransmision_timer(Timeout, #state{protocol_specific = PS} = State) ->
+ {State#state{protocol_specific = PS#{flight_state => {retransmit, new_timeout(Timeout)}}},
[{state_timeout, Timeout, flight_retransmission_timeout}]}.
new_timeout(N) when N =< 30 ->
@@ -1045,37 +1082,46 @@ new_timeout(_) ->
send_handshake_flight(#state{static_env = #static_env{socket = Socket,
transport_cb = Transport},
- flight_buffer = #{handshakes := Flight,
+ connection_env = #connection_env{negotiated_version = Version},
+ flight_buffer = #{handshakes := Flight,
change_cipher_spec := undefined},
- negotiated_version = Version,
- connection_states = ConnectionStates0} = State0, Epoch) ->
+ connection_states = ConnectionStates0,
+ ssl_options = #ssl_options{log_level = LogLevel}} = State0,
+ Epoch) ->
%% TODO remove hardcoded Max size
{Encoded, ConnectionStates} =
encode_handshake_flight(lists:reverse(Flight), Version, 1400, Epoch, ConnectionStates0),
- send(Transport, Socket, Encoded),
+ send(Transport, Socket, Encoded),
+ ssl_logger:debug(LogLevel, outbound, 'record', Encoded),
{State0#state{connection_states = ConnectionStates}, []};
send_handshake_flight(#state{static_env = #static_env{socket = Socket,
transport_cb = Transport},
+ connection_env = #connection_env{negotiated_version = Version},
flight_buffer = #{handshakes := [_|_] = Flight0,
change_cipher_spec := ChangeCipher,
handshakes_after_change_cipher_spec := []},
- negotiated_version = Version,
- connection_states = ConnectionStates0} = State0, Epoch) ->
+ connection_states = ConnectionStates0,
+ ssl_options = #ssl_options{log_level = LogLevel}} = State0,
+ Epoch) ->
{HsBefore, ConnectionStates1} =
encode_handshake_flight(lists:reverse(Flight0), Version, 1400, Epoch, ConnectionStates0),
{EncChangeCipher, ConnectionStates} = encode_change_cipher(ChangeCipher, Version, Epoch, ConnectionStates1),
send(Transport, Socket, [HsBefore, EncChangeCipher]),
+ ssl_logger:debug(LogLevel, outbound, 'record', [HsBefore]),
+ ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]),
{State0#state{connection_states = ConnectionStates}, []};
send_handshake_flight(#state{static_env = #static_env{socket = Socket,
transport_cb = Transport},
+ connection_env = #connection_env{negotiated_version = Version},
flight_buffer = #{handshakes := [_|_] = Flight0,
change_cipher_spec := ChangeCipher,
handshakes_after_change_cipher_spec := Flight1},
- negotiated_version = Version,
- connection_states = ConnectionStates0} = State0, Epoch) ->
+ connection_states = ConnectionStates0,
+ ssl_options = #ssl_options{log_level = LogLevel}} = State0,
+ Epoch) ->
{HsBefore, ConnectionStates1} =
encode_handshake_flight(lists:reverse(Flight0), Version, 1400, Epoch-1, ConnectionStates0),
{EncChangeCipher, ConnectionStates2} =
@@ -1083,20 +1129,27 @@ send_handshake_flight(#state{static_env = #static_env{socket = Socket,
{HsAfter, ConnectionStates} =
encode_handshake_flight(lists:reverse(Flight1), Version, 1400, Epoch, ConnectionStates2),
send(Transport, Socket, [HsBefore, EncChangeCipher, HsAfter]),
+ ssl_logger:debug(LogLevel, outbound, 'record', [HsBefore]),
+ ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]),
+ ssl_logger:debug(LogLevel, outbound, 'record', [HsAfter]),
{State0#state{connection_states = ConnectionStates}, []};
send_handshake_flight(#state{static_env = #static_env{socket = Socket,
transport_cb = Transport},
+ connection_env = #connection_env{negotiated_version = Version},
flight_buffer = #{handshakes := [],
change_cipher_spec := ChangeCipher,
handshakes_after_change_cipher_spec := Flight1},
- negotiated_version = Version,
- connection_states = ConnectionStates0} = State0, Epoch) ->
+ connection_states = ConnectionStates0,
+ ssl_options = #ssl_options{log_level = LogLevel}} = State0,
+ Epoch) ->
{EncChangeCipher, ConnectionStates1} =
encode_change_cipher(ChangeCipher, Version, Epoch-1, ConnectionStates0),
{HsAfter, ConnectionStates} =
encode_handshake_flight(lists:reverse(Flight1), Version, 1400, Epoch, ConnectionStates1),
send(Transport, Socket, [EncChangeCipher, HsAfter]),
+ ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]),
+ ssl_logger:debug(LogLevel, outbound, 'record', [HsAfter]),
{State0#state{connection_states = ConnectionStates}, []}.
retransmit_epoch(_StateName, #state{connection_states = ConnectionStates}) ->
@@ -1135,31 +1188,33 @@ is_ignore_alert(#alert{description = ?ILLEGAL_PARAMETER}) ->
is_ignore_alert(_) ->
false.
-log_ignore_alert(true, StateName, Alert, Role) ->
+log_ignore_alert(debug, StateName, Alert, Role) ->
Txt = ssl_alert:alert_txt(Alert),
- error_logger:format("DTLS over UDP ~p: In state ~p ignored to send ALERT ~s as DoS-attack mitigation \n",
- [Role, StateName, Txt]);
-log_ignore_alert(false, _, _,_) ->
+ ?LOG_ERROR("DTLS over UDP ~p: In state ~p ignored to send ALERT ~s as DoS-attack mitigation \n",
+ [Role, StateName, Txt]);
+log_ignore_alert(_, _, _, _) ->
ok.
send_application_data(Data, From, _StateName,
#state{static_env = #static_env{socket = Socket,
- protocol_cb = Connection,
transport_cb = Transport},
- negotiated_version = Version,
+ connection_env = #connection_env{negotiated_version = Version},
+ handshake_env = HsEnv,
connection_states = ConnectionStates0,
- ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}} = State0) ->
+ ssl_options = #ssl_options{renegotiate_at = RenegotiateAt,
+ log_level = LogLevel}} = State0) ->
case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of
true ->
- renegotiate(State0#state{renegotiation = {true, internal}},
+ renegotiate(State0#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}}},
[{next_event, {call, From}, {application_data, Data}}]);
false ->
{Msgs, ConnectionStates} =
- Connection:encode_data(Data, Version, ConnectionStates0),
+ dtls_record:encode_data(Data, Version, ConnectionStates0),
State = State0#state{connection_states = ConnectionStates},
- case Connection:send(Transport, Socket, Msgs) of
+ case send(Transport, Socket, Msgs) of
ok ->
+ ssl_logger:debug(LogLevel, outbound, 'record', Msgs),
ssl_connection:hibernate_after(connection, State, [{reply, From, ok}]);
Result ->
ssl_connection:hibernate_after(connection, State, [{reply, From, Result}])
@@ -1180,3 +1235,4 @@ is_time_to_renegotiate(N, M) when N < M->
false;
is_time_to_renegotiate(_,_) ->
true.
+
diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl
index 3f70eaec8a..46e8348ce0 100644
--- a/lib/ssl/src/dtls_handshake.erl
+++ b/lib/ssl/src/dtls_handshake.erl
@@ -37,7 +37,7 @@
-export([fragment_handshake/2, encode_handshake/3]).
%% Handshake decodeing
--export([get_dtls_handshake/3]).
+-export([get_dtls_handshake/4]).
-type dtls_handshake() :: #client_hello{} | #hello_verify_request{} |
ssl_handshake:ssl_handshake().
@@ -46,7 +46,7 @@
%% Handshake handling
%%====================================================================
%%--------------------------------------------------------------------
--spec client_hello(host(), inet:port_number(), ssl_record:connection_states(),
+-spec client_hello(ssl:host(), inet:port_number(), ssl_record:connection_states(),
#ssl_options{}, integer(), atom(), boolean(), der_cert()) ->
#client_hello{}.
%%
@@ -59,7 +59,7 @@ client_hello(Host, Port, ConnectionStates, SslOpts,
Cache, CacheCb, Renegotiation, OwnCert).
%%--------------------------------------------------------------------
--spec client_hello(host(), inet:port_number(), term(), ssl_record:connection_states(),
+-spec client_hello(ssl:host(), inet:port_number(), term(), ssl_record:connection_states(),
#ssl_options{}, integer(), atom(), boolean(), der_cert()) ->
#client_hello{}.
%%
@@ -79,7 +79,7 @@ client_hello(Host, Port, Cookie, ConnectionStates,
Extensions = ssl_handshake:client_hello_extensions(TLSVersion, CipherSuites,
SslOpts, ConnectionStates,
- Renegotiation),
+ Renegotiation, undefined),
Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert),
#client_hello{session_id = Id,
@@ -123,7 +123,7 @@ cookie(Key, Address, Port, #client_hello{client_version = {Major, Minor},
Random, SessionId, CipherSuites, CompressionMethods],
crypto:hmac(sha, Key, CookieData).
%%--------------------------------------------------------------------
--spec hello_verify_request(binary(), dtls_record:dtls_version()) -> #hello_verify_request{}.
+-spec hello_verify_request(binary(), ssl_record:ssl_version()) -> #hello_verify_request{}.
%%
%% Description: Creates a hello verify request message sent by server to
%% verify client
@@ -151,15 +151,15 @@ encode_handshake(Handshake, Version, Seq) ->
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
--spec get_dtls_handshake(dtls_record:dtls_version(), binary(), #protocol_buffers{}) ->
+-spec get_dtls_handshake(ssl_record:ssl_version(), binary(), #protocol_buffers{}, #ssl_options{}) ->
{[dtls_handshake()], #protocol_buffers{}}.
%%
%% Description: Given buffered and new data from dtls_record, collects
%% and returns it as a list of handshake messages, also returns
%% possible leftover data in the new "protocol_buffers".
%%--------------------------------------------------------------------
-get_dtls_handshake(Version, Fragment, ProtocolBuffers) ->
- handle_fragments(Version, Fragment, ProtocolBuffers, []).
+get_dtls_handshake(Version, Fragment, ProtocolBuffers, Options) ->
+ handle_fragments(Version, Fragment, ProtocolBuffers, Options, []).
%%--------------------------------------------------------------------
%%% Internal functions
@@ -169,10 +169,7 @@ handle_client_hello(Version,
cipher_suites = CipherSuites,
compression_methods = Compressions,
random = Random,
- extensions =
- #hello_extensions{elliptic_curves = Curves,
- signature_algs = ClientHashSigns}
- = HelloExt},
+ extensions = HelloExt},
#ssl_options{versions = Versions,
signature_algs = SupportedHashSigns,
eccs = SupportedECCs,
@@ -181,6 +178,8 @@ handle_client_hello(Version,
Renegotiation) ->
case dtls_record:is_acceptable_version(Version, Versions) of
true ->
+ Curves = maps:get(elliptic_curves, HelloExt, undefined),
+ ClientHashSigns = maps:get(signature_algs, HelloExt, undefined),
TLSVersion = dtls_v1:corresponding_tls_version(Version),
AvailableHashSigns = ssl_handshake:available_signature_algs(
ClientHashSigns, SupportedHashSigns, Cert,TLSVersion),
@@ -195,7 +194,7 @@ handle_client_hello(Version,
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY);
_ ->
#{key_exchange := KeyExAlg} = ssl_cipher_format:suite_definition(CipherSuite),
- case ssl_handshake:select_hashsign(ClientHashSigns, Cert, KeyExAlg,
+ case ssl_handshake:select_hashsign({ClientHashSigns, undefined}, Cert, KeyExAlg,
SupportedHashSigns, TLSVersion) of
#alert{} = Alert ->
Alert;
@@ -215,8 +214,6 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites,
HelloExt, dtls_v1:corresponding_tls_version(Version),
SslOpts, Session0,
ConnectionStates0, Renegotiation) of
- #alert{} = Alert ->
- Alert;
{Session, ConnectionStates, Protocol, ServerHelloExt} ->
{Version, {Type, Session}, ConnectionStates, Protocol, ServerHelloExt, HashSign}
catch throw:Alert ->
@@ -225,17 +222,16 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites,
handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation) ->
- case ssl_handshake:handle_server_hello_extensions(dtls_record, Random, CipherSuite,
- Compression, HelloExt,
- dtls_v1:corresponding_tls_version(Version),
- SslOpt, ConnectionStates0, Renegotiation) of
- #alert{} = Alert ->
- Alert;
+ try ssl_handshake:handle_server_hello_extensions(dtls_record, Random, CipherSuite,
+ Compression, HelloExt,
+ dtls_v1:corresponding_tls_version(Version),
+ SslOpt, ConnectionStates0, Renegotiation) of
{ConnectionStates, ProtoExt, Protocol} ->
{Version, SessionId, ConnectionStates, ProtoExt, Protocol}
+ catch throw:Alert ->
+ Alert
end.
-
%%--------------------------------------------------------------------
enc_handshake(#hello_verify_request{protocol_version = {Major, Minor},
@@ -314,20 +310,21 @@ address_to_bin({A,B,C,D,E,F,G,H}, Port) ->
%%--------------------------------------------------------------------
-handle_fragments(Version, FragmentData, Buffers0, Acc) ->
+handle_fragments(Version, FragmentData, Buffers0, Options, Acc) ->
Fragments = decode_handshake_fragments(FragmentData),
- do_handle_fragments(Version, Fragments, Buffers0, Acc).
+ do_handle_fragments(Version, Fragments, Buffers0, Options, Acc).
-do_handle_fragments(_, [], Buffers, Acc) ->
+do_handle_fragments(_, [], Buffers, _Options, Acc) ->
{lists:reverse(Acc), Buffers};
-do_handle_fragments(Version, [Fragment | Fragments], Buffers0, Acc) ->
+do_handle_fragments(Version, [Fragment | Fragments], Buffers0, Options, Acc) ->
case reassemble(Version, Fragment, Buffers0) of
{more_data, Buffers} when Fragments == [] ->
{lists:reverse(Acc), Buffers};
{more_data, Buffers} ->
- do_handle_fragments(Version, Fragments, Buffers, Acc);
- {HsPacket, Buffers} ->
- do_handle_fragments(Version, Fragments, Buffers, [HsPacket | Acc])
+ do_handle_fragments(Version, Fragments, Buffers, Options, Acc);
+ {{Handshake, _} = HsPacket, Buffers} ->
+ ssl_logger:debug(Options#ssl_options.log_level, inbound, 'handshake', Handshake),
+ do_handle_fragments(Version, Fragments, Buffers, Options, [HsPacket | Acc])
end.
decode_handshake(Version, <<?BYTE(Type), Bin/binary>>) ->
@@ -335,7 +332,7 @@ decode_handshake(Version, <<?BYTE(Type), Bin/binary>>) ->
decode_handshake(_, ?HELLO_REQUEST, <<>>) ->
#hello_request{};
-decode_handshake(_Version, ?CLIENT_HELLO, <<?UINT24(_), ?UINT16(_),
+decode_handshake(Version, ?CLIENT_HELLO, <<?UINT24(_), ?UINT16(_),
?UINT24(_), ?UINT24(_),
?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SID_length), Session_ID:SID_length/binary,
@@ -343,8 +340,10 @@ decode_handshake(_Version, ?CLIENT_HELLO, <<?UINT24(_), ?UINT16(_),
?UINT16(Cs_length), CipherSuites:Cs_length/binary,
?BYTE(Cm_length), Comp_methods:Cm_length/binary,
Extensions/binary>>) ->
-
- DecodedExtensions = ssl_handshake:decode_hello_extensions({client, Extensions}),
+ TLSVersion = dtls_v1:corresponding_tls_version(Version),
+ LegacyVersion = dtls_v1:corresponding_tls_version({Major, Minor}),
+ Exts = ssl_handshake:decode_vector(Extensions),
+ DecodedExtensions = ssl_handshake:decode_hello_extensions(Exts, TLSVersion, LegacyVersion, client),
#client_hello{
client_version = {Major,Minor},
@@ -365,9 +364,9 @@ decode_handshake(_Version, ?HELLO_VERIFY_REQUEST, <<?UINT24(_), ?UINT16(_),
decode_handshake(Version, Tag, <<?UINT24(_), ?UINT16(_),
?UINT24(_), ?UINT24(_), Msg/binary>>) ->
%% DTLS specifics stripped
- decode_tls_thandshake(Version, Tag, Msg).
+ decode_tls_handshake(Version, Tag, Msg).
-decode_tls_thandshake(Version, Tag, Msg) ->
+decode_tls_handshake(Version, Tag, Msg) ->
TLSVersion = dtls_v1:corresponding_tls_version(Version),
ssl_handshake:decode_handshake(TLSVersion, Tag, Msg).
diff --git a/lib/ssl/src/dtls_handshake.hrl b/lib/ssl/src/dtls_handshake.hrl
index 50e92027d2..de2be1daeb 100644
--- a/lib/ssl/src/dtls_handshake.hrl
+++ b/lib/ssl/src/dtls_handshake.hrl
@@ -26,22 +26,13 @@
-ifndef(dtls_handshake).
-define(dtls_handshake, true).
+-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").
-define(HELLO_VERIFY_REQUEST, 3).
-define(HELLO_VERIFY_REQUEST_VERSION, {254, 255}).
--record(client_hello, {
- client_version,
- random,
- session_id, % opaque SessionID<0..32>
- cookie, % opaque<2..2^16-1>
- cipher_suites, % cipher_suites<2..2^16-1>
- compression_methods, % compression_methods<1..2^8-1>,
- %% Extensions
- extensions
- }).
-
-record(hello_verify_request, {
protocol_version,
cookie
@@ -56,4 +47,11 @@
fragment
}).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% RFC 7764 Datagram Transport Layer Security (DTLS) Extension to Establish Keys
+%% for the Secure Real-time Transport Protocol (SRTP)
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Not supported
+-define(USE_SRTP, 14).
+
-endif. % -ifdef(dtls_handshake).
diff --git a/lib/ssl/src/dtls_packet_demux.erl b/lib/ssl/src/dtls_packet_demux.erl
index 1497c77cf3..e0423b07b4 100644
--- a/lib/ssl/src/dtls_packet_demux.erl
+++ b/lib/ssl/src/dtls_packet_demux.erl
@@ -24,6 +24,7 @@
-behaviour(gen_server).
-include("ssl_internal.hrl").
+-include_lib("kernel/include/logger.hrl").
%% API
-export([start_link/5, active_once/3, accept/2, sockname/1, close/1,
@@ -144,13 +145,13 @@ handle_info({Transport, Socket, IP, InPortNo, _} = Msg, #state{listener = Socket
%% UDP socket does not have a connection and should not receive an econnreset
%% This does however happens on some windows versions. Just ignoring it
%% appears to make things work as expected!
-handle_info({Error, Socket, econnreset = Error}, #state{listener = Socket, transport = {_,_,_, udp_error}} = State) ->
+handle_info({udp_error, Socket, econnreset = Error}, #state{listener = Socket, transport = {_,_,_, udp_error}} = State) ->
Report = io_lib:format("Ignore SSL UDP Listener: Socket error: ~p ~n", [Error]),
- error_logger:info_report(Report),
+ ?LOG_NOTICE(Report),
{noreply, State};
-handle_info({Error, Socket, Error}, #state{listener = Socket, transport = {_,_,_, Error}} = State) ->
+handle_info({ErrorTag, Socket, Error}, #state{listener = Socket, transport = {_,_,_, ErrorTag}} = State) ->
Report = io_lib:format("SSL Packet muliplxer shutdown: Socket error: ~p ~n", [Error]),
- error_logger:info_report(Report),
+ ?LOG_NOTICE(Report),
{noreply, State#state{close=true}};
handle_info({'DOWN', _, process, Pid, _}, #state{clients = Clients,
@@ -297,6 +298,9 @@ do_set_emulated_opts([], Opts) ->
Opts;
do_set_emulated_opts([{mode, Value} | Rest], Opts) ->
do_set_emulated_opts(Rest, Opts#socket_options{mode = Value});
+do_set_emulated_opts([{active, N0} | Rest], Opts=#socket_options{active = Active}) when is_integer(N0) ->
+ N = tls_socket:update_active_n(N0, Active),
+ do_set_emulated_opts(Rest, Opts#socket_options{active = N});
do_set_emulated_opts([{active, Value} | Rest], Opts) ->
do_set_emulated_opts(Rest, Opts#socket_options{active = Value}).
diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl
index b7346d3ec8..a4846f42c5 100644
--- a/lib/ssl/src/dtls_record.erl
+++ b/lib/ssl/src/dtls_record.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2019. All 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,7 @@
-include("ssl_cipher.hrl").
%% Handling of incoming data
--export([get_dtls_records/3, init_connection_states/2, empty_connection_state/1]).
+-export([get_dtls_records/4, init_connection_states/2, empty_connection_state/1]).
-export([save_current_connection_state/2, next_epoch/2, get_connection_state_by_epoch/3, replay_detect/2,
init_connection_state_seq/2, current_connection_state_epoch/2]).
@@ -49,9 +49,8 @@
is_acceptable_version/2, hello_version/2]).
--export_type([dtls_version/0, dtls_atom_version/0]).
+-export_type([dtls_atom_version/0]).
--type dtls_version() :: ssl_record:ssl_version().
-type dtls_atom_version() :: dtlsv1 | 'dtlsv1.2'.
-define(REPLAY_WINDOW_SIZE, 64).
@@ -135,7 +134,7 @@ set_connection_state_by_epoch(ReadState, Epoch, #{saved_read := #{epoch := Epoch
States#{saved_read := ReadState}.
%%--------------------------------------------------------------------
--spec init_connection_state_seq(dtls_version(), ssl_record:connection_states()) ->
+-spec init_connection_state_seq(ssl_record:ssl_version(), ssl_record:connection_states()) ->
ssl_record:connection_state().
%%
%% Description: Copy the read sequence number to the write sequence number
@@ -163,24 +162,25 @@ current_connection_state_epoch(#{current_write := #{epoch := Epoch}},
Epoch.
%%--------------------------------------------------------------------
--spec get_dtls_records(binary(), [dtls_version()], binary()) -> {[binary()], binary()} | #alert{}.
+-spec get_dtls_records(binary(), [ssl_record:ssl_version()], binary(),
+ #ssl_options{}) -> {[binary()], binary()} | #alert{}.
%%
%% Description: Given old buffer and new data from UDP/SCTP, packs up a records
%% and returns it as a list of tls_compressed binaries also returns leftover
%% data
%%--------------------------------------------------------------------
-get_dtls_records(Data, Versions, Buffer) ->
+get_dtls_records(Data, Versions, Buffer, SslOpts) ->
BinData = list_to_binary([Buffer, Data]),
case erlang:byte_size(BinData) of
N when N >= 3 ->
case assert_version(BinData, Versions) of
true ->
- get_dtls_records_aux(BinData, []);
+ get_dtls_records_aux(BinData, [], SslOpts);
false ->
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
end;
_ ->
- get_dtls_records_aux(BinData, [])
+ get_dtls_records_aux(BinData, [], SslOpts)
end.
%%====================================================================
@@ -188,7 +188,7 @@ get_dtls_records(Data, Versions, Buffer) ->
%%====================================================================
%%--------------------------------------------------------------------
--spec encode_handshake(iolist(), dtls_version(), integer(), ssl_record:connection_states()) ->
+-spec encode_handshake(iolist(), ssl_record:ssl_version(), integer(), ssl_record:connection_states()) ->
{iolist(), ssl_record:connection_states()}.
%
%% Description: Encodes a handshake message to send on the ssl-socket.
@@ -198,7 +198,7 @@ encode_handshake(Frag, Version, Epoch, ConnectionStates) ->
%%--------------------------------------------------------------------
--spec encode_alert_record(#alert{}, dtls_version(), ssl_record:connection_states()) ->
+-spec encode_alert_record(#alert{}, ssl_record:ssl_version(), ssl_record:connection_states()) ->
{iolist(), ssl_record:connection_states()}.
%%
%% Description: Encodes an alert message to send on the ssl-socket.
@@ -210,7 +210,7 @@ encode_alert_record(#alert{level = Level, description = Description},
ConnectionStates).
%%--------------------------------------------------------------------
--spec encode_change_cipher_spec(dtls_version(), integer(), ssl_record:connection_states()) ->
+-spec encode_change_cipher_spec(ssl_record:ssl_version(), integer(), ssl_record:connection_states()) ->
{iolist(), ssl_record:connection_states()}.
%%
%% Description: Encodes a change_cipher_spec-message to send on the ssl socket.
@@ -219,7 +219,7 @@ encode_change_cipher_spec(Version, Epoch, ConnectionStates) ->
encode_plain_text(?CHANGE_CIPHER_SPEC, Version, Epoch, ?byte(?CHANGE_CIPHER_SPEC_PROTO), ConnectionStates).
%%--------------------------------------------------------------------
--spec encode_data(binary(), dtls_version(), ssl_record:connection_states()) ->
+-spec encode_data(binary(), ssl_record:ssl_version(), ssl_record:connection_states()) ->
{iolist(),ssl_record:connection_states()}.
%%
%% Description: Encodes data to send on the ssl-socket.
@@ -248,8 +248,8 @@ decode_cipher_text(#ssl_tls{epoch = Epoch} = CipherText, ConnnectionStates0) ->
%%====================================================================
%%--------------------------------------------------------------------
--spec protocol_version(dtls_atom_version() | dtls_version()) ->
- dtls_version() | dtls_atom_version().
+-spec protocol_version(dtls_atom_version() | ssl_record:ssl_version()) ->
+ ssl_record:ssl_version() | dtls_atom_version().
%%
%% Description: Creates a protocol version record from a version atom
%% or vice versa.
@@ -263,7 +263,7 @@ protocol_version({254, 253}) ->
protocol_version({254, 255}) ->
dtlsv1.
%%--------------------------------------------------------------------
--spec lowest_protocol_version(dtls_version(), dtls_version()) -> dtls_version().
+-spec lowest_protocol_version(ssl_record:ssl_version(), ssl_record:ssl_version()) -> ssl_record:ssl_version().
%%
%% Description: Lowes protocol version of two given versions
%%--------------------------------------------------------------------
@@ -277,7 +277,7 @@ lowest_protocol_version(_,Version) ->
Version.
%%--------------------------------------------------------------------
--spec lowest_protocol_version([dtls_version()]) -> dtls_version().
+-spec lowest_protocol_version([ssl_record:ssl_version()]) -> ssl_record:ssl_version().
%%
%% Description: Lowest protocol version present in a list
%%--------------------------------------------------------------------
@@ -288,7 +288,7 @@ lowest_protocol_version(Versions) ->
lowest_list_protocol_version(Ver, Vers).
%%--------------------------------------------------------------------
--spec highest_protocol_version([dtls_version()]) -> dtls_version().
+-spec highest_protocol_version([ssl_record:ssl_version()]) -> ssl_record:ssl_version().
%%
%% Description: Highest protocol version present in a list
%%--------------------------------------------------------------------
@@ -299,7 +299,7 @@ highest_protocol_version(Versions) ->
highest_list_protocol_version(Ver, Vers).
%%--------------------------------------------------------------------
--spec highest_protocol_version(dtls_version(), dtls_version()) -> dtls_version().
+-spec highest_protocol_version(ssl_record:ssl_version(), ssl_record:ssl_version()) -> ssl_record:ssl_version().
%%
%% Description: Highest protocol version of two given versions
%%--------------------------------------------------------------------
@@ -315,7 +315,7 @@ highest_protocol_version(_,Version) ->
Version.
%%--------------------------------------------------------------------
--spec is_higher(V1 :: dtls_version(), V2::dtls_version()) -> boolean().
+-spec is_higher(V1 :: ssl_record:ssl_version(), V2::ssl_record:ssl_version()) -> boolean().
%%
%% Description: Is V1 > V2
%%--------------------------------------------------------------------
@@ -327,7 +327,7 @@ is_higher(_, _) ->
false.
%%--------------------------------------------------------------------
--spec supported_protocol_versions() -> [dtls_version()].
+-spec supported_protocol_versions() -> [ssl_record:ssl_version()].
%%
%% Description: Protocol versions supported
%%--------------------------------------------------------------------
@@ -370,7 +370,7 @@ supported_protocol_versions([_|_] = Vsns) ->
end.
%%--------------------------------------------------------------------
--spec is_acceptable_version(dtls_version(), Supported :: [dtls_version()]) -> boolean().
+-spec is_acceptable_version(ssl_record:ssl_version(), Supported :: [ssl_record:ssl_version()]) -> boolean().
%%
%% Description: ssl version 2 is not acceptable security risks are too big.
%%
@@ -378,7 +378,7 @@ supported_protocol_versions([_|_] = Vsns) ->
is_acceptable_version(Version, Versions) ->
lists:member(Version, Versions).
--spec hello_version(dtls_version(), [dtls_version()]) -> dtls_version().
+-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} ->
@@ -410,42 +410,47 @@ assert_version(<<?BYTE(_), ?BYTE(MajVer), ?BYTE(MinVer), _/binary>>, Versions) -
get_dtls_records_aux(<<?BYTE(?APPLICATION_DATA),?BYTE(MajVer),?BYTE(MinVer),
?UINT16(Epoch), ?UINT48(SequenceNumber),
- ?UINT16(Length), Data:Length/binary, Rest/binary>>,
- Acc) ->
+ ?UINT16(Length), Data:Length/binary, Rest/binary>> = RawDTLSRecord,
+ Acc, SslOpts) ->
+ ssl_logger:debug(SslOpts#ssl_options.log_level, inbound, 'record', [RawDTLSRecord]),
get_dtls_records_aux(Rest, [#ssl_tls{type = ?APPLICATION_DATA,
version = {MajVer, MinVer},
epoch = Epoch, sequence_number = SequenceNumber,
- fragment = Data} | Acc]);
+ fragment = Data} | Acc], SslOpts);
get_dtls_records_aux(<<?BYTE(?HANDSHAKE),?BYTE(MajVer),?BYTE(MinVer),
?UINT16(Epoch), ?UINT48(SequenceNumber),
?UINT16(Length),
- Data:Length/binary, Rest/binary>>, Acc) when MajVer >= 128 ->
+ Data:Length/binary, Rest/binary>> = RawDTLSRecord,
+ Acc, SslOpts) when MajVer >= 128 ->
+ ssl_logger:debug(SslOpts#ssl_options.log_level, inbound, 'record', [RawDTLSRecord]),
get_dtls_records_aux(Rest, [#ssl_tls{type = ?HANDSHAKE,
version = {MajVer, MinVer},
epoch = Epoch, sequence_number = SequenceNumber,
- fragment = Data} | Acc]);
+ fragment = Data} | Acc], SslOpts);
get_dtls_records_aux(<<?BYTE(?ALERT),?BYTE(MajVer),?BYTE(MinVer),
?UINT16(Epoch), ?UINT48(SequenceNumber),
?UINT16(Length), Data:Length/binary,
- Rest/binary>>, Acc) ->
+ Rest/binary>> = RawDTLSRecord, Acc, SslOpts) ->
+ ssl_logger:debug(SslOpts#ssl_options.log_level, inbound, 'record', [RawDTLSRecord]),
get_dtls_records_aux(Rest, [#ssl_tls{type = ?ALERT,
version = {MajVer, MinVer},
epoch = Epoch, sequence_number = SequenceNumber,
- fragment = Data} | Acc]);
+ fragment = Data} | Acc], SslOpts);
get_dtls_records_aux(<<?BYTE(?CHANGE_CIPHER_SPEC),?BYTE(MajVer),?BYTE(MinVer),
?UINT16(Epoch), ?UINT48(SequenceNumber),
- ?UINT16(Length), Data:Length/binary, Rest/binary>>,
- Acc) ->
+ ?UINT16(Length), Data:Length/binary, Rest/binary>> = RawDTLSRecord,
+ Acc, SslOpts) ->
+ ssl_logger:debug(SslOpts#ssl_options.log_level, inbound, 'record', [RawDTLSRecord]),
get_dtls_records_aux(Rest, [#ssl_tls{type = ?CHANGE_CIPHER_SPEC,
version = {MajVer, MinVer},
epoch = Epoch, sequence_number = SequenceNumber,
- fragment = Data} | Acc]);
+ fragment = Data} | Acc], SslOpts);
get_dtls_records_aux(<<?BYTE(_), ?BYTE(_MajVer), ?BYTE(_MinVer),
?UINT16(Length), _/binary>>,
- _Acc) when Length > ?MAX_CIPHER_TEXT_LENGTH ->
+ _Acc, _) when Length > ?MAX_CIPHER_TEXT_LENGTH ->
?ALERT_REC(?FATAL, ?RECORD_OVERFLOW);
-get_dtls_records_aux(Data, Acc) ->
+get_dtls_records_aux(Data, Acc, _) ->
case size(Data) =< ?MAX_CIPHER_TEXT_LENGTH + ?INITIAL_BYTES of
true ->
{lists:reverse(Acc), Data};
@@ -547,15 +552,15 @@ decode_cipher_text(#ssl_tls{type = Type, version = Version,
compression_algorithm = CompAlg}} = ReadState0,
ConnnectionStates0) ->
AAD = start_additional_data(Type, Version, Epoch, Seq),
- CipherS1 = ssl_record:nonce_seed(BulkCipherAlgo, <<?UINT16(Epoch), ?UINT48(Seq)>>, CipherS0),
+ CipherS = ssl_record:nonce_seed(BulkCipherAlgo, <<?UINT16(Epoch), ?UINT48(Seq)>>, CipherS0),
TLSVersion = dtls_v1:corresponding_tls_version(Version),
- case ssl_record:decipher_aead(BulkCipherAlgo, CipherS1, AAD, CipherFragment, TLSVersion) of
- {PlainFragment, CipherState} ->
- {Plain, CompressionS1} = ssl_record:uncompress(CompAlg,
+ case ssl_record:decipher_aead(BulkCipherAlgo, CipherS, AAD, CipherFragment, TLSVersion) of
+ PlainFragment when is_binary(PlainFragment) ->
+ {Plain, CompressionS} = ssl_record:uncompress(CompAlg,
PlainFragment, CompressionS0),
- ReadState0 = ReadState0#{compression_state => CompressionS1,
- cipher_state => CipherState},
- ReadState = update_replay_window(Seq, ReadState0),
+ ReadState1 = ReadState0#{compression_state := CompressionS,
+ cipher_state := CipherS},
+ ReadState = update_replay_window(Seq, ReadState1),
ConnnectionStates = set_connection_state_by_epoch(ReadState, Epoch, ConnnectionStates0, read),
{CipherText#ssl_tls{fragment = Plain}, ConnnectionStates};
#alert{} = Alert ->
diff --git a/lib/ssl/src/dtls_socket.erl b/lib/ssl/src/dtls_socket.erl
index 2001afd02f..4d07372e31 100644
--- a/lib/ssl/src/dtls_socket.erl
+++ b/lib/ssl/src/dtls_socket.erl
@@ -38,7 +38,9 @@ listen(Port, #config{transport_info = TransportInfo,
case dtls_listener_sup:start_child([Port, TransportInfo, emulated_socket_options(EmOpts, #socket_options{}),
Options ++ internal_inet_values(), SslOpts]) of
{ok, Pid} ->
- {ok, #sslsocket{pid = {dtls, Config#config{dtls_handler = {Pid, Port}}}}};
+ Socket = #sslsocket{pid = {dtls, Config#config{dtls_handler = {Pid, Port}}}},
+ check_active_n(EmOpts, Socket),
+ {ok, Socket};
Err = {error, _} ->
Err
end.
@@ -81,8 +83,9 @@ socket(Pids, Transport, Socket, ConnectionCb) ->
#sslsocket{pid = Pids,
%% "The name "fd" is keept for backwards compatibility
fd = {Transport, Socket, ConnectionCb}}.
-setopts(_, #sslsocket{pid = {dtls, #config{dtls_handler = {ListenPid, _}}}}, Options) ->
- SplitOpts = tls_socket:split_options(Options),
+setopts(_, Socket = #sslsocket{pid = {dtls, #config{dtls_handler = {ListenPid, _}}}}, Options) ->
+ SplitOpts = {_, EmOpts} = tls_socket:split_options(Options),
+ check_active_n(EmOpts, Socket),
dtls_packet_demux:set_sock_opts(ListenPid, SplitOpts);
%%% Following clauses will not be called for emulated options, they are handled in the connection process
setopts(gen_udp, Socket, Options) ->
@@ -90,6 +93,32 @@ setopts(gen_udp, Socket, Options) ->
setopts(Transport, Socket, Options) ->
Transport:setopts(Socket, Options).
+check_active_n(EmulatedOpts, Socket = #sslsocket{pid = {dtls, #config{dtls_handler = {ListenPid, _}}}}) ->
+ %% We check the resulting options to send an ssl_passive message if necessary.
+ case proplists:lookup(active, EmulatedOpts) of
+ %% The provided value is out of bound.
+ {_, N} when is_integer(N), N < -32768 ->
+ throw(einval);
+ {_, N} when is_integer(N), N > 32767 ->
+ throw(einval);
+ {_, N} when is_integer(N) ->
+ {ok, #socket_options{active = Active}, _} = dtls_packet_demux:get_all_opts(ListenPid),
+ case Active of
+ Atom when is_atom(Atom), N =< 0 ->
+ self() ! {ssl_passive, Socket};
+ %% The result of the addition is out of bound.
+ %% We do not need to check < -32768 because Active can't be below 1.
+ A when is_integer(A), A + N > 32767 ->
+ throw(einval);
+ A when is_integer(A), A + N =< 0 ->
+ self() ! {ssl_passive, Socket};
+ _ ->
+ ok
+ end;
+ _ ->
+ ok
+ end.
+
getopts(_, #sslsocket{pid = {dtls, #config{dtls_handler = {ListenPid, _}}}}, Options) ->
SplitOpts = tls_socket:split_options(Options),
dtls_packet_demux:get_sock_opts(ListenPid, SplitOpts);
@@ -161,9 +190,18 @@ emulated_socket_options(InetValues, #socket_options{
mode = proplists:get_value(mode, InetValues, Mode),
packet = proplists:get_value(packet, InetValues, Packet),
packet_size = proplists:get_value(packet_size, InetValues, PacketSize),
- active = proplists:get_value(active, InetValues, Active)
+ active = emulated_active_option(InetValues, Active)
}.
+emulated_active_option([], Active) ->
+ Active;
+emulated_active_option([{active, Active} | _], _) when Active =< 0 ->
+ false;
+emulated_active_option([{active, Active} | _], _) ->
+ Active;
+emulated_active_option([_|Tail], Active) ->
+ emulated_active_option(Tail, Active).
+
emulated_options([{mode, Value} = Opt |Opts], Inet, Emulated) ->
validate_inet_option(mode, Value),
emulated_options(Opts, Inet, [Opt | proplists:delete(mode, Emulated)]);
@@ -185,6 +223,9 @@ validate_inet_option(mode, Value)
when Value =/= list, Value =/= binary ->
throw({error, {options, {mode,Value}}});
validate_inet_option(active, Value)
+ when Value >= -32768, Value =< 32767 ->
+ ok;
+validate_inet_option(active, Value)
when Value =/= true, Value =/= false, Value =/= once ->
throw({error, {options, {active,Value}}});
validate_inet_option(_, _) ->
diff --git a/lib/ssl/src/inet_tls_dist.erl b/lib/ssl/src/inet_tls_dist.erl
index a4f8bb7562..e7fab7ebc5 100644
--- a/lib/ssl/src/inet_tls_dist.erl
+++ b/lib/ssl/src/inet_tls_dist.erl
@@ -41,6 +41,7 @@
-include_lib("public_key/include/public_key.hrl").
-include("ssl_api.hrl").
+-include_lib("kernel/include/logger.hrl").
%% -------------------------------------------------------------------------
@@ -225,7 +226,7 @@ accept_loop(Driver, Listen, Kernel) ->
true ->
accept_loop(Driver, Listen, Kernel, Socket);
{false,IP} ->
- error_logger:error_msg(
+ ?LOG_ERROR(
"** Connection attempt from "
"disallowed IP ~w ** ~n", [IP]),
?shutdown2(no_node, trace({disallowed, IP}))
@@ -260,7 +261,7 @@ accept_loop(Driver, Listen, Kernel, Socket) ->
{error, {options, _}} = Error ->
%% Bad options: that's probably our fault.
%% Let's log that.
- error_logger:error_msg(
+ ?LOG_ERROR(
"Cannot accept TLS distribution connection: ~s~n",
[ssl:format_error(Error)]),
gen_tcp:close(Socket),
@@ -436,7 +437,7 @@ allowed_nodes(SslSocket, Allowed) ->
PeerCert, allowed_hosts(Allowed), PeerIP)
of
[] ->
- error_logger:error_msg(
+ ?LOG_ERROR(
"** Connection attempt from "
"disallowed node(s) ~p ** ~n", [PeerIP]),
?shutdown2(
@@ -480,22 +481,25 @@ 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).
gen_setup(Driver, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
Kernel = self(),
monitor_pid(
- spawn_opt(
- fun() ->
- do_setup(
- Driver, Kernel, Node, Type,
- MyNode, LongOrShortNames, SetupTime)
- end,
- [link, {priority, max}])).
+ spawn_opt(setup_fun(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime),
+ [link, {priority, max}])).
+
+-spec setup_fun(_,_,_,_,_,_,_) -> fun(() -> no_return()).
+setup_fun(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
+ fun() ->
+ do_setup(
+ Driver, Kernel, Node, Type,
+ MyNode, LongOrShortNames, SetupTime)
+ 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(),
@@ -520,6 +524,8 @@ do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
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))),
dist_util:reset_timer(Timer),
@@ -564,7 +570,7 @@ gen_close(Driver, Socket) ->
%% Determine if EPMD module supports address resolving. Default
%% is to use inet_tcp:getaddr/2.
%% ------------------------------------------------------------
-get_address_resolver(EpmdModule, Driver) ->
+get_address_resolver(EpmdModule, _Driver) ->
case erlang:function_exported(EpmdModule, address_please, 3) of
true -> {EpmdModule, address_please};
_ -> {erl_epmd, address_please}
@@ -690,12 +696,12 @@ split_node(Driver, Node, LongOrShortNames) ->
{node, Name, Host} ->
check_node(Driver, Node, Name, Host, LongOrShortNames);
{host, _} ->
- error_logger:error_msg(
+ ?LOG_ERROR(
"** Nodename ~p illegal, no '@' character **~n",
[Node]),
?shutdown2(Node, trace({illegal_node_n@me, Node}));
_ ->
- error_logger:error_msg(
+ ?LOG_ERROR(
"** Nodename ~p illegal **~n", [Node]),
?shutdown2(Node, trace({illegal_node_name, Node}))
end.
@@ -707,7 +713,7 @@ check_node(Driver, Node, Name, Host, LongOrShortNames) ->
{ok, _} ->
{Name, Host};
_ ->
- error_logger:error_msg(
+ ?LOG_ERROR(
"** System running to use "
"fully qualified hostnames **~n"
"** Hostname ~s is illegal **~n",
@@ -715,7 +721,7 @@ check_node(Driver, Node, Name, Host, LongOrShortNames) ->
?shutdown2(Node, trace({not_longnames, Host}))
end;
[_,_|_] when LongOrShortNames =:= shortnames ->
- error_logger:error_msg(
+ ?LOG_ERROR(
"** System NOT running to use "
"fully qualified hostnames **~n"
"** Hostname ~s is illegal **~n",
@@ -845,13 +851,13 @@ monitor_pid(Pid) ->
%% MRef = erlang:monitor(process, Pid),
%% receive
%% {'DOWN', MRef, _, _, normal} ->
- %% error_logger:error_report(
- %% [dist_proc_died,
+ %% ?LOG_ERROR(
+ %% [{slogan, dist_proc_died},
%% {reason, normal},
%% {pid, Pid}]);
%% {'DOWN', MRef, _, _, Reason} ->
- %% error_logger:info_report(
- %% [dist_proc_died,
+ %% ?LOG_NOTICE(
+ %% [{slogan, dist_proc_died},
%% {reason, Reason},
%% {pid, Pid}])
%% end
diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src
index 936df12e70..e7a4d73ec4 100644
--- a/lib/ssl/src/ssl.app.src
+++ b/lib/ssl/src/ssl.app.src
@@ -4,13 +4,17 @@
{modules, [
%% TLS/SSL
tls_connection,
+ tls_connection_1_3,
tls_handshake,
+ tls_handshake_1_3,
tls_record,
+ tls_record_1_3,
tls_socket,
tls_v1,
ssl_v3,
tls_connection_sup,
tls_sender,
+ ssl_dh_groups,
%% DTLS
dtls_connection,
dtls_handshake,
@@ -51,6 +55,8 @@
ssl_crl_cache,
ssl_crl_cache_api,
ssl_crl_hash_dir,
+ %% Logging
+ ssl_logger,
%% App structure
ssl_app,
ssl_sup,
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index 03a1e40bfc..5a2d31ffc2 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2019. All 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,26 +51,349 @@
%% SSL/TLS protocol handling
-export([cipher_suites/0, cipher_suites/1, cipher_suites/2, filter_cipher_suites/2,
prepend_cipher_suites/2, append_cipher_suites/2,
- eccs/0, eccs/1, versions/0,
+ eccs/0, eccs/1, versions/0, groups/0, groups/1,
format_error/1, renegotiate/1, prf/5, negotiated_protocol/1,
connection_information/1, connection_information/2]).
%% Misc
--export([handle_options/2, tls_version/1, new_ssl_options/3, suite_to_str/1]).
+-export([handle_options/2, tls_version/1, new_ssl_options/3, suite_to_str/1,
+ set_log_level/1]).
-deprecated({ssl_accept, 1, eventually}).
-deprecated({ssl_accept, 2, eventually}).
-deprecated({ssl_accept, 3, eventually}).
+-export_type([socket/0,
+ sslsocket/0,
+ socket_option/0,
+ active_msgs/0,
+ host/0,
+ tls_option/0,
+ tls_client_option/0,
+ tls_server_option/0,
+ erl_cipher_suite/0,
+ old_cipher_suite/0,
+ ciphers/0,
+ cipher/0,
+ hash/0,
+ key/0,
+ kex_algo/0,
+ prf_random/0,
+ cipher_filters/0,
+ sign_algo/0,
+ protocol_version/0,
+ protocol_extensions/0,
+ session_id/0,
+ error_alert/0,
+ srp_param_type/0]).
+
+%% -------------------------------------------------------------------------------------------------------
+-type socket() :: gen_tcp:socket().
+-type socket_option() :: gen_tcp:connect_option() | gen_tcp:listen_option() | gen_udp:option().
+-type sslsocket() :: any().
+-type tls_option() :: tls_client_option() | tls_server_option().
+-type tls_client_option() :: client_option() | common_option() | socket_option() | transport_option().
+-type tls_server_option() :: server_option() | common_option() | socket_option() | transport_option().
+-type active_msgs() :: {ssl, sslsocket(), Data::binary() | list()} | {ssl_closed, sslsocket()} |
+ {ssl_error, sslsocket(), Reason::term()} | {ssl_passive, sslsocket()}.
+-type transport_option() :: {cb_info, {CallbackModule::atom(), DataTag::atom(),
+ ClosedTag::atom(), ErrTag::atom()}}.
+-type host() :: hostname() | ip_address().
+-type hostname() :: string().
+-type ip_address() :: inet:ip_address().
+-type session_id() :: binary().
+-type protocol_version() :: tls_version() | dtls_version().
+-type tls_version() :: tlsv1 | 'tlsv1.1' | 'tlsv1.2' | 'tlsv1.3' | legacy_version().
+-type dtls_version() :: 'dtlsv1' | 'dtlsv1.2'.
+-type legacy_version() :: sslv3.
+-type verify_type() :: verify_none | verify_peer.
+-type cipher() :: aes_128_cbc |
+ aes_256_cbc |
+ aes_128_gcm |
+ aes_256_gcm |
+ chacha20_poly1305 |
+ legacy_cipher().
+-type legacy_cipher() :: rc4_128 |
+ des_cbc |
+ '3des_ede_cbc'.
+
+-type hash() :: sha |
+ sha2() |
+ legacy_hash().
+
+-type sha2() :: sha224 |
+ sha256 |
+ sha384 |
+ sha512.
+
+-type legacy_hash() :: md5.
+
+-type sign_algo() :: rsa | dsa | ecdsa.
+
+-type sign_scheme() :: rsa_pkcs1_sha256
+ | rsa_pkcs1_sha384
+ | rsa_pkcs1_sha512
+ | ecdsa_secp256r1_sha256
+ | ecdsa_secp384r1_sha384
+ | ecdsa_secp521r1_sha512
+ | rsa_pss_rsae_sha256
+ | rsa_pss_rsae_sha384
+ | rsa_pss_rsae_sha512
+ | rsa_pss_pss_sha256
+ | rsa_pss_pss_sha384
+ | rsa_pss_pss_sha512
+ | rsa_pkcs1_sha1
+ | ecdsa_sha1.
+-type kex_algo() :: rsa |
+ dhe_rsa | dhe_dss |
+ ecdhe_ecdsa | ecdh_ecdsa | ecdh_rsa |
+ srp_rsa| srp_dss |
+ psk | dhe_psk | rsa_psk |
+ dh_anon | ecdh_anon | srp_anon |
+ any. %% TLS 1.3
+-type erl_cipher_suite() :: #{key_exchange := kex_algo(),
+ cipher := cipher(),
+ mac := hash() | aead,
+ prf := hash() | default_prf %% Old cipher suites, version dependent
+ }.
+
+-type old_cipher_suite() :: {kex_algo(), cipher(), hash()} % Pre TLS 1.2
+ %% TLS 1.2, internally PRE TLS 1.2 will use default_prf
+ | {kex_algo(), cipher(), hash() | aead, hash()}.
+
+-type named_curve() :: 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.
+
+-type srp_param_type() :: srp_1024 |
+ srp_1536 |
+ srp_2048 |
+ srp_3072 |
+ srp_4096 |
+ srp_6144 |
+ srp_8192.
+
+-type error_alert() :: {tls_alert, {tls_alert(), Description::string()}}.
+
+-type tls_alert() :: close_notify |
+ unexpected_message |
+ bad_record_mac |
+ record_overflow |
+ handshake_failure |
+ bad_certificate |
+ unsupported_certificate |
+ certificate_revoked |
+ certificate_expired |
+ certificate_unknown |
+ illegal_parameter |
+ unknown_ca |
+ access_denied |
+ decode_error |
+ decrypt_error |
+ export_restriction|
+ protocol_version |
+ insufficient_security |
+ internal_error |
+ inappropriate_fallback |
+ user_canceled |
+ no_renegotiation |
+ unsupported_extension |
+ certificate_unobtainable |
+ unrecognized_name |
+ bad_certificate_status_response |
+ bad_certificate_hash_value |
+ unknown_psk_identity |
+ no_application_protocol.
+%% -------------------------------------------------------------------------------------------------------
+-type common_option() :: {protocol, protocol()} |
+ {handshake, handshake_completion()} |
+ {cert, cert()} |
+ {certfile, cert_pem()} |
+ {key, key()} |
+ {keyfile, key_pem()} |
+ {password, key_password()} |
+ {ciphers, cipher_suites()} |
+ {eccs, eccs()} |
+ {signature_algs_cert, signature_schemes()} |
+ {secure_renegotiate, secure_renegotiation()} |
+ {depth, allowed_cert_chain_length()} |
+ {verify_fun, custom_verify()} |
+ {crl_check, crl_check()} |
+ {crl_cache, crl_cache_opts()} |
+ {max_handshake_size, handshake_size()} |
+ {partial_chain, root_fun()} |
+ {versions, protocol_versions()} |
+ {user_lookup_fun, custom_user_lookup()} |
+ {log_level, logging_level()} |
+ {log_alert, log_alert()} |
+ {hibernate_after, hibernate_after()} |
+ {padding_check, padding_check()} |
+ {beast_mitigation, beast_mitigation()} |
+ {ssl_imp, ssl_imp()}.
+
+-type protocol() :: tls | dtls.
+-type handshake_completion() :: hello | full.
+-type cert() :: public_key:der_encoded().
+-type cert_pem() :: file:filename().
+-type key() :: {'RSAPrivateKey'| 'DSAPrivateKey' | 'ECPrivateKey' |'PrivateKeyInfo',
+ public_key:der_encoded()} |
+ #{algorithm := rsa | dss | ecdsa,
+ engine := crypto:engine_ref(),
+ key_id := crypto:key_id(),
+ password => crypto:password()}.
+-type key_pem() :: file:filename().
+-type key_password() :: string().
+-type cipher_suites() :: ciphers().
+-type ciphers() :: [erl_cipher_suite()] |
+ string(). % (according to old API)
+-type cipher_filters() :: list({key_exchange | cipher | mac | prf,
+ algo_filter()}).
+-type algo_filter() :: fun((kex_algo()|cipher()|hash()|aead|default_prf) -> true | false).
+-type eccs() :: [named_curve()].
+-type secure_renegotiation() :: boolean().
+-type allowed_cert_chain_length() :: integer().
+
+-type custom_verify() :: {Verifyfun :: fun(), InitialUserState :: term()}.
+-type crl_check() :: boolean() | peer | best_effort.
+-type crl_cache_opts() :: [term()].
+-type handshake_size() :: integer().
+-type hibernate_after() :: timeout().
+-type root_fun() :: fun().
+-type protocol_versions() :: [protocol_version()].
+-type signature_algs() :: [{hash(), sign_algo()}].
+-type signature_schemes() :: [sign_scheme()].
+-type custom_user_lookup() :: {Lookupfun :: fun(), UserState :: term()}.
+-type padding_check() :: boolean().
+-type beast_mitigation() :: one_n_minus_one | zero_n | disabled.
+-type srp_identity() :: {Username :: string(), Password :: string()}.
+-type psk_identity() :: string().
+-type log_alert() :: boolean().
+-type logging_level() :: logger:level().
+
+%% -------------------------------------------------------------------------------------------------------
+
+-type client_option() :: {verify, client_verify_type()} |
+ {reuse_session, client_reuse_session()} |
+ {reuse_sessions, client_reuse_sessions()} |
+ {cacerts, client_cacerts()} |
+ {cacertfile, client_cafile()} |
+ {alpn_advertised_protocols, client_alpn()} |
+ {client_preferred_next_protocols, client_preferred_next_protocols()} |
+ {psk_identity, client_psk_identity()} |
+ {srp_identity, client_srp_identity()} |
+ {server_name_indication, sni()} |
+ {customize_hostname_check, customize_hostname_check()} |
+ {signature_algs, client_signature_algs()} |
+ {fallback, fallback()}.
+
+-type client_verify_type() :: verify_type().
+-type client_reuse_session() :: session_id().
+-type client_reuse_sessions() :: boolean() | save.
+-type client_cacerts() :: [public_key:der_encoded()].
+-type client_cafile() :: file:filename().
+-type app_level_protocol() :: binary().
+-type client_alpn() :: [app_level_protocol()].
+-type client_preferred_next_protocols() :: {Precedence :: server | client,
+ ClientPrefs :: [app_level_protocol()]} |
+ {Precedence :: server | client,
+ ClientPrefs :: [app_level_protocol()],
+ Default::app_level_protocol()}.
+-type client_psk_identity() :: psk_identity().
+-type client_srp_identity() :: srp_identity().
+-type customize_hostname_check() :: list().
+-type sni() :: HostName :: hostname() | disable.
+-type client_signature_algs() :: signature_algs().
+-type fallback() :: boolean().
+-type ssl_imp() :: new | old.
+
+%% -------------------------------------------------------------------------------------------------------
+
+-type server_option() :: {cacerts, server_cacerts()} |
+ {cacertfile, server_cafile()} |
+ {dh, dh_der()} |
+ {dhfile, dh_file()} |
+ {verify, server_verify_type()} |
+ {fail_if_no_peer_cert, fail_if_no_peer_cert()} |
+ {reuse_sessions, server_reuse_sessions()} |
+ {reuse_session, server_reuse_session()} |
+ {alpn_preferred_protocols, server_alpn()} |
+ {next_protocols_advertised, server_next_protocol()} |
+ {psk_identity, server_psk_identity()} |
+ {honor_cipher_order, boolean()} |
+ {sni_hosts, sni_hosts()} |
+ {sni_fun, sni_fun()} |
+ {honor_cipher_order, honor_cipher_order()} |
+ {honor_ecc_order, honor_ecc_order()} |
+ {client_renegotiation, client_renegotiation()}|
+ {signature_algs, server_signature_algs()}.
+
+-type server_cacerts() :: [public_key:der_encoded()].
+-type server_cafile() :: file:filename().
+-type server_alpn() :: [app_level_protocol()].
+-type server_next_protocol() :: [app_level_protocol()].
+-type server_psk_identity() :: psk_identity().
+-type dh_der() :: binary().
+-type dh_file() :: file:filename().
+-type server_verify_type() :: verify_type().
+-type fail_if_no_peer_cert() :: boolean().
+-type server_signature_algs() :: signature_algs().
+-type server_reuse_session() :: fun().
+-type server_reuse_sessions() :: boolean().
+-type sni_hosts() :: [{hostname(), [server_option() | common_option()]}].
+-type sni_fun() :: fun().
+-type honor_cipher_order() :: boolean().
+-type honor_ecc_order() :: boolean().
+-type client_renegotiation() :: boolean().
+%% -------------------------------------------------------------------------------------------------------
+-type prf_random() :: client_random | server_random.
+-type protocol_extensions() :: #{renegotiation_info => binary(),
+ signature_algs => signature_algs(),
+ alpn => app_level_protocol(),
+ srp => binary(),
+ next_protocol => app_level_protocol(),
+ ec_point_formats => [0..2],
+ elliptic_curves => [public_key:oid()],
+ sni => hostname()}.
+%% -------------------------------------------------------------------------------------------------------
+
+%%%--------------------------------------------------------------------
+%%% API
+%%%--------------------------------------------------------------------
+
%%--------------------------------------------------------------------
--spec start() -> ok | {error, reason()}.
--spec start(permanent | transient | temporary) -> ok | {error, reason()}.
%%
%% Description: Utility function that starts the ssl and applications
%% that it depends on.
%% see application(3)
%%--------------------------------------------------------------------
+-spec start() -> ok | {error, reason()}.
start() ->
start(temporary).
+-spec start(permanent | transient | temporary) -> ok | {error, reason()}.
start(Type) ->
case application:ensure_all_started(ssl, Type) of
{ok, _} ->
@@ -87,20 +410,17 @@ stop() ->
application:stop(ssl).
%%--------------------------------------------------------------------
--spec connect(host() | port(), [connect_option()]) -> {ok, #sslsocket{}} |
- {error, reason()}.
--spec connect(host() | port(), [connect_option()] | inet:port_number(),
- timeout() | list()) ->
- {ok, #sslsocket{}} | {error, reason()}.
--spec connect(host() | port(), inet:port_number(), list(), timeout()) ->
- {ok, #sslsocket{}} | {error, reason()}.
-
%%
%% Description: Connect to an ssl server.
%%--------------------------------------------------------------------
+-spec connect(host() | port(), [tls_client_option()]) -> {ok, #sslsocket{}} |
+ {error, reason()}.
connect(Socket, SslOptions) when is_port(Socket) ->
connect(Socket, SslOptions, infinity).
+-spec connect(host() | port(), [tls_client_option()] | inet:port_number(),
+ timeout() | list()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
connect(Socket, SslOptions0, Timeout) when is_port(Socket),
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
{Transport,_,_,_} = proplists:get_value(cb_info, SslOptions0,
@@ -117,6 +437,9 @@ connect(Socket, SslOptions0, Timeout) when is_port(Socket),
connect(Host, Port, Options) ->
connect(Host, Port, Options, infinity).
+-spec connect(host() | port(), inet:port_number(), list(), timeout()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+
connect(Host, Port, Options, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
try
{ok, Config} = handle_options(Options, client, Host),
@@ -132,7 +455,7 @@ connect(Host, Port, Options, Timeout) when (is_integer(Timeout) andalso Timeout
end.
%%--------------------------------------------------------------------
--spec listen(inet:port_number(), [listen_option()]) ->{ok, #sslsocket{}} | {error, reason()}.
+-spec listen(inet:port_number(), [tls_server_option()]) ->{ok, #sslsocket{}} | {error, reason()}.
%%
%% Description: Creates an ssl listen socket.
@@ -148,16 +471,16 @@ listen(Port, Options0) ->
Error
end.
%%--------------------------------------------------------------------
--spec transport_accept(#sslsocket{}) -> {ok, #sslsocket{}} |
- {error, reason()}.
--spec transport_accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} |
- {error, reason()}.
%%
%% Description: Performs transport accept on an ssl listen socket
%%--------------------------------------------------------------------
+-spec transport_accept(#sslsocket{}) -> {ok, #sslsocket{}} |
+ {error, reason()}.
transport_accept(ListenSocket) ->
transport_accept(ListenSocket, infinity).
+-spec transport_accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} |
+ {error, reason()}.
transport_accept(#sslsocket{pid = {ListenSocket,
#config{connection_cb = ConnectionCb} = Config}}, Timeout)
when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
@@ -169,25 +492,25 @@ transport_accept(#sslsocket{pid = {ListenSocket,
end.
%%--------------------------------------------------------------------
--spec ssl_accept(#sslsocket{}) -> ok | {error, reason()}.
--spec ssl_accept(#sslsocket{} | port(), timeout()| [ssl_option()
- | transport_option()]) ->
- ok | {ok, #sslsocket{}} | {error, reason()}.
-
--spec ssl_accept(#sslsocket{} | port(), [ssl_option()] | [ssl_option()| transport_option()], timeout()) ->
- ok | {ok, #sslsocket{}} | {error, reason()}.
%%
%% Description: Performs accept on an ssl listen socket. e.i. performs
%% ssl handshake.
%%--------------------------------------------------------------------
+-spec ssl_accept(#sslsocket{}) -> ok | {error, timeout | closed | {options, any()}| error_alert()}.
ssl_accept(ListenSocket) ->
ssl_accept(ListenSocket, [], infinity).
+
+-spec ssl_accept(#sslsocket{} | port(), timeout()| [tls_server_option()]) ->
+ ok | {ok, #sslsocket{}} | {error, timeout | closed | {options, any()}| error_alert()}.
ssl_accept(Socket, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
ssl_accept(Socket, [], Timeout);
ssl_accept(ListenSocket, SslOptions) when is_port(ListenSocket) ->
ssl_accept(ListenSocket, SslOptions, infinity);
ssl_accept(Socket, Timeout) ->
ssl_accept(Socket, [], Timeout).
+
+-spec ssl_accept(#sslsocket{} | port(), [tls_server_option()], timeout()) ->
+ ok | {ok, #sslsocket{}} | {error, timeout | closed | {options, any()}| error_alert()}.
ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket) ->
handshake(Socket, SslOptions, Timeout);
ssl_accept(Socket, SslOptions, Timeout) ->
@@ -198,27 +521,34 @@ ssl_accept(Socket, SslOptions, Timeout) ->
Error
end.
%%--------------------------------------------------------------------
--spec handshake(#sslsocket{}) -> {ok, #sslsocket{}} | {error, reason()}.
--spec handshake(#sslsocket{} | port(), timeout()| [ssl_option()
- | transport_option()]) ->
- {ok, #sslsocket{}} | {error, reason()}.
-
--spec handshake(#sslsocket{} | port(), [ssl_option()] | [ssl_option()| transport_option()], timeout()) ->
- {ok, #sslsocket{}} | {error, reason()}.
%%
%% Description: Performs accept on an ssl listen socket. e.i. performs
%% ssl handshake.
%%--------------------------------------------------------------------
+
+%% Performs the SSL/TLS/DTLS server-side handshake.
+-spec handshake(#sslsocket{}) -> {ok, #sslsocket{}} | {error, timeout | closed | {options, any()} | error_alert()}.
+
handshake(ListenSocket) ->
handshake(ListenSocket, infinity).
+-spec handshake(#sslsocket{} | port(), timeout()| [tls_server_option()]) ->
+ {ok, #sslsocket{}} | {error, timeout | closed | {options, any()} | error_alert()}.
handshake(#sslsocket{} = Socket, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or
(Timeout == infinity) ->
ssl_connection:handshake(Socket, Timeout);
+%% If Socket is a ordinary socket(): upgrades a gen_tcp, or equivalent, socket to
+%% an SSL socket, that is, performs the SSL/TLS server-side handshake and returns
+%% the SSL socket.
+%%
+%% If Socket is an sslsocket(): provides extra SSL/TLS/DTLS options to those
+%% specified in ssl:listen/2 and then performs the SSL/TLS/DTLS handshake.
handshake(ListenSocket, SslOptions) when is_port(ListenSocket) ->
handshake(ListenSocket, SslOptions, infinity).
+-spec handshake(#sslsocket{} | port(), [tls_server_option()], timeout()) ->
+ {ok, #sslsocket{}} | {error, timeout | closed | {options, any()} | error_alert()}.
handshake(#sslsocket{} = Socket, [], Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or
(Timeout == infinity)->
handshake(Socket, Timeout);
@@ -261,7 +591,7 @@ handshake(Socket, SslOptions, Timeout) when is_port(Socket),
%%--------------------------------------------------------------------
--spec handshake_continue(#sslsocket{}, [ssl_option()]) ->
+-spec handshake_continue(#sslsocket{}, [tls_client_option() | tls_server_option()]) ->
{ok, #sslsocket{}} | {error, reason()}.
%%
%%
@@ -270,7 +600,7 @@ handshake(Socket, SslOptions, Timeout) when is_port(Socket),
handshake_continue(Socket, SSLOptions) ->
handshake_continue(Socket, SSLOptions, infinity).
%%--------------------------------------------------------------------
--spec handshake_continue(#sslsocket{}, [ssl_option()], timeout()) ->
+-spec handshake_continue(#sslsocket{}, [tls_client_option() | tls_server_option()], timeout()) ->
{ok, #sslsocket{}} | {error, reason()}.
%%
%%
@@ -322,7 +652,7 @@ close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_, _, _}
send(#sslsocket{pid = [Pid]}, Data) when is_pid(Pid) ->
ssl_connection:send(Pid, Data);
send(#sslsocket{pid = [_, Pid]}, Data) when is_pid(Pid) ->
- tls_sender:send_data(Pid, erlang:iolist_to_binary(Data));
+ tls_sender:send_data(Pid, erlang:iolist_to_iovec(Data));
send(#sslsocket{pid = {_, #config{transport_info={_, udp, _, _}}}}, _) ->
{error,enotconn}; %% Emulate connection behaviour
send(#sslsocket{pid = {dtls,_}}, _) ->
@@ -331,13 +661,14 @@ send(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport, _, _, _}
Transport:send(ListenSocket, Data). %% {error,enotconn}
%%--------------------------------------------------------------------
--spec recv(#sslsocket{}, integer()) -> {ok, binary()| list()} | {error, reason()}.
--spec recv(#sslsocket{}, integer(), timeout()) -> {ok, binary()| list()} | {error, reason()}.
%%
%% Description: Receives data when active = false
%%--------------------------------------------------------------------
+-spec recv(#sslsocket{}, integer()) -> {ok, binary()| list()} | {error, reason()}.
recv(Socket, Length) ->
recv(Socket, Length, infinity).
+
+-spec recv(#sslsocket{}, integer(), timeout()) -> {ok, binary()| list()} | {error, reason()}.
recv(#sslsocket{pid = [Pid|_]}, Length, Timeout) when is_pid(Pid),
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)->
ssl_connection:recv(Pid, Length, Timeout);
@@ -440,13 +771,13 @@ negotiated_protocol(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) ->
ssl_connection:negotiated_protocol(Pid).
%%--------------------------------------------------------------------
--spec cipher_suites() -> [ssl_cipher_format:old_erl_cipher_suite()] | [string()].
+-spec cipher_suites() -> [old_cipher_suite()] | [string()].
%%--------------------------------------------------------------------
cipher_suites() ->
cipher_suites(erlang).
%%--------------------------------------------------------------------
-spec cipher_suites(erlang | openssl | all) ->
- [ssl_cipher_format:old_erl_cipher_suite() | string()].
+ [old_cipher_suite() | string()].
%% Description: Returns all supported cipher suites.
%%--------------------------------------------------------------------
cipher_suites(erlang) ->
@@ -460,9 +791,9 @@ cipher_suites(all) ->
[ssl_cipher_format:erl_suite_definition(Suite) || Suite <- available_suites(all)].
%%--------------------------------------------------------------------
--spec cipher_suites(default | all | anonymous, tls_record:tls_version() | dtls_record:dtls_version() |
+-spec cipher_suites(default | all | anonymous, ssl_record:ssl_version() |
tls_record:tls_atom_version() | dtls_record:dtls_atom_version()) ->
- [ssl_cipher_format:erl_cipher_suite()].
+ [erl_cipher_suite()].
%% Description: Returns all default and all supported cipher suites for a
%% TLS/DTLS version
%%--------------------------------------------------------------------
@@ -478,9 +809,10 @@ cipher_suites(Base, Version) ->
[ssl_cipher_format:suite_definition(Suite) || Suite <- supported_suites(Base, Version)].
%%--------------------------------------------------------------------
--spec filter_cipher_suites([ssl_cipher_format:erl_cipher_suite()],
+-spec filter_cipher_suites([erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()] ,
[{key_exchange | cipher | mac | prf, fun()}] | []) ->
- [ssl_cipher_format:erl_cipher_suite()].
+ [erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()].
+
%% Description: Removes cipher suites if any of the filter functions returns false
%% for any part of the cipher suite. This function also calls default filter functions
%% to make sure the cipher suite are supported by crypto.
@@ -497,10 +829,10 @@ filter_cipher_suites(Suites, Filters0) ->
prf_filters => add_filter(proplists:get_value(prf, Filters0), PrfF)},
ssl_cipher:filter_suites(Suites, Filters).
%%--------------------------------------------------------------------
--spec prepend_cipher_suites([ssl_cipher_format:erl_cipher_suite()] |
+-spec prepend_cipher_suites([erl_cipher_suite()] |
[{key_exchange | cipher | mac | prf, fun()}],
- [ssl_cipher_format:erl_cipher_suite()]) ->
- [ssl_cipher_format:erl_cipher_suite()].
+ [erl_cipher_suite()]) ->
+ [erl_cipher_suite()].
%% Description: Make <Preferred> suites become the most prefered
%% suites that is put them at the head of the cipher suite list
%% and remove them from <Suites> if present. <Preferred> may be a
@@ -515,10 +847,10 @@ prepend_cipher_suites(Filters, Suites) ->
Preferred = filter_cipher_suites(Suites, Filters),
Preferred ++ (Suites -- Preferred).
%%--------------------------------------------------------------------
--spec append_cipher_suites(Deferred :: [ssl_cipher_format:erl_cipher_suite()] |
+-spec append_cipher_suites(Deferred :: [erl_cipher_suite()] |
[{key_exchange | cipher | mac | prf, fun()}],
- [ssl_cipher_format:erl_cipher_suite()]) ->
- [ssl_cipher_format:erl_cipher_suite()].
+ [erl_cipher_suite()]) ->
+ [erl_cipher_suite()].
%% Description: Make <Deferred> suites suites become the
%% least prefered suites that is put them at the end of the cipher suite list
%% and removed them from <Suites> if present.
@@ -540,8 +872,8 @@ eccs() ->
eccs_filter_supported(Curves).
%%--------------------------------------------------------------------
--spec eccs(tls_record:tls_version() | tls_record:tls_atom_version() |
- dtls_record:dtls_version() | dtls_record:dtls_atom_version()) ->
+-spec eccs(tls_record:tls_atom_version() |
+ ssl_record:ssl_version() | dtls_record:dtls_atom_version()) ->
tls_v1:curves().
%% Description: returns the curves supported for a given version of
%% ssl/tls.
@@ -568,6 +900,20 @@ eccs_filter_supported(Curves) ->
Curves).
%%--------------------------------------------------------------------
+-spec groups() -> tls_v1:supported_groups().
+%% Description: returns all supported groups (TLS 1.3 and later)
+%%--------------------------------------------------------------------
+groups() ->
+ tls_v1:groups(4).
+
+%%--------------------------------------------------------------------
+-spec groups(default) -> tls_v1:supported_groups().
+%% Description: returns the default groups (TLS 1.3 and later)
+%%--------------------------------------------------------------------
+groups(default) ->
+ tls_v1:default_groups(4).
+
+%%--------------------------------------------------------------------
-spec getopts(#sslsocket{}, [gen_tcp:option_name()]) ->
{ok, [gen_tcp:option()]} | {error, reason()}.
%%
@@ -723,7 +1069,7 @@ versions() ->
SupportedDTLSVsns = [dtls_record:protocol_version(Vsn) || Vsn <- DTLSVsns],
AvailableTLSVsns = ?ALL_AVAILABLE_VERSIONS,
AvailableDTLSVsns = ?ALL_AVAILABLE_DATAGRAM_VERSIONS,
- [{ssl_app, ?VSN}, {supported, SupportedTLSVsns},
+ [{ssl_app, "9.2"}, {supported, SupportedTLSVsns},
{supported_dtls, SupportedDTLSVsns},
{available, AvailableTLSVsns},
{available_dtls, AvailableDTLSVsns}].
@@ -783,8 +1129,8 @@ format_error(Reason) when is_list(Reason) ->
Reason;
format_error(closed) ->
"TLS connection is closed";
-format_error({tls_alert, Description}) ->
- "TLS Alert: " ++ Description;
+format_error({tls_alert, {_, Description}}) ->
+ Description;
format_error({options,{FileType, File, Reason}}) when FileType == cacertfile;
FileType == certfile;
FileType == keyfile;
@@ -813,7 +1159,7 @@ tls_version({254, _} = Version) ->
%%--------------------------------------------------------------------
--spec suite_to_str(ssl_cipher_format:erl_cipher_suite()) -> string().
+-spec suite_to_str(erl_cipher_suite()) -> string().
%%
%% Description: Return the string representation of a cipher suite.
%%--------------------------------------------------------------------
@@ -821,6 +1167,32 @@ suite_to_str(Cipher) ->
ssl_cipher_format:suite_to_str(Cipher).
+%%--------------------------------------------------------------------
+-spec set_log_level(atom()) -> ok | {error, term()}.
+%%
+%% Description: Set log level for the SSL application
+%%--------------------------------------------------------------------
+set_log_level(Level) ->
+ case application:get_all_key(ssl) of
+ {ok, PropList} ->
+ Modules = proplists:get_value(modules, PropList),
+ set_module_level(Modules, Level);
+ undefined ->
+ {error, ssl_not_started}
+ end.
+
+set_module_level(Modules, Level) ->
+ Fun = fun (Module) ->
+ ok = logger:set_module_level(Module, Level)
+ end,
+ try lists:map(Fun, Modules) of
+ _ ->
+ ok
+ catch
+ error:{badmatch, Error} ->
+ Error
+ end.
+
%%%--------------------------------------------------------------
%%% Internal functions
%%%--------------------------------------------------------------------
@@ -880,9 +1252,10 @@ handle_options(Opts0, #ssl_options{protocol = Protocol, cacerts = CaCerts0,
[] ->
new_ssl_options(SslOpts1, NewVerifyOpts, RecordCB);
Value ->
- Versions = [RecordCB:protocol_version(Vsn) || Vsn <- Value],
+ Versions0 = [RecordCB:protocol_version(Vsn) || Vsn <- Value],
+ Versions1 = lists:sort(fun RecordCB:is_higher/2, Versions0),
new_ssl_options(proplists:delete(versions, SslOpts1),
- NewVerifyOpts#ssl_options{versions = Versions}, record_cb(Protocol))
+ NewVerifyOpts#ssl_options{versions = Versions1}, record_cb(Protocol))
end;
%% Handle all options in listen and connect
@@ -891,22 +1264,21 @@ handle_options(Opts0, Role, Host) ->
{list, [{mode, list}]}], Opts0),
assert_proplist(Opts),
RecordCb = record_cb(Opts),
-
- ReuseSessionFun = fun(_, _, _, _) -> true end,
CaCerts = handle_option(cacerts, Opts, undefined),
{Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun, PartialChainHanlder, VerifyClientOnce} =
handle_verify_options(Opts, CaCerts),
CertFile = handle_option(certfile, Opts, <<>>),
- RecordCb = record_cb(Opts),
- Versions = case handle_option(versions, Opts, []) of
- [] ->
- RecordCb:supported_protocol_versions();
- Vsns ->
- [RecordCb:protocol_version(Vsn) || Vsn <- Vsns]
- end,
+ [HighestVersion|_] = Versions =
+ case handle_option(versions, Opts, []) of
+ [] ->
+ RecordCb:supported_protocol_versions();
+ Vsns ->
+ Versions0 = [RecordCb:protocol_version(Vsn) || Vsn <- Vsns],
+ lists:sort(fun RecordCb:is_higher/2, Versions0)
+ end,
Protocol = handle_option(protocol, Opts, tls),
@@ -917,7 +1289,7 @@ handle_options(Opts0, Role, Host) ->
ok
end,
- SSLOptions = #ssl_options{
+ SSLOptions0 = #ssl_options{
versions = Versions,
verify = validate_option(verify, Verify),
verify_fun = VerifyFun,
@@ -938,16 +1310,31 @@ handle_options(Opts0, Role, Host) ->
psk_identity = handle_option(psk_identity, Opts, undefined),
srp_identity = handle_option(srp_identity, Opts, undefined),
ciphers = handle_cipher_option(proplists:get_value(ciphers, Opts, []),
- RecordCb:highest_protocol_version(Versions)),
+ HighestVersion),
eccs = handle_eccs_option(proplists:get_value(eccs, Opts, eccs()),
- RecordCb:highest_protocol_version(Versions)),
- signature_algs = handle_hashsigns_option(proplists:get_value(signature_algs, Opts,
- default_option_role(server,
- tls_v1:default_signature_algs(Versions), Role)),
- tls_version(RecordCb:highest_protocol_version(Versions))),
- %% Server side option
- reuse_session = handle_option(reuse_session, Opts, ReuseSessionFun),
- reuse_sessions = handle_option(reuse_sessions, Opts, true),
+ HighestVersion),
+ supported_groups = handle_supported_groups_option(
+ proplists:get_value(supported_groups, Opts, groups(default)),
+ HighestVersion),
+ signature_algs =
+ handle_hashsigns_option(
+ proplists:get_value(
+ signature_algs,
+ Opts,
+ default_option_role_sign_algs(server,
+ tls_v1:default_signature_algs(HighestVersion),
+ Role,
+ HighestVersion)),
+ tls_version(HighestVersion)),
+ signature_algs_cert =
+ handle_signature_algorithms_option(
+ proplists:get_value(
+ signature_algs_cert,
+ Opts,
+ undefined), %% Do not send by default
+ tls_version(HighestVersion)),
+ reuse_sessions = handle_reuse_sessions_option(reuse_sessions, Opts, Role),
+ reuse_session = handle_reuse_session_option(reuse_session, Opts, Role),
secure_renegotiate = handle_option(secure_renegotiate, Opts, true),
client_renegotiation = handle_option(client_renegotiation, Opts,
default_option_role(server, true, Role),
@@ -964,7 +1351,6 @@ handle_options(Opts0, Role, Host) ->
next_protocol_selector =
make_next_protocol_selector(
handle_option(client_preferred_next_protocols, Opts, undefined)),
- log_alert = handle_option(log_alert, Opts, true),
server_name_indication = handle_option(server_name_indication, Opts,
default_option_role(client,
server_name_indication_default(Host), Role)),
@@ -990,6 +1376,10 @@ handle_options(Opts0, Role, Host) ->
handshake = handle_option(handshake, Opts, full),
customize_hostname_check = handle_option(customize_hostname_check, Opts, [])
},
+ LogLevel = handle_option(log_alert, Opts, true),
+ SSLOptions = SSLOptions0#ssl_options{
+ log_level = handle_option(log_level, Opts, LogLevel)
+ },
CbInfo = proplists:get_value(cb_info, Opts, default_cb_info(Protocol)),
SslOptions = [protocol, versions, verify, verify_fun, partial_chain,
@@ -1001,10 +1391,12 @@ handle_options(Opts0, Role, Host) ->
cb_info, renegotiate_at, secure_renegotiate, hibernate_after,
erl_dist, alpn_advertised_protocols, sni_hosts, sni_fun,
alpn_preferred_protocols, next_protocols_advertised,
- client_preferred_next_protocols, log_alert,
+ client_preferred_next_protocols, log_alert, log_level,
server_name_indication, honor_cipher_order, padding_check, crl_check, crl_cache,
- fallback, signature_algs, eccs, honor_ecc_order, beast_mitigation,
- max_handshake_size, handshake, customize_hostname_check],
+ fallback, signature_algs, signature_algs_cert, eccs, honor_ecc_order,
+ beast_mitigation, max_handshake_size, handshake, customize_hostname_check,
+ supported_groups],
+
SockOpts = lists:foldl(fun(Key, PropList) ->
proplists:delete(Key, PropList)
end, Opts, SslOptions),
@@ -1138,11 +1530,16 @@ validate_option(srp_identity, {Username, Password})
{unicode:characters_to_binary(Username),
unicode:characters_to_binary(Password)};
+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_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(client_renegotiation, Value) when is_boolean(Value) ->
@@ -1180,7 +1577,20 @@ validate_option(client_preferred_next_protocols, {Precedence, PreferredProtocols
Value;
validate_option(client_preferred_next_protocols, undefined) ->
undefined;
-validate_option(log_alert, Value) when is_boolean(Value) ->
+validate_option(log_alert, true) ->
+ notice;
+validate_option(log_alert, false) ->
+ warning;
+validate_option(log_level, Value) when
+ is_atom(Value) andalso
+ (Value =:= emergency orelse
+ Value =:= alert orelse
+ Value =:= critical orelse
+ Value =:= error orelse
+ Value =:= warning orelse
+ Value =:= notice orelse
+ Value =:= info orelse
+ Value =:= debug) ->
Value;
validate_option(next_protocols_advertised, Value) when is_list(Value) ->
validate_binary_list(next_protocols_advertised, Value),
@@ -1252,19 +1662,62 @@ validate_option(customize_hostname_check, Value) when is_list(Value) ->
validate_option(Opt, Value) ->
throw({error, {options, {Opt, Value}}}).
+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} ->
+ 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(_, Version) when Version =:= {3, 3} ->
handle_hashsigns_option(tls_v1:default_signature_algs(Version), Version);
handle_hashsigns_option(_, _Version) ->
undefined.
+handle_signature_algorithms_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_cert, Value}}});
+ _ ->
+ Value
+ end;
+handle_signature_algorithms_option(_, _Version) ->
+ undefined.
+
+handle_reuse_sessions_option(Key, Opts, client) ->
+ Value = proplists:get_value(Key, Opts, true),
+ validate_option(Key, Value),
+ Value;
+handle_reuse_sessions_option(Key, Opts0, server) ->
+ Opts = proplists:delete({Key, save}, Opts0),
+ Value = proplists:get_value(Key, Opts, true),
+ validate_option(Key, Value),
+ Value.
+
+handle_reuse_session_option(Key, Opts, client) ->
+ Value = proplists:get_value(Key, Opts, undefined),
+ validate_option(Key, Value),
+ Value;
+handle_reuse_session_option(Key, Opts, server) ->
+ ReuseSessionFun = fun(_, _, _, _) -> true end,
+ Value = proplists:get_value(Key, Opts, ReuseSessionFun),
+ validate_option(Key, Value),
+ Value.
+
validate_options([]) ->
[];
validate_options([{Opt, Value} | Tail]) ->
@@ -1288,7 +1741,8 @@ validate_binary_list(Opt, List) ->
end, List).
validate_versions([], Versions) ->
Versions;
-validate_versions([Version | Rest], Versions) when Version == 'tlsv1.2';
+validate_versions([Version | Rest], Versions) when Version == 'tlsv1.3';
+ Version == 'tlsv1.2';
Version == 'tlsv1.1';
Version == tlsv1;
Version == sslv3 ->
@@ -1301,10 +1755,11 @@ validate_versions([Ver| _], Versions) ->
tls_validate_versions([], Versions) ->
Versions;
-tls_validate_versions([Version | Rest], Versions) when Version == 'tlsv1.2';
- Version == 'tlsv1.1';
- Version == tlsv1;
- Version == sslv3 ->
+tls_validate_versions([Version | Rest], Versions) when Version == 'tlsv1.3';
+ Version == 'tlsv1.2';
+ Version == 'tlsv1.1';
+ Version == tlsv1;
+ Version == sslv3 ->
tls_validate_versions(Rest, Versions);
tls_validate_versions([Ver| _], Versions) ->
throw({error, {options, {Ver, {versions, Versions}}}}).
@@ -1410,6 +1865,16 @@ handle_eccs_option(Value, Version) when is_list(Value) ->
error:_ -> throw({error, {options, {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}
+ catch
+ exit:_ -> throw({error, {options, {supported_groups, Value}}});
+ error:_ -> throw({error, {options, {supported_groups, Value}}})
+ end.
+
+
unexpected_format(Error) ->
lists:flatten(io_lib:format("Unexpected error: ~p", [Error])).
@@ -1555,8 +2020,10 @@ new_ssl_options([{next_protocols_advertised, Value} | Rest], #ssl_options{} = Op
new_ssl_options([{client_preferred_next_protocols, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
new_ssl_options(Rest, Opts#ssl_options{next_protocol_selector =
make_next_protocol_selector(validate_option(client_preferred_next_protocols, Value))}, RecordCB);
-new_ssl_options([{log_alert, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#ssl_options{log_alert = validate_option(log_alert, Value)}, RecordCB);
+new_ssl_options([{log_alert, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest, Opts#ssl_options{log_level = validate_option(log_alert, Value)}, RecordCB);
+new_ssl_options([{log_level, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest, Opts#ssl_options{log_level = validate_option(log_level, Value)}, RecordCB);
new_ssl_options([{server_name_indication, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
new_ssl_options(Rest, Opts#ssl_options{server_name_indication = validate_option(server_name_indication, Value)}, RecordCB);
new_ssl_options([{honor_cipher_order, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
@@ -1569,12 +2036,26 @@ new_ssl_options([{eccs, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
handle_eccs_option(Value, RecordCB:highest_protocol_version())
},
RecordCB);
+new_ssl_options([{supported_groups, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest,
+ Opts#ssl_options{supported_groups =
+ handle_supported_groups_option(Value, RecordCB:highest_protocol_version())
+ },
+ RecordCB);
new_ssl_options([{signature_algs, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
new_ssl_options(Rest,
Opts#ssl_options{signature_algs =
handle_hashsigns_option(Value,
tls_version(RecordCB:highest_protocol_version()))},
RecordCB);
+new_ssl_options([{signature_algs_cert, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(
+ Rest,
+ Opts#ssl_options{signature_algs_cert =
+ handle_signature_algorithms_option(
+ Value,
+ tls_version(RecordCB:highest_protocol_version()))},
+ RecordCB);
new_ssl_options([{protocol, dtls = Value} | Rest], #ssl_options{} = Opts, dtls_record = RecordCB) ->
new_ssl_options(Rest, Opts#ssl_options{protocol = Value}, RecordCB);
new_ssl_options([{protocol, tls = Value} | Rest], #ssl_options{} = Opts, tls_record = RecordCB) ->
@@ -1636,11 +2117,20 @@ handle_verify_options(Opts, CaCerts) ->
throw({error, {options, {verify, Value}}})
end.
+%% 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};
default_cb_info(dtls) ->
diff --git a/lib/ssl/src/ssl_alert.erl b/lib/ssl/src/ssl_alert.erl
index 34e9797f1f..e17476f33b 100644
--- a/lib/ssl/src/ssl_alert.erl
+++ b/lib/ssl/src/ssl_alert.erl
@@ -48,8 +48,8 @@ decode(Bin) ->
decode(Bin, [], 0).
%%--------------------------------------------------------------------
--spec reason_code(#alert{}, client | server) ->
- closed | {tls_alert, unicode:chardata()}.
+%% -spec reason_code(#alert{}, client | server) ->
+%% {tls_alert, unicode:chardata()} | closed.
%-spec reason_code(#alert{}, client | server) -> closed | {essl, string()}.
%%
%% Description: Returns the error reason that will be returned to the
@@ -58,8 +58,10 @@ decode(Bin) ->
reason_code(#alert{description = ?CLOSE_NOTIFY}, _) ->
closed;
-reason_code(#alert{description = Description}, _) ->
- {tls_alert, string:casefold(description_txt(Description))}.
+reason_code(#alert{description = Description, role = Role} = Alert, Role) ->
+ {tls_alert, {description_atom(Description), own_alert_txt(Alert)}};
+reason_code(#alert{description = Description} = Alert, Role) ->
+ {tls_alert, {description_atom(Description), alert_txt(Alert#alert{role = Role})}}.
%%--------------------------------------------------------------------
-spec own_alert_txt(#alert{}) -> string().
@@ -163,6 +165,8 @@ description_txt(?USER_CANCELED) ->
"User Canceled";
description_txt(?NO_RENEGOTIATION) ->
"No Renegotiation";
+description_txt(?MISSING_EXTENSION) ->
+ "Missing extension";
description_txt(?UNSUPPORTED_EXTENSION) ->
"Unsupported Extension";
description_txt(?CERTIFICATE_UNOBTAINABLE) ->
@@ -177,7 +181,76 @@ description_txt(?UNKNOWN_PSK_IDENTITY) ->
"Unknown Psk Identity";
description_txt(?INAPPROPRIATE_FALLBACK) ->
"Inappropriate Fallback";
+description_txt(?CERTIFICATE_REQUIRED) ->
+ "Certificate required";
description_txt(?NO_APPLICATION_PROTOCOL) ->
"No application protocol";
description_txt(Enum) ->
lists:flatten(io_lib:format("unsupported/unknown alert: ~p", [Enum])).
+
+description_atom(?CLOSE_NOTIFY) ->
+ close_notify;
+description_atom(?UNEXPECTED_MESSAGE) ->
+ unexpected_message;
+description_atom(?BAD_RECORD_MAC) ->
+ bad_record_mac;
+description_atom(?DECRYPTION_FAILED_RESERVED) ->
+ decryption_failed_reserved;
+description_atom(?RECORD_OVERFLOW) ->
+ record_overflow;
+description_atom(?DECOMPRESSION_FAILURE) ->
+ decompression_failure;
+description_atom(?HANDSHAKE_FAILURE) ->
+ handshake_failure;
+description_atom(?NO_CERTIFICATE_RESERVED) ->
+ no_certificate_reserved;
+description_atom(?BAD_CERTIFICATE) ->
+ bad_certificate;
+description_atom(?UNSUPPORTED_CERTIFICATE) ->
+ unsupported_certificate;
+description_atom(?CERTIFICATE_REVOKED) ->
+ certificate_revoked;
+description_atom(?CERTIFICATE_EXPIRED) ->
+ certificate_expired;
+description_atom(?CERTIFICATE_UNKNOWN) ->
+ certificate_unknown;
+description_atom(?ILLEGAL_PARAMETER) ->
+ illegal_parameter;
+description_atom(?UNKNOWN_CA) ->
+ unknown_ca;
+description_atom(?ACCESS_DENIED) ->
+ access_denied;
+description_atom(?DECODE_ERROR) ->
+ decode_error;
+description_atom(?DECRYPT_ERROR) ->
+ decrypt_error;
+description_atom(?EXPORT_RESTRICTION) ->
+ export_restriction;
+description_atom(?PROTOCOL_VERSION) ->
+ protocol_version;
+description_atom(?INSUFFICIENT_SECURITY) ->
+ insufficient_security;
+description_atom(?INTERNAL_ERROR) ->
+ internal_error;
+description_atom(?USER_CANCELED) ->
+ user_canceled;
+description_atom(?NO_RENEGOTIATION) ->
+ no_renegotiation;
+description_atom(?UNSUPPORTED_EXTENSION) ->
+ unsupported_extension;
+description_atom(?CERTIFICATE_UNOBTAINABLE) ->
+ certificate_unobtainable;
+description_atom(?UNRECOGNISED_NAME) ->
+ unrecognised_name;
+description_atom(?BAD_CERTIFICATE_STATUS_RESPONSE) ->
+ bad_certificate_status_response;
+description_atom(?BAD_CERTIFICATE_HASH_VALUE) ->
+ bad_certificate_hash_value;
+description_atom(?UNKNOWN_PSK_IDENTITY) ->
+ unknown_psk_identity;
+description_atom(?INAPPROPRIATE_FALLBACK) ->
+ inappropriate_fallback;
+description_atom(?NO_APPLICATION_PROTOCOL) ->
+ no_application_protocol;
+description_atom(_) ->
+ 'unsupported/unkonwn_alert'.
diff --git a/lib/ssl/src/ssl_alert.hrl b/lib/ssl/src/ssl_alert.hrl
index b23123905e..9b2322da17 100644
--- a/lib/ssl/src/ssl_alert.hrl
+++ b/lib/ssl/src/ssl_alert.hrl
@@ -29,6 +29,9 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Alert protocol - RFC 2246 section 7.2
+%%% updated by RFC 8486 with
+%%% missing_extension(109),
+%%% certificate_required(116),
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% AlertLevel
@@ -100,12 +103,14 @@
-define(INAPPROPRIATE_FALLBACK, 86).
-define(USER_CANCELED, 90).
-define(NO_RENEGOTIATION, 100).
+-define(MISSING_EXTENSION, 109).
-define(UNSUPPORTED_EXTENSION, 110).
-define(CERTIFICATE_UNOBTAINABLE, 111).
-define(UNRECOGNISED_NAME, 112).
-define(BAD_CERTIFICATE_STATUS_RESPONSE, 113).
-define(BAD_CERTIFICATE_HASH_VALUE, 114).
-define(UNKNOWN_PSK_IDENTITY, 115).
+-define(CERTIFICATE_REQUIRED, 116).
-define(NO_APPLICATION_PROTOCOL, 120).
-define(ALERT_REC(Level,Desc), #alert{level=Level,description=Desc,where={?FILE, ?LINE}}).
diff --git a/lib/ssl/src/ssl_api.hrl b/lib/ssl/src/ssl_api.hrl
index 7b7b1cbcd9..f4594912bd 100644
--- a/lib/ssl/src/ssl_api.hrl
+++ b/lib/ssl/src/ssl_api.hrl
@@ -21,56 +21,7 @@
-ifndef(ssl_api).
-define(ssl_api, true).
--include("ssl_cipher.hrl").
-
-%% Visible in API
--export_type([connect_option/0, listen_option/0, ssl_option/0, transport_option/0,
- prf_random/0, sslsocket/0]).
-
-
%% Looks like it does for backwards compatibility reasons
-record(sslsocket, {fd = nil, pid = nil}).
-
--type sslsocket() :: #sslsocket{}.
--type connect_option() :: socket_connect_option() | ssl_option() | transport_option().
--type socket_connect_option() :: gen_tcp:connect_option().
--type listen_option() :: socket_listen_option() | ssl_option() | transport_option().
--type socket_listen_option() :: gen_tcp:listen_option().
-
--type ssl_option() :: {versions, ssl_record:ssl_atom_version()} |
- {verify, verify_type()} |
- {verify_fun, {fun(), InitialUserState::term()}} |
- {fail_if_no_peer_cert, boolean()} | {depth, integer()} |
- {cert, Der::binary()} | {certfile, path()} |
- {key, {private_key_type(), Der::binary()}} |
- {keyfile, path()} | {password, string()} | {cacerts, [Der::binary()]} |
- {cacertfile, path()} | {dh, Der::binary()} | {dhfile, path()} |
- {user_lookup_fun, {fun(), InitialUserState::term()}} |
- {psk_identity, string()} |
- {srp_identity, {string(), string()}} |
- {ciphers, ciphers()} | {ssl_imp, ssl_imp()} | {reuse_sessions, boolean()} |
- {reuse_session, fun()} | {hibernate_after, integer()|undefined} |
- {alpn_advertised_protocols, [binary()]} |
- {alpn_preferred_protocols, [binary()]} |
- {next_protocols_advertised, list(binary())} |
- {client_preferred_next_protocols, binary(), client | server, list(binary())}.
-
--type verify_type() :: verify_none | verify_peer.
--type path() :: string().
--type ciphers() :: [ssl_cipher_format:erl_cipher_suite()] |
- string(). % (according to old API)
--type ssl_imp() :: new | old.
-
--type transport_option() :: {cb_info, {CallbackModule::atom(), DataTag::atom(),
- ClosedTag::atom(), ErrTag::atom()}}.
--type prf_random() :: client_random | server_random.
-
--type private_key_type() :: rsa | %% Backwards compatibility
- dsa | %% Backwards compatibility
- 'RSAPrivateKey' |
- 'DSAPrivateKey' |
- 'ECPrivateKey' |
- 'PrivateKeyInfo'.
-
-endif. % -ifdef(ssl_api).
diff --git a/lib/ssl/src/ssl_app.erl b/lib/ssl/src/ssl_app.erl
index 62e8765d4a..2a5047c75c 100644
--- a/lib/ssl/src/ssl_app.erl
+++ b/lib/ssl/src/ssl_app.erl
@@ -29,9 +29,26 @@
-export([start/2, stop/1]).
start(_Type, _StartArgs) ->
+ start_logger(),
ssl_sup:start_link().
stop(_State) ->
+ stop_logger(),
ok.
+%%
+%% Description: Start SSL logger
+start_logger() ->
+ Config = #{level => debug,
+ filter_default => stop,
+ formatter => {ssl_logger, #{}}},
+ Filter = {fun logger_filters:domain/2,{log,sub,[otp,ssl]}},
+ logger:add_handler(ssl_handler, logger_std_h, Config),
+ logger:add_handler_filter(ssl_handler, filter_non_ssl, Filter).
+
+%%
+%% Description: Stop SSL logger
+stop_logger() ->
+ logger:remove_handler(ssl_handler).
+
diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl
index 549e557beb..9997f5e0c8 100644
--- a/lib/ssl/src/ssl_certificate.erl
+++ b/lib/ssl/src/ssl_certificate.erl
@@ -71,7 +71,7 @@ trusted_cert_and_path(CertChain, CertDbHandle, CertDbRef, PartialChainHandler) -
case SignedAndIssuerID of
{error, issuer_not_found} ->
- %% The root CA was not sent and can not be found.
+ %% The root CA was not sent and cannot be found.
handle_incomplete_chain(Path, PartialChainHandler);
{self, _} when length(Path) == 1 ->
{selfsigned_peer, Path};
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index 66a00c60f1..6e751f9ceb 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -1,7 +1,7 @@
%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2019. All 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,9 +31,10 @@
-include("ssl_cipher.hrl").
-include("ssl_handshake.hrl").
-include("ssl_alert.hrl").
+-include("tls_handshake_1_3.hrl").
-include_lib("public_key/include/public_key.hrl").
--export([security_parameters/2, security_parameters/3,
+-export([security_parameters/2, security_parameters/3, security_parameters_1_3/2,
cipher_init/3, nonce_seed/2, decipher/6, cipher/5, aead_encrypt/5, aead_decrypt/6,
suites/1, all_suites/1, crypto_support_filters/0,
chacha_suites/1, anonymous_suites/1, psk_suites/1, psk_suites_anon/1,
@@ -41,8 +42,13 @@
rc4_suites/1, des_suites/1, rsa_suites/1,
filter/3, filter_suites/1, filter_suites/2,
hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2, is_fallback/1,
- random_bytes/1, calc_mac_hash/4,
- is_stream_ciphersuite/1]).
+ random_bytes/1, calc_mac_hash/4, calc_mac_hash/6,
+ is_stream_ciphersuite/1, signature_scheme/1,
+ scheme_to_components/1, hash_size/1, effective_key_bits/1,
+ key_material/1]).
+
+%% RFC 8446 TLS 1.3
+-export([generate_client_shares/1, generate_server_share/1, add_zero_padding/2]).
-compile(inline).
@@ -83,6 +89,15 @@ security_parameters(Version, CipherSuite, SecParams) ->
prf_algorithm = prf_algorithm(PrfHashAlg, Version),
hash_size = hash_size(Hash)}.
+security_parameters_1_3(SecParams, CipherSuite) ->
+ #{cipher := Cipher, prf := PrfHashAlg} =
+ ssl_cipher_format:suite_definition(CipherSuite),
+ SecParams#security_parameters{
+ cipher_suite = CipherSuite,
+ bulk_cipher_algorithm = bulk_cipher_algorithm(Cipher),
+ prf_algorithm = PrfHashAlg, %% HKDF hash algorithm
+ cipher_type = ?AEAD}.
+
%%--------------------------------------------------------------------
-spec cipher_init(cipher_enum(), binary(), binary()) -> #cipher_state{}.
%%
@@ -97,7 +112,8 @@ cipher_init(?AES_GCM, IV, Key) ->
cipher_init(?CHACHA20_POLY1305, IV, Key) ->
#cipher_state{iv = IV, key = Key, tag_len = 16};
cipher_init(_BCA, IV, Key) ->
- #cipher_state{iv = IV, key = Key}.
+ %% Initialize random IV cache, not used for aead ciphers
+ #cipher_state{iv = IV, key = Key, state = <<>>}.
nonce_seed(Seed, CipherState) ->
CipherState#cipher_state{nonce = Seed}.
@@ -112,12 +128,11 @@ nonce_seed(Seed, CipherState) ->
%% data is calculated and the data plus the HMAC is ecncrypted.
%%-------------------------------------------------------------------
cipher(?NULL, CipherState, <<>>, Fragment, _Version) ->
- GenStreamCipherList = [Fragment, <<>>],
- {GenStreamCipherList, CipherState};
+ {iolist_to_binary(Fragment), CipherState};
cipher(?RC4, CipherState = #cipher_state{state = State0}, Mac, Fragment, _Version) ->
GenStreamCipherList = [Fragment, Mac],
{State1, T} = crypto:stream_encrypt(State0, GenStreamCipherList),
- {T, CipherState#cipher_state{state = State1}};
+ {iolist_to_binary(T), CipherState#cipher_state{state = State1}};
cipher(?DES, CipherState, Mac, Fragment, Version) ->
block_cipher(fun(Key, IV, T) ->
crypto:block_encrypt(des_cbc, Key, IV, T)
@@ -146,8 +161,7 @@ aead_type(?CHACHA20_POLY1305) ->
build_cipher_block(BlockSz, Mac, Fragment) ->
TotSz = byte_size(Mac) + erlang:iolist_size(Fragment) + 1,
- {PaddingLength, Padding} = get_padding(TotSz, BlockSz),
- [Fragment, Mac, PaddingLength, Padding].
+ [Fragment, Mac, padding_with_len(TotSz, BlockSz)].
block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV} = CS0,
Mac, Fragment, {3, N})
@@ -157,14 +171,21 @@ block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV} = CS0,
NextIV = next_iv(T, IV),
{T, CS0#cipher_state{iv=NextIV}};
-block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV} = CS0,
+block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV, state = IV_Cache0} = CS0,
Mac, Fragment, {3, N})
- when N == 2; N == 3 ->
- NextIV = random_iv(IV),
+ when N == 2; N == 3; N == 4 ->
+ IV_Size = byte_size(IV),
+ <<NextIV:IV_Size/binary, IV_Cache/binary>> =
+ case IV_Cache0 of
+ <<>> ->
+ random_bytes(IV_Size bsl 5); % 32 IVs
+ _ ->
+ IV_Cache0
+ end,
L0 = build_cipher_block(BlockSz, Mac, Fragment),
L = [NextIV|L0],
T = Fun(Key, IV, L),
- {T, CS0#cipher_state{iv=NextIV}}.
+ {T, CS0#cipher_state{iv=NextIV, state = IV_Cache}}.
%%--------------------------------------------------------------------
-spec decipher(cipher_enum(), integer(), #cipher_state{}, binary(),
@@ -288,6 +309,8 @@ anonymous_suites({3, N}) ->
srp_suites_anon() ++ anonymous_suites(N);
anonymous_suites({254, _} = Version) ->
dtls_v1:anonymous_suites(Version);
+anonymous_suites(4) ->
+ []; %% Raw public key negotiation may be used instead
anonymous_suites(N)
when N >= 3 ->
psk_suites_anon(N) ++
@@ -322,6 +345,8 @@ anonymous_suites(N) when N == 0;
%%--------------------------------------------------------------------
psk_suites({3, N}) ->
psk_suites(N);
+psk_suites(4) ->
+ []; %% TODO Add new PSK, PSK_(EC)DHE suites
psk_suites(N)
when N >= 3 ->
[
@@ -412,11 +437,12 @@ rc4_suites({3, Minor}) ->
rc4_suites(0) ->
[?TLS_RSA_WITH_RC4_128_SHA,
?TLS_RSA_WITH_RC4_128_MD5];
-rc4_suites(N) when N =< 3 ->
+rc4_suites(N) when N =< 4 ->
[?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
?TLS_ECDHE_RSA_WITH_RC4_128_SHA,
?TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
?TLS_ECDH_RSA_WITH_RC4_128_SHA].
+
%%--------------------------------------------------------------------
-spec des_suites(Version::ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()].
%%
@@ -451,7 +477,7 @@ rsa_suites(0) ->
?TLS_RSA_WITH_AES_128_CBC_SHA,
?TLS_RSA_WITH_3DES_EDE_CBC_SHA
];
-rsa_suites(N) when N =< 3 ->
+rsa_suites(N) when N =< 4 ->
[
?TLS_RSA_WITH_AES_256_GCM_SHA384,
?TLS_RSA_WITH_AES_256_CBC_SHA256,
@@ -481,8 +507,8 @@ filter(DerCert, Ciphers0, Version) ->
filter_suites_signature(Sign, Ciphers, Version).
%%--------------------------------------------------------------------
--spec filter_suites([ssl_cipher_format:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()], map()) ->
- [ssl_cipher_format:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()].
+-spec filter_suites([ssl:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()], map()) ->
+ [ssl:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()].
%%
%% Description: Filter suites using supplied filter funs
%%-------------------------------------------------------------------
@@ -508,8 +534,8 @@ filter_suite(Suite, Filters) ->
filter_suite(ssl_cipher_format:suite_definition(Suite), Filters).
%%--------------------------------------------------------------------
--spec filter_suites([ssl_cipher_format:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()]) ->
- [ssl_cipher_format:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()].
+-spec filter_suites([ssl:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()]) ->
+ [ssl:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()].
%%
%% Description: Filter suites for algorithms supported by crypto.
%%-------------------------------------------------------------------
@@ -550,7 +576,8 @@ crypto_support_filters() ->
end]}.
is_acceptable_keyexchange(KeyExchange, _Algos) when KeyExchange == psk;
- KeyExchange == null ->
+ KeyExchange == null;
+ KeyExchange == any ->
true;
is_acceptable_keyexchange(KeyExchange, Algos) when KeyExchange == dh_anon;
KeyExchange == dhe_psk ->
@@ -593,7 +620,7 @@ is_acceptable_cipher(rc4_128, Algos) ->
is_acceptable_cipher(des_cbc, Algos) ->
proplists:get_bool(des_cbc, Algos);
is_acceptable_cipher('3des_ede_cbc', Algos) ->
- proplists:get_bool(des3_cbc, Algos);
+ proplists:get_bool(des_ede3, Algos);
is_acceptable_cipher(aes_128_cbc, Algos) ->
proplists:get_bool(aes_cbc128, Algos);
is_acceptable_cipher(aes_256_cbc, Algos) ->
@@ -633,17 +660,40 @@ random_bytes(N) ->
calc_mac_hash(Type, Version,
PlainFragment, #{sequence_number := SeqNo,
mac_secret := MacSecret,
- security_parameters:=
- SecPars}) ->
+ security_parameters :=
+ #security_parameters{mac_algorithm = MacAlgorithm}}) ->
+ calc_mac_hash(Type, Version, PlainFragment, MacAlgorithm, MacSecret, SeqNo).
+%%
+calc_mac_hash(Type, Version, PlainFragment, MacAlgorithm, MacSecret, SeqNo) ->
Length = erlang:iolist_size(PlainFragment),
- mac_hash(Version, SecPars#security_parameters.mac_algorithm,
- MacSecret, SeqNo, Type,
- Length, PlainFragment).
+ mac_hash(Version, MacAlgorithm, MacSecret, SeqNo, Type, Length, PlainFragment).
is_stream_ciphersuite(#{cipher := rc4_128}) ->
true;
is_stream_ciphersuite(_) ->
false.
+
+-spec hash_size(atom()) -> integer().
+hash_size(null) ->
+ 0;
+%% The AEAD MAC hash size is not used in the context
+%% of calculating the master secret. See RFC 5246 Section 6.2.3.3.
+hash_size(aead) ->
+ 0;
+hash_size(md5) ->
+ 16;
+hash_size(sha) ->
+ 20;
+%% Uncomment when adding cipher suite that needs it
+%hash_size(sha224) ->
+% 28;
+hash_size(sha256) ->
+ 32;
+hash_size(sha384) ->
+ 48;
+hash_size(sha512) ->
+ 64.
+
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
@@ -653,7 +703,7 @@ mac_hash({_,_}, ?NULL, _MacSecret, _SeqNo, _Type,
mac_hash({3, 0}, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) ->
ssl_v3:mac_hash(MacAlg, MacSecret, SeqNo, Type, Length, Fragment);
mac_hash({3, N} = Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment)
- when N =:= 1; N =:= 2; N =:= 3 ->
+ when N =:= 1; N =:= 2; N =:= 3; N =:= 4 ->
tls_v1:mac_hash(MacAlg, MacSecret, SeqNo, Type, Version,
Length, Fragment).
@@ -722,7 +772,6 @@ expanded_key_material(Cipher) when Cipher == aes_128_cbc;
Cipher == chacha20_poly1305 ->
unknown.
-
effective_key_bits(null) ->
0;
effective_key_bits(des_cbc) ->
@@ -742,18 +791,15 @@ iv_size(Cipher) when Cipher == null;
Cipher == rc4_128;
Cipher == chacha20_poly1305->
0;
-
iv_size(Cipher) when Cipher == aes_128_gcm;
Cipher == aes_256_gcm ->
4;
-
iv_size(Cipher) ->
block_size(Cipher).
block_size(Cipher) when Cipher == des_cbc;
Cipher == '3des_ede_cbc' ->
8;
-
block_size(Cipher) when Cipher == aes_128_cbc;
Cipher == aes_256_cbc;
Cipher == aes_128_gcm;
@@ -801,26 +847,58 @@ sign_algorithm(?ECDSA) -> ecdsa;
sign_algorithm(Other) when is_integer(Other) andalso ((Other >= 4) and (Other =< 223)) -> unassigned;
sign_algorithm(Other) when is_integer(Other) andalso ((Other >= 224) and (Other =< 255)) -> Other.
-hash_size(null) ->
- 0;
-%% The AEAD MAC hash size is not used in the context
-%% of calculating the master secret. See RFC 5246 Section 6.2.3.3.
-hash_size(aead) ->
- 0;
-hash_size(md5) ->
- 16;
-hash_size(sha) ->
- 20;
-%% Uncomment when adding cipher suite that needs it
-%hash_size(sha224) ->
-% 28;
-hash_size(sha256) ->
- 32;
-hash_size(sha384) ->
- 48.
-%% Uncomment when adding cipher suite that needs it
-%hash_size(sha512) ->
-% 64.
+
+signature_scheme(rsa_pkcs1_sha256) -> ?RSA_PKCS1_SHA256;
+signature_scheme(rsa_pkcs1_sha384) -> ?RSA_PKCS1_SHA384;
+signature_scheme(rsa_pkcs1_sha512) -> ?RSA_PKCS1_SHA512;
+signature_scheme(ecdsa_secp256r1_sha256) -> ?ECDSA_SECP256R1_SHA256;
+signature_scheme(ecdsa_secp384r1_sha384) -> ?ECDSA_SECP384R1_SHA384;
+signature_scheme(ecdsa_secp521r1_sha512) -> ?ECDSA_SECP521R1_SHA512;
+signature_scheme(rsa_pss_rsae_sha256) -> ?RSA_PSS_RSAE_SHA256;
+signature_scheme(rsa_pss_rsae_sha384) -> ?RSA_PSS_RSAE_SHA384;
+signature_scheme(rsa_pss_rsae_sha512) -> ?RSA_PSS_RSAE_SHA512;
+signature_scheme(ed25519) -> ?ED25519;
+signature_scheme(ed448) -> ?ED448;
+signature_scheme(rsa_pss_pss_sha256) -> ?RSA_PSS_PSS_SHA256;
+signature_scheme(rsa_pss_pss_sha384) -> ?RSA_PSS_PSS_SHA384;
+signature_scheme(rsa_pss_pss_sha512) -> ?RSA_PSS_PSS_SHA512;
+signature_scheme(rsa_pkcs1_sha1) -> ?RSA_PKCS1_SHA1;
+signature_scheme(ecdsa_sha1) -> ?ECDSA_SHA1;
+signature_scheme(?RSA_PKCS1_SHA256) -> rsa_pkcs1_sha256;
+signature_scheme(?RSA_PKCS1_SHA384) -> rsa_pkcs1_sha384;
+signature_scheme(?RSA_PKCS1_SHA512) -> rsa_pkcs1_sha512;
+signature_scheme(?ECDSA_SECP256R1_SHA256) -> ecdsa_secp256r1_sha256;
+signature_scheme(?ECDSA_SECP384R1_SHA384) -> ecdsa_secp384r1_sha384;
+signature_scheme(?ECDSA_SECP521R1_SHA512) -> ecdsa_secp521r1_sha512;
+signature_scheme(?RSA_PSS_RSAE_SHA256) -> rsa_pss_rsae_sha256;
+signature_scheme(?RSA_PSS_RSAE_SHA384) -> rsa_pss_rsae_sha384;
+signature_scheme(?RSA_PSS_RSAE_SHA512) -> rsa_pss_rsae_sha512;
+signature_scheme(?ED25519) -> ed25519;
+signature_scheme(?ED448) -> ed448;
+signature_scheme(?RSA_PSS_PSS_SHA256) -> rsa_pss_pss_sha256;
+signature_scheme(?RSA_PSS_PSS_SHA384) -> rsa_pss_pss_sha384;
+signature_scheme(?RSA_PSS_PSS_SHA512) -> rsa_pss_pss_sha512;
+signature_scheme(?RSA_PKCS1_SHA1) -> rsa_pkcs1_sha1;
+signature_scheme(?ECDSA_SHA1) -> ecdsa_sha1;
+signature_scheme(_) -> unassigned.
+%% TODO: reserved code points?
+
+scheme_to_components(rsa_pkcs1_sha256) -> {sha256, rsa_pkcs1, undefined};
+scheme_to_components(rsa_pkcs1_sha384) -> {sha384, rsa_pkcs1, undefined};
+scheme_to_components(rsa_pkcs1_sha512) -> {sha512, rsa_pkcs1, undefined};
+scheme_to_components(ecdsa_secp256r1_sha256) -> {sha256, ecdsa, secp256r1};
+scheme_to_components(ecdsa_secp384r1_sha384) -> {sha384, ecdsa, secp384r1};
+scheme_to_components(ecdsa_secp521r1_sha512) -> {sha512, ecdsa, secp521r1};
+scheme_to_components(rsa_pss_rsae_sha256) -> {sha256, rsa_pss_rsae, undefined};
+scheme_to_components(rsa_pss_rsae_sha384) -> {sha384, rsa_pss_rsae, undefined};
+scheme_to_components(rsa_pss_rsae_sha512) -> {sha512, rsa_pss_rsae, undefined};
+scheme_to_components(ed25519) -> {undefined, undefined, undefined};
+scheme_to_components(ed448) -> {undefined, undefined, undefined};
+scheme_to_components(rsa_pss_pss_sha256) -> {sha256, rsa_pss_pss, undefined};
+scheme_to_components(rsa_pss_pss_sha384) -> {sha384, rsa_pss_pss, undefined};
+scheme_to_components(rsa_pss_pss_sha512) -> {sha512, rsa_pss_pss, undefined};
+scheme_to_components(rsa_pkcs1_sha1) -> {sha1, rsa_pkcs1, undefined};
+scheme_to_components(ecdsa_sha1) -> {sha1, ecdsa, undefined}.
%% RFC 5246: 6.2.3.2. CBC Block Cipher
%%
@@ -858,7 +936,7 @@ generic_block_cipher_from_bin({3, N}, T, IV, HashSize)
next_iv = IV};
generic_block_cipher_from_bin({3, N}, T, IV, HashSize)
- when N == 2; N == 3 ->
+ when N == 2; N == 3; N == 4 ->
Sz1 = byte_size(T) - 1,
<<_:Sz1/binary, ?BYTE(PadLength)>> = T,
IVLength = byte_size(IV),
@@ -888,21 +966,51 @@ is_correct_padding(GenBlockCipher, {3, 1}, false) ->
%% Padding must be checked in TLS 1.1 and after
is_correct_padding(#generic_block_cipher{padding_length = Len,
padding = Padding}, _, _) ->
- Len == byte_size(Padding) andalso
- binary:copy(?byte(Len), Len) == Padding.
-
-get_padding(Length, BlockSize) ->
- get_padding_aux(BlockSize, Length rem BlockSize).
-
-get_padding_aux(_, 0) ->
- {0, <<>>};
-get_padding_aux(BlockSize, PadLength) ->
- N = BlockSize - PadLength,
- {N, binary:copy(?byte(N), N)}.
+ (Len == byte_size(Padding)) andalso (padding(Len) == Padding).
+
+padding(PadLen) ->
+ case PadLen of
+ 0 -> <<>>;
+ 1 -> <<1>>;
+ 2 -> <<2,2>>;
+ 3 -> <<3,3,3>>;
+ 4 -> <<4,4,4,4>>;
+ 5 -> <<5,5,5,5,5>>;
+ 6 -> <<6,6,6,6,6,6>>;
+ 7 -> <<7,7,7,7,7,7,7>>;
+ 8 -> <<8,8,8,8,8,8,8,8>>;
+ 9 -> <<9,9,9,9,9,9,9,9,9>>;
+ 10 -> <<10,10,10,10,10,10,10,10,10,10>>;
+ 11 -> <<11,11,11,11,11,11,11,11,11,11,11>>;
+ 12 -> <<12,12,12,12,12,12,12,12,12,12,12,12>>;
+ 13 -> <<13,13,13,13,13,13,13,13,13,13,13,13,13>>;
+ 14 -> <<14,14,14,14,14,14,14,14,14,14,14,14,14,14>>;
+ 15 -> <<15,15,15,15,15,15,15,15,15,15,15,15,15,15,15>>;
+ _ ->
+ binary:copy(<<PadLen>>, PadLen)
+ end.
-random_iv(IV) ->
- IVSz = byte_size(IV),
- random_bytes(IVSz).
+padding_with_len(TextLen, BlockSize) ->
+ case BlockSize - (TextLen rem BlockSize) of
+ 0 -> <<0>>;
+ 1 -> <<1,1>>;
+ 2 -> <<2,2,2>>;
+ 3 -> <<3,3,3,3>>;
+ 4 -> <<4,4,4,4,4>>;
+ 5 -> <<5,5,5,5,5,5>>;
+ 6 -> <<6,6,6,6,6,6,6>>;
+ 7 -> <<7,7,7,7,7,7,7,7>>;
+ 8 -> <<8,8,8,8,8,8,8,8,8>>;
+ 9 -> <<9,9,9,9,9,9,9,9,9,9>>;
+ 10 -> <<10,10,10,10,10,10,10,10,10,10,10>>;
+ 11 -> <<11,11,11,11,11,11,11,11,11,11,11,11>>;
+ 12 -> <<12,12,12,12,12,12,12,12,12,12,12,12,12>>;
+ 13 -> <<13,13,13,13,13,13,13,13,13,13,13,13,13,13>>;
+ 14 -> <<14,14,14,14,14,14,14,14,14,14,14,14,14,14,14>>;
+ 15 -> <<15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15>>;
+ PadLen ->
+ binary:copy(<<PadLen>>, PadLen + 1)
+ end.
next_iv(Bin, IV) ->
BinSz = byte_size(Bin),
@@ -1126,3 +1234,55 @@ filter_keyuse_suites(Use, KeyUse, CipherSuits, Suites) ->
false ->
CipherSuits -- Suites
end.
+
+generate_server_share(Group) ->
+ Key = generate_key_exchange(Group),
+ #key_share_server_hello{
+ server_share = #key_share_entry{
+ group = 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]).
+
+
+generate_key_exchange(secp256r1) ->
+ public_key:generate_key({namedCurve, secp256r1});
+generate_key_exchange(secp384r1) ->
+ public_key:generate_key({namedCurve, secp384r1});
+generate_key_exchange(secp521r1) ->
+ public_key:generate_key({namedCurve, secp521r1});
+generate_key_exchange(x25519) ->
+ crypto:generate_key(ecdh, x25519);
+generate_key_exchange(x448) ->
+ crypto:generate_key(ecdh, x448);
+generate_key_exchange(FFDHE) ->
+ public_key:generate_key(ssl_dh_groups:dh_params(FFDHE)).
+
+
+%% TODO: Move this functionality to crypto!
+%% 7.4.1. Finite Field Diffie-Hellman
+%%
+%% For finite field groups, a conventional Diffie-Hellman [DH76]
+%% computation is performed. The negotiated key (Z) is converted to a
+%% byte string by encoding in big-endian form and left-padded with zeros
+%% up to the size of the prime. This byte string is used as the shared
+%% secret in the key schedule as specified above.
+add_zero_padding(Bin, PrimeSize)
+ when byte_size (Bin) =:= PrimeSize ->
+ Bin;
+add_zero_padding(Bin, PrimeSize) ->
+ add_zero_padding(<<0, Bin/binary>>, PrimeSize).
diff --git a/lib/ssl/src/ssl_cipher.hrl b/lib/ssl/src/ssl_cipher.hrl
index 2371e8bd32..00822ad9de 100644
--- a/lib/ssl/src/ssl_cipher.hrl
+++ b/lib/ssl/src/ssl_cipher.hrl
@@ -47,6 +47,7 @@
-record(cipher_state, {
iv,
key,
+ finished_key,
state,
nonce,
tag_len
@@ -611,4 +612,21 @@
%% TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = {0xcc, 0x15}
-define(TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256, <<?BYTE(16#CC), ?BYTE(16#15)>>).
+%%% TLS 1.3 cipher suites RFC8446
+
+%% TLS_AES_128_GCM_SHA256 = {0x13,0x01}
+-define(TLS_AES_128_GCM_SHA256, <<?BYTE(16#13), ?BYTE(16#01)>>).
+
+%% TLS_AES_256_GCM_SHA384 = {0x13,0x02}
+-define(TLS_AES_256_GCM_SHA384, <<?BYTE(16#13),?BYTE(16#02)>>).
+
+%% TLS_CHACHA20_POLY1305_SHA256 = {0x13,0x03}
+-define(TLS_CHACHA20_POLY1305_SHA256, <<?BYTE(16#13),?BYTE(16#03)>>).
+
+%% %% TLS_AES_128_CCM_SHA256 = {0x13,0x04}
+%% -define(TLS_AES_128_CCM_SHA256, <<?BYTE(16#13), ?BYTE(16#04)>>).
+
+%% %% TLS_AES_128_CCM_8_SHA256 = {0x13,0x05}
+%% -define(TLS_AES_128_CCM_8_SHA256, <<?BYTE(16#13),?BYTE(16#05)>>).
+
-endif. % -ifdef(ssl_cipher).
diff --git a/lib/ssl/src/ssl_cipher_format.erl b/lib/ssl/src/ssl_cipher_format.erl
index c311c0d097..b592295d56 100644
--- a/lib/ssl/src/ssl_cipher_format.erl
+++ b/lib/ssl/src/ssl_cipher_format.erl
@@ -25,26 +25,25 @@
%%----------------------------------------------------------------------
-module(ssl_cipher_format).
+-include("ssl_api.hrl").
-include("ssl_cipher.hrl").
-include("ssl_internal.hrl").
-include_lib("public_key/include/public_key.hrl").
--export_type([cipher_suite/0,
- erl_cipher_suite/0, old_erl_cipher_suite/0, openssl_cipher_suite/0,
- hash/0, key_algo/0, sign_algo/0]).
+-export_type([old_erl_cipher_suite/0, openssl_cipher_suite/0, cipher_suite/0]).
--type cipher() :: null |rc4_128 | des_cbc | '3des_ede_cbc' | aes_128_cbc | aes_256_cbc | aes_128_gcm | aes_256_gcm | chacha20_poly1305.
--type hash() :: null | md5 | sha | sha224 | sha256 | sha384 | sha512.
--type sign_algo() :: rsa | dsa | ecdsa.
--type key_algo() :: null | rsa | dhe_rsa | dhe_dss | ecdhe_ecdsa| ecdh_ecdsa | ecdh_rsa| srp_rsa| srp_dss | psk | dhe_psk | rsa_psk | dh_anon | ecdh_anon | srp_anon.
--type erl_cipher_suite() :: #{key_exchange := key_algo(),
- cipher := cipher(),
- mac := hash() | aead,
- prf := hash() | default_prf %% Old cipher suites, version dependent
+-type internal_cipher() :: null | ssl:cipher().
+-type internal_hash() :: null | ssl:hash().
+-type internal_kex_algo() :: null | ssl:kex_algo().
+-type internal_erl_cipher_suite() :: #{key_exchange := internal_kex_algo(),
+ cipher := internal_cipher(),
+ mac := internal_hash() | aead,
+ prf := internal_hash() | default_prf %% Old cipher suites, version dependent
}.
--type old_erl_cipher_suite() :: {key_algo(), cipher(), hash()} % Pre TLS 1.2
+-type old_erl_cipher_suite() :: {ssl:kex_algo(), internal_cipher(), internal_hash()} % Pre TLS 1.2
%% TLS 1.2, internally PRE TLS 1.2 will use default_prf
- | {key_algo(), cipher(), hash(), hash() | default_prf}.
+ | {ssl:kex_algo(), internal_cipher(), internal_hash(),
+ internal_hash() | default_prf}.
-type cipher_suite() :: binary().
-type openssl_cipher_suite() :: string().
@@ -53,7 +52,7 @@
openssl_suite/1, openssl_suite_name/1]).
%%--------------------------------------------------------------------
--spec suite_to_str(erl_cipher_suite()) -> string().
+-spec suite_to_str(internal_erl_cipher_suite()) -> string().
%%
%% Description: Return the string representation of a cipher suite.
%%--------------------------------------------------------------------
@@ -62,6 +61,12 @@ suite_to_str(#{key_exchange := null,
mac := null,
prf := null}) ->
"TLS_EMPTY_RENEGOTIATION_INFO_SCSV";
+suite_to_str(#{key_exchange := any,
+ cipher := Cipher,
+ mac := aead,
+ prf := PRF}) ->
+ "TLS_" ++ string:to_upper(atom_to_list(Cipher)) ++
+ "_" ++ string:to_upper(atom_to_list(PRF));
suite_to_str(#{key_exchange := Kex,
cipher := Cipher,
mac := aead,
@@ -77,7 +82,7 @@ suite_to_str(#{key_exchange := Kex,
"_" ++ string:to_upper(atom_to_list(Mac)).
%%--------------------------------------------------------------------
--spec suite_definition(cipher_suite()) -> erl_cipher_suite().
+-spec suite_definition(cipher_suite()) -> internal_erl_cipher_suite().
%%
%% Description: Return erlang cipher suite definition.
%% Note: Currently not supported suites are commented away.
@@ -802,10 +807,37 @@ suite_definition(?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256) ->
#{key_exchange => dhe_rsa,
cipher => chacha20_poly1305,
mac => aead,
+ prf => sha256};
+%% TLS 1.3 Cipher Suites RFC8446
+suite_definition(?TLS_AES_128_GCM_SHA256) ->
+ #{key_exchange => any,
+ cipher => aes_128_gcm,
+ mac => aead,
+ prf => sha256};
+suite_definition(?TLS_AES_256_GCM_SHA384) ->
+ #{key_exchange => any,
+ cipher => aes_256_gcm,
+ mac => aead,
+ prf => sha384};
+suite_definition(?TLS_CHACHA20_POLY1305_SHA256) ->
+ #{key_exchange => any,
+ cipher => chacha20_poly1305,
+ mac => aead,
prf => sha256}.
+%% suite_definition(?TLS_AES_128_CCM_SHA256) ->
+%% #{key_exchange => any,
+%% cipher => aes_128_ccm,
+%% mac => aead,
+%% prf => sha256};
+%% suite_definition(?TLS_AES_128_CCM_8_SHA256) ->
+%% #{key_exchange => any,
+%% cipher => aes_128_ccm_8,
+%% mac => aead,
+%% prf => sha256}.
+
%%--------------------------------------------------------------------
--spec erl_suite_definition(cipher_suite() | erl_cipher_suite()) -> old_erl_cipher_suite().
+-spec erl_suite_definition(cipher_suite() | internal_erl_cipher_suite()) -> old_erl_cipher_suite().
%%
%% Description: Return erlang cipher suite definition. Filters last value
%% for now (compatibility reasons).
@@ -822,7 +854,7 @@ erl_suite_definition(#{key_exchange := KeyExchange, cipher := Cipher,
end.
%%--------------------------------------------------------------------
--spec suite(erl_cipher_suite()) -> cipher_suite().
+-spec suite(internal_erl_cipher_suite()) -> cipher_suite().
%%
%% Description: Return TLS cipher suite definition.
%%--------------------------------------------------------------------
@@ -1427,8 +1459,33 @@ suite(#{key_exchange := dhe_rsa,
cipher := chacha20_poly1305,
mac := aead,
prf := sha256}) ->
- ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256.
-
+ ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256;
+%% TLS 1.3 Cipher Suites RFC8446
+suite(#{key_exchange := any,
+ cipher := aes_128_gcm,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_AES_128_GCM_SHA256;
+suite(#{key_exchange := any,
+ cipher := aes_256_gcm,
+ mac := aead,
+ prf := sha384}) ->
+ ?TLS_AES_256_GCM_SHA384;
+suite(#{key_exchange := any,
+ cipher := chacha20_poly1305,
+ mac := aead,
+ prf := sha256}) ->
+ ?TLS_CHACHA20_POLY1305_SHA256.
+%% suite(#{key_exchange := any,
+%% cipher := aes_128_ccm,
+%% mac := aead,
+%% prf := sha256}) ->
+%% ?TLS_AES_128_CCM_SHA256;
+%% suite(#{key_exchange := any,
+%% cipher := aes_128_ccm_8,
+%% mac := aead,
+%% prf := sha256}) ->
+%% ?TLS_AES_128_CCM_8_SHA256.
%%--------------------------------------------------------------------
-spec openssl_suite(openssl_cipher_suite()) -> cipher_suite().
%%
@@ -1582,10 +1639,23 @@ openssl_suite("ECDHE-RSA-AES256-GCM-SHA384") ->
openssl_suite("ECDH-RSA-AES128-GCM-SHA256") ->
?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256;
openssl_suite("ECDH-RSA-AES256-GCM-SHA384") ->
- ?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384.
+ ?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384;
+
+%% TLS 1.3 Cipher Suites RFC8446
+openssl_suite("TLS_AES_128_GCM_SHA256") ->
+ ?TLS_AES_128_GCM_SHA256;
+openssl_suite("TLS_AES_256_GCM_SHA384") ->
+ ?TLS_AES_256_GCM_SHA384;
+openssl_suite("TLS_CHACHA20_POLY1305_SHA256") ->
+ ?TLS_CHACHA20_POLY1305_SHA256.
+%% openssl_suite("TLS_AES_128_CCM_SHA256") ->
+%% ?TLS_AES_128_CCM_SHA256;
+%% openssl_suite("TLS_AES_128_CCM_8_SHA256") ->
+%% ?TLS_AES_128_CCM_8_SHA256.
+
%%--------------------------------------------------------------------
--spec openssl_suite_name(cipher_suite()) -> openssl_cipher_suite() | erl_cipher_suite().
+-spec openssl_suite_name(cipher_suite()) -> openssl_cipher_suite() | internal_erl_cipher_suite().
%%
%% Description: Return openssl cipher suite name if possible
%%-------------------------------------------------------------------
@@ -1759,6 +1829,18 @@ openssl_suite_name(?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256) ->
openssl_suite_name(?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384) ->
"ECDH-RSA-AES256-GCM-SHA384";
+%% TLS 1.3 Cipher Suites RFC8446
+openssl_suite_name(?TLS_AES_128_GCM_SHA256) ->
+ "TLS_AES_128_GCM_SHA256";
+openssl_suite_name(?TLS_AES_256_GCM_SHA384) ->
+ "TLS_AES_256_GCM_SHA384";
+openssl_suite_name(?TLS_CHACHA20_POLY1305_SHA256) ->
+ "TLS_CHACHA20_POLY1305_SHA256";
+%% openssl_suite(?TLS_AES_128_CCM_SHA256) ->
+%% "TLS_AES_128_CCM_SHA256";
+%% openssl_suite(?TLS_AES_128_CCM_8_SHA256) ->
+%% "TLS_AES_128_CCM_8_SHA256";
+
%% No oppenssl name
openssl_suite_name(Cipher) ->
suite_definition(Cipher).
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index 0e2a677273..422c4c94ae 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2019. All 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,12 +35,13 @@
-include("ssl_internal.hrl").
-include("ssl_srp.hrl").
-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/logger.hrl").
%% Setup
--export([connect/8, handshake/7, handshake/2, handshake/3,
+-export([connect/8, handshake/7, handshake/2, handshake/3, handle_common_event/5,
handshake_continue/3, handshake_cancel/1,
- socket_control/4, socket_control/5, start_or_recv_cancel_timer/2]).
+ socket_control/4, socket_control/5]).
%% User Events
-export([send/2, recv/3, close/2, shutdown/2,
@@ -59,7 +60,7 @@
%% Help functions for tls|dtls_connection.erl
-export([handle_session/7, ssl_config/3,
- prepare_connection/2, hibernate_after/3, map_extensions/1]).
+ prepare_connection/2, hibernate_after/3]).
%% General gen_statem state functions with extra callback argument
%% to determine if it is an SSL/TLS or DTLS gen_statem machine
@@ -70,14 +71,14 @@
-export([terminate/3, format_status/2]).
%% Erlang Distribution export
--export([get_sslsocket/1, dist_handshake_complete/2]).
+-export([dist_handshake_complete/2]).
%%====================================================================
%% Setup
%%====================================================================
%%--------------------------------------------------------------------
-spec connect(tls_connection | dtls_connection,
- host(), inet:port_number(),
+ ssl:host(), inet:port_number(),
port() | {tuple(), port()}, %% TLS | DTLS
{#ssl_options{}, #socket_options{},
%% Tracker only needed on server side
@@ -143,7 +144,7 @@ handshake(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) ->
end.
%%--------------------------------------------------------------------
--spec handshake_continue(#sslsocket{}, [ssl_option()],
+-spec handshake_continue(#sslsocket{}, [ssl:tls_server_option()],
timeout()) -> {ok, #sslsocket{}}| {error, reason()}.
%%
%% Description: Continues handshake with new options
@@ -182,27 +183,23 @@ socket_control(Connection, Socket, Pid, Transport) ->
%%--------------------------------------------------------------------
socket_control(Connection, Socket, Pids, Transport, udp_listener) ->
%% dtls listener process must have the socket control
- {ok, Connection:socket(Pids, Transport, Socket, Connection, undefined)};
+ {ok, Connection:socket(Pids, Transport, Socket, undefined)};
socket_control(tls_connection = Connection, Socket, [Pid|_] = Pids, Transport, ListenTracker) ->
case Transport:controlling_process(Socket, Pid) of
ok ->
- {ok, Connection:socket(Pids, Transport, Socket, Connection, ListenTracker)};
+ {ok, Connection:socket(Pids, Transport, Socket, ListenTracker)};
{error, Reason} ->
{error, Reason}
end;
socket_control(dtls_connection = Connection, {_, Socket}, [Pid|_] = Pids, Transport, ListenTracker) ->
case Transport:controlling_process(Socket, Pid) of
ok ->
- {ok, Connection:socket(Pids, Transport, Socket, Connection, ListenTracker)};
+ {ok, Connection:socket(Pids, Transport, Socket, ListenTracker)};
{error, Reason} ->
{error, Reason}
end.
-start_or_recv_cancel_timer(infinity, _RecvFrom) ->
- undefined;
-start_or_recv_cancel_timer(Timeout, RecvFrom) ->
- erlang:send_after(Timeout, self(), {cancel_start_or_recv, RecvFrom}).
%%====================================================================
%% User events
@@ -215,9 +212,9 @@ start_or_recv_cancel_timer(Timeout, RecvFrom) ->
%%--------------------------------------------------------------------
send(Pid, Data) ->
call(Pid, {application_data,
- %% iolist_to_binary should really
- %% be called iodata_to_binary()
- erlang:iolist_to_binary(Data)}).
+ %% iolist_to_iovec should really
+ %% be called iodata_to_iovec()
+ erlang:iolist_to_iovec(Data)}).
%%--------------------------------------------------------------------
-spec recv(pid(), integer(), timeout()) ->
@@ -315,9 +312,6 @@ renegotiation(ConnectionPid) ->
internal_renegotiation(ConnectionPid, #{current_write := WriteState}) ->
gen_statem:cast(ConnectionPid, {internal_renegotiate, WriteState}).
-get_sslsocket(ConnectionPid) ->
- call(ConnectionPid, get_sslsocket).
-
dist_handshake_complete(ConnectionPid, DHandle) ->
gen_statem:cast(ConnectionPid, {dist_handshake_complete, DHandle}).
@@ -344,7 +338,9 @@ handle_own_alert(Alert, _, StateName,
ignore
end,
try %% Try to tell the local user
- log_alert(SslOpts#ssl_options.log_alert, Role, Connection:protocol_name(), StateName, Alert#alert{role = Role}),
+ log_alert(SslOpts#ssl_options.log_level, Role,
+ Connection:protocol_name(), StateName,
+ Alert#alert{role = Role}),
handle_normal_shutdown(Alert,StateName, State)
catch _:_ ->
ok
@@ -356,8 +352,8 @@ handle_normal_shutdown(Alert, _, #state{static_env = #static_env{role = Role,
transport_cb = Transport,
protocol_cb = Connection,
tracker = Tracker},
- start_or_recv_from = StartFrom,
- renegotiation = {false, first}} = State) ->
+ handshake_env = #handshake_env{renegotiation = {false, first}},
+ start_or_recv_from = StartFrom} = State) ->
Pids = Connection:pids(State),
alert_user(Pids, Transport, Tracker,Socket, StartFrom, Alert, Role, Connection);
@@ -366,8 +362,8 @@ handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role =
transport_cb = Transport,
protocol_cb = Connection,
tracker = Tracker},
- socket_options = Opts,
- user_application = {_Mon, Pid},
+ connection_env = #connection_env{user_application = {_Mon, Pid}},
+ socket_options = Opts,
start_or_recv_from = RecvFrom} = State) ->
Pids = Connection:pids(State),
alert_user(Pids, Transport, Tracker, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role, Connection).
@@ -380,12 +376,13 @@ handle_alert(#alert{level = ?FATAL} = Alert, StateName,
tracker = Tracker,
transport_cb = Transport,
protocol_cb = Connection},
+ connection_env = #connection_env{user_application = {_Mon, Pid}},
ssl_options = SslOpts,
start_or_recv_from = From,
- session = Session, user_application = {_Mon, Pid},
+ session = Session,
socket_options = Opts} = State) ->
invalidate_session(Role, Host, Port, Session),
- log_alert(SslOpts#ssl_options.log_alert, Role, Connection:protocol_name(),
+ log_alert(SslOpts#ssl_options.log_level, Role, Connection:protocol_name(),
StateName, Alert#alert{role = opposite_role(Role)}),
Pids = Connection:pids(State),
alert_user(Pids, Transport, Tracker, Socket, StateName, Opts, Pid, From, Alert, Role, Connection),
@@ -401,9 +398,9 @@ handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName,
#state{static_env = #static_env{role = Role,
protocol_cb = Connection},
- ssl_options = SslOpts,
- renegotiation = {true, internal}} = State) ->
- log_alert(SslOpts#ssl_options.log_alert, Role,
+ handshake_env = #handshake_env{renegotiation = {true, internal}},
+ ssl_options = SslOpts} = State) ->
+ log_alert(SslOpts#ssl_options.log_level, Role,
Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}),
handle_normal_shutdown(Alert, StateName, State),
{stop,{shutdown, peer_close}, State};
@@ -411,26 +408,26 @@ handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert,
handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, connection = StateName,
#state{static_env = #static_env{role = Role,
protocol_cb = Connection},
- ssl_options = SslOpts,
- renegotiation = {true, From}
+ handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv,
+ ssl_options = SslOpts
} = State0) ->
- log_alert(SslOpts#ssl_options.log_alert, Role,
+ log_alert(SslOpts#ssl_options.log_level, Role,
Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}),
gen_statem:reply(From, {error, renegotiation_rejected}),
State = Connection:reinit_handshake_data(State0),
- Connection:next_event(connection, no_record, State#state{renegotiation = undefined});
+ Connection:next_event(connection, no_record, State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}});
handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName,
#state{static_env = #static_env{role = Role,
protocol_cb = Connection},
- ssl_options = SslOpts,
- renegotiation = {true, From}
- } = State0) ->
- log_alert(SslOpts#ssl_options.log_alert, Role,
+ handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv,
+ ssl_options = SslOpts
+ } = State0) ->
+ log_alert(SslOpts#ssl_options.log_level, Role,
Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}),
gen_statem:reply(From, {error, renegotiation_rejected}),
%% Go back to connection!
- State = Connection:reinit(State0#state{renegotiation = undefined}),
+ State = Connection:reinit(State0#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}),
Connection:next_event(connection, no_record, State);
%% Gracefully log and ignore all other warning alerts
@@ -438,110 +435,258 @@ handle_alert(#alert{level = ?WARNING} = Alert, StateName,
#state{static_env = #static_env{role = Role,
protocol_cb = Connection},
ssl_options = SslOpts} = State) ->
- log_alert(SslOpts#ssl_options.log_alert, Role,
- Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}),
+ log_alert(SslOpts#ssl_options.log_level, Role,
+ Connection:protocol_name(), StateName,
+ Alert#alert{role = opposite_role(Role)}),
Connection:next_event(StateName, no_record, State).
%%====================================================================
%% Data handling
%%====================================================================
-passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName, Connection) ->
- case Buffer of
- <<>> ->
- {Record, State} = Connection:next_record(State0),
- Connection:next_event(StateName, Record, State);
+passive_receive(State0 = #state{user_data_buffer = {_,BufferSize,_}}, StateName, Connection, StartTimerAction) ->
+ case BufferSize of
+ 0 ->
+ Connection:next_event(StateName, no_record, State0, StartTimerAction);
_ ->
case read_application_data(<<>>, State0) of
{stop, _, _} = ShutdownError ->
ShutdownError;
{Record, State} ->
- Connection:next_event(StateName, Record, State)
+ case State#state.start_or_recv_from of
+ undefined ->
+ %% Cancel recv timeout as data has been delivered
+ Connection:next_event(StateName, Record, State,
+ [{{timeout, recv}, infinity, timeout}]);
+ _ ->
+ Connection:next_event(StateName, Record, State, StartTimerAction)
+ end
+ end
+ end.
+
+read_application_data(
+ Data,
+ #state{
+ user_data_buffer = {Front0,BufferSize0,Rear0},
+ connection_env = #connection_env{erl_dist_handle = DHandle}} = State) ->
+ %%
+ Front = Front0,
+ BufferSize = BufferSize0 + byte_size(Data),
+ Rear = [Data|Rear0],
+ case DHandle of
+ undefined ->
+ read_application_data(State, Front, BufferSize, Rear);
+ _ ->
+ try read_application_dist_data(DHandle, Front, BufferSize, Rear) of
+ Buffer ->
+ {no_record, State#state{user_data_buffer = Buffer}}
+ catch error:_ ->
+ {stop,disconnect,
+ State#state{user_data_buffer = {Front,BufferSize,Rear}}}
end
end.
-read_application_data(Data, #state{static_env = #static_env{socket = Socket,
- protocol_cb = Connection,
- transport_cb = Transport,
- tracker = Tracker},
- user_application = {_Mon, Pid},
- socket_options = SOpts,
- bytes_to_read = BytesToRead,
- start_or_recv_from = RecvFrom,
- timer = Timer,
- user_data_buffer = Buffer0} = State0) ->
- Buffer1 = if
- Buffer0 =:= <<>> -> Data;
- Data =:= <<>> -> Buffer0;
- true -> <<Buffer0/binary, Data/binary>>
- end,
- case get_data(SOpts, BytesToRead, Buffer1) of
- {ok, ClientData, Buffer} -> % Send data
- #state{ssl_options = #ssl_options{erl_dist = Dist},
- erl_dist_data = DistData} = State0,
- case Dist andalso is_dist_up(DistData) of
- true ->
- dist_app_data(ClientData, State0#state{user_data_buffer = Buffer,
- bytes_to_read = undefined});
- _ ->
- SocketOpt =
- deliver_app_data(Connection:pids(State0),
- Transport, Socket, SOpts,
- ClientData, Pid, RecvFrom, Tracker, Connection),
- cancel_timer(Timer),
- State =
- State0#state{
- user_data_buffer = Buffer,
- start_or_recv_from = undefined,
- timer = undefined,
- bytes_to_read = undefined,
- socket_options = SocketOpt
- },
- if
- SocketOpt#socket_options.active =:= false;
- Buffer =:= <<>> ->
- %% Passive mode, wait for active once or recv
- %% Active and empty, get more data
- {no_record, State};
- true -> %% We have more data
- read_application_data(<<>>, State)
- end
- end;
- {more, Buffer} -> % no reply, we need more data
- {no_record, State0#state{user_data_buffer = Buffer}};
- {passive, Buffer} ->
- {no_record, State0#state{user_data_buffer = Buffer}};
- {error,_Reason} -> %% Invalid packet in packet mode
- deliver_packet_error(Connection:pids(State0),
- Transport, Socket, SOpts, Buffer1, Pid, RecvFrom, Tracker, Connection),
- {stop, {shutdown, normal}, State0}
+
+read_application_data(#state{
+ socket_options = SocketOpts,
+ bytes_to_read = BytesToRead,
+ start_or_recv_from = RecvFrom} = State, Front, BufferSize, Rear) ->
+ read_application_data(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead).
+
+%% Pick binary from queue front, if empty wait for more data
+read_application_data(State, [Bin|Front], BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead) ->
+ read_application_data_bin(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead, Bin);
+read_application_data(State, [] = Front, BufferSize, [] = Rear, SocketOpts, RecvFrom, BytesToRead) ->
+ 0 = BufferSize, % Assert
+ {no_record, State#state{socket_options = SocketOpts,
+ bytes_to_read = BytesToRead,
+ start_or_recv_from = RecvFrom,
+ user_data_buffer = {Front,BufferSize,Rear}}};
+read_application_data(State, [], BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead) ->
+ [Bin|Front] = lists:reverse(Rear),
+ read_application_data_bin(State, Front, BufferSize, [], SocketOpts, RecvFrom, BytesToRead, Bin).
+
+read_application_data_bin(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead, <<>>) ->
+ %% Done with this binary - get next
+ read_application_data(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead);
+read_application_data_bin(State, Front0, BufferSize0, Rear0, SocketOpts0, RecvFrom, BytesToRead, Bin0) ->
+ %% Decode one packet from a binary
+ case get_data(SocketOpts0, BytesToRead, Bin0) of
+ {ok, Data, Bin} -> % Send data
+ BufferSize = BufferSize0 - (byte_size(Bin0) - byte_size(Bin)),
+ read_application_data_deliver(
+ State, [Bin|Front0], BufferSize, Rear0, SocketOpts0, RecvFrom, Data);
+ {more, undefined} ->
+ %% We need more data, do not know how much
+ if
+ byte_size(Bin0) < BufferSize0 ->
+ %% We have more data in the buffer besides the first binary - concatenate all and retry
+ Bin = iolist_to_binary([Bin0,Front0|lists:reverse(Rear0)]),
+ read_application_data_bin(
+ State, [], BufferSize0, [], SocketOpts0, RecvFrom, BytesToRead, Bin);
+ true ->
+ %% All data is in the first binary, no use to retry - wait for more
+ {no_record, State#state{socket_options = SocketOpts0,
+ bytes_to_read = BytesToRead,
+ start_or_recv_from = RecvFrom,
+ user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}}
+ end;
+ {more, Size} when Size =< BufferSize0 ->
+ %% We have a packet in the buffer - collect it in a binary and decode
+ {Data,Front,Rear} = iovec_from_front(Size - byte_size(Bin0), Front0, Rear0, [Bin0]),
+ Bin = iolist_to_binary(Data),
+ read_application_data_bin(
+ State, Front, BufferSize0, Rear, SocketOpts0, RecvFrom, BytesToRead, Bin);
+ {more, _Size} ->
+ %% We do not have a packet in the buffer - wait for more
+ {no_record, State#state{socket_options = SocketOpts0,
+ bytes_to_read = BytesToRead,
+ start_or_recv_from = RecvFrom,
+ user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}};
+ passive ->
+ {no_record, State#state{socket_options = SocketOpts0,
+ bytes_to_read = BytesToRead,
+ start_or_recv_from = RecvFrom,
+ user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}};
+ {error,_Reason} ->
+ %% Invalid packet in packet mode
+ #state{
+ static_env =
+ #static_env{
+ socket = Socket,
+ protocol_cb = Connection,
+ transport_cb = Transport,
+ tracker = Tracker},
+ connection_env =
+ #connection_env{user_application = {_Mon, Pid}}} = State,
+ Buffer = iolist_to_binary([Bin0,Front0|lists:reverse(Rear0)]),
+ deliver_packet_error(
+ Connection:pids(State), Transport, Socket, SocketOpts0,
+ Buffer, Pid, RecvFrom, Tracker, Connection),
+ {stop, {shutdown, normal}, State#state{socket_options = SocketOpts0,
+ bytes_to_read = BytesToRead,
+ start_or_recv_from = RecvFrom,
+ user_data_buffer = {[Buffer],BufferSize0,[]}}}
end.
-dist_app_data(ClientData, #state{erl_dist_data = #{dist_handle := undefined,
- dist_buffer := DistBuff} = DistData} = State) ->
- {no_record, State#state{erl_dist_data = DistData#{dist_buffer => [ClientData, DistBuff]}}};
-dist_app_data(ClientData, #state{erl_dist_data = #{dist_handle := DHandle,
- dist_buffer := DistBuff} = ErlDistData,
- user_data_buffer = Buffer,
- socket_options = SOpts} = State) ->
- Data = merge_dist_data(DistBuff, ClientData),
- try erlang:dist_ctrl_put_data(DHandle, Data) of
- _ when SOpts#socket_options.active =:= false;
- Buffer =:= <<>> ->
+read_application_data_deliver(State, Front, BufferSize, Rear, SocketOpts0, RecvFrom, Data) ->
+ #state{
+ static_env =
+ #static_env{
+ socket = Socket,
+ protocol_cb = Connection,
+ transport_cb = Transport,
+ tracker = Tracker},
+ connection_env =
+ #connection_env{user_application = {_Mon, Pid}}} = State,
+ SocketOpts =
+ deliver_app_data(
+ Connection:pids(State), Transport, Socket, SocketOpts0, Data, Pid, RecvFrom, Tracker, Connection),
+ if
+ SocketOpts#socket_options.active =:= false ->
%% Passive mode, wait for active once or recv
- %% Active and empty, get more data
- {no_record, State#state{erl_dist_data = ErlDistData#{dist_buffer => <<>>}}};
- _ -> %% We have more data
- read_application_data(<<>>, State)
- catch error:_ ->
- {stop, State, disconnect}
+ {no_record,
+ State#state{
+ user_data_buffer = {Front,BufferSize,Rear},
+ start_or_recv_from = undefined,
+ bytes_to_read = undefined,
+ socket_options = SocketOpts
+ }};
+ true -> %% Try to deliver more data
+ read_application_data(State, Front, BufferSize, Rear, SocketOpts, undefined, undefined)
+ end.
+
+
+read_application_dist_data(DHandle, [Bin|Front], BufferSize, Rear) ->
+ read_application_dist_data(DHandle, Front, BufferSize, Rear, Bin);
+read_application_dist_data(_DHandle, [] = Front, BufferSize, [] = Rear) ->
+ BufferSize = 0,
+ {Front,BufferSize,Rear};
+read_application_dist_data(DHandle, [], BufferSize, Rear) ->
+ [Bin|Front] = lists:reverse(Rear),
+ read_application_dist_data(DHandle, Front, BufferSize, [], Bin).
+%%
+read_application_dist_data(DHandle, Front0, BufferSize, Rear0, Bin0) ->
+ case Bin0 of
+ %%
+ %% START Optimization
+ %% It is cheaper to match out several packets in one match operation than to loop for each
+ <<SizeA:32, DataA:SizeA/binary,
+ SizeB:32, DataB:SizeB/binary,
+ SizeC:32, DataC:SizeC/binary,
+ SizeD:32, DataD:SizeD/binary, Rest/binary>> ->
+ %% We have 4 complete packets in the first binary
+ erlang:dist_ctrl_put_data(DHandle, DataA),
+ erlang:dist_ctrl_put_data(DHandle, DataB),
+ erlang:dist_ctrl_put_data(DHandle, DataC),
+ erlang:dist_ctrl_put_data(DHandle, DataD),
+ read_application_dist_data(
+ DHandle, Front0, BufferSize - (4*4+SizeA+SizeB+SizeC+SizeD), Rear0, Rest);
+ <<SizeA:32, DataA:SizeA/binary,
+ SizeB:32, DataB:SizeB/binary,
+ SizeC:32, DataC:SizeC/binary, Rest/binary>> ->
+ %% We have 3 complete packets in the first binary
+ erlang:dist_ctrl_put_data(DHandle, DataA),
+ erlang:dist_ctrl_put_data(DHandle, DataB),
+ erlang:dist_ctrl_put_data(DHandle, DataC),
+ read_application_dist_data(
+ DHandle, Front0, BufferSize - (3*4+SizeA+SizeB+SizeC), Rear0, Rest);
+ <<SizeA:32, DataA:SizeA/binary,
+ SizeB:32, DataB:SizeB/binary, Rest/binary>> ->
+ %% We have 2 complete packets in the first binary
+ erlang:dist_ctrl_put_data(DHandle, DataA),
+ erlang:dist_ctrl_put_data(DHandle, DataB),
+ read_application_dist_data(
+ DHandle, Front0, BufferSize - (2*4+SizeA+SizeB), Rear0, Rest);
+ %% END Optimization
+ %%
+ %% Basic one packet code path
+ <<Size:32, Data:Size/binary, Rest/binary>> ->
+ %% We have a complete packet in the first binary
+ erlang:dist_ctrl_put_data(DHandle, Data),
+ read_application_dist_data(DHandle, Front0, BufferSize - (4+Size), Rear0, Rest);
+ <<Size:32, FirstData/binary>> when 4+Size =< BufferSize ->
+ %% We have a complete packet in the buffer
+ %% - fetch the missing content from the buffer front
+ {Data,Front,Rear} = iovec_from_front(Size - byte_size(FirstData), Front0, Rear0, [FirstData]),
+ erlang:dist_ctrl_put_data(DHandle, Data),
+ read_application_dist_data(DHandle, Front, BufferSize - (4+Size), Rear);
+ <<Bin/binary>> ->
+ %% In OTP-21 the match context reuse optimization fails if we use Bin0 in recursion, so here we
+ %% match out the whole binary which will trick the optimization into keeping the match context
+ %% for the first binary contains complete packet code above
+ case Bin of
+ <<_Size:32, _InsufficientData/binary>> ->
+ %% We have a length field in the first binary but there is not enough data
+ %% in the buffer to form a complete packet - await more data
+ {[Bin|Front0],BufferSize,Rear0};
+ <<IncompleteLengthField/binary>> when 4 < BufferSize ->
+ %% We do not have a length field in the first binary but the buffer
+ %% contains enough data to maybe form a packet
+ %% - fetch a tiny binary from the buffer front to complete the length field
+ {LengthField,Front,Rear} =
+ iovec_from_front(4 - byte_size(IncompleteLengthField), Front0, Rear0, [IncompleteLengthField]),
+ LengthBin = iolist_to_binary(LengthField),
+ read_application_dist_data(DHandle, Front, BufferSize, Rear, LengthBin);
+ <<IncompleteLengthField/binary>> ->
+ %% We do not have enough data in the buffer to even form a length field - await more data
+ {[IncompleteLengthField|Front0],BufferSize,Rear0}
+ end
end.
-merge_dist_data(<<>>, ClientData) ->
- ClientData;
-merge_dist_data(DistBuff, <<>>) ->
- DistBuff;
-merge_dist_data(DistBuff, ClientData) ->
- [DistBuff, ClientData].
+iovec_from_front(Size, [], Rear, Acc) ->
+ iovec_from_front(Size, lists:reverse(Rear), [], Acc);
+iovec_from_front(Size, [Bin|Front], Rear, Acc) ->
+ case Bin of
+ <<Last:Size/binary>> -> % Just enough
+ {lists:reverse(Acc, [Last]),Front,Rear};
+ <<Last:Size/binary, Rest/binary>> -> % More than enough, split here
+ {lists:reverse(Acc, [Last]),[Rest|Front],Rear};
+ <<_/binary>> -> % Not enough
+ BinSize = byte_size(Bin),
+ iovec_from_front(Size - BinSize, Front, Rear, [Bin|Acc])
+ end.
+
+
%%====================================================================
%% Help functions for tls|dtls_connection.erl
%%====================================================================
@@ -554,8 +699,8 @@ handle_session(#server_hello{cipher_suite = CipherSuite,
compression_method = Compression},
Version, NewId, ConnectionStates, ProtoExt, Protocol0,
#state{session = #session{session_id = OldId},
- negotiated_version = ReqVersion,
- negotiated_protocol = CurrentProtocol} = State0) ->
+ handshake_env = #handshake_env{negotiated_protocol = CurrentProtocol} = HsEnv,
+ connection_env = #connection_env{negotiated_version = ReqVersion} = CEnv} = State0) ->
#{key_exchange := KeyAlgorithm} =
ssl_cipher_format:suite_definition(CipherSuite),
@@ -568,12 +713,12 @@ handle_session(#server_hello{cipher_suite = CipherSuite,
{ProtoExt =:= npn, Protocol0}
end,
- State = State0#state{key_algorithm = KeyAlgorithm,
- negotiated_version = Version,
- connection_states = ConnectionStates,
- premaster_secret = PremasterSecret,
- expecting_next_protocol_negotiation = ExpectNPN,
- negotiated_protocol = Protocol},
+ State = State0#state{connection_states = ConnectionStates,
+ handshake_env = HsEnv#handshake_env{kex_algorithm = KeyAlgorithm,
+ premaster_secret = PremasterSecret,
+ expecting_next_protocol_negotiation = ExpectNPN,
+ negotiated_protocol = Protocol},
+ connection_env = CEnv#connection_env{negotiated_version = Version}},
case ssl_session:is_new(OldId, NewId) of
true ->
@@ -587,10 +732,9 @@ handle_session(#server_hello{cipher_suite = CipherSuite,
%%--------------------------------------------------------------------
-spec ssl_config(#ssl_options{}, client | server, #state{}) -> #state{}.
%%--------------------------------------------------------------------
-ssl_config(Opts, Role, State) ->
- ssl_config(Opts, Role, State, new).
-
-ssl_config(Opts, Role, #state{static_env = InitStatEnv0} =State0, Type) ->
+ssl_config(Opts, Role, #state{static_env = InitStatEnv0,
+ handshake_env = HsEnv,
+ connection_env = CEnv} = State0) ->
{ok, #{cert_db_ref := Ref,
cert_db_handle := CertDbHandle,
fileref_db_handle := FileRefHandle,
@@ -602,27 +746,19 @@ ssl_config(Opts, Role, #state{static_env = InitStatEnv0} =State0, Type) ->
ssl_config:init(Opts, Role),
TimeStamp = erlang:monotonic_time(),
Session = State0#state.session,
-
- State = State0#state{session = Session#session{own_certificate = OwnCert,
- time_stamp = TimeStamp},
- static_env = InitStatEnv0#static_env{
- file_ref_db = FileRefHandle,
- cert_db_ref = Ref,
- cert_db = CertDbHandle,
- crl_db = CRLDbHandle,
- session_cache = CacheHandle
- },
- private_key = Key,
- diffie_hellman_params = DHParams,
- ssl_options = Opts},
- case Type of
- new ->
- Handshake = ssl_handshake:init_handshake_history(),
- State#state{tls_handshake_history = Handshake};
- continue ->
- State
- end.
-
+
+ State0#state{session = Session#session{own_certificate = OwnCert,
+ time_stamp = TimeStamp},
+ static_env = InitStatEnv0#static_env{
+ file_ref_db = FileRefHandle,
+ cert_db_ref = Ref,
+ cert_db = CertDbHandle,
+ crl_db = CRLDbHandle,
+ session_cache = CacheHandle
+ },
+ handshake_env = HsEnv#handshake_env{diffie_hellman_params = DHParams},
+ connection_env = CEnv#connection_env{private_key = Key},
+ ssl_options = Opts}.
%%====================================================================
%% gen_statem general state functions with connection cb argument
@@ -635,8 +771,8 @@ ssl_config(Opts, Role, #state{static_env = InitStatEnv0} =State0, Type) ->
%%--------------------------------------------------------------------
init({call, From}, {start, Timeout}, State0, Connection) ->
- Timer = start_or_recv_cancel_timer(Timeout, From),
- Connection:next_event(hello, no_record, State0#state{start_or_recv_from = From, timer = Timer});
+ Connection:next_event(hello, no_record, State0#state{start_or_recv_from = From},
+ [{{timeout, handshake}, Timeout, close}]);
init({call, From}, {start, {Opts, EmOpts}, Timeout},
#state{static_env = #static_env{role = Role},
ssl_options = OrigSSLOptions,
@@ -683,21 +819,18 @@ hello(info, Msg, State, _) ->
hello(Type, Msg, State, Connection) ->
handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
-user_hello({call, From}, cancel, #state{negotiated_version = Version} = State, _) ->
+user_hello({call, From}, cancel, #state{connection_env = #connection_env{negotiated_version = Version}} = State, _) ->
gen_statem:reply(From, ok),
handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled),
Version, ?FUNCTION_NAME, State);
user_hello({call, From}, {handshake_continue, NewOptions, Timeout},
- #state{hello = Hello,
- static_env = #static_env{role = Role},
- start_or_recv_from = RecvFrom,
+ #state{static_env = #static_env{role = Role},
+ handshake_env = #handshake_env{hello = Hello},
ssl_options = Options0} = State0, _Connection) ->
- Timer = start_or_recv_cancel_timer(Timeout, RecvFrom),
Options = ssl:handle_options(NewOptions, Options0#ssl_options{handshake = full}),
- State = ssl_config(Options, Role, State0, continue),
- {next_state, hello, State#state{start_or_recv_from = From,
- timer = Timer},
- [{next_event, internal, Hello}]};
+ State = ssl_config(Options, Role, State0),
+ {next_state, hello, State#state{start_or_recv_from = From},
+ [{next_event, internal, Hello}, {{timeout, handshake}, Timeout, close}]};
user_hello(_, _, _, _) ->
{keep_state_and_data, [postpone]}.
@@ -711,41 +844,41 @@ abbreviated({call, From}, Msg, State, Connection) ->
handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
abbreviated(internal, #finished{verify_data = Data} = Finished,
#state{static_env = #static_env{role = server},
- negotiated_version = Version,
- expecting_finished = true,
- tls_handshake_history = Handshake,
+ handshake_env = #handshake_env{tls_handshake_history = Hist,
+ expecting_finished = true} = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
session = #session{master_secret = MasterSecret},
connection_states = ConnectionStates0} =
State0, Connection) ->
case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, client,
get_current_prf(ConnectionStates0, write),
- MasterSecret, Handshake) of
+ MasterSecret, Hist) of
verified ->
ConnectionStates =
ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0),
{Record, State} = prepare_connection(State0#state{connection_states = ConnectionStates,
- expecting_finished = false}, Connection),
- Connection:next_event(connection, Record, State);
+ handshake_env = HsEnv#handshake_env{expecting_finished = false}}, Connection),
+ Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close}]);
#alert{} = Alert ->
handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
end;
abbreviated(internal, #finished{verify_data = Data} = Finished,
#state{static_env = #static_env{role = client},
- tls_handshake_history = Handshake0,
+ handshake_env = #handshake_env{tls_handshake_history = Hist0},
+ connection_env = #connection_env{negotiated_version = Version},
session = #session{master_secret = MasterSecret},
- negotiated_version = Version,
connection_states = ConnectionStates0} = State0, Connection) ->
case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, server,
get_pending_prf(ConnectionStates0, write),
- MasterSecret, Handshake0) of
+ MasterSecret, Hist0) of
verified ->
ConnectionStates1 =
ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0),
- {State1, Actions} =
+ {#state{handshake_env = HsEnv} = State1, Actions} =
finalize_handshake(State0#state{connection_states = ConnectionStates1},
?FUNCTION_NAME, Connection),
- {Record, State} = prepare_connection(State1#state{expecting_finished = false}, Connection),
- Connection:next_event(connection, Record, State, Actions);
+ {Record, State} = prepare_connection(State1#state{handshake_env = HsEnv#handshake_env{expecting_finished = false}}, Connection),
+ Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close} | Actions]);
#alert{} = Alert ->
handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
end;
@@ -753,19 +886,20 @@ abbreviated(internal, #finished{verify_data = Data} = Finished,
%% & before finished message and it is not allowed during renegotiation
abbreviated(internal, #next_protocol{selected_protocol = SelectedProtocol},
#state{static_env = #static_env{role = server},
- expecting_next_protocol_negotiation = true} = State,
+ handshake_env = #handshake_env{expecting_next_protocol_negotiation = true} = HsEnv} = State,
Connection) ->
Connection:next_event(?FUNCTION_NAME, no_record,
- State#state{negotiated_protocol = SelectedProtocol,
- expecting_next_protocol_negotiation = false});
+ State#state{handshake_env = HsEnv#handshake_env{negotiated_protocol = SelectedProtocol,
+ expecting_next_protocol_negotiation = false}});
abbreviated(internal,
#change_cipher_spec{type = <<1>>},
- #state{connection_states = ConnectionStates0} = State, Connection) ->
+ #state{connection_states = ConnectionStates0,
+ handshake_env = HsEnv} = State, Connection) ->
ConnectionStates1 =
ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection),
Connection:next_event(?FUNCTION_NAME, no_record, State#state{connection_states =
ConnectionStates1,
- expecting_finished = true});
+ handshake_env = HsEnv#handshake_env{expecting_finished = true}});
abbreviated(info, Msg, State, _) ->
handle_info(Msg, ?FUNCTION_NAME, State);
abbreviated(Type, Msg, State, Connection) ->
@@ -784,7 +918,7 @@ certify(info, Msg, State, _) ->
handle_info(Msg, ?FUNCTION_NAME, State);
certify(internal, #certificate{asn1_certificates = []},
#state{static_env = #static_env{role = server},
- negotiated_version = Version,
+ connection_env = #connection_env{negotiated_version = Version},
ssl_options = #ssl_options{verify = verify_peer,
fail_if_no_peer_cert = true}} =
State, _) ->
@@ -798,7 +932,7 @@ certify(internal, #certificate{asn1_certificates = []},
Connection:next_event(?FUNCTION_NAME, no_record, State0#state{client_certificate_requested = false});
certify(internal, #certificate{},
#state{static_env = #static_env{role = server},
- negotiated_version = Version,
+ connection_env = #connection_env{negotiated_version = Version},
ssl_options = #ssl_options{verify = verify_none}} =
State, _) ->
Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, unrequested_certificate),
@@ -810,7 +944,7 @@ certify(internal, #certificate{} = Cert,
cert_db = CertDbHandle,
cert_db_ref = CertDbRef,
crl_db = CRLDbInfo},
- negotiated_version = Version,
+ connection_env = #connection_env{negotiated_version = Version},
ssl_options = Opts} = State, Connection) ->
case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef,
Opts, CRLDbInfo, Role, Host) of
@@ -822,34 +956,42 @@ certify(internal, #certificate{} = Cert,
end;
certify(internal, #server_key_exchange{exchange_keys = Keys},
#state{static_env = #static_env{role = client},
- negotiated_version = Version,
- key_algorithm = Alg,
- public_key_info = PubKeyInfo,
+ handshake_env = #handshake_env{kex_algorithm = KexAlg,
+ public_key_info = PubKeyInfo} = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
session = Session,
connection_states = ConnectionStates} = State, Connection)
- when Alg == dhe_dss; Alg == dhe_rsa;
- Alg == ecdhe_rsa; Alg == ecdhe_ecdsa;
- Alg == dh_anon; Alg == ecdh_anon;
- Alg == psk; Alg == dhe_psk; Alg == ecdhe_psk; Alg == rsa_psk;
- Alg == srp_dss; Alg == srp_rsa; Alg == srp_anon ->
-
- Params = ssl_handshake:decode_server_key(Keys, Alg, ssl:tls_version(Version)),
+ when KexAlg == dhe_dss;
+ KexAlg == dhe_rsa;
+ KexAlg == ecdhe_rsa;
+ KexAlg == ecdhe_ecdsa;
+ KexAlg == dh_anon;
+ KexAlg == ecdh_anon;
+ KexAlg == psk;
+ KexAlg == dhe_psk;
+ KexAlg == ecdhe_psk;
+ KexAlg == rsa_psk;
+ KexAlg == srp_dss;
+ KexAlg == srp_rsa;
+ KexAlg == srp_anon ->
+
+ Params = ssl_handshake:decode_server_key(Keys, KexAlg, ssl:tls_version(Version)),
%% Use negotiated value if TLS-1.2 otherwhise return default
- HashSign = negotiated_hashsign(Params#server_key_params.hashsign, Alg, PubKeyInfo, ssl:tls_version(Version)),
+ HashSign = negotiated_hashsign(Params#server_key_params.hashsign, KexAlg, PubKeyInfo, ssl:tls_version(Version)),
- case is_anonymous(Alg) of
+ case is_anonymous(KexAlg) of
true ->
calculate_secret(Params#server_key_params.params,
- State#state{hashsign_algorithm = HashSign}, Connection);
+ State#state{handshake_env = HsEnv#handshake_env{hashsign_algorithm = HashSign}}, Connection);
false ->
case ssl_handshake:verify_server_key(Params, HashSign,
ConnectionStates, ssl:tls_version(Version), PubKeyInfo) of
true ->
calculate_secret(Params#server_key_params.params,
- State#state{hashsign_algorithm = HashSign,
- session = session_handle_params(Params#server_key_params.params, Session)},
- Connection);
+ State#state{handshake_env = HsEnv#handshake_env{hashsign_algorithm = HashSign},
+ session = session_handle_params(Params#server_key_params.params, Session)},
+ Connection);
false ->
handle_own_alert(?ALERT_REC(?FATAL, ?DECRYPT_ERROR),
Version, ?FUNCTION_NAME, State)
@@ -857,11 +999,17 @@ certify(internal, #server_key_exchange{exchange_keys = Keys},
end;
certify(internal, #certificate_request{},
#state{static_env = #static_env{role = client},
- negotiated_version = Version,
- key_algorithm = Alg} = State, _)
- when Alg == dh_anon; Alg == ecdh_anon;
- Alg == psk; Alg == dhe_psk; Alg == ecdhe_psk; Alg == rsa_psk;
- Alg == srp_dss; Alg == srp_rsa; Alg == srp_anon ->
+ handshake_env = #handshake_env{kex_algorithm = KexAlg},
+ connection_env = #connection_env{negotiated_version = Version}} = State, _)
+ when KexAlg == dh_anon;
+ KexAlg == ecdh_anon;
+ KexAlg == psk;
+ KexAlg == dhe_psk;
+ KexAlg == ecdhe_psk;
+ KexAlg == rsa_psk;
+ KexAlg == srp_dss;
+ KexAlg == srp_rsa;
+ KexAlg == srp_anon ->
handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE),
Version, ?FUNCTION_NAME, State);
certify(internal, #certificate_request{},
@@ -873,62 +1021,66 @@ certify(internal, #certificate_request{},
Connection:next_event(?FUNCTION_NAME, no_record, State#state{client_certificate_requested = true});
certify(internal, #certificate_request{} = CertRequest,
#state{static_env = #static_env{role = client},
+ handshake_env = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
session = #session{own_certificate = Cert},
- ssl_options = #ssl_options{signature_algs = SupportedHashSigns},
- negotiated_version = Version} = State, Connection) ->
- case ssl_handshake:select_hashsign(CertRequest, Cert, SupportedHashSigns, ssl:tls_version(Version)) of
+ ssl_options = #ssl_options{signature_algs = SupportedHashSigns}} = State, Connection) ->
+ case ssl_handshake:select_hashsign(CertRequest, Cert,
+ SupportedHashSigns, ssl:tls_version(Version)) of
#alert {} = Alert ->
handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
NegotiatedHashSign ->
Connection:next_event(?FUNCTION_NAME, no_record,
State#state{client_certificate_requested = true,
- cert_hashsign_algorithm = NegotiatedHashSign})
+ handshake_env = HsEnv#handshake_env{cert_hashsign_algorithm = NegotiatedHashSign}})
end;
%% PSK and RSA_PSK might bypass the Server-Key-Exchange
certify(internal, #server_hello_done{},
#state{static_env = #static_env{role = client},
session = #session{master_secret = undefined},
- negotiated_version = Version,
- psk_identity = PSKIdentity,
- ssl_options = #ssl_options{user_lookup_fun = PSKLookup},
- premaster_secret = undefined,
- key_algorithm = Alg} = State0, Connection)
- when Alg == psk ->
- case ssl_handshake:premaster_secret({Alg, PSKIdentity}, PSKLookup) of
+ connection_env = #connection_env{negotiated_version = Version},
+ handshake_env = #handshake_env{kex_algorithm = KexAlg,
+ premaster_secret = undefined,
+ server_psk_identity = PSKIdentity} = HsEnv,
+ ssl_options = #ssl_options{user_lookup_fun = PSKLookup}} = State0, Connection)
+ when KexAlg == psk ->
+ case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup) of
#alert{} = Alert ->
handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0);
PremasterSecret ->
State = master_secret(PremasterSecret,
- State0#state{premaster_secret = PremasterSecret}),
- client_certify_and_key_exchange(State, Connection)
+ State0#state{handshake_env =
+ HsEnv#handshake_env{premaster_secret = PremasterSecret}}),
+ client_certify_and_key_exchange(State, Connection)
end;
certify(internal, #server_hello_done{},
#state{static_env = #static_env{role = client},
+ connection_env = #connection_env{negotiated_version = {Major, Minor}} = Version,
+ handshake_env = #handshake_env{kex_algorithm = KexAlg,
+ premaster_secret = undefined,
+ server_psk_identity = PSKIdentity} = HsEnv,
session = #session{master_secret = undefined},
- ssl_options = #ssl_options{user_lookup_fun = PSKLookup},
- negotiated_version = {Major, Minor} = Version,
- psk_identity = PSKIdentity,
- premaster_secret = undefined,
- key_algorithm = Alg} = State0, Connection)
- when Alg == rsa_psk ->
+ ssl_options = #ssl_options{user_lookup_fun = PSKLookup}} = State0, Connection)
+ when KexAlg == rsa_psk ->
Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2),
RSAPremasterSecret = <<?BYTE(Major), ?BYTE(Minor), Rand/binary>>,
- case ssl_handshake:premaster_secret({Alg, PSKIdentity}, PSKLookup,
+ case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup,
RSAPremasterSecret) of
#alert{} = Alert ->
handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0);
PremasterSecret ->
State = master_secret(PremasterSecret,
- State0#state{premaster_secret = RSAPremasterSecret}),
+ State0#state{handshake_env =
+ HsEnv#handshake_env{premaster_secret = RSAPremasterSecret}}),
client_certify_and_key_exchange(State, Connection)
end;
%% Master secret was determined with help of server-key exchange msg
certify(internal, #server_hello_done{},
#state{static_env = #static_env{role = client},
- session = #session{master_secret = MasterSecret} = Session,
- connection_states = ConnectionStates0,
- negotiated_version = Version,
- premaster_secret = undefined} = State0, Connection) ->
+ connection_env = #connection_env{negotiated_version = Version},
+ handshake_env = #handshake_env{premaster_secret = undefined},
+ session = #session{master_secret = MasterSecret} = Session,
+ connection_states = ConnectionStates0} = State0, Connection) ->
case ssl_handshake:master_secret(ssl:tls_version(Version), Session,
ConnectionStates0, client) of
{MasterSecret, ConnectionStates} ->
@@ -940,10 +1092,10 @@ certify(internal, #server_hello_done{},
%% Master secret is calculated from premaster_secret
certify(internal, #server_hello_done{},
#state{static_env = #static_env{role = client},
+ connection_env = #connection_env{negotiated_version = Version},
+ handshake_env = #handshake_env{premaster_secret = PremasterSecret},
session = Session0,
- connection_states = ConnectionStates0,
- negotiated_version = Version,
- premaster_secret = PremasterSecret} = State0, Connection) ->
+ connection_states = ConnectionStates0} = State0, Connection) ->
case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret,
ConnectionStates0, client) of
{MasterSecret, ConnectionStates} ->
@@ -962,7 +1114,8 @@ certify(internal = Type, #client_key_exchange{} = Msg,
%% We expect a certificate here
handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection);
certify(internal, #client_key_exchange{exchange_keys = Keys},
- State = #state{key_algorithm = KeyAlg, negotiated_version = Version}, Connection) ->
+ State = #state{handshake_env = #handshake_env{kex_algorithm = KeyAlg},
+ connection_env = #connection_env{negotiated_version = Version}}, Connection) ->
try
certify_client_key_exchange(ssl_handshake:decode_client_key(Keys, KeyAlg, ssl:tls_version(Version)),
State, Connection)
@@ -986,50 +1139,51 @@ cipher(info, Msg, State, _) ->
cipher(internal, #certificate_verify{signature = Signature,
hashsign_algorithm = CertHashSign},
#state{static_env = #static_env{role = server},
- key_algorithm = KexAlg,
- public_key_info = PublicKeyInfo,
- negotiated_version = Version,
- session = #session{master_secret = MasterSecret},
- tls_handshake_history = Handshake
+ handshake_env = #handshake_env{tls_handshake_history = Hist,
+ kex_algorithm = KexAlg,
+ public_key_info = PubKeyInfo} = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
+ session = #session{master_secret = MasterSecret}
} = State, Connection) ->
TLSVersion = ssl:tls_version(Version),
%% Use negotiated value if TLS-1.2 otherwhise return default
- HashSign = negotiated_hashsign(CertHashSign, KexAlg, PublicKeyInfo, TLSVersion),
- case ssl_handshake:certificate_verify(Signature, PublicKeyInfo,
- TLSVersion, HashSign, MasterSecret, Handshake) of
+ HashSign = negotiated_hashsign(CertHashSign, KexAlg, PubKeyInfo, TLSVersion),
+ case ssl_handshake:certificate_verify(Signature, PubKeyInfo,
+ TLSVersion, HashSign, MasterSecret, Hist) of
valid ->
Connection:next_event(?FUNCTION_NAME, no_record,
- State#state{cert_hashsign_algorithm = HashSign});
+ State#state{handshake_env = HsEnv#handshake_env{cert_hashsign_algorithm = HashSign}});
#alert{} = Alert ->
handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
end;
%% client must send a next protocol message if we are expecting it
cipher(internal, #finished{},
#state{static_env = #static_env{role = server},
- expecting_next_protocol_negotiation = true,
- negotiated_protocol = undefined, negotiated_version = Version} = State0,
+ handshake_env = #handshake_env{expecting_next_protocol_negotiation = true,
+ negotiated_protocol = undefined},
+ connection_env = #connection_env{negotiated_version = Version}} = State0,
_Connection) ->
handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, ?FUNCTION_NAME, State0);
cipher(internal, #finished{verify_data = Data} = Finished,
#state{static_env = #static_env{role = Role,
host = Host,
port = Port},
- negotiated_version = Version,
- expecting_finished = true,
+ handshake_env = #handshake_env{tls_handshake_history = Hist,
+ expecting_finished = true} = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
session = #session{master_secret = MasterSecret}
= Session0,
ssl_options = SslOpts,
- connection_states = ConnectionStates0,
- tls_handshake_history = Handshake0} = State, Connection) ->
+ connection_states = ConnectionStates0} = State, Connection) ->
case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished,
opposite_role(Role),
get_current_prf(ConnectionStates0, read),
- MasterSecret, Handshake0) of
+ MasterSecret, Hist) of
verified ->
- Session = register_session(Role, host_id(Role, Host, SslOpts), Port, Session0),
+ Session = handle_session(Role, SslOpts, Host, Port, Session0),
cipher_role(Role, Data, Session,
- State#state{expecting_finished = false}, Connection);
+ State#state{handshake_env = HsEnv#handshake_env{expecting_finished = false}}, Connection);
#alert{} = Alert ->
handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
end;
@@ -1037,19 +1191,17 @@ cipher(internal, #finished{verify_data = Data} = Finished,
%% & before finished message and it is not allowed during renegotiation
cipher(internal, #next_protocol{selected_protocol = SelectedProtocol},
#state{static_env = #static_env{role = server},
- expecting_next_protocol_negotiation = true,
- expecting_finished = true} = State0, Connection) ->
- {Record, State} =
- Connection:next_record(State0#state{negotiated_protocol = SelectedProtocol}),
- Connection:next_event(?FUNCTION_NAME, Record,
- State#state{expecting_next_protocol_negotiation = false});
-cipher(internal, #change_cipher_spec{type = <<1>>}, #state{connection_states = ConnectionStates0} =
+ handshake_env = #handshake_env{expecting_finished = true,
+ expecting_next_protocol_negotiation = true} = HsEnv} = State, Connection) ->
+ Connection:next_event(?FUNCTION_NAME, no_record,
+ State#state{handshake_env = HsEnv#handshake_env{negotiated_protocol = SelectedProtocol,
+ expecting_next_protocol_negotiation = false}});
+cipher(internal, #change_cipher_spec{type = <<1>>}, #state{handshake_env = HsEnv, connection_states = ConnectionStates0} =
State, Connection) ->
ConnectionStates =
ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection),
- Connection:next_event(?FUNCTION_NAME, no_record, State#state{connection_states =
- ConnectionStates,
- expecting_finished = true});
+ Connection:next_event(?FUNCTION_NAME, no_record, State#state{handshake_env = HsEnv#handshake_env{expecting_finished = true},
+ connection_states = ConnectionStates});
cipher(Type, Msg, State, Connection) ->
handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
@@ -1062,14 +1214,14 @@ connection({call, RecvFrom}, {recv, N, Timeout},
#state{static_env = #static_env{protocol_cb = Connection},
socket_options =
#socket_options{active = false}} = State0, Connection) ->
- Timer = start_or_recv_cancel_timer(Timeout, RecvFrom),
passive_receive(State0#state{bytes_to_read = N,
- start_or_recv_from = RecvFrom,
- timer = Timer}, ?FUNCTION_NAME, Connection);
+ start_or_recv_from = RecvFrom}, ?FUNCTION_NAME, Connection,
+ [{{timeout, recv}, Timeout, timeout}]);
-connection({call, From}, renegotiate, #state{static_env = #static_env{protocol_cb = Connection}} = State,
+connection({call, From}, renegotiate, #state{static_env = #static_env{protocol_cb = Connection},
+ handshake_env = HsEnv} = State,
Connection) ->
- Connection:renegotiate(State#state{renegotiation = {true, From}}, []);
+ Connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, From}}}, []);
connection({call, From}, peer_certificate,
#state{session = #session{peer_certificate = Cert}} = State, _) ->
hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Cert}}]);
@@ -1080,35 +1232,36 @@ connection({call, From}, {connection_information, false}, State, _) ->
Info = connection_info(State),
hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]);
connection({call, From}, negotiated_protocol,
- #state{negotiated_protocol = undefined} = State, _) ->
+ #state{handshake_env = #handshake_env{negotiated_protocol = undefined}} = State, _) ->
hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]);
connection({call, From}, negotiated_protocol,
- #state{negotiated_protocol = SelectedProtocol} = State, _) ->
+ #state{handshake_env = #handshake_env{negotiated_protocol = SelectedProtocol}} = State, _) ->
hibernate_after(?FUNCTION_NAME, State,
[{reply, From, {ok, SelectedProtocol}}]);
connection({call, From}, Msg, State, Connection) ->
handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
connection(cast, {internal_renegotiate, WriteState}, #state{static_env = #static_env{protocol_cb = Connection},
+ handshake_env = HsEnv,
connection_states = ConnectionStates}
= State, Connection) ->
- Connection:renegotiate(State#state{renegotiation = {true, internal},
+ Connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}},
connection_states = ConnectionStates#{current_write => WriteState}}, []);
connection(cast, {dist_handshake_complete, DHandle},
#state{ssl_options = #ssl_options{erl_dist = true},
- erl_dist_data = ErlDistData,
+ connection_env = CEnv,
socket_options = SockOpts} = State0, Connection) ->
process_flag(priority, normal),
State1 =
State0#state{
- socket_options =
- SockOpts#socket_options{active = true},
- erl_dist_data = ErlDistData#{dist_handle => DHandle}},
- {Record, State} = dist_app_data(<<>>, State1),
+ socket_options = SockOpts#socket_options{active = true},
+ connection_env = CEnv#connection_env{erl_dist_handle = DHandle},
+ bytes_to_read = undefined},
+ {Record, State} = read_application_data(<<>>, State1),
Connection:next_event(connection, Record, State);
connection(info, Msg, State, _) ->
handle_info(Msg, ?FUNCTION_NAME, State);
-connection(internal, {recv, _}, State, Connection) ->
- passive_receive(State, ?FUNCTION_NAME, Connection);
+connection(internal, {recv, Timeout}, State, Connection) ->
+ passive_receive(State, ?FUNCTION_NAME, Connection, [{{timeout, recv}, Timeout, timeout}]);
connection(Type, Msg, State, Connection) ->
handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
@@ -1125,59 +1278,74 @@ downgrade(Type, Event, State, Connection) ->
%% common or unexpected events for the state.
%%--------------------------------------------------------------------
handle_common_event(internal, {handshake, {#hello_request{} = Handshake, _}}, connection = StateName,
- #state{static_env = #static_env{role = client}} = State, _) ->
+ #state{static_env = #static_env{role = client},
+ handshake_env = HsEnv} = State, _) ->
%% Should not be included in handshake history
- {next_state, StateName, State#state{renegotiation = {true, peer}}, [{next_event, internal, Handshake}]};
+ {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, peer}}},
+ [{next_event, internal, Handshake}]};
handle_common_event(internal, {handshake, {#hello_request{}, _}}, StateName,
#state{static_env = #static_env{role = client}}, _)
when StateName =/= connection ->
keep_state_and_data;
handle_common_event(internal, {handshake, {Handshake, Raw}}, StateName,
- #state{tls_handshake_history = Hs0} = State0,
+ #state{handshake_env = #handshake_env{tls_handshake_history = Hist0}} = State0,
Connection) ->
PossibleSNI = Connection:select_sni_extension(Handshake),
%% This function handles client SNI hello extension when Handshake is
%% a client_hello, which needs to be determined by the connection callback.
%% In other cases this is a noop
- State = handle_sni_extension(PossibleSNI, State0),
- HsHist = ssl_handshake:update_handshake_history(Hs0, iolist_to_binary(Raw)),
- {next_state, StateName, State#state{tls_handshake_history = HsHist},
+ State = #state{handshake_env = HsEnv} = handle_sni_extension(PossibleSNI, State0),
+
+ Hist = ssl_handshake:update_handshake_history(Hist0, Raw),
+ {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}},
[{next_event, internal, Handshake}]};
handle_common_event(internal, {protocol_record, TLSorDTLSRecord}, StateName, State, Connection) ->
Connection:handle_protocol_record(TLSorDTLSRecord, StateName, State);
handle_common_event(timeout, hibernate, _, _, _) ->
{keep_state_and_data, [hibernate]};
handle_common_event(internal, #change_cipher_spec{type = <<1>>}, StateName,
- #state{negotiated_version = Version} = State, _) ->
+ #state{connection_env = #connection_env{negotiated_version = Version}} = State, _) ->
handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Version,
StateName, State);
-handle_common_event(_Type, Msg, StateName, #state{negotiated_version = Version} = State,
+handle_common_event({timeout, handshake}, close, _StateName, #state{start_or_recv_from = StartFrom} = State, _) ->
+ {stop_and_reply,
+ {shutdown, user_timeout},
+ {reply, StartFrom, {error, timeout}}, State#state{start_or_recv_from = undefined}};
+handle_common_event({timeout, recv}, timeout, StateName, #state{start_or_recv_from = RecvFrom} = State, _) ->
+ {next_state, StateName, State#state{start_or_recv_from = undefined,
+ bytes_to_read = undefined}, [{reply, RecvFrom, {error, timeout}}]};
+handle_common_event(Type, Msg, StateName, #state{connection_env =
+ #connection_env{negotiated_version = Version}} = State,
_) ->
- Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, {unexpected_msg, Msg}),
+ Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, {unexpected_msg, {Type,Msg}}),
handle_own_alert(Alert, Version, StateName, State).
handle_call({application_data, _Data}, _, _, _, _) ->
%% In renegotiation priorities handshake, send data when handshake is finished
{keep_state_and_data, [postpone]};
-handle_call({close, _} = Close, From, StateName, State, _Connection) ->
+handle_call({close, _} = Close, From, StateName, #state{connection_env = CEnv} = State, _Connection) ->
%% Run terminate before returning so that the reuseaddr
%% inet-option works properly
Result = terminate(Close, StateName, State),
{stop_and_reply,
{shutdown, normal},
- {reply, From, Result}, State#state{terminated = true}};
+ {reply, From, Result}, State#state{connection_env = CEnv#connection_env{terminated = true}}};
handle_call({shutdown, read_write = How}, From, StateName,
#state{static_env = #static_env{transport_cb = Transport,
- socket = Socket}} = State, _) ->
+ socket = Socket},
+ connection_env = CEnv} = State, _) ->
try send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
StateName, State) of
_ ->
case Transport:shutdown(Socket, How) of
ok ->
- {next_state, StateName, State#state{terminated = true}, [{reply, From, ok}]};
+ {next_state, StateName, State#state{connection_env =
+ CEnv#connection_env{terminated = true}},
+ [{reply, From, ok}]};
Error ->
- {stop_and_reply, {shutdown, normal}, {reply, From, Error}, State#state{terminated = true}}
+ {stop_and_reply, {shutdown, normal}, {reply, From, Error},
+ State#state{connection_env = CEnv#connection_env{terminated = true}}}
end
catch
throw:Return ->
@@ -1199,15 +1367,13 @@ handle_call({recv, _N, _Timeout}, From, _,
handle_call({recv, N, Timeout}, RecvFrom, StateName, State, _) ->
%% Doing renegotiate wait with handling request until renegotiate is
%% finished.
- Timer = start_or_recv_cancel_timer(Timeout, RecvFrom),
- {next_state, StateName, State#state{bytes_to_read = N, start_or_recv_from = RecvFrom,
- timer = Timer},
- [{next_event, internal, {recv, RecvFrom}}]};
+ {next_state, StateName, State#state{bytes_to_read = N, start_or_recv_from = RecvFrom},
+ [{next_event, internal, {recv, RecvFrom}} , {{timeout, recv}, Timeout, timeout}]};
handle_call({new_user, User}, From, StateName,
- State =#state{user_application = {OldMon, _}}, _) ->
+ State = #state{connection_env = #connection_env{user_application = {OldMon, _}} = CEnv}, _) ->
NewMon = erlang:monitor(process, User),
erlang:demonitor(OldMon, [flush]),
- {next_state, StateName, State#state{user_application = {NewMon,User}},
+ {next_state, StateName, State#state{connection_env = CEnv#connection_env{user_application = {NewMon, User}}},
[{reply, From, ok}]};
handle_call({get_opts, OptTags}, From, _,
#state{static_env = #static_env{socket = Socket,
@@ -1217,23 +1383,31 @@ handle_call({get_opts, OptTags}, From, _,
{keep_state_and_data, [{reply, From, OptsReply}]};
handle_call({set_opts, Opts0}, From, StateName,
#state{static_env = #static_env{socket = Socket,
- transport_cb = Transport},
+ transport_cb = Transport,
+ tracker = Tracker},
+ connection_env =
+ #connection_env{user_application = {_Mon, Pid}},
socket_options = Opts1
} = State0, Connection) ->
{Reply, Opts} = set_socket_opts(Connection, Transport, Socket, Opts0, Opts1, []),
+ case {proplists:lookup(active, Opts0), Opts} of
+ {{_, N}, #socket_options{active=false}} when is_integer(N) ->
+ send_user(
+ Pid,
+ format_passive(
+ Connection:pids(State0), Transport, Socket, Tracker, Connection));
+ _ ->
+ ok
+ end,
State = State0#state{socket_options = Opts},
handle_active_option(Opts#socket_options.active, StateName, From, Reply, State);
handle_call(renegotiate, From, StateName, _, _) when StateName =/= connection ->
{keep_state_and_data, [{reply, From, {error, already_renegotiating}}]};
-handle_call(get_sslsocket, From, _StateName, State, Connection) ->
- SslSocket = Connection:socket(State),
- {keep_state_and_data, [{reply, From, SslSocket}]};
-
handle_call({prf, Secret, Label, Seed, WantedLength}, From, _,
#state{connection_states = ConnectionStates,
- negotiated_version = Version}, _) ->
+ connection_env = #connection_env{negotiated_version = Version}}, _) ->
#{security_parameters := SecParams} =
ssl_record:current_connection_state(ConnectionStates, read),
#security_parameters{master_secret = MasterSecret,
@@ -1276,19 +1450,19 @@ handle_info({ErrorTag, Socket, econnaborted}, StateName,
handle_info({ErrorTag, Socket, Reason}, StateName, #state{static_env = #static_env{socket = Socket,
error_tag = ErrorTag}} = State) ->
Report = io_lib:format("SSL: Socket error: ~p ~n", [Reason]),
- error_logger:error_report(Report),
+ ?LOG_ERROR(Report),
handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State),
{stop, {shutdown,normal}, State};
handle_info({'DOWN', MonitorRef, _, _, Reason}, _,
- #state{user_application = {MonitorRef, _Pid},
+ #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}},
ssl_options = #ssl_options{erl_dist = true}}) ->
{stop, {shutdown, Reason}};
handle_info({'DOWN', MonitorRef, _, _, _}, _,
- #state{user_application = {MonitorRef, _Pid}}) ->
+ #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}}}) ->
{stop, {shutdown, normal}};
handle_info({'EXIT', Pid, _Reason}, StateName,
- #state{user_application = {_MonitorRef, Pid}} = State) ->
+ #state{connection_env = #connection_env{user_application = {_MonitorRef, Pid}}} = State) ->
%% It seems the user application has linked to us
%% - ignore that and let the monitor handle this
{next_state, StateName, State};
@@ -1301,32 +1475,18 @@ handle_info({'EXIT', Socket, normal}, _StateName, #state{static_env = #static_en
handle_info({'EXIT', Socket, Reason}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) ->
{stop,{shutdown, Reason}, State};
-handle_info(allow_renegotiate, StateName, State) ->
- {next_state, StateName, State#state{allow_renegotiate = true}};
-
-handle_info({cancel_start_or_recv, StartFrom}, StateName,
- #state{renegotiation = {false, first}} = State) when StateName =/= connection ->
- {stop_and_reply,
- {shutdown, user_timeout},
- {reply, StartFrom, {error, timeout}},
- State#state{timer = undefined}};
-handle_info({cancel_start_or_recv, RecvFrom}, StateName,
- #state{start_or_recv_from = RecvFrom} = State) when RecvFrom =/= undefined ->
- {next_state, StateName, State#state{start_or_recv_from = undefined,
- bytes_to_read = undefined,
- timer = undefined}, [{reply, RecvFrom, {error, timeout}}]};
-handle_info({cancel_start_or_recv, _RecvFrom}, StateName, State) ->
- {next_state, StateName, State#state{timer = undefined}};
+handle_info(allow_renegotiate, StateName, #state{handshake_env = HsEnv} = State) ->
+ {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{allow_renegotiate = true}}};
handle_info(Msg, StateName, #state{static_env = #static_env{socket = Socket, error_tag = Tag}} = State) ->
Report = io_lib:format("SSL: Got unexpected info: ~p ~n", [{Msg, Tag, Socket}]),
- error_logger:info_report(Report),
+ ?LOG_NOTICE(Report),
{next_state, StateName, State}.
%%====================================================================
%% general gen_statem callbacks
%%====================================================================
-terminate(_, _, #state{terminated = true}) ->
+terminate(_, _, #state{connection_env = #connection_env{terminated = true}}) ->
%% Happens when user closes the connection using ssl:close/1
%% we want to guarantee that Transport:close has been called
%% when ssl:close/1 returns unless it is a downgrade where
@@ -1390,14 +1550,9 @@ format_status(terminate, [_, StateName, State]) ->
[{data, [{"State", {StateName, State#state{connection_states = ?SECRET_PRINTOUT,
protocol_buffers = ?SECRET_PRINTOUT,
user_data_buffer = ?SECRET_PRINTOUT,
- tls_handshake_history = ?SECRET_PRINTOUT,
+ handshake_env = ?SECRET_PRINTOUT,
+ connection_env = ?SECRET_PRINTOUT,
session = ?SECRET_PRINTOUT,
- private_key = ?SECRET_PRINTOUT,
- diffie_hellman_params = ?SECRET_PRINTOUT,
- diffie_hellman_keys = ?SECRET_PRINTOUT,
- srp_params = ?SECRET_PRINTOUT,
- srp_keys = ?SECRET_PRINTOUT,
- premaster_secret = ?SECRET_PRINTOUT,
ssl_options = NewOptions,
flight_buffer = ?SECRET_PRINTOUT}
}}]}].
@@ -1411,10 +1566,10 @@ send_alert(Alert, _, #state{static_env = #static_env{protocol_cb = Connection}}
Connection:send_alert(Alert, State).
connection_info(#state{static_env = #static_env{protocol_cb = Connection},
- sni_hostname = SNIHostname,
+ handshake_env = #handshake_env{sni_hostname = SNIHostname},
session = #session{session_id = SessionId,
cipher_suite = CipherSuite, ecc = ECCCurve},
- negotiated_version = {_,_} = Version,
+ connection_env = #connection_env{negotiated_version = {_,_} = Version},
ssl_options = Opts}) ->
RecordCB = record_cb(Connection),
CipherSuiteDef = #{key_exchange := KexAlg} = ssl_cipher_format:suite_definition(CipherSuite),
@@ -1440,18 +1595,24 @@ security_info(#state{connection_states = ConnectionStates}) ->
ssl_record:current_connection_state(ConnectionStates, read),
[{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}].
-do_server_hello(Type, #hello_extensions{next_protocol_negotiation = NextProtocols} =
+do_server_hello(Type, #{next_protocol_negotiation := NextProtocols} =
ServerHelloExt,
- #state{negotiated_version = Version,
+ #state{connection_env = #connection_env{negotiated_version = Version},
+ handshake_env = HsEnv,
session = #session{session_id = SessId},
- connection_states = ConnectionStates0}
+ connection_states = ConnectionStates0,
+ ssl_options = #ssl_options{versions = [HighestVersion|_]}}
= State0, Connection) when is_atom(Type) ->
-
+ %% TLS 1.3 - Section 4.1.3
+ %% Override server random values for TLS 1.3 downgrade protection mechanism.
+ ConnectionStates1 = update_server_random(ConnectionStates0, Version, HighestVersion),
+ State1 = State0#state{connection_states = ConnectionStates1},
ServerHello =
- ssl_handshake:server_hello(SessId, ssl:tls_version(Version), ConnectionStates0, ServerHelloExt),
+ ssl_handshake:server_hello(SessId, ssl:tls_version(Version),
+ ConnectionStates1, ServerHelloExt),
State = server_hello(ServerHello,
- State0#state{expecting_next_protocol_negotiation =
- NextProtocols =/= undefined}, Connection),
+ State1#state{handshake_env = HsEnv#handshake_env{expecting_next_protocol_negotiation =
+ NextProtocols =/= undefined}}, Connection),
case Type of
new ->
new_server_hello(ServerHello, State, Connection);
@@ -1459,11 +1620,65 @@ do_server_hello(Type, #hello_extensions{next_protocol_negotiation = NextProtocol
resumed_server_hello(State, Connection)
end.
+update_server_random(#{pending_read := #{security_parameters := ReadSecParams0} =
+ ReadState0,
+ pending_write := #{security_parameters := WriteSecParams0} =
+ WriteState0} = ConnectionStates,
+ Version, HighestVersion) ->
+ ReadRandom = override_server_random(
+ ReadSecParams0#security_parameters.server_random,
+ Version,
+ HighestVersion),
+ WriteRandom = override_server_random(
+ WriteSecParams0#security_parameters.server_random,
+ Version,
+ HighestVersion),
+ ReadSecParams = ReadSecParams0#security_parameters{server_random = ReadRandom},
+ WriteSecParams = WriteSecParams0#security_parameters{server_random = WriteRandom},
+ ReadState = ReadState0#{security_parameters => ReadSecParams},
+ WriteState = WriteState0#{security_parameters => WriteSecParams},
+
+ ConnectionStates#{pending_read => ReadState, pending_write => WriteState}.
+
+%% TLS 1.3 - Section 4.1.3
+%%
+%% If negotiating TLS 1.2, TLS 1.3 servers MUST set the last eight bytes
+%% of their Random value to the bytes:
+%%
+%% 44 4F 57 4E 47 52 44 01
+%%
+%% If negotiating TLS 1.1 or below, TLS 1.3 servers MUST and TLS 1.2
+%% servers SHOULD set the last eight bytes of their Random value to the
+%% bytes:
+%%
+%% 44 4F 57 4E 47 52 44 00
+override_server_random(<<Random0:24/binary,_:8/binary>> = Random, {M,N}, {Major,Minor})
+ when Major > 3 orelse Major =:= 3 andalso Minor >= 4 -> %% TLS 1.3 or above
+ if M =:= 3 andalso N =:= 3 -> %% Negotating TLS 1.2
+ Down = ?RANDOM_OVERRIDE_TLS12,
+ <<Random0/binary,Down/binary>>;
+ M =:= 3 andalso N < 3 -> %% Negotating TLS 1.1 or prior
+ Down = ?RANDOM_OVERRIDE_TLS11,
+ <<Random0/binary,Down/binary>>;
+ true ->
+ Random
+ end;
+override_server_random(<<Random0:24/binary,_:8/binary>> = Random, {M,N}, {Major,Minor})
+ when Major =:= 3 andalso Minor =:= 3 -> %% TLS 1.2
+ if M =:= 3 andalso N < 3 -> %% Negotating TLS 1.1 or prior
+ Down = ?RANDOM_OVERRIDE_TLS11,
+ <<Random0/binary,Down/binary>>;
+ true ->
+ Random
+ end;
+override_server_random(Random, _, _) ->
+ Random.
+
new_server_hello(#server_hello{cipher_suite = CipherSuite,
compression_method = Compression,
session_id = SessionId},
- #state{session = Session0,
- negotiated_version = Version} = State0, Connection) ->
+ #state{session = Session0,
+ connection_env = #connection_env{negotiated_version = Version}} = State0, Connection) ->
try server_certify_and_key_exchange(State0, Connection) of
#state{} = State1 ->
{State, Actions} = server_hello_done(State1, Connection),
@@ -1479,7 +1694,7 @@ new_server_hello(#server_hello{cipher_suite = CipherSuite,
resumed_server_hello(#state{session = Session,
connection_states = ConnectionStates0,
- negotiated_version = Version} = State0, Connection) ->
+ connection_env = #connection_env{negotiated_version = Version}} = State0, Connection) ->
case ssl_handshake:master_secret(ssl:tls_version(Version), Session,
ConnectionStates0, server) of
@@ -1496,19 +1711,20 @@ resumed_server_hello(#state{session = Session,
server_hello(ServerHello, State0, Connection) ->
CipherSuite = ServerHello#server_hello.cipher_suite,
#{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_definition(CipherSuite),
- State = Connection:queue_handshake(ServerHello, State0),
- State#state{key_algorithm = KeyAlgorithm}.
+ #state{handshake_env = HsEnv} = State = Connection:queue_handshake(ServerHello, State0),
+ State#state{handshake_env = HsEnv#handshake_env{kex_algorithm = KeyAlgorithm}}.
server_hello_done(State, Connection) ->
HelloDone = ssl_handshake:server_hello_done(),
Connection:send_handshake(HelloDone, State).
handle_peer_cert(Role, PeerCert, PublicKeyInfo,
- #state{session = #session{cipher_suite = CipherSuite} = Session} = State0,
+ #state{handshake_env = HsEnv,
+ session = #session{cipher_suite = CipherSuite} = Session} = State0,
Connection) ->
- State1 = State0#state{session =
- Session#session{peer_certificate = PeerCert},
- public_key_info = PublicKeyInfo},
+ State1 = State0#state{handshake_env = HsEnv#handshake_env{public_key_info = PublicKeyInfo},
+ session =
+ Session#session{peer_certificate = PeerCert}},
#{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_definition(CipherSuite),
State = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlgorithm, State1),
Connection:next_event(certify, no_record, State).
@@ -1516,21 +1732,13 @@ handle_peer_cert(Role, PeerCert, PublicKeyInfo,
handle_peer_cert_key(client, _,
{?'id-ecPublicKey', #'ECPoint'{point = _ECPoint} = PublicKey,
PublicKeyParams},
- KeyAlg, #state{session = Session} = State) when KeyAlg == ecdh_rsa;
+ KeyAlg, #state{handshake_env = HsEnv,
+ session = Session} = State) when KeyAlg == ecdh_rsa;
KeyAlg == ecdh_ecdsa ->
ECDHKey = public_key:generate_key(PublicKeyParams),
PremasterSecret = ssl_handshake:premaster_secret(PublicKey, ECDHKey),
- master_secret(PremasterSecret, State#state{diffie_hellman_keys = ECDHKey,
+ master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKey},
session = Session#session{ecc = PublicKeyParams}});
-%% We do currently not support cipher suites that use fixed DH.
-%% If we want to implement that the following clause can be used
-%% to extract DH parameters form cert.
-%% handle_peer_cert_key(client, _PeerCert, {?dhpublicnumber, PublicKey, PublicKeyParams},
-%% {_,SignAlg},
-%% #state{diffie_hellman_keys = {_, MyPrivatKey}} = State) when
-%% SignAlg == dh_rsa;
-%% SignAlg == dh_dss ->
-%% dh_master_secret(PublicKeyParams, PublicKey, MyPrivatKey, State);
handle_peer_cert_key(_, _, _, _, State) ->
State.
@@ -1546,16 +1754,16 @@ certify_client(#state{client_certificate_requested = false} = State, _) ->
State.
verify_client_cert(#state{static_env = #static_env{role = client},
+ handshake_env = #handshake_env{tls_handshake_history = Hist,
+ cert_hashsign_algorithm = HashSign},
+ connection_env = #connection_env{negotiated_version = Version,
+ private_key = PrivateKey},
client_certificate_requested = true,
- negotiated_version = Version,
- private_key = PrivateKey,
session = #session{master_secret = MasterSecret,
- own_certificate = OwnCert},
- cert_hashsign_algorithm = HashSign,
- tls_handshake_history = Handshake0} = State, Connection) ->
+ own_certificate = OwnCert}} = State, Connection) ->
case ssl_handshake:client_certificate_verify(OwnCert, MasterSecret,
- ssl:tls_version(Version), HashSign, PrivateKey, Handshake0) of
+ ssl:tls_version(Version), HashSign, PrivateKey, Hist) of
#certificate_verify{} = Verified ->
Connection:queue_handshake(Verified, State);
ignore ->
@@ -1566,7 +1774,7 @@ verify_client_cert(#state{static_env = #static_env{role = client},
verify_client_cert(#state{client_certificate_requested = false} = State, _) ->
State.
-client_certify_and_key_exchange(#state{negotiated_version = Version} =
+client_certify_and_key_exchange(#state{connection_env = #connection_env{negotiated_version = Version}} =
State0, Connection) ->
try do_client_certify_and_key_exchange(State0, Connection) of
State1 = #state{} ->
@@ -1591,7 +1799,9 @@ server_certify_and_key_exchange(State0, Connection) ->
request_client_cert(State2, Connection).
certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS},
- #state{private_key = Key, client_hello_version = {Major, Minor} = Version} = State, Connection) ->
+ #state{connection_env = #connection_env{private_key = Key},
+ handshake_env = #handshake_env{client_hello_version = {Major, Minor} = Version}}
+ = State, Connection) ->
FakeSecret = make_premaster_secret(Version, rsa),
%% Countermeasure for Bleichenbacher attack always provide some kind of premaster secret
%% and fail handshake later.RFC 5246 section 7.4.7.1.
@@ -1612,14 +1822,15 @@ certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS
end,
calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPublicDhKey},
- #state{diffie_hellman_params = #'DHParameter'{} = Params,
- diffie_hellman_keys = {_, ServerDhPrivateKey}} = State,
+ #state{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params,
+ kex_keys = {_, ServerDhPrivateKey}}
+ } = State,
Connection) ->
PremasterSecret = ssl_handshake:premaster_secret(ClientPublicDhKey, ServerDhPrivateKey, Params),
calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
certify_client_key_exchange(#client_ec_diffie_hellman_public{dh_public = ClientPublicEcDhPoint},
- #state{diffie_hellman_keys = ECDHKey} = State, Connection) ->
+ #state{handshake_env = #handshake_env{kex_keys = ECDHKey}} = State, Connection) ->
PremasterSecret = ssl_handshake:premaster_secret(#'ECPoint'{point = ClientPublicEcDhPoint}, ECDHKey),
calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
certify_client_key_exchange(#client_psk_identity{} = ClientKey,
@@ -1629,8 +1840,8 @@ certify_client_key_exchange(#client_psk_identity{} = ClientKey,
PremasterSecret = ssl_handshake:premaster_secret(ClientKey, PSKLookup),
calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher);
certify_client_key_exchange(#client_dhe_psk_identity{} = ClientKey,
- #state{diffie_hellman_params = #'DHParameter'{} = Params,
- diffie_hellman_keys = {_, ServerDhPrivateKey},
+ #state{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params,
+ kex_keys = {_, ServerDhPrivateKey}},
ssl_options =
#ssl_options{user_lookup_fun = PSKLookup}} = State0,
Connection) ->
@@ -1638,7 +1849,7 @@ certify_client_key_exchange(#client_dhe_psk_identity{} = ClientKey,
ssl_handshake:premaster_secret(ClientKey, ServerDhPrivateKey, Params, PSKLookup),
calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher);
certify_client_key_exchange(#client_ecdhe_psk_identity{} = ClientKey,
- #state{diffie_hellman_keys = ServerEcDhPrivateKey,
+ #state{handshake_env = #handshake_env{kex_keys = ServerEcDhPrivateKey},
ssl_options =
#ssl_options{user_lookup_fun = PSKLookup}} = State,
Connection) ->
@@ -1646,25 +1857,26 @@ certify_client_key_exchange(#client_ecdhe_psk_identity{} = ClientKey,
ssl_handshake:premaster_secret(ClientKey, ServerEcDhPrivateKey, PSKLookup),
calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
certify_client_key_exchange(#client_rsa_psk_identity{} = ClientKey,
- #state{private_key = Key,
+ #state{connection_env = #connection_env{private_key = Key},
ssl_options =
#ssl_options{user_lookup_fun = PSKLookup}} = State0,
Connection) ->
PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, PSKLookup),
calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher);
certify_client_key_exchange(#client_srp_public{} = ClientKey,
- #state{srp_params = Params,
- srp_keys = Key
+ #state{handshake_env = #handshake_env{srp_params = Params,
+ kex_keys = Key}
} = State0, Connection) ->
PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, Params),
calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher).
-certify_server(#state{key_algorithm = Algo} = State, _) when Algo == dh_anon;
- Algo == ecdh_anon;
- Algo == psk;
- Algo == dhe_psk;
- Algo == ecdhe_psk;
- Algo == srp_anon ->
+certify_server(#state{handshake_env = #handshake_env{kex_algorithm = KexAlg}} =
+ State, _) when KexAlg == dh_anon;
+ KexAlg == ecdh_anon;
+ KexAlg == psk;
+ KexAlg == dhe_psk;
+ KexAlg == ecdhe_psk;
+ KexAlg == srp_anon ->
State;
certify_server(#state{static_env = #static_env{cert_db = CertDbHandle,
cert_db_ref = CertDbRef},
@@ -1676,18 +1888,19 @@ certify_server(#state{static_env = #static_env{cert_db = CertDbHandle,
throw(Alert)
end.
-key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = rsa} = State,_) ->
+key_exchange(#state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{kex_algorithm = rsa}} = State,_) ->
State;
-key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = Algo,
- hashsign_algorithm = HashSignAlgo,
- diffie_hellman_params = #'DHParameter'{} = Params,
- private_key = PrivateKey,
- connection_states = ConnectionStates0,
- negotiated_version = Version
- } = State0, Connection)
- when Algo == dhe_dss;
- Algo == dhe_rsa;
- Algo == dh_anon ->
+key_exchange(#state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{kex_algorithm = KexAlg,
+ diffie_hellman_params = #'DHParameter'{} = Params,
+ hashsign_algorithm = HashSignAlgo},
+ connection_env = #connection_env{negotiated_version = Version,
+ private_key = PrivateKey},
+ connection_states = ConnectionStates0} = State0, Connection)
+ when KexAlg == dhe_dss;
+ KexAlg == dhe_rsa;
+ KexAlg == dh_anon ->
DHKeys = public_key:generate_key(Params),
#{security_parameters := SecParams} =
ssl_record:pending_connection_state(ConnectionStates0, read),
@@ -1697,24 +1910,26 @@ key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = Alg
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
- State = Connection:queue_handshake(Msg, State0),
- State#state{diffie_hellman_keys = DHKeys};
+ #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
+ State#state{handshake_env = HsEnv#handshake_env{kex_keys = DHKeys}};
key_exchange(#state{static_env = #static_env{role = server},
- private_key = #'ECPrivateKey'{parameters = ECCurve} = Key,
- key_algorithm = Algo,
+ handshake_env = #handshake_env{kex_algorithm = KexAlg} = HsEnv,
+ connection_env = #connection_env{private_key = #'ECPrivateKey'{parameters = ECCurve} = Key},
session = Session} = State, _)
- when Algo == ecdh_ecdsa; Algo == ecdh_rsa ->
- State#state{diffie_hellman_keys = Key,
+ when KexAlg == ecdh_ecdsa;
+ KexAlg == ecdh_rsa ->
+ State#state{handshake_env = HsEnv#handshake_env{kex_keys = Key},
session = Session#session{ecc = ECCurve}};
-key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = Algo,
- hashsign_algorithm = HashSignAlgo,
- private_key = PrivateKey,
+key_exchange(#state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{kex_algorithm = KexAlg,
+ hashsign_algorithm = HashSignAlgo},
+ connection_env = #connection_env{negotiated_version = Version,
+ private_key = PrivateKey},
session = #session{ecc = ECCCurve},
- connection_states = ConnectionStates0,
- negotiated_version = Version
- } = State0, Connection)
- when Algo == ecdhe_ecdsa; Algo == ecdhe_rsa;
- Algo == ecdh_anon ->
+ connection_states = ConnectionStates0} = State0, Connection)
+ when KexAlg == ecdhe_ecdsa;
+ KexAlg == ecdhe_rsa;
+ KexAlg == ecdh_anon ->
ECDHKeys = public_key:generate_key(ECCCurve),
#{security_parameters := SecParams} =
@@ -1726,18 +1941,19 @@ key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = Alg
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
- State = Connection:queue_handshake(Msg, State0),
- State#state{diffie_hellman_keys = ECDHKeys};
-key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = psk,
+ #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
+ State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}};
+key_exchange(#state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{kex_algorithm = psk},
ssl_options = #ssl_options{psk_identity = undefined}} = State, _) ->
State;
-key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = psk,
+key_exchange(#state{static_env = #static_env{role = server},
ssl_options = #ssl_options{psk_identity = PskIdentityHint},
- hashsign_algorithm = HashSignAlgo,
- private_key = PrivateKey,
- connection_states = ConnectionStates0,
- negotiated_version = Version
- } = State0, Connection) ->
+ handshake_env = #handshake_env{kex_algorithm = psk,
+ hashsign_algorithm = HashSignAlgo},
+ connection_env = #connection_env{negotiated_version = Version,
+ private_key = PrivateKey},
+ connection_states = ConnectionStates0} = State0, Connection) ->
#{security_parameters := SecParams} =
ssl_record:pending_connection_state(ConnectionStates0, read),
#security_parameters{client_random = ClientRandom,
@@ -1746,15 +1962,16 @@ key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = psk
{psk, PskIdentityHint,
HashSignAlgo, ClientRandom,
ServerRandom,
- PrivateKey}),
+ PrivateKey}),
Connection:queue_handshake(Msg, State0);
-key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = dhe_psk,
+key_exchange(#state{static_env = #static_env{role = server},
ssl_options = #ssl_options{psk_identity = PskIdentityHint},
- hashsign_algorithm = HashSignAlgo,
- diffie_hellman_params = #'DHParameter'{} = Params,
- private_key = PrivateKey,
- connection_states = ConnectionStates0,
- negotiated_version = Version
+ handshake_env = #handshake_env{kex_algorithm = dhe_psk,
+ diffie_hellman_params = #'DHParameter'{} = Params,
+ hashsign_algorithm = HashSignAlgo},
+ connection_env = #connection_env{negotiated_version = Version,
+ private_key = PrivateKey},
+ connection_states = ConnectionStates0
} = State0, Connection) ->
DHKeys = public_key:generate_key(Params),
#{security_parameters := SecParams} =
@@ -1767,15 +1984,16 @@ key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = dhe
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
- State = Connection:queue_handshake(Msg, State0),
- State#state{diffie_hellman_keys = DHKeys};
-key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = ecdhe_psk,
+ #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
+ State#state{handshake_env = HsEnv#handshake_env{kex_keys = DHKeys}};
+key_exchange(#state{static_env = #static_env{role = server},
ssl_options = #ssl_options{psk_identity = PskIdentityHint},
- hashsign_algorithm = HashSignAlgo,
- private_key = PrivateKey,
+ handshake_env = #handshake_env{kex_algorithm = ecdhe_psk,
+ hashsign_algorithm = HashSignAlgo},
+ connection_env = #connection_env{negotiated_version = Version,
+ private_key = PrivateKey},
session = #session{ecc = ECCCurve},
- connection_states = ConnectionStates0,
- negotiated_version = Version
+ connection_states = ConnectionStates0
} = State0, Connection) ->
ECDHKeys = public_key:generate_key(ECCCurve),
#{security_parameters := SecParams} =
@@ -1788,17 +2006,19 @@ key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = ecd
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
- State = Connection:queue_handshake(Msg, State0),
- State#state{diffie_hellman_keys = ECDHKeys};
-key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = rsa_psk,
+ #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
+ State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}};
+key_exchange(#state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{kex_algorithm = rsa_psk},
ssl_options = #ssl_options{psk_identity = undefined}} = State, _) ->
State;
-key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = rsa_psk,
+key_exchange(#state{static_env = #static_env{role = server},
ssl_options = #ssl_options{psk_identity = PskIdentityHint},
- hashsign_algorithm = HashSignAlgo,
- private_key = PrivateKey,
- connection_states = ConnectionStates0,
- negotiated_version = Version
+ handshake_env = #handshake_env{kex_algorithm = rsa_psk,
+ hashsign_algorithm = HashSignAlgo},
+ connection_env = #connection_env{negotiated_version = Version,
+ private_key = PrivateKey},
+ connection_states = ConnectionStates0
} = State0, Connection) ->
#{security_parameters := SecParams} =
ssl_record:pending_connection_state(ConnectionStates0, read),
@@ -1810,17 +2030,18 @@ key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = rsa
ServerRandom,
PrivateKey}),
Connection:queue_handshake(Msg, State0);
-key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = Algo,
+key_exchange(#state{static_env = #static_env{role = server},
ssl_options = #ssl_options{user_lookup_fun = LookupFun},
- hashsign_algorithm = HashSignAlgo,
+ handshake_env = #handshake_env{kex_algorithm = KexAlg,
+ hashsign_algorithm = HashSignAlgo},
+ connection_env = #connection_env{negotiated_version = Version,
+ private_key = PrivateKey},
session = #session{srp_username = Username},
- private_key = PrivateKey,
- connection_states = ConnectionStates0,
- negotiated_version = Version
+ connection_states = ConnectionStates0
} = State0, Connection)
- when Algo == srp_dss;
- Algo == srp_rsa;
- Algo == srp_anon ->
+ when KexAlg == srp_dss;
+ KexAlg == srp_rsa;
+ KexAlg == srp_anon ->
SrpParams = handle_srp_identity(Username, LookupFun),
Keys = case generate_srp_server_keys(SrpParams, 0) of
Alert = #alert{} ->
@@ -1837,82 +2058,86 @@ key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = Alg
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
- State = Connection:queue_handshake(Msg, State0),
- State#state{srp_params = SrpParams,
- srp_keys = Keys};
+ #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0),
+ State#state{handshake_env = HsEnv#handshake_env{srp_params = SrpParams,
+ kex_keys = Keys}};
key_exchange(#state{static_env = #static_env{role = client},
- key_algorithm = rsa,
- public_key_info = PublicKeyInfo,
- negotiated_version = Version,
- premaster_secret = PremasterSecret} = State0, Connection) ->
+ handshake_env = #handshake_env{kex_algorithm = rsa,
+ public_key_info = PublicKeyInfo,
+ premaster_secret = PremasterSecret},
+ connection_env = #connection_env{negotiated_version = Version}
+ } = State0, Connection) ->
Msg = rsa_key_exchange(ssl:tls_version(Version), PremasterSecret, PublicKeyInfo),
Connection:queue_handshake(Msg, State0);
key_exchange(#state{static_env = #static_env{role = client},
- key_algorithm = Algorithm,
- negotiated_version = Version,
- diffie_hellman_keys = {DhPubKey, _}
- } = State0, Connection)
- when Algorithm == dhe_dss;
- Algorithm == dhe_rsa;
- Algorithm == dh_anon ->
+ handshake_env = #handshake_env{kex_algorithm = KexAlg,
+ kex_keys = {DhPubKey, _}},
+ connection_env = #connection_env{negotiated_version = Version}
+ } = State0, Connection)
+ when KexAlg == dhe_dss;
+ KexAlg == dhe_rsa;
+ KexAlg == dh_anon ->
Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {dh, DhPubKey}),
Connection:queue_handshake(Msg, State0);
key_exchange(#state{static_env = #static_env{role = client},
- key_algorithm = Algorithm,
- negotiated_version = Version,
- session = Session,
- diffie_hellman_keys = #'ECPrivateKey'{parameters = ECCurve} = Key} = State0, Connection)
- when Algorithm == ecdhe_ecdsa; Algorithm == ecdhe_rsa;
- Algorithm == ecdh_ecdsa; Algorithm == ecdh_rsa;
- Algorithm == ecdh_anon ->
+ handshake_env = #handshake_env{kex_algorithm = KexAlg,
+ kex_keys = #'ECPrivateKey'{parameters = ECCurve} = Key},
+ connection_env = #connection_env{negotiated_version = Version},
+ session = Session
+ } = State0, Connection)
+ when KexAlg == ecdhe_ecdsa;
+ KexAlg == ecdhe_rsa;
+ KexAlg == ecdh_ecdsa;
+ KexAlg == ecdh_rsa;
+ KexAlg == ecdh_anon ->
Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {ecdh, Key}),
Connection:queue_handshake(Msg, State0#state{session = Session#session{ecc = ECCurve}});
key_exchange(#state{static_env = #static_env{role = client},
- ssl_options = SslOpts,
- key_algorithm = psk,
- negotiated_version = Version} = State0, Connection) ->
+ handshake_env = #handshake_env{kex_algorithm = psk},
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = SslOpts} = State0, Connection) ->
Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version),
{psk, SslOpts#ssl_options.psk_identity}),
Connection:queue_handshake(Msg, State0);
key_exchange(#state{static_env = #static_env{role = client},
- ssl_options = SslOpts,
- key_algorithm = dhe_psk,
- negotiated_version = Version,
- diffie_hellman_keys = {DhPubKey, _}} = State0, Connection) ->
+ handshake_env = #handshake_env{kex_algorithm = dhe_psk,
+ kex_keys = {DhPubKey, _}},
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = SslOpts} = State0, Connection) ->
Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version),
{dhe_psk,
SslOpts#ssl_options.psk_identity, DhPubKey}),
Connection:queue_handshake(Msg, State0);
key_exchange(#state{static_env = #static_env{role = client},
- ssl_options = SslOpts,
- key_algorithm = ecdhe_psk,
- negotiated_version = Version,
- diffie_hellman_keys = ECDHKeys} = State0, Connection) ->
+ handshake_env = #handshake_env{kex_algorithm = ecdhe_psk,
+ kex_keys = ECDHKeys},
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = SslOpts} = State0, Connection) ->
Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version),
{ecdhe_psk,
SslOpts#ssl_options.psk_identity, ECDHKeys}),
Connection:queue_handshake(Msg, State0);
key_exchange(#state{static_env = #static_env{role = client},
- ssl_options = SslOpts,
- key_algorithm = rsa_psk,
- public_key_info = PublicKeyInfo,
- negotiated_version = Version,
- premaster_secret = PremasterSecret}
+ handshake_env = #handshake_env{kex_algorithm = rsa_psk,
+ public_key_info = PublicKeyInfo,
+ premaster_secret = PremasterSecret},
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = SslOpts}
= State0, Connection) ->
Msg = rsa_psk_key_exchange(ssl:tls_version(Version), SslOpts#ssl_options.psk_identity,
PremasterSecret, PublicKeyInfo),
Connection:queue_handshake(Msg, State0);
key_exchange(#state{static_env = #static_env{role = client},
- key_algorithm = Algorithm,
- negotiated_version = Version,
- srp_keys = {ClientPubKey, _}}
+ handshake_env = #handshake_env{kex_algorithm = KexAlg,
+ kex_keys = {ClientPubKey, _}},
+ connection_env = #connection_env{negotiated_version = Version}}
= State0, Connection)
- when Algorithm == srp_dss;
- Algorithm == srp_rsa;
- Algorithm == srp_anon ->
+ when KexAlg == srp_dss;
+ KexAlg == srp_rsa;
+ KexAlg == srp_anon ->
Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {srp, ClientPubKey}),
Connection:queue_handshake(Msg, State0).
@@ -1949,18 +2174,24 @@ rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret,
rsa_psk_key_exchange(_, _, _, _) ->
throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)).
-request_client_cert(#state{key_algorithm = Alg} = State, _)
- when Alg == dh_anon; Alg == ecdh_anon;
- Alg == psk; Alg == dhe_psk; Alg == ecdhe_psk; Alg == rsa_psk;
- Alg == srp_dss; Alg == srp_rsa; Alg == srp_anon ->
+request_client_cert(#state{handshake_env = #handshake_env{kex_algorithm = Alg}} = State, _)
+ when Alg == dh_anon;
+ Alg == ecdh_anon;
+ Alg == psk;
+ Alg == dhe_psk;
+ Alg == ecdhe_psk;
+ Alg == rsa_psk;
+ Alg == srp_dss;
+ Alg == srp_rsa;
+ Alg == srp_anon ->
State;
request_client_cert(#state{static_env = #static_env{cert_db = CertDbHandle,
cert_db_ref = CertDbRef},
+ connection_env = #connection_env{negotiated_version = Version},
ssl_options = #ssl_options{verify = verify_peer,
signature_algs = SupportedHashSigns},
- connection_states = ConnectionStates0,
- negotiated_version = Version} = State0, Connection) ->
+ connection_states = ConnectionStates0} = State0, Connection) ->
#{security_parameters :=
#security_parameters{cipher_suite = CipherSuite}} =
ssl_record:pending_connection_state(ConnectionStates0, read),
@@ -1977,7 +2208,7 @@ request_client_cert(#state{ssl_options = #ssl_options{verify = verify_none}} =
State.
calculate_master_secret(PremasterSecret,
- #state{negotiated_version = Version,
+ #state{connection_env = #connection_env{negotiated_version = Version},
connection_states = ConnectionStates0,
session = Session0} = State0, Connection,
_Current, Next) ->
@@ -2006,11 +2237,11 @@ finalize_handshake(State0, StateName, Connection) ->
next_protocol(#state{static_env = #static_env{role = server}} = State, _) ->
State;
-next_protocol(#state{negotiated_protocol = undefined} = State, _) ->
+next_protocol(#state{handshake_env = #handshake_env{negotiated_protocol = undefined}} = State, _) ->
State;
-next_protocol(#state{expecting_next_protocol_negotiation = false} = State, _) ->
+next_protocol(#state{handshake_env = #handshake_env{expecting_next_protocol_negotiation = false}} = State, _) ->
State;
-next_protocol(#state{negotiated_protocol = NextProtocol} = State0, Connection) ->
+next_protocol(#state{handshake_env = #handshake_env{negotiated_protocol = NextProtocol}} = State0, Connection) ->
NextProtocolMessage = ssl_handshake:next_protocol(NextProtocol),
Connection:queue_handshake(NextProtocolMessage, State0).
@@ -2018,14 +2249,15 @@ cipher_protocol(State, Connection) ->
Connection:queue_change_cipher(#change_cipher_spec{}, State).
finished(#state{static_env = #static_env{role = Role},
- negotiated_version = Version,
+ handshake_env = #handshake_env{tls_handshake_history = Hist},
+ connection_env = #connection_env{negotiated_version = Version},
session = Session,
- connection_states = ConnectionStates0,
- tls_handshake_history = Handshake0} = State0, StateName, Connection) ->
+ connection_states = ConnectionStates0} = State0,
+ StateName, Connection) ->
MasterSecret = Session#session.master_secret,
Finished = ssl_handshake:finished(ssl:tls_version(Version), Role,
get_current_prf(ConnectionStates0, write),
- MasterSecret, Handshake0),
+ MasterSecret, Hist),
ConnectionStates = save_verify_data(Role, Finished, ConnectionStates0, StateName),
Connection:send_handshake(Finished, State0#state{connection_states =
ConnectionStates}).
@@ -2041,65 +2273,71 @@ save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, abbrev
calculate_secret(#server_dh_params{dh_p = Prime, dh_g = Base,
dh_y = ServerPublicDhKey} = Params,
- State, Connection) ->
+ #state{handshake_env = HsEnv} = State, Connection) ->
Keys = {_, PrivateDhKey} = crypto:generate_key(dh, [Prime, Base]),
PremasterSecret =
ssl_handshake:premaster_secret(ServerPublicDhKey, PrivateDhKey, Params),
calculate_master_secret(PremasterSecret,
- State#state{diffie_hellman_keys = Keys},
+ State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}},
Connection, certify, certify);
calculate_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey},
- State=#state{session=Session}, Connection) ->
+ #state{handshake_env = HsEnv,
+ session = Session} = State, Connection) ->
ECDHKeys = public_key:generate_key(ECCurve),
PremasterSecret =
ssl_handshake:premaster_secret(#'ECPoint'{point = ECServerPubKey}, ECDHKeys),
calculate_master_secret(PremasterSecret,
- State#state{diffie_hellman_keys = ECDHKeys,
+ State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys},
session = Session#session{ecc = ECCurve}},
Connection, certify, certify);
calculate_secret(#server_psk_params{
hint = IdentityHint},
- State, Connection) ->
+ #state{handshake_env = HsEnv} = State, Connection) ->
%% store for later use
- Connection:next_event(certify, no_record, State#state{psk_identity = IdentityHint});
+ Connection:next_event(certify, no_record,
+ State#state{handshake_env =
+ HsEnv#handshake_env{server_psk_identity = IdentityHint}});
calculate_secret(#server_dhe_psk_params{
dh_params = #server_dh_params{dh_p = Prime, dh_g = Base}} = ServerKey,
- #state{ssl_options = #ssl_options{user_lookup_fun = PSKLookup}} =
+ #state{handshake_env = HsEnv,
+ ssl_options = #ssl_options{user_lookup_fun = PSKLookup}} =
State, Connection) ->
Keys = {_, PrivateDhKey} =
crypto:generate_key(dh, [Prime, Base]),
PremasterSecret = ssl_handshake:premaster_secret(ServerKey, PrivateDhKey, PSKLookup),
- calculate_master_secret(PremasterSecret, State#state{diffie_hellman_keys = Keys},
+ calculate_master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}},
Connection, certify, certify);
calculate_secret(#server_ecdhe_psk_params{
dh_params = #server_ecdh_params{curve = ECCurve}} = ServerKey,
#state{ssl_options = #ssl_options{user_lookup_fun = PSKLookup}} =
- State=#state{session=Session}, Connection) ->
+ #state{handshake_env = HsEnv,
+ session = Session} = State, Connection) ->
ECDHKeys = public_key:generate_key(ECCurve),
PremasterSecret = ssl_handshake:premaster_secret(ServerKey, ECDHKeys, PSKLookup),
calculate_master_secret(PremasterSecret,
- State#state{diffie_hellman_keys = ECDHKeys,
+ State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys},
session = Session#session{ecc = ECCurve}},
Connection, certify, certify);
calculate_secret(#server_srp_params{srp_n = Prime, srp_g = Generator} = ServerKey,
- #state{ssl_options = #ssl_options{srp_identity = SRPId}} = State,
+ #state{handshake_env = HsEnv,
+ ssl_options = #ssl_options{srp_identity = SRPId}} = State,
Connection) ->
Keys = generate_srp_client_keys(Generator, Prime, 0),
PremasterSecret = ssl_handshake:premaster_secret(ServerKey, Keys, SRPId),
- calculate_master_secret(PremasterSecret, State#state{srp_keys = Keys}, Connection,
+ calculate_master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}}, Connection,
certify, certify).
master_secret(#alert{} = Alert, _) ->
Alert;
master_secret(PremasterSecret, #state{static_env = #static_env{role = Role},
+ connection_env = #connection_env{negotiated_version = Version},
session = Session,
- negotiated_version = Version,
connection_states = ConnectionStates0} = State) ->
case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret,
ConnectionStates0, Role) of
@@ -2159,7 +2397,7 @@ cipher_role(client, Data, Session, #state{connection_states = ConnectionStates0}
{Record, State} = prepare_connection(State0#state{session = Session,
connection_states = ConnectionStates},
Connection),
- Connection:next_event(connection, Record, State);
+ Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close}]);
cipher_role(server, Data, Session, #state{connection_states = ConnectionStates0} = State0,
Connection) ->
ConnectionStates1 = ssl_record:set_client_verify_data(current_read, Data,
@@ -2168,15 +2406,15 @@ cipher_role(server, Data, Session, #state{connection_states = ConnectionStates0
finalize_handshake(State0#state{connection_states = ConnectionStates1,
session = Session}, cipher, Connection),
{Record, State} = prepare_connection(State1, Connection),
- Connection:next_event(connection, Record, State, Actions).
-
-is_anonymous(Algo) when Algo == dh_anon;
- Algo == ecdh_anon;
- Algo == psk;
- Algo == dhe_psk;
- Algo == ecdhe_psk;
- Algo == rsa_psk;
- Algo == srp_anon ->
+ Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close} | Actions]).
+
+is_anonymous(KexAlg) when KexAlg == dh_anon;
+ KexAlg == ecdh_anon;
+ KexAlg == psk;
+ KexAlg == dhe_psk;
+ KexAlg == ecdhe_psk;
+ KexAlg == rsa_psk;
+ KexAlg == srp_anon ->
true;
is_anonymous(_) ->
false.
@@ -2290,6 +2528,30 @@ set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active}| Opts], SockO
Active == false ->
set_socket_opts(ConnectionCb, Transport, Socket, Opts,
SockOpts#socket_options{active = Active}, Other);
+set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active1} = Opt| Opts],
+ SockOpts=#socket_options{active = Active0}, Other)
+ when Active1 >= -32768, Active1 =< 32767 ->
+ Active = if
+ is_integer(Active0), Active0 + Active1 < -32768 ->
+ error;
+ is_integer(Active0), Active0 + Active1 =< 0 ->
+ false;
+ is_integer(Active0), Active0 + Active1 > 32767 ->
+ error;
+ Active1 =< 0 ->
+ false;
+ is_integer(Active0) ->
+ Active0 + Active1;
+ true ->
+ Active1
+ end,
+ case Active of
+ error ->
+ {{error, {options, {socket_options, Opt}} }, SockOpts};
+ _ ->
+ set_socket_opts(ConnectionCb, Transport, Socket, Opts,
+ SockOpts#socket_options{active = Active}, Other)
+ end;
set_socket_opts(_,_, _, [{active, _} = Opt| _], SockOpts, _) ->
{{error, {options, {socket_options, Opt}} }, SockOpts};
set_socket_opts(ConnectionCb, Transport, Socket, [Opt | Opts], SockOpts, Other) ->
@@ -2304,22 +2566,6 @@ hibernate_after(connection = StateName,
hibernate_after(StateName, State, Actions) ->
{next_state, StateName, State, Actions}.
-map_extensions(#hello_extensions{renegotiation_info = RenegotiationInfo,
- signature_algs = SigAlg,
- alpn = Alpn,
- next_protocol_negotiation = Next,
- srp = SRP,
- ec_point_formats = ECPointFmt,
- elliptic_curves = ECCCurves,
- sni = SNI}) ->
- #{renegotiation_info => ssl_handshake:extension_value(RenegotiationInfo),
- signature_algs => ssl_handshake:extension_value(SigAlg),
- alpn => ssl_handshake:extension_value(Alpn),
- srp => ssl_handshake:extension_value(SRP),
- next_protocol => ssl_handshake:extension_value(Next),
- ec_point_formats => ssl_handshake:extension_value(ECPointFmt),
- elliptic_curves => ssl_handshake:extension_value(ECCCurves),
- sni => ssl_handshake:extension_value(SNI)}.
terminate_alert(normal) ->
?ALERT_REC(?WARNING, ?CLOSE_NOTIFY);
@@ -2353,7 +2599,7 @@ handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref,
ok
end.
-prepare_connection(#state{renegotiation = Renegotiate,
+prepare_connection(#state{handshake_env = #handshake_env{renegotiation = Renegotiate},
start_or_recv_from = RecvFrom} = State0, Connection)
when Renegotiate =/= {false, first},
RecvFrom =/= undefined ->
@@ -2363,42 +2609,54 @@ prepare_connection(State0, Connection) ->
State = Connection:reinit(State0),
{no_record, ack_connection(State)}.
-ack_connection(#state{renegotiation = {true, Initiater}} = State) when Initiater == peer;
- Initiater == internal ->
- State#state{renegotiation = undefined};
-ack_connection(#state{renegotiation = {true, From}} = State) ->
+ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, Initiater}} = HsEnv} = State) when Initiater == peer;
+ Initiater == internal ->
+ State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}};
+ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv} = State) ->
gen_statem:reply(From, ok),
- State#state{renegotiation = undefined};
-ack_connection(#state{renegotiation = {false, first},
- start_or_recv_from = StartFrom,
- timer = Timer} = State) when StartFrom =/= undefined ->
+ State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}};
+ack_connection(#state{handshake_env = #handshake_env{renegotiation = {false, first}} = HsEnv,
+ start_or_recv_from = StartFrom} = State) when StartFrom =/= undefined ->
gen_statem:reply(StartFrom, connected),
- cancel_timer(Timer),
- State#state{renegotiation = undefined,
- start_or_recv_from = undefined, timer = undefined};
+ State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined},
+ start_or_recv_from = undefined};
ack_connection(State) ->
State.
-cancel_timer(undefined) ->
- ok;
-cancel_timer(Timer) ->
- erlang:cancel_timer(Timer),
- ok.
-
session_handle_params(#server_ecdh_params{curve = ECCurve}, Session) ->
Session#session{ecc = ECCurve};
session_handle_params(_, Session) ->
Session.
-register_session(client, Host, Port, #session{is_resumable = new} = Session0) ->
+handle_session(Role = server, #ssl_options{reuse_sessions = true} = SslOpts,
+ Host, Port, Session0) ->
+ register_session(Role, host_id(Role, Host, SslOpts), Port, Session0, true);
+handle_session(Role = client, #ssl_options{verify = verify_peer,
+ reuse_sessions = Reuse} = SslOpts,
+ Host, Port, Session0) when Reuse =/= false ->
+ register_session(Role, host_id(Role, Host, SslOpts), Port, Session0, reg_type(Reuse));
+handle_session(server, _, Host, Port, Session) ->
+ %% Remove "session of type new" entry from session DB
+ ssl_manager:invalidate_session(Host, Port, Session),
+ Session;
+handle_session(client, _,_,_, Session) ->
+ %% In client case there is no entry yet, so nothing to remove
+ Session.
+
+reg_type(save) ->
+ true;
+reg_type(true) ->
+ unique.
+
+register_session(client, Host, Port, #session{is_resumable = new} = Session0, Save) ->
Session = Session0#session{is_resumable = true},
- ssl_manager:register_session(Host, Port, Session),
+ ssl_manager:register_session(Host, Port, Session, Save),
Session;
-register_session(server, _, Port, #session{is_resumable = new} = Session0) ->
+register_session(server, _, Port, #session{is_resumable = new} = Session0, _) ->
Session = Session0#session{is_resumable = true},
ssl_manager:register_session(Port, Session),
Session;
-register_session(_, _, _, Session) ->
+register_session(_, _, _, Session, _) ->
Session. %% Already registered
host_id(client, _Host, #ssl_options{server_name_indication = Hostname}) when is_list(Hostname) ->
@@ -2420,9 +2678,8 @@ handle_resumed_session(SessId, #state{static_env = #static_env{host = Host,
protocol_cb = Connection,
session_cache = Cache,
session_cache_cb = CacheCb},
- connection_states = ConnectionStates0,
- negotiated_version = Version
- } = State) ->
+ connection_env = #connection_env{negotiated_version = Version},
+ connection_states = ConnectionStates0} = State) ->
Session = CacheCb:lookup(Cache, {{Host, Port}, SessId}),
case ssl_handshake:master_secret(ssl:tls_version(Version), Session,
ConnectionStates0, client) of
@@ -2480,7 +2737,7 @@ handle_active_option(false, connection = StateName, To, Reply, State) ->
hibernate_after(StateName, State, [{reply, To, Reply}]);
handle_active_option(_, connection = StateName0, To, Reply, #state{static_env = #static_env{protocol_cb = Connection},
- user_data_buffer = <<>>} = State0) ->
+ user_data_buffer = {_,0,_}} = State0) ->
case Connection:next_event(StateName0, no_record, State0) of
{next_state, StateName, State} ->
hibernate_after(StateName, State, [{reply, To, Reply}]);
@@ -2489,11 +2746,11 @@ handle_active_option(_, connection = StateName0, To, Reply, #state{static_env =
{stop, _, _} = Stop ->
Stop
end;
-handle_active_option(_, StateName, To, Reply, #state{user_data_buffer = <<>>} = State) ->
+handle_active_option(_, StateName, To, Reply, #state{user_data_buffer = {_,0,_}} = State) ->
%% Active once already set
{next_state, StateName, State, [{reply, To, Reply}]};
-%% user_data_buffer =/= <<>>
+%% user_data_buffer nonempty
handle_active_option(_, StateName0, To, Reply,
#state{static_env = #static_env{protocol_cb = Connection}} = State0) ->
case read_application_data(<<>>, State0) of
@@ -2513,33 +2770,25 @@ handle_active_option(_, StateName0, To, Reply,
%% Picks ClientData
-get_data(_, _, <<>>) ->
- {more, <<>>};
-%% Recv timed out save buffer data until next recv
-get_data(#socket_options{active=false}, undefined, Buffer) ->
- {passive, Buffer};
-get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Buffer)
+get_data(#socket_options{active=false}, undefined, _Bin) ->
+ %% Recv timed out save buffer data until next recv
+ passive;
+get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Bin)
when Raw =:= raw; Raw =:= 0 -> %% Raw Mode
- if
- Active =/= false orelse BytesToRead =:= 0 ->
+ case Bin of
+ <<_/binary>> when Active =/= false orelse BytesToRead =:= 0 ->
%% Active true or once, or passive mode recv(0)
- {ok, Buffer, <<>>};
- byte_size(Buffer) >= BytesToRead ->
+ {ok, Bin, <<>>};
+ <<Data:BytesToRead/binary, Rest/binary>> ->
%% Passive Mode, recv(Bytes)
- <<Data:BytesToRead/binary, Rest/binary>> = Buffer,
- {ok, Data, Rest};
- true ->
+ {ok, Data, Rest};
+ <<_/binary>> ->
%% Passive Mode not enough data
- {more, Buffer}
+ {more, BytesToRead}
end;
-get_data(#socket_options{packet=Type, packet_size=Size}, _, Buffer) ->
+get_data(#socket_options{packet=Type, packet_size=Size}, _, Bin) ->
PacketOpts = [{packet_size, Size}],
- case decode_packet(Type, Buffer, PacketOpts) of
- {more, _} ->
- {more, Buffer};
- Decoded ->
- Decoded
- end.
+ decode_packet(Type, Bin, PacketOpts).
decode_packet({http, headers}, Buffer, PacketOpts) ->
decode_packet(httph, Buffer, PacketOpts);
@@ -2557,24 +2806,39 @@ decode_packet(Type, Buffer, PacketOpts) ->
%% Note that if the user has explicitly configured the socket to expect
%% HTTP headers using the {packet, httph} option, we don't do any automatic
%% switching of states.
-deliver_app_data(CPids, Transport, Socket, SOpts = #socket_options{active=Active, packet=Type},
- Data, Pid, From, Tracker, Connection) ->
- send_or_reply(Active, Pid, From,
- format_reply(CPids, Transport, Socket, SOpts, Data, Tracker, Connection)),
- SO = case Data of
- {P, _, _, _} when ((P =:= http_request) or (P =:= http_response)),
- ((Type =:= http) or (Type =:= http_bin)) ->
- SOpts#socket_options{packet={Type, headers}};
- http_eoh when tuple_size(Type) =:= 2 ->
- % End of headers - expect another Request/Response line
- {Type1, headers} = Type,
- SOpts#socket_options{packet=Type1};
- _ ->
- SOpts
- end,
+deliver_app_data(
+ CPids, Transport, Socket,
+ #socket_options{active=Active, packet=Type} = SOpts,
+ Data, Pid, From, Tracker, Connection) ->
+ %%
+ send_or_reply(
+ Active, Pid, From,
+ format_reply(
+ CPids, Transport, Socket, SOpts, Data, Tracker, Connection)),
+ SO =
+ case Data of
+ {P, _, _, _}
+ when ((P =:= http_request) or (P =:= http_response)),
+ ((Type =:= http) or (Type =:= http_bin)) ->
+ SOpts#socket_options{packet={Type, headers}};
+ http_eoh when tuple_size(Type) =:= 2 ->
+ %% End of headers - expect another Request/Response line
+ {Type1, headers} = Type,
+ SOpts#socket_options{packet=Type1};
+ _ ->
+ SOpts
+ end,
case Active of
once ->
SO#socket_options{active=false};
+ 1 ->
+ send_user(
+ Pid,
+ format_passive(
+ CPids, Transport, Socket, Tracker, Connection)),
+ SO#socket_options{active=false};
+ N when is_integer(N) ->
+ SO#socket_options{active=N - 1};
_ ->
SO
end.
@@ -2584,7 +2848,7 @@ format_reply(_, _, _,#socket_options{active = false, mode = Mode, packet = Packe
{ok, do_format_reply(Mode, Packet, Header, Data)};
format_reply(CPids, Transport, Socket, #socket_options{active = _, mode = Mode, packet = Packet,
header = Header}, Data, Tracker, Connection) ->
- {ssl, Connection:socket(CPids, Transport, Socket, Connection, Tracker),
+ {ssl, Connection:socket(CPids, Transport, Socket, Tracker),
do_format_reply(Mode, Packet, Header, Data)}.
deliver_packet_error(CPids, Transport, Socket,
@@ -2596,7 +2860,7 @@ format_packet_error(_, _, _,#socket_options{active = false, mode = Mode}, Data,
{error, {invalid_packet, do_format_reply(Mode, raw, 0, Data)}};
format_packet_error(CPids, Transport, Socket, #socket_options{active = _, mode = Mode},
Data, Tracker, Connection) ->
- {ssl_error, Connection:socket(CPids, Transport, Socket, Connection, Tracker),
+ {ssl_error, Connection:socket(CPids, Transport, Socket, Tracker),
{invalid_packet, do_format_reply(Mode, raw, 0, Data)}}.
do_format_reply(binary, _, N, Data) when N > 0 -> % Header mode
@@ -2611,6 +2875,9 @@ do_format_reply(list, Packet, _, Data)
do_format_reply(list, _,_, Data) ->
binary_to_list(Data).
+format_passive(CPids, Transport, Socket, Tracker, Connection) ->
+ {ssl_passive, Connection:socket(CPids, Transport, Socket, Tracker)}.
+
header(0, <<>>) ->
<<>>;
header(_, <<>>) ->
@@ -2652,22 +2919,20 @@ alert_user(Pids, Transport, Tracker, Socket, Active, Pid, From, Alert, Role, Con
case ssl_alert:reason_code(Alert, Role) of
closed ->
send_or_reply(Active, Pid, From,
- {ssl_closed, Connection:socket(Pids,
- Transport, Socket, Connection, Tracker)});
+ {ssl_closed, Connection:socket(Pids, Transport, Socket, Tracker)});
ReasonCode ->
send_or_reply(Active, Pid, From,
- {ssl_error, Connection:socket(Pids,
- Transport, Socket, Connection, Tracker), ReasonCode})
+ {ssl_error, Connection:socket(Pids, Transport, Socket, Tracker), ReasonCode})
end.
-log_alert(true, Role, ProtocolName, StateName, #alert{role = Role} = Alert) ->
+log_alert(Level, Role, ProtocolName, StateName, #alert{role = Role} = Alert) ->
Txt = ssl_alert:own_alert_txt(Alert),
- error_logger:info_report(io_lib:format("~s ~p: In state ~p ~s\n", [ProtocolName, Role, StateName, Txt]));
-log_alert(true, Role, ProtocolName, StateName, Alert) ->
+ Report = io_lib:format("~s ~p: In state ~p ~s\n", [ProtocolName, Role, StateName, Txt]),
+ ssl_logger:notice(Level, Report);
+log_alert(Level, Role, ProtocolName, StateName, Alert) ->
Txt = ssl_alert:alert_txt(Alert),
- error_logger:info_report(io_lib:format("~s ~p: In state ~p ~s\n", [ProtocolName, Role, StateName, Txt]));
-log_alert(false, _, _, _, _) ->
- ok.
+ Report = io_lib:format("~s ~p: In state ~p ~s\n", [ProtocolName, Role, StateName, Txt]),
+ ssl_logger:notice(Level, Report).
invalidate_session(client, Host, Port, Session) ->
ssl_manager:invalidate_session(Host, Port, Session);
@@ -2676,7 +2941,9 @@ invalidate_session(server, _, Port, Session) ->
handle_sni_extension(undefined, State) ->
State;
-handle_sni_extension(#sni{hostname = Hostname}, #state{static_env = #static_env{role = Role} = InitStatEnv0} = State0) ->
+handle_sni_extension(#sni{hostname = Hostname}, #state{static_env = #static_env{role = Role} = InitStatEnv0,
+ handshake_env = HsEnv,
+ connection_env = CEnv} = State0) ->
NewOptions = update_ssl_options_from_sni(State0#state.ssl_options, Hostname),
case NewOptions of
undefined ->
@@ -2699,12 +2966,12 @@ handle_sni_extension(#sni{hostname = Hostname}, #state{static_env = #static_env{
cert_db = CertDbHandle,
crl_db = CRLDbHandle,
session_cache = CacheHandle
- },
- private_key = Key,
- diffie_hellman_params = DHParams,
- ssl_options = NewOptions,
- sni_hostname = Hostname
- }
+ },
+ connection_env = CEnv#connection_env{private_key = Key},
+ ssl_options = NewOptions,
+ handshake_env = HsEnv#handshake_env{sni_hostname = Hostname,
+ diffie_hellman_params = DHParams}
+ }
end.
update_ssl_options_from_sni(OrigSSLOptions, SNIHostname) ->
@@ -2727,8 +2994,3 @@ new_emulated([], EmOpts) ->
EmOpts;
new_emulated(NewEmOpts, _) ->
NewEmOpts.
-
-is_dist_up(#{dist_handle := Handle}) when Handle =/= undefined ->
- true;
-is_dist_up(_) ->
- false.
diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl
index 2f4dfefdda..201164949a 100644
--- a/lib/ssl/src/ssl_connection.hrl
+++ b/lib/ssl/src/ssl_connection.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2019. All 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,64 +51,132 @@
cert_db_ref :: certdb_ref() | 'undefined',
tracker :: pid() | 'undefined' %% Tracker process for listen socket
}).
+
+-record(handshake_env, {
+ client_hello_version :: ssl_record:ssl_version() | 'undefined',
+ unprocessed_handshake_events = 0 :: integer(),
+ tls_handshake_history :: ssl_handshake:ssl_handshake_history() | secret_printout()
+ | 'undefined',
+ expecting_finished = false ::boolean(),
+ renegotiation :: undefined | {boolean(), From::term() | internal | peer},
+ allow_renegotiate = true ::boolean(),
+ %% Ext handling
+ hello, %%:: #client_hello{} | #server_hello{}
+ sni_hostname = undefined,
+ expecting_next_protocol_negotiation = false ::boolean(),
+ next_protocol = undefined :: undefined | binary(),
+ negotiated_protocol,
+ hashsign_algorithm = {undefined, undefined},
+ cert_hashsign_algorithm = {undefined, undefined},
+ %% key exchange
+ kex_algorithm :: ssl:kex_algo(),
+ kex_keys :: {PublicKey :: binary(), PrivateKey :: binary()} | #'ECPrivateKey'{} | undefined | secret_printout(),
+ diffie_hellman_params:: #'DHParameter'{} | undefined | secret_printout(),
+ srp_params :: #srp_user{} | secret_printout() | 'undefined',
+ public_key_info :: ssl_handshake:public_key_info() | 'undefined',
+ premaster_secret :: binary() | secret_printout() | 'undefined',
+ server_psk_identity :: binary() | 'undefined' % server psk identity hint
+ }).
+
+-record(connection_env, {
+ user_application :: {Monitor::reference(), User::pid()},
+ downgrade,
+ terminated = false ::boolean() | closed,
+ negotiated_version :: ssl_record:ssl_version() | 'undefined',
+ erl_dist_handle = undefined :: erlang:dist_handle() | 'undefined',
+ private_key :: public_key:private_key() | secret_printout() | 'undefined'
+ }).
+
-record(state, {
static_env :: #static_env{},
- %% Change seldome
- user_application :: {Monitor::reference(), User::pid()},
+ connection_env :: #connection_env{} | secret_printout(),
ssl_options :: #ssl_options{},
socket_options :: #socket_options{},
- session :: #session{} | secret_printout(),
- allow_renegotiate = true ::boolean(),
- terminated = false ::boolean() | closed,
- negotiated_version :: ssl_record:ssl_version() | 'undefined',
- bytes_to_read :: undefined | integer(), %% bytes to read in passive mode
- downgrade,
- %% Changed often
+ %% Hanshake %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ handshake_env :: #handshake_env{} | secret_printout(),
+ %% Buffer of TLS/DTLS records, used during the TLS
+ %% handshake to when possible pack more than one TLS
+ %% record into the underlaying packet
+ %% format. Introduced by DTLS - RFC 4347. The
+ %% mecahnism is also usefull in TLS although we do not
+ %% need to worry about packet loss in TLS. In DTLS we
+ %% need to track DTLS handshake seqnr
+ flight_buffer = [] :: list() | map(),
+ client_certificate_requested = false :: boolean(),
+ protocol_specific = #{} :: map(),
+ session :: #session{} | secret_printout(),
+ key_share,
+ %% Data shuffling %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
connection_states :: ssl_record:connection_states() | secret_printout(),
protocol_buffers :: term() | secret_printout() , %% #protocol_buffers{} from tls_record.hrl or dtls_recor.hr
- user_data_buffer :: undefined | binary() | secret_printout(),
-
- %% Used only in HS
- unprocessed_handshake_events = 0 :: integer(),
- tls_handshake_history :: ssl_handshake:ssl_handshake_history() | secret_printout()
- | 'undefined',
- client_hello_version :: ssl_record:ssl_version() | 'undefined',
- client_certificate_requested = false :: boolean(),
- key_algorithm :: ssl_cipher_format:key_algo(),
- hashsign_algorithm = {undefined, undefined},
- cert_hashsign_algorithm = {undefined, undefined},
- public_key_info :: ssl_handshake:public_key_info() | 'undefined',
- private_key :: public_key:private_key() | secret_printout() | 'undefined',
- diffie_hellman_params:: #'DHParameter'{} | undefined | secret_printout(),
- diffie_hellman_keys :: {PublicKey :: binary(), PrivateKey :: binary()} | #'ECPrivateKey'{} | undefined | secret_printout(),
- psk_identity :: binary() | 'undefined', % server psk identity hint
- srp_params :: #srp_user{} | secret_printout() | 'undefined',
- srp_keys ::{PublicKey :: binary(), PrivateKey :: binary()} | secret_printout() | 'undefined',
- premaster_secret :: binary() | secret_printout() | 'undefined',
- renegotiation :: undefined | {boolean(), From::term() | internal | peer},
+ user_data_buffer :: undefined | {[binary()],non_neg_integer(),[binary()]} | secret_printout(),
+ bytes_to_read :: undefined | integer(), %% bytes to read in passive mode
+ %% recv and start handling
start_or_recv_from :: term(),
- timer :: undefined | reference(), % start_or_recive_timer
- hello, %%:: #client_hello{} | #server_hello{},
- expecting_next_protocol_negotiation = false ::boolean(),
- expecting_finished = false ::boolean(),
- next_protocol = undefined :: undefined | binary(),
- negotiated_protocol,
- sni_hostname = undefined,
- flight_buffer = [] :: list() | map(), %% Buffer of TLS/DTLS records, used during the TLS handshake
- %% to when possible pack more than one TLS record into the
- %% underlaying packet format. Introduced by DTLS - RFC 4347.
- %% The mecahnism is also usefull in TLS although we do not
- %% need to worry about packet loss in TLS. In DTLS we need to track DTLS handshake seqnr
- flight_state = reliable, %% reliable | {retransmit, integer()}| {waiting, ref(), integer()} - last two is used in DTLS over udp.
- erl_dist_data = #{} :: map(),
- protocol_specific = #{} :: map()
+ log_level
}).
-
-define(DEFAULT_DIFFIE_HELLMAN_PARAMS,
#'DHParameter'{prime = ?DEFAULT_DIFFIE_HELLMAN_PRIME,
base = ?DEFAULT_DIFFIE_HELLMAN_GENERATOR}).
-define(WAIT_TO_ALLOW_RENEGOTIATION, 12000).
+
+%%----------------------------------------------------------------------
+%% TLS 1.3
+%%----------------------------------------------------------------------
+
+%% TLS 1.3 uses the same state record with the following differences:
+%%
+%% state :: record()
+%%
+%% session_cache - not implemented
+%% session_cache_cb - not implemented
+%% crl_db - not implemented
+%% client_hello_version - Bleichenbacher mitigation in TLS 1.2
+%% client_certificate_requested - Built into TLS 1.3 state machine
+%% key_algorithm - not used
+%% diffie_hellman_params - used in TLS 1.2 ECDH key exchange
+%% diffie_hellman_keys - used in TLS 1.2 ECDH key exchange
+%% psk_identity - not used
+%% srp_params - not used, no srp extension in TLS 1.3
+%% srp_keys - not used, no srp extension in TLS 1.3
+%% premaster_secret - not used
+%% renegotiation - TLS 1.3 forbids renegotiation
+%% hello - used in user_hello, handshake continue
+%% allow_renegotiate - TLS 1.3 forbids renegotiation
+%% expecting_next_protocol_negotiation - ALPN replaced NPN, depricated in TLS 1.3
+%% expecting_finished - not implemented, used by abbreviated
+%% next_protocol - ALPN replaced NPN, depricated in TLS 1.3
+%%
+%% connection_state :: map()
+%%
+%% compression_state - not used
+%% mac_secret - not used
+%% sequence_number - not used
+%% secure_renegotiation - not used, no renegotiation_info in TLS 1.3
+%% client_verify_data - not used, no renegotiation_info in TLS 1.3
+%% server_verify_data - not used, no renegotiation_info in TLS 1.3
+%% beast_mitigation - not used
+%%
+%% security_parameters :: map()
+%%
+%% cipher_type - TLS 1.3 uses only AEAD ciphers
+%% iv_size - not used
+%% key_size - not used
+%% key_material_length - not used
+%% expanded_key_material_length - used in SSL 3.0
+%% mac_algorithm - not used
+%% prf_algorithm - not used
+%% hash_size - not used
+%% compression_algorithm - not used
+%% master_secret - used for multiple secret types in TLS 1.3
+%% client_random - not used
+%% server_random - not used
+%% exportable - not used
+%%
+%% cipher_state :: record()
+%% nonce - used for sequence_number
+
-endif. % -ifdef(ssl_connection).
diff --git a/lib/ssl/src/ssl_crl_cache.erl b/lib/ssl/src/ssl_crl_cache.erl
index 9c1af86eeb..841620ce57 100644
--- a/lib/ssl/src/ssl_crl_cache.erl
+++ b/lib/ssl/src/ssl_crl_cache.erl
@@ -28,6 +28,10 @@
-behaviour(ssl_crl_cache_api).
+-export_type([crl_src/0, uri/0]).
+-type crl_src() :: {file, file:filename()} | {der, public_key:der_encoded()}.
+-type uri() :: uri_string:uri_string().
+
-export([lookup/3, select/2, fresh_crl/2]).
-export([insert/1, insert/2, delete/1]).
diff --git a/lib/ssl/src/ssl_crl_cache_api.erl b/lib/ssl/src/ssl_crl_cache_api.erl
index d5380583e7..8a750b3929 100644
--- a/lib/ssl/src/ssl_crl_cache_api.erl
+++ b/lib/ssl/src/ssl_crl_cache_api.erl
@@ -21,12 +21,15 @@
%%
-module(ssl_crl_cache_api).
-
-include_lib("public_key/include/public_key.hrl").
--type db_handle() :: term().
--type issuer_name() :: {rdnSequence, [#'AttributeTypeAndValue'{}]}.
+-export_type([dist_point/0, crl_cache_ref/0]).
+
+-type crl_cache_ref() :: any().
+-type issuer_name() :: {rdnSequence,[#'AttributeTypeAndValue'{}]}.
+-type dist_point() :: #'DistributionPoint'{}.
--callback lookup(#'DistributionPoint'{}, issuer_name(), db_handle()) -> not_available | [public_key:der_encoded()].
--callback select(issuer_name(), db_handle()) -> [public_key:der_encoded()].
--callback fresh_crl(#'DistributionPoint'{}, public_key:der_encoded()) -> public_key:der_encoded().
+
+-callback lookup(dist_point(), issuer_name(), crl_cache_ref()) -> not_available | [public_key:der_encoded()].
+-callback select(issuer_name(), crl_cache_ref()) -> [public_key:der_encoded()].
+-callback fresh_crl(dist_point(), public_key:der_encoded()) -> public_key:der_encoded().
diff --git a/lib/ssl/src/ssl_crl_hash_dir.erl b/lib/ssl/src/ssl_crl_hash_dir.erl
index bb62737232..9478ff9b78 100644
--- a/lib/ssl/src/ssl_crl_hash_dir.erl
+++ b/lib/ssl/src/ssl_crl_hash_dir.erl
@@ -20,6 +20,7 @@
-module(ssl_crl_hash_dir).
-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/logger.hrl").
-behaviour(ssl_crl_cache_api).
@@ -55,7 +56,7 @@ select(Issuer, {_DbHandle, [{dir, Dir}]}) ->
%% is happy with that, but if it's true, this is an error.
[];
{error, Error} ->
- error_logger:error_report(
+ ?LOG_ERROR(
[{cannot_find_crl, Error},
{dir, Dir},
{module, ?MODULE},
@@ -86,7 +87,7 @@ find_crls(Issuer, Hash, Dir, N, Acc) ->
error:Error ->
%% Something is wrong with the file. Report
%% it, and try the next one.
- error_logger:error_report(
+ ?LOG_ERROR(
[{crl_parse_error, Error},
{filename, Filename},
{module, ?MODULE},
diff --git a/lib/ssl/src/ssl_dh_groups.erl b/lib/ssl/src/ssl_dh_groups.erl
new file mode 100644
index 0000000000..20d53de430
--- /dev/null
+++ b/lib/ssl/src/ssl_dh_groups.erl
@@ -0,0 +1,467 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2018. All 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_dh_groups).
+
+-include_lib("public_key/include/public_key.hrl").
+
+-export([modp2048_generator/0, modp2048_prime/0,
+ ffdhe2048_generator/0, ffdhe2048_prime/0,
+ ffdhe3072_generator/0, ffdhe3072_prime/0,
+ ffdhe4096_generator/0, ffdhe4096_prime/0,
+ ffdhe6144_generator/0, ffdhe6144_prime/0,
+ ffdhe8192_generator/0, ffdhe8192_prime/0,
+ dh_params/1]).
+
+%% RFC3526 - 2048-bit MODP Group
+%% This group is assigned id 14.
+%%
+%% This prime is: 2^2048 - 2^1984 - 1 + 2^64 * { [2^1918 pi] + 124476 }
+%%
+%% Its hexadecimal value is:
+%%
+%% FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1
+%% 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD
+%% EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245
+%% E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED
+%% EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D
+%% C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F
+%% 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
+%% 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B
+%% E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9
+%% DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510
+%% 15728E5A 8AACAA68 FFFFFFFF FFFFFFFF
+%%
+%% The generator is: 2.
+modp2048_generator() ->
+ 2.
+
+modp2048_prime() ->
+ P = "FFFFFFFF" "FFFFFFFF" "C90FDAA2" "2168C234" "C4C6628B" "80DC1CD1"
+ "29024E08" "8A67CC74" "020BBEA6" "3B139B22" "514A0879" "8E3404DD"
+ "EF9519B3" "CD3A431B" "302B0A6D" "F25F1437" "4FE1356D" "6D51C245"
+ "E485B576" "625E7EC6" "F44C42E9" "A637ED6B" "0BFF5CB6" "F406B7ED"
+ "EE386BFB" "5A899FA5" "AE9F2411" "7C4B1FE6" "49286651" "ECE45B3D"
+ "C2007CB8" "A163BF05" "98DA4836" "1C55D39A" "69163FA8" "FD24CF5F"
+ "83655D23" "DCA3AD96" "1C62F356" "208552BB" "9ED52907" "7096966D"
+ "670C354E" "4ABC9804" "F1746C08" "CA18217C" "32905E46" "2E36CE3B"
+ "E39E772C" "180E8603" "9B2783A2" "EC07A28F" "B5C55DF0" "6F4C52C9"
+ "DE2BCBF6" "95581718" "3995497C" "EA956AE5" "15D22618" "98FA0510"
+ "15728E5A" "8AACAA68" "FFFFFFFF" "FFFFFFFF",
+ list_to_integer(P, 16).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% RFC8446 - TLS 1.3
+%%% RFC7919 - Negotiated FFDHE for TLS
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% ffdhe2048
+%% ---------
+%% The 2048-bit group has registry value 256 and is calculated from the
+%% following formula:
+%%
+%% The modulus is:
+%%
+%% p = 2^2048 - 2^1984 + {[2^1918 * e] + 560316 } * 2^64 - 1
+%%
+%% The hexadecimal representation of p is:
+%%
+%% FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1
+%% D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9
+%% 7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561
+%% 2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935
+%% 984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735
+%% 30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB
+%% B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19
+%% 0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61
+%% 9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73
+%% 3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA
+%% 886B4238 61285C97 FFFFFFFF FFFFFFFF
+%%
+%% The generator is: g = 2
+%%
+%% The group size is: q = (p-1)/2
+%%
+%% The estimated symmetric-equivalent strength of this group is 103
+%% bits.
+ffdhe2048_generator() ->
+ 2.
+
+ffdhe2048_prime() ->
+ P = "FFFFFFFF" "FFFFFFFF" "ADF85458" "A2BB4A9A" "AFDC5620" "273D3CF1"
+ "D8B9C583" "CE2D3695" "A9E13641" "146433FB" "CC939DCE" "249B3EF9"
+ "7D2FE363" "630C75D8" "F681B202" "AEC4617A" "D3DF1ED5" "D5FD6561"
+ "2433F51F" "5F066ED0" "85636555" "3DED1AF3" "B557135E" "7F57C935"
+ "984F0C70" "E0E68B77" "E2A689DA" "F3EFE872" "1DF158A1" "36ADE735"
+ "30ACCA4F" "483A797A" "BC0AB182" "B324FB61" "D108A94B" "B2C8E3FB"
+ "B96ADAB7" "60D7F468" "1D4F42A3" "DE394DF4" "AE56EDE7" "6372BB19"
+ "0B07A7C8" "EE0A6D70" "9E02FCE1" "CDF7E2EC" "C03404CD" "28342F61"
+ "9172FE9C" "E98583FF" "8E4F1232" "EEF28183" "C3FE3B1B" "4C6FAD73"
+ "3BB5FCBC" "2EC22005" "C58EF183" "7D1683B2" "C6F34A26" "C1B2EFFA"
+ "886B4238" "61285C97" "FFFFFFFF" "FFFFFFFF",
+ list_to_integer(P, 16).
+
+
+%% ffdhe3072
+%% ---------
+%% The 3072-bit prime has registry value 257 and is calculated from the
+%% following formula:
+%%
+%% The modulus is:
+%%
+%% p = 2^3072 - 2^3008 + {[2^2942 * e] + 2625351} * 2^64 - 1
+%%
+%% The hexadecimal representation of p is:
+%%
+%% FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1
+%% D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9
+%% 7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561
+%% 2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935
+%% 984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735
+%% 30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB
+%% B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19
+%% 0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61
+%% 9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73
+%% 3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA
+%% 886B4238 611FCFDC DE355B3B 6519035B BC34F4DE F99C0238
+%% 61B46FC9 D6E6C907 7AD91D26 91F7F7EE 598CB0FA C186D91C
+%% AEFE1309 85139270 B4130C93 BC437944 F4FD4452 E2D74DD3
+%% 64F2E21E 71F54BFF 5CAE82AB 9C9DF69E E86D2BC5 22363A0D
+%% ABC52197 9B0DEADA 1DBF9A42 D5C4484E 0ABCD06B FA53DDEF
+%% 3C1B20EE 3FD59D7C 25E41D2B 66C62E37 FFFFFFFF FFFFFFFF
+%%
+%% The generator is: g = 2
+%%
+%% The group size is: q = (p-1)/2
+%%
+%% The estimated symmetric-equivalent strength of this group is 125
+%% bits.
+ffdhe3072_generator() ->
+ 2.
+
+ffdhe3072_prime() ->
+ P = "FFFFFFFF" "FFFFFFFF" "ADF85458" "A2BB4A9A" "AFDC5620" "273D3CF1"
+ "D8B9C583" "CE2D3695" "A9E13641" "146433FB" "CC939DCE" "249B3EF9"
+ "7D2FE363" "630C75D8" "F681B202" "AEC4617A" "D3DF1ED5" "D5FD6561"
+ "2433F51F" "5F066ED0" "85636555" "3DED1AF3" "B557135E" "7F57C935"
+ "984F0C70" "E0E68B77" "E2A689DA" "F3EFE872" "1DF158A1" "36ADE735"
+ "30ACCA4F" "483A797A" "BC0AB182" "B324FB61" "D108A94B" "B2C8E3FB"
+ "B96ADAB7" "60D7F468" "1D4F42A3" "DE394DF4" "AE56EDE7" "6372BB19"
+ "0B07A7C8" "EE0A6D70" "9E02FCE1" "CDF7E2EC" "C03404CD" "28342F61"
+ "9172FE9C" "E98583FF" "8E4F1232" "EEF28183" "C3FE3B1B" "4C6FAD73"
+ "3BB5FCBC" "2EC22005" "C58EF183" "7D1683B2" "C6F34A26" "C1B2EFFA"
+ "886B4238" "611FCFDC" "DE355B3B" "6519035B" "BC34F4DE" "F99C0238"
+ "61B46FC9" "D6E6C907" "7AD91D26" "91F7F7EE" "598CB0FA" "C186D91C"
+ "AEFE1309" "85139270" "B4130C93" "BC437944" "F4FD4452" "E2D74DD3"
+ "64F2E21E" "71F54BFF" "5CAE82AB" "9C9DF69E" "E86D2BC5" "22363A0D"
+ "ABC52197" "9B0DEADA" "1DBF9A42" "D5C4484E" "0ABCD06B" "FA53DDEF"
+ "3C1B20EE" "3FD59D7C" "25E41D2B" "66C62E37" "FFFFFFFF" "FFFFFFFF",
+ list_to_integer(P, 16).
+
+
+%% ffdhe4096
+%% ---------
+%% The 4096-bit group has registry value 258 and is calculated from the
+%% following formula:
+%%
+%% The modulus is:
+%%
+%% p = 2^4096 - 2^4032 + {[2^3966 * e] + 5736041} * 2^64 - 1
+%%
+%% The hexadecimal representation of p is:
+%%
+%% FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1
+%% D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9
+%% 7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561
+%% 2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935
+%% 984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735
+%% 30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB
+%% B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19
+%% 0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61
+%% 9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73
+%% 3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA
+%% 886B4238 611FCFDC DE355B3B 6519035B BC34F4DE F99C0238
+%% 61B46FC9 D6E6C907 7AD91D26 91F7F7EE 598CB0FA C186D91C
+%% AEFE1309 85139270 B4130C93 BC437944 F4FD4452 E2D74DD3
+%% 64F2E21E 71F54BFF 5CAE82AB 9C9DF69E E86D2BC5 22363A0D
+%% ABC52197 9B0DEADA 1DBF9A42 D5C4484E 0ABCD06B FA53DDEF
+%% 3C1B20EE 3FD59D7C 25E41D2B 669E1EF1 6E6F52C3 164DF4FB
+%% 7930E9E4 E58857B6 AC7D5F42 D69F6D18 7763CF1D 55034004
+%% 87F55BA5 7E31CC7A 7135C886 EFB4318A ED6A1E01 2D9E6832
+%% A907600A 918130C4 6DC778F9 71AD0038 092999A3 33CB8B7A
+%% 1A1DB93D 7140003C 2A4ECEA9 F98D0ACC 0A8291CD CEC97DCF
+%% 8EC9B55A 7F88A46B 4DB5A851 F44182E1 C68A007E 5E655F6A
+%% FFFFFFFF FFFFFFFF
+%%
+%% The generator is: g = 2
+%%
+%% The group size is: q = (p-1)/2
+%%
+%% The estimated symmetric-equivalent strength of this group is 150
+%% bits.
+ffdhe4096_generator() ->
+ 2.
+
+ffdhe4096_prime() ->
+ P = "FFFFFFFF" "FFFFFFFF" "ADF85458" "A2BB4A9A" "AFDC5620" "273D3CF1"
+ "D8B9C583" "CE2D3695" "A9E13641" "146433FB" "CC939DCE" "249B3EF9"
+ "7D2FE363" "630C75D8" "F681B202" "AEC4617A" "D3DF1ED5" "D5FD6561"
+ "2433F51F" "5F066ED0" "85636555" "3DED1AF3" "B557135E" "7F57C935"
+ "984F0C70" "E0E68B77" "E2A689DA" "F3EFE872" "1DF158A1" "36ADE735"
+ "30ACCA4F" "483A797A" "BC0AB182" "B324FB61" "D108A94B" "B2C8E3FB"
+ "B96ADAB7" "60D7F468" "1D4F42A3" "DE394DF4" "AE56EDE7" "6372BB19"
+ "0B07A7C8" "EE0A6D70" "9E02FCE1" "CDF7E2EC" "C03404CD" "28342F61"
+ "9172FE9C" "E98583FF" "8E4F1232" "EEF28183" "C3FE3B1B" "4C6FAD73"
+ "3BB5FCBC" "2EC22005" "C58EF183" "7D1683B2" "C6F34A26" "C1B2EFFA"
+ "886B4238" "611FCFDC" "DE355B3B" "6519035B" "BC34F4DE" "F99C0238"
+ "61B46FC9" "D6E6C907" "7AD91D26" "91F7F7EE" "598CB0FA" "C186D91C"
+ "AEFE1309" "85139270" "B4130C93" "BC437944" "F4FD4452" "E2D74DD3"
+ "64F2E21E" "71F54BFF" "5CAE82AB" "9C9DF69E" "E86D2BC5" "22363A0D"
+ "ABC52197" "9B0DEADA" "1DBF9A42" "D5C4484E" "0ABCD06B" "FA53DDEF"
+ "3C1B20EE" "3FD59D7C" "25E41D2B" "669E1EF1" "6E6F52C3" "164DF4FB"
+ "7930E9E4" "E58857B6" "AC7D5F42" "D69F6D18" "7763CF1D" "55034004"
+ "87F55BA5" "7E31CC7A" "7135C886" "EFB4318A" "ED6A1E01" "2D9E6832"
+ "A907600A" "918130C4" "6DC778F9" "71AD0038" "092999A3" "33CB8B7A"
+ "1A1DB93D" "7140003C" "2A4ECEA9" "F98D0ACC" "0A8291CD" "CEC97DCF"
+ "8EC9B55A" "7F88A46B" "4DB5A851" "F44182E1" "C68A007E" "5E655F6A"
+ "FFFFFFFF" "FFFFFFFF",
+ list_to_integer(P, 16).
+
+
+%% ffdhe6144
+%% ---------
+%% The 6144-bit group has registry value 259 and is calculated from the
+%% following formula:
+%%
+%% The modulus is:
+%%
+%% p = 2^6144 - 2^6080 + {[2^6014 * e] + 15705020} * 2^64 - 1
+%%
+%% The hexadecimal representation of p is:
+%%
+%% FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1
+%% D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9
+%% 7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561
+%% 2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935
+%% 984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735
+%% 30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB
+%% B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19
+%% 0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61
+%% 9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73
+%% 3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA
+%% 886B4238 611FCFDC DE355B3B 6519035B BC34F4DE F99C0238
+%% 61B46FC9 D6E6C907 7AD91D26 91F7F7EE 598CB0FA C186D91C
+%% AEFE1309 85139270 B4130C93 BC437944 F4FD4452 E2D74DD3
+%% 64F2E21E 71F54BFF 5CAE82AB 9C9DF69E E86D2BC5 22363A0D
+%% ABC52197 9B0DEADA 1DBF9A42 D5C4484E 0ABCD06B FA53DDEF
+%% 3C1B20EE 3FD59D7C 25E41D2B 669E1EF1 6E6F52C3 164DF4FB
+%% 7930E9E4 E58857B6 AC7D5F42 D69F6D18 7763CF1D 55034004
+%% 87F55BA5 7E31CC7A 7135C886 EFB4318A ED6A1E01 2D9E6832
+%% A907600A 918130C4 6DC778F9 71AD0038 092999A3 33CB8B7A
+%% 1A1DB93D 7140003C 2A4ECEA9 F98D0ACC 0A8291CD CEC97DCF
+%% 8EC9B55A 7F88A46B 4DB5A851 F44182E1 C68A007E 5E0DD902
+%% 0BFD64B6 45036C7A 4E677D2C 38532A3A 23BA4442 CAF53EA6
+%% 3BB45432 9B7624C8 917BDD64 B1C0FD4C B38E8C33 4C701C3A
+%% CDAD0657 FCCFEC71 9B1F5C3E 4E46041F 388147FB 4CFDB477
+%% A52471F7 A9A96910 B855322E DB6340D8 A00EF092 350511E3
+%% 0ABEC1FF F9E3A26E 7FB29F8C 183023C3 587E38DA 0077D9B4
+%% 763E4E4B 94B2BBC1 94C6651E 77CAF992 EEAAC023 2A281BF6
+%% B3A739C1 22611682 0AE8DB58 47A67CBE F9C9091B 462D538C
+%% D72B0374 6AE77F5E 62292C31 1562A846 505DC82D B854338A
+%% E49F5235 C95B9117 8CCF2DD5 CACEF403 EC9D1810 C6272B04
+%% 5B3B71F9 DC6B80D6 3FDD4A8E 9ADB1E69 62A69526 D43161C1
+%% A41D570D 7938DAD4 A40E329C D0E40E65 FFFFFFFF FFFFFFFF
+%%
+%% The generator is: g = 2
+%%
+%% The group size is: q = (p-1)/2
+%%
+%% The estimated symmetric-equivalent strength of this group is 175
+%% bits.
+ffdhe6144_generator() ->
+ 2.
+
+ffdhe6144_prime() ->
+ P = "FFFFFFFF" "FFFFFFFF" "ADF85458" "A2BB4A9A" "AFDC5620" "273D3CF1"
+ "D8B9C583" "CE2D3695" "A9E13641" "146433FB" "CC939DCE" "249B3EF9"
+ "7D2FE363" "630C75D8" "F681B202" "AEC4617A" "D3DF1ED5" "D5FD6561"
+ "2433F51F" "5F066ED0" "85636555" "3DED1AF3" "B557135E" "7F57C935"
+ "984F0C70" "E0E68B77" "E2A689DA" "F3EFE872" "1DF158A1" "36ADE735"
+ "30ACCA4F" "483A797A" "BC0AB182" "B324FB61" "D108A94B" "B2C8E3FB"
+ "B96ADAB7" "60D7F468" "1D4F42A3" "DE394DF4" "AE56EDE7" "6372BB19"
+ "0B07A7C8" "EE0A6D70" "9E02FCE1" "CDF7E2EC" "C03404CD" "28342F61"
+ "9172FE9C" "E98583FF" "8E4F1232" "EEF28183" "C3FE3B1B" "4C6FAD73"
+ "3BB5FCBC" "2EC22005" "C58EF183" "7D1683B2" "C6F34A26" "C1B2EFFA"
+ "886B4238" "611FCFDC" "DE355B3B" "6519035B" "BC34F4DE" "F99C0238"
+ "61B46FC9" "D6E6C907" "7AD91D26" "91F7F7EE" "598CB0FA" "C186D91C"
+ "AEFE1309" "85139270" "B4130C93" "BC437944" "F4FD4452" "E2D74DD3"
+ "64F2E21E" "71F54BFF" "5CAE82AB" "9C9DF69E" "E86D2BC5" "22363A0D"
+ "ABC52197" "9B0DEADA" "1DBF9A42" "D5C4484E" "0ABCD06B" "FA53DDEF"
+ "3C1B20EE" "3FD59D7C" "25E41D2B" "669E1EF1" "6E6F52C3" "164DF4FB"
+ "7930E9E4" "E58857B6" "AC7D5F42" "D69F6D18" "7763CF1D" "55034004"
+ "87F55BA5" "7E31CC7A" "7135C886" "EFB4318A" "ED6A1E01" "2D9E6832"
+ "A907600A" "918130C4" "6DC778F9" "71AD0038" "092999A3" "33CB8B7A"
+ "1A1DB93D" "7140003C" "2A4ECEA9" "F98D0ACC" "0A8291CD" "CEC97DCF"
+ "8EC9B55A" "7F88A46B" "4DB5A851" "F44182E1" "C68A007E" "5E0DD902"
+ "0BFD64B6" "45036C7A" "4E677D2C" "38532A3A" "23BA4442" "CAF53EA6"
+ "3BB45432" "9B7624C8" "917BDD64" "B1C0FD4C" "B38E8C33" "4C701C3A"
+ "CDAD0657" "FCCFEC71" "9B1F5C3E" "4E46041F" "388147FB" "4CFDB477"
+ "A52471F7" "A9A96910" "B855322E" "DB6340D8" "A00EF092" "350511E3"
+ "0ABEC1FF" "F9E3A26E" "7FB29F8C" "183023C3" "587E38DA" "0077D9B4"
+ "763E4E4B" "94B2BBC1" "94C6651E" "77CAF992" "EEAAC023" "2A281BF6"
+ "B3A739C1" "22611682" "0AE8DB58" "47A67CBE" "F9C9091B" "462D538C"
+ "D72B0374" "6AE77F5E" "62292C31" "1562A846" "505DC82D" "B854338A"
+ "E49F5235" "C95B9117" "8CCF2DD5" "CACEF403" "EC9D1810" "C6272B04"
+ "5B3B71F9" "DC6B80D6" "3FDD4A8E" "9ADB1E69" "62A69526" "D43161C1"
+ "A41D570D" "7938DAD4" "A40E329C" "D0E40E65" "FFFFFFFF" "FFFFFFFF",
+ list_to_integer(P, 16).
+
+
+%% ffdhe8192
+%% ---------
+%% The 8192-bit group has registry value 260 and is calculated from the
+%% following formula:
+%%
+%% The modulus is:
+%%
+%% p = 2^8192 - 2^8128 + {[2^8062 * e] + 10965728} * 2^64 - 1
+%%
+%% The hexadecimal representation of p is:
+%%
+%% FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1
+%% D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9
+%% 7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561
+%% 2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935
+%% 984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735
+%% 30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB
+%% B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19
+%% 0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61
+%% 9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73
+%% 3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA
+%% 886B4238 611FCFDC DE355B3B 6519035B BC34F4DE F99C0238
+%% 61B46FC9 D6E6C907 7AD91D26 91F7F7EE 598CB0FA C186D91C
+%% AEFE1309 85139270 B4130C93 BC437944 F4FD4452 E2D74DD3
+%% 64F2E21E 71F54BFF 5CAE82AB 9C9DF69E E86D2BC5 22363A0D
+%% ABC52197 9B0DEADA 1DBF9A42 D5C4484E 0ABCD06B FA53DDEF
+%% 3C1B20EE 3FD59D7C 25E41D2B 669E1EF1 6E6F52C3 164DF4FB
+%% 7930E9E4 E58857B6 AC7D5F42 D69F6D18 7763CF1D 55034004
+%% 87F55BA5 7E31CC7A 7135C886 EFB4318A ED6A1E01 2D9E6832
+%% A907600A 918130C4 6DC778F9 71AD0038 092999A3 33CB8B7A
+%% 1A1DB93D 7140003C 2A4ECEA9 F98D0ACC 0A8291CD CEC97DCF
+%% 8EC9B55A 7F88A46B 4DB5A851 F44182E1 C68A007E 5E0DD902
+%% 0BFD64B6 45036C7A 4E677D2C 38532A3A 23BA4442 CAF53EA6
+%% 3BB45432 9B7624C8 917BDD64 B1C0FD4C B38E8C33 4C701C3A
+%% CDAD0657 FCCFEC71 9B1F5C3E 4E46041F 388147FB 4CFDB477
+%% A52471F7 A9A96910 B855322E DB6340D8 A00EF092 350511E3
+%% 0ABEC1FF F9E3A26E 7FB29F8C 183023C3 587E38DA 0077D9B4
+%% 763E4E4B 94B2BBC1 94C6651E 77CAF992 EEAAC023 2A281BF6
+%% B3A739C1 22611682 0AE8DB58 47A67CBE F9C9091B 462D538C
+%% D72B0374 6AE77F5E 62292C31 1562A846 505DC82D B854338A
+%% E49F5235 C95B9117 8CCF2DD5 CACEF403 EC9D1810 C6272B04
+%% 5B3B71F9 DC6B80D6 3FDD4A8E 9ADB1E69 62A69526 D43161C1
+%% A41D570D 7938DAD4 A40E329C CFF46AAA 36AD004C F600C838
+%% 1E425A31 D951AE64 FDB23FCE C9509D43 687FEB69 EDD1CC5E
+%% 0B8CC3BD F64B10EF 86B63142 A3AB8829 555B2F74 7C932665
+%% CB2C0F1C C01BD702 29388839 D2AF05E4 54504AC7 8B758282
+%% 2846C0BA 35C35F5C 59160CC0 46FD8251 541FC68C 9C86B022
+%% BB709987 6A460E74 51A8A931 09703FEE 1C217E6C 3826E52C
+%% 51AA691E 0E423CFC 99E9E316 50C1217B 624816CD AD9A95F9
+%% D5B80194 88D9C0A0 A1FE3075 A577E231 83F81D4A 3F2FA457
+%% 1EFC8CE0 BA8A4FE8 B6855DFE 72B0A66E DED2FBAB FBE58A30
+%% FAFABE1C 5D71A87E 2F741EF8 C1FE86FE A6BBFDE5 30677F0D
+%% 97D11D49 F7A8443D 0822E506 A9F4614E 011E2A94 838FF88C
+%% D68C8BB7 C5C6424C FFFFFFFF FFFFFFFF
+%%
+%% The generator is: g = 2
+%%
+%% The group size is: q = (p-1)/2
+%%
+%% The estimated symmetric-equivalent strength of this group is 192
+%% bits.
+ffdhe8192_generator() ->
+ 2.
+
+ffdhe8192_prime() ->
+ P = "FFFFFFFF" "FFFFFFFF" "ADF85458" "A2BB4A9A" "AFDC5620" "273D3CF1"
+ "D8B9C583" "CE2D3695" "A9E13641" "146433FB" "CC939DCE" "249B3EF9"
+ "7D2FE363" "630C75D8" "F681B202" "AEC4617A" "D3DF1ED5" "D5FD6561"
+ "2433F51F" "5F066ED0" "85636555" "3DED1AF3" "B557135E" "7F57C935"
+ "984F0C70" "E0E68B77" "E2A689DA" "F3EFE872" "1DF158A1" "36ADE735"
+ "30ACCA4F" "483A797A" "BC0AB182" "B324FB61" "D108A94B" "B2C8E3FB"
+ "B96ADAB7" "60D7F468" "1D4F42A3" "DE394DF4" "AE56EDE7" "6372BB19"
+ "0B07A7C8" "EE0A6D70" "9E02FCE1" "CDF7E2EC" "C03404CD" "28342F61"
+ "9172FE9C" "E98583FF" "8E4F1232" "EEF28183" "C3FE3B1B" "4C6FAD73"
+ "3BB5FCBC" "2EC22005" "C58EF183" "7D1683B2" "C6F34A26" "C1B2EFFA"
+ "886B4238" "611FCFDC" "DE355B3B" "6519035B" "BC34F4DE" "F99C0238"
+ "61B46FC9" "D6E6C907" "7AD91D26" "91F7F7EE" "598CB0FA" "C186D91C"
+ "AEFE1309" "85139270" "B4130C93" "BC437944" "F4FD4452" "E2D74DD3"
+ "64F2E21E" "71F54BFF" "5CAE82AB" "9C9DF69E" "E86D2BC5" "22363A0D"
+ "ABC52197" "9B0DEADA" "1DBF9A42" "D5C4484E" "0ABCD06B" "FA53DDEF"
+ "3C1B20EE" "3FD59D7C" "25E41D2B" "669E1EF1" "6E6F52C3" "164DF4FB"
+ "7930E9E4" "E58857B6" "AC7D5F42" "D69F6D18" "7763CF1D" "55034004"
+ "87F55BA5" "7E31CC7A" "7135C886" "EFB4318A" "ED6A1E01" "2D9E6832"
+ "A907600A" "918130C4" "6DC778F9" "71AD0038" "092999A3" "33CB8B7A"
+ "1A1DB93D" "7140003C" "2A4ECEA9" "F98D0ACC" "0A8291CD" "CEC97DCF"
+ "8EC9B55A" "7F88A46B" "4DB5A851" "F44182E1" "C68A007E" "5E0DD902"
+ "0BFD64B6" "45036C7A" "4E677D2C" "38532A3A" "23BA4442" "CAF53EA6"
+ "3BB45432" "9B7624C8" "917BDD64" "B1C0FD4C" "B38E8C33" "4C701C3A"
+ "CDAD0657" "FCCFEC71" "9B1F5C3E" "4E46041F" "388147FB" "4CFDB477"
+ "A52471F7" "A9A96910" "B855322E" "DB6340D8" "A00EF092" "350511E3"
+ "0ABEC1FF" "F9E3A26E" "7FB29F8C" "183023C3" "587E38DA" "0077D9B4"
+ "763E4E4B" "94B2BBC1" "94C6651E" "77CAF992" "EEAAC023" "2A281BF6"
+ "B3A739C1" "22611682" "0AE8DB58" "47A67CBE" "F9C9091B" "462D538C"
+ "D72B0374" "6AE77F5E" "62292C31" "1562A846" "505DC82D" "B854338A"
+ "E49F5235" "C95B9117" "8CCF2DD5" "CACEF403" "EC9D1810" "C6272B04"
+ "5B3B71F9" "DC6B80D6" "3FDD4A8E" "9ADB1E69" "62A69526" "D43161C1"
+ "A41D570D" "7938DAD4" "A40E329C" "CFF46AAA" "36AD004C" "F600C838"
+ "1E425A31" "D951AE64" "FDB23FCE" "C9509D43" "687FEB69" "EDD1CC5E"
+ "0B8CC3BD" "F64B10EF" "86B63142" "A3AB8829" "555B2F74" "7C932665"
+ "CB2C0F1C" "C01BD702" "29388839" "D2AF05E4" "54504AC7" "8B758282"
+ "2846C0BA" "35C35F5C" "59160CC0" "46FD8251" "541FC68C" "9C86B022"
+ "BB709987" "6A460E74" "51A8A931" "09703FEE" "1C217E6C" "3826E52C"
+ "51AA691E" "0E423CFC" "99E9E316" "50C1217B" "624816CD" "AD9A95F9"
+ "D5B80194" "88D9C0A0" "A1FE3075" "A577E231" "83F81D4A" "3F2FA457"
+ "1EFC8CE0" "BA8A4FE8" "B6855DFE" "72B0A66E" "DED2FBAB" "FBE58A30"
+ "FAFABE1C" "5D71A87E" "2F741EF8" "C1FE86FE" "A6BBFDE5" "30677F0D"
+ "97D11D49" "F7A8443D" "0822E506" "A9F4614E" "011E2A94" "838FF88C"
+ "D68C8BB7" "C5C6424C" "FFFFFFFF" "FFFFFFFF",
+ list_to_integer(P, 16).
+
+dh_params(ffdhe2048) ->
+ #'DHParameter'{
+ prime = ffdhe2048_prime(),
+ base = ffdhe2048_generator()};
+dh_params(ffdhe3072) ->
+ #'DHParameter'{
+ prime = ffdhe3072_prime(),
+ base = ffdhe3072_generator()};
+dh_params(ffdhe4096) ->
+ #'DHParameter'{
+ prime = ffdhe4096_prime(),
+ base = ffdhe4096_generator()};
+dh_params(ffdhe6144) ->
+ #'DHParameter'{
+ prime = ffdhe6144_prime(),
+ base = ffdhe6144_generator()};
+dh_params(ffdhe8192) ->
+ #'DHParameter'{
+ prime = ffdhe8192_prime(),
+ base = ffdhe8192_generator()}.
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index be0b241017..260f603e90 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -30,6 +30,7 @@
-include("ssl_alert.hrl").
-include("ssl_internal.hrl").
-include("ssl_srp.hrl").
+-include("tls_handshake_1_3.hrl").
-include_lib("public_key/include/public_key.hrl").
-export_type([ssl_handshake/0, ssl_handshake_history/0,
@@ -38,7 +39,7 @@
-type oid() :: tuple().
-type public_key_params() :: #'Dss-Parms'{} | {namedCurve, oid()} | #'ECParameters'{} | term().
-type public_key_info() :: {oid(), #'RSAPublicKey'{} | integer() | #'ECPoint'{}, public_key_params()}.
--type ssl_handshake_history() :: {[binary()], [binary()]}.
+-type ssl_handshake_history() :: {iodata(), iodata()}.
-type ssl_handshake() :: #server_hello{} | #server_hello_done{} | #certificate{} | #certificate_request{} |
#client_key_exchange{} | #finished{} | #certificate_verify{} |
@@ -53,14 +54,14 @@
-export([certify/7, certificate_verify/6, verify_signature/5,
master_secret/4, server_key_exchange_hash/2, verify_connection/6,
init_handshake_history/0, update_handshake_history/2, verify_server_key/5,
- select_version/3, extension_value/1
+ select_version/3, select_supported_version/2, extension_value/1
]).
%% Encode
--export([encode_handshake/2, encode_hello_extensions/1,
+-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_hello_extensions/1,
+-export([decode_handshake/3, decode_vector/1, decode_hello_extensions/4, decode_extensions/3,
decode_server_key/3, decode_client_key/3,
decode_suites/2
]).
@@ -71,13 +72,15 @@
premaster_secret/2, premaster_secret/3, premaster_secret/4]).
%% Extensions handling
--export([client_hello_extensions/5,
+-export([client_hello_extensions/6,
handle_client_hello_extensions/9, %% Returns server hello extensions
handle_server_hello_extensions/9, select_curve/2, select_curve/3,
select_hashsign/4, select_hashsign/5,
- select_hashsign_algs/3
+ select_hashsign_algs/3, empty_extensions/2, add_server_share/3
]).
+-export([get_cert_params/1]).
+
%%====================================================================
%% Create handshake messages
%%====================================================================
@@ -93,7 +96,7 @@ hello_request() ->
%%--------------------------------------------------------------------
-spec server_hello(#session{}, ssl_record:ssl_version(), ssl_record:connection_states(),
- #hello_extensions{}) -> #server_hello{}.
+ Extension::map()) -> #server_hello{}.
%%
%% Description: Creates a server hello message.
%%--------------------------------------------------------------------
@@ -504,6 +507,21 @@ verify_server_key(#server_key_params{params_bin = EncParams,
select_version(RecordCB, ClientVersion, Versions) ->
do_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)
+ end.
+
+
%%====================================================================
%% Encode handshake
%%====================================================================
@@ -517,7 +535,7 @@ encode_handshake(#server_hello{server_version = {Major, Minor},
session_id = Session_ID,
cipher_suite = CipherSuite,
compression_method = Comp_method,
- extensions = #hello_extensions{} = Extensions}, _Version) ->
+ extensions = Extensions}, _Version) ->
SID_length = byte_size(Session_ID),
ExtensionsBin = encode_hello_extensions(Extensions),
{?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
@@ -567,71 +585,126 @@ encode_handshake(#certificate_verify{signature = BinSig, hashsign_algorithm = Ha
encode_handshake(#finished{verify_data = VerifyData}, _Version) ->
{?FINISHED, VerifyData}.
-encode_hello_extensions(#hello_extensions{} = Extensions) ->
- encode_hello_extensions(hello_extensions_list(Extensions), <<>>).
-encode_hello_extensions([], <<>>) ->
- <<>>;
-encode_hello_extensions([], Acc) ->
+encode_hello_extensions(Extensions) ->
+ encode_extensions(hello_extensions_list(Extensions), <<>>).
+
+encode_extensions(Exts) ->
+ encode_extensions(Exts, <<>>).
+
+encode_extensions([], <<>>) ->
+ <<?UINT16(0)>>;
+encode_extensions([], Acc) ->
Size = byte_size(Acc),
<<?UINT16(Size), Acc/binary>>;
-
-encode_hello_extensions([#alpn{extension_data = ExtensionData} | Rest], Acc) ->
- Len = byte_size(ExtensionData),
+encode_extensions([#alpn{extension_data = ExtensionData} | Rest], Acc) ->
+ Len = byte_size(ExtensionData),
ExtLen = Len + 2,
- encode_hello_extensions(Rest, <<?UINT16(?ALPN_EXT), ?UINT16(ExtLen), ?UINT16(Len),
- ExtensionData/binary, Acc/binary>>);
-encode_hello_extensions([#next_protocol_negotiation{extension_data = ExtensionData} | Rest], Acc) ->
+ encode_extensions(Rest, <<?UINT16(?ALPN_EXT), ?UINT16(ExtLen), ?UINT16(Len),
+ ExtensionData/binary, Acc/binary>>);
+encode_extensions([#next_protocol_negotiation{extension_data = ExtensionData} | Rest], Acc) ->
Len = byte_size(ExtensionData),
- encode_hello_extensions(Rest, <<?UINT16(?NEXTPROTONEG_EXT), ?UINT16(Len),
+ encode_extensions(Rest, <<?UINT16(?NEXTPROTONEG_EXT), ?UINT16(Len),
ExtensionData/binary, Acc/binary>>);
-encode_hello_extensions([#renegotiation_info{renegotiated_connection = undefined} | Rest], Acc) ->
- encode_hello_extensions(Rest, Acc);
-encode_hello_extensions([#renegotiation_info{renegotiated_connection = ?byte(0) = Info} | Rest], Acc) ->
+encode_extensions([#renegotiation_info{renegotiated_connection = undefined} | Rest], Acc) ->
+ encode_extensions(Rest, Acc);
+encode_extensions([#renegotiation_info{renegotiated_connection = ?byte(0) = Info} | Rest], Acc) ->
Len = byte_size(Info),
- encode_hello_extensions(Rest, <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), Info/binary, Acc/binary>>);
+ encode_extensions(Rest, <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), Info/binary, Acc/binary>>);
-encode_hello_extensions([#renegotiation_info{renegotiated_connection = Info} | Rest], Acc) ->
+encode_extensions([#renegotiation_info{renegotiated_connection = Info} | Rest], Acc) ->
InfoLen = byte_size(Info),
Len = InfoLen +1,
- encode_hello_extensions(Rest, <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), ?BYTE(InfoLen),
+ encode_extensions(Rest, <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), ?BYTE(InfoLen),
Info/binary, Acc/binary>>);
-encode_hello_extensions([#elliptic_curves{elliptic_curve_list = EllipticCurves} | Rest], Acc) ->
+encode_extensions([#elliptic_curves{elliptic_curve_list = EllipticCurves} | Rest], Acc) ->
EllipticCurveList = << <<(tls_v1:oid_to_enum(X)):16>> || X <- EllipticCurves>>,
ListLen = byte_size(EllipticCurveList),
Len = ListLen + 2,
- encode_hello_extensions(Rest, <<?UINT16(?ELLIPTIC_CURVES_EXT),
+ encode_extensions(Rest, <<?UINT16(?ELLIPTIC_CURVES_EXT),
?UINT16(Len), ?UINT16(ListLen), EllipticCurveList/binary, Acc/binary>>);
-encode_hello_extensions([#ec_point_formats{ec_point_format_list = ECPointFormats} | Rest], Acc) ->
+encode_extensions([#supported_groups{supported_groups = SupportedGroups} | Rest], Acc) ->
+
+ SupportedGroupList = << <<(tls_v1:group_to_enum(X)):16>> || X <- SupportedGroups>>,
+ ListLen = byte_size(SupportedGroupList),
+ Len = ListLen + 2,
+ encode_extensions(Rest, <<?UINT16(?ELLIPTIC_CURVES_EXT),
+ ?UINT16(Len), ?UINT16(ListLen),
+ SupportedGroupList/binary, Acc/binary>>);
+encode_extensions([#ec_point_formats{ec_point_format_list = ECPointFormats} | Rest], Acc) ->
ECPointFormatList = list_to_binary(ECPointFormats),
ListLen = byte_size(ECPointFormatList),
Len = ListLen + 1,
- encode_hello_extensions(Rest, <<?UINT16(?EC_POINT_FORMATS_EXT),
+ encode_extensions(Rest, <<?UINT16(?EC_POINT_FORMATS_EXT),
?UINT16(Len), ?BYTE(ListLen), ECPointFormatList/binary, Acc/binary>>);
-encode_hello_extensions([#srp{username = UserName} | Rest], Acc) ->
+encode_extensions([#srp{username = UserName} | Rest], Acc) ->
SRPLen = byte_size(UserName),
Len = SRPLen + 1,
- encode_hello_extensions(Rest, <<?UINT16(?SRP_EXT), ?UINT16(Len), ?BYTE(SRPLen),
+ encode_extensions(Rest, <<?UINT16(?SRP_EXT), ?UINT16(Len), ?BYTE(SRPLen),
UserName/binary, Acc/binary>>);
-encode_hello_extensions([#hash_sign_algos{hash_sign_algos = HashSignAlgos} | Rest], Acc) ->
+encode_extensions([#hash_sign_algos{hash_sign_algos = HashSignAlgos} | Rest], Acc) ->
SignAlgoList = << <<(ssl_cipher:hash_algorithm(Hash)):8, (ssl_cipher:sign_algorithm(Sign)):8>> ||
{Hash, Sign} <- HashSignAlgos >>,
ListLen = byte_size(SignAlgoList),
Len = ListLen + 2,
- encode_hello_extensions(Rest, <<?UINT16(?SIGNATURE_ALGORITHMS_EXT),
+ encode_extensions(Rest, <<?UINT16(?SIGNATURE_ALGORITHMS_EXT),
?UINT16(Len), ?UINT16(ListLen), SignAlgoList/binary, Acc/binary>>);
-encode_hello_extensions([#sni{hostname = Hostname} | Rest], Acc) ->
+encode_extensions([#signature_algorithms{
+ signature_scheme_list = SignatureSchemes} | Rest], Acc) ->
+ SignSchemeList = << <<(ssl_cipher:signature_scheme(SignatureScheme)):16 >> ||
+ SignatureScheme <- SignatureSchemes >>,
+ ListLen = byte_size(SignSchemeList),
+ Len = ListLen + 2,
+ encode_extensions(Rest, <<?UINT16(?SIGNATURE_ALGORITHMS_EXT),
+ ?UINT16(Len), ?UINT16(ListLen), SignSchemeList/binary, Acc/binary>>);
+encode_extensions([#signature_algorithms_cert{
+ signature_scheme_list = SignatureSchemes} | Rest], Acc) ->
+ SignSchemeList = << <<(ssl_cipher:signature_scheme(SignatureScheme)):16 >> ||
+ SignatureScheme <- SignatureSchemes >>,
+ ListLen = byte_size(SignSchemeList),
+ Len = ListLen + 2,
+ encode_extensions(Rest, <<?UINT16(?SIGNATURE_ALGORITHMS_CERT_EXT),
+ ?UINT16(Len), ?UINT16(ListLen), SignSchemeList/binary, Acc/binary>>);
+encode_extensions([#sni{hostname = Hostname} | Rest], Acc) ->
HostLen = length(Hostname),
HostnameBin = list_to_binary(Hostname),
% Hostname type (1 byte) + Hostname length (2 bytes) + Hostname (HostLen bytes)
ServerNameLength = 1 + 2 + HostLen,
% ServerNameListSize (2 bytes) + ServerNameLength
ExtLength = 2 + ServerNameLength,
- encode_hello_extensions(Rest, <<?UINT16(?SNI_EXT), ?UINT16(ExtLength),
- ?UINT16(ServerNameLength),
- ?BYTE(?SNI_NAMETYPE_HOST_NAME),
- ?UINT16(HostLen), HostnameBin/binary,
- Acc/binary>>).
+ encode_extensions(Rest, <<?UINT16(?SNI_EXT), ?UINT16(ExtLength),
+ ?UINT16(ServerNameLength),
+ ?BYTE(?SNI_NAMETYPE_HOST_NAME),
+ ?UINT16(HostLen), HostnameBin/binary,
+ Acc/binary>>);
+encode_extensions([#client_hello_versions{versions = Versions0} | Rest], Acc) ->
+ Versions = encode_versions(Versions0),
+ VerLen = byte_size(Versions),
+ Len = VerLen + 1,
+ encode_extensions(Rest, <<?UINT16(?SUPPORTED_VERSIONS_EXT),
+ ?UINT16(Len), ?BYTE(VerLen), Versions/binary, Acc/binary>>);
+encode_extensions([#server_hello_selected_version{selected_version = Version0} | Rest], Acc) ->
+ Version = encode_versions([Version0]),
+ Len = byte_size(Version), %% 2
+ encode_extensions(Rest, <<?UINT16(?SUPPORTED_VERSIONS_EXT),
+ ?UINT16(Len), Version/binary, Acc/binary>>);
+encode_extensions([#key_share_client_hello{client_shares = ClientShares0} | Rest], Acc) ->
+ ClientShares = encode_client_shares(ClientShares0),
+ ClientSharesLen = byte_size(ClientShares),
+ Len = ClientSharesLen + 2,
+ encode_extensions(Rest, <<?UINT16(?KEY_SHARE_EXT),
+ ?UINT16(Len), ?UINT16(ClientSharesLen),
+ ClientShares/binary, Acc/binary>>);
+encode_extensions([#key_share_server_hello{server_share = ServerShare0} | Rest], Acc) ->
+ ServerShare = encode_key_share_entry(ServerShare0),
+ Len = byte_size(ServerShare),
+ encode_extensions(Rest, <<?UINT16(?KEY_SHARE_EXT),
+ ?UINT16(Len), ServerShare/binary, Acc/binary>>);
+encode_extensions([#key_share_hello_retry_request{selected_group = Group0} | Rest], Acc) ->
+ Group = tls_v1:group_to_enum(Group0),
+ encode_extensions(Rest, <<?UINT16(?KEY_SHARE_EXT),
+ ?UINT16(2), ?UINT16(Group), Acc/binary>>).
+
encode_client_protocol_negotiation(undefined, _) ->
undefined;
@@ -657,7 +730,7 @@ decode_handshake(_, ?NEXT_PROTOCOL, <<?BYTE(SelectedProtocolLength),
?BYTE(PaddingLength), _Padding:PaddingLength/binary>>) ->
#next_protocol{selected_protocol = SelectedProtocol};
-decode_handshake(_Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
+decode_handshake(Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SID_length), Session_ID:SID_length/binary,
Cipher_suite:2/binary, ?BYTE(Comp_method)>>) ->
#server_hello{
@@ -666,14 +739,13 @@ decode_handshake(_Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:3
session_id = Session_ID,
cipher_suite = Cipher_suite,
compression_method = Comp_method,
- extensions = #hello_extensions{}};
+ extensions = empty_extensions(Version, server_hello)};
-decode_handshake(_Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
+decode_handshake(Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SID_length), Session_ID:SID_length/binary,
Cipher_suite:2/binary, ?BYTE(Comp_method),
?UINT16(ExtLen), Extensions:ExtLen/binary>>) ->
-
- HelloExtensions = decode_hello_extensions(Extensions),
+ HelloExtensions = decode_hello_extensions(Extensions, Version, {Major, Minor}, server_hello),
#server_hello{
server_version = {Major,Minor},
@@ -716,20 +788,52 @@ decode_handshake(_Version, ?FINISHED, VerifyData) ->
decode_handshake(_, Message, _) ->
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {unknown_or_malformed_handshake, Message})).
+
+%%--------------------------------------------------------------------
+-spec decode_vector(binary()) -> binary().
+%%
+%% Description: Remove length tag from TLS Vector type. Needed
+%% for client hello when extensions in older versions may be empty.
+%%
+%%--------------------------------------------------------------------
+decode_vector(<<>>) ->
+ <<>>;
+decode_vector(<<?UINT16(Len), Vector:Len/binary>>) ->
+ Vector.
+
+%%--------------------------------------------------------------------
+-spec decode_hello_extensions(binary(), ssl_record:ssl_version(),
+ ssl_record:ssl_version(), atom()) -> map().
+%%
+%% Description: Decodes TLS hello extensions
+%%--------------------------------------------------------------------
+decode_hello_extensions(Extensions, LocalVersion, LegacyVersion, MessageType0) ->
+ %% Convert legacy atoms
+ MessageType =
+ case MessageType0 of
+ client -> client_hello;
+ server -> server_hello;
+ T -> T
+ end,
+ %% RFC 8446 - 4.2.1
+ %% Servers MUST be prepared to receive ClientHellos that include this extension but
+ %% do not include 0x0304 in the list of versions.
+ %% Clients MUST check for this extension prior to processing the rest of the
+ %% ServerHello (although they will have to parse the ServerHello in order to read
+ %% the extension).
+ Version = process_supported_versions_extension(Extensions, LocalVersion, LegacyVersion),
+ decode_extensions(Extensions, Version, MessageType, empty_extensions(Version, MessageType)).
+
%%--------------------------------------------------------------------
--spec decode_hello_extensions({client, binary()} | binary()) -> #hello_extensions{}.
+-spec decode_extensions(binary(),tuple(), atom()) -> map().
%%
%% Description: Decodes TLS hello extensions
%%--------------------------------------------------------------------
-decode_hello_extensions({client, <<>>}) ->
- #hello_extensions{};
-decode_hello_extensions({client, <<?UINT16(ExtLen), Extensions:ExtLen/binary>>}) ->
- decode_hello_extensions(Extensions);
-decode_hello_extensions(Extensions) ->
- dec_hello_extensions(Extensions, #hello_extensions{}).
+decode_extensions(Extensions, Version, MessageType) ->
+ decode_extensions(Extensions, Version, MessageType, empty_extensions()).
%%--------------------------------------------------------------------
--spec decode_server_key(binary(), ssl_cipher_format:key_algo(), ssl_record:ssl_version()) ->
+-spec decode_server_key(binary(), ssl:kex_algo(), ssl_record:ssl_version()) ->
#server_key_params{}.
%%
%% Description: Decode server_key data and return appropriate type
@@ -738,7 +842,7 @@ decode_server_key(ServerKey, Type, Version) ->
dec_server_key(ServerKey, key_exchange_alg(Type), Version).
%%--------------------------------------------------------------------
--spec decode_client_key(binary(), ssl_cipher_format:key_algo(), ssl_record:ssl_version()) ->
+-spec decode_client_key(binary(), ssl:kex_algo(), ssl_record:ssl_version()) ->
#encrypted_premaster_secret{}
| #client_diffie_hellman_public{}
| #client_ec_diffie_hellman_public{}
@@ -936,98 +1040,218 @@ premaster_secret(EncSecret, #{algorithm := rsa} = Engine) ->
%%====================================================================
%% Extensions handling
%%====================================================================
-client_hello_extensions(Version, CipherSuites,
- #ssl_options{signature_algs = SupportedHashSigns,
- eccs = SupportedECCs} = SslOpts, ConnectionStates, Renegotiation) ->
- {EcPointFormats, EllipticCurves} =
- case advertises_ec_ciphers(lists:map(fun ssl_cipher_format:suite_definition/1, CipherSuites)) of
- true ->
- client_ecc_extensions(SupportedECCs);
- false ->
- {undefined, undefined}
- end,
+client_hello_extensions(Version, CipherSuites, SslOpts, ConnectionStates, Renegotiation, KeyShare) ->
+ HelloExtensions0 = add_tls12_extensions(Version, SslOpts, ConnectionStates, Renegotiation),
+ HelloExtensions1 = add_common_extensions(Version, HelloExtensions0, CipherSuites, SslOpts),
+ maybe_add_tls13_extensions(Version, HelloExtensions1, SslOpts, KeyShare).
+
+
+add_tls12_extensions(_Version,
+ SslOpts,
+ ConnectionStates,
+ Renegotiation) ->
SRP = srp_user(SslOpts),
+ #{renegotiation_info => renegotiation_info(tls_record, client,
+ ConnectionStates, Renegotiation),
+ srp => SRP,
+ alpn => encode_alpn(SslOpts#ssl_options.alpn_advertised_protocols, Renegotiation),
+ next_protocol_negotiation =>
+ encode_client_protocol_negotiation(SslOpts#ssl_options.next_protocol_selector,
+ Renegotiation),
+ sni => sni(SslOpts#ssl_options.server_name_indication)
+ }.
+
- #hello_extensions{
- renegotiation_info = renegotiation_info(tls_record, client,
- ConnectionStates, Renegotiation),
- srp = SRP,
- signature_algs = available_signature_algs(SupportedHashSigns, Version),
- ec_point_formats = EcPointFormats,
- elliptic_curves = EllipticCurves,
- alpn = encode_alpn(SslOpts#ssl_options.alpn_advertised_protocols, Renegotiation),
- next_protocol_negotiation =
- encode_client_protocol_negotiation(SslOpts#ssl_options.next_protocol_selector,
- Renegotiation),
- sni = sni(SslOpts#ssl_options.server_name_indication)}.
+add_common_extensions({3,4},
+ HelloExtensions,
+ _CipherSuites,
+ #ssl_options{eccs = SupportedECCs,
+ supported_groups = Groups,
+ signature_algs = SignatureSchemes}) ->
+ {EcPointFormats, _} =
+ client_ecc_extensions(SupportedECCs),
+ HelloExtensions#{ec_point_formats => EcPointFormats,
+ elliptic_curves => Groups,
+ signature_algs => signature_algs_ext(SignatureSchemes)};
+
+add_common_extensions(Version,
+ HelloExtensions,
+ CipherSuites,
+ #ssl_options{eccs = SupportedECCs,
+ signature_algs = SupportedHashSigns}) ->
+
+ {EcPointFormats, EllipticCurves} =
+ case advertises_ec_ciphers(
+ lists:map(fun ssl_cipher_format:suite_definition/1,
+ CipherSuites)) of
+ true ->
+ client_ecc_extensions(SupportedECCs);
+ false ->
+ {undefined, undefined}
+ end,
+ HelloExtensions#{ec_point_formats => EcPointFormats,
+ elliptic_curves => EllipticCurves,
+ signature_algs => available_signature_algs(SupportedHashSigns, Version)}.
+
+
+maybe_add_tls13_extensions({3,4},
+ HelloExtensions0,
+ #ssl_options{signature_algs_cert = SignatureSchemes,
+ versions = SupportedVersions},
+ KeyShare) ->
+ HelloExtensions =
+ HelloExtensions0#{client_hello_versions =>
+ #client_hello_versions{versions = SupportedVersions},
+ signature_algs_cert =>
+ signature_algs_cert(SignatureSchemes)},
+ maybe_add_key_share(HelloExtensions, KeyShare);
+maybe_add_tls13_extensions(_, HelloExtensions, _, _) ->
+ HelloExtensions.
+
+
+%% TODO: Add support for PSK key establishment
+
+%% RFC 8446 (TLS 1.3) - 4.2.8. Key Share
+%%
+%% 4.2.8.1. Diffie-Hellman Parameters
+%% Diffie-Hellman [DH76] parameters for both clients and servers are
+%% encoded in the opaque key_exchange field of a KeyShareEntry in a
+%% KeyShare structure. The opaque value contains the Diffie-Hellman
+%% public value (Y = g^X mod p) for the specified group (see [RFC7919]
+%% for group definitions) encoded as a big-endian integer and padded to
+%% the left with zeros to the size of p in bytes.
+%%
+%% 4.2.8.2. ECDHE Parameters
+%%
+%% ECDHE parameters for both clients and servers are encoded in the
+%% opaque key_exchange field of a KeyShareEntry in a KeyShare structure.
+%%
+%% For secp256r1, secp384r1, and secp521r1, the contents are the
+%% serialized value of the following struct:
+%%
+%% struct {
+%% uint8 legacy_form = 4;
+%% opaque X[coordinate_length];
+%% opaque Y[coordinate_length];
+%% } UncompressedPointRepresentation;
+%%
+%% X and Y, respectively, are the binary representations of the x and y
+%% values in network byte order. There are no internal length markers,
+%% so each number representation occupies as many octets as implied by
+%% the curve parameters. For P-256, this means that each of X and Y use
+%% 32 octets, padded on the left by zeros if necessary. For P-384, they
+%% take 48 octets each. For P-521, they take 66 octets each.
+maybe_add_key_share(HelloExtensions, undefined) ->
+ HelloExtensions;
+maybe_add_key_share(HelloExtensions, KeyShare) ->
+ #key_share_client_hello{client_shares = ClientShares0} = KeyShare,
+ %% Keep only public keys
+ ClientShares = lists:map(fun kse_remove_private_key/1, ClientShares0),
+ HelloExtensions#{key_share => #key_share_client_hello{
+ client_shares = ClientShares}}.
+
+add_server_share(server_hello, Extensions, KeyShare) ->
+ #key_share_server_hello{server_share = ServerShare0} = KeyShare,
+ %% Keep only public keys
+ ServerShare = kse_remove_private_key(ServerShare0),
+ Extensions#{key_share => #key_share_server_hello{
+ server_share = ServerShare}};
+add_server_share(hello_retry_request, Extensions,
+ #key_share_server_hello{
+ server_share = #key_share_entry{group = Group}}) ->
+ Extensions#{key_share => #key_share_hello_retry_request{
+ selected_group = Group}}.
+
+
+kse_remove_private_key(#key_share_entry{
+ group = Group,
+ key_exchange =
+ #'ECPrivateKey'{publicKey = PublicKey}}) ->
+ #key_share_entry{
+ group = Group,
+ key_exchange = PublicKey};
+kse_remove_private_key(#key_share_entry{
+ group = Group,
+ key_exchange =
+ {PublicKey, _}}) ->
+ #key_share_entry{
+ group = Group,
+ key_exchange = PublicKey}.
+
+signature_algs_ext(undefined) ->
+ undefined;
+signature_algs_ext(SignatureSchemes0) ->
+ %% The SSL option signature_algs contains both hash-sign algorithms (tuples) and
+ %% signature schemes (atoms) if TLS 1.3 is configured.
+ %% Filter out all hash-sign tuples when creating the signature_algs extension.
+ %% (TLS 1.3 specific record type)
+ SignatureSchemes = lists:filter(fun is_atom/1, SignatureSchemes0),
+ #signature_algorithms{signature_scheme_list = SignatureSchemes}.
+
+signature_algs_cert(undefined) ->
+ undefined;
+signature_algs_cert(SignatureSchemes) ->
+ #signature_algorithms_cert{signature_scheme_list = SignatureSchemes}.
handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites,
- #hello_extensions{renegotiation_info = Info,
- srp = SRP,
- ec_point_formats = ECCFormat,
- alpn = ALPN,
- next_protocol_negotiation = NextProtocolNegotiation}, Version,
+ Exts, Version,
#ssl_options{secure_renegotiate = SecureRenegotation,
alpn_preferred_protocols = ALPNPreferredProtocols} = Opts,
#session{cipher_suite = NegotiatedCipherSuite,
compression_method = Compression} = Session0,
ConnectionStates0, Renegotiation) ->
- Session = handle_srp_extension(SRP, Session0),
- ConnectionStates = handle_renegotiation_extension(server, RecordCB, Version, Info,
+ Session = handle_srp_extension(maps:get(srp, Exts, undefined), Session0),
+ ConnectionStates = handle_renegotiation_extension(server, RecordCB, Version, maps:get(renegotiation_info, Exts, undefined),
Random, NegotiatedCipherSuite,
ClientCipherSuites, Compression,
ConnectionStates0, Renegotiation, SecureRenegotation),
- ServerHelloExtensions = #hello_extensions{
- renegotiation_info = renegotiation_info(RecordCB, server,
- ConnectionStates, Renegotiation),
- ec_point_formats = server_ecc_extension(Version, ECCFormat)
- },
-
+ Empty = empty_extensions(Version, server_hello),
+ ServerHelloExtensions = Empty#{renegotiation_info => renegotiation_info(RecordCB, server,
+ ConnectionStates, Renegotiation),
+ ec_point_formats => server_ecc_extension(Version,
+ maps:get(ec_point_formats, Exts, undefined))
+ },
+
%% If we receive an ALPN extension and have ALPN configured for this connection,
%% we handle it. Otherwise we check for the NPN extension.
+ ALPN = maps:get(alpn, Exts, undefined),
if
ALPN =/= undefined, ALPNPreferredProtocols =/= undefined ->
- case handle_alpn_extension(ALPNPreferredProtocols, decode_alpn(ALPN)) of
- #alert{} = Alert ->
- Alert;
- Protocol ->
- {Session, ConnectionStates, Protocol,
- ServerHelloExtensions#hello_extensions{alpn=encode_alpn([Protocol], Renegotiation)}}
- end;
+ Protocol = handle_alpn_extension(ALPNPreferredProtocols, decode_alpn(ALPN)),
+ {Session, ConnectionStates, Protocol,
+ ServerHelloExtensions#{alpn => encode_alpn([Protocol], Renegotiation)}};
true ->
+ NextProtocolNegotiation = maps:get(next_protocol_negotiation, Exts, undefined),
ProtocolsToAdvertise = handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, Opts),
{Session, ConnectionStates, undefined,
- ServerHelloExtensions#hello_extensions{next_protocol_negotiation=
- encode_protocols_advertised_on_server(ProtocolsToAdvertise)}}
+ ServerHelloExtensions#{next_protocol_negotiation =>
+ encode_protocols_advertised_on_server(ProtocolsToAdvertise)}}
end.
handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression,
- #hello_extensions{renegotiation_info = Info,
- alpn = ALPN,
- next_protocol_negotiation = NextProtocolNegotiation}, Version,
+ Exts, Version,
#ssl_options{secure_renegotiate = SecureRenegotation,
next_protocol_selector = NextProtoSelector},
ConnectionStates0, Renegotiation) ->
- ConnectionStates = handle_renegotiation_extension(client, RecordCB, Version, Info, Random,
+ ConnectionStates = handle_renegotiation_extension(client, RecordCB, Version,
+ maps:get(renegotiation_info, Exts, undefined), Random,
CipherSuite, undefined,
Compression, ConnectionStates0,
Renegotiation, SecureRenegotation),
%% If we receive an ALPN extension then this is the protocol selected,
%% otherwise handle the NPN extension.
+ ALPN = maps:get(alpn, Exts, undefined),
case decode_alpn(ALPN) of
%% ServerHello contains exactly one protocol: the one selected.
%% We also ignore the ALPN extension during renegotiation (see encode_alpn/2).
[Protocol] when not Renegotiation ->
{ConnectionStates, alpn, Protocol};
undefined ->
- case handle_next_protocol(NextProtocolNegotiation, NextProtoSelector, Renegotiation) of
- #alert{} = Alert ->
- Alert;
- Protocol ->
- {ConnectionStates, npn, Protocol}
- end;
+ NextProtocolNegotiation = maps:get(next_protocol_negotiation, Exts, undefined),
+ Protocol = handle_next_protocol(NextProtocolNegotiation, NextProtoSelector, Renegotiation),
+ {ConnectionStates, npn, Protocol};
{error, Reason} ->
?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason);
[] ->
@@ -1070,26 +1294,50 @@ select_hashsign(_, _, KeyExAlgo, _, _Version) when KeyExAlgo == dh_anon;
{null, anon};
%% The signature_algorithms extension was introduced with TLS 1.2. Ignore it if we have
%% negotiated a lower version.
-select_hashsign(HashSigns, Cert, KeyExAlgo,
- undefined, {Major, Minor} = Version) when Major >= 3 andalso Minor >= 3->
- select_hashsign(HashSigns, Cert, KeyExAlgo, tls_v1:default_signature_algs(Version), Version);
-select_hashsign(#hash_sign_algos{hash_sign_algos = HashSigns}, Cert, KeyExAlgo, SupportedHashSigns,
- {Major, Minor}) when Major >= 3 andalso Minor >= 3 ->
- #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp),
- #'OTPSubjectPublicKeyInfo'{algorithm = {_, SubjAlgo, _}} =
- TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
-
- SubSign = sign_algo(SubjAlgo),
-
- case lists:filter(fun({_, S} = Algos) when S == SubSign ->
- is_acceptable_hash_sign(Algos, KeyExAlgo, SupportedHashSigns);
- (_) ->
- false
- end, HashSigns) of
- [] ->
- ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm);
- [HashSign | _] ->
- HashSign
+select_hashsign({ClientHashSigns, ClientSignatureSchemes},
+ Cert, KeyExAlgo, undefined, {Major, Minor} = Version)
+ when Major >= 3 andalso Minor >= 3->
+ 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, {Major, Minor})
+ when Major >= 3 andalso Minor >= 3 ->
+ ClientSignatureSchemes = get_signature_scheme(ClientSignatureSchemes0),
+ {SignAlgo0, Param, PublicKeyAlgo0} = get_cert_params(Cert),
+ SignAlgo = sign_algo(SignAlgo0),
+ PublicKeyAlgo = public_key_algo(PublicKeyAlgo0),
+
+ %% RFC 5246 (TLS 1.2)
+ %% If the client provided a "signature_algorithms" extension, then all
+ %% certificates provided by the server MUST be signed by a
+ %% hash/signature algorithm pair that appears in that extension.
+ %%
+ %% RFC 8446 (TLS 1.3)
+ %% TLS 1.3 provides two extensions for indicating which signature
+ %% algorithms may be used in digital signatures. The
+ %% "signature_algorithms_cert" extension applies to signatures in
+ %% certificates and the "signature_algorithms" extension, which
+ %% originally appeared in TLS 1.2, applies to signatures in
+ %% CertificateVerify messages.
+ %%
+ %% If no "signature_algorithms_cert" extension is
+ %% present, then the "signature_algorithms" extension also applies to
+ %% signatures appearing in certificates.
+ case is_supported_sign(SignAlgo, Param, ClientHashSigns, ClientSignatureSchemes) of
+ true ->
+ case lists:filter(fun({_, S} = Algos) when S == PublicKeyAlgo ->
+ is_acceptable_hash_sign(Algos, KeyExAlgo, SupportedHashSigns);
+ (_) ->
+ false
+ end, ClientHashSigns) of
+ [] ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm);
+ [HashSign | _] ->
+ HashSign
+ end;
+ false ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm)
end;
select_hashsign(_, Cert, _, _, Version) ->
#'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp),
@@ -1103,21 +1351,23 @@ select_hashsign(_, Cert, _, _, Version) ->
%%
%% Description: Handles signature algorithms selection for certificate requests (client)
%%--------------------------------------------------------------------
-select_hashsign(#certificate_request{hashsign_algorithms = #hash_sign_algos{hash_sign_algos = HashSigns},
- certificate_types = Types}, Cert, SupportedHashSigns,
+select_hashsign(#certificate_request{
+ hashsign_algorithms = #hash_sign_algos{
+ hash_sign_algos = HashSigns},
+ certificate_types = Types},
+ Cert,
+ SupportedHashSigns,
{Major, Minor}) when Major >= 3 andalso Minor >= 3->
- #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp),
- #'OTPCertificate'{tbsCertificate = TBSCert,
- signatureAlgorithm = {_,SignAlgo, _}} = public_key:pkix_decode_cert(Cert, otp),
- #'OTPSubjectPublicKeyInfo'{algorithm = {_, SubjAlgo, _}} =
- TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
-
- Sign = sign_algo(SignAlgo),
- SubSign = sign_algo(SubjAlgo),
-
- case is_acceptable_cert_type(SubSign, HashSigns, Types) andalso is_supported_sign(Sign, HashSigns) of
+ {SignAlgo0, Param, PublicKeyAlgo0} = get_cert_params(Cert),
+ SignAlgo = sign_algo(SignAlgo0),
+ PublicKeyAlgo = public_key_algo(PublicKeyAlgo0),
+
+ case is_acceptable_cert_type(PublicKeyAlgo, Types) andalso
+ %% certificate_request has no "signature_algorithms_cert"
+ %% extension in TLS 1.2.
+ is_supported_sign(SignAlgo, Param, HashSigns, undefined) of
true ->
- case lists:filter(fun({_, S} = Algos) when S == SubSign ->
+ case lists:filter(fun({_, S} = Algos) when S == PublicKeyAlgo ->
is_acceptable_hash_sign(Algos, SupportedHashSigns);
(_) ->
false
@@ -1130,8 +1380,38 @@ select_hashsign(#certificate_request{hashsign_algorithms = #hash_sign_algos{hash
false ->
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm)
end;
-select_hashsign(#certificate_request{}, Cert, _, Version) ->
- select_hashsign(undefined, Cert, undefined, [], Version).
+select_hashsign(#certificate_request{certificate_types = Types}, Cert, _, Version) ->
+ {_, _, PublicKeyAlgo0} = get_cert_params(Cert),
+ PublicKeyAlgo = public_key_algo(PublicKeyAlgo0),
+
+ %% Check cert even for TLS 1.0/1.1
+ case is_acceptable_cert_type(PublicKeyAlgo, Types) of
+ true ->
+ select_hashsign(undefined, Cert, undefined, [], Version);
+ false ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm)
+ end.
+
+
+%% Gets the relevant parameters of a certificate:
+%% - signature algorithm
+%% - parameters of the signature algorithm
+%% - public key algorithm (key type)
+get_cert_params(Cert) ->
+ #'OTPCertificate'{tbsCertificate = TBSCert,
+ signatureAlgorithm =
+ {_,SignAlgo, Param}} = public_key:pkix_decode_cert(Cert, otp),
+ #'OTPSubjectPublicKeyInfo'{algorithm = {_, PublicKeyAlgo, _}} =
+ TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
+ {SignAlgo, Param, PublicKeyAlgo}.
+
+
+get_signature_scheme(undefined) ->
+ undefined;
+get_signature_scheme(#signature_algorithms_cert{
+ signature_scheme_list = ClientSignatureSchemes}) ->
+ ClientSignatureSchemes.
+
%%--------------------------------------------------------------------
-spec select_hashsign_algs({atom(), atom()}| undefined, oid(), ssl_record:ssl_version()) ->
@@ -1180,6 +1460,8 @@ extension_value(#ec_point_formats{ec_point_format_list = List}) ->
List;
extension_value(#elliptic_curves{elliptic_curve_list = List}) ->
List;
+extension_value(#supported_groups{supported_groups = SupportedGroups}) ->
+ SupportedGroups;
extension_value(#hash_sign_algos{hash_sign_algos = Algos}) ->
Algos;
extension_value(#alpn{extension_data = Data}) ->
@@ -1200,33 +1482,30 @@ int_to_bin(I) ->
L = (length(integer_to_list(I, 16)) + 1) div 2,
<<I:(L*8)>>.
-certificate_types(_, {N, M}) when N >= 3 andalso M >= 3 ->
- case proplists:get_bool(ecdsa,
- proplists:get_value(public_keys, crypto:supports())) of
- true ->
- <<?BYTE(?ECDSA_SIGN), ?BYTE(?RSA_SIGN), ?BYTE(?DSS_SIGN)>>;
- false ->
- <<?BYTE(?RSA_SIGN), ?BYTE(?DSS_SIGN)>>
- end;
-
-certificate_types(#{key_exchange := KeyExchange}, _) when KeyExchange == rsa;
- KeyExchange == dh_rsa;
- KeyExchange == dhe_rsa;
- KeyExchange == ecdhe_rsa ->
- <<?BYTE(?RSA_SIGN)>>;
-
-certificate_types(#{key_exchange := KeyExchange}, _) when KeyExchange == dh_dss;
- KeyExchange == dhe_dss;
- KeyExchange == srp_dss ->
- <<?BYTE(?DSS_SIGN)>>;
-
-certificate_types(#{key_exchange := KeyExchange}, _) when KeyExchange == dh_ecdsa;
- KeyExchange == dhe_ecdsa;
- KeyExchange == ecdh_ecdsa;
- KeyExchange == ecdhe_ecdsa ->
- <<?BYTE(?ECDSA_SIGN)>>;
+%% TLS 1.0+
+%% The end-entity certificate provided by the client MUST contain a
+%% key that is compatible with certificate_types.
+certificate_types(_, {N, M}) when N >= 3 andalso M >= 1 ->
+ 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),
+ <<ECDSA/binary,RSA/binary,DSS/binary>>;
+%% SSL 3.0
certificate_types(_, _) ->
- <<?BYTE(?RSA_SIGN)>>.
+ RSA = supported_cert_type_or_empty(rsa, ?RSA_SIGN),
+ DSS = supported_cert_type_or_empty(dss, ?DSS_SIGN),
+ <<RSA/binary,DSS/binary>>.
+
+%% Returns encoded certificate_type if algorithm is supported
+supported_cert_type_or_empty(Algo, Type) ->
+ case proplists:get_bool(
+ Algo,
+ proplists:get_value(public_keys, crypto:supports())) of
+ true ->
+ <<?BYTE(Type)>>;
+ false ->
+ <<>>
+ end.
certificate_authorities(CertDbHandle, CertDbRef) ->
Authorities = certificate_authorities_from_db(CertDbHandle, CertDbRef),
@@ -1769,16 +2048,32 @@ encode_alpn(undefined, _) ->
encode_alpn(Protocols, _) ->
#alpn{extension_data = lists:foldl(fun encode_protocol/2, <<>>, Protocols)}.
-hello_extensions_list(#hello_extensions{renegotiation_info = RenegotiationInfo,
- srp = SRP,
- signature_algs = HashSigns,
- ec_point_formats = EcPointFormats,
- elliptic_curves = EllipticCurves,
- alpn = ALPN,
- next_protocol_negotiation = NextProtocolNegotiation,
- sni = Sni}) ->
- [Ext || Ext <- [RenegotiationInfo, SRP, HashSigns,
- EcPointFormats, EllipticCurves, ALPN, NextProtocolNegotiation, Sni], Ext =/= undefined].
+
+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>>).
+
+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(#key_share_entry{
+ group = Group,
+ key_exchange = KeyExchange}) ->
+ Len = byte_size(KeyExchange),
+ <<?UINT16((tls_v1:group_to_enum(Group))),?UINT16(Len),KeyExchange/binary>>.
+
+hello_extensions_list(HelloExtensions) ->
+ [Ext || {_, Ext} <- maps:to_list(HelloExtensions), Ext =/= undefined].
%%-------------Decode handshakes---------------------------------
dec_server_key(<<?UINT16(PLen), P:PLen/binary,
@@ -1918,16 +2213,60 @@ dec_server_key_signature(Params, <<?UINT16(Len), Signature:Len/binary>>, _) ->
dec_server_key_signature(_, _, _) ->
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, failed_to_decrypt_server_key_sign)).
-dec_hello_extensions(<<>>, Acc) ->
+%% Processes a ClientHello/ServerHello message and returns the version to be used
+%% in the decoding functions. The following rules apply:
+%% - IF supported_versions extension is absent:
+%% RETURN the lowest of (LocalVersion and LegacyVersion)
+%% - IF supported_versions estension is present:
+%% RETURN the lowest of (LocalVersion and first element of supported versions)
+process_supported_versions_extension(<<>>, LocalVersion, LegacyVersion)
+ when LegacyVersion =< LocalVersion ->
+ LegacyVersion;
+process_supported_versions_extension(<<>>, LocalVersion, _LegacyVersion) ->
+ LocalVersion;
+process_supported_versions_extension(<<?UINT16(?SUPPORTED_VERSIONS_EXT), ?UINT16(Len),
+ ExtData:Len/binary, _Rest/binary>>,
+ LocalVersion, _LegacyVersion) when Len > 2 ->
+ <<?BYTE(_),Versions0/binary>> = ExtData,
+ [Highest|_] = decode_versions(Versions0),
+ if Highest =< LocalVersion ->
+ Highest;
+ true ->
+ LocalVersion
+ end;
+process_supported_versions_extension(<<?UINT16(?SUPPORTED_VERSIONS_EXT), ?UINT16(Len),
+ ?BYTE(Major),?BYTE(Minor), _Rest/binary>>,
+ LocalVersion, _LegacyVersion) when Len =:= 2 ->
+ SelectedVersion = {Major, Minor},
+ if SelectedVersion =< LocalVersion ->
+ SelectedVersion;
+ true ->
+ LocalVersion
+ end;
+process_supported_versions_extension(<<?UINT16(_), ?UINT16(Len),
+ _ExtData:Len/binary, Rest/binary>>,
+ LocalVersion, LegacyVersion) ->
+ process_supported_versions_extension(Rest, LocalVersion, LegacyVersion);
+%% Tolerate protocol encoding errors and skip parsing the rest of the extension.
+process_supported_versions_extension(_, LocalVersion, LegacyVersion)
+ when LegacyVersion =< LocalVersion ->
+ LegacyVersion;
+process_supported_versions_extension(_, LocalVersion, _) ->
+ LocalVersion.
+
+decode_extensions(<<>>, _Version, _MessageType, Acc) ->
Acc;
-dec_hello_extensions(<<?UINT16(?ALPN_EXT), ?UINT16(ExtLen), ?UINT16(Len), ExtensionData:Len/binary, Rest/binary>>, Acc)
- when Len + 2 =:= ExtLen ->
+decode_extensions(<<?UINT16(?ALPN_EXT), ?UINT16(ExtLen), ?UINT16(Len),
+ ExtensionData:Len/binary, Rest/binary>>, Version, MessageType, Acc)
+ when Len + 2 =:= ExtLen ->
ALPN = #alpn{extension_data = ExtensionData},
- dec_hello_extensions(Rest, Acc#hello_extensions{alpn = ALPN});
-dec_hello_extensions(<<?UINT16(?NEXTPROTONEG_EXT), ?UINT16(Len), ExtensionData:Len/binary, Rest/binary>>, Acc) ->
+ decode_extensions(Rest, Version, MessageType, Acc#{alpn => ALPN});
+decode_extensions(<<?UINT16(?NEXTPROTONEG_EXT), ?UINT16(Len),
+ ExtensionData:Len/binary, Rest/binary>>, Version, MessageType, Acc) ->
NextP = #next_protocol_negotiation{extension_data = ExtensionData},
- dec_hello_extensions(Rest, Acc#hello_extensions{next_protocol_negotiation = NextP});
-dec_hello_extensions(<<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), Info:Len/binary, Rest/binary>>, Acc) ->
+ decode_extensions(Rest, Version, MessageType, Acc#{next_protocol_negotiation => NextP});
+decode_extensions(<<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len),
+ Info:Len/binary, Rest/binary>>, Version, MessageType, Acc) ->
RenegotiateInfo = case Len of
1 -> % Initial handshake
Info; % should be <<0>> will be matched in handle_renegotiation_info
@@ -1936,25 +2275,54 @@ dec_hello_extensions(<<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), Info:Len/binar
<<?BYTE(VerifyLen), VerifyInfo/binary>> = Info,
VerifyInfo
end,
- dec_hello_extensions(Rest, Acc#hello_extensions{renegotiation_info =
- #renegotiation_info{renegotiated_connection =
- RenegotiateInfo}});
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{renegotiation_info =>
+ #renegotiation_info{renegotiated_connection =
+ RenegotiateInfo}});
-dec_hello_extensions(<<?UINT16(?SRP_EXT), ?UINT16(Len), ?BYTE(SRPLen), SRP:SRPLen/binary, Rest/binary>>, Acc)
+decode_extensions(<<?UINT16(?SRP_EXT), ?UINT16(Len), ?BYTE(SRPLen),
+ SRP:SRPLen/binary, Rest/binary>>, Version, MessageType, Acc)
when Len == SRPLen + 1 ->
- dec_hello_extensions(Rest, Acc#hello_extensions{srp = #srp{username = SRP}});
+ decode_extensions(Rest, Version, MessageType, Acc#{srp => #srp{username = SRP}});
-dec_hello_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len),
- ExtData:Len/binary, Rest/binary>>, Acc) ->
+decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len),
+ ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc)
+ when Version < {3,4} ->
SignAlgoListLen = Len - 2,
<<?UINT16(SignAlgoListLen), SignAlgoList/binary>> = ExtData,
HashSignAlgos = [{ssl_cipher:hash_algorithm(Hash), ssl_cipher:sign_algorithm(Sign)} ||
<<?BYTE(Hash), ?BYTE(Sign)>> <= SignAlgoList],
- dec_hello_extensions(Rest, Acc#hello_extensions{signature_algs =
- #hash_sign_algos{hash_sign_algos = HashSignAlgos}});
-
-dec_hello_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len),
- ExtData:Len/binary, Rest/binary>>, Acc) ->
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{signature_algs =>
+ #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,4} ->
+ SignSchemeListLen = Len - 2,
+ <<?UINT16(SignSchemeListLen), SignSchemeList/binary>> = ExtData,
+ SignSchemes = [ssl_cipher:signature_scheme(SignScheme) ||
+ <<?UINT16(SignScheme)>> <= SignSchemeList],
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{signature_algs =>
+ #signature_algorithms{
+ signature_scheme_list = SignSchemes}});
+
+decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_CERT_EXT), ?UINT16(Len),
+ ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) ->
+ SignSchemeListLen = Len - 2,
+ <<?UINT16(SignSchemeListLen), SignSchemeList/binary>> = ExtData,
+ SignSchemes = [ssl_cipher:signature_scheme(SignScheme) ||
+ <<?UINT16(SignScheme)>> <= SignSchemeList],
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{signature_algs_cert =>
+ #signature_algorithms_cert{
+ signature_scheme_list = SignSchemes}});
+
+decode_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len),
+ ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc)
+ when Version < {3,4} ->
<<?UINT16(_), EllipticCurveList/binary>> = ExtData,
%% Ignore unknown curves
Pick = fun(Enum) ->
@@ -1966,31 +2334,103 @@ dec_hello_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len),
end
end,
EllipticCurves = lists:filtermap(Pick, [ECC || <<ECC:16>> <= EllipticCurveList]),
- dec_hello_extensions(Rest, Acc#hello_extensions{elliptic_curves =
- #elliptic_curves{elliptic_curve_list =
- EllipticCurves}});
-dec_hello_extensions(<<?UINT16(?EC_POINT_FORMATS_EXT), ?UINT16(Len),
- ExtData:Len/binary, Rest/binary>>, Acc) ->
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{elliptic_curves =>
+ #elliptic_curves{elliptic_curve_list =
+ EllipticCurves}});
+
+decode_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len),
+ ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc)
+ when Version =:= {3,4} ->
+ <<?UINT16(_), GroupList/binary>> = ExtData,
+ %% Ignore unknown curves
+ Pick = fun(Enum) ->
+ case tls_v1:enum_to_group(Enum) of
+ undefined ->
+ false;
+ Group ->
+ {true, Group}
+ end
+ end,
+ SupportedGroups = lists:filtermap(Pick, [Group || <<Group:16>> <= GroupList]),
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{elliptic_curves =>
+ #supported_groups{supported_groups =
+ SupportedGroups}});
+
+decode_extensions(<<?UINT16(?EC_POINT_FORMATS_EXT), ?UINT16(Len),
+ ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) ->
<<?BYTE(_), ECPointFormatList/binary>> = ExtData,
ECPointFormats = binary_to_list(ECPointFormatList),
- dec_hello_extensions(Rest, Acc#hello_extensions{ec_point_formats =
- #ec_point_formats{ec_point_format_list =
- ECPointFormats}});
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{ec_point_formats =>
+ #ec_point_formats{ec_point_format_list =
+ ECPointFormats}});
+
+decode_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len),
+ Rest/binary>>, Version, MessageType, Acc) when Len == 0 ->
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{sni => #sni{hostname = ""}}); %% Server may send an empy SNI
+
+decode_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len),
+ ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) ->
+ <<?UINT16(_), NameList/binary>> = ExtData,
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{sni => dec_sni(NameList)});
+
+decode_extensions(<<?UINT16(?SUPPORTED_VERSIONS_EXT), ?UINT16(Len),
+ ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) when Len > 2 ->
+ <<?BYTE(_),Versions/binary>> = ExtData,
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{client_hello_versions =>
+ #client_hello_versions{
+ versions = decode_versions(Versions)}});
+
+decode_extensions(<<?UINT16(?SUPPORTED_VERSIONS_EXT), ?UINT16(Len),
+ ?UINT16(SelectedVersion), Rest/binary>>, Version, MessageType, Acc)
+ when Len =:= 2, SelectedVersion =:= 16#0304 ->
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{server_hello_selected_version =>
+ #server_hello_selected_version{selected_version =
+ {3,4}}});
+
+decode_extensions(<<?UINT16(?KEY_SHARE_EXT), ?UINT16(Len),
+ ExtData:Len/binary, Rest/binary>>,
+ Version, MessageType = client_hello, Acc) ->
+ <<?UINT16(_),ClientShares/binary>> = ExtData,
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{key_share =>
+ #key_share_client_hello{
+ client_shares = decode_client_shares(ClientShares)}});
+
+decode_extensions(<<?UINT16(?KEY_SHARE_EXT), ?UINT16(Len),
+ ExtData:Len/binary, Rest/binary>>,
+ Version, MessageType = server_hello, Acc) ->
+ <<?UINT16(Group),?UINT16(KeyLen),KeyExchange:KeyLen/binary>> = ExtData,
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{key_share =>
+ #key_share_server_hello{
+ server_share =
+ #key_share_entry{
+ group = tls_v1:enum_to_group(Group),
+ key_exchange = KeyExchange}}});
+
+decode_extensions(<<?UINT16(?KEY_SHARE_EXT), ?UINT16(Len),
+ ExtData:Len/binary, Rest/binary>>,
+ Version, MessageType = hello_retry_request, Acc) ->
+ <<?UINT16(Group),Rest/binary>> = ExtData,
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{key_share =>
+ #key_share_hello_retry_request{
+ selected_group = tls_v1:enum_to_group(Group)}});
-dec_hello_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len), Rest/binary>>, Acc) when Len == 0 ->
- dec_hello_extensions(Rest, Acc#hello_extensions{sni = #sni{hostname = ""}}); %% Server may send an empy SNI
-dec_hello_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len),
- ExtData:Len/binary, Rest/binary>>, Acc) ->
- <<?UINT16(_), NameList/binary>> = ExtData,
- dec_hello_extensions(Rest, Acc#hello_extensions{sni = dec_sni(NameList)});
%% Ignore data following the ClientHello (i.e.,
%% extensions) if not understood.
-
-dec_hello_extensions(<<?UINT16(_), ?UINT16(Len), _Unknown:Len/binary, Rest/binary>>, Acc) ->
- dec_hello_extensions(Rest, Acc);
+decode_extensions(<<?UINT16(_), ?UINT16(Len), _Unknown:Len/binary, Rest/binary>>, Version, MessageType, Acc) ->
+ decode_extensions(Rest, Version, MessageType, Acc);
%% This theoretically should not happen if the protocol is followed, but if it does it is ignored.
-dec_hello_extensions(_, Acc) ->
+decode_extensions(_, _, _, Acc) ->
Acc.
dec_hashsign(<<?BYTE(HashAlgo), ?BYTE(SignAlgo)>>) ->
@@ -2008,6 +2448,26 @@ decode_alpn(undefined) ->
decode_alpn(#alpn{extension_data=Data}) ->
decode_protocols(Data, []).
+decode_versions(Versions) ->
+ decode_versions(Versions, []).
+%%
+decode_versions(<<>>, Acc) ->
+ lists:reverse(Acc);
+decode_versions(<<?BYTE(M),?BYTE(N),Rest/binary>>, Acc) ->
+ decode_versions(Rest, [{M,N}|Acc]).
+
+
+decode_client_shares(ClientShares) ->
+ decode_client_shares(ClientShares, []).
+%%
+decode_client_shares(<<>>, Acc) ->
+ lists:reverse(Acc);
+decode_client_shares(<<?UINT16(Group),?UINT16(Len),KeyExchange:Len/binary,Rest/binary>>, Acc) ->
+ decode_client_shares(Rest, [#key_share_entry{
+ group = tls_v1:enum_to_group(Group),
+ key_exchange= KeyExchange
+ }|Acc]).
+
decode_next_protocols({next_protocol_negotiation, Protocols}) ->
decode_protocols(Protocols, []).
@@ -2189,30 +2649,26 @@ filter_unavailable_ecc_suites(_, Suites) ->
handle_renegotiation_extension(Role, RecordCB, Version, Info, Random, NegotiatedCipherSuite,
ClientCipherSuites, Compression,
ConnectionStates0, Renegotiation, SecureRenegotation) ->
- case handle_renegotiation_info(RecordCB, Role, Info, ConnectionStates0,
- Renegotiation, SecureRenegotation,
- ClientCipherSuites) of
- {ok, ConnectionStates} ->
- hello_pending_connection_states(RecordCB, Role,
- Version,
- NegotiatedCipherSuite,
- Random,
- Compression,
- ConnectionStates);
- #alert{} = Alert ->
- throw(Alert)
- end.
+ {ok, ConnectionStates} = handle_renegotiation_info(RecordCB, Role, Info, ConnectionStates0,
+ Renegotiation, SecureRenegotation,
+ ClientCipherSuites),
+ hello_pending_connection_states(RecordCB, Role,
+ Version,
+ NegotiatedCipherSuite,
+ Random,
+ Compression,
+ ConnectionStates).
%% Receive protocols, choose one from the list, return it.
handle_alpn_extension(_, {error, Reason}) ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason);
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason));
handle_alpn_extension([], _) ->
- ?ALERT_REC(?FATAL, ?NO_APPLICATION_PROTOCOL);
+ throw(?ALERT_REC(?FATAL, ?NO_APPLICATION_PROTOCOL));
handle_alpn_extension([ServerProtocol|Tail], ClientProtocols) ->
- case lists:member(ServerProtocol, ClientProtocols) of
- true -> ServerProtocol;
- false -> handle_alpn_extension(Tail, ClientProtocols)
- end.
+ case lists:member(ServerProtocol, ClientProtocols) of
+ true -> ServerProtocol;
+ false -> handle_alpn_extension(Tail, ClientProtocols)
+ end.
handle_next_protocol(undefined,
_NextProtocolSelector, _Renegotiating) ->
@@ -2225,14 +2681,14 @@ handle_next_protocol(#next_protocol_negotiation{} = NextProtocols,
true ->
select_next_protocol(decode_next_protocols(NextProtocols), NextProtocolSelector);
false ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_next_protocol_extension)
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_next_protocol_extension))
end.
handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, SslOpts)->
case handle_next_protocol_on_server(NextProtocolNegotiation, Renegotiation, SslOpts) of
#alert{} = Alert ->
- Alert;
+ throw(Alert);
ProtocolsToAdvertise ->
ProtocolsToAdvertise
end.
@@ -2265,17 +2721,6 @@ handle_srp_extension(undefined, Session) ->
handle_srp_extension(#srp{username = Username}, Session) ->
Session#session{srp_username = Username}.
-
-sign_algo(?rsaEncryption) ->
- rsa;
-sign_algo(?'id-ecPublicKey') ->
- ecdsa;
-sign_algo(?'id-dsa') ->
- dsa;
-sign_algo(Alg) ->
- {_, Sign} =public_key:pkix_sign_types(Alg),
- Sign.
-
is_acceptable_hash_sign( _, KeyExAlgo, _) when
KeyExAlgo == psk;
KeyExAlgo == dhe_psk;
@@ -2291,15 +2736,80 @@ is_acceptable_hash_sign(Algos,_, SupportedHashSigns) ->
is_acceptable_hash_sign(Algos, SupportedHashSigns) ->
lists:member(Algos, SupportedHashSigns).
-is_acceptable_cert_type(Sign, _HashSigns, Types) ->
+is_acceptable_cert_type(Sign, Types) ->
lists:member(sign_type(Sign), binary_to_list(Types)).
-is_supported_sign(Sign, HashSigns) ->
- [] =/= lists:dropwhile(fun({_, S}) when S =/= Sign ->
- true;
- (_)->
- false
- end, HashSigns).
+%% signature_algorithms_cert = undefined
+is_supported_sign(SignAlgo, _, HashSigns, undefined) ->
+ lists:member(SignAlgo, HashSigns);
+
+%% {'SignatureAlgorithm',{1,2,840,113549,1,1,11},'NULL'}
+is_supported_sign({Hash, Sign}, 'NULL', _, SignatureSchemes) ->
+ Fun = fun (Scheme, Acc) ->
+ {H0, S0, _} = ssl_cipher:scheme_to_components(Scheme),
+ S1 = case S0 of
+ rsa_pkcs1 -> rsa;
+ S -> S
+ end,
+ H1 = case H0 of
+ sha1 -> sha;
+ H -> H
+ end,
+ Acc orelse (Sign =:= S1 andalso
+ Hash =:= H1)
+ end,
+ lists:foldl(Fun, false, SignatureSchemes);
+
+%% TODO: Implement validation for the curve used in the signature
+%% RFC 3279 - 2.2.3 ECDSA Signature Algorithm
+%% When the ecdsa-with-SHA1 algorithm identifier appears as the
+%% algorithm field in an AlgorithmIdentifier, the encoding MUST omit the
+%% parameters field. That is, the AlgorithmIdentifier SHALL be a
+%% SEQUENCE of one component: the OBJECT IDENTIFIER ecdsa-with-SHA1.
+%%
+%% The elliptic curve parameters in the subjectPublicKeyInfo field of
+%% the certificate of the issuer SHALL apply to the verification of the
+%% signature.
+is_supported_sign({Hash, Sign}, _Param, _, SignatureSchemes) ->
+ Fun = fun (Scheme, Acc) ->
+ {H0, S0, _} = ssl_cipher:scheme_to_components(Scheme),
+ S1 = case S0 of
+ rsa_pkcs1 -> rsa;
+ S -> S
+ end,
+ H1 = case H0 of
+ sha1 -> sha;
+ H -> H
+ end,
+ Acc orelse (Sign =:= S1 andalso
+ Hash =:= H1)
+ end,
+ lists:foldl(Fun, false, SignatureSchemes).
+
+%% SupportedPublicKeyAlgorithms PUBLIC-KEY-ALGORITHM-CLASS ::= {
+%% dsa | rsa-encryption | dh | kea | ec-public-key }
+public_key_algo(?rsaEncryption) ->
+ rsa;
+public_key_algo(?'id-ecPublicKey') ->
+ ecdsa;
+public_key_algo(?'id-dsa') ->
+ dsa.
+
+%% SupportedSignatureAlgorithms SIGNATURE-ALGORITHM-CLASS ::= {
+%% dsa-with-sha1 | dsaWithSHA1 | md2-with-rsa-encryption |
+%% md5-with-rsa-encryption | sha1-with-rsa-encryption | sha-1with-rsa-encryption |
+%% sha224-with-rsa-encryption |
+%% sha256-with-rsa-encryption |
+%% sha384-with-rsa-encryption |
+%% sha512-with-rsa-encryption |
+%% ecdsa-with-sha1 |
+%% ecdsa-with-sha224 |
+%% ecdsa-with-sha256 |
+%% ecdsa-with-sha384 |
+%% ecdsa-with-sha512 }
+sign_algo(Alg) ->
+ public_key:pkix_sign_types(Alg).
+
sign_type(rsa) ->
?RSA_SIGN;
sign_type(dsa) ->
@@ -2318,6 +2828,11 @@ client_ecc_extensions(SupportedECCs) ->
CryptoSupport = proplists:get_value(public_keys, crypto:supports()),
case proplists:get_bool(ecdh, CryptoSupport) of
true ->
+ %% RFC 8422 - 5.1. Client Hello Extensions
+ %% Clients SHOULD send both the Supported Elliptic Curves Extension and the
+ %% Supported Point Formats Extension. If the Supported Point Formats
+ %% Extension is indeed sent, it MUST contain the value 0 (uncompressed)
+ %% as one of the items in the list of point formats.
EcPointFormats = #ec_point_formats{ec_point_format_list = [?ECPOINT_UNCOMPRESSED]},
EllipticCurves = SupportedECCs,
{EcPointFormats, EllipticCurves};
@@ -2428,14 +2943,14 @@ handle_renegotiation_info(_RecordCB, client, #renegotiation_info{renegotiated_co
true ->
{ok, ConnectionStates};
false ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, client_renegotiation)
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, client_renegotiation))
end;
handle_renegotiation_info(_RecordCB, server, #renegotiation_info{renegotiated_connection = ClientVerify},
ConnectionStates, true, _, CipherSuites) ->
case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of
true ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv});
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv}));
false ->
ConnectionState = ssl_record:current_connection_state(ConnectionStates, read),
Data = maps:get(client_verify_data, ConnectionState),
@@ -2443,7 +2958,7 @@ handle_renegotiation_info(_RecordCB, server, #renegotiation_info{renegotiated_co
true ->
{ok, ConnectionStates};
false ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, server_renegotiation)
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, server_renegotiation))
end
end;
@@ -2453,7 +2968,7 @@ handle_renegotiation_info(RecordCB, client, undefined, ConnectionStates, true, S
handle_renegotiation_info(RecordCB, server, undefined, ConnectionStates, true, SecureRenegotation, CipherSuites) ->
case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of
true ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv});
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv}));
false ->
handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation)
end.
@@ -2462,9 +2977,9 @@ handle_renegotiation_info(_RecordCB, ConnectionStates, SecureRenegotation) ->
ConnectionState = ssl_record:current_connection_state(ConnectionStates, read),
case {SecureRenegotation, maps:get(secure_renegotiation, ConnectionState)} of
{_, true} ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, already_secure);
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, already_secure));
{true, false} ->
- ?ALERT_REC(?FATAL, ?NO_RENEGOTIATION);
+ throw(?ALERT_REC(?FATAL, ?NO_RENEGOTIATION));
{false, false} ->
{ok, ConnectionStates}
end.
@@ -2485,4 +3000,51 @@ cert_curve(Cert, ECCCurve0, CipherSuite) ->
{ECCCurve0, CipherSuite}
end.
-
+empty_extensions() ->
+ #{}.
+
+empty_extensions({3,4}, client_hello) ->
+ #{
+ sni => undefined,
+ %% max_fragment_length => undefined,
+ %% status_request => undefined,
+ elliptic_curves => undefined,
+ signature_algs => undefined,
+ %% use_srtp => undefined,
+ %% heartbeat => undefined,
+ alpn => undefined,
+ %% signed_cert_timestamp => undefined,
+ %% client_cert_type => undefined,
+ %% server_cert_type => undefined,
+ %% padding => undefined,
+ key_share => undefined,
+ pre_shared_key => undefined,
+ %% psk_key_exhange_modes => undefined,
+ %% early_data => undefined,
+ %% cookie => undefined,
+ client_hello_versions => undefined,
+ %% cert_authorities => undefined,
+ %% post_handshake_auth => undefined,
+ signature_algs_cert => undefined
+ };
+empty_extensions({3, 3}, client_hello) ->
+ Ext = empty_extensions({3,2}, client_hello),
+ Ext#{signature_algs => undefined};
+empty_extensions(_, client_hello) ->
+ #{renegotiation_info => undefined,
+ alpn => undefined,
+ next_protocol_negotiation => undefined,
+ srp => undefined,
+ ec_point_formats => undefined,
+ elliptic_curves => undefined,
+ sni => undefined};
+empty_extensions({3,4}, server_hello) ->
+ #{server_hello_selected_version => undefined,
+ key_share => undefined,
+ pre_shared_key => undefined
+ };
+empty_extensions(_, server_hello) ->
+ #{renegotiation_info => undefined,
+ alpn => undefined,
+ next_protocol_negotiation => undefined,
+ ec_point_formats => undefined}.
diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl
index a191fcf766..d4233bea9b 100644
--- a/lib/ssl/src/ssl_handshake.hrl
+++ b/lib/ssl/src/ssl_handshake.hrl
@@ -52,9 +52,8 @@
-define(NUM_OF_SESSION_ID_BYTES, 32). % TSL 1.1 & SSL 3
-define(NUM_OF_PREMASTERSECRET_BYTES, 48).
--define(DEFAULT_DIFFIE_HELLMAN_GENERATOR, 2).
--define(DEFAULT_DIFFIE_HELLMAN_PRIME,
- 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF).
+-define(DEFAULT_DIFFIE_HELLMAN_GENERATOR, ssl_dh_groups:modp2048_generator()).
+-define(DEFAULT_DIFFIE_HELLMAN_PRIME, ssl_dh_groups:modp2048_prime()).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Handsake protocol - RFC 4346 section 7.4
@@ -105,7 +104,11 @@
srp,
ec_point_formats,
elliptic_curves,
- sni
+ sni,
+ client_hello_versions,
+ server_hello_selected_version,
+ signature_algs_cert,
+ key_share
}).
-record(server_hello, {
@@ -313,12 +316,12 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-define(SIGNATURE_ALGORITHMS_EXT, 13).
--record(hash_sign_algos, {
- hash_sign_algos
- }).
+-record(hash_sign_algos, {hash_sign_algos}).
+%% RFC 8446 (TLS 1.3)
+-record(signature_algorithms, {signature_scheme_list}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% Application-Layer Protocol Negotiation RFC 7301
+%% RFC 7301 Application-Layer Protocol Negotiation
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-define(ALPN_EXT, 16).
@@ -338,9 +341,8 @@
-record(next_protocol, {selected_protocol}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% ECC Extensions RFC 4492 section 4 and 5
+%% ECC Extensions RFC 8422 section 4 and 5
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-define(ELLIPTIC_CURVES_EXT, 10).
-define(EC_POINT_FORMATS_EXT, 11).
@@ -348,11 +350,18 @@
elliptic_curve_list
}).
+%% RFC 8446 (TLS 1.3) renamed the "elliptic_curve" extension.
+-record(supported_groups, {
+ supported_groups
+ }).
+
-record(ec_point_formats, {
ec_point_format_list
}).
-define(ECPOINT_UNCOMPRESSED, 0).
+%% Defined in RFC 4492, deprecated by RFC 8422
+%% RFC 8422 compliant implementations MUST not support the two formats below
-define(ECPOINT_ANSIX962_COMPRESSED_PRIME, 1).
-define(ECPOINT_ANSIX962_COMPRESSED_CHAR2, 2).
@@ -365,10 +374,11 @@
-define(NAMED_CURVE, 3).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% Server name indication RFC 6066 section 3
+%% RFC 6066 Server name indication
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
--define(SNI_EXT, 16#0000).
+%% section 3
+-define(SNI_EXT, 0).
%% enum { host_name(0), (255) } NameType;
-define(SNI_NAMETYPE_HOST_NAME, 0).
@@ -377,4 +387,56 @@
hostname = undefined
}).
+%% Other possible values from RFC 6066, not supported
+-define(MAX_FRAGMENT_LENGTH, 1).
+-define(CLIENT_CERTIFICATE_URL, 2).
+-define(TRUSTED_CA_KEYS, 3).
+-define(TRUNCATED_HMAC, 4).
+-define(STATUS_REQUEST, 5).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% RFC 7250 Using Raw Public Keys in Transport Layer Security (TLS)
+%% and Datagram Transport Layer Security (DTLS)
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Not supported
+-define(CLIENT_CERTIFICATE_TYPE, 19).
+-define(SERVER_CERTIFICATE_TYPE, 20).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% RFC 6520 Transport Layer Security (TLS) and
+%% Datagram Transport Layer Security (DTLS) Heartbeat Extension
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Not supported
+-define(HS_HEARTBEAT, 15).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% RFC 6962 Certificate Transparency
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Not supported
+-define(SIGNED_CERTIFICATE_TIMESTAMP, 18).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% RFC 7685 A Transport Layer Security (TLS) ClientHello Padding Extension
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Not supported
+-define(PADDING, 21).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Supported Versions RFC 8446 (TLS 1.3) section 4.2.1 also affects TLS-1.2
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-define(SUPPORTED_VERSIONS_EXT, 43).
+
+-record(client_hello_versions, {versions}).
+-record(server_hello_selected_version, {selected_version}).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Signature Algorithms RFC 8446 (TLS 1.3) section 4.2.3 also affects TLS-1.2
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-define(SIGNATURE_ALGORITHMS_CERT_EXT, 50).
+
+-record(signature_algorithms_cert, {signature_scheme_list}).
+
-endif. % -ifdef(ssl_handshake).
diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl
index 63e751440a..3d117a655f 100644
--- a/lib/ssl/src/ssl_internal.hrl
+++ b/lib/ssl/src/ssl_internal.hrl
@@ -25,14 +25,13 @@
-include_lib("public_key/include/public_key.hrl").
+-define(VSN, "8.2.6").
-define(SECRET_PRINTOUT, "***").
-type reason() :: term().
-type reply() :: term().
-type msg() :: term().
-type from() :: term().
--type host() :: inet:ip_address() | inet:hostname().
--type session_id() :: 0 | binary().
-type certdb_ref() :: reference().
-type db_handle() :: term().
-type der_cert() :: binary().
@@ -72,14 +71,39 @@
-define(FALSE, 1).
%% sslv3 is considered insecure due to lack of padding check (Poodle attack)
-%% Keep as interop with legacy software but do not support as default
--define(ALL_AVAILABLE_VERSIONS, ['tlsv1.2', 'tlsv1.1', tlsv1, sslv3]).
+%% Keep as interop with legacy software but do not support as default
+%% tlsv1.3 is under development (experimental).
+-define(ALL_AVAILABLE_VERSIONS, ['tlsv1.3', 'tlsv1.2', 'tlsv1.1', tlsv1, sslv3]).
-define(ALL_AVAILABLE_DATAGRAM_VERSIONS, ['dtlsv1.2', dtlsv1]).
+%% Defines the default versions when not specified by an ssl option.
-define(ALL_SUPPORTED_VERSIONS, ['tlsv1.2', 'tlsv1.1', tlsv1]).
-define(MIN_SUPPORTED_VERSIONS, ['tlsv1.1', tlsv1]).
+
+%% Versions allowed in TLSCiphertext.version (TLS 1.2 and prior) and
+%% TLSCiphertext.legacy_record_version (TLS 1.3).
+%% TLS 1.3 sets TLSCiphertext.legacy_record_version to 0x0303 for all records
+%% generated other than an than an initial ClientHello, where it MAY also be 0x0301.
+%% Thus, the allowed range is limited to 0x0300 - 0x0303.
+-define(ALL_TLS_RECORD_VERSIONS, ['tlsv1.2', 'tlsv1.1', tlsv1, sslv3]).
+
-define(ALL_DATAGRAM_SUPPORTED_VERSIONS, ['dtlsv1.2', dtlsv1]).
-define(MIN_DATAGRAM_SUPPORTED_VERSIONS, [dtlsv1]).
+%% TLS 1.3 - Section 4.1.3
+%%
+%% If negotiating TLS 1.2, TLS 1.3 servers MUST set the last eight bytes
+%% of their Random value to the bytes:
+%%
+%% 44 4F 57 4E 47 52 44 01
+%%
+%% If negotiating TLS 1.1 or below, TLS 1.3 servers MUST and TLS 1.2
+%% servers SHOULD set the last eight bytes of their Random value to the
+%% bytes:
+%%
+%% 44 4F 57 4E 47 52 44 00
+-define(RANDOM_OVERRIDE_TLS12, <<16#44,16#4F,16#57,16#4E,16#47,16#52,16#44,16#01>>).
+-define(RANDOM_OVERRIDE_TLS11, <<16#44,16#4F,16#57,16#4E,16#47,16#52,16#44,16#00>>).
+
-define('24H_in_msec', 86400000).
-define('24H_in_sec', 86400).
@@ -98,7 +122,8 @@
cert :: public_key:der_encoded() | secret_printout() | 'undefined',
keyfile :: binary(),
key :: {'RSAPrivateKey' | 'DSAPrivateKey' | 'ECPrivateKey' | 'PrivateKeyInfo',
- public_key:der_encoded()} | key_map() | secret_printout() | 'undefined',
+ public_key:der_encoded()} | map() %%map() -> ssl:key() how to handle dialyzer?
+ | secret_printout() | 'undefined',
password :: string() | secret_printout() | 'undefined',
cacerts :: [public_key:der_encoded()] | secret_printout() | 'undefined',
cacertfile :: binary(),
@@ -111,10 +136,10 @@
%% Local policy for the server if it want's to reuse the session
%% or not. Defaluts to allways returning true.
%% fun(SessionId, PeerCert, Compression, CipherSuite) -> boolean()
- reuse_session,
+ reuse_session :: fun() | binary() | undefined, %% Server side is a fun()
%% If false sessions will never be reused, if true they
%% will be reused if possible.
- reuse_sessions :: boolean(),
+ reuse_sessions :: boolean() | save, %% Only client side can use value save
renegotiate_at,
secure_renegotiate,
client_renegotiation,
@@ -128,7 +153,7 @@
alpn_preferred_protocols = undefined :: [binary()] | undefined,
next_protocols_advertised = undefined :: [binary()] | undefined,
next_protocol_selector = undefined, %% fun([binary()]) -> binary())
- log_alert :: boolean(),
+ log_level = notice :: atom(),
server_name_indication = undefined,
sni_hosts :: [{inet:hostname(), [tuple()]}],
sni_fun :: function() | undefined,
@@ -143,11 +168,15 @@
crl_check :: boolean() | peer | best_effort,
crl_cache,
signature_algs,
+ signature_algs_cert,
eccs,
+ supported_groups, %% RFC 8422, RFC 8446
honor_ecc_order :: boolean(),
max_handshake_size :: integer(),
handshake,
customize_hostname_check
+ %% ,
+ %% save_session :: boolean()
}).
-record(socket_options,
@@ -168,19 +197,12 @@
connection_cb
}).
--type key_map() :: #{algorithm := rsa | dss | ecdsa,
- %% engine and key_id ought to
- %% be :=, but putting it in
- %% the spec gives dialyzer warning
- %% of correct code!
- engine => crypto:engine_ref(),
- key_id => crypto:key_id(),
- password => crypto:password()
- }.
-type state_name() :: hello | abbreviated | certify | cipher | connection.
-type gen_fsm_state_return() :: {next_state, state_name(), term()} |
{next_state, state_name(), term(), timeout()} |
{stop, term(), term()}.
+-type ssl_options() :: #ssl_options{}.
+
-endif. % -ifdef(ssl_internal).
diff --git a/lib/ssl/src/ssl_logger.erl b/lib/ssl/src/ssl_logger.erl
new file mode 100644
index 0000000000..b82b3937a1
--- /dev/null
+++ b/lib/ssl/src/ssl_logger.erl
@@ -0,0 +1,433 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1999-2019. All 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_logger).
+
+-export([debug/4,
+ format/2,
+ notice/2]).
+
+-define(DEC2HEX(X),
+ if ((X) >= 0) andalso ((X) =< 9) -> (X) + $0;
+ ((X) >= 10) andalso ((X) =< 15) -> (X) + $a - 10
+ end).
+
+-define(rec_info(T,R),lists:zip(record_info(fields,T),tl(tuple_to_list(R)))).
+
+-include("tls_record.hrl").
+-include("ssl_cipher.hrl").
+-include("ssl_internal.hrl").
+-include("tls_handshake.hrl").
+-include("dtls_handshake.hrl").
+-include("tls_handshake_1_3.hrl").
+-include_lib("kernel/include/logger.hrl").
+
+%%-------------------------------------------------------------------------
+%% External API
+%%-------------------------------------------------------------------------
+
+%% SSL log formatter
+format(#{level:= _Level, msg:= {report, Msg}, meta:= _Meta}, _Config0) ->
+ #{direction := Direction,
+ protocol := Protocol,
+ message := Content} = Msg,
+ case Protocol of
+ 'record' ->
+ BinMsg =
+ case Content of
+ #ssl_tls{} ->
+ [tls_record:build_tls_record(Content)];
+ _ when is_list(Content) ->
+ lists:flatten(Content)
+ end,
+ format_tls_record(Direction, BinMsg);
+ 'handshake' ->
+ format_handshake(Direction, Content);
+ _Other ->
+ []
+ end.
+
+%% Stateful logging
+debug(Level, Direction, Protocol, Message)
+ when (Direction =:= inbound orelse Direction =:= outbound) andalso
+ (Protocol =:= 'record' orelse Protocol =:= 'handshake') ->
+ case logger:compare_levels(Level, debug) of
+ lt ->
+ ?LOG_DEBUG(#{direction => Direction,
+ protocol => Protocol,
+ message => Message},
+ #{domain => [otp,ssl,Protocol]});
+ eq ->
+ ?LOG_DEBUG(#{direction => Direction,
+ protocol => Protocol,
+ message => Message},
+ #{domain => [otp,ssl,Protocol]});
+ _ ->
+ ok
+ end.
+
+%% Stateful logging
+notice(Level, Report) ->
+ case logger:compare_levels(Level, notice) of
+ lt ->
+ ?LOG_NOTICE(Report);
+ eq ->
+ ?LOG_NOTICE(Report);
+ _ ->
+ ok
+ end.
+
+
+%%-------------------------------------------------------------------------
+%% Handshake Protocol
+%%-------------------------------------------------------------------------
+format_handshake(Direction, BinMsg) ->
+ {Header, Message} = parse_handshake(Direction, BinMsg),
+ io_lib:format("~s~n~s~n", [Header, Message]).
+
+
+parse_handshake(Direction, #client_hello{
+ client_version = Version0,
+ cipher_suites = CipherSuites0,
+ extensions = Extensions
+ } = ClientHello) ->
+ Version = get_client_version(Version0, Extensions),
+ Header = io_lib:format("~s ~s Handshake, ClientHello",
+ [header_prefix(Direction),
+ version(Version)]),
+ CipherSuites = parse_cipher_suites(CipherSuites0),
+ Message = io_lib:format("~p",
+ [?rec_info(client_hello,
+ ClientHello#client_hello{cipher_suites = CipherSuites})]),
+ {Header, Message};
+parse_handshake(Direction, #server_hello{
+ server_version = Version0,
+ cipher_suite = CipherSuite0,
+ extensions = Extensions
+ } = ServerHello) ->
+ Version = get_server_version(Version0, Extensions),
+ Header = io_lib:format("~s ~s Handshake, ServerHello",
+ [header_prefix(Direction),
+ version(Version)]),
+ CipherSuite = format_cipher(CipherSuite0),
+ Message = io_lib:format("~p",
+ [?rec_info(server_hello,
+ ServerHello#server_hello{cipher_suite = CipherSuite})]),
+ {Header, Message};
+parse_handshake(Direction, #hello_verify_request{} = HelloVerifyRequest) ->
+ Header = io_lib:format("~s Handshake, HelloVerifyRequest",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(hello_verify_request, HelloVerifyRequest)]),
+ {Header, Message};
+parse_handshake(Direction, #certificate{} = Certificate) ->
+ Header = io_lib:format("~s Handshake, Certificate",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(certificate, Certificate)]),
+ {Header, Message};
+parse_handshake(Direction, #server_key_exchange{} = ServerKeyExchange) ->
+ Header = io_lib:format("~s Handshake, ServerKeyExchange",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(server_key_exchange, ServerKeyExchange)]),
+ {Header, Message};
+parse_handshake(Direction, #server_key_params{} = ServerKeyExchange) ->
+ Header = io_lib:format("~s Handshake, ServerKeyExchange",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(server_key_params, ServerKeyExchange)]),
+ {Header, Message};
+parse_handshake(Direction, #certificate_request{} = CertificateRequest) ->
+ Header = io_lib:format("~s Handshake, CertificateRequest",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(certificate_request, CertificateRequest)]),
+ {Header, Message};
+parse_handshake(Direction, #server_hello_done{} = ServerHelloDone) ->
+ Header = io_lib:format("~s Handshake, ServerHelloDone",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(server_hello_done, ServerHelloDone)]),
+ {Header, Message};
+parse_handshake(Direction, #client_key_exchange{} = ClientKeyExchange) ->
+ Header = io_lib:format("~s Handshake, ClientKeyExchange",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(client_key_exchange, ClientKeyExchange)]),
+ {Header, Message};
+parse_handshake(Direction, #certificate_verify{} = CertificateVerify) ->
+ Header = io_lib:format("~s Handshake, CertificateVerify",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(certificate_verify, CertificateVerify)]),
+ {Header, Message};
+parse_handshake(Direction, #finished{} = Finished) ->
+ Header = io_lib:format("~s Handshake, Finished",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(finished, Finished)]),
+ {Header, Message};
+parse_handshake(Direction, #hello_request{} = HelloRequest) ->
+ Header = io_lib:format("~s Handshake, HelloRequest",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(hello_request, HelloRequest)]),
+ {Header, Message};
+parse_handshake(Direction, #certificate_1_3{} = Certificate) ->
+ Header = io_lib:format("~s Handshake, Certificate",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(certificate_1_3, Certificate)]),
+ {Header, Message};
+parse_handshake(Direction, #certificate_verify_1_3{} = CertificateVerify) ->
+ Header = io_lib:format("~s Handshake, CertificateVerify",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(certificate_verify_1_3, CertificateVerify)]),
+ {Header, Message};
+parse_handshake(Direction, #encrypted_extensions{} = EncryptedExtensions) ->
+ Header = io_lib:format("~s Handshake, EncryptedExtensions",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(encrypted_extensions, EncryptedExtensions)]),
+ {Header, Message}.
+
+
+parse_cipher_suites([_|_] = Ciphers) ->
+ [format_cipher(C) || C <- Ciphers].
+
+format_cipher(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV) ->
+ 'TLS_EMPTY_RENEGOTIATION_INFO_SCSV';
+format_cipher(C0) ->
+ list_to_atom(ssl_cipher_format:openssl_suite_name(C0)).
+
+get_client_version(Version, Extensions) ->
+ CHVersions = maps:get(client_hello_versions, Extensions, undefined),
+ case CHVersions of
+ #client_hello_versions{versions = [Highest|_]} ->
+ Highest;
+ undefined ->
+ Version
+ end.
+
+get_server_version(Version, Extensions) ->
+ SHVersion = maps:get(server_hello_selected_version, Extensions, undefined),
+ case SHVersion of
+ #server_hello_selected_version{selected_version = SelectedVersion} ->
+ SelectedVersion;
+ undefined ->
+ Version
+ end.
+
+version({3,4}) ->
+ "TLS 1.3";
+version({3,3}) ->
+ "TLS 1.2";
+version({3,2}) ->
+ "TLS 1.1";
+version({3,1}) ->
+ "TLS 1.0";
+version({3,0}) ->
+ "SSL 3.0";
+version({254,253}) ->
+ "DTLS 1.2";
+version({254,255}) ->
+ "DTLS 1.0";
+version({M,N}) ->
+ io_lib:format("TLS/DTLS [0x0~B0~B]", [M,N]).
+
+header_prefix(inbound) ->
+ "<<<";
+header_prefix(outbound) ->
+ ">>>".
+
+
+%%-------------------------------------------------------------------------
+%% TLS Record Protocol
+%%-------------------------------------------------------------------------
+format_tls_record(Direction, BinMsg) ->
+ {Message, Size} = convert_to_hex('tls_record', BinMsg),
+ Header = io_lib:format("~s (~B bytes) ~s~n",
+ [header_prefix_tls_record(Direction),
+ Size,
+ tls_record_version(BinMsg)]),
+ Header ++ Message.
+
+
+header_prefix_tls_record(inbound) ->
+ "reading";
+header_prefix_tls_record(outbound) ->
+ "writing".
+
+
+tls_record_version([<<?BYTE(B),?BYTE(3),?BYTE(3),_/binary>>|_]) ->
+ io_lib:format("TLS 1.2 Record Protocol, ~s", [msg_type(B)]);
+tls_record_version([<<?BYTE(B),?BYTE(3),?BYTE(2),_/binary>>|_]) ->
+ io_lib:format("TLS 1.1 Record Protocol, ~s", [msg_type(B)]);
+tls_record_version([<<?BYTE(B),?BYTE(3),?BYTE(1),_/binary>>|_]) ->
+ io_lib:format("TLS 1.0 Record Protocol, ~s", [msg_type(B)]);
+tls_record_version([<<?BYTE(B),?BYTE(3),?BYTE(0),_/binary>>|_]) ->
+ io_lib:format("SSL 3.0 Record Protocol, ~s", [msg_type(B)]);
+tls_record_version([<<?BYTE(B),?BYTE(254),?BYTE(253),_/binary>>|_]) ->
+ io_lib:format("DTLS 1.2 Record Protocol, ~s", [msg_type(B)]);
+tls_record_version([<<?BYTE(B),?BYTE(254),?BYTE(255),_/binary>>|_]) ->
+ io_lib:format("DTLS 1.0 Record Protocol, ~s", [msg_type(B)]);
+tls_record_version([<<?BYTE(B),?BYTE(M),?BYTE(N),_/binary>>|_]) ->
+ io_lib:format("TLS/DTLS [0x0~B0~B] Record Protocol, ~s", [M, N, msg_type(B)]).
+
+
+msg_type(20) -> "change_cipher_spec";
+msg_type(21) -> "alert";
+msg_type(22) -> "handshake";
+msg_type(23) -> "application_data";
+msg_type(_) -> unknown.
+
+
+%%-------------------------------------------------------------------------
+%% Hex encoding functions
+%%-------------------------------------------------------------------------
+convert_to_hex(Protocol, BinMsg) ->
+ convert_to_hex(Protocol, BinMsg, [], [], 0).
+%%
+convert_to_hex(P, [], Row0, Acc, C) when C rem 16 =:= 0 ->
+ Row = lists:reverse(end_row(P, Row0)),
+ {lists:reverse(Acc) ++ Row ++ io_lib:nl(), C};
+convert_to_hex(P, [], Row0, Acc, C) ->
+ Row = lists:reverse(end_row(P, Row0)),
+ Padding = calculate_padding(Row0, Acc),
+ PaddedRow = string:pad(Row, Padding, leading, $ ),
+ {lists:reverse(Acc) ++ PaddedRow ++ io_lib:nl(), C};
+convert_to_hex(P, [H|T], Row, Acc, C) when is_list(H) ->
+ convert_to_hex(P, H ++ T, Row, Acc, C);
+convert_to_hex(P, [<<>>|T], Row, Acc, C) ->
+ convert_to_hex(P, T, Row, Acc, C);
+
+%% First line
+convert_to_hex(P, [<<A:4,B:4,R/binary>>|T], Row, Acc, C) when C =:= 0 ->
+ convert_to_hex(P, [<<R/binary>>|T],
+ update_row(<<A:4,B:4>>, Row),
+ prepend_first_row(P, A, B, Acc, C),
+ C + 1);
+%% New line
+convert_to_hex(P, [<<A:4,B:4,R/binary>>|T], Row, Acc, C) when C rem 16 =:= 0 ->
+ convert_to_hex(P, [<<R/binary>>|T],
+ update_row(<<A:4,B:4>>, []),
+ prepend_row(P, A, B, Row, Acc, C),
+ C + 1);
+%% Add 8th hex with extra whitespace
+%% 0000 - 16 03 02 00 bd 01 00 00 b9 ...
+%% ^^^^
+convert_to_hex(P, [<<A:4,B:4,R/binary>>|T], Row, Acc, C) when C rem 8 =:= 7 ->
+ convert_to_hex(P, [<<R/binary>>|T],
+ update_row(<<A:4,B:4>>, Row),
+ prepend_eighths_hex(A, B, Acc),
+ C + 1);
+convert_to_hex(P, [<<A:4,B:4,R/binary>>|T], Row, Acc, C) ->
+ convert_to_hex(P, [<<R/binary>>|T],
+ update_row(<<A:4,B:4>>, Row),
+ prepend_hex(A, B, Acc),
+ C + 1);
+%% First line
+convert_to_hex(P, [H|T], Row, Acc, C) when is_integer(H), C =:= 0 ->
+ convert_to_hex(P, T,
+ update_row(H, Row),
+ prepend_first_row(P, H, Acc, C),
+ C + 1);
+%% New line
+convert_to_hex(P, [H|T], Row, Acc, C) when is_integer(H), C rem 16 =:= 0 ->
+ convert_to_hex(P, T,
+ update_row(H, []),
+ prepend_row(P, H, Row, Acc, C),
+ C + 1);
+%% Add 8th hex with extra whitespace
+%% 0000 - 16 03 02 00 bd 01 00 00 b9 ...
+%% ^^^^
+convert_to_hex(P, [H|T], Row, Acc, C) when is_integer(H), C rem 8 =:= 7 ->
+ convert_to_hex(P, T,
+ update_row(H, Row),
+ prepend_eighths_hex(H, Acc),
+ C + 1);
+convert_to_hex(P, [H|T], Row, Acc, C) when is_integer(H) ->
+ convert_to_hex(P, T,
+ update_row(H, Row),
+ prepend_hex(H, Acc),
+ C + 1).
+
+
+row_prefix(_ , N) ->
+ S = string:pad(string:to_lower(erlang:integer_to_list(N, 16)),4,leading,$0),
+ lists:reverse(lists:flatten(S ++ " - ")).
+
+
+end_row(_, Row) ->
+ Row ++ " ".
+
+
+%% Calculate padding of the "printable character" lines in order to be
+%% visually aligned.
+calculate_padding(Row, Acc) ->
+ %% Number of new line characters
+ NNL = (length(Acc) div 75) * length(io_lib:nl()),
+ %% Length of the last printed line
+ Length = (length(Acc) - NNL) rem 75,
+ %% Adjusted length of the last printed line
+ PaddedLength = 75 - (16 - length(Row)), %% Length
+ %% Padding
+ PaddedLength - Length.
+
+
+%%-------------------------------------------------------------------------
+%% Functions operating on reversed lists
+%%-------------------------------------------------------------------------
+update_row(B, Row) when is_binary(B) ->
+ case binary_to_list(B) of
+ [C] when 32 =< C, C =< 126 ->
+ [C|Row];
+ _Else ->
+ [$.|Row]
+ end;
+update_row(C, Row) when 32 =< C, C =< 126 ->
+ [C|Row];
+update_row(_, Row) ->
+ [$.|Row].
+
+
+prepend_first_row(P, A, B, Acc, C) ->
+ prepend_hex(A, B,row_prefix(P, C) ++ Acc).
+%%
+prepend_first_row(P, N, Acc, C) ->
+ prepend_hex(N,row_prefix(P, C) ++ Acc).
+
+prepend_row(P, A, B, Row, Acc, C) ->
+ prepend_hex(A, B,row_prefix(P, C) ++ io_lib:nl() ++ end_row(P, Row) ++ Acc).
+%%
+prepend_row(P, N, Row, Acc, C) ->
+ prepend_hex(N,row_prefix(P, C) ++ io_lib:nl() ++ end_row(P, Row) ++ Acc).
+
+
+
+prepend_hex(A, B, Acc) ->
+ [$ ,?DEC2HEX(B),?DEC2HEX(A)|Acc].
+%%
+prepend_hex(N, Acc) ->
+ " " ++ number_to_hex(N) ++ Acc.
+
+
+prepend_eighths_hex(A, B, Acc) ->
+ [$ ,$ ,?DEC2HEX(B),?DEC2HEX(A)|Acc].
+%%
+prepend_eighths_hex(N, Acc) ->
+ " " ++ number_to_hex(N) ++ Acc.
+
+number_to_hex(N) ->
+ case string:to_lower(erlang:integer_to_list(N, 16)) of
+ H when length(H) < 2 ->
+ lists:append(H, "0");
+ H ->
+ lists:reverse(H)
+ end.
diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl
index 4b735b2400..456a560bf6 100644
--- a/lib/ssl/src/ssl_manager.erl
+++ b/lib/ssl/src/ssl_manager.erl
@@ -30,7 +30,7 @@
connection_init/3, cache_pem_file/2,
lookup_trusted_cert/4,
new_session_id/1, clean_cert_db/2,
- register_session/2, register_session/3, invalidate_session/2,
+ register_session/2, register_session/4, invalidate_session/2,
insert_crls/2, insert_crls/3, delete_crls/1, delete_crls/2,
invalidate_session/3, name/1]).
@@ -42,6 +42,8 @@
-include("ssl_handshake.hrl").
-include("ssl_internal.hrl").
+-include("ssl_api.hrl").
+
-include_lib("kernel/include/file.hrl").
-record(state, {
@@ -148,7 +150,7 @@ lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer) ->
ssl_pkix_db:lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer).
%%--------------------------------------------------------------------
--spec new_session_id(integer()) -> session_id().
+-spec new_session_id(integer()) -> ssl:session_id().
%%
%% Description: Creates a session id for the server.
%%--------------------------------------------------------------------
@@ -170,9 +172,11 @@ clean_cert_db(Ref, File) ->
%%
%% Description: Make the session available for reuse.
%%--------------------------------------------------------------------
--spec register_session(host(), inet:port_number(), #session{}) -> ok.
-register_session(Host, Port, Session) ->
- cast({register_session, Host, Port, Session}).
+-spec register_session(ssl:host(), inet:port_number(), #session{}, unique | true) -> ok.
+register_session(Host, Port, Session, true) ->
+ call({register_session, Host, Port, Session});
+register_session(Host, Port, Session, unique = Save) ->
+ cast({register_session, Host, Port, Session, Save}).
-spec register_session(inet:port_number(), #session{}) -> ok.
register_session(Port, Session) ->
@@ -183,7 +187,7 @@ register_session(Port, Session) ->
%% a the session has been marked "is_resumable = false" for some while
%% it will be safe to remove the data from the session database.
%%--------------------------------------------------------------------
--spec invalidate_session(host(), inet:port_number(), #session{}) -> ok.
+-spec invalidate_session(ssl:host(), inet:port_number(), #session{}) -> ok.
invalidate_session(Host, Port, Session) ->
load_mitigation(),
cast({invalidate_session, Host, Port, Session}).
@@ -301,7 +305,10 @@ handle_call({{new_session_id, Port}, _},
_, #state{session_cache_cb = CacheCb,
session_cache_server = Cache} = State) ->
Id = new_id(Port, ?GEN_UNIQUE_ID_MAX_TRIES, Cache, CacheCb),
- {reply, Id, State}.
+ {reply, Id, State};
+handle_call({{register_session, Host, Port, Session},_}, _, State0) ->
+ State = client_register_session(Host, Port, Session, State0),
+ {reply, ok, State}.
%%--------------------------------------------------------------------
-spec handle_cast(msg(), #state{}) -> {noreply, #state{}}.
@@ -311,8 +318,12 @@ handle_call({{new_session_id, Port}, _},
%%
%% Description: Handling cast messages
%%--------------------------------------------------------------------
-handle_cast({register_session, Host, Port, Session}, State0) ->
- State = ssl_client_register_session(Host, Port, Session, State0),
+handle_cast({register_session, Host, Port, Session, unique}, State0) ->
+ State = client_register_unique_session(Host, Port, Session, State0),
+ {noreply, State};
+
+handle_cast({register_session, Host, Port, Session, true}, State0) ->
+ State = client_register_session(Host, Port, Session, State0),
{noreply, State};
handle_cast({register_session, Port, Session}, State0) ->
@@ -505,10 +516,10 @@ last_delay_timer({{_,_},_}, TRef, {LastServer, _}) ->
last_delay_timer({_,_}, TRef, {_, LastClient}) ->
{TRef, LastClient}.
-%% If we can not generate a not allready in use session ID in
+%% If we cannot generate a not allready in use session ID in
%% ?GEN_UNIQUE_ID_MAX_TRIES we make the new session uncacheable The
%% value of ?GEN_UNIQUE_ID_MAX_TRIES is stolen from open SSL which
-%% states : "If we can not find a session id in
+%% states : "If we cannot find a session id in
%% ?GEN_UNIQUE_ID_MAX_TRIES either the RAND code is broken or someone
%% is trying to open roughly very close to 2^128 (or 2^256) SSL
%% sessions to our server"
@@ -519,7 +530,7 @@ new_id(Port, Tries, Cache, CacheCb) ->
case CacheCb:lookup(Cache, {Port, Id}) of
undefined ->
Now = erlang:monotonic_time(),
- %% New sessions can not be set to resumable
+ %% New sessions cannot be set to resumable
%% until handshake is compleate and the
%% other session values are set.
CacheCb:update(Cache, {Port, Id}, #session{session_id = Id,
@@ -540,10 +551,10 @@ clean_cert_db(Ref, CertDb, RefDb, FileMapDb, File) ->
ok
end.
-ssl_client_register_session(Host, Port, Session, #state{session_cache_client = Cache,
- session_cache_cb = CacheCb,
- session_cache_client_max = Max,
- session_client_invalidator = Pid0} = State) ->
+client_register_unique_session(Host, Port, Session, #state{session_cache_client = Cache,
+ session_cache_cb = CacheCb,
+ session_cache_client_max = Max,
+ session_client_invalidator = Pid0} = State) ->
TimeStamp = erlang:monotonic_time(),
NewSession = Session#session{time_stamp = TimeStamp},
@@ -557,6 +568,17 @@ ssl_client_register_session(Host, Port, Session, #state{session_cache_client = C
register_unique_session(Sessions, NewSession, {Host, Port}, State)
end.
+client_register_session(Host, Port, Session, #state{session_cache_client = Cache,
+ session_cache_cb = CacheCb,
+ session_cache_client_max = Max,
+ session_client_invalidator = Pid0} = State) ->
+ TimeStamp = erlang:monotonic_time(),
+ NewSession = Session#session{time_stamp = TimeStamp},
+ Pid = do_register_session({{Host, Port},
+ NewSession#session.session_id},
+ NewSession, Max, Pid0, Cache, CacheCb),
+ State#state{session_client_invalidator = Pid}.
+
server_register_session(Port, Session, #state{session_cache_server_max = Max,
session_cache_server = Cache,
session_cache_cb = CacheCb,
diff --git a/lib/ssl/src/ssl_pkix_db.erl b/lib/ssl/src/ssl_pkix_db.erl
index f7ddbd060e..dec48fa914 100644
--- a/lib/ssl/src/ssl_pkix_db.erl
+++ b/lib/ssl/src/ssl_pkix_db.erl
@@ -27,6 +27,7 @@
-include("ssl_internal.hrl").
-include_lib("public_key/include/public_key.hrl").
-include_lib("kernel/include/file.hrl").
+-include_lib("kernel/include/logger.hrl").
-export([create/1, create_pem_cache/1,
add_crls/3, remove_crls/2, remove/1, add_trusted_certs/3,
@@ -311,7 +312,7 @@ decode_certs(Ref, Cert) ->
error:_ ->
Report = io_lib:format("SSL WARNING: Ignoring a CA cert as "
"it could not be correctly decoded.~n", []),
- error_logger:info_report(Report),
+ ?LOG_NOTICE(Report),
undefined
end.
diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl
index b9d1320ef3..91f1876980 100644
--- a/lib/ssl/src/ssl_record.erl
+++ b/lib/ssl/src/ssl_record.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2019. All 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 @@
-module(ssl_record).
-include("ssl_record.hrl").
+-include("ssl_connection.hrl").
-include("ssl_internal.hrl").
-include("ssl_cipher.hrl").
-include("ssl_alert.hrl").
@@ -39,20 +40,23 @@
set_renegotiation_flag/2,
set_client_verify_data/3,
set_server_verify_data/3,
- empty_connection_state/2, initial_connection_state/2, record_protocol_role/1]).
+ empty_connection_state/2, initial_connection_state/2, record_protocol_role/1,
+ step_encryption_state/1]).
%% Compression
-export([compress/3, uncompress/3, compressions/0]).
%% Payload encryption/decryption
--export([cipher/4, decipher/4, cipher_aead/4, decipher_aead/5, is_correct_mac/2, nonce_seed/3]).
+-export([cipher/4, cipher/5, decipher/4,
+ cipher_aead/4, cipher_aead/5, decipher_aead/5,
+ is_correct_mac/2, nonce_seed/3]).
-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 connection_states() :: term(). %% Map
--type connection_state() :: term(). %% Map
+-type connection_states() :: map(). %% Map
+-type connection_state() :: map(). %% Map
%%====================================================================
%% Connection state handling
@@ -118,6 +122,22 @@ activate_pending_connection_state(#{current_write := Current,
}.
%%--------------------------------------------------------------------
+-spec step_encryption_state(#state{}) -> #state{}.
+%%
+%% Description: Activates the next encyrption state (e.g. handshake
+%% encryption).
+%%--------------------------------------------------------------------
+step_encryption_state(#state{connection_states =
+ #{pending_read := PendingRead,
+ pending_write := PendingWrite} = ConnStates} = State) ->
+ NewRead = PendingRead#{sequence_number => 0},
+ NewWrite = PendingWrite#{sequence_number => 0},
+ State#state{connection_states =
+ ConnStates#{current_read => NewRead,
+ current_write => NewWrite}}.
+
+
+%%--------------------------------------------------------------------
-spec set_security_params(#security_parameters{}, #security_parameters{},
connection_states()) -> connection_states().
%%
@@ -278,13 +298,12 @@ compress(?NULL, Data, CS) ->
{Data, CS}.
%%--------------------------------------------------------------------
--spec compressions() -> [binary()].
+-spec compressions() -> [integer()].
%%
%% Description: return a list of compressions supported (currently none)
%%--------------------------------------------------------------------
compressions() ->
- [?byte(?NULL)].
-
+ [?NULL].
%%====================================================================
%% Payload encryption/decryption
@@ -302,27 +321,49 @@ cipher(Version, Fragment,
#security_parameters{bulk_cipher_algorithm =
BulkCipherAlgo}
} = WriteState0, MacHash) ->
-
+ %%
{CipherFragment, CipherS1} =
ssl_cipher:cipher(BulkCipherAlgo, CipherS0, MacHash, Fragment, Version),
{CipherFragment, WriteState0#{cipher_state => CipherS1}}.
+
+%%--------------------------------------------------------------------
+-spec cipher(ssl_version(), iodata(), #cipher_state{}, MacHash::binary(), #security_parameters{}) ->
+ {CipherFragment::binary(), #cipher_state{}}.
+%%
+%% Description: Payload encryption
+%%--------------------------------------------------------------------
+cipher(Version, Fragment, CipherS0, MacHash,
+ #security_parameters{bulk_cipher_algorithm = BulkCipherAlgo}) ->
+ %%
+ ssl_cipher:cipher(BulkCipherAlgo, CipherS0, MacHash, Fragment, Version).
+
%%--------------------------------------------------------------------
-spec cipher_aead(ssl_version(), iodata(), connection_state(), AAD::binary()) ->
{CipherFragment::binary(), connection_state()}.
%% Description: Payload encryption
%% %%--------------------------------------------------------------------
-cipher_aead(Version, Fragment,
+cipher_aead(_Version, Fragment,
#{cipher_state := CipherS0,
security_parameters :=
#security_parameters{bulk_cipher_algorithm =
BulkCipherAlgo}
} = WriteState0, AAD) ->
{CipherFragment, CipherS1} =
- cipher_aead(BulkCipherAlgo, CipherS0, AAD, Fragment, Version),
+ do_cipher_aead(BulkCipherAlgo, Fragment, CipherS0, AAD),
{CipherFragment, WriteState0#{cipher_state => CipherS1}}.
%%--------------------------------------------------------------------
+-spec cipher_aead(ssl_version(), iodata(), #cipher_state{}, AAD::binary(), #security_parameters{}) ->
+ {CipherFragment::binary(), #cipher_state{}}.
+
+%% Description: Payload encryption
+%% %%--------------------------------------------------------------------
+cipher_aead(_Version, Fragment, CipherS, AAD,
+ #security_parameters{bulk_cipher_algorithm = BulkCipherAlgo}) ->
+ do_cipher_aead(BulkCipherAlgo, Fragment, CipherS, AAD).
+
+%%--------------------------------------------------------------------
-spec decipher(ssl_version(), binary(), connection_state(), boolean()) ->
{binary(), binary(), connection_state()} | #alert{}.
%%
@@ -343,9 +384,8 @@ decipher(Version, CipherFragment,
Alert
end.
%%--------------------------------------------------------------------
--spec decipher_aead(ssl_cipher:cipher_enum(), #cipher_state{},
- binary(), binary(), ssl_record:ssl_version()) ->
- {binary(), #cipher_state{}} | #alert{}.
+-spec decipher_aead(ssl_cipher:cipher_enum(), #cipher_state{}, binary(), binary(), ssl_record:ssl_version()) ->
+ binary() | #alert{}.
%%
%% Description: Decrypts the data and checks the associated data (AAD) MAC using
%% cipher described by cipher_enum() and updating the cipher state.
@@ -357,7 +397,7 @@ decipher_aead(Type, #cipher_state{key = Key} = CipherState, AAD0, CipherFragment
{AAD, CipherText, CipherTag} = aead_ciphertext_split(Type, CipherState, CipherFragment, AAD0),
case ssl_cipher:aead_decrypt(Type, Key, Nonce, CipherText, CipherTag, AAD) of
Content when is_binary(Content) ->
- {Content, CipherState};
+ Content;
_ ->
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed)
end
@@ -399,11 +439,13 @@ random() ->
Random_28_bytes = ssl_cipher:random_bytes(28),
<<?UINT32(Secs_since_1970), Random_28_bytes/binary>>.
+-compile({inline, [is_correct_mac/2]}).
is_correct_mac(Mac, Mac) ->
true;
is_correct_mac(_M,_H) ->
false.
+-compile({inline, [record_protocol_role/1]}).
record_protocol_role(client) ->
?CLIENT;
record_protocol_role(server) ->
@@ -427,13 +469,15 @@ initial_security_params(ConnectionEnd) ->
compression_algorithm = ?NULL},
ssl_cipher:security_parameters(?TLS_NULL_WITH_NULL_NULL, SecParams).
-cipher_aead(?CHACHA20_POLY1305 = Type, #cipher_state{key=Key} = CipherState, AAD0, Fragment, _Version) ->
- AAD = end_additional_data(AAD0, erlang:iolist_size(Fragment)),
+-define(end_additional_data(AAD, Len), << (begin(AAD)end)/binary, ?UINT16(begin(Len)end) >>).
+
+do_cipher_aead(?CHACHA20_POLY1305 = Type, Fragment, #cipher_state{key=Key} = CipherState, AAD0) ->
+ AAD = ?end_additional_data(AAD0, erlang:iolist_size(Fragment)),
Nonce = encrypt_nonce(Type, CipherState),
{Content, CipherTag} = ssl_cipher:aead_encrypt(Type, Key, Nonce, Fragment, AAD),
{<<Content/binary, CipherTag/binary>>, CipherState};
-cipher_aead(Type, #cipher_state{key=Key, nonce = ExplicitNonce} = CipherState, AAD0, Fragment, _Version) ->
- AAD = end_additional_data(AAD0, erlang:iolist_size(Fragment)),
+do_cipher_aead(Type, Fragment, #cipher_state{key=Key, nonce = ExplicitNonce} = CipherState, AAD0) ->
+ AAD = ?end_additional_data(AAD0, erlang:iolist_size(Fragment)),
Nonce = encrypt_nonce(Type, CipherState),
{Content, CipherTag} = ssl_cipher:aead_encrypt(Type, Key, Nonce, Fragment, AAD),
{<<ExplicitNonce:64/integer, Content/binary, CipherTag/binary>>, CipherState#cipher_state{nonce = ExplicitNonce + 1}}.
@@ -449,15 +493,12 @@ decrypt_nonce(?CHACHA20_POLY1305, #cipher_state{nonce = Nonce, iv = IV}, _) ->
decrypt_nonce(?AES_GCM, #cipher_state{iv = <<Salt:4/bytes, _/binary>>}, <<ExplicitNonce:8/bytes, _/binary>>) ->
<<Salt/binary, ExplicitNonce/binary>>.
+-compile({inline, [aead_ciphertext_split/4]}).
aead_ciphertext_split(?CHACHA20_POLY1305, #cipher_state{tag_len = Len}, CipherTextFragment, AAD) ->
- CipherLen = size(CipherTextFragment) - Len,
+ CipherLen = byte_size(CipherTextFragment) - Len,
<<CipherText:CipherLen/bytes, CipherTag:Len/bytes>> = CipherTextFragment,
- {end_additional_data(AAD, CipherLen), CipherText, CipherTag};
+ {?end_additional_data(AAD, CipherLen), CipherText, CipherTag};
aead_ciphertext_split(?AES_GCM, #cipher_state{tag_len = Len}, CipherTextFragment, AAD) ->
- CipherLen = size(CipherTextFragment) - (Len + 8), %% 8 is length of explicit Nonce
+ CipherLen = byte_size(CipherTextFragment) - (Len + 8), %% 8 is length of explicit Nonce
<< _:8/bytes, CipherText:CipherLen/bytes, CipherTag:Len/bytes>> = CipherTextFragment,
- {end_additional_data(AAD, CipherLen), CipherText, CipherTag}.
-
-end_additional_data(AAD, Len) ->
- <<AAD/binary, ?UINT16(Len)>>.
-
+ {?end_additional_data(AAD, CipherLen), CipherText, CipherTag}.
diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl
index ed007f58d7..eb718fd20c 100644
--- a/lib/ssl/src/ssl_record.hrl
+++ b/lib/ssl/src/ssl_record.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2019. All 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 @@
-define(INITIAL_BYTES, 5).
-define(MAX_SEQENCE_NUMBER, 18446744073709551615). %% (1 bsl 64) - 1 = 18446744073709551615
-%% Sequence numbers can not wrap so when max is about to be reached we should renegotiate.
+%% Sequence numbers cannot wrap so when max is about to be reached we should renegotiate.
%% We will renegotiate a little before so that there will be sequence numbers left
%% for the rehandshake and a little data. Currently we decided to renegotiate a little more
%% often as we can have a cheaper test to check if it is time to renegotiate. It will still
@@ -140,6 +140,9 @@
-define(ALERT, 21).
-define(HANDSHAKE, 22).
-define(APPLICATION_DATA, 23).
+-define(HEARTBEAT, 24).
+-define(KNOWN_RECORD_TYPE(Type),
+ (is_integer(Type) andalso (20 =< (Type)) andalso ((Type) =< 23))).
-define(MAX_PLAIN_TEXT_LENGTH, 16384).
-define(MAX_COMPRESSED_LENGTH, (?MAX_PLAIN_TEXT_LENGTH+1024)).
-define(MAX_CIPHER_TEXT_LENGTH, (?MAX_PLAIN_TEXT_LENGTH+2048)).
diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl
index c9607489e9..44305c65fe 100644
--- a/lib/ssl/src/ssl_session.erl
+++ b/lib/ssl/src/ssl_session.erl
@@ -27,6 +27,7 @@
-include("ssl_handshake.hrl").
-include("ssl_internal.hrl").
+-include("ssl_api.hrl").
%% Internal application API
-export([is_new/2, client_id/4, server_id/6, valid_session/2]).
@@ -34,7 +35,7 @@
-type seconds() :: integer().
%%--------------------------------------------------------------------
--spec is_new(session_id(), session_id()) -> boolean().
+-spec is_new(ssl:session_id(), ssl:session_id()) -> boolean().
%%
%% Description: Checks if the session id decided by the server is a
%% new or resumed sesion id.
@@ -47,12 +48,19 @@ is_new(_ClientSuggestion, _ServerDecision) ->
true.
%%--------------------------------------------------------------------
--spec client_id({host(), inet:port_number(), #ssl_options{}}, db_handle(), atom(),
+-spec client_id({ssl:host(), inet:port_number(), #ssl_options{}}, db_handle(), atom(),
undefined | binary()) -> binary().
%%
%% Description: Should be called by the client side to get an id
%% for the client hello message.
%%--------------------------------------------------------------------
+client_id({Host, Port, #ssl_options{reuse_session = SessionId}}, Cache, CacheCb, _) when is_binary(SessionId)->
+ case CacheCb:lookup(Cache, {{Host, Port}, SessionId}) of
+ undefined ->
+ <<>>;
+ #session{} ->
+ SessionId
+ end;
client_id(ClientInfo, Cache, CacheCb, OwnCert) ->
case select_session(ClientInfo, Cache, CacheCb, OwnCert) of
no_session ->
@@ -91,7 +99,8 @@ server_id(Port, SuggestedId, Options, Cert, Cache, CacheCb) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-select_session({_, _, #ssl_options{reuse_sessions=false}}, _Cache, _CacheCb, _OwnCert) ->
+select_session({_, _, #ssl_options{reuse_sessions = Reuse}}, _Cache, _CacheCb, _OwnCert) when Reuse =/= true ->
+ %% If reuse_sessions == true | save a new session should be created
no_session;
select_session({HostIP, Port, SslOpts}, Cache, CacheCb, OwnCert) ->
Sessions = CacheCb:select_session(Cache, {HostIP, Port}),
@@ -132,7 +141,7 @@ is_resumable(SuggestedSessionId, Port, #ssl_options{reuse_session = ReuseFun} =
false -> {false, undefined}
end;
undefined ->
- {false, undefined}
+ {false, undefined}
end.
resumable(new) ->
diff --git a/lib/ssl/src/ssl_session_cache_api.erl b/lib/ssl/src/ssl_session_cache_api.erl
index b68c75a09b..5f96f905b1 100644
--- a/lib/ssl/src/ssl_session_cache_api.erl
+++ b/lib/ssl/src/ssl_session_cache_api.erl
@@ -23,14 +23,20 @@
-module(ssl_session_cache_api).
-include("ssl_handshake.hrl").
-include("ssl_internal.hrl").
+-include("ssl_api.hrl").
--type key() :: {{host(), inet:port_number()}, session_id()} | {inet:port_number(), session_id()}.
+-export_type([session_cache_key/0, session/0, partial_key/0, session_cache_ref/0]).
--callback init(list()) -> db_handle().
--callback terminate(db_handle()) -> any().
--callback lookup(db_handle(), key()) -> #session{} | undefined.
--callback update(db_handle(), key(), #session{}) -> any().
--callback delete(db_handle(), key()) -> any().
--callback foldl(fun(), term(), db_handle()) -> term().
--callback select_session(db_handle(), {host(), inet:port_number()} | inet:port_number()) -> [#session{}].
--callback size(db_handle()) -> integer().
+-type session_cache_ref() :: any().
+-type session_cache_key() :: {partial_key(), ssl:session_id()}.
+-opaque session() :: #session{}.
+-opaque partial_key() :: {ssl:host(), inet:port_number()} | inet:port_number().
+
+-callback init(list()) -> session_cache_ref().
+-callback terminate(session_cache_ref()) -> any().
+-callback lookup(session_cache_ref(), session_cache_key()) -> #session{} | undefined.
+-callback update(session_cache_ref(), session_cache_key(), #session{}) -> any().
+-callback delete(session_cache_ref(), session_cache_key()) -> any().
+-callback foldl(fun(), term(), session_cache_ref()) -> term().
+-callback select_session(session_cache_ref(), {ssl:host(), inet:port_number()} | inet:port_number()) -> [#session{}].
+-callback size(session_cache_ref()) -> integer().
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index 80a8c6b72c..8eb9e56375 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2019. All 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,8 @@
-include("ssl_api.hrl").
-include("ssl_internal.hrl").
-include("ssl_srp.hrl").
--include_lib("public_key/include/public_key.hrl").
+-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/logger.hrl").
%% Internal application API
@@ -49,7 +50,8 @@
handle_protocol_record/3]).
%% Handshake handling
--export([renegotiation/2, renegotiate/2, send_handshake/2,
+-export([renegotiation/2, renegotiate/2, send_handshake/2,
+ send_handshake_flight/1,
queue_handshake/2, queue_change_cipher/2,
reinit/1, reinit_handshake_data/1, select_sni_extension/1,
empty_connection_state/2]).
@@ -57,19 +59,35 @@
%% Alert and close handling
-export([send_alert/2, send_alert_in_connection/2,
send_sync_alert/2,
- encode_alert/3, close/5, protocol_name/0]).
+ close/5, protocol_name/0]).
%% Data handling
--export([encode_data/3, next_record/1,
- send/3, socket/5, setopts/3, getopts/3]).
+-export([next_record/1, socket/4, setopts/3, getopts/3]).
%% gen_statem state functions
-export([init/3, error/3, downgrade/3, %% Initiation and take down states
hello/3, user_hello/3, certify/3, cipher/3, abbreviated/3, %% Handshake states
connection/3]).
+%% TLS 1.3 state functions (server)
+-export([start/3, %% common state with client
+ negotiated/3,
+ recvd_ch/3,
+ wait_cert/3, %% common state with client
+ wait_cv/3, %% common state with client
+ wait_eoed/3,
+ wait_finished/3, %% common state with client
+ wait_flight2/3,
+ connected/3 %% common state with client
+ ]).
+%% TLS 1.3 state functions (client)
+-export([wait_cert_cr/3,
+ wait_ee/3,
+ wait_sh/3
+ ]).
%% gen_statem callbacks
-export([callback_mode/0, terminate/3, code_change/4, format_status/2]).
+-export([encode_handshake/4]).
-define(DIST_CNTRL_SPAWN_OPTS, [{priority, max}]).
@@ -108,7 +126,7 @@ start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = true},_, Tracker} =
end.
%%--------------------------------------------------------------------
--spec start_link(atom(), pid(), host(), inet:port_number(), port(), list(), pid(), tuple()) ->
+-spec start_link(atom(), pid(), ssl:host(), inet:port_number(), port(), list(), pid(), tuple()) ->
{ok, pid()} | ignore | {error, reason()}.
%%
%% Description: Creates a gen_statem process which calls Module:init/1 to
@@ -143,23 +161,17 @@ pids(#state{protocol_specific = #{sender := Sender}}) ->
%%====================================================================
%% State transition handling
%%====================================================================
-next_record(#state{unprocessed_handshake_events = N} = State) when N > 0 ->
- {no_record, State#state{unprocessed_handshake_events = N-1}};
-
+next_record(#state{handshake_env =
+ #handshake_env{unprocessed_handshake_events = N} = HsEnv}
+ = State) when N > 0 ->
+ {no_record, State#state{handshake_env =
+ HsEnv#handshake_env{unprocessed_handshake_events = N-1}}};
next_record(#state{protocol_buffers =
- #protocol_buffers{tls_packets = [], tls_cipher_texts = [CT | Rest]}
- = Buffers,
- connection_states = ConnStates0,
- ssl_options = #ssl_options{padding_check = Check}} = State) ->
- case tls_record:decode_cipher_text(CT, ConnStates0, Check) of
- {Plain, ConnStates} ->
- {Plain, State#state{protocol_buffers =
- Buffers#protocol_buffers{tls_cipher_texts = Rest},
- connection_states = ConnStates}};
- #alert{} = Alert ->
- {Alert, State}
- end;
-next_record(#state{protocol_buffers = #protocol_buffers{tls_packets = [], tls_cipher_texts = []},
+ #protocol_buffers{tls_cipher_texts = [_|_] = CipherTexts},
+ connection_states = ConnectionStates,
+ ssl_options = #ssl_options{padding_check = Check}} = State) ->
+ next_record(State, CipherTexts, ConnectionStates, Check);
+next_record(#state{protocol_buffers = #protocol_buffers{tls_cipher_texts = []},
protocol_specific = #{active_n_toggle := true, active_n := N} = ProtocolSpec,
static_env = #static_env{socket = Socket,
close_tag = CloseTag,
@@ -175,16 +187,56 @@ next_record(#state{protocol_buffers = #protocol_buffers{tls_packets = [], tls_ci
next_record(State) ->
{no_record, State}.
+%% Decipher next record and concatenate consecutive ?APPLICATION_DATA records into one
+%%
+next_record(State, CipherTexts, ConnectionStates, Check) ->
+ next_record(State, CipherTexts, ConnectionStates, Check, []).
+%%
+next_record(#state{connection_env = #connection_env{negotiated_version = Version}} = State,
+ [CT|CipherTexts], ConnectionStates0, Check, Acc) ->
+ case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of
+ {#ssl_tls{type = ?APPLICATION_DATA, fragment = Fragment}, ConnectionStates} ->
+ case CipherTexts of
+ [] ->
+ %% End of cipher texts - build and deliver an ?APPLICATION_DATA record
+ %% from the accumulated fragments
+ next_record_done(State, [], ConnectionStates,
+ #ssl_tls{type = ?APPLICATION_DATA,
+ fragment = iolist_to_binary(lists:reverse(Acc, [Fragment]))});
+ [_|_] ->
+ next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc])
+ end;
+ {Record, ConnectionStates} when Acc =:= [] ->
+ %% Singelton non-?APPLICATION_DATA record - deliver
+ next_record_done(State, CipherTexts, ConnectionStates, Record);
+ {_Record, _ConnectionStates_to_forget} ->
+ %% Not ?APPLICATION_DATA but we have accumulated fragments
+ %% -> build an ?APPLICATION_DATA record with concatenated fragments
+ %% and forget about decrypting this record - we'll decrypt it again next time
+ next_record_done(State, [CT|CipherTexts], ConnectionStates0,
+ #ssl_tls{type = ?APPLICATION_DATA, fragment = iolist_to_binary(lists:reverse(Acc))});
+ #alert{} = Alert ->
+ Alert
+ end.
+
+next_record_done(#state{protocol_buffers = Buffers} = State, CipherTexts, ConnectionStates, Record) ->
+ {Record,
+ State#state{protocol_buffers = Buffers#protocol_buffers{tls_cipher_texts = CipherTexts},
+ connection_states = ConnectionStates}}.
+
+
next_event(StateName, Record, State) ->
next_event(StateName, Record, State, []).
+%%
next_event(StateName, no_record, State0, Actions) ->
case next_record(State0) of
{no_record, State} ->
{next_state, StateName, State, Actions};
{#ssl_tls{} = Record, State} ->
{next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]};
- {#alert{} = Alert, State} ->
- {next_state, StateName, State, [{next_event, internal, Alert} | Actions]}
+ #alert{} = Alert ->
+ Version = State0#state.connection_env#connection_env.negotiated_version,
+ ssl_connection:handle_own_alert(Alert, Version, StateName, State0)
end;
next_event(StateName, Record, State, Actions) ->
case Record of
@@ -193,27 +245,34 @@ next_event(StateName, Record, State, Actions) ->
#ssl_tls{} = Record ->
{next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]};
#alert{} = Alert ->
- {next_state, StateName, State, [{next_event, internal, Alert} | Actions]}
+ Version = State#state.connection_env#connection_env.negotiated_version,
+ ssl_connection:handle_own_alert(Alert, Version, StateName, State)
end.
+
%%% TLS record protocol level application data messages
-handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, State0) ->
+handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName0, State0) ->
case ssl_connection:read_application_data(Data, State0) of
{stop, _, _} = Stop->
Stop;
{Record, State1} ->
- {next_state, StateName, State, Actions} = next_event(StateName, Record, State1),
- ssl_connection:hibernate_after(StateName, State, Actions)
+ case next_event(StateName0, Record, State1) of
+ {next_state, StateName, State, Actions} ->
+ ssl_connection:hibernate_after(StateName, State, Actions);
+ {stop, _, _} = Stop ->
+ Stop
+ end
end;
%%% TLS record protocol level handshake messages
handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data},
StateName, #state{protocol_buffers =
#protocol_buffers{tls_handshake_buffer = Buf0} = Buffers,
- negotiated_version = Version,
+ connection_env = #connection_env{negotiated_version = Version},
ssl_options = Options} = State0) ->
try
- {Packets, Buf} = tls_handshake:get_tls_handshake(Version,Data,Buf0, Options),
+ EffectiveVersion = effective_version(Version, Options),
+ {Packets, Buf} = tls_handshake:get_tls_handshake(EffectiveVersion,Data,Buf0, Options),
State =
State0#state{protocol_buffers =
Buffers#protocol_buffers{tls_handshake_buffer = Buf}},
@@ -227,8 +286,12 @@ handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data},
connection ->
ssl_connection:hibernate_after(StateName, State, Events);
_ ->
+ HsEnv = State#state.handshake_env,
{next_state, StateName,
- State#state{unprocessed_handshake_events = unprocessed_events(Events)}, Events}
+ State#state{protocol_buffers = Buffers,
+ handshake_env =
+ HsEnv#handshake_env{unprocessed_handshake_events
+ = unprocessed_events(Events)}}, Events}
end
end
catch throw:#alert{} = Alert ->
@@ -239,7 +302,7 @@ handle_protocol_record(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, St
{next_state, StateName, State, [{next_event, internal, #change_cipher_spec{type = Data}}]};
%%% TLS record protocol level Alert messages
handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName,
- #state{negotiated_version = Version} = State) ->
+ #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
try decode_alerts(EncAlerts) of
Alerts = [_|_] ->
handle_alerts(Alerts, {next_state, StateName, State});
@@ -263,73 +326,83 @@ handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) ->
renegotiation(Pid, WriteState) ->
gen_statem:call(Pid, {user_renegotiate, WriteState}).
-renegotiate(#state{static_env = #static_env{role = client}} = State, Actions) ->
+renegotiate(#state{static_env = #static_env{role = client},
+ handshake_env = HsEnv} = State, Actions) ->
%% Handle same way as if server requested
%% the renegotiation
Hs0 = ssl_handshake:init_handshake_history(),
- {next_state, connection, State#state{tls_handshake_history = Hs0},
+ {next_state, connection, State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = Hs0}},
[{next_event, internal, #hello_request{}} | Actions]};
renegotiate(#state{static_env = #static_env{role = server,
socket = Socket,
transport_cb = Transport},
- negotiated_version = Version,
+ handshake_env = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
connection_states = ConnectionStates0} = State0, Actions) ->
HelloRequest = ssl_handshake:hello_request(),
Frag = tls_handshake:encode_handshake(HelloRequest, Version),
Hs0 = ssl_handshake:init_handshake_history(),
{BinMsg, ConnectionStates} =
tls_record:encode_handshake(Frag, Version, ConnectionStates0),
- send(Transport, Socket, BinMsg),
+ tls_socket:send(Transport, Socket, BinMsg),
State = State0#state{connection_states =
ConnectionStates,
- tls_handshake_history = Hs0},
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = Hs0}},
next_event(hello, no_record, State, Actions).
send_handshake(Handshake, State) ->
send_handshake_flight(queue_handshake(Handshake, State)).
-queue_handshake(Handshake, #state{negotiated_version = Version,
- tls_handshake_history = Hist0,
- flight_buffer = Flight0,
+
+queue_handshake(Handshake, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv,
+ connection_env = #connection_env{negotiated_version = Version},
+ flight_buffer = Flight0,
+ ssl_options = SslOpts,
connection_states = ConnectionStates0} = State0) ->
{BinHandshake, ConnectionStates, Hist} =
encode_handshake(Handshake, Version, ConnectionStates0, Hist0),
+ ssl_logger:debug(SslOpts#ssl_options.log_level, outbound, 'handshake', Handshake),
+ ssl_logger:debug(SslOpts#ssl_options.log_level, outbound, 'record', BinHandshake),
+
State0#state{connection_states = ConnectionStates,
- tls_handshake_history = Hist,
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist},
flight_buffer = Flight0 ++ [BinHandshake]}.
send_handshake_flight(#state{static_env = #static_env{socket = Socket,
transport_cb = Transport},
flight_buffer = Flight} = State0) ->
- send(Transport, Socket, Flight),
+ tls_socket:send(Transport, Socket, Flight),
{State0#state{flight_buffer = []}, []}.
-queue_change_cipher(Msg, #state{negotiated_version = Version,
- flight_buffer = Flight0,
- connection_states = ConnectionStates0} = State0) ->
+
+queue_change_cipher(Msg, #state{connection_env = #connection_env{negotiated_version = Version},
+ flight_buffer = Flight0,
+ ssl_options = SslOpts,
+ connection_states = ConnectionStates0} = State0) ->
{BinChangeCipher, ConnectionStates} =
encode_change_cipher(Msg, Version, ConnectionStates0),
+ ssl_logger:debug(SslOpts#ssl_options.log_level, outbound, 'record', BinChangeCipher),
State0#state{connection_states = ConnectionStates,
flight_buffer = Flight0 ++ [BinChangeCipher]}.
reinit(#state{protocol_specific = #{sender := Sender},
- negotiated_version = Version,
+ connection_env = #connection_env{negotiated_version = Version},
connection_states = #{current_write := Write}} = State) ->
tls_sender:update_connection_state(Sender, Write, Version),
reinit_handshake_data(State).
-reinit_handshake_data(State) ->
+reinit_handshake_data(#state{handshake_env = HsEnv} =State) ->
%% premaster_secret, public_key_info and tls_handshake_info
%% are only needed during the handshake phase.
%% To reduce memory foot print of a connection reinitialize them.
State#state{
- premaster_secret = undefined,
- public_key_info = undefined,
- tls_handshake_history = ssl_handshake:init_handshake_history()
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = ssl_handshake:init_handshake_history(),
+ public_key_info = undefined,
+ premaster_secret = undefined}
}.
-select_sni_extension(#client_hello{extensions = HelloExtensions}) ->
- HelloExtensions#hello_extensions.sni;
+select_sni_extension(#client_hello{extensions = #{sni := SNI}}) ->
+ SNI;
select_sni_extension(_) ->
undefined.
@@ -339,6 +412,7 @@ empty_connection_state(ConnectionEnd, BeastMitigation) ->
%%====================================================================
%% Alert and close handling
%%====================================================================
+
%%--------------------------------------------------------------------
-spec encode_alert(#alert{}, ssl_record:ssl_version(), ssl_record:connection_states()) ->
{iolist(), ssl_record:connection_states()}.
@@ -348,13 +422,15 @@ empty_connection_state(ConnectionEnd, BeastMitigation) ->
encode_alert(#alert{} = Alert, Version, ConnectionStates) ->
tls_record:encode_alert_record(Alert, Version, ConnectionStates).
-send_alert(Alert, #state{negotiated_version = Version,
- static_env = #static_env{socket = Socket,
+send_alert(Alert, #state{static_env = #static_env{socket = Socket,
transport_cb = Transport},
+ connection_env = #connection_env{negotiated_version = Version},
+ ssl_options = SslOpts,
connection_states = ConnectionStates0} = StateData0) ->
{BinMsg, ConnectionStates} =
encode_alert(Alert, Version, ConnectionStates0),
- send(Transport, Socket, BinMsg),
+ tls_socket:send(Transport, Socket, BinMsg),
+ ssl_logger:debug(SslOpts#ssl_options.log_level, outbound, 'record', BinMsg),
StateData0#state{connection_states = ConnectionStates}.
%% If an ALERT sent in the connection state, should cause the TLS
@@ -408,14 +484,9 @@ protocol_name() ->
%%====================================================================
%% Data handling
%%====================================================================
-encode_data(Data, Version, ConnectionStates0)->
- tls_record:encode_data(Data, Version, ConnectionStates0).
-
-send(Transport, Socket, Data) ->
- tls_socket:send(Transport, Socket, Data).
-socket(Pids, Transport, Socket, Connection, Tracker) ->
- tls_socket:socket(Pids, Transport, Socket, Connection, Tracker).
+socket(Pids, Transport, Socket, Tracker) ->
+ tls_socket:socket(Pids, Transport, Socket, ?MODULE, Tracker).
setopts(Transport, Socket, Other) ->
tls_socket:setopts(Transport, Socket, Other).
@@ -440,29 +511,33 @@ init({call, From}, {start, Timeout},
socket = Socket,
session_cache = Cache,
session_cache_cb = CacheCb},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv,
+ connection_env = CEnv,
ssl_options = SslOpts,
session = #session{own_certificate = Cert} = Session0,
- connection_states = ConnectionStates0,
- renegotiation = {Renegotiation, _}
+ connection_states = ConnectionStates0
} = State0) ->
- Timer = ssl_connection:start_or_recv_cancel_timer(Timeout, From),
+ KeyShare = maybe_generate_client_shares(SslOpts),
Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
- Cache, CacheCb, Renegotiation, Cert),
-
- Version = Hello#client_hello.client_version,
- HelloVersion = tls_record:hello_version(Version, SslOpts#ssl_options.versions),
+ Cache, CacheCb, Renegotiation, Cert, KeyShare),
+
+ HelloVersion = tls_record:hello_version(SslOpts#ssl_options.versions),
Handshake0 = ssl_handshake:init_handshake_history(),
{BinMsg, ConnectionStates, Handshake} =
encode_handshake(Hello, HelloVersion, ConnectionStates0, Handshake0),
- send(Transport, Socket, BinMsg),
+ tls_socket:send(Transport, Socket, BinMsg),
+ ssl_logger:debug(SslOpts#ssl_options.log_level, outbound, 'handshake', Hello),
+ ssl_logger:debug(SslOpts#ssl_options.log_level, outbound, 'record', BinMsg),
+
State = State0#state{connection_states = ConnectionStates,
- negotiated_version = Version, %% Requested version
+ connection_env = CEnv#connection_env{negotiated_version = HelloVersion}, %% Requested version
session =
Session0#session{session_id = Hello#client_hello.session_id},
- tls_handshake_history = Handshake,
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = Handshake},
start_or_recv_from = From,
- timer = Timer},
- next_event(hello, no_record, State);
+ key_share = KeyShare},
+ next_event(hello, no_record, State, [{{timeout, handshake}, Timeout, close}]);
+
init(Type, Event, State) ->
gen_handshake(?FUNCTION_NAME, Type, Event, State).
@@ -489,57 +564,76 @@ error(_, _, _) ->
%%--------------------------------------------------------------------
hello(internal, #client_hello{extensions = Extensions} = Hello,
#state{ssl_options = #ssl_options{handshake = hello},
+ handshake_env = HsEnv,
start_or_recv_from = From} = State) ->
{next_state, user_hello, State#state{start_or_recv_from = undefined,
- hello = Hello},
- [{reply, From, {ok, ssl_connection:map_extensions(Extensions)}}]};
+ handshake_env = HsEnv#handshake_env{hello = Hello}},
+ [{reply, From, {ok, Extensions}}]};
hello(internal, #server_hello{extensions = Extensions} = Hello,
#state{ssl_options = #ssl_options{handshake = hello},
+ handshake_env = HsEnv,
start_or_recv_from = From} = State) ->
{next_state, user_hello, State#state{start_or_recv_from = undefined,
- hello = Hello},
- [{reply, From, {ok, ssl_connection:map_extensions(Extensions)}}]};
+ handshake_env = HsEnv#handshake_env{hello = Hello}},
+ [{reply, From, {ok, Extensions}}]};
+
hello(internal, #client_hello{client_version = ClientVersion} = Hello,
#state{connection_states = ConnectionStates0,
static_env = #static_env{
port = Port,
session_cache = Cache,
session_cache_cb = CacheCb},
+ handshake_env = #handshake_env{kex_algorithm = KeyExAlg,
+ renegotiation = {Renegotiation, _},
+ negotiated_protocol = CurrentProtocol} = HsEnv,
+ connection_env = CEnv,
session = #session{own_certificate = Cert} = Session0,
- renegotiation = {Renegotiation, _},
- negotiated_protocol = CurrentProtocol,
- key_algorithm = KeyExAlg,
ssl_options = SslOpts} = State) ->
- case tls_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb,
- ConnectionStates0, Cert, KeyExAlg}, Renegotiation) of
- #alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, ClientVersion, hello,
- State#state{negotiated_version
- = ClientVersion});
- {Version, {Type, Session},
- ConnectionStates, Protocol0, ServerHelloExt, HashSign} ->
- Protocol = case Protocol0 of
- undefined -> CurrentProtocol;
- _ -> Protocol0
- end,
- gen_handshake(?FUNCTION_NAME, internal, {common_client_hello, Type, ServerHelloExt},
- State#state{connection_states = ConnectionStates,
- negotiated_version = Version,
- hashsign_algorithm = HashSign,
- client_hello_version = ClientVersion,
- session = Session,
- negotiated_protocol = Protocol})
+
+ case choose_tls_version(SslOpts, Hello) of
+ 'tls_v1.3' ->
+ %% Continue in TLS 1.3 'start' state
+ {next_state, start, State, [{next_event, internal, Hello}]};
+ 'tls_v1.2' ->
+ case tls_handshake:hello(Hello,
+ SslOpts,
+ {Port, Session0, Cache, CacheCb,
+ ConnectionStates0, Cert, KeyExAlg},
+ Renegotiation) of
+ #alert{} = Alert ->
+ ssl_connection:handle_own_alert(Alert, ClientVersion, hello,
+ State#state{connection_env = CEnv#connection_env{negotiated_version
+ = ClientVersion}});
+ {Version, {Type, Session},
+ ConnectionStates, Protocol0, ServerHelloExt, HashSign} ->
+ Protocol = case Protocol0 of
+ undefined -> CurrentProtocol;
+ _ -> Protocol0
+ end,
+ gen_handshake(?FUNCTION_NAME,
+ internal,
+ {common_client_hello, Type, ServerHelloExt},
+ State#state{connection_states = ConnectionStates,
+ connection_env = CEnv#connection_env{negotiated_version = Version},
+ handshake_env = HsEnv#handshake_env{
+ hashsign_algorithm = HashSign,
+ client_hello_version = ClientVersion,
+ negotiated_protocol = Protocol},
+ session = Session
+ })
+ end
+
end;
hello(internal, #server_hello{} = Hello,
#state{connection_states = ConnectionStates0,
- negotiated_version = ReqVersion,
+ connection_env = #connection_env{negotiated_version = ReqVersion} = CEnv,
static_env = #static_env{role = client},
- renegotiation = {Renegotiation, _},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
ssl_options = SslOptions} = State) ->
case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of
- #alert{} = Alert ->
+ #alert{} = Alert -> %%TODO
ssl_connection:handle_own_alert(Alert, ReqVersion, hello,
- State#state{negotiated_version = ReqVersion});
+ State#state{connection_env = CEnv#connection_env{negotiated_version = ReqVersion}});
{Version, NewId, ConnectionStates, ProtoExt, Protocol} ->
ssl_connection:handle_session(Hello,
Version, NewId, ConnectionStates, ProtoExt, Protocol, State)
@@ -592,13 +686,16 @@ connection({call, From}, {user_renegotiate, WriteState},
[{next_event,{call, From}, renegotiate}]};
connection({call, From},
{close, {Pid, _Timeout}},
- #state{terminated = closed} = State) ->
- {next_state, downgrade, State#state{terminated = true, downgrade = {Pid, From}},
+ #state{connection_env = #connection_env{terminated = closed} =CEnv} = State) ->
+ {next_state, downgrade, State#state{connection_env =
+ CEnv#connection_env{terminated = true,
+ downgrade = {Pid, From}}},
[{next_event, internal, ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY)}]};
connection({call, From},
{close,{Pid, Timeout}},
#state{connection_states = ConnectionStates,
- protocol_specific = #{sender := Sender}
+ protocol_specific = #{sender := Sender},
+ connection_env = CEnv
} = State0) ->
case tls_sender:downgrade(Sender, Timeout) of
{ok, Write} ->
@@ -609,8 +706,10 @@ connection({call, From},
State = send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
State0#state{connection_states =
ConnectionStates#{current_write => Write}}),
- {next_state, downgrade, State#state{downgrade = {Pid, From},
- terminated = true}, [{timeout, Timeout, downgrade}]};
+ {next_state, downgrade, State#state{connection_env =
+ CEnv#connection_env{downgrade = {Pid, From},
+ terminated = true}},
+ [{timeout, Timeout, downgrade}]};
{error, timeout} ->
{stop_and_reply, {shutdown, downgrade_fail}, [{reply, From, {error, timeout}}]}
end;
@@ -620,7 +719,7 @@ connection(internal, #hello_request{},
port = Port,
session_cache = Cache,
session_cache_cb = CacheCb},
- renegotiation = {Renegotiation, peer},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, peer}},
session = #session{own_certificate = Cert} = Session0,
ssl_options = SslOpts,
protocol_specific = #{sender := Pid},
@@ -628,7 +727,7 @@ connection(internal, #hello_request{},
try tls_sender:peer_renegotiate(Pid) of
{ok, Write} ->
Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts,
- Cache, CacheCb, Renegotiation, Cert),
+ Cache, CacheCb, Renegotiation, Cert, undefined),
{State, Actions} = send_handshake(Hello, State0#state{connection_states = ConnectionStates#{current_write => Write}}),
next_event(hello, no_record, State#state{session = Session0#session{session_id
= Hello#client_hello.session_id}}, Actions)
@@ -642,18 +741,19 @@ connection(internal, #hello_request{},
port = Port,
session_cache = Cache,
session_cache_cb = CacheCb},
- renegotiation = {Renegotiation, _},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
session = #session{own_certificate = Cert} = Session0,
ssl_options = SslOpts,
connection_states = ConnectionStates} = State0) ->
Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts,
- Cache, CacheCb, Renegotiation, Cert),
+ Cache, CacheCb, Renegotiation, Cert, undefined),
+
{State, Actions} = send_handshake(Hello, State0),
next_event(hello, no_record, State#state{session = Session0#session{session_id
= Hello#client_hello.session_id}}, Actions);
connection(internal, #client_hello{} = Hello,
#state{static_env = #static_env{role = server},
- allow_renegotiate = true,
+ handshake_env = #handshake_env{allow_renegotiate = true}= HsEnv,
connection_states = CS,
protocol_specific = #{sender := Sender}
} = State) ->
@@ -665,17 +765,16 @@ connection(internal, #client_hello{} = Hello,
erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate),
{ok, Write} = tls_sender:renegotiate(Sender),
next_event(hello, no_record, State#state{connection_states = CS#{current_write => Write},
- allow_renegotiate = false,
- renegotiation = {true, peer}
+ handshake_env = HsEnv#handshake_env{renegotiation = {true, peer},
+ allow_renegotiate = false}
},
[{next_event, internal, Hello}]);
connection(internal, #client_hello{},
- #state{static_env = #static_env{role = server,
- protocol_cb = Connection},
- allow_renegotiate = false} = State0) ->
+ #state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{allow_renegotiate = false}} = State0) ->
Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION),
send_alert_in_connection(Alert, State0),
- State = Connection:reinit_handshake_data(State0),
+ State = reinit_handshake_data(State0),
next_event(?FUNCTION_NAME, no_record, State);
connection(Type, Event, State) ->
@@ -688,21 +787,134 @@ connection(Type, Event, State) ->
downgrade(internal, #alert{description = ?CLOSE_NOTIFY},
#state{static_env = #static_env{transport_cb = Transport,
socket = Socket},
- downgrade = {Pid, From}} = State) ->
+ connection_env = #connection_env{downgrade = {Pid, From}}} = State) ->
tls_socket:setopts(Transport, Socket, [{active, false}, {packet, 0}, {mode, binary}]),
Transport:controlling_process(Socket, Pid),
{stop_and_reply, {shutdown, downgrade},[{reply, From, {ok, Socket}}], State};
-downgrade(timeout, downgrade, #state{downgrade = {_, From}} = State) ->
+downgrade(timeout, downgrade, #state{ connection_env = #connection_env{downgrade = {_, From}}} = State) ->
{stop_and_reply, {shutdown, normal},[{reply, From, {error, timeout}}], State};
downgrade(info, {CloseTag, Socket},
#state{static_env = #static_env{socket = Socket,
- close_tag = CloseTag}, downgrade = {_, From}} =
+ close_tag = CloseTag},
+ connection_env = #connection_env{downgrade = {_, From}}} =
State) ->
{stop_and_reply, {shutdown, normal},[{reply, From, {error, CloseTag}}], State};
downgrade(info, Info, State) ->
handle_info(Info, ?FUNCTION_NAME, State);
downgrade(Type, Event, State) ->
- ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
+ ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
+
+%%--------------------------------------------------------------------
+%% TLS 1.3 state functions
+%%--------------------------------------------------------------------
+%%--------------------------------------------------------------------
+-spec start(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+start(info, Event, State) ->
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
+start(Type, Event, State) ->
+ gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec negotiated(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+negotiated(info, Event, State) ->
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
+negotiated(Type, Event, State) ->
+ gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec recvd_ch(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+recvd_ch(info, Event, State) ->
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
+recvd_ch(Type, Event, State) ->
+ gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec wait_cert(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_cert(info, Event, State) ->
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
+wait_cert(Type, Event, State) ->
+ gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec wait_cv(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_cv(info, Event, State) ->
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
+wait_cv(Type, Event, State) ->
+ gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec wait_eoed(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_eoed(info, Event, State) ->
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
+wait_eoed(Type, Event, State) ->
+ gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec wait_finished(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_finished(info, Event, State) ->
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
+wait_finished(Type, Event, State) ->
+ gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec wait_flight2(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_flight2(info, Event, State) ->
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
+wait_flight2(Type, Event, State) ->
+ gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec connected(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+connected(info, Event, State) ->
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
+connected(Type, Event, State) ->
+ gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec wait_cert_cr(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_cert_cr(info, Event, State) ->
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
+wait_cert_cr(Type, Event, State) ->
+ gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec wait_ee(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_ee(info, Event, State) ->
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
+wait_ee(Type, Event, State) ->
+ gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec wait_sh(gen_statem:event_type(), term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_sh(info, Event, State) ->
+ gen_info_1_3(Event, ?FUNCTION_NAME, State);
+wait_sh(Type, Event, State) ->
+ gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State).
+
%--------------------------------------------------------------------
%% gen_statem callbacks
%%--------------------------------------------------------------------
@@ -733,8 +945,6 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac
#ssl_options{beast_mitigation = BeastMitigation,
erl_dist = IsErlDist} = SSLOptions,
ConnectionStates = tls_record:init_connection_states(Role, BeastMitigation),
-
- ErlDistData = erl_dist_data(IsErlDist),
SessionCacheCb = case application:get_env(ssl, session_cb) of
{ok, Cb} when is_atom(Cb) ->
Cb;
@@ -763,16 +973,18 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac
},
#state{
static_env = InitStatEnv,
+ handshake_env = #handshake_env{
+ tls_handshake_history = ssl_handshake:init_handshake_history(),
+ renegotiation = {false, first},
+ allow_renegotiate = SSLOptions#ssl_options.client_renegotiation
+ },
+ connection_env = #connection_env{user_application = {UserMonitor, User}},
socket_options = SocketOptions,
ssl_options = SSLOptions,
session = #session{is_resumable = new},
- erl_dist_data = ErlDistData,
connection_states = ConnectionStates,
protocol_buffers = #protocol_buffers{},
- user_application = {UserMonitor, User},
- user_data_buffer = <<>>,
- renegotiation = {false, first},
- allow_renegotiate = SSLOptions#ssl_options.client_renegotiation,
+ user_data_buffer = {[],0,[]},
start_or_recv_from = undefined,
flight_buffer = [],
protocol_specific = #{sender => Sender,
@@ -781,22 +993,16 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac
}
}.
-erl_dist_data(true) ->
- #{dist_handle => undefined,
- dist_buffer => <<>>};
-erl_dist_data(false) ->
- #{}.
-
initialize_tls_sender(#state{static_env = #static_env{
role = Role,
transport_cb = Transport,
- protocol_cb = Connection,
socket = Socket,
tracker = Tracker
},
- socket_options = SockOpts,
- negotiated_version = Version,
- ssl_options = #ssl_options{renegotiate_at = RenegotiateAt},
+ connection_env = #connection_env{negotiated_version = Version},
+ socket_options = SockOpts,
+ ssl_options = #ssl_options{renegotiate_at = RenegotiateAt,
+ log_level = LogLevel},
connection_states = #{current_write := ConnectionWriteState},
protocol_specific = #{sender := Sender}}) ->
Init = #{current_write => ConnectionWriteState,
@@ -804,19 +1010,29 @@ initialize_tls_sender(#state{static_env = #static_env{
socket => Socket,
socket_options => SockOpts,
tracker => Tracker,
- protocol_cb => Connection,
transport_cb => Transport,
negotiated_version => Version,
- renegotiate_at => RenegotiateAt},
+ renegotiate_at => RenegotiateAt,
+ log_level => LogLevel},
tls_sender:initialize(Sender, Init).
-
-next_tls_record(Data, StateName, #state{protocol_buffers =
- #protocol_buffers{tls_record_buffer = Buf0,
- tls_cipher_texts = CT0} = Buffers}
- = State0) ->
- case tls_record:get_tls_records(Data,
- acceptable_record_versions(StateName, State0),
- Buf0) of
+
+next_tls_record(Data, StateName,
+ #state{protocol_buffers =
+ #protocol_buffers{tls_record_buffer = Buf0,
+ tls_cipher_texts = CT0} = Buffers,
+ ssl_options = SslOpts} = State0) ->
+ Versions =
+ %% TLS 1.3 Client/Server
+ %% - Ignore TLSPlaintext.legacy_record_version
+ %% - Verify that TLSCiphertext.legacy_record_version is set to 0x0303 for all records
+ %% other than an initial ClientHello, where it MAY also be 0x0301.
+ case StateName of
+ hello ->
+ [tls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_VERSIONS];
+ _ ->
+ State0#state.connection_env#connection_env.negotiated_version
+ end,
+ case tls_record:get_tls_records(Data, Versions, Buf0, SslOpts) of
{Records, Buf1} ->
CT1 = CT0 ++ Records,
next_record(State0#state{protocol_buffers =
@@ -827,11 +1043,6 @@ next_tls_record(Data, StateName, #state{protocol_buffers =
end.
-acceptable_record_versions(StateName, #state{negotiated_version = Version}) when StateName =/= hello->
- Version;
-acceptable_record_versions(hello, _) ->
- [tls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_VERSIONS].
-
handle_record_alert(Alert, _) ->
Alert.
@@ -858,18 +1069,18 @@ handle_info({tcp_passive, Socket}, StateName,
State#state{protocol_specific = PS#{active_n_toggle => true}});
handle_info({CloseTag, Socket}, StateName,
#state{static_env = #static_env{socket = Socket, close_tag = CloseTag},
+ connection_env = #connection_env{negotiated_version = Version},
socket_options = #socket_options{active = Active},
protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs},
- user_data_buffer = Buffer,
- protocol_specific = PS,
- negotiated_version = Version} = State) ->
+ user_data_buffer = {_,BufferSize,_},
+ protocol_specific = PS} = State) ->
%% Note that as of TLS 1.1,
%% failure to properly close a connection no longer requires that a
%% session not be resumed. This is a change from TLS 1.0 to conform
%% with widespread implementation practice.
- case (Active == false) andalso ((CTs =/= []) or (Buffer =/= <<>>)) of
+ case (Active == false) andalso ((CTs =/= []) or (BufferSize =/= 0)) of
false ->
case Version of
{1, N} when N >= 1 ->
@@ -902,11 +1113,13 @@ handle_alerts([], Result) ->
handle_alerts(_, {stop, _, _} = Stop) ->
Stop;
handle_alerts([#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} | _Alerts],
- {next_state, connection = StateName, #state{user_data_buffer = Buffer,
+ {next_state, connection = StateName, #state{connection_env = CEnv,
+ socket_options = #socket_options{active = false},
+ user_data_buffer = {_,BufferSize,_},
protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs}} =
- State}) when (Buffer =/= <<>>) orelse
+ State}) when (BufferSize =/= 0) orelse
(CTs =/= []) ->
- {next_state, StateName, State#state{terminated = true}};
+ {next_state, StateName, State#state{connection_env = CEnv#connection_env{terminated = true}}};
handle_alerts([Alert | Alerts], {next_state, StateName, State}) ->
handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State));
handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) ->
@@ -926,7 +1139,7 @@ decode_alerts(Bin) ->
ssl_alert:decode(Bin).
gen_handshake(StateName, Type, Event,
- #state{negotiated_version = Version} = State) ->
+ #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
try ssl_connection:StateName(Type, Event, State, ?MODULE) of
Result ->
Result
@@ -937,18 +1150,32 @@ gen_handshake(StateName, Type, Event,
Version, StateName, State)
end.
-gen_info(Event, connection = StateName, #state{negotiated_version = Version} = State) ->
+
+gen_handshake_1_3(StateName, Type, Event,
+ #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
+ try tls_connection_1_3:StateName(Type, Event, State, ?MODULE) of
+ Result ->
+ Result
+ catch
+ _:_ ->
+ ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ malformed_handshake_data),
+ Version, StateName, State)
+ end.
+
+
+gen_info(Event, connection = StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
try handle_info(Event, StateName, State) of
Result ->
Result
catch
- _:_ ->
+ _:_ ->
ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR,
malformed_data),
Version, StateName, State)
end;
-gen_info(Event, StateName, #state{negotiated_version = Version} = State) ->
+gen_info(Event, StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
try handle_info(Event, StateName, State) of
Result ->
Result
@@ -958,6 +1185,29 @@ gen_info(Event, StateName, #state{negotiated_version = Version} = State) ->
malformed_handshake_data),
Version, StateName, State)
end.
+
+gen_info_1_3(Event, connected = StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
+ try handle_info(Event, StateName, State) of
+ Result ->
+ Result
+ catch
+ _:_ ->
+ ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR,
+ malformed_data),
+ Version, StateName, State)
+ end;
+
+gen_info_1_3(Event, StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) ->
+ try handle_info(Event, StateName, State) of
+ Result ->
+ Result
+ catch
+ _:_ ->
+ ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ malformed_handshake_data),
+ Version, StateName, State)
+ end.
+
unprocessed_events(Events) ->
%% The first handshake event will be processed immediately
@@ -1002,3 +1252,34 @@ ensure_sender_terminate(_, #state{protocol_specific = #{sender := Sender}}) ->
end
end,
spawn(Kill).
+
+maybe_generate_client_shares(#ssl_options{
+ versions = [Version|_],
+ supported_groups =
+ #supported_groups{
+ supported_groups = Groups}})
+ when Version =:= {3,4} ->
+ ssl_cipher:generate_client_shares(Groups);
+maybe_generate_client_shares(_) ->
+ undefined.
+
+choose_tls_version(#ssl_options{versions = Versions},
+ #client_hello{
+ extensions = #{client_hello_versions :=
+ #client_hello_versions{versions = ClientVersions}
+ }
+ }) ->
+ case ssl_handshake:select_supported_version(ClientVersions, Versions) of
+ {3,4} ->
+ 'tls_v1.3';
+ _Else ->
+ 'tls_v1.2'
+ end;
+choose_tls_version(_, _) ->
+ 'tls_v1.2'.
+
+
+effective_version(undefined, #ssl_options{versions = [Version|_]}) ->
+ Version;
+effective_version(Version, _) ->
+ Version.
diff --git a/lib/ssl/src/tls_connection.hrl b/lib/ssl/src/tls_connection.hrl
index 0af2258932..9063b1b736 100644
--- a/lib/ssl/src/tls_connection.hrl
+++ b/lib/ssl/src/tls_connection.hrl
@@ -30,7 +30,6 @@
-include("tls_record.hrl").
-record(protocol_buffers, {
- tls_packets = [], %% :: [#ssl_tls{}], % Not yet handled decode SSL/TLS packets.
tls_record_buffer = <<>>, %% :: binary(), % Buffer of incomplete records
tls_handshake_buffer = <<>>, %% :: binary(), % Buffer of incomplete handshakes
tls_cipher_texts = [] %%:: [binary()]
diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl
new file mode 100644
index 0000000000..71ac6a9310
--- /dev/null
+++ b/lib/ssl/src/tls_connection_1_3.erl
@@ -0,0 +1,168 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2018. All 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: TODO
+%%----------------------------------------------------------------------
+
+%% 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_handshake.hrl").
+-include("tls_handshake_1_3.hrl").
+
+%% gen_statem helper functions
+-export([start/4,
+ negotiated/4,
+ wait_finished/4
+ ]).
+
+
+start(internal,
+ #change_cipher_spec{} = ChangeCipherSpec, State0, _Module) ->
+ case tls_handshake_1_3:do_start(ChangeCipherSpec, State0) of
+ #alert{} = Alert ->
+ ssl_connection:handle_own_alert(Alert, {3,4}, start, State0);
+ State1 ->
+ {Record, State} = tls_connection:next_record(State1),
+ tls_connection:next_event(?FUNCTION_NAME, Record, State)
+ end;
+start(internal, #client_hello{} = Hello, State0, _Module) ->
+ case tls_handshake_1_3:do_start(Hello, State0) of
+ #alert{} = Alert ->
+ ssl_connection:handle_own_alert(Alert, {3,4}, start, State0);
+ {State, _, start} ->
+ {next_state, start, State, []};
+ {State, Context, negotiated} ->
+ {next_state, negotiated, State, [{next_event, internal, Context}]}
+ end;
+start(Type, Msg, State, Connection) ->
+ ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
+
+
+negotiated(internal, Map, State0, _Module) ->
+ case tls_handshake_1_3:do_negotiated(Map, State0) of
+ #alert{} = Alert ->
+ ssl_connection:handle_own_alert(Alert, {3,4}, negotiated, State0);
+ State ->
+ {next_state, wait_finished, State, []}
+
+ end.
+
+
+wait_finished(internal,
+ #change_cipher_spec{} = ChangeCipherSpec, State0, _Module) ->
+ case tls_handshake_1_3:do_wait_finished(ChangeCipherSpec, State0) of
+ #alert{} = Alert ->
+ ssl_connection:handle_own_alert(Alert, {3,4}, wait_finished, State0);
+ State1 ->
+ {Record, State} = tls_connection:next_record(State1),
+ tls_connection:next_event(?FUNCTION_NAME, Record, State)
+ end;
+wait_finished(internal,
+ #finished{} = Finished, State0, Module) ->
+ case tls_handshake_1_3:do_wait_finished(Finished, State0) of
+ #alert{} = Alert ->
+ ssl_connection:handle_own_alert(Alert, {3,4}, finished, State0);
+ State1 ->
+ {Record, State} = ssl_connection:prepare_connection(State1, Module),
+ tls_connection:next_event(connection, Record, State)
+ end;
+wait_finished(Type, Msg, State, Connection) ->
+ ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl
index 19a5eb0348..e7cee1956b 100644
--- a/lib/ssl/src/tls_handshake.erl
+++ b/lib/ssl/src/tls_handshake.erl
@@ -26,14 +26,17 @@
-module(tls_handshake).
-include("tls_handshake.hrl").
+-include("tls_handshake_1_3.hrl").
-include("tls_record.hrl").
-include("ssl_alert.hrl").
-include("ssl_internal.hrl").
-include("ssl_cipher.hrl").
+-include("ssl_api.hrl").
-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/logger.hrl").
%% Handshake handling
--export([client_hello/8, hello/4]).
+-export([client_hello/9, hello/4]).
%% Handshake encoding
-export([encode_handshake/2]).
@@ -47,8 +50,9 @@
%% Handshake handling
%%====================================================================
%%--------------------------------------------------------------------
--spec client_hello(host(), inet:port_number(), ssl_record:connection_states(),
- #ssl_options{}, integer(), atom(), boolean(), der_cert()) ->
+-spec client_hello(ssl:host(), inet:port_number(), ssl_record:connection_states(),
+ #ssl_options{}, integer(), atom(), boolean(), der_cert(),
+ #key_share_client_hello{} | undefined) ->
#client_hello{}.
%%
%% Description: Creates a client hello message.
@@ -58,19 +62,32 @@ client_hello(Host, Port, ConnectionStates,
ciphers = UserSuites,
fallback = Fallback
} = SslOpts,
- Cache, CacheCb, Renegotiation, OwnCert) ->
+ Cache, CacheCb, Renegotiation, OwnCert, KeyShare) ->
Version = tls_record:highest_protocol_version(Versions),
+
+ %% In TLS 1.3, the client indicates its version preferences in 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.
+ LegacyVersion =
+ case tls_record:is_higher(Version, {3,2}) of
+ true ->
+ {3,3};
+ false ->
+ Version
+ end,
#{security_parameters := SecParams} =
ssl_record:pending_connection_state(ConnectionStates, read),
AvailableCipherSuites = ssl_handshake:available_suites(UserSuites, Version),
Extensions = ssl_handshake:client_hello_extensions(Version,
AvailableCipherSuites,
SslOpts, ConnectionStates,
- Renegotiation),
+ Renegotiation,
+ KeyShare),
CipherSuites = ssl_handshake:cipher_suites(AvailableCipherSuites, Renegotiation, Fallback),
Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert),
#client_hello{session_id = Id,
- client_version = Version,
+ client_version = LegacyVersion,
cipher_suites = CipherSuites,
compression_methods = ssl_record:compressions(),
random = SecParams#security_parameters.client_random,
@@ -81,17 +98,75 @@ client_hello(Host, Port, ConnectionStates,
-spec hello(#server_hello{} | #client_hello{}, #ssl_options{},
ssl_record:connection_states() | {inet:port_number(), #session{}, db_handle(),
atom(), ssl_record:connection_states(),
- binary() | undefined, ssl_cipher_format:key_algo()},
+ binary() | undefined, ssl:kex_algo()},
boolean()) ->
- {tls_record:tls_version(), session_id(),
+ {tls_record:tls_version(), ssl:session_id(),
ssl_record:connection_states(), alpn | npn, binary() | undefined}|
{tls_record:tls_version(), {resumed | new, #session{}},
ssl_record:connection_states(), binary() | undefined,
- #hello_extensions{}, {ssl_cipher_format:hash(), ssl_cipher_format:sign_algo()} |
+ HelloExt::map(), {ssl:hash(), ssl:sign_algo()} |
undefined} | #alert{}.
%%
%% Description: Handles a received hello message
%%--------------------------------------------------------------------
+
+
+%% TLS 1.3 - Section 4.1.3
+%% TLS 1.3 clients receiving a ServerHello indicating TLS 1.2 or below
+%% MUST check that the last eight bytes are not equal to either of these
+%% values.
+hello(#server_hello{server_version = {Major, Minor},
+ random = <<_:24/binary,Down:8/binary>>},
+ #ssl_options{versions = [{M,N}|_]}, _, _)
+ when (M > 3 orelse M =:= 3 andalso N >= 4) andalso %% TLS 1.3 client
+ (Major =:= 3 andalso Minor =:= 3 andalso %% Negotiating TLS 1.2
+ Down =:= ?RANDOM_OVERRIDE_TLS12) orelse
+
+ (M > 3 orelse M =:= 3 andalso N >= 4) andalso %% TLS 1.3 client
+ (Major =:= 3 andalso Minor < 3 andalso %% Negotiating TLS 1.1 or prior
+ Down =:= ?RANDOM_OVERRIDE_TLS11) ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
+
+%% TLS 1.2 clients SHOULD also check that the last eight bytes are not
+%% equal to the second value if the ServerHello indicates TLS 1.1 or below.
+hello(#server_hello{server_version = {Major, Minor},
+ random = <<_:24/binary,Down:8/binary>>},
+ #ssl_options{versions = [{M,N}|_]}, _, _)
+ when (M =:= 3 andalso N =:= 3) andalso %% TLS 1.2 client
+ (Major =:= 3 andalso Minor < 3 andalso %% Negotiating TLS 1.1 or prior
+ Down =:= ?RANDOM_OVERRIDE_TLS11) ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
+
+
+%% TLS 1.3 - 4.2.1. Supported Versions
+%% If the "supported_versions" extension in the ServerHello contains a
+%% version not offered by the client or contains a version prior to TLS
+%% 1.3, the client MUST abort the handshake with an "illegal_parameter"
+%% alert.
+%%--------------------------------------------------------------------
+%% TLS 1.2 Client
+%%
+%% - If "supported_version" is present (ServerHello):
+%% - Abort handshake with an "illegal_parameter" alert
+hello(#server_hello{server_version = Version,
+ extensions = #{server_hello_selected_version :=
+ #server_hello_selected_version{selected_version = Version}}
+ },
+ #ssl_options{versions = SupportedVersions},
+ _ConnectionStates0, _Renegotiation) ->
+ case tls_record:is_higher({3,4}, Version) of
+ true ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
+ false ->
+ case tls_record:is_acceptable_version(Version, SupportedVersions) of
+ true ->
+ %% Implement TLS 1.3 statem ???
+ ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION);
+ false ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)
+ end
+ end;
+
hello(#server_hello{server_version = Version, random = Random,
cipher_suite = CipherSuite,
compression_method = Compression,
@@ -106,6 +181,36 @@ hello(#server_hello{server_version = Version, random = Random,
false ->
?ALERT_REC(?FATAL, ?PROTOCOL_VERSION)
end;
+
+
+%% TLS 1.2 Server
+%% - If "supported_versions" is present (ClientHello):
+%% - Select version from "supported_versions" (ignore ClientHello.legacy_version)
+%% - If server only supports versions greater than "supported_versions":
+%% - Abort handshake with a "protocol_version" alert (*)
+%% - If "supported_versions" is absent (ClientHello):
+%% - Negotiate the minimum of ClientHello.legacy_version and TLS 1.2 (**)
+%% - If server only supports versions greater than ClientHello.legacy_version:
+%% - Abort handshake with a "protocol_version" alert
+%%
+%% (*) Sends alert even if there is a gap in supported versions
+%% e.g. Server 1.0,1.2 Client 1.1,1.3
+%% (**) Current implementation can negotiate a version not supported by the client
+%% e.g. Server 1.0,1.2 Client 1.1 -> ServerHello 1.0
+hello(#client_hello{client_version = _ClientVersion,
+ cipher_suites = CipherSuites,
+ extensions = #{client_hello_versions :=
+ #client_hello_versions{versions = ClientVersions}
+ }} = Hello,
+ #ssl_options{versions = Versions} = SslOpts,
+ Info, Renegotiation) ->
+ try
+ Version = ssl_handshake:select_supported_version(ClientVersions, Versions),
+ do_hello(Version, Versions, CipherSuites, Hello, SslOpts, Info, Renegotiation)
+ catch
+ _:_ ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake_data)
+ end;
hello(#client_hello{client_version = ClientVersion,
cipher_suites = CipherSuites} = Hello,
@@ -113,18 +218,7 @@ hello(#client_hello{client_version = ClientVersion,
Info, Renegotiation) ->
try
Version = ssl_handshake:select_version(tls_record, ClientVersion, Versions),
- case ssl_cipher:is_fallback(CipherSuites) of
- true ->
- Highest = tls_record:highest_protocol_version(Versions),
- case tls_record:is_higher(Highest, Version) of
- true ->
- ?ALERT_REC(?FATAL, ?INAPPROPRIATE_FALLBACK);
- false ->
- handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation)
- end;
- false ->
- handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation)
- end
+ do_hello(Version, Versions, CipherSuites, Hello, SslOpts, Info, Renegotiation)
catch
error:{case_clause,{asn1, Asn1Reason}} ->
%% ASN-1 decode of certificate somehow failed
@@ -139,7 +233,8 @@ hello(#client_hello{client_version = ClientVersion,
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
--spec encode_handshake(tls_handshake(), tls_record:tls_version()) -> iolist().
+-spec encode_handshake(tls_handshake() | tls_handshake_1_3:tls_handshake_1_3(),
+ tls_record:tls_version()) -> iolist().
%%
%% Description: Encode a handshake packet
%%--------------------------------------------------------------------
@@ -175,10 +270,7 @@ handle_client_hello(Version,
cipher_suites = CipherSuites,
compression_methods = Compressions,
random = Random,
- extensions =
- #hello_extensions{elliptic_curves = Curves,
- signature_algs = ClientHashSigns}
- = HelloExt},
+ extensions = HelloExt},
#ssl_options{versions = Versions,
signature_algs = SupportedHashSigns,
eccs = SupportedECCs,
@@ -187,6 +279,9 @@ handle_client_hello(Version,
Renegotiation) ->
case tls_record:is_acceptable_version(Version, Versions) of
true ->
+ Curves = maps:get(elliptic_curves, HelloExt, undefined),
+ ClientHashSigns = maps:get(signature_algs, HelloExt, undefined),
+ ClientSignatureSchemes = maps:get(signature_algs_cert, HelloExt, undefined),
AvailableHashSigns = ssl_handshake:available_signature_algs(
ClientHashSigns, SupportedHashSigns, Cert, Version),
ECCCurve = ssl_handshake:select_curve(Curves, SupportedECCs, ECCOrder),
@@ -200,8 +295,10 @@ handle_client_hello(Version,
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_ciphers);
_ ->
#{key_exchange := KeyExAlg} = ssl_cipher_format:suite_definition(CipherSuite),
- case ssl_handshake:select_hashsign(ClientHashSigns, Cert, KeyExAlg,
- SupportedHashSigns, Version) of
+ case ssl_handshake:select_hashsign({ClientHashSigns, ClientSignatureSchemes},
+ Cert, KeyExAlg,
+ SupportedHashSigns,
+ Version) of
#alert{} = Alert ->
Alert;
HashSign ->
@@ -223,8 +320,6 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites,
HelloExt, Version, SslOpts,
Session0, ConnectionStates0,
Renegotiation) of
- #alert{} = Alert ->
- Alert;
{Session, ConnectionStates, Protocol, ServerHelloExt} ->
{Version, {Type, Session}, ConnectionStates, Protocol,
ServerHelloExt, HashSign}
@@ -235,17 +330,35 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites,
handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation) ->
- case ssl_handshake:handle_server_hello_extensions(tls_record, Random, CipherSuite,
+ try ssl_handshake:handle_server_hello_extensions(tls_record, Random, CipherSuite,
Compression, HelloExt, Version,
SslOpt, ConnectionStates0,
- Renegotiation) of
- #alert{} = Alert ->
- Alert;
+ Renegotiation) of
{ConnectionStates, ProtoExt, Protocol} ->
{Version, SessionId, ConnectionStates, ProtoExt, Protocol}
+ catch throw:Alert ->
+ Alert
end.
+
+
+do_hello(undefined, _Versions, _CipherSuites, _Hello, _SslOpts, _Info, _Renegotiation) ->
+ ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION);
+do_hello(Version, Versions, CipherSuites, Hello, SslOpts, Info, Renegotiation) ->
+ case ssl_cipher:is_fallback(CipherSuites) of
+ true ->
+ Highest = tls_record:highest_protocol_version(Versions),
+ case tls_record:is_higher(Highest, Version) of
+ true ->
+ ?ALERT_REC(?FATAL, ?INAPPROPRIATE_FALLBACK);
+ false ->
+ handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation)
+ end;
+ false ->
+ handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation)
+ end.
+
%%--------------------------------------------------------------------
-enc_handshake(#hello_request{}, _Version) ->
+enc_handshake(#hello_request{}, {3, N}) when N < 4 ->
{?HELLO_REQUEST, <<>>};
enc_handshake(#client_hello{client_version = {Major, Minor},
random = Random,
@@ -264,7 +377,8 @@ enc_handshake(#client_hello{client_version = {Major, Minor},
?BYTE(SIDLength), SessionID/binary,
?UINT16(CsLength), BinCipherSuites/binary,
?BYTE(CmLength), BinCompMethods/binary, ExtensionsBin/binary>>};
-
+enc_handshake(HandshakeMsg, {3, 4}) ->
+ tls_handshake_1_3:encode_handshake(HandshakeMsg);
enc_handshake(HandshakeMsg, Version) ->
ssl_handshake:encode_handshake(HandshakeMsg, Version).
@@ -275,6 +389,7 @@ get_tls_handshake_aux(Version, <<?BYTE(Type), ?UINT24(Length),
Raw = <<?BYTE(Type), ?UINT24(Length), Body/binary>>,
try decode_handshake(Version, Type, Body) of
Handshake ->
+ ssl_logger:debug(Opts#ssl_options.log_level, inbound, 'handshake', Handshake),
get_tls_handshake_aux(Version, Rest, Opts, [{Handshake,Raw} | Acc])
catch
_:_ ->
@@ -283,24 +398,26 @@ get_tls_handshake_aux(Version, <<?BYTE(Type), ?UINT24(Length),
get_tls_handshake_aux(_Version, Data, _, Acc) ->
{lists:reverse(Acc), Data}.
-decode_handshake(_, ?HELLO_REQUEST, <<>>) ->
+decode_handshake({3, N}, ?HELLO_REQUEST, <<>>) when N < 4 ->
#hello_request{};
-decode_handshake(_Version, ?CLIENT_HELLO,
+decode_handshake(Version, ?CLIENT_HELLO,
<<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SID_length), Session_ID:SID_length/binary,
?UINT16(Cs_length), CipherSuites:Cs_length/binary,
?BYTE(Cm_length), Comp_methods:Cm_length/binary,
Extensions/binary>>) ->
- DecodedExtensions = ssl_handshake:decode_hello_extensions({client, Extensions}),
+ Exts = ssl_handshake:decode_vector(Extensions),
+ DecodedExtensions = ssl_handshake:decode_hello_extensions(Exts, Version, {Major, Minor},
+ client_hello),
#client_hello{
client_version = {Major,Minor},
random = Random,
session_id = Session_ID,
cipher_suites = ssl_handshake:decode_suites('2_bytes', CipherSuites),
- compression_methods = Comp_methods,
+ compression_methods = erlang:binary_to_list(Comp_methods),
extensions = DecodedExtensions
};
+decode_handshake({3, 4}, Tag, Msg) ->
+ tls_handshake_1_3:decode_handshake(Tag, Msg);
decode_handshake(Version, Tag, Msg) ->
ssl_handshake:decode_handshake(Version, Tag, Msg).
-
-
diff --git a/lib/ssl/src/tls_handshake.hrl b/lib/ssl/src/tls_handshake.hrl
index f6644f64af..fc67bb61fd 100644
--- a/lib/ssl/src/tls_handshake.hrl
+++ b/lib/ssl/src/tls_handshake.hrl
@@ -32,6 +32,7 @@
client_version,
random,
session_id, % opaque SessionID<0..32>
+ cookie, % opaque<2..2^16-1>
cipher_suites, % cipher_suites<2..2^16-1>
compression_methods, % compression_methods<1..2^8-1>,
%% Extensions
diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl
new file mode 100644
index 0000000000..3bc1290361
--- /dev/null
+++ b/lib/ssl/src/tls_handshake_1_3.erl
@@ -0,0 +1,1048 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2018. All 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: Help funtions for handling the TLS 1.3 (specific parts of)
+%%% TLS handshake protocol
+%%----------------------------------------------------------------------
+
+-module(tls_handshake_1_3).
+
+-include("tls_handshake_1_3.hrl").
+-include("ssl_alert.hrl").
+-include("ssl_cipher.hrl").
+-include("ssl_connection.hrl").
+-include("ssl_internal.hrl").
+-include("ssl_record.hrl").
+-include_lib("public_key/include/public_key.hrl").
+
+%% Encode
+-export([encode_handshake/1, decode_handshake/2]).
+
+%% Create handshake messages
+-export([certificate/5,
+ certificate_verify/4,
+ encrypted_extensions/0,
+ server_hello/4]).
+
+-export([do_start/2,
+ do_negotiated/2,
+ do_wait_finished/2]).
+
+%%====================================================================
+%% Create handshake messages
+%%====================================================================
+
+server_hello(MsgType, SessionId, KeyShare, ConnectionStates) ->
+ #{security_parameters := SecParams} =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ Extensions = server_hello_extensions(MsgType, KeyShare),
+ #server_hello{server_version = {3,3}, %% legacy_version
+ cipher_suite = SecParams#security_parameters.cipher_suite,
+ compression_method = 0, %% legacy attribute
+ random = server_hello_random(MsgType, SecParams),
+ session_id = SessionId,
+ extensions = Extensions
+ }.
+
+server_hello_extensions(MsgType, KeyShare) ->
+ SupportedVersions = #server_hello_selected_version{selected_version = {3,4}},
+ Extensions = #{server_hello_selected_version => SupportedVersions},
+ ssl_handshake:add_server_share(MsgType, Extensions, KeyShare).
+
+server_hello_random(server_hello, #security_parameters{server_random = Random}) ->
+ Random;
+%% 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
+server_hello_random(hello_retry_request, _) ->
+ crypto:hash(sha256, "HelloRetryRequest").
+
+
+%% TODO: implement support for encrypted_extensions
+encrypted_extensions() ->
+ #encrypted_extensions{
+ extensions = #{}
+ }.
+
+
+%% TODO: use maybe monad for error handling!
+%% enum {
+%% X509(0),
+%% RawPublicKey(2),
+%% (255)
+%% } CertificateType;
+%%
+%% struct {
+%% select (certificate_type) {
+%% case RawPublicKey:
+%% /* From RFC 7250 ASN.1_subjectPublicKeyInfo */
+%% opaque ASN1_subjectPublicKeyInfo<1..2^24-1>;
+%%
+%% case X509:
+%% opaque cert_data<1..2^24-1>;
+%% };
+%% Extension extensions<0..2^16-1>;
+%% } CertificateEntry;
+%%
+%% struct {
+%% opaque certificate_request_context<0..2^8-1>;
+%% CertificateEntry certificate_list<0..2^24-1>;
+%% } Certificate;
+certificate(OwnCert, CertDbHandle, CertDbRef, _CRContext, server) ->
+ case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of
+ {ok, _, Chain} ->
+ CertList = chain_to_cert_list(Chain),
+ %% If this message is in response to a CertificateRequest, the value of
+ %% certificate_request_context in that message. Otherwise (in the case
+ %%of server authentication), this field SHALL be zero length.
+ #certificate_1_3{
+ certificate_request_context = <<>>,
+ certificate_list = CertList};
+ {error, Error} ->
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {server_has_no_suitable_certificates, Error})
+ end.
+
+
+certificate_verify(PrivateKey, SignatureScheme,
+ #state{connection_states = ConnectionStates,
+ handshake_env =
+ #handshake_env{
+ tls_handshake_history = {Messages, _}}}, server) ->
+ #{security_parameters := SecParamsR} =
+ ssl_record:pending_connection_state(ConnectionStates, write),
+ #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
+
+ {HashAlgo, _, _} =
+ ssl_cipher:scheme_to_components(SignatureScheme),
+
+ Context = lists:reverse(Messages),
+
+ %% Transcript-Hash uses the HKDF hash function defined by the cipher suite.
+ THash = tls_v1:transcript_hash(Context, HKDFAlgo),
+
+ %% Digital signatures use the hash function defined by the selected signature
+ %% scheme.
+ case digitally_sign(THash, <<"TLS 1.3, server CertificateVerify">>,
+ HashAlgo, PrivateKey) of
+ {ok, Signature} ->
+ {ok, #certificate_verify_1_3{
+ algorithm = SignatureScheme,
+ signature = Signature
+ }};
+ {error, badarg} ->
+ {error, badarg}
+
+ end.
+
+
+finished(#state{connection_states = ConnectionStates,
+ handshake_env =
+ #handshake_env{
+ tls_handshake_history = {Messages, _}}}) ->
+ #{security_parameters := SecParamsR,
+ cipher_state := #cipher_state{finished_key = FinishedKey}} =
+ ssl_record:current_connection_state(ConnectionStates, write),
+ #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
+
+ VerifyData = tls_v1:finished_verify_data(FinishedKey, HKDFAlgo, Messages),
+
+ #finished{
+ verify_data = VerifyData
+ }.
+
+
+%%====================================================================
+%% Encode handshake
+%%====================================================================
+
+encode_handshake(#certificate_request_1_3{
+ 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_list = Entries}) ->
+ EncContext = encode_cert_req_context(Context),
+ EncEntries = encode_cert_entries(Entries),
+ {?CERTIFICATE, <<EncContext/binary, EncEntries/binary>>};
+encode_handshake(#certificate_verify_1_3{
+ algorithm = Algorithm,
+ signature = Signature}) ->
+ EncAlgo = encode_algorithm(Algorithm),
+ EncSign = encode_signature(Signature),
+ {?CERTIFICATE_VERIFY, <<EncAlgo/binary, EncSign/binary>>};
+encode_handshake(#encrypted_extensions{extensions = Exts})->
+ {?ENCRYPTED_EXTENSIONS, encode_extensions(Exts)};
+encode_handshake(#new_session_ticket{
+ ticket_lifetime = LifeTime,
+ ticket_age_add = Age,
+ ticket_nonce = Nonce,
+ ticket = Ticket,
+ extensions = Exts}) ->
+ TicketSize = byte_size(Ticket),
+ BinExts = encode_extensions(Exts),
+ {?NEW_SESSION_TICKET, <<?UINT32(LifeTime), ?UINT32(Age),
+ ?BYTE(Nonce), ?UINT16(TicketSize), Ticket/binary,
+ BinExts/binary>>};
+encode_handshake(#end_of_early_data{}) ->
+ {?END_OF_EARLY_DATA, <<>>};
+encode_handshake(#key_update{request_update = Update}) ->
+ {?KEY_UPDATE, <<?BYTE(Update)>>};
+encode_handshake(HandshakeMsg) ->
+ ssl_handshake:encode_handshake(HandshakeMsg, {3,4}).
+
+
+%%====================================================================
+%% Decode handshake
+%%====================================================================
+
+decode_handshake(?CERTIFICATE_REQUEST, <<?BYTE(0), ?UINT16(Size), EncExts:Size/binary>>) ->
+ Exts = decode_extensions(EncExts, certificate_request),
+ #certificate_request_1_3{
+ certificate_request_context = <<>>,
+ extensions = Exts};
+decode_handshake(?CERTIFICATE_REQUEST, <<?BYTE(CSize), Context:CSize/binary,
+ ?UINT16(Size), EncExts:Size/binary>>) ->
+ Exts = decode_extensions(EncExts, certificate_request),
+ #certificate_request_1_3{
+ certificate_request_context = Context,
+ extensions = Exts};
+decode_handshake(?CERTIFICATE, <<?BYTE(0), ?UINT24(Size), Certs:Size/binary>>) ->
+ CertList = decode_cert_entries(Certs),
+ #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_request_context = Context,
+ certificate_list = CertList
+ };
+decode_handshake(?CERTIFICATE_VERIFY, <<?UINT16(EncAlgo), ?UINT16(Size), Signature:Size/binary>>) ->
+ Algorithm = ssl_cipher:signature_scheme(EncAlgo),
+ #certificate_verify_1_3{
+ algorithm = Algorithm,
+ signature = Signature};
+decode_handshake(?ENCRYPTED_EXTENSIONS, <<?UINT16(Size), EncExts:Size/binary>>) ->
+ #encrypted_extensions{
+ extensions = decode_extensions(EncExts, encrypted_extensions)
+ };
+decode_handshake(?NEW_SESSION_TICKET, <<?UINT32(LifeTime), ?UINT32(Age),
+ ?BYTE(Nonce), ?UINT16(TicketSize), Ticket:TicketSize/binary,
+ BinExts/binary>>) ->
+ Exts = decode_extensions(BinExts, encrypted_extensions),
+ #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 = Update};
+decode_handshake(Tag, HandshakeMsg) ->
+ ssl_handshake:decode_handshake({3,4}, Tag, HandshakeMsg).
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+encode_cert_req_context(<<>>) ->
+ <<?BYTE(0)>>;
+encode_cert_req_context(Bin) ->
+ Size = byte_size(Bin),
+ <<?BYTE(Size), Bin/binary>>.
+
+encode_cert_entries(Entries) ->
+ CertEntryList = encode_cert_entries(Entries, []),
+ Size = byte_size(CertEntryList),
+ <<?UINT24(Size), CertEntryList/binary>>.
+
+encode_cert_entries([], Acc) ->
+ iolist_to_binary(lists:reverse(Acc));
+encode_cert_entries([#certificate_entry{data = Data,
+ extensions = Exts} | Rest], Acc) ->
+ DSize = byte_size(Data),
+ BinExts = encode_extensions(Exts),
+ encode_cert_entries(Rest,
+ [<<?UINT24(DSize), Data/binary, BinExts/binary>> | Acc]).
+
+encode_algorithm(Algo) ->
+ Scheme = ssl_cipher:signature_scheme(Algo),
+ <<?UINT16(Scheme)>>.
+
+encode_signature(Signature) ->
+ Size = byte_size(Signature),
+ <<?UINT16(Size), Signature/binary>>.
+
+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]).
+
+encode_extensions(Exts)->
+ ssl_handshake:encode_extensions(extensions_list(Exts)).
+decode_extensions(Exts, MessageType) ->
+ ssl_handshake:decode_extensions(Exts, {3,4}, MessageType).
+
+extensions_list(HelloExtensions) ->
+ [Ext || {_, Ext} <- maps:to_list(HelloExtensions)].
+
+
+%% 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,
+ extensions = #{} %% Extensions not supported.
+ }.
+
+%% The digital signature is then computed over the concatenation of:
+%% - A string that consists of octet 32 (0x20) repeated 64 times
+%% - The context string
+%% - A single 0 byte which serves as the separator
+%% - The content to be signed
+%%
+%% For example, if the transcript hash was 32 bytes of 01 (this length
+%% would make sense for SHA-256), the content covered by the digital
+%% signature for a server CertificateVerify would be:
+%%
+%% 2020202020202020202020202020202020202020202020202020202020202020
+%% 2020202020202020202020202020202020202020202020202020202020202020
+%% 544c5320312e332c207365727665722043657274696669636174655665726966
+%% 79
+%% 00
+%% 0101010101010101010101010101010101010101010101010101010101010101
+digitally_sign(THash, Context, HashAlgo, PrivateKey) ->
+ Content = build_content(Context, THash),
+
+ %% The length of the Salt MUST be equal to the length of the output
+ %% of the digest algorithm: rsa_pss_saltlen = -1
+ try public_key:sign(Content, HashAlgo, PrivateKey,
+ [{rsa_padding, rsa_pkcs1_pss_padding},
+ {rsa_pss_saltlen, -1},
+ {rsa_mgf1_md, HashAlgo}]) of
+ Signature ->
+ {ok, Signature}
+ catch
+ error:badarg ->
+ {error, badarg}
+ end.
+
+
+build_content(Context, THash) ->
+ Prefix = binary:copy(<<32>>, 64),
+ <<Prefix/binary,Context/binary,?BYTE(0),THash/binary>>.
+
+
+%%====================================================================
+%% Handle handshake messages
+%%====================================================================
+
+
+do_start(#change_cipher_spec{},
+ #state{connection_states = _ConnectionStates0,
+ session = #session{session_id = _SessionId,
+ own_certificate = _OwnCert},
+ ssl_options = #ssl_options{} = _SslOpts,
+ key_share = _KeyShare,
+ handshake_env = #handshake_env{tls_handshake_history = _HHistory0},
+ static_env = #static_env{
+ cert_db = _CertDbHandle,
+ cert_db_ref = _CertDbRef,
+ socket = _Socket,
+ transport_cb = _Transport}
+ } = State0) ->
+ %% {Ref,Maybe} = maybe(),
+
+ try
+
+ State0
+
+ catch
+ {_Ref, {state_not_implemented, State}} ->
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {state_not_implemented, State})
+ end;
+do_start(#client_hello{cipher_suites = ClientCiphers,
+ session_id = SessionId,
+ extensions = Extensions} = _Hello,
+ #state{connection_states = _ConnectionStates0,
+ ssl_options = #ssl_options{ciphers = ServerCiphers,
+ signature_algs = ServerSignAlgs,
+ signature_algs_cert = _SignatureSchemes, %% TODO: check!
+ supported_groups = ServerGroups0},
+ session = #session{own_certificate = Cert}} = State0) ->
+
+ ClientGroups0 = maps:get(elliptic_curves, Extensions, undefined),
+ ClientGroups = get_supported_groups(ClientGroups0),
+ ServerGroups = get_supported_groups(ServerGroups0),
+
+ ClientShares0 = maps:get(key_share, Extensions, undefined),
+ ClientShares = get_key_shares(ClientShares0),
+
+ ClientSignAlgs = get_signature_scheme_list(
+ maps:get(signature_algs, Extensions, undefined)),
+ ClientSignAlgsCert = get_signature_scheme_list(
+ maps:get(signature_algs_cert, Extensions, undefined)),
+
+ %% TODO: use library function if it exists
+ %% Init the maybe "monad"
+ {Ref,Maybe} = maybe(),
+
+ try
+ %% 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(ClientCiphers, ServerCiphers)),
+ Groups = Maybe(select_common_groups(ServerGroups, ClientGroups)),
+ Maybe(validate_key_share(ClientGroups, ClientShares)),
+
+ {PublicKeyAlgo, SignAlgo, SignHash} = get_certificate_params(Cert),
+
+ %% Check if client supports signature algorithm of server certificate
+ Maybe(check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgs, ClientSignAlgsCert)),
+
+ %% Select signature algorithm (used in CertificateVerify message).
+ SelectedSignAlg = Maybe(select_sign_algo(PublicKeyAlgo, ClientSignAlgs, ServerSignAlgs)),
+
+ %% 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),
+
+ State1 = update_start_state(State0, Cipher, KeyShare, SessionId),
+
+ %% 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.
+ {State2, NextState} =
+ Maybe(send_hello_retry_request(State1, ClientPubKey, KeyShare, SessionId)),
+
+ %% TODO: Add Context to state?
+ Context = #{group => Group,
+ sign_alg => SelectedSignAlg,
+ client_share => ClientPubKey},
+ {State2, Context, NextState}
+
+ %% TODO:
+ %% - session handling
+ %% - handle extensions: ALPN
+ %% (do not handle: NPN, srp, renegotiation_info, ec_point_formats)
+
+ catch
+ {Ref, {insufficient_security, no_suitable_groups}} ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_groups);
+ {Ref, illegal_parameter} ->
+ ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
+ {Ref, no_suitable_cipher} ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_cipher);
+ {Ref, {insufficient_security, no_suitable_signature_algorithm}} ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm);
+ {Ref, {insufficient_security, no_suitable_public_key}} ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_public_key)
+ end.
+
+
+do_negotiated(#{client_share := ClientKey,
+ group := SelectedGroup,
+ sign_alg := SignatureScheme
+ },
+ #state{connection_states = ConnectionStates0,
+ session = #session{session_id = SessionId,
+ own_certificate = OwnCert},
+ ssl_options = #ssl_options{} = _SslOpts,
+ key_share = KeyShare,
+ handshake_env = #handshake_env{tls_handshake_history = _HHistory0},
+ connection_env = #connection_env{private_key = CertPrivateKey},
+ static_env = #static_env{
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef,
+ socket = _Socket,
+ transport_cb = _Transport}
+ } = State0) ->
+ {Ref,Maybe} = maybe(),
+
+ try
+ %% Create server_hello
+ %% Extensions: supported_versions, key_share, (pre_shared_key)
+ ServerHello = server_hello(server_hello, SessionId, KeyShare, ConnectionStates0),
+
+ {State1, _} = tls_connection:send_handshake(ServerHello, State0),
+
+ State2 =
+ calculate_handshake_secrets(ClientKey, SelectedGroup, KeyShare, State1),
+
+ State3 = ssl_record:step_encryption_state(State2),
+
+ %% Create EncryptedExtensions
+ EncryptedExtensions = encrypted_extensions(),
+
+ %% Encode EncryptedExtensions
+ State4 = tls_connection:queue_handshake(EncryptedExtensions, State3),
+
+ %% Create Certificate
+ Certificate = certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, server),
+
+ %% Encode Certificate
+ State5 = tls_connection:queue_handshake(Certificate, State4),
+
+ %% Create CertificateVerify
+ CertificateVerify = Maybe(certificate_verify(CertPrivateKey, SignatureScheme,
+ State5, server)),
+ %% Encode CertificateVerify
+ State6 = tls_connection:queue_handshake(CertificateVerify, State5),
+
+ %% Create Finished
+ Finished = finished(State6),
+
+ %% Encode Finished
+ State7 = tls_connection:queue_handshake(Finished, State6),
+
+ %% Send first flight
+ {State8, _} = tls_connection:send_handshake_flight(State7),
+
+ State8
+
+ catch
+ {Ref, {state_not_implemented, State}} ->
+ %% TODO
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {state_not_implemented, State})
+ end.
+
+
+do_wait_finished(#change_cipher_spec{},
+ #state{connection_states = _ConnectionStates0,
+ session = #session{session_id = _SessionId,
+ own_certificate = _OwnCert},
+ ssl_options = #ssl_options{} = _SslOpts,
+ key_share = _KeyShare,
+ handshake_env = #handshake_env{tls_handshake_history = _HHistory0},
+ static_env = #static_env{
+ cert_db = _CertDbHandle,
+ cert_db_ref = _CertDbRef,
+ socket = _Socket,
+ transport_cb = _Transport}
+ } = State0) ->
+ %% {Ref,Maybe} = maybe(),
+
+ try
+
+ State0
+
+ catch
+ {_Ref, {state_not_implemented, State}} ->
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {state_not_implemented, State})
+ end;
+do_wait_finished(#finished{verify_data = VerifyData},
+ #state{connection_states = _ConnectionStates0,
+ session = #session{session_id = _SessionId,
+ own_certificate = _OwnCert},
+ ssl_options = #ssl_options{} = _SslOpts,
+ key_share = _KeyShare,
+ handshake_env = #handshake_env{tls_handshake_history = _HHistory0},
+ static_env = #static_env{
+ cert_db = _CertDbHandle,
+ cert_db_ref = _CertDbRef,
+ socket = _Socket,
+ transport_cb = _Transport}
+ } = State0) ->
+
+ {Ref,Maybe} = maybe(),
+
+ try
+ Maybe(validate_client_finished(State0, VerifyData)),
+
+ State1 = calculate_traffic_secrets(State0),
+
+ %% Configure traffic keys
+ ssl_record:step_encryption_state(State1)
+
+
+ catch
+ {Ref, decrypt_error} ->
+ ?ALERT_REC(?FATAL, ?DECRYPT_ERROR, decrypt_error);
+ {_, {state_not_implemented, State}} ->
+ %% TODO
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {state_not_implemented, State})
+ end.
+
+
+%% TODO: Remove this function!
+%% not_implemented(State) ->
+%% {error, {state_not_implemented, State}}.
+
+
+%% Recipients of Finished messages MUST verify that the contents are
+%% correct and if incorrect MUST terminate the connection with a
+%% "decrypt_error" alert.
+validate_client_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 client's finished message, it is not part of the handshake context
+ %% when the client 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, decrypt_error}.
+
+
+send_hello_retry_request(#state{connection_states = ConnectionStates0} = State0,
+ no_suitable_key, KeyShare, SessionId) ->
+ ServerHello = server_hello(hello_retry_request, SessionId, KeyShare, ConnectionStates0),
+ {State1, _} = tls_connection:send_handshake(ServerHello, State0),
+
+ %% TODO: Fix handshake history!
+ State2 = replace_ch1_with_message_hash(State1),
+
+ {ok, {State2, start}};
+send_hello_retry_request(State0, _, _, _) ->
+ %% Suitable key found.
+ {ok, {State0, negotiated}}.
+
+
+%% 4.4.1. The Transcript Hash
+%%
+%% As an exception to this general rule, when the server responds to a
+%% ClientHello with a HelloRetryRequest, the value of ClientHello1 is
+%% replaced with a special synthetic handshake message of handshake type
+%% "message_hash" containing Hash(ClientHello1). I.e.,
+%%
+%% Transcript-Hash(ClientHello1, HelloRetryRequest, ... Mn) =
+%% Hash(message_hash || /* Handshake type */
+%% 00 00 Hash.length || /* Handshake message length (bytes) */
+%% Hash(ClientHello1) || /* Hash of ClientHello1 */
+%% HelloRetryRequest || ... || Mn)
+%%
+%% NOTE: Hash.length is used in practice (openssl) and not message length!
+%% It is most probably a fault in the RFC.
+replace_ch1_with_message_hash(#state{connection_states = ConnectionStates,
+ handshake_env =
+ #handshake_env{
+ tls_handshake_history =
+ {[HRR,CH1|HHistory], LM}} = HSEnv} = State0) ->
+ #{security_parameters := SecParamsR} =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
+ MessageHash = message_hash(CH1, HKDFAlgo),
+ State0#state{handshake_env =
+ HSEnv#handshake_env{
+ tls_handshake_history =
+ {[HRR,MessageHash|HHistory], LM}}}.
+
+
+message_hash(ClientHello1, HKDFAlgo) ->
+ [?MESSAGE_HASH,
+ 0,0,ssl_cipher:hash_size(HKDFAlgo),
+ crypto:hash(HKDFAlgo, ClientHello1)].
+
+
+calculate_handshake_secrets(ClientKey, SelectedGroup, KeyShare,
+ #state{connection_states = ConnectionStates,
+ handshake_env =
+ #handshake_env{
+ tls_handshake_history = HHistory}} = State0) ->
+ #{security_parameters := SecParamsR} =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ #security_parameters{prf_algorithm = HKDFAlgo,
+ cipher_suite = CipherSuite} = SecParamsR,
+
+ %% Calculate handshake_secret
+ PSK = binary:copy(<<0>>, ssl_cipher:hash_size(HKDFAlgo)),
+ EarlySecret = tls_v1:key_schedule(early_secret, HKDFAlgo , {psk, PSK}),
+ PrivateKey = get_server_private_key(KeyShare), %% #'ECPrivateKey'{}
+
+ IKM = calculate_shared_secret(ClientKey, PrivateKey, SelectedGroup),
+ HandshakeSecret = tls_v1:key_schedule(handshake_secret, HKDFAlgo, IKM, EarlySecret),
+
+ %% Calculate [sender]_handshake_traffic_secret
+ {Messages, _} = HHistory,
+ ClientHSTrafficSecret =
+ tls_v1:client_handshake_traffic_secret(HKDFAlgo, HandshakeSecret, lists:reverse(Messages)),
+ ServerHSTrafficSecret =
+ tls_v1:server_handshake_traffic_secret(HKDFAlgo, HandshakeSecret, lists:reverse(Messages)),
+
+ %% Calculate traffic keys
+ #{cipher := Cipher} = ssl_cipher_format:suite_definition(CipherSuite),
+ {ReadKey, ReadIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ClientHSTrafficSecret),
+ {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ServerHSTrafficSecret),
+
+ %% Calculate Finished Keys
+ ReadFinishedKey = tls_v1:finished_key(ClientHSTrafficSecret, HKDFAlgo),
+ WriteFinishedKey = tls_v1:finished_key(ServerHSTrafficSecret, HKDFAlgo),
+
+ update_pending_connection_states(State0, HandshakeSecret,
+ ReadKey, ReadIV, ReadFinishedKey,
+ WriteKey, WriteIV, WriteFinishedKey).
+
+calculate_traffic_secrets(#state{connection_states = ConnectionStates,
+ handshake_env =
+ #handshake_env{
+ tls_handshake_history = HHistory}} = State0) ->
+ #{security_parameters := SecParamsR} =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ #security_parameters{prf_algorithm = HKDFAlgo,
+ cipher_suite = CipherSuite,
+ master_secret = HandshakeSecret} = SecParamsR,
+
+ MasterSecret =
+ tls_v1:key_schedule(master_secret, HKDFAlgo, HandshakeSecret),
+
+ {Messages0, _} = HHistory,
+
+ %% Drop Client Finish
+ [_|Messages] = Messages0,
+
+ %% Calculate [sender]_application_traffic_secret_0
+ ClientAppTrafficSecret0 =
+ tls_v1:client_application_traffic_secret_0(HKDFAlgo, MasterSecret, lists:reverse(Messages)),
+ ServerAppTrafficSecret0 =
+ tls_v1:server_application_traffic_secret_0(HKDFAlgo, MasterSecret, lists:reverse(Messages)),
+
+ %% Calculate traffic keys
+ #{cipher := Cipher} = ssl_cipher_format:suite_definition(CipherSuite),
+ {ReadKey, ReadIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ClientAppTrafficSecret0),
+ {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ServerAppTrafficSecret0),
+
+ update_pending_connection_states(State0, MasterSecret,
+ ReadKey, ReadIV, undefined,
+ 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)
+ when is_binary(OthersKey) andalso is_binary(MyKey) andalso
+ (Group =:= x25519 orelse Group =:= x448)->
+ crypto:compute_key(ecdh, OthersKey, MyKey, Group);
+%% FFDHE
+calculate_shared_secret(OthersKey, MyKey, Group)
+ when is_binary(OthersKey) andalso is_binary(MyKey) ->
+ Params = #'DHParameter'{prime = P} = ssl_dh_groups:dh_params(Group),
+ S = public_key:compute_key(OthersKey, MyKey, Params),
+ Size = byte_size(binary:encode_unsigned(P)),
+ ssl_cipher:add_zero_padding(S, Size);
+%% ECDHE
+calculate_shared_secret(OthersKey, MyKey = #'ECPrivateKey'{}, _Group)
+ when is_binary(OthersKey) ->
+ Point = #'ECPoint'{point = OthersKey},
+ public_key:compute_key(Point, MyKey).
+
+
+update_pending_connection_states(#state{connection_states =
+ CS = #{pending_read := PendingRead0,
+ pending_write := PendingWrite0}} = State,
+ HandshakeSecret,
+ ReadKey, ReadIV, ReadFinishedKey,
+ WriteKey, WriteIV, WriteFinishedKey) ->
+ PendingRead = update_connection_state(PendingRead0, HandshakeSecret,
+ ReadKey, ReadIV, ReadFinishedKey),
+ PendingWrite = update_connection_state(PendingWrite0, HandshakeSecret,
+ WriteKey, WriteIV, WriteFinishedKey),
+ State#state{connection_states = CS#{pending_read => PendingRead,
+ pending_write => PendingWrite}}.
+
+update_connection_state(ConnectionState = #{security_parameters := SecurityParameters0},
+ HandshakeSecret, Key, IV, FinishedKey) ->
+ %% Store secret
+ SecurityParameters = SecurityParameters0#security_parameters{
+ master_secret = HandshakeSecret},
+ ConnectionState#{security_parameters => SecurityParameters,
+ cipher_state => cipher_init(Key, IV, FinishedKey)}.
+
+
+update_start_state(#state{connection_states = ConnectionStates0,
+ connection_env = CEnv,
+ session = Session} = State,
+ Cipher, KeyShare, SessionId) ->
+ #{security_parameters := SecParamsR0} = PendingRead =
+ maps:get(pending_read, ConnectionStates0),
+ #{security_parameters := SecParamsW0} = PendingWrite =
+ maps:get(pending_write, ConnectionStates0),
+ SecParamsR = ssl_cipher:security_parameters_1_3(SecParamsR0, Cipher),
+ SecParamsW = ssl_cipher:security_parameters_1_3(SecParamsW0, Cipher),
+ ConnectionStates =
+ ConnectionStates0#{pending_read => PendingRead#{security_parameters => SecParamsR},
+ pending_write => PendingWrite#{security_parameters => SecParamsW}},
+ State#state{connection_states = ConnectionStates,
+ key_share = KeyShare,
+ session = Session#session{session_id = SessionId},
+ connection_env = CEnv#connection_env{negotiated_version = {3,4}}}.
+
+
+cipher_init(Key, IV, FinishedKey) ->
+ #cipher_state{key = Key,
+ iv = IV,
+ finished_key = FinishedKey,
+ tag_len = 16}.
+
+
+%% If there is no overlap between the received
+%% "supported_groups" and the groups supported by the server, then the
+%% server MUST abort the handshake with a "handshake_failure" or an
+%% "insufficient_security" alert.
+select_common_groups(_, []) ->
+ {error, {insufficient_security, no_suitable_groups}};
+select_common_groups(ServerGroups, ClientGroups) ->
+ Fun = fun(E) -> lists:member(E, ClientGroups) end,
+ case lists:filter(Fun, ServerGroups) of
+ [] ->
+ {error, {insufficient_security, no_suitable_groups}};
+ L ->
+ {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_key_share(_ ,[]) ->
+ ok;
+validate_key_share([], _) ->
+ {error, illegal_parameter};
+validate_key_share([G|ClientGroups], [{_, G, _}|ClientShares]) ->
+ validate_key_share(ClientGroups, ClientShares);
+validate_key_share([_|ClientGroups], [_|_] = ClientShares) ->
+ validate_key_share(ClientGroups, ClientShares).
+
+
+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_public_key(Group, ClientShares) ->
+%% case lists:keysearch(Group, 2, ClientShares) of
+%% {value, {_, _, ClientPublicKey}} ->
+%% ClientPublicKey;
+%% false ->
+%% %% 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.
+%% no_suitable_key
+%% end.
+
+select_cipher_suite([], _) ->
+ {error, no_suitable_cipher};
+select_cipher_suite([Cipher|ClientCiphers], ServerCiphers) ->
+ case lists:member(Cipher, tls_v1:suites('TLS_v1.3')) andalso
+ lists:member(Cipher, ServerCiphers) of
+ true ->
+ {ok, Cipher};
+ false ->
+ select_cipher_suite(ClientCiphers, ServerCiphers)
+ end.
+
+%% RFC 8446 (TLS 1.3)
+%% TLS 1.3 provides two extensions for indicating which signature
+%% algorithms may be used in digital signatures. The
+%% "signature_algorithms_cert" extension applies to signatures in
+%% certificates and the "signature_algorithms" extension, which
+%% originally appeared in TLS 1.2, applies to signatures in
+%% CertificateVerify messages.
+%%
+%% If no "signature_algorithms_cert" extension is
+%% present, then the "signature_algorithms" extension also applies to
+%% signatures appearing in certificates.
+
+%% Check if the signature algorithm of the server certificate is supported
+%% by the client.
+check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgs, undefined) ->
+ do_check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgs);
+check_cert_sign_algo(SignAlgo, SignHash, _, ClientSignAlgsCert) ->
+ do_check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgsCert).
+
+
+%% DSA keys are not supported by TLS 1.3
+select_sign_algo(dsa, _ClientSignAlgs, _ServerSignAlgs) ->
+ {error, {insufficient_security, no_suitable_public_key}};
+%% TODO: Implement support for ECDSA keys!
+select_sign_algo(_, [], _) ->
+ {error, {insufficient_security, no_suitable_signature_algorithm}};
+select_sign_algo(PublicKeyAlgo, [C|ClientSignAlgs], ServerSignAlgs) ->
+ {_, S, _} = ssl_cipher:scheme_to_components(C),
+ %% RSASSA-PKCS1-v1_5 and Legacy algorithms are not defined for use in signed
+ %% TLS handshake messages: filter sha-1 and rsa_pkcs1.
+ case ((PublicKeyAlgo =:= rsa andalso S =:= rsa_pss_rsae)
+ orelse (PublicKeyAlgo =:= rsa_pss andalso S =:= rsa_pss_rsae))
+ andalso
+ lists:member(C, ServerSignAlgs) of
+ true ->
+ {ok, C};
+ false ->
+ select_sign_algo(PublicKeyAlgo, ClientSignAlgs, ServerSignAlgs)
+ end.
+
+
+do_check_cert_sign_algo(_, _, []) ->
+ {error, {insufficient_security, no_suitable_signature_algorithm}};
+do_check_cert_sign_algo(SignAlgo, SignHash, [Scheme|T]) ->
+ {Hash, Sign, _Curve} = ssl_cipher:scheme_to_components(Scheme),
+ case compare_sign_algos(SignAlgo, SignHash, Sign, Hash) of
+ true ->
+ ok;
+ _Else ->
+ do_check_cert_sign_algo(SignAlgo, SignHash, T)
+ end.
+
+
+%% id-RSASSA-PSS (rsa_pss) indicates that the key may only be used for PSS signatures.
+%% TODO: Uncomment when rsa_pss signatures are supported in certificates
+%% compare_sign_algos(rsa_pss, Hash, Algo, Hash)
+%% when Algo =:= rsa_pss_pss ->
+%% true;
+%% rsaEncryption (rsa) allows the key to be used for any of the standard encryption or
+%% signature schemes.
+compare_sign_algos(rsa, Hash, Algo, Hash)
+ when Algo =:= rsa_pss_rsae orelse
+ Algo =:= rsa_pkcs1 ->
+ true;
+compare_sign_algos(Algo, Hash, Algo, Hash) ->
+ true;
+compare_sign_algos(_, _, _, _) ->
+ false.
+
+
+get_certificate_params(Cert) ->
+ {SignAlgo0, _Param, PublicKeyAlgo0} = ssl_handshake:get_cert_params(Cert),
+ {SignHash0, SignAlgo} = public_key:pkix_sign_types(SignAlgo0),
+ %% Convert hash to new format
+ SignHash = case SignHash0 of
+ sha ->
+ sha1;
+ H -> H
+ end,
+ PublicKeyAlgo = public_key_algo(PublicKeyAlgo0),
+ {PublicKeyAlgo, SignAlgo, SignHash}.
+
+
+%% Note: copied from ssl_handshake
+public_key_algo(?'id-RSASSA-PSS') ->
+ rsa_pss;
+public_key_algo(?rsaEncryption) ->
+ rsa;
+public_key_algo(?'id-ecPublicKey') ->
+ ecdsa;
+public_key_algo(?'id-dsa') ->
+ dsa.
+
+get_signature_scheme_list(undefined) ->
+ undefined;
+get_signature_scheme_list(#signature_algorithms_cert{
+ signature_scheme_list = ClientSignatureSchemes}) ->
+ ClientSignatureSchemes;
+get_signature_scheme_list(#signature_algorithms{
+ signature_scheme_list = ClientSignatureSchemes}) ->
+ ClientSignatureSchemes.
+
+get_supported_groups(#supported_groups{supported_groups = Groups}) ->
+ Groups.
+
+get_key_shares(#key_share_client_hello{client_shares = ClientShares}) ->
+ ClientShares.
+
+maybe() ->
+ Ref = erlang:make_ref(),
+ Ok = fun(ok) -> ok;
+ ({ok,R}) -> R;
+ ({error,Reason}) ->
+ throw({Ref,Reason})
+ end,
+ {Ref,Ok}.
diff --git a/lib/ssl/src/tls_handshake_1_3.hrl b/lib/ssl/src/tls_handshake_1_3.hrl
new file mode 100644
index 0000000000..7ae1b93e1c
--- /dev/null
+++ b/lib/ssl/src/tls_handshake_1_3.hrl
@@ -0,0 +1,238 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2018. All 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: Record and constant defenitions for the TLS-handshake protocol
+%% see RFC 8446. Also includes supported hello extensions.
+%%----------------------------------------------------------------------
+
+-ifndef(tls_handshake_1_3).
+-define(tls_handshake_1_3, true).
+
+%% Common to TLS-1.3 and previous TLS versions
+%% Some defenitions may not exist in TLS-1.3 this is
+%% handled elsewhere
+-include("tls_handshake.hrl").
+
+%% New handshake types in TLS-1.3 RFC 8446 B.3
+-define(NEW_SESSION_TICKET, 4).
+-define(END_OF_EARLY_DATA, 5).
+-define(ENCRYPTED_EXTENSIONS, 8).
+-define(KEY_UPDATE, 24).
+%% %% Not really a message but special way to handle handshake hashes
+%% %% when a "hello-retry-request" (special server_hello) is sent
+-define(MESSAGE_HASH, 254).
+
+%% %% RFC 8446 B.3.1.
+%% %% New extension types in TLS-1.3
+-define(PRE_SHARED_KEY_EXT, 41).
+-define(EARLY_DATA_EXT, 42).
+%%-define(SUPPORTED_VERSIONS_EXT, 43). %% Updates TLS 1.2 so defined in ssl_handshake.hrl
+-define(COOKIE_EXT, 44).
+-define(PSK_KEY_EXCHANGE_MODES_EXT, 45).
+-define(CERTIFICATE_AUTHORITIES_EXT, 47).
+-define(OID_FILTERS_EXT, 48).
+-define(POST_HANDSHAKE_AUTH_EXT, 49).
+%% -define(SIGNATURE_ALGORITHMS_CERT_EXT, 50). %% Updates TLS 1.2 so defined in ssl_handshake.hrl
+-define(KEY_SHARE_EXT, 51).
+
+%% RFC 8446 B.3.1
+-record(key_share_entry, {
+ group, %NamedGroup
+ key_exchange %key_exchange<1..2^16-1>;
+ }).
+-record(key_share_client_hello, {
+ client_shares %% KeyShareEntry client_shares<0..2^16-1>;
+ }).
+-record(key_share_hello_retry_request, {
+ selected_group %% NamedGroup
+ }).
+-record(key_share_server_hello, {
+ server_share %% KeyShareEntry server_share;
+ }).
+
+-record(uncompressed_point_representation, {
+ legacy_form = 4, % uint8 legacy_form = 4;
+ x, % opaque X[coordinate_length];
+ y % opaque Y[coordinate_length];
+ }).
+
+-define(PSK_KE, 0).
+-define(PSK_DHE_KE, 1).
+
+-record(psk_keyexchange_modes, {
+ ke_modes % ke_modes<1..255>
+ }).
+-record(empty, {
+ }).
+-record(early_data_indication, {
+ indication % uint32 max_early_data_size (new_session_ticket) |
+ %% #empty{} (client_hello, encrypted_extensions)
+ }).
+-record(psk_identity, {
+ identity, % opaque identity<1..2^16-1>
+ obfuscated_ticket_age % uint32
+ }).
+-record(offered_psks, {
+ psk_identity, %identities<7..2^16-1>;
+ psk_binder_entry %binders<33..2^16-1>, opaque PskBinderEntry<32..255>
+ }).
+-record(pre_shared_keyextension,{
+ extension %OfferedPsks (client_hello) | uint16 selected_identity (server_hello)
+ }).
+
+%% RFC 8446 B.3.1.2.
+-record(cookie, {
+ cookie %cookie<1..2^16-1>;
+ }).
+
+%%% RFC 8446 B.3.1.3. Signature Algorithm Extension
+%% Signature Schemes
+%% RSASSA-PKCS1-v1_5 algorithms
+-define(RSA_PKCS1_SHA256, 16#0401).
+-define(RSA_PKCS1_SHA384, 16#0501).
+-define(RSA_PKCS1_SHA512, 16#0601).
+
+%% ECDSA algorithms
+-define(ECDSA_SECP256R1_SHA256, 16#0403).
+-define(ECDSA_SECP384R1_SHA384, 16#0503).
+-define(ECDSA_SECP521R1_SHA512, 16#0603).
+
+%% RSASSA-PSS algorithms with public key OID rsaEncryption
+-define(RSA_PSS_RSAE_SHA256, 16#0804).
+-define(RSA_PSS_RSAE_SHA384, 16#0805).
+-define(RSA_PSS_RSAE_SHA512, 16#0806).
+
+%% EdDSA algorithms
+-define(ED25519, 16#0807).
+-define(ED448, 16#0808).
+
+%% RSASSA-PSS algorithms with public key OID RSASSA-PSS
+-define(RSA_PSS_PSS_SHA256, 16#0809).
+-define(RSA_PSS_PSS_SHA384, 16#080a).
+-define(RSA_PSS_PSS_SHA512, 16#080b).
+
+%% Legacy algorithms
+-define(RSA_PKCS1_SHA1, 16#201).
+-define(ECDSA_SHA1, 16#0203).
+
+%% RFC 8446 B.3.1.4. Supported Groups Extension
+%% Elliptic Curve Groups (ECDHE)
+-define(SECP256R1, 16#0017).
+-define(SECP384R1, 16#0018).
+-define(SECP521R1, 16#0019).
+-define(X25519, 16#001D).
+-define(X448, 16#001E).
+
+%% RFC 8446 Finite Field Groups (DHE)
+-define(FFDHE2048, 16#0100).
+-define(FFDHE3072, 16#0101).
+-define(FFDHE4096, 16#0102).
+-define(FFDHE6144, 16#0103).
+-define(FFDHE8192 ,16#0104).
+
+-record(named_group_list, {
+ named_group_list %named_group_list<2..2^16-1>;
+ }).
+
+%% RFC 8446 B.3.2 Server Parameters Messages
+%% opaque DistinguishedName<1..2^16-1>;XS
+-record(certificate_authoritie_sextension, {
+ authorities %DistinguishedName authorities<3..2^16-1>;
+ }).
+
+-record(oid_filter, {
+ certificate_extension_oid, % opaque certificate_extension_oid<1..2^8-1>;
+ certificate_extension_values % opaque certificate_extension_values<0..2^16-1>;
+ }).
+
+-record(oid_filter_extension, {
+ filters %OIDFilter filters<0..2^16-1>;
+ }).
+-record(post_handshake_auth, {
+ }).
+
+-record(encrypted_extensions, {
+ extensions %extensions<0..2^16-1>;
+ }).
+
+-record(certificate_request_1_3, {
+ certificate_request_context, % opaque certificate_request_context<0..2^8-1>;
+ extensions %Extension extensions<2..2^16-1>;
+ }).
+
+%% RFC 8446 B.3.3 Authentication Messages
+
+%% Certificate Type
+-define(X509, 0).
+-define(OpenPGP_RESERVED, 1).
+-define(RawPublicKey, 2).
+
+-record(certificate_entry, {
+ data,
+ %% select (certificate_type) {
+ %% case RawPublicKey:
+ %% /* From RFC 7250 ASN.1_subjectPublicKeyInfo */
+ %% opaque ASN1_subjectPublicKeyInfo<1..2^24-1>;
+ %%
+ %% case X509:
+ %% opaque cert_data<1..2^24-1>;
+ %% };
+ extensions %% Extension extensions<0..2^16-1>;
+ }).
+
+-record(certificate_1_3, {
+ certificate_request_context, % opaque certificate_request_context<0..2^8-1>;
+ certificate_list % CertificateEntry certificate_list<0..2^24-1>;
+ }).
+
+-record(certificate_verify_1_3, {
+ algorithm, % SignatureScheme
+ signature % signature<0..2^16-1>
+ }).
+
+%% RFC 8446 B.3.4. Ticket Establishment
+-record(new_session_ticket, {
+ ticket_lifetime, %unit32
+ ticket_age_add, %unit32
+ ticket_nonce, %opaque ticket_nonce<0..255>;
+ ticket, %opaque ticket<1..2^16-1>
+ extensions %extensions<0..2^16-2>
+ }).
+
+%% RFC 8446 B.3.5. Updating Keys
+-record(end_of_early_data, {
+ }).
+
+-define(UPDATE_NOT_REQUESTED, 0).
+-define(UPDATE_REQUESTED, 1).
+
+-record(key_update, {
+ request_update
+ }).
+
+-type tls_handshake_1_3() :: #encrypted_extensions{} |
+ #certificate_request_1_3{} |
+ #certificate_1_3{} |
+ #certificate_verify_1_3{}.
+
+-export_type([tls_handshake_1_3/0]).
+
+-endif. % -ifdef(tls_handshake_1_3).
diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl
index 1776ec2627..9f0c588cb6 100644
--- a/lib/ssl/src/tls_record.erl
+++ b/lib/ssl/src/tls_record.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2019. All 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,9 +30,10 @@
-include("ssl_alert.hrl").
-include("tls_handshake.hrl").
-include("ssl_cipher.hrl").
+-include_lib("kernel/include/logger.hrl").
%% Handling of incoming data
--export([get_tls_records/3, init_connection_states/2]).
+-export([get_tls_records/4, init_connection_states/2]).
%% Encoding TLS records
-export([encode_handshake/3, encode_alert_record/3,
@@ -40,13 +41,16 @@
-export([encode_plain_text/4]).
%% Decoding
--export([decode_cipher_text/3]).
+-export([decode_cipher_text/4]).
+
+%% Logging helper
+-export([build_tls_record/1]).
%% Protocol version handling
-export([protocol_version/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/1, is_acceptable_version/2, hello_version/2]).
+ is_acceptable_version/1, is_acceptable_version/2, hello_version/1]).
-export_type([tls_version/0, tls_atom_version/0]).
@@ -75,15 +79,24 @@ init_connection_states(Role, BeastMitigation) ->
pending_write => Pending}.
%%--------------------------------------------------------------------
--spec get_tls_records(binary(), [tls_version()] | tls_version(), binary()) -> {[binary()], binary()} | #alert{}.
+-spec get_tls_records(
+ binary(),
+ [tls_version()] | tls_version(),
+ Buffer0 :: binary() | {'undefined' | #ssl_tls{}, {[binary()],non_neg_integer(),[binary()]}},
+ #ssl_options{}) ->
+ {Records :: [#ssl_tls{}],
+ Buffer :: {'undefined' | #ssl_tls{}, {[binary()],non_neg_integer(),[binary()]}}} |
+ #alert{}.
%%
%% and returns it as a list of tls_compressed binaries also returns leftover
%% Description: Given old buffer and new data from TCP, packs up a records
%% data
%%--------------------------------------------------------------------
-get_tls_records(Data, Version, Buffer) ->
- get_tls_records_aux(Version, <<Buffer/binary, Data/binary>>, []).
-
+get_tls_records(Data, Versions, Buffer, SslOpts) when is_binary(Buffer) ->
+ parse_tls_records(Versions, {[Data],byte_size(Data),[]}, SslOpts, undefined);
+get_tls_records(Data, Versions, {Hdr, {Front,Size,Rear}}, SslOpts) ->
+ parse_tls_records(Versions, {Front,Size + byte_size(Data),[Data|Rear]}, SslOpts, Hdr).
+
%%====================================================================
%% Encoding
%%====================================================================
@@ -94,6 +107,8 @@ get_tls_records(Data, Version, Buffer) ->
%
%% Description: Encodes a handshake message to send on the ssl-socket.
%%--------------------------------------------------------------------
+encode_handshake(Frag, {3, 4}, ConnectionStates) ->
+ tls_record_1_3:encode_handshake(Frag, ConnectionStates);
encode_handshake(Frag, Version,
#{current_write :=
#{beast_mitigation := BeastMitigation,
@@ -102,8 +117,8 @@ encode_handshake(Frag, Version,
ConnectionStates) ->
case iolist_size(Frag) of
N when N > ?MAX_PLAIN_TEXT_LENGTH ->
- Data = split_bin(iolist_to_binary(Frag), Version, BCA, BeastMitigation),
- encode_iolist(?HANDSHAKE, Data, Version, ConnectionStates);
+ Data = split_iovec(erlang:iolist_to_iovec(Frag), Version, BCA, BeastMitigation),
+ encode_fragments(?HANDSHAKE, Version, Data, ConnectionStates);
_ ->
encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates)
end.
@@ -114,6 +129,8 @@ encode_handshake(Frag, Version,
%%
%% Description: Encodes an alert message to send on the ssl-socket.
%%--------------------------------------------------------------------
+encode_alert_record(Alert, {3, 4}, ConnectionStates) ->
+ tls_record_1_3:encode_alert_record(Alert, ConnectionStates);
encode_alert_record(#alert{level = Level, description = Description},
Version, ConnectionStates) ->
encode_plain_text(?ALERT, Version, <<?BYTE(Level), ?BYTE(Description)>>,
@@ -129,80 +146,86 @@ encode_change_cipher_spec(Version, ConnectionStates) ->
encode_plain_text(?CHANGE_CIPHER_SPEC, Version, ?byte(?CHANGE_CIPHER_SPEC_PROTO), ConnectionStates).
%%--------------------------------------------------------------------
--spec encode_data(binary(), tls_version(), ssl_record:connection_states()) ->
- {iolist(), ssl_record:connection_states()}.
+-spec encode_data([binary()], tls_version(), ssl_record:connection_states()) ->
+ {[[binary()]], ssl_record:connection_states()}.
%%
%% Description: Encodes data to send on the ssl-socket.
%%--------------------------------------------------------------------
-encode_data(Frag, Version,
+encode_data(Data, {3, 4}, ConnectionStates) ->
+ tls_record_1_3:encode_data(Data, ConnectionStates);
+encode_data(Data, Version,
#{current_write := #{beast_mitigation := BeastMitigation,
security_parameters :=
#security_parameters{bulk_cipher_algorithm = BCA}}} =
ConnectionStates) ->
- Data = split_bin(Frag, Version, BCA, BeastMitigation),
- encode_iolist(?APPLICATION_DATA, Data, Version, ConnectionStates).
+ Fragments = split_iovec(Data, Version, BCA, BeastMitigation),
+ encode_fragments(?APPLICATION_DATA, Version, Fragments, ConnectionStates).
%%====================================================================
%% Decoding
%%====================================================================
%%--------------------------------------------------------------------
--spec decode_cipher_text(#ssl_tls{}, ssl_record:connection_states(), boolean()) ->
+-spec decode_cipher_text(tls_version(), #ssl_tls{}, ssl_record:connection_states(), boolean()) ->
{#ssl_tls{}, ssl_record:connection_states()}| #alert{}.
%%
%% Description: Decode cipher text
%%--------------------------------------------------------------------
-decode_cipher_text(#ssl_tls{type = Type, version = Version,
- fragment = CipherFragment} = CipherText,
+decode_cipher_text({3,4}, CipherTextRecord, ConnectionStates, _) ->
+ tls_record_1_3:decode_cipher_text(CipherTextRecord, ConnectionStates);
+decode_cipher_text(_, CipherTextRecord,
#{current_read :=
- #{compression_state := CompressionS0,
- sequence_number := Seq,
- cipher_state := CipherS0,
+ #{sequence_number := Seq,
security_parameters :=
- #security_parameters{
- cipher_type = ?AEAD,
- bulk_cipher_algorithm =
- BulkCipherAlgo,
- compression_algorithm = CompAlg}
- } = ReadState0} = ConnnectionStates0, _) ->
- AAD = start_additional_data(Type, Version, ReadState0),
- CipherS1 = ssl_record:nonce_seed(BulkCipherAlgo, <<?UINT64(Seq)>>, CipherS0),
- case ssl_record:decipher_aead(BulkCipherAlgo, CipherS1, AAD, CipherFragment, Version) of
- {PlainFragment, CipherState} ->
- {Plain, CompressionS1} = ssl_record:uncompress(CompAlg,
- PlainFragment, CompressionS0),
- ConnnectionStates = ConnnectionStates0#{
+ #security_parameters{cipher_type = ?AEAD,
+ bulk_cipher_algorithm = BulkCipherAlgo},
+ cipher_state := CipherS0
+ }
+ } = ConnectionStates0, _) ->
+ SeqBin = <<?UINT64(Seq)>>,
+ #ssl_tls{type = Type, version = {MajVer,MinVer} = Version, fragment = Fragment} = CipherTextRecord,
+ StartAdditionalData = <<SeqBin/binary, ?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer)>>,
+ CipherS = ssl_record:nonce_seed(BulkCipherAlgo, SeqBin, CipherS0),
+ case ssl_record:decipher_aead(
+ BulkCipherAlgo, CipherS, StartAdditionalData, Fragment, Version)
+ of
+ PlainFragment when is_binary(PlainFragment) ->
+ #{current_read :=
+ #{security_parameters := SecParams,
+ compression_state := CompressionS0} = ReadState0} = ConnectionStates0,
+ {Plain, CompressionS} = ssl_record:uncompress(SecParams#security_parameters.compression_algorithm,
+ PlainFragment, CompressionS0),
+ ConnectionStates = ConnectionStates0#{
current_read => ReadState0#{
- cipher_state => CipherState,
+ cipher_state => CipherS,
sequence_number => Seq + 1,
- compression_state => CompressionS1}},
- {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates};
+ compression_state => CompressionS}},
+ {CipherTextRecord#ssl_tls{fragment = Plain}, ConnectionStates};
#alert{} = Alert ->
Alert
end;
-decode_cipher_text(#ssl_tls{type = Type, version = Version,
- fragment = CipherFragment} = CipherText,
- #{current_read :=
- #{compression_state := CompressionS0,
- sequence_number := Seq,
- security_parameters :=
- #security_parameters{compression_algorithm = CompAlg}
- } = ReadState0} = ConnnectionStates0, PaddingCheck) ->
+decode_cipher_text(_, #ssl_tls{version = Version,
+ fragment = CipherFragment} = CipherTextRecord,
+ #{current_read := ReadState0} = ConnnectionStates0, PaddingCheck) ->
case ssl_record:decipher(Version, CipherFragment, ReadState0, PaddingCheck) of
{PlainFragment, Mac, ReadState1} ->
- MacHash = ssl_cipher:calc_mac_hash(Type, Version, PlainFragment, ReadState1),
+ MacHash = ssl_cipher:calc_mac_hash(CipherTextRecord#ssl_tls.type, Version, PlainFragment, ReadState1),
case ssl_record:is_correct_mac(Mac, MacHash) of
true ->
+ #{sequence_number := Seq,
+ compression_state := CompressionS0,
+ security_parameters :=
+ #security_parameters{compression_algorithm = CompAlg}} = ReadState0,
{Plain, CompressionS1} = ssl_record:uncompress(CompAlg,
PlainFragment, CompressionS0),
- ConnnectionStates = ConnnectionStates0#{
- current_read => ReadState1#{
- sequence_number => Seq + 1,
- compression_state => CompressionS1}},
- {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates};
+ ConnnectionStates =
+ ConnnectionStates0#{current_read =>
+ ReadState1#{sequence_number => Seq + 1,
+ compression_state => CompressionS1}},
+ {CipherTextRecord#ssl_tls{fragment = Plain}, ConnnectionStates};
false ->
- ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
end;
#alert{} = Alert ->
Alert
@@ -219,6 +242,8 @@ decode_cipher_text(#ssl_tls{type = Type, version = 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') ->
@@ -229,6 +254,8 @@ protocol_version(sslv3) ->
{3, 0};
protocol_version(sslv2) -> %% Backwards compatibility
{2, 0};
+protocol_version({3, 4}) ->
+ 'tlsv1.3';
protocol_version({3, 3}) ->
'tlsv1.2';
protocol_version({3, 2}) ->
@@ -362,10 +389,10 @@ is_acceptable_version({N,_} = Version, Versions)
is_acceptable_version(_,_) ->
false.
--spec hello_version(tls_version(), [tls_version()]) -> tls_version().
-hello_version(Version, _) when Version >= {3, 3} ->
- Version;
-hello_version(_, Versions) ->
+-spec hello_version([tls_version()]) -> tls_version().
+hello_version([Highest|_]) when Highest >= {3,3} ->
+ Highest;
+hello_version(Versions) ->
lowest_protocol_version(Versions).
%%--------------------------------------------------------------------
@@ -384,124 +411,230 @@ initial_connection_state(ConnectionEnd, BeastMitigation) ->
server_verify_data => undefined
}.
-get_tls_records_aux({MajVer, MinVer} = Version, <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer),
- ?UINT16(Length), Data:Length/binary, Rest/binary>>,
- Acc) when Type == ?APPLICATION_DATA;
- Type == ?HANDSHAKE;
- Type == ?ALERT;
- Type == ?CHANGE_CIPHER_SPEC ->
- get_tls_records_aux(Version, Rest, [#ssl_tls{type = Type,
- version = Version,
- fragment = Data} | Acc]);
-get_tls_records_aux(Versions, <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer),
- ?UINT16(Length), Data:Length/binary, Rest/binary>>,
- Acc) when is_list(Versions) andalso
- ((Type == ?APPLICATION_DATA)
- orelse
- (Type == ?HANDSHAKE)
- orelse
- (Type == ?ALERT)
- orelse
- (Type == ?CHANGE_CIPHER_SPEC)) ->
- case is_acceptable_version({MajVer, MinVer}, Versions) of
+%% Used by logging to recreate the received bytes
+build_tls_record(#ssl_tls{type = Type, version = {MajVer, MinVer}, fragment = Fragment}) ->
+ Length = byte_size(Fragment),
+ <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer),?UINT16(Length), Fragment/binary>>.
+
+
+parse_tls_records(Versions, Q, SslOpts, undefined) ->
+ decode_tls_records(Versions, Q, SslOpts, [], undefined, undefined, undefined);
+parse_tls_records(Versions, Q, SslOpts, #ssl_tls{type = Type, version = Version, fragment = Length}) ->
+ decode_tls_records(Versions, Q, SslOpts, [], Type, Version, Length).
+
+%% Generic code path
+decode_tls_records(Versions, {_,Size,_} = Q0, SslOpts, Acc, undefined, _Version, _Length) ->
+ if
+ 5 =< Size ->
+ {<<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer), ?UINT16(Length)>>, Q} = binary_from_front(5, Q0),
+ validate_tls_records_type(Versions, Q, SslOpts, Acc, Type, {MajVer,MinVer}, Length);
+ 3 =< Size ->
+ {<<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer)>>, Q} = binary_from_front(3, Q0),
+ validate_tls_records_type(Versions, Q, SslOpts, Acc, Type, {MajVer,MinVer}, undefined);
+ 1 =< Size ->
+ {<<?BYTE(Type)>>, Q} = binary_from_front(1, Q0),
+ validate_tls_records_type(Versions, Q, SslOpts, Acc, Type, undefined, undefined);
+ true ->
+ validate_tls_records_type(Versions, Q0, SslOpts, Acc, undefined, undefined, undefined)
+ end;
+decode_tls_records(Versions, {_,Size,_} = Q0, SslOpts, Acc, Type, undefined, _Length) ->
+ if
+ 4 =< Size ->
+ {<<?BYTE(MajVer),?BYTE(MinVer), ?UINT16(Length)>>, Q} = binary_from_front(4, Q0),
+ validate_tls_record_version(Versions, Q, SslOpts, Acc, Type, {MajVer,MinVer}, Length);
+ 2 =< Size ->
+ {<<?BYTE(MajVer),?BYTE(MinVer)>>, Q} = binary_from_front(2, Q0),
+ validate_tls_record_version(Versions, Q, SslOpts, Acc, Type, {MajVer,MinVer}, undefined);
true ->
- get_tls_records_aux(Versions, Rest, [#ssl_tls{type = Type,
- version = {MajVer, MinVer},
- fragment = Data} | Acc]);
- false ->
+ validate_tls_record_version(Versions, Q0, SslOpts, Acc, Type, undefined, undefined)
+ end;
+decode_tls_records(Versions, {_,Size,_} = Q0, SslOpts, Acc, Type, Version, undefined) ->
+ if
+ 2 =< Size ->
+ {<<?UINT16(Length)>>, Q} = binary_from_front(2, Q0),
+ validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length);
+ true ->
+ validate_tls_record_length(Versions, Q0, SslOpts, Acc, Type, Version, undefined)
+ end;
+decode_tls_records(Versions, Q, SslOpts, Acc, Type, Version, Length) ->
+ validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length).
+
+validate_tls_records_type(_Versions, Q, _SslOpts, Acc, undefined, _Version, _Length) ->
+ {lists:reverse(Acc),
+ {undefined, Q}};
+validate_tls_records_type(Versions, Q, SslOpts, Acc, Type, Version, Length) ->
+ if
+ ?KNOWN_RECORD_TYPE(Type) ->
+ validate_tls_record_version(Versions, Q, SslOpts, Acc, Type, Version, Length);
+ true ->
+ %% Not ?KNOWN_RECORD_TYPE(Type)
+ ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE)
+ end.
+
+validate_tls_record_version(_Versions, Q, _SslOpts, Acc, Type, undefined, _Length) ->
+ {lists:reverse(Acc),
+ {#ssl_tls{type = Type, version = undefined, fragment = undefined}, Q}};
+validate_tls_record_version(Versions, Q, 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, SslOpts, Acc, Type, Version, Length);
+ false ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
+ end;
+ {3, 4} when Version =:= {3, 3} ->
+ validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length);
+ Version ->
+ %% Exact version match
+ validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length);
+ _ ->
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
+ end.
+
+validate_tls_record_length(_Versions, Q, _SslOpts, Acc, Type, Version, undefined) ->
+ {lists:reverse(Acc),
+ {#ssl_tls{type = Type, version = Version, fragment = undefined}, Q}};
+validate_tls_record_length(Versions, {_,Size0,_} = Q0, SslOpts, Acc, Type, Version, Length) ->
+ if
+ Length =< ?MAX_CIPHER_TEXT_LENGTH ->
+ if
+ Length =< Size0 ->
+ %% Complete record
+ {Fragment, Q} = binary_from_front(Length, Q0),
+ Record = #ssl_tls{type = Type, version = Version, fragment = Fragment},
+ ssl_logger:debug(SslOpts#ssl_options.log_level, inbound, 'record', Record),
+ decode_tls_records(Versions, Q, SslOpts, [Record|Acc], undefined, undefined, undefined);
+ true ->
+ {lists:reverse(Acc),
+ {#ssl_tls{type = Type, version = Version, fragment = Length}, Q0}}
+ end;
+ true ->
+ ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW)
+ end.
+
+
+binary_from_front(SplitSize, {Front,Size,Rear}) ->
+ binary_from_front(SplitSize, Front, Size, Rear, []).
+%%
+binary_from_front(SplitSize, [], Size, [_] = Rear, Acc) ->
+ %% Optimize a simple case
+ binary_from_front(SplitSize, Rear, Size, [], Acc);
+binary_from_front(SplitSize, [], Size, Rear, Acc) ->
+ binary_from_front(SplitSize, lists:reverse(Rear), Size, [], Acc);
+binary_from_front(SplitSize, [Bin|Front], Size, Rear, []) ->
+ %% Optimize a frequent case
+ BinSize = byte_size(Bin),
+ if
+ SplitSize < BinSize ->
+ {RetBin, Rest} = erlang:split_binary(Bin, SplitSize),
+ {RetBin, {[Rest|Front],Size - SplitSize,Rear}};
+ BinSize < SplitSize ->
+ binary_from_front(SplitSize - BinSize, Front, Size, Rear, [Bin]);
+ true -> % Perfect fit
+ {Bin, {Front,Size - SplitSize,Rear}}
end;
-get_tls_records_aux(_, <<?BYTE(Type),?BYTE(_MajVer),?BYTE(_MinVer),
- ?UINT16(Length), _:Length/binary, _Rest/binary>>,
- _) when Type == ?APPLICATION_DATA;
- Type == ?HANDSHAKE;
- Type == ?ALERT;
- Type == ?CHANGE_CIPHER_SPEC ->
- ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC);
-get_tls_records_aux(_, <<0:1, _CT:7, ?BYTE(_MajVer), ?BYTE(_MinVer),
- ?UINT16(Length), _/binary>>,
- _Acc) when Length > ?MAX_CIPHER_TEXT_LENGTH ->
- ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW);
-get_tls_records_aux(_, Data, Acc) ->
- case size(Data) =< ?MAX_CIPHER_TEXT_LENGTH + ?INITIAL_BYTES of
- true ->
- {lists:reverse(Acc), Data};
- false ->
- ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE)
+binary_from_front(SplitSize, [Bin|Front], Size, Rear, Acc) ->
+ BinSize = byte_size(Bin),
+ if
+ SplitSize < BinSize ->
+ {Last, Rest} = erlang:split_binary(Bin, SplitSize),
+ RetBin = iolist_to_binary(lists:reverse(Acc, [Last])),
+ {RetBin, {[Rest|Front],Size - byte_size(RetBin),Rear}};
+ BinSize < SplitSize ->
+ binary_from_front(SplitSize - BinSize, Front, Size, Rear, [Bin|Acc]);
+ true -> % Perfect fit
+ RetBin = iolist_to_binary(lists:reverse(Acc, [Bin])),
+ {RetBin, {Front,Size - byte_size(RetBin),Rear}}
end.
+
%%--------------------------------------------------------------------
-encode_plain_text(Type, Version, Data, #{current_write := Write0} = ConnectionStates) ->
- {CipherFragment, Write1} = do_encode_plain_text(Type, Version, Data, Write0),
- {CipherText, Write} = encode_tls_cipher_text(Type, Version, CipherFragment, Write1),
- {CipherText, ConnectionStates#{current_write => Write}}.
-
-encode_tls_cipher_text(Type, {MajVer, MinVer}, Fragment, #{sequence_number := Seq} = Write) ->
- Length = erlang:iolist_size(Fragment),
- {[<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Length)>>, Fragment],
- Write#{sequence_number => Seq +1}}.
-
-encode_iolist(Type, Data, Version, ConnectionStates0) ->
- {ConnectionStates, EncodedMsg} =
- lists:foldl(fun(Text, {CS0, Encoded}) ->
- {Enc, CS1} =
- encode_plain_text(Type, Version, Text, CS0),
- {CS1, [Enc | Encoded]}
- end, {ConnectionStates0, []}, Data),
- {lists:reverse(EncodedMsg), ConnectionStates}.
-%%--------------------------------------------------------------------
-do_encode_plain_text(Type, Version, Data, #{compression_state := CompS0,
- cipher_state := CipherS0,
- sequence_number := Seq,
- security_parameters :=
- #security_parameters{
- cipher_type = ?AEAD,
- bulk_cipher_algorithm = BCAlg,
- compression_algorithm = CompAlg}
- } = WriteState0) ->
- {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0),
- CipherS = ssl_record:nonce_seed(BCAlg, <<?UINT64(Seq)>>, CipherS0),
- WriteState = WriteState0#{compression_state => CompS1,
- cipher_state => CipherS},
- AAD = start_additional_data(Type, Version, WriteState),
- ssl_record:cipher_aead(Version, Comp, WriteState, AAD);
-do_encode_plain_text(Type, Version, Data, #{compression_state := CompS0,
- security_parameters :=
- #security_parameters{compression_algorithm = CompAlg}
- }= WriteState0) ->
- {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0),
- WriteState1 = WriteState0#{compression_state => CompS1},
- MacHash = ssl_cipher:calc_mac_hash(Type, Version, Comp, WriteState1),
- ssl_record:cipher(Version, Comp, WriteState1, MacHash);
-do_encode_plain_text(_,_,_,CS) ->
+encode_plain_text(Type, Version, Data, ConnectionStates0) ->
+ {[CipherText],ConnectionStates} = encode_fragments(Type, Version, [Data], ConnectionStates0),
+ {CipherText,ConnectionStates}.
+%%--------------------------------------------------------------------
+encode_fragments(Type, Version, Data,
+ #{current_write := #{compression_state := CompS,
+ cipher_state := CipherS,
+ sequence_number := Seq}} = ConnectionStates) ->
+ encode_fragments(Type, Version, Data, ConnectionStates, CompS, CipherS, Seq, []).
+%%
+encode_fragments(_Type, _Version, [], #{current_write := WriteS} = CS,
+ CompS, CipherS, Seq, CipherFragments) ->
+ {lists:reverse(CipherFragments),
+ CS#{current_write := WriteS#{compression_state := CompS,
+ cipher_state := CipherS,
+ sequence_number := Seq}}};
+encode_fragments(Type, Version, [Text|Data],
+ #{current_write := #{security_parameters :=
+ #security_parameters{cipher_type = ?AEAD,
+ bulk_cipher_algorithm = BCAlg,
+ compression_algorithm = CompAlg} = SecPars}} = CS,
+ CompS0, CipherS0, Seq, CipherFragments) ->
+ {CompText, CompS} = ssl_record:compress(CompAlg, Text, CompS0),
+ SeqBin = <<?UINT64(Seq)>>,
+ CipherS1 = ssl_record:nonce_seed(BCAlg, SeqBin, CipherS0),
+ {MajVer, MinVer} = Version,
+ VersionBin = <<?BYTE(MajVer), ?BYTE(MinVer)>>,
+ StartAdditionalData = <<SeqBin/binary, ?BYTE(Type), VersionBin/binary>>,
+ {CipherFragment,CipherS} = ssl_record:cipher_aead(Version, CompText, CipherS1, StartAdditionalData, SecPars),
+ Length = byte_size(CipherFragment),
+ CipherHeader = <<?BYTE(Type), VersionBin/binary, ?UINT16(Length)>>,
+ encode_fragments(Type, Version, Data, CS, CompS, CipherS, Seq + 1,
+ [[CipherHeader, CipherFragment] | CipherFragments]);
+encode_fragments(Type, Version, [Text|Data],
+ #{current_write := #{security_parameters :=
+ #security_parameters{compression_algorithm = CompAlg,
+ mac_algorithm = MacAlgorithm} = SecPars,
+ mac_secret := MacSecret}} = CS,
+ CompS0, CipherS0, Seq, CipherFragments) ->
+ {CompText, CompS} = ssl_record:compress(CompAlg, Text, CompS0),
+ MacHash = ssl_cipher:calc_mac_hash(Type, Version, CompText, MacAlgorithm, MacSecret, Seq),
+ {CipherFragment,CipherS} = ssl_record:cipher(Version, CompText, CipherS0, MacHash, SecPars),
+ Length = byte_size(CipherFragment),
+ {MajVer, MinVer} = Version,
+ CipherHeader = <<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Length)>>,
+ encode_fragments(Type, Version, Data, CS, CompS, CipherS, Seq + 1,
+ [[CipherHeader, CipherFragment] | CipherFragments]);
+encode_fragments(_Type, _Version, _Data, CS, _CompS, _CipherS, _Seq, _CipherFragments) ->
exit({cs, CS}).
%%--------------------------------------------------------------------
-start_additional_data(Type, {MajVer, MinVer},
- #{sequence_number := SeqNo}) ->
- <<?UINT64(SeqNo), ?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer)>>.
%% 1/n-1 splitting countermeasure Rizzo/Duong-Beast, RC4 chiphers are
%% not vulnerable to this attack.
-split_bin(<<FirstByte:8, Rest/binary>>, Version, BCA, one_n_minus_one) when
- BCA =/= ?RC4 andalso ({3, 1} == Version orelse
- {3, 0} == Version) ->
- [[FirstByte]|do_split_bin(Rest)];
+split_iovec([<<FirstByte:8, Rest/binary>>|Data], Version, BCA, one_n_minus_one)
+ when (BCA =/= ?RC4) andalso ({3, 1} == Version orelse
+ {3, 0} == Version) ->
+ [[FirstByte]|split_iovec([Rest|Data])];
%% 0/n splitting countermeasure for clients that are incompatible with 1/n-1
%% splitting.
-split_bin(Bin, Version, BCA, zero_n) when
- BCA =/= ?RC4 andalso ({3, 1} == Version orelse
- {3, 0} == Version) ->
- [<<>>|do_split_bin(Bin)];
-split_bin(Bin, _, _, _) ->
- do_split_bin(Bin).
-
-do_split_bin(<<>>) -> [];
-do_split_bin(Bin) ->
- case Bin of
- <<Chunk:?MAX_PLAIN_TEXT_LENGTH/binary, Rest/binary>> ->
- [Chunk|do_split_bin(Rest)];
- _ ->
- [Bin]
- end.
+split_iovec(Data, Version, BCA, zero_n)
+ when (BCA =/= ?RC4) andalso ({3, 1} == Version orelse
+ {3, 0} == Version) ->
+ [<<>>|split_iovec(Data)];
+split_iovec(Data, _Version, _BCA, _BeatMitigation) ->
+ split_iovec(Data).
+
+split_iovec([]) ->
+ [];
+split_iovec(Data) ->
+ {Part,Rest} = split_iovec(Data, ?MAX_PLAIN_TEXT_LENGTH, []),
+ [Part|split_iovec(Rest)].
+%%
+split_iovec([Bin|Data], SplitSize, Acc) ->
+ BinSize = byte_size(Bin),
+ if
+ SplitSize < BinSize ->
+ {Last, Rest} = erlang:split_binary(Bin, SplitSize),
+ {lists:reverse(Acc, [Last]), [Rest|Data]};
+ BinSize < SplitSize ->
+ split_iovec(Data, SplitSize - BinSize, [Bin|Acc]);
+ true -> % Perfect match
+ {lists:reverse(Acc, [Bin]), Data}
+ end;
+split_iovec([], _SplitSize, Acc) ->
+ {lists:reverse(Acc),[]}.
+
%%--------------------------------------------------------------------
lowest_list_protocol_version(Ver, []) ->
Ver;
diff --git a/lib/ssl/src/tls_record_1_3.erl b/lib/ssl/src/tls_record_1_3.erl
new file mode 100644
index 0000000000..05acc08392
--- /dev/null
+++ b/lib/ssl/src/tls_record_1_3.erl
@@ -0,0 +1,298 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2018. All 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_record_1_3).
+
+-include("tls_record.hrl").
+-include("tls_record_1_3.hrl").
+-include("ssl_internal.hrl").
+-include("ssl_alert.hrl").
+-include("ssl_cipher.hrl").
+
+%% Encoding
+-export([encode_handshake/2, encode_alert_record/2,
+ encode_data/2]).
+-export([encode_plain_text/3]).
+
+%% Decoding
+-export([decode_cipher_text/2]).
+
+%%====================================================================
+%% Encoding
+%%====================================================================
+
+%%--------------------------------------------------------------------
+-spec encode_handshake(iolist(), ssl_record:connection_states()) ->
+ {iolist(), ssl_record:connection_states()}.
+%
+%% Description: Encodes a handshake message to send on the tls-1.3-socket.
+%%--------------------------------------------------------------------
+encode_handshake(Frag, ConnectionStates) ->
+ case iolist_size(Frag) of
+ N when N > ?MAX_PLAIN_TEXT_LENGTH ->
+ %% TODO: Consider padding here
+ Data = split_bin(iolist_to_binary(Frag), ?MAX_PLAIN_TEXT_LENGTH),
+ encode_iolist(?HANDSHAKE, Data, ConnectionStates);
+ _ ->
+ encode_plain_text(?HANDSHAKE, Frag, ConnectionStates)
+ end.
+
+%%--------------------------------------------------------------------
+-spec encode_alert_record(#alert{}, ssl_record:connection_states()) ->
+ {iolist(), ssl_record:connection_states()}.
+%%
+%% Description: Encodes an alert message to send on the ssl-socket.
+%%--------------------------------------------------------------------
+encode_alert_record(#alert{level = Level, description = Description},
+ ConnectionStates) ->
+ encode_plain_text(?ALERT, <<?BYTE(Level), ?BYTE(Description)>>,
+ ConnectionStates).
+%%--------------------------------------------------------------------
+-spec encode_data(binary(), ssl_record:connection_states()) ->
+ {iolist(), ssl_record:connection_states()}.
+%%
+%% Description: Encodes data to send on the ssl-socket.
+%%--------------------------------------------------------------------
+encode_data(Frag, ConnectionStates) ->
+ Data = split_bin(Frag, ?MAX_PLAIN_TEXT_LENGTH, {3,4}),
+ encode_iolist(?APPLICATION_DATA, Data, ConnectionStates).
+
+encode_plain_text(Type, Data0, #{current_write := Write0} = ConnectionStates) ->
+ PadLen = 0, %% TODO where to specify PadLen?
+ Data = inner_plaintext(Type, Data0, PadLen),
+ CipherFragment = encode_plain_text(Data, Write0),
+ {CipherText, Write} = encode_tls_cipher_text(CipherFragment, Write0),
+ {CipherText, ConnectionStates#{current_write => Write}}.
+
+encode_iolist(Type, Data, ConnectionStates0) ->
+ {ConnectionStates, EncodedMsg} =
+ lists:foldl(fun(Text, {CS0, Encoded}) ->
+ {Enc, CS1} =
+ encode_plain_text(Type, Text, CS0),
+ {CS1, [Enc | Encoded]}
+ end, {ConnectionStates0, []}, Data),
+ {lists:reverse(EncodedMsg), ConnectionStates}.
+
+%%====================================================================
+%% Decoding
+%%====================================================================
+
+%%--------------------------------------------------------------------
+-spec decode_cipher_text(#ssl_tls{}, ssl_record:connection_states()) ->
+ {#ssl_tls{}, ssl_record:connection_states()}| #alert{}.
+%%
+%% Description: Decode cipher text, use legacy type ssl_tls instead of tls_cipher_text
+%% in decoding context so that we can reuse the code from erlier versions.
+%%--------------------------------------------------------------------
+decode_cipher_text(#ssl_tls{type = ?OPAQUE_TYPE,
+ version = ?LEGACY_VERSION,
+ fragment = CipherFragment},
+ #{current_read :=
+ #{sequence_number := Seq,
+ cipher_state := #cipher_state{key = Key,
+ iv = IV,
+ tag_len = TagLen},
+ security_parameters :=
+ #security_parameters{
+ cipher_type = ?AEAD,
+ bulk_cipher_algorithm =
+ BulkCipherAlgo}
+ } = ReadState0} = ConnectionStates0) ->
+ case decipher_aead(CipherFragment, BulkCipherAlgo, Key, Seq, IV, TagLen) of
+ #alert{} = Alert ->
+ Alert;
+ PlainFragment ->
+ ConnectionStates =
+ ConnectionStates0#{current_read =>
+ ReadState0#{sequence_number => Seq + 1}},
+ {decode_inner_plaintext(PlainFragment), ConnectionStates}
+ end;
+
+%% RFC8446 - TLS 1.3
+%% 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.
+decode_cipher_text(#ssl_tls{type = ?CHANGE_CIPHER_SPEC,
+ version = ?LEGACY_VERSION,
+ fragment = <<1>>},
+ ConnectionStates0) ->
+ {#ssl_tls{type = ?CHANGE_CIPHER_SPEC,
+ version = {3,4}, %% Internally use real version
+ fragment = <<1>>}, ConnectionStates0};
+
+decode_cipher_text(#ssl_tls{type = Type,
+ version = ?LEGACY_VERSION,
+ fragment = CipherFragment},
+ #{current_read :=
+ #{security_parameters :=
+ #security_parameters{
+ cipher_suite = ?TLS_NULL_WITH_NULL_NULL}
+ }} = ConnnectionStates0) ->
+ {#ssl_tls{type = Type,
+ version = {3,4}, %% Internally use real version
+ fragment = CipherFragment}, ConnnectionStates0};
+decode_cipher_text(#ssl_tls{type = Type}, _) ->
+ %% Version mismatch is already asserted
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, {record_type_mismatch, Type}).
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+split_bin(Bin, ChunkSize) ->
+ split_bin(Bin, ChunkSize, []).
+split_bin(Bin, ChunkSize, _) ->
+ do_split_bin(Bin, ChunkSize, []).
+
+do_split_bin(<<>>, _, Acc) ->
+ lists:reverse(Acc);
+do_split_bin(Bin, ChunkSize, Acc) ->
+ case Bin of
+ <<Chunk:ChunkSize/binary, Rest/binary>> ->
+ do_split_bin(Rest, ChunkSize, [Chunk | Acc]);
+ _ ->
+ lists:reverse(Acc, [Bin])
+ end.
+
+inner_plaintext(Type, Data, Length) ->
+ #inner_plaintext{
+ content = Data,
+ type = Type,
+ zeros = zero_padding(Length)
+ }.
+zero_padding(Length)->
+ binary:copy(<<?BYTE(0)>>, Length).
+
+encode_plain_text(#inner_plaintext{
+ content = Data,
+ type = Type,
+ zeros = Zeros
+ }, #{cipher_state := #cipher_state{key= Key,
+ iv = IV,
+ tag_len = TagLen},
+ sequence_number := Seq,
+ security_parameters :=
+ #security_parameters{
+ cipher_type = ?AEAD,
+ bulk_cipher_algorithm = BulkCipherAlgo}
+ }) ->
+ PlainText = [Data, Type, Zeros],
+ Encoded = cipher_aead(PlainText, BulkCipherAlgo, Key, Seq, IV, TagLen),
+ #tls_cipher_text{opaque_type = 23, %% 23 (application_data) for outward compatibility
+ legacy_version = {3,3},
+ encoded_record = Encoded};
+encode_plain_text(#inner_plaintext{
+ content = Data,
+ type = Type
+ }, #{security_parameters :=
+ #security_parameters{
+ cipher_suite = ?TLS_NULL_WITH_NULL_NULL}
+ }) ->
+ %% RFC8446 - 5.1. Record Layer
+ %% 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},
+ encoded_record = Data};
+
+encode_plain_text(_, CS) ->
+ exit({cs, CS}).
+
+additional_data(Length) ->
+ <<?BYTE(?OPAQUE_TYPE), ?BYTE(3), ?BYTE(3),?UINT16(Length)>>.
+
+%% The per-record nonce for the AEAD construction is formed as
+%% follows:
+%%
+%% 1. The 64-bit record sequence number is encoded in network byte
+%% order and padded to the left with zeros to iv_length.
+%%
+%% 2. The padded sequence number is XORed with either the static
+%% client_write_iv or server_write_iv (depending on the role).
+%%
+%% The resulting quantity (of length iv_length) is used as the
+%% per-record nonce.
+nonce(Seq, IV) ->
+ Padding = binary:copy(<<0>>, byte_size(IV) - 8),
+ crypto:exor(<<Padding/binary,?UINT64(Seq)>>, IV).
+
+cipher_aead(Fragment, BulkCipherAlgo, Key, Seq, IV, TagLen) ->
+ AAD = additional_data(erlang:iolist_size(Fragment) + TagLen),
+ Nonce = nonce(Seq, IV),
+ {Content, CipherTag} =
+ ssl_cipher:aead_encrypt(BulkCipherAlgo, Key, Nonce, Fragment, AAD),
+ <<Content/binary, CipherTag/binary>>.
+
+encode_tls_cipher_text(#tls_cipher_text{opaque_type = Type,
+ legacy_version = {MajVer, MinVer},
+ encoded_record = Encoded}, #{sequence_number := Seq} = Write) ->
+ Length = erlang:iolist_size(Encoded),
+ {[<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Length)>>, Encoded],
+ Write#{sequence_number => Seq +1}}.
+
+decipher_aead(CipherFragment, BulkCipherAlgo, Key, Seq, IV, TagLen) ->
+ try
+ AAD = additional_data(erlang:iolist_size(CipherFragment)),
+ Nonce = nonce(Seq, IV),
+ {CipherText, CipherTag} = aead_ciphertext_split(CipherFragment, TagLen),
+ case ssl_cipher:aead_decrypt(BulkCipherAlgo, Key, Nonce, CipherText, CipherTag, AAD) of
+ Content when is_binary(Content) ->
+ Content;
+ _ ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed)
+ end
+ catch
+ _:_ ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed)
+ end.
+
+
+aead_ciphertext_split(CipherTextFragment, TagLen)
+ when is_binary(CipherTextFragment) ->
+ CipherLen = erlang:byte_size(CipherTextFragment) - TagLen,
+ <<CipherText:CipherLen/bytes, CipherTag:TagLen/bytes>> = CipherTextFragment,
+ {CipherText, CipherTag};
+aead_ciphertext_split(CipherTextFragment, TagLen)
+ when is_list(CipherTextFragment) ->
+ CipherLen = erlang:iolist_size(CipherTextFragment) - TagLen,
+ <<CipherText:CipherLen/bytes, CipherTag:TagLen/bytes>> =
+ erlang:iolist_to_binary(CipherTextFragment),
+ {CipherText, CipherTag}.
+
+decode_inner_plaintext(PlainText) ->
+ case binary:last(PlainText) of
+ 0 ->
+ decode_inner_plaintext(init_binary(PlainText));
+ Type when Type =:= ?APPLICATION_DATA orelse
+ Type =:= ?HANDSHAKE orelse
+ Type =:= ?ALERT ->
+ #ssl_tls{type = Type,
+ version = {3,4}, %% Internally use real version
+ fragment = init_binary(PlainText)};
+ _Else ->
+ ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, empty_alert)
+ end.
+
+init_binary(B) ->
+ {Init, _} =
+ split_binary(B, byte_size(B) - 1),
+ Init.
diff --git a/lib/ssl/src/tls_record_1_3.hrl b/lib/ssl/src/tls_record_1_3.hrl
new file mode 100644
index 0000000000..273427a34e
--- /dev/null
+++ b/lib/ssl/src/tls_record_1_3.hrl
@@ -0,0 +1,58 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2018. All 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: Record and constant defenitions for the TLS-1.3-record protocol
+%% see RFC 8446 not present in earlier versions
+%%----------------------------------------------------------------------
+
+-ifndef(tls_record_1_3).
+-define(tls_record_1_3, true).
+
+%% enum {
+%% invalid(0),
+%% %% defined in ssl_record.hrl
+%% change_cipher_spec(20),
+%% alert(21),
+%% handshake(22),
+%% application_data(23),
+%% heartbeat(24), /* RFC 6520 */
+%% (255)
+%% } ContentType;
+
+-define(INVALID, 0).
+-define(LEGACY_VERSION, {3,3}).
+-define(OPAQUE_TYPE, 23).
+
+-record(inner_plaintext, {
+ content, %% data
+ type, %% Contentype
+ zeros %% padding "uint8 zeros[length_of_padding]"
+ }).
+-record(tls_cipher_text, { %% Equivalent of encrypted version of #ssl_tls from previous versions
+ %% 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,
+ encoded_record
+ }).
+
+-endif. % -ifdef(tls_record_1_3).
diff --git a/lib/ssl/src/tls_sender.erl b/lib/ssl/src/tls_sender.erl
index 11fcc6def0..d0604565e3 100644
--- a/lib/ssl/src/tls_sender.erl
+++ b/lib/ssl/src/tls_sender.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2019. All 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,19 +38,24 @@
-define(SERVER, ?MODULE).
--record(data, {connection_pid,
- connection_states = #{},
- role,
- socket,
- socket_options,
- tracker,
- protocol_cb,
- transport_cb,
- negotiated_version,
- renegotiate_at,
- connection_monitor,
- dist_handle
- }).
+-record(static,
+ {connection_pid,
+ role,
+ socket,
+ socket_options,
+ tracker,
+ transport_cb,
+ negotiated_version,
+ renegotiate_at,
+ connection_monitor,
+ dist_handle,
+ log_level
+ }).
+
+-record(data,
+ {static = #static{},
+ connection_states = #{}
+ }).
%%%===================================================================
%%% API
@@ -171,6 +176,10 @@ dist_tls_socket(Pid) ->
callback_mode() ->
state_functions.
+
+-define(HANDLE_COMMON,
+ ?FUNCTION_NAME(Type, Msg, StateData) ->
+ handle_common(Type, Msg, StateData)).
%%--------------------------------------------------------------------
-spec init(Args :: term()) ->
gen_statem:init_result(atom()).
@@ -192,39 +201,37 @@ init({call, From}, {Pid, #{current_write := WriteState,
socket := Socket,
socket_options := SockOpts,
tracker := Tracker,
- protocol_cb := Connection,
transport_cb := Transport,
negotiated_version := Version,
- renegotiate_at := RenegotiateAt}},
- #data{connection_states = ConnectionStates} = StateData0) ->
+ renegotiate_at := RenegotiateAt,
+ log_level := LogLevel}},
+ #data{connection_states = ConnectionStates, static = Static0} = StateData0) ->
Monitor = erlang:monitor(process, Pid),
StateData =
- StateData0#data{connection_pid = Pid,
- connection_monitor = Monitor,
- connection_states =
- ConnectionStates#{current_write => WriteState},
- role = Role,
- socket = Socket,
- socket_options = SockOpts,
- tracker = Tracker,
- protocol_cb = Connection,
- transport_cb = Transport,
- negotiated_version = Version,
- renegotiate_at = RenegotiateAt},
+ StateData0#data{connection_states = ConnectionStates#{current_write => WriteState},
+ static = Static0#static{connection_pid = Pid,
+ connection_monitor = Monitor,
+ role = Role,
+ socket = Socket,
+ socket_options = SockOpts,
+ tracker = Tracker,
+ transport_cb = Transport,
+ negotiated_version = Version,
+ renegotiate_at = RenegotiateAt,
+ log_level = LogLevel}},
{next_state, handshake, StateData, [{reply, From, ok}]};
-init(info, Msg, StateData) ->
- handle_info(Msg, ?FUNCTION_NAME, StateData).
+init(_, _, _) ->
+ %% Just in case anything else sneeks through
+ {keep_state_and_data, [postpone]}.
+
%%--------------------------------------------------------------------
-spec connection(gen_statem:event_type(),
Msg :: term(),
StateData :: term()) ->
gen_statem:event_handler_result(atom()).
%%--------------------------------------------------------------------
-connection({call, From}, renegotiate,
- #data{connection_states = #{current_write := Write}} = StateData) ->
- {next_state, handshake, StateData, [{reply, From, {ok, Write}}]};
connection({call, From}, {application_data, AppData},
- #data{socket_options = #socket_options{packet = Packet}} =
+ #data{static = #static{socket_options = #socket_options{packet = Packet}}} =
StateData) ->
case encode_packet(Packet, AppData) of
{error, _} = Error ->
@@ -232,40 +239,40 @@ connection({call, From}, {application_data, AppData},
Data ->
send_application_data(Data, From, ?FUNCTION_NAME, StateData)
end;
-connection({call, From}, {set_opts, _} = Call, StateData) ->
- handle_call(From, Call, ?FUNCTION_NAME, StateData);
+connection({call, From}, {ack_alert, #alert{} = Alert}, StateData0) ->
+ StateData = send_tls_alert(Alert, StateData0),
+ {next_state, ?FUNCTION_NAME, StateData,
+ [{reply,From,ok}]};
+connection({call, From}, renegotiate,
+ #data{connection_states = #{current_write := Write}} = StateData) ->
+ {next_state, handshake, StateData, [{reply, From, {ok, Write}}]};
+connection({call, From}, downgrade, #data{connection_states =
+ #{current_write := Write}} = StateData) ->
+ {next_state, death_row, StateData, [{reply,From, {ok, Write}}]};
+connection({call, From}, {set_opts, Opts}, StateData) ->
+ handle_set_opts(From, Opts, StateData);
connection({call, From}, dist_get_tls_socket,
- #data{protocol_cb = Connection,
- transport_cb = Transport,
- socket = Socket,
- connection_pid = Pid,
- tracker = Tracker} = StateData) ->
- TLSSocket = Connection:socket([Pid, self()], Transport, Socket, Connection, Tracker),
+ #data{static = #static{transport_cb = Transport,
+ socket = Socket,
+ connection_pid = Pid,
+ tracker = Tracker}} = StateData) ->
+ TLSSocket = tls_connection:socket([Pid, self()], Transport, Socket, Tracker),
{next_state, ?FUNCTION_NAME, StateData, [{reply, From, {ok, TLSSocket}}]};
connection({call, From}, {dist_handshake_complete, _Node, DHandle},
- #data{connection_pid = Pid,
- socket_options = #socket_options{packet = Packet}} =
- StateData) ->
+ #data{static = #static{connection_pid = Pid} = Static} = StateData) ->
ok = erlang:dist_ctrl_input_handler(DHandle, Pid),
ok = ssl_connection:dist_handshake_complete(Pid, DHandle),
%% From now on we execute on normal priority
process_flag(priority, normal),
- {next_state, ?FUNCTION_NAME, StateData#data{dist_handle = DHandle},
- [{reply, From, ok}
- | case dist_data(DHandle, Packet) of
- [] ->
- [];
- Data ->
- [{next_event, internal,
- {application_packets,{self(),undefined},Data}}]
- end]};
-connection({call, From}, {ack_alert, #alert{} = Alert}, StateData0) ->
- StateData = send_tls_alert(Alert, StateData0),
- {next_state, ?FUNCTION_NAME, StateData,
- [{reply,From,ok}]};
-connection({call, From}, downgrade, #data{connection_states =
- #{current_write := Write}} = StateData) ->
- {next_state, death_row, StateData, [{reply,From, {ok, Write}}]};
+ {keep_state, StateData#data{static = Static#static{dist_handle = DHandle}},
+ [{reply,From,ok}|
+ case dist_data(DHandle) of
+ [] ->
+ [];
+ Data ->
+ [{next_event, internal,
+ {application_packets,{self(),undefined},erlang:iolist_to_iovec(Data)}}]
+ end]};
connection(internal, {application_packets, From, Data}, StateData) ->
send_application_data(Data, From, ?FUNCTION_NAME, StateData);
%%
@@ -273,29 +280,26 @@ connection(cast, #alert{} = Alert, StateData0) ->
StateData = send_tls_alert(Alert, StateData0),
{next_state, ?FUNCTION_NAME, StateData};
connection(cast, {new_write, WritesState, Version},
- #data{connection_states = ConnectionStates0} = StateData) ->
+ #data{connection_states = ConnectionStates, static = Static} = StateData) ->
{next_state, connection,
StateData#data{connection_states =
- ConnectionStates0#{current_write => WritesState},
- negotiated_version = Version}};
+ ConnectionStates#{current_write => WritesState},
+ static = Static#static{negotiated_version = Version}}};
%%
-connection(info, dist_data,
- #data{dist_handle = DHandle,
- socket_options = #socket_options{packet = Packet}} =
- StateData) ->
- {next_state, ?FUNCTION_NAME, StateData,
- case dist_data(DHandle, Packet) of
+connection(info, dist_data, #data{static = #static{dist_handle = DHandle}}) ->
+ {keep_state_and_data,
+ case dist_data(DHandle) of
[] ->
[];
Data ->
[{next_event, internal,
- {application_packets,{self(),undefined},Data}}]
+ {application_packets,{self(),undefined},erlang:iolist_to_iovec(Data)}}]
end};
connection(info, tick, StateData) ->
consume_ticks(),
- {next_state, ?FUNCTION_NAME, StateData,
- [{next_event, {call, {self(), undefined}},
- {application_data, <<>>}}]};
+ Data = [<<0:32>>], % encode_packet(4, <<>>)
+ From = {self(), undefined},
+ send_application_data(Data, From, ?FUNCTION_NAME, StateData);
connection(info, {send, From, Ref, Data}, _StateData) ->
%% This is for testing only!
%%
@@ -304,29 +308,37 @@ connection(info, {send, From, Ref, Data}, _StateData) ->
From ! {Ref, ok},
{keep_state_and_data,
[{next_event, {call, {self(), undefined}},
- {application_data, iolist_to_binary(Data)}}]};
-connection(info, Msg, StateData) ->
- handle_info(Msg, ?FUNCTION_NAME, StateData).
+ {application_data, erlang:iolist_to_iovec(Data)}}]};
+?HANDLE_COMMON.
+
%%--------------------------------------------------------------------
-spec handshake(gen_statem:event_type(),
Msg :: term(),
StateData :: term()) ->
gen_statem:event_handler_result(atom()).
%%--------------------------------------------------------------------
-handshake({call, From}, {set_opts, _} = Call, StateData) ->
- handle_call(From, Call, ?FUNCTION_NAME, StateData);
+handshake({call, From}, {set_opts, Opts}, StateData) ->
+ handle_set_opts(From, Opts, StateData);
handshake({call, _}, _, _) ->
+ %% Postpone all calls to the connection state
+ {keep_state_and_data, [postpone]};
+handshake(internal, {application_packets,_,_}, _) ->
{keep_state_and_data, [postpone]};
handshake(cast, {new_write, WritesState, Version},
- #data{connection_states = ConnectionStates0} = StateData) ->
+ #data{connection_states = ConnectionStates, static = Static} = StateData) ->
{next_state, connection,
- StateData#data{connection_states =
- ConnectionStates0#{current_write => WritesState},
- negotiated_version = Version}};
-handshake(internal, {application_packets,_,_}, _) ->
+ StateData#data{connection_states = ConnectionStates#{current_write => WritesState},
+ static = Static#static{negotiated_version = Version}}};
+handshake(info, dist_data, _) ->
{keep_state_and_data, [postpone]};
-handshake(info, Msg, StateData) ->
- handle_info(Msg, ?FUNCTION_NAME, StateData).
+handshake(info, tick, _) ->
+ %% Ignore - data is sent anyway during handshake
+ consume_ticks(),
+ keep_state_and_data;
+handshake(info, {send, _, _, _}, _) ->
+ %% Testing only, OTP distribution test suites...
+ {keep_state_and_data, [postpone]};
+?HANDLE_COMMON.
%%--------------------------------------------------------------------
-spec death_row(gen_statem:event_type(),
@@ -361,54 +373,76 @@ code_change(_OldVsn, State, Data, _Extra) ->
%%%===================================================================
%%% Internal functions
%%%===================================================================
-handle_call(From, {set_opts, Opts}, StateName, #data{socket_options = SockOpts} = StateData) ->
- {next_state, StateName, StateData#data{socket_options = set_opts(SockOpts, Opts)}, [{reply, From, ok}]}.
-
-handle_info({'DOWN', Monitor, _, _, Reason}, _,
- #data{connection_monitor = Monitor,
- dist_handle = Handle} = StateData) when Handle =/= undefined->
- {next_state, death_row, StateData, [{state_timeout, 5000, Reason}]};
-handle_info({'DOWN', Monitor, _, _, _}, _,
- #data{connection_monitor = Monitor} = StateData) ->
+
+handle_set_opts(
+ From, Opts, #data{static = #static{socket_options = SockOpts} = Static} = StateData) ->
+ {keep_state, StateData#data{static = Static#static{socket_options = set_opts(SockOpts, Opts)}},
+ [{reply, From, ok}]}.
+
+handle_common(
+ {call, From}, {set_opts, Opts},
+ #data{static = #static{socket_options = SockOpts} = Static} = StateData) ->
+ {keep_state, StateData#data{static = Static#static{socket_options = set_opts(SockOpts, Opts)}},
+ [{reply, From, ok}]};
+handle_common(
+ info, {'DOWN', Monitor, _, _, Reason},
+ #data{static = #static{connection_monitor = Monitor,
+ dist_handle = Handle}} = StateData) when Handle =/= undefined ->
+ {next_state, death_row, StateData,
+ [{state_timeout, 5000, Reason}]};
+handle_common(
+ info, {'DOWN', Monitor, _, _, _},
+ #data{static = #static{connection_monitor = Monitor}} = StateData) ->
{stop, normal, StateData};
-handle_info(_,_,_) ->
+handle_common(info, Msg, _) ->
+ Report =
+ io_lib:format("TLS sender: Got unexpected info: ~p ~n", [Msg]),
+ error_logger:info_report(Report),
+ keep_state_and_data;
+handle_common(Type, Msg, _) ->
+ Report =
+ io_lib:format(
+ "TLS sender: Got unexpected event: ~p ~n", [{Type,Msg}]),
+ error_logger:error_report(Report),
keep_state_and_data.
-send_tls_alert(Alert, #data{negotiated_version = Version,
- socket = Socket,
- protocol_cb = Connection,
- transport_cb = Transport,
- connection_states = ConnectionStates0} = StateData0) ->
+send_tls_alert(#alert{} = Alert,
+ #data{static = #static{negotiated_version = Version,
+ socket = Socket,
+ transport_cb = Transport,
+ log_level = LogLevel},
+ connection_states = ConnectionStates0} = StateData0) ->
{BinMsg, ConnectionStates} =
- Connection:encode_alert(Alert, Version, ConnectionStates0),
- Connection:send(Transport, Socket, BinMsg),
+ tls_record:encode_alert_record(Alert, Version, ConnectionStates0),
+ tls_socket:send(Transport, Socket, BinMsg),
+ ssl_logger:debug(LogLevel, outbound, 'record', BinMsg),
StateData0#data{connection_states = ConnectionStates}.
send_application_data(Data, From, StateName,
- #data{connection_pid = Pid,
- socket = Socket,
- dist_handle = DistHandle,
- negotiated_version = Version,
- protocol_cb = Connection,
- transport_cb = Transport,
- connection_states = ConnectionStates0,
- renegotiate_at = RenegotiateAt} = StateData0) ->
+ #data{static = #static{connection_pid = Pid,
+ socket = Socket,
+ dist_handle = DistHandle,
+ negotiated_version = Version,
+ transport_cb = Transport,
+ renegotiate_at = RenegotiateAt,
+ log_level = LogLevel},
+ connection_states = ConnectionStates0} = StateData0) ->
case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of
true ->
ssl_connection:internal_renegotiation(Pid, ConnectionStates0),
{next_state, handshake, StateData0,
[{next_event, internal, {application_packets, From, Data}}]};
false ->
- {Msgs, ConnectionStates} =
- Connection:encode_data(
- iolist_to_binary(Data), Version, ConnectionStates0),
+ {Msgs, ConnectionStates} = tls_record:encode_data(Data, Version, ConnectionStates0),
StateData = StateData0#data{connection_states = ConnectionStates},
- case Connection:send(Transport, Socket, Msgs) of
+ case tls_socket:send(Transport, Socket, Msgs) of
ok when DistHandle =/= undefined ->
+ ssl_logger:debug(LogLevel, outbound, 'record', Msgs),
{next_state, StateName, StateData, []};
Reason when DistHandle =/= undefined ->
{next_state, death_row, StateData, [{state_timeout, 5000, Reason}]};
ok ->
+ ssl_logger:debug(LogLevel, outbound, 'record', Msgs),
{next_state, StateName, StateData, [{reply, From, ok}]};
Result ->
{next_state, StateName, StateData, [{reply, From, Result}]}
@@ -419,9 +453,9 @@ send_application_data(Data, From, StateName,
encode_packet(Packet, Data) ->
Len = iolist_size(Data),
case Packet of
- 1 when Len < (1 bsl 8) -> [<<Len:8>>,Data];
- 2 when Len < (1 bsl 16) -> [<<Len:16>>,Data];
- 4 when Len < (1 bsl 32) -> [<<Len:32>>,Data];
+ 1 when Len < (1 bsl 8) -> [<<Len:8>>|Data];
+ 2 when Len < (1 bsl 16) -> [<<Len:16>>|Data];
+ 4 when Len < (1 bsl 32) -> [<<Len:32>>|Data];
N when N =:= 1; N =:= 2; N =:= 4 ->
{error,
{badarg, {packet_to_large, Len, (1 bsl (Packet bsl 3)) - 1}}};
@@ -458,22 +492,30 @@ call(FsmPid, Event) ->
{error, closed}
end.
-%%---------------Erlang distribution --------------------------------------
+%%-------------- Erlang distribution helpers ------------------------------
-dist_data(DHandle, Packet) ->
+dist_data(DHandle) ->
case erlang:dist_ctrl_get_data(DHandle) of
none ->
erlang:dist_ctrl_get_data_notification(DHandle),
[];
- Data ->
- %% This is encode_packet(4, Data) without Len check
- %% since the emulator will always deliver a Data
- %% smaller than 4 GB, and the distribution will
- %% therefore always have to use {packet,4}
+ %% This is encode_packet(4, Data) without Len check
+ %% since the emulator will always deliver a Data
+ %% smaller than 4 GB, and the distribution will
+ %% therefore always have to use {packet,4}
+ Data when is_binary(Data) ->
+ Len = byte_size(Data),
+ [[<<Len:32>>,Data]|dist_data(DHandle)];
+ [BA,BB] = Data ->
+ Len = byte_size(BA) + byte_size(BB),
+ [[<<Len:32>>|Data]|dist_data(DHandle)];
+ Data when is_list(Data) ->
Len = iolist_size(Data),
- [<<Len:32>>,Data|dist_data(DHandle, Packet)]
+ [[<<Len:32>>|Data]|dist_data(DHandle)]
end.
+
+%% Empty the inbox from distribution ticks - do not let them accumulate
consume_ticks() ->
receive tick ->
consume_ticks()
diff --git a/lib/ssl/src/tls_socket.erl b/lib/ssl/src/tls_socket.erl
index a391bc53de..c3c41d3e12 100644
--- a/lib/ssl/src/tls_socket.erl
+++ b/lib/ssl/src/tls_socket.erl
@@ -32,6 +32,7 @@
emulated_socket_options/2, get_emulated_opts/1,
set_emulated_opts/2, get_all_opts/1, handle_call/3, handle_cast/2,
handle_info/2, code_change/3]).
+-export([update_active_n/2]).
-record(state, {
emulated_opts,
@@ -51,7 +52,9 @@ listen(Transport, Port, #config{transport_info = {Transport, _, _, _},
case Transport:listen(Port, Options ++ internal_inet_values()) of
{ok, ListenSocket} ->
{ok, Tracker} = inherit_tracker(ListenSocket, EmOpts, SslOpts),
- {ok, #sslsocket{pid = {ListenSocket, Config#config{emulated = Tracker}}}};
+ Socket = #sslsocket{pid = {ListenSocket, Config#config{emulated = Tracker}}},
+ check_active_n(EmOpts, Socket),
+ {ok, Socket};
Err = {error, _} ->
Err
end.
@@ -117,14 +120,16 @@ socket(Pids, Transport, Socket, ConnectionCb, Tracker) ->
#sslsocket{pid = Pids,
%% "The name "fd" is keept for backwards compatibility
fd = {Transport, Socket, ConnectionCb, Tracker}}.
-setopts(gen_tcp, #sslsocket{pid = {ListenSocket, #config{emulated = Tracker}}}, Options) ->
+setopts(gen_tcp, Socket = #sslsocket{pid = {ListenSocket, #config{emulated = Tracker}}}, Options) ->
{SockOpts, EmulatedOpts} = split_options(Options),
ok = set_emulated_opts(Tracker, EmulatedOpts),
+ check_active_n(EmulatedOpts, Socket),
inet:setopts(ListenSocket, SockOpts);
-setopts(_, #sslsocket{pid = {ListenSocket, #config{transport_info = {Transport,_,_,_},
+setopts(_, Socket = #sslsocket{pid = {ListenSocket, #config{transport_info = {Transport,_,_,_},
emulated = Tracker}}}, Options) ->
{SockOpts, EmulatedOpts} = split_options(Options),
ok = set_emulated_opts(Tracker, EmulatedOpts),
+ check_active_n(EmulatedOpts, Socket),
Transport:setopts(ListenSocket, SockOpts);
%%% Following clauses will not be called for emulated options, they are handled in the connection process
setopts(gen_tcp, Socket, Options) ->
@@ -132,6 +137,31 @@ setopts(gen_tcp, Socket, Options) ->
setopts(Transport, Socket, Options) ->
Transport:setopts(Socket, Options).
+check_active_n(EmulatedOpts, Socket = #sslsocket{pid = {_, #config{emulated = Tracker}}}) ->
+ %% We check the resulting options to send an ssl_passive message if necessary.
+ case proplists:lookup(active, EmulatedOpts) of
+ %% The provided value is out of bound.
+ {_, N} when is_integer(N), N < -32768 ->
+ throw(einval);
+ {_, N} when is_integer(N), N > 32767 ->
+ throw(einval);
+ {_, N} when is_integer(N) ->
+ case get_emulated_opts(Tracker, [active]) of
+ [{_, false}] ->
+ self() ! {ssl_passive, Socket},
+ ok;
+ %% The result of the addition is out of bound.
+ [{_, A}] when is_integer(A), A < -32768 ->
+ throw(einval);
+ [{_, A}] when is_integer(A), A > 32767 ->
+ throw(einval);
+ _ ->
+ ok
+ end;
+ _ ->
+ ok
+ end.
+
getopts(gen_tcp, #sslsocket{pid = {ListenSocket, #config{emulated = Tracker}}}, Options) ->
{SockOptNames, EmulatedOptNames} = split_options(Options),
EmulatedOpts = get_emulated_opts(Tracker, EmulatedOptNames),
@@ -209,7 +239,7 @@ start_link(Port, SockOpts, SslOpts) ->
init([Port, Opts, SslOpts]) ->
process_flag(trap_exit, true),
true = link(Port),
- {ok, #state{emulated_opts = Opts, port = Port, ssl_opts = SslOpts}}.
+ {ok, #state{emulated_opts = do_set_emulated_opts(Opts, []), port = Port, ssl_opts = SslOpts}}.
%%--------------------------------------------------------------------
-spec handle_call(msg(), from(), #state{}) -> {reply, reply(), #state{}}.
@@ -304,9 +334,24 @@ split_options([Name | Opts], Emu, SocketOptNames, EmuOptNames) ->
do_set_emulated_opts([], Opts) ->
Opts;
+do_set_emulated_opts([{active, N0} | Rest], Opts) when is_integer(N0) ->
+ N = update_active_n(N0, proplists:get_value(active, Opts, false)),
+ do_set_emulated_opts(Rest, [{active, N} | proplists:delete(active, Opts)]);
do_set_emulated_opts([{Name,_} = Opt | Rest], Opts) ->
do_set_emulated_opts(Rest, [Opt | proplists:delete(Name, Opts)]).
+update_active_n(New, Current) ->
+ if
+ is_integer(Current), New + Current =< 0 ->
+ false;
+ is_integer(Current) ->
+ New + Current;
+ New =< 0 ->
+ false;
+ true ->
+ New
+ end.
+
get_socket_opts(_, [], _) ->
[];
get_socket_opts(ListenSocket, SockOptNames, Cb) ->
@@ -366,6 +411,9 @@ validate_inet_option(header, Value)
when not is_integer(Value) ->
throw({error, {options, {header,Value}}});
validate_inet_option(active, Value)
+ when Value >= -32768, Value =< 32767 ->
+ ok;
+validate_inet_option(active, Value)
when Value =/= true, Value =/= false, Value =/= once ->
throw({error, {options, {active,Value}}});
validate_inet_option(_, _) ->
diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl
index 1bfd9a8b6d..f103f3218b 100644
--- a/lib/ssl/src/tls_v1.erl
+++ b/lib/ssl/src/tls_v1.erl
@@ -32,7 +32,19 @@
-export([master_secret/4, finished/5, certificate_verify/3, mac_hash/7, hmac_hash/3,
setup_keys/8, suites/1, prf/5,
ecc_curves/1, ecc_curves/2, oid_to_enum/1, enum_to_oid/1,
- default_signature_algs/1, signature_algs/2]).
+ default_signature_algs/1, signature_algs/2,
+ default_signature_schemes/1, signature_schemes/2,
+ 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, hkdf_expand/4,
+ key_schedule/3, key_schedule/4, create_info/3,
+ external_binder_key/2, resumption_binder_key/2,
+ client_early_traffic_secret/3, early_exporter_master_secret/3,
+ client_handshake_traffic_secret/3, server_handshake_traffic_secret/3,
+ client_application_traffic_secret_0/3, server_application_traffic_secret_0/3,
+ exporter_master_secret/3, resumption_master_secret/3,
+ update_traffic_secret/2, calculate_traffic_keys/3,
+ transcript_hash/2, finished_key/2, finished_verify_data/3]).
-type named_curve() :: sect571r1 | sect571k1 | secp521r1 | brainpoolP512r1 |
sect409k1 | sect409r1 | brainpoolP384r1 | secp384r1 |
@@ -41,12 +53,68 @@
sect193r1 | sect193r2 | secp192k1 | secp192r1 | sect163k1 |
sect163r1 | sect163r2 | secp160k1 | secp160r1 | secp160r2.
-type curves() :: [named_curve()].
--export_type([curves/0, named_curve/0]).
+-type group() :: secp256r1 | secp384r1 | secp521r1 | ffdhe2048 |
+ ffdhe3072 | ffdhe4096 | ffdhe6144 | ffdhe8192.
+-type supported_groups() :: [group()].
+-export_type([curves/0, named_curve/0, group/0, supported_groups/0]).
%%====================================================================
%% Internal application API
%%====================================================================
+%% TLS 1.3 ---------------------------------------------------
+-spec derive_secret(Secret::binary(), Label::binary(),
+ Messages::iodata(), Algo::ssl:hash()) -> Key::binary().
+derive_secret(Secret, Label, Messages, Algo) ->
+ Hash = crypto:hash(mac_algo(Algo), Messages),
+ hkdf_expand_label(Secret, Label,
+ Hash, ssl_cipher:hash_size(Algo), Algo).
+
+-spec hkdf_expand_label(Secret::binary(), Label0::binary(),
+ Context::binary(), Length::integer(),
+ Algo::ssl:hash()) -> KeyingMaterial::binary().
+hkdf_expand_label(Secret, Label0, Context, Length, Algo) ->
+ HkdfLabel = create_info(Label0, Context, Length),
+ hkdf_expand(Secret, HkdfLabel, Length, Algo).
+
+%% Create info parameter for HKDF-Expand:
+%% HKDF-Expand(PRK, info, L) -> OKM
+create_info(Label0, Context0, Length) ->
+ %% struct {
+ %% uint16 length = Length;
+ %% opaque label<7..255> = "tls13 " + Label;
+ %% opaque context<0..255> = Context;
+ %% } HkdfLabel;
+ Label1 = << <<"tls13 ">>/binary, Label0/binary>>,
+ LabelLen = size(Label1),
+ Label = <<?BYTE(LabelLen), Label1/binary>>,
+ ContextLen = size(Context0),
+ Context = <<?BYTE(ContextLen),Context0/binary>>,
+ Content = <<Label/binary, Context/binary>>,
+ <<?UINT16(Length), Content/binary>>.
+
+-spec hkdf_extract(MacAlg::ssl:hash(), Salt::binary(),
+ KeyingMaterial::binary()) -> PseudoRandKey::binary().
+
+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) ->
+ Iterations = erlang:ceil(Length / ssl_cipher:hash_size(Algo)),
+ hkdf_expand(Algo, PseudoRandKey, ContextInfo, Length, 1, Iterations, <<>>, <<>>).
+
+
+-spec transcript_hash(Messages::iodata(), Algo::ssl:hash()) -> Hash::binary().
+
+transcript_hash(Messages, Algo) ->
+ crypto:hash(mac_algo(Algo), Messages).
+%% TLS 1.3 ---------------------------------------------------
+
+%% TLS 1.0 -1.2 ---------------------------------------------------
-spec master_secret(integer(), binary(), binary(), binary()) -> binary().
master_secret(PrfAlgo, PreMasterSecret, ClientRandom, ServerRandom) ->
@@ -56,9 +124,10 @@ master_secret(PrfAlgo, PreMasterSecret, ClientRandom, ServerRandom) ->
prf(PrfAlgo, PreMasterSecret, <<"master secret">>,
[ClientRandom, ServerRandom], 48).
+%% TLS 1.0 -1.2 ---------------------------------------------------
-spec finished(client | server, integer(), integer(), binary(), [binary()]) -> binary().
-
+%% TLS 1.0 -1.1 ---------------------------------------------------
finished(Role, Version, PrfAlgo, MasterSecret, Handshake)
when Version == 1; Version == 2; PrfAlgo == ?MD5SHA ->
%% RFC 2246 & 4346 - 7.4.9. Finished
@@ -72,7 +141,9 @@ finished(Role, Version, PrfAlgo, MasterSecret, Handshake)
MD5 = crypto:hash(md5, Handshake),
SHA = crypto:hash(sha, Handshake),
prf(?MD5SHA, MasterSecret, finished_label(Role), [MD5, SHA], 12);
+%% TLS 1.0 -1.1 ---------------------------------------------------
+%% TLS 1.2 ---------------------------------------------------
finished(Role, Version, PrfAlgo, MasterSecret, Handshake)
when Version == 3 ->
%% RFC 5246 - 7.4.9. Finished
@@ -84,21 +155,28 @@ finished(Role, Version, PrfAlgo, MasterSecret, Handshake)
%% PRF(master_secret, finished_label, Hash(handshake_messages)) [0..11];
Hash = crypto:hash(mac_algo(PrfAlgo), Handshake),
prf(PrfAlgo, MasterSecret, finished_label(Role), Hash, 12).
+%% TLS 1.2 ---------------------------------------------------
+
+%% TODO 1.3 finished
-spec certificate_verify(md5sha | sha, integer(), [binary()]) -> binary().
+%% TLS 1.0 -1.1 ---------------------------------------------------
certificate_verify(md5sha, _Version, Handshake) ->
MD5 = crypto:hash(md5, Handshake),
SHA = crypto:hash(sha, Handshake),
<<MD5/binary, SHA/binary>>;
+%% TLS 1.0 -1.1 ---------------------------------------------------
+%% TLS 1.2 ---------------------------------------------------
certificate_verify(HashAlgo, _Version, Handshake) ->
crypto:hash(HashAlgo, Handshake).
+%% TLS 1.2 ---------------------------------------------------
-spec setup_keys(integer(), 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 ->
@@ -123,8 +201,9 @@ setup_keys(Version, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize
ClientIV:IVSize/binary, ServerIV:IVSize/binary>> = KeyBlock,
{ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey,
ServerWriteKey, ClientIV, ServerIV};
+%% TLS v1.0 ---------------------------------------------------
-%% TLS v1.1
+%% TLS v1.1 ---------------------------------------------------
setup_keys(Version, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
KeyMatLen, IVSize)
when Version == 2 ->
@@ -150,11 +229,12 @@ setup_keys(Version, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize
ClientIV:IVSize/binary, ServerIV:IVSize/binary>> = KeyBlock,
{ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey,
ServerWriteKey, ClientIV, ServerIV};
+%% TLS v1.1 ---------------------------------------------------
-%% TLS v1.2
+%% TLS v1.2 ---------------------------------------------------
setup_keys(Version, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
KeyMatLen, IVSize)
- when Version == 3 ->
+ when Version == 3; Version == 4 ->
%% RFC 5246 - 6.3. Key calculation
%% key_block = PRF(SecurityParameters.master_secret,
%% "key expansion",
@@ -176,8 +256,177 @@ setup_keys(Version, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
ClientIV:IVSize/binary, ServerIV:IVSize/binary>> = KeyBlock,
{ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey,
ServerWriteKey, ClientIV, ServerIV}.
+%% TLS v1.2 ---------------------------------------------------
--spec mac_hash(integer(), binary(), integer(), integer(), tls_record:tls_version(),
+%% TLS v1.3 ---------------------------------------------------
+%% RFC 8446 - 7.1. Key Schedule
+%%
+%% 0
+%% |
+%% v
+%% PSK -> HKDF-Extract = Early Secret
+%% |
+%% +-----> Derive-Secret(., "ext binder" | "res binder", "")
+%% | = binder_key
+%% |
+%% +-----> Derive-Secret(., "c e traffic", ClientHello)
+%% | = client_early_traffic_secret
+%% |
+%% +-----> Derive-Secret(., "e exp master", ClientHello)
+%% | = early_exporter_master_secret
+%% v
+%% Derive-Secret(., "derived", "")
+%% |
+%% v
+%% (EC)DHE -> HKDF-Extract = Handshake Secret
+%% |
+%% +-----> Derive-Secret(., "c hs traffic",
+%% | ClientHello...ServerHello)
+%% | = client_handshake_traffic_secret
+%% |
+%% +-----> Derive-Secret(., "s hs traffic",
+%% | ClientHello...ServerHello)
+%% | = server_handshake_traffic_secret
+%% v
+%% Derive-Secret(., "derived", "")
+%% |
+%% v
+%% 0 -> HKDF-Extract = Master Secret
+%% |
+%% +-----> Derive-Secret(., "c ap traffic",
+%% | ClientHello...server Finished)
+%% | = client_application_traffic_secret_0
+%% |
+%% +-----> Derive-Secret(., "s ap traffic",
+%% | ClientHello...server Finished)
+%% | = server_application_traffic_secret_0
+%% |
+%% +-----> Derive-Secret(., "exp master",
+%% | ClientHello...server Finished)
+%% | = exporter_master_secret
+%% |
+%% +-----> Derive-Secret(., "res master",
+%% ClientHello...client Finished)
+%% = resumption_master_secret
+-spec key_schedule(early_secret | handshake_secret | master_secret,
+ atom(), {psk | early_secret | handshake_secret, binary()}) ->
+ {early_secret | handshake_secret | master_secret, binary()}.
+
+key_schedule(early_secret, Algo, {psk, PSK}) ->
+ Len = ssl_cipher:hash_size(Algo),
+ Salt = binary:copy(<<?BYTE(0)>>, Len),
+ {early_secret, hkdf_extract(Algo, Salt, PSK)};
+key_schedule(master_secret, Algo, {handshake_secret, Secret}) ->
+ Len = ssl_cipher:hash_size(Algo),
+ IKM = binary:copy(<<?BYTE(0)>>, Len),
+ Salt = derive_secret(Secret, <<"derived">>, <<>>, Algo),
+ {master_secret, hkdf_extract(Algo, Salt, IKM)}.
+%%
+key_schedule(handshake_secret, Algo, IKM, {early_secret, Secret}) ->
+ Salt = derive_secret(Secret, <<"derived">>, <<>>, Algo),
+ {handshake_secret, hkdf_extract(Algo, Salt, IKM)}.
+
+-spec external_binder_key(atom(), {early_secret, binary()}) -> binary().
+external_binder_key(Algo, {early_secret, Secret}) ->
+ derive_secret(Secret, <<"ext binder">>, <<>>, Algo).
+
+-spec resumption_binder_key(atom(), {early_secret, binary()}) -> binary().
+resumption_binder_key(Algo, {early_secret, Secret}) ->
+ derive_secret(Secret, <<"res binder">>, <<>>, Algo).
+
+-spec client_early_traffic_secret(atom(), {early_secret, binary()}, iodata()) -> binary().
+%% M = ClientHello
+client_early_traffic_secret(Algo, {early_secret, Secret}, M) ->
+ derive_secret(Secret, <<"c e traffic">>, M, Algo).
+
+-spec early_exporter_master_secret(atom(), {early_secret, binary()}, iodata()) -> binary().
+%% M = ClientHello
+early_exporter_master_secret(Algo, {early_secret, Secret}, M) ->
+ derive_secret(Secret, <<"e exp master">>, M, Algo).
+
+-spec client_handshake_traffic_secret(atom(), {handshake_secret, binary()}, iodata()) -> binary().
+%% M = ClientHello...ServerHello
+client_handshake_traffic_secret(Algo, {handshake_secret, Secret}, M) ->
+ derive_secret(Secret, <<"c hs traffic">>, M, Algo).
+
+-spec server_handshake_traffic_secret(atom(), {handshake_secret, binary()}, iodata()) -> binary().
+%% M = ClientHello...ServerHello
+server_handshake_traffic_secret(Algo, {handshake_secret, Secret}, M) ->
+ derive_secret(Secret, <<"s hs traffic">>, M, Algo).
+
+-spec client_application_traffic_secret_0(atom(), {master_secret, binary()}, iodata()) -> binary().
+%% M = ClientHello...server Finished
+client_application_traffic_secret_0(Algo, {master_secret, Secret}, M) ->
+ derive_secret(Secret, <<"c ap traffic">>, M, Algo).
+
+-spec server_application_traffic_secret_0(atom(), {master_secret, binary()}, iodata()) -> binary().
+%% M = ClientHello...server Finished
+server_application_traffic_secret_0(Algo, {master_secret, Secret}, M) ->
+ derive_secret(Secret, <<"s ap traffic">>, M, Algo).
+
+-spec exporter_master_secret(atom(), {master_secret, binary()}, iodata()) -> binary().
+%% M = ClientHello...server Finished
+exporter_master_secret(Algo, {master_secret, Secret}, M) ->
+ derive_secret(Secret, <<"exp master">>, M, Algo).
+
+-spec resumption_master_secret(atom(), {master_secret, binary()}, iodata()) -> binary().
+%% M = ClientHello...client Finished
+resumption_master_secret(Algo, {master_secret, Secret}, M) ->
+ derive_secret(Secret, <<"res master">>, M, Algo).
+
+-spec finished_key(binary(), atom()) -> binary().
+finished_key(BaseKey, Algo) ->
+ %% finished_key =
+ %% HKDF-Expand-Label(BaseKey, "finished", "", Hash.length)
+ ssl_cipher:hash_size(Algo),
+ hkdf_expand_label(BaseKey, <<"finished">>, <<>>, ssl_cipher:hash_size(Algo), Algo).
+
+-spec finished_verify_data(binary(), atom(), iodata()) -> binary().
+finished_verify_data(FinishedKey, HKDFAlgo, Messages) ->
+ %% The verify_data value is computed as follows:
+ %%
+ %% verify_data =
+ %% HMAC(finished_key,
+ %% Transcript-Hash(Handshake Context,
+ %% Certificate*, CertificateVerify*))
+ Context = lists:reverse(Messages),
+ THash = tls_v1:transcript_hash(Context, HKDFAlgo),
+ tls_v1:hmac_hash(HKDFAlgo, FinishedKey, THash).
+
+%% The next-generation application_traffic_secret is computed as:
+%%
+%% application_traffic_secret_N+1 =
+%% HKDF-Expand-Label(application_traffic_secret_N,
+%% "traffic upd", "", Hash.length)
+-spec update_traffic_secret(atom(), binary()) -> binary().
+update_traffic_secret(Algo, Secret) ->
+ hkdf_expand_label(Secret, <<"traffic upd">>, <<>>, ssl_cipher:hash_size(Algo), Algo).
+
+%% The traffic keying material is generated from the following input
+%% values:
+%%
+%% - A secret value
+%%
+%% - A purpose value indicating the specific value being generated
+%%
+%% - The length of the key being generated
+%%
+%% The traffic keying material is generated from an input traffic secret
+%% value using:
+%%
+%% [sender]_write_key = HKDF-Expand-Label(Secret, "key", "", key_length)
+%% [sender]_write_iv = HKDF-Expand-Label(Secret, "iv", "", iv_length)
+-spec calculate_traffic_keys(atom(), atom(), binary()) -> {binary(), binary()}.
+calculate_traffic_keys(HKDFAlgo, Cipher, Secret) ->
+ Key = hkdf_expand_label(Secret, <<"key">>, <<>>, ssl_cipher:key_material(Cipher), HKDFAlgo),
+ %% TODO: remove hard coded IV size
+ IV = hkdf_expand_label(Secret, <<"iv">>, <<>>, 12, HKDFAlgo),
+ {Key, IV}.
+
+%% TLS v1.3 ---------------------------------------------------
+
+%% TLS 1.0 -1.2 ---------------------------------------------------
+-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},
@@ -191,8 +440,11 @@ mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor},
?BYTE(Major), ?BYTE(Minor), ?UINT16(Length)>>,
Fragment]),
Mac.
+%% TLS 1.0 -1.2 ---------------------------------------------------
+
+%% TODO 1.3 same as above?
--spec suites(1|2|3) -> [ssl_cipher_format:cipher_suite()].
+-spec suites(1|2|3|4|'TLS_v1.3') -> [ssl_cipher_format:cipher_suite()].
suites(Minor) when Minor == 1; Minor == 2 ->
[
@@ -244,8 +496,29 @@ suites(3) ->
%% ?TLS_DH_DSS_WITH_AES_256_GCM_SHA384,
%% ?TLS_DH_RSA_WITH_AES_128_GCM_SHA256,
%% ?TLS_DH_DSS_WITH_AES_128_GCM_SHA256
- ] ++ suites(2).
-
+ ] ++ suites(2);
+
+suites(4) ->
+ [?TLS_AES_256_GCM_SHA384,
+ ?TLS_AES_128_GCM_SHA256,
+ ?TLS_CHACHA20_POLY1305_SHA256
+ %% Not supported
+ %% ?TLS_AES_128_CCM_SHA256,
+ %% ?TLS_AES_128_CCM_8_SHA256
+ ] ++ suites(3);
+
+suites('TLS_v1.3') ->
+ [?TLS_AES_256_GCM_SHA384,
+ ?TLS_AES_128_GCM_SHA256,
+ ?TLS_CHACHA20_POLY1305_SHA256
+ %% Not supported
+ %% ?TLS_AES_128_CCM_SHA256,
+ %% ?TLS_AES_128_CCM_8_SHA256
+ ].
+
+
+signature_algs({3, 4}, HashSigns) ->
+ signature_algs({3, 3}, HashSigns);
signature_algs({3, 3}, HashSigns) ->
CryptoSupports = crypto:supports(),
Hashes = proplists:get_value(hashs, CryptoSupports),
@@ -273,6 +546,10 @@ signature_algs({3, 3}, HashSigns) ->
end, [], HashSigns),
lists:reverse(Supported).
+default_signature_algs({3, 4} = Version) ->
+ %% TLS 1.3 servers shall be prepared to process TLS 1.2 ClientHellos
+ %% containing legacy hash-sign tuples.
+ default_signature_schemes(Version) ++ default_signature_algs({3,3});
default_signature_algs({3, 3} = Version) ->
Default = [%% SHA2
{sha512, ecdsa},
@@ -291,15 +568,99 @@ default_signature_algs({3, 3} = Version) ->
default_signature_algs(_) ->
undefined.
+
+signature_schemes(Version, SignatureSchemes) when is_tuple(Version)
+ andalso Version >= {3, 3} ->
+ CryptoSupports = crypto:supports(),
+ Hashes = proplists:get_value(hashs, CryptoSupports),
+ PubKeys = proplists:get_value(public_keys, CryptoSupports),
+ Curves = proplists:get_value(curves, CryptoSupports),
+ RSAPSSSupported = lists:member(rsa_pkcs1_pss_padding,
+ proplists:get_value(rsa_opts, CryptoSupports)),
+ Fun = fun (Scheme, Acc) when is_atom(Scheme) ->
+ {Hash0, Sign0, Curve} =
+ ssl_cipher:scheme_to_components(Scheme),
+ Sign = case Sign0 of
+ rsa_pkcs1 ->
+ rsa;
+ rsa_pss_rsae when RSAPSSSupported ->
+ rsa;
+ rsa_pss_pss when RSAPSSSupported ->
+ rsa;
+ S -> S
+ end,
+ Hash = case Hash0 of
+ sha1 ->
+ sha;
+ H -> H
+ end,
+ case proplists:get_bool(Sign, PubKeys)
+ andalso proplists:get_bool(Hash, Hashes)
+ andalso (Curve =:= undefined orelse
+ proplists:get_bool(Curve, Curves))
+ andalso is_pair(Hash, Sign, Hashes)
+ of
+ true ->
+ [Scheme | Acc];
+ false ->
+ Acc
+ end;
+ %% Special clause for filtering out the legacy hash-sign tuples.
+ (_ , Acc) ->
+ Acc
+ end,
+ Supported = lists:foldl(Fun, [], SignatureSchemes),
+ lists:reverse(Supported);
+signature_schemes(_, _) ->
+ [].
+
+default_signature_schemes(Version) ->
+ Default = [
+ 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,
+ %% ed25519,
+ %% ed448,
+
+ %% These values refer solely to signatures
+ %% which appear in certificates (see Section 4.4.2.2) and are not
+ %% defined for use in signed TLS handshake messages, although they
+ %% MAY appear in "signature_algorithms" and
+ %% "signature_algorithms_cert" for backward compatibility with
+ %% TLS 1.2.
+ rsa_pkcs1_sha512,
+ rsa_pkcs1_sha384,
+ rsa_pkcs1_sha256,
+ ecdsa_sha1,
+ rsa_pkcs1_sha1
+ ],
+ signature_schemes(Version, Default).
+
+
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
+hkdf_expand(Algo, PseudoRandKey, ContextInfo, Length, N, N, Prev, Acc) ->
+ Keyingmaterial = hmac_hash(Algo, PseudoRandKey, <<Prev/binary, ContextInfo/binary, ?BYTE(N)>>),
+ binary:part(<<Acc/binary, Keyingmaterial/binary>>, {0, Length});
+hkdf_expand(Algo, PseudoRandKey, ContextInfo, Length, M, N, Prev, Acc) ->
+ Keyingmaterial = hmac_hash(Algo, PseudoRandKey, <<Prev/binary, ContextInfo/binary, ?BYTE(M)>>),
+ hkdf_expand(Algo, PseudoRandKey, ContextInfo, Length, M + 1, N, Keyingmaterial, <<Acc/binary, Keyingmaterial/binary>>).
+
%%%% HMAC and the Pseudorandom Functions RFC 2246 & 4346 - 5.%%%%
hmac_hash(?NULL, _, _) ->
<<>>;
hmac_hash(Alg, Key, Value) ->
crypto:hmac(mac_algo(Alg), Key, Value).
+mac_algo(Alg) when is_atom(Alg) ->
+ Alg;
mac_algo(?MD5) -> md5;
mac_algo(?SHA) -> sha;
mac_algo(?SHA256) -> sha256;
@@ -395,6 +756,7 @@ ecc_curves(all) ->
sect239k1,sect233k1,sect233r1,secp224k1,secp224r1,
sect193r1,sect193r2,secp192k1,secp192r1,sect163k1,
sect163r1,sect163r2,secp160k1,secp160r1,secp160r2];
+
ecc_curves(Minor) ->
TLSCurves = ecc_curves(all),
ecc_curves(Minor, TLSCurves).
@@ -409,6 +771,63 @@ ecc_curves(_Minor, TLSCurves) ->
end
end, [], TLSCurves).
+-spec groups(4 | all | default) -> [group()].
+groups(all) ->
+ [x25519,
+ x448,
+ secp256r1,
+ secp384r1,
+ secp521r1,
+ ffdhe2048,
+ ffdhe3072,
+ ffdhe4096,
+ ffdhe6144,
+ ffdhe8192];
+groups(default) ->
+ [x25519,
+ x448,
+ secp256r1,
+ secp384r1];
+groups(Minor) ->
+ TLSGroups = groups(all),
+ groups(Minor, TLSGroups).
+%%
+-spec groups(4, [group()]) -> [group()].
+groups(_Minor, TLSGroups) ->
+ CryptoGroups = supported_groups(),
+ lists:filter(fun(Group) -> proplists:get_bool(Group, CryptoGroups) end, TLSGroups).
+
+default_groups(Minor) ->
+ TLSGroups = groups(default),
+ groups(Minor, TLSGroups).
+
+supported_groups() ->
+ %% TODO: Add new function to crypto?
+ proplists:get_value(curves, crypto:supports()) ++
+ [ffdhe2048,ffdhe3072,ffdhe4096,ffdhe6144,ffdhe8192].
+
+group_to_enum(secp256r1) -> 23;
+group_to_enum(secp384r1) -> 24;
+group_to_enum(secp521r1) -> 25;
+group_to_enum(x25519) -> 29;
+group_to_enum(x448) -> 30;
+group_to_enum(ffdhe2048) -> 256;
+group_to_enum(ffdhe3072) -> 257;
+group_to_enum(ffdhe4096) -> 258;
+group_to_enum(ffdhe6144) -> 259;
+group_to_enum(ffdhe8192) -> 260.
+
+enum_to_group(23) -> secp256r1;
+enum_to_group(24) -> secp384r1;
+enum_to_group(25) -> secp521r1;
+enum_to_group(29) -> x25519;
+enum_to_group(30) -> x448;
+enum_to_group(256) -> ffdhe2048;
+enum_to_group(257) -> ffdhe3072;
+enum_to_group(258) -> ffdhe4096;
+enum_to_group(259) -> ffdhe6144;
+enum_to_group(260) -> ffdhe8192;
+enum_to_group(_) -> undefined.
%% ECC curves from draft-ietf-tls-ecc-12.txt (Oct. 17, 2005)
oid_to_enum(?sect163k1) -> 1;
diff --git a/lib/ssl/test/Makefile b/lib/ssl/test/Makefile
index 9dfb2eba53..57b74115ed 100644
--- a/lib/ssl/test/Makefile
+++ b/lib/ssl/test/Makefile
@@ -29,7 +29,7 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
# Application version
# ----------------------------------------------------
include ../vsn.mk
-VSN=$(GS_VSN)
+VSN=$(SSL_VSN)
# ----------------------------------------------------
# Target Specs
@@ -61,6 +61,8 @@ MODULES = \
ssl_ECC\
ssl_upgrade_SUITE\
ssl_sni_SUITE \
+ ssl_eqc_SUITE \
+ ssl_rfc_5869_SUITE \
make_certs\
x509_test
@@ -144,7 +146,7 @@ release_tests_spec: opt
$(INSTALL_DATA) $(ERL_FILES) $(HRL_FILES) $(HRL_FILES_NEEDED_IN_TEST) $(COVER_FILE) "$(RELSYSDIR)"
$(INSTALL_DATA) ssl.spec ssl_bench.spec ssl.cover "$(RELSYSDIR)"
chmod -R u+w "$(RELSYSDIR)"
- @tar cf - *_SUITE_data | (cd "$(RELSYSDIR)"; tar xf -)
+ @tar cf - *_SUITE_data property_test | (cd "$(RELSYSDIR)"; tar xf -)
release_docs_spec:
diff --git a/lib/ssl/test/make_certs.erl b/lib/ssl/test/make_certs.erl
index 8fe7c54549..76bf0fa895 100644
--- a/lib/ssl/test/make_certs.erl
+++ b/lib/ssl/test/make_certs.erl
@@ -189,6 +189,18 @@ gencrl(Root, CA, C, CrlHours) ->
Env = [{"ROOTDIR", filename:absname(Root)}],
cmd(Cmd, Env).
+%% This function sets the number of seconds until the next CRL is due.
+gencrl_sec(Root, CA, C, CrlSecs) ->
+ CACnfFile = filename:join([Root, CA, "ca.cnf"]),
+ CACRLFile = filename:join([Root, CA, "crl.pem"]),
+ Cmd = [C#config.openssl_cmd, " ca"
+ " -gencrl ",
+ " -crlsec ", integer_to_list(CrlSecs),
+ " -out ", CACRLFile,
+ " -config ", CACnfFile],
+ Env = [{"ROOTDIR", filename:absname(Root)}],
+ cmd(Cmd, Env).
+
can_generate_expired_crls(C) ->
%% OpenSSL can generate CRLs with an expiration date in the past,
%% if we pass a negative number for -crlhours. However, LibreSSL
@@ -365,7 +377,7 @@ req_cnf(Root, C) ->
"default_bits = ", integer_to_list(C#config.default_bits), "\n"
"RANDFILE = $ROOTDIR/RAND\n"
"encrypt_key = no\n"
- "default_md = md5\n"
+ "default_md = sha1\n"
"#string_mask = pkix\n"
"x509_extensions = ca_ext\n"
"prompt = no\n"
@@ -415,7 +427,7 @@ ca_cnf(
["crl_extensions = crl_ext\n" || C#config.v2_crls],
"unique_subject = no\n"
"default_days = 3600\n"
- "default_md = md5\n"
+ "default_md = sha1\n"
"preserve = no\n"
"policy = policy_match\n"
"\n"
@@ -499,7 +511,7 @@ ca_cnf(
["crl_extensions = crl_ext\n" || C#config.v2_crls],
"unique_subject = no\n"
"default_days = 3600\n"
- "default_md = md5\n"
+ "default_md = sha1\n"
"preserve = no\n"
"policy = policy_match\n"
"\n"
diff --git a/lib/ssl/test/property_test/ssl_eqc_handshake.erl b/lib/ssl/test/property_test/ssl_eqc_handshake.erl
new file mode 100644
index 0000000000..38a4b7fb11
--- /dev/null
+++ b/lib/ssl/test/property_test/ssl_eqc_handshake.erl
@@ -0,0 +1,812 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2018. 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%
+%%
+%%
+
+-module(ssl_eqc_handshake).
+
+-compile(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.
+
+-include_lib("kernel/include/inet.hrl").
+-include_lib("ssl/src/tls_handshake_1_3.hrl").
+-include_lib("ssl/src/tls_handshake.hrl").
+-include_lib("ssl/src/ssl_handshake.hrl").
+-include_lib("ssl/src/ssl_alert.hrl").
+-include_lib("ssl/src/ssl_internal.hrl").
+
+-define('TLS_v1.3', {3,4}).
+-define('TLS_v1.2', {3,3}).
+-define('TLS_v1.1', {3,2}).
+-define('TLS_v1', {3,1}).
+-define('SSL_v3', {3,0}).
+
+%%--------------------------------------------------------------------
+%% Properties --------------------------------------------------------
+%%--------------------------------------------------------------------
+
+prop_tls_hs_encode_decode() ->
+ ?FORALL({Handshake, TLSVersion}, ?LET(Version, tls_version(), {tls_msg(Version), Version}),
+ try
+ [Type, _Length, Data] = tls_handshake:encode_handshake(Handshake, TLSVersion),
+ case tls_handshake:decode_handshake(TLSVersion, Type, Data) of
+ Handshake ->
+ true;
+ _ ->
+ false
+ end
+ catch
+ throw:#alert{} ->
+ true
+ end
+ ).
+
+%%--------------------------------------------------------------------
+%% Message Generators -----------------------------------------------
+%%--------------------------------------------------------------------
+
+tls_msg(?'TLS_v1.3'= Version) ->
+ oneof([client_hello(Version),
+ server_hello(Version),
+ %%new_session_ticket()
+ #end_of_early_data{},
+ encrypted_extensions(),
+ certificate_1_3(),
+ %%certificate_request_1_3,
+ certificate_verify_1_3(),
+ finished(),
+ key_update()
+ ]);
+tls_msg(Version) ->
+ oneof([
+ #hello_request{},
+ client_hello(Version),
+ server_hello(Version),
+ certificate(),
+ %%server_key_exchange()
+ certificate_request(Version),
+ #server_hello_done{},
+ %%certificate_verify()
+ %%client_key_exchange()
+ finished()
+ ]).
+
+%%
+%% Shared messages
+%%
+client_hello(?'TLS_v1.3' = Version) ->
+ #client_hello{session_id = session_id(),
+ client_version = ?'TLS_v1.2',
+ cipher_suites = cipher_suites(Version),
+ compression_methods = compressions(Version),
+ random = client_random(Version),
+ extensions = client_hello_extensions(Version)
+ };
+client_hello(Version) ->
+ #client_hello{session_id = session_id(),
+ client_version = Version,
+ cipher_suites = cipher_suites(Version),
+ compression_methods = compressions(Version),
+ random = client_random(Version),
+ extensions = client_hello_extensions(Version)
+ }.
+
+server_hello(?'TLS_v1.3' = Version) ->
+ #server_hello{server_version = ?'TLS_v1.2',
+ session_id = session_id(),
+ random = server_random(Version),
+ cipher_suite = cipher_suite(Version),
+ compression_method = compression(Version),
+ extensions = server_hello_extensions(Version)
+ };
+server_hello(Version) ->
+ #server_hello{server_version = Version,
+ session_id = session_id(),
+ random = server_random(Version),
+ cipher_suite = cipher_suite(Version),
+ compression_method = compression(Version),
+ extensions = server_hello_extensions(Version)
+ }.
+
+certificate() ->
+ #certificate{
+ asn1_certificates = certificate_chain()
+ }.
+
+certificate_1_3() ->
+ ?LET(Certs, certificate_chain(),
+ #certificate_1_3{
+ certificate_request_context = certificate_request_context(),
+ certificate_list = certificate_entries(Certs, [])
+ }).
+
+certificate_verify_1_3() ->
+ ?LET(Certs, certificate_chain(),
+ #certificate_verify_1_3{
+ algorithm = sig_scheme(),
+ signature = signature()
+ }).
+
+finished() ->
+ ?LET(Size, digest_size(),
+ #finished{verify_data = crypto:strong_rand_bytes(Size)}).
+
+%%
+%% TLS 1.0-1.2 messages
+%%
+
+
+
+%%
+%% TLS 1.3 messages
+%%
+
+encrypted_extensions() ->
+ ?LET(Exts, extensions(?'TLS_v1.3', encrypted_extensions),
+ #encrypted_extensions{extensions = Exts}).
+
+
+key_update() ->
+ #key_update{request_update = request_update()}.
+
+
+%%--------------------------------------------------------------------
+%% Messge Data Generators -------------------------------------------
+%%--------------------------------------------------------------------
+
+tls_version() ->
+ oneof([?'TLS_v1.3', ?'TLS_v1.2', ?'TLS_v1.1', ?'TLS_v1', ?'SSL_v3']).
+
+cipher_suite(Version) ->
+ oneof(cipher_suites(Version)).
+
+cipher_suites(Version) ->
+ ssl_cipher:suites(Version).
+
+session_id() ->
+ crypto:strong_rand_bytes(?NUM_OF_SESSION_ID_BYTES).
+
+compression(Version) ->
+ oneof(compressions(Version)).
+
+compressions(_) ->
+ ssl_record:compressions().
+
+client_random(_) ->
+ crypto:strong_rand_bytes(32).
+
+server_random(_) ->
+ crypto:strong_rand_bytes(32).
+
+
+client_hello_extensions(Version) ->
+ ?LET(Exts, extensions(Version, client_hello),
+ maps:merge(ssl_handshake:empty_extensions(Version, client_hello),
+ Exts)).
+
+server_hello_extensions(Version) ->
+ ?LET(Exts, extensions(Version, server_hello),
+ maps:merge(ssl_handshake:empty_extensions(Version, server_hello),
+ Exts)).
+
+key_share_client_hello() ->
+ oneof([undefined]).
+ %%oneof([#key_share_client_hello{}, undefined]).
+
+key_share_server_hello() ->
+ oneof([undefined]).
+ %%oneof([#key_share_server_hello{}, undefined]).
+
+pre_shared_keyextension() ->
+ oneof([undefined]).
+ %%oneof([#pre_shared_keyextension{},undefined]).
+
+%% +--------------------------------------------------+-------------+
+%% | Extension | TLS 1.3 |
+%% +--------------------------------------------------+-------------+
+%% | server_name [RFC6066] | CH, EE |
+%% | | |
+%% | max_fragment_length [RFC6066] | CH, EE |
+%% | | |
+%% | status_request [RFC6066] | CH, CR, CT |
+%% | | |
+%% | supported_groups [RFC7919] | CH, EE |
+%% | | |
+%% | signature_algorithms (RFC 8446) | CH, CR |
+%% | | |
+%% | use_srtp [RFC5764] | CH, EE |
+%% | | |
+%% | heartbeat [RFC6520] | CH, EE |
+%% | | |
+%% | application_layer_protocol_negotiation [RFC7301] | CH, EE |
+%% | | |
+%% | signed_certificate_timestamp [RFC6962] | CH, CR, CT |
+%% | | |
+%% | client_certificate_type [RFC7250] | CH, EE |
+%% | | |
+%% | server_certificate_type [RFC7250] | CH, EE |
+%% | | |
+%% | padding [RFC7685] | CH |
+%% | | |
+%% | key_share (RFC 8446) | CH, SH, HRR |
+%% | | |
+%% | pre_shared_key (RFC 8446) | CH, SH |
+%% | | |
+%% | psk_key_exchange_modes (RFC 8446) | CH |
+%% | | |
+%% | early_data (RFC 8446) | CH, EE, NST |
+%% | | |
+%% | cookie (RFC 8446) | CH, HRR |
+%% | | |
+%% | supported_versions (RFC 8446) | CH, SH, HRR |
+%% | | |
+%% | certificate_authorities (RFC 8446) | CH, CR |
+%% | | |
+%% | oid_filters (RFC 8446) | CR |
+%% | | |
+%% | post_handshake_auth (RFC 8446) | CH |
+%% | | |
+%% | signature_algorithms_cert (RFC 8446) | CH, CR |
+%% +--------------------------------------------------+-------------+
+extensions(?'TLS_v1.3' = Version, client_hello) ->
+ ?LET({
+ ServerName,
+ %% MaxFragmentLength,
+ %% StatusRequest,
+ SupportedGroups,
+ SignatureAlgorithms,
+ %% UseSrtp,
+ %% Heartbeat,
+ ALPN,
+ %% SignedCertTimestamp,
+ %% ClientCertiticateType,
+ %% ServerCertificateType,
+ %% Padding,
+ KeyShare,
+ %% PreSharedKey,
+ %% PSKKeyExchangeModes,
+ %% EarlyData,
+ %% Cookie,
+ SupportedVersions,
+ %% CertAuthorities,
+ %% PostHandshakeAuth,
+ SignatureAlgorithmsCert
+ },
+ {
+ oneof([server_name(), undefined]),
+ %% oneof([max_fragment_length(), undefined]),
+ %% oneof([status_request(), undefined]),
+ oneof([supported_groups(Version), undefined]),
+ oneof([signature_algs(Version), undefined]),
+ %% oneof([use_srtp(), undefined]),
+ %% oneof([heartbeat(), undefined]),
+ oneof([alpn(), undefined]),
+ %% oneof([signed_cert_timestamp(), undefined]),
+ %% oneof([client_cert_type(), undefined]),
+ %% oneof([server_cert_type(), undefined]),
+ %% oneof([padding(), undefined]),
+ oneof([key_share(client_hello), undefined]),
+ %% oneof([pre_shared_key(), undefined]),
+ %% oneof([psk_key_exchange_modes(), undefined]),
+ %% oneof([early_data(), undefined]),
+ %% oneof([cookie(), undefined]),
+ oneof([client_hello_versions(Version)]),
+ %% oneof([cert_authorities(), undefined]),
+ %% oneof([post_handshake_auth(), undefined]),
+ oneof([signature_algs_cert(), undefined])
+ },
+ maps:filter(fun(_, undefined) ->
+ false;
+ (_,_) ->
+ true
+ end,
+ #{
+ sni => ServerName,
+ %% max_fragment_length => MaxFragmentLength,
+ %% status_request => StatusRequest,
+ elliptic_curves => SupportedGroups,
+ signature_algs => SignatureAlgorithms,
+ %% use_srtp => UseSrtp,
+ %% heartbeat => Heartbeat,
+ alpn => ALPN,
+ %% signed_cert_timestamp => SignedCertTimestamp,
+ %% client_cert_type => ClientCertificateType,
+ %% server_cert_type => ServerCertificateType,
+ %% padding => Padding,
+ key_share => KeyShare,
+ %% pre_shared_key => PreSharedKey,
+ %% psk_key_exhange_modes => PSKKeyExchangeModes,
+ %% early_data => EarlyData,
+ %% cookie => Cookie,
+ client_hello_versions => SupportedVersions,
+ %% cert_authorities => CertAuthorities,
+ %% post_handshake_auth => PostHandshakeAuth,
+ signature_algs_cert => SignatureAlgorithmsCert
+ }));
+extensions(?'SSL_v3', client_hello) ->
+ #{};
+extensions(Version, client_hello) ->
+ ?LET({
+ SNI,
+ ECPoitF,
+ ECCurves,
+ ALPN,
+ NextP,
+ SRP
+ %% RenegotiationInfo
+ },
+ {
+ oneof([sni(), undefined]),
+ oneof([ec_point_formats(), undefined]),
+ oneof([elliptic_curves(Version), undefined]),
+ oneof([alpn(), undefined]),
+ oneof([next_protocol_negotiation(), undefined]),
+ oneof([srp(), undefined])
+ %% oneof([renegotiation_info(), undefined])
+ },
+ maps:filter(fun(_, undefined) ->
+ false;
+ (_,_) ->
+ true
+ end,
+ #{
+ sni => SNI,
+ ec_point_formats => ECPoitF,
+ elliptic_curves => ECCurves,
+ alpn => ALPN,
+ next_protocol_negotiation => NextP,
+ srp => SRP
+ %% renegotiation_info => RenegotiationInfo
+ }));
+extensions(?'TLS_v1.3' = Version, server_hello) ->
+ ?LET({
+ KeyShare,
+ %% PreSharedKeys,
+ SupportedVersions
+ },
+ {
+ oneof([key_share(server_hello), undefined]),
+ %% oneof([pre_shared_keys(), undefined]),
+ oneof([server_hello_selected_version()])
+ },
+ maps:filter(fun(_, undefined) ->
+ false;
+ (_,_) ->
+ true
+ end,
+ #{
+ key_share => KeyShare,
+ %% pre_shared_keys => PreSharedKeys,
+ server_hello_selected_version => SupportedVersions
+ }));
+extensions(Version, server_hello) ->
+ ?LET({
+ ECPoitF,
+ ALPN,
+ NextP
+ %% RenegotiationInfo,
+ },
+ {
+ oneof([ec_point_formats(), undefined]),
+ oneof([alpn(), undefined]),
+ oneof([next_protocol_negotiation(), undefined])
+ %% oneof([renegotiation_info(), undefined]),
+ },
+ maps:filter(fun(_, undefined) ->
+ false;
+ (_,_) ->
+ true
+ end,
+ #{
+ ec_point_formats => ECPoitF,
+ alpn => ALPN,
+ next_protocol_negotiation => NextP
+ %% renegotiation_info => RenegotiationInfo
+ }));
+extensions(?'TLS_v1.3' = Version, encrypted_extensions) ->
+ ?LET({
+ ServerName,
+ %% MaxFragmentLength,
+ SupportedGroups,
+ %% UseSrtp,
+ %% Heartbeat,
+ ALPN
+ %% ClientCertiticateType,
+ %% ServerCertificateType,
+ %% EarlyData
+ },
+ {
+ oneof([server_name(), undefined]),
+ %% oneof([max_fragment_length(), undefined]),
+ oneof([supported_groups(Version), undefined]),
+ %% oneof([use_srtp(), undefined]),
+ %% oneof([heartbeat(), undefined]),
+ oneof([alpn(), undefined])
+ %% oneof([client_cert_type(), undefined]),
+ %% oneof([server_cert_type(), undefined]),
+ %% oneof([early_data(), undefined])
+ },
+ maps:filter(fun(_, undefined) ->
+ false;
+ (_,_) ->
+ true
+ end,
+ #{
+ sni => ServerName,
+ %% max_fragment_length => MaxFragmentLength,
+ elliptic_curves => SupportedGroups,
+ %% use_srtp => UseSrtp,
+ %% heartbeat => Heartbeat,
+ alpn => ALPN
+ %% client_cert_type => ClientCertificateType,
+ %% server_cert_type => ServerCertificateType,
+ %% early_data => EarlyData
+ })).
+
+server_name() ->
+ ?LET(ServerName, sni(),
+ ServerName).
+ %% sni().
+
+signature_algs_cert() ->
+ ?LET(List, sig_scheme_list(),
+ #signature_algorithms_cert{signature_scheme_list = List}).
+
+signature_algorithms() ->
+ ?LET(List, sig_scheme_list(),
+ #signature_algorithms{signature_scheme_list = List}).
+
+sig_scheme_list() ->
+ oneof([[rsa_pkcs1_sha256],
+ [rsa_pkcs1_sha256, ecdsa_sha1],
+ [rsa_pkcs1_sha256,
+ rsa_pkcs1_sha384,
+ rsa_pkcs1_sha512,
+ ecdsa_secp256r1_sha256,
+ ecdsa_secp384r1_sha384,
+ ecdsa_secp521r1_sha512,
+ rsa_pss_rsae_sha256,
+ rsa_pss_rsae_sha384,
+ rsa_pss_rsae_sha512,
+ rsa_pss_pss_sha256,
+ rsa_pss_pss_sha384,
+ rsa_pss_pss_sha512,
+ rsa_pkcs1_sha1,
+ ecdsa_sha1]
+ ]).
+
+sig_scheme() ->
+ oneof([rsa_pkcs1_sha256,
+ rsa_pkcs1_sha384,
+ rsa_pkcs1_sha512,
+ ecdsa_secp256r1_sha256,
+ ecdsa_secp384r1_sha384,
+ ecdsa_secp521r1_sha512,
+ rsa_pss_rsae_sha256,
+ rsa_pss_rsae_sha384,
+ rsa_pss_rsae_sha512,
+ rsa_pss_pss_sha256,
+ rsa_pss_pss_sha384,
+ rsa_pss_pss_sha512,
+ rsa_pkcs1_sha1,
+ ecdsa_sha1]).
+
+signature() ->
+ <<44,119,215,137,54,84,156,26,121,212,64,173,189,226,
+ 191,46,76,89,204,2,78,79,163,228,90,21,89,179,4,198,
+ 109,14,52,26,230,22,56,8,170,129,86,0,7,132,245,81,
+ 181,131,62,70,79,167,112,85,14,171,175,162,110,29,
+ 212,198,45,188,83,176,251,197,224,104,95,74,89,59,
+ 26,60,63,79,238,196,137,65,23,199,127,145,176,184,
+ 216,3,48,116,172,106,97,83,227,172,246,137,91,79,
+ 173,119,169,60,67,1,177,117,9,93,38,86,232,253,73,
+ 140,17,147,130,110,136,245,73,10,91,70,105,53,225,
+ 158,107,60,190,30,14,26,92,147,221,60,117,104,53,70,
+ 142,204,7,131,11,183,192,120,246,243,68,99,147,183,
+ 49,149,48,188,8,218,17,150,220,121,2,99,194,140,35,
+ 13,249,201,37,216,68,45,87,58,18,10,106,11,132,241,
+ 71,170,225,216,197,212,29,107,36,80,189,184,202,56,
+ 86,213,45,70,34,74,71,48,137,79,212,194,172,151,57,
+ 57,30,126,24,157,198,101,220,84,162,89,105,185,245,
+ 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') ->
+ ?LET(SupportedVersions,
+ oneof([[{3,4}],
+ %% 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}]
+ ]),
+ #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}]
+ ]),
+ #client_hello_versions{versions = SupportedVersions}).
+
+server_hello_selected_version() ->
+ #server_hello_selected_version{selected_version = {3,4}}.
+
+request_update() ->
+ oneof([?UPDATE_NOT_REQUESTED, ?UPDATE_REQUESTED]).
+
+certificate_chain()->
+ Conf = cert_conf(),
+ ?LET(Chain,
+ choose_certificate_chain(Conf),
+ Chain).
+
+choose_certificate_chain(#{server_config := ServerConf,
+ client_config := ClientConf}) ->
+ oneof([certificate_chain(ServerConf), certificate_chain(ClientConf)]).
+
+certificate_request_context() ->
+ oneof([<<>>,
+ <<1>>,
+ <<"foobar">>
+ ]).
+certificate_entries([], Acc) ->
+ lists:reverse(Acc);
+certificate_entries([Cert | Rest], Acc) ->
+ certificate_entries(Rest, [certificate_entry(Cert) | Acc]).
+
+certificate_entry(Cert) ->
+ #certificate_entry{data = Cert,
+ extensions = certificate_entry_extensions()
+ }.
+certificate_entry_extensions() ->
+ #{}.
+
+certificate_chain(Conf) ->
+ CAs = proplists:get_value(cacerts, Conf),
+ Cert = proplists:get_value(cert, Conf),
+ %% Middle argument are of correct type but will not be used
+ {ok, _, Chain} = ssl_certificate:certificate_chain(Cert, ets:new(foo, []), make_ref(), CAs),
+ Chain.
+
+cert_conf()->
+ Hostname = net_adm:localhost(),
+ {ok, #hostent{h_addr_list = [_IP |_]}} = inet:gethostbyname(net_adm:localhost()),
+ 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)}]],
+ peer => [{extensions, [#'Extension'{extnID =
+ ?'id-ce-subjectAltName',
+ extnValue = [{dNSName, Hostname}],
+ critical = false}]},
+ {key, ssl_test_lib:hardcode_rsa_key(3)}
+ ]},
+ client_chain =>
+ #{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)}]}}).
+
+certificate_request(Version) ->
+ #certificate_request{certificate_types = certificate_types(Version),
+ hashsign_algorithms = hashsign_algorithms(Version),
+ certificate_authorities = certificate_authorities()}.
+
+certificate_types(?'TLS_v1.3') ->
+ iolist_to_binary([<<?BYTE(?ECDSA_SIGN)>>, <<?BYTE(?RSA_SIGN)>>]);
+certificate_types(?'TLS_v1.2') ->
+ iolist_to_binary([<<?BYTE(?ECDSA_SIGN)>>, <<?BYTE(?RSA_SIGN)>>, <<?BYTE(?DSS_SIGN)>>]);
+certificate_types(_) ->
+ iolist_to_binary([<<?BYTE(?ECDSA_SIGN)>>, <<?BYTE(?RSA_SIGN)>>, <<?BYTE(?DSS_SIGN)>>]).
+
+
+
+signature_algs({3,4}) ->
+ ?LET(Algs, signature_algorithms(),
+ Algs);
+signature_algs({3,3} = Version) ->
+ #hash_sign_algos{hash_sign_algos = hash_alg_list(Version)};
+signature_algs(Version) when Version < {3,3} ->
+ undefined.
+
+
+
+hashsign_algorithms({_, N} = Version) when N >= 3 ->
+ #hash_sign_algos{hash_sign_algos = hash_alg_list(Version)};
+hashsign_algorithms(_) ->
+ undefined.
+
+hash_alg_list(Version) ->
+ ?LET(NumOf, choose(1,15),
+ ?LET(List, [hash_alg(Version) || _ <- lists:seq(1,NumOf)],
+ lists:usort(List)
+ )).
+
+hash_alg(Version) ->
+ ?LET(Alg, sign_algorithm(Version),
+ {hash_algorithm(Version, Alg), Alg}
+ ).
+
+hash_algorithm(?'TLS_v1.3', _) ->
+ oneof([sha, sha224, sha256, sha384, sha512]);
+hash_algorithm(?'TLS_v1.2', rsa) ->
+ oneof([sha, sha224, sha256, sha384, sha512]);
+hash_algorithm(_, rsa) ->
+ oneof([md5, sha, sha224, sha256, sha384, sha512]);
+hash_algorithm(_, ecdsa) ->
+ oneof([sha, sha224, sha256, sha384, sha512]);
+hash_algorithm(_, dsa) ->
+ sha.
+
+sign_algorithm(?'TLS_v1.3') ->
+ oneof([rsa, ecdsa]);
+sign_algorithm(_) ->
+ oneof([rsa, dsa, ecdsa]).
+
+certificate_authorities() ->
+ #{server_config := ServerConf} = cert_conf(),
+ Authorities = proplists:get_value(cacerts, ServerConf),
+ Enc = fun(#'OTPCertificate'{tbsCertificate=TBSCert}) ->
+ OTPSubj = TBSCert#'OTPTBSCertificate'.subject,
+ DNEncodedBin = public_key:pkix_encode('Name', OTPSubj, otp),
+ DNEncodedLen = byte_size(DNEncodedBin),
+ <<?UINT16(DNEncodedLen), DNEncodedBin/binary>>
+ end,
+ list_to_binary([Enc(public_key:pkix_decode_cert(DERCert, otp)) || DERCert <- Authorities]).
+
+digest_size()->
+ oneof([160,224,256,384,512]).
+
+key_share_entry() ->
+ undefined.
+ %%#key_share_entry{}.
+
+server_hello_selected_version(Version) ->
+ #server_hello_selected_version{selected_version = Version}.
+
+sni() ->
+ #sni{hostname = net_adm:localhost()}.
+
+ec_point_formats() ->
+ #ec_point_formats{ec_point_format_list = ec_point_format_list()}.
+
+ec_point_format_list() ->
+ [?ECPOINT_UNCOMPRESSED].
+
+elliptic_curves({_, Minor}) when Minor < 4 ->
+ Curves = tls_v1:ecc_curves(Minor),
+ #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{supported_groups = SupportedGroups}.
+
+
+alpn() ->
+ ?LET(ExtD, alpn_protocols(), #alpn{extension_data = ExtD}).
+
+alpn_protocols() ->
+ oneof([<<"spdy/2">>, <<"spdy/3">>, <<"http/2">>, <<"http/1.0">>, <<"http/1.1">>]).
+
+next_protocol_negotiation() ->
+ %% Predecessor to APLN
+ ?LET(ExtD, alpn_protocols(), #next_protocol_negotiation{extension_data = ExtD}).
+
+srp() ->
+ ?LET(Name, gen_name(), #srp{username = list_to_binary(Name)}).
+
+renegotiation_info() ->
+ #renegotiation_info{renegotiated_connection = 0}.
+
+gen_name() ->
+ ?LET(Size, choose(1,10), gen_string(Size)).
+
+gen_char() ->
+ choose($a,$z).
+
+gen_string(N) ->
+ gen_string(N, []).
+
+gen_string(0, Acc) ->
+ Acc;
+gen_string(N, Acc) ->
+ ?LET(Char, gen_char(), gen_string(N-1, [Char | Acc])).
+
+key_share(client_hello) ->
+ ?LET(ClientShares, key_share_entry_list(),
+ #key_share_client_hello{
+ client_shares = ClientShares});
+key_share(server_hello) ->
+ ?LET([ServerShare], key_share_entry_list(1),
+ #key_share_server_hello{
+ server_share = ServerShare}).
+
+key_share_entry_list() ->
+ Max = length(ssl:groups()),
+ ?LET(Size, choose(1,Max), key_share_entry_list(Size)).
+%%
+key_share_entry_list(N) ->
+ key_share_entry_list(N, ssl:groups(), []).
+%%
+key_share_entry_list(0, _Pool, Acc) ->
+ Acc;
+key_share_entry_list(N, Pool, Acc) ->
+ R = rand:uniform(length(Pool)),
+ G = lists:nth(R, Pool),
+ P = generate_public_key(G),
+ KeyShareEntry =
+ #key_share_entry{
+ group = G,
+ key_exchange = P},
+ key_share_entry_list(N - 1, Pool -- [G], [KeyShareEntry|Acc]).
+
+%% TODO: fix curve generation
+generate_public_key(Group)
+ when Group =:= secp256r1 orelse
+ Group =:= secp384r1 orelse
+ Group =:= secp521r1 orelse
+ Group =:= x448 orelse
+ Group =:= x25519 ->
+ #'ECPrivateKey'{publicKey = PublicKey} =
+ public_key:generate_key({namedCurve, secp256r1}),
+ PublicKey;
+generate_public_key(Group) ->
+ {PublicKey, _} =
+ public_key:generate_key(ssl_dh_groups:dh_params(Group)),
+ PublicKey.
+
+groups() ->
+ Max = length(ssl:groups()),
+ ?LET(Size, choose(1,Max), group_list(Size)).
+
+group_list(N) ->
+ group_list(N, ssl:groups(), []).
+%%
+group_list(0, _Pool, Acc) ->
+ Acc;
+group_list(N, Pool, Acc) ->
+ R = rand:uniform(length(Pool)),
+ G = lists:nth(R, Pool),
+ group_list(N - 1, Pool -- [G], [G|Acc]).
diff --git a/lib/ssl/test/ssl.spec b/lib/ssl/test/ssl.spec
index cb54168d36..24272327c3 100644
--- a/lib/ssl/test/ssl.spec
+++ b/lib/ssl/test/ssl.spec
@@ -1,4 +1,10 @@
-{suites,"../ssl_test",all}.
-{skip_suites, "../ssl_test",
- [ssl_bench_SUITE, ssl_dist_bench_SUITE],
- "Benchmarks run separately"}.
+% {merge_tests,false}.
+{alias,dir,"../ssl_test"}.
+
+{suites,dir,all}.
+{skip_groups,dir,ssl_bench_SUITE,setup,"Benchmarks run separately"}.
+{skip_groups,dir,ssl_bench_SUITE,payload,"Benchmarks run separately"}.
+{skip_groups,dir,ssl_bench_SUITE,pem_cache,"Benchmarks run separately"}.
+{skip_groups,dir,ssl_dist_bench_SUITE,setup,"Benchmarks run separately"}.
+{skip_groups,dir,ssl_dist_bench_SUITE,throughput,"Benchmarks run separately"}.
+
diff --git a/lib/ssl/test/ssl_ECC_SUITE.erl b/lib/ssl/test/ssl_ECC_SUITE.erl
index a5309e866b..ca8d0ec70c 100644
--- a/lib/ssl/test/ssl_ECC_SUITE.erl
+++ b/lib/ssl/test/ssl_ECC_SUITE.erl
@@ -212,53 +212,61 @@ 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(0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
- ecdhe_ecdsa, ecdhe_ecdsa, Config),
+ ecdhe_ecdsa, ecdhe_ecdsa,
+ Config, DefaultCurve),
COpts = ssl_test_lib:ssl_options(COpts0, Config),
SOpts = ssl_test_lib:ssl_options(SOpts0, Config),
ECCOpts = [],
- case ssl_test_lib:supported_eccs([{eccs, [sect571r1]}]) of
- true -> ssl_test_lib:ecc_test(sect571r1, COpts, SOpts, [], ECCOpts, Config);
+ case ssl_test_lib:supported_eccs([{eccs, [DefaultCurve]}]) of
+ true -> ssl_test_lib:ecc_test(DefaultCurve, COpts, SOpts, [], ECCOpts, Config);
false -> {skip, "unsupported named curves"}
end.
ecc_default_order_custom_curves(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
- ecdhe_ecdsa, ecdhe_ecdsa, Config),
+ ecdhe_ecdsa, ecdhe_ecdsa,
+ Config, DefaultCurve),
COpts = ssl_test_lib:ssl_options(COpts0, Config),
SOpts = ssl_test_lib:ssl_options(SOpts0, Config),
- ECCOpts = [{eccs, [secp256r1, sect571r1]}],
+ ECCOpts = [{eccs, [secp256r1, DefaultCurve]}],
case ssl_test_lib:supported_eccs(ECCOpts) of
- true -> ssl_test_lib:ecc_test(sect571r1, COpts, SOpts, [], ECCOpts, Config);
+ true -> ssl_test_lib:ecc_test(DefaultCurve, COpts, SOpts, [], ECCOpts, Config);
false -> {skip, "unsupported named curves"}
end.
ecc_client_order(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
- ecdhe_ecdsa, ecdhe_ecdsa, Config),
+ ecdhe_ecdsa, ecdhe_ecdsa,
+ Config, DefaultCurve),
COpts = ssl_test_lib:ssl_options(COpts0, Config),
SOpts = ssl_test_lib:ssl_options(SOpts0, Config),
ECCOpts = [{honor_ecc_order, false}],
- case ssl_test_lib:supported_eccs([{eccs, [sect571r1]}]) of
- true -> ssl_test_lib:ecc_test(sect571r1, COpts, SOpts, [], ECCOpts, Config);
+ case ssl_test_lib:supported_eccs([{eccs, [DefaultCurve]}]) of
+ true -> ssl_test_lib:ecc_test(DefaultCurve, COpts, SOpts, [], ECCOpts, Config);
false -> {skip, "unsupported named curves"}
end.
ecc_client_order_custom_curves(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
- ecdhe_ecdsa, ecdhe_ecdsa, Config),
+ ecdhe_ecdsa, ecdhe_ecdsa,
+ Config, DefaultCurve),
COpts = ssl_test_lib:ssl_options(COpts0, Config),
SOpts = ssl_test_lib:ssl_options(SOpts0, Config),
- ECCOpts = [{honor_ecc_order, false}, {eccs, [secp256r1, sect571r1]}],
+ ECCOpts = [{honor_ecc_order, false}, {eccs, [secp256r1, DefaultCurve]}],
case ssl_test_lib:supported_eccs(ECCOpts) of
- true -> ssl_test_lib:ecc_test(sect571r1, COpts, SOpts, [], ECCOpts, Config);
+ true -> ssl_test_lib:ecc_test(DefaultCurve, COpts, SOpts, [], ECCOpts, Config);
false -> {skip, "unsupported named curves"}
end.
@@ -274,12 +282,13 @@ ecc_unknown_curve(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(0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdh_rsa, ecdhe_ecdsa, Config),
COpts = ssl_test_lib:ssl_options(COpts0, Config),
SOpts = ssl_test_lib:ssl_options(SOpts0, Config),
- ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}],
+ ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, DefaultCurve]}],
case ssl_test_lib:supported_eccs(ECCOpts) of
true -> ssl_test_lib:ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config);
false -> {skip, "unsupported named curves"}
@@ -287,12 +296,13 @@ 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(0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdh_rsa, ecdhe_rsa, Config),
COpts = ssl_test_lib:ssl_options(COpts0, Config),
SOpts = ssl_test_lib:ssl_options(SOpts0, Config),
- ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}],
+ ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, DefaultCurve]}],
case ssl_test_lib:supported_eccs(ECCOpts) of
true -> ssl_test_lib:ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config);
@@ -301,12 +311,13 @@ 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(0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdhe_rsa, ecdhe_ecdsa, Config),
COpts = ssl_test_lib:ssl_options(COpts0, Config),
SOpts = ssl_test_lib:ssl_options(SOpts0, Config),
- ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}],
+ ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, DefaultCurve]}],
case ssl_test_lib:supported_eccs(ECCOpts) of
true -> ssl_test_lib:ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config);
false -> {skip, "unsupported named curves"}
@@ -314,19 +325,21 @@ 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(0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdhe_rsa, ecdhe_rsa, Config),
COpts = ssl_test_lib:ssl_options(COpts0, Config),
SOpts = ssl_test_lib:ssl_options(SOpts0, Config),
- ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}],
+ ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, DefaultCurve]}],
case ssl_test_lib:supported_eccs(ECCOpts) of
true -> ssl_test_lib:ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config);
false -> {skip, "unsupported named curves"}
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(0))),
Ext = x509_test:extensions([{key_usage, [keyEncipherment]}]),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, [[], [], [{extensions, Ext}]]},
{client_chain, Default}],
@@ -334,8 +347,8 @@ client_ecdhe_rsa_server_ecdh_rsa_server_custom(Config) ->
COpts = ssl_test_lib:ssl_options(COpts0, Config),
SOpts = ssl_test_lib:ssl_options(SOpts0, Config),
- ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}],
- Expected = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(0))), %% The certificate curve
+ ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, DefaultCurve]}],
+ Expected = secp256r1, %% The certificate curve
case ssl_test_lib:supported_eccs(ECCOpts) of
true -> ssl_test_lib:ecc_test(Expected, COpts, SOpts, [], ECCOpts, Config);
@@ -344,12 +357,13 @@ 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(0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdhe_ecdsa, ecdhe_ecdsa, Config),
COpts = ssl_test_lib:ssl_options(COpts0, Config),
SOpts = ssl_test_lib:ssl_options(SOpts0, Config),
- ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}],
+ ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, DefaultCurve]}],
case ssl_test_lib:supported_eccs(ECCOpts) of
true -> ssl_test_lib:ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config);
false -> {skip, "unsupported named curves"}
@@ -357,12 +371,13 @@ 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(0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdhe_ecdsa, ecdhe_rsa, Config),
COpts = ssl_test_lib:ssl_options(COpts0, Config),
SOpts = ssl_test_lib:ssl_options(SOpts0, Config),
- ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}],
+ ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, DefaultCurve]}],
case ssl_test_lib:supported_eccs(ECCOpts) of
true -> ssl_test_lib:ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config);
false -> {skip, "unsupported named curves"}
@@ -370,12 +385,13 @@ 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(0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdhe_ecdsa, ecdhe_ecdsa, Config),
COpts = ssl_test_lib:ssl_options(COpts0, Config),
SOpts = ssl_test_lib:ssl_options(SOpts0, Config),
- ECCOpts = [{eccs, [secp256r1, sect571r1]}],
+ ECCOpts = [{eccs, [secp256r1, DefaultCurve]}],
case ssl_test_lib:supported_eccs(ECCOpts) of
true -> ssl_test_lib:ecc_test(secp256r1, COpts, SOpts, ECCOpts, [], Config);
false -> {skip, "unsupported named curves"}
@@ -383,12 +399,13 @@ 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(0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdhe_rsa, ecdhe_ecdsa, Config),
COpts = ssl_test_lib:ssl_options(COpts0, Config),
SOpts = ssl_test_lib:ssl_options(SOpts0, Config),
- ECCOpts = [{eccs, [secp256r1, sect571r1]}],
+ ECCOpts = [{eccs, [secp256r1, DefaultCurve]}],
case ssl_test_lib:supported_eccs(ECCOpts) of
true -> ssl_test_lib:ecc_test(secp256r1, COpts, SOpts, ECCOpts, [], Config);
false -> {skip, "unsupported named curves"}
diff --git a/lib/ssl/test/ssl_alpn_handshake_SUITE.erl b/lib/ssl/test/ssl_alpn_handshake_SUITE.erl
index 04c4b257d9..dfc780479e 100644
--- a/lib/ssl/test/ssl_alpn_handshake_SUITE.erl
+++ b/lib/ssl/test/ssl_alpn_handshake_SUITE.erl
@@ -153,41 +153,41 @@ protocols_must_be_a_binary_list(Config) when is_list(Config) ->
empty_client(Config) when is_list(Config) ->
run_failing_handshake(Config,
- [{alpn_advertised_protocols, []}],
- [{alpn_preferred_protocols, [<<"spdy/2">>, <<"spdy/3">>, <<"http/2">>]}],
- {error,{tls_alert,"no application protocol"}}).
+ [{alpn_advertised_protocols, []}],
+ [{alpn_preferred_protocols, [<<"spdy/2">>, <<"spdy/3">>, <<"http/2">>]}],
+ no_application_protocol).
%--------------------------------------------------------------------------------
empty_server(Config) when is_list(Config) ->
run_failing_handshake(Config,
- [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}],
- [{alpn_preferred_protocols, []}],
- {error,{tls_alert,"no application protocol"}}).
+ [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}],
+ [{alpn_preferred_protocols, []}],
+ no_application_protocol).
%--------------------------------------------------------------------------------
empty_client_empty_server(Config) when is_list(Config) ->
run_failing_handshake(Config,
- [{alpn_advertised_protocols, []}],
- [{alpn_preferred_protocols, []}],
- {error,{tls_alert,"no application protocol"}}).
+ [{alpn_advertised_protocols, []}],
+ [{alpn_preferred_protocols, []}],
+ no_application_protocol).
%--------------------------------------------------------------------------------
no_matching_protocol(Config) when is_list(Config) ->
run_failing_handshake(Config,
- [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}],
- [{alpn_preferred_protocols, [<<"spdy/2">>, <<"spdy/3">>, <<"http/2">>]}],
- {error,{tls_alert,"no application protocol"}}).
+ [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}],
+ [{alpn_preferred_protocols, [<<"spdy/2">>, <<"spdy/3">>, <<"http/2">>]}],
+ no_application_protocol).
%--------------------------------------------------------------------------------
client_alpn_and_server_alpn(Config) when is_list(Config) ->
run_handshake(Config,
- [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}],
- [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}],
- {ok, <<"http/1.1">>}).
+ [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}],
+ [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}],
+ {ok, <<"http/1.1">>}).
%--------------------------------------------------------------------------------
@@ -262,52 +262,12 @@ client_renegotiate(Config) when is_list(Config) ->
%--------------------------------------------------------------------------------
session_reused(Config) when is_list(Config)->
- ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ClientOpts = [{alpn_advertised_protocols, [<<"http/1.0">>]}] ++ ClientOpts0,
ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}] ++ ServerOpts0,
- {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, session_info_result, []}},
- {options, 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, no_result_msg, []}},
- {options, ClientOpts}]),
-
- SessionInfo =
- receive
- {Server, Info} ->
- Info
- end,
-
- Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}},
-
- %% Make sure session is registered
- ct:sleep(?SLEEP),
-
- Client1 =
- ssl_test_lib:start_client([{node, ClientNode},
- {port, Port}, {host, Hostname},
- {mfa, {ssl_test_lib, session_info_result, []}},
- {from, self()}, {options, ClientOpts}]),
-
- receive
- {Client1, SessionInfo} ->
- ok;
- {Client1, Other} ->
- ct:fail(Other)
- end,
-
- ssl_test_lib:close(Server),
- ssl_test_lib:close(Client),
- ssl_test_lib:close(Client1).
-
+ ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config).
%--------------------------------------------------------------------------------
alpn_not_supported_client(Config) when is_list(Config) ->
@@ -337,7 +297,7 @@ alpn_not_supported_server(Config) when is_list(Config)->
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
-run_failing_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedResult) ->
+run_failing_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedAlert) ->
ClientOpts = ClientExtraOpts ++ ssl_test_lib:ssl_options(client_rsa_opts, Config),
ServerOpts = ServerExtraOpts ++ ssl_test_lib:ssl_options(server_rsa_opts, Config),
@@ -353,8 +313,7 @@ run_failing_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedResult)
{from, self()},
{mfa, {?MODULE, placeholder, []}},
{options, ClientOpts}]),
- ssl_test_lib:check_result(Server, ExpectedResult,
- Client, ExpectedResult).
+ ssl_test_lib:check_client_alert(Server, Client, ExpectedAlert).
run_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedProtocol) ->
Data = "hello world",
diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl
index 90fcde609f..292916692d 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-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2019. All 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,6 +29,7 @@
-include_lib("public_key/include/public_key.hrl").
-include("ssl_api.hrl").
+-include("ssl_cipher.hrl").
-include("ssl_internal.hrl").
-include("ssl_alert.hrl").
-include("ssl_internal.hrl").
@@ -53,7 +54,8 @@ all() ->
{group, options_tls},
{group, session},
{group, 'dtlsv1.2'},
- {group, 'dtlsv1'},
+ {group, 'dtlsv1'},
+ {group, 'tlsv1.3'},
{group, 'tlsv1.2'},
{group, 'tlsv1.1'},
{group, 'tlsv1'},
@@ -67,6 +69,7 @@ groups() ->
{options_tls, [], options_tests_tls()},
{'dtlsv1.2', [], all_versions_groups()},
{'dtlsv1', [], all_versions_groups()},
+ {'tlsv1.3', [], tls13_test_group()},
{'tlsv1.2', [], all_versions_groups() ++ tls_versions_groups() ++ [conf_signature_algs, no_common_signature_algs]},
{'tlsv1.1', [], all_versions_groups() ++ tls_versions_groups()},
{'tlsv1', [], all_versions_groups() ++ tls_versions_groups() ++ rizzo_tests()},
@@ -164,8 +167,10 @@ api_tests() ->
accept_pool,
prf,
socket_options,
+ active_n,
cipher_suites,
handshake_continue,
+ handshake_continue_timeout,
hello_client_cancel,
hello_server_cancel
].
@@ -242,6 +247,7 @@ error_handling_tests()->
[close_transport_accept,
recv_active,
recv_active_once,
+ recv_active_n,
recv_error_handling,
call_in_error_state,
close_in_error_state,
@@ -266,6 +272,17 @@ rizzo_tests() ->
rizzo_zero_n,
rizzo_disabled].
+%% For testing TLS 1.3 features and possible regressions
+tls13_test_group() ->
+ [tls13_enable_client_side,
+ tls13_enable_server_side,
+ tls_record_1_3_encode_decode,
+ tls13_finished_verify_data,
+ tls13_1_RTT_handshake,
+ tls13_basic_ssl_server_openssl_client,
+ tls13_custom_groups_ssl_server_openssl_client,
+ tls13_hello_retry_request_ssl_server_openssl_client].
+
%%--------------------------------------------------------------------
init_per_suite(Config0) ->
catch crypto:stop(),
@@ -295,7 +312,8 @@ init_per_group(GroupName, Config) when GroupName == basic_tls;
GroupName == options;
GroupName == basic;
GroupName == session;
- GroupName == error_handling_tests_tls
+ GroupName == error_handling_tests_tls;
+ GroupName == tls13_test_group
->
ssl_test_lib:clean_tls_version(Config);
init_per_group(GroupName, Config) ->
@@ -654,8 +672,8 @@ new_options_in_accept(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_verification_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_verification_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},
@@ -681,6 +699,34 @@ handshake_continue(Config) when is_list(Config) ->
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
+%%------------------------------------------------------------------
+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_verification_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {timeout, 1},
+ {options, ssl_test_lib:ssl_options([{reuseaddr, true}, {handshake, hello}],
+ Config)},
+ {continue_options, proplists:delete(reuseaddr, ServerOpts)}
+ ]),
+
+ Port = ssl_test_lib:inet_port(Server),
+
+
+ {connect_failed, _} = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {options, ClientOpts}]),
+
+ ssl_test_lib:check_result(Server, {error,timeout}),
+ ssl_test_lib:close(Server).
+
+
%%--------------------------------------------------------------------
hello_client_cancel() ->
[{doc, "Test API function ssl:handshake_cancel/1 on the client side"}].
@@ -702,19 +748,12 @@ hello_client_cancel(Config) when is_list(Config) ->
{from, self()},
{options, ssl_test_lib:ssl_options([{handshake, hello}], Config)},
{continue_options, cancel}]),
- receive
- {Server, {error, {tls_alert, "user canceled"}}} ->
- ok;
- {Server, {error, closed}} ->
- ct:pal("Did not receive the ALERT"),
- ok
- end.
-
+ 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_verification_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([{node, ServerNode}, {port, 0},
@@ -756,8 +795,8 @@ prf(Config) when is_list(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_verification_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_verification_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},
@@ -1183,9 +1222,8 @@ fallback(Config) when is_list(Config) ->
[{fallback, true},
{versions, ['tlsv1']}
| ClientOpts]}]),
-
- ssl_test_lib:check_result(Server, {error,{tls_alert,"inappropriate fallback"}},
- Client, {error,{tls_alert,"inappropriate fallback"}}).
+ ssl_test_lib:check_server_alert(Server, Client, inappropriate_fallback).
+
%%--------------------------------------------------------------------
cipher_format() ->
@@ -1446,8 +1484,8 @@ cipher_suites_mix() ->
cipher_suites_mix(Config) when is_list(Config) ->
CipherSuites = [{dhe_rsa,aes_128_cbc,sha256,sha256}, {dhe_rsa,aes_128_cbc,sha}],
- ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_verification_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),
@@ -1957,7 +1995,7 @@ recv_active(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
recv_active_once() ->
- [{doc,"Test recv on active socket"}].
+ [{doc,"Test recv on active (once) socket"}].
recv_active_once(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
@@ -1982,6 +2020,178 @@ recv_active_once(Config) when is_list(Config) ->
ssl_test_lib:close(Client).
%%--------------------------------------------------------------------
+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_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Server =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, try_recv_active_once, []}},
+ {options, [{active, 1} | ServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client =
+ ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, try_recv_active_once, []}},
+ {options, [{active, 1} | ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
+%%--------------------------------------------------------------------
+%% Test case adapted from gen_tcp_misc_SUITE.
+active_n() ->
+ [{doc,"Test {active,N} option"}].
+
+active_n(Config) when is_list(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
+ Port = ssl_test_lib:inet_port(node()),
+ N = 3,
+ LS = ok(ssl:listen(Port, [{active,N}|ServerOpts])),
+ [{active,N}] = ok(ssl:getopts(LS, [active])),
+ active_n_common(LS, N),
+ Self = self(),
+ spawn_link(fun() ->
+ S0 = ok(ssl:transport_accept(LS)),
+ {ok, S} = ssl:handshake(S0),
+ ok = ssl:setopts(S, [{active,N}]),
+ [{active,N}] = ok(ssl:getopts(S, [active])),
+ ssl:controlling_process(S, Self),
+ Self ! {server, S}
+ end),
+ C = ok(ssl:connect("localhost", Port, [{active,N}|ClientOpts])),
+ [{active,N}] = ok(ssl:getopts(C, [active])),
+ S = receive
+ {server, S0} -> S0
+ after
+ 1000 ->
+ exit({error, connect})
+ end,
+ active_n_common(C, N),
+ active_n_common(S, N),
+ ok = ssl:setopts(C, [{active,N}]),
+ ok = ssl:setopts(S, [{active,N}]),
+ ReceiveMsg = fun(Socket, Msg) ->
+ receive
+ {ssl,Socket,Msg} ->
+ ok;
+ {ssl,Socket,Begin} ->
+ receive
+ {ssl,Socket,End} ->
+ Msg = Begin ++ End,
+ ok
+ after 1000 ->
+ exit(timeout)
+ end
+ after 1000 ->
+ exit(timeout)
+ end
+ end,
+ repeat(3, fun(I) ->
+ Msg = "message "++integer_to_list(I),
+ ok = ssl:send(C, Msg),
+ ReceiveMsg(S, Msg),
+ ok = ssl:send(S, Msg),
+ ReceiveMsg(C, Msg)
+ end),
+ receive
+ {ssl_passive,S} ->
+ [{active,false}] = ok(ssl:getopts(S, [active]))
+ after
+ 1000 ->
+ exit({error,ssl_passive})
+ end,
+ receive
+ {ssl_passive,C} ->
+ [{active,false}] = ok(ssl:getopts(C, [active]))
+ after
+ 1000 ->
+ exit({error,ssl_passive})
+ end,
+ LS2 = ok(ssl:listen(0, [{active,0}])),
+ receive
+ {ssl_passive,LS2} ->
+ [{active,false}] = ok(ssl:getopts(LS2, [active]))
+ after
+ 1000 ->
+ exit({error,ssl_passive})
+ end,
+ ok = ssl:close(LS2),
+ ok = ssl:close(C),
+ ok = ssl:close(S),
+ ok = ssl:close(LS),
+ ok.
+
+active_n_common(S, N) ->
+ ok = ssl:setopts(S, [{active,-N}]),
+ receive
+ {ssl_passive, S} -> ok
+ after
+ 1000 ->
+ error({error,ssl_passive_failure})
+ end,
+ [{active,false}] = ok(ssl:getopts(S, [active])),
+ ok = ssl:setopts(S, [{active,0}]),
+ receive
+ {ssl_passive, S} -> ok
+ after
+ 1000 ->
+ error({error,ssl_passive_failure})
+ end,
+ ok = ssl:setopts(S, [{active,32767}]),
+ {error,{options,_}} = ssl:setopts(S, [{active,1}]),
+ {error,{options,_}} = ssl:setopts(S, [{active,-32769}]),
+ ok = ssl:setopts(S, [{active,-32768}]),
+ receive
+ {ssl_passive, S} -> ok
+ after
+ 1000 ->
+ error({error,ssl_passive_failure})
+ end,
+ [{active,false}] = ok(ssl:getopts(S, [active])),
+ ok = ssl:setopts(S, [{active,N}]),
+ ok = ssl:setopts(S, [{active,true}]),
+ [{active,true}] = ok(ssl:getopts(S, [active])),
+ receive
+ _ -> error({error,active_n})
+ after
+ 0 ->
+ ok
+ end,
+ ok = ssl:setopts(S, [{active,N}]),
+ ok = ssl:setopts(S, [{active,once}]),
+ [{active,once}] = ok(ssl:getopts(S, [active])),
+ receive
+ _ -> error({error,active_n})
+ after
+ 0 ->
+ ok
+ end,
+ {error,{options,_}} = ssl:setopts(S, [{active,32768}]),
+ ok = ssl:setopts(S, [{active,false}]),
+ [{active,false}] = ok(ssl:getopts(S, [active])),
+ ok.
+
+ok({ok,V}) -> V.
+
+repeat(N, Fun) ->
+ repeat(N, N, Fun).
+
+repeat(N, T, Fun) when is_integer(N), N > 0 ->
+ Fun(T-N),
+ repeat(N-1, T, Fun);
+repeat(_, _, _) ->
+ ok.
+
+%%--------------------------------------------------------------------
dh_params() ->
[{doc,"Test to specify DH-params file in server."}].
@@ -2358,8 +2568,8 @@ invalid_options() ->
[{doc,"Test what happens when we give invalid options"}].
invalid_options(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_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),
Check = fun(Client, Server, {versions, [sslv2, sslv3]} = Option) ->
@@ -2374,27 +2584,28 @@ invalid_options(Config) when is_list(Config) ->
{error, {options, Option}})
end,
- TestOpts = [{versions, [sslv2, sslv3]},
- {verify, 4},
- {verify_fun, function},
- {fail_if_no_peer_cert, 0},
- {verify_client_once, 1},
- {depth, four},
- {certfile, 'cert.pem'},
- {keyfile,'key.pem' },
- {password, foo},
- {cacertfile, ""},
- {dhfile,'dh.pem' },
- {ciphers, [{foo, bar, sha, ignore}]},
- {reuse_session, foo},
- {reuse_sessions, 0},
- {renegotiate_at, "10"},
- {mode, depech},
- {packet, 8.0},
- {packet_size, "2"},
- {header, a},
- {active, trice},
- {key, 'key.pem' }],
+ TestOpts =
+ [{versions, [sslv2, sslv3]},
+ {verify, 4},
+ {verify_fun, function},
+ {fail_if_no_peer_cert, 0},
+ {verify_client_once, 1},
+ {depth, four},
+ {certfile, 'cert.pem'},
+ {keyfile,'key.pem' },
+ {password, foo},
+ {cacertfile, ""},
+ {dhfile,'dh.pem' },
+ {ciphers, [{foo, bar, sha, ignore}]},
+ {reuse_session, foo},
+ {reuse_sessions, 0},
+ {renegotiate_at, "10"},
+ {mode, depech},
+ {packet, 8.0},
+ {packet_size, "2"},
+ {header, a},
+ {active, trice},
+ {key, 'key.pem' }],
[begin
Server =
@@ -2650,8 +2861,7 @@ default_reject_anonymous(Config) when is_list(Config) ->
[{ciphers,[CipherSuite]} |
ClientOpts]}]),
- ssl_test_lib:check_result(Server, {error, {tls_alert, "insufficient security"}},
- Client, {error, {tls_alert, "insufficient security"}}).
+ ssl_test_lib:check_server_alert(Server, Client, insufficient_security).
%%--------------------------------------------------------------------
ciphers_ecdsa_signed_certs() ->
@@ -2687,175 +2897,69 @@ ciphers_ecdh_rsa_signed_certs_openssl_names(Config) when is_list(Config) ->
reuse_session() ->
[{doc,"Test reuse of sessions (short handshake)"}].
reuse_session(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_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, session_info_result, []}},
- {options, ServerOpts}]),
- Port = ssl_test_lib:inet_port(Server),
- Client0 =
- ssl_test_lib:start_client([{node, ClientNode},
- {port, Port}, {host, Hostname},
- {mfa, {ssl_test_lib, no_result, []}},
- {from, self()}, {options, ClientOpts}]),
- SessionInfo =
- receive
- {Server, Info} ->
- Info
- end,
-
- Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}},
-
- %% Make sure session is registered
- ct:sleep(?SLEEP),
-
- Client1 =
- ssl_test_lib:start_client([{node, ClientNode},
- {port, Port}, {host, Hostname},
- {mfa, {ssl_test_lib, session_info_result, []}},
- {from, self()}, {options, ClientOpts}]),
- receive
- {Client1, SessionInfo} ->
- ok;
- {Client1, Other} ->
- ct:log("Expected: ~p, Unexpected: ~p~n",
- [SessionInfo, Other]),
- ct:fail(session_not_reused)
- end,
-
- Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}},
-
- Client2 =
- ssl_test_lib:start_client([{node, ClientNode},
- {port, Port}, {host, Hostname},
- {mfa, {ssl_test_lib, session_info_result, []}},
- {from, self()}, {options, [{reuse_sessions, false}
- | ClientOpts]}]),
- receive
- {Client2, SessionInfo} ->
- ct:fail(
- session_reused_when_session_reuse_disabled_by_client);
- {Client2, _} ->
- ok
- end,
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- ssl_test_lib:close(Server),
-
- Server1 =
- ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {mfa, {ssl_test_lib, session_info_result, []}},
- {options, [{reuse_sessions, false} | ServerOpts]}]),
-
- Port1 = ssl_test_lib:inet_port(Server1),
- Client3 =
- ssl_test_lib:start_client([{node, ClientNode},
- {port, Port1}, {host, Hostname},
- {mfa, {ssl_test_lib, no_result, []}},
- {from, self()}, {options, ClientOpts}]),
-
- SessionInfo1 =
- receive
- {Server1, Info1} ->
- Info1
- end,
-
- Server1 ! {listen, {mfa, {ssl_test_lib, no_result, []}}},
-
- %% Make sure session is registered
- ct:sleep(?SLEEP),
-
- Client4 =
- ssl_test_lib:start_client([{node, ClientNode},
- {port, Port1}, {host, Hostname},
- {mfa, {ssl_test_lib, session_info_result, []}},
- {from, self()}, {options, ClientOpts}]),
-
- receive
- {Client4, SessionInfo1} ->
- ct:fail(
- session_reused_when_session_reuse_disabled_by_server);
- {Client4, _Other} ->
- ct:log("OTHER: ~p ~n", [_Other]),
- ok
- end,
-
- ssl_test_lib:close(Server1),
- ssl_test_lib:close(Client0),
- ssl_test_lib:close(Client1),
- ssl_test_lib:close(Client2),
- ssl_test_lib:close(Client3),
- ssl_test_lib:close(Client4).
-
+ ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config).
%%--------------------------------------------------------------------
reuse_session_expired() ->
[{doc,"Test sessions is not reused when it has expired"}].
reuse_session_expired(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_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 =
- ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+
+ Server0 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
{from, self()},
- {mfa, {ssl_test_lib, session_info_result, []}},
- {options, ServerOpts}]),
- Port = ssl_test_lib:inet_port(Server),
- Client0 =
- ssl_test_lib:start_client([{node, ClientNode},
- {port, Port}, {host, Hostname},
- {mfa, {ssl_test_lib, no_result, []}},
- {from, self()}, {options, ClientOpts}]),
- SessionInfo =
- receive
- {Server, Info} ->
- Info
- end,
-
- Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {tcp_options, [{active, false}]},
+ {options, ServerOpts}]),
+ Port0 = ssl_test_lib:inet_port(Server0),
- %% Make sure session is registered
- ct:sleep(?SLEEP),
-
- Client1 =
- ssl_test_lib:start_client([{node, ClientNode},
- {port, Port}, {host, Hostname},
- {mfa, {ssl_test_lib, session_info_result, []}},
- {from, self()}, {options, ClientOpts}]),
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
+ {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]}]),
+ Server0 ! listen,
+
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
+ {from, self()}, {options, ClientOpts}]),
+
+ SID = receive
+ {Client0, Id0} ->
+ Id0
+ end,
+
receive
- {Client1, SessionInfo} ->
- ok;
- {Client1, Other} ->
- ct:log("Expected: ~p, Unexpected: ~p~n",
- [SessionInfo, Other]),
- ct:fail(session_not_reused)
+ {Client1, SID} ->
+ ok
+ after ?SLEEP ->
+ ct:fail(session_not_reused)
end,
- Server ! listen,
-
+ Server0 ! listen,
+
%% Make sure session is unregistered due to expiration
- ct:sleep((?EXPIRE+1)),
- [{session_id, Id} |_] = SessionInfo,
+ ct:sleep((?EXPIRE*2)),
- make_sure_expired(Hostname, Port, Id),
+ make_sure_expired(Hostname, Port0, SID),
Client2 =
ssl_test_lib:start_client([{node, ClientNode},
- {port, Port}, {host, Hostname},
- {mfa, {ssl_test_lib, session_info_result, []}},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
{from, self()}, {options, ClientOpts}]),
receive
- {Client2, SessionInfo} ->
+ {Client2, SID} ->
ct:fail(session_reused_when_session_expired);
{Client2, _} ->
ok
end,
process_flag(trap_exit, false),
- ssl_test_lib:close(Server),
+ ssl_test_lib:close(Server0),
ssl_test_lib:close(Client0),
ssl_test_lib:close(Client1),
ssl_test_lib:close(Client2).
@@ -2864,16 +2968,16 @@ make_sure_expired(Host, Port, Id) ->
{status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)),
[_, _,_, _, Prop] = StatusInfo,
State = ssl_test_lib:state(Prop),
- Cache = element(2, State),
+ ClientCache = element(2, State),
- case ssl_session_cache:lookup(Cache, {{Host, Port}, Id}) of
+ case ssl_session_cache:lookup(ClientCache, {{Host, Port}, Id}) of
undefined ->
- ok;
+ ok;
#session{is_resumable = false} ->
- ok;
+ ok;
_ ->
ct:sleep(?SLEEP),
- make_sure_expired(Host, Port, Id)
+ make_sure_expired(Host, Port, Id)
end.
%%--------------------------------------------------------------------
@@ -3526,7 +3630,7 @@ honor_cipher_order(Config, Honor, ServerCiphers, ClientCiphers, Expected) ->
%%--------------------------------------------------------------------
tls_ciphersuite_vs_version() ->
- [{doc,"Test a SSLv3 client can not negotiate a TLSv* cipher suite."}].
+ [{doc,"Test a SSLv3 client cannot negotiate a TLSv* cipher suite."}].
tls_ciphersuite_vs_version(Config) when is_list(Config) ->
{_ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -3569,14 +3673,14 @@ conf_signature_algs(Config) when is_list(Config) ->
ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
{from, self()},
{mfa, {ssl_test_lib, send_recv_result, []}},
- {options, [{active, false}, {signature_algs, [{sha256, rsa}]} | ServerOpts]}]),
+ {options, [{active, false}, {signature_algs, [{sha, rsa}]} | 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, [{active, false}, {signature_algs, [{sha256, rsa}]} | ClientOpts]}]),
+ {options, [{active, false}, {signature_algs, [{sha, rsa}]} | ClientOpts]}]),
ct:log("Testcase ~p, Client ~p Server ~p ~n",
[self(), Client, Server]),
@@ -3609,8 +3713,7 @@ no_common_signature_algs(Config) when is_list(Config) ->
{options, [{signature_algs, [{sha384, rsa}]}
| ClientOpts]}]),
- ssl_test_lib:check_result(Server, {error, {tls_alert, "insufficient security"}},
- Client, {error, {tls_alert, "insufficient security"}}).
+ ssl_test_lib:check_server_alert(Server, Client, insufficient_security).
%%--------------------------------------------------------------------
@@ -3641,7 +3744,7 @@ tls_dont_crash_on_handshake_garbage(Config) ->
<<22, 3,3, 5:16, 92,64,37,228,209>> % garbage
]),
% Send unexpected change_cipher_spec
- ok = gen_tcp:send(Socket, <<20, 0,0,12, 111,40,244,7,137,224,16,109,197,110,249,152>>),
+ ok = gen_tcp:send(Socket, <<20, 3,3, 12:16, 111,40,244,7,137,224,16,109,197,110,249,152>>),
% Ensure we receive an alert, not sudden disconnect
{ok, <<21, _/binary>>} = drop_handshakes(Socket, 1000).
@@ -4169,6 +4272,9 @@ rizzo_one_n_minus_one(Config) when is_list(Config) ->
{cipher,
fun(rc4_128) ->
false;
+ %% TODO: remove this clause when chacha is fixed!
+ (chacha20_poly1305) ->
+ false;
(_) ->
true
end}]),
@@ -4310,8 +4416,7 @@ tls_versions_option(Config) when is_list(Config) ->
{Server, _} ->
ok
end,
-
- ssl_test_lib:check_result(ErrClient, {error, {tls_alert, "protocol version"}}).
+ ssl_test_lib:check_client_alert(ErrClient, protocol_version).
%%--------------------------------------------------------------------
@@ -4472,6 +4577,1017 @@ accept_pool(Config) when is_list(Config) ->
ssl_test_lib:close(Client1),
ssl_test_lib:close(Client2).
+%%--------------------------------------------------------------------
+%% TLS 1.3
+%%--------------------------------------------------------------------
+
+tls13_enable_client_side() ->
+ [{doc,"Test that a TLS 1.3 client can connect to a TLS 1.2 server."}].
+
+tls13_enable_client_side(Config) when is_list(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, protocol_info_result, []}},
+ {options, [{versions,
+ ['tlsv1.1', 'tlsv1.2']} | ServerOpts] }]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, protocol_info_result, []}},
+ {options, [{versions,
+ ['tlsv1.2', 'tlsv1.3']} | ClientOpts]}]),
+
+ ServerMsg = ClientMsg = {ok, 'tlsv1.2'},
+ ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg).
+
+tls13_enable_server_side() ->
+ [{doc,"Test that a TLS 1.2 client can connect to a TLS 1.3 server."}].
+
+tls13_enable_server_side(Config) when is_list(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, protocol_info_result, []}},
+ {options, [{versions,
+ ['tlsv1.2', 'tlsv1.3']} | ServerOpts] }]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, protocol_info_result, []}},
+ {options, [{versions,
+ ['tlsv1.2', 'tlsv1.1']} | ClientOpts]}]),
+
+ ServerMsg = ClientMsg = {ok, 'tlsv1.2'},
+ ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg).
+
+tls_record_1_3_encode_decode() ->
+ [{doc,"Test TLS 1.3 record encode/decode functions"}].
+
+tls_record_1_3_encode_decode(_Config) ->
+ ConnectionStates =
+ #{current_read =>
+ #{beast_mitigation => one_n_minus_one,
+ cipher_state =>
+ {cipher_state,
+ <<14,172,111,243,199,170,242,203,126,205,34,93,122,115,226,14,
+ 15,117,155,48,24,112,61,15,113,208,127,51,179,227,194,232>>,
+ <<197,54,168,218,54,91,157,58,30,201,197,142,51,58,53,231,228,
+ 131,57,122,170,78,82,196,30,48,23,16,95,255,185,236>>,
+ undefined,undefined,undefined,16},
+ client_verify_data => undefined,compression_state => undefined,
+ mac_secret => undefined,secure_renegotiation => undefined,
+ security_parameters =>
+ {security_parameters,
+ <<19,2>>,
+ 0,8,2,undefined,undefined,undefined,undefined,undefined,
+ sha384,undefined,undefined,
+ {handshake_secret,
+ <<128,229,186,211,62,127,182,20,62,166,233,23,135,64,121,
+ 3,104,251,214,161,253,31,3,2,232,37,8,221,189,72,64,218,
+ 121,41,112,148,254,34,68,164,228,60,161,201,132,55,56,
+ 157>>},
+ undefined,
+ <<92,24,205,75,244,60,136,212,250,32,214,20,37,3,213,87,61,207,
+ 147,61,168,145,177,118,160,153,33,53,48,108,191,174>>,
+ undefined},
+ sequence_number => 0,server_verify_data => undefined},
+ current_write =>
+ #{beast_mitigation => one_n_minus_one,
+ cipher_state =>
+ {cipher_state,
+ <<14,172,111,243,199,170,242,203,126,205,34,93,122,115,226,14,
+ 15,117,155,48,24,112,61,15,113,208,127,51,179,227,194,232>>,
+ <<197,54,168,218,54,91,157,58,30,201,197,142,51,58,53,231,228,
+ 131,57,122,170,78,82,196,30,48,23,16,95,255,185,236>>,
+ undefined,undefined,undefined,16},
+ client_verify_data => undefined,compression_state => undefined,
+ mac_secret => undefined,secure_renegotiation => undefined,
+ security_parameters =>
+ {security_parameters,
+ <<19,2>>,
+ 0,8,2,undefined,undefined,undefined,undefined,undefined,
+ sha384,undefined,undefined,
+ {handshake_secret,
+ <<128,229,186,211,62,127,182,20,62,166,233,23,135,64,121,
+ 3,104,251,214,161,253,31,3,2,232,37,8,221,189,72,64,218,
+ 121,41,112,148,254,34,68,164,228,60,161,201,132,55,56,
+ 157>>},
+ undefined,
+ <<92,24,205,75,244,60,136,212,250,32,214,20,37,3,213,87,61,207,
+ 147,61,168,145,177,118,160,153,33,53,48,108,191,174>>,
+ undefined},
+ sequence_number => 0,server_verify_data => undefined}},
+
+ PlainText = [11,
+ <<0,2,175>>,
+ <<0,0,2,171,0,2,166,48,130,2,162,48,130,1,138,2,9,0,186,57,220,137,88,255,
+ 191,235,48,13,6,9,42,134,72,134,247,13,1,1,11,5,0,48,18,49,16,48,14,6,3,85,
+ 4,3,12,7,84,101,115,116,32,67,65,48,30,23,13,49,56,48,53,48,52,49,52,49,50,
+ 51,56,90,23,13,50,56,48,50,48,52,49,52,49,50,51,56,90,48,20,49,18,48,16,6,
+ 3,85,4,3,12,9,108,111,99,97,108,104,111,115,116,48,130,1,34,48,13,6,9,42,
+ 134,72,134,247,13,1,1,1,5,0,3,130,1,15,0,48,130,1,10,2,130,1,1,0,169,40,
+ 144,176,121,63,134,97,144,126,243,183,225,157,37,131,183,225,87,243,23,88,
+ 230,70,9,134,32,147,7,27,167,98,51,81,224,75,199,12,229,251,195,207,75,179,
+ 181,78,128,3,255,44,58,39,43,172,142,45,186,58,51,65,187,199,154,153,245,
+ 70,133,137,1,27,87,42,116,65,251,129,109,145,233,97,171,71,54,213,185,74,
+ 209,166,11,218,189,119,206,86,170,60,212,213,85,189,30,50,215,23,185,53,
+ 132,238,132,176,198,250,139,251,198,221,225,128,109,113,23,220,39,143,71,
+ 30,59,189,51,244,61,158,214,146,180,196,103,169,189,221,136,78,129,216,148,
+ 2,9,8,65,37,224,215,233,13,209,21,235,20,143,33,74,59,53,208,90,152,94,251,
+ 54,114,171,39,88,230,227,158,211,135,37,182,67,205,161,59,20,138,58,253,15,
+ 53,48,8,157,9,95,197,9,177,116,21,54,9,125,78,109,182,83,20,16,234,223,116,
+ 41,155,123,87,77,17,120,153,246,239,124,130,105,219,166,146,242,151,66,198,
+ 75,72,63,28,246,86,16,244,223,22,36,50,15,247,222,98,6,152,136,154,72,150,
+ 73,127,2,3,1,0,1,48,13,6,9,42,134,72,134,247,13,1,1,11,5,0,3,130,1,1,0,76,
+ 33,54,160,229,219,219,193,150,116,245,252,18,39,235,145,86,12,167,171,52,
+ 117,166,30,83,5,216,245,177,217,247,95,1,136,94,246,212,108,248,230,111,
+ 225,202,189,6,129,8,70,128,245,18,204,215,87,82,129,253,227,122,66,182,184,
+ 189,30,193,169,144,218,216,109,105,110,215,144,60,104,162,178,101,164,218,
+ 122,60,37,41,143,57,150,52,59,51,112,238,113,239,168,114,69,183,143,154,73,
+ 61,58,80,247,172,95,251,55,28,186,28,200,206,230,118,243,92,202,189,49,76,
+ 124,252,76,0,247,112,85,194,69,59,222,163,228,103,49,110,104,109,251,155,
+ 138,9,37,167,49,189,48,134,52,158,185,129,24,96,153,196,251,90,206,76,239,
+ 175,119,174,165,133,108,222,125,237,125,187,149,152,83,190,16,202,94,202,
+ 201,40,218,22,254,63,189,41,174,97,140,203,70,18,196,118,237,175,134,79,78,
+ 246,2,61,54,77,186,112,32,17,193,192,188,217,252,215,200,7,245,180,179,132,
+ 183,212,229,155,15,152,206,135,56,81,88,3,123,244,149,110,182,72,109,70,62,
+ 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},
+
+ {#ssl_tls{type = 22, version = {3,4}, fragment = DecodedText}, _} =
+ tls_record_1_3:decode_cipher_text(CipherText, ConnectionStates),
+
+ DecodedText = iolist_to_binary(PlainText),
+ ct:log("Decoded: ~p ~n", [DecodedText]),
+ ok.
+
+tls13_1_RTT_handshake() ->
+ [{doc,"Test TLS 1.3 1-RTT Handshake"}].
+
+tls13_1_RTT_handshake(_Config) ->
+ %% ConnectionStates with NULL cipher
+ ConnStatesNull =
+ #{current_write =>
+ #{security_parameters =>
+ #security_parameters{cipher_suite = ?TLS_NULL_WITH_NULL_NULL},
+ sequence_number => 0
+ }
+ },
+
+ %% {client} construct a ClientHello handshake message:
+ %%
+ %% ClientHello (196 octets): 01 00 00 c0 03 03 cb 34 ec b1 e7 81 63
+ %% ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 ec 18 a2 ef 62 83
+ %% 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 00 91 00 00 00 0b
+ %% 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00
+ %% 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 23
+ %% 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d e5 60 e4 bd 43 d2
+ %% 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a
+ %% af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03
+ %% 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06
+ %% 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01
+ %%
+ %% {client} send handshake record:
+ %%
+ %% payload (196 octets): 01 00 00 c0 03 03 cb 34 ec b1 e7 81 63 ba
+ %% 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 ec 18 a2 ef 62 83 02
+ %% 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 00 91 00 00 00 0b 00
+ %% 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00 12
+ %% 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 23 00
+ %% 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d e5 60 e4 bd 43 d2 3d
+ %% 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a af
+ %% 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03 02
+ %% 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06 02
+ %% 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01
+ %%
+ %% complete record (201 octets): 16 03 01 00 c4 01 00 00 c0 03 03 cb
+ %% 34 ec b1 e7 81 63 ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12
+ %% ec 18 a2 ef 62 83 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00
+ %% 00 91 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01
+ %% 00 00 0a 00 14 00 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02
+ %% 01 03 01 04 00 23 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d
+ %% e5 60 e4 bd 43 d2 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d
+ %% 54 13 69 1e 52 9a af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e
+ %% 04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02
+ %% 01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01
+ ClientHello =
+ hexstr2bin("01 00 00 c0 03 03 cb 34 ec b1 e7 81 63
+ ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 ec 18 a2 ef 62 83
+ 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 00 91 00 00 00 0b
+ 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00
+ 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 23
+ 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d e5 60 e4 bd 43 d2
+ 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a
+ af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03
+ 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06
+ 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01"),
+
+ ClientHelloRecord =
+ %% Current implementation always sets
+ %% legacy_record_version to Ox0303
+ hexstr2bin("16 03 03 00 c4 01 00 00 c0 03 03 cb
+ 34 ec b1 e7 81 63 ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12
+ ec 18 a2 ef 62 83 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00
+ 00 91 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01
+ 00 00 0a 00 14 00 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02
+ 01 03 01 04 00 23 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d
+ e5 60 e4 bd 43 d2 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d
+ 54 13 69 1e 52 9a af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e
+ 04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02
+ 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),
+ ClientHelloRecord = iolist_to_binary(CHEncrypted),
+
+ %% {server} extract secret "early":
+ %%
+ %% salt: 0 (all zero octets)
+ %%
+ %% IKM (32 octets): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ %% 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ %%
+ %% secret (32 octets): 33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c
+ %% e2 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a
+ HKDFAlgo = sha256,
+ Salt = binary:copy(<<?BYTE(0)>>, 32),
+ IKM = binary:copy(<<?BYTE(0)>>, 32),
+ EarlySecret =
+ hexstr2bin("33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c
+ e2 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a"),
+
+ {early_secret, EarlySecret} = tls_v1:key_schedule(early_secret, HKDFAlgo, {psk, Salt}),
+
+ %% {client} create an ephemeral x25519 key pair:
+ %%
+ %% private key (32 octets): 49 af 42 ba 7f 79 94 85 2d 71 3e f2 78
+ %% 4b cb ca a7 91 1d e2 6a dc 56 42 cb 63 45 40 e7 ea 50 05
+ %%
+ %% public key (32 octets): 99 38 1d e5 60 e4 bd 43 d2 3d 8e 43 5a 7d
+ %% ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a af 2c
+ CPublicKey =
+ hexstr2bin("99 38 1d e5 60 e4 bd 43 d2 3d 8e 43 5a 7d
+ ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a af 2c"),
+
+ %% {server} create an ephemeral x25519 key pair:
+ %%
+ %% private key (32 octets): b1 58 0e ea df 6d d5 89 b8 ef 4f 2d 56
+ %% 52 57 8c c8 10 e9 98 01 91 ec 8d 05 83 08 ce a2 16 a2 1e
+ %%
+ %% public key (32 octets): c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6
+ %% 72 e1 56 d6 cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f
+ SPrivateKey =
+ hexstr2bin("b1 58 0e ea df 6d d5 89 b8 ef 4f 2d 56
+ 52 57 8c c8 10 e9 98 01 91 ec 8d 05 83 08 ce a2 16 a2 1e"),
+
+ SPublicKey =
+ hexstr2bin("c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6
+ 72 e1 56 d6 cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f"),
+
+ %% {server} construct a ServerHello handshake message:
+ %%
+ %% ServerHello (90 octets): 02 00 00 56 03 03 a6 af 06 a4 12 18 60
+ %% dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 34 da c1 55 77 2e
+ %% d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00 1d 00 20 c9 82 88
+ %% 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 cc 25 3b 83 3d f1
+ %% dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04
+ ServerHello =
+ hexstr2bin("02 00 00 56 03 03 a6 af 06 a4 12 18 60
+ dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 34 da c1 55 77 2e
+ d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00 1d 00 20 c9 82 88
+ 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 cc 25 3b 83 3d f1
+ dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04"),
+
+ %% {server} derive secret for handshake "tls13 derived":
+ %%
+ %% PRK (32 octets): 33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c e2
+ %% 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a
+ %%
+ %% hash (32 octets): e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24
+ %% 27 ae 41 e4 64 9b 93 4c a4 95 99 1b 78 52 b8 55
+ %%
+ %% info (49 octets): 00 20 0d 74 6c 73 31 33 20 64 65 72 69 76 65 64
+ %% 20 e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 27 ae 41 e4
+ %% 64 9b 93 4c a4 95 99 1b 78 52 b8 55
+ %%
+ %% expanded (32 octets): 6f 26 15 a1 08 c7 02 c5 67 8f 54 fc 9d ba
+ %% b6 97 16 c0 76 18 9c 48 25 0c eb ea c3 57 6c 36 11 ba
+ Hash =
+ hexstr2bin("e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24
+ 27 ae 41 e4 64 9b 93 4c a4 95 99 1b 78 52 b8 55"),
+
+ Hash = crypto:hash(HKDFAlgo, <<>>),
+
+ Info =
+ hexstr2bin("00 20 0d 74 6c 73 31 33 20 64 65 72 69 76 65 64
+ 20 e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 27 ae 41 e4
+ 64 9b 93 4c a4 95 99 1b 78 52 b8 55"),
+
+ Info = tls_v1:create_info(<<"derived">>, Hash, ssl_cipher:hash_size(HKDFAlgo)),
+
+ Expanded =
+ hexstr2bin("6f 26 15 a1 08 c7 02 c5 67 8f 54 fc 9d ba
+ b6 97 16 c0 76 18 9c 48 25 0c eb ea c3 57 6c 36 11 ba"),
+
+ Expanded = tls_v1:derive_secret(EarlySecret, <<"derived">>, <<>>, HKDFAlgo),
+
+ %% {server} extract secret "handshake":
+ %%
+ %% salt (32 octets): 6f 26 15 a1 08 c7 02 c5 67 8f 54 fc 9d ba b6 97
+ %% 16 c0 76 18 9c 48 25 0c eb ea c3 57 6c 36 11 ba
+ %%
+ %% IKM (32 octets): 8b d4 05 4f b5 5b 9d 63 fd fb ac f9 f0 4b 9f 0d
+ %% 35 e6 d6 3f 53 75 63 ef d4 62 72 90 0f 89 49 2d
+ %%
+ %% secret (32 octets): 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b
+ %% 01 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac
+
+ %% salt = Expanded
+ HandshakeIKM =
+ hexstr2bin("8b d4 05 4f b5 5b 9d 63 fd fb ac f9 f0 4b 9f 0d
+ 35 e6 d6 3f 53 75 63 ef d4 62 72 90 0f 89 49 2d"),
+
+ HandshakeSecret =
+ hexstr2bin("1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b
+ 01 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac"),
+
+ HandshakeIKM = crypto:compute_key(ecdh, CPublicKey, SPrivateKey, x25519),
+
+ {handshake_secret, HandshakeSecret} =
+ tls_v1:key_schedule(handshake_secret, HKDFAlgo, HandshakeIKM,
+ {early_secret, EarlySecret}),
+
+ %% {server} derive secret "tls13 c hs traffic":
+ %%
+ %% PRK (32 octets): 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b 01
+ %% 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac
+ %%
+ %% hash (32 octets): 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 ed
+ %% d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8
+ %%
+ %% info (54 octets): 00 20 12 74 6c 73 31 33 20 63 20 68 73 20 74 72
+ %% 61 66 66 69 63 20 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58
+ %% ed d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8
+ %%
+ %% expanded (32 octets): b3 ed db 12 6e 06 7f 35 a7 80 b3 ab f4 5e
+ %% 2d 8f 3b 1a 95 07 38 f5 2e 96 00 74 6a 0e 27 a5 5a 21
+
+ %% PRK = HandshakeSecret
+ CHSTHash =
+ hexstr2bin("86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 ed
+ d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8"),
+
+ CHSTInfo =
+ hexstr2bin("00 20 12 74 6c 73 31 33 20 63 20 68 73 20 74 72
+ 61 66 66 69 63 20 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58
+ ed d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8"),
+
+ CHSTrafficSecret =
+ hexstr2bin(" b3 ed db 12 6e 06 7f 35 a7 80 b3 ab f4 5e
+ 2d 8f 3b 1a 95 07 38 f5 2e 96 00 74 6a 0e 27 a5 5a 21"),
+
+ CHSH = <<ClientHello/binary,ServerHello/binary>>,
+ CHSTHash = crypto:hash(HKDFAlgo, CHSH),
+ CHSTInfo = tls_v1:create_info(<<"c hs traffic">>, CHSTHash, ssl_cipher:hash_size(HKDFAlgo)),
+
+ CHSTrafficSecret =
+ tls_v1:client_handshake_traffic_secret(HKDFAlgo, {handshake_secret, HandshakeSecret}, CHSH),
+
+ %% {server} derive secret "tls13 s hs traffic":
+ %%
+ %% PRK (32 octets): 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b 01
+ %% 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac
+ %%
+ %% hash (32 octets): 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 ed
+ %% d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8
+ %%
+ %% info (54 octets): 00 20 12 74 6c 73 31 33 20 73 20 68 73 20 74 72
+ %% 61 66 66 69 63 20 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58
+ %% ed d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8
+ %%
+ %% expanded (32 octets): b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d
+ %% 37 b4 e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38
+
+ %% PRK = HandshakeSecret
+ %% hash = CHSTHash
+ SHSTInfo =
+ hexstr2bin("00 20 12 74 6c 73 31 33 20 73 20 68 73 20 74 72
+ 61 66 66 69 63 20 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58
+ ed d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8"),
+
+ SHSTrafficSecret =
+ hexstr2bin("b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d
+ 37 b4 e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38"),
+
+ SHSTInfo = tls_v1:create_info(<<"s hs traffic">>, CHSTHash, ssl_cipher:hash_size(HKDFAlgo)),
+
+ SHSTrafficSecret =
+ tls_v1:server_handshake_traffic_secret(HKDFAlgo, {handshake_secret, HandshakeSecret}, CHSH),
+
+
+ %% {server} derive secret for master "tls13 derived":
+ %%
+ %% PRK (32 octets): 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b 01
+ %% 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac
+ %%
+ %% hash (32 octets): e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24
+ %% 27 ae 41 e4 64 9b 93 4c a4 95 99 1b 78 52 b8 55
+ %%
+ %% info (49 octets): 00 20 0d 74 6c 73 31 33 20 64 65 72 69 76 65 64
+ %% 20 e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 27 ae 41 e4
+ %% 64 9b 93 4c a4 95 99 1b 78 52 b8 55
+ %%
+ %% expanded (32 octets): 43 de 77 e0 c7 77 13 85 9a 94 4d b9 db 25
+ %% 90 b5 31 90 a6 5b 3e e2 e4 f1 2d d7 a0 bb 7c e2 54 b4
+
+ %% PRK = HandshakeSecret
+ %% hash = Hash
+ %% info = Info
+ MasterDeriveSecret =
+ hexstr2bin("43 de 77 e0 c7 77 13 85 9a 94 4d b9 db 25
+ 90 b5 31 90 a6 5b 3e e2 e4 f1 2d d7 a0 bb 7c e2 54 b4"),
+
+ MasterDeriveSecret = tls_v1:derive_secret(HandshakeSecret, <<"derived">>, <<>>, HKDFAlgo),
+
+ %% {server} extract secret "master":
+ %%
+ %% salt (32 octets): 43 de 77 e0 c7 77 13 85 9a 94 4d b9 db 25 90 b5
+ %% 31 90 a6 5b 3e e2 e4 f1 2d d7 a0 bb 7c e2 54 b4
+ %%
+ %% IKM (32 octets): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ %% 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ %%
+ %% secret (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a
+ %% 47 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19
+
+ %% salt = MasterDeriveSecret
+ %% IKM = IKM
+ MasterSecret =
+ hexstr2bin("18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a
+ 47 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19"),
+
+ {master_secret, MasterSecret} =
+ tls_v1:key_schedule(master_secret, HKDFAlgo, {handshake_secret, HandshakeSecret}),
+
+ %% {server} send handshake record:
+ %%
+ %% payload (90 octets): 02 00 00 56 03 03 a6 af 06 a4 12 18 60 dc 5e
+ %% 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 34 da c1 55 77 2e d3 e2
+ %% 69 28 00 13 01 00 00 2e 00 33 00 24 00 1d 00 20 c9 82 88 76 11
+ %% 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 cc 25 3b 83 3d f1 dd 69
+ %% b1 b0 4e 75 1f 0f 00 2b 00 02 03 04
+ %%
+ %% complete record (95 octets): 16 03 03 00 5a 02 00 00 56 03 03 a6
+ %% af 06 a4 12 18 60 dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14
+ %% 34 da c1 55 77 2e d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00
+ %% 1d 00 20 c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6
+ %% cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04
+
+ %% payload = ServerHello
+ ServerHelloRecord =
+ hexstr2bin("16 03 03 00 5a 02 00 00 56 03 03 a6
+ af 06 a4 12 18 60 dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14
+ 34 da c1 55 77 2e d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00
+ 1d 00 20 c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6
+ 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),
+ ServerHelloRecord = iolist_to_binary(SHEncrypted),
+
+ %% {server} derive write traffic keys for handshake data:
+ %%
+ %% PRK (32 octets): b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d 37 b4
+ %% e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38
+ %%
+ %% key info (13 octets): 00 10 09 74 6c 73 31 33 20 6b 65 79 00
+ %%
+ %% key expanded (16 octets): 3f ce 51 60 09 c2 17 27 d0 f2 e4 e8 6e
+ %% e4 03 bc
+ %%
+ %% iv info (12 octets): 00 0c 08 74 6c 73 31 33 20 69 76 00
+ %%
+ %% iv expanded (12 octets): 5d 31 3e b2 67 12 76 ee 13 00 0b 30
+
+ %% PRK = SHSTrafficSecret
+ WriteKeyInfo =
+ hexstr2bin("00 10 09 74 6c 73 31 33 20 6b 65 79 00"),
+
+ WriteKey =
+ hexstr2bin("3f ce 51 60 09 c2 17 27 d0 f2 e4 e8 6e e4 03 bc"),
+
+ WriteIVInfo =
+ hexstr2bin("00 0c 08 74 6c 73 31 33 20 69 76 00"),
+
+ WriteIV =
+ hexstr2bin(" 5d 31 3e b2 67 12 76 ee 13 00 0b 30"),
+
+ Cipher = aes_128_gcm, %% TODO: get from ServerHello
+
+ WriteKeyInfo = tls_v1:create_info(<<"key">>, <<>>, ssl_cipher:key_material(Cipher)),
+ %% TODO: remove hardcoded IV size
+ WriteIVInfo = tls_v1:create_info(<<"iv">>, <<>>, 12),
+
+ {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, SHSTrafficSecret),
+
+ %% {server} construct an EncryptedExtensions handshake message:
+ %%
+ %% EncryptedExtensions (40 octets): 08 00 00 24 00 22 00 0a 00 14 00
+ %% 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 1c
+ %% 00 02 40 01 00 00 00 00
+ %%
+ %% {server} construct a Certificate handshake message:
+ %%
+ %% Certificate (445 octets): 0b 00 01 b9 00 00 01 b5 00 01 b0 30 82
+ %% 01 ac 30 82 01 15 a0 03 02 01 02 02 01 02 30 0d 06 09 2a 86 48
+ %% 86 f7 0d 01 01 0b 05 00 30 0e 31 0c 30 0a 06 03 55 04 03 13 03
+ %% 72 73 61 30 1e 17 0d 31 36 30 37 33 30 30 31 32 33 35 39 5a 17
+ %% 0d 32 36 30 37 33 30 30 31 32 33 35 39 5a 30 0e 31 0c 30 0a 06
+ %% 03 55 04 03 13 03 72 73 61 30 81 9f 30 0d 06 09 2a 86 48 86 f7
+ %% 0d 01 01 01 05 00 03 81 8d 00 30 81 89 02 81 81 00 b4 bb 49 8f
+ %% 82 79 30 3d 98 08 36 39 9b 36 c6 98 8c 0c 68 de 55 e1 bd b8 26
+ %% d3 90 1a 24 61 ea fd 2d e4 9a 91 d0 15 ab bc 9a 95 13 7a ce 6c
+ %% 1a f1 9e aa 6a f9 8c 7c ed 43 12 09 98 e1 87 a8 0e e0 cc b0 52
+ %% 4b 1b 01 8c 3e 0b 63 26 4d 44 9a 6d 38 e2 2a 5f da 43 08 46 74
+ %% 80 30 53 0e f0 46 1c 8c a9 d9 ef bf ae 8e a6 d1 d0 3e 2b d1 93
+ %% ef f0 ab 9a 80 02 c4 74 28 a6 d3 5a 8d 88 d7 9f 7f 1e 3f 02 03
+ %% 01 00 01 a3 1a 30 18 30 09 06 03 55 1d 13 04 02 30 00 30 0b 06
+ %% 03 55 1d 0f 04 04 03 02 05 a0 30 0d 06 09 2a 86 48 86 f7 0d 01
+ %% 01 0b 05 00 03 81 81 00 85 aa d2 a0 e5 b9 27 6b 90 8c 65 f7 3a
+ %% 72 67 17 06 18 a5 4c 5f 8a 7b 33 7d 2d f7 a5 94 36 54 17 f2 ea
+ %% e8 f8 a5 8c 8f 81 72 f9 31 9c f3 6b 7f d6 c5 5b 80 f2 1a 03 01
+ %% 51 56 72 60 96 fd 33 5e 5e 67 f2 db f1 02 70 2e 60 8c ca e6 be
+ %% c1 fc 63 a4 2a 99 be 5c 3e b7 10 7c 3c 54 e9 b9 eb 2b d5 20 3b
+ %% 1c 3b 84 e0 a8 b2 f7 59 40 9b a3 ea c9 d9 1d 40 2d cc 0c c8 f8
+ %% 96 12 29 ac 91 87 b4 2b 4d e1 00 00
+ %%
+ %% {server} construct a CertificateVerify handshake message:
+ %%
+ %% CertificateVerify (136 octets): 0f 00 00 84 08 04 00 80 5a 74 7c
+ %% 5d 88 fa 9b d2 e5 5a b0 85 a6 10 15 b7 21 1f 82 4c d4 84 14 5a
+ %% b3 ff 52 f1 fd a8 47 7b 0b 7a bc 90 db 78 e2 d3 3a 5c 14 1a 07
+ %% 86 53 fa 6b ef 78 0c 5e a2 48 ee aa a7 85 c4 f3 94 ca b6 d3 0b
+ %% be 8d 48 59 ee 51 1f 60 29 57 b1 54 11 ac 02 76 71 45 9e 46 44
+ %% 5c 9e a5 8c 18 1e 81 8e 95 b8 c3 fb 0b f3 27 84 09 d3 be 15 2a
+ %% 3d a5 04 3e 06 3d da 65 cd f5 ae a2 0d 53 df ac d4 2f 74 f3
+ EncryptedExtensions =
+ hexstr2bin("08 00 00 24 00 22 00 0a 00 14 00
+ 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 1c
+ 00 02 40 01 00 00 00 00"),
+
+ Certificate =
+ hexstr2bin("0b 00 01 b9 00 00 01 b5 00 01 b0 30 82
+ 01 ac 30 82 01 15 a0 03 02 01 02 02 01 02 30 0d 06 09 2a 86 48
+ 86 f7 0d 01 01 0b 05 00 30 0e 31 0c 30 0a 06 03 55 04 03 13 03
+ 72 73 61 30 1e 17 0d 31 36 30 37 33 30 30 31 32 33 35 39 5a 17
+ 0d 32 36 30 37 33 30 30 31 32 33 35 39 5a 30 0e 31 0c 30 0a 06
+ 03 55 04 03 13 03 72 73 61 30 81 9f 30 0d 06 09 2a 86 48 86 f7
+ 0d 01 01 01 05 00 03 81 8d 00 30 81 89 02 81 81 00 b4 bb 49 8f
+ 82 79 30 3d 98 08 36 39 9b 36 c6 98 8c 0c 68 de 55 e1 bd b8 26
+ d3 90 1a 24 61 ea fd 2d e4 9a 91 d0 15 ab bc 9a 95 13 7a ce 6c
+ 1a f1 9e aa 6a f9 8c 7c ed 43 12 09 98 e1 87 a8 0e e0 cc b0 52
+ 4b 1b 01 8c 3e 0b 63 26 4d 44 9a 6d 38 e2 2a 5f da 43 08 46 74
+ 80 30 53 0e f0 46 1c 8c a9 d9 ef bf ae 8e a6 d1 d0 3e 2b d1 93
+ ef f0 ab 9a 80 02 c4 74 28 a6 d3 5a 8d 88 d7 9f 7f 1e 3f 02 03
+ 01 00 01 a3 1a 30 18 30 09 06 03 55 1d 13 04 02 30 00 30 0b 06
+ 03 55 1d 0f 04 04 03 02 05 a0 30 0d 06 09 2a 86 48 86 f7 0d 01
+ 01 0b 05 00 03 81 81 00 85 aa d2 a0 e5 b9 27 6b 90 8c 65 f7 3a
+ 72 67 17 06 18 a5 4c 5f 8a 7b 33 7d 2d f7 a5 94 36 54 17 f2 ea
+ e8 f8 a5 8c 8f 81 72 f9 31 9c f3 6b 7f d6 c5 5b 80 f2 1a 03 01
+ 51 56 72 60 96 fd 33 5e 5e 67 f2 db f1 02 70 2e 60 8c ca e6 be
+ c1 fc 63 a4 2a 99 be 5c 3e b7 10 7c 3c 54 e9 b9 eb 2b d5 20 3b
+ 1c 3b 84 e0 a8 b2 f7 59 40 9b a3 ea c9 d9 1d 40 2d cc 0c c8 f8
+ 96 12 29 ac 91 87 b4 2b 4d e1 00 00"),
+
+ CertificateVerify =
+ hexstr2bin("0f 00 00 84 08 04 00 80 5a 74 7c
+ 5d 88 fa 9b d2 e5 5a b0 85 a6 10 15 b7 21 1f 82 4c d4 84 14 5a
+ b3 ff 52 f1 fd a8 47 7b 0b 7a bc 90 db 78 e2 d3 3a 5c 14 1a 07
+ 86 53 fa 6b ef 78 0c 5e a2 48 ee aa a7 85 c4 f3 94 ca b6 d3 0b
+ be 8d 48 59 ee 51 1f 60 29 57 b1 54 11 ac 02 76 71 45 9e 46 44
+ 5c 9e a5 8c 18 1e 81 8e 95 b8 c3 fb 0b f3 27 84 09 d3 be 15 2a
+ 3d a5 04 3e 06 3d da 65 cd f5 ae a2 0d 53 df ac d4 2f 74 f3"),
+
+ %% {server} calculate finished "tls13 finished":
+ %%
+ %% PRK (32 octets): b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d 37 b4
+ %% e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38
+ %%
+ %% hash (0 octets): (empty)
+ %%
+ %% info (18 octets): 00 20 0e 74 6c 73 31 33 20 66 69 6e 69 73 68 65
+ %% 64 00
+ %%
+ %% expanded (32 octets): 00 8d 3b 66 f8 16 ea 55 9f 96 b5 37 e8 85
+ %% c3 1f c0 68 bf 49 2c 65 2f 01 f2 88 a1 d8 cd c1 9f c8
+ %%
+ %% finished (32 octets): 9b 9b 14 1d 90 63 37 fb d2 cb dc e7 1d f4
+ %% de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07 18
+
+ %% PRK = SHSTrafficSecret
+ FInfo =
+ hexstr2bin("00 20 0e 74 6c 73 31 33 20 66 69 6e 69 73 68 65
+ 64 00"),
+
+ FExpanded =
+ hexstr2bin("00 8d 3b 66 f8 16 ea 55 9f 96 b5 37 e8 85
+ c3 1f c0 68 bf 49 2c 65 2f 01 f2 88 a1 d8 cd c1 9f c8"),
+
+ FinishedVerifyData =
+ hexstr2bin("9b 9b 14 1d 90 63 37 fb d2 cb dc e7 1d f4
+ de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07 18"),
+
+ FInfo = tls_v1:create_info(<<"finished">>, <<>>, ssl_cipher:hash_size(HKDFAlgo)),
+
+ FExpanded = tls_v1:finished_key(SHSTrafficSecret, HKDFAlgo),
+
+ MessageHistory0 = [CertificateVerify,
+ Certificate,
+ EncryptedExtensions,
+ ServerHello,
+ ClientHello],
+
+ FinishedVerifyData = tls_v1:finished_verify_data(FExpanded, HKDFAlgo, MessageHistory0),
+
+ %% {server} construct a Finished handshake message:
+ %%
+ %% Finished (36 octets): 14 00 00 20 9b 9b 14 1d 90 63 37 fb d2 cb
+ %% dc e7 1d f4 de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07
+ %% 18
+ FinishedHSBin =
+ hexstr2bin("14 00 00 20 9b 9b 14 1d 90 63 37 fb d2 cb
+ dc e7 1d f4 de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07
+ 18"),
+
+ FinishedHS = #finished{verify_data = FinishedVerifyData},
+
+ FinishedIOList = tls_handshake:encode_handshake(FinishedHS, {3,4}),
+ FinishedHSBin = iolist_to_binary(FinishedIOList),
+
+ %% {server} derive secret "tls13 c ap traffic":
+ %%
+ %% PRK (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a 47
+ %% 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19
+ %%
+ %% hash (32 octets): 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a
+ %% 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13
+ %%
+ %% info (54 octets): 00 20 12 74 6c 73 31 33 20 63 20 61 70 20 74 72
+ %% 61 66 66 69 63 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b
+ %% 1a 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13
+ %%
+ %% expanded (32 octets): 9e 40 64 6c e7 9a 7f 9d c0 5a f8 88 9b ce
+ %% 65 52 87 5a fa 0b 06 df 00 87 f7 92 eb b7 c1 75 04 a5
+
+ %% PRK = MasterSecret
+ CAPTHash =
+ hexstr2bin("96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a
+ 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13"),
+ CAPTInfo =
+ hexstr2bin("00 20 12 74 6c 73 31 33 20 63 20 61 70 20 74 72
+ 61 66 66 69 63 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b
+ 1a 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13"),
+
+ CAPTrafficSecret =
+ hexstr2bin("9e 40 64 6c e7 9a 7f 9d c0 5a f8 88 9b ce
+ 65 52 87 5a fa 0b 06 df 00 87 f7 92 eb b7 c1 75 04 a5"),
+
+ CHSF = <<ClientHello/binary,
+ ServerHello/binary,
+ EncryptedExtensions/binary,
+ Certificate/binary,
+ CertificateVerify/binary,
+ FinishedHSBin/binary>>,
+
+ CAPTHash = crypto:hash(HKDFAlgo, CHSF),
+
+ CAPTInfo =
+ tls_v1:create_info(<<"c ap traffic">>, CAPTHash, ssl_cipher:hash_size(HKDFAlgo)),
+
+ CAPTrafficSecret =
+ tls_v1:client_application_traffic_secret_0(HKDFAlgo, {master_secret, MasterSecret}, CHSF),
+
+ %% {server} derive secret "tls13 s ap traffic":
+ %%
+ %% PRK (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a 47
+ %% 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19
+ %%
+ %% hash (32 octets): 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a
+ %% 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13
+ %%
+ %% info (54 octets): 00 20 12 74 6c 73 31 33 20 73 20 61 70 20 74 72
+ %% 61 66 66 69 63 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b
+ %% 1a 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13
+ %%
+ %% expanded (32 octets): a1 1a f9 f0 55 31 f8 56 ad 47 11 6b 45 a9
+ %% 50 32 82 04 b4 f4 4b fb 6b 3a 4b 4f 1f 3f cb 63 16 43
+
+ %% PRK = MasterSecret
+ %% hash = CAPTHash
+ SAPTInfo =
+ hexstr2bin(" 00 20 12 74 6c 73 31 33 20 73 20 61 70 20 74 72
+ 61 66 66 69 63 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b
+ 1a 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13"),
+
+ SAPTrafficSecret =
+ hexstr2bin("a1 1a f9 f0 55 31 f8 56 ad 47 11 6b 45 a9
+ 50 32 82 04 b4 f4 4b fb 6b 3a 4b 4f 1f 3f cb 63 16 43"),
+
+ SAPTInfo =
+ tls_v1:create_info(<<"s ap traffic">>, CAPTHash, ssl_cipher:hash_size(HKDFAlgo)),
+
+ SAPTrafficSecret =
+ tls_v1:server_application_traffic_secret_0(HKDFAlgo, {master_secret, MasterSecret}, CHSF),
+
+ %% {server} derive secret "tls13 exp master":
+ %%
+ %% PRK (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a 47
+ %% 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19
+ %%
+ %% hash (32 octets): 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a
+ %% 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13
+ %%
+ %% info (52 octets): 00 20 10 74 6c 73 31 33 20 65 78 70 20 6d 61 73
+ %% 74 65 72 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a 00
+ %% 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13
+ %%
+ %% expanded (32 octets): fe 22 f8 81 17 6e da 18 eb 8f 44 52 9e 67
+ %% 92 c5 0c 9a 3f 89 45 2f 68 d8 ae 31 1b 43 09 d3 cf 50
+
+ %% PRK = MasterSecret
+ %% hash = CAPTHash
+ ExporterInfo =
+ hexstr2bin("00 20 10 74 6c 73 31 33 20 65 78 70 20 6d 61 73
+ 74 65 72 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a 00
+ 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13"),
+
+ ExporterMasterSecret =
+ hexstr2bin("fe 22 f8 81 17 6e da 18 eb 8f 44 52 9e 67
+ 92 c5 0c 9a 3f 89 45 2f 68 d8 ae 31 1b 43 09 d3 cf 50"),
+
+ ExporterInfo =
+ tls_v1:create_info(<<"exp master">>, CAPTHash, ssl_cipher:hash_size(HKDFAlgo)),
+
+ ExporterMasterSecret =
+ tls_v1:exporter_master_secret(HKDFAlgo, {master_secret, MasterSecret}, CHSF),
+
+ %% {server} derive write traffic keys for application data:
+ %%
+ %% PRK (32 octets): a1 1a f9 f0 55 31 f8 56 ad 47 11 6b 45 a9 50 32
+ %% 82 04 b4 f4 4b fb 6b 3a 4b 4f 1f 3f cb 63 16 43
+ %%
+ %% key info (13 octets): 00 10 09 74 6c 73 31 33 20 6b 65 79 00
+ %%
+ %% key expanded (16 octets): 9f 02 28 3b 6c 9c 07 ef c2 6b b9 f2 ac
+ %% 92 e3 56
+ %%
+ %% iv info (12 octets): 00 0c 08 74 6c 73 31 33 20 69 76 00
+ %%
+ %% iv expanded (12 octets): cf 78 2b 88 dd 83 54 9a ad f1 e9 84
+
+ %% PRK = SAPTrafficsecret
+ %% key info = WriteKeyInfo
+ %% iv info = WrtieIVInfo
+ SWKey =
+ hexstr2bin("9f 02 28 3b 6c 9c 07 ef c2 6b b9 f2 ac 92 e3 56"),
+
+ SWIV =
+ hexstr2bin("cf 78 2b 88 dd 83 54 9a ad f1 e9 84"),
+
+ {SWKey, SWIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, SAPTrafficSecret),
+
+ %% {server} derive read traffic keys for handshake data:
+ %%
+ %% PRK (32 octets): b3 ed db 12 6e 06 7f 35 a7 80 b3 ab f4 5e 2d 8f
+ %% 3b 1a 95 07 38 f5 2e 96 00 74 6a 0e 27 a5 5a 21
+ %%
+ %% key info (13 octets): 00 10 09 74 6c 73 31 33 20 6b 65 79 00
+ %%
+ %% key expanded (16 octets): db fa a6 93 d1 76 2c 5b 66 6a f5 d9 50
+ %% 25 8d 01
+ %%
+ %% iv info (12 octets): 00 0c 08 74 6c 73 31 33 20 69 76 00
+ %%
+ %% iv expanded (12 octets): 5b d3 c7 1b 83 6e 0b 76 bb 73 26 5f
+
+ %% PRK = CHSTrafficsecret
+ %% key info = WriteKeyInfo
+ %% iv info = WrtieIVInfo
+ SRKey =
+ hexstr2bin("db fa a6 93 d1 76 2c 5b 66 6a f5 d9 50 25 8d 01"),
+
+ SRIV =
+ hexstr2bin("5b d3 c7 1b 83 6e 0b 76 bb 73 26 5f"),
+
+ {SRKey, SRIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, CHSTrafficSecret).
+
+
+tls13_finished_verify_data() ->
+ [{doc,"Test TLS 1.3 Finished message handling"}].
+
+tls13_finished_verify_data(_Config) ->
+ ClientHello =
+ hexstr2bin("01 00 00 c6 03 03 00 01 02 03 04 05 06 07 08 09
+ 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19
+ 1a 1b 1c 1d 1e 1f 20 e0 e1 e2 e3 e4 e5 e6 e7 e8
+ e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8
+ f9 fa fb fc fd fe ff 00 06 13 01 13 02 13 03 01
+ 00 00 77 00 00 00 18 00 16 00 00 13 65 78 61 6d
+ 70 6c 65 2e 75 6c 66 68 65 69 6d 2e 6e 65 74 00
+ 0a 00 08 00 06 00 1d 00 17 00 18 00 0d 00 14 00
+ 12 04 03 08 04 04 01 05 03 08 05 05 01 08 06 06
+ 01 02 01 00 33 00 26 00 24 00 1d 00 20 35 80 72
+ d6 36 58 80 d1 ae ea 32 9a df 91 21 38 38 51 ed
+ 21 a2 8e 3b 75 e9 65 d0 d2 cd 16 62 54 00 2d 00
+ 02 01 01 00 2b 00 03 02 03 04"),
+
+ ServerHello =
+ hexstr2bin("02 00 00 76 03 03 70 71 72 73 74 75 76 77 78 79
+ 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89
+ 8a 8b 8c 8d 8e 8f 20 e0 e1 e2 e3 e4 e5 e6 e7 e8
+ e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8
+ f9 fa fb fc fd fe ff 13 01 00 00 2e 00 33 00 24
+ 00 1d 00 20 9f d7 ad 6d cf f4 29 8d d3 f9 6d 5b
+ 1b 2a f9 10 a0 53 5b 14 88 d7 f8 fa bb 34 9a 98
+ 28 80 b6 15 00 2b 00 02 03 04"),
+
+ EncryptedExtensions =
+ hexstr2bin("08 00 00 02 00 00"),
+
+ Certificate =
+ hexstr2bin("0b 00 03 2e 00 00 03 2a 00 03 25 30 82 03 21 30
+ 82 02 09 a0 03 02 01 02 02 08 15 5a 92 ad c2 04
+ 8f 90 30 0d 06 09 2a 86 48 86 f7 0d 01 01 0b 05
+ 00 30 22 31 0b 30 09 06 03 55 04 06 13 02 55 53
+ 31 13 30 11 06 03 55 04 0a 13 0a 45 78 61 6d 70
+ 6c 65 20 43 41 30 1e 17 0d 31 38 31 30 30 35 30
+ 31 33 38 31 37 5a 17 0d 31 39 31 30 30 35 30 31
+ 33 38 31 37 5a 30 2b 31 0b 30 09 06 03 55 04 06
+ 13 02 55 53 31 1c 30 1a 06 03 55 04 03 13 13 65
+ 78 61 6d 70 6c 65 2e 75 6c 66 68 65 69 6d 2e 6e
+ 65 74 30 82 01 22 30 0d 06 09 2a 86 48 86 f7 0d
+ 01 01 01 05 00 03 82 01 0f 00 30 82 01 0a 02 82
+ 01 01 00 c4 80 36 06 ba e7 47 6b 08 94 04 ec a7
+ b6 91 04 3f f7 92 bc 19 ee fb 7d 74 d7 a8 0d 00
+ 1e 7b 4b 3a 4a e6 0f e8 c0 71 fc 73 e7 02 4c 0d
+ bc f4 bd d1 1d 39 6b ba 70 46 4a 13 e9 4a f8 3d
+ f3 e1 09 59 54 7b c9 55 fb 41 2d a3 76 52 11 e1
+ f3 dc 77 6c aa 53 37 6e ca 3a ec be c3 aa b7 3b
+ 31 d5 6c b6 52 9c 80 98 bc c9 e0 28 18 e2 0b f7
+ f8 a0 3a fd 17 04 50 9e ce 79 bd 9f 39 f1 ea 69
+ ec 47 97 2e 83 0f b5 ca 95 de 95 a1 e6 04 22 d5
+ ee be 52 79 54 a1 e7 bf 8a 86 f6 46 6d 0d 9f 16
+ 95 1a 4c f7 a0 46 92 59 5c 13 52 f2 54 9e 5a fb
+ 4e bf d7 7a 37 95 01 44 e4 c0 26 87 4c 65 3e 40
+ 7d 7d 23 07 44 01 f4 84 ff d0 8f 7a 1f a0 52 10
+ d1 f4 f0 d5 ce 79 70 29 32 e2 ca be 70 1f df ad
+ 6b 4b b7 11 01 f4 4b ad 66 6a 11 13 0f e2 ee 82
+ 9e 4d 02 9d c9 1c dd 67 16 db b9 06 18 86 ed c1
+ ba 94 21 02 03 01 00 01 a3 52 30 50 30 0e 06 03
+ 55 1d 0f 01 01 ff 04 04 03 02 05 a0 30 1d 06 03
+ 55 1d 25 04 16 30 14 06 08 2b 06 01 05 05 07 03
+ 02 06 08 2b 06 01 05 05 07 03 01 30 1f 06 03 55
+ 1d 23 04 18 30 16 80 14 89 4f de 5b cc 69 e2 52
+ cf 3e a3 00 df b1 97 b8 1d e1 c1 46 30 0d 06 09
+ 2a 86 48 86 f7 0d 01 01 0b 05 00 03 82 01 01 00
+ 59 16 45 a6 9a 2e 37 79 e4 f6 dd 27 1a ba 1c 0b
+ fd 6c d7 55 99 b5 e7 c3 6e 53 3e ff 36 59 08 43
+ 24 c9 e7 a5 04 07 9d 39 e0 d4 29 87 ff e3 eb dd
+ 09 c1 cf 1d 91 44 55 87 0b 57 1d d1 9b df 1d 24
+ f8 bb 9a 11 fe 80 fd 59 2b a0 39 8c de 11 e2 65
+ 1e 61 8c e5 98 fa 96 e5 37 2e ef 3d 24 8a fd e1
+ 74 63 eb bf ab b8 e4 d1 ab 50 2a 54 ec 00 64 e9
+ 2f 78 19 66 0d 3f 27 cf 20 9e 66 7f ce 5a e2 e4
+ ac 99 c7 c9 38 18 f8 b2 51 07 22 df ed 97 f3 2e
+ 3e 93 49 d4 c6 6c 9e a6 39 6d 74 44 62 a0 6b 42
+ c6 d5 ba 68 8e ac 3a 01 7b dd fc 8e 2c fc ad 27
+ cb 69 d3 cc dc a2 80 41 44 65 d3 ae 34 8c e0 f3
+ 4a b2 fb 9c 61 83 71 31 2b 19 10 41 64 1c 23 7f
+ 11 a5 d6 5c 84 4f 04 04 84 99 38 71 2b 95 9e d6
+ 85 bc 5c 5d d6 45 ed 19 90 94 73 40 29 26 dc b4
+ 0e 34 69 a1 59 41 e8 e2 cc a8 4b b6 08 46 36 a0
+ 00 00"),
+
+ CertificateVerify =
+ hexstr2bin("0f 00 01 04 08 04 01 00 17 fe b5 33 ca 6d 00 7d
+ 00 58 25 79 68 42 4b bc 3a a6 90 9e 9d 49 55 75
+ 76 a5 20 e0 4a 5e f0 5f 0e 86 d2 4f f4 3f 8e b8
+ 61 ee f5 95 22 8d 70 32 aa 36 0f 71 4e 66 74 13
+ 92 6e f4 f8 b5 80 3b 69 e3 55 19 e3 b2 3f 43 73
+ df ac 67 87 06 6d cb 47 56 b5 45 60 e0 88 6e 9b
+ 96 2c 4a d2 8d ab 26 ba d1 ab c2 59 16 b0 9a f2
+ 86 53 7f 68 4f 80 8a ef ee 73 04 6c b7 df 0a 84
+ fb b5 96 7a ca 13 1f 4b 1c f3 89 79 94 03 a3 0c
+ 02 d2 9c bd ad b7 25 12 db 9c ec 2e 5e 1d 00 e5
+ 0c af cf 6f 21 09 1e bc 4f 25 3c 5e ab 01 a6 79
+ ba ea be ed b9 c9 61 8f 66 00 6b 82 44 d6 62 2a
+ aa 56 88 7c cf c6 6a 0f 38 51 df a1 3a 78 cf f7
+ 99 1e 03 cb 2c 3a 0e d8 7d 73 67 36 2e b7 80 5b
+ 00 b2 52 4f f2 98 a4 da 48 7c ac de af 8a 23 36
+ c5 63 1b 3e fa 93 5b b4 11 e7 53 ca 13 b0 15 fe
+ c7 e4 a7 30 f1 36 9f 9e"),
+
+ BaseKey =
+ hexstr2bin("a2 06 72 65 e7 f0 65 2a 92 3d 5d 72 ab 04 67 c4
+ 61 32 ee b9 68 b6 a3 2d 31 1c 80 58 68 54 88 14"),
+
+ VerifyData =
+ hexstr2bin("ea 6e e1 76 dc cc 4a f1 85 9e 9e 4e 93 f7 97 ea
+ c9 a7 8c e4 39 30 1e 35 27 5a d4 3f 3c dd bd e3"),
+
+ Messages = [CertificateVerify,
+ Certificate,
+ EncryptedExtensions,
+ ServerHello,
+ ClientHello],
+
+ FinishedKey = tls_v1:finished_key(BaseKey, sha256),
+ VerifyData = tls_v1:finished_verify_data(FinishedKey, sha256, Messages).
+
+tls13_basic_ssl_server_openssl_client() ->
+ [{doc,"Test TLS 1.3 basic connection between ssl server and openssl s_client"}].
+
+tls13_basic_ssl_server_openssl_client(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ %% Set versions
+ ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0],
+ {_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_active, []}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts),
+
+ ssl_test_lib:check_result(Server, ok),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close_port(Client).
+
+tls13_custom_groups_ssl_server_openssl_client() ->
+ [{doc,"Test that ssl server can select a common group for key-exchange"}].
+
+tls13_custom_groups_ssl_server_openssl_client(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ %% Set versions
+ ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']},
+ {supported_groups, [x448, secp256r1, secp384r1]}|ServerOpts0],
+ {_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_active, []}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ ClientOpts = [{groups,"P-384:P-256:X25519"}|ClientOpts0],
+ Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts),
+
+ ssl_test_lib:check_result(Server, ok),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close_port(Client).
+
+tls13_hello_retry_request_ssl_server_openssl_client() ->
+ [{doc,"Test that ssl server can request a new group when the client's first key share"
+ "is not supported"}].
+
+tls13_hello_retry_request_ssl_server_openssl_client(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ %% Set versions
+ ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']},
+ {supported_groups, [x448, x25519]}|ServerOpts0],
+ {_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_active, []}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ ClientOpts = [{groups,"P-256:X25519"}|ClientOpts0],
+ Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts),
+
+ ssl_test_lib:check_result(Server, ok),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close_port(Client).
+
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
@@ -4486,8 +5602,8 @@ tcp_send_recv_result(Socket) ->
ok.
basic_verify_test_no_close(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_verification_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),
@@ -4962,16 +6078,16 @@ run_suites(Ciphers, Config, Type) ->
{ClientOpts, ServerOpts} =
case Type of
rsa ->
- {ssl_test_lib:ssl_options(client_verification_opts, Config),
+ {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
[{ciphers, Ciphers} |
- ssl_test_lib:ssl_options(server_verification_opts, Config)]};
+ ssl_test_lib:ssl_options(server_rsa_opts, Config)]};
dsa ->
- {ssl_test_lib:ssl_options(client_verification_opts, Config),
+ {ssl_test_lib:ssl_options(client_dsa_verify_opts, Config),
[{ciphers, Ciphers} |
ssl_test_lib:ssl_options(server_dsa_opts, Config)]};
anonymous ->
%% No certs in opts!
- {ssl_test_lib:ssl_options(client_verification_opts, Config),
+ {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
[{ciphers, Ciphers} |
ssl_test_lib:ssl_options([], Config)]};
psk ->
@@ -4993,46 +6109,50 @@ run_suites(Ciphers, Config, Type) ->
ssl_test_lib:ssl_options(server_psk_anon_hint, Config)]};
srp ->
{ssl_test_lib:ssl_options(client_srp, Config),
- ssl_test_lib:ssl_options(server_srp, Config)};
+ [{ciphers, Ciphers} |
+ ssl_test_lib:ssl_options(server_srp, Config)]};
srp_anon ->
{ssl_test_lib:ssl_options(client_srp, Config),
- ssl_test_lib:ssl_options(server_srp_anon, Config)};
+ [{ciphers, Ciphers} |
+ ssl_test_lib:ssl_options(server_srp_anon, Config)]};
srp_dsa ->
{ssl_test_lib:ssl_options(client_srp_dsa, Config),
- ssl_test_lib:ssl_options(server_srp_dsa, Config)};
+ [{ciphers, Ciphers} |
+ ssl_test_lib:ssl_options(server_srp_dsa, Config)]};
ecdsa ->
- {ssl_test_lib:ssl_options(client_verification_opts, Config),
+ {ssl_test_lib:ssl_options(client_ecdsa_opts, Config),
[{ciphers, Ciphers} |
ssl_test_lib:ssl_options(server_ecdsa_opts, Config)]};
ecdh_rsa ->
- {ssl_test_lib:ssl_options(client_verification_opts, Config),
- ssl_test_lib:ssl_options(server_ecdh_rsa_opts, Config)};
+ {ssl_test_lib:ssl_options(client_ecdh_rsa_opts, Config),
+ [{ciphers, Ciphers} |
+ ssl_test_lib:ssl_options(server_ecdh_rsa_opts, Config)]};
rc4_rsa ->
- {ssl_test_lib:ssl_options(client_verification_opts, Config),
+ {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
[{ciphers, Ciphers} |
- ssl_test_lib:ssl_options(server_verification_opts, Config)]};
+ ssl_test_lib:ssl_options(server_rsa_verify_opts, Config)]};
rc4_ecdh_rsa ->
- {ssl_test_lib:ssl_options(client_verification_opts, Config),
+ {ssl_test_lib:ssl_options(client_ecdh_rsa_opts, Config),
[{ciphers, Ciphers} |
ssl_test_lib:ssl_options(server_ecdh_rsa_opts, Config)]};
rc4_ecdsa ->
- {ssl_test_lib:ssl_options(client_verification_opts, Config),
+ {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
[{ciphers, Ciphers} |
ssl_test_lib:ssl_options(server_ecdsa_opts, Config)]};
des_dhe_rsa ->
- {ssl_test_lib:ssl_options(client_verification_opts, Config),
+ {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
[{ciphers, Ciphers} |
ssl_test_lib:ssl_options(server_verification_opts, Config)]};
des_rsa ->
- {ssl_test_lib:ssl_options(client_verification_opts, Config),
+ {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
[{ciphers, Ciphers} |
- ssl_test_lib:ssl_options(server_verification_opts, Config)]};
+ ssl_test_lib:ssl_options(server_rsa_verify_opts, Config)]};
chacha_rsa ->
- {ssl_test_lib:ssl_options(client_verification_opts, Config),
+ {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
[{ciphers, Ciphers} |
- ssl_test_lib:ssl_options(server_verification_opts, Config)]};
+ ssl_test_lib:ssl_options(server_rsa_verify_opts, Config)]};
chacha_ecdsa ->
- {ssl_test_lib:ssl_options(client_verification_opts, Config),
+ {ssl_test_lib:ssl_options(client_ecdsa_opts, Config),
[{ciphers, Ciphers} |
ssl_test_lib:ssl_options(server_ecdsa_opts, Config)]}
end,
@@ -5256,3 +6376,31 @@ tls_or_dtls('dtlsv1.2') ->
dtls;
tls_or_dtls(_) ->
tls.
+
+hexstr2int(S) ->
+ B = hexstr2bin(S),
+ Bits = size(B) * 8,
+ <<Integer:Bits/integer>> = B,
+ Integer.
+
+hexstr2bin(S) when is_binary(S) ->
+ hexstr2bin(S, <<>>);
+hexstr2bin(S) ->
+ hexstr2bin(list_to_binary(S), <<>>).
+%%
+hexstr2bin(<<>>, Acc) ->
+ Acc;
+hexstr2bin(<<C,T/binary>>, Acc) when C =:= 32; %% SPACE
+ C =:= 10; %% LF
+ C =:= 13 -> %% CR
+ hexstr2bin(T, Acc);
+hexstr2bin(<<X,Y,T/binary>>, Acc) ->
+ I = hex2int(X) * 16 + hex2int(Y),
+ hexstr2bin(T, <<Acc/binary,I>>).
+
+hex2int(C) when $0 =< C, C =< $9 ->
+ C - $0;
+hex2int(C) when $A =< C, C =< $F ->
+ C - $A + 10;
+hex2int(C) when $a =< C, C =< $f ->
+ C - $a + 10.
diff --git a/lib/ssl/test/ssl_bench.spec b/lib/ssl/test/ssl_bench.spec
index 8b746c5ca9..217cc6fc83 100644
--- a/lib/ssl/test/ssl_bench.spec
+++ b/lib/ssl/test/ssl_bench.spec
@@ -1 +1,2 @@
{suites,"../ssl_test",[ssl_bench_SUITE, ssl_dist_bench_SUITE]}.
+{skip_groups,"../ssl_test",ssl_bench_SUITE,basic,"Benchmarks run separately"}.
diff --git a/lib/ssl/test/ssl_bench_SUITE.erl b/lib/ssl/test/ssl_bench_SUITE.erl
index 13097b08b6..35efa2b8a3 100644
--- a/lib/ssl/test/ssl_bench_SUITE.erl
+++ b/lib/ssl/test/ssl_bench_SUITE.erl
@@ -25,10 +25,11 @@
suite() -> [{ct_hooks,[{ts_install_cth,[{nodenames,2}]}]}].
-all() -> [{group, setup}, {group, payload}, {group, pem_cache}].
+all() -> [{group, basic}, {group, setup}, {group, payload}, {group, pem_cache}].
groups() ->
- [{setup, [{repeat, 3}], [setup_sequential, setup_concurrent]},
+ [{basic, [], [basic_pem_cache]},
+ {setup, [{repeat, 3}], [setup_sequential, setup_concurrent]},
{payload, [{repeat, 3}], [payload_simple]},
{pem_cache, [{repeat, 3}], [use_pem_cache, bypass_pem_cache]}
].
@@ -40,6 +41,7 @@ end_per_group(_GroupName, _Config) ->
ok.
init_per_suite(Config) ->
+ ct:timetrap({minutes, 1}),
case node() of
nonode@nohost ->
{skipped, "Node not distributed"};
@@ -51,29 +53,21 @@ init_per_suite(Config) ->
end_per_suite(_Config) ->
ok.
-init_per_testcase(use_pem_cache, Conf) ->
+init_per_testcase(TC, Conf) when TC =:= use_pem_cache;
+ TC =:= bypass_pem_cache;
+ TC =:= basic_pem_cache ->
case bypass_pem_cache_supported() of
false -> {skipped, "PEM cache bypass support required"};
true ->
application:set_env(ssl, bypass_pem_cache, false),
Conf
end;
-init_per_testcase(bypass_pem_cache, Conf) ->
- case bypass_pem_cache_supported() of
- false -> {skipped, "PEM cache bypass support required"};
- true ->
- application:set_env(ssl, bypass_pem_cache, true),
- Conf
- end;
init_per_testcase(_Func, Conf) ->
Conf.
-end_per_testcase(use_pem_cache, _Config) ->
- case bypass_pem_cache_supported() of
- false -> ok;
- true -> application:set_env(ssl, bypass_pem_cache, false)
- end;
-end_per_testcase(bypass_pem_cache, _Config) ->
+end_per_testcase(TC, _Config) when TC =:= use_pem_cache;
+ TC =:= bypass_pem_cache;
+ TC =:= basic_pem_cache ->
case bypass_pem_cache_supported() of
false -> ok;
true -> application:set_env(ssl, bypass_pem_cache, false)
@@ -119,6 +113,9 @@ payload_simple(Config) ->
{suite, "ssl"}, {name, "Payload simple"}]}),
ok.
+basic_pem_cache(_Config) ->
+ do_test(ssl, pem_cache, 10, 5, node()).
+
use_pem_cache(_Config) ->
{ok, Result} = do_test(ssl, pem_cache, 100, 500, node()),
ct_event:notify(#event{name = benchmark_data,
@@ -167,7 +164,7 @@ do_test(Type, TC, Loop, ParallellConnections, Server) ->
end
end,
Spawn = fun(Id) ->
- Pid = spawn(fun() -> Test(Id) end),
+ Pid = spawn_link(fun() -> Test(Id) end),
receive {Pid, init} -> Pid end
end,
Pids = [Spawn(Id) || Id <- lists:seq(ParallellConnections, 1, -1)],
@@ -184,42 +181,42 @@ do_test(Type, TC, Loop, ParallellConnections, Server) ->
{ok, TestPerSecond}.
server_init(ssl, setup_connection, _, _, Server) ->
- {ok, Socket} = ssl:listen(0, ssl_opts(listen)),
- {ok, {_Host, Port}} = ssl:sockname(Socket),
+ {ok, LSocket} = ssl:listen(0, ssl_opts(listen)),
+ {ok, {_Host, Port}} = ssl:sockname(LSocket),
{ok, Host} = inet:gethostname(),
?FPROF_SERVER andalso start_profile(fprof, [whereis(ssl_manager), new]),
%%?EPROF_SERVER andalso start_profile(eprof, [ssl_connection_sup, ssl_manager]),
?EPROF_SERVER andalso start_profile(eprof, [ssl_manager]),
Server ! {self(), {init, Host, Port}},
Test = fun(TSocket) ->
- ok = ssl:ssl_accept(TSocket),
- ssl:close(TSocket)
+ {ok, Socket} = ssl:handshake(TSocket),
+ ssl:close(Socket)
end,
- setup_server_connection(Socket, Test);
+ setup_server_connection(LSocket, Test);
server_init(ssl, payload, Loop, _, Server) ->
- {ok, Socket} = ssl:listen(0, ssl_opts(listen)),
- {ok, {_Host, Port}} = ssl:sockname(Socket),
+ {ok, LSocket} = ssl:listen(0, ssl_opts(listen)),
+ {ok, {_Host, Port}} = ssl:sockname(LSocket),
{ok, Host} = inet:gethostname(),
Server ! {self(), {init, Host, Port}},
Test = fun(TSocket) ->
- ok = ssl:ssl_accept(TSocket),
+ {ok, Socket} = ssl:handshake(TSocket),
Size = byte_size(msg()),
- server_echo(TSocket, Size, Loop),
- ssl:close(TSocket)
+ server_echo(Socket, Size, Loop),
+ ssl:close(Socket)
end,
- setup_server_connection(Socket, Test);
+ setup_server_connection(LSocket, Test);
server_init(ssl, pem_cache, Loop, _, Server) ->
- {ok, Socket} = ssl:listen(0, ssl_opts(listen_der)),
- {ok, {_Host, Port}} = ssl:sockname(Socket),
+ {ok, LSocket} = ssl:listen(0, ssl_opts(listen_der)),
+ {ok, {_Host, Port}} = ssl:sockname(LSocket),
{ok, Host} = inet:gethostname(),
Server ! {self(), {init, Host, Port}},
Test = fun(TSocket) ->
- ok = ssl:ssl_accept(TSocket),
+ {ok, Socket} = ssl:handshake(TSocket),
Size = byte_size(msg()),
- server_echo(TSocket, Size, Loop),
- ssl:close(TSocket)
+ server_echo(Socket, Size, Loop),
+ ssl:close(Socket)
end,
- setup_server_connection(Socket, Test);
+ setup_server_connection(LSocket, Test);
server_init(Type, Tc, _, _, Server) ->
io:format("No server init code for ~p ~p~n",[Type, Tc]),
diff --git a/lib/ssl/test/ssl_certificate_verify_SUITE.erl b/lib/ssl/test/ssl_certificate_verify_SUITE.erl
index 588ca153a9..8690faed54 100644
--- a/lib/ssl/test/ssl_certificate_verify_SUITE.erl
+++ b/lib/ssl/test/ssl_certificate_verify_SUITE.erl
@@ -298,15 +298,8 @@ server_require_peer_cert_fail(Config) when is_list(Config) ->
{host, Hostname},
{from, self()},
{options, [{active, Active} | BadClientOpts]}]),
- receive
- {Server, {error, {tls_alert, "handshake failure"}}} ->
- receive
- {Client, {error, {tls_alert, "handshake failure"}}} ->
- ok;
- {Client, {error, closed}} ->
- ok
- end
- end.
+
+ ssl_test_lib:check_server_alert(Server, Client, handshake_failure).
%%--------------------------------------------------------------------
server_require_peer_cert_empty_ok() ->
@@ -365,15 +358,8 @@ server_require_peer_cert_partial_chain(Config) when is_list(Config) ->
{options, [{active, Active},
{cacerts, [RootCA]} |
proplists:delete(cacertfile, ClientOpts)]}]),
- receive
- {Server, {error, {tls_alert, "unknown ca"}}} ->
- receive
- {Client, {error, {tls_alert, "unknown ca"}}} ->
- ok;
- {Client, {error, closed}} ->
- ok
- end
- end.
+ ssl_test_lib:check_server_alert(Server, Client, unknown_ca).
+
%%--------------------------------------------------------------------
server_require_peer_cert_allow_partial_chain() ->
[{doc, "Server trusts intermediat CA and accepts a partial chain. (partial_chain option)"}].
@@ -446,17 +432,7 @@ server_require_peer_cert_do_not_allow_partial_chain(Config) when is_list(Config)
{from, self()},
{mfa, {ssl_test_lib, no_result, []}},
{options, ClientOpts}]),
-
- receive
- {Server, {error, {tls_alert, "unknown ca"}}} ->
- receive
- {Client, {error, {tls_alert, "unknown ca"}}} ->
- ok;
- {Client, {error, closed}} ->
- ok
- end
- end.
-
+ ssl_test_lib:check_server_alert(Server, Client, unknown_ca).
%%--------------------------------------------------------------------
server_require_peer_cert_partial_chain_fun_fail() ->
[{doc, "If parial_chain fun crashes, treat it as if it returned unkown_ca"}].
@@ -487,16 +463,7 @@ server_require_peer_cert_partial_chain_fun_fail(Config) when is_list(Config) ->
{from, self()},
{mfa, {ssl_test_lib, no_result, []}},
{options, ClientOpts}]),
-
- receive
- {Server, {error, {tls_alert, "unknown ca"}}} ->
- receive
- {Client, {error, {tls_alert, "unknown ca"}}} ->
- ok;
- {Client, {error, closed}} ->
- ok
- end
- end.
+ ssl_test_lib:check_server_alert(Server, Client, unknown_ca).
%%--------------------------------------------------------------------
verify_fun_always_run_client() ->
@@ -514,7 +481,7 @@ verify_fun_always_run_client(Config) when is_list(Config) ->
Port = ssl_test_lib:inet_port(Server),
%% If user verify fun is called correctly we fail the connection.
- %% otherwise we can not tell this case apart form where we miss
+ %% otherwise we cannot tell this case apart form where we miss
%% to call users verify fun
FunAndState = {fun(_,{extension, _}, UserState) ->
{unknown, UserState};
@@ -535,14 +502,8 @@ verify_fun_always_run_client(Config) when is_list(Config) ->
[{verify, verify_peer},
{verify_fun, FunAndState}
| ClientOpts]}]),
- %% Server error may be {tls_alert,"handshake failure"} or closed depending on timing
- %% this is not a bug it is a circumstance of how tcp works!
- receive
- {Server, ServerError} ->
- ct:log("Server Error ~p~n", [ServerError])
- end,
- ssl_test_lib:check_result(Client, {error, {tls_alert, "handshake failure"}}).
+ ssl_test_lib:check_client_alert(Server, Client, handshake_failure).
%%--------------------------------------------------------------------
verify_fun_always_run_server() ->
@@ -553,7 +514,7 @@ verify_fun_always_run_server(Config) when is_list(Config) ->
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
%% If user verify fun is called correctly we fail the connection.
- %% otherwise we can not tell this case apart form where we miss
+ %% otherwise we cannot tell this case apart form where we miss
%% to call users verify fun
FunAndState = {fun(_,{extension, _}, UserState) ->
{unknown, UserState};
@@ -581,16 +542,8 @@ verify_fun_always_run_server(Config) when is_list(Config) ->
{mfa, {ssl_test_lib,
no_result, []}},
{options, ClientOpts}]),
-
- %% Client error may be {tls_alert, "handshake failure" } or closed depending on timing
- %% this is not a bug it is a circumstance of how tcp works!
- receive
- {Client, ClientError} ->
- ct:log("Client Error ~p~n", [ClientError])
- end,
-
- ssl_test_lib:check_result(Server, {error, {tls_alert, "handshake failure"}}).
-
+
+ ssl_test_lib:check_client_alert(Server, Client, handshake_failure).
%%--------------------------------------------------------------------
cert_expired() ->
@@ -620,8 +573,7 @@ cert_expired(Config) when is_list(Config) ->
{from, self()},
{options, [{verify, verify_peer}, {active, Active} | ClientOpts]}]),
- ssl_test_lib:check_result(Server, {error, {tls_alert, "certificate expired"}},
- Client, {error, {tls_alert, "certificate expired"}}).
+ ssl_test_lib:check_client_alert(Server, Client, certificate_expired).
two_digits_str(N) when N < 10 ->
lists:flatten(io_lib:format("0~p", [N]));
@@ -727,12 +679,8 @@ critical_extension_verify_server(Config) when is_list(Config) ->
{options, [{verify, verify_none}, {active, Active} | ClientOpts]}]),
%% This certificate has a critical extension that we don't
- %% understand. Therefore, verification should fail.
-
- ssl_test_lib:check_result(Server, {error, {tls_alert, "unsupported certificate"}},
- Client, {error, {tls_alert, "unsupported certificate"}}),
-
- ssl_test_lib:close(Server).
+ %% understand. Therefore, verification should fail.
+ ssl_test_lib:check_server_alert(Server, Client, unsupported_certificate).
%%--------------------------------------------------------------------
critical_extension_verify_client() ->
@@ -763,12 +711,7 @@ critical_extension_verify_client(Config) when is_list(Config) ->
{mfa, {ssl_test_lib, ReceiveFunction, []}},
{options, [{verify, verify_peer}, {active, Active} | ClientOpts]}]),
- %% This certificate has a critical extension that we don't
- %% understand. Therefore, verification should fail.
- ssl_test_lib:check_result(Server, {error, {tls_alert, "unsupported certificate"}},
- Client, {error, {tls_alert, "unsupported certificate"}}),
-
- ssl_test_lib:close(Server).
+ ssl_test_lib:check_client_alert(Server, Client, unsupported_certificate).
%%--------------------------------------------------------------------
critical_extension_verify_none() ->
@@ -908,10 +851,7 @@ invalid_signature_server(Config) when is_list(Config) ->
{host, Hostname},
{from, self()},
{options, [{verify, verify_peer} | ClientOpts]}]),
-
- ssl_test_lib:check_result(Server, {error, {tls_alert, "unknown ca"}},
- Client, {error, {tls_alert, "unknown ca"}}).
-
+ ssl_test_lib:check_server_alert(Server, Client, unknown_ca).
%%--------------------------------------------------------------------
invalid_signature_client() ->
@@ -946,9 +886,7 @@ invalid_signature_client(Config) when is_list(Config) ->
{from, self()},
{options, NewClientOpts}]),
- ssl_test_lib:check_result(Server, {error, {tls_alert, "unknown ca"}},
- Client, {error, {tls_alert, "unknown ca"}}).
-
+ ssl_test_lib:check_client_alert(Server, Client, unknown_ca).
%%--------------------------------------------------------------------
@@ -1034,16 +972,7 @@ unknown_server_ca_fail(Config) when is_list(Config) ->
[{verify, verify_peer},
{verify_fun, FunAndState}
| ClientOpts]}]),
- receive
- {Client, {error, {tls_alert, "unknown ca"}}} ->
- receive
- {Server, {error, {tls_alert, "unknown ca"}}} ->
- ok;
- {Server, {error, closed}} ->
- ok
- end
- end.
-
+ ssl_test_lib:check_client_alert(Server, Client, unknown_ca).
%%--------------------------------------------------------------------
unknown_server_ca_accept_verify_none() ->
@@ -1193,11 +1122,7 @@ customize_hostname_check(Config) when is_list(Config) ->
{mfa, {ssl_test_lib, no_result, []}},
{options, ClientOpts}
]),
- ssl_test_lib:check_result(Client1, {error, {tls_alert, "handshake failure"}},
- Server, {error, {tls_alert, "handshake failure"}}),
-
- ssl_test_lib:close(Server),
- ssl_test_lib:close(Client).
+ ssl_test_lib:check_client_alert(Server, Client1, handshake_failure).
incomplete_chain() ->
[{doc,"Test option verify_peer"}].
diff --git a/lib/ssl/test/ssl_crl_SUITE.erl b/lib/ssl/test/ssl_crl_SUITE.erl
index 23c5eaf84d..b2fd3874a8 100644
--- a/lib/ssl/test/ssl_crl_SUITE.erl
+++ b/lib/ssl/test/ssl_crl_SUITE.erl
@@ -238,7 +238,7 @@ crl_verify_revoked(Config) when is_list(Config) ->
end,
crl_verify_error(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts,
- "certificate revoked").
+ certificate_revoked).
crl_verify_no_crl() ->
[{doc,"Verify a simple CRL chain when the CRL is missing"}].
@@ -277,10 +277,10 @@ crl_verify_no_crl(Config) when is_list(Config) ->
%% The error "revocation status undetermined" gets turned
%% into "bad certificate".
crl_verify_error(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts,
- "bad certificate");
+ bad_certificate);
peer ->
crl_verify_error(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts,
- "bad certificate");
+ bad_certificate);
best_effort ->
%% In "best effort" mode, we consider the certificate not
%% to be revoked if we can't find the appropriate CRL.
@@ -341,7 +341,7 @@ crl_hash_dir_collision(Config) when is_list(Config) ->
%% First certificate revoked; first fails, second succeeds.
crl_verify_error(Hostname, ServerNode, ServerOpts1, ClientNode, ClientOpts,
- "certificate revoked"),
+ certificate_revoked),
crl_verify_valid(Hostname, ServerNode, ServerOpts2, ClientNode, ClientOpts),
make_certs:revoke(PrivDir, CA2, "collision-client-2", CertsConfig),
@@ -352,9 +352,9 @@ crl_hash_dir_collision(Config) when is_list(Config) ->
%% Second certificate revoked; both fail.
crl_verify_error(Hostname, ServerNode, ServerOpts1, ClientNode, ClientOpts,
- "certificate revoked"),
+ certificate_revoked),
crl_verify_error(Hostname, ServerNode, ServerOpts2, ClientNode, ClientOpts,
- "certificate revoked"),
+ certificate_revoked),
ok.
@@ -383,8 +383,11 @@ crl_hash_dir_expired(Config) when is_list(Config) ->
{verify, verify_peer}],
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
- %% First make a CRL that expired yesterday.
- make_certs:gencrl(PrivDir, CA, CertsConfig, -24),
+ %% First make a CRL that will expire in one second.
+ make_certs:gencrl_sec(PrivDir, CA, CertsConfig, 1),
+ %% Sleep until the next CRL is due
+ ct:sleep({seconds, 1}),
+
CrlDir = filename:join(PrivDir, "crls"),
populate_crl_hash_dir(PrivDir, CrlDir,
[{CA, "1627b4b0"}],
@@ -397,10 +400,10 @@ crl_hash_dir_expired(Config) when is_list(Config) ->
%% The error "revocation status undetermined" gets turned
%% into "bad certificate".
crl_verify_error(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts,
- "bad certificate");
+ bad_certificate);
peer ->
crl_verify_error(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts,
- "bad certificate");
+ bad_certificate);
best_effort ->
%% In "best effort" mode, we consider the certificate not
%% to be revoked if we can't find the appropriate CRL.
@@ -448,11 +451,8 @@ crl_verify_error(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts, Expec
{host, Hostname},
{from, self()},
{options, ClientOpts}]),
- receive
- {Server, AlertOrClose} ->
- ct:pal("Server Alert or Close ~p", [AlertOrClose])
- end,
- ssl_test_lib:check_result(Client, {error, {tls_alert, ExpectedAlert}}).
+
+ ssl_test_lib:check_client_alert(Server, Client, ExpectedAlert).
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
diff --git a/lib/ssl/test/ssl_dist_bench_SUITE.erl b/lib/ssl/test/ssl_dist_bench_SUITE.erl
index 7409b69639..7e7de5c9bf 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-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2017-2019. All 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,6 +32,7 @@
-export(
[setup/1,
roundtrip/1,
+ sched_utilization/1,
throughput_0/1,
throughput_64/1,
throughput_1024/1,
@@ -42,7 +43,7 @@
throughput_1048576/1]).
%% Debug
--export([payload/1]).
+-export([payload/1, roundtrip_runner/3, setup_runner/3, throughput_runner/4]).
%%%-------------------------------------------------------------------
@@ -56,6 +57,7 @@ groups() ->
%%
{setup, [{repeat, 1}], [setup]},
{roundtrip, [{repeat, 1}], [roundtrip]},
+ {sched_utilization,[{repeat, 1}], [sched_utilization]},
{throughput, [{repeat, 1}],
[throughput_0,
throughput_64,
@@ -69,7 +71,9 @@ groups() ->
all_groups() ->
[{group, setup},
{group, roundtrip},
- {group, throughput}].
+ {group, throughput},
+ {group, sched_utilization}
+ ].
init_per_suite(Config) ->
Digest = sha1,
@@ -351,6 +355,99 @@ roundtrip_client(Pid, Mon, StartTime, N) ->
roundtrip_client(Pid, Mon, StartTime, N - 1)
end.
+%%---------------------------------------
+%% Scheduler utilization at constant load
+
+
+sched_utilization(Config) ->
+ run_nodepair_test(
+ fun(A, B, Prefix, HA, HB) ->
+ sched_utilization(A, B, Prefix, HA, HB, proplists:get_value(ssl_dist, Config))
+ end, Config).
+
+sched_utilization(A, B, Prefix, HA, HB, SSL) ->
+ [] = ssl_apply(HA, erlang, nodes, []),
+ [] = ssl_apply(HB, erlang, nodes, []),
+ {ClientMsacc, ServerMsacc, Msgs} =
+ ssl_apply(HA, fun () -> sched_util_runner(A, B, SSL) end),
+ [B] = ssl_apply(HA, erlang, nodes, []),
+ [A] = ssl_apply(HB, erlang, nodes, []),
+ msacc:print(ClientMsacc),
+ msacc:print(ServerMsacc),
+ ct:pal("Got ~p msgs",[length(Msgs)]),
+ report(Prefix++" Sched Utilization Client",
+ 10000 * msacc:stats(system_runtime,ClientMsacc) /
+ msacc:stats(system_realtime,ClientMsacc), "util 0.01 %"),
+ report(Prefix++" Sched Utilization Server",
+ 10000 * msacc:stats(system_runtime,ServerMsacc) /
+ msacc:stats(system_realtime,ServerMsacc), "util 0.01 %"),
+ ok.
+
+%% 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, true) ->
+ sched_util_runner(A, B, 50);
+sched_util_runner(A, B, false) ->
+ sched_util_runner(A, B, 250);
+sched_util_runner(A, B, Senders) ->
+ Payload = payload(5),
+ [A] = rpc:call(B, erlang, nodes, []),
+ ServerPid =
+ erlang:spawn(
+ B,
+ fun () -> throughput_server() end),
+ ServerMsacc =
+ erlang:spawn(
+ B,
+ fun() ->
+ receive
+ {start,Pid} ->
+ msacc:start(10000),
+ Pid ! {ServerPid,msacc:stats()}
+ end
+ end),
+ spawn_link(
+ fun() ->
+ %% We spawn 250 senders which should mean that we
+ %% have a load of 250 msgs/msec
+ [spawn_link(
+ fun() ->
+ throughput_client(ServerPid,Payload)
+ end) || _ <- lists:seq(1, Senders)]
+ end),
+
+ erlang:system_monitor(self(),[busy_dist_port]),
+ ServerMsacc ! {start,self()},
+ msacc:start(10000),
+ ClientMsaccStats = msacc:stats(),
+ ServerMsaccStats = receive {ServerPid,Stats} -> Stats end,
+ {ClientMsaccStats,ServerMsaccStats, flush()}.
+
+flush() ->
+ receive
+ M ->
+ [M | flush()]
+ after 0 ->
+ []
+ end.
+
+throughput_server() ->
+ receive _ -> ok end,
+ receive _ -> ok end,
+ receive _ -> ok end,
+ receive _ -> ok end,
+ receive _ -> ok end,
+ receive _ -> ok end,
+ receive _ -> ok end,
+ receive _ -> ok end,
+ receive _ -> ok end,
+ receive _ -> ok end,
+ throughput_server().
+
+throughput_client(Pid, Payload) ->
+ Pid ! Payload,
+ receive after 1 -> throughput_client(Pid, Payload) end.
%%-----------------
%% Throughput speed
@@ -407,17 +504,19 @@ throughput(A, B, Prefix, HA, HB, Packets, Size) ->
[] = ssl_apply(HA, erlang, nodes, []),
[] = ssl_apply(HB, erlang, nodes, []),
#{time := Time,
- dist_stats := DistStats,
+ client_dist_stats := ClientDistStats,
client_msacc_stats := ClientMsaccStats,
client_prof := ClientProf,
server_msacc_stats := ServerMsaccStats,
- server_prof := ServerProf} =
+ server_prof := ServerProf,
+ server_gc_before := Server_GC_Before,
+ server_gc_after := Server_GC_After} =
ssl_apply(HA, fun () -> throughput_runner(A, B, Packets, Size) end),
[B] = ssl_apply(HA, erlang, nodes, []),
[A] = ssl_apply(HB, erlang, nodes, []),
ClientMsaccStats =:= undefined orelse
msacc:print(ClientMsaccStats),
- io:format("DistStats: ~p~n", [DistStats]),
+ io:format("ClientDistStats: ~p~n", [ClientDistStats]),
Overhead =
50 % Distribution protocol headers (empirical) (TLS+=54)
+ byte_size(erlang:term_to_binary([0|<<>>])), % Benchmark overhead
@@ -436,6 +535,8 @@ throughput(A, B, Prefix, HA, HB, Packets, Size) ->
end,
io:format("******* ClientProf:~n", []), prof_print(ClientProf),
io:format("******* ServerProf:~n", []), prof_print(ServerProf),
+ 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_"++integer_to_list(Size), Speed, "kB/s").
@@ -457,10 +558,10 @@ throughput_runner(A, B, Rounds, Size) ->
ok
end,
prof_start(),
- {Time,ServerMsaccStats,ServerProf} =
+ #{time := Time} = Result =
throughput_client(ServerPid, ServerMon, Payload, Rounds),
prof_stop(),
- ClientMsaccStats =
+ MsaccStats =
case msacc:available() of
true ->
MStats = msacc:stats(),
@@ -469,15 +570,13 @@ throughput_runner(A, B, Rounds, Size) ->
false ->
undefined
end,
- ClientProf = prof_end(),
+ Prof = prof_end(),
[{_Node,Socket}] = dig_dist_node_sockets(),
DistStats = inet:getstat(Socket),
- #{time => microseconds(Time),
- dist_stats => DistStats,
- client_msacc_stats => ClientMsaccStats,
- client_prof => ClientProf,
- server_msacc_stats => ServerMsaccStats,
- server_prof => ServerProf}.
+ Result#{time := microseconds(Time),
+ client_dist_stats => DistStats,
+ client_msacc_stats => MsaccStats,
+ client_prof => Prof}.
dig_dist_node_sockets() ->
[case DistCtrl of
@@ -500,6 +599,9 @@ dig_dist_node_sockets() ->
throughput_server(Pid, N) ->
+ GC_Before = get_server_gc_info(),
+ %% dbg:tracer(port, dbg:trace_port(file, "throughput_server_gc.log")),
+ %% dbg:p(TLSDistReceiver, garbage_collection),
msacc:available() andalso
begin
msacc:stop(),
@@ -508,9 +610,9 @@ throughput_server(Pid, N) ->
ok
end,
prof_start(),
- throughput_server_loop(Pid, N).
+ throughput_server_loop(Pid, GC_Before, N).
-throughput_server_loop(_Pid, 0) ->
+throughput_server_loop(_Pid, GC_Before, 0) ->
prof_stop(),
MsaccStats =
case msacc:available() of
@@ -523,11 +625,26 @@ throughput_server_loop(_Pid, 0) ->
undefined
end,
Prof = prof_end(),
- exit({ok,MsaccStats,Prof});
-throughput_server_loop(Pid, N) ->
+ %% dbg:flush_trace_port(),
+ exit(#{server_msacc_stats => MsaccStats,
+ server_prof => Prof,
+ server_gc_before => GC_Before,
+ server_gc_after => get_server_gc_info()});
+throughput_server_loop(Pid, GC_Before, N) ->
receive
{Pid, N, _} ->
- throughput_server_loop(Pid, N-1)
+ throughput_server_loop(Pid, GC_Before, N-1)
+ end.
+
+get_server_gc_info() ->
+ case whereis(ssl_connection_sup_dist) of
+ undefined ->
+ undefined;
+ SupPid ->
+ [{_Id,TLSDistReceiver,_Type,_Modules}|_] =
+ supervisor:which_children(SupPid),
+ erlang:process_info(
+ TLSDistReceiver, [garbage_collection,garbage_collection_info])
end.
throughput_client(Pid, Mon, Payload, N) ->
@@ -535,8 +652,8 @@ throughput_client(Pid, Mon, Payload, N) ->
throughput_client_loop(_Pid, Mon, _Payload, 0, StartTime) ->
receive
- {'DOWN', Mon, _, _, {ok,MsaccStats,Prof}} ->
- {elapsed_time(StartTime),MsaccStats,Prof};
+ {'DOWN', Mon, _, _, #{} = Result} ->
+ Result#{time => elapsed_time(StartTime)};
{'DOWN', Mon, _, _, Other} ->
exit(Other)
end;
@@ -554,6 +671,7 @@ prof_start() ->
ok.
-elif(?prof =:= eprof).
prof_start() ->
+ catch eprof:stop(),
{ok,_} = eprof:start(),
profiling = eprof:start_profiling(processes()),
ok.
diff --git a/lib/ssl/test/ssl_dist_test_lib.erl b/lib/ssl/test/ssl_dist_test_lib.erl
index 1b9c853fc4..b8b805b2c4 100644
--- a/lib/ssl/test/ssl_dist_test_lib.erl
+++ b/lib/ssl/test/ssl_dist_test_lib.erl
@@ -144,6 +144,8 @@ mk_node_cmdline(ListenPort, Name, Args) ->
++ integer_to_list(ListenPort) ++ " "
++ Args ++ " "
++ "-env ERL_CRASH_DUMP " ++ Pwd ++ "/erl_crash_dump." ++ Name ++ " "
+ ++ "-kernel inet_dist_connect_options \"[{recbuf,12582912},{sndbuf,12582912},{high_watermark,8388608},{low_watermark,4194304}]\" "
+ ++ "-kernel inet_dist_listen_options \"[{recbuf,12582912},{sndbuf,12582912},{high_watermark,8388608},{low_watermark,4194304}]\" "
++ "-kernel error_logger \"{file,\\\"" ++ Pwd ++ "/error_log." ++ Name ++ "\\\"}\" "
++ "-setcookie " ++ atom_to_list(erlang:get_cookie()).
@@ -179,8 +181,9 @@ check_ssl_node_up(Socket, Name, Bin) ->
Parent = self(),
Go = make_ref(),
%% Spawn connection handler on test server side
- Pid = spawn_link(
+ Pid = spawn(
fun () ->
+ link(group_leader()),
receive Go -> ok end,
process_flag(trap_exit, true),
tstsrvr_con_loop(Name, Socket, Parent)
diff --git a/lib/ssl/test/ssl_eqc_SUITE.erl b/lib/ssl/test/ssl_eqc_SUITE.erl
new file mode 100644
index 0000000000..bd36d35c02
--- /dev/null
+++ b/lib/ssl/test/ssl_eqc_SUITE.erl
@@ -0,0 +1,58 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2015-2015. 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%
+%%
+%%
+
+-module(ssl_eqc_SUITE).
+
+-compile(export_all).
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+
+all() ->
+ [
+ tls_handshake_encoding
+ ].
+
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ ct_property_test:init_per_suite(Config).
+end_per_suite(Config) ->
+ Config.
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_,Config) ->
+ Config.
+
+init_per_testcase(_, Config0) ->
+ Config0.
+
+end_per_testcase(_TestCase, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Test Cases --------------------------------------------------------
+%%--------------------------------------------------------------------
+
+tls_handshake_encoding(Config) when is_list(Config) ->
+ %% manual test: proper:quickcheck(ssl_eqc_handshake:prop_tls_hs_encode_decode()).
+ true = ct_property_test:quickcheck(ssl_eqc_handshake:prop_tls_hs_encode_decode(),
+ Config).
diff --git a/lib/ssl/test/ssl_handshake_SUITE.erl b/lib/ssl/test/ssl_handshake_SUITE.erl
index 1fa6029963..1b432970b6 100644
--- a/lib/ssl/test/ssl_handshake_SUITE.erl
+++ b/lib/ssl/test/ssl_handshake_SUITE.erl
@@ -25,6 +25,7 @@
-compile(export_all).
-include_lib("common_test/include/ct.hrl").
+-include("ssl_alert.hrl").
-include("ssl_handshake.hrl").
-include("ssl_internal.hrl").
-include("tls_handshake.hrl").
@@ -42,7 +43,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,
- unorded_chain,
+ unorded_chain, signature_algorithms,
encode_decode_srp].
%%--------------------------------------------------------------------
@@ -57,7 +58,9 @@ init_per_group(_GroupName, Config) ->
end_per_group(_,Config) ->
Config.
-init_per_testcase(ignore_hassign_extension_pre_tls_1_2, Config0) ->
+init_per_testcase(TC, Config0) when
+ TC =:= ignore_hassign_extension_pre_tls_1_2 orelse
+ TC =:= signature_algorithms ->
catch crypto:stop(),
try crypto:start() of
ok ->
@@ -106,15 +109,13 @@ decode_hello_handshake(_Config) ->
#ssl_options{}),
{Hello, _Data} = hd(Records),
- #renegotiation_info{renegotiated_connection = <<0>>}
- = (Hello#server_hello.extensions)#hello_extensions.renegotiation_info.
-
+ Extensions = Hello#server_hello.extensions,
+ #{renegotiation_info := #renegotiation_info{renegotiated_connection = <<0>>}} = Extensions.
decode_single_hello_extension_correctly(_Config) ->
Renegotiation = <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(1), 0>>,
- Extensions = ssl_handshake:decode_hello_extensions(Renegotiation),
- #renegotiation_info{renegotiated_connection = <<0>>}
- = Extensions#hello_extensions.renegotiation_info.
+ Extensions = ssl_handshake:decode_extensions(Renegotiation, {3,3}, undefined),
+ #{renegotiation_info := #renegotiation_info{renegotiated_connection = <<0>>}} = Extensions.
decode_supported_elliptic_curves_hello_extension_correctly(_Config) ->
% List of supported and unsupported curves (RFC4492:S5.1.1)
@@ -125,37 +126,34 @@ 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
- #hello_extensions{elliptic_curves = DecodedCurves} = ssl_handshake:decode_hello_extensions(Extension),
- #elliptic_curves{elliptic_curve_list = [?sect233k1, ?sect193r2]} = DecodedCurves.
+ Extensions = ssl_handshake:decode_hello_extensions(Extension, {3,2}, {3,2}, 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>>),
- #renegotiation_info{renegotiated_connection = <<0>>}
- = Extensions#hello_extensions.renegotiation_info.
+ Extensions = ssl_handshake:decode_hello_extensions(<<FourByteUnknown/binary, Renegotiation/binary>>, {3,2}, {3,2}, client),
+ #{renegotiation_info := #renegotiation_info{renegotiated_connection = <<0>>}} = Extensions.
+
encode_single_hello_sni_extension_correctly(_Config) ->
- Exts = #hello_extensions{sni = #sni{hostname = "test.com"}},
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>>,
ExtSize = byte_size(SNI),
HelloExt = <<ExtSize:16/unsigned-big-integer, SNI/binary>>,
- Encoded = ssl_handshake:encode_hello_extensions(Exts),
+ Encoded = ssl_handshake:encode_extensions([#sni{hostname = "test.com"}]),
HelloExt = Encoded.
decode_single_hello_sni_extension_correctly(_Config) ->
- Exts = #hello_extensions{sni = #sni{hostname = "test.com"}},
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),
- Exts = Decoded.
+ Decoded = ssl_handshake:decode_hello_extensions(SNI, {3,3}, {3,3}, client),
+ #{sni := #sni{hostname = "test.com"}} = Decoded.
decode_empty_server_sni_correctly(_Config) ->
- Exts = #hello_extensions{sni = #sni{hostname = ""}},
SNI = <<?UINT16(?SNI_EXT),?UINT16(0)>>,
- Decoded = ssl_handshake:decode_hello_extensions(SNI),
- Exts = Decoded.
+ Decoded = ssl_handshake:decode_hello_extensions(SNI, {3,3}, {3,3}, server),
+ #{sni := #sni{hostname = ""}} = Decoded.
select_proper_tls_1_2_rsa_default_hashsign(_Config) ->
@@ -170,11 +168,11 @@ ignore_hassign_extension_pre_tls_1_2(Config) ->
Opts = proplists:get_value(server_opts, 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}]},
- {sha512, rsa} = ssl_handshake:select_hashsign(HashSigns, Cert, ecdhe_rsa, tls_v1:default_signature_algs({3,3}), {3,3}),
+ HashSigns = #hash_sign_algos{hash_sign_algos = [{sha512, rsa}, {sha, dsa}, {sha, rsa}]},
+ {sha512, rsa} = ssl_handshake:select_hashsign({HashSigns, undefined}, Cert, ecdhe_rsa, tls_v1:default_signature_algs({3,3}), {3,3}),
%%% Ignore
- {md5sha, rsa} = ssl_handshake:select_hashsign(HashSigns, Cert, ecdhe_rsa, tls_v1:default_signature_algs({3,2}), {3,2}),
- {md5sha, rsa} = ssl_handshake:select_hashsign(HashSigns, Cert, ecdhe_rsa, tls_v1:default_signature_algs({3,0}), {3,0}).
+ {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}).
unorded_chain(Config) when is_list(Config) ->
DefConf = ssl_test_lib:default_cert_chain_conf(),
@@ -195,30 +193,78 @@ unorded_chain(Config) when is_list(Config) ->
ssl_certificate:certificate_chain(PeerCert, ets:new(foo, []), ExtractedCerts, UnordedChain).
encode_decode_srp(_Config) ->
- Exts = #hello_extensions{
- 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
- },
- EncodedExts = <<0,20, % Length
+ 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"
- 0,12, % SRP extension
- 0,4, % Length
- 3, % srp_I length
- 102,111,111>>, % username = "foo"
- EncodedExts = ssl_handshake:encode_hello_extensions(Exts),
- Exts = ssl_handshake:decode_hello_extensions({client, EncodedExts}).
+ 98,97,114>>, % hostname = "bar"
+ EncodedExts0 = <<?UINT16(_),EncodedExts/binary>> =
+ ssl_handshake:encode_hello_extensions(Exts),
+ Exts = ssl_handshake:decode_hello_extensions(EncodedExts, {3,3}, {3,3}, client).
+signature_algorithms(Config) ->
+ Opts = proplists:get_value(server_opts, Config),
+ CertFile = proplists:get_value(certfile, Opts),
+ io:format("Cert = ~p~n", [CertFile]),
+ [{_, Cert, _}] = ssl_test_lib:pem_to_der(CertFile),
+ HashSigns0 = #hash_sign_algos{
+ hash_sign_algos = [{sha512, rsa},
+ {sha, dsa},
+ {sha, rsa}]},
+ Schemes0 = #signature_algorithms_cert{
+ signature_scheme_list = [rsa_pkcs1_sha1,
+ ecdsa_sha1]},
+ {sha512, rsa} = ssl_handshake:select_hashsign(
+ {HashSigns0, Schemes0},
+ Cert, ecdhe_rsa,
+ tls_v1:default_signature_algs({3,3}),
+ {3,3}),
+ HashSigns1 = #hash_sign_algos{
+ hash_sign_algos = [{sha, dsa},
+ {sha, rsa}]},
+ {sha, rsa} = ssl_handshake:select_hashsign(
+ {HashSigns1, Schemes0},
+ Cert, ecdhe_rsa,
+ tls_v1:default_signature_algs({3,3}),
+ {3,3}),
+ Schemes1 = #signature_algorithms_cert{
+ signature_scheme_list = [rsa_pkcs1_sha256,
+ ecdsa_sha1]},
+ %% Signature not supported
+ #alert{} = ssl_handshake:select_hashsign(
+ {HashSigns1, Schemes1},
+ Cert, ecdhe_rsa,
+ tls_v1:default_signature_algs({3,3}),
+ {3,3}),
+ %% No scheme, hashsign is used
+ {sha, rsa} = ssl_handshake:select_hashsign(
+ {HashSigns1, undefined},
+ Cert, ecdhe_rsa,
+ tls_v1:default_signature_algs({3,3}),
+ {3,3}),
+ 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}).
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
diff --git a/lib/ssl/test/ssl_npn_handshake_SUITE.erl b/lib/ssl/test/ssl_npn_handshake_SUITE.erl
index 1c7d6b5f9f..878e983bb9 100644
--- a/lib/ssl/test/ssl_npn_handshake_SUITE.erl
+++ b/lib/ssl/test/ssl_npn_handshake_SUITE.erl
@@ -64,13 +64,12 @@ next_protocol_not_supported() ->
npn_not_supported_server
].
-init_per_suite(Config) ->
+init_per_suite(Config0) ->
catch crypto:stop(),
try crypto:start() of
ok ->
ssl_test_lib:clean_start(),
- {ok, _} = make_certs:all(proplists:get_value(data_dir, Config),
- proplists:get_value(priv_dir, Config)),
+ Config = ssl_test_lib:make_rsa_cert(Config0),
ssl_test_lib:cert_options(Config)
catch _:_ ->
{skip, "Crypto did not start"}
@@ -196,10 +195,10 @@ client_negotiate_server_does_not_support(Config) when is_list(Config) ->
renegotiate_from_client_after_npn_handshake(Config) when is_list(Config) ->
Data = "hello world",
- ClientOpts0 = ssl_test_lib:ssl_options(client_opts, Config),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ClientOpts = [{client_preferred_next_protocols,
{client, [<<"http/1.0">>], <<"http/1.1">>}}] ++ ClientOpts0,
- ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
ServerOpts = [{next_protocols_advertised,
[<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}] ++ ServerOpts0,
ExpectedProtocol = {ok, <<"http/1.0">>},
@@ -221,7 +220,7 @@ renegotiate_from_client_after_npn_handshake(Config) when is_list(Config) ->
%--------------------------------------------------------------------------------
npn_not_supported_client(Config) when is_list(Config) ->
- ClientOpts0 = ssl_test_lib:ssl_options(client_opts, Config),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
PrefProtocols = {client_preferred_next_protocols,
{client, [<<"http/1.0">>], <<"http/1.1">>}},
ClientOpts = [PrefProtocols] ++ ClientOpts0,
@@ -236,7 +235,7 @@ npn_not_supported_client(Config) when is_list(Config) ->
%--------------------------------------------------------------------------------
npn_not_supported_server(Config) when is_list(Config)->
- ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
AdvProtocols = {next_protocols_advertised, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]},
ServerOpts = [AdvProtocols] ++ ServerOpts0,
@@ -244,63 +243,24 @@ npn_not_supported_server(Config) when is_list(Config)->
%--------------------------------------------------------------------------------
npn_handshake_session_reused(Config) when is_list(Config)->
- ClientOpts0 = ssl_test_lib:ssl_options(client_opts, Config),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ClientOpts = [{client_preferred_next_protocols,
{client, [<<"http/1.0">>], <<"http/1.1">>}}] ++ ClientOpts0,
- ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
ServerOpts =[{next_protocols_advertised,
[<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}] ++ ServerOpts0,
- {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, session_info_result, []}},
- {options, 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, no_result_msg, []}},
- {options, ClientOpts}]),
-
- SessionInfo =
- receive
- {Server, Info} ->
- Info
- end,
-
- Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}},
-
- %% Make sure session is registered
- ct:sleep(?SLEEP),
-
- Client1 =
- ssl_test_lib:start_client([{node, ClientNode},
- {port, Port}, {host, Hostname},
- {mfa, {ssl_test_lib, session_info_result, []}},
- {from, self()}, {options, ClientOpts}]),
-
- receive
- {Client1, SessionInfo} ->
- ok;
- {Client1, Other} ->
- ct:fail(Other)
- end,
+ ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config).
- ssl_test_lib:close(Server),
- ssl_test_lib:close(Client),
- ssl_test_lib:close(Client1).
-
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
run_npn_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedProtocol) ->
Data = "hello world",
- ClientOpts0 = ssl_test_lib:ssl_options(client_opts, Config),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ClientOpts = ClientExtraOpts ++ ClientOpts0,
- ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
ServerOpts = ServerExtraOpts ++ ServerOpts0,
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
diff --git a/lib/ssl/test/ssl_npn_hello_SUITE.erl b/lib/ssl/test/ssl_npn_hello_SUITE.erl
index 35af666e9e..46734ba180 100644
--- a/lib/ssl/test/ssl_npn_hello_SUITE.erl
+++ b/lib/ssl/test/ssl_npn_hello_SUITE.erl
@@ -71,44 +71,46 @@ encode_and_decode_client_hello_test(Config) ->
Version = ssl_test_lib:protocol_version(Config),
{[{DecodedHandshakeMessage, _Raw}], _} =
tls_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>, #ssl_options{}),
- NextProtocolNegotiation = (DecodedHandshakeMessage#client_hello.extensions)#hello_extensions.next_protocol_negotiation,
- NextProtocolNegotiation = undefined.
+ Extensions = DecodedHandshakeMessage#client_hello.extensions,
+ #{next_protocol_negotiation := undefined} = Extensions.
%%--------------------------------------------------------------------
encode_and_decode_npn_client_hello_test(Config) ->
HandShakeData = create_client_handshake(#next_protocol_negotiation{extension_data = <<>>}),
Version = ssl_test_lib:protocol_version(Config),
{[{DecodedHandshakeMessage, _Raw}], _} =
tls_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>, #ssl_options{}),
- NextProtocolNegotiation = (DecodedHandshakeMessage#client_hello.extensions)#hello_extensions.next_protocol_negotiation,
- NextProtocolNegotiation = #next_protocol_negotiation{extension_data = <<>>}.
+ Extensions = DecodedHandshakeMessage#client_hello.extensions,
+ #{next_protocol_negotiation := #next_protocol_negotiation{extension_data = <<>>}} = Extensions.
%%--------------------------------------------------------------------
encode_and_decode_server_hello_test(Config) ->
HandShakeData = create_server_handshake(undefined),
Version = ssl_test_lib:protocol_version(Config),
{[{DecodedHandshakeMessage, _Raw}], _} =
tls_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>, #ssl_options{}),
- NextProtocolNegotiation = (DecodedHandshakeMessage#server_hello.extensions)#hello_extensions.next_protocol_negotiation,
- NextProtocolNegotiation = undefined.
+ Extensions = DecodedHandshakeMessage#server_hello.extensions,
+ #{next_protocol_negotiation := undefined} = Extensions.
+
%%--------------------------------------------------------------------
encode_and_decode_npn_server_hello_test(Config) ->
HandShakeData = create_server_handshake(#next_protocol_negotiation{extension_data = <<6, "spdy/2">>}),
Version = ssl_test_lib:protocol_version(Config),
{[{DecodedHandshakeMessage, _Raw}], _} =
tls_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>, #ssl_options{}),
- NextProtocolNegotiation = (DecodedHandshakeMessage#server_hello.extensions)#hello_extensions.next_protocol_negotiation,
- ct:log("~p ~n", [NextProtocolNegotiation]),
- NextProtocolNegotiation = #next_protocol_negotiation{extension_data = <<6, "spdy/2">>}.
+ Extensions = DecodedHandshakeMessage#server_hello.extensions,
+ ct:log("~p ~n", [Extensions]),
+ #{next_protocol_negotiation := #next_protocol_negotiation{extension_data = <<6, "spdy/2">>}} = Extensions.
%%--------------------------------------------------------------------
create_server_hello_with_no_advertised_protocols_test(_Config) ->
- Hello = ssl_handshake:server_hello(<<>>, {3, 0}, create_connection_states(), #hello_extensions{}),
- undefined = (Hello#server_hello.extensions)#hello_extensions.next_protocol_negotiation.
+ Hello = ssl_handshake:server_hello(<<>>, {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_extensions{next_protocol_negotiation = [<<"spdy/1">>, <<"http/1.0">>, <<"http/1.1">>]}),
- [<<"spdy/1">>, <<"http/1.0">>, <<"http/1.1">>] =
- (Hello#server_hello.extensions)#hello_extensions.next_protocol_negotiation.
+ #{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.
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
@@ -120,9 +122,8 @@ create_client_handshake(Npn) ->
session_id = <<>>,
cipher_suites = [?TLS_DHE_DSS_WITH_DES_CBC_SHA],
compression_methods = "",
- extensions = #hello_extensions{
- next_protocol_negotiation = Npn,
- renegotiation_info = #renegotiation_info{}}
+ extensions = #{next_protocol_negotiation => Npn,
+ renegotiation_info => #renegotiation_info{}}
}, Vsn).
create_server_handshake(Npn) ->
@@ -133,9 +134,8 @@ create_server_handshake(Npn) ->
session_id = <<>>,
cipher_suite = ?TLS_DHE_DSS_WITH_DES_CBC_SHA,
compression_method = 1,
- extensions = #hello_extensions{
- next_protocol_negotiation = Npn,
- renegotiation_info = #renegotiation_info{}}
+ extensions = #{next_protocol_negotiation => Npn,
+ renegotiation_info => #renegotiation_info{}}
}, Vsn).
create_connection_states() ->
@@ -146,5 +146,5 @@ create_connection_states() ->
}
},
current_read => #{secure_renegotiation => false
- }
+ }
}.
diff --git a/lib/ssl/test/ssl_payload_SUITE.erl b/lib/ssl/test/ssl_payload_SUITE.erl
index 0f5a041a1b..27b9c258a0 100644
--- a/lib/ssl/test/ssl_payload_SUITE.erl
+++ b/lib/ssl/test/ssl_payload_SUITE.erl
@@ -574,50 +574,42 @@ client_active_once_server_close(
[{node, ClientNode}, {port, Port},
{host, Hostname},
{from, self()},
- {mfa, {?MODULE, active_once_recv, [Length]}},
+ {mfa, {ssl_test_lib, active_once_recv, [Length]}},
{options,[{active, once}, {mode, binary} | ClientOpts]}]),
%%
ssl_test_lib:check_result(Server, ok, Client, ok).
-send(Socket, Data, Count, Verify) ->
- send(Socket, Data, Count, <<>>, Verify).
-%%
-send(_Socket, _Data, 0, Acc, _Verify) ->
- Acc;
-send(Socket, Data, Count, Acc, Verify) ->
+send(_Socket, _Data, 0, _) ->
+ ok;
+send(Socket, Data, Count, RecvEcho) ->
ok = ssl:send(Socket, Data),
- NewAcc = Verify(Acc),
- send(Socket, Data, Count - 1, NewAcc, Verify).
-
+ RecvEcho(),
+ send(Socket, Data, Count - 1, RecvEcho).
send_close(Socket, Data) ->
ok = ssl:send(Socket, Data),
ssl:close(Socket).
-
+
sender(Socket, Data) ->
ct:log("Sender recv: ~p~n", [ssl:getopts(Socket, [active])]),
- <<>> =
- send(
- Socket, Data, 100,
- fun(Acc) -> verify_recv(Socket, Data, Acc) end),
- ok.
+ send(Socket, Data, 100,
+ fun() ->
+ ssl_test_lib:recv_disregard(Socket, byte_size(Data))
+ end).
sender_active_once(Socket, Data) ->
ct:log("Sender active once: ~p~n", [ssl:getopts(Socket, [active])]),
- <<>> =
- send(
- Socket, Data, 100,
- fun(Acc) -> verify_active_once(Socket, Data, Acc) end),
- ok.
+ send(Socket, Data, 100,
+ fun() ->
+ ssl_test_lib:active_once_disregard(Socket, byte_size(Data))
+ end).
sender_active(Socket, Data) ->
ct:log("Sender active: ~p~n", [ssl:getopts(Socket, [active])]),
- <<>> =
- send(
- Socket, Data, 100,
- fun(Acc) -> verify_active(Socket, Data, Acc) end),
- ok.
-
+ send(Socket, Data, 100,
+ fun() ->
+ ssl_test_lib:active_disregard(Socket, byte_size(Data))
+ end).
echoer(Socket, Size) ->
ct:log("Echoer recv: ~p~n", [ssl:getopts(Socket, [active])]),
@@ -633,108 +625,32 @@ echoer_active(Socket, Size) ->
%% Receive Size bytes
+echo_recv(_Socket, 0) ->
+ ok;
echo_recv(Socket, Size) ->
{ok, Data} = ssl:recv(Socket, 0),
ok = ssl:send(Socket, Data),
- NewSize = Size - byte_size(Data),
- if
- 0 < NewSize ->
- echo_recv(Socket, NewSize);
- 0 == NewSize ->
- ok
- end.
-
-%% Verify that received data is SentData, return any superflous data
-verify_recv(Socket, SentData, Acc) ->
- {ok, NewData} = ssl:recv(Socket, 0),
- SentSize = byte_size(SentData),
- NewAcc = <<Acc/binary, NewData/binary>>,
- NewSize = byte_size(NewAcc),
- if
- SentSize < NewSize ->
- {SentData,Rest} = split_binary(NewAcc, SentSize),
- Rest;
- NewSize < SentSize ->
- verify_recv(Socket, SentData, NewAcc);
- true ->
- SentData = NewAcc,
- <<>>
- end.
+ echo_recv(Socket, Size - byte_size(Data)).
%% Receive Size bytes
+echo_active_once(_Socket, 0) ->
+ ok;
echo_active_once(Socket, Size) ->
receive
{ssl, Socket, Data} ->
ok = ssl:send(Socket, Data),
NewSize = Size - byte_size(Data),
ssl:setopts(Socket, [{active, once}]),
- if
- 0 < NewSize ->
- echo_active_once(Socket, NewSize);
- 0 == NewSize ->
- ok
- end
+ echo_active_once(Socket, NewSize)
end.
-%% Verify that received data is SentData, return any superflous data
-verify_active_once(Socket, SentData, Acc) ->
- receive
- {ssl, Socket, Data} ->
- SentSize = byte_size(SentData),
- NewAcc = <<Acc/binary, Data/binary>>,
- NewSize = byte_size(NewAcc),
- ssl:setopts(Socket, [{active, once}]),
- if
- SentSize < NewSize ->
- {SentData,Rest} = split_binary(NewAcc, SentSize),
- Rest;
- NewSize < SentSize ->
- verify_active_once(Socket, SentData, NewAcc);
- true ->
- SentData = NewAcc,
- <<>>
- end
- end.
-
-
%% Receive Size bytes
+echo_active(_Socket, 0) ->
+ ok;
echo_active(Socket, Size) ->
receive
{ssl, Socket, Data} ->
ok = ssl:send(Socket, Data),
- NewSize = Size - byte_size(Data),
- if
- 0 < NewSize ->
- echo_active(Socket, NewSize);
- 0 == NewSize ->
- ok
- end
- end.
-
-%% Verify that received data is SentData, return any superflous data
-verify_active(Socket, SentData, Acc) ->
- receive
- {ssl, Socket, Data} ->
- SentSize = byte_size(SentData),
- NewAcc = <<Acc/binary, Data/binary>>,
- NewSize = byte_size(NewAcc),
- if
- SentSize < NewSize ->
- {SentData,Rest} = split_binary(NewAcc, SentSize),
- Rest;
- NewSize < SentSize ->
- verify_active(Socket, SentData, NewAcc);
- true ->
- SentData = NewAcc,
- <<>>
- end
- end.
-
-active_once_recv(_Socket, 0) ->
- ok;
-active_once_recv(Socket, N) ->
- receive
- {ssl, Socket, Bytes} ->
- ssl:setopts(Socket, [{active, once}]),
- active_once_recv(Socket, N-byte_size(Bytes))
- end.
+ echo_active(Socket, Size - byte_size(Data))
+ end.
+
diff --git a/lib/ssl/test/ssl_pem_cache_SUITE.erl b/lib/ssl/test/ssl_pem_cache_SUITE.erl
index 25d2cb300d..6f11e2bbe8 100644
--- a/lib/ssl/test/ssl_pem_cache_SUITE.erl
+++ b/lib/ssl/test/ssl_pem_cache_SUITE.erl
@@ -44,11 +44,8 @@ init_per_suite(Config0) ->
try crypto:start() of
ok ->
ssl_test_lib:clean_start(),
- %% make rsa certs using oppenssl
- {ok, _} = make_certs:all(proplists:get_value(data_dir, Config0),
- proplists:get_value(priv_dir, Config0)),
- Config1 = ssl_test_lib:make_dsa_cert(Config0),
- ssl_test_lib:cert_options(Config1)
+ %% make rsa certs
+ ssl_test_lib:make_rsa_cert(Config0)
catch _:_ ->
{skip, "Crypto did not start"}
end.
@@ -86,8 +83,8 @@ pem_cleanup() ->
[{doc, "Test pem cache invalidate mechanism"}].
pem_cleanup(Config)when is_list(Config) ->
process_flag(trap_exit, true),
- ClientOpts = proplists:get_value(client_verification_opts, Config),
- ServerOpts = proplists:get_value(server_verification_opts, Config),
+ ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
+ ServerOpts = proplists:get_value(server_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server =
@@ -118,8 +115,8 @@ invalid_insert() ->
invalid_insert(Config)when is_list(Config) ->
process_flag(trap_exit, true),
- ClientOpts = proplists:get_value(client_verification_opts, Config),
- ServerOpts = proplists:get_value(server_verification_opts, Config),
+ ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
+ ServerOpts = proplists:get_value(server_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
BadClientOpts = [{cacertfile, "tmp/does_not_exist.pem"} | proplists:delete(cacertfile, ClientOpts)],
Server =
diff --git a/lib/ssl/test/ssl_rfc_5869_SUITE.erl b/lib/ssl/test/ssl_rfc_5869_SUITE.erl
new file mode 100644
index 0000000000..8b2d1c2082
--- /dev/null
+++ b/lib/ssl/test/ssl_rfc_5869_SUITE.erl
@@ -0,0 +1,316 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2018. All 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_rfc_5869_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+all() ->
+ [sha_256_basic,
+ sha_256_long,
+ sha_256_no_salt,
+ sha_basic,
+ sha_long,
+ sha_no_salt,
+ sha_default_salt
+ ].
+
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ catch crypto:stop(),
+ try crypto:start() of
+ ok ->
+ Config
+ catch _:_ ->
+ {skip, "Crypto did not start"}
+ end.
+
+end_per_suite(_Config) ->
+ application:stop(crypto).
+
+%%--------------------------------------------------------------------
+init_per_testcase(_TestCase, Config) ->
+ ct:timetrap({seconds, 5}),
+ Config.
+
+end_per_testcase(_TestCase, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Test Cases --------------------------------------------------------
+%%--------------------------------------------------------------------
+
+sha_256_basic() ->
+ [{doc, "Basic test case with SHA-256"}].
+sha_256_basic(Config) when is_list(Config) ->
+ %% Hash = SHA-256
+ %% IKM = 0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b (22 octets)
+ %% salt = 0x000102030405060708090a0b0c (13 octets)
+ %% info = 0xf0f1f2f3f4f5f6f7f8f9 (10 octets)
+ %% L = 42
+ %% PRK = 0x077709362c2e32df0ddc3f0dc47bba63
+ %% 90b6c73bb50f9c3122ec844ad7c2b3e5 (32 octets)
+ %% OKM = 0x3cb25f25faacd57a90434f64d0362f2a
+ %% 2d2d0a90cf1a5a4c5db02d56ecc4c5bf
+ %% 34007208d5b887185865 (42 octets)
+ IKM = hexstr2bin("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
+ Salt = hexstr2bin("000102030405060708090a0b0c"),
+ Info = hexstr2bin("f0f1f2f3f4f5f6f7f8f9"),
+ PRK = hexstr2bin("077709362c2e32df0ddc3f0dc47bba63"
+ "90b6c73bb50f9c3122ec844ad7c2b3e5"),
+ OKM = hexstr2bin("3cb25f25faacd57a90434f64d0362f2a"
+ "2d2d0a90cf1a5a4c5db02d56ecc4c5bf"
+ "34007208d5b887185865"),
+ hkdf_test(sha256, Salt, IKM, PRK, Info, 42, OKM).
+
+sha_256_long() ->
+ [{doc, "Test with SHA-256 and longer inputs/outputs"}].
+sha_256_long(Config) when is_list(Config) ->
+ %% Hash = SHA-256
+ %% IKM = 0x000102030405060708090a0b0c0d0e0f
+ %% 101112131415161718191a1b1c1d1e1f
+ %% 202122232425262728292a2b2c2d2e2f
+ %% 303132333435363738393a3b3c3d3e3f
+ %% 404142434445464748494a4b4c4d4e4f (80 octets)
+ %% salt = 0x606162636465666768696a6b6c6d6e6f
+ %% 707172737475767778797a7b7c7d7e7f
+ %% 808182838485868788898a8b8c8d8e8f
+ %% 909192939495969798999a9b9c9d9e9f
+ %% a0a1a2a3a4a5a6a7a8a9aaabacadaeaf (80 octets)
+ %% info = 0xb0b1b2b3b4b5b6b7b8b9babbbcbdbebf
+ %% c0c1c2c3c4c5c6c7c8c9cacbcccdcecf
+ %% d0d1d2d3d4d5d6d7d8d9dadbdcdddedf
+ %% e0e1e2e3e4e5e6e7e8e9eaebecedeeef
+ %% f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff (80 octets)
+ %% L = 82
+
+ %% PRK = 0x06a6b88c5853361a06104c9ceb35b45c
+ %% ef760014904671014a193f40c15fc244 (32 octets)
+ %% OKM = 0xb11e398dc80327a1c8e7f78c596a4934
+ %% 4f012eda2d4efad8a050cc4c19afa97c
+ %% 59045a99cac7827271cb41c65e590e09
+ %% da3275600c2f09b8367793a9aca3db71
+ %% cc30c58179ec3e87c14c01d5c1f3434f
+ %% 1d87 (82 octets)
+ IKM = hexstr2bin("000102030405060708090a0b0c0d0e0f"
+ "101112131415161718191a1b1c1d1e1f"
+ "202122232425262728292a2b2c2d2e2f"
+ "303132333435363738393a3b3c3d3e3f"
+ "404142434445464748494a4b4c4d4e4f"
+ ),
+ Salt = hexstr2bin("606162636465666768696a6b6c6d6e6f"
+ "707172737475767778797a7b7c7d7e7f"
+ "808182838485868788898a8b8c8d8e8f"
+ "909192939495969798999a9b9c9d9e9f"
+ "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
+ ),
+ Info = hexstr2bin("b0b1b2b3b4b5b6b7b8b9babbbcbdbebf"
+ "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
+ "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"
+ "e0e1e2e3e4e5e6e7e8e9eaebecedeeef"
+ "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
+ ),
+ PRK = hexstr2bin("06a6b88c5853361a06104c9ceb35b45c"
+ "ef760014904671014a193f40c15fc244"),
+ OKM = hexstr2bin("b11e398dc80327a1c8e7f78c596a4934"
+ "4f012eda2d4efad8a050cc4c19afa97c"
+ "59045a99cac7827271cb41c65e590e09"
+ "da3275600c2f09b8367793a9aca3db71"
+ "cc30c58179ec3e87c14c01d5c1f3434f"
+ "1d87"
+ ),
+ hkdf_test(sha256, Salt, IKM, PRK, Info, 82, OKM).
+sha_256_no_salt() ->
+ [{doc, "Test with SHA-256 and zero-length salt/info"}].
+sha_256_no_salt(Config) when is_list(Config) ->
+ %% Hash = SHA-256
+ %% IKM = 0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b (22 octets)
+ %% salt = (0 octets)
+ %% info = (0 octets)
+ %% L = 42
+
+ %% PRK = 0x19ef24a32c717b167f33a91d6f648bdf
+ %% 96596776afdb6377ac434c1c293ccb04 (32 octets)
+ %% OKM = 0x8da4e775a563c18f715f802a063c5a31
+ %% b8a11f5c5ee1879ec3454e5f3c738d2d
+ %% 9d201395faa4b61a96c8 (42 octets)
+ IKM = hexstr2bin("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
+ Salt = <<>>,
+ Info = <<>>,
+ PRK = hexstr2bin("19ef24a32c717b167f33a91d6f648bdf"
+ "96596776afdb6377ac434c1c293ccb04"),
+ OKM = hexstr2bin("8da4e775a563c18f715f802a063c5a31"
+ "b8a11f5c5ee1879ec3454e5f3c738d2d"
+ "9d201395faa4b61a96c8"),
+ hkdf_test(sha256, Salt, IKM, PRK, Info, 42, OKM).
+
+sha_basic() ->
+ [{doc, " Basic test case with SHA-1"}].
+sha_basic(Config) when is_list(Config) ->
+ %% Hash = SHA-1
+ %% IKM = 0x0b0b0b0b0b0b0b0b0b0b0b (11 octets)
+ %% salt = 0x000102030405060708090a0b0c (13 octets)
+ %% info = 0xf0f1f2f3f4f5f6f7f8f9 (10 octets)
+ %% L = 42
+
+ %% PRK = 0x9b6c18c432a7bf8f0e71c8eb88f4b30baa2ba243 (20 octets)
+ %% OKM = 0x085a01ea1b10f36933068b56efa5ad81
+ %% a4f14b822f5b091568a9cdd4f155fda2
+ %% c22e422478d305f3f896 (42 octets)
+ IKM = hexstr2bin("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
+ Salt = hexstr2bin("000102030405060708090a0b0c"),
+ Info = hexstr2bin("f0f1f2f3f4f5f6f7f8f9"),
+ PRK = hexstr2bin("077709362c2e32df0ddc3f0dc47bba63"
+ "90b6c73bb50f9c3122ec844ad7c2b3e5"),
+ OKM = hexstr2bin("3cb25f25faacd57a90434f64d0362f2a"
+ "2d2d0a90cf1a5a4c5db02d56ecc4c5bf"
+ "34007208d5b887185865"),
+ hkdf_test(sha256, Salt, IKM, PRK, Info, 42, OKM).
+
+sha_long() ->
+ [{doc, "Test with SHA-1 and longer inputs/outputs"}].
+sha_long(Config) when is_list(Config) ->
+ %% Hash = SHA-1
+ %% IKM = 0x000102030405060708090a0b0c0d0e0f
+ %% 101112131415161718191a1b1c1d1e1f
+ %% 202122232425262728292a2b2c2d2e2f
+ %% 303132333435363738393a3b3c3d3e3f
+ %% 404142434445464748494a4b4c4d4e4f (80 octets)
+ %% salt = 0x606162636465666768696a6b6c6d6e6f
+ %% 707172737475767778797a7b7c7d7e7f
+ %% 808182838485868788898a8b8c8d8e8f
+ %% 909192939495969798999a9b9c9d9e9f
+ %% a0a1a2a3a4a5a6a7a8a9aaabacadaeaf (80 octets)
+ %% info = 0xb0b1b2b3b4b5b6b7b8b9babbbcbdbebf
+ %% c0c1c2c3c4c5c6c7c8c9cacbcccdcecf
+ %% d0d1d2d3d4d5d6d7d8d9dadbdcdddedf
+ %% e0e1e2e3e4e5e6e7e8e9eaebecedeeef
+ %% f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff (80 octets)
+ %% L = 82
+
+ %% PRK = 0x8adae09a2a307059478d309b26c4115a224cfaf6 (20 octets)
+ %% OKM = 0x0bd770a74d1160f7c9f12cd5912a06eb
+ %% ff6adcae899d92191fe4305673ba2ffe
+ %% 8fa3f1a4e5ad79f3f334b3b202b2173c
+ %% 486ea37ce3d397ed034c7f9dfeb15c5e
+ %% 927336d0441f4c4300e2cff0d0900b52
+ %% d3b4 (82 octets)
+ IKM = hexstr2bin("000102030405060708090a0b0c0d0e0f"
+ "101112131415161718191a1b1c1d1e1f"
+ "202122232425262728292a2b2c2d2e2f"
+ "303132333435363738393a3b3c3d3e3f"
+ "404142434445464748494a4b4c4d4e4f"
+ ),
+ Salt = hexstr2bin("606162636465666768696a6b6c6d6e6f"
+ "707172737475767778797a7b7c7d7e7f"
+ "808182838485868788898a8b8c8d8e8f"
+ "909192939495969798999a9b9c9d9e9f"
+ "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
+ ),
+ Info = hexstr2bin("b0b1b2b3b4b5b6b7b8b9babbbcbdbebf"
+ "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
+ "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"
+ "e0e1e2e3e4e5e6e7e8e9eaebecedeeef"
+ "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
+ ),
+ PRK = hexstr2bin("8adae09a2a307059478d309b26c4115a224cfaf6"),
+ OKM = hexstr2bin("0bd770a74d1160f7c9f12cd5912a06eb"
+ "ff6adcae899d92191fe4305673ba2ffe"
+ "8fa3f1a4e5ad79f3f334b3b202b2173c"
+ "486ea37ce3d397ed034c7f9dfeb15c5e"
+ "927336d0441f4c4300e2cff0d0900b52"
+ "d3b4"
+ ),
+ hkdf_test(sha, Salt, IKM, PRK, Info, 82, OKM).
+
+sha_no_salt() ->
+ [{doc, "Test with SHA-1 and zero-length salt/info"}].
+sha_no_salt(Config) when is_list(Config) ->
+ %% Hash = SHA-1
+ %% IKM = 0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b (22 octets)
+ %% salt = (0 octets)
+ %% info = (0 octets)
+ %% L = 42
+
+ %% PRK = 0xda8c8a73c7fa77288ec6f5e7c297786aa0d32d01 (20 octets)
+ %% OKM = 0x0ac1af7002b3d761d1e55298da9d0506
+ %% b9ae52057220a306e07b6b87e8df21d0
+ %% ea00033de03984d34918 (42 octets)
+ IKM = hexstr2bin("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
+ Salt = <<>>,
+ Info = <<>>,
+ PRK = hexstr2bin("da8c8a73c7fa77288ec6f5e7c297786aa0d32d01"),
+ OKM = hexstr2bin("0ac1af7002b3d761d1e55298da9d0506"
+ "b9ae52057220a306e07b6b87e8df21d0"
+ "ea00033de03984d34918"),
+ hkdf_test(sha, Salt, IKM, PRK, Info, 42, OKM).
+
+
+sha_default_salt() ->
+ [{doc, "Test with SHA-1, salt not provided (defaults to HashLen zero octets),
+ zero-length info"}].
+sha_default_salt(Config) when is_list(Config) ->
+ %% Hash = SHA-1
+ %% IKM = 0x0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c (22 octets)
+ %% salt = not provided (defaults to HashLen zero octets)
+ %% info = (0 octets)
+ %% L = 42
+
+ %% PRK = 0x2adccada18779e7c2077ad2eb19d3f3e731385dd (20 octets)
+ %% OKM = 0x2c91117204d745f3500d636a62f64f0a
+ %% b3bae548aa53d423b0d1f27ebba6f5e5
+ %% 673a081d70cce7acfc48 (42 octets)
+ IKM = hexstr2bin("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c"),
+ Salt = binary:copy(<<0>>, 20),
+ Info = <<>>,
+ PRK = hexstr2bin("2adccada18779e7c2077ad2eb19d3f3e731385dd"),
+ OKM = hexstr2bin("2c91117204d745f3500d636a62f64f0a"
+ "b3bae548aa53d423b0d1f27ebba6f5e5"
+ "673a081d70cce7acfc48"),
+ hkdf_test(sha, Salt, IKM, PRK, Info, 42, OKM).
+
+hkdf_test(HashAlg, Salt, KeyingMaterial, PsedoRandKey, ContextInfo, Length, Key) ->
+ PsedoRandKey = tls_v1:hkdf_extract(HashAlg, Salt, KeyingMaterial),
+ Key = tls_v1:hkdf_expand(PsedoRandKey, ContextInfo, Length, HashAlg).
+
+hexstr2bin(S) when is_binary(S) ->
+ list_to_binary(hexstr2list(binary_to_list(S)));
+hexstr2bin(S) ->
+ list_to_binary(hexstr2list(S)).
+
+hexstr2list([$ |T]) ->
+ hexstr2list(T);
+hexstr2list([X,Y|T]) ->
+ [mkint(X)*16 + mkint(Y) | hexstr2list(T)];
+hexstr2list([]) ->
+ [].
+mkint(C) when $0 =< C, C =< $9 ->
+ C - $0;
+mkint(C) when $A =< C, C =< $F ->
+ C - $A + 10;
+mkint(C) when $a =< C, C =< $f ->
+ C - $a + 10.
diff --git a/lib/ssl/test/ssl_session_cache_SUITE.erl b/lib/ssl/test/ssl_session_cache_SUITE.erl
index a0fab58b9d..7f33fe3204 100644
--- a/lib/ssl/test/ssl_session_cache_SUITE.erl
+++ b/lib/ssl/test/ssl_session_cache_SUITE.erl
@@ -48,7 +48,8 @@ all() ->
session_cache_process_list,
session_cache_process_mnesia,
client_unique_session,
- max_table_size
+ max_table_size,
+ save_specific_session
].
groups() ->
@@ -60,10 +61,7 @@ init_per_suite(Config0) ->
ok ->
ssl_test_lib:clean_start(),
%% make rsa certs using
- {ok, _} = make_certs:all(proplists:get_value(data_dir, Config0),
- proplists:get_value(priv_dir, Config0)),
- Config = ssl_test_lib:make_dsa_cert(Config0),
- ssl_test_lib:cert_options(Config)
+ ssl_test_lib:make_rsa_cert(Config0)
catch _:_ ->
{skip, "Crypto did not start"}
end.
@@ -97,7 +95,10 @@ init_per_testcase(session_cleanup, Config) ->
init_per_testcase(client_unique_session, Config) ->
ct:timetrap({seconds, 40}),
Config;
-
+init_per_testcase(save_specific_session, Config) ->
+ ssl_test_lib:clean_start(),
+ ct:timetrap({seconds, 5}),
+ Config;
init_per_testcase(max_table_size, Config) ->
ssl:stop(),
application:load(ssl),
@@ -141,7 +142,7 @@ end_per_testcase(max_table_size, Config) ->
end_per_testcase(default_action, Config);
end_per_testcase(Case, Config) when Case == session_cache_process_list;
Case == session_cache_process_mnesia ->
- ets:delete(ssl_test),
+ catch ets:delete(ssl_test),
Config;
end_per_testcase(_, Config) ->
Config.
@@ -154,8 +155,8 @@ client_unique_session() ->
"sets up many connections"}].
client_unique_session(Config) when is_list(Config) ->
process_flag(trap_exit, true),
- ClientOpts = proplists:get_value(client_opts, Config),
- ServerOpts = proplists:get_value(server_opts, Config),
+ ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
+ ServerOpts = proplists:get_value(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server =
ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
@@ -164,8 +165,7 @@ client_unique_session(Config) when is_list(Config) ->
{tcp_options, [{active, false}]},
{options, ServerOpts}]),
Port = ssl_test_lib:inet_port(Server),
- LastClient = clients_start(Server,
- ClientNode, Hostname, Port, ClientOpts, client_unique_session, 20),
+ LastClient = clients_start(Server, ClientNode, Hostname, Port, ClientOpts, 20),
receive
{LastClient, {ok, _}} ->
ok
@@ -185,8 +185,8 @@ session_cleanup() ->
"does not grow and grow ..."}].
session_cleanup(Config) when is_list(Config) ->
process_flag(trap_exit, true),
- ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_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 =
@@ -254,13 +254,75 @@ session_cache_process_mnesia(Config) when is_list(Config) ->
session_cache_process(mnesia,Config).
%%--------------------------------------------------------------------
+save_specific_session() ->
+ [{doc, "Test that we can save a specific client session"
+ }].
+save_specific_session(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
+ ServerOpts = proplists:get_value(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()},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {tcp_options, [{active, false}]},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
+ {from, self()}, {options, ClientOpts}]),
+ Server ! listen,
+
+ Client2 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
+ {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]}]),
+ SessionID1 =
+ receive
+ {Client1, S1} ->
+ S1
+ end,
+
+ SessionID2 =
+ receive
+ {Client2, S2} ->
+ S2
+ end,
+
+ true = SessionID1 =/= SessionID2,
+
+ {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)),
+ [_, _,_, _, Prop] = StatusInfo,
+ State = ssl_test_lib:state(Prop),
+ ClientCache = element(2, State),
+ 2 = ssl_session_cache:size(ClientCache),
+
+ Server ! listen,
+
+ Client3 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
+ {from, self()}, {options, [{reuse_session, SessionID2} | ClientOpts]}]),
+ receive
+ {Client3, SessionID2} ->
+ ok;
+ {Client3, SessionID3}->
+ ct:fail({got, SessionID3, expected, SessionID2});
+ Other ->
+ ct:fail({got,Other})
+ end.
+
+%%--------------------------------------------------------------------
max_table_size() ->
[{doc,"Test max limit on session table"}].
max_table_size(Config) when is_list(Config) ->
process_flag(trap_exit, true),
- ClientOpts = proplists:get_value(client_verification_opts, Config),
- ServerOpts = proplists:get_value(server_verification_opts, Config),
+ ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
+ ServerOpts = proplists:get_value(server_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server =
ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
@@ -270,7 +332,7 @@ max_table_size(Config) when is_list(Config) ->
{options, ServerOpts}]),
Port = ssl_test_lib:inet_port(Server),
LastClient = clients_start(Server,
- ClientNode, Hostname, Port, ClientOpts, max_table_size, 20),
+ ClientNode, Hostname, Port, ClientOpts, 20),
receive
{LastClient, {ok, _}} ->
ok
@@ -426,25 +488,27 @@ session_loop(Sess) ->
%%--------------------------------------------------------------------
session_cache_process(_Type,Config) when is_list(Config) ->
- ssl_basic_SUITE:reuse_session(Config).
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config).
-clients_start(_Server, ClientNode, Hostname, Port, ClientOpts, Test, 0) ->
+clients_start(_Server, ClientNode, Hostname, Port, ClientOpts, 0) ->
%% Make sure session is registered
ct:sleep(?SLEEP * 2),
ssl_test_lib:start_client([{node, ClientNode},
{port, Port}, {host, Hostname},
{mfa, {?MODULE, connection_info_result, []}},
- {from, self()}, {options, test_copts(Test, 0, ClientOpts)}]);
-clients_start(Server, ClientNode, Hostname, Port, ClientOpts, Test, N) ->
+ {from, self()}, {options, ClientOpts}]);
+clients_start(Server, ClientNode, Hostname, Port, ClientOpts, N) ->
spawn_link(ssl_test_lib, start_client,
[[{node, ClientNode},
{port, Port}, {host, Hostname},
{mfa, {ssl_test_lib, no_result, []}},
- {from, self()}, {options, test_copts(Test, N, ClientOpts)}]]),
+ {from, self()}, {options, ClientOpts}]]),
Server ! listen,
wait_for_server(),
- clients_start(Server, ClientNode, Hostname, Port, ClientOpts, Test, N-1).
+ clients_start(Server, ClientNode, Hostname, Port, ClientOpts, N-1).
connection_info_result(Socket) ->
ssl:connection_information(Socket, [protocol, cipher_suite]).
@@ -481,21 +545,3 @@ get_delay_timers() ->
wait_for_server() ->
ct:sleep(100).
-
-
-test_copts(_, 0, ClientOpts) ->
- ClientOpts;
-test_copts(max_table_size, N, ClientOpts) ->
- Version = tls_record:highest_protocol_version([]),
- CipherSuites = %%lists:map(fun(X) -> ssl_cipher_format:suite_definition(X) end, ssl_cipher:filter_suites(ssl_cipher:suites(Version))),
-[ Y|| Y = {Alg,_, _, _} <- lists:map(fun(X) -> ssl_cipher_format:suite_definition(X) end, ssl_cipher:filter_suites(ssl_cipher:suites(Version))), Alg =/= ecdhe_ecdsa, Alg =/= ecdh_ecdsa, Alg =/= ecdh_rsa, Alg =/= ecdhe_rsa, Alg =/= dhe_dss, Alg =/= dss],
- case length(CipherSuites) of
- M when M >= N ->
- Cipher = lists:nth(N, CipherSuites),
- ct:pal("~p",[Cipher]),
- [{ciphers, [Cipher]} | ClientOpts];
- _ ->
- ClientOpts
- end;
-test_copts(_, _, ClientOpts) ->
- ClientOpts.
diff --git a/lib/ssl/test/ssl_sni_SUITE.erl b/lib/ssl/test/ssl_sni_SUITE.erl
index 251b6a2639..7629d75100 100644
--- a/lib/ssl/test/ssl_sni_SUITE.erl
+++ b/lib/ssl/test/ssl_sni_SUITE.erl
@@ -236,8 +236,8 @@ dns_name_reuse(Config) ->
{mfa, {ssl_test_lib, session_info_result, []}},
{from, self()}, {options, [{verify, verify_peer} | ClientConf]}]),
- ssl_test_lib:check_result(Client1, {error, {tls_alert, "handshake failure"}}),
- ssl_test_lib:close(Client0).
+ ssl_test_lib:check_client_alert(Client1, handshake_failure).
+
%%--------------------------------------------------------------------
%% Internal Functions ------------------------------------------------
%%--------------------------------------------------------------------
@@ -370,8 +370,8 @@ unsuccessfull_connect(ServerOptions, ClientOptions, Hostname0, Config) ->
{from, self()},
{options, ClientOptions}]),
- ssl_test_lib:check_result(Server, {error, {tls_alert, "handshake failure"}},
- Client, {error, {tls_alert, "handshake failure"}}).
+ ssl_test_lib:check_server_alert(Server, Client, handshake_failure).
+
host_name(undefined, Hostname) ->
Hostname;
host_name(Hostname, _) ->
diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl
index 7767d76a0d..f628b4e6d4 100644
--- a/lib/ssl/test/ssl_test_lib.erl
+++ b/lib/ssl/test/ssl_test_lib.erl
@@ -30,6 +30,7 @@
-record(sslsocket, { fd = nil, pid = nil}).
-define(SLEEP, 1000).
+-define(DEFAULT_CURVE, secp256r1).
%% For now always run locally
run_where(_) ->
@@ -437,6 +438,37 @@ check_result(Pid, Msg) ->
{got, Unexpected}},
ct:fail(Reason)
end.
+check_server_alert(Pid, Alert) ->
+ receive
+ {Pid, {error, {tls_alert, {Alert, _}}}} ->
+ ok
+ end.
+check_server_alert(Server, Client, Alert) ->
+ receive
+ {Server, {error, {tls_alert, {Alert, _}}}} ->
+ receive
+ {Client, {error, {tls_alert, {Alert, _}}}} ->
+ ok;
+ {Client, {error, closed}} ->
+ ok
+ end
+ end.
+check_client_alert(Pid, Alert) ->
+ receive
+ {Pid, {error, {tls_alert, {Alert, _}}}} ->
+ ok
+ end.
+check_client_alert(Server, Client, Alert) ->
+ receive
+ {Client, {error, {tls_alert, {Alert, _}}}} ->
+ receive
+ {Server, {error, {tls_alert, {Alert, _}}}} ->
+ ok;
+ {Server, {error, closed}} ->
+ ok
+ end
+ end.
+
wait_for_result(Server, ServerMsg, Client, ClientMsg) ->
receive
@@ -523,7 +555,7 @@ cert_options(Config) ->
{client_verification_opts, [{cacertfile, ServerCaCertFile},
{certfile, ClientCertFile},
{keyfile, ClientKeyFile},
- {ssl_imp, new}]},
+ {verify, verify_peer}]},
{client_verification_opts_digital_signature_only, [{cacertfile, ServerCaCertFile},
{certfile, ClientCertFileDigitalSignatureOnly},
{keyfile, ClientKeyFile},
@@ -618,9 +650,12 @@ make_rsa_cert_chains(UserConf, Config, Suffix) ->
}.
make_ec_cert_chains(UserConf, ClientChainType, ServerChainType, Config) ->
+ make_ec_cert_chains(UserConf, ClientChainType, ServerChainType, Config, ?DEFAULT_CURVE).
+%%
+make_ec_cert_chains(UserConf, ClientChainType, ServerChainType, Config, Curve) ->
ClientChain = proplists:get_value(client_chain, UserConf, default_cert_chain_conf()),
ServerChain = proplists:get_value(server_chain, UserConf, default_cert_chain_conf()),
- CertChainConf = gen_conf(ClientChainType, ServerChainType, ClientChain, ServerChain),
+ CertChainConf = gen_conf(ClientChainType, ServerChainType, ClientChain, ServerChain, Curve),
ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), atom_to_list(ClientChainType)]),
ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), atom_to_list(ServerChainType)]),
GenCertData = public_key:pkix_test_data(CertChainConf),
@@ -635,7 +670,11 @@ default_cert_chain_conf() ->
%% Use only default options
[[],[],[]].
-gen_conf(mix, mix, UserClient, UserServer) ->
+
+gen_conf(ClientChainType, ServerChainType, UserClient, UserServer) ->
+ gen_conf(ClientChainType, ServerChainType, UserClient, UserServer, ?DEFAULT_CURVE).
+%%
+gen_conf(mix, mix, UserClient, UserServer, _) ->
ClientTag = conf_tag("client"),
ServerTag = conf_tag("server"),
@@ -646,12 +685,12 @@ gen_conf(mix, mix, UserClient, UserServer) ->
ServerConf = merge_chain_spec(UserServer, DefaultServer, []),
new_format([{ClientTag, ClientConf}, {ServerTag, ServerConf}]);
-gen_conf(ClientChainType, ServerChainType, UserClient, UserServer) ->
+gen_conf(ClientChainType, ServerChainType, UserClient, UserServer, Curve) ->
ClientTag = conf_tag("client"),
ServerTag = conf_tag("server"),
- DefaultClient = chain_spec(client, ClientChainType),
- DefaultServer = chain_spec(server, ServerChainType),
+ DefaultClient = chain_spec(client, ClientChainType, Curve),
+ DefaultServer = chain_spec(server, ServerChainType, Curve),
ClientConf = merge_chain_spec(UserClient, DefaultClient, []),
ServerConf = merge_chain_spec(UserServer, DefaultServer, []),
@@ -673,43 +712,43 @@ proplist_to_map([Head | Rest]) ->
conf_tag(Role) ->
list_to_atom(Role ++ "_chain").
-chain_spec(_Role, ecdh_rsa) ->
+chain_spec(_Role, ecdh_rsa, Curve) ->
Digest = {digest, appropriate_sha(crypto:supports())},
- CurveOid = hd(tls_v1:ecc_curves(0)),
+ CurveOid = pubkey_cert_records:namedCurves(Curve),
[[Digest, {key, {namedCurve, CurveOid}}],
[Digest, {key, hardcode_rsa_key(1)}],
[Digest, {key, {namedCurve, CurveOid}}]];
-chain_spec(_Role, ecdhe_ecdsa) ->
+chain_spec(_Role, ecdhe_ecdsa, Curve) ->
Digest = {digest, appropriate_sha(crypto:supports())},
- CurveOid = hd(tls_v1:ecc_curves(0)),
+ CurveOid = pubkey_cert_records:namedCurves(Curve),
[[Digest, {key, {namedCurve, CurveOid}}],
[Digest, {key, {namedCurve, CurveOid}}],
[Digest, {key, {namedCurve, CurveOid}}]];
-chain_spec(_Role, ecdh_ecdsa) ->
+chain_spec(_Role, ecdh_ecdsa, Curve) ->
Digest = {digest, appropriate_sha(crypto:supports())},
- CurveOid = hd(tls_v1:ecc_curves(0)),
+ CurveOid = pubkey_cert_records:namedCurves(Curve),
[[Digest, {key, {namedCurve, CurveOid}}],
[Digest, {key, {namedCurve, CurveOid}}],
[Digest, {key, {namedCurve, CurveOid}}]];
-chain_spec(_Role, ecdhe_rsa) ->
+chain_spec(_Role, ecdhe_rsa, _) ->
Digest = {digest, appropriate_sha(crypto:supports())},
[[Digest, {key, hardcode_rsa_key(1)}],
[Digest, {key, hardcode_rsa_key(2)}],
[Digest, {key, hardcode_rsa_key(3)}]];
-chain_spec(_Role, ecdsa) ->
+chain_spec(_Role, ecdsa, Curve) ->
Digest = {digest, appropriate_sha(crypto:supports())},
- CurveOid = hd(tls_v1:ecc_curves(0)),
+ CurveOid = pubkey_cert_records:namedCurves(Curve),
[[Digest, {key, {namedCurve, CurveOid}}],
[Digest, {key, {namedCurve, CurveOid}}],
[Digest, {key, {namedCurve, CurveOid}}]];
-chain_spec(_Role, rsa) ->
+chain_spec(_Role, rsa, _) ->
Digest = {digest, appropriate_sha(crypto:supports())},
[[Digest, {key, hardcode_rsa_key(1)}],
[Digest, {key, hardcode_rsa_key(2)}],
[Digest, {key, hardcode_rsa_key(3)}]];
-chain_spec(_Role, dsa) ->
+chain_spec(_Role, dsa, _) ->
Digest = {digest, appropriate_sha(crypto:supports())},
[[Digest, {key, hardcode_dsa_key(1)}],
[Digest, {key, hardcode_dsa_key(2)}],
@@ -742,7 +781,7 @@ merge_spec(User, Default, [Conf | Rest], Acc) ->
make_mix_cert(Config) ->
Ext = x509_test:extensions([{key_usage, [digitalSignature]}]),
Digest = {digest, appropriate_sha(crypto:supports())},
- CurveOid = hd(tls_v1:ecc_curves(0)),
+ CurveOid = pubkey_cert_records:namedCurves(?DEFAULT_CURVE),
Mix = proplists:get_value(mix, Config, peer_ecc),
ClientChainType =ServerChainType = mix,
{ClientChain, ServerChain} = mix(Mix, Digest, CurveOid, Ext),
@@ -825,7 +864,8 @@ make_rsa_cert(Config) ->
Config
end.
appropriate_sha(CryptoSupport) ->
- case proplists:get_bool(sha256, CryptoSupport) of
+ Hashes = proplists:get_value(hashs, CryptoSupport),
+ case lists:member(sha256, Hashes) of
true ->
sha256;
false ->
@@ -1064,8 +1104,28 @@ ecc_test(Expect, COpts, SOpts, CECCOpts, SECCOpts, Config) ->
ecc_test_error(COpts, SOpts, CECCOpts, SECCOpts, Config) ->
{Server, Port} = start_server_ecc_error(erlang, SOpts, SECCOpts, Config),
Client = start_client_ecc_error(erlang, Port, COpts, CECCOpts, Config),
- Error = {error, {tls_alert, "insufficient security"}},
- check_result(Server, Error, Client, Error).
+ check_server_alert(Server, Client, insufficient_security).
+
+start_basic_client(openssl, Version, Port, ClientOpts) ->
+ Cert = proplists:get_value(certfile, ClientOpts),
+ Key = proplists:get_value(keyfile, ClientOpts),
+ CA = proplists:get_value(cacertfile, ClientOpts),
+ Groups0 = proplists:get_value(groups, ClientOpts),
+ Exe = "openssl",
+ Args0 = ["s_client", "-verify", "2", "-port", integer_to_list(Port),
+ ssl_test_lib:version_flag(Version),
+ "-cert", Cert, "-CAfile", CA,
+ "-key", Key, "-host","localhost", "-msg", "-debug"],
+ Args =
+ case Groups0 of
+ undefined ->
+ Args0;
+ G ->
+ Args0 ++ ["-groups", G]
+ end,
+ OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args),
+ true = port_command(OpenSslPort, "Hello world"),
+ OpenSslPort.
start_client(openssl, Port, ClientOpts, Config) ->
Cert = proplists:get_value(certfile, ClientOpts),
@@ -1073,11 +1133,11 @@ start_client(openssl, Port, ClientOpts, Config) ->
CA = proplists:get_value(cacertfile, ClientOpts),
Version = ssl_test_lib:protocol_version(Config),
Exe = "openssl",
- Args = ["s_client", "-verify", "2", "-port", integer_to_list(Port),
+ Args0 = ["s_client", "-verify", "2", "-port", integer_to_list(Port),
ssl_test_lib:version_flag(Version),
"-cert", Cert, "-CAfile", CA,
"-key", Key, "-host","localhost", "-msg", "-debug"],
-
+ Args = maybe_force_ipv4(Args0),
OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args),
true = port_command(OpenSslPort, "Hello world"),
OpenSslPort;
@@ -1091,6 +1151,18 @@ start_client(erlang, Port, ClientOpts, Config) ->
{mfa, {ssl_test_lib, check_key_exchange_send_active, [KeyEx]}},
{options, [{verify, verify_peer} | ClientOpts]}]).
+%% Workaround for running tests on machines where openssl
+%% s_client would use an IPv6 address with localhost. As
+%% this test suite and the ssl application is not prepared
+%% for that we have to force s_client to use IPv4 if
+%% OpenSSL supports IPv6.
+maybe_force_ipv4(Args0) ->
+ case is_ipv6_supported() of
+ true ->
+ Args0 ++ ["-4"];
+ false ->
+ Args0
+ end.
start_client_ecc(erlang, Port, ClientOpts, Expect, ECCOpts, Config) ->
{ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
@@ -1537,7 +1609,12 @@ init_tls_version(Version, Config) ->
clean_tls_version(Config) ->
proplists:delete(protocol_opts, proplists:delete(protocol, Config)).
-
+
+sufficient_crypto_support(Version)
+ when Version == 'tlsv1.3' ->
+ CryptoSupport = crypto:supports(),
+ lists:member(rsa_pkcs1_pss_padding, proplists:get_value(rsa_opts, CryptoSupport)) andalso
+ lists:member(x448, proplists:get_value(curves, CryptoSupport));
sufficient_crypto_support(Version)
when Version == 'tlsv1.2'; Version == 'dtlsv1.2' ->
CryptoSupport = crypto:supports(),
@@ -1583,35 +1660,22 @@ v_1_2_check(ecdh_rsa, ecdh_ecdsa) ->
v_1_2_check(_, _) ->
false.
-send_recv_result_active(Socket) ->
- ssl:send(Socket, "Hello world"),
- receive
- {ssl, Socket, "H"} ->
- receive
- {ssl, Socket, "ello world"} ->
- ok
- end;
- {ssl, Socket, "Hello world"} ->
- ok
- end.
-
send_recv_result(Socket) ->
- ssl:send(Socket, "Hello world"),
- {ok,"Hello world"} = ssl:recv(Socket, 11),
+ Data = "Hello world",
+ ssl:send(Socket, Data),
+ {ok, Data} = ssl:recv(Socket, length(Data)),
+ ok.
+
+send_recv_result_active(Socket) ->
+ Data = "Hello world",
+ ssl:send(Socket, Data),
+ Data = active_recv(Socket, length(Data)),
ok.
send_recv_result_active_once(Socket) ->
- ssl:send(Socket, "Hello world"),
- receive
- {ssl, Socket, "H"} ->
- ssl:setopts(Socket, [{active, once}]),
- receive
- {ssl, Socket, "ello world"} ->
- ok
- end;
- {ssl, Socket, "Hello world"} ->
- ok
- end.
+ Data = "Hello world",
+ ssl:send(Socket, Data),
+ active_once_recv_list(Socket, length(Data)).
active_recv(Socket, N) ->
active_recv(Socket, N, []).
@@ -1624,6 +1688,55 @@ active_recv(Socket, N, Acc) ->
active_recv(Socket, N-length(Bytes), Acc ++ Bytes)
end.
+active_once_recv(_Socket, 0) ->
+ ok;
+active_once_recv(Socket, N) ->
+ receive
+ {ssl, Socket, Bytes} ->
+ ssl:setopts(Socket, [{active, once}]),
+ active_once_recv(Socket, N-byte_size(Bytes))
+ end.
+
+active_once_recv_list(_Socket, 0) ->
+ ok;
+active_once_recv_list(Socket, N) ->
+ receive
+ {ssl, Socket, Bytes} ->
+ ssl:setopts(Socket, [{active, once}]),
+ active_once_recv_list(Socket, N-length(Bytes))
+ end.
+recv_disregard(_Socket, 0) ->
+ ok;
+recv_disregard(Socket, N) ->
+ {ok, Bytes} = ssl:recv(Socket, 0),
+ recv_disregard(Socket, N-byte_size(Bytes)).
+
+active_disregard(_Socket, 0) ->
+ ok;
+active_disregard(Socket, N) ->
+ receive
+ {ssl, Socket, Bytes} ->
+ active_disregard(Socket, N-byte_size(Bytes))
+ end.
+active_once_disregard(_Socket, 0) ->
+ ok;
+active_once_disregard(Socket, N) ->
+ receive
+ {ssl, Socket, Bytes} ->
+ ssl:setopts(Socket, [{active, once}]),
+ active_once_disregard(Socket, N-byte_size(Bytes))
+ end.
+
+is_ipv6_supported() ->
+ case os:cmd("openssl version") of
+ "OpenSSL 0.9.8" ++ _ -> % Does not support IPv6
+ false;
+ "OpenSSL 1.0" ++ _ -> % Does not support IPv6
+ false;
+ _ ->
+ true
+ end.
+
is_sane_ecc(openssl) ->
case os:cmd("openssl version") of
"OpenSSL 1.0.0a" ++ _ -> % Known bug in openssl
@@ -1819,6 +1932,8 @@ version_flag('tlsv1.1') ->
"-tls1_1";
version_flag('tlsv1.2') ->
"-tls1_2";
+version_flag('tlsv1.3') ->
+ "-tls1_3";
version_flag(sslv3) ->
"-ssl3";
version_flag(sslv2) ->
@@ -2161,3 +2276,98 @@ server_msg(Server, ServerMsg) ->
Unexpected ->
ct:fail(Unexpected)
end.
+
+session_id(Socket) ->
+ {ok, [{session_id, ID}]} = ssl:connection_information(Socket, [session_id]),
+ ID.
+
+reuse_session(ClientOpts, ServerOpts, Config) ->
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server0 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {tcp_options, [{active, false}]},
+ {options, ServerOpts}]),
+ Port0 = ssl_test_lib:inet_port(Server0),
+
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
+ {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]}]),
+ Server0 ! listen,
+
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
+ {from, self()}, {options, ClientOpts}]),
+
+ SID = receive
+ {Client0, Id0} ->
+ Id0
+ end,
+
+ receive
+ {Client1, SID} ->
+ ok
+ after ?SLEEP ->
+ ct:fail(session_not_reused)
+ end,
+
+ Server0 ! listen,
+
+ Client2 =
+ ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
+ {from, self()}, {options, [{reuse_sessions, false}
+ | ClientOpts]}]),
+ receive
+ {Client2, SID} ->
+ ct:fail(session_reused_when_session_reuse_disabled_by_client);
+ {Client2, _} ->
+ ok
+ end,
+
+ ssl_test_lib:close(Server0),
+ ssl_test_lib:close(Client0),
+ ssl_test_lib:close(Client1),
+ ssl_test_lib:close(Client2),
+
+ Server1 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {tcp_options, [{active, false}]},
+ {options, [{reuse_sessions, false} |ServerOpts]}]),
+ Port1 = ssl_test_lib:inet_port(Server1),
+
+ Client3 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port1}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
+ {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]}]),
+ SID1 = receive
+ {Client3, Id3} ->
+ Id3
+ end,
+
+ Server1 ! listen,
+
+ Client4 =
+ ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port1}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
+ {from, self()}, {options, ClientOpts}]),
+
+ receive
+ {Client4, SID1} ->
+ ct:fail(session_reused_when_session_reuse_disabled_by_server);
+ {Client4, _} ->
+ ok
+ end,
+
+ ssl_test_lib:close(Server1),
+ ssl_test_lib:close(Client3),
+ ssl_test_lib:close(Client4).
+
diff --git a/lib/ssl/test/ssl_to_openssl_SUITE.erl b/lib/ssl/test/ssl_to_openssl_SUITE.erl
index 3c8b25b912..df84411b6d 100644
--- a/lib/ssl/test/ssl_to_openssl_SUITE.erl
+++ b/lib/ssl/test/ssl_to_openssl_SUITE.erl
@@ -260,8 +260,9 @@ special_init(TestCase, Config) when
Config;
special_init(TestCase, Config)
when TestCase == erlang_client_openssl_server_renegotiate;
- TestCase == erlang_client_openssl_server_nowrap_seqnum;
- TestCase == erlang_server_openssl_client_nowrap_seqnum
+ TestCase == erlang_client_openssl_server_nowrap_seqnum;
+ TestCase == erlang_server_openssl_client_nowrap_seqnum;
+ TestCase == erlang_client_openssl_server_renegotiate_after_client_data
->
{ok, Version} = application:get_env(ssl, protocol_version),
check_sane_openssl_renegotaite(Config, Version);
@@ -761,8 +762,8 @@ erlang_client_openssl_server_renegotiate() ->
[{doc,"Test erlang client when openssl server issuses a renegotiate"}].
erlang_client_openssl_server_renegotiate(Config) when is_list(Config) ->
process_flag(trap_exit, true),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
{ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
@@ -771,12 +772,14 @@ erlang_client_openssl_server_renegotiate(Config) when is_list(Config) ->
Port = ssl_test_lib:inet_port(node()),
CertFile = proplists:get_value(certfile, ServerOpts),
+ CaCertFile = proplists:get_value(cacertfile, ServerOpts),
KeyFile = proplists:get_value(keyfile, ServerOpts),
Version = ssl_test_lib:protocol_version(Config),
Exe = "openssl",
Args = ["s_server", "-accept", integer_to_list(Port),
ssl_test_lib:version_flag(Version),
+ "-CAfile", CaCertFile,
"-cert", CertFile, "-key", KeyFile, "-msg"],
OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),
@@ -806,8 +809,8 @@ erlang_client_openssl_server_renegotiate_after_client_data() ->
[{doc,"Test erlang client when openssl server issuses a renegotiate after reading client data"}].
erlang_client_openssl_server_renegotiate_after_client_data(Config) when is_list(Config) ->
process_flag(trap_exit, true),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
{ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
@@ -815,6 +818,7 @@ erlang_client_openssl_server_renegotiate_after_client_data(Config) when is_list(
OpenSslData = "From openssl to erlang",
Port = ssl_test_lib:inet_port(node()),
+ CaCertFile = proplists:get_value(cacertfile, ServerOpts),
CertFile = proplists:get_value(certfile, ServerOpts),
KeyFile = proplists:get_value(keyfile, ServerOpts),
Version = ssl_test_lib:protocol_version(Config),
@@ -822,6 +826,7 @@ erlang_client_openssl_server_renegotiate_after_client_data(Config) when is_list(
Exe = "openssl",
Args = ["s_server", "-accept", integer_to_list(Port),
ssl_test_lib:version_flag(Version),
+ "-CAfile", CaCertFile,
"-cert", CertFile, "-key", KeyFile, "-msg"],
OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),
@@ -856,7 +861,7 @@ erlang_client_openssl_server_nowrap_seqnum() ->
" to lower treashold substantially."}].
erlang_client_openssl_server_nowrap_seqnum(Config) when is_list(Config) ->
process_flag(trap_exit, true),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
{ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
@@ -865,12 +870,14 @@ erlang_client_openssl_server_nowrap_seqnum(Config) when is_list(Config) ->
N = 10,
Port = ssl_test_lib:inet_port(node()),
+ CaCertFile = proplists:get_value(cacertfile, ServerOpts),
CertFile = proplists:get_value(certfile, ServerOpts),
KeyFile = proplists:get_value(keyfile, ServerOpts),
Version = ssl_test_lib:protocol_version(Config),
Exe = "openssl",
Args = ["s_server", "-accept", integer_to_list(Port),
ssl_test_lib:version_flag(Version),
+ "-CAfile", CaCertFile,
"-cert", CertFile, "-key", KeyFile, "-msg"],
OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),
@@ -899,7 +906,7 @@ erlang_server_openssl_client_nowrap_seqnum() ->
" to lower treashold substantially."}].
erlang_server_openssl_client_nowrap_seqnum(Config) when is_list(Config) ->
process_flag(trap_exit, true),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
{_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -1242,7 +1249,7 @@ ssl2_erlang_server_openssl_client(Config) when is_list(Config) ->
ct:log("Ports ~p~n", [[erlang:port_info(P) || P <- erlang:ports()]]),
ssl_test_lib:consume_port_exit(OpenSslPort),
- ssl_test_lib:check_result(Server, {error, {tls_alert, "bad record mac"}}),
+ ssl_test_lib:check_server_alert(Server, bad_record_mac),
process_flag(trap_exit, false).
%%--------------------------------------------------------------------
@@ -1648,8 +1655,8 @@ cipher(CipherSuite, Version, Config, ClientOpts, ServerOpts) ->
start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, OpensslServerOpts, Data, Callback) ->
process_flag(trap_exit, true),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ClientOpts = ErlangClientOpts ++ ClientOpts0,
{ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
@@ -1657,6 +1664,7 @@ start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, Opens
Data = "From openssl to erlang",
Port = ssl_test_lib:inet_port(node()),
+ CaCertFile = proplists:get_value(cacertfile, ServerOpts),
CertFile = proplists:get_value(certfile, ServerOpts),
KeyFile = proplists:get_value(keyfile, ServerOpts),
Version = ssl_test_lib:protocol_version(Config),
@@ -1666,10 +1674,12 @@ start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, Opens
[] ->
["s_server", "-accept",
integer_to_list(Port), ssl_test_lib:version_flag(Version),
+ "-CAfile", CaCertFile,
"-cert", CertFile,"-key", KeyFile];
[Opt, Value] ->
["s_server", Opt, Value, "-accept",
integer_to_list(Port), ssl_test_lib:version_flag(Version),
+ "-CAfile", CaCertFile,
"-cert", CertFile,"-key", KeyFile]
end,
@@ -1694,8 +1704,8 @@ start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, Opens
start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, Callback) ->
process_flag(trap_exit, true),
- ServerOpts = proplists:get_value(server_rsa_opts, Config),
- ClientOpts0 = proplists:get_value(client_rsa_opts, Config),
+ ServerOpts = proplists:get_value(server_rsa_verify_opts, Config),
+ ClientOpts0 = proplists:get_value(client_rsa_verify_opts, Config),
ClientOpts = [{alpn_advertised_protocols, [<<"spdy/2">>]} | ClientOpts0],
{ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
@@ -1703,12 +1713,14 @@ start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, Callba
Data = "From openssl to erlang",
Port = ssl_test_lib:inet_port(node()),
+ CaCertFile = proplists:get_value(cacertfile, ServerOpts),
CertFile = proplists:get_value(certfile, ServerOpts),
KeyFile = proplists:get_value(keyfile, ServerOpts),
Version = ssl_test_lib:protocol_version(Config),
Exe = "openssl",
Args = ["s_server", "-msg", "-alpn", "http/1.1,spdy/2", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version),
+ "-CAfile", CaCertFile,
"-cert", CertFile, "-key", KeyFile],
OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),
ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)),
@@ -1826,8 +1838,8 @@ start_erlang_server_and_openssl_client_for_alpn_npn_negotiation(Config, Data, Ca
start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, Callback) ->
process_flag(trap_exit, true),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ClientOpts = [{client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"http/1.1">>}} | ClientOpts0],
{ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
@@ -1835,6 +1847,7 @@ start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, Callbac
Data = "From openssl to erlang",
Port = ssl_test_lib:inet_port(node()),
+ CaCertFile = proplists:get_value(cacertfile, ServerOpts),
CertFile = proplists:get_value(certfile, ServerOpts),
KeyFile = proplists:get_value(keyfile, ServerOpts),
Version = ssl_test_lib:protocol_version(Config),
@@ -1842,6 +1855,7 @@ start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, Callbac
Exe = "openssl",
Args = ["s_server", "-msg", "-nextprotoneg", "http/1.1,spdy/2", "-accept", integer_to_list(Port),
ssl_test_lib:version_flag(Version),
+ "-CAfile", CaCertFile,
"-cert", CertFile, "-key", KeyFile],
OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),
@@ -1932,6 +1946,11 @@ erlang_ssl_receive(Socket, Data) ->
ct:log("Connection info: ~p~n",
[ssl:connection_information(Socket)]),
receive
+ {ssl, Socket, "R\n"} ->
+ %% Swallow s_client renegotiation command.
+ %% openssl s_client connected commands can appear on
+ %% server side with some openssl versions.
+ erlang_ssl_receive(Socket,Data);
{ssl, Socket, Data} ->
io:format("Received ~p~n",[Data]),
%% open_ssl server sometimes hangs waiting in blocking read
diff --git a/lib/stdlib/doc/src/array.xml b/lib/stdlib/doc/src/array.xml
index db0ab42372..aa1577a067 100644
--- a/lib/stdlib/doc/src/array.xml
+++ b/lib/stdlib/doc/src/array.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>array.xml</file>
</header>
- <module>array</module>
+ <module since="">array</module>
<modulesummary>Functional, extendible arrays.</modulesummary>
<description>
<p>Functional, extendible arrays. Arrays can have fixed size, or can grow
@@ -137,7 +137,7 @@ A3 = array:fix(A2).</pre>
<funcs>
<func>
- <name name="default" arity="1"/>
+ <name name="default" arity="1" since=""/>
<fsummary>Get the value used for uninitialized entries.</fsummary>
<desc><marker id="default-1"/>
<p>Gets the value used for uninitialized entries.</p>
@@ -146,7 +146,7 @@ A3 = array:fix(A2).</pre>
</func>
<func>
- <name name="fix" arity="1"/>
+ <name name="fix" arity="1" since=""/>
<fsummary>Fix the array size.</fsummary>
<desc><marker id="fix-1"/>
<p>Fixes the array size. This prevents it from growing automatically
@@ -157,7 +157,7 @@ A3 = array:fix(A2).</pre>
</func>
<func>
- <name name="foldl" arity="3"/>
+ <name name="foldl" arity="3" since=""/>
<fsummary>Fold the array elements using the specified function and initial
accumulator value.</fsummary>
<desc><marker id="foldl-3"/>
@@ -172,7 +172,7 @@ A3 = array:fix(A2).</pre>
</func>
<func>
- <name name="foldr" arity="3"/>
+ <name name="foldr" arity="3" since=""/>
<fsummary>Fold the array elements right-to-left using the specified
function and initial accumulator value.</fsummary>
<desc><marker id="foldr-3"/>
@@ -186,7 +186,7 @@ A3 = array:fix(A2).</pre>
</func>
<func>
- <name name="from_list" arity="1"/>
+ <name name="from_list" arity="1" since=""/>
<fsummary>Equivalent to <c>from_list(List, undefined)</c>.</fsummary>
<desc><marker id="from_list-1"/>
<p>Equivalent to
@@ -195,7 +195,7 @@ A3 = array:fix(A2).</pre>
</func>
<func>
- <name name="from_list" arity="2"/>
+ <name name="from_list" arity="2" since=""/>
<fsummary>Convert a list to an extendible array.</fsummary>
<desc><marker id="from_list-2"/>
<p>Converts a list to an extendible array. <c><anno>Default</anno></c>
@@ -208,7 +208,7 @@ A3 = array:fix(A2).</pre>
</func>
<func>
- <name name="from_orddict" arity="1"/>
+ <name name="from_orddict" arity="1" since=""/>
<fsummary>Equivalent to <c>from_orddict(Orddict, undefined)</c>.
</fsummary>
<desc><marker id="from_orddict-1"/>
@@ -218,7 +218,7 @@ A3 = array:fix(A2).</pre>
</func>
<func>
- <name name="from_orddict" arity="2"/>
+ <name name="from_orddict" arity="2" since=""/>
<fsummary>Convert an ordered list of pairs <c>{Index, Value}</c> to a
corresponding extendible array.</fsummary>
<desc><marker id="from_orddict-2"/>
@@ -234,7 +234,7 @@ A3 = array:fix(A2).</pre>
</func>
<func>
- <name name="get" arity="2"/>
+ <name name="get" arity="2" since=""/>
<fsummary>Get the value of entry <c>I</c>.</fsummary>
<desc><marker id="get-2"/>
<p>Gets the value of entry <c><anno>I</anno></c>. If
@@ -249,7 +249,7 @@ A3 = array:fix(A2).</pre>
</func>
<func>
- <name name="is_array" arity="1"/>
+ <name name="is_array" arity="1" since=""/>
<fsummary>Returns <c>true</c> if <c>X</c> is an array, otherwise
<c>false</c>.</fsummary>
<desc><marker id="is_array-1"/>
@@ -261,7 +261,7 @@ A3 = array:fix(A2).</pre>
</func>
<func>
- <name name="is_fix" arity="1"/>
+ <name name="is_fix" arity="1" since=""/>
<fsummary>Check if the array has fixed size.</fsummary>
<desc><marker id="is_fix-1"/>
<p>Checks if the array has fixed size. Returns <c>true</c> if the array
@@ -271,7 +271,7 @@ A3 = array:fix(A2).</pre>
</func>
<func>
- <name name="map" arity="2"/>
+ <name name="map" arity="2" since=""/>
<fsummary>Map the specified function onto each array element.</fsummary>
<desc><marker id="map-2"/>
<p>Maps the specified function onto each array element. The elements are
@@ -285,7 +285,7 @@ A3 = array:fix(A2).</pre>
</func>
<func>
- <name name="new" arity="0"/>
+ <name name="new" arity="0" since=""/>
<fsummary>Create a new, extendible array with initial size zero.
</fsummary>
<desc><marker id="new-0"/>
@@ -296,7 +296,7 @@ A3 = array:fix(A2).</pre>
</func>
<func>
- <name name="new" arity="1"/>
+ <name name="new" arity="1" since=""/>
<fsummary>Create a new array according to the specified options.
</fsummary>
<desc><marker id="new-1"/>
@@ -346,7 +346,7 @@ array:new([{size,10},{fixed,false},{default,-1}])</pre>
</func>
<func>
- <name name="new" arity="2"/>
+ <name name="new" arity="2" since=""/>
<fsummary>Create a new array according to the specified size and options.
</fsummary>
<desc><marker id="new-2"/>
@@ -370,7 +370,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="relax" arity="1"/>
+ <name name="relax" arity="1" since=""/>
<fsummary>Make the array resizable.</fsummary>
<desc><marker id="relax-1"/>
<p>Makes the array resizable. (Reverses the effects of
@@ -380,7 +380,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="reset" arity="2"/>
+ <name name="reset" arity="2" since=""/>
<fsummary>Reset entry <c>I</c> to the default value for the array.
</fsummary>
<desc><marker id="reset-2"/>
@@ -399,7 +399,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="resize" arity="1"/>
+ <name name="resize" arity="1" since=""/>
<fsummary>Change the array size to that reported by <c>sparse_size/1</c>.
</fsummary>
<desc><marker id="resize-1"/>
@@ -413,7 +413,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="resize" arity="2"/>
+ <name name="resize" arity="2" since=""/>
<fsummary>Change the array size.</fsummary>
<desc><marker id="resize-2"/>
<p>Change the array size. If <c><anno>Size</anno></c> is not a
@@ -424,7 +424,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="set" arity="3"/>
+ <name name="set" arity="3" since=""/>
<fsummary>Set entry <c>I</c> of the array to <c>Value</c>.</fsummary>
<desc><marker id="set-3"/>
<p>Sets entry <c><anno>I</anno></c> of the array to
@@ -441,7 +441,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="size" arity="1"/>
+ <name name="size" arity="1" since=""/>
<fsummary>Get the number of entries in the array.</fsummary>
<desc><marker id="size-1"/>
<p>Gets the number of entries in the array. Entries are numbered from
@@ -454,7 +454,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="sparse_foldl" arity="3"/>
+ <name name="sparse_foldl" arity="3" since=""/>
<fsummary>Fold the array elements using the specified function and initial
accumulator value, skipping default-valued entries.</fsummary>
<desc><marker id="sparse_foldl-3"/>
@@ -469,7 +469,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="sparse_foldr" arity="3"/>
+ <name name="sparse_foldr" arity="3" since=""/>
<fsummary>Fold the array elements right-to-left using the specified
function and initial accumulator value, skipping default-valued
entries.</fsummary>
@@ -485,7 +485,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="sparse_map" arity="2"/>
+ <name name="sparse_map" arity="2" since=""/>
<fsummary>Map the specified function onto each array element, skipping
default-valued entries.</fsummary>
<desc><marker id="sparse_map-2"/>
@@ -498,7 +498,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="sparse_size" arity="1"/>
+ <name name="sparse_size" arity="1" since=""/>
<fsummary>Get the number of entries in the array up until the last
non-default-valued entry.</fsummary>
<desc><marker id="sparse_size-1"/>
@@ -512,7 +512,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="sparse_to_list" arity="1"/>
+ <name name="sparse_to_list" arity="1" since=""/>
<fsummary>Convert the array to a list, skipping default-valued entries.
</fsummary>
<desc><marker id="sparse_to_list-1"/>
@@ -522,7 +522,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="sparse_to_orddict" arity="1"/>
+ <name name="sparse_to_orddict" arity="1" since=""/>
<fsummary>Convert the array to an ordered list of pairs <c>{Index,
Value}</c>, skipping default-valued entries.</fsummary>
<desc><marker id="sparse_to_orddict-1"/>
@@ -534,7 +534,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="to_list" arity="1"/>
+ <name name="to_list" arity="1" since=""/>
<fsummary>Convert the array to a list.</fsummary>
<desc><marker id="to_list-1"/>
<p>Converts the array to a list.</p>
@@ -545,7 +545,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="to_orddict" arity="1"/>
+ <name name="to_orddict" arity="1" since=""/>
<fsummary>Convert the array to an ordered list of pairs <c>{Index,
Value}</c>.</fsummary>
<desc><marker id="to_orddict-1"/>
diff --git a/lib/stdlib/doc/src/base64.xml b/lib/stdlib/doc/src/base64.xml
index cfa1ecc006..479072ba4f 100644
--- a/lib/stdlib/doc/src/base64.xml
+++ b/lib/stdlib/doc/src/base64.xml
@@ -29,7 +29,7 @@
<rev></rev>
<file>base64.xml</file>
</header>
- <module>base64</module>
+ <module since="">base64</module>
<modulesummary>Provides base64 encode and decode, see
RFC 2045.</modulesummary>
<description>
@@ -51,10 +51,10 @@
<funcs>
<func>
- <name name="decode" arity="1"/>
- <name name="decode_to_string" arity="1"/>
- <name name="mime_decode" arity="1"/>
- <name name="mime_decode_to_string" arity="1"/>
+ <name name="decode" arity="1" since=""/>
+ <name name="decode_to_string" arity="1" since=""/>
+ <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="Data" name_i="1"/>
@@ -69,8 +69,8 @@
</func>
<func>
- <name name="encode" arity="1"/>
- <name name="encode_to_string" arity="1"/>
+ <name name="encode" arity="1" since=""/>
+ <name name="encode_to_string" arity="1" since=""/>
<fsummary>Encode data into base64.</fsummary>
<type variable="Data"/>
<type variable="Base64" name_i="1"/>
diff --git a/lib/stdlib/doc/src/beam_lib.xml b/lib/stdlib/doc/src/beam_lib.xml
index 213170df7f..8bb4cf9101 100644
--- a/lib/stdlib/doc/src/beam_lib.xml
+++ b/lib/stdlib/doc/src/beam_lib.xml
@@ -28,7 +28,7 @@
<date>1999-10-30</date>
<rev>PA1</rev>
</header>
- <module>beam_lib</module>
+ <module since="">beam_lib</module>
<modulesummary>An interface to the BEAM file format.</modulesummary>
<description>
<p>This module provides an interface to files created by
@@ -267,7 +267,7 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code>
<funcs>
<func>
- <name name="all_chunks" arity="1"/>
+ <name name="all_chunks" arity="1" since="OTP 18.2"/>
<fsummary>Read all chunks from a BEAM file or binary</fsummary>
<desc>
<p>Reads chunk data for all chunks.</p>
@@ -275,7 +275,7 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code>
</func>
<func>
- <name name="build_module" arity="1"/>
+ <name name="build_module" arity="1" since="OTP 18.2"/>
<fsummary>Create a BEAM module from a list of chunks.</fsummary>
<desc>
<p>Builds a BEAM module (as a binary) from a list of chunks.</p>
@@ -283,7 +283,7 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code>
</func>
<func>
- <name name="chunks" arity="2"/>
+ <name name="chunks" arity="2" since=""/>
<fsummary>Read selected chunks from a BEAM file or binary.</fsummary>
<desc>
<p>Reads chunk data for selected chunks references. The order of
@@ -293,7 +293,7 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code>
</func>
<func>
- <name name="chunks" arity="3"/>
+ <name name="chunks" arity="3" since=""/>
<fsummary>Read selected chunks from a BEAM file or binary.</fsummary>
<desc>
<p>Reads chunk data for selected chunks references. The order of
@@ -312,7 +312,7 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code>
</func>
<func>
- <name name="clear_crypto_key_fun" arity="0"/>
+ <name name="clear_crypto_key_fun" arity="0" since=""/>
<fsummary>Unregister the current crypto key fun.</fsummary>
<desc>
<p>Unregisters the crypto key fun and terminates the process
@@ -327,7 +327,7 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code>
</func>
<func>
- <name name="cmp" arity="2"/>
+ <name name="cmp" arity="2" since=""/>
<fsummary>Compare two BEAM files.</fsummary>
<type name="cmp_rsn"/>
<desc>
@@ -341,7 +341,7 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code>
</func>
<func>
- <name name="cmp_dirs" arity="2"/>
+ <name name="cmp_dirs" arity="2" since=""/>
<fsummary>Compare the BEAM files in two directories.</fsummary>
<desc>
<p>Compares the BEAM files in
@@ -359,7 +359,7 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code>
</func>
<func>
- <name name="crypto_key_fun" arity="1"/>
+ <name name="crypto_key_fun" arity="1" since=""/>
<fsummary>Register a fun that provides a crypto key.</fsummary>
<type name="crypto_fun"/>
<type name="crypto_fun_arg"/>
@@ -398,7 +398,7 @@ CryptoKeyFun(clear) -> term()</code>
</func>
<func>
- <name name="diff_dirs" arity="2"/>
+ <name name="diff_dirs" arity="2" since=""/>
<fsummary>Compare the BEAM files in two directories.</fsummary>
<desc>
<p>Compares the BEAM files in two directories as
@@ -409,7 +409,7 @@ CryptoKeyFun(clear) -> term()</code>
</func>
<func>
- <name name="format_error" arity="1"/>
+ <name name="format_error" arity="1" since=""/>
<fsummary>Return an English description of a BEAM read error reply.
</fsummary>
<desc>
@@ -422,7 +422,7 @@ CryptoKeyFun(clear) -> term()</code>
</func>
<func>
- <name name="info" arity="1"/>
+ <name name="info" arity="1" since=""/>
<fsummary>Information about a BEAM file.</fsummary>
<desc>
<p>Returns a list containing some information about a BEAM file
@@ -449,7 +449,7 @@ CryptoKeyFun(clear) -> term()</code>
</func>
<func>
- <name name="md5" arity="1"/>
+ <name name="md5" arity="1" since=""/>
<fsummary>Read the module version of the BEAM file.</fsummary>
<desc>
<p>Calculates an MD5 redundancy check for the code of the module
@@ -458,7 +458,7 @@ CryptoKeyFun(clear) -> term()</code>
</func>
<func>
- <name name="strip" arity="1"/>
+ <name name="strip" arity="1" since=""/>
<fsummary>Remove chunks not needed by the loader from a BEAM file.
</fsummary>
<desc>
@@ -470,7 +470,7 @@ CryptoKeyFun(clear) -> term()</code>
</func>
<func>
- <name name="strip_files" arity="1"/>
+ <name name="strip_files" arity="1" since=""/>
<fsummary>Removes chunks not needed by the loader from BEAM files.
</fsummary>
<desc>
@@ -483,7 +483,7 @@ CryptoKeyFun(clear) -> term()</code>
</func>
<func>
- <name name="strip_release" arity="1"/>
+ <name name="strip_release" arity="1" since=""/>
<fsummary>Remove chunks not needed by the loader from all BEAM files of
a release.</fsummary>
<desc>
@@ -497,7 +497,7 @@ CryptoKeyFun(clear) -> term()</code>
</func>
<func>
- <name name="version" arity="1"/>
+ <name name="version" arity="1" since=""/>
<fsummary>Read the module version of the BEAM file.</fsummary>
<desc>
<p>Returns the module version or versions. A version is defined by
diff --git a/lib/stdlib/doc/src/binary.xml b/lib/stdlib/doc/src/binary.xml
index 6a86d6c7ba..f3d4edd30f 100644
--- a/lib/stdlib/doc/src/binary.xml
+++ b/lib/stdlib/doc/src/binary.xml
@@ -34,7 +34,7 @@
<rev>A</rev>
<file>binary.xml</file>
</header>
- <module>binary</module>
+ <module since="OTP R14B">binary</module>
<modulesummary>Library for handling binary data.</modulesummary>
<description>
@@ -79,7 +79,7 @@
<funcs>
<func>
- <name name="at" arity="2"/>
+ <name name="at" arity="2" since="OTP R14B"/>
<fsummary>Return the byte at a specific position in a binary.</fsummary>
<desc>
<p>Returns the byte at position <c><anno>Pos</anno></c> (zero-based) in
@@ -90,7 +90,7 @@
</func>
<func>
- <name name="bin_to_list" arity="1"/>
+ <name name="bin_to_list" arity="1" since="OTP R14B"/>
<fsummary>Convert a binary to a list of integers.</fsummary>
<desc>
<p>Same as <c>bin_to_list(<anno>Subject</anno>, {0,byte_size(<anno>Subject</anno>)})</c>.</p>
@@ -98,7 +98,7 @@
</func>
<func>
- <name name="bin_to_list" arity="2"/>
+ <name name="bin_to_list" arity="2" since="OTP R14B"/>
<fsummary>Convert a binary to a list of integers.</fsummary>
<desc>
<p>Converts <c><anno>Subject</anno></c> to a list of <c>byte()</c>s, each
@@ -118,7 +118,7 @@
</func>
<func>
- <name name="bin_to_list" arity="3"/>
+ <name name="bin_to_list" arity="3" since="OTP R14B"/>
<fsummary>Convert a binary to a list of integers.</fsummary>
<desc>
<p>Same as<c> bin_to_list(<anno>Subject</anno>, {<anno>Pos</anno>, <anno>Len</anno>})</c>.</p>
@@ -126,7 +126,7 @@
</func>
<func>
- <name name="compile_pattern" arity="1"/>
+ <name name="compile_pattern" arity="1" since="OTP R14B"/>
<fsummary>Precompile a binary search pattern.</fsummary>
<desc>
<p>Builds an internal structure representing a compilation of a
@@ -158,7 +158,7 @@
</func>
<func>
- <name name="copy" arity="1"/>
+ <name name="copy" arity="1" since="OTP R14B"/>
<fsummary>Create a duplicate of a binary.</fsummary>
<desc>
<p>Same as <c>copy(<anno>Subject</anno>, 1)</c>.</p>
@@ -166,7 +166,7 @@
</func>
<func>
- <name name="copy" arity="2"/>
+ <name name="copy" arity="2" since="OTP R14B"/>
<fsummary>Duplicate a binary <c>N</c> times and create a new.</fsummary>
<desc>
<p>Creates a binary with the content of <c><anno>Subject</anno></c>
@@ -193,7 +193,7 @@
</func>
<func>
- <name name="decode_unsigned" arity="1"/>
+ <name name="decode_unsigned" arity="1" since="OTP R14B"/>
<fsummary>Decode a whole binary into an integer of arbitrary size.
</fsummary>
<desc>
@@ -202,7 +202,7 @@
</func>
<func>
- <name name="decode_unsigned" arity="2"/>
+ <name name="decode_unsigned" arity="2" since="OTP R14B"/>
<fsummary>Decode a whole binary into an integer of arbitrary size.
</fsummary>
<desc>
@@ -219,7 +219,7 @@
</func>
<func>
- <name name="encode_unsigned" arity="1"/>
+ <name name="encode_unsigned" arity="1" since="OTP R14B"/>
<fsummary>Encode an unsigned integer into the minimal binary.</fsummary>
<desc>
<p>Same as <c>encode_unsigned(<anno>Unsigned</anno>, big)</c>.</p>
@@ -227,7 +227,7 @@
</func>
<func>
- <name name="encode_unsigned" arity="2"/>
+ <name name="encode_unsigned" arity="2" since="OTP R14B"/>
<fsummary>Encode an unsigned integer into the minimal binary.</fsummary>
<desc>
<p>Converts a positive integer to the smallest possible
@@ -243,7 +243,7 @@
</func>
<func>
- <name name="first" arity="1"/>
+ <name name="first" arity="1" since="OTP R14B"/>
<fsummary>Return the first byte of a binary.</fsummary>
<desc>
<p>Returns the first byte of binary <c><anno>Subject</anno></c> as an
@@ -253,7 +253,7 @@
</func>
<func>
- <name name="last" arity="1"/>
+ <name name="last" arity="1" since="OTP R14B"/>
<fsummary>Return the last byte of a binary.</fsummary>
<desc>
<p>Returns the last byte of binary <c><anno>Subject</anno></c> as an
@@ -263,7 +263,7 @@
</func>
<func>
- <name name="list_to_bin" arity="1"/>
+ <name name="list_to_bin" arity="1" since="OTP R14B"/>
<fsummary>Convert a list of integers and binaries to a binary.</fsummary>
<desc>
<p>Works exactly as
@@ -273,7 +273,7 @@
</func>
<func>
- <name name="longest_common_prefix" arity="1"/>
+ <name name="longest_common_prefix" arity="1" since="OTP R14B"/>
<fsummary>Return length of longest common prefix for a set of binaries.
</fsummary>
<desc>
@@ -294,7 +294,7 @@
</func>
<func>
- <name name="longest_common_suffix" arity="1"/>
+ <name name="longest_common_suffix" arity="1" since="OTP R14B"/>
<fsummary>Return length of longest common suffix for a set of binaries.
</fsummary>
<desc>
@@ -315,7 +315,7 @@
</func>
<func>
- <name name="match" arity="2"/>
+ <name name="match" arity="2" since="OTP R14B"/>
<fsummary>Search for the first match of a pattern in a binary.</fsummary>
<desc>
<p>Same as <c>match(<anno>Subject</anno>, <anno>Pattern</anno>, [])</c>.
@@ -324,7 +324,7 @@
</func>
<func>
- <name name="match" arity="3"/>
+ <name name="match" arity="3" since="OTP R14B"/>
<fsummary>Search for the first match of a pattern in a binary.</fsummary>
<type name="part"/>
<desc>
@@ -372,7 +372,7 @@
</func>
<func>
- <name name="matches" arity="2"/>
+ <name name="matches" arity="2" since="OTP R14B"/>
<fsummary>Search for all matches of a pattern in a binary.</fsummary>
<desc>
<p>Same as <c>matches(<anno>Subject</anno>, <anno>Pattern</anno>, [])</c>.
@@ -381,7 +381,7 @@
</func>
<func>
- <name name="matches" arity="3"/>
+ <name name="matches" arity="3" since="OTP R14B"/>
<fsummary>Search for all matches of a pattern in a binary.</fsummary>
<type name="part"/>
<desc>
@@ -425,7 +425,7 @@
</func>
<func>
- <name name="part" arity="2"/>
+ <name name="part" arity="2" since="OTP R14B"/>
<fsummary>Extract a part of a binary.</fsummary>
<desc>
<p>Extracts the part of binary <c><anno>Subject</anno></c> described by
@@ -453,7 +453,7 @@
</func>
<func>
- <name name="part" arity="3"/>
+ <name name="part" arity="3" since="OTP R14B"/>
<fsummary>Extract a part of a binary.</fsummary>
<desc>
<p>Same as <c>part(<anno>Subject</anno>, {<anno>Pos</anno>,
@@ -462,7 +462,7 @@
</func>
<func>
- <name name="referenced_byte_size" arity="1"/>
+ <name name="referenced_byte_size" arity="1" since="OTP R14B"/>
<fsummary>Determine the size of the binary pointed out by a subbinary.
</fsummary>
<desc>
@@ -525,7 +525,7 @@ store(Binary, GBSet) ->
</func>
<func>
- <name name="replace" arity="3"/>
+ <name name="replace" arity="3" since="OTP R14B"/>
<fsummary>Replace bytes in a binary according to a pattern.</fsummary>
<desc>
<p>Same as <c>replace(<anno>Subject</anno>, <anno>Pattern</anno>, <anno>Replacement</anno>,[])</c>.</p>
@@ -533,7 +533,7 @@ store(Binary, GBSet) ->
</func>
<func>
- <name name="replace" arity="4"/>
+ <name name="replace" arity="4" since="OTP R14B"/>
<fsummary>Replace bytes in a binary according to a pattern.</fsummary>
<type_desc variable="OnePos">An integer() =&lt; byte_size(<anno>Replacement</anno>)
</type_desc>
@@ -575,7 +575,7 @@ store(Binary, GBSet) ->
</func>
<func>
- <name name="split" arity="2"/>
+ <name name="split" arity="2" since="OTP R14B"/>
<fsummary>Split a binary according to a pattern.</fsummary>
<desc>
<p>Same as <c>split(<anno>Subject</anno>, <anno>Pattern</anno>,
@@ -584,7 +584,7 @@ store(Binary, GBSet) ->
</func>
<func>
- <name name="split" arity="3"/>
+ <name name="split" arity="3" since="OTP R14B"/>
<fsummary>Split a binary according to a pattern.</fsummary>
<desc>
<p>Splits <c><anno>Subject</anno></c> into a list of binaries based on
diff --git a/lib/stdlib/doc/src/c.xml b/lib/stdlib/doc/src/c.xml
index b6cb6f5aae..29edc373c7 100644
--- a/lib/stdlib/doc/src/c.xml
+++ b/lib/stdlib/doc/src/c.xml
@@ -28,7 +28,7 @@
<date>1996-10-30</date>
<rev>B</rev>
</header>
- <module>c</module>
+ <module since="">c</module>
<modulesummary>Command interface module.</modulesummary>
<description>
<p>This module enables users to enter the short form of
@@ -41,7 +41,7 @@
<funcs>
<func>
- <name name="bt" arity="1"/>
+ <name name="bt" arity="1" since=""/>
<fsummary>Stack backtrace for a process.</fsummary>
<desc>
<p>Stack backtrace for a process. Equivalent to
@@ -50,9 +50,9 @@
</func>
<func>
- <name name="c" arity="1"/>
- <name name="c" arity="2"/>
- <name name="c" arity="3"/>
+ <name name="c" arity="1" since=""/>
+ <name name="c" arity="2" since=""/>
+ <name name="c" arity="3" since="OTP 20.0"/>
<fsummary>Compile and load a file or module.</fsummary>
<desc>
<p>Compiles and then purges and loads the code for a module.
@@ -80,7 +80,7 @@
</func>
<func>
- <name name="cd" arity="1"/>
+ <name name="cd" arity="1" since=""/>
<fsummary>Change working directory.</fsummary>
<desc>
<p>Changes working directory to <c><anno>Dir</anno></c>, which can be a
@@ -94,7 +94,7 @@
</func>
<func>
- <name name="erlangrc" arity="1"/>
+ <name name="erlangrc" arity="1" since="OTP 21.0"/>
<fsummary>Load an erlang resource file.</fsummary>
<desc>
<p>Search <c>PathList</c> and load <c>.erlang</c> resource file if
@@ -103,7 +103,7 @@
</func>
<func>
- <name name="flush" arity="0"/>
+ <name name="flush" arity="0" since=""/>
<fsummary>Flush any messages sent to the shell.</fsummary>
<desc>
<p>Flushes any messages sent to the shell.</p>
@@ -111,7 +111,7 @@
</func>
<func>
- <name name="help" arity="0"/>
+ <name name="help" arity="0" since=""/>
<fsummary>Help information.</fsummary>
<desc>
<p>Displays help information: all valid shell internal commands,
@@ -120,8 +120,8 @@
</func>
<func>
- <name name="i" arity="0"/>
- <name name="ni" arity="0"/>
+ <name name="i" arity="0" since=""/>
+ <name name="ni" arity="0" since=""/>
<fsummary>System information.</fsummary>
<desc>
<p><c>i/0</c> displays system information, listing
@@ -131,7 +131,7 @@
</func>
<func>
- <name name="i" arity="3"/>
+ <name name="i" arity="3" since=""/>
<fsummary>Information about pid &lt;X.Y.Z&gt;.</fsummary>
<desc>
<p>Displays information about a process, Equivalent to
@@ -141,7 +141,7 @@
</func>
<func>
- <name name="l" arity="1"/>
+ <name name="l" arity="1" since=""/>
<fsummary>Load or reload a module.</fsummary>
<desc>
<p>Purges and loads, or reloads, a module by calling
@@ -154,7 +154,7 @@
</func>
<func>
- <name>lc(Files) -> ok</name>
+ <name since="">lc(Files) -> ok</name>
<fsummary>Compile a list of files.</fsummary>
<type>
<v>Files = [File]</v>
@@ -171,7 +171,7 @@
</func>
<func>
- <name name="lm" arity="0"/>
+ <name name="lm" arity="0" since="OTP 20.0"/>
<fsummary>Loads all modified modules.</fsummary>
<desc>
<p>Reloads all currently loaded modules that have changed on disk (see <c>mm()</c>).
@@ -180,7 +180,7 @@
</func>
<func>
- <name name="ls" arity="0"/>
+ <name name="ls" arity="0" since=""/>
<fsummary>List files in the current directory.</fsummary>
<desc>
<p>Lists files in the current directory.</p>
@@ -188,7 +188,7 @@
</func>
<func>
- <name name="ls" arity="1"/>
+ <name name="ls" arity="1" since=""/>
<fsummary>List files in a directory or a single file.</fsummary>
<desc>
<p>Lists files in directory <c><anno>Dir</anno></c> or, if <c>Dir</c>
@@ -197,7 +197,7 @@
</func>
<func>
- <name name="m" arity="0"/>
+ <name name="m" arity="0" since=""/>
<fsummary>Which modules are loaded.</fsummary>
<desc>
<p>Displays information about the loaded modules, including
@@ -206,7 +206,7 @@
</func>
<func>
- <name name="m" arity="1"/>
+ <name name="m" arity="1" since=""/>
<fsummary>Information about a module.</fsummary>
<desc>
<p>Displays information about <c><anno>Module</anno></c>.</p>
@@ -214,7 +214,7 @@
</func>
<func>
- <name name="mm" arity="0"/>
+ <name name="mm" arity="0" since="OTP 20.0"/>
<fsummary>Lists all modified modules.</fsummary>
<desc>
<p>Lists all modified modules. Shorthand for
@@ -223,7 +223,7 @@
</func>
<func>
- <name name="memory" arity="0"/>
+ <name name="memory" arity="0" since=""/>
<fsummary>Memory allocation information.</fsummary>
<desc>
<p>Memory allocation information. Equivalent to
@@ -232,8 +232,8 @@
</func>
<func>
- <name name="memory" arity="1" clause_i="1"/>
- <name name="memory" arity="1" clause_i="2"/>
+ <name name="memory" arity="1" clause_i="1" since=""/>
+ <name name="memory" arity="1" clause_i="2" since=""/>
<fsummary>Memory allocation information.</fsummary>
<desc>
<p>Memory allocation information. Equivalent to
@@ -242,8 +242,8 @@
</func>
<func>
- <name name="nc" arity="1"/>
- <name name="nc" arity="2"/>
+ <name name="nc" arity="1" since=""/>
+ <name name="nc" arity="2" since=""/>
<fsummary>Compile and load code in a file on all nodes.</fsummary>
<desc>
<p>Compiles and then loads the code for a file on all nodes.
@@ -255,7 +255,7 @@ compile:file(<anno>File</anno>, <anno>Options</anno> ++ [report_errors, report_w
</func>
<func>
- <name name="nl" arity="1"/>
+ <name name="nl" arity="1" since=""/>
<fsummary>Load module on all nodes.</fsummary>
<desc>
<p>Loads <c><anno>Module</anno></c> on all nodes.</p>
@@ -263,7 +263,7 @@ compile:file(<anno>File</anno>, <anno>Options</anno> ++ [report_errors, report_w
</func>
<func>
- <name name="pid" arity="3"/>
+ <name name="pid" arity="3" since=""/>
<fsummary>Convert <c>X,Y,Z</c> to a pid.</fsummary>
<desc>
<p>Converts <c><anno>X</anno></c>, <c><anno>Y</anno></c>,
@@ -273,7 +273,7 @@ compile:file(<anno>File</anno>, <anno>Options</anno> ++ [report_errors, report_w
</func>
<func>
- <name name="pwd" arity="0"/>
+ <name name="pwd" arity="0" since=""/>
<fsummary>Print working directory.</fsummary>
<desc>
<p>Prints the name of the working directory.</p>
@@ -281,7 +281,7 @@ compile:file(<anno>File</anno>, <anno>Options</anno> ++ [report_errors, report_w
</func>
<func>
- <name name="q" arity="0"/>
+ <name name="q" arity="0" since=""/>
<fsummary>Quit - shorthand for <c>init:stop()</c>.</fsummary>
<desc>
<p>This function is shorthand for <c>init:stop()</c>, that is,
@@ -290,8 +290,8 @@ compile:file(<anno>File</anno>, <anno>Options</anno> ++ [report_errors, report_w
</func>
<func>
- <name name="regs" arity="0"/>
- <name name="nregs" arity="0"/>
+ <name name="regs" arity="0" since=""/>
+ <name name="nregs" arity="0" since=""/>
<fsummary>Information about registered processes.</fsummary>
<desc>
<p><c>regs/0</c> displays information about all registered
@@ -301,7 +301,7 @@ compile:file(<anno>File</anno>, <anno>Options</anno> ++ [report_errors, report_w
</func>
<func>
- <name name="uptime" arity="0"/>
+ <name name="uptime" arity="0" since="OTP 18.0"/>
<fsummary>Print node uptime.</fsummary>
<desc>
<p>Prints the node uptime (as specified by
@@ -310,7 +310,7 @@ compile:file(<anno>File</anno>, <anno>Options</anno> ++ [report_errors, report_w
</func>
<func>
- <name>xm(ModSpec) -> void()</name>
+ <name since="">xm(ModSpec) -> void()</name>
<fsummary>Cross-reference check a module.</fsummary>
<type>
<v>ModSpec = Module | Filename</v>
@@ -325,7 +325,7 @@ compile:file(<anno>File</anno>, <anno>Options</anno> ++ [report_errors, report_w
</func>
<func>
- <name>y(File) -> YeccRet</name>
+ <name since="">y(File) -> YeccRet</name>
<fsummary>Generate an LALR-1 parser.</fsummary>
<type>
<v>File = name()</v>
@@ -344,7 +344,7 @@ yecc:file(File)</code>
</func>
<func>
- <name>y(File, Options) -> YeccRet</name>
+ <name since="">y(File, Options) -> YeccRet</name>
<fsummary>Generate an LALR-1 parser.</fsummary>
<type>
<v>File = name()</v>
diff --git a/lib/stdlib/doc/src/calendar.xml b/lib/stdlib/doc/src/calendar.xml
index 5aee635c38..518a085c89 100644
--- a/lib/stdlib/doc/src/calendar.xml
+++ b/lib/stdlib/doc/src/calendar.xml
@@ -28,7 +28,7 @@
<date>1996-11-05</date>
<rev>B</rev>
</header>
- <module>calendar</module>
+ <module since="">calendar</module>
<modulesummary>Local and universal time, day of the week, date and time
conversions.</modulesummary>
<description>
@@ -128,8 +128,8 @@
<funcs>
<func>
- <name name="date_to_gregorian_days" arity="1"/>
- <name name="date_to_gregorian_days" arity="3"/>
+ <name name="date_to_gregorian_days" arity="1" since=""/>
+ <name name="date_to_gregorian_days" arity="3" since=""/>
<fsummary>Compute the number of days from year 0 up to the specified
date.</fsummary>
<type variable="Date" name_i="1"/>
@@ -143,7 +143,7 @@
</func>
<func>
- <name name="datetime_to_gregorian_seconds" arity="1"/>
+ <name name="datetime_to_gregorian_seconds" arity="1" since=""/>
<fsummary>Compute the number of seconds from year 0 up to the specified
date and time.</fsummary>
<desc>
@@ -153,8 +153,8 @@
</func>
<func>
- <name name="day_of_the_week" arity="1"/>
- <name name="day_of_the_week" arity="3"/>
+ <name name="day_of_the_week" arity="1" since=""/>
+ <name name="day_of_the_week" arity="3" since=""/>
<fsummary>Compute the day of the week.</fsummary>
<type variable="Date" name_i="1"/>
<type variable="Year"/>
@@ -169,7 +169,7 @@
</func>
<func>
- <name name="gregorian_days_to_date" arity="1"/>
+ <name name="gregorian_days_to_date" arity="1" since=""/>
<fsummary>Compute the date from the number of gregorian days.</fsummary>
<desc>
<p>Computes the date from the specified number of gregorian days.</p>
@@ -177,7 +177,7 @@
</func>
<func>
- <name name="gregorian_seconds_to_datetime" arity="1"/>
+ <name name="gregorian_seconds_to_datetime" arity="1" since=""/>
<fsummary>Compute the date and time from the number of gregorian seconds.
</fsummary>
<desc>
@@ -187,7 +187,7 @@
</func>
<func>
- <name name="is_leap_year" arity="1"/>
+ <name name="is_leap_year" arity="1" since=""/>
<fsummary>Check if the year is a leap year.</fsummary>
<desc>
<p>Checks if the specified year is a leap year.</p>
@@ -195,7 +195,7 @@
</func>
<func>
- <name name="iso_week_number" arity="0"/>
+ <name name="iso_week_number" arity="0" since="OTP R14B02"/>
<fsummary>Compute the ISO week number for the actual date.</fsummary>
<desc>
<p>Returns tuple <c>{Year, WeekNum}</c> representing
@@ -206,7 +206,7 @@
</func>
<func>
- <name name="iso_week_number" arity="1"/>
+ <name name="iso_week_number" arity="1" since="OTP R14B02"/>
<fsummary>Compute the ISO week number for the specified date.</fsummary>
<desc>
<p>Returns tuple <c>{Year, WeekNum}</c> representing
@@ -215,7 +215,7 @@
</func>
<func>
- <name name="last_day_of_the_month" arity="2"/>
+ <name name="last_day_of_the_month" arity="2" since=""/>
<fsummary>Compute the number of days in a month.</fsummary>
<desc>
<p>Computes the number of days in a month.</p>
@@ -223,7 +223,7 @@
</func>
<func>
- <name name="local_time" arity="0"/>
+ <name name="local_time" arity="0" since=""/>
<fsummary>Compute local time.</fsummary>
<desc>
<p>Returns the local time reported by
@@ -232,7 +232,7 @@
</func>
<func>
- <name name="local_time_to_universal_time" arity="1"/>
+ <name name="local_time_to_universal_time" arity="1" since=""/>
<fsummary>Convert from local time to universal time (deprecated).
</fsummary>
<desc>
@@ -253,7 +253,7 @@
</func>
<func>
- <name name="local_time_to_universal_time_dst" arity="1"/>
+ <name name="local_time_to_universal_time_dst" arity="1" since=""/>
<fsummary>Convert from local time to universal time(s).</fsummary>
<desc>
<p>Converts from local time to Universal Coordinated Time (UTC).
@@ -285,7 +285,7 @@
</func>
<func>
- <name name="now_to_datetime" arity="1"/>
+ <name name="now_to_datetime" arity="1" since=""/>
<fsummary>Convert now to date and time.</fsummary>
<desc>
<p>Returns Universal Coordinated Time (UTC)
@@ -296,7 +296,7 @@
</func>
<func>
- <name name="now_to_local_time" arity="1"/>
+ <name name="now_to_local_time" arity="1" since=""/>
<fsummary>Convert now to local date and time.</fsummary>
<desc>
<p>Returns local date and time converted from the return value from
@@ -306,7 +306,7 @@
</func>
<func>
- <name name="now_to_universal_time" arity="1"/>
+ <name name="now_to_universal_time" arity="1" since=""/>
<fsummary>Convert now to date and time.</fsummary>
<desc>
<p>Returns Universal Coordinated Time (UTC)
@@ -317,8 +317,8 @@
</func>
<func>
- <name name="rfc3339_to_system_time" arity="1"/>
- <name name="rfc3339_to_system_time" arity="2"/>
+ <name name="rfc3339_to_system_time" arity="1" since="OTP 21.0"/>
+ <name name="rfc3339_to_system_time" arity="2" since="OTP 21.0"/>
<fsummary>Convert from RFC 3339 timestamp to system time.</fsummary>
<type name="rfc3339_string"/>
<type name="rfc3339_time_unit"/>
@@ -343,7 +343,7 @@
</func>
<func>
- <name name="seconds_to_daystime" arity="1"/>
+ <name name="seconds_to_daystime" arity="1" since=""/>
<fsummary>Compute days and time from seconds.</fsummary>
<desc>
<p>Converts a specified number of seconds into days, hours, minutes,
@@ -354,7 +354,7 @@
</func>
<func>
- <name name="seconds_to_time" arity="1"/>
+ <name name="seconds_to_time" arity="1" since=""/>
<fsummary>Compute time from seconds.</fsummary>
<type name="secs_per_day"/>
<desc>
@@ -365,7 +365,7 @@
</func>
<func>
- <name name="system_time_to_local_time" arity="2"/>
+ <name name="system_time_to_local_time" arity="2" since="OTP 21.0"/>
<fsummary>Convert system time to local date and time.</fsummary>
<desc>
<p>Converts a specified system time into local date and time.</p>
@@ -373,8 +373,8 @@
</func>
<func>
- <name name="system_time_to_rfc3339" arity="1"/>
- <name name="system_time_to_rfc3339" arity="2"/>
+ <name name="system_time_to_rfc3339" arity="1" since="OTP 21.0"/>
+ <name name="system_time_to_rfc3339" arity="2" since="OTP 21.0"/>
<fsummary>Convert from system to RFC 3339 timestamp.</fsummary>
<type name="offset"/>
<type name="rfc3339_string"/>
@@ -426,7 +426,7 @@
</func>
<func>
- <name name="system_time_to_universal_time" arity="2"/>
+ <name name="system_time_to_universal_time" arity="2" since="OTP 21.0"/>
<fsummary>Convert system time to universal date and time.</fsummary>
<desc>
<p>Converts a specified system time into universal date and time.</p>
@@ -434,7 +434,7 @@
</func>
<func>
- <name name="time_difference" arity="2"/>
+ <name name="time_difference" arity="2" since=""/>
<fsummary>Compute the difference between two times (deprecated).
</fsummary>
<desc>
@@ -449,7 +449,7 @@
</func>
<func>
- <name name="time_to_seconds" arity="1"/>
+ <name name="time_to_seconds" arity="1" since=""/>
<fsummary>Compute the number of seconds since midnight up to the
specified time.</fsummary>
<type name="secs_per_day"/>
@@ -460,7 +460,7 @@
</func>
<func>
- <name name="universal_time" arity="0"/>
+ <name name="universal_time" arity="0" since=""/>
<fsummary>Compute universal time.</fsummary>
<desc>
<p>Returns the Universal Coordinated Time (UTC)
@@ -470,7 +470,7 @@
</func>
<func>
- <name name="universal_time_to_local_time" arity="1"/>
+ <name name="universal_time_to_local_time" arity="1" since=""/>
<fsummary>Convert from universal time to local time.</fsummary>
<desc>
<p>Converts from Universal Coordinated Time (UTC) to local time.
@@ -480,8 +480,8 @@
</func>
<func>
- <name name="valid_date" arity="1"/>
- <name name="valid_date" arity="3"/>
+ <name name="valid_date" arity="1" since=""/>
+ <name name="valid_date" arity="3" since=""/>
<fsummary>Check if a date is valid</fsummary>
<type variable="Date" name_i="1"/>
<type variable="Year"/>
diff --git a/lib/stdlib/doc/src/dets.xml b/lib/stdlib/doc/src/dets.xml
index eb6e32aecf..8e4e002000 100644
--- a/lib/stdlib/doc/src/dets.xml
+++ b/lib/stdlib/doc/src/dets.xml
@@ -32,7 +32,7 @@
<rev>B</rev>
<file>dets.xml</file>
</header>
- <module>dets</module>
+ <module since="">dets</module>
<modulesummary>A disk-based term storage.</modulesummary>
<description>
<p>This module provides a term storage on file. The
@@ -188,7 +188,7 @@
<funcs>
<func>
- <name name="all" arity="0"/>
+ <name name="all" arity="0" since=""/>
<fsummary>Return a list of the names of all open Dets tables on
this node.</fsummary>
<desc>
@@ -197,7 +197,7 @@
</func>
<func>
- <name name="bchunk" arity="2"/>
+ <name name="bchunk" arity="2" since=""/>
<fsummary>Return a chunk of objects stored in a Dets table.
</fsummary>
<desc>
@@ -227,7 +227,7 @@
</func>
<func>
- <name name="close" arity="1"/>
+ <name name="close" arity="1" since=""/>
<fsummary>Close a Dets table.</fsummary>
<desc>
<p>Closes a table. Only processes that have opened a table are
@@ -239,7 +239,7 @@
</func>
<func>
- <name name="delete" arity="2"/>
+ <name name="delete" arity="2" since=""/>
<fsummary>Delete all objects with a specified key from a Dets
table.</fsummary>
<desc>
@@ -249,7 +249,7 @@
</func>
<func>
- <name name="delete_all_objects" arity="1"/>
+ <name name="delete_all_objects" arity="1" since=""/>
<fsummary>Delete all objects from a Dets table.</fsummary>
<desc>
<p>Deletes all objects from a table in almost constant time.
@@ -259,7 +259,7 @@
</func>
<func>
- <name name="delete_object" arity="2"/>
+ <name name="delete_object" arity="2" since=""/>
<fsummary>Delete a specified object from a Dets table.</fsummary>
<desc>
<p>Deletes all instances of a specified object from a table. If a
@@ -270,7 +270,7 @@
</func>
<func>
- <name name="first" arity="1"/>
+ <name name="first" arity="1" since=""/>
<fsummary>Return the first key stored in a Dets table.</fsummary>
<desc>
<p>Returns the first key stored in table <c><anno>Name</anno></c>
@@ -295,8 +295,8 @@
</func>
<func>
- <name name="foldl" arity="3"/>
- <name name="foldr" arity="3"/>
+ <name name="foldl" arity="3" since=""/>
+ <name name="foldr" arity="3" since=""/>
<fsummary>Fold a function over a Dets table.</fsummary>
<desc>
<p>Calls <c><anno>Function</anno></c> on successive elements of
@@ -309,7 +309,7 @@
</func>
<func>
- <name name="from_ets" arity="2"/>
+ <name name="from_ets" arity="2" since=""/>
<fsummary>Replace the objects of a Dets table with the objects
of an ETS table.</fsummary>
<desc>
@@ -322,7 +322,7 @@
</func>
<func>
- <name name="info" arity="1"/>
+ <name name="info" arity="1" since=""/>
<fsummary>Return information about a Dets table.</fsummary>
<desc>
<p>Returns information about table <c><anno>Name</anno></c>
@@ -354,7 +354,7 @@
</func>
<func>
- <name name="info" arity="2"/>
+ <name name="info" arity="2" since=""/>
<fsummary>Return the information associated with a specified item for
a Dets table.</fsummary>
<desc>
@@ -455,8 +455,8 @@
</func>
<func>
- <name name="init_table" arity="2"/>
- <name name="init_table" arity="3"/>
+ <name name="init_table" arity="2" since=""/>
+ <name name="init_table" arity="3" since=""/>
<fsummary>Replace all objects of a Dets table.</fsummary>
<desc>
<p>Replaces the existing objects of table <c><anno>Name</anno></c>
@@ -516,7 +516,7 @@
</func>
<func>
- <name name="insert" arity="2"/>
+ <name name="insert" arity="2" since=""/>
<fsummary>Insert one or more objects into a Dets table.</fsummary>
<desc>
<p>Inserts one or more objects into the table <c><anno>Name</anno></c>.
@@ -527,7 +527,7 @@
</func>
<func>
- <name name="insert_new" arity="2"/>
+ <name name="insert_new" arity="2" since=""/>
<fsummary>Insert one or more objects into a Dets table.</fsummary>
<desc>
<p>Inserts one or more objects into table <c><anno>Name</anno></c>.
@@ -539,7 +539,7 @@
</func>
<func>
- <name name="is_compatible_bchunk_format" arity="2"/>
+ <name name="is_compatible_bchunk_format" arity="2" since=""/>
<fsummary>Test compatibility of chunk data of a table.</fsummary>
<desc>
<p>Returns <c>true</c> if it would be possible to initialize
@@ -554,7 +554,7 @@
</func>
<func>
- <name name="is_dets_file" arity="1"/>
+ <name name="is_dets_file" arity="1" since=""/>
<fsummary>Test for a Dets table.</fsummary>
<desc>
<p>Returns <c>true</c> if file <c><anno>Filename</anno></c>
@@ -563,7 +563,7 @@
</func>
<func>
- <name name="lookup" arity="2"/>
+ <name name="lookup" arity="2" since=""/>
<fsummary>Return all objects with a specified key stored in a
Dets table.</fsummary>
<desc>
@@ -590,7 +590,7 @@ ok
</func>
<func>
- <name name="match" arity="1"/>
+ <name name="match" arity="1" since=""/>
<fsummary>Match a chunk of objects stored in a Dets table and
return a list of variable bindings.</fsummary>
<desc>
@@ -606,7 +606,7 @@ ok
</func>
<func>
- <name name="match" arity="2"/>
+ <name name="match" arity="2" since=""/>
<fsummary>Match the objects stored in a Dets table and return a
list of variable bindings.</fsummary>
<desc>
@@ -622,7 +622,7 @@ ok
</func>
<func>
- <name name="match" arity="3"/>
+ <name name="match" arity="3" since=""/>
<fsummary>Match the first chunk of objects stored in a Dets table
and return a list of variable bindings.</fsummary>
<desc>
@@ -654,7 +654,7 @@ ok
</func>
<func>
- <name name="match_delete" arity="2"/>
+ <name name="match_delete" arity="2" since=""/>
<fsummary>Delete all objects that match a given pattern from a
Dets table.</fsummary>
<desc>
@@ -667,7 +667,7 @@ ok
</func>
<func>
- <name name="match_object" arity="1"/>
+ <name name="match_object" arity="1" since=""/>
<fsummary>Match a chunk of objects stored in a Dets table and
return a list of objects.</fsummary>
<desc>
@@ -683,7 +683,7 @@ ok
</func>
<func>
- <name name="match_object" arity="2"/>
+ <name name="match_object" arity="2" since=""/>
<fsummary>Match the objects stored in a Dets table and return
a list of objects.</fsummary>
<desc>
@@ -702,7 +702,7 @@ ok
</func>
<func>
- <name name="match_object" arity="3"/>
+ <name name="match_object" arity="3" since=""/>
<fsummary>Match the first chunk of objects stored in a Dets table
and return a list of objects.</fsummary>
<desc>
@@ -735,7 +735,7 @@ ok
</func>
<func>
- <name name="member" arity="2"/>
+ <name name="member" arity="2" since=""/>
<fsummary>Test for occurrence of a key in a Dets table.</fsummary>
<desc>
<p>Works like <seealso marker="#lookup/2"><c>lookup/2</c></seealso>,
@@ -746,7 +746,7 @@ ok
</func>
<func>
- <name name="next" arity="2"/>
+ <name name="next" arity="2" since=""/>
<fsummary>Return the next key in a Dets table.</fsummary>
<desc>
<p>Returns either the key following <c><anno>Key1</anno></c> in table
@@ -760,7 +760,7 @@ ok
</func>
<func>
- <name name="open_file" arity="1"/>
+ <name name="open_file" arity="1" since=""/>
<fsummary>Open an existing Dets table.</fsummary>
<desc>
<p>Opens an existing table. If the table is not properly closed,
@@ -770,7 +770,7 @@ ok
</func>
<func>
- <name name="open_file" arity="2"/>
+ <name name="open_file" arity="2" since=""/>
<fsummary>Open a Dets table.</fsummary>
<desc>
<p>Opens a table. An empty Dets table is created if no file
@@ -872,7 +872,7 @@ ok
</func>
<func>
- <name name="pid2name" arity="1"/>
+ <name name="pid2name" arity="1" since=""/>
<fsummary>Return the name of the Dets table handled by a pid.</fsummary>
<desc>
<p>Returns the table name given the pid of a process
@@ -883,7 +883,7 @@ ok
</func>
<func>
- <name name="repair_continuation" arity="2"/>
+ <name name="repair_continuation" arity="2" since=""/>
<fsummary>Repair a continuation from <c>select/1</c> or <c>select/3</c>.
</fsummary>
<desc>
@@ -917,7 +917,7 @@ ok
</func>
<func>
- <name name="safe_fixtable" arity="2"/>
+ <name name="safe_fixtable" arity="2" since=""/>
<fsummary>Fix a Dets table for safe traversal.</fsummary>
<desc>
<p>If <c><anno>Fix</anno></c> is <c>true</c>, table
@@ -945,7 +945,7 @@ ok
</func>
<func>
- <name name="select" arity="1"/>
+ <name name="select" arity="1" since=""/>
<fsummary>Apply a match specification to some objects stored in a
Dets table.</fsummary>
<desc>
@@ -962,7 +962,7 @@ ok
</func>
<func>
- <name name="select" arity="2"/>
+ <name name="select" arity="2" since=""/>
<fsummary>Apply a match specification to all objects stored in a
Dets table.</fsummary>
<desc>
@@ -984,7 +984,7 @@ ok
</func>
<func>
- <name name="select" arity="3"/>
+ <name name="select" arity="3" since=""/>
<fsummary>Apply a match specification to the first chunk of objects
stored in a Dets table.</fsummary>
<desc>
@@ -1019,7 +1019,7 @@ ok
</func>
<func>
- <name name="select_delete" arity="2"/>
+ <name name="select_delete" arity="2" since=""/>
<fsummary>Delete all objects that match a given pattern from a
Dets table.</fsummary>
<desc>
@@ -1036,7 +1036,7 @@ ok
</func>
<func>
- <name name="slot" arity="2"/>
+ <name name="slot" arity="2" since=""/>
<fsummary>Return the list of objects associated with a slot of a
Dets table.</fsummary>
<desc>
@@ -1049,7 +1049,7 @@ ok
</func>
<func>
- <name name="sync" arity="1"/>
+ <name name="sync" arity="1" since=""/>
<fsummary>Ensure that all updates made to a Dets table are written
to disk.</fsummary>
<desc>
@@ -1064,8 +1064,8 @@ ok
</func>
<func>
- <name name="table" arity="1"/>
- <name name="table" arity="2"/>
+ <name name="table" arity="1" since=""/>
+ <name name="table" arity="2" since=""/>
<fsummary>Return a QLC query handle.</fsummary>
<desc>
<p>Returns a Query List
@@ -1140,7 +1140,7 @@ true</pre>
</func>
<func>
- <name name="to_ets" arity="2"/>
+ <name name="to_ets" arity="2" since=""/>
<fsummary>Insert all objects of a Dets table into an ETS
table.</fsummary>
<desc>
@@ -1153,7 +1153,7 @@ true</pre>
</func>
<func>
- <name name="traverse" arity="2"/>
+ <name name="traverse" arity="2" since=""/>
<fsummary>Apply a function to all or some objects stored in a Dets
table.</fsummary>
<desc>
@@ -1192,7 +1192,7 @@ fun(X) -> {continue, X} end.</pre>
</func>
<func>
- <name name="update_counter" arity="3"/>
+ <name name="update_counter" arity="3" since=""/>
<fsummary>Update a counter object stored in a Dets table.
</fsummary>
<desc>
diff --git a/lib/stdlib/doc/src/dict.xml b/lib/stdlib/doc/src/dict.xml
index c229a18721..95a98cef12 100644
--- a/lib/stdlib/doc/src/dict.xml
+++ b/lib/stdlib/doc/src/dict.xml
@@ -28,7 +28,7 @@
<date>1997-01-15</date>
<rev>B</rev>
</header>
- <module>dict</module>
+ <module since="">dict</module>
<modulesummary>Key-value dictionary.</modulesummary>
<description>
<p>This module provides a <c>Key</c>-<c>Value</c> dictionary.
@@ -55,7 +55,7 @@
<funcs>
<func>
- <name name="append" arity="3"/>
+ <name name="append" arity="3" since=""/>
<fsummary>Append a value to keys in a dictionary.</fsummary>
<desc>
<p>Appends a new <c><anno>Value</anno></c> to the current list
@@ -65,7 +65,7 @@
</func>
<func>
- <name name="append_list" arity="3"/>
+ <name name="append_list" arity="3" since=""/>
<fsummary>Append new values to keys in a dictionary.</fsummary>
<desc>
<p>Appends a list of values <c><anno>ValList</anno></c> to
@@ -77,7 +77,7 @@
</func>
<func>
- <name name="erase" arity="2"/>
+ <name name="erase" arity="2" since=""/>
<fsummary>Erase a key from a dictionary.</fsummary>
<desc>
<p>Erases all items with a given key from a dictionary.</p>
@@ -85,7 +85,7 @@
</func>
<func>
- <name name="fetch" arity="2"/>
+ <name name="fetch" arity="2" since=""/>
<fsummary>Look up values in a dictionary.</fsummary>
<desc>
<p>Returns the value associated with <c><anno>Key</anno></c>
@@ -98,7 +98,7 @@
</func>
<func>
- <name name="fetch_keys" arity="1"/>
+ <name name="fetch_keys" arity="1" since=""/>
<fsummary>Return all keys in a dictionary.</fsummary>
<desc>
<p>Returns a list of all keys in dictionary <c>Dict</c>.</p>
@@ -106,7 +106,7 @@
</func>
<func>
- <name name="take" arity="2"/>
+ <name name="take" arity="2" since="OTP 20.0"/>
<fsummary>Return value and new dictionary without element with this value.</fsummary>
<desc>
<p>This function returns value from dictionary and a
@@ -116,7 +116,7 @@
</func>
<func>
- <name name="filter" arity="2"/>
+ <name name="filter" arity="2" since=""/>
<fsummary>Select elements that satisfy a predicate.</fsummary>
<desc>
<p><c><anno>Dict2</anno></c> is a dictionary of all keys and values in
@@ -127,7 +127,7 @@
</func>
<func>
- <name name="find" arity="2"/>
+ <name name="find" arity="2" since=""/>
<fsummary>Search for a key in a dictionary.</fsummary>
<desc>
<p>Searches for a key in dictionary <c>Dict</c>. Returns
@@ -139,7 +139,7 @@
</func>
<func>
- <name name="fold" arity="3"/>
+ <name name="fold" arity="3" since=""/>
<fsummary>Fold a function over a dictionary.</fsummary>
<desc>
<p>Calls <c><anno>Fun</anno></c> on successive keys and values of
@@ -153,7 +153,7 @@
</func>
<func>
- <name name="from_list" arity="1"/>
+ <name name="from_list" arity="1" since=""/>
<fsummary>Convert a list of pairs to a dictionary.</fsummary>
<desc>
<p>Converts the <c><anno>Key</anno></c>-<c><anno>Value</anno></c> list
@@ -162,7 +162,7 @@
</func>
<func>
- <name name="is_empty" arity="1"/>
+ <name name="is_empty" arity="1" since="OTP 17.0"/>
<fsummary>Return <c>true</c> if the dictionary is empty.</fsummary>
<desc>
<p>Returns <c>true</c> if dictionary <c><anno>Dict</anno></c> has no
@@ -171,7 +171,7 @@
</func>
<func>
- <name name="is_key" arity="2"/>
+ <name name="is_key" arity="2" since=""/>
<fsummary>Test if a key is in a dictionary.</fsummary>
<desc>
<p>Tests if <c><anno>Key</anno></c> is contained in
@@ -180,7 +180,7 @@
</func>
<func>
- <name name="map" arity="2"/>
+ <name name="map" arity="2" since=""/>
<fsummary>Map a function over a dictionary.</fsummary>
<desc>
<p>Calls <c><anno>Fun</anno></c> on successive keys and values
@@ -190,7 +190,7 @@
</func>
<func>
- <name name="merge" arity="3"/>
+ <name name="merge" arity="3" since=""/>
<fsummary>Merge two dictionaries.</fsummary>
<desc>
<p>Merges two dictionaries, <c><anno>Dict1</anno></c> and
@@ -209,7 +209,7 @@ merge(Fun, D1, D2) ->
</func>
<func>
- <name name="new" arity="0"/>
+ <name name="new" arity="0" since=""/>
<fsummary>Create a dictionary.</fsummary>
<desc>
<p>Creates a new dictionary.</p>
@@ -217,7 +217,7 @@ merge(Fun, D1, D2) ->
</func>
<func>
- <name name="size" arity="1"/>
+ <name name="size" arity="1" since=""/>
<fsummary>Return the number of elements in a dictionary.</fsummary>
<desc>
<p>Returns the number of elements in dictionary
@@ -226,7 +226,7 @@ merge(Fun, D1, D2) ->
</func>
<func>
- <name name="store" arity="3"/>
+ <name name="store" arity="3" since=""/>
<fsummary>Store a value in a dictionary.</fsummary>
<desc>
<p>Stores a <c><anno>Key</anno></c>-<c><anno>Value</anno></c> pair in
@@ -237,7 +237,7 @@ merge(Fun, D1, D2) ->
</func>
<func>
- <name name="to_list" arity="1"/>
+ <name name="to_list" arity="1" since=""/>
<fsummary>Convert a dictionary to a list of pairs.</fsummary>
<desc>
<p>Converts dictionary <c>Dict</c> to a list representation.</p>
@@ -245,7 +245,7 @@ merge(Fun, D1, D2) ->
</func>
<func>
- <name name="update" arity="3"/>
+ <name name="update" arity="3" since=""/>
<fsummary>Update a value in a dictionary.</fsummary>
<desc>
<p>Updates a value in a dictionary by calling <c><anno>Fun</anno></c> on
@@ -255,7 +255,7 @@ merge(Fun, D1, D2) ->
</func>
<func>
- <name name="update" arity="4"/>
+ <name name="update" arity="4" since=""/>
<fsummary>Update a value in a dictionary.</fsummary>
<desc>
<p>Updates a value in a dictionary by calling <c><anno>Fun</anno></c> on
@@ -269,7 +269,7 @@ append(Key, Val, D) ->
</func>
<func>
- <name name="update_counter" arity="3"/>
+ <name name="update_counter" arity="3" since=""/>
<fsummary>Increment a value in a dictionary.</fsummary>
<desc>
<p>Adds <c><anno>Increment</anno></c> to the value associated with
diff --git a/lib/stdlib/doc/src/digraph.xml b/lib/stdlib/doc/src/digraph.xml
index a5252b443b..cf2c0844c9 100644
--- a/lib/stdlib/doc/src/digraph.xml
+++ b/lib/stdlib/doc/src/digraph.xml
@@ -32,7 +32,7 @@
<rev>C</rev>
<file>digraph.xml</file>
</header>
- <module>digraph</module>
+ <module since="">digraph</module>
<modulesummary>Directed graphs.</modulesummary>
<description>
<p>This module provides a version of labeled
@@ -144,9 +144,9 @@
<funcs>
<func>
- <name name="add_edge" arity="3"/>
- <name name="add_edge" arity="4"/>
- <name name="add_edge" arity="5"/>
+ <name name="add_edge" arity="3" since=""/>
+ <name name="add_edge" arity="4" since=""/>
+ <name name="add_edge" arity="5" since=""/>
<fsummary>Add an edge to a digraph.</fsummary>
<type name="add_edge_err_rsn"/>
<desc>
@@ -183,9 +183,9 @@
</func>
<func>
- <name name="add_vertex" arity="1"/>
- <name name="add_vertex" arity="2"/>
- <name name="add_vertex" arity="3"/>
+ <name name="add_vertex" arity="1" since=""/>
+ <name name="add_vertex" arity="2" since=""/>
+ <name name="add_vertex" arity="3" since=""/>
<fsummary>Add or modify a vertex of a digraph.</fsummary>
<desc>
<p><c>add_vertex/3</c> creates (or modifies) vertex
@@ -204,7 +204,7 @@
</func>
<func>
- <name name="del_edge" arity="2"/>
+ <name name="del_edge" arity="2" since=""/>
<fsummary>Delete an edge from a digraph.</fsummary>
<desc>
<p>Deletes edge <c><anno>E</anno></c> from digraph
@@ -213,7 +213,7 @@
</func>
<func>
- <name name="del_edges" arity="2"/>
+ <name name="del_edges" arity="2" since=""/>
<fsummary>Delete edges from a digraph.</fsummary>
<desc>
<p>Deletes the edges in list <c><anno>Edges</anno></c> from digraph
@@ -222,7 +222,7 @@
</func>
<func>
- <name name="del_path" arity="3"/>
+ <name name="del_path" arity="3" since=""/>
<fsummary>Delete paths from a digraph.</fsummary>
<desc>
<p>Deletes edges from digraph <c><anno>G</anno></c> until there are no
@@ -252,7 +252,7 @@
</func>
<func>
- <name name="del_vertex" arity="2"/>
+ <name name="del_vertex" arity="2" since=""/>
<fsummary>Delete a vertex from a digraph.</fsummary>
<desc>
<p>Deletes vertex <c><anno>V</anno></c> from digraph
@@ -265,7 +265,7 @@
</func>
<func>
- <name name="del_vertices" arity="2"/>
+ <name name="del_vertices" arity="2" since=""/>
<fsummary>Delete vertices from a digraph.</fsummary>
<desc>
<p>Deletes the vertices in list <c><anno>Vertices</anno></c> from
@@ -274,7 +274,7 @@
</func>
<func>
- <name name="delete" arity="1"/>
+ <name name="delete" arity="1" since=""/>
<fsummary>Delete a digraph.</fsummary>
<desc>
<p>Deletes digraph <c><anno>G</anno></c>. This call is important
@@ -285,7 +285,7 @@
</func>
<func>
- <name name="edge" arity="2"/>
+ <name name="edge" arity="2" since=""/>
<fsummary>Return the vertices and the label of an edge of a digraph.
</fsummary>
<desc>
@@ -303,7 +303,7 @@
</func>
<func>
- <name name="edges" arity="1"/>
+ <name name="edges" arity="1" since=""/>
<fsummary>Return all edges of a digraph.</fsummary>
<desc>
<p>Returns a list of all edges of digraph <c><anno>G</anno></c>, in
@@ -312,7 +312,7 @@
</func>
<func>
- <name name="edges" arity="2"/>
+ <name name="edges" arity="2" since=""/>
<fsummary>Return the edges emanating from or incident on a vertex of
a digraph.</fsummary>
<desc>
@@ -324,7 +324,7 @@
</func>
<func>
- <name name="get_cycle" arity="2"/>
+ <name name="get_cycle" arity="2" since=""/>
<fsummary>Find one cycle in a digraph.</fsummary>
<desc>
<p>If a <seealso marker="#simple_cycle">simple cycle</seealso> of
@@ -341,7 +341,7 @@
</func>
<func>
- <name name="get_path" arity="3"/>
+ <name name="get_path" arity="3" since=""/>
<fsummary>Find one path in a digraph.</fsummary>
<desc>
<p>Tries to find
@@ -357,7 +357,7 @@
</func>
<func>
- <name name="get_short_cycle" arity="2"/>
+ <name name="get_short_cycle" arity="2" since=""/>
<fsummary>Find one short cycle in a digraph.</fsummary>
<desc>
<p>Tries to find an as short as possible
@@ -375,7 +375,7 @@
</func>
<func>
- <name name="get_short_path" arity="3"/>
+ <name name="get_short_path" arity="3" since=""/>
<fsummary>Find one short path in a digraph.</fsummary>
<desc>
<p>Tries to find an as short as possible
@@ -392,7 +392,7 @@
</func>
<func>
- <name name="in_degree" arity="2"/>
+ <name name="in_degree" arity="2" since=""/>
<fsummary>Return the in-degree of a vertex of a digraph.</fsummary>
<desc>
<p>Returns the <seealso marker="#in_degree">in-degree</seealso> of
@@ -401,7 +401,7 @@
</func>
<func>
- <name name="in_edges" arity="2"/>
+ <name name="in_edges" arity="2" since=""/>
<fsummary>Return all edges incident on a vertex of a digraph.</fsummary>
<desc>
<p>Returns a list of all
@@ -412,7 +412,7 @@
</func>
<func>
- <name name="in_neighbours" arity="2"/>
+ <name name="in_neighbours" arity="2" since=""/>
<fsummary>Return all in-neighbors of a vertex of a digraph.</fsummary>
<desc>
<p>Returns a list of
@@ -423,7 +423,7 @@
</func>
<func>
- <name name="info" arity="1"/>
+ <name name="info" arity="1" since=""/>
<fsummary>Return information about a digraph.</fsummary>
<type name="d_cyclicity"/>
<type name="d_protection"/>
@@ -453,7 +453,7 @@
</func>
<func>
- <name name="new" arity="0"/>
+ <name name="new" arity="0" since=""/>
<fsummary>Return a protected empty digraph, where cycles are allowed.
</fsummary>
<desc>
@@ -462,7 +462,7 @@
</func>
<func>
- <name name="new" arity="1"/>
+ <name name="new" arity="1" since=""/>
<fsummary>Create a new empty digraph.</fsummary>
<type variable="Type"/>
<type name="d_type"/>
@@ -492,7 +492,7 @@
</func>
<func>
- <name name="no_edges" arity="1"/>
+ <name name="no_edges" arity="1" since=""/>
<fsummary>Return the number of edges of a digraph.</fsummary>
<desc>
<p>Returns the number of edges of digraph <c><anno>G</anno></c>.</p>
@@ -500,7 +500,7 @@
</func>
<func>
- <name name="no_vertices" arity="1"/>
+ <name name="no_vertices" arity="1" since=""/>
<fsummary>Return the number of vertices of a digraph.</fsummary>
<desc>
<p>Returns the number of vertices of digraph <c><anno>G</anno></c>.</p>
@@ -508,7 +508,7 @@
</func>
<func>
- <name name="out_degree" arity="2"/>
+ <name name="out_degree" arity="2" since=""/>
<fsummary>Return the out-degree of a vertex of a digraph.</fsummary>
<desc>
<p>Returns the <seealso marker="#out_degree">out-degree</seealso> of
@@ -517,7 +517,7 @@
</func>
<func>
- <name name="out_edges" arity="2"/>
+ <name name="out_edges" arity="2" since=""/>
<fsummary>Return all edges emanating from a vertex of a digraph.
</fsummary>
<desc>
@@ -529,7 +529,7 @@
</func>
<func>
- <name name="out_neighbours" arity="2"/>
+ <name name="out_neighbours" arity="2" since=""/>
<fsummary>Return all out-neighbors of a vertex of a digraph.</fsummary>
<desc>
<p>Returns a list of
@@ -540,7 +540,7 @@
</func>
<func>
- <name name="vertex" arity="2"/>
+ <name name="vertex" arity="2" since=""/>
<fsummary>Return the label of a vertex of a digraph.</fsummary>
<desc>
<p>Returns <c>{<anno>V</anno>,&nbsp;<anno>Label</anno>}</c>,
@@ -553,7 +553,7 @@
</func>
<func>
- <name name="vertices" arity="1"/>
+ <name name="vertices" arity="1" since=""/>
<fsummary>Return all vertices of a digraph.</fsummary>
<desc>
<p>Returns a list of all vertices of digraph <c><anno>G</anno></c>, in
diff --git a/lib/stdlib/doc/src/digraph_utils.xml b/lib/stdlib/doc/src/digraph_utils.xml
index cb316e5b93..13b0aaad9e 100644
--- a/lib/stdlib/doc/src/digraph_utils.xml
+++ b/lib/stdlib/doc/src/digraph_utils.xml
@@ -32,7 +32,7 @@
<rev>PA1</rev>
<file>digraph_utils.xml</file>
</header>
- <module>digraph_utils</module>
+ <module since="">digraph_utils</module>
<modulesummary>Algorithms for directed graphs.</modulesummary>
<description>
<p>This module provides algorithms based on depth-first traversal of
@@ -154,7 +154,7 @@
<funcs>
<func>
- <name name="arborescence_root" arity="1"/>
+ <name name="arborescence_root" arity="1" since=""/>
<fsummary>Check if a digraph is an arborescence.</fsummary>
<desc>
<p>Returns <c>{yes, <anno>Root</anno>}</c> if <c><anno>Root</anno></c>
@@ -164,7 +164,7 @@
</func>
<func>
- <name name="components" arity="1"/>
+ <name name="components" arity="1" since=""/>
<fsummary>Return the components of a digraph.</fsummary>
<desc>
<p>Returns a list
@@ -177,7 +177,7 @@
</func>
<func>
- <name name="condensation" arity="1"/>
+ <name name="condensation" arity="1" since=""/>
<fsummary>Return a condensed graph of a digraph.</fsummary>
<desc>
<p>Creates a digraph where the vertices are
@@ -202,7 +202,7 @@
</func>
<func>
- <name name="cyclic_strong_components" arity="1"/>
+ <name name="cyclic_strong_components" arity="1" since=""/>
<fsummary>Return the cyclic strong components of a digraph.</fsummary>
<desc>
<p>Returns a list of <seealso marker="#strong_components">strongly
@@ -218,7 +218,7 @@
</func>
<func>
- <name name="is_acyclic" arity="1"/>
+ <name name="is_acyclic" arity="1" since=""/>
<fsummary>Check if a digraph is acyclic.</fsummary>
<desc>
<p>Returns <c>true</c> if and only if digraph
@@ -228,7 +228,7 @@
</func>
<func>
- <name name="is_arborescence" arity="1"/>
+ <name name="is_arborescence" arity="1" since=""/>
<fsummary>Check if a digraph is an arborescence.</fsummary>
<desc>
<p>Returns <c>true</c> if and only if digraph
@@ -238,7 +238,7 @@
</func>
<func>
- <name name="is_tree" arity="1"/>
+ <name name="is_tree" arity="1" since=""/>
<fsummary>Check if a digraph is a tree.</fsummary>
<desc>
<p>Returns <c>true</c> if and only if digraph
@@ -248,7 +248,7 @@
</func>
<func>
- <name name="loop_vertices" arity="1"/>
+ <name name="loop_vertices" arity="1" since=""/>
<fsummary>Return the vertices of a digraph included in some loop.
</fsummary>
<desc>
@@ -258,7 +258,7 @@
</func>
<func>
- <name name="postorder" arity="1"/>
+ <name name="postorder" arity="1" since=""/>
<fsummary>Return the vertices of a digraph in postorder.</fsummary>
<desc>
<p>Returns all vertices of digraph <c><anno>Digraph</anno></c>.
@@ -273,7 +273,7 @@
</func>
<func>
- <name name="preorder" arity="1"/>
+ <name name="preorder" arity="1" since=""/>
<fsummary>Return the vertices of a digraph in preorder.</fsummary>
<desc>
<p>Returns all vertices of digraph <c><anno>Digraph</anno></c>.
@@ -285,7 +285,7 @@
</func>
<func>
- <name name="reachable" arity="2"/>
+ <name name="reachable" arity="2" since=""/>
<fsummary>Return the vertices reachable from some vertices of a digraph.
</fsummary>
<desc>
@@ -300,7 +300,7 @@
</func>
<func>
- <name name="reachable_neighbours" arity="2"/>
+ <name name="reachable_neighbours" arity="2" since=""/>
<fsummary>Return the neighbors reachable from some vertices of a
digraph.</fsummary>
<desc>
@@ -316,7 +316,7 @@
</func>
<func>
- <name name="reaching" arity="2"/>
+ <name name="reaching" arity="2" since=""/>
<fsummary>Return the vertices that reach some vertices of a digraph.
</fsummary>
<desc>
@@ -330,7 +330,7 @@
</func>
<func>
- <name name="reaching_neighbours" arity="2"/>
+ <name name="reaching_neighbours" arity="2" since=""/>
<fsummary>Return the neighbors that reach some vertices of a digraph.
</fsummary>
<desc>
@@ -345,7 +345,7 @@
</func>
<func>
- <name name="strong_components" arity="1"/>
+ <name name="strong_components" arity="1" since=""/>
<fsummary>Return the strong components of a digraph.</fsummary>
<desc>
<p>Returns a list of <seealso marker="#strong_components">strongly
@@ -359,8 +359,8 @@
</func>
<func>
- <name name="subgraph" arity="2"/>
- <name name="subgraph" arity="3"/>
+ <name name="subgraph" arity="2" since=""/>
+ <name name="subgraph" arity="3" since=""/>
<fsummary>Return a subgraph of a digraph.</fsummary>
<desc>
<p>Creates a maximal <seealso marker="#subgraph">subgraph</seealso>
@@ -387,7 +387,7 @@
</func>
<func>
- <name name="topsort" arity="1"/>
+ <name name="topsort" arity="1" since=""/>
<fsummary>Return a topological sorting of the vertices of a digraph.
</fsummary>
<desc>
diff --git a/lib/stdlib/doc/src/epp.xml b/lib/stdlib/doc/src/epp.xml
index d803d259aa..110c1cea2c 100644
--- a/lib/stdlib/doc/src/epp.xml
+++ b/lib/stdlib/doc/src/epp.xml
@@ -32,7 +32,7 @@
<rev>B</rev>
<file>epp.xml</file>
</header>
- <module>epp</module>
+ <module since="">epp</module>
<modulesummary>An Erlang code preprocessor.</modulesummary>
<description>
<p>The Erlang code preprocessor includes functions that are used by the
@@ -76,7 +76,7 @@
<funcs>
<func>
- <name name="close" arity="1"/>
+ <name name="close" arity="1" since=""/>
<fsummary>Close the preprocessing of the file associated with <c>Epp</c>.
</fsummary>
<desc>
@@ -85,7 +85,7 @@
</func>
<func>
- <name name="default_encoding" arity="0"/>
+ <name name="default_encoding" arity="0" since="OTP R16B"/>
<fsummary>Return the default encoding of Erlang source files.</fsummary>
<desc>
<p>Returns the default encoding of Erlang source files.</p>
@@ -93,7 +93,7 @@
</func>
<func>
- <name name="encoding_to_string" arity="1"/>
+ <name name="encoding_to_string" arity="1" since="OTP R16B"/>
<fsummary>Return a string representation of an encoding.</fsummary>
<desc>
<p>Returns a string representation of an encoding. The string
@@ -107,7 +107,7 @@
</func>
<func>
- <name name="format_error" arity="1"/>
+ <name name="format_error" arity="1" since="OTP R14B03"/>
<fsummary>Format an error descriptor.</fsummary>
<desc>
<p>Takes an <c><anno>ErrorDescriptor</anno></c> and returns
@@ -120,7 +120,7 @@
</func>
<func>
- <name name="open" arity="1"/>
+ <name name="open" arity="1" since="OTP 17.0"/>
<fsummary>Open a file for preprocessing.</fsummary>
<desc>
<p>Opens a file for preprocessing.</p>
@@ -136,7 +136,7 @@
</func>
<func>
- <name name="open" arity="2"/>
+ <name name="open" arity="2" since=""/>
<fsummary>Open a file for preprocessing.</fsummary>
<desc>
<p>Equivalent to
@@ -145,7 +145,7 @@
</func>
<func>
- <name name="open" arity="3"/>
+ <name name="open" arity="3" since=""/>
<fsummary>Open a file for preprocessing.</fsummary>
<desc>
<p>Equivalent to <c>epp:open([{name, FileName}, {includes, IncludePath},
@@ -154,7 +154,7 @@
</func>
<func>
- <name name="parse_erl_form" arity="1"/>
+ <name name="parse_erl_form" arity="1" since=""/>
<fsummary>Return the next Erlang form from the opened Erlang source file.
</fsummary>
<type name="warning_info"/>
@@ -167,7 +167,7 @@
</func>
<func>
- <name name="parse_file" arity="2"/>
+ <name name="parse_file" arity="2" since="OTP 17.0"/>
<fsummary>Preprocess and parse an Erlang source file.</fsummary>
<desc>
<p>Preprocesses and parses an Erlang source file.
@@ -185,7 +185,7 @@
</func>
<func>
- <name name="parse_file" arity="3"/>
+ <name name="parse_file" arity="3" since=""/>
<fsummary>Preprocess and parse an Erlang source file.</fsummary>
<desc>
<p>Equivalent to <c>epp:parse_file(FileName, [{includes, IncludePath},
@@ -194,8 +194,8 @@
</func>
<func>
- <name name="read_encoding" arity="1"/>
- <name name="read_encoding" arity="2"/>
+ <name name="read_encoding" arity="1" since="OTP R16B"/>
+ <name name="read_encoding" arity="2" since="OTP R16B"/>
<fsummary>Read the encoding from a file.</fsummary>
<desc>
<p>Read the <seealso marker="#encoding">encoding</seealso> from
@@ -209,8 +209,8 @@
</func>
<func>
- <name name="read_encoding_from_binary" arity="1"/>
- <name name="read_encoding_from_binary" arity="2"/>
+ <name name="read_encoding_from_binary" arity="1" since="OTP R16B"/>
+ <name name="read_encoding_from_binary" arity="2" since="OTP R16B"/>
<fsummary>Read the encoding from a binary.</fsummary>
<desc>
<p>Read the <seealso marker="#encoding">encoding</seealso> from
@@ -224,7 +224,7 @@
</func>
<func>
- <name name="set_encoding" arity="1"/>
+ <name name="set_encoding" arity="1" since="OTP R16B"/>
<fsummary>Read and set the encoding of an I/O device.</fsummary>
<desc>
<p>Reads the <seealso marker="#encoding">encoding</seealso> from
@@ -239,7 +239,7 @@
</func>
<func>
- <name name="set_encoding" arity="2"/>
+ <name name="set_encoding" arity="2" since="OTP 17.0"/>
<fsummary>Read and set the encoding of an I/O device.</fsummary>
<desc>
<p>Reads the <seealso marker="#encoding">encoding</seealso> from
diff --git a/lib/stdlib/doc/src/erl_anno.xml b/lib/stdlib/doc/src/erl_anno.xml
index f316f63d98..dff93619ab 100644
--- a/lib/stdlib/doc/src/erl_anno.xml
+++ b/lib/stdlib/doc/src/erl_anno.xml
@@ -34,7 +34,7 @@
<rev>A</rev>
<file>erl_anno.xml</file>
</header>
- <module>erl_anno</module>
+ <module since="OTP 18.0">erl_anno</module>
<modulesummary>Abstract datatype for the annotations of the Erlang Compiler.
</modulesummary>
@@ -135,7 +135,7 @@
<funcs>
<func>
- <name name="column" arity="1"/>
+ <name name="column" arity="1" since="OTP 18.0"/>
<fsummary>Return the column.</fsummary>
<type name="column"></type>
<desc>
@@ -144,7 +144,7 @@
</func>
<func>
- <name name="end_location" arity="1"/>
+ <name name="end_location" arity="1" since="OTP 18.0"/>
<fsummary>Return the end location of the text.</fsummary>
<type name="location"></type>
<desc>
@@ -155,7 +155,7 @@
</func>
<func>
- <name name="file" arity="1"/>
+ <name name="file" arity="1" since="OTP 18.0"/>
<fsummary>Return the filename.</fsummary>
<type name="filename"></type>
<desc>
@@ -165,7 +165,7 @@
</func>
<func>
- <name name="from_term" arity="1"/>
+ <name name="from_term" arity="1" since="OTP 18.0"/>
<fsummary>Return annotations given a term.</fsummary>
<desc>
<p>Returns annotations with representation <anno>Term</anno>.</p>
@@ -174,7 +174,7 @@
</func>
<func>
- <name name="generated" arity="1"/>
+ <name name="generated" arity="1" since="OTP 18.0"/>
<fsummary>Return the generated Boolean.</fsummary>
<type name="generated"></type>
<desc>
@@ -185,7 +185,7 @@
</func>
<func>
- <name name="is_anno" arity="1"/>
+ <name name="is_anno" arity="1" since="OTP 18.0"/>
<fsummary>Test for a collection of annotations.</fsummary>
<desc>
<p>Returns <c>true</c> if <anno>Term</anno> is a collection of
@@ -194,7 +194,7 @@
</func>
<func>
- <name name="line" arity="1"/>
+ <name name="line" arity="1" since="OTP 18.0"/>
<fsummary>Return the line.</fsummary>
<type name="line"></type>
<desc>
@@ -203,7 +203,7 @@
</func>
<func>
- <name name="location" arity="1"/>
+ <name name="location" arity="1" since="OTP 18.0"/>
<fsummary>Return the location.</fsummary>
<type name="location"></type>
<desc>
@@ -212,7 +212,7 @@
</func>
<func>
- <name name="new" arity="1"/>
+ <name name="new" arity="1" since="OTP 18.0"/>
<fsummary>Create a new collection of annotations.</fsummary>
<type name="location"></type>
<desc>
@@ -221,7 +221,7 @@
</func>
<func>
- <name name="set_file" arity="2"/>
+ <name name="set_file" arity="2" since="OTP 18.0"/>
<fsummary>Modify the filename.</fsummary>
<type name="filename"></type>
<desc>
@@ -230,7 +230,7 @@
</func>
<func>
- <name name="set_generated" arity="2"/>
+ <name name="set_generated" arity="2" since="OTP 18.0"/>
<fsummary>Modify the generated marker.</fsummary>
<type name="generated"></type>
<desc>
@@ -240,7 +240,7 @@
</func>
<func>
- <name name="set_line" arity="2"/>
+ <name name="set_line" arity="2" since="OTP 18.0"/>
<fsummary>Modify the line.</fsummary>
<type name="line"></type>
<desc>
@@ -249,7 +249,7 @@
</func>
<func>
- <name name="set_location" arity="2"/>
+ <name name="set_location" arity="2" since="OTP 18.0"/>
<fsummary>Modify the location.</fsummary>
<type name="location"></type>
<desc>
@@ -258,7 +258,7 @@
</func>
<func>
- <name name="set_record" arity="2"/>
+ <name name="set_record" arity="2" since="OTP 18.0"/>
<fsummary>Modify the record marker.</fsummary>
<type name="record"></type>
<desc>
@@ -267,7 +267,7 @@
</func>
<func>
- <name name="set_text" arity="2"/>
+ <name name="set_text" arity="2" since="OTP 18.0"/>
<fsummary>Modify the text.</fsummary>
<type name="text"></type>
<desc>
@@ -276,7 +276,7 @@
</func>
<func>
- <name name="text" arity="1"/>
+ <name name="text" arity="1" since="OTP 18.0"/>
<fsummary>Return the text.</fsummary>
<type name="text"></type>
<desc>
@@ -286,7 +286,7 @@
</func>
<func>
- <name name="to_term" arity="1"/>
+ <name name="to_term" arity="1" since="OTP 18.0"/>
<fsummary>Return the term representing a collection of annotations.
</fsummary>
<desc>
diff --git a/lib/stdlib/doc/src/erl_eval.xml b/lib/stdlib/doc/src/erl_eval.xml
index 1c0f7f062f..813cbecd89 100644
--- a/lib/stdlib/doc/src/erl_eval.xml
+++ b/lib/stdlib/doc/src/erl_eval.xml
@@ -32,7 +32,7 @@
<rev>B</rev>
<file>erl_eval.xml</file>
</header>
- <module>erl_eval</module>
+ <module since="">erl_eval</module>
<modulesummary>The Erlang meta interpreter.</modulesummary>
<description>
<p>This module provides an interpreter for Erlang expressions. The
@@ -96,7 +96,7 @@
<funcs>
<func>
- <name name="add_binding" arity="3"/>
+ <name name="add_binding" arity="3" since=""/>
<fsummary>Add a binding.</fsummary>
<desc>
<p>Adds binding <c><anno>Name</anno>=<anno>Value</anno></c>
@@ -106,7 +106,7 @@
</func>
<func>
- <name name="binding" arity="2"/>
+ <name name="binding" arity="2" since=""/>
<fsummary>Return bindings.</fsummary>
<desc>
<p>Returns the binding of <c><anno>Name</anno></c>
@@ -115,7 +115,7 @@
</func>
<func>
- <name name="bindings" arity="1"/>
+ <name name="bindings" arity="1" since=""/>
<fsummary>Return bindings.</fsummary>
<desc>
<p>Returns the list of bindings contained in the binding
@@ -124,7 +124,7 @@
</func>
<func>
- <name name="del_binding" arity="2"/>
+ <name name="del_binding" arity="2" since=""/>
<fsummary>Delete a binding.</fsummary>
<desc>
<p>Removes the binding of <c><anno>Name</anno></c>
@@ -134,10 +134,10 @@
</func>
<func>
- <name name="expr" arity="2"/>
- <name name="expr" arity="3"/>
- <name name="expr" arity="4"/>
- <name name="expr" arity="5"/>
+ <name name="expr" arity="2" since=""/>
+ <name name="expr" arity="3" since=""/>
+ <name name="expr" arity="4" since=""/>
+ <name name="expr" arity="5" since=""/>
<fsummary>Evaluate expression.</fsummary>
<desc>
<p>Evaluates <c><anno>Expression</anno></c> with the set of bindings
@@ -157,9 +157,9 @@
</func>
<func>
- <name name="expr_list" arity="2"/>
- <name name="expr_list" arity="3"/>
- <name name="expr_list" arity="4"/>
+ <name name="expr_list" arity="2" since=""/>
+ <name name="expr_list" arity="3" since=""/>
+ <name name="expr_list" arity="4" since=""/>
<fsummary>Evaluate a list of expressions.</fsummary>
<desc>
<p>Evaluates a list of expressions in parallel, using the same
@@ -174,9 +174,9 @@
</func>
<func>
- <name name="exprs" arity="2"/>
- <name name="exprs" arity="3"/>
- <name name="exprs" arity="4"/>
+ <name name="exprs" arity="2" since=""/>
+ <name name="exprs" arity="3" since=""/>
+ <name name="exprs" arity="4" since=""/>
<fsummary>Evaluate expressions.</fsummary>
<desc>
<p>Evaluates <c><anno>Expressions</anno></c> with the set of bindings
@@ -197,7 +197,7 @@
</func>
<func>
- <name name="new_bindings" arity="0"/>
+ <name name="new_bindings" arity="0" since=""/>
<fsummary>Return a bindings structure.</fsummary>
<desc>
<p>Returns an empty binding structure.</p>
diff --git a/lib/stdlib/doc/src/erl_expand_records.xml b/lib/stdlib/doc/src/erl_expand_records.xml
index b6aa75ed03..20e5f1960b 100644
--- a/lib/stdlib/doc/src/erl_expand_records.xml
+++ b/lib/stdlib/doc/src/erl_expand_records.xml
@@ -34,7 +34,7 @@
<rev>PA1</rev>
<file>erl_expand_records.xml</file>
</header>
- <module>erl_expand_records</module>
+ <module since="">erl_expand_records</module>
<modulesummary>Expands records in a module.</modulesummary>
<description>
<p>This module expands records in a module.</p>
@@ -42,7 +42,7 @@
<funcs>
<func>
- <name name="module" arity="2"/>
+ <name name="module" arity="2" since=""/>
<fsummary>Expand all records in a module.</fsummary>
<desc>
<p>Expands all records in a module to use explicit tuple
diff --git a/lib/stdlib/doc/src/erl_id_trans.xml b/lib/stdlib/doc/src/erl_id_trans.xml
index 16952a9582..ec66842ac0 100644
--- a/lib/stdlib/doc/src/erl_id_trans.xml
+++ b/lib/stdlib/doc/src/erl_id_trans.xml
@@ -34,7 +34,7 @@
<rev>B</rev>
<file>erl_id_trans.xml</file>
</header>
- <module>erl_id_trans</module>
+ <module since="">erl_id_trans</module>
<modulesummary>An identity parse transform.</modulesummary>
<description>
<p>This module performs an identity parse transformation of Erlang code.
@@ -46,7 +46,7 @@
<funcs>
<func>
- <name>parse_transform(Forms, Options) -> Forms</name>
+ <name since="">parse_transform(Forms, Options) -> Forms</name>
<fsummary>Transform Erlang forms.</fsummary>
<type>
<v>Forms = [<seealso marker="erl_parse#type-abstract_form">erl_parse:abstract_form()</seealso>
diff --git a/lib/stdlib/doc/src/erl_internal.xml b/lib/stdlib/doc/src/erl_internal.xml
index 17cd0fb240..77551ffed7 100644
--- a/lib/stdlib/doc/src/erl_internal.xml
+++ b/lib/stdlib/doc/src/erl_internal.xml
@@ -34,7 +34,7 @@
<rev>B</rev>
<file>erl_internal.xml</file>
</header>
- <module>erl_internal</module>
+ <module since="">erl_internal</module>
<modulesummary>Internal Erlang definitions.</modulesummary>
<description>
<p>This module defines Erlang BIFs, guard tests, and operators.
@@ -44,7 +44,7 @@
<funcs>
<func>
- <name name="add_predefined_functions" arity="1"/>
+ <name name="add_predefined_functions" arity="1" since="OTP 20.0"/>
<fsummary>Add code for pre-defined functions.</fsummary>
<desc>
<p>Adds to <c><anno>Forms</anno></c> the code for the standard
@@ -54,7 +54,7 @@
</func>
<func>
- <name name="arith_op" arity="2"/>
+ <name name="arith_op" arity="2" since=""/>
<fsummary>Test for an arithmetic operator.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>OpName</anno>/<anno>Arity</anno></c>
@@ -63,7 +63,7 @@
</func>
<func>
- <name name="bif" arity="2"/>
+ <name name="bif" arity="2" since=""/>
<fsummary>Test for an Erlang BIF.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Name</anno>/<anno>Arity</anno></c>
@@ -73,7 +73,7 @@
</func>
<func>
- <name name="bool_op" arity="2"/>
+ <name name="bool_op" arity="2" since=""/>
<fsummary>Test for a Boolean operator.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>OpName</anno>/<anno>Arity</anno></c>
@@ -82,7 +82,7 @@
</func>
<func>
- <name name="comp_op" arity="2"/>
+ <name name="comp_op" arity="2" since=""/>
<fsummary>Test for a comparison operator.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>OpName</anno>/<anno>Arity</anno></c>
@@ -91,7 +91,7 @@
</func>
<func>
- <name name="guard_bif" arity="2"/>
+ <name name="guard_bif" arity="2" since=""/>
<fsummary>Test for an Erlang BIF allowed in guards.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Name</anno>/<anno>Arity</anno></c> is
@@ -100,7 +100,7 @@
</func>
<func>
- <name name="list_op" arity="2"/>
+ <name name="list_op" arity="2" since=""/>
<fsummary>Test for a list operator.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>OpName</anno>/<anno>Arity</anno></c>
@@ -109,7 +109,7 @@
</func>
<func>
- <name name="op_type" arity="2"/>
+ <name name="op_type" arity="2" since=""/>
<fsummary>Return operator type.</fsummary>
<desc>
<p>Returns the <c><anno>Type</anno></c> of operator that
@@ -120,7 +120,7 @@
</func>
<func>
- <name name="send_op" arity="2"/>
+ <name name="send_op" arity="2" since=""/>
<fsummary>Test for a send operator.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>OpName</anno>/<anno>Arity</anno></c>
@@ -129,7 +129,7 @@
</func>
<func>
- <name name="type_test" arity="2"/>
+ <name name="type_test" arity="2" since=""/>
<fsummary>Test for a valid type test.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Name</anno>/<anno>Arity</anno></c> is
diff --git a/lib/stdlib/doc/src/erl_lint.xml b/lib/stdlib/doc/src/erl_lint.xml
index 77cb7a9916..12eaafc3a8 100644
--- a/lib/stdlib/doc/src/erl_lint.xml
+++ b/lib/stdlib/doc/src/erl_lint.xml
@@ -32,7 +32,7 @@
<rev>B</rev>
<file>erl_lint.xml</file>
</header>
- <module>erl_lint</module>
+ <module since="">erl_lint</module>
<modulesummary>The Erlang code linter.</modulesummary>
<description>
<p>This module is used to check Erlang code for illegal syntax and
@@ -78,7 +78,7 @@
<funcs>
<func>
- <name name="format_error" arity="1"/>
+ <name name="format_error" arity="1" since=""/>
<fsummary>Format an error descriptor.</fsummary>
<desc>
<p>Takes an <c><anno>ErrorDescriptor</anno></c> and returns a string
@@ -90,7 +90,7 @@
</func>
<func>
- <name name="is_guard_test" arity="1"/>
+ <name name="is_guard_test" arity="1" since=""/>
<fsummary>Test for a guard test.</fsummary>
<desc>
<p>Tests if <c><anno>Expr</anno></c> is a legal guard test.
@@ -102,9 +102,9 @@
</func>
<func>
- <name name="module" arity="1"/>
- <name name="module" arity="2"/>
- <name name="module" arity="3"/>
+ <name name="module" arity="1" since=""/>
+ <name name="module" arity="2" since=""/>
+ <name name="module" arity="3" since=""/>
<fsummary>Check a module for errors.</fsummary>
<desc>
<p>Checks all the forms in a module for errors. It returns:</p>
diff --git a/lib/stdlib/doc/src/erl_parse.xml b/lib/stdlib/doc/src/erl_parse.xml
index 647f36883c..8142e5c0aa 100644
--- a/lib/stdlib/doc/src/erl_parse.xml
+++ b/lib/stdlib/doc/src/erl_parse.xml
@@ -32,7 +32,7 @@
<rev>B</rev>
<file>erl_parse.xml</file>
</header>
- <module>erl_parse</module>
+ <module since="">erl_parse</module>
<modulesummary>The Erlang parser.</modulesummary>
<description>
<p>This module is the basic Erlang parser that converts tokens into
@@ -89,7 +89,7 @@
<funcs>
<func>
- <name name="abstract" arity="1"/>
+ <name name="abstract" arity="1" since=""/>
<fsummary>Convert an Erlang term into an abstract form.</fsummary>
<desc>
<p>Converts the Erlang data structure <c><anno>Data</anno></c> into an
@@ -102,7 +102,7 @@
</func>
<func>
- <name name="abstract" arity="2"/>
+ <name name="abstract" arity="2" since="OTP R16B01"/>
<fsummary>Convert an Erlang term into an abstract form.</fsummary>
<type name="encoding_func"/>
<desc>
@@ -124,7 +124,7 @@
</func>
<func>
- <name name="anno_from_term" arity="1"/>
+ <name name="anno_from_term" arity="1" since="OTP 18.0"/>
<fsummary>Return annotations as terms.</fsummary>
<desc>
<p>Assumes that <c><anno>Term</anno></c> is a term with the same
@@ -140,7 +140,7 @@
</func>
<func>
- <name name="anno_to_term" arity="1"/>
+ <name name="anno_to_term" arity="1" since="OTP 18.0"/>
<fsummary>Return the representation of annotations.</fsummary>
<desc>
<p>Returns a term where each collection of annotations
@@ -154,7 +154,7 @@
</func>
<func>
- <name name="fold_anno" arity="3"/>
+ <name name="fold_anno" arity="3" since="OTP 18.0"/>
<fsummary>Fold a function over the annotations of an <c>erl_parse</c> tree.
</fsummary>
<desc>
@@ -171,7 +171,7 @@
</func>
<func>
- <name>format_error(ErrorDescriptor) -> Chars</name>
+ <name since="">format_error(ErrorDescriptor) -> Chars</name>
<fsummary>Format an error descriptor.</fsummary>
<type>
<v>ErrorDescriptor = <seealso
@@ -188,7 +188,7 @@
</func>
<func>
- <name name="map_anno" arity="2"/>
+ <name name="map_anno" arity="2" since="OTP 18.0"/>
<fsummary>Map a function over the annotations of an <c>erl_parse</c> tree.
</fsummary>
<desc>
@@ -201,7 +201,7 @@
</func>
<func>
- <name name="mapfold_anno" arity="3"/>
+ <name name="mapfold_anno" arity="3" since="OTP 18.0"/>
<fsummary>Map and fold a function over the annotations of an
<c>erl_parse</c> tree.</fsummary>
<desc>
@@ -220,7 +220,7 @@
</func>
<func>
- <name name="new_anno" arity="1"/>
+ <name name="new_anno" arity="1" since="OTP 18.0"/>
<fsummary>Create new annotations.</fsummary>
<desc>
<p>Assumes that <c><anno>Term</anno></c> is a term with the same
@@ -236,7 +236,7 @@
</func>
<func>
- <name name="normalise" arity="1"/>
+ <name name="normalise" arity="1" since=""/>
<fsummary>Convert abstract form to an Erlang term.</fsummary>
<desc>
<p>Converts the abstract form <c><anno>AbsTerm</anno></c> of a
@@ -247,7 +247,7 @@
</func>
<func>
- <name name="parse_exprs" arity="1"/>
+ <name name="parse_exprs" arity="1" since=""/>
<fsummary>Parse Erlang expressions.</fsummary>
<desc>
<p>Parses <c><anno>Tokens</anno></c> as if it was a list of expressions.
@@ -267,7 +267,7 @@
</func>
<func>
- <name name="parse_form" arity="1"/>
+ <name name="parse_form" arity="1" since=""/>
<fsummary>Parse an Erlang form.</fsummary>
<desc>
<p>Parses <c><anno>Tokens</anno></c> as if it was a form. Returns one
@@ -287,7 +287,7 @@
</func>
<func>
- <name name="parse_term" arity="1"/>
+ <name name="parse_term" arity="1" since=""/>
<fsummary>Parse an Erlang term.</fsummary>
<desc>
<p>Parses <c><anno>Tokens</anno></c> as if it was a term. Returns
@@ -307,8 +307,8 @@
</func>
<func>
- <name name="tokens" arity="1"/>
- <name name="tokens" arity="2"/>
+ <name name="tokens" arity="1" since=""/>
+ <name name="tokens" arity="2" since=""/>
<fsummary>Generate a list of tokens for an expression.</fsummary>
<desc>
<p>Generates a list of tokens representing the abstract
diff --git a/lib/stdlib/doc/src/erl_pp.xml b/lib/stdlib/doc/src/erl_pp.xml
index 77a7f1e8d1..f1c3aa5a41 100644
--- a/lib/stdlib/doc/src/erl_pp.xml
+++ b/lib/stdlib/doc/src/erl_pp.xml
@@ -34,7 +34,7 @@
<rev>B</rev>
<file>erl_pp.xml</file>
</header>
- <module>erl_pp</module>
+ <module since="">erl_pp</module>
<modulesummary>The Erlang pretty printer.</modulesummary>
<description>
<p>The functions in this module are used to generate
@@ -73,8 +73,8 @@
<funcs>
<func>
- <name name="attribute" arity="1"/>
- <name name="attribute" arity="2"/>
+ <name name="attribute" arity="1" since=""/>
+ <name name="attribute" arity="2" since=""/>
<fsummary>Pretty print an attribute.</fsummary>
<desc>
<p>Same as <seealso marker="#form/1"><c>form/1,2</c></seealso>,
@@ -83,10 +83,10 @@
</func>
<func>
- <name name="expr" arity="1"/>
- <name name="expr" arity="2"/>
- <name name="expr" arity="3"/>
- <name name="expr" arity="4"/>
+ <name name="expr" arity="1" since=""/>
+ <name name="expr" arity="2" since=""/>
+ <name name="expr" arity="3" since=""/>
+ <name name="expr" arity="4" since=""/>
<fsummary>Pretty print one <c>Expression</c>.</fsummary>
<desc>
<p>Prints one expression. It is useful for implementing hooks (see
@@ -96,9 +96,9 @@
</func>
<func>
- <name name="exprs" arity="1"/>
- <name name="exprs" arity="2"/>
- <name name="exprs" arity="3"/>
+ <name name="exprs" arity="1" since=""/>
+ <name name="exprs" arity="2" since=""/>
+ <name name="exprs" arity="3" since=""/>
<fsummary>Pretty print <c>Expressions</c>.</fsummary>
<desc>
<p>Same as <seealso marker="#form/1"><c>form/1,2</c></seealso>,
@@ -108,8 +108,8 @@
</func>
<func>
- <name name="form" arity="1"/>
- <name name="form" arity="2"/>
+ <name name="form" arity="1" since=""/>
+ <name name="form" arity="2" since=""/>
<fsummary>Pretty print a form.</fsummary>
<desc>
<p>Pretty prints a
@@ -120,8 +120,8 @@
</func>
<func>
- <name name="function" arity="1"/>
- <name name="function" arity="2"/>
+ <name name="function" arity="1" since=""/>
+ <name name="function" arity="2" since=""/>
<fsummary>Pretty print a function.</fsummary>
<desc>
<p>Same as <seealso marker="#form/1"><c>form/1,2</c></seealso>,
@@ -130,8 +130,8 @@
</func>
<func>
- <name name="guard" arity="1"/>
- <name name="guard" arity="2"/>
+ <name name="guard" arity="1" since=""/>
+ <name name="guard" arity="2" since=""/>
<fsummary>Pretty print a guard.</fsummary>
<desc>
<p>Same as <seealso marker="#form/1"><c>form/1,2</c></seealso>,
diff --git a/lib/stdlib/doc/src/erl_scan.xml b/lib/stdlib/doc/src/erl_scan.xml
index 137ccd3416..38111f73bc 100644
--- a/lib/stdlib/doc/src/erl_scan.xml
+++ b/lib/stdlib/doc/src/erl_scan.xml
@@ -32,7 +32,7 @@
<rev>B</rev>
<file>erl_scan.xml</file>
</header>
- <module>erl_scan</module>
+ <module since="">erl_scan</module>
<modulesummary>The Erlang token scanner.</modulesummary>
<description>
<p>This module contains functions for tokenizing (scanning) characters into
@@ -74,7 +74,7 @@
<funcs>
<func>
- <name name="category" arity="1"/>
+ <name name="category" arity="1" since="OTP 18.0"/>
<fsummary>Return the category.</fsummary>
<desc>
<p>Returns the category of <c><anno>Token</anno></c>.</p>
@@ -82,7 +82,7 @@
</func>
<func>
- <name name="column" arity="1"/>
+ <name name="column" arity="1" since="OTP 18.0"/>
<fsummary>Return the column.</fsummary>
<desc>
<p>Returns the column of <c><anno>Token</anno></c>'s
@@ -91,7 +91,7 @@
</func>
<func>
- <name name="end_location" arity="1"/>
+ <name name="end_location" arity="1" since="OTP 18.0"/>
<fsummary>Return the end location of the text.</fsummary>
<desc>
<p>Returns the end location of the text of
@@ -101,7 +101,7 @@
</func>
<func>
- <name name="format_error" arity="1"/>
+ <name name="format_error" arity="1" since=""/>
<fsummary>Format an error descriptor.</fsummary>
<desc>
<p>Uses an <c><anno>ErrorDescriptor</anno></c> and returns a string
@@ -113,7 +113,7 @@
</func>
<func>
- <name name="line" arity="1"/>
+ <name name="line" arity="1" since="OTP 18.0"/>
<fsummary>Return the line.</fsummary>
<desc>
<p>Returns the line of <c><anno>Token</anno></c>'s collection
@@ -122,7 +122,7 @@
</func>
<func>
- <name name="location" arity="1"/>
+ <name name="location" arity="1" since="OTP 18.0"/>
<fsummary>Return the location.</fsummary>
<desc>
<p>Returns the location of <c><anno>Token</anno></c>'s
@@ -131,7 +131,7 @@
</func>
<func>
- <name name="reserved_word" arity="1"/>
+ <name name="reserved_word" arity="1" since=""/>
<fsummary>Test for a reserved word.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Atom</anno></c> is an
@@ -140,9 +140,9 @@
</func>
<func>
- <name name="string" arity="1"/>
- <name name="string" arity="2"/>
- <name name="string" arity="3"/>
+ <name name="string" arity="1" since=""/>
+ <name name="string" arity="2" since=""/>
+ <name name="string" arity="3" since=""/>
<fsummary>Scan a string and return the Erlang tokens.</fsummary>
<desc>
<p>Takes the list of characters <c><anno>String</anno></c> and tries to
@@ -229,7 +229,7 @@
</func>
<func>
- <name name="symbol" arity="1"/>
+ <name name="symbol" arity="1" since="OTP 18.0"/>
<fsummary>Return the symbol.</fsummary>
<desc>
<p>Returns the symbol of <c><anno>Token</anno></c>.</p>
@@ -237,7 +237,7 @@
</func>
<func>
- <name name="text" arity="1"/>
+ <name name="text" arity="1" since="OTP 18.0"/>
<fsummary>Return the text.</fsummary>
<desc>
<p>Returns the text of <c><anno>Token</anno></c>'s collection
@@ -247,8 +247,8 @@
</func>
<func>
- <name name="tokens" arity="3"/>
- <name name="tokens" arity="4"/>
+ <name name="tokens" arity="3" since=""/>
+ <name name="tokens" arity="4" since=""/>
<fsummary>Re-entrant scanner.</fsummary>
<type name="char_spec"/>
<type name="return_cont"/>
diff --git a/lib/stdlib/doc/src/erl_tar.xml b/lib/stdlib/doc/src/erl_tar.xml
index 68fa071090..ea8173748a 100644
--- a/lib/stdlib/doc/src/erl_tar.xml
+++ b/lib/stdlib/doc/src/erl_tar.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>erl_tar.xml</file>
</header>
- <module>erl_tar</module>
+ <module since="">erl_tar</module>
<modulesummary>Unix 'tar' utility for reading and writing tar archives.
</modulesummary>
<description>
@@ -127,7 +127,7 @@
<funcs>
<func>
- <name>add(TarDescriptor, Filename, Options) -> RetValue</name>
+ <name since="">add(TarDescriptor, Filename, Options) -> RetValue</name>
<fsummary>Add a file to an open tar file.</fsummary>
<type>
<v>TarDescriptor = term()</v>
@@ -211,7 +211,7 @@
</func>
<func>
- <name>add(TarDescriptor, FilenameOrBin, NameInArchive, Options) ->
+ <name since="">add(TarDescriptor, FilenameOrBin, NameInArchive, Options) ->
RetValue </name>
<fsummary>Add a file to an open tar file.</fsummary>
<type>
@@ -233,7 +233,7 @@
</func>
<func>
- <name>close(TarDescriptor)</name>
+ <name since="">close(TarDescriptor)</name>
<fsummary>Close an open tar file.</fsummary>
<type>
<v>TarDescriptor = term()</v>
@@ -245,7 +245,7 @@
</func>
<func>
- <name>create(Name, FileList) ->RetValue </name>
+ <name since="">create(Name, FileList) ->RetValue </name>
<fsummary>Create a tar archive.</fsummary>
<type>
<v>Name = filename()</v>
@@ -264,7 +264,7 @@
</func>
<func>
- <name>create(Name, FileList, OptionList)</name>
+ <name since="">create(Name, FileList, OptionList)</name>
<fsummary>Create a tar archive with options.</fsummary>
<type>
<v>Name = filename()</v>
@@ -315,7 +315,7 @@
</func>
<func>
- <name>extract(Name) -> RetValue</name>
+ <name since="">extract(Name) -> RetValue</name>
<fsummary>Extract all files from a tar file.</fsummary>
<type>
<v>Name = filename() | {binary,binary()} | {file,Fd}</v>
@@ -339,7 +339,7 @@
</func>
<func>
- <name>extract(Name, OptionList)</name>
+ <name since="">extract(Name, OptionList)</name>
<fsummary>Extract files from a tar file.</fsummary>
<type>
<v>Name = filename() | {binary,binary()} | {file,Fd}</v>
@@ -411,7 +411,7 @@
</func>
<func>
- <name>format_error(Reason) -> string()</name>
+ <name since="">format_error(Reason) -> string()</name>
<fsummary>Convert error term to a readable string.</fsummary>
<type>
<v>Reason = term()</v>
@@ -423,7 +423,7 @@
</func>
<func>
- <name>init(UserPrivate, AccessMode, Fun) ->
+ <name since="OTP 17.4">init(UserPrivate, AccessMode, Fun) ->
{ok,TarDescriptor} | {error,Reason}</name>
<fsummary>Create a <c>TarDescriptor</c> used in subsequent tar operations
when defining own low-level storage access functions.</fsummary>
@@ -518,7 +518,7 @@ erl_tar:close(TarDesc)</code>
</func>
<func>
- <name>open(Name, OpenModeList) -> RetValue</name>
+ <name since="">open(Name, OpenModeList) -> RetValue</name>
<fsummary>Open a tar file for writing.</fsummary>
<type>
<v>Name = filename()</v>
@@ -565,7 +565,7 @@ erl_tar:close(TarDesc)</code>
</func>
<func>
- <name>table(Name) -> RetValue</name>
+ <name since="">table(Name) -> RetValue</name>
<fsummary>Retrieve the name of all files in a tar file.</fsummary>
<type>
<v>Name = filename()|{binary,binary()}|{file,file_descriptor()}</v>
@@ -578,7 +578,7 @@ erl_tar:close(TarDesc)</code>
</func>
<func>
- <name>table(Name, Options)</name>
+ <name since="">table(Name, Options)</name>
<fsummary>Retrieve name and information of all files in a tar file.
</fsummary>
<type>
@@ -590,7 +590,7 @@ erl_tar:close(TarDesc)</code>
</func>
<func>
- <name>t(Name)</name>
+ <name since="">t(Name)</name>
<fsummary>Print the name of each file in a tar file.</fsummary>
<type>
<v>Name = filename()|{binary,binary()}|{file,file_descriptor()}</v>
@@ -602,7 +602,7 @@ erl_tar:close(TarDesc)</code>
</func>
<func>
- <name>tt(Name)</name>
+ <name since="">tt(Name)</name>
<fsummary>Print name and information for each file in a tar file.
</fsummary>
<type>
diff --git a/lib/stdlib/doc/src/ets.xml b/lib/stdlib/doc/src/ets.xml
index ad006f9a2b..d2ac6a75b1 100644
--- a/lib/stdlib/doc/src/ets.xml
+++ b/lib/stdlib/doc/src/ets.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>ets</module>
+ <module since="">ets</module>
<modulesummary>Built-in term storage.</modulesummary>
<description>
<p>This module is an interface to the Erlang built-in term storage
@@ -138,23 +138,96 @@
operation. In database terms the isolation level can be seen as
"serializable", as if all isolated operations are carried out serially,
one after the other in a strict order.</p>
+ </section>
- <p>No other support is available within this module that would guarantee
- consistency between objects. However, function
- <seealso marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seealso>
- can be used to guarantee that a sequence of
- <seealso marker="#first/1"><c>first/1</c></seealso> and
- <seealso marker="#next/2"><c>next/2</c></seealso> calls traverse the
- table without errors and that each existing object in the table is
- visited exactly once, even if another (or the same) process
- simultaneously deletes or inserts objects into the table.
- Nothing else is guaranteed; in particular objects that are inserted
- or deleted during such a traversal can be visited once or not at all.
- Functions that internally traverse over a table, like
- <seealso marker="#select/1"><c>select</c></seealso> and
- <seealso marker="#match/1"><c>match</c></seealso>,
- give the same guarantee as
- <seealso marker="#safe_fixtable/2"><c>safe_fixtable</c></seealso>.</p>
+ <section><marker id="traversal"></marker>
+ <title>Table traversal</title>
+ <p>There are different ways to traverse through the objects of a table.</p>
+ <list type="bulleted">
+ <item><p><em>Single-step</em> traversal one key at at time, using
+ <seealso marker="#first/1"><c>first/1</c></seealso>,
+ <seealso marker="#next/2"><c>next/2</c></seealso>,
+ <seealso marker="#last/1"><c>last/1</c></seealso> and
+ <seealso marker="#prev/2"><c>prev/2</c></seealso>.</p>
+ </item>
+ <item><p>Search with simple <em>match patterns</em>, using
+ <seealso marker="#match/1"><c>match/1/2/3</c></seealso>,
+ <seealso marker="#match_delete/2"><c>match_delete/2</c></seealso> and
+ <seealso marker="#match_object/1"><c>match_object/1/2/3</c></seealso>.</p>
+ </item>
+ <item><p>Search with more powerful <em>match specifications</em>, using
+ <seealso marker="#select/1"><c>select/1/2/3</c></seealso>,
+ <seealso marker="#select_count/2"><c>select_count/2</c></seealso>,
+ <seealso marker="#select_delete/2"><c>select_delete/2</c></seealso>,
+ <seealso marker="#select_replace/2"><c>select_replace/2</c></seealso> and
+ <seealso marker="#select_reverse/1"><c>select_reverse/1/2/3</c></seealso>.</p>
+ </item>
+ <item><p><em>Table conversions</em>, using
+ <seealso marker="#tab2file/2"><c>tab2file/2/3</c></seealso> and
+ <seealso marker="#tab2list/1"><c>tab2list/1</c></seealso>.</p>
+ </item>
+ </list>
+ <p>None of these ways of table traversal will guarantee a consistent table snapshot
+ if the table is also updated during the traversal. Moreover, traversals not
+ done in a <em>safe</em> way, on tables where keys are inserted or deleted
+ during the traversal, may yield the following undesired effects:</p>
+ <list type="bulleted">
+ <item><p>Any key may be missed.</p></item>
+ <item><p>Any key may be found more than once.</p></item>
+ <item><p>The traversal may fail with <c>badarg</c> exception if keys are deleted.</p>
+ </item>
+ </list>
+ <p>A table traversal is <em>safe</em> if either</p>
+ <list type="bulleted">
+ <item><p>the table is of type <c>ordered_set</c>.</p>
+ </item>
+ <item><p>the entire table traversal is done within one ETS function
+ call.</p>
+ </item>
+ <item><p>function <seealso marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seealso>
+ is used to keep the table fixated during the entire traversal.</p>
+ </item>
+ </list>
+ <note>
+ <p>Even though the access of a single object is always guaranteed to be
+ <seealso marker="#concurrency">atomic and isolated</seealso>, each traversal
+ through a table to find the next key is not done with such guarantees. This is often
+ not a problem, but may cause rare subtle "unexpected" effects if a concurrent
+ process inserts objects during a traversal. For example, consider one
+ process doing</p>
+<pre>
+ets:new(t, [ordered_set, named_table]),
+ets:insert(t, {1}),
+ets:insert(t, {2}),
+ets:insert(t, {3}),
+</pre>
+ <p>A concurrent call to <c>ets:first(t)</c>, done by another
+ process, may then in rare cases return <c>2</c> even though
+ <c>2</c> has never existed in the table ordered as the first key. In
+ the same way, a concurrent call to <c>ets:next(t, 1)</c> may return
+ <c>3</c> even though <c>3</c> never existed in the table
+ ordered directly after <c>1</c>.</p>
+ <p>Effects like this are improbable but possible. The probability will
+ further be reduced (if not vanish) if table option
+ <seealso marker="#new_2_write_concurrency"><c>write_concurrency</c></seealso>
+ is not enabled. This can also only be a potential concern for
+ <c>ordered_set</c> where the traversal order is defined.</p>
+ </note>
+ <p>Traversals using <c>match</c> and <c>select</c> functions may not need to
+ scan the entire table depending on how the key is specified. A match
+ pattern with a <em>fully bound key</em> (without any match variables) will
+ optimize the operation to a single key lookup without any table traversal
+ at all. For <c>ordered_set</c> a <em>partially bound key</em> will limit the
+ traversal to only scan a subset of the table based on term order. A
+ partially bound key is either a list or a tuple with a prefix that is fully
+ bound. Example:</p>
+<pre>
+1> <input>T = ets:new(t,[ordered_set]), ets:insert(T, {"555-1234", "John Smith"}).</input>
+true
+2> <input>%% Efficient search of all with area code 555</input>
+2> <input>ets:match(T,{[$5,$5,$5,$- |'$1'],'$2'}).</input>
+[["1234","John Smith"]]
+</pre>
</section>
<section>
@@ -207,7 +280,7 @@
<funcs>
<func>
- <name name="all" arity="0"/>
+ <name name="all" arity="0" since=""/>
<fsummary>Return a list of all ETS tables.</fsummary>
<desc>
<p>Returns a list of all tables at the node. Named tables are
@@ -222,7 +295,7 @@
</func>
<func>
- <name name="delete" arity="1"/>
+ <name name="delete" arity="1" since=""/>
<fsummary>Delete an entire ETS table.</fsummary>
<desc>
<p>Deletes the entire table <c><anno>Tab</anno></c>.</p>
@@ -230,7 +303,7 @@
</func>
<func>
- <name name="delete" arity="2"/>
+ <name name="delete" arity="2" since=""/>
<fsummary>Delete all objects with a specified key from an ETS
table.</fsummary>
<desc>
@@ -240,7 +313,7 @@
</func>
<func>
- <name name="delete_all_objects" arity="1"/>
+ <name name="delete_all_objects" arity="1" since=""/>
<fsummary>Delete all objects in an ETS table.</fsummary>
<desc>
<p>Delete all objects in the ETS table <c><anno>Tab</anno></c>.
@@ -250,7 +323,7 @@
</func>
<func>
- <name name="delete_object" arity="2"/>
+ <name name="delete_object" arity="2" since=""/>
<fsummary>Deletes a specific from an ETS table.</fsummary>
<desc>
<p>Delete the exact object <c><anno>Object</anno></c> from the
@@ -262,7 +335,7 @@
</func>
<func>
- <name name="file2tab" arity="1"/>
+ <name name="file2tab" arity="1" since=""/>
<fsummary>Read an ETS table from a file.</fsummary>
<desc>
<p>Reads a file produced by <seealso marker="#tab2file/2">
@@ -274,7 +347,7 @@
</func>
<func>
- <name name="file2tab" arity="2"/>
+ <name name="file2tab" arity="2" since=""/>
<fsummary>Read an ETS table from a file.</fsummary>
<desc>
<p>Reads a file produced by <seealso marker="#tab2file/2">
@@ -306,7 +379,7 @@
</func>
<func>
- <name name="first" arity="1"/>
+ <name name="first" arity="1" since=""/>
<fsummary>Return the first key in an ETS table.</fsummary>
<desc>
<p>Returns the first key <c><anno>Key</anno></c> in table
@@ -321,7 +394,7 @@
</func>
<func>
- <name name="foldl" arity="3"/>
+ <name name="foldl" arity="3" since=""/>
<fsummary>Fold a function over an ETS table.</fsummary>
<desc>
<p><c><anno>Acc0</anno></c> is returned if the table is empty.
@@ -337,7 +410,7 @@
</func>
<func>
- <name name="foldr" arity="3"/>
+ <name name="foldr" arity="3" since=""/>
<fsummary>Fold a function over an ETS table.</fsummary>
<desc>
<p><c><anno>Acc0</anno></c> is returned if the table is empty.
@@ -353,7 +426,7 @@
</func>
<func>
- <name name="from_dets" arity="2"/>
+ <name name="from_dets" arity="2" since=""/>
<fsummary>Fill an ETS table with objects from a Dets
table.</fsummary>
<desc>
@@ -367,7 +440,7 @@
</func>
<func>
- <name name="fun2ms" arity="1"/>
+ <name name="fun2ms" arity="1" since=""/>
<fsummary>Pseudo function that transforms fun syntax to a match
specification.</fsummary>
<desc>
@@ -436,7 +509,7 @@ Error: fun containing local Erlang function calls
</func>
<func>
- <name name="give_away" arity="3"/>
+ <name name="give_away" arity="3" since=""/>
<fsummary>Change owner of a table.</fsummary>
<desc>
<p>Make process <c><anno>Pid</anno></c> the new owner of table
@@ -454,7 +527,7 @@ Error: fun containing local Erlang function calls
</func>
<func>
- <name name="i" arity="0"/>
+ <name name="i" arity="0" since=""/>
<fsummary>Display information about all ETS tables on a terminal.
</fsummary>
<desc>
@@ -463,7 +536,7 @@ Error: fun containing local Erlang function calls
</func>
<func>
- <name name="i" arity="1"/>
+ <name name="i" arity="1" since=""/>
<fsummary>Browse an ETS table on a terminal.</fsummary>
<desc>
<p>Browses table <c><anno>Tab</anno></c> on a terminal.</p>
@@ -471,7 +544,7 @@ Error: fun containing local Erlang function calls
</func>
<func>
- <name name="info" arity="1"/>
+ <name name="info" arity="1" since=""/>
<fsummary>Return information about an <c>table</c>.</fsummary>
<desc>
<p>Returns information about table <c><anno>Tab</anno></c> as a list of
@@ -547,7 +620,7 @@ Error: fun containing local Erlang function calls
</func>
<func>
- <name name="info" arity="2"/>
+ <name name="info" arity="2" since=""/>
<fsummary>Return the information associated with the specified item for
an ETS table.</fsummary>
<desc>
@@ -611,15 +684,14 @@ Error: fun containing local Erlang function calls
</item>
<item>
<p><c>Item=stats, Value=tuple()</c></p>
- <p>Returns internal statistics about <c>set</c>, <c>bag</c>, and
- <c>duplicate_bag</c> tables on an internal format used by OTP
- test suites. Not for production use.</p></item>
+ <p>Returns internal statistics about tables on an internal format
+ used by OTP test suites. Not for production use.</p></item>
</list>
</desc>
</func>
<func>
- <name name="init_table" arity="2"/>
+ <name name="init_table" arity="2" since=""/>
<fsummary>Replace all objects of an ETS table.</fsummary>
<desc>
<p>Replaces the existing objects of table <c><anno>Tab</anno></c> with
@@ -649,7 +721,7 @@ Error: fun containing local Erlang function calls
</func>
<func>
- <name name="insert" arity="2"/>
+ <name name="insert" arity="2" since=""/>
<fsummary>Insert an object into an ETS table.</fsummary>
<desc>
<p>Inserts the object or all of the objects in list
@@ -681,7 +753,7 @@ Error: fun containing local Erlang function calls
</func>
<func>
- <name name="insert_new" arity="2"/>
+ <name name="insert_new" arity="2" since=""/>
<fsummary>Insert an object into an ETS table if the key is not
already present.</fsummary>
<desc>
@@ -700,7 +772,7 @@ Error: fun containing local Erlang function calls
</func>
<func>
- <name name="is_compiled_ms" arity="1"/>
+ <name name="is_compiled_ms" arity="1" since=""/>
<fsummary>Check if an Erlang term is the result of
<c>match_spec_compile</c>.</fsummary>
<desc>
@@ -732,7 +804,7 @@ ets:is_compiled_ms(Broken).</code>
</func>
<func>
- <name name="last" arity="1"/>
+ <name name="last" arity="1" since=""/>
<fsummary>Return the last key in an ETS table of type
<c>ordered_set</c>.</fsummary>
<desc>
@@ -747,7 +819,7 @@ ets:is_compiled_ms(Broken).</code>
</func>
<func>
- <name name="lookup" arity="2"/>
+ <name name="lookup" arity="2" since=""/>
<fsummary>Return all objects with a specified key in an ETS table.
</fsummary>
<desc>
@@ -787,7 +859,7 @@ ets:is_compiled_ms(Broken).</code>
</func>
<func>
- <name name="lookup_element" arity="3"/>
+ <name name="lookup_element" arity="3" since=""/>
<fsummary>Return the <c>Pos</c>:th element of all objects with a
specified key in an ETS table.</fsummary>
<desc>
@@ -810,7 +882,7 @@ ets:is_compiled_ms(Broken).</code>
</func>
<func>
- <name name="match" arity="1"/>
+ <name name="match" arity="1" since=""/>
<fsummary>Continues matching objects in an ETS table.</fsummary>
<desc>
<p>Continues a match started with
@@ -824,7 +896,7 @@ ets:is_compiled_ms(Broken).</code>
</func>
<func>
- <name name="match" arity="2"/>
+ <name name="match" arity="2" since=""/>
<fsummary>Match the objects in an ETS table against a pattern.
</fsummary>
<desc>
@@ -856,7 +928,7 @@ ets:is_compiled_ms(Broken).</code>
</func>
<func>
- <name name="match" arity="3"/>
+ <name name="match" arity="3" since=""/>
<fsummary>Match the objects in an ETS table against a pattern
and return part of the answers.</fsummary>
<desc>
@@ -871,11 +943,14 @@ ets:is_compiled_ms(Broken).</code>
<seealso marker="#first/1"><c>first/1</c></seealso> and
<seealso marker="#next/2"><c>next/2</c></seealso>.</p>
<p>If the table is empty, <c>'$end_of_table'</c> is returned.</p>
+ <p>Use <seealso marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seealso>
+ to guarantee <seealso marker="#traversal">safe traversal</seealso>
+ for subsequent calls to <seealso marker="#match/1"><c>match/1</c></seealso>.</p>
</desc>
</func>
<func>
- <name name="match_delete" arity="2"/>
+ <name name="match_delete" arity="2" since=""/>
<fsummary>Delete all objects that match a specified pattern from an
ETS table.</fsummary>
<desc>
@@ -886,7 +961,7 @@ ets:is_compiled_ms(Broken).</code>
</func>
<func>
- <name name="match_object" arity="1"/>
+ <name name="match_object" arity="1" since=""/>
<fsummary>Continues matching objects in an ETS table.</fsummary>
<desc>
<p>Continues a match started with
@@ -901,7 +976,7 @@ ets:is_compiled_ms(Broken).</code>
</func>
<func>
- <name name="match_object" arity="2"/>
+ <name name="match_object" arity="2" since=""/>
<fsummary>Match the objects in an ETS table against a pattern.
</fsummary>
<desc>
@@ -920,7 +995,7 @@ ets:is_compiled_ms(Broken).</code>
</func>
<func>
- <name name="match_object" arity="3"/>
+ <name name="match_object" arity="3" since=""/>
<fsummary>Match the objects in an ETS table against a pattern and
return part of the answers.</fsummary>
<desc>
@@ -936,11 +1011,15 @@ ets:is_compiled_ms(Broken).</code>
<seealso marker="#first/1"><c>first/1</c></seealso> and
<seealso marker="#next/2"><c>next/2</c></seealso>.</p>
<p>If the table is empty, <c>'$end_of_table'</c> is returned.</p>
+ <p>Use <seealso marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seealso>
+ to guarantee <seealso marker="#traversal">safe traversal</seealso>
+ for subsequent calls to <seealso marker="#match_object/1">
+ <c>match_object/1</c></seealso>.</p>
</desc>
</func>
<func>
- <name name="match_spec_compile" arity="1"/>
+ <name name="match_spec_compile" arity="1" since=""/>
<fsummary>Compile a match specification into its internal representation.
</fsummary>
<desc>
@@ -968,7 +1047,7 @@ ets:is_compiled_ms(Broken).</code>
</func>
<func>
- <name name="match_spec_run" arity="2"/>
+ <name name="match_spec_run" arity="2" since=""/>
<fsummary>Perform matching, using a compiled match specification on a
list of terms.</fsummary>
<desc>
@@ -1005,7 +1084,7 @@ ets:select(Table, MatchSpec),</code>
</func>
<func>
- <name name="member" arity="2"/>
+ <name name="member" arity="2" since=""/>
<fsummary>Tests for occurrence of a key in an ETS table.</fsummary>
<desc>
<p>Works like <seealso marker="#lookup/2"><c>lookup/2</c></seealso>,
@@ -1016,7 +1095,7 @@ ets:select(Table, MatchSpec),</code>
</func>
<func>
- <name name="new" arity="2"/>
+ <name name="new" arity="2" since=""/>
<fsummary>Create a new ETS table.</fsummary>
<desc>
<p>Creates a new table and returns a table identifier that can
@@ -1135,11 +1214,17 @@ ets:select(Table, MatchSpec),</code>
Functions that makes such promises over many objects (like
<seealso marker="#insert/2"><c>insert/2</c></seealso>)
gain less (or nothing) from this option.</p>
- <p>Table type <c>ordered_set</c> is not affected by this option.
- Also, the memory consumption inflicted by
- both <c>write_concurrency</c> and <c>read_concurrency</c> is a
- constant overhead per table. This overhead can be especially
- large when both options are combined.</p>
+ <p>The memory consumption inflicted by both <c>write_concurrency</c>
+ and <c>read_concurrency</c> is a constant overhead per table for
+ <c>set</c>, <c>bag</c> and <c>duplicate_bag</c>. For
+ <c>ordered_set</c> the memory overhead depends on the number
+ of inserted objects and the amount of actual detected
+ concurrency in runtime. The memory overhead can be especially
+ large when both options are combined.</p>
+ <note>
+ <p>Prior to stdlib-3.7 (OTP-22.0) <c>write_concurrency</c> had no
+ effect on <c>ordered_set</c>.</p>
+ </note>
<marker id="new_2_read_concurrency"></marker>
</item>
<tag><c>{read_concurrency,boolean()}</c></tag>
@@ -1180,7 +1265,7 @@ ets:select(Table, MatchSpec),</code>
</func>
<func>
- <name name="next" arity="2"/>
+ <name name="next" arity="2" since=""/>
<fsummary>Return the next key in an ETS table.</fsummary>
<desc>
<p>Returns the next key <c><anno>Key2</anno></c>, following key
@@ -1192,17 +1277,18 @@ ets:select(Table, MatchSpec),</code>
<p>To find the first key in the table, use
<seealso marker="#first/1"><c>first/1</c></seealso>.</p>
<p>Unless a table of type <c>set</c>, <c>bag</c>, or
- <c>duplicate_bag</c> is protected using
+ <c>duplicate_bag</c> is fixated using
<seealso marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seealso>,
- a traversal can fail if
- concurrent updates are made to the table. For table
- type <c>ordered_set</c>, the function returns the next key in
- order, even if the object does no longer exist.</p>
+ a call to <c>next/2</c> will fail if <c><anno>Key1</anno></c> no longer
+ exists in the table. For table type <c>ordered_set</c>, the function
+ always returns the next key after <c><anno>Key1</anno></c> in term
+ order, regardless whether <c><anno>Key1</anno></c> ever existed in the
+ table.</p>
</desc>
</func>
<func>
- <name name="prev" arity="2"/>
+ <name name="prev" arity="2" since=""/>
<fsummary>Return the previous key in an ETS table of type
<c>ordered_set</c>.</fsummary>
<desc>
@@ -1212,13 +1298,13 @@ ets:select(Table, MatchSpec),</code>
table types, the function is synonymous to
<seealso marker="#next/2"><c>next/2</c></seealso>.
If no previous key exists, <c>'$end_of_table'</c> is returned.</p>
- <p>To find the last key in the table, use
+ <p>To find the last key in an <c>ordered_set</c> table, use
<seealso marker="#last/1"><c>last/1</c></seealso>.</p>
</desc>
</func>
<func>
- <name name="rename" arity="2"/>
+ <name name="rename" arity="2" since=""/>
<fsummary>Rename a named ETS table.</fsummary>
<desc>
<p>Renames the named table <c><anno>Tab</anno></c> to the new name
@@ -1228,7 +1314,7 @@ ets:select(Table, MatchSpec),</code>
</func>
<func>
- <name name="repair_continuation" arity="2"/>
+ <name name="repair_continuation" arity="2" since=""/>
<fsummary>Repair a continuation from <c>ets:select/1 or ets:select/3</c>
that has passed through external representation.</fsummary>
<desc>
@@ -1283,11 +1369,20 @@ ets:select(ets:repair_continuation(Broken,MS)).</code>
</func>
<func>
- <name name="safe_fixtable" arity="2"/>
+ <name name="safe_fixtable" arity="2" since=""/>
<fsummary>Fix an ETS table for safe traversal.</fsummary>
<desc>
<p>Fixes a table of type <c>set</c>, <c>bag</c>, or
- <c>duplicate_bag</c> for safe traversal.</p>
+ <c>duplicate_bag</c> for <seealso marker="#traversal">
+ safe traversal</seealso> using
+ <seealso marker="#first/1"><c>first/1</c></seealso> &amp;
+ <seealso marker="#next/2"><c>next/2</c></seealso>,
+ <seealso marker="#match/3"><c>match/3</c></seealso> &amp;
+ <seealso marker="#match/1"><c>match/1</c></seealso>,
+ <seealso marker="#match_object/3"><c>match_object/3</c></seealso> &amp;
+ <seealso marker="#match_object/1"><c>match_object/1</c></seealso>, or
+ <seealso marker="#select/3"><c>select/3</c></seealso> &amp;
+ <seealso marker="#select/1"><c>select/1</c></seealso>.</p>
<p>A process fixes a table by calling
<c>safe_fixtable(<anno>Tab</anno>, true)</c>. The table remains
fixed until the process releases it by calling
@@ -1300,11 +1395,11 @@ ets:select(ets:repair_continuation(Broken,MS)).</code>
<p>When a table is fixed, a sequence of
<seealso marker="#first/1"><c>first/1</c></seealso> and
<seealso marker="#next/2"><c>next/2</c></seealso> calls are
- guaranteed to succeed, and each object in
- the table is returned only once, even if objects
- are removed or inserted during the traversal. The keys for new
- objects inserted during the traversal <em>can</em> be returned by
- <c>next/2</c> (it depends on the internal ordering of the keys).</p>
+ guaranteed to succeed even if keys are removed during the
+ traversal. The keys for objects inserted or deleted during a
+ traversal may or may not be returned by <c>next/2</c> depending on
+ the ordering of keys within the table and if the key exists at the time
+ <c>next/2</c> is called.</p>
<p><em>Example:</em></p>
<code type="none">
clean_all_with_value(Tab,X) ->
@@ -1322,7 +1417,7 @@ clean_all_with_value(Tab,X,Key) ->
true
end,
clean_all_with_value(Tab,X,ets:next(Tab,Key)).</code>
- <p>Notice that no deleted objects are removed from a
+ <p>Notice that deleted objects are not freed from a
fixed table until it has been released. If a process fixes a
table but never releases it, the memory used by the deleted
objects is never freed. The performance of operations on
@@ -1332,14 +1427,14 @@ clean_all_with_value(Tab,X,Key) ->
<c>info(Tab, safe_fixed_monotonic_time)</c></seealso>. A system with
many processes fixing tables can need a monitor that sends alarms
when tables have been fixed for too long.</p>
- <p>Notice that for table type <c>ordered_set</c>,
- <c>safe_fixtable/2</c> is not necessary, as calls to
- <c>first/1</c> and <c>next/2</c> always succeed.</p>
+ <p>Notice that <c>safe_fixtable/2</c> is not necessary for table type
+ <c>ordered_set</c> and for traversals done by a single ETS function call,
+ like <seealso marker="#select/2"><c>select/2</c></seealso>.</p>
</desc>
</func>
<func>
- <name name="select" arity="1"/>
+ <name name="select" arity="1" since=""/>
<fsummary>Continue matching objects in an ETS table.</fsummary>
<desc>
<p>Continues a match started with
@@ -1353,7 +1448,7 @@ clean_all_with_value(Tab,X,Key) ->
</func>
<func>
- <name name="select" arity="2"/>
+ <name name="select" arity="2" since=""/>
<fsummary>Match the objects in an ETS table against a
match specification.</fsummary>
<desc>
@@ -1448,7 +1543,7 @@ is_integer(X), is_integer(Y), X + Y < 4711]]></code>
</func>
<func>
- <name name="select" arity="3"/>
+ <name name="select" arity="3" since=""/>
<fsummary>Match the objects in an ETS table against a match
specification and return part of the answers.</fsummary>
<desc>
@@ -1462,12 +1557,15 @@ is_integer(X), is_integer(Y), X + Y < 4711]]></code>
table, which is still faster than traversing the table object by
object using <seealso marker="#first/1"><c>first/1</c></seealso>
and <seealso marker="#next/2"><c>next/2</c></seealso>.</p>
- <p>If the table is empty, <c>'$end_of_table'</c> is returned.</p>
+ <p>If the table is empty, <c>'$end_of_table'</c> is returned.</p>
+ <p>Use <seealso marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seealso>
+ to guarantee <seealso marker="#traversal">safe traversal</seealso>
+ for subsequent calls to <seealso marker="#select/1"><c>select/1</c></seealso>.</p>
</desc>
</func>
<func>
- <name name="select_count" arity="2"/>
+ <name name="select_count" arity="2" since=""/>
<fsummary>Match the objects in an ETS table against a match
specification and return the number of objects for which the match
specification returned <c>true</c>.</fsummary>
@@ -1486,7 +1584,7 @@ is_integer(X), is_integer(Y), X + Y < 4711]]></code>
</func>
<func>
- <name name="select_delete" arity="2"/>
+ <name name="select_delete" arity="2" since=""/>
<fsummary>Match the objects in an ETS table against a match
specification and delete objects where the match specification
returns <c>true</c>.</fsummary>
@@ -1510,7 +1608,7 @@ is_integer(X), is_integer(Y), X + Y < 4711]]></code>
</func>
<func>
- <name name="select_replace" arity="2"/>
+ <name name="select_replace" arity="2" since="OTP 20.0"/>
<fsummary>Match and replace objects atomically in an ETS table</fsummary>
<desc>
<p>Matches the objects in the table <c><anno>Tab</anno></c> using a
@@ -1519,7 +1617,7 @@ is_integer(X), is_integer(Y), X + Y < 4711]]></code>
the match specification result.</p>
<p>The match-and-replace operation for each individual object is guaranteed to be
<seealso marker="#concurrency">atomic and isolated</seealso>. The
- <c>select_replace</c> table iteration as a whole, like all other select functions,
+ <c>select_replace</c> table traversal as a whole, like all other select functions,
does not give such guarantees.</p>
<p>The match specifiction must be guaranteed to <em>retain the key</em>
of any matched object. If not, <c>select_replace</c> will fail with <c>badarg</c>
@@ -1549,7 +1647,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
</func>
<func>
- <name name="select_reverse" arity="1"/>
+ <name name="select_reverse" arity="1" since="OTP R14B"/>
<fsummary>Continue matching objects in an ETS table.</fsummary>
<desc>
<p>Continues a match started with <seealso marker="#select_reverse/3">
@@ -1582,7 +1680,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
</func>
<func>
- <name name="select_reverse" arity="2"/>
+ <name name="select_reverse" arity="2" since="OTP R14B"/>
<fsummary>Match the objects in an ETS table against a
match specification.</fsummary>
<desc>
@@ -1594,7 +1692,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
</func>
<func>
- <name name="select_reverse" arity="3"/>
+ <name name="select_reverse" arity="3" since="OTP R14B"/>
<fsummary>Match the objects in an ETS table against a
match specification and return part of the answers.</fsummary>
<desc>
@@ -1612,7 +1710,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
</func>
<func>
- <name name="setopts" arity="2"/>
+ <name name="setopts" arity="2" since=""/>
<fsummary>Set table options.</fsummary>
<desc>
<p>Sets table options. The only allowed option to be set after the
@@ -1623,7 +1721,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
</func>
<func>
- <name name="slot" arity="2"/>
+ <name name="slot" arity="2" since=""/>
<fsummary>Return all objects in a specified slot of an ETS table.
</fsummary>
<desc>
@@ -1648,7 +1746,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
</func>
<func>
- <name name="tab2file" arity="2"/>
+ <name name="tab2file" arity="2" since=""/>
<fsummary>Dump an ETS table to a file.</fsummary>
<desc>
<p>Dumps table <c><anno>Tab</anno></c> to file
@@ -1659,7 +1757,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
</func>
<func>
- <name name="tab2file" arity="3"/>
+ <name name="tab2file" arity="3" since=""/>
<fsummary>Dump an ETS table to a file.</fsummary>
<desc>
<p>Dumps table <c><anno>Tab</anno></c> to file
@@ -1706,7 +1804,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
</func>
<func>
- <name name="tab2list" arity="1"/>
+ <name name="tab2list" arity="1" since=""/>
<fsummary>Return a list of all objects in an ETS table.</fsummary>
<desc>
<p>Returns a list of all objects in table <c><anno>Tab</anno></c>.</p>
@@ -1714,7 +1812,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
</func>
<func>
- <name name="tabfile_info" arity="1"/>
+ <name name="tabfile_info" arity="1" since=""/>
<fsummary>Return a list of all objects in an ETS table.</fsummary>
<desc>
<p>Returns information about the table dumped to file by
@@ -1792,8 +1890,8 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
</func>
<func>
- <name name="table" arity="1"/>
- <name name="table" arity="2"/>
+ <name name="table" arity="1" since=""/>
+ <name name="table" arity="2" since=""/>
<fsummary>Return a QLC query handle.</fsummary>
<desc>
<p>Returns a Query List
@@ -1869,7 +1967,7 @@ true</pre>
</func>
<func>
- <name name="take" arity="2"/>
+ <name name="take" arity="2" since="OTP 18.0"/>
<fsummary>Return and remove all objects with a specified key from an
ETS table.</fsummary>
<desc>
@@ -1884,7 +1982,7 @@ true</pre>
</desc>
</func>
<func>
- <name name="test_ms" arity="2"/>
+ <name name="test_ms" arity="2" since=""/>
<fsummary>Test a match specification for use in <c>select/2</c>.
</fsummary>
<desc>
@@ -1911,7 +2009,7 @@ true</pre>
</func>
<func>
- <name name="to_dets" arity="2"/>
+ <name name="to_dets" arity="2" since=""/>
<fsummary>Fill a Dets table with objects from an ETS table.
</fsummary>
<desc>
@@ -1922,12 +2020,12 @@ true</pre>
</func>
<func>
- <name name="update_counter" arity="3" clause_i="1"/>
- <name name="update_counter" arity="4" clause_i="1"/>
- <name name="update_counter" arity="3" clause_i="2"/>
- <name name="update_counter" arity="4" clause_i="2"/>
- <name name="update_counter" arity="3" clause_i="3"/>
- <name name="update_counter" arity="4" clause_i="3"/>
+ <name name="update_counter" arity="3" clause_i="1" since=""/>
+ <name name="update_counter" arity="4" clause_i="1" since="OTP 18.0"/>
+ <name name="update_counter" arity="3" clause_i="2" since=""/>
+ <name name="update_counter" arity="4" clause_i="2" since="OTP 18.0"/>
+ <name name="update_counter" arity="3" clause_i="3" since=""/>
+ <name name="update_counter" arity="4" clause_i="3" since="OTP 18.0"/>
<fsummary>Update a counter object in an ETS table.</fsummary>
<type variable="Tab"/>
<type variable="Key"/>
@@ -1940,9 +2038,8 @@ true</pre>
<p>This function provides an efficient way to update one or more
counters, without the trouble of having to look up an object, update
the object by incrementing an element, and insert the resulting
- object into the table again. (The update is done atomically,
- that is, no process
- can access the ETS table in the middle of the operation.)</p>
+ object into the table again. The operation is guaranteed to be
+ <seealso marker="#concurrency">atomic and isolated</seealso>.</p>
<p>This function destructively update the object with key
<c><anno>Key</anno></c> in table <c><anno>Tab</anno></c> by adding
<c><anno>Incr</anno></c> to the element at position
@@ -2006,8 +2103,8 @@ true</pre>
</func>
<func>
- <name name="update_element" arity="3" clause_i="1"/>
- <name name="update_element" arity="3" clause_i="2"/>
+ <name name="update_element" arity="3" clause_i="1" since=""/>
+ <name name="update_element" arity="3" clause_i="2" since=""/>
<fsummary>Update the <c>Pos</c>:th element of the object with a
specified key in an ETS table.</fsummary>
<type variable="Tab"/>
@@ -2049,7 +2146,7 @@ true</pre>
</func>
<func>
- <name name="whereis" arity="1"/>
+ <name name="whereis" arity="1" since="OTP 21.0"/>
<fsummary>Retrieves the tid() of a named table.</fsummary>
<desc>
<p>This function returns the
diff --git a/lib/stdlib/doc/src/file_sorter.xml b/lib/stdlib/doc/src/file_sorter.xml
index e988d58c2f..942d98fe61 100644
--- a/lib/stdlib/doc/src/file_sorter.xml
+++ b/lib/stdlib/doc/src/file_sorter.xml
@@ -32,7 +32,7 @@
<rev>PA1</rev>
<file>file_sorter.xml</file>
</header>
- <module>file_sorter</module>
+ <module since="">file_sorter</module>
<modulesummary>File sorter.</modulesummary>
<description>
<p>This module contains functions for sorting terms on files, merging
@@ -334,8 +334,8 @@ output(L) ->
<funcs>
<func>
- <name name="check" arity="1"/>
- <name name="check" arity="2"/>
+ <name name="check" arity="1" since=""/>
+ <name name="check" arity="2" since=""/>
<fsummary>Check whether terms on files are sorted.</fsummary>
<desc>
<p>Checks files for sortedness. If a file is not sorted, the
@@ -347,8 +347,8 @@ output(L) ->
</func>
<func>
- <name name="keycheck" arity="2"/>
- <name name="keycheck" arity="3"/>
+ <name name="keycheck" arity="2" since=""/>
+ <name name="keycheck" arity="3" since=""/>
<fsummary>Check whether terms on files are sorted by key.</fsummary>
<desc>
<p>Checks files for sortedness. If a file is not sorted, the
@@ -360,8 +360,8 @@ output(L) ->
</func>
<func>
- <name name="keymerge" arity="3"/>
- <name name="keymerge" arity="4"/>
+ <name name="keymerge" arity="3" since=""/>
+ <name name="keymerge" arity="4" since=""/>
<fsummary>Merge terms on files by key.</fsummary>
<desc>
<p>Merges tuples on files. Each input file is assumed to be
@@ -372,7 +372,7 @@ output(L) ->
</func>
<func>
- <name name="keysort" arity="2"/>
+ <name name="keysort" arity="2" since=""/>
<fsummary>Sort terms on files by key.</fsummary>
<desc>
<p>Sorts tuples on files.</p>
@@ -382,8 +382,8 @@ output(L) ->
</func>
<func>
- <name name="keysort" arity="3"/>
- <name name="keysort" arity="4"/>
+ <name name="keysort" arity="3" since=""/>
+ <name name="keysort" arity="4" since=""/>
<fsummary>Sort terms on files by key.</fsummary>
<desc>
<p>Sorts tuples on files. The sort is performed on the
@@ -397,8 +397,8 @@ output(L) ->
</func>
<func>
- <name name="merge" arity="2"/>
- <name name="merge" arity="3"/>
+ <name name="merge" arity="2" since=""/>
+ <name name="merge" arity="3" since=""/>
<fsummary>Merge terms on files.</fsummary>
<desc>
<p>Merges terms on files. Each input file is assumed to be
@@ -409,7 +409,7 @@ output(L) ->
</func>
<func>
- <name name="sort" arity="1"/>
+ <name name="sort" arity="1" since=""/>
<fsummary>Sort terms on files.</fsummary>
<desc>
<p>Sorts terms on files.</p>
@@ -419,8 +419,8 @@ output(L) ->
</func>
<func>
- <name name="sort" arity="2"/>
- <name name="sort" arity="3"/>
+ <name name="sort" arity="2" since=""/>
+ <name name="sort" arity="3" since=""/>
<fsummary>Sort terms on files.</fsummary>
<desc>
<p>Sorts terms on files.</p>
diff --git a/lib/stdlib/doc/src/filelib.xml b/lib/stdlib/doc/src/filelib.xml
index 3b5be75bc0..5df415834f 100644
--- a/lib/stdlib/doc/src/filelib.xml
+++ b/lib/stdlib/doc/src/filelib.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>filelib.xml</file>
</header>
- <module>filelib</module>
+ <module since="">filelib</module>
<modulesummary>File utilities, such as wildcard matching of filenames.
</modulesummary>
<description>
@@ -94,7 +94,7 @@
<funcs>
<func>
- <name name="ensure_dir" arity="1"/>
+ <name name="ensure_dir" arity="1" since=""/>
<fsummary>Ensure that all parent directories for a file or directory
exist.</fsummary>
<desc>
@@ -108,7 +108,7 @@
</func>
<func>
- <name name="file_size" arity="1"/>
+ <name name="file_size" arity="1" since=""/>
<fsummary>Return the size in bytes of a file.</fsummary>
<desc>
<p>Returns the size of the specified file.</p>
@@ -116,7 +116,7 @@
</func>
<func>
- <name name="fold_files" arity="5"/>
+ <name name="fold_files" arity="5" since=""/>
<fsummary>Fold over all files matching a regular expression.</fsummary>
<desc>
<p>Folds function <c><anno>Fun</anno></c> over all (regular) files
@@ -142,7 +142,7 @@
</func>
<func>
- <name name="is_dir" arity="1"/>
+ <name name="is_dir" arity="1" since=""/>
<fsummary>Test whether <c>Name</c> refers to a directory.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Name</anno></c>
@@ -151,7 +151,7 @@
</func>
<func>
- <name name="is_file" arity="1"/>
+ <name name="is_file" arity="1" since=""/>
<fsummary>Test whether <c>Name</c> refers to a file or directory.
</fsummary>
<desc>
@@ -161,7 +161,7 @@
</func>
<func>
- <name name="is_regular" arity="1"/>
+ <name name="is_regular" arity="1" since=""/>
<fsummary>Test whether <c>Name</c> refers to a (regular) file.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Name</anno></c>
@@ -170,7 +170,7 @@
</func>
<func>
- <name name="last_modified" arity="1"/>
+ <name name="last_modified" arity="1" since=""/>
<fsummary>Return the local date and time when a file was last modified.
</fsummary>
<desc>
@@ -180,7 +180,7 @@
</func>
<func>
- <name name="wildcard" arity="1"/>
+ <name name="wildcard" arity="1" since=""/>
<fsummary>Match filenames using Unix-style wildcards.</fsummary>
<desc>
<p>Returns a list of all files that match Unix-style wildcard string
@@ -252,7 +252,7 @@ filelib:wildcard("lib/**/*.{erl,hrl}")</code>
</func>
<func>
- <name name="wildcard" arity="2"/>
+ <name name="wildcard" arity="2" since=""/>
<fsummary>Match filenames using Unix-style wildcards starting at a
specified directory.</fsummary>
<desc>
@@ -263,8 +263,8 @@ filelib:wildcard("lib/**/*.{erl,hrl}")</code>
</func>
<func>
- <name name="find_file" arity="2"/>
- <name name="find_file" arity="3"/>
+ <name name="find_file" arity="2" since="OTP 20.0"/>
+ <name name="find_file" arity="3" since="OTP 20.0"/>
<fsummary>Find a file relative to a given directory.</fsummary>
<desc>
<p>Looks for a file of the given name by applying suffix rules to
@@ -278,7 +278,7 @@ filelib:wildcard("lib/**/*.{erl,hrl}")</code>
</desc>
</func>
<func>
- <name name="find_source" arity="1"/>
+ <name name="find_source" arity="1" since="OTP 20.0"/>
<fsummary>Find the source file for a given object file.</fsummary>
<desc>
<p>Equivalent to <c>find_source(Base, Dir)</c>, where <c>Dir</c> is
@@ -287,8 +287,8 @@ filelib:wildcard("lib/**/*.{erl,hrl}")</code>
</desc>
</func>
<func>
- <name name="find_source" arity="2"/>
- <name name="find_source" arity="3"/>
+ <name name="find_source" arity="2" since="OTP 20.0"/>
+ <name name="find_source" arity="3" since="OTP 20.0"/>
<fsummary>Find a source file relative to a given directory.</fsummary>
<desc>
<p>Applies file extension specific rules to find the source file for
diff --git a/lib/stdlib/doc/src/filename.xml b/lib/stdlib/doc/src/filename.xml
index 36254c2d00..60b7eb3436 100644
--- a/lib/stdlib/doc/src/filename.xml
+++ b/lib/stdlib/doc/src/filename.xml
@@ -28,7 +28,7 @@
<date>1997-11-13</date>
<rev>B</rev>
</header>
- <module>filename</module>
+ <module since="">filename</module>
<modulesummary>Filename manipulation functions.</modulesummary>
<description>
<p>This module provides functions
@@ -87,7 +87,7 @@
<funcs>
<func>
- <name name="absname" arity="1"/>
+ <name name="absname" arity="1" since=""/>
<fsummary>Convert a filename to an absolute name, relative the working
directory.</fsummary>
<desc>
@@ -119,7 +119,7 @@
</func>
<func>
- <name name="absname" arity="2"/>
+ <name name="absname" arity="2" since=""/>
<fsummary>Convert a filename to an absolute name, relative a specified
directory.</fsummary>
<desc>
@@ -130,7 +130,7 @@
</func>
<func>
- <name name="absname_join" arity="2"/>
+ <name name="absname_join" arity="2" since=""/>
<fsummary>Join an absolute directory with a relative filename.</fsummary>
<desc>
<p>Joins an absolute directory with a relative filename. Similar to
@@ -144,8 +144,8 @@
</func>
<func>
- <name name="basedir" arity="2" clause_i="1"/>
- <name name="basedir" arity="2" clause_i="2"/>
+ <name name="basedir" arity="2" clause_i="1" since="OTP 19.0"/>
+ <name name="basedir" arity="2" clause_i="2" since="OTP 19.0"/>
<fsummary>Equivalent to <c>basedir(<anno>PathType</anno>,
<anno>Application</anno>,#{})</c> or
<c>basedir(<anno>PathsType</anno>, <anno>Application</anno>,#{})</c>.
@@ -165,8 +165,8 @@ basedir(<anno>PathsType</anno>, <anno>Application</anno>, #{})</seealso>.
</desc>
</func>
<func>
- <name name="basedir" arity="3" clause_i="1" anchor="basedir_3_1"/>
- <name name="basedir" arity="3" clause_i="2" anchor="basedir_3_2"/>
+ <name name="basedir" arity="3" clause_i="1" anchor="basedir_3_1" since="OTP 19.0"/>
+ <name name="basedir" arity="3" clause_i="2" anchor="basedir_3_2" since="OTP 19.0"/>
<fsummary></fsummary>
<type variable="PathType" name_i="1"/>
<type name="basedir_path_type"/>
@@ -314,7 +314,7 @@ true
</desc>
</func>
<func>
- <name name="basename" arity="1"/>
+ <name name="basename" arity="1" since=""/>
<fsummary>Return the last component of a filename.</fsummary>
<desc>
<p>Returns the last component of <c><anno>Filename</anno></c>, or
@@ -332,7 +332,7 @@ true
</func>
<func>
- <name name="basename" arity="2"/>
+ <name name="basename" arity="2" since=""/>
<fsummary>Return the last component of a filename, stripped of the
specified extension.</fsummary>
<desc>
@@ -357,7 +357,7 @@ true
</func>
<func>
- <name name="dirname" arity="1"/>
+ <name name="dirname" arity="1" since=""/>
<fsummary>Return the directory part of a path name.</fsummary>
<desc>
<p>Returns the directory part of <c><anno>Filename</anno></c>.</p>
@@ -374,7 +374,7 @@ true
</func>
<func>
- <name name="extension" arity="1"/>
+ <name name="extension" arity="1" since=""/>
<fsummary>Return the file extension.</fsummary>
<desc>
<p>Returns the file extension of <c><anno>Filename</anno></c>,
@@ -390,8 +390,8 @@ true
</func>
<func>
- <name name="find_src" arity="1"/>
- <name name="find_src" arity="2"/>
+ <name name="find_src" arity="1" since=""/>
+ <name name="find_src" arity="2" since=""/>
<fsummary>Find the filename and compiler options for a module.</fsummary>
<desc>
<p>Finds the source filename and compiler options for a module.
@@ -438,7 +438,7 @@ true
</func>
<func>
- <name name="flatten" arity="1"/>
+ <name name="flatten" arity="1" since=""/>
<fsummary>Convert a filename to a flat string.</fsummary>
<desc>
<p>Converts a possibly deep list filename consisting of
@@ -448,7 +448,7 @@ true
</func>
<func>
- <name name="join" arity="1"/>
+ <name name="join" arity="1" since=""/>
<fsummary>Join a list of filename components with directory separators.
</fsummary>
<desc>
@@ -476,7 +476,7 @@ true
</func>
<func>
- <name name="join" arity="2"/>
+ <name name="join" arity="2" since=""/>
<fsummary>Join two filename components with directory separators.
</fsummary>
<desc>
@@ -487,7 +487,7 @@ true
</func>
<func>
- <name name="nativename" arity="1"/>
+ <name name="nativename" arity="1" since=""/>
<fsummary>Return the native form of a file path.</fsummary>
<desc>
<p>Converts <c><anno>Path</anno></c> to a form accepted by the command
@@ -506,7 +506,7 @@ true
</func>
<func>
- <name name="pathtype" arity="1"/>
+ <name name="pathtype" arity="1" since=""/>
<fsummary>Return the path type.</fsummary>
<desc>
<p>Returns the path type, which is one of the following:</p>
@@ -536,8 +536,8 @@ true
</func>
<func>
- <name name="rootname" arity="1"/>
- <name name="rootname" arity="2"/>
+ <name name="rootname" arity="1" since=""/>
+ <name name="rootname" arity="2" since=""/>
<fsummary>Remove a filename extension.</fsummary>
<desc>
<p>Removes a filename extension. <c>rootname/2</c> works as
@@ -546,7 +546,7 @@ true
<p><em>Examples:</em></p>
<pre>
20> <input>filename:rootname("/beam.src/kalle").</input>
-/beam.src/kalle"
+"/beam.src/kalle"
21> <input>filename:rootname("/beam.src/foo.erl").</input>
"/beam.src/foo"
22> <input>filename:rootname("/beam.src/foo.erl", ".erl").</input>
@@ -557,7 +557,7 @@ true
</func>
<func>
- <name name="safe_relative_path" arity="1"/>
+ <name name="safe_relative_path" arity="1" since="OTP 19.3"/>
<fsummary>Sanitize a relative path to avoid directory traversal attacks.</fsummary>
<desc>
<p>Sanitizes the relative path by eliminating ".." and "."
@@ -584,7 +584,7 @@ unsafe</pre>
</func>
<func>
- <name name="split" arity="1"/>
+ <name name="split" arity="1" since=""/>
<fsummary>Split a filename into its path components.</fsummary>
<desc>
<p>Returns a list whose elements are the path components of
diff --git a/lib/stdlib/doc/src/gb_sets.xml b/lib/stdlib/doc/src/gb_sets.xml
index 03397b4503..a9596c6e4d 100644
--- a/lib/stdlib/doc/src/gb_sets.xml
+++ b/lib/stdlib/doc/src/gb_sets.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>gb_sets</module>
+ <module since="">gb_sets</module>
<modulesummary>General balanced trees.</modulesummary>
<description>
<p>This module provides ordered sets using Prof. Arne Andersson's
@@ -123,8 +123,8 @@
<funcs>
<func>
- <name name="add" arity="2"/>
- <name name="add_element" arity="2"/>
+ <name name="add" arity="2" since=""/>
+ <name name="add_element" arity="2" since=""/>
<fsummary>Add a (possibly existing) element to a set.</fsummary>
<desc>
<p>Returns a new set formed from <c><anno>Set1</anno></c> with
@@ -135,7 +135,7 @@
</func>
<func>
- <name name="balance" arity="1"/>
+ <name name="balance" arity="1" since=""/>
<fsummary>Rebalance tree representation of a set.</fsummary>
<desc>
<p>Rebalances the tree representation of <c><anno>Set1</anno></c>.
@@ -149,7 +149,7 @@
</func>
<func>
- <name name="del_element" arity="2"/>
+ <name name="del_element" arity="2" since=""/>
<fsummary>Remove a (possibly non-existing) element from a set.</fsummary>
<desc>
<p>Returns a new set formed from <c><anno>Set1</anno></c> with
@@ -160,7 +160,7 @@
</func>
<func>
- <name name="delete" arity="2"/>
+ <name name="delete" arity="2" since=""/>
<fsummary>Remove an element from a set.</fsummary>
<desc>
<p>Returns a new set formed from <c><anno>Set1</anno></c> with
@@ -171,7 +171,7 @@
</func>
<func>
- <name name="delete_any" arity="2"/>
+ <name name="delete_any" arity="2" since=""/>
<fsummary>Remove a (possibly non-existing) element from a set.</fsummary>
<desc>
<p>Returns a new set formed from <c><anno>Set1</anno></c> with
@@ -182,7 +182,7 @@
</func>
<func>
- <name name="difference" arity="2"/>
+ <name name="difference" arity="2" since=""/>
<fsummary>Return the difference of two sets.</fsummary>
<desc>
<p>Returns only the elements of <c><anno>Set1</anno></c> that are not
@@ -191,7 +191,7 @@
</func>
<func>
- <name name="empty" arity="0"/>
+ <name name="empty" arity="0" since=""/>
<fsummary>Return an empty set.</fsummary>
<desc>
<p>Returns a new empty set.</p>
@@ -199,7 +199,7 @@
</func>
<func>
- <name name="filter" arity="2"/>
+ <name name="filter" arity="2" since=""/>
<fsummary>Filter set elements.</fsummary>
<desc>
<p>Filters elements in <c><anno>Set1</anno></c> using predicate function
@@ -208,7 +208,7 @@
</func>
<func>
- <name name="fold" arity="3"/>
+ <name name="fold" arity="3" since=""/>
<fsummary>Fold over set elements.</fsummary>
<desc>
<p>Folds <c><anno>Function</anno></c> over every element in
@@ -218,7 +218,7 @@
</func>
<func>
- <name name="from_list" arity="1"/>
+ <name name="from_list" arity="1" since=""/>
<fsummary>Convert a list into a set.</fsummary>
<desc>
<p>Returns a set of the elements in <c><anno>List</anno></c>, where
@@ -227,7 +227,7 @@
</func>
<func>
- <name name="from_ordset" arity="1"/>
+ <name name="from_ordset" arity="1" since=""/>
<fsummary>Make a set from an ordset list.</fsummary>
<desc>
<p>Turns an ordered-set list <c><anno>List</anno></c> into a set.
@@ -236,7 +236,7 @@
</func>
<func>
- <name name="insert" arity="2"/>
+ <name name="insert" arity="2" since=""/>
<fsummary>Add a new element to a set.</fsummary>
<desc>
<p>Returns a new set formed from <c><anno>Set1</anno></c> with
@@ -247,7 +247,7 @@
</func>
<func>
- <name name="intersection" arity="1"/>
+ <name name="intersection" arity="1" since=""/>
<fsummary>Return the intersection of a list of sets.</fsummary>
<desc>
<p>Returns the intersection of the non-empty list of sets.</p>
@@ -255,7 +255,7 @@
</func>
<func>
- <name name="intersection" arity="2"/>
+ <name name="intersection" arity="2" since=""/>
<fsummary>Return the intersection of two sets.</fsummary>
<desc>
<p>Returns the intersection of <c><anno>Set1</anno></c> and
@@ -264,7 +264,7 @@
</func>
<func>
- <name name="is_disjoint" arity="2"/>
+ <name name="is_disjoint" arity="2" since=""/>
<fsummary>Check whether two sets are disjoint.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Set1</anno></c> and
@@ -274,7 +274,7 @@
</func>
<func>
- <name name="is_element" arity="2"/>
+ <name name="is_element" arity="2" since=""/>
<fsummary>Test for membership of a set.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Element</anno></c> is an element of
@@ -283,7 +283,7 @@
</func>
<func>
- <name name="is_empty" arity="1"/>
+ <name name="is_empty" arity="1" since=""/>
<fsummary>Test for empty set.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Set</anno></c> is an empty set,
@@ -292,7 +292,7 @@
</func>
<func>
- <name name="is_member" arity="2"/>
+ <name name="is_member" arity="2" since=""/>
<fsummary>Test for membership of a set.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Element</anno></c> is an element of
@@ -301,7 +301,7 @@
</func>
<func>
- <name name="is_set" arity="1"/>
+ <name name="is_set" arity="1" since=""/>
<fsummary>Test for a set.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> appears to be a set,
@@ -310,7 +310,7 @@
</func>
<func>
- <name name="is_subset" arity="2"/>
+ <name name="is_subset" arity="2" since=""/>
<fsummary>Test for subset.</fsummary>
<desc>
<p>Returns <c>true</c> when every element of <c><anno>Set1</anno></c> is
@@ -319,7 +319,7 @@
</func>
<func>
- <name name="iterator" arity="1"/>
+ <name name="iterator" arity="1" since=""/>
<fsummary>Return an iterator for a set.</fsummary>
<desc>
<p>Returns an iterator that can be used for traversing the entries of
@@ -336,7 +336,7 @@
</func>
<func>
- <name name="iterator_from" arity="2"/>
+ <name name="iterator_from" arity="2" since="OTP 18.0"/>
<fsummary>Return an iterator for a set starting from a specified element.
</fsummary>
<desc>
@@ -351,7 +351,7 @@
</func>
<func>
- <name name="largest" arity="1"/>
+ <name name="largest" arity="1" since=""/>
<fsummary>Return largest element.</fsummary>
<desc>
<p>Returns the largest element in <c><anno>Set</anno></c>. Assumes that
@@ -360,7 +360,7 @@
</func>
<func>
- <name name="new" arity="0"/>
+ <name name="new" arity="0" since=""/>
<fsummary>Return an empty set.</fsummary>
<desc>
<p>Returns a new empty set.</p>
@@ -368,7 +368,7 @@
</func>
<func>
- <name name="next" arity="1"/>
+ <name name="next" arity="1" since=""/>
<fsummary>Traverse a set with an iterator.</fsummary>
<desc>
<p>Returns <c>{<anno>Element</anno>, <anno>Iter2</anno>}</c>, where
@@ -381,7 +381,7 @@
</func>
<func>
- <name name="singleton" arity="1"/>
+ <name name="singleton" arity="1" since=""/>
<fsummary>Return a set with one element.</fsummary>
<desc>
<p>Returns a set containing only element <c><anno>Element</anno></c>.
@@ -390,7 +390,7 @@
</func>
<func>
- <name name="size" arity="1"/>
+ <name name="size" arity="1" since=""/>
<fsummary>Return the number of elements in a set.</fsummary>
<desc>
<p>Returns the number of elements in <c><anno>Set</anno></c>.</p>
@@ -398,7 +398,7 @@
</func>
<func>
- <name name="smallest" arity="1"/>
+ <name name="smallest" arity="1" since=""/>
<fsummary>Return smallest element.</fsummary>
<desc>
<p>Returns the smallest element in <c><anno>Set</anno></c>. Assumes that
@@ -407,7 +407,7 @@
</func>
<func>
- <name name="subtract" arity="2"/>
+ <name name="subtract" arity="2" since=""/>
<fsummary>Return the difference of two sets.</fsummary>
<desc>
<p>Returns only the elements of <c><anno>Set1</anno></c> that are not
@@ -416,7 +416,7 @@
</func>
<func>
- <name name="take_largest" arity="1"/>
+ <name name="take_largest" arity="1" since=""/>
<fsummary>Extract largest element.</fsummary>
<desc>
<p>Returns <c>{<anno>Element</anno>, <anno>Set2</anno>}</c>, where
@@ -428,7 +428,7 @@
</func>
<func>
- <name name="take_smallest" arity="1"/>
+ <name name="take_smallest" arity="1" since=""/>
<fsummary>Extract smallest element.</fsummary>
<desc>
<p>Returns <c>{<anno>Element</anno>, <anno>Set2</anno>}</c>, where
@@ -440,7 +440,7 @@
</func>
<func>
- <name name="to_list" arity="1"/>
+ <name name="to_list" arity="1" since=""/>
<fsummary>Convert a set into a list.</fsummary>
<desc>
<p>Returns the elements of <c><anno>Set</anno></c> as a list.</p>
@@ -448,7 +448,7 @@
</func>
<func>
- <name name="union" arity="1"/>
+ <name name="union" arity="1" since=""/>
<fsummary>Return the union of a list of sets.</fsummary>
<desc>
<p>Returns the merged (union) set of the list of sets.</p>
@@ -456,7 +456,7 @@
</func>
<func>
- <name name="union" arity="2"/>
+ <name name="union" arity="2" since=""/>
<fsummary>Return the union of two sets.</fsummary>
<desc>
<p>Returns the merged (union) set of <c><anno>Set1</anno></c> and
diff --git a/lib/stdlib/doc/src/gb_trees.xml b/lib/stdlib/doc/src/gb_trees.xml
index 5cfff021c1..570c9c7cb6 100644
--- a/lib/stdlib/doc/src/gb_trees.xml
+++ b/lib/stdlib/doc/src/gb_trees.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>gb_trees</module>
+ <module since="">gb_trees</module>
<modulesummary>General balanced trees.</modulesummary>
<description>
<p>This module provides Prof. Arne Andersson's General
@@ -75,7 +75,7 @@
<funcs>
<func>
- <name name="balance" arity="1"/>
+ <name name="balance" arity="1" since=""/>
<fsummary>Rebalance a tree.</fsummary>
<desc>
<p>Rebalances <c><anno>Tree1</anno></c>. Notice that this is
@@ -88,7 +88,7 @@
</func>
<func>
- <name name="delete" arity="2"/>
+ <name name="delete" arity="2" since=""/>
<fsummary>Remove a node from a tree.</fsummary>
<desc>
<p>Removes the node with key <c><anno>Key</anno></c> from
@@ -98,7 +98,7 @@
</func>
<func>
- <name name="delete_any" arity="2"/>
+ <name name="delete_any" arity="2" since=""/>
<fsummary>Remove a (possibly non-existing) node from a tree.</fsummary>
<desc>
<p>Removes the node with key <c><anno>Key</anno></c> from
@@ -109,7 +109,7 @@
</func>
<func>
- <name name="take" arity="2"/>
+ <name name="take" arity="2" since="OTP 20.0"/>
<fsummary>Returns a value and new tree without node with key <c>Key</c>.</fsummary>
<desc>
<p>Returns a value <c><anno>Value</anno></c> from node with key <c><anno>Key</anno></c>
@@ -120,7 +120,7 @@
</func>
<func>
- <name name="take_any" arity="2"/>
+ <name name="take_any" arity="2" since="OTP 20.0"/>
<fsummary>Returns a value and new tree without node with key <c>Key</c>.</fsummary>
<desc>
<p>Returns a value <c><anno>Value</anno></c> from node with key <c><anno>Key</anno></c>
@@ -131,7 +131,7 @@
</func>
<func>
- <name name="empty" arity="0"/>
+ <name name="empty" arity="0" since=""/>
<fsummary>Return an empty tree.</fsummary>
<desc>
<p>Returns a new empty tree.</p>
@@ -139,7 +139,7 @@
</func>
<func>
- <name name="enter" arity="3"/>
+ <name name="enter" arity="3" since=""/>
<fsummary>Insert or update key with value in a tree.</fsummary>
<desc>
<p>Inserts <c><anno>Key</anno></c> with value <c><anno>Value</anno></c>
@@ -151,7 +151,7 @@
</func>
<func>
- <name name="from_orddict" arity="1"/>
+ <name name="from_orddict" arity="1" since=""/>
<fsummary>Make a tree from an orddict.</fsummary>
<desc>
<p>Turns an ordered list <c><anno>List</anno></c> of key-value tuples
@@ -160,7 +160,7 @@
</func>
<func>
- <name name="get" arity="2"/>
+ <name name="get" arity="2" since=""/>
<fsummary>Look up a key in a tree, if present.</fsummary>
<desc>
<p>Retrieves the value stored with <c><anno>Key</anno></c> in
@@ -171,7 +171,7 @@
</func>
<func>
- <name name="insert" arity="3"/>
+ <name name="insert" arity="3" since=""/>
<fsummary>Insert a new key and value in a tree.</fsummary>
<desc>
<p>Inserts <c><anno>Key</anno></c> with value <c><anno>Value</anno></c>
@@ -182,7 +182,7 @@
</func>
<func>
- <name name="is_defined" arity="2"/>
+ <name name="is_defined" arity="2" since=""/>
<fsummary>Test for membership of a tree.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Key</anno></c> is present in
@@ -191,7 +191,7 @@
</func>
<func>
- <name name="is_empty" arity="1"/>
+ <name name="is_empty" arity="1" since=""/>
<fsummary>Test for empty tree.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Tree</anno></c> is an empty tree,
@@ -200,7 +200,7 @@
</func>
<func>
- <name name="iterator" arity="1"/>
+ <name name="iterator" arity="1" since=""/>
<fsummary>Return an iterator for a tree.</fsummary>
<desc>
<p>Returns an iterator that can be used for traversing the
@@ -218,7 +218,7 @@
</func>
<func>
- <name name="iterator_from" arity="2"/>
+ <name name="iterator_from" arity="2" since="OTP 18.0"/>
<fsummary>Return an iterator for a tree starting from a specified key.
</fsummary>
<desc>
@@ -233,7 +233,7 @@
</func>
<func>
- <name name="keys" arity="1"/>
+ <name name="keys" arity="1" since=""/>
<fsummary>Return a list of the keys in a tree.</fsummary>
<desc>
<p>Returns the keys in <c><anno>Tree</anno></c> as an ordered list.</p>
@@ -241,7 +241,7 @@
</func>
<func>
- <name name="largest" arity="1"/>
+ <name name="largest" arity="1" since=""/>
<fsummary>Return largest key and value.</fsummary>
<desc>
<p>Returns <c>{<anno>Key</anno>, <anno>Value</anno>}</c>, where
@@ -253,7 +253,7 @@
</func>
<func>
- <name name="lookup" arity="2"/>
+ <name name="lookup" arity="2" since=""/>
<fsummary>Look up a key in a tree.</fsummary>
<desc>
<p>Looks up <c><anno>Key</anno></c> in <c><anno>Tree</anno></c>.
@@ -263,7 +263,7 @@
</func>
<func>
- <name name="map" arity="2"/>
+ <name name="map" arity="2" since=""/>
<fsummary>Return largest key and value.</fsummary>
<desc>
<p>Maps function F(<anno>K</anno>, <anno>V1</anno>) -> <anno>V2</anno>
@@ -275,7 +275,7 @@
</func>
<func>
- <name name="next" arity="1"/>
+ <name name="next" arity="1" since=""/>
<fsummary>Traverse a tree with an iterator.</fsummary>
<desc>
<p>Returns <c>{<anno>Key</anno>, <anno>Value</anno>,
@@ -288,7 +288,7 @@
</func>
<func>
- <name name="size" arity="1"/>
+ <name name="size" arity="1" since=""/>
<fsummary>Return the number of nodes in a tree.</fsummary>
<desc>
<p>Returns the number of nodes in <c><anno>Tree</anno></c>.</p>
@@ -296,7 +296,7 @@
</func>
<func>
- <name name="smallest" arity="1"/>
+ <name name="smallest" arity="1" since=""/>
<fsummary>Return smallest key and value.</fsummary>
<desc>
<p>Returns <c>{<anno>Key</anno>, <anno>Value</anno>}</c>, where
@@ -308,7 +308,7 @@
</func>
<func>
- <name name="take_largest" arity="1"/>
+ <name name="take_largest" arity="1" since=""/>
<fsummary>Extract largest key and value.</fsummary>
<desc>
<p>Returns <c>{<anno>Key</anno>, <anno>Value</anno>,
@@ -321,7 +321,7 @@
</func>
<func>
- <name name="take_smallest" arity="1"/>
+ <name name="take_smallest" arity="1" since=""/>
<fsummary>Extract smallest key and value.</fsummary>
<desc>
<p>Returns <c>{<anno>Key</anno>, <anno>Value</anno>,
@@ -334,7 +334,7 @@
</func>
<func>
- <name name="to_list" arity="1"/>
+ <name name="to_list" arity="1" since=""/>
<fsummary>Convert a tree into a list.</fsummary>
<desc>
<p>Converts a tree into an ordered list of key-value tuples.</p>
@@ -342,7 +342,7 @@
</func>
<func>
- <name name="update" arity="3"/>
+ <name name="update" arity="3" since=""/>
<fsummary>Update a key to new value in a tree.</fsummary>
<desc>
<p>Updates <c><anno>Key</anno></c> to value <c><anno>Value</anno></c>
@@ -352,7 +352,7 @@
</func>
<func>
- <name name="values" arity="1"/>
+ <name name="values" arity="1" since=""/>
<fsummary>Return a list of the values in a tree.</fsummary>
<desc>
<p>Returns the values in <c><anno>Tree</anno></c> as an ordered list,
diff --git a/lib/stdlib/doc/src/gen_event.xml b/lib/stdlib/doc/src/gen_event.xml
index fc34e51216..2915c4f507 100644
--- a/lib/stdlib/doc/src/gen_event.xml
+++ b/lib/stdlib/doc/src/gen_event.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>gen_event</module>
+ <module since="">gen_event</module>
<modulesummary>Generic event handling behavior.</modulesummary>
<description>
<p>This behavior module provides event handling functionality. It
@@ -130,7 +130,7 @@ gen_event:stop -----> Module:terminate/2
<funcs>
<func>
- <name>add_handler(EventMgrRef, Handler, Args) -> Result</name>
+ <name since="">add_handler(EventMgrRef, Handler, Args) -> Result</name>
<fsummary>Add an event handler to a generic event manager.</fsummary>
<type>
<v>EventMgrRef = Name | {Name,Node} | {global,GlobalName}
@@ -178,7 +178,7 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>add_sup_handler(EventMgrRef, Handler, Args) -> Result</name>
+ <name since="">add_sup_handler(EventMgrRef, Handler, Args) -> Result</name>
<fsummary>Add a supervised event handler to a generic event manager.
</fsummary>
<type>
@@ -241,8 +241,8 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>call(EventMgrRef, Handler, Request) -> Result</name>
- <name>call(EventMgrRef, Handler, Request, Timeout) -> Result</name>
+ <name since="">call(EventMgrRef, Handler, Request) -> Result</name>
+ <name since="">call(EventMgrRef, Handler, Request, Timeout) -> Result</name>
<fsummary>Make a synchronous call to a generic event manager.</fsummary>
<type>
<v>EventMgrRef = Name | {Name,Node} | {global,GlobalName}
@@ -285,7 +285,7 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>delete_handler(EventMgrRef, Handler, Args) -> Result</name>
+ <name since="">delete_handler(EventMgrRef, Handler, Args) -> Result</name>
<fsummary>Delete an event handler from a generic event manager.</fsummary>
<type>
<v>EventMgrRef = Name | {Name,Node} | {global,GlobalName}
@@ -318,8 +318,8 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>notify(EventMgrRef, Event) -> ok</name>
- <name>sync_notify(EventMgrRef, Event) -> ok</name>
+ <name since="">notify(EventMgrRef, Event) -> ok</name>
+ <name since="">sync_notify(EventMgrRef, Event) -> ok</name>
<fsummary>Notify an event manager about an event.</fsummary>
<type>
<v>EventMgrRef = Name | {Name,Node} | {global,GlobalName}
@@ -349,9 +349,9 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>start() -> Result</name>
- <name>start(EventMgrName | Options) -> Result</name>
- <name>start(EventMgrName, Options) -> Result</name>
+ <name since="">start() -> Result</name>
+ <name since="">start(EventMgrName | Options) -> Result</name>
+ <name since="OTP 20.0">start(EventMgrName, Options) -> Result</name>
<fsummary>Create a stand-alone event manager process.</fsummary>
<type>
<v>EventMgrName = {local,Name} | {global,GlobalName} | {via,Module,ViaName}</v>
@@ -375,9 +375,9 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>start_link() -> Result</name>
- <name>start_link(EventMgrName | Options) -> Result</name>
- <name>start_link(EventMgrName, Options) -> Result</name>
+ <name since="">start_link() -> Result</name>
+ <name since="">start_link(EventMgrName | Options) -> Result</name>
+ <name since="OTP 20.0">start_link(EventMgrName, Options) -> Result</name>
<fsummary>Create a generic event manager process in a supervision tree.
</fsummary>
<type>
@@ -436,8 +436,8 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>stop(EventMgrRef) -> ok</name>
- <name>stop(EventMgrRef, Reason, Timeout) -> ok</name>
+ <name since="">stop(EventMgrRef) -> ok</name>
+ <name since="OTP 18.0">stop(EventMgrRef, Reason, Timeout) -> ok</name>
<fsummary>Terminate a generic event manager.</fsummary>
<type>
<v>EventMgrRef = Name | {Name,Node} | {global,GlobalName}
@@ -474,7 +474,7 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>swap_handler(EventMgrRef, {Handler1,Args1}, {Handler2,Args2}) -> Result</name>
+ <name since="">swap_handler(EventMgrRef, {Handler1,Args1}, {Handler2,Args2}) -> Result</name>
<fsummary>Replace an event handler in a generic event manager.</fsummary>
<type>
<v>EventMgrRef = Name | {Name,Node} | {global,GlobalName}
@@ -521,7 +521,7 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>swap_sup_handler(EventMgrRef, {Handler1,Args1}, {Handler2,Args2}) -> Result</name>
+ <name since="">swap_sup_handler(EventMgrRef, {Handler1,Args1}, {Handler2,Args2}) -> Result</name>
<fsummary>Replace an event handler in a generic event manager.</fsummary>
<type>
<v>EventMgrRef = Name | {Name,Node} | {global,GlobalName}
@@ -546,7 +546,7 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>which_handlers(EventMgrRef) -> [Handler]</name>
+ <name since="">which_handlers(EventMgrRef) -> [Handler]</name>
<fsummary>Return all event handlers installed in a generic event manager.
</fsummary>
<type>
@@ -575,7 +575,7 @@ gen_event:stop -----> Module:terminate/2
<funcs>
<func>
- <name>Module:code_change(OldVsn, State, Extra) -> {ok, NewState}</name>
+ <name since="">Module:code_change(OldVsn, State, Extra) -> {ok, NewState}</name>
<fsummary>Update the internal state during upgrade/downgrade.</fsummary>
<type>
<v>OldVsn = Vsn | {down, Vsn}</v>
@@ -611,7 +611,7 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>Module:format_status(Opt, [PDict, State]) -> Status</name>
+ <name since="OTP R14B">Module:format_status(Opt, [PDict, State]) -> Status</name>
<fsummary>Optional function for providing a term describing the
current event handler state.</fsummary>
<type>
@@ -667,7 +667,7 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>Module:handle_call(Request, State) -> Result</name>
+ <name since="">Module:handle_call(Request, State) -> Result</name>
<fsummary>Handle a synchronous request.</fsummary>
<type>
<v>Request = term()</v>
@@ -698,7 +698,7 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>Module:handle_event(Event, State) -> Result</name>
+ <name since="">Module:handle_event(Event, State) -> Result</name>
<fsummary>Handle an event.</fsummary>
<type>
<v>Event = term()</v>
@@ -756,7 +756,7 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>Module:handle_info(Info, State) -> Result</name>
+ <name since="">Module:handle_info(Info, State) -> Result</name>
<fsummary>Handle an incoming message.</fsummary>
<type>
<v>Info = term()</v>
@@ -788,7 +788,7 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>Module:init(InitArgs) -> {ok,State} | {ok,State,hibernate} | {error,Reason}</name>
+ <name since="">Module:init(InitArgs) -> {ok,State} | {ok,State,hibernate} | {error,Reason}</name>
<fsummary>Initialize an event handler.</fsummary>
<type>
<v>InitArgs = Args | {Args,Term}</v>
@@ -825,7 +825,7 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>Module:terminate(Arg, State) -> term()</name>
+ <name since="">Module:terminate(Arg, State) -> term()</name>
<fsummary>Clean up before deletion.</fsummary>
<type>
<v>Arg = Args | {stop,Reason} | stop | remove_handler</v>
diff --git a/lib/stdlib/doc/src/gen_server.xml b/lib/stdlib/doc/src/gen_server.xml
index 106bda85f5..a4554d7657 100644
--- a/lib/stdlib/doc/src/gen_server.xml
+++ b/lib/stdlib/doc/src/gen_server.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>gen_server</module>
+ <module since="">gen_server</module>
<modulesummary>Generic server behavior.</modulesummary>
<description>
<p>This behavior module provides the server of a client-server
@@ -101,8 +101,8 @@ gen_server:abcast -----> Module:handle_cast/2
<funcs>
<func>
- <name>abcast(Name, Request) -> abcast</name>
- <name>abcast(Nodes, Name, Request) -> abcast</name>
+ <name since="">abcast(Name, Request) -> abcast</name>
+ <name since="">abcast(Nodes, Name, Request) -> abcast</name>
<fsummary>Send an asynchronous request to many generic servers.</fsummary>
<type>
<v>Nodes = [Node]</v>
@@ -124,8 +124,8 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>call(ServerRef, Request) -> Reply</name>
- <name>call(ServerRef, Request, Timeout) -> Reply</name>
+ <name since="">call(ServerRef, Request) -> Reply</name>
+ <name since="">call(ServerRef, Request, Timeout) -> Reply</name>
<fsummary>Make a synchronous call to a generic server.</fsummary>
<type>
<v>ServerRef = Name | {Name,Node} | {global,GlobalName}</v>
@@ -175,7 +175,7 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>cast(ServerRef, Request) -> ok</name>
+ <name since="">cast(ServerRef, Request) -> ok</name>
<fsummary>Send an asynchronous request to a generic server.</fsummary>
<type>
<v>ServerRef = Name | {Name,Node} | {global,GlobalName}</v>
@@ -200,10 +200,10 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>enter_loop(Module, Options, State)</name>
- <name>enter_loop(Module, Options, State, ServerName)</name>
- <name>enter_loop(Module, Options, State, Timeout)</name>
- <name>enter_loop(Module, Options, State, ServerName, Timeout)</name>
+ <name since="">enter_loop(Module, Options, State)</name>
+ <name since="">enter_loop(Module, Options, State, ServerName)</name>
+ <name since="">enter_loop(Module, Options, State, Timeout)</name>
+ <name since="">enter_loop(Module, Options, State, ServerName, Timeout)</name>
<fsummary>Enter the <c>gen_server</c> receive loop.</fsummary>
<type>
<v>Module = atom()</v>
@@ -248,9 +248,9 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>multi_call(Name, Request) -> Result</name>
- <name>multi_call(Nodes, Name, Request) -> Result</name>
- <name>multi_call(Nodes, Name, Request, Timeout) -> Result</name>
+ <name since="">multi_call(Name, Request) -> Result</name>
+ <name since="">multi_call(Nodes, Name, Request) -> Result</name>
+ <name since="">multi_call(Nodes, Name, Request, Timeout) -> Result</name>
<fsummary>Make a synchronous call to many generic servers.</fsummary>
<type>
<v>Nodes = [Node]</v>
@@ -307,7 +307,7 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>reply(Client, Reply) -> Result</name>
+ <name since="">reply(Client, Reply) -> Result</name>
<fsummary>Send a reply to a client.</fsummary>
<type>
<v>Client - see below</v>
@@ -332,8 +332,8 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>start(Module, Args, Options) -> Result</name>
- <name>start(ServerName, Module, Args, Options) -> Result</name>
+ <name since="">start(Module, Args, Options) -> Result</name>
+ <name since="">start(ServerName, Module, Args, Options) -> Result</name>
<fsummary>Create a standalone <c>gen_server</c> process.</fsummary>
<type>
<v>ServerName = {local,Name} | {global,GlobalName}</v>
@@ -361,8 +361,8 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>start_link(Module, Args, Options) -> Result</name>
- <name>start_link(ServerName, Module, Args, Options) -> Result</name>
+ <name since="">start_link(Module, Args, Options) -> Result</name>
+ <name since="">start_link(ServerName, Module, Args, Options) -> Result</name>
<fsummary>Create a <c>gen_server</c> process in a supervision tree.
</fsummary>
<type>
@@ -466,8 +466,8 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>stop(ServerRef) -> ok</name>
- <name>stop(ServerRef, Reason, Timeout) -> ok</name>
+ <name since="OTP 18.0">stop(ServerRef) -> ok</name>
+ <name since="OTP 18.0">stop(ServerRef, Reason, Timeout) -> ok</name>
<fsummary>Synchronously stop a generic server.</fsummary>
<type>
<v>ServerRef = Name | {Name,Node} | {global,GlobalName}</v>
@@ -508,7 +508,7 @@ gen_server:abcast -----> Module:handle_cast/2
<funcs>
<func>
- <name>Module:code_change(OldVsn, State, Extra) -> {ok, NewState} | {error, Reason}</name>
+ <name since="">Module:code_change(OldVsn, State, Extra) -> {ok, NewState} | {error, Reason}</name>
<fsummary>Update the internal state during upgrade/downgrade.</fsummary>
<type>
<v>OldVsn = Vsn | {down, Vsn}</v>
@@ -550,7 +550,7 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>Module:format_status(Opt, [PDict, State]) -> Status</name>
+ <name since="OTP R13B04">Module:format_status(Opt, [PDict, State]) -> Status</name>
<fsummary>Optional function for providing a term describing the
current <c>gen_server</c> status.</fsummary>
<type>
@@ -610,7 +610,7 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>Module:handle_call(Request, From, State) -> Result</name>
+ <name since="">Module:handle_call(Request, From, State) -> Result</name>
<fsummary>Handle a synchronous request.</fsummary>
<type>
<v>Request = term()</v>
@@ -677,7 +677,7 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>Module:handle_cast(Request, State) -> Result</name>
+ <name since="">Module:handle_cast(Request, State) -> Result</name>
<fsummary>Handle an asynchronous request.</fsummary>
<type>
<v>Request = term()</v>
@@ -703,7 +703,7 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>Module:handle_continue(Continue, State) -> Result</name>
+ <name since="OTP 21.0">Module:handle_continue(Continue, State) -> Result</name>
<fsummary>Handle a continue instruction.</fsummary>
<type>
<v>Continue = term()</v>
@@ -738,7 +738,7 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>Module:handle_info(Info, State) -> Result</name>
+ <name since="">Module:handle_info(Info, State) -> Result</name>
<fsummary>Handle an incoming message.</fsummary>
<type>
<v>Info = timeout | term()</v>
@@ -770,7 +770,7 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>Module:init(Args) -> Result</name>
+ <name since="">Module:init(Args) -> Result</name>
<fsummary>Initialize process and internal state.</fsummary>
<type>
<v>Args = term()</v>
@@ -811,7 +811,7 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>Module:terminate(Reason, State)</name>
+ <name since="">Module:terminate(Reason, State)</name>
<fsummary>Clean up before termination.</fsummary>
<type>
<v>Reason = normal | shutdown | {shutdown,term()} | term()</v>
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml
index a808d3af55..0cad5929ca 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>2018</year>
+ <year>2016</year><year>2019</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>gen_statem</module>
+ <module since="OTP 19.0">gen_statem</module>
<modulesummary>Generic state machine behavior.</modulesummary>
<description>
<p>
@@ -167,7 +167,7 @@ erlang:'!' -----> Module:StateName/3
</p>
<marker id="state callback"/>
<p>
- The "<em>state callback</em>" for a specific
+ The <em>state callback</em> for a specific
<seealso marker="#type-state">state</seealso>
in a <c>gen_statem</c> is the callback function that is called
for all events in this state. It is selected depending on which
@@ -179,7 +179,7 @@ erlang:'!' -----> Module:StateName/3
When the
<seealso marker="#type-callback_mode"><em>callback mode</em></seealso>
is <c>state_functions</c>, the state must be an atom and
- is used as the state callback name; see
+ is used as the <em>state callback</em> name; see
<seealso marker="#Module:StateName/3"><c>Module:StateName/3</c></seealso>.
This co-locates all code for a specific state
in one function as the <c>gen_statem</c> engine
@@ -192,7 +192,7 @@ erlang:'!' -----> Module:StateName/3
When the
<seealso marker="#type-callback_mode"><em>callback mode</em></seealso>
is <c>handle_event_function</c>, the state can be any term
- and the state callback name is
+ and the <em>state callback</em> name is
<seealso marker="#Module:handle_event/4"><c>Module:handle_event/4</c></seealso>.
This makes it easy to branch depending on state or event as you desire.
Be careful about which events you handle in which
@@ -200,12 +200,36 @@ erlang:'!' -----> Module:StateName/3
forever creating an infinite busy loop.
</p>
<p>
- The <c>gen_statem</c> enqueues incoming events in order of arrival
- and presents these to the
- <seealso marker="#state callback">state callback</seealso>
- in that order. The state callback can postpone an event
- so it is not retried in the current state.
- After a state change the queue restarts with the postponed events.
+ When <c>gen_statem</c> receives a process message it is
+ converted into an event and the
+ <seealso marker="#state callback"><em>state callback</em></seealso>
+ is called with the event as two arguments: type and content.
+ When the
+ <seealso marker="#state callback"><em>state callback</em></seealso>
+ has processed the event it returns to <c>gen_statem</c>
+ which does a <em>state transition</em>.
+ If this <em>state transition</em> is to a different state,
+ that is: <c>NextState =/= State</c>, it is a <em>state change</em>.
+ </p>
+ <p>
+ The
+ <seealso marker="#state callback"><em>state callback</em></seealso>
+ may return
+ <seealso marker="#type-action"><em>transition actions</em></seealso>
+ for <c>gen_statem</c>
+ to execute during the <em>state transition</em>,
+ for example to reply to a
+ <seealso marker="#call/2"><c>gen_statem:call/2,3</c></seealso>.
+ </p>
+ <p>
+ One of the possible <em>transition actions</em>
+ is to postpone the current event.
+ Then it is not retried in the current state.
+ The <c>gen_statem</c> engine keeps a queue of events
+ divided into the postponed events
+ and the events still to process.
+ After a <em>state change</em> the queue restarts
+ with the postponed events.
</p>
<p>
The <c>gen_statem</c> event queue model is sufficient
@@ -215,13 +239,17 @@ erlang:'!' -----> Module:StateName/3
to entering a new receive statement.
</p>
<p>
- The <seealso marker="#state callback">state callback</seealso>
+ The
+ <seealso marker="#state callback"><em>state callback</em></seealso>
can insert events using the
- <seealso marker="#type-action"><c>action()</c></seealso>
+ <seealso marker="#type-action"><em>transition actions</em></seealso>
<c>next_event</c>
- and such an event is inserted as the next to present
- to the state callback. That is, as if it is
- the oldest incoming event. A dedicated
+ and such an event is inserted in the event queue
+ as the next to call the
+ <seealso marker="#state callback"><em>state callback</em></seealso>
+ with.
+ That is, as if it is the oldest incoming event.
+ A dedicated
<seealso marker="#type-event_type"><c>event_type()</c></seealso>
<c>internal</c> can be used for such events making them impossible
to mistake for external events.
@@ -236,24 +264,26 @@ erlang:'!' -----> Module:StateName/3
<p>
The <c>gen_statem</c> engine can automatically
make a specialized call to the
- <seealso marker="#state callback">state callback</seealso>
+ <seealso marker="#state callback"><em>state callback</em></seealso>
whenever a new state is entered; see
<seealso marker="#type-state_enter"><c>state_enter()</c></seealso>.
This is for writing code common to all state entries.
- Another way to do it is to insert an event at the state transition,
- and/or to use a dedicated state transition function,
+ Another way to do it is to explicitly insert an event
+ at the <em>state transition</em>,
+ and/or to use a dedicated <em>state transition</em> function,
but that is something you will have to remember
- at every state transition to the state(s) that need it.
+ at every <em>state transition</em> to the state(s) that need it.
</p>
<note>
<p>If you in <c>gen_statem</c>, for example, postpone
- an event in one state and then call another state callback
- of yours, you have not changed states and hence the postponed event
- is not retried, which is logical but can be confusing.
+ an event in one state and then call another <em>state callback</em>
+ of yours, you have not done a <em>state change</em>
+ and hence the postponed event is not retried,
+ which is logical but can be confusing.
</p>
</note>
<p>
- For the details of a state transition, see type
+ For the details of a <em>state transition</em>, see type
<seealso marker="#type-transition_option"><c>transition_option()</c></seealso>.
</p>
<p>
@@ -276,7 +306,8 @@ erlang:'!' -----> Module:StateName/3
The <c>gen_statem</c> process can go into hibernation; see
<seealso marker="proc_lib#hibernate/3"><c>proc_lib:hibernate/3</c></seealso>.
It is done when a
- <seealso marker="#state callback">state callback</seealso> or
+ <seealso marker="#state callback"><em>state callback</em></seealso>
+ or
<seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
specifies <c>hibernate</c> in the returned
<seealso marker="#type-action"><c>Actions</c></seealso>
@@ -483,36 +514,6 @@ handle_event(_, _, State, Data) ->
</desc>
</datatype>
<datatype>
- <name name="debug_opt"/>
- <desc>
- <p>
- Debug option that can be used when starting
- a <c>gen_statem</c> server through,
- <seealso marker="#enter_loop/4"><c>enter_loop/4-6</c></seealso>.
- </p>
- <p>
- For every entry in <c><anno>Dbgs</anno></c>,
- the corresponding function in
- <seealso marker="sys"><c>sys</c></seealso> is called.
- </p>
- </desc>
- </datatype>
- <datatype>
- <name name="hibernate_after_opt"/>
- <desc>
- <p>
- hibernate_after option that can be used when starting
- a <c>gen_statem</c> server through,
- <seealso marker="#enter_loop/4"><c>enter_loop/4-6</c></seealso>.
- </p>
- <p>If option<seealso marker="#type-hibernate_after_opt"><c>{hibernate_after,HibernateAfterTimeout}</c></seealso> is present, the <c>gen_statem</c>
- process awaits any message for <c>HibernateAfterTimeout</c> milliseconds and
- if no message is received, the process goes into hibernation automatically
- (by calling <seealso marker="proc_lib#hibernate/3"><c>proc_lib:hibernate/3</c></seealso>).
- </p>
- </desc>
- </datatype>
- <datatype>
<name name="start_opt"/>
<desc>
<p>
@@ -532,6 +533,37 @@ handle_event(_, _, State, Data) ->
</desc>
</datatype>
<datatype>
+ <name name="enter_loop_opt"/>
+ <desc>
+ <p>
+ Options that can be used when starting
+ a <c>gen_statem</c> server through,
+ <seealso marker="#enter_loop/4"><c>enter_loop/4-6</c></seealso>.
+ </p>
+ <taglist>
+ <tag><c>hibernate_after</c></tag>
+ <item>
+ <p>
+ <c>HibernateAfterTimeout</c>
+ specifies that the <c>gen_statem</c> process awaits
+ any message for <c>HibernateAfterTimeout</c> milliseconds and
+ if no message is received, the process goes into hibernation
+ automatically (by calling
+ <seealso marker="proc_lib#hibernate/3"><c>proc_lib:hibernate/3</c></seealso>).
+ </p>
+ </item>
+ <tag><c>debug</c></tag>
+ <item>
+ <p>
+ For every entry in <c><anno>Dbgs</anno></c>,
+ the corresponding function in
+ <seealso marker="sys"><c>sys</c></seealso> is called.
+ </p>
+ </item>
+ </taglist>
+ </desc>
+ </datatype>
+ <datatype>
<name name="from"/>
<desc>
<p>
@@ -551,7 +583,7 @@ handle_event(_, _, State, Data) ->
<seealso marker="#type-callback_mode"><em>callback mode</em></seealso>
is <c>handle_event_function</c>,
the state can be any term.
- After a state change (<c>NextState =/= State</c>),
+ After a <em>state change</em> (<c>NextState =/= State</c>),
all postponed events are retried.
</p>
</desc>
@@ -564,7 +596,7 @@ handle_event(_, _, State, Data) ->
<seealso marker="#type-callback_mode"><em>callback mode</em></seealso>
is <c>state_functions</c>,
the state must be of this type.
- After a state change (<c>NextState =/= State</c>),
+ After a <em>state change</em> (<c>NextState =/= State</c>),
all postponed events are retried.
</p>
</desc>
@@ -595,7 +627,7 @@ handle_event(_, _, State, Data) ->
</p>
<p>
<c>internal</c> events can only be generated by the
- state machine itself through the state transition action
+ state machine itself through the <em>transition action</em>
<seealso marker="#type-action"><c>next_event</c></seealso>.
</p>
</desc>
@@ -633,9 +665,9 @@ handle_event(_, _, State, Data) ->
This is the return type from
<seealso marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>
and selects
- <seealso marker="#type-callback_mode">callback mode</seealso>
+ <seealso marker="#type-callback_mode"><em>callback mode</em></seealso>
and whether to do
- <seealso marker="#type-state_enter">state enter calls</seealso>,
+ <seealso marker="#type-state_enter"><em>state enter calls</em></seealso>,
or not.
</p>
</desc>
@@ -684,13 +716,15 @@ handle_event(_, _, State, Data) ->
If
<seealso marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>
returns a list containing <c>state_enter</c>,
- the <c>gen_statem</c> engine will, at every state change,
+ the <c>gen_statem</c> engine will, at every <em>state change</em>,
call the
<seealso marker="#state callback">state callback</seealso>
with arguments <c>(enter, OldState, Data)</c>.
This may look like an event but is really a call
- performed after the previous state callback returned
- and before any event is delivered to the new state callback.
+ performed after the previous
+ <seealso marker="#state callback"><em>state callback</em></seealso>
+ returned and before any event is delivered to the new
+ <seealso marker="#state callback"><em>state callback</em></seealso>.
See
<seealso marker="#Module:StateName/3"><c>Module:StateName/3</c></seealso>
and
@@ -703,27 +737,27 @@ handle_event(_, _, State, Data) ->
<seealso marker="#type-state_callback_result">
<c>repeat_state_and_data</c>
</seealso>
- tuple from the state callback.
+ tuple from the <em>state callback</em>.
</p>
<p>
If
<seealso marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>
- does not return such a list, no state enter calls are done.
+ does not return such a list, no <em>state enter calls</em> are done.
</p>
<p>
If
<seealso marker="#Module:code_change/4"><c>Module:code_change/4</c></seealso>
should transform the state,
- it is regarded as a state rename and not a state change,
- which will not cause a state enter call.
+ it is regarded as a state rename and not a <em>state change</em>,
+ which will not cause a <em>state enter call</em>.
</p>
<p>
- Note that a state enter call <em>will</em> be done
+ Note that a <em>state enter call</em> <em>will</em> be done
right before entering the initial state even though this
- formally is not a state change.
- In this case <c>OldState</c> will be the same as <c>State</c>,
+ actually is not a <em>state change</em>.
+ In this case <c>OldState =:= State</c>,
which can not happen for a subsequent state change,
- but will happen when repeating the state enter call.
+ but will happen when repeating the <em>state enter call</em>.
</p>
</desc>
</datatype>
@@ -733,8 +767,11 @@ handle_event(_, _, State, Data) ->
<p>
Transition options can be set by
<seealso marker="#type-action">actions</seealso>
- and modify the state transition.
- Here are the sequence of steps for a state transition:
+ and modify the <em>state transition</em>.
+ The <em>state transition</em> takes place when the
+ <seealso marker="#state callback"><em>state callback</em></seealso>
+ has processed an event and returns.
+ Here are the sequence of steps for a <em>state transition</em>:
</p>
<list type="ordered">
<item>
@@ -765,7 +802,7 @@ handle_event(_, _, State, Data) ->
returned by the state callback that caused the state entry.
</p>
<p>
- Should this state enter call return any of
+ Should this <em>state enter call</em> return any of
the mentioned <c>repeat_*</c> callback results
it is repeated again, with the updated <c>Data</c>.
</p>
@@ -787,7 +824,8 @@ handle_event(_, _, State, Data) ->
</item>
<item>
<p>
- If the state changes, the queue of incoming events
+ If this is a <em>state change</em>,
+ the queue of incoming events
is reset to start with the oldest postponed.
</p>
</item>
@@ -821,7 +859,7 @@ handle_event(_, _, State, Data) ->
if the event queue is empty.
</p>
<p>
- A state change cancels a
+ A <em>state change</em> cancels a
<seealso marker="#type-state_timeout"><c>state_timeout()</c></seealso>
and any new transition option of this type
belongs to the new state.
@@ -830,7 +868,7 @@ handle_event(_, _, State, Data) ->
<item>
<p>
If there are enqueued events the
- <seealso marker="#state callback">state callback</seealso>
+ <seealso marker="#state callback"><em>state callback</em></seealso>
for the possibly new state
is called with the oldest enqueued event,
and we start again from the top of this list.
@@ -848,7 +886,7 @@ handle_event(_, _, State, Data) ->
the next incoming message awakens the <c>gen_statem</c>,
but if it is a system event it goes right back into hibernation.
When a new message arrives the
- <seealso marker="#state callback">state callback</seealso>
+ <seealso marker="#state callback"><em>state callback</em></seealso>
is called with the corresponding event,
and we start again from the top of this sequence.
</p>
@@ -861,7 +899,7 @@ handle_event(_, _, State, Data) ->
<desc>
<p>
If <c>true</c>, postpones the current event and retries
- it when the state changes
+ it after a <em>state change</em>
(<c>NextState =/= State</c>).
</p>
</desc>
@@ -1021,9 +1059,9 @@ handle_event(_, _, State, Data) ->
<name name="action"/>
<desc>
<p>
- These state transition actions can be invoked by
+ These <em>transition actions</em> can be invoked by
returning them from the
- <seealso marker="#state callback">state callback</seealso>
+ <seealso marker="#state callback"><em>state callback</em></seealso>
when it is called with an
<seealso marker="#type-event_type">event</seealso>,
from
@@ -1054,7 +1092,7 @@ handle_event(_, _, State, Data) ->
<c>transition_option()</c>
</seealso>
<seealso marker="#type-postpone"><c>postpone()</c></seealso>
- for this state transition.
+ for this <em>state transition</em>.
This action is ignored when returned from
<seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
or given to
@@ -1093,9 +1131,9 @@ handle_event(_, _, State, Data) ->
<name name="enter_action"/>
<desc>
<p>
- These state transition actions can be invoked by
+ These <em>transition actions</em> can be invoked by
returning them from the
- <seealso marker="#state callback">state callback</seealso>, from
+ <seealso marker="#state callback"><em>state callback</em></seealso>, from
<seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
or by giving them to
<seealso marker="#enter_loop/5"><c>enter_loop/5,6</c></seealso>.
@@ -1119,7 +1157,7 @@ handle_event(_, _, State, Data) ->
Sets the
<seealso marker="#type-transition_option"><c>transition_option()</c></seealso>
<seealso marker="#type-hibernate"><c>hibernate()</c></seealso>
- for this state transition.
+ for this <em>state transition</em>.
</p>
</item>
</taglist>
@@ -1129,9 +1167,9 @@ handle_event(_, _, State, Data) ->
<name name="timeout_action"/>
<desc>
<p>
- These state transition actions can be invoked by
+ These <em>transition actions</em> can be invoked by
returning them from the
- <seealso marker="#state callback">state callback</seealso>, from
+ <seealso marker="#state callback"><em>state callback</em></seealso>, from
<seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
or by giving them to
<seealso marker="#enter_loop/5"><c>enter_loop/5,6</c></seealso>.
@@ -1147,7 +1185,7 @@ handle_event(_, _, State, Data) ->
Short for <c>{timeout,Time,Time}</c>, that is,
the time-out message is the time-out time.
This form exists to make the
- <seealso marker="#state callback">state callback</seealso>
+ <seealso marker="#state callback"><em>state callback</em></seealso>
return value <c>{next_state,NextState,NewData,Time}</c>
allowed like for <c>gen_fsm</c>.
</p>
@@ -1193,9 +1231,9 @@ handle_event(_, _, State, Data) ->
<name name="reply_action"/>
<desc>
<p>
- This state transition action can be invoked by
+ This <em>transition action</em> can be invoked by
returning it from the
- <seealso marker="#state callback">state callback</seealso>, from
+ <seealso marker="#state callback"><em>state callback</em></seealso>, from
<seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
or by giving it to
<seealso marker="#enter_loop/5"><c>enter_loop/5,6</c></seealso>.
@@ -1210,7 +1248,7 @@ handle_event(_, _, State, Data) ->
<c><anno>From</anno></c> must be the term from argument
<seealso marker="#type-event_type"><c>{call,<anno>From</anno>}</c></seealso>
in a call to a
- <seealso marker="#state callback">state callback</seealso>.
+ <seealso marker="#state callback"><em>state callback</em></seealso>.
</p>
<p>
Note that using this action from
@@ -1219,7 +1257,7 @@ handle_event(_, _, State, Data) ->
<seealso marker="#enter_loop/5"><c>enter_loop/5,6</c></seealso>
would be weird on the border of witchcraft
since there has been no earlier call to a
- <seealso marker="#state callback">state callback</seealso>
+ <seealso marker="#state callback"><em>state callback</em></seealso>
in this server.
</p>
</desc>
@@ -1239,7 +1277,7 @@ handle_event(_, _, State, Data) ->
The <seealso marker="#type-action"><c>Actions</c></seealso>
are executed when entering the first
<seealso marker="#type-state">state</seealso> just as for a
- <seealso marker="#state callback">state callback</seealso>,
+ <seealso marker="#state callback"><em>state callback</em></seealso>,
except that the action <c>postpone</c> is forced to
<c>false</c> since there is no event to postpone.
</p>
@@ -1256,7 +1294,7 @@ handle_event(_, _, State, Data) ->
<desc>
<p>
<c><anno>State</anno></c> is the current state
- and it can not be changed since the state callback
+ and it cannot be changed since the state callback
was called with a
<seealso marker="#type-state_enter"><em>state enter call</em></seealso>.
</p>
@@ -1292,11 +1330,13 @@ handle_event(_, _, State, Data) ->
<tag><c>next_state</c></tag>
<item>
<p>
- The <c>gen_statem</c> does a state transition to
+ The <c>gen_statem</c> does a <em>state transition</em> to
<c><anno>NextState</anno></c>
(which can be the same as the current state),
sets <c><anno>NewData</anno></c>,
and executes all <c><anno>Actions</anno></c>.
+ If <c><anno>NextState</anno> =/= CurrentState</c>
+ the <em>state transition</em> is a <em>state change</em>.
</p>
</item>
</taglist>
@@ -1318,54 +1358,33 @@ handle_event(_, _, State, Data) ->
<tag><c>keep_state</c></tag>
<item>
<p>
- The <c>gen_statem</c> keeps the current state, or
- does a state transition to the current state if you like,
- sets <c><anno>NewData</anno></c>,
- and executes all <c><anno>Actions</anno></c>.
- This is the same as
+ The same as
<c>{next_state,CurrentState,<anno>NewData</anno>,<anno>Actions</anno>}</c>.
</p>
</item>
<tag><c>keep_state_and_data</c></tag>
<item>
<p>
- The <c>gen_statem</c> keeps the current state or
- does a state transition to the current state if you like,
- keeps the current server data,
- and executes all <c><anno>Actions</anno></c>.
- This is the same as
- <c>{next_state,CurrentState,CurrentData,<anno>Actions</anno>}</c>.
+ The same as
+ <c>{keep_state,CurrentData,<anno>Actions</anno>}</c>.
</p>
</item>
<tag><c>repeat_state</c></tag>
<item>
<p>
- The <c>gen_statem</c> keeps the current state, or
- does a state transition to the current state if you like,
- sets <c><anno>NewData</anno></c>,
- and executes all <c><anno>Actions</anno></c>.
If the <c>gen_statem</c> runs with
<seealso marker="#type-state_enter"><em>state enter calls</em></seealso>,
- the state enter call is repeated, see type
+ the <em>state enter call</em> is repeated, see type
<seealso marker="#type-transition_option"><c>transition_option()</c></seealso>,
- otherwise <c>repeat_state</c> is the same as
+ other than that <c>repeat_state</c> is the same as
<c>keep_state</c>.
</p>
</item>
<tag><c>repeat_state_and_data</c></tag>
<item>
<p>
- The <c>gen_statem</c> keeps the current state and data, or
- does a state transition to the current state if you like,
- and executes all <c><anno>Actions</anno></c>.
- This is the same as
+ The same as
<c>{repeat_state,CurrentData,<anno>Actions</anno>}</c>.
- If the <c>gen_statem</c> runs with
- <seealso marker="#type-state_enter"><em>state enter calls</em></seealso>,
- the state enter call is repeated, see type
- <seealso marker="#type-transition_option"><c>transition_option()</c></seealso>,
- otherwise <c>repeat_state_and_data</c> is the same as
- <c>keep_state_and_data</c>.
</p>
</item>
<tag><c>stop</c></tag>
@@ -1398,8 +1417,8 @@ handle_event(_, _, State, Data) ->
<funcs>
<func>
- <name name="call" arity="2"/>
- <name name="call" arity="3"/>
+ <name name="call" arity="2" since="OTP 19.0"/>
+ <name name="call" arity="3" since="OTP 19.0"/>
<fsummary>Make a synchronous call to a <c>gen_statem</c>.</fsummary>
<desc>
<p>
@@ -1408,14 +1427,15 @@ handle_event(_, _, State, Data) ->
by sending a request
and waiting until its reply arrives.
The <c>gen_statem</c> calls the
- <seealso marker="#state callback">state callback</seealso> with
+ <seealso marker="#state callback"><em>state callback</em></seealso>
+ with
<seealso marker="#type-event_type"><c>event_type()</c></seealso>
<c>{call,From}</c> and event content
<c><anno>Request</anno></c>.
</p>
<p>
A <c><anno>Reply</anno></c> is generated when a
- <seealso marker="#state callback">state callback</seealso>
+ <seealso marker="#state callback"><em>state callback</em></seealso>
returns with
<c>{reply,From,<anno>Reply</anno>}</c> as one
<seealso marker="#type-action"><c>action()</c></seealso>,
@@ -1474,7 +1494,7 @@ handle_event(_, _, State, Data) ->
</func>
<func>
- <name name="cast" arity="2"/>
+ <name name="cast" arity="2" since="OTP 19.0"/>
<fsummary>Send an asynchronous event to a <c>gen_statem</c>.</fsummary>
<desc>
<p>
@@ -1484,7 +1504,8 @@ handle_event(_, _, State, Data) ->
ignoring if the destination node or <c>gen_statem</c>
does not exist.
The <c>gen_statem</c> calls the
- <seealso marker="#state callback">state callback</seealso> with
+ <seealso marker="#state callback"><em>state callback</em></seealso>
+ with
<seealso marker="#type-event_type"><c>event_type()</c></seealso>
<c>cast</c> and event content
<c><anno>Msg</anno></c>.
@@ -1493,7 +1514,7 @@ handle_event(_, _, State, Data) ->
</func>
<func>
- <name name="enter_loop" arity="4"/>
+ <name name="enter_loop" arity="4" since="OTP 19.1"/>
<fsummary>Enter the <c>gen_statem</c> receive loop.</fsummary>
<desc>
<p>
@@ -1507,7 +1528,7 @@ handle_event(_, _, State, Data) ->
</func>
<func>
- <name name="enter_loop" arity="5"/>
+ <name name="enter_loop" arity="5" since="OTP 19.0"/>
<fsummary>Enter the <c>gen_statem</c> receive loop.</fsummary>
<desc>
<p>
@@ -1531,7 +1552,7 @@ handle_event(_, _, State, Data) ->
</func>
<func>
- <name name="enter_loop" arity="6"/>
+ <name name="enter_loop" arity="6" since="OTP 19.0"/>
<fsummary>Enter the <c>gen_statem</c> receive loop.</fsummary>
<desc>
<p>
@@ -1588,8 +1609,8 @@ handle_event(_, _, State, Data) ->
</func>
<func>
- <name name="reply" arity="1"/>
- <name name="reply" arity="2"/>
+ <name name="reply" arity="1" since="OTP 19.0"/>
+ <name name="reply" arity="2" since="OTP 19.0"/>
<fsummary>Reply to a caller.</fsummary>
<desc>
<p>
@@ -1598,18 +1619,18 @@ handle_event(_, _, State, Data) ->
<seealso marker="#call/2"><c>call/2</c></seealso>
when the reply cannot be defined in
the return value of a
- <seealso marker="#state callback">state callback</seealso>.
+ <seealso marker="#state callback"><em>state callback</em></seealso>.
</p>
<p>
<c><anno>From</anno></c> must be the term from argument
<seealso marker="#type-event_type"><c>{call,<anno>From</anno>}</c></seealso>
to the
- <seealso marker="#state callback">state callback</seealso>.
+ <seealso marker="#state callback"><em>state callback</em></seealso>.
A reply or multiple replies canalso be sent
using one or several
<seealso marker="#type-reply_action"><c>reply_action()</c></seealso>s
from a
- <seealso marker="#state callback">state callback</seealso>.
+ <seealso marker="#state callback"><em>state callback</em></seealso>.
</p>
<note>
<p>
@@ -1621,8 +1642,8 @@ handle_event(_, _, State, Data) ->
</func>
<func>
- <name name="start" arity="3"/>
- <name name="start" arity="4"/>
+ <name name="start" arity="3" since="OTP 19.0"/>
+ <name name="start" arity="4" since="OTP 19.0"/>
<fsummary>Create a standalone <c>gen_statem</c> process.</fsummary>
<desc>
<p>
@@ -1642,8 +1663,8 @@ handle_event(_, _, State, Data) ->
</func>
<func>
- <name name="start_link" arity="3"/>
- <name name="start_link" arity="4"/>
+ <name name="start_link" arity="3" since="OTP 19.0"/>
+ <name name="start_link" arity="4" since="OTP 19.0"/>
<fsummary>Create a linked <c>gen_statem</c> process.</fsummary>
<desc>
<p>
@@ -1681,7 +1702,11 @@ handle_event(_, _, State, Data) ->
<list type="bulleted">
<item>
<p>
- If option <c>{timeout,Time}</c> is present in
+ If option
+ <seealso marker="#type-start_opt">
+ <c>{timeout,Time}</c>
+ </seealso>
+ is present in
<c><anno>Opts</anno></c>, the <c>gen_statem</c>
is allowed to spend <c>Time</c> milliseconds initializing
or it terminates and the start function returns
@@ -1689,23 +1714,33 @@ handle_event(_, _, State, Data) ->
</p>
</item>
<item>
- <p>If option<seealso marker="#type-hibernate_after_opt"><c>{hibernate_after,HibernateAfterTimeout}</c></seealso> is present, the <c>gen_statem</c>
- process awaits any message for <c>HibernateAfterTimeout</c> milliseconds and
- if no message is received, the process goes into hibernation automatically
- (by calling <seealso marker="proc_lib#hibernate/3"><c>proc_lib:hibernate/3</c></seealso>).
+ <p>If option
+ <seealso marker="#type-enter_loop_opt">
+ <c>{hibernate_after,HibernateAfterTimeout}</c>
+ </seealso>
+ is present, the <c>gen_statem</c>
+ process awaits any message for <c>HibernateAfterTimeout</c> milliseconds and
+ if no message is received, the process goes into hibernation automatically
+ (by calling <seealso marker="proc_lib#hibernate/3"><c>proc_lib:hibernate/3</c></seealso>).
</p>
</item>
<item>
<p>
If option
- <seealso marker="#type-debug_opt"><c>{debug,Dbgs}</c></seealso>
+ <seealso marker="#type-enter_loop_opt">
+ <c>{debug,Dbgs}</c>
+ </seealso>
is present in <c><anno>Opts</anno></c>, debugging through
<seealso marker="sys"><c>sys</c></seealso> is activated.
</p>
</item>
<item>
<p>
- If option <c>{spawn_opt,SpawnOpts}</c> is present in
+ If option
+ <seealso marker="#type-start_opt">
+ <c>{spawn_opt,SpawnOpts}</c>
+ </seealso>
+ is present in
<c><anno>Opts</anno></c>, <c>SpawnOpts</c> is passed
as option list to
<seealso marker="erts:erlang#spawn_opt/2"><c>erlang:spawn_opt/2</c></seealso>,
@@ -1750,7 +1785,7 @@ handle_event(_, _, State, Data) ->
</func>
<func>
- <name name="stop" arity="1"/>
+ <name name="stop" arity="1" since="OTP 19.0"/>
<fsummary>Synchronously stop a generic server.</fsummary>
<desc>
<p>
@@ -1761,7 +1796,7 @@ handle_event(_, _, State, Data) ->
</func>
<func>
- <name name="stop" arity="3"/>
+ <name name="stop" arity="3" since="OTP 19.0"/>
<fsummary>Synchronously stop a generic server.</fsummary>
<desc>
<p>
@@ -1807,7 +1842,7 @@ handle_event(_, _, State, Data) ->
<funcs>
<func>
- <name>Module:callback_mode() -> CallbackMode</name>
+ <name since="OTP 19.1">Module:callback_mode() -> CallbackMode</name>
<fsummary>Update the internal state during upgrade/downgrade.</fsummary>
<type>
<v>
@@ -1826,7 +1861,7 @@ handle_event(_, _, State, Data) ->
for efficiency reasons, so this function is only called
once after server start and after code change,
but before the first
- <seealso marker="#state callback">state callback</seealso>
+ <seealso marker="#state callback"><em>state callback</em></seealso>
in the current code version is called.
More occasions may be added in future versions
of <c>gen_statem</c>.
@@ -1858,7 +1893,7 @@ handle_event(_, _, State, Data) ->
</func>
<func>
- <name>Module:code_change(OldVsn, OldState, OldData, Extra) ->
+ <name since="OTP 19.0">Module:code_change(OldVsn, OldState, OldData, Extra) ->
Result
</name>
<fsummary>Update the internal state during upgrade/downgrade.</fsummary>
@@ -1883,7 +1918,7 @@ handle_event(_, _, State, Data) ->
<p>
This callback is optional, so callback modules need not export it.
If a release upgrade/downgrade with
- <c>Change={advanced,Extra}</c>
+ <c>Change = {advanced,Extra}</c>
specified in the <c>.appup</c> file is made
when <c>code_change/4</c> is not implemented
the process will crash with exit reason <c>undef</c>.
@@ -1893,7 +1928,7 @@ handle_event(_, _, State, Data) ->
This function is called by a <c>gen_statem</c> when it is to
update its internal state during a release upgrade/downgrade,
that is, when the instruction <c>{update,Module,Change,...}</c>,
- where <c>Change={advanced,Extra}</c>, is specified in the
+ where <c>Change = {advanced,Extra}</c>, is specified in the
<seealso marker="sasl:appup"><c>appup</c></seealso>
file. For more information, see
<seealso marker="doc/design_principles:release_handling#instr">OTP Design Principles</seealso>.
@@ -1922,7 +1957,7 @@ handle_event(_, _, State, Data) ->
<p>
If the function returns a failure <c>Reason</c>, the ongoing
upgrade fails and rolls back to the old release.
- Note that <c>Reason</c> can not be an <c>{ok,_,_}</c> tuple
+ Note that <c>Reason</c> cannot be an <c>{ok,_,_}</c> tuple
since that will be regarded as a
<c>{ok,NewState,NewData}</c> tuple,
and that a tuple matching <c>{ok,_}</c>
@@ -1933,13 +1968,13 @@ handle_event(_, _, State, Data) ->
<p>
Also note when upgrading a <c>gen_statem</c>,
this function and hence
- the <c>Change={advanced,Extra}</c> parameter in the
+ the <c>Change = {advanced,Extra}</c> parameter in the
<seealso marker="sasl:appup"><c>appup</c></seealso> file
is not only needed to update the internal state
or to act on the <c>Extra</c> argument.
It is also needed if an upgrade or downgrade should change
<seealso marker="#type-callback_mode"><em>callback mode</em></seealso>,
- or else the callback mode after the code change
+ or else the <em>callback mode</em> after the code change
will not be honoured,
most probably causing a server crash.
</p>
@@ -1947,7 +1982,7 @@ handle_event(_, _, State, Data) ->
</func>
<func>
- <name>Module:init(Args) -> Result(StateType)</name>
+ <name since="OTP 19.0">Module:init(Args) -> Result(StateType)</name>
<fsummary>
Initializing process and internal state.
</fsummary>
@@ -1989,7 +2024,7 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre>
</func>
<func>
- <name>Module:format_status(Opt, [PDict,State,Data]) ->
+ <name since="OTP 19.0">Module:format_status(Opt, [PDict,State,Data]) ->
Status
</name>
<fsummary>Optional function for providing a term describing the
@@ -2088,16 +2123,16 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre>
</func>
<func>
- <name>Module:StateName(enter, OldState, Data) ->
+ <name since="OTP 19.0">Module:StateName(enter, OldState, Data) ->
StateEnterResult(StateName)
</name>
- <name>Module:StateName(EventType, EventContent, Data) ->
+ <name since="OTP 19.0">Module:StateName(EventType, EventContent, Data) ->
StateFunctionResult
</name>
- <name>Module:handle_event(enter, OldState, State, Data) ->
+ <name since="OTP 19.0">Module:handle_event(enter, OldState, State, Data) ->
StateEnterResult(State)
</name>
- <name>Module:handle_event(EventType, EventContent, State, Data) ->
+ <name since="OTP 19.0">Module:handle_event(EventType, EventContent, State, Data) ->
HandleEventResult
</name>
<fsummary>Handle an event.</fsummary>
@@ -2148,7 +2183,7 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre>
<seealso marker="#type-event_type"><c>{call,From}</c></seealso>,
the caller waits for a reply. The reply can be sent
from this or from any other
- <seealso marker="#state callback">state callback</seealso>
+ <seealso marker="#state callback"><em>state callback</em></seealso>
by returning with <c>{reply,From,Reply}</c> in
<seealso marker="#type-action"><c>Actions</c></seealso>, in
<seealso marker="#type-reply_action"><c>Replies</c></seealso>,
@@ -2173,9 +2208,9 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre>
</p>
<p>
When the <c>gen_statem</c> runs with
- <seealso marker="#type-state_enter">state enter calls</seealso>,
+ <seealso marker="#type-state_enter"><em>state enter calls</em></seealso>,
these functions are also called with arguments
- <c>(enter, OldState, ...)</c> whenever the state changes.
+ <c>(enter, OldState, ...)</c> during every <em>state change</em>.
In this case there are some restrictions on the
<seealso marker="#type-enter_action">actions</seealso>
that may be returned:
@@ -2208,7 +2243,7 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre>
<seealso marker="erts:erlang#throw/1"><c>throw</c></seealso>
to return the result, which can be useful.
For example to bail out with <c>throw(keep_state_and_data)</c>
- from deep within complex code that can not
+ from deep within complex code that cannot
return <c>{next_state,State,Data}</c> because
<c>State</c> or <c>Data</c> is no longer in scope.
</p>
@@ -2216,7 +2251,7 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre>
</func>
<func>
- <name>Module:terminate(Reason, State, Data) -> Ignored</name>
+ <name since="OTP 19.0">Module:terminate(Reason, State, Data) -> Ignored</name>
<fsummary>Clean up before termination.</fsummary>
<type>
<v>Reason = normal | shutdown | {shutdown,term()} | term()</v>
diff --git a/lib/stdlib/doc/src/io.xml b/lib/stdlib/doc/src/io.xml
index d4a2713840..d69e808586 100644
--- a/lib/stdlib/doc/src/io.xml
+++ b/lib/stdlib/doc/src/io.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>io</module>
+ <module since="">io</module>
<modulesummary>Standard I/O server interface functions.</modulesummary>
<description>
<p>This module provides an interface to standard Erlang I/O servers.
@@ -104,8 +104,8 @@
<funcs>
<func>
- <name name="columns" arity="0"/>
- <name name="columns" arity="1"/>
+ <name name="columns" arity="0" since=""/>
+ <name name="columns" arity="1" since=""/>
<fsummary>Get the number of columns of an I/O device.</fsummary>
<desc>
<p>Retrieves the number of columns of the
@@ -116,12 +116,12 @@
</func>
<func>
- <name name="format" arity="1"/>
- <name name="format" arity="2"/>
- <name name="format" arity="3"/>
- <name name="fwrite" arity="1"/>
- <name name="fwrite" arity="2"/>
- <name name="fwrite" arity="3"/>
+ <name name="format" arity="1" since=""/>
+ <name name="format" arity="2" since=""/>
+ <name name="format" arity="3" since=""/>
+ <name name="fwrite" arity="1" since=""/>
+ <name name="fwrite" arity="2" since=""/>
+ <name name="fwrite" arity="3" since=""/>
<fsummary>Write formatted output.</fsummary>
<desc>
<p>Writes the items in <c><anno>Data</anno></c> (<c>[]</c>) on the
@@ -523,8 +523,8 @@ ok
</func>
<func>
- <name name="fread" arity="2"/>
- <name name="fread" arity="3"/>
+ <name name="fread" arity="2" since=""/>
+ <name name="fread" arity="3" since=""/>
<fsummary>Read formatted input.</fsummary>
<type name="server_no_data"/>
<desc>
@@ -690,8 +690,8 @@ enter><input>:</input> <input>alan</input> <input>:</input> <input>joe</in
</func>
<func>
- <name name="get_chars" arity="2"/>
- <name name="get_chars" arity="3"/>
+ <name name="get_chars" arity="2" since=""/>
+ <name name="get_chars" arity="3" since=""/>
<fsummary>Read a specified number of characters.</fsummary>
<type name="server_no_data"/>
<desc>
@@ -722,8 +722,8 @@ enter><input>:</input> <input>alan</input> <input>:</input> <input>joe</in
</func>
<func>
- <name name="get_line" arity="1"/>
- <name name="get_line" arity="2"/>
+ <name name="get_line" arity="1" since=""/>
+ <name name="get_line" arity="2" since=""/>
<fsummary>Read a line.</fsummary>
<type name="server_no_data"/>
<desc>
@@ -754,8 +754,8 @@ enter><input>:</input> <input>alan</input> <input>:</input> <input>joe</in
</func>
<func>
- <name name="getopts" arity="0"/>
- <name name="getopts" arity="1"/>
+ <name name="getopts" arity="0" since=""/>
+ <name name="getopts" arity="1" since=""/>
<fsummary>Get the supported options and values from an I/O server.
</fsummary>
<desc>
@@ -781,8 +781,8 @@ enter><input>:</input> <input>alan</input> <input>:</input> <input>joe</in
</func>
<func>
- <name name="nl" arity="0"/>
- <name name="nl" arity="1"/>
+ <name name="nl" arity="0" since=""/>
+ <name name="nl" arity="1" since=""/>
<fsummary>Write a newline.</fsummary>
<desc>
<p>Writes new line to the standard output
@@ -791,10 +791,10 @@ enter><input>:</input> <input>alan</input> <input>:</input> <input>joe</in
</func>
<func>
- <name name="parse_erl_exprs" arity="1"/>
- <name name="parse_erl_exprs" arity="2"/>
- <name name="parse_erl_exprs" arity="3"/>
- <name name="parse_erl_exprs" arity="4"/>
+ <name name="parse_erl_exprs" arity="1" since=""/>
+ <name name="parse_erl_exprs" arity="2" since=""/>
+ <name name="parse_erl_exprs" arity="3" since=""/>
+ <name name="parse_erl_exprs" arity="4" since="OTP R16B"/>
<fsummary>Read, tokenize, and parse Erlang expressions.</fsummary>
<type name="parse_ret"/>
<type name="server_no_data"/>
@@ -844,10 +844,10 @@ enter><input>abc("hey".</input>
</func>
<func>
- <name name="parse_erl_form" arity="1"/>
- <name name="parse_erl_form" arity="2"/>
- <name name="parse_erl_form" arity="3"/>
- <name name="parse_erl_form" arity="4"/>
+ <name name="parse_erl_form" arity="1" since=""/>
+ <name name="parse_erl_form" arity="2" since=""/>
+ <name name="parse_erl_form" arity="3" since=""/>
+ <name name="parse_erl_form" arity="4" since="OTP R16B"/>
<fsummary>Read, tokenize, and parse an Erlang form.</fsummary>
<type name="parse_form_ret"/>
<type name="server_no_data"/>
@@ -888,7 +888,7 @@ enter><input>abc("hey".</input>
</func>
<func>
- <name name="printable_range" arity="0"/>
+ <name name="printable_range" arity="0" since="OTP R16B"/>
<fsummary>Get user-requested printable character range.</fsummary>
<desc>
<p>Returns the user-requested range of printable Unicode characters.</p>
@@ -918,8 +918,8 @@ enter><input>abc("hey".</input>
</func>
<func>
- <name name="put_chars" arity="1"/>
- <name name="put_chars" arity="2"/>
+ <name name="put_chars" arity="1" since=""/>
+ <name name="put_chars" arity="2" since=""/>
<fsummary>Write a list of characters.</fsummary>
<desc>
<p>Writes the characters of <c><anno>CharData</anno></c> to the I/O
@@ -928,8 +928,8 @@ enter><input>abc("hey".</input>
</func>
<func>
- <name name="read" arity="1"/>
- <name name="read" arity="2"/>
+ <name name="read" arity="1" since=""/>
+ <name name="read" arity="2" since=""/>
<fsummary>Read a term.</fsummary>
<type name="server_no_data"/>
<desc>
@@ -960,8 +960,8 @@ enter><input>abc("hey".</input>
</func>
<func>
- <name name="read" arity="3"/>
- <name name="read" arity="4"/>
+ <name name="read" arity="3" since=""/>
+ <name name="read" arity="4" since="OTP R16B"/>
<fsummary>Read a term.</fsummary>
<type name="server_no_data"/>
<desc>
@@ -997,8 +997,8 @@ enter><input>abc("hey".</input>
</func>
<func>
- <name name="rows" arity="0"/>
- <name name="rows" arity="1"/>
+ <name name="rows" arity="0" since=""/>
+ <name name="rows" arity="1" since=""/>
<fsummary>Get the number of rows of an I/O device.</fsummary>
<desc>
<p>Retrieves the number of rows of <c><anno>IoDevice</anno></c>
@@ -1009,10 +1009,10 @@ enter><input>abc("hey".</input>
</func>
<func>
- <name name="scan_erl_exprs" arity="1"/>
- <name name="scan_erl_exprs" arity="2"/>
- <name name="scan_erl_exprs" arity="3"/>
- <name name="scan_erl_exprs" arity="4"/>
+ <name name="scan_erl_exprs" arity="1" since=""/>
+ <name name="scan_erl_exprs" arity="2" since=""/>
+ <name name="scan_erl_exprs" arity="3" since=""/>
+ <name name="scan_erl_exprs" arity="4" since="OTP R16B"/>
<fsummary>Read and tokenize Erlang expressions.</fsummary>
<type name="server_no_data"/>
<desc>
@@ -1060,10 +1060,10 @@ enter><input>1.0er.</input>
</func>
<func>
- <name name="scan_erl_form" arity="1"/>
- <name name="scan_erl_form" arity="2"/>
- <name name="scan_erl_form" arity="3"/>
- <name name="scan_erl_form" arity="4"/>
+ <name name="scan_erl_form" arity="1" since=""/>
+ <name name="scan_erl_form" arity="2" since=""/>
+ <name name="scan_erl_form" arity="3" since=""/>
+ <name name="scan_erl_form" arity="4" since="OTP R16B"/>
<fsummary>Read and tokenize an Erlang form.</fsummary>
<type name="server_no_data"/>
<desc>
@@ -1083,8 +1083,8 @@ enter><input>1.0er.</input>
</func>
<func>
- <name name="setopts" arity="1"/>
- <name name="setopts" arity="2"/>
+ <name name="setopts" arity="1" since=""/>
+ <name name="setopts" arity="2" since=""/>
<fsummary>Set options.</fsummary>
<desc>
<p>Set options for the standard I/O device
@@ -1198,8 +1198,8 @@ fun("") -> {yes, "quit", []};
</func>
<func>
- <name name="write" arity="1"/>
- <name name="write" arity="2"/>
+ <name name="write" arity="1" since=""/>
+ <name name="write" arity="2" since=""/>
<fsummary>Write a term.</fsummary>
<desc>
<p>Writes term <c><anno>Term</anno></c> to the standard output
diff --git a/lib/stdlib/doc/src/io_lib.xml b/lib/stdlib/doc/src/io_lib.xml
index a3df2897ac..4d527f8ed3 100644
--- a/lib/stdlib/doc/src/io_lib.xml
+++ b/lib/stdlib/doc/src/io_lib.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>io_lib</module>
+ <module since="">io_lib</module>
<modulesummary>I/O library functions.</modulesummary>
<description>
<p>This module contains functions for converting to and from
@@ -99,7 +99,7 @@
<funcs>
<func>
- <name name="build_text" arity="1"/>
+ <name name="build_text" arity="1" since="OTP 18.0"/>
<fsummary>Build the output text for a preparsed format list.</fsummary>
<desc>
<p>For details, see
@@ -108,7 +108,7 @@
</func>
<func>
- <name name="char_list" arity="1"/>
+ <name name="char_list" arity="1" since=""/>
<fsummary>Test for a list of characters.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is a flat list of
@@ -117,7 +117,7 @@
</func>
<func>
- <name name="deep_char_list" arity="1"/>
+ <name name="deep_char_list" arity="1" since=""/>
<fsummary>Test for a deep list of characters.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is a, possibly deep,
@@ -126,7 +126,7 @@
</func>
<func>
- <name name="deep_latin1_char_list" arity="1"/>
+ <name name="deep_latin1_char_list" arity="1" since="OTP R16B"/>
<fsummary>Test for a deep list of characters.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is a, possibly deep,
@@ -136,8 +136,8 @@
</func>
<func>
- <name name="format" arity="2"/>
- <name name="fwrite" arity="2"/>
+ <name name="format" arity="2" since=""/>
+ <name name="fwrite" arity="2" since=""/>
<fsummary>Write formatted output.</fsummary>
<desc>
<p>Returns a character list that represents <c><anno>Data</anno></c>
@@ -156,8 +156,8 @@
</func>
<func>
- <name name="format" arity="3"/>
- <name name="fwrite" arity="3"/>
+ <name name="format" arity="3" since="OTP 21.0"/>
+ <name name="fwrite" arity="3" since="OTP 21.0"/>
<fsummary>Write formatted output.</fsummary>
<desc>
<p>Returns a character list that represents <c><anno>Data</anno></c>
@@ -181,7 +181,7 @@
</func>
<func>
- <name name="fread" arity="2"/>
+ <name name="fread" arity="2" since=""/>
<fsummary>Read formatted input.</fsummary>
<desc>
<p>Tries to read <c><anno>String</anno></c> in accordance with the
@@ -222,7 +222,7 @@
</func>
<func>
- <name name="fread" arity="3"/>
+ <name name="fread" arity="3" since=""/>
<fsummary>Re-entrant formatted reader</fsummary>
<desc>
<p>This is the re-entrant formatted reader. The continuation of
@@ -268,7 +268,7 @@
</func>
<func>
- <name name="indentation" arity="2"/>
+ <name name="indentation" arity="2" since=""/>
<fsummary>Indentation after printing string.</fsummary>
<desc>
<p>Returns the indentation if <c><anno>String</anno></c> has been
@@ -277,7 +277,7 @@
</func>
<func>
- <name name="latin1_char_list" arity="1"/>
+ <name name="latin1_char_list" arity="1" since="OTP R16B"/>
<fsummary>Test for a list of ISO Latin-1 characters.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is a flat list of
@@ -286,7 +286,7 @@
</func>
<func>
- <name name="nl" arity="0"/>
+ <name name="nl" arity="0" since=""/>
<fsummary>Write a newline.</fsummary>
<desc>
<p>Returns a character list that represents a new line character.</p>
@@ -294,8 +294,8 @@
</func>
<func>
- <name name="print" arity="1"/>
- <name name="print" arity="4"/>
+ <name name="print" arity="1" since=""/>
+ <name name="print" arity="4" since=""/>
<fsummary>Pretty print a term.</fsummary>
<desc>
<p>Returns a list of characters that represents
@@ -315,7 +315,7 @@
</func>
<func>
- <name name="printable_latin1_list" arity="1"/>
+ <name name="printable_latin1_list" arity="1" since="OTP R16B"/>
<fsummary>Test for a list of printable ISO Latin-1 characters.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is a flat list of
@@ -324,7 +324,7 @@
</func>
<func>
- <name name="printable_list" arity="1"/>
+ <name name="printable_list" arity="1" since=""/>
<fsummary>Test for a list of printable characters.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is a flat list of
@@ -338,7 +338,7 @@
</func>
<func>
- <name name="printable_unicode_list" arity="1"/>
+ <name name="printable_unicode_list" arity="1" since="OTP R16B"/>
<fsummary>Test for a list of printable Unicode characters.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is a flat list of
@@ -347,7 +347,7 @@
</func>
<func>
- <name name="scan_format" arity="2"/>
+ <name name="scan_format" arity="2" since="OTP 18.0"/>
<fsummary>Parse all control sequences in the format string.</fsummary>
<desc>
<p>Returns a list corresponding to the specified format string,
@@ -373,7 +373,7 @@
</func>
<func>
- <name name="unscan_format" arity="1"/>
+ <name name="unscan_format" arity="1" since="OTP 18.0"/>
<fsummary>Revert a preparsed format list to a plain character list
and a list of arguments.</fsummary>
<desc>
@@ -383,9 +383,9 @@
</func>
<func>
- <name name="write" arity="1"/>
- <name name="write" arity="2" clause_i="1"/>
- <name name="write" arity="2" clause_i="2"/>
+ <name name="write" arity="1" since=""/>
+ <name name="write" arity="2" clause_i="1" since=""/>
+ <name name="write" arity="2" clause_i="2" since="OTP 20.0"/>
<fsummary>Write a term.</fsummary>
<desc>
<p>Returns a character list that represents <c><anno>Term</anno></c>.
@@ -411,7 +411,7 @@
</func>
<func>
- <name name="write_atom" arity="1"/>
+ <name name="write_atom" arity="1" since=""/>
<fsummary>Write an atom.</fsummary>
<desc>
<p>Returns the list of characters needed to print atom
@@ -420,7 +420,7 @@
</func>
<func>
- <name name="write_atom_as_latin1" arity="1"/>
+ <name name="write_atom_as_latin1" arity="1" since="OTP 20.0"/>
<fsummary>Write an atom.</fsummary>
<desc>
<p>Returns the list of characters needed to print atom
@@ -430,7 +430,7 @@
</func>
<func>
- <name name="write_char" arity="1"/>
+ <name name="write_char" arity="1" since=""/>
<fsummary>Write a character.</fsummary>
<desc>
<p>Returns the list of characters needed to print a character
@@ -439,7 +439,7 @@
</func>
<func>
- <name name="write_char_as_latin1" arity="1"/>
+ <name name="write_char_as_latin1" arity="1" since="OTP R16B"/>
<fsummary>Write a character.</fsummary>
<desc>
<p>Returns the list of characters needed to print a character
@@ -449,7 +449,7 @@
</func>
<func>
- <name name="write_latin1_char" arity="1"/>
+ <name name="write_latin1_char" arity="1" since="OTP R16B"/>
<fsummary>Write an ISO Latin-1 character.</fsummary>
<desc>
<p>Returns the list of characters needed to print a character
@@ -458,7 +458,7 @@
</func>
<func>
- <name name="write_latin1_string" arity="1"/>
+ <name name="write_latin1_string" arity="1" since="OTP R16B"/>
<fsummary>Write an ISO Latin-1 string.</fsummary>
<desc>
<p>Returns the list of characters needed to print
@@ -467,7 +467,7 @@
</func>
<func>
- <name name="write_string" arity="1"/>
+ <name name="write_string" arity="1" since=""/>
<fsummary>Write a string.</fsummary>
<desc>
<p>Returns the list of characters needed to print
@@ -476,7 +476,7 @@
</func>
<func>
- <name name="write_string_as_latin1" arity="1"/>
+ <name name="write_string_as_latin1" arity="1" since="OTP R16B"/>
<fsummary>Write a string.</fsummary>
<desc>
<p>Returns the list of characters needed to print
diff --git a/lib/stdlib/doc/src/lists.xml b/lib/stdlib/doc/src/lists.xml
index e4215a5336..2755fb3dce 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>2018</year>
+ <year>1996</year><year>2019</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -28,7 +28,7 @@
<date>1996-09-28</date>
<rev>A</rev>
</header>
- <module>lists</module>
+ <module since="">lists</module>
<modulesummary>List processing functions.</modulesummary>
<description>
<p>This module contains functions for list processing.</p>
@@ -63,7 +63,7 @@
<funcs>
<func>
- <name name="all" arity="2"/>
+ <name name="all" arity="2" since=""/>
<fsummary>Return <c>true</c> if all elements in a list satisfy
<c>Pred</c>.</fsummary>
<desc>
@@ -74,7 +74,7 @@
</func>
<func>
- <name name="any" arity="2"/>
+ <name name="any" arity="2" since=""/>
<fsummary>Return <c>true</c> if any of the elements in a list
satisfies <c>Pred</c>.</fsummary>
<desc>
@@ -85,7 +85,7 @@
</func>
<func>
- <name name="append" arity="1"/>
+ <name name="append" arity="1" since=""/>
<fsummary>Append a list of lists.</fsummary>
<desc>
<p>Returns a list in which all the sublists of
@@ -98,7 +98,7 @@
</func>
<func>
- <name name="append" arity="2"/>
+ <name name="append" arity="2" since=""/>
<fsummary>Append two lists.</fsummary>
<desc>
<p>Returns a new list <c><anno>List3</anno></c>, which is made from
@@ -113,7 +113,7 @@
</func>
<func>
- <name name="concat" arity="1"/>
+ <name name="concat" arity="1" since=""/>
<fsummary>Concatenate a list of atoms.</fsummary>
<desc>
<p>Concatenates the text representation of the elements of
@@ -127,7 +127,7 @@
</func>
<func>
- <name name="delete" arity="2"/>
+ <name name="delete" arity="2" since=""/>
<fsummary>Delete an element from a list.</fsummary>
<desc>
<p>Returns a copy of <c><anno>List1</anno></c> where the first element
@@ -137,7 +137,7 @@
</func>
<func>
- <name name="droplast" arity="1"/>
+ <name name="droplast" arity="1" since="OTP 17.0"/>
<fsummary>Drop the last element of a list.</fsummary>
<desc>
<p>Drops the last element of a <c><anno>List</anno></c>. The list is to
@@ -147,7 +147,7 @@
</func>
<func>
- <name name="dropwhile" arity="2"/>
+ <name name="dropwhile" arity="2" since=""/>
<fsummary>Drop elements from a list while a predicate is <c>true</c>.
</fsummary>
<desc>
@@ -159,7 +159,7 @@
</func>
<func>
- <name name="duplicate" arity="2"/>
+ <name name="duplicate" arity="2" since=""/>
<fsummary>Make <c>N</c> copies of element.</fsummary>
<desc>
<p>Returns a list containing <c><anno>N</anno></c> copies of term
@@ -172,7 +172,7 @@
</func>
<func>
- <name name="filter" arity="2"/>
+ <name name="filter" arity="2" since=""/>
<fsummary>Select elements that satisfy a predicate.</fsummary>
<desc>
<p><c><anno>List2</anno></c> is a list of all elements
@@ -182,7 +182,7 @@
</func>
<func>
- <name name="filtermap" arity="2"/>
+ <name name="filtermap" arity="2" since="OTP R16B01"/>
<fsummary>Filter and map elements that satisfy a function.</fsummary>
<desc>
<p>Calls <c><anno>Fun</anno>(<anno>Elem</anno>)</c> on successive
@@ -211,7 +211,7 @@ filtermap(Fun, List1) ->
</func>
<func>
- <name name="flatlength" arity="1"/>
+ <name name="flatlength" arity="1" since=""/>
<fsummary>Length of flattened deep list.</fsummary>
<desc>
<p>Equivalent to <c>length(flatten(<anno>DeepList</anno>))</c>, but
@@ -220,7 +220,7 @@ filtermap(Fun, List1) ->
</func>
<func>
- <name name="flatmap" arity="2"/>
+ <name name="flatmap" arity="2" since=""/>
<fsummary>Map and flatten in one pass.</fsummary>
<desc>
<p>Takes a function from <c><anno>A</anno></c>s to lists of
@@ -241,7 +241,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="flatten" arity="1"/>
+ <name name="flatten" arity="1" since=""/>
<fsummary>Flatten a deep list.</fsummary>
<desc>
<p>Returns a flattened version of <c><anno>DeepList</anno></c>.</p>
@@ -249,7 +249,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="flatten" arity="2"/>
+ <name name="flatten" arity="2" since=""/>
<fsummary>Flatten a deep list.</fsummary>
<desc>
<p>Returns a flattened version of <c><anno>DeepList</anno></c> with tail
@@ -258,7 +258,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="foldl" arity="3"/>
+ <name name="foldl" arity="3" since=""/>
<fsummary>Fold a function over a list.</fsummary>
<desc>
<p>Calls <c><anno>Fun</anno>(<anno>Elem</anno>, <anno>AccIn</anno>)</c>
@@ -278,7 +278,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="foldr" arity="3"/>
+ <name name="foldr" arity="3" since=""/>
<fsummary>Fold a function over a list.</fsummary>
<desc>
<p>Like <seealso marker="#foldl/3"><c>foldl/3</c></seealso>, but the
@@ -297,7 +297,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="join" arity="2"/>
+ <name name="join" arity="2" since="OTP 19.0"/>
<fsummary>Insert an element between elements in a list</fsummary>
<desc>
<p>Inserts <c><anno>Sep</anno></c> between each element in <c><anno>List1</anno></c>. Has no
@@ -312,7 +312,7 @@ flatmap(Fun, List1) ->
</desc>
</func>
<func>
- <name name="foreach" arity="2"/>
+ <name name="foreach" arity="2" since=""/>
<fsummary>Apply a function to each element of a list.</fsummary>
<desc>
<p>Calls <c><anno>Fun</anno>(<anno>Elem</anno>)</c> for each element
@@ -324,7 +324,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="keydelete" arity="3"/>
+ <name name="keydelete" arity="3" since=""/>
<fsummary>Delete an element from a list of tuples.</fsummary>
<type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc>
<desc>
@@ -336,7 +336,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="keyfind" arity="3"/>
+ <name name="keyfind" arity="3" since=""/>
<fsummary>Search for an element in a list of tuples.</fsummary>
<type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc>
<desc>
@@ -349,7 +349,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="keymap" arity="3"/>
+ <name name="keymap" arity="3" since=""/>
<fsummary>Map a function over a list of tuples.</fsummary>
<type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc>
<desc>
@@ -368,7 +368,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="keymember" arity="3"/>
+ <name name="keymember" arity="3" since=""/>
<fsummary>Test for membership of a list of tuples.</fsummary>
<type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc>
<desc>
@@ -379,7 +379,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="keymerge" arity="3"/>
+ <name name="keymerge" arity="3" since=""/>
<fsummary>Merge two key-sorted lists of tuples.</fsummary>
<type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc>
<desc>
@@ -395,7 +395,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="keyreplace" arity="4"/>
+ <name name="keyreplace" arity="4" since=""/>
<fsummary>Replace an element in a list of tuples.</fsummary>
<type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc>
<desc>
@@ -407,7 +407,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="keysearch" arity="3"/>
+ <name name="keysearch" arity="3" since=""/>
<fsummary>Search for an element in a list of tuples.</fsummary>
<type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc>
<desc>
@@ -425,7 +425,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="keysort" arity="2"/>
+ <name name="keysort" arity="2" since=""/>
<fsummary>Sort a list of tuples.</fsummary>
<type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc>
<desc>
@@ -436,7 +436,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="keystore" arity="4"/>
+ <name name="keystore" arity="4" since=""/>
<fsummary>Store an element in a list of tuples.</fsummary>
<type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc>
<desc>
@@ -452,7 +452,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="keytake" arity="3"/>
+ <name name="keytake" arity="3" since=""/>
<fsummary>Extract an element from a list of tuples.</fsummary>
<type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc>
<desc>
@@ -467,7 +467,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="last" arity="1"/>
+ <name name="last" arity="1" since=""/>
<fsummary>Return last element in a list.</fsummary>
<desc>
<p>Returns the last element in <c><anno>List</anno></c>.</p>
@@ -475,7 +475,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="map" arity="2"/>
+ <name name="map" arity="2" since=""/>
<fsummary>Map a function over a list.</fsummary>
<desc>
<p>Takes a function from <c><anno>A</anno></c>s to
@@ -488,7 +488,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="mapfoldl" arity="3"/>
+ <name name="mapfoldl" arity="3" since=""/>
<fsummary>Map and fold in one pass.</fsummary>
<desc>
<p>Combines the operations of
@@ -504,7 +504,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="mapfoldr" arity="3"/>
+ <name name="mapfoldr" arity="3" since=""/>
<fsummary>Map and fold in one pass.</fsummary>
<desc>
<p>Combines the operations of
@@ -514,7 +514,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="max" arity="1"/>
+ <name name="max" arity="1" since=""/>
<fsummary>Return maximum element of a list.</fsummary>
<desc>
<p>Returns the first element of <c><anno>List</anno></c> that compares
@@ -524,7 +524,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="member" arity="2"/>
+ <name name="member" arity="2" since=""/>
<fsummary>Test for membership of a list.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Elem</anno></c> matches some element
@@ -533,7 +533,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="merge" arity="1"/>
+ <name name="merge" arity="1" since=""/>
<fsummary>Merge a list of sorted lists.</fsummary>
<desc>
<p>Returns the sorted list formed by merging all the sublists of
@@ -546,7 +546,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="merge" arity="2"/>
+ <name name="merge" arity="2" since=""/>
<fsummary>Merge two sorted lists.</fsummary>
<desc>
<p>Returns the sorted list formed by merging <c><anno>List1</anno></c>
@@ -559,7 +559,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="merge" arity="3"/>
+ <name name="merge" arity="3" since=""/>
<fsummary>Merge two sorted list.</fsummary>
<desc>
<p>Returns the sorted list formed by merging <c><anno>List1</anno></c>
@@ -577,7 +577,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="merge3" arity="3"/>
+ <name name="merge3" arity="3" since=""/>
<fsummary>Merge three sorted lists.</fsummary>
<desc>
<p>Returns the sorted list formed by merging <c><anno>List1</anno></c>,
@@ -593,7 +593,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="min" arity="1"/>
+ <name name="min" arity="1" since=""/>
<fsummary>Return minimum element of a list.</fsummary>
<desc>
<p>Returns the first element of <c><anno>List</anno></c> that compares
@@ -603,7 +603,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="nth" arity="2"/>
+ <name name="nth" arity="2" since=""/>
<fsummary>Return the <c>N</c>th element of a list.</fsummary>
<type_desc variable="N">1..length(<anno>List</anno>)</type_desc>
<desc>
@@ -617,7 +617,7 @@ c</pre>
</func>
<func>
- <name name="nthtail" arity="2"/>
+ <name name="nthtail" arity="2" since=""/>
<fsummary>Return the <c>N</c>th tail of a list.</fsummary>
<type_desc variable="N">0..length(<anno>List</anno>)</type_desc>
<desc>
@@ -638,7 +638,7 @@ c</pre>
</func>
<func>
- <name name="partition" arity="2"/>
+ <name name="partition" arity="2" since=""/>
<fsummary>Partition a list into two lists based on a predicate.</fsummary>
<desc>
<p>Partitions <c><anno>List</anno></c> into two lists, where the first
@@ -658,7 +658,7 @@ c</pre>
</func>
<func>
- <name name="prefix" arity="2"/>
+ <name name="prefix" arity="2" since=""/>
<fsummary>Test for list prefix.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>List1</anno></c> is a prefix of
@@ -667,7 +667,7 @@ c</pre>
</func>
<func>
- <name name="reverse" arity="1"/>
+ <name name="reverse" arity="1" since=""/>
<fsummary>Reverse a list.</fsummary>
<desc>
<p>Returns a list with the elements in <c><anno>List1</anno></c>
@@ -676,7 +676,7 @@ c</pre>
</func>
<func>
- <name name="reverse" arity="2"/>
+ <name name="reverse" arity="2" since=""/>
<fsummary>Reverse a list appending a tail.</fsummary>
<desc>
<p>Returns a list with the elements in <c><anno>List1</anno></c>
@@ -689,8 +689,20 @@ c</pre>
</func>
<func>
- <name name="seq" arity="2"/>
- <name name="seq" arity="3"/>
+ <name name="search" arity="2" since="OTP 21.0"/>
+ <fsummary>Find the first element that satisfies a predicate.</fsummary>
+ <desc>
+ <p>If there is a <c><anno>Value</anno></c> in <c><anno>List</anno></c>
+ such that <c><anno>Pred</anno>(<anno>Value</anno>)</c> returns
+ <c>true</c>, returns <c>{value, <anno>Value</anno>}</c>
+ for the first such <c><anno>Value</anno></c>,
+ otherwise returns <c>false</c>.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="seq" arity="2" since=""/>
+ <name name="seq" arity="3" since=""/>
<fsummary>Generate a sequence of integers.</fsummary>
<desc>
<p>Returns a sequence of integers that starts with
@@ -736,7 +748,7 @@ length(lists:seq(From, To, Incr)) =:= (To - From + Incr) div Incr</code>
</func>
<func>
- <name name="sort" arity="1"/>
+ <name name="sort" arity="1" since=""/>
<fsummary>Sort a list.</fsummary>
<desc>
<p>Returns a list containing the sorted elements of
@@ -745,7 +757,7 @@ length(lists:seq(From, To, Incr)) =:= (To - From + Incr) div Incr</code>
</func>
<func>
- <name name="sort" arity="2"/>
+ <name name="sort" arity="2" since=""/>
<fsummary>Sort a list.</fsummary>
<desc>
<p>Returns a list containing the sorted elements of
@@ -759,7 +771,7 @@ length(lists:seq(From, To, Incr)) =:= (To - From + Incr) div Incr</code>
</func>
<func>
- <name name="split" arity="2"/>
+ <name name="split" arity="2" since=""/>
<fsummary>Split a list into two lists.</fsummary>
<type_desc variable="N">0..length(<anno>List1</anno>)</type_desc>
<desc>
@@ -771,19 +783,7 @@ length(lists:seq(From, To, Incr)) =:= (To - From + Incr) div Incr</code>
</func>
<func>
- <name name="search" arity="2"/>
- <fsummary>Find the first element that satisfies a predicate.</fsummary>
- <desc>
- <p>If there is a <c><anno>Value</anno></c> in <c><anno>List</anno></c>
- such that <c><anno>Pred</anno>(<anno>Value</anno>)</c> returns
- <c>true</c>, returns <c>{value, <anno>Value</anno>}</c>
- for the first such <c><anno>Value</anno></c>,
- otherwise returns <c>false</c>.</p>
- </desc>
- </func>
-
- <func>
- <name name="splitwith" arity="2"/>
+ <name name="splitwith" arity="2" since=""/>
<fsummary>Split a list into two lists based on a predicate.</fsummary>
<desc>
<p>Partitions <c><anno>List</anno></c> into two lists according to
@@ -804,7 +804,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="sublist" arity="2"/>
+ <name name="sublist" arity="2" since=""/>
<fsummary>Return a sublist of a certain length, starting at the first
position.</fsummary>
<desc>
@@ -816,7 +816,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="sublist" arity="3"/>
+ <name name="sublist" arity="3" since=""/>
<fsummary>Return a sublist starting at a specified position and with a
specified number of elements.</fsummary>
<type_desc variable="Start">1..(length(<anno>List1</anno>)+1)</type_desc>
@@ -838,7 +838,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="subtract" arity="2"/>
+ <name name="subtract" arity="2" since=""/>
<fsummary>Subtract the element in one list from another list.</fsummary>
<desc>
<p>Returns a new list <c><anno>List3</anno></c> that is a copy of
@@ -854,7 +854,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="suffix" arity="2"/>
+ <name name="suffix" arity="2" since=""/>
<fsummary>Test for list suffix.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>List1</anno></c> is a suffix of
@@ -863,7 +863,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="sum" arity="1"/>
+ <name name="sum" arity="1" since=""/>
<fsummary>Return the sum of elements in a list.</fsummary>
<desc>
<p>Returns the sum of the elements in <c><anno>List</anno></c>.</p>
@@ -871,7 +871,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="takewhile" arity="2"/>
+ <name name="takewhile" arity="2" since=""/>
<fsummary>Take elements from a list while a predicate is <c>true</c>.
</fsummary>
<desc>
@@ -884,7 +884,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="ukeymerge" arity="3"/>
+ <name name="ukeymerge" arity="3" since=""/>
<fsummary>Merge two key-sorted lists of tuples, removing duplicates.
</fsummary>
<type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc>
@@ -902,7 +902,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="ukeysort" arity="2"/>
+ <name name="ukeysort" arity="2" since=""/>
<fsummary>Sort a list of tuples, removing duplicates.</fsummary>
<type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc>
<desc>
@@ -914,7 +914,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="umerge" arity="1"/>
+ <name name="umerge" arity="1" since=""/>
<fsummary>Merge a list of sorted lists, removing duplicates.</fsummary>
<desc>
<p>Returns the sorted list formed by merging all the sublists
@@ -927,7 +927,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="umerge" arity="2"/>
+ <name name="umerge" arity="2" since=""/>
<fsummary>Merge two sorted lists, removing duplicates.</fsummary>
<desc>
<p>Returns the sorted list formed by merging <c><anno>List1</anno></c>
@@ -941,7 +941,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="umerge" arity="3"/>
+ <name name="umerge" arity="3" since=""/>
<fsummary>Merge two sorted lists, removing duplicates.</fsummary>
<desc>
<p>Returns the sorted list formed by merging <c><anno>List1</anno></c>
@@ -958,7 +958,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="umerge3" arity="3"/>
+ <name name="umerge3" arity="3" since=""/>
<fsummary>Merge three sorted lists, removing duplicates.</fsummary>
<desc>
<p>Returns the sorted list formed by merging <c><anno>List1</anno></c>,
@@ -973,7 +973,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="unzip" arity="1"/>
+ <name name="unzip" arity="1" since=""/>
<fsummary>Unzip a list of two-tuples into two lists.</fsummary>
<desc>
<p>"Unzips" a list of two-tuples into two lists, where the first
@@ -983,7 +983,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="unzip3" arity="1"/>
+ <name name="unzip3" arity="1" since=""/>
<fsummary>Unzip a list of three-tuples into three lists.</fsummary>
<desc>
<p>"Unzips" a list of three-tuples into three lists, where
@@ -994,7 +994,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="usort" arity="1"/>
+ <name name="usort" arity="1" since=""/>
<fsummary>Sort a list, removing duplicates.</fsummary>
<desc>
<p>Returns a list containing the sorted elements of
@@ -1004,7 +1004,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="usort" arity="2"/>
+ <name name="usort" arity="2" since=""/>
<fsummary>Sort a list, removing duplicates.</fsummary>
<desc>
<p>Returns a list containing the sorted elements of
@@ -1019,7 +1019,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="zip" arity="2"/>
+ <name name="zip" arity="2" since=""/>
<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,
@@ -1030,7 +1030,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="zip3" arity="3"/>
+ <name name="zip3" arity="3" since=""/>
<fsummary>Zip three lists into a list of three-tuples.</fsummary>
<desc>
<p>"Zips" three lists of equal length into one list of
@@ -1042,7 +1042,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="zipwith" arity="3"/>
+ <name name="zipwith" arity="3" since=""/>
<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.
@@ -1059,7 +1059,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="zipwith3" arity="4"/>
+ <name name="zipwith3" arity="4" since=""/>
<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
diff --git a/lib/stdlib/doc/src/log_mf_h.xml b/lib/stdlib/doc/src/log_mf_h.xml
index edc3d31025..b922006cc0 100644
--- a/lib/stdlib/doc/src/log_mf_h.xml
+++ b/lib/stdlib/doc/src/log_mf_h.xml
@@ -34,7 +34,7 @@
<rev>A</rev>
<file>log_mf_h.xml</file>
</header>
- <module>log_mf_h</module>
+ <module since="">log_mf_h</module>
<modulesummary>An event handler that logs events to disk.</modulesummary>
<description>
<p>This module is a <c>gen_event</c> handler module that can be installed
@@ -60,8 +60,8 @@
<funcs>
<func>
- <name name="init" arity="3"/>
- <name name="init" arity="4"/>
+ <name name="init" arity="3" since=""/>
+ <name name="init" arity="4" since=""/>
<fsummary>Initiate the event handler.</fsummary>
<desc>
<p>Initiates the event handler. Returns <c><anno>Args</anno></c>, which
diff --git a/lib/stdlib/doc/src/maps.xml b/lib/stdlib/doc/src/maps.xml
index a225dea3b5..8e88882b56 100644
--- a/lib/stdlib/doc/src/maps.xml
+++ b/lib/stdlib/doc/src/maps.xml
@@ -27,7 +27,7 @@
<date>2014-02-28</date>
<rev>A</rev>
</header>
- <module>maps</module>
+ <module since="OTP 17.0">maps</module>
<modulesummary>Maps processing functions.</modulesummary>
<description>
<p>This module contains functions for maps processing.</p>
@@ -35,9 +35,10 @@
<datatypes>
<datatype>
- <name name="iterator"/>
+ <name name="iterator" n_vars="2"/>
<desc>
- <p>An iterator representing the key value associations in a map.</p>
+ <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 <seealso marker="#iterator-1"><c>maps:iterator/1</c></seealso>.</p>
<p>Consumed by <seealso marker="#next-1"><c>maps:next/1</c></seealso>,
<seealso marker="#filter-2"><c>maps:filter/2</c></seealso>,
@@ -45,11 +46,15 @@
<seealso marker="#map-2"><c>maps:map/2</c></seealso>.</p>
</desc>
</datatype>
+
+ <datatype>
+ <name name="iterator" n_vars="0"/>
+ </datatype>
</datatypes>
<funcs>
<func>
- <name name="filter" arity="2"/>
+ <name name="filter" arity="2" since="OTP 18.0"/>
<fsummary>Select pairs that satisfy a predicate.</fsummary>
<desc>
<p>Returns a map <c><anno>Map</anno></c> for which predicate
@@ -68,7 +73,7 @@
</func>
<func>
- <name name="find" arity="2"/>
+ <name name="find" arity="2" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Returns a tuple <c>{ok, Value}</c>, where <c><anno>Value</anno></c>
@@ -87,16 +92,16 @@
</func>
<func>
- <name name="fold" arity="3"/>
+ <name name="fold" arity="3" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
- <p>Calls <c>F(K, V, AccIn)</c> for every <c><anno>K</anno></c> to value
- <c><anno>V</anno></c> association in <c><anno>MapOrIter</anno></c> in
- any order. Function <c>fun F/3</c> must return a new
- accumulator, which is passed to the next successive call.
- This function returns the final value of the accumulator. The initial
- accumulator value <c><anno>Init</anno></c> is returned if the map is
- empty.</p>
+ <p>Calls <c>F(Key, Value, AccIn)</c> for every <c><anno>Key</anno></c>
+ to value <c><anno>Value</anno></c> association in
+ <c><anno>MapOrIter</anno></c> in any order. Function <c>fun F/3</c>
+ must return a new accumulator, which is passed to the next successive
+ call. This function returns the final value of the accumulator.
+ The initial accumulator value <c><anno>Init</anno></c> is returned
+ if the map is empty.</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,
or with <c>badarg</c> if <c><anno>Fun</anno></c> is not a
@@ -111,7 +116,7 @@
</func>
<func>
- <name name="from_list" arity="1"/>
+ <name name="from_list" arity="1" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Takes a list of key-value tuples elements and builds a map. The
@@ -128,7 +133,7 @@
</func>
<func>
- <name name="get" arity="2"/>
+ <name name="get" arity="2" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Returns value <c><anno>Value</anno></c> associated with
@@ -147,7 +152,7 @@
</func>
<func>
- <name name="get" arity="3"/>
+ <name name="get" arity="3" since="OTP 17.1"/>
<fsummary></fsummary>
<desc>
<p>Returns value <c><anno>Value</anno></c> associated with
@@ -168,7 +173,7 @@ val1
</func>
<func>
- <name name="is_key" arity="2"/>
+ <name name="is_key" arity="2" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Returns <c>true</c> if map <c><anno>Map</anno></c> contains
@@ -188,7 +193,7 @@ false</code>
</func>
<func>
- <name name="iterator" arity="1"/>
+ <name name="iterator" arity="1" since="OTP 21.0"/>
<fsummary>Create a map iterator.</fsummary>
<desc>
<p>Returns a map iterator <c><anno>Iterator</anno></c> that can
@@ -202,19 +207,19 @@ false</code>
<code type="none">
> M = #{ a => 1, b => 2 }.
#{a => 1,b => 2}
-> I = maps:iterator(M).
-[{a,1},{b,2}]
-> {K1, V1, I2} = maps:next(I).
-{a,1,[{b,2}]}
-> {K2, V2, I3} = maps:next(I2).
-{b,2,[]}
+> I = maps:iterator(M), ok.
+ok
+> {K1, V1, I2} = maps:next(I), {K1, V1}.
+{a,1}
+> {K2, V2, I3} = maps:next(I2),{K2, V2}.
+{b,2}
> maps:next(I3).
none</code>
</desc>
</func>
<func>
- <name name="keys" arity="1"/>
+ <name name="keys" arity="1" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Returns a complete list of keys, in any order, which resides
@@ -230,15 +235,16 @@ none</code>
</func>
<func>
- <name name="map" arity="2"/>
+ <name name="map" arity="2" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Produces a new map <c><anno>Map</anno></c> by calling function
- <c>fun F(K, V1)</c> for every <c><anno>K</anno></c> to value
- <c><anno>V1</anno></c> association in <c><anno>MapOrIter</anno></c> in
- any order. Function <c>fun F/2</c> must return value
- <c><anno>V2</anno></c> to be associated with key <c><anno>K</anno></c>
- for the new map <c><anno>Map</anno></c>.</p>
+ <c>fun F(Key, Value1)</c> for every <c><anno>Key</anno></c> to value
+ <c><anno>Value1</anno></c> association in
+ <c><anno>MapOrIter</anno></c> in any order. Function <c>fun Fun/2</c>
+ must return value <c><anno>Value2</anno></c> to be associated with
+ key <c><anno>Key</anno></c> for the new map
+ <c><anno>Map</anno></c>.</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,
or with <c>badarg</c> if <c><anno>Fun</anno></c> is not a
@@ -253,7 +259,7 @@ none</code>
</func>
<func>
- <name name="merge" arity="2"/>
+ <name name="merge" arity="2" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Merges two maps into a single map <c><anno>Map3</anno></c>. If two
@@ -271,7 +277,7 @@ none</code>
</func>
<func>
- <name name="new" arity="0"/>
+ <name name="new" arity="0" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Returns a new empty map.</p>
@@ -283,7 +289,7 @@ none</code>
</func>
<func>
- <name name="next" arity="1"/>
+ <name name="next" arity="1" since="OTP 21.0"/>
<fsummary>Get the next key and value from an iterator.</fsummary>
<desc>
<p>Returns the next key-value association in
@@ -298,21 +304,21 @@ none</code>
<code type="none">
> Map = #{a => 1, b => 2, c => 3}.
#{a => 1,b => 2,c => 3}
-> Iter = maps:iterator(Map).
-[{a,1},{b,2},{c,3}]
-> {_, _, Iter1} = maps:next(Iter).
-{a,1,[{b,2},{c,3}]}
-> {_, _, Iter2} = maps:next(Iter1).
-{b,2,[{c,3}]}
-> {_, _, Iter3} = maps:next(Iter2).
-{c,3,[]}
-> maps:next(Iter3).
+> I = maps:iterator(Map), ok.
+ok
+> {K1, V1, I1} = maps:next(I), {K1, V1}.
+{a,1}
+> {K2, V2, I2} = maps:next(I1), {K2, V2}.
+{b,2}
+> {K3, V3, I3} = maps:next(I2), {K3, V3}.
+{c,3}
+> maps:next(I3).
none</code>
</desc>
</func>
<func>
- <name name="put" arity="3"/>
+ <name name="put" arity="3" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Associates <c><anno>Key</anno></c> with value
@@ -336,7 +342,7 @@ none</code>
</func>
<func>
- <name name="remove" arity="2"/>
+ <name name="remove" arity="2" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Removes the <c><anno>Key</anno></c>, if it exists, and its
@@ -356,7 +362,7 @@ none</code>
</func>
<func>
- <name name="size" arity="1"/>
+ <name name="size" arity="1" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Returns the number of key-value associations in
@@ -370,7 +376,7 @@ none</code>
</func>
<func>
- <name name="take" arity="2"/>
+ <name name="take" arity="2" since="OTP 19.0"/>
<fsummary></fsummary>
<desc>
<p>The function removes the <c><anno>Key</anno></c>, if it
@@ -395,7 +401,7 @@ error</code>
</func>
<func>
- <name name="to_list" arity="1"/>
+ <name name="to_list" arity="1" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Returns a list of pairs representing the key-value associations of
@@ -412,7 +418,7 @@ error</code>
</func>
<func>
- <name name="update" arity="3"/>
+ <name name="update" arity="3" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>If <c><anno>Key</anno></c> exists in <c><anno>Map1</anno></c>, the
@@ -432,7 +438,7 @@ error</code>
</func>
<func>
- <name name="update_with" arity="3"/>
+ <name name="update_with" arity="3" since="OTP 19.0"/>
<fsummary></fsummary>
<desc>
<p>Update a value in a <c><anno>Map1</anno></c> associated
@@ -451,7 +457,7 @@ error</code>
</func>
<func>
- <name name="update_with" arity="4"/>
+ <name name="update_with" arity="4" since="OTP 19.0"/>
<fsummary></fsummary>
<desc>
<p>Update a value in a <c><anno>Map1</anno></c> associated
@@ -471,7 +477,7 @@ error</code>
</func>
<func>
- <name name="values" arity="1"/>
+ <name name="values" arity="1" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Returns a complete list of values, in arbitrary order, contained in
@@ -487,7 +493,7 @@ error</code>
</func>
<func>
- <name name="with" arity="2"/>
+ <name name="with" arity="2" since="OTP 17.3"/>
<fsummary></fsummary>
<desc>
<p>Returns a new map <c><anno>Map2</anno></c> with the keys <c>K1</c>
@@ -504,7 +510,7 @@ error</code>
</func>
<func>
- <name name="without" arity="2"/>
+ <name name="without" arity="2" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Returns a new map <c><anno>Map2</anno></c> without keys <c>K1</c>
diff --git a/lib/stdlib/doc/src/math.xml b/lib/stdlib/doc/src/math.xml
index b4f096217a..d89310e2c8 100644
--- a/lib/stdlib/doc/src/math.xml
+++ b/lib/stdlib/doc/src/math.xml
@@ -34,7 +34,7 @@
<rev>B</rev>
<file>math.xml</file>
</header>
- <module>math</module>
+ <module since="">math</module>
<modulesummary>Mathematical functions.</modulesummary>
<description>
<p>This module provides an interface to a number of mathematical
@@ -50,28 +50,28 @@
<funcs>
<func>
- <name name="acos" arity="1"/>
- <name name="acosh" arity="1"/>
- <name name="asin" arity="1"/>
- <name name="asinh" arity="1"/>
- <name name="atan" arity="1"/>
- <name name="atan2" arity="2"/>
- <name name="atanh" arity="1"/>
- <name name="ceil" arity="1"/>
- <name name="cos" arity="1"/>
- <name name="cosh" arity="1"/>
- <name name="exp" arity="1"/>
- <name name="floor" arity="1"/>
- <name name="fmod" arity="2"/>
- <name name="log" arity="1"/>
- <name name="log10" arity="1"/>
- <name name="log2" arity="1"/>
- <name name="pow" arity="2"/>
- <name name="sin" arity="1"/>
- <name name="sinh" arity="1"/>
- <name name="sqrt" arity="1"/>
- <name name="tan" arity="1"/>
- <name name="tanh" arity="1"/>
+ <name name="acos" arity="1" since=""/>
+ <name name="acosh" arity="1" since=""/>
+ <name name="asin" arity="1" since=""/>
+ <name name="asinh" arity="1" since=""/>
+ <name name="atan" arity="1" since=""/>
+ <name name="atan2" arity="2" since=""/>
+ <name name="atanh" arity="1" since=""/>
+ <name name="ceil" arity="1" since="OTP 20.0"/>
+ <name name="cos" arity="1" since=""/>
+ <name name="cosh" arity="1" since=""/>
+ <name name="exp" arity="1" since=""/>
+ <name name="floor" arity="1" since="OTP 20.0"/>
+ <name name="fmod" arity="2" since="OTP 20.0"/>
+ <name name="log" arity="1" since=""/>
+ <name name="log10" arity="1" since=""/>
+ <name name="log2" arity="1" since="OTP 18.0"/>
+ <name name="pow" arity="2" since=""/>
+ <name name="sin" arity="1" since=""/>
+ <name name="sinh" arity="1" since=""/>
+ <name name="sqrt" arity="1" since=""/>
+ <name name="tan" arity="1" since=""/>
+ <name name="tanh" arity="1" since=""/>
<fsummary>Diverse math functions.</fsummary>
<type variable="X" name_i="6"/>
<type variable="Y" name_i="6"/>
@@ -82,7 +82,7 @@
</func>
<func>
- <name name="erf" arity="1"/>
+ <name name="erf" arity="1" since=""/>
<fsummary>Error function.</fsummary>
<desc>
<p>Returns the error function of <c><anno>X</anno></c>, where:</p>
@@ -92,7 +92,7 @@ erf(X) = 2/sqrt(pi)*integral from 0 to X of exp(-t*t) dt.</pre>
</func>
<func>
- <name name="erfc" arity="1"/>
+ <name name="erfc" arity="1" since=""/>
<fsummary>Another error function.</fsummary>
<desc>
<p><c>erfc(X)</c> returns <c>1.0</c> - <c>erf(X)</c>, computed by
@@ -101,7 +101,7 @@ erf(X) = 2/sqrt(pi)*integral from 0 to X of exp(-t*t) dt.</pre>
</func>
<func>
- <name name="pi" arity="0"/>
+ <name name="pi" arity="0" since=""/>
<fsummary>A useful number.</fsummary>
<desc>
<p>A useful number.</p>
diff --git a/lib/stdlib/doc/src/ms_transform.xml b/lib/stdlib/doc/src/ms_transform.xml
index 0a05fa37c5..65cc150507 100644
--- a/lib/stdlib/doc/src/ms_transform.xml
+++ b/lib/stdlib/doc/src/ms_transform.xml
@@ -32,7 +32,7 @@
<rev>C</rev>
<file>ms_transform.xml</file>
</header>
- <module>ms_transform</module>
+ <module since="">ms_transform</module>
<modulesummary>A parse transformation that translates fun syntax into match
specifications.</modulesummary>
<description>
@@ -731,7 +731,7 @@ ets:select(Table, [{{'$1',test,'$2'},[],['$_']}]).</code>
<funcs>
<func>
- <name name="format_error" arity="1"/>
+ <name name="format_error" arity="1" since=""/>
<fsummary>Error formatting function as required by the parse transformation interface.</fsummary>
<desc>
<p>Takes an error code returned by one of the other functions
@@ -741,7 +741,7 @@ ets:select(Table, [{{'$1',test,'$2'},[],['$_']}]).</code>
</func>
<func>
- <name name="parse_transform" arity="2"/>
+ <name name="parse_transform" arity="2" since=""/>
<fsummary>Transforms Erlang abstract format containing calls to
ets/dbg:fun2ms/1 into literal match specifications.</fsummary>
<type_desc variable="Options">Option list, required but not used.
@@ -762,7 +762,7 @@ ets:select(Table, [{{'$1',test,'$2'},[],['$_']}]).</code>
</func>
<func>
- <name name="transform_from_shell" arity="3"/>
+ <name name="transform_from_shell" arity="3" since=""/>
<fsummary>Used when transforming funs created in the shell into
match_specifications.</fsummary>
<type_desc variable="BoundEnvironment">List of variable bindings in the
diff --git a/lib/stdlib/doc/src/notes.xml b/lib/stdlib/doc/src/notes.xml
index 7ba19a98ea..dee7136eb1 100644
--- a/lib/stdlib/doc/src/notes.xml
+++ b/lib/stdlib/doc/src/notes.xml
@@ -31,6 +31,21 @@
</header>
<p>This document describes the changes made to the STDLIB application.</p>
+<section><title>STDLIB 3.7.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p> Optimize pretty printing of terms. The slower
+ behaviour was introduced in Erlang/OTP 20. </p>
+ <p>
+ Own Id: OTP-15573 Aux Id: ERIERL-306 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>STDLIB 3.7</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -3803,7 +3818,7 @@
you use erlang:halt/2 with an integer first argument and
an option list containing {flush,false} as the second
argument. Note that now is flushing not dependant of the
- exit code, and you can not only flush async threads
+ exit code, and you cannot only flush async threads
operations which we deemed as a strange behaviour anyway.
</p>
<p>Also, erlang:halt/1,2 has gotten a new feature: If the
@@ -4317,9 +4332,9 @@
Supervisors should not save child-specs for temporary
processes when they terminate as they should not be
restarted. Saving the temporary child spec will result in
- that you can not start a new temporary process with the
+ that you cannot start a new temporary process with the
same child spec as an already terminated temporary
- process. Since R14B02 you can not restart a temporary
+ process. Since R14B02 you cannot restart a temporary
temporary process as arguments are no longer saved, it
has however always been semantically incorrect to restart
a temporary process. Thanks to Filipe David Manana for
diff --git a/lib/stdlib/doc/src/orddict.xml b/lib/stdlib/doc/src/orddict.xml
index 26bbf499c6..27ccccee7e 100644
--- a/lib/stdlib/doc/src/orddict.xml
+++ b/lib/stdlib/doc/src/orddict.xml
@@ -32,7 +32,7 @@
<rev>B</rev>
<file>orddict.xml</file>
</header>
- <module>orddict</module>
+ <module since="">orddict</module>
<modulesummary>Key-value dictionary as ordered list.</modulesummary>
<description>
<p>This module provides a <c>Key</c>-<c>Value</c> dictionary.
@@ -61,7 +61,7 @@
<funcs>
<func>
- <name name="append" arity="3"/>
+ <name name="append" arity="3" since=""/>
<fsummary>Append a value to keys in a dictionary.</fsummary>
<desc>
<p>Appends a new <c><anno>Value</anno></c> to the current list
@@ -73,7 +73,7 @@
</func>
<func>
- <name name="append_list" arity="3"/>
+ <name name="append_list" arity="3" since=""/>
<fsummary>Append new values to keys in a dictionary.</fsummary>
<desc>
<p>Appends a list of values <c><anno>ValList</anno></c> to
@@ -85,7 +85,7 @@
</func>
<func>
- <name name="erase" arity="2"/>
+ <name name="erase" arity="2" since=""/>
<fsummary>Erase a key from a dictionary.</fsummary>
<desc>
<p>Erases all items with a specified key from a dictionary.</p>
@@ -93,7 +93,7 @@
</func>
<func>
- <name name="fetch" arity="2"/>
+ <name name="fetch" arity="2" since=""/>
<fsummary>Look up values in a dictionary.</fsummary>
<desc>
<p>Returns the value associated with <c><anno>Key</anno></c>
@@ -105,7 +105,7 @@
</func>
<func>
- <name name="fetch_keys" arity="1"/>
+ <name name="fetch_keys" arity="1" since=""/>
<fsummary>Return all keys in a dictionary.</fsummary>
<desc>
<p>Returns a list of all keys in a dictionary.</p>
@@ -113,7 +113,7 @@
</func>
<func>
- <name name="take" arity="2"/>
+ <name name="take" arity="2" since="OTP 20.0"/>
<fsummary>Return value and new dictionary without element with this value.</fsummary>
<desc>
<p>This function returns value from dictionary and new dictionary without this value.
@@ -122,7 +122,7 @@
</func>
<func>
- <name name="filter" arity="2"/>
+ <name name="filter" arity="2" since=""/>
<fsummary>Select elements that satisfy a predicate.</fsummary>
<desc>
<p><c><anno>Orddict2</anno></c> is a dictionary of all keys and values
@@ -133,7 +133,7 @@
</func>
<func>
- <name name="find" arity="2"/>
+ <name name="find" arity="2" since=""/>
<fsummary>Search for a key in a dictionary.</fsummary>
<desc>
<p>Searches for a key in a dictionary. Returns
@@ -145,7 +145,7 @@
</func>
<func>
- <name name="fold" arity="3"/>
+ <name name="fold" arity="3" since=""/>
<fsummary>Fold a function over a dictionary.</fsummary>
<desc>
<p>Calls <c><anno>Fun</anno></c> on successive keys and values of
@@ -157,7 +157,7 @@
</func>
<func>
- <name name="from_list" arity="1"/>
+ <name name="from_list" arity="1" since=""/>
<fsummary>Convert a list of pairs to a dictionary.</fsummary>
<desc>
<p>Converts the <c><anno>Key</anno></c>-<c><anno>Value</anno></c> list
@@ -166,7 +166,7 @@
</func>
<func>
- <name name="is_empty" arity="1"/>
+ <name name="is_empty" arity="1" since="OTP 17.0"/>
<fsummary>Return true if the dictionary is empty.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Orddict</anno></c> has no elements,
@@ -175,7 +175,7 @@
</func>
<func>
- <name name="is_key" arity="2"/>
+ <name name="is_key" arity="2" since=""/>
<fsummary>Test if a key is in a dictionary.</fsummary>
<desc>
<p>Tests if <c><anno>Key</anno></c> is contained in
@@ -184,7 +184,7 @@
</func>
<func>
- <name name="map" arity="2"/>
+ <name name="map" arity="2" since=""/>
<fsummary>Map a function over a dictionary.</fsummary>
<desc>
<p>Calls <c><anno>Fun</anno></c> on successive keys and values of
@@ -193,7 +193,7 @@
</func>
<func>
- <name name="merge" arity="3"/>
+ <name name="merge" arity="3" since=""/>
<fsummary>Merge two dictionaries.</fsummary>
<desc>
<p>Merges two dictionaries, <c><anno>Orddict1</anno></c> and
@@ -212,7 +212,7 @@ merge(Fun, D1, D2) ->
</func>
<func>
- <name name="new" arity="0"/>
+ <name name="new" arity="0" since=""/>
<fsummary>Create a dictionary.</fsummary>
<desc>
<p>Creates a new dictionary.</p>
@@ -220,7 +220,7 @@ merge(Fun, D1, D2) ->
</func>
<func>
- <name name="size" arity="1"/>
+ <name name="size" arity="1" since=""/>
<fsummary>Return the number of elements in an ordered dictionary.
</fsummary>
<desc>
@@ -229,7 +229,7 @@ merge(Fun, D1, D2) ->
</func>
<func>
- <name name="store" arity="3"/>
+ <name name="store" arity="3" since=""/>
<fsummary>Store a value in a dictionary.</fsummary>
<desc>
<p>Stores a <c><anno>Key</anno></c>-<c><anno>Value</anno></c> pair in a
@@ -240,7 +240,7 @@ merge(Fun, D1, D2) ->
</func>
<func>
- <name name="to_list" arity="1"/>
+ <name name="to_list" arity="1" since=""/>
<fsummary>Convert a dictionary to a list of pairs.</fsummary>
<desc>
<p>Converts a dictionary to a list representation.</p>
@@ -248,7 +248,7 @@ merge(Fun, D1, D2) ->
</func>
<func>
- <name name="update" arity="3"/>
+ <name name="update" arity="3" since=""/>
<fsummary>Update a value in a dictionary.</fsummary>
<desc>
<p>Updates a value in a dictionary by calling <c><anno>Fun</anno></c>
@@ -258,7 +258,7 @@ merge(Fun, D1, D2) ->
</func>
<func>
- <name name="update" arity="4"/>
+ <name name="update" arity="4" since=""/>
<fsummary>Update a value in a dictionary.</fsummary>
<desc>
<p>Updates a value in a dictionary by calling <c><anno>Fun</anno></c>
@@ -273,7 +273,7 @@ append(Key, Val, D) ->
</func>
<func>
- <name name="update_counter" arity="3"/>
+ <name name="update_counter" arity="3" since=""/>
<fsummary>Increment a value in a dictionary.</fsummary>
<desc>
<p>Adds <c><anno>Increment</anno></c> to the value associated with
diff --git a/lib/stdlib/doc/src/ordsets.xml b/lib/stdlib/doc/src/ordsets.xml
index 11f98c8fb7..fbe334c009 100644
--- a/lib/stdlib/doc/src/ordsets.xml
+++ b/lib/stdlib/doc/src/ordsets.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>ordsets.xml</file>
</header>
- <module>ordsets</module>
+ <module since="">ordsets</module>
<modulesummary>Functions for manipulating sets as ordered lists.
</modulesummary>
<description>
@@ -60,7 +60,7 @@
<funcs>
<func>
- <name name="add_element" arity="2"/>
+ <name name="add_element" arity="2" since=""/>
<fsummary>Add an element to an <c>Ordset</c>.</fsummary>
<desc>
<p>Returns a new ordered set formed from <c><anno>Ordset1</anno></c>
@@ -69,7 +69,7 @@
</func>
<func>
- <name name="del_element" arity="2"/>
+ <name name="del_element" arity="2" since=""/>
<fsummary>Remove an element from an <c>Ordset</c>.</fsummary>
<desc>
<p>Returns <c><anno>Ordset1</anno></c>, but with
@@ -78,7 +78,7 @@
</func>
<func>
- <name name="filter" arity="2"/>
+ <name name="filter" arity="2" since=""/>
<fsummary>Filter set elements.</fsummary>
<desc>
<p>Filters elements in <c><anno>Ordset1</anno></c> with boolean function
@@ -87,7 +87,7 @@
</func>
<func>
- <name name="fold" arity="3"/>
+ <name name="fold" arity="3" since=""/>
<fsummary>Fold over set elements.</fsummary>
<desc>
<p>Folds <c><anno>Function</anno></c> over every element in
@@ -97,7 +97,7 @@
</func>
<func>
- <name name="from_list" arity="1"/>
+ <name name="from_list" arity="1" since=""/>
<fsummary>Convert a list into an <c>Ordset</c>.</fsummary>
<desc>
<p>Returns an ordered set of the elements in <c><anno>List</anno></c>.
@@ -106,7 +106,7 @@
</func>
<func>
- <name name="intersection" arity="1"/>
+ <name name="intersection" arity="1" since=""/>
<fsummary>Return the intersection of a list of <c>Ordsets</c></fsummary>
<desc>
<p>Returns the intersection of the non-empty list of sets.</p>
@@ -114,7 +114,7 @@
</func>
<func>
- <name name="intersection" arity="2"/>
+ <name name="intersection" arity="2" since=""/>
<fsummary>Return the intersection of two <c>Ordsets</c>.</fsummary>
<desc>
<p>Returns the intersection of <c><anno>Ordset1</anno></c> and
@@ -123,7 +123,7 @@
</func>
<func>
- <name name="is_disjoint" arity="2"/>
+ <name name="is_disjoint" arity="2" since=""/>
<fsummary>Check whether two <c>Ordsets</c> are disjoint.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Ordset1</anno></c> and
@@ -133,7 +133,7 @@
</func>
<func>
- <name name="is_element" arity="2"/>
+ <name name="is_element" arity="2" since=""/>
<fsummary>Test for membership of an <c>Ordset</c>.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Element</anno></c> is an element of
@@ -142,7 +142,7 @@
</func>
<func>
- <name name="is_empty" arity="1"/>
+ <name name="is_empty" arity="1" since="OTP 21.0"/>
<fsummary>Test for empty set.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Ordset</anno></c> is an empty set,
@@ -151,7 +151,7 @@
</func>
<func>
- <name name="is_set" arity="1"/>
+ <name name="is_set" arity="1" since=""/>
<fsummary>Test for an <c>Ordset</c>.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Ordset</anno></c> is an ordered set
@@ -160,7 +160,7 @@
</func>
<func>
- <name name="is_subset" arity="2"/>
+ <name name="is_subset" arity="2" since=""/>
<fsummary>Test for subset.</fsummary>
<desc>
<p>Returns <c>true</c> when every element of <c><anno>Ordset1</anno></c>
@@ -170,7 +170,7 @@
</func>
<func>
- <name name="new" arity="0"/>
+ <name name="new" arity="0" since=""/>
<fsummary>Return an empty set.</fsummary>
<desc>
<p>Returns a new empty ordered set.</p>
@@ -178,7 +178,7 @@
</func>
<func>
- <name name="size" arity="1"/>
+ <name name="size" arity="1" since=""/>
<fsummary>Return the number of elements in a set.</fsummary>
<desc>
<p>Returns the number of elements in <c><anno>Ordset</anno></c>.</p>
@@ -186,7 +186,7 @@
</func>
<func>
- <name name="subtract" arity="2"/>
+ <name name="subtract" arity="2" since=""/>
<fsummary>Return the difference of two <c>Ordsets</c>.</fsummary>
<desc>
<p>Returns only the elements of <c><anno>Ordset1</anno></c> that are not
@@ -195,7 +195,7 @@
</func>
<func>
- <name name="to_list" arity="1"/>
+ <name name="to_list" arity="1" since=""/>
<fsummary>Convert an <c>Ordset</c> into a list.</fsummary>
<desc>
<p>Returns the elements of <c><anno>Ordset</anno></c> as a list.</p>
@@ -203,7 +203,7 @@
</func>
<func>
- <name name="union" arity="1"/>
+ <name name="union" arity="1" since=""/>
<fsummary>Return the union of a list of <c>Ordsets</c>.</fsummary>
<desc>
<p>Returns the merged (union) set of the list of sets.</p>
@@ -211,7 +211,7 @@
</func>
<func>
- <name name="union" arity="2"/>
+ <name name="union" arity="2" since=""/>
<fsummary>Return the union of two <c>Ordsets</c>.</fsummary>
<desc>
<p>Returns the merged (union) set of <c><anno>Ordset1</anno></c> and
diff --git a/lib/stdlib/doc/src/pool.xml b/lib/stdlib/doc/src/pool.xml
index 05d12ade28..675ee08bfb 100644
--- a/lib/stdlib/doc/src/pool.xml
+++ b/lib/stdlib/doc/src/pool.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>pool</module>
+ <module since="">pool</module>
<modulesummary>Load distribution facility.</modulesummary>
<description>
<p>This module can be used to run a set of Erlang nodes as a pool
@@ -54,7 +54,7 @@
<funcs>
<func>
- <name name="attach" arity="1"/>
+ <name name="attach" arity="1" since=""/>
<fsummary>Ensure that a pool master is running.</fsummary>
<desc>
<p>Ensures that a pool master is running and includes
@@ -63,7 +63,7 @@
</func>
<func>
- <name name="get_node" arity="0"/>
+ <name name="get_node" arity="0" since=""/>
<fsummary>Return the node with the expected lowest future load.</fsummary>
<desc>
<p>Returns the node with the expected lowest future load.</p>
@@ -71,7 +71,7 @@
</func>
<func>
- <name name="get_nodes" arity="0"/>
+ <name name="get_nodes" arity="0" since=""/>
<fsummary>Return a list of the current member nodes of the pool.
</fsummary>
<desc>
@@ -80,7 +80,7 @@
</func>
<func>
- <name name="pspawn" arity="3"/>
+ <name name="pspawn" arity="3" since=""/>
<fsummary>Spawn a process on the pool node with expected lowest future
load.</fsummary>
<desc>
@@ -90,7 +90,7 @@
</func>
<func>
- <name name="pspawn_link" arity="3"/>
+ <name name="pspawn_link" arity="3" since=""/>
<fsummary>Spawn and link to a process on the pool node with expected
lowest future load.</fsummary>
<desc>
@@ -100,8 +100,8 @@
</func>
<func>
- <name name="start" arity="1"/>
- <name name="start" arity="2"/>
+ <name name="start" arity="1" since=""/>
+ <name name="start" arity="2" since=""/>
<fsummary>>Start a new pool.</fsummary>
<desc>
<p>Starts a new pool. The file <c>.hosts.erlang</c> is read to
@@ -122,7 +122,7 @@
</func>
<func>
- <name name="stop" arity="0"/>
+ <name name="stop" arity="0" since=""/>
<fsummary>Stop the pool and kill all the slave nodes.</fsummary>
<desc>
<p>Stops the pool and kills all the slave nodes.</p>
diff --git a/lib/stdlib/doc/src/proc_lib.xml b/lib/stdlib/doc/src/proc_lib.xml
index b85fab67d5..aeb9f48735 100644
--- a/lib/stdlib/doc/src/proc_lib.xml
+++ b/lib/stdlib/doc/src/proc_lib.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>proc_lib</module>
+ <module since="">proc_lib</module>
<modulesummary>Functions for asynchronous and synchronous start of processes
adhering to the OTP design principles.</modulesummary>
<description>
@@ -102,7 +102,7 @@
<funcs>
<func>
- <name name="format" arity="1"/>
+ <name name="format" arity="1" since=""/>
<fsummary>Format a crash report.</fsummary>
<desc>
<p>Equivalent to <seealso marker="#format/2">
@@ -111,7 +111,7 @@
</func>
<func>
- <name name="format" arity="2"/>
+ <name name="format" arity="2" since="OTP R16B"/>
<fsummary>Format a crash report.</fsummary>
<desc>
<note>
@@ -138,7 +138,7 @@
</func>
<func>
- <name name="format" arity="3"/>
+ <name name="format" arity="3" since="OTP 18.1"/>
<fsummary>Format a crash report.</fsummary>
<desc>
<note>
@@ -162,7 +162,7 @@
</func>
<func>
- <name name="hibernate" arity="3"/>
+ <name name="hibernate" arity="3" since=""/>
<fsummary>Hibernate a process until a message is sent to it.</fsummary>
<desc>
<p>This function does the same as (and does call) the
@@ -176,8 +176,8 @@
</func>
<func>
- <name name="init_ack" arity="1"/>
- <name name="init_ack" arity="2"/>
+ <name name="init_ack" arity="1" since=""/>
+ <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
@@ -214,7 +214,7 @@ init(Parent) ->
</func>
<func>
- <name name="initial_call" arity="1"/>
+ <name name="initial_call" arity="1" since=""/>
<fsummary>Extract the initial call of a <c>proc_lib</c>spawned process.
</fsummary>
<desc>
@@ -244,10 +244,10 @@ init(Parent) ->
</func>
<func>
- <name name="spawn" arity="1"/>
- <name name="spawn" arity="2"/>
- <name name="spawn" arity="3"/>
- <name name="spawn" arity="4"/>
+ <name name="spawn" arity="1" since=""/>
+ <name name="spawn" arity="2" since=""/>
+ <name name="spawn" arity="3" since=""/>
+ <name name="spawn" arity="4" since=""/>
<fsummary>Spawn a new process.</fsummary>
<type variable="Node"/>
<type variable="Fun" name_i="1"/>
@@ -262,10 +262,10 @@ init(Parent) ->
</func>
<func>
- <name name="spawn_link" arity="1"/>
- <name name="spawn_link" arity="2"/>
- <name name="spawn_link" arity="3"/>
- <name name="spawn_link" arity="4"/>
+ <name name="spawn_link" arity="1" since=""/>
+ <name name="spawn_link" arity="2" since=""/>
+ <name name="spawn_link" arity="3" since=""/>
+ <name name="spawn_link" arity="4" since=""/>
<fsummary>Spawn and link to a new process.</fsummary>
<type variable="Node"/>
<type variable="Fun" name_i="1"/>
@@ -281,10 +281,10 @@ init(Parent) ->
</func>
<func>
- <name name="spawn_opt" arity="2"/>
- <name name="spawn_opt" arity="3"/>
- <name name="spawn_opt" arity="4"/>
- <name name="spawn_opt" arity="5"/>
+ <name name="spawn_opt" arity="2" since=""/>
+ <name name="spawn_opt" arity="3" since=""/>
+ <name name="spawn_opt" arity="4" since=""/>
+ <name name="spawn_opt" arity="5" since=""/>
<fsummary>Spawn a new process with specified options.</fsummary>
<type variable="Node"/>
<type variable="Fun" name_i="1"/>
@@ -306,12 +306,12 @@ init(Parent) ->
</func>
<func>
- <name name="start" arity="3"/>
- <name name="start" arity="4"/>
- <name name="start" arity="5"/>
- <name name="start_link" arity="3"/>
- <name name="start_link" arity="4"/>
- <name name="start_link" arity="5"/>
+ <name name="start" arity="3" since=""/>
+ <name name="start" arity="4" since=""/>
+ <name name="start" arity="5" since=""/>
+ <name name="start_link" arity="3" since=""/>
+ <name name="start_link" arity="4" since=""/>
+ <name name="start_link" arity="5" since=""/>
<fsummary>Start a new process synchronously.</fsummary>
<desc>
<p>Starts a new process synchronously. Spawns the process and
@@ -341,7 +341,7 @@ init(Parent) ->
</func>
<func>
- <name name="stop" arity="1"/>
+ <name name="stop" arity="1" since="OTP 18.0"/>
<fsummary>Terminate a process synchronously.</fsummary>
<type variable="Process"/>
<desc>
@@ -351,7 +351,7 @@ init(Parent) ->
</func>
<func>
- <name name="stop" arity="3"/>
+ <name name="stop" arity="3" since="OTP 18.0"/>
<fsummary>Terminate a process synchronously.</fsummary>
<type variable="Process"/>
<type variable="Reason"/>
@@ -375,7 +375,7 @@ init(Parent) ->
</func>
<func>
- <name name="translate_initial_call" arity="1"/>
+ <name name="translate_initial_call" arity="1" since=""/>
<fsummary>Extract and translate the initial call of a
<c>proc_lib</c>spawned process.</fsummary>
<desc>
diff --git a/lib/stdlib/doc/src/proplists.xml b/lib/stdlib/doc/src/proplists.xml
index f9a54bf804..4465103469 100644
--- a/lib/stdlib/doc/src/proplists.xml
+++ b/lib/stdlib/doc/src/proplists.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>proplists.xml</file>
</header>
- <module>proplists</module>
+ <module since="">proplists</module>
<modulesummary>Support functions for property lists.</modulesummary>
<description>
<p>Property lists are ordinary lists containing entries in the form
@@ -57,11 +57,16 @@
<datatype>
<name name="property"/>
</datatype>
+
+ <datatype>
+ <name name="proplist"/>
+ </datatype>
+
</datatypes>
<funcs>
<func>
- <name name="append_values" arity="2"/>
+ <name name="append_values" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Similar to
@@ -79,7 +84,7 @@ append_values(a, [{a, [1,2]}, {b, 0}, {a, 3}, {c, -1}, {a, [4]}])</code>
</func>
<func>
- <name name="compact" arity="1"/>
+ <name name="compact" arity="1" since=""/>
<fsummary></fsummary>
<desc>
<p>Minimizes the representation of all entries in the list. This is
@@ -91,7 +96,7 @@ append_values(a, [{a, [1,2]}, {b, 0}, {a, 3}, {c, -1}, {a, [4]}])</code>
</func>
<func>
- <name name="delete" arity="2"/>
+ <name name="delete" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Deletes all entries associated with <c><anno>Key</anno></c> from
@@ -100,7 +105,7 @@ append_values(a, [{a, [1,2]}, {b, 0}, {a, 3}, {c, -1}, {a, [4]}])</code>
</func>
<func>
- <name name="expand" arity="2"/>
+ <name name="expand" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Expands particular properties to corresponding sets of
@@ -133,7 +138,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
</func>
<func>
- <name name="get_all_values" arity="2"/>
+ <name name="get_all_values" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Similar to
@@ -145,7 +150,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
</func>
<func>
- <name name="get_bool" arity="2"/>
+ <name name="get_bool" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Returns the value of a boolean key/value option. If
@@ -159,7 +164,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
</func>
<func>
- <name name="get_keys" arity="1"/>
+ <name name="get_keys" arity="1" since=""/>
<fsummary></fsummary>
<desc>
<p>Returns an unordered list of the keys used in
@@ -168,7 +173,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
</func>
<func>
- <name name="get_value" arity="2"/>
+ <name name="get_value" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Equivalent to
@@ -177,7 +182,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
</func>
<func>
- <name name="get_value" arity="3"/>
+ <name name="get_value" arity="3" since=""/>
<fsummary></fsummary>
<desc>
<p>Returns the value of a simple key/value property in
@@ -194,7 +199,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
</func>
<func>
- <name name="is_defined" arity="2"/>
+ <name name="is_defined" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>List</anno></c> contains at least
@@ -204,7 +209,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
</func>
<func>
- <name name="lookup" arity="2"/>
+ <name name="lookup" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Returns the first entry associated with <c><anno>Key</anno></c> in
@@ -219,7 +224,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
</func>
<func>
- <name name="lookup_all" arity="2"/>
+ <name name="lookup_all" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Returns the list of all entries associated with
@@ -231,7 +236,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
</func>
<func>
- <name name="normalize" arity="2"/>
+ <name name="normalize" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Passes <c><anno>ListIn</anno></c> through a sequence of
@@ -263,7 +268,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
</func>
<func>
- <name name="property" arity="1"/>
+ <name name="property" arity="1" since=""/>
<fsummary></fsummary>
<desc>
<p>Creates a normal form (minimal) representation of a property. If
@@ -276,7 +281,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
</func>
<func>
- <name name="property" arity="2"/>
+ <name name="property" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Creates a normal form (minimal) representation of a simple key/value
@@ -289,7 +294,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
</func>
<func>
- <name name="split" arity="2"/>
+ <name name="split" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Partitions <c><anno>List</anno></c> into a list of sublists and a
@@ -310,7 +315,7 @@ split([{c, 2}, {e, 1}, a, {c, 3, 4}, d, {b, 5}, b], [a, b, c])</code>
</func>
<func>
- <name name="substitute_aliases" arity="2"/>
+ <name name="substitute_aliases" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Substitutes keys of properties. For each entry in
@@ -332,7 +337,7 @@ split([{c, 2}, {e, 1}, a, {c, 3, 4}, d, {b, 5}, b], [a, b, c])</code>
</func>
<func>
- <name name="substitute_negations" arity="2"/>
+ <name name="substitute_negations" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Substitutes keys of boolean-valued properties and
@@ -360,7 +365,7 @@ split([{c, 2}, {e, 1}, a, {c, 3, 4}, d, {b, 5}, b], [a, b, c])</code>
</func>
<func>
- <name name="unfold" arity="1"/>
+ <name name="unfold" arity="1" since=""/>
<fsummary></fsummary>
<desc>
<p>Unfolds all occurrences of atoms in <c><anno>ListIn</anno></c> to
diff --git a/lib/stdlib/doc/src/qlc.xml b/lib/stdlib/doc/src/qlc.xml
index fe14a6334c..fe60c2e9bb 100644
--- a/lib/stdlib/doc/src/qlc.xml
+++ b/lib/stdlib/doc/src/qlc.xml
@@ -32,7 +32,7 @@
<rev>PA1</rev>
<file>qlc.xml</file>
</header>
- <module>qlc</module>
+ <module since="">qlc</module>
<modulesummary>Query interface to Mnesia, ETS, Dets, and so on.
</modulesummary>
<description>
@@ -720,7 +720,7 @@ ets:match_spec_run(ets:lookup(86033, {2,2}),
<funcs>
<func>
- <name name="append" arity="1"/>
+ <name name="append" arity="1" since=""/>
<fsummary>Return a query handle.</fsummary>
<desc>
<p>Returns a query handle. When evaluating query handle
@@ -731,7 +731,7 @@ ets:match_spec_run(ets:lookup(86033, {2,2}),
</func>
<func>
- <name name="append" arity="2"/>
+ <name name="append" arity="2" since=""/>
<fsummary>Return a query handle.</fsummary>
<desc>
<p>Returns a query handle. When evaluating query handle
@@ -744,8 +744,8 @@ ets:match_spec_run(ets:lookup(86033, {2,2}),
</func>
<func>
- <name name="cursor" arity="1"/>
- <name name="cursor" arity="2"/>
+ <name name="cursor" arity="1" since=""/>
+ <name name="cursor" arity="2" since=""/>
<fsummary>Create a query cursor.</fsummary>
<desc>
<p>Creates a query cursor and
@@ -777,7 +777,7 @@ ok</pre>
</func>
<func>
- <name name="delete_cursor" arity="1"/>
+ <name name="delete_cursor" arity="1" since=""/>
<fsummary>Delete a query cursor.</fsummary>
<desc>
<p>Deletes a query cursor. Only the owner of the cursor can
@@ -786,10 +786,10 @@ ok</pre>
</func>
<func>
- <name name="e" arity="1"/>
- <name name="e" arity="2"/>
- <name name="eval" arity="1"/>
- <name name="eval" arity="2"/>
+ <name name="e" arity="1" since=""/>
+ <name name="e" arity="2" since=""/>
+ <name name="eval" arity="1" since=""/>
+ <name name="eval" arity="2" since=""/>
<fsummary>Return all answers to a query.</fsummary>
<desc>
<p>Evaluates a query handle in the
@@ -805,8 +805,8 @@ ok</pre>
</func>
<func>
- <name name="fold" arity="3"/>
- <name name="fold" arity="4"/>
+ <name name="fold" arity="3" since=""/>
+ <name name="fold" arity="4" since=""/>
<fsummary>Fold a function over the answers to a query.</fsummary>
<desc>
<p>Calls <c><anno>Function</anno></c> on successive answers to
@@ -830,7 +830,7 @@ ok</pre>
</func>
<func>
- <name name="format_error" arity="1"/>
+ <name name="format_error" arity="1" since=""/>
<fsummary>Return an English description of a an error tuple.</fsummary>
<desc>
<p>Returns a descriptive string in English of an error tuple
@@ -841,8 +841,8 @@ ok</pre>
</func>
<func>
- <name name="info" arity="1"/>
- <name name="info" arity="2"/>
+ <name name="info" arity="1" since=""/>
+ <name name="info" arity="2" since=""/>
<fsummary>Return code describing a query handle.</fsummary>
<desc>
<p>Returns information about a
@@ -946,8 +946,8 @@ end</pre>
</func>
<func>
- <name name="keysort" arity="2"/>
- <name name="keysort" arity="3"/>
+ <name name="keysort" arity="2" since=""/>
+ <name name="keysort" arity="3" since=""/>
<fsummary>Return a query handle.</fsummary>
<desc>
<p>Returns a query handle. When evaluating query handle
@@ -967,8 +967,8 @@ end</pre>
</func>
<func>
- <name name="next_answers" arity="1"/>
- <name name="next_answers" arity="2"/>
+ <name name="next_answers" arity="1" since=""/>
+ <name name="next_answers" arity="2" since=""/>
<fsummary>Return some or all answers to a query.</fsummary>
<desc>
<p>Returns some or all of the remaining answers to a query
@@ -983,8 +983,8 @@ end</pre>
</func>
<func>
- <name name="q" arity="1"/>
- <name name="q" arity="2"/>
+ <name name="q" arity="1" since=""/>
+ <name name="q" arity="2" since=""/>
<fsummary>Return a handle for a query list comprehension.</fsummary>
<desc>
<p>Returns a query handle for a QLC.
@@ -1188,8 +1188,8 @@ ets:match_spec_run(
</func>
<func>
- <name name="sort" arity="1"/>
- <name name="sort" arity="2"/>
+ <name name="sort" arity="1" since=""/>
+ <name name="sort" arity="2" since=""/>
<fsummary>Return a query handle.</fsummary>
<desc>
<p>Returns a query handle. When evaluating query handle
@@ -1208,9 +1208,9 @@ ets:match_spec_run(
</func>
<func>
- <name name="string_to_handle" arity="1"/>
- <name name="string_to_handle" arity="2"/>
- <name name="string_to_handle" arity="3"/>
+ <name name="string_to_handle" arity="1" since=""/>
+ <name name="string_to_handle" arity="2" since=""/>
+ <name name="string_to_handle" arity="3" since=""/>
<fsummary>Return a handle for a query list comprehension.</fsummary>
<desc>
<p>A string version of <seealso marker="#q/1"><c>q/1,2</c></seealso>.
@@ -1238,7 +1238,7 @@ ets:match_spec_run(
</func>
<func>
- <name name="table" arity="2"/>
+ <name name="table" arity="2" since=""/>
<fsummary>Return a query handle for a table.</fsummary>
<desc>
<p>Returns a query handle for a QLC table.
diff --git a/lib/stdlib/doc/src/queue.xml b/lib/stdlib/doc/src/queue.xml
index 9f3aff03a3..83a8afea81 100644
--- a/lib/stdlib/doc/src/queue.xml
+++ b/lib/stdlib/doc/src/queue.xml
@@ -32,7 +32,7 @@
<rev>B</rev>
<file>queue.xml</file>
</header>
- <module>queue</module>
+ <module since="">queue</module>
<modulesummary>Abstract data type for FIFO queues.</modulesummary>
<description>
<p>This module provides (double-ended) FIFO queues
@@ -113,7 +113,7 @@
<funcs>
<func>
- <name name="filter" arity="2"/>
+ <name name="filter" arity="2" since=""/>
<fsummary>Filter a queue.</fsummary>
<desc>
<p>Returns a queue <c><anno>Q2</anno></c> that is the result of calling
@@ -134,7 +134,7 @@
</func>
<func>
- <name name="from_list" arity="1"/>
+ <name name="from_list" arity="1" since=""/>
<fsummary>Convert a list to a queue.</fsummary>
<desc>
<p>Returns a queue containing the items in <c><anno>L</anno></c> in the
@@ -144,7 +144,7 @@
</func>
<func>
- <name name="in" arity="2"/>
+ <name name="in" arity="2" since=""/>
<fsummary>Insert an item at the rear of a queue.</fsummary>
<desc>
<p>Inserts <c><anno>Item</anno></c> at the rear of queue
@@ -154,7 +154,7 @@
</func>
<func>
- <name name="in_r" arity="2"/>
+ <name name="in_r" arity="2" since=""/>
<fsummary>Insert an item at the front of a queue.</fsummary>
<desc>
<p>Inserts <c><anno>Item</anno></c> at the front of queue
@@ -164,7 +164,7 @@
</func>
<func>
- <name name="is_empty" arity="1"/>
+ <name name="is_empty" arity="1" since=""/>
<fsummary>Test if a queue is empty.</fsummary>
<desc>
<p>Tests if <c><anno>Q</anno></c> is empty and returns <c>true</c> if
@@ -173,7 +173,7 @@
</func>
<func>
- <name name="is_queue" arity="1"/>
+ <name name="is_queue" arity="1" since=""/>
<fsummary>Test if a term is a queue.</fsummary>
<desc>
<p>Tests if <c><anno>Term</anno></c> is a queue and returns <c>true</c>
@@ -182,7 +182,7 @@
</func>
<func>
- <name name="join" arity="2"/>
+ <name name="join" arity="2" since=""/>
<fsummary>Join two queues.</fsummary>
<desc>
<p>Returns a queue <c><anno>Q3</anno></c> that is the result of joining
@@ -192,7 +192,7 @@
</func>
<func>
- <name name="len" arity="1"/>
+ <name name="len" arity="1" since=""/>
<fsummary>Get the length of a queue.</fsummary>
<desc>
<p>Calculates and returns the length of queue <c><anno>Q</anno></c>.</p>
@@ -200,7 +200,7 @@
</func>
<func>
- <name name="member" arity="2"/>
+ <name name="member" arity="2" since=""/>
<fsummary>Test if an item is in a queue.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Item</anno></c> matches some element
@@ -209,7 +209,7 @@
</func>
<func>
- <name name="new" arity="0"/>
+ <name name="new" arity="0" since=""/>
<fsummary>Create an empty queue.</fsummary>
<desc>
<p>Returns an empty queue.</p>
@@ -217,7 +217,7 @@
</func>
<func>
- <name name="out" arity="1"/>
+ <name name="out" arity="1" since=""/>
<fsummary>Remove the front item from a queue.</fsummary>
<desc>
<p>Removes the item at the front of queue <c><anno>Q1</anno></c>.
@@ -230,7 +230,7 @@
</func>
<func>
- <name name="out_r" arity="1"/>
+ <name name="out_r" arity="1" since=""/>
<fsummary>Remove the rear item from a queue.</fsummary>
<desc>
<p>Removes the item at the rear of queue <c><anno>Q1</anno></c>.
@@ -242,7 +242,7 @@
</func>
<func>
- <name name="reverse" arity="1"/>
+ <name name="reverse" arity="1" since=""/>
<fsummary>Reverse a queue.</fsummary>
<desc>
<p>Returns a queue <c><anno>Q2</anno></c> containing the items of
@@ -251,7 +251,7 @@
</func>
<func>
- <name name="split" arity="2"/>
+ <name name="split" arity="2" since=""/>
<fsummary>Split a queue in two.</fsummary>
<desc>
<p>Splits <c><anno>Q1</anno></c> in two. The <c><anno>N</anno></c>
@@ -261,7 +261,7 @@
</func>
<func>
- <name name="to_list" arity="1"/>
+ <name name="to_list" arity="1" since=""/>
<fsummary>Convert a queue to a list.</fsummary>
<desc>
<p>Returns a list of the items in the queue in the same order;
@@ -276,7 +276,7 @@
<funcs>
<func>
- <name name="drop" arity="1"/>
+ <name name="drop" arity="1" since=""/>
<fsummary>Remove the front item from a queue.</fsummary>
<desc>
<p>Returns a queue <c><anno>Q2</anno></c> that is the result of removing
@@ -286,7 +286,7 @@
</func>
<func>
- <name name="drop_r" arity="1"/>
+ <name name="drop_r" arity="1" since=""/>
<fsummary>Remove the rear item from a queue.</fsummary>
<desc>
<p>Returns a queue <c><anno>Q2</anno></c> that is the result of removing
@@ -296,7 +296,7 @@
</func>
<func>
- <name name="get" arity="1"/>
+ <name name="get" arity="1" since=""/>
<fsummary>Return the front item of a queue.</fsummary>
<desc>
<p>Returns <c><anno>Item</anno></c> at the front of queue
@@ -306,7 +306,7 @@
</func>
<func>
- <name name="get_r" arity="1"/>
+ <name name="get_r" arity="1" since=""/>
<fsummary>Return the rear item of a queue.</fsummary>
<desc>
<p>Returns <c><anno>Item</anno></c> at the rear of queue
@@ -316,7 +316,7 @@
</func>
<func>
- <name name="peek" arity="1"/>
+ <name name="peek" arity="1" since=""/>
<fsummary>Return the front item of a queue.</fsummary>
<desc>
<p>Returns tuple <c>{value, <anno>Item</anno>}</c>, where
@@ -326,7 +326,7 @@
</func>
<func>
- <name name="peek_r" arity="1"/>
+ <name name="peek_r" arity="1" since=""/>
<fsummary>Return the rear item of a queue.</fsummary>
<desc>
<p>Returns tuple <c>{value, <anno>Item</anno>}</c>, where
@@ -342,7 +342,7 @@
<funcs>
<func>
- <name name="cons" arity="2"/>
+ <name name="cons" arity="2" since=""/>
<fsummary>Insert an item at the head of a queue.</fsummary>
<desc>
<p>Inserts <c><anno>Item</anno></c> at the head of queue
@@ -352,7 +352,7 @@
</func>
<func>
- <name name="daeh" arity="1"/>
+ <name name="daeh" arity="1" since=""/>
<fsummary>Return the tail item of a queue.</fsummary>
<desc>
<p>Returns the tail item of queue <c><anno>Q</anno></c>.</p>
@@ -361,7 +361,7 @@
</func>
<func>
- <name name="head" arity="1"/>
+ <name name="head" arity="1" since=""/>
<fsummary>Return the item at the head of a queue.</fsummary>
<desc>
<p>Returns <c><anno>Item</anno></c> from the head of queue
@@ -371,7 +371,7 @@
</func>
<func>
- <name name="init" arity="1"/>
+ <name name="init" arity="1" since=""/>
<fsummary>Remove the tail item from a queue.</fsummary>
<desc>
<p>Returns a queue <c><anno>Q2</anno></c> that is the result of removing
@@ -381,7 +381,7 @@
</func>
<func>
- <name name="lait" arity="1"/>
+ <name name="lait" arity="1" since=""/>
<fsummary>Remove the tail item from a queue.</fsummary>
<desc>
<p>Returns a queue <c><anno>Q2</anno></c> that is the result of removing
@@ -392,7 +392,7 @@
</func>
<func>
- <name name="last" arity="1"/>
+ <name name="last" arity="1" since=""/>
<fsummary>Return the tail item of a queue.</fsummary>
<desc>
<p>Returns the tail item of queue <c><anno>Q</anno></c>.</p>
@@ -401,7 +401,7 @@
</func>
<func>
- <name name="liat" arity="1"/>
+ <name name="liat" arity="1" since=""/>
<fsummary>Remove the tail item from a queue.</fsummary>
<desc>
<p>Returns a queue <c><anno>Q2</anno></c> that is the result of removing
@@ -411,7 +411,7 @@
</func>
<func>
- <name name="snoc" arity="2"/>
+ <name name="snoc" arity="2" since=""/>
<fsummary>Insert an item at the tail of a queue.</fsummary>
<desc>
<p>Inserts <c><anno>Item</anno></c> as the tail item of queue
@@ -421,7 +421,7 @@
</func>
<func>
- <name name="tail" arity="1"/>
+ <name name="tail" arity="1" since=""/>
<fsummary>Remove the head item from a queue.</fsummary>
<desc>
<p>Returns a queue <c><anno>Q2</anno></c> that is the result of removing
diff --git a/lib/stdlib/doc/src/rand.xml b/lib/stdlib/doc/src/rand.xml
index 21f680a0ee..b4737b48f8 100644
--- a/lib/stdlib/doc/src/rand.xml
+++ b/lib/stdlib/doc/src/rand.xml
@@ -32,31 +32,77 @@
<rev>A</rev>
<file>rand.xml</file>
</header>
- <module>rand</module>
+ <module since="OTP 18.0">rand</module>
<modulesummary>Pseudo random number generation.</modulesummary>
<description>
<p>
This module provides a pseudo random number generator.
The module contains a number of algorithms.
- The uniform distribution algorithms use the
+ The uniform distribution algorithms are based on the
<url href="http://xorshift.di.unimi.it">
- xoroshiro116+ and xorshift1024* algorithms by Sebastiano Vigna.
+ Xoroshiro and Xorshift algorithms
</url>
+ by Sebastiano Vigna.
The normal distribution algorithm uses the
<url href="http://www.jstatsoft.org/v05/i08">
Ziggurat Method by Marsaglia and Tsang
</url>
on top of the uniform distribution algorithm.
</p>
- <p>For some algorithms, jump functions are provided for generating
- non-overlapping sequences for parallel computations.
- The jump functions perform calculations
- equivalent to perform a large number of repeated calls
- for calculating new states. </p>
+ <p>
+ For most algorithms, jump functions are provided for generating
+ non-overlapping sequences for parallel computations.
+ The jump functions perform calculations
+ equivalent to perform a large number of repeated calls
+ for calculating new states.
+ </p>
<p>The following algorithms are provided:</p>
<taglist>
+ <tag><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>
+ <p>
+ This is the Xorshift116 generator combined with the StarStar scrambler
+ from the 2018 paper by David Blackman and Sebastiano Vigna:
+ <url href="http://vigna.di.unimi.it/ftp/papers/ScrambledLinear.pdf">
+ Scrambled Linear Pseudorandom Number Generators
+ </url>
+ </p>
+ <p>
+ The generator does not need 58-bit rotates so it is faster
+ than the Xoroshiro116 generator, and when combined with
+ the StarStar scrambler it does not have any weak low bits
+ like <c>exrop</c> (Xoroshiro116+).
+ </p>
+ <p>
+ Alas, this combination is about 10% slower than <c>exrop</c>,
+ but is despite that the default algorithm thanks to its
+ statistical qualities.
+ </p>
+ </item>
+ <tag><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>
+ <p>
+ This is a 58 bit version of Xoroshiro1024**,
+ from the 2018 paper by David Blackman and Sebastiano Vigna:
+ <url href="http://vigna.di.unimi.it/ftp/papers/ScrambledLinear.pdf">
+ Scrambled Linear Pseudorandom Number Generators
+ </url>
+ that on a 64 bit Erlang system executes only about 40% slower than
+ the default <c>exsss</c> algorithm but with much longer period
+ and better statistical properties, and on the flip side
+ a larger state.
+ </p>
+ <p>
+ Many thanks to Sebastiano Vigna for his help with
+ the 58 bit adaption.
+ </p>
+ </item>
<tag><c>exrop</c></tag>
<item>
<p>Xoroshiro116+, 58 bits precision and period of 2^116-1</p>
@@ -83,7 +129,7 @@
</taglist>
<p>
- The default algorithm is <c>exrop</c> (Xoroshiro116+).
+ The default algorithm is <c>exsss</c> (Xorshift116**).
If a specific algorithm is
required, ensure to always use <seealso marker="#seed-1">
<c>seed/1</c></seealso> to initialize the state.
@@ -154,19 +200,19 @@ R1 = rand:uniform(),</pre>
<p>Use a specified algorithm:</p>
<pre>
-_ = rand:seed(exs1024s),
+_ = rand:seed(exs928ss),
R2 = rand:uniform(),</pre>
<p>Use a specified algorithm with a constant seed:</p>
<pre>
-_ = rand:seed(exs1024s, {123, 123534, 345345}),
+_ = rand:seed(exs928ss, {123, 123534, 345345}),
R3 = rand:uniform(),</pre>
<p>Use the functional API with a non-constant seed:</p>
<pre>
-S0 = rand:seed_s(exrop),
+S0 = rand:seed_s(exsss),
{R4, S1} = rand:uniform_s(S0),</pre>
<p>Textbook basic form Box-Muller standard normal deviate</p>
@@ -195,8 +241,9 @@ SND0 = math:sqrt(-2 * math:log(R5)) * math:cos(math:pi() * R6)</pre>
</note>
<p>
- For all these generators the lowest bit(s) has got
- a slightly less random behaviour than all other bits.
+ For all these generators except <c>exro928ss</c> and <c>exsss</c>
+ the lowest bit(s) has got a slightly less
+ random behaviour than all other bits.
1 bit for <c>exrop</c> (and <c>exsp</c>),
and 3 bits for <c>exs1024s</c>.
See for example the explanation in the
@@ -211,7 +258,7 @@ up to (and included) 16TB, with the exception of binary rank tests,
which fail due to the lowest bit being an LFSR; all other bits pass all
tests. We suggest to use a sign test to extract a random Boolean value.</pre>
<p>
- If this is a problem; to generate a boolean
+ If this is a problem; to generate a boolean with these algorithms
use something like this:
</p>
<pre>(rand:uniform(16) > 8)</pre>
@@ -254,26 +301,55 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
</desc>
</datatype>
<datatype>
- <name name="exs64_state"/>
- <desc><p>Algorithm specific internal state</p></desc>
+ <name name="seed"/>
+ <desc>
+ <p>
+ A seed value for the generator.
+ </p>
+ <p>
+ A list of integers sets the generator's internal state directly,
+ after algorithm-dependent checks of the value
+ and masking to the proper word size.
+ </p>
+ <p>
+ An integer is used as the initial state for a SplitMix64 generator.
+ The output values of that is then used for setting
+ the generator's internal state
+ after masking to the proper word size
+ and if needed avoiding zero values.
+ </p>
+ <p>
+ A traditional 3-tuple of integers seed is passed through
+ algorithm-dependent hashing functions to create
+ the generator's initial state.
+ </p>
+ </desc>
</datatype>
<datatype>
<name name="exsplus_state"/>
<desc><p>Algorithm specific internal state</p></desc>
</datatype>
<datatype>
- <name name="exs1024_state"/>
+ <name name="exro928_state"/>
<desc><p>Algorithm specific internal state</p></desc>
</datatype>
<datatype>
<name name="exrop_state"/>
<desc><p>Algorithm specific internal state</p></desc>
</datatype>
+ <datatype>
+ <name name="exs1024_state"/>
+ <desc><p>Algorithm specific internal state</p></desc>
+ </datatype>
+ <datatype>
+ <name name="exs64_state"/>
+ <desc><p>Algorithm specific internal state</p></desc>
+ </datatype>
</datatypes>
<funcs>
<func>
- <name name="export_seed" arity="0"/>
+ <name name="export_seed" arity="0" since="OTP 18.0"/>
<fsummary>Export the random number generation state.</fsummary>
<desc><marker id="export_seed-0"/>
<p>Returns the random number state in an external format.
@@ -282,7 +358,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
</func>
<func>
- <name name="export_seed_s" arity="1"/>
+ <name name="export_seed_s" arity="1" since="OTP 18.0"/>
<fsummary>Export the random number generation state.</fsummary>
<desc><marker id="export_seed_s-1"/>
<p>Returns the random number generator state in an external format.
@@ -291,7 +367,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
</func>
<func>
- <name name="jump" arity="0"/>
+ <name name="jump" arity="0" since="OTP 20.0"/>
<fsummary>Return the seed after performing jump calculation
to the state in the process dictionary.</fsummary>
<desc><marker id="jump-0" />
@@ -306,7 +382,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
</func>
<func>
- <name name="jump" arity="1"/>
+ <name name="jump" arity="1" since="OTP 20.0"/>
<fsummary>Return the seed after performing jump calculation.</fsummary>
<desc><marker id="jump-1" />
<p>Returns the state after performing jump calculation
@@ -318,7 +394,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
</func>
<func>
- <name name="normal" arity="0"/>
+ <name name="normal" arity="0" since="OTP 18.0"/>
<fsummary>Return a standard normal distributed random float.</fsummary>
<desc>
<p>Returns a standard normal deviate float (that is, the mean
@@ -328,7 +404,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
</func>
<func>
- <name name="normal" arity="2"/>
+ <name name="normal" arity="2" since="OTP 20.0"/>
<fsummary>Return a normal distributed random float.</fsummary>
<desc>
<p>Returns a normal N(Mean, Variance) deviate float
@@ -337,7 +413,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
</func>
<func>
- <name name="normal_s" arity="1"/>
+ <name name="normal_s" arity="1" since="OTP 18.0"/>
<fsummary>Return a standard normal distributed random float.</fsummary>
<desc>
<p>Returns, for a specified state, a standard normal
@@ -347,7 +423,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
</func>
<func>
- <name name="normal_s" arity="3"/>
+ <name name="normal_s" arity="3" since="OTP 20.0"/>
<fsummary>Return a normal distributed random float.</fsummary>
<desc>
<p>Returns, for a specified state, a normal N(Mean, Variance)
@@ -356,7 +432,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
</func>
<func>
- <name name="seed" arity="1"/>
+ <name name="seed" arity="1" since="OTP 18.0"/>
<fsummary>Seed random number generator.</fsummary>
<desc>
<marker id="seed-1"/>
@@ -372,7 +448,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
</func>
<func>
- <name name="seed" arity="2"/>
+ <name name="seed" arity="2" since="OTP 18.0"/>
<fsummary>Seed the random number generation.</fsummary>
<desc>
<p>Seeds random number generation with the specified algorithm and
@@ -381,7 +457,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
</func>
<func>
- <name name="seed_s" arity="1"/>
+ <name name="seed_s" arity="1" since="OTP 18.0"/>
<fsummary>Seed random number generator.</fsummary>
<desc>
<p>
@@ -396,7 +472,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
</func>
<func>
- <name name="seed_s" arity="2"/>
+ <name name="seed_s" arity="2" since="OTP 18.0"/>
<fsummary>Seed the random number generation.</fsummary>
<desc>
<p>Seeds random number generation with the specified algorithm and
@@ -405,7 +481,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
</func>
<func>
- <name name="uniform" arity="0"/>
+ <name name="uniform" arity="0" since="OTP 18.0"/>
<fsummary>Return a random float.</fsummary>
<desc><marker id="uniform-0"/>
<p>
@@ -441,7 +517,7 @@ end.</pre>
</func>
<func>
- <name name="uniform_real" arity="0"/>
+ <name name="uniform_real" arity="0" since="OTP 21.0"/>
<fsummary>Return a random float.</fsummary>
<desc><marker id="uniform_real-0"/>
<p>
@@ -477,7 +553,7 @@ end.</pre>
</func>
<func>
- <name name="uniform" arity="1"/>
+ <name name="uniform" arity="1" since="OTP 18.0"/>
<fsummary>Return a random integer.</fsummary>
<desc><marker id="uniform-1"/>
<p>Returns, for a specified integer <c><anno>N</anno> >= 1</c>,
@@ -488,7 +564,7 @@ end.</pre>
</func>
<func>
- <name name="uniform_s" arity="1"/>
+ <name name="uniform_s" arity="1" since="OTP 18.0"/>
<fsummary>Return a random float.</fsummary>
<desc>
<p>
@@ -524,7 +600,7 @@ end.</pre>
</func>
<func>
- <name name="uniform_real_s" arity="1"/>
+ <name name="uniform_real_s" arity="1" since="OTP 21.0"/>
<fsummary>Return a random float.</fsummary>
<desc>
<p>
@@ -586,7 +662,7 @@ end.</pre>
</func>
<func>
- <name name="uniform_s" arity="2"/>
+ <name name="uniform_s" arity="2" since="OTP 18.0"/>
<fsummary>Return a random integer.</fsummary>
<desc>
<p>Returns, for a specified integer <c><anno>N</anno> >= 1</c>
diff --git a/lib/stdlib/doc/src/random.xml b/lib/stdlib/doc/src/random.xml
index 8d090d20b3..f0261ed009 100644
--- a/lib/stdlib/doc/src/random.xml
+++ b/lib/stdlib/doc/src/random.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>random.xml</file>
</header>
- <module>random</module>
+ <module since="">random</module>
<modulesummary>Pseudo-random number generation.</modulesummary>
<description>
<p>This module provides a random number generator. The method is attributed
@@ -73,7 +73,7 @@
<funcs>
<func>
- <name name="seed" arity="0"/>
+ <name name="seed" arity="0" since=""/>
<fsummary>Seed random number generation with default values.</fsummary>
<desc>
<p>Seeds random number generation with default (fixed) values
@@ -82,7 +82,7 @@
</func>
<func>
- <name name="seed" arity="1"/>
+ <name name="seed" arity="1" since=""/>
<fsummary>Seed random number generator.</fsummary>
<desc>
<p><c>seed({<anno>A1</anno>, <anno>A2</anno>, <anno>A3</anno>})</c>
@@ -92,7 +92,7 @@
</func>
<func>
- <name name="seed" arity="3"/>
+ <name name="seed" arity="3" since=""/>
<fsummary>Seed random number generator.</fsummary>
<desc>
<p>Seeds random number generation with integer values in the process
@@ -116,7 +116,7 @@ random:seed(erlang:phash2([node()]),
</func>
<func>
- <name name="seed0" arity="0"/>
+ <name name="seed0" arity="0" since=""/>
<fsummary>Return default state for random number generation.</fsummary>
<desc>
<p>Returns the default state.</p>
@@ -124,7 +124,7 @@ random:seed(erlang:phash2([node()]),
</func>
<func>
- <name name="uniform" arity="0"/>
+ <name name="uniform" arity="0" since=""/>
<fsummary>Return a random float.</fsummary>
<desc>
<p>Returns a random float uniformly distributed between <c>0.0</c>
@@ -133,7 +133,7 @@ random:seed(erlang:phash2([node()]),
</func>
<func>
- <name name="uniform" arity="1"/>
+ <name name="uniform" arity="1" since=""/>
<fsummary>Return a random integer.</fsummary>
<desc>
<p>Returns, for a specified integer <c><anno>N</anno> >= 1</c>,
@@ -144,7 +144,7 @@ random:seed(erlang:phash2([node()]),
</func>
<func>
- <name name="uniform_s" arity="1"/>
+ <name name="uniform_s" arity="1" since=""/>
<fsummary>Return a random float.</fsummary>
<desc>
<p>Returns, for a specified state, a random float uniformly
@@ -153,7 +153,7 @@ random:seed(erlang:phash2([node()]),
</func>
<func>
- <name name="uniform_s" arity="2"/>
+ <name name="uniform_s" arity="2" since=""/>
<fsummary>Return a random integer.</fsummary>
<desc>
<p>Returns, for a specified integer <c><anno>N</anno> >= 1</c> and a
diff --git a/lib/stdlib/doc/src/re.xml b/lib/stdlib/doc/src/re.xml
index 078ca0e38c..b04434492d 100644
--- a/lib/stdlib/doc/src/re.xml
+++ b/lib/stdlib/doc/src/re.xml
@@ -34,7 +34,7 @@
<rev>A</rev>
<file>re.xml</file>
</header>
- <module>re</module>
+ <module since="">re</module>
<modulesummary>Perl-like regular expressions for Erlang.</modulesummary>
<description>
<p>This module contains regular expression matching functions for
@@ -79,7 +79,7 @@
<funcs>
<func>
- <name name="version" arity="0"/>
+ <name name="version" arity="0" since="OTP 20.0"/>
<fsummary>Gives the PCRE version of the system in a string format</fsummary>
<desc>
<p>The return of this function is a string with the PCRE version of the system that was used in the Erlang/OTP compilation.</p>
@@ -87,7 +87,7 @@
</func>
<func>
- <name name="compile" arity="1"/>
+ <name name="compile" arity="1" since=""/>
<fsummary>Compile a regular expression into a match program</fsummary>
<desc>
<p>The same as <c>compile(<anno>Regexp</anno>,[])</c></p>
@@ -95,7 +95,7 @@
</func>
<func>
- <name name="compile" arity="2"/>
+ <name name="compile" arity="2" since=""/>
<fsummary>Compile a regular expression into a match program.</fsummary>
<desc>
<p>Compiles a regular expression, with the syntax
@@ -304,7 +304,7 @@
</func>
<func>
- <name name="inspect" arity="2"/>
+ <name name="inspect" arity="2" since="OTP 17.0"/>
<fsummary>Inspects a compiled regular expression.</fsummary>
<desc>
<p>Takes a compiled regular expression and an item, and returns the
@@ -348,7 +348,7 @@
</func>
<func>
- <name name="replace" arity="3"/>
+ <name name="replace" arity="3" since=""/>
<fsummary>Match a subject against regular expression and replace matching
elements with Replacement.</fsummary>
<desc>
@@ -358,7 +358,7 @@
</func>
<func>
- <name name="replace" arity="4"/>
+ <name name="replace" arity="4" since=""/>
<fsummary>Match a subject against regular expression and replace matching
elements with Replacement.</fsummary>
<desc>
@@ -408,7 +408,7 @@ re:replace("abcd","c","[\\&amp;]",[{return,list}]).</code>
</func>
<func>
- <name name="run" arity="2"/>
+ <name name="run" arity="2" since=""/>
<fsummary>Match a subject against regular expression and capture
subpatterns.</fsummary>
<desc>
@@ -417,7 +417,7 @@ re:replace("abcd","c","[\\&amp;]",[{return,list}]).</code>
</func>
<func>
- <name name="run" arity="3"/>
+ <name name="run" arity="3" since=""/>
<fsummary>Match a subject against regular expression and capture
subpatterns.</fsummary>
<type_desc variable="CompileOpt">See <seealso marker="#compile_options">
@@ -992,7 +992,7 @@ re:run("cacb","c(a|b)",[global,{capture,[1],list}]).</code>
</func>
<func>
- <name name="split" arity="2"/>
+ <name name="split" arity="2" since=""/>
<fsummary>Split a string by tokens specified as a regular expression.
</fsummary>
<desc>
@@ -1001,7 +1001,7 @@ re:run("cacb","c(a|b)",[global,{capture,[1],list}]).</code>
</func>
<func>
- <name name="split" arity="3"/>
+ <name name="split" arity="3" since=""/>
<fsummary>Split a string by tokens specified as a regular expression</fsummary>
<type_desc variable="CompileOpt">See <seealso marker="#compile_options">
<c>compile/2</c></seealso>.</type_desc>
diff --git a/lib/stdlib/doc/src/sets.xml b/lib/stdlib/doc/src/sets.xml
index 8db3e1e623..07ce41b7a7 100644
--- a/lib/stdlib/doc/src/sets.xml
+++ b/lib/stdlib/doc/src/sets.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>sets.xml</file>
</header>
- <module>sets</module>
+ <module since="">sets</module>
<modulesummary>Functions for set manipulation.</modulesummary>
<description>
<p>Sets are collections of elements with no duplicate elements.
@@ -59,7 +59,7 @@
<funcs>
<func>
- <name name="add_element" arity="2"/>
+ <name name="add_element" arity="2" since=""/>
<fsummary>Add an element to a <c>Set</c>.</fsummary>
<desc>
<p>Returns a new set formed from <c><anno>Set1</anno></c> with
@@ -68,7 +68,7 @@
</func>
<func>
- <name name="del_element" arity="2"/>
+ <name name="del_element" arity="2" since=""/>
<fsummary>Remove an element from a <c>Set</c>.</fsummary>
<desc>
<p>Returns <c><anno>Set1</anno></c>, but with
@@ -77,7 +77,7 @@
</func>
<func>
- <name name="filter" arity="2"/>
+ <name name="filter" arity="2" since=""/>
<fsummary>Filter set elements.</fsummary>
<desc>
<p>Filters elements in <c><anno>Set1</anno></c> with boolean function
@@ -86,7 +86,7 @@
</func>
<func>
- <name name="fold" arity="3"/>
+ <name name="fold" arity="3" since=""/>
<fsummary>Fold over set elements.</fsummary>
<desc>
<p>Folds <c><anno>Function</anno></c> over every element in
@@ -96,7 +96,7 @@
</func>
<func>
- <name name="from_list" arity="1"/>
+ <name name="from_list" arity="1" since=""/>
<fsummary>Convert a list into a <c>Set</c>.</fsummary>
<desc>
<p>Returns a set of the elements in <c><anno>List</anno></c>.</p>
@@ -104,7 +104,7 @@
</func>
<func>
- <name name="intersection" arity="1"/>
+ <name name="intersection" arity="1" since=""/>
<fsummary>Return the intersection of a list of <c>Sets</c>.</fsummary>
<desc>
<p>Returns the intersection of the non-empty list of sets.</p>
@@ -112,7 +112,7 @@
</func>
<func>
- <name name="intersection" arity="2"/>
+ <name name="intersection" arity="2" since=""/>
<fsummary>Return the intersection of two <c>Sets</c>.</fsummary>
<desc>
<p>Returns the intersection of <c><anno>Set1</anno></c> and
@@ -121,7 +121,7 @@
</func>
<func>
- <name name="is_disjoint" arity="2"/>
+ <name name="is_disjoint" arity="2" since=""/>
<fsummary>Check whether two <c>Sets</c> are disjoint.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Set1</anno></c> and
@@ -131,7 +131,7 @@
</func>
<func>
- <name name="is_element" arity="2"/>
+ <name name="is_element" arity="2" since=""/>
<fsummary>Test for membership of a <c>Set</c>.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Element</anno></c> is an element of
@@ -140,7 +140,7 @@
</func>
<func>
- <name name="is_empty" arity="1"/>
+ <name name="is_empty" arity="1" since="OTP 21.0"/>
<fsummary>Test for empty set.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Set</anno></c> is an empty set,
@@ -149,7 +149,7 @@
</func>
<func>
- <name name="is_set" arity="1"/>
+ <name name="is_set" arity="1" since=""/>
<fsummary>Test for a <c>Set</c>.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Set</anno></c> is a set of
@@ -158,7 +158,7 @@
</func>
<func>
- <name name="is_subset" arity="2"/>
+ <name name="is_subset" arity="2" since=""/>
<fsummary>Test for subset.</fsummary>
<desc>
<p>Returns <c>true</c> when every element of <c><anno>Set1</anno></c> is
@@ -167,7 +167,7 @@
</func>
<func>
- <name name="new" arity="0"/>
+ <name name="new" arity="0" since=""/>
<fsummary>Return an empty set.</fsummary>
<desc>
<p>Returns a new empty set.</p>
@@ -175,7 +175,7 @@
</func>
<func>
- <name name="size" arity="1"/>
+ <name name="size" arity="1" since=""/>
<fsummary>Return the number of elements in a set.</fsummary>
<desc>
<p>Returns the number of elements in <c><anno>Set</anno></c>.</p>
@@ -183,7 +183,7 @@
</func>
<func>
- <name name="subtract" arity="2"/>
+ <name name="subtract" arity="2" since=""/>
<fsummary>Return the difference of two <c>Sets</c>.</fsummary>
<desc>
<p>Returns only the elements of <c><anno>Set1</anno></c> that are not
@@ -192,7 +192,7 @@
</func>
<func>
- <name name="to_list" arity="1"/>
+ <name name="to_list" arity="1" since=""/>
<fsummary>Convert a <c>Set</c>into a list.</fsummary>
<desc>
<p>Returns the elements of <c><anno>Set</anno></c> as a list.
@@ -201,7 +201,7 @@
</func>
<func>
- <name name="union" arity="1"/>
+ <name name="union" arity="1" since=""/>
<fsummary>Return the union of a list of <c>Sets</c>.</fsummary>
<desc>
<p>Returns the merged (union) set of the list of sets.</p>
@@ -209,7 +209,7 @@
</func>
<func>
- <name name="union" arity="2"/>
+ <name name="union" arity="2" since=""/>
<fsummary>Return the union of two <c>Sets</c>.</fsummary>
<desc>
<p>Returns the merged (union) set of <c><anno>Set1</anno></c> and
diff --git a/lib/stdlib/doc/src/shell.xml b/lib/stdlib/doc/src/shell.xml
index 2593d3690b..50a0968531 100644
--- a/lib/stdlib/doc/src/shell.xml
+++ b/lib/stdlib/doc/src/shell.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>shell.xml</file>
</header>
- <module>shell</module>
+ <module since="">shell</module>
<modulesummary>The Erlang shell.</modulesummary>
<description>
<p>This module provides an Erlang shell.</p>
@@ -874,7 +874,7 @@ q - quit erlang
<funcs>
<func>
- <name>catch_exception(Bool) -> boolean()</name>
+ <name since="">catch_exception(Bool) -> boolean()</name>
<fsummary>Set the exception handling of the shell.</fsummary>
<type>
<v>Bool = boolean()</v>
@@ -892,7 +892,7 @@ q - quit erlang
</func>
<func>
- <name name="history" arity="1"/>
+ <name name="history" arity="1" since=""/>
<fsummary>Set the number of previous commands to keep.</fsummary>
<desc>
<p>Sets the number of previous commands to keep in the
@@ -902,7 +902,7 @@ q - quit erlang
</func>
<func>
- <name name="prompt_func" arity="1"/>
+ <name name="prompt_func" arity="1" since="OTP R13B04"/>
<fsummary>Set the shell prompt.</fsummary>
<desc>
<p>Sets the shell prompt function to <c><anno>PromptFunc</anno></c>.
@@ -911,7 +911,7 @@ q - quit erlang
</func>
<func>
- <name name="results" arity="1"/>
+ <name name="results" arity="1" since=""/>
<fsummary>Set the number of previous results to keep.</fsummary>
<desc>
<p>Sets the number of results from previous commands to keep in
@@ -921,7 +921,7 @@ q - quit erlang
</func>
<func>
- <name name="start_restricted" arity="1"/>
+ <name name="start_restricted" arity="1" since=""/>
<fsummary>Exit a normal shell and starts a restricted shell.</fsummary>
<desc>
<p>Exits a normal shell and starts a restricted shell.
@@ -936,7 +936,7 @@ q - quit erlang
</func>
<func>
- <name name="stop_restricted" arity="0"/>
+ <name name="stop_restricted" arity="0" since=""/>
<fsummary>Exit a restricted shell and starts a normal shell.</fsummary>
<desc>
<p>Exits a restricted shell and starts a normal shell. The function
@@ -945,7 +945,7 @@ q - quit erlang
</func>
<func>
- <name name="strings" arity="1"/>
+ <name name="strings" arity="1" since="OTP R16B"/>
<fsummary>Set the shell's string recognition flag.</fsummary>
<desc>
<p>Sets pretty printing of lists to <c><anno>Strings</anno></c>.
diff --git a/lib/stdlib/doc/src/slave.xml b/lib/stdlib/doc/src/slave.xml
index e53ec8231b..778c5f66e5 100644
--- a/lib/stdlib/doc/src/slave.xml
+++ b/lib/stdlib/doc/src/slave.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>slave</module>
+ <module since="">slave</module>
<modulesummary>Functions for starting and controlling slave nodes.
</modulesummary>
<description>
@@ -39,23 +39,30 @@
done through the master.</p>
<p>Slave nodes on other hosts than the current one are started with
- the <c>rsh</c> program. The user must be allowed to <c>rsh</c> to
+ the <c>ssh</c> program. The user must be allowed to <c>ssh</c> to
the remote hosts without being prompted for a password. This can
- be arranged in a number of ways (for details, see the <c>rsh</c>
+ be arranged in a number of ways (for details, see the <c>ssh</c>
documentation). A slave node started on the same host
as the master inherits certain environment values from the master,
such as the current directory and the environment variables. For
what can be assumed about the environment when a slave is started
- on another host, see the documentation for the <c>rsh</c>
+ on another host, see the documentation for the <c>ssh</c>
program.</p>
- <p>An alternative to the <c>rsh</c> program can be specified on
+ <p>An alternative to the <c>ssh</c> program can be specified on
the command line to
<seealso marker="erts:erl#erl"><c>erl(1)</c></seealso> as follows:</p>
<pre>
-rsh Program</pre>
+ <p>Note that the command specified with the <c>-rsh</c> flag is
+ treated as a file name which may contain spaces. It is thus not
+ possible to include any command line options. The remote node will
+ be launched as <c>"$RSH" "$REMOTE_HOSTNAME" erl -detached -noinput
+ ...</c>, so the
+ <c>erl</c> command must be found in the path on the remote host.</p>
+
<p>The slave node is to use the same file system at the master. At
least, Erlang/OTP is to be installed in the same place on both
computers and the same version of Erlang is to be used.</p>
@@ -68,7 +75,7 @@
<funcs>
<func>
- <name>pseudo([Master | ServerList]) -> ok</name>
+ <name since="">pseudo([Master | ServerList]) -> ok</name>
<fsummary>Start a number of pseudo servers.</fsummary>
<type>
<v>Master = node()</v>
@@ -84,7 +91,7 @@
</func>
<func>
- <name name="pseudo" arity="2"/>
+ <name name="pseudo" arity="2" since=""/>
<fsummary>Start a number of pseudo servers.</fsummary>
<desc>
<p>Starts a number of pseudo servers. A pseudo server is a
@@ -102,7 +109,7 @@ rpc:call(N, slave, pseudo, [node(), [pxw_server]]).</code>
</func>
<func>
- <name name="relay" arity="1"/>
+ <name name="relay" arity="1" since=""/>
<fsummary>Run a pseudo server.</fsummary>
<desc>
<p>Runs a pseudo server. This function never returns any value
@@ -113,9 +120,9 @@ rpc:call(N, slave, pseudo, [node(), [pxw_server]]).</code>
</func>
<func>
- <name name="start" arity="1"/>
- <name name="start" arity="2"/>
- <name name="start" arity="3"/>
+ <name name="start" arity="1" since=""/>
+ <name name="start" arity="2" since=""/>
+ <name name="start" arity="3" since=""/>
<fsummary>Start a slave node on a host.</fsummary>
<desc>
<p>Starts a slave node on host <c><anno>Host</anno></c>. Host names
@@ -166,7 +173,9 @@ slave:start(H, Name, Arg).</code>
</item>
<tag><c>no_rsh</c></tag>
<item>
- <p>There is no <c>rsh</c> program on the computer.</p>
+ <p>No remote shell program was found on the computer. Note
+ that <c>ssh</c> is used by default, but this can be overridden
+ with the <c>-rsh</c> flag.</p>
</item>
<tag><c>{already_running, <anno>Node</anno>}</c></tag>
<item>
@@ -178,9 +187,9 @@ slave:start(H, Name, Arg).</code>
</func>
<func>
- <name name="start_link" arity="1"/>
- <name name="start_link" arity="2"/>
- <name name="start_link" arity="3"/>
+ <name name="start_link" arity="1" since=""/>
+ <name name="start_link" arity="2" since=""/>
+ <name name="start_link" arity="3" since=""/>
<fsummary>Start and link to a slave node on a host.</fsummary>
<desc>
<p>Starts a slave node in the same way as <c>start/1,2,3</c>,
@@ -193,7 +202,7 @@ slave:start(H, Name, Arg).</code>
</func>
<func>
- <name name="stop" arity="1"/>
+ <name name="stop" arity="1" since=""/>
<fsummary>Stop (kill) a node.</fsummary>
<desc>
<p>Stops (kills) a node.</p>
diff --git a/lib/stdlib/doc/src/sofs.xml b/lib/stdlib/doc/src/sofs.xml
index 4cf1984d46..a0759d2f52 100644
--- a/lib/stdlib/doc/src/sofs.xml
+++ b/lib/stdlib/doc/src/sofs.xml
@@ -32,7 +32,7 @@
<rev>PA1</rev>
<file>sofs.xml</file>
</header>
- <module>sofs</module>
+ <module since="">sofs</module>
<modulesummary>Functions for manipulating sets of sets.</modulesummary>
<description>
<p>This module provides operations on finite sets and
@@ -456,8 +456,8 @@ fun(S) -> sofs:partition(1, S) end
<funcs>
<func>
- <name name="a_function" arity="1"/>
- <name name="a_function" arity="2"/>
+ <name name="a_function" arity="1" since=""/>
+ <name name="a_function" arity="2" since=""/>
<fsummary>Create a function.</fsummary>
<desc>
<p>Creates a <seealso marker="#function">function</seealso>.
@@ -470,7 +470,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="canonical_relation" arity="1"/>
+ <name name="canonical_relation" arity="1" since=""/>
<fsummary>Return the canonical map.</fsummary>
<desc>
<p>Returns the binary relation containing the elements
@@ -490,7 +490,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="composite" arity="2"/>
+ <name name="composite" arity="2" since=""/>
<fsummary>Return the composite of two functions.</fsummary>
<desc>
<p>Returns the <seealso marker="#composite">composite</seealso> of
@@ -506,7 +506,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="constant_function" arity="2"/>
+ <name name="constant_function" arity="2" since=""/>
<fsummary>Create the function that maps each element of a
set onto another set.</fsummary>
<desc>
@@ -522,7 +522,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="converse" arity="1"/>
+ <name name="converse" arity="1" since=""/>
<fsummary>Return the converse of a binary relation.</fsummary>
<desc>
<p>Returns the <seealso marker="#converse">converse</seealso>
@@ -536,7 +536,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="difference" arity="2"/>
+ <name name="difference" arity="2" since=""/>
<fsummary>Return the difference of two sets.</fsummary>
<desc>
<p>Returns the <seealso marker="#difference">difference</seealso> of
@@ -545,8 +545,8 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="digraph_to_family" arity="1"/>
- <name name="digraph_to_family" arity="2"/>
+ <name name="digraph_to_family" arity="1" since=""/>
+ <name name="digraph_to_family" arity="2" since=""/>
<fsummary>Create a family from a directed graph.</fsummary>
<desc>
<p>Creates a <seealso marker="#family">family</seealso> from
@@ -565,7 +565,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="domain" arity="1"/>
+ <name name="domain" arity="1" since=""/>
<fsummary>Return the domain of a binary relation.</fsummary>
<desc>
<p>Returns the <seealso marker="#domain">domain</seealso> of
@@ -579,7 +579,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="drestriction" arity="2"/>
+ <name name="drestriction" arity="2" since=""/>
<fsummary>Return a restriction of a binary relation.</fsummary>
<desc>
<p>Returns the difference between the binary relation
@@ -598,7 +598,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="drestriction" arity="3"/>
+ <name name="drestriction" arity="3" since=""/>
<fsummary>Return a restriction of a relation.</fsummary>
<desc>
<p>Returns a subset of <c><anno>Set1</anno></c> containing those
@@ -618,7 +618,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="empty_set" arity="0"/>
+ <name name="empty_set" arity="0" since=""/>
<fsummary>Return the untyped empty set.</fsummary>
<desc>
<p>Returns the <seealso marker="#sets_definition">untyped empty
@@ -628,7 +628,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="extension" arity="3"/>
+ <name name="extension" arity="3" since=""/>
<fsummary>Extend the domain of a binary relation.</fsummary>
<desc>
<p>Returns the <seealso marker="#extension">extension</seealso> of
@@ -648,8 +648,8 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="family" arity="1"/>
- <name name="family" arity="2"/>
+ <name name="family" arity="1" since=""/>
+ <name name="family" arity="2" since=""/>
<fsummary>Create a family of subsets.</fsummary>
<desc>
<p>Creates a <seealso marker="#family">family of subsets</seealso>.
@@ -662,7 +662,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="family_difference" arity="2"/>
+ <name name="family_difference" arity="2" since=""/>
<fsummary>Return the difference of two families.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> and <c><anno>Family2</anno></c>
@@ -683,7 +683,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="family_domain" arity="1"/>
+ <name name="family_domain" arity="1" since=""/>
<fsummary>Return a family of domains.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> is
@@ -704,7 +704,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="family_field" arity="1"/>
+ <name name="family_field" arity="1" since=""/>
<fsummary>Return a family of fields.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> is
@@ -728,7 +728,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="family_intersection" arity="1"/>
+ <name name="family_intersection" arity="1" since=""/>
<fsummary>Return the intersection of a family
of sets of sets.</fsummary>
<desc>
@@ -752,7 +752,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="family_intersection" arity="2"/>
+ <name name="family_intersection" arity="2" since=""/>
<fsummary>Return the intersection of two families.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> and <c><anno>Family2</anno></c>
@@ -772,7 +772,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="family_projection" arity="2"/>
+ <name name="family_projection" arity="2" since=""/>
<fsummary>Return a family of modified subsets.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> is
@@ -791,7 +791,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="family_range" arity="1"/>
+ <name name="family_range" arity="1" since=""/>
<fsummary>Return a family of ranges.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> is
@@ -812,7 +812,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="family_specification" arity="2"/>
+ <name name="family_specification" arity="2" since=""/>
<fsummary>Select a subset of a family using a predicate.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> is
@@ -837,8 +837,8 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="family_to_digraph" arity="1"/>
- <name name="family_to_digraph" arity="2"/>
+ <name name="family_to_digraph" arity="1" since=""/>
+ <name name="family_to_digraph" arity="2" since=""/>
<fsummary>Create a directed graph from a family.</fsummary>
<desc>
<p>Creates a directed graph from
@@ -863,7 +863,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="family_to_relation" arity="1"/>
+ <name name="family_to_relation" arity="1" since=""/>
<fsummary>Create a binary relation from a family.</fsummary>
<desc>
<p>If <c><anno>Family</anno></c> is
@@ -881,7 +881,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="family_union" arity="1"/>
+ <name name="family_union" arity="1" since=""/>
<fsummary>Return the union of a family of sets of sets.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> is
@@ -904,7 +904,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="family_union" arity="2"/>
+ <name name="family_union" arity="2" since=""/>
<fsummary>Return the union of two families.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> and <c><anno>Family2</anno></c>
@@ -926,7 +926,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="field" arity="1"/>
+ <name name="field" arity="1" since=""/>
<fsummary>Return the field of a binary relation.</fsummary>
<desc>
<p>Returns the <seealso marker="#field">field</seealso> of the
@@ -942,7 +942,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="from_external" arity="2"/>
+ <name name="from_external" arity="2" since=""/>
<fsummary>Create a set.</fsummary>
<desc>
<p>Creates a set from the <seealso marker="#external_set">external
@@ -955,7 +955,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="from_sets" arity="1" clause_i="1"/>
+ <name name="from_sets" arity="1" clause_i="1" since=""/>
<fsummary>Create a set out of a list of sets.</fsummary>
<desc>
<p>Returns the <seealso marker="#sets_definition">unordered
@@ -971,7 +971,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="from_sets" arity="1" clause_i="2"/>
+ <name name="from_sets" arity="1" clause_i="2" since=""/>
<fsummary>Create an ordered set out of a tuple of sets.</fsummary>
<desc>
<p>Returns the <seealso marker="#sets_definition">ordered
@@ -981,8 +981,8 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="from_term" arity="1"/>
- <name name="from_term" arity="2"/>
+ <name name="from_term" arity="1" since=""/>
+ <name name="from_term" arity="2" since=""/>
<fsummary>Create a set.</fsummary>
<desc>
<p><marker id="from_term"></marker>Creates an element
@@ -1031,7 +1031,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="image" arity="2"/>
+ <name name="image" arity="2" since=""/>
<fsummary>Return the image of a set under a binary relation.</fsummary>
<desc>
<p>Returns the <seealso marker="#image">image</seealso> of
@@ -1047,7 +1047,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="intersection" arity="1"/>
+ <name name="intersection" arity="1" since=""/>
<fsummary>Return the intersection of a set of sets.</fsummary>
<desc>
<p>Returns
@@ -1059,7 +1059,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="intersection" arity="2"/>
+ <name name="intersection" arity="2" since=""/>
<fsummary>Return the intersection of two sets.</fsummary>
<desc>
<p>Returns
@@ -1069,7 +1069,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="intersection_of_family" arity="1"/>
+ <name name="intersection_of_family" arity="1" since=""/>
<fsummary>Return the intersection of a family.</fsummary>
<desc>
<p>Returns the intersection of
@@ -1086,7 +1086,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="inverse" arity="1"/>
+ <name name="inverse" arity="1" since=""/>
<fsummary>Return the inverse of a function.</fsummary>
<desc>
<p>Returns the <seealso marker="#inverse">inverse</seealso>
@@ -1100,7 +1100,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="inverse_image" arity="2"/>
+ <name name="inverse_image" arity="2" since=""/>
<fsummary>Return the inverse image of a set under
a binary relation.</fsummary>
<desc>
@@ -1117,7 +1117,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="is_a_function" arity="1"/>
+ <name name="is_a_function" arity="1" since=""/>
<fsummary>Test for a function.</fsummary>
<desc>
<p>Returns <c>true</c> if the binary relation <c><anno>BinRel</anno></c>
@@ -1127,7 +1127,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="is_disjoint" arity="2"/>
+ <name name="is_disjoint" arity="2" since=""/>
<fsummary>Test for disjoint sets.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Set1</anno></c>
@@ -1138,7 +1138,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="is_empty_set" arity="1"/>
+ <name name="is_empty_set" arity="1" since=""/>
<fsummary>Test for an empty set.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>AnySet</anno></c> is an empty
@@ -1147,7 +1147,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="is_equal" arity="2"/>
+ <name name="is_equal" arity="2" since=""/>
<fsummary>Test two sets for equality.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>AnySet1</anno></c>
@@ -1164,7 +1164,7 @@ true</pre>
</func>
<func>
- <name name="is_set" arity="1"/>
+ <name name="is_set" arity="1" since=""/>
<fsummary>Test for an unordered set.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>AnySet</anno></c> is
@@ -1175,7 +1175,7 @@ true</pre>
</func>
<func>
- <name name="is_sofs_set" arity="1"/>
+ <name name="is_sofs_set" arity="1" since=""/>
<fsummary>Test for an unordered set.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is
@@ -1185,7 +1185,7 @@ true</pre>
</func>
<func>
- <name name="is_subset" arity="2"/>
+ <name name="is_subset" arity="2" since=""/>
<fsummary>Test two sets for subset.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Set1</anno></c> is
@@ -1195,7 +1195,7 @@ true</pre>
</func>
<func>
- <name name="is_type" arity="1"/>
+ <name name="is_type" arity="1" since=""/>
<fsummary>Test for a type.</fsummary>
<desc>
<p>Returns <c>true</c> if term <c><anno>Term</anno></c> is
@@ -1204,7 +1204,7 @@ true</pre>
</func>
<func>
- <name name="join" arity="4"/>
+ <name name="join" arity="4" since=""/>
<fsummary>Return the join of two relations.</fsummary>
<desc>
<p>Returns the <seealso marker="#natural_join">natural
@@ -1221,7 +1221,7 @@ true</pre>
</func>
<func>
- <name name="multiple_relative_product" arity="2"/>
+ <name name="multiple_relative_product" arity="2" since=""/>
<fsummary>Return the multiple relative product of a tuple of binary
relations and a relation.</fsummary>
<desc>
@@ -1242,7 +1242,7 @@ true</pre>
</func>
<func>
- <name name="no_elements" arity="1"/>
+ <name name="no_elements" arity="1" since=""/>
<fsummary>Return the number of elements of a set.</fsummary>
<desc>
<p>Returns the number of elements of the ordered or unordered
@@ -1251,7 +1251,7 @@ true</pre>
</func>
<func>
- <name name="partition" arity="1"/>
+ <name name="partition" arity="1" since=""/>
<fsummary>Return the coarsest partition given a set of sets.</fsummary>
<desc>
<p>Returns the <seealso marker="#partition">partition</seealso> of
@@ -1268,7 +1268,7 @@ true</pre>
</func>
<func>
- <name name="partition" arity="2"/>
+ <name name="partition" arity="2" since=""/>
<fsummary>Return a partition of a set.</fsummary>
<desc>
<p>Returns the <seealso marker="#partition">partition</seealso> of
@@ -1284,7 +1284,7 @@ true</pre>
</func>
<func>
- <name name="partition" arity="3"/>
+ <name name="partition" arity="3" since=""/>
<fsummary>Return a partition of a set.</fsummary>
<desc>
<p>Returns a pair of sets that, regarded as constituting a
@@ -1307,7 +1307,7 @@ true</pre>
</func>
<func>
- <name name="partition_family" arity="2"/>
+ <name name="partition_family" arity="2" since=""/>
<fsummary>Return a family indexing a partition.</fsummary>
<desc>
<p>Returns <seealso marker="#family">family</seealso>
@@ -1328,7 +1328,7 @@ true</pre>
</func>
<func>
- <name name="product" arity="1"/>
+ <name name="product" arity="1" since=""/>
<fsummary>Return the Cartesian product of a tuple of sets.</fsummary>
<desc>
<p>Returns the <seealso marker="#Cartesian_product_tuple">Cartesian
@@ -1347,7 +1347,7 @@ true</pre>
</func>
<func>
- <name name="product" arity="2"/>
+ <name name="product" arity="2" since=""/>
<fsummary>Return the Cartesian product of two sets.</fsummary>
<desc>
<p>Returns the <seealso marker="#Cartesian_product">Cartesian
@@ -1365,7 +1365,7 @@ true</pre>
</func>
<func>
- <name name="projection" arity="2"/>
+ <name name="projection" arity="2" since=""/>
<fsummary>Return a set of substituted elements.</fsummary>
<desc>
<p>Returns the set created by substituting each element of
@@ -1384,7 +1384,7 @@ true</pre>
</func>
<func>
- <name name="range" arity="1"/>
+ <name name="range" arity="1" since=""/>
<fsummary>Return the range of a binary relation.</fsummary>
<desc>
<p>Returns the <seealso marker="#range">range</seealso> of the
@@ -1398,8 +1398,8 @@ true</pre>
</func>
<func>
- <name name="relation" arity="1"/>
- <name name="relation" arity="2"/>
+ <name name="relation" arity="1" since=""/>
+ <name name="relation" arity="2" since=""/>
<fsummary>Create a relation.</fsummary>
<desc>
<p>Creates a <seealso marker="#relation">relation</seealso>.
@@ -1417,7 +1417,7 @@ true</pre>
</func>
<func>
- <name name="relation_to_family" arity="1"/>
+ <name name="relation_to_family" arity="1" since=""/>
<fsummary>Create a family from a binary relation.</fsummary>
<desc>
<p>Returns <seealso marker="#family">family</seealso>
@@ -1435,8 +1435,8 @@ true</pre>
</func>
<func>
- <name name="relative_product" arity="1"/>
- <name name="relative_product" arity="2" clause_i="1"/>
+ <name name="relative_product" arity="1" since=""/>
+ <name name="relative_product" arity="2" clause_i="1" since=""/>
<fsummary>Return the relative product of a list of binary relations
and a binary relation.</fsummary>
<desc>
@@ -1466,7 +1466,7 @@ true</pre>
</func>
<func>
- <name name="relative_product" arity="2" clause_i="2"/>
+ <name name="relative_product" arity="2" clause_i="2" since=""/>
<fsummary>Return the relative product of
two binary relations.</fsummary>
<desc>
@@ -1477,7 +1477,7 @@ true</pre>
</func>
<func>
- <name name="relative_product1" arity="2"/>
+ <name name="relative_product1" arity="2" since=""/>
<fsummary>Return the relative_product of
two binary relations.</fsummary>
<desc>
@@ -1498,7 +1498,7 @@ true</pre>
</func>
<func>
- <name name="restriction" arity="2"/>
+ <name name="restriction" arity="2" since=""/>
<fsummary>Return a restriction of a binary relation.</fsummary>
<desc>
<p>Returns the <seealso marker="#restriction">restriction</seealso> of
@@ -1514,7 +1514,7 @@ true</pre>
</func>
<func>
- <name name="restriction" arity="3"/>
+ <name name="restriction" arity="3" since=""/>
<fsummary>Return a restriction of a set.</fsummary>
<desc>
<p>Returns a subset of <c><anno>Set1</anno></c> containing those
@@ -1530,8 +1530,8 @@ true</pre>
</func>
<func>
- <name name="set" arity="1"/>
- <name name="set" arity="2"/>
+ <name name="set" arity="1" since=""/>
+ <name name="set" arity="2" since=""/>
<fsummary>Create a set of atoms or any type of sets.</fsummary>
<desc>
<p>Creates an <seealso marker="#sets_definition">unordered
@@ -1543,7 +1543,7 @@ true</pre>
</func>
<func>
- <name name="specification" arity="2"/>
+ <name name="specification" arity="2" since=""/>
<fsummary>Select a subset using a predicate.</fsummary>
<desc>
<p>Returns the set containing every element
@@ -1564,7 +1564,7 @@ true</pre>
</func>
<func>
- <name name="strict_relation" arity="1"/>
+ <name name="strict_relation" arity="1" since=""/>
<fsummary>Return the strict relation corresponding to
a given relation.</fsummary>
<desc>
@@ -1580,7 +1580,7 @@ true</pre>
</func>
<func>
- <name name="substitution" arity="2"/>
+ <name name="substitution" arity="2" since=""/>
<fsummary>Return a function with a given set as domain.</fsummary>
<desc>
<p>Returns a function, the domain of which
@@ -1629,7 +1629,7 @@ images2(SetOfSets, BinRel) ->
</func>
<func>
- <name name="symdiff" arity="2"/>
+ <name name="symdiff" arity="2" since=""/>
<fsummary>Return the symmetric difference of two sets.</fsummary>
<desc>
<p>Returns the <seealso marker="#symmetric_difference">symmetric
@@ -1645,7 +1645,7 @@ images2(SetOfSets, BinRel) ->
</func>
<func>
- <name name="symmetric_partition" arity="2"/>
+ <name name="symmetric_partition" arity="2" since=""/>
<fsummary>Return a partition of two sets.</fsummary>
<desc>
<p>Returns a triple of sets:</p>
@@ -1666,7 +1666,7 @@ images2(SetOfSets, BinRel) ->
</func>
<func>
- <name name="to_external" arity="1"/>
+ <name name="to_external" arity="1" since=""/>
<fsummary>Return the elements of a set.</fsummary>
<desc>
<p>Returns the <seealso marker="#external_set">external
@@ -1675,7 +1675,7 @@ images2(SetOfSets, BinRel) ->
</func>
<func>
- <name name="to_sets" arity="1"/>
+ <name name="to_sets" arity="1" since=""/>
<fsummary>Return a list or a tuple of the elements of a set.</fsummary>
<desc>
<p>Returns the elements of the ordered set <c><anno>ASet</anno></c>
@@ -1686,7 +1686,7 @@ images2(SetOfSets, BinRel) ->
</func>
<func>
- <name name="type" arity="1"/>
+ <name name="type" arity="1" since=""/>
<fsummary>Return the type of a set.</fsummary>
<desc>
<p>Returns the <seealso marker="#type">type</seealso> of an
@@ -1695,7 +1695,7 @@ images2(SetOfSets, BinRel) ->
</func>
<func>
- <name name="union" arity="1"/>
+ <name name="union" arity="1" since=""/>
<fsummary>Return the union of a set of sets.</fsummary>
<desc>
<p>Returns the <seealso marker="#union_n">union</seealso> of the
@@ -1704,7 +1704,7 @@ images2(SetOfSets, BinRel) ->
</func>
<func>
- <name name="union" arity="2"/>
+ <name name="union" arity="2" since=""/>
<fsummary>Return the union of two sets.</fsummary>
<desc>
<p>Returns the <seealso marker="#union">union</seealso> of
@@ -1713,7 +1713,7 @@ images2(SetOfSets, BinRel) ->
</func>
<func>
- <name name="union_of_family" arity="1"/>
+ <name name="union_of_family" arity="1" since=""/>
<fsummary>Return the union of a family.</fsummary>
<desc>
<p>Returns the union of <seealso marker="#family">family</seealso>
@@ -1727,7 +1727,7 @@ images2(SetOfSets, BinRel) ->
</func>
<func>
- <name name="weak_relation" arity="1"/>
+ <name name="weak_relation" arity="1" since=""/>
<fsummary>Return the weak relation corresponding to
a given relation.</fsummary>
<desc>
diff --git a/lib/stdlib/doc/src/string.xml b/lib/stdlib/doc/src/string.xml
index 3348464eba..d102191a57 100644
--- a/lib/stdlib/doc/src/string.xml
+++ b/lib/stdlib/doc/src/string.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>string.xml</file>
</header>
- <module>string</module>
+ <module since="">string</module>
<modulesummary>String processing functions.</modulesummary>
<description>
<p>This module provides functions for string processing.</p>
@@ -130,7 +130,7 @@
<funcs>
<func>
- <name name="casefold" arity="1"/>
+ <name name="casefold" arity="1" since="OTP 20.0"/>
<fsummary>Convert a string to a comparable string.</fsummary>
<desc>
<p>
@@ -147,7 +147,7 @@
</func>
<func>
- <name name="chomp" arity="1"/>
+ <name name="chomp" arity="1" since="OTP 20.0"/>
<fsummary>Remove trailing end of line control characters.</fsummary>
<desc>
<p>
@@ -164,9 +164,9 @@
</func>
<func>
- <name name="equal" arity="2"/>
- <name name="equal" arity="3"/>
- <name name="equal" arity="4"/>
+ <name name="equal" arity="2" since=""/>
+ <name name="equal" arity="3" since="OTP 20.0"/>
+ <name name="equal" arity="4" since="OTP 20.0"/>
<fsummary>Test string equality.</fsummary>
<desc>
<p>
@@ -201,8 +201,8 @@ true</pre>
</func>
<func>
- <name name="find" arity="2"/>
- <name name="find" arity="3"/>
+ <name name="find" arity="2" since="OTP 20.0"/>
+ <name name="find" arity="3" since="OTP 20.0"/>
<fsummary>Find start of substring.</fsummary>
<desc>
<p>
@@ -230,7 +230,7 @@ nomatch</pre>
</func>
<func>
- <name name="is_empty" arity="1"/>
+ <name name="is_empty" arity="1" since="OTP 20.0"/>
<fsummary>Check if the string is empty.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>String</anno></c> is the
@@ -245,7 +245,7 @@ true</pre>
</func>
<func>
- <name name="length" arity="1"/>
+ <name name="length" arity="1" since="OTP 20.0"/>
<fsummary>Calculate length of the string.</fsummary>
<desc>
<p>
@@ -261,7 +261,7 @@ true</pre>
</func>
<func>
- <name name="lexemes" arity="2"/>
+ <name name="lexemes" arity="2" since="OTP 20.0"/>
<fsummary>Split string into lexemes.</fsummary>
<desc>
<p>
@@ -287,7 +287,7 @@ true</pre>
</func>
<func>
- <name name="lowercase" arity="1"/>
+ <name name="lowercase" arity="1" since="OTP 20.0"/>
<fsummary>Convert a string to lowercase</fsummary>
<desc>
<p>
@@ -306,7 +306,7 @@ true</pre>
</func>
<func>
- <name name="next_codepoint" arity="1"/>
+ <name name="next_codepoint" arity="1" since="OTP 20.0"/>
<fsummary>Pick the first codepoint.</fsummary>
<desc>
<p>
@@ -323,7 +323,7 @@ true</pre>
</func>
<func>
- <name name="next_grapheme" arity="1"/>
+ <name name="next_grapheme" arity="1" since="OTP 20.0"/>
<fsummary>Pick the first grapheme cluster.</fsummary>
<desc>
<p>
@@ -340,7 +340,7 @@ true</pre>
</func>
<func>
- <name name="nth_lexeme" arity="3"/>
+ <name name="nth_lexeme" arity="3" since="OTP 20.0"/>
<fsummary>Pick the nth lexeme.</fsummary>
<desc>
<p>Returns lexeme number <c><anno>N</anno></c> in
@@ -355,9 +355,9 @@ true</pre>
</func>
<func>
- <name name="pad" arity="2"/>
- <name name="pad" arity="3"/>
- <name name="pad" arity="4"/>
+ <name name="pad" arity="2" since="OTP 20.0"/>
+ <name name="pad" arity="3" since="OTP 20.0"/>
+ <name name="pad" arity="4" since="OTP 20.0"/>
<fsummary>Pad a string to given length.</fsummary>
<desc>
<p>
@@ -381,7 +381,7 @@ true</pre>
</func>
<func>
- <name name="prefix" arity="2"/>
+ <name name="prefix" arity="2" since="OTP 20.0"/>
<fsummary>Remove prefix from string.</fsummary>
<desc>
<p>
@@ -400,8 +400,8 @@ nomatch</pre>
</func>
<func>
- <name name="replace" arity="3"/>
- <name name="replace" arity="4"/>
+ <name name="replace" arity="3" since="OTP 20.0"/>
+ <name name="replace" arity="4" since="OTP 20.0"/>
<fsummary>Replace a pattern in string.</fsummary>
<desc>
<p>
@@ -423,7 +423,7 @@ nomatch</pre>
</func>
<func>
- <name name="reverse" arity="1"/>
+ <name name="reverse" arity="1" since="OTP 20.0"/>
<fsummary>Reverses a string</fsummary>
<desc>
<p>
@@ -439,8 +439,8 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="slice" arity="2"/>
- <name name="slice" arity="3"/>
+ <name name="slice" arity="2" since="OTP 20.0"/>
+ <name name="slice" arity="3" since="OTP 20.0"/>
<fsummary>Extract a part of string</fsummary>
<desc>
<p>Returns a substring of <c><anno>String</anno></c> of
@@ -459,8 +459,8 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="split" arity="2"/>
- <name name="split" arity="3"/>
+ <name name="split" arity="2" since="OTP 20.0"/>
+ <name name="split" arity="3" since="OTP 20.0"/>
<fsummary>Split a string into substrings.</fsummary>
<desc>
<p>
@@ -482,9 +482,9 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="take" arity="2"/>
- <name name="take" arity="3"/>
- <name name="take" arity="4"/>
+ <name name="take" arity="2" since="OTP 20.0"/>
+ <name name="take" arity="3" since="OTP 20.0"/>
+ <name name="take" arity="4" since="OTP 20.0"/>
<fsummary>Take leading or trailing parts.</fsummary>
<desc>
<p>Takes characters from <c><anno>String</anno></c> as long as
@@ -508,7 +508,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="titlecase" arity="1"/>
+ <name name="titlecase" arity="1" since="OTP 20.0"/>
<fsummary>Convert a string to titlecase.</fsummary>
<desc>
<p>
@@ -522,7 +522,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="to_float" arity="1"/>
+ <name name="to_float" arity="1" since=""/>
<fsummary>Return a float whose text representation is the integers
(ASCII values) of a string.</fsummary>
<desc>
@@ -544,7 +544,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="to_integer" arity="1"/>
+ <name name="to_integer" arity="1" since=""/>
<fsummary>Return an integer whose text representation is the integers
(ASCII values) of a string.</fsummary>
<desc>
@@ -566,7 +566,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="to_graphemes" arity="1"/>
+ <name name="to_graphemes" arity="1" since="OTP 20.0"/>
<fsummary>Convert a string to a list of grapheme clusters.</fsummary>
<desc>
<p>
@@ -582,9 +582,9 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="trim" arity="1"/>
- <name name="trim" arity="2"/>
- <name name="trim" arity="3"/>
+ <name name="trim" arity="1" since="OTP 20.0"/>
+ <name name="trim" arity="2" since="OTP 20.0"/>
+ <name name="trim" arity="3" since="OTP 20.0"/>
<fsummary>Trim leading or trailing, or both, characters.</fsummary>
<desc>
<p>
@@ -616,7 +616,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="uppercase" arity="1"/>
+ <name name="uppercase" arity="1" since="OTP 20.0"/>
<fsummary>Convert a string to uppercase.</fsummary>
<desc>
<p>
@@ -649,8 +649,8 @@ ÖÄÅ</pre>
<funcs>
<func>
- <name name="centre" arity="2"/>
- <name name="centre" arity="3"/>
+ <name name="centre" arity="2" since=""/>
+ <name name="centre" arity="3" since=""/>
<fsummary>Center a string.</fsummary>
<desc>
<p>Returns a string, where <c><anno>String</anno></c> is centered in the
@@ -664,8 +664,8 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="chars" arity="2"/>
- <name name="chars" arity="3"/>
+ <name name="chars" arity="2" since=""/>
+ <name name="chars" arity="3" since=""/>
<fsummary>Return a string consisting of numbers of characters.</fsummary>
<desc>
<p>Returns a string consisting of <c><anno>Number</anno></c> characters
@@ -678,7 +678,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="chr" arity="2"/>
+ <name name="chr" arity="2" since=""/>
<fsummary>Return the index of the first occurrence of
a character in a string.</fsummary>
<desc>
@@ -692,7 +692,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="concat" arity="2"/>
+ <name name="concat" arity="2" since=""/>
<fsummary>Concatenate two strings.</fsummary>
<desc>
<p>Concatenates <c><anno>String1</anno></c> and
@@ -712,7 +712,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="copies" arity="2"/>
+ <name name="copies" arity="2" since=""/>
<fsummary>Copy a string.</fsummary>
<desc>
<p>Returns a string containing <c><anno>String</anno></c> repeated
@@ -724,7 +724,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="cspan" arity="2"/>
+ <name name="cspan" arity="2" since=""/>
<fsummary>Span characters at start of a string.</fsummary>
<desc>
<p>Returns the length of the maximum initial segment of
@@ -741,7 +741,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="join" arity="2"/>
+ <name name="join" arity="2" since=""/>
<fsummary>Join a list of strings with separator.</fsummary>
<desc>
<p>Returns a string with the elements of <c><anno>StringList</anno></c>
@@ -757,8 +757,8 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="left" arity="2"/>
- <name name="left" arity="3"/>
+ <name name="left" arity="2" since=""/>
+ <name name="left" arity="3" since=""/>
<fsummary>Adjust left end of a string.</fsummary>
<desc>
<p>Returns <c><anno>String</anno></c> with the length adjusted in
@@ -778,7 +778,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="len" arity="1"/>
+ <name name="len" arity="1" since=""/>
<fsummary>Return the length of a string.</fsummary>
<desc>
<p>Returns the number of characters in <c><anno>String</anno></c>.</p>
@@ -789,7 +789,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="rchr" arity="2"/>
+ <name name="rchr" arity="2" since=""/>
<fsummary>Return the index of the last occurrence of
a character in a string.</fsummary>
<desc>
@@ -803,8 +803,8 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="right" arity="2"/>
- <name name="right" arity="3"/>
+ <name name="right" arity="2" since=""/>
+ <name name="right" arity="3" since=""/>
<fsummary>Adjust right end of a string.</fsummary>
<desc>
<p>Returns <c><anno>String</anno></c> with the length adjusted in
@@ -823,7 +823,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="rstr" arity="2"/>
+ <name name="rstr" arity="2" since=""/>
<fsummary>Find the index of a substring.</fsummary>
<desc>
<p>Returns the position where the last occurrence of
@@ -841,7 +841,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="span" arity="2"/>
+ <name name="span" arity="2" since=""/>
<fsummary>Span characters at start of a string.</fsummary>
<desc>
<p>Returns the length of the maximum initial segment of
@@ -858,7 +858,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="str" arity="2"/>
+ <name name="str" arity="2" since=""/>
<fsummary>Find the index of a substring.</fsummary>
<desc>
<p>Returns the position where the first occurrence of
@@ -876,9 +876,9 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="strip" arity="1"/>
- <name name="strip" arity="2"/>
- <name name="strip" arity="3"/>
+ <name name="strip" arity="1" since=""/>
+ <name name="strip" arity="2" since=""/>
+ <name name="strip" arity="3" since=""/>
<fsummary>Strip leading or trailing characters.</fsummary>
<desc>
<p>Returns a string, where leading or trailing, or both, blanks or a
@@ -898,8 +898,8 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="sub_string" arity="2"/>
- <name name="sub_string" arity="3"/>
+ <name name="sub_string" arity="2" since=""/>
+ <name name="sub_string" arity="3" since=""/>
<fsummary>Extract a substring.</fsummary>
<desc>
<p>Returns a substring of <c><anno>String</anno></c>, starting at
@@ -916,8 +916,8 @@ sub_string("Hello World", 4, 8).
</func>
<func>
- <name name="substr" arity="2"/>
- <name name="substr" arity="3"/>
+ <name name="substr" arity="2" since=""/>
+ <name name="substr" arity="3" since=""/>
<fsummary>Return a substring of a string.</fsummary>
<desc>
<p>Returns a substring of <c><anno>String</anno></c>, starting at
@@ -934,8 +934,8 @@ sub_string("Hello World", 4, 8).
</func>
<func>
- <name name="sub_word" arity="2"/>
- <name name="sub_word" arity="3"/>
+ <name name="sub_word" arity="2" since=""/>
+ <name name="sub_word" arity="3" since=""/>
<fsummary>Extract subword.</fsummary>
<desc>
<p>Returns the word in position <c><anno>Number</anno></c> of
@@ -952,10 +952,10 @@ sub_string("Hello World", 4, 8).
</func>
<func>
- <name name="to_lower" arity="1" clause_i="1"/>
- <name name="to_lower" arity="1" clause_i="2"/>
- <name name="to_upper" arity="1" clause_i="1"/>
- <name name="to_upper" arity="1" clause_i="2"/>
+ <name name="to_lower" arity="1" clause_i="1" since=""/>
+ <name name="to_lower" arity="1" clause_i="2" since=""/>
+ <name name="to_upper" arity="1" clause_i="1" since=""/>
+ <name name="to_upper" arity="1" clause_i="2" since=""/>
<fsummary>Convert case of string (ISO/IEC 8859-1).</fsummary>
<type variable="String" name_i="1"/>
<type variable="Result" name_i="1"/>
@@ -974,7 +974,7 @@ sub_string("Hello World", 4, 8).
</func>
<func>
- <name name="tokens" arity="2"/>
+ <name name="tokens" arity="2" since=""/>
<fsummary>Split string into tokens.</fsummary>
<desc>
<p>Returns a list of tokens in <c><anno>String</anno></c>, separated
@@ -994,8 +994,8 @@ sub_string("Hello World", 4, 8).
</func>
<func>
- <name name="words" arity="1"/>
- <name name="words" arity="2"/>
+ <name name="words" arity="1" since=""/>
+ <name name="words" arity="2" since=""/>
<fsummary>Count blank separated words.</fsummary>
<desc>
<p>Returns the number of words in <c><anno>String</anno></c>, separated
diff --git a/lib/stdlib/doc/src/supervisor.xml b/lib/stdlib/doc/src/supervisor.xml
index 5fd5760499..f15b1a2dd3 100644
--- a/lib/stdlib/doc/src/supervisor.xml
+++ b/lib/stdlib/doc/src/supervisor.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>supervisor</module>
+ <module since="">supervisor</module>
<modulesummary>Generic supervisor behavior.</modulesummary>
<description>
<p>This behavior module provides a supervisor, a process that
@@ -318,7 +318,7 @@ child_spec() = #{id => child_id(), % mandatory
<funcs>
<func>
- <name name="check_childspecs" arity="1"/>
+ <name name="check_childspecs" arity="1" since=""/>
<fsummary>Check if children specifications are syntactically correct.
</fsummary>
<desc>
@@ -329,7 +329,7 @@ child_spec() = #{id => child_id(), % mandatory
</func>
<func>
- <name name="count_children" arity="1"/>
+ <name name="count_children" arity="1" since="OTP R13B04"/>
<fsummary>Return counts for the number of child specifications,
active children, supervisors, and workers.</fsummary>
<desc>
@@ -366,7 +366,7 @@ child_spec() = #{id => child_id(), % mandatory
</func>
<func>
- <name name="delete_child" arity="2"/>
+ <name name="delete_child" arity="2" since=""/>
<fsummary>Delete a child specification from a supervisor.</fsummary>
<desc>
<p>Tells supervisor <c><anno>SupRef</anno></c> to delete the child
@@ -387,7 +387,7 @@ child_spec() = #{id => child_id(), % mandatory
</func>
<func>
- <name name="get_childspec" arity="2"/>
+ <name name="get_childspec" arity="2" since="OTP 18.0"/>
<fsummary>Return the child specification map for the specified
child.</fsummary>
<desc>
@@ -400,7 +400,7 @@ child_spec() = #{id => child_id(), % mandatory
</func>
<func>
- <name name="restart_child" arity="2"/>
+ <name name="restart_child" arity="2" since=""/>
<fsummary>Restart a terminated child process belonging to a supervisor.
</fsummary>
<desc>
@@ -436,7 +436,7 @@ child_spec() = #{id => child_id(), % mandatory
</func>
<func>
- <name name="start_child" arity="2"/>
+ <name name="start_child" arity="2" since=""/>
<fsummary>Dynamically add a child process to a supervisor.</fsummary>
<type name="startchild_ret"/>
<type name="startchild_err"/>
@@ -503,8 +503,8 @@ child_spec() = #{id => child_id(), % mandatory
</func>
<func>
- <name name="start_link" arity="2"/>
- <name name="start_link" arity="3"/>
+ <name name="start_link" arity="2" since=""/>
+ <name name="start_link" arity="3" since=""/>
<fsummary>Create a supervisor process.</fsummary>
<type name="startlink_ret"/>
<type name="startlink_err"/>
@@ -584,7 +584,7 @@ child_spec() = #{id => child_id(), % mandatory
</func>
<func>
- <name name="terminate_child" arity="2"/>
+ <name name="terminate_child" arity="2" since=""/>
<fsummary>Terminate a child process belonging to a supervisor.</fsummary>
<desc>
<p>Tells supervisor <c><anno>SupRef</anno></c> to terminate the
@@ -621,7 +621,7 @@ child_spec() = #{id => child_id(), % mandatory
</func>
<func>
- <name name="which_children" arity="1"/>
+ <name name="which_children" arity="1" since=""/>
<fsummary>Return information about all children specifications and
child processes belonging to a supervisor.</fsummary>
<desc>
@@ -666,7 +666,7 @@ child_spec() = #{id => child_id(), % mandatory
<funcs>
<func>
- <name>Module:init(Args) -> Result</name>
+ <name since="">Module:init(Args) -> Result</name>
<fsummary>Return a supervisor specification.</fsummary>
<type>
<v>Args = term()</v>
diff --git a/lib/stdlib/doc/src/supervisor_bridge.xml b/lib/stdlib/doc/src/supervisor_bridge.xml
index c4c1b37548..ee5d97fea1 100644
--- a/lib/stdlib/doc/src/supervisor_bridge.xml
+++ b/lib/stdlib/doc/src/supervisor_bridge.xml
@@ -30,7 +30,7 @@
<date></date>
<rev></rev>
</header>
- <module>supervisor_bridge</module>
+ <module since="">supervisor_bridge</module>
<modulesummary>Generic supervisor bridge behavior.</modulesummary>
<description>
<p>This behavior module provides a supervisor bridge, a process
@@ -57,8 +57,8 @@
<funcs>
<func>
- <name name="start_link" arity="2"/>
- <name name="start_link" arity="3"/>
+ <name name="start_link" arity="2" since=""/>
+ <name name="start_link" arity="3" since=""/>
<fsummary>Create a supervisor bridge process.</fsummary>
<desc>
<p>Creates a supervisor bridge process, linked to the calling process,
@@ -133,7 +133,7 @@
<funcs>
<func>
- <name>Module:init(Args) -> Result</name>
+ <name since="">Module:init(Args) -> Result</name>
<fsummary>Initialize process and start subsystem.</fsummary>
<type>
<v>Args = term()</v>
@@ -164,7 +164,7 @@
</func>
<func>
- <name>Module:terminate(Reason, State)</name>
+ <name since="">Module:terminate(Reason, State)</name>
<fsummary>Clean up and stop subsystem.</fsummary>
<type>
<v>Reason = shutdown | term()</v>
diff --git a/lib/stdlib/doc/src/sys.xml b/lib/stdlib/doc/src/sys.xml
index 9fe816e33a..e22ca89ef5 100644
--- a/lib/stdlib/doc/src/sys.xml
+++ b/lib/stdlib/doc/src/sys.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2018</year>
+ <year>1996</year><year>2019</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -32,7 +32,7 @@
<rev></rev>
<file>sys.xml</file>
</header>
- <module>sys</module>
+ <module since="">sys</module>
<modulesummary>A functional interface to system messages.</modulesummary>
<description>
<p>This module contains functions for sending system messages used by
@@ -114,6 +114,154 @@
</datatype>
<datatype>
<name name="system_event"/>
+ <desc>
+ <taglist>
+ <tag><c>{in,<anno>Msg</anno>}</c></tag>
+ <item>
+ <p>
+ Is produced by <c>gen_server</c> and <c>gen_event</c>
+ when the message <c>Msg</c> arrives.
+ </p>
+ </item>
+ <tag><c>{in,<anno>Msg</anno>,<anno>State</anno>}</c></tag>
+ <item>
+ <p>
+ Is produced by <c>gen_statem</c>
+ when the message <c>Msg</c> arrives in state <c>State</c>.
+ </p>
+ <p>
+ For <c>gen_statem</c> the <c><anno>Msg</anno></c> term is
+ an <c>{EventType,EventContent}</c> tuple.
+ </p>
+ </item>
+ <tag><c>{out,<anno>Msg</anno>,<anno>To</anno>}</c></tag>
+ <item>
+ <p>
+ Is produced by <c>gen_statem</c> when the reply <c>Msg</c>
+ is sent back to <c>To</c> by returning
+ a <c>{reply,To,Msg}</c> action from the callback module.
+ </p>
+ <p>
+ <c><anno>To</anno></c> is of the same type
+ as the first argument to <c>gen_statem:reply/2</c>.
+ </p>
+ </item>
+ <tag>
+ <c>{out,<anno>Msg</anno>,<anno>To</anno>,<anno>State</anno>}</c>
+ </tag>
+ <item>
+ <p>
+ Is produced by <c>gen_server</c>
+ when the reply <c><anno>Msg</anno></c>
+ is sent back to <c><anno>To</anno></c>
+ by returning a <c>{reply,...}</c> tuple
+ from the callback module.
+ </p>
+ <p>
+ <c><anno>To</anno></c> is of the same type
+ as the first argument to <c>gen_server:reply/2</c>.
+ </p>
+ <p>
+ <c><anno>State</anno></c> is the new server state.
+ </p>
+ </item>
+ <tag>
+ <c>{noreply,<anno>State</anno>}</c>
+ </tag>
+ <item>
+ <p>
+ Is produced by <c>gen_server</c>
+ when a <c>{noreply,...}</c> tuple is returned
+ from the callback module.
+ </p>
+ <p>
+ <c><anno>State</anno></c> is the new server state.
+ </p>
+ </item>
+ <tag>
+ <c>{continue,<anno>Continuation</anno>}</c>
+ </tag>
+ <item>
+ <p>
+ Is produced by <c>gen_server</c>
+ when a <c>{continue,<anno>Continuation</anno>}</c>
+ tuple is returned from the callback module.
+ </p>
+ </item>
+ <tag>
+ <c>{code_change,<anno>Event</anno>,<anno>State</anno>}</c>
+ </tag>
+ <item>
+ <p>
+ Is produced by <c>gen_statem</c>
+ when the message <c><anno>Event</anno></c>
+ arrives in state <c><anno>State</anno></c>
+ as the first event after a code change.
+ </p>
+ <p>
+ <c><anno>Event</anno></c> is
+ an <c>{EventType,EventContent}</c> tuple.
+ </p>
+ </item>
+ <tag>
+ <c>
+ {postpone,<anno>Event</anno>,<anno>State</anno>,<anno>NextState</anno>}
+ </c>
+ </tag>
+ <item>
+ <p>
+ Is produced by <c>gen_statem</c>
+ when the message <c><anno>Event</anno></c>
+ is postponed in state <c><anno>State</anno></c>.
+ <c><anno>NextState</anno></c> is the new state.
+ </p>
+ <p>
+ <c><anno>Event</anno></c> is
+ an <c>{EventType,EventContent}</c> tuple.
+ </p>
+ </item>
+ <tag>
+ <c>
+ {consume,<anno>Event</anno>,<anno>State</anno>,<anno>NextState</anno>}
+ </c>
+ </tag>
+ <item>
+ <p>
+ Is produced by <c>gen_statem</c>
+ when the message <c><anno>Event</anno></c>
+ is consumed in state <c><anno>State</anno></c>.
+ <c><anno>NextState</anno></c> is the new state.
+ </p>
+ <p>
+ <c><anno>Event</anno></c> is
+ an <c>{EventType,EventContent}</c> tuple.
+ </p>
+ </item>
+ <tag>
+ <c>
+ {enter,<anno>State</anno>}
+ </c>
+ </tag>
+ <item>
+ <p>
+ Is produced by <c>gen_statem</c>
+ when the first state <c><anno>State</anno></c> is entered.
+ </p>
+ </item>
+ <tag>
+ <c>
+ {terminate,<anno>Reason</anno>,<anno>State</anno>}
+ </c>
+ </tag>
+ <item>
+ <p>
+ Is produced by <c>gen_statem</c>
+ when it terminates with reason <c><anno>Reason</anno></c>
+ in state <c><anno>State</anno></c>.
+ </p>
+ </item>
+ </taglist>
+ </desc>
</datatype>
<datatype>
<name name="dbg_opt"/>
@@ -123,14 +271,17 @@
<name name="dbg_fun"/>
</datatype>
<datatype>
+ <name name="debug_option"/>
+ </datatype>
+ <datatype>
<name name="format_fun"/>
</datatype>
</datatypes>
<funcs>
<func>
- <name name="change_code" arity="4"/>
- <name name="change_code" arity="5"/>
+ <name name="change_code" arity="4" since=""/>
+ <name name="change_code" arity="5" since=""/>
<fsummary>Send the code change system message to the process.</fsummary>
<desc>
<p>Tells the process to change code. The process must be
@@ -143,8 +294,8 @@
</func>
<func>
- <name name="get_state" arity="1"/>
- <name name="get_state" arity="2"/>
+ <name name="get_state" arity="1" since="OTP R16B01"/>
+ <name name="get_state" arity="2" since="OTP R16B01"/>
<fsummary>Get the state of the process.</fsummary>
<desc>
<p>Gets the state of the process.</p>
@@ -227,8 +378,8 @@
</func>
<func>
- <name name="get_status" arity="1"/>
- <name name="get_status" arity="2"/>
+ <name name="get_status" arity="1" since=""/>
+ <name name="get_status" arity="2" since=""/>
<fsummary>Get the status of the process.</fsummary>
<desc>
<p>Gets the status of the process.</p>
@@ -265,8 +416,8 @@
</func>
<func>
- <name name="install" arity="2"/>
- <name name="install" arity="3"/>
+ <name name="install" arity="2" since=""/>
+ <name name="install" arity="3" since=""/>
<fsummary>Install a debug function in the process.</fsummary>
<desc>
<p>Enables installation of alternative debug functions. An example of
@@ -283,8 +434,8 @@
</func>
<func>
- <name name="log" arity="2"/>
- <name name="log" arity="3"/>
+ <name name="log" arity="2" since=""/>
+ <name name="log" arity="3" since=""/>
<fsummary>Log system events in memory.</fsummary>
<desc>
<p>Turns the logging of system events on or off. If on, a
@@ -302,8 +453,8 @@
</func>
<func>
- <name name="log_to_file" arity="2"/>
- <name name="log_to_file" arity="3"/>
+ <name name="log_to_file" arity="2" since=""/>
+ <name name="log_to_file" arity="3" since=""/>
<fsummary>Log system events to the specified file.</fsummary>
<desc>
<p>Enables or disables the logging of all system events in text
@@ -315,8 +466,8 @@
</func>
<func>
- <name name="no_debug" arity="1"/>
- <name name="no_debug" arity="2"/>
+ <name name="no_debug" arity="1" since=""/>
+ <name name="no_debug" arity="2" since=""/>
<fsummary>Turn off debugging.</fsummary>
<desc>
<p>Turns off all debugging for the process. This includes
@@ -327,8 +478,8 @@
</func>
<func>
- <name name="remove" arity="2"/>
- <name name="remove" arity="3"/>
+ <name name="remove" arity="2" since=""/>
+ <name name="remove" arity="3" since=""/>
<fsummary>Remove a debug function from the process.</fsummary>
<desc>
<p>Removes an installed debug function from the
@@ -338,8 +489,8 @@
</func>
<func>
- <name name="replace_state" arity="2"/>
- <name name="replace_state" arity="3"/>
+ <name name="replace_state" arity="2" since="OTP R16B01"/>
+ <name name="replace_state" arity="3" since="OTP R16B01"/>
<fsummary>Replace the state of the process.</fsummary>
<desc>
<p>Replaces the state of the process, and returns the new state.</p>
@@ -451,8 +602,8 @@
</func>
<func>
- <name name="resume" arity="1"/>
- <name name="resume" arity="2"/>
+ <name name="resume" arity="1" since=""/>
+ <name name="resume" arity="2" since=""/>
<fsummary>Resume a suspended process.</fsummary>
<desc>
<p>Resumes a suspended process.</p>
@@ -460,8 +611,8 @@
</func>
<func>
- <name name="statistics" arity="2"/>
- <name name="statistics" arity="3"/>
+ <name name="statistics" arity="2" since=""/>
+ <name name="statistics" arity="3" since=""/>
<fsummary>Enable or disable the collections of statistics.</fsummary>
<desc>
<p>Enables or disables the collection of statistics. If
@@ -471,8 +622,8 @@
</func>
<func>
- <name name="suspend" arity="1"/>
- <name name="suspend" arity="2"/>
+ <name name="suspend" arity="1" since=""/>
+ <name name="suspend" arity="2" since=""/>
<fsummary>Suspend the process.</fsummary>
<desc>
<p>Suspends the process. When the process is suspended, it
@@ -482,8 +633,8 @@
</func>
<func>
- <name name="terminate" arity="2"/>
- <name name="terminate" arity="3"/>
+ <name name="terminate" arity="2" since="OTP 18.0"/>
+ <name name="terminate" arity="3" since="OTP 18.0"/>
<fsummary>Terminate the process.</fsummary>
<desc>
<p>Orders the process to terminate with the
@@ -494,8 +645,8 @@
</func>
<func>
- <name name="trace" arity="2"/>
- <name name="trace" arity="3"/>
+ <name name="trace" arity="2" since=""/>
+ <name name="trace" arity="3" since=""/>
<fsummary>Print all system events on <c>standard_io</c>.</fsummary>
<desc>
<p>Prints all system events on <c>standard_io</c>. The events are
@@ -518,7 +669,7 @@
<funcs>
<func>
- <name name="debug_options" arity="1"/>
+ <name name="debug_options" arity="1" since=""/>
<fsummary>Convert a list of options to a debug structure.</fsummary>
<desc>
<p>Can be used by a process that initiates a debug
@@ -529,9 +680,15 @@
</func>
<func>
- <name name="get_debug" arity="3"/>
+ <name name="get_debug" arity="3" since=""/>
<fsummary>Get the data associated with a debug option.</fsummary>
<desc>
+ <warning>
+ <p>
+ <c>get_debug/3</c> is deprecated since it returns
+ data of an internal type only useful for debugging.
+ </p>
+ </warning>
<p>Gets the data associated with a debug option.
<c><anno>Default</anno></c>
is returned if <c><anno>Item</anno></c> is not found. Can be
@@ -541,7 +698,7 @@
</func>
<func>
- <name name="handle_debug" arity="4"/>
+ <name name="handle_debug" arity="4" since=""/>
<fsummary>Generate a system event.</fsummary>
<desc>
<p>This function is called by a process when it generates a
@@ -556,7 +713,7 @@
</func>
<func>
- <name name="handle_system_msg" arity="6"/>
+ <name name="handle_system_msg" arity="6" since=""/>
<fsummary>Take care of system messages.</fsummary>
<desc>
<p>This function is used by a process module to take care of system
@@ -594,7 +751,7 @@
</func>
<func>
- <name name="print_log" arity="1"/>
+ <name name="print_log" arity="1" since=""/>
<fsummary>Print the logged events in the debug structure.</fsummary>
<desc>
<p>Prints the logged system events in the debug structure,
@@ -605,7 +762,19 @@
</func>
<func>
- <name>Module:system_code_change(Misc, Module, OldVsn, Extra) ->
+ <name name="get_log" arity="1" since="OTP-22.0"/>
+ <fsummary>Return the logged events in the debug structure.</fsummary>
+ <desc>
+ <p>
+ Returns the logged system events in the debug structure,
+ that is the last argument to
+ <seealso marker="#handle_debug/4"><c>handle_debug/4</c></seealso>.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="">Module:system_code_change(Misc, Module, OldVsn, Extra) ->
{ok, NMisc}</name>
<fsummary>Called when the process is to perform a code change.</fsummary>
<type>
@@ -628,7 +797,7 @@
</func>
<func>
- <name>Module:system_continue(Parent, Debug, Misc) -> none()</name>
+ <name since="">Module:system_continue(Parent, Debug, Misc) -> none()</name>
<fsummary>Called when the process is to continue its execution.</fsummary>
<type>
<v>Parent = pid()</v>
@@ -644,7 +813,7 @@
</func>
<func>
- <name>Module:system_get_state(Misc) -> {ok, State}</name>
+ <name since="OTP 17.0">Module:system_get_state(Misc) -> {ok, State}</name>
<fsummary>Called when the process is to return its current state.
</fsummary>
<type>
@@ -661,7 +830,7 @@
</func>
<func>
- <name>Module:system_replace_state(StateFun, Misc) ->
+ <name since="OTP 17.0">Module:system_replace_state(StateFun, Misc) ->
{ok, NState, NMisc}</name>
<fsummary>Called when the process is to replace its current state.
</fsummary>
@@ -681,7 +850,7 @@
</func>
<func>
- <name>Module:system_terminate(Reason, Parent, Debug, Misc) -> none()</name>
+ <name since="">Module:system_terminate(Reason, Parent, Debug, Misc) -> none()</name>
<fsummary>Called when the process is to terminate.</fsummary>
<type>
<v>Reason = term()</v>
diff --git a/lib/stdlib/doc/src/timer.xml b/lib/stdlib/doc/src/timer.xml
index e913e33589..165eecfbb0 100644
--- a/lib/stdlib/doc/src/timer.xml
+++ b/lib/stdlib/doc/src/timer.xml
@@ -32,7 +32,7 @@
<rev>D</rev>
<file>timer.xml</file>
</header>
- <module>timer</module>
+ <module since="">timer</module>
<modulesummary>Timer functions.</modulesummary>
<description>
<p>This module provides useful functions related to time. Unless otherwise
@@ -62,7 +62,7 @@
<funcs>
<func>
- <name name="apply_after" arity="4"/>
+ <name name="apply_after" arity="4" since=""/>
<fsummary>Apply <c>Module:Function(Arguments)</c> after a specified
<c>Time</c>.</fsummary>
<desc>
@@ -75,7 +75,7 @@
</func>
<func>
- <name name="apply_interval" arity="4"/>
+ <name name="apply_interval" arity="4" since=""/>
<fsummary>Evaluate <c>Module:Function(Arguments)</c> repeatedly at
intervals of <c>Time</c>.</fsummary>
<desc>
@@ -88,7 +88,7 @@
</func>
<func>
- <name name="cancel" arity="1"/>
+ <name name="cancel" arity="1" since=""/>
<fsummary>Cancel a previously requested time-out identified by
<c>TRef</c>.</fsummary>
<desc>
@@ -101,8 +101,8 @@
</func>
<func>
- <name name="exit_after" arity="2"/>
- <name name="exit_after" arity="3"/>
+ <name name="exit_after" arity="2" since=""/>
+ <name name="exit_after" arity="3" since=""/>
<fsummary>Send an exit signal with <c>Reason</c> after a specified
<c>Time</c>.</fsummary>
<desc>
@@ -117,7 +117,7 @@
</func>
<func>
- <name name="hms" arity="3"/>
+ <name name="hms" arity="3" since=""/>
<fsummary>Convert <c>Hours</c>+<c>Minutes</c>+<c>Seconds</c> to
<c>Milliseconds</c>.</fsummary>
<desc>
@@ -127,7 +127,7 @@
</func>
<func>
- <name name="hours" arity="1"/>
+ <name name="hours" arity="1" since=""/>
<fsummary>Convert <c>Hours</c> to <c>Milliseconds</c>.</fsummary>
<desc>
<p>Returns the number of milliseconds in <c><anno>Hours</anno></c>.</p>
@@ -135,8 +135,8 @@
</func>
<func>
- <name name="kill_after" arity="1"/>
- <name name="kill_after" arity="2"/>
+ <name name="kill_after" arity="1" since=""/>
+ <name name="kill_after" arity="2" since=""/>
<fsummary>Send an exit signal with <c>Reason</c> after a specified
<c>Time</c>.</fsummary>
<desc>
@@ -148,7 +148,7 @@
</func>
<func>
- <name name="minutes" arity="1"/>
+ <name name="minutes" arity="1" since=""/>
<fsummary>Converts <c>Minutes</c> to <c>Milliseconds</c>.</fsummary>
<desc>
<p>Returns the number of milliseconds in
@@ -157,7 +157,7 @@
</func>
<func>
- <name name="now_diff" arity="2"/>
+ <name name="now_diff" arity="2" since=""/>
<fsummary>Calculate time difference between time stamps.</fsummary>
<type_desc variable="Tdiff">In microseconds</type_desc>
<desc>
@@ -173,7 +173,7 @@
</func>
<func>
- <name name="seconds" arity="1"/>
+ <name name="seconds" arity="1" since=""/>
<fsummary>Convert <c>Seconds</c> to <c>Milliseconds</c>.</fsummary>
<desc>
<p>Returns the number of milliseconds in
@@ -182,8 +182,8 @@
</func>
<func>
- <name name="send_after" arity="2"/>
- <name name="send_after" arity="3"/>
+ <name name="send_after" arity="2" since=""/>
+ <name name="send_after" arity="3" since=""/>
<fsummary>Send <c>Message</c> to <c>Pid</c> after a specified
<c>Time</c>.</fsummary>
<desc>
@@ -206,8 +206,8 @@
</func>
<func>
- <name name="send_interval" arity="2"/>
- <name name="send_interval" arity="3"/>
+ <name name="send_interval" arity="2" since=""/>
+ <name name="send_interval" arity="3" since=""/>
<fsummary>Send <c>Message</c> repeatedly at intervals of <c>Time</c>.
</fsummary>
<desc>
@@ -231,7 +231,7 @@
</func>
<func>
- <name name="sleep" arity="1"/>
+ <name name="sleep" arity="1" since=""/>
<fsummary>Suspend the calling process for <c>Time</c> milliseconds.
</fsummary>
<desc>
@@ -244,7 +244,7 @@
</func>
<func>
- <name name="start" arity="0"/>
+ <name name="start" arity="0" since=""/>
<fsummary>Start a global timer server (named <c>timer_server</c>).
</fsummary>
<desc>
@@ -258,9 +258,9 @@
</func>
<func>
- <name name="tc" arity="1"/>
- <name name="tc" arity="2"/>
- <name name="tc" arity="3"/>
+ <name name="tc" arity="1" since="OTP R14B03"/>
+ <name name="tc" arity="2" since="OTP R14B"/>
+ <name name="tc" arity="3" since=""/>
<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>
diff --git a/lib/stdlib/doc/src/unicode.xml b/lib/stdlib/doc/src/unicode.xml
index d822aca89c..b7696a4b7e 100644
--- a/lib/stdlib/doc/src/unicode.xml
+++ b/lib/stdlib/doc/src/unicode.xml
@@ -30,7 +30,7 @@
<date></date>
<rev></rev>
</header>
- <module>unicode</module>
+ <module since="">unicode</module>
<modulesummary>Functions for converting Unicode characters.</modulesummary>
<description>
<p>This module contains functions for converting between different character
@@ -139,7 +139,7 @@
<funcs>
<func>
- <name name="bom_to_encoding" arity="1"/>
+ <name name="bom_to_encoding" arity="1" since=""/>
<fsummary>Identify UTF byte order marks in a binary.</fsummary>
<type name="endian"/>
<type_desc variable="Bin">
@@ -156,7 +156,7 @@
</func>
<func>
- <name name="characters_to_binary" arity="1"/>
+ <name name="characters_to_binary" arity="1" since=""/>
<fsummary>Convert a collection of characters to a UTF-8 binary.</fsummary>
<desc>
<p>Same as <c>characters_to_binary(<anno>Data</anno>, unicode,
@@ -165,7 +165,7 @@
</func>
<func>
- <name name="characters_to_binary" arity="2"/>
+ <name name="characters_to_binary" arity="2" since=""/>
<fsummary>Convert a collection of characters to a UTF-8 binary.</fsummary>
<desc>
<p>Same as <c>characters_to_binary(<anno>Data</anno>,
@@ -174,7 +174,7 @@
</func>
<func>
- <name name="characters_to_binary" arity="3"/>
+ <name name="characters_to_binary" arity="3" since=""/>
<fsummary>Convert a collection of characters to a UTF-8 binary.</fsummary>
<desc>
<p>Behaves as <seealso marker="#characters_to_list/2">
@@ -211,7 +211,7 @@
</func>
<func>
- <name name="characters_to_list" arity="1"/>
+ <name name="characters_to_list" arity="1" since=""/>
<fsummary>Convert a collection of characters to a list of Unicode
characters.</fsummary>
<desc>
@@ -220,7 +220,7 @@
</func>
<func>
- <name name="characters_to_list" arity="2"/>
+ <name name="characters_to_list" arity="2" since=""/>
<fsummary>Convert a collection of characters to a list of Unicode
characters.</fsummary>
<desc>
@@ -367,7 +367,7 @@ decode_data(Data) ->
</func>
<func>
- <name name="characters_to_nfc_list" arity="1"/>
+ <name name="characters_to_nfc_list" arity="1" since="OTP 20.0"/>
<fsummary>Normalize characters to a list of canonical equivalent
composed Unicode characters.</fsummary>
<desc>
@@ -386,7 +386,7 @@ decode_data(Data) ->
</func>
<func>
- <name name="characters_to_nfc_binary" arity="1"/>
+ <name name="characters_to_nfc_binary" arity="1" since="OTP 20.0"/>
<fsummary>Normalize characters to a utf8 binary of canonical equivalent
composed Unicode characters.</fsummary>
<desc>
@@ -404,7 +404,7 @@ decode_data(Data) ->
</func>
<func>
- <name name="characters_to_nfd_list" arity="1"/>
+ <name name="characters_to_nfd_list" arity="1" since="OTP 20.0"/>
<fsummary>Normalize characters to a list of canonical equivalent
decomposed Unicode characters.</fsummary>
<desc>
@@ -423,7 +423,7 @@ decode_data(Data) ->
</func>
<func>
- <name name="characters_to_nfd_binary" arity="1"/>
+ <name name="characters_to_nfd_binary" arity="1" since="OTP 20.0"/>
<fsummary>Normalize characters to a utf8 binary of canonical equivalent
decomposed Unicode characters.</fsummary>
<desc>
@@ -441,7 +441,7 @@ decode_data(Data) ->
</func>
<func>
- <name name="characters_to_nfkc_list" arity="1"/>
+ <name name="characters_to_nfkc_list" arity="1" since="OTP 20.0"/>
<fsummary>Normalize characters to a list of canonical equivalent
composed Unicode characters.</fsummary>
<desc>
@@ -460,7 +460,7 @@ decode_data(Data) ->
</func>
<func>
- <name name="characters_to_nfkc_binary" arity="1"/>
+ <name name="characters_to_nfkc_binary" arity="1" since="OTP 20.0"/>
<fsummary>Normalize characters to a utf8 binary of compatibly equivalent
composed Unicode characters.</fsummary>
<desc>
@@ -478,7 +478,7 @@ decode_data(Data) ->
</func>
<func>
- <name name="characters_to_nfkd_list" arity="1"/>
+ <name name="characters_to_nfkd_list" arity="1" since="OTP 20.0"/>
<fsummary>Normalize characters to a list of compatibly equivalent
decomposed Unicode characters.</fsummary>
<desc>
@@ -497,7 +497,7 @@ decode_data(Data) ->
</func>
<func>
- <name name="characters_to_nfkd_binary" arity="1"/>
+ <name name="characters_to_nfkd_binary" arity="1" since="OTP 20.0"/>
<fsummary>Normalize characters to a utf8 binary of compatibly equivalent
decomposed Unicode characters.</fsummary>
<desc>
@@ -515,7 +515,7 @@ decode_data(Data) ->
</func>
<func>
- <name name="encoding_to_bom" arity="1"/>
+ <name name="encoding_to_bom" arity="1" since=""/>
<fsummary>Create a binary UTF byte order mark from encoding.</fsummary>
<type_desc variable="Bin">
A <c>binary()</c> such that <c>byte_size(<anno>Bin</anno>) >= 4</c>.
diff --git a/lib/stdlib/doc/src/uri_string.xml b/lib/stdlib/doc/src/uri_string.xml
index 88d4600611..ad443486c5 100644
--- a/lib/stdlib/doc/src/uri_string.xml
+++ b/lib/stdlib/doc/src/uri_string.xml
@@ -27,7 +27,7 @@
<date>2018-02-07</date>
<rev>A</rev>
</header>
- <module>uri_string</module>
+ <module since="OTP 21.0">uri_string</module>
<modulesummary>URI processing functions.</modulesummary>
<description>
<p>This module contains functions for parsing and handling URIs
@@ -150,7 +150,7 @@
<funcs>
<func>
- <name name="compose_query" arity="1"/>
+ <name name="compose_query" arity="1" since="OTP 21.0"/>
<fsummary>Compose urlencoded query string.</fsummary>
<desc>
<p>Composes a form-urlencoded <c><anno>QueryString</anno></c> based on a
@@ -176,7 +176,7 @@
</func>
<func>
- <name name="compose_query" arity="2"/>
+ <name name="compose_query" arity="2" since="OTP 21.0"/>
<fsummary>Compose urlencoded query string.</fsummary>
<desc>
<p>Same as <c>compose_query/1</c> but with an additional
@@ -210,7 +210,7 @@
</func>
<func>
- <name name="dissect_query" arity="1"/>
+ <name name="dissect_query" arity="1" since="OTP 21.0"/>
<fsummary>Dissect query string.</fsummary>
<desc>
<p>Dissects an urlencoded <c><anno>QueryString</anno></c> and returns a
@@ -236,7 +236,7 @@
</func>
<func>
- <name name="normalize" arity="1"/>
+ <name name="normalize" arity="1" since="OTP 21.0"/>
<fsummary>Syntax-based normalization.</fsummary>
<desc>
<p>Transforms an <c><anno>URI</anno></c> into a normalized form
@@ -261,7 +261,7 @@
</func>
<func>
- <name name="normalize" arity="2"/>
+ <name name="normalize" arity="2" since="OTP 21.0"/>
<fsummary>Syntax-based normalization.</fsummary>
<desc>
<p>Same as <c>normalize/1</c> but with an additional
@@ -285,7 +285,7 @@
</func>
<func>
- <name name="parse" arity="1"/>
+ <name name="parse" arity="1" since="OTP 21.0"/>
<fsummary>Parse URI into a map.</fsummary>
<desc>
<p>Parses an <url href="https://www.ietf.org/rfc/rfc3986.txt">RFC 3986</url>
@@ -309,7 +309,7 @@
</func>
<func>
- <name name="recompose" arity="1"/>
+ <name name="recompose" arity="1" since="OTP 21.0"/>
<fsummary>Recompose URI.</fsummary>
<desc>
<p>Creates an <url href="https://www.ietf.org/rfc/rfc3986.txt">RFC 3986</url> compliant
@@ -332,7 +332,7 @@
</func>
<func>
- <name name="transcode" arity="2"/>
+ <name name="transcode" arity="2" since="OTP 21.0"/>
<fsummary>Transcode URI.</fsummary>
<desc>
<p>Transcodes an <url href="https://www.ietf.org/rfc/rfc3986.txt">RFC 3986</url>
diff --git a/lib/stdlib/doc/src/win32reg.xml b/lib/stdlib/doc/src/win32reg.xml
index f4a4fa1626..5e2aed6062 100644
--- a/lib/stdlib/doc/src/win32reg.xml
+++ b/lib/stdlib/doc/src/win32reg.xml
@@ -32,7 +32,7 @@
<rev>PA1</rev>
<file>win32reg.xml</file>
</header>
- <module>win32reg</module>
+ <module since="">win32reg</module>
<modulesummary>Provides access to the registry on Windows.</modulesummary>
<description>
<p>This module provides read and write access to the
@@ -112,7 +112,7 @@ hkdd HKEY_DYN_DATA</pre>
<funcs>
<func>
- <name name="change_key" arity="2"/>
+ <name name="change_key" arity="2" since=""/>
<fsummary>Move to a key in the registry.</fsummary>
<desc>
<p>Changes the current key to another key. Works like <c>cd</c>.
@@ -122,7 +122,7 @@ hkdd HKEY_DYN_DATA</pre>
</func>
<func>
- <name name="change_key_create" arity="2"/>
+ <name name="change_key_create" arity="2" since=""/>
<fsummary>Move to a key, create it if it is not there.</fsummary>
<desc>
<p>Creates a key, or just changes to it, if it is already there. Works
@@ -133,7 +133,7 @@ hkdd HKEY_DYN_DATA</pre>
</func>
<func>
- <name name="close" arity="1"/>
+ <name name="close" arity="1" since=""/>
<fsummary>Close the registry.</fsummary>
<desc>
<p>Closes the registry. After that, the <c><anno>RegHandle</anno></c>
@@ -142,7 +142,7 @@ hkdd HKEY_DYN_DATA</pre>
</func>
<func>
- <name name="current_key" arity="1"/>
+ <name name="current_key" arity="1" since=""/>
<fsummary>Return the path to the current key.</fsummary>
<desc>
<p>Returns the path to the current key. This is the equivalent of
@@ -153,7 +153,7 @@ hkdd HKEY_DYN_DATA</pre>
</func>
<func>
- <name name="delete_key" arity="1"/>
+ <name name="delete_key" arity="1" since=""/>
<fsummary>Delete the current key.</fsummary>
<desc>
<p>Deletes the current key, if it is valid. Calls the Win32 API
@@ -166,7 +166,7 @@ hkdd HKEY_DYN_DATA</pre>
</func>
<func>
- <name name="delete_value" arity="2"/>
+ <name name="delete_value" arity="2" since=""/>
<fsummary>Delete the named value on the current key.</fsummary>
<desc>
<p>Deletes a named value on the current key. The atom <c>default</c> is
@@ -176,7 +176,7 @@ hkdd HKEY_DYN_DATA</pre>
</func>
<func>
- <name name="expand" arity="1"/>
+ <name name="expand" arity="1" since=""/>
<fsummary>Expand a string with environment variables.</fsummary>
<desc>
<p>Expands a string containing environment variables between percent
@@ -189,7 +189,7 @@ hkdd HKEY_DYN_DATA</pre>
</func>
<func>
- <name name="format_error" arity="1"/>
+ <name name="format_error" arity="1" since=""/>
<fsummary>Convert a POSIX error code to a string.</fsummary>
<desc>
<p>Converts a POSIX error code to a string
@@ -198,7 +198,7 @@ hkdd HKEY_DYN_DATA</pre>
</func>
<func>
- <name name="open" arity="1"/>
+ <name name="open" arity="1" since=""/>
<fsummary>Open the registry for reading or writing.</fsummary>
<desc>
<p>Opens the registry for reading or writing. The current key is the
@@ -211,7 +211,7 @@ hkdd HKEY_DYN_DATA</pre>
</func>
<func>
- <name name="set_value" arity="3"/>
+ <name name="set_value" arity="3" since=""/>
<fsummary>Set value at the current registry key with specified name.
</fsummary>
<desc>
@@ -230,7 +230,7 @@ hkdd HKEY_DYN_DATA</pre>
</func>
<func>
- <name name="sub_keys" arity="1"/>
+ <name name="sub_keys" arity="1" since=""/>
<fsummary>Get subkeys to the current key.</fsummary>
<desc>
<p>Returns a list of subkeys to the current key. Calls the Win32
@@ -240,7 +240,7 @@ hkdd HKEY_DYN_DATA</pre>
</func>
<func>
- <name name="value" arity="2"/>
+ <name name="value" arity="2" since=""/>
<fsummary>Get the named value on the current key.</fsummary>
<desc>
<p>Retrieves the named value (or default) on the current key.
@@ -251,7 +251,7 @@ hkdd HKEY_DYN_DATA</pre>
</func>
<func>
- <name name="values" arity="1"/>
+ <name name="values" arity="1" since=""/>
<fsummary>Get all values on the current key.</fsummary>
<desc>
<p>Retrieves a list of all values on the current key. The values
diff --git a/lib/stdlib/doc/src/zip.xml b/lib/stdlib/doc/src/zip.xml
index 0b5eac1e16..bb2ed7727a 100644
--- a/lib/stdlib/doc/src/zip.xml
+++ b/lib/stdlib/doc/src/zip.xml
@@ -32,7 +32,7 @@
<rev>PA1</rev>
<file>zip.xml</file>
</header>
- <module>zip</module>
+ <module since="">zip</module>
<modulesummary>Utility for reading and creating 'zip' archives.
</modulesummary>
<description>
@@ -180,7 +180,7 @@
<funcs>
<func>
- <name name="foldl" arity="3"/>
+ <name name="foldl" arity="3" since="OTP R14B"/>
<fsummary>Fold a function over all files in a zip archive.</fsummary>
<desc>
<p>Calls <c><anno>Fun</anno>(<anno>FileInArchive</anno>, <anno>GetInfo
@@ -234,10 +234,10 @@
</func>
<func>
- <name name="list_dir" arity="1"/>
- <name name="list_dir" arity="2"/>
- <name name="table" arity="1" />
- <name name="table" arity="2"/>
+ <name name="list_dir" arity="1" since=""/>
+ <name name="list_dir" arity="2" since=""/>
+ <name name="table" arity="1" since=""/>
+ <name name="table" arity="2" since=""/>
<fsummary>Retrieve the name of all files in a zip archive.</fsummary>
<desc>
<p><c>list_dir/1</c> retrieves all filenames in the zip archive
@@ -263,7 +263,7 @@
</func>
<func>
- <name name="t" arity="1"/>
+ <name name="t" arity="1" since=""/>
<fsummary>Print the name of each file in a zip archive.</fsummary>
<desc>
<p>Prints all filenames in the zip archive <c><anno>Archive</anno></c>
@@ -272,7 +272,7 @@
</func>
<func>
- <name name="tt" arity="1"/>
+ <name name="tt" arity="1" since=""/>
<fsummary>Print name and information for each file in a zip archive.
</fsummary>
<desc>
@@ -283,10 +283,10 @@
</func>
<func>
- <name name="unzip" arity="1"/>
- <name name="unzip" arity="2"/>
- <name name="extract" arity="1"/>
- <name name="extract" arity="2"/>
+ <name name="unzip" arity="1" since=""/>
+ <name name="unzip" arity="2" since=""/>
+ <name name="extract" arity="1" since=""/>
+ <name name="extract" arity="2" since=""/>
<fsummary>Extract files from a zip archive.</fsummary>
<desc>
<p><c>unzip/1</c> extracts all files from a zip archive.</p>
@@ -353,10 +353,10 @@
</func>
<func>
- <name name="zip" arity="2"/>
- <name name="zip" arity="3"/>
- <name name="create" arity="2"/>
- <name name="create" arity="3"/>
+ <name name="zip" arity="2" since=""/>
+ <name name="zip" arity="3" since=""/>
+ <name name="create" arity="2" since=""/>
+ <name name="create" arity="3" since=""/>
<fsummary>Create a zip archive with options.</fsummary>
<desc>
<p>Creates a zip archive containing the files specified in
@@ -481,7 +481,7 @@
</func>
<func>
- <name name="zip_close" arity="1"/>
+ <name name="zip_close" arity="1" since=""/>
<fsummary>Close an open archive.</fsummary>
<desc>
<p>Closes a zip archive, previously opened with
@@ -492,8 +492,8 @@
</func>
<func>
- <name name="zip_get" arity="1"/>
- <name name="zip_get" arity="2"/>
+ <name name="zip_get" arity="1" since=""/>
+ <name name="zip_get" arity="2" since=""/>
<fsummary>Extract files from an open archive.</fsummary>
<desc>
<p>Extracts one or all files from an open archive.</p>
@@ -505,7 +505,7 @@
</func>
<func>
- <name name="zip_list_dir" arity="1"/>
+ <name name="zip_list_dir" arity="1" since=""/>
<fsummary>Return a table of files in open zip archive.</fsummary>
<desc>
<p>Returns the file list of an open zip archive. The first returned
@@ -514,8 +514,8 @@
</func>
<func>
- <name name="zip_open" arity="1"/>
- <name name="zip_open" arity="2"/>
+ <name name="zip_open" arity="1" since=""/>
+ <name name="zip_open" arity="2" since=""/>
<fsummary>Open an archive and return a handle to it.</fsummary>
<desc>
<p>Opens a zip archive, and reads and saves its directory. This
diff --git a/lib/stdlib/include/assert.hrl b/lib/stdlib/include/assert.hrl
index 2ec89e7d8a..28d25c6589 100644
--- a/lib/stdlib/include/assert.hrl
+++ b/lib/stdlib/include/assert.hrl
@@ -140,7 +140,7 @@
-endif.
%% This is mostly a convenience which gives more detailed reports.
-%% Note: Guard is a guarded pattern, and can not be used for value.
+%% Note: Guard is a guarded pattern, and cannot be used for value.
-ifdef(NOASSERT).
-define(assertMatch(Guard, Expr), ok).
-define(assertMatch(Guard, Expr, Comment), ok).
@@ -289,7 +289,7 @@
end).
-endif.
-%% Note: Class and Term are patterns, and can not be used for value.
+%% Note: Class and Term are patterns, and cannot be used for value.
%% Term can be a guarded pattern, but Class cannot.
-ifdef(NOASSERT).
-define(assertException(Class, Term, Expr), ok).
@@ -364,7 +364,7 @@
?assertException(throw, Term, Expr, Comment)).
%% This is the inverse case of assertException, for convenience.
-%% Note: Class and Term are patterns, and can not be used for value.
+%% Note: Class and Term are patterns, and cannot be used for value.
%% Both Class and Term can be guarded patterns.
-ifdef(NOASSERT).
-define(assertNotException(Class, Term, Expr), ok).
diff --git a/lib/stdlib/src/calendar.erl b/lib/stdlib/src/calendar.erl
index bb5d450cd6..3a8fe2211b 100644
--- a/lib/stdlib/src/calendar.erl
+++ b/lib/stdlib/src/calendar.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -357,13 +357,17 @@ rfc3339_to_system_time(DateTimeString) ->
rfc3339_to_system_time(DateTimeString, Options) ->
Unit = proplists:get_value(unit, Options, second),
%% _T is the character separating the date and the time:
- {DateStr, [_T|TimeStr]} = lists:split(10, DateTimeString),
- {TimeStr2, TimeStr3} = lists:split(8, TimeStr),
- {ok, [Hour, Min, Sec], []} = io_lib:fread("~d:~d:~d", TimeStr2),
- {ok, [Year, Month, Day], []} = io_lib:fread("~d-~d-~d", DateStr),
+ [Y1, Y2, Y3, Y4, $-, Mon1, Mon2, $-, D1, D2, _T,
+ H1, H2, $:, Min1, Min2, $:, S1, S2 | TimeStr] = DateTimeString,
+ Hour = list_to_integer([H1, H2]),
+ Min = list_to_integer([Min1, Min2]),
+ Sec = list_to_integer([S1, S2]),
+ Year = list_to_integer([Y1, Y2, Y3, Y4]),
+ Month = list_to_integer([Mon1, Mon2]),
+ Day = list_to_integer([D1, D2]),
DateTime = {{Year, Month, Day}, {Hour, Min, Sec}},
IsFractionChar = fun(C) -> C >= $0 andalso C =< $9 orelse C =:= $. end,
- {FractionStr, UtcOffset} = lists:splitwith(IsFractionChar, TimeStr3),
+ {FractionStr, UtcOffset} = lists:splitwith(IsFractionChar, TimeStr),
Time = datetime_to_system_time(DateTime),
Secs = Time - offset_adjustment(Time, second, UtcOffset),
check(DateTimeString, Options, Secs),
@@ -451,8 +455,9 @@ system_time_to_rfc3339(Time, Options) ->
DateTime = system_time_to_datetime(Secs),
{{Year, Month, Day}, {Hour, Min, Sec}} = DateTime,
FractionStr = fraction_str(Factor, AdjustedTime),
- flat_fwrite("~4.10.0B-~2.10.0B-~2.10.0B~c~2.10.0B:~2.10.0B:~2.10.0B~s~s",
- [Year, Month, Day, T, Hour, Min, Sec, FractionStr, Offset]).
+ L = [pad4(Year), "-", pad2(Month), "-", pad2(Day), [T],
+ pad2(Hour), ":", pad2(Min), ":", pad2(Sec), FractionStr, Offset],
+ lists:append(L).
%% time_difference(T1, T2) = Tdiff
%%
@@ -529,24 +534,41 @@ valid_date({Y, M, D}) ->
%% day_to_year(DayOfEpoch) = {Year, DayOfYear}
%%
-%% The idea here is to first guess a year, and then adjust. Although
-%% the implementation is recursive, at most 1 or 2 recursive steps
+%% The idea here is to first set the upper and lower bounds for a year,
+%% and then adjust a range by interpolation search. Although complexity
+%% of the algorithm is log(log(n)), at most 1 or 2 recursive steps
%% are taken.
-%% If DayOfEpoch is very large, we need far more than 1 or 2 iterations,
-%% since we just subtract a yearful of days at a time until we're there.
%%
-spec day_to_year(non_neg_integer()) -> {year(), day_of_year()}.
day_to_year(DayOfEpoch) when DayOfEpoch >= 0 ->
- Y0 = DayOfEpoch div ?DAYS_PER_YEAR,
- {Y1, D1} = dty(Y0, DayOfEpoch, dy(Y0)),
+ YMax = DayOfEpoch div ?DAYS_PER_YEAR,
+ YMin = DayOfEpoch div ?DAYS_PER_LEAP_YEAR,
+ {Y1, D1} = dty(YMin, YMax, DayOfEpoch, dy(YMin), dy(YMax)),
{Y1, DayOfEpoch - D1}.
--spec dty(year(), non_neg_integer(), non_neg_integer()) ->
+-spec dty(year(), year(), non_neg_integer(), non_neg_integer(),
+ non_neg_integer()) ->
{year(), non_neg_integer()}.
-dty(Y, D1, D2) when D1 < D2 ->
- dty(Y-1, D1, dy(Y-1));
-dty(Y, _D1, D2) ->
- {Y, D2}.
+dty(Min, Max, _D1, DMin, _DMax) when Min == Max ->
+ {Min, DMin};
+dty(Min, Max, D1, DMin, DMax) ->
+ Diff = Max - Min,
+ Mid = Min + (Diff * (D1 - DMin)) div (DMax - DMin),
+ MidLength =
+ case is_leap_year(Mid) of
+ true -> ?DAYS_PER_LEAP_YEAR;
+ false -> ?DAYS_PER_YEAR
+ end,
+ case dy(Mid) of
+ D2 when D1 < D2 ->
+ NewMax = Mid - 1,
+ dty(Min, NewMax, D1, DMin, dy(NewMax));
+ D2 when D1 - D2 >= MidLength ->
+ NewMin = Mid + 1,
+ dty(NewMin, Max, D1, dy(NewMin), DMax);
+ D2 ->
+ {Mid, D2}
+ end.
%%
%% The Gregorian days of the iso week 01 day 1 for a given year.
@@ -663,7 +685,7 @@ offset(OffsetOption, Secs0) when OffsetOption =:= "";
Secs = abs(Secs0),
Hour = Secs div 3600,
Min = (Secs rem 3600) div 60,
- io_lib:fwrite("~c~2.10.0B:~2.10.0B", [Sign, Hour, Min]);
+ [Sign | lists:append([pad2(Hour), ":", pad2(Min)])];
offset(OffsetOption, _Secs) ->
OffsetOption.
@@ -678,8 +700,10 @@ offset_string_adjustment(_Time, _Unit, "Z") ->
0;
offset_string_adjustment(_Time, _Unit, "z") ->
0;
-offset_string_adjustment(_Time, _Unit, [Sign|Tz]) ->
- {ok, [Hour, Min], []} = io_lib:fread("~d:~d", Tz),
+offset_string_adjustment(_Time, _Unit, Tz) ->
+ [Sign, H1, H2, $:, M1, M2] = Tz,
+ Hour = list_to_integer([H1, H2]),
+ Min = list_to_integer([M1, M2]),
Adjustment = 3600 * Hour + 60 * Min,
case Sign of
$- -> -Adjustment;
@@ -687,8 +711,9 @@ offset_string_adjustment(_Time, _Unit, [Sign|Tz]) ->
end.
local_offset(SystemTime, Unit) ->
- LocalTime = system_time_to_local_time(SystemTime, Unit),
+ %% Not optimized for special cases.
UniversalTime = system_time_to_universal_time(SystemTime, Unit),
+ LocalTime = erlang:universaltime_to_localtime(UniversalTime),
LocalSecs = datetime_to_gregorian_seconds(LocalTime),
UniversalSecs = datetime_to_gregorian_seconds(UniversalTime),
LocalSecs - UniversalSecs.
@@ -697,7 +722,8 @@ fraction_str(1, _Time) ->
"";
fraction_str(Factor, Time) ->
Fraction = Time rem Factor,
- io_lib:fwrite(".~*..0B", [log10(Factor), abs(Fraction)]).
+ S = integer_to_list(abs(Fraction)),
+ [$. | pad(log10(Factor) - length(S), S)].
fraction(second, _) ->
0;
@@ -718,5 +744,21 @@ log10(1000) -> 3;
log10(1000000) -> 6;
log10(1000000000) -> 9.
-flat_fwrite(F, S) ->
- lists:flatten(io_lib:fwrite(F, S)).
+pad(0, S) ->
+ S;
+pad(I, S) ->
+ [$0 | pad(I - 1, S)].
+
+pad2(N) when N < 10 ->
+ [$0 | integer_to_list(N)];
+pad2(N) ->
+ integer_to_list(N).
+
+pad4(N) when N < 10 ->
+ [$0, $0, $0 | integer_to_list(N)];
+pad4(N) when N < 100 ->
+ [$0, $0 | integer_to_list(N)];
+pad4(N) when N < 1000 ->
+ [$0 | integer_to_list(N)];
+pad4(N) ->
+ integer_to_list(N).
diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl
index e0cd68617b..3ec78a2667 100644
--- a/lib/stdlib/src/erl_lint.erl
+++ b/lib/stdlib/src/erl_lint.erl
@@ -831,13 +831,15 @@ bif_clashes(Forms, #lint{nowarn_bif_clash=Nowarn} = St) ->
%% not_deprecated(Forms, State0) -> State
-not_deprecated(Forms, St0) ->
+not_deprecated(Forms, #lint{compile=Opts}=St0) ->
%% There are no line numbers in St0#lint.compile.
MFAsL = [{MFA,L} ||
{attribute, L, compile, Args} <- Forms,
{nowarn_deprecated_function, MFAs0} <- lists:flatten([Args]),
MFA <- lists:flatten([MFAs0])],
- Nowarn = [MFA || {MFA,_L} <- MFAsL],
+ Nowarn = [MFA ||
+ {nowarn_deprecated_function, MFAs0} <- Opts,
+ MFA <- lists:flatten([MFAs0])],
ML = [{M,L} || {{M,_F,_A},L} <- MFAsL, is_atom(M)],
St1 = foldl(fun ({M,L}, St2) ->
check_module_name(M, L, St2)
diff --git a/lib/stdlib/src/erl_parse.yrl b/lib/stdlib/src/erl_parse.yrl
index 9602f0bcd9..4ad94f2507 100644
--- a/lib/stdlib/src/erl_parse.yrl
+++ b/lib/stdlib/src/erl_parse.yrl
@@ -841,7 +841,7 @@ Erlang code.
-type af_record_field(T) :: {'record_field', anno(), af_field_name(), T}.
-type af_map_pattern() ::
- {'map', anno(), [af_assoc_exact(abstract_expr)]}.
+ {'map', anno(), [af_assoc_exact(abstract_expr())]}.
-type abstract_type() :: af_annotated_type()
| af_atom()
@@ -872,7 +872,7 @@ Erlang code.
-type af_fun_type() :: {'type', anno(), 'fun', []}
| {'type', anno(), 'fun', [{'type', anno(), 'any'} |
abstract_type()]}
- | {'type', anno(), 'fun', af_function_type()}.
+ | af_function_type().
-type af_integer_range_type() ::
{'type', anno(), 'range', [af_singleton_integer_type()]}.
@@ -924,10 +924,11 @@ Erlang code.
-type af_function_constraint() :: [af_constraint()].
-type af_constraint() :: {'type', anno(), 'constraint',
- af_lit_atom('is_subtype'),
- [af_type_variable() | abstract_type()]}. % [V, T]
+ [af_lit_atom('is_subtype') |
+ [af_type_variable() | abstract_type()]]}. % [IsSubtype, [V, T]]
-type af_singleton_integer_type() :: af_integer()
+ | af_character()
| af_unary_op(af_singleton_integer_type())
| af_binary_op(af_singleton_integer_type()).
diff --git a/lib/stdlib/src/erl_posix_msg.erl b/lib/stdlib/src/erl_posix_msg.erl
index 8959fea498..b9ed4a3a9d 100644
--- a/lib/stdlib/src/erl_posix_msg.erl
+++ b/lib/stdlib/src/erl_posix_msg.erl
@@ -81,9 +81,9 @@ message_1(el2hlt) -> <<"level 2 halted">>;
message_1(el2nsync) -> <<"level 2 not synchronized">>;
message_1(el3hlt) -> <<"level 3 halted">>;
message_1(el3rst) -> <<"level 3 reset">>;
-message_1(elibacc) -> <<"can not access a needed shared library">>;
+message_1(elibacc) -> <<"cannot access a needed shared library">>;
message_1(elibbad) -> <<"accessing a corrupted shared library">>;
-message_1(elibexec) -> <<"can not exec a shared library directly">>;
+message_1(elibexec) -> <<"cannot exec a shared library directly">>;
message_1(elibmax) ->
<<"attempting to link in more shared libraries than system limit">>;
message_1(elibscn) -> <<".lib section in a.out corrupted">>;
diff --git a/lib/stdlib/src/erl_pp.erl b/lib/stdlib/src/erl_pp.erl
index dd302a2880..ada3ff5de3 100644
--- a/lib/stdlib/src/erl_pp.erl
+++ b/lib/stdlib/src/erl_pp.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -697,6 +697,8 @@ fun_info(Extra) ->
%% BITS:
+bit_grp([], _Opts) ->
+ leaf("<<>>");
bit_grp(Fs, Opts) ->
append([['<<'], [bit_elems(Fs, Opts)], ['>>']]).
diff --git a/lib/stdlib/src/gen_fsm.erl b/lib/stdlib/src/gen_fsm.erl
index caaaf8fa2e..1e18710738 100644
--- a/lib/stdlib/src/gen_fsm.erl
+++ b/lib/stdlib/src/gen_fsm.erl
@@ -411,12 +411,13 @@ decode_msg(Msg,Parent, Name, StateName, StateData, Mod, Time, HibernateAfterTime
sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug,
[Name, StateName, StateData, Mod, Time, HibernateAfterTimeout], Hib);
{'EXIT', Parent, Reason} ->
- terminate(Reason, Name, Msg, Mod, StateName, StateData, Debug);
+ terminate(
+ Reason, Name, undefined, Msg, Mod, StateName, StateData, Debug);
_Msg when Debug =:= [] ->
handle_msg(Msg, Parent, Name, StateName, StateData, Mod, Time, HibernateAfterTimeout);
_Msg ->
Debug1 = sys:handle_debug(Debug, fun print_event/3,
- {Name, StateName}, {in, Msg}),
+ Name, {in, Msg, StateName}),
handle_msg(Msg, Parent, Name, StateName, StateData,
Mod, Time, HibernateAfterTimeout, Debug1)
end.
@@ -431,7 +432,7 @@ system_continue(Parent, Debug, [Name, StateName, StateData, Mod, Time, Hibernate
system_terminate(Reason, _Parent, Debug,
[Name, StateName, StateData, Mod, _Time, _HibernateAfterTimeout]) ->
- terminate(Reason, Name, [], Mod, StateName, StateData, Debug).
+ terminate(Reason, Name, undefined, [], Mod, StateName, StateData, Debug).
system_code_change([Name, StateName, StateData, Mod, Time, HibernateAfterTimeout],
_Module, OldVsn, Extra) ->
@@ -452,7 +453,7 @@ system_replace_state(StateFun, [Name, StateName, StateData, Mod, Time, Hibernate
%% Format debug messages. Print them as the call-back module sees
%% them, not as the real erlang messages. Use trace for that.
%%-----------------------------------------------------------------
-print_event(Dev, {in, Msg}, {Name, StateName}) ->
+print_event(Dev, {in, Msg, StateName}, Name) ->
case Msg of
{'$gen_event', Event} ->
io:format(Dev, "*DBG* ~tp got event ~tp in state ~tw~n",
@@ -461,6 +462,16 @@ print_event(Dev, {in, Msg}, {Name, StateName}) ->
io:format(Dev,
"*DBG* ~tp got all_state_event ~tp in state ~tw~n",
[Name, Event, StateName]);
+ {'$gen_sync_event', {From,_Tag}, Event} ->
+ io:format(Dev,
+ "*DBG* ~tp got sync_event ~tp "
+ "from ~tw in state ~tw~n",
+ [Name, Event, From, StateName]);
+ {'$gen_sync_all_state_event', {From,_Tag}, Event} ->
+ io:format(Dev,
+ "*DBG* ~tp got sync_all_state_event ~tp "
+ "from ~tw in state ~tw~n",
+ [Name, Event, From, StateName]);
{timeout, Ref, {'$gen_timer', Message}} ->
io:format(Dev,
"*DBG* ~tp got timer ~tp in state ~tw~n",
@@ -473,11 +484,11 @@ print_event(Dev, {in, Msg}, {Name, StateName}) ->
io:format(Dev, "*DBG* ~tp got ~tp in state ~tw~n",
[Name, Msg, StateName])
end;
-print_event(Dev, {out, Msg, To, StateName}, Name) ->
+print_event(Dev, {out, Msg, {To,_Tag}, StateName}, Name) ->
io:format(Dev, "*DBG* ~tp sent ~tp to ~tw~n"
" and switched to state ~tw~n",
[Name, Msg, To, StateName]);
-print_event(Dev, return, {Name, StateName}) ->
+print_event(Dev, {noreply, StateName}, Name) ->
io:format(Dev, "*DBG* ~tp switched to state ~tw~n",
[Name, StateName]).
@@ -495,9 +506,9 @@ handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time, HibernateAfterTi
reply(From, Reply),
loop(Parent, Name, NStateName, NStateData, Mod, Time1, HibernateAfterTimeout, []);
{stop, Reason, NStateData} ->
- terminate(Reason, Name, Msg, Mod, StateName, NStateData, []);
+ terminate(Reason, Name, From, Msg, Mod, StateName, NStateData, []);
{stop, Reason, Reply, NStateData} when From =/= undefined ->
- {'EXIT', R} = (catch terminate(Reason, Name, Msg, Mod,
+ {'EXIT', R} = (catch terminate(Reason, Name, From, Msg, Mod,
StateName, NStateData, [])),
reply(From, Reply),
exit(R);
@@ -510,10 +521,10 @@ handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time, HibernateAfterTi
error_logger=>#{tag=>warning_msg}}),
loop(Parent, Name, StateName, StateData, Mod, infinity, HibernateAfterTimeout, []);
{'EXIT', What} ->
- terminate(What, Name, Msg, Mod, StateName, StateData, []);
+ terminate(What, Name, From, Msg, Mod, StateName, StateData, []);
Reply ->
terminate({bad_return_value, Reply},
- Name, Msg, Mod, StateName, StateData, [])
+ Name, From, Msg, Mod, StateName, StateData, [])
end.
handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time, HibernateAfterTimeout, Debug) ->
@@ -521,11 +532,11 @@ handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time, HibernateAfterTi
case catch dispatch(Msg, Mod, StateName, StateData) of
{next_state, NStateName, NStateData} ->
Debug1 = sys:handle_debug(Debug, fun print_event/3,
- {Name, NStateName}, return),
+ Name, {noreply, NStateName}),
loop(Parent, Name, NStateName, NStateData, Mod, infinity, HibernateAfterTimeout, Debug1);
{next_state, NStateName, NStateData, Time1} ->
Debug1 = sys:handle_debug(Debug, fun print_event/3,
- {Name, NStateName}, return),
+ Name, {noreply, NStateName}),
loop(Parent, Name, NStateName, NStateData, Mod, Time1, HibernateAfterTimeout, Debug1);
{reply, Reply, NStateName, NStateData} when From =/= undefined ->
Debug1 = reply(Name, From, Reply, Debug, NStateName),
@@ -534,17 +545,18 @@ handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time, HibernateAfterTi
Debug1 = reply(Name, From, Reply, Debug, NStateName),
loop(Parent, Name, NStateName, NStateData, Mod, Time1, HibernateAfterTimeout, Debug1);
{stop, Reason, NStateData} ->
- terminate(Reason, Name, Msg, Mod, StateName, NStateData, Debug);
+ terminate(
+ Reason, Name, From, Msg, Mod, StateName, NStateData, Debug);
{stop, Reason, Reply, NStateData} when From =/= undefined ->
- {'EXIT', R} = (catch terminate(Reason, Name, Msg, Mod,
+ {'EXIT', R} = (catch terminate(Reason, Name, From, Msg, Mod,
StateName, NStateData, Debug)),
_ = reply(Name, From, Reply, Debug, StateName),
exit(R);
{'EXIT', What} ->
- terminate(What, Name, Msg, Mod, StateName, StateData, Debug);
+ terminate(What, Name, From, Msg, Mod, StateName, StateData, Debug);
Reply ->
terminate({bad_return_value, Reply},
- Name, Msg, Mod, StateName, StateData, Debug)
+ Name, From, Msg, Mod, StateName, StateData, Debug)
end.
dispatch({'$gen_event', Event}, Mod, StateName, StateData) ->
@@ -571,24 +583,25 @@ from(_) -> undefined.
reply({To, Tag}, Reply) ->
catch To ! {Tag, Reply}.
-reply(Name, {To, Tag}, Reply, Debug, StateName) ->
- reply({To, Tag}, Reply),
+reply(Name, From, Reply, Debug, StateName) ->
+ reply(From, Reply),
sys:handle_debug(Debug, fun print_event/3, Name,
- {out, Reply, To, StateName}).
+ {out, Reply, From, StateName}).
%%% ---------------------------------------------------
%%% Terminate the server.
%%% ---------------------------------------------------
--spec terminate(term(), _, _, atom(), _, _, _) -> no_return().
+-spec terminate(term(), _, _, _, atom(), _, _, _) -> no_return().
-terminate(Reason, Name, Msg, Mod, StateName, StateData, Debug) ->
+terminate(Reason, Name, From, Msg, Mod, StateName, StateData, Debug) ->
case erlang:function_exported(Mod, terminate, 3) of
true ->
case catch Mod:terminate(Reason, StateName, StateData) of
{'EXIT', R} ->
FmtStateData = format_status(terminate, Mod, get(), StateData),
- error_info(R, Name, Msg, StateName, FmtStateData, Debug),
+ error_info(
+ R, Name, From, Msg, StateName, FmtStateData, Debug),
exit(R);
_ ->
ok
@@ -605,29 +618,51 @@ terminate(Reason, Name, Msg, Mod, StateName, StateData, Debug) ->
exit(Shutdown);
_ ->
FmtStateData1 = format_status(terminate, Mod, get(), StateData),
- error_info(Reason,Name,Msg,StateName,FmtStateData1,Debug),
+ error_info(
+ Reason, Name, From, Msg, StateName, FmtStateData1, Debug),
exit(Reason)
end.
-error_info(Reason, Name, Msg, StateName, StateData, Debug) ->
+error_info(Reason, Name, From, Msg, StateName, StateData, Debug) ->
+ Log = sys:get_log(Debug),
?LOG_ERROR(#{label=>{gen_fsm,terminate},
name=>Name,
last_message=>Msg,
state_name=>StateName,
state_data=>StateData,
- reason=>Reason},
+ log=>Log,
+ reason=>Reason,
+ client_info=>client_stacktrace(From)},
#{domain=>[otp],
report_cb=>fun gen_fsm:format_log/1,
error_logger=>#{tag=>error}}),
- sys:print_log(Debug),
ok.
+client_stacktrace(undefined) ->
+ undefined;
+client_stacktrace({Pid,_Tag}) ->
+ client_stacktrace(Pid);
+client_stacktrace(Pid) when is_pid(Pid), node(Pid) =:= node() ->
+ case process_info(Pid, [current_stacktrace, registered_name]) of
+ undefined ->
+ {Pid,dead};
+ [{current_stacktrace, Stacktrace}, {registered_name, []}] ->
+ {Pid,{Pid,Stacktrace}};
+ [{current_stacktrace, Stacktrace}, {registered_name, Name}] ->
+ {Pid,{Name,Stacktrace}}
+ end;
+client_stacktrace(Pid) when is_pid(Pid) ->
+ {Pid,remote}.
+
+
format_log(#{label:={gen_fsm,terminate},
name:=Name,
last_message:=Msg,
state_name:=StateName,
state_data:=StateData,
- reason:=Reason}) ->
+ log:=Log,
+ reason:=Reason,
+ client_info:=ClientInfo}) ->
Reason1 =
case Reason of
{undef,[{M,F,A,L}|MFAs]} ->
@@ -645,27 +680,39 @@ format_log(#{label:={gen_fsm,terminate},
_ ->
Reason
end,
+ {ClientFmt,ClientArgs} = format_client_log(ClientInfo),
{"** State machine ~tp terminating \n" ++
get_msg_str(Msg) ++
"** When State == ~tp~n"
"** Data == ~tp~n"
- "** Reason for termination = ~n** ~tp~n",
- [Name, get_msg(Msg), StateName, StateData, Reason1]};
+ "** Reason for termination ==~n** ~tp~n" ++
+ case Log of
+ [] -> [];
+ _ -> "** Log ==~n** ~tp~n"
+ end ++ ClientFmt,
+ [Name|error_logger:limit_term(get_msg(Msg))] ++
+ [StateName,
+ error_logger:limit_term(StateData),
+ error_logger:limit_term(Reason1) |
+ case Log of
+ [] -> [];
+ _ -> [[error_logger:limit_term(D) || D <- Log]]
+ end] ++ ClientArgs};
format_log(#{label:={gen_fsm,no_handle_info},
module:=Mod,
message:=Msg}) ->
{"** Undefined handle_info in ~p~n"
"** Unhandled message: ~tp~n",
- [Mod, Msg]}.
+ [Mod, error_logger:limit_term(Msg)]}.
get_msg_str({'$gen_event', _Event}) ->
"** Last event in was ~tp~n";
-get_msg_str({'$gen_sync_event', _Event}) ->
- "** Last sync event in was ~tp~n";
+get_msg_str({'$gen_sync_event', _From, _Event}) ->
+ "** Last sync event in was ~tp from ~tw~n";
get_msg_str({'$gen_all_state_event', _Event}) ->
"** Last event in was ~tp (for all states)~n";
-get_msg_str({'$gen_sync_all_state_event', _Event}) ->
- "** Last sync event in was ~tp (for all states)~n";
+get_msg_str({'$gen_sync_all_state_event', _From, _Event}) ->
+ "** Last sync event in was ~tp (for all states) from ~tw~n";
get_msg_str({timeout, _Ref, {'$gen_timer', _Msg}}) ->
"** Last timer event in was ~tp~n";
get_msg_str({timeout, _Ref, {'$gen_event', _Msg}}) ->
@@ -673,13 +720,24 @@ get_msg_str({timeout, _Ref, {'$gen_event', _Msg}}) ->
get_msg_str(_Msg) ->
"** Last message in was ~tp~n".
-get_msg({'$gen_event', Event}) -> Event;
-get_msg({'$gen_sync_event', Event}) -> Event;
-get_msg({'$gen_all_state_event', Event}) -> Event;
-get_msg({'$gen_sync_all_state_event', Event}) -> Event;
-get_msg({timeout, Ref, {'$gen_timer', Msg}}) -> {timeout, Ref, Msg};
-get_msg({timeout, _Ref, {'$gen_event', Event}}) -> Event;
-get_msg(Msg) -> Msg.
+get_msg({'$gen_event', Event}) -> [Event];
+get_msg({'$gen_sync_event', {From,_Tag}, Event}) -> [Event,From];
+get_msg({'$gen_all_state_event', Event}) -> [Event];
+get_msg({'$gen_sync_all_state_event', {From,_Tag}, Event}) -> [Event,From];
+get_msg({timeout, Ref, {'$gen_timer', Msg}}) -> [{timeout, Ref, Msg}];
+get_msg({timeout, _Ref, {'$gen_event', Event}}) -> [Event];
+get_msg(Msg) -> [Msg].
+
+format_client_log(undefined) ->
+ {"", []};
+format_client_log({From,dead}) ->
+ {"** Client ~p is dead~n", [From]};
+format_client_log({From,remote}) ->
+ {"** Client ~p is remote on node ~p~n", [From, node(From)]};
+format_client_log({_From,{Name,Stacktrace}}) ->
+ {"** Client ~tp stacktrace~n"
+ "** ~tp~n",
+ [Name, error_logger:limit_term(Stacktrace)]}.
%%-----------------------------------------------------------------
%% Status information
@@ -689,18 +747,18 @@ format_status(Opt, StatusData) ->
StatusData,
Header = gen:format_status_header("Status for state machine",
Name),
- Log = sys:get_debug(log, Debug, []),
- Specfic = format_status(Opt, Mod, PDict, StateData),
- Specfic = case format_status(Opt, Mod, PDict, StateData) of
- S when is_list(S) -> S;
- S -> [S]
- end,
+ Log = sys:get_log(Debug),
+ Specific =
+ case format_status(Opt, Mod, PDict, StateData) of
+ S when is_list(S) -> S;
+ S -> [S]
+ end,
[{header, Header},
{data, [{"Status", SysState},
{"Parent", Parent},
{"Logged events", Log},
{"StateName", StateName}]} |
- Specfic].
+ Specific].
format_status(Opt, Mod, PDict, State) ->
DefStatus = case Opt of
diff --git a/lib/stdlib/src/gen_server.erl b/lib/stdlib/src/gen_server.erl
index 44e9231ebe..c7b6406f54 100644
--- a/lib/stdlib/src/gen_server.erl
+++ b/lib/stdlib/src/gen_server.erl
@@ -773,10 +773,10 @@ handle_common_reply(Reply, Parent, Name, From, Msg, Mod, HibernateAfterTimeout,
terminate({bad_return_value, BadReply}, ?STACKTRACE(), Name, From, Msg, Mod, State, Debug)
end.
-reply(Name, {To, Tag}, Reply, State, Debug) ->
- reply({To, Tag}, Reply),
+reply(Name, From, Reply, State, Debug) ->
+ reply(From, Reply),
sys:handle_debug(Debug, fun print_event/3, Name,
- {out, Reply, To, State} ).
+ {out, Reply, From, State} ).
%%-----------------------------------------------------------------
@@ -810,7 +810,7 @@ system_replace_state(StateFun, [Name, State, Mod, Time, HibernateAfterTimeout])
print_event(Dev, {in, Msg}, Name) ->
case Msg of
{'$gen_call', {From, _Tag}, Call} ->
- io:format(Dev, "*DBG* ~tp got call ~tp from ~w~n",
+ io:format(Dev, "*DBG* ~tp got call ~tp from ~tw~n",
[Name, Call, From]);
{'$gen_cast', Cast} ->
io:format(Dev, "*DBG* ~tp got cast ~tp~n",
@@ -818,8 +818,8 @@ print_event(Dev, {in, Msg}, Name) ->
_ ->
io:format(Dev, "*DBG* ~tp got ~tp~n", [Name, Msg])
end;
-print_event(Dev, {out, Msg, To, State}, Name) ->
- io:format(Dev, "*DBG* ~tp sent ~tp to ~w, new state ~tp~n",
+print_event(Dev, {out, Msg, {To,_Tag}, State}, Name) ->
+ io:format(Dev, "*DBG* ~tp sent ~tp to ~tw, new state ~tp~n",
[Name, Msg, To, State]);
print_event(Dev, {noreply, State}, Name) ->
io:format(Dev, "*DBG* ~tp new state ~tp~n", [Name, State]);
@@ -885,16 +885,17 @@ error_info(_Reason, application_controller, _From, _Msg, _Mod, _State, _Debug) -
%% of it instead
ok;
error_info(Reason, Name, From, Msg, Mod, State, Debug) ->
+ Log = sys:get_log(Debug),
?LOG_ERROR(#{label=>{gen_server,terminate},
name=>Name,
last_message=>Msg,
state=>format_status(terminate, Mod, get(), State),
+ log=>format_log_state(Mod, Log),
reason=>Reason,
client_info=>client_stacktrace(From)},
#{domain=>[otp],
report_cb=>fun gen_server:format_log/1,
error_logger=>#{tag=>error}}),
- sys:print_log(Debug),
ok.
client_stacktrace(undefined) ->
@@ -917,6 +918,7 @@ format_log(#{label:={gen_server,terminate},
name:=Name,
last_message:=Msg,
state:=State,
+ log:=Log,
reason:=Reason,
client_info:=Client}) ->
Reason1 =
@@ -934,20 +936,30 @@ format_log(#{label:={gen_server,terminate},
end
end;
_ ->
- error_logger:limit_term(Reason)
+ Reason
end,
{ClientFmt,ClientArgs} = format_client_log(Client),
+ [LimitedMsg,LimitedState,LimitedReason|LimitedLog] =
+ [error_logger:limit_term(D) || D <- [Msg,State,Reason1|Log]],
{"** Generic server ~tp terminating \n"
"** Last message in was ~tp~n"
"** When Server state == ~tp~n"
- "** Reason for termination == ~n** ~tp~n" ++ ClientFmt,
- [Name, Msg, error_logger:limit_term(State), Reason1] ++ ClientArgs};
+ "** Reason for termination ==~n** ~tp~n" ++
+ case LimitedLog of
+ [] -> [];
+ _ -> "** Log ==~n** ~tp~n"
+ end ++ ClientFmt,
+ [Name, LimitedMsg, LimitedState, LimitedReason] ++
+ case LimitedLog of
+ [] -> [];
+ _ -> [LimitedLog]
+ end ++ ClientArgs};
format_log(#{label:={gen_server,no_handle_info},
module:=Mod,
message:=Msg}) ->
{"** Undefined handle_info in ~p~n"
"** Unhandled message: ~tp~n",
- [Mod, Msg]}.
+ [Mod, error_logger:limit_term(Msg)]}.
format_client_log(undefined) ->
{"", []};
@@ -958,7 +970,7 @@ format_client_log({From,remote}) ->
format_client_log({_From,{Name,Stacktrace}}) ->
{"** Client ~tp stacktrace~n"
"** ~tp~n",
- [Name, Stacktrace]}.
+ [Name, error_logger:limit_term(Stacktrace)]}.
%%-----------------------------------------------------------------
%% Status information
@@ -966,16 +978,25 @@ format_client_log({_From,{Name,Stacktrace}}) ->
format_status(Opt, StatusData) ->
[PDict, SysState, Parent, Debug, [Name, State, Mod, _Time, _HibernateAfterTimeout]] = StatusData,
Header = gen:format_status_header("Status for generic server", Name),
- Log = sys:get_debug(log, Debug, []),
- Specfic = case format_status(Opt, Mod, PDict, State) of
+ Log = sys:get_log(Debug),
+ Specific = case format_status(Opt, Mod, PDict, State) of
S when is_list(S) -> S;
S -> [S]
end,
[{header, Header},
{data, [{"Status", SysState},
{"Parent", Parent},
- {"Logged events", Log}]} |
- Specfic].
+ {"Logged events", format_log_state(Mod, Log)}]} |
+ Specific].
+
+format_log_state(Mod, Log) ->
+ [case Event of
+ {out,Msg,From,State} ->
+ {out,Msg,From,format_status(terminate, Mod, get(), State)};
+ {noreply,State} ->
+ {noreply,format_status(terminate, Mod, get(), State)};
+ _ -> Event
+ end || Event <- Log].
format_status(Opt, Mod, PDict, State) ->
DefStatus = case Opt of
diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl
index faa43fbc1e..49911eac2c 100644
--- a/lib/stdlib/src/gen_statem.erl
+++ b/lib/stdlib/src/gen_statem.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2016-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2016-2019. All 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,14 @@
%% Type that is exported just to be documented
-export_type([transition_option/0]).
+%% Type exports for start_link & friends
+-export_type(
+ [server_name/0,
+ server_ref/0,
+ start_opt/0,
+ start_ret/0,
+ enter_loop_opt/0]).
+
%%%==========================================================================
%%% Interface functions.
%%%==========================================================================
@@ -330,6 +338,7 @@
%% Type validation functions
+%% - return true if the value is of the type, false otherwise
-compile(
{inline,
[callback_mode/1, state_enter/1,
@@ -373,77 +382,76 @@ timeout_event_type(Type) ->
-define(
+ relative_timeout(T),
+ ((is_integer(T) andalso 0 =< (T)) orelse (T) =:= infinity)).
+
+-define(
+ absolute_timeout(T),
+ (is_integer(T) orelse (T) =:= infinity)).
+
+-define(
STACKTRACE(),
element(2, erlang:process_info(self(), current_stacktrace))).
-define(not_sys_debug, []).
%%
%% This is a macro to only evaluate arguments if Debug =/= [].
-%% Debug is evaluated multiple times.
+%% Debug is evaluated 2 times.
-define(
- sys_debug(Debug, NameState, Entry),
+ sys_debug(Debug, Extra, SystemEvent),
case begin Debug end of
?not_sys_debug ->
begin Debug end;
_ ->
- sys_debug(begin Debug end, begin NameState end, begin Entry end)
+ sys_debug(
+ begin Debug end, begin Extra end, begin SystemEvent end)
end).
--record(state,
+-record(params,
{callback_mode = undefined :: callback_mode() | undefined,
state_enter = false :: boolean(),
+ parent :: pid(),
module :: atom(),
- name :: atom(),
- state :: term(),
- data :: term(),
+ name :: atom() | pid(),
+ hibernate_after = infinity :: timeout()
+ }).
+
+-record(state,
+ {state_data = {undefined,undefined} ::
+ {State :: term(),Data :: term()},
postponed = [] :: [{event_type(),term()}],
- %%
- timer_refs = #{} :: % timer ref => the timer's event type
- #{reference() => timeout_event_type()},
- timer_types = #{} :: % timer's event type => timer ref
- #{timeout_event_type() => reference()},
- cancel_timers = 0 :: non_neg_integer(),
- %% We add a timer to both timer_refs and timer_types
- %% when we start it. When we request an asynchronous
- %% timer cancel we remove it from timer_types. When
- %% the timer cancel message arrives we remove it from
- %% timer_refs.
- %%
- hibernate = false :: boolean(),
- hibernate_after = infinity :: timeout()}).
-
--record(trans_opts,
- {hibernate = false,
- postpone = false,
- timeouts_r = [],
- next_events_r = []}).
+ timers = {#{},#{}} ::
+ {%% TimerRef => TimeoutType
+ TimerRefs :: #{reference() => timeout_event_type()},
+ %% TimeoutType => TimerRef
+ TimeoutTypes :: #{timeout_event_type() => reference()}},
+ hibernate = false :: boolean()
+ }).
%%%==========================================================================
%%% API
-type server_name() ::
- {'global', GlobalName :: term()}
+ {'global', GlobalName :: term()}
| {'via', RegMod :: module(), Name :: term()}
| {'local', atom()}.
-type server_ref() ::
- pid()
+ pid()
| (LocalName :: atom())
| {Name :: atom(), Node :: atom()}
| {'global', GlobalName :: term()}
| {'via', RegMod :: module(), ViaName :: term()}.
--type debug_opt() ::
- {'debug',
- Dbgs ::
- ['trace' | 'log' | 'statistics' | 'debug'
- | {'logfile', string()}]}.
--type hibernate_after_opt() ::
- {'hibernate_after', HibernateAfterTimeout :: timeout()}.
-type start_opt() ::
- debug_opt()
- | {'timeout', Time :: timeout()}
- | hibernate_after_opt()
- | {'spawn_opt', [proc_lib:spawn_option()]}.
--type start_ret() :: {'ok', pid()} | 'ignore' | {'error', term()}.
+ {'timeout', Time :: timeout()}
+ | {'spawn_opt', [proc_lib:spawn_option()]}
+ | enter_loop_opt().
+-type start_ret() ::
+ {'ok', pid()}
+ | 'ignore'
+ | {'error', term()}.
+-type enter_loop_opt() ::
+ {'hibernate_after', HibernateAfterTimeout :: timeout()}
+ | {'debug', Dbgs :: [sys:debug_option()]}.
@@ -556,14 +564,14 @@ reply({To,Tag}, Reply) when is_pid(To) ->
%% started by proc_lib into a state machine using
%% the same arguments as you would have returned from init/1
-spec enter_loop(
- Module :: module(), Opts :: [debug_opt() | hibernate_after_opt()],
+ Module :: module(), Opts :: [enter_loop_opt()],
State :: state(), Data :: data()) ->
no_return().
enter_loop(Module, Opts, State, Data) ->
enter_loop(Module, Opts, State, Data, self()).
%%
-spec enter_loop(
- Module :: module(), Opts :: [debug_opt() | hibernate_after_opt()],
+ Module :: module(), Opts :: [enter_loop_opt()],
State :: state(), Data :: data(),
Server_or_Actions ::
server_name() | pid() | [action()]) ->
@@ -577,7 +585,7 @@ enter_loop(Module, Opts, State, Data, Server_or_Actions) ->
end.
%%
-spec enter_loop(
- Module :: module(), Opts :: [debug_opt() | hibernate_after_opt()],
+ Module :: module(), Opts :: [enter_loop_opt()],
State :: state(), Data :: data(),
Server :: server_name() | pid(),
Actions :: [action()] | action()) ->
@@ -585,7 +593,12 @@ enter_loop(Module, Opts, State, Data, Server_or_Actions) ->
enter_loop(Module, Opts, State, Data, Server, Actions) ->
is_atom(Module) orelse error({atom,Module}),
Parent = gen:get_parent(),
- enter(Module, Opts, State, Data, Server, Actions, Parent).
+ Name = gen:get_proc_name(Server),
+ Debug = gen:debug_options(Name, Opts),
+ HibernateAfterTimeout = gen:hibernate_after(Opts),
+ enter(
+ Parent, Debug, Module, Name, HibernateAfterTimeout,
+ State, Data, Actions).
%%---------------------------------------------------------------------------
%% API helpers
@@ -657,36 +670,29 @@ send(Proc, Msg) ->
ok.
%% Here the init_it/6 and enter_loop/5,6,7 functions converge
-enter(Module, Opts, State, Data, Server, Actions, Parent) ->
+enter(
+ Parent, Debug, Module, Name, HibernateAfterTimeout,
+ State, Data, Actions) ->
%% The values should already have been type checked
- Name = gen:get_proc_name(Server),
- Debug = gen:debug_options(Name, Opts),
- HibernateAfterTimeout = gen:hibernate_after(Opts),
- Events = [],
- Event = {internal,init_state},
+ Q = [{internal,init_state}],
%% We enforce {postpone,false} to ensure that
%% our fake Event gets discarded, thought it might get logged
- NewActions = listify(Actions) ++ [{postpone,false}],
- S =
- #state{
+ Actions_1 = listify(Actions) ++ [{postpone,false}],
+ P =
+ #params{
+ parent = Parent,
module = Module,
name = Name,
- state = State,
- data = Data,
hibernate_after = HibernateAfterTimeout},
- CallEnter = true,
- NewDebug = ?sys_debug(Debug, {Name,State}, {enter,Event,State}),
- case call_callback_mode(S) of
- #state{} = NewS ->
- loop_event_actions_list(
- Parent, NewDebug, NewS,
- Events, Event, State, Data, false,
- NewActions, CallEnter);
- [Class,Reason,Stacktrace] ->
- terminate(
- Class, Reason, Stacktrace, NewDebug,
- S, [Event|Events])
- end.
+ S = #state{state_data = {State,Data}},
+ Debug_1 = ?sys_debug(Debug, Name, {enter,State}),
+ loop_callback_mode(
+ P, Debug_1, S, Q, {State,Data},
+ %% Tunneling Actions through CallbackEvent here...
+ %% Special path to go to action handling, after first
+ %% finding out the callback mode. CallbackEvent is
+ %% a 2-tuple and Actions a list, which achieves this distinction.
+ Actions_1).
%%%==========================================================================
%%% gen callbacks
@@ -694,34 +700,46 @@ enter(Module, Opts, State, Data, Server, Actions, Parent) ->
init_it(Starter, self, ServerRef, Module, Args, Opts) ->
init_it(Starter, self(), ServerRef, Module, Args, Opts);
init_it(Starter, Parent, ServerRef, Module, Args, Opts) ->
+ Name = gen:get_proc_name(ServerRef),
+ Debug = gen:debug_options(Name, Opts),
+ HibernateAfterTimeout = gen:hibernate_after(Opts),
try Module:init(Args) of
Result ->
- init_result(Starter, Parent, ServerRef, Module, Result, Opts)
+ init_result(
+ Starter, Parent, ServerRef, Module, Result,
+ Name, Debug, HibernateAfterTimeout)
catch
Result ->
- init_result(Starter, Parent, ServerRef, Module, Result, Opts);
+ init_result(
+ Starter, Parent, ServerRef, Module, Result,
+ Name, Debug, HibernateAfterTimeout);
Class:Reason:Stacktrace ->
- Name = gen:get_proc_name(ServerRef),
gen:unregister_name(ServerRef),
proc_lib:init_ack(Starter, {error,Reason}),
error_info(
- Class, Reason, Stacktrace,
- #state{name = Name},
- []),
+ Class, Reason, Stacktrace, Debug,
+ #params{parent = Parent, name = Name, module = Module},
+ #state{}, []),
erlang:raise(Class, Reason, Stacktrace)
end.
%%---------------------------------------------------------------------------
%% gen callbacks helpers
-init_result(Starter, Parent, ServerRef, Module, Result, Opts) ->
+init_result(
+ Starter, Parent, ServerRef, Module, Result,
+ Name, Debug, HibernateAfterTimeout) ->
case Result of
{ok,State,Data} ->
proc_lib:init_ack(Starter, {ok,self()}),
- enter(Module, Opts, State, Data, ServerRef, [], Parent);
+ enter(
+ Parent, Debug, Module, Name, HibernateAfterTimeout,
+ State, Data, []);
{ok,State,Data,Actions} ->
proc_lib:init_ack(Starter, {ok,self()}),
- enter(Module, Opts, State, Data, ServerRef, Actions, Parent);
+ enter(
+ Parent, Debug, Module, Name, HibernateAfterTimeout,
+ State, Data, Actions);
{stop,Reason} ->
gen:unregister_name(ServerRef),
proc_lib:init_ack(Starter, {error,Reason}),
@@ -731,31 +749,34 @@ init_result(Starter, Parent, ServerRef, Module, Result, Opts) ->
proc_lib:init_ack(Starter, ignore),
exit(normal);
_ ->
- Name = gen:get_proc_name(ServerRef),
gen:unregister_name(ServerRef),
Error = {bad_return_from_init,Result},
proc_lib:init_ack(Starter, {error,Error}),
error_info(
- error, Error, ?STACKTRACE(),
- #state{name = Name},
- []),
+ error, Error, ?STACKTRACE(), Debug,
+ #params{parent = Parent, name = Name, module = Module},
+ #state{}, []),
exit(Error)
end.
%%%==========================================================================
%%% sys callbacks
+%%%
+%%% We use {P,S} as state (Misc) for the sys module,
+%%% wrap/unwrap it for the server loop* and update
+%%% P#params{parent = Parent}.
-system_continue(Parent, Debug, S) ->
- loop(Parent, Debug, S).
+system_continue(Parent, Debug, {P,S}) ->
+ loop(update_parent(P, Parent), Debug, S).
-system_terminate(Reason, _Parent, Debug, S) ->
- terminate(exit, Reason, ?STACKTRACE(), Debug, S, []).
+system_terminate(Reason, Parent, Debug, {P,S}) ->
+ terminate(
+ exit, Reason, ?STACKTRACE(),
+ update_parent(P, Parent), Debug, S, []).
system_code_change(
- #state{
- module = Module,
- state = State,
- data = Data} = S,
+ {#params{module = Module} = P,
+ #state{state_data = {State,Data}} = S},
_Mod, OldVsn, Extra) ->
case
try Module:code_change(OldVsn, State, Data, Extra)
@@ -765,44 +786,54 @@ system_code_change(
of
{ok,NewState,NewData} ->
{ok,
- S#state{
- callback_mode = undefined,
- state = NewState,
- data = NewData}};
+ {P#params{callback_mode = undefined},
+ S#state{state_data = {NewState,NewData}}}};
{ok,_} = Error ->
error({case_clause,Error});
Error ->
Error
end.
-system_get_state(#state{state = State, data = Data}) ->
- {ok,{State,Data}}.
+system_get_state({_P,#state{state_data = State_Data}}) ->
+ {ok,State_Data}.
system_replace_state(
- StateFun,
- #state{
- state = State,
- data = Data} = S) ->
- {NewState,NewData} = Result = StateFun({State,Data}),
- {ok,Result,S#state{state = NewState, data = NewData}}.
+ StateFun, {P,#state{state_data = State_Data} = S}) ->
+ %%
+ NewState_NewData = StateFun(State_Data),
+ {ok,NewState_NewData,{P,S#state{state_data = NewState_NewData}}}.
format_status(
Opt,
[PDict,SysState,Parent,Debug,
- #state{name = Name, postponed = P} = S]) ->
+ {#params{name = Name} = P,
+ #state{postponed = Postponed} = S}]) ->
Header = gen:format_status_header("Status for state machine", Name),
- Log = sys:get_debug(log, Debug, []),
+ Log = sys:get_log(Debug),
[{header,Header},
{data,
[{"Status",SysState},
{"Parent",Parent},
{"Logged Events",Log},
- {"Postponed",P}]} |
- case format_status(Opt, PDict, S) of
+ {"Postponed",Postponed}]} |
+ case format_status(Opt, PDict, update_parent(P, Parent), S) of
L when is_list(L) -> L;
T -> [T]
end].
+%% Update #params.parent only if it differs. This should not
+%% be possible today (OTP-22.0), but could happen for example
+%% if someone implements changing a server's parent
+%% in a new sys call.
+-compile({inline, update_parent/2}).
+update_parent(P, Parent) ->
+ case P of
+ #params{parent = Parent} ->
+ P;
+ #params{} ->
+ P#params{parent = Parent}
+ end.
+
%%---------------------------------------------------------------------------
%% Format debug messages. Print them as the call-back module sees
%% them, not as the real erlang messages. Use trace for that.
@@ -811,34 +842,46 @@ format_status(
sys_debug(Debug, NameState, Entry) ->
sys:handle_debug(Debug, fun print_event/3, NameState, Entry).
-print_event(Dev, {in,Event}, {Name,State}) ->
- io:format(
- Dev, "*DBG* ~tp receive ~ts in state ~tp~n",
- [Name,event_string(Event),State]);
-print_event(Dev, {out,Reply,{To,_Tag}}, {Name,State}) ->
- io:format(
- Dev, "*DBG* ~tp send ~tp to ~p from state ~tp~n",
- [Name,Reply,To,State]);
-print_event(Dev, {terminate,Reason}, {Name,State}) ->
- io:format(
- Dev, "*DBG* ~tp terminate ~tp in state ~tp~n",
- [Name,Reason,State]);
-print_event(Dev, {Tag,Event,NextState}, {Name,State}) ->
- StateString =
- case NextState of
- State ->
- io_lib:format("~tp", [State]);
- _ ->
- io_lib:format("~tp => ~tp", [State,NextState])
- end,
- io:format(
- Dev, "*DBG* ~tp ~tw ~ts in state ~ts~n",
- [Name,Tag,event_string(Event),StateString]).
+print_event(Dev, SystemEvent, Name) ->
+ case SystemEvent of
+ {in,Event,State} ->
+ io:format(
+ Dev, "*DBG* ~tp receive ~ts in state ~tp~n",
+ [Name,event_string(Event),State]);
+ {code_change,Event,State} ->
+ io:format(
+ Dev, "*DBG* ~tp receive ~ts after code change in state ~tp~n",
+ [Name,event_string(Event),State]);
+ {out,Reply,{To,_Tag}} ->
+ io:format(
+ Dev, "*DBG* ~tp send ~tp to ~tw~n",
+ [Name,Reply,To]);
+ {enter,State} ->
+ io:format(
+ Dev, "*DBG* ~tp enter in state ~tp~n",
+ [Name,State]);
+ {terminate,Reason,State} ->
+ io:format(
+ Dev, "*DBG* ~tp terminate ~tp in state ~tp~n",
+ [Name,Reason,State]);
+ {Tag,Event,State,NextState}
+ when Tag =:= postpone; Tag =:= consume ->
+ StateString =
+ case NextState of
+ State ->
+ io_lib:format("~tp", [State]);
+ _ ->
+ io_lib:format("~tp => ~tp", [State,NextState])
+ end,
+ io:format(
+ Dev, "*DBG* ~tp ~tw ~ts in state ~ts~n",
+ [Name,Tag,event_string(Event),StateString])
+ end.
event_string(Event) ->
case Event of
{{call,{Pid,_Tag}},Request} ->
- io_lib:format("call ~tp from ~w", [Request,Pid]);
+ io_lib:format("call ~tp from ~tw", [Request,Pid]);
{EventType,EventContent} ->
io_lib:format("~tw ~tp", [EventType,EventContent])
end.
@@ -846,942 +889,1169 @@ event_string(Event) ->
%%%==========================================================================
%%% Internal callbacks
-wakeup_from_hibernate(Parent, Debug, S) ->
+wakeup_from_hibernate(P, Debug, S) ->
%% It is a new message that woke us up so we have to receive it now
- loop_receive(Parent, Debug, S).
+ loop_receive(P, Debug, S).
%%%==========================================================================
-%%% State Machine engine implementation of proc_lib/gen server
+%%% State Machine engine implementation on proc_lib/gen
%% Server loop, consists of all loop* functions
%% and detours through sys:handle_system_message/7 and proc_lib:hibernate/3
+%%
+%% The loop tries to keep all temporary values in arguments
+%% and takes shortcuts for ?not_sys_debug, empty lists, etc.
+%% The engine state #state{} is picked apart during the loop,
+%% new values are kept in arguments, and a new #state{} is
+%% composed at the end of the loop. #params{} collect engine
+%% state fields that rarely changes.
+%%
+%% The loop is optimized a bit for staying in the loop, assuming that
+%% system events are rare. So a detour to sys requires re-packing
+%% of the engine state.
%% Entry point for system_continue/3
-loop(Parent, Debug, #state{hibernate = true, cancel_timers = 0} = S) ->
- loop_hibernate(Parent, Debug, S);
-loop(Parent, Debug, S) ->
- loop_receive(Parent, Debug, S).
+%%
+loop(P, Debug, #state{hibernate = true} = S) ->
+ loop_hibernate(P, Debug, S);
+loop(P, Debug, S) ->
+ loop_receive(P, Debug, S).
-loop_hibernate(Parent, Debug, S) ->
+%% Go to hibernation
+%%
+loop_hibernate(P, Debug, S) ->
%%
%% Does not return but restarts process at
%% wakeup_from_hibernate/3 that jumps to loop_receive/3
%%
- proc_lib:hibernate(
- ?MODULE, wakeup_from_hibernate, [Parent,Debug,S]),
+ proc_lib:hibernate(?MODULE, wakeup_from_hibernate, [P, Debug, S]),
error(
{should_not_have_arrived_here_but_instead_in,
- {wakeup_from_hibernate,3}}).
+ {?MODULE,wakeup_from_hibernate,3}}).
+
%% Entry point for wakeup_from_hibernate/3
+%%
+%% Receive a new process message
+%%
loop_receive(
- Parent, Debug, #state{hibernate_after = HibernateAfterTimeout} = S) ->
+ #params{hibernate_after = HibernateAfterTimeout} = P, Debug, S) ->
%%
receive
Msg ->
case Msg of
+ {'$gen_call',From,Request} ->
+ loop_receive_result(P, Debug, S, {{call,From},Request});
+ {'$gen_cast',Cast} ->
+ loop_receive_result(P, Debug, S, {cast,Cast});
+ %%
+ {timeout,TimerRef,TimeoutMsg} ->
+ {TimerRefs,TimeoutTypes} = S#state.timers,
+ case TimerRefs of
+ #{TimerRef := TimeoutType} ->
+ %% Our timer
+ Timers =
+ {maps:remove(TimerRef, TimerRefs),
+ maps:remove(TimeoutType, TimeoutTypes)},
+ S_1 = S#state{timers = Timers},
+ loop_receive_result(
+ P, Debug, S_1, {TimeoutType,TimeoutMsg});
+ #{} ->
+ loop_receive_result(P, Debug, S, {info,Msg})
+ end;
+ %%
{system,Pid,Req} ->
%% Does not return but tail recursively calls
%% system_continue/3 that jumps to loop/3
sys:handle_system_msg(
- Req, Pid, Parent, ?MODULE, Debug, S,
+ Req, Pid, P#params.parent, ?MODULE, Debug,
+ {P,S},
S#state.hibernate);
- {'EXIT',Parent,Reason} = EXIT ->
- %% EXIT is not a 2-tuple therefore
- %% not an event but this will stand out
- %% in the crash report...
- Q = [EXIT],
- terminate(exit, Reason, ?STACKTRACE(), Debug, S, Q);
- {timeout,TimerRef,TimerMsg} ->
- #state{
- timer_refs = TimerRefs,
- timer_types = TimerTypes} = S,
- case TimerRefs of
- #{TimerRef := TimerType} ->
- %% We know of this timer; is it a running
- %% timer or a timer being cancelled that
- %% managed to send a late timeout message?
- case TimerTypes of
- #{TimerType := TimerRef} ->
- %% The timer type maps back to this
- %% timer ref, so it was a running timer
- %% Unregister the triggered timeout
- NewTimerRefs =
- maps:remove(TimerRef, TimerRefs),
- NewTimerTypes =
- maps:remove(TimerType, TimerTypes),
- loop_receive_result(
- Parent, Debug,
- S#state{
- timer_refs = NewTimerRefs,
- timer_types = NewTimerTypes},
- TimerType, TimerMsg);
- _ ->
- %% This was a late timeout message
- %% from timer being cancelled, so
- %% ignore it and expect a cancel_timer
- %% msg shortly
- loop_receive(Parent, Debug, S)
- end;
- _ ->
- %% Not our timer; present it as an event
- loop_receive_result(Parent, Debug, S, info, Msg)
- end;
- {cancel_timer,TimerRef,_} ->
- #state{
- timer_refs = TimerRefs,
- cancel_timers = CancelTimers,
- hibernate = Hibernate} = S,
- case TimerRefs of
- #{TimerRef := _} ->
- %% We must have requested a cancel
- %% of this timer so it is already
- %% removed from TimerTypes
- NewTimerRefs =
- maps:remove(TimerRef, TimerRefs),
- NewCancelTimers = CancelTimers - 1,
- NewS =
- S#state{
- timer_refs = NewTimerRefs,
- cancel_timers = NewCancelTimers},
- if
- Hibernate =:= true, NewCancelTimers =:= 0 ->
- %% No more cancel_timer msgs to expect;
- %% we can hibernate
- loop_hibernate(Parent, Debug, NewS);
- NewCancelTimers >= 0 -> % Assert
- loop_receive(Parent, Debug, NewS)
- end;
- _ ->
- %% Not our cancel_timer msg;
- %% present it as an event
- loop_receive_result(Parent, Debug, S, info, Msg)
- end;
- _ ->
- %% External msg
- case Msg of
- {'$gen_call',From,Request} ->
- loop_receive_result(
- Parent, Debug, S, {call,From}, Request);
- {'$gen_cast',Cast} ->
- loop_receive_result(Parent, Debug, S, cast, Cast);
+ {'EXIT',Pid,Reason} ->
+ case P#params.parent of
+ Pid ->
+ terminate(
+ exit, Reason, ?STACKTRACE(), P, Debug, S, []);
_ ->
- loop_receive_result(Parent, Debug, S, info, Msg)
- end
+ loop_receive_result(P, Debug, S, {info,Msg})
+ end;
+ %%
+ _ ->
+ loop_receive_result(P, Debug, S, {info,Msg})
end
after
- HibernateAfterTimeout ->
- loop_hibernate(Parent, Debug, S)
+ HibernateAfterTimeout ->
+ loop_hibernate(P, Debug, S)
end.
-loop_receive_result(Parent, ?not_sys_debug, S, Type, Content) ->
+%% We have received an event
+%%
+loop_receive_result(P, ?not_sys_debug = Debug, S, Event) ->
%% Here is the queue of not yet handled events created
Events = [],
- loop_event(Parent, ?not_sys_debug, S, Events, Type, Content);
+ loop_event(P, Debug, S, Event, Events);
loop_receive_result(
- Parent, Debug, #state{name = Name, state = State} = S, Type, Content) ->
- NewDebug = sys_debug(Debug, {Name,State}, {in,{Type,Content}}),
+ #params{name = Name, callback_mode = CallbackMode} = P, Debug,
+ #state{state_data = {State,_Data}} = S, Event) ->
+ Debug_1 =
+ case CallbackMode of
+ undefined ->
+ sys_debug(Debug, Name, {code_change,Event,State});
+ _ ->
+ sys_debug(Debug, Name, {in,Event,State})
+ end,
%% Here is the queue of not yet handled events created
Events = [],
- loop_event(Parent, NewDebug, S, Events, Type, Content).
+ loop_event(P, Debug_1, S, Event, Events).
-%% Entry point for handling an event, received or enqueued
+%% Handle one event; received or enqueued
+%%
loop_event(
- Parent, Debug, #state{hibernate = Hibernate} = S,
- Events, Type, Content) ->
+ P, Debug, #state{hibernate = true} = S, Event, Events) ->
%%
- case Hibernate of
- true ->
- %%
- %% If (this old) Hibernate is true here it can only be
- %% because it was set from an event action
- %% and we did not go into hibernation since there were
- %% events in queue, so we do what the user
- %% might rely on i.e collect garbage which
- %% would have happened if we actually hibernated
- %% and immediately was awakened.
- %%
- _ = garbage_collect(),
- loop_event_state_function(
- Parent, Debug, S, Events, Type, Content);
- false ->
- loop_event_state_function(
- Parent, Debug, S, Events, Type, Content)
- end.
+ %% If (this old) Hibernate is true here it can only be
+ %% because it was set from an event action
+ %% and we did not go into hibernation since there were
+ %% events in queue, so we do what the user
+ %% might rely on i.e collect garbage which
+ %% would have happened if we actually hibernated
+ %% and immediately was awakened.
+ %%
+ _ = garbage_collect(),
+ loop_event_handler(P, Debug, S, Event, Events);
+loop_event(P, Debug, S, Event, Events) ->
+ loop_event_handler(P, Debug, S, Event, Events).
-%% Call the state function
-loop_event_state_function(
- Parent, Debug,
- #state{state = State, data = Data} = S,
- Events, Type, Content) ->
+%% Call the state function, eventually
+%%
+-compile({inline, [loop_event_handler/5]}).
+loop_event_handler(
+ P, Debug, #state{state_data = State_Data} = S, Event, Events) ->
%%
%% The field 'hibernate' in S is now invalid and will be
- %% restored when looping back to loop/3 or loop_event/6.
+ %% restored when looping back to loop/3 or loop_event/5.
%%
- Event = {Type,Content},
- TransOpts = false,
- case call_state_function(S, Type, Content, State, Data) of
- {Result, NewS} ->
- loop_event_result(
- Parent, Debug, NewS,
- Events, Event, State, Data, TransOpts, Result);
- [Class,Reason,Stacktrace] ->
- terminate(
- Class, Reason, Stacktrace, Debug, S, [Event|Events])
- end.
+ Q = [Event|Events],
+ loop_callback_mode(P, Debug, S, Q, State_Data, Event).
-%% Make a state enter call to the state function
-loop_event_state_enter(
- Parent, Debug, #state{state = PrevState} = S,
- Events, Event, NextState, NewData, TransOpts) ->
+%% Figure out the callback mode
+%%
+loop_callback_mode(
+ #params{callback_mode = undefined} = P, Debug, S,
+ Q, State_Data, CallbackEvent) ->
%%
- case call_state_function(S, enter, PrevState, NextState, NewData) of
- {Result, NewS} ->
- loop_event_result(
- Parent, Debug, NewS,
- Events, Event, NextState, NewData, TransOpts, Result);
- [Class,Reason,Stacktrace] ->
+ Module = P#params.module,
+ try Module:callback_mode() of
+ CallbackMode ->
+ loop_callback_mode_result(
+ P, Debug, S,
+ Q, State_Data, CallbackEvent,
+ CallbackMode, listify(CallbackMode), undefined, false)
+ catch
+ CallbackMode ->
+ loop_callback_mode_result(
+ P, Debug, S,
+ Q, State_Data, CallbackEvent,
+ CallbackMode, listify(CallbackMode), undefined, false);
+ Class:Reason:Stacktrace ->
terminate(
- Class, Reason, Stacktrace, Debug, S, [Event|Events])
+ Class, Reason, Stacktrace, P, Debug, S, Q)
+ end;
+loop_callback_mode(P, Debug, S, Q, State_Data, CallbackEvent) ->
+ loop_state_callback(P, Debug, S, Q, State_Data, CallbackEvent).
+
+%% Check the result of Module:callback_mode()
+%%
+loop_callback_mode_result(
+ P, Debug, S, Q, State_Data, CallbackEvent,
+ CallbackMode, [H|T], NewCallbackMode, NewStateEnter) ->
+ %%
+ case callback_mode(H) of
+ true ->
+ loop_callback_mode_result(
+ P, Debug, S, Q, State_Data, CallbackEvent,
+ CallbackMode, T, H, NewStateEnter);
+ false ->
+ case state_enter(H) of
+ true ->
+ loop_callback_mode_result(
+ P, Debug, S, Q, State_Data, CallbackEvent,
+ CallbackMode, T, NewCallbackMode, true);
+ false ->
+ terminate(
+ error,
+ {bad_return_from_callback_mode,CallbackMode},
+ ?STACKTRACE(),
+ P, Debug, S, Q)
+ end
+ end;
+loop_callback_mode_result(
+ P, Debug, S, Q, State_Data, CallbackEvent,
+ CallbackMode, [], NewCallbackMode, NewStateEnter) ->
+ %%
+ case NewCallbackMode of
+ undefined ->
+ terminate(
+ error,
+ {bad_return_from_callback_mode,CallbackMode},
+ ?STACKTRACE(),
+ P, Debug, S, Q);
+ _ ->
+ P_1 =
+ P#params{
+ callback_mode = NewCallbackMode,
+ state_enter = NewStateEnter},
+ loop_state_callback(
+ P_1, Debug, S, Q, State_Data, CallbackEvent)
end.
-%% Process the result from the state function.
-%% When TransOpts =:= false it was a state function call,
-%% otherwise it is an option tuple and it was a state enter call.
+
+%% Make a state enter call to the state function, we loop back here
+%% from further down if state enter calls are enabled
+%%
+loop_state_enter(
+ P, Debug, #state{state_data = {PrevState,_PrevData}} = S,
+ Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone) ->
+ %%
+ StateCall = false,
+ CallbackEvent = {enter,PrevState},
+ loop_state_callback(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ StateCall, CallbackEvent).
+
+%% Make a state call (not state enter call) to the state function
+%%
+loop_state_callback(P, Debug, S, Q, State_Data, CallbackEvent) ->
+ NextEventsR = [],
+ Hibernate = false,
+ TimeoutsR = [],
+ Postpone = false,
+ StateCall = true,
+ loop_state_callback(
+ P, Debug, S, Q, State_Data,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ StateCall, CallbackEvent).
+%%
+loop_state_callback(
+ #params{callback_mode = CallbackMode, module = Module} = P,
+ Debug, S, Q, {State,Data} = State_Data,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ StateCall, {Type,Content}) ->
+ try
+ case CallbackMode of
+ state_functions ->
+ Module:State(Type, Content, Data);
+ handle_event_function ->
+ Module:handle_event(Type, Content, State, Data)
+ end
+ of
+ Result ->
+ loop_state_callback_result(
+ P, Debug, S, Q, State_Data,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ StateCall, Result)
+ catch
+ Result ->
+ loop_state_callback_result(
+ P, Debug, S, Q, State_Data,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ StateCall, Result);
+ Class:Reason:Stacktrace ->
+ terminate(Class, Reason, Stacktrace, P, Debug, S, Q)
+ end;
+loop_state_callback(
+ P, Debug, S, Q, State_Data,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ StateCall, Actions) when is_list(Actions) ->
+ %% Tunneled actions from enter/8
+ CallEnter = true,
+ loop_actions_list(
+ P, Debug, S, Q, State_Data,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions).
+
+%% Process the result from the state function
%%
-loop_event_result(
- Parent, Debug, S,
- Events, Event, State, Data, TransOpts, Result) ->
+loop_state_callback_result(
+ P, Debug, S, Q, {State,_Data} = State_Data,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ StateCall, Result) ->
%%
case Result of
{next_state,State,NewData} ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, NewData, TransOpts,
- [], false);
+ loop_actions(
+ P, Debug, S, Q, {State,NewData},
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ false);
{next_state,NextState,NewData}
- when TransOpts =:= false ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, NextState, NewData, TransOpts,
- [], true);
+ when StateCall ->
+ loop_actions(
+ P, Debug, S, Q, {NextState,NewData},
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ true);
{next_state,_NextState,_NewData} ->
terminate(
error,
{bad_state_enter_return_from_state_function,Result},
- ?STACKTRACE(), Debug,
+ ?STACKTRACE(), P, Debug,
S#state{
- state = State, data = Data,
- hibernate = hibernate_in_trans_opts(TransOpts)},
- [Event|Events]);
+ state_data = State_Data,
+ hibernate = Hibernate},
+ Q);
{next_state,State,NewData,Actions} ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, NewData, TransOpts,
- Actions, false);
+ loop_actions(
+ P, Debug, S, Q, {State,NewData},
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ false, StateCall, Actions);
{next_state,NextState,NewData,Actions}
- when TransOpts =:= false ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, NextState, NewData, TransOpts,
- Actions, true);
+ when StateCall ->
+ loop_actions(
+ P, Debug, S, Q, {NextState,NewData},
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ true, StateCall, Actions);
{next_state,_NextState,_NewData,_Actions} ->
terminate(
error,
{bad_state_enter_return_from_state_function,Result},
- ?STACKTRACE(), Debug,
+ ?STACKTRACE(), P, Debug,
S#state{
- state = State, data = Data,
- hibernate = hibernate_in_trans_opts(TransOpts)},
- [Event|Events]);
+ state_data = State_Data,
+ hibernate = Hibernate},
+ Q);
%%
{keep_state,NewData} ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, NewData, TransOpts,
- [], false);
+ loop_actions(
+ P, Debug, S, Q, {State,NewData},
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ false);
{keep_state,NewData,Actions} ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, NewData, TransOpts,
- Actions, false);
+ loop_actions(
+ P, Debug, S, Q, {State,NewData},
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ false, StateCall, Actions);
%%
keep_state_and_data ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, Data, TransOpts,
- [], false);
+ loop_actions(
+ P, Debug, S, Q, State_Data,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ false);
{keep_state_and_data,Actions} ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, Data, TransOpts,
- Actions, false);
+ loop_actions(
+ P, Debug, S, Q, State_Data,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ false, StateCall, Actions);
%%
{repeat_state,NewData} ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, NewData, TransOpts,
- [], true);
+ loop_actions(
+ P, Debug, S, Q, {State,NewData},
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ true);
{repeat_state,NewData,Actions} ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, NewData, TransOpts,
- Actions, true);
+ loop_actions(
+ P, Debug, S, Q, {State,NewData},
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ true, StateCall, Actions);
%%
repeat_state_and_data ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, Data, TransOpts,
- [], true);
+ loop_actions(
+ P, Debug, S, Q, State_Data,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ true);
{repeat_state_and_data,Actions} ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, Data, TransOpts,
- Actions, true);
+ loop_actions(
+ P, Debug, S, Q, State_Data,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ true, StateCall, Actions);
%%
stop ->
terminate(
- exit, normal, ?STACKTRACE(), Debug,
+ exit, normal, ?STACKTRACE(), P, Debug,
S#state{
- state = State, data = Data,
- hibernate = hibernate_in_trans_opts(TransOpts)},
- [Event|Events]);
+ state_data = State_Data,
+ hibernate = Hibernate},
+ Q);
{stop,Reason} ->
terminate(
- exit, Reason, ?STACKTRACE(), Debug,
+ exit, Reason, ?STACKTRACE(), P, Debug,
S#state{
- state = State, data = Data,
- hibernate = hibernate_in_trans_opts(TransOpts)},
- [Event|Events]);
+ state_data = State_Data,
+ hibernate = Hibernate},
+ Q);
{stop,Reason,NewData} ->
terminate(
- exit, Reason, ?STACKTRACE(), Debug,
+ exit, Reason, ?STACKTRACE(), P, Debug,
S#state{
- state = State, data = NewData,
- hibernate = hibernate_in_trans_opts(TransOpts)},
- [Event|Events]);
+ state_data = {State,NewData},
+ hibernate = Hibernate},
+ Q);
%%
{stop_and_reply,Reason,Replies} ->
reply_then_terminate(
- exit, Reason, ?STACKTRACE(), Debug,
+ exit, Reason, ?STACKTRACE(), P, Debug,
S#state{
- state = State, data = Data,
- hibernate = hibernate_in_trans_opts(TransOpts)},
- [Event|Events], Replies);
+ state_data = State_Data,
+ hibernate = Hibernate},
+ Q, Replies);
{stop_and_reply,Reason,Replies,NewData} ->
reply_then_terminate(
- exit, Reason, ?STACKTRACE(), Debug,
+ exit, Reason, ?STACKTRACE(), P, Debug,
S#state{
- state = State, data = NewData,
- hibernate = hibernate_in_trans_opts(TransOpts)},
- [Event|Events], Replies);
+ state_data = {State,NewData},
+ hibernate = Hibernate},
+ Q, Replies);
%%
_ ->
terminate(
error,
{bad_return_from_state_function,Result},
- ?STACKTRACE(), Debug,
+ ?STACKTRACE(), P, Debug,
S#state{
- state = State, data = Data,
- hibernate = hibernate_in_trans_opts(TransOpts)},
- [Event|Events])
+ state_data = State_Data,
+ hibernate = Hibernate},
+ Q)
end.
%% Ensure that Actions are a list
-loop_event_actions(
- Parent, Debug, S,
- Events, Event, NextState, NewerData, TransOpts,
- Actions, CallEnter) ->
- loop_event_actions_list(
- Parent, Debug, S,
- Events, Event, NextState, NewerData, TransOpts,
- listify(Actions), CallEnter).
-
-%% Process actions from the state function
-loop_event_actions_list(
- Parent, Debug, #state{state_enter = StateEnter} = S,
- Events, Event, NextState, NewerData, TransOpts,
- Actions, CallEnter) ->
+%%
+loop_actions(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, _StateCall, []) ->
+ loop_actions(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter);
+loop_actions(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions) ->
%%
- case parse_actions(TransOpts, Debug, S, Actions) of
- {NewDebug,NewTransOpts}
- when StateEnter, CallEnter ->
- loop_event_state_enter(
- Parent, NewDebug, S,
- Events, Event, NextState, NewerData, NewTransOpts);
- {NewDebug,NewTransOpts} ->
- loop_event_done(
- Parent, NewDebug, S,
- Events, Event, NextState, NewerData, NewTransOpts);
- [Class,Reason,Stacktrace,NewDebug] ->
- terminate(
- Class, Reason, Stacktrace, NewDebug,
- S#state{
- state = NextState,
- data = NewerData,
- hibernate = hibernate_in_trans_opts(TransOpts)},
- [Event|Events])
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, listify(Actions)).
+%%
+%% Shortcut for no actions
+-compile({inline, [loop_actions/10]}).
+loop_actions(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter) ->
+ %%
+ %% Shortcut for no actions
+ case CallEnter andalso P#params.state_enter of
+ true ->
+ loop_state_enter(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone);
+ false ->
+ loop_state_transition(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone)
end.
--compile({inline, [hibernate_in_trans_opts/1]}).
-hibernate_in_trans_opts(false) ->
- (#trans_opts{})#trans_opts.hibernate;
-hibernate_in_trans_opts(#trans_opts{hibernate = Hibernate}) ->
- Hibernate.
-
-parse_actions(false, Debug, S, Actions) ->
- parse_actions(true, Debug, S, Actions, #trans_opts{});
-parse_actions(TransOpts, Debug, S, Actions) ->
- parse_actions(false, Debug, S, Actions, TransOpts).
+%% Process the returned actions
%%
-parse_actions(_StateCall, Debug, _S, [], TransOpts) ->
- {Debug,TransOpts};
-parse_actions(StateCall, Debug, S, [Action|Actions], TransOpts) ->
+loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, _StateCall, []) ->
+ %%
+ case P#params.state_enter of
+ true when CallEnter ->
+ loop_state_enter(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone);
+ _ ->
+ loop_state_transition(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone)
+ end;
+loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, [Action|Actions]) ->
+ %%
case Action of
%% Actual actions
{reply,From,Reply} ->
- parse_actions_reply(
- StateCall, Debug, S, Actions, TransOpts, From, Reply);
+ loop_actions_reply(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions,
+ From, Reply);
%%
%% Actions that set options
- {hibernate,NewHibernate} when is_boolean(NewHibernate) ->
- parse_actions(
- StateCall, Debug, S, Actions,
- TransOpts#trans_opts{hibernate = NewHibernate});
+ {hibernate,Hibernate_1} when is_boolean(Hibernate_1) ->
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate_1, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions);
hibernate ->
- parse_actions(
- StateCall, Debug, S, Actions,
- TransOpts#trans_opts{hibernate = true});
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, true, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions);
%%
- {postpone,NewPostpone} when not NewPostpone orelse StateCall ->
- parse_actions(
- StateCall, Debug, S, Actions,
- TransOpts#trans_opts{postpone = NewPostpone});
+ {postpone,Postpone_1} when not Postpone_1 orelse StateCall ->
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone_1,
+ CallEnter, StateCall, Actions);
postpone when StateCall ->
- parse_actions(
- StateCall, Debug, S, Actions,
- TransOpts#trans_opts{postpone = true});
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, true,
+ CallEnter, StateCall, Actions);
postpone ->
- [error,
- {bad_state_enter_action_from_state_function,Action},
- ?STACKTRACE(),
- Debug];
+ terminate(
+ error,
+ {bad_state_enter_action_from_state_function,Action},
+ ?STACKTRACE(), P, Debug,
+ S#state{
+ state_data = NextState_NewData,
+ hibernate = Hibernate},
+ Q);
%%
{next_event,Type,Content} ->
- parse_actions_next_event(
- StateCall, Debug, S, Actions, TransOpts, Type, Content);
+ loop_actions_next_event(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions, Type, Content);
%%
- _ ->
- parse_actions_timeout(
- StateCall, Debug, S, Actions, TransOpts, Action)
+ Timeout ->
+ loop_actions_timeout(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions, Timeout)
end.
-parse_actions_reply(
- StateCall, ?not_sys_debug, S, Actions, TransOpts,
+%% Process a reply action
+%%
+loop_actions_reply(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions,
From, Reply) ->
%%
case from(From) of
true ->
+ %% No need for a separate ?not_sys_debug clause here
+ %% since the external call to erlang:'!'/2 in reply/2
+ %% will cause swap out of all live registers anyway
reply(From, Reply),
- parse_actions(StateCall, ?not_sys_debug, S, Actions, TransOpts);
- false ->
- [error,
- {bad_action_from_state_function,{reply,From,Reply}},
- ?STACKTRACE(),
- ?not_sys_debug]
- end;
-parse_actions_reply(
- StateCall, Debug, #state{name = Name, state = State} = S,
- Actions, TransOpts, From, Reply) ->
- %%
- case from(From) of
- true ->
- reply(From, Reply),
- NewDebug = sys_debug(Debug, {Name,State}, {out,Reply,From}),
- parse_actions(StateCall, NewDebug, S, Actions, TransOpts);
+ Debug_1 = ?sys_debug(Debug, P#params.name, {out,Reply,From}),
+ loop_actions_list(
+ P, Debug_1, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions);
false ->
- [error,
- {bad_action_from_state_function,{reply,From,Reply}},
- ?STACKTRACE(),
- Debug]
+ terminate(
+ error,
+ {bad_action_from_state_function,{reply,From,Reply}},
+ ?STACKTRACE(), P, Debug,
+ S#state{
+ state_data = NextState_NewData,
+ hibernate = Hibernate},
+ Q)
end.
-parse_actions_next_event(
- StateCall, ?not_sys_debug, S,
- Actions, TransOpts, Type, Content) ->
- case event_type(Type) of
- true when StateCall ->
- NextEventsR = TransOpts#trans_opts.next_events_r,
- parse_actions(
- StateCall, ?not_sys_debug, S, Actions,
- TransOpts#trans_opts{
- next_events_r = [{Type,Content}|NextEventsR]});
- _ ->
- [error,
- {bad_state_enter_action_from_state_function,
- {next_event,Type,Content}},
- ?STACKTRACE(),
- ?not_sys_debug]
- end;
-parse_actions_next_event(
- StateCall, Debug, #state{name = Name, state = State} = S,
- Actions, TransOpts, Type, Content) ->
+%% Process a next_event action
+%%
+loop_actions_next_event(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions, Type, Content) ->
case event_type(Type) of
true when StateCall ->
- NewDebug = sys_debug(Debug, {Name,State}, {in,{Type,Content}}),
- NextEventsR = TransOpts#trans_opts.next_events_r,
- parse_actions(
- StateCall, NewDebug, S, Actions,
- TransOpts#trans_opts{
- next_events_r = [{Type,Content}|NextEventsR]});
+ NextEvent = {Type,Content},
+ case Debug of
+ ?not_sys_debug ->
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ [NextEvent|NextEventsR],
+ Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions);
+ _ ->
+ Name = P#params.name,
+ {State,_Data} = S#state.state_data,
+ Debug_1 =
+ sys_debug(Debug, Name, {in,{Type,Content},State}),
+ loop_actions_list(
+ P, Debug_1, S, Q, NextState_NewData,
+ [NextEvent|NextEventsR],
+ Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions)
+ end;
_ ->
- [error,
- {bad_state_enter_action_from_state_function,
- {next_event,Type,Content}},
- ?STACKTRACE(),
- Debug]
+ terminate(
+ error,
+ {if
+ StateCall ->
+ bad_action_from_state_function;
+ true ->
+ bad_state_enter_action_from_state_function
+ end,
+ {next_event,Type,Content}},
+ ?STACKTRACE(), P, Debug,
+ S#state{
+ state_data = NextState_NewData,
+ hibernate = Hibernate},
+ Q)
end.
-parse_actions_timeout(
- StateCall, Debug, S, Actions, TransOpts,
- {TimeoutType,Time,TimerMsg,TimerOpts} = AbsoluteTimeout) ->
+%% Process a timeout action, or also any unrecognized action
+%%
+loop_actions_timeout(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions,
+ {TimeoutType,Time,TimeoutMsg,TimeoutOpts} = Timeout) ->
%%
- case classify_timeout(TimeoutType, Time, listify(TimerOpts)) of
- absolute ->
- parse_actions_timeout_add(
- StateCall, Debug, S, Actions,
- TransOpts, AbsoluteTimeout);
- relative ->
- RelativeTimeout = {TimeoutType,Time,TimerMsg},
- parse_actions_timeout_add(
- StateCall, Debug, S, Actions,
- TransOpts, RelativeTimeout);
- badarg ->
- [error,
- {bad_action_from_state_function,AbsoluteTimeout},
- ?STACKTRACE(),
- Debug]
+ case timeout_event_type(TimeoutType) of
+ true ->
+ case listify(TimeoutOpts) of
+ %% Optimization cases
+ [] when ?relative_timeout(Time) ->
+ RelativeTimeout = {TimeoutType,Time,TimeoutMsg},
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate,
+ [RelativeTimeout|TimeoutsR], Postpone,
+ CallEnter, StateCall, Actions);
+ [{abs,true}] when ?absolute_timeout(Time) ->
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate,
+ [Timeout|TimeoutsR], Postpone,
+ CallEnter, StateCall, Actions);
+ [{abs,false}] when ?relative_timeout(Time) ->
+ RelativeTimeout = {TimeoutType,Time,TimeoutMsg},
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate,
+ [RelativeTimeout|TimeoutsR], Postpone,
+ CallEnter, StateCall, Actions);
+ %% Generic case
+ TimeoutOptsList ->
+ case parse_timeout_opts_abs(TimeoutOptsList) of
+ true when ?absolute_timeout(Time) ->
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate,
+ [Timeout|TimeoutsR], Postpone,
+ CallEnter, StateCall, Actions);
+ false when ?relative_timeout(Time) ->
+ RelativeTimeout =
+ {TimeoutType,Time,TimeoutMsg},
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate,
+ [RelativeTimeout|TimeoutsR], Postpone,
+ CallEnter, StateCall, Actions);
+ badarg ->
+ terminate(
+ error,
+ {bad_action_from_state_function,Timeout},
+ ?STACKTRACE(), P, Debug,
+ S#state{
+ state_data = NextState_NewData,
+ hibernate = Hibernate},
+ Q)
+ end
+ end;
+ false ->
+ terminate(
+ error,
+ {bad_action_from_state_function,Timeout},
+ ?STACKTRACE(), P, Debug,
+ S#state{
+ state_data = NextState_NewData,
+ hibernate = Hibernate},
+ Q)
end;
-parse_actions_timeout(
- StateCall, Debug, S, Actions, TransOpts,
- {TimeoutType,Time,_} = RelativeTimeout) ->
- case classify_timeout(TimeoutType, Time, []) of
- relative ->
- parse_actions_timeout_add(
- StateCall, Debug, S, Actions,
- TransOpts, RelativeTimeout);
- badarg ->
- [error,
- {bad_action_from_state_function,RelativeTimeout},
- ?STACKTRACE(),
- Debug]
+loop_actions_timeout(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions,
+ {TimeoutType,Time,_} = Timeout) ->
+ %%
+ case timeout_event_type(TimeoutType) of
+ true when ?relative_timeout(Time) ->
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate,
+ [Timeout|TimeoutsR], Postpone,
+ CallEnter, StateCall, Actions);
+ _ ->
+ terminate(
+ error,
+ {bad_action_from_state_function,Timeout},
+ ?STACKTRACE(), P, Debug,
+ S#state{
+ state_data = NextState_NewData,
+ hibernate = Hibernate},
+ Q)
end;
-parse_actions_timeout(
- StateCall, Debug, S, Actions, TransOpts,
- Time) ->
- case classify_timeout(timeout, Time, []) of
- relative ->
+loop_actions_timeout(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions, Time) ->
+ %%
+ if
+ ?relative_timeout(Time) ->
RelativeTimeout = {timeout,Time,Time},
- parse_actions_timeout_add(
- StateCall, Debug, S, Actions,
- TransOpts, RelativeTimeout);
- badarg ->
- [error,
- {bad_action_from_state_function,Time},
- ?STACKTRACE(),
- Debug]
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate,
+ [RelativeTimeout|TimeoutsR], Postpone,
+ CallEnter, StateCall, Actions);
+ true ->
+ terminate(
+ error,
+ {bad_action_from_state_function,Time},
+ ?STACKTRACE(), P, Debug,
+ S#state{
+ state_data = NextState_NewData,
+ hibernate = Hibernate},
+ Q)
end.
-parse_actions_timeout_add(
- StateCall, Debug, S, Actions,
- #trans_opts{timeouts_r = TimeoutsR} = TransOpts, Timeout) ->
- parse_actions(
- StateCall, Debug, S, Actions,
- TransOpts#trans_opts{timeouts_r = [Timeout|TimeoutsR]}).
-
%% Do the state transition
-loop_event_done(
- Parent, ?not_sys_debug,
- #state{postponed = P} = S,
- Events, Event, NextState, NewData,
- #trans_opts{
- postpone = Postpone, hibernate = Hibernate,
- timeouts_r = [], next_events_r = []}) ->
- %%
- %% Optimize the simple cases
- %% i.e no timer changes, no inserted events and no debug,
- %% by duplicate stripped down code
- %%
- %% Fast path
- %%
- case Postpone of
- true ->
- loop_event_done_fast(
- Parent, Hibernate,
- S,
- Events, [Event|P], NextState, NewData);
- false ->
- loop_event_done_fast(
- Parent, Hibernate,
- S,
- Events, P, NextState, NewData)
- end;
-loop_event_done(
- Parent, Debug_0,
- #state{
- state = State, postponed = P_0,
- timer_refs = TimerRefs_0, timer_types = TimerTypes_0,
- cancel_timers = CancelTimers_0} = S,
- Events_0, Event_0, NextState, NewData,
- #trans_opts{
- hibernate = Hibernate, timeouts_r = TimeoutsR,
- postpone = Postpone, next_events_r = NextEventsR}) ->
+%%
+loop_state_transition(
+ P, Debug, #state{state_data = {State,_Data}, postponed = Postponed} = S,
+ [Event|Events], {NextState,_NewData} = NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone) ->
%%
%% All options have been collected and next_events are buffered.
%% Do the actual state transition.
%%
- %% Full feature path
- %%
- [Debug_1|P_1] = % Move current event to postponed if Postpone
+ Postponed_1 = % Move current event to postponed if Postpone
case Postpone of
true ->
- [?sys_debug(
- Debug_0,
- {S#state.name,State},
- {postpone,Event_0,NextState}),
- Event_0|P_0];
+ [Event|Postponed];
false ->
- [?sys_debug(
- Debug_0,
- {S#state.name,State},
- {consume,Event_0,NextState})|P_0]
- end,
- {Events_2,P_2,Timers_2} =
- %% Move all postponed events to queue,
- %% cancel the event timer,
- %% and cancel the state timeout if the state changes
- if
- NextState =:= State ->
- {Events_0,P_1,
- cancel_timer_by_type(
- timeout, {TimerTypes_0,CancelTimers_0})};
- true ->
- {lists:reverse(P_1, Events_0),
- [],
- cancel_timer_by_type(
- state_timeout,
- cancel_timer_by_type(
- timeout, {TimerTypes_0,CancelTimers_0}))}
- %% The state timer is removed from TimerTypes
- %% but remains in TimerRefs until we get
- %% the cancel_timer msg
+ Postponed
end,
- {TimerRefs_3,{TimerTypes_3,CancelTimers_3},TimeoutEvents} =
- %% Stop and start timers
- parse_timers(TimerRefs_0, Timers_2, TimeoutsR),
- %% Place next events last in reversed queue
- Events_3R = lists:reverse(Events_2, NextEventsR),
- %% Enqueue immediate timeout events
- Events_4R = prepend_timeout_events(TimeoutEvents, Events_3R),
- loop_event_done(
- Parent, Debug_1,
- S#state{
- state = NextState,
- data = NewData,
- postponed = P_2,
- timer_refs = TimerRefs_3,
- timer_types = TimerTypes_3,
- cancel_timers = CancelTimers_3,
- hibernate = Hibernate},
- lists:reverse(Events_4R)).
-
-%% Fast path
-%%
-loop_event_done_fast(
- Parent, Hibernate,
- #state{
- state = NextState,
- timer_types = #{timeout := _} = TimerTypes,
- cancel_timers = CancelTimers} = S,
- Events, P, NextState, NewData) ->
- %%
- %% Same state, event timeout active
- %%
- loop_event_done_fast(
- Parent, Hibernate, S,
- Events, P, NextState, NewData,
- cancel_timer_by_type(
- timeout, {TimerTypes,CancelTimers}));
-loop_event_done_fast(
- Parent, Hibernate,
- #state{state = NextState} = S,
- Events, P, NextState, NewData) ->
- %%
- %% Same state
+ case Debug of
+ ?not_sys_debug ->
+ %% Optimization for no sys_debug
+ %% - avoid calling sys_debug/3
+ if
+ NextState =:= State ->
+ loop_keep_state(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed_1);
+ true ->
+ loop_state_change(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed_1)
+ end;
+ _ ->
+ %% With sys_debug
+ Name = P#params.name,
+ Debug_1 =
+ case Postpone of
+ true ->
+ sys_debug(
+ Debug, Name,
+ {postpone,Event,State,NextState});
+ false ->
+ sys_debug(
+ Debug, Name,
+ {consume,Event,State,NextState})
+ end,
+ if
+ NextState =:= State ->
+ loop_keep_state(
+ P, Debug_1, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed_1);
+ true ->
+ loop_state_change(
+ P, Debug_1, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed_1)
+ end
+ end.
+
+%% State transition to the same state
+%%
+loop_keep_state(
+ P, Debug, #state{timers = {TimerRefs,TimeoutTypes} = Timers} = S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed) ->
%%
- loop_event_done(
- Parent, ?not_sys_debug,
- S#state{
- data = NewData,
- postponed = P,
- hibernate = Hibernate},
- Events);
-loop_event_done_fast(
- Parent, Hibernate,
- #state{
- timer_types = #{timeout := _} = TimerTypes,
- cancel_timers = CancelTimers} = S,
- Events, P, NextState, NewData) ->
+ %% Cancel event timeout
%%
- %% State change, event timeout active
+ case TimeoutTypes of
+ %% Optimization
+ %% - only cancel timer when there is a timer to cancel
+ #{timeout := TimerRef} ->
+ %% Event timeout active
+ loop_next_events(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ cancel_timer_by_ref_and_type(
+ TimerRef, timeout, TimerRefs, TimeoutTypes));
+ _ ->
+ %% No event timeout active
+ loop_next_events(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers)
+ end.
+
+%% State transition to a different state
+%%
+loop_state_change(
+ P, Debug, S, Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed) ->
%%
- loop_event_done_fast(
- Parent, Hibernate, S,
- lists:reverse(P, Events), [], NextState, NewData,
- cancel_timer_by_type(
- state_timeout,
- cancel_timer_by_type(
- timeout, {TimerTypes,CancelTimers})));
-loop_event_done_fast(
- Parent, Hibernate,
- #state{
- timer_types = #{state_timeout := _} = TimerTypes,
- cancel_timers = CancelTimers} = S,
- Events, P, NextState, NewData) ->
+ %% Retry postponed events
%%
- %% State change, state timeout active
+ case Postponed of
+ [] ->
+ loop_state_change(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR);
+ [E1] ->
+ loop_state_change(
+ P, Debug, S,
+ [E1|Events], NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR);
+ [E2,E1] ->
+ loop_state_change(
+ P, Debug, S,
+ [E1,E2|Events], NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR);
+ _ ->
+ loop_state_change(
+ P, Debug, S,
+ lists:reverse(Postponed, Events), NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR)
+ end.
+%%
+loop_state_change(
+ P, Debug, #state{timers = {TimerRefs,TimeoutTypes} = Timers} = S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR) ->
%%
- loop_event_done_fast(
- Parent, Hibernate, S,
- lists:reverse(P, Events), [], NextState, NewData,
- cancel_timer_by_type(
- state_timeout,
- cancel_timer_by_type(
- timeout, {TimerTypes,CancelTimers})));
-loop_event_done_fast(
- Parent, Hibernate,
- #state{} = S,
- Events, P, NextState, NewData) ->
+ %% Cancel state and event timeout
%%
- %% State change, no timeout to automatically cancel
+ case TimeoutTypes of
+ %% Optimization
+ %% - only cancel timeout when there is an active timeout
+ %%
+ #{state_timeout := TimerRef} ->
+ %% State timeout active
+ %% - cancel event timeout too since it is faster than inspecting
+ loop_next_events(
+ P, Debug, S, Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, [],
+ cancel_timer_by_type(
+ timeout,
+ cancel_timer_by_ref_and_type(
+ TimerRef, state_timeout, TimerRefs, TimeoutTypes)));
+ #{timeout := TimerRef} ->
+ %% Event timeout active but not state timeout
+ %% - cancel event timeout only
+ loop_next_events(
+ P, Debug, S, Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, [],
+ cancel_timer_by_ref_and_type(
+ TimerRef, timeout, TimerRefs, TimeoutTypes));
+ _ ->
+ %% No state nor event timeout active.
+ loop_next_events(
+ P, Debug, S, Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, [],
+ Timers)
+ end.
+
+%% Continue state transition with processing of
+%% inserted events and timeout events
+%%
+loop_next_events(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, [], Postponed,
+ Timers) ->
%%
- loop_event_done(
- Parent, ?not_sys_debug,
+ %% Optimization when there are no timeout actions
+ %% hence no timeout zero events to append to Events
+ %% - avoid loop_timeouts
+ loop_done(
+ P, Debug,
S#state{
- state = NextState,
- data = NewData,
- postponed = [],
- hibernate = Hibernate},
- lists:reverse(P, Events)).
-%%
-%% Fast path
-%%
-loop_event_done_fast(
- Parent, Hibernate, S,
- Events, P, NextState, NewData,
- {TimerTypes,CancelTimers}) ->
+ state_data = NextState_NewData,
+ postponed = Postponed,
+ timers = Timers,
+ hibernate = Hibernate},
+ NextEventsR, Events);
+loop_next_events(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers) ->
%%
- loop_event_done(
- Parent, ?not_sys_debug,
- S#state{
- state = NextState,
- data = NewData,
- postponed = P,
- timer_types = TimerTypes,
- cancel_timers = CancelTimers,
- hibernate = Hibernate},
- Events).
-
-loop_event_done(Parent, Debug, S, Q) ->
- case Q of
+ Seen = #{},
+ TimeoutEvents = [],
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen, TimeoutEvents).
+
+%% Continue state transition with processing of timeout events
+%%
+loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, [], Postponed,
+ Timers, _Seen, TimeoutEvents) ->
+ %%
+ S_1 =
+ S#state{
+ state_data = NextState_NewData,
+ postponed = Postponed,
+ timers = Timers,
+ hibernate = Hibernate},
+ case TimeoutEvents of
[] ->
- %% Get a new event
- loop(Parent, Debug, S);
- [{Type,Content}|Events] ->
- %% Loop until out of enqueued events
- loop_event(Parent, Debug, S, Events, Type, Content)
+ loop_done(P, Debug, S_1, NextEventsR, Events);
+ _ ->
+ case Events of
+ [] ->
+ loop_prepend_timeout_events(
+ P, Debug, S_1, TimeoutEvents,
+ NextEventsR);
+ [E1] ->
+ loop_prepend_timeout_events(
+ P, Debug, S_1, TimeoutEvents,
+ [E1|NextEventsR]);
+ [E2,E1] ->
+ loop_prepend_timeout_events(
+ P, Debug, S_1, TimeoutEvents,
+ [E1,E2|NextEventsR]);
+ _ ->
+ loop_prepend_timeout_events(
+ P, Debug, S_1, TimeoutEvents,
+ lists:reverse(Events, NextEventsR))
+ end
+ end;
+loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, [Timeout|TimeoutsR], Postponed,
+ Timers, Seen, TimeoutEvents) ->
+ %%
+ case Timeout of
+ {TimeoutType,Time,TimeoutMsg} ->
+ %% Relative timeout
+ case Seen of
+ #{TimeoutType := _} ->
+ %% Type seen before - ignore
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen, TimeoutEvents);
+ #{} ->
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen, TimeoutEvents,
+ TimeoutType, Time, TimeoutMsg, [])
+ end;
+ {TimeoutType,Time,TimeoutMsg,TimeoutOpts} ->
+ %% Absolute timeout
+ case Seen of
+ #{TimeoutType := _} ->
+ %% Type seen before - ignore
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen, TimeoutEvents);
+ #{} ->
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen, TimeoutEvents,
+ TimeoutType, Time, TimeoutMsg, listify(TimeoutOpts))
+ end
end.
-
-
-%%---------------------------------------------------------------------------
-%% Server loop helpers
-
-call_callback_mode(#state{module = Module} = S) ->
- try Module:callback_mode() of
- CallbackMode ->
- callback_mode_result(S, CallbackMode)
- catch
- CallbackMode ->
- callback_mode_result(S, CallbackMode);
- Class:Reason:Stacktrace ->
- [Class,Reason,Stacktrace]
+%%
+loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen, TimeoutEvents,
+ TimeoutType, Time, TimeoutMsg, TimeoutOpts) ->
+ %%
+ case Time of
+ infinity ->
+ %% Cancel any running timer
+ loop_timeouts_cancel(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen, TimeoutEvents,
+ TimeoutType);
+ 0 when TimeoutOpts =:= [] ->
+ %% Relative timeout zero
+ %% - cancel any running timer
+ %% handle timeout zero events later
+ %%
+ loop_timeouts_cancel(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen, [{TimeoutType,TimeoutMsg}|TimeoutEvents],
+ TimeoutType);
+ _ ->
+ %% (Re)start the timer
+ TimerRef =
+ erlang:start_timer(Time, self(), TimeoutMsg, TimeoutOpts),
+ {TimerRefs,TimeoutTypes} = Timers,
+ case TimeoutTypes of
+ #{TimeoutType := OldTimerRef} ->
+ %% Cancel the running timer,
+ %% update the timeout type,
+ %% insert the new timer ref,
+ %% and remove the old timer ref
+ Timers_1 =
+ {maps:remove(
+ OldTimerRef,
+ TimerRefs#{TimerRef => TimeoutType}),
+ TimeoutTypes#{TimeoutType := TimerRef}},
+ cancel_timer(OldTimerRef),
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers_1, Seen#{TimeoutType => true}, TimeoutEvents);
+ #{} ->
+ %% Insert the new timer type and ref
+ Timers_1 =
+ {TimerRefs#{TimerRef => TimeoutType},
+ TimeoutTypes#{TimeoutType => TimerRef}},
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers_1, Seen#{TimeoutType => true}, TimeoutEvents)
+ end
end.
-callback_mode_result(S, CallbackMode) ->
- callback_mode_result(
- S, CallbackMode, listify(CallbackMode), undefined, false).
-%%
-callback_mode_result(_S, CallbackMode, [], undefined, _StateEnter) ->
- [error,
- {bad_return_from_callback_mode,CallbackMode},
- ?STACKTRACE()];
-callback_mode_result(S, _CallbackMode, [], CBMode, StateEnter) ->
- S#state{callback_mode = CBMode, state_enter = StateEnter};
-callback_mode_result(S, CallbackMode, [H|T], CBMode, StateEnter) ->
- case callback_mode(H) of
- true ->
- callback_mode_result(S, CallbackMode, T, H, StateEnter);
- false ->
- case state_enter(H) of
- true ->
- callback_mode_result(S, CallbackMode, T, CBMode, true);
- false ->
- [error,
- {bad_return_from_callback_mode,CallbackMode},
- ?STACKTRACE()]
- end
+%% Loop helper to cancel a timeout
+%%
+loop_timeouts_cancel(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ {TimerRefs,TimeoutTypes} = Timers, Seen, TimeoutEvents,
+ TimeoutType) ->
+ %% This function body should have been:
+ %% Timers_1 = cancel_timer_by_type(TimeoutType, Timers),
+ %% loop_timeouts(
+ %% P, Debug, S,
+ %% Events, NextState_NewData,
+ %% NextEventsR, Hibernate, TimeoutsR, Postponed,
+ %% Timers_1, Seen#{TimeoutType => true}, TimeoutEvents).
+ %%
+ %% Explicitly separate cases to get separate code paths for when
+ %% the map key exists vs. not, since otherwise the external call
+ %% to erlang:cancel_timer/1 and to map:remove/2 within
+ %% cancel_timer_by_type/2 would cause all live registers
+ %% to be saved to and restored from the stack also for
+ %% the case when the map key TimeoutType does not exist
+ case TimeoutTypes of
+ #{TimeoutType := TimerRef} ->
+ Timers_1 =
+ cancel_timer_by_ref_and_type(
+ TimerRef, TimeoutType, TimerRefs, TimeoutTypes),
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers_1, Seen#{TimeoutType => true}, TimeoutEvents);
+ #{} ->
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen#{TimeoutType => true}, TimeoutEvents)
end.
+%% Continue state transition with prepending timeout zero events
+%% before event queue reversal i.e appending timeout zero events
+%%
+loop_prepend_timeout_events(P, Debug, S, TimeoutEvents, EventsR) ->
+ {Debug_1,Events_1R} =
+ prepend_timeout_events(P, Debug, S, TimeoutEvents, EventsR),
+ loop_done(P, Debug_1, S, Events_1R, []).
-call_state_function(
- #state{callback_mode = undefined} = S, Type, Content, State, Data) ->
- case call_callback_mode(S) of
- #state{} = NewS ->
- call_state_function(NewS, Type, Content, State, Data);
- Error ->
- Error
- end;
-call_state_function(
- #state{callback_mode = CallbackMode, module = Module} = S,
- Type, Content, State, Data) ->
- try
- case CallbackMode of
- state_functions ->
- Module:State(Type, Content, Data);
- handle_event_function ->
- Module:handle_event(Type, Content, State, Data)
- end
- of
- Result ->
- {Result,S}
- catch
- Result ->
- {Result,S};
- Class:Reason:Stacktrace ->
- [Class,Reason,Stacktrace]
+%% Place inserted events first in the event queue
+%%
+loop_done(P, Debug, S, NextEventsR, Events) ->
+ case NextEventsR of
+ [] ->
+ loop_done(P, Debug, S, Events);
+ [E1] ->
+ loop_done(P, Debug, S, [E1|Events]);
+ [E2,E1] ->
+ loop_done(P, Debug, S, [E1,E2|Events]);
+ _ ->
+ loop_done(P, Debug, S, lists:reverse(NextEventsR, Events))
end.
-
-
-%% -> absolute | relative | badarg
-classify_timeout(TimeoutType, Time, Opts) ->
- case timeout_event_type(TimeoutType) of
- true ->
- classify_time(false, Time, Opts);
- false ->
- badarg
+%%
+%% State transition is done, keep looping if there are
+%% enqueued events, otherwise get a new event
+%%
+loop_done(P, Debug, S, Q) ->
+%%% io:format(
+%%% "loop_done: state_data = ~p,~n"
+%%% " postponed = ~p, Q = ~p,~n",
+%%% " timers = ~p.~n"
+%%% [S#state.state_data,,S#state.postponed,Q,S#state.timers]),
+ case Q of
+ [] ->
+ %% Get a new event
+ loop(P, Debug, S);
+ [Event|Events] ->
+ %% Loop until out of enqueued events
+ loop_event(P, Debug, S, Event, Events)
end.
-classify_time(Abs, Time, []) ->
- case Abs of
- true when
- is_integer(Time);
- Time =:= infinity ->
- absolute;
- false when
- is_integer(Time), 0 =< Time;
- Time =:= infinity ->
- relative;
- _ ->
- badarg
- end;
-classify_time(_, Time, [{abs,Abs}|Opts]) when is_boolean(Abs) ->
- classify_time(Abs, Time, Opts);
-classify_time(_, _, Opts) when is_list(Opts) ->
- badarg.
+%%---------------------------------------------------------------------------
+%% Server loop helpers
-%% Stop and start timers as well as create timeout zero events
-%% and pending event timer
+%% Parse an option list for erlang:start_timer/4 to figure out
+%% if the timeout will be absolute or relative
%%
-%% Stop and start timers non-event timers
-parse_timers(TimerRefs, Timers, TimeoutsR) ->
- parse_timers(TimerRefs, Timers, TimeoutsR, #{}, []).
+parse_timeout_opts_abs(Opts) ->
+ parse_timeout_opts_abs(Opts, false).
%%
-parse_timers(
- TimerRefs, Timers, [], _Seen, TimeoutEvents) ->
- %%
- {TimerRefs,Timers,TimeoutEvents};
-parse_timers(
- TimerRefs, Timers, [Timeout|TimeoutsR], Seen, TimeoutEvents) ->
- %%
- case Timeout of
- {TimerType,Time,TimerMsg,TimerOpts} ->
- %% Absolute timer
- parse_timers(
- TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents,
- TimerType, Time, TimerMsg, listify(TimerOpts));
- %% Relative timers below
- {TimerType,0,TimerMsg} ->
- parse_timers(
- TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents,
- TimerType, zero, TimerMsg, []);
- {TimerType,Time,TimerMsg} ->
- parse_timers(
- TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents,
- TimerType, Time, TimerMsg, [])
- end.
-
-parse_timers(
- TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents,
- TimerType, Time, TimerMsg, TimerOpts) ->
- case Seen of
- #{TimerType := _} ->
- %% Type seen before - ignore
- parse_timers(
- TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents);
- #{} ->
- %% Unseen type - handle
- NewSeen = Seen#{TimerType => true},
- case Time of
- infinity ->
- %% Cancel any running timer
- parse_timers(
- TimerRefs, cancel_timer_by_type(TimerType, Timers),
- TimeoutsR, NewSeen, TimeoutEvents);
- zero ->
- %% Cancel any running timer
- %% Handle zero time timeouts later
- parse_timers(
- TimerRefs, cancel_timer_by_type(TimerType, Timers),
- TimeoutsR, NewSeen,
- [{TimerType,TimerMsg}|TimeoutEvents]);
- _ ->
- %% (Re)start the timer
- TimerRef =
- erlang:start_timer(
- Time, self(), TimerMsg, TimerOpts),
- case Timers of
- {#{TimerType := OldTimerRef} = TimerTypes,
- CancelTimers} ->
- %% Cancel the running timer
- cancel_timer(OldTimerRef),
- NewCancelTimers = CancelTimers + 1,
- %% Insert the new timer into
- %% both TimerRefs and TimerTypes
- parse_timers(
- TimerRefs#{TimerRef => TimerType},
- {TimerTypes#{TimerType => TimerRef},
- NewCancelTimers},
- TimeoutsR, NewSeen, TimeoutEvents);
- {#{} = TimerTypes,CancelTimers} ->
- %% Insert the new timer into
- %% both TimerRefs and TimerTypes
- parse_timers(
- TimerRefs#{TimerRef => TimerType},
- {TimerTypes#{TimerType => TimerRef},
- CancelTimers},
- TimeoutsR, NewSeen, TimeoutEvents)
- end
- end
+parse_timeout_opts_abs(Opts, Abs) ->
+ case Opts of
+ [] ->
+ Abs;
+ [{abs,Abs_1}|Opts] when is_boolean(Abs_1) ->
+ parse_timeout_opts_abs(Opts, Abs_1);
+ _ ->
+ badarg
end.
%% Enqueue immediate timeout events (timeout 0 events)
@@ -1791,62 +2061,94 @@ parse_timers(
%% so if there are enqueued events before the event timer
%% timeout 0 event - the event timer is cancelled hence no event.
%%
-%% Other (state_timeout) timeout 0 events that are after
-%% the event timer timeout 0 events are considered to
+%% Other (state_timeout and {timeout,Name}) timeout 0 events
+%% that are after an event timer timeout 0 event are considered to
%% belong to timers that were started after the event timer
%% timeout 0 event fired, so they do not cancel the event timer.
%%
-prepend_timeout_events([], EventsR) ->
- EventsR;
-prepend_timeout_events([{timeout,_} = TimeoutEvent|TimeoutEvents], []) ->
- prepend_timeout_events(TimeoutEvents, [TimeoutEvent]);
-prepend_timeout_events([{timeout,_}|TimeoutEvents], EventsR) ->
+prepend_timeout_events(_P, Debug, _S, [], EventsR) ->
+ {Debug,EventsR};
+prepend_timeout_events(
+ P, Debug, S, [{timeout,_} = TimeoutEvent|TimeoutEvents], []) ->
+ %% Prepend this since there are no other events in queue
+ case Debug of
+ ?not_sys_debug ->
+ prepend_timeout_events(
+ P, Debug, S, TimeoutEvents, [TimeoutEvent]);
+ _ ->
+ {State,_Data} = S#state.state_data,
+ Debug_1 =
+ sys_debug(
+ Debug, P#params.name, {in,TimeoutEvent,State}),
+ prepend_timeout_events(
+ P, Debug_1, S, TimeoutEvents, [TimeoutEvent])
+ end;
+prepend_timeout_events(
+ P, Debug, S, [{timeout,_}|TimeoutEvents], EventsR) ->
%% Ignore since there are other events in queue
%% so they have cancelled the event timeout 0.
- prepend_timeout_events(TimeoutEvents, EventsR);
-prepend_timeout_events([TimeoutEvent|TimeoutEvents], EventsR) ->
+ prepend_timeout_events(P, Debug, S, TimeoutEvents, EventsR);
+prepend_timeout_events(
+ P, Debug, S, [TimeoutEvent|TimeoutEvents], EventsR) ->
%% Just prepend all others
- prepend_timeout_events(TimeoutEvents, [TimeoutEvent|EventsR]).
+ case Debug of
+ ?not_sys_debug ->
+ prepend_timeout_events(
+ P, Debug, S, TimeoutEvents, [TimeoutEvent|EventsR]);
+ _ ->
+ {State,_Data} = S#state.state_data,
+ Debug_1 =
+ sys_debug(
+ Debug, P#params.name, {in,TimeoutEvent,State}),
+ prepend_timeout_events(
+ P, Debug_1, S, TimeoutEvents, [TimeoutEvent|EventsR])
+ end.
%%---------------------------------------------------------------------------
%% Server helpers
-reply_then_terminate(Class, Reason, Stacktrace, Debug, S, Q, Replies) ->
+reply_then_terminate(Class, Reason, Stacktrace, P, Debug, S, Q, Replies) ->
do_reply_then_terminate(
- Class, Reason, Stacktrace, Debug, S, Q, listify(Replies)).
+ Class, Reason, Stacktrace, P, Debug, S, Q, listify(Replies)).
%%
do_reply_then_terminate(
- Class, Reason, Stacktrace, Debug, S, Q, []) ->
- terminate(Class, Reason, Stacktrace, Debug, S, Q);
+ Class, Reason, Stacktrace, P, Debug, S, Q, []) ->
+ terminate(Class, Reason, Stacktrace, P, Debug, S, Q);
do_reply_then_terminate(
- Class, Reason, Stacktrace, Debug, S, Q, [R|Rs]) ->
+ Class, Reason, Stacktrace, P, Debug, S, Q, [R|Rs]) ->
case R of
- {reply,{_To,_Tag}=From,Reply} ->
- reply(From, Reply),
- NewDebug =
- ?sys_debug(
- Debug,
- begin
- #state{name = Name, state = State} = S,
- {Name,State}
- end,
- {out,Reply,From}),
- do_reply_then_terminate(
- Class, Reason, Stacktrace, NewDebug, S, Q, Rs);
+ {reply,From,Reply} ->
+ case from(From) of
+ true ->
+ reply(From, Reply),
+ Debug_1 =
+ ?sys_debug(
+ Debug,
+ P#params.name,
+ {out,Reply,From}),
+ do_reply_then_terminate(
+ Class, Reason, Stacktrace, P, Debug_1, S, Q, Rs);
+ false ->
+ terminate(
+ error,
+ {bad_reply_action_from_state_function,R},
+ ?STACKTRACE(),
+ P, Debug, S, Q)
+ end;
_ ->
terminate(
error,
{bad_reply_action_from_state_function,R},
?STACKTRACE(),
- Debug, S, Q)
+ P, Debug, S, Q)
end.
terminate(
- Class, Reason, Stacktrace, Debug,
- #state{module = Module, state = State, data = Data} = S,
- Q) ->
+ Class, Reason, Stacktrace,
+ #params{module = Module} = P, Debug,
+ #state{state_data = {State,Data}} = S, Q) ->
case erlang:function_exported(Module, terminate, 3) of
true ->
try Module:terminate(Reason, State, Data) of
@@ -1854,8 +2156,7 @@ terminate(
catch
_ -> ok;
C:R:ST ->
- error_info(C, R, ST, S, Q),
- sys:print_log(Debug),
+ error_info(C, R, ST, Debug, P, S, Q),
erlang:raise(C, R, ST)
end;
false ->
@@ -1864,14 +2165,13 @@ terminate(
_ =
case Reason of
normal ->
- terminate_sys_debug(Debug, S, State, Reason);
+ terminate_sys_debug(Debug, P, State, Reason);
shutdown ->
- terminate_sys_debug(Debug, S, State, Reason);
+ terminate_sys_debug(Debug, P, State, Reason);
{shutdown,_} ->
- terminate_sys_debug(Debug, S, State, Reason);
+ terminate_sys_debug(Debug, P, State, Reason);
_ ->
- error_info(Class, Reason, Stacktrace, S, Q),
- sys:print_log(Debug)
+ error_info(Class, Reason, Stacktrace, Debug, P, S, Q)
end,
case Stacktrace of
[] ->
@@ -1880,38 +2180,67 @@ terminate(
erlang:raise(Class, Reason, Stacktrace)
end.
-terminate_sys_debug(Debug, S, State, Reason) ->
- ?sys_debug(Debug, {S#state.name,State}, {terminate,Reason}).
+terminate_sys_debug(Debug, P, State, Reason) ->
+ ?sys_debug(Debug, P#params.name, {terminate,Reason,State}).
error_info(
- Class, Reason, Stacktrace,
- #state{
+ Class, Reason, Stacktrace, Debug,
+ #params{
name = Name,
callback_mode = CallbackMode,
- state_enter = StateEnter,
- postponed = P} = S,
+ state_enter = StateEnter} = P,
+ #state{postponed = Postponed} = S,
Q) ->
+ Log = sys:get_log(Debug),
?LOG_ERROR(#{label=>{gen_statem,terminate},
name=>Name,
queue=>Q,
- postponed=>P,
+ postponed=>Postponed,
callback_mode=>CallbackMode,
state_enter=>StateEnter,
- state=>format_status(terminate, get(), S),
- reason=>{Class,Reason,Stacktrace}},
+ state=>format_status(terminate, get(), P, S),
+ log=>Log,
+ reason=>{Class,Reason,Stacktrace},
+ client_info=>client_stacktrace(Q)},
#{domain=>[otp],
report_cb=>fun gen_statem:format_log/1,
error_logger=>#{tag=>error}}).
+client_stacktrace([]) ->
+ undefined;
+client_stacktrace([{{call,{Pid,_Tag}},_Req}|_]) when is_pid(Pid) ->
+ if
+ node(Pid) =:= node() ->
+ case
+ process_info(Pid, [current_stacktrace, registered_name])
+ of
+ undefined ->
+ {Pid,dead};
+ [{current_stacktrace, Stacktrace},
+ {registered_name, []}] ->
+ {Pid,{Pid,Stacktrace}};
+ [{current_stacktrace, Stacktrace},
+ {registered_name, Name}] ->
+ {Pid,{Name,Stacktrace}}
+ end;
+ true ->
+ {Pid,remote}
+ end;
+client_stacktrace([_|_]) ->
+ undefined.
+
+
format_log(#{label:={gen_statem,terminate},
name:=Name,
queue:=Q,
- postponed:=P,
+ postponed:=Postponed,
callback_mode:=CallbackMode,
state_enter:=StateEnter,
state:=FmtData,
- reason:={Class,Reason,Stacktrace}}) ->
+ log:=Log,
+ reason:={Class,Reason,Stacktrace},
+ client_info:=ClientInfo}) ->
{FixedReason,FixedStacktrace} =
case Stacktrace of
[{M,F,Args,_}|ST]
@@ -1931,14 +2260,12 @@ format_log(#{label:={gen_statem,terminate},
true ->
{Reason,Stacktrace};
false ->
- {{'function not exported',{M,F,Arity}},
- ST}
+ {{'function not exported',{M,F,Arity}},ST}
end
end;
_ -> {Reason,Stacktrace}
end,
- [LimitedP, LimitedFmtData, LimitedFixedReason] =
- [error_logger:limit_term(D) || D <- [P, FmtData, FixedReason]],
+ {ClientFmt,ClientArgs} = format_client_log(ClientInfo),
CBMode =
case StateEnter of
true ->
@@ -1958,39 +2285,60 @@ format_log(#{label:={gen_statem,terminate},
[_,_|_] -> "** Queued = ~tp~n";
_ -> ""
end ++
- case P of
+ case Postponed of
[] -> "";
_ -> "** Postponed = ~tp~n"
end ++
case FixedStacktrace of
[] -> "";
_ -> "** Stacktrace =~n** ~tp~n"
- end,
+ end ++
+ case Log of
+ [] -> "";
+ _ -> "** Log =~n** ~tp~n"
+ end ++ ClientFmt,
[Name |
case Q of
[] -> [];
- [Event|_] -> [Event]
+ [Event|_] -> [error_logger:limit_term(Event)]
end] ++
- [LimitedFmtData,
- Class,LimitedFixedReason,
+ [error_logger:limit_term(FmtData),
+ Class,error_logger:limit_term(FixedReason),
CBMode] ++
case Q of
- [_|[_|_] = Events] -> [Events];
+ [_|[_|_] = Events] -> [error_logger:limit_term(Events)];
_ -> []
end ++
- case P of
+ case Postponed of
[] -> [];
- _ -> [LimitedP]
+ _ -> [error_logger:limit_term(Postponed)]
end ++
case FixedStacktrace of
[] -> [];
- _ -> [FixedStacktrace]
- end}.
+ _ -> [error_logger:limit_term(FixedStacktrace)]
+ end ++
+ case Log of
+ [] -> [];
+ _ -> [[error_logger:limit_term(T) || T <- Log]]
+ end ++ ClientArgs}.
+
+format_client_log(undefined) ->
+ {"", []};
+format_client_log({Pid,dead}) ->
+ {"** Client ~p is dead~n", [Pid]};
+format_client_log({Pid,remote}) ->
+ {"** Client ~p is remote on node ~p~n", [Pid, node(Pid)]};
+format_client_log({_Pid,{Name,Stacktrace}}) ->
+ {"** Client ~tp stacktrace~n"
+ "** ~tp~n",
+ [Name, error_logger:limit_term(Stacktrace)]}.
+
%% Call Module:format_status/2 or return a default value
format_status(
Opt, PDict,
- #state{module = Module, state = State, data = Data}) ->
+ #params{module = Module},
+ #state{state_data = {State,Data} = State_Data}) ->
case erlang:function_exported(Module, format_status, 2) of
true ->
try Module:format_status(Opt, [PDict,State,Data])
@@ -1998,21 +2346,21 @@ format_status(
Result -> Result;
_:_ ->
format_status_default(
- Opt, State,
- atom_to_list(Module) ++ ":format_status/2 crashed")
+ Opt,
+ {State,
+ atom_to_list(Module) ++ ":format_status/2 crashed"})
end;
false ->
- format_status_default(Opt, State, Data)
+ format_status_default(Opt, State_Data)
end.
-%% The default Module:format_status/2
-format_status_default(Opt, State, Data) ->
- StateData = {State,Data},
+%% The default Module:format_status/3
+format_status_default(Opt, State_Data) ->
case Opt of
terminate ->
- StateData;
+ State_Data;
_ ->
- [{data,[{"State",StateData}]}]
+ [{data,[{"State",State_Data}]}]
end.
-compile({inline, [listify/1]}).
@@ -2021,24 +2369,41 @@ listify(Item) when is_list(Item) ->
listify(Item) ->
[Item].
+
+-define(cancel_timer(TimerRef),
+ case erlang:cancel_timer(TimerRef) of
+ false ->
+ %% No timer found and we have not seen the timeout message
+ receive
+ {timeout,(TimerRef),_} ->
+ ok
+ end;
+ _ ->
+ %% Timer was running
+ ok
+ end).
+
+-compile({inline, [cancel_timer/1]}).
+cancel_timer(TimerRef) ->
+ ?cancel_timer(TimerRef).
+
%% Cancel timer if running, otherwise no op
%%
-%% This is an asynchronous cancel so the timer is not really cancelled
-%% until we get a cancel_timer msg i.e {cancel_timer,TimerRef,_}.
-%% In the mean time we might get a timeout message.
-%%
-%% Remove the timer from TimerTypes.
-%% When we get the cancel_timer msg we remove it from TimerRefs.
+%% Remove the timer from Timers
-compile({inline, [cancel_timer_by_type/2]}).
-cancel_timer_by_type(TimerType, {TimerTypes,CancelTimers} = TT_CT) ->
- case TimerTypes of
- #{TimerType := TimerRef} ->
- ok = erlang:cancel_timer(TimerRef, [{async,true}]),
- {maps:remove(TimerType, TimerTypes),CancelTimers + 1};
- #{} ->
- TT_CT
+cancel_timer_by_type(TimeoutType, {TimerRefs,TimeoutTypes} = Timers) ->
+ case TimeoutTypes of
+ #{TimeoutType := TimerRef} ->
+ ?cancel_timer(TimerRef),
+ {maps:remove(TimerRef, TimerRefs),
+ maps:remove(TimeoutType, TimeoutTypes)};
+ #{} ->
+ Timers
end.
--compile({inline, [cancel_timer/1]}).
-cancel_timer(TimerRef) ->
- ok = erlang:cancel_timer(TimerRef, [{async,true}]).
+-compile({inline, [cancel_timer_by_ref_and_type/4]}).
+cancel_timer_by_ref_and_type(
+ TimerRef, TimeoutType, TimerRefs, TimeoutTypes) ->
+ ?cancel_timer(TimerRef),
+ {maps:remove(TimerRef, TimerRefs),
+ maps:remove(TimeoutType, TimeoutTypes)}.
diff --git a/lib/stdlib/src/io_lib.erl b/lib/stdlib/src/io_lib.erl
index 8223a52873..2b5a374cf2 100644
--- a/lib/stdlib/src/io_lib.erl
+++ b/lib/stdlib/src/io_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All 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 @@
-export([limit_term/2]).
+-export([chars_length/1]).
+
-export_type([chars/0, latin1_string/0, continuation/0,
fread_error/0, fread_item/0, format_spec/0, chars_limit/0]).
@@ -1131,3 +1133,17 @@ test_limit_map_assoc(K, V, D) ->
test_limit(V, D - 1).
test_limit_bitstring(_, _) -> ok.
+
+-spec chars_length(chars()) -> non_neg_integer().
+%% Optimized for deep lists S such that deep_latin1_char_list(S) is
+%% true. No binaries allowed! It is assumed that $\r is never followed
+%% by $\n if S is an iolist() (string:length() assigns such a
+%% sub-sequence length 1).
+chars_length(S) ->
+ try
+ %% true = deep_latin1_char_list(S),
+ iolist_size(S)
+ catch
+ _:_ ->
+ string:length(S)
+ end.
diff --git a/lib/stdlib/src/io_lib_format.erl b/lib/stdlib/src/io_lib_format.erl
index ab9031573b..d1aa4cd157 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-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -248,7 +248,7 @@ count_small([#{control_char := $s}|Cs], #{w := W} = Cnts) ->
count_small(Cs, Cnts#{w := W + 1});
count_small([S|Cs], #{other := Other} = Cnts) when is_list(S);
is_binary(S) ->
- count_small(Cs, Cnts#{other := Other + string:length(S)});
+ count_small(Cs, Cnts#{other := Other + io_lib:chars_length(S)});
count_small([C|Cs], #{other := Other} = Cnts) when is_integer(C) ->
count_small(Cs, Cnts#{other := Other + 1});
count_small([], #{p := P, s := S, w := W, other := Other}) ->
@@ -280,10 +280,15 @@ build_limited([#{control_char := C, args := As, width := F, adjust := Ad,
true -> MaxLen0 div Count0
end,
S = control_limited(C, As, F, Ad, P, Pad, Enc, Str, MaxChars, I),
- Len = string:length(S),
NumOfPs = decr_pc(C, NumOfPs0),
Count = Count0 - 1,
- MaxLen = sub(MaxLen0, Len),
+ MaxLen = if
+ MaxLen0 < 0 -> % optimization
+ MaxLen0;
+ true ->
+ Len = io_lib:chars_length(S),
+ sub(MaxLen0, Len)
+ end,
if
NumOfPs > 0 -> [S|build_limited(Cs, NumOfPs, Count,
MaxLen, indentation(S, I))];
@@ -406,7 +411,7 @@ base(B) when is_integer(B) ->
term(T, none, _Adj, none, _Pad) -> T;
term(T, none, Adj, P, Pad) -> term(T, P, Adj, P, Pad);
term(T, F, Adj, P0, Pad) ->
- L = string:length(T),
+ L = io_lib:chars_length(T),
P = erlang:min(L, case P0 of none -> F; _ -> min(P0, F) end),
if
L > P ->
@@ -713,7 +718,7 @@ fwrite_g(Fl, F, Adj, P, Pad) when P >= 1 ->
end.
-%% iolist_to_chars(iolist()) -> deep_char_list()
+%% iolist_to_chars(iolist()) -> io_lib:chars()
iolist_to_chars([C|Cs]) when is_integer(C), C >= $\000, C =< $\377 ->
[C | iolist_to_chars(Cs)];
@@ -729,7 +734,7 @@ iolist_to_chars(B) when is_binary(B) ->
%% cbinary() | nil())
%% cbinary() :: unicode:unicode_binary() | unicode:latin1_binary()
-%% cdata_to_chars(cdata()) -> io_lib:deep_char_list()
+%% cdata_to_chars(cdata()) -> io_lib:chars()
cdata_to_chars([C|Cs]) when is_integer(C), C >= $\000 ->
[C | cdata_to_chars(Cs)];
@@ -745,7 +750,7 @@ cdata_to_chars(B) when is_binary(B) ->
limit_string(S, F, CharsLimit) when CharsLimit < 0; CharsLimit >= F -> S;
limit_string(S, _F, CharsLimit) ->
- case string:length(S) =< CharsLimit of
+ case io_lib:chars_length(S) =< CharsLimit of
true -> S;
false -> [string:slice(S, 0, sub(CharsLimit, 3)), "..."]
end.
@@ -759,11 +764,11 @@ limit_field(F, CharsLimit) ->
string(S, none, _Adj, none, _Pad) -> S;
string(S, F, Adj, none, Pad) ->
- string_field(S, F, Adj, string:length(S), Pad);
+ string_field(S, F, Adj, io_lib:chars_length(S), Pad);
string(S, none, _Adj, P, Pad) ->
- string_field(S, P, left, string:length(S), Pad);
+ string_field(S, P, left, io_lib:chars_length(S), Pad);
string(S, F, Adj, P, Pad) when F >= P ->
- N = string:length(S),
+ N = io_lib:chars_length(S),
if F > P ->
if N > P ->
adjust(flat_trunc(S, P), chars(Pad, F-P), Adj);
diff --git a/lib/stdlib/src/io_lib_pretty.erl b/lib/stdlib/src/io_lib_pretty.erl
index ba9d9e8434..5483ea87b5 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-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -507,20 +507,20 @@ print_length(#{}=M, _D, _T, _RF, _Enc, _Str) when map_size(M) =:= 0 ->
{"#{}", 3, 0, no_more};
print_length(Atom, _D, _T, _RF, Enc, _Str) when is_atom(Atom) ->
S = write_atom(Atom, Enc),
- {S, string:length(S), 0, no_more};
+ {S, io_lib:chars_length(S), 0, no_more};
print_length(List, D, T, RF, Enc, Str) when is_list(List) ->
%% only flat lists are "printable"
case Str andalso printable_list(List, D, T, Enc) of
true ->
%% print as string, escaping double-quotes in the list
S = write_string(List, Enc),
- {S, string:length(S), 0, no_more};
+ {S, io_lib:chars_length(S), 0, no_more};
{true, Prefix} ->
%% Truncated lists when T < 0 could break some existing code.
S = write_string(Prefix, Enc),
%% NumOfDots = 0 to avoid looping--increasing the depth
%% does not make Prefix longer.
- {[S | "..."], 3 + string:length(S), 0, no_more};
+ {[S | "..."], 3 + io_lib:chars_length(S), 0, no_more};
false ->
case print_length_list(List, D, T, RF, Enc, Str) of
{What, Len, Dots, _More} when Dots > 0 ->
@@ -564,7 +564,7 @@ print_length(<<_/bitstring>> = Bin, D, T, RF, Enc, Str) ->
{[$<,$<,S,$>,$>], 4 + length(S), 0, no_more};
{false, List} when is_list(List) ->
S = io_lib:write_string(List, $"), %"
- {[$<,$<,S,"/utf8>>"], 9 + string:length(S), 0, no_more};
+ {[$<,$<,S,"/utf8>>"], 9 + io_lib:chars_length(S), 0, no_more};
{true, true, Prefix} ->
S = io_lib:write_string(Prefix, $"), %"
More = fun(T1, Dd) ->
@@ -576,7 +576,7 @@ print_length(<<_/bitstring>> = Bin, D, T, RF, Enc, Str) ->
More = fun(T1, Dd) ->
?FUNCTION_NAME(Bin, D+Dd, T1, RF, Enc, Str)
end,
- {[$<,$<,S|"/utf8...>>"], 12 + string:length(S), 3, More};
+ {[$<,$<,S|"/utf8...>>"], 12 + io_lib:chars_length(S), 3, More};
false ->
case io_lib:write_binary(Bin, D, T) of
{S, <<>>} ->
@@ -591,7 +591,7 @@ print_length(<<_/bitstring>> = Bin, D, T, RF, Enc, Str) ->
print_length(Term, _D, _T, _RF, _Enc, _Str) ->
S = io_lib:write(Term),
%% S can contain unicode, so iolist_size(S) cannot be used here
- {S, string:length(S), 0, no_more}.
+ {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,
@@ -651,7 +651,7 @@ print_length_record(Tuple, 1, _T, RF, RDefs, Enc, Str) ->
{"{...}", 5, 3, More};
print_length_record(Tuple, D, T, RF, RDefs, Enc, Str) ->
Name = [$# | write_atom(element(1, Tuple), Enc)],
- NameL = string:length(Name),
+ NameL = io_lib:chars_length(Name),
T1 = tsub(T, NameL+2),
L = print_length_fields(RDefs, D - 1, T1, Tuple, 2, RF, Enc, Str),
{Len, Dots} = list_length(L, NameL + 2, 0),
@@ -677,7 +677,7 @@ print_length_fields([Def | Defs], D, T, Tuple, I, RF, Enc, Str) ->
print_length_field(Def, D, T, E, RF, Enc, Str) ->
Name = write_atom(Def, Enc),
- NameL = string:length(Name) + 3,
+ NameL = io_lib:chars_length(Name) + 3,
{_, Len, Dots, _} =
Field = print_length(E, D, tsub(T, NameL), RF, Enc, Str),
{{field, Name, NameL, Field}, NameL + Len, Dots, no_more}.
@@ -738,7 +738,7 @@ printable_list(L, _D, T, _Uni) when T < 0->
io_lib:printable_list(L).
slice(L, N) ->
- try string:length(L) =< N of
+ try io_lib:chars_length(L) =< N of
true ->
all;
false ->
diff --git a/lib/stdlib/src/maps.erl b/lib/stdlib/src/maps.erl
index 60463feec2..51965ddb57 100644
--- a/lib/stdlib/src/maps.erl
+++ b/lib/stdlib/src/maps.erl
@@ -21,7 +21,7 @@
-module(maps).
-export([get/3, filter/2,fold/3,
- map/2, size/1,
+ map/2, size/1, new/0,
update_with/3, update_with/4,
without/2, with/2,
iterator/1, next/1]).
@@ -29,13 +29,15 @@
%% BIFs
-export([get/2, find/2, from_list/1,
is_key/2, keys/1, merge/2,
- new/0, put/3, remove/2, take/2,
+ put/3, remove/2, take/2,
to_list/1, update/3, values/1]).
--opaque iterator() :: {term(), term(), iterator()}
- | none | nonempty_improper_list(integer(),map()).
+-opaque iterator(Key, Value) :: {Key, Value, iterator(Key, Value)} | none
+ | nonempty_improper_list(integer(), #{Key => Value}).
--export_type([iterator/0]).
+-type iterator() :: iterator(term(), term()).
+
+-export_type([iterator/2, iterator/0]).
-dialyzer({no_improper_lists, iterator/1}).
@@ -50,9 +52,7 @@
get(_,_) -> erlang:nif_error(undef).
-spec find(Key,Map) -> {ok, Value} | error when
- Key :: term(),
- Map :: map(),
- Value :: term().
+ Map :: #{Key => Value, _ => _}.
find(_,_) -> erlang:nif_error(undef).
@@ -75,9 +75,8 @@ is_key(_,_) -> erlang:nif_error(undef).
-spec keys(Map) -> Keys when
- Map :: map(),
- Keys :: [Key],
- Key :: term().
+ Map :: #{Key => _},
+ Keys :: [Key].
keys(_) -> erlang:nif_error(undef).
@@ -91,13 +90,6 @@ keys(_) -> erlang:nif_error(undef).
merge(_,_) -> erlang:nif_error(undef).
-
--spec new() -> Map when
- Map :: map().
-
-new() -> erlang:nif_error(undef).
-
-
%% Shadowed by erl_bif_types: maps:put/3
-spec put(Key,Value,Map1) -> Map2 when
Key :: term(),
@@ -116,17 +108,13 @@ put(_,_,_) -> erlang:nif_error(undef).
remove(_,_) -> erlang:nif_error(undef).
-spec take(Key,Map1) -> {Value,Map2} | error when
- Key :: term(),
- Map1 :: map(),
- Value :: term(),
- Map2 :: map().
+ Map1 :: #{Key => Value, _ => _},
+ Map2 :: #{_ => _}.
take(_,_) -> erlang:nif_error(undef).
-spec to_list(Map) -> [{Key,Value}] when
- Map :: map(),
- Key :: term(),
- Value :: term().
+ Map :: #{Key => Value}.
to_list(Map) when is_map(Map) ->
to_list_internal(erts_internal:map_next(0, Map, []));
@@ -140,79 +128,69 @@ to_list_internal(Acc) ->
%% Shadowed by erl_bif_types: maps:update/3
-spec update(Key,Value,Map1) -> Map2 when
- Key :: term(),
- Value :: term(),
- Map1 :: map(),
- Map2 :: map().
+ Map1 :: #{Key := _, _ => _},
+ Map2 :: #{Key := Value, _ => _}.
update(_,_,_) -> erlang:nif_error(undef).
-spec values(Map) -> Values when
- Map :: map(),
- Values :: [Value],
- Value :: term().
+ Map :: #{_ => Value},
+ Values :: [Value].
values(_) -> erlang:nif_error(undef).
%% End of BIFs
+-spec new() -> Map when
+ Map :: #{}.
+
+new() -> #{}.
+
-spec update_with(Key,Fun,Map1) -> Map2 when
- Key :: term(),
- Map1 :: map(),
- Map2 :: map(),
- Fun :: fun((Value1 :: term()) -> Value2 :: term()).
+ Map1 :: #{Key := Value1, _ => _},
+ Map2 :: #{Key := Value2, _ => _},
+ Fun :: fun((Value1) -> Value2).
update_with(Key,Fun,Map) when is_function(Fun,1), is_map(Map) ->
- try maps:get(Key,Map) of
- Val -> maps:update(Key,Fun(Val),Map)
- catch
- error:{badkey,_} ->
- erlang:error({badkey,Key},[Key,Fun,Map])
+ case Map of
+ #{Key := Value} -> Map#{Key := Fun(Value)};
+ #{} -> erlang:error({badkey,Key},[Key,Fun,Map])
end;
update_with(Key,Fun,Map) ->
erlang:error(error_type(Map),[Key,Fun,Map]).
-spec update_with(Key,Fun,Init,Map1) -> Map2 when
- Key :: term(),
- Map1 :: Map1,
- Map2 :: Map2,
- Fun :: fun((Value1 :: term()) -> Value2 :: term()),
- Init :: term().
+ Map1 :: #{Key => Value1, _ => _},
+ Map2 :: #{Key := Value2 | Init, _ => _},
+ Fun :: fun((Value1) -> Value2).
update_with(Key,Fun,Init,Map) when is_function(Fun,1), is_map(Map) ->
- case maps:find(Key,Map) of
- {ok,Val} -> maps:update(Key,Fun(Val),Map);
- error -> maps:put(Key,Init,Map)
+ case Map of
+ #{Key := Value} -> Map#{Key := Fun(Value)};
+ #{} -> Map#{Key => Init}
end;
update_with(Key,Fun,Init,Map) ->
erlang:error(error_type(Map),[Key,Fun,Init,Map]).
-spec get(Key, Map, Default) -> Value | Default when
- Key :: term(),
- Map :: map(),
- Value :: term(),
- Default :: term().
+ Map :: #{Key => Value, _ => _}.
get(Key,Map,Default) when is_map(Map) ->
- case maps:find(Key, Map) of
- {ok, Value} ->
- Value;
- error ->
- Default
+ case Map of
+ #{Key := Value} -> Value;
+ #{} -> Default
end;
get(Key,Map,Default) ->
erlang:error({badmap,Map},[Key,Map,Default]).
--spec filter(Pred,MapOrIter) -> Map when
+-spec filter(Pred, MapOrIter) -> Map when
Pred :: fun((Key, Value) -> boolean()),
- Key :: term(),
- Value :: term(),
- MapOrIter :: map() | iterator(),
- Map :: map().
+ MapOrIter :: #{Key => Value} | iterator(Key, Value),
+ Map :: #{Key => Value}.
filter(Pred,Map) when is_function(Pred,2), is_map(Map) ->
maps:from_list(filter_1(Pred, iterator(Map)));
@@ -235,14 +213,11 @@ filter_1(Pred, Iter) ->
end.
-spec fold(Fun,Init,MapOrIter) -> Acc when
- Fun :: fun((K, V, AccIn) -> AccOut),
+ Fun :: fun((Key, Value, AccIn) -> AccOut),
Init :: term(),
- Acc :: term(),
- AccIn :: term(),
- AccOut :: term(),
- MapOrIter :: map() | iterator(),
- K :: term(),
- V :: term().
+ Acc :: AccOut,
+ AccIn :: Init | AccOut,
+ MapOrIter :: #{Key => Value} | iterator(Key, Value).
fold(Fun,Init,Map) when is_function(Fun,3), is_map(Map) ->
fold_1(Fun,Init,iterator(Map));
@@ -260,12 +235,9 @@ fold_1(Fun, Acc, Iter) ->
end.
-spec map(Fun,MapOrIter) -> Map when
- Fun :: fun((K, V1) -> V2),
- MapOrIter :: map() | iterator(),
- Map :: map(),
- K :: term(),
- V1 :: term(),
- V2 :: term().
+ Fun :: fun((Key, Value1) -> Value2),
+ MapOrIter :: #{Key => Value1} | iterator(Key, Value1),
+ Map :: #{Key => Value2}.
map(Fun,Map) when is_function(Fun, 2), is_map(Map) ->
maps:from_list(map_1(Fun, iterator(Map)));
@@ -291,17 +263,15 @@ size(Val) ->
erlang:error({badmap,Val},[Val]).
-spec iterator(Map) -> Iterator when
- Map :: map(),
- Iterator :: iterator().
+ Map :: #{Key => Value},
+ Iterator :: iterator(Key, Value).
iterator(M) when is_map(M) -> [0 | M];
iterator(M) -> erlang:error({badmap, M}, [M]).
-spec next(Iterator) -> {Key, Value, NextIterator} | 'none' when
- Iterator :: iterator(),
- Key :: term(),
- Value :: term(),
- NextIterator :: iterator().
+ 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) ->
@@ -318,29 +288,26 @@ next(Iter) ->
K :: term().
without(Ks,M) when is_list(Ks), is_map(M) ->
- lists:foldl(fun(K, M1) -> maps:remove(K, M1) end, M, Ks);
+ lists:foldl(fun maps:remove/2, M, Ks);
without(Ks,M) ->
erlang:error(error_type(M),[Ks,M]).
-spec with(Ks, Map1) -> Map2 when
Ks :: [K],
- Map1 :: map(),
- Map2 :: map(),
- K :: term().
+ Map1 :: #{K => V, _ => _},
+ Map2 :: #{K => V}.
with(Ks,Map1) when is_list(Ks), is_map(Map1) ->
- Fun = fun(K, List) ->
- case maps:find(K, Map1) of
- {ok, V} ->
- [{K, V} | List];
- error ->
- List
- end
- end,
- maps:from_list(lists:foldl(Fun, [], Ks));
+ maps:from_list(with_1(Ks, Map1));
with(Ks,M) ->
erlang:error(error_type(M),[Ks,M]).
+with_1([K|Ks], Map) ->
+ case Map of
+ #{K := V} -> [{K,V}|with_1(Ks, Map)];
+ #{} -> with_1(Ks, Map)
+ end;
+with_1([], _Map) -> [].
error_type(M) when is_map(M) -> badarg;
error_type(V) -> {badmap, V}.
diff --git a/lib/stdlib/src/ms_transform.erl b/lib/stdlib/src/ms_transform.erl
index 6d243e1bec..97ec785c62 100644
--- a/lib/stdlib/src/ms_transform.erl
+++ b/lib/stdlib/src/ms_transform.erl
@@ -556,8 +556,8 @@ tg({call, Line, {remote,_,{atom,_,erlang},{atom, Line2, FunName}},ParaList},
FunName,length(ParaList)}})
end;
tg({call, Line, {remote,_,{atom,_,ModuleName},
- {atom, _, FunName}},_ParaList},B) ->
- throw({error,Line,{?ERR_GENREMOTECALL+B#tgd.eb,ModuleName,FunName}});
+ {atom, _, FunName}},ParaList},B) ->
+ throw({error,Line,{?ERR_GENREMOTECALL+B#tgd.eb,ModuleName,FunName,length(ParaList)}});
tg({cons,Line, H, T},B) ->
{cons, Line, tg(H,B), tg(T,B)};
tg({nil, Line},_B) ->
diff --git a/lib/stdlib/src/otp_internal.erl b/lib/stdlib/src/otp_internal.erl
index aaed13ba3a..b95cb8f525 100644
--- a/lib/stdlib/src/otp_internal.erl
+++ b/lib/stdlib/src/otp_internal.erl
@@ -44,8 +44,19 @@ obsolete(Module, Name, Arity) ->
no
end.
-obsolete_1(net, _, _) ->
- {deprecated, "module 'net' obsolete; use 'net_adm'"};
+obsolete_1(net, call, 4) ->
+ {deprecated, {rpc, call, 4}};
+obsolete_1(net, cast, 4) ->
+ {deprecated, {rpc, cast, 4}};
+obsolete_1(net, broadcast, 3) ->
+ {deprecated, {rpc, eval_everywhere, 3}};
+obsolete_1(net, ping, 1) ->
+ {deprecated, {net_adm, ping, 1}};
+obsolete_1(net, sleep, 1) ->
+ {deprecated, "Use 'receive after T -> ok end' instead"};
+obsolete_1(net, relay, 1) ->
+ {deprecated, {slave, relay, 1}};
+
obsolete_1(erlang, now, 0) ->
{deprecated,
@@ -55,6 +66,14 @@ obsolete_1(erlang, now, 0) ->
obsolete_1(calendar, local_time_to_universal_time, 1) ->
{deprecated, {calendar, local_time_to_universal_time_dst, 1}};
+%% *** STDLIB added in OTP 22 ***
+
+obsolete_1(sys, get_debug, 3) ->
+ {deprecated,
+ "Deprecated function. "
+ "Incorrectly documented and in fact only for internal use. "
+ "Can often be replaced with sys:get_log/1."};
+
%% *** STDLIB added in OTP 20 ***
obsolete_1(gen_fsm, start, 3) ->
@@ -398,10 +417,8 @@ obsolete_1(megaco, format_versions, 1) ->
%% *** OS-MON-MIB ***
-obsolete_1(os_mon_mib, init, 1) ->
- {deprecated, {os_mon_mib, load, 1}};
-obsolete_1(os_mon_mib, stop, 1) ->
- {deprecated, {os_mon_mib, unload, 1}};
+obsolete_1(os_mon_mib, _, _) ->
+ {removed, "was removed in 22.0"};
obsolete_1(auth, is_auth, 1) ->
{deprecated, {net_adm, ping, 1}};
diff --git a/lib/stdlib/src/rand.erl b/lib/stdlib/src/rand.erl
index 362e98006e..3a9a1e007b 100644
--- a/lib/stdlib/src/rand.erl
+++ b/lib/stdlib/src/rand.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2015-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2015-2018. All 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,18 +32,24 @@
uniform/0, uniform/1, uniform_s/1, uniform_s/2,
uniform_real/0, uniform_real_s/1,
jump/0, jump/1,
- normal/0, normal/2, normal_s/1, normal_s/3
+ normal/0, normal/2, normal_s/1, normal_s/3
]).
+%% Test, dev and internal
+-export([exro928_jump_2pow512/1, exro928_jump_2pow20/1,
+ exro928_seed/1, exro928_next/1, exro928_next_state/1,
+ format_jumpconst58/1, seed58/2]).
+
%% Debug
-export([make_float/3, float2str/1, bc64/1]).
--compile({inline, [exs64_next/1, exsplus_next/1,
+-compile({inline, [exs64_next/1, exsplus_next/1, exsss_next/1,
exs1024_next/1, exs1024_calc/2,
+ exro928_next_state/4,
exrop_next/1, exrop_next_s/2,
get_52/1, normal_kiwi/1]}).
--define(DEFAULT_ALG_HANDLER, exrop).
+-define(DEFAULT_ALG_HANDLER, exsss).
-define(SEED_DICT, rand_seed).
%% =====================================================================
@@ -80,8 +86,8 @@
%% This depends on the algorithm handler function
-type alg_state() ::
- exs64_state() | exsplus_state() | exs1024_state() |
- exrop_state() | term().
+ exsplus_state() | exro928_state() | exrop_state() | exs1024_state() |
+ exs64_state() | term().
%% This is the algorithm handling definition within this module,
%% and the type to use for plugins.
@@ -124,14 +130,17 @@
%% Algorithm state
-type state() :: {alg_handler(), alg_state()}.
--type builtin_alg() :: exs64 | exsplus | exsp | exs1024 | exs1024s | exrop.
+-type builtin_alg() ::
+ exsss | exro928ss | exrop | exs1024s | exsp | exs64 | exsplus | exs1024.
-type alg() :: builtin_alg() | atom().
-type export_state() :: {alg(), alg_state()}.
+-type seed() :: [integer()] | integer() | {integer(), integer(), integer()}.
-export_type(
[builtin_alg/0, alg/0, alg_handler/0, alg_state/0,
- state/0, export_state/0]).
+ state/0, export_state/0, seed/0]).
-export_type(
- [exs64_state/0, exsplus_state/0, exs1024_state/0, exrop_state/0]).
+ [exsplus_state/0, exro928_state/0, exrop_state/0, exs1024_state/0,
+ exs64_state/0]).
%% =====================================================================
%% Range macro and helper
@@ -229,12 +238,12 @@ export_seed() ->
end.
-spec export_seed_s(State :: state()) -> export_state().
-export_seed_s({#{type:=Alg}, Seed}) -> {Alg, Seed}.
+export_seed_s({#{type:=Alg}, AlgState}) -> {Alg, AlgState}.
%% seed(Alg) seeds RNG with runtime dependent values
%% and return the NEW state
-%% seed({Alg,Seed}) setup RNG with a previously exported seed
+%% seed({Alg,AlgState}) setup RNG with a previously exported seed
%% and return the NEW state
-spec seed(
@@ -246,11 +255,11 @@ seed(Alg) ->
-spec seed_s(
AlgOrStateOrExpState :: builtin_alg() | state() | export_state()) ->
state().
-seed_s({AlgHandler, _Seed} = State) when is_map(AlgHandler) ->
+seed_s({AlgHandler, _AlgState} = State) when is_map(AlgHandler) ->
State;
-seed_s({Alg0, Seed}) ->
- {Alg,_SeedFun} = mk_alg(Alg0),
- {Alg, Seed};
+seed_s({Alg, AlgState}) when is_atom(Alg) ->
+ {AlgHandler,_SeedFun} = mk_alg(Alg),
+ {AlgHandler,AlgState};
seed_s(Alg) ->
seed_s(Alg, {erlang:phash2([{node(),self()}]),
erlang:system_time(),
@@ -259,19 +268,15 @@ seed_s(Alg) ->
%% seed/2: seeds RNG with the algorithm and given values
%% and returns the NEW state.
--spec seed(
- Alg :: builtin_alg(), Seed :: {integer(), integer(), integer()}) ->
- state().
-seed(Alg0, S0) ->
- seed_put(seed_s(Alg0, S0)).
+-spec seed(Alg :: builtin_alg(), Seed :: seed()) -> state().
+seed(Alg, Seed) ->
+ seed_put(seed_s(Alg, Seed)).
--spec seed_s(
- Alg :: builtin_alg(), Seed :: {integer(), integer(), integer()}) ->
- state().
-seed_s(Alg0, S0 = {_, _, _}) ->
- {Alg, Seed} = mk_alg(Alg0),
- AS = Seed(S0),
- {Alg, AS}.
+-spec seed_s(Alg :: builtin_alg(), Seed :: seed()) -> state().
+seed_s(Alg, Seed) ->
+ {AlgHandler,SeedFun} = mk_alg(Alg),
+ AlgState = SeedFun(Seed),
+ {AlgHandler,AlgState}.
%%% uniform/0, uniform/1, uniform_s/1, uniform_s/2 are all
%%% uniformly distributed random numbers.
@@ -281,8 +286,8 @@ seed_s(Alg0, S0 = {_, _, _}) ->
-spec uniform() -> X :: float().
uniform() ->
- {X, Seed} = uniform_s(seed_get()),
- _ = seed_put(Seed),
+ {X, State} = uniform_s(seed_get()),
+ _ = seed_put(State),
X.
%% uniform/1: given an integer N >= 1,
@@ -291,8 +296,8 @@ uniform() ->
-spec uniform(N :: pos_integer()) -> X :: pos_integer().
uniform(N) ->
- {X, Seed} = uniform_s(N, seed_get()),
- _ = seed_put(Seed),
+ {X, State} = uniform_s(N, seed_get()),
+ _ = seed_put(State),
X.
%% uniform_s/1: given a state, uniform_s/1
@@ -486,7 +491,7 @@ uniform_real_s(Alg, Next, M0, BitNo, R1, V1, Bits) ->
{M1 * math:pow(2.0, BitNo - 56), {Alg, R1}};
BitNo =:= -1008 ->
%% Endgame
- %% For the last round we can not have 14 zeros or more
+ %% For the last round we cannot have 14 zeros or more
%% at the top of M1 because then we will underflow,
%% so we need at least 43 bits
if
@@ -613,6 +618,11 @@ mk_alg(exsp) ->
uniform=>fun exsp_uniform/1, uniform_n=>fun exsp_uniform/2,
jump=>fun exsplus_jump/1},
fun exsplus_seed/1};
+mk_alg(exsss) ->
+ {#{type=>exsss, bits=>58, next=>fun exsss_next/1,
+ uniform=>fun exsss_uniform/1, uniform_n=>fun exsss_uniform/2,
+ jump=>fun exsplus_jump/1},
+ fun exsss_seed/1};
mk_alg(exs1024) ->
{#{type=>exs1024, max=>?MASK(64), next=>fun exs1024_next/1,
jump=>fun exs1024_jump/1},
@@ -625,7 +635,13 @@ mk_alg(exrop) ->
{#{type=>exrop, bits=>58, weak_low_bits=>1, next=>fun exrop_next/1,
uniform=>fun exrop_uniform/1, uniform_n=>fun exrop_uniform/2,
jump=>fun exrop_jump/1},
- fun exrop_seed/1}.
+ fun exrop_seed/1};
+mk_alg(exro928ss) ->
+ {#{type=>exro928ss, bits=>58, next=>fun exro928ss_next/1,
+ uniform=>fun exro928ss_uniform/1,
+ uniform_n=>fun exro928ss_uniform/2,
+ jump=>fun exro928_jump/1},
+ fun exro928_seed/1}.
%% =====================================================================
%% exs64 PRNG: Xorshift64*
@@ -635,6 +651,14 @@ mk_alg(exrop) ->
-opaque exs64_state() :: uint64().
+exs64_seed(L) when is_list(L) ->
+ [R] = seed64_nz(1, L),
+ R;
+exs64_seed(A) when is_integer(A) ->
+ [R] = seed64(1, ?MASK(64, A)),
+ R;
+%%
+%% Traditional integer triplet seed
exs64_seed({A1, A2, A3}) ->
{V1, _} = exs64_next((?MASK(32, A1) * 4294967197 + 1)),
{V2, _} = exs64_next((?MASK(32, A2) * 4294967231 + 1)),
@@ -656,11 +680,49 @@ exs64_next(R) ->
%% 58 bits fits into an immediate on 64bits erlang and is thus much faster.
%% Modification of the original Xorshift128+ algorithm to 116
%% by Sebastiano Vigna, a lot of thanks for his help and work.
+%%
+%% Reference C code for Xorshift116+ and Xorshift116**
+%%
+%% #include <stdint.h>
+%%
+%% #define MASK(b, v) (((UINT64_C(1) << (b)) - 1) & (v))
+%% #define BSL(b, v, n) (MASK((b)-(n), (v)) << (n))
+%% #define ROTL(b, v, n) (BSL((b), (v), (n)) | ((v) >> ((b)-(n))))
+%%
+%% uint64_t s[2];
+%%
+%% uint64_t next(void) {
+%% uint64_t s1 = s[0];
+%% const uint64_t s0 = s[1];
+%%
+%% s1 ^= BSL(58, s1, 24); // a
+%% s1 ^= s0 ^ (s1 >> 11) ^ (s0 >> 41); // b, c
+%% s[0] = s0;
+%% s[1] = s1;
+%%
+%% const uint64_t result_plus = MASK(58, s0 + s1);
+%% uint64_t result_starstar = s0;
+%% result_starstar = MASK(58, result_starstar * 5);
+%% result_starstar = ROTL(58, result_starstar, 7);
+%% result_starstar = MASK(58, result_starstar * 9);
+%%
+%% return result_plus;
+%% return result_starstar;
+%% }
+%%
%% =====================================================================
-opaque exsplus_state() :: nonempty_improper_list(uint58(), uint58()).
-dialyzer({no_improper_lists, exsplus_seed/1}).
+exsplus_seed(L) when is_list(L) ->
+ [S0,S1] = seed58_nz(2, L),
+ [S0|S1];
+exsplus_seed(X) when is_integer(X) ->
+ [S0,S1] = seed58(2, ?MASK(64, X)),
+ [S0|S1];
+%%
+%% Traditional integer triplet seed
exsplus_seed({A1, A2, A3}) ->
{_, R1} = exsplus_next(
[?MASK(58, (A1 * 4294967197) + 1)|
@@ -670,16 +732,62 @@ exsplus_seed({A1, A2, A3}) ->
tl(R1)]),
R2.
+-dialyzer({no_improper_lists, exsss_seed/1}).
+
+exsss_seed(L) when is_list(L) ->
+ [S0,S1] = seed58_nz(2, L),
+ [S0|S1];
+exsss_seed(X) when is_integer(X) ->
+ [S0,S1] = seed58(2, ?MASK(64, X)),
+ [S0|S1];
+%%
+%% Seed from traditional integer triple - mix into splitmix
+exsss_seed({A1, A2, A3}) ->
+ {_, X0} = seed58(?MASK(64, A1)),
+ {S0, X1} = seed58(?MASK(64, A2) bxor X0),
+ {S1, _} = seed58(?MASK(64, A3) bxor X1),
+ [S0|S1].
+
+%% Advance Xorshift116 state one step
+-define(
+ exs_next(S0, S1, S1_b),
+ begin
+ S1_b = S1 bxor ?BSL(58, S1, 24),
+ S1_b bxor S0 bxor (S1_b bsr 11) bxor (S0 bsr 41)
+ end).
+
+-define(
+ scramble_starstar(S, V_a, V_b),
+ begin
+ %% The multiply by add shifted trick avoids creating bignums
+ %% which improves performance significantly
+ %%
+ V_a = ?MASK(58, S + ?BSL(58, S, 2)), % V_a = S * 5
+ V_b = ?ROTL(58, V_a, 7),
+ ?MASK(58, V_b + ?BSL(58, V_b, 3)) % V_b * 9
+ end).
+
-dialyzer({no_improper_lists, exsplus_next/1}).
-%% Advance xorshift116+ state for one step and generate 58bit unsigned integer
+%% Advance state and generate 58bit unsigned integer
-spec exsplus_next(exsplus_state()) -> {uint58(), exsplus_state()}.
exsplus_next([S1|S0]) ->
%% Note: members s0 and s1 are swapped here
- S11 = S1 bxor ?BSL(58, S1, 24),
- S12 = S11 bxor S0 bxor (S11 bsr 11) bxor (S0 bsr 41),
- {?MASK(58, S0 + S12), [S0|S12]}.
+ NewS1 = ?exs_next(S0, S1, S1_1),
+ {?MASK(58, S0 + NewS1), [S0|NewS1]}.
+%% %% Note: members s0 and s1 are swapped here
+%% S11 = S1 bxor ?BSL(58, S1, 24),
+%% S12 = S11 bxor S0 bxor (S11 bsr 11) bxor (S0 bsr 41),
+%% {?MASK(58, S0 + S12), [S0|S12]}.
+
+-dialyzer({no_improper_lists, exsss_next/1}).
+-spec exsss_next(exsplus_state()) -> {uint58(), exsplus_state()}.
+exsss_next([S1|S0]) ->
+ %% Note: members s0 and s1 are swapped here
+ NewS1 = ?exs_next(S0, S1, S1_1),
+ {?scramble_starstar(S0, V_0, V_1), [S0|NewS1]}.
+%% {?MASK(58, S0 + NewS1), [S0|NewS1]}.
exsp_uniform({Alg, R0}) ->
{I, R1} = exsplus_next(R0),
@@ -687,18 +795,48 @@ exsp_uniform({Alg, R0}) ->
%% randomness quality than the others
{(I bsr (58-53)) * ?TWO_POW_MINUS53, {Alg, R1}}.
+exsss_uniform({Alg, R0}) ->
+ {I, R1} = exsss_next(R0),
+ {(I bsr (58-53)) * ?TWO_POW_MINUS53, {Alg, R1}}.
+
exsp_uniform(Range, {Alg, R}) ->
{V, R1} = exsplus_next(R),
MaxMinusRange = ?BIT(58) - Range,
?uniform_range(Range, Alg, R1, V, MaxMinusRange, I).
+exsss_uniform(Range, {Alg, R}) ->
+ {V, R1} = exsss_next(R),
+ MaxMinusRange = ?BIT(58) - Range,
+ ?uniform_range(Range, Alg, R1, V, MaxMinusRange, I).
-%% This is the jump function for the exsplus generator, equivalent
+
+%% This is the jump function for the exs* generators,
+%% i.e the Xorshift116 generators, equivalent
%% to 2^64 calls to next/1; it can be used to generate 2^52
%% non-overlapping subsequences for parallel computations.
%% Note: the jump function takes 116 times of the execution time of
%% next/1.
-
+%%
+%% #include <stdint.h>
+%%
+%% void jump(void) {
+%% static const uint64_t JUMP[] = { 0x02f8ea6bc32c797,
+%% 0x345d2a0f85f788c };
+%% int i, b;
+%% uint64_t s0 = 0;
+%% uint64_t s1 = 0;
+%% for(i = 0; i < sizeof JUMP / sizeof *JUMP; i++)
+%% for(b = 0; b < 58; b++) {
+%% if (JUMP[i] & 1ULL << b) {
+%% s0 ^= s[0];
+%% s1 ^= s[1];
+%% }
+%% next();
+%% }
+%% s[0] = s0;
+%% s[1] = s1;
+%% }
+%%
%% -define(JUMPCONST, 16#000d174a83e17de2302f8ea6bc32c797).
%% split into 58-bit chunks
%% and two iterative executions
@@ -708,7 +846,8 @@ exsp_uniform(Range, {Alg, R}) ->
-define(JUMPELEMLEN, 58).
-dialyzer({no_improper_lists, exsplus_jump/1}).
--spec exsplus_jump(state()) -> state().
+-spec exsplus_jump({alg_handler(), exsplus_state()}) ->
+ {alg_handler(), exsplus_state()}.
exsplus_jump({Alg, S}) ->
{S1, AS1} = exsplus_jump(S, [0|0], ?JUMPCONST1, ?JUMPELEMLEN),
{_, AS2} = exsplus_jump(S1, AS1, ?JUMPCONST2, ?JUMPELEMLEN),
@@ -735,6 +874,12 @@ exsplus_jump(S, [AS0|AS1], J, N) ->
-opaque exs1024_state() :: {list(uint64()), list(uint64())}.
+exs1024_seed(L) when is_list(L) ->
+ {seed64_nz(16, L), []};
+exs1024_seed(X) when is_integer(X) ->
+ {seed64(16, ?MASK(64, X)), []};
+%%
+%% Seed from traditional triple, remain backwards compatible
exs1024_seed({A1, A2, A3}) ->
B1 = ?MASK(21, (?MASK(21, A1) + 1) * 2097131),
B2 = ?MASK(21, (?MASK(21, A2) + 1) * 2097133),
@@ -806,8 +951,8 @@ exs1024_next({[H], RL}) ->
-define(JUMPTOTALLEN, 1024).
-define(RINGLEN, 16).
--spec exs1024_jump(state()) -> state().
-
+-spec exs1024_jump({alg_handler(), exs1024_state()}) ->
+ {alg_handler(), exs1024_state()}.
exs1024_jump({Alg, {L, RL}}) ->
P = length(RL),
AS = exs1024_jump({L, RL},
@@ -832,6 +977,195 @@ exs1024_jump({L, RL}, AS, JL, J, N, TN) ->
end.
%% =====================================================================
+%% exro928ss PRNG: Xoroshiro928**
+%%
+%% Reference URL: http://vigna.di.unimi.it/ftp/papers/ScrambledLinear.pdf
+%% i.e the Xoroshiro1024 generator with ** scrambler
+%% with {S, R, T} = {5, 7, 9} as recommended in the paper.
+%%
+%% {A, B, C} were tried out and selected as {44, 9, 45}
+%% and the jump coefficients calculated.
+%%
+%% Standard jump function pseudocode:
+%%
+%% Jump constant j = 0xb10773cb...44085302f77130ca
+%% Generator state: s
+%% New generator state: t = 0
+%% foreach bit in j, low to high:
+%% if the bit is one:
+%% t ^= s
+%% next s
+%% s = t
+%%
+%% Generator used for reference value calculation:
+%%
+%% #include <stdint.h>
+%% #include <stdio.h>
+%%
+%% int p = 0;
+%% uint64_t s[16];
+%%
+%% #define MASK(x) ((x) & ((UINT64_C(1) << 58) - 1))
+%% static __inline uint64_t rotl(uint64_t x, int n) {
+%% return MASK(x << n) | (x >> (58 - n));
+%% }
+%%
+%% uint64_t next() {
+%% const int q = p;
+%% const uint64_t s0 = s[p = (p + 1) & 15];
+%% uint64_t s15 = s[q];
+%%
+%% const uint64_t result_starstar = MASK(rotl(MASK(s0 * 5), 7) * 9);
+%%
+%% s15 ^= s0;
+%% s[q] = rotl(s0, 44) ^ s15 ^ MASK(s15 << 9);
+%% s[p] = rotl(s15, 45);
+%%
+%% return result_starstar;
+%% }
+%%
+%% static const uint64_t jump_2pow512[15] =
+%% { 0x44085302f77130ca, 0xba05381fdfd14902, 0x10a1de1d7d6813d2,
+%% 0xb83fe51a1eb3be19, 0xa81b0090567fd9f0, 0x5ac26d5d20f9b49f,
+%% 0x4ddd98ee4be41e01, 0x0657e19f00d4b358, 0xf02f778573cf0f0a,
+%% 0xb45a3a8a3cef3cc0, 0x6e62a33cc2323831, 0xbcb3b7c4cc049c53,
+%% 0x83f240c6007e76ce, 0xe19f5fc1a1504acd, 0x00000000b10773cb };
+%%
+%% static const uint64_t jump_2pow20[15] =
+%% { 0xbdb966a3daf905e6, 0x644807a56270cf78, 0xda90f4a806c17e9e,
+%% 0x4a426866bfad3c77, 0xaf699c306d8e7566, 0x8ebc73c700b8b091,
+%% 0xc081a7bf148531fb, 0xdc4d3af15f8a4dfd, 0x90627c014098f4b6,
+%% 0x06df2eb1feaf0fb6, 0x5bdeb1a5a90f2e6b, 0xa480c5878c3549bd,
+%% 0xff45ef33c82f3d48, 0xa30bebc15fefcc78, 0x00000000cb3d181c };
+%%
+%% void jump(const uint64_t *jump) {
+%% uint64_t j, t[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
+%% int m, n, k;
+%% for (m = 0; m < 15; m++, jump++) {
+%% for (n = 0, j = *jump; n < 64; n++, j >>= 1) {
+%% if ((j & 1) != 0) {
+%% for (k = 0; k < 16; k++) {
+%% t[k] ^= s[(p + k) & 15];
+%% }
+%% }
+%% next();
+%% }
+%% }
+%% for (k = 0; k < 16; k++) {
+%% s[(p + k) & 15] = t[k];
+%% }
+%% }
+%%
+%% =====================================================================
+
+-opaque exro928_state() :: {list(uint58()), list(uint58())}.
+
+-spec exro928_seed(
+ list(uint58()) | integer() | {integer(), integer(), integer()}) ->
+ exro928_state().
+exro928_seed(L) when is_list(L) ->
+ {seed58_nz(16, L), []};
+exro928_seed(X) when is_integer(X) ->
+ {seed58(16, ?MASK(64, X)), []};
+%%
+%% Seed from traditional integer triple - mix into splitmix
+exro928_seed({A1, A2, A3}) ->
+ {S0, X0} = seed58(?MASK(64, A1)),
+ {S1, X1} = seed58(?MASK(64, A2) bxor X0),
+ {S2, X2} = seed58(?MASK(64, A3) bxor X1),
+ {[S0,S1,S2|seed58(13, X2)], []}.
+
+
+%% Update the state and calculate output word
+-spec exro928ss_next(exro928_state()) -> {uint58(), exro928_state()}.
+exro928ss_next({[S15,S0|Ss], Rs}) ->
+ SR = exro928_next_state(Ss, Rs, S15, S0),
+ %%
+ %% {S, R, T} = {5, 7, 9}
+ %% const uint64_t result_starstar = rotl(s0 * S, R) * T;
+ %%
+ {?scramble_starstar(S0, V_0, V_1), SR};
+%% %% The multiply by add shifted trick avoids creating bignums
+%% %% which improves performance significantly
+%% %%
+%% V0 = ?MASK(58, S0 + ?BSL(58, S0, 2)), % V0 = S0 * 5
+%% V1 = ?ROTL(58, V0, 7),
+%% V = ?MASK(58, V1 + ?BSL(58, V1, 3)), % V = V1 * 9
+%% {V, SR};
+exro928ss_next({[S15], Rs}) ->
+ exro928ss_next({[S15|lists:reverse(Rs)], []}).
+
+-spec exro928_next(exro928_state()) -> {{uint58(),uint58()}, exro928_state()}.
+exro928_next({[S15,S0|Ss], Rs}) ->
+ SR = exro928_next_state(Ss, Rs, S15, S0),
+ {{S15,S0}, SR};
+exro928_next({[S15], Rs}) ->
+ exro928_next({[S15|lists:reverse(Rs)], []}).
+
+%% Just update the state
+-spec exro928_next_state(exro928_state()) -> exro928_state().
+exro928_next_state({[S15,S0|Ss], Rs}) ->
+ exro928_next_state(Ss, Rs, S15, S0);
+exro928_next_state({[S15], Rs}) ->
+ [S0|Ss] = lists:reverse(Rs),
+ exro928_next_state(Ss, [], S15, S0).
+
+exro928_next_state(Ss, Rs, S15, S0) ->
+ %% {A, B, C} = {44, 9, 45},
+ %% s15 ^= s0;
+ %% NewS15: s[q] = rotl(s0, A) ^ s15 ^ (s15 << B);
+ %% NewS0: s[p] = rotl(s15, C);
+ %%
+ Q = S15 bxor S0,
+ NewS15 = ?ROTL(58, S0, 44) bxor Q bxor ?BSL(58, Q, 9),
+ NewS0 = ?ROTL(58, Q, 45),
+ {[NewS0|Ss], [NewS15|Rs]}.
+
+
+exro928ss_uniform({Alg, SR}) ->
+ {V, NewSR} = exro928ss_next(SR),
+ {(V bsr (58-53)) * ?TWO_POW_MINUS53, {Alg, NewSR}}.
+
+exro928ss_uniform(Range, {Alg, SR}) ->
+ {V, NewSR} = exro928ss_next(SR),
+ MaxMinusRange = ?BIT(58) - Range,
+ ?uniform_range(Range, Alg, NewSR, V, MaxMinusRange, I).
+
+
+-spec exro928_jump({alg_handler(), exro928_state()}) ->
+ {alg_handler(), exro928_state()}.
+exro928_jump({Alg, SR}) ->
+ {Alg,exro928_jump_2pow512(SR)}.
+
+-spec exro928_jump_2pow512(exro928_state()) -> exro928_state().
+exro928_jump_2pow512(SR) ->
+ polyjump(
+ SR, fun exro928_next_state/1,
+ %% 2^512
+ [16#4085302F77130CA, 16#54E07F7F4524091,
+ 16#5E1D7D6813D2BA0, 16#4687ACEF8644287,
+ 16#4567FD9F0B83FE5, 16#43E6D27EA06C024,
+ 16#641E015AC26D5D2, 16#6CD61377663B92F,
+ 16#70A0657E19F00D4, 16#43C0BDDE15CF3C3,
+ 16#745A3A8A3CEF3CC, 16#58A8CF308C8E0C6,
+ 16#7B7C4CC049C536E, 16#431801F9DB3AF2C,
+ 16#41A1504ACD83F24, 16#6C41DCF2F867D7F]).
+
+-spec exro928_jump_2pow20(exro928_state()) -> exro928_state().
+exro928_jump_2pow20(SR) ->
+ polyjump(
+ SR, fun exro928_next_state/1,
+ %% 2^20
+ [16#5B966A3DAF905E6, 16#601E9589C33DE2F,
+ 16#74A806C17E9E644, 16#59AFEB4F1DF6A43,
+ 16#46D8E75664A4268, 16#42E2C246BDA670C,
+ 16#4531FB8EBC73C70, 16#537F702069EFC52,
+ 16#4B6DC4D3AF15F8A, 16#5A4189F0050263D,
+ 16#46DF2EB1FEAF0FB, 16#77AC696A43CB9AC,
+ 16#4C5878C3549BD5B, 16#7CCF20BCF522920,
+ 16#415FEFCC78FF45E, 16#72CF460728C2FAF]).
+
+%% =====================================================================
%% exrop PRNG: Xoroshiro116+
%%
%% Reference URL: http://xorshift.di.unimi.it/
@@ -899,6 +1233,15 @@ exs1024_jump({L, RL}, AS, JL, J, N, TN) ->
-opaque exrop_state() :: nonempty_improper_list(uint58(), uint58()).
-dialyzer({no_improper_lists, exrop_seed/1}).
+
+exrop_seed(L) when is_list(L) ->
+ [S0,S1] = seed58_nz(2, L),
+ [S0|S1];
+exrop_seed(X) when is_integer(X) ->
+ [S0,S1] = seed58(2, ?MASK(64, X)),
+ [S0|S1];
+%%
+%% Traditional integer triplet seed
exrop_seed({A1, A2, A3}) ->
[_|S1] =
exrop_next_s(
@@ -962,6 +1305,142 @@ exrop_jump([S__0|S__1] = _S, S0, S1, J, Js) ->
end.
%% =====================================================================
+%% Mask and fill state list, ensure not all zeros
+%% =====================================================================
+
+seed58_nz(N, Ss) ->
+ seed_nz(N, Ss, 58, false).
+
+seed64_nz(N, Ss) ->
+ seed_nz(N, Ss, 64, false).
+
+seed_nz(_N, [], _M, false) ->
+ erlang:error(zero_seed);
+seed_nz(0, [_|_], _M, _NZ) ->
+ erlang:error(too_many_seed_integers);
+seed_nz(0, [], _M, _NZ) ->
+ [];
+seed_nz(N, [], M, true) ->
+ [0|seed_nz(N - 1, [], M, true)];
+seed_nz(N, [S|Ss], M, NZ) ->
+ if
+ is_integer(S) ->
+ R = ?MASK(M, S),
+ [R|seed_nz(N - 1, Ss, M, NZ orelse R =/= 0)];
+ true ->
+ erlang:error(non_integer_seed)
+ end.
+
+%% =====================================================================
+%% Splitmix seeders, lowest bits of SplitMix64, zeros skipped
+%% =====================================================================
+
+-spec seed58(non_neg_integer(), uint64()) -> list(uint58()).
+seed58(0, _X) ->
+ [];
+seed58(N, X) ->
+ {Z,NewX} = seed58(X),
+ [Z|seed58(N - 1, NewX)].
+%%
+seed58(X_0) ->
+ {Z0,X} = splitmix64_next(X_0),
+ case ?MASK(58, Z0) of
+ 0 ->
+ seed58(X);
+ Z ->
+ {Z,X}
+ end.
+
+-spec seed64(non_neg_integer(), uint64()) -> list(uint64()).
+seed64(0, _X) ->
+ [];
+seed64(N, X) ->
+ {Z,NewX} = seed64(X),
+ [Z|seed64(N - 1, NewX)].
+%%
+seed64(X_0) ->
+ {Z,X} = ZX = splitmix64_next(X_0),
+ if
+ Z =:= 0 ->
+ seed64(X);
+ true ->
+ ZX
+ end.
+
+%% The SplitMix64 generator:
+%%
+%% uint64_t splitmix64_next() {
+%% uint64_t z = (x += 0x9e3779b97f4a7c15);
+%% z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
+%% z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
+%% return z ^ (z >> 31);
+%% }
+%%
+splitmix64_next(X_0) ->
+ X = ?MASK(64, X_0 + 16#9e3779b97f4a7c15),
+ Z_0 = ?MASK(64, (X bxor (X bsr 30)) * 16#bf58476d1ce4e5b9),
+ Z_1 = ?MASK(64, (Z_0 bxor (Z_0 bsr 27)) * 16#94d049bb133111eb),
+ {?MASK(64, Z_1 bxor (Z_1 bsr 31)),X}.
+
+%% =====================================================================
+%% Polynomial jump with a jump constant word list,
+%% high bit in each word marking top of word,
+%% SR is a {Forward, Reverse} queue tuple with Forward never empty
+%% =====================================================================
+
+polyjump({Ss, Rs} = SR, NextState, JumpConst) ->
+ %% Create new state accumulator T
+ Ts = lists:duplicate(length(Ss) + length(Rs), 0),
+ polyjump(SR, NextState, JumpConst, Ts).
+%%
+%% Foreach jump word
+polyjump(_SR, _NextState, [], Ts) ->
+ %% Return new calculated state
+ {Ts, []};
+polyjump(SR, NextState, [J|Js], Ts) ->
+ polyjump(SR, NextState, Js, Ts, J).
+%%
+%% Foreach bit in jump word until top bit
+polyjump(SR, NextState, Js, Ts, 1) ->
+ polyjump(SR, NextState, Js, Ts);
+polyjump({Ss, Rs} = SR, NextState, Js, Ts, J) when J =/= 0 ->
+ NewSR = NextState(SR),
+ NewJ = J bsr 1,
+ case ?MASK(1, J) of
+ 0 ->
+ polyjump(NewSR, NextState, Js, Ts, NewJ);
+ 1 ->
+ %% Xor this state onto T
+ polyjump(NewSR, NextState, Js, xorzip_sr(Ts, Ss, Rs), NewJ)
+ end.
+
+xorzip_sr([], [], undefined) ->
+ [];
+xorzip_sr(Ts, [], Rs) ->
+ xorzip_sr(Ts, lists:reverse(Rs), undefined);
+xorzip_sr([T|Ts], [S|Ss], Rs) ->
+ [T bxor S|xorzip_sr(Ts, Ss, Rs)].
+
+%% =====================================================================
+
+format_jumpconst58(String) ->
+ ReOpts = [{newline,any},{capture,all_but_first,binary},global],
+ {match,Matches} = re:run(String, "0x([a-zA-Z0-9]+)", ReOpts),
+ format_jumcons58_matches(lists:reverse(Matches), 0).
+
+format_jumcons58_matches([], J) ->
+ format_jumpconst58_value(J);
+format_jumcons58_matches([[Bin]|Matches], J) ->
+ NewJ = (J bsl 64) bor binary_to_integer(Bin, 16),
+ format_jumcons58_matches(Matches, NewJ).
+
+format_jumpconst58_value(0) ->
+ ok;
+format_jumpconst58_value(J) ->
+ io:format("16#~s,~n", [integer_to_list(?MASK(58, J) bor ?BIT(58), 16)]),
+ format_jumpconst58_value(J bsr 58).
+
+%% =====================================================================
%% Ziggurat cont
%% =====================================================================
-define(NOR_R, 3.6541528853610087963519472518).
diff --git a/lib/stdlib/src/slave.erl b/lib/stdlib/src/slave.erl
index 5e8c1a43ea..4791e3ebda 100644
--- a/lib/stdlib/src/slave.erl
+++ b/lib/stdlib/src/slave.erl
@@ -104,18 +104,17 @@ relay1(Pid) ->
%% this to work is that the 'erl' program can be found in PATH.
%%
%% If the master and slave are on different hosts, start/N uses
-%% the 'rsh' program to spawn an Erlang node on the other host.
+%% the 'ssh' program to spawn an Erlang node on the other host.
%% Alternative, if the master was started as
%% 'erl -sname xxx -rsh my_rsh...', then 'my_rsh' will be used instead
-%% of 'rsh' (this is useful for systems where the rsh program is named
-%% 'remsh').
+%% of 'ssh' (this is useful for systems still using rsh or remsh).
%%
%% For this to work, the following conditions must be fulfilled:
%%
-%% 1. There must be an Rsh program on computer; if not an error
+%% 1. There must be an ssh program on computer; if not an error
%% is returned.
%%
-%% 2. The hosts must be configured to allowed 'rsh' access without
+%% 2. The hosts must be configured to allow 'ssh' access without
%% prompts for password.
%%
%% The slave node will have its filer and user server redirected
@@ -286,7 +285,7 @@ register_unique_name(Number) ->
%% Makes up the command to start the nodes.
%% If the node should run on the local host, there is
-%% no need to use rsh.
+%% no need to use a remote shell.
mk_cmd(Host, Name, Args, Waiter, Prog0) ->
Prog = quote_progname(Prog0),
@@ -342,9 +341,7 @@ do_quote_progname([Prog,Arg|Args]) ->
lists:flatten(lists:map(fun(X) -> [" ",X] end, [Arg|Args]))
end.
-%% Give the user an opportunity to run another program,
-%% than the "rsh". On HP-UX rsh is called remsh; thus HP users
-%% must start erlang as erl -rsh remsh.
+%% Give the user an opportunity to run another program than "ssh".
%%
%% Also checks that the given program exists.
%%
@@ -354,7 +351,7 @@ rsh() ->
Rsh =
case init:get_argument(rsh) of
{ok, [[Prog]]} -> Prog;
- _ -> "rsh"
+ _ -> "ssh"
end,
case os:find_executable(Rsh) of
false -> {error, no_rsh};
diff --git a/lib/stdlib/src/stdlib.app.src b/lib/stdlib/src/stdlib.app.src
index cd09872b87..9cd425db9a 100644
--- a/lib/stdlib/src/stdlib.app.src
+++ b/lib/stdlib/src/stdlib.app.src
@@ -108,7 +108,7 @@
dets]},
{applications, [kernel]},
{env, []},
- {runtime_dependencies, ["sasl-3.0","kernel-6.0","erts-10.0","crypto-3.3",
+ {runtime_dependencies, ["sasl-3.0","kernel-6.0","erts-@OTP-15128@","crypto-3.3",
"compiler-5.0"]}
]}.
diff --git a/lib/stdlib/src/stdlib.appup.src b/lib/stdlib/src/stdlib.appup.src
index 2a324aef82..9e5d6a3bd8 100644
--- a/lib/stdlib/src/stdlib.appup.src
+++ b/lib/stdlib/src/stdlib.appup.src
@@ -38,7 +38,9 @@
{<<"^3\\.5\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^3\\.6$">>,[restart_new_emulator]},
- {<<"^3\\.6\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}],
+ {<<"^3\\.6\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.7$">>,[restart_new_emulator]},
+ {<<"^3\\.7\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}],
[{<<"^3\\.4$">>,[restart_new_emulator]},
{<<"^3\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
@@ -50,4 +52,6 @@
{<<"^3\\.5\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^3\\.6$">>,[restart_new_emulator]},
- {<<"^3\\.6\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}]}.
+ {<<"^3\\.6\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.7$">>,[restart_new_emulator]},
+ {<<"^3\\.7\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}]}.
diff --git a/lib/stdlib/src/sys.erl b/lib/stdlib/src/sys.erl
index 0064414d6f..6ff9aa33b4 100644
--- a/lib/stdlib/src/sys.erl
+++ b/lib/stdlib/src/sys.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All 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,22 +30,30 @@
log_to_file/2, log_to_file/3, no_debug/1, no_debug/2,
install/2, install/3, remove/2, remove/3]).
-export([handle_system_msg/6, handle_system_msg/7, handle_debug/4,
- print_log/1, get_debug/3, debug_options/1, suspend_loop_hib/6]).
+ print_log/1, get_log/1, get_debug/3, debug_options/1, suspend_loop_hib/6]).
+-deprecated([{get_debug,3,eventually}]).
%%-----------------------------------------------------------------
%% Types
%%-----------------------------------------------------------------
--export_type([dbg_opt/0]).
+-export_type([dbg_opt/0, dbg_fun/0, debug_option/0]).
-type name() :: pid() | atom()
| {'global', term()}
| {'via', module(), term()}.
-type system_event() :: {'in', Msg :: _}
- | {'in', Msg :: _, From :: _}
+ | {'in', Msg :: _, State :: _}
| {'out', Msg :: _, To :: _}
| {'out', Msg :: _, To :: _, State :: _}
- | term().
+ | {'noreply', State :: _}
+ | {'continue', Continuation :: _}
+ | {'code_change', Event :: _, State :: _}
+ | {'postpone', Event :: _, State :: _, NextState :: _}
+ | {'consume', Event :: _, State :: _, NextState :: _}
+ | {'enter', State :: _}
+ | {'terminate', Reason :: _, State :: _}
+ | term().
-opaque dbg_opt() :: {'trace', 'true'}
| {'log',
{N :: non_neg_integer(),
@@ -67,6 +75,16 @@
Event :: system_event(),
Extra :: term()) -> any()).
+-type debug_option() ::
+ 'trace'
+ | 'log'
+ | {'log', N :: pos_integer()}
+ | 'statistics'
+ | {'log_to_file', FileName :: file:name()}
+ | {'install',
+ {Func :: dbg_fun(), FuncState :: term()}
+ | {FuncId :: term(), Func :: dbg_fun(), FuncState :: term()}}.
+
%%-----------------------------------------------------------------
%% System messages
%%-----------------------------------------------------------------
@@ -385,31 +403,41 @@ handle_system_msg(SysState, Msg, From, Parent, Mod, Debug, Misc, Hib) ->
FormFunc :: format_fun(),
Extra :: term(),
Event :: system_event().
-handle_debug([{trace, true} | T], FormFunc, State, Event) ->
+handle_debug([{trace, true} = DbgOpt | T], FormFunc, State, Event) ->
print_event({Event, State, FormFunc}),
- [{trace, true} | handle_debug(T, FormFunc, State, Event)];
-handle_debug([{log, {N, LogData}} | T], FormFunc, State, Event) ->
- NLogData = [{Event, State, FormFunc} | trim(N, LogData)],
- [{log, {N, NLogData}} | handle_debug(T, FormFunc, State, Event)];
-handle_debug([{log_to_file, Fd} | T], FormFunc, State, Event) ->
+ [DbgOpt | handle_debug(T, FormFunc, State, Event)];
+handle_debug([{log, NLog} | T], FormFunc, State, Event) ->
+ Item = {Event, State, FormFunc},
+ [{log, nlog_put(Item, NLog)} | handle_debug(T, FormFunc, State, Event)];
+handle_debug([{log_to_file, Fd} = DbgOpt | T], FormFunc, State, Event) ->
print_event(Fd, {Event, State, FormFunc}),
- [{log_to_file, Fd} | handle_debug(T, FormFunc, State, Event)];
+ [DbgOpt | handle_debug(T, FormFunc, State, Event)];
handle_debug([{statistics, StatData} | T], FormFunc, State, Event) ->
NStatData = stat(Event, StatData),
[{statistics, NStatData} | handle_debug(T, FormFunc, State, Event)];
handle_debug([{FuncId, {Func, FuncState}} | T], FormFunc, State, Event) ->
- case catch Func(FuncState, Event, State) of
+ try Func(FuncState, Event, State) of
+ done -> handle_debug(T, FormFunc, State, Event);
+ NFuncState ->
+ [{FuncId, {Func, NFuncState}} |
+ handle_debug(T, FormFunc, State, Event)]
+ catch
done -> handle_debug(T, FormFunc, State, Event);
- {'EXIT', _} -> handle_debug(T, FormFunc, State, Event);
NFuncState ->
- [{FuncId, {Func, NFuncState}} | handle_debug(T, FormFunc, State, Event)]
+ [{FuncId, {Func, NFuncState}} |
+ handle_debug(T, FormFunc, State, Event)];
+ _:_ -> handle_debug(T, FormFunc, State, Event)
end;
handle_debug([{Func, FuncState} | T], FormFunc, State, Event) ->
- case catch Func(FuncState, Event, State) of
+ try Func(FuncState, Event, State) of
done -> handle_debug(T, FormFunc, State, Event);
- {'EXIT', _} -> handle_debug(T, FormFunc, State, Event);
- NFuncState ->
+ NFuncState ->
[{Func, NFuncState} | handle_debug(T, FormFunc, State, Event)]
+ catch
+ done -> handle_debug(T, FormFunc, State, Event);
+ NFuncState ->
+ [{Func, NFuncState} | handle_debug(T, FormFunc, State, Event)];
+ _:_ -> handle_debug(T, FormFunc, State, Event)
end;
handle_debug([], _FormFunc, _State, _Event) ->
[].
@@ -526,19 +554,19 @@ debug_cmd({trace, true}, Debug) ->
debug_cmd({trace, false}, Debug) ->
{ok, remove_debug(trace, Debug)};
debug_cmd({log, true}, Debug) ->
- {_N, Logs} = get_debug(log, Debug, {0, []}),
- {ok, install_debug(log, {10, trim(10, Logs)}, Debug)};
-debug_cmd({log, {true, N}}, Debug) when is_integer(N), N > 0 ->
- {_N, Logs} = get_debug(log, Debug, {0, []}),
- {ok, install_debug(log, {N, trim(N, Logs)}, Debug)};
+ NLog = get_debug(log, Debug, nlog_new()),
+ {ok, install_debug(log, nlog_new(NLog), Debug)};
+debug_cmd({log, {true, N}}, Debug) when is_integer(N), 1 =< N ->
+ NLog = get_debug(log, Debug, nlog_new(N)),
+ {ok, install_debug(log, nlog_new(N, NLog), Debug)};
debug_cmd({log, false}, Debug) ->
{ok, remove_debug(log, Debug)};
debug_cmd({log, print}, Debug) ->
print_log(Debug),
{ok, Debug};
debug_cmd({log, get}, Debug) ->
- {_N, Logs} = get_debug(log, Debug, {0, []}),
- {{ok, lists:reverse(Logs)}, Debug};
+ NLog = get_debug(log, Debug, nlog_new()),
+ {{ok, [Event || {Event, _State, _FormFunc} <- nlog_get(NLog)]}, Debug};
debug_cmd({log_to_file, false}, Debug) ->
NDebug = close_log_file(Debug),
{ok, NDebug};
@@ -595,9 +623,6 @@ stat({out, _Msg, _To}, {Time, Reds, In, Out}) -> {Time, Reds, In, Out+1};
stat({out, _Msg, _To, _State}, {Time, Reds, In, Out}) -> {Time, Reds, In, Out+1};
stat(_, StatData) -> StatData.
-trim(N, LogData) ->
- lists:sublist(LogData, 1, N-1).
-
%%-----------------------------------------------------------------
%% Debug structure manipulating functions
%%-----------------------------------------------------------------
@@ -625,9 +650,14 @@ get_debug2(Item, Debug, Default) ->
-spec print_log(Debug) -> 'ok' when
Debug :: [dbg_opt()].
print_log(Debug) ->
- {_N, Logs} = get_debug(log, Debug, {0, []}),
- lists:foreach(fun print_event/1,
- lists:reverse(Logs)).
+ NLog = get_debug(log, Debug, nlog_new()),
+ lists:foreach(fun print_event/1, nlog_get(NLog)).
+
+-spec get_log(Debug) -> [system_event()] when
+ Debug :: [dbg_opt()].
+get_log(Debug) ->
+ NLog = get_debug(log, Debug, nlog_new()),
+ [Event || {Event, _State, _FormFunc} <- nlog_get(NLog)].
close_log_file(Debug) ->
case get_debug2(log_to_file, Debug, []) of
@@ -639,6 +669,74 @@ close_log_file(Debug) ->
end.
%%-----------------------------------------------------------------
+%% Keep the last N Log functions
+%%-----------------------------------------------------------------
+%%
+%% Streamlined Okasaki queue as base for "keep the last N" log.
+%%
+%% To the reverse list head we cons new items.
+%% The forward list contains elements in insertion order,
+%% so the head is the oldest and the one to drop off
+%% when the log is full.
+%%
+%% Here is how we can get away with only using one cons cell
+%% to wrap the forward and reverse list, and the log size:
+%%
+%% A full log does not need a counter; we just cons one
+%% and drop one:
+%%
+%% [ReverseList|ForwardList]
+%%
+%% A non-full log is filling up to N elements;
+%% use a down counter instead of a list as first element:
+%%
+%% [RemainingToFullCount|ReverseList]
+
+nlog_new() ->
+ nlog_new(10).
+%%
+nlog_new([_|_] = NLog) ->
+ nlog_new(10, NLog);
+nlog_new(N) ->
+ [N]. % Empty log size N >= 1
+%%
+nlog_new(N, NLog) ->
+ lists:foldl(
+ fun (Item, NL) -> nlog_put(Item, NL) end,
+ nlog_new(N),
+ nlog_get(NLog)).
+
+%%
+nlog_put(Item, NLog) ->
+ case NLog of
+ [R|FF] when is_list(R) ->
+ %% Full log
+ case FF of
+ [_|F] ->
+ %% Cons to reverse list, drop from forward list
+ [[Item|R]|F];
+ [] ->
+ %% Create new forward list from reverse list,
+ %% create new empty reverse list
+ [_|F] = lists:reverse(R, [Item]),
+ [[]|F]
+ end;
+ [1|R] ->
+ %% Log now gets full
+ [[Item|R]];
+ [J|R] ->
+ %% Filling up to N elements
+ [J - 1,Item|R]
+ end.
+
+nlog_get([[]|F]) ->
+ F;
+nlog_get([[_|_] = R|F]) ->
+ F ++ lists:reverse(R);
+nlog_get([_J|R]) ->
+ lists:reverse(R).
+
+%%-----------------------------------------------------------------
%% Func: debug_options/1
%% Purpose: Initiate a debug structure. Called by a process that
%% wishes to initiate the debug structure without the
@@ -646,28 +744,16 @@ close_log_file(Debug) ->
%% Returns: [debug_opts()]
%%-----------------------------------------------------------------
--spec debug_options(Options) -> [dbg_opt()] when
- Options :: [Opt],
- Opt :: 'trace'
- | 'log'
- | {'log', pos_integer()}
- | 'statistics'
- | {'log_to_file', FileName}
- | {'install', FuncSpec},
- FileName :: file:name(),
- FuncSpec :: {Func, FuncState} | {FuncId, Func, FuncState},
- FuncId :: term(),
- Func :: dbg_fun(),
- FuncState :: term().
+-spec debug_options([Opt :: debug_option()]) -> [dbg_opt()].
debug_options(Options) ->
debug_options(Options, []).
debug_options([trace | T], Debug) ->
debug_options(T, install_debug(trace, true, Debug));
debug_options([log | T], Debug) ->
- debug_options(T, install_debug(log, {10, []}, Debug));
+ debug_options(T, install_debug(log, nlog_new(), Debug));
debug_options([{log, N} | T], Debug) when is_integer(N), N > 0 ->
- debug_options(T, install_debug(log, {N, []}, Debug));
+ debug_options(T, install_debug(log, nlog_new(N), Debug));
debug_options([statistics | T], Debug) ->
debug_options(T, install_debug(statistics, init_stat(), Debug));
debug_options([{log_to_file, FileName} | T], Debug) ->
diff --git a/lib/stdlib/src/uri_string.erl b/lib/stdlib/src/uri_string.erl
index f07307c039..d33dc89af8 100644
--- a/lib/stdlib/src/uri_string.erl
+++ b/lib/stdlib/src/uri_string.erl
@@ -415,7 +415,7 @@ transcode(URIString, Options) when is_list(URIString) ->
%% (application/x-www-form-urlencoded encoding algorithm)
%%-------------------------------------------------------------------------
-spec compose_query(QueryList) -> QueryString when
- QueryList :: [{unicode:chardata(), unicode:chardata()}],
+ QueryList :: [{unicode:chardata(), unicode:chardata() | true}],
QueryString :: uri_string()
| error().
compose_query(List) ->
@@ -423,7 +423,7 @@ compose_query(List) ->
-spec compose_query(QueryList, Options) -> QueryString when
- QueryList :: [{unicode:chardata(), unicode:chardata()}],
+ QueryList :: [{unicode:chardata(), unicode:chardata() | true}],
Options :: [{encoding, atom()}],
QueryString :: uri_string()
| error().
@@ -435,6 +435,11 @@ compose_query(List, Options) ->
throw:{error, Atom, RestData} -> {error, Atom, RestData}
end.
%%
+compose_query([{Key,true}|Rest], Options, IsList, Acc) ->
+ Separator = get_separator(Rest),
+ K = form_urlencode(Key, Options),
+ IsListNew = IsList orelse is_list(Key),
+ compose_query(Rest, Options, IsListNew, <<Acc/binary,K/binary,Separator/binary>>);
compose_query([{Key,Value}|Rest], Options, IsList, Acc) ->
Separator = get_separator(Rest),
K = form_urlencode(Key, Options),
@@ -454,7 +459,7 @@ compose_query([], _Options, IsList, Acc) ->
%%-------------------------------------------------------------------------
-spec dissect_query(QueryString) -> QueryList when
QueryString :: uri_string(),
- QueryList :: [{unicode:chardata(), unicode:chardata()}]
+ QueryList :: [{unicode:chardata(), unicode:chardata() | true}]
| error().
dissect_query(<<>>) ->
[];
@@ -1889,13 +1894,12 @@ dissect_query_key(<<$=,T/binary>>, IsList, Acc, Key, Value) ->
dissect_query_value(T, IsList, Acc, Key, Value);
dissect_query_key(<<"&#",T/binary>>, IsList, Acc, Key, Value) ->
dissect_query_key(T, IsList, Acc, <<Key/binary,"&#">>, Value);
-dissect_query_key(<<$&,_T/binary>>, _IsList, _Acc, _Key, _Value) ->
- throw({error, missing_value, "&"});
+dissect_query_key(T = <<$&,_/binary>>, IsList, Acc, Key, <<>>) ->
+ dissect_query_value(T, IsList, Acc, Key, true);
dissect_query_key(<<H,T/binary>>, IsList, Acc, Key, Value) ->
dissect_query_key(T, IsList, Acc, <<Key/binary,H>>, Value);
-dissect_query_key(B, _, _, _, _) ->
- throw({error, missing_value, B}).
-
+dissect_query_key(T = <<>>, IsList, Acc, Key, <<>>) ->
+ dissect_query_value(T, IsList, Acc, Key, true).
dissect_query_value(<<$&,T/binary>>, IsList, Acc, Key, Value) ->
K = form_urldecode(IsList, Key),
@@ -1908,9 +1912,10 @@ dissect_query_value(<<>>, IsList, Acc, Key, Value) ->
V = form_urldecode(IsList, Value),
lists:reverse([{K,V}|Acc]).
-
%% HTML 5.2 - 4.10.21.6 URL-encoded form data - WHATWG URL (10 Jan 2018) - UTF-8
%% HTML 5.0 - 4.10.22.6 URL-encoded form data - decoding (non UTF-8)
+form_urldecode(_, true) ->
+ true;
form_urldecode(true, B) ->
Result = base10_decode(form_urldecode(B, <<>>)),
convert_to_list(Result, utf8);
diff --git a/lib/stdlib/test/Makefile b/lib/stdlib/test/Makefile
index bbe3cefa42..712b1b92fb 100644
--- a/lib/stdlib/test/Makefile
+++ b/lib/stdlib/test/Makefile
@@ -99,11 +99,9 @@ MODULES= \
maps_SUITE \
zzz_SUITE
-ERL_FILES= $(MODULES:%=%.erl)
+ERTS_MODULES= erts_test_utils
-TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
-
-INSTALL_PROGS= $(TARGET_FILES)
+ERL_FILES= $(MODULES:%=%.erl) $(ERTS_MODULES:%=$(ERL_TOP)/erts/emulator/test/%.erl)
# ----------------------------------------------------
# Release directory specification
@@ -128,7 +126,7 @@ COVERFILE=stdlib.cover
# ----------------------------------------------------
make_emakefile:
- $(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES) \
+ $(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES) $(ERTS_MODULES) \
> $(EMAKEFILE)
tests debug opt: make_emakefile
diff --git a/lib/stdlib/test/binary_module_SUITE.erl b/lib/stdlib/test/binary_module_SUITE.erl
index c5cfea5e9e..9b2033ec4a 100644
--- a/lib/stdlib/test/binary_module_SUITE.erl
+++ b/lib/stdlib/test/binary_module_SUITE.erl
@@ -22,7 +22,8 @@
-export([all/0, suite/0,
interesting/1,scope_return/1,random_ref_comp/1,random_ref_sr_comp/1,
random_ref_fla_comp/1,parts/1, bin_to_list/1, list_to_bin/1,
- copy/1, referenced/1,guard/1,encode_decode/1,badargs/1,longest_common_trap/1]).
+ copy/1, referenced/1,guard/1,encode_decode/1,badargs/1,longest_common_trap/1,
+ check_no_invalid_read_bug/1]).
-export([random_number/1, make_unaligned/1]).
@@ -36,7 +37,7 @@ all() ->
[scope_return,interesting, random_ref_fla_comp, random_ref_sr_comp,
random_ref_comp, parts, bin_to_list, list_to_bin, copy,
referenced, guard, encode_decode, badargs,
- longest_common_trap].
+ longest_common_trap, check_no_invalid_read_bug].
-define(MASK_ERROR(EXPR),mask_error((catch (EXPR)))).
@@ -754,8 +755,9 @@ list_to_bin(Config) when is_list(Config) ->
copy(Config) when is_list(Config) ->
<<1,2,3>> = binary:copy(<<1,2,3>>),
RS = random_string({1,10000}),
- RS = RS2 = binary:copy(RS),
- false = erts_debug:same(RS,RS2),
+ RS2 = binary:copy(RS),
+ true = RS =:= RS2,
+ false = erts_debug:same(RS, RS2),
<<>> = ?MASK_ERROR(binary:copy(<<1,2,3>>,0)),
badarg = ?MASK_ERROR(binary:copy(<<1,2,3:3>>,2)),
badarg = ?MASK_ERROR(binary:copy([],0)),
@@ -1361,3 +1363,13 @@ make_unaligned2(Bin0) when is_binary(Bin0) ->
Bin.
id(I) -> I.
+
+check_no_invalid_read_bug(Config) when is_list(Config) ->
+ check_no_invalid_read_bug(24);
+check_no_invalid_read_bug(60) ->
+ ok;
+check_no_invalid_read_bug(I) ->
+ N = 1 bsl I,
+ binary:encode_unsigned(N+N),
+ binary:encode_unsigned(N+N, little),
+ check_no_invalid_read_bug(I+1).
diff --git a/lib/stdlib/test/calendar_SUITE.erl b/lib/stdlib/test/calendar_SUITE.erl
index df62c0921d..224c0d5625 100644
--- a/lib/stdlib/test/calendar_SUITE.erl
+++ b/lib/stdlib/test/calendar_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2019. All 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 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
gregorian_days/1,
+ big_gregorian_days/1,
gregorian_seconds/1,
day_of_the_week/1,
day_of_the_week_calibrate/1,
@@ -36,13 +37,16 @@
-define(START_YEAR, 1947).
-define(END_YEAR, 2012).
+-define(BIG_START_YEAR, 20000000).
+-define(BIG_END_YEAR, 20000020).
+
suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[gregorian_days, gregorian_seconds, day_of_the_week,
day_of_the_week_calibrate, leap_years,
last_day_of_the_month, local_time_to_universal_time_dst,
- iso_week_number, system_time, rfc3339].
+ iso_week_number, system_time, rfc3339, big_gregorian_days].
groups() ->
[].
@@ -67,6 +71,14 @@ gregorian_days(Config) when is_list(Config) ->
MaxDays = calendar:date_to_gregorian_days({?END_YEAR, 1, 1}),
check_gregorian_days(Days, MaxDays).
+%% Tests that date_to_gregorian_days and gregorian_days_to_date
+%% are each others inverses from ?BIG_START_YEAR-01-01 up to ?BIG_END_YEAR-01-01.
+%% At the same time valid_date is tested.
+big_gregorian_days(Config) when is_list(Config) ->
+ Days = calendar:date_to_gregorian_days({?BIG_START_YEAR, 1, 1}),
+ MaxDays = calendar:date_to_gregorian_days({?BIG_END_YEAR, 1, 1}),
+ check_gregorian_days(Days, MaxDays).
+
%% Tests that datetime_to_gregorian_seconds and
%% gregorian_seconds_to_date are each others inverses for a sampled
%% number of seconds from ?START_YEAR-01-01 up to ?END_YEAR-01-01: We check
diff --git a/lib/stdlib/test/epp_SUITE.erl b/lib/stdlib/test/epp_SUITE.erl
index 0ac99ad03a..a90beed4f3 100644
--- a/lib/stdlib/test/epp_SUITE.erl
+++ b/lib/stdlib/test/epp_SUITE.erl
@@ -1372,7 +1372,7 @@ otp_8562(Config) when is_list(Config) ->
otp_8911(Config) when is_list(Config) ->
case test_server:is_cover() of
true ->
- {skip, "Testing cover, so can not run when cover is already running"};
+ {skip, "Testing cover, so cannot run when cover is already running"};
false ->
do_otp_8911(Config)
end.
diff --git a/lib/stdlib/test/erl_eval_SUITE.erl b/lib/stdlib/test/erl_eval_SUITE.erl
index f4019d477b..2436c8091c 100644
--- a/lib/stdlib/test/erl_eval_SUITE.erl
+++ b/lib/stdlib/test/erl_eval_SUITE.erl
@@ -1156,7 +1156,17 @@ simple() ->
{A}.
simple1() ->
- erlang:error(simple).
+ %% If the compiler could see that this function would always
+ %% throw an error exception, it would rewrite simple() like this:
+ %%
+ %% simple() -> simple1().
+ %%
+ %% That would change the stacktrace. To prevent the compiler from
+ %% doing that optimization, we must obfuscate the code.
+ case get(a_key_that_is_not_defined) of
+ undefined -> erlang:error(simple);
+ WillNeverHappen -> WillNeverHappen
+ end.
%% Simple cases, just to cover some code.
funs(Config) when is_list(Config) ->
diff --git a/lib/stdlib/test/erl_lint_SUITE.erl b/lib/stdlib/test/erl_lint_SUITE.erl
index d18c7c255c..e791da48cf 100644
--- a/lib/stdlib/test/erl_lint_SUITE.erl
+++ b/lib/stdlib/test/erl_lint_SUITE.erl
@@ -68,7 +68,7 @@
non_latin1_module/1, otp_14323/1,
stacktrace_syntax/1,
otp_14285/1, otp_14378/1,
- external_funs/1]).
+ external_funs/1,otp_15456/1]).
suite() ->
[{ct_hooks,[ts_install_cth]},
@@ -89,7 +89,8 @@ all() ->
maps, maps_type, maps_parallel_match,
otp_11851, otp_11879, otp_13230,
record_errors, otp_11879_cont, non_latin1_module, otp_14323,
- stacktrace_syntax, otp_14285, otp_14378, external_funs].
+ stacktrace_syntax, otp_14285, otp_14378, external_funs,
+ otp_15456].
groups() ->
[{unused_vars_warn, [],
@@ -2119,6 +2120,61 @@ otp_5362(Config) when is_list(Config) ->
[] = run(Config, Ts),
ok.
+%% OTP-15456. All compiler options can now be given in the option list
+%% (as opposed to only in files).
+otp_15456(Config) when is_list(Config) ->
+ Ts = [
+ %% {nowarn_deprecated_function,[{M,F,A}]} can now be given
+ %% in the option list as well as in an attribute.
+ %% Wherever it occurs, it is not affected by
+ %% warn_deprecated_function.
+ {otp_15456_1,
+ <<"-compile({nowarn_deprecated_function,{erlang,now,0}}).
+ -export([foo/0]).
+
+ foo() ->
+ {erlang:now(), random:seed0(), random:seed(1, 2, 3),
+ random:uniform(), random:uniform(42)}.
+ ">>,
+ {[{nowarn_deprecated_function,{random,seed0,0}},
+ {nowarn_deprecated_function,[{random,uniform,0},
+ {random,uniform,1}]},
+ %% There should be no warnings when attempting to
+ %% turn of warnings for functions that are not
+ %% deprecated or not used in the module.
+ {nowarn_deprecated_function,{random,uniform_s,1}},
+ {nowarn_deprecated_function,{erlang,abs,1}},
+ warn_deprecated_function]},
+ {warnings,[{5,erl_lint,
+ {deprecated,{random,seed,3},
+ "the 'random' module is deprecated; "
+ "use the 'rand' module instead"}}]}},
+
+ %% {nowarn_unused_function,[{M,F,A}]} can be given
+ %% in the option list as well as in an attribute.
+ %% It was incorrectly documented to only work when
+ %% given in an attribute.
+ {otp_15456_2,
+ <<"-compile({nowarn_unused_function,foo/0}).
+ foo() -> ok.
+ bar() -> ok.
+ foobar() -> ok.
+ barf(_) -> ok.
+ other() -> ok.
+ ">>,
+ {[{nowarn_unused_function,[{bar,0},{foobar,0}]},
+ {nowarn_unused_function,{barf,1}},
+ %% There should be no warnings when attempting to
+ %% turn of warnings for unused functions that are not
+ %% defined in the module.
+ {nowarn_unused_function,{not_defined_in_module,1}},
+ warn_unused_function]},
+ {warnings,[{6,erl_lint,{unused_function,{other,0}}}]}
+ }],
+
+ [] = run(Config, Ts),
+ ok.
+
%% OTP-5371. Aliases for bit syntax expressions are no longer allowed.
otp_5371(Config) when is_list(Config) ->
Ts = [{otp_5371_1,
@@ -2731,7 +2787,7 @@ bif_clash(Config) when is_list(Config) ->
[],
{errors,[{2,erl_lint,{call_to_redefined_old_bif,{size,1}}}],[]}},
- %% Verify that warnings can not be turned off in the old way.
+ %% Verify that warnings cannot be turned off in the old way.
{clash2,
<<"-export([t/1,size/1]).
t(X) ->
diff --git a/lib/stdlib/test/erl_pp_SUITE.erl b/lib/stdlib/test/erl_pp_SUITE.erl
index dda8d0a12e..f5d80e7e68 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-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2019. All 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 @@
otp_6321/1, otp_6911/1, otp_6914/1, otp_8150/1, otp_8238/1,
otp_8473/1, otp_8522/1, otp_8567/1, otp_8664/1, otp_9147/1,
otp_10302/1, otp_10820/1, otp_11100/1, otp_11861/1, pr_1014/1,
- otp_13662/1, otp_14285/1]).
+ otp_13662/1, otp_14285/1, otp_15592/1]).
%% Internal export.
-export([ehook/6]).
@@ -81,7 +81,7 @@ groups() ->
[otp_6321, otp_6911, otp_6914, otp_8150, otp_8238,
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_14285, otp_15592]}].
init_per_suite(Config) ->
Config.
@@ -1167,6 +1167,11 @@ otp_14285(_Config) ->
[{encoding,latin1}])),
ok.
+otp_15592(_Config) ->
+ ok = pp_expr(<<"long12345678901234567890123456789012345678901234"
+ "56789012345678901234:f(<<>>)">>),
+ ok.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
compile(Config, Tests) ->
diff --git a/lib/stdlib/test/ets_SUITE.erl b/lib/stdlib/test/ets_SUITE.erl
index aa2e352c29..22c77aa172 100644
--- a/lib/stdlib/test/ets_SUITE.erl
+++ b/lib/stdlib/test/ets_SUITE.erl
@@ -66,7 +66,9 @@
meta_lookup_named_read/1, meta_lookup_named_write/1,
meta_newdel_unnamed/1, meta_newdel_named/1]).
-export([smp_insert/1, smp_fixed_delete/1, smp_unfix_fix/1, smp_select_delete/1,
+ smp_ordered_iteration/1,
smp_select_replace/1, otp_8166/1, otp_8732/1, delete_unfix_race/1]).
+-export([throughput_benchmark/0, test_throughput_benchmark/1]).
-export([exit_large_table_owner/1,
exit_many_large_table_owner/1,
exit_many_tables_owner/1,
@@ -132,7 +134,8 @@ all() ->
otp_5340, otp_6338, otp_6842_select_1000, otp_7665,
otp_8732, meta_wb, grow_shrink, grow_pseudo_deleted,
shrink_pseudo_deleted, {group, meta_smp}, smp_insert,
- smp_fixed_delete, smp_unfix_fix, smp_select_replace,
+ smp_fixed_delete, smp_unfix_fix, smp_select_replace,
+ smp_ordered_iteration,
smp_select_delete, otp_8166, exit_large_table_owner,
exit_many_large_table_owner, exit_many_tables_owner,
exit_many_many_tables_owner, write_concurrency, heir,
@@ -144,7 +147,8 @@ all() ->
massive_ets_all,
take,
whereis_table,
- delete_unfix_race].
+ delete_unfix_race,
+ test_throughput_benchmark].
groups() ->
[{new, [],
@@ -743,7 +747,7 @@ select_bound_chunk(_Config) ->
repeat_for_opts(fun select_bound_chunk_do/1, [all_types]).
select_bound_chunk_do(Opts) ->
- T = ets:new(x, Opts),
+ T = ets_new(x, Opts),
ets:insert(T, [{key, 1}]),
{[{key, 1}], '$end_of_table'} = ets:select(T, [{{key,1},[],['$_']}], 100000),
ok.
@@ -789,38 +793,42 @@ check_badarg({'EXIT', {badarg, [{M,F,A,_} | _]}}, M, F, Args) ->
%% Test ets:delete_all_objects/1.
t_delete_all_objects(Config) when is_list(Config) ->
EtsMem = etsmem(),
- repeat_for_opts(fun t_delete_all_objects_do/1),
+ repeat_for_opts_all_set_table_types(fun t_delete_all_objects_do/1),
verify_etsmem(EtsMem).
get_kept_objects(T) ->
case ets:info(T,stats) of
- false ->
- 0;
{_,_,_,_,_,_,KO} ->
- KO
+ KO;
+ _ ->
+ 0
end.
t_delete_all_objects_do(Opts) ->
- T=ets_new(x,Opts),
- filltabint(T,4000),
+ KeyRange = 4000,
+ T=ets_new(x, Opts, KeyRange),
+ filltabint(T,KeyRange),
O=ets:first(T),
ets:next(T,O),
ets:safe_fixtable(T,true),
true = ets:delete_all_objects(T),
'$end_of_table' = ets:next(T,O),
0 = ets:info(T,size),
- 4000 = get_kept_objects(T),
+ case ets:info(T,type) of
+ ordered_set -> ok;
+ _ -> KeyRange = get_kept_objects(T)
+ end,
ets:safe_fixtable(T,false),
0 = ets:info(T,size),
0 = get_kept_objects(T),
- filltabint(T,4000),
- 4000 = ets:info(T,size),
+ filltabint(T, KeyRange),
+ KeyRange = ets:info(T,size),
true = ets:delete_all_objects(T),
0 = ets:info(T,size),
ets:delete(T),
%% Test delete_all_objects is atomic
- T2 = ets:new(t_delete_all_objects, [public | Opts]),
+ T2 = ets_new(t_delete_all_objects, [public | Opts]),
Self = self(),
Inserters = [spawn_link(fun() -> inserter(T2, 100*1000, 1, Self) end) || _ <- [1,2,3,4]],
[receive {Ipid, running} -> ok end || Ipid <- Inserters],
@@ -2372,17 +2380,29 @@ write_concurrency(Config) when is_list(Config) ->
Yes6 = ets_new(foo,[duplicate_bag,protected,{write_concurrency,true}]),
No3 = ets_new(foo,[duplicate_bag,private,{write_concurrency,true}]),
- No4 = ets_new(foo,[ordered_set,public,{write_concurrency,true}]),
- No5 = ets_new(foo,[ordered_set,protected,{write_concurrency,true}]),
- No6 = ets_new(foo,[ordered_set,private,{write_concurrency,true}]),
-
- No7 = ets_new(foo,[public,{write_concurrency,false}]),
- No8 = ets_new(foo,[protected,{write_concurrency,false}]),
+ Yes7 = ets_new(foo,[ordered_set,public,{write_concurrency,true}]),
+ Yes8 = ets_new(foo,[ordered_set,protected,{write_concurrency,true}]),
+ Yes9 = ets_new(foo,[ordered_set,{write_concurrency,true}]),
+ Yes10 = ets_new(foo,[{write_concurrency,true},ordered_set,public]),
+ Yes11 = ets_new(foo,[{write_concurrency,true},ordered_set,protected]),
+ Yes12 = ets_new(foo,[set,{write_concurrency,false},
+ {write_concurrency,true},ordered_set,public]),
+ Yes13 = ets_new(foo,[private,public,set,{write_concurrency,false},
+ {write_concurrency,true},ordered_set]),
+ No4 = ets_new(foo,[ordered_set,private,{write_concurrency,true}]),
+ No5 = ets_new(foo,[ordered_set,public,{write_concurrency,false}]),
+ No6 = ets_new(foo,[ordered_set,protected,{write_concurrency,false}]),
+ No7 = ets_new(foo,[ordered_set,private,{write_concurrency,false}]),
+
+ No8 = ets_new(foo,[public,{write_concurrency,false}]),
+ No9 = ets_new(foo,[protected,{write_concurrency,false}]),
YesMem = ets:info(Yes1,memory),
NoHashMem = ets:info(No1,memory),
+ YesTreeMem = ets:info(Yes7,memory),
NoTreeMem = ets:info(No4,memory),
- io:format("YesMem=~p NoHashMem=~p NoTreeMem=~p\n",[YesMem,NoHashMem,NoTreeMem]),
+ io:format("YesMem=~p NoHashMem=~p NoTreeMem=~p YesTreeMem=~p\n",[YesMem,NoHashMem,
+ NoTreeMem,YesTreeMem]),
YesMem = ets:info(Yes2,memory),
YesMem = ets:info(Yes3,memory),
@@ -2391,13 +2411,24 @@ write_concurrency(Config) when is_list(Config) ->
YesMem = ets:info(Yes6,memory),
NoHashMem = ets:info(No2,memory),
NoHashMem = ets:info(No3,memory),
+ YesTreeMem = ets:info(Yes7,memory),
+ YesTreeMem = ets:info(Yes8,memory),
+ YesTreeMem = ets:info(Yes9,memory),
+ YesTreeMem = ets:info(Yes10,memory),
+ YesTreeMem = ets:info(Yes11,memory),
+ YesTreeMem = ets:info(Yes12,memory),
+ YesTreeMem = ets:info(Yes13,memory),
+ NoTreeMem = ets:info(No4,memory),
NoTreeMem = ets:info(No5,memory),
NoTreeMem = ets:info(No6,memory),
- NoHashMem = ets:info(No7,memory),
+ NoTreeMem = ets:info(No7,memory),
NoHashMem = ets:info(No8,memory),
+ NoHashMem = ets:info(No9,memory),
true = YesMem > NoHashMem,
true = YesMem > NoTreeMem,
+ true = YesMem > YesTreeMem,
+ true = YesTreeMem < NoTreeMem,
{'EXIT',{badarg,_}} = (catch ets_new(foo,[public,{write_concurrency,foo}])),
{'EXIT',{badarg,_}} = (catch ets_new(foo,[public,{write_concurrency}])),
@@ -2405,8 +2436,8 @@ write_concurrency(Config) when is_list(Config) ->
{'EXIT',{badarg,_}} = (catch ets_new(foo,[public,write_concurrency])),
lists:foreach(fun(T) -> ets:delete(T) end,
- [Yes1,Yes2,Yes3,Yes4,Yes5,Yes6,
- No1,No2,No3,No4,No5,No6,No7,No8]),
+ [Yes1,Yes2,Yes3,Yes4,Yes5,Yes6,Yes7,Yes8,Yes9,Yes10,Yes11,Yes12,Yes13,
+ No1,No2,No3,No4,No5,No6,No7,No8,No9]),
verify_etsmem(EtsMem),
ok.
@@ -3051,41 +3082,43 @@ pick_all_backwards(T) ->
%% Small test case for both set and bag type ets tables.
setbag(Config) when is_list(Config) ->
EtsMem = etsmem(),
- Set = ets_new(set,[set]),
- Bag = ets_new(bag,[bag]),
- Key = {foo,bar},
-
- %% insert some value
- ets:insert(Set,{Key,val1}),
- ets:insert(Bag,{Key,val1}),
-
- %% insert new value for same key again
- ets:insert(Set,{Key,val2}),
- ets:insert(Bag,{Key,val2}),
-
- %% check
- [{Key,val2}] = ets:lookup(Set,Key),
- [{Key,val1},{Key,val2}] = ets:lookup(Bag,Key),
-
- true = ets:delete(Set),
- true = ets:delete(Bag),
+ lists:foreach(fun(SetType) ->
+ Set = ets_new(SetType,[SetType]),
+ Bag = ets_new(bag,[bag]),
+ Key = {foo,bar},
+
+ %% insert some value
+ ets:insert(Set,{Key,val1}),
+ ets:insert(Bag,{Key,val1}),
+
+ %% insert new value for same key again
+ ets:insert(Set,{Key,val2}),
+ ets:insert(Bag,{Key,val2}),
+
+ %% check
+ [{Key,val2}] = ets:lookup(Set,Key),
+ [{Key,val1},{Key,val2}] = ets:lookup(Bag,Key),
+
+ true = ets:delete(Set),
+ true = ets:delete(Bag)
+ end, [set, cat_ord_set,stim_cat_ord_set,ordered_set]),
verify_etsmem(EtsMem).
%% Test case to check proper return values for illegal ets_new() calls.
badnew(Config) when is_list(Config) ->
EtsMem = etsmem(),
- {'EXIT',{badarg,_}} = (catch ets_new(12,[])),
- {'EXIT',{badarg,_}} = (catch ets_new({a,b},[])),
- {'EXIT',{badarg,_}} = (catch ets_new(name,[foo])),
- {'EXIT',{badarg,_}} = (catch ets_new(name,{bag})),
- {'EXIT',{badarg,_}} = (catch ets_new(name,bag)),
+ {'EXIT',{badarg,_}} = (catch ets:new(12,[])),
+ {'EXIT',{badarg,_}} = (catch ets:new({a,b},[])),
+ {'EXIT',{badarg,_}} = (catch ets:new(name,[foo])),
+ {'EXIT',{badarg,_}} = (catch ets:new(name,{bag})),
+ {'EXIT',{badarg,_}} = (catch ets:new(name,bag)),
verify_etsmem(EtsMem).
%% OTP-2314. Test case to check that a non-proper list does not
%% crash the emulator.
verybadnew(Config) when is_list(Config) ->
EtsMem = etsmem(),
- {'EXIT',{badarg,_}} = (catch ets_new(verybad,[set|protected])),
+ {'EXIT',{badarg,_}} = (catch ets:new(verybad,[set|protected])),
verify_etsmem(EtsMem).
%% Small check to see if named tables work.
@@ -3101,11 +3134,13 @@ named(Config) when is_list(Config) ->
%% Test case to check if specified keypos works.
keypos2(Config) when is_list(Config) ->
EtsMem = etsmem(),
- Tab = make_table(foo,
- [set,{keypos,2}],
- [{val,key}, {val2,key}]),
- [{val2,key}] = ets:lookup(Tab,key),
- true = ets:delete(Tab),
+ lists:foreach(fun(SetType) ->
+ Tab = make_table(foo,
+ [SetType,{keypos,2}],
+ [{val,key}, {val2,key}]),
+ [{val2,key}] = ets:lookup(Tab,key),
+ true = ets:delete(Tab)
+ end, [set, cat_ord_set,stim_cat_ord_set,ordered_set]),
verify_etsmem(EtsMem).
%% Privacy check. Check that a named(public/private/protected) table
@@ -3197,7 +3232,7 @@ rotate_tuple(Tuple, N) ->
%% Check lookup in an empty table and lookup of a non-existing key.
empty(Config) when is_list(Config) ->
- repeat_for_opts(fun empty_do/1).
+ repeat_for_opts_all_table_types(fun empty_do/1).
empty_do(Opts) ->
EtsMem = etsmem(),
@@ -3210,7 +3245,7 @@ empty_do(Opts) ->
%% Check proper return values for illegal insert operations.
badinsert(Config) when is_list(Config) ->
- repeat_for_opts(fun badinsert_do/1).
+ repeat_for_opts_all_table_types(fun badinsert_do/1).
badinsert_do(Opts) ->
EtsMem = etsmem(),
@@ -3234,7 +3269,7 @@ badinsert_do(Opts) ->
time_lookup(Config) when is_list(Config) ->
%% just for timing, really
EtsMem = etsmem(),
- Values = repeat_for_opts(fun time_lookup_do/1),
+ Values = repeat_for_opts_all_table_types(fun time_lookup_do/1),
verify_etsmem(EtsMem),
{comment,lists:flatten(io_lib:format(
"~p ets lookups/s",[Values]))}.
@@ -3431,19 +3466,29 @@ delete_tab_do(Opts) ->
%% Check that ets:delete/1 works and that other processes can run.
delete_large_tab(Config) when is_list(Config) ->
- ct:timetrap({minutes,30}), %% valgrind needs a lot
- Data = [{erlang:phash2(I, 16#ffffff),I} || I <- lists:seq(1, 200000)],
+ ct:timetrap({minutes,60}), %% valgrind needs a lot
+ KeyRange = 16#ffffff,
+ Data = [{erlang:phash2(I, KeyRange),I} || I <- lists:seq(1, 200000)],
EtsMem = etsmem(),
- repeat_for_opts(fun(Opts) -> delete_large_tab_do(Opts,Data) end),
+ repeat_for_opts(fun(Opts) -> delete_large_tab_do(key_range(Opts,KeyRange),
+ Data) end),
verify_etsmem(EtsMem).
delete_large_tab_do(Opts,Data) ->
delete_large_tab_1(foo_hash, Opts, Data, false),
delete_large_tab_1(foo_tree, [ordered_set | Opts], Data, false),
- delete_large_tab_1(foo_hash, Opts, Data, true).
+ delete_large_tab_1(foo_tree, [stim_cat_ord_set | Opts], Data, false),
+ delete_large_tab_1(foo_hash_fix, Opts, Data, true).
delete_large_tab_1(Name, Flags, Data, Fix) ->
+ case is_redundant_opts_combo(Flags) of
+ true -> skip;
+ false ->
+ delete_large_tab_2(Name, Flags, Data, Fix)
+ end.
+
+delete_large_tab_2(Name, Flags, Data, Fix) ->
Tab = ets_new(Name, Flags),
ets:insert(Tab, Data),
@@ -3502,18 +3547,30 @@ delete_large_tab_1(Name, Flags, Data, Fix) ->
%% Delete a large name table and try to create a new table with
%% the same name in another process.
delete_large_named_table(Config) when is_list(Config) ->
- Data = [{erlang:phash2(I, 16#ffffff),I} || I <- lists:seq(1, 200000)],
+ KeyRange = 16#ffffff,
+ Data = [{erlang:phash2(I, KeyRange),I} || I <- lists:seq(1, 200000)],
EtsMem = etsmem(),
- repeat_for_opts(fun(Opts) -> delete_large_named_table_do(Opts,Data) end),
+ repeat_for_opts(fun(Opts) ->
+ delete_large_named_table_do(key_range(Opts,KeyRange),
+ Data)
+ end),
verify_etsmem(EtsMem),
ok.
delete_large_named_table_do(Opts,Data) ->
delete_large_named_table_1(foo_hash, [named_table | Opts], Data, false),
delete_large_named_table_1(foo_tree, [ordered_set,named_table | Opts], Data, false),
+ delete_large_named_table_1(foo_tree, [stim_cat_ord_set,named_table | Opts], Data, false),
delete_large_named_table_1(foo_hash, [named_table | Opts], Data, true).
delete_large_named_table_1(Name, Flags, Data, Fix) ->
+ case is_redundant_opts_combo(Flags) of
+ true -> skip;
+ false ->
+ delete_large_named_table_2(Name, Flags, Data, Fix)
+ end.
+
+delete_large_named_table_2(Name, Flags, Data, Fix) ->
Tab = ets_new(Name, Flags),
ets:insert(Tab, Data),
@@ -3537,8 +3594,12 @@ delete_large_named_table_1(Name, Flags, Data, Fix) ->
%% Delete a large table, and kill the process during the delete.
evil_delete(Config) when is_list(Config) ->
- Data = [{I,I*I} || I <- lists:seq(1, 100000)],
- repeat_for_opts(fun(Opts) -> evil_delete_do(Opts,Data) end).
+ KeyRange = 100000,
+ Data = [{I,I*I} || I <- lists:seq(1, KeyRange)],
+ repeat_for_opts(fun(Opts) ->
+ evil_delete_do(key_range(Opts,KeyRange),
+ Data)
+ end).
evil_delete_do(Opts,Data) ->
EtsMem = etsmem(),
@@ -3548,16 +3609,27 @@ evil_delete_do(Opts,Data) ->
verify_etsmem(EtsMem),
evil_delete_owner(foo_tree, [ordered_set | Opts], Data, false),
verify_etsmem(EtsMem),
+ evil_delete_owner(foo_catree, [stim_cat_ord_set | Opts], Data, false),
+ verify_etsmem(EtsMem),
TabA = evil_delete_not_owner(foo_hash, Opts, Data, false),
verify_etsmem(EtsMem),
TabB = evil_delete_not_owner(foo_hash, Opts, Data, true),
verify_etsmem(EtsMem),
TabC = evil_delete_not_owner(foo_tree, [ordered_set | Opts], Data, false),
verify_etsmem(EtsMem),
+ TabD = evil_delete_not_owner(foo_catree, [stim_cat_ord_set | Opts], Data, false),
+ verify_etsmem(EtsMem),
lists:foreach(fun(T) -> undefined = ets:info(T) end,
- [TabA,TabB,TabC]).
+ [TabA,TabB,TabC,TabD]).
evil_delete_not_owner(Name, Flags, Data, Fix) ->
+ case is_redundant_opts_combo(Flags) of
+ true -> skip;
+ false ->
+ evil_delete_not_owner_1(Name, Flags, Data, Fix)
+ end.
+
+evil_delete_not_owner_1(Name, Flags, Data, Fix) ->
io:format("Not owner: ~p, fix = ~p", [Name,Fix]),
Tab = ets_new(Name, [public|Flags]),
ets:insert(Tab, Data),
@@ -3583,6 +3655,13 @@ evil_delete_not_owner(Name, Flags, Data, Fix) ->
Tab.
evil_delete_owner(Name, Flags, Data, Fix) ->
+ case is_redundant_opts_combo(Flags) of
+ true -> skip;
+ false ->
+ evil_delete_owner_1(Name, Flags, Data, Fix)
+ end.
+
+evil_delete_owner_1(Name, Flags, Data, Fix) ->
Fun = fun() ->
Tab = ets_new(Name, [public|Flags]),
ets:insert(Tab, Data),
@@ -3770,7 +3849,7 @@ verify_rescheduling_exit(Config, ForEachData, Flags, Fix, NOTabs, NOProcs) ->
%% Make sure that slots for ets tables are cleared properly.
table_leak(Config) when is_list(Config) ->
- repeat_for_opts(fun(Opts) -> table_leak_1(Opts,20000) end).
+ repeat_for_opts_all_non_stim_table_types(fun(Opts) -> table_leak_1(Opts,20000) end).
table_leak_1(_,0) -> ok;
table_leak_1(Opts,N) ->
@@ -3833,7 +3912,7 @@ match_delete3_do(Opts) ->
%% Test ets:first/1 & ets:next/2.
firstnext(Config) when is_list(Config) ->
- repeat_for_opts(fun firstnext_do/1).
+ repeat_for_opts_all_set_table_types(fun firstnext_do/1).
firstnext_do(Opts) ->
EtsMem = etsmem(),
@@ -3855,15 +3934,20 @@ firstnext_collect(Tab,Key,List) ->
%% Tests ets:first/1 & ets:next/2.
firstnext_concurrent(Config) when is_list(Config) ->
- register(master, self()),
- ets_init(?MODULE, 20),
- [dynamic_go() || _ <- lists:seq(1, 2)],
- receive
- after 5000 -> ok
- end.
+ lists:foreach(
+ fun(TableType) ->
+ register(master, self()),
+ TableName = list_to_atom(atom_to_list(?MODULE) ++ atom_to_list(TableType)),
+ ets_init(TableName, 20, TableType),
+ [dynamic_go(TableName) || _ <- lists:seq(1, 2)],
+ receive
+ after 5000 -> ok
+ end,
+ unregister(master)
+ end, repeat_for_opts_atom2list(ord_set_types)).
-ets_init(Tab, N) ->
- ets_new(Tab, [named_table,public,ordered_set]),
+ets_init(Tab, N, TableType) ->
+ ets_new(Tab, [named_table,public,TableType]),
cycle(Tab, lists:seq(1,N+1)).
cycle(_Tab, [H|T]) when H > length(T)-> ok;
@@ -3871,9 +3955,9 @@ cycle(Tab, L) ->
ets:insert(Tab,list_to_tuple(L)),
cycle(Tab, tl(L)++[hd(L)]).
-dynamic_go() -> my_spawn_link(fun dynamic_init/0).
+dynamic_go(TableName) -> my_spawn_link(fun() -> dynamic_init(TableName) end).
-dynamic_init() -> [dyn_lookup(?MODULE) || _ <- lists:seq(1, 10)].
+dynamic_init(TableName) -> [dyn_lookup(TableName) || _ <- lists:seq(1, 10)].
dyn_lookup(T) -> dyn_lookup(T, ets:first(T)).
@@ -3891,7 +3975,7 @@ dyn_lookup(T, K) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
slot(Config) when is_list(Config) ->
- repeat_for_opts(fun slot_do/1).
+ repeat_for_opts_all_set_table_types(fun slot_do/1).
slot_do(Opts) ->
EtsMem = etsmem(),
@@ -3916,7 +4000,7 @@ slot_loop(Tab,SlotNo,EltsSoFar) ->
match1(Config) when is_list(Config) ->
- repeat_for_opts(fun match1_do/1).
+ repeat_for_opts_all_set_table_types(fun match1_do/1).
match1_do(Opts) ->
EtsMem = etsmem(),
@@ -3979,7 +4063,7 @@ match2_do(Opts) ->
%% Some ets:match_object tests.
match_object(Config) when is_list(Config) ->
- repeat_for_opts(fun match_object_do/1).
+ repeat_for_opts_all_set_table_types(fun match_object_do/1).
match_object_do(Opts) ->
EtsMem = etsmem(),
@@ -4079,23 +4163,16 @@ match_object_do(Opts) ->
%% Tests that db_match_object does not generate a `badarg' when
%% resuming a search with no previous matches.
match_object2(Config) when is_list(Config) ->
- repeat_for_opts(fun match_object2_do/1).
+ repeat_for_opts_all_table_types(fun match_object2_do/1).
match_object2_do(Opts) ->
EtsMem = etsmem(),
- Tab = ets_new(foo, [bag, {keypos, 2} | Opts]),
- fill_tab2(Tab, 0, 13005), % match_db_object does 1000
+ KeyRange = 13005,
+ Tab = ets_new(foo, [{keypos, 2} | Opts], KeyRange),
+ fill_tab2(Tab, 0, KeyRange), % match_db_object does 1000
% elements per pass, might
% change in the future.
- case catch ets:match_object(Tab, {hej, '$1'}) of
- {'EXIT', _} ->
- ets:delete(Tab),
- ct:fail("match_object EXIT:ed");
- [] ->
- io:format("Nothing matched.");
- List ->
- io:format("Matched:~p~n",[List])
- end,
+ [] = ets:match_object(Tab, {hej, '$1'}),
ets:delete(Tab),
verify_etsmem(EtsMem).
@@ -4104,18 +4181,21 @@ match_object2_do(Opts) ->
%% OTP-3319. Test tab2list.
tab2list(Config) when is_list(Config) ->
- EtsMem = etsmem(),
- Tab = make_table(foo,
- [ordered_set],
- [{a,b}, {c,b}, {b,b}, {a,c}]),
- [{a,c},{b,b},{c,b}] = ets:tab2list(Tab),
- true = ets:delete(Tab),
- verify_etsmem(EtsMem).
+ repeat_for_all_ord_set_table_types(
+ fun(Opts) ->
+ EtsMem = etsmem(),
+ Tab = make_table(foo,
+ Opts,
+ [{a,b}, {c,b}, {b,b}, {a,c}]),
+ [{a,c},{b,b},{c,b}] = ets:tab2list(Tab),
+ true = ets:delete(Tab),
+ verify_etsmem(EtsMem)
+ end).
%% Simple general small test. If this fails, ets is in really bad
%% shape.
misc1(Config) when is_list(Config) ->
- repeat_for_opts(fun misc1_do/1).
+ repeat_for_opts_all_table_types(fun misc1_do/1).
misc1_do(Opts) ->
EtsMem = etsmem(),
@@ -4133,7 +4213,7 @@ misc1_do(Opts) ->
%% Check the safe_fixtable function.
safe_fixtable(Config) when is_list(Config) ->
- repeat_for_opts(fun safe_fixtable_do/1).
+ repeat_for_opts_all_table_types(fun safe_fixtable_do/1).
safe_fixtable_do(Opts) ->
EtsMem = etsmem(),
@@ -4191,10 +4271,42 @@ safe_fixtable_do(Opts) ->
%% Tests ets:info result for required tuples.
info(Config) when is_list(Config) ->
- repeat_for_opts(fun info_do/1).
+ repeat_for_opts_all_table_types(fun info_do/1).
info_do(Opts) ->
EtsMem = etsmem(),
+ TableType = lists:foldl(
+ fun(Item, Curr) ->
+ case Item of
+ set -> set;
+ ordered_set -> ordered_set;
+ cat_ord_set -> ordered_set;
+ stim_cat_ord_set -> ordered_set;
+ bag -> bag;
+ duplicate_bag -> duplicate_bag;
+ _ -> Curr
+ end
+ end, set, Opts),
+ PublicOrCurr =
+ fun(Curr) ->
+ case lists:member({write_concurrency, false}, Opts) or
+ lists:member(private, Opts) or
+ lists:member(protected, Opts) of
+ true -> Curr;
+ false -> public
+ end
+ end,
+ Protection = lists:foldl(
+ fun(Item, Curr) ->
+ case Item of
+ public -> public;
+ protected -> protected;
+ private -> private;
+ cat_ord_set -> PublicOrCurr(Curr); %% Special items
+ stim_cat_ord_set -> PublicOrCurr(Curr);
+ _ -> Curr
+ end
+ end, protected, Opts),
MeMyselfI=self(),
ThisNode=node(),
Tab = ets_new(foobar, [{keypos, 2} | Opts]),
@@ -4208,9 +4320,9 @@ info_do(Opts) ->
{value, {size, 0}} = lists:keysearch(size, 1, Res),
{value, {node, ThisNode}} = lists:keysearch(node, 1, Res),
{value, {named_table, false}} = lists:keysearch(named_table, 1, Res),
- {value, {type, set}} = lists:keysearch(type, 1, Res),
+ {value, {type, TableType}} = lists:keysearch(type, 1, Res),
{value, {keypos, 2}} = lists:keysearch(keypos, 1, Res),
- {value, {protection, protected}} =
+ {value, {protection, Protection}} =
lists:keysearch(protection, 1, Res),
{value, {id, Tab}} = lists:keysearch(id, 1, Res),
true = ets:delete(Tab),
@@ -4255,20 +4367,29 @@ dups_do(Opts) ->
%% Test the ets:tab2file function on an empty ets table.
tab2file(Config) when is_list(Config) ->
FName = filename:join([proplists:get_value(priv_dir, Config),"tab2file_case"]),
- tab2file_do(FName, []),
- tab2file_do(FName, [{sync,true}]),
- tab2file_do(FName, [{sync,false}]),
- {'EXIT',{{badmatch,{error,_}},_}} = (catch tab2file_do(FName, [{sync,yes}])),
- {'EXIT',{{badmatch,{error,_}},_}} = (catch tab2file_do(FName, [sync])),
+ tab2file_do(FName, [], set),
+ tab2file_do(FName, [], ordered_set),
+ tab2file_do(FName, [], cat_ord_set),
+ tab2file_do(FName, [], stim_cat_ord_set),
+ tab2file_do(FName, [{sync,true}], set),
+ tab2file_do(FName, [{sync,false}], set),
+ {'EXIT',{{badmatch,{error,_}},_}} = (catch tab2file_do(FName, [{sync,yes}], set)),
+ {'EXIT',{{badmatch,{error,_}},_}} = (catch tab2file_do(FName, [sync], set)),
ok.
-tab2file_do(FName, Opts) ->
+tab2file_do(FName, Opts, TableType) ->
%% Write an empty ets table to a file, read back and check properties.
- Tab = ets_new(ets_SUITE_foo_tab, [named_table, set, public,
+ Tab = ets_new(ets_SUITE_foo_tab, [named_table, TableType, public,
{keypos, 2},
compressed,
{write_concurrency,true},
{read_concurrency,true}]),
+ ActualTableType =
+ case TableType of
+ cat_ord_set -> ordered_set;
+ stim_cat_ord_set -> ordered_set;
+ _ -> TableType
+ end,
catch file:delete(FName),
Res = ets:tab2file(Tab, FName, Opts),
true = ets:delete(Tab),
@@ -4279,7 +4400,7 @@ tab2file_do(FName, Opts) ->
public = ets:info(Tab2, protection),
true = ets:info(Tab2, named_table),
2 = ets:info(Tab2, keypos),
- set = ets:info(Tab2, type),
+ ActualTableType = ets:info(Tab2, type),
true = ets:info(Tab2, compressed),
Smp = erlang:system_info(smp_support),
Smp = ets:info(Tab2, read_concurrency),
@@ -4292,14 +4413,15 @@ tab2file_do(FName, Opts) ->
tab2file2(Config) when is_list(Config) ->
repeat_for_opts(fun(Opts) ->
tab2file2_do(Opts, Config)
- end, [[set,bag],compressed]).
+ end, [[stim_cat_ord_set,cat_ord_set,set,bag],compressed]).
tab2file2_do(Opts, Config) ->
EtsMem = etsmem(),
- Tab = ets_new(ets_SUITE_foo_tab, [named_table, private,
- {keypos, 2} | Opts]),
+ KeyRange = 10000,
+ Tab = ets_new(ets_SUITE_foo_tab, [named_table, private, {keypos, 2} | Opts],
+ KeyRange),
FName = filename:join([proplists:get_value(priv_dir, Config),"tab2file2_case"]),
- ok = fill_tab2(Tab, 0, 10000), % Fill up the table (grucho mucho!)
+ ok = fill_tab2(Tab, 0, KeyRange), % Fill up the table (grucho mucho!)
Len = length(ets:tab2list(Tab)),
Mem = ets:info(Tab, memory),
Type = ets:info(Tab, type),
@@ -4353,13 +4475,14 @@ fill_tab2(Tab, Val, Num) ->
%% Test verification of tables with object count extended_info.
tabfile_ext1(Config) when is_list(Config) ->
- repeat_for_opts(fun(Opts) -> tabfile_ext1_do(Opts, Config) end).
+ repeat_for_opts_all_set_table_types(fun(Opts) -> tabfile_ext1_do(Opts, Config) end).
tabfile_ext1_do(Opts,Config) ->
FName = filename:join([proplists:get_value(priv_dir, Config),"nisse.dat"]),
FName2 = filename:join([proplists:get_value(priv_dir, Config),"countflip.dat"]),
- L = lists:seq(1,10),
- T = ets_new(x,Opts),
+ KeyRange = 10,
+ L = lists:seq(1,KeyRange),
+ T = ets_new(x,Opts,KeyRange),
Name = make_ref(),
[ets:insert(T,{X,integer_to_list(X)}) || X <- L],
ok = ets:tab2file(T,FName,[{extended_info,[object_count]}]),
@@ -4391,13 +4514,14 @@ tabfile_ext1_do(Opts,Config) ->
%% Test verification of tables with md5sum extended_info.
tabfile_ext2(Config) when is_list(Config) ->
- repeat_for_opts(fun(Opts) -> tabfile_ext2_do(Opts,Config) end).
+ repeat_for_opts_all_set_table_types(fun(Opts) -> tabfile_ext2_do(Opts,Config) end).
tabfile_ext2_do(Opts,Config) ->
FName = filename:join([proplists:get_value(priv_dir, Config),"olle.dat"]),
FName2 = filename:join([proplists:get_value(priv_dir, Config),"bitflip.dat"]),
- L = lists:seq(1,10),
- T = ets_new(x,Opts),
+ KeyRange = 10,
+ L = lists:seq(1, KeyRange),
+ T = ets_new(x, Opts, KeyRange),
Name = make_ref(),
[ets:insert(T,{X,integer_to_list(X)}) || X <- L],
ok = ets:tab2file(T,FName,[{extended_info,[md5sum]}]),
@@ -4428,71 +4552,77 @@ tabfile_ext2_do(Opts,Config) ->
%% Test verification of (named) tables without extended info.
tabfile_ext3(Config) when is_list(Config) ->
- FName = filename:join([proplists:get_value(priv_dir, Config),"namn.dat"]),
- FName2 = filename:join([proplists:get_value(priv_dir, Config),"ncountflip.dat"]),
- L = lists:seq(1,10),
- Name = make_ref(),
- ?MODULE = ets_new(?MODULE,[named_table]),
- [ets:insert(?MODULE,{X,integer_to_list(X)}) || X <- L],
- ets:tab2file(?MODULE,FName),
- {error,cannot_create_table} = ets:file2tab(FName),
- true = ets:delete(?MODULE),
- {ok,?MODULE} = ets:file2tab(FName),
- true = ets:delete(?MODULE),
- disk_log:open([{name,Name},{file,FName}]),
- {_,[H2|T2]} = disk_log:chunk(Name,start),
- disk_log:close(Name),
- NewT2=lists:keydelete(8,1,T2),
- file:delete(FName2),
- disk_log:open([{name,Name},{file,FName2},{mode,read_write}]),
- disk_log:log_terms(Name,[H2|NewT2]),
- disk_log:close(Name),
- 9 = length(ets:tab2list(element(2,ets:file2tab(FName2)))),
- true = ets:delete(?MODULE),
- {error,invalid_object_count} = ets:file2tab(FName2,[{verify,true}]),
- {'EXIT',_} = (catch ets:delete(?MODULE)),
- {ok,_} = ets:tabfile_info(FName2),
- {ok,_} = ets:tabfile_info(FName),
- file:delete(FName),
- file:delete(FName2),
+ repeat_for_all_set_table_types(
+ fun(Opts) ->
+ FName = filename:join([proplists:get_value(priv_dir, Config),"namn.dat"]),
+ FName2 = filename:join([proplists:get_value(priv_dir, Config),"ncountflip.dat"]),
+ L = lists:seq(1,10),
+ Name = make_ref(),
+ ?MODULE = ets_new(?MODULE,[named_table|Opts]),
+ [ets:insert(?MODULE,{X,integer_to_list(X)}) || X <- L],
+ ets:tab2file(?MODULE,FName),
+ {error,cannot_create_table} = ets:file2tab(FName),
+ true = ets:delete(?MODULE),
+ {ok,?MODULE} = ets:file2tab(FName),
+ true = ets:delete(?MODULE),
+ disk_log:open([{name,Name},{file,FName}]),
+ {_,[H2|T2]} = disk_log:chunk(Name,start),
+ disk_log:close(Name),
+ NewT2=lists:keydelete(8,1,T2),
+ file:delete(FName2),
+ disk_log:open([{name,Name},{file,FName2},{mode,read_write}]),
+ disk_log:log_terms(Name,[H2|NewT2]),
+ disk_log:close(Name),
+ 9 = length(ets:tab2list(element(2,ets:file2tab(FName2)))),
+ true = ets:delete(?MODULE),
+ {error,invalid_object_count} = ets:file2tab(FName2,[{verify,true}]),
+ {'EXIT',_} = (catch ets:delete(?MODULE)),
+ {ok,_} = ets:tabfile_info(FName2),
+ {ok,_} = ets:tabfile_info(FName),
+ file:delete(FName),
+ file:delete(FName2)
+ end),
ok.
%% Tests verification of large table with md5 sum.
tabfile_ext4(Config) when is_list(Config) ->
- FName = filename:join([proplists:get_value(priv_dir, Config),"bauta.dat"]),
- LL = lists:seq(1,10000),
- TL = ets_new(x,[]),
- Name2 = make_ref(),
- [ets:insert(TL,{X,integer_to_list(X)}) || X <- LL],
- ok = ets:tab2file(TL,FName,[{extended_info,[md5sum]}]),
- {ok, Name2} = disk_log:open([{name, Name2}, {file, FName},
- {mode, read_only}]),
- {C,[_|_]} = disk_log:chunk(Name2,start),
- {_,[_|_]} = disk_log:chunk(Name2,C),
- disk_log:close(Name2),
- true = lists:sort(ets:tab2list(TL)) =:=
- lists:sort(ets:tab2list(element(2,ets:file2tab(FName)))),
- Res = [begin
- {ok,FD} = file:open(FName,[binary,read,write]),
- {ok, Bin} = file:pread(FD,0,1000),
- <<B1:N/binary,Ch:8,B2/binary>> = Bin,
- Ch2 = (Ch + 1) rem 255,
- Bin2 = <<B1/binary,Ch2:8,B2/binary>>,
- ok = file:pwrite(FD,0,Bin2),
- ok = file:close(FD),
- X = case ets:file2tab(FName) of
- {ok,TL2} ->
- true = lists:sort(ets:tab2list(TL)) =/=
- lists:sort(ets:tab2list(TL2));
- _ ->
- totally_broken
- end,
- {error,Y} = ets:file2tab(FName,[{verify,true}]),
- ets:tab2file(TL,FName,[{extended_info,[md5sum]}]),
- {X,Y}
- end || N <- lists:seq(500,600)],
- io:format("~p~n",[Res]),
- file:delete(FName),
+ repeat_for_all_set_table_types(
+ fun(Opts) ->
+ FName = filename:join([proplists:get_value(priv_dir, Config),"bauta.dat"]),
+ LL = lists:seq(1,10000),
+ TL = ets_new(x,Opts),
+ Name2 = make_ref(),
+ [ets:insert(TL,{X,integer_to_list(X)}) || X <- LL],
+ ok = ets:tab2file(TL,FName,[{extended_info,[md5sum]}]),
+ {ok, Name2} = disk_log:open([{name, Name2}, {file, FName},
+ {mode, read_only}]),
+ {C,[_|_]} = disk_log:chunk(Name2,start),
+ {_,[_|_]} = disk_log:chunk(Name2,C),
+ disk_log:close(Name2),
+ true = lists:sort(ets:tab2list(TL)) =:=
+ lists:sort(ets:tab2list(element(2,ets:file2tab(FName)))),
+ Res = [begin
+ {ok,FD} = file:open(FName,[binary,read,write]),
+ {ok, Bin} = file:pread(FD,0,1000),
+ <<B1:N/binary,Ch:8,B2/binary>> = Bin,
+ Ch2 = (Ch + 1) rem 255,
+ Bin2 = <<B1/binary,Ch2:8,B2/binary>>,
+ ok = file:pwrite(FD,0,Bin2),
+ ok = file:close(FD),
+ X = case ets:file2tab(FName) of
+ {ok,TL2} ->
+ true = lists:sort(ets:tab2list(TL)) =/=
+ lists:sort(ets:tab2list(TL2));
+ _ ->
+ totally_broken
+ end,
+ {error,Y} = ets:file2tab(FName,[{verify,true}]),
+ ets:tab2file(TL,FName,[{extended_info,[md5sum]}]),
+ {X,Y}
+ end || N <- lists:seq(500,600)],
+ io:format("~p~n",[Res]),
+ file:delete(FName)
+ end),
ok.
%% Test that no disk_log is left open when file has been corrupted.
@@ -4556,13 +4686,14 @@ make_sub_binary(List, Num) when is_list(List) ->
%% Perform multiple lookups for every key in a large table.
heavy_lookup(Config) when is_list(Config) ->
- repeat_for_opts(fun heavy_lookup_do/1).
+ repeat_for_opts_all_set_table_types(fun heavy_lookup_do/1).
heavy_lookup_do(Opts) ->
EtsMem = etsmem(),
- Tab = ets_new(foobar_table, [set, protected, {keypos, 2} | Opts]),
- ok = fill_tab2(Tab, 0, 7000),
- _ = [do_lookup(Tab, 6999) || _ <- lists:seq(1, 50)],
+ KeyRange = 7000,
+ Tab = ets_new(foobar_table, [{keypos, 2} | Opts], KeyRange),
+ ok = fill_tab2(Tab, 0, KeyRange),
+ _ = [do_lookup(Tab, KeyRange-1) || _ <- lists:seq(1, 50)],
true = ets:delete(Tab),
verify_etsmem(EtsMem).
@@ -4579,15 +4710,16 @@ do_lookup(Tab, N) ->
%% Perform multiple lookups for every element in a large table.
heavy_lookup_element(Config) when is_list(Config) ->
- repeat_for_opts(fun heavy_lookup_element_do/1).
+ repeat_for_opts_all_set_table_types(fun heavy_lookup_element_do/1).
heavy_lookup_element_do(Opts) ->
EtsMem = etsmem(),
- Tab = ets_new(foobar_table, [set, protected, {keypos, 2} | Opts]),
- ok = fill_tab2(Tab, 0, 7000),
+ KeyRange = 7000,
+ Tab = ets_new(foobar_table, [{keypos, 2} | Opts], KeyRange),
+ ok = fill_tab2(Tab, 0, KeyRange),
%% lookup ALL elements 50 times
Laps = 50 div syrup_factor(),
- _ = [do_lookup_element(Tab, 6999, 1) || _ <- lists:seq(1, Laps)],
+ _ = [do_lookup_element(Tab, KeyRange-1, 1) || _ <- lists:seq(1, Laps)],
true = ets:delete(Tab),
verify_etsmem(EtsMem).
@@ -4606,15 +4738,15 @@ do_lookup_element(Tab, N, M) ->
heavy_concurrent(Config) when is_list(Config) ->
- ct:timetrap({minutes,30}), %% valgrind needs a lot of time
- repeat_for_opts(fun do_heavy_concurrent/1).
+ ct:timetrap({minutes,120}), %% valgrind needs a lot of time
+ repeat_for_opts_all_set_table_types(fun do_heavy_concurrent/1).
do_heavy_concurrent(Opts) ->
- Size = 10000,
+ KeyRange = 10000,
Laps = 10000 div syrup_factor(),
EtsMem = etsmem(),
- Tab = ets_new(blupp, [set, public, {keypos, 2} | Opts]),
- ok = fill_tab2(Tab, 0, Size),
+ Tab = ets_new(blupp, [public, {keypos, 2} | Opts], KeyRange),
+ ok = fill_tab2(Tab, 0, KeyRange),
Procs = lists:map(
fun (N) ->
my_spawn_link(
@@ -4647,48 +4779,68 @@ do_heavy_concurrent_proc(Tab, N, Offs) ->
fold_empty(Config) when is_list(Config) ->
- EtsMem = etsmem(),
- Tab = make_table(a, [], []),
- [] = ets:foldl(fun(_X) -> exit(hej) end, [], Tab),
- [] = ets:foldr(fun(_X) -> exit(hej) end, [], Tab),
- true = ets:delete(Tab),
- verify_etsmem(EtsMem).
+ repeat_for_opts_all_set_table_types(
+ fun(Opts) ->
+ EtsMem = etsmem(),
+ Tab = make_table(a, Opts, []),
+ [] = ets:foldl(fun(_X) -> exit(hej) end, [], Tab),
+ [] = ets:foldr(fun(_X) -> exit(hej) end, [], Tab),
+ true = ets:delete(Tab),
+ verify_etsmem(EtsMem)
+ end),
+ ok.
foldl(Config) when is_list(Config) ->
- EtsMem = etsmem(),
- L = [{a,1}, {c,3}, {b,2}],
- LS = lists:sort(L),
- Tab = make_table(a, [bag], L),
- LS = lists:sort(ets:foldl(fun(E,A) -> [E|A] end, [], Tab)),
- true = ets:delete(Tab),
- verify_etsmem(EtsMem).
+ repeat_for_opts_all_table_types(
+ fun(Opts) ->
+ EtsMem = etsmem(),
+ L = [{a,1}, {c,3}, {b,2}],
+ LS = lists:sort(L),
+ Tab = make_table(a, Opts, L),
+ LS = lists:sort(ets:foldl(fun(E,A) -> [E|A] end, [], Tab)),
+ true = ets:delete(Tab),
+ verify_etsmem(EtsMem)
+ end),
+ ok.
foldr(Config) when is_list(Config) ->
- EtsMem = etsmem(),
- L = [{a,1}, {c,3}, {b,2}],
- LS = lists:sort(L),
- Tab = make_table(a, [bag], L),
- LS = lists:sort(ets:foldr(fun(E,A) -> [E|A] end, [], Tab)),
- true = ets:delete(Tab),
- verify_etsmem(EtsMem).
+ repeat_for_opts_all_table_types(
+ fun(Opts) ->
+ EtsMem = etsmem(),
+ L = [{a,1}, {c,3}, {b,2}],
+ LS = lists:sort(L),
+ Tab = make_table(a, Opts, L),
+ LS = lists:sort(ets:foldr(fun(E,A) -> [E|A] end, [], Tab)),
+ true = ets:delete(Tab),
+ verify_etsmem(EtsMem)
+ end),
+ ok.
foldl_ordered(Config) when is_list(Config) ->
- EtsMem = etsmem(),
- L = [{a,1}, {c,3}, {b,2}],
- LS = lists:sort(L),
- Tab = make_table(a, [ordered_set], L),
- LS = lists:reverse(ets:foldl(fun(E,A) -> [E|A] end, [], Tab)),
- true = ets:delete(Tab),
- verify_etsmem(EtsMem).
+ repeat_for_opts_all_ord_set_table_types(
+ fun(Opts) ->
+ EtsMem = etsmem(),
+ L = [{a,1}, {c,3}, {b,2}],
+ LS = lists:sort(L),
+ Tab = make_table(a, Opts, L),
+ LS = lists:reverse(ets:foldl(fun(E,A) -> [E|A] end, [], Tab)),
+ true = ets:delete(Tab),
+ verify_etsmem(EtsMem)
+ end),
+ ok.
foldr_ordered(Config) when is_list(Config) ->
- EtsMem = etsmem(),
- L = [{a,1}, {c,3}, {b,2}],
- LS = lists:sort(L),
- Tab = make_table(a, [ordered_set], L),
- LS = ets:foldr(fun(E,A) -> [E|A] end, [], Tab),
- true = ets:delete(Tab),
- verify_etsmem(EtsMem).
+ repeat_for_opts_all_ord_set_table_types(
+ fun(Opts) ->
+ EtsMem = etsmem(),
+ L = [{a,1}, {c,3}, {b,2}],
+ LS = lists:sort(L),
+ Tab = make_table(a, Opts, L),
+ LS = ets:foldr(fun(E,A) -> [E|A] end, [], Tab),
+ true = ets:delete(Tab),
+ verify_etsmem(EtsMem)
+ end),
+ ok.
%% Test ets:member BIF.
member(Config) when is_list(Config) ->
@@ -4873,6 +5025,7 @@ filltabint(Tab,0) ->
filltabint(Tab,N) ->
ets:insert(Tab,{N,integer_to_list(N)}),
filltabint(Tab,N-1).
+
filltabint2(Tab,0) ->
Tab;
filltabint2(Tab,N) ->
@@ -5087,27 +5240,31 @@ gen_dets_filename(Config,N) ->
"testdets_" ++ integer_to_list(N) ++ ".dets").
otp_6842_select_1000(Config) when is_list(Config) ->
- Tab = ets_new(xxx,[ordered_set]),
- [ets:insert(Tab,{X,X}) || X <- lists:seq(1,10000)],
- AllTrue = lists:duplicate(10,true),
- AllTrue =
- [ length(
- element(1,
- ets:select(Tab,[{'_',[],['$_']}],X*1000))) =:=
- X*1000 || X <- lists:seq(1,10) ],
- Sequences = [[1000,1000,1000,1000,1000,1000,1000,1000,1000,1000],
- [2000,2000,2000,2000,2000],
- [3000,3000,3000,1000],
- [4000,4000,2000],
- [5000,5000],
- [6000,4000],
- [7000,3000],
- [8000,2000],
- [9000,1000],
- [10000]],
- AllTrue = [ check_seq(Tab, ets:select(Tab,[{'_',[],['$_']}],hd(L)),L) ||
- L <- Sequences ],
- ets:delete(Tab),
+ repeat_for_opts_all_ord_set_table_types(
+ fun(Opts) ->
+ KeyRange = 10000,
+ Tab = ets_new(xxx, Opts, KeyRange),
+ [ets:insert(Tab,{X,X}) || X <- lists:seq(1,KeyRange)],
+ AllTrue = lists:duplicate(10,true),
+ AllTrue =
+ [ length(
+ element(1,
+ ets:select(Tab,[{'_',[],['$_']}],X*1000))) =:=
+ X*1000 || X <- lists:seq(1,10) ],
+ Sequences = [[1000,1000,1000,1000,1000,1000,1000,1000,1000,1000],
+ [2000,2000,2000,2000,2000],
+ [3000,3000,3000,1000],
+ [4000,4000,2000],
+ [5000,5000],
+ [6000,4000],
+ [7000,3000],
+ [8000,2000],
+ [9000,1000],
+ [10000]],
+ AllTrue = [ check_seq(Tab, ets:select(Tab,[{'_',[],['$_']}],hd(L)),L) ||
+ L <- Sequences ],
+ ets:delete(Tab)
+ end),
ok.
check_seq(_,'$end_of_table',[]) ->
@@ -5119,17 +5276,21 @@ check_seq(A,B,C) ->
false.
otp_6338(Config) when is_list(Config) ->
- L = binary_to_term(<<131,108,0,0,0,2,104,2,108,0,0,0,2,103,100,0,19,112,112,
- 98,49,95,98,115,49,50,64,98,108,97,100,101,95,48,95,53,
- 0,0,33,50,0,0,0,4,1,98,0,0,23,226,106,100,0,4,101,120,
- 105,116,104,2,108,0,0,0,2,104,2,100,0,3,115,98,109,100,
- 0,19,112,112,98,50,95,98,115,49,50,64,98,108,97,100,
- 101,95,48,95,56,98,0,0,18,231,106,100,0,4,114,101,99,
- 118,106>>),
- T = ets_new(xxx,[ordered_set]),
- lists:foreach(fun(X) -> ets:insert(T,X) end,L),
- [[4839,recv]] = ets:match(T,{[{sbm,ppb2_bs12@blade_0_8},'$1'],'$2'}),
- ets:delete(T).
+ repeat_for_opts_all_ord_set_table_types(
+ fun(Opts) ->
+ L = binary_to_term(<<131,108,0,0,0,2,104,2,108,0,0,0,2,103,100,0,19,112,112,
+ 98,49,95,98,115,49,50,64,98,108,97,100,101,95,48,95,53,
+ 0,0,33,50,0,0,0,4,1,98,0,0,23,226,106,100,0,4,101,120,
+ 105,116,104,2,108,0,0,0,2,104,2,100,0,3,115,98,109,100,
+ 0,19,112,112,98,50,95,98,115,49,50,64,98,108,97,100,
+ 101,95,48,95,56,98,0,0,18,231,106,100,0,4,114,101,99,
+ 118,106>>),
+ T = ets_new(xxx,Opts),
+ lists:foreach(fun(X) -> ets:insert(T,X) end,L),
+ [[4839,recv]] = ets:match(T,{[{sbm,ppb2_bs12@blade_0_8},'$1'],'$2'}),
+ ets:delete(T)
+ end),
+ ok.
%% Elements could come in the wrong order in a bag if a rehash occurred.
otp_5340(Config) when is_list(Config) ->
@@ -5199,7 +5360,7 @@ otp_7665_act(Tab,Min,Max,DelNr) ->
%% Whitebox testing of meta name table hashing.
meta_wb(Config) when is_list(Config) ->
EtsMem = etsmem(),
- repeat_for_opts(fun meta_wb_do/1),
+ repeat_for_opts_all_non_stim_table_types(fun meta_wb_do/1),
verify_etsmem(EtsMem).
@@ -5268,13 +5429,16 @@ colliding_names(Name) ->
%% OTP_6913: Grow and shrink.
grow_shrink(Config) when is_list(Config) ->
- EtsMem = etsmem(),
-
- Set = ets_new(a, [set]),
- grow_shrink_0(0, 3071, 3000, 5000, Set),
- ets:delete(Set),
-
- verify_etsmem(EtsMem).
+ repeat_for_all_set_table_types(
+ fun(Opts) ->
+ EtsMem = etsmem(),
+
+ Set = ets_new(a, Opts, 5000),
+ grow_shrink_0(0, 3071, 3000, 5000, Set),
+ ets:delete(Set),
+
+ verify_etsmem(EtsMem)
+ end).
grow_shrink_0(N, _, _, Max, _) when N >= Max ->
ok;
@@ -5298,7 +5462,7 @@ grow_shrink_3(N, ShrinkTo, T) ->
true = ets:delete(T, N),
grow_shrink_3(N-1, ShrinkTo, T).
-%% Grow a table that still contains pseudo-deleted objects.
+%% Grow a hash table that still contains pseudo-deleted objects.
grow_pseudo_deleted(Config) when is_list(Config) ->
only_if_smp(fun() -> grow_pseudo_deleted_do() end).
@@ -5351,7 +5515,7 @@ grow_pseudo_deleted_do(Type) ->
ets:delete(T),
process_flag(scheduler,0).
-%% Shrink a table that still contains pseudo-deleted objects.
+%% Shrink a hash table that still contains pseudo-deleted objects.
shrink_pseudo_deleted(Config) when is_list(Config) ->
only_if_smp(fun()->shrink_pseudo_deleted_do() end).
@@ -5471,10 +5635,16 @@ meta_newdel_named(Config) when is_list(Config) ->
%% Concurrent insert's on same table.
smp_insert(Config) when is_list(Config) ->
- ets_new(smp_insert,[named_table,public,{write_concurrency,true}]),
+ repeat_for_opts(fun smp_insert_do/1,
+ [[set,ordered_set,stim_cat_ord_set]]).
+
+smp_insert_do(Opts) ->
+ KeyRange = 10000,
+ ets_new(smp_insert,[named_table,public,{write_concurrency,true}|Opts],
+ KeyRange),
InitF = fun(_) -> ok end,
- ExecF = fun(_) -> true = ets:insert(smp_insert,{rand:uniform(10000)})
- end,
+ ExecF = fun(_) -> true = ets:insert(smp_insert,{rand:uniform(KeyRange)})
+ end,
FiniF = fun(_) -> ok end,
run_smp_workers(InitF,ExecF,FiniF,100000),
verify_table_load(smp_insert),
@@ -5482,7 +5652,7 @@ smp_insert(Config) when is_list(Config) ->
%% Concurrent deletes on same fixated table.
smp_fixed_delete(Config) when is_list(Config) ->
- only_if_smp(fun()->smp_fixed_delete_do() end).
+ only_if_smp(fun() -> smp_fixed_delete_do() end).
smp_fixed_delete_do() ->
T = ets_new(foo,[public,{write_concurrency,true}]),
@@ -5493,17 +5663,20 @@ smp_fixed_delete_do() ->
Buckets = num_of_buckets(T),
InitF = fun([ProcN,NumOfProcs|_]) -> {ProcN,NumOfProcs} end,
ExecF = fun({Key,_}) when Key > NumOfObjs ->
- [end_of_work];
- ({Key,Increment}) ->
- true = ets:delete(T,Key),
- {Key+Increment,Increment}
- end,
+ [end_of_work];
+ ({Key,Increment}) ->
+ true = ets:delete(T,Key),
+ {Key+Increment,Increment}
+ end,
FiniF = fun(_) -> ok end,
run_sched_workers(InitF,ExecF,FiniF,NumOfObjs),
0 = ets:info(T,size),
true = ets:info(T,fixed),
Buckets = num_of_buckets(T),
- NumOfObjs = get_kept_objects(T),
+ case ets:info(T,type) of
+ set -> NumOfObjs = get_kept_objects(T);
+ _ -> ok
+ end,
ets:safe_fixtable(T,false),
%% Will fail as unfix does not shrink the table:
%%Mem = ets:info(T,memory),
@@ -5551,7 +5724,12 @@ delete_unfix_race(Config) when is_list(Config) ->
verify_etsmem(EtsMem).
num_of_buckets(T) ->
- element(1,ets:info(T,stats)).
+ case ets:info(T,type) of
+ set -> element(1,ets:info(T,stats));
+ bag -> element(1,ets:info(T,stats));
+ duplicate_bag -> element(1,ets:info(T,stats));
+ _ -> ok
+ end.
%% Fixate hash table while other process is busy doing unfix.
smp_unfix_fix(Config) when is_list(Config) ->
@@ -5716,106 +5894,126 @@ otp_8166_zombie_creator(T,Deleted) ->
verify_table_load(T) ->
- Stats = ets:info(T,stats),
- {Buckets,AvgLen,StdDev,ExpSD,_MinLen,_MaxLen,_} = Stats,
- ok = if
- AvgLen > 1.2 ->
- io:format("Table overloaded: Stats=~p\n~p\n",
- [Stats, ets:info(T)]),
- false;
-
- Buckets>256, AvgLen < 0.47 ->
- io:format("Table underloaded: Stats=~p\n~p\n",
- [Stats, ets:info(T)]),
- false;
-
- StdDev > ExpSD*2 ->
- io:format("Too large standard deviation (poor hashing?),"
- " stats=~p\n~p\n",[Stats, ets:info(T)]),
- false;
-
- true ->
- io:format("Stats = ~p\n",[Stats]),
- ok
- end.
+ case ets:info(T,type) of
+ ordered_set -> ok;
+ _ ->
+ Stats = ets:info(T,stats),
+ {Buckets,AvgLen,StdDev,ExpSD,_MinLen,_MaxLen,_} = Stats,
+ ok = if
+ AvgLen > 1.2 ->
+ io:format("Table overloaded: Stats=~p\n~p\n",
+ [Stats, ets:info(T)]),
+ false;
+
+ Buckets>256, AvgLen < 0.47 ->
+ io:format("Table underloaded: Stats=~p\n~p\n",
+ [Stats, ets:info(T)]),
+ false;
+
+ StdDev > ExpSD*2 ->
+ io:format("Too large standard deviation (poor hashing?),"
+ " stats=~p\n~p\n",[Stats, ets:info(T)]),
+ false;
+
+ true ->
+ io:format("Stats = ~p\n",[Stats]),
+ ok
+ end
+ end.
%% ets:select on a tree with NIL key object.
otp_8732(Config) when is_list(Config) ->
- Tab = ets_new(noname,[ordered_set]),
- filltabstr(Tab,999),
- ets:insert(Tab,{[],"nasty NIL object"}),
- [] = ets:match(Tab,{'_',nomatch}), %% Will hang if bug not fixed
+ repeat_for_all_ord_set_table_types(
+ fun(Opts) ->
+ KeyRange = 999,
+ KeyFun = fun(K) -> integer_to_list(K) end,
+ Tab = ets_new(noname,Opts, KeyRange, KeyFun),
+ filltabstr(Tab, KeyRange),
+ ets:insert(Tab,{[],"nasty NIL object"}),
+ [] = ets:match(Tab,{'_',nomatch}) %% Will hang if bug not fixed
+ end),
ok.
%% Run concurrent select_delete (and inserts) on same table.
smp_select_delete(Config) when is_list(Config) ->
- T = ets_new(smp_select_delete,[named_table,public,{write_concurrency,true}]),
- Mod = 17,
- Zeros = erlang:make_tuple(Mod,0),
- InitF = fun(_) -> Zeros end,
- ExecF = fun(Diffs0) ->
- case rand:uniform(20) of
- 1 ->
- Mod = 17,
- Eq = rand:uniform(Mod) - 1,
- Deleted = ets:select_delete(T,
- [{{'_', '$1'},
- [{'=:=', {'rem', '$1', Mod}, Eq}],
- [true]}]),
- Diffs1 = setelement(Eq+1, Diffs0,
- element(Eq+1,Diffs0) - Deleted),
- Diffs1;
- _ ->
- Key = rand:uniform(10000),
- Eq = Key rem Mod,
- case ets:insert_new(T,{Key,Key}) of
- true ->
- Diffs1 = setelement(Eq+1, Diffs0,
- element(Eq+1,Diffs0)+1),
- Diffs1;
- false -> Diffs0
- end
- end
- end,
- FiniF = fun(Result) -> Result end,
- Results = run_sched_workers(InitF,ExecF,FiniF,20000),
- TotCnts = lists:foldl(fun(Diffs, Sum) -> add_lists(Sum,tuple_to_list(Diffs)) end,
- lists:duplicate(Mod, 0), Results),
- io:format("TotCnts = ~p\n",[TotCnts]),
- LeftInTab = lists:foldl(fun(N,Sum) -> Sum+N end,
- 0, TotCnts),
- io:format("LeftInTab = ~p\n",[LeftInTab]),
- LeftInTab = ets:info(T,size),
- lists:foldl(fun(Cnt,Eq) ->
- WasCnt = ets:select_count(T,
- [{{'_', '$1'},
- [{'=:=', {'rem', '$1', Mod}, Eq}],
- [true]}]),
- io:format("~p: ~p =?= ~p\n",[Eq,Cnt,WasCnt]),
- Cnt = WasCnt,
- Eq+1
- end,
- 0, TotCnts),
- %% May fail as select_delete does not shrink table (enough)
- %%verify_table_load(T),
- LeftInTab = ets:select_delete(T, [{{'$1','$1'}, [], [true]}]),
- 0 = ets:info(T,size),
- false = ets:info(T,fixed),
- ets:delete(T).
+ repeat_for_opts(fun smp_select_delete_do/1,
+ [[set,ordered_set,stim_cat_ord_set],
+ read_concurrency, compressed]).
+
+smp_select_delete_do(Opts) ->
+ KeyRange = 10000,
+ begin % indentation
+ T = ets_new(smp_select_delete,[named_table,public,{write_concurrency,true}|Opts],
+ KeyRange),
+ Mod = 17,
+ Zeros = erlang:make_tuple(Mod,0),
+ InitF = fun(_) -> Zeros end,
+ ExecF = fun(Diffs0) ->
+ case rand:uniform(20) of
+ 1 ->
+ Mod = 17,
+ Eq = rand:uniform(Mod) - 1,
+ Deleted = ets:select_delete(T,
+ [{{'_', '$1'},
+ [{'=:=', {'rem', '$1', Mod}, Eq}],
+ [true]}]),
+ Diffs1 = setelement(Eq+1, Diffs0,
+ element(Eq+1,Diffs0) - Deleted),
+ Diffs1;
+ _ ->
+ Key = rand:uniform(KeyRange),
+ Eq = Key rem Mod,
+ case ets:insert_new(T,{Key,Key}) of
+ true ->
+ Diffs1 = setelement(Eq+1, Diffs0,
+ element(Eq+1,Diffs0)+1),
+ Diffs1;
+ false -> Diffs0
+ end
+ end
+ end,
+ FiniF = fun(Result) -> Result end,
+ Results = run_sched_workers(InitF,ExecF,FiniF,20000),
+ TotCnts = lists:foldl(fun(Diffs, Sum) -> add_lists(Sum,tuple_to_list(Diffs)) end,
+ lists:duplicate(Mod, 0), Results),
+ io:format("TotCnts = ~p\n",[TotCnts]),
+ LeftInTab = lists:foldl(fun(N,Sum) -> Sum+N end,
+ 0, TotCnts),
+ io:format("LeftInTab = ~p\n",[LeftInTab]),
+ LeftInTab = ets:info(T,size),
+ lists:foldl(fun(Cnt,Eq) ->
+ WasCnt = ets:select_count(T,
+ [{{'_', '$1'},
+ [{'=:=', {'rem', '$1', Mod}, Eq}],
+ [true]}]),
+ io:format("~p: ~p =?= ~p\n",[Eq,Cnt,WasCnt]),
+ Cnt = WasCnt,
+ Eq+1
+ end,
+ 0, TotCnts),
+ %% May fail as select_delete does not shrink table (enough)
+ %%verify_table_load(T),
+ LeftInTab = ets:select_delete(T, [{{'$1','$1'}, [], [true]}]),
+ 0 = ets:info(T,size),
+ false = ets:info(T,fixed),
+ ets:delete(T)
+ end, % indentation
+ ok.
smp_select_replace(Config) when is_list(Config) ->
repeat_for_opts(fun smp_select_replace_do/1,
- [[set,ordered_set,duplicate_bag]]).
+ [[set,ordered_set,stim_cat_ord_set,duplicate_bag]]).
smp_select_replace_do(Opts) ->
+ KeyRange = 20,
T = ets_new(smp_select_replace,
- [public, {write_concurrency, true} | Opts]),
- ObjCount = 20,
+ [public, {write_concurrency, true} | Opts],
+ KeyRange),
InitF = fun (_) -> 0 end,
ExecF = fun (Cnt0) ->
- CounterId = rand:uniform(ObjCount),
+ CounterId = rand:uniform(KeyRange),
Match = [{{'$1', '$2'},
[{'=:=', '$1', CounterId}],
[{{'$1', {'+', '$2', 1}}}]}],
@@ -5839,15 +6037,143 @@ smp_select_replace_do(Opts) ->
FinalCounts = ets:select(T, [{{'_', '$1'}, [], ['$1']}]),
Total = lists:sum(FinalCounts),
Total = lists:sum(Results),
- ObjCount = ets:select_delete(T, [{{'_', '_'}, [], [true]}]),
+ KeyRange = ets:select_delete(T, [{{'_', '_'}, [], [true]}]),
0 = ets:info(T, size),
true = ets:delete(T),
ok.
+%% Iterate ordered_set with write_concurrency
+%% and make sure we hit all "stable" long lived keys
+%% while "volatile" objects are randomly inserted and deleted.
+smp_ordered_iteration(Config) when is_list(Config) ->
+ repeat_for_opts(fun smp_ordered_iteration_do/1,
+ [[cat_ord_set,stim_cat_ord_set]]).
+
+
+smp_ordered_iteration_do(Opts) ->
+ KeyRange = 1000,
+ OffHeap = erts_test_utils:mk_ext_pid({a@b,1}, 4711, 1),
+ KeyFun = fun(K, Type) ->
+ {K div 10, K rem 10, Type, OffHeap}
+ end,
+ StimKeyFun = fun(K) ->
+ KeyFun(K, element(rand:uniform(3),
+ {stable, other, volatile}))
+ end,
+ T = ets_new(smp_ordered_iteration, [public, {write_concurrency,true} | Opts],
+ KeyRange, StimKeyFun),
+ NStable = KeyRange div 4,
+ prefill_table(T, KeyRange, NStable, fun(K) -> {KeyFun(K, stable), 0} end),
+ NStable = ets:info(T, size),
+ NVolatile = KeyRange div 2,
+ prefill_table(T, KeyRange, NVolatile, fun(K) -> {KeyFun(K, volatile), 0} end),
+
+ InitF = fun (_) -> #{insert => 0, delete => 0,
+ select_delete_bk => 0, select_delete_pbk => 0,
+ select_replace_bk => 0, select_replace_pbk => 0}
+ end,
+ ExecF = fun (Counters) ->
+ K = rand:uniform(KeyRange),
+ Key = KeyFun(K, volatile),
+ Acc = case rand:uniform(22) of
+ R when R =< 10 ->
+ ets:insert(T, {Key}),
+ incr_counter(insert, Counters);
+ R when R =< 15 ->
+ ets:delete(T, Key),
+ incr_counter(delete, Counters);
+ R when R =< 19 ->
+ %% Delete bound key
+ ets:select_delete(T, [{{Key, '_'}, [], [true]}]),
+ incr_counter(select_delete_bk, Counters);
+ R when R =< 20 ->
+ %% Delete partially bound key
+ ets:select_delete(T, [{{{K div 10, '_', volatile, '_'}, '_'}, [], [true]}]),
+ incr_counter(select_delete_pbk, Counters);
+ R when R =< 21 ->
+ %% Replace bound key
+ ets:select_replace(T, [{{Key, '$1'}, [],
+ [{{{const,Key}, {'+','$1',1}}}]}]),
+ incr_counter(select_replace_bk, Counters);
+ _ ->
+ %% Replace partially bound key
+ ets:select_replace(T, [{{{K div 10, '_', volatile, '_'}, '$1'}, [],
+ [{{{element,1,'$_'}, {'+','$1',1}}}]}]),
+ incr_counter(select_replace_pbk, Counters)
+ end,
+ receive stop ->
+ [end_of_work | Acc]
+ after 0 ->
+ Acc
+ end
+ end,
+ FiniF = fun (Acc) -> Acc end,
+ Pids = run_sched_workers(InitF, ExecF, FiniF, infinite),
+ timer:send_after(1000, stop),
+
+ Log2ChunkMax = math:log2(NStable*2),
+ Rounds = fun Loop(N) ->
+ MS = [{{{'_', '_', stable, '_'}, '_'}, [], [true]}],
+ NStable = ets:select_count(T, MS),
+ NStable = count_stable(T, next, ets:first(T), 0),
+ NStable = count_stable(T, prev, ets:last(T), 0),
+ NStable = length(ets:select(T, MS)),
+ NStable = length(ets:select_reverse(T, MS)),
+ Chunk = round(math:pow(2, rand:uniform()*Log2ChunkMax)),
+ NStable = ets_select_chunks_count(T, MS, Chunk),
+ receive stop -> N
+ after 0 -> Loop(N+1)
+ end
+ end (1),
+ [P ! stop || P <- Pids],
+ Results = wait_pids(Pids),
+ io:format("Ops = ~p\n", [maps_sum(Results)]),
+ io:format("Diff = ~p\n", [ets:info(T,size) - NStable - NVolatile]),
+ io:format("Stats = ~p\n", [ets:info(T,stats)]),
+ io:format("Rounds = ~p\n", [Rounds]),
+ true = ets:delete(T),
+
+ %% Verify no leakage of offheap key data
+ ok = erts_test_utils:check_node_dist(),
+ ok.
+
+incr_counter(Name, Counters) ->
+ Counters#{Name => maps:get(Name, Counters, 0) + 1}.
+
+count_stable(T, Next, {_, _, stable, _}=Key, N) ->
+ count_stable(T, Next, ets:Next(T, Key), N+1);
+count_stable(T, Next, {_, _, volatile, _}=Key, N) ->
+ count_stable(T, Next, ets:Next(T, Key), N);
+count_stable(_, _, '$end_of_table', N) ->
+ N.
+
+ets_select_chunks_count(T, MS, Chunk) ->
+ ets_select_chunks_count(ets:select(T, MS, Chunk), 0).
+
+ets_select_chunks_count('$end_of_table', N) ->
+ N;
+ets_select_chunks_count({List, Continuation}, N) ->
+ ets_select_chunks_count(ets:select(Continuation),
+ length(List) + N).
+
+maps_sum([Ma | Tail]) when is_map(Ma) ->
+ maps_sum([lists:sort(maps:to_list(Ma)) | Tail]);
+maps_sum([La, Mb | Tail]) ->
+ Lab = lists:zipwith(fun({K,Va}, {K,Vb}) -> {K,Va+Vb} end,
+ La,
+ lists:sort(maps:to_list(Mb))),
+ maps_sum([Lab | Tail]);
+maps_sum([L]) ->
+ L.
+
+
+
+
%% Test different types.
types(Config) when is_list(Config) ->
init_externals(),
- repeat_for_opts(fun types_do/1, [[set,ordered_set],compressed]).
+ repeat_for_opts(fun types_do/1, [repeat_for_opts_atom2list(set_types),
+ compressed]).
types_do(Opts) ->
EtsMem = etsmem(),
@@ -5874,7 +6200,7 @@ types_do(Opts) ->
%% OTP-9932: Memory overwrite when inserting large integers in compressed bag.
%% Will crash with segv on 64-bit opt if not fixed.
otp_9932(Config) when is_list(Config) ->
- T = ets:new(xxx, [bag, compressed]),
+ T = ets_new(xxx, [bag, compressed]),
Fun = fun(N) ->
Key = {1316110174588445 bsl N,1316110174588583 bsl N},
S = {Key, Key},
@@ -5890,48 +6216,56 @@ otp_9932(Config) when is_list(Config) ->
%% vm-deadlock caused by race between ets:delete and others on
%% write_concurrency table.
otp_9423(Config) when is_list(Config) ->
- InitF = fun(_) -> {0,0} end,
- ExecF = fun({S,F}) ->
- receive
- stop ->
- io:format("~p got stop\n", [self()]),
- [end_of_work | {"Succeded=",S,"Failed=",F}]
- after 0 ->
- %%io:format("~p (~p) doing lookup\n", [self(), {S,F}]),
- try ets:lookup(otp_9423, key) of
- [] -> {S+1,F}
- catch
- error:badarg -> {S,F+1}
- end
- end
- end,
- FiniF = fun(R) -> R end,
- case run_smp_workers(InitF, ExecF, FiniF, infinite, 1) of
- Pids when is_list(Pids) ->
- %%[P ! start || P <- Pids],
- repeat(fun() -> ets:new(otp_9423, [named_table, public, {write_concurrency,true}]),
- ets:delete(otp_9423)
- end, 10000),
- [P ! stop || P <- Pids],
- wait_pids(Pids),
- ok;
+ repeat_for_all_non_stim_set_table_types(
+ fun(Opts) ->
+ InitF = fun(_) -> {0,0} end,
+ ExecF = fun({S,F}) ->
+ receive
+ stop ->
+ io:format("~p got stop\n", [self()]),
+ [end_of_work | {"Succeded=",S,"Failed=",F}]
+ after 0 ->
+ %%io:format("~p (~p) doing lookup\n", [self(), {S,F}]),
+ try ets:lookup(otp_9423, key) of
+ [] -> {S+1,F}
+ catch
+ error:badarg -> {S,F+1}
+ end
+ end
+ end,
+ FiniF = fun(R) -> R end,
+ case run_smp_workers(InitF, ExecF, FiniF, infinite, 1) of
+ Pids when is_list(Pids) ->
+ %%[P ! start || P <- Pids],
+ repeat(fun() -> ets_new(otp_9423, [named_table, public,
+ {write_concurrency,true}|Opts]),
+ ets:delete(otp_9423)
+ end, 10000),
+ [P ! stop || P <- Pids],
+ wait_pids(Pids),
+ ok;
+
+ Skipped -> Skipped
+ end
+ end).
- Skipped -> Skipped
- end.
%% Corrupted binary in compressed table
otp_10182(Config) when is_list(Config) ->
- Bin = <<"aHR0cDovL2hvb3RzdWl0ZS5jb20vYy9wcm8tYWRyb2xsLWFi">>,
- Key = {test, Bin},
- Value = base64:decode(Bin),
- In = {Key,Value},
- Db = ets:new(undefined, [set, protected, {read_concurrency, true}, compressed]),
- ets:insert(Db, In),
- [Out] = ets:lookup(Db, Key),
- io:format("In : ~p\nOut: ~p\n", [In,Out]),
- ets:delete(Db),
- In = Out.
+ repeat_for_opts_all_table_types(
+ fun(Opts) ->
+ Bin = <<"aHR0cDovL2hvb3RzdWl0ZS5jb20vYy9wcm8tYWRyb2xsLWFi">>,
+ Key = {test, Bin},
+ Value = base64:decode(Bin),
+ In = {Key,Value},
+ Db = ets_new(undefined, Opts),
+ ets:insert(Db, In),
+ [Out] = ets:lookup(Db, Key),
+ io:format("In : ~p\nOut: ~p\n", [In,Out]),
+ ets:delete(Db),
+ In = Out
+ end).
%% Test that ets:all include/exclude tables that we know are created/deleted
ets_all(Config) when is_list(Config) ->
@@ -6022,19 +6356,23 @@ take(Config) when is_list(Config) ->
ets:insert(T1, {{'not',<<"immediate">>},ok}),
[{{'not',<<"immediate">>},ok}] = ets:take(T1, {'not',<<"immediate">>}),
%% Same with ordered tables.
- T2 = ets_new(b, [ordered_set]),
- [] = ets:take(T2, foo),
- ets:insert(T2, {foo,bar}),
- [] = ets:take(T2, bar),
- [{foo,bar}] = ets:take(T2, foo),
- [] = ets:tab2list(T2),
- ets:insert(T2, {{'not',<<"immediate">>},ok}),
- [{{'not',<<"immediate">>},ok}] = ets:take(T2, {'not',<<"immediate">>}),
- %% Arithmetically-equal keys.
- ets:insert(T2, [{1.0,float},{2,integer}]),
- [{1.0,float}] = ets:take(T2, 1),
- [{2,integer}] = ets:take(T2, 2.0),
- [] = ets:tab2list(T2),
+ repeat_for_all_ord_set_table_types(
+ fun(Opts) ->
+ T2 = ets_new(b, Opts),
+ [] = ets:take(T2, foo),
+ ets:insert(T2, {foo,bar}),
+ [] = ets:take(T2, bar),
+ [{foo,bar}] = ets:take(T2, foo),
+ [] = ets:tab2list(T2),
+ ets:insert(T2, {{'not',<<"immediate">>},ok}),
+ [{{'not',<<"immediate">>},ok}] = ets:take(T2, {'not',<<"immediate">>}),
+ %% Arithmetically-equal keys.
+ ets:insert(T2, [{1.0,float},{2,integer}]),
+ [{1.0,float}] = ets:take(T2, 1),
+ [{2,integer}] = ets:take(T2, 2.0),
+ [] = ets:tab2list(T2),
+ ets:delete(T2)
+ end),
%% Same with bag.
T3 = ets_new(c, [bag]),
ets:insert(T3, [{1,1},{1,2},{3,3}]),
@@ -6042,7 +6380,6 @@ take(Config) when is_list(Config) ->
[{3,3}] = ets:take(T3, 3),
[] = ets:tab2list(T3),
ets:delete(T1),
- ets:delete(T2),
ets:delete(T3),
ok.
@@ -6077,9 +6414,372 @@ whereis_table(Config) when is_list(Config) ->
ok.
-%%
-%% Utility functions:
-%%
+
+%% The following work functions are used by
+%% throughput_benchmark/4. They are declared on the top level beacuse
+%% declaring them as function local funs cause a scalability issue.
+get_op([{_,O}], _RandNum) ->
+ O;
+get_op([{Prob,O}|Rest], RandNum) ->
+ case RandNum < Prob of
+ true -> O;
+ false -> get_op(Rest, RandNum)
+ end.
+do_op(Table, ProbHelpTab, Range, Operations) ->
+ RandNum = rand:uniform(),
+ Op = get_op(ProbHelpTab, RandNum),
+ #{ Op := TheOp} = Operations,
+ TheOp(Table, Range).
+do_work(WorksDoneSoFar, Table, ProbHelpTab, Range, Operations) ->
+ receive
+ stop -> WorksDoneSoFar
+ after
+ 0 -> do_op(Table, ProbHelpTab, Range, Operations),
+ do_work(WorksDoneSoFar + 1, Table, ProbHelpTab, Range, Operations)
+ end.
+
+prefill_table(T, KeyRange, Num, ObjFun) ->
+ Seed = rand:uniform(KeyRange),
+ %%io:format("prefill_table: Seed = ~p\n", [Seed]),
+ RState = unique_rand_start(KeyRange, Seed),
+ prefill_table_loop(T, RState, Num, ObjFun).
+
+prefill_table_loop(_, _, 0, _) ->
+ ok;
+prefill_table_loop(T, RS0, N, ObjFun) ->
+ {Key, RS1} = unique_rand_next(RS0),
+ ets:insert(T, ObjFun(Key)),
+ prefill_table_loop(T, RS1, N-1, ObjFun).
+
+throughput_benchmark() ->
+ throughput_benchmark(false, not_set, not_set).
+
+throughput_benchmark(TestMode, BenchmarkRunMs, RecoverTimeMs) ->
+ NrOfSchedulers = erlang:system_info(schedulers),
+ %% Definitions of operations that are supported by the benchmark
+ NextSeqOp =
+ fun (T, KeyRange, SeqSize) ->
+ Start = rand:uniform(KeyRange),
+ Last =
+ lists:foldl(
+ fun(_, Prev) ->
+ case Prev of
+ '$end_of_table'-> ok;
+ _ ->
+ try ets:next(T, Prev) of
+ Normal -> Normal
+ catch
+ error:badarg ->
+ % sets (not ordered_sets) cannot handle when the argument
+ % to next is not in the set
+ rand:uniform(KeyRange)
+ end
+ end
+ end,
+ Start,
+ lists:seq(1, SeqSize)),
+ case Last =:= -1 of
+ true -> io:format("Will never be printed");
+ false -> ok
+ end
+ end,
+ PartialSelectOp =
+ fun (T, KeyRange, SeqSize) ->
+ Start = rand:uniform(KeyRange),
+ Last = Start + SeqSize,
+ case -1 =:= ets:select_count(T,
+ ets:fun2ms(fun({X}) when X > Start andalso X =< Last -> true end)) of
+ true -> io:format("Will never be printed");
+ false -> ok
+ end
+
+ end,
+ %% Mapping benchmark operation names to their corresponding functions that do them
+ Operations =
+ #{insert =>
+ fun(T,KeyRange) ->
+ Num = rand:uniform(KeyRange),
+ ets:insert(T, {Num})
+ end,
+ delete =>
+ fun(T,KeyRange) ->
+ Num = rand:uniform(KeyRange),
+ ets:delete(T, Num)
+ end,
+ lookup =>
+ fun(T,KeyRange) ->
+ Num = rand:uniform(KeyRange),
+ ets:lookup(T, Num)
+ end,
+ nextseq10 =>
+ fun(T,KeyRange) -> NextSeqOp(T,KeyRange,10) end,
+ nextseq100 =>
+ fun(T,KeyRange) -> NextSeqOp(T,KeyRange,100) end,
+ nextseq1000 =>
+ fun(T,KeyRange) -> NextSeqOp(T,KeyRange,1000) end,
+ selectAll =>
+ fun(T,_KeyRange) ->
+ case -1 =:= ets:select_count(T, ets:fun2ms(fun(X) -> true end)) of
+ true -> io:format("Will never be printed");
+ false -> ok
+ end
+ end,
+ partial_select1000 =>
+ fun(T,KeyRange) -> PartialSelectOp(T,KeyRange,1000) end
+ },
+ %% Helper functions
+ CalculateThreadCounts = fun Calculate([Count|Rest]) ->
+ case Count > NrOfSchedulers of
+ true -> lists:reverse(Rest);
+ false -> Calculate([Count*2,Count|Rest])
+ end
+ end,
+ CalculateOpsProbHelpTab =
+ fun Calculate([{_, OpName}], _) ->
+ [{1.0, OpName}];
+ Calculate([{OpPropability, OpName}|Res], Current) ->
+ NewCurrent = Current + OpPropability,
+ [{NewCurrent, OpName}| Calculate(Res, NewCurrent)]
+ end,
+ RenderScenario =
+ fun R([], StringSoFar) ->
+ StringSoFar;
+ R([{Fraction, Operation}], StringSoFar) ->
+ io_lib:format("~s ~f% ~p",[StringSoFar, Fraction * 100.0, Operation]);
+ R([{Fraction, Operation}|Rest], StringSoFar) ->
+ R(Rest,
+ io_lib:format("~s ~f% ~p, ",[StringSoFar, Fraction * 100.0, Operation]))
+ end,
+ SafeFixTableIfRequired =
+ fun(Table, Scenario, On) ->
+ case set =:= ets:info(Table, type) of
+ true ->
+ HasNotRequiringOp =
+ lists:search(
+ fun({_,nextseq10}) -> true;
+ ({_,nextseq100}) -> true;
+ ({_,nextseq1000}) -> true;
+ (_) -> false
+ end, Scenario),
+ case HasNotRequiringOp of
+ false -> ok;
+ _ -> ets:safe_fixtable(Table, On)
+ end;
+ false -> ok
+ end
+ end,
+ %% Function that runs a benchmark instance and returns the number
+ %% of operations that were performed
+ RunBenchmark =
+ fun(NrOfProcs, TableConfig, Scenario,
+ Range, Duration, RecoverTime) ->
+ ProbHelpTab = CalculateOpsProbHelpTab(Scenario, 0),
+ Table = ets:new(t, TableConfig),
+ Nobj = Range div 2,
+ prefill_table(Table, Range, Nobj, fun(K) -> {K} end),
+ Nobj = ets:info(Table, size),
+ SafeFixTableIfRequired(Table, Scenario, true),
+ ParentPid = self(),
+ ChildPids =
+ lists:map(
+ fun(_N) ->
+ spawn(fun() ->
+ receive start -> ok end,
+ WorksDone =
+ do_work(0, Table, ProbHelpTab, Range, Operations),
+ ParentPid ! WorksDone
+ end)
+ end, lists:seq(1, NrOfProcs)),
+ lists:foreach(fun(Pid) -> Pid ! start end, ChildPids),
+ timer:sleep(Duration),
+ lists:foreach(fun(Pid) -> Pid ! stop end, ChildPids),
+ TotalWorksDone = lists:foldl(
+ fun(_, Sum) ->
+ receive
+ Count -> Sum + Count
+ end
+ end, 0, ChildPids),
+ SafeFixTableIfRequired(Table, Scenario, false),
+ ets:delete(Table),
+ timer:sleep(RecoverTime),
+ TotalWorksDone
+ end,
+ %%
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %%%% Benchmark Configuration %%%%%%%%%%%%%%%%%%%%%%%%
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %%
+ %% Change the following variables to configure the benchmark runs
+ ThreadCounts =
+ case TestMode of
+ true -> [1, NrOfSchedulers];
+ false -> CalculateThreadCounts([1])
+ end,
+ KeyRanges = % Sizes of the key ranges
+ case TestMode of
+ true -> [50000];
+ false -> [1000000]
+ end,
+ Duration =
+ case BenchmarkRunMs of % Duration of a benchmark run in milliseconds
+ not_set -> 30000;
+ _ -> BenchmarkRunMs
+ end,
+ TimeMsToSleepAfterEachBenchmarkRun =
+ case RecoverTimeMs of
+ not_set -> 1000;
+ _ -> RecoverTimeMs
+ end,
+ TableTypes = % The table types that will be benchmarked
+ [
+ [ordered_set, public],
+ [ordered_set, public, {write_concurrency, true}],
+ [ordered_set, public, {read_concurrency, true}],
+ [ordered_set, public, {write_concurrency, true}, {read_concurrency, true}],
+ [set, public],
+ [set, public, {write_concurrency, true}],
+ [set, public, {read_concurrency, true}],
+ [set, public, {write_concurrency, true}, {read_concurrency, true}]
+ ],
+ Scenarios = % Benchmark scenarios (the fractions should add up to approximately 1.0)
+ [
+ [
+ {0.5, insert},
+ {0.5, delete}
+ ],
+ [
+ {0.1, insert},
+ {0.1, delete},
+ {0.8, lookup}
+ ],
+ [
+ {0.01, insert},
+ {0.01, delete},
+ {0.98, lookup}
+ ],
+ [
+ {1.0, lookup}
+ ],
+ [
+ {0.1, insert},
+ {0.1, delete},
+ {0.4, lookup},
+ {0.4, nextseq10}
+ ],
+ [
+ {0.1, insert},
+ {0.1, delete},
+ {0.4, lookup},
+ {0.4, nextseq100}
+ ],
+ [
+ {0.1, insert},
+ {0.1, delete},
+ {0.4, lookup},
+ {0.4, nextseq1000}
+ ],
+ [
+ {1.0, nextseq1000}
+ ],
+ [
+ {0.1, insert},
+ {0.1, delete},
+ {0.79, lookup},
+ {0.01, selectAll}
+ ],
+ [
+ {0.1, insert},
+ {0.1, delete},
+ {0.7999, lookup},
+ {0.0001, selectAll}
+ ],
+ [
+ {0.1, insert},
+ {0.1, delete},
+ {0.799999, lookup},
+ {0.000001, selectAll}
+ ],
+ [
+ {0.1, insert},
+ {0.1, delete},
+ {0.79, lookup},
+ {0.01, partial_select1000}
+ ],
+ [
+ {0.1, insert},
+ {0.1, delete},
+ {0.7999, lookup},
+ {0.0001, partial_select1000}
+ ],
+ [
+ {0.1, insert},
+ {0.1, delete},
+ {0.799999, lookup},
+ {0.000001, partial_select1000}
+ ]
+ ],
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %%%% End of Benchmark Configuration %%%%%%%%%%%%%%%%
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %% Prepare for memory check
+ EtsMem = case TestMode of
+ true -> etsmem();
+ false -> ok
+ end,
+ %% Run the benchmark
+ io:format("# Each instance of the benchmark runs for ~w seconds:~n", [Duration/1000]),
+ io:format("# The result of a benchmark instance is presented as a number representing~n"),
+ io:format("# the number of operations performed per second:~n~n~n"),
+ io:format("# To plot graphs for the results below:~n"),
+ io:format("# 1. Open \"$ERL_TOP/lib/stdlib/test/ets_SUITE_data/visualize_throughput.html\" in a web browser~n"),
+ io:format("# 2. Copy the lines between \"#BENCHMARK STARTED$\" and \"#BENCHMARK ENDED$\" below~n"),
+ io:format("# 3. Paste the lines copied in step 2 to the text box in the browser window opened in~n"),
+ io:format("# step 1 and press the Render button~n~n"),
+ io:format("#BENCHMARK STARTED$~n"),
+ %% The following loop runs all benchmark scenarios and prints the results (i.e, operations/second)
+ lists:foreach(
+ fun(KeyRange) ->
+ lists:foreach(
+ fun(Scenario) ->
+ io:format("Scenario: ~s | Key Range Size: ~w$~n",
+ [RenderScenario(Scenario, ""),
+ KeyRange]),
+ lists:foreach(
+ fun(ThreadCount) ->
+ io:format("; ~w",[ThreadCount])
+ end,
+ ThreadCounts),
+ io:format("$~n",[]),
+ lists:foreach(
+ fun(TableType) ->
+ io:format("~w ",[TableType]),
+ lists:foreach(
+ fun(ThreadCount) ->
+ Result = RunBenchmark(ThreadCount,
+ TableType,
+ Scenario,
+ KeyRange,
+ Duration,
+ TimeMsToSleepAfterEachBenchmarkRun),
+ io:format("; ~f",[Result/(Duration/1000.0)])
+ end,
+ ThreadCounts),
+ io:format("$~n",[])
+ end,
+ TableTypes)
+ end,
+ Scenarios)
+ end,
+ KeyRanges),
+ io:format("~n#BENCHMARK ENDED$~n~n"),
+ case TestMode of
+ true -> verify_etsmem(EtsMem);
+ false -> ok
+ end.
+
+test_throughput_benchmark(Config) when is_list(Config) ->
+ throughput_benchmark(true, 100, 0).
+
add_lists(L1,L2) ->
add_lists(L1,L2,[]).
@@ -6140,8 +6840,11 @@ wait_pids(Pids, Acc) ->
{Pid,Result} ->
true = lists:member(Pid,Pids),
Others = lists:delete(Pid,Pids),
- io:format("wait_pid got ~p from ~p, still waiting for ~p\n",[Result,Pid,Others]),
+ %%io:format("wait_pid got ~p from ~p\n",[Result,Pid]),
wait_pids(Others,[Result | Acc])
+ after 60*1000 ->
+ io:format("Still waiting for workers ~p\n",[Pids]),
+ wait_pids(Pids, Acc)
end.
@@ -6165,48 +6868,25 @@ wait_for_memory_deallocations() ->
wait_for_memory_deallocations()
end.
-
etsmem() ->
- wait_for_memory_deallocations(),
-
- AllTabs = lists:map(fun(T) -> {T,ets:info(T,name),ets:info(T,size),
- ets:info(T,memory),ets:info(T,type)}
- end, ets:all()),
-
- EtsAllocInfo = erlang:system_info({allocator,ets_alloc}),
- ErlangMemoryEts = try erlang:memory(ets) catch error:notsup -> notsup end,
-
- Mem =
- {ErlangMemoryEts,
- case EtsAllocInfo of
- false -> undefined;
- MemInfo ->
- CS = lists:foldl(
- fun ({instance, _, L}, Acc) ->
- {value,{mbcs,MBCS}} = lists:keysearch(mbcs, 1, L),
- {value,{sbcs,SBCS}} = lists:keysearch(sbcs, 1, L),
- NewAcc = [MBCS, SBCS | Acc],
- case lists:keysearch(mbcs_pool, 1, L) of
- {value,{mbcs_pool, MBCS_POOL}} ->
- [MBCS_POOL|NewAcc];
- _ -> NewAcc
- end
- end,
- [],
- MemInfo),
- lists:foldl(
- fun(L, {Bl0,BlSz0}) ->
- {value,BlTup} = lists:keysearch(blocks, 1, L),
- blocks = element(1, BlTup),
- Bl = element(2, BlTup),
- {value,BlSzTup} = lists:keysearch(blocks_size, 1, L),
- blocks_size = element(1, BlSzTup),
- BlSz = element(2, BlSzTup),
- {Bl0+Bl,BlSz0+BlSz}
- end, {0,0}, CS)
- end},
- {Mem,AllTabs}.
+ % The following is done twice to avoid an inconsistent memory
+ % "snapshot" (see verify_etsmem/2).
+ lists:foldl(
+ fun(_,_) ->
+ wait_for_memory_deallocations(),
+ AllTabs = lists:map(fun(T) -> {T,ets:info(T,name),ets:info(T,size),
+ ets:info(T,memory),ets:info(T,type)}
+ end, ets:all()),
+
+ EtsAllocSize = erts_debug:alloc_blocks_size(ets_alloc),
+ ErlangMemoryEts = try erlang:memory(ets) catch error:notsup -> notsup end,
+
+ Mem = {ErlangMemoryEts, EtsAllocSize},
+ {Mem, AllTabs}
+ end,
+ not_used,
+ lists:seq(1,2)).
verify_etsmem(MI) ->
wait_for_test_procs(),
@@ -6227,15 +6907,15 @@ verify_etsmem({MemInfo,AllTabs}, Try) ->
end;
{MemInfo2, AllTabs2} ->
- io:format("Expected: ~p", [MemInfo]),
- io:format("Actual: ~p", [MemInfo2]),
- io:format("Changed tables before: ~p\n",[AllTabs -- AllTabs2]),
- io:format("Changed tables after: ~p\n", [AllTabs2 -- AllTabs]),
+ io:format("#Expected: ~p", [MemInfo]),
+ io:format("#Actual: ~p", [MemInfo2]),
+ io:format("#Changed tables before: ~p\n",[AllTabs -- AllTabs2]),
+ io:format("#Changed tables after: ~p\n", [AllTabs2 -- AllTabs]),
case Try < 2 of
true ->
- io:format("\nThis discrepancy could be caused by an "
+ io:format("\n#This discrepancy could be caused by an "
"inconsistent memory \"snapshot\""
- "\nTry again...\n", []),
+ "\n#Try again...\n", []),
verify_etsmem({MemInfo, AllTabs}, Try+1);
false ->
ct:fail("Failed memory check")
@@ -6709,22 +7389,49 @@ make_unaligned_sub_binary(List) ->
repeat_for_opts(F) ->
repeat_for_opts(F, [write_concurrency, read_concurrency, compressed]).
+repeat_for_opts_all_table_types(F) ->
+ repeat_for_opts(F, [all_types, write_concurrency, read_concurrency, compressed]).
+
+repeat_for_opts_all_non_stim_table_types(F) ->
+ repeat_for_opts(F, [all_non_stim_types, write_concurrency, read_concurrency, compressed]).
+
+repeat_for_opts_all_set_table_types(F) ->
+ repeat_for_opts(F, [set_types, write_concurrency, read_concurrency, compressed]).
+
+repeat_for_all_set_table_types(F) ->
+ repeat_for_opts(F, [set_types]).
+
+repeat_for_all_ord_set_table_types(F) ->
+ repeat_for_opts(F, [ord_set_types]).
+
+repeat_for_all_non_stim_set_table_types(F) ->
+ repeat_for_opts(F, [all_non_stim_set_types]).
+
+repeat_for_opts_all_ord_set_table_types(F) ->
+ repeat_for_opts(F, [ord_set_types, write_concurrency, read_concurrency, compressed]).
+
repeat_for_opts(F, OptGenList) when is_function(F, 1) ->
repeat_for_opts(F, OptGenList, []).
repeat_for_opts(F, [], Acc) ->
lists:foldl(fun(Opts, RV_Acc) ->
OptList = lists:filter(fun(E) -> E =/= void end, Opts),
- io:format("Calling with options ~p\n",[OptList]),
- RV = F(OptList),
- case RV_Acc of
- {comment,_} -> RV_Acc;
- _ -> case RV of
- {comment,_} -> RV;
- _ -> [RV | RV_Acc]
- end
- end
- end, [], Acc);
+ case is_redundant_opts_combo(OptList) of
+ true ->
+ %%io:format("Ignoring redundant options ~p\n",[OptList]),
+ ok;
+ false ->
+ io:format("Calling with options ~p\n",[OptList]),
+ RV = F(OptList),
+ case RV_Acc of
+ {comment,_} -> RV_Acc;
+ _ -> case RV of
+ {comment,_} -> RV;
+ _ -> [RV | RV_Acc]
+ end
+ end
+ end
+ end, [], Acc);
repeat_for_opts(F, [OptList | Tail], []) when is_list(OptList) ->
repeat_for_opts(F, Tail, [[Opt] || Opt <- OptList]);
repeat_for_opts(F, [OptList | Tail], AccList) when is_list(OptList) ->
@@ -6732,14 +7439,127 @@ repeat_for_opts(F, [OptList | Tail], AccList) when is_list(OptList) ->
repeat_for_opts(F, [Atom | Tail], AccList) when is_atom(Atom) ->
repeat_for_opts(F, [repeat_for_opts_atom2list(Atom) | Tail ], AccList).
-repeat_for_opts_atom2list(all_types) -> [set,ordered_set,bag,duplicate_bag];
+repeat_for_opts_atom2list(set_types) -> [set,ordered_set,stim_cat_ord_set,cat_ord_set];
+repeat_for_opts_atom2list(ord_set_types) -> [ordered_set,stim_cat_ord_set,cat_ord_set];
+repeat_for_opts_atom2list(all_types) -> [set,ordered_set,stim_cat_ord_set,cat_ord_set,bag,duplicate_bag];
+repeat_for_opts_atom2list(all_non_stim_types) -> [set,ordered_set,cat_ord_set,bag,duplicate_bag];
+repeat_for_opts_atom2list(all_non_stim_set_types) -> [set,ordered_set,cat_ord_set];
repeat_for_opts_atom2list(write_concurrency) -> [{write_concurrency,false},{write_concurrency,true}];
repeat_for_opts_atom2list(read_concurrency) -> [{read_concurrency,false},{read_concurrency,true}];
repeat_for_opts_atom2list(compressed) -> [compressed,void].
-ets_new(Name, Opts) ->
- %%ets:new(Name, [compressed | Opts]).
- ets:new(Name, Opts).
+is_redundant_opts_combo(Opts) ->
+ (lists:member(stim_cat_ord_set, Opts) orelse
+ lists:member(cat_ord_set, Opts))
+ andalso
+ (lists:member({write_concurrency, false}, Opts) orelse
+ lists:member(private, Opts) orelse
+ lists:member(protected, Opts)).
+
+%% Add fake table option with info about key range.
+%% Will be consumed by ets_new and used for stim_cat_ord_set.
+key_range(Opts, KeyRange) ->
+ [{key_range, KeyRange} | Opts].
+
+ets_new(Name, Opts0) ->
+ {KeyRange, Opts1} = case lists:keytake(key_range, 1, Opts0) of
+ {value, {key_range, KR}, Rest1} ->
+ {KR, Rest1};
+ false ->
+ {1000*1000, Opts0}
+ end,
+ ets_new(Name, Opts1, KeyRange).
+
+ets_new(Name, Opts, KeyRange) ->
+ ets_new(Name, Opts, KeyRange, fun id/1).
+
+ets_new(Name, Opts0, KeyRange, KeyFun) ->
+ {CATree, Stimulate, RevOpts} =
+ lists:foldl(fun(cat_ord_set, {false, false, Lacc}) ->
+ {true, false, [ordered_set | Lacc]};
+ (stim_cat_ord_set, {false, false, Lacc}) ->
+ {true, true, [ordered_set | Lacc]};
+ (Other, {CAT, STIM, Lacc}) ->
+ {CAT, STIM, [Other | Lacc]}
+ end,
+ {false, false, []},
+ Opts0),
+ Opts = lists:reverse(RevOpts),
+ EtsNewHelper =
+ fun (UseOpts) ->
+ case get(ets_new_opts) of
+ UseOpts ->
+ silence; %% suppress identical table opts spam
+ _ ->
+ put(ets_new_opts, UseOpts),
+ io:format("ets:new(~p, ~p)~n", [Name, UseOpts])
+ end,
+ ets:new(Name, UseOpts)
+ end,
+ case CATree andalso
+ (not lists:member({write_concurrency, false}, Opts)) andalso
+ (not lists:member(private, Opts)) andalso
+ (not lists:member(protected, Opts)) of
+ true ->
+ NewOpts1 =
+ case lists:member({write_concurrency, true}, Opts) of
+ true -> Opts;
+ false -> [{write_concurrency, true}|Opts]
+ end,
+ NewOpts2 =
+ case lists:member(public, NewOpts1) of
+ true -> NewOpts1;
+ false -> [public|NewOpts1]
+ end,
+ T = EtsNewHelper(NewOpts2),
+ case Stimulate of
+ false -> ok;
+ true -> stimulate_contention(T, KeyRange, KeyFun)
+ end,
+ T;
+ false ->
+ EtsNewHelper(Opts)
+ end.
+
+% The purpose of this function is to stimulate fine grained locking in
+% tables of types ordered_set with the write_concurrency options
+% turned on. The erts_debug feature 'ets_force_split' is used to easier
+% generate a routing tree with fine grained locking without having to
+% provoke lots of actual lock contentions.
+stimulate_contention(Tid, KeyRange, KeyFun) ->
+ T = case Tid of
+ A when is_atom(A) -> ets:whereis(A);
+ _ -> Tid
+ end,
+ erts_debug:set_internal_state(ets_force_split, {T, true}),
+ Num = case KeyRange > 50 of
+ true -> 50;
+ false -> KeyRange
+ end,
+ Seed = rand:uniform(KeyRange),
+ %%io:format("prefill_table: Seed = ~p\n", [Seed]),
+ RState = unique_rand_start(KeyRange, Seed),
+ stim_inserter_loop(T, RState, Num, KeyFun),
+ Num = ets:info(T, size),
+ ets:match_delete(T, {'$1','$1','$1'}),
+ 0 = ets:info(T, size),
+ erts_debug:set_internal_state(ets_force_split, {T, false}),
+ case ets:info(T,stats) of
+ {0, _, _} ->
+ io:format("No routing nodes in table?\n"
+ "Debug feature 'ets_force_split' does not seem to work.\n", []),
+ ct:fail("No ets_force_split?");
+ Stats ->
+ io:format("stimulated ordered_set: ~p\n", [Stats])
+ end.
+
+stim_inserter_loop(_, _, 0, _) ->
+ ok;
+stim_inserter_loop(T, RS0, N, KeyFun) ->
+ {K, RS1} = unique_rand_next(RS0),
+ Key = KeyFun(K),
+ ets:insert(T, {Key, Key, Key}),
+ stim_inserter_loop(T, RS1, N-1, KeyFun).
do_tc(Do, Report) ->
T1 = erlang:monotonic_time(),
@@ -6753,3 +7573,50 @@ syrup_factor() ->
valgrind -> 20;
_ -> 1
end.
+
+
+%%
+%% This is a pseudo random number generator for UNIQUE integers.
+%% All integers between 1 and Max will be generated before it repeat itself.
+%% It's a variant of this one using quadratic residues by Jeff Preshing:
+%% http://preshing.com/20121224/how-to-generate-a-sequence-of-unique-random-integers/
+%%
+unique_rand_start(Max, Seed) ->
+ L = lists:dropwhile(fun(P) -> P < Max end,
+ primes_3mod4()),
+ [P | _] = case L of
+ [] ->
+ error("Random range too large");
+ _ ->
+ L
+ end,
+ 3 = P rem 4,
+ {0, {Max, P, Seed}}.
+
+unique_rand_next({N, {Max, P, Seed}=Const}) ->
+ case dquad(P, N, Seed) + 1 of
+ RND when RND > Max -> % Too large, skip
+ unique_rand_next({N+1, Const});
+ RND ->
+ {RND, {N+1, Const}}
+ end.
+
+%% A one-to-one relation between all integers 0 =< X < Prime
+%% if Prime rem 4 == 3.
+quad(Prime, X) ->
+ Rem = X*X rem Prime,
+ case 2*X < Prime of
+ true ->
+ Rem;
+ false ->
+ Prime - Rem
+ end.
+
+dquad(Prime, X, Seed) ->
+ quad(Prime, (quad(Prime, X) + Seed) rem Prime).
+
+%% Primes where P rem 4 == 3.
+primes_3mod4() ->
+ [103, 211, 503, 1019, 2003, 5003, 10007, 20011, 50023,
+ 100003, 200003, 500083, 1000003, 2000003, 5000011,
+ 10000019, 20000003, 50000047, 100000007].
diff --git a/lib/stdlib/test/ets_SUITE_data/visualize_throughput.html b/lib/stdlib/test/ets_SUITE_data/visualize_throughput.html
new file mode 100644
index 0000000000..a2c61aa938
--- /dev/null
+++ b/lib/stdlib/test/ets_SUITE_data/visualize_throughput.html
@@ -0,0 +1,253 @@
+<!doctype html>
+<html lang="en">
+
+<!-- %% -->
+<!-- %% %CopyrightBegin% -->
+<!-- %% -->
+<!-- %% Copyright Ericsson AB and Kjell Winblad 1996-2018. All 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% -->
+<!-- %% -->
+<!-- %% Author: Kjell Winblad -->
+<!-- %% -->
+
+ <head>
+ <meta charset="utf-8">
+ <title>ETS Benchmark Result Viewer</title>
+ </head>
+
+ <body>
+ <div id="insertPlaceholder"></div>
+ <h1>ETS Benchmark Result Viewer</h1>
+ <p>
+ This page generates graphs from data produced by the ETS benchmark which is defined in the function <code>ets_SUITE:throughput_benchmark/0</code> (see "<code>$ERL_TOP/lib/stdlib/test/ets_SUITE.erl</code>").
+ </p>
+ <p>
+ Note that one can paste results from several benchmark runs into the field below. Results from the same scenario but from different benchmark runs will be relabeled and ploted in the same graph automatically. This makes comparisons of different ETS versions easy.
+ </p>
+ <p>
+ Note also that that lines can be hidden by clicking on the corresponding label.
+ </p>
+ Paste the generated data in the field below and press the Render button:
+ <br>
+ <textarea id="dataField" rows="4" cols="50"></textarea>
+ <br>
+ <input type="checkbox" id="barPlot"> Bar Plot
+ <br>
+ <input type="checkbox" id="sameSpacing" checked> Same X Spacing Between Points
+ <br>
+ <input type="checkbox" class="showCheck" value="[ordered_set,public]" checked> Show <code>[ordered_set,public]</code>
+ <br>
+ <input type="checkbox" class="showCheck" value="[ordered_set,public,{write_concurrency,true}]" checked> Show <code>[ordered_set,public,{write_concurrency,true}]</code>
+ <br>
+ <input type="checkbox" class="showCheck" value="[ordered_set,public,{read_concurrency,true}]" checked> Show <code>[ordered_set,public,{read_concurrency,true}]</code>
+ <br>
+ <input type="checkbox" class="showCheck" value="[ordered_set,public,{write_concurrency,true},{read_concurrency,true}]" checked> Show <code>[ordered_set,public,{write_concurrency,true},{read_concurrency,true}]</code>
+ <br>
+ <input type="checkbox" class="showCheck" value="[set,public]"> Show <code>[set,public]</code>
+ <br>
+ <input type="checkbox" class="showCheck" value="[set,public,{write_concurrency,true}]"> Show <code>[set,public,{write_concurrency,true}]</code>
+ <br>
+ <input type="checkbox" class="showCheck" value="[set,public,{read_concurrency,true}]"> Show <code>[set,public,{read_concurrency,true}]</code>
+ <br>
+ <input type="checkbox" class="showCheck" value="[set,public,{write_concurrency,true},{read_concurrency,true}]"> Show <code>[set,public,{write_concurrency,true},{read_concurrency,true}]</code>
+ <br>
+ <button id="renderButton" type="button">Render</button>
+
+ <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
+ integrity="sha256-3edrmyuQ0w65f8gfBsqowzjJe2iM6n0nKciPUp8y+7E="
+ crossorigin="anonymous"></script>
+ <script>
+ var loading = false;
+ function toggleLoadingScreen(){
+ if(loading){
+ $("#loading").remove();
+ loading = false;
+ }else{
+ $('<div id="loading">'+
+ '<span style="position: fixed; top: 50%;left: 50%;color: white;"><b>Loading...</b></span>'+
+ '</div>')
+ .css({position: "fixed",
+ top: 0,
+ left: 0,
+ width: "100%",
+ height: "100%",
+ 'background-color': "#000",
+ filter:"alpha(opacity=50)",
+ '-moz-opacity':"0.5",
+ '-khtml-opacity': "0.5",
+ opacity: "0.5",
+ 'z-index': "10000"})
+ .appendTo(document.body);
+ loading = true;
+
+ }
+ }
+ //Start loading screen before downloading plotly which is quite large
+ toggleLoadingScreen();
+ </script>
+ <script src="https://cdn.plot.ly/plotly-1.5.0.min.js"></script>
+ <script>
+ String.prototype.replaceAll = function(search, replacement) {
+ var target = this;
+ return target.split(search).join(replacement);
+ };
+ String.prototype.myTrim = function() {
+ var target = this;
+ return target.replace(/^\s+|\s+$/g, '');
+ };
+ function plotGraph(lines, sameSpacing, barPlot, prefix) {
+ var xvals = null;
+ var data = [];
+ while(lines.length > 0 &&
+ (lines[0].myTrim() == "" ||
+ lines[0].myTrim().indexOf(";") !== -1)){
+ var line = lines.shift().myTrim();
+ if(line == "" || line.startsWith("#")){
+ continue;
+ } else if(line.startsWith(";")) {
+ xvals = line.split(";")
+ xvals.shift(); // Remove first
+ xvals = $.map(xvals, function (i){
+ if(sameSpacing){
+ return "_"+i.myTrim();
+ }else{
+ return parseInt(i.myTrim(), 10);
+ }
+ });
+ }else{
+ line = line.split(";")
+ var label = prefix + line.shift().myTrim();
+ var yvals = $.map(line, function (i){
+ return parseFloat(i.myTrim(), 10);
+ });
+ var trace = {
+ x: xvals,
+ y: yvals,
+ mode: 'lines+markers',
+ name: label
+ };
+ if(barPlot){
+ trace['type'] = "bar";
+ }
+ data.push(trace);
+ }
+
+ }
+ return data;
+ }
+ function plotGraphs(){
+ var insertPlaceholder = $("#insertPlaceholder");
+ var sameSpacing = $('#sameSpacing').is(":checked");
+ var barPlot = $('#barPlot').is(":checked");
+ var lines = $("#dataField").val();
+ $('.showCheck').each(function() {
+ var item = $(this);
+ if(!item.is(":checked")){
+ lines = lines.replaceAll(item.val(), "#"+item.val())
+ }
+ });
+ lines = lines.split("$");
+ var nrOfGraphs = 0;
+ var scenarioDataMap = {};
+ var scenarioNrOfVersionsMap = {};
+ var scenarioList = [];
+ while(lines.length > 0){
+ var line = lines.shift().myTrim();
+ if(line == ""){
+ continue;
+ } else if(line.startsWith("Scenario:")) {
+ nrOfGraphs = nrOfGraphs + 1;
+ var name = line;
+ if(scenarioDataMap[name] === undefined){
+ scenarioDataMap[name] = [];
+ scenarioNrOfVersionsMap[name] = 0;
+ scenarioList.push(line);
+ }
+ scenarioNrOfVersionsMap[name] = scenarioNrOfVersionsMap[name] + 1;
+ var prefix = undefined;
+ if(scenarioNrOfVersionsMap[name] === 1){
+ prefix = "";
+ }else{
+ prefix = "Ver: " + scenarioNrOfVersionsMap[name] + " ";
+ }
+ scenarioDataMap[name] =
+ scenarioDataMap[name].concat(
+ plotGraph(lines, sameSpacing, barPlot, prefix));
+ }
+ }
+ $.each(scenarioList,
+ function( index, name ) {
+ var nrOfGraphs = index + 1;
+ var data = scenarioDataMap[name];
+ $( "<div class='added' id='graph"+nrOfGraphs+"'>")
+ .insertBefore( insertPlaceholder );
+ $( "<button type='button' class='added' id='fullscreenButton"+nrOfGraphs+"'>Fill screen</button>")
+ .insertBefore( insertPlaceholder );
+ $( "<span class='added'><br><hr><br></span>")
+ .insertBefore( insertPlaceholder );
+ var layout = {
+ title:name,
+ xaxis: {
+ title: '# of Processes'
+ },
+ yaxis: {
+ title: 'Operations/Second'
+ }
+
+ };
+
+ $("#fullscreenButton"+nrOfGraphs).click(
+ function(){
+ $('#graph'+nrOfGraphs).replaceWith(
+ $("<div class='added' id='graph"+nrOfGraphs+"'>"));
+ layout = $.extend({}, layout, {
+ width:$(window).width()-40,
+ height:$(window).height()-40
+ });
+ Plotly.newPlot('graph'+nrOfGraphs, data, layout);
+ });
+ Plotly.newPlot('graph'+nrOfGraphs, data, layout);
+
+ });
+
+
+ }
+ $(document).ready(function(){
+ $('#renderButton').click(
+ function(){
+ toggleLoadingScreen();
+ setTimeout(function(){
+ try {
+ $( ".added" ).remove();
+ plotGraphs();
+ toggleLoadingScreen();
+ } catch(e){
+ toggleLoadingScreen();
+ console.log(e);
+ alert("Error happened when parsing data.\n" +
+ "See console for more info");
+ }
+ }, 10);
+ });
+ setTimeout(function(){
+ $( ".added" ).remove();
+ plotGraphs();
+ toggleLoadingScreen();
+ }, 10);
+ });
+ </script>
+ </body>
+</html>
diff --git a/lib/stdlib/test/gen_fsm_SUITE.erl b/lib/stdlib/test/gen_fsm_SUITE.erl
index a8264e5a84..62d9d0e0ae 100644
--- a/lib/stdlib/test/gen_fsm_SUITE.erl
+++ b/lib/stdlib/test/gen_fsm_SUITE.erl
@@ -521,14 +521,16 @@ error_format_status(Config) when is_list(Config) ->
error_logger_forwarder:register(),
OldFl = process_flag(trap_exit, true),
StateData = "called format_status",
+ Parent = self(),
{ok, Pid} = gen_fsm:start(gen_fsm_SUITE, {state_data, StateData}, []),
%% bad return value in the gen_fsm loop
{'EXIT',{{bad_return_value, badreturn},_}} =
(catch gen_fsm:sync_send_event(Pid, badreturn)),
receive
{error,_GroupLeader,{Pid,
- "** State machine"++_,
- [Pid,{_,_,badreturn},idle,{formatted,StateData},_]}} ->
+ "** State machine "++_,
+ [Pid,badreturn,Parent,idle,{formatted,StateData},
+ {bad_return_value,badreturn}|_]}} ->
ok;
Other ->
io:format("Unexpected: ~p", [Other]),
@@ -541,12 +543,14 @@ terminate_crash_format(Config) when is_list(Config) ->
error_logger_forwarder:register(),
OldFl = process_flag(trap_exit, true),
StateData = crash_terminate,
+ Parent = self(),
{ok, Pid} = gen_fsm:start(gen_fsm_SUITE, {state_data, StateData}, []),
stop_it(Pid),
receive
{error,_GroupLeader,{Pid,
- "** State machine"++_,
- [Pid,{_,_,_},idle,{formatted, StateData},_]}} ->
+ "** State machine "++_,
+ [Pid,stop,Parent,idle,{formatted, StateData},
+ {crash,terminate}|_]}} ->
ok;
Other ->
io:format("Unexpected: ~p", [Other]),
diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl
index 053233df9b..16cf8f43f9 100644
--- a/lib/stdlib/test/gen_statem_SUITE.erl
+++ b/lib/stdlib/test/gen_statem_SUITE.erl
@@ -121,7 +121,8 @@ end_per_testcase(_CaseName, Config) ->
start1(Config) ->
%%OldFl = process_flag(trap_exit, true),
- {ok,Pid0} = gen_statem:start_link(?MODULE, start_arg(Config, []), []),
+ {ok,Pid0} =
+ gen_statem:start_link(?MODULE, start_arg(Config, []), [{debug,[trace]}]),
ok = do_func_test(Pid0),
ok = do_sync_func_test(Pid0),
stop_it(Pid0),
@@ -135,7 +136,8 @@ start1(Config) ->
%% anonymous w. shutdown
start2(Config) ->
%% Dont link when shutdown
- {ok,Pid0} = gen_statem:start(?MODULE, start_arg(Config, []), []),
+ {ok,Pid0} =
+ gen_statem:start(?MODULE, start_arg(Config, []), []),
ok = do_func_test(Pid0),
ok = do_sync_func_test(Pid0),
stopped = gen_statem:call(Pid0, {stop,shutdown}),
@@ -511,7 +513,9 @@ abnormal1dirty(Config) ->
%% trap exit since we must link to get the real bad_return_ error
abnormal2(Config) ->
OldFl = process_flag(trap_exit, true),
- {ok,Pid} = gen_statem:start_link(?MODULE, start_arg(Config, []), []),
+ {ok,Pid} =
+ gen_statem:start_link(
+ ?MODULE, start_arg(Config, []), [{debug,[log]}]),
%% bad return value in the gen_statem loop
{{{bad_return_from_state_function,badreturn},_},_} =
@@ -529,7 +533,9 @@ abnormal2(Config) ->
%% trap exit since we must link to get the real bad_return_ error
abnormal3(Config) ->
OldFl = process_flag(trap_exit, true),
- {ok,Pid} = gen_statem:start_link(?MODULE, start_arg(Config, []), []),
+ {ok,Pid} =
+ gen_statem:start_link(
+ ?MODULE, start_arg(Config, []), [{debug,[log]}]),
%% bad return value in the gen_statem loop
{{{bad_action_from_state_function,badaction},_},_} =
@@ -547,7 +553,9 @@ abnormal3(Config) ->
%% trap exit since we must link to get the real bad_return_ error
abnormal4(Config) ->
OldFl = process_flag(trap_exit, true),
- {ok,Pid} = gen_statem:start_link(?MODULE, start_arg(Config, []), []),
+ {ok,Pid} =
+ gen_statem:start_link(
+ ?MODULE, start_arg(Config, []), [{debug,[log]}]),
%% bad return value in the gen_statem loop
BadTimeout = {badtimeout,4711,ouch},
@@ -641,51 +649,80 @@ state_enter(_Config) ->
end,
start =>
fun (enter, Prev, N) ->
- Self ! {enter,start,Prev,N},
+ Self ! {N,enter,start,Prev},
{keep_state,N + 1};
(internal, Prev, N) ->
- Self ! {internal,start,Prev,N},
+ Self ! {N,internal,start,Prev},
{keep_state,N + 1};
+ (timeout, M, N) ->
+ {keep_state, N + 1,
+ {reply, {Self,N}, {timeout,M}}};
({call,From}, repeat, N) ->
{repeat_state,N + 1,
- [{reply,From,{repeat,start,N}}]};
+ [{reply,From,{N,repeat,start}}]};
({call,From}, echo, N) ->
{next_state,wait,N + 1,
- {reply,From,{echo,start,N}}};
+ [{reply,From,{N,echo,start}},{timeout,0,N}]};
({call,From}, {stop,Reason}, N) ->
{stop_and_reply,Reason,
- [{reply,From,{stop,N}}],N + 1}
+ [{reply,From,{N,stop}}],N + 1}
end,
wait =>
fun (enter, Prev, N) when N < 5 ->
{repeat_state,N + 1,
- {reply,{Self,N},{enter,Prev}}};
+ [{reply,{Self,N},{enter,Prev}},
+ {timeout,0,N},
+ {state_timeout,0,N}]};
(enter, Prev, N) ->
- Self ! {enter,wait,Prev,N},
- {keep_state,N + 1};
+ Self ! {N,enter,wait,Prev},
+ {keep_state,N + 1,
+ [{timeout,0,N},
+ {state_timeout,0,N}]};
+ (timeout, M, N) ->
+ {keep_state, N + 1,
+ {reply, {Self,N}, {timeout,M}}};
+ (state_timeout, M, N) ->
+ {keep_state, N + 1,
+ {reply, {Self,N}, {state_timeout,M}}};
({call,From}, repeat, N) ->
{repeat_state_and_data,
- [{reply,From,{repeat,wait,N}}]};
+ [{reply,From,{N,repeat,wait}},
+ {timeout,0,N}]};
({call,From}, echo, N) ->
{next_state,start,N + 1,
[{next_event,internal,wait},
- {reply,From,{echo,wait,N}}]}
+ {reply,From,{N,echo,wait}}]}
end},
{ok,STM} =
gen_statem:start_link(
- ?MODULE, {map_statem,Machine,[state_enter]}, []),
-
- [{enter,start,start,1}] = flush(),
- {echo,start,2} = gen_statem:call(STM, echo),
- [{3,{enter,start}},{4,{enter,start}},{enter,wait,start,5}] = flush(),
- {wait,[6|_]} = sys:get_state(STM),
- {repeat,wait,6} = gen_statem:call(STM, repeat),
- [{enter,wait,wait,6}] = flush(),
- {echo,wait,7} = gen_statem:call(STM, echo),
- [{enter,start,wait,8},{internal,start,wait,9}] = flush(),
- {repeat,start,10} = gen_statem:call(STM, repeat),
- [{enter,start,start,11}] = flush(),
- {stop,12} = gen_statem:call(STM, {stop,bye}),
+ ?MODULE, {map_statem,Machine,[state_enter]},
+ [{debug,[trace,{log,17}]}]),
+ ok = sys:log(STM, false),
+ ok = sys:log(STM, true),
+
+ [{1,enter,start,start}] = flush(),
+ {2,echo,start} = gen_statem:call(STM, echo),
+ [{3,{enter,start}},
+ {4,{enter,start}},
+ {5,enter,wait,start},
+ {6,{timeout,5}},
+ {7,{state_timeout,5}}] = flush(),
+ {wait,[8|_]} = sys:get_state(STM),
+ {8,repeat,wait} = gen_statem:call(STM, repeat),
+ [{8,enter,wait,wait},
+ {9,{timeout,8}},
+ {10,{state_timeout,8}}] = flush(),
+ {11,echo,wait} = gen_statem:call(STM, echo),
+ [{12,enter,start,wait},
+ {13,internal,start,wait}] = flush(),
+ {14,repeat,start} = gen_statem:call(STM, repeat),
+ [{15,enter,start,start}] = flush(),
+
+ {ok,Log} = sys:log(STM, get),
+ io:format("sys:log ~p~n", [Log]),
+ ok = sys:log(STM, print),
+
+ {16,stop} = gen_statem:call(STM, {stop,bye}),
[{'EXIT',STM,bye}] = flush(),
{noproc,_} =
@@ -1447,7 +1484,8 @@ enter_loop(_Config) ->
%% Locally registered process + {local,Name}
{ok,Pid1a} =
- proc_lib:start_link(?MODULE, enter_loop, [local,local]),
+ proc_lib:start_link(
+ ?MODULE, enter_loop, [local,local,[{debug,[{log,7}]}]]),
yes = gen_statem:call(Pid1a, 'alive?'),
stopped = gen_statem:call(Pid1a, stop),
receive
@@ -1459,7 +1497,8 @@ enter_loop(_Config) ->
%% Unregistered process + {local,Name}
{ok,Pid1b} =
- proc_lib:start_link(?MODULE, enter_loop, [anon,local]),
+ proc_lib:start_link(
+ ?MODULE, enter_loop, [anon,local,[{debug,[log]}]]),
receive
{'EXIT',Pid1b,process_not_registered} ->
ok
@@ -1568,6 +1607,9 @@ enter_loop(_Config) ->
ok = verify_empty_msgq().
enter_loop(Reg1, Reg2) ->
+ enter_loop(Reg1, Reg2, []).
+%%
+enter_loop(Reg1, Reg2, Opts) ->
process_flag(trap_exit, true),
case Reg1 of
local -> register(armitage, self());
@@ -1579,15 +1621,15 @@ enter_loop(Reg1, Reg2) ->
case Reg2 of
local ->
gen_statem:enter_loop(
- ?MODULE, [], state0, [], {local,armitage});
+ ?MODULE, Opts, state0, [], {local,armitage});
global ->
gen_statem:enter_loop(
- ?MODULE, [], state0, [], {global,armitage});
+ ?MODULE, Opts, state0, [], {global,armitage});
via ->
gen_statem:enter_loop(
- ?MODULE, [], state0, [], {via, dummy_via, armitage});
+ ?MODULE, Opts, state0, [], {via, dummy_via, armitage});
anon ->
- gen_statem:enter_loop(?MODULE, [], state0, [])
+ gen_statem:enter_loop(?MODULE, Opts, state0, [])
end.
undef_code_change(_Config) ->
@@ -1619,7 +1661,9 @@ undef_terminate2(_Config) ->
undef_in_terminate(_Config) ->
Data = {undef_in_terminate, {?MODULE, terminate}},
- {ok, Statem} = gen_statem:start(?MODULE, {data, Data}, []),
+ {ok, Statem} =
+ gen_statem:start(
+ ?MODULE, {data, Data}, [{debug,[log]}]),
try
gen_statem:stop(Statem),
ct:fail(should_crash)
diff --git a/lib/stdlib/test/lists_SUITE.erl b/lib/stdlib/test/lists_SUITE.erl
index 984b51e7ae..5dab6f6697 100644
--- a/lib/stdlib/test/lists_SUITE.erl
+++ b/lib/stdlib/test/lists_SUITE.erl
@@ -158,6 +158,20 @@ append_2(Config) when is_list(Config) ->
"abcdef"=lists:append("abc", "def"),
[hej, du]=lists:append([hej], [du]),
[10, [elem]]=lists:append([10], [[elem]]),
+
+ %% Trapping, both crashing and otherwise.
+ [append_trapping_1(N) || N <- lists:seq(0, 20)],
+
+ ok.
+
+append_trapping_1(N) ->
+ List = lists:duplicate(N + (1 bsl N), gurka),
+ ImproperList = List ++ crash,
+
+ {'EXIT',_} = (catch (ImproperList ++ [])),
+
+ [3, 2, 1 | List] = lists:reverse(List ++ [1, 2, 3]),
+
ok.
%% Tests the lists:reverse() implementation. The function is
@@ -1679,7 +1693,7 @@ make_fun() ->
receive {Pid, Fun} -> Fun end.
make_fun(Pid) ->
- Pid ! {self(), fun make_fun/1}.
+ Pid ! {self(), fun (X) -> {X, Pid} end}.
fun_pid(Fun) ->
erlang:fun_info(Fun, pid).
diff --git a/lib/stdlib/test/qlc_SUITE.erl b/lib/stdlib/test/qlc_SUITE.erl
index d7354438f9..2354a08f78 100644
--- a/lib/stdlib/test/qlc_SUITE.erl
+++ b/lib/stdlib/test/qlc_SUITE.erl
@@ -3163,19 +3163,13 @@ lookup2(Config) when is_list(Config) ->
[a] = lookup_keys(Q)
end, [{a},{b},{c}])">>,
- {cres,
- <<"etsc(fun(E) ->
+ <<"etsc(fun(E) ->
Q = qlc:q([X || {X}=Y <- ets:table(E),
element(2, Y) == b,
X =:= 1]),
[] = qlc:e(Q),
false = lookup_keys(Q)
- end, [{1,b},{2,3}])">>,
- %% {warnings,[{2,sys_core_fold,nomatch_guard},
- %% {3,qlc,nomatch_filter},
- %% {3,sys_core_fold,{eval_failure,badarg}}]}},
- {warnings,[{2,sys_core_fold,nomatch_guard},
- {3,sys_core_fold,{eval_failure,badarg}}]}},
+ end, [{1,b},{2,3}])">>,
<<"etsc(fun(E) ->
Q = qlc:q([X || {X} <- ets:table(E), element(1,{X}) =:= 1]),
diff --git a/lib/stdlib/test/rand_SUITE.erl b/lib/stdlib/test/rand_SUITE.erl
index b76c9f5341..7685c17967 100644
--- a/lib/stdlib/test/rand_SUITE.erl
+++ b/lib/stdlib/test/rand_SUITE.erl
@@ -21,24 +21,7 @@
-compile({nowarn_deprecated_function,[{random,seed,1},
{random,uniform_s,1},
{random,uniform_s,2}]}).
-
--export([all/0, suite/0, groups/0, group/1]).
-
--export([interval_int/1, interval_float/1, seed/1,
- api_eq/1, reference/1,
- basic_stats_uniform_1/1, basic_stats_uniform_2/1,
- basic_stats_standard_normal/1,
- basic_stats_normal/1,
- stats_standard_normal_box_muller/1,
- stats_standard_normal_box_muller_2/1,
- stats_standard_normal/1,
- uniform_real_conv/1,
- plugin/1, measure/1,
- reference_jump_state/1, reference_jump_procdict/1]).
-
--export([test/0, gen/1]).
-
--export([uniform_real_gen/1, uniform_gen/2]).
+-compile([export_all, nowarn_export_all]).
-include_lib("common_test/include/ct.hrl").
@@ -56,7 +39,8 @@ all() ->
{group, distr_stats},
uniform_real_conv,
plugin, measure,
- {group, reference_jump}
+ {group, reference_jump},
+ short_jump
].
groups() ->
@@ -95,7 +79,7 @@ test() ->
end, Tests).
algs() ->
- [exrop, exsp, exs1024s, exs64, exsplus, exs1024].
+ [exsss, exrop, exsp, exs1024s, exs64, exsplus, exs1024, exro928ss].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -125,7 +109,7 @@ seed_1(Alg) ->
S0 = get(rand_seed),
S0 = rand:seed_s(Alg, {0, 0, 0}),
%% Check that process_dict should not be used for seed_s functionality
- _ = rand:seed_s(Alg, {1, 0, 0}),
+ _ = rand:seed_s(Alg, 4711),
S0 = get(rand_seed),
%% Test export
ES0 = rand:export_seed(),
@@ -262,31 +246,43 @@ reference(Config) when is_list(Config) ->
ok.
reference_1(Alg) ->
- Refval = reference_val(Alg),
- Testval = gen(Alg),
- case Refval =:= Testval of
- true -> ok;
- false when Refval =:= not_implemented ->
- exit({not_implemented,Alg});
- false ->
- io:format("Failed: ~p~n",[Alg]),
- io:format("Length ~p ~p~n",[length(Refval), length(Testval)]),
- io:format("Head ~p ~p~n",[hd(Refval), hd(Testval)]),
- exit(wrong_value)
+ Refval = reference_val(Alg),
+ if
+ Refval =:= not_implemented -> Refval;
+ true ->
+ case gen(Alg) of
+ Refval ->
+ io:format("Ok: ~p~n",[Alg]),
+ ok;
+ Testval ->
+ io:format("Failed: ~p~n",[Alg]),
+ io:format("Length ~p ~p~n",[length(Refval), length(Testval)]),
+ io:format("Head ~p ~p~n",[hd(Refval), hd(Testval)]),
+ show_wrong(Refval, Testval),
+ exit(wrong_value)
+ end
end.
+show_wrong([], []) ->
+ ok;
+show_wrong([H|T1], [H|T2]) ->
+ show_wrong(T1, T2);
+show_wrong([H1|_], [H2|_]) ->
+ io:format("Wrong ~p ~p~n",[H1,H2]).
+
+
gen(Algo) ->
State =
- case Algo of
- exs64 -> %% Printed with orig 'C' code and this seed
- rand:seed_s({exs64, 12345678});
- _ when Algo =:= exsplus; Algo =:= exsp; Algo =:= exrop ->
+ if
+ Algo =:= exs64 -> %% Printed with orig 'C' code and this seed
+ rand:seed_s(exs64, [12345678]);
+ Algo =:= exsplus; Algo =:= exsp; Algo =:= exrop; Algo =:= exsss ->
%% Printed with orig 'C' code and this seed
- rand:seed_s({Algo, [12345678|12345678]});
- _ when Algo =:= exs1024; Algo =:= exs1024s ->
+ rand:seed_s(Algo, [12345678,12345678]);
+ Algo =:= exs1024; Algo =:= exs1024s; Algo =:= exro928ss ->
%% Printed with orig 'C' code and this seed
- rand:seed_s({Algo, {lists:duplicate(16, 12345678), []}});
- _ ->
+ rand:seed_s(Algo, lists:duplicate(16, 12345678));
+ true ->
rand:seed(Algo, {100, 200, 300})
end,
Max = range(State),
@@ -442,7 +438,7 @@ stats_standard_normal_box_muller(Config) when is_list(Config) ->
([S|Z]) ->
{Z, [S]}
end,
- State = [rand:seed(exrop)],
+ State = [rand:seed(exsss)],
stats_standard_normal(NormalS, State, 3)
catch error:_ ->
{skip, "math:erfc/1 not supported"}
@@ -467,7 +463,7 @@ stats_standard_normal_box_muller_2(Config) when is_list(Config) ->
([S|Z]) ->
{Z, [S]}
end,
- State = [rand:seed(exrop)],
+ State = [rand:seed(exsss)],
stats_standard_normal(NormalS, State, 3)
catch error:_ ->
{skip, "math:erfc/1 not supported"}
@@ -479,7 +475,7 @@ stats_standard_normal(Config) when is_list(Config) ->
try math:erfc(1.0) of
_ ->
stats_standard_normal(
- fun rand:normal_s/1, rand:seed_s(exrop), Retries)
+ fun rand:normal_s/1, rand:seed_s(exsss), Retries)
catch error:_ ->
{skip, "math:erfc/1 not supported"}
end.
@@ -853,7 +849,8 @@ do_measure(_Config) ->
Algs =
algs() ++
try crypto:strong_rand_bytes(1) of
- <<_>> -> [crypto64, crypto_cache, crypto]
+ <<_>> ->
+ [crypto64, crypto_cache, crypto_aes, crypto]
catch
error:low_entropy -> [];
error:undef -> []
@@ -1074,7 +1071,7 @@ do_measure(_Config) ->
end,
State)
end,
- exrop, TMarkNormalFloat),
+ exsss, TMarkNormalFloat),
ok.
-define(LOOP_MEASURE, (?LOOP div 5)).
@@ -1102,6 +1099,10 @@ measure_1(RangeFun, Fun, Alg, TMark) ->
{rand, crypto:rand_seed_alg(crypto_cache)};
crypto ->
{rand, crypto:rand_seed_s()};
+ crypto_aes ->
+ {rand,
+ crypto:rand_seed_alg(
+ crypto_aes, crypto:strong_rand_bytes(256))};
random ->
{random, random:seed(os:timestamp()), get(random_seed)};
_ ->
@@ -1117,7 +1118,7 @@ measure_1(RangeFun, Fun, Alg, TMark) ->
_ -> (Time * 100 + 50) div TMark
end,
io:format(
- "~.12w: ~p ns ~p% [16#~.16b]~n",
+ "~.20w: ~p ns ~p% [16#~.16b]~n",
[Alg, (Time * 1000 + 500) div ?LOOP_MEASURE,
Percent, Range]),
Parent ! {self(), Time},
@@ -1142,104 +1143,156 @@ reference_jump_state(Config) when is_list(Config) ->
ok.
reference_jump_1(Alg) ->
- Refval = reference_jump_val(Alg),
- Testval = gen_jump_1(Alg),
- case Refval =:= Testval of
- true -> ok;
- false ->
- io:format("Failed: ~p~n",[Alg]),
- io:format("Length ~p ~p~n",[length(Refval), length(Testval)]),
- io:format("Head ~p ~p~n",[hd(Refval), hd(Testval)]),
- io:format("Vals ~p ~p~n",[Refval, Testval]),
- exit(wrong_value)
+ Refval = reference_jump_val(Alg),
+ if
+ Refval =:= not_implemented -> Refval;
+ true ->
+ case gen_jump_1(Alg) of
+ Refval -> ok;
+ Testval ->
+ io:format(
+ "Failed: ~p~n",[Alg]),
+ io:format(
+ "Length ~p ~p~n",
+ [length(Refval), length(Testval)]),
+ io:format(
+ "Head ~p ~p~n",[hd(Refval), hd(Testval)]),
+ io:format(
+ "Vals ~p ~p~n",[Refval, Testval]),
+ exit(wrong_value)
+ end
end.
gen_jump_1(Algo) ->
- State =
- case Algo of
- exs64 -> %% Test exception of not_implemented notice
- try rand:jump(rand:seed_s(exs64))
- catch
- error:not_implemented -> not_implemented
- end;
- _ when Algo =:= exsplus; Algo =:= exsp; Algo =:= exrop ->
- %% Printed with orig 'C' code and this seed
- rand:seed_s({Algo, [12345678|12345678]});
- _ when Algo =:= exs1024; Algo =:= exs1024s ->
- %% Printed with orig 'C' code and this seed
- rand:seed_s({Algo, {lists:duplicate(16, 12345678), []}});
- _ -> % unimplemented
- not_implemented
- end,
- case State of
- not_implemented -> [not_implemented];
- _ ->
- Max = range(State),
- gen_jump_1(?LOOP_JUMP, State, Max, [])
+ case Algo of
+ exs64 -> %% Test exception of not_implemented notice
+ try rand:jump(rand:seed_s(exs64))
+ catch
+ error:not_implemented -> [error_not_implemented]
+ end;
+ _ when Algo =:= exsplus; Algo =:= exsp; Algo =:= exrop; Algo =:= exsss ->
+ %% Printed with orig 'C' code and this seed
+ gen_jump_2(
+ rand:seed_s(Algo, [12345678,12345678]));
+ _ when Algo =:= exs1024; Algo =:= exs1024s; Algo =:= exro928ss ->
+ %% Printed with orig 'C' code and this seed
+ gen_jump_2(
+ rand:seed_s(Algo, lists:duplicate(16, 12345678)))
end.
-gen_jump_1(N, State0, Max, Acc) when N > 0 ->
+gen_jump_2(State) ->
+ Max = range(State),
+ gen_jump_3(?LOOP_JUMP, State, Max, []).
+
+gen_jump_3(N, State0, Max, Acc) when N > 0 ->
{_, State1} = rand:uniform_s(Max, State0),
{Random, State2} = rand:uniform_s(Max, rand:jump(State1)),
case N rem (?LOOP_JUMP div 100) of
- 0 -> gen_jump_1(N-1, State2, Max, [Random|Acc]);
- _ -> gen_jump_1(N-1, State2, Max, Acc)
+ 0 -> gen_jump_3(N-1, State2, Max, [Random|Acc]);
+ _ -> gen_jump_3(N-1, State2, Max, Acc)
end;
-gen_jump_1(_, _, _, Acc) -> lists:reverse(Acc).
+gen_jump_3(_, _, _, Acc) -> lists:reverse(Acc).
%% Check if each algorithm generates the proper jump sequence
%% with the internal state in the process dictionary.
reference_jump_procdict(Config) when is_list(Config) ->
- [reference_jump_0(Alg) || Alg <- algs()],
+ [reference_jump_p1(Alg) || Alg <- algs()],
ok.
-reference_jump_0(Alg) ->
+reference_jump_p1(Alg) ->
Refval = reference_jump_val(Alg),
- Testval = gen_jump_0(Alg),
- case Refval =:= Testval of
- true -> ok;
- false ->
- io:format("Failed: ~p~n",[Alg]),
- io:format("Length ~p ~p~n",[length(Refval), length(Testval)]),
- io:format("Head ~p ~p~n",[hd(Refval), hd(Testval)]),
- exit(wrong_value)
+ if
+ Refval =:= not_implemented -> Refval;
+ true ->
+ case gen_jump_p1(Alg) of
+ Refval -> ok;
+ Testval ->
+ io:format("Failed: ~p~n",[Alg]),
+ io:format("Length ~p ~p~n",[length(Refval), length(Testval)]),
+ io:format("Head ~p ~p~n",[hd(Refval), hd(Testval)]),
+ exit(wrong_value)
+ end
end.
-gen_jump_0(Algo) ->
- Seed = case Algo of
- exs64 -> %% Test exception of not_implemented notice
- try
- _ = rand:seed(exs64),
- rand:jump()
- catch
- error:not_implemented -> not_implemented
- end;
- _ when Algo =:= exsplus; Algo =:= exsp; Algo =:= exrop ->
- %% Printed with orig 'C' code and this seed
- rand:seed({Algo, [12345678|12345678]});
- _ when Algo =:= exs1024; Algo =:= exs1024s ->
- %% Printed with orig 'C' code and this seed
- rand:seed({Algo, {lists:duplicate(16, 12345678), []}});
- _ -> % unimplemented
- not_implemented
- end,
- case Seed of
- not_implemented -> [not_implemented];
- _ ->
- Max = range(Seed),
- gen_jump_0(?LOOP_JUMP, Max, [])
+gen_jump_p1(Algo) ->
+ case Algo of
+ exs64 -> %% Test exception of not_implemented notice
+ try
+ _ = rand:seed(exs64),
+ rand:jump()
+ catch
+ error:not_implemented -> [error_not_implemented]
+ end;
+ _ when Algo =:= exsplus; Algo =:= exsp; Algo =:= exrop; Algo =:= exsss ->
+ %% Printed with orig 'C' code and this seed
+ gen_jump_p2(
+ rand:seed(Algo, [12345678,12345678]));
+ _ when Algo =:= exs1024; Algo =:= exs1024s; Algo =:= exro928ss ->
+ %% Printed with orig 'C' code and this seed
+ gen_jump_p2(
+ rand:seed(Algo, lists:duplicate(16, 12345678)))
end.
-gen_jump_0(N, Max, Acc) when N > 0 ->
+gen_jump_p2(Seed) ->
+ Max = range(Seed),
+ gen_jump_p3(?LOOP_JUMP, Max, []).
+
+gen_jump_p3(N, Max, Acc) when N > 0 ->
_ = rand:uniform(Max),
_ = rand:jump(),
Random = rand:uniform(Max),
case N rem (?LOOP_JUMP div 100) of
- 0 -> gen_jump_0(N-1, Max, [Random|Acc]);
- _ -> gen_jump_0(N-1, Max, Acc)
+ 0 -> gen_jump_p3(N-1, Max, [Random|Acc]);
+ _ -> gen_jump_p3(N-1, Max, Acc)
end;
-gen_jump_0(_, _, Acc) -> lists:reverse(Acc).
+gen_jump_p3(_, _, Acc) -> lists:reverse(Acc).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+short_jump(Config) when is_list(Config) ->
+ Seed = erlang:system_time(),
+ short_jump(
+ rand:seed_s(exro928ss, Seed),
+ fun ({Alg,AlgState}) ->
+ {Alg,rand:exro928_jump_2pow20(AlgState)}
+ end),
+ short_jump(
+ crypto:rand_seed_alg_s(crypto_aes, integer_to_list(Seed)),
+ fun ({Alg,AlgState}) ->
+ {Alg,crypto:rand_plugin_aes_jump_2pow20(AlgState)}
+ end),
+ ok.
+
+short_jump({#{bits := Bits},_} = State_0, Jump2Pow20) ->
+ Range = 1 bsl Bits,
+ State_1 = repeat(7, Range, State_0),
+ %%
+ State_2a = repeat(1 bsl 20, Range, State_1),
+ State_2b = Jump2Pow20(State_1),
+ check(17, Range, State_2a, State_2b),
+ %%
+ {_,State_3a} = rand:uniform_s(Range, State_2a),
+ State_4a = Jump2Pow20(State_3a),
+ State_4b = repeat((1 bsl 20) + 1, Range, State_2b),
+ check(17, Range, State_4a, State_4b).
+
+repeat(0, _Range, State) ->
+ State;
+repeat(N, Range, State) ->
+ {_, NewState} = rand:uniform_s(Range, State),
+ repeat(N - 1, Range, NewState).
+
+check(0, _Range, _StateA, _StateB) ->
+ ok;
+check(N, Range, StateA, StateB) ->
+ {V,NewStateA} = rand:uniform_s(Range, StateA),
+ case rand:uniform_s(Range, StateB) of
+ {V,NewStateB} ->
+ check(N - 1, Range, NewStateA, NewStateB);
+ {Wrong,_} ->
+ ct:fail({Wrong,neq,V,for,N})
+ end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Data
@@ -1324,6 +1377,34 @@ reference_val(exsplus) ->
16#3dd493b8012970f,16#be13bed1e00e5c,16#ceef033b74ae10,16#3da38c6a50abe03,
16#15cbd1a421c7a8c,16#22794e3ec6ef3b1,16#26154d26e7ea99f,16#3a66681359a6ab6];
+reference_val(exsss) ->
+ [16#108e8d5b01,16#33b72092117209a,16#224d4d2961a2d0a,16#2c4c81aac3da48d,
+ 16#2f4bc39bfc36f3a,16#41826d4c4d243a,16#19871b8bb4e23ee,16#3e2112cdf9384b1,
+ 16#69801943bf91ab,16#2de1a603c31ec45,16#a90ca1991b831e,16#51ca29571a69a7,
+ 16#93ce3e511906cf,16#93ebc5768aef75,16#2412f284b902ae7,16#1ac10e758410c52,
+ 16#3f32494560368f6,16#39a5e82dcf0de95,16#3f4b14d59cc6a21,16#3174668db0b36ae,
+ 16#1449812fb8bd54e,16#eaca1f8ece51e1,16#2564b2545fd23c1,16#3cf3a2d2217e0d7,
+ 16#226f4164ba1d054,16#10dac9ae207ceef,16#17f2c4b2d40fcb9,16#1c1b282d386fdcb,
+ 16#a264f450ba2912,16#2a0a1dd67e52666,16#2be84eb835cb1e1,16#2a1cd9aa16ccc37,
+ 16#7dd5e8c2b3f490,16#254a3db4976c05b,16#2a0a67971ec1e63,16#13a0cbf7c0eed8a,
+ 16#3192d7edc0a20bc,16#2705ad756292e84,16#3ec429a18119c81,16#25944b38baa975b,
+ 16#291dcc43e3256f4,16#30d10b759237db,16#c1522a652058a,16#8ef1e9378381e6,
+ 16#1f442f33c2439f4,16#186087710a73818,16#12887f94b2b8387,16#3e42e8b1f3c9b4b,
+ 16#e462859d55f9d8,16#2356ae85be908de,16#15e96a927b3bc52,16#35c6dc52511ce46,
+ 16#7bc0624ce66e01,16#33ab7d95b738322,16#26f01effc182aa0,16#1b66ae7eaafea88,
+ 16#278f3dc14943b90,16#22178bc8d8faf28,16#396c37d53c11985,16#5e0d79d0b10f18,
+ 16#1be3de3b5675ec,16#d4db298f1f4b50,16#2da6cb99bb5c7b1,16#130b2dc17d03be8,
+ 16#f1847e7e059e9f,16#2da6591788326e7,16#222e4a18c24211c,16#949213ca49baab,
+ 16#b5129fec56f6a2,16#30f25f1e926f43e,16#1ddd8d04445fb4d,16#15995b542514150,
+ 16#1595fe879296296,16#e2f237a488453b,16#23e5cd2d6047890,16#3a5dc88fc954666,
+ 16#89bca9969b103,16#5e6893cd35dc63,16#1fed534feeeef5a,16#26f40e2147ee558,
+ 16#30c131a00625837,16#2618a7e617422e9,16#23630b297e45e7,16#1143b17502f3219,
+ 16#15607dac41168da,16#2886bdc314b3fb8,16#465d1cc1536546,16#30b09123e3a02e4,
+ 16#245a375f810be52,16#6a1b0792376a03,16#221425f59f2470f,16#867ce16dfac81c,
+ 16#9c62d95fae9b58,16#380381db1394426,16#34908dedc01c324,16#1f0ff517089b561,
+ 16#1571366dd873d32,16#3ee353dc56e192,16#15a1dee8d889b11,16#41036ad76d9888
+ ];
+
reference_val(exsp) ->
reference_val(exsplus);
reference_val(exs1024s) ->
@@ -1390,7 +1471,50 @@ reference_val(exrop) ->
250789092615679985,78848633178610658,72059442721196128,
98223942961505519,191144652663779840,
102425686803727694,89058927716079076,80721467542933080,
- 8462479817391645,2774921106204163].
+ 8462479817391645,2774921106204163];
+
+reference_val(exro928ss) ->
+%% Same as for exrop, but this state init:
+%% for (n = 0; n < 16; n++) {
+%% s[n] = 12345678;
+ [16#000000108e8d5b01,16#03604028f2769dff,16#007f92f60bc7170c,
+ 16#035ea81a9898a5e2,16#0104c90c5a0c8178,16#0313514025cca717,
+ 16#03c5506b2a2e98cf,16#0098a5405961552e,16#004ad29eabb785a0,
+ 16#033ea8ec4efb8058,16#00b21545e62bef1c,16#0333fc5320703482,
+ 16#02c3c650e51a8d47,16#03a3b7fc848c9cda,16#03775adea6cddff5,
+ 16#01ae5499c9049973,16#03d3c90e5504e16b,16#0383cd6b6cb852e6,
+ 16#009c8d0996ef543a,16#0059cf671371af60,16#03dfd68ed980b719,
+ 16#0290f2a0acf2c5b0,16#029061df18d63b55,16#02e702ea4b45137b,
+ 16#029a0ccca604d848,16#01664c7cd31f0fa6,16#00dced83e60ccddc,
+ 16#008764d2c9a05f3e,16#02b9ca5f6a80c4ba,16#02daf93d2c566750,
+ 16#0147d326ead18ace,16#014b452efc19297f,16#0242d3f7a7237eca,
+ 16#0141bb68c2abce39,16#02d798e1230baf45,16#0216bf8f25c1ec2d,
+ 16#003a43ea733f1e1f,16#036c75390db736f3,16#028cca5f5f48c6f9,
+ 16#0186e4a17174d6cf,16#02152679dfa4c25c,16#01429b9f15e3b9d6,
+ 16#0134a61411d22bb0,16#01593f7d970d1c94,16#0205a7d8a305490f,
+ 16#01dd092272595a9c,16#0028c95208aad2d4,16#016347c25cc24162,
+ 16#025306acfb891309,16#0207a07e2bebef2f,16#024ee78d86ff5288,
+ 16#030b53192db97613,16#03f765cb9e98e611,16#025ec35a1e237377,
+ 16#03d81fd73102ef6f,16#0242dc8fea9a68b2,16#00abb876c1d4ea1b,
+ 16#00871ffd2b7e45fb,16#03593ff73c9be08d,16#00b96b2b8aca3688,
+ 16#0174aba957b7cf7b,16#012b7a5d4cf4a5b7,16#032a5260f2123db8,
+ 16#00f9374d88ee0080,16#030df39bec2ad657,16#00dce0cb81d006c4,
+ 16#038213b806303c76,16#03940aafdbfabf84,16#0398dbb26aeba037,
+ 16#01eb28d61951587f,16#00fed3d2aacfeef4,16#03499587547d6e40,
+ 16#01b192fe6e979e3c,16#00e974bf5f0a26d0,16#012ed94f76459c83,
+ 16#02d76859e7a82587,16#00d1d2c7b791f51b,16#03988058017a031b,
+ 16#00bbcf4b59d8e86d,16#015ed8b73a1b767c,16#0277283ea6a5ee74,
+ 16#002211460dd6d422,16#001ad62761ee9fbd,16#037311b44518b067,
+ 16#02b5ed61bf70904e,16#011862a05c1929fa,16#014be68683c3bab4,
+ 16#025c29aa5c508b07,16#00895c6106f97378,16#026ce91a3d671c7f,
+ 16#02591f4c74784293,16#02f0ed2a70bc1853,16#00a2762ff614bfbc,
+ 16#008f4e354f0c20d4,16#038b66fb587ed430,16#00636296e188de89,
+ 16#0278fadd143e74f5,16#029697ccf1b3a4c2,16#011eccb273404458,
+ 16#03f204064a9fe0c0];
+
+reference_val(_) ->
+ not_implemented.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -1421,6 +1545,33 @@ reference_jump_val(exsplus) ->
12504080415362731, 45083100453836317, 270968267812126657, 93505647407734103,
252852934678537969, 258758309277167202, 74250882143432077, 141629095984552833];
+reference_jump_val(exsss) ->
+ [16#304ae783d40db2b,16#1dfb196b3a5600a,16#2a24116effc6a0d,16#1f138d68c56725,
+ 16#9360a445e2f989,16#32ed8080390e242,16#294ca85a270cff6,16#1418e6296a88bf,
+ 16#114fae3dc578ba7,16#479c42c760eb72,16#334a40655df22d6,16#e7a85dd4d37d72,
+ 16#181db16c8925c77,16#1b8a5a8afd16cbd,16#329107bf9777a39,16#2fc915c08535e42,
+ 16#16696d142c6078,16#2e2a2601c919448,16#2246150d1000568,16#26109007cb3dd44,
+ 16#3761360723e3175,16#169abd352db74de,16#1c97d520983684f,16#12455f0adee8c66,
+ 16#46719cff00622d,16#1fc92792ed4e437,16#18e2edae21affb5,16#3a67fa9e3e7d46e,
+ 16#1313fdc2728aa74,16#1c1a2b577581db8,16#db49357ea196b1,16#10e219a21d93fc7,
+ 16#3c43abede083666,16#3eef5055a58bbf9,16#1975056f95d90e3,16#3916c133ab16d87,
+ 16#2bc0bea891c26f1,16#391e4b369fc6b36,16#183f83155a359f6,16#1d9f137e9d2e488,
+ 16#ef084de5f4cd3c,16#36a9cf7e29e55d3,16#19eca704e0409a7,16#1bdb99902896c69,
+ 16#21777e2ad128203,16#5d0369ec0563e4,16#36db40b863bd74a,16#33feb71b7515159,
+ 16#208d923ce26f257,16#3841b32891c082d,16#2748f224c2ba226,16#2fcd93b2daf79bb,
+ 16#2c8e6cacad58ec4,16#39850131a1a85f,16#134648d6eea624d,16#2e102e197d5725c,
+ 16#12ac280fa744758,16#1c18266c7442d16,16#22b5f91b15fe17e,16#316740ca870f7c8,
+ 16#720ed4836c426,16#1aac0f738d04f8c,16#34fcd2a647b462c,16#3d430ac755114a3,
+ 16#3692e3670fdf2a,16#265279ab0fc0a15,16#10bd883dee80945,16#10e7843413175e4,
+ 16#b291deba08cee2,16#3915a8234caf11,16#34b911b96707dbd,16#ae63fcda15fde6,
+ 16#b13b9091e82e41,16#29de1b6d70dc04f,16#23fbcbc409617e8,16#1389a0738061066,
+ 16#360f39af790f5d1,16#f436da2a7d12f5,16#2d06ba8da21e08,16#3601a6492b887d,
+ 16#2b2590b8c6cc186,16#f8d613b6904464,16#e5456786e46b78,16#201b8b1f96ed80c,
+ 16#1b75b86d9b843f2,16#2e8bfaa7243a630,16#125ff068a78c3b4,16#3875a28c48bd26e,
+ 16#f09a06941fc9d7,16#107c4de8ca77744,16#357c34144bb9ed6,16#3ccc55d3ebb3378,
+ 16#28db7cea7d3fdee,16#3197fd0b49f6370,16#11af6fedb708ea6,16#2bde0382e37469e,
+ 16#10666171abddb3f,16#1a8876c1f4e78a8,16#169c0efd4422043,16#1501c49abf0440f];
+
reference_jump_val(exs1024) ->
[2655961906500790629, 17003395417078685063, 10466831598958356428, 7603399148503548021,
1650550950190587188, 12294992315080723704, 15743995773860389219, 5492181000145247327,
@@ -1452,7 +1603,7 @@ reference_jump_val(exsp) ->
reference_jump_val(exsplus);
reference_jump_val(exs1024s) ->
reference_jump_val(exs1024);
-reference_jump_val(exs64) -> [not_implemented];
+reference_jump_val(exs64) -> [error_not_implemented];
reference_jump_val(exrop) ->
%% #include <stdint.h>
%% #include <stdio.h>
@@ -1517,7 +1668,50 @@ reference_jump_val(exrop) ->
250227633882474729,171181147785250210,55437891969696407,
241227318715885854,77323084015890802,
1663590009695191,234064400749487599,222983191707424780,
- 254956809144783896,203898972156838252].
+ 254956809144783896,203898972156838252];
+
+reference_jump_val(exro928ss) ->
+%% Same as for exrop, but this state init:
+%% for (n = 0; n < 16; n++) {
+%% s[n] = 12345678;
+ [16#031ee449e53b6689,16#001afeee12813137,16#005e2172711df36b,
+ 16#02850aea3a595d36,16#0029705187e891c7,16#001794badd489667,
+ 16#00ab621be15be56c,16#024b663a6924786b,16#03cab70b8ab854bf,
+ 16#01daa37601285320,16#02db955a53c40e89,16#01fbef51d5c65891,
+ 16#02fecf4116ed5f77,16#0349c2057246ac5d,16#01217f257c4fa148,
+ 16#0367ee84d020697d,16#01d5cf647fe23335,16#020941838adfb750,
+ 16#02c2da26b1d7b3e5,16#00d1583d34cea6c0,16#038be9cb5b527f50,
+ 16#00bfa93c1d7f4864,16#03778912a4f56b14,16#037fcabc483fa5c5,
+ 16#00a3c9de6aaf5fc7,16#03600b883b2f2b42,16#03797a99ffddfdfb,
+ 16#0189fead429945b7,16#0103ac90cd912508,16#03e3d872fd950d64,
+ 16#0214fc3e77dc2f02,16#02a084f4f0e580ca,16#035d2fe72266a7f3,
+ 16#02887c49ae7e41a4,16#0011dc026af83c51,16#02d28bfd32c2c517,
+ 16#022e4165c33ad4f3,16#01f053cf0687b052,16#035315e6e53c8918,
+ 16#01255312da07b572,16#0237f1da11ec9221,16#02faf2e282fb1fb1,
+ 16#0227423ec1787ebc,16#011fa5eb1505571c,16#0275ff9eaaa1abdd,
+ 16#03e2d032c3981cb4,16#0181bb32d51d3072,16#01b1d3939b9f16ec,
+ 16#0259f09f55d1112f,16#0396464a2767e428,16#039777c0368bdb9e,
+ 16#0320925f35f36c5f,16#02a35289e0af1248,16#02e80bd4bc72254b,
+ 16#00a8b11af1674d68,16#027735036100a69e,16#03c8c268ded7f254,
+ 16#03de80aa57c65217,16#00f2247754d24000,16#005582a42b467f89,
+ 16#0031906569729477,16#00fd523f2ca4fefe,16#00ad223113d1e336,
+ 16#0238ddf026cbfca9,16#028b98211cfed876,16#0354353ebcc0de9a,
+ 16#009ee370c1e154f4,16#033131af3b8a7f88,16#032291baa45801e3,
+ 16#00941fc2b45eb217,16#035d6a61fa101647,16#03fdb51f736f1bbc,
+ 16#0232f7b98539faa0,16#0311b35319e3a61e,16#0048356b17860eb5,
+ 16#01a205b2554ce71e,16#03f873ea136e29d6,16#003c67d5c3df5ffd,
+ 16#00cd19e7a8641648,16#0149a8c54e4ba45e,16#0329498d134d2f6a,
+ 16#03b69421ae65ee2b,16#01a8d20b59447429,16#006b2292571032a2,
+ 16#00c193b17da22ba5,16#01faa7ab62181249,16#00acd401cd596a00,
+ 16#005b5086c3531402,16#0259113d5d3d058d,16#00bef3f3ce4a43b2,
+ 16#014837a4070b893c,16#00460a26ac2eeec1,16#026219a8b8c63d7e,
+ 16#03c7b8ed032cf5a6,16#004da912a1fff131,16#0297de3716215741,
+ 16#0079fb9b4c715466,16#00a73bad4ae5a356,16#0072e606c0d4ab86,
+ 16#02374382d5f9bd2e];
+
+reference_jump_val(_) ->
+ not_implemented.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/stdlib/test/rand_Xoroshiro928ss_dev.txt b/lib/stdlib/test/rand_Xoroshiro928ss_dev.txt
new file mode 100644
index 0000000000..150f37fcfa
--- /dev/null
+++ b/lib/stdlib/test/rand_Xoroshiro928ss_dev.txt
@@ -0,0 +1,343 @@
+%CopyrightBegin%
+
+Copyright Ericsson AB 2015-2017. All 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%
+
+
+Memorable facts from designing the Xoroshiro928** generator
+===========================================================
+AKA: exro928ss in the rand module
+
+Author: Raimo Niskanen, for the Erlang/OTP team @ Ericsson.
+
+Reference URL: http://vigna.di.unimi.it/ftp/papers/ScrambledLinear.pdf
+i.e the Xoroshiro1024 generator with ** scrambler:
+
+ int p;
+ uint64_t s[16];
+
+ const int q = p;
+ const uint64_t s0 = s[p = (p + 1) & 15];
+
+ uint64_t s15 = s[q];
+
+ const uint64_t result_starstar = rotl(s0 * S, R) * T;
+
+ s15 ^= s0;
+ s[q] = rotl(s0, A) ^ s15 ^ (s15 << B);
+ s[p] = rotl(s15, C);
+
+Where {S, R, T} = {5, 7, 9} as recommended in the paper.
+
+We want to scale down to 58 bit words (16 of them)
+so we get a generator with period 2^928 - 1.
+
+{A, B, C} were deduced as follows
+---------------------------------
+
+First, to find which triplets that give a full period generator
+one have to factor 2^928 - 1.
+
+https://www.alpertron.com.ar/ECM.HTM actually could do that
+and gave the result:
+
+ Value
+ 2^928 - 1
+
+ 2269 007733 883335 972287 082669 296112 915239 349672 942191 252221 331572
+ 442536 403137 824056 312817 862695 551072 066953 619064 625508 194663
+ 368599 769448 406663 254670 871573 830845 597595 897613 333042 429214
+ 224697 474472 410882 236254 024057 110212 260250 671521 235807 709272
+ 244389 361641 091086 035023 229622 419455 (280 digits) = 3 × 5 × 17 × 59 ×
+ 233 × 257 × 929 × 1103 × 2089 × 5569 × 8353 × 59393 × 65537 × 3 033169 ×
+ 39 594977 × 107 367629 × 536 903681 × 748 264961 × 2245 984577 × 239
+ 686663 718401 × 15 929619 591127 520827 829953 × 82280 195167 144119
+ 832390 568177 × 6033 312171 721035 031651 315652 130497 (34 digits) × 18
+ 774318 450142 955120 650303 957350 521748 903233 (44 digits) × 15 694604
+ 006012 505869 851221 169365 594050 637743 819041 (50 digits)
+
+Sebastiano Vigna from that calculated all full period triplets, at the
+end of this document, and the ones with the highest degree were:
+
+ 23-25-12 411
+ 36-2-35 411
+ 55-5-54 411
+ 14-19-11 415
+ 34-37-5 415
+ 37-3-56 415
+ 55-11-54 417
+ 30-3-41 419
+ 11-45-50 423
+ 50-19-47 423
+ 52-27-13 427
+ 54-9-25 433
+ 56-43-35 433
+ 44-9-45 441
+
+All these candidates were tested with TestU01-1.2.3:
+
+ http://simul.iro.umontreal.ca/testu01/tu01.html
+
+A plugin was created with parameters for {A, B, C} and, since
+TestU01 is a 32-bit test tool, with parameters to reverse
+the generated bits or not, and to take the 32 highest or lowest
+bits from the reversed or non-reversed 58 bit output.
+
+The generators were seeded with a SplitMix64 generator like the
+one used for seeding this generator in the rand module,
+taking the 58 lowest bits and wasting all zero values.
+
+3 runs were made with all candidates and all four bit selection variants.
+For these runs the seeder was initialized with 12345678, 876543212345678
+and 1234567890.
+
+After all these runs the candidate with the highest degree: 44-9-45
+had not gotten any suspicious p-value at all. All the other
+got p-values around 5.0e-4 worst 9.8e-6 suggesting only random
+failures, so they would probably have worked about as well.
+
+Finally 44-9-45 was run through PractRand-0.93:
+
+ http://pracrand.sourceforge.net/
+
+Again, all 4 bit selection variants of 32 bits were run.
+Random failures with p-values around e-3..e-4 for the "smaller"
+tests, but for 8, 16 and 32 TB tests no anomalies were found
+(with the internal seeder masked to 58 bits):
+
+ Xoroshiro928High seed: 0x3178ec5d
+ Xoroshiro928Low seed: 0xa9a04fb9
+ Xoroshiro928ReverseHigh seed: 0xfa0bdbab
+ Xoroshiro928ReverseLow seed: 0xada51705
+
+
+Then, S. Vigna calculated the 2^512 jump coefficient as well
+as a 2^20 jump coefficient (for testing purposes) for 44-9-45.
+
+2^512:
+ { 0x44085302f77130ca, 0xba05381fdfd14902, 0x10a1de1d7d6813d2,
+ 0xb83fe51a1eb3be19, 0xa81b0090567fd9f0, 0x5ac26d5d20f9b49f,
+ 0x4ddd98ee4be41e01, 0x0657e19f00d4b358, 0xf02f778573cf0f0a,
+ 0xb45a3a8a3cef3cc0, 0x6e62a33cc2323831, 0xbcb3b7c4cc049c53,
+ 0x83f240c6007e76ce, 0xe19f5fc1a1504acd, 0x00000000b10773cb }
+
+2^20:
+ { 0xbdb966a3daf905e6, 0x644807a56270cf78, 0xda90f4a806c17e9e,
+ 0x4a426866bfad3c77, 0xaf699c306d8e7566, 0x8ebc73c700b8b091,
+ 0xc081a7bf148531fb, 0xdc4d3af15f8a4dfd, 0x90627c014098f4b6,
+ 0x06df2eb1feaf0fb6, 0x5bdeb1a5a90f2e6b, 0xa480c5878c3549bd,
+ 0xff45ef33c82f3d48, 0xa30bebc15fefcc78, 0x00000000cb3d181c }
+
+Standard jump function pseudocode:
+
+ Jump constant j = 0xb10773cb...44085302f77130ca
+ Generator state: s
+ New generator state: t = 0
+ foreach bit in j, low to high:
+ if the bit is one:
+ t ^= s
+ next s
+ s = t
+
+
+The complete list of full period constants
+------------------------------------------
+
+29-48-54 27 x^928 + x^874 + x^870 + x^840 + x^814 + x^784 + x^759 + x^750 + x^724 + x^720 + x^634 + x^630 + x^600 + x^574 + x^544 + x^519 + x^510 + x^484 + x^480 + x^390 + x^360 + x^270 + x^240 + x^150 + x^120 + x^30 + 1
+
+29-18-56 65 x^928 + x^870 + x^850 + x^840 + x^832 + x^821 + x^802 + x^791 + x^761 + x^750 + x^734 + x^731 + x^720 + x^712 + x^701 + x^686 + x^674 + x^671 + x^663 + x^641 + x^630 + x^611 + x^610 + x^603 + x^600 + x^596 + x^577 + x^566 + x^547 + x^524 + x^517 + x^510 + x^487 + x^480 + x^476 + x^464 + x^457 + x^446 + x^427 + x^423 + x^397 + x^390 + x^367 + x^363 + x^360 + x^356 + x^352 + x^341 + x^326 + x^322 + x^311 + x^281 + x^270 + x^251 + x^240 + x^236 + x^232 + x^221 + x^191 + x^161 + x^150 + x^131 + x^120 + x^30 + 1
+
+29-32-36 81 x^928 + x^874 + x^870 + x^840 + x^820 + x^819 + x^794 + x^790 + x^770 + x^765 + x^764 + x^760 + x^754 + x^750 + x^740 + x^739 + x^735 + x^734 + x^730 + x^709 + x^704 + x^674 + x^670 + x^665 + x^660 + x^650 + x^649 + x^635 + x^634 + x^619 + x^614 + x^605 + x^595 + x^585 + x^584 + x^579 + x^564 + x^555 + x^545 + x^540 + x^524 + x^515 + x^514 + x^495 + x^490 + x^485 + x^460 + x^455 + x^435 + x^425 + x^400 + x^395 + x^375 + x^370 + x^365 + x^340 + x^315 + x^310 + x^305 + x^300 + x^290 + x^285 + x^275 + x^260 + x^250 + x^245 + x^240 + x^215 + x^190 + x^180 + x^170 + x^150 + x^130 + x^120 + x^115 + x^105 + x^100 + x^75 + x^40 + x^30 + 1
+
+39-10-20 105 x^928 + x^898 + x^870 + x^841 + x^840 + x^808 + x^783 + x^782 + x^781 + x^780 + x^778 + x^754 + x^752 + x^724 + x^721 + x^720 + x^696 + x^692 + x^688 + x^667 + x^666 + x^661 + x^660 + x^658 + x^638 + x^636 + x^632 + x^609 + x^607 + x^606 + x^605 + x^604 + x^601 + x^600 + x^580 + x^578 + x^576 + x^572 + x^568 + x^549 + x^548 + x^541 + x^540 + x^538 + x^516 + x^512 + x^489 + x^485 + x^484 + x^481 + x^480 + x^456 + x^452 + x^448 + x^429 + x^428 + x^421 + x^420 + x^418 + x^400 + x^398 + x^396 + x^392 + x^368 + x^365 + x^364 + x^336 + x^328 + x^319 + x^318 + x^315 + x^314 + x^298 + x^286 + x^278 + x^276 + x^274 + x^255 + x^254 + x^249 + x^245 + x^228 + x^226 + x^220 + x^208 + x^199 + x^198 + x^189 + x^188 + x^178 + x^168 + x^166 + x^160 + x^129 + x^128 + x^108 + x^100 + x^88 + x^79 + x^78 + x^69 + x^68 + x^58 + x^40 + 1
+
+4-4-9 149 x^928 + x^898 + x^886 + x^870 + x^856 + x^826 + x^823 + x^808 + x^796 + x^793 + x^784 + x^778 + x^772 + x^766 + x^760 + x^754 + x^751 + x^742 + x^736 + x^734 + x^724 + x^713 + x^709 + x^706 + x^703 + x^694 + x^691 + x^688 + x^676 + x^674 + x^673 + x^664 + x^662 + x^658 + x^653 + x^641 + x^640 + x^634 + x^632 + x^631 + x^620 + x^614 + x^611 + x^608 + x^604 + x^602 + x^599 + x^590 + x^583 + x^581 + x^578 + x^574 + x^572 + x^571 + x^569 + x^568 + x^561 + x^554 + x^553 + x^551 + x^544 + x^542 + x^540 + x^538 + x^532 + x^531 + x^518 + x^514 + x^512 + x^502 + x^498 + x^489 + x^488 + x^484 + x^482 + x^480 + x^479 + x^468 + x^463 + x^456 + x^454 + x^452 + x^449 + x^448 + x^438 + x^433 + x^429 + x^424 + x^422 + x^420 + x^418 + x^408 + x^406 + x^401 + x^396 + x^394 + x^392 + x^380 + x^378 + x^376 + x^371 + x^367 + x^364 + x^362 + x^360 + x^350 + x^348 + x^346 + x^341 + x^334 + x^332 + x^328 + x^318 + x^311 + x^307 + x^304 + x^302 + x^300 + x^298 + x^288 + x^274 + x^272 + x^254 + x^249 + x^242 + x^240 + x^233 + x^212 + x^208 + x^189 + x^180 + x^178 + x^166 + x^152 + x^143 + x^136 + x^127 + x^122 + x^120 + x^113 + x^106 + x^88 + x^83 + x^81 + x^67 + x^58 + x^51 + x^14 + 1
+
+44-44-45 157 x^928 + x^898 + x^870 + x^850 + x^830 + x^820 + x^808 + x^800 + x^790 + x^782 + x^778 + x^777 + x^760 + x^757 + x^752 + x^747 + x^742 + x^727 + x^702 + x^694 + x^689 + x^688 + x^679 + x^674 + x^659 + x^658 + x^657 + x^644 + x^642 + x^637 + x^634 + x^627 + x^619 + x^612 + x^607 + x^606 + x^601 + x^596 + x^594 + x^591 + x^590 + x^584 + x^576 + x^568 + x^566 + x^561 + x^560 + x^556 + x^554 + x^546 + x^541 + x^539 + x^538 + x^518 + x^516 + x^511 + x^509 + x^503 + x^502 + x^499 + x^496 + x^488 + x^476 + x^471 + x^468 + x^466 + x^464 + x^448 + x^446 + x^441 + x^436 + x^421 + x^420 + x^415 + x^411 + x^408 + x^404 + x^400 + x^388 + x^383 + x^381 + x^379 + x^376 + x^363 + x^358 + x^356 + x^354 + x^346 + x^344 + x^340 + x^333 + x^330 + x^328 + x^325 + x^316 + x^312 + x^307 + x^305 + x^302 + x^284 + x^282 + x^277 + x^270 + x^268 + x^267 + x^256 + x^252 + x^249 + x^245 + x^244 + x^239 + x^238 + x^236 + x^231 + x^228 + x^226 + x^224 + x^222 + x^215 + x^208 + x^207 + x^205 + x^203 + x^201 + x^199 + x^196 + x^194 + x^192 + x^189 + x^185 + x^184 + x^182 + x^180 + x^175 + x^171 + x^168 + x^166 + x^162 + x^161 + x^155 + x^152 + x^150 + x^141 + x^134 + x^132 + x^131 + x^127 + x^122 + x^111 + x^104 + x^92 + x^90 + x^83 + x^81 + x^58 + x^37 + 1
+
+40-40-7 167 x^928 + x^898 + x^870 + x^866 + x^838 + x^836 + x^807 + x^806 + x^804 + x^778 + x^777 + x^774 + x^745 + x^744 + x^742 + x^718 + x^714 + x^713 + x^712 + x^687 + x^658 + x^657 + x^656 + x^655 + x^654 + x^626 + x^620 + x^619 + x^598 + x^596 + x^592 + x^591 + x^589 + x^566 + x^564 + x^563 + x^562 + x^560 + x^558 + x^538 + x^534 + x^530 + x^529 + x^504 + x^503 + x^502 + x^500 + x^499 + x^498 + x^496 + x^478 + x^474 + x^470 + x^469 + x^443 + x^442 + x^441 + x^439 + x^438 + x^436 + x^434 + x^432 + x^418 + x^416 + x^414 + x^410 + x^409 + x^407 + x^406 + x^405 + x^402 + x^386 + x^383 + x^380 + x^374 + x^373 + x^372 + x^370 + x^356 + x^350 + x^349 + x^348 + x^347 + x^346 + x^345 + x^340 + x^328 + x^326 + x^324 + x^323 + x^321 + x^316 + x^314 + x^308 + x^298 + x^296 + x^294 + x^289 + x^285 + x^283 + x^282 + x^281 + x^280 + x^278 + x^263 + x^262 + x^259 + x^257 + x^255 + x^251 + x^249 + x^248 + x^246 + x^233 + x^232 + x^229 + x^220 + x^218 + x^217 + x^216 + x^208 + x^202 + x^201 + x^199 + x^195 + x^190 + x^188 + x^187 + x^186 + x^178 + x^172 + x^169 + x^167 + x^162 + x^161 + x^159 + x^158 + x^157 + x^146 + x^142 + x^139 + x^135 + x^125 + x^123 + x^116 + x^112 + x^107 + x^105 + x^94 + x^93 + x^88 + x^86 + x^84 + x^82 + x^75 + x^73 + x^69 + x^64 + x^58 + x^56 + x^54 + x^52 + x^43 + x^41 + x^39 + x^32 + 1
+
+47-47-18 217 x^928 + x^898 + x^871 + x^870 + x^843 + x^842 + x^841 + x^813 + x^812 + x^811 + x^808 + x^787 + x^786 + x^785 + x^783 + x^781 + x^778 + x^759 + x^757 + x^754 + x^753 + x^751 + x^729 + x^727 + x^726 + x^723 + x^721 + x^701 + x^700 + x^699 + x^697 + x^693 + x^691 + x^688 + x^673 + x^672 + x^670 + x^667 + x^663 + x^661 + x^658 + x^647 + x^644 + x^641 + x^637 + x^633 + x^631 + x^618 + x^617 + x^614 + x^613 + x^612 + x^611 + x^610 + x^607 + x^603 + x^601 + x^586 + x^585 + x^582 + x^581 + x^580 + x^577 + x^573 + x^571 + x^568 + x^562 + x^560 + x^556 + x^555 + x^554 + x^553 + x^551 + x^547 + x^543 + x^541 + x^538 + x^532 + x^530 + x^528 + x^524 + x^522 + x^521 + x^517 + x^513 + x^511 + x^506 + x^504 + x^502 + x^500 + x^497 + x^495 + x^491 + x^487 + x^483 + x^481 + x^478 + x^474 + x^472 + x^467 + x^466 + x^465 + x^461 + x^457 + x^453 + x^451 + x^444 + x^438 + x^437 + x^436 + x^435 + x^431 + x^427 + x^423 + x^412 + x^408 + x^407 + x^405 + x^401 + x^397 + x^390 + x^384 + x^377 + x^375 + x^371 + x^367 + x^366 + x^365 + x^363 + x^362 + x^360 + x^358 + x^356 + x^352 + x^350 + x^347 + x^345 + x^341 + x^337 + x^336 + x^334 + x^332 + x^324 + x^317 + x^315 + x^311 + x^307 + x^306 + x^305 + x^304 + x^302 + x^300 + x^298 + x^296 + x^287 + x^285 + x^281 + x^279 + x^277 + x^276 + x^274 + x^268 + x^266 + x^257 + x^255 + x^247 + x^244 + x^242 + x^240 + x^238 + x^234 + x^227 + x^221 + x^219 + x^216 + x^214 + x^206 + x^197 + x^195 + x^193 + x^182 + x^176 + x^161 + x^156 + x^152 + x^146 + x^139 + x^133 + x^116 + x^111 + x^109 + x^107 + x^96 + x^94 + x^92 + x^90 + x^88 + x^85 + x^81 + x^79 + x^77 + x^73 + x^66 + x^64 + x^62 + x^60 + x^53 + x^51 + x^47 + x^45 + x^36 + x^34 + x^32 + x^19 + x^17 + x^15 + 1
+
+31-52-54 249 x^928 + x^898 + x^872 + x^870 + x^842 + x^841 + x^838 + x^836 + x^812 + x^810 + x^782 + x^781 + x^779 + x^778 + x^774 + x^753 + x^752 + x^750 + x^748 + x^723 + x^722 + x^721 + x^719 + x^717 + x^714 + x^712 + x^694 + x^690 + x^689 + x^688 + x^686 + x^684 + x^664 + x^663 + x^662 + x^661 + x^660 + x^658 + x^657 + x^656 + x^655 + x^654 + x^653 + x^652 + x^650 + x^634 + x^633 + x^631 + x^630 + x^629 + x^628 + x^622 + x^604 + x^599 + x^597 + x^596 + x^593 + x^592 + x^590 + x^588 + x^570 + x^567 + x^566 + x^562 + x^560 + x^536 + x^534 + x^533 + x^531 + x^530 + x^526 + x^513 + x^512 + x^510 + x^509 + x^508 + x^507 + x^506 + x^505 + x^504 + x^502 + x^498 + x^483 + x^482 + x^481 + x^479 + x^478 + x^476 + x^466 + x^464 + x^454 + x^445 + x^438 + x^424 + x^423 + x^422 + x^421 + x^419 + x^418 + x^411 + x^404 + x^402 + x^394 + x^393 + x^392 + x^391 + x^390 + x^387 + x^386 + x^383 + x^380 + x^379 + x^378 + x^377 + x^376 + x^374 + x^364 + x^362 + x^361 + x^359 + x^357 + x^356 + x^355 + x^354 + x^351 + x^349 + x^348 + x^347 + x^345 + x^342 + x^340 + x^332 + x^330 + x^329 + x^328 + x^327 + x^325 + x^324 + x^323 + x^321 + x^319 + x^318 + x^316 + x^314 + x^312 + x^302 + x^301 + x^300 + x^296 + x^295 + x^294 + x^291 + x^289 + x^287 + x^286 + x^283 + x^278 + x^266 + x^265 + x^264 + x^263 + x^262 + x^260 + x^259 + x^255 + x^250 + x^234 + x^232 + x^229 + x^227 + x^226 + x^225 + x^222 + x^218 + x^216 + x^208 + x^204 + x^200 + x^198 + x^197 + x^195 + x^194 + x^193 + x^190 + x^188 + x^178 + x^174 + x^173 + x^172 + x^171 + x^170 + x^169 + x^168 + x^166 + x^165 + x^164 + x^162 + x^161 + x^160 + x^159 + x^158 + x^157 + x^156 + x^154 + x^142 + x^141 + x^139 + x^138 + x^135 + x^131 + x^128 + x^126 + x^116 + x^112 + x^110 + x^107 + x^106 + x^105 + x^104 + x^102 + x^100 + x^98 + x^96 + x^95 + x^94 + x^92 + x^88 + x^80 + x^79 + x^76 + x^74 + x^73 + x^71 + x^70 + x^69 + x^66 + x^65 + x^64 + x^56 + x^54 + x^48 + x^46 + x^45 + x^43 + x^38 + x^35 + x^33 + x^30 + 1
+
+57-54-32 249 x^928 + x^898 + x^870 + x^855 + x^848 + x^834 + x^826 + x^825 + x^808 + x^806 + x^805 + x^804 + x^798 + x^796 + x^788 + x^778 + x^776 + x^775 + x^766 + x^763 + x^756 + x^752 + x^745 + x^742 + x^736 + x^735 + x^734 + x^731 + x^715 + x^714 + x^713 + x^705 + x^703 + x^694 + x^688 + x^684 + x^682 + x^671 + x^660 + x^658 + x^652 + x^642 + x^641 + x^639 + x^634 + x^630 + x^621 + x^620 + x^616 + x^612 + x^609 + x^604 + x^602 + x^593 + x^591 + x^584 + x^579 + x^570 + x^568 + x^566 + x^565 + x^562 + x^558 + x^554 + x^550 + x^544 + x^536 + x^535 + x^533 + x^530 + x^529 + x^528 + x^524 + x^522 + x^517 + x^512 + x^505 + x^496 + x^494 + x^492 + x^491 + x^489 + x^480 + x^478 + x^475 + x^471 + x^464 + x^461 + x^454 + x^452 + x^450 + x^446 + x^445 + x^441 + x^438 + x^437 + x^436 + x^434 + x^430 + x^429 + x^427 + x^425 + x^420 + x^418 + x^416 + x^415 + x^413 + x^411 + x^409 + x^402 + x^397 + x^394 + x^392 + x^388 + x^386 + x^384 + x^377 + x^375 + x^371 + x^368 + x^367 + x^365 + x^364 + x^363 + x^362 + x^360 + x^358 + x^356 + x^353 + x^351 + x^346 + x^342 + x^341 + x^338 + x^335 + x^333 + x^330 + x^328 + x^326 + x^325 + x^317 + x^307 + x^304 + x^300 + x^296 + x^295 + x^293 + x^292 + x^288 + x^286 + x^285 + x^284 + x^283 + x^278 + x^277 + x^276 + x^273 + x^271 + x^270 + x^262 + x^261 + x^255 + x^253 + x^249 + x^248 + x^246 + x^245 + x^240 + x^236 + x^232 + x^231 + x^227 + x^224 + x^223 + x^221 + x^220 + x^217 + x^216 + x^212 + x^208 + x^205 + x^204 + x^198 + x^196 + x^192 + x^191 + x^190 + x^189 + x^187 + x^186 + x^178 + x^176 + x^175 + x^171 + x^169 + x^165 + x^160 + x^159 + x^158 + x^154 + x^152 + x^150 + x^148 + x^146 + x^144 + x^141 + x^134 + x^132 + x^131 + x^129 + x^127 + x^125 + x^123 + x^120 + x^118 + x^114 + x^113 + x^111 + x^106 + x^104 + x^103 + x^102 + x^101 + x^100 + x^99 + x^97 + x^95 + x^93 + x^88 + x^85 + x^77 + x^67 + x^62 + x^60 + x^55 + x^54 + x^53 + x^52 + x^51 + x^50 + x^48 + x^46 + x^40 + x^37 + x^33 + x^28 + x^27 + x^24 + 1
+
+1-38-28 251 x^928 + x^898 + x^870 + x^866 + x^836 + x^834 + x^832 + x^818 + x^808 + x^806 + x^804 + x^797 + x^788 + x^778 + x^776 + x^770 + x^767 + x^765 + x^738 + x^736 + x^733 + x^724 + x^717 + x^708 + x^706 + x^703 + x^701 + x^690 + x^688 + x^685 + x^678 + x^677 + x^676 + x^674 + x^672 + x^664 + x^658 + x^656 + x^652 + x^647 + x^645 + x^640 + x^626 + x^624 + x^621 + x^620 + x^616 + x^610 + x^608 + x^604 + x^600 + x^594 + x^593 + x^592 + x^590 + x^580 + x^578 + x^576 + x^575 + x^574 + x^572 + x^568 + x^566 + x^565 + x^564 + x^562 + x^560 + x^558 + x^552 + x^550 + x^549 + x^548 + x^544 + x^541 + x^538 + x^536 + x^533 + x^532 + x^529 + x^526 + x^522 + x^520 + x^519 + x^517 + x^514 + x^513 + x^509 + x^508 + x^504 + x^501 + x^494 + x^493 + x^484 + x^482 + x^469 + x^468 + x^466 + x^461 + x^453 + x^452 + x^448 + x^447 + x^436 + x^428 + x^424 + x^422 + x^420 + x^416 + x^414 + x^413 + x^412 + x^409 + x^408 + x^406 + x^405 + x^404 + x^400 + x^399 + x^397 + x^393 + x^391 + x^388 + x^385 + x^382 + x^381 + x^376 + x^374 + x^372 + x^370 + x^368 + x^366 + x^365 + x^360 + x^350 + x^349 + x^344 + x^341 + x^340 + x^336 + x^335 + x^334 + x^333 + x^332 + x^328 + x^326 + x^322 + x^319 + x^318 + x^308 + x^304 + x^300 + x^298 + x^293 + x^292 + x^290 + x^289 + x^287 + x^284 + x^281 + x^279 + x^278 + x^272 + x^271 + x^269 + x^264 + x^263 + x^261 + x^256 + x^254 + x^253 + x^250 + x^240 + x^238 + x^234 + x^229 + x^228 + x^226 + x^223 + x^221 + x^218 + x^213 + x^210 + x^208 + x^204 + x^198 + x^197 + x^196 + x^194 + x^191 + x^188 + x^186 + x^180 + x^176 + x^174 + x^172 + x^170 + x^169 + x^167 + x^165 + x^161 + x^159 + x^157 + x^153 + x^150 + x^149 + x^148 + x^145 + x^136 + x^135 + x^134 + x^133 + x^132 + x^130 + x^128 + x^127 + x^125 + x^122 + x^120 + x^118 + x^113 + x^110 + x^109 + x^108 + x^100 + x^96 + x^94 + x^92 + x^90 + x^88 + x^86 + x^85 + x^84 + x^81 + x^80 + x^72 + x^70 + x^66 + x^58 + x^53 + x^45 + x^36 + x^34 + x^32 + x^30 + x^24 + x^20 + x^16 + x^12 + x^8 + x^2 + 1
+
+47-57-20 279 x^928 + x^898 + x^880 + x^870 + x^864 + x^851 + x^850 + x^846 + x^830 + x^821 + x^820 + x^817 + x^812 + x^808 + x^807 + x^804 + x^796 + x^790 + x^783 + x^782 + x^777 + x^773 + x^762 + x^757 + x^753 + x^744 + x^743 + x^739 + x^731 + x^728 + x^714 + x^713 + x^710 + x^709 + x^705 + x^701 + x^694 + x^692 + x^688 + x^684 + x^683 + x^675 + x^672 + x^671 + x^668 + x^662 + x^660 + x^654 + x^653 + x^646 + x^642 + x^641 + x^637 + x^626 + x^624 + x^623 + x^608 + x^607 + x^603 + x^598 + x^593 + x^592 + x^590 + x^585 + x^578 + x^577 + x^574 + x^573 + x^572 + x^569 + x^568 + x^567 + x^563 + x^559 + x^558 + x^555 + x^551 + x^548 + x^547 + x^542 + x^539 + x^538 + x^537 + x^536 + x^535 + x^532 + x^530 + x^529 + x^525 + x^524 + x^521 + x^518 + x^517 + x^506 + x^505 + x^501 + x^496 + x^491 + x^490 + x^487 + x^471 + x^467 + x^462 + x^456 + x^452 + x^448 + x^446 + x^441 + x^440 + x^437 + x^433 + x^428 + x^420 + x^412 + x^411 + x^404 + x^403 + x^402 + x^401 + x^399 + x^398 + x^396 + x^394 + x^381 + x^378 + x^376 + x^371 + x^370 + x^366 + x^365 + x^360 + x^355 + x^354 + x^351 + x^348 + x^346 + x^344 + x^342 + x^340 + x^339 + x^337 + x^335 + x^333 + x^331 + x^330 + x^329 + x^328 + x^327 + x^320 + x^318 + x^313 + x^307 + x^299 + x^298 + x^297 + x^296 + x^295 + x^294 + x^293 + x^291 + x^283 + x^282 + x^281 + x^280 + x^279 + x^277 + x^276 + x^266 + x^265 + x^264 + x^263 + x^262 + x^261 + x^259 + x^258 + x^257 + x^256 + x^251 + x^248 + x^247 + x^242 + x^240 + x^235 + x^234 + x^233 + x^231 + x^230 + x^229 + x^227 + x^225 + x^224 + x^219 + x^214 + x^212 + x^210 + x^206 + x^204 + x^203 + x^200 + x^196 + x^195 + x^194 + x^192 + x^191 + x^190 + x^188 + x^185 + x^182 + x^178 + x^176 + x^173 + x^159 + x^158 + x^157 + x^156 + x^151 + x^146 + x^144 + x^143 + x^142 + x^140 + x^138 + x^136 + x^131 + x^130 + x^128 + x^127 + x^126 + x^123 + x^122 + x^118 + x^117 + x^115 + x^114 + x^113 + x^108 + x^105 + x^99 + x^98 + x^94 + x^90 + x^88 + x^87 + x^86 + x^84 + x^82 + x^81 + x^80 + x^79 + x^78 + x^75 + x^74 + x^72 + x^71 + x^70 + x^65 + x^64 + x^58 + x^57 + x^56 + x^55 + x^52 + x^50 + x^48 + x^47 + x^46 + x^45 + x^42 + x^39 + x^26 + x^24 + x^23 + x^22 + x^20 + x^18 + x^15 + x^8 + x^4 + 1
+
+3-16-56 281 x^928 + x^898 + x^882 + x^870 + x^836 + x^820 + x^816 + x^808 + x^803 + x^802 + x^799 + x^778 + x^773 + x^770 + x^762 + x^757 + x^754 + x^753 + x^750 + x^737 + x^733 + x^720 + x^717 + x^713 + x^700 + x^688 + x^683 + x^680 + x^679 + x^674 + x^660 + x^658 + x^656 + x^654 + x^653 + x^650 + x^646 + x^644 + x^641 + x^640 + x^637 + x^634 + x^633 + x^623 + x^618 + x^617 + x^616 + x^613 + x^597 + x^590 + x^585 + x^584 + x^581 + x^577 + x^574 + x^572 + x^563 + x^562 + x^560 + x^555 + x^554 + x^553 + x^551 + x^547 + x^544 + x^537 + x^536 + x^533 + x^525 + x^521 + x^520 + x^519 + x^517 + x^515 + x^513 + x^510 + x^508 + x^505 + x^502 + x^501 + x^498 + x^493 + x^490 + x^485 + x^480 + x^477 + x^476 + x^475 + x^472 + x^469 + x^461 + x^458 + x^453 + x^449 + x^448 + x^445 + x^444 + x^442 + x^435 + x^433 + x^432 + x^431 + x^426 + x^425 + x^422 + x^420 + x^418 + x^412 + x^411 + x^409 + x^405 + x^403 + x^401 + x^400 + x^397 + x^393 + x^385 + x^381 + x^368 + x^366 + x^365 + x^362 + x^361 + x^359 + x^356 + x^353 + x^350 + x^346 + x^341 + x^338 + x^335 + x^334 + x^333 + x^332 + x^328 + x^326 + x^325 + x^323 + x^322 + x^320 + x^319 + x^317 + x^316 + x^315 + x^313 + x^312 + x^310 + x^309 + x^307 + x^305 + x^300 + x^295 + x^293 + x^291 + x^290 + x^289 + x^288 + x^286 + x^285 + x^282 + x^277 + x^274 + x^273 + x^266 + x^265 + x^263 + x^261 + x^259 + x^255 + x^253 + x^251 + x^248 + x^247 + x^246 + x^242 + x^241 + x^239 + x^234 + x^233 + x^232 + x^230 + x^229 + x^228 + x^226 + x^225 + x^224 + x^222 + x^218 + x^217 + x^215 + x^212 + x^210 + x^208 + x^206 + x^205 + x^203 + x^201 + x^200 + x^194 + x^193 + x^191 + x^189 + x^183 + x^181 + x^180 + x^178 + x^177 + x^176 + x^174 + x^172 + x^171 + x^169 + x^167 + x^166 + x^163 + x^162 + x^159 + x^157 + x^155 + x^151 + x^150 + x^149 + x^147 + x^140 + x^139 + x^138 + x^137 + x^136 + x^134 + x^131 + x^128 + x^127 + x^126 + x^121 + x^119 + x^118 + x^117 + x^111 + x^106 + x^104 + x^102 + x^101 + x^99 + x^98 + x^91 + x^90 + x^88 + x^86 + x^85 + x^81 + x^79 + x^75 + x^74 + x^73 + x^72 + x^71 + x^69 + x^68 + x^65 + x^64 + x^63 + x^62 + x^61 + x^58 + x^57 + x^56 + x^55 + x^51 + x^50 + x^49 + x^41 + x^38 + x^36 + x^34 + x^33 + x^32 + x^24 + x^21 + x^9 + x^6 + 1
+
+10-38-9 285 x^928 + x^898 + x^870 + x^841 + x^840 + x^838 + x^836 + x^811 + x^810 + x^781 + x^777 + x^774 + x^751 + x^748 + x^723 + x^721 + x^720 + x^719 + x^718 + x^717 + x^716 + x^714 + x^712 + x^694 + x^692 + x^691 + x^688 + x^686 + x^664 + x^663 + x^661 + x^660 + x^659 + x^657 + x^652 + x^650 + x^632 + x^631 + x^630 + x^628 + x^627 + x^625 + x^624 + x^623 + x^605 + x^604 + x^603 + x^599 + x^597 + x^594 + x^593 + x^591 + x^590 + x^588 + x^576 + x^575 + x^574 + x^572 + x^571 + x^570 + x^569 + x^567 + x^565 + x^560 + x^544 + x^543 + x^542 + x^540 + x^539 + x^537 + x^535 + x^531 + x^526 + x^514 + x^510 + x^509 + x^507 + x^506 + x^505 + x^504 + x^503 + x^502 + x^498 + x^485 + x^484 + x^478 + x^474 + x^471 + x^470 + x^468 + x^466 + x^464 + x^458 + x^455 + x^452 + x^450 + x^449 + x^448 + x^445 + x^441 + x^439 + x^428 + x^419 + x^416 + x^415 + x^411 + x^407 + x^406 + x^405 + x^404 + x^402 + x^398 + x^392 + x^388 + x^385 + x^384 + x^383 + x^382 + x^381 + x^380 + x^379 + x^376 + x^374 + x^364 + x^359 + x^357 + x^356 + x^355 + x^354 + x^353 + x^351 + x^349 + x^346 + x^345 + x^343 + x^340 + x^338 + x^334 + x^328 + x^326 + x^325 + x^324 + x^321 + x^320 + x^319 + x^318 + x^315 + x^314 + x^312 + x^306 + x^298 + x^297 + x^295 + x^294 + x^293 + x^291 + x^288 + x^287 + x^286 + x^285 + x^283 + x^282 + x^281 + x^278 + x^276 + x^274 + x^270 + x^269 + x^268 + x^263 + x^261 + x^256 + x^255 + x^254 + x^244 + x^242 + x^237 + x^236 + x^235 + x^234 + x^233 + x^232 + x^229 + x^225 + x^222 + x^219 + x^216 + x^214 + x^212 + x^210 + x^209 + x^208 + x^207 + x^206 + x^202 + x^198 + x^194 + x^193 + x^192 + x^191 + x^190 + x^184 + x^182 + x^178 + x^176 + x^175 + x^174 + x^173 + x^168 + x^166 + x^164 + x^160 + x^159 + x^157 + x^154 + x^152 + x^151 + x^150 + x^145 + x^144 + x^142 + x^136 + x^135 + x^134 + x^129 + x^128 + x^122 + x^121 + x^119 + x^118 + x^116 + x^114 + x^113 + x^112 + x^111 + x^108 + x^105 + x^103 + x^100 + x^98 + x^97 + x^95 + x^94 + x^92 + x^89 + x^88 + x^87 + x^85 + x^84 + x^83 + x^78 + x^76 + x^74 + x^73 + x^71 + x^70 + x^69 + x^67 + x^66 + x^64 + x^61 + x^59 + x^57 + x^56 + x^54 + x^53 + x^51 + x^49 + x^48 + x^46 + x^45 + x^43 + x^42 + x^41 + x^40 + x^38 + x^37 + x^36 + x^35 + x^34 + x^33 + x^32 + x^30 + x^4 + 1
+
+15-22-28 289 x^928 + x^898 + x^870 + x^838 + x^821 + x^806 + x^805 + x^792 + x^790 + x^778 + x^774 + x^762 + x^745 + x^744 + x^718 + x^697 + x^682 + x^672 + x^670 + x^667 + x^666 + x^658 + x^656 + x^653 + x^652 + x^651 + x^650 + x^642 + x^641 + x^638 + x^637 + x^623 + x^608 + x^607 + x^598 + x^594 + x^588 + x^575 + x^574 + x^573 + x^566 + x^565 + x^564 + x^563 + x^562 + x^557 + x^548 + x^547 + x^542 + x^538 + x^533 + x^531 + x^529 + x^526 + x^517 + x^516 + x^514 + x^513 + x^505 + x^504 + x^503 + x^502 + x^501 + x^499 + x^498 + x^497 + x^488 + x^487 + x^484 + x^483 + x^482 + x^474 + x^470 + x^468 + x^467 + x^466 + x^464 + x^457 + x^454 + x^453 + x^449 + x^447 + x^444 + x^443 + x^442 + x^441 + x^440 + x^439 + x^437 + x^436 + x^435 + x^428 + x^427 + x^426 + x^425 + x^422 + x^418 + x^417 + x^414 + x^409 + x^406 + x^405 + x^392 + x^391 + x^389 + x^388 + x^387 + x^386 + x^379 + x^378 + x^374 + x^372 + x^371 + x^368 + x^367 + x^362 + x^361 + x^358 + x^357 + x^354 + x^350 + x^349 + x^347 + x^342 + x^341 + x^340 + x^334 + x^333 + x^332 + x^331 + x^330 + x^329 + x^328 + x^327 + x^324 + x^323 + x^320 + x^319 + x^313 + x^308 + x^307 + x^305 + x^303 + x^302 + x^301 + x^300 + x^299 + x^298 + x^297 + x^296 + x^295 + x^294 + x^293 + x^292 + x^288 + x^287 + x^286 + x^283 + x^282 + x^280 + x^279 + x^278 + x^277 + x^276 + x^271 + x^269 + x^268 + x^267 + x^266 + x^265 + x^255 + x^254 + x^253 + x^249 + x^247 + x^241 + x^238 + x^237 + x^236 + x^235 + x^226 + x^225 + x^224 + x^223 + x^220 + x^219 + x^215 + x^214 + x^212 + x^210 + x^209 + x^205 + x^202 + x^201 + x^200 + x^199 + x^197 + x^190 + x^186 + x^182 + x^181 + x^180 + x^177 + x^174 + x^173 + x^172 + x^171 + x^170 + x^169 + x^168 + x^165 + x^163 + x^161 + x^160 + x^158 + x^155 + x^153 + x^151 + x^150 + x^146 + x^143 + x^142 + x^137 + x^136 + x^135 + x^131 + x^130 + x^128 + x^127 + x^126 + x^125 + x^124 + x^123 + x^122 + x^120 + x^119 + x^117 + x^114 + x^113 + x^111 + x^105 + x^104 + x^103 + x^102 + x^101 + x^99 + x^97 + x^93 + x^90 + x^89 + x^88 + x^87 + x^86 + x^84 + x^83 + x^82 + x^81 + x^80 + x^79 + x^77 + x^75 + x^72 + x^71 + x^70 + x^69 + x^68 + x^65 + x^60 + x^59 + x^58 + x^55 + x^54 + x^52 + x^51 + x^50 + x^48 + x^43 + x^42 + x^40 + x^39 + x^30 + x^28 + x^27 + x^24 + x^23 + x^19 + x^16 + x^15 + 1
+
+12-41-35 291 x^928 + x^898 + x^886 + x^873 + x^870 + x^860 + x^856 + x^847 + x^844 + x^834 + x^830 + x^826 + x^818 + x^817 + x^805 + x^804 + x^796 + x^795 + x^791 + x^784 + x^782 + x^779 + x^769 + x^765 + x^762 + x^758 + x^756 + x^753 + x^752 + x^743 + x^739 + x^736 + x^735 + x^732 + x^731 + x^730 + x^727 + x^726 + x^723 + x^722 + x^717 + x^713 + x^710 + x^705 + x^701 + x^700 + x^692 + x^685 + x^683 + x^680 + x^676 + x^671 + x^663 + x^659 + x^657 + x^654 + x^653 + x^645 + x^642 + x^637 + x^632 + x^628 + x^624 + x^623 + x^619 + x^616 + x^615 + x^612 + x^610 + x^607 + x^606 + x^603 + x^598 + x^597 + x^594 + x^593 + x^590 + x^589 + x^585 + x^580 + x^577 + x^576 + x^572 + x^567 + x^564 + x^560 + x^559 + x^556 + x^550 + x^545 + x^543 + x^538 + x^537 + x^534 + x^533 + x^532 + x^528 + x^520 + x^516 + x^513 + x^508 + x^507 + x^506 + x^504 + x^499 + x^496 + x^491 + x^490 + x^487 + x^486 + x^485 + x^481 + x^477 + x^474 + x^466 + x^465 + x^457 + x^452 + x^448 + x^447 + x^446 + x^438 + x^436 + x^435 + x^434 + x^430 + x^425 + x^422 + x^421 + x^417 + x^413 + x^409 + x^404 + x^395 + x^393 + x^392 + x^388 + x^387 + x^386 + x^384 + x^382 + x^380 + x^379 + x^374 + x^373 + x^370 + x^367 + x^366 + x^362 + x^361 + x^358 + x^357 + x^354 + x^353 + x^352 + x^350 + x^349 + x^345 + x^344 + x^343 + x^340 + x^337 + x^336 + x^332 + x^331 + x^328 + x^324 + x^323 + x^317 + x^315 + x^314 + x^313 + x^311 + x^305 + x^302 + x^298 + x^297 + x^296 + x^295 + x^292 + x^291 + x^288 + x^283 + x^280 + x^276 + x^275 + x^272 + x^268 + x^266 + x^262 + x^259 + x^258 + x^255 + x^251 + x^250 + x^242 + x^241 + x^240 + x^232 + x^227 + x^225 + x^224 + x^223 + x^216 + x^214 + x^212 + x^211 + x^210 + x^206 + x^203 + x^198 + x^189 + x^188 + x^177 + x^176 + x^175 + x^173 + x^168 + x^164 + x^162 + x^160 + x^159 + x^156 + x^155 + x^154 + x^152 + x^149 + x^147 + x^146 + x^145 + x^142 + x^139 + x^138 + x^137 + x^134 + x^132 + x^130 + x^124 + x^123 + x^122 + x^115 + x^113 + x^112 + x^111 + x^110 + x^108 + x^106 + x^103 + x^102 + x^98 + x^97 + x^96 + x^95 + x^94 + x^92 + x^91 + x^90 + x^88 + x^85 + x^80 + x^78 + x^77 + x^76 + x^73 + x^71 + x^70 + x^69 + x^68 + x^67 + x^65 + x^64 + x^63 + x^54 + x^53 + x^52 + x^51 + x^50 + x^49 + x^48 + x^47 + x^44 + x^43 + x^42 + x^40 + x^39 + x^31 + x^30 + x^29 + x^21 + x^13 + 1
+
+35-54-56 293 x^928 + x^898 + x^870 + x^862 + x^832 + x^826 + x^808 + x^796 + x^794 + x^790 + x^778 + x^765 + x^758 + x^754 + x^734 + x^729 + x^728 + x^722 + x^718 + x^715 + x^706 + x^700 + x^699 + x^698 + x^693 + x^686 + x^682 + x^676 + x^673 + x^672 + x^669 + x^668 + x^662 + x^658 + x^656 + x^655 + x^652 + x^650 + x^649 + x^646 + x^645 + x^642 + x^639 + x^638 + x^636 + x^630 + x^622 + x^614 + x^613 + x^612 + x^610 + x^608 + x^607 + x^600 + x^596 + x^595 + x^592 + x^590 + x^589 + x^587 + x^582 + x^574 + x^571 + x^568 + x^566 + x^560 + x^558 + x^554 + x^553 + x^552 + x^548 + x^544 + x^542 + x^538 + x^536 + x^534 + x^530 + x^528 + x^527 + x^524 + x^523 + x^522 + x^520 + x^518 + x^516 + x^515 + x^511 + x^506 + x^499 + x^497 + x^493 + x^492 + x^490 + x^487 + x^482 + x^478 + x^474 + x^467 + x^462 + x^461 + x^452 + x^445 + x^442 + x^439 + x^437 + x^436 + x^433 + x^428 + x^427 + x^422 + x^418 + x^413 + x^412 + x^410 + x^409 + x^407 + x^406 + x^403 + x^401 + x^398 + x^396 + x^392 + x^390 + x^386 + x^384 + x^383 + x^382 + x^380 + x^378 + x^377 + x^373 + x^372 + x^371 + x^366 + x^365 + x^362 + x^353 + x^352 + x^350 + x^348 + x^346 + x^344 + x^342 + x^340 + x^338 + x^336 + x^335 + x^334 + x^332 + x^331 + x^328 + x^326 + x^320 + x^318 + x^317 + x^316 + x^313 + x^312 + x^311 + x^310 + x^308 + x^304 + x^300 + x^299 + x^298 + x^296 + x^295 + x^293 + x^290 + x^288 + x^284 + x^282 + x^275 + x^271 + x^269 + x^268 + x^266 + x^265 + x^264 + x^263 + x^262 + x^260 + x^259 + x^253 + x^250 + x^247 + x^246 + x^244 + x^242 + x^240 + x^239 + x^238 + x^235 + x^233 + x^232 + x^228 + x^227 + x^226 + x^224 + x^221 + x^220 + x^218 + x^216 + x^214 + x^212 + x^208 + x^203 + x^199 + x^198 + x^196 + x^195 + x^194 + x^193 + x^192 + x^189 + x^187 + x^182 + x^178 + x^176 + x^175 + x^173 + x^168 + x^167 + x^166 + x^163 + x^161 + x^160 + x^156 + x^152 + x^150 + x^148 + x^146 + x^140 + x^138 + x^135 + x^133 + x^131 + x^130 + x^129 + x^128 + x^127 + x^126 + x^125 + x^123 + x^122 + x^120 + x^118 + x^116 + x^115 + x^113 + x^112 + x^109 + x^106 + x^105 + x^104 + x^103 + x^101 + x^100 + x^95 + x^94 + x^92 + x^84 + x^82 + x^80 + x^79 + x^77 + x^76 + x^74 + x^71 + x^69 + x^68 + x^66 + x^65 + x^64 + x^63 + x^55 + x^54 + x^53 + x^49 + x^48 + x^46 + x^45 + x^43 + x^42 + x^41 + x^40 + x^38 + x^36 + x^34 + x^24 + x^18 + x^6 + 1
+
+44-42-5 307 x^928 + x^898 + x^880 + x^870 + x^856 + x^850 + x^839 + x^832 + x^826 + x^820 + x^816 + x^815 + x^808 + x^792 + x^791 + x^790 + x^784 + x^779 + x^778 + x^768 + x^762 + x^760 + x^756 + x^754 + x^753 + x^747 + x^744 + x^738 + x^737 + x^736 + x^731 + x^726 + x^723 + x^720 + x^719 + x^717 + x^713 + x^707 + x^696 + x^693 + x^689 + x^688 + x^682 + x^678 + x^677 + x^670 + x^666 + x^665 + x^663 + x^660 + x^658 + x^657 + x^653 + x^646 + x^633 + x^630 + x^628 + x^624 + x^617 + x^616 + x^612 + x^611 + x^606 + x^605 + x^604 + x^603 + x^600 + x^599 + x^597 + x^594 + x^592 + x^588 + x^587 + x^586 + x^582 + x^580 + x^576 + x^575 + x^568 + x^563 + x^556 + x^552 + x^546 + x^544 + x^542 + x^540 + x^539 + x^537 + x^534 + x^533 + x^526 + x^522 + x^521 + x^520 + x^518 + x^517 + x^516 + x^514 + x^511 + x^510 + x^508 + x^505 + x^503 + x^494 + x^492 + x^488 + x^485 + x^481 + x^475 + x^474 + x^470 + x^467 + x^464 + x^463 + x^462 + x^454 + x^452 + x^449 + x^447 + x^446 + x^444 + x^443 + x^442 + x^439 + x^438 + x^437 + x^432 + x^431 + x^430 + x^427 + x^426 + x^424 + x^422 + x^419 + x^418 + x^413 + x^410 + x^409 + x^406 + x^404 + x^401 + x^398 + x^396 + x^393 + x^392 + x^391 + x^390 + x^389 + x^385 + x^383 + x^380 + x^379 + x^376 + x^374 + x^372 + x^370 + x^368 + x^367 + x^366 + x^365 + x^364 + x^363 + x^361 + x^358 + x^355 + x^354 + x^353 + x^349 + x^348 + x^347 + x^344 + x^343 + x^342 + x^341 + x^338 + x^337 + x^336 + x^334 + x^332 + x^329 + x^328 + x^318 + x^317 + x^312 + x^310 + x^308 + x^307 + x^306 + x^305 + x^304 + x^299 + x^296 + x^294 + x^293 + x^291 + x^289 + x^281 + x^280 + x^278 + x^277 + x^276 + x^274 + x^272 + x^270 + x^267 + x^265 + x^264 + x^263 + x^260 + x^253 + x^252 + x^251 + x^250 + x^247 + x^242 + x^241 + x^239 + x^234 + x^233 + x^231 + x^230 + x^229 + x^224 + x^223 + x^222 + x^217 + x^214 + x^212 + x^210 + x^209 + x^208 + x^203 + x^200 + x^199 + x^198 + x^196 + x^195 + x^192 + x^191 + x^186 + x^185 + x^184 + x^181 + x^180 + x^179 + x^178 + x^177 + x^176 + x^175 + x^172 + x^171 + x^170 + x^169 + x^168 + x^166 + x^165 + x^161 + x^160 + x^159 + x^151 + x^146 + x^144 + x^143 + x^142 + x^141 + x^139 + x^137 + x^136 + x^135 + x^133 + x^131 + x^130 + x^120 + x^116 + x^114 + x^113 + x^108 + x^107 + x^106 + x^104 + x^101 + x^98 + x^97 + x^96 + x^91 + x^90 + x^89 + x^87 + x^82 + x^80 + x^78 + x^73 + x^72 + x^67 + x^62 + x^58 + x^56 + x^55 + x^53 + x^49 + x^46 + x^44 + x^39 + x^32 + x^26 + x^17 + 1
+
+18-7-29 311 x^928 + x^926 + x^920 + x^918 + x^912 + x^910 + x^904 + x^902 + x^898 + x^896 + x^894 + x^890 + x^888 + x^886 + x^882 + x^880 + x^878 + x^874 + x^872 + x^870 + x^866 + x^858 + x^850 + x^842 + x^808 + x^806 + x^792 + x^790 + x^778 + x^776 + x^774 + x^762 + x^760 + x^758 + x^746 + x^730 + x^688 + x^686 + x^680 + x^678 + x^669 + x^667 + x^661 + x^659 + x^658 + x^656 + x^655 + x^654 + x^651 + x^650 + x^648 + x^647 + x^646 + x^643 + x^626 + x^618 + x^611 + x^605 + x^595 + x^589 + x^587 + x^575 + x^569 + x^568 + x^566 + x^561 + x^553 + x^541 + x^539 + x^538 + x^537 + x^536 + x^534 + x^533 + x^515 + x^511 + x^509 + x^507 + x^506 + x^503 + x^501 + x^497 + x^495 + x^493 + x^489 + x^485 + x^483 + x^481 + x^479 + x^475 + x^473 + x^471 + x^469 + x^457 + x^455 + x^453 + x^451 + x^449 + x^448 + x^447 + x^446 + x^443 + x^440 + x^439 + x^438 + x^437 + x^435 + x^433 + x^432 + x^431 + x^429 + x^426 + x^425 + x^424 + x^421 + x^418 + x^417 + x^416 + x^415 + x^414 + x^412 + x^409 + x^408 + x^406 + x^405 + x^404 + x^401 + x^398 + x^396 + x^395 + x^393 + x^392 + x^390 + x^388 + x^387 + x^380 + x^379 + x^377 + x^376 + x^372 + x^371 + x^370 + x^369 + x^367 + x^364 + x^363 + x^362 + x^361 + x^359 + x^358 + x^354 + x^352 + x^350 + x^349 + x^348 + x^341 + x^340 + x^337 + x^336 + x^335 + x^331 + x^330 + x^328 + x^326 + x^320 + x^319 + x^318 + x^317 + x^315 + x^314 + x^313 + x^312 + x^310 + x^308 + x^307 + x^302 + x^301 + x^299 + x^297 + x^295 + x^293 + x^291 + x^287 + x^284 + x^283 + x^282 + x^281 + x^280 + x^279 + x^278 + x^274 + x^271 + x^267 + x^266 + x^264 + x^263 + x^257 + x^256 + x^255 + x^252 + x^251 + x^250 + x^246 + x^241 + x^240 + x^239 + x^234 + x^233 + x^231 + x^230 + x^229 + x^226 + x^225 + x^224 + x^223 + x^222 + x^218 + x^216 + x^214 + x^212 + x^211 + x^206 + x^203 + x^202 + x^199 + x^198 + x^197 + x^193 + x^191 + x^190 + x^188 + x^185 + x^183 + x^181 + x^180 + x^179 + x^176 + x^171 + x^169 + x^167 + x^158 + x^156 + x^155 + x^154 + x^153 + x^151 + x^150 + x^148 + x^145 + x^143 + x^142 + x^141 + x^138 + x^133 + x^132 + x^130 + x^127 + x^125 + x^123 + x^121 + x^118 + x^109 + x^108 + x^105 + x^104 + x^103 + x^102 + x^100 + x^97 + x^95 + x^94 + x^92 + x^90 + x^85 + x^84 + x^82 + x^80 + x^76 + x^74 + x^73 + x^71 + x^70 + x^68 + x^67 + x^66 + x^65 + x^64 + x^61 + x^59 + x^57 + x^55 + x^53 + x^52 + x^48 + x^47 + x^45 + x^41 + x^38 + x^36 + x^34 + x^31 + x^28 + x^26 + x^24 + x^22 + x^18 + x^16 + x^12 + x^10 + x^2 + 1
+
+48-55-49 313 x^928 + x^898 + x^894 + x^893 + x^870 + x^864 + x^860 + x^858 + x^838 + x^836 + x^832 + x^831 + x^830 + x^828 + x^804 + x^802 + x^796 + x^794 + x^778 + x^774 + x^773 + x^770 + x^769 + x^768 + x^741 + x^740 + x^737 + x^736 + x^734 + x^732 + x^731 + x^718 + x^712 + x^711 + x^706 + x^704 + x^702 + x^701 + x^684 + x^681 + x^679 + x^678 + x^676 + x^675 + x^672 + x^671 + x^669 + x^658 + x^656 + x^654 + x^653 + x^652 + x^651 + x^647 + x^646 + x^645 + x^621 + x^618 + x^617 + x^616 + x^615 + x^614 + x^611 + x^610 + x^609 + x^598 + x^596 + x^592 + x^590 + x^589 + x^583 + x^582 + x^580 + x^579 + x^576 + x^561 + x^559 + x^558 + x^557 + x^554 + x^548 + x^547 + x^545 + x^538 + x^534 + x^533 + x^531 + x^529 + x^527 + x^526 + x^524 + x^522 + x^521 + x^515 + x^512 + x^500 + x^497 + x^486 + x^484 + x^478 + x^474 + x^470 + x^469 + x^468 + x^467 + x^466 + x^465 + x^464 + x^462 + x^460 + x^458 + x^456 + x^454 + x^453 + x^450 + x^444 + x^438 + x^436 + x^435 + x^434 + x^433 + x^432 + x^429 + x^424 + x^422 + x^421 + x^418 + x^416 + x^413 + x^412 + x^407 + x^406 + x^405 + x^404 + x^402 + x^399 + x^398 + x^397 + x^396 + x^394 + x^392 + x^391 + x^388 + x^384 + x^380 + x^378 + x^377 + x^375 + x^373 + x^372 + x^371 + x^364 + x^359 + x^356 + x^354 + x^353 + x^352 + x^351 + x^350 + x^347 + x^343 + x^342 + x^340 + x^339 + x^334 + x^331 + x^328 + x^327 + x^325 + x^324 + x^322 + x^316 + x^312 + x^311 + x^310 + x^308 + x^306 + x^305 + x^304 + x^302 + x^301 + x^300 + x^298 + x^297 + x^295 + x^294 + x^292 + x^291 + x^290 + x^289 + x^288 + x^282 + x^280 + x^279 + x^278 + x^277 + x^276 + x^275 + x^273 + x^271 + x^268 + x^264 + x^261 + x^258 + x^256 + x^254 + x^253 + x^251 + x^249 + x^248 + x^246 + x^245 + x^244 + x^242 + x^241 + x^240 + x^239 + x^237 + x^236 + x^232 + x^231 + x^229 + x^228 + x^227 + x^226 + x^224 + x^223 + x^219 + x^216 + x^215 + x^211 + x^210 + x^207 + x^206 + x^204 + x^203 + x^199 + x^198 + x^194 + x^188 + x^187 + x^186 + x^182 + x^181 + x^180 + x^174 + x^171 + x^170 + x^168 + x^165 + x^164 + x^160 + x^152 + x^151 + x^150 + x^149 + x^148 + x^147 + x^144 + x^143 + x^142 + x^141 + x^139 + x^134 + x^129 + x^127 + x^124 + x^121 + x^119 + x^115 + x^108 + x^107 + x^105 + x^104 + x^103 + x^102 + x^95 + x^94 + x^90 + x^89 + x^88 + x^86 + x^84 + x^79 + x^77 + x^73 + x^72 + x^69 + x^66 + x^64 + x^63 + x^62 + x^60 + x^57 + x^56 + x^55 + x^54 + x^53 + x^52 + x^50 + x^49 + x^48 + x^46 + x^45 + x^41 + x^36 + x^34 + x^31 + x^29 + x^28 + x^26 + x^25 + 1
+
+40-44-57 319 x^928 + x^898 + x^870 + x^846 + x^822 + x^819 + x^808 + x^800 + x^794 + x^792 + x^786 + x^778 + x^770 + x^764 + x^759 + x^748 + x^746 + x^740 + x^732 + x^716 + x^713 + x^697 + x^696 + x^691 + x^686 + x^674 + x^664 + x^661 + x^658 + x^650 + x^648 + x^645 + x^644 + x^636 + x^629 + x^626 + x^624 + x^623 + x^621 + x^620 + x^618 + x^615 + x^594 + x^593 + x^591 + x^590 + x^586 + x^583 + x^580 + x^579 + x^577 + x^575 + x^574 + x^572 + x^571 + x^568 + x^564 + x^563 + x^561 + x^558 + x^556 + x^555 + x^553 + x^550 + x^547 + x^545 + x^544 + x^542 + x^541 + x^538 + x^536 + x^533 + x^528 + x^526 + x^525 + x^522 + x^519 + x^513 + x^512 + x^510 + x^509 + x^508 + x^507 + x^504 + x^503 + x^501 + x^495 + x^488 + x^487 + x^485 + x^484 + x^483 + x^482 + x^480 + x^479 + x^474 + x^473 + x^471 + x^470 + x^466 + x^463 + x^460 + x^458 + x^457 + x^454 + x^453 + x^450 + x^449 + x^446 + x^444 + x^443 + x^441 + x^440 + x^439 + x^436 + x^434 + x^433 + x^432 + x^431 + x^430 + x^427 + x^425 + x^424 + x^422 + x^420 + x^417 + x^416 + x^415 + x^413 + x^412 + x^411 + x^410 + x^409 + x^408 + x^407 + x^406 + x^405 + x^403 + x^398 + x^397 + x^395 + x^392 + x^390 + x^387 + x^386 + x^385 + x^383 + x^375 + x^370 + x^369 + x^368 + x^367 + x^365 + x^364 + x^363 + x^360 + x^359 + x^357 + x^356 + x^353 + x^351 + x^349 + x^342 + x^340 + x^339 + x^336 + x^335 + x^329 + x^327 + x^326 + x^324 + x^323 + x^320 + x^319 + x^318 + x^317 + x^316 + x^315 + x^312 + x^311 + x^307 + x^306 + x^302 + x^299 + x^298 + x^297 + x^295 + x^294 + x^293 + x^291 + x^290 + x^287 + x^285 + x^284 + x^283 + x^282 + x^281 + x^279 + x^277 + x^276 + x^275 + x^272 + x^271 + x^268 + x^266 + x^263 + x^262 + x^261 + x^255 + x^251 + x^249 + x^248 + x^245 + x^244 + x^243 + x^240 + x^239 + x^238 + x^236 + x^235 + x^234 + x^230 + x^229 + x^228 + x^227 + x^226 + x^224 + x^221 + x^217 + x^214 + x^212 + x^209 + x^208 + x^204 + x^202 + x^201 + x^197 + x^194 + x^193 + x^192 + x^191 + x^189 + x^188 + x^185 + x^184 + x^182 + x^181 + x^178 + x^176 + x^173 + x^172 + x^171 + x^169 + x^166 + x^163 + x^161 + x^160 + x^158 + x^156 + x^155 + x^154 + x^152 + x^151 + x^149 + x^148 + x^146 + x^145 + x^144 + x^142 + x^141 + x^134 + x^132 + x^129 + x^122 + x^121 + x^119 + x^117 + x^116 + x^113 + x^112 + x^110 + x^109 + x^103 + x^102 + x^101 + x^100 + x^97 + x^95 + x^94 + x^93 + x^92 + x^91 + x^87 + x^86 + x^85 + x^84 + x^81 + x^80 + x^75 + x^74 + x^71 + x^70 + x^68 + x^66 + x^63 + x^52 + x^51 + x^50 + x^47 + x^45 + x^44 + x^43 + x^39 + x^38 + x^34 + x^24 + x^16 + x^8 + 1
+
+21-52-12 321 x^928 + x^898 + x^870 + x^868 + x^856 + x^838 + x^828 + x^826 + x^816 + x^815 + x^808 + x^804 + x^786 + x^784 + x^778 + x^748 + x^744 + x^736 + x^734 + x^733 + x^732 + x^731 + x^726 + x^722 + x^718 + x^710 + x^706 + x^704 + x^703 + x^694 + x^692 + x^691 + x^688 + x^683 + x^680 + x^679 + x^678 + x^674 + x^672 + x^668 + x^664 + x^660 + x^658 + x^656 + x^653 + x^649 + x^648 + x^644 + x^643 + x^638 + x^632 + x^630 + x^625 + x^623 + x^619 + x^618 + x^616 + x^615 + x^613 + x^610 + x^607 + x^606 + x^602 + x^601 + x^600 + x^595 + x^593 + x^589 + x^588 + x^586 + x^585 + x^580 + x^579 + x^578 + x^571 + x^570 + x^566 + x^565 + x^564 + x^563 + x^560 + x^559 + x^555 + x^554 + x^550 + x^548 + x^547 + x^546 + x^544 + x^543 + x^541 + x^539 + x^538 + x^537 + x^535 + x^531 + x^530 + x^529 + x^522 + x^520 + x^514 + x^513 + x^509 + x^507 + x^504 + x^501 + x^500 + x^499 + x^498 + x^496 + x^495 + x^494 + x^492 + x^491 + x^490 + x^486 + x^483 + x^480 + x^479 + x^476 + x^473 + x^469 + x^466 + x^463 + x^460 + x^459 + x^457 + x^451 + x^449 + x^443 + x^441 + x^440 + x^436 + x^433 + x^432 + x^431 + x^424 + x^420 + x^418 + x^416 + x^414 + x^411 + x^410 + x^409 + x^408 + x^406 + x^404 + x^403 + x^401 + x^400 + x^398 + x^393 + x^390 + x^388 + x^385 + x^384 + x^381 + x^375 + x^372 + x^366 + x^365 + x^361 + x^358 + x^356 + x^354 + x^352 + x^347 + x^346 + x^341 + x^340 + x^338 + x^335 + x^334 + x^333 + x^331 + x^330 + x^326 + x^323 + x^320 + x^319 + x^318 + x^317 + x^315 + x^312 + x^311 + x^309 + x^305 + x^303 + x^301 + x^299 + x^297 + x^296 + x^295 + x^290 + x^289 + x^283 + x^282 + x^277 + x^276 + x^273 + x^268 + x^267 + x^266 + x^264 + x^262 + x^261 + x^260 + x^256 + x^254 + x^251 + x^250 + x^247 + x^245 + x^244 + x^243 + x^240 + x^238 + x^235 + x^234 + x^233 + x^231 + x^229 + x^225 + x^224 + x^217 + x^212 + x^211 + x^209 + x^208 + x^205 + x^204 + x^202 + x^200 + x^198 + x^196 + x^195 + x^191 + x^190 + x^188 + x^187 + x^186 + x^182 + x^181 + x^180 + x^175 + x^174 + x^172 + x^171 + x^170 + x^169 + x^166 + x^164 + x^158 + x^154 + x^153 + x^152 + x^149 + x^148 + x^147 + x^146 + x^144 + x^142 + x^141 + x^140 + x^138 + x^137 + x^136 + x^135 + x^133 + x^131 + x^127 + x^125 + x^123 + x^122 + x^121 + x^120 + x^119 + x^115 + x^113 + x^112 + x^111 + x^106 + x^102 + x^101 + x^100 + x^98 + x^96 + x^95 + x^93 + x^92 + x^91 + x^90 + x^89 + x^88 + x^87 + x^81 + x^79 + x^76 + x^72 + x^71 + x^70 + x^64 + x^60 + x^57 + x^56 + x^55 + x^54 + x^50 + x^49 + x^48 + x^47 + x^43 + x^39 + x^38 + x^30 + x^29 + x^26 + x^25 + x^14 + x^10 + 1
+
+25-44-4 331 x^928 + x^898 + x^870 + x^866 + x^862 + x^859 + x^836 + x^834 + x^829 + x^822 + x^808 + x^806 + x^804 + x^800 + x^799 + x^798 + x^797 + x^795 + x^778 + x^776 + x^770 + x^769 + x^767 + x^762 + x^753 + x^739 + x^735 + x^734 + x^731 + x^721 + x^713 + x^709 + x^708 + x^706 + x^705 + x^703 + x^701 + x^698 + x^688 + x^679 + x^677 + x^676 + x^674 + x^670 + x^669 + x^667 + x^666 + x^661 + x^659 + x^658 + x^649 + x^647 + x^645 + x^643 + x^642 + x^638 + x^635 + x^633 + x^629 + x^626 + x^624 + x^620 + x^618 + x^617 + x^614 + x^611 + x^605 + x^603 + x^598 + x^596 + x^594 + x^593 + x^590 + x^587 + x^581 + x^580 + x^579 + x^571 + x^570 + x^568 + x^566 + x^562 + x^558 + x^557 + x^551 + x^550 + x^548 + x^547 + x^546 + x^544 + x^542 + x^541 + x^539 + x^536 + x^533 + x^532 + x^530 + x^527 + x^526 + x^525 + x^524 + x^523 + x^521 + x^520 + x^519 + x^518 + x^517 + x^515 + x^514 + x^513 + x^507 + x^506 + x^504 + x^501 + x^497 + x^493 + x^492 + x^490 + x^488 + x^483 + x^482 + x^481 + x^478 + x^477 + x^476 + x^475 + x^473 + x^467 + x^466 + x^465 + x^463 + x^462 + x^461 + x^460 + x^456 + x^454 + x^453 + x^452 + x^450 + x^448 + x^446 + x^444 + x^443 + x^442 + x^433 + x^426 + x^423 + x^422 + x^420 + x^419 + x^416 + x^413 + x^412 + x^411 + x^410 + x^406 + x^404 + x^403 + x^402 + x^401 + x^400 + x^398 + x^396 + x^395 + x^392 + x^391 + x^389 + x^387 + x^386 + x^385 + x^380 + x^372 + x^366 + x^365 + x^363 + x^362 + x^359 + x^357 + x^354 + x^353 + x^352 + x^348 + x^346 + x^345 + x^342 + x^340 + x^338 + x^335 + x^334 + x^333 + x^332 + x^325 + x^324 + x^323 + x^320 + x^319 + x^318 + x^314 + x^312 + x^309 + x^308 + x^303 + x^302 + x^301 + x^299 + x^295 + x^294 + x^292 + x^291 + x^290 + x^289 + x^286 + x^284 + x^279 + x^278 + x^274 + x^273 + x^268 + x^266 + x^265 + x^263 + x^261 + x^257 + x^256 + x^255 + x^254 + x^253 + x^248 + x^245 + x^244 + x^241 + x^240 + x^237 + x^235 + x^234 + x^233 + x^232 + x^230 + x^229 + x^228 + x^226 + x^223 + x^222 + x^220 + x^218 + x^214 + x^212 + x^211 + x^209 + x^207 + x^206 + x^203 + x^202 + x^201 + x^200 + x^199 + x^198 + x^196 + x^191 + x^189 + x^184 + x^178 + x^177 + x^176 + x^175 + x^172 + x^171 + x^169 + x^166 + x^165 + x^162 + x^161 + x^159 + x^157 + x^156 + x^155 + x^150 + x^149 + x^148 + x^147 + x^143 + x^142 + x^141 + x^140 + x^137 + x^136 + x^135 + x^133 + x^131 + x^130 + x^129 + x^127 + x^126 + x^125 + x^124 + x^123 + x^120 + x^113 + x^112 + x^108 + x^105 + x^104 + x^98 + x^96 + x^92 + x^90 + x^82 + x^81 + x^80 + x^78 + x^76 + x^74 + x^73 + x^68 + x^66 + x^64 + x^62 + x^60 + x^58 + x^54 + x^42 + x^38 + x^36 + x^32 + x^26 + x^18 + x^16 + x^10 + x^8 + x^2 + 1
+
+16-8-37 341 x^928 + x^898 + x^870 + x^842 + x^814 + x^808 + x^788 + x^786 + x^784 + x^778 + x^777 + x^775 + x^773 + x^762 + x^756 + x^751 + x^745 + x^736 + x^723 + x^721 + x^717 + x^715 + x^713 + x^710 + x^700 + x^697 + x^693 + x^691 + x^688 + x^685 + x^678 + x^674 + x^673 + x^672 + x^667 + x^654 + x^650 + x^647 + x^645 + x^644 + x^643 + x^642 + x^641 + x^635 + x^634 + x^632 + x^631 + x^630 + x^622 + x^621 + x^618 + x^615 + x^614 + x^612 + x^608 + x^605 + x^604 + x^603 + x^601 + x^596 + x^595 + x^592 + x^590 + x^589 + x^588 + x^587 + x^586 + x^585 + x^584 + x^583 + x^581 + x^579 + x^578 + x^576 + x^575 + x^573 + x^571 + x^569 + x^565 + x^564 + x^561 + x^560 + x^557 + x^556 + x^555 + x^554 + x^552 + x^544 + x^543 + x^540 + x^535 + x^533 + x^530 + x^528 + x^527 + x^524 + x^522 + x^520 + x^519 + x^517 + x^516 + x^515 + x^514 + x^513 + x^510 + x^509 + x^508 + x^507 + x^503 + x^502 + x^500 + x^497 + x^496 + x^493 + x^491 + x^490 + x^486 + x^480 + x^477 + x^476 + x^475 + x^474 + x^473 + x^472 + x^471 + x^467 + x^464 + x^463 + x^461 + x^458 + x^457 + x^453 + x^446 + x^444 + x^441 + x^439 + x^436 + x^434 + x^433 + x^432 + x^430 + x^427 + x^426 + x^422 + x^418 + x^417 + x^416 + x^414 + x^412 + x^411 + x^410 + x^405 + x^404 + x^401 + x^399 + x^396 + x^395 + x^392 + x^391 + x^390 + x^388 + x^387 + x^386 + x^385 + x^384 + x^383 + x^378 + x^377 + x^375 + x^373 + x^372 + x^371 + x^366 + x^364 + x^358 + x^357 + x^356 + x^352 + x^348 + x^345 + x^342 + x^340 + x^339 + x^338 + x^336 + x^335 + x^331 + x^330 + x^329 + x^327 + x^324 + x^323 + x^322 + x^321 + x^320 + x^317 + x^316 + x^315 + x^314 + x^310 + x^307 + x^306 + x^303 + x^297 + x^295 + x^294 + x^293 + x^292 + x^291 + x^290 + x^288 + x^286 + x^284 + x^283 + x^282 + x^278 + x^276 + x^275 + x^274 + x^269 + x^268 + x^267 + x^263 + x^262 + x^261 + x^260 + x^259 + x^258 + x^256 + x^251 + x^249 + x^248 + x^247 + x^245 + x^242 + x^241 + x^239 + x^238 + x^236 + x^235 + x^234 + x^233 + x^232 + x^230 + x^229 + x^225 + x^224 + x^223 + x^222 + x^220 + x^218 + x^216 + x^214 + x^213 + x^212 + x^211 + x^209 + x^206 + x^201 + x^200 + x^199 + x^197 + x^196 + x^194 + x^193 + x^190 + x^186 + x^184 + x^181 + x^180 + x^179 + x^178 + x^177 + x^176 + x^175 + x^169 + x^168 + x^166 + x^164 + x^160 + x^158 + x^157 + x^156 + x^153 + x^152 + x^147 + x^146 + x^136 + x^134 + x^132 + x^129 + x^126 + x^125 + x^124 + x^123 + x^122 + x^120 + x^119 + x^116 + x^114 + x^112 + x^108 + x^107 + x^106 + x^104 + x^102 + x^98 + x^95 + x^91 + x^85 + x^84 + x^82 + x^81 + x^80 + x^79 + x^77 + x^76 + x^73 + x^72 + x^70 + x^62 + x^60 + x^59 + x^58 + x^56 + x^55 + x^53 + x^49 + x^44 + x^42 + x^41 + x^39 + x^38 + x^35 + x^34 + x^32 + x^28 + x^26 + x^24 + x^22 + 1
+
+17-12-36 341 x^928 + x^898 + x^870 + x^868 + x^860 + x^848 + x^838 + x^830 + x^829 + x^806 + x^798 + x^797 + x^789 + x^788 + x^772 + x^770 + x^767 + x^762 + x^761 + x^756 + x^751 + x^749 + x^748 + x^746 + x^742 + x^740 + x^738 + x^730 + x^726 + x^721 + x^718 + x^716 + x^714 + x^710 + x^709 + x^707 + x^706 + x^705 + x^696 + x^688 + x^684 + x^681 + x^677 + x^676 + x^674 + x^669 + x^666 + x^664 + x^659 + x^658 + x^657 + x^656 + x^655 + x^654 + x^651 + x^650 + x^647 + x^645 + x^639 + x^637 + x^636 + x^634 + x^631 + x^627 + x^625 + x^624 + x^622 + x^621 + x^619 + x^615 + x^614 + x^608 + x^605 + x^601 + x^599 + x^597 + x^596 + x^594 + x^593 + x^592 + x^591 + x^588 + x^587 + x^586 + x^585 + x^583 + x^582 + x^580 + x^578 + x^577 + x^576 + x^575 + x^574 + x^573 + x^572 + x^569 + x^568 + x^567 + x^565 + x^564 + x^561 + x^560 + x^559 + x^558 + x^557 + x^554 + x^553 + x^546 + x^545 + x^543 + x^542 + x^541 + x^539 + x^538 + x^536 + x^533 + x^530 + x^529 + x^527 + x^526 + x^525 + x^521 + x^520 + x^513 + x^509 + x^508 + x^504 + x^501 + x^500 + x^499 + x^498 + x^497 + x^496 + x^494 + x^488 + x^486 + x^484 + x^480 + x^479 + x^478 + x^475 + x^471 + x^469 + x^466 + x^463 + x^458 + x^456 + x^455 + x^452 + x^450 + x^449 + x^445 + x^437 + x^436 + x^433 + x^432 + x^430 + x^428 + x^426 + x^425 + x^423 + x^417 + x^416 + x^415 + x^413 + x^401 + x^400 + x^395 + x^394 + x^392 + x^391 + x^387 + x^386 + x^385 + x^384 + x^383 + x^379 + x^375 + x^373 + x^372 + x^371 + x^368 + x^366 + x^361 + x^360 + x^359 + x^358 + x^357 + x^354 + x^352 + x^350 + x^349 + x^348 + x^347 + x^345 + x^344 + x^343 + x^340 + x^334 + x^332 + x^330 + x^328 + x^327 + x^325 + x^323 + x^321 + x^318 + x^317 + x^316 + x^315 + x^314 + x^311 + x^310 + x^309 + x^308 + x^307 + x^306 + x^304 + x^299 + x^298 + x^296 + x^295 + x^294 + x^292 + x^291 + x^288 + x^287 + x^286 + x^282 + x^281 + x^280 + x^272 + x^271 + x^268 + x^267 + x^262 + x^256 + x^253 + x^252 + x^250 + x^244 + x^242 + x^241 + x^240 + x^238 + x^234 + x^233 + x^232 + x^231 + x^229 + x^228 + x^227 + x^226 + x^223 + x^222 + x^221 + x^220 + x^219 + x^218 + x^217 + x^215 + x^214 + x^212 + x^211 + x^208 + x^206 + x^203 + x^201 + x^199 + x^198 + x^195 + x^194 + x^193 + x^192 + x^189 + x^185 + x^182 + x^180 + x^179 + x^177 + x^176 + x^173 + x^172 + x^170 + x^164 + x^163 + x^160 + x^157 + x^155 + x^153 + x^152 + x^150 + x^147 + x^146 + x^139 + x^137 + x^132 + x^131 + x^127 + x^126 + x^125 + x^123 + x^122 + x^120 + x^115 + x^113 + x^111 + x^109 + x^107 + x^106 + x^104 + x^103 + x^101 + x^100 + x^99 + x^98 + x^97 + x^94 + x^88 + x^85 + x^78 + x^77 + x^76 + x^71 + x^68 + x^66 + x^64 + x^51 + x^47 + x^45 + x^39 + x^38 + x^36 + x^31 + x^30 + x^26 + x^24 + x^22 + x^20 + x^15 + 1
+
+2-30-27 343 x^928 + x^898 + x^881 + x^870 + x^868 + x^866 + x^851 + x^838 + x^819 + x^817 + x^808 + x^806 + x^804 + x^778 + x^776 + x^774 + x^772 + x^761 + x^759 + x^755 + x^753 + x^748 + x^742 + x^740 + x^731 + x^725 + x^718 + x^716 + x^712 + x^710 + x^706 + x^695 + x^693 + x^691 + x^689 + x^688 + x^682 + x^678 + x^674 + x^665 + x^663 + x^661 + x^658 + x^657 + x^652 + x^646 + x^644 + x^642 + x^641 + x^637 + x^629 + x^628 + x^627 + x^626 + x^625 + x^618 + x^616 + x^614 + x^612 + x^611 + x^610 + x^607 + x^601 + x^598 + x^597 + x^593 + x^592 + x^588 + x^580 + x^579 + x^577 + x^575 + x^573 + x^571 + x^569 + x^568 + x^566 + x^564 + x^563 + x^562 + x^561 + x^560 + x^554 + x^552 + x^550 + x^548 + x^543 + x^539 + x^538 + x^536 + x^534 + x^531 + x^530 + x^529 + x^528 + x^524 + x^521 + x^519 + x^517 + x^515 + x^513 + x^509 + x^508 + x^507 + x^505 + x^502 + x^500 + x^499 + x^498 + x^497 + x^492 + x^491 + x^487 + x^486 + x^484 + x^479 + x^478 + x^477 + x^476 + x^473 + x^470 + x^468 + x^466 + x^465 + x^464 + x^458 + x^454 + x^450 + x^448 + x^447 + x^446 + x^445 + x^439 + x^436 + x^435 + x^434 + x^433 + x^432 + x^428 + x^426 + x^422 + x^420 + x^416 + x^415 + x^413 + x^411 + x^409 + x^407 + x^406 + x^404 + x^399 + x^396 + x^392 + x^389 + x^384 + x^383 + x^382 + x^381 + x^379 + x^377 + x^376 + x^374 + x^372 + x^370 + x^367 + x^359 + x^355 + x^352 + x^347 + x^346 + x^344 + x^342 + x^341 + x^339 + x^338 + x^336 + x^335 + x^334 + x^328 + x^327 + x^324 + x^320 + x^318 + x^316 + x^314 + x^313 + x^309 + x^308 + x^304 + x^303 + x^300 + x^298 + x^297 + x^296 + x^294 + x^292 + x^287 + x^286 + x^284 + x^283 + x^282 + x^278 + x^276 + x^272 + x^269 + x^266 + x^265 + x^264 + x^261 + x^256 + x^252 + x^250 + x^248 + x^247 + x^245 + x^244 + x^243 + x^242 + x^239 + x^238 + x^237 + x^236 + x^235 + x^234 + x^233 + x^231 + x^229 + x^226 + x^225 + x^224 + x^220 + x^219 + x^216 + x^213 + x^212 + x^210 + x^205 + x^204 + x^203 + x^201 + x^199 + x^197 + x^194 + x^192 + x^191 + x^190 + x^189 + x^188 + x^186 + x^184 + x^183 + x^182 + x^178 + x^177 + x^176 + x^175 + x^173 + x^171 + x^170 + x^169 + x^166 + x^165 + x^163 + x^162 + x^161 + x^157 + x^156 + x^155 + x^153 + x^152 + x^150 + x^148 + x^147 + x^146 + x^144 + x^143 + x^141 + x^139 + x^137 + x^134 + x^133 + x^129 + x^128 + x^127 + x^126 + x^125 + x^123 + x^119 + x^117 + x^116 + x^115 + x^114 + x^113 + x^111 + x^110 + x^102 + x^101 + x^100 + x^99 + x^97 + x^96 + x^94 + x^93 + x^89 + x^88 + x^84 + x^83 + x^81 + x^78 + x^74 + x^72 + x^71 + x^69 + x^67 + x^62 + x^61 + x^59 + x^57 + x^56 + x^55 + x^53 + x^51 + x^47 + x^44 + x^42 + x^39 + x^37 + x^36 + x^35 + x^33 + x^30 + x^26 + x^24 + x^23 + x^21 + x^19 + x^18 + x^17 + x^16 + x^10 + x^8 + x^2 + 1
+
+26-55-39 345 x^928 + x^904 + x^898 + x^870 + x^861 + x^856 + x^851 + x^850 + x^831 + x^821 + x^820 + x^818 + x^813 + x^802 + x^796 + x^794 + x^790 + x^788 + x^783 + x^778 + x^775 + x^772 + x^770 + x^765 + x^760 + x^754 + x^746 + x^745 + x^741 + x^740 + x^736 + x^735 + x^734 + x^732 + x^731 + x^729 + x^727 + x^723 + x^722 + x^721 + x^716 + x^715 + x^710 + x^708 + x^705 + x^701 + x^699 + x^686 + x^685 + x^684 + x^681 + x^679 + x^678 + x^676 + x^674 + x^673 + x^672 + x^668 + x^664 + x^663 + x^661 + x^660 + x^658 + x^656 + x^650 + x^648 + x^645 + x^643 + x^641 + x^638 + x^637 + x^636 + x^632 + x^622 + x^621 + x^618 + x^616 + x^615 + x^614 + x^612 + x^608 + x^603 + x^600 + x^598 + x^595 + x^594 + x^593 + x^591 + x^590 + x^589 + x^588 + x^585 + x^582 + x^581 + x^576 + x^574 + x^571 + x^569 + x^568 + x^564 + x^562 + x^557 + x^556 + x^553 + x^550 + x^549 + x^548 + x^547 + x^546 + x^545 + x^544 + x^543 + x^538 + x^535 + x^530 + x^525 + x^520 + x^517 + x^515 + x^514 + x^510 + x^509 + x^506 + x^501 + x^497 + x^495 + x^489 + x^486 + x^484 + x^483 + x^482 + x^480 + x^479 + x^476 + x^474 + x^473 + x^471 + x^470 + x^469 + x^467 + x^464 + x^462 + x^460 + x^453 + x^452 + x^450 + x^449 + x^448 + x^446 + x^444 + x^443 + x^441 + x^436 + x^435 + x^434 + x^433 + x^432 + x^429 + x^428 + x^427 + x^424 + x^419 + x^418 + x^417 + x^416 + x^413 + x^412 + x^410 + x^407 + x^404 + x^403 + x^401 + x^399 + x^398 + x^397 + x^396 + x^394 + x^393 + x^392 + x^390 + x^386 + x^385 + x^383 + x^379 + x^378 + x^377 + x^376 + x^375 + x^373 + x^372 + x^366 + x^362 + x^359 + x^357 + x^355 + x^354 + x^351 + x^350 + x^347 + x^346 + x^344 + x^340 + x^339 + x^338 + x^337 + x^333 + x^332 + x^331 + x^330 + x^328 + x^326 + x^325 + x^323 + x^319 + x^315 + x^314 + x^313 + x^311 + x^310 + x^308 + x^307 + x^306 + x^304 + x^301 + x^300 + x^298 + x^296 + x^294 + x^293 + x^292 + x^291 + x^290 + x^286 + x^283 + x^280 + x^277 + x^274 + x^272 + x^265 + x^264 + x^263 + x^261 + x^260 + x^258 + x^255 + x^252 + x^251 + x^250 + x^249 + x^247 + x^245 + x^244 + x^241 + x^238 + x^235 + x^234 + x^230 + x^228 + x^225 + x^221 + x^218 + x^212 + x^211 + x^210 + x^208 + x^206 + x^204 + x^203 + x^202 + x^199 + x^198 + x^196 + x^195 + x^193 + x^188 + x^186 + x^184 + x^183 + x^177 + x^176 + x^167 + x^166 + x^165 + x^163 + x^161 + x^160 + x^158 + x^156 + x^153 + x^151 + x^146 + x^144 + x^141 + x^140 + x^134 + x^133 + x^129 + x^128 + x^127 + x^124 + x^123 + x^121 + x^119 + x^117 + x^115 + x^114 + x^113 + x^112 + x^111 + x^110 + x^109 + x^105 + x^104 + x^103 + x^100 + x^99 + x^93 + x^92 + x^91 + x^86 + x^84 + x^81 + x^80 + x^77 + x^75 + x^72 + x^71 + x^70 + x^68 + x^66 + x^65 + x^64 + x^61 + x^54 + x^53 + x^47 + x^45 + x^36 + x^35 + x^34 + x^32 + x^29 + x^18 + x^7 + 1
+
+40-36-23 345 x^928 + x^898 + x^886 + x^870 + x^862 + x^844 + x^838 + x^826 + x^820 + x^816 + x^814 + x^808 + x^797 + x^792 + x^791 + x^790 + x^784 + x^778 + x^777 + x^772 + x^768 + x^767 + x^766 + x^762 + x^761 + x^756 + x^753 + x^749 + x^747 + x^744 + x^742 + x^738 + x^735 + x^734 + x^726 + x^724 + x^723 + x^720 + x^712 + x^702 + x^701 + x^695 + x^689 + x^684 + x^683 + x^681 + x^677 + x^675 + x^670 + x^669 + x^664 + x^663 + x^660 + x^659 + x^658 + x^653 + x^648 + x^646 + x^644 + x^634 + x^629 + x^628 + x^627 + x^626 + x^625 + x^622 + x^621 + x^617 + x^616 + x^611 + x^609 + x^605 + x^604 + x^603 + x^602 + x^597 + x^595 + x^594 + x^593 + x^592 + x^586 + x^585 + x^584 + x^578 + x^575 + x^574 + x^573 + x^572 + x^571 + x^569 + x^565 + x^563 + x^561 + x^559 + x^557 + x^555 + x^554 + x^547 + x^545 + x^543 + x^539 + x^537 + x^536 + x^535 + x^534 + x^529 + x^528 + x^527 + x^525 + x^524 + x^521 + x^520 + x^519 + x^518 + x^517 + x^515 + x^514 + x^512 + x^510 + x^509 + x^508 + x^507 + x^506 + x^504 + x^503 + x^501 + x^499 + x^496 + x^494 + x^490 + x^489 + x^487 + x^485 + x^484 + x^482 + x^481 + x^480 + x^479 + x^478 + x^477 + x^475 + x^473 + x^470 + x^466 + x^465 + x^464 + x^463 + x^460 + x^457 + x^456 + x^454 + x^453 + x^452 + x^447 + x^446 + x^442 + x^441 + x^439 + x^436 + x^434 + x^432 + x^430 + x^428 + x^427 + x^423 + x^421 + x^416 + x^415 + x^414 + x^413 + x^411 + x^408 + x^405 + x^402 + x^401 + x^399 + x^396 + x^394 + x^393 + x^391 + x^390 + x^389 + x^388 + x^387 + x^386 + x^384 + x^381 + x^379 + x^378 + x^377 + x^375 + x^374 + x^372 + x^369 + x^367 + x^366 + x^364 + x^363 + x^361 + x^360 + x^359 + x^358 + x^356 + x^355 + x^354 + x^353 + x^352 + x^349 + x^345 + x^343 + x^342 + x^338 + x^336 + x^335 + x^334 + x^333 + x^331 + x^329 + x^328 + x^327 + x^320 + x^318 + x^317 + x^316 + x^312 + x^307 + x^304 + x^303 + x^300 + x^298 + x^296 + x^295 + x^294 + x^293 + x^291 + x^290 + x^288 + x^287 + x^286 + x^285 + x^284 + x^276 + x^275 + x^274 + x^273 + x^272 + x^271 + x^269 + x^268 + x^266 + x^263 + x^262 + x^259 + x^257 + x^255 + x^254 + x^251 + x^248 + x^246 + x^244 + x^240 + x^238 + x^237 + x^235 + x^234 + x^232 + x^228 + x^226 + x^225 + x^224 + x^222 + x^220 + x^217 + x^213 + x^212 + x^211 + x^209 + x^208 + x^207 + x^202 + x^200 + x^199 + x^198 + x^192 + x^190 + x^187 + x^183 + x^182 + x^180 + x^175 + x^172 + x^171 + x^170 + x^169 + x^167 + x^166 + x^165 + x^164 + x^163 + x^160 + x^157 + x^154 + x^153 + x^152 + x^150 + x^149 + x^148 + x^147 + x^146 + x^144 + x^142 + x^140 + x^137 + x^136 + x^134 + x^132 + x^129 + x^128 + x^124 + x^119 + x^117 + x^116 + x^115 + x^110 + x^108 + x^106 + x^103 + x^98 + x^88 + x^82 + x^76 + x^75 + x^74 + x^69 + x^68 + x^64 + x^62 + x^51 + x^47 + x^46 + x^39 + x^32 + x^23 + x^22 + 1
+
+24-36-37 347 x^928 + x^898 + x^878 + x^870 + x^854 + x^830 + x^824 + x^821 + x^818 + x^816 + x^813 + x^808 + x^806 + x^800 + x^792 + x^791 + x^789 + x^783 + x^780 + x^778 + x^776 + x^768 + x^765 + x^759 + x^753 + x^750 + x^747 + x^745 + x^744 + x^741 + x^740 + x^736 + x^734 + x^729 + x^721 + x^717 + x^715 + x^714 + x^708 + x^705 + x^704 + x^702 + x^699 + x^694 + x^690 + x^688 + x^684 + x^682 + x^681 + x^680 + x^679 + x^670 + x^669 + x^666 + x^664 + x^663 + x^660 + x^652 + x^649 + x^646 + x^645 + x^643 + x^639 + x^636 + x^634 + x^633 + x^631 + x^629 + x^627 + x^622 + x^621 + x^620 + x^614 + x^613 + x^612 + x^611 + x^610 + x^609 + x^605 + x^603 + x^601 + x^600 + x^599 + x^596 + x^593 + x^590 + x^587 + x^586 + x^585 + x^584 + x^583 + x^582 + x^581 + x^579 + x^577 + x^573 + x^565 + x^564 + x^563 + x^562 + x^561 + x^560 + x^559 + x^558 + x^557 + x^554 + x^550 + x^549 + x^546 + x^545 + x^544 + x^543 + x^542 + x^539 + x^538 + x^536 + x^535 + x^534 + x^529 + x^527 + x^525 + x^524 + x^523 + x^521 + x^519 + x^518 + x^516 + x^515 + x^512 + x^510 + x^505 + x^504 + x^503 + x^501 + x^500 + x^499 + x^493 + x^492 + x^488 + x^484 + x^483 + x^479 + x^474 + x^472 + x^470 + x^465 + x^464 + x^459 + x^457 + x^455 + x^454 + x^450 + x^440 + x^435 + x^434 + x^433 + x^427 + x^426 + x^424 + x^421 + x^420 + x^419 + x^414 + x^413 + x^412 + x^409 + x^407 + x^406 + x^405 + x^403 + x^401 + x^400 + x^399 + x^398 + x^395 + x^393 + x^391 + x^390 + x^388 + x^383 + x^381 + x^376 + x^374 + x^370 + x^367 + x^365 + x^363 + x^359 + x^356 + x^355 + x^353 + x^352 + x^348 + x^347 + x^346 + x^345 + x^344 + x^343 + x^342 + x^339 + x^336 + x^330 + x^327 + x^326 + x^325 + x^324 + x^323 + x^321 + x^318 + x^316 + x^315 + x^314 + x^312 + x^311 + x^310 + x^302 + x^300 + x^298 + x^295 + x^294 + x^292 + x^284 + x^282 + x^279 + x^278 + x^277 + x^276 + x^271 + x^268 + x^267 + x^266 + x^265 + x^264 + x^262 + x^260 + x^256 + x^253 + x^250 + x^249 + x^248 + x^244 + x^243 + x^242 + x^239 + x^236 + x^235 + x^232 + x^231 + x^229 + x^227 + x^226 + x^222 + x^220 + x^219 + x^216 + x^215 + x^214 + x^213 + x^211 + x^209 + x^208 + x^204 + x^202 + x^200 + x^199 + x^198 + x^196 + x^193 + x^191 + x^190 + x^189 + x^188 + x^187 + x^186 + x^179 + x^178 + x^177 + x^176 + x^175 + x^174 + x^170 + x^169 + x^167 + x^163 + x^162 + x^161 + x^160 + x^153 + x^150 + x^149 + x^147 + x^145 + x^140 + x^138 + x^136 + x^135 + x^133 + x^131 + x^130 + x^128 + x^127 + x^119 + x^117 + x^113 + x^111 + x^110 + x^108 + x^104 + x^103 + x^102 + x^101 + x^100 + x^99 + x^97 + x^96 + x^93 + x^88 + x^87 + x^86 + x^85 + x^84 + x^83 + x^82 + x^80 + x^78 + x^77 + x^72 + x^71 + x^70 + x^67 + x^66 + x^64 + x^63 + x^62 + x^61 + x^59 + x^58 + x^56 + x^54 + x^53 + x^50 + x^47 + x^46 + x^38 + x^24 + x^16 + 1
+
+26-38-9 353 x^928 + x^898 + x^875 + x^870 + x^848 + x^822 + x^808 + x^806 + x^800 + x^798 + x^797 + x^795 + x^792 + x^788 + x^778 + x^776 + x^769 + x^767 + x^765 + x^762 + x^758 + x^756 + x^747 + x^741 + x^732 + x^730 + x^728 + x^727 + x^723 + x^719 + x^714 + x^712 + x^711 + x^709 + x^706 + x^703 + x^700 + x^697 + x^695 + x^689 + x^688 + x^686 + x^684 + x^682 + x^680 + x^678 + x^676 + x^674 + x^673 + x^667 + x^666 + x^664 + x^661 + x^658 + x^653 + x^652 + x^649 + x^648 + x^646 + x^645 + x^635 + x^634 + x^633 + x^629 + x^626 + x^625 + x^620 + x^616 + x^614 + x^612 + x^611 + x^610 + x^607 + x^606 + x^604 + x^603 + x^599 + x^595 + x^594 + x^591 + x^583 + x^580 + x^578 + x^577 + x^572 + x^569 + x^568 + x^567 + x^566 + x^564 + x^559 + x^558 + x^555 + x^554 + x^550 + x^549 + x^548 + x^543 + x^542 + x^541 + x^539 + x^533 + x^532 + x^531 + x^528 + x^527 + x^526 + x^523 + x^519 + x^518 + x^515 + x^514 + x^513 + x^512 + x^511 + x^510 + x^509 + x^508 + x^507 + x^506 + x^502 + x^497 + x^495 + x^491 + x^488 + x^487 + x^485 + x^483 + x^482 + x^481 + x^478 + x^477 + x^476 + x^475 + x^473 + x^469 + x^466 + x^463 + x^462 + x^460 + x^458 + x^457 + x^456 + x^451 + x^447 + x^444 + x^443 + x^442 + x^440 + x^438 + x^437 + x^435 + x^433 + x^432 + x^430 + x^427 + x^424 + x^418 + x^417 + x^416 + x^414 + x^410 + x^409 + x^403 + x^401 + x^398 + x^397 + x^396 + x^394 + x^393 + x^391 + x^389 + x^388 + x^386 + x^385 + x^384 + x^382 + x^381 + x^380 + x^375 + x^374 + x^368 + x^364 + x^363 + x^359 + x^358 + x^357 + x^356 + x^354 + x^350 + x^349 + x^347 + x^346 + x^339 + x^337 + x^336 + x^334 + x^332 + x^330 + x^329 + x^328 + x^327 + x^326 + x^325 + x^324 + x^320 + x^318 + x^317 + x^315 + x^313 + x^309 + x^307 + x^305 + x^304 + x^303 + x^300 + x^299 + x^295 + x^293 + x^290 + x^288 + x^286 + x^285 + x^281 + x^279 + x^277 + x^274 + x^273 + x^269 + x^267 + x^266 + x^265 + x^263 + x^262 + x^257 + x^256 + x^255 + x^251 + x^249 + x^247 + x^246 + x^243 + x^241 + x^237 + x^236 + x^233 + x^230 + x^228 + x^227 + x^226 + x^225 + x^224 + x^223 + x^222 + x^221 + x^220 + x^212 + x^211 + x^210 + x^209 + x^208 + x^205 + x^204 + x^202 + x^201 + x^200 + x^197 + x^192 + x^191 + x^186 + x^184 + x^183 + x^181 + x^177 + x^175 + x^174 + x^170 + x^167 + x^165 + x^164 + x^162 + x^156 + x^154 + x^149 + x^148 + x^147 + x^144 + x^143 + x^142 + x^141 + x^139 + x^138 + x^137 + x^133 + x^131 + x^130 + x^128 + x^127 + x^126 + x^120 + x^116 + x^115 + x^113 + x^111 + x^108 + x^107 + x^105 + x^101 + x^99 + x^97 + x^95 + x^91 + x^90 + x^88 + x^87 + x^85 + x^81 + x^79 + x^78 + x^75 + x^72 + x^71 + x^70 + x^69 + x^66 + x^65 + x^63 + x^59 + x^57 + x^55 + x^54 + x^52 + x^51 + x^50 + x^49 + x^46 + x^44 + x^43 + x^39 + x^36 + x^34 + x^33 + x^32 + x^31 + x^30 + x^26 + x^24 + x^22 + x^16 + x^14 + x^8 + x^6 + 1
+
+31-6-30 353 x^928 + x^898 + x^870 + x^834 + x^814 + x^796 + x^790 + x^788 + x^784 + x^774 + x^772 + x^770 + x^762 + x^760 + x^756 + x^748 + x^740 + x^734 + x^732 + x^728 + x^727 + x^726 + x^718 + x^716 + x^712 + x^707 + x^701 + x^700 + x^697 + x^692 + x^688 + x^686 + x^684 + x^682 + x^681 + x^678 + x^677 + x^676 + x^674 + x^671 + x^670 + x^668 + x^665 + x^660 + x^658 + x^655 + x^652 + x^651 + x^650 + x^649 + x^646 + x^642 + x^641 + x^639 + x^638 + x^637 + x^636 + x^635 + x^630 + x^625 + x^624 + x^623 + x^622 + x^621 + x^620 + x^619 + x^617 + x^612 + x^611 + x^610 + x^609 + x^608 + x^607 + x^606 + x^604 + x^601 + x^598 + x^597 + x^593 + x^592 + x^591 + x^590 + x^582 + x^579 + x^578 + x^572 + x^570 + x^567 + x^566 + x^565 + x^563 + x^561 + x^559 + x^558 + x^555 + x^554 + x^544 + x^543 + x^542 + x^540 + x^539 + x^536 + x^533 + x^529 + x^528 + x^525 + x^522 + x^517 + x^516 + x^512 + x^511 + x^510 + x^507 + x^505 + x^502 + x^501 + x^499 + x^494 + x^492 + x^491 + x^489 + x^488 + x^487 + x^486 + x^484 + x^483 + x^481 + x^480 + x^476 + x^473 + x^469 + x^468 + x^466 + x^465 + x^464 + x^457 + x^451 + x^449 + x^448 + x^445 + x^444 + x^441 + x^440 + x^438 + x^436 + x^435 + x^433 + x^432 + x^431 + x^428 + x^425 + x^422 + x^420 + x^418 + x^414 + x^413 + x^412 + x^411 + x^410 + x^406 + x^405 + x^402 + x^401 + x^400 + x^394 + x^390 + x^389 + x^387 + x^384 + x^382 + x^380 + x^376 + x^375 + x^372 + x^371 + x^370 + x^369 + x^366 + x^363 + x^362 + x^361 + x^359 + x^357 + x^356 + x^354 + x^352 + x^351 + x^349 + x^347 + x^346 + x^344 + x^343 + x^342 + x^341 + x^340 + x^338 + x^337 + x^336 + x^334 + x^333 + x^331 + x^328 + x^323 + x^322 + x^318 + x^315 + x^313 + x^312 + x^310 + x^309 + x^308 + x^307 + x^301 + x^300 + x^298 + x^296 + x^295 + x^294 + x^292 + x^287 + x^285 + x^284 + x^283 + x^282 + x^279 + x^276 + x^275 + x^272 + x^270 + x^269 + x^268 + x^267 + x^263 + x^261 + x^259 + x^258 + x^256 + x^250 + x^248 + x^247 + x^246 + x^245 + x^244 + x^240 + x^239 + x^238 + x^237 + x^236 + x^235 + x^232 + x^231 + x^230 + x^229 + x^226 + x^223 + x^220 + x^217 + x^216 + x^213 + x^212 + x^210 + x^208 + x^205 + x^204 + x^201 + x^195 + x^194 + x^192 + x^191 + x^189 + x^188 + x^182 + x^181 + x^177 + x^175 + x^174 + x^173 + x^171 + x^169 + x^167 + x^162 + x^161 + x^159 + x^158 + x^154 + x^153 + x^148 + x^146 + x^145 + x^144 + x^143 + x^142 + x^141 + x^140 + x^139 + x^138 + x^135 + x^134 + x^133 + x^122 + x^119 + x^118 + x^115 + x^111 + x^110 + x^108 + x^107 + x^106 + x^105 + x^101 + x^99 + x^97 + x^96 + x^95 + x^94 + x^93 + x^92 + x^90 + x^87 + x^84 + x^83 + x^80 + x^74 + x^73 + x^71 + x^70 + x^67 + x^66 + x^63 + x^55 + x^53 + x^52 + x^51 + x^49 + x^45 + x^44 + x^43 + x^40 + x^35 + x^34 + x^33 + x^32 + x^27 + x^25 + x^24 + x^22 + x^21 + x^20 + x^17 + x^16 + x^11 + x^4 + 1
+
+54-21-7 353 x^928 + x^898 + x^870 + x^861 + x^860 + x^859 + x^858 + x^846 + x^842 + x^837 + x^836 + x^835 + x^831 + x^830 + x^829 + x^828 + x^822 + x^818 + x^811 + x^810 + x^808 + x^807 + x^804 + x^798 + x^792 + x^788 + x^781 + x^778 + x^758 + x^756 + x^751 + x^750 + x^746 + x^745 + x^740 + x^739 + x^738 + x^722 + x^720 + x^716 + x^703 + x^701 + x^696 + x^695 + x^688 + x^686 + x^674 + x^666 + x^659 + x^658 + x^656 + x^655 + x^654 + x^649 + x^644 + x^643 + x^641 + x^637 + x^632 + x^630 + x^629 + x^626 + x^623 + x^621 + x^620 + x^618 + x^614 + x^613 + x^606 + x^601 + x^599 + x^597 + x^595 + x^592 + x^591 + x^589 + x^588 + x^586 + x^584 + x^582 + x^577 + x^576 + x^575 + x^568 + x^567 + x^565 + x^564 + x^562 + x^558 + x^556 + x^553 + x^551 + x^550 + x^548 + x^547 + x^541 + x^540 + x^535 + x^534 + x^528 + x^527 + x^526 + x^522 + x^521 + x^520 + x^517 + x^514 + x^512 + x^510 + x^504 + x^503 + x^502 + x^498 + x^497 + x^496 + x^487 + x^482 + x^474 + x^473 + x^469 + x^468 + x^466 + x^463 + x^462 + x^459 + x^454 + x^453 + x^452 + x^451 + x^447 + x^442 + x^441 + x^433 + x^431 + x^430 + x^429 + x^426 + x^424 + x^423 + x^422 + x^421 + x^419 + x^417 + x^415 + x^412 + x^411 + x^409 + x^405 + x^402 + x^397 + x^396 + x^393 + x^392 + x^391 + x^385 + x^384 + x^382 + x^381 + x^380 + x^370 + x^369 + x^367 + x^365 + x^364 + x^361 + x^359 + x^356 + x^355 + x^351 + x^350 + x^349 + x^343 + x^342 + x^341 + x^340 + x^339 + x^338 + x^336 + x^335 + x^333 + x^332 + x^331 + x^330 + x^326 + x^325 + x^324 + x^323 + x^318 + x^317 + x^315 + x^313 + x^310 + x^309 + x^306 + x^301 + x^300 + x^298 + x^295 + x^294 + x^293 + x^292 + x^291 + x^290 + x^289 + x^288 + x^282 + x^281 + x^278 + x^277 + x^276 + x^275 + x^273 + x^271 + x^268 + x^266 + x^265 + x^263 + x^261 + x^260 + x^259 + x^257 + x^252 + x^249 + x^247 + x^244 + x^243 + x^238 + x^236 + x^232 + x^231 + x^229 + x^228 + x^227 + x^226 + x^225 + x^224 + x^221 + x^220 + x^219 + x^215 + x^214 + x^211 + x^209 + x^207 + x^204 + x^201 + x^198 + x^196 + x^195 + x^194 + x^193 + x^191 + x^189 + x^186 + x^185 + x^182 + x^181 + x^179 + x^178 + x^174 + x^173 + x^172 + x^170 + x^168 + x^166 + x^164 + x^162 + x^160 + x^159 + x^156 + x^155 + x^154 + x^152 + x^151 + x^149 + x^148 + x^147 + x^146 + x^145 + x^143 + x^140 + x^134 + x^133 + x^130 + x^129 + x^128 + x^126 + x^124 + x^123 + x^122 + x^121 + x^119 + x^118 + x^117 + x^116 + x^115 + x^113 + x^110 + x^107 + x^106 + x^105 + x^104 + x^103 + x^102 + x^101 + x^99 + x^97 + x^96 + x^95 + x^94 + x^93 + x^92 + x^91 + x^90 + x^89 + x^86 + x^85 + x^82 + x^81 + x^80 + x^79 + x^78 + x^76 + x^73 + x^71 + x^69 + x^65 + x^63 + x^60 + x^58 + x^57 + x^56 + x^55 + x^54 + x^52 + x^49 + x^47 + x^46 + x^45 + x^43 + x^42 + x^40 + x^39 + x^38 + x^36 + x^33 + x^32 + x^28 + x^25 + x^19 + x^18 + x^7 + 1
+
+8-36-47 355 x^928 + x^898 + x^870 + x^860 + x^856 + x^830 + x^818 + x^814 + x^813 + x^808 + x^800 + x^797 + x^796 + x^792 + x^788 + x^783 + x^778 + x^776 + x^772 + x^771 + x^770 + x^767 + x^766 + x^762 + x^755 + x^750 + x^740 + x^737 + x^736 + x^734 + x^730 + x^729 + x^724 + x^719 + x^711 + x^710 + x^707 + x^706 + x^698 + x^693 + x^692 + x^690 + x^688 + x^687 + x^680 + x^676 + x^674 + x^673 + x^672 + x^669 + x^665 + x^663 + x^662 + x^658 + x^657 + x^656 + x^653 + x^651 + x^650 + x^648 + x^647 + x^642 + x^635 + x^632 + x^631 + x^630 + x^627 + x^625 + x^624 + x^623 + x^622 + x^620 + x^619 + x^618 + x^609 + x^600 + x^595 + x^593 + x^592 + x^591 + x^589 + x^587 + x^583 + x^582 + x^579 + x^578 + x^577 + x^574 + x^570 + x^566 + x^565 + x^561 + x^559 + x^558 + x^557 + x^556 + x^555 + x^552 + x^550 + x^548 + x^544 + x^542 + x^541 + x^540 + x^537 + x^536 + x^534 + x^533 + x^532 + x^531 + x^528 + x^527 + x^525 + x^524 + x^521 + x^519 + x^518 + x^517 + x^516 + x^515 + x^514 + x^513 + x^512 + x^511 + x^509 + x^507 + x^505 + x^504 + x^502 + x^501 + x^497 + x^496 + x^493 + x^492 + x^491 + x^490 + x^488 + x^486 + x^485 + x^480 + x^478 + x^476 + x^475 + x^473 + x^469 + x^468 + x^467 + x^466 + x^463 + x^462 + x^453 + x^452 + x^451 + x^449 + x^447 + x^446 + x^445 + x^444 + x^443 + x^440 + x^438 + x^437 + x^436 + x^435 + x^427 + x^426 + x^423 + x^421 + x^419 + x^416 + x^411 + x^410 + x^409 + x^408 + x^406 + x^405 + x^404 + x^403 + x^402 + x^400 + x^398 + x^397 + x^395 + x^391 + x^390 + x^389 + x^385 + x^384 + x^380 + x^378 + x^377 + x^374 + x^373 + x^372 + x^370 + x^367 + x^366 + x^365 + x^363 + x^361 + x^360 + x^354 + x^352 + x^351 + x^350 + x^347 + x^342 + x^339 + x^337 + x^334 + x^333 + x^330 + x^328 + x^326 + x^324 + x^323 + x^315 + x^314 + x^309 + x^306 + x^304 + x^302 + x^298 + x^297 + x^296 + x^295 + x^293 + x^291 + x^289 + x^284 + x^282 + x^281 + x^279 + x^275 + x^274 + x^273 + x^269 + x^267 + x^265 + x^263 + x^261 + x^260 + x^259 + x^257 + x^256 + x^254 + x^252 + x^248 + x^247 + x^246 + x^244 + x^243 + x^242 + x^241 + x^238 + x^235 + x^234 + x^232 + x^229 + x^227 + x^225 + x^224 + x^223 + x^219 + x^217 + x^215 + x^211 + x^208 + x^206 + x^205 + x^203 + x^200 + x^197 + x^195 + x^190 + x^189 + x^187 + x^185 + x^176 + x^174 + x^172 + x^169 + x^165 + x^160 + x^159 + x^158 + x^156 + x^155 + x^154 + x^153 + x^152 + x^150 + x^146 + x^141 + x^139 + x^137 + x^135 + x^132 + x^131 + x^130 + x^127 + x^124 + x^120 + x^119 + x^116 + x^114 + x^111 + x^110 + x^109 + x^108 + x^105 + x^102 + x^99 + x^98 + x^95 + x^94 + x^93 + x^92 + x^90 + x^87 + x^86 + x^76 + x^75 + x^74 + x^72 + x^70 + x^68 + x^67 + x^65 + x^64 + x^63 + x^60 + x^59 + x^58 + x^57 + x^54 + x^51 + x^50 + x^48 + x^46 + x^45 + x^44 + x^43 + x^41 + x^39 + x^38 + x^36 + x^30 + x^27 + x^26 + x^24 + x^18 + x^14 + x^12 + 1
+
+44-14-41 357 x^928 + x^898 + x^870 + x^856 + x^852 + x^847 + x^831 + x^822 + x^817 + x^814 + x^808 + x^796 + x^795 + x^792 + x^784 + x^778 + x^777 + x^775 + x^772 + x^771 + x^766 + x^763 + x^756 + x^754 + x^747 + x^746 + x^745 + x^744 + x^742 + x^736 + x^733 + x^732 + x^730 + x^727 + x^726 + x^724 + x^721 + x^717 + x^716 + x^715 + x^714 + x^711 + x^705 + x^704 + x^694 + x^693 + x^691 + x^684 + x^682 + x^678 + x^674 + x^670 + x^664 + x^662 + x^655 + x^653 + x^647 + x^645 + x^644 + x^643 + x^637 + x^636 + x^635 + x^633 + x^631 + x^627 + x^626 + x^625 + x^624 + x^620 + x^618 + x^605 + x^604 + x^603 + x^601 + x^597 + x^588 + x^585 + x^583 + x^578 + x^575 + x^574 + x^569 + x^568 + x^567 + x^566 + x^565 + x^563 + x^558 + x^556 + x^554 + x^553 + x^552 + x^551 + x^550 + x^548 + x^544 + x^543 + x^542 + x^539 + x^538 + x^537 + x^535 + x^533 + x^532 + x^530 + x^528 + x^525 + x^523 + x^521 + x^520 + x^516 + x^515 + x^514 + x^513 + x^510 + x^509 + x^506 + x^504 + x^502 + x^497 + x^496 + x^491 + x^487 + x^485 + x^484 + x^482 + x^481 + x^480 + x^473 + x^471 + x^470 + x^468 + x^466 + x^464 + x^463 + x^462 + x^461 + x^459 + x^458 + x^455 + x^452 + x^450 + x^449 + x^447 + x^446 + x^443 + x^442 + x^440 + x^438 + x^436 + x^435 + x^432 + x^429 + x^428 + x^423 + x^420 + x^418 + x^417 + x^416 + x^413 + x^412 + x^411 + x^410 + x^407 + x^406 + x^405 + x^403 + x^402 + x^401 + x^400 + x^397 + x^396 + x^393 + x^392 + x^391 + x^390 + x^389 + x^386 + x^385 + x^384 + x^382 + x^380 + x^378 + x^376 + x^371 + x^370 + x^369 + x^368 + x^365 + x^364 + x^363 + x^359 + x^358 + x^357 + x^356 + x^353 + x^351 + x^350 + x^349 + x^348 + x^346 + x^345 + x^343 + x^342 + x^340 + x^339 + x^338 + x^335 + x^333 + x^332 + x^327 + x^324 + x^322 + x^317 + x^316 + x^315 + x^313 + x^311 + x^308 + x^307 + x^306 + x^304 + x^303 + x^301 + x^300 + x^299 + x^296 + x^294 + x^293 + x^292 + x^291 + x^290 + x^289 + x^284 + x^281 + x^279 + x^278 + x^276 + x^275 + x^273 + x^272 + x^271 + x^269 + x^266 + x^264 + x^260 + x^258 + x^256 + x^255 + x^251 + x^250 + x^248 + x^247 + x^244 + x^243 + x^241 + x^240 + x^239 + x^236 + x^235 + x^233 + x^232 + x^230 + x^226 + x^224 + x^223 + x^221 + x^220 + x^218 + x^217 + x^216 + x^215 + x^211 + x^210 + x^209 + x^208 + x^206 + x^204 + x^201 + x^200 + x^197 + x^196 + x^192 + x^187 + x^185 + x^183 + x^182 + x^181 + x^179 + x^178 + x^177 + x^175 + x^173 + x^170 + x^169 + x^168 + x^166 + x^159 + x^158 + x^157 + x^155 + x^154 + x^150 + x^149 + x^146 + x^144 + x^139 + x^138 + x^134 + x^132 + x^131 + x^128 + x^125 + x^123 + x^122 + x^120 + x^119 + x^116 + x^115 + x^111 + x^110 + x^109 + x^108 + x^101 + x^100 + x^99 + x^97 + x^96 + x^93 + x^92 + x^90 + x^87 + x^77 + x^74 + x^71 + x^70 + x^63 + x^58 + x^56 + x^54 + x^53 + x^52 + x^51 + x^50 + x^46 + x^42 + x^40 + x^38 + x^37 + x^36 + x^30 + x^28 + x^26 + x^14 + x^12 + 1
+
+22-40-11 359 x^928 + x^898 + x^870 + x^829 + x^816 + x^814 + x^808 + x^803 + x^799 + x^786 + x^784 + x^778 + x^777 + x^773 + x^764 + x^762 + x^743 + x^738 + x^736 + x^734 + x^725 + x^723 + x^719 + x^713 + x^710 + x^708 + x^699 + x^691 + x^688 + x^686 + x^684 + x^682 + x^673 + x^672 + x^665 + x^661 + x^657 + x^656 + x^654 + x^652 + x^650 + x^648 + x^645 + x^644 + x^642 + x^639 + x^637 + x^633 + x^631 + x^630 + x^626 + x^624 + x^622 + x^621 + x^620 + x^619 + x^618 + x^615 + x^614 + x^612 + x^611 + x^609 + x^606 + x^605 + x^604 + x^603 + x^601 + x^600 + x^599 + x^598 + x^594 + x^591 + x^587 + x^586 + x^585 + x^582 + x^580 + x^578 + x^577 + x^576 + x^572 + x^562 + x^561 + x^559 + x^558 + x^557 + x^556 + x^554 + x^553 + x^552 + x^550 + x^549 + x^548 + x^545 + x^543 + x^541 + x^540 + x^537 + x^535 + x^534 + x^533 + x^531 + x^530 + x^528 + x^527 + x^526 + x^522 + x^521 + x^518 + x^517 + x^516 + x^515 + x^514 + x^512 + x^511 + x^510 + x^509 + x^508 + x^507 + x^501 + x^499 + x^494 + x^491 + x^490 + x^489 + x^488 + x^486 + x^484 + x^483 + x^481 + x^480 + x^479 + x^478 + x^476 + x^475 + x^474 + x^472 + x^471 + x^469 + x^468 + x^466 + x^465 + x^464 + x^463 + x^461 + x^459 + x^457 + x^450 + x^449 + x^448 + x^446 + x^445 + x^439 + x^438 + x^437 + x^436 + x^435 + x^433 + x^431 + x^430 + x^429 + x^427 + x^426 + x^423 + x^418 + x^417 + x^415 + x^413 + x^403 + x^402 + x^401 + x^399 + x^398 + x^397 + x^395 + x^394 + x^393 + x^386 + x^382 + x^379 + x^378 + x^377 + x^368 + x^367 + x^366 + x^364 + x^363 + x^362 + x^360 + x^359 + x^356 + x^354 + x^352 + x^344 + x^343 + x^342 + x^341 + x^340 + x^337 + x^336 + x^331 + x^330 + x^325 + x^323 + x^321 + x^320 + x^319 + x^318 + x^315 + x^313 + x^311 + x^310 + x^308 + x^306 + x^303 + x^302 + x^300 + x^298 + x^297 + x^296 + x^294 + x^293 + x^292 + x^291 + x^290 + x^288 + x^286 + x^285 + x^283 + x^278 + x^277 + x^275 + x^274 + x^273 + x^271 + x^269 + x^268 + x^267 + x^265 + x^261 + x^260 + x^259 + x^257 + x^256 + x^255 + x^254 + x^251 + x^249 + x^248 + x^245 + x^244 + x^242 + x^240 + x^238 + x^237 + x^236 + x^231 + x^229 + x^227 + x^225 + x^224 + x^223 + x^222 + x^221 + x^216 + x^215 + x^213 + x^211 + x^205 + x^202 + x^196 + x^194 + x^193 + x^192 + x^191 + x^190 + x^184 + x^180 + x^179 + x^178 + x^177 + x^176 + x^175 + x^174 + x^171 + x^169 + x^164 + x^162 + x^161 + x^160 + x^155 + x^153 + x^149 + x^143 + x^142 + x^139 + x^138 + x^135 + x^134 + x^133 + x^132 + x^130 + x^128 + x^127 + x^126 + x^124 + x^123 + x^120 + x^118 + x^117 + x^115 + x^114 + x^113 + x^112 + x^111 + x^109 + x^108 + x^107 + x^104 + x^103 + x^101 + x^98 + x^95 + x^93 + x^92 + x^89 + x^88 + x^86 + x^82 + x^80 + x^76 + x^75 + x^69 + x^67 + x^65 + x^64 + x^60 + x^58 + x^57 + x^55 + x^53 + x^50 + x^49 + x^47 + x^46 + x^45 + x^44 + x^43 + x^42 + x^39 + x^37 + x^36 + x^35 + x^34 + x^32 + x^24 + x^22 + x^20 + 1
+
+33-20-8 359 x^928 + x^898 + x^874 + x^870 + x^845 + x^840 + x^824 + x^808 + x^794 + x^790 + x^787 + x^786 + x^780 + x^778 + x^764 + x^762 + x^761 + x^760 + x^756 + x^752 + x^741 + x^737 + x^736 + x^734 + x^731 + x^729 + x^728 + x^726 + x^720 + x^716 + x^712 + x^710 + x^707 + x^706 + x^703 + x^700 + x^697 + x^696 + x^688 + x^686 + x^683 + x^682 + x^681 + x^675 + x^671 + x^669 + x^667 + x^666 + x^660 + x^658 + x^657 + x^654 + x^653 + x^652 + x^649 + x^647 + x^646 + x^640 + x^639 + x^637 + x^636 + x^634 + x^629 + x^628 + x^627 + x^625 + x^624 + x^618 + x^613 + x^609 + x^608 + x^607 + x^600 + x^598 + x^597 + x^596 + x^595 + x^594 + x^593 + x^592 + x^591 + x^589 + x^587 + x^586 + x^585 + x^583 + x^582 + x^581 + x^580 + x^579 + x^577 + x^570 + x^567 + x^562 + x^554 + x^553 + x^550 + x^549 + x^548 + x^547 + x^546 + x^545 + x^544 + x^542 + x^539 + x^537 + x^535 + x^533 + x^531 + x^530 + x^524 + x^523 + x^521 + x^520 + x^517 + x^516 + x^512 + x^511 + x^504 + x^503 + x^502 + x^501 + x^497 + x^494 + x^492 + x^490 + x^489 + x^486 + x^480 + x^478 + x^477 + x^476 + x^475 + x^472 + x^467 + x^465 + x^463 + x^462 + x^461 + x^460 + x^459 + x^458 + x^456 + x^452 + x^449 + x^447 + x^446 + x^442 + x^440 + x^439 + x^438 + x^437 + x^436 + x^435 + x^434 + x^433 + x^432 + x^431 + x^430 + x^429 + x^426 + x^425 + x^423 + x^422 + x^421 + x^420 + x^415 + x^414 + x^413 + x^412 + x^411 + x^410 + x^408 + x^404 + x^402 + x^400 + x^399 + x^398 + x^395 + x^394 + x^392 + x^390 + x^387 + x^386 + x^385 + x^383 + x^379 + x^377 + x^376 + x^374 + x^373 + x^372 + x^371 + x^368 + x^366 + x^365 + x^362 + x^361 + x^360 + x^359 + x^358 + x^350 + x^347 + x^342 + x^341 + x^338 + x^337 + x^335 + x^331 + x^330 + x^326 + x^325 + x^322 + x^321 + x^320 + x^319 + x^318 + x^317 + x^312 + x^311 + x^308 + x^307 + x^305 + x^303 + x^301 + x^298 + x^295 + x^292 + x^289 + x^286 + x^282 + x^280 + x^277 + x^273 + x^271 + x^270 + x^265 + x^263 + x^262 + x^261 + x^260 + x^257 + x^253 + x^252 + x^251 + x^249 + x^248 + x^247 + x^246 + x^245 + x^243 + x^240 + x^239 + x^236 + x^234 + x^230 + x^228 + x^226 + x^225 + x^224 + x^222 + x^220 + x^219 + x^218 + x^215 + x^214 + x^213 + x^210 + x^205 + x^203 + x^201 + x^195 + x^194 + x^192 + x^191 + x^187 + x^185 + x^184 + x^183 + x^181 + x^178 + x^176 + x^174 + x^173 + x^172 + x^171 + x^170 + x^168 + x^165 + x^164 + x^163 + x^160 + x^157 + x^155 + x^154 + x^153 + x^152 + x^151 + x^150 + x^149 + x^148 + x^139 + x^138 + x^135 + x^134 + x^132 + x^131 + x^129 + x^128 + x^127 + x^126 + x^125 + x^122 + x^121 + x^119 + x^118 + x^115 + x^114 + x^111 + x^110 + x^106 + x^103 + x^101 + x^99 + x^98 + x^95 + x^91 + x^90 + x^89 + x^83 + x^78 + x^77 + x^75 + x^74 + x^70 + x^65 + x^64 + x^61 + x^56 + x^55 + x^52 + x^51 + x^49 + x^46 + x^41 + x^38 + x^36 + x^35 + x^34 + x^32 + x^29 + x^28 + x^27 + x^23 + x^16 + x^12 + x^8 + x^4 + 1
+
+6-42-43 359 x^928 + x^898 + x^877 + x^870 + x^847 + x^846 + x^844 + x^822 + x^808 + x^799 + x^794 + x^793 + x^790 + x^786 + x^778 + x^775 + x^774 + x^772 + x^771 + x^769 + x^766 + x^764 + x^750 + x^749 + x^748 + x^747 + x^744 + x^743 + x^742 + x^739 + x^736 + x^724 + x^721 + x^718 + x^715 + x^712 + x^710 + x^706 + x^702 + x^699 + x^695 + x^693 + x^692 + x^689 + x^687 + x^685 + x^684 + x^683 + x^682 + x^679 + x^674 + x^671 + x^670 + x^668 + x^667 + x^664 + x^662 + x^653 + x^652 + x^650 + x^649 + x^645 + x^644 + x^642 + x^639 + x^638 + x^637 + x^634 + x^633 + x^630 + x^624 + x^622 + x^619 + x^618 + x^616 + x^614 + x^613 + x^611 + x^610 + x^609 + x^608 + x^603 + x^601 + x^598 + x^595 + x^592 + x^589 + x^587 + x^586 + x^585 + x^584 + x^583 + x^582 + x^578 + x^574 + x^572 + x^568 + x^567 + x^565 + x^562 + x^561 + x^559 + x^558 + x^557 + x^555 + x^554 + x^550 + x^549 + x^547 + x^545 + x^541 + x^534 + x^528 + x^527 + x^523 + x^520 + x^518 + x^515 + x^510 + x^503 + x^502 + x^501 + x^500 + x^499 + x^498 + x^497 + x^496 + x^495 + x^494 + x^492 + x^489 + x^486 + x^483 + x^482 + x^479 + x^476 + x^475 + x^473 + x^472 + x^468 + x^467 + x^465 + x^464 + x^463 + x^461 + x^459 + x^458 + x^455 + x^452 + x^451 + x^450 + x^449 + x^448 + x^446 + x^445 + x^444 + x^443 + x^441 + x^440 + x^437 + x^434 + x^431 + x^430 + x^429 + x^425 + x^423 + x^422 + x^421 + x^419 + x^418 + x^417 + x^411 + x^408 + x^406 + x^405 + x^403 + x^401 + x^398 + x^397 + x^396 + x^393 + x^390 + x^382 + x^378 + x^376 + x^373 + x^372 + x^370 + x^369 + x^366 + x^361 + x^359 + x^358 + x^356 + x^354 + x^352 + x^351 + x^350 + x^349 + x^348 + x^347 + x^344 + x^340 + x^339 + x^336 + x^335 + x^333 + x^332 + x^329 + x^325 + x^324 + x^322 + x^321 + x^317 + x^316 + x^311 + x^310 + x^309 + x^303 + x^302 + x^301 + x^299 + x^298 + x^295 + x^294 + x^290 + x^289 + x^288 + x^287 + x^283 + x^282 + x^280 + x^279 + x^278 + x^277 + x^272 + x^271 + x^270 + x^268 + x^263 + x^261 + x^260 + x^259 + x^257 + x^256 + x^254 + x^250 + x^248 + x^247 + x^245 + x^244 + x^243 + x^241 + x^239 + x^238 + x^237 + x^235 + x^231 + x^230 + x^229 + x^228 + x^226 + x^225 + x^224 + x^221 + x^219 + x^217 + x^214 + x^210 + x^209 + x^208 + x^205 + x^204 + x^202 + x^199 + x^196 + x^191 + x^190 + x^189 + x^186 + x^184 + x^183 + x^180 + x^179 + x^178 + x^177 + x^175 + x^174 + x^170 + x^168 + x^167 + x^166 + x^165 + x^164 + x^162 + x^161 + x^160 + x^159 + x^157 + x^154 + x^153 + x^151 + x^143 + x^137 + x^135 + x^134 + x^129 + x^127 + x^126 + x^122 + x^121 + x^119 + x^118 + x^113 + x^111 + x^106 + x^104 + x^101 + x^100 + x^99 + x^98 + x^96 + x^92 + x^91 + x^90 + x^83 + x^82 + x^81 + x^79 + x^75 + x^74 + x^72 + x^71 + x^68 + x^64 + x^63 + x^60 + x^57 + x^56 + x^54 + x^52 + x^51 + x^49 + x^48 + x^46 + x^45 + x^43 + x^42 + x^39 + x^38 + x^37 + x^36 + x^32 + x^28 + x^26 + x^24 + x^16 + x^14 + 1
+
+33-22-4 361 x^928 + x^898 + x^870 + x^832 + x^822 + x^812 + x^808 + x^805 + x^802 + x^792 + x^784 + x^778 + x^775 + x^774 + x^762 + x^757 + x^756 + x^754 + x^746 + x^744 + x^740 + x^738 + x^736 + x^732 + x^728 + x^726 + x^720 + x^716 + x^711 + x^701 + x^697 + x^692 + x^684 + x^682 + x^680 + x^678 + x^673 + x^672 + x^671 + x^670 + x^658 + x^655 + x^653 + x^652 + x^651 + x^644 + x^637 + x^636 + x^630 + x^626 + x^622 + x^615 + x^613 + x^612 + x^611 + x^610 + x^608 + x^607 + x^606 + x^600 + x^599 + x^594 + x^593 + x^592 + x^590 + x^589 + x^588 + x^587 + x^586 + x^585 + x^583 + x^580 + x^578 + x^577 + x^574 + x^569 + x^567 + x^564 + x^562 + x^561 + x^559 + x^554 + x^553 + x^552 + x^549 + x^548 + x^544 + x^543 + x^538 + x^537 + x^536 + x^535 + x^533 + x^530 + x^525 + x^524 + x^523 + x^522 + x^521 + x^519 + x^517 + x^516 + x^515 + x^514 + x^511 + x^507 + x^506 + x^500 + x^499 + x^498 + x^496 + x^494 + x^487 + x^486 + x^485 + x^483 + x^480 + x^473 + x^468 + x^466 + x^465 + x^464 + x^463 + x^461 + x^460 + x^454 + x^452 + x^448 + x^447 + x^445 + x^441 + x^440 + x^439 + x^438 + x^436 + x^435 + x^434 + x^433 + x^429 + x^427 + x^425 + x^423 + x^421 + x^420 + x^418 + x^416 + x^415 + x^413 + x^411 + x^410 + x^409 + x^408 + x^404 + x^402 + x^401 + x^399 + x^397 + x^396 + x^393 + x^391 + x^390 + x^388 + x^387 + x^386 + x^383 + x^381 + x^380 + x^379 + x^376 + x^375 + x^374 + x^371 + x^370 + x^369 + x^368 + x^366 + x^364 + x^363 + x^361 + x^359 + x^356 + x^355 + x^354 + x^353 + x^350 + x^349 + x^344 + x^341 + x^338 + x^337 + x^336 + x^335 + x^332 + x^330 + x^329 + x^328 + x^327 + x^323 + x^322 + x^319 + x^318 + x^315 + x^314 + x^313 + x^309 + x^308 + x^307 + x^306 + x^304 + x^303 + x^301 + x^300 + x^298 + x^296 + x^295 + x^294 + x^292 + x^290 + x^289 + x^288 + x^286 + x^284 + x^282 + x^280 + x^279 + x^278 + x^275 + x^273 + x^271 + x^268 + x^266 + x^262 + x^261 + x^259 + x^258 + x^256 + x^255 + x^254 + x^253 + x^250 + x^245 + x^241 + x^240 + x^239 + x^237 + x^235 + x^234 + x^232 + x^231 + x^229 + x^226 + x^225 + x^221 + x^220 + x^218 + x^217 + x^215 + x^213 + x^211 + x^209 + x^206 + x^205 + x^201 + x^200 + x^196 + x^195 + x^192 + x^189 + x^188 + x^187 + x^186 + x^184 + x^183 + x^181 + x^180 + x^178 + x^177 + x^176 + x^175 + x^174 + x^172 + x^171 + x^169 + x^166 + x^165 + x^162 + x^160 + x^157 + x^154 + x^152 + x^150 + x^149 + x^145 + x^141 + x^138 + x^137 + x^133 + x^131 + x^129 + x^127 + x^125 + x^124 + x^123 + x^120 + x^118 + x^117 + x^115 + x^114 + x^112 + x^109 + x^108 + x^105 + x^102 + x^100 + x^99 + x^97 + x^96 + x^91 + x^90 + x^88 + x^87 + x^86 + x^85 + x^82 + x^80 + x^79 + x^77 + x^75 + x^74 + x^70 + x^66 + x^65 + x^63 + x^62 + x^61 + x^60 + x^59 + x^54 + x^52 + x^50 + x^49 + x^46 + x^42 + x^41 + x^40 + x^39 + x^38 + x^37 + x^36 + x^35 + x^33 + x^31 + x^27 + x^26 + x^22 + x^21 + x^19 + x^16 + x^14 + x^12 + 1
+
+46-48-7 361 x^928 + x^898 + x^870 + x^862 + x^848 + x^837 + x^829 + x^820 + x^815 + x^808 + x^807 + x^806 + x^802 + x^799 + x^798 + x^795 + x^790 + x^788 + x^787 + x^785 + x^776 + x^769 + x^765 + x^762 + x^760 + x^756 + x^755 + x^740 + x^739 + x^737 + x^732 + x^730 + x^727 + x^725 + x^717 + x^715 + x^714 + x^710 + x^709 + x^706 + x^704 + x^695 + x^688 + x^687 + x^686 + x^684 + x^682 + x^680 + x^679 + x^678 + x^676 + x^675 + x^667 + x^664 + x^661 + x^658 + x^657 + x^655 + x^654 + x^653 + x^652 + x^650 + x^649 + x^647 + x^646 + x^644 + x^643 + x^638 + x^637 + x^636 + x^634 + x^633 + x^627 + x^624 + x^623 + x^622 + x^620 + x^619 + x^618 + x^615 + x^612 + x^611 + x^606 + x^604 + x^601 + x^594 + x^591 + x^590 + x^589 + x^588 + x^587 + x^585 + x^583 + x^580 + x^575 + x^574 + x^572 + x^571 + x^569 + x^568 + x^567 + x^564 + x^563 + x^558 + x^557 + x^556 + x^552 + x^551 + x^546 + x^544 + x^542 + x^541 + x^535 + x^534 + x^532 + x^531 + x^530 + x^529 + x^528 + x^526 + x^525 + x^523 + x^521 + x^519 + x^518 + x^516 + x^514 + x^513 + x^506 + x^505 + x^504 + x^499 + x^498 + x^497 + x^496 + x^495 + x^493 + x^491 + x^490 + x^488 + x^487 + x^486 + x^485 + x^484 + x^481 + x^480 + x^477 + x^476 + x^473 + x^472 + x^471 + x^466 + x^465 + x^463 + x^462 + x^461 + x^460 + x^459 + x^455 + x^451 + x^449 + x^448 + x^447 + x^446 + x^444 + x^443 + x^441 + x^440 + x^439 + x^437 + x^431 + x^429 + x^428 + x^426 + x^424 + x^421 + x^420 + x^419 + x^418 + x^417 + x^416 + x^414 + x^411 + x^410 + x^408 + x^407 + x^406 + x^405 + x^403 + x^402 + x^401 + x^400 + x^397 + x^394 + x^392 + x^390 + x^388 + x^382 + x^381 + x^379 + x^378 + x^377 + x^376 + x^374 + x^367 + x^364 + x^363 + x^360 + x^357 + x^356 + x^354 + x^353 + x^352 + x^351 + x^350 + x^348 + x^343 + x^342 + x^341 + x^337 + x^334 + x^333 + x^330 + x^326 + x^316 + x^313 + x^309 + x^306 + x^305 + x^300 + x^299 + x^296 + x^294 + x^293 + x^292 + x^287 + x^285 + x^282 + x^281 + x^278 + x^276 + x^275 + x^271 + x^269 + x^267 + x^263 + x^262 + x^261 + x^260 + x^257 + x^256 + x^255 + x^253 + x^249 + x^248 + x^244 + x^243 + x^242 + x^240 + x^239 + x^238 + x^236 + x^235 + x^234 + x^231 + x^226 + x^224 + x^221 + x^217 + x^216 + x^214 + x^211 + x^210 + x^209 + x^208 + x^206 + x^205 + x^202 + x^200 + x^197 + x^196 + x^195 + x^190 + x^186 + x^184 + x^183 + x^181 + x^176 + x^172 + x^171 + x^167 + x^166 + x^165 + x^164 + x^159 + x^157 + x^154 + x^153 + x^151 + x^150 + x^146 + x^145 + x^144 + x^142 + x^141 + x^140 + x^139 + x^137 + x^136 + x^134 + x^131 + x^130 + x^129 + x^125 + x^124 + x^123 + x^120 + x^118 + x^117 + x^111 + x^110 + x^108 + x^107 + x^106 + x^105 + x^98 + x^96 + x^95 + x^94 + x^92 + x^88 + x^83 + x^81 + x^78 + x^76 + x^72 + x^71 + x^67 + x^66 + x^65 + x^63 + x^60 + x^58 + x^57 + x^56 + x^52 + x^51 + x^49 + x^48 + x^46 + x^44 + x^40 + x^38 + x^34 + x^33 + x^32 + x^31 + x^30 + x^28 + x^20 + 1
+
+49-44-52 361 x^928 + x^898 + x^870 + x^864 + x^842 + x^837 + x^834 + x^812 + x^810 + x^808 + x^804 + x^788 + x^786 + x^783 + x^782 + x^780 + x^778 + x^774 + x^758 + x^756 + x^753 + x^752 + x^734 + x^732 + x^729 + x^727 + x^724 + x^723 + x^722 + x^717 + x^704 + x^702 + x^699 + x^696 + x^693 + x^692 + x^690 + x^680 + x^678 + x^672 + x^667 + x^665 + x^663 + x^662 + x^661 + x^660 + x^650 + x^646 + x^637 + x^636 + x^633 + x^631 + x^626 + x^621 + x^618 + x^616 + x^614 + x^612 + x^611 + x^610 + x^609 + x^605 + x^603 + x^602 + x^601 + x^599 + x^597 + x^596 + x^594 + x^589 + x^588 + x^586 + x^576 + x^575 + x^573 + x^572 + x^571 + x^569 + x^568 + x^565 + x^562 + x^561 + x^560 + x^558 + x^557 + x^555 + x^554 + x^551 + x^549 + x^548 + x^543 + x^542 + x^539 + x^536 + x^532 + x^529 + x^528 + x^527 + x^526 + x^525 + x^518 + x^517 + x^514 + x^513 + x^512 + x^511 + x^509 + x^508 + x^506 + x^505 + x^499 + x^496 + x^495 + x^493 + x^492 + x^491 + x^489 + x^486 + x^483 + x^482 + x^479 + x^477 + x^476 + x^474 + x^468 + x^467 + x^462 + x^461 + x^457 + x^456 + x^454 + x^453 + x^452 + x^451 + x^449 + x^444 + x^442 + x^441 + x^439 + x^438 + x^437 + x^436 + x^432 + x^431 + x^429 + x^428 + x^427 + x^425 + x^424 + x^423 + x^422 + x^420 + x^419 + x^418 + x^416 + x^414 + x^413 + x^409 + x^407 + x^406 + x^404 + x^403 + x^402 + x^401 + x^395 + x^394 + x^393 + x^392 + x^390 + x^389 + x^387 + x^386 + x^384 + x^380 + x^378 + x^377 + x^376 + x^372 + x^371 + x^370 + x^369 + x^365 + x^363 + x^362 + x^361 + x^358 + x^357 + x^356 + x^355 + x^352 + x^351 + x^350 + x^347 + x^346 + x^344 + x^343 + x^342 + x^340 + x^339 + x^338 + x^332 + x^331 + x^328 + x^326 + x^325 + x^323 + x^322 + x^321 + x^319 + x^317 + x^316 + x^314 + x^313 + x^309 + x^305 + x^303 + x^301 + x^300 + x^299 + x^298 + x^297 + x^294 + x^292 + x^290 + x^285 + x^284 + x^282 + x^280 + x^279 + x^278 + x^276 + x^272 + x^267 + x^266 + x^265 + x^264 + x^263 + x^262 + x^259 + x^256 + x^254 + x^253 + x^251 + x^250 + x^249 + x^248 + x^247 + x^245 + x^243 + x^241 + x^240 + x^238 + x^234 + x^231 + x^229 + x^228 + x^227 + x^224 + x^222 + x^221 + x^215 + x^214 + x^211 + x^210 + x^209 + x^207 + x^206 + x^205 + x^204 + x^197 + x^196 + x^194 + x^192 + x^191 + x^189 + x^188 + x^187 + x^186 + x^182 + x^180 + x^178 + x^175 + x^172 + x^168 + x^167 + x^166 + x^163 + x^162 + x^158 + x^154 + x^153 + x^150 + x^148 + x^146 + x^145 + x^144 + x^138 + x^136 + x^132 + x^131 + x^130 + x^129 + x^124 + x^122 + x^120 + x^116 + x^115 + x^113 + x^112 + x^109 + x^108 + x^107 + x^106 + x^103 + x^102 + x^101 + x^99 + x^98 + x^97 + x^96 + x^94 + x^92 + x^90 + x^86 + x^85 + x^84 + x^83 + x^82 + x^80 + x^79 + x^77 + x^76 + x^65 + x^60 + x^59 + x^57 + x^54 + x^52 + x^47 + x^45 + x^44 + x^42 + x^38 + x^37 + x^34 + x^33 + x^31 + x^29 + x^26 + x^25 + x^24 + x^22 + x^21 + x^19 + x^18 + x^17 + x^15 + x^14 + x^12 + x^10 + 1
+
+34-42-39 365 x^928 + x^898 + x^870 + x^860 + x^850 + x^840 + x^831 + x^830 + x^811 + x^808 + x^801 + x^800 + x^792 + x^790 + x^780 + x^778 + x^771 + x^770 + x^762 + x^753 + x^744 + x^742 + x^740 + x^734 + x^720 + x^715 + x^712 + x^710 + x^703 + x^702 + x^694 + x^693 + x^691 + x^688 + x^685 + x^682 + x^680 + x^676 + x^674 + x^672 + x^664 + x^663 + x^662 + x^660 + x^658 + x^657 + x^656 + x^653 + x^652 + x^650 + x^647 + x^646 + x^645 + x^643 + x^637 + x^634 + x^628 + x^627 + x^623 + x^622 + x^618 + x^617 + x^615 + x^614 + x^608 + x^607 + x^604 + x^603 + x^600 + x^597 + x^593 + x^592 + x^589 + x^588 + x^587 + x^586 + x^585 + x^584 + x^579 + x^578 + x^577 + x^574 + x^570 + x^567 + x^565 + x^563 + x^562 + x^560 + x^558 + x^555 + x^550 + x^549 + x^547 + x^544 + x^542 + x^541 + x^536 + x^533 + x^532 + x^529 + x^527 + x^526 + x^521 + x^520 + x^518 + x^517 + x^516 + x^514 + x^510 + x^509 + x^508 + x^506 + x^504 + x^503 + x^502 + x^501 + x^497 + x^496 + x^489 + x^488 + x^487 + x^486 + x^484 + x^481 + x^480 + x^476 + x^475 + x^473 + x^472 + x^471 + x^470 + x^467 + x^466 + x^463 + x^462 + x^459 + x^458 + x^457 + x^456 + x^453 + x^451 + x^450 + x^446 + x^445 + x^444 + x^443 + x^442 + x^439 + x^438 + x^437 + x^436 + x^433 + x^432 + x^430 + x^428 + x^427 + x^425 + x^421 + x^420 + x^413 + x^412 + x^411 + x^408 + x^406 + x^405 + x^404 + x^403 + x^401 + x^399 + x^398 + x^396 + x^391 + x^388 + x^385 + x^381 + x^378 + x^376 + x^375 + x^374 + x^372 + x^370 + x^368 + x^364 + x^363 + x^360 + x^359 + x^357 + x^354 + x^353 + x^350 + x^349 + x^348 + x^347 + x^346 + x^344 + x^343 + x^342 + x^340 + x^339 + x^337 + x^335 + x^334 + x^333 + x^331 + x^330 + x^329 + x^327 + x^326 + x^325 + x^324 + x^323 + x^321 + x^320 + x^317 + x^312 + x^310 + x^309 + x^303 + x^300 + x^298 + x^296 + x^295 + x^292 + x^291 + x^290 + x^288 + x^287 + x^286 + x^285 + x^284 + x^282 + x^278 + x^277 + x^275 + x^274 + x^273 + x^267 + x^266 + x^265 + x^264 + x^263 + x^262 + x^261 + x^260 + x^257 + x^254 + x^250 + x^249 + x^244 + x^243 + x^242 + x^239 + x^238 + x^237 + x^236 + x^231 + x^230 + x^229 + x^228 + x^226 + x^224 + x^223 + x^222 + x^221 + x^220 + x^217 + x^214 + x^213 + x^212 + x^211 + x^208 + x^207 + x^206 + x^205 + x^204 + x^202 + x^198 + x^197 + x^195 + x^194 + x^192 + x^189 + x^187 + x^186 + x^185 + x^181 + x^180 + x^179 + x^178 + x^175 + x^173 + x^170 + x^168 + x^167 + x^166 + x^165 + x^164 + x^157 + x^155 + x^154 + x^150 + x^149 + x^148 + x^144 + x^143 + x^140 + x^138 + x^137 + x^135 + x^134 + x^132 + x^126 + x^124 + x^123 + x^120 + x^119 + x^117 + x^115 + x^113 + x^105 + x^104 + x^102 + x^100 + x^97 + x^96 + x^94 + x^91 + x^88 + x^87 + x^85 + x^84 + x^83 + x^82 + x^80 + x^79 + x^77 + x^76 + x^69 + x^68 + x^64 + x^61 + x^60 + x^55 + x^53 + x^51 + x^50 + x^48 + x^45 + x^44 + x^43 + x^40 + x^39 + x^38 + x^37 + x^36 + x^34 + x^33 + x^30 + x^22 + x^20 + x^19 + x^18 + x^16 + x^14 + 1
+
+34-2-25 367 x^928 + x^898 + x^870 + x^868 + x^866 + x^850 + x^838 + x^832 + x^822 + x^820 + x^808 + x^804 + x^802 + x^797 + x^792 + x^790 + x^788 + x^786 + x^778 + x^770 + x^768 + x^767 + x^760 + x^754 + x^748 + x^747 + x^746 + x^744 + x^738 + x^736 + x^735 + x^728 + x^726 + x^720 + x^717 + x^712 + x^708 + x^696 + x^694 + x^692 + x^690 + x^688 + x^687 + x^685 + x^684 + x^678 + x^677 + x^675 + x^673 + x^669 + x^667 + x^662 + x^658 + x^657 + x^654 + x^652 + x^647 + x^643 + x^642 + x^640 + x^639 + x^638 + x^637 + x^634 + x^633 + x^624 + x^613 + x^612 + x^611 + x^610 + x^609 + x^607 + x^606 + x^603 + x^600 + x^598 + x^597 + x^593 + x^591 + x^590 + x^588 + x^587 + x^586 + x^584 + x^583 + x^582 + x^581 + x^577 + x^576 + x^574 + x^571 + x^570 + x^568 + x^567 + x^564 + x^563 + x^562 + x^560 + x^558 + x^557 + x^554 + x^553 + x^551 + x^549 + x^548 + x^547 + x^546 + x^545 + x^539 + x^538 + x^535 + x^533 + x^531 + x^530 + x^529 + x^527 + x^525 + x^524 + x^523 + x^522 + x^520 + x^517 + x^515 + x^514 + x^511 + x^510 + x^507 + x^506 + x^504 + x^503 + x^500 + x^499 + x^498 + x^496 + x^493 + x^491 + x^490 + x^489 + x^487 + x^485 + x^483 + x^482 + x^480 + x^478 + x^477 + x^474 + x^473 + x^471 + x^469 + x^468 + x^465 + x^464 + x^460 + x^459 + x^457 + x^455 + x^451 + x^450 + x^449 + x^447 + x^446 + x^442 + x^441 + x^438 + x^430 + x^428 + x^427 + x^426 + x^425 + x^421 + x^420 + x^417 + x^416 + x^415 + x^414 + x^411 + x^410 + x^409 + x^406 + x^405 + x^404 + x^402 + x^396 + x^394 + x^393 + x^390 + x^389 + x^388 + x^386 + x^384 + x^382 + x^381 + x^380 + x^379 + x^378 + x^377 + x^376 + x^374 + x^373 + x^372 + x^371 + x^369 + x^366 + x^365 + x^364 + x^363 + x^356 + x^354 + x^352 + x^351 + x^344 + x^342 + x^338 + x^336 + x^335 + x^334 + x^327 + x^323 + x^321 + x^319 + x^318 + x^317 + x^312 + x^308 + x^305 + x^303 + x^302 + x^301 + x^299 + x^298 + x^297 + x^289 + x^286 + x^285 + x^283 + x^277 + x^276 + x^275 + x^272 + x^271 + x^269 + x^268 + x^267 + x^266 + x^263 + x^258 + x^255 + x^254 + x^252 + x^251 + x^247 + x^246 + x^235 + x^233 + x^232 + x^229 + x^226 + x^225 + x^223 + x^220 + x^219 + x^216 + x^212 + x^210 + x^209 + x^208 + x^206 + x^205 + x^204 + x^202 + x^201 + x^200 + x^198 + x^196 + x^193 + x^190 + x^187 + x^185 + x^184 + x^182 + x^180 + x^179 + x^174 + x^173 + x^172 + x^171 + x^169 + x^167 + x^166 + x^165 + x^164 + x^159 + x^158 + x^156 + x^155 + x^154 + x^151 + x^150 + x^149 + x^148 + x^147 + x^146 + x^144 + x^143 + x^141 + x^137 + x^133 + x^132 + x^131 + x^130 + x^129 + x^126 + x^124 + x^123 + x^122 + x^118 + x^112 + x^111 + x^109 + x^108 + x^107 + x^105 + x^104 + x^103 + x^102 + x^99 + x^96 + x^93 + x^92 + x^91 + x^89 + x^86 + x^84 + x^83 + x^82 + x^81 + x^79 + x^77 + x^76 + x^75 + x^71 + x^70 + x^67 + x^65 + x^63 + x^53 + x^49 + x^44 + x^40 + x^38 + x^37 + x^36 + x^35 + x^34 + x^32 + x^31 + x^28 + x^26 + x^25 + x^24 + x^22 + x^18 + x^16 + x^10 + x^8 + x^4 + 1
+
+34-39-1 367 x^928 + x^898 + x^894 + x^874 + x^870 + x^864 + x^855 + x^844 + x^840 + x^835 + x^834 + x^825 + x^820 + x^815 + x^814 + x^808 + x^805 + x^804 + x^801 + x^800 + x^796 + x^790 + x^785 + x^784 + x^778 + x^776 + x^770 + x^766 + x^761 + x^760 + x^756 + x^754 + x^751 + x^750 + x^746 + x^741 + x^740 + x^735 + x^732 + x^731 + x^730 + x^727 + x^724 + x^722 + x^721 + x^720 + x^716 + x^715 + x^712 + x^706 + x^705 + x^700 + x^697 + x^694 + x^692 + x^688 + x^686 + x^685 + x^682 + x^677 + x^676 + x^672 + x^667 + x^664 + x^657 + x^655 + x^653 + x^651 + x^650 + x^648 + x^642 + x^638 + x^632 + x^631 + x^630 + x^628 + x^627 + x^625 + x^622 + x^620 + x^618 + x^617 + x^616 + x^612 + x^611 + x^602 + x^600 + x^598 + x^597 + x^591 + x^588 + x^586 + x^582 + x^581 + x^579 + x^578 + x^577 + x^574 + x^573 + x^572 + x^568 + x^566 + x^564 + x^560 + x^552 + x^541 + x^538 + x^537 + x^535 + x^534 + x^532 + x^531 + x^529 + x^528 + x^523 + x^522 + x^521 + x^519 + x^518 + x^516 + x^515 + x^510 + x^509 + x^508 + x^506 + x^504 + x^500 + x^499 + x^498 + x^497 + x^495 + x^492 + x^490 + x^489 + x^488 + x^484 + x^482 + x^477 + x^475 + x^474 + x^472 + x^471 + x^470 + x^469 + x^467 + x^466 + x^465 + x^464 + x^462 + x^461 + x^460 + x^457 + x^455 + x^454 + x^453 + x^448 + x^447 + x^446 + x^444 + x^443 + x^442 + x^441 + x^440 + x^437 + x^431 + x^430 + x^429 + x^426 + x^424 + x^423 + x^421 + x^419 + x^418 + x^417 + x^416 + x^412 + x^411 + x^409 + x^408 + x^407 + x^406 + x^405 + x^403 + x^402 + x^397 + x^395 + x^394 + x^389 + x^388 + x^384 + x^383 + x^382 + x^380 + x^379 + x^377 + x^376 + x^375 + x^374 + x^373 + x^372 + x^370 + x^369 + x^368 + x^367 + x^366 + x^361 + x^360 + x^359 + x^357 + x^355 + x^353 + x^350 + x^349 + x^344 + x^342 + x^341 + x^340 + x^338 + x^337 + x^336 + x^335 + x^334 + x^333 + x^332 + x^331 + x^329 + x^328 + x^326 + x^321 + x^320 + x^318 + x^317 + x^316 + x^313 + x^311 + x^310 + x^309 + x^306 + x^303 + x^302 + x^300 + x^299 + x^296 + x^295 + x^294 + x^293 + x^292 + x^291 + x^288 + x^287 + x^286 + x^284 + x^279 + x^276 + x^274 + x^270 + x^269 + x^268 + x^265 + x^264 + x^262 + x^259 + x^257 + x^256 + x^255 + x^251 + x^248 + x^247 + x^245 + x^244 + x^243 + x^240 + x^237 + x^234 + x^232 + x^230 + x^227 + x^224 + x^223 + x^219 + x^214 + x^212 + x^206 + x^204 + x^201 + x^198 + x^197 + x^194 + x^192 + x^190 + x^189 + x^186 + x^185 + x^182 + x^179 + x^178 + x^174 + x^172 + x^171 + x^168 + x^166 + x^163 + x^162 + x^161 + x^159 + x^158 + x^154 + x^153 + x^152 + x^151 + x^149 + x^148 + x^146 + x^143 + x^142 + x^140 + x^138 + x^137 + x^135 + x^134 + x^133 + x^132 + x^131 + x^128 + x^126 + x^125 + x^124 + x^123 + x^119 + x^118 + x^116 + x^115 + x^109 + x^108 + x^106 + x^105 + x^103 + x^100 + x^98 + x^91 + x^89 + x^86 + x^84 + x^83 + x^81 + x^79 + x^78 + x^70 + x^69 + x^67 + x^66 + x^65 + x^59 + x^58 + x^56 + x^48 + x^45 + x^43 + x^42 + x^38 + x^37 + x^34 + x^29 + x^26 + x^21 + x^10 + 1
+
+8-38-53 367 x^928 + x^898 + x^870 + x^843 + x^838 + x^833 + x^830 + x^820 + x^816 + x^811 + x^808 + x^800 + x^786 + x^781 + x^778 + x^773 + x^768 + x^760 + x^756 + x^752 + x^750 + x^743 + x^742 + x^738 + x^733 + x^732 + x^730 + x^728 + x^726 + x^723 + x^720 + x^718 + x^711 + x^710 + x^708 + x^700 + x^699 + x^698 + x^696 + x^695 + x^693 + x^691 + x^690 + x^688 + x^686 + x^685 + x^683 + x^681 + x^672 + x^670 + x^667 + x^666 + x^665 + x^664 + x^663 + x^662 + x^661 + x^658 + x^657 + x^655 + x^654 + x^650 + x^648 + x^646 + x^645 + x^644 + x^642 + x^640 + x^639 + x^638 + x^636 + x^634 + x^626 + x^622 + x^620 + x^617 + x^615 + x^614 + x^613 + x^612 + x^606 + x^605 + x^603 + x^600 + x^597 + x^596 + x^592 + x^591 + x^590 + x^589 + x^588 + x^585 + x^584 + x^581 + x^578 + x^577 + x^576 + x^575 + x^574 + x^572 + x^569 + x^568 + x^564 + x^562 + x^561 + x^556 + x^554 + x^551 + x^549 + x^544 + x^543 + x^542 + x^540 + x^539 + x^538 + x^533 + x^532 + x^530 + x^529 + x^527 + x^526 + x^524 + x^521 + x^520 + x^517 + x^515 + x^514 + x^510 + x^507 + x^504 + x^503 + x^502 + x^501 + x^500 + x^499 + x^498 + x^497 + x^495 + x^493 + x^489 + x^487 + x^486 + x^483 + x^482 + x^480 + x^477 + x^474 + x^471 + x^469 + x^468 + x^463 + x^461 + x^459 + x^456 + x^453 + x^450 + x^447 + x^443 + x^442 + x^441 + x^439 + x^438 + x^437 + x^436 + x^435 + x^431 + x^424 + x^422 + x^415 + x^411 + x^410 + x^409 + x^407 + x^406 + x^405 + x^404 + x^401 + x^400 + x^396 + x^395 + x^392 + x^391 + x^389 + x^388 + x^385 + x^382 + x^381 + x^380 + x^379 + x^377 + x^374 + x^369 + x^368 + x^367 + x^366 + x^365 + x^364 + x^359 + x^357 + x^353 + x^351 + x^349 + x^345 + x^344 + x^342 + x^341 + x^340 + x^339 + x^337 + x^335 + x^334 + x^333 + x^331 + x^330 + x^329 + x^327 + x^326 + x^324 + x^323 + x^322 + x^320 + x^319 + x^318 + x^314 + x^313 + x^312 + x^311 + x^309 + x^307 + x^304 + x^303 + x^300 + x^298 + x^294 + x^288 + x^285 + x^284 + x^281 + x^280 + x^279 + x^277 + x^276 + x^275 + x^273 + x^271 + x^270 + x^269 + x^268 + x^266 + x^261 + x^254 + x^253 + x^251 + x^250 + x^249 + x^248 + x^247 + x^245 + x^244 + x^242 + x^240 + x^238 + x^236 + x^233 + x^231 + x^230 + x^227 + x^226 + x^224 + x^223 + x^222 + x^221 + x^220 + x^219 + x^218 + x^216 + x^215 + x^213 + x^212 + x^209 + x^208 + x^207 + x^202 + x^198 + x^195 + x^189 + x^186 + x^183 + x^182 + x^180 + x^179 + x^177 + x^175 + x^172 + x^170 + x^168 + x^167 + x^159 + x^158 + x^154 + x^153 + x^150 + x^149 + x^144 + x^141 + x^140 + x^138 + x^136 + x^135 + x^134 + x^133 + x^132 + x^130 + x^129 + x^126 + x^125 + x^124 + x^123 + x^122 + x^121 + x^117 + x^116 + x^114 + x^113 + x^112 + x^111 + x^110 + x^108 + x^106 + x^105 + x^102 + x^98 + x^97 + x^96 + x^95 + x^92 + x^91 + x^90 + x^87 + x^79 + x^77 + x^74 + x^73 + x^72 + x^69 + x^64 + x^61 + x^60 + x^59 + x^58 + x^57 + x^56 + x^55 + x^53 + x^50 + x^49 + x^47 + x^46 + x^44 + x^43 + x^36 + x^35 + x^30 + x^29 + x^22 + x^16 + 1
+
+40-6-51 369 x^928 + x^898 + x^870 + x^859 + x^840 + x^830 + x^819 + x^790 + x^780 + x^779 + x^771 + x^770 + x^768 + x^761 + x^760 + x^757 + x^752 + x^750 + x^748 + x^742 + x^741 + x^740 + x^739 + x^738 + x^732 + x^728 + x^727 + x^721 + x^719 + x^718 + x^717 + x^712 + x^709 + x^708 + x^702 + x^699 + x^692 + x^690 + x^687 + x^684 + x^678 + x^677 + x^673 + x^672 + x^671 + x^668 + x^666 + x^659 + x^658 + x^657 + x^655 + x^654 + x^653 + x^652 + x^651 + x^648 + x^647 + x^645 + x^644 + x^643 + x^637 + x^636 + x^634 + x^632 + x^631 + x^624 + x^622 + x^621 + x^620 + x^616 + x^614 + x^613 + x^612 + x^611 + x^609 + x^608 + x^607 + x^598 + x^596 + x^594 + x^593 + x^592 + x^591 + x^588 + x^585 + x^583 + x^579 + x^578 + x^576 + x^575 + x^574 + x^573 + x^572 + x^571 + x^570 + x^567 + x^566 + x^563 + x^562 + x^560 + x^557 + x^555 + x^554 + x^549 + x^547 + x^546 + x^544 + x^542 + x^538 + x^537 + x^535 + x^534 + x^533 + x^531 + x^529 + x^527 + x^526 + x^522 + x^520 + x^518 + x^517 + x^515 + x^514 + x^513 + x^511 + x^510 + x^509 + x^508 + x^505 + x^504 + x^500 + x^497 + x^496 + x^494 + x^492 + x^481 + x^477 + x^475 + x^473 + x^471 + x^470 + x^468 + x^467 + x^466 + x^465 + x^464 + x^463 + x^462 + x^460 + x^457 + x^456 + x^453 + x^451 + x^450 + x^449 + x^447 + x^446 + x^444 + x^443 + x^441 + x^436 + x^435 + x^434 + x^429 + x^428 + x^427 + x^423 + x^422 + x^421 + x^419 + x^418 + x^416 + x^414 + x^413 + x^412 + x^410 + x^408 + x^406 + x^405 + x^402 + x^401 + x^400 + x^399 + x^398 + x^396 + x^395 + x^394 + x^393 + x^391 + x^390 + x^388 + x^385 + x^384 + x^382 + x^381 + x^379 + x^378 + x^377 + x^376 + x^375 + x^374 + x^373 + x^372 + x^371 + x^370 + x^367 + x^366 + x^365 + x^363 + x^360 + x^358 + x^355 + x^354 + x^353 + x^352 + x^350 + x^347 + x^344 + x^342 + x^340 + x^339 + x^338 + x^335 + x^334 + x^331 + x^330 + x^328 + x^324 + x^323 + x^322 + x^321 + x^318 + x^316 + x^315 + x^313 + x^307 + x^306 + x^304 + x^303 + x^301 + x^300 + x^299 + x^298 + x^297 + x^290 + x^287 + x^285 + x^283 + x^282 + x^281 + x^280 + x^279 + x^278 + x^276 + x^275 + x^274 + x^270 + x^264 + x^263 + x^262 + x^261 + x^259 + x^258 + x^254 + x^253 + x^252 + x^244 + x^242 + x^241 + x^240 + x^239 + x^237 + x^236 + x^235 + x^230 + x^228 + x^224 + x^220 + x^217 + x^214 + x^210 + x^209 + x^208 + x^206 + x^203 + x^202 + x^201 + x^200 + x^199 + x^196 + x^195 + x^194 + x^192 + x^189 + x^188 + x^186 + x^185 + x^184 + x^181 + x^176 + x^175 + x^173 + x^172 + x^171 + x^170 + x^165 + x^163 + x^161 + x^159 + x^156 + x^154 + x^153 + x^144 + x^143 + x^137 + x^135 + x^134 + x^132 + x^130 + x^129 + x^126 + x^125 + x^122 + x^120 + x^118 + x^114 + x^113 + x^110 + x^109 + x^108 + x^107 + x^98 + x^97 + x^95 + x^92 + x^88 + x^87 + x^86 + x^85 + x^84 + x^83 + x^80 + x^76 + x^74 + x^71 + x^70 + x^69 + x^64 + x^63 + x^62 + x^61 + x^57 + x^53 + x^52 + x^50 + x^46 + x^40 + x^38 + x^37 + x^34 + x^33 + x^32 + x^31 + x^28 + x^25 + x^22 + x^18 + x^6 + 1
+
+48-40-25 371 x^928 + x^898 + x^878 + x^870 + x^851 + x^844 + x^832 + x^828 + x^818 + x^809 + x^808 + x^802 + x^790 + x^782 + x^779 + x^772 + x^755 + x^751 + x^749 + x^744 + x^742 + x^740 + x^738 + x^732 + x^731 + x^728 + x^725 + x^719 + x^718 + x^714 + x^713 + x^712 + x^708 + x^705 + x^695 + x^691 + x^690 + x^688 + x^687 + x^686 + x^684 + x^683 + x^679 + x^676 + x^674 + x^671 + x^668 + x^665 + x^663 + x^662 + x^661 + x^656 + x^655 + x^652 + x^650 + x^649 + x^648 + x^647 + x^646 + x^641 + x^640 + x^638 + x^636 + x^635 + x^633 + x^630 + x^627 + x^626 + x^625 + x^624 + x^621 + x^620 + x^619 + x^618 + x^617 + x^614 + x^613 + x^608 + x^607 + x^606 + x^605 + x^604 + x^603 + x^602 + x^599 + x^597 + x^596 + x^591 + x^589 + x^586 + x^585 + x^584 + x^582 + x^578 + x^576 + x^572 + x^569 + x^566 + x^564 + x^558 + x^555 + x^553 + x^550 + x^549 + x^548 + x^545 + x^543 + x^541 + x^540 + x^538 + x^537 + x^533 + x^530 + x^527 + x^524 + x^523 + x^521 + x^520 + x^519 + x^517 + x^516 + x^515 + x^513 + x^510 + x^504 + x^503 + x^501 + x^500 + x^498 + x^497 + x^496 + x^494 + x^492 + x^491 + x^488 + x^485 + x^482 + x^478 + x^476 + x^474 + x^473 + x^471 + x^469 + x^468 + x^464 + x^461 + x^460 + x^459 + x^454 + x^450 + x^449 + x^448 + x^444 + x^439 + x^438 + x^437 + x^436 + x^435 + x^433 + x^431 + x^428 + x^426 + x^424 + x^420 + x^417 + x^416 + x^413 + x^411 + x^408 + x^407 + x^406 + x^403 + x^400 + x^399 + x^398 + x^397 + x^396 + x^392 + x^390 + x^389 + x^388 + x^387 + x^386 + x^385 + x^381 + x^378 + x^374 + x^370 + x^369 + x^368 + x^367 + x^365 + x^364 + x^359 + x^356 + x^354 + x^350 + x^348 + x^346 + x^344 + x^343 + x^342 + x^340 + x^338 + x^337 + x^334 + x^332 + x^330 + x^329 + x^327 + x^322 + x^317 + x^316 + x^314 + x^311 + x^308 + x^307 + x^301 + x^300 + x^299 + x^296 + x^295 + x^292 + x^291 + x^290 + x^286 + x^284 + x^282 + x^279 + x^278 + x^276 + x^273 + x^270 + x^269 + x^265 + x^263 + x^259 + x^258 + x^257 + x^256 + x^255 + x^253 + x^251 + x^249 + x^247 + x^245 + x^244 + x^243 + x^242 + x^241 + x^240 + x^238 + x^236 + x^231 + x^230 + x^229 + x^228 + x^226 + x^223 + x^221 + x^220 + x^219 + x^218 + x^217 + x^215 + x^214 + x^212 + x^211 + x^210 + x^208 + x^207 + x^206 + x^205 + x^202 + x^200 + x^198 + x^193 + x^192 + x^191 + x^187 + x^186 + x^184 + x^182 + x^179 + x^173 + x^171 + x^169 + x^168 + x^164 + x^163 + x^162 + x^161 + x^157 + x^156 + x^155 + x^154 + x^153 + x^151 + x^149 + x^147 + x^146 + x^145 + x^143 + x^140 + x^137 + x^136 + x^133 + x^127 + x^122 + x^121 + x^119 + x^115 + x^114 + x^112 + x^111 + x^110 + x^108 + x^104 + x^103 + x^101 + x^98 + x^94 + x^88 + x^87 + x^85 + x^83 + x^81 + x^80 + x^79 + x^78 + x^77 + x^75 + x^74 + x^73 + x^70 + x^69 + x^68 + x^67 + x^65 + x^63 + x^62 + x^60 + x^59 + x^58 + x^57 + x^56 + x^54 + x^53 + x^52 + x^50 + x^49 + x^47 + x^46 + x^45 + x^41 + x^39 + x^36 + x^34 + x^33 + x^32 + x^31 + x^30 + x^29 + x^28 + x^26 + x^25 + x^21 + x^16 + x^14 + x^4 + 1
+
+19-22-10 373 x^928 + x^898 + x^870 + x^866 + x^842 + x^836 + x^834 + x^821 + x^808 + x^807 + x^806 + x^804 + x^802 + x^794 + x^792 + x^782 + x^778 + x^777 + x^776 + x^770 + x^766 + x^761 + x^757 + x^753 + x^748 + x^743 + x^739 + x^738 + x^736 + x^735 + x^734 + x^732 + x^728 + x^725 + x^722 + x^721 + x^718 + x^716 + x^713 + x^711 + x^710 + x^709 + x^708 + x^705 + x^703 + x^702 + x^693 + x^686 + x^681 + x^680 + x^677 + x^664 + x^662 + x^659 + x^652 + x^651 + x^649 + x^646 + x^644 + x^643 + x^641 + x^638 + x^636 + x^629 + x^627 + x^625 + x^623 + x^622 + x^619 + x^617 + x^616 + x^615 + x^614 + x^612 + x^611 + x^609 + x^606 + x^605 + x^600 + x^597 + x^596 + x^595 + x^592 + x^591 + x^590 + x^586 + x^582 + x^580 + x^577 + x^576 + x^574 + x^572 + x^570 + x^569 + x^567 + x^565 + x^563 + x^562 + x^560 + x^557 + x^556 + x^555 + x^554 + x^552 + x^549 + x^548 + x^546 + x^545 + x^544 + x^542 + x^540 + x^538 + x^536 + x^534 + x^533 + x^532 + x^530 + x^528 + x^525 + x^520 + x^519 + x^518 + x^516 + x^514 + x^513 + x^511 + x^509 + x^504 + x^501 + x^497 + x^496 + x^495 + x^488 + x^487 + x^485 + x^482 + x^481 + x^480 + x^479 + x^478 + x^476 + x^475 + x^474 + x^472 + x^470 + x^468 + x^466 + x^465 + x^463 + x^458 + x^453 + x^451 + x^449 + x^448 + x^447 + x^446 + x^445 + x^444 + x^443 + x^439 + x^438 + x^437 + x^436 + x^433 + x^428 + x^424 + x^418 + x^417 + x^414 + x^412 + x^411 + x^408 + x^407 + x^406 + x^404 + x^403 + x^401 + x^400 + x^399 + x^396 + x^395 + x^393 + x^390 + x^389 + x^388 + x^387 + x^386 + x^385 + x^384 + x^382 + x^380 + x^379 + x^375 + x^371 + x^370 + x^369 + x^365 + x^363 + x^362 + x^357 + x^355 + x^354 + x^351 + x^348 + x^346 + x^345 + x^344 + x^342 + x^340 + x^338 + x^336 + x^334 + x^331 + x^328 + x^327 + x^326 + x^324 + x^323 + x^322 + x^320 + x^315 + x^308 + x^306 + x^298 + x^295 + x^292 + x^287 + x^286 + x^285 + x^284 + x^282 + x^281 + x^280 + x^279 + x^278 + x^276 + x^274 + x^273 + x^272 + x^270 + x^269 + x^268 + x^267 + x^265 + x^262 + x^258 + x^256 + x^253 + x^252 + x^251 + x^250 + x^246 + x^245 + x^244 + x^243 + x^242 + x^240 + x^239 + x^238 + x^237 + x^235 + x^232 + x^228 + x^226 + x^224 + x^217 + x^215 + x^213 + x^211 + x^210 + x^208 + x^206 + x^201 + x^199 + x^196 + x^195 + x^192 + x^190 + x^187 + x^186 + x^185 + x^184 + x^182 + x^180 + x^178 + x^175 + x^174 + x^173 + x^172 + x^165 + x^160 + x^159 + x^158 + x^157 + x^155 + x^154 + x^153 + x^148 + x^146 + x^145 + x^143 + x^142 + x^137 + x^135 + x^133 + x^132 + x^131 + x^128 + x^127 + x^124 + x^123 + x^122 + x^121 + x^117 + x^115 + x^114 + x^113 + x^112 + x^108 + x^107 + x^106 + x^105 + x^103 + x^102 + x^101 + x^100 + x^94 + x^93 + x^92 + x^89 + x^87 + x^86 + x^85 + x^83 + x^80 + x^79 + x^78 + x^77 + x^75 + x^74 + x^72 + x^70 + x^67 + x^66 + x^62 + x^61 + x^59 + x^58 + x^57 + x^55 + x^51 + x^50 + x^49 + x^42 + x^41 + x^39 + x^36 + x^34 + x^32 + x^30 + x^26 + x^24 + x^23 + x^22 + x^21 + x^18 + x^16 + x^14 + x^10 + x^9 + x^8 + x^2 + 1
+
+52-17-25 375 x^928 + x^898 + x^870 + x^861 + x^860 + x^859 + x^858 + x^849 + x^848 + x^847 + x^846 + x^842 + x^836 + x^835 + x^831 + x^830 + x^829 + x^828 + x^823 + x^822 + x^811 + x^810 + x^808 + x^807 + x^806 + x^804 + x^800 + x^799 + x^793 + x^792 + x^789 + x^788 + x^786 + x^780 + x^778 + x^777 + x^775 + x^774 + x^770 + x^769 + x^768 + x^764 + x^763 + x^762 + x^758 + x^757 + x^756 + x^755 + x^751 + x^750 + x^744 + x^740 + x^739 + x^738 + x^733 + x^731 + x^728 + x^721 + x^720 + x^716 + x^715 + x^714 + x^713 + x^710 + x^709 + x^702 + x^701 + x^697 + x^696 + x^695 + x^692 + x^688 + x^686 + x^684 + x^683 + x^680 + x^679 + x^678 + x^677 + x^674 + x^673 + x^672 + x^665 + x^662 + x^658 + x^654 + x^647 + x^643 + x^636 + x^635 + x^631 + x^628 + x^626 + x^625 + x^622 + x^621 + x^619 + x^618 + x^617 + x^614 + x^613 + x^612 + x^611 + x^609 + x^608 + x^598 + x^595 + x^591 + x^589 + x^588 + x^587 + x^586 + x^584 + x^582 + x^576 + x^574 + x^571 + x^569 + x^567 + x^564 + x^563 + x^560 + x^557 + x^550 + x^549 + x^548 + x^547 + x^546 + x^544 + x^539 + x^537 + x^536 + x^529 + x^527 + x^526 + x^523 + x^522 + x^521 + x^516 + x^513 + x^512 + x^509 + x^508 + x^505 + x^503 + x^502 + x^499 + x^493 + x^492 + x^491 + x^487 + x^486 + x^484 + x^482 + x^479 + x^478 + x^477 + x^475 + x^474 + x^471 + x^468 + x^467 + x^465 + x^463 + x^462 + x^461 + x^460 + x^455 + x^452 + x^445 + x^443 + x^442 + x^437 + x^433 + x^432 + x^429 + x^427 + x^426 + x^424 + x^423 + x^422 + x^421 + x^417 + x^411 + x^410 + x^408 + x^407 + x^403 + x^402 + x^400 + x^399 + x^393 + x^391 + x^390 + x^389 + x^386 + x^384 + x^381 + x^379 + x^375 + x^373 + x^372 + x^369 + x^367 + x^366 + x^364 + x^363 + x^362 + x^361 + x^360 + x^357 + x^356 + x^353 + x^352 + x^348 + x^345 + x^344 + x^342 + x^341 + x^340 + x^335 + x^332 + x^330 + x^329 + x^328 + x^327 + x^322 + x^321 + x^319 + x^318 + x^317 + x^315 + x^312 + x^311 + x^310 + x^308 + x^305 + x^302 + x^300 + x^299 + x^298 + x^297 + x^296 + x^292 + x^290 + x^289 + x^288 + x^286 + x^284 + x^283 + x^282 + x^281 + x^278 + x^277 + x^276 + x^274 + x^272 + x^269 + x^267 + x^263 + x^262 + x^260 + x^259 + x^256 + x^254 + x^253 + x^251 + x^250 + x^249 + x^248 + x^242 + x^241 + x^240 + x^235 + x^234 + x^231 + x^226 + x^225 + x^221 + x^219 + x^214 + x^212 + x^211 + x^209 + x^207 + x^206 + x^205 + x^202 + x^199 + x^198 + x^195 + x^190 + x^189 + x^187 + x^185 + x^182 + x^181 + x^180 + x^179 + x^177 + x^176 + x^175 + x^174 + x^171 + x^169 + x^165 + x^164 + x^163 + x^162 + x^161 + x^160 + x^155 + x^154 + x^153 + x^152 + x^146 + x^145 + x^142 + x^139 + x^137 + x^136 + x^128 + x^127 + x^124 + x^121 + x^120 + x^119 + x^117 + x^114 + x^113 + x^112 + x^110 + x^109 + x^108 + x^104 + x^102 + x^101 + x^99 + x^97 + x^87 + x^85 + x^84 + x^83 + x^82 + x^78 + x^76 + x^73 + x^72 + x^69 + x^64 + x^62 + x^59 + x^52 + x^51 + x^50 + x^49 + x^46 + x^45 + x^44 + x^42 + x^41 + x^40 + x^36 + x^35 + x^33 + x^28 + x^26 + x^25 + x^21 + x^19 + x^13 + x^12 + x^7 + x^6 + 1
+
+35-28-44 377 x^928 + x^898 + x^894 + x^870 + x^859 + x^829 + x^828 + x^825 + x^808 + x^804 + x^800 + x^799 + x^795 + x^794 + x^789 + x^778 + x^774 + x^769 + x^768 + x^759 + x^758 + x^755 + x^754 + x^748 + x^744 + x^739 + x^735 + x^734 + x^727 + x^726 + x^722 + x^718 + x^714 + x^713 + x^709 + x^708 + x^705 + x^704 + x^700 + x^694 + x^691 + x^690 + x^688 + x^687 + x^685 + x^682 + x^681 + x^675 + x^668 + x^665 + x^659 + x^657 + x^656 + x^655 + x^653 + x^652 + x^651 + x^649 + x^647 + x^644 + x^640 + x^638 + x^635 + x^634 + x^631 + x^630 + x^629 + x^628 + x^625 + x^624 + x^619 + x^617 + x^614 + x^613 + x^612 + x^611 + x^610 + x^608 + x^602 + x^599 + x^598 + x^593 + x^592 + x^591 + x^590 + x^586 + x^585 + x^583 + x^582 + x^581 + x^580 + x^579 + x^578 + x^576 + x^575 + x^573 + x^569 + x^566 + x^560 + x^559 + x^558 + x^557 + x^553 + x^552 + x^551 + x^549 + x^542 + x^536 + x^535 + x^533 + x^524 + x^522 + x^521 + x^520 + x^518 + x^517 + x^516 + x^515 + x^512 + x^509 + x^507 + x^506 + x^504 + x^502 + x^500 + x^499 + x^498 + x^497 + x^496 + x^491 + x^489 + x^485 + x^484 + x^483 + x^482 + x^481 + x^480 + x^477 + x^476 + x^474 + x^467 + x^466 + x^465 + x^462 + x^460 + x^459 + x^454 + x^450 + x^447 + x^446 + x^445 + x^443 + x^439 + x^434 + x^433 + x^432 + x^431 + x^430 + x^429 + x^428 + x^427 + x^426 + x^425 + x^424 + x^423 + x^421 + x^419 + x^417 + x^413 + x^412 + x^411 + x^409 + x^408 + x^407 + x^406 + x^405 + x^404 + x^403 + x^401 + x^400 + x^394 + x^389 + x^386 + x^383 + x^381 + x^380 + x^379 + x^378 + x^376 + x^375 + x^373 + x^372 + x^371 + x^369 + x^368 + x^367 + x^366 + x^365 + x^364 + x^362 + x^359 + x^356 + x^349 + x^347 + x^346 + x^345 + x^343 + x^342 + x^341 + x^340 + x^339 + x^338 + x^337 + x^336 + x^335 + x^332 + x^331 + x^330 + x^329 + x^325 + x^324 + x^320 + x^319 + x^318 + x^317 + x^316 + x^315 + x^311 + x^309 + x^307 + x^304 + x^303 + x^301 + x^300 + x^298 + x^297 + x^296 + x^295 + x^294 + x^292 + x^291 + x^289 + x^288 + x^285 + x^283 + x^282 + x^281 + x^274 + x^272 + x^271 + x^269 + x^262 + x^261 + x^260 + x^258 + x^257 + x^252 + x^251 + x^250 + x^249 + x^247 + x^246 + x^244 + x^241 + x^236 + x^234 + x^231 + x^230 + x^227 + x^226 + x^219 + x^218 + x^216 + x^215 + x^213 + x^206 + x^205 + x^204 + x^203 + x^202 + x^201 + x^200 + x^198 + x^196 + x^195 + x^194 + x^193 + x^192 + x^190 + x^186 + x^185 + x^184 + x^182 + x^176 + x^174 + x^167 + x^166 + x^164 + x^163 + x^162 + x^161 + x^159 + x^155 + x^154 + x^153 + x^151 + x^150 + x^146 + x^145 + x^143 + x^140 + x^138 + x^135 + x^131 + x^130 + x^129 + x^125 + x^124 + x^123 + x^118 + x^116 + x^113 + x^110 + x^107 + x^101 + x^100 + x^99 + x^95 + x^94 + x^93 + x^92 + x^91 + x^88 + x^86 + x^82 + x^81 + x^80 + x^79 + x^78 + x^76 + x^75 + x^73 + x^72 + x^71 + x^70 + x^69 + x^68 + x^67 + x^65 + x^63 + x^62 + x^60 + x^58 + x^57 + x^56 + x^55 + x^52 + x^50 + x^48 + x^46 + x^44 + x^42 + x^40 + x^39 + x^38 + x^33 + x^31 + x^30 + x^29 + x^28 + x^27 + x^24 + x^21 + x^16 + x^13 + x^8 + 1
+
+45-7-6 377 x^928 + x^898 + x^897 + x^870 + x^868 + x^854 + x^853 + x^850 + x^836 + x^835 + x^832 + x^821 + x^810 + x^809 + x^808 + x^807 + x^805 + x^803 + x^802 + x^801 + x^798 + x^794 + x^792 + x^791 + x^790 + x^780 + x^778 + x^775 + x^770 + x^767 + x^766 + x^765 + x^764 + x^760 + x^754 + x^750 + x^743 + x^740 + x^737 + x^735 + x^732 + x^726 + x^723 + x^722 + x^721 + x^720 + x^716 + x^714 + x^710 + x^707 + x^706 + x^703 + x^701 + x^700 + x^699 + x^695 + x^691 + x^689 + x^687 + x^686 + x^685 + x^683 + x^680 + x^676 + x^675 + x^672 + x^671 + x^670 + x^668 + x^667 + x^666 + x^665 + x^662 + x^658 + x^655 + x^653 + x^652 + x^648 + x^644 + x^643 + x^642 + x^637 + x^629 + x^628 + x^627 + x^626 + x^624 + x^621 + x^617 + x^616 + x^612 + x^610 + x^609 + x^608 + x^607 + x^603 + x^602 + x^600 + x^595 + x^594 + x^592 + x^591 + x^589 + x^586 + x^585 + x^582 + x^578 + x^576 + x^574 + x^573 + x^571 + x^570 + x^566 + x^563 + x^561 + x^560 + x^558 + x^557 + x^556 + x^555 + x^554 + x^553 + x^552 + x^551 + x^549 + x^548 + x^545 + x^538 + x^533 + x^530 + x^524 + x^520 + x^519 + x^512 + x^509 + x^508 + x^505 + x^503 + x^502 + x^497 + x^495 + x^493 + x^492 + x^486 + x^485 + x^484 + x^482 + x^480 + x^479 + x^477 + x^472 + x^470 + x^469 + x^466 + x^464 + x^462 + x^460 + x^459 + x^458 + x^456 + x^455 + x^454 + x^452 + x^451 + x^448 + x^446 + x^442 + x^441 + x^440 + x^437 + x^431 + x^430 + x^427 + x^426 + x^425 + x^424 + x^423 + x^420 + x^417 + x^415 + x^414 + x^413 + x^412 + x^410 + x^408 + x^406 + x^405 + x^404 + x^403 + x^401 + x^399 + x^395 + x^393 + x^392 + x^384 + x^381 + x^375 + x^370 + x^368 + x^367 + x^364 + x^363 + x^361 + x^360 + x^356 + x^355 + x^354 + x^353 + x^351 + x^349 + x^346 + x^343 + x^341 + x^340 + x^339 + x^335 + x^334 + x^333 + x^330 + x^328 + x^326 + x^325 + x^324 + x^323 + x^319 + x^318 + x^316 + x^315 + x^312 + x^306 + x^303 + x^302 + x^301 + x^299 + x^294 + x^292 + x^291 + x^290 + x^289 + x^283 + x^281 + x^276 + x^275 + x^274 + x^273 + x^271 + x^270 + x^269 + x^268 + x^267 + x^265 + x^264 + x^263 + x^262 + x^261 + x^260 + x^259 + x^258 + x^257 + x^255 + x^254 + x^253 + x^252 + x^250 + x^247 + x^246 + x^245 + x^243 + x^241 + x^240 + x^239 + x^235 + x^228 + x^224 + x^223 + x^220 + x^217 + x^216 + x^215 + x^209 + x^205 + x^204 + x^203 + x^202 + x^197 + x^196 + x^195 + x^192 + x^191 + x^189 + x^187 + x^183 + x^181 + x^180 + x^179 + x^178 + x^173 + x^172 + x^170 + x^165 + x^163 + x^161 + x^159 + x^156 + x^155 + x^153 + x^148 + x^146 + x^141 + x^138 + x^134 + x^133 + x^131 + x^130 + x^127 + x^126 + x^125 + x^123 + x^122 + x^119 + x^117 + x^116 + x^110 + x^109 + x^108 + x^107 + x^106 + x^95 + x^93 + x^92 + x^91 + x^88 + x^87 + x^86 + x^84 + x^82 + x^80 + x^79 + x^76 + x^75 + x^73 + x^72 + x^69 + x^68 + x^64 + x^63 + x^61 + x^59 + x^57 + x^55 + x^54 + x^53 + x^51 + x^49 + x^46 + x^43 + x^41 + x^40 + x^36 + x^35 + x^34 + x^33 + x^31 + x^27 + x^25 + x^23 + x^22 + x^21 + x^19 + x^15 + x^13 + x^12 + x^11 + x^10 + x^9 + x^8 + 1
+
+23-49-24 379 x^928 + x^898 + x^872 + x^870 + x^856 + x^843 + x^842 + x^827 + x^826 + x^824 + x^818 + x^813 + x^812 + x^811 + x^797 + x^796 + x^795 + x^792 + x^789 + x^788 + x^781 + x^780 + x^772 + x^767 + x^765 + x^759 + x^757 + x^752 + x^743 + x^742 + x^741 + x^740 + x^737 + x^735 + x^730 + x^729 + x^728 + x^727 + x^724 + x^722 + x^721 + x^720 + x^718 + x^714 + x^712 + x^710 + x^707 + x^706 + x^704 + x^703 + x^702 + x^700 + x^699 + x^698 + x^696 + x^694 + x^692 + x^691 + x^690 + x^689 + x^688 + x^686 + x^682 + x^681 + x^679 + x^677 + x^675 + x^674 + x^665 + x^663 + x^661 + x^660 + x^657 + x^656 + x^655 + x^651 + x^649 + x^647 + x^646 + x^644 + x^642 + x^640 + x^637 + x^635 + x^633 + x^625 + x^622 + x^620 + x^619 + x^617 + x^616 + x^615 + x^613 + x^612 + x^610 + x^609 + x^607 + x^606 + x^605 + x^601 + x^600 + x^597 + x^596 + x^595 + x^593 + x^592 + x^590 + x^588 + x^584 + x^583 + x^582 + x^579 + x^578 + x^574 + x^571 + x^570 + x^567 + x^566 + x^564 + x^561 + x^560 + x^559 + x^558 + x^554 + x^553 + x^547 + x^543 + x^539 + x^536 + x^535 + x^534 + x^533 + x^529 + x^527 + x^526 + x^525 + x^518 + x^516 + x^515 + x^510 + x^509 + x^508 + x^506 + x^505 + x^504 + x^503 + x^499 + x^498 + x^497 + x^495 + x^493 + x^491 + x^489 + x^488 + x^487 + x^486 + x^485 + x^484 + x^482 + x^479 + x^475 + x^473 + x^472 + x^469 + x^468 + x^464 + x^463 + x^462 + x^459 + x^457 + x^455 + x^453 + x^448 + x^446 + x^444 + x^443 + x^442 + x^438 + x^435 + x^431 + x^429 + x^427 + x^424 + x^423 + x^418 + x^416 + x^414 + x^406 + x^404 + x^403 + x^401 + x^397 + x^396 + x^390 + x^387 + x^384 + x^380 + x^377 + x^375 + x^373 + x^369 + x^366 + x^365 + x^364 + x^358 + x^356 + x^355 + x^354 + x^353 + x^352 + x^348 + x^346 + x^340 + x^338 + x^336 + x^334 + x^329 + x^326 + x^325 + x^322 + x^320 + x^318 + x^312 + x^307 + x^305 + x^302 + x^301 + x^299 + x^297 + x^296 + x^294 + x^293 + x^291 + x^288 + x^287 + x^286 + x^281 + x^278 + x^277 + x^276 + x^274 + x^273 + x^272 + x^271 + x^269 + x^268 + x^267 + x^266 + x^262 + x^261 + x^258 + x^256 + x^255 + x^253 + x^251 + x^250 + x^249 + x^248 + x^247 + x^243 + x^240 + x^237 + x^236 + x^235 + x^233 + x^231 + x^230 + x^223 + x^222 + x^220 + x^218 + x^216 + x^212 + x^211 + x^208 + x^207 + x^205 + x^203 + x^202 + x^201 + x^200 + x^199 + x^198 + x^197 + x^195 + x^193 + x^192 + x^191 + x^187 + x^186 + x^183 + x^181 + x^180 + x^179 + x^176 + x^174 + x^173 + x^169 + x^166 + x^161 + x^159 + x^158 + x^157 + x^156 + x^154 + x^152 + x^151 + x^149 + x^145 + x^144 + x^143 + x^142 + x^141 + x^139 + x^136 + x^135 + x^131 + x^128 + x^127 + x^126 + x^125 + x^123 + x^122 + x^120 + x^118 + x^117 + x^116 + x^114 + x^111 + x^110 + x^108 + x^107 + x^106 + x^103 + x^100 + x^99 + x^97 + x^96 + x^95 + x^93 + x^92 + x^91 + x^90 + x^87 + x^84 + x^81 + x^80 + x^79 + x^78 + x^77 + x^74 + x^73 + x^71 + x^67 + x^64 + x^61 + x^59 + x^58 + x^54 + x^49 + x^47 + x^44 + x^43 + x^41 + x^38 + x^37 + x^33 + x^30 + x^25 + x^17 + x^16 + x^15 + x^14 + x^12 + x^11 + x^8 + x^7 + x^4 + 1
+
+19-18-26 381 x^928 + x^898 + x^870 + x^844 + x^836 + x^832 + x^820 + x^814 + x^808 + x^802 + x^798 + x^794 + x^791 + x^790 + x^784 + x^778 + x^774 + x^770 + x^769 + x^766 + x^764 + x^761 + x^754 + x^748 + x^743 + x^734 + x^732 + x^731 + x^727 + x^726 + x^724 + x^723 + x^721 + x^718 + x^716 + x^715 + x^713 + x^711 + x^709 + x^706 + x^704 + x^703 + x^702 + x^701 + x^700 + x^699 + x^697 + x^696 + x^695 + x^693 + x^692 + x^691 + x^690 + x^685 + x^684 + x^676 + x^674 + x^667 + x^664 + x^663 + x^658 + x^657 + x^656 + x^654 + x^653 + x^651 + x^650 + x^646 + x^645 + x^644 + x^643 + x^639 + x^638 + x^637 + x^633 + x^631 + x^630 + x^627 + x^626 + x^625 + x^624 + x^620 + x^611 + x^610 + x^607 + x^606 + x^604 + x^603 + x^598 + x^597 + x^595 + x^593 + x^590 + x^589 + x^588 + x^586 + x^585 + x^584 + x^582 + x^580 + x^577 + x^575 + x^570 + x^569 + x^568 + x^563 + x^562 + x^561 + x^558 + x^557 + x^556 + x^555 + x^552 + x^551 + x^550 + x^548 + x^547 + x^546 + x^545 + x^542 + x^541 + x^540 + x^539 + x^538 + x^537 + x^535 + x^534 + x^533 + x^531 + x^530 + x^528 + x^523 + x^522 + x^521 + x^517 + x^515 + x^514 + x^512 + x^510 + x^509 + x^508 + x^506 + x^497 + x^494 + x^493 + x^490 + x^487 + x^486 + x^483 + x^481 + x^479 + x^476 + x^474 + x^472 + x^471 + x^467 + x^465 + x^464 + x^463 + x^462 + x^460 + x^456 + x^454 + x^452 + x^450 + x^449 + x^448 + x^447 + x^445 + x^444 + x^440 + x^439 + x^438 + x^437 + x^436 + x^432 + x^431 + x^430 + x^429 + x^428 + x^427 + x^425 + x^424 + x^422 + x^420 + x^419 + x^418 + x^416 + x^414 + x^413 + x^409 + x^407 + x^405 + x^404 + x^402 + x^397 + x^396 + x^392 + x^391 + x^389 + x^386 + x^385 + x^384 + x^381 + x^377 + x^376 + x^373 + x^371 + x^368 + x^366 + x^364 + x^362 + x^360 + x^359 + x^357 + x^355 + x^354 + x^353 + x^352 + x^351 + x^350 + x^349 + x^348 + x^346 + x^343 + x^341 + x^340 + x^338 + x^337 + x^336 + x^334 + x^331 + x^329 + x^328 + x^327 + x^325 + x^324 + x^323 + x^320 + x^316 + x^314 + x^313 + x^311 + x^309 + x^307 + x^306 + x^305 + x^304 + x^302 + x^301 + x^299 + x^297 + x^291 + x^289 + x^288 + x^285 + x^283 + x^282 + x^280 + x^279 + x^272 + x^271 + x^270 + x^269 + x^268 + x^264 + x^263 + x^260 + x^259 + x^257 + x^249 + x^245 + x^242 + x^241 + x^238 + x^234 + x^233 + x^232 + x^230 + x^229 + x^227 + x^226 + x^225 + x^223 + x^221 + x^214 + x^211 + x^210 + x^209 + x^206 + x^205 + x^204 + x^203 + x^201 + x^200 + x^199 + x^197 + x^195 + x^188 + x^186 + x^181 + x^180 + x^177 + x^174 + x^173 + x^171 + x^160 + x^159 + x^158 + x^157 + x^154 + x^148 + x^147 + x^144 + x^142 + x^141 + x^140 + x^139 + x^135 + x^134 + x^129 + x^124 + x^123 + x^120 + x^118 + x^117 + x^116 + x^113 + x^112 + x^110 + x^109 + x^108 + x^107 + x^106 + x^104 + x^103 + x^100 + x^97 + x^95 + x^94 + x^89 + x^86 + x^84 + x^83 + x^79 + x^76 + x^75 + x^73 + x^72 + x^70 + x^67 + x^66 + x^65 + x^63 + x^61 + x^60 + x^59 + x^58 + x^56 + x^53 + x^52 + x^51 + x^50 + x^45 + x^41 + x^40 + x^39 + x^37 + x^36 + x^35 + x^29 + x^26 + x^22 + x^19 + x^18 + x^17 + x^16 + x^13 + x^11 + x^6 + 1
+
+26-3-47 381 x^928 + x^898 + x^878 + x^870 + x^869 + x^860 + x^859 + x^858 + x^850 + x^849 + x^848 + x^840 + x^839 + x^838 + x^830 + x^820 + x^818 + x^810 + x^808 + x^807 + x^798 + x^788 + x^780 + x^778 + x^777 + x^769 + x^767 + x^758 + x^750 + x^740 + x^730 + x^729 + x^728 + x^727 + x^721 + x^720 + x^719 + x^717 + x^716 + x^711 + x^701 + x^700 + x^696 + x^690 + x^689 + x^687 + x^686 + x^681 + x^679 + x^678 + x^676 + x^670 + x^668 + x^667 + x^661 + x^660 + x^657 + x^656 + x^652 + x^650 + x^649 + x^648 + x^647 + x^646 + x^641 + x^638 + x^637 + x^636 + x^632 + x^629 + x^627 + x^625 + x^622 + x^620 + x^619 + x^617 + x^616 + x^610 + x^607 + x^606 + x^602 + x^601 + x^600 + x^599 + x^597 + x^596 + x^592 + x^591 + x^590 + x^588 + x^587 + x^586 + x^585 + x^581 + x^578 + x^576 + x^572 + x^571 + x^570 + x^567 + x^562 + x^560 + x^559 + x^558 + x^557 + x^555 + x^551 + x^550 + x^549 + x^548 + x^546 + x^542 + x^541 + x^536 + x^534 + x^531 + x^530 + x^528 + x^525 + x^523 + x^522 + x^520 + x^519 + x^518 + x^515 + x^514 + x^511 + x^510 + x^509 + x^508 + x^505 + x^504 + x^502 + x^501 + x^500 + x^499 + x^494 + x^493 + x^485 + x^484 + x^481 + x^480 + x^479 + x^477 + x^476 + x^475 + x^473 + x^466 + x^464 + x^462 + x^461 + x^460 + x^458 + x^457 + x^453 + x^447 + x^444 + x^442 + x^441 + x^440 + x^439 + x^438 + x^433 + x^432 + x^429 + x^428 + x^426 + x^425 + x^424 + x^422 + x^421 + x^420 + x^419 + x^418 + x^414 + x^412 + x^411 + x^407 + x^404 + x^401 + x^398 + x^396 + x^393 + x^392 + x^391 + x^389 + x^387 + x^385 + x^383 + x^382 + x^381 + x^380 + x^378 + x^377 + x^374 + x^372 + x^371 + x^368 + x^365 + x^363 + x^362 + x^359 + x^357 + x^356 + x^355 + x^354 + x^350 + x^347 + x^344 + x^343 + x^341 + x^338 + x^337 + x^335 + x^334 + x^333 + x^331 + x^330 + x^329 + x^328 + x^324 + x^322 + x^319 + x^317 + x^314 + x^310 + x^306 + x^300 + x^299 + x^295 + x^294 + x^291 + x^290 + x^289 + x^288 + x^287 + x^286 + x^285 + x^284 + x^282 + x^281 + x^279 + x^278 + x^275 + x^272 + x^271 + x^270 + x^269 + x^266 + x^264 + x^262 + x^260 + x^259 + x^258 + x^254 + x^250 + x^249 + x^247 + x^246 + x^244 + x^243 + x^241 + x^239 + x^237 + x^236 + x^233 + x^232 + x^231 + x^229 + x^227 + x^226 + x^221 + x^220 + x^218 + x^217 + x^216 + x^215 + x^214 + x^213 + x^212 + x^210 + x^209 + x^207 + x^206 + x^202 + x^201 + x^200 + x^199 + x^198 + x^197 + x^195 + x^193 + x^192 + x^189 + x^188 + x^180 + x^176 + x^174 + x^173 + x^172 + x^170 + x^167 + x^165 + x^164 + x^162 + x^160 + x^157 + x^155 + x^153 + x^152 + x^151 + x^149 + x^148 + x^147 + x^144 + x^142 + x^140 + x^139 + x^136 + x^134 + x^133 + x^128 + x^127 + x^124 + x^123 + x^122 + x^121 + x^119 + x^117 + x^115 + x^113 + x^112 + x^110 + x^107 + x^105 + x^101 + x^100 + x^99 + x^98 + x^94 + x^92 + x^91 + x^90 + x^89 + x^87 + x^85 + x^83 + x^81 + x^80 + x^78 + x^77 + x^74 + x^72 + x^71 + x^70 + x^69 + x^65 + x^64 + x^63 + x^62 + x^61 + x^57 + x^56 + x^55 + x^54 + x^53 + x^49 + x^48 + x^42 + x^41 + x^39 + x^36 + x^34 + x^32 + x^31 + x^24 + x^23 + x^21 + x^14 + x^10 + 1
+
+37-33-14 381 x^928 + x^898 + x^874 + x^870 + x^849 + x^845 + x^841 + x^837 + x^836 + x^832 + x^824 + x^820 + x^812 + x^811 + x^804 + x^802 + x^794 + x^789 + x^785 + x^783 + x^781 + x^777 + x^775 + x^771 + x^770 + x^764 + x^761 + x^757 + x^754 + x^751 + x^746 + x^742 + x^741 + x^740 + x^738 + x^736 + x^734 + x^733 + x^731 + x^727 + x^726 + x^725 + x^724 + x^723 + x^721 + x^720 + x^718 + x^717 + x^716 + x^715 + x^714 + x^713 + x^712 + x^711 + x^709 + x^705 + x^704 + x^703 + x^701 + x^695 + x^692 + x^691 + x^690 + x^688 + x^687 + x^686 + x^685 + x^684 + x^682 + x^680 + x^679 + x^676 + x^671 + x^670 + x^665 + x^663 + x^662 + x^661 + x^660 + x^659 + x^653 + x^652 + x^651 + x^650 + x^648 + x^644 + x^643 + x^642 + x^641 + x^637 + x^635 + x^634 + x^631 + x^630 + x^629 + x^627 + x^626 + x^623 + x^622 + x^621 + x^619 + x^617 + x^616 + x^614 + x^613 + x^611 + x^610 + x^603 + x^601 + x^600 + x^599 + x^597 + x^596 + x^594 + x^593 + x^592 + x^591 + x^589 + x^587 + x^585 + x^583 + x^582 + x^579 + x^575 + x^573 + x^571 + x^570 + x^567 + x^566 + x^560 + x^557 + x^556 + x^554 + x^553 + x^552 + x^551 + x^548 + x^546 + x^541 + x^540 + x^538 + x^535 + x^534 + x^531 + x^527 + x^526 + x^525 + x^524 + x^523 + x^517 + x^516 + x^511 + x^509 + x^507 + x^499 + x^496 + x^493 + x^491 + x^490 + x^485 + x^482 + x^481 + x^479 + x^477 + x^475 + x^474 + x^470 + x^469 + x^468 + x^465 + x^462 + x^461 + x^459 + x^458 + x^453 + x^451 + x^446 + x^444 + x^441 + x^436 + x^435 + x^434 + x^432 + x^429 + x^420 + x^419 + x^416 + x^414 + x^411 + x^410 + x^409 + x^407 + x^405 + x^404 + x^403 + x^402 + x^401 + x^397 + x^395 + x^393 + x^392 + x^390 + x^388 + x^384 + x^381 + x^379 + x^376 + x^374 + x^368 + x^367 + x^365 + x^362 + x^361 + x^360 + x^359 + x^357 + x^355 + x^351 + x^349 + x^346 + x^345 + x^344 + x^342 + x^341 + x^339 + x^337 + x^336 + x^335 + x^333 + x^331 + x^326 + x^320 + x^318 + x^316 + x^315 + x^314 + x^313 + x^312 + x^311 + x^310 + x^309 + x^306 + x^304 + x^303 + x^302 + x^301 + x^300 + x^298 + x^297 + x^296 + x^295 + x^294 + x^293 + x^292 + x^290 + x^289 + x^288 + x^284 + x^283 + x^282 + x^278 + x^277 + x^274 + x^273 + x^272 + x^270 + x^266 + x^265 + x^261 + x^258 + x^256 + x^255 + x^254 + x^248 + x^246 + x^245 + x^239 + x^237 + x^234 + x^230 + x^229 + x^225 + x^224 + x^222 + x^221 + x^217 + x^215 + x^214 + x^213 + x^212 + x^208 + x^206 + x^204 + x^203 + x^202 + x^201 + x^199 + x^198 + x^195 + x^194 + x^191 + x^189 + x^188 + x^186 + x^184 + x^182 + x^181 + x^179 + x^177 + x^176 + x^175 + x^171 + x^169 + x^168 + x^162 + x^161 + x^159 + x^157 + x^156 + x^151 + x^150 + x^148 + x^146 + x^145 + x^142 + x^140 + x^139 + x^138 + x^135 + x^134 + x^131 + x^129 + x^126 + x^124 + x^119 + x^118 + x^117 + x^116 + x^108 + x^106 + x^105 + x^103 + x^99 + x^97 + x^93 + x^92 + x^91 + x^90 + x^87 + x^86 + x^84 + x^83 + x^78 + x^76 + x^73 + x^66 + x^65 + x^64 + x^62 + x^61 + x^58 + x^53 + x^52 + x^51 + x^48 + x^47 + x^46 + x^45 + x^44 + x^42 + x^41 + x^38 + x^34 + x^29 + x^28 + x^26 + x^23 + x^16 + x^8 + x^6 + 1
+
+36-46-33 383 x^928 + x^898 + x^870 + x^857 + x^828 + x^827 + x^816 + x^815 + x^809 + x^808 + x^804 + x^797 + x^792 + x^786 + x^785 + x^780 + x^778 + x^775 + x^773 + x^768 + x^767 + x^762 + x^755 + x^751 + x^750 + x^737 + x^734 + x^732 + x^728 + x^727 + x^725 + x^722 + x^716 + x^715 + x^714 + x^710 + x^708 + x^707 + x^704 + x^703 + x^701 + x^698 + x^697 + x^695 + x^689 + x^688 + x^685 + x^681 + x^680 + x^679 + x^678 + x^677 + x^674 + x^665 + x^658 + x^656 + x^651 + x^650 + x^649 + x^648 + x^647 + x^640 + x^637 + x^635 + x^634 + x^633 + x^631 + x^628 + x^626 + x^624 + x^620 + x^619 + x^617 + x^616 + x^610 + x^607 + x^605 + x^603 + x^602 + x^601 + x^598 + x^591 + x^590 + x^587 + x^584 + x^582 + x^580 + x^577 + x^574 + x^573 + x^571 + x^567 + x^563 + x^562 + x^560 + x^559 + x^558 + x^557 + x^555 + x^553 + x^551 + x^550 + x^549 + x^548 + x^547 + x^544 + x^542 + x^540 + x^539 + x^538 + x^537 + x^535 + x^534 + x^533 + x^531 + x^528 + x^527 + x^525 + x^519 + x^517 + x^516 + x^515 + x^510 + x^509 + x^508 + x^507 + x^505 + x^500 + x^499 + x^496 + x^495 + x^494 + x^493 + x^489 + x^488 + x^487 + x^486 + x^485 + x^484 + x^482 + x^480 + x^479 + x^477 + x^475 + x^473 + x^470 + x^468 + x^467 + x^466 + x^465 + x^463 + x^461 + x^460 + x^455 + x^451 + x^450 + x^448 + x^444 + x^443 + x^442 + x^439 + x^438 + x^437 + x^435 + x^434 + x^433 + x^427 + x^426 + x^425 + x^423 + x^421 + x^420 + x^419 + x^418 + x^416 + x^413 + x^411 + x^410 + x^408 + x^407 + x^404 + x^402 + x^400 + x^399 + x^398 + x^397 + x^396 + x^395 + x^394 + x^393 + x^387 + x^386 + x^382 + x^379 + x^378 + x^377 + x^376 + x^375 + x^374 + x^372 + x^371 + x^370 + x^369 + x^366 + x^364 + x^363 + x^362 + x^361 + x^359 + x^358 + x^357 + x^356 + x^355 + x^354 + x^352 + x^347 + x^346 + x^345 + x^344 + x^343 + x^341 + x^340 + x^336 + x^335 + x^332 + x^330 + x^329 + x^328 + x^325 + x^324 + x^323 + x^322 + x^321 + x^320 + x^318 + x^316 + x^315 + x^312 + x^302 + x^301 + x^298 + x^295 + x^288 + x^287 + x^286 + x^285 + x^280 + x^279 + x^278 + x^275 + x^273 + x^272 + x^270 + x^267 + x^266 + x^264 + x^261 + x^259 + x^258 + x^255 + x^253 + x^252 + x^250 + x^249 + x^248 + x^247 + x^244 + x^243 + x^242 + x^241 + x^240 + x^237 + x^236 + x^235 + x^231 + x^230 + x^228 + x^225 + x^224 + x^222 + x^220 + x^218 + x^217 + x^216 + x^215 + x^212 + x^211 + x^205 + x^204 + x^203 + x^201 + x^196 + x^194 + x^193 + x^192 + x^191 + x^190 + x^187 + x^186 + x^184 + x^183 + x^182 + x^180 + x^179 + x^178 + x^174 + x^173 + x^172 + x^164 + x^163 + x^162 + x^161 + x^160 + x^159 + x^158 + x^156 + x^154 + x^153 + x^150 + x^149 + x^148 + x^146 + x^144 + x^143 + x^140 + x^134 + x^131 + x^127 + x^125 + x^124 + x^120 + x^119 + x^118 + x^116 + x^115 + x^113 + x^112 + x^109 + x^108 + x^107 + x^106 + x^103 + x^100 + x^99 + x^97 + x^96 + x^95 + x^94 + x^91 + x^87 + x^86 + x^83 + x^81 + x^80 + x^78 + x^77 + x^75 + x^74 + x^72 + x^71 + x^69 + x^68 + x^66 + x^64 + x^63 + x^59 + x^57 + x^55 + x^53 + x^48 + x^47 + x^44 + x^42 + x^41 + x^40 + x^37 + x^28 + x^27 + x^26 + x^20 + x^18 + x^16 + 1
+
+40-1-13 383 x^928 + x^898 + x^870 + x^864 + x^861 + x^858 + x^855 + x^852 + x^837 + x^834 + x^825 + x^808 + x^804 + x^801 + x^798 + x^792 + x^791 + x^788 + x^785 + x^778 + x^777 + x^774 + x^773 + x^758 + x^755 + x^752 + x^743 + x^740 + x^738 + x^737 + x^735 + x^734 + x^725 + x^721 + x^719 + x^718 + x^716 + x^715 + x^713 + x^710 + x^707 + x^705 + x^704 + x^703 + x^698 + x^695 + x^691 + x^689 + x^686 + x^682 + x^680 + x^678 + x^677 + x^676 + x^674 + x^673 + x^671 + x^668 + x^667 + x^664 + x^662 + x^661 + x^659 + x^658 + x^656 + x^652 + x^650 + x^648 + x^647 + x^645 + x^644 + x^643 + x^642 + x^641 + x^640 + x^636 + x^633 + x^630 + x^629 + x^628 + x^627 + x^626 + x^623 + x^621 + x^618 + x^615 + x^612 + x^608 + x^607 + x^605 + x^603 + x^602 + x^600 + x^598 + x^596 + x^594 + x^593 + x^588 + x^583 + x^582 + x^579 + x^574 + x^573 + x^569 + x^568 + x^567 + x^565 + x^564 + x^562 + x^558 + x^557 + x^556 + x^555 + x^552 + x^551 + x^548 + x^545 + x^544 + x^542 + x^540 + x^537 + x^535 + x^534 + x^533 + x^532 + x^531 + x^530 + x^526 + x^525 + x^524 + x^523 + x^521 + x^520 + x^519 + x^518 + x^517 + x^515 + x^513 + x^509 + x^508 + x^507 + x^502 + x^500 + x^499 + x^496 + x^495 + x^491 + x^489 + x^488 + x^485 + x^482 + x^480 + x^478 + x^476 + x^474 + x^471 + x^468 + x^466 + x^463 + x^462 + x^461 + x^460 + x^459 + x^457 + x^456 + x^453 + x^451 + x^450 + x^447 + x^443 + x^440 + x^439 + x^436 + x^435 + x^434 + x^433 + x^432 + x^426 + x^425 + x^424 + x^422 + x^421 + x^419 + x^417 + x^416 + x^410 + x^409 + x^408 + x^406 + x^405 + x^402 + x^401 + x^399 + x^398 + x^396 + x^389 + x^388 + x^387 + x^385 + x^383 + x^382 + x^381 + x^379 + x^378 + x^377 + x^376 + x^375 + x^373 + x^371 + x^368 + x^366 + x^364 + x^363 + x^360 + x^358 + x^356 + x^355 + x^353 + x^350 + x^349 + x^346 + x^345 + x^344 + x^340 + x^339 + x^338 + x^332 + x^331 + x^329 + x^326 + x^324 + x^323 + x^321 + x^316 + x^315 + x^313 + x^312 + x^310 + x^309 + x^307 + x^306 + x^305 + x^303 + x^302 + x^300 + x^299 + x^296 + x^294 + x^293 + x^292 + x^289 + x^288 + x^286 + x^285 + x^283 + x^282 + x^279 + x^277 + x^274 + x^273 + x^272 + x^270 + x^269 + x^268 + x^267 + x^265 + x^264 + x^261 + x^259 + x^256 + x^251 + x^249 + x^245 + x^241 + x^240 + x^239 + x^238 + x^236 + x^235 + x^234 + x^232 + x^230 + x^226 + x^222 + x^220 + x^217 + x^215 + x^211 + x^210 + x^208 + x^203 + x^200 + x^199 + x^197 + x^195 + x^194 + x^193 + x^190 + x^187 + x^186 + x^184 + x^183 + x^182 + x^178 + x^176 + x^174 + x^169 + x^168 + x^167 + x^164 + x^162 + x^161 + x^160 + x^159 + x^158 + x^154 + x^153 + x^147 + x^142 + x^138 + x^137 + x^129 + x^128 + x^127 + x^126 + x^124 + x^123 + x^121 + x^119 + x^116 + x^115 + x^112 + x^110 + x^109 + x^107 + x^106 + x^103 + x^101 + x^100 + x^99 + x^98 + x^95 + x^94 + x^93 + x^92 + x^91 + x^89 + x^85 + x^83 + x^80 + x^79 + x^77 + x^75 + x^73 + x^72 + x^71 + x^63 + x^61 + x^59 + x^58 + x^57 + x^55 + x^51 + x^48 + x^47 + x^46 + x^45 + x^39 + x^38 + x^37 + x^34 + x^33 + x^32 + x^29 + x^28 + x^25 + x^23 + x^20 + x^17 + x^11 + x^10 + x^7 + 1
+
+46-53-31 383 x^928 + x^898 + x^896 + x^875 + x^870 + x^864 + x^856 + x^854 + x^848 + x^845 + x^834 + x^833 + x^827 + x^817 + x^814 + x^812 + x^808 + x^807 + x^806 + x^804 + x^801 + x^796 + x^794 + x^791 + x^787 + x^786 + x^778 + x^777 + x^776 + x^774 + x^773 + x^772 + x^770 + x^767 + x^765 + x^764 + x^761 + x^757 + x^756 + x^754 + x^746 + x^745 + x^744 + x^741 + x^735 + x^734 + x^730 + x^727 + x^725 + x^723 + x^716 + x^715 + x^714 + x^713 + x^712 + x^705 + x^704 + x^703 + x^702 + x^697 + x^696 + x^692 + x^688 + x^685 + x^684 + x^683 + x^682 + x^681 + x^674 + x^673 + x^672 + x^671 + x^670 + x^667 + x^666 + x^664 + x^663 + x^661 + x^660 + x^658 + x^655 + x^654 + x^653 + x^652 + x^651 + x^650 + x^645 + x^639 + x^637 + x^634 + x^631 + x^630 + x^626 + x^625 + x^624 + x^621 + x^620 + x^618 + x^615 + x^610 + x^608 + x^607 + x^606 + x^605 + x^604 + x^602 + x^596 + x^594 + x^588 + x^586 + x^584 + x^582 + x^580 + x^578 + x^574 + x^573 + x^571 + x^570 + x^568 + x^567 + x^560 + x^553 + x^551 + x^548 + x^545 + x^543 + x^542 + x^538 + x^537 + x^536 + x^534 + x^533 + x^532 + x^530 + x^529 + x^528 + x^523 + x^521 + x^513 + x^512 + x^509 + x^508 + x^505 + x^502 + x^491 + x^490 + x^489 + x^487 + x^481 + x^479 + x^468 + x^466 + x^465 + x^464 + x^461 + x^460 + x^459 + x^457 + x^455 + x^454 + x^453 + x^451 + x^450 + x^444 + x^442 + x^440 + x^438 + x^431 + x^430 + x^429 + x^428 + x^427 + x^426 + x^425 + x^424 + x^423 + x^422 + x^421 + x^416 + x^415 + x^414 + x^412 + x^410 + x^409 + x^408 + x^402 + x^400 + x^399 + x^398 + x^396 + x^395 + x^393 + x^392 + x^391 + x^388 + x^385 + x^384 + x^381 + x^380 + x^379 + x^377 + x^376 + x^374 + x^373 + x^371 + x^368 + x^366 + x^365 + x^364 + x^362 + x^358 + x^357 + x^356 + x^351 + x^348 + x^345 + x^342 + x^339 + x^338 + x^336 + x^334 + x^331 + x^330 + x^328 + x^327 + x^323 + x^320 + x^319 + x^318 + x^312 + x^311 + x^307 + x^306 + x^305 + x^304 + x^303 + x^302 + x^301 + x^298 + x^295 + x^293 + x^292 + x^291 + x^287 + x^286 + x^285 + x^284 + x^283 + x^280 + x^279 + x^278 + x^276 + x^275 + x^271 + x^269 + x^266 + x^264 + x^263 + x^262 + x^260 + x^257 + x^255 + x^254 + x^253 + x^252 + x^251 + x^250 + x^248 + x^242 + x^241 + x^238 + x^234 + x^231 + x^225 + x^223 + x^222 + x^221 + x^220 + x^219 + x^218 + x^217 + x^214 + x^211 + x^209 + x^208 + x^207 + x^206 + x^204 + x^200 + x^196 + x^194 + x^191 + x^188 + x^186 + x^185 + x^182 + x^181 + x^178 + x^176 + x^175 + x^174 + x^169 + x^168 + x^166 + x^164 + x^160 + x^159 + x^158 + x^155 + x^154 + x^153 + x^151 + x^149 + x^147 + x^145 + x^143 + x^138 + x^135 + x^132 + x^131 + x^130 + x^128 + x^127 + x^124 + x^123 + x^121 + x^120 + x^119 + x^117 + x^116 + x^115 + x^112 + x^111 + x^110 + x^109 + x^106 + x^105 + x^104 + x^101 + x^99 + x^96 + x^87 + x^85 + x^84 + x^83 + x^81 + x^80 + x^79 + x^75 + x^73 + x^70 + x^69 + x^68 + x^67 + x^66 + x^63 + x^61 + x^60 + x^58 + x^56 + x^54 + x^53 + x^51 + x^49 + x^47 + x^45 + x^44 + x^43 + x^42 + x^41 + x^39 + x^36 + x^34 + x^31 + x^30 + x^29 + x^27 + x^24 + x^22 + x^14 + x^13 + x^12 + 1
+
+1-41-10 385 x^928 + x^898 + x^879 + x^870 + x^855 + x^850 + x^849 + x^831 + x^826 + x^825 + x^819 + x^816 + x^808 + x^807 + x^802 + x^801 + x^800 + x^796 + x^792 + x^789 + x^777 + x^776 + x^768 + x^766 + x^762 + x^758 + x^756 + x^754 + x^748 + x^746 + x^744 + x^742 + x^738 + x^736 + x^735 + x^733 + x^729 + x^728 + x^725 + x^720 + x^716 + x^710 + x^705 + x^704 + x^701 + x^700 + x^696 + x^688 + x^686 + x^682 + x^681 + x^680 + x^679 + x^678 + x^677 + x^676 + x^675 + x^674 + x^671 + x^670 + x^669 + x^666 + x^665 + x^661 + x^660 + x^658 + x^655 + x^653 + x^652 + x^649 + x^648 + x^645 + x^640 + x^637 + x^633 + x^629 + x^628 + x^624 + x^623 + x^621 + x^620 + x^619 + x^618 + x^617 + x^616 + x^610 + x^609 + x^607 + x^605 + x^604 + x^601 + x^600 + x^595 + x^591 + x^589 + x^586 + x^584 + x^581 + x^579 + x^575 + x^573 + x^569 + x^568 + x^561 + x^559 + x^556 + x^555 + x^546 + x^542 + x^540 + x^538 + x^537 + x^534 + x^533 + x^531 + x^530 + x^528 + x^526 + x^525 + x^523 + x^518 + x^517 + x^513 + x^511 + x^507 + x^497 + x^496 + x^494 + x^493 + x^491 + x^489 + x^488 + x^487 + x^485 + x^484 + x^483 + x^482 + x^479 + x^477 + x^476 + x^475 + x^474 + x^471 + x^470 + x^468 + x^466 + x^465 + x^464 + x^463 + x^462 + x^461 + x^459 + x^458 + x^454 + x^453 + x^452 + x^451 + x^449 + x^447 + x^446 + x^442 + x^441 + x^440 + x^434 + x^431 + x^428 + x^427 + x^426 + x^424 + x^421 + x^418 + x^417 + x^416 + x^414 + x^412 + x^411 + x^408 + x^405 + x^401 + x^400 + x^399 + x^398 + x^395 + x^394 + x^393 + x^392 + x^391 + x^388 + x^386 + x^385 + x^381 + x^380 + x^379 + x^378 + x^376 + x^375 + x^373 + x^372 + x^370 + x^367 + x^366 + x^365 + x^364 + x^361 + x^360 + x^359 + x^356 + x^354 + x^353 + x^349 + x^348 + x^346 + x^344 + x^340 + x^335 + x^332 + x^330 + x^325 + x^324 + x^321 + x^318 + x^316 + x^315 + x^314 + x^312 + x^310 + x^309 + x^307 + x^304 + x^301 + x^300 + x^299 + x^298 + x^297 + x^295 + x^291 + x^290 + x^289 + x^288 + x^287 + x^286 + x^285 + x^284 + x^281 + x^280 + x^276 + x^274 + x^269 + x^268 + x^267 + x^265 + x^264 + x^261 + x^260 + x^259 + x^257 + x^256 + x^255 + x^254 + x^251 + x^250 + x^245 + x^242 + x^239 + x^236 + x^233 + x^229 + x^227 + x^225 + x^223 + x^222 + x^219 + x^217 + x^214 + x^213 + x^210 + x^207 + x^205 + x^204 + x^203 + x^202 + x^201 + x^200 + x^199 + x^198 + x^197 + x^196 + x^195 + x^192 + x^190 + x^189 + x^181 + x^180 + x^178 + x^177 + x^175 + x^174 + x^173 + x^172 + x^169 + x^168 + x^165 + x^162 + x^159 + x^158 + x^154 + x^153 + x^152 + x^150 + x^148 + x^144 + x^143 + x^142 + x^141 + x^139 + x^138 + x^137 + x^133 + x^132 + x^128 + x^127 + x^124 + x^123 + x^122 + x^121 + x^118 + x^116 + x^106 + x^102 + x^101 + x^98 + x^96 + x^92 + x^91 + x^90 + x^89 + x^88 + x^86 + x^85 + x^84 + x^83 + x^81 + x^80 + x^78 + x^76 + x^73 + x^72 + x^71 + x^69 + x^67 + x^66 + x^65 + x^63 + x^58 + x^54 + x^53 + x^52 + x^51 + x^50 + x^47 + x^46 + x^45 + x^44 + x^43 + x^40 + x^39 + x^38 + x^37 + x^34 + x^33 + x^32 + x^30 + x^25 + x^23 + x^21 + x^20 + x^19 + x^17 + x^16 + x^15 + x^12 + x^9 + x^8 + x^4 + 1
+
+11-33-48 385 x^928 + x^898 + x^882 + x^870 + x^860 + x^853 + x^844 + x^835 + x^828 + x^827 + x^822 + x^819 + x^815 + x^811 + x^808 + x^806 + x^805 + x^794 + x^793 + x^786 + x^784 + x^781 + x^778 + x^773 + x^772 + x^769 + x^765 + x^762 + x^760 + x^759 + x^756 + x^755 + x^748 + x^746 + x^743 + x^739 + x^735 + x^734 + x^733 + x^731 + x^729 + x^727 + x^723 + x^718 + x^713 + x^709 + x^707 + x^706 + x^704 + x^702 + x^699 + x^696 + x^693 + x^692 + x^691 + x^686 + x^685 + x^683 + x^679 + x^677 + x^673 + x^670 + x^669 + x^666 + x^657 + x^656 + x^655 + x^649 + x^648 + x^646 + x^641 + x^639 + x^638 + x^635 + x^633 + x^632 + x^630 + x^628 + x^627 + x^625 + x^620 + x^616 + x^614 + x^613 + x^611 + x^610 + x^609 + x^607 + x^606 + x^600 + x^595 + x^593 + x^592 + x^589 + x^587 + x^584 + x^581 + x^580 + x^579 + x^577 + x^572 + x^570 + x^568 + x^567 + x^566 + x^564 + x^560 + x^559 + x^557 + x^555 + x^554 + x^552 + x^551 + x^546 + x^542 + x^538 + x^535 + x^534 + x^533 + x^531 + x^530 + x^528 + x^526 + x^524 + x^523 + x^520 + x^519 + x^511 + x^509 + x^506 + x^504 + x^503 + x^502 + x^501 + x^500 + x^499 + x^498 + x^495 + x^494 + x^491 + x^490 + x^489 + x^488 + x^487 + x^485 + x^484 + x^479 + x^475 + x^473 + x^466 + x^459 + x^457 + x^456 + x^455 + x^454 + x^451 + x^450 + x^449 + x^448 + x^447 + x^446 + x^444 + x^443 + x^442 + x^441 + x^440 + x^433 + x^432 + x^430 + x^428 + x^427 + x^425 + x^423 + x^419 + x^418 + x^417 + x^414 + x^409 + x^408 + x^406 + x^404 + x^402 + x^400 + x^398 + x^397 + x^396 + x^395 + x^393 + x^389 + x^388 + x^385 + x^379 + x^377 + x^375 + x^374 + x^369 + x^365 + x^364 + x^363 + x^362 + x^361 + x^359 + x^355 + x^354 + x^350 + x^348 + x^345 + x^344 + x^339 + x^336 + x^335 + x^334 + x^333 + x^332 + x^331 + x^330 + x^329 + x^328 + x^326 + x^319 + x^318 + x^316 + x^315 + x^314 + x^313 + x^312 + x^309 + x^308 + x^304 + x^303 + x^302 + x^299 + x^298 + x^297 + x^295 + x^294 + x^293 + x^292 + x^291 + x^290 + x^288 + x^285 + x^282 + x^281 + x^279 + x^277 + x^276 + x^274 + x^272 + x^271 + x^270 + x^269 + x^268 + x^267 + x^265 + x^264 + x^262 + x^261 + x^260 + x^257 + x^253 + x^251 + x^250 + x^247 + x^246 + x^243 + x^242 + x^241 + x^240 + x^237 + x^236 + x^233 + x^232 + x^230 + x^229 + x^226 + x^223 + x^222 + x^221 + x^220 + x^217 + x^215 + x^211 + x^210 + x^209 + x^207 + x^206 + x^205 + x^203 + x^201 + x^200 + x^198 + x^196 + x^195 + x^193 + x^189 + x^188 + x^186 + x^184 + x^180 + x^179 + x^177 + x^176 + x^170 + x^168 + x^163 + x^162 + x^160 + x^159 + x^158 + x^157 + x^156 + x^154 + x^151 + x^149 + x^148 + x^145 + x^144 + x^141 + x^140 + x^138 + x^137 + x^136 + x^135 + x^134 + x^133 + x^132 + x^131 + x^130 + x^129 + x^125 + x^124 + x^121 + x^117 + x^116 + x^110 + x^109 + x^108 + x^106 + x^104 + x^103 + x^102 + x^99 + x^98 + x^96 + x^95 + x^93 + x^92 + x^90 + x^89 + x^88 + x^85 + x^83 + x^81 + x^78 + x^76 + x^72 + x^71 + x^70 + x^68 + x^67 + x^66 + x^62 + x^59 + x^58 + x^56 + x^51 + x^48 + x^46 + x^45 + x^44 + x^43 + x^41 + x^40 + x^38 + x^37 + x^35 + x^34 + x^33 + x^32 + x^27 + x^22 + x^19 + x^13 + 1
+
+19-8-4 387 x^928 + x^898 + x^870 + x^844 + x^838 + x^834 + x^828 + x^818 + x^814 + x^806 + x^804 + x^803 + x^797 + x^782 + x^778 + x^776 + x^774 + x^773 + x^772 + x^768 + x^767 + x^764 + x^752 + x^751 + x^745 + x^744 + x^742 + x^740 + x^738 + x^736 + x^733 + x^730 + x^721 + x^718 + x^715 + x^714 + x^710 + x^707 + x^704 + x^702 + x^700 + x^699 + x^698 + x^697 + x^694 + x^692 + x^691 + x^688 + x^686 + x^685 + x^682 + x^676 + x^673 + x^671 + x^670 + x^669 + x^666 + x^662 + x^661 + x^658 + x^656 + x^655 + x^654 + x^652 + x^649 + x^648 + x^647 + x^645 + x^644 + x^642 + x^640 + x^638 + x^636 + x^632 + x^631 + x^630 + x^623 + x^622 + x^621 + x^616 + x^614 + x^612 + x^611 + x^606 + x^605 + x^602 + x^598 + x^597 + x^594 + x^593 + x^591 + x^590 + x^588 + x^587 + x^586 + x^584 + x^582 + x^581 + x^579 + x^577 + x^574 + x^572 + x^571 + x^568 + x^567 + x^566 + x^563 + x^562 + x^560 + x^559 + x^558 + x^556 + x^553 + x^551 + x^550 + x^549 + x^546 + x^545 + x^543 + x^542 + x^538 + x^536 + x^535 + x^534 + x^533 + x^531 + x^530 + x^529 + x^527 + x^525 + x^524 + x^522 + x^521 + x^520 + x^518 + x^517 + x^513 + x^512 + x^510 + x^509 + x^507 + x^506 + x^505 + x^504 + x^501 + x^500 + x^499 + x^493 + x^492 + x^491 + x^489 + x^488 + x^486 + x^485 + x^484 + x^480 + x^479 + x^478 + x^477 + x^474 + x^473 + x^472 + x^470 + x^468 + x^464 + x^462 + x^458 + x^456 + x^455 + x^454 + x^453 + x^452 + x^451 + x^450 + x^449 + x^447 + x^446 + x^445 + x^441 + x^439 + x^438 + x^437 + x^436 + x^434 + x^433 + x^432 + x^431 + x^429 + x^426 + x^425 + x^424 + x^423 + x^421 + x^419 + x^418 + x^416 + x^414 + x^412 + x^410 + x^407 + x^405 + x^404 + x^398 + x^397 + x^394 + x^393 + x^392 + x^391 + x^387 + x^386 + x^385 + x^382 + x^381 + x^379 + x^377 + x^375 + x^374 + x^369 + x^368 + x^365 + x^364 + x^363 + x^362 + x^361 + x^359 + x^358 + x^356 + x^355 + x^352 + x^351 + x^350 + x^345 + x^343 + x^341 + x^340 + x^338 + x^333 + x^331 + x^330 + x^329 + x^328 + x^327 + x^325 + x^324 + x^323 + x^322 + x^318 + x^315 + x^312 + x^310 + x^308 + x^305 + x^304 + x^303 + x^299 + x^296 + x^295 + x^294 + x^290 + x^289 + x^288 + x^285 + x^284 + x^281 + x^280 + x^275 + x^274 + x^273 + x^271 + x^268 + x^266 + x^264 + x^263 + x^262 + x^261 + x^260 + x^258 + x^256 + x^255 + x^254 + x^251 + x^245 + x^240 + x^237 + x^236 + x^231 + x^230 + x^229 + x^228 + x^223 + x^221 + x^219 + x^218 + x^215 + x^210 + x^208 + x^207 + x^206 + x^204 + x^202 + x^197 + x^196 + x^195 + x^194 + x^193 + x^192 + x^187 + x^186 + x^184 + x^183 + x^182 + x^180 + x^178 + x^177 + x^175 + x^174 + x^169 + x^168 + x^166 + x^165 + x^163 + x^160 + x^159 + x^156 + x^155 + x^152 + x^150 + x^145 + x^140 + x^138 + x^137 + x^136 + x^135 + x^134 + x^133 + x^131 + x^128 + x^127 + x^126 + x^122 + x^121 + x^118 + x^117 + x^114 + x^109 + x^105 + x^102 + x^101 + x^100 + x^99 + x^96 + x^94 + x^93 + x^91 + x^90 + x^89 + x^88 + x^87 + x^81 + x^79 + x^76 + x^74 + x^71 + x^70 + x^64 + x^63 + x^61 + x^59 + x^56 + x^55 + x^49 + x^47 + x^46 + x^44 + x^43 + x^40 + x^35 + x^34 + x^33 + x^31 + x^28 + x^26 + x^23 + x^21 + x^18 + x^13 + 1
+
+36-23-57 387 x^928 + x^898 + x^897 + x^890 + x^870 + x^860 + x^859 + x^854 + x^853 + x^846 + x^836 + x^835 + x^824 + x^816 + x^810 + x^809 + x^808 + x^804 + x^802 + x^792 + x^791 + x^785 + x^784 + x^780 + x^778 + x^777 + x^776 + x^775 + x^774 + x^773 + x^765 + x^755 + x^753 + x^748 + x^747 + x^744 + x^742 + x^733 + x^732 + x^728 + x^725 + x^724 + x^721 + x^716 + x^714 + x^713 + x^703 + x^701 + x^700 + x^699 + x^697 + x^693 + x^692 + x^691 + x^690 + x^689 + x^680 + x^679 + x^675 + x^673 + x^672 + x^666 + x^663 + x^662 + x^661 + x^658 + x^656 + x^655 + x^653 + x^652 + x^649 + x^647 + x^645 + x^642 + x^641 + x^639 + x^638 + x^637 + x^633 + x^630 + x^629 + x^625 + x^620 + x^619 + x^614 + x^613 + x^612 + x^611 + x^610 + x^606 + x^605 + x^604 + x^603 + x^601 + x^600 + x^599 + x^597 + x^593 + x^591 + x^589 + x^587 + x^586 + x^584 + x^583 + x^581 + x^579 + x^576 + x^572 + x^571 + x^566 + x^565 + x^563 + x^559 + x^554 + x^552 + x^549 + x^548 + x^545 + x^544 + x^543 + x^540 + x^535 + x^534 + x^533 + x^531 + x^529 + x^524 + x^522 + x^517 + x^516 + x^513 + x^512 + x^511 + x^509 + x^507 + x^506 + x^505 + x^503 + x^502 + x^496 + x^495 + x^494 + x^492 + x^491 + x^490 + x^489 + x^487 + x^478 + x^477 + x^475 + x^474 + x^472 + x^471 + x^470 + x^469 + x^466 + x^463 + x^461 + x^459 + x^458 + x^457 + x^456 + x^455 + x^454 + x^453 + x^452 + x^448 + x^447 + x^446 + x^444 + x^442 + x^440 + x^439 + x^438 + x^437 + x^434 + x^431 + x^428 + x^427 + x^426 + x^425 + x^424 + x^423 + x^414 + x^409 + x^408 + x^407 + x^403 + x^401 + x^400 + x^396 + x^395 + x^394 + x^390 + x^389 + x^388 + x^387 + x^386 + x^379 + x^378 + x^377 + x^375 + x^374 + x^370 + x^366 + x^360 + x^359 + x^358 + x^357 + x^356 + x^355 + x^353 + x^351 + x^350 + x^349 + x^348 + x^347 + x^345 + x^343 + x^342 + x^341 + x^340 + x^339 + x^338 + x^336 + x^335 + x^330 + x^329 + x^327 + x^325 + x^324 + x^323 + x^322 + x^319 + x^318 + x^315 + x^313 + x^309 + x^305 + x^304 + x^302 + x^300 + x^298 + x^296 + x^291 + x^289 + x^286 + x^285 + x^284 + x^283 + x^282 + x^281 + x^279 + x^275 + x^274 + x^272 + x^271 + x^265 + x^264 + x^263 + x^259 + x^258 + x^257 + x^253 + x^252 + x^249 + x^246 + x^245 + x^244 + x^242 + x^239 + x^233 + x^232 + x^231 + x^230 + x^229 + x^228 + x^221 + x^217 + x^216 + x^212 + x^211 + x^209 + x^208 + x^205 + x^202 + x^201 + x^200 + x^199 + x^198 + x^197 + x^196 + x^195 + x^193 + x^192 + x^191 + x^189 + x^187 + x^186 + x^183 + x^182 + x^181 + x^177 + x^174 + x^172 + x^170 + x^166 + x^164 + x^163 + x^162 + x^160 + x^156 + x^155 + x^152 + x^151 + x^147 + x^144 + x^143 + x^142 + x^141 + x^138 + x^137 + x^135 + x^133 + x^130 + x^129 + x^126 + x^125 + x^123 + x^121 + x^120 + x^117 + x^114 + x^112 + x^107 + x^106 + x^103 + x^102 + x^100 + x^98 + x^95 + x^94 + x^93 + x^91 + x^90 + x^89 + x^87 + x^85 + x^84 + x^82 + x^81 + x^80 + x^78 + x^76 + x^75 + x^71 + x^70 + x^69 + x^68 + x^66 + x^65 + x^61 + x^60 + x^59 + x^58 + x^56 + x^55 + x^51 + x^49 + x^48 + x^46 + x^44 + x^42 + x^36 + x^34 + x^33 + x^32 + x^29 + x^24 + x^23 + x^20 + x^14 + x^13 + x^12 + x^11 + x^10 + 1
+
+44-4-19 389 x^928 + x^898 + x^874 + x^870 + x^836 + x^832 + x^824 + x^812 + x^809 + x^802 + x^794 + x^793 + x^789 + x^785 + x^781 + x^766 + x^764 + x^763 + x^762 + x^759 + x^758 + x^751 + x^750 + x^746 + x^744 + x^742 + x^738 + x^734 + x^733 + x^732 + x^729 + x^721 + x^719 + x^716 + x^715 + x^714 + x^713 + x^710 + x^706 + x^705 + x^703 + x^702 + x^700 + x^699 + x^698 + x^696 + x^693 + x^691 + x^690 + x^689 + x^688 + x^684 + x^680 + x^678 + x^677 + x^674 + x^672 + x^669 + x^667 + x^665 + x^663 + x^662 + x^660 + x^659 + x^658 + x^656 + x^652 + x^651 + x^649 + x^646 + x^645 + x^642 + x^641 + x^640 + x^633 + x^632 + x^630 + x^627 + x^626 + x^622 + x^617 + x^612 + x^611 + x^610 + x^606 + x^605 + x^604 + x^603 + x^602 + x^601 + x^599 + x^597 + x^596 + x^594 + x^593 + x^592 + x^591 + x^582 + x^581 + x^580 + x^578 + x^577 + x^575 + x^574 + x^573 + x^568 + x^567 + x^564 + x^560 + x^559 + x^558 + x^556 + x^554 + x^550 + x^548 + x^547 + x^545 + x^544 + x^541 + x^540 + x^539 + x^538 + x^536 + x^535 + x^534 + x^532 + x^531 + x^529 + x^523 + x^521 + x^518 + x^517 + x^516 + x^514 + x^511 + x^510 + x^508 + x^507 + x^505 + x^504 + x^499 + x^498 + x^495 + x^494 + x^492 + x^491 + x^489 + x^488 + x^487 + x^486 + x^484 + x^483 + x^482 + x^481 + x^478 + x^477 + x^476 + x^475 + x^471 + x^470 + x^468 + x^465 + x^464 + x^460 + x^459 + x^455 + x^454 + x^453 + x^451 + x^445 + x^438 + x^434 + x^432 + x^431 + x^430 + x^429 + x^428 + x^427 + x^424 + x^423 + x^419 + x^418 + x^417 + x^416 + x^415 + x^413 + x^411 + x^409 + x^408 + x^406 + x^405 + x^404 + x^403 + x^402 + x^400 + x^397 + x^395 + x^390 + x^389 + x^387 + x^386 + x^385 + x^384 + x^382 + x^380 + x^379 + x^377 + x^376 + x^374 + x^373 + x^370 + x^369 + x^366 + x^365 + x^364 + x^362 + x^360 + x^359 + x^357 + x^356 + x^355 + x^354 + x^352 + x^349 + x^345 + x^342 + x^341 + x^340 + x^338 + x^334 + x^333 + x^326 + x^324 + x^322 + x^321 + x^319 + x^318 + x^317 + x^315 + x^312 + x^311 + x^309 + x^305 + x^304 + x^302 + x^298 + x^297 + x^296 + x^294 + x^293 + x^290 + x^289 + x^287 + x^286 + x^285 + x^279 + x^277 + x^275 + x^273 + x^268 + x^265 + x^264 + x^263 + x^260 + x^258 + x^254 + x^251 + x^249 + x^248 + x^247 + x^245 + x^244 + x^242 + x^241 + x^240 + x^239 + x^232 + x^229 + x^226 + x^225 + x^223 + x^222 + x^220 + x^219 + x^218 + x^215 + x^214 + x^213 + x^212 + x^210 + x^209 + x^206 + x^203 + x^202 + x^199 + x^197 + x^195 + x^194 + x^192 + x^191 + x^189 + x^188 + x^185 + x^184 + x^181 + x^177 + x^176 + x^175 + x^174 + x^172 + x^171 + x^170 + x^169 + x^167 + x^164 + x^162 + x^160 + x^159 + x^157 + x^156 + x^154 + x^153 + x^152 + x^150 + x^148 + x^145 + x^144 + x^139 + x^138 + x^137 + x^136 + x^133 + x^126 + x^124 + x^123 + x^122 + x^121 + x^120 + x^119 + x^113 + x^111 + x^108 + x^105 + x^104 + x^103 + x^102 + x^101 + x^98 + x^97 + x^96 + x^95 + x^92 + x^86 + x^85 + x^83 + x^81 + x^80 + x^77 + x^74 + x^73 + x^72 + x^71 + x^70 + x^68 + x^67 + x^66 + x^64 + x^62 + x^61 + x^60 + x^59 + x^57 + x^53 + x^51 + x^43 + x^41 + x^40 + x^39 + x^35 + x^30 + x^29 + x^27 + x^26 + x^24 + x^20 + x^18 + x^14 + x^6 + 1
+
+8-3-35 389 x^928 + x^898 + x^895 + x^870 + x^865 + x^835 + x^832 + x^824 + x^821 + x^812 + x^810 + x^808 + x^807 + x^805 + x^804 + x^794 + x^791 + x^788 + x^778 + x^777 + x^774 + x^772 + x^771 + x^763 + x^755 + x^752 + x^750 + x^747 + x^746 + x^742 + x^739 + x^733 + x^731 + x^728 + x^725 + x^722 + x^721 + x^720 + x^719 + x^716 + x^714 + x^713 + x^712 + x^711 + x^708 + x^705 + x^704 + x^701 + x^700 + x^698 + x^695 + x^694 + x^692 + x^689 + x^683 + x^680 + x^675 + x^674 + x^670 + x^668 + x^667 + x^664 + x^662 + x^661 + x^658 + x^656 + x^654 + x^651 + x^650 + x^645 + x^643 + x^640 + x^639 + x^637 + x^635 + x^634 + x^633 + x^631 + x^630 + x^628 + x^621 + x^620 + x^616 + x^615 + x^614 + x^613 + x^608 + x^605 + x^604 + x^602 + x^601 + x^598 + x^597 + x^591 + x^590 + x^588 + x^587 + x^581 + x^579 + x^578 + x^577 + x^576 + x^573 + x^572 + x^571 + x^570 + x^569 + x^568 + x^566 + x^564 + x^563 + x^562 + x^559 + x^557 + x^553 + x^550 + x^549 + x^547 + x^546 + x^540 + x^537 + x^536 + x^534 + x^530 + x^527 + x^526 + x^525 + x^524 + x^522 + x^521 + x^520 + x^519 + x^518 + x^510 + x^509 + x^505 + x^504 + x^498 + x^496 + x^493 + x^492 + x^490 + x^489 + x^488 + x^487 + x^484 + x^483 + x^482 + x^481 + x^480 + x^479 + x^477 + x^475 + x^474 + x^472 + x^470 + x^468 + x^465 + x^464 + x^463 + x^461 + x^458 + x^457 + x^455 + x^454 + x^453 + x^452 + x^451 + x^450 + x^448 + x^447 + x^445 + x^441 + x^440 + x^437 + x^435 + x^434 + x^433 + x^432 + x^431 + x^425 + x^421 + x^418 + x^415 + x^413 + x^412 + x^411 + x^410 + x^409 + x^408 + x^404 + x^403 + x^402 + x^399 + x^398 + x^394 + x^393 + x^391 + x^390 + x^389 + x^384 + x^383 + x^381 + x^380 + x^379 + x^378 + x^375 + x^374 + x^372 + x^370 + x^369 + x^367 + x^365 + x^364 + x^363 + x^362 + x^358 + x^357 + x^355 + x^354 + x^353 + x^351 + x^349 + x^347 + x^343 + x^342 + x^338 + x^337 + x^336 + x^334 + x^332 + x^329 + x^328 + x^327 + x^326 + x^323 + x^321 + x^320 + x^319 + x^317 + x^315 + x^311 + x^308 + x^305 + x^304 + x^303 + x^300 + x^298 + x^294 + x^293 + x^292 + x^290 + x^283 + x^281 + x^280 + x^279 + x^276 + x^274 + x^273 + x^269 + x^268 + x^267 + x^266 + x^264 + x^263 + x^259 + x^256 + x^248 + x^247 + x^246 + x^245 + x^244 + x^243 + x^242 + x^241 + x^240 + x^239 + x^235 + x^234 + x^231 + x^228 + x^227 + x^226 + x^225 + x^224 + x^220 + x^219 + x^218 + x^217 + x^216 + x^213 + x^211 + x^210 + x^206 + x^203 + x^200 + x^197 + x^196 + x^195 + x^193 + x^192 + x^191 + x^185 + x^184 + x^180 + x^178 + x^176 + x^175 + x^173 + x^170 + x^169 + x^167 + x^166 + x^165 + x^164 + x^162 + x^161 + x^160 + x^159 + x^154 + x^153 + x^152 + x^147 + x^141 + x^140 + x^138 + x^135 + x^133 + x^132 + x^131 + x^129 + x^127 + x^126 + x^125 + x^119 + x^115 + x^112 + x^111 + x^108 + x^106 + x^99 + x^97 + x^95 + x^94 + x^93 + x^92 + x^91 + x^89 + x^87 + x^84 + x^82 + x^81 + x^78 + x^76 + x^74 + x^69 + x^68 + x^67 + x^65 + x^63 + x^61 + x^58 + x^57 + x^56 + x^55 + x^54 + x^53 + x^51 + x^49 + x^48 + x^47 + x^46 + x^43 + x^40 + x^39 + x^35 + x^34 + x^32 + x^30 + x^28 + x^27 + x^25 + x^24 + x^21 + x^20 + x^15 + x^14 + x^10 + 1
+
+1-12-46 393 x^928 + x^898 + x^896 + x^870 + x^866 + x^836 + x^834 + x^828 + x^826 + x^808 + x^806 + x^804 + x^802 + x^796 + x^794 + x^778 + x^774 + x^767 + x^766 + x^758 + x^756 + x^744 + x^742 + x^740 + x^738 + x^736 + x^732 + x^731 + x^730 + x^727 + x^726 + x^718 + x^712 + x^710 + x^708 + x^707 + x^706 + x^704 + x^703 + x^701 + x^700 + x^699 + x^698 + x^697 + x^696 + x^695 + x^692 + x^686 + x^685 + x^680 + x^676 + x^672 + x^669 + x^668 + x^665 + x^664 + x^662 + x^661 + x^657 + x^656 + x^655 + x^653 + x^647 + x^646 + x^643 + x^640 + x^639 + x^638 + x^635 + x^628 + x^626 + x^623 + x^622 + x^621 + x^615 + x^612 + x^610 + x^609 + x^608 + x^607 + x^605 + x^604 + x^603 + x^602 + x^600 + x^599 + x^598 + x^597 + x^590 + x^589 + x^587 + x^586 + x^584 + x^583 + x^582 + x^581 + x^579 + x^578 + x^577 + x^576 + x^574 + x^573 + x^571 + x^567 + x^563 + x^561 + x^560 + x^559 + x^556 + x^554 + x^550 + x^548 + x^545 + x^544 + x^542 + x^535 + x^530 + x^529 + x^528 + x^526 + x^524 + x^522 + x^521 + x^519 + x^517 + x^516 + x^515 + x^512 + x^508 + x^507 + x^506 + x^505 + x^504 + x^499 + x^498 + x^497 + x^496 + x^495 + x^494 + x^489 + x^488 + x^487 + x^486 + x^484 + x^483 + x^481 + x^477 + x^476 + x^475 + x^474 + x^473 + x^471 + x^469 + x^466 + x^465 + x^464 + x^461 + x^460 + x^459 + x^457 + x^455 + x^452 + x^451 + x^449 + x^448 + x^447 + x^446 + x^445 + x^443 + x^442 + x^440 + x^439 + x^438 + x^433 + x^427 + x^425 + x^424 + x^422 + x^421 + x^419 + x^416 + x^415 + x^414 + x^413 + x^412 + x^408 + x^405 + x^404 + x^403 + x^402 + x^399 + x^398 + x^397 + x^396 + x^394 + x^393 + x^392 + x^391 + x^389 + x^388 + x^387 + x^386 + x^384 + x^381 + x^380 + x^376 + x^375 + x^374 + x^373 + x^366 + x^363 + x^362 + x^359 + x^358 + x^357 + x^356 + x^355 + x^353 + x^352 + x^351 + x^347 + x^344 + x^343 + x^342 + x^341 + x^339 + x^337 + x^336 + x^333 + x^331 + x^329 + x^328 + x^327 + x^326 + x^325 + x^321 + x^320 + x^317 + x^316 + x^315 + x^312 + x^311 + x^310 + x^307 + x^306 + x^303 + x^300 + x^298 + x^296 + x^295 + x^292 + x^291 + x^290 + x^287 + x^283 + x^279 + x^278 + x^277 + x^276 + x^273 + x^272 + x^271 + x^270 + x^269 + x^263 + x^262 + x^257 + x^256 + x^255 + x^254 + x^252 + x^249 + x^247 + x^245 + x^242 + x^241 + x^240 + x^235 + x^234 + x^233 + x^232 + x^231 + x^230 + x^229 + x^227 + x^225 + x^224 + x^223 + x^222 + x^219 + x^218 + x^217 + x^216 + x^215 + x^214 + x^213 + x^211 + x^206 + x^205 + x^203 + x^200 + x^199 + x^196 + x^194 + x^191 + x^186 + x^185 + x^183 + x^180 + x^179 + x^177 + x^176 + x^175 + x^174 + x^172 + x^171 + x^170 + x^169 + x^167 + x^160 + x^159 + x^158 + x^157 + x^156 + x^155 + x^154 + x^152 + x^146 + x^145 + x^144 + x^143 + x^142 + x^141 + x^139 + x^138 + x^137 + x^134 + x^133 + x^129 + x^126 + x^125 + x^124 + x^122 + x^121 + x^119 + x^118 + x^117 + x^113 + x^111 + x^110 + x^109 + x^108 + x^99 + x^98 + x^97 + x^95 + x^93 + x^92 + x^90 + x^85 + x^78 + x^76 + x^75 + x^70 + x^69 + x^65 + x^64 + x^63 + x^62 + x^59 + x^56 + x^54 + x^53 + x^51 + x^50 + x^49 + x^45 + x^44 + x^42 + x^41 + x^39 + x^36 + x^35 + x^34 + x^32 + x^31 + x^30 + x^23 + x^20 + x^16 + x^13 + x^12 + 1
+
+21-29-14 393 x^928 + x^898 + x^885 + x^870 + x^858 + x^856 + x^855 + x^831 + x^829 + x^828 + x^826 + x^815 + x^813 + x^812 + x^808 + x^804 + x^802 + x^801 + x^799 + x^798 + x^796 + x^788 + x^785 + x^778 + x^777 + x^775 + x^774 + x^772 + x^769 + x^768 + x^766 + x^765 + x^758 + x^752 + x^748 + x^747 + x^742 + x^741 + x^739 + x^738 + x^736 + x^735 + x^732 + x^728 + x^726 + x^725 + x^721 + x^718 + x^717 + x^714 + x^712 + x^709 + x^708 + x^706 + x^705 + x^702 + x^701 + x^699 + x^698 + x^694 + x^691 + x^687 + x^685 + x^684 + x^682 + x^681 + x^680 + x^679 + x^676 + x^675 + x^674 + x^671 + x^667 + x^664 + x^658 + x^657 + x^655 + x^653 + x^652 + x^650 + x^649 + x^648 + x^647 + x^646 + x^644 + x^641 + x^640 + x^637 + x^635 + x^632 + x^627 + x^625 + x^623 + x^622 + x^619 + x^618 + x^617 + x^613 + x^610 + x^608 + x^601 + x^595 + x^593 + x^592 + x^586 + x^585 + x^583 + x^582 + x^577 + x^572 + x^571 + x^570 + x^568 + x^565 + x^563 + x^561 + x^559 + x^557 + x^555 + x^553 + x^550 + x^548 + x^545 + x^544 + x^538 + x^537 + x^534 + x^532 + x^529 + x^526 + x^525 + x^521 + x^520 + x^518 + x^512 + x^511 + x^510 + x^508 + x^507 + x^503 + x^501 + x^500 + x^499 + x^498 + x^497 + x^496 + x^494 + x^489 + x^486 + x^485 + x^481 + x^480 + x^477 + x^476 + x^475 + x^466 + x^464 + x^462 + x^461 + x^460 + x^459 + x^458 + x^454 + x^452 + x^451 + x^449 + x^447 + x^446 + x^444 + x^441 + x^440 + x^439 + x^433 + x^431 + x^430 + x^429 + x^427 + x^425 + x^424 + x^422 + x^416 + x^414 + x^412 + x^408 + x^406 + x^403 + x^402 + x^401 + x^399 + x^398 + x^395 + x^393 + x^390 + x^389 + x^387 + x^386 + x^385 + x^384 + x^383 + x^381 + x^380 + x^378 + x^375 + x^374 + x^372 + x^371 + x^370 + x^368 + x^367 + x^362 + x^361 + x^358 + x^356 + x^354 + x^353 + x^351 + x^350 + x^347 + x^346 + x^343 + x^340 + x^339 + x^337 + x^335 + x^331 + x^330 + x^329 + x^328 + x^326 + x^325 + x^323 + x^322 + x^319 + x^318 + x^316 + x^315 + x^311 + x^310 + x^309 + x^308 + x^306 + x^304 + x^303 + x^298 + x^296 + x^295 + x^294 + x^292 + x^291 + x^290 + x^289 + x^287 + x^284 + x^281 + x^280 + x^276 + x^274 + x^272 + x^271 + x^270 + x^268 + x^267 + x^259 + x^258 + x^257 + x^256 + x^255 + x^254 + x^249 + x^248 + x^247 + x^246 + x^244 + x^243 + x^241 + x^240 + x^236 + x^234 + x^233 + x^232 + x^229 + x^224 + x^221 + x^220 + x^217 + x^216 + x^215 + x^213 + x^211 + x^210 + x^208 + x^207 + x^200 + x^199 + x^198 + x^197 + x^196 + x^195 + x^193 + x^192 + x^191 + x^190 + x^185 + x^184 + x^183 + x^181 + x^179 + x^176 + x^173 + x^170 + x^169 + x^168 + x^164 + x^162 + x^160 + x^157 + x^155 + x^154 + x^152 + x^151 + x^150 + x^149 + x^146 + x^144 + x^143 + x^142 + x^140 + x^136 + x^134 + x^132 + x^130 + x^129 + x^128 + x^126 + x^123 + x^120 + x^117 + x^112 + x^111 + x^108 + x^107 + x^105 + x^103 + x^100 + x^99 + x^96 + x^95 + x^91 + x^90 + x^89 + x^87 + x^86 + x^84 + x^83 + x^81 + x^79 + x^78 + x^76 + x^69 + x^68 + x^67 + x^66 + x^62 + x^61 + x^57 + x^56 + x^55 + x^53 + x^52 + x^51 + x^49 + x^48 + x^46 + x^43 + x^40 + x^38 + x^37 + x^36 + x^35 + x^34 + x^33 + x^31 + x^30 + x^29 + x^28 + x^27 + x^26 + x^24 + x^21 + x^20 + x^17 + x^13 + 1
+
+37-29-44 393 x^928 + x^898 + x^885 + x^870 + x^858 + x^856 + x^855 + x^831 + x^829 + x^828 + x^826 + x^815 + x^813 + x^812 + x^808 + x^804 + x^802 + x^801 + x^799 + x^798 + x^796 + x^788 + x^785 + x^778 + x^777 + x^775 + x^774 + x^772 + x^769 + x^768 + x^766 + x^765 + x^758 + x^752 + x^748 + x^747 + x^742 + x^741 + x^739 + x^738 + x^736 + x^735 + x^732 + x^728 + x^726 + x^725 + x^721 + x^718 + x^717 + x^714 + x^712 + x^709 + x^708 + x^706 + x^705 + x^702 + x^701 + x^699 + x^698 + x^694 + x^691 + x^687 + x^685 + x^684 + x^682 + x^681 + x^680 + x^679 + x^676 + x^675 + x^674 + x^671 + x^667 + x^664 + x^658 + x^657 + x^655 + x^653 + x^652 + x^650 + x^649 + x^648 + x^647 + x^646 + x^644 + x^641 + x^640 + x^637 + x^635 + x^632 + x^627 + x^625 + x^623 + x^622 + x^619 + x^618 + x^617 + x^613 + x^610 + x^608 + x^601 + x^595 + x^593 + x^592 + x^586 + x^585 + x^583 + x^582 + x^577 + x^572 + x^571 + x^570 + x^568 + x^565 + x^563 + x^561 + x^559 + x^557 + x^555 + x^553 + x^550 + x^548 + x^545 + x^544 + x^538 + x^537 + x^534 + x^532 + x^529 + x^526 + x^525 + x^521 + x^520 + x^518 + x^512 + x^511 + x^510 + x^508 + x^507 + x^503 + x^501 + x^500 + x^499 + x^498 + x^497 + x^496 + x^494 + x^489 + x^486 + x^485 + x^481 + x^480 + x^477 + x^476 + x^475 + x^466 + x^464 + x^462 + x^461 + x^460 + x^459 + x^458 + x^454 + x^452 + x^451 + x^449 + x^447 + x^446 + x^444 + x^441 + x^440 + x^439 + x^433 + x^431 + x^430 + x^429 + x^427 + x^425 + x^424 + x^422 + x^416 + x^414 + x^412 + x^408 + x^406 + x^403 + x^402 + x^401 + x^399 + x^398 + x^395 + x^393 + x^390 + x^389 + x^387 + x^386 + x^385 + x^384 + x^383 + x^381 + x^380 + x^378 + x^375 + x^374 + x^372 + x^371 + x^370 + x^368 + x^367 + x^362 + x^361 + x^358 + x^356 + x^354 + x^353 + x^351 + x^350 + x^347 + x^346 + x^343 + x^340 + x^339 + x^337 + x^335 + x^331 + x^330 + x^329 + x^328 + x^326 + x^325 + x^323 + x^322 + x^319 + x^318 + x^316 + x^315 + x^311 + x^310 + x^309 + x^308 + x^306 + x^304 + x^303 + x^298 + x^296 + x^295 + x^294 + x^292 + x^291 + x^290 + x^289 + x^287 + x^284 + x^281 + x^280 + x^276 + x^274 + x^272 + x^271 + x^270 + x^268 + x^267 + x^259 + x^258 + x^257 + x^256 + x^255 + x^254 + x^249 + x^248 + x^247 + x^246 + x^244 + x^243 + x^241 + x^240 + x^236 + x^234 + x^233 + x^232 + x^229 + x^224 + x^221 + x^220 + x^217 + x^216 + x^215 + x^213 + x^211 + x^210 + x^208 + x^207 + x^200 + x^199 + x^198 + x^197 + x^196 + x^195 + x^193 + x^192 + x^191 + x^190 + x^185 + x^184 + x^183 + x^181 + x^179 + x^176 + x^173 + x^170 + x^169 + x^168 + x^164 + x^162 + x^160 + x^157 + x^155 + x^154 + x^152 + x^151 + x^150 + x^149 + x^146 + x^144 + x^143 + x^142 + x^140 + x^136 + x^134 + x^132 + x^130 + x^129 + x^128 + x^126 + x^123 + x^120 + x^117 + x^112 + x^111 + x^108 + x^107 + x^105 + x^103 + x^100 + x^99 + x^96 + x^95 + x^91 + x^90 + x^89 + x^87 + x^86 + x^84 + x^83 + x^81 + x^79 + x^78 + x^76 + x^69 + x^68 + x^67 + x^66 + x^62 + x^61 + x^57 + x^56 + x^55 + x^53 + x^52 + x^51 + x^49 + x^48 + x^46 + x^43 + x^40 + x^38 + x^37 + x^36 + x^35 + x^34 + x^33 + x^31 + x^30 + x^29 + x^28 + x^27 + x^26 + x^24 + x^21 + x^20 + x^17 + x^13 + 1
+
+7-48-42 393 x^928 + x^898 + x^892 + x^870 + x^862 + x^843 + x^820 + x^813 + x^808 + x^800 + x^794 + x^784 + x^783 + x^780 + x^778 + x^774 + x^771 + x^770 + x^767 + x^760 + x^757 + x^753 + x^750 + x^748 + x^743 + x^741 + x^740 + x^737 + x^731 + x^727 + x^724 + x^723 + x^718 + x^713 + x^707 + x^705 + x^704 + x^701 + x^700 + x^697 + x^695 + x^693 + x^688 + x^684 + x^683 + x^682 + x^681 + x^678 + x^677 + x^674 + x^671 + x^669 + x^667 + x^664 + x^663 + x^660 + x^653 + x^652 + x^651 + x^650 + x^649 + x^645 + x^644 + x^641 + x^639 + x^638 + x^637 + x^634 + x^633 + x^630 + x^628 + x^625 + x^623 + x^621 + x^620 + x^619 + x^615 + x^612 + x^611 + x^608 + x^607 + x^605 + x^604 + x^603 + x^599 + x^595 + x^593 + x^585 + x^584 + x^581 + x^580 + x^578 + x^577 + x^575 + x^573 + x^572 + x^569 + x^568 + x^565 + x^564 + x^563 + x^561 + x^558 + x^557 + x^551 + x^550 + x^548 + x^547 + x^544 + x^538 + x^537 + x^534 + x^533 + x^532 + x^530 + x^529 + x^524 + x^522 + x^521 + x^519 + x^518 + x^516 + x^515 + x^514 + x^513 + x^512 + x^510 + x^508 + x^506 + x^505 + x^502 + x^501 + x^500 + x^499 + x^496 + x^493 + x^492 + x^491 + x^488 + x^487 + x^483 + x^482 + x^481 + x^479 + x^478 + x^476 + x^475 + x^474 + x^473 + x^472 + x^471 + x^470 + x^469 + x^467 + x^463 + x^460 + x^458 + x^453 + x^452 + x^451 + x^447 + x^446 + x^445 + x^439 + x^435 + x^434 + x^433 + x^432 + x^431 + x^430 + x^428 + x^427 + x^425 + x^421 + x^420 + x^419 + x^417 + x^415 + x^413 + x^412 + x^410 + x^407 + x^406 + x^405 + x^404 + x^402 + x^400 + x^397 + x^395 + x^394 + x^393 + x^392 + x^391 + x^390 + x^388 + x^385 + x^381 + x^378 + x^377 + x^376 + x^374 + x^373 + x^370 + x^369 + x^368 + x^366 + x^365 + x^364 + x^363 + x^361 + x^358 + x^356 + x^355 + x^354 + x^351 + x^350 + x^348 + x^347 + x^344 + x^341 + x^340 + x^337 + x^335 + x^334 + x^329 + x^326 + x^325 + x^324 + x^322 + x^321 + x^319 + x^318 + x^314 + x^312 + x^311 + x^310 + x^309 + x^308 + x^307 + x^303 + x^302 + x^301 + x^298 + x^293 + x^292 + x^291 + x^288 + x^281 + x^280 + x^279 + x^278 + x^275 + x^271 + x^270 + x^267 + x^265 + x^264 + x^263 + x^262 + x^261 + x^260 + x^259 + x^258 + x^257 + x^256 + x^253 + x^252 + x^251 + x^250 + x^249 + x^248 + x^247 + x^244 + x^243 + x^242 + x^240 + x^237 + x^236 + x^233 + x^227 + x^226 + x^224 + x^222 + x^221 + x^220 + x^219 + x^218 + x^217 + x^216 + x^213 + x^212 + x^210 + x^208 + x^207 + x^206 + x^205 + x^202 + x^201 + x^200 + x^197 + x^195 + x^194 + x^193 + x^192 + x^191 + x^190 + x^189 + x^186 + x^184 + x^183 + x^182 + x^181 + x^178 + x^177 + x^176 + x^170 + x^168 + x^167 + x^166 + x^162 + x^159 + x^157 + x^149 + x^148 + x^147 + x^143 + x^141 + x^139 + x^138 + x^136 + x^135 + x^131 + x^127 + x^121 + x^119 + x^117 + x^114 + x^110 + x^109 + x^107 + x^103 + x^99 + x^98 + x^96 + x^95 + x^94 + x^92 + x^90 + x^89 + x^88 + x^87 + x^86 + x^85 + x^84 + x^82 + x^80 + x^79 + x^78 + x^77 + x^76 + x^74 + x^71 + x^69 + x^68 + x^66 + x^64 + x^62 + x^61 + x^58 + x^57 + x^53 + x^50 + x^46 + x^44 + x^43 + x^42 + x^41 + x^40 + x^37 + x^35 + x^34 + x^32 + x^31 + x^29 + x^27 + x^26 + x^25 + x^22 + x^20 + x^16 + x^12 + x^10 + 1
+
+57-51-22 397 x^928 + x^898 + x^895 + x^870 + x^866 + x^865 + x^862 + x^860 + x^857 + x^835 + x^833 + x^832 + x^828 + x^822 + x^819 + x^810 + x^808 + x^805 + x^797 + x^796 + x^791 + x^790 + x^789 + x^786 + x^784 + x^782 + x^781 + x^778 + x^777 + x^773 + x^767 + x^766 + x^762 + x^757 + x^752 + x^743 + x^742 + x^737 + x^734 + x^717 + x^715 + x^713 + x^708 + x^707 + x^706 + x^705 + x^703 + x^702 + x^701 + x^698 + x^696 + x^690 + x^688 + x^685 + x^683 + x^682 + x^680 + x^677 + x^673 + x^671 + x^668 + x^667 + x^666 + x^663 + x^662 + x^661 + x^656 + x^654 + x^653 + x^649 + x^647 + x^646 + x^645 + x^643 + x^637 + x^635 + x^632 + x^630 + x^629 + x^627 + x^626 + x^625 + x^624 + x^622 + x^619 + x^613 + x^610 + x^608 + x^602 + x^597 + x^594 + x^593 + x^591 + x^590 + x^587 + x^586 + x^584 + x^583 + x^582 + x^580 + x^577 + x^575 + x^574 + x^573 + x^570 + x^569 + x^568 + x^562 + x^556 + x^553 + x^552 + x^551 + x^550 + x^547 + x^546 + x^545 + x^543 + x^541 + x^539 + x^538 + x^535 + x^534 + x^533 + x^528 + x^527 + x^526 + x^524 + x^522 + x^521 + x^515 + x^512 + x^509 + x^505 + x^503 + x^502 + x^501 + x^500 + x^499 + x^498 + x^497 + x^493 + x^491 + x^483 + x^480 + x^477 + x^474 + x^473 + x^472 + x^471 + x^470 + x^468 + x^467 + x^463 + x^459 + x^458 + x^457 + x^455 + x^453 + x^452 + x^451 + x^448 + x^447 + x^446 + x^445 + x^444 + x^443 + x^440 + x^439 + x^436 + x^435 + x^434 + x^432 + x^430 + x^429 + x^428 + x^427 + x^426 + x^423 + x^422 + x^421 + x^420 + x^417 + x^416 + x^415 + x^414 + x^413 + x^411 + x^410 + x^407 + x^406 + x^403 + x^401 + x^400 + x^391 + x^390 + x^387 + x^386 + x^385 + x^381 + x^380 + x^379 + x^377 + x^374 + x^373 + x^370 + x^369 + x^362 + x^359 + x^357 + x^356 + x^355 + x^354 + x^349 + x^346 + x^345 + x^344 + x^343 + x^340 + x^339 + x^338 + x^337 + x^336 + x^334 + x^331 + x^330 + x^328 + x^323 + x^322 + x^321 + x^319 + x^318 + x^317 + x^316 + x^314 + x^313 + x^312 + x^311 + x^310 + x^305 + x^303 + x^299 + x^295 + x^294 + x^293 + x^290 + x^289 + x^286 + x^285 + x^284 + x^281 + x^280 + x^278 + x^275 + x^272 + x^271 + x^269 + x^267 + x^266 + x^263 + x^262 + x^261 + x^260 + x^258 + x^257 + x^256 + x^255 + x^254 + x^253 + x^252 + x^251 + x^247 + x^246 + x^241 + x^240 + x^239 + x^238 + x^236 + x^233 + x^232 + x^230 + x^228 + x^224 + x^223 + x^220 + x^216 + x^215 + x^213 + x^212 + x^211 + x^208 + x^206 + x^205 + x^204 + x^201 + x^199 + x^197 + x^195 + x^194 + x^193 + x^191 + x^189 + x^188 + x^187 + x^186 + x^185 + x^184 + x^182 + x^179 + x^178 + x^176 + x^172 + x^171 + x^170 + x^169 + x^168 + x^167 + x^166 + x^163 + x^162 + x^161 + x^160 + x^157 + x^156 + x^155 + x^153 + x^152 + x^148 + x^145 + x^142 + x^140 + x^139 + x^137 + x^134 + x^133 + x^131 + x^123 + x^119 + x^118 + x^114 + x^112 + x^111 + x^110 + x^108 + x^107 + x^105 + x^104 + x^102 + x^99 + x^98 + x^95 + x^94 + x^93 + x^89 + x^88 + x^86 + x^84 + x^82 + x^80 + x^78 + x^76 + x^73 + x^71 + x^70 + x^68 + x^67 + x^66 + x^64 + x^62 + x^60 + x^59 + x^57 + x^53 + x^52 + x^49 + x^48 + x^47 + x^46 + x^40 + x^37 + x^36 + x^35 + x^34 + x^32 + x^31 + x^28 + x^26 + x^25 + x^24 + x^22 + x^20 + x^19 + x^18 + x^15 + x^10 + x^8 + x^5 + 1
+
+41-40-20 399 x^928 + x^898 + x^870 + x^846 + x^844 + x^821 + x^820 + x^816 + x^814 + x^808 + x^804 + x^801 + x^800 + x^798 + x^795 + x^778 + x^771 + x^770 + x^769 + x^768 + x^765 + x^760 + x^749 + x^748 + x^744 + x^743 + x^741 + x^738 + x^734 + x^729 + x^726 + x^719 + x^717 + x^716 + x^715 + x^714 + x^713 + x^712 + x^711 + x^710 + x^709 + x^706 + x^704 + x^703 + x^702 + x^701 + x^694 + x^693 + x^691 + x^690 + x^685 + x^684 + x^682 + x^681 + x^680 + x^678 + x^677 + x^676 + x^675 + x^674 + x^673 + x^671 + x^669 + x^667 + x^665 + x^661 + x^660 + x^659 + x^658 + x^656 + x^652 + x^651 + x^649 + x^648 + x^643 + x^642 + x^639 + x^637 + x^634 + x^631 + x^629 + x^628 + x^624 + x^620 + x^619 + x^618 + x^617 + x^616 + x^615 + x^614 + x^613 + x^611 + x^610 + x^605 + x^604 + x^603 + x^601 + x^599 + x^598 + x^595 + x^594 + x^593 + x^590 + x^589 + x^585 + x^584 + x^583 + x^579 + x^577 + x^575 + x^573 + x^572 + x^571 + x^570 + x^569 + x^566 + x^564 + x^562 + x^561 + x^560 + x^557 + x^555 + x^554 + x^553 + x^552 + x^551 + x^550 + x^549 + x^547 + x^545 + x^542 + x^540 + x^539 + x^538 + x^537 + x^536 + x^535 + x^533 + x^532 + x^530 + x^529 + x^524 + x^518 + x^516 + x^514 + x^513 + x^512 + x^510 + x^509 + x^504 + x^503 + x^501 + x^493 + x^490 + x^489 + x^487 + x^486 + x^485 + x^484 + x^481 + x^479 + x^478 + x^475 + x^473 + x^471 + x^470 + x^468 + x^467 + x^466 + x^462 + x^460 + x^455 + x^454 + x^451 + x^450 + x^449 + x^447 + x^446 + x^445 + x^444 + x^443 + x^442 + x^441 + x^440 + x^439 + x^438 + x^436 + x^430 + x^429 + x^424 + x^423 + x^420 + x^419 + x^418 + x^415 + x^414 + x^413 + x^410 + x^409 + x^408 + x^407 + x^406 + x^405 + x^403 + x^402 + x^400 + x^398 + x^397 + x^396 + x^395 + x^394 + x^392 + x^391 + x^389 + x^387 + x^386 + x^385 + x^383 + x^382 + x^381 + x^379 + x^375 + x^374 + x^369 + x^367 + x^366 + x^365 + x^363 + x^360 + x^359 + x^358 + x^357 + x^355 + x^354 + x^351 + x^339 + x^338 + x^337 + x^336 + x^335 + x^334 + x^332 + x^330 + x^329 + x^323 + x^320 + x^317 + x^315 + x^312 + x^311 + x^309 + x^305 + x^302 + x^301 + x^300 + x^299 + x^298 + x^297 + x^296 + x^295 + x^292 + x^291 + x^288 + x^286 + x^285 + x^284 + x^283 + x^282 + x^280 + x^279 + x^278 + x^273 + x^268 + x^267 + x^264 + x^263 + x^258 + x^257 + x^256 + x^255 + x^254 + x^253 + x^250 + x^249 + x^248 + x^244 + x^243 + x^242 + x^238 + x^237 + x^236 + x^234 + x^233 + x^231 + x^230 + x^222 + x^218 + x^215 + x^214 + x^211 + x^208 + x^206 + x^205 + x^201 + x^200 + x^198 + x^197 + x^195 + x^194 + x^193 + x^191 + x^189 + x^185 + x^182 + x^179 + x^176 + x^174 + x^173 + x^164 + x^163 + x^162 + x^159 + x^158 + x^156 + x^155 + x^152 + x^150 + x^148 + x^142 + x^140 + x^139 + x^138 + x^137 + x^135 + x^134 + x^133 + x^132 + x^131 + x^128 + x^124 + x^122 + x^121 + x^120 + x^119 + x^117 + x^116 + x^114 + x^111 + x^110 + x^108 + x^107 + x^105 + x^104 + x^103 + x^101 + x^95 + x^94 + x^93 + x^91 + x^89 + x^88 + x^87 + x^84 + x^83 + x^82 + x^81 + x^80 + x^79 + x^78 + x^77 + x^76 + x^74 + x^73 + x^72 + x^66 + x^65 + x^64 + x^59 + x^57 + x^56 + x^53 + x^49 + x^47 + x^44 + x^43 + x^39 + x^38 + x^37 + x^34 + x^33 + x^32 + x^31 + x^28 + x^23 + x^22 + x^19 + x^12 + x^8 + 1
+
+55-27-24 399 x^928 + x^898 + x^864 + x^847 + x^846 + x^834 + x^824 + x^818 + x^817 + x^816 + x^812 + x^810 + x^808 + x^806 + x^801 + x^800 + x^795 + x^794 + x^788 + x^783 + x^781 + x^777 + x^776 + x^775 + x^771 + x^765 + x^764 + x^753 + x^750 + x^749 + x^747 + x^746 + x^745 + x^743 + x^742 + x^741 + x^737 + x^734 + x^731 + x^728 + x^726 + x^725 + x^724 + x^721 + x^719 + x^717 + x^716 + x^714 + x^712 + x^706 + x^704 + x^703 + x^702 + x^701 + x^699 + x^698 + x^697 + x^695 + x^694 + x^691 + x^690 + x^688 + x^687 + x^684 + x^683 + x^681 + x^680 + x^673 + x^672 + x^670 + x^667 + x^666 + x^664 + x^663 + x^661 + x^659 + x^657 + x^655 + x^650 + x^649 + x^647 + x^640 + x^639 + x^637 + x^636 + x^634 + x^630 + x^626 + x^625 + x^623 + x^622 + x^620 + x^618 + x^617 + x^615 + x^612 + x^611 + x^609 + x^608 + x^607 + x^605 + x^604 + x^602 + x^600 + x^599 + x^596 + x^594 + x^592 + x^590 + x^587 + x^581 + x^578 + x^576 + x^575 + x^574 + x^570 + x^567 + x^566 + x^565 + x^564 + x^563 + x^561 + x^560 + x^555 + x^554 + x^553 + x^552 + x^551 + x^550 + x^549 + x^548 + x^543 + x^537 + x^536 + x^533 + x^532 + x^531 + x^529 + x^528 + x^527 + x^525 + x^524 + x^523 + x^522 + x^519 + x^518 + x^516 + x^515 + x^514 + x^513 + x^512 + x^511 + x^509 + x^507 + x^505 + x^500 + x^498 + x^497 + x^496 + x^494 + x^489 + x^486 + x^481 + x^480 + x^479 + x^476 + x^471 + x^469 + x^467 + x^466 + x^463 + x^459 + x^455 + x^451 + x^450 + x^449 + x^448 + x^447 + x^445 + x^444 + x^441 + x^440 + x^432 + x^430 + x^429 + x^428 + x^426 + x^424 + x^420 + x^418 + x^416 + x^415 + x^413 + x^411 + x^408 + x^406 + x^405 + x^404 + x^400 + x^398 + x^392 + x^390 + x^387 + x^386 + x^382 + x^379 + x^378 + x^377 + x^376 + x^374 + x^373 + x^371 + x^370 + x^368 + x^367 + x^365 + x^364 + x^363 + x^362 + x^360 + x^358 + x^351 + x^350 + x^344 + x^343 + x^341 + x^340 + x^339 + x^338 + x^337 + x^332 + x^331 + x^329 + x^327 + x^323 + x^321 + x^320 + x^318 + x^317 + x^314 + x^313 + x^312 + x^311 + x^309 + x^308 + x^306 + x^304 + x^301 + x^300 + x^299 + x^296 + x^295 + x^294 + x^291 + x^290 + x^287 + x^286 + x^285 + x^280 + x^275 + x^274 + x^271 + x^270 + x^268 + x^264 + x^263 + x^258 + x^257 + x^256 + x^254 + x^252 + x^249 + x^248 + x^246 + x^245 + x^242 + x^241 + x^239 + x^238 + x^236 + x^233 + x^231 + x^228 + x^226 + x^225 + x^222 + x^219 + x^218 + x^217 + x^216 + x^215 + x^213 + x^210 + x^209 + x^207 + x^205 + x^203 + x^202 + x^198 + x^197 + x^196 + x^193 + x^192 + x^189 + x^188 + x^187 + x^184 + x^183 + x^182 + x^181 + x^179 + x^178 + x^177 + x^175 + x^172 + x^171 + x^170 + x^169 + x^164 + x^162 + x^161 + x^160 + x^159 + x^155 + x^154 + x^152 + x^151 + x^148 + x^147 + x^143 + x^140 + x^139 + x^137 + x^134 + x^133 + x^132 + x^131 + x^126 + x^125 + x^120 + x^119 + x^118 + x^117 + x^113 + x^111 + x^110 + x^108 + x^104 + x^102 + x^101 + x^100 + x^97 + x^96 + x^90 + x^89 + x^85 + x^82 + x^81 + x^77 + x^76 + x^73 + x^69 + x^68 + x^67 + x^65 + x^64 + x^63 + x^62 + x^61 + x^55 + x^54 + x^53 + x^52 + x^49 + x^47 + x^46 + x^40 + x^39 + x^37 + x^36 + x^35 + x^34 + x^33 + x^32 + x^31 + x^29 + x^26 + x^25 + x^24 + x^23 + x^22 + x^19 + x^17 + x^16 + x^12 + x^9 + x^8 + x^5 + 1
+
+39-32-32 401 x^928 + x^898 + x^870 + x^867 + x^842 + x^837 + x^836 + x^832 + x^807 + x^806 + x^805 + x^802 + x^801 + x^780 + x^778 + x^775 + x^773 + x^771 + x^770 + x^768 + x^766 + x^758 + x^752 + x^749 + x^747 + x^740 + x^730 + x^724 + x^721 + x^719 + x^717 + x^716 + x^715 + x^714 + x^713 + x^711 + x^709 + x^708 + x^707 + x^704 + x^702 + x^700 + x^696 + x^693 + x^691 + x^689 + x^687 + x^686 + x^685 + x^683 + x^682 + x^680 + x^679 + x^677 + x^676 + x^674 + x^670 + x^669 + x^668 + x^665 + x^663 + x^661 + x^660 + x^659 + x^655 + x^653 + x^652 + x^651 + x^648 + x^647 + x^646 + x^645 + x^644 + x^642 + x^641 + x^640 + x^639 + x^638 + x^637 + x^635 + x^634 + x^633 + x^632 + x^631 + x^630 + x^625 + x^624 + x^620 + x^618 + x^615 + x^613 + x^611 + x^606 + x^605 + x^604 + x^602 + x^599 + x^597 + x^596 + x^593 + x^592 + x^591 + x^590 + x^589 + x^585 + x^584 + x^583 + x^580 + x^578 + x^577 + x^576 + x^575 + x^574 + x^572 + x^571 + x^570 + x^568 + x^567 + x^566 + x^563 + x^562 + x^560 + x^556 + x^554 + x^552 + x^549 + x^548 + x^547 + x^546 + x^544 + x^538 + x^536 + x^535 + x^534 + x^533 + x^530 + x^529 + x^528 + x^527 + x^526 + x^525 + x^524 + x^521 + x^519 + x^517 + x^516 + x^514 + x^513 + x^512 + x^510 + x^509 + x^507 + x^506 + x^504 + x^502 + x^500 + x^499 + x^498 + x^497 + x^493 + x^492 + x^490 + x^489 + x^485 + x^484 + x^481 + x^476 + x^475 + x^473 + x^467 + x^463 + x^462 + x^461 + x^459 + x^456 + x^453 + x^450 + x^449 + x^448 + x^445 + x^443 + x^442 + x^440 + x^438 + x^436 + x^433 + x^432 + x^431 + x^430 + x^429 + x^427 + x^421 + x^420 + x^419 + x^416 + x^415 + x^414 + x^413 + x^411 + x^410 + x^408 + x^407 + x^406 + x^405 + x^403 + x^400 + x^398 + x^397 + x^396 + x^394 + x^390 + x^389 + x^388 + x^385 + x^384 + x^383 + x^379 + x^376 + x^374 + x^373 + x^372 + x^370 + x^369 + x^368 + x^364 + x^362 + x^358 + x^355 + x^354 + x^353 + x^351 + x^348 + x^347 + x^344 + x^343 + x^339 + x^337 + x^336 + x^334 + x^333 + x^329 + x^328 + x^323 + x^321 + x^320 + x^319 + x^318 + x^317 + x^315 + x^314 + x^311 + x^309 + x^307 + x^306 + x^304 + x^302 + x^300 + x^297 + x^295 + x^294 + x^291 + x^289 + x^287 + x^285 + x^284 + x^283 + x^282 + x^281 + x^280 + x^278 + x^277 + x^276 + x^270 + x^269 + x^268 + x^267 + x^263 + x^261 + x^259 + x^257 + x^255 + x^253 + x^252 + x^250 + x^248 + x^247 + x^245 + x^244 + x^242 + x^241 + x^240 + x^238 + x^234 + x^233 + x^230 + x^227 + x^226 + x^224 + x^220 + x^219 + x^217 + x^216 + x^215 + x^209 + x^208 + x^207 + x^206 + x^205 + x^204 + x^203 + x^201 + x^199 + x^198 + x^197 + x^196 + x^193 + x^191 + x^187 + x^184 + x^183 + x^182 + x^173 + x^172 + x^169 + x^167 + x^166 + x^165 + x^162 + x^161 + x^160 + x^158 + x^157 + x^156 + x^155 + x^154 + x^152 + x^151 + x^149 + x^147 + x^141 + x^138 + x^137 + x^136 + x^135 + x^134 + x^131 + x^129 + x^126 + x^125 + x^122 + x^121 + x^117 + x^116 + x^113 + x^110 + x^109 + x^108 + x^104 + x^102 + x^101 + x^100 + x^98 + x^97 + x^95 + x^94 + x^87 + x^83 + x^82 + x^78 + x^77 + x^74 + x^73 + x^72 + x^70 + x^69 + x^66 + x^65 + x^62 + x^60 + x^55 + x^54 + x^52 + x^51 + x^49 + x^46 + x^45 + x^41 + x^40 + x^37 + x^33 + x^31 + x^29 + x^27 + x^26 + x^24 + x^20 + x^18 + x^17 + x^15 + x^6 + 1
+
+40-7-11 401 x^928 + x^898 + x^897 + x^882 + x^870 + x^866 + x^860 + x^854 + x^852 + x^851 + x^836 + x^832 + x^830 + x^829 + x^826 + x^824 + x^820 + x^814 + x^812 + x^811 + x^808 + x^806 + x^804 + x^800 + x^796 + x^793 + x^792 + x^790 + x^786 + x^780 + x^778 + x^777 + x^774 + x^770 + x^769 + x^767 + x^762 + x^761 + x^760 + x^756 + x^749 + x^748 + x^746 + x^742 + x^736 + x^733 + x^730 + x^728 + x^726 + x^725 + x^724 + x^723 + x^719 + x^718 + x^717 + x^716 + x^715 + x^712 + x^709 + x^708 + x^707 + x^705 + x^701 + x^700 + x^699 + x^698 + x^695 + x^689 + x^688 + x^687 + x^684 + x^683 + x^680 + x^678 + x^675 + x^672 + x^670 + x^669 + x^666 + x^664 + x^663 + x^656 + x^655 + x^654 + x^651 + x^647 + x^641 + x^639 + x^638 + x^636 + x^635 + x^633 + x^632 + x^631 + x^630 + x^629 + x^627 + x^626 + x^625 + x^623 + x^621 + x^620 + x^619 + x^617 + x^613 + x^611 + x^610 + x^609 + x^608 + x^607 + x^606 + x^604 + x^603 + x^600 + x^598 + x^596 + x^595 + x^592 + x^591 + x^590 + x^587 + x^586 + x^585 + x^584 + x^582 + x^581 + x^578 + x^577 + x^576 + x^575 + x^574 + x^569 + x^566 + x^565 + x^564 + x^560 + x^558 + x^557 + x^556 + x^554 + x^552 + x^550 + x^549 + x^548 + x^545 + x^539 + x^538 + x^536 + x^535 + x^534 + x^533 + x^532 + x^527 + x^524 + x^523 + x^522 + x^520 + x^518 + x^517 + x^514 + x^513 + x^512 + x^510 + x^507 + x^504 + x^501 + x^499 + x^497 + x^496 + x^494 + x^492 + x^491 + x^490 + x^489 + x^487 + x^485 + x^483 + x^479 + x^478 + x^477 + x^476 + x^475 + x^473 + x^472 + x^470 + x^469 + x^468 + x^465 + x^464 + x^462 + x^460 + x^457 + x^452 + x^449 + x^448 + x^447 + x^445 + x^443 + x^441 + x^440 + x^436 + x^434 + x^432 + x^430 + x^428 + x^426 + x^424 + x^420 + x^416 + x^415 + x^410 + x^408 + x^407 + x^406 + x^404 + x^397 + x^395 + x^394 + x^390 + x^389 + x^387 + x^384 + x^382 + x^380 + x^374 + x^370 + x^368 + x^366 + x^365 + x^364 + x^362 + x^360 + x^359 + x^358 + x^356 + x^352 + x^350 + x^349 + x^347 + x^346 + x^344 + x^342 + x^341 + x^337 + x^336 + x^332 + x^331 + x^327 + x^325 + x^323 + x^322 + x^320 + x^317 + x^316 + x^315 + x^314 + x^312 + x^309 + x^307 + x^306 + x^305 + x^304 + x^303 + x^301 + x^300 + x^298 + x^296 + x^294 + x^293 + x^290 + x^289 + x^287 + x^285 + x^282 + x^281 + x^279 + x^277 + x^273 + x^265 + x^264 + x^259 + x^258 + x^257 + x^255 + x^254 + x^253 + x^251 + x^250 + x^249 + x^248 + x^247 + x^246 + x^244 + x^237 + x^235 + x^233 + x^232 + x^231 + x^230 + x^229 + x^228 + x^227 + x^225 + x^221 + x^215 + x^214 + x^211 + x^208 + x^206 + x^204 + x^200 + x^199 + x^198 + x^196 + x^192 + x^188 + x^186 + x^185 + x^184 + x^183 + x^181 + x^179 + x^176 + x^175 + x^173 + x^171 + x^170 + x^168 + x^165 + x^164 + x^160 + x^158 + x^155 + x^154 + x^152 + x^151 + x^149 + x^147 + x^144 + x^141 + x^140 + x^139 + x^137 + x^136 + x^133 + x^131 + x^130 + x^128 + x^127 + x^126 + x^125 + x^123 + x^122 + x^121 + x^120 + x^119 + x^117 + x^116 + x^115 + x^112 + x^109 + x^106 + x^102 + x^101 + x^98 + x^92 + x^89 + x^87 + x^86 + x^81 + x^74 + x^72 + x^71 + x^69 + x^65 + x^61 + x^57 + x^55 + x^53 + x^51 + x^50 + x^47 + x^45 + x^44 + x^43 + x^42 + x^40 + x^39 + x^38 + x^37 + x^35 + x^31 + x^30 + x^29 + x^27 + x^24 + x^22 + x^21 + x^20 + x^19 + 1
+
+44-24-27 401 x^928 + x^898 + x^870 + x^858 + x^824 + x^821 + x^820 + x^798 + x^792 + x^791 + x^790 + x^789 + x^784 + x^776 + x^771 + x^770 + x^768 + x^764 + x^762 + x^761 + x^760 + x^759 + x^755 + x^754 + x^751 + x^742 + x^738 + x^735 + x^734 + x^731 + x^730 + x^729 + x^728 + x^725 + x^722 + x^721 + x^715 + x^711 + x^710 + x^706 + x^705 + x^701 + x^700 + x^698 + x^695 + x^692 + x^691 + x^689 + x^686 + x^685 + x^681 + x^676 + x^675 + x^674 + x^672 + x^671 + x^669 + x^668 + x^667 + x^666 + x^665 + x^658 + x^656 + x^654 + x^653 + x^651 + x^650 + x^648 + x^646 + x^644 + x^642 + x^635 + x^634 + x^633 + x^629 + x^626 + x^625 + x^624 + x^622 + x^621 + x^620 + x^618 + x^616 + x^614 + x^612 + x^608 + x^607 + x^606 + x^605 + x^604 + x^603 + x^602 + x^594 + x^593 + x^590 + x^589 + x^588 + x^586 + x^585 + x^584 + x^583 + x^582 + x^580 + x^577 + x^576 + x^575 + x^574 + x^569 + x^568 + x^566 + x^565 + x^564 + x^561 + x^559 + x^556 + x^554 + x^553 + x^552 + x^547 + x^543 + x^542 + x^541 + x^540 + x^539 + x^538 + x^536 + x^535 + x^534 + x^531 + x^530 + x^529 + x^527 + x^526 + x^524 + x^519 + x^514 + x^512 + x^511 + x^507 + x^504 + x^503 + x^502 + x^499 + x^498 + x^494 + x^493 + x^491 + x^489 + x^488 + x^486 + x^485 + x^483 + x^482 + x^480 + x^478 + x^476 + x^473 + x^468 + x^465 + x^463 + x^461 + x^460 + x^459 + x^458 + x^457 + x^455 + x^454 + x^453 + x^452 + x^451 + x^450 + x^449 + x^447 + x^446 + x^445 + x^443 + x^442 + x^439 + x^437 + x^436 + x^435 + x^433 + x^432 + x^429 + x^424 + x^423 + x^421 + x^418 + x^416 + x^412 + x^411 + x^406 + x^404 + x^403 + x^401 + x^400 + x^399 + x^398 + x^397 + x^394 + x^390 + x^388 + x^384 + x^383 + x^382 + x^381 + x^380 + x^378 + x^377 + x^376 + x^374 + x^373 + x^372 + x^368 + x^367 + x^366 + x^363 + x^361 + x^360 + x^359 + x^355 + x^353 + x^351 + x^350 + x^349 + x^348 + x^347 + x^345 + x^344 + x^342 + x^341 + x^340 + x^338 + x^337 + x^334 + x^329 + x^328 + x^326 + x^325 + x^319 + x^316 + x^314 + x^312 + x^311 + x^310 + x^308 + x^307 + x^305 + x^300 + x^296 + x^292 + x^291 + x^290 + x^287 + x^286 + x^284 + x^282 + x^280 + x^279 + x^277 + x^275 + x^274 + x^273 + x^272 + x^269 + x^268 + x^265 + x^264 + x^263 + x^262 + x^261 + x^258 + x^257 + x^256 + x^255 + x^254 + x^253 + x^252 + x^250 + x^249 + x^244 + x^242 + x^239 + x^238 + x^237 + x^235 + x^234 + x^232 + x^231 + x^228 + x^226 + x^225 + x^224 + x^221 + x^220 + x^219 + x^218 + x^216 + x^214 + x^213 + x^211 + x^210 + x^207 + x^205 + x^204 + x^203 + x^202 + x^200 + x^199 + x^198 + x^194 + x^186 + x^185 + x^184 + x^183 + x^182 + x^180 + x^179 + x^177 + x^174 + x^171 + x^169 + x^168 + x^165 + x^164 + x^163 + x^161 + x^159 + x^158 + x^157 + x^154 + x^152 + x^151 + x^148 + x^146 + x^145 + x^144 + x^142 + x^141 + x^139 + x^136 + x^135 + x^134 + x^132 + x^131 + x^130 + x^129 + x^128 + x^127 + x^124 + x^123 + x^120 + x^119 + x^117 + x^116 + x^114 + x^112 + x^106 + x^103 + x^101 + x^100 + x^98 + x^94 + x^93 + x^91 + x^86 + x^83 + x^82 + x^78 + x^76 + x^74 + x^73 + x^71 + x^69 + x^66 + x^65 + x^61 + x^60 + x^59 + x^58 + x^57 + x^56 + x^55 + x^54 + x^47 + x^45 + x^44 + x^43 + x^42 + x^40 + x^39 + x^38 + x^34 + x^31 + x^29 + x^28 + x^19 + x^16 + x^12 + x^6 + 1
+
+10-49-23 403 x^928 + x^900 + x^898 + x^893 + x^872 + x^870 + x^865 + x^858 + x^842 + x^840 + x^835 + x^833 + x^826 + x^823 + x^821 + x^816 + x^814 + x^809 + x^808 + x^807 + x^805 + x^802 + x^796 + x^793 + x^782 + x^780 + x^778 + x^775 + x^772 + x^770 + x^767 + x^766 + x^765 + x^760 + x^758 + x^756 + x^754 + x^753 + x^751 + x^747 + x^744 + x^740 + x^739 + x^738 + x^736 + x^735 + x^732 + x^728 + x^724 + x^723 + x^722 + x^720 + x^718 + x^717 + x^714 + x^712 + x^709 + x^708 + x^707 + x^706 + x^705 + x^702 + x^701 + x^700 + x^698 + x^697 + x^696 + x^694 + x^693 + x^691 + x^689 + x^688 + x^684 + x^682 + x^681 + x^680 + x^679 + x^677 + x^676 + x^674 + x^673 + x^672 + x^670 + x^668 + x^667 + x^666 + x^664 + x^661 + x^658 + x^657 + x^655 + x^652 + x^649 + x^646 + x^645 + x^643 + x^641 + x^640 + x^639 + x^638 + x^636 + x^635 + x^634 + x^632 + x^631 + x^628 + x^623 + x^622 + x^620 + x^619 + x^615 + x^612 + x^610 + x^607 + x^604 + x^602 + x^599 + x^598 + x^596 + x^593 + x^590 + x^588 + x^587 + x^585 + x^582 + x^581 + x^578 + x^576 + x^575 + x^573 + x^571 + x^570 + x^569 + x^568 + x^567 + x^565 + x^564 + x^563 + x^559 + x^557 + x^556 + x^552 + x^550 + x^549 + x^548 + x^546 + x^545 + x^543 + x^537 + x^536 + x^535 + x^534 + x^533 + x^532 + x^529 + x^526 + x^525 + x^523 + x^521 + x^520 + x^518 + x^515 + x^513 + x^512 + x^511 + x^508 + x^503 + x^502 + x^499 + x^498 + x^495 + x^494 + x^493 + x^492 + x^491 + x^480 + x^479 + x^475 + x^474 + x^472 + x^470 + x^469 + x^466 + x^465 + x^460 + x^453 + x^452 + x^451 + x^447 + x^444 + x^443 + x^438 + x^436 + x^431 + x^428 + x^426 + x^424 + x^423 + x^421 + x^420 + x^418 + x^417 + x^414 + x^413 + x^411 + x^410 + x^404 + x^401 + x^396 + x^395 + x^390 + x^389 + x^386 + x^384 + x^380 + x^378 + x^377 + x^376 + x^373 + x^372 + x^371 + x^369 + x^364 + x^361 + x^360 + x^359 + x^357 + x^356 + x^354 + x^353 + x^348 + x^347 + x^346 + x^342 + x^341 + x^339 + x^338 + x^333 + x^332 + x^331 + x^329 + x^328 + x^324 + x^322 + x^320 + x^315 + x^314 + x^312 + x^311 + x^310 + x^307 + x^306 + x^303 + x^295 + x^294 + x^292 + x^289 + x^288 + x^283 + x^281 + x^279 + x^274 + x^272 + x^271 + x^270 + x^268 + x^267 + x^266 + x^265 + x^264 + x^261 + x^258 + x^257 + x^256 + x^255 + x^252 + x^251 + x^248 + x^245 + x^243 + x^242 + x^238 + x^237 + x^236 + x^235 + x^233 + x^232 + x^231 + x^229 + x^228 + x^226 + x^225 + x^224 + x^222 + x^221 + x^220 + x^216 + x^215 + x^213 + x^212 + x^210 + x^209 + x^208 + x^202 + x^200 + x^199 + x^197 + x^194 + x^193 + x^192 + x^191 + x^190 + x^189 + x^187 + x^186 + x^184 + x^183 + x^181 + x^179 + x^178 + x^177 + x^176 + x^174 + x^173 + x^172 + x^171 + x^169 + x^166 + x^164 + x^163 + x^160 + x^158 + x^157 + x^155 + x^152 + x^151 + x^150 + x^148 + x^146 + x^145 + x^143 + x^142 + x^137 + x^133 + x^129 + x^126 + x^118 + x^116 + x^114 + x^113 + x^112 + x^110 + x^106 + x^102 + x^99 + x^98 + x^97 + x^96 + x^92 + x^91 + x^88 + x^86 + x^85 + x^83 + x^82 + x^81 + x^79 + x^78 + x^77 + x^76 + x^73 + x^71 + x^67 + x^66 + x^65 + x^64 + x^63 + x^61 + x^60 + x^58 + x^53 + x^52 + x^51 + x^48 + x^47 + x^44 + x^43 + x^42 + x^41 + x^39 + x^37 + x^36 + x^35 + x^34 + x^33 + x^31 + x^26 + x^21 + x^20 + x^16 + x^10 + x^5 + 1
+
+16-2-49 403 x^928 + x^898 + x^870 + x^858 + x^855 + x^846 + x^831 + x^828 + x^822 + x^818 + x^808 + x^800 + x^798 + x^795 + x^788 + x^786 + x^784 + x^783 + x^776 + x^775 + x^770 + x^768 + x^764 + x^759 + x^753 + x^751 + x^750 + x^746 + x^743 + x^740 + x^736 + x^735 + x^734 + x^724 + x^723 + x^719 + x^712 + x^708 + x^706 + x^703 + x^699 + x^698 + x^696 + x^695 + x^694 + x^693 + x^691 + x^690 + x^688 + x^686 + x^684 + x^683 + x^676 + x^675 + x^673 + x^672 + x^671 + x^670 + x^668 + x^664 + x^663 + x^662 + x^660 + x^655 + x^654 + x^652 + x^649 + x^644 + x^642 + x^636 + x^634 + x^633 + x^623 + x^622 + x^620 + x^619 + x^616 + x^614 + x^613 + x^611 + x^609 + x^608 + x^606 + x^604 + x^603 + x^602 + x^601 + x^600 + x^599 + x^598 + x^596 + x^595 + x^594 + x^592 + x^591 + x^588 + x^586 + x^585 + x^584 + x^583 + x^579 + x^578 + x^577 + x^576 + x^575 + x^574 + x^572 + x^569 + x^562 + x^560 + x^555 + x^553 + x^550 + x^549 + x^548 + x^547 + x^546 + x^545 + x^544 + x^543 + x^542 + x^538 + x^536 + x^533 + x^532 + x^531 + x^530 + x^529 + x^528 + x^527 + x^526 + x^523 + x^518 + x^516 + x^515 + x^511 + x^509 + x^507 + x^503 + x^501 + x^500 + x^498 + x^497 + x^496 + x^494 + x^491 + x^490 + x^487 + x^485 + x^484 + x^483 + x^482 + x^478 + x^477 + x^475 + x^474 + x^472 + x^470 + x^468 + x^467 + x^466 + x^465 + x^464 + x^462 + x^461 + x^460 + x^458 + x^457 + x^456 + x^455 + x^454 + x^453 + x^448 + x^446 + x^444 + x^443 + x^442 + x^441 + x^440 + x^437 + x^436 + x^435 + x^434 + x^433 + x^432 + x^431 + x^430 + x^429 + x^428 + x^427 + x^426 + x^425 + x^422 + x^421 + x^420 + x^419 + x^417 + x^414 + x^413 + x^411 + x^410 + x^406 + x^405 + x^400 + x^399 + x^397 + x^395 + x^394 + x^393 + x^392 + x^391 + x^389 + x^387 + x^382 + x^380 + x^379 + x^377 + x^375 + x^371 + x^370 + x^366 + x^365 + x^364 + x^363 + x^362 + x^358 + x^356 + x^354 + x^353 + x^351 + x^348 + x^346 + x^344 + x^343 + x^342 + x^340 + x^339 + x^337 + x^334 + x^333 + x^332 + x^331 + x^328 + x^327 + x^326 + x^325 + x^324 + x^323 + x^322 + x^321 + x^320 + x^318 + x^317 + x^316 + x^315 + x^314 + x^312 + x^310 + x^306 + x^304 + x^303 + x^301 + x^297 + x^295 + x^293 + x^290 + x^285 + x^283 + x^282 + x^273 + x^271 + x^268 + x^266 + x^262 + x^260 + x^259 + x^258 + x^253 + x^252 + x^251 + x^249 + x^248 + x^246 + x^245 + x^244 + x^243 + x^242 + x^241 + x^240 + x^239 + x^238 + x^237 + x^234 + x^233 + x^231 + x^229 + x^228 + x^227 + x^223 + x^222 + x^217 + x^216 + x^214 + x^213 + x^211 + x^209 + x^208 + x^203 + x^202 + x^201 + x^198 + x^197 + x^196 + x^194 + x^193 + x^189 + x^188 + x^187 + x^183 + x^182 + x^181 + x^177 + x^176 + x^174 + x^170 + x^166 + x^163 + x^162 + x^161 + x^160 + x^158 + x^157 + x^155 + x^153 + x^152 + x^151 + x^149 + x^148 + x^147 + x^146 + x^144 + x^142 + x^141 + x^140 + x^138 + x^132 + x^131 + x^129 + x^128 + x^126 + x^125 + x^123 + x^122 + x^120 + x^119 + x^117 + x^116 + x^115 + x^112 + x^109 + x^107 + x^105 + x^101 + x^97 + x^95 + x^94 + x^92 + x^91 + x^90 + x^87 + x^86 + x^85 + x^83 + x^82 + x^81 + x^79 + x^78 + x^77 + x^73 + x^72 + x^71 + x^68 + x^67 + x^65 + x^64 + x^63 + x^60 + x^58 + x^54 + x^50 + x^45 + x^44 + x^43 + x^42 + x^39 + x^37 + x^26 + x^24 + x^20 + x^16 + x^14 + x^10 + 1
+
+17-2-46 405 x^928 + x^898 + x^870 + x^860 + x^848 + x^831 + x^830 + x^818 + x^812 + x^808 + x^804 + x^801 + x^800 + x^792 + x^791 + x^790 + x^784 + x^780 + x^778 + x^775 + x^774 + x^770 + x^763 + x^762 + x^761 + x^760 + x^756 + x^754 + x^750 + x^748 + x^746 + x^745 + x^736 + x^733 + x^732 + x^731 + x^729 + x^728 + x^726 + x^720 + x^719 + x^712 + x^710 + x^706 + x^703 + x^702 + x^701 + x^700 + x^690 + x^686 + x^682 + x^679 + x^676 + x^675 + x^673 + x^672 + x^671 + x^669 + x^668 + x^666 + x^664 + x^663 + x^658 + x^652 + x^651 + x^648 + x^647 + x^646 + x^645 + x^644 + x^643 + x^642 + x^638 + x^633 + x^628 + x^626 + x^624 + x^623 + x^621 + x^617 + x^616 + x^614 + x^613 + x^611 + x^610 + x^609 + x^607 + x^606 + x^604 + x^599 + x^595 + x^594 + x^593 + x^592 + x^590 + x^589 + x^588 + x^584 + x^580 + x^579 + x^578 + x^577 + x^576 + x^574 + x^573 + x^569 + x^568 + x^567 + x^566 + x^564 + x^563 + x^561 + x^560 + x^559 + x^558 + x^557 + x^555 + x^549 + x^548 + x^547 + x^544 + x^543 + x^542 + x^540 + x^535 + x^534 + x^532 + x^531 + x^530 + x^528 + x^527 + x^526 + x^525 + x^520 + x^519 + x^516 + x^513 + x^510 + x^509 + x^500 + x^499 + x^498 + x^497 + x^496 + x^493 + x^492 + x^490 + x^489 + x^487 + x^484 + x^482 + x^480 + x^479 + x^476 + x^473 + x^471 + x^470 + x^468 + x^467 + x^465 + x^464 + x^463 + x^462 + x^461 + x^460 + x^457 + x^453 + x^452 + x^450 + x^448 + x^447 + x^443 + x^440 + x^439 + x^438 + x^437 + x^431 + x^430 + x^429 + x^428 + x^427 + x^426 + x^424 + x^423 + x^420 + x^418 + x^416 + x^415 + x^408 + x^407 + x^406 + x^405 + x^404 + x^403 + x^397 + x^395 + x^391 + x^389 + x^388 + x^386 + x^384 + x^382 + x^381 + x^380 + x^379 + x^378 + x^375 + x^374 + x^373 + x^372 + x^371 + x^369 + x^368 + x^365 + x^364 + x^363 + x^362 + x^360 + x^359 + x^358 + x^357 + x^353 + x^351 + x^350 + x^345 + x^341 + x^338 + x^336 + x^335 + x^334 + x^332 + x^331 + x^329 + x^328 + x^327 + x^323 + x^321 + x^320 + x^319 + x^317 + x^316 + x^314 + x^313 + x^312 + x^310 + x^308 + x^307 + x^304 + x^303 + x^300 + x^297 + x^295 + x^293 + x^291 + x^289 + x^288 + x^287 + x^285 + x^284 + x^282 + x^280 + x^279 + x^277 + x^273 + x^272 + x^269 + x^268 + x^267 + x^266 + x^265 + x^264 + x^262 + x^260 + x^259 + x^256 + x^255 + x^254 + x^253 + x^249 + x^247 + x^245 + x^243 + x^241 + x^240 + x^239 + x^238 + x^237 + x^236 + x^235 + x^234 + x^232 + x^228 + x^226 + x^225 + x^224 + x^223 + x^220 + x^219 + x^217 + x^209 + x^207 + x^206 + x^204 + x^203 + x^202 + x^201 + x^200 + x^199 + x^195 + x^194 + x^189 + x^187 + x^186 + x^185 + x^184 + x^182 + x^180 + x^178 + x^174 + x^173 + x^172 + x^170 + x^167 + x^166 + x^165 + x^164 + x^162 + x^161 + x^160 + x^159 + x^158 + x^157 + x^156 + x^155 + x^154 + x^153 + x^152 + x^151 + x^147 + x^143 + x^141 + x^138 + x^131 + x^130 + x^129 + x^127 + x^122 + x^121 + x^120 + x^118 + x^117 + x^114 + x^111 + x^109 + x^106 + x^105 + x^104 + x^103 + x^102 + x^101 + x^100 + x^99 + x^98 + x^97 + x^93 + x^92 + x^91 + x^90 + x^88 + x^87 + x^86 + x^82 + x^81 + x^80 + x^79 + x^78 + x^77 + x^75 + x^74 + x^73 + x^72 + x^71 + x^68 + x^59 + x^58 + x^51 + x^50 + x^43 + x^42 + x^40 + x^39 + x^34 + x^27 + x^25 + x^24 + x^23 + x^19 + x^18 + x^17 + x^15 + x^14 + x^13 + x^11 + x^10 + 1
+
+55-23-24 407 x^928 + x^898 + x^894 + x^870 + x^865 + x^849 + x^846 + x^842 + x^834 + x^833 + x^830 + x^826 + x^820 + x^819 + x^816 + x^813 + x^808 + x^804 + x^803 + x^794 + x^782 + x^778 + x^772 + x^769 + x^767 + x^765 + x^764 + x^762 + x^760 + x^759 + x^756 + x^751 + x^745 + x^743 + x^740 + x^739 + x^737 + x^733 + x^730 + x^729 + x^728 + x^726 + x^722 + x^720 + x^718 + x^717 + x^716 + x^713 + x^710 + x^709 + x^708 + x^707 + x^702 + x^699 + x^697 + x^694 + x^692 + x^691 + x^688 + x^687 + x^682 + x^680 + x^679 + x^678 + x^676 + x^675 + x^674 + x^670 + x^668 + x^666 + x^662 + x^661 + x^657 + x^652 + x^651 + x^649 + x^647 + x^646 + x^643 + x^640 + x^639 + x^637 + x^635 + x^634 + x^633 + x^630 + x^618 + x^616 + x^614 + x^613 + x^611 + x^610 + x^609 + x^608 + x^604 + x^603 + x^602 + x^597 + x^593 + x^591 + x^590 + x^585 + x^582 + x^579 + x^575 + x^574 + x^573 + x^572 + x^569 + x^567 + x^566 + x^564 + x^562 + x^561 + x^560 + x^557 + x^556 + x^555 + x^554 + x^551 + x^550 + x^547 + x^545 + x^542 + x^541 + x^539 + x^537 + x^534 + x^531 + x^530 + x^529 + x^528 + x^526 + x^525 + x^523 + x^522 + x^521 + x^520 + x^519 + x^518 + x^517 + x^515 + x^514 + x^513 + x^511 + x^510 + x^509 + x^508 + x^506 + x^505 + x^500 + x^498 + x^492 + x^485 + x^484 + x^480 + x^479 + x^478 + x^477 + x^476 + x^471 + x^470 + x^469 + x^467 + x^465 + x^464 + x^462 + x^458 + x^457 + x^454 + x^453 + x^452 + x^451 + x^450 + x^445 + x^444 + x^443 + x^441 + x^439 + x^437 + x^436 + x^435 + x^433 + x^431 + x^430 + x^429 + x^425 + x^424 + x^421 + x^419 + x^414 + x^411 + x^410 + x^409 + x^408 + x^407 + x^406 + x^405 + x^396 + x^395 + x^394 + x^390 + x^388 + x^385 + x^383 + x^381 + x^379 + x^378 + x^377 + x^376 + x^375 + x^373 + x^371 + x^369 + x^368 + x^367 + x^360 + x^358 + x^353 + x^351 + x^350 + x^348 + x^347 + x^346 + x^344 + x^343 + x^341 + x^338 + x^336 + x^335 + x^334 + x^331 + x^330 + x^329 + x^327 + x^326 + x^324 + x^322 + x^318 + x^314 + x^313 + x^311 + x^307 + x^305 + x^304 + x^302 + x^299 + x^295 + x^294 + x^293 + x^289 + x^284 + x^282 + x^281 + x^277 + x^272 + x^271 + x^270 + x^268 + x^265 + x^262 + x^259 + x^257 + x^254 + x^252 + x^251 + x^250 + x^249 + x^244 + x^243 + x^240 + x^239 + x^237 + x^235 + x^234 + x^233 + x^231 + x^230 + x^227 + x^224 + x^223 + x^220 + x^219 + x^218 + x^217 + x^215 + x^214 + x^213 + x^212 + x^211 + x^210 + x^209 + x^208 + x^207 + x^205 + x^203 + x^201 + x^200 + x^197 + x^193 + x^190 + x^187 + x^185 + x^184 + x^181 + x^180 + x^179 + x^178 + x^177 + x^176 + x^175 + x^173 + x^172 + x^169 + x^168 + x^166 + x^165 + x^162 + x^159 + x^157 + x^156 + x^155 + x^154 + x^153 + x^152 + x^150 + x^146 + x^145 + x^143 + x^142 + x^141 + x^139 + x^138 + x^137 + x^136 + x^135 + x^134 + x^132 + x^129 + x^123 + x^119 + x^115 + x^114 + x^112 + x^108 + x^107 + x^102 + x^100 + x^98 + x^97 + x^95 + x^93 + x^91 + x^90 + x^87 + x^86 + x^85 + x^83 + x^80 + x^79 + x^78 + x^77 + x^75 + x^74 + x^72 + x^69 + x^68 + x^65 + x^64 + x^61 + x^60 + x^59 + x^58 + x^57 + x^56 + x^55 + x^52 + x^51 + x^50 + x^45 + x^44 + x^43 + x^42 + x^41 + x^40 + x^39 + x^38 + x^37 + x^34 + x^33 + x^31 + x^30 + x^29 + x^26 + x^25 + x^23 + x^21 + x^20 + x^19 + x^18 + x^17 + x^13 + x^12 + x^10 + x^8 + x^7 + 1
+
+15-3-26 409 x^928 + x^898 + x^892 + x^870 + x^863 + x^862 + x^858 + x^856 + x^852 + x^833 + x^828 + x^827 + x^826 + x^824 + x^823 + x^820 + x^818 + x^816 + x^812 + x^808 + x^803 + x^801 + x^799 + x^798 + x^797 + x^795 + x^794 + x^792 + x^791 + x^790 + x^788 + x^787 + x^784 + x^783 + x^782 + x^773 + x^772 + x^771 + x^768 + x^767 + x^763 + x^760 + x^757 + x^753 + x^750 + x^746 + x^744 + x^737 + x^732 + x^731 + x^730 + x^729 + x^728 + x^726 + x^725 + x^724 + x^721 + x^717 + x^716 + x^713 + x^711 + x^710 + x^707 + x^706 + x^705 + x^703 + x^702 + x^699 + x^698 + x^697 + x^696 + x^694 + x^692 + x^691 + x^690 + x^689 + x^688 + x^681 + x^678 + x^673 + x^668 + x^666 + x^662 + x^658 + x^657 + x^656 + x^653 + x^651 + x^650 + x^648 + x^644 + x^643 + x^642 + x^641 + x^640 + x^638 + x^636 + x^635 + x^634 + x^633 + x^632 + x^630 + x^628 + x^626 + x^625 + x^623 + x^622 + x^620 + x^618 + x^617 + x^616 + x^614 + x^611 + x^609 + x^607 + x^605 + x^603 + x^596 + x^595 + x^593 + x^592 + x^590 + x^587 + x^586 + x^585 + x^584 + x^583 + x^582 + x^580 + x^577 + x^576 + x^571 + x^569 + x^568 + x^567 + x^565 + x^564 + x^562 + x^561 + x^560 + x^559 + x^558 + x^557 + x^556 + x^555 + x^554 + x^553 + x^552 + x^548 + x^547 + x^546 + x^545 + x^544 + x^542 + x^538 + x^537 + x^534 + x^533 + x^532 + x^529 + x^527 + x^526 + x^524 + x^523 + x^518 + x^517 + x^509 + x^505 + x^503 + x^501 + x^494 + x^493 + x^492 + x^488 + x^485 + x^484 + x^483 + x^480 + x^479 + x^477 + x^475 + x^474 + x^473 + x^472 + x^471 + x^469 + x^467 + x^466 + x^465 + x^464 + x^461 + x^458 + x^456 + x^453 + x^449 + x^446 + x^445 + x^444 + x^443 + x^442 + x^441 + x^440 + x^436 + x^435 + x^432 + x^431 + x^427 + x^426 + x^423 + x^418 + x^415 + x^413 + x^412 + x^411 + x^410 + x^407 + x^406 + x^403 + x^400 + x^398 + x^396 + x^385 + x^383 + x^382 + x^381 + x^375 + x^374 + x^373 + x^364 + x^363 + x^362 + x^360 + x^358 + x^357 + x^355 + x^353 + x^352 + x^350 + x^349 + x^345 + x^344 + x^343 + x^342 + x^340 + x^337 + x^331 + x^329 + x^328 + x^325 + x^324 + x^323 + x^322 + x^315 + x^314 + x^312 + x^311 + x^308 + x^304 + x^303 + x^299 + x^297 + x^295 + x^289 + x^284 + x^282 + x^281 + x^279 + x^278 + x^275 + x^274 + x^273 + x^272 + x^271 + x^270 + x^264 + x^263 + x^262 + x^260 + x^259 + x^258 + x^256 + x^253 + x^252 + x^251 + x^250 + x^248 + x^247 + x^246 + x^245 + x^243 + x^242 + x^240 + x^238 + x^237 + x^234 + x^233 + x^232 + x^231 + x^230 + x^228 + x^221 + x^220 + x^218 + x^213 + x^212 + x^211 + x^210 + x^204 + x^201 + x^200 + x^196 + x^195 + x^194 + x^191 + x^190 + x^189 + x^185 + x^183 + x^181 + x^180 + x^178 + x^176 + x^175 + x^170 + x^168 + x^167 + x^165 + x^164 + x^163 + x^160 + x^159 + x^158 + x^157 + x^156 + x^153 + x^151 + x^150 + x^149 + x^146 + x^145 + x^141 + x^139 + x^138 + x^137 + x^133 + x^132 + x^131 + x^130 + x^129 + x^128 + x^127 + x^125 + x^124 + x^122 + x^121 + x^120 + x^116 + x^115 + x^114 + x^112 + x^110 + x^106 + x^103 + x^101 + x^100 + x^97 + x^96 + x^94 + x^93 + x^92 + x^89 + x^88 + x^87 + x^86 + x^85 + x^84 + x^82 + x^80 + x^77 + x^73 + x^72 + x^71 + x^70 + x^68 + x^67 + x^65 + x^61 + x^60 + x^59 + x^56 + x^55 + x^48 + x^47 + x^42 + x^41 + x^36 + x^35 + x^33 + x^29 + x^27 + x^26 + x^25 + x^18 + x^14 + x^12 + x^10 + x^8 + x^4 + 1
+
+33-51-22 409 x^928 + x^898 + x^886 + x^870 + x^859 + x^858 + x^857 + x^844 + x^842 + x^832 + x^831 + x^830 + x^826 + x^815 + x^812 + x^808 + x^805 + x^804 + x^803 + x^799 + x^798 + x^789 + x^787 + x^786 + x^785 + x^782 + x^777 + x^776 + x^775 + x^771 + x^769 + x^766 + x^763 + x^758 + x^756 + x^752 + x^749 + x^745 + x^744 + x^743 + x^741 + x^739 + x^738 + x^735 + x^734 + x^731 + x^730 + x^721 + x^720 + x^715 + x^714 + x^711 + x^708 + x^705 + x^704 + x^703 + x^700 + x^698 + x^695 + x^693 + x^692 + x^690 + x^689 + x^684 + x^682 + x^681 + x^680 + x^677 + x^676 + x^673 + x^672 + x^671 + x^667 + x^666 + x^662 + x^661 + x^658 + x^656 + x^653 + x^652 + x^650 + x^649 + x^647 + x^646 + x^645 + x^644 + x^642 + x^641 + x^640 + x^638 + x^636 + x^634 + x^633 + x^632 + x^631 + x^629 + x^625 + x^624 + x^623 + x^621 + x^620 + x^619 + x^616 + x^615 + x^614 + x^612 + x^611 + x^609 + x^606 + x^605 + x^602 + x^601 + x^599 + x^596 + x^595 + x^592 + x^591 + x^588 + x^587 + x^586 + x^583 + x^582 + x^578 + x^577 + x^576 + x^574 + x^572 + x^570 + x^566 + x^562 + x^560 + x^555 + x^552 + x^550 + x^546 + x^545 + x^544 + x^543 + x^542 + x^539 + x^537 + x^535 + x^534 + x^533 + x^532 + x^531 + x^530 + x^528 + x^525 + x^524 + x^523 + x^519 + x^516 + x^513 + x^511 + x^509 + x^508 + x^507 + x^506 + x^504 + x^500 + x^499 + x^497 + x^495 + x^494 + x^491 + x^488 + x^487 + x^484 + x^480 + x^479 + x^476 + x^474 + x^472 + x^471 + x^470 + x^469 + x^468 + x^464 + x^463 + x^462 + x^460 + x^459 + x^458 + x^457 + x^456 + x^455 + x^454 + x^451 + x^449 + x^448 + x^447 + x^446 + x^445 + x^441 + x^440 + x^439 + x^437 + x^436 + x^435 + x^431 + x^430 + x^427 + x^423 + x^421 + x^419 + x^412 + x^410 + x^407 + x^406 + x^405 + x^404 + x^402 + x^400 + x^398 + x^396 + x^395 + x^394 + x^392 + x^389 + x^387 + x^386 + x^384 + x^382 + x^381 + x^379 + x^378 + x^377 + x^375 + x^372 + x^370 + x^369 + x^363 + x^361 + x^360 + x^351 + x^350 + x^347 + x^343 + x^342 + x^340 + x^336 + x^328 + x^327 + x^326 + x^325 + x^323 + x^322 + x^321 + x^319 + x^318 + x^315 + x^314 + x^313 + x^312 + x^311 + x^308 + x^307 + x^305 + x^301 + x^299 + x^298 + x^295 + x^293 + x^292 + x^290 + x^289 + x^288 + x^286 + x^284 + x^283 + x^282 + x^280 + x^279 + x^275 + x^273 + x^272 + x^269 + x^266 + x^265 + x^264 + x^255 + x^251 + x^249 + x^248 + x^246 + x^244 + x^243 + x^242 + x^238 + x^237 + x^235 + x^234 + x^232 + x^230 + x^229 + x^228 + x^226 + x^224 + x^222 + x^221 + x^218 + x^213 + x^210 + x^209 + x^208 + x^206 + x^203 + x^200 + x^195 + x^193 + x^189 + x^187 + x^186 + x^185 + x^182 + x^181 + x^179 + x^177 + x^174 + x^173 + x^170 + x^163 + x^160 + x^159 + x^155 + x^154 + x^153 + x^152 + x^151 + x^147 + x^146 + x^143 + x^141 + x^133 + x^130 + x^128 + x^125 + x^123 + x^118 + x^116 + x^115 + x^114 + x^110 + x^108 + x^106 + x^105 + x^104 + x^102 + x^100 + x^98 + x^97 + x^96 + x^95 + x^94 + x^93 + x^92 + x^91 + x^89 + x^85 + x^84 + x^83 + x^82 + x^81 + x^80 + x^78 + x^77 + x^74 + x^72 + x^71 + x^69 + x^67 + x^65 + x^64 + x^63 + x^61 + x^58 + x^56 + x^55 + x^53 + x^52 + x^51 + x^50 + x^48 + x^47 + x^46 + x^45 + x^44 + x^43 + x^40 + x^39 + x^37 + x^36 + x^35 + x^33 + x^32 + x^30 + x^29 + x^28 + x^27 + x^25 + x^23 + x^21 + x^16 + x^13 + x^11 + x^10 + x^8 + 1
+
+23-25-12 411 x^928 + x^898 + x^873 + x^870 + x^852 + x^847 + x^844 + x^842 + x^838 + x^836 + x^831 + x^828 + x^822 + x^821 + x^818 + x^817 + x^816 + x^814 + x^813 + x^812 + x^811 + x^801 + x^800 + x^798 + x^797 + x^796 + x^792 + x^791 + x^790 + x^787 + x^780 + x^776 + x^774 + x^770 + x^768 + x^766 + x^765 + x^762 + x^761 + x^759 + x^758 + x^757 + x^756 + x^755 + x^752 + x^749 + x^748 + x^747 + x^744 + x^741 + x^740 + x^739 + x^737 + x^735 + x^731 + x^730 + x^729 + x^728 + x^726 + x^725 + x^722 + x^720 + x^718 + x^714 + x^712 + x^711 + x^709 + x^708 + x^707 + x^706 + x^705 + x^704 + x^698 + x^694 + x^693 + x^691 + x^689 + x^686 + x^685 + x^682 + x^676 + x^674 + x^670 + x^669 + x^668 + x^667 + x^666 + x^665 + x^662 + x^661 + x^658 + x^655 + x^653 + x^652 + x^650 + x^647 + x^644 + x^643 + x^640 + x^638 + x^637 + x^635 + x^634 + x^632 + x^629 + x^627 + x^626 + x^625 + x^624 + x^619 + x^618 + x^614 + x^612 + x^611 + x^606 + x^604 + x^602 + x^601 + x^595 + x^592 + x^591 + x^590 + x^589 + x^586 + x^585 + x^581 + x^579 + x^578 + x^575 + x^574 + x^573 + x^568 + x^567 + x^565 + x^564 + x^562 + x^560 + x^559 + x^558 + x^555 + x^554 + x^553 + x^552 + x^549 + x^548 + x^546 + x^544 + x^543 + x^542 + x^541 + x^538 + x^533 + x^532 + x^529 + x^527 + x^526 + x^523 + x^522 + x^521 + x^517 + x^511 + x^508 + x^507 + x^506 + x^505 + x^504 + x^502 + x^497 + x^496 + x^494 + x^490 + x^489 + x^486 + x^485 + x^483 + x^482 + x^481 + x^477 + x^475 + x^473 + x^472 + x^471 + x^468 + x^464 + x^462 + x^461 + x^460 + x^459 + x^455 + x^454 + x^453 + x^449 + x^448 + x^447 + x^446 + x^438 + x^437 + x^435 + x^432 + x^429 + x^427 + x^426 + x^424 + x^423 + x^421 + x^419 + x^418 + x^416 + x^415 + x^413 + x^412 + x^410 + x^409 + x^407 + x^406 + x^405 + x^402 + x^401 + x^399 + x^397 + x^396 + x^394 + x^393 + x^390 + x^385 + x^384 + x^383 + x^380 + x^379 + x^378 + x^376 + x^375 + x^374 + x^372 + x^370 + x^368 + x^367 + x^366 + x^365 + x^363 + x^359 + x^357 + x^356 + x^355 + x^354 + x^353 + x^352 + x^351 + x^350 + x^348 + x^345 + x^341 + x^337 + x^335 + x^334 + x^333 + x^325 + x^319 + x^317 + x^316 + x^314 + x^305 + x^301 + x^298 + x^294 + x^293 + x^292 + x^289 + x^284 + x^281 + x^279 + x^272 + x^271 + x^269 + x^268 + x^267 + x^266 + x^264 + x^263 + x^262 + x^261 + x^256 + x^255 + x^254 + x^252 + x^251 + x^249 + x^248 + x^244 + x^242 + x^239 + x^235 + x^234 + x^233 + x^227 + x^226 + x^223 + x^221 + x^220 + x^216 + x^214 + x^211 + x^209 + x^207 + x^205 + x^204 + x^200 + x^198 + x^197 + x^196 + x^193 + x^188 + x^187 + x^185 + x^183 + x^182 + x^181 + x^179 + x^177 + x^175 + x^173 + x^172 + x^170 + x^169 + x^167 + x^163 + x^162 + x^158 + x^157 + x^156 + x^155 + x^154 + x^152 + x^151 + x^150 + x^149 + x^145 + x^144 + x^143 + x^142 + x^140 + x^139 + x^138 + x^136 + x^135 + x^133 + x^131 + x^130 + x^127 + x^126 + x^124 + x^122 + x^120 + x^118 + x^116 + x^114 + x^111 + x^110 + x^106 + x^105 + x^103 + x^102 + x^101 + x^100 + x^99 + x^98 + x^97 + x^95 + x^94 + x^93 + x^92 + x^91 + x^90 + x^86 + x^85 + x^82 + x^81 + x^78 + x^75 + x^70 + x^66 + x^64 + x^63 + x^61 + x^60 + x^55 + x^54 + x^53 + x^52 + x^50 + x^49 + x^46 + x^45 + x^44 + x^41 + x^38 + x^37 + x^36 + x^32 + x^30 + x^29 + x^27 + x^22 + x^18 + x^16 + x^13 + x^9 + x^8 + x^7 + x^5 + 1
+
+36-2-35 411 x^928 + x^898 + x^870 + x^860 + x^850 + x^842 + x^830 + x^824 + x^822 + x^812 + x^808 + x^806 + x^804 + x^794 + x^792 + x^790 + x^786 + x^782 + x^778 + x^776 + x^772 + x^768 + x^764 + x^754 + x^752 + x^747 + x^746 + x^745 + x^742 + x^738 + x^737 + x^736 + x^729 + x^728 + x^725 + x^724 + x^722 + x^721 + x^720 + x^718 + x^716 + x^715 + x^714 + x^712 + x^707 + x^706 + x^704 + x^703 + x^701 + x^699 + x^693 + x^691 + x^689 + x^688 + x^687 + x^684 + x^682 + x^680 + x^674 + x^672 + x^671 + x^669 + x^668 + x^663 + x^662 + x^661 + x^660 + x^659 + x^658 + x^654 + x^652 + x^651 + x^650 + x^649 + x^648 + x^644 + x^642 + x^640 + x^639 + x^638 + x^635 + x^633 + x^632 + x^624 + x^623 + x^620 + x^619 + x^618 + x^617 + x^611 + x^607 + x^606 + x^604 + x^602 + x^601 + x^600 + x^598 + x^596 + x^594 + x^593 + x^589 + x^585 + x^583 + x^580 + x^579 + x^577 + x^576 + x^575 + x^569 + x^568 + x^567 + x^565 + x^564 + x^563 + x^560 + x^558 + x^556 + x^555 + x^553 + x^552 + x^551 + x^549 + x^548 + x^545 + x^542 + x^541 + x^539 + x^536 + x^534 + x^533 + x^530 + x^528 + x^526 + x^524 + x^521 + x^519 + x^517 + x^515 + x^511 + x^510 + x^508 + x^506 + x^505 + x^504 + x^503 + x^502 + x^501 + x^500 + x^499 + x^498 + x^496 + x^494 + x^493 + x^492 + x^491 + x^490 + x^488 + x^487 + x^484 + x^480 + x^478 + x^474 + x^472 + x^471 + x^466 + x^465 + x^464 + x^463 + x^461 + x^459 + x^458 + x^457 + x^454 + x^453 + x^446 + x^445 + x^441 + x^440 + x^439 + x^438 + x^437 + x^436 + x^435 + x^433 + x^428 + x^427 + x^426 + x^425 + x^414 + x^407 + x^406 + x^405 + x^403 + x^402 + x^401 + x^399 + x^398 + x^397 + x^395 + x^393 + x^392 + x^391 + x^390 + x^387 + x^385 + x^384 + x^383 + x^382 + x^379 + x^378 + x^376 + x^374 + x^373 + x^371 + x^370 + x^369 + x^367 + x^365 + x^364 + x^363 + x^359 + x^357 + x^355 + x^354 + x^353 + x^350 + x^346 + x^345 + x^344 + x^343 + x^340 + x^338 + x^337 + x^336 + x^335 + x^330 + x^322 + x^321 + x^320 + x^317 + x^316 + x^311 + x^310 + x^309 + x^308 + x^307 + x^306 + x^305 + x^303 + x^299 + x^296 + x^295 + x^294 + x^293 + x^292 + x^289 + x^288 + x^284 + x^283 + x^282 + x^281 + x^280 + x^278 + x^277 + x^275 + x^269 + x^265 + x^263 + x^260 + x^258 + x^256 + x^255 + x^254 + x^253 + x^252 + x^250 + x^247 + x^246 + x^245 + x^242 + x^241 + x^240 + x^239 + x^234 + x^233 + x^232 + x^230 + x^226 + x^224 + x^222 + x^218 + x^217 + x^216 + x^215 + x^214 + x^213 + x^212 + x^211 + x^208 + x^204 + x^203 + x^202 + x^201 + x^200 + x^199 + x^198 + x^197 + x^194 + x^192 + x^191 + x^184 + x^181 + x^180 + x^178 + x^177 + x^176 + x^175 + x^174 + x^173 + x^171 + x^170 + x^168 + x^166 + x^161 + x^160 + x^157 + x^156 + x^153 + x^149 + x^148 + x^147 + x^145 + x^144 + x^143 + x^139 + x^137 + x^136 + x^134 + x^132 + x^131 + x^130 + x^129 + x^128 + x^127 + x^126 + x^125 + x^118 + x^117 + x^116 + x^115 + x^114 + x^113 + x^111 + x^107 + x^105 + x^104 + x^103 + x^101 + x^99 + x^98 + x^97 + x^96 + x^92 + x^91 + x^90 + x^89 + x^87 + x^86 + x^85 + x^84 + x^83 + x^82 + x^81 + x^79 + x^78 + x^76 + x^72 + x^70 + x^69 + x^68 + x^67 + x^65 + x^64 + x^62 + x^61 + x^58 + x^54 + x^53 + x^52 + x^50 + x^49 + x^48 + x^46 + x^44 + x^43 + x^40 + x^39 + x^38 + x^35 + x^32 + x^31 + x^30 + x^29 + x^28 + x^27 + x^25 + x^24 + x^18 + x^14 + x^10 + 1
+
+55-5-54 411 x^928 + x^898 + x^883 + x^870 + x^867 + x^854 + x^853 + x^840 + x^838 + x^837 + x^836 + x^834 + x^824 + x^822 + x^820 + x^812 + x^811 + x^810 + x^808 + x^807 + x^800 + x^797 + x^796 + x^795 + x^793 + x^792 + x^783 + x^779 + x^777 + x^776 + x^774 + x^770 + x^769 + x^768 + x^764 + x^762 + x^760 + x^754 + x^753 + x^752 + x^751 + x^749 + x^747 + x^746 + x^741 + x^740 + x^739 + x^738 + x^735 + x^734 + x^733 + x^732 + x^730 + x^728 + x^723 + x^721 + x^717 + x^715 + x^712 + x^711 + x^710 + x^709 + x^708 + x^707 + x^706 + x^704 + x^702 + x^701 + x^698 + x^697 + x^696 + x^694 + x^692 + x^690 + x^687 + x^683 + x^682 + x^681 + x^675 + x^674 + x^670 + x^669 + x^667 + x^666 + x^664 + x^663 + x^662 + x^657 + x^655 + x^654 + x^653 + x^652 + x^650 + x^646 + x^645 + x^644 + x^642 + x^637 + x^636 + x^635 + x^630 + x^629 + x^627 + x^626 + x^620 + x^615 + x^614 + x^612 + x^611 + x^610 + x^607 + x^606 + x^605 + x^604 + x^603 + x^601 + x^597 + x^594 + x^592 + x^591 + x^586 + x^584 + x^583 + x^582 + x^579 + x^577 + x^574 + x^573 + x^568 + x^565 + x^564 + x^563 + x^562 + x^557 + x^554 + x^553 + x^552 + x^550 + x^548 + x^546 + x^544 + x^543 + x^542 + x^540 + x^537 + x^536 + x^532 + x^531 + x^530 + x^524 + x^521 + x^520 + x^519 + x^517 + x^516 + x^515 + x^513 + x^512 + x^510 + x^509 + x^507 + x^505 + x^503 + x^502 + x^501 + x^500 + x^497 + x^496 + x^488 + x^486 + x^484 + x^481 + x^480 + x^478 + x^476 + x^475 + x^467 + x^465 + x^464 + x^463 + x^459 + x^458 + x^457 + x^455 + x^453 + x^452 + x^446 + x^445 + x^444 + x^441 + x^440 + x^439 + x^438 + x^434 + x^433 + x^432 + x^431 + x^427 + x^425 + x^421 + x^414 + x^412 + x^411 + x^409 + x^407 + x^406 + x^405 + x^403 + x^401 + x^400 + x^399 + x^398 + x^397 + x^396 + x^395 + x^394 + x^389 + x^385 + x^383 + x^380 + x^377 + x^376 + x^375 + x^374 + x^373 + x^372 + x^370 + x^367 + x^366 + x^365 + x^364 + x^363 + x^362 + x^360 + x^358 + x^356 + x^352 + x^351 + x^350 + x^349 + x^346 + x^342 + x^340 + x^338 + x^336 + x^335 + x^330 + x^329 + x^325 + x^324 + x^323 + x^321 + x^317 + x^314 + x^312 + x^307 + x^304 + x^303 + x^301 + x^298 + x^296 + x^295 + x^294 + x^293 + x^290 + x^289 + x^288 + x^287 + x^286 + x^285 + x^278 + x^277 + x^274 + x^273 + x^272 + x^270 + x^267 + x^265 + x^261 + x^260 + x^259 + x^258 + x^257 + x^256 + x^255 + x^254 + x^252 + x^249 + x^243 + x^241 + x^240 + x^239 + x^238 + x^235 + x^232 + x^231 + x^230 + x^227 + x^226 + x^224 + x^222 + x^221 + x^220 + x^219 + x^215 + x^214 + x^213 + x^211 + x^210 + x^209 + x^207 + x^206 + x^205 + x^200 + x^198 + x^195 + x^193 + x^192 + x^190 + x^186 + x^185 + x^181 + x^178 + x^176 + x^175 + x^172 + x^170 + x^169 + x^168 + x^166 + x^164 + x^158 + x^153 + x^152 + x^150 + x^149 + x^148 + x^147 + x^146 + x^143 + x^142 + x^140 + x^139 + x^136 + x^134 + x^133 + x^128 + x^125 + x^122 + x^121 + x^120 + x^116 + x^115 + x^114 + x^112 + x^111 + x^107 + x^106 + x^103 + x^101 + x^100 + x^99 + x^98 + x^96 + x^95 + x^94 + x^93 + x^92 + x^89 + x^88 + x^87 + x^86 + x^85 + x^84 + x^81 + x^79 + x^77 + x^76 + x^74 + x^72 + x^71 + x^70 + x^69 + x^65 + x^64 + x^62 + x^55 + x^53 + x^49 + x^48 + x^46 + x^44 + x^43 + x^39 + x^38 + x^37 + x^35 + x^33 + x^32 + x^31 + x^29 + x^27 + x^26 + x^24 + x^23 + x^22 + x^20 + x^18 + x^17 + x^8 + 1
+
+14-19-11 415 x^928 + x^898 + x^882 + x^870 + x^863 + x^862 + x^860 + x^854 + x^852 + x^844 + x^843 + x^833 + x^832 + x^825 + x^822 + x^819 + x^817 + x^816 + x^813 + x^811 + x^802 + x^798 + x^797 + x^795 + x^794 + x^790 + x^789 + x^787 + x^786 + x^781 + x^778 + x^776 + x^773 + x^772 + x^771 + x^768 + x^759 + x^754 + x^749 + x^746 + x^744 + x^743 + x^741 + x^740 + x^735 + x^734 + x^733 + x^730 + x^729 + x^725 + x^722 + x^717 + x^714 + x^711 + x^706 + x^705 + x^698 + x^697 + x^695 + x^691 + x^687 + x^686 + x^683 + x^681 + x^675 + x^674 + x^673 + x^672 + x^671 + x^670 + x^667 + x^665 + x^662 + x^661 + x^660 + x^658 + x^657 + x^656 + x^655 + x^654 + x^652 + x^649 + x^648 + x^645 + x^643 + x^642 + x^638 + x^636 + x^635 + x^634 + x^633 + x^632 + x^630 + x^629 + x^627 + x^626 + x^625 + x^624 + x^623 + x^622 + x^620 + x^617 + x^616 + x^615 + x^613 + x^612 + x^611 + x^608 + x^604 + x^603 + x^602 + x^599 + x^598 + x^597 + x^595 + x^594 + x^593 + x^592 + x^591 + x^590 + x^587 + x^586 + x^585 + x^584 + x^582 + x^580 + x^579 + x^578 + x^577 + x^570 + x^569 + x^568 + x^567 + x^566 + x^563 + x^562 + x^560 + x^558 + x^552 + x^550 + x^549 + x^546 + x^544 + x^543 + x^540 + x^537 + x^536 + x^535 + x^533 + x^532 + x^531 + x^530 + x^529 + x^527 + x^526 + x^522 + x^521 + x^519 + x^517 + x^514 + x^513 + x^512 + x^511 + x^510 + x^509 + x^507 + x^506 + x^504 + x^503 + x^502 + x^501 + x^500 + x^499 + x^498 + x^496 + x^494 + x^492 + x^491 + x^489 + x^488 + x^487 + x^485 + x^480 + x^479 + x^478 + x^475 + x^472 + x^463 + x^458 + x^455 + x^453 + x^452 + x^448 + x^443 + x^438 + x^436 + x^435 + x^432 + x^429 + x^428 + x^427 + x^421 + x^420 + x^419 + x^418 + x^417 + x^416 + x^414 + x^412 + x^411 + x^410 + x^406 + x^405 + x^404 + x^402 + x^401 + x^400 + x^399 + x^393 + x^388 + x^387 + x^386 + x^385 + x^384 + x^380 + x^379 + x^378 + x^376 + x^375 + x^373 + x^370 + x^368 + x^367 + x^365 + x^363 + x^362 + x^359 + x^358 + x^357 + x^356 + x^353 + x^350 + x^347 + x^343 + x^342 + x^341 + x^339 + x^338 + x^334 + x^333 + x^327 + x^325 + x^323 + x^321 + x^318 + x^317 + x^314 + x^310 + x^309 + x^308 + x^307 + x^306 + x^305 + x^304 + x^301 + x^300 + x^298 + x^295 + x^292 + x^288 + x^286 + x^285 + x^283 + x^282 + x^281 + x^278 + x^277 + x^275 + x^270 + x^269 + x^263 + x^260 + x^256 + x^254 + x^253 + x^250 + x^249 + x^248 + x^245 + x^244 + x^241 + x^239 + x^237 + x^233 + x^227 + x^225 + x^223 + x^222 + x^220 + x^219 + x^218 + x^216 + x^214 + x^212 + x^207 + x^203 + x^201 + x^200 + x^198 + x^197 + x^196 + x^195 + x^191 + x^189 + x^188 + x^184 + x^183 + x^181 + x^180 + x^179 + x^176 + x^174 + x^173 + x^170 + x^169 + x^168 + x^167 + x^165 + x^164 + x^163 + x^162 + x^161 + x^159 + x^158 + x^157 + x^155 + x^154 + x^152 + x^151 + x^150 + x^147 + x^144 + x^143 + x^142 + x^140 + x^139 + x^138 + x^134 + x^131 + x^130 + x^129 + x^124 + x^123 + x^122 + x^121 + x^120 + x^116 + x^115 + x^114 + x^111 + x^110 + x^108 + x^106 + x^104 + x^103 + x^99 + x^98 + x^95 + x^93 + x^91 + x^87 + x^84 + x^83 + x^80 + x^77 + x^76 + x^75 + x^74 + x^73 + x^70 + x^65 + x^64 + x^63 + x^62 + x^61 + x^57 + x^56 + x^55 + x^53 + x^51 + x^50 + x^49 + x^47 + x^46 + x^45 + x^43 + x^42 + x^41 + x^39 + x^37 + x^36 + x^33 + x^31 + x^29 + x^28 + x^24 + x^23 + x^22 + x^21 + x^18 + x^17 + x^16 + x^11 + x^10 + 1
+
+34-37-5 415 x^928 + x^898 + x^889 + x^874 + x^870 + x^861 + x^846 + x^844 + x^839 + x^835 + x^833 + x^831 + x^818 + x^816 + x^812 + x^809 + x^808 + x^805 + x^804 + x^794 + x^792 + x^790 + x^788 + x^787 + x^784 + x^783 + x^778 + x^776 + x^775 + x^773 + x^772 + x^770 + x^769 + x^768 + x^764 + x^760 + x^759 + x^757 + x^756 + x^755 + x^754 + x^749 + x^748 + x^744 + x^740 + x^737 + x^735 + x^730 + x^729 + x^728 + x^727 + x^726 + x^725 + x^721 + x^720 + x^719 + x^718 + x^716 + x^715 + x^713 + x^712 + x^710 + x^709 + x^706 + x^699 + x^695 + x^689 + x^686 + x^682 + x^681 + x^680 + x^677 + x^674 + x^673 + x^672 + x^671 + x^665 + x^664 + x^663 + x^662 + x^661 + x^660 + x^657 + x^656 + x^651 + x^649 + x^646 + x^640 + x^639 + x^635 + x^634 + x^633 + x^632 + x^629 + x^628 + x^624 + x^623 + x^621 + x^620 + x^616 + x^615 + x^614 + x^613 + x^611 + x^610 + x^609 + x^607 + x^606 + x^605 + x^604 + x^601 + x^599 + x^597 + x^596 + x^595 + x^593 + x^591 + x^590 + x^585 + x^584 + x^583 + x^582 + x^580 + x^577 + x^576 + x^573 + x^569 + x^567 + x^564 + x^560 + x^559 + x^558 + x^557 + x^556 + x^555 + x^552 + x^551 + x^550 + x^547 + x^545 + x^544 + x^542 + x^541 + x^540 + x^539 + x^538 + x^535 + x^534 + x^533 + x^526 + x^523 + x^520 + x^518 + x^517 + x^515 + x^514 + x^513 + x^511 + x^510 + x^509 + x^504 + x^503 + x^502 + x^501 + x^496 + x^495 + x^494 + x^493 + x^492 + x^489 + x^485 + x^484 + x^482 + x^481 + x^478 + x^475 + x^472 + x^469 + x^468 + x^467 + x^465 + x^464 + x^462 + x^460 + x^455 + x^453 + x^452 + x^451 + x^449 + x^448 + x^447 + x^446 + x^445 + x^443 + x^442 + x^441 + x^438 + x^435 + x^434 + x^432 + x^431 + x^430 + x^427 + x^424 + x^423 + x^422 + x^419 + x^412 + x^411 + x^409 + x^408 + x^406 + x^405 + x^404 + x^403 + x^401 + x^400 + x^399 + x^393 + x^390 + x^389 + x^387 + x^385 + x^384 + x^380 + x^378 + x^376 + x^374 + x^372 + x^371 + x^370 + x^369 + x^366 + x^365 + x^360 + x^359 + x^357 + x^350 + x^349 + x^347 + x^346 + x^345 + x^344 + x^342 + x^341 + x^340 + x^337 + x^335 + x^334 + x^332 + x^331 + x^330 + x^328 + x^327 + x^325 + x^323 + x^322 + x^321 + x^320 + x^316 + x^314 + x^310 + x^308 + x^306 + x^305 + x^304 + x^301 + x^300 + x^295 + x^291 + x^290 + x^289 + x^288 + x^287 + x^283 + x^281 + x^280 + x^279 + x^277 + x^274 + x^272 + x^271 + x^268 + x^266 + x^264 + x^263 + x^261 + x^259 + x^255 + x^254 + x^253 + x^250 + x^249 + x^246 + x^244 + x^242 + x^241 + x^237 + x^233 + x^230 + x^229 + x^227 + x^223 + x^222 + x^217 + x^216 + x^208 + x^207 + x^206 + x^205 + x^204 + x^203 + x^198 + x^196 + x^192 + x^191 + x^190 + x^189 + x^188 + x^187 + x^186 + x^185 + x^182 + x^181 + x^180 + x^178 + x^175 + x^171 + x^166 + x^165 + x^164 + x^163 + x^162 + x^161 + x^159 + x^157 + x^156 + x^149 + x^148 + x^146 + x^145 + x^143 + x^142 + x^141 + x^140 + x^139 + x^138 + x^136 + x^135 + x^131 + x^130 + x^129 + x^127 + x^125 + x^124 + x^117 + x^115 + x^113 + x^110 + x^108 + x^107 + x^106 + x^105 + x^103 + x^99 + x^98 + x^95 + x^93 + x^90 + x^87 + x^84 + x^83 + x^81 + x^80 + x^79 + x^78 + x^77 + x^72 + x^71 + x^68 + x^65 + x^64 + x^63 + x^62 + x^59 + x^58 + x^56 + x^55 + x^54 + x^53 + x^52 + x^51 + x^46 + x^45 + x^44 + x^43 + x^40 + x^39 + x^36 + x^35 + x^33 + x^32 + x^31 + x^30 + x^27 + x^25 + x^24 + x^22 + x^18 + x^17 + x^14 + x^13 + x^12 + x^10 + 1
+
+37-3-56 415 x^928 + x^898 + x^878 + x^870 + x^862 + x^860 + x^849 + x^840 + x^831 + x^822 + x^819 + x^818 + x^811 + x^802 + x^801 + x^796 + x^795 + x^794 + x^793 + x^790 + x^789 + x^784 + x^781 + x^780 + x^777 + x^775 + x^772 + x^766 + x^760 + x^758 + x^755 + x^753 + x^752 + x^747 + x^745 + x^744 + x^743 + x^740 + x^738 + x^737 + x^736 + x^732 + x^730 + x^729 + x^728 + x^726 + x^721 + x^716 + x^715 + x^713 + x^712 + x^711 + x^710 + x^709 + x^706 + x^705 + x^702 + x^700 + x^699 + x^698 + x^697 + x^696 + x^693 + x^692 + x^690 + x^687 + x^686 + x^684 + x^683 + x^682 + x^681 + x^680 + x^678 + x^675 + x^674 + x^673 + x^672 + x^670 + x^669 + x^667 + x^664 + x^662 + x^661 + x^660 + x^658 + x^657 + x^656 + x^654 + x^653 + x^652 + x^650 + x^649 + x^648 + x^645 + x^644 + x^641 + x^639 + x^635 + x^634 + x^631 + x^630 + x^629 + x^628 + x^622 + x^621 + x^620 + x^617 + x^616 + x^614 + x^610 + x^609 + x^608 + x^606 + x^604 + x^601 + x^600 + x^598 + x^597 + x^596 + x^593 + x^592 + x^590 + x^588 + x^587 + x^586 + x^585 + x^584 + x^583 + x^579 + x^578 + x^575 + x^574 + x^573 + x^569 + x^568 + x^566 + x^564 + x^563 + x^561 + x^559 + x^557 + x^556 + x^553 + x^552 + x^550 + x^548 + x^546 + x^543 + x^542 + x^541 + x^540 + x^538 + x^537 + x^536 + x^533 + x^532 + x^529 + x^528 + x^527 + x^525 + x^522 + x^520 + x^519 + x^518 + x^517 + x^515 + x^514 + x^513 + x^509 + x^507 + x^506 + x^504 + x^503 + x^502 + x^499 + x^495 + x^494 + x^493 + x^492 + x^490 + x^486 + x^482 + x^481 + x^480 + x^479 + x^477 + x^476 + x^475 + x^474 + x^473 + x^472 + x^470 + x^468 + x^467 + x^463 + x^457 + x^453 + x^451 + x^448 + x^446 + x^445 + x^440 + x^438 + x^437 + x^434 + x^433 + x^432 + x^431 + x^430 + x^425 + x^424 + x^423 + x^417 + x^414 + x^409 + x^407 + x^406 + x^405 + x^402 + x^400 + x^399 + x^398 + x^397 + x^396 + x^395 + x^394 + x^391 + x^389 + x^387 + x^386 + x^384 + x^383 + x^379 + x^377 + x^372 + x^371 + x^369 + x^368 + x^366 + x^365 + x^364 + x^361 + x^359 + x^357 + x^354 + x^351 + x^348 + x^347 + x^340 + x^339 + x^337 + x^336 + x^334 + x^329 + x^327 + x^326 + x^325 + x^322 + x^320 + x^318 + x^315 + x^314 + x^313 + x^312 + x^310 + x^309 + x^308 + x^306 + x^305 + x^301 + x^300 + x^298 + x^294 + x^293 + x^292 + x^291 + x^288 + x^285 + x^284 + x^282 + x^279 + x^275 + x^272 + x^271 + x^270 + x^269 + x^268 + x^266 + x^259 + x^257 + x^254 + x^253 + x^251 + x^250 + x^248 + x^246 + x^244 + x^243 + x^242 + x^238 + x^237 + x^234 + x^233 + x^232 + x^231 + x^230 + x^227 + x^223 + x^222 + x^221 + x^219 + x^216 + x^215 + x^212 + x^207 + x^205 + x^203 + x^200 + x^199 + x^198 + x^196 + x^195 + x^193 + x^191 + x^187 + x^185 + x^183 + x^181 + x^179 + x^178 + x^177 + x^174 + x^171 + x^170 + x^168 + x^166 + x^165 + x^163 + x^160 + x^156 + x^155 + x^154 + x^151 + x^150 + x^149 + x^146 + x^145 + x^142 + x^141 + x^136 + x^135 + x^134 + x^132 + x^128 + x^127 + x^125 + x^121 + x^118 + x^117 + x^116 + x^113 + x^112 + x^111 + x^108 + x^106 + x^105 + x^104 + x^102 + x^101 + x^100 + x^99 + x^96 + x^95 + x^94 + x^89 + x^85 + x^83 + x^81 + x^80 + x^79 + x^69 + x^65 + x^63 + x^62 + x^61 + x^59 + x^58 + x^57 + x^56 + x^52 + x^49 + x^46 + x^45 + x^43 + x^42 + x^41 + x^40 + x^39 + x^38 + x^37 + x^36 + x^33 + x^32 + x^28 + x^24 + x^23 + x^20 + x^18 + x^17 + x^14 + x^11 + x^10 + x^9 + x^8 + 1
+
+55-11-54 417 x^928 + x^898 + x^896 + x^870 + x^867 + x^866 + x^864 + x^853 + x^846 + x^837 + x^836 + x^835 + x^828 + x^826 + x^824 + x^823 + x^817 + x^814 + x^812 + x^810 + x^808 + x^807 + x^806 + x^805 + x^804 + x^803 + x^802 + x^800 + x^796 + x^791 + x^789 + x^786 + x^784 + x^781 + x^780 + x^777 + x^772 + x^771 + x^770 + x^767 + x^762 + x^761 + x^757 + x^753 + x^747 + x^742 + x^740 + x^739 + x^737 + x^736 + x^735 + x^734 + x^733 + x^731 + x^730 + x^725 + x^723 + x^722 + x^717 + x^715 + x^708 + x^707 + x^705 + x^704 + x^703 + x^699 + x^698 + x^695 + x^690 + x^689 + x^686 + x^685 + x^684 + x^682 + x^681 + x^680 + x^678 + x^677 + x^675 + x^669 + x^667 + x^666 + x^665 + x^664 + x^663 + x^662 + x^661 + x^660 + x^658 + x^657 + x^656 + x^655 + x^654 + x^651 + x^650 + x^646 + x^645 + x^643 + x^641 + x^640 + x^639 + x^637 + x^634 + x^633 + x^632 + x^631 + x^627 + x^624 + x^622 + x^620 + x^617 + x^616 + x^615 + x^614 + x^613 + x^608 + x^601 + x^600 + x^599 + x^595 + x^592 + x^591 + x^590 + x^586 + x^580 + x^579 + x^578 + x^577 + x^576 + x^575 + x^571 + x^570 + x^569 + x^567 + x^566 + x^565 + x^564 + x^563 + x^562 + x^561 + x^560 + x^558 + x^557 + x^555 + x^554 + x^553 + x^552 + x^551 + x^550 + x^549 + x^545 + x^543 + x^542 + x^539 + x^537 + x^532 + x^530 + x^526 + x^525 + x^520 + x^519 + x^517 + x^515 + x^513 + x^511 + x^510 + x^508 + x^504 + x^502 + x^501 + x^498 + x^493 + x^492 + x^490 + x^489 + x^488 + x^487 + x^485 + x^484 + x^483 + x^481 + x^480 + x^479 + x^477 + x^475 + x^473 + x^472 + x^469 + x^468 + x^467 + x^466 + x^463 + x^462 + x^461 + x^459 + x^455 + x^453 + x^451 + x^450 + x^449 + x^445 + x^444 + x^443 + x^442 + x^441 + x^440 + x^437 + x^435 + x^434 + x^431 + x^430 + x^429 + x^427 + x^426 + x^422 + x^421 + x^420 + x^413 + x^410 + x^409 + x^408 + x^407 + x^405 + x^403 + x^402 + x^401 + x^397 + x^396 + x^393 + x^389 + x^386 + x^384 + x^380 + x^379 + x^376 + x^373 + x^372 + x^370 + x^369 + x^367 + x^365 + x^364 + x^363 + x^358 + x^356 + x^355 + x^354 + x^353 + x^349 + x^345 + x^342 + x^334 + x^332 + x^331 + x^329 + x^325 + x^324 + x^322 + x^316 + x^315 + x^314 + x^312 + x^310 + x^307 + x^306 + x^298 + x^297 + x^296 + x^294 + x^293 + x^292 + x^288 + x^285 + x^284 + x^283 + x^282 + x^279 + x^273 + x^271 + x^267 + x^262 + x^257 + x^255 + x^253 + x^252 + x^251 + x^250 + x^248 + x^247 + x^245 + x^244 + x^243 + x^240 + x^238 + x^237 + x^236 + x^234 + x^232 + x^229 + x^224 + x^223 + x^222 + x^221 + x^218 + x^216 + x^215 + x^213 + x^211 + x^210 + x^209 + x^207 + x^205 + x^203 + x^202 + x^201 + x^198 + x^195 + x^194 + x^193 + x^192 + x^190 + x^189 + x^187 + x^186 + x^185 + x^184 + x^183 + x^182 + x^181 + x^179 + x^177 + x^176 + x^175 + x^174 + x^172 + x^170 + x^167 + x^160 + x^157 + x^156 + x^153 + x^151 + x^150 + x^149 + x^143 + x^140 + x^137 + x^136 + x^134 + x^133 + x^129 + x^128 + x^124 + x^123 + x^120 + x^118 + x^117 + x^116 + x^115 + x^114 + x^112 + x^111 + x^110 + x^108 + x^105 + x^104 + x^102 + x^101 + x^100 + x^98 + x^97 + x^93 + x^92 + x^91 + x^90 + x^88 + x^87 + x^85 + x^81 + x^79 + x^77 + x^76 + x^74 + x^73 + x^72 + x^70 + x^65 + x^63 + x^62 + x^61 + x^59 + x^55 + x^53 + x^51 + x^50 + x^47 + x^46 + x^45 + x^44 + x^43 + x^40 + x^38 + x^34 + x^32 + x^30 + x^29 + x^28 + x^27 + x^24 + x^22 + x^21 + x^18 + x^17 + x^14 + x^11 + x^4 + 1
+
+30-3-41 419 x^928 + x^906 + x^898 + x^884 + x^879 + x^876 + x^870 + x^862 + x^854 + x^852 + x^849 + x^844 + x^835 + x^832 + x^829 + x^825 + x^822 + x^819 + x^818 + x^813 + x^812 + x^807 + x^805 + x^803 + x^802 + x^798 + x^795 + x^794 + x^792 + x^790 + x^789 + x^788 + x^786 + x^785 + x^778 + x^777 + x^769 + x^768 + x^762 + x^760 + x^753 + x^751 + x^748 + x^747 + x^746 + x^742 + x^738 + x^735 + x^734 + x^733 + x^731 + x^729 + x^726 + x^725 + x^723 + x^719 + x^718 + x^716 + x^713 + x^706 + x^703 + x^702 + x^701 + x^700 + x^699 + x^696 + x^693 + x^690 + x^687 + x^686 + x^685 + x^684 + x^682 + x^679 + x^677 + x^673 + x^672 + x^671 + x^670 + x^664 + x^659 + x^656 + x^653 + x^651 + x^650 + x^647 + x^643 + x^642 + x^639 + x^637 + x^635 + x^633 + x^630 + x^627 + x^625 + x^624 + x^621 + x^616 + x^615 + x^614 + x^613 + x^612 + x^607 + x^604 + x^603 + x^600 + x^597 + x^596 + x^595 + x^593 + x^592 + x^591 + x^588 + x^586 + x^583 + x^581 + x^580 + x^578 + x^575 + x^574 + x^571 + x^569 + x^568 + x^567 + x^566 + x^565 + x^564 + x^562 + x^560 + x^559 + x^558 + x^557 + x^555 + x^554 + x^553 + x^552 + x^550 + x^546 + x^545 + x^543 + x^542 + x^537 + x^535 + x^534 + x^532 + x^530 + x^527 + x^525 + x^523 + x^521 + x^520 + x^512 + x^506 + x^505 + x^504 + x^503 + x^501 + x^500 + x^499 + x^498 + x^497 + x^495 + x^494 + x^493 + x^489 + x^487 + x^485 + x^484 + x^479 + x^478 + x^477 + x^475 + x^474 + x^471 + x^470 + x^469 + x^468 + x^467 + x^466 + x^464 + x^462 + x^459 + x^458 + x^457 + x^456 + x^451 + x^449 + x^448 + x^447 + x^446 + x^443 + x^441 + x^440 + x^439 + x^437 + x^435 + x^434 + x^433 + x^432 + x^431 + x^430 + x^427 + x^425 + x^424 + x^423 + x^420 + x^419 + x^418 + x^417 + x^416 + x^414 + x^412 + x^410 + x^409 + x^405 + x^404 + x^402 + x^401 + x^400 + x^396 + x^394 + x^393 + x^391 + x^390 + x^389 + x^388 + x^387 + x^386 + x^385 + x^380 + x^379 + x^369 + x^368 + x^363 + x^362 + x^361 + x^360 + x^359 + x^355 + x^354 + x^351 + x^347 + x^346 + x^345 + x^342 + x^337 + x^336 + x^332 + x^328 + x^324 + x^322 + x^321 + x^319 + x^318 + x^317 + x^315 + x^312 + x^310 + x^309 + x^307 + x^303 + x^302 + x^300 + x^298 + x^295 + x^289 + x^288 + x^287 + x^286 + x^285 + x^284 + x^283 + x^282 + x^281 + x^276 + x^275 + x^273 + x^272 + x^271 + x^268 + x^265 + x^263 + x^262 + x^260 + x^259 + x^255 + x^254 + x^253 + x^252 + x^249 + x^245 + x^244 + x^243 + x^242 + x^241 + x^240 + x^239 + x^236 + x^235 + x^233 + x^232 + x^231 + x^230 + x^224 + x^223 + x^222 + x^221 + x^218 + x^215 + x^214 + x^213 + x^209 + x^206 + x^204 + x^199 + x^197 + x^196 + x^194 + x^190 + x^186 + x^185 + x^183 + x^182 + x^181 + x^180 + x^179 + x^178 + x^177 + x^176 + x^175 + x^174 + x^173 + x^168 + x^167 + x^166 + x^165 + x^164 + x^163 + x^162 + x^161 + x^159 + x^158 + x^156 + x^155 + x^153 + x^151 + x^148 + x^144 + x^143 + x^142 + x^141 + x^139 + x^136 + x^135 + x^131 + x^129 + x^128 + x^127 + x^126 + x^125 + x^124 + x^123 + x^121 + x^119 + x^118 + x^116 + x^113 + x^111 + x^109 + x^107 + x^106 + x^103 + x^102 + x^100 + x^99 + x^98 + x^96 + x^93 + x^92 + x^90 + x^86 + x^85 + x^83 + x^82 + x^80 + x^76 + x^73 + x^72 + x^71 + x^70 + x^67 + x^66 + x^64 + x^61 + x^60 + x^59 + x^56 + x^53 + x^50 + x^49 + x^45 + x^44 + x^42 + x^39 + x^36 + x^35 + x^30 + x^28 + x^25 + x^22 + x^19 + x^18 + x^17 + x^15 + x^14 + x^11 + x^10 + x^5 + 1
+
+11-45-50 423 x^928 + x^898 + x^891 + x^870 + x^862 + x^861 + x^858 + x^857 + x^854 + x^851 + x^828 + x^825 + x^824 + x^822 + x^818 + x^814 + x^811 + x^808 + x^802 + x^800 + x^798 + x^797 + x^787 + x^786 + x^785 + x^783 + x^782 + x^781 + x^776 + x^772 + x^771 + x^770 + x^768 + x^765 + x^760 + x^758 + x^756 + x^754 + x^750 + x^749 + x^746 + x^742 + x^738 + x^737 + x^736 + x^735 + x^734 + x^732 + x^729 + x^723 + x^719 + x^718 + x^716 + x^715 + x^714 + x^713 + x^708 + x^707 + x^705 + x^704 + x^703 + x^702 + x^701 + x^700 + x^698 + x^697 + x^696 + x^694 + x^692 + x^688 + x^684 + x^683 + x^681 + x^680 + x^678 + x^666 + x^664 + x^663 + x^662 + x^660 + x^659 + x^658 + x^656 + x^654 + x^653 + x^652 + x^651 + x^650 + x^649 + x^647 + x^645 + x^642 + x^641 + x^639 + x^638 + x^637 + x^636 + x^634 + x^632 + x^631 + x^626 + x^625 + x^623 + x^620 + x^617 + x^616 + x^614 + x^612 + x^610 + x^609 + x^607 + x^605 + x^603 + x^601 + x^600 + x^598 + x^597 + x^596 + x^595 + x^594 + x^591 + x^589 + x^588 + x^586 + x^585 + x^584 + x^581 + x^579 + x^578 + x^577 + x^576 + x^575 + x^573 + x^572 + x^570 + x^569 + x^568 + x^566 + x^561 + x^559 + x^558 + x^557 + x^554 + x^552 + x^551 + x^550 + x^549 + x^548 + x^546 + x^544 + x^543 + x^541 + x^540 + x^538 + x^533 + x^531 + x^529 + x^528 + x^523 + x^519 + x^515 + x^512 + x^511 + x^510 + x^508 + x^507 + x^506 + x^501 + x^498 + x^497 + x^496 + x^495 + x^494 + x^492 + x^490 + x^489 + x^488 + x^487 + x^486 + x^485 + x^483 + x^477 + x^476 + x^474 + x^472 + x^470 + x^469 + x^467 + x^465 + x^462 + x^460 + x^458 + x^452 + x^451 + x^449 + x^447 + x^446 + x^445 + x^442 + x^441 + x^440 + x^437 + x^436 + x^434 + x^433 + x^431 + x^428 + x^427 + x^426 + x^425 + x^423 + x^422 + x^420 + x^419 + x^418 + x^417 + x^415 + x^414 + x^413 + x^412 + x^411 + x^409 + x^407 + x^404 + x^403 + x^402 + x^399 + x^398 + x^396 + x^395 + x^392 + x^391 + x^389 + x^388 + x^387 + x^386 + x^385 + x^384 + x^381 + x^379 + x^378 + x^377 + x^375 + x^373 + x^369 + x^367 + x^365 + x^364 + x^362 + x^359 + x^358 + x^357 + x^355 + x^351 + x^348 + x^347 + x^346 + x^345 + x^344 + x^343 + x^335 + x^334 + x^332 + x^328 + x^325 + x^324 + x^321 + x^319 + x^318 + x^316 + x^314 + x^312 + x^311 + x^310 + x^304 + x^300 + x^297 + x^296 + x^294 + x^293 + x^292 + x^291 + x^289 + x^288 + x^285 + x^283 + x^277 + x^276 + x^274 + x^271 + x^270 + x^269 + x^268 + x^265 + x^264 + x^260 + x^259 + x^258 + x^257 + x^256 + x^252 + x^251 + x^249 + x^248 + x^247 + x^246 + x^244 + x^242 + x^238 + x^236 + x^233 + x^232 + x^231 + x^230 + x^229 + x^228 + x^227 + x^222 + x^220 + x^218 + x^214 + x^212 + x^209 + x^208 + x^207 + x^204 + x^203 + x^202 + x^200 + x^193 + x^190 + x^189 + x^188 + x^186 + x^185 + x^183 + x^178 + x^176 + x^174 + x^171 + x^169 + x^168 + x^166 + x^165 + x^163 + x^162 + x^161 + x^158 + x^156 + x^155 + x^154 + x^153 + x^150 + x^147 + x^146 + x^145 + x^141 + x^136 + x^134 + x^132 + x^128 + x^127 + x^126 + x^123 + x^120 + x^119 + x^114 + x^112 + x^111 + x^105 + x^104 + x^101 + x^100 + x^98 + x^97 + x^96 + x^95 + x^94 + x^92 + x^91 + x^88 + x^87 + x^85 + x^84 + x^83 + x^80 + x^78 + x^77 + x^76 + x^74 + x^73 + x^72 + x^68 + x^64 + x^62 + x^61 + x^60 + x^56 + x^55 + x^54 + x^53 + x^50 + x^46 + x^42 + x^41 + x^40 + x^39 + x^36 + x^32 + x^31 + x^30 + x^28 + x^25 + x^24 + x^16 + x^15 + x^14 + x^13 + x^10 + x^9 + x^6 + x^3 + 1
+
+50-19-47 423 x^928 + x^906 + x^898 + x^897 + x^884 + x^876 + x^870 + x^862 + x^854 + x^840 + x^836 + x^835 + x^832 + x^831 + x^830 + x^821 + x^814 + x^813 + x^812 + x^810 + x^808 + x^805 + x^803 + x^796 + x^794 + x^792 + x^791 + x^786 + x^785 + x^783 + x^777 + x^774 + x^773 + x^767 + x^766 + x^764 + x^762 + x^761 + x^760 + x^759 + x^758 + x^754 + x^750 + x^748 + x^747 + x^746 + x^743 + x^742 + x^739 + x^734 + x^732 + x^728 + x^727 + x^724 + x^722 + x^720 + x^718 + x^716 + x^714 + x^713 + x^712 + x^711 + x^709 + x^708 + x^705 + x^704 + x^702 + x^699 + x^698 + x^695 + x^694 + x^693 + x^692 + x^691 + x^688 + x^686 + x^683 + x^682 + x^681 + x^679 + x^676 + x^674 + x^672 + x^671 + x^668 + x^664 + x^663 + x^661 + x^659 + x^657 + x^656 + x^654 + x^652 + x^646 + x^645 + x^641 + x^638 + x^635 + x^634 + x^632 + x^629 + x^628 + x^619 + x^618 + x^617 + x^615 + x^613 + x^612 + x^610 + x^609 + x^607 + x^606 + x^605 + x^604 + x^603 + x^602 + x^598 + x^597 + x^596 + x^595 + x^593 + x^591 + x^590 + x^589 + x^584 + x^583 + x^582 + x^581 + x^580 + x^579 + x^576 + x^575 + x^573 + x^569 + x^568 + x^567 + x^565 + x^564 + x^562 + x^556 + x^554 + x^552 + x^551 + x^550 + x^548 + x^547 + x^546 + x^545 + x^542 + x^541 + x^540 + x^539 + x^537 + x^535 + x^534 + x^532 + x^528 + x^526 + x^525 + x^524 + x^522 + x^521 + x^519 + x^515 + x^513 + x^510 + x^508 + x^507 + x^506 + x^505 + x^504 + x^502 + x^501 + x^500 + x^499 + x^496 + x^495 + x^494 + x^492 + x^490 + x^489 + x^486 + x^485 + x^484 + x^483 + x^481 + x^479 + x^478 + x^476 + x^474 + x^471 + x^469 + x^467 + x^465 + x^463 + x^458 + x^456 + x^455 + x^454 + x^451 + x^449 + x^448 + x^443 + x^442 + x^440 + x^439 + x^437 + x^436 + x^433 + x^432 + x^431 + x^430 + x^428 + x^427 + x^426 + x^425 + x^424 + x^421 + x^420 + x^416 + x^415 + x^413 + x^412 + x^411 + x^410 + x^409 + x^408 + x^406 + x^405 + x^403 + x^400 + x^399 + x^398 + x^396 + x^395 + x^393 + x^391 + x^388 + x^387 + x^386 + x^381 + x^380 + x^377 + x^376 + x^375 + x^374 + x^373 + x^371 + x^370 + x^369 + x^368 + x^361 + x^359 + x^358 + x^357 + x^355 + x^352 + x^350 + x^348 + x^347 + x^346 + x^343 + x^342 + x^339 + x^337 + x^335 + x^334 + x^332 + x^329 + x^325 + x^324 + x^323 + x^322 + x^318 + x^317 + x^315 + x^314 + x^311 + x^310 + x^306 + x^304 + x^300 + x^299 + x^297 + x^295 + x^294 + x^289 + x^286 + x^283 + x^279 + x^278 + x^276 + x^275 + x^273 + x^269 + x^268 + x^266 + x^264 + x^260 + x^258 + x^257 + x^254 + x^252 + x^250 + x^248 + x^247 + x^246 + x^245 + x^241 + x^239 + x^237 + x^236 + x^235 + x^234 + x^232 + x^228 + x^227 + x^226 + x^225 + x^218 + x^215 + x^214 + x^212 + x^211 + x^210 + x^208 + x^206 + x^205 + x^204 + x^203 + x^201 + x^198 + x^196 + x^193 + x^192 + x^190 + x^189 + x^188 + x^184 + x^181 + x^180 + x^178 + x^177 + x^176 + x^175 + x^174 + x^173 + x^171 + x^169 + x^168 + x^166 + x^165 + x^164 + x^162 + x^160 + x^159 + x^158 + x^153 + x^150 + x^148 + x^147 + x^146 + x^143 + x^142 + x^139 + x^136 + x^135 + x^134 + x^133 + x^132 + x^126 + x^124 + x^121 + x^118 + x^116 + x^112 + x^109 + x^108 + x^107 + x^104 + x^101 + x^98 + x^97 + x^95 + x^93 + x^88 + x^86 + x^85 + x^84 + x^83 + x^78 + x^77 + x^73 + x^70 + x^69 + x^68 + x^66 + x^64 + x^63 + x^62 + x^58 + x^57 + x^53 + x^52 + x^50 + x^48 + x^45 + x^43 + x^42 + x^41 + x^40 + x^39 + x^38 + x^33 + x^26 + x^22 + x^21 + x^20 + x^18 + x^17 + x^10 + x^8 + 1
+
+52-27-13 427 x^928 + x^898 + x^870 + x^866 + x^858 + x^855 + x^854 + x^847 + x^844 + x^836 + x^833 + x^832 + x^829 + x^828 + x^825 + x^822 + x^818 + x^817 + x^814 + x^811 + x^808 + x^807 + x^800 + x^799 + x^798 + x^796 + x^795 + x^792 + x^789 + x^781 + x^777 + x^774 + x^770 + x^769 + x^768 + x^767 + x^765 + x^762 + x^759 + x^756 + x^755 + x^754 + x^752 + x^751 + x^748 + x^746 + x^745 + x^744 + x^740 + x^739 + x^738 + x^736 + x^735 + x^734 + x^732 + x^730 + x^729 + x^728 + x^724 + x^721 + x^719 + x^718 + x^717 + x^716 + x^713 + x^712 + x^710 + x^709 + x^706 + x^705 + x^704 + x^702 + x^701 + x^699 + x^698 + x^697 + x^695 + x^693 + x^690 + x^689 + x^687 + x^686 + x^685 + x^683 + x^682 + x^681 + x^680 + x^677 + x^676 + x^675 + x^674 + x^671 + x^669 + x^668 + x^667 + x^666 + x^665 + x^663 + x^660 + x^659 + x^655 + x^654 + x^653 + x^652 + x^650 + x^648 + x^647 + x^643 + x^642 + x^640 + x^639 + x^638 + x^635 + x^634 + x^633 + x^632 + x^630 + x^629 + x^628 + x^626 + x^624 + x^620 + x^618 + x^617 + x^616 + x^615 + x^612 + x^610 + x^607 + x^606 + x^604 + x^603 + x^602 + x^598 + x^597 + x^593 + x^589 + x^588 + x^587 + x^585 + x^583 + x^581 + x^580 + x^579 + x^578 + x^570 + x^569 + x^566 + x^565 + x^564 + x^563 + x^561 + x^559 + x^558 + x^557 + x^556 + x^555 + x^551 + x^550 + x^548 + x^547 + x^545 + x^544 + x^540 + x^539 + x^538 + x^537 + x^536 + x^535 + x^534 + x^532 + x^529 + x^528 + x^527 + x^526 + x^525 + x^524 + x^523 + x^522 + x^519 + x^517 + x^516 + x^515 + x^513 + x^512 + x^511 + x^507 + x^504 + x^502 + x^495 + x^493 + x^492 + x^491 + x^490 + x^489 + x^484 + x^483 + x^481 + x^480 + x^478 + x^466 + x^464 + x^463 + x^460 + x^459 + x^457 + x^455 + x^454 + x^451 + x^450 + x^448 + x^447 + x^435 + x^434 + x^433 + x^432 + x^431 + x^428 + x^424 + x^423 + x^422 + x^421 + x^420 + x^419 + x^414 + x^413 + x^412 + x^411 + x^410 + x^409 + x^407 + x^404 + x^401 + x^396 + x^395 + x^394 + x^391 + x^390 + x^389 + x^388 + x^385 + x^382 + x^379 + x^377 + x^372 + x^370 + x^367 + x^365 + x^362 + x^361 + x^357 + x^355 + x^354 + x^351 + x^349 + x^348 + x^345 + x^343 + x^342 + x^340 + x^338 + x^334 + x^330 + x^328 + x^324 + x^323 + x^320 + x^319 + x^318 + x^317 + x^315 + x^313 + x^310 + x^309 + x^307 + x^303 + x^302 + x^301 + x^300 + x^299 + x^298 + x^297 + x^296 + x^295 + x^292 + x^289 + x^288 + x^287 + x^285 + x^283 + x^280 + x^274 + x^273 + x^271 + x^267 + x^265 + x^264 + x^261 + x^259 + x^258 + x^256 + x^254 + x^253 + x^248 + x^247 + x^244 + x^239 + x^238 + x^237 + x^233 + x^232 + x^230 + x^229 + x^228 + x^227 + x^224 + x^221 + x^217 + x^216 + x^215 + x^214 + x^209 + x^207 + x^206 + x^205 + x^203 + x^201 + x^200 + x^199 + x^196 + x^195 + x^194 + x^192 + x^191 + x^190 + x^189 + x^187 + x^186 + x^182 + x^180 + x^177 + x^176 + x^174 + x^173 + x^170 + x^169 + x^168 + x^167 + x^164 + x^160 + x^154 + x^153 + x^152 + x^148 + x^144 + x^143 + x^142 + x^141 + x^140 + x^139 + x^135 + x^134 + x^133 + x^132 + x^129 + x^125 + x^124 + x^122 + x^121 + x^120 + x^118 + x^117 + x^116 + x^115 + x^112 + x^111 + x^110 + x^109 + x^108 + x^107 + x^106 + x^104 + x^103 + x^101 + x^96 + x^95 + x^93 + x^90 + x^87 + x^86 + x^82 + x^79 + x^77 + x^75 + x^70 + x^68 + x^67 + x^66 + x^62 + x^60 + x^57 + x^54 + x^53 + x^52 + x^51 + x^49 + x^46 + x^45 + x^43 + x^42 + x^41 + x^40 + x^39 + x^38 + x^36 + x^33 + x^31 + x^30 + x^29 + x^27 + x^26 + x^24 + x^22 + x^18 + x^7 + x^6 + 1
+
+54-9-25 433 x^928 + x^898 + x^875 + x^870 + x^860 + x^850 + x^847 + x^840 + x^835 + x^832 + x^822 + x^819 + x^815 + x^812 + x^808 + x^807 + x^805 + x^804 + x^800 + x^797 + x^794 + x^792 + x^791 + x^790 + x^787 + x^785 + x^782 + x^778 + x^777 + x^776 + x^771 + x^770 + x^767 + x^766 + x^764 + x^763 + x^760 + x^759 + x^757 + x^755 + x^754 + x^753 + x^752 + x^748 + x^743 + x^742 + x^739 + x^738 + x^737 + x^735 + x^734 + x^733 + x^731 + x^730 + x^729 + x^725 + x^724 + x^723 + x^720 + x^719 + x^717 + x^716 + x^714 + x^713 + x^711 + x^710 + x^709 + x^706 + x^705 + x^700 + x^699 + x^697 + x^696 + x^695 + x^693 + x^692 + x^691 + x^688 + x^687 + x^686 + x^685 + x^683 + x^682 + x^680 + x^676 + x^671 + x^670 + x^664 + x^663 + x^662 + x^661 + x^660 + x^658 + x^657 + x^656 + x^654 + x^653 + x^649 + x^648 + x^646 + x^645 + x^642 + x^637 + x^635 + x^633 + x^632 + x^630 + x^627 + x^624 + x^623 + x^621 + x^620 + x^619 + x^617 + x^615 + x^614 + x^613 + x^610 + x^609 + x^608 + x^607 + x^604 + x^602 + x^601 + x^600 + x^599 + x^593 + x^592 + x^591 + x^589 + x^585 + x^584 + x^580 + x^576 + x^575 + x^574 + x^570 + x^569 + x^566 + x^565 + x^564 + x^563 + x^562 + x^561 + x^559 + x^558 + x^554 + x^552 + x^551 + x^547 + x^546 + x^545 + x^543 + x^541 + x^540 + x^535 + x^532 + x^531 + x^530 + x^529 + x^525 + x^524 + x^523 + x^521 + x^520 + x^519 + x^518 + x^517 + x^516 + x^515 + x^513 + x^512 + x^510 + x^509 + x^504 + x^503 + x^502 + x^501 + x^500 + x^497 + x^496 + x^494 + x^493 + x^488 + x^487 + x^486 + x^484 + x^482 + x^481 + x^480 + x^479 + x^475 + x^472 + x^465 + x^464 + x^462 + x^461 + x^460 + x^457 + x^456 + x^455 + x^454 + x^452 + x^451 + x^450 + x^449 + x^448 + x^446 + x^445 + x^444 + x^443 + x^441 + x^438 + x^437 + x^436 + x^434 + x^432 + x^430 + x^427 + x^425 + x^424 + x^421 + x^420 + x^419 + x^418 + x^417 + x^414 + x^413 + x^410 + x^409 + x^407 + x^404 + x^403 + x^402 + x^401 + x^400 + x^399 + x^398 + x^396 + x^393 + x^392 + x^391 + x^390 + x^389 + x^385 + x^382 + x^381 + x^380 + x^379 + x^378 + x^375 + x^373 + x^372 + x^367 + x^366 + x^362 + x^358 + x^356 + x^355 + x^354 + x^353 + x^349 + x^348 + x^347 + x^342 + x^340 + x^339 + x^336 + x^334 + x^331 + x^329 + x^328 + x^327 + x^326 + x^325 + x^323 + x^319 + x^317 + x^316 + x^314 + x^313 + x^312 + x^310 + x^309 + x^308 + x^307 + x^302 + x^299 + x^298 + x^297 + x^296 + x^293 + x^291 + x^289 + x^288 + x^286 + x^285 + x^284 + x^282 + x^279 + x^274 + x^272 + x^271 + x^268 + x^266 + x^263 + x^261 + x^258 + x^255 + x^252 + x^251 + x^249 + x^248 + x^247 + x^246 + x^243 + x^242 + x^240 + x^237 + x^236 + x^234 + x^233 + x^232 + x^231 + x^228 + x^226 + x^223 + x^222 + x^221 + x^220 + x^219 + x^217 + x^216 + x^215 + x^213 + x^212 + x^210 + x^205 + x^203 + x^202 + x^199 + x^197 + x^195 + x^194 + x^193 + x^191 + x^189 + x^188 + x^187 + x^184 + x^182 + x^178 + x^173 + x^172 + x^170 + x^169 + x^167 + x^165 + x^162 + x^159 + x^158 + x^157 + x^156 + x^154 + x^149 + x^148 + x^145 + x^144 + x^141 + x^140 + x^135 + x^134 + x^133 + x^131 + x^130 + x^129 + x^127 + x^126 + x^123 + x^120 + x^119 + x^118 + x^117 + x^115 + x^110 + x^108 + x^106 + x^105 + x^104 + x^99 + x^97 + x^95 + x^89 + x^86 + x^84 + x^82 + x^78 + x^75 + x^71 + x^70 + x^69 + x^68 + x^65 + x^63 + x^62 + x^61 + x^59 + x^58 + x^51 + x^50 + x^49 + x^48 + x^47 + x^44 + x^43 + x^42 + x^39 + x^38 + x^37 + x^35 + x^30 + x^27 + x^25 + x^21 + x^19 + x^17 + x^16 + x^13 + x^12 + x^10 + x^8 + x^6 + 1
+
+56-43-35 433 x^928 + x^898 + x^896 + x^871 + x^870 + x^864 + x^848 + x^847 + x^846 + x^841 + x^840 + x^834 + x^833 + x^832 + x^829 + x^821 + x^814 + x^811 + x^808 + x^806 + x^803 + x^798 + x^797 + x^796 + x^792 + x^788 + x^786 + x^784 + x^783 + x^781 + x^779 + x^777 + x^776 + x^774 + x^773 + x^770 + x^769 + x^766 + x^762 + x^760 + x^756 + x^754 + x^752 + x^751 + x^749 + x^746 + x^745 + x^740 + x^737 + x^736 + x^734 + x^730 + x^727 + x^723 + x^721 + x^720 + x^718 + x^716 + x^714 + x^712 + x^709 + x^707 + x^706 + x^705 + x^704 + x^703 + x^702 + x^701 + x^700 + x^699 + x^697 + x^696 + x^695 + x^690 + x^689 + x^687 + x^685 + x^684 + x^682 + x^678 + x^675 + x^672 + x^670 + x^669 + x^668 + x^666 + x^665 + x^663 + x^662 + x^661 + x^659 + x^657 + x^655 + x^651 + x^650 + x^648 + x^646 + x^641 + x^639 + x^634 + x^632 + x^631 + x^630 + x^629 + x^626 + x^625 + x^623 + x^620 + x^619 + x^614 + x^612 + x^610 + x^609 + x^608 + x^607 + x^606 + x^604 + x^603 + x^602 + x^601 + x^600 + x^597 + x^596 + x^595 + x^594 + x^593 + x^591 + x^588 + x^585 + x^584 + x^583 + x^582 + x^581 + x^580 + x^578 + x^576 + x^574 + x^573 + x^572 + x^571 + x^570 + x^569 + x^563 + x^561 + x^560 + x^558 + x^556 + x^553 + x^552 + x^551 + x^550 + x^549 + x^546 + x^545 + x^544 + x^543 + x^542 + x^539 + x^538 + x^537 + x^536 + x^534 + x^532 + x^531 + x^529 + x^523 + x^519 + x^518 + x^517 + x^515 + x^514 + x^513 + x^512 + x^510 + x^509 + x^508 + x^503 + x^502 + x^501 + x^500 + x^499 + x^498 + x^496 + x^495 + x^494 + x^491 + x^490 + x^489 + x^487 + x^486 + x^484 + x^482 + x^481 + x^473 + x^471 + x^468 + x^466 + x^465 + x^464 + x^461 + x^460 + x^457 + x^456 + x^455 + x^453 + x^450 + x^448 + x^444 + x^442 + x^436 + x^435 + x^434 + x^433 + x^432 + x^431 + x^430 + x^429 + x^422 + x^421 + x^420 + x^419 + x^418 + x^417 + x^416 + x^414 + x^413 + x^412 + x^411 + x^409 + x^408 + x^407 + x^406 + x^404 + x^403 + x^402 + x^401 + x^398 + x^395 + x^392 + x^391 + x^389 + x^387 + x^385 + x^380 + x^377 + x^374 + x^372 + x^371 + x^369 + x^366 + x^363 + x^362 + x^361 + x^359 + x^358 + x^356 + x^355 + x^354 + x^353 + x^352 + x^350 + x^349 + x^348 + x^347 + x^345 + x^344 + x^343 + x^336 + x^334 + x^331 + x^330 + x^327 + x^325 + x^324 + x^322 + x^317 + x^310 + x^308 + x^307 + x^304 + x^301 + x^300 + x^299 + x^298 + x^297 + x^293 + x^290 + x^286 + x^284 + x^283 + x^282 + x^281 + x^279 + x^277 + x^275 + x^274 + x^270 + x^269 + x^267 + x^264 + x^262 + x^259 + x^254 + x^253 + x^248 + x^247 + x^245 + x^243 + x^242 + x^241 + x^237 + x^236 + x^232 + x^231 + x^230 + x^229 + x^228 + x^226 + x^225 + x^224 + x^223 + x^221 + x^218 + x^215 + x^214 + x^213 + x^212 + x^210 + x^208 + x^204 + x^202 + x^201 + x^199 + x^196 + x^195 + x^193 + x^192 + x^191 + x^190 + x^186 + x^184 + x^179 + x^178 + x^177 + x^176 + x^175 + x^174 + x^173 + x^171 + x^169 + x^167 + x^166 + x^165 + x^160 + x^158 + x^155 + x^154 + x^153 + x^152 + x^151 + x^150 + x^149 + x^148 + x^147 + x^145 + x^143 + x^140 + x^139 + x^138 + x^134 + x^132 + x^130 + x^129 + x^128 + x^127 + x^120 + x^118 + x^117 + x^115 + x^113 + x^112 + x^109 + x^108 + x^107 + x^106 + x^105 + x^101 + x^100 + x^96 + x^95 + x^94 + x^93 + x^91 + x^89 + x^88 + x^87 + x^85 + x^84 + x^83 + x^82 + x^81 + x^79 + x^77 + x^75 + x^74 + x^73 + x^72 + x^70 + x^68 + x^67 + x^65 + x^64 + x^63 + x^60 + x^58 + x^57 + x^56 + x^54 + x^53 + x^52 + x^50 + x^45 + x^44 + x^43 + x^41 + x^35 + x^30 + x^23 + x^20 + x^18 + x^15 + x^6 + 1
+
+44-9-45 441 x^928 + x^898 + x^890 + x^885 + x^880 + x^872 + x^870 + x^867 + x^860 + x^850 + x^847 + x^842 + x^837 + x^834 + x^830 + x^825 + x^824 + x^817 + x^816 + x^811 + x^808 + x^806 + x^802 + x^800 + x^798 + x^797 + x^794 + x^792 + x^790 + x^787 + x^786 + x^782 + x^778 + x^777 + x^776 + x^774 + x^773 + x^772 + x^771 + x^769 + x^768 + x^767 + x^765 + x^760 + x^759 + x^758 + x^757 + x^754 + x^750 + x^749 + x^747 + x^746 + x^745 + x^743 + x^741 + x^738 + x^737 + x^736 + x^734 + x^732 + x^725 + x^724 + x^722 + x^721 + x^720 + x^717 + x^716 + x^714 + x^713 + x^710 + x^708 + x^707 + x^705 + x^704 + x^702 + x^701 + x^699 + x^698 + x^696 + x^694 + x^692 + x^691 + x^690 + x^686 + x^683 + x^681 + x^679 + x^676 + x^674 + x^673 + x^672 + x^671 + x^668 + x^667 + x^665 + x^664 + x^663 + x^662 + x^661 + x^659 + x^658 + x^657 + x^655 + x^654 + x^652 + x^651 + x^648 + x^644 + x^643 + x^641 + x^640 + x^635 + x^634 + x^633 + x^632 + x^631 + x^630 + x^629 + x^628 + x^624 + x^621 + x^619 + x^618 + x^617 + x^616 + x^614 + x^611 + x^610 + x^608 + x^607 + x^599 + x^598 + x^596 + x^594 + x^590 + x^589 + x^588 + x^587 + x^586 + x^584 + x^581 + x^579 + x^578 + x^577 + x^575 + x^573 + x^572 + x^570 + x^569 + x^568 + x^567 + x^565 + x^564 + x^563 + x^562 + x^561 + x^559 + x^558 + x^554 + x^553 + x^552 + x^551 + x^549 + x^546 + x^544 + x^541 + x^537 + x^536 + x^535 + x^532 + x^531 + x^529 + x^526 + x^517 + x^516 + x^514 + x^511 + x^509 + x^507 + x^502 + x^501 + x^498 + x^497 + x^495 + x^494 + x^493 + x^491 + x^487 + x^486 + x^485 + x^484 + x^483 + x^482 + x^481 + x^480 + x^477 + x^474 + x^473 + x^470 + x^469 + x^467 + x^466 + x^465 + x^463 + x^462 + x^461 + x^460 + x^459 + x^457 + x^455 + x^454 + x^453 + x^451 + x^449 + x^448 + x^446 + x^443 + x^442 + x^440 + x^437 + x^436 + x^435 + x^434 + x^433 + x^431 + x^430 + x^427 + x^423 + x^419 + x^418 + x^414 + x^412 + x^411 + x^410 + x^409 + x^407 + x^406 + x^405 + x^402 + x^397 + x^395 + x^394 + x^389 + x^388 + x^384 + x^381 + x^380 + x^379 + x^378 + x^376 + x^373 + x^372 + x^371 + x^370 + x^368 + x^367 + x^366 + x^365 + x^364 + x^362 + x^361 + x^355 + x^354 + x^352 + x^350 + x^347 + x^345 + x^344 + x^343 + x^342 + x^339 + x^337 + x^335 + x^333 + x^332 + x^329 + x^327 + x^323 + x^322 + x^321 + x^320 + x^319 + x^316 + x^312 + x^310 + x^309 + x^308 + x^307 + x^305 + x^303 + x^302 + x^301 + x^298 + x^294 + x^293 + x^283 + x^282 + x^276 + x^275 + x^274 + x^271 + x^267 + x^264 + x^262 + x^261 + x^259 + x^258 + x^257 + x^256 + x^253 + x^252 + x^251 + x^250 + x^246 + x^245 + x^244 + x^243 + x^242 + x^241 + x^240 + x^238 + x^237 + x^236 + x^235 + x^233 + x^232 + x^229 + x^228 + x^225 + x^224 + x^223 + x^220 + x^216 + x^211 + x^210 + x^207 + x^205 + x^204 + x^202 + x^201 + x^200 + x^197 + x^196 + x^195 + x^194 + x^190 + x^189 + x^185 + x^183 + x^182 + x^181 + x^180 + x^179 + x^177 + x^176 + x^174 + x^173 + x^172 + x^171 + x^168 + x^167 + x^164 + x^163 + x^157 + x^156 + x^152 + x^151 + x^150 + x^148 + x^147 + x^146 + x^143 + x^142 + x^141 + x^140 + x^139 + x^138 + x^137 + x^135 + x^134 + x^133 + x^132 + x^129 + x^128 + x^127 + x^125 + x^122 + x^120 + x^119 + x^113 + x^112 + x^110 + x^109 + x^108 + x^107 + x^106 + x^105 + x^104 + x^101 + x^98 + x^97 + x^96 + x^93 + x^92 + x^91 + x^87 + x^83 + x^80 + x^78 + x^77 + x^74 + x^72 + x^66 + x^64 + x^63 + x^60 + x^59 + x^56 + x^51 + x^50 + x^49 + x^46 + x^43 + x^42 + x^41 + x^39 + x^38 + x^32 + x^31 + x^29 + x^28 + x^26 + x^24 + x^23 + x^21 + x^20 + x^19 + x^18 + x^15 + x^10 + 1
diff --git a/lib/stdlib/test/stdlib.spec b/lib/stdlib/test/stdlib.spec
index 9c625091a8..4de7c1a0eb 100644
--- a/lib/stdlib/test/stdlib.spec
+++ b/lib/stdlib/test/stdlib.spec
@@ -1,4 +1,4 @@
{suites,"../stdlib_test",all}.
{skip_groups,"../stdlib_test",stdlib_bench_SUITE,
- [base64,gen_server,gen_statem,unicode],
+ [binary,base64,gen_server,gen_statem,unicode],
"Benchmark only"}.
diff --git a/lib/stdlib/test/stdlib_bench_SUITE.erl b/lib/stdlib/test/stdlib_bench_SUITE.erl
index 2364e8376f..3456442088 100644
--- a/lib/stdlib/test/stdlib_bench_SUITE.erl
+++ b/lib/stdlib/test/stdlib_bench_SUITE.erl
@@ -29,7 +29,7 @@ suite() -> [{ct_hooks,[{ts_install_cth,[{nodenames,2}]}]}].
all() ->
- [{group,unicode},{group,base64},
+ [{group,unicode},{group,base64},{group,binary},
{group,gen_server},{group,gen_statem},
{group,gen_server_comparison},{group,gen_statem_comparison}].
@@ -38,6 +38,13 @@ groups() ->
[norm_nfc_list, norm_nfc_deep_l, norm_nfc_binary,
string_lexemes_list, string_lexemes_binary
]},
+ %% Only run 1 binary match repeat as it is very slow pre OTP-22.
+ %% The results seem to be stable enough anyway
+ {binary, [{repeat, 1}],
+ [match_single_pattern_no_match,
+ matches_single_pattern_no_match,
+ matches_single_pattern_eventual_match,
+ matches_single_pattern_frequent_match]},
{base64,[{repeat,5}],
[decode_binary, decode_binary_to_string,
decode_list, decode_list_to_string,
@@ -60,9 +67,9 @@ cases(gen_server) ->
[simple, simple_timer, simple_mon, simple_timer_mon,
generic, generic_timer];
cases(gen_statem) ->
- [generic, generic_fsm, generic_fsm_transit,
- generic_statem, generic_statem_transit,
- generic_statem_complex].
+ [generic, generic_log, generic_log100, generic_fsm, generic_fsm_transit,
+ generic_statem, generic_statem_log, generic_statem_log100,
+ generic_statem_transit, generic_statem_complex].
init_per_group(gen_server, Config) ->
compile_servers(Config),
@@ -157,41 +164,59 @@ norm_data(Config) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+match_single_pattern_no_match(_Config) ->
+ Binary = binary:copy(<<"ugbcfuysabfuqyfikgfsdalpaskfhgjsdgfjwsalp">>, 1000000),
+ comment(test(100, binary, match, [Binary, <<"o">>])).
+
+matches_single_pattern_no_match(_Config) ->
+ Binary = binary:copy(<<"ugbcfuysabfuqyfikgfsdalpaskfhgjsdgfjwsalp">>, 1000000),
+ comment(test(100, binary, matches, [Binary, <<"o">>])).
+
+matches_single_pattern_eventual_match(_Config) ->
+ Binary = binary:copy(<<"ugbcfuysabfuqyfikgfsdalpaskfhgjsdgfjwsal\n">>, 1000000),
+ comment(test(100, binary, matches, [Binary, <<"\n">>])).
+
+matches_single_pattern_frequent_match(_Config) ->
+ Binary = binary:copy(<<"abc\n">>, 1000000),
+ comment(test(100, binary, matches, [Binary, <<"abc">>])).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
decode_binary(_Config) ->
- comment(test(decode, encoded_binary())).
+ comment(test(base64, decode, [encoded_binary()])).
decode_binary_to_string(_Config) ->
- comment(test(decode_to_string, encoded_binary())).
+ comment(test(base64, decode_to_string, [encoded_binary()])).
decode_list(_Config) ->
- comment(test(decode, encoded_list())).
+ comment(test(base64, decode, [encoded_list()])).
decode_list_to_string(_Config) ->
- comment(test(decode_to_string, encoded_list())).
+ comment(test(base64, decode_to_string, [encoded_list()])).
encode_binary(_Config) ->
- comment(test(encode, binary())).
+ comment(test(base64, encode, [binary()])).
encode_binary_to_string(_Config) ->
- comment(test(encode_to_string, binary())).
+ comment(test(base64, encode_to_string, [binary()])).
encode_list(_Config) ->
- comment(test(encode, list())).
+ comment(test(base64, encode, [list()])).
encode_list_to_string(_Config) ->
- comment(test(encode_to_string, list())).
+ comment(test(base64, encode_to_string, [list()])).
mime_binary_decode(_Config) ->
- comment(test(mime_decode, encoded_binary())).
+ comment(test(base64, mime_decode, [encoded_binary()])).
mime_binary_decode_to_string(_Config) ->
- comment(test(mime_decode_to_string, encoded_binary())).
+ comment(test(base64, mime_decode_to_string, [encoded_binary()])).
mime_list_decode(_Config) ->
- comment(test(mime_decode, encoded_list())).
+ comment(test(base64, mime_decode, [encoded_list()])).
mime_list_decode_to_string(_Config) ->
- comment(test(mime_decode_to_string, encoded_list())).
+ comment(test(base64, mime_decode_to_string, [encoded_list()])).
-define(SIZE, 10000).
-define(N, 1000).
@@ -209,15 +234,17 @@ binary() ->
list() ->
random_byte_list(?SIZE).
-test(Func, Data) ->
- F = fun() -> loop(?N, Func, Data) end,
+test(Mod, Fun, Args) ->
+ test(?N, Mod, Fun, Args).
+test(Iter, Mod, Fun, Args) ->
+ F = fun() -> loop(Iter, Mod, Fun, Args) end,
{Time, ok} = timer:tc(fun() -> lspawn(F) end),
- report_base64(Time).
+ report_mfa(Iter, Time, Mod).
-loop(0, _F, _D) -> garbage_collect(), ok;
-loop(N, F, D) ->
- _ = base64:F(D),
- loop(N - 1, F, D).
+loop(0, _M, _F, _A) -> garbage_collect(), ok;
+loop(N, M, F, A) ->
+ _ = apply(M, F, A),
+ loop(N - 1, M, F, A).
lspawn(Fun) ->
{Pid, Ref} = spawn_monitor(fun() -> exit(Fun()) end),
@@ -225,10 +252,10 @@ lspawn(Fun) ->
{'DOWN', Ref, process, Pid, Rep} -> Rep
end.
-report_base64(Time) ->
- Tps = round((?N*1000000)/Time),
+report_mfa(Iter, Time, Mod) ->
+ Tps = round((Iter*1000000)/Time),
ct_event:notify(#event{name = benchmark_data,
- data = [{suite, "stdlib_base64"},
+ data = [{suite, "stdlib_" ++ atom_to_list(Mod)},
{value, Tps}]}),
Tps.
@@ -267,12 +294,24 @@ simple_timer_mon(Config) when is_list(Config) ->
generic(Config) when is_list(Config) ->
comment(do_tests(generic, single_small, Config)).
+generic_log(Config) when is_list(Config) ->
+ comment(do_tests(generic_log, single_small, Config)).
+
+generic_log100(Config) when is_list(Config) ->
+ comment(do_tests(generic_log100, single_small, Config)).
+
generic_timer(Config) when is_list(Config) ->
comment(do_tests(generic_timer, single_small, Config)).
generic_statem(Config) when is_list(Config) ->
comment(do_tests(generic_statem, single_small, Config)).
+generic_statem_log(Config) when is_list(Config) ->
+ comment(do_tests(generic_statem_log, single_small, Config)).
+
+generic_statem_log100(Config) when is_list(Config) ->
+ comment(do_tests(generic_statem_log100, single_small, Config)).
+
generic_statem_transit(Config) when is_list(Config) ->
comment(do_tests(generic_statem_transit, single_small, Config)).
@@ -344,9 +383,9 @@ norm(T, Ref) ->
do_tests(Test, ParamSet, Config) ->
BenchmarkSuite = ?config(benchmark_suite, Config),
- {Client, ServerMod} = bench(Test),
+ {Client, ServerMod, ServerArg} = bench(Test),
{Parallelism, Message} = bench_params(ParamSet),
- Fun = create_clients(Message, ServerMod, Client, Parallelism),
+ Fun = create_clients(Message, ServerMod, ServerArg, Client, Parallelism),
{TotalLoops, AllPidTime} = run_test(Fun),
try ?CALLS_PER_LOOP * round((1000 * TotalLoops) / AllPidTime) of
PerSecond ->
@@ -461,27 +500,35 @@ generic_fsm_transit_client(N, M, P) ->
generic_fsm_transit_client(N+1, M, P).
bench(simple) ->
- {fun simple_client/3, simple_server};
+ {fun simple_client/3, simple_server, term};
bench(simple_timer) ->
- {fun simple_client_timer/3, simple_server_timer};
+ {fun simple_client_timer/3, simple_server_timer, term};
bench(simple_mon) ->
- {fun simple_client_mon/3, simple_server_mon};
+ {fun simple_client_mon/3, simple_server_mon, term};
bench(simple_timer_mon) ->
- {fun simple_client_timer_mon/3, simple_server_timer_mon};
+ {fun simple_client_timer_mon/3, simple_server_timer_mon, term};
bench(generic) ->
- {fun generic_client/3, generic_server};
+ {fun generic_client/3, generic_server, [term]};
+bench(generic_log) ->
+ {fun generic_client/3, generic_server, [term,{debug,[log]}]};
+bench(generic_log100) ->
+ {fun generic_client/3, generic_server, [term,{debug,[{log,100}]}]};
bench(generic_timer) ->
- {fun generic_timer_client/3, generic_server_timer};
+ {fun generic_timer_client/3, generic_server_timer, term};
bench(generic_statem) ->
- {fun generic_statem_client/3, generic_statem};
+ {fun generic_statem_client/3, generic_statem, [term]};
+bench(generic_statem_log) ->
+ {fun generic_statem_client/3, generic_statem, [term,{debug,[log]}]};
+bench(generic_statem_log100) ->
+ {fun generic_statem_client/3, generic_statem, [term,{debug,[{log,100}]}]};
bench(generic_statem_transit) ->
- {fun generic_statem_transit_client/3, generic_statem};
+ {fun generic_statem_transit_client/3, generic_statem, [term]};
bench(generic_statem_complex) ->
- {fun generic_statem_complex_client/3, generic_statem_complex};
+ {fun generic_statem_complex_client/3, generic_statem_complex, term};
bench(generic_fsm) ->
- {fun generic_fsm_client/3, generic_fsm};
+ {fun generic_fsm_client/3, generic_fsm, term};
bench(generic_fsm_transit) ->
- {fun generic_fsm_transit_client/3, generic_fsm}.
+ {fun generic_fsm_transit_client/3, generic_fsm, term}.
%% -> {Parallelism, MessageTerm}
bench_params(single_small) -> {1, small()};
@@ -509,10 +556,9 @@ parallelism() ->
_ -> 1
end.
-create_clients(M, ServerMod, Client, Parallel) ->
+create_clients(M, ServerMod, ServerArg, Client, Parallel) ->
fun() ->
- State = term,
- ServerPid = ServerMod:start(State),
+ ServerPid = ServerMod:start(ServerArg),
PidRefs = [spawn_monitor(fun() -> Client(0, M, ServerPid) end) ||
_ <- lists:seq(1, Parallel)],
timer:sleep(?MAX_TIME),
diff --git a/lib/stdlib/test/stdlib_bench_SUITE_data/generic_server.erl b/lib/stdlib/test/stdlib_bench_SUITE_data/generic_server.erl
index abd61dcdef..3707d00dee 100644
--- a/lib/stdlib/test/stdlib_bench_SUITE_data/generic_server.erl
+++ b/lib/stdlib/test/stdlib_bench_SUITE_data/generic_server.erl
@@ -8,8 +8,8 @@
-define(GEN_SERVER, gen_server).
-start(State) ->
- {ok, Pid} = ?GEN_SERVER:start(?MODULE, State, []),
+start([State|Opts]) ->
+ {ok, Pid} = ?GEN_SERVER:start(?MODULE, State, Opts),
Pid.
init(State) ->
diff --git a/lib/stdlib/test/stdlib_bench_SUITE_data/generic_statem.erl b/lib/stdlib/test/stdlib_bench_SUITE_data/generic_statem.erl
index 2e0491f060..b106619568 100644
--- a/lib/stdlib/test/stdlib_bench_SUITE_data/generic_statem.erl
+++ b/lib/stdlib/test/stdlib_bench_SUITE_data/generic_statem.erl
@@ -28,8 +28,8 @@
%% API
-start(Data) ->
- {ok, Pid} = gen_statem:start(?MODULE, Data, []),
+start([Data|Opts]) ->
+ {ok, Pid} = gen_statem:start(?MODULE, Data, Opts),
Pid.
stop(P) ->
@@ -52,7 +52,9 @@ init(Data) ->
state1({call, From}, {reply, M}, Data) ->
{keep_state, Data, {reply, From, M}};
state1({call, From}, {transit, M}, Data) ->
- {next_state, state2, Data, {reply, From, M}}.
+ {next_state, state2, Data,
+ [{reply, From, M},{state_timeout,5000,5000}]}.
state2({call, From}, {transit, M}, Data) ->
- {next_state, state1, Data, {reply, From, M}}.
+ {next_state, state1, Data,
+ [{reply, From, M},{state_timeout,5000,5000}]}.
diff --git a/lib/stdlib/test/unicode_util_SUITE_data/GraphemeBreakTest.txt b/lib/stdlib/test/unicode_util_SUITE_data/GraphemeBreakTest.txt
index d7d8f90de0..6847953c23 100644
--- a/lib/stdlib/test/unicode_util_SUITE_data/GraphemeBreakTest.txt
+++ b/lib/stdlib/test/unicode_util_SUITE_data/GraphemeBreakTest.txt
@@ -1,6 +1,6 @@
-# GraphemeBreakTest-10.0.0.txt
-# Date: 2017-04-14, 05:40:29 GMT
-# © 2017 Unicode®, Inc.
+# GraphemeBreakTest-11.0.0.txt
+# Date: 2018-03-18, 13:30:33 GMT
+# © 2018 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
#
@@ -23,828 +23,678 @@
# These samples may be extended or changed in the future.
#
÷ 0020 ÷ 0020 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] SPACE (Other) ÷ [0.3]
-÷ 0020 × 0308 ÷ 0020 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 0020 × 0308 ÷ 0020 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 0020 ÷ 000D ÷ # ÷ [0.2] SPACE (Other) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ 0020 × 0308 ÷ 000D ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0020 × 0308 ÷ 000D ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
÷ 0020 ÷ 000A ÷ # ÷ [0.2] SPACE (Other) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ 0020 × 0308 ÷ 000A ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0020 × 0308 ÷ 000A ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
÷ 0020 ÷ 0001 ÷ # ÷ [0.2] SPACE (Other) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 0020 × 0308 ÷ 0001 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 0020 × 0300 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
-÷ 0020 × 0308 × 0300 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
+÷ 0020 × 0308 ÷ 0001 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0020 × 034F ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 0020 × 0308 × 034F ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 0020 ÷ 1F1E6 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0020 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
÷ 0020 ÷ 0600 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ 0020 × 0308 ÷ 0600 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0020 × 0308 ÷ 0600 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
÷ 0020 × 0903 ÷ # ÷ [0.2] SPACE (Other) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ 0020 × 0308 × 0903 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0020 × 0308 × 0903 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
÷ 0020 ÷ 1100 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ 0020 × 0308 ÷ 1100 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0020 × 0308 ÷ 1100 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
÷ 0020 ÷ 1160 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ 0020 × 0308 ÷ 1160 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0020 × 0308 ÷ 1160 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
÷ 0020 ÷ 11A8 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ 0020 × 0308 ÷ 11A8 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0020 × 0308 ÷ 11A8 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
÷ 0020 ÷ AC00 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ 0020 × 0308 ÷ AC00 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0020 × 0308 ÷ AC00 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
÷ 0020 ÷ AC01 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 0020 × 0308 ÷ AC01 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 0020 ÷ 1F1E6 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 0020 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 0020 ÷ 261D ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 0020 × 0308 ÷ 261D ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 0020 ÷ 1F3FB ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 0020 × 0308 ÷ 1F3FB ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 0020 × 200D ÷ # ÷ [0.2] SPACE (Other) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 0020 × 0308 × 200D ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 0020 ÷ 2640 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 0020 × 0308 ÷ 2640 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 0020 ÷ 1F466 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] BOY (EBG) ÷ [0.3]
-÷ 0020 × 0308 ÷ 1F466 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] BOY (EBG) ÷ [0.3]
+÷ 0020 × 0308 ÷ AC01 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0020 ÷ 231A ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0020 × 0308 ÷ 231A ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0020 × 0300 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0020 × 0308 × 0300 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0020 × 200D ÷ # ÷ [0.2] SPACE (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0020 × 0308 × 200D ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ 0020 ÷ 0378 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 0020 × 0308 ÷ 0378 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 0020 × 0308 ÷ 0378 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
÷ 0020 ÷ D800 ÷ # ÷ [0.2] SPACE (Other) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 0020 × 0308 ÷ D800 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
+÷ 0020 × 0308 ÷ D800 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ 000D ÷ 0020 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] SPACE (Other) ÷ [0.3]
-÷ 000D ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 000D ÷ 000D ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ 000D ÷ 0308 ÷ 000D ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ 000D ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
÷ 000D × 000A ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) × [3.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ 000D ÷ 0308 ÷ 000A ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ 000A ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
÷ 000D ÷ 0001 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 000D ÷ 0308 ÷ 0001 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 000D ÷ 0300 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
-÷ 000D ÷ 0308 × 0300 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ 0001 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 000D ÷ 034F ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 000D ÷ 0308 × 034F ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 000D ÷ 1F1E6 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ 1F1E6 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
÷ 000D ÷ 0600 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ 000D ÷ 0308 ÷ 0600 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ 0600 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
÷ 000D ÷ 0903 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ 000D ÷ 0308 × 0903 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 000D ÷ 0308 × 0903 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
÷ 000D ÷ 1100 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ 000D ÷ 0308 ÷ 1100 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ 1100 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
÷ 000D ÷ 1160 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ 000D ÷ 0308 ÷ 1160 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ 1160 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
÷ 000D ÷ 11A8 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ 000D ÷ 0308 ÷ 11A8 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ 11A8 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
÷ 000D ÷ AC00 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ 000D ÷ 0308 ÷ AC00 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ AC00 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
÷ 000D ÷ AC01 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 000D ÷ 0308 ÷ AC01 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 000D ÷ 1F1E6 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 000D ÷ 0308 ÷ 1F1E6 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 000D ÷ 261D ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 000D ÷ 0308 ÷ 261D ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 000D ÷ 1F3FB ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 000D ÷ 0308 ÷ 1F3FB ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 000D ÷ 200D ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 000D ÷ 0308 × 200D ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 000D ÷ 2640 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 000D ÷ 0308 ÷ 2640 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 000D ÷ 1F466 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] BOY (EBG) ÷ [0.3]
-÷ 000D ÷ 0308 ÷ 1F466 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] BOY (EBG) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ AC01 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 000D ÷ 231A ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] WATCH (ExtPict) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ 231A ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 000D ÷ 0300 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 000D ÷ 0308 × 0300 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 000D ÷ 200D ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 000D ÷ 0308 × 200D ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ 000D ÷ 0378 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 000D ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
÷ 000D ÷ D800 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 000D ÷ 0308 ÷ D800 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
+÷ 000D ÷ 0308 ÷ D800 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ 000A ÷ 0020 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] SPACE (Other) ÷ [0.3]
-÷ 000A ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 000A ÷ 000D ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ 000A ÷ 0308 ÷ 000D ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ 000D ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
÷ 000A ÷ 000A ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ 000A ÷ 0308 ÷ 000A ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ 000A ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
÷ 000A ÷ 0001 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 000A ÷ 0308 ÷ 0001 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 000A ÷ 0300 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
-÷ 000A ÷ 0308 × 0300 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ 0001 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 000A ÷ 034F ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 000A ÷ 0308 × 034F ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 000A ÷ 1F1E6 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ 1F1E6 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
÷ 000A ÷ 0600 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ 000A ÷ 0308 ÷ 0600 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ 0600 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
÷ 000A ÷ 0903 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ 000A ÷ 0308 × 0903 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 000A ÷ 0308 × 0903 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
÷ 000A ÷ 1100 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ 000A ÷ 0308 ÷ 1100 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ 1100 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
÷ 000A ÷ 1160 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ 000A ÷ 0308 ÷ 1160 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ 1160 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
÷ 000A ÷ 11A8 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ 000A ÷ 0308 ÷ 11A8 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ 11A8 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
÷ 000A ÷ AC00 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ 000A ÷ 0308 ÷ AC00 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ AC00 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
÷ 000A ÷ AC01 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 000A ÷ 0308 ÷ AC01 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 000A ÷ 1F1E6 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 000A ÷ 0308 ÷ 1F1E6 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 000A ÷ 261D ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 000A ÷ 0308 ÷ 261D ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 000A ÷ 1F3FB ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 000A ÷ 0308 ÷ 1F3FB ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 000A ÷ 200D ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 000A ÷ 0308 × 200D ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 000A ÷ 2640 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 000A ÷ 0308 ÷ 2640 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 000A ÷ 1F466 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] BOY (EBG) ÷ [0.3]
-÷ 000A ÷ 0308 ÷ 1F466 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] BOY (EBG) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ AC01 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 000A ÷ 231A ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] WATCH (ExtPict) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ 231A ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 000A ÷ 0300 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 000A ÷ 0308 × 0300 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 000A ÷ 200D ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 000A ÷ 0308 × 200D ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ 000A ÷ 0378 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 000A ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
÷ 000A ÷ D800 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 000A ÷ 0308 ÷ D800 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
+÷ 000A ÷ 0308 ÷ D800 ÷ # ÷ [0.2] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ 0001 ÷ 0020 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] SPACE (Other) ÷ [0.3]
-÷ 0001 ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 0001 ÷ 000D ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ 0001 ÷ 0308 ÷ 000D ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ 000D ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
÷ 0001 ÷ 000A ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ 0001 ÷ 0308 ÷ 000A ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ 000A ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
÷ 0001 ÷ 0001 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 0001 ÷ 0308 ÷ 0001 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 0001 ÷ 0300 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
-÷ 0001 ÷ 0308 × 0300 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ 0001 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0001 ÷ 034F ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 0001 ÷ 0308 × 034F ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 0001 ÷ 1F1E6 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ 1F1E6 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
÷ 0001 ÷ 0600 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ 0001 ÷ 0308 ÷ 0600 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ 0600 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
÷ 0001 ÷ 0903 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ 0001 ÷ 0308 × 0903 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0001 ÷ 0308 × 0903 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
÷ 0001 ÷ 1100 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ 0001 ÷ 0308 ÷ 1100 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ 1100 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
÷ 0001 ÷ 1160 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ 0001 ÷ 0308 ÷ 1160 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ 1160 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
÷ 0001 ÷ 11A8 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ 0001 ÷ 0308 ÷ 11A8 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ 11A8 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
÷ 0001 ÷ AC00 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ 0001 ÷ 0308 ÷ AC00 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ AC00 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
÷ 0001 ÷ AC01 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 0001 ÷ 0308 ÷ AC01 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 0001 ÷ 1F1E6 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 0001 ÷ 0308 ÷ 1F1E6 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 0001 ÷ 261D ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 0001 ÷ 0308 ÷ 261D ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 0001 ÷ 1F3FB ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 0001 ÷ 0308 ÷ 1F3FB ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 0001 ÷ 200D ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 0001 ÷ 0308 × 200D ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 0001 ÷ 2640 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 0001 ÷ 0308 ÷ 2640 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 0001 ÷ 1F466 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] BOY (EBG) ÷ [0.3]
-÷ 0001 ÷ 0308 ÷ 1F466 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] BOY (EBG) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ AC01 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0001 ÷ 231A ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ 231A ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0001 ÷ 0300 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0001 ÷ 0308 × 0300 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0001 ÷ 200D ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0001 ÷ 0308 × 200D ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ 0001 ÷ 0378 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 0001 ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
÷ 0001 ÷ D800 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 0001 ÷ 0308 ÷ D800 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 0300 ÷ 0020 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3]
-÷ 0300 × 0308 ÷ 0020 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3]
-÷ 0300 ÷ 000D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ 0300 × 0308 ÷ 000D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ 0300 ÷ 000A ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ 0300 × 0308 ÷ 000A ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ 0300 ÷ 0001 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 0300 × 0308 ÷ 0001 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 0300 × 0300 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
-÷ 0300 × 0308 × 0300 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
-÷ 0300 ÷ 0600 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ 0300 × 0308 ÷ 0600 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ 0300 × 0903 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ 0300 × 0308 × 0903 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) × [9.0] COMBINING DIAERESIS (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ 0300 ÷ 1100 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ 0300 × 0308 ÷ 1100 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ 0300 ÷ 1160 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ 0300 × 0308 ÷ 1160 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ 0300 ÷ 11A8 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ 0300 × 0308 ÷ 11A8 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ 0300 ÷ AC00 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ 0300 × 0308 ÷ AC00 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ 0300 ÷ AC01 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 0300 × 0308 ÷ AC01 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 0300 ÷ 1F1E6 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 0300 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 0300 ÷ 261D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 0300 × 0308 ÷ 261D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 0300 ÷ 1F3FB ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 0300 × 0308 ÷ 1F3FB ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 0300 × 200D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 0300 × 0308 × 200D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 0300 ÷ 2640 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 0300 × 0308 ÷ 2640 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 0300 ÷ 1F466 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) ÷ [999.0] BOY (EBG) ÷ [0.3]
-÷ 0300 × 0308 ÷ 1F466 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] BOY (EBG) ÷ [0.3]
-÷ 0300 ÷ 0378 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 0300 × 0308 ÷ 0378 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 0300 ÷ D800 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 0300 × 0308 ÷ D800 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
+÷ 0001 ÷ 0308 ÷ D800 ÷ # ÷ [0.2] <START OF HEADING> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
+÷ 034F ÷ 0020 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 034F × 0308 ÷ 0020 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 034F ÷ 000D ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 034F × 0308 ÷ 000D ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 034F ÷ 000A ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 034F × 0308 ÷ 000A ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 034F ÷ 0001 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 034F × 0308 ÷ 0001 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 034F × 034F ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 034F × 0308 × 034F ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 034F ÷ 1F1E6 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 034F × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 034F ÷ 0600 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 034F × 0308 ÷ 0600 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 034F × 0903 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 034F × 0308 × 0903 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 034F ÷ 1100 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 034F × 0308 ÷ 1100 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 034F ÷ 1160 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 034F × 0308 ÷ 1160 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 034F ÷ 11A8 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 034F × 0308 ÷ 11A8 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 034F ÷ AC00 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 034F × 0308 ÷ AC00 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 034F ÷ AC01 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 034F × 0308 ÷ AC01 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 034F ÷ 231A ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 034F × 0308 ÷ 231A ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 034F × 0300 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 034F × 0308 × 0300 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 034F × 200D ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 034F × 0308 × 200D ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 034F ÷ 0378 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 034F × 0308 ÷ 0378 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 034F ÷ D800 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
+÷ 034F × 0308 ÷ D800 ÷ # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
+÷ 1F1E6 ÷ 0020 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ 0020 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 1F1E6 ÷ 000D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ 000D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 1F1E6 ÷ 000A ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ 000A ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 1F1E6 ÷ 0001 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ 0001 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 1F1E6 × 034F ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 1F1E6 × 0308 × 034F ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 1F1E6 × 1F1E6 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [12.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 1F1E6 ÷ 0600 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ 0600 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 1F1E6 × 0903 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 1F1E6 × 0308 × 0903 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 1F1E6 ÷ 1100 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ 1100 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 1F1E6 ÷ 1160 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ 1160 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 1F1E6 ÷ 11A8 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ 11A8 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 1F1E6 ÷ AC00 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ AC00 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 1F1E6 ÷ AC01 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ AC01 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 1F1E6 ÷ 231A ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ 231A ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 1F1E6 × 0300 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 1F1E6 × 0308 × 0300 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 1F1E6 × 200D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 1F1E6 × 0308 × 200D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 1F1E6 ÷ 0378 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ 0378 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 1F1E6 ÷ D800 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
+÷ 1F1E6 × 0308 ÷ D800 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ 0600 × 0020 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] SPACE (Other) ÷ [0.3]
-÷ 0600 × 0308 ÷ 0020 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 0600 × 0308 ÷ 0020 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 0600 ÷ 000D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ 0600 × 0308 ÷ 000D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0600 × 0308 ÷ 000D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
÷ 0600 ÷ 000A ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ 0600 × 0308 ÷ 000A ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0600 × 0308 ÷ 000A ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
÷ 0600 ÷ 0001 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 0600 × 0308 ÷ 0001 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 0600 × 0300 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
-÷ 0600 × 0308 × 0300 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
+÷ 0600 × 0308 ÷ 0001 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0600 × 034F ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 0600 × 0308 × 034F ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 0600 × 1F1E6 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0600 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
÷ 0600 × 0600 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ 0600 × 0308 ÷ 0600 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0600 × 0308 ÷ 0600 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
÷ 0600 × 0903 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ 0600 × 0308 × 0903 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0600 × 0308 × 0903 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
÷ 0600 × 1100 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ 0600 × 0308 ÷ 1100 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0600 × 0308 ÷ 1100 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
÷ 0600 × 1160 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ 0600 × 0308 ÷ 1160 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0600 × 0308 ÷ 1160 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
÷ 0600 × 11A8 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ 0600 × 0308 ÷ 11A8 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0600 × 0308 ÷ 11A8 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
÷ 0600 × AC00 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ 0600 × 0308 ÷ AC00 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0600 × 0308 ÷ AC00 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
÷ 0600 × AC01 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 0600 × 0308 ÷ AC01 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 0600 × 1F1E6 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 0600 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 0600 × 261D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 0600 × 0308 ÷ 261D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 0600 × 1F3FB ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 0600 × 0308 ÷ 1F3FB ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 0600 × 200D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 0600 × 0308 × 200D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 0600 × 2640 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 0600 × 0308 ÷ 2640 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 0600 × 1F466 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] BOY (EBG) ÷ [0.3]
-÷ 0600 × 0308 ÷ 1F466 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] BOY (EBG) ÷ [0.3]
+÷ 0600 × 0308 ÷ AC01 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0600 × 231A ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] WATCH (ExtPict) ÷ [0.3]
+÷ 0600 × 0308 ÷ 231A ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0600 × 0300 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0600 × 0308 × 0300 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0600 × 200D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0600 × 0308 × 200D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ 0600 × 0378 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] <reserved-0378> (Other) ÷ [0.3]
-÷ 0600 × 0308 ÷ 0378 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 0600 × 0308 ÷ 0378 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
÷ 0600 ÷ D800 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 0600 × 0308 ÷ D800 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
+÷ 0600 × 0308 ÷ D800 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ 0903 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] SPACE (Other) ÷ [0.3]
-÷ 0903 × 0308 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 0903 × 0308 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 0903 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ 0903 × 0308 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0903 × 0308 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
÷ 0903 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ 0903 × 0308 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0903 × 0308 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
÷ 0903 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 0903 × 0308 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 0903 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
-÷ 0903 × 0308 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
+÷ 0903 × 0308 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0903 × 034F ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 0903 × 0308 × 034F ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 0903 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0903 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
÷ 0903 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ 0903 × 0308 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0903 × 0308 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
÷ 0903 × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ 0903 × 0308 × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0903 × 0308 × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
÷ 0903 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ 0903 × 0308 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0903 × 0308 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
÷ 0903 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ 0903 × 0308 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0903 × 0308 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
÷ 0903 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ 0903 × 0308 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0903 × 0308 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
÷ 0903 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ 0903 × 0308 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0903 × 0308 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
÷ 0903 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 0903 × 0308 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 0903 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 0903 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 0903 ÷ 261D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 0903 × 0308 ÷ 261D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 0903 ÷ 1F3FB ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 0903 × 0308 ÷ 1F3FB ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 0903 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 0903 × 0308 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 0903 ÷ 2640 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 0903 × 0308 ÷ 2640 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 0903 ÷ 1F466 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] BOY (EBG) ÷ [0.3]
-÷ 0903 × 0308 ÷ 1F466 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] BOY (EBG) ÷ [0.3]
+÷ 0903 × 0308 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0903 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0903 × 0308 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0903 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0903 × 0308 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0903 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0903 × 0308 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ 0903 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 0903 × 0308 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 0903 × 0308 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
÷ 0903 ÷ D800 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 0903 × 0308 ÷ D800 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
+÷ 0903 × 0308 ÷ D800 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ 1100 ÷ 0020 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] SPACE (Other) ÷ [0.3]
-÷ 1100 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 1100 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 1100 ÷ 000D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ 1100 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 1100 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
÷ 1100 ÷ 000A ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ 1100 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 1100 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
÷ 1100 ÷ 0001 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 1100 × 0308 ÷ 0001 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 1100 × 0300 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
-÷ 1100 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
+÷ 1100 × 0308 ÷ 0001 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 1100 × 034F ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 1100 × 0308 × 034F ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 1100 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 1100 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
÷ 1100 ÷ 0600 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ 1100 × 0308 ÷ 0600 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 1100 × 0308 ÷ 0600 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
÷ 1100 × 0903 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ 1100 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 1100 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
÷ 1100 × 1100 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ 1100 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 1100 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
÷ 1100 × 1160 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ 1100 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 1100 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
÷ 1100 ÷ 11A8 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ 1100 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 1100 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
÷ 1100 × AC00 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ 1100 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 1100 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
÷ 1100 × AC01 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 1100 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 1100 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 1100 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 1100 ÷ 261D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 1100 × 0308 ÷ 261D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 1100 ÷ 1F3FB ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 1100 × 0308 ÷ 1F3FB ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 1100 × 200D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 1100 × 0308 × 200D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 1100 ÷ 2640 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 1100 × 0308 ÷ 2640 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 1100 ÷ 1F466 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] BOY (EBG) ÷ [0.3]
-÷ 1100 × 0308 ÷ 1F466 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] BOY (EBG) ÷ [0.3]
+÷ 1100 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 1100 ÷ 231A ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 1100 × 0308 ÷ 231A ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 1100 × 0300 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 1100 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 1100 × 200D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 1100 × 0308 × 200D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ 1100 ÷ 0378 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 1100 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 1100 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
÷ 1100 ÷ D800 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 1100 × 0308 ÷ D800 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
+÷ 1100 × 0308 ÷ D800 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ 1160 ÷ 0020 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] SPACE (Other) ÷ [0.3]
-÷ 1160 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 1160 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 1160 ÷ 000D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ 1160 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 1160 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
÷ 1160 ÷ 000A ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ 1160 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 1160 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
÷ 1160 ÷ 0001 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 1160 × 0308 ÷ 0001 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 1160 × 0300 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
-÷ 1160 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
+÷ 1160 × 0308 ÷ 0001 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 1160 × 034F ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 1160 × 0308 × 034F ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 1160 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 1160 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
÷ 1160 ÷ 0600 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ 1160 × 0308 ÷ 0600 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 1160 × 0308 ÷ 0600 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
÷ 1160 × 0903 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ 1160 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 1160 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
÷ 1160 ÷ 1100 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ 1160 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 1160 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
÷ 1160 × 1160 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [7.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ 1160 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 1160 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
÷ 1160 × 11A8 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [7.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ 1160 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 1160 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
÷ 1160 ÷ AC00 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ 1160 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 1160 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
÷ 1160 ÷ AC01 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 1160 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 1160 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 1160 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 1160 ÷ 261D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 1160 × 0308 ÷ 261D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 1160 ÷ 1F3FB ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 1160 × 0308 ÷ 1F3FB ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 1160 × 200D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 1160 × 0308 × 200D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 1160 ÷ 2640 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 1160 × 0308 ÷ 2640 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 1160 ÷ 1F466 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] BOY (EBG) ÷ [0.3]
-÷ 1160 × 0308 ÷ 1F466 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] BOY (EBG) ÷ [0.3]
+÷ 1160 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 1160 ÷ 231A ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 1160 × 0308 ÷ 231A ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 1160 × 0300 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 1160 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 1160 × 200D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 1160 × 0308 × 200D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ 1160 ÷ 0378 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 1160 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 1160 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
÷ 1160 ÷ D800 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 1160 × 0308 ÷ D800 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
+÷ 1160 × 0308 ÷ D800 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ 11A8 ÷ 0020 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] SPACE (Other) ÷ [0.3]
-÷ 11A8 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 11A8 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 11A8 ÷ 000D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ 11A8 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 11A8 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
÷ 11A8 ÷ 000A ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ 11A8 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 11A8 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
÷ 11A8 ÷ 0001 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 11A8 × 0308 ÷ 0001 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 11A8 × 0300 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
-÷ 11A8 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
+÷ 11A8 × 0308 ÷ 0001 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 11A8 × 034F ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 11A8 × 0308 × 034F ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 11A8 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 11A8 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
÷ 11A8 ÷ 0600 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ 11A8 × 0308 ÷ 0600 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 11A8 × 0308 ÷ 0600 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
÷ 11A8 × 0903 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ 11A8 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 11A8 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
÷ 11A8 ÷ 1100 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ 11A8 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 11A8 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
÷ 11A8 ÷ 1160 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ 11A8 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 11A8 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
÷ 11A8 × 11A8 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [8.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ 11A8 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 11A8 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
÷ 11A8 ÷ AC00 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ 11A8 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 11A8 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
÷ 11A8 ÷ AC01 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 11A8 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 11A8 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 11A8 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 11A8 ÷ 261D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 11A8 × 0308 ÷ 261D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 11A8 ÷ 1F3FB ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 11A8 × 0308 ÷ 1F3FB ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 11A8 × 200D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 11A8 × 0308 × 200D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 11A8 ÷ 2640 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 11A8 × 0308 ÷ 2640 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 11A8 ÷ 1F466 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] BOY (EBG) ÷ [0.3]
-÷ 11A8 × 0308 ÷ 1F466 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] BOY (EBG) ÷ [0.3]
+÷ 11A8 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 11A8 ÷ 231A ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 11A8 × 0308 ÷ 231A ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 11A8 × 0300 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 11A8 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 11A8 × 200D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 11A8 × 0308 × 200D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ 11A8 ÷ 0378 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 11A8 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 11A8 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
÷ 11A8 ÷ D800 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 11A8 × 0308 ÷ D800 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
+÷ 11A8 × 0308 ÷ D800 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ AC00 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] SPACE (Other) ÷ [0.3]
-÷ AC00 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ AC00 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ AC00 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ AC00 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ AC00 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
÷ AC00 ÷ 000A ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ AC00 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ AC00 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
÷ AC00 ÷ 0001 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ AC00 × 0308 ÷ 0001 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ AC00 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
-÷ AC00 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
+÷ AC00 × 0308 ÷ 0001 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ AC00 × 034F ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ AC00 × 0308 × 034F ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ AC00 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ AC00 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
÷ AC00 ÷ 0600 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ AC00 × 0308 ÷ 0600 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ AC00 × 0308 ÷ 0600 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
÷ AC00 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ AC00 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ AC00 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
÷ AC00 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ AC00 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ AC00 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
÷ AC00 × 1160 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [7.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ AC00 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ AC00 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
÷ AC00 × 11A8 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [7.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ AC00 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ AC00 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
÷ AC00 ÷ AC00 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ AC00 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ AC00 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
÷ AC00 ÷ AC01 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ AC00 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ AC00 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ AC00 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ AC00 ÷ 261D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ AC00 × 0308 ÷ 261D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ AC00 ÷ 1F3FB ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ AC00 × 0308 ÷ 1F3FB ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ AC00 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ AC00 × 0308 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ AC00 ÷ 2640 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ AC00 × 0308 ÷ 2640 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ AC00 ÷ 1F466 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] BOY (EBG) ÷ [0.3]
-÷ AC00 × 0308 ÷ 1F466 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] BOY (EBG) ÷ [0.3]
+÷ AC00 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ AC00 ÷ 231A ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ AC00 × 0308 ÷ 231A ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ AC00 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ AC00 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ AC00 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ AC00 × 0308 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ AC00 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ AC00 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ AC00 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
÷ AC00 ÷ D800 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ AC00 × 0308 ÷ D800 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
+÷ AC00 × 0308 ÷ D800 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ AC01 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] SPACE (Other) ÷ [0.3]
-÷ AC01 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ AC01 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ AC01 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ AC01 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ AC01 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
÷ AC01 ÷ 000A ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ AC01 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ AC01 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
÷ AC01 ÷ 0001 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ AC01 × 0308 ÷ 0001 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ AC01 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
-÷ AC01 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
+÷ AC01 × 0308 ÷ 0001 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ AC01 × 034F ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ AC01 × 0308 × 034F ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ AC01 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ AC01 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
÷ AC01 ÷ 0600 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ AC01 × 0308 ÷ 0600 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ AC01 × 0308 ÷ 0600 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
÷ AC01 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ AC01 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ AC01 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
÷ AC01 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ AC01 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ AC01 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
÷ AC01 ÷ 1160 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ AC01 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ AC01 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
÷ AC01 × 11A8 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [8.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ AC01 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ AC01 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
÷ AC01 ÷ AC00 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ AC01 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ AC01 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
÷ AC01 ÷ AC01 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ AC01 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ AC01 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ AC01 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ AC01 ÷ 261D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ AC01 × 0308 ÷ 261D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ AC01 ÷ 1F3FB ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ AC01 × 0308 ÷ 1F3FB ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ AC01 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ AC01 × 0308 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ AC01 ÷ 2640 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ AC01 × 0308 ÷ 2640 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ AC01 ÷ 1F466 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] BOY (EBG) ÷ [0.3]
-÷ AC01 × 0308 ÷ 1F466 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] BOY (EBG) ÷ [0.3]
+÷ AC01 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ AC01 ÷ 231A ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ AC01 × 0308 ÷ 231A ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ AC01 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ AC01 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ AC01 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ AC01 × 0308 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ AC01 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ AC01 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ AC01 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
÷ AC01 ÷ D800 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ AC01 × 0308 ÷ D800 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 1F1E6 ÷ 0020 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] SPACE (Other) ÷ [0.3]
-÷ 1F1E6 × 0308 ÷ 0020 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3]
-÷ 1F1E6 ÷ 000D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ 1F1E6 × 0308 ÷ 000D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ 1F1E6 ÷ 000A ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ 1F1E6 × 0308 ÷ 000A ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ 1F1E6 ÷ 0001 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 1F1E6 × 0308 ÷ 0001 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 1F1E6 × 0300 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
-÷ 1F1E6 × 0308 × 0300 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
-÷ 1F1E6 ÷ 0600 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ 1F1E6 × 0308 ÷ 0600 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ 1F1E6 × 0903 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ 1F1E6 × 0308 × 0903 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ 1F1E6 ÷ 1100 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ 1F1E6 × 0308 ÷ 1100 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ 1F1E6 ÷ 1160 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ 1F1E6 × 0308 ÷ 1160 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ 1F1E6 ÷ 11A8 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ 1F1E6 × 0308 ÷ 11A8 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ 1F1E6 ÷ AC00 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ 1F1E6 × 0308 ÷ AC00 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ 1F1E6 ÷ AC01 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 1F1E6 × 0308 ÷ AC01 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 1F1E6 × 1F1E6 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [12.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 1F1E6 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 1F1E6 ÷ 261D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 1F1E6 × 0308 ÷ 261D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 1F1E6 ÷ 1F3FB ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 1F1E6 × 0308 ÷ 1F3FB ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 1F1E6 × 200D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 1F1E6 × 0308 × 200D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 1F1E6 ÷ 2640 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 1F1E6 × 0308 ÷ 2640 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 1F1E6 ÷ 1F466 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] BOY (EBG) ÷ [0.3]
-÷ 1F1E6 × 0308 ÷ 1F466 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] BOY (EBG) ÷ [0.3]
-÷ 1F1E6 ÷ 0378 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 1F1E6 × 0308 ÷ 0378 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 1F1E6 ÷ D800 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 1F1E6 × 0308 ÷ D800 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 261D ÷ 0020 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) ÷ [999.0] SPACE (Other) ÷ [0.3]
-÷ 261D × 0308 ÷ 0020 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3]
-÷ 261D ÷ 000D ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ 261D × 0308 ÷ 000D ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ 261D ÷ 000A ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ 261D × 0308 ÷ 000A ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ 261D ÷ 0001 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 261D × 0308 ÷ 0001 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 261D × 0300 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
-÷ 261D × 0308 × 0300 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
-÷ 261D ÷ 0600 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ 261D × 0308 ÷ 0600 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ 261D × 0903 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ 261D × 0308 × 0903 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) × [9.0] COMBINING DIAERESIS (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ 261D ÷ 1100 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ 261D × 0308 ÷ 1100 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ 261D ÷ 1160 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ 261D × 0308 ÷ 1160 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ 261D ÷ 11A8 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ 261D × 0308 ÷ 11A8 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ 261D ÷ AC00 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ 261D × 0308 ÷ AC00 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ 261D ÷ AC01 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 261D × 0308 ÷ AC01 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 261D ÷ 1F1E6 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 261D × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 261D ÷ 261D ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 261D × 0308 ÷ 261D ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 261D × 1F3FB ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) × [10.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 261D × 0308 × 1F3FB ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) × [9.0] COMBINING DIAERESIS (Extend) × [10.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 261D × 200D ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 261D × 0308 × 200D ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 261D ÷ 2640 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 261D × 0308 ÷ 2640 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 261D ÷ 1F466 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) ÷ [999.0] BOY (EBG) ÷ [0.3]
-÷ 261D × 0308 ÷ 1F466 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] BOY (EBG) ÷ [0.3]
-÷ 261D ÷ 0378 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 261D × 0308 ÷ 0378 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 261D ÷ D800 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 261D × 0308 ÷ D800 ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 1F3FB ÷ 0020 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [999.0] SPACE (Other) ÷ [0.3]
-÷ 1F3FB × 0308 ÷ 0020 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3]
-÷ 1F3FB ÷ 000D ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ 1F3FB × 0308 ÷ 000D ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ 1F3FB ÷ 000A ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ 1F3FB × 0308 ÷ 000A ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ 1F3FB ÷ 0001 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 1F3FB × 0308 ÷ 0001 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 1F3FB × 0300 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
-÷ 1F3FB × 0308 × 0300 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
-÷ 1F3FB ÷ 0600 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ 1F3FB × 0308 ÷ 0600 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ 1F3FB × 0903 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ 1F3FB × 0308 × 0903 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) × [9.0] COMBINING DIAERESIS (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ 1F3FB ÷ 1100 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ 1F3FB × 0308 ÷ 1100 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ 1F3FB ÷ 1160 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ 1F3FB × 0308 ÷ 1160 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ 1F3FB ÷ 11A8 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ 1F3FB × 0308 ÷ 11A8 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ 1F3FB ÷ AC00 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ 1F3FB × 0308 ÷ AC00 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ 1F3FB ÷ AC01 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 1F3FB × 0308 ÷ AC01 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 1F3FB ÷ 1F1E6 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 1F3FB × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 1F3FB ÷ 261D ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 1F3FB × 0308 ÷ 261D ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 1F3FB ÷ 1F3FB ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 1F3FB × 0308 ÷ 1F3FB ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 1F3FB × 200D ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 1F3FB × 0308 × 200D ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 1F3FB ÷ 2640 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 1F3FB × 0308 ÷ 2640 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 1F3FB ÷ 1F466 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [999.0] BOY (EBG) ÷ [0.3]
-÷ 1F3FB × 0308 ÷ 1F466 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] BOY (EBG) ÷ [0.3]
-÷ 1F3FB ÷ 0378 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 1F3FB × 0308 ÷ 0378 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 1F3FB ÷ D800 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 1F3FB × 0308 ÷ D800 ÷ # ÷ [0.2] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 200D ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] SPACE (Other) ÷ [0.3]
-÷ 200D × 0308 ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3]
-÷ 200D ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ 200D × 0308 ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ 200D ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ 200D × 0308 ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ 200D ÷ 0001 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 200D × 0308 ÷ 0001 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 200D × 0300 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
-÷ 200D × 0308 × 0300 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
-÷ 200D ÷ 0600 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ 200D × 0308 ÷ 0600 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ 200D × 0903 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ 200D × 0308 × 0903 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ 200D ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ 200D × 0308 ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ 200D ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ 200D × 0308 ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ 200D ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ 200D × 0308 ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ 200D ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ 200D × 0308 ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ 200D ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 200D × 0308 ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 200D ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 200D × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 200D ÷ 261D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 200D × 0308 ÷ 261D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 200D ÷ 1F3FB ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 200D × 0308 ÷ 1F3FB ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 200D × 200D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 200D × 0308 × 200D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 200D × 2640 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [11.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 200D × 0308 ÷ 2640 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 200D × 1F466 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [11.0] BOY (EBG) ÷ [0.3]
-÷ 200D × 0308 ÷ 1F466 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] BOY (EBG) ÷ [0.3]
-÷ 200D ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 200D × 0308 ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 200D ÷ D800 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 200D × 0308 ÷ D800 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 2640 ÷ 0020 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
-÷ 2640 × 0308 ÷ 0020 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3]
-÷ 2640 ÷ 000D ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ 2640 × 0308 ÷ 000D ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ 2640 ÷ 000A ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ 2640 × 0308 ÷ 000A ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ 2640 ÷ 0001 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 2640 × 0308 ÷ 0001 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 2640 × 0300 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
-÷ 2640 × 0308 × 0300 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
-÷ 2640 ÷ 0600 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ 2640 × 0308 ÷ 0600 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ 2640 × 0903 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ 2640 × 0308 × 0903 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) × [9.0] COMBINING DIAERESIS (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ 2640 ÷ 1100 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ 2640 × 0308 ÷ 1100 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ 2640 ÷ 1160 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ 2640 × 0308 ÷ 1160 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ 2640 ÷ 11A8 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ 2640 × 0308 ÷ 11A8 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ 2640 ÷ AC00 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ 2640 × 0308 ÷ AC00 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ 2640 ÷ AC01 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 2640 × 0308 ÷ AC01 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 2640 ÷ 1F1E6 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 2640 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 2640 ÷ 261D ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 2640 × 0308 ÷ 261D ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 2640 ÷ 1F3FB ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 2640 × 0308 ÷ 1F3FB ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 2640 × 200D ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 2640 × 0308 × 200D ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 2640 ÷ 2640 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 2640 × 0308 ÷ 2640 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 2640 ÷ 1F466 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) ÷ [999.0] BOY (EBG) ÷ [0.3]
-÷ 2640 × 0308 ÷ 1F466 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] BOY (EBG) ÷ [0.3]
-÷ 2640 ÷ 0378 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 2640 × 0308 ÷ 0378 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 2640 ÷ D800 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 2640 × 0308 ÷ D800 ÷ # ÷ [0.2] FEMALE SIGN (Glue_After_Zwj) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 1F466 ÷ 0020 ÷ # ÷ [0.2] BOY (EBG) ÷ [999.0] SPACE (Other) ÷ [0.3]
-÷ 1F466 × 0308 ÷ 0020 ÷ # ÷ [0.2] BOY (EBG) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3]
-÷ 1F466 ÷ 000D ÷ # ÷ [0.2] BOY (EBG) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ 1F466 × 0308 ÷ 000D ÷ # ÷ [0.2] BOY (EBG) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ 1F466 ÷ 000A ÷ # ÷ [0.2] BOY (EBG) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ 1F466 × 0308 ÷ 000A ÷ # ÷ [0.2] BOY (EBG) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ 1F466 ÷ 0001 ÷ # ÷ [0.2] BOY (EBG) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 1F466 × 0308 ÷ 0001 ÷ # ÷ [0.2] BOY (EBG) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 1F466 × 0300 ÷ # ÷ [0.2] BOY (EBG) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
-÷ 1F466 × 0308 × 0300 ÷ # ÷ [0.2] BOY (EBG) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
-÷ 1F466 ÷ 0600 ÷ # ÷ [0.2] BOY (EBG) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ 1F466 × 0308 ÷ 0600 ÷ # ÷ [0.2] BOY (EBG) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ 1F466 × 0903 ÷ # ÷ [0.2] BOY (EBG) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ 1F466 × 0308 × 0903 ÷ # ÷ [0.2] BOY (EBG) × [9.0] COMBINING DIAERESIS (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ 1F466 ÷ 1100 ÷ # ÷ [0.2] BOY (EBG) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ 1F466 × 0308 ÷ 1100 ÷ # ÷ [0.2] BOY (EBG) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ 1F466 ÷ 1160 ÷ # ÷ [0.2] BOY (EBG) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ 1F466 × 0308 ÷ 1160 ÷ # ÷ [0.2] BOY (EBG) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ 1F466 ÷ 11A8 ÷ # ÷ [0.2] BOY (EBG) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ 1F466 × 0308 ÷ 11A8 ÷ # ÷ [0.2] BOY (EBG) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ 1F466 ÷ AC00 ÷ # ÷ [0.2] BOY (EBG) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ 1F466 × 0308 ÷ AC00 ÷ # ÷ [0.2] BOY (EBG) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ 1F466 ÷ AC01 ÷ # ÷ [0.2] BOY (EBG) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 1F466 × 0308 ÷ AC01 ÷ # ÷ [0.2] BOY (EBG) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 1F466 ÷ 1F1E6 ÷ # ÷ [0.2] BOY (EBG) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 1F466 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] BOY (EBG) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 1F466 ÷ 261D ÷ # ÷ [0.2] BOY (EBG) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 1F466 × 0308 ÷ 261D ÷ # ÷ [0.2] BOY (EBG) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 1F466 × 1F3FB ÷ # ÷ [0.2] BOY (EBG) × [10.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 1F466 × 0308 × 1F3FB ÷ # ÷ [0.2] BOY (EBG) × [9.0] COMBINING DIAERESIS (Extend) × [10.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 1F466 × 200D ÷ # ÷ [0.2] BOY (EBG) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 1F466 × 0308 × 200D ÷ # ÷ [0.2] BOY (EBG) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 1F466 ÷ 2640 ÷ # ÷ [0.2] BOY (EBG) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 1F466 × 0308 ÷ 2640 ÷ # ÷ [0.2] BOY (EBG) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 1F466 ÷ 1F466 ÷ # ÷ [0.2] BOY (EBG) ÷ [999.0] BOY (EBG) ÷ [0.3]
-÷ 1F466 × 0308 ÷ 1F466 ÷ # ÷ [0.2] BOY (EBG) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] BOY (EBG) ÷ [0.3]
-÷ 1F466 ÷ 0378 ÷ # ÷ [0.2] BOY (EBG) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 1F466 × 0308 ÷ 0378 ÷ # ÷ [0.2] BOY (EBG) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 1F466 ÷ D800 ÷ # ÷ [0.2] BOY (EBG) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 1F466 × 0308 ÷ D800 ÷ # ÷ [0.2] BOY (EBG) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
+÷ AC01 × 0308 ÷ D800 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
+÷ 231A ÷ 0020 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 231A × 0308 ÷ 0020 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 231A ÷ 000D ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 231A × 0308 ÷ 000D ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 231A ÷ 000A ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 231A × 0308 ÷ 000A ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 231A ÷ 0001 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 231A × 0308 ÷ 0001 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 231A × 034F ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 231A × 0308 × 034F ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 231A ÷ 1F1E6 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 231A × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 231A ÷ 0600 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 231A × 0308 ÷ 0600 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 231A × 0903 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 231A × 0308 × 0903 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 231A ÷ 1100 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 231A × 0308 ÷ 1100 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 231A ÷ 1160 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 231A × 0308 ÷ 1160 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 231A ÷ 11A8 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 231A × 0308 ÷ 11A8 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 231A ÷ AC00 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 231A × 0308 ÷ AC00 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 231A ÷ AC01 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 231A × 0308 ÷ AC01 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 231A ÷ 231A ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 231A × 0308 ÷ 231A ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 231A × 0300 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 231A × 0308 × 0300 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 231A × 200D ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 231A × 0308 × 200D ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 231A ÷ 0378 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 231A × 0308 ÷ 0378 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 231A ÷ D800 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
+÷ 231A × 0308 ÷ D800 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
+÷ 0300 ÷ 0020 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 0300 × 0308 ÷ 0020 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 0300 ÷ 000D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0300 × 0308 ÷ 000D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0300 ÷ 000A ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0300 × 0308 ÷ 000A ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0300 ÷ 0001 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0300 × 0308 ÷ 0001 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0300 × 034F ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 0300 × 0308 × 034F ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 0300 ÷ 1F1E6 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0300 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0300 ÷ 0600 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0300 × 0308 ÷ 0600 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0300 × 0903 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0300 × 0308 × 0903 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0300 ÷ 1100 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0300 × 0308 ÷ 1100 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0300 ÷ 1160 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0300 × 0308 ÷ 1160 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0300 ÷ 11A8 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0300 × 0308 ÷ 11A8 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0300 ÷ AC00 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0300 × 0308 ÷ AC00 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0300 ÷ AC01 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0300 × 0308 ÷ AC01 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0300 ÷ 231A ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0300 × 0308 ÷ 231A ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0300 × 0300 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0300 × 0308 × 0300 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0300 × 200D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0300 × 0308 × 200D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0300 ÷ 0378 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 0300 × 0308 ÷ 0378 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 0300 ÷ D800 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
+÷ 0300 × 0308 ÷ D800 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
+÷ 200D ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 200D × 0308 ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 200D ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 200D × 0308 ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 200D ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 200D × 0308 ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 200D ÷ 0001 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 200D × 0308 ÷ 0001 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 200D × 034F ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 200D × 0308 × 034F ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 200D ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 200D × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 200D ÷ 0600 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 200D × 0308 ÷ 0600 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 200D × 0903 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 200D × 0308 × 0903 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 200D ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 200D × 0308 ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 200D ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 200D × 0308 ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 200D ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 200D × 0308 ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 200D ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 200D × 0308 ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 200D ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 200D × 0308 ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 200D ÷ 231A ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 200D × 0308 ÷ 231A ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 200D × 0300 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 200D × 0308 × 0300 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 200D × 200D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 200D × 0308 × 200D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 200D ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 200D × 0308 ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 200D ÷ D800 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
+÷ 200D × 0308 ÷ D800 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ 0378 ÷ 0020 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] SPACE (Other) ÷ [0.3]
-÷ 0378 × 0308 ÷ 0020 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ 0378 × 0308 ÷ 0020 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 0378 ÷ 000D ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ 0378 × 0308 ÷ 000D ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ 0378 × 0308 ÷ 000D ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
÷ 0378 ÷ 000A ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ 0378 × 0308 ÷ 000A ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ 0378 × 0308 ÷ 000A ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
÷ 0378 ÷ 0001 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 0378 × 0308 ÷ 0001 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ 0378 × 0300 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
-÷ 0378 × 0308 × 0300 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
+÷ 0378 × 0308 ÷ 0001 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ 0378 × 034F ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 0378 × 0308 × 034F ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ 0378 ÷ 1F1E6 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ 0378 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
÷ 0378 ÷ 0600 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ 0378 × 0308 ÷ 0600 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ 0378 × 0308 ÷ 0600 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
÷ 0378 × 0903 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ 0378 × 0308 × 0903 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ 0378 × 0308 × 0903 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
÷ 0378 ÷ 1100 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ 0378 × 0308 ÷ 1100 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ 0378 × 0308 ÷ 1100 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
÷ 0378 ÷ 1160 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ 0378 × 0308 ÷ 1160 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ 0378 × 0308 ÷ 1160 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
÷ 0378 ÷ 11A8 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ 0378 × 0308 ÷ 11A8 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ 0378 × 0308 ÷ 11A8 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
÷ 0378 ÷ AC00 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ 0378 × 0308 ÷ AC00 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ 0378 × 0308 ÷ AC00 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
÷ 0378 ÷ AC01 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 0378 × 0308 ÷ AC01 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ 0378 ÷ 1F1E6 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 0378 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ 0378 ÷ 261D ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 0378 × 0308 ÷ 261D ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 0378 ÷ 1F3FB ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 0378 × 0308 ÷ 1F3FB ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 0378 × 200D ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 0378 × 0308 × 200D ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 0378 ÷ 2640 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 0378 × 0308 ÷ 2640 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 0378 ÷ 1F466 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] BOY (EBG) ÷ [0.3]
-÷ 0378 × 0308 ÷ 1F466 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] BOY (EBG) ÷ [0.3]
+÷ 0378 × 0308 ÷ AC01 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ 0378 ÷ 231A ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0378 × 0308 ÷ 231A ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ 0378 × 0300 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0378 × 0308 × 0300 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0378 × 200D ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0378 × 0308 × 200D ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ 0378 ÷ 0378 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
-÷ 0378 × 0308 ÷ 0378 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ 0378 × 0308 ÷ 0378 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
÷ 0378 ÷ D800 ÷ # ÷ [0.2] <reserved-0378> (Other) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 0378 × 0308 ÷ D800 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
+÷ 0378 × 0308 ÷ D800 ÷ # ÷ [0.2] <reserved-0378> (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
÷ D800 ÷ 0020 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] SPACE (Other) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ D800 ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ D800 ÷ 000D ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ 000D ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+÷ D800 ÷ 0308 ÷ 000D ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
÷ D800 ÷ 000A ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ 000A ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+÷ D800 ÷ 0308 ÷ 000A ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [0.3]
÷ D800 ÷ 0001 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ 0001 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
-÷ D800 ÷ 0300 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
-÷ D800 ÷ 0308 × 0300 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend) ÷ [0.3]
+÷ D800 ÷ 0308 ÷ 0001 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <START OF HEADING> (Control) ÷ [0.3]
+÷ D800 ÷ 034F ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ D800 ÷ 0308 × 034F ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+÷ D800 ÷ 1F1E6 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+÷ D800 ÷ 0308 ÷ 1F1E6 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
÷ D800 ÷ 0600 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ 0600 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+÷ D800 ÷ 0308 ÷ 0600 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
÷ D800 ÷ 0903 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
-÷ D800 ÷ 0308 × 0903 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
+÷ D800 ÷ 0308 × 0903 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3]
÷ D800 ÷ 1100 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ 1100 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+÷ D800 ÷ 0308 ÷ 1100 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
÷ D800 ÷ 1160 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ 1160 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+÷ D800 ÷ 0308 ÷ 1160 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
÷ D800 ÷ 11A8 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ 11A8 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+÷ D800 ÷ 0308 ÷ 11A8 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
÷ D800 ÷ AC00 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ AC00 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+÷ D800 ÷ 0308 ÷ AC00 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
÷ D800 ÷ AC01 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ AC01 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
-÷ D800 ÷ 1F1E6 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ 1F1E6 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-÷ D800 ÷ 261D ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ 261D ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ D800 ÷ 1F3FB ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ 1F3FB ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ D800 ÷ 200D ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ D800 ÷ 0308 × 200D ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ D800 ÷ 2640 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ 2640 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ D800 ÷ 1F466 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] BOY (EBG) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ 1F466 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] BOY (EBG) ÷ [0.3]
+÷ D800 ÷ 0308 ÷ AC01 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+÷ D800 ÷ 231A ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] WATCH (ExtPict) ÷ [0.3]
+÷ D800 ÷ 0308 ÷ 231A ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+÷ D800 ÷ 0300 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ D800 ÷ 0308 × 0300 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+÷ D800 ÷ 200D ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ D800 ÷ 0308 × 200D ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
÷ D800 ÷ 0378 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] <reserved-0378> (Other) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
+÷ D800 ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] <reserved-0378> (Other) ÷ [0.3]
÷ D800 ÷ D800 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ D800 ÷ 0308 ÷ D800 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
-÷ 000D × 000A ÷ 0061 ÷ 000A ÷ 0308 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) × [3.0] <LINE FEED (LF)> (LF) ÷ [4.0] LATIN SMALL LETTER A (Other) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend) ÷ [0.3]
-÷ 0061 × 0308 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [0.3]
-÷ 0020 × 200D ÷ 0646 ÷ # ÷ [0.2] SPACE (Other) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] ARABIC LETTER NOON (Other) ÷ [0.3]
-÷ 0646 × 200D ÷ 0020 ÷ # ÷ [0.2] ARABIC LETTER NOON (Other) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] SPACE (Other) ÷ [0.3]
+÷ D800 ÷ 0308 ÷ D800 ÷ # ÷ [0.2] <surrogate-D800> (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] <surrogate-D800> (Control) ÷ [0.3]
+÷ 000D × 000A ÷ 0061 ÷ 000A ÷ 0308 ÷ # ÷ [0.2] <CARRIAGE RETURN (CR)> (CR) × [3.0] <LINE FEED (LF)> (LF) ÷ [4.0] LATIN SMALL LETTER A (Other) ÷ [5.0] <LINE FEED (LF)> (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0061 × 0308 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [0.3]
+÷ 0020 × 200D ÷ 0646 ÷ # ÷ [0.2] SPACE (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] ARABIC LETTER NOON (Other) ÷ [0.3]
+÷ 0646 × 200D ÷ 0020 ÷ # ÷ [0.2] ARABIC LETTER NOON (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
÷ 1100 × 1100 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
÷ AC00 × 11A8 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [7.0] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
÷ AC01 × 11A8 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [8.0] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
÷ 1F1E6 × 1F1E7 ÷ 1F1E8 ÷ 0062 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [12.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3]
÷ 0061 ÷ 1F1E6 × 1F1E7 ÷ 1F1E8 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3]
-÷ 0061 ÷ 1F1E6 × 1F1E7 × 200D ÷ 1F1E8 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3]
-÷ 0061 ÷ 1F1E6 × 200D ÷ 1F1E7 × 1F1E8 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3]
+÷ 0061 ÷ 1F1E6 × 1F1E7 × 200D ÷ 1F1E8 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3]
+÷ 0061 ÷ 1F1E6 × 200D ÷ 1F1E7 × 1F1E8 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3]
÷ 0061 ÷ 1F1E6 × 1F1E7 ÷ 1F1E8 × 1F1E9 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER D (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3]
-÷ 0061 × 200D ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3]
-÷ 0061 × 0308 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] COMBINING DIAERESIS (Extend) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3]
+÷ 0061 × 200D ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+÷ 0061 × 0308 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3]
÷ 0061 × 0903 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3]
÷ 0061 ÷ 0600 × 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) × [9.2] LATIN SMALL LETTER B (Other) ÷ [0.3]
-÷ 261D × 1F3FB ÷ 261D ÷ # ÷ [0.2] WHITE UP POINTING INDEX (E_Base) × [10.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [999.0] WHITE UP POINTING INDEX (E_Base) ÷ [0.3]
-÷ 1F466 × 1F3FB ÷ # ÷ [0.2] BOY (EBG) × [10.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 200D × 1F466 × 1F3FB ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [11.0] BOY (EBG) × [10.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (E_Modifier) ÷ [0.3]
-÷ 200D × 2640 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [11.0] FEMALE SIGN (Glue_After_Zwj) ÷ [0.3]
-÷ 200D × 1F466 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [11.0] BOY (EBG) ÷ [0.3]
-÷ 1F466 ÷ 1F466 ÷ # ÷ [0.2] BOY (EBG) ÷ [999.0] BOY (EBG) ÷ [0.3]
+÷ 1F476 × 1F3FF ÷ 1F476 ÷ # ÷ [0.2] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend) ÷ [999.0] BABY (ExtPict) ÷ [0.3]
+÷ 0061 × 1F3FF ÷ 1F476 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend) ÷ [999.0] BABY (ExtPict) ÷ [0.3]
+÷ 0061 × 1F3FF ÷ 1F476 × 200D × 1F6D1 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend) ÷ [999.0] BABY (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3]
+÷ 1F476 × 1F3FF × 0308 × 200D × 1F476 × 1F3FF ÷ # ÷ [0.2] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend) ÷ [0.3]
+÷ 1F6D1 × 200D × 1F6D1 ÷ # ÷ [0.2] OCTAGONAL SIGN (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3]
+÷ 0061 × 200D ÷ 1F6D1 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3]
+÷ 2701 × 200D × 2701 ÷ # ÷ [0.2] UPPER BLADE SCISSORS (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] UPPER BLADE SCISSORS (Other) ÷ [0.3]
+÷ 0061 × 200D ÷ 2701 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] UPPER BLADE SCISSORS (Other) ÷ [0.3]
#
-# Lines: 822
+# Lines: 672
#
# EOF
diff --git a/lib/stdlib/test/unicode_util_SUITE_data/LineBreakTest.txt b/lib/stdlib/test/unicode_util_SUITE_data/LineBreakTest.txt
index 6715446aba..0e9e678a85 100644
--- a/lib/stdlib/test/unicode_util_SUITE_data/LineBreakTest.txt
+++ b/lib/stdlib/test/unicode_util_SUITE_data/LineBreakTest.txt
@@ -1,6 +1,6 @@
-# LineBreakTest-10.0.0.txt
-# Date: 2017-04-14, 05:40:30 GMT
-# © 2017 Unicode®, Inc.
+# LineBreakTest-11.0.0.txt
+# Date: 2018-05-20, 09:03:09 GMT
+# © 2018 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
#
@@ -6242,174 +6242,174 @@
× 0001 × 0020 ÷ 3041 ÷ # × [0.3] <START OF HEADING> (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] HIRAGANA LETTER SMALL A (CJ_NS) ÷ [0.3]
× 0001 × 0308 × 3041 ÷ # × [0.3] <START OF HEADING> (CM1_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [21.03] HIRAGANA LETTER SMALL A (CJ_NS) ÷ [0.3]
× 0001 × 0308 × 0020 ÷ 3041 ÷ # × [0.3] <START OF HEADING> (CM1_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] HIRAGANA LETTER SMALL A (CJ_NS) ÷ [0.3]
-× 200D × 0023 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [28.0] NUMBER SIGN (AL) ÷ [0.3]
+× 200D × 0023 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] NUMBER SIGN (AL) ÷ [0.3]
× 200D × 0020 ÷ 0023 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] NUMBER SIGN (AL) ÷ [0.3]
-× 200D × 0308 × 0023 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [28.0] NUMBER SIGN (AL) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ 0023 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] NUMBER SIGN (AL) ÷ [0.3]
-× 200D ÷ 2014 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) ÷ [999.0] EM DASH (B2) ÷ [0.3]
+× 200D × 0308 × 0023 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [28.0] NUMBER SIGN (AL) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ 0023 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] NUMBER SIGN (AL) ÷ [0.3]
+× 200D × 2014 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] EM DASH (B2) ÷ [0.3]
× 200D × 0020 ÷ 2014 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] EM DASH (B2) ÷ [0.3]
-× 200D × 0308 ÷ 2014 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) ÷ [999.0] EM DASH (B2) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ 2014 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] EM DASH (B2) ÷ [0.3]
-× 200D × 0009 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [21.01] <CHARACTER TABULATION> (BA) ÷ [0.3]
+× 200D × 0308 ÷ 2014 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) ÷ [999.0] EM DASH (B2) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ 2014 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] EM DASH (B2) ÷ [0.3]
+× 200D × 0009 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] <CHARACTER TABULATION> (BA) ÷ [0.3]
× 200D × 0020 ÷ 0009 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] <CHARACTER TABULATION> (BA) ÷ [0.3]
-× 200D × 0308 × 0009 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [21.01] <CHARACTER TABULATION> (BA) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ 0009 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] <CHARACTER TABULATION> (BA) ÷ [0.3]
-× 200D ÷ 00B4 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) ÷ [999.0] ACUTE ACCENT (BB) ÷ [0.3]
+× 200D × 0308 × 0009 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [21.01] <CHARACTER TABULATION> (BA) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ 0009 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] <CHARACTER TABULATION> (BA) ÷ [0.3]
+× 200D × 00B4 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] ACUTE ACCENT (BB) ÷ [0.3]
× 200D × 0020 ÷ 00B4 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] ACUTE ACCENT (BB) ÷ [0.3]
-× 200D × 0308 ÷ 00B4 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) ÷ [999.0] ACUTE ACCENT (BB) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ 00B4 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] ACUTE ACCENT (BB) ÷ [0.3]
+× 200D × 0308 ÷ 00B4 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) ÷ [999.0] ACUTE ACCENT (BB) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ 00B4 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] ACUTE ACCENT (BB) ÷ [0.3]
× 200D × 000B ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [6.0] <LINE TABULATION> (BK) ÷ [0.3]
× 200D × 0020 × 000B ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) × [6.0] <LINE TABULATION> (BK) ÷ [0.3]
-× 200D × 0308 × 000B ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [6.0] <LINE TABULATION> (BK) ÷ [0.3]
-× 200D × 0308 × 0020 × 000B ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) × [6.0] <LINE TABULATION> (BK) ÷ [0.3]
-× 200D ÷ FFFC ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) ÷ [20.01] OBJECT REPLACEMENT CHARACTER (CB) ÷ [0.3]
+× 200D × 0308 × 000B ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [6.0] <LINE TABULATION> (BK) ÷ [0.3]
+× 200D × 0308 × 0020 × 000B ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) × [6.0] <LINE TABULATION> (BK) ÷ [0.3]
+× 200D × FFFC ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] OBJECT REPLACEMENT CHARACTER (CB) ÷ [0.3]
× 200D × 0020 ÷ FFFC ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] OBJECT REPLACEMENT CHARACTER (CB) ÷ [0.3]
-× 200D × 0308 ÷ FFFC ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) ÷ [20.01] OBJECT REPLACEMENT CHARACTER (CB) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ FFFC ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] OBJECT REPLACEMENT CHARACTER (CB) ÷ [0.3]
-× 200D × 007D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [13.04] RIGHT CURLY BRACKET (CL) ÷ [0.3]
+× 200D × 0308 ÷ FFFC ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) ÷ [20.01] OBJECT REPLACEMENT CHARACTER (CB) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ FFFC ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] OBJECT REPLACEMENT CHARACTER (CB) ÷ [0.3]
+× 200D × 007D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] RIGHT CURLY BRACKET (CL) ÷ [0.3]
× 200D × 0020 × 007D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) × [13.02] RIGHT CURLY BRACKET (CL) ÷ [0.3]
-× 200D × 0308 × 007D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [13.04] RIGHT CURLY BRACKET (CL) ÷ [0.3]
-× 200D × 0308 × 0020 × 007D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) × [13.02] RIGHT CURLY BRACKET (CL) ÷ [0.3]
-× 200D × 0029 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [13.04] RIGHT PARENTHESIS (CP) ÷ [0.3]
+× 200D × 0308 × 007D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [13.04] RIGHT CURLY BRACKET (CL) ÷ [0.3]
+× 200D × 0308 × 0020 × 007D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) × [13.02] RIGHT CURLY BRACKET (CL) ÷ [0.3]
+× 200D × 0029 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] RIGHT PARENTHESIS (CP) ÷ [0.3]
× 200D × 0020 × 0029 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) × [13.02] RIGHT PARENTHESIS (CP) ÷ [0.3]
-× 200D × 0308 × 0029 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [13.04] RIGHT PARENTHESIS (CP) ÷ [0.3]
-× 200D × 0308 × 0020 × 0029 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) × [13.02] RIGHT PARENTHESIS (CP) ÷ [0.3]
+× 200D × 0308 × 0029 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [13.04] RIGHT PARENTHESIS (CP) ÷ [0.3]
+× 200D × 0308 × 0020 × 0029 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) × [13.02] RIGHT PARENTHESIS (CP) ÷ [0.3]
× 200D × 000D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [6.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
× 200D × 0020 × 000D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) × [6.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-× 200D × 0308 × 000D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [6.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-× 200D × 0308 × 0020 × 000D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) × [6.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
-× 200D × 0021 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [13.01] EXCLAMATION MARK (EX) ÷ [0.3]
+× 200D × 0308 × 000D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [6.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+× 200D × 0308 × 0020 × 000D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) × [6.0] <CARRIAGE RETURN (CR)> (CR) ÷ [0.3]
+× 200D × 0021 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] EXCLAMATION MARK (EX) ÷ [0.3]
× 200D × 0020 × 0021 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) × [13.01] EXCLAMATION MARK (EX) ÷ [0.3]
-× 200D × 0308 × 0021 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [13.01] EXCLAMATION MARK (EX) ÷ [0.3]
-× 200D × 0308 × 0020 × 0021 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) × [13.01] EXCLAMATION MARK (EX) ÷ [0.3]
-× 200D × 00A0 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [12.3] NO-BREAK SPACE (GL) ÷ [0.3]
+× 200D × 0308 × 0021 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [13.01] EXCLAMATION MARK (EX) ÷ [0.3]
+× 200D × 0308 × 0020 × 0021 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) × [13.01] EXCLAMATION MARK (EX) ÷ [0.3]
+× 200D × 00A0 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] NO-BREAK SPACE (GL) ÷ [0.3]
× 200D × 0020 ÷ 00A0 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] NO-BREAK SPACE (GL) ÷ [0.3]
-× 200D × 0308 × 00A0 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [12.3] NO-BREAK SPACE (GL) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ 00A0 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] NO-BREAK SPACE (GL) ÷ [0.3]
-× 200D ÷ AC00 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) ÷ [999.0] HANGUL SYLLABLE GA (H2) ÷ [0.3]
+× 200D × 0308 × 00A0 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [12.3] NO-BREAK SPACE (GL) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ 00A0 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] NO-BREAK SPACE (GL) ÷ [0.3]
+× 200D × AC00 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] HANGUL SYLLABLE GA (H2) ÷ [0.3]
× 200D × 0020 ÷ AC00 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] HANGUL SYLLABLE GA (H2) ÷ [0.3]
-× 200D × 0308 ÷ AC00 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) ÷ [999.0] HANGUL SYLLABLE GA (H2) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ AC00 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] HANGUL SYLLABLE GA (H2) ÷ [0.3]
-× 200D ÷ AC01 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) ÷ [999.0] HANGUL SYLLABLE GAG (H3) ÷ [0.3]
+× 200D × 0308 ÷ AC00 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) ÷ [999.0] HANGUL SYLLABLE GA (H2) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ AC00 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] HANGUL SYLLABLE GA (H2) ÷ [0.3]
+× 200D × AC01 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] HANGUL SYLLABLE GAG (H3) ÷ [0.3]
× 200D × 0020 ÷ AC01 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] HANGUL SYLLABLE GAG (H3) ÷ [0.3]
-× 200D × 0308 ÷ AC01 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) ÷ [999.0] HANGUL SYLLABLE GAG (H3) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ AC01 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] HANGUL SYLLABLE GAG (H3) ÷ [0.3]
-× 200D × 05D0 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [28.0] HEBREW LETTER ALEF (HL) ÷ [0.3]
+× 200D × 0308 ÷ AC01 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) ÷ [999.0] HANGUL SYLLABLE GAG (H3) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ AC01 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] HANGUL SYLLABLE GAG (H3) ÷ [0.3]
+× 200D × 05D0 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] HEBREW LETTER ALEF (HL) ÷ [0.3]
× 200D × 0020 ÷ 05D0 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] HEBREW LETTER ALEF (HL) ÷ [0.3]
-× 200D × 0308 × 05D0 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [28.0] HEBREW LETTER ALEF (HL) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ 05D0 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] HEBREW LETTER ALEF (HL) ÷ [0.3]
-× 200D × 002D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [21.02] HYPHEN-MINUS (HY) ÷ [0.3]
+× 200D × 0308 × 05D0 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [28.0] HEBREW LETTER ALEF (HL) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ 05D0 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] HEBREW LETTER ALEF (HL) ÷ [0.3]
+× 200D × 002D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] HYPHEN-MINUS (HY) ÷ [0.3]
× 200D × 0020 ÷ 002D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] HYPHEN-MINUS (HY) ÷ [0.3]
-× 200D × 0308 × 002D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [21.02] HYPHEN-MINUS (HY) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ 002D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] HYPHEN-MINUS (HY) ÷ [0.3]
+× 200D × 0308 × 002D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [21.02] HYPHEN-MINUS (HY) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ 002D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] HYPHEN-MINUS (HY) ÷ [0.3]
× 200D × 231A ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] WATCH (ID) ÷ [0.3]
× 200D × 0020 ÷ 231A ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] WATCH (ID) ÷ [0.3]
-× 200D × 0308 ÷ 231A ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) ÷ [999.0] WATCH (ID) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ 231A ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] WATCH (ID) ÷ [0.3]
-× 200D × 2024 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [22.01] ONE DOT LEADER (IN) ÷ [0.3]
+× 200D × 0308 ÷ 231A ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) ÷ [999.0] WATCH (ID) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ 231A ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] WATCH (ID) ÷ [0.3]
+× 200D × 2024 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] ONE DOT LEADER (IN) ÷ [0.3]
× 200D × 0020 ÷ 2024 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] ONE DOT LEADER (IN) ÷ [0.3]
-× 200D × 0308 × 2024 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [22.01] ONE DOT LEADER (IN) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ 2024 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] ONE DOT LEADER (IN) ÷ [0.3]
-× 200D × 002C ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [13.04] COMMA (IS) ÷ [0.3]
+× 200D × 0308 × 2024 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [22.01] ONE DOT LEADER (IN) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ 2024 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] ONE DOT LEADER (IN) ÷ [0.3]
+× 200D × 002C ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMMA (IS) ÷ [0.3]
× 200D × 0020 × 002C ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) × [13.02] COMMA (IS) ÷ [0.3]
-× 200D × 0308 × 002C ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [13.04] COMMA (IS) ÷ [0.3]
-× 200D × 0308 × 0020 × 002C ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) × [13.02] COMMA (IS) ÷ [0.3]
-× 200D ÷ 1100 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) ÷ [999.0] HANGUL CHOSEONG KIYEOK (JL) ÷ [0.3]
+× 200D × 0308 × 002C ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [13.04] COMMA (IS) ÷ [0.3]
+× 200D × 0308 × 0020 × 002C ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) × [13.02] COMMA (IS) ÷ [0.3]
+× 200D × 1100 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] HANGUL CHOSEONG KIYEOK (JL) ÷ [0.3]
× 200D × 0020 ÷ 1100 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] HANGUL CHOSEONG KIYEOK (JL) ÷ [0.3]
-× 200D × 0308 ÷ 1100 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) ÷ [999.0] HANGUL CHOSEONG KIYEOK (JL) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ 1100 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] HANGUL CHOSEONG KIYEOK (JL) ÷ [0.3]
-× 200D ÷ 11A8 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) ÷ [999.0] HANGUL JONGSEONG KIYEOK (JT) ÷ [0.3]
+× 200D × 0308 ÷ 1100 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) ÷ [999.0] HANGUL CHOSEONG KIYEOK (JL) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ 1100 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] HANGUL CHOSEONG KIYEOK (JL) ÷ [0.3]
+× 200D × 11A8 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] HANGUL JONGSEONG KIYEOK (JT) ÷ [0.3]
× 200D × 0020 ÷ 11A8 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] HANGUL JONGSEONG KIYEOK (JT) ÷ [0.3]
-× 200D × 0308 ÷ 11A8 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) ÷ [999.0] HANGUL JONGSEONG KIYEOK (JT) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ 11A8 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] HANGUL JONGSEONG KIYEOK (JT) ÷ [0.3]
-× 200D ÷ 1160 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) ÷ [999.0] HANGUL JUNGSEONG FILLER (JV) ÷ [0.3]
+× 200D × 0308 ÷ 11A8 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) ÷ [999.0] HANGUL JONGSEONG KIYEOK (JT) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ 11A8 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] HANGUL JONGSEONG KIYEOK (JT) ÷ [0.3]
+× 200D × 1160 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] HANGUL JUNGSEONG FILLER (JV) ÷ [0.3]
× 200D × 0020 ÷ 1160 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] HANGUL JUNGSEONG FILLER (JV) ÷ [0.3]
-× 200D × 0308 ÷ 1160 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) ÷ [999.0] HANGUL JUNGSEONG FILLER (JV) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ 1160 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] HANGUL JUNGSEONG FILLER (JV) ÷ [0.3]
+× 200D × 0308 ÷ 1160 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) ÷ [999.0] HANGUL JUNGSEONG FILLER (JV) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ 1160 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] HANGUL JUNGSEONG FILLER (JV) ÷ [0.3]
× 200D × 000A ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [6.0] <LINE FEED (LF)> (LF) ÷ [0.3]
× 200D × 0020 × 000A ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) × [6.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-× 200D × 0308 × 000A ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [6.0] <LINE FEED (LF)> (LF) ÷ [0.3]
-× 200D × 0308 × 0020 × 000A ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) × [6.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+× 200D × 0308 × 000A ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [6.0] <LINE FEED (LF)> (LF) ÷ [0.3]
+× 200D × 0308 × 0020 × 000A ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) × [6.0] <LINE FEED (LF)> (LF) ÷ [0.3]
× 200D × 0085 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [6.0] <NEXT LINE (NEL)> (NL) ÷ [0.3]
× 200D × 0020 × 0085 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) × [6.0] <NEXT LINE (NEL)> (NL) ÷ [0.3]
-× 200D × 0308 × 0085 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [6.0] <NEXT LINE (NEL)> (NL) ÷ [0.3]
-× 200D × 0308 × 0020 × 0085 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) × [6.0] <NEXT LINE (NEL)> (NL) ÷ [0.3]
-× 200D × 17D6 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [21.03] KHMER SIGN CAMNUC PII KUUH (NS) ÷ [0.3]
+× 200D × 0308 × 0085 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [6.0] <NEXT LINE (NEL)> (NL) ÷ [0.3]
+× 200D × 0308 × 0020 × 0085 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) × [6.0] <NEXT LINE (NEL)> (NL) ÷ [0.3]
+× 200D × 17D6 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] KHMER SIGN CAMNUC PII KUUH (NS) ÷ [0.3]
× 200D × 0020 ÷ 17D6 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] KHMER SIGN CAMNUC PII KUUH (NS) ÷ [0.3]
-× 200D × 0308 × 17D6 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [21.03] KHMER SIGN CAMNUC PII KUUH (NS) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ 17D6 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] KHMER SIGN CAMNUC PII KUUH (NS) ÷ [0.3]
-× 200D × 0030 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [23.02] DIGIT ZERO (NU) ÷ [0.3]
+× 200D × 0308 × 17D6 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [21.03] KHMER SIGN CAMNUC PII KUUH (NS) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ 17D6 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] KHMER SIGN CAMNUC PII KUUH (NS) ÷ [0.3]
+× 200D × 0030 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] DIGIT ZERO (NU) ÷ [0.3]
× 200D × 0020 ÷ 0030 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] DIGIT ZERO (NU) ÷ [0.3]
-× 200D × 0308 × 0030 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [23.02] DIGIT ZERO (NU) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ 0030 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] DIGIT ZERO (NU) ÷ [0.3]
-× 200D × 0028 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [30.01] LEFT PARENTHESIS (OP) ÷ [0.3]
+× 200D × 0308 × 0030 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [23.02] DIGIT ZERO (NU) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ 0030 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] DIGIT ZERO (NU) ÷ [0.3]
+× 200D × 0028 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] LEFT PARENTHESIS (OP) ÷ [0.3]
× 200D × 0020 ÷ 0028 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] LEFT PARENTHESIS (OP) ÷ [0.3]
-× 200D × 0308 × 0028 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [30.01] LEFT PARENTHESIS (OP) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ 0028 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] LEFT PARENTHESIS (OP) ÷ [0.3]
-× 200D × 0025 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [24.03] PERCENT SIGN (PO) ÷ [0.3]
+× 200D × 0308 × 0028 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [30.01] LEFT PARENTHESIS (OP) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ 0028 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] LEFT PARENTHESIS (OP) ÷ [0.3]
+× 200D × 0025 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] PERCENT SIGN (PO) ÷ [0.3]
× 200D × 0020 ÷ 0025 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] PERCENT SIGN (PO) ÷ [0.3]
-× 200D × 0308 × 0025 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [24.03] PERCENT SIGN (PO) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ 0025 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] PERCENT SIGN (PO) ÷ [0.3]
-× 200D × 0024 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [24.03] DOLLAR SIGN (PR) ÷ [0.3]
+× 200D × 0308 × 0025 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [24.03] PERCENT SIGN (PO) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ 0025 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] PERCENT SIGN (PO) ÷ [0.3]
+× 200D × 0024 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] DOLLAR SIGN (PR) ÷ [0.3]
× 200D × 0020 ÷ 0024 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] DOLLAR SIGN (PR) ÷ [0.3]
-× 200D × 0308 × 0024 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [24.03] DOLLAR SIGN (PR) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ 0024 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] DOLLAR SIGN (PR) ÷ [0.3]
-× 200D × 0022 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [19.01] QUOTATION MARK (QU) ÷ [0.3]
+× 200D × 0308 × 0024 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [24.03] DOLLAR SIGN (PR) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ 0024 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] DOLLAR SIGN (PR) ÷ [0.3]
+× 200D × 0022 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] QUOTATION MARK (QU) ÷ [0.3]
× 200D × 0020 ÷ 0022 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] QUOTATION MARK (QU) ÷ [0.3]
-× 200D × 0308 × 0022 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [19.01] QUOTATION MARK (QU) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ 0022 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] QUOTATION MARK (QU) ÷ [0.3]
+× 200D × 0308 × 0022 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [19.01] QUOTATION MARK (QU) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ 0022 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] QUOTATION MARK (QU) ÷ [0.3]
× 200D × 0020 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [0.3]
× 200D × 0020 × 0020 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) × [7.01] SPACE (SP) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [0.3]
-× 200D × 0308 × 0020 × 0020 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) × [7.01] SPACE (SP) ÷ [0.3]
-× 200D × 002F ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [13.04] SOLIDUS (SY) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [0.3]
+× 200D × 0308 × 0020 × 0020 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) × [7.01] SPACE (SP) ÷ [0.3]
+× 200D × 002F ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] SOLIDUS (SY) ÷ [0.3]
× 200D × 0020 × 002F ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) × [13.02] SOLIDUS (SY) ÷ [0.3]
-× 200D × 0308 × 002F ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [13.04] SOLIDUS (SY) ÷ [0.3]
-× 200D × 0308 × 0020 × 002F ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) × [13.02] SOLIDUS (SY) ÷ [0.3]
-× 200D × 2060 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [11.01] WORD JOINER (WJ) ÷ [0.3]
+× 200D × 0308 × 002F ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [13.04] SOLIDUS (SY) ÷ [0.3]
+× 200D × 0308 × 0020 × 002F ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) × [13.02] SOLIDUS (SY) ÷ [0.3]
+× 200D × 2060 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] WORD JOINER (WJ) ÷ [0.3]
× 200D × 0020 × 2060 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) × [11.01] WORD JOINER (WJ) ÷ [0.3]
-× 200D × 0308 × 2060 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [11.01] WORD JOINER (WJ) ÷ [0.3]
-× 200D × 0308 × 0020 × 2060 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) × [11.01] WORD JOINER (WJ) ÷ [0.3]
+× 200D × 0308 × 2060 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [11.01] WORD JOINER (WJ) ÷ [0.3]
+× 200D × 0308 × 0020 × 2060 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) × [11.01] WORD JOINER (WJ) ÷ [0.3]
× 200D × 200B ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.02] ZERO WIDTH SPACE (ZW) ÷ [0.3]
× 200D × 0020 × 200B ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) × [7.02] ZERO WIDTH SPACE (ZW) ÷ [0.3]
-× 200D × 0308 × 200B ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.02] ZERO WIDTH SPACE (ZW) ÷ [0.3]
-× 200D × 0308 × 0020 × 200B ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) × [7.02] ZERO WIDTH SPACE (ZW) ÷ [0.3]
-× 200D ÷ 1F1E6 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+× 200D × 0308 × 200B ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.02] ZERO WIDTH SPACE (ZW) ÷ [0.3]
+× 200D × 0308 × 0020 × 200B ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) × [7.02] ZERO WIDTH SPACE (ZW) ÷ [0.3]
+× 200D × 1F1E6 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
× 200D × 0020 ÷ 1F1E6 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-× 200D × 0308 ÷ 1F1E6 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ 1F1E6 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+× 200D × 0308 ÷ 1F1E6 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ 1F1E6 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
× 200D × 261D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] WHITE UP POINTING INDEX (EB) ÷ [0.3]
× 200D × 0020 ÷ 261D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] WHITE UP POINTING INDEX (EB) ÷ [0.3]
-× 200D × 0308 ÷ 261D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) ÷ [999.0] WHITE UP POINTING INDEX (EB) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ 261D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] WHITE UP POINTING INDEX (EB) ÷ [0.3]
+× 200D × 0308 ÷ 261D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) ÷ [999.0] WHITE UP POINTING INDEX (EB) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ 261D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] WHITE UP POINTING INDEX (EB) ÷ [0.3]
× 200D × 1F3FB ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (EM) ÷ [0.3]
× 200D × 0020 ÷ 1F3FB ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (EM) ÷ [0.3]
-× 200D × 0308 ÷ 1F3FB ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (EM) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ 1F3FB ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (EM) ÷ [0.3]
-× 200D × 0001 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] <START OF HEADING> (CM1_CM) ÷ [0.3]
+× 200D × 0308 ÷ 1F3FB ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) ÷ [999.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (EM) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ 1F3FB ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] EMOJI MODIFIER FITZPATRICK TYPE-1-2 (EM) ÷ [0.3]
+× 200D × 0001 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] <START OF HEADING> (CM1_CM) ÷ [0.3]
× 200D × 0020 ÷ 0001 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] <START OF HEADING> (CM1_CM) ÷ [0.3]
-× 200D × 0308 × 0001 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [9.0] <START OF HEADING> (CM1_CM) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ 0001 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] <START OF HEADING> (CM1_CM) ÷ [0.3]
-× 200D × 200D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) ÷ [0.3]
+× 200D × 0308 × 0001 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [9.0] <START OF HEADING> (CM1_CM) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ 0001 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] <START OF HEADING> (CM1_CM) ÷ [0.3]
+× 200D × 200D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) ÷ [0.3]
× 200D × 0020 ÷ 200D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) ÷ [0.3]
-× 200D × 0308 × 200D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [9.0] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ 200D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) ÷ [0.3]
-× 200D × 00A7 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [28.0] SECTION SIGN (AI_AL) ÷ [0.3]
+× 200D × 0308 × 200D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [9.0] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ 200D ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) ÷ [0.3]
+× 200D × 00A7 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] SECTION SIGN (AI_AL) ÷ [0.3]
× 200D × 0020 ÷ 00A7 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] SECTION SIGN (AI_AL) ÷ [0.3]
-× 200D × 0308 × 00A7 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [28.0] SECTION SIGN (AI_AL) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ 00A7 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] SECTION SIGN (AI_AL) ÷ [0.3]
-× 200D × 50005 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [28.0] <reserved-50005> (XX_AL) ÷ [0.3]
+× 200D × 0308 × 00A7 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [28.0] SECTION SIGN (AI_AL) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ 00A7 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] SECTION SIGN (AI_AL) ÷ [0.3]
+× 200D × 50005 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] <reserved-50005> (XX_AL) ÷ [0.3]
× 200D × 0020 ÷ 50005 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] <reserved-50005> (XX_AL) ÷ [0.3]
-× 200D × 0308 × 50005 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [28.0] <reserved-50005> (XX_AL) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ 50005 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] <reserved-50005> (XX_AL) ÷ [0.3]
-× 200D × 0E01 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [28.0] THAI CHARACTER KO KAI (SA_AL) ÷ [0.3]
+× 200D × 0308 × 50005 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [28.0] <reserved-50005> (XX_AL) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ 50005 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] <reserved-50005> (XX_AL) ÷ [0.3]
+× 200D × 0E01 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] THAI CHARACTER KO KAI (SA_AL) ÷ [0.3]
× 200D × 0020 ÷ 0E01 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] THAI CHARACTER KO KAI (SA_AL) ÷ [0.3]
-× 200D × 0308 × 0E01 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [28.0] THAI CHARACTER KO KAI (SA_AL) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ 0E01 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] THAI CHARACTER KO KAI (SA_AL) ÷ [0.3]
-× 200D × 3041 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [21.03] HIRAGANA LETTER SMALL A (CJ_NS) ÷ [0.3]
+× 200D × 0308 × 0E01 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [28.0] THAI CHARACTER KO KAI (SA_AL) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ 0E01 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] THAI CHARACTER KO KAI (SA_AL) ÷ [0.3]
+× 200D × 3041 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] HIRAGANA LETTER SMALL A (CJ_NS) ÷ [0.3]
× 200D × 0020 ÷ 3041 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [18.0] HIRAGANA LETTER SMALL A (CJ_NS) ÷ [0.3]
-× 200D × 0308 × 3041 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [21.03] HIRAGANA LETTER SMALL A (CJ_NS) ÷ [0.3]
-× 200D × 0308 × 0020 ÷ 3041 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] HIRAGANA LETTER SMALL A (CJ_NS) ÷ [0.3]
+× 200D × 0308 × 3041 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [21.03] HIRAGANA LETTER SMALL A (CJ_NS) ÷ [0.3]
+× 200D × 0308 × 0020 ÷ 3041 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] HIRAGANA LETTER SMALL A (CJ_NS) ÷ [0.3]
× 00A7 × 0023 ÷ # × [0.3] SECTION SIGN (AI_AL) × [28.0] NUMBER SIGN (AL) ÷ [0.3]
× 00A7 × 0020 ÷ 0023 ÷ # × [0.3] SECTION SIGN (AI_AL) × [7.01] SPACE (SP) ÷ [18.0] NUMBER SIGN (AL) ÷ [0.3]
× 00A7 × 0308 × 0023 ÷ # × [0.3] SECTION SIGN (AI_AL) × [9.0] COMBINING DIAERESIS (CM1_CM) × [28.0] NUMBER SIGN (AL) ÷ [0.3]
@@ -7084,7 +7084,7 @@
× 3041 × 0308 × 0020 ÷ 3041 ÷ # × [0.3] HIRAGANA LETTER SMALL A (CJ_NS) × [9.0] COMBINING DIAERESIS (CM1_CM) × [7.01] SPACE (SP) ÷ [18.0] HIRAGANA LETTER SMALL A (CJ_NS) ÷ [0.3]
× 000D × 000A ÷ 0061 × 000A ÷ 0308 ÷ # × [0.3] <CARRIAGE RETURN (CR)> (CR) × [5.01] <LINE FEED (LF)> (LF) ÷ [5.03] LATIN SMALL LETTER A (AL) × [6.0] <LINE FEED (LF)> (LF) ÷ [5.03] COMBINING DIAERESIS (CM1_CM) ÷ [0.3]
× 0061 × 0308 ÷ # × [0.3] LATIN SMALL LETTER A (AL) × [9.0] COMBINING DIAERESIS (CM1_CM) ÷ [0.3]
-× 0020 ÷ 200D × 0646 ÷ # × [0.3] SPACE (SP) ÷ [18.0] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [28.0] ARABIC LETTER NOON (AL) ÷ [0.3]
+× 0020 ÷ 200D × 0646 ÷ # × [0.3] SPACE (SP) ÷ [18.0] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] ARABIC LETTER NOON (AL) ÷ [0.3]
× 0646 × 200D × 0020 ÷ # × [0.3] ARABIC LETTER NOON (AL) × [9.0] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [7.01] SPACE (SP) ÷ [0.3]
× 000B ÷ 3041 ÷ # × [0.3] <LINE TABULATION> (BK) ÷ [4.0] HIRAGANA LETTER SMALL A (CJ_NS) ÷ [0.3]
× 000D ÷ 3041 ÷ # × [0.3] <CARRIAGE RETURN (CR)> (CR) ÷ [5.02] HIRAGANA LETTER SMALL A (CJ_NS) ÷ [0.3]
@@ -7093,8 +7093,8 @@
× 3041 × 2060 ÷ # × [0.3] HIRAGANA LETTER SMALL A (CJ_NS) × [11.01] WORD JOINER (WJ) ÷ [0.3]
× 2060 × 3041 ÷ # × [0.3] WORD JOINER (WJ) × [11.02] HIRAGANA LETTER SMALL A (CJ_NS) ÷ [0.3]
× 3041 × 0308 × 00A0 ÷ # × [0.3] HIRAGANA LETTER SMALL A (CJ_NS) × [9.0] COMBINING DIAERESIS (CM1_CM) × [12.2] NO-BREAK SPACE (GL) ÷ [0.3]
-× 200D × 00A0 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [12.3] NO-BREAK SPACE (GL) ÷ [0.3]
-× 200D × 002F ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [13.04] SOLIDUS (SY) ÷ [0.3]
+× 200D × 00A0 ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] NO-BREAK SPACE (GL) ÷ [0.3]
+× 200D × 002F ÷ # × [0.3] ZERO WIDTH JOINER (ZWJ_O_ZWJ_CM) × [8.1] SOLIDUS (SY) ÷ [0.3]
× 2014 × 2014 ÷ # × [0.3] EM DASH (B2) × [17.0] EM DASH (B2) ÷ [0.3]
× 3041 ÷ FFFC ÷ # × [0.3] HIRAGANA LETTER SMALL A (CJ_NS) ÷ [20.01] OBJECT REPLACEMENT CHARACTER (CB) ÷ [0.3]
× FFFC ÷ 3041 ÷ # × [0.3] OBJECT REPLACEMENT CHARACTER (CB) ÷ [20.02] HIRAGANA LETTER SMALL A (CJ_NS) ÷ [0.3]
diff --git a/lib/stdlib/test/unicode_util_SUITE_data/NormalizationTest.txt b/lib/stdlib/test/unicode_util_SUITE_data/NormalizationTest.txt
index 71f2371c5e..72a31bcdf1 100644
--- a/lib/stdlib/test/unicode_util_SUITE_data/NormalizationTest.txt
+++ b/lib/stdlib/test/unicode_util_SUITE_data/NormalizationTest.txt
@@ -1,6 +1,6 @@
-# NormalizationTest-10.0.0.txt
-# Date: 2017-03-08, 08:41:55 GMT
-# © 2017 Unicode®, Inc.
+# NormalizationTest-11.0.0.txt
+# Date: 2018-02-19, 18:33:08 GMT
+# © 2018 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
#
@@ -17479,6 +17479,8 @@ FFEE;FFEE;FFEE;25CB;25CB; # (○; ○; ○; ○; ○; ) HALFWIDTH WHITE CIRCLE
0061 07F2 059A 0316 302A 0062;0061 302A 07F2 0316 059A 0062;0061 302A 07F2 0316 059A 0062;0061 302A 07F2 0316 059A 0062;0061 302A 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, IDEOGRAPHIC LEVEL TONE MARK, 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 302A 07FD 0062;0061 302A 0316 07FD 059A 0062;0061 302A 0316 07FD 059A 0062;0061 302A 0316 07FD 059A 0062;0061 302A 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, IDEOGRAPHIC LEVEL TONE MARK, NKO DANTAYALAN, LATIN SMALL LETTER B
+0061 07FD 059A 0316 302A 0062;0061 302A 07FD 0316 059A 0062;0061 302A 07FD 0316 059A 0062;0061 302A 07FD 0316 059A 0062;0061 302A 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, IDEOGRAPHIC LEVEL TONE MARK, 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
@@ -17527,6 +17529,8 @@ FFEE;FFEE;FFEE;25CB;25CB; # (○; ○; ○; ○; ○; ) HALFWIDTH WHITE CIRCLE
0061 085A 059A 0316 302A 0062;0061 302A 085A 0316 059A 0062;0061 302A 085A 0316 059A 0062;0061 302A 085A 0316 059A 0062;0061 302A 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, IDEOGRAPHIC LEVEL TONE MARK, LATIN SMALL LETTER B
0061 059A 0316 302A 085B 0062;0061 302A 0316 085B 059A 0062;0061 302A 0316 085B 059A 0062;0061 302A 0316 085B 059A 0062;0061 302A 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, IDEOGRAPHIC LEVEL TONE MARK, MANDAIC GEMINATION MARK, LATIN SMALL LETTER B
0061 085B 059A 0316 302A 0062;0061 302A 085B 0316 059A 0062;0061 302A 085B 0316 059A 0062;0061 302A 085B 0316 059A 0062;0061 302A 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, IDEOGRAPHIC LEVEL TONE MARK, LATIN SMALL LETTER B
+0061 059A 0316 302A 08D3 0062;0061 302A 0316 08D3 059A 0062;0061 302A 0316 08D3 059A 0062;0061 302A 0316 08D3 059A 0062;0061 302A 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, IDEOGRAPHIC LEVEL TONE MARK, ARABIC SMALL LOW WAW, LATIN SMALL LETTER B
+0061 08D3 059A 0316 302A 0062;0061 302A 08D3 0316 059A 0062;0061 302A 08D3 0316 059A 0062;0061 302A 08D3 0316 059A 0062;0061 302A 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, IDEOGRAPHIC LEVEL TONE MARK, 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
@@ -17629,6 +17633,8 @@ FFEE;FFEE;FFEE;25CB;25CB; # (○; ○; ○; ○; ○; ) HALFWIDTH WHITE CIRCLE
0061 09BC 3099 093C 0334 0062;0061 0334 09BC 093C 3099 0062;0061 0334 09BC 093C 3099 0062;0061 0334 09BC 093C 3099 0062;0061 0334 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, COMBINING TILDE OVERLAY, 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 0334 0A3C 0062;0061 0334 093C 0A3C 3099 0062;0061 0334 093C 0A3C 3099 0062;0061 0334 093C 0A3C 3099 0062;0061 0334 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, COMBINING TILDE OVERLAY, GURMUKHI SIGN NUKTA, LATIN SMALL LETTER B
0061 0A3C 3099 093C 0334 0062;0061 0334 0A3C 093C 3099 0062;0061 0334 0A3C 093C 3099 0062;0061 0334 0A3C 093C 3099 0062;0061 0334 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, COMBINING TILDE OVERLAY, 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
@@ -18329,6 +18335,36 @@ FFEE;FFEE;FFEE;25CB;25CB; # (○; ○; ○; ○; ○; ) HALFWIDTH WHITE CIRCLE
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 302A 10AE6 0062;0061 302A 0316 10AE6 059A 0062;0061 302A 0316 10AE6 059A 0062;0061 302A 0316 10AE6 059A 0062;0061 302A 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, IDEOGRAPHIC LEVEL TONE MARK, MANICHAEAN ABBREVIATION MARK BELOW, LATIN SMALL LETTER B
0061 10AE6 059A 0316 302A 0062;0061 302A 10AE6 0316 059A 0062;0061 302A 10AE6 0316 059A 0062;0061 302A 10AE6 0316 059A 0062;0061 302A 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, IDEOGRAPHIC LEVEL TONE MARK, 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 059A 0316 302A 10F46 0062;0061 302A 0316 10F46 059A 0062;0061 302A 0316 10F46 059A 0062;0061 302A 0316 10F46 059A 0062;0061 302A 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, IDEOGRAPHIC LEVEL TONE MARK, SOGDIAN COMBINING DOT BELOW, LATIN SMALL LETTER B
+0061 10F46 059A 0316 302A 0062;0061 302A 10F46 0316 059A 0062;0061 302A 10F46 0316 059A 0062;0061 302A 10F46 0316 059A 0062;0061 302A 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, IDEOGRAPHIC LEVEL TONE MARK, LATIN SMALL LETTER B
+0061 059A 0316 302A 10F47 0062;0061 302A 0316 10F47 059A 0062;0061 302A 0316 10F47 059A 0062;0061 302A 0316 10F47 059A 0062;0061 302A 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, IDEOGRAPHIC LEVEL TONE MARK, SOGDIAN COMBINING TWO DOTS BELOW, LATIN SMALL LETTER B
+0061 10F47 059A 0316 302A 0062;0061 302A 10F47 0316 059A 0062;0061 302A 10F47 0316 059A 0062;0061 302A 10F47 0316 059A 0062;0061 302A 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, IDEOGRAPHIC LEVEL TONE MARK, 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 302A 10F4B 0062;0061 302A 0316 10F4B 059A 0062;0061 302A 0316 10F4B 059A 0062;0061 302A 0316 10F4B 059A 0062;0061 302A 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, IDEOGRAPHIC LEVEL TONE MARK, SOGDIAN COMBINING CURVE BELOW, LATIN SMALL LETTER B
+0061 10F4B 059A 0316 302A 0062;0061 302A 10F4B 0316 059A 0062;0061 302A 10F4B 0316 059A 0062;0061 302A 10F4B 0316 059A 0062;0061 302A 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, IDEOGRAPHIC LEVEL TONE MARK, 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 302A 10F4D 0062;0061 302A 0316 10F4D 059A 0062;0061 302A 0316 10F4D 059A 0062;0061 302A 0316 10F4D 059A 0062;0061 302A 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, IDEOGRAPHIC LEVEL TONE MARK, SOGDIAN COMBINING HOOK BELOW, LATIN SMALL LETTER B
+0061 10F4D 059A 0316 302A 0062;0061 302A 10F4D 0316 059A 0062;0061 302A 10F4D 0316 059A 0062;0061 302A 10F4D 0316 059A 0062;0061 302A 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, IDEOGRAPHIC LEVEL TONE MARK, LATIN SMALL LETTER B
+0061 059A 0316 302A 10F4E 0062;0061 302A 0316 10F4E 059A 0062;0061 302A 0316 10F4E 059A 0062;0061 302A 0316 10F4E 059A 0062;0061 302A 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, IDEOGRAPHIC LEVEL TONE MARK, SOGDIAN COMBINING LONG HOOK BELOW, LATIN SMALL LETTER B
+0061 10F4E 059A 0316 302A 0062;0061 302A 10F4E 0316 059A 0062;0061 302A 10F4E 0316 059A 0062;0061 302A 10F4E 0316 059A 0062;0061 302A 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, IDEOGRAPHIC LEVEL TONE MARK, LATIN SMALL LETTER B
+0061 059A 0316 302A 10F4F 0062;0061 302A 0316 10F4F 059A 0062;0061 302A 0316 10F4F 059A 0062;0061 302A 0316 10F4F 059A 0062;0061 302A 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, IDEOGRAPHIC LEVEL TONE MARK, SOGDIAN COMBINING RESH BELOW, LATIN SMALL LETTER B
+0061 10F4F 059A 0316 302A 0062;0061 302A 10F4F 0316 059A 0062;0061 302A 10F4F 0316 059A 0062;0061 302A 10F4F 0316 059A 0062;0061 302A 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, IDEOGRAPHIC LEVEL TONE MARK, LATIN SMALL LETTER B
+0061 059A 0316 302A 10F50 0062;0061 302A 0316 10F50 059A 0062;0061 302A 0316 10F50 059A 0062;0061 302A 0316 10F50 059A 0062;0061 302A 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, IDEOGRAPHIC LEVEL TONE MARK, SOGDIAN COMBINING STROKE BELOW, LATIN SMALL LETTER B
+0061 10F50 059A 0316 302A 0062;0061 302A 10F50 0316 059A 0062;0061 302A 10F50 0316 059A 0062;0061 302A 10F50 0316 059A 0062;0061 302A 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, IDEOGRAPHIC LEVEL TONE MARK, 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 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
@@ -18361,6 +18397,8 @@ FFEE;FFEE;FFEE;25CB;25CB; # (○; ○; ○; ○; ○; ) HALFWIDTH WHITE CIRCLE
0061 112E9 3099 093C 0334 0062;0061 0334 112E9 093C 3099 0062;0061 0334 112E9 093C 3099 0062;0061 0334 112E9 093C 3099 0062;0061 0334 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, COMBINING TILDE OVERLAY, 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 0334 1133B 0062;0061 0334 093C 1133B 3099 0062;0061 0334 093C 1133B 3099 0062;0061 0334 093C 1133B 3099 0062;0061 0334 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, COMBINING TILDE OVERLAY, COMBINING BINDU BELOW, LATIN SMALL LETTER B
+0061 1133B 3099 093C 0334 0062;0061 0334 1133B 093C 3099 0062;0061 0334 1133B 093C 3099 0062;0061 0334 1133B 093C 3099 0062;0061 0334 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, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
0061 3099 093C 0334 1133C 0062;0061 0334 093C 1133C 3099 0062;0061 0334 093C 1133C 3099 0062;0061 0334 093C 1133C 3099 0062;0061 0334 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, COMBINING TILDE OVERLAY, GRANTHA SIGN NUKTA, LATIN SMALL LETTER B
0061 1133C 3099 093C 0334 0062;0061 0334 1133C 093C 3099 0062;0061 0334 1133C 093C 3099 0062;0061 0334 1133C 093C 3099 0062;0061 0334 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, COMBINING TILDE OVERLAY, 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
@@ -18393,6 +18431,8 @@ FFEE;FFEE;FFEE;25CB;25CB; # (○; ○; ○; ○; ○; ) HALFWIDTH WHITE CIRCLE
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 0334 11446 0062;0061 0334 093C 11446 3099 0062;0061 0334 093C 11446 3099 0062;0061 0334 093C 11446 3099 0062;0061 0334 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, COMBINING TILDE OVERLAY, NEWA SIGN NUKTA, LATIN SMALL LETTER B
0061 11446 3099 093C 0334 0062;0061 0334 11446 093C 3099 0062;0061 0334 11446 093C 3099 0062;0061 0334 11446 093C 3099 0062;0061 0334 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, COMBINING TILDE OVERLAY, 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 0334 114C3 0062;0061 0334 093C 114C3 3099 0062;0061 0334 093C 114C3 3099 0062;0061 0334 093C 114C3 3099 0062;0061 0334 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, COMBINING TILDE OVERLAY, TIRHUTA SIGN NUKTA, LATIN SMALL LETTER B
@@ -18409,6 +18449,10 @@ FFEE;FFEE;FFEE;25CB;25CB; # (○; ○; ○; ○; ○; ) HALFWIDTH WHITE CIRCLE
0061 116B7 3099 093C 0334 0062;0061 0334 116B7 093C 3099 0062;0061 0334 116B7 093C 3099 0062;0061 0334 116B7 093C 3099 0062;0061 0334 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, COMBINING TILDE OVERLAY, 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 0334 1183A 0062;0061 0334 093C 1183A 3099 0062;0061 0334 093C 1183A 3099 0062;0061 0334 093C 1183A 3099 0062;0061 0334 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, COMBINING TILDE OVERLAY, DOGRA SIGN NUKTA, LATIN SMALL LETTER B
+0061 1183A 3099 093C 0334 0062;0061 0334 1183A 093C 3099 0062;0061 0334 1183A 093C 3099 0062;0061 0334 1183A 093C 3099 0062;0061 0334 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, COMBINING TILDE OVERLAY, 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
@@ -18423,6 +18467,8 @@ FFEE;FFEE;FFEE;25CB;25CB; # (○; ○; ○; ○; ○; ) HALFWIDTH WHITE CIRCLE
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 093C 0334 16AF0 0062;0061 0334 16AF0 093C 0062;0061 0334 16AF0 093C 0062;0061 0334 16AF0 093C 0062;0061 0334 16AF0 093C 0062; # (a◌़◌̴◌𖫰b; a◌̴◌𖫰◌़b; a◌̴◌𖫰◌़b; a◌̴◌𖫰◌़b; a◌̴◌𖫰◌़b; ) LATIN SMALL LETTER A, DEVANAGARI SIGN NUKTA, COMBINING TILDE OVERLAY, BASSA VAH COMBINING HIGH TONE, LATIN SMALL LETTER B
0061 16AF0 093C 0334 0062;0061 16AF0 0334 093C 0062;0061 16AF0 0334 093C 0062;0061 16AF0 0334 093C 0062;0061 16AF0 0334 093C 0062; # (a◌𖫰◌़◌̴b; a◌𖫰◌̴◌़b; a◌𖫰◌̴◌़b; a◌𖫰◌̴◌़b; a◌𖫰◌̴◌़b; ) LATIN SMALL LETTER A, BASSA VAH COMBINING HIGH TONE, DEVANAGARI SIGN NUKTA, COMBINING TILDE OVERLAY, LATIN SMALL LETTER B
0061 093C 0334 16AF1 0062;0061 0334 16AF1 093C 0062;0061 0334 16AF1 093C 0062;0061 0334 16AF1 093C 0062;0061 0334 16AF1 093C 0062; # (a◌़◌̴◌𖫱b; a◌̴◌𖫱◌़b; a◌̴◌𖫱◌़b; a◌̴◌𖫱◌़b; a◌̴◌𖫱◌़b; ) LATIN SMALL LETTER A, DEVANAGARI SIGN NUKTA, COMBINING TILDE OVERLAY, BASSA VAH COMBINING LOW TONE, LATIN SMALL LETTER B
diff --git a/lib/stdlib/test/uri_string_SUITE.erl b/lib/stdlib/test/uri_string_SUITE.erl
index 4fc0d76be8..ddaead9c7c 100644
--- a/lib/stdlib/test/uri_string_SUITE.erl
+++ b/lib/stdlib/test/uri_string_SUITE.erl
@@ -862,9 +862,11 @@ transcode_negative(_Config) ->
compose_query(_Config) ->
[] = uri_string:compose_query([]),
"foo=1&bar=2" = uri_string:compose_query([{<<"foo">>,"1"}, {"bar", "2"}]),
+ "foo=1&bar" = uri_string:compose_query([{<<"foo">>,"1"}, {"bar", true}]),
"foo=1&b%C3%A4r=2" = uri_string:compose_query([{"foo","1"}, {"bär", "2"}],[{encoding,utf8}]),
"foo=1&b%C3%A4r=2" = uri_string:compose_query([{"foo","1"}, {"bär", "2"}],[{encoding,unicode}]),
"foo=1&b%E4r=2" = uri_string:compose_query([{"foo","1"}, {"bär", "2"}],[{encoding,latin1}]),
+ "foo&b%E4r=2" = uri_string:compose_query([{"foo",true}, {"bär", "2"}],[{encoding,latin1}]),
"foo+bar=1&%E5%90%88=2" = uri_string:compose_query([{"foo bar","1"}, {"合", "2"}]),
"foo+bar=1&%26%2321512%3B=2" =
uri_string:compose_query([{"foo bar","1"}, {"合", "2"}],[{encoding,latin1}]),
@@ -906,11 +908,13 @@ dissect_query(_Config) ->
[{"föo bar","1"},{"ö","2"}] =
uri_string:dissect_query("föo+bar=1&%C3%B6=2"),
[{<<"föo bar"/utf8>>,<<"1">>},{<<"ö"/utf8>>,<<"2">>}] =
- uri_string:dissect_query(<<"föo+bar=1&%C3%B6=2"/utf8>>).
+ uri_string:dissect_query(<<"föo+bar=1&%C3%B6=2"/utf8>>),
+ [{"foo1",true},{"bar","2"}] =
+ uri_string:dissect_query("foo1&bar=2"),
+ [{<<"foo1">>,<<"1">>},{<<"bar">>,true}] =
+ uri_string:dissect_query(<<"foo1=1&bar">>).
dissect_query_negative(_Config) ->
- {error,missing_value,"&"} =
- uri_string:dissect_query("foo1&bar=2"),
{error,invalid_percent_encoding,"%XX%B6"} = uri_string:dissect_query("foo=%XX%B6&amp;bar=2"),
{error,invalid_input,[153]} =
uri_string:dissect_query("foo=%99%B6&amp;bar=2"),
diff --git a/lib/stdlib/uc_spec/CaseFolding.txt b/lib/stdlib/uc_spec/CaseFolding.txt
index efdf18e441..cce350f49c 100644
--- a/lib/stdlib/uc_spec/CaseFolding.txt
+++ b/lib/stdlib/uc_spec/CaseFolding.txt
@@ -1,6 +1,6 @@
-# CaseFolding-10.0.0.txt
-# Date: 2017-04-14, 05:40:18 GMT
-# © 2017 Unicode®, Inc.
+# CaseFolding-11.0.0.txt
+# Date: 2018-01-31, 08:20:09 GMT
+# © 2018 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
#
@@ -603,6 +603,52 @@
1C86; C; 044A; # CYRILLIC SMALL LETTER TALL HARD SIGN
1C87; C; 0463; # CYRILLIC SMALL LETTER TALL YAT
1C88; C; A64B; # CYRILLIC SMALL LETTER UNBLENDED UK
+1C90; C; 10D0; # GEORGIAN MTAVRULI CAPITAL LETTER AN
+1C91; C; 10D1; # GEORGIAN MTAVRULI CAPITAL LETTER BAN
+1C92; C; 10D2; # GEORGIAN MTAVRULI CAPITAL LETTER GAN
+1C93; C; 10D3; # GEORGIAN MTAVRULI CAPITAL LETTER DON
+1C94; C; 10D4; # GEORGIAN MTAVRULI CAPITAL LETTER EN
+1C95; C; 10D5; # GEORGIAN MTAVRULI CAPITAL LETTER VIN
+1C96; C; 10D6; # GEORGIAN MTAVRULI CAPITAL LETTER ZEN
+1C97; C; 10D7; # GEORGIAN MTAVRULI CAPITAL LETTER TAN
+1C98; C; 10D8; # GEORGIAN MTAVRULI CAPITAL LETTER IN
+1C99; C; 10D9; # GEORGIAN MTAVRULI CAPITAL LETTER KAN
+1C9A; C; 10DA; # GEORGIAN MTAVRULI CAPITAL LETTER LAS
+1C9B; C; 10DB; # GEORGIAN MTAVRULI CAPITAL LETTER MAN
+1C9C; C; 10DC; # GEORGIAN MTAVRULI CAPITAL LETTER NAR
+1C9D; C; 10DD; # GEORGIAN MTAVRULI CAPITAL LETTER ON
+1C9E; C; 10DE; # GEORGIAN MTAVRULI CAPITAL LETTER PAR
+1C9F; C; 10DF; # GEORGIAN MTAVRULI CAPITAL LETTER ZHAR
+1CA0; C; 10E0; # GEORGIAN MTAVRULI CAPITAL LETTER RAE
+1CA1; C; 10E1; # GEORGIAN MTAVRULI CAPITAL LETTER SAN
+1CA2; C; 10E2; # GEORGIAN MTAVRULI CAPITAL LETTER TAR
+1CA3; C; 10E3; # GEORGIAN MTAVRULI CAPITAL LETTER UN
+1CA4; C; 10E4; # GEORGIAN MTAVRULI CAPITAL LETTER PHAR
+1CA5; C; 10E5; # GEORGIAN MTAVRULI CAPITAL LETTER KHAR
+1CA6; C; 10E6; # GEORGIAN MTAVRULI CAPITAL LETTER GHAN
+1CA7; C; 10E7; # GEORGIAN MTAVRULI CAPITAL LETTER QAR
+1CA8; C; 10E8; # GEORGIAN MTAVRULI CAPITAL LETTER SHIN
+1CA9; C; 10E9; # GEORGIAN MTAVRULI CAPITAL LETTER CHIN
+1CAA; C; 10EA; # GEORGIAN MTAVRULI CAPITAL LETTER CAN
+1CAB; C; 10EB; # GEORGIAN MTAVRULI CAPITAL LETTER JIL
+1CAC; C; 10EC; # GEORGIAN MTAVRULI CAPITAL LETTER CIL
+1CAD; C; 10ED; # GEORGIAN MTAVRULI CAPITAL LETTER CHAR
+1CAE; C; 10EE; # GEORGIAN MTAVRULI CAPITAL LETTER XAN
+1CAF; C; 10EF; # GEORGIAN MTAVRULI CAPITAL LETTER JHAN
+1CB0; C; 10F0; # GEORGIAN MTAVRULI CAPITAL LETTER HAE
+1CB1; C; 10F1; # GEORGIAN MTAVRULI CAPITAL LETTER HE
+1CB2; C; 10F2; # GEORGIAN MTAVRULI CAPITAL LETTER HIE
+1CB3; C; 10F3; # GEORGIAN MTAVRULI CAPITAL LETTER WE
+1CB4; C; 10F4; # GEORGIAN MTAVRULI CAPITAL LETTER HAR
+1CB5; C; 10F5; # GEORGIAN MTAVRULI CAPITAL LETTER HOE
+1CB6; C; 10F6; # GEORGIAN MTAVRULI CAPITAL LETTER FI
+1CB7; C; 10F7; # GEORGIAN MTAVRULI CAPITAL LETTER YN
+1CB8; C; 10F8; # GEORGIAN MTAVRULI CAPITAL LETTER ELIFI
+1CB9; C; 10F9; # GEORGIAN MTAVRULI CAPITAL LETTER TURNED GAN
+1CBA; C; 10FA; # GEORGIAN MTAVRULI CAPITAL LETTER AIN
+1CBD; C; 10FD; # GEORGIAN MTAVRULI CAPITAL LETTER AEN
+1CBE; C; 10FE; # GEORGIAN MTAVRULI CAPITAL LETTER HARD SIGN
+1CBF; C; 10FF; # GEORGIAN MTAVRULI CAPITAL LETTER LABIAL SIGN
1E00; C; 1E01; # LATIN CAPITAL LETTER A WITH RING BELOW
1E02; C; 1E03; # LATIN CAPITAL LETTER B WITH DOT ABOVE
1E04; C; 1E05; # LATIN CAPITAL LETTER B WITH DOT BELOW
@@ -1180,6 +1226,7 @@ A7B2; C; 029D; # LATIN CAPITAL LETTER J WITH CROSSED-TAIL
A7B3; C; AB53; # LATIN CAPITAL LETTER CHI
A7B4; C; A7B5; # LATIN CAPITAL LETTER BETA
A7B6; C; A7B7; # LATIN CAPITAL LETTER OMEGA
+A7B8; C; A7B9; # LATIN CAPITAL LETTER U WITH STROKE
AB70; C; 13A0; # CHEROKEE SMALL LETTER A
AB71; C; 13A1; # CHEROKEE SMALL LETTER E
AB72; C; 13A2; # CHEROKEE SMALL LETTER I
@@ -1457,6 +1504,38 @@ FF3A; C; FF5A; # FULLWIDTH LATIN CAPITAL LETTER Z
118BD; C; 118DD; # WARANG CITI CAPITAL LETTER SSUU
118BE; C; 118DE; # WARANG CITI CAPITAL LETTER SII
118BF; C; 118DF; # WARANG CITI CAPITAL LETTER VIYO
+16E40; C; 16E60; # MEDEFAIDRIN CAPITAL LETTER M
+16E41; C; 16E61; # MEDEFAIDRIN CAPITAL LETTER S
+16E42; C; 16E62; # MEDEFAIDRIN CAPITAL LETTER V
+16E43; C; 16E63; # MEDEFAIDRIN CAPITAL LETTER W
+16E44; C; 16E64; # MEDEFAIDRIN CAPITAL LETTER ATIU
+16E45; C; 16E65; # MEDEFAIDRIN CAPITAL LETTER Z
+16E46; C; 16E66; # MEDEFAIDRIN CAPITAL LETTER KP
+16E47; C; 16E67; # MEDEFAIDRIN CAPITAL LETTER P
+16E48; C; 16E68; # MEDEFAIDRIN CAPITAL LETTER T
+16E49; C; 16E69; # MEDEFAIDRIN CAPITAL LETTER G
+16E4A; C; 16E6A; # MEDEFAIDRIN CAPITAL LETTER F
+16E4B; C; 16E6B; # MEDEFAIDRIN CAPITAL LETTER I
+16E4C; C; 16E6C; # MEDEFAIDRIN CAPITAL LETTER K
+16E4D; C; 16E6D; # MEDEFAIDRIN CAPITAL LETTER A
+16E4E; C; 16E6E; # MEDEFAIDRIN CAPITAL LETTER J
+16E4F; C; 16E6F; # MEDEFAIDRIN CAPITAL LETTER E
+16E50; C; 16E70; # MEDEFAIDRIN CAPITAL LETTER B
+16E51; C; 16E71; # MEDEFAIDRIN CAPITAL LETTER C
+16E52; C; 16E72; # MEDEFAIDRIN CAPITAL LETTER U
+16E53; C; 16E73; # MEDEFAIDRIN CAPITAL LETTER YU
+16E54; C; 16E74; # MEDEFAIDRIN CAPITAL LETTER L
+16E55; C; 16E75; # MEDEFAIDRIN CAPITAL LETTER Q
+16E56; C; 16E76; # MEDEFAIDRIN CAPITAL LETTER HP
+16E57; C; 16E77; # MEDEFAIDRIN CAPITAL LETTER NY
+16E58; C; 16E78; # MEDEFAIDRIN CAPITAL LETTER X
+16E59; C; 16E79; # MEDEFAIDRIN CAPITAL LETTER D
+16E5A; C; 16E7A; # MEDEFAIDRIN CAPITAL LETTER OE
+16E5B; C; 16E7B; # MEDEFAIDRIN CAPITAL LETTER N
+16E5C; C; 16E7C; # MEDEFAIDRIN CAPITAL LETTER R
+16E5D; C; 16E7D; # MEDEFAIDRIN CAPITAL LETTER O
+16E5E; C; 16E7E; # MEDEFAIDRIN CAPITAL LETTER AI
+16E5F; C; 16E7F; # MEDEFAIDRIN CAPITAL LETTER Y
1E900; C; 1E922; # ADLAM CAPITAL LETTER ALIF
1E901; C; 1E923; # ADLAM CAPITAL LETTER DAALI
1E902; C; 1E924; # ADLAM CAPITAL LETTER LAAM
diff --git a/lib/stdlib/uc_spec/CompositionExclusions.txt b/lib/stdlib/uc_spec/CompositionExclusions.txt
index ff42508686..ea63595bd3 100644
--- a/lib/stdlib/uc_spec/CompositionExclusions.txt
+++ b/lib/stdlib/uc_spec/CompositionExclusions.txt
@@ -1,5 +1,5 @@
-# CompositionExclusions-10.0.0.txt
-# Date: 2017-02-15, 00:00:00 GMT [KW, LI]
+# CompositionExclusions-11.0.0.txt
+# Date: 2017-12-06, 00:00:00 GMT [KW, LI]
# © 2017 Unicode®, Inc.
# For terms of use, see http://www.unicode.org/terms_of_use.html
#
diff --git a/lib/stdlib/uc_spec/GraphemeBreakProperty.txt b/lib/stdlib/uc_spec/GraphemeBreakProperty.txt
index 32bb12e47e..52052e6e33 100644
--- a/lib/stdlib/uc_spec/GraphemeBreakProperty.txt
+++ b/lib/stdlib/uc_spec/GraphemeBreakProperty.txt
@@ -1,6 +1,6 @@
-# GraphemeBreakProperty-10.0.0.txt
-# Date: 2017-03-12, 07:03:41 GMT
-# © 2017 Unicode®, Inc.
+# GraphemeBreakProperty-11.0.0.txt
+# Date: 2018-03-16, 20:34:02 GMT
+# © 2018 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
#
@@ -24,12 +24,13 @@
08E2 ; Prepend # Cf ARABIC DISPUTED END OF AYAH
0D4E ; Prepend # Lo MALAYALAM LETTER DOT REPH
110BD ; Prepend # Cf KAITHI NUMBER SIGN
+110CD ; Prepend # Cf KAITHI NUMBER SIGN ABOVE
111C2..111C3 ; Prepend # Lo [2] SHARADA SIGN JIHVAMULIYA..SHARADA SIGN UPADHMANIYA
11A3A ; Prepend # Lo ZANABAZAR SQUARE CLUSTER-INITIAL LETTER RA
11A86..11A89 ; Prepend # Lo [4] SOYOMBO CLUSTER-INITIAL LETTER RA..SOYOMBO CLUSTER-INITIAL LETTER SA
11D46 ; Prepend # Lo MASARAM GONDI REPHA
-# Total code points: 19
+# Total code points: 20
# ================================================
@@ -95,12 +96,13 @@ E01F0..E0FFF ; Control # Cn [3600] <reserved-E01F0>..<reserved-E0FFF>
0730..074A ; Extend # Mn [27] SYRIAC PTHAHA ABOVE..SYRIAC BARREKH
07A6..07B0 ; Extend # Mn [11] THAANA ABAFILI..THAANA SUKUN
07EB..07F3 ; Extend # Mn [9] NKO COMBINING SHORT HIGH TONE..NKO COMBINING DOUBLE DOT ABOVE
+07FD ; Extend # Mn NKO DANTAYALAN
0816..0819 ; Extend # Mn [4] SAMARITAN MARK IN..SAMARITAN MARK DAGESH
081B..0823 ; Extend # Mn [9] SAMARITAN MARK EPENTHETIC YUT..SAMARITAN VOWEL SIGN A
0825..0827 ; Extend # Mn [3] SAMARITAN VOWEL SIGN SHORT A..SAMARITAN VOWEL SIGN U
0829..082D ; Extend # Mn [5] SAMARITAN VOWEL SIGN LONG I..SAMARITAN MARK NEQUDAA
0859..085B ; Extend # Mn [3] MANDAIC AFFRICATION MARK..MANDAIC GEMINATION MARK
-08D4..08E1 ; Extend # Mn [14] ARABIC SMALL HIGH WORD AR-RUB..ARABIC SMALL HIGH SIGN SAFHA
+08D3..08E1 ; Extend # Mn [15] ARABIC SMALL LOW WAW..ARABIC SMALL HIGH SIGN SAFHA
08E3..0902 ; Extend # Mn [32] ARABIC TURNED DAMMA BELOW..DEVANAGARI SIGN ANUSVARA
093A ; Extend # Mn DEVANAGARI VOWEL SIGN OE
093C ; Extend # Mn DEVANAGARI SIGN NUKTA
@@ -115,6 +117,7 @@ E01F0..E0FFF ; Control # Cn [3600] <reserved-E01F0>..<reserved-E0FFF>
09CD ; Extend # Mn BENGALI SIGN VIRAMA
09D7 ; Extend # Mc BENGALI AU LENGTH MARK
09E2..09E3 ; Extend # Mn [2] BENGALI VOWEL SIGN VOCALIC L..BENGALI VOWEL SIGN VOCALIC LL
+09FE ; Extend # Mn BENGALI SANDHI MARK
0A01..0A02 ; Extend # Mn [2] GURMUKHI SIGN ADAK BINDI..GURMUKHI SIGN BINDI
0A3C ; Extend # Mn GURMUKHI SIGN NUKTA
0A41..0A42 ; Extend # Mn [2] GURMUKHI VOWEL SIGN U..GURMUKHI VOWEL SIGN UU
@@ -145,6 +148,7 @@ E01F0..E0FFF ; Control # Cn [3600] <reserved-E01F0>..<reserved-E0FFF>
0BCD ; Extend # Mn TAMIL SIGN VIRAMA
0BD7 ; Extend # Mc TAMIL AU LENGTH MARK
0C00 ; Extend # Mn TELUGU SIGN COMBINING CANDRABINDU ABOVE
+0C04 ; Extend # Mn TELUGU SIGN COMBINING ANUSVARA ABOVE
0C3E..0C40 ; Extend # Mn [3] TELUGU VOWEL SIGN AA..TELUGU VOWEL SIGN II
0C46..0C48 ; Extend # Mn [3] TELUGU VOWEL SIGN E..TELUGU VOWEL SIGN AI
0C4A..0C4D ; Extend # Mn [4] TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA
@@ -273,6 +277,7 @@ A80B ; Extend # Mn SYLOTI NAGRI SIGN ANUSVARA
A825..A826 ; Extend # Mn [2] SYLOTI NAGRI VOWEL SIGN U..SYLOTI NAGRI VOWEL SIGN E
A8C4..A8C5 ; Extend # Mn [2] SAURASHTRA SIGN VIRAMA..SAURASHTRA SIGN CANDRABINDU
A8E0..A8F1 ; Extend # Mn [18] COMBINING DEVANAGARI DIGIT ZERO..COMBINING DEVANAGARI SIGN AVAGRAHA
+A8FF ; Extend # Mn DEVANAGARI VOWEL SIGN AY
A926..A92D ; Extend # Mn [8] KAYAH LI VOWEL UE..KAYAH LI TONE CALYA PLOPHU
A947..A951 ; Extend # Mn [11] REJANG VOWEL SIGN I..REJANG CONSONANT SIGN R
A980..A982 ; Extend # Mn [3] JAVANESE SIGN PANYANGGA..JAVANESE SIGN LAYAR
@@ -309,6 +314,8 @@ FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDT
10A38..10A3A ; Extend # Mn [3] KHAROSHTHI SIGN BAR ABOVE..KHAROSHTHI SIGN DOT BELOW
10A3F ; Extend # Mn KHAROSHTHI VIRAMA
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
+10F46..10F50 ; Extend # Mn [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW
11001 ; Extend # Mn BRAHMI SIGN ANUSVARA
11038..11046 ; Extend # Mn [15] BRAHMI VOWEL SIGN AA..BRAHMI VIRAMA
1107F..11081 ; Extend # Mn [3] BRAHMI NUMBER JOINER..KAITHI SIGN ANUSVARA
@@ -320,7 +327,7 @@ FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDT
11173 ; Extend # Mn MAHAJANI SIGN NUKTA
11180..11181 ; Extend # Mn [2] SHARADA SIGN CANDRABINDU..SHARADA SIGN ANUSVARA
111B6..111BE ; Extend # Mn [9] SHARADA VOWEL SIGN U..SHARADA VOWEL SIGN O
-111CA..111CC ; Extend # Mn [3] SHARADA SIGN NUKTA..SHARADA EXTRA SHORT VOWEL MARK
+111C9..111CC ; Extend # Mn [4] SHARADA SANDHI MARK..SHARADA EXTRA SHORT VOWEL MARK
1122F..11231 ; Extend # Mn [3] KHOJKI VOWEL SIGN U..KHOJKI VOWEL SIGN AI
11234 ; Extend # Mn KHOJKI SIGN ANUSVARA
11236..11237 ; Extend # Mn [2] KHOJKI SIGN NUKTA..KHOJKI SIGN SHADDA
@@ -328,7 +335,7 @@ FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDT
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
-1133C ; Extend # Mn GRANTHA SIGN NUKTA
+1133B..1133C ; Extend # Mn [2] COMBINING BINDU BELOW..GRANTHA SIGN NUKTA
1133E ; Extend # Mc GRANTHA VOWEL SIGN AA
11340 ; Extend # Mn GRANTHA VOWEL SIGN II
11357 ; Extend # Mc GRANTHA AU LENGTH MARK
@@ -337,6 +344,7 @@ FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDT
11438..1143F ; Extend # Mn [8] NEWA VOWEL SIGN U..NEWA VOWEL SIGN AI
11442..11444 ; Extend # Mn [3] NEWA SIGN VIRAMA..NEWA SIGN ANUSVARA
11446 ; Extend # Mn NEWA SIGN NUKTA
+1145E ; Extend # Mn NEWA SANDHI MARK
114B0 ; Extend # Mc TIRHUTA VOWEL SIGN AA
114B3..114B8 ; Extend # Mn [6] TIRHUTA VOWEL SIGN U..TIRHUTA VOWEL SIGN VOCALIC LL
114BA ; Extend # Mn TIRHUTA VOWEL SIGN SHORT E
@@ -358,8 +366,9 @@ FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDT
1171D..1171F ; Extend # Mn [3] AHOM CONSONANT SIGN MEDIAL LA..AHOM CONSONANT SIGN MEDIAL LIGATING RA
11722..11725 ; Extend # Mn [4] AHOM VOWEL SIGN I..AHOM VOWEL SIGN UU
11727..1172B ; Extend # Mn [5] AHOM VOWEL SIGN AW..AHOM SIGN KILLER
-11A01..11A06 ; Extend # Mn [6] ZANABAZAR SQUARE VOWEL SIGN I..ZANABAZAR SQUARE VOWEL SIGN O
-11A09..11A0A ; Extend # Mn [2] ZANABAZAR SQUARE VOWEL SIGN REVERSED I..ZANABAZAR SQUARE VOWEL LENGTH MARK
+1182F..11837 ; Extend # Mn [9] DOGRA VOWEL SIGN U..DOGRA SIGN ANUSVARA
+11839..1183A ; Extend # Mn [2] DOGRA SIGN VIRAMA..DOGRA SIGN NUKTA
+11A01..11A0A ; Extend # Mn [10] ZANABAZAR SQUARE VOWEL SIGN I..ZANABAZAR SQUARE VOWEL LENGTH MARK
11A33..11A38 ; Extend # Mn [6] ZANABAZAR SQUARE FINAL CONSONANT MARK..ZANABAZAR SQUARE SIGN ANUSVARA
11A3B..11A3E ; Extend # Mn [4] ZANABAZAR SQUARE CLUSTER-FINAL LETTER YA..ZANABAZAR SQUARE CLUSTER-FINAL LETTER VA
11A47 ; Extend # Mn ZANABAZAR SQUARE SUBJOINER
@@ -379,6 +388,10 @@ FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDT
11D3C..11D3D ; Extend # Mn [2] MASARAM GONDI VOWEL SIGN AI..MASARAM GONDI VOWEL SIGN O
11D3F..11D45 ; Extend # Mn [7] MASARAM GONDI VOWEL SIGN AU..MASARAM GONDI VIRAMA
11D47 ; Extend # Mn MASARAM GONDI RA-KARA
+11D90..11D91 ; Extend # Mn [2] GUNJALA GONDI VOWEL SIGN EE..GUNJALA GONDI VOWEL SIGN AI
+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
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
16F8F..16F92 ; Extend # Mn [4] MIAO TONE RIGHT..MIAO TONE BELOW
@@ -403,10 +416,11 @@ FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDT
1E026..1E02A ; Extend # Mn [5] COMBINING GLAGOLITIC LETTER YO..COMBINING GLAGOLITIC LETTER FITA
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: 1901
+# Total code points: 1948
# ================================================
@@ -517,6 +531,7 @@ ABEC ; SpacingMark # Mc MEETEI MAYEK LUM IYEK
110B0..110B2 ; SpacingMark # Mc [3] KAITHI VOWEL SIGN AA..KAITHI VOWEL SIGN II
110B7..110B8 ; SpacingMark # Mc [2] KAITHI VOWEL SIGN O..KAITHI VOWEL SIGN AU
1112C ; SpacingMark # Mc CHAKMA VOWEL SIGN E
+11145..11146 ; SpacingMark # Mc [2] CHAKMA VOWEL SIGN AA..CHAKMA VOWEL SIGN EI
11182 ; SpacingMark # Mc SHARADA SIGN VISARGA
111B3..111B5 ; SpacingMark # Mc [3] SHARADA VOWEL SIGN AA..SHARADA VOWEL SIGN II
111BF..111C0 ; SpacingMark # Mc [2] SHARADA VOWEL SIGN AU..SHARADA SIGN VIRAMA
@@ -549,7 +564,8 @@ ABEC ; SpacingMark # Mc MEETEI MAYEK LUM IYEK
116B6 ; SpacingMark # Mc TAKRI SIGN VIRAMA
11720..11721 ; SpacingMark # Mc [2] AHOM VOWEL SIGN A..AHOM VOWEL SIGN AA
11726 ; SpacingMark # Mc AHOM VOWEL SIGN E
-11A07..11A08 ; SpacingMark # Mc [2] ZANABAZAR SQUARE VOWEL SIGN AI..ZANABAZAR SQUARE VOWEL SIGN AU
+1182C..1182E ; SpacingMark # Mc [3] DOGRA VOWEL SIGN AA..DOGRA VOWEL SIGN II
+11838 ; SpacingMark # Mc DOGRA SIGN VISARGA
11A39 ; SpacingMark # Mc ZANABAZAR SQUARE SIGN VISARGA
11A57..11A58 ; SpacingMark # Mc [2] SOYOMBO VOWEL SIGN AI..SOYOMBO VOWEL SIGN AU
11A97 ; SpacingMark # Mc SOYOMBO SIGN VISARGA
@@ -558,11 +574,15 @@ ABEC ; SpacingMark # Mc MEETEI MAYEK LUM IYEK
11CA9 ; SpacingMark # Mc MARCHEN SUBJOINED LETTER YA
11CB1 ; SpacingMark # Mc MARCHEN VOWEL SIGN I
11CB4 ; SpacingMark # Mc MARCHEN VOWEL SIGN O
+11D8A..11D8E ; SpacingMark # Mc [5] GUNJALA GONDI VOWEL SIGN AA..GUNJALA GONDI VOWEL SIGN UU
+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
16F51..16F7E ; SpacingMark # Mc [46] MIAO SIGN ASPIRATION..MIAO VOWEL SIGN NG
1D166 ; SpacingMark # Mc MUSICAL SYMBOL COMBINING SPRECHGESANG STEM
1D16D ; SpacingMark # Mc MUSICAL SYMBOL COMBINING AUGMENTATION DOT
-# Total code points: 348
+# Total code points: 362
# ================================================
@@ -1395,81 +1415,8 @@ D789..D7A3 ; LVT # Lo [27] HANGUL SYLLABLE HIG..HANGUL SYLLABLE HIH
# ================================================
-261D ; E_Base # So WHITE UP POINTING INDEX
-26F9 ; E_Base # So PERSON WITH BALL
-270A..270D ; E_Base # So [4] RAISED FIST..WRITING HAND
-1F385 ; E_Base # So FATHER CHRISTMAS
-1F3C2..1F3C4 ; E_Base # So [3] SNOWBOARDER..SURFER
-1F3C7 ; E_Base # So HORSE RACING
-1F3CA..1F3CC ; E_Base # So [3] SWIMMER..GOLFER
-1F442..1F443 ; E_Base # So [2] EAR..NOSE
-1F446..1F450 ; E_Base # So [11] WHITE UP POINTING BACKHAND INDEX..OPEN HANDS SIGN
-1F46E ; E_Base # So POLICE OFFICER
-1F470..1F478 ; E_Base # So [9] BRIDE WITH VEIL..PRINCESS
-1F47C ; E_Base # So BABY ANGEL
-1F481..1F483 ; E_Base # So [3] INFORMATION DESK PERSON..DANCER
-1F485..1F487 ; E_Base # So [3] NAIL POLISH..HAIRCUT
-1F4AA ; E_Base # So FLEXED BICEPS
-1F574..1F575 ; E_Base # So [2] MAN IN BUSINESS SUIT LEVITATING..SLEUTH OR SPY
-1F57A ; E_Base # So MAN DANCING
-1F590 ; E_Base # So RAISED HAND WITH FINGERS SPLAYED
-1F595..1F596 ; E_Base # So [2] REVERSED HAND WITH MIDDLE FINGER EXTENDED..RAISED HAND WITH PART BETWEEN MIDDLE AND RING FINGERS
-1F645..1F647 ; E_Base # So [3] FACE WITH NO GOOD GESTURE..PERSON BOWING DEEPLY
-1F64B..1F64F ; E_Base # So [5] HAPPY PERSON RAISING ONE HAND..PERSON WITH FOLDED HANDS
-1F6A3 ; E_Base # So ROWBOAT
-1F6B4..1F6B6 ; E_Base # So [3] BICYCLIST..PEDESTRIAN
-1F6C0 ; E_Base # So BATH
-1F6CC ; E_Base # So SLEEPING ACCOMMODATION
-1F918..1F91C ; E_Base # So [5] SIGN OF THE HORNS..RIGHT-FACING FIST
-1F91E..1F91F ; E_Base # So [2] HAND WITH INDEX AND MIDDLE FINGERS CROSSED..I LOVE YOU HAND SIGN
-1F926 ; E_Base # So FACE PALM
-1F930..1F939 ; E_Base # So [10] PREGNANT WOMAN..JUGGLING
-1F93D..1F93E ; E_Base # So [2] WATER POLO..HANDBALL
-1F9D1..1F9DD ; E_Base # So [13] ADULT..ELF
-
-# Total code points: 98
-
-# ================================================
-
-1F3FB..1F3FF ; E_Modifier # Sk [5] EMOJI MODIFIER FITZPATRICK TYPE-1-2..EMOJI MODIFIER FITZPATRICK TYPE-6
-
-# Total code points: 5
-
-# ================================================
-
200D ; ZWJ # Cf ZERO WIDTH JOINER
# Total code points: 1
-# ================================================
-
-2640 ; Glue_After_Zwj # So FEMALE SIGN
-2642 ; Glue_After_Zwj # So MALE SIGN
-2695..2696 ; Glue_After_Zwj # So [2] STAFF OF AESCULAPIUS..SCALES
-2708 ; Glue_After_Zwj # So AIRPLANE
-2764 ; Glue_After_Zwj # So HEAVY BLACK HEART
-1F308 ; Glue_After_Zwj # So RAINBOW
-1F33E ; Glue_After_Zwj # So EAR OF RICE
-1F373 ; Glue_After_Zwj # So COOKING
-1F393 ; Glue_After_Zwj # So GRADUATION CAP
-1F3A4 ; Glue_After_Zwj # So MICROPHONE
-1F3A8 ; Glue_After_Zwj # So ARTIST PALETTE
-1F3EB ; Glue_After_Zwj # So SCHOOL
-1F3ED ; Glue_After_Zwj # So FACTORY
-1F48B ; Glue_After_Zwj # So KISS MARK
-1F4BB..1F4BC ; Glue_After_Zwj # So [2] PERSONAL COMPUTER..BRIEFCASE
-1F527 ; Glue_After_Zwj # So WRENCH
-1F52C ; Glue_After_Zwj # So MICROSCOPE
-1F5E8 ; Glue_After_Zwj # So LEFT SPEECH BUBBLE
-1F680 ; Glue_After_Zwj # So ROCKET
-1F692 ; Glue_After_Zwj # So FIRE ENGINE
-
-# Total code points: 22
-
-# ================================================
-
-1F466..1F469 ; E_Base_GAZ # So [4] BOY..WOMAN
-
-# Total code points: 4
-
# EOF
diff --git a/lib/stdlib/uc_spec/PropList.txt b/lib/stdlib/uc_spec/PropList.txt
index 9a2d0e4b1c..ef86795abe 100644
--- a/lib/stdlib/uc_spec/PropList.txt
+++ b/lib/stdlib/uc_spec/PropList.txt
@@ -1,6 +1,6 @@
-# PropList-10.0.0.txt
-# Date: 2017-03-10, 08:25:30 GMT
-# © 2017 Unicode®, Inc.
+# PropList-11.0.0.txt
+# Date: 2018-03-15, 04:28:35 GMT
+# © 2018 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
#
@@ -125,7 +125,7 @@ FF63 ; Quotation_Mark # Pe HALFWIDTH RIGHT CORNER BRACKET
05C3 ; Terminal_Punctuation # Po HEBREW PUNCTUATION SOF PASUQ
060C ; Terminal_Punctuation # Po ARABIC COMMA
061B ; Terminal_Punctuation # Po ARABIC SEMICOLON
-061F ; Terminal_Punctuation # Po ARABIC QUESTION MARK
+061E..061F ; Terminal_Punctuation # Po [2] ARABIC TRIPLE DOT PUNCTUATION MARK..ARABIC QUESTION MARK
06D4 ; Terminal_Punctuation # Po ARABIC FULL STOP
0700..070A ; Terminal_Punctuation # Po [11] SYRIAC END OF PARAGRAPH..SYRIAC CONTRACTION
070C ; Terminal_Punctuation # Po SYRIAC HARKLEAN METOBELUS
@@ -156,6 +156,8 @@ FF63 ; Quotation_Mark # Pe HALFWIDTH RIGHT CORNER BRACKET
2E2E ; Terminal_Punctuation # Po REVERSED QUESTION MARK
2E3C ; Terminal_Punctuation # Po STENOGRAPHIC FULL STOP
2E41 ; Terminal_Punctuation # Po REVERSED COMMA
+2E4C ; Terminal_Punctuation # Po MEDIEVAL COMMA
+2E4E ; Terminal_Punctuation # Po PUNCTUS ELEVATUS MARK
3001..3002 ; Terminal_Punctuation # Po [2] IDEOGRAPHIC COMMA..IDEOGRAPHIC FULL STOP
A4FE..A4FF ; Terminal_Punctuation # Po [2] LISU PUNCTUATION COMMA..LISU PUNCTUATION FULL STOP
A60D..A60F ; Terminal_Punctuation # Po [3] VAI COMMA..VAI QUESTION MARK
@@ -185,6 +187,7 @@ FF64 ; Terminal_Punctuation # Po HALFWIDTH IDEOGRAPHIC COMMA
10AF0..10AF5 ; Terminal_Punctuation # Po [6] MANICHAEAN PUNCTUATION STAR..MANICHAEAN PUNCTUATION TWO DOTS
10B3A..10B3F ; Terminal_Punctuation # Po [6] TINY TWO DOTS OVER ONE DOT PUNCTUATION..LARGE ONE RING OVER TWO RINGS PUNCTUATION
10B99..10B9C ; Terminal_Punctuation # Po [4] PSALTER PAHLAVI SECTION MARK..PSALTER PAHLAVI FOUR DOTS WITH DOT
+10F55..10F59 ; Terminal_Punctuation # Po [5] SOGDIAN PUNCTUATION TWO VERTICAL BARS..SOGDIAN PUNCTUATION HALF CIRCLE WITH DOT
11047..1104D ; Terminal_Punctuation # Po [7] BRAHMI DANDA..BRAHMI PUNCTUATION LOTUS
110BE..110C1 ; Terminal_Punctuation # Po [4] KAITHI SECTION MARK..KAITHI DOUBLE DANDA
11141..11143 ; Terminal_Punctuation # Po [3] CHAKMA DANDA..CHAKMA QUESTION MARK
@@ -204,15 +207,17 @@ FF64 ; Terminal_Punctuation # Po HALFWIDTH IDEOGRAPHIC COMMA
11AA1..11AA2 ; Terminal_Punctuation # Po [2] SOYOMBO TERMINAL MARK-1..SOYOMBO TERMINAL MARK-2
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
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
16B37..16B39 ; Terminal_Punctuation # Po [3] PAHAWH HMONG SIGN VOS THOM..PAHAWH HMONG SIGN CIM CHEEM
16B44 ; Terminal_Punctuation # Po PAHAWH HMONG SIGN XAUS
+16E97..16E98 ; Terminal_Punctuation # Po [2] MEDEFAIDRIN COMMA..MEDEFAIDRIN FULL STOP
1BC9F ; Terminal_Punctuation # Po DUPLOYAN PUNCTUATION CHINOOK FULL STOP
1DA87..1DA8A ; Terminal_Punctuation # Po [4] SIGNWRITING COMMA..SIGNWRITING COLON
-# Total code points: 252
+# Total code points: 264
# ================================================
@@ -661,6 +666,7 @@ FB1E ; Other_Alphabetic # Mn HEBREW POINT JUDEO-SPANISH VARIKA
10A01..10A03 ; Other_Alphabetic # Mn [3] KHAROSHTHI VOWEL SIGN I..KHAROSHTHI VOWEL SIGN VOCALIC R
10A05..10A06 ; Other_Alphabetic # Mn [2] KHAROSHTHI VOWEL SIGN E..KHAROSHTHI VOWEL SIGN O
10A0C..10A0F ; Other_Alphabetic # Mn [4] KHAROSHTHI VOWEL LENGTH MARK..KHAROSHTHI SIGN VISARGA
+10D24..10D27 ; Other_Alphabetic # Mn [4] HANIFI ROHINGYA SIGN HARBAHAY..HANIFI ROHINGYA SIGN TASSI
11000 ; Other_Alphabetic # Mc BRAHMI SIGN CANDRABINDU
11001 ; Other_Alphabetic # Mn BRAHMI SIGN ANUSVARA
11002 ; Other_Alphabetic # Mc BRAHMI SIGN VISARGA
@@ -673,6 +679,7 @@ FB1E ; Other_Alphabetic # Mn HEBREW POINT JUDEO-SPANISH VARIKA
11127..1112B ; Other_Alphabetic # Mn [5] CHAKMA VOWEL SIGN A..CHAKMA VOWEL SIGN UU
1112C ; Other_Alphabetic # Mc CHAKMA VOWEL SIGN E
1112D..11132 ; Other_Alphabetic # Mn [6] CHAKMA VOWEL SIGN AI..CHAKMA AU MARK
+11145..11146 ; Other_Alphabetic # Mc [2] CHAKMA VOWEL SIGN AA..CHAKMA VOWEL SIGN EI
11180..11181 ; Other_Alphabetic # Mn [2] SHARADA SIGN CANDRABINDU..SHARADA SIGN ANUSVARA
11182 ; Other_Alphabetic # Mc SHARADA SIGN VISARGA
111B3..111B5 ; Other_Alphabetic # Mc [3] SHARADA VOWEL SIGN AA..SHARADA VOWEL SIGN II
@@ -730,9 +737,10 @@ FB1E ; Other_Alphabetic # Mn HEBREW POINT JUDEO-SPANISH VARIKA
11722..11725 ; Other_Alphabetic # Mn [4] AHOM VOWEL SIGN I..AHOM VOWEL SIGN UU
11726 ; Other_Alphabetic # Mc AHOM VOWEL SIGN E
11727..1172A ; Other_Alphabetic # Mn [4] AHOM VOWEL SIGN AW..AHOM VOWEL SIGN AM
-11A01..11A06 ; Other_Alphabetic # Mn [6] ZANABAZAR SQUARE VOWEL SIGN I..ZANABAZAR SQUARE VOWEL SIGN O
-11A07..11A08 ; Other_Alphabetic # Mc [2] ZANABAZAR SQUARE VOWEL SIGN AI..ZANABAZAR SQUARE VOWEL SIGN AU
-11A09..11A0A ; Other_Alphabetic # Mn [2] ZANABAZAR SQUARE VOWEL SIGN REVERSED I..ZANABAZAR SQUARE VOWEL LENGTH MARK
+1182C..1182E ; Other_Alphabetic # Mc [3] DOGRA VOWEL SIGN AA..DOGRA VOWEL SIGN II
+1182F..11837 ; Other_Alphabetic # Mn [9] DOGRA VOWEL SIGN U..DOGRA SIGN ANUSVARA
+11838 ; Other_Alphabetic # Mc DOGRA SIGN VISARGA
+11A01..11A0A ; Other_Alphabetic # Mn [10] ZANABAZAR SQUARE VOWEL SIGN I..ZANABAZAR SQUARE VOWEL LENGTH MARK
11A35..11A38 ; Other_Alphabetic # Mn [4] ZANABAZAR SQUARE SIGN CANDRABINDU..ZANABAZAR SQUARE SIGN ANUSVARA
11A39 ; Other_Alphabetic # Mc ZANABAZAR SQUARE SIGN VISARGA
11A3B..11A3E ; Other_Alphabetic # Mn [4] ZANABAZAR SQUARE CLUSTER-FINAL LETTER YA..ZANABAZAR SQUARE CLUSTER-FINAL LETTER VA
@@ -758,6 +766,13 @@ FB1E ; Other_Alphabetic # Mn HEBREW POINT JUDEO-SPANISH VARIKA
11D3F..11D41 ; Other_Alphabetic # Mn [3] MASARAM GONDI VOWEL SIGN AU..MASARAM GONDI SIGN VISARGA
11D43 ; Other_Alphabetic # Mn MASARAM GONDI SIGN CANDRA
11D47 ; Other_Alphabetic # Mn MASARAM GONDI RA-KARA
+11D8A..11D8E ; Other_Alphabetic # Mc [5] GUNJALA GONDI VOWEL SIGN AA..GUNJALA GONDI VOWEL SIGN UU
+11D90..11D91 ; Other_Alphabetic # Mn [2] GUNJALA GONDI VOWEL SIGN EE..GUNJALA GONDI VOWEL SIGN AI
+11D93..11D94 ; Other_Alphabetic # Mc [2] GUNJALA GONDI VOWEL SIGN OO..GUNJALA GONDI VOWEL SIGN AU
+11D95 ; Other_Alphabetic # Mn GUNJALA GONDI SIGN ANUSVARA
+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
16B30..16B36 ; Other_Alphabetic # Mn [7] PAHAWH HMONG MARK CIM TUB..PAHAWH HMONG MARK CIM TAUM
16F51..16F7E ; Other_Alphabetic # Mc [46] MIAO SIGN ASPIRATION..MIAO VOWEL SIGN NG
1BC9E ; Other_Alphabetic # Mn DUPLOYAN DOUBLE MARK
@@ -771,7 +786,7 @@ FB1E ; Other_Alphabetic # Mn HEBREW POINT JUDEO-SPANISH VARIKA
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: 1300
+# Total code points: 1334
# ================================================
@@ -780,10 +795,10 @@ FB1E ; Other_Alphabetic # Mn HEBREW POINT JUDEO-SPANISH VARIKA
3021..3029 ; Ideographic # Nl [9] HANGZHOU NUMERAL ONE..HANGZHOU NUMERAL NINE
3038..303A ; Ideographic # Nl [3] HANGZHOU NUMERAL TEN..HANGZHOU NUMERAL THIRTY
3400..4DB5 ; Ideographic # Lo [6582] CJK UNIFIED IDEOGRAPH-3400..CJK UNIFIED IDEOGRAPH-4DB5
-4E00..9FEA ; Ideographic # Lo [20971] CJK UNIFIED IDEOGRAPH-4E00..CJK UNIFIED IDEOGRAPH-9FEA
+4E00..9FEF ; Ideographic # Lo [20976] CJK UNIFIED IDEOGRAPH-4E00..CJK UNIFIED IDEOGRAPH-9FEF
F900..FA6D ; Ideographic # Lo [366] CJK COMPATIBILITY IDEOGRAPH-F900..CJK COMPATIBILITY IDEOGRAPH-FA6D
FA70..FAD9 ; Ideographic # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPATIBILITY IDEOGRAPH-FAD9
-17000..187EC ; Ideographic # Lo [6125] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187EC
+17000..187F1 ; Ideographic # Lo [6130] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F1
18800..18AF2 ; Ideographic # Lo [755] TANGUT COMPONENT-001..TANGUT COMPONENT-755
1B170..1B2FB ; Ideographic # Lo [396] NUSHU CHARACTER-1B170..NUSHU CHARACTER-1B2FB
20000..2A6D6 ; Ideographic # Lo [42711] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6D6
@@ -793,7 +808,7 @@ FA70..FAD9 ; Ideographic # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COM
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
-# Total code points: 96174
+# Total code points: 96184
# ================================================
@@ -953,6 +968,9 @@ FF9E..FF9F ; Diacritic # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFW
FFE3 ; Diacritic # Sk FULLWIDTH MACRON
102E0 ; Diacritic # Mn COPTIC EPACT THOUSANDS MARK
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
+10F46..10F50 ; Diacritic # Mn [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW
110B9..110BA ; Diacritic # Mn [2] KAITHI SIGN VIRAMA..KAITHI SIGN NUKTA
11133..11134 ; Diacritic # Mn [2] CHAKMA VIRAMA..CHAKMA MAAYYAA
11173 ; Diacritic # Mn MAHAJANI SIGN NUKTA
@@ -973,12 +991,14 @@ FFE3 ; Diacritic # Sk FULLWIDTH MACRON
116B6 ; Diacritic # Mc TAKRI SIGN VIRAMA
116B7 ; Diacritic # Mn TAKRI SIGN NUKTA
1172B ; Diacritic # Mn AHOM SIGN KILLER
+11839..1183A ; Diacritic # Mn [2] DOGRA SIGN VIRAMA..DOGRA SIGN NUKTA
11A34 ; Diacritic # Mn ZANABAZAR SQUARE SIGN VIRAMA
11A47 ; Diacritic # Mn ZANABAZAR SQUARE SUBJOINER
11A99 ; Diacritic # Mn SOYOMBO SUBJOINER
11C3F ; Diacritic # Mn BHAIKSUKI SIGN VIRAMA
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
16AF0..16AF4 ; Diacritic # Mn [5] BASSA VAH COMBINING HIGH TONE..BASSA VAH COMBINING HIGH-LOW TONE
16F8F..16F92 ; Diacritic # Mn [4] MIAO TONE RIGHT..MIAO TONE BELOW
16F93..16F9F ; Diacritic # Lm [13] MIAO LETTER TONE-2..MIAO LETTER REFORMED TONE-8
@@ -991,7 +1011,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: 798
+# Total code points: 818
# ================================================
@@ -1137,7 +1157,7 @@ E0020..E007F ; Other_Grapheme_Extend # Cf [96] TAG SPACE..CANCEL TAG
# ================================================
3400..4DB5 ; Unified_Ideograph # Lo [6582] CJK UNIFIED IDEOGRAPH-3400..CJK UNIFIED IDEOGRAPH-4DB5
-4E00..9FEA ; Unified_Ideograph # Lo [20971] CJK UNIFIED IDEOGRAPH-4E00..CJK UNIFIED IDEOGRAPH-9FEA
+4E00..9FEF ; Unified_Ideograph # Lo [20976] CJK UNIFIED IDEOGRAPH-4E00..CJK UNIFIED IDEOGRAPH-9FEF
FA0E..FA0F ; Unified_Ideograph # Lo [2] CJK COMPATIBILITY IDEOGRAPH-FA0E..CJK COMPATIBILITY IDEOGRAPH-FA0F
FA11 ; Unified_Ideograph # Lo CJK COMPATIBILITY IDEOGRAPH-FA11
FA13..FA14 ; Unified_Ideograph # Lo [2] CJK COMPATIBILITY IDEOGRAPH-FA13..CJK COMPATIBILITY IDEOGRAPH-FA14
@@ -1151,7 +1171,7 @@ FA27..FA29 ; Unified_Ideograph # Lo [3] CJK COMPATIBILITY IDEOGRAPH-FA27..C
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
-# Total code points: 87882
+# Total code points: 87887
# ================================================
@@ -1255,10 +1275,13 @@ AABB..AABC ; Logical_Order_Exception # Lo [2] TAI VIET VOWEL AUE..TAI VIET
002E ; Sentence_Terminal # Po FULL STOP
003F ; Sentence_Terminal # Po QUESTION MARK
0589 ; Sentence_Terminal # Po ARMENIAN FULL STOP
-061F ; Sentence_Terminal # Po ARABIC QUESTION MARK
+061E..061F ; Sentence_Terminal # Po [2] ARABIC TRIPLE DOT PUNCTUATION MARK..ARABIC QUESTION MARK
06D4 ; Sentence_Terminal # Po ARABIC FULL STOP
0700..0702 ; Sentence_Terminal # Po [3] SYRIAC END OF PARAGRAPH..SYRIAC SUBLINEAR FULL STOP
07F9 ; Sentence_Terminal # Po NKO EXCLAMATION MARK
+0837 ; Sentence_Terminal # Po SAMARITAN PUNCTUATION MELODIC QITSA
+0839 ; Sentence_Terminal # Po SAMARITAN PUNCTUATION QITSA
+083D..083E ; Sentence_Terminal # Po [2] SAMARITAN PUNCTUATION SOF MASHFAAT..SAMARITAN PUNCTUATION ANNAAU
0964..0965 ; Sentence_Terminal # Po [2] DEVANAGARI DANDA..DEVANAGARI DOUBLE DANDA
104A..104B ; Sentence_Terminal # Po [2] MYANMAR SIGN LITTLE SECTION..MYANMAR SIGN SECTION
1362 ; Sentence_Terminal # Po ETHIOPIC FULL STOP
@@ -1296,6 +1319,7 @@ FF0E ; Sentence_Terminal # Po FULLWIDTH FULL STOP
FF1F ; Sentence_Terminal # Po FULLWIDTH QUESTION MARK
FF61 ; Sentence_Terminal # Po HALFWIDTH IDEOGRAPHIC FULL STOP
10A56..10A57 ; Sentence_Terminal # Po [2] KHAROSHTHI PUNCTUATION DANDA..KHAROSHTHI PUNCTUATION DOUBLE DANDA
+10F55..10F59 ; Sentence_Terminal # Po [5] SOGDIAN PUNCTUATION TWO VERTICAL BARS..SOGDIAN PUNCTUATION HALF CIRCLE WITH DOT
11047..11048 ; Sentence_Terminal # Po [2] BRAHMI DANDA..BRAHMI DOUBLE DANDA
110BE..110C1 ; Sentence_Terminal # Po [4] KAITHI SECTION MARK..KAITHI DOUBLE DANDA
11141..11143 ; Sentence_Terminal # Po [3] CHAKMA DANDA..CHAKMA QUESTION MARK
@@ -1313,14 +1337,16 @@ FF61 ; Sentence_Terminal # Po HALFWIDTH IDEOGRAPHIC FULL STOP
11A42..11A43 ; Sentence_Terminal # Po [2] ZANABAZAR SQUARE MARK SHAD..ZANABAZAR SQUARE MARK DOUBLE SHAD
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
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
16B44 ; Sentence_Terminal # Po PAHAWH HMONG SIGN XAUS
+16E98 ; Sentence_Terminal # Po MEDEFAIDRIN FULL STOP
1BC9F ; Sentence_Terminal # Po DUPLOYAN PUNCTUATION CHINOOK FULL STOP
1DA88 ; Sentence_Terminal # Po SIGNWRITING FULL STOP
-# Total code points: 128
+# Total code points: 141
# ================================================
@@ -1521,14 +1547,10 @@ E0100..E01EF ; Variation_Selector # Mn [240] VARIATION SELECTOR-17..VARIATION S
2B74..2B75 ; Pattern_Syntax # Cn [2] <reserved-2B74>..<reserved-2B75>
2B76..2B95 ; Pattern_Syntax # So [32] NORTH WEST TRIANGLE-HEADED ARROW TO BAR..RIGHTWARDS BLACK ARROW
2B96..2B97 ; Pattern_Syntax # Cn [2] <reserved-2B96>..<reserved-2B97>
-2B98..2BB9 ; Pattern_Syntax # So [34] THREE-D TOP-LIGHTED LEFTWARDS EQUILATERAL ARROWHEAD..UP ARROWHEAD IN A RECTANGLE BOX
-2BBA..2BBC ; Pattern_Syntax # Cn [3] <reserved-2BBA>..<reserved-2BBC>
-2BBD..2BC8 ; Pattern_Syntax # So [12] BALLOT BOX WITH LIGHT X..BLACK MEDIUM RIGHT-POINTING TRIANGLE CENTRED
+2B98..2BC8 ; Pattern_Syntax # So [49] THREE-D TOP-LIGHTED LEFTWARDS EQUILATERAL ARROWHEAD..BLACK MEDIUM RIGHT-POINTING TRIANGLE CENTRED
2BC9 ; Pattern_Syntax # Cn <reserved-2BC9>
-2BCA..2BD2 ; Pattern_Syntax # So [9] TOP HALF BLACK CIRCLE..GROUP MARK
-2BD3..2BEB ; Pattern_Syntax # Cn [25] <reserved-2BD3>..<reserved-2BEB>
-2BEC..2BEF ; Pattern_Syntax # So [4] LEFTWARDS TWO-HEADED ARROW WITH TRIANGLE ARROWHEADS..DOWNWARDS TWO-HEADED ARROW WITH TRIANGLE ARROWHEADS
-2BF0..2BFF ; Pattern_Syntax # Cn [16] <reserved-2BF0>..<reserved-2BFF>
+2BCA..2BFE ; Pattern_Syntax # So [53] TOP HALF BLACK CIRCLE..REVERSED RIGHT ANGLE
+2BFF ; Pattern_Syntax # Cn <reserved-2BFF>
2E00..2E01 ; Pattern_Syntax # Po [2] RIGHT ANGLE SUBSTITUTION MARKER..RIGHT ANGLE DOTTED SUBSTITUTION MARKER
2E02 ; Pattern_Syntax # Pi LEFT SUBSTITUTION BRACKET
2E03 ; Pattern_Syntax # Pf RIGHT SUBSTITUTION BRACKET
@@ -1566,8 +1588,8 @@ E0100..E01EF ; Variation_Selector # Mn [240] VARIATION SELECTOR-17..VARIATION S
2E40 ; Pattern_Syntax # Pd DOUBLE HYPHEN
2E41 ; Pattern_Syntax # Po REVERSED COMMA
2E42 ; Pattern_Syntax # Ps DOUBLE LOW-REVERSED-9 QUOTATION MARK
-2E43..2E49 ; Pattern_Syntax # Po [7] DASH WITH LEFT UPTURN..DOUBLE STACKED COMMA
-2E4A..2E7F ; Pattern_Syntax # Cn [54] <reserved-2E4A>..<reserved-2E7F>
+2E43..2E4E ; Pattern_Syntax # Po [12] DASH WITH LEFT UPTURN..PUNCTUS ELEVATUS MARK
+2E4F..2E7F ; Pattern_Syntax # Cn [49] <reserved-2E4F>..<reserved-2E7F>
3001..3003 ; Pattern_Syntax # Po [3] IDEOGRAPHIC COMMA..DITTO MARK
3008 ; Pattern_Syntax # Ps LEFT ANGLE BRACKET
3009 ; Pattern_Syntax # Pe RIGHT ANGLE BRACKET
@@ -1606,8 +1628,9 @@ FE45..FE46 ; Pattern_Syntax # Po [2] SESAME DOT..WHITE SESAME DOT
070F ; Prepended_Concatenation_Mark # Cf SYRIAC ABBREVIATION MARK
08E2 ; Prepended_Concatenation_Mark # Cf ARABIC DISPUTED END OF AYAH
110BD ; Prepended_Concatenation_Mark # Cf KAITHI NUMBER SIGN
+110CD ; Prepended_Concatenation_Mark # Cf KAITHI NUMBER SIGN ABOVE
-# Total code points: 10
+# Total code points: 11
# ================================================
diff --git a/lib/stdlib/uc_spec/README-UPDATE.txt b/lib/stdlib/uc_spec/README-UPDATE.txt
index d59337e1a5..e1f5c8fcd0 100644
--- a/lib/stdlib/uc_spec/README-UPDATE.txt
+++ b/lib/stdlib/uc_spec/README-UPDATE.txt
@@ -1,7 +1,13 @@
When updating the unicode version copy the necessary files to this
directory.
-
And update the test files in stdlib/test/unicode_util_SUITE_data/*
+Unicode 11 was updated from:
+https://www.unicode.org/Public/11.0.0/ucd/
+https://www.unicode.org/Public/11.0.0/ucd/auxiliary/
+https://www.unicode.org/Public/emoji/11.0/
+
Update the spec_version(..) function in the generator,
gen_unicode_mod.escript
+
+
diff --git a/lib/stdlib/uc_spec/SpecialCasing.txt b/lib/stdlib/uc_spec/SpecialCasing.txt
index b9ba0d81c1..c90d09acb3 100644
--- a/lib/stdlib/uc_spec/SpecialCasing.txt
+++ b/lib/stdlib/uc_spec/SpecialCasing.txt
@@ -1,6 +1,6 @@
-# SpecialCasing-10.0.0.txt
-# Date: 2017-04-14, 05:40:43 GMT
-# © 2017 Unicode®, Inc.
+# SpecialCasing-11.0.0.txt
+# Date: 2018-02-22, 06:16:47 GMT
+# © 2018 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
#
@@ -121,7 +121,7 @@ FB17; FB17; 0544 056D; 0544 053D; # ARMENIAN SMALL LIGATURE MEN XEH
# The following cases are already in the UnicodeData.txt file, so are only commented here.
-# 0345; 0345; 0345; 0399; # COMBINING GREEK YPOGEGRAMMENI
+# 0345; 0345; 0399; 0399; # COMBINING GREEK YPOGEGRAMMENI
# All letters with YPOGEGRAMMENI (iota-subscript) or PROSGEGRAMMENI (iota adscript)
# have special uppercases.
diff --git a/lib/stdlib/uc_spec/UnicodeData.txt b/lib/stdlib/uc_spec/UnicodeData.txt
index d89c64f526..ec32fafbce 100644
--- a/lib/stdlib/uc_spec/UnicodeData.txt
+++ b/lib/stdlib/uc_spec/UnicodeData.txt
@@ -1362,6 +1362,7 @@
055D;ARMENIAN COMMA;Po;0;L;;;;;N;;;;;
055E;ARMENIAN QUESTION MARK;Po;0;L;;;;;N;;;;;
055F;ARMENIAN ABBREVIATION MARK;Po;0;L;;;;;N;;;;;
+0560;ARMENIAN SMALL LETTER TURNED AYB;Ll;0;L;;;;;N;;;;;
0561;ARMENIAN SMALL LETTER AYB;Ll;0;L;;;;;N;;;0531;;0531
0562;ARMENIAN SMALL LETTER BEN;Ll;0;L;;;;;N;;;0532;;0532
0563;ARMENIAN SMALL LETTER GIM;Ll;0;L;;;;;N;;;0533;;0533
@@ -1401,6 +1402,7 @@
0585;ARMENIAN SMALL LETTER OH;Ll;0;L;;;;;N;;;0555;;0555
0586;ARMENIAN SMALL LETTER FEH;Ll;0;L;;;;;N;;;0556;;0556
0587;ARMENIAN SMALL LIGATURE ECH YIWN;Ll;0;L;<compat> 0565 0582;;;;N;;;;;
+0588;ARMENIAN SMALL LETTER YI WITH STROKE;Ll;0;L;;;;;N;;;;;
0589;ARMENIAN FULL STOP;Po;0;L;;;;;N;ARMENIAN PERIOD;;;;
058A;ARMENIAN HYPHEN;Pd;0;ON;;;;;N;;;;;
058D;RIGHT-FACING ARMENIAN ETERNITY SIGN;So;0;ON;;;;;N;;;;;
@@ -1488,6 +1490,7 @@
05E8;HEBREW LETTER RESH;Lo;0;R;;;;;N;;;;;
05E9;HEBREW LETTER SHIN;Lo;0;R;;;;;N;;;;;
05EA;HEBREW LETTER TAV;Lo;0;R;;;;;N;;;;;
+05EF;HEBREW YOD TRIANGLE;Lo;0;R;;;;;N;;;;;
05F0;HEBREW LIGATURE YIDDISH DOUBLE VAV;Lo;0;R;;;;;N;HEBREW LETTER DOUBLE VAV;;;;
05F1;HEBREW LIGATURE YIDDISH VAV YOD;Lo;0;R;;;;;N;HEBREW LETTER VAV YOD;;;;
05F2;HEBREW LIGATURE YIDDISH DOUBLE YOD;Lo;0;R;;;;;N;HEBREW LETTER DOUBLE YOD;;;;
@@ -1982,6 +1985,9 @@
07F8;NKO COMMA;Po;0;ON;;;;;N;;;;;
07F9;NKO EXCLAMATION MARK;Po;0;ON;;;;;N;;;;;
07FA;NKO LAJANYALAN;Lm;0;R;;;;;N;;;;;
+07FD;NKO DANTAYALAN;Mn;220;NSM;;;;;N;;;;;
+07FE;NKO DOROME SIGN;Sc;0;R;;;;;N;;;;;
+07FF;NKO TAMAN SIGN;Sc;0;R;;;;;N;;;;;
0800;SAMARITAN LETTER ALAF;Lo;0;R;;;;;N;;;;;
0801;SAMARITAN LETTER BIT;Lo;0;R;;;;;N;;;;;
0802;SAMARITAN LETTER GAMAN;Lo;0;R;;;;;N;;;;;
@@ -2112,6 +2118,7 @@
08BB;ARABIC LETTER AFRICAN FEH;Lo;0;AL;;;;;N;;;;;
08BC;ARABIC LETTER AFRICAN QAF;Lo;0;AL;;;;;N;;;;;
08BD;ARABIC LETTER AFRICAN NOON;Lo;0;AL;;;;;N;;;;;
+08D3;ARABIC SMALL LOW WAW;Mn;220;NSM;;;;;N;;;;;
08D4;ARABIC SMALL HIGH WORD AR-RUB;Mn;230;NSM;;;;;N;;;;;
08D5;ARABIC SMALL HIGH SAD;Mn;230;NSM;;;;;N;;;;;
08D6;ARABIC SMALL HIGH AIN;Mn;230;NSM;;;;;N;;;;;
@@ -2379,6 +2386,7 @@
09FB;BENGALI GANDA MARK;Sc;0;ET;;;;;N;;;;;
09FC;BENGALI LETTER VEDIC ANUSVARA;Lo;0;L;;;;;N;;;;;
09FD;BENGALI ABBREVIATION SIGN;Po;0;L;;;;;N;;;;;
+09FE;BENGALI SANDHI MARK;Mn;230;NSM;;;;;N;;;;;
0A01;GURMUKHI SIGN ADAK BINDI;Mn;0;NSM;;;;;N;;;;;
0A02;GURMUKHI SIGN BINDI;Mn;0;NSM;;;;;N;;;;;
0A03;GURMUKHI SIGN VISARGA;Mc;0;L;;;;;N;;;;;
@@ -2458,6 +2466,7 @@
0A73;GURMUKHI URA;Lo;0;L;;;;;N;;;;;
0A74;GURMUKHI EK ONKAR;Lo;0;L;;;;;N;;;;;
0A75;GURMUKHI SIGN YAKASH;Mn;0;NSM;;;;;N;;;;;
+0A76;GURMUKHI ABBREVIATION SIGN;Po;0;L;;;;;N;;;;;
0A81;GUJARATI SIGN CANDRABINDU;Mn;0;NSM;;;;;N;;;;;
0A82;GUJARATI SIGN ANUSVARA;Mn;0;NSM;;;;;N;;;;;
0A83;GUJARATI SIGN VISARGA;Mc;0;L;;;;;N;;;;;
@@ -2715,6 +2724,7 @@
0C01;TELUGU SIGN CANDRABINDU;Mc;0;L;;;;;N;;;;;
0C02;TELUGU SIGN ANUSVARA;Mc;0;L;;;;;N;;;;;
0C03;TELUGU SIGN VISARGA;Mc;0;L;;;;;N;;;;;
+0C04;TELUGU SIGN COMBINING ANUSVARA ABOVE;Mn;0;NSM;;;;;N;;;;;
0C05;TELUGU LETTER A;Lo;0;L;;;;;N;;;;;
0C06;TELUGU LETTER AA;Lo;0;L;;;;;N;;;;;
0C07;TELUGU LETTER I;Lo;0;L;;;;;N;;;;;
@@ -2811,6 +2821,7 @@
0C81;KANNADA SIGN CANDRABINDU;Mn;0;NSM;;;;;N;;;;;
0C82;KANNADA SIGN ANUSVARA;Mc;0;L;;;;;N;;;;;
0C83;KANNADA SIGN VISARGA;Mc;0;L;;;;;N;;;;;
+0C84;KANNADA SIGN SIDDHAM;Po;0;L;;;;;N;;;;;
0C85;KANNADA LETTER A;Lo;0;L;;;;;N;;;;;
0C86;KANNADA LETTER AA;Lo;0;L;;;;;N;;;;;
0C87;KANNADA LETTER I;Lo;0;L;;;;;N;;;;;
@@ -3667,54 +3678,54 @@
10C5;GEORGIAN CAPITAL LETTER HOE;Lu;0;L;;;;;N;;;;2D25;
10C7;GEORGIAN CAPITAL LETTER YN;Lu;0;L;;;;;N;;;;2D27;
10CD;GEORGIAN CAPITAL LETTER AEN;Lu;0;L;;;;;N;;;;2D2D;
-10D0;GEORGIAN LETTER AN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER AN;;;;
-10D1;GEORGIAN LETTER BAN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER BAN;;;;
-10D2;GEORGIAN LETTER GAN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER GAN;;;;
-10D3;GEORGIAN LETTER DON;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER DON;;;;
-10D4;GEORGIAN LETTER EN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER EN;;;;
-10D5;GEORGIAN LETTER VIN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER VIN;;;;
-10D6;GEORGIAN LETTER ZEN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER ZEN;;;;
-10D7;GEORGIAN LETTER TAN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER TAN;;;;
-10D8;GEORGIAN LETTER IN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER IN;;;;
-10D9;GEORGIAN LETTER KAN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER KAN;;;;
-10DA;GEORGIAN LETTER LAS;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER LAS;;;;
-10DB;GEORGIAN LETTER MAN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER MAN;;;;
-10DC;GEORGIAN LETTER NAR;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER NAR;;;;
-10DD;GEORGIAN LETTER ON;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER ON;;;;
-10DE;GEORGIAN LETTER PAR;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER PAR;;;;
-10DF;GEORGIAN LETTER ZHAR;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER ZHAR;;;;
-10E0;GEORGIAN LETTER RAE;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER RAE;;;;
-10E1;GEORGIAN LETTER SAN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER SAN;;;;
-10E2;GEORGIAN LETTER TAR;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER TAR;;;;
-10E3;GEORGIAN LETTER UN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER UN;;;;
-10E4;GEORGIAN LETTER PHAR;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER PHAR;;;;
-10E5;GEORGIAN LETTER KHAR;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER KHAR;;;;
-10E6;GEORGIAN LETTER GHAN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER GHAN;;;;
-10E7;GEORGIAN LETTER QAR;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER QAR;;;;
-10E8;GEORGIAN LETTER SHIN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER SHIN;;;;
-10E9;GEORGIAN LETTER CHIN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER CHIN;;;;
-10EA;GEORGIAN LETTER CAN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER CAN;;;;
-10EB;GEORGIAN LETTER JIL;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER JIL;;;;
-10EC;GEORGIAN LETTER CIL;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER CIL;;;;
-10ED;GEORGIAN LETTER CHAR;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER CHAR;;;;
-10EE;GEORGIAN LETTER XAN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER XAN;;;;
-10EF;GEORGIAN LETTER JHAN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER JHAN;;;;
-10F0;GEORGIAN LETTER HAE;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER HAE;;;;
-10F1;GEORGIAN LETTER HE;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER HE;;;;
-10F2;GEORGIAN LETTER HIE;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER HIE;;;;
-10F3;GEORGIAN LETTER WE;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER WE;;;;
-10F4;GEORGIAN LETTER HAR;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER HAR;;;;
-10F5;GEORGIAN LETTER HOE;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER HOE;;;;
-10F6;GEORGIAN LETTER FI;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER FI;;;;
-10F7;GEORGIAN LETTER YN;Lo;0;L;;;;;N;;;;;
-10F8;GEORGIAN LETTER ELIFI;Lo;0;L;;;;;N;;;;;
-10F9;GEORGIAN LETTER TURNED GAN;Lo;0;L;;;;;N;;;;;
-10FA;GEORGIAN LETTER AIN;Lo;0;L;;;;;N;;;;;
+10D0;GEORGIAN LETTER AN;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER AN;;1C90;;10D0
+10D1;GEORGIAN LETTER BAN;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER BAN;;1C91;;10D1
+10D2;GEORGIAN LETTER GAN;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER GAN;;1C92;;10D2
+10D3;GEORGIAN LETTER DON;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER DON;;1C93;;10D3
+10D4;GEORGIAN LETTER EN;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER EN;;1C94;;10D4
+10D5;GEORGIAN LETTER VIN;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER VIN;;1C95;;10D5
+10D6;GEORGIAN LETTER ZEN;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER ZEN;;1C96;;10D6
+10D7;GEORGIAN LETTER TAN;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER TAN;;1C97;;10D7
+10D8;GEORGIAN LETTER IN;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER IN;;1C98;;10D8
+10D9;GEORGIAN LETTER KAN;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER KAN;;1C99;;10D9
+10DA;GEORGIAN LETTER LAS;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER LAS;;1C9A;;10DA
+10DB;GEORGIAN LETTER MAN;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER MAN;;1C9B;;10DB
+10DC;GEORGIAN LETTER NAR;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER NAR;;1C9C;;10DC
+10DD;GEORGIAN LETTER ON;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER ON;;1C9D;;10DD
+10DE;GEORGIAN LETTER PAR;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER PAR;;1C9E;;10DE
+10DF;GEORGIAN LETTER ZHAR;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER ZHAR;;1C9F;;10DF
+10E0;GEORGIAN LETTER RAE;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER RAE;;1CA0;;10E0
+10E1;GEORGIAN LETTER SAN;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER SAN;;1CA1;;10E1
+10E2;GEORGIAN LETTER TAR;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER TAR;;1CA2;;10E2
+10E3;GEORGIAN LETTER UN;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER UN;;1CA3;;10E3
+10E4;GEORGIAN LETTER PHAR;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER PHAR;;1CA4;;10E4
+10E5;GEORGIAN LETTER KHAR;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER KHAR;;1CA5;;10E5
+10E6;GEORGIAN LETTER GHAN;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER GHAN;;1CA6;;10E6
+10E7;GEORGIAN LETTER QAR;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER QAR;;1CA7;;10E7
+10E8;GEORGIAN LETTER SHIN;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER SHIN;;1CA8;;10E8
+10E9;GEORGIAN LETTER CHIN;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER CHIN;;1CA9;;10E9
+10EA;GEORGIAN LETTER CAN;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER CAN;;1CAA;;10EA
+10EB;GEORGIAN LETTER JIL;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER JIL;;1CAB;;10EB
+10EC;GEORGIAN LETTER CIL;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER CIL;;1CAC;;10EC
+10ED;GEORGIAN LETTER CHAR;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER CHAR;;1CAD;;10ED
+10EE;GEORGIAN LETTER XAN;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER XAN;;1CAE;;10EE
+10EF;GEORGIAN LETTER JHAN;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER JHAN;;1CAF;;10EF
+10F0;GEORGIAN LETTER HAE;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER HAE;;1CB0;;10F0
+10F1;GEORGIAN LETTER HE;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER HE;;1CB1;;10F1
+10F2;GEORGIAN LETTER HIE;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER HIE;;1CB2;;10F2
+10F3;GEORGIAN LETTER WE;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER WE;;1CB3;;10F3
+10F4;GEORGIAN LETTER HAR;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER HAR;;1CB4;;10F4
+10F5;GEORGIAN LETTER HOE;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER HOE;;1CB5;;10F5
+10F6;GEORGIAN LETTER FI;Ll;0;L;;;;;N;GEORGIAN SMALL LETTER FI;;1CB6;;10F6
+10F7;GEORGIAN LETTER YN;Ll;0;L;;;;;N;;;1CB7;;10F7
+10F8;GEORGIAN LETTER ELIFI;Ll;0;L;;;;;N;;;1CB8;;10F8
+10F9;GEORGIAN LETTER TURNED GAN;Ll;0;L;;;;;N;;;1CB9;;10F9
+10FA;GEORGIAN LETTER AIN;Ll;0;L;;;;;N;;;1CBA;;10FA
10FB;GEORGIAN PARAGRAPH SEPARATOR;Po;0;L;;;;;N;;;;;
10FC;MODIFIER LETTER GEORGIAN NAR;Lm;0;L;<super> 10DC;;;;N;;;;;
-10FD;GEORGIAN LETTER AEN;Lo;0;L;;;;;N;;;;;
-10FE;GEORGIAN LETTER HARD SIGN;Lo;0;L;;;;;N;;;;;
-10FF;GEORGIAN LETTER LABIAL SIGN;Lo;0;L;;;;;N;;;;;
+10FD;GEORGIAN LETTER AEN;Ll;0;L;;;;;N;;;1CBD;;10FD
+10FE;GEORGIAN LETTER HARD SIGN;Ll;0;L;;;;;N;;;1CBE;;10FE
+10FF;GEORGIAN LETTER LABIAL SIGN;Ll;0;L;;;;;N;;;1CBF;;10FF
1100;HANGUL CHOSEONG KIYEOK;Lo;0;L;;;;;N;;;;;
1101;HANGUL CHOSEONG SSANGKIYEOK;Lo;0;L;;;;;N;;;;;
1102;HANGUL CHOSEONG NIEUN;Lo;0;L;;;;;N;;;;;
@@ -5513,6 +5524,7 @@
1875;MONGOLIAN LETTER MANCHU RA;Lo;0;L;;;;;N;;;;;
1876;MONGOLIAN LETTER MANCHU FA;Lo;0;L;;;;;N;;;;;
1877;MONGOLIAN LETTER MANCHU ZHA;Lo;0;L;;;;;N;;;;;
+1878;MONGOLIAN LETTER CHA WITH TWO DOTS;Lo;0;L;;;;;N;;;;;
1880;MONGOLIAN LETTER ALI GALI ANUSVARA ONE;Lo;0;L;;;;;N;;;;;
1881;MONGOLIAN LETTER ALI GALI VISARGA ONE;Lo;0;L;;;;;N;;;;;
1882;MONGOLIAN LETTER ALI GALI DAMARU;Lo;0;L;;;;;N;;;;;
@@ -6388,6 +6400,52 @@
1C86;CYRILLIC SMALL LETTER TALL HARD SIGN;Ll;0;L;;;;;N;;;042A;;042A
1C87;CYRILLIC SMALL LETTER TALL YAT;Ll;0;L;;;;;N;;;0462;;0462
1C88;CYRILLIC SMALL LETTER UNBLENDED UK;Ll;0;L;;;;;N;;;A64A;;A64A
+1C90;GEORGIAN MTAVRULI CAPITAL LETTER AN;Lu;0;L;;;;;N;;;;10D0;
+1C91;GEORGIAN MTAVRULI CAPITAL LETTER BAN;Lu;0;L;;;;;N;;;;10D1;
+1C92;GEORGIAN MTAVRULI CAPITAL LETTER GAN;Lu;0;L;;;;;N;;;;10D2;
+1C93;GEORGIAN MTAVRULI CAPITAL LETTER DON;Lu;0;L;;;;;N;;;;10D3;
+1C94;GEORGIAN MTAVRULI CAPITAL LETTER EN;Lu;0;L;;;;;N;;;;10D4;
+1C95;GEORGIAN MTAVRULI CAPITAL LETTER VIN;Lu;0;L;;;;;N;;;;10D5;
+1C96;GEORGIAN MTAVRULI CAPITAL LETTER ZEN;Lu;0;L;;;;;N;;;;10D6;
+1C97;GEORGIAN MTAVRULI CAPITAL LETTER TAN;Lu;0;L;;;;;N;;;;10D7;
+1C98;GEORGIAN MTAVRULI CAPITAL LETTER IN;Lu;0;L;;;;;N;;;;10D8;
+1C99;GEORGIAN MTAVRULI CAPITAL LETTER KAN;Lu;0;L;;;;;N;;;;10D9;
+1C9A;GEORGIAN MTAVRULI CAPITAL LETTER LAS;Lu;0;L;;;;;N;;;;10DA;
+1C9B;GEORGIAN MTAVRULI CAPITAL LETTER MAN;Lu;0;L;;;;;N;;;;10DB;
+1C9C;GEORGIAN MTAVRULI CAPITAL LETTER NAR;Lu;0;L;;;;;N;;;;10DC;
+1C9D;GEORGIAN MTAVRULI CAPITAL LETTER ON;Lu;0;L;;;;;N;;;;10DD;
+1C9E;GEORGIAN MTAVRULI CAPITAL LETTER PAR;Lu;0;L;;;;;N;;;;10DE;
+1C9F;GEORGIAN MTAVRULI CAPITAL LETTER ZHAR;Lu;0;L;;;;;N;;;;10DF;
+1CA0;GEORGIAN MTAVRULI CAPITAL LETTER RAE;Lu;0;L;;;;;N;;;;10E0;
+1CA1;GEORGIAN MTAVRULI CAPITAL LETTER SAN;Lu;0;L;;;;;N;;;;10E1;
+1CA2;GEORGIAN MTAVRULI CAPITAL LETTER TAR;Lu;0;L;;;;;N;;;;10E2;
+1CA3;GEORGIAN MTAVRULI CAPITAL LETTER UN;Lu;0;L;;;;;N;;;;10E3;
+1CA4;GEORGIAN MTAVRULI CAPITAL LETTER PHAR;Lu;0;L;;;;;N;;;;10E4;
+1CA5;GEORGIAN MTAVRULI CAPITAL LETTER KHAR;Lu;0;L;;;;;N;;;;10E5;
+1CA6;GEORGIAN MTAVRULI CAPITAL LETTER GHAN;Lu;0;L;;;;;N;;;;10E6;
+1CA7;GEORGIAN MTAVRULI CAPITAL LETTER QAR;Lu;0;L;;;;;N;;;;10E7;
+1CA8;GEORGIAN MTAVRULI CAPITAL LETTER SHIN;Lu;0;L;;;;;N;;;;10E8;
+1CA9;GEORGIAN MTAVRULI CAPITAL LETTER CHIN;Lu;0;L;;;;;N;;;;10E9;
+1CAA;GEORGIAN MTAVRULI CAPITAL LETTER CAN;Lu;0;L;;;;;N;;;;10EA;
+1CAB;GEORGIAN MTAVRULI CAPITAL LETTER JIL;Lu;0;L;;;;;N;;;;10EB;
+1CAC;GEORGIAN MTAVRULI CAPITAL LETTER CIL;Lu;0;L;;;;;N;;;;10EC;
+1CAD;GEORGIAN MTAVRULI CAPITAL LETTER CHAR;Lu;0;L;;;;;N;;;;10ED;
+1CAE;GEORGIAN MTAVRULI CAPITAL LETTER XAN;Lu;0;L;;;;;N;;;;10EE;
+1CAF;GEORGIAN MTAVRULI CAPITAL LETTER JHAN;Lu;0;L;;;;;N;;;;10EF;
+1CB0;GEORGIAN MTAVRULI CAPITAL LETTER HAE;Lu;0;L;;;;;N;;;;10F0;
+1CB1;GEORGIAN MTAVRULI CAPITAL LETTER HE;Lu;0;L;;;;;N;;;;10F1;
+1CB2;GEORGIAN MTAVRULI CAPITAL LETTER HIE;Lu;0;L;;;;;N;;;;10F2;
+1CB3;GEORGIAN MTAVRULI CAPITAL LETTER WE;Lu;0;L;;;;;N;;;;10F3;
+1CB4;GEORGIAN MTAVRULI CAPITAL LETTER HAR;Lu;0;L;;;;;N;;;;10F4;
+1CB5;GEORGIAN MTAVRULI CAPITAL LETTER HOE;Lu;0;L;;;;;N;;;;10F5;
+1CB6;GEORGIAN MTAVRULI CAPITAL LETTER FI;Lu;0;L;;;;;N;;;;10F6;
+1CB7;GEORGIAN MTAVRULI CAPITAL LETTER YN;Lu;0;L;;;;;N;;;;10F7;
+1CB8;GEORGIAN MTAVRULI CAPITAL LETTER ELIFI;Lu;0;L;;;;;N;;;;10F8;
+1CB9;GEORGIAN MTAVRULI CAPITAL LETTER TURNED GAN;Lu;0;L;;;;;N;;;;10F9;
+1CBA;GEORGIAN MTAVRULI CAPITAL LETTER AIN;Lu;0;L;;;;;N;;;;10FA;
+1CBD;GEORGIAN MTAVRULI CAPITAL LETTER AEN;Lu;0;L;;;;;N;;;;10FD;
+1CBE;GEORGIAN MTAVRULI CAPITAL LETTER HARD SIGN;Lu;0;L;;;;;N;;;;10FE;
+1CBF;GEORGIAN MTAVRULI CAPITAL LETTER LABIAL SIGN;Lu;0;L;;;;;N;;;;10FF;
1CC0;SUNDANESE PUNCTUATION BINDU SURYA;Po;0;L;;;;;N;;;;;
1CC1;SUNDANESE PUNCTUATION BINDU PANGLONG;Po;0;L;;;;;N;;;;;
1CC2;SUNDANESE PUNCTUATION BINDU PURNAMA;Po;0;L;;;;;N;;;;;
@@ -9559,7 +9617,7 @@
299E;ANGLE WITH S INSIDE;Sm;0;ON;;;;;Y;;;;;
299F;ACUTE ANGLE;Sm;0;ON;;;;;Y;;;;;
29A0;SPHERICAL ANGLE OPENING LEFT;Sm;0;ON;;;;;Y;;;;;
-29A1;SPHERICAL ANGLE OPENING UP;Sm;0;ON;;;;;Y;;;;;
+29A1;SPHERICAL ANGLE OPENING UP;Sm;0;ON;;;;;N;;;;;
29A2;TURNED ANGLE;Sm;0;ON;;;;;Y;;;;;
29A3;REVERSED ANGLE;Sm;0;ON;;;;;Y;;;;;
29A4;ANGLE WITH UNDERBAR;Sm;0;ON;;;;;Y;;;;;
@@ -10092,6 +10150,9 @@
2BB7;RIBBON ARROW RIGHT DOWN;So;0;ON;;;;;N;;;;;
2BB8;UPWARDS WHITE ARROW FROM BAR WITH HORIZONTAL BAR;So;0;ON;;;;;N;;;;;
2BB9;UP ARROWHEAD IN A RECTANGLE BOX;So;0;ON;;;;;N;;;;;
+2BBA;OVERLAPPING WHITE SQUARES;So;0;ON;;;;;N;;;;;
+2BBB;OVERLAPPING WHITE AND BLACK SQUARES;So;0;ON;;;;;N;;;;;
+2BBC;OVERLAPPING BLACK SQUARES;So;0;ON;;;;;N;;;;;
2BBD;BALLOT BOX WITH LIGHT X;So;0;ON;;;;;N;;;;;
2BBE;CIRCLED X;So;0;ON;;;;;N;;;;;
2BBF;CIRCLED BOLD X;So;0;ON;;;;;N;;;;;
@@ -10113,10 +10174,50 @@
2BD0;SQUARE POSITION INDICATOR;So;0;ON;;;;;N;;;;;
2BD1;UNCERTAINTY SIGN;So;0;ON;;;;;N;;;;;
2BD2;GROUP MARK;So;0;ON;;;;;N;;;;;
+2BD3;PLUTO FORM TWO;So;0;ON;;;;;N;;;;;
+2BD4;PLUTO FORM THREE;So;0;ON;;;;;N;;;;;
+2BD5;PLUTO FORM FOUR;So;0;ON;;;;;N;;;;;
+2BD6;PLUTO FORM FIVE;So;0;ON;;;;;N;;;;;
+2BD7;TRANSPLUTO;So;0;ON;;;;;N;;;;;
+2BD8;PROSERPINA;So;0;ON;;;;;N;;;;;
+2BD9;ASTRAEA;So;0;ON;;;;;N;;;;;
+2BDA;HYGIEA;So;0;ON;;;;;N;;;;;
+2BDB;PHOLUS;So;0;ON;;;;;N;;;;;
+2BDC;NESSUS;So;0;ON;;;;;N;;;;;
+2BDD;WHITE MOON SELENA;So;0;ON;;;;;N;;;;;
+2BDE;BLACK DIAMOND ON CROSS;So;0;ON;;;;;N;;;;;
+2BDF;TRUE LIGHT MOON ARTA;So;0;ON;;;;;N;;;;;
+2BE0;CUPIDO;So;0;ON;;;;;N;;;;;
+2BE1;HADES;So;0;ON;;;;;N;;;;;
+2BE2;ZEUS;So;0;ON;;;;;N;;;;;
+2BE3;KRONOS;So;0;ON;;;;;N;;;;;
+2BE4;APOLLON;So;0;ON;;;;;N;;;;;
+2BE5;ADMETOS;So;0;ON;;;;;N;;;;;
+2BE6;VULCANUS;So;0;ON;;;;;N;;;;;
+2BE7;POSEIDON;So;0;ON;;;;;N;;;;;
+2BE8;LEFT HALF BLACK STAR;So;0;ON;;;;;N;;;;;
+2BE9;RIGHT HALF BLACK STAR;So;0;ON;;;;;N;;;;;
+2BEA;STAR WITH LEFT HALF BLACK;So;0;ON;;;;;N;;;;;
+2BEB;STAR WITH RIGHT HALF BLACK;So;0;ON;;;;;N;;;;;
2BEC;LEFTWARDS TWO-HEADED ARROW WITH TRIANGLE ARROWHEADS;So;0;ON;;;;;N;;;;;
2BED;UPWARDS TWO-HEADED ARROW WITH TRIANGLE ARROWHEADS;So;0;ON;;;;;N;;;;;
2BEE;RIGHTWARDS TWO-HEADED ARROW WITH TRIANGLE ARROWHEADS;So;0;ON;;;;;N;;;;;
2BEF;DOWNWARDS TWO-HEADED ARROW WITH TRIANGLE ARROWHEADS;So;0;ON;;;;;N;;;;;
+2BF0;ERIS FORM ONE;So;0;ON;;;;;N;;;;;
+2BF1;ERIS FORM TWO;So;0;ON;;;;;N;;;;;
+2BF2;SEDNA;So;0;ON;;;;;N;;;;;
+2BF3;RUSSIAN ASTROLOGICAL SYMBOL VIGINTILE;So;0;ON;;;;;N;;;;;
+2BF4;RUSSIAN ASTROLOGICAL SYMBOL NOVILE;So;0;ON;;;;;N;;;;;
+2BF5;RUSSIAN ASTROLOGICAL SYMBOL QUINTILE;So;0;ON;;;;;N;;;;;
+2BF6;RUSSIAN ASTROLOGICAL SYMBOL BINOVILE;So;0;ON;;;;;N;;;;;
+2BF7;RUSSIAN ASTROLOGICAL SYMBOL SENTAGON;So;0;ON;;;;;N;;;;;
+2BF8;RUSSIAN ASTROLOGICAL SYMBOL TREDECILE;So;0;ON;;;;;N;;;;;
+2BF9;EQUALS SIGN WITH INFINITY BELOW;So;0;ON;;;;;N;;;;;
+2BFA;UNITED SYMBOL;So;0;ON;;;;;N;;;;;
+2BFB;SEPARATED SYMBOL;So;0;ON;;;;;N;;;;;
+2BFC;DOUBLED SYMBOL;So;0;ON;;;;;N;;;;;
+2BFD;PASSED SYMBOL;So;0;ON;;;;;N;;;;;
+2BFE;REVERSED RIGHT ANGLE;So;0;ON;;;;;Y;;;;;
2C00;GLAGOLITIC CAPITAL LETTER AZU;Lu;0;L;;;;;N;;;;2C30;
2C01;GLAGOLITIC CAPITAL LETTER BUKY;Lu;0;L;;;;;N;;;;2C31;
2C02;GLAGOLITIC CAPITAL LETTER VEDE;Lu;0;L;;;;;N;;;;2C32;
@@ -10650,6 +10751,11 @@
2E47;LOW KAVYKA;Po;0;ON;;;;;N;;;;;
2E48;LOW KAVYKA WITH DOT;Po;0;ON;;;;;N;;;;;
2E49;DOUBLE STACKED COMMA;Po;0;ON;;;;;N;;;;;
+2E4A;DOTTED SOLIDUS;Po;0;ON;;;;;N;;;;;
+2E4B;TRIPLE DAGGER;Po;0;ON;;;;;N;;;;;
+2E4C;MEDIEVAL COMMA;Po;0;ON;;;;;N;;;;;
+2E4D;PARAGRAPHUS MARK;Po;0;ON;;;;;N;;;;;
+2E4E;PUNCTUS ELEVATUS MARK;Po;0;ON;;;;;N;;;;;
2E80;CJK RADICAL REPEAT;So;0;ON;;;;;N;;;;;
2E81;CJK RADICAL CLIFF;So;0;ON;;;;;N;;;;;
2E82;CJK RADICAL SECOND ONE;So;0;ON;;;;;N;;;;;
@@ -11286,6 +11392,7 @@
312C;BOPOMOFO LETTER GN;Lo;0;L;;;;;N;;;;;
312D;BOPOMOFO LETTER IH;Lo;0;L;;;;;N;;;;;
312E;BOPOMOFO LETTER O WITH DOT ABOVE;Lo;0;L;;;;;N;;;;;
+312F;BOPOMOFO LETTER NN;Lo;0;L;;;;;N;;;;;
3131;HANGUL LETTER KIYEOK;Lo;0;L;<compat> 1100;;;;N;HANGUL LETTER GIYEOG;;;;
3132;HANGUL LETTER SSANGKIYEOK;Lo;0;L;<compat> 1101;;;;N;HANGUL LETTER SSANG GIYEOG;;;;
3133;HANGUL LETTER KIYEOK-SIOS;Lo;0;L;<compat> 11AA;;;;N;HANGUL LETTER GIYEOG SIOS;;;;
@@ -12052,7 +12159,7 @@
4DFE;HEXAGRAM FOR AFTER COMPLETION;So;0;ON;;;;;N;;;;;
4DFF;HEXAGRAM FOR BEFORE COMPLETION;So;0;ON;;;;;N;;;;;
4E00;<CJK Ideograph, First>;Lo;0;L;;;;;N;;;;;
-9FEA;<CJK Ideograph, Last>;Lo;0;L;;;;;N;;;;;
+9FEF;<CJK Ideograph, Last>;Lo;0;L;;;;;N;;;;;
A000;YI SYLLABLE IT;Lo;0;L;;;;;N;;;;;
A001;YI SYLLABLE IX;Lo;0;L;;;;;N;;;;;
A002;YI SYLLABLE I;Lo;0;L;;;;;N;;;;;
@@ -13980,6 +14087,7 @@ A7AB;LATIN CAPITAL LETTER REVERSED OPEN E;Lu;0;L;;;;;N;;;;025C;
A7AC;LATIN CAPITAL LETTER SCRIPT G;Lu;0;L;;;;;N;;;;0261;
A7AD;LATIN CAPITAL LETTER L WITH BELT;Lu;0;L;;;;;N;;;;026C;
A7AE;LATIN CAPITAL LETTER SMALL CAPITAL I;Lu;0;L;;;;;N;;;;026A;
+A7AF;LATIN LETTER SMALL CAPITAL Q;Ll;0;L;;;;;N;;;;;
A7B0;LATIN CAPITAL LETTER TURNED K;Lu;0;L;;;;;N;;;;029E;
A7B1;LATIN CAPITAL LETTER TURNED T;Lu;0;L;;;;;N;;;;0287;
A7B2;LATIN CAPITAL LETTER J WITH CROSSED-TAIL;Lu;0;L;;;;;N;;;;029D;
@@ -13988,6 +14096,8 @@ A7B4;LATIN CAPITAL LETTER BETA;Lu;0;L;;;;;N;;;;A7B5;
A7B5;LATIN SMALL LETTER BETA;Ll;0;L;;;;;N;;;A7B4;;A7B4
A7B6;LATIN CAPITAL LETTER OMEGA;Lu;0;L;;;;;N;;;;A7B7;
A7B7;LATIN SMALL LETTER OMEGA;Ll;0;L;;;;;N;;;A7B6;;A7B6
+A7B8;LATIN CAPITAL LETTER U WITH STROKE;Lu;0;L;;;;;N;;;;A7B9;
+A7B9;LATIN SMALL LETTER U WITH STROKE;Ll;0;L;;;;;N;;;A7B8;;A7B8
A7F7;LATIN EPIGRAPHIC LETTER SIDEWAYS I;Lo;0;L;;;;;N;;;;;
A7F8;MODIFIER LETTER CAPITAL H WITH STROKE;Lm;0;L;<super> 0126;;;;N;;;;;
A7F9;MODIFIER LETTER SMALL LIGATURE OE;Lm;0;L;<super> 0153;;;;N;;;;;
@@ -14219,6 +14329,8 @@ A8FA;DEVANAGARI CARET;Po;0;L;;;;;N;;;;;
A8FB;DEVANAGARI HEADSTROKE;Lo;0;L;;;;;N;;;;;
A8FC;DEVANAGARI SIGN SIDDHAM;Po;0;L;;;;;N;;;;;
A8FD;DEVANAGARI JAIN OM;Lo;0;L;;;;;N;;;;;
+A8FE;DEVANAGARI LETTER AY;Lo;0;L;;;;;N;;;;;
+A8FF;DEVANAGARI VOWEL SIGN AY;Mn;0;NSM;;;;;N;;;;;
A900;KAYAH LI DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;;
A901;KAYAH LI DIGIT ONE;Nd;0;L;;1;1;1;N;;;;;
A902;KAYAH LI DIGIT TWO;Nd;0;L;;2;2;2;N;;;;;
@@ -18363,6 +18475,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
10A31;KHAROSHTHI LETTER HA;Lo;0;R;;;;;N;;;;;
10A32;KHAROSHTHI LETTER KKA;Lo;0;R;;;;;N;;;;;
10A33;KHAROSHTHI LETTER TTTHA;Lo;0;R;;;;;N;;;;;
+10A34;KHAROSHTHI LETTER TTTA;Lo;0;R;;;;;N;;;;;
+10A35;KHAROSHTHI LETTER VHA;Lo;0;R;;;;;N;;;;;
10A38;KHAROSHTHI SIGN BAR ABOVE;Mn;230;NSM;;;;;N;;;;;
10A39;KHAROSHTHI SIGN CAUDA;Mn;1;NSM;;;;;N;;;;;
10A3A;KHAROSHTHI SIGN DOT BELOW;Mn;220;NSM;;;;;N;;;;;
@@ -18375,6 +18489,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
10A45;KHAROSHTHI NUMBER TWENTY;No;0;R;;;;20;N;;;;;
10A46;KHAROSHTHI NUMBER ONE HUNDRED;No;0;R;;;;100;N;;;;;
10A47;KHAROSHTHI NUMBER ONE THOUSAND;No;0;R;;;;1000;N;;;;;
+10A48;KHAROSHTHI FRACTION ONE HALF;No;0;R;;;;1/2;N;;;;;
10A50;KHAROSHTHI PUNCTUATION DOT;Po;0;R;;;;;N;;;;;
10A51;KHAROSHTHI PUNCTUATION SMALL CIRCLE;Po;0;R;;;;;N;;;;;
10A52;KHAROSHTHI PUNCTUATION CIRCLE;Po;0;R;;;;;N;;;;;
@@ -18827,6 +18942,56 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
10CFD;OLD HUNGARIAN NUMBER FIFTY;No;0;R;;;;50;N;;;;;
10CFE;OLD HUNGARIAN NUMBER ONE HUNDRED;No;0;R;;;;100;N;;;;;
10CFF;OLD HUNGARIAN NUMBER ONE THOUSAND;No;0;R;;;;1000;N;;;;;
+10D00;HANIFI ROHINGYA LETTER A;Lo;0;AL;;;;;N;;;;;
+10D01;HANIFI ROHINGYA LETTER BA;Lo;0;AL;;;;;N;;;;;
+10D02;HANIFI ROHINGYA LETTER PA;Lo;0;AL;;;;;N;;;;;
+10D03;HANIFI ROHINGYA LETTER TA;Lo;0;AL;;;;;N;;;;;
+10D04;HANIFI ROHINGYA LETTER TTA;Lo;0;AL;;;;;N;;;;;
+10D05;HANIFI ROHINGYA LETTER JA;Lo;0;AL;;;;;N;;;;;
+10D06;HANIFI ROHINGYA LETTER CA;Lo;0;AL;;;;;N;;;;;
+10D07;HANIFI ROHINGYA LETTER HA;Lo;0;AL;;;;;N;;;;;
+10D08;HANIFI ROHINGYA LETTER KHA;Lo;0;AL;;;;;N;;;;;
+10D09;HANIFI ROHINGYA LETTER FA;Lo;0;AL;;;;;N;;;;;
+10D0A;HANIFI ROHINGYA LETTER DA;Lo;0;AL;;;;;N;;;;;
+10D0B;HANIFI ROHINGYA LETTER DDA;Lo;0;AL;;;;;N;;;;;
+10D0C;HANIFI ROHINGYA LETTER RA;Lo;0;AL;;;;;N;;;;;
+10D0D;HANIFI ROHINGYA LETTER RRA;Lo;0;AL;;;;;N;;;;;
+10D0E;HANIFI ROHINGYA LETTER ZA;Lo;0;AL;;;;;N;;;;;
+10D0F;HANIFI ROHINGYA LETTER SA;Lo;0;AL;;;;;N;;;;;
+10D10;HANIFI ROHINGYA LETTER SHA;Lo;0;AL;;;;;N;;;;;
+10D11;HANIFI ROHINGYA LETTER KA;Lo;0;AL;;;;;N;;;;;
+10D12;HANIFI ROHINGYA LETTER GA;Lo;0;AL;;;;;N;;;;;
+10D13;HANIFI ROHINGYA LETTER LA;Lo;0;AL;;;;;N;;;;;
+10D14;HANIFI ROHINGYA LETTER MA;Lo;0;AL;;;;;N;;;;;
+10D15;HANIFI ROHINGYA LETTER NA;Lo;0;AL;;;;;N;;;;;
+10D16;HANIFI ROHINGYA LETTER WA;Lo;0;AL;;;;;N;;;;;
+10D17;HANIFI ROHINGYA LETTER KINNA WA;Lo;0;AL;;;;;N;;;;;
+10D18;HANIFI ROHINGYA LETTER YA;Lo;0;AL;;;;;N;;;;;
+10D19;HANIFI ROHINGYA LETTER KINNA YA;Lo;0;AL;;;;;N;;;;;
+10D1A;HANIFI ROHINGYA LETTER NGA;Lo;0;AL;;;;;N;;;;;
+10D1B;HANIFI ROHINGYA LETTER NYA;Lo;0;AL;;;;;N;;;;;
+10D1C;HANIFI ROHINGYA LETTER VA;Lo;0;AL;;;;;N;;;;;
+10D1D;HANIFI ROHINGYA VOWEL A;Lo;0;AL;;;;;N;;;;;
+10D1E;HANIFI ROHINGYA VOWEL I;Lo;0;AL;;;;;N;;;;;
+10D1F;HANIFI ROHINGYA VOWEL U;Lo;0;AL;;;;;N;;;;;
+10D20;HANIFI ROHINGYA VOWEL E;Lo;0;AL;;;;;N;;;;;
+10D21;HANIFI ROHINGYA VOWEL O;Lo;0;AL;;;;;N;;;;;
+10D22;HANIFI ROHINGYA MARK SAKIN;Lo;0;AL;;;;;N;;;;;
+10D23;HANIFI ROHINGYA MARK NA KHONNA;Lo;0;AL;;;;;N;;;;;
+10D24;HANIFI ROHINGYA SIGN HARBAHAY;Mn;230;NSM;;;;;N;;;;;
+10D25;HANIFI ROHINGYA SIGN TAHALA;Mn;230;NSM;;;;;N;;;;;
+10D26;HANIFI ROHINGYA SIGN TANA;Mn;230;NSM;;;;;N;;;;;
+10D27;HANIFI ROHINGYA SIGN TASSI;Mn;230;NSM;;;;;N;;;;;
+10D30;HANIFI ROHINGYA DIGIT ZERO;Nd;0;AN;;0;0;0;N;;;;;
+10D31;HANIFI ROHINGYA DIGIT ONE;Nd;0;AN;;1;1;1;N;;;;;
+10D32;HANIFI ROHINGYA DIGIT TWO;Nd;0;AN;;2;2;2;N;;;;;
+10D33;HANIFI ROHINGYA DIGIT THREE;Nd;0;AN;;3;3;3;N;;;;;
+10D34;HANIFI ROHINGYA DIGIT FOUR;Nd;0;AN;;4;4;4;N;;;;;
+10D35;HANIFI ROHINGYA DIGIT FIVE;Nd;0;AN;;5;5;5;N;;;;;
+10D36;HANIFI ROHINGYA DIGIT SIX;Nd;0;AN;;6;6;6;N;;;;;
+10D37;HANIFI ROHINGYA DIGIT SEVEN;Nd;0;AN;;7;7;7;N;;;;;
+10D38;HANIFI ROHINGYA DIGIT EIGHT;Nd;0;AN;;8;8;8;N;;;;;
+10D39;HANIFI ROHINGYA DIGIT NINE;Nd;0;AN;;9;9;9;N;;;;;
10E60;RUMI DIGIT ONE;No;0;AN;;;1;1;N;;;;;
10E61;RUMI DIGIT TWO;No;0;AN;;;2;2;N;;;;;
10E62;RUMI DIGIT THREE;No;0;AN;;;3;3;N;;;;;
@@ -18858,6 +19023,88 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
10E7C;RUMI FRACTION ONE QUARTER;No;0;AN;;;;1/4;N;;;;;
10E7D;RUMI FRACTION ONE THIRD;No;0;AN;;;;1/3;N;;;;;
10E7E;RUMI FRACTION TWO THIRDS;No;0;AN;;;;2/3;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;;;;;
+10F03;OLD SOGDIAN LETTER FINAL BETH;Lo;0;R;;;;;N;;;;;
+10F04;OLD SOGDIAN LETTER GIMEL;Lo;0;R;;;;;N;;;;;
+10F05;OLD SOGDIAN LETTER HE;Lo;0;R;;;;;N;;;;;
+10F06;OLD SOGDIAN LETTER FINAL HE;Lo;0;R;;;;;N;;;;;
+10F07;OLD SOGDIAN LETTER WAW;Lo;0;R;;;;;N;;;;;
+10F08;OLD SOGDIAN LETTER ZAYIN;Lo;0;R;;;;;N;;;;;
+10F09;OLD SOGDIAN LETTER HETH;Lo;0;R;;;;;N;;;;;
+10F0A;OLD SOGDIAN LETTER YODH;Lo;0;R;;;;;N;;;;;
+10F0B;OLD SOGDIAN LETTER KAPH;Lo;0;R;;;;;N;;;;;
+10F0C;OLD SOGDIAN LETTER LAMEDH;Lo;0;R;;;;;N;;;;;
+10F0D;OLD SOGDIAN LETTER MEM;Lo;0;R;;;;;N;;;;;
+10F0E;OLD SOGDIAN LETTER NUN;Lo;0;R;;;;;N;;;;;
+10F0F;OLD SOGDIAN LETTER FINAL NUN;Lo;0;R;;;;;N;;;;;
+10F10;OLD SOGDIAN LETTER FINAL NUN WITH VERTICAL TAIL;Lo;0;R;;;;;N;;;;;
+10F11;OLD SOGDIAN LETTER SAMEKH;Lo;0;R;;;;;N;;;;;
+10F12;OLD SOGDIAN LETTER AYIN;Lo;0;R;;;;;N;;;;;
+10F13;OLD SOGDIAN LETTER ALTERNATE AYIN;Lo;0;R;;;;;N;;;;;
+10F14;OLD SOGDIAN LETTER PE;Lo;0;R;;;;;N;;;;;
+10F15;OLD SOGDIAN LETTER SADHE;Lo;0;R;;;;;N;;;;;
+10F16;OLD SOGDIAN LETTER FINAL SADHE;Lo;0;R;;;;;N;;;;;
+10F17;OLD SOGDIAN LETTER FINAL SADHE WITH VERTICAL TAIL;Lo;0;R;;;;;N;;;;;
+10F18;OLD SOGDIAN LETTER RESH-AYIN-DALETH;Lo;0;R;;;;;N;;;;;
+10F19;OLD SOGDIAN LETTER SHIN;Lo;0;R;;;;;N;;;;;
+10F1A;OLD SOGDIAN LETTER TAW;Lo;0;R;;;;;N;;;;;
+10F1B;OLD SOGDIAN LETTER FINAL TAW;Lo;0;R;;;;;N;;;;;
+10F1C;OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL;Lo;0;R;;;;;N;;;;;
+10F1D;OLD SOGDIAN NUMBER ONE;No;0;R;;;;1;N;;;;;
+10F1E;OLD SOGDIAN NUMBER TWO;No;0;R;;;;2;N;;;;;
+10F1F;OLD SOGDIAN NUMBER THREE;No;0;R;;;;3;N;;;;;
+10F20;OLD SOGDIAN NUMBER FOUR;No;0;R;;;;4;N;;;;;
+10F21;OLD SOGDIAN NUMBER FIVE;No;0;R;;;;5;N;;;;;
+10F22;OLD SOGDIAN NUMBER TEN;No;0;R;;;;10;N;;;;;
+10F23;OLD SOGDIAN NUMBER TWENTY;No;0;R;;;;20;N;;;;;
+10F24;OLD SOGDIAN NUMBER THIRTY;No;0;R;;;;30;N;;;;;
+10F25;OLD SOGDIAN NUMBER ONE HUNDRED;No;0;R;;;;100;N;;;;;
+10F26;OLD SOGDIAN FRACTION ONE HALF;No;0;R;;;;1/2;N;;;;;
+10F27;OLD SOGDIAN LIGATURE AYIN-DALETH;Lo;0;R;;;;;N;;;;;
+10F30;SOGDIAN LETTER ALEPH;Lo;0;AL;;;;;N;;;;;
+10F31;SOGDIAN LETTER BETH;Lo;0;AL;;;;;N;;;;;
+10F32;SOGDIAN LETTER GIMEL;Lo;0;AL;;;;;N;;;;;
+10F33;SOGDIAN LETTER HE;Lo;0;AL;;;;;N;;;;;
+10F34;SOGDIAN LETTER WAW;Lo;0;AL;;;;;N;;;;;
+10F35;SOGDIAN LETTER ZAYIN;Lo;0;AL;;;;;N;;;;;
+10F36;SOGDIAN LETTER HETH;Lo;0;AL;;;;;N;;;;;
+10F37;SOGDIAN LETTER YODH;Lo;0;AL;;;;;N;;;;;
+10F38;SOGDIAN LETTER KAPH;Lo;0;AL;;;;;N;;;;;
+10F39;SOGDIAN LETTER LAMEDH;Lo;0;AL;;;;;N;;;;;
+10F3A;SOGDIAN LETTER MEM;Lo;0;AL;;;;;N;;;;;
+10F3B;SOGDIAN LETTER NUN;Lo;0;AL;;;;;N;;;;;
+10F3C;SOGDIAN LETTER SAMEKH;Lo;0;AL;;;;;N;;;;;
+10F3D;SOGDIAN LETTER AYIN;Lo;0;AL;;;;;N;;;;;
+10F3E;SOGDIAN LETTER PE;Lo;0;AL;;;;;N;;;;;
+10F3F;SOGDIAN LETTER SADHE;Lo;0;AL;;;;;N;;;;;
+10F40;SOGDIAN LETTER RESH-AYIN;Lo;0;AL;;;;;N;;;;;
+10F41;SOGDIAN LETTER SHIN;Lo;0;AL;;;;;N;;;;;
+10F42;SOGDIAN LETTER TAW;Lo;0;AL;;;;;N;;;;;
+10F43;SOGDIAN LETTER FETH;Lo;0;AL;;;;;N;;;;;
+10F44;SOGDIAN LETTER LESH;Lo;0;AL;;;;;N;;;;;
+10F45;SOGDIAN INDEPENDENT SHIN;Lo;0;AL;;;;;N;;;;;
+10F46;SOGDIAN COMBINING DOT BELOW;Mn;220;NSM;;;;;N;;;;;
+10F47;SOGDIAN COMBINING TWO DOTS BELOW;Mn;220;NSM;;;;;N;;;;;
+10F48;SOGDIAN COMBINING DOT ABOVE;Mn;230;NSM;;;;;N;;;;;
+10F49;SOGDIAN COMBINING TWO DOTS ABOVE;Mn;230;NSM;;;;;N;;;;;
+10F4A;SOGDIAN COMBINING CURVE ABOVE;Mn;230;NSM;;;;;N;;;;;
+10F4B;SOGDIAN COMBINING CURVE BELOW;Mn;220;NSM;;;;;N;;;;;
+10F4C;SOGDIAN COMBINING HOOK ABOVE;Mn;230;NSM;;;;;N;;;;;
+10F4D;SOGDIAN COMBINING HOOK BELOW;Mn;220;NSM;;;;;N;;;;;
+10F4E;SOGDIAN COMBINING LONG HOOK BELOW;Mn;220;NSM;;;;;N;;;;;
+10F4F;SOGDIAN COMBINING RESH BELOW;Mn;220;NSM;;;;;N;;;;;
+10F50;SOGDIAN COMBINING STROKE BELOW;Mn;220;NSM;;;;;N;;;;;
+10F51;SOGDIAN NUMBER ONE;No;0;AL;;;;1;N;;;;;
+10F52;SOGDIAN NUMBER TEN;No;0;AL;;;;10;N;;;;;
+10F53;SOGDIAN NUMBER TWENTY;No;0;AL;;;;20;N;;;;;
+10F54;SOGDIAN NUMBER ONE HUNDRED;No;0;AL;;;;100;N;;;;;
+10F55;SOGDIAN PUNCTUATION TWO VERTICAL BARS;Po;0;AL;;;;;N;;;;;
+10F56;SOGDIAN PUNCTUATION TWO VERTICAL BARS WITH DOTS;Po;0;AL;;;;;N;;;;;
+10F57;SOGDIAN PUNCTUATION CIRCLE WITH DOT;Po;0;AL;;;;;N;;;;;
+10F58;SOGDIAN PUNCTUATION TWO CIRCLES WITH DOTS;Po;0;AL;;;;;N;;;;;
+10F59;SOGDIAN PUNCTUATION HALF CIRCLE WITH DOT;Po;0;AL;;;;;N;;;;;
11000;BRAHMI SIGN CANDRABINDU;Mc;0;L;;;;;N;;;;;
11001;BRAHMI SIGN ANUSVARA;Mn;0;NSM;;;;;N;;;;;
11002;BRAHMI SIGN VISARGA;Mc;0;L;;;;;N;;;;;
@@ -19033,6 +19280,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
110BF;KAITHI DOUBLE SECTION MARK;Po;0;L;;;;;N;;;;;
110C0;KAITHI DANDA;Po;0;L;;;;;N;;;;;
110C1;KAITHI DOUBLE DANDA;Po;0;L;;;;;N;;;;;
+110CD;KAITHI NUMBER SIGN ABOVE;Cf;0;L;;;;;N;;;;;
110D0;SORA SOMPENG LETTER SAH;Lo;0;L;;;;;N;;;;;
110D1;SORA SOMPENG LETTER TAH;Lo;0;L;;;;;N;;;;;
110D2;SORA SOMPENG LETTER BAH;Lo;0;L;;;;;N;;;;;
@@ -19135,6 +19383,9 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
11141;CHAKMA DANDA;Po;0;L;;;;;N;;;;;
11142;CHAKMA DOUBLE DANDA;Po;0;L;;;;;N;;;;;
11143;CHAKMA QUESTION MARK;Po;0;L;;;;;N;;;;;
+11144;CHAKMA LETTER LHAA;Lo;0;L;;;;;N;;;;;
+11145;CHAKMA VOWEL SIGN AA;Mc;0;L;;;;;N;;;;;
+11146;CHAKMA VOWEL SIGN EI;Mc;0;L;;;;;N;;;;;
11150;MAHAJANI LETTER A;Lo;0;L;;;;;N;;;;;
11151;MAHAJANI LETTER I;Lo;0;L;;;;;N;;;;;
11152;MAHAJANI LETTER U;Lo;0;L;;;;;N;;;;;
@@ -19247,7 +19498,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
111C6;SHARADA DOUBLE DANDA;Po;0;L;;;;;N;;;;;
111C7;SHARADA ABBREVIATION SIGN;Po;0;L;;;;;N;;;;;
111C8;SHARADA SEPARATOR;Po;0;L;;;;;N;;;;;
-111C9;SHARADA SANDHI MARK;Po;0;L;;;;;N;;;;;
+111C9;SHARADA SANDHI MARK;Mn;0;NSM;;;;;N;;;;;
111CA;SHARADA SIGN NUKTA;Mn;7;NSM;;;;;N;;;;;
111CB;SHARADA VOWEL MODIFIER MARK;Mn;0;NSM;;;;;N;;;;;
111CC;SHARADA EXTRA SHORT VOWEL MARK;Mn;0;NSM;;;;;N;;;;;
@@ -19507,6 +19758,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
11337;GRANTHA LETTER SSA;Lo;0;L;;;;;N;;;;;
11338;GRANTHA LETTER SA;Lo;0;L;;;;;N;;;;;
11339;GRANTHA LETTER HA;Lo;0;L;;;;;N;;;;;
+1133B;COMBINING BINDU BELOW;Mn;7;NSM;;;;;N;;;;;
1133C;GRANTHA SIGN NUKTA;Mn;7;NSM;;;;;N;;;;;
1133D;GRANTHA SIGN AVAGRAHA;Lo;0;L;;;;;N;;;;;
1133E;GRANTHA VOWEL SIGN AA;Mc;0;L;;;;;N;;;;;
@@ -19634,6 +19886,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
11459;NEWA DIGIT NINE;Nd;0;L;;9;9;9;N;;;;;
1145B;NEWA PLACEHOLDER MARK;Po;0;L;;;;;N;;;;;
1145D;NEWA INSERTION SIGN;Po;0;L;;;;;N;;;;;
+1145E;NEWA SANDHI MARK;Mn;230;NSM;;;;;N;;;;;
11480;TIRHUTA ANJI;Lo;0;L;;;;;N;;;;;
11481;TIRHUTA LETTER A;Lo;0;L;;;;;N;;;;;
11482;TIRHUTA LETTER AA;Lo;0;L;;;;;N;;;;;
@@ -19992,6 +20245,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
11717;AHOM LETTER GHA;Lo;0;L;;;;;N;;;;;
11718;AHOM LETTER BHA;Lo;0;L;;;;;N;;;;;
11719;AHOM LETTER JHA;Lo;0;L;;;;;N;;;;;
+1171A;AHOM LETTER ALTERNATE BA;Lo;0;L;;;;;N;;;;;
1171D;AHOM CONSONANT SIGN MEDIAL LA;Mn;0;NSM;;;;;N;;;;;
1171E;AHOM CONSONANT SIGN MEDIAL RA;Mn;0;NSM;;;;;N;;;;;
1171F;AHOM CONSONANT SIGN MEDIAL LIGATING RA;Mn;0;NSM;;;;;N;;;;;
@@ -20023,6 +20277,66 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1173D;AHOM SIGN SECTION;Po;0;L;;;;;N;;;;;
1173E;AHOM SIGN RULAI;Po;0;L;;;;;N;;;;;
1173F;AHOM SYMBOL VI;So;0;L;;;;;N;;;;;
+11800;DOGRA LETTER A;Lo;0;L;;;;;N;;;;;
+11801;DOGRA LETTER AA;Lo;0;L;;;;;N;;;;;
+11802;DOGRA LETTER I;Lo;0;L;;;;;N;;;;;
+11803;DOGRA LETTER II;Lo;0;L;;;;;N;;;;;
+11804;DOGRA LETTER U;Lo;0;L;;;;;N;;;;;
+11805;DOGRA LETTER UU;Lo;0;L;;;;;N;;;;;
+11806;DOGRA LETTER E;Lo;0;L;;;;;N;;;;;
+11807;DOGRA LETTER AI;Lo;0;L;;;;;N;;;;;
+11808;DOGRA LETTER O;Lo;0;L;;;;;N;;;;;
+11809;DOGRA LETTER AU;Lo;0;L;;;;;N;;;;;
+1180A;DOGRA LETTER KA;Lo;0;L;;;;;N;;;;;
+1180B;DOGRA LETTER KHA;Lo;0;L;;;;;N;;;;;
+1180C;DOGRA LETTER GA;Lo;0;L;;;;;N;;;;;
+1180D;DOGRA LETTER GHA;Lo;0;L;;;;;N;;;;;
+1180E;DOGRA LETTER NGA;Lo;0;L;;;;;N;;;;;
+1180F;DOGRA LETTER CA;Lo;0;L;;;;;N;;;;;
+11810;DOGRA LETTER CHA;Lo;0;L;;;;;N;;;;;
+11811;DOGRA LETTER JA;Lo;0;L;;;;;N;;;;;
+11812;DOGRA LETTER JHA;Lo;0;L;;;;;N;;;;;
+11813;DOGRA LETTER NYA;Lo;0;L;;;;;N;;;;;
+11814;DOGRA LETTER TTA;Lo;0;L;;;;;N;;;;;
+11815;DOGRA LETTER TTHA;Lo;0;L;;;;;N;;;;;
+11816;DOGRA LETTER DDA;Lo;0;L;;;;;N;;;;;
+11817;DOGRA LETTER DDHA;Lo;0;L;;;;;N;;;;;
+11818;DOGRA LETTER NNA;Lo;0;L;;;;;N;;;;;
+11819;DOGRA LETTER TA;Lo;0;L;;;;;N;;;;;
+1181A;DOGRA LETTER THA;Lo;0;L;;;;;N;;;;;
+1181B;DOGRA LETTER DA;Lo;0;L;;;;;N;;;;;
+1181C;DOGRA LETTER DHA;Lo;0;L;;;;;N;;;;;
+1181D;DOGRA LETTER NA;Lo;0;L;;;;;N;;;;;
+1181E;DOGRA LETTER PA;Lo;0;L;;;;;N;;;;;
+1181F;DOGRA LETTER PHA;Lo;0;L;;;;;N;;;;;
+11820;DOGRA LETTER BA;Lo;0;L;;;;;N;;;;;
+11821;DOGRA LETTER BHA;Lo;0;L;;;;;N;;;;;
+11822;DOGRA LETTER MA;Lo;0;L;;;;;N;;;;;
+11823;DOGRA LETTER YA;Lo;0;L;;;;;N;;;;;
+11824;DOGRA LETTER RA;Lo;0;L;;;;;N;;;;;
+11825;DOGRA LETTER LA;Lo;0;L;;;;;N;;;;;
+11826;DOGRA LETTER VA;Lo;0;L;;;;;N;;;;;
+11827;DOGRA LETTER SHA;Lo;0;L;;;;;N;;;;;
+11828;DOGRA LETTER SSA;Lo;0;L;;;;;N;;;;;
+11829;DOGRA LETTER SA;Lo;0;L;;;;;N;;;;;
+1182A;DOGRA LETTER HA;Lo;0;L;;;;;N;;;;;
+1182B;DOGRA LETTER RRA;Lo;0;L;;;;;N;;;;;
+1182C;DOGRA VOWEL SIGN AA;Mc;0;L;;;;;N;;;;;
+1182D;DOGRA VOWEL SIGN I;Mc;0;L;;;;;N;;;;;
+1182E;DOGRA VOWEL SIGN II;Mc;0;L;;;;;N;;;;;
+1182F;DOGRA VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;;
+11830;DOGRA VOWEL SIGN UU;Mn;0;NSM;;;;;N;;;;;
+11831;DOGRA VOWEL SIGN VOCALIC R;Mn;0;NSM;;;;;N;;;;;
+11832;DOGRA VOWEL SIGN VOCALIC RR;Mn;0;NSM;;;;;N;;;;;
+11833;DOGRA VOWEL SIGN E;Mn;0;NSM;;;;;N;;;;;
+11834;DOGRA VOWEL SIGN AI;Mn;0;NSM;;;;;N;;;;;
+11835;DOGRA VOWEL SIGN O;Mn;0;NSM;;;;;N;;;;;
+11836;DOGRA VOWEL SIGN AU;Mn;0;NSM;;;;;N;;;;;
+11837;DOGRA SIGN ANUSVARA;Mn;0;NSM;;;;;N;;;;;
+11838;DOGRA SIGN VISARGA;Mc;0;L;;;;;N;;;;;
+11839;DOGRA SIGN VIRAMA;Mn;9;NSM;;;;;N;;;;;
+1183A;DOGRA SIGN NUKTA;Mn;7;NSM;;;;;N;;;;;
+1183B;DOGRA ABBREVIATION SIGN;Po;0;L;;;;;N;;;;;
118A0;WARANG CITI CAPITAL LETTER NGAA;Lu;0;L;;;;;N;;;;118C0;
118A1;WARANG CITI CAPITAL LETTER A;Lu;0;L;;;;;N;;;;118C1;
118A2;WARANG CITI CAPITAL LETTER WI;Lu;0;L;;;;;N;;;;118C2;
@@ -20114,8 +20428,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
11A04;ZANABAZAR SQUARE VOWEL SIGN E;Mn;0;NSM;;;;;N;;;;;
11A05;ZANABAZAR SQUARE VOWEL SIGN OE;Mn;0;NSM;;;;;N;;;;;
11A06;ZANABAZAR SQUARE VOWEL SIGN O;Mn;0;NSM;;;;;N;;;;;
-11A07;ZANABAZAR SQUARE VOWEL SIGN AI;Mc;0;L;;;;;N;;;;;
-11A08;ZANABAZAR SQUARE VOWEL SIGN AU;Mc;0;L;;;;;N;;;;;
+11A07;ZANABAZAR SQUARE VOWEL SIGN AI;Mn;0;L;;;;;N;;;;;
+11A08;ZANABAZAR SQUARE VOWEL SIGN AU;Mn;0;L;;;;;N;;;;;
11A09;ZANABAZAR SQUARE VOWEL SIGN REVERSED I;Mn;0;NSM;;;;;N;;;;;
11A0A;ZANABAZAR SQUARE VOWEL LENGTH MARK;Mn;0;NSM;;;;;N;;;;;
11A0B;ZANABAZAR SQUARE LETTER KA;Lo;0;L;;;;;N;;;;;
@@ -20254,6 +20568,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
11A9A;SOYOMBO MARK TSHEG;Po;0;L;;;;;N;;;;;
11A9B;SOYOMBO MARK SHAD;Po;0;L;;;;;N;;;;;
11A9C;SOYOMBO MARK DOUBLE SHAD;Po;0;L;;;;;N;;;;;
+11A9D;SOYOMBO MARK PLUTA;Lo;0;L;;;;;N;;;;;
11A9E;SOYOMBO HEAD MARK WITH MOON AND SUN AND TRIPLE FLAME;Po;0;L;;;;;N;;;;;
11A9F;SOYOMBO HEAD MARK WITH MOON AND SUN AND FLAME;Po;0;L;;;;;N;;;;;
11AA0;SOYOMBO HEAD MARK WITH MOON AND SUN;Po;0;L;;;;;N;;;;;
@@ -20556,6 +20871,94 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
11D57;MASARAM GONDI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;;
11D58;MASARAM GONDI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;;
11D59;MASARAM GONDI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;;
+11D60;GUNJALA GONDI LETTER A;Lo;0;L;;;;;N;;;;;
+11D61;GUNJALA GONDI LETTER AA;Lo;0;L;;;;;N;;;;;
+11D62;GUNJALA GONDI LETTER I;Lo;0;L;;;;;N;;;;;
+11D63;GUNJALA GONDI LETTER II;Lo;0;L;;;;;N;;;;;
+11D64;GUNJALA GONDI LETTER U;Lo;0;L;;;;;N;;;;;
+11D65;GUNJALA GONDI LETTER UU;Lo;0;L;;;;;N;;;;;
+11D67;GUNJALA GONDI LETTER EE;Lo;0;L;;;;;N;;;;;
+11D68;GUNJALA GONDI LETTER AI;Lo;0;L;;;;;N;;;;;
+11D6A;GUNJALA GONDI LETTER OO;Lo;0;L;;;;;N;;;;;
+11D6B;GUNJALA GONDI LETTER AU;Lo;0;L;;;;;N;;;;;
+11D6C;GUNJALA GONDI LETTER YA;Lo;0;L;;;;;N;;;;;
+11D6D;GUNJALA GONDI LETTER VA;Lo;0;L;;;;;N;;;;;
+11D6E;GUNJALA GONDI LETTER BA;Lo;0;L;;;;;N;;;;;
+11D6F;GUNJALA GONDI LETTER BHA;Lo;0;L;;;;;N;;;;;
+11D70;GUNJALA GONDI LETTER MA;Lo;0;L;;;;;N;;;;;
+11D71;GUNJALA GONDI LETTER KA;Lo;0;L;;;;;N;;;;;
+11D72;GUNJALA GONDI LETTER KHA;Lo;0;L;;;;;N;;;;;
+11D73;GUNJALA GONDI LETTER TA;Lo;0;L;;;;;N;;;;;
+11D74;GUNJALA GONDI LETTER THA;Lo;0;L;;;;;N;;;;;
+11D75;GUNJALA GONDI LETTER LA;Lo;0;L;;;;;N;;;;;
+11D76;GUNJALA GONDI LETTER GA;Lo;0;L;;;;;N;;;;;
+11D77;GUNJALA GONDI LETTER GHA;Lo;0;L;;;;;N;;;;;
+11D78;GUNJALA GONDI LETTER DA;Lo;0;L;;;;;N;;;;;
+11D79;GUNJALA GONDI LETTER DHA;Lo;0;L;;;;;N;;;;;
+11D7A;GUNJALA GONDI LETTER NA;Lo;0;L;;;;;N;;;;;
+11D7B;GUNJALA GONDI LETTER CA;Lo;0;L;;;;;N;;;;;
+11D7C;GUNJALA GONDI LETTER CHA;Lo;0;L;;;;;N;;;;;
+11D7D;GUNJALA GONDI LETTER TTA;Lo;0;L;;;;;N;;;;;
+11D7E;GUNJALA GONDI LETTER TTHA;Lo;0;L;;;;;N;;;;;
+11D7F;GUNJALA GONDI LETTER LLA;Lo;0;L;;;;;N;;;;;
+11D80;GUNJALA GONDI LETTER JA;Lo;0;L;;;;;N;;;;;
+11D81;GUNJALA GONDI LETTER JHA;Lo;0;L;;;;;N;;;;;
+11D82;GUNJALA GONDI LETTER DDA;Lo;0;L;;;;;N;;;;;
+11D83;GUNJALA GONDI LETTER DDHA;Lo;0;L;;;;;N;;;;;
+11D84;GUNJALA GONDI LETTER NGA;Lo;0;L;;;;;N;;;;;
+11D85;GUNJALA GONDI LETTER PA;Lo;0;L;;;;;N;;;;;
+11D86;GUNJALA GONDI LETTER PHA;Lo;0;L;;;;;N;;;;;
+11D87;GUNJALA GONDI LETTER HA;Lo;0;L;;;;;N;;;;;
+11D88;GUNJALA GONDI LETTER RA;Lo;0;L;;;;;N;;;;;
+11D89;GUNJALA GONDI LETTER SA;Lo;0;L;;;;;N;;;;;
+11D8A;GUNJALA GONDI VOWEL SIGN AA;Mc;0;L;;;;;N;;;;;
+11D8B;GUNJALA GONDI VOWEL SIGN I;Mc;0;L;;;;;N;;;;;
+11D8C;GUNJALA GONDI VOWEL SIGN II;Mc;0;L;;;;;N;;;;;
+11D8D;GUNJALA GONDI VOWEL SIGN U;Mc;0;L;;;;;N;;;;;
+11D8E;GUNJALA GONDI VOWEL SIGN UU;Mc;0;L;;;;;N;;;;;
+11D90;GUNJALA GONDI VOWEL SIGN EE;Mn;0;NSM;;;;;N;;;;;
+11D91;GUNJALA GONDI VOWEL SIGN AI;Mn;0;NSM;;;;;N;;;;;
+11D93;GUNJALA GONDI VOWEL SIGN OO;Mc;0;L;;;;;N;;;;;
+11D94;GUNJALA GONDI VOWEL SIGN AU;Mc;0;L;;;;;N;;;;;
+11D95;GUNJALA GONDI SIGN ANUSVARA;Mn;0;NSM;;;;;N;;;;;
+11D96;GUNJALA GONDI SIGN VISARGA;Mc;0;L;;;;;N;;;;;
+11D97;GUNJALA GONDI VIRAMA;Mn;9;NSM;;;;;N;;;;;
+11D98;GUNJALA GONDI OM;Lo;0;L;;;;;N;;;;;
+11DA0;GUNJALA GONDI DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;;
+11DA1;GUNJALA GONDI DIGIT ONE;Nd;0;L;;1;1;1;N;;;;;
+11DA2;GUNJALA GONDI DIGIT TWO;Nd;0;L;;2;2;2;N;;;;;
+11DA3;GUNJALA GONDI DIGIT THREE;Nd;0;L;;3;3;3;N;;;;;
+11DA4;GUNJALA GONDI DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;;
+11DA5;GUNJALA GONDI DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;;
+11DA6;GUNJALA GONDI DIGIT SIX;Nd;0;L;;6;6;6;N;;;;;
+11DA7;GUNJALA GONDI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;;
+11DA8;GUNJALA GONDI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;;
+11DA9;GUNJALA GONDI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;;
+11EE0;MAKASAR LETTER KA;Lo;0;L;;;;;N;;;;;
+11EE1;MAKASAR LETTER GA;Lo;0;L;;;;;N;;;;;
+11EE2;MAKASAR LETTER NGA;Lo;0;L;;;;;N;;;;;
+11EE3;MAKASAR LETTER PA;Lo;0;L;;;;;N;;;;;
+11EE4;MAKASAR LETTER BA;Lo;0;L;;;;;N;;;;;
+11EE5;MAKASAR LETTER MA;Lo;0;L;;;;;N;;;;;
+11EE6;MAKASAR LETTER TA;Lo;0;L;;;;;N;;;;;
+11EE7;MAKASAR LETTER DA;Lo;0;L;;;;;N;;;;;
+11EE8;MAKASAR LETTER NA;Lo;0;L;;;;;N;;;;;
+11EE9;MAKASAR LETTER CA;Lo;0;L;;;;;N;;;;;
+11EEA;MAKASAR LETTER JA;Lo;0;L;;;;;N;;;;;
+11EEB;MAKASAR LETTER NYA;Lo;0;L;;;;;N;;;;;
+11EEC;MAKASAR LETTER YA;Lo;0;L;;;;;N;;;;;
+11EED;MAKASAR LETTER RA;Lo;0;L;;;;;N;;;;;
+11EEE;MAKASAR LETTER LA;Lo;0;L;;;;;N;;;;;
+11EEF;MAKASAR LETTER VA;Lo;0;L;;;;;N;;;;;
+11EF0;MAKASAR LETTER SA;Lo;0;L;;;;;N;;;;;
+11EF1;MAKASAR LETTER A;Lo;0;L;;;;;N;;;;;
+11EF2;MAKASAR ANGKA;Lo;0;L;;;;;N;;;;;
+11EF3;MAKASAR VOWEL SIGN I;Mn;0;NSM;;;;;N;;;;;
+11EF4;MAKASAR VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;;
+11EF5;MAKASAR VOWEL SIGN E;Mc;0;L;;;;;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;;;;;
12000;CUNEIFORM SIGN A;Lo;0;L;;;;;N;;;;;
12001;CUNEIFORM SIGN A TIMES A;Lo;0;L;;;;;N;;;;;
12002;CUNEIFORM SIGN A TIMES BAD;Lo;0;L;;;;;N;;;;;
@@ -24219,6 +24622,97 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
16B8D;PAHAWH HMONG CLAN SIGN TSWB;Lo;0;L;;;;;N;;;;;
16B8E;PAHAWH HMONG CLAN SIGN KWM;Lo;0;L;;;;;N;;;;;
16B8F;PAHAWH HMONG CLAN SIGN VWJ;Lo;0;L;;;;;N;;;;;
+16E40;MEDEFAIDRIN CAPITAL LETTER M;Lu;0;L;;;;;N;;;;16E60;
+16E41;MEDEFAIDRIN CAPITAL LETTER S;Lu;0;L;;;;;N;;;;16E61;
+16E42;MEDEFAIDRIN CAPITAL LETTER V;Lu;0;L;;;;;N;;;;16E62;
+16E43;MEDEFAIDRIN CAPITAL LETTER W;Lu;0;L;;;;;N;;;;16E63;
+16E44;MEDEFAIDRIN CAPITAL LETTER ATIU;Lu;0;L;;;;;N;;;;16E64;
+16E45;MEDEFAIDRIN CAPITAL LETTER Z;Lu;0;L;;;;;N;;;;16E65;
+16E46;MEDEFAIDRIN CAPITAL LETTER KP;Lu;0;L;;;;;N;;;;16E66;
+16E47;MEDEFAIDRIN CAPITAL LETTER P;Lu;0;L;;;;;N;;;;16E67;
+16E48;MEDEFAIDRIN CAPITAL LETTER T;Lu;0;L;;;;;N;;;;16E68;
+16E49;MEDEFAIDRIN CAPITAL LETTER G;Lu;0;L;;;;;N;;;;16E69;
+16E4A;MEDEFAIDRIN CAPITAL LETTER F;Lu;0;L;;;;;N;;;;16E6A;
+16E4B;MEDEFAIDRIN CAPITAL LETTER I;Lu;0;L;;;;;N;;;;16E6B;
+16E4C;MEDEFAIDRIN CAPITAL LETTER K;Lu;0;L;;;;;N;;;;16E6C;
+16E4D;MEDEFAIDRIN CAPITAL LETTER A;Lu;0;L;;;;;N;;;;16E6D;
+16E4E;MEDEFAIDRIN CAPITAL LETTER J;Lu;0;L;;;;;N;;;;16E6E;
+16E4F;MEDEFAIDRIN CAPITAL LETTER E;Lu;0;L;;;;;N;;;;16E6F;
+16E50;MEDEFAIDRIN CAPITAL LETTER B;Lu;0;L;;;;;N;;;;16E70;
+16E51;MEDEFAIDRIN CAPITAL LETTER C;Lu;0;L;;;;;N;;;;16E71;
+16E52;MEDEFAIDRIN CAPITAL LETTER U;Lu;0;L;;;;;N;;;;16E72;
+16E53;MEDEFAIDRIN CAPITAL LETTER YU;Lu;0;L;;;;;N;;;;16E73;
+16E54;MEDEFAIDRIN CAPITAL LETTER L;Lu;0;L;;;;;N;;;;16E74;
+16E55;MEDEFAIDRIN CAPITAL LETTER Q;Lu;0;L;;;;;N;;;;16E75;
+16E56;MEDEFAIDRIN CAPITAL LETTER HP;Lu;0;L;;;;;N;;;;16E76;
+16E57;MEDEFAIDRIN CAPITAL LETTER NY;Lu;0;L;;;;;N;;;;16E77;
+16E58;MEDEFAIDRIN CAPITAL LETTER X;Lu;0;L;;;;;N;;;;16E78;
+16E59;MEDEFAIDRIN CAPITAL LETTER D;Lu;0;L;;;;;N;;;;16E79;
+16E5A;MEDEFAIDRIN CAPITAL LETTER OE;Lu;0;L;;;;;N;;;;16E7A;
+16E5B;MEDEFAIDRIN CAPITAL LETTER N;Lu;0;L;;;;;N;;;;16E7B;
+16E5C;MEDEFAIDRIN CAPITAL LETTER R;Lu;0;L;;;;;N;;;;16E7C;
+16E5D;MEDEFAIDRIN CAPITAL LETTER O;Lu;0;L;;;;;N;;;;16E7D;
+16E5E;MEDEFAIDRIN CAPITAL LETTER AI;Lu;0;L;;;;;N;;;;16E7E;
+16E5F;MEDEFAIDRIN CAPITAL LETTER Y;Lu;0;L;;;;;N;;;;16E7F;
+16E60;MEDEFAIDRIN SMALL LETTER M;Ll;0;L;;;;;N;;;16E40;;16E40
+16E61;MEDEFAIDRIN SMALL LETTER S;Ll;0;L;;;;;N;;;16E41;;16E41
+16E62;MEDEFAIDRIN SMALL LETTER V;Ll;0;L;;;;;N;;;16E42;;16E42
+16E63;MEDEFAIDRIN SMALL LETTER W;Ll;0;L;;;;;N;;;16E43;;16E43
+16E64;MEDEFAIDRIN SMALL LETTER ATIU;Ll;0;L;;;;;N;;;16E44;;16E44
+16E65;MEDEFAIDRIN SMALL LETTER Z;Ll;0;L;;;;;N;;;16E45;;16E45
+16E66;MEDEFAIDRIN SMALL LETTER KP;Ll;0;L;;;;;N;;;16E46;;16E46
+16E67;MEDEFAIDRIN SMALL LETTER P;Ll;0;L;;;;;N;;;16E47;;16E47
+16E68;MEDEFAIDRIN SMALL LETTER T;Ll;0;L;;;;;N;;;16E48;;16E48
+16E69;MEDEFAIDRIN SMALL LETTER G;Ll;0;L;;;;;N;;;16E49;;16E49
+16E6A;MEDEFAIDRIN SMALL LETTER F;Ll;0;L;;;;;N;;;16E4A;;16E4A
+16E6B;MEDEFAIDRIN SMALL LETTER I;Ll;0;L;;;;;N;;;16E4B;;16E4B
+16E6C;MEDEFAIDRIN SMALL LETTER K;Ll;0;L;;;;;N;;;16E4C;;16E4C
+16E6D;MEDEFAIDRIN SMALL LETTER A;Ll;0;L;;;;;N;;;16E4D;;16E4D
+16E6E;MEDEFAIDRIN SMALL LETTER J;Ll;0;L;;;;;N;;;16E4E;;16E4E
+16E6F;MEDEFAIDRIN SMALL LETTER E;Ll;0;L;;;;;N;;;16E4F;;16E4F
+16E70;MEDEFAIDRIN SMALL LETTER B;Ll;0;L;;;;;N;;;16E50;;16E50
+16E71;MEDEFAIDRIN SMALL LETTER C;Ll;0;L;;;;;N;;;16E51;;16E51
+16E72;MEDEFAIDRIN SMALL LETTER U;Ll;0;L;;;;;N;;;16E52;;16E52
+16E73;MEDEFAIDRIN SMALL LETTER YU;Ll;0;L;;;;;N;;;16E53;;16E53
+16E74;MEDEFAIDRIN SMALL LETTER L;Ll;0;L;;;;;N;;;16E54;;16E54
+16E75;MEDEFAIDRIN SMALL LETTER Q;Ll;0;L;;;;;N;;;16E55;;16E55
+16E76;MEDEFAIDRIN SMALL LETTER HP;Ll;0;L;;;;;N;;;16E56;;16E56
+16E77;MEDEFAIDRIN SMALL LETTER NY;Ll;0;L;;;;;N;;;16E57;;16E57
+16E78;MEDEFAIDRIN SMALL LETTER X;Ll;0;L;;;;;N;;;16E58;;16E58
+16E79;MEDEFAIDRIN SMALL LETTER D;Ll;0;L;;;;;N;;;16E59;;16E59
+16E7A;MEDEFAIDRIN SMALL LETTER OE;Ll;0;L;;;;;N;;;16E5A;;16E5A
+16E7B;MEDEFAIDRIN SMALL LETTER N;Ll;0;L;;;;;N;;;16E5B;;16E5B
+16E7C;MEDEFAIDRIN SMALL LETTER R;Ll;0;L;;;;;N;;;16E5C;;16E5C
+16E7D;MEDEFAIDRIN SMALL LETTER O;Ll;0;L;;;;;N;;;16E5D;;16E5D
+16E7E;MEDEFAIDRIN SMALL LETTER AI;Ll;0;L;;;;;N;;;16E5E;;16E5E
+16E7F;MEDEFAIDRIN SMALL LETTER Y;Ll;0;L;;;;;N;;;16E5F;;16E5F
+16E80;MEDEFAIDRIN DIGIT ZERO;No;0;L;;;;0;N;;;;;
+16E81;MEDEFAIDRIN DIGIT ONE;No;0;L;;;;1;N;;;;;
+16E82;MEDEFAIDRIN DIGIT TWO;No;0;L;;;;2;N;;;;;
+16E83;MEDEFAIDRIN DIGIT THREE;No;0;L;;;;3;N;;;;;
+16E84;MEDEFAIDRIN DIGIT FOUR;No;0;L;;;;4;N;;;;;
+16E85;MEDEFAIDRIN DIGIT FIVE;No;0;L;;;;5;N;;;;;
+16E86;MEDEFAIDRIN DIGIT SIX;No;0;L;;;;6;N;;;;;
+16E87;MEDEFAIDRIN DIGIT SEVEN;No;0;L;;;;7;N;;;;;
+16E88;MEDEFAIDRIN DIGIT EIGHT;No;0;L;;;;8;N;;;;;
+16E89;MEDEFAIDRIN DIGIT NINE;No;0;L;;;;9;N;;;;;
+16E8A;MEDEFAIDRIN NUMBER TEN;No;0;L;;;;10;N;;;;;
+16E8B;MEDEFAIDRIN NUMBER ELEVEN;No;0;L;;;;11;N;;;;;
+16E8C;MEDEFAIDRIN NUMBER TWELVE;No;0;L;;;;12;N;;;;;
+16E8D;MEDEFAIDRIN NUMBER THIRTEEN;No;0;L;;;;13;N;;;;;
+16E8E;MEDEFAIDRIN NUMBER FOURTEEN;No;0;L;;;;14;N;;;;;
+16E8F;MEDEFAIDRIN NUMBER FIFTEEN;No;0;L;;;;15;N;;;;;
+16E90;MEDEFAIDRIN NUMBER SIXTEEN;No;0;L;;;;16;N;;;;;
+16E91;MEDEFAIDRIN NUMBER SEVENTEEN;No;0;L;;;;17;N;;;;;
+16E92;MEDEFAIDRIN NUMBER EIGHTEEN;No;0;L;;;;18;N;;;;;
+16E93;MEDEFAIDRIN NUMBER NINETEEN;No;0;L;;;;19;N;;;;;
+16E94;MEDEFAIDRIN DIGIT ONE ALTERNATE FORM;No;0;L;;;;1;N;;;;;
+16E95;MEDEFAIDRIN DIGIT TWO ALTERNATE FORM;No;0;L;;;;2;N;;;;;
+16E96;MEDEFAIDRIN DIGIT THREE ALTERNATE FORM;No;0;L;;;;3;N;;;;;
+16E97;MEDEFAIDRIN COMMA;Po;0;L;;;;;N;;;;;
+16E98;MEDEFAIDRIN FULL STOP;Po;0;L;;;;;N;;;;;
+16E99;MEDEFAIDRIN SYMBOL AIVA;Po;0;L;;;;;N;;;;;
+16E9A;MEDEFAIDRIN EXCLAMATION OH;Po;0;L;;;;;N;;;;;
16F00;MIAO LETTER PA;Lo;0;L;;;;;N;;;;;
16F01;MIAO LETTER BA;Lo;0;L;;;;;N;;;;;
16F02;MIAO LETTER YI PA;Lo;0;L;;;;;N;;;;;
@@ -24355,7 +24849,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
16FE0;TANGUT ITERATION MARK;Lm;0;L;;;;;N;;;;;
16FE1;NUSHU ITERATION MARK;Lm;0;L;;;;;N;;;;;
17000;<Tangut Ideograph, First>;Lo;0;L;;;;;N;;;;;
-187EC;<Tangut Ideograph, Last>;Lo;0;L;;;;;N;;;;;
+187F1;<Tangut Ideograph, Last>;Lo;0;L;;;;;N;;;;;
18800;TANGUT COMPONENT-001;Lo;0;L;;;;;N;;;;;
18801;TANGUT COMPONENT-002;Lo;0;L;;;;;N;;;;;
18802;TANGUT COMPONENT-003;Lo;0;L;;;;;N;;;;;
@@ -26488,6 +26982,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;;;;;
+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;;;;;
+1D2E3;MAYAN NUMERAL THREE;No;0;L;;;;3;N;;;;;
+1D2E4;MAYAN NUMERAL FOUR;No;0;L;;;;4;N;;;;;
+1D2E5;MAYAN NUMERAL FIVE;No;0;L;;;;5;N;;;;;
+1D2E6;MAYAN NUMERAL SIX;No;0;L;;;;6;N;;;;;
+1D2E7;MAYAN NUMERAL SEVEN;No;0;L;;;;7;N;;;;;
+1D2E8;MAYAN NUMERAL EIGHT;No;0;L;;;;8;N;;;;;
+1D2E9;MAYAN NUMERAL NINE;No;0;L;;;;9;N;;;;;
+1D2EA;MAYAN NUMERAL TEN;No;0;L;;;;10;N;;;;;
+1D2EB;MAYAN NUMERAL ELEVEN;No;0;L;;;;11;N;;;;;
+1D2EC;MAYAN NUMERAL TWELVE;No;0;L;;;;12;N;;;;;
+1D2ED;MAYAN NUMERAL THIRTEEN;No;0;L;;;;13;N;;;;;
+1D2EE;MAYAN NUMERAL FOURTEEN;No;0;L;;;;14;N;;;;;
+1D2EF;MAYAN NUMERAL FIFTEEN;No;0;L;;;;15;N;;;;;
+1D2F0;MAYAN NUMERAL SIXTEEN;No;0;L;;;;16;N;;;;;
+1D2F1;MAYAN NUMERAL SEVENTEEN;No;0;L;;;;17;N;;;;;
+1D2F2;MAYAN NUMERAL EIGHTEEN;No;0;L;;;;18;N;;;;;
+1D2F3;MAYAN NUMERAL NINETEEN;No;0;L;;;;19;N;;;;;
1D300;MONOGRAM FOR EARTH;So;0;ON;;;;;N;;;;;
1D301;DIGRAM FOR HEAVENLY EARTH;So;0;ON;;;;;N;;;;;
1D302;DIGRAM FOR HUMAN EARTH;So;0;ON;;;;;N;;;;;
@@ -26593,6 +27107,13 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1D36F;COUNTING ROD TENS DIGIT SEVEN;No;0;L;;;;70;N;;;;;
1D370;COUNTING ROD TENS DIGIT EIGHT;No;0;L;;;;80;N;;;;;
1D371;COUNTING ROD TENS DIGIT NINE;No;0;L;;;;90;N;;;;;
+1D372;IDEOGRAPHIC TALLY MARK ONE;No;0;L;;;;1;N;;;;;
+1D373;IDEOGRAPHIC TALLY MARK TWO;No;0;L;;;;2;N;;;;;
+1D374;IDEOGRAPHIC TALLY MARK THREE;No;0;L;;;;3;N;;;;;
+1D375;IDEOGRAPHIC TALLY MARK FOUR;No;0;L;;;;4;N;;;;;
+1D376;IDEOGRAPHIC TALLY MARK FIVE;No;0;L;;;;5;N;;;;;
+1D377;TALLY MARK ONE;No;0;L;;;;1;N;;;;;
+1D378;TALLY MARK FIVE;No;0;L;;;;5;N;;;;;
1D400;MATHEMATICAL BOLD CAPITAL A;Lu;0;L;<font> 0041;;;;N;;;;;
1D401;MATHEMATICAL BOLD CAPITAL B;Lu;0;L;<font> 0042;;;;N;;;;;
1D402;MATHEMATICAL BOLD CAPITAL C;Lu;0;L;<font> 0043;;;;N;;;;;
@@ -28599,6 +29120,74 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1E959;ADLAM DIGIT NINE;Nd;0;R;;9;9;9;N;;;;;
1E95E;ADLAM INITIAL EXCLAMATION MARK;Po;0;R;;;;;N;;;;;
1E95F;ADLAM INITIAL QUESTION MARK;Po;0;R;;;;;N;;;;;
+1EC71;INDIC SIYAQ NUMBER ONE;No;0;AL;;;;1;N;;;;;
+1EC72;INDIC SIYAQ NUMBER TWO;No;0;AL;;;;2;N;;;;;
+1EC73;INDIC SIYAQ NUMBER THREE;No;0;AL;;;;3;N;;;;;
+1EC74;INDIC SIYAQ NUMBER FOUR;No;0;AL;;;;4;N;;;;;
+1EC75;INDIC SIYAQ NUMBER FIVE;No;0;AL;;;;5;N;;;;;
+1EC76;INDIC SIYAQ NUMBER SIX;No;0;AL;;;;6;N;;;;;
+1EC77;INDIC SIYAQ NUMBER SEVEN;No;0;AL;;;;7;N;;;;;
+1EC78;INDIC SIYAQ NUMBER EIGHT;No;0;AL;;;;8;N;;;;;
+1EC79;INDIC SIYAQ NUMBER NINE;No;0;AL;;;;9;N;;;;;
+1EC7A;INDIC SIYAQ NUMBER TEN;No;0;AL;;;;10;N;;;;;
+1EC7B;INDIC SIYAQ NUMBER TWENTY;No;0;AL;;;;20;N;;;;;
+1EC7C;INDIC SIYAQ NUMBER THIRTY;No;0;AL;;;;30;N;;;;;
+1EC7D;INDIC SIYAQ NUMBER FORTY;No;0;AL;;;;40;N;;;;;
+1EC7E;INDIC SIYAQ NUMBER FIFTY;No;0;AL;;;;50;N;;;;;
+1EC7F;INDIC SIYAQ NUMBER SIXTY;No;0;AL;;;;60;N;;;;;
+1EC80;INDIC SIYAQ NUMBER SEVENTY;No;0;AL;;;;70;N;;;;;
+1EC81;INDIC SIYAQ NUMBER EIGHTY;No;0;AL;;;;80;N;;;;;
+1EC82;INDIC SIYAQ NUMBER NINETY;No;0;AL;;;;90;N;;;;;
+1EC83;INDIC SIYAQ NUMBER ONE HUNDRED;No;0;AL;;;;100;N;;;;;
+1EC84;INDIC SIYAQ NUMBER TWO HUNDRED;No;0;AL;;;;200;N;;;;;
+1EC85;INDIC SIYAQ NUMBER THREE HUNDRED;No;0;AL;;;;300;N;;;;;
+1EC86;INDIC SIYAQ NUMBER FOUR HUNDRED;No;0;AL;;;;400;N;;;;;
+1EC87;INDIC SIYAQ NUMBER FIVE HUNDRED;No;0;AL;;;;500;N;;;;;
+1EC88;INDIC SIYAQ NUMBER SIX HUNDRED;No;0;AL;;;;600;N;;;;;
+1EC89;INDIC SIYAQ NUMBER SEVEN HUNDRED;No;0;AL;;;;700;N;;;;;
+1EC8A;INDIC SIYAQ NUMBER EIGHT HUNDRED;No;0;AL;;;;800;N;;;;;
+1EC8B;INDIC SIYAQ NUMBER NINE HUNDRED;No;0;AL;;;;900;N;;;;;
+1EC8C;INDIC SIYAQ NUMBER ONE THOUSAND;No;0;AL;;;;1000;N;;;;;
+1EC8D;INDIC SIYAQ NUMBER TWO THOUSAND;No;0;AL;;;;2000;N;;;;;
+1EC8E;INDIC SIYAQ NUMBER THREE THOUSAND;No;0;AL;;;;3000;N;;;;;
+1EC8F;INDIC SIYAQ NUMBER FOUR THOUSAND;No;0;AL;;;;4000;N;;;;;
+1EC90;INDIC SIYAQ NUMBER FIVE THOUSAND;No;0;AL;;;;5000;N;;;;;
+1EC91;INDIC SIYAQ NUMBER SIX THOUSAND;No;0;AL;;;;6000;N;;;;;
+1EC92;INDIC SIYAQ NUMBER SEVEN THOUSAND;No;0;AL;;;;7000;N;;;;;
+1EC93;INDIC SIYAQ NUMBER EIGHT THOUSAND;No;0;AL;;;;8000;N;;;;;
+1EC94;INDIC SIYAQ NUMBER NINE THOUSAND;No;0;AL;;;;9000;N;;;;;
+1EC95;INDIC SIYAQ NUMBER TEN THOUSAND;No;0;AL;;;;10000;N;;;;;
+1EC96;INDIC SIYAQ NUMBER TWENTY THOUSAND;No;0;AL;;;;20000;N;;;;;
+1EC97;INDIC SIYAQ NUMBER THIRTY THOUSAND;No;0;AL;;;;30000;N;;;;;
+1EC98;INDIC SIYAQ NUMBER FORTY THOUSAND;No;0;AL;;;;40000;N;;;;;
+1EC99;INDIC SIYAQ NUMBER FIFTY THOUSAND;No;0;AL;;;;50000;N;;;;;
+1EC9A;INDIC SIYAQ NUMBER SIXTY THOUSAND;No;0;AL;;;;60000;N;;;;;
+1EC9B;INDIC SIYAQ NUMBER SEVENTY THOUSAND;No;0;AL;;;;70000;N;;;;;
+1EC9C;INDIC SIYAQ NUMBER EIGHTY THOUSAND;No;0;AL;;;;80000;N;;;;;
+1EC9D;INDIC SIYAQ NUMBER NINETY THOUSAND;No;0;AL;;;;90000;N;;;;;
+1EC9E;INDIC SIYAQ NUMBER LAKH;No;0;AL;;;;100000;N;;;;;
+1EC9F;INDIC SIYAQ NUMBER LAKHAN;No;0;AL;;;;200000;N;;;;;
+1ECA0;INDIC SIYAQ LAKH MARK;No;0;AL;;;;100000;N;;;;;
+1ECA1;INDIC SIYAQ NUMBER KAROR;No;0;AL;;;;10000000;N;;;;;
+1ECA2;INDIC SIYAQ NUMBER KARORAN;No;0;AL;;;;20000000;N;;;;;
+1ECA3;INDIC SIYAQ NUMBER PREFIXED ONE;No;0;AL;;;;1;N;;;;;
+1ECA4;INDIC SIYAQ NUMBER PREFIXED TWO;No;0;AL;;;;2;N;;;;;
+1ECA5;INDIC SIYAQ NUMBER PREFIXED THREE;No;0;AL;;;;3;N;;;;;
+1ECA6;INDIC SIYAQ NUMBER PREFIXED FOUR;No;0;AL;;;;4;N;;;;;
+1ECA7;INDIC SIYAQ NUMBER PREFIXED FIVE;No;0;AL;;;;5;N;;;;;
+1ECA8;INDIC SIYAQ NUMBER PREFIXED SIX;No;0;AL;;;;6;N;;;;;
+1ECA9;INDIC SIYAQ NUMBER PREFIXED SEVEN;No;0;AL;;;;7;N;;;;;
+1ECAA;INDIC SIYAQ NUMBER PREFIXED EIGHT;No;0;AL;;;;8;N;;;;;
+1ECAB;INDIC SIYAQ NUMBER PREFIXED NINE;No;0;AL;;;;9;N;;;;;
+1ECAC;INDIC SIYAQ PLACEHOLDER;So;0;AL;;;;;N;;;;;
+1ECAD;INDIC SIYAQ FRACTION ONE QUARTER;No;0;AL;;;;1/4;N;;;;;
+1ECAE;INDIC SIYAQ FRACTION ONE HALF;No;0;AL;;;;1/2;N;;;;;
+1ECAF;INDIC SIYAQ FRACTION THREE QUARTERS;No;0;AL;;;;3/4;N;;;;;
+1ECB0;INDIC SIYAQ RUPEE MARK;Sc;0;AL;;;;;N;;;;;
+1ECB1;INDIC SIYAQ NUMBER ALTERNATE ONE;No;0;AL;;;;1;N;;;;;
+1ECB2;INDIC SIYAQ NUMBER ALTERNATE TWO;No;0;AL;;;;2;N;;;;;
+1ECB3;INDIC SIYAQ NUMBER ALTERNATE TEN THOUSAND;No;0;AL;;;;10000;N;;;;;
+1ECB4;INDIC SIYAQ ALTERNATE LAKH MARK;No;0;AL;;;;100000;N;;;;;
1EE00;ARABIC MATHEMATICAL ALEF;Lo;0;AL;<font> 0627;;;;N;;;;;
1EE01;ARABIC MATHEMATICAL BEH;Lo;0;AL;<font> 0628;;;;N;;;;;
1EE02;ARABIC MATHEMATICAL JEEM;Lo;0;AL;<font> 062C;;;;N;;;;;
@@ -29012,6 +29601,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1F12C;CIRCLED ITALIC LATIN CAPITAL LETTER R;So;0;L;<circle> 0052;;;;N;;;;;
1F12D;CIRCLED CD;So;0;L;<circle> 0043 0044;;;;N;;;;;
1F12E;CIRCLED WZ;So;0;L;<circle> 0057 005A;;;;N;;;;;
+1F12F;COPYLEFT SYMBOL;So;0;ON;;;;;N;;;;;
1F130;SQUARED LATIN CAPITAL LETTER A;So;0;L;<square> 0041;;;;N;;;;;
1F131;SQUARED LATIN CAPITAL LETTER B;So;0;L;<square> 0042;;;;N;;;;;
1F132;SQUARED LATIN CAPITAL LETTER C;So;0;L;<square> 0043;;;;N;;;;;
@@ -30226,6 +30816,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1F6F6;CANOE;So;0;ON;;;;;N;;;;;
1F6F7;SLED;So;0;ON;;;;;N;;;;;
1F6F8;FLYING SAUCER;So;0;ON;;;;;N;;;;;
+1F6F9;SKATEBOARD;So;0;ON;;;;;N;;;;;
1F700;ALCHEMICAL SYMBOL FOR QUINTESSENCE;So;0;ON;;;;;N;;;;;
1F701;ALCHEMICAL SYMBOL FOR AIR;So;0;ON;;;;;N;;;;;
1F702;ALCHEMICAL SYMBOL FOR FIRE;So;0;ON;;;;;N;;;;;
@@ -30427,6 +31018,10 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1F7D2;LIGHT TWELVE POINTED BLACK STAR;So;0;ON;;;;;N;;;;;
1F7D3;HEAVY TWELVE POINTED BLACK STAR;So;0;ON;;;;;N;;;;;
1F7D4;HEAVY TWELVE POINTED PINWHEEL STAR;So;0;ON;;;;;N;;;;;
+1F7D5;CIRCLED TRIANGLE;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;;;;;
1F800;LEFTWARDS ARROW WITH SMALL TRIANGLE ARROWHEAD;So;0;ON;;;;;N;;;;;
1F801;UPWARDS ARROW WITH SMALL TRIANGLE ARROWHEAD;So;0;ON;;;;;N;;;;;
1F802;RIGHTWARDS ARROW WITH SMALL TRIANGLE ARROWHEAD;So;0;ON;;;;;N;;;;;
@@ -30647,6 +31242,9 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1F94A;BOXING GLOVE;So;0;ON;;;;;N;;;;;
1F94B;MARTIAL ARTS UNIFORM;So;0;ON;;;;;N;;;;;
1F94C;CURLING STONE;So;0;ON;;;;;N;;;;;
+1F94D;LACROSSE STICK AND BALL;So;0;ON;;;;;N;;;;;
+1F94E;SOFTBALL;So;0;ON;;;;;N;;;;;
+1F94F;FLYING DISC;So;0;ON;;;;;N;;;;;
1F950;CROISSANT;So;0;ON;;;;;N;;;;;
1F951;AVOCADO;So;0;ON;;;;;N;;;;;
1F952;CUCUMBER;So;0;ON;;;;;N;;;;;
@@ -30675,6 +31273,20 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1F969;CUT OF MEAT;So;0;ON;;;;;N;;;;;
1F96A;SANDWICH;So;0;ON;;;;;N;;;;;
1F96B;CANNED FOOD;So;0;ON;;;;;N;;;;;
+1F96C;LEAFY GREEN;So;0;ON;;;;;N;;;;;
+1F96D;MANGO;So;0;ON;;;;;N;;;;;
+1F96E;MOON CAKE;So;0;ON;;;;;N;;;;;
+1F96F;BAGEL;So;0;ON;;;;;N;;;;;
+1F970;SMILING FACE WITH SMILING EYES AND THREE HEARTS;So;0;ON;;;;;N;;;;;
+1F973;FACE WITH PARTY HORN AND PARTY HAT;So;0;ON;;;;;N;;;;;
+1F974;FACE WITH UNEVEN EYES AND WAVY MOUTH;So;0;ON;;;;;N;;;;;
+1F975;OVERHEATED FACE;So;0;ON;;;;;N;;;;;
+1F976;FREEZING FACE;So;0;ON;;;;;N;;;;;
+1F97A;FACE WITH PLEADING EYES;So;0;ON;;;;;N;;;;;
+1F97C;LAB COAT;So;0;ON;;;;;N;;;;;
+1F97D;GOGGLES;So;0;ON;;;;;N;;;;;
+1F97E;HIKING BOOT;So;0;ON;;;;;N;;;;;
+1F97F;FLAT SHOE;So;0;ON;;;;;N;;;;;
1F980;CRAB;So;0;ON;;;;;N;;;;;
1F981;LION FACE;So;0;ON;;;;;N;;;;;
1F982;SCORPION;So;0;ON;;;;;N;;;;;
@@ -30699,7 +31311,30 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1F995;SAUROPOD;So;0;ON;;;;;N;;;;;
1F996;T-REX;So;0;ON;;;;;N;;;;;
1F997;CRICKET;So;0;ON;;;;;N;;;;;
+1F998;KANGAROO;So;0;ON;;;;;N;;;;;
+1F999;LLAMA;So;0;ON;;;;;N;;;;;
+1F99A;PEACOCK;So;0;ON;;;;;N;;;;;
+1F99B;HIPPOPOTAMUS;So;0;ON;;;;;N;;;;;
+1F99C;PARROT;So;0;ON;;;;;N;;;;;
+1F99D;RACCOON;So;0;ON;;;;;N;;;;;
+1F99E;LOBSTER;So;0;ON;;;;;N;;;;;
+1F99F;MOSQUITO;So;0;ON;;;;;N;;;;;
+1F9A0;MICROBE;So;0;ON;;;;;N;;;;;
+1F9A1;BADGER;So;0;ON;;;;;N;;;;;
+1F9A2;SWAN;So;0;ON;;;;;N;;;;;
+1F9B0;EMOJI COMPONENT RED HAIR;So;0;ON;;;;;N;;;;;
+1F9B1;EMOJI COMPONENT CURLY HAIR;So;0;ON;;;;;N;;;;;
+1F9B2;EMOJI COMPONENT BALD;So;0;ON;;;;;N;;;;;
+1F9B3;EMOJI COMPONENT WHITE HAIR;So;0;ON;;;;;N;;;;;
+1F9B4;BONE;So;0;ON;;;;;N;;;;;
+1F9B5;LEG;So;0;ON;;;;;N;;;;;
+1F9B6;FOOT;So;0;ON;;;;;N;;;;;
+1F9B7;TOOTH;So;0;ON;;;;;N;;;;;
+1F9B8;SUPERHERO;So;0;ON;;;;;N;;;;;
+1F9B9;SUPERVILLAIN;So;0;ON;;;;;N;;;;;
1F9C0;CHEESE WEDGE;So;0;ON;;;;;N;;;;;
+1F9C1;CUPCAKE;So;0;ON;;;;;N;;;;;
+1F9C2;SALT SHAKER;So;0;ON;;;;;N;;;;;
1F9D0;FACE WITH MONOCLE;So;0;ON;;;;;N;;;;;
1F9D1;ADULT;So;0;ON;;;;;N;;;;;
1F9D2;CHILD;So;0;ON;;;;;N;;;;;
@@ -30723,6 +31358,45 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;;
1F9E4;GLOVES;So;0;ON;;;;;N;;;;;
1F9E5;COAT;So;0;ON;;;;;N;;;;;
1F9E6;SOCKS;So;0;ON;;;;;N;;;;;
+1F9E7;RED GIFT ENVELOPE;So;0;ON;;;;;N;;;;;
+1F9E8;FIRECRACKER;So;0;ON;;;;;N;;;;;
+1F9E9;JIGSAW PUZZLE PIECE;So;0;ON;;;;;N;;;;;
+1F9EA;TEST TUBE;So;0;ON;;;;;N;;;;;
+1F9EB;PETRI DISH;So;0;ON;;;;;N;;;;;
+1F9EC;DNA DOUBLE HELIX;So;0;ON;;;;;N;;;;;
+1F9ED;COMPASS;So;0;ON;;;;;N;;;;;
+1F9EE;ABACUS;So;0;ON;;;;;N;;;;;
+1F9EF;FIRE EXTINGUISHER;So;0;ON;;;;;N;;;;;
+1F9F0;TOOLBOX;So;0;ON;;;;;N;;;;;
+1F9F1;BRICK;So;0;ON;;;;;N;;;;;
+1F9F2;MAGNET;So;0;ON;;;;;N;;;;;
+1F9F3;LUGGAGE;So;0;ON;;;;;N;;;;;
+1F9F4;LOTION BOTTLE;So;0;ON;;;;;N;;;;;
+1F9F5;SPOOL OF THREAD;So;0;ON;;;;;N;;;;;
+1F9F6;BALL OF YARN;So;0;ON;;;;;N;;;;;
+1F9F7;SAFETY PIN;So;0;ON;;;;;N;;;;;
+1F9F8;TEDDY BEAR;So;0;ON;;;;;N;;;;;
+1F9F9;BROOM;So;0;ON;;;;;N;;;;;
+1F9FA;BASKET;So;0;ON;;;;;N;;;;;
+1F9FB;ROLL OF PAPER;So;0;ON;;;;;N;;;;;
+1F9FC;BAR OF SOAP;So;0;ON;;;;;N;;;;;
+1F9FD;SPONGE;So;0;ON;;;;;N;;;;;
+1F9FE;RECEIPT;So;0;ON;;;;;N;;;;;
+1F9FF;NAZAR AMULET;So;0;ON;;;;;N;;;;;
+1FA60;XIANGQI RED GENERAL;So;0;ON;;;;;N;;;;;
+1FA61;XIANGQI RED MANDARIN;So;0;ON;;;;;N;;;;;
+1FA62;XIANGQI RED ELEPHANT;So;0;ON;;;;;N;;;;;
+1FA63;XIANGQI RED HORSE;So;0;ON;;;;;N;;;;;
+1FA64;XIANGQI RED CHARIOT;So;0;ON;;;;;N;;;;;
+1FA65;XIANGQI RED CANNON;So;0;ON;;;;;N;;;;;
+1FA66;XIANGQI RED SOLDIER;So;0;ON;;;;;N;;;;;
+1FA67;XIANGQI BLACK GENERAL;So;0;ON;;;;;N;;;;;
+1FA68;XIANGQI BLACK MANDARIN;So;0;ON;;;;;N;;;;;
+1FA69;XIANGQI BLACK ELEPHANT;So;0;ON;;;;;N;;;;;
+1FA6A;XIANGQI BLACK HORSE;So;0;ON;;;;;N;;;;;
+1FA6B;XIANGQI BLACK CHARIOT;So;0;ON;;;;;N;;;;;
+1FA6C;XIANGQI BLACK CANNON;So;0;ON;;;;;N;;;;;
+1FA6D;XIANGQI BLACK SOLDIER;So;0;ON;;;;;N;;;;;
20000;<CJK Ideograph Extension B, First>;Lo;0;L;;;;;N;;;;;
2A6D6;<CJK Ideograph Extension B, Last>;Lo;0;L;;;;;N;;;;;
2A700;<CJK Ideograph Extension C, First>;Lo;0;L;;;;;N;;;;;
diff --git a/lib/stdlib/uc_spec/emoji-data.txt b/lib/stdlib/uc_spec/emoji-data.txt
new file mode 100644
index 0000000000..6e66455252
--- /dev/null
+++ b/lib/stdlib/uc_spec/emoji-data.txt
@@ -0,0 +1,714 @@
+# emoji-data.txt
+# Date: 2018-02-07, 07:55:18 GMT
+# © 2018 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
+#
+# Emoji Data for UTS #51
+# Version: 11.0
+#
+# For documentation and usage, see http://www.unicode.org/reports/tr51
+#
+# Format:
+# <codepoint(s)> ; <property> # <comments>
+# Note: there is no guarantee as to the structure of whitespace or comments
+#
+# Characters and sequences are listed in code point order. Users should be shown a more natural order.
+# See the CLDR collation order for Emoji.
+
+
+# ================================================
+
+# All omitted code points have Emoji=No
+# @missing: 0000..10FFFF ; Emoji ; No
+
+0023 ; Emoji # 1.1 [1] (#️) number sign
+002A ; Emoji # 1.1 [1] (*️) asterisk
+0030..0039 ; Emoji # 1.1 [10] (0️..9️) digit zero..digit nine
+00A9 ; Emoji # 1.1 [1] (©️) copyright
+00AE ; Emoji # 1.1 [1] (®️) registered
+203C ; Emoji # 1.1 [1] (‼️) double exclamation mark
+2049 ; Emoji # 3.0 [1] (⁉️) exclamation question mark
+2122 ; Emoji # 1.1 [1] (™️) trade mark
+2139 ; Emoji # 3.0 [1] (ℹ️) information
+2194..2199 ; Emoji # 1.1 [6] (↔️..↙️) left-right arrow..down-left arrow
+21A9..21AA ; Emoji # 1.1 [2] (↩️..↪️) right arrow curving left..left arrow curving right
+231A..231B ; Emoji # 1.1 [2] (⌚..⌛) watch..hourglass done
+2328 ; Emoji # 1.1 [1] (⌨️) keyboard
+23CF ; Emoji # 4.0 [1] (⏏️) eject button
+23E9..23F3 ; Emoji # 6.0 [11] (⏩..⏳) fast-forward button..hourglass not done
+23F8..23FA ; Emoji # 7.0 [3] (⏸️..⏺️) pause button..record button
+24C2 ; Emoji # 1.1 [1] (Ⓜ️) circled M
+25AA..25AB ; Emoji # 1.1 [2] (▪️..▫️) black small square..white small square
+25B6 ; Emoji # 1.1 [1] (▶️) play button
+25C0 ; Emoji # 1.1 [1] (◀️) reverse button
+25FB..25FE ; Emoji # 3.2 [4] (◻️..◾) white medium square..black medium-small square
+2600..2604 ; Emoji # 1.1 [5] (☀️..☄️) sun..comet
+260E ; Emoji # 1.1 [1] (☎️) telephone
+2611 ; Emoji # 1.1 [1] (☑️) ballot box with check
+2614..2615 ; Emoji # 4.0 [2] (☔..☕) umbrella with rain drops..hot beverage
+2618 ; Emoji # 4.1 [1] (☘️) shamrock
+261D ; Emoji # 1.1 [1] (☝️) index pointing up
+2620 ; Emoji # 1.1 [1] (☠️) skull and crossbones
+2622..2623 ; Emoji # 1.1 [2] (☢️..☣️) radioactive..biohazard
+2626 ; Emoji # 1.1 [1] (☦️) orthodox cross
+262A ; Emoji # 1.1 [1] (☪️) star and crescent
+262E..262F ; Emoji # 1.1 [2] (☮️..☯️) peace symbol..yin yang
+2638..263A ; Emoji # 1.1 [3] (☸️..☺️) wheel of dharma..smiling face
+2640 ; Emoji # 1.1 [1] (♀️) female sign
+2642 ; Emoji # 1.1 [1] (♂️) male sign
+2648..2653 ; Emoji # 1.1 [12] (♈..♓) Aries..Pisces
+265F..2660 ; Emoji # 1.1 [2] (♟️..♠️) chess pawn..spade suit
+2663 ; Emoji # 1.1 [1] (♣️) club suit
+2665..2666 ; Emoji # 1.1 [2] (♥️..♦️) heart suit..diamond suit
+2668 ; Emoji # 1.1 [1] (♨️) hot springs
+267B ; Emoji # 3.2 [1] (♻️) recycling symbol
+267E..267F ; Emoji # 4.1 [2] (♾️..♿) infinity..wheelchair symbol
+2692..2697 ; Emoji # 4.1 [6] (⚒️..⚗️) hammer and pick..alembic
+2699 ; Emoji # 4.1 [1] (⚙️) gear
+269B..269C ; Emoji # 4.1 [2] (⚛️..⚜️) atom symbol..fleur-de-lis
+26A0..26A1 ; Emoji # 4.0 [2] (⚠️..⚡) warning..high voltage
+26AA..26AB ; Emoji # 4.1 [2] (⚪..⚫) white circle..black circle
+26B0..26B1 ; Emoji # 4.1 [2] (⚰️..⚱️) coffin..funeral urn
+26BD..26BE ; Emoji # 5.2 [2] (⚽..⚾) soccer ball..baseball
+26C4..26C5 ; Emoji # 5.2 [2] (⛄..⛅) snowman without snow..sun behind cloud
+26C8 ; Emoji # 5.2 [1] (⛈️) cloud with lightning and rain
+26CE ; Emoji # 6.0 [1] (⛎) Ophiuchus
+26CF ; Emoji # 5.2 [1] (⛏️) pick
+26D1 ; Emoji # 5.2 [1] (⛑️) rescue worker’s helmet
+26D3..26D4 ; Emoji # 5.2 [2] (⛓️..⛔) chains..no entry
+26E9..26EA ; Emoji # 5.2 [2] (⛩️..⛪) shinto shrine..church
+26F0..26F5 ; Emoji # 5.2 [6] (⛰️..⛵) mountain..sailboat
+26F7..26FA ; Emoji # 5.2 [4] (⛷️..⛺) skier..tent
+26FD ; Emoji # 5.2 [1] (⛽) fuel pump
+2702 ; Emoji # 1.1 [1] (✂️) scissors
+2705 ; Emoji # 6.0 [1] (✅) white heavy check mark
+2708..2709 ; Emoji # 1.1 [2] (✈️..✉️) airplane..envelope
+270A..270B ; Emoji # 6.0 [2] (✊..✋) raised fist..raised hand
+270C..270D ; Emoji # 1.1 [2] (✌️..✍️) victory hand..writing hand
+270F ; Emoji # 1.1 [1] (✏️) pencil
+2712 ; Emoji # 1.1 [1] (✒️) black nib
+2714 ; Emoji # 1.1 [1] (✔️) heavy check mark
+2716 ; Emoji # 1.1 [1] (✖️) heavy multiplication x
+271D ; Emoji # 1.1 [1] (✝️) latin cross
+2721 ; Emoji # 1.1 [1] (✡️) star of David
+2728 ; Emoji # 6.0 [1] (✨) sparkles
+2733..2734 ; Emoji # 1.1 [2] (✳️..✴️) eight-spoked asterisk..eight-pointed star
+2744 ; Emoji # 1.1 [1] (❄️) snowflake
+2747 ; Emoji # 1.1 [1] (❇️) sparkle
+274C ; Emoji # 6.0 [1] (❌) cross mark
+274E ; Emoji # 6.0 [1] (❎) cross mark button
+2753..2755 ; Emoji # 6.0 [3] (❓..❕) question mark..white exclamation mark
+2757 ; Emoji # 5.2 [1] (❗) exclamation mark
+2763..2764 ; Emoji # 1.1 [2] (❣️..❤️) heavy heart exclamation..red heart
+2795..2797 ; Emoji # 6.0 [3] (➕..➗) heavy plus sign..heavy division sign
+27A1 ; Emoji # 1.1 [1] (➡️) right arrow
+27B0 ; Emoji # 6.0 [1] (➰) curly loop
+27BF ; Emoji # 6.0 [1] (➿) double curly loop
+2934..2935 ; Emoji # 3.2 [2] (⤴️..⤵️) right arrow curving up..right arrow curving down
+2B05..2B07 ; Emoji # 4.0 [3] (⬅️..⬇️) left arrow..down arrow
+2B1B..2B1C ; Emoji # 5.1 [2] (⬛..⬜) black large square..white large square
+2B50 ; Emoji # 5.1 [1] (⭐) star
+2B55 ; Emoji # 5.2 [1] (⭕) heavy large circle
+3030 ; Emoji # 1.1 [1] (〰️) wavy dash
+303D ; Emoji # 3.2 [1] (〽️) part alternation mark
+3297 ; Emoji # 1.1 [1] (㊗️) Japanese “congratulations” button
+3299 ; Emoji # 1.1 [1] (㊙️) Japanese “secret” button
+1F004 ; Emoji # 5.1 [1] (🀄) mahjong red dragon
+1F0CF ; Emoji # 6.0 [1] (🃏) joker
+1F170..1F171 ; Emoji # 6.0 [2] (🅰️..🅱️) A button (blood type)..B button (blood type)
+1F17E ; Emoji # 6.0 [1] (🅾️) O button (blood type)
+1F17F ; Emoji # 5.2 [1] (🅿️) P button
+1F18E ; Emoji # 6.0 [1] (🆎) AB button (blood type)
+1F191..1F19A ; Emoji # 6.0 [10] (🆑..🆚) CL button..VS button
+1F1E6..1F1FF ; Emoji # 6.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z
+1F201..1F202 ; Emoji # 6.0 [2] (🈁..🈂️) Japanese “here” button..Japanese “service charge” button
+1F21A ; Emoji # 5.2 [1] (🈚) Japanese “free of charge” button
+1F22F ; Emoji # 5.2 [1] (🈯) Japanese “reserved” button
+1F232..1F23A ; Emoji # 6.0 [9] (🈲..🈺) Japanese “prohibited” button..Japanese “open for business” button
+1F250..1F251 ; Emoji # 6.0 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button
+1F300..1F320 ; Emoji # 6.0 [33] (🌀..🌠) cyclone..shooting star
+1F321 ; Emoji # 7.0 [1] (🌡️) thermometer
+1F324..1F32C ; Emoji # 7.0 [9] (🌤️..🌬️) sun behind small cloud..wind face
+1F32D..1F32F ; Emoji # 8.0 [3] (🌭..🌯) hot dog..burrito
+1F330..1F335 ; Emoji # 6.0 [6] (🌰..🌵) chestnut..cactus
+1F336 ; Emoji # 7.0 [1] (🌶️) hot pepper
+1F337..1F37C ; Emoji # 6.0 [70] (🌷..🍼) tulip..baby bottle
+1F37D ; Emoji # 7.0 [1] (🍽️) fork and knife with plate
+1F37E..1F37F ; Emoji # 8.0 [2] (🍾..🍿) bottle with popping cork..popcorn
+1F380..1F393 ; Emoji # 6.0 [20] (🎀..🎓) ribbon..graduation cap
+1F396..1F397 ; Emoji # 7.0 [2] (🎖️..🎗️) military medal..reminder ribbon
+1F399..1F39B ; Emoji # 7.0 [3] (🎙️..🎛️) studio microphone..control knobs
+1F39E..1F39F ; Emoji # 7.0 [2] (🎞️..🎟️) film frames..admission tickets
+1F3A0..1F3C4 ; Emoji # 6.0 [37] (🎠..🏄) carousel horse..person surfing
+1F3C5 ; Emoji # 7.0 [1] (🏅) sports medal
+1F3C6..1F3CA ; Emoji # 6.0 [5] (🏆..🏊) trophy..person swimming
+1F3CB..1F3CE ; Emoji # 7.0 [4] (🏋️..🏎️) person lifting weights..racing car
+1F3CF..1F3D3 ; Emoji # 8.0 [5] (🏏..🏓) cricket game..ping pong
+1F3D4..1F3DF ; Emoji # 7.0 [12] (🏔️..🏟️) snow-capped mountain..stadium
+1F3E0..1F3F0 ; Emoji # 6.0 [17] (🏠..🏰) house..castle
+1F3F3..1F3F5 ; Emoji # 7.0 [3] (🏳️..🏵️) white flag..rosette
+1F3F7 ; Emoji # 7.0 [1] (🏷️) label
+1F3F8..1F3FF ; Emoji # 8.0 [8] (🏸..🏿) badminton..dark skin tone
+1F400..1F43E ; Emoji # 6.0 [63] (🐀..🐾) rat..paw prints
+1F43F ; Emoji # 7.0 [1] (🐿️) chipmunk
+1F440 ; Emoji # 6.0 [1] (👀) eyes
+1F441 ; Emoji # 7.0 [1] (👁️) eye
+1F442..1F4F7 ; Emoji # 6.0[182] (👂..📷) ear..camera
+1F4F8 ; Emoji # 7.0 [1] (📸) camera with flash
+1F4F9..1F4FC ; Emoji # 6.0 [4] (📹..📼) video camera..videocassette
+1F4FD ; Emoji # 7.0 [1] (📽️) film projector
+1F4FF ; Emoji # 8.0 [1] (📿) prayer beads
+1F500..1F53D ; Emoji # 6.0 [62] (🔀..🔽) shuffle tracks button..downwards button
+1F549..1F54A ; Emoji # 7.0 [2] (🕉️..🕊️) om..dove
+1F54B..1F54E ; Emoji # 8.0 [4] (🕋..🕎) kaaba..menorah
+1F550..1F567 ; Emoji # 6.0 [24] (🕐..🕧) one o’clock..twelve-thirty
+1F56F..1F570 ; Emoji # 7.0 [2] (🕯️..🕰️) candle..mantelpiece clock
+1F573..1F579 ; Emoji # 7.0 [7] (🕳️..🕹️) hole..joystick
+1F57A ; Emoji # 9.0 [1] (🕺) man dancing
+1F587 ; Emoji # 7.0 [1] (🖇️) linked paperclips
+1F58A..1F58D ; Emoji # 7.0 [4] (🖊️..🖍️) pen..crayon
+1F590 ; Emoji # 7.0 [1] (🖐️) hand with fingers splayed
+1F595..1F596 ; Emoji # 7.0 [2] (🖕..🖖) middle finger..vulcan salute
+1F5A4 ; Emoji # 9.0 [1] (🖤) black heart
+1F5A5 ; Emoji # 7.0 [1] (🖥️) desktop computer
+1F5A8 ; Emoji # 7.0 [1] (🖨️) printer
+1F5B1..1F5B2 ; Emoji # 7.0 [2] (🖱️..🖲️) computer mouse..trackball
+1F5BC ; Emoji # 7.0 [1] (🖼️) framed picture
+1F5C2..1F5C4 ; Emoji # 7.0 [3] (🗂️..🗄️) card index dividers..file cabinet
+1F5D1..1F5D3 ; Emoji # 7.0 [3] (🗑️..🗓️) wastebasket..spiral calendar
+1F5DC..1F5DE ; Emoji # 7.0 [3] (🗜️..🗞️) clamp..rolled-up newspaper
+1F5E1 ; Emoji # 7.0 [1] (🗡️) dagger
+1F5E3 ; Emoji # 7.0 [1] (🗣️) speaking head
+1F5E8 ; Emoji # 7.0 [1] (🗨️) left speech bubble
+1F5EF ; Emoji # 7.0 [1] (🗯️) right anger bubble
+1F5F3 ; Emoji # 7.0 [1] (🗳️) ballot box with ballot
+1F5FA ; Emoji # 7.0 [1] (🗺️) world map
+1F5FB..1F5FF ; Emoji # 6.0 [5] (🗻..🗿) mount fuji..moai
+1F600 ; Emoji # 6.1 [1] (😀) grinning face
+1F601..1F610 ; Emoji # 6.0 [16] (😁..😐) beaming face with smiling eyes..neutral face
+1F611 ; Emoji # 6.1 [1] (😑) expressionless face
+1F612..1F614 ; Emoji # 6.0 [3] (😒..😔) unamused face..pensive face
+1F615 ; Emoji # 6.1 [1] (😕) confused face
+1F616 ; Emoji # 6.0 [1] (😖) confounded face
+1F617 ; Emoji # 6.1 [1] (😗) kissing face
+1F618 ; Emoji # 6.0 [1] (😘) face blowing a kiss
+1F619 ; Emoji # 6.1 [1] (😙) kissing face with smiling eyes
+1F61A ; Emoji # 6.0 [1] (😚) kissing face with closed eyes
+1F61B ; Emoji # 6.1 [1] (😛) face with tongue
+1F61C..1F61E ; Emoji # 6.0 [3] (😜..😞) winking face with tongue..disappointed face
+1F61F ; Emoji # 6.1 [1] (😟) worried face
+1F620..1F625 ; Emoji # 6.0 [6] (😠..😥) angry face..sad but relieved face
+1F626..1F627 ; Emoji # 6.1 [2] (😦..😧) frowning face with open mouth..anguished face
+1F628..1F62B ; Emoji # 6.0 [4] (😨..😫) fearful face..tired face
+1F62C ; Emoji # 6.1 [1] (😬) grimacing face
+1F62D ; Emoji # 6.0 [1] (😭) loudly crying face
+1F62E..1F62F ; Emoji # 6.1 [2] (😮..😯) face with open mouth..hushed face
+1F630..1F633 ; Emoji # 6.0 [4] (😰..😳) anxious face with sweat..flushed face
+1F634 ; Emoji # 6.1 [1] (😴) sleeping face
+1F635..1F640 ; Emoji # 6.0 [12] (😵..🙀) dizzy face..weary cat face
+1F641..1F642 ; Emoji # 7.0 [2] (🙁..🙂) slightly frowning face..slightly smiling face
+1F643..1F644 ; Emoji # 8.0 [2] (🙃..🙄) upside-down face..face with rolling eyes
+1F645..1F64F ; Emoji # 6.0 [11] (🙅..🙏) person gesturing NO..folded hands
+1F680..1F6C5 ; Emoji # 6.0 [70] (🚀..🛅) rocket..left luggage
+1F6CB..1F6CF ; Emoji # 7.0 [5] (🛋️..🛏️) couch and lamp..bed
+1F6D0 ; Emoji # 8.0 [1] (🛐) place of worship
+1F6D1..1F6D2 ; Emoji # 9.0 [2] (🛑..🛒) stop sign..shopping cart
+1F6E0..1F6E5 ; Emoji # 7.0 [6] (🛠️..🛥️) hammer and wrench..motor boat
+1F6E9 ; Emoji # 7.0 [1] (🛩️) small airplane
+1F6EB..1F6EC ; Emoji # 7.0 [2] (🛫..🛬) airplane departure..airplane arrival
+1F6F0 ; Emoji # 7.0 [1] (🛰️) satellite
+1F6F3 ; Emoji # 7.0 [1] (🛳️) passenger ship
+1F6F4..1F6F6 ; Emoji # 9.0 [3] (🛴..🛶) kick scooter..canoe
+1F6F7..1F6F8 ; Emoji # 10.0 [2] (🛷..🛸) sled..flying saucer
+1F6F9 ; Emoji # 11.0 [1] (🛹) skateboard
+1F910..1F918 ; Emoji # 8.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns
+1F919..1F91E ; Emoji # 9.0 [6] (🤙..🤞) call me hand..crossed fingers
+1F91F ; Emoji # 10.0 [1] (🤟) love-you gesture
+1F920..1F927 ; Emoji # 9.0 [8] (🤠..🤧) cowboy hat face..sneezing face
+1F928..1F92F ; Emoji # 10.0 [8] (🤨..🤯) face with raised eyebrow..exploding head
+1F930 ; Emoji # 9.0 [1] (🤰) pregnant woman
+1F931..1F932 ; Emoji # 10.0 [2] (🤱..🤲) breast-feeding..palms up together
+1F933..1F93A ; Emoji # 9.0 [8] (🤳..🤺) selfie..person fencing
+1F93C..1F93E ; Emoji # 9.0 [3] (🤼..🤾) people wrestling..person playing handball
+1F940..1F945 ; Emoji # 9.0 [6] (🥀..🥅) wilted flower..goal net
+1F947..1F94B ; Emoji # 9.0 [5] (🥇..🥋) 1st place medal..martial arts uniform
+1F94C ; Emoji # 10.0 [1] (🥌) curling stone
+1F94D..1F94F ; Emoji # 11.0 [3] (🥍..🥏) lacrosse..flying disc
+1F950..1F95E ; Emoji # 9.0 [15] (🥐..🥞) croissant..pancakes
+1F95F..1F96B ; Emoji # 10.0 [13] (🥟..🥫) dumpling..canned food
+1F96C..1F970 ; Emoji # 11.0 [5] (🥬..🥰) leafy green..smiling face with 3 hearts
+1F973..1F976 ; Emoji # 11.0 [4] (🥳..🥶) partying face..cold face
+1F97A ; Emoji # 11.0 [1] (🥺) pleading face
+1F97C..1F97F ; Emoji # 11.0 [4] (🥼..🥿) lab coat..woman’s flat shoe
+1F980..1F984 ; Emoji # 8.0 [5] (🦀..🦄) crab..unicorn face
+1F985..1F991 ; Emoji # 9.0 [13] (🦅..🦑) eagle..squid
+1F992..1F997 ; Emoji # 10.0 [6] (🦒..🦗) giraffe..cricket
+1F998..1F9A2 ; Emoji # 11.0 [11] (🦘..🦢) kangaroo..swan
+1F9B0..1F9B9 ; Emoji # 11.0 [10] (🦰..🦹) red-haired..supervillain
+1F9C0 ; Emoji # 8.0 [1] (🧀) cheese wedge
+1F9C1..1F9C2 ; Emoji # 11.0 [2] (🧁..🧂) cupcake..salt
+1F9D0..1F9E6 ; Emoji # 10.0 [23] (🧐..🧦) face with monocle..socks
+1F9E7..1F9FF ; Emoji # 11.0 [25] (🧧..🧿) red envelope..nazar amulet
+
+# Total elements: 1250
+
+# ================================================
+
+# All omitted code points have Emoji_Presentation=No
+# @missing: 0000..10FFFF ; Emoji_Presentation ; No
+
+231A..231B ; Emoji_Presentation # 1.1 [2] (⌚..⌛) watch..hourglass done
+23E9..23EC ; Emoji_Presentation # 6.0 [4] (⏩..⏬) fast-forward button..fast down button
+23F0 ; Emoji_Presentation # 6.0 [1] (⏰) alarm clock
+23F3 ; Emoji_Presentation # 6.0 [1] (⏳) hourglass not done
+25FD..25FE ; Emoji_Presentation # 3.2 [2] (◽..◾) white medium-small square..black medium-small square
+2614..2615 ; Emoji_Presentation # 4.0 [2] (☔..☕) umbrella with rain drops..hot beverage
+2648..2653 ; Emoji_Presentation # 1.1 [12] (♈..♓) Aries..Pisces
+267F ; Emoji_Presentation # 4.1 [1] (♿) wheelchair symbol
+2693 ; Emoji_Presentation # 4.1 [1] (⚓) anchor
+26A1 ; Emoji_Presentation # 4.0 [1] (⚡) high voltage
+26AA..26AB ; Emoji_Presentation # 4.1 [2] (⚪..⚫) white circle..black circle
+26BD..26BE ; Emoji_Presentation # 5.2 [2] (⚽..⚾) soccer ball..baseball
+26C4..26C5 ; Emoji_Presentation # 5.2 [2] (⛄..⛅) snowman without snow..sun behind cloud
+26CE ; Emoji_Presentation # 6.0 [1] (⛎) Ophiuchus
+26D4 ; Emoji_Presentation # 5.2 [1] (⛔) no entry
+26EA ; Emoji_Presentation # 5.2 [1] (⛪) church
+26F2..26F3 ; Emoji_Presentation # 5.2 [2] (⛲..⛳) fountain..flag in hole
+26F5 ; Emoji_Presentation # 5.2 [1] (⛵) sailboat
+26FA ; Emoji_Presentation # 5.2 [1] (⛺) tent
+26FD ; Emoji_Presentation # 5.2 [1] (⛽) fuel pump
+2705 ; Emoji_Presentation # 6.0 [1] (✅) white heavy check mark
+270A..270B ; Emoji_Presentation # 6.0 [2] (✊..✋) raised fist..raised hand
+2728 ; Emoji_Presentation # 6.0 [1] (✨) sparkles
+274C ; Emoji_Presentation # 6.0 [1] (❌) cross mark
+274E ; Emoji_Presentation # 6.0 [1] (❎) cross mark button
+2753..2755 ; Emoji_Presentation # 6.0 [3] (❓..❕) question mark..white exclamation mark
+2757 ; Emoji_Presentation # 5.2 [1] (❗) exclamation mark
+2795..2797 ; Emoji_Presentation # 6.0 [3] (➕..➗) heavy plus sign..heavy division sign
+27B0 ; Emoji_Presentation # 6.0 [1] (➰) curly loop
+27BF ; Emoji_Presentation # 6.0 [1] (➿) double curly loop
+2B1B..2B1C ; Emoji_Presentation # 5.1 [2] (⬛..⬜) black large square..white large square
+2B50 ; Emoji_Presentation # 5.1 [1] (⭐) star
+2B55 ; Emoji_Presentation # 5.2 [1] (⭕) heavy large circle
+1F004 ; Emoji_Presentation # 5.1 [1] (🀄) mahjong red dragon
+1F0CF ; Emoji_Presentation # 6.0 [1] (🃏) joker
+1F18E ; Emoji_Presentation # 6.0 [1] (🆎) AB button (blood type)
+1F191..1F19A ; Emoji_Presentation # 6.0 [10] (🆑..🆚) CL button..VS button
+1F1E6..1F1FF ; Emoji_Presentation # 6.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z
+1F201 ; Emoji_Presentation # 6.0 [1] (🈁) Japanese “here” button
+1F21A ; Emoji_Presentation # 5.2 [1] (🈚) Japanese “free of charge” button
+1F22F ; Emoji_Presentation # 5.2 [1] (🈯) Japanese “reserved” button
+1F232..1F236 ; Emoji_Presentation # 6.0 [5] (🈲..🈶) Japanese “prohibited” button..Japanese “not free of charge” button
+1F238..1F23A ; Emoji_Presentation # 6.0 [3] (🈸..🈺) Japanese “application” button..Japanese “open for business” button
+1F250..1F251 ; Emoji_Presentation # 6.0 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button
+1F300..1F320 ; Emoji_Presentation # 6.0 [33] (🌀..🌠) cyclone..shooting star
+1F32D..1F32F ; Emoji_Presentation # 8.0 [3] (🌭..🌯) hot dog..burrito
+1F330..1F335 ; Emoji_Presentation # 6.0 [6] (🌰..🌵) chestnut..cactus
+1F337..1F37C ; Emoji_Presentation # 6.0 [70] (🌷..🍼) tulip..baby bottle
+1F37E..1F37F ; Emoji_Presentation # 8.0 [2] (🍾..🍿) bottle with popping cork..popcorn
+1F380..1F393 ; Emoji_Presentation # 6.0 [20] (🎀..🎓) ribbon..graduation cap
+1F3A0..1F3C4 ; Emoji_Presentation # 6.0 [37] (🎠..🏄) carousel horse..person surfing
+1F3C5 ; Emoji_Presentation # 7.0 [1] (🏅) sports medal
+1F3C6..1F3CA ; Emoji_Presentation # 6.0 [5] (🏆..🏊) trophy..person swimming
+1F3CF..1F3D3 ; Emoji_Presentation # 8.0 [5] (🏏..🏓) cricket game..ping pong
+1F3E0..1F3F0 ; Emoji_Presentation # 6.0 [17] (🏠..🏰) house..castle
+1F3F4 ; Emoji_Presentation # 7.0 [1] (🏴) black flag
+1F3F8..1F3FF ; Emoji_Presentation # 8.0 [8] (🏸..🏿) badminton..dark skin tone
+1F400..1F43E ; Emoji_Presentation # 6.0 [63] (🐀..🐾) rat..paw prints
+1F440 ; Emoji_Presentation # 6.0 [1] (👀) eyes
+1F442..1F4F7 ; Emoji_Presentation # 6.0[182] (👂..📷) ear..camera
+1F4F8 ; Emoji_Presentation # 7.0 [1] (📸) camera with flash
+1F4F9..1F4FC ; Emoji_Presentation # 6.0 [4] (📹..📼) video camera..videocassette
+1F4FF ; Emoji_Presentation # 8.0 [1] (📿) prayer beads
+1F500..1F53D ; Emoji_Presentation # 6.0 [62] (🔀..🔽) shuffle tracks button..downwards button
+1F54B..1F54E ; Emoji_Presentation # 8.0 [4] (🕋..🕎) kaaba..menorah
+1F550..1F567 ; Emoji_Presentation # 6.0 [24] (🕐..🕧) one o’clock..twelve-thirty
+1F57A ; Emoji_Presentation # 9.0 [1] (🕺) man dancing
+1F595..1F596 ; Emoji_Presentation # 7.0 [2] (🖕..🖖) middle finger..vulcan salute
+1F5A4 ; Emoji_Presentation # 9.0 [1] (🖤) black heart
+1F5FB..1F5FF ; Emoji_Presentation # 6.0 [5] (🗻..🗿) mount fuji..moai
+1F600 ; Emoji_Presentation # 6.1 [1] (😀) grinning face
+1F601..1F610 ; Emoji_Presentation # 6.0 [16] (😁..😐) beaming face with smiling eyes..neutral face
+1F611 ; Emoji_Presentation # 6.1 [1] (😑) expressionless face
+1F612..1F614 ; Emoji_Presentation # 6.0 [3] (😒..😔) unamused face..pensive face
+1F615 ; Emoji_Presentation # 6.1 [1] (😕) confused face
+1F616 ; Emoji_Presentation # 6.0 [1] (😖) confounded face
+1F617 ; Emoji_Presentation # 6.1 [1] (😗) kissing face
+1F618 ; Emoji_Presentation # 6.0 [1] (😘) face blowing a kiss
+1F619 ; Emoji_Presentation # 6.1 [1] (😙) kissing face with smiling eyes
+1F61A ; Emoji_Presentation # 6.0 [1] (😚) kissing face with closed eyes
+1F61B ; Emoji_Presentation # 6.1 [1] (😛) face with tongue
+1F61C..1F61E ; Emoji_Presentation # 6.0 [3] (😜..😞) winking face with tongue..disappointed face
+1F61F ; Emoji_Presentation # 6.1 [1] (😟) worried face
+1F620..1F625 ; Emoji_Presentation # 6.0 [6] (😠..😥) angry face..sad but relieved face
+1F626..1F627 ; Emoji_Presentation # 6.1 [2] (😦..😧) frowning face with open mouth..anguished face
+1F628..1F62B ; Emoji_Presentation # 6.0 [4] (😨..😫) fearful face..tired face
+1F62C ; Emoji_Presentation # 6.1 [1] (😬) grimacing face
+1F62D ; Emoji_Presentation # 6.0 [1] (😭) loudly crying face
+1F62E..1F62F ; Emoji_Presentation # 6.1 [2] (😮..😯) face with open mouth..hushed face
+1F630..1F633 ; Emoji_Presentation # 6.0 [4] (😰..😳) anxious face with sweat..flushed face
+1F634 ; Emoji_Presentation # 6.1 [1] (😴) sleeping face
+1F635..1F640 ; Emoji_Presentation # 6.0 [12] (😵..🙀) dizzy face..weary cat face
+1F641..1F642 ; Emoji_Presentation # 7.0 [2] (🙁..🙂) slightly frowning face..slightly smiling face
+1F643..1F644 ; Emoji_Presentation # 8.0 [2] (🙃..🙄) upside-down face..face with rolling eyes
+1F645..1F64F ; Emoji_Presentation # 6.0 [11] (🙅..🙏) person gesturing NO..folded hands
+1F680..1F6C5 ; Emoji_Presentation # 6.0 [70] (🚀..🛅) rocket..left luggage
+1F6CC ; Emoji_Presentation # 7.0 [1] (🛌) person in bed
+1F6D0 ; Emoji_Presentation # 8.0 [1] (🛐) place of worship
+1F6D1..1F6D2 ; Emoji_Presentation # 9.0 [2] (🛑..🛒) stop sign..shopping cart
+1F6EB..1F6EC ; Emoji_Presentation # 7.0 [2] (🛫..🛬) airplane departure..airplane arrival
+1F6F4..1F6F6 ; Emoji_Presentation # 9.0 [3] (🛴..🛶) kick scooter..canoe
+1F6F7..1F6F8 ; Emoji_Presentation # 10.0 [2] (🛷..🛸) sled..flying saucer
+1F6F9 ; Emoji_Presentation # 11.0 [1] (🛹) skateboard
+1F910..1F918 ; Emoji_Presentation # 8.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns
+1F919..1F91E ; Emoji_Presentation # 9.0 [6] (🤙..🤞) call me hand..crossed fingers
+1F91F ; Emoji_Presentation # 10.0 [1] (🤟) love-you gesture
+1F920..1F927 ; Emoji_Presentation # 9.0 [8] (🤠..🤧) cowboy hat face..sneezing face
+1F928..1F92F ; Emoji_Presentation # 10.0 [8] (🤨..🤯) face with raised eyebrow..exploding head
+1F930 ; Emoji_Presentation # 9.0 [1] (🤰) pregnant woman
+1F931..1F932 ; Emoji_Presentation # 10.0 [2] (🤱..🤲) breast-feeding..palms up together
+1F933..1F93A ; Emoji_Presentation # 9.0 [8] (🤳..🤺) selfie..person fencing
+1F93C..1F93E ; Emoji_Presentation # 9.0 [3] (🤼..🤾) people wrestling..person playing handball
+1F940..1F945 ; Emoji_Presentation # 9.0 [6] (🥀..🥅) wilted flower..goal net
+1F947..1F94B ; Emoji_Presentation # 9.0 [5] (🥇..🥋) 1st place medal..martial arts uniform
+1F94C ; Emoji_Presentation # 10.0 [1] (🥌) curling stone
+1F94D..1F94F ; Emoji_Presentation # 11.0 [3] (🥍..🥏) lacrosse..flying disc
+1F950..1F95E ; Emoji_Presentation # 9.0 [15] (🥐..🥞) croissant..pancakes
+1F95F..1F96B ; Emoji_Presentation # 10.0 [13] (🥟..🥫) dumpling..canned food
+1F96C..1F970 ; Emoji_Presentation # 11.0 [5] (🥬..🥰) leafy green..smiling face with 3 hearts
+1F973..1F976 ; Emoji_Presentation # 11.0 [4] (🥳..🥶) partying face..cold face
+1F97A ; Emoji_Presentation # 11.0 [1] (🥺) pleading face
+1F97C..1F97F ; Emoji_Presentation # 11.0 [4] (🥼..🥿) lab coat..woman’s flat shoe
+1F980..1F984 ; Emoji_Presentation # 8.0 [5] (🦀..🦄) crab..unicorn face
+1F985..1F991 ; Emoji_Presentation # 9.0 [13] (🦅..🦑) eagle..squid
+1F992..1F997 ; Emoji_Presentation # 10.0 [6] (🦒..🦗) giraffe..cricket
+1F998..1F9A2 ; Emoji_Presentation # 11.0 [11] (🦘..🦢) kangaroo..swan
+1F9B0..1F9B9 ; Emoji_Presentation # 11.0 [10] (🦰..🦹) red-haired..supervillain
+1F9C0 ; Emoji_Presentation # 8.0 [1] (🧀) cheese wedge
+1F9C1..1F9C2 ; Emoji_Presentation # 11.0 [2] (🧁..🧂) cupcake..salt
+1F9D0..1F9E6 ; Emoji_Presentation # 10.0 [23] (🧐..🧦) face with monocle..socks
+1F9E7..1F9FF ; Emoji_Presentation # 11.0 [25] (🧧..🧿) red envelope..nazar amulet
+
+# Total elements: 1032
+
+# ================================================
+
+# All omitted code points have Emoji_Modifier=No
+# @missing: 0000..10FFFF ; Emoji_Modifier ; No
+
+1F3FB..1F3FF ; Emoji_Modifier # 8.0 [5] (🏻..🏿) light skin tone..dark skin tone
+
+# Total elements: 5
+
+# ================================================
+
+# All omitted code points have Emoji_Modifier_Base=No
+# @missing: 0000..10FFFF ; Emoji_Modifier_Base ; No
+
+261D ; Emoji_Modifier_Base # 1.1 [1] (☝️) index pointing up
+26F9 ; Emoji_Modifier_Base # 5.2 [1] (⛹️) person bouncing ball
+270A..270B ; Emoji_Modifier_Base # 6.0 [2] (✊..✋) raised fist..raised hand
+270C..270D ; Emoji_Modifier_Base # 1.1 [2] (✌️..✍️) victory hand..writing hand
+1F385 ; Emoji_Modifier_Base # 6.0 [1] (🎅) Santa Claus
+1F3C2..1F3C4 ; Emoji_Modifier_Base # 6.0 [3] (🏂..🏄) snowboarder..person surfing
+1F3C7 ; Emoji_Modifier_Base # 6.0 [1] (🏇) horse racing
+1F3CA ; Emoji_Modifier_Base # 6.0 [1] (🏊) person swimming
+1F3CB..1F3CC ; Emoji_Modifier_Base # 7.0 [2] (🏋️..🏌️) person lifting weights..person golfing
+1F442..1F443 ; Emoji_Modifier_Base # 6.0 [2] (👂..👃) ear..nose
+1F446..1F450 ; Emoji_Modifier_Base # 6.0 [11] (👆..👐) backhand index pointing up..open hands
+1F466..1F469 ; Emoji_Modifier_Base # 6.0 [4] (👦..👩) boy..woman
+1F46E ; Emoji_Modifier_Base # 6.0 [1] (👮) police officer
+1F470..1F478 ; Emoji_Modifier_Base # 6.0 [9] (👰..👸) bride with veil..princess
+1F47C ; Emoji_Modifier_Base # 6.0 [1] (👼) baby angel
+1F481..1F483 ; Emoji_Modifier_Base # 6.0 [3] (💁..💃) person tipping hand..woman dancing
+1F485..1F487 ; Emoji_Modifier_Base # 6.0 [3] (💅..💇) nail polish..person getting haircut
+1F4AA ; Emoji_Modifier_Base # 6.0 [1] (💪) flexed biceps
+1F574..1F575 ; Emoji_Modifier_Base # 7.0 [2] (🕴️..🕵️) man in suit levitating..detective
+1F57A ; Emoji_Modifier_Base # 9.0 [1] (🕺) man dancing
+1F590 ; Emoji_Modifier_Base # 7.0 [1] (🖐️) hand with fingers splayed
+1F595..1F596 ; Emoji_Modifier_Base # 7.0 [2] (🖕..🖖) middle finger..vulcan salute
+1F645..1F647 ; Emoji_Modifier_Base # 6.0 [3] (🙅..🙇) person gesturing NO..person bowing
+1F64B..1F64F ; Emoji_Modifier_Base # 6.0 [5] (🙋..🙏) person raising hand..folded hands
+1F6A3 ; Emoji_Modifier_Base # 6.0 [1] (🚣) person rowing boat
+1F6B4..1F6B6 ; Emoji_Modifier_Base # 6.0 [3] (🚴..🚶) person biking..person walking
+1F6C0 ; Emoji_Modifier_Base # 6.0 [1] (🛀) person taking bath
+1F6CC ; Emoji_Modifier_Base # 7.0 [1] (🛌) person in bed
+1F918 ; Emoji_Modifier_Base # 8.0 [1] (🤘) sign of the horns
+1F919..1F91C ; Emoji_Modifier_Base # 9.0 [4] (🤙..🤜) call me hand..right-facing fist
+1F91E ; Emoji_Modifier_Base # 9.0 [1] (🤞) crossed fingers
+1F91F ; Emoji_Modifier_Base # 10.0 [1] (🤟) love-you gesture
+1F926 ; Emoji_Modifier_Base # 9.0 [1] (🤦) person facepalming
+1F930 ; Emoji_Modifier_Base # 9.0 [1] (🤰) pregnant woman
+1F931..1F932 ; Emoji_Modifier_Base # 10.0 [2] (🤱..🤲) breast-feeding..palms up together
+1F933..1F939 ; Emoji_Modifier_Base # 9.0 [7] (🤳..🤹) selfie..person juggling
+1F93D..1F93E ; Emoji_Modifier_Base # 9.0 [2] (🤽..🤾) person playing water polo..person playing handball
+1F9B5..1F9B6 ; Emoji_Modifier_Base # 11.0 [2] (🦵..🦶) leg..foot
+1F9B8..1F9B9 ; Emoji_Modifier_Base # 11.0 [2] (🦸..🦹) superhero..supervillain
+1F9D1..1F9DD ; Emoji_Modifier_Base # 10.0 [13] (🧑..🧝) adult..elf
+
+# Total elements: 106
+
+# ================================================
+
+# All omitted code points have Emoji_Component=No
+# @missing: 0000..10FFFF ; Emoji_Component ; No
+
+0023 ; Emoji_Component # 1.1 [1] (#️) number sign
+002A ; Emoji_Component # 1.1 [1] (*️) asterisk
+0030..0039 ; Emoji_Component # 1.1 [10] (0️..9️) digit zero..digit nine
+200D ; Emoji_Component # 1.1 [1] (‍) zero width joiner
+20E3 ; Emoji_Component # 3.0 [1] (⃣) combining enclosing keycap
+FE0F ; Emoji_Component # 3.2 [1] () VARIATION SELECTOR-16
+1F1E6..1F1FF ; Emoji_Component # 6.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z
+1F3FB..1F3FF ; Emoji_Component # 8.0 [5] (🏻..🏿) light skin tone..dark skin tone
+1F9B0..1F9B3 ; Emoji_Component # 11.0 [4] (🦰..🦳) red-haired..white-haired
+E0020..E007F ; Emoji_Component # 3.1 [96] (󠀠..󠁿) tag space..cancel tag
+
+# Total elements: 146
+
+# ================================================
+
+# All omitted code points have Extended_Pictographic=No
+# @missing: 0000..10FFFF ; Extended_Pictographic ; No
+
+00A9 ; Extended_Pictographic# 1.1 [1] (©️) copyright
+00AE ; Extended_Pictographic# 1.1 [1] (®️) registered
+203C ; Extended_Pictographic# 1.1 [1] (‼️) double exclamation mark
+2049 ; Extended_Pictographic# 3.0 [1] (⁉️) exclamation question mark
+2122 ; Extended_Pictographic# 1.1 [1] (™️) trade mark
+2139 ; Extended_Pictographic# 3.0 [1] (ℹ️) information
+2194..2199 ; Extended_Pictographic# 1.1 [6] (↔️..↙️) left-right arrow..down-left arrow
+21A9..21AA ; Extended_Pictographic# 1.1 [2] (↩️..↪️) right arrow curving left..left arrow curving right
+231A..231B ; Extended_Pictographic# 1.1 [2] (⌚..⌛) watch..hourglass done
+2328 ; Extended_Pictographic# 1.1 [1] (⌨️) keyboard
+2388 ; Extended_Pictographic# 3.0 [1] (⎈️) HELM SYMBOL
+23CF ; Extended_Pictographic# 4.0 [1] (⏏️) eject button
+23E9..23F3 ; Extended_Pictographic# 6.0 [11] (⏩..⏳) fast-forward button..hourglass not done
+23F8..23FA ; Extended_Pictographic# 7.0 [3] (⏸️..⏺️) pause button..record button
+24C2 ; Extended_Pictographic# 1.1 [1] (Ⓜ️) circled M
+25AA..25AB ; Extended_Pictographic# 1.1 [2] (▪️..▫️) black small square..white small square
+25B6 ; Extended_Pictographic# 1.1 [1] (▶️) play button
+25C0 ; Extended_Pictographic# 1.1 [1] (◀️) reverse button
+25FB..25FE ; Extended_Pictographic# 3.2 [4] (◻️..◾) white medium square..black medium-small square
+2600..2605 ; Extended_Pictographic# 1.1 [6] (☀️..★️) sun..BLACK STAR
+2607..2612 ; Extended_Pictographic# 1.1 [12] (☇️..☒️) LIGHTNING..BALLOT BOX WITH X
+2614..2615 ; Extended_Pictographic# 4.0 [2] (☔..☕) umbrella with rain drops..hot beverage
+2616..2617 ; Extended_Pictographic# 3.2 [2] (☖️..☗️) WHITE SHOGI PIECE..BLACK SHOGI PIECE
+2618 ; Extended_Pictographic# 4.1 [1] (☘️) shamrock
+2619 ; Extended_Pictographic# 3.0 [1] (☙️) REVERSED ROTATED FLORAL HEART BULLET
+261A..266F ; Extended_Pictographic# 1.1 [86] (☚️..♯️) BLACK LEFT POINTING INDEX..MUSIC SHARP SIGN
+2670..2671 ; Extended_Pictographic# 3.0 [2] (♰️..♱️) WEST SYRIAC CROSS..EAST SYRIAC CROSS
+2672..267D ; Extended_Pictographic# 3.2 [12] (♲️..♽️) UNIVERSAL RECYCLING SYMBOL..PARTIALLY-RECYCLED PAPER SYMBOL
+267E..267F ; Extended_Pictographic# 4.1 [2] (♾️..♿) infinity..wheelchair symbol
+2680..2685 ; Extended_Pictographic# 3.2 [6] (⚀️..⚅️) DIE FACE-1..DIE FACE-6
+2690..2691 ; Extended_Pictographic# 4.0 [2] (⚐️..⚑️) WHITE FLAG..BLACK FLAG
+2692..269C ; Extended_Pictographic# 4.1 [11] (⚒️..⚜️) hammer and pick..fleur-de-lis
+269D ; Extended_Pictographic# 5.1 [1] (⚝️) OUTLINED WHITE STAR
+269E..269F ; Extended_Pictographic# 5.2 [2] (⚞️..⚟️) THREE LINES CONVERGING RIGHT..THREE LINES CONVERGING LEFT
+26A0..26A1 ; Extended_Pictographic# 4.0 [2] (⚠️..⚡) warning..high voltage
+26A2..26B1 ; Extended_Pictographic# 4.1 [16] (⚢️..⚱️) DOUBLED FEMALE SIGN..funeral urn
+26B2 ; Extended_Pictographic# 5.0 [1] (⚲️) NEUTER
+26B3..26BC ; Extended_Pictographic# 5.1 [10] (⚳️..⚼️) CERES..SESQUIQUADRATE
+26BD..26BF ; Extended_Pictographic# 5.2 [3] (⚽..⚿️) soccer ball..SQUARED KEY
+26C0..26C3 ; Extended_Pictographic# 5.1 [4] (⛀️..⛃️) WHITE DRAUGHTS MAN..BLACK DRAUGHTS KING
+26C4..26CD ; Extended_Pictographic# 5.2 [10] (⛄..⛍️) snowman without snow..DISABLED CAR
+26CE ; Extended_Pictographic# 6.0 [1] (⛎) Ophiuchus
+26CF..26E1 ; Extended_Pictographic# 5.2 [19] (⛏️..⛡️) pick..RESTRICTED LEFT ENTRY-2
+26E2 ; Extended_Pictographic# 6.0 [1] (⛢️) ASTRONOMICAL SYMBOL FOR URANUS
+26E3 ; Extended_Pictographic# 5.2 [1] (⛣️) HEAVY CIRCLE WITH STROKE AND TWO DOTS ABOVE
+26E4..26E7 ; Extended_Pictographic# 6.0 [4] (⛤️..⛧️) PENTAGRAM..INVERTED PENTAGRAM
+26E8..26FF ; Extended_Pictographic# 5.2 [24] (⛨️..⛿️) BLACK CROSS ON SHIELD..WHITE FLAG WITH HORIZONTAL MIDDLE BLACK STRIPE
+2700 ; Extended_Pictographic# 7.0 [1] (✀️) BLACK SAFETY SCISSORS
+2701..2704 ; Extended_Pictographic# 1.1 [4] (✁️..✄️) UPPER BLADE SCISSORS..WHITE SCISSORS
+2705 ; Extended_Pictographic# 6.0 [1] (✅) white heavy check mark
+2708..2709 ; Extended_Pictographic# 1.1 [2] (✈️..✉️) airplane..envelope
+270A..270B ; Extended_Pictographic# 6.0 [2] (✊..✋) raised fist..raised hand
+270C..2712 ; Extended_Pictographic# 1.1 [7] (✌️..✒️) victory hand..black nib
+2714 ; Extended_Pictographic# 1.1 [1] (✔️) heavy check mark
+2716 ; Extended_Pictographic# 1.1 [1] (✖️) heavy multiplication x
+271D ; Extended_Pictographic# 1.1 [1] (✝️) latin cross
+2721 ; Extended_Pictographic# 1.1 [1] (✡️) star of David
+2728 ; Extended_Pictographic# 6.0 [1] (✨) sparkles
+2733..2734 ; Extended_Pictographic# 1.1 [2] (✳️..✴️) eight-spoked asterisk..eight-pointed star
+2744 ; Extended_Pictographic# 1.1 [1] (❄️) snowflake
+2747 ; Extended_Pictographic# 1.1 [1] (❇️) sparkle
+274C ; Extended_Pictographic# 6.0 [1] (❌) cross mark
+274E ; Extended_Pictographic# 6.0 [1] (❎) cross mark button
+2753..2755 ; Extended_Pictographic# 6.0 [3] (❓..❕) question mark..white exclamation mark
+2757 ; Extended_Pictographic# 5.2 [1] (❗) exclamation mark
+2763..2767 ; Extended_Pictographic# 1.1 [5] (❣️..❧️) heavy heart exclamation..ROTATED FLORAL HEART BULLET
+2795..2797 ; Extended_Pictographic# 6.0 [3] (➕..➗) heavy plus sign..heavy division sign
+27A1 ; Extended_Pictographic# 1.1 [1] (➡️) right arrow
+27B0 ; Extended_Pictographic# 6.0 [1] (➰) curly loop
+27BF ; Extended_Pictographic# 6.0 [1] (➿) double curly loop
+2934..2935 ; Extended_Pictographic# 3.2 [2] (⤴️..⤵️) right arrow curving up..right arrow curving down
+2B05..2B07 ; Extended_Pictographic# 4.0 [3] (⬅️..⬇️) left arrow..down arrow
+2B1B..2B1C ; Extended_Pictographic# 5.1 [2] (⬛..⬜) black large square..white large square
+2B50 ; Extended_Pictographic# 5.1 [1] (⭐) star
+2B55 ; Extended_Pictographic# 5.2 [1] (⭕) heavy large circle
+3030 ; Extended_Pictographic# 1.1 [1] (〰️) wavy dash
+303D ; Extended_Pictographic# 3.2 [1] (〽️) part alternation mark
+3297 ; Extended_Pictographic# 1.1 [1] (㊗️) Japanese “congratulations” button
+3299 ; Extended_Pictographic# 1.1 [1] (㊙️) Japanese “secret” button
+1F000..1F02B ; Extended_Pictographic# 5.1 [44] (🀀️..🀫️) MAHJONG TILE EAST WIND..MAHJONG TILE BACK
+1F02C..1F02F ; Extended_Pictographic# NA [4] (🀬️..🀯️) <reserved-1F02C>..<reserved-1F02F>
+1F030..1F093 ; Extended_Pictographic# 5.1[100] (🀰️..🂓️) DOMINO TILE HORIZONTAL BACK..DOMINO TILE VERTICAL-06-06
+1F094..1F09F ; Extended_Pictographic# NA [12] (🂔️..🂟️) <reserved-1F094>..<reserved-1F09F>
+1F0A0..1F0AE ; Extended_Pictographic# 6.0 [15] (🂠️..🂮️) PLAYING CARD BACK..PLAYING CARD KING OF SPADES
+1F0AF..1F0B0 ; Extended_Pictographic# NA [2] (🂯️..🂰️) <reserved-1F0AF>..<reserved-1F0B0>
+1F0B1..1F0BE ; Extended_Pictographic# 6.0 [14] (🂱️..🂾️) PLAYING CARD ACE OF HEARTS..PLAYING CARD KING OF HEARTS
+1F0BF ; Extended_Pictographic# 7.0 [1] (🂿️) PLAYING CARD RED JOKER
+1F0C0 ; Extended_Pictographic# NA [1] (🃀️) <reserved-1F0C0>
+1F0C1..1F0CF ; Extended_Pictographic# 6.0 [15] (🃁️..🃏) PLAYING CARD ACE OF DIAMONDS..joker
+1F0D0 ; Extended_Pictographic# NA [1] (🃐️) <reserved-1F0D0>
+1F0D1..1F0DF ; Extended_Pictographic# 6.0 [15] (🃑️..🃟️) PLAYING CARD ACE OF CLUBS..PLAYING CARD WHITE JOKER
+1F0E0..1F0F5 ; Extended_Pictographic# 7.0 [22] (🃠️..🃵️) PLAYING CARD FOOL..PLAYING CARD TRUMP-21
+1F0F6..1F0FF ; Extended_Pictographic# NA [10] (🃶️..🃿️) <reserved-1F0F6>..<reserved-1F0FF>
+1F10D..1F10F ; Extended_Pictographic# NA [3] (🄍️..🄏️) <reserved-1F10D>..<reserved-1F10F>
+1F12F ; Extended_Pictographic# 11.0 [1] (🄯️) COPYLEFT SYMBOL
+1F16C..1F16F ; Extended_Pictographic# NA [4] (🅬️..🅯️) <reserved-1F16C>..<reserved-1F16F>
+1F170..1F171 ; Extended_Pictographic# 6.0 [2] (🅰️..🅱️) A button (blood type)..B button (blood type)
+1F17E ; Extended_Pictographic# 6.0 [1] (🅾️) O button (blood type)
+1F17F ; Extended_Pictographic# 5.2 [1] (🅿️) P button
+1F18E ; Extended_Pictographic# 6.0 [1] (🆎) AB button (blood type)
+1F191..1F19A ; Extended_Pictographic# 6.0 [10] (🆑..🆚) CL button..VS button
+1F1AD..1F1E5 ; Extended_Pictographic# NA [57] (🆭️..🇥️) <reserved-1F1AD>..<reserved-1F1E5>
+1F201..1F202 ; Extended_Pictographic# 6.0 [2] (🈁..🈂️) Japanese “here” button..Japanese “service charge” button
+1F203..1F20F ; Extended_Pictographic# NA [13] (🈃️..🈏️) <reserved-1F203>..<reserved-1F20F>
+1F21A ; Extended_Pictographic# 5.2 [1] (🈚) Japanese “free of charge” button
+1F22F ; Extended_Pictographic# 5.2 [1] (🈯) Japanese “reserved” button
+1F232..1F23A ; Extended_Pictographic# 6.0 [9] (🈲..🈺) Japanese “prohibited” button..Japanese “open for business” button
+1F23C..1F23F ; Extended_Pictographic# NA [4] (🈼️..🈿️) <reserved-1F23C>..<reserved-1F23F>
+1F249..1F24F ; Extended_Pictographic# NA [7] (🉉️..🉏️) <reserved-1F249>..<reserved-1F24F>
+1F250..1F251 ; Extended_Pictographic# 6.0 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button
+1F252..1F25F ; Extended_Pictographic# NA [14] (🉒️..🉟️) <reserved-1F252>..<reserved-1F25F>
+1F260..1F265 ; Extended_Pictographic# 10.0 [6] (🉠️..🉥️) ROUNDED SYMBOL FOR FU..ROUNDED SYMBOL FOR CAI
+1F266..1F2FF ; Extended_Pictographic# NA[154] (🉦️..🋿️) <reserved-1F266>..<reserved-1F2FF>
+1F300..1F320 ; Extended_Pictographic# 6.0 [33] (🌀..🌠) cyclone..shooting star
+1F321..1F32C ; Extended_Pictographic# 7.0 [12] (🌡️..🌬️) thermometer..wind face
+1F32D..1F32F ; Extended_Pictographic# 8.0 [3] (🌭..🌯) hot dog..burrito
+1F330..1F335 ; Extended_Pictographic# 6.0 [6] (🌰..🌵) chestnut..cactus
+1F336 ; Extended_Pictographic# 7.0 [1] (🌶️) hot pepper
+1F337..1F37C ; Extended_Pictographic# 6.0 [70] (🌷..🍼) tulip..baby bottle
+1F37D ; Extended_Pictographic# 7.0 [1] (🍽️) fork and knife with plate
+1F37E..1F37F ; Extended_Pictographic# 8.0 [2] (🍾..🍿) bottle with popping cork..popcorn
+1F380..1F393 ; Extended_Pictographic# 6.0 [20] (🎀..🎓) ribbon..graduation cap
+1F394..1F39F ; Extended_Pictographic# 7.0 [12] (🎔️..🎟️) HEART WITH TIP ON THE LEFT..admission tickets
+1F3A0..1F3C4 ; Extended_Pictographic# 6.0 [37] (🎠..🏄) carousel horse..person surfing
+1F3C5 ; Extended_Pictographic# 7.0 [1] (🏅) sports medal
+1F3C6..1F3CA ; Extended_Pictographic# 6.0 [5] (🏆..🏊) trophy..person swimming
+1F3CB..1F3CE ; Extended_Pictographic# 7.0 [4] (🏋️..🏎️) person lifting weights..racing car
+1F3CF..1F3D3 ; Extended_Pictographic# 8.0 [5] (🏏..🏓) cricket game..ping pong
+1F3D4..1F3DF ; Extended_Pictographic# 7.0 [12] (🏔️..🏟️) snow-capped mountain..stadium
+1F3E0..1F3F0 ; Extended_Pictographic# 6.0 [17] (🏠..🏰) house..castle
+1F3F1..1F3F7 ; Extended_Pictographic# 7.0 [7] (🏱️..🏷️) WHITE PENNANT..label
+1F3F8..1F3FA ; Extended_Pictographic# 8.0 [3] (🏸..🏺) badminton..amphora
+1F400..1F43E ; Extended_Pictographic# 6.0 [63] (🐀..🐾) rat..paw prints
+1F43F ; Extended_Pictographic# 7.0 [1] (🐿️) chipmunk
+1F440 ; Extended_Pictographic# 6.0 [1] (👀) eyes
+1F441 ; Extended_Pictographic# 7.0 [1] (👁️) eye
+1F442..1F4F7 ; Extended_Pictographic# 6.0[182] (👂..📷) ear..camera
+1F4F8 ; Extended_Pictographic# 7.0 [1] (📸) camera with flash
+1F4F9..1F4FC ; Extended_Pictographic# 6.0 [4] (📹..📼) video camera..videocassette
+1F4FD..1F4FE ; Extended_Pictographic# 7.0 [2] (📽️..📾️) film projector..PORTABLE STEREO
+1F4FF ; Extended_Pictographic# 8.0 [1] (📿) prayer beads
+1F500..1F53D ; Extended_Pictographic# 6.0 [62] (🔀..🔽) shuffle tracks button..downwards button
+1F546..1F54A ; Extended_Pictographic# 7.0 [5] (🕆️..🕊️) WHITE LATIN CROSS..dove
+1F54B..1F54F ; Extended_Pictographic# 8.0 [5] (🕋..🕏️) kaaba..BOWL OF HYGIEIA
+1F550..1F567 ; Extended_Pictographic# 6.0 [24] (🕐..🕧) one o’clock..twelve-thirty
+1F568..1F579 ; Extended_Pictographic# 7.0 [18] (🕨️..🕹️) RIGHT SPEAKER..joystick
+1F57A ; Extended_Pictographic# 9.0 [1] (🕺) man dancing
+1F57B..1F5A3 ; Extended_Pictographic# 7.0 [41] (🕻️..🖣️) LEFT HAND TELEPHONE RECEIVER..BLACK DOWN POINTING BACKHAND INDEX
+1F5A4 ; Extended_Pictographic# 9.0 [1] (🖤) black heart
+1F5A5..1F5FA ; Extended_Pictographic# 7.0 [86] (🖥️..🗺️) desktop computer..world map
+1F5FB..1F5FF ; Extended_Pictographic# 6.0 [5] (🗻..🗿) mount fuji..moai
+1F600 ; Extended_Pictographic# 6.1 [1] (😀) grinning face
+1F601..1F610 ; Extended_Pictographic# 6.0 [16] (😁..😐) beaming face with smiling eyes..neutral face
+1F611 ; Extended_Pictographic# 6.1 [1] (😑) expressionless face
+1F612..1F614 ; Extended_Pictographic# 6.0 [3] (😒..😔) unamused face..pensive face
+1F615 ; Extended_Pictographic# 6.1 [1] (😕) confused face
+1F616 ; Extended_Pictographic# 6.0 [1] (😖) confounded face
+1F617 ; Extended_Pictographic# 6.1 [1] (😗) kissing face
+1F618 ; Extended_Pictographic# 6.0 [1] (😘) face blowing a kiss
+1F619 ; Extended_Pictographic# 6.1 [1] (😙) kissing face with smiling eyes
+1F61A ; Extended_Pictographic# 6.0 [1] (😚) kissing face with closed eyes
+1F61B ; Extended_Pictographic# 6.1 [1] (😛) face with tongue
+1F61C..1F61E ; Extended_Pictographic# 6.0 [3] (😜..😞) winking face with tongue..disappointed face
+1F61F ; Extended_Pictographic# 6.1 [1] (😟) worried face
+1F620..1F625 ; Extended_Pictographic# 6.0 [6] (😠..😥) angry face..sad but relieved face
+1F626..1F627 ; Extended_Pictographic# 6.1 [2] (😦..😧) frowning face with open mouth..anguished face
+1F628..1F62B ; Extended_Pictographic# 6.0 [4] (😨..😫) fearful face..tired face
+1F62C ; Extended_Pictographic# 6.1 [1] (😬) grimacing face
+1F62D ; Extended_Pictographic# 6.0 [1] (😭) loudly crying face
+1F62E..1F62F ; Extended_Pictographic# 6.1 [2] (😮..😯) face with open mouth..hushed face
+1F630..1F633 ; Extended_Pictographic# 6.0 [4] (😰..😳) anxious face with sweat..flushed face
+1F634 ; Extended_Pictographic# 6.1 [1] (😴) sleeping face
+1F635..1F640 ; Extended_Pictographic# 6.0 [12] (😵..🙀) dizzy face..weary cat face
+1F641..1F642 ; Extended_Pictographic# 7.0 [2] (🙁..🙂) slightly frowning face..slightly smiling face
+1F643..1F644 ; Extended_Pictographic# 8.0 [2] (🙃..🙄) upside-down face..face with rolling eyes
+1F645..1F64F ; Extended_Pictographic# 6.0 [11] (🙅..🙏) person gesturing NO..folded hands
+1F680..1F6C5 ; Extended_Pictographic# 6.0 [70] (🚀..🛅) rocket..left luggage
+1F6C6..1F6CF ; Extended_Pictographic# 7.0 [10] (🛆️..🛏️) TRIANGLE WITH ROUNDED CORNERS..bed
+1F6D0 ; Extended_Pictographic# 8.0 [1] (🛐) place of worship
+1F6D1..1F6D2 ; Extended_Pictographic# 9.0 [2] (🛑..🛒) stop sign..shopping cart
+1F6D3..1F6D4 ; Extended_Pictographic# 10.0 [2] (🛓️..🛔️) STUPA..PAGODA
+1F6D5..1F6DF ; Extended_Pictographic# NA [11] (🛕️..🛟️) <reserved-1F6D5>..<reserved-1F6DF>
+1F6E0..1F6EC ; Extended_Pictographic# 7.0 [13] (🛠️..🛬) hammer and wrench..airplane arrival
+1F6ED..1F6EF ; Extended_Pictographic# NA [3] (🛭️..🛯️) <reserved-1F6ED>..<reserved-1F6EF>
+1F6F0..1F6F3 ; Extended_Pictographic# 7.0 [4] (🛰️..🛳️) satellite..passenger ship
+1F6F4..1F6F6 ; Extended_Pictographic# 9.0 [3] (🛴..🛶) kick scooter..canoe
+1F6F7..1F6F8 ; Extended_Pictographic# 10.0 [2] (🛷..🛸) sled..flying saucer
+1F6F9 ; Extended_Pictographic# 11.0 [1] (🛹) skateboard
+1F6FA..1F6FF ; Extended_Pictographic# NA [6] (🛺️..🛿️) <reserved-1F6FA>..<reserved-1F6FF>
+1F774..1F77F ; Extended_Pictographic# NA [12] (🝴️..🝿️) <reserved-1F774>..<reserved-1F77F>
+1F7D5..1F7D8 ; Extended_Pictographic# 11.0 [4] (🟕️..🟘️) CIRCLED TRIANGLE..NEGATIVE CIRCLED SQUARE
+1F7D9..1F7FF ; Extended_Pictographic# NA [39] (🟙️..🟿️) <reserved-1F7D9>..<reserved-1F7FF>
+1F80C..1F80F ; Extended_Pictographic# NA [4] (🠌️..🠏️) <reserved-1F80C>..<reserved-1F80F>
+1F848..1F84F ; Extended_Pictographic# NA [8] (🡈️..🡏️) <reserved-1F848>..<reserved-1F84F>
+1F85A..1F85F ; Extended_Pictographic# NA [6] (🡚️..🡟️) <reserved-1F85A>..<reserved-1F85F>
+1F888..1F88F ; Extended_Pictographic# NA [8] (🢈️..🢏️) <reserved-1F888>..<reserved-1F88F>
+1F8AE..1F8FF ; Extended_Pictographic# NA [82] (🢮️..🣿️) <reserved-1F8AE>..<reserved-1F8FF>
+1F90C..1F90F ; Extended_Pictographic# NA [4] (🤌️..🤏️) <reserved-1F90C>..<reserved-1F90F>
+1F910..1F918 ; Extended_Pictographic# 8.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns
+1F919..1F91E ; Extended_Pictographic# 9.0 [6] (🤙..🤞) call me hand..crossed fingers
+1F91F ; Extended_Pictographic# 10.0 [1] (🤟) love-you gesture
+1F920..1F927 ; Extended_Pictographic# 9.0 [8] (🤠..🤧) cowboy hat face..sneezing face
+1F928..1F92F ; Extended_Pictographic# 10.0 [8] (🤨..🤯) face with raised eyebrow..exploding head
+1F930 ; Extended_Pictographic# 9.0 [1] (🤰) pregnant woman
+1F931..1F932 ; Extended_Pictographic# 10.0 [2] (🤱..🤲) breast-feeding..palms up together
+1F933..1F93A ; Extended_Pictographic# 9.0 [8] (🤳..🤺) selfie..person fencing
+1F93C..1F93E ; Extended_Pictographic# 9.0 [3] (🤼..🤾) people wrestling..person playing handball
+1F93F ; Extended_Pictographic# NA [1] (🤿️) <reserved-1F93F>
+1F940..1F945 ; Extended_Pictographic# 9.0 [6] (🥀..🥅) wilted flower..goal net
+1F947..1F94B ; Extended_Pictographic# 9.0 [5] (🥇..🥋) 1st place medal..martial arts uniform
+1F94C ; Extended_Pictographic# 10.0 [1] (🥌) curling stone
+1F94D..1F94F ; Extended_Pictographic# 11.0 [3] (🥍..🥏) lacrosse..flying disc
+1F950..1F95E ; Extended_Pictographic# 9.0 [15] (🥐..🥞) croissant..pancakes
+1F95F..1F96B ; Extended_Pictographic# 10.0 [13] (🥟..🥫) dumpling..canned food
+1F96C..1F970 ; Extended_Pictographic# 11.0 [5] (🥬..🥰) leafy green..smiling face with 3 hearts
+1F971..1F972 ; Extended_Pictographic# NA [2] (🥱️..🥲️) <reserved-1F971>..<reserved-1F972>
+1F973..1F976 ; Extended_Pictographic# 11.0 [4] (🥳..🥶) partying face..cold face
+1F977..1F979 ; Extended_Pictographic# NA [3] (🥷️..🥹️) <reserved-1F977>..<reserved-1F979>
+1F97A ; Extended_Pictographic# 11.0 [1] (🥺) pleading face
+1F97B ; Extended_Pictographic# NA [1] (🥻️) <reserved-1F97B>
+1F97C..1F97F ; Extended_Pictographic# 11.0 [4] (🥼..🥿) lab coat..woman’s flat shoe
+1F980..1F984 ; Extended_Pictographic# 8.0 [5] (🦀..🦄) crab..unicorn face
+1F985..1F991 ; Extended_Pictographic# 9.0 [13] (🦅..🦑) eagle..squid
+1F992..1F997 ; Extended_Pictographic# 10.0 [6] (🦒..🦗) giraffe..cricket
+1F998..1F9A2 ; Extended_Pictographic# 11.0 [11] (🦘..🦢) kangaroo..swan
+1F9A3..1F9AF ; Extended_Pictographic# NA [13] (🦣️..🦯️) <reserved-1F9A3>..<reserved-1F9AF>
+1F9B0..1F9B9 ; Extended_Pictographic# 11.0 [10] (🦰..🦹) red-haired..supervillain
+1F9BA..1F9BF ; Extended_Pictographic# NA [6] (🦺️..🦿️) <reserved-1F9BA>..<reserved-1F9BF>
+1F9C0 ; Extended_Pictographic# 8.0 [1] (🧀) cheese wedge
+1F9C1..1F9C2 ; Extended_Pictographic# 11.0 [2] (🧁..🧂) cupcake..salt
+1F9C3..1F9CF ; Extended_Pictographic# NA [13] (🧃️..🧏️) <reserved-1F9C3>..<reserved-1F9CF>
+1F9D0..1F9E6 ; Extended_Pictographic# 10.0 [23] (🧐..🧦) face with monocle..socks
+1F9E7..1F9FF ; Extended_Pictographic# 11.0 [25] (🧧..🧿) red envelope..nazar amulet
+1FA00..1FA5F ; Extended_Pictographic# NA [96] (🨀️..🩟️) <reserved-1FA00>..<reserved-1FA5F>
+1FA60..1FA6D ; Extended_Pictographic# 11.0 [14] (🩠️..🩭️) XIANGQI RED GENERAL..XIANGQI BLACK SOLDIER
+1FA6E..1FFFD ; Extended_Pictographic# NA[1424] (🩮️..🿽️) <reserved-1FA6E>..<reserved-1FFFD>
+
+# Total elements: 3793
+
+#EOF
diff --git a/lib/stdlib/uc_spec/gen_unicode_mod.escript b/lib/stdlib/uc_spec/gen_unicode_mod.escript
index 535f01a1c5..70eec1a6f2 100755
--- a/lib/stdlib/uc_spec/gen_unicode_mod.escript
+++ b/lib/stdlib/uc_spec/gen_unicode_mod.escript
@@ -48,13 +48,18 @@ main(_) ->
ok = file:close(ExclF),
%% GraphemeBreakProperty table
+ {ok, Emoji} = file:open("../uc_spec/emoji-data.txt", [read, raw, {read_ahead, 1000000}]),
+ 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}]),
- Props0 = foldl(fun parse_properties/2, [], GBPF),
+ 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}]),
- Props1 = foldl(fun parse_properties/2, Props0, PropF),
+ Props2 = foldl(fun parse_properties/2, Props1, PropF),
ok = file:close(PropF),
- Props = sofs:to_external(sofs:relation_to_family(sofs:relation(Props1))),
+ Props = sofs:to_external(sofs:relation_to_family(sofs:relation(Props2))),
%% Make module
{ok, Out} = file:open(?MOD++".erl", [write]),
@@ -170,7 +175,7 @@ gen_header(Fd) ->
io:put_chars(Fd, "-export([spec_version/0, lookup/1, get_case/1]).\n"),
io:put_chars(Fd, "-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, gc_e_cont/2]}).\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"),
ok.
@@ -186,7 +191,7 @@ gen_static(Fd) ->
" {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() -> {10,0}.\n\n\n"),
+ io:put_chars(Fd, "spec_version() -> {11,0}.\n\n\n"),
io:put_chars(Fd, "class(Codepoint) -> {CCC,_,_} = unicode_table(Codepoint),\n CCC.\n\n"),
io:put_chars(Fd, "-spec uppercase(unicode:chardata()) -> "
"maybe_improper_list(gc(),unicode:chardata()).\n"),
@@ -495,33 +500,34 @@ gen_gc(Fd, GBP) ->
" [$\\n|R1] -> [[$\\r,$\\n]|R1];\n"
" _ -> R\n"
" end;\n"
- %% "gc_1([CP1, CP2|_]=T) when CP1 < 256, CP2 < 256 ->\n"
- %% " T; %% Fast path\n"
- %% "gc_1([CP1|<<CP2/utf8, _/binary>>]=T) when CP1 < 256, CP2 < 256 ->\n"
- %% " T; %% Fast path\n"
),
- io:put_chars(Fd, "%% Handle control\n"),
+ GenExtP = fun(Range) -> io:format(Fd, "gc_1~s gc_ext_pict(R1,[CP]);\n", [gen_clause(Range)]) end,
+ ExtendedPictographic0 = merge_ranges(maps:get(extended_pictographic,GBP)),
+ %% Pick codepoints below 256 (some data knowledge here)
+ {ExtendedPictographicLow,ExtendedPictographicHigh} =
+ lists:splitwith(fun({Start,undefined}) -> Start < 256 end,ExtendedPictographic0),
+
+ io:put_chars(Fd, "\n%% Handle control\n"),
GenControl = fun(Range) -> io:format(Fd, "gc_1~s R0;\n", [gen_clause(Range)]) end,
CRs0 = merge_ranges(maps:get(cr, GBP) ++ maps:get(lf, GBP) ++ maps:get(control, GBP), false),
[R1,R2,R3|Crs] = CRs0,
[GenControl(CP) || CP <- merge_ranges([R1,R2,R3], split), CP =/= {$\r, undefined}],
%%GenControl(R1),GenControl(R2),GenControl(R3),
- io:format(Fd, "gc_1([CP|R]) when CP < 256 -> gc_extend(R,CP);\n", []),
+ io:put_chars(Fd, "\n%% Optimize Latin-1\n"),
+ [GenExtP(CP) || CP <- merge_ranges(ExtendedPictographicLow)],
+ io:format(Fd, "gc_1([CP|R]) when CP < 256 -> gc_extend(R,CP);\n\n", []),
+ io:put_chars(Fd, "\n%% Continue control\n"),
[GenControl(CP) || CP <- Crs],
%% One clause per CP
%% CRs0 = merge_ranges(maps:get(cr, GBP) ++ maps:get(lf, GBP) ++ maps:get(control, GBP)),
%% [GenControl(CP) || CP <- CRs0, CP =/= {$\r, undefined}],
- io:put_chars(Fd, "%% Handle ZWJ\n"),
- GenZWJ = fun(Range) -> io:format(Fd, "gc_1~s gc_zwj(R1, [CP]);\n", [gen_clause(Range)]) end,
- [GenZWJ(CP) || CP <- merge_ranges(maps:get(zwj,GBP))],
-
- io:put_chars(Fd, "%% Handle prepend\n"),
+ io:put_chars(Fd, "\n%% Handle prepend\n"),
GenPrepend = fun(Range) -> io:format(Fd, "gc_1~s gc_prepend(R1, CP);\n", [gen_clause(Range)]) end,
[GenPrepend(CP) || CP <- merge_ranges(maps:get(prepend,GBP))],
- io:put_chars(Fd, "%% Handle Hangul L\n"),
+ io:put_chars(Fd, "\n%% Handle Hangul L\n"),
GenHangulL = fun(Range) -> io:format(Fd, "gc_1~s gc_h_L(R1,[CP]);\n", [gen_clause(Range)]) end,
[GenHangulL(CP) || CP <- merge_ranges(maps:get(l,GBP))],
io:put_chars(Fd, "%% Handle Hangul V\n"),
@@ -533,16 +539,19 @@ gen_gc(Fd, 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, []);\n"),
- io:put_chars(Fd, "%% Handle Regional\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,
[GenRegional(CP) || CP <- merge_ranges(maps:get(regional_indicator,GBP))],
- io:put_chars(Fd, "%% Handle E_Base\n"),
- GenEBase = fun(Range) -> io:format(Fd, "gc_1~s gc_e_cont(R1,[CP]);\n", [gen_clause(Range)]) end,
- [GenEBase(CP) || CP <- merge_ranges(maps:get(e_base,GBP))],
- io:put_chars(Fd, "%% Handle EBG\n"),
- GenEBG = fun(Range) -> io:format(Fd, "gc_1~s gc_e_cont(R1,[CP]);\n", [gen_clause(Range)]) end,
- [GenEBG(CP) || CP <- merge_ranges(maps:get(e_base_gaz,GBP))],
-
+ %% io:put_chars(Fd, "%% Handle E_Base\n"),
+ %% GenEBase = fun(Range) -> io:format(Fd, "gc_1~s gc_e_cont(R1,[CP]);\n", [gen_clause(Range)]) end,
+ %% [GenEBase(CP) || CP <- merge_ranges(maps:get(e_base,GBP))],
+ %% io:put_chars(Fd, "%% Handle EBG\n"),
+ %% GenEBG = fun(Range) -> io:format(Fd, "gc_1~s gc_e_cont(R1,[CP]);\n", [gen_clause(Range)]) end,
+ %% [GenEBG(CP) || CP <- merge_ranges(maps:get(e_base_gaz,GBP))],
+
+ io:put_chars(Fd, "%% Handle extended_pictographic\n"),
+ [GenExtP(CP) || CP <- merge_ranges(ExtendedPictographicHigh)],
+ io:put_chars(Fd, "\n%% default clauses\n"),
io:put_chars(Fd, "gc_1([CP|R]) -> gc_extend(R, CP);\n"),
io:put_chars(Fd, "gc_1([]) -> [];\n"),
io:put_chars(Fd, "gc_1({error,_}=Error) -> Error.\n\n"),
@@ -577,21 +586,16 @@ gen_gc(Fd, GBP) ->
io:put_chars(Fd,
"gc_extend([CP|T], T0, Acc0) ->\n"
" case is_extend(CP) of\n"
- " zwj ->\n"
- " case Acc0 of\n"
- " [_|_] -> gc_zwj(T, [CP|Acc0]);\n"
- " Acc -> gc_zwj(T, [CP,Acc])\n"
- " end;\n"
- " true ->\n"
- " case Acc0 of\n"
- " [_|_] -> gc_extend(T, [CP|Acc0]);\n"
- " Acc -> gc_extend(T, [CP,Acc])\n"
- " end;\n"
" false ->\n"
" case Acc0 of\n"
" [Acc] -> [Acc|T0];\n"
" [_|_]=Acc -> [lists:reverse(Acc)|T0];\n"
" Acc -> [Acc|T0]\n"
+ " end;\n"
+ " _TrueOrZWJ ->\n"
+ " case Acc0 of\n"
+ " [_|_] -> gc_extend(T, [CP|Acc0]);\n"
+ " Acc -> gc_extend(T, [CP,Acc])\n"
" end\n"
" end;\n"
"gc_extend([], _, Acc0) ->\n"
@@ -612,49 +616,46 @@ gen_gc(Fd, GBP) ->
io:put_chars(Fd, "is_extend(_) -> false.\n\n"),
io:put_chars(Fd,
- "gc_e_cont(R0, Acc) ->\n"
- " case cp(R0) of\n"
- " [CP|R1] ->\n"
- " case is_extend(CP) of\n"
- " zwj -> gc_zwj(R1, [CP|Acc]);\n"
- " true -> gc_e_cont(R1, [CP|Acc]);\n"
- " false ->\n"
- " case is_emodifier(CP) of\n"
- " true -> [lists:reverse([CP|Acc])|R1];\n"
- " false ->\n"
- " case Acc of\n"
- " [A] -> [A|R0];\n"
- " _ -> [lists:reverse(Acc)|R0]\n"
- " end\n"
- " end\n"
- " end;\n"
- " [] ->\n"
+ "gc_ext_pict(T, Acc) ->\n"
+ " gc_ext_pict(cp(T), T, Acc).\n\n"
+ "gc_ext_pict([CP|R1], T0, Acc) ->\n"
+ " case is_extend(CP) of\n"
+ " zwj -> gc_ext_pict_zwj(cp(R1), R1, [CP|Acc]);\n"
+ " true -> gc_ext_pict(R1, [CP|Acc]);\n"
+ " false ->\n"
" case Acc of\n"
- " [A] -> [A];\n"
- " _ -> [lists:reverse(Acc)]\n"
- " end;\n"
- " {error,R} ->\n"
+ " [A] -> [A|T0];\n"
+ " _ -> [lists:reverse(Acc)|T0]\n"
+ " end\n"
+ " end;\n"
+ "gc_ext_pict([], _T0, Acc) ->\n"
+ " case Acc of\n"
+ " [A] -> [A];\n"
+ " _ -> [lists:reverse(Acc)]\n"
+ " end;\n"
+ "gc_ext_pict({error,R}, T, Acc) ->\n"
+ " gc_ext_pict([], T, Acc) ++ [R].\n\n"),
+ io:put_chars(Fd,
+ "gc_ext_pict_zwj([CP|R1], T0, Acc) ->\n"
+ " case is_ext_pict(CP) of\n"
+ " true -> gc_ext_pict(R1, [CP|Acc]);\n"
+ " false ->\n"
" case Acc of\n"
- " [A] -> [A|R];\n"
- " _ -> [lists:reverse(Acc)|R]\n"
+ " [A] -> [A|T0];\n"
+ " _ -> [lists:reverse(Acc)|T0]\n"
" end\n"
- " end.\n\n"),
-
- GenEMod = fun(Range) -> io:format(Fd, "is_emodifier~s true;\n", [gen_single_clause(Range)]) end,
- EMods = merge_ranges(maps:get(e_modifier, GBP), split),
- [GenEMod(CP) || CP <- EMods],
- io:put_chars(Fd, "is_emodifier(_) -> false.\n\n"),
-
- io:put_chars(Fd, "gc_zwj(R0, Acc) ->\n case cp(R0) of\n"),
- GenZWJGlue = fun(Range) -> io:format(Fd, "~8c~s gc_extend(cp(R1), R0, [CP|Acc]);\n",
- [$\s,gen_case_clause(Range)]) end,
- [GenZWJGlue(CP) || CP <- merge_ranges(maps:get(glue_after_zwj,GBP))],
- GenZWJEBG = fun(Range) -> io:format(Fd, "~8c~s gc_e_cont(R1, [CP|Acc]);\n",
- [$\s,gen_case_clause(Range)]) end,
- [GenZWJEBG(CP) || CP <- merge_ranges(maps:get(e_base_gaz,GBP))],
- io:put_chars(Fd," R1 -> gc_extend(R1, R0, Acc)\n"
- " end.\n\n"),
+ " end;\n"
+ "gc_ext_pict_zwj([], _, Acc) ->\n"
+ " case Acc of\n"
+ " [A] -> [A];\n"
+ " _ -> [lists:reverse(Acc)]\n"
+ " end;\n"
+ "gc_ext_pict_zwj({error,R}, T, Acc) ->\n"
+ " gc_ext_pict_zwj([], T, Acc) ++ [R].\n\n"),
+ GenExtPict = fun(Range) -> io:format(Fd, "is_ext_pict~s true;\n", [gen_single_clause(Range)]) end,
+ [GenExtPict(CP) || CP <- ExtendedPictographic0],
+ io:put_chars(Fd, "is_ext_pict(_) -> false.\n\n"),
%% --------------------
io:put_chars(Fd, "%% Handle Regional\n"),
diff --git a/lib/stdlib/vsn.mk b/lib/stdlib/vsn.mk
index e0217418fe..d46173497b 100644
--- a/lib/stdlib/vsn.mk
+++ b/lib/stdlib/vsn.mk
@@ -1 +1 @@
-STDLIB_VSN = 3.7
+STDLIB_VSN = 3.7.1
diff --git a/lib/syntax_tools/src/erl_prettypr.erl b/lib/syntax_tools/src/erl_prettypr.erl
index 6906ef1553..6ad9bec2e6 100644
--- a/lib/syntax_tools/src/erl_prettypr.erl
+++ b/lib/syntax_tools/src/erl_prettypr.erl
@@ -1101,8 +1101,9 @@ lay_2(Node, Ctxt) ->
Ctxt1 = reset_prec(Ctxt),
D1 = lay(erl_syntax:constrained_function_type_body(Node),
Ctxt1),
+ Ctxt2 = Ctxt1#ctxt{clause = undefined},
D2 = lay(erl_syntax:constrained_function_type_argument(Node),
- Ctxt1),
+ Ctxt2),
beside(D1,
beside(floating(text(" when ")), D2));
@@ -1113,7 +1114,7 @@ lay_2(Node, Ctxt) ->
_ ->
{"fun(", ")"}
end,
- Ctxt1 = reset_prec(Ctxt),
+ Ctxt1 = (reset_prec(Ctxt))#ctxt{clause = undefined},
D1 = case erl_syntax:function_type_arguments(Node) of
any_arity ->
text("(...)");
diff --git a/lib/syntax_tools/src/igor.erl b/lib/syntax_tools/src/igor.erl
index 16e3511734..b712b77e9f 100644
--- a/lib/syntax_tools/src/igor.erl
+++ b/lib/syntax_tools/src/igor.erl
@@ -660,7 +660,7 @@ merge_files1(Files, Opts) ->
%% transitions), code replacement is expected to be detected. Then, if
%% we in the merged code do not check at these points if the
%% <em>target</em> module (the result of the merge) has been replaced,
-%% we can not be sure in general that we will be able to do code
+%% we cannot be sure in general that we will be able to do code
%% replacement of the merged state machine - it could run forever
%% without detecting the code change. Therefore, all such calls must
%% remain remote-calls (detecting code changes), but may call the target
diff --git a/lib/syntax_tools/test/syntax_tools_SUITE.erl b/lib/syntax_tools/test/syntax_tools_SUITE.erl
index 9dbd0e302a..6b42f7a0a1 100644
--- a/lib/syntax_tools/test/syntax_tools_SUITE.erl
+++ b/lib/syntax_tools/test/syntax_tools_SUITE.erl
@@ -26,14 +26,14 @@
-export([app_test/1,appup_test/1,smoke_test/1,revert/1,revert_map/1,
revert_map_type/1,
t_abstract_type/1,t_erl_parse_type/1,t_type/1, t_epp_dodger/1,
- t_comment_scan/1,t_igor/1,t_erl_tidy/1]).
+ t_comment_scan/1,t_igor/1,t_erl_tidy/1,t_prettypr/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[app_test,appup_test,smoke_test,revert,revert_map,revert_map_type,
t_abstract_type,t_erl_parse_type,t_type,t_epp_dodger,
- t_comment_scan,t_igor,t_erl_tidy].
+ t_comment_scan,t_igor,t_erl_tidy,t_prettypr].
groups() ->
[].
@@ -300,6 +300,14 @@ t_comment_scan(Config) when is_list(Config) ->
ok = test_comment_scan(Filenames,DataDir),
ok.
+t_prettypr(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+ PrivDir = ?config(priv_dir, Config),
+ Filenames = ["type_specs.erl",
+ "specs_and_funs.erl"],
+ ok = test_prettypr(Filenames,DataDir,PrivDir),
+ ok.
+
test_files(Config) ->
DataDir = ?config(data_dir, Config),
[ filename:join(DataDir,Filename) || Filename <- test_files() ].
@@ -307,7 +315,8 @@ test_files(Config) ->
test_files() ->
["syntax_tools_SUITE_test_module.erl",
"syntax_tools_test.erl",
- "type_specs.erl"].
+ "type_specs.erl",
+ "specs_and_funs.erl"].
t_igor(Config) when is_list(Config) ->
DataDir = ?config(data_dir, Config),
@@ -359,6 +368,27 @@ test_comment_scan([File|Files],DataDir) ->
test_comment_scan(Files,DataDir).
+test_prettypr([],_,_) -> ok;
+test_prettypr([File|Files],DataDir,PrivDir) ->
+ Filename = filename:join(DataDir,File),
+ io:format("Parsing ~p~n", [Filename]),
+ {ok, Fs0} = epp:parse_file(Filename, [], []),
+ Fs = erl_syntax:form_list(Fs0),
+ PP = erl_prettypr:format(Fs, [{paper, 120}, {ribbon, 110}]),
+ io:put_chars(PP),
+ OutFile = filename:join(PrivDir, File),
+ ok = file:write_file(OutFile,iolist_to_binary(PP)),
+ io:format("Parsing OutFile: ~s~n", [OutFile]),
+ {ok, Fs2} = epp:parse_file(OutFile, [], []),
+ case [Error || {error, _} = Error <- Fs2] of
+ [] ->
+ ok;
+ Errors ->
+ ?t:fail(Errors)
+ end,
+ test_prettypr(Files,DataDir,PrivDir).
+
+
test_epp_dodger([], _, _) -> ok;
test_epp_dodger([Filename|Files],DataDir,PrivDir) ->
io:format("Parsing ~p~n", [Filename]),
diff --git a/lib/syntax_tools/test/syntax_tools_SUITE_data/specs_and_funs.erl b/lib/syntax_tools/test/syntax_tools_SUITE_data/specs_and_funs.erl
new file mode 100644
index 0000000000..8dfeaf5a6b
--- /dev/null
+++ b/lib/syntax_tools/test/syntax_tools_SUITE_data/specs_and_funs.erl
@@ -0,0 +1,18 @@
+-module(specs_and_funs).
+
+-export([my_apply/3, two/1]).
+
+%% OTP-15519, ERL-815
+
+-spec my_apply(Fun, Arg, fun((A) -> A)) -> Result when
+ Fun :: fun((Arg) -> Result),
+ Arg :: any(),
+ Result :: any().
+
+my_apply(Fun, Arg, _) ->
+ Fun(Arg).
+
+-spec two(fun((A) -> A)) -> fun((B) -> B).
+
+two(F) ->
+ F(fun(X) -> X end).
diff --git a/lib/tftp/doc/src/tftp.xml b/lib/tftp/doc/src/tftp.xml
index 4ed54bc462..57d64b7379 100644
--- a/lib/tftp/doc/src/tftp.xml
+++ b/lib/tftp/doc/src/tftp.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>tftp</module>
+ <module since="">tftp</module>
<modulesummary>Trivial FTP.</modulesummary>
<description>
<p>Interface module for the <c>tftp</c> application.</p>
@@ -172,7 +172,7 @@
<funcs>
<func>
- <name>change_config(daemons, Options) -> [{Pid, Result}]</name>
+ <name since="">change_config(daemons, Options) -> [{Pid, Result}]</name>
<fsummary>Changes configuration for all daemons.
</fsummary>
<type>
@@ -187,7 +187,7 @@
</func>
<func>
- <name>change_config(servers, Options) -> [{Pid, Result}]</name>
+ <name since="">change_config(servers, Options) -> [{Pid, Result}]</name>
<fsummary>Changes configuration for all servers.
</fsummary>
<type>
@@ -202,7 +202,7 @@
</func>
<func>
- <name>change_config(Pid, Options) -> Result</name>
+ <name since="">change_config(Pid, Options) -> Result</name>
<fsummary>Changes configuration for a TFTP daemon, server,
or client process.</fsummary>
<type>
@@ -217,7 +217,7 @@
</func>
<func>
- <name>info(daemons) -> [{Pid, Options}]</name>
+ <name since="">info(daemons) -> [{Pid, Options}]</name>
<fsummary>Returns information about all daemons.</fsummary>
<type>
<v>Pid = [pid()]</v>
@@ -230,7 +230,7 @@
</func>
<func>
- <name>info(servers) -> [{Pid, Options}]</name>
+ <name since="">info(servers) -> [{Pid, Options}]</name>
<fsummary>Returns information about all servers.</fsummary>
<type>
<v>Pid = [pid()]</v>
@@ -243,7 +243,7 @@
</func>
<func>
- <name>info(Pid) -> {ok, Options} | {error, Reason}</name>
+ <name since="">info(Pid) -> {ok, Options} | {error, Reason}</name>
<fsummary>Returns information about a daemon, server, or client process.</fsummary>
<type>
<v>Options = [option()]</v>
@@ -255,7 +255,7 @@
</func>
<func>
- <name>read_file(RemoteFilename, LocalFilename, Options) -> {ok, LastCallbackState} | {error, Reason}</name>
+ <name since="">read_file(RemoteFilename, LocalFilename, Options) -> {ok, LastCallbackState} | {error, Reason}</name>
<fsummary>Reads a (virtual) file from a TFTP server.</fsummary>
<type>
<v>RemoteFilename = string()</v>
@@ -285,7 +285,7 @@
</func>
<func>
- <name>start(Options) -> {ok, Pid} | {error, Reason}</name>
+ <name since="">start(Options) -> {ok, Pid} | {error, Reason}</name>
<fsummary>Starts a daemon process.</fsummary>
<type>
<v>Options = [option()]</v>
@@ -301,7 +301,7 @@
</func>
<func>
- <name>write_file(RemoteFilename, LocalFilename, Options) -> {ok, LastCallbackState} | {error, Reason}</name>
+ <name since="">write_file(RemoteFilename, LocalFilename, Options) -> {ok, LastCallbackState} | {error, Reason}</name>
<fsummary>Writes a (virtual) file to a TFTP server.</fsummary>
<type>
<v>RemoteFilename = string()</v>
@@ -389,7 +389,7 @@
<funcs>
<func>
- <name>Module:abort(Code, Text, State) -> ok</name>
+ <name since="OTP 18.1">Module:abort(Code, Text, State) -> ok</name>
<fsummary>Aborts the file transfer.</fsummary>
<type>
<v>Code = undef | enoent | eacces | enospc</v>
@@ -413,7 +413,7 @@
</func>
<func>
- <name>Module:open(Peer, Access, Filename, Mode, SuggestedOptions, State) -> {ok, AcceptedOptions, NewState} | {error, {Code, Text}}</name>
+ <name since="OTP 18.1">Module:open(Peer, Access, Filename, Mode, SuggestedOptions, State) -> {ok, AcceptedOptions, NewState} | {error, {Code, Text}}</name>
<fsummary>Opens a file for read or write access.</fsummary>
<type>
<v>Peer = {PeerType, PeerHost, PeerPort}</v>
@@ -448,7 +448,7 @@
</func>
<func>
- <name>Module:prepare(Peer, Access, Filename, Mode, SuggestedOptions, InitialState) -> {ok, AcceptedOptions, NewState} | {error, {Code, Text}}</name>
+ <name since="OTP 18.1">Module:prepare(Peer, Access, Filename, Mode, SuggestedOptions, InitialState) -> {ok, AcceptedOptions, NewState} | {error, {Code, Text}}</name>
<fsummary>Prepares to open a file on the client side.</fsummary>
<type>
<v>Peer = {PeerType, PeerHost, PeerPort}</v>
@@ -483,7 +483,7 @@
</func>
<func>
- <name>Module:read(State) -> {more, Bin, NewState} | {last, Bin, FileSize} | {error, {Code, Text}}</name>
+ <name since="OTP 18.1">Module:read(State) -> {more, Bin, NewState} | {last, Bin, FileSize} | {error, {Code, Text}}</name>
<fsummary>Reads a chunk from the file.</fsummary>
<type>
<v>State = NewState = term()</v>
@@ -510,7 +510,7 @@
</func>
<func>
- <name>Module:write(Bin, State) -> {more, NewState} | {last, FileSize} | {error, {Code, Text}}</name>
+ <name since="OTP 18.1">Module:write(Bin, State) -> {more, NewState} | {last, FileSize} | {error, {Code, Text}}</name>
<fsummary>Writes a chunk to the file.</fsummary>
<type>
<v>Bin = binary()</v>
@@ -549,7 +549,7 @@
<funcs>
<func>
- <name>Logger:error_msg(Format, Data) -> ok | exit(Reason)</name>
+ <name since="OTP 18.1">Logger:error_msg(Format, Data) -> ok | exit(Reason)</name>
<fsummary>Logs an error message.</fsummary>
<type>
<v>Format = string()</v>
@@ -565,7 +565,7 @@
</func>
<func>
- <name>Logger:info_msg(Format, Data) -> ok | exit(Reason)</name>
+ <name since="OTP 18.1">Logger:info_msg(Format, Data) -> ok | exit(Reason)</name>
<fsummary>Logs an info message.</fsummary>
<type>
<v>Format = string()</v>
@@ -579,7 +579,7 @@
</func>
<func>
- <name>Logger:warning_msg(Format, Data) -> ok | exit(Reason)</name>
+ <name since="OTP 18.1">Logger:warning_msg(Format, Data) -> ok | exit(Reason)</name>
<fsummary>Logs a warning message.</fsummary>
<type>
<v>Format = string()</v>
diff --git a/lib/tools/doc/src/cover.xml b/lib/tools/doc/src/cover.xml
index 15cd784253..e9f782977d 100644
--- a/lib/tools/doc/src/cover.xml
+++ b/lib/tools/doc/src/cover.xml
@@ -30,7 +30,7 @@
<date></date>
<rev></rev>
</header>
- <module>cover</module>
+ <module since="">cover</module>
<modulesummary>A Coverage Analysis Tool for Erlang</modulesummary>
<description>
<p>The module <c>cover</c> provides a set of functions for coverage
@@ -115,7 +115,7 @@
</description>
<funcs>
<func>
- <name>start() -> {ok,Pid} | {error,Reason}</name>
+ <name since="">start() -> {ok,Pid} | {error,Reason}</name>
<fsummary>Start Cover.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -128,21 +128,33 @@
</desc>
</func>
<func>
- <name>start(Nodes) -> {ok,StartedNodes} | {error,not_main_node}</name>
+ <name since="OTP 22.0">local_only() -> ok | {error,too_late}</name>
+ <fsummary>Only support running Cover on the local node.</fsummary>
+ <desc>
+ <p>Only support running Cover on the local node. This function
+ must be called before any modules have been compiled or any
+ nodes added. When running in this mode, modules will be Cover
+ compiled in a more efficient way, but the resulting code will
+ only work on the same node they were compiled on.</p>
+ </desc>
+ </func>
+ <func>
+ <name since="">start(Nodes) -> {ok,StartedNodes} | {error,not_main_node} | {error,local_only}</name>
<fsummary>Start Cover on remote nodes.</fsummary>
<type>
<v>Nodes = StartedNodes = [atom()]</v>
</type>
<desc>
<p>Starts a Cover server on the each of given nodes, and loads
- all cover compiled modules.</p>
+ all cover compiled modules. This call will fail if
+ <c>cover:local_only/0</c> has been called.</p>
</desc>
</func>
<func>
- <name>compile(ModFiles) -> Result | [Result]</name>
- <name>compile(ModFiles, Options) -> Result | [Result]</name>
- <name>compile_module(ModFiles) -> Result | [Result]</name>
- <name>compile_module(ModFiles, Options) -> Result | [Result]</name>
+ <name since="">compile(ModFiles) -> Result | [Result]</name>
+ <name since="">compile(ModFiles, Options) -> Result | [Result]</name>
+ <name since="">compile_module(ModFiles) -> Result | [Result]</name>
+ <name since="">compile_module(ModFiles, Options) -> Result | [Result]</name>
<fsummary>Compile one or more modules for Cover analysis.</fsummary>
<type>
<v>ModFiles = ModFile | [ModFile]</v>
@@ -176,9 +188,9 @@
</desc>
</func>
<func>
- <name>compile_directory() -> [Result] | {error,Reason}</name>
- <name>compile_directory(Dir) -> [Result] | {error,Reason}</name>
- <name>compile_directory(Dir, Options) -> [Result] | {error,Reason}</name>
+ <name since="">compile_directory() -> [Result] | {error,Reason}</name>
+ <name since="">compile_directory(Dir) -> [Result] | {error,Reason}</name>
+ <name since="">compile_directory(Dir, Options) -> [Result] | {error,Reason}</name>
<fsummary>Compile all modules in a directory for Cover analysis.</fsummary>
<type>
<v>Dir = string()</v>
@@ -199,7 +211,7 @@
</desc>
</func>
<func>
- <name>compile_beam(ModFiles) -> Result | [Result]</name>
+ <name since="">compile_beam(ModFiles) -> Result | [Result]</name>
<fsummary>Compile one or more modules for Cover analysis, using existing beam(s).</fsummary>
<type>
<v>ModFiles = ModFile | [ModFile]</v>
@@ -234,15 +246,15 @@
<c>{already_cover_compiled,no_beam_found,Module}</c> is
returned.</p>
<p><c>{error,BeamFile}</c> is returned if the compiled code
- can not be loaded on the node.</p>
+ cannot be loaded on the node.</p>
<p>If a list of <c>ModFiles</c> is given as input, a list
of <c>Result</c> will be returned. The order of the returned
list is undefined.</p>
</desc>
</func>
<func>
- <name>compile_beam_directory() -> [Result] | {error,Reason}</name>
- <name>compile_beam_directory(Dir) -> [Result] | {error,Reason}</name>
+ <name since="">compile_beam_directory() -> [Result] | {error,Reason}</name>
+ <name since="">compile_beam_directory(Dir) -> [Result] | {error,Reason}</name>
<fsummary>Compile all .beam files in a directory for Cover analysis.</fsummary>
<type>
<v>Dir = string()</v>
@@ -260,14 +272,14 @@
</desc>
</func>
<func>
- <name>analyse() -> {result,Ok,Fail} | {error,not_main_node}</name>
- <name>analyse(Modules) -> OneResult | {result,Ok,Fail} | {error,not_main_node}</name>
- <name>analyse(Analysis) -> {result,Ok,Fail} | {error,not_main_node}</name>
- <name>analyse(Level) -> {result,Ok,Fail} | {error,not_main_node}</name>
- <name>analyse(Modules, Analysis) -> OneResult | {result,Ok,Fail} | {error,not_main_node}</name>
- <name>analyse(Modules, Level) -> OneResult | {result,Ok,Fail} | {error,not_main_node}</name>
- <name>analyse(Analysis, Level) -> {result,Ok,Fail} | {error,not_main_node}</name>
- <name>analyse(Modules, Analysis, Level) -> OneResult | {result,Ok,Fail} | {error,not_main_node}</name>
+ <name since="OTP 18.0">analyse() -> {result,Ok,Fail} | {error,not_main_node}</name>
+ <name since="">analyse(Modules) -> OneResult | {result,Ok,Fail} | {error,not_main_node}</name>
+ <name since="">analyse(Analysis) -> {result,Ok,Fail} | {error,not_main_node}</name>
+ <name since="">analyse(Level) -> {result,Ok,Fail} | {error,not_main_node}</name>
+ <name since="">analyse(Modules, Analysis) -> OneResult | {result,Ok,Fail} | {error,not_main_node}</name>
+ <name since="">analyse(Modules, Level) -> OneResult | {result,Ok,Fail} | {error,not_main_node}</name>
+ <name since="">analyse(Analysis, Level) -> {result,Ok,Fail} | {error,not_main_node}</name>
+ <name since="">analyse(Modules, Analysis, Level) -> OneResult | {result,Ok,Fail} | {error,not_main_node}</name>
<fsummary>Analyse one or more Cover compiled modules.</fsummary>
<type>
<v>Modules = Module | [Module]</v>
@@ -305,10 +317,10 @@
</desc>
</func>
<func>
- <name>analyse_to_file() -> {result,Ok,Fail} | {error,not_main_node}</name>
- <name>analyse_to_file(Modules) -> Answer | {result,Ok,Fail} | {error,not_main_node}</name>
- <name>analyse_to_file(Options) -> {result,Ok,Fail} | {error,not_main_node}</name>
- <name>analyse_to_file(Modules,Options) -> Answer | {result,Ok,Fail} | {error,not_main_node}</name>
+ <name since="OTP 18.0">analyse_to_file() -> {result,Ok,Fail} | {error,not_main_node}</name>
+ <name since="">analyse_to_file(Modules) -> Answer | {result,Ok,Fail} | {error,not_main_node}</name>
+ <name since="">analyse_to_file(Options) -> {result,Ok,Fail} | {error,not_main_node}</name>
+ <name since="">analyse_to_file(Modules,Options) -> Answer | {result,Ok,Fail} | {error,not_main_node}</name>
<fsummary>Detailed coverage analysis of one or more Cover compiled modules.</fsummary>
<type>
<v>Modules = Module | [Module]</v>
@@ -359,10 +371,10 @@
</desc>
</func>
<func>
- <name>async_analyse_to_file(Module) -> </name>
- <name>async_analyse_to_file(Module,Options) -> </name>
- <name>async_analyse_to_file(Module, OutFile) -> </name>
- <name>async_analyse_to_file(Module, OutFile, Options) -> pid()</name>
+ <name since="OTP R14B02">async_analyse_to_file(Module) -> </name>
+ <name since="OTP R14B02">async_analyse_to_file(Module,Options) -> </name>
+ <name since="OTP R14B02">async_analyse_to_file(Module, OutFile) -> </name>
+ <name since="OTP R14B02">async_analyse_to_file(Module, OutFile, Options) -> pid()</name>
<fsummary>Asynchronous call to analyse_to_file.</fsummary>
<type>
<v>Module = atom()</v>
@@ -384,7 +396,7 @@
</desc>
</func>
<func>
- <name>modules() -> [Module] | {error,not_main_node}</name>
+ <name since="">modules() -> [Module] | {error,not_main_node}</name>
<fsummary>Return all Cover compiled modules.</fsummary>
<type>
<v>Module = atom()</v>
@@ -395,7 +407,7 @@
</desc>
</func>
<func>
- <name>imported_modules() -> [Module] | {error,not_main_node}</name>
+ <name since="">imported_modules() -> [Module] | {error,not_main_node}</name>
<fsummary>Return all modules for which there are imported data.</fsummary>
<type>
<v>Module = atom()</v>
@@ -406,7 +418,7 @@
</desc>
</func>
<func>
- <name>imported() -> [File] | {error,not_main_node}</name>
+ <name since="">imported() -> [File] | {error,not_main_node}</name>
<fsummary>Return all imported files.</fsummary>
<type>
<v>File = string()</v>
@@ -416,7 +428,7 @@
</desc>
</func>
<func>
- <name>which_nodes() -> [Node] | {error,not_main_node}</name>
+ <name since="">which_nodes() -> [Node] | {error,not_main_node}</name>
<fsummary>Return all nodes that are part of the coverage analysis.</fsummary>
<type>
<v>Node = atom()</v>
@@ -428,7 +440,7 @@
</desc>
</func>
<func>
- <name>is_compiled(Module) -> {file,File} | false | {error,not_main_node}</name>
+ <name since="">is_compiled(Module) -> {file,File} | false | {error,not_main_node}</name>
<fsummary>Check if a module is Cover compiled.</fsummary>
<type>
<v>Module = atom()</v>
@@ -442,8 +454,8 @@
</desc>
</func>
<func>
- <name>reset(Module) -></name>
- <name>reset() -> ok | {error,not_main_node}</name>
+ <name since="">reset(Module) -></name>
+ <name since="">reset() -> ok | {error,not_main_node}</name>
<fsummary>Reset coverage data for Cover compiled modules.</fsummary>
<type>
<v>Module = atom()</v>
@@ -458,8 +470,8 @@
</desc>
</func>
<func>
- <name>export(ExportFile)</name>
- <name>export(ExportFile,Module) -> ok | {error,Reason}</name>
+ <name since="">export(ExportFile)</name>
+ <name since="">export(ExportFile,Module) -> ok | {error,Reason}</name>
<fsummary>Reset coverage data for Cover compiled modules.</fsummary>
<type>
<v>ExportFile = string()</v>
@@ -470,7 +482,7 @@
<p>Exports the current coverage data for <c>Module</c> to the
file <c>ExportFile</c>. It is recommended to name the
<c>ExportFile</c> with the extension <c>.coverdata</c>, since
- other filenames can not be read by the web based interface to
+ other filenames cannot be read by the web based interface to
cover.</p>
<p>If <c>Module</c> is not given, data for all Cover compiled
or earlier imported modules is exported.</p>
@@ -480,7 +492,7 @@
</desc>
</func>
<func>
- <name>import(ExportFile) -> ok | {error,Reason}</name>
+ <name since="">import(ExportFile) -> ok | {error,Reason}</name>
<fsummary>Reset coverage data for Cover compiled modules.</fsummary>
<type>
<v>ExportFile = string()</v>
@@ -496,7 +508,7 @@
<p>Coverage data from several export files can be imported
into one system. The coverage data is then added up when
analysing.</p>
- <p>Coverage data for a module can not be imported from the
+ <p>Coverage data for a module cannot be imported from the
same file twice unless the module is first reset or
compiled. The check is based on the filename, so you can
easily fool the system by renaming your export file.</p>
@@ -504,14 +516,14 @@
</desc>
</func>
<func>
- <name>stop() -> ok | {error,not_main_node}</name>
+ <name since="">stop() -> ok | {error,not_main_node}</name>
<fsummary>Stop Cover.</fsummary>
<desc>
<p>Stops the Cover server and unloads all Cover compiled code.</p>
</desc>
</func>
<func>
- <name>stop(Nodes) -> ok | {error,not_main_node}</name>
+ <name since="">stop(Nodes) -> ok | {error,not_main_node}</name>
<fsummary>Stop Cover on remote nodes.</fsummary>
<type>
<v>Nodes = [atom()]</v>
@@ -523,7 +535,7 @@
</desc>
</func>
<func>
- <name>flush(Nodes) -> ok | {error,not_main_node}</name>
+ <name since="OTP R16B">flush(Nodes) -> ok | {error,not_main_node}</name>
<fsummary>Collect cover data from remote nodes.</fsummary>
<type>
<v>Nodes = [atom()]</v>
diff --git a/lib/tools/doc/src/cprof.xml b/lib/tools/doc/src/cprof.xml
index df0acbe617..b6af8b6d28 100644
--- a/lib/tools/doc/src/cprof.xml
+++ b/lib/tools/doc/src/cprof.xml
@@ -34,7 +34,7 @@
<rev>PA1</rev>
<file>cprof.sgml</file>
</header>
- <module>cprof</module>
+ <module since="">cprof</module>
<modulesummary>A simple Call Count Profiling Tool using breakpoints for minimal runtime performance impact.</modulesummary>
<description>
<p>The <c>cprof</c> module is used to profile a program
@@ -65,10 +65,10 @@
</description>
<funcs>
<func>
- <name>analyse() -> {AllCallCount, ModAnalysisList}</name>
- <name>analyse(Limit) -> {AllCallCount, ModAnalysisList}</name>
- <name>analyse(Mod) -> ModAnalysis</name>
- <name>analyse(Mod, Limit) -> ModAnalysis</name>
+ <name since="">analyse() -> {AllCallCount, ModAnalysisList}</name>
+ <name since="">analyse(Limit) -> {AllCallCount, ModAnalysisList}</name>
+ <name since="">analyse(Mod) -> ModAnalysis</name>
+ <name since="">analyse(Mod, Limit) -> ModAnalysis</name>
<fsummary>Collect and analyse call counters.</fsummary>
<type>
<v>Limit = integer()</v>
@@ -122,7 +122,7 @@
</desc>
</func>
<func>
- <name>pause() -> integer()</name>
+ <name since="">pause() -> integer()</name>
<fsummary>Pause running call count trace for all functions.</fsummary>
<desc>
<p>Pause call count tracing for all functions in all modules
@@ -137,9 +137,9 @@
</desc>
</func>
<func>
- <name>pause(FuncSpec) -> integer()</name>
- <name>pause(Mod, Func) -> integer()</name>
- <name>pause(Mod, Func, Arity) -> integer()</name>
+ <name since="">pause(FuncSpec) -> integer()</name>
+ <name since="">pause(Mod, Func) -> integer()</name>
+ <name since="">pause(Mod, Func, Arity) -> integer()</name>
<fsummary>Pause running call count trace for matching functions.</fsummary>
<type>
<v>FuncSpec = Mod | {Mod,Func,Arity}, {FS}</v>
@@ -167,10 +167,10 @@
</desc>
</func>
<func>
- <name>restart() -> integer()</name>
- <name>restart(FuncSpec) -> integer()</name>
- <name>restart(Mod, Func) -> integer()</name>
- <name>restart(Mod, Func, Arity) -> integer()</name>
+ <name since="">restart() -> integer()</name>
+ <name since="">restart(FuncSpec) -> integer()</name>
+ <name since="">restart(Mod, Func) -> integer()</name>
+ <name since="">restart(Mod, Func, Arity) -> integer()</name>
<fsummary>Restart existing call counters for matching functions.</fsummary>
<type>
<v>FuncSpec = Mod | {Mod,Func,Arity}, {FS}</v>
@@ -197,7 +197,7 @@
</desc>
</func>
<func>
- <name>start() -> integer()</name>
+ <name since="">start() -> integer()</name>
<fsummary>Start call count tracing for all functions.</fsummary>
<desc>
<p>Start call count tracing for all functions in all modules,
@@ -212,9 +212,9 @@
</desc>
</func>
<func>
- <name>start(FuncSpec) -> integer()</name>
- <name>start(Mod, Func) -> integer()</name>
- <name>start(Mod, Func, Arity) -> integer()</name>
+ <name since="">start(FuncSpec) -> integer()</name>
+ <name since="">start(Mod, Func) -> integer()</name>
+ <name since="">start(Mod, Func, Arity) -> integer()</name>
<fsummary>Start call count tracing for matching functions.</fsummary>
<type>
<v>FuncSpec = Mod | {Mod,Func,Arity}, {FS}</v>
@@ -240,7 +240,7 @@
</desc>
</func>
<func>
- <name>stop() -> integer()</name>
+ <name since="">stop() -> integer()</name>
<fsummary>Stop call count tracing for all functions.</fsummary>
<desc>
<p>Stop call count tracing for all functions in all modules,
@@ -255,9 +255,9 @@
</desc>
</func>
<func>
- <name>stop(FuncSpec) -> integer()</name>
- <name>stop(Mod, Func) -> integer()</name>
- <name>stop(Mod, Func, Arity) -> integer()</name>
+ <name since="">stop(FuncSpec) -> integer()</name>
+ <name since="">stop(Mod, Func) -> integer()</name>
+ <name since="">stop(Mod, Func, Arity) -> integer()</name>
<fsummary>Stop call count tracing for matching functions.</fsummary>
<type>
<v>FuncSpec = Mod | {Mod,Func,Arity}, {FS}</v>
diff --git a/lib/tools/doc/src/eprof.xml b/lib/tools/doc/src/eprof.xml
index f098b7d39e..c9e4edd991 100644
--- a/lib/tools/doc/src/eprof.xml
+++ b/lib/tools/doc/src/eprof.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>eprof</module>
+ <module since="">eprof</module>
<modulesummary>A Time Profiling Tool for Erlang</modulesummary>
<description>
<p>The module <c>eprof</c> provides a set of functions for time
@@ -40,7 +40,7 @@
</description>
<funcs>
<func>
- <name>start() -> {ok,Pid} | {error,Reason}</name>
+ <name since="">start() -> {ok,Pid} | {error,Reason}</name>
<fsummary>Start Eprof.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -51,9 +51,9 @@
</desc>
</func>
<func>
- <name>start_profiling(Rootset) -> profiling | {error, Reason}</name>
- <name>start_profiling(Rootset,Pattern) -> profiling | {error, Reason}</name>
- <name>start_profiling(Rootset,Pattern,Options) -> profiling | {error, Reason}</name>
+ <name since="">start_profiling(Rootset) -> profiling | {error, Reason}</name>
+ <name since="OTP R14B">start_profiling(Rootset,Pattern) -> profiling | {error, Reason}</name>
+ <name since="OTP R16B01">start_profiling(Rootset,Pattern,Options) -> profiling | {error, Reason}</name>
<fsummary>Start profiling.</fsummary>
<type>
<v>Rootset = [atom() | pid()]</v>
@@ -79,7 +79,7 @@
</desc>
</func>
<func>
- <name>stop_profiling() -> profiling_stopped | profiling_already_stopped</name>
+ <name since="">stop_profiling() -> profiling_stopped | profiling_already_stopped</name>
<fsummary>Stop profiling.</fsummary>
<desc>
<p>Stops profiling started with <c>start_profiling/1</c> or
@@ -87,14 +87,14 @@
</desc>
</func>
<func>
- <name>profile(Fun) -> profiling | {error, Reason}</name>
- <name>profile(Fun, Options) -> profiling | {error, Reason}</name>
- <name>profile(Rootset) -> profiling | {error, Reason}</name>
- <name>profile(Rootset,Fun) -> {ok, Value} | {error,Reason}</name>
- <name>profile(Rootset,Fun,Pattern) -> {ok, Value} | {error, Reason}</name>
- <name>profile(Rootset,Module,Function,Args) -> {ok, Value} | {error, Reason}</name>
- <name>profile(Rootset,Module,Function,Args,Pattern) -> {ok, Value} | {error, Reason}</name>
- <name>profile(Rootset,Module,Function,Args,Pattern,Options) -> {ok, Value} | {error, Reason}</name>
+ <name since="">profile(Fun) -> profiling | {error, Reason}</name>
+ <name since="">profile(Fun, Options) -> profiling | {error, Reason}</name>
+ <name since="">profile(Rootset) -> profiling | {error, Reason}</name>
+ <name since="">profile(Rootset,Fun) -> {ok, Value} | {error,Reason}</name>
+ <name since="OTP R14B">profile(Rootset,Fun,Pattern) -> {ok, Value} | {error, Reason}</name>
+ <name since="">profile(Rootset,Module,Function,Args) -> {ok, Value} | {error, Reason}</name>
+ <name since="OTP R14B">profile(Rootset,Module,Function,Args,Pattern) -> {ok, Value} | {error, Reason}</name>
+ <name since="OTP R16B01">profile(Rootset,Module,Function,Args,Pattern,Options) -> {ok, Value} | {error, Reason}</name>
<fsummary>Start profiling.</fsummary>
<type>
<v>Rootset = [atom() | pid()]</v>
@@ -128,9 +128,9 @@
</desc>
</func>
<func>
- <name>analyze() -> ok</name>
- <name>analyze(Type) -> ok</name>
- <name>analyze(Type,Options) -> ok</name>
+ <name since="OTP R14B">analyze() -> ok</name>
+ <name since="OTP R14B">analyze(Type) -> ok</name>
+ <name since="OTP R14B">analyze(Type,Options) -> ok</name>
<fsummary>Display profiling results per process.</fsummary>
<type>
<v>Type = procs | total</v>
@@ -152,7 +152,7 @@
</desc>
</func>
<func>
- <name>log(File) -> ok</name>
+ <name since="">log(File) -> ok</name>
<fsummary>Activate logging of <c>eprof</c>printouts.</fsummary>
<type>
<v>File = atom() | string()</v>
@@ -164,7 +164,7 @@
</desc>
</func>
<func>
- <name>stop() -> stopped</name>
+ <name since="">stop() -> stopped</name>
<fsummary>Stop Eprof.</fsummary>
<desc>
<p>Stops the Eprof server.</p>
diff --git a/lib/tools/doc/src/fprof.xml b/lib/tools/doc/src/fprof.xml
index 1fd828d127..4bb8862016 100644
--- a/lib/tools/doc/src/fprof.xml
+++ b/lib/tools/doc/src/fprof.xml
@@ -32,7 +32,7 @@
<rev>PA1</rev>
<file>fprof.sgml</file>
</header>
- <module>fprof</module>
+ <module since="">fprof</module>
<modulesummary>A Time Profiling Tool using trace to file for minimal runtime performance impact.</modulesummary>
<description>
<p>This module is used to profile a program
@@ -101,7 +101,7 @@
</description>
<funcs>
<func>
- <name>start() -> {ok, Pid} | {error, {already_started, Pid}}</name>
+ <name since="">start() -> {ok, Pid} | {error, {already_started, Pid}}</name>
<fsummary>Starts the <c>fprof</c>&nbsp;server.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -117,14 +117,14 @@
</desc>
</func>
<func>
- <name>stop() -> ok</name>
+ <name since="">stop() -> ok</name>
<fsummary>Same as <c>stop(normal)</c>.</fsummary>
<desc>
<p>Same as <c>stop(normal)</c>.</p>
</desc>
</func>
<func>
- <name>stop(Reason) -> ok</name>
+ <name since="">stop(Reason) -> ok</name>
<fsummary>Stops the <c>fprof</c>&nbsp;server.</fsummary>
<type>
<v>Reason = term()</v>
@@ -149,7 +149,7 @@
</desc>
</func>
<func>
- <name>apply(Func, Args) -> term()</name>
+ <name since="">apply(Func, Args) -> term()</name>
<fsummary>Same as <c>apply(Func, Args, [])</c>.</fsummary>
<type>
<v>Func = function() | {Module, Function}</v>
@@ -162,7 +162,7 @@
</desc>
</func>
<func>
- <name>apply(Module, Function, Args) -> term()</name>
+ <name since="">apply(Module, Function, Args) -> term()</name>
<fsummary>Same as <c>apply({Module, Function}, Args, [])</c>.</fsummary>
<type>
<v>Args = [term()]</v>
@@ -174,7 +174,7 @@
</desc>
</func>
<func>
- <name>apply(Func, Args, OptionList) -> term()</name>
+ <name since="">apply(Func, Args, OptionList) -> term()</name>
<fsummary>Calls <c>erlang:apply(Func, Args)</c>surrounded by<c>trace([start | OptionList])</c>and<c>trace(stop)</c>.</fsummary>
<type>
<v>Func = function() | {Module, Function}</v>
@@ -210,7 +210,7 @@
</desc>
</func>
<func>
- <name>apply(Module, Function, Args, OptionList) -> term()</name>
+ <name since="">apply(Module, Function, Args, OptionList) -> term()</name>
<fsummary>Same as <c>apply({Module, Function}, Args, OptionList)</c>.</fsummary>
<type>
<v>Module = atom()</v>
@@ -228,7 +228,7 @@
</desc>
</func>
<func>
- <name>trace(start, Filename) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">trace(start, Filename) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>trace([start, {file, Filename}])</c>.</fsummary>
<type>
<v>Reason = term()</v>
@@ -238,7 +238,7 @@
</desc>
</func>
<func>
- <name>trace(verbose, Filename) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">trace(verbose, Filename) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>trace([start, verbose, {file, Filename}])</c>.</fsummary>
<type>
<v>Reason = term()</v>
@@ -249,7 +249,7 @@
</desc>
</func>
<func>
- <name>trace(OptionName, OptionValue) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">trace(OptionName, OptionValue) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>trace([{OptionName, OptionValue}])</c>.</fsummary>
<type>
<v>OptionName = atom()</v>
@@ -262,7 +262,7 @@
</desc>
</func>
<func>
- <name>trace(verbose) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">trace(verbose) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>trace([start, verbose])</c>.</fsummary>
<type>
<v>Reason = term()</v>
@@ -272,7 +272,7 @@
</desc>
</func>
<func>
- <name>trace(OptionName) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">trace(OptionName) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>trace([OptionName])</c>.</fsummary>
<type>
<v>OptionName = atom()</v>
@@ -283,7 +283,7 @@
</desc>
</func>
<func>
- <name>trace({OptionName, OptionValue}) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">trace({OptionName, OptionValue}) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>trace([{OptionName, OptionValue}])</c>.</fsummary>
<type>
<v>OptionName = atom()</v>
@@ -296,7 +296,7 @@
</desc>
</func>
<func>
- <name>trace([Option]) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">trace([Option]) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Starts or stops tracing.</fsummary>
<type>
<v>Option = start | stop | {procs, PidSpec} | {procs, [PidSpec]} | verbose | {verbose, bool()} | file | {file, Filename} | {tracer, Tracer}</v>
@@ -360,7 +360,7 @@
</desc>
</func>
<func>
- <name>profile() -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">profile() -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>profile([])</c>.</fsummary>
<type>
<v>Reason = term()</v>
@@ -370,7 +370,7 @@
</desc>
</func>
<func>
- <name>profile(OptionName, OptionValue) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">profile(OptionName, OptionValue) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>profile([{OptionName, OptionValue}])</c>.</fsummary>
<type>
<v>OptionName = atom()</v>
@@ -383,7 +383,7 @@
</desc>
</func>
<func>
- <name>profile(OptionName) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">profile(OptionName) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>profile([OptionName])</c>.</fsummary>
<type>
<v>OptionName = atom()</v>
@@ -394,7 +394,7 @@
</desc>
</func>
<func>
- <name>profile({OptionName, OptionValue}) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">profile({OptionName, OptionValue}) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>profile([{OptionName, OptionValue}])</c>.</fsummary>
<type>
<v>OptionName = atom()</v>
@@ -407,7 +407,7 @@
</desc>
</func>
<func>
- <name>profile([Option]) -> ok | {ok, Tracer} | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">profile([Option]) -> ok | {ok, Tracer} | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Compiles a trace into raw profile data held by the <c>fprof</c>&nbsp;server.</fsummary>
<type>
<v>Option = file | {file, Filename} | dump | {dump, Dump} | append | start | stop</v>
@@ -465,7 +465,7 @@
</desc>
</func>
<func>
- <name>analyse() -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">analyse() -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>analyse([])</c>.</fsummary>
<type>
<v>Reason = term()</v>
@@ -475,7 +475,7 @@
</desc>
</func>
<func>
- <name>analyse(OptionName, OptionValue) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">analyse(OptionName, OptionValue) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>analyse([{OptionName, OptionValue}])</c>.</fsummary>
<type>
<v>OptionName = atom()</v>
@@ -488,7 +488,7 @@
</desc>
</func>
<func>
- <name>analyse(OptionName) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">analyse(OptionName) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>analyse([OptionName])</c>.</fsummary>
<type>
<v>OptionName = atom()</v>
@@ -499,7 +499,7 @@
</desc>
</func>
<func>
- <name>analyse({OptionName, OptionValue}) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">analyse({OptionName, OptionValue}) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>analyse([{OptionName, OptionValue}])</c>.</fsummary>
<type>
<v>OptionName = atom()</v>
@@ -512,7 +512,7 @@
</desc>
</func>
<func>
- <name>analyse([Option]) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">analyse([Option]) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Analyses raw profile data in the <c>fprof</c>&nbsp;server.</fsummary>
<type>
<v>Option = dest | {dest, Dest} | append | {cols, Cols} | callers | {callers, bool()} | no_callers | {sort, SortSpec} | totals | {totals, bool()} | details | {details, bool()} | no_details</v>
diff --git a/lib/tools/doc/src/instrument.xml b/lib/tools/doc/src/instrument.xml
index 9fd9332373..7e9cbaebb0 100644
--- a/lib/tools/doc/src/instrument.xml
+++ b/lib/tools/doc/src/instrument.xml
@@ -32,7 +32,7 @@
<rev>PA1</rev>
<file>instrument.sgml</file>
</header>
- <module>instrument</module>
+ <module since="">instrument</module>
<modulesummary>Analysis and Utility Functions for Instrumentation</modulesummary>
<description>
<p>The module <c>instrument</c> contains support for studying the resource
@@ -92,7 +92,7 @@
<funcs>
<func>
- <name name="allocations" arity="0"/>
+ <name name="allocations" arity="0" since="OTP 21.0"/>
<fsummary>Return a summary of all allocations in the system.</fsummary>
<desc>
<p>Shorthand for
@@ -101,7 +101,7 @@
</func>
<func>
- <name name="allocations" arity="1"/>
+ <name name="allocations" arity="1" since="OTP 21.0"/>
<fsummary>Return a summary of all allocations filtered by allocator type
and scheduler id.</fsummary>
<desc>
@@ -111,15 +111,18 @@
default, but this can be configured an a per-allocator basis with the
<seealso marker="erts:erts_alloc#M_atags"><c>+M&lt;S&gt;atags</c>
</seealso> emulator option.</p>
- <p>If tagged allocations are not enabled on any of the specified
- allocator types, the call will fail with
- <c>{error, not_enabled}</c>.</p>
+ <p>If the specified allocator types are not enabled, the call will fail
+ with <c>{error, not_enabled}</c>.</p>
<p>The following options can be used:</p>
<taglist>
<tag><c>allocator_types</c></tag>
<item>
- <p>The allocator types that will be searched. Defaults to all
- <c>alloc_util</c> allocators.</p>
+ <p>The allocator types that will be searched. Note that blocks can
+ move freely between allocator types, so restricting the search to
+ certain allocators may return unexpected types (e.g. process
+ heaps when searching binary_alloc), or hide blocks that were
+ migrated out.</p>
+ <p>Defaults to all <c>alloc_util</c> allocators.</p>
</item>
<tag><c>scheduler_ids</c></tag>
<item>
@@ -170,7 +173,7 @@
</func>
<func>
- <name name="carriers" arity="0"/>
+ <name name="carriers" arity="0" since="OTP 21.0"/>
<fsummary>Return a list of all carriers in the system.</fsummary>
<desc>
<p>Shorthand for
@@ -179,7 +182,7 @@
</func>
<func>
- <name name="carriers" arity="1"/>
+ <name name="carriers" arity="1" since="OTP 21.0"/>
<fsummary>Return a list of all carriers filtered by allocator type and
scheduler id.</fsummary>
<desc>
diff --git a/lib/tools/doc/src/lcnt.xml b/lib/tools/doc/src/lcnt.xml
index d2595cdb60..1d434decfc 100644
--- a/lib/tools/doc/src/lcnt.xml
+++ b/lib/tools/doc/src/lcnt.xml
@@ -34,7 +34,7 @@
<rev>PA1</rev>
<file>lcnt.xml</file>
</header>
- <module>lcnt</module>
+ <module since="OTP R13B04">lcnt</module>
<modulesummary>A runtime system Lock Profiling tool.</modulesummary>
<description>
<p>The <c>lcnt</c> module is used to profile the internal ethread locks in the
@@ -71,7 +71,7 @@
<funcs>
<func>
- <name>start() -> {ok, Pid} | {error, {already_started, Pid}} </name>
+ <name since="OTP R13B04">start() -> {ok, Pid} | {error, {already_started, Pid}} </name>
<fsummary>Starts the lock profiler server.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -84,7 +84,7 @@
</func>
<func>
- <name>stop() -> ok</name>
+ <name since="OTP R13B04">stop() -> ok</name>
<fsummary>Stops the lock profiler server.</fsummary>
<desc>
<p>Stops the lock profiler server.</p>
@@ -92,13 +92,13 @@
</func>
<func>
- <name>collect() -> ok</name>
+ <name since="OTP R13B04">collect() -> ok</name>
<fsummary>Same as <c>collect(node())</c>.</fsummary>
<desc><p>Same as <c>collect(node())</c>.</p></desc>
</func>
<func>
- <name>collect(Node) -> ok</name>
+ <name since="OTP R13B04">collect(Node) -> ok</name>
<fsummary>Collects lock statistics from the runtime system.</fsummary>
<type>
<v>Node = node()</v>
@@ -113,13 +113,13 @@
</func>
<func>
- <name>clear() -> ok</name>
+ <name since="OTP R13B04">clear() -> ok</name>
<fsummary>Same as <c>clear(node())</c>.</fsummary>
<desc><p>Same as <c>clear(node())</c>.</p></desc>
</func>
<func>
- <name>clear(Node) -> ok</name>
+ <name since="OTP R13B04">clear(Node) -> ok</name>
<fsummary>Clears the internal lock statistics from runtime system.</fsummary>
<type>
<v>Node = node()</v>
@@ -133,12 +133,12 @@
</desc>
</func>
<func>
- <name>conflicts() -> ok</name>
+ <name since="OTP R13B04">conflicts() -> ok</name>
<fsummary>Same as <c>conflicts([])</c>.</fsummary>
<desc><p>Same as <c>conflicts([])</c>.</p></desc>
</func>
<func>
- <name>conflicts([Option]) -> ok</name>
+ <name since="OTP R13B04">conflicts([Option]) -> ok</name>
<fsummary>Prints a list of internal lock counters.</fsummary>
<type>
<v>Option = {sort, Sort} | {reverse, bool()} | {thresholds, [Thresholds]} | {print, [Print | {Print, integer()}]} | {max_locks, MaxLocks} | {combine, bool()}</v>
@@ -154,14 +154,14 @@
</func>
<func>
- <name>locations() -> ok</name>
+ <name since="OTP R13B04">locations() -> ok</name>
<fsummary>Same as <c>locations([])</c>.</fsummary>
<desc>
<p>Same as <c>locations([])</c>.</p>
</desc>
</func>
<func>
- <name>locations([Option]) -> ok</name>
+ <name since="OTP R13B04">locations([Option]) -> ok</name>
<fsummary>Prints a list of internal lock counters by source code locations.</fsummary>
<type>
<v>Option = {sort, Sort} | {thresholds, [Thresholds]} | {print, [Print | {Print, integer()}]} | {max_locks, MaxLocks} | {combine, bool()}</v>
@@ -177,12 +177,12 @@
</func>
<func>
- <name>inspect(Lock) -> ok</name>
+ <name since="OTP R13B04">inspect(Lock) -> ok</name>
<fsummary>Same as <c>inspect(Lock, [])</c>.</fsummary>
<desc><p>Same as <c>inspect(Lock, [])</c>.</p></desc>
</func>
<func>
- <name>inspect(Lock, [Option]) -> ok</name>
+ <name since="OTP R13B04">inspect(Lock, [Option]) -> ok</name>
<fsummary>Prints a list of internal lock counters for a specific lock.</fsummary>
<type>
<v>Lock = Name | {Name, Id | [Id]}</v>
@@ -268,7 +268,7 @@
</func>
<func>
- <name>information() -> ok</name>
+ <name since="OTP R13B04">information() -> ok</name>
<fsummary>Prints lcnt server state and generic information about collected lock statistics.</fsummary>
<desc>
<p>Prints lcnt server state and generic information about collected lock statistics.</p>
@@ -276,7 +276,7 @@
</func>
<func>
- <name>swap_pid_keys() -> ok</name>
+ <name since="OTP R13B04">swap_pid_keys() -> ok</name>
<fsummary>Swaps places on <c>Name</c> and <c>Id</c> space for ports and processes.</fsummary>
<desc>
<p>Swaps places on <c>Name</c> and <c>Id</c> space for ports and processes.</p>
@@ -284,7 +284,7 @@
</func>
<func>
- <name>load(Filename) -> ok</name>
+ <name since="OTP R13B04">load(Filename) -> ok</name>
<fsummary>Restores previously saved data to the server.</fsummary>
<type>
<v>Filename = filename()</v>
@@ -295,7 +295,7 @@
</func>
<func>
- <name>save(Filename) -> ok</name>
+ <name since="OTP R13B04">save(Filename) -> ok</name>
<fsummary>Saves the collected data to file.</fsummary>
<type>
<v>Filename = filename()</v>
@@ -312,7 +312,7 @@
</section>
<funcs>
<func>
- <name>apply(Fun) -> term()</name>
+ <name since="OTP R13B04">apply(Fun) -> term()</name>
<fsummary>Same as <c>apply(Fun, [])</c>.</fsummary>
<type>
<v>Fun = fun()</v>
@@ -322,7 +322,7 @@
</desc>
</func>
<func>
- <name>apply(Fun, Args) -> term()</name>
+ <name since="OTP R13B04">apply(Fun, Args) -> term()</name>
<fsummary>Same as <c>apply(Module, Function, Args)</c>.</fsummary>
<type>
<v>Fun = fun()</v>
@@ -333,7 +333,7 @@
</desc>
</func>
<func>
- <name>apply(Module, Function, Args) -> term()</name>
+ <name since="OTP R13B04">apply(Module, Function, Args) -> term()</name>
<fsummary>Clears counters, applies function and collects the profiling results.</fsummary>
<type>
<v>Module = atom()</v>
@@ -358,12 +358,12 @@
</func>
<func>
- <name>pid(Id, Serial) -> pid()</name>
+ <name since="OTP R13B04">pid(Id, Serial) -> pid()</name>
<fsummary>Same as <c>pid(node(), Id, Serial)</c>.</fsummary>
<desc><p>Same as <c>pid(node(), Id, Serial)</c>.</p></desc>
</func>
<func>
- <name>pid(Node, Id, Serial) -> pid()</name>
+ <name since="OTP R13B04">pid(Node, Id, Serial) -> pid()</name>
<fsummary>Creates a process id with creation 0.</fsummary>
<type>
<v>Node = node()</v>
@@ -376,12 +376,12 @@
</func>
<func>
- <name>port(Id) -> port()</name>
+ <name since="OTP R13B04">port(Id) -> port()</name>
<fsummary>Same as <c>port(node(), Id)</c>.</fsummary>
<desc><p>Same as <c>port(node(), Id)</c>.</p></desc>
</func>
<func>
- <name>port(Node, Id) -> port()</name>
+ <name since="OTP R13B04">port(Node, Id) -> port()</name>
<fsummary>Creates a port id with creation 0.</fsummary>
<type>
<v>Node = node()</v>
@@ -399,12 +399,12 @@
<funcs>
<func>
- <name>rt_collect() -> [lock_counter_data()]</name>
+ <name since="OTP R13B04">rt_collect() -> [lock_counter_data()]</name>
<fsummary>Same as <c>rt_collect(node())</c>.</fsummary>
<desc> <p>Same as <c>rt_collect(node())</c>.</p> </desc>
</func>
<func>
- <name>rt_collect(Node) -> [lock_counter_data()]</name>
+ <name since="OTP R13B04">rt_collect(Node) -> [lock_counter_data()]</name>
<fsummary>Returns a list of raw lock counter data.</fsummary>
<type>
<v>Node = node()</v>
@@ -413,12 +413,12 @@
</func>
<func>
- <name>rt_clear() -> ok</name>
+ <name since="OTP R13B04">rt_clear() -> ok</name>
<fsummary>Same as <c>rt_clear(node())</c>.</fsummary>
<desc> <p>Same as <c>rt_clear(node())</c>.</p> </desc>
</func>
<func>
- <name>rt_clear(Node) -> ok</name>
+ <name since="OTP R13B04">rt_clear(Node) -> ok</name>
<fsummary>Clears the internal counters.</fsummary>
<type>
<v>Node = node()</v>
@@ -427,13 +427,13 @@
</func>
<func>
- <name>rt_mask() -> [category_atom()]</name>
+ <name since="OTP 20.1">rt_mask() -> [category_atom()]</name>
<fsummary>Same as <c>rt_mask(node())</c>.</fsummary>
<desc><p>Same as <c>rt_mask(node())</c>.</p></desc>
</func>
<func>
- <name>rt_mask(Node) -> [category_atom()]</name>
+ <name since="OTP 20.1">rt_mask(Node) -> [category_atom()]</name>
<fsummary>Returns the current lock category mask.</fsummary>
<type>
<v>Node = node()</v>
@@ -447,7 +447,7 @@
</func>
<func>
- <name>rt_mask(Categories) -> ok | {error, copy_save_enabled}</name>
+ <name since="OTP 20.1">rt_mask(Categories) -> ok | {error, copy_save_enabled}</name>
<fsummary>Same as <c>rt_mask(node(), Categories)</c>.</fsummary>
<type>
<v>Categories = [atom()]</v>
@@ -456,7 +456,7 @@
</func>
<func>
- <name>rt_mask(Node, Categories) -> ok | {error, copy_save_enabled}</name>
+ <name since="OTP 20.1">rt_mask(Node, Categories) -> ok | {error, copy_save_enabled}</name>
<fsummary>Changes the lock category mask.</fsummary>
<type>
<v>Node = node()</v>
@@ -489,12 +489,12 @@
</func>
<func>
- <name>rt_opt({Type, bool()}) -> bool()</name>
+ <name since="OTP R13B04">rt_opt({Type, bool()}) -> bool()</name>
<fsummary>Same as <c>rt_opt(node(), {Type, Opt})</c>.</fsummary>
<desc> <p>Same as <c>rt_opt(node(), {Type, Opt})</c>.</p> </desc>
</func>
<func>
- <name>rt_opt(Node, {Type, bool()}) -> bool()</name>
+ <name since="OTP R13B04">rt_opt(Node, {Type, bool()}) -> bool()</name>
<fsummary>Changes the lock counter behavior and returns the previous behaviour.</fsummary>
<type>
<v>Node = node()</v>
diff --git a/lib/tools/doc/src/make.xml b/lib/tools/doc/src/make.xml
index 123fcd4afc..af2404707f 100644
--- a/lib/tools/doc/src/make.xml
+++ b/lib/tools/doc/src/make.xml
@@ -30,7 +30,7 @@
<date></date>
<rev></rev>
</header>
- <module>make</module>
+ <module since="">make</module>
<modulesummary>A Make Utility for Erlang</modulesummary>
<description>
<p>The module <c>make</c> provides a set of functions similar to
@@ -38,8 +38,8 @@
</description>
<funcs>
<func>
- <name>all() -> up_to_date | error</name>
- <name>all(Options) -> up_to_date | error</name>
+ <name since="">all() -> up_to_date | error</name>
+ <name since="">all(Options) -> up_to_date | error</name>
<fsummary>Compile a set of modules.</fsummary>
<type>
<v>Options = [Option]</v>
@@ -87,8 +87,8 @@
</desc>
</func>
<func>
- <name>files(ModFiles) -> up_to_date | error</name>
- <name>files(ModFiles, Options) -> up_to_date | error</name>
+ <name since="">files(ModFiles) -> up_to_date | error</name>
+ <name since="">files(ModFiles, Options) -> up_to_date | error</name>
<fsummary>Compile a set of modules.</fsummary>
<type>
<v>ModFiles = [Module | File]</v>
diff --git a/lib/tools/doc/src/tags.xml b/lib/tools/doc/src/tags.xml
index ea0ae5cc4d..90a8b28177 100644
--- a/lib/tools/doc/src/tags.xml
+++ b/lib/tools/doc/src/tags.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>tags.sgml</file>
</header>
- <module>tags</module>
+ <module since="">tags</module>
<modulesummary>Generate Emacs TAGS file from Erlang source files</modulesummary>
<description>
<p>A <c>TAGS</c> file is used by Emacs to find function and variable
@@ -42,14 +42,14 @@
</description>
<funcs>
<func>
- <name>file(File [, Options])</name>
+ <name since="">file(File [, Options])</name>
<fsummary>Create a <c>TAGS</c>file for the file <c>File</c>.</fsummary>
<desc>
<p>Create a <c>TAGS</c> file for the file <c>File</c>.</p>
</desc>
</func>
<func>
- <name>files(FileList [, Options])</name>
+ <name since="">files(FileList [, Options])</name>
<fsummary>Create a TAGS file for the files in the list<c>FileList</c>.</fsummary>
<desc>
<p>Create a TAGS file for the files in the list
@@ -57,7 +57,7 @@
</desc>
</func>
<func>
- <name>dir(Dir [, Options])</name>
+ <name since="">dir(Dir [, Options])</name>
<fsummary>Create a TAGS file for all files in directory<c>Dir</c>.</fsummary>
<desc>
<p>Create a TAGS file for all files in directory
@@ -65,7 +65,7 @@
</desc>
</func>
<func>
- <name>dirs(DirList [, Options])</name>
+ <name since="">dirs(DirList [, Options])</name>
<fsummary>Create a TAGS file for all files in any directory in<c>DirList</c>.</fsummary>
<desc>
<p>Create a TAGS file for all files in any directory in
@@ -73,7 +73,7 @@
</desc>
</func>
<func>
- <name>subdir(Dir [, Options])</name>
+ <name since="">subdir(Dir [, Options])</name>
<fsummary>Descend recursively down the directory <c>Dir</c>and create a <c>TAGS</c>file based on all files found.</fsummary>
<desc>
<p>Descend recursively down the directory <c>Dir</c> and
@@ -81,7 +81,7 @@
</desc>
</func>
<func>
- <name>subdirs(DirList [, Options])</name>
+ <name since="">subdirs(DirList [, Options])</name>
<fsummary>Descend recursively down all the directories in<c>DirList</c>and create a <c>TAGS</c>file based on all files found.</fsummary>
<desc>
<p>Descend recursively down all the directories in
@@ -90,7 +90,7 @@
</desc>
</func>
<func>
- <name>root([Options])</name>
+ <name since="">root([Options])</name>
<fsummary>Create a <c>TAGS</c>file covering all files in the Erlang distribution.</fsummary>
<desc>
<p>Create a <c>TAGS</c> file covering all files in
diff --git a/lib/tools/doc/src/xref.xml b/lib/tools/doc/src/xref.xml
index 6f833246ad..ab3641a52f 100644
--- a/lib/tools/doc/src/xref.xml
+++ b/lib/tools/doc/src/xref.xml
@@ -32,7 +32,7 @@
<rev>PA1</rev>
<file>xref.sgml</file>
</header>
- <module>xref</module>
+ <module since="">xref</module>
<modulesummary>A Cross Reference Tool for analyzing dependencies between functions, modules, applications and releases.</modulesummary>
<description>
<p>Xref is a cross reference tool that can be used for finding
@@ -729,7 +729,7 @@ xref() = atom() | pid() </pre>
</description>
<funcs>
<func>
- <name>add_application(Xref, Directory [, Options]) -> {ok, application()} | Error</name>
+ <name since="">add_application(Xref, Directory [, Options]) -> {ok, application()} | Error</name>
<fsummary>Add the modules of an application.</fsummary>
<type>
<v>Directory = directory()</v>
@@ -761,7 +761,7 @@ xref() = atom() | pid() </pre>
</desc>
</func>
<func>
- <name>add_directory(Xref, Directory [, Options]) -> {ok, Modules} | Error</name>
+ <name since="">add_directory(Xref, Directory [, Options]) -> {ok, Modules} | Error</name>
<fsummary>Add the modules in a directory.</fsummary>
<type>
<v>Directory = directory()</v>
@@ -791,7 +791,7 @@ xref() = atom() | pid() </pre>
</desc>
</func>
<func>
- <name>add_module(Xref, File [, Options]) -> {ok, module()} | Error</name>
+ <name since="">add_module(Xref, File [, Options]) -> {ok, module()} | Error</name>
<fsummary>Add a module.</fsummary>
<type>
<v>Error = {error, module(), Reason}</v>
@@ -814,7 +814,7 @@ xref() = atom() | pid() </pre>
</desc>
</func>
<func>
- <name>add_release(Xref, Directory [, Options]) -> {ok, release()} | Error</name>
+ <name since="">add_release(Xref, Directory [, Options]) -> {ok, release()} | Error</name>
<fsummary>Add the modules of a release.</fsummary>
<type>
<v>Directory = directory()</v>
@@ -849,7 +849,7 @@ xref() = atom() | pid() </pre>
</desc>
</func>
<func>
- <name>analyze(Xref, Analysis [, Options]) -> {ok, Answer} | Error</name>
+ <name since="">analyze(Xref, Analysis [, Options]) -> {ok, Answer} | Error</name>
<fsummary>Evaluate a predefined analysis.</fsummary>
<type>
<v>Analysis = undefined_function_calls | undefined_functions | locals_not_used | exports_not_used | deprecated_function_calls | {deprecated_function_calls, DeprFlag} | deprecated_functions | {deprecated_functions, DeprFlag} | {call, FuncSpec} | {use, FuncSpec} | {module_call, ModSpec} | {module_use, ModSpec} | {application_call, AppSpec} | {application_use, AppSpec} | {release_call, RelSpec} | {release_use, RelSpec}</v>
@@ -939,7 +939,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>d(Directory) -> [DebugInfoResult] | [NoDebugInfoResult] | Error</name>
+ <name since="">d(Directory) -> [DebugInfoResult] | [NoDebugInfoResult] | Error</name>
<fsummary>Check the modules in a directory using the code path.</fsummary>
<type>
<v>Directory = directory()</v>
@@ -979,8 +979,8 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>forget(Xref) -> ok</name>
- <name>forget(Xref, Variables) -> ok | Error</name>
+ <name since="">forget(Xref) -> ok</name>
+ <name since="">forget(Xref, Variables) -> ok | Error</name>
<fsummary>Remove user variables and their values.</fsummary>
<type>
<v>Error = {error, module(), Reason}</v>
@@ -994,7 +994,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>format_error(Error) -> Chars</name>
+ <name since="">format_error(Error) -> Chars</name>
<fsummary>Return an English description of an Xref error reply.</fsummary>
<type>
<v>Error = {error, module(), term()}</v>
@@ -1008,8 +1008,8 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>get_default(Xref) -> [{Option, Value}]</name>
- <name>get_default(Xref, Option) -> {ok, Value} | Error</name>
+ <name since="">get_default(Xref) -> [{Option, Value}]</name>
+ <name since="">get_default(Xref, Option) -> {ok, Value} | Error</name>
<fsummary>Return the default values of options.</fsummary>
<type>
<v>Error = {error, module(), Reason}</v>
@@ -1023,7 +1023,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>get_library_path(Xref) -> {ok, LibraryPath}</name>
+ <name since="">get_library_path(Xref) -> {ok, LibraryPath}</name>
<fsummary>Return the library path.</fsummary>
<type>
<v>LibraryPath = library_path()</v>
@@ -1034,9 +1034,9 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>info(Xref) -> [Info]</name>
- <name>info(Xref, Category) -> [{Item, [Info]}]</name>
- <name>info(Xref, Category, Items) -> [{Item, [Info]}]</name>
+ <name since="">info(Xref) -> [Info]</name>
+ <name since="">info(Xref, Category) -> [{Item, [Info]}]</name>
+ <name since="">info(Xref, Category, Items) -> [{Item, [Info]}]</name>
<fsummary>Return information about an Xref server.</fsummary>
<type>
<v>Application = [] | [application()]</v>
@@ -1220,8 +1220,8 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>m(Module) -> [DebugInfoResult] | [NoDebugInfoResult] | Error</name>
- <name>m(File) -> [DebugInfoResult] | [NoDebugInfoResult] | Error</name>
+ <name since="">m(Module) -> [DebugInfoResult] | [NoDebugInfoResult] | Error</name>
+ <name since="">m(File) -> [DebugInfoResult] | [NoDebugInfoResult] | Error</name>
<fsummary>Check a module using the code path.</fsummary>
<type>
<v>DebugInfoResult = {deprecated, [funcall()]} | {undefined, [funcall()]} | {unused, [mfa()]}</v>
@@ -1263,7 +1263,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>q(Xref, Query [, Options]) -> {ok, Answer} | Error</name>
+ <name since="">q(Xref, Query [, Options]) -> {ok, Answer} | Error</name>
<fsummary>Evaluate a query.</fsummary>
<type>
<v>Answer = false | [constant()] | [Call] | [Component] | int() | [DefineAt] | [CallAt] | [AllLines]</v>
@@ -1322,7 +1322,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>remove_application(Xref, Applications) -> ok | Error</name>
+ <name since="">remove_application(Xref, Applications) -> ok | Error</name>
<fsummary>Remove applications and their modules.</fsummary>
<type>
<v>Applications = application() | [application()]</v>
@@ -1335,7 +1335,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>remove_module(Xref, Modules) -> ok | Error</name>
+ <name since="">remove_module(Xref, Modules) -> ok | Error</name>
<fsummary>Remove analyzed modules.</fsummary>
<type>
<v>Error = {error, module(), Reason}</v>
@@ -1348,7 +1348,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>remove_release(Xref, Releases) -> ok | Error</name>
+ <name since="">remove_release(Xref, Releases) -> ok | Error</name>
<fsummary>Remove releases and their applications and modules.</fsummary>
<type>
<v>Error = {error, module(), Reason}</v>
@@ -1363,7 +1363,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>replace_application(Xref, Application, Directory [, Options]) -> {ok, application()} | Error</name>
+ <name since="">replace_application(Xref, Application, Directory [, Options]) -> {ok, application()} | Error</name>
<fsummary>Replace an application's modules.</fsummary>
<type>
<v>Application = application()</v>
@@ -1384,7 +1384,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>replace_module(Xref, Module, File [, Options]) -> {ok, module()} | Error</name>
+ <name since="">replace_module(Xref, Module, File [, Options]) -> {ok, module()} | Error</name>
<fsummary>Replace an analyzed module.</fsummary>
<type>
<v>Error = {error, module(), Reason}</v>
@@ -1409,8 +1409,8 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>set_default(Xref, Option, Value) -> {ok, OldValue} | Error</name>
- <name>set_default(Xref, OptionValues) -> ok | Error</name>
+ <name since="">set_default(Xref, Option, Value) -> {ok, OldValue} | Error</name>
+ <name since="">set_default(Xref, OptionValues) -> ok | Error</name>
<fsummary>Set the default values of options.</fsummary>
<type>
<v>Error = {error, module(), Reason}</v>
@@ -1435,7 +1435,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>set_library_path(Xref, LibraryPath [, Options]) -> ok | Error</name>
+ <name since="">set_library_path(Xref, LibraryPath [, Options]) -> ok | Error</name>
<fsummary>Set the library path and finds the library modules.</fsummary>
<type>
<v>Error = {error, module(), Reason}</v>
@@ -1469,7 +1469,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>start(NameOrOptions) -> Return</name>
+ <name since="">start(NameOrOptions) -> Return</name>
<fsummary>Create an Xref server.</fsummary>
<type>
<v>NameOrOptions = Name | Options</v>
@@ -1487,7 +1487,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>start(Name, Options) -> Return</name>
+ <name since="">start(Name, Options) -> Return</name>
<fsummary>Create an Xref server.</fsummary>
<type>
<v>Name = atom()</v>
@@ -1504,7 +1504,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>stop(Xref)</name>
+ <name since="">stop(Xref)</name>
<fsummary>Delete an Xref server.</fsummary>
<type>
<v>Xref = xref()</v>
@@ -1514,7 +1514,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>update(Xref [, Options]) -> {ok, Modules} | Error</name>
+ <name since="">update(Xref [, Options]) -> {ok, Modules} | Error</name>
<fsummary>Replace newly compiled analyzed modules.</fsummary>
<type>
<v>Error = {error, module(), Reason}</v>
@@ -1534,7 +1534,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>variables(Xref [, Options]) -> {ok, [VariableInfo]}</name>
+ <name since="">variables(Xref [, Options]) -> {ok, [VariableInfo]}</name>
<fsummary>Return the names of variables.</fsummary>
<type>
<v>Options = [Option] | Option</v>
diff --git a/lib/tools/priv/styles.css b/lib/tools/priv/styles.css
index e10e94e3ad..84f00be9fd 100644
--- a/lib/tools/priv/styles.css
+++ b/lib/tools/priv/styles.css
@@ -53,21 +53,25 @@ table thead {
display: none;
}
table td.line,
+table td.line a,
table td.hits {
width: 20px;
background: #eaeaea;
text-align: center;
+ text-decoration: none;
font-size: 11px;
padding: 0 10px;
color: #949494;
}
table td.hits {
width: 10px;
+ text-align: right;
padding: 2px 5px;
color: rgba(0, 0, 0, 0.6);
background-color: #f0f0f0;
}
tr.miss td.line,
+tr.miss td.line a,
tr.miss td.hits {
background-color: #ffdce0;
border-color: #fdaeb7;
@@ -76,6 +80,7 @@ tr.miss td {
background-color: #ffeef0;
}
tr.hit td.line,
+tr.hit td.line a,
tr.hit td.hits {
background-color: #cdffd8;
border-color: #bef5cb;
diff --git a/lib/tools/src/cover.erl b/lib/tools/src/cover.erl
index d7269e3f27..8fe866cb69 100644
--- a/lib/tools/src/cover.erl
+++ b/lib/tools/src/cover.erl
@@ -23,6 +23,7 @@
%% This module implements the Erlang coverage tool.
%%
%% ARCHITECTURE
+%%
%% The coverage tool consists of one process on each node involved in
%% coverage analysis. The process is registered as 'cover_server'
%% (?SERVER). The cover_server on the 'main' node is in charge, and
@@ -30,45 +31,62 @@
%% 'DOWN' message for another cover_server, it marks the node as
%% 'lost'. If a nodeup is received for a lost node the main node
%% ensures that the cover compiled modules are loaded again. If the
-%% remote node was alive during the disconnected periode, cover data
-%% for this periode will also be included in the analysis.
+%% remote node was alive during the disconnected period, cover data
+%% for this period will also be included in the analysis.
%%
%% The cover_server process on the main node is implemented by the
%% functions init_main/1 and main_process_loop/1. The cover_server on
%% the remote nodes are implemented by the functions init_remote/2 and
%% remote_process_loop/1.
%%
+%% COUNTERS
+%%
+%% The 'counters' modules is used for counting how many time each line
+%% executed. Each cover-compiled module will have its own array of
+%% counters.
+%%
+%% The counter reference for module Module is stored in a persistent
+%% term with the key {cover,Module}.
+%%
+%% When the cover:local_only/0 function has been called, the reference
+%% for the counter array will be compiled into each cover-compiled
+%% module directly (instead of retrieving it from a persistent term).
+%% That will be faster, but the resulting code can be only be used on
+%% the main node.
+%%
%% TABLES
-%% Each nodes has two tables: cover_internal_data_table (?COVER_TABLE) and.
-%% cover_internal_clause_table (?COVER_CLAUSE_TABLE).
-%% ?COVER_TABLE contains the bump data i.e. the data about which lines
-%% have been executed how many times.
+%%
+%% Each node has two tables: ?COVER_MAPPING_TABLE and ?COVER_CLAUSE_TABLE.
+%% ?COVER_MAPPING_TABLE maps from a #bump{} record to an index in the
+%% counter array for the module. It is used both during instrumentation
+%% of cover-compiled modules and when collecting the counter values.
+%%
%% ?COVER_CLAUSE_TABLE contains information about which clauses in which modules
%% cover is currently collecting statistics.
-%%
-%% The main node owns tables named
-%% 'cover_collected_remote_data_table' (?COLLECTION_TABLE) and
-%% 'cover_collected_remote_clause_table' (?COLLECTION_CLAUSE_TABLE).
-%% These tables contain data which is collected from remote nodes (either when a
-%% remote node is stopped with cover:stop/1 or when analysing). When
-%% analysing, data is even moved from the COVER tables on the main
-%% node to the COLLECTION tables.
%%
-%% The main node also has a table named 'cover_binary_code_table'
-%% (?BINARY_TABLE). This table contains the binary code for each cover
-%% compiled module. This is necessary so that the code can be loaded
-%% on remote nodes that are started after the compilation.
+%% The main node owns the tables ?COLLECTION_TABLE and
+%% ?COLLECTION_CLAUSE_TABLE. The counter data is consolidated into those
+%% tables from the counters on both the main node and from remote nodes.
+%% This consolidation is done when a remote node is stopped with
+%% cover:stop/1 or just before starting an analysis.
+%%
+%% The main node also has a table named ?BINARY_TABLE. This table
+%% contains the abstract code code for each cover-compiled
+%% module. This is necessary so that the code can be loaded on remote
+%% nodes that are started after the compilation.
%%
%% PARALLELISM
+%%
%% To take advantage of SMP when doing the cover analysis both the data
%% collection and analysis has been parallelized. One process is spawned for
%% each node when collecting data, and on the remote node when collecting data
%% one process is spawned per module.
%%
-%% When analyzing data it is possible to issue multiple analyse(_to_file)/X
-%% calls at once. They are however all calls (for backwards compatibility
-%% reasons) so the user of cover will have to spawn several processes to to the
-%% calls ( or use async_analyse_to_file ).
+%% When analyzing data it is possible to issue multiple
+%% analyse(_to_file)/X calls at once. They are, however, all calls
+%% (for backwards compatibility reasons), so the user of cover will
+%% have to spawn several processes to to the calls (or use
+%% async_analyse_to_file/X).
%%
%% External exports
@@ -89,7 +107,8 @@
modules/0, imported/0, imported_modules/0, which_nodes/0, is_compiled/1,
reset/1, reset/0,
flush/1,
- stop/0, stop/1]).
+ stop/0, stop/1,
+ local_only/0]).
-export([remote_start/1,get_main_node/0]).
%% Used internally to ensure we upgrade the code to the latest version.
@@ -98,9 +117,16 @@
-record(main_state, {compiled=[], % [{Module,File}]
imported=[], % [{Module,File,ImportFile}]
stopper, % undefined | pid()
+ local_only=false, % true | false
nodes=[], % [Node]
lost_nodes=[]}). % [Node]
+-record(remote_data, {module,
+ file,
+ code,
+ mapping,
+ clauses}).
+
-record(remote_state, {compiled=[], % [{Module,File}]
main_node}). % atom()
@@ -126,11 +152,12 @@
is_guard=false % boolean
}).
--define(COVER_TABLE, 'cover_internal_data_table').
+-define(COVER_MAPPING_TABLE, 'cover_internal_mapping_table').
-define(COVER_CLAUSE_TABLE, 'cover_internal_clause_table').
-define(BINARY_TABLE, 'cover_binary_code_table').
-define(COLLECTION_TABLE, 'cover_collected_remote_data_table').
-define(COLLECTION_CLAUSE_TABLE, 'cover_collected_remote_clause_table').
+
-define(TAG, cover_compiled).
-define(SERVER, cover_server).
@@ -186,6 +213,11 @@ start(Node) when is_atom(Node) ->
start(Nodes) ->
call({start_nodes,remove_myself(Nodes,[])}).
+%% local_only() -> ok | {error,too_late}
+
+local_only() ->
+ call(local_only).
+
%% compile(ModFiles) ->
%% compile(ModFiles, Options) ->
%% compile_module(ModFiles) -> Result
@@ -255,15 +287,8 @@ compile_directory(Dir, Options) when is_list(Dir), is_list(Options) ->
compile_modules(Files,Options) ->
Options2 = filter_options(Options),
- %% compile_modules(Files,Options2,[]).
call({compile, Files, Options2}).
-%% compile_modules([File|Files], Options, Result) ->
-%% R = call({compile, File, Options}),
-%% compile_modules(Files,Options,[R|Result]);
-%% compile_modules([],_Opts,Result) ->
-%% lists:reverse(Result).
-
filter_options(Options) ->
lists:filter(fun(Option) ->
case Option of
@@ -561,16 +586,6 @@ flush(Nodes) ->
get_main_node() ->
call(get_main_node).
-%% bump(Module, Function, Arity, Clause, Line)
-%% Module = Function = atom()
-%% Arity = Clause = Line = integer()
-%% This function is inserted into Cover compiled modules, once for each
-%% executable line.
-%bump(Module, Function, Arity, Clause, Line) ->
-% Key = #bump{module=Module, function=Function, arity=Arity, clause=Clause,
-% line=Line},
-% ets:update_counter(?COVER_TABLE, Key, 1).
-
call(Request) ->
Ref = erlang:monitor(process,?SERVER),
receive {'DOWN', Ref, _Type, _Object, noproc} ->
@@ -631,10 +646,8 @@ remote_reply(MainNode,Reply) ->
init_main(Starter) ->
register(?SERVER,self()),
- %% Having write concurrancy here gives a 40% performance boost
- %% when collect/1 is called.
- ?COVER_TABLE = ets:new(?COVER_TABLE, [set, public, named_table,
- {write_concurrency, true}]),
+ ?COVER_MAPPING_TABLE = ets:new(?COVER_MAPPING_TABLE,
+ [ordered_set, public, named_table]),
?COVER_CLAUSE_TABLE = ets:new(?COVER_CLAUSE_TABLE, [set, public,
named_table]),
?BINARY_TABLE = ets:new(?BINARY_TABLE, [set, public, named_table]),
@@ -648,10 +661,26 @@ init_main(Starter) ->
main_process_loop(State) ->
receive
+ {From, local_only} ->
+ case State of
+ #main_state{compiled=[],nodes=[]} ->
+ reply(From, ok),
+ main_process_loop(State#main_state{local_only=true});
+ #main_state{} ->
+ reply(From, {error,too_late}),
+ main_process_loop(State)
+ end;
+
{From, {start_nodes,Nodes}} ->
- {StartedNodes,State1} = do_start_nodes(Nodes, State),
- reply(From, {ok,StartedNodes}),
- main_process_loop(State1);
+ case State#main_state.local_only of
+ false ->
+ {StartedNodes,State1} = do_start_nodes(Nodes, State),
+ reply(From, {ok,StartedNodes}),
+ main_process_loop(State1);
+ true ->
+ reply(From, {error,local_only}),
+ main_process_loop(State)
+ end;
{From, {compile, Files, Options}} ->
{R,S} = do_compile(Files, Options, State),
@@ -742,11 +771,12 @@ main_process_loop(State) ->
end,
State#main_state.nodes),
reload_originals(State#main_state.compiled),
- ets:delete(?COVER_TABLE),
+ ets:delete(?COVER_MAPPING_TABLE),
ets:delete(?COVER_CLAUSE_TABLE),
ets:delete(?BINARY_TABLE),
ets:delete(?COLLECTION_TABLE),
ets:delete(?COLLECTION_CLAUSE_TABLE),
+ delete_all_counters(),
unregister(?SERVER),
reply(From, ok);
@@ -878,10 +908,8 @@ main_process_loop(State) ->
init_remote(Starter,MainNode) ->
register(?SERVER,self()),
- %% write_concurrency here makes otp_8270 break :(
- ?COVER_TABLE = ets:new(?COVER_TABLE, [set, public, named_table
- %,{write_concurrency, true}
- ]),
+ ?COVER_MAPPING_TABLE = ets:new(?COVER_MAPPING_TABLE,
+ [ordered_set, public, named_table]),
?COVER_CLAUSE_TABLE = ets:new(?COVER_CLAUSE_TABLE, [set, public,
named_table]),
Starter ! {self(),started},
@@ -904,7 +932,7 @@ remote_process_loop(State) ->
remote_process_loop(State#remote_state{compiled=Compiled});
{remote,reset,Module} ->
- do_reset(Module),
+ reset_counters(Module),
remote_reply(State#remote_state.main_node, ok),
remote_process_loop(State);
@@ -925,8 +953,9 @@ remote_process_loop(State) ->
{remote,stop} ->
reload_originals(State#remote_state.compiled),
- ets:delete(?COVER_TABLE),
+ ets:delete(?COVER_MAPPING_TABLE),
ets:delete(?COVER_CLAUSE_TABLE),
+ delete_all_counters(),
unregister(?SERVER),
ok; % not replying since 'DOWN' message will be received anyway
@@ -961,28 +990,12 @@ remote_process_loop(State) ->
end.
do_collect(Modules, CollectorPid, From) ->
- _ = pmap(
- fun(Module) ->
- Pattern = {#bump{module=Module, _='_'}, '$1'},
- MatchSpec = [{Pattern,[{'=/=','$1',0}],['$_']}],
- Match = ets:select(?COVER_TABLE,MatchSpec,?CHUNK_SIZE),
- send_chunks(Match, CollectorPid, [])
- end,Modules),
+ _ = pmap(fun(Module) ->
+ send_counters(Module, CollectorPid)
+ end, Modules),
CollectorPid ! done,
remote_reply(From, ok).
-send_chunks('$end_of_table', _CollectorPid, Mons) ->
- get_downs(Mons);
-send_chunks({Chunk,Continuation}, CollectorPid, Mons) ->
- Mon = spawn_monitor(
- fun() ->
- lists:foreach(fun({Bump,_N}) ->
- ets:insert(?COVER_TABLE, {Bump,0})
- end,
- Chunk) end),
- send_chunk(CollectorPid,Chunk),
- send_chunks(ets:select(Continuation), CollectorPid, [Mon|Mons]).
-
send_chunk(CollectorPid,Chunk) ->
CollectorPid ! {chunk,Chunk,self()},
receive continue -> ok end.
@@ -1021,10 +1034,15 @@ do_reload_original(Module) ->
ignore
end.
-load_compiled([{Module,File,Binary,InitialTable}|Compiled],Acc) ->
- %% Make sure the #bump{} records are available *before* the
- %% module is loaded.
- insert_initial_data(InitialTable),
+load_compiled([Data|Compiled],Acc) ->
+ %% Make sure the #bump{} records and counters are available *before*
+ %% compiling and loading the code.
+ #remote_data{module=Module,file=File,code=Beam,
+ mapping=InitialMapping,clauses=InitialClauses} = Data,
+ ets:insert(?COVER_MAPPING_TABLE, InitialMapping),
+ ets:insert(?COVER_CLAUSE_TABLE, InitialClauses),
+ maybe_create_counters(Module, true),
+
Sticky = case code:is_sticky(Module) of
true ->
code:unstick_mod(Module),
@@ -1032,7 +1050,7 @@ load_compiled([{Module,File,Binary,InitialTable}|Compiled],Acc) ->
false ->
false
end,
- NewAcc = case code:load_binary(Module, ?TAG, Binary) of
+ NewAcc = case code:load_binary(Module, ?TAG, Beam) of
{module,Module} ->
add_compiled(Module, File, Acc);
_ ->
@@ -1047,16 +1065,6 @@ load_compiled([{Module,File,Binary,InitialTable}|Compiled],Acc) ->
load_compiled([],Acc) ->
Acc.
-insert_initial_data([Item|Items]) when is_atom(element(1,Item)) ->
- ets:insert(?COVER_CLAUSE_TABLE, Item),
- insert_initial_data(Items);
-insert_initial_data([Item|Items]) ->
- ets:insert(?COVER_TABLE, Item),
- insert_initial_data(Items);
-insert_initial_data([]) ->
- ok.
-
-
unload([Module|Modules]) ->
do_clear(Module),
do_reload_original(Module),
@@ -1177,7 +1185,7 @@ get_downs_r([]) ->
[];
get_downs_r(Mons) ->
receive
- {'DOWN', Ref, _Type, Pid, R={_,_,_,_}} ->
+ {'DOWN', Ref, _Type, Pid, #remote_data{}=R} ->
[R|get_downs_r(lists:delete({Pid,Ref},Mons))];
{'DOWN', Ref, _Type, Pid, Reason} = Down ->
case lists:member({Pid,Ref},Mons) of
@@ -1196,19 +1204,13 @@ get_downs_r(Mons) ->
%% Binary is the beam code for the module and InitialTable is the initial
%% data to insert in ?COVER_TABLE.
get_data_for_remote_loading({Module,File}) ->
- [{Module,Binary}] = ets:lookup(?BINARY_TABLE,Module),
+ [{Module,Code}] = ets:lookup(?BINARY_TABLE, Module),
%%! The InitialTable list will be long if the module is big - what to do??
- InitialBumps = ets:select(?COVER_TABLE,ms(Module)),
+ Mapping = counters_mapping_table(Module),
InitialClauses = ets:lookup(?COVER_CLAUSE_TABLE,Module),
- {Module,File,Binary,InitialBumps ++ InitialClauses}.
-
-%% Create a match spec which returns the clause info {Module,InitInfo} and
-%% all #bump keys for the given module with 0 number of calls.
-ms(Module) ->
- ets:fun2ms(fun({Key,_}) when Key#bump.module=:=Module ->
- {Key,0}
- end).
+ #remote_data{module=Module,file=File,code=Code,
+ mapping=Mapping,clauses=InitialClauses}.
%% Unload modules on remote nodes
remote_unload(Nodes,UnloadedModules) ->
@@ -1464,7 +1466,7 @@ get_compiled_still_loaded(Nodes,Compiled0) ->
do_compile_beams(ModsAndFiles, State) ->
Result0 = pmap(fun({ok,Module,File}) ->
- do_compile_beam(Module,File,State);
+ do_compile_beam(Module, File, State);
(Error) ->
Error
end,
@@ -1476,8 +1478,10 @@ do_compile_beams(ModsAndFiles, State) ->
do_compile_beam(Module,BeamFile0,State) ->
case get_beam_file(Module,BeamFile0,State#main_state.compiled) of
{ok,BeamFile} ->
+ LocalOnly = State#main_state.local_only,
UserOptions = get_compile_options(Module,BeamFile),
- case do_compile_beam1(Module,BeamFile,UserOptions) of
+ case do_compile_beam1(Module,BeamFile,
+ UserOptions,LocalOnly) of
{ok, Module} ->
{ok,Module,BeamFile};
error ->
@@ -1503,41 +1507,39 @@ fix_state_and_result([],State,Acc) ->
do_compile(Files, Options, State) ->
+ LocalOnly = State#main_state.local_only,
Result0 = pmap(fun(File) ->
- do_compile(File, Options)
+ do_compile1(File, Options, LocalOnly)
end,
Files),
Compiled = [{M,F} || {ok,M,F} <- Result0],
remote_load_compiled(State#main_state.nodes,Compiled),
fix_state_and_result(Result0,State,[]).
-do_compile(File, Options) ->
- case do_compile1(File, Options) of
+do_compile1(File, Options, LocalOnly) ->
+ case do_compile2(File, Options, LocalOnly) of
{ok, Module} ->
{ok,Module,File};
error ->
{error,File}
end.
-%% do_compile1(File, Options) -> {ok,Module} | error
-do_compile1(File, UserOptions) ->
+%% do_compile2(File, Options) -> {ok,Module} | error
+do_compile2(File, UserOptions, LocalOnly) ->
Options = [debug_info,binary,report_errors,report_warnings] ++ UserOptions,
case compile:file(File, Options) of
{ok, Module, Binary} ->
- do_compile_beam1(Module,Binary,UserOptions);
+ do_compile_beam1(Module,Binary,UserOptions,LocalOnly);
error ->
error
end.
%% Beam is a binary or a .beam file name
-do_compile_beam1(Module,Beam,UserOptions) ->
+do_compile_beam1(Module,Beam,UserOptions,LocalOnly) ->
%% Clear database
do_clear(Module),
- %% Extract the abstract format and insert calls to bump/6 at
- %% every executable line and, as a side effect, initiate
- %% the database
-
+ %% Extract the abstract format.
case get_abstract_code(Module, Beam) of
no_abstract_code=E ->
{error,E};
@@ -1547,7 +1549,8 @@ do_compile_beam1(Module,Beam,UserOptions) ->
Forms0 = epp:interpret_file_attribute(Code),
case find_main_filename(Forms0) of
{ok,MainFile} ->
- do_compile_beam2(Module,Beam,UserOptions,Forms0,MainFile);
+ do_compile_beam2(Module,Beam,UserOptions,
+ Forms0,MainFile,LocalOnly);
Error ->
Error
end;
@@ -1566,26 +1569,35 @@ get_abstract_code(Module, Beam) ->
Error -> Error
end.
-do_compile_beam2(Module,Beam,UserOptions,Forms0,MainFile) ->
- {Forms,Vars} = transform(Forms0, Module, MainFile),
+do_compile_beam2(Module,Beam,UserOptions,Forms0,MainFile,LocalOnly) ->
+ init_counter_mapping(Module),
+
+ %% Instrument the abstract code by inserting
+ %% calls to update the counters.
+ {Forms,Vars} = transform(Forms0, Module, MainFile, LocalOnly),
+
+ %% Create counters.
+ maybe_create_counters(Module, not LocalOnly),
%% We need to recover the source from the compilation
%% info otherwise the newly compiled module will have
%% source pointing to the current directory
SourceInfo = get_source_info(Module, Beam),
- %% Compile and load the result
+ %% Compile and load the result.
%% It's necessary to check the result of loading since it may
- %% fail, for example if Module resides in a sticky directory
- {ok, Module, Binary} = compile:forms(Forms, SourceInfo ++ UserOptions),
+ %% fail, for example if Module resides in a sticky directory.
+ Options = SourceInfo ++ UserOptions,
+ {ok, Module, Binary} = compile:forms(Forms, Options),
+
case code:load_binary(Module, ?TAG, Binary) of
{module, Module} ->
- %% Store info about all function clauses in database
+ %% Store info about all function clauses in database.
InitInfo = lists:reverse(Vars#vars.init_info),
ets:insert(?COVER_CLAUSE_TABLE, {Module, InitInfo}),
- %% Store binary code so it can be loaded on remote nodes
+ %% Store binary code so it can be loaded on remote nodes.
ets:insert(?BINARY_TABLE, {Module, Binary}),
{ok, Module};
@@ -1617,11 +1629,12 @@ get_compile_info(Module, Beam) ->
[]
end.
-transform(Code, Module, MainFile) ->
+transform(Code, Module, MainFile, LocalOnly) ->
Vars0 = #vars{module=Module},
- {ok,MungedForms,Vars} = transform_2(Code,[],Vars0,MainFile,on),
+ {ok,MungedForms0,Vars} = transform_2(Code, [], Vars0, MainFile, on),
+ MungedForms = patch_code(Module, MungedForms0, LocalOnly),
{MungedForms,Vars}.
-
+
%% Helpfunction which returns the first found file-attribute, which can
%% be interpreted as the name of the main erlang source file.
find_main_filename([{attribute,_,file,{MainFile,_}}|_]) ->
@@ -1788,19 +1801,7 @@ munge_body([Expr|Body], Vars, MungedBody, LastExprBumpLines) ->
MungedExprs1 = [MungedExpr|MungedBody1],
munge_body(Body, Vars3, MungedExprs1, NewBumps);
false ->
- ets:insert(?COVER_TABLE, {#bump{module = Vars#vars.module,
- function = Vars#vars.function,
- arity = Vars#vars.arity,
- clause = Vars#vars.clause,
- line = Line},
- 0}),
Bump = bump_call(Vars, Line),
-% Bump = {call, 0, {remote, 0, {atom,0,cover}, {atom,0,bump}},
-% [{atom, 0, Vars#vars.module},
-% {atom, 0, Vars#vars.function},
-% {integer, 0, Vars#vars.arity},
-% {integer, 0, Vars#vars.clause},
-% {integer, 0, Line}]},
Lines2 = [Line|Lines],
{MungedExpr, Vars2} = munge_expr(Expr, Vars#vars{lines=Lines2}),
NewBumps = new_bumps(Vars2, Vars),
@@ -1855,8 +1856,10 @@ maybe_fix_last_expr(MungedExprs, Vars, LastExprBumpLines) ->
last_expr_needs_fixing(Vars, LastExprBumpLines) ->
case common_elems(Vars#vars.no_bump_lines, LastExprBumpLines) of
- [Line] -> {yes, Line};
- _ -> no
+ [Line] ->
+ {yes, Line};
+ _ ->
+ no
end.
fix_last_expr([MungedExpr|MungedExprs], Line, Vars) ->
@@ -1921,9 +1924,7 @@ fix_cls([Cl | Cls], Line, Bump) ->
bumps_line(E, L) ->
try bumps_line1(E, L) catch true -> true end.
-bumps_line1({call,_,{remote,_,{atom,_,ets},{atom,_,update_counter}},
- [{atom,_,?COVER_TABLE},{tuple,_,[_,_,_,_,_,{integer,_,Line}]},_]},
- Line) ->
+bumps_line1({'BUMP',Line,_}, Line) ->
throw(true);
bumps_line1([E | Es], Line) ->
bumps_line1(E, Line),
@@ -1933,19 +1934,12 @@ bumps_line1(T, Line) when is_tuple(T) ->
bumps_line1(_, _) ->
false.
-%%% End of fix of last expression.
-
+%% Insert a place holder for the call to counters:add/3 in the
+%% abstract code.
bump_call(Vars, Line) ->
- A = erl_anno:new(0),
- {call,A,{remote,A,{atom,A,ets},{atom,A,update_counter}},
- [{atom,A,?COVER_TABLE},
- {tuple,A,[{atom,A,?BUMP_REC_NAME},
- {atom,A,Vars#vars.module},
- {atom,A,Vars#vars.function},
- {integer,A,Vars#vars.arity},
- {integer,A,Vars#vars.clause},
- {integer,A,Line}]},
- {integer,A,1}]}.
+ {'BUMP',Line,counter_index(Vars, Line)}.
+
+%%% End of fix of last expression.
munge_expr({match,Line,ExprL,ExprR}, Vars) ->
{MungedExprL, Vars2} = munge_expr(ExprL, Vars),
@@ -2105,6 +2099,159 @@ subtract(L1, L2) ->
common_elems(L1, L2) ->
[E || E <- L1, lists:member(E, L2)].
+%%%--Counters------------------------------------------------------------
+
+init_counter_mapping(Mod) ->
+ true = ets:insert_new(?COVER_MAPPING_TABLE, {Mod,0}),
+ ok.
+
+counter_index(Vars, Line) ->
+ #vars{module=Mod,function=F,arity=A,clause=C} = Vars,
+ Key = #bump{module=Mod,function=F,arity=A,
+ clause=C,line=Line},
+ case ets:lookup(?COVER_MAPPING_TABLE, Key) of
+ [] ->
+ Index = ets:update_counter(?COVER_MAPPING_TABLE,
+ Mod, {2,1}),
+ true = ets:insert(?COVER_MAPPING_TABLE, {Key,Index}),
+ Index;
+ [{Key,Index}] ->
+ Index
+ end.
+
+%% Create the counter array and store as a persistent term.
+maybe_create_counters(Mod, true) ->
+ Cref = create_counters(Mod),
+ Key = {?MODULE,Mod},
+ persistent_term:put(Key, Cref),
+ ok;
+maybe_create_counters(_Mod, false) ->
+ ok.
+
+create_counters(Mod) ->
+ Size0 = ets:lookup_element(?COVER_MAPPING_TABLE, Mod, 2),
+ Size = max(1, Size0), %Size must not be 0.
+ Cref = counters:new(Size, [write_concurrency]),
+ ets:insert(?COVER_MAPPING_TABLE, {{counters,Mod},Cref}),
+ Cref.
+
+patch_code(Mod, Forms, false) ->
+ A = erl_anno:new(0),
+ AbstrKey = {tuple,A,[{atom,A,?MODULE},{atom,A,Mod}]},
+ patch_code1(Forms, {distributed,AbstrKey});
+patch_code(Mod, Forms, true) ->
+ Cref = create_counters(Mod),
+ AbstrCref = cid_to_abstract(Cref),
+ patch_code1(Forms, {local_only,AbstrCref}).
+
+%% Go through the abstract code and replace 'BUMP' forms
+%% with the actual code to increment the counters.
+patch_code1({'BUMP',_Line,Index}, {distributed,AbstrKey}) ->
+ %% Replace with counters:add(persistent_term:get(Key), Index, 1).
+ %% This code will work on any node.
+ A = element(2, AbstrKey),
+ GetCref = {call,A,{remote,A,{atom,A,persistent_term},{atom,A,get}},
+ [AbstrKey]},
+ {call,A,{remote,A,{atom,A,counters},{atom,A,add}},
+ [GetCref,{integer,A,Index},{integer,A,1}]};
+patch_code1({'BUMP',_Line,Index}, {local_only,AbstrCref}) ->
+ %% Replace with counters:add(Cref, Index, 1). This code
+ %% will only work on the local node.
+ A = element(2, AbstrCref),
+ {call,A,{remote,A,{atom,A,counters},{atom,A,add}},
+ [AbstrCref,{integer,A,Index},{integer,A,1}]};
+patch_code1({clauses,Cs}, Key) ->
+ {clauses,[patch_code1(El, Key) || El <- Cs]};
+patch_code1([_|_]=List, Key) ->
+ [patch_code1(El, Key) || El <- List];
+patch_code1(Tuple, Key) when tuple_size(Tuple) >= 3 ->
+ Acc = [element(2, Tuple),element(1, Tuple)],
+ patch_code_tuple(3, tuple_size(Tuple), Tuple, Key, Acc);
+patch_code1(Other, _Key) ->
+ Other.
+
+patch_code_tuple(I, Size, Tuple, Key, Acc) when I =< Size ->
+ El = patch_code1(element(I, Tuple), Key),
+ patch_code_tuple(I + 1, Size, Tuple, Key, [El|Acc]);
+patch_code_tuple(_I, _Size, _Tuple, _Key, Acc) ->
+ list_to_tuple(lists:reverse(Acc)).
+
+%% Don't try this at home! Assumes knowledge of the internal
+%% representation of a counter ref.
+cid_to_abstract(Cref0) ->
+ A = erl_anno:new(0),
+ %% Disable dialyzer warning for breaking opacity.
+ Cref = binary_to_term(term_to_binary(Cref0)),
+ {write_concurrency,Ref} = Cref,
+ {tuple,A,[{atom,A,write_concurrency},{integer,A,Ref}]}.
+
+%% Called on the remote node. Collect and send counters to
+%% the main node. Also zero the counters.
+send_counters(Mod, CollectorPid) ->
+ Process = fun(Chunk) -> send_chunk(CollectorPid, Chunk) end,
+ move_counters(Mod, Process).
+
+%% Called on the main node. Collect the counters and consolidate
+%% them into the collection table. Also zero the counters.
+move_counters(Mod) ->
+ move_counters(Mod, fun insert_in_collection_table/1).
+
+move_counters(Mod, Process) ->
+ Pattern = {#bump{module=Mod,_='_'},'_'},
+ Matches = ets:match_object(?COVER_MAPPING_TABLE, Pattern, ?CHUNK_SIZE),
+ Cref = get_counters_ref(Mod),
+ move_counters1(Matches, Cref, Process).
+
+move_counters1({Mappings,Continuation}, Cref, Process) ->
+ Move = fun({Key,Index}) ->
+ Count = counters:get(Cref, Index),
+ ok = counters:sub(Cref, Index, Count),
+ {Key,Count}
+ end,
+ Process(lists:map(Move, Mappings)),
+ move_counters1(ets:match_object(Continuation), Cref, Process);
+move_counters1('$end_of_table', _Cref, _Process) ->
+ ok.
+
+counters_mapping_table(Mod) ->
+ Mapping = counters_mapping(Mod),
+ Cref = get_counters_ref(Mod),
+ #{size:=Size} = counters:info(Cref),
+ [{Mod,Size}|Mapping].
+
+get_counters_ref(Mod) ->
+ ets:lookup_element(?COVER_MAPPING_TABLE, {counters,Mod}, 2).
+
+counters_mapping(Mod) ->
+ Pattern = {#bump{module=Mod,_='_'},'_'},
+ ets:match_object(?COVER_MAPPING_TABLE, Pattern).
+
+clear_counters(Mod) ->
+ _ = persistent_term:erase({?MODULE,Mod}),
+ ets:delete(?COVER_MAPPING_TABLE, Mod),
+ Pattern = {#bump{module=Mod,_='_'},'_'},
+ _ = ets:match_delete(?COVER_MAPPING_TABLE, Pattern),
+ ok.
+
+%% Reset counters (set counters to 0).
+reset_counters(Mod) ->
+ Pattern = {#bump{module=Mod,_='_'},'$1'},
+ MatchSpec = [{Pattern,[],['$1']}],
+ Matches = ets:select(?COVER_MAPPING_TABLE,
+ MatchSpec, ?CHUNK_SIZE),
+ Cref = get_counters_ref(Mod),
+ reset_counters1(Matches, Cref).
+
+reset_counters1({Indices,Continuation}, Cref) ->
+ _ = [counters:put(Cref, N, 0) || N <- Indices],
+ reset_counters1(ets:select(Continuation), Cref);
+reset_counters1('$end_of_table', _Cref) ->
+ ok.
+
+delete_all_counters() ->
+ _ = [persistent_term:erase(Key) || {?MODULE,_}=Key <- persistent_term:get()],
+ ok.
+
%%%--Analysis------------------------------------------------------------
%% Collect data for all modules
@@ -2140,20 +2287,7 @@ collect(Module,Clauses,Nodes) ->
%% ?COLLECTION_TABLE. Resetting data in ?COVER_TABLE
move_modules({Module,Clauses}) ->
ets:insert(?COLLECTION_CLAUSE_TABLE,{Module,Clauses}),
- Pattern = {#bump{module=Module, _='_'}, '_'},
- MatchSpec = [{Pattern,[],['$_']}],
- Match = ets:select(?COVER_TABLE,MatchSpec,?CHUNK_SIZE),
- do_move_module(Match).
-
-do_move_module({Bumps,Continuation}) ->
- lists:foreach(fun({Key,Val}) ->
- ets:insert(?COVER_TABLE, {Key,0}),
- insert_in_collection_table(Key,Val)
- end,
- Bumps),
- do_move_module(ets:select(Continuation));
-do_move_module('$end_of_table') ->
- ok.
+ move_counters(Module).
%% Given a .beam file, find the .erl file. Look first in same directory as
%% the .beam file, then in ../src, then in compile info.
@@ -2563,11 +2697,13 @@ table_row(Line, L) ->
table_data(Line, L, N) ->
LineNoNL = Line -- "\n",
["<td class=\"line\" id=\"L",integer_to_list(L),"\">",
+ "<a href=\"#L",integer_to_list(L),"\">",
integer_to_list(L),
- "</td>\n",
+ "</a></td>\n",
"<td class=\"hits\">",maybe_integer_to_list(N),"</td>\n",
"<td class=\"source\"><code>",LineNoNL,"</code></td>\n</tr>\n"].
+maybe_integer_to_list(0) -> "<pre style=\"display: inline;\">:-(</pre>";
maybe_integer_to_list(N) when is_integer(N) -> integer_to_list(N);
maybe_integer_to_list(_) -> "".
@@ -2707,7 +2843,7 @@ get_term(Fd) ->
%% Reset main node and all remote nodes
do_reset_main_node(Module,Nodes) ->
- do_reset(Module),
+ reset_counters(Module),
do_reset_collection_table(Module),
remote_reset(Module,Nodes).
@@ -2715,27 +2851,9 @@ do_reset_collection_table(Module) ->
ets:delete(?COLLECTION_CLAUSE_TABLE,Module),
ets:match_delete(?COLLECTION_TABLE, {#bump{module=Module},'_'}).
-%% do_reset(Module) -> ok
-%% The reset is done on ?CHUNK_SIZE number of bumps to avoid building
-%% long lists in the case of very large modules
-do_reset(Module) ->
- Pattern = {#bump{module=Module, _='_'}, '$1'},
- MatchSpec = [{Pattern,[{'=/=','$1',0}],['$_']}],
- Match = ets:select(?COVER_TABLE,MatchSpec,?CHUNK_SIZE),
- do_reset2(Match).
-
-do_reset2({Bumps,Continuation}) ->
- lists:foreach(fun({Bump,_N}) ->
- ets:insert(?COVER_TABLE, {Bump,0})
- end,
- Bumps),
- do_reset2(ets:select(Continuation));
-do_reset2('$end_of_table') ->
- ok.
-
do_clear(Module) ->
ets:match_delete(?COVER_CLAUSE_TABLE, {Module,'_'}),
- ets:match_delete(?COVER_TABLE, {#bump{module=Module},'_'}),
+ clear_counters(Module),
case lists:member(?COLLECTION_TABLE, ets:all()) of
true ->
%% We're on the main node
diff --git a/lib/tools/src/tools.app.src b/lib/tools/src/tools.app.src
index f8c6aa22cb..f0e0fc4bec 100644
--- a/lib/tools/src/tools.app.src
+++ b/lib/tools/src/tools.app.src
@@ -21,11 +21,13 @@
[{description, "DEVTOOLS CXC 138 16"},
{vsn, "%VSN%"},
{modules, [cover,
+ cprof,
eprof,
fprof,
instrument,
lcnt,
make,
+ tags,
xref,
xref_base,
xref_compiler,
diff --git a/lib/tools/test/cover_SUITE.erl b/lib/tools/test/cover_SUITE.erl
index 161b0105b9..ee58fd7a10 100644
--- a/lib/tools/test/cover_SUITE.erl
+++ b/lib/tools/test/cover_SUITE.erl
@@ -24,7 +24,8 @@
-include_lib("common_test/include/ct.hrl").
suite() ->
- [{ct_hooks,[ts_install_cth]}].
+ [{ct_hooks,[ts_install_cth]},
+ {timetrap,{minutes,5}}].
all() ->
NoStartStop = [eif,otp_5305,otp_5418,otp_7095,otp_8273,
@@ -35,7 +36,8 @@ all() ->
distribution, reconnect, die_and_reconnect,
dont_reconnect_after_stop, stop_node_after_disconnect,
export_import, otp_5031, otp_6115,
- otp_8270, otp_10979_hanging_node, otp_14817],
+ otp_8270, otp_10979_hanging_node, otp_14817,
+ local_only],
case whereis(cover_server) of
undefined ->
[coverage,StartStop ++ NoStartStop];
@@ -1742,6 +1744,40 @@ otp_13289(Config) ->
ok = file:delete(File),
ok.
+local_only(Config) ->
+ ok = file:set_cwd(proplists:get_value(data_dir, Config)),
+
+ %% Trying restricting to local nodes too late.
+ cover:start(),
+ {ok,a} = cover:compile(a),
+ [a] = cover:modules(),
+ {error,too_late} = cover:local_only(),
+ cover:stop(),
+
+ %% Now test local only mode.
+ cover:start(),
+ ok = cover:local_only(),
+ [] = cover:modules(),
+ {ok,a} = cover:compile(a),
+ [a] = cover:modules(),
+ done = a:start(5),
+ {ok, {a,{17,2}}} = cover:analyse(a, coverage, module),
+ {ok, [{{a,exit_kalle,0},{1,0}},
+ {{a,loop,3},{5,1}},
+ {{a,pong,1},{1,0}},
+ {{a,start,1},{6,0}},
+ {{a,stop,1},{0,1}},
+ {{a,trycatch,1},{4,0}}]} =
+ cover:analyse(a, coverage, function),
+
+ %% Make sure that it is not possible to run cover on
+ %% slave nodes.
+ {ok,Name} = test_server:start_node(?FUNCTION_NAME, slave, []),
+ {error,local_only} = cover:start([Name]),
+ test_server:stop_node(Name),
+
+ ok.
+
%%--Auxiliary------------------------------------------------------------
analyse_expr(Expr, Config) ->
diff --git a/lib/tools/test/instrument_SUITE.erl b/lib/tools/test/instrument_SUITE.erl
index 8c521b2e1a..33259df58f 100644
--- a/lib/tools/test/instrument_SUITE.erl
+++ b/lib/tools/test/instrument_SUITE.erl
@@ -77,6 +77,8 @@ allocations_ramv(Config) when is_list(Config) ->
verify_allocations_disabled(_AllocType, Result) ->
verify_allocations_disabled(Result).
+verify_allocations_disabled({ok, {_HistStart, _UnscannedBytes, Allocs}}) ->
+ true = Allocs =:= #{};
verify_allocations_disabled({error, not_enabled}) ->
ok.
@@ -91,6 +93,13 @@ verify_allocations_enabled(_AllocType, Result) ->
verify_allocations_enabled({ok, {_HistStart, _UnscannedBytes, Allocs}}) ->
true = Allocs =/= #{}.
+verify_allocations_output(#{}, {ok, {_, _, Allocs}}) when Allocs =:= #{} ->
+ %% This happens when the allocator is enabled but tagging is disabled. If
+ %% there's an error that causes Allocs to always be empty when enabled it
+ %% will be caught by verify_allocations_enabled.
+ ok;
+verify_allocations_output(#{}, {error, not_enabled}) ->
+ ok;
verify_allocations_output(#{ histogram_start := HistStart,
histogram_width := HistWidth },
{ok, {HistStart, _UnscannedBytes, ByOrigin}}) ->
@@ -124,8 +133,6 @@ verify_allocations_output(#{ histogram_start := HistStart,
[BlockCount, GenTotalBlockCount])
end,
- ok;
-verify_allocations_output(#{}, {error, not_enabled}) ->
ok.
%% %% %% %% %% %%
@@ -214,7 +221,8 @@ verify_carriers_output(#{ histogram_start := HistStart,
ct:fail("Carrier count is ~p, expected at least ~p (SBC).",
[CarrierCount, GenSBCCount]);
CarrierCount >= GenSBCCount ->
- ok
+ ct:pal("Found ~p carriers, required at least ~p (SBC)." ,
+ [CarrierCount, GenSBCCount])
end,
ok;
@@ -292,9 +300,19 @@ start_slave(Args) ->
MicroSecs = erlang:monotonic_time(),
Name = "instr" ++ integer_to_list(MicroSecs),
Pa = filename:dirname(code:which(?MODULE)),
- {ok, Node} = test_server:start_node(list_to_atom(Name),
- slave,
- [{args, "-pa " ++ Pa ++ " " ++ Args}]),
+
+ %% We pass arguments through ZFLAGS as the nightly tests rotate
+ %% +Meamax/+Meamin which breaks the _enabled and _disabled tests unless
+ %% overridden.
+ ZFlags = os:getenv("ERL_ZFLAGS", ""),
+ {ok, Node} = try
+ os:putenv("ERL_ZFLAGS", ZFlags ++ [" " | Args]),
+ test_server:start_node(list_to_atom(Name),
+ slave,
+ [{args, "-pa " ++ Pa}])
+ after
+ os:putenv("ERL_ZFLAGS", ZFlags)
+ end,
Node.
generate_test_blocks() ->
@@ -309,8 +327,9 @@ generate_test_blocks() ->
MBCs = [<<I, 0:64/unit:8>> ||
I <- lists:seq(1, ?GENERATED_MBC_BLOCK_COUNT)],
Runner ! Ref,
- receive after infinity -> ok end,
- unreachable ! {SBCs, MBCs}
+ receive
+ gurka -> gaffel ! {SBCs, MBCs}
+ end
end),
receive
Ref -> ok
diff --git a/lib/wx/api_gen/wx_extra/added_func.h b/lib/wx/api_gen/wx_extra/added_func.h
index bffe391140..28fecbf454 100644
--- a/lib/wx/api_gen/wx_extra/added_func.h
+++ b/lib/wx/api_gen/wx_extra/added_func.h
@@ -44,3 +44,9 @@ class wxWindowGTK {
public:
double GetContentScaleFactor();
};
+
+class wxDisplay {
+ public:
+ // get the resolution of this monitor in pixels per inch
+ wxSize GetPPI() const;
+};
diff --git a/lib/wx/api_gen/wx_gen.erl b/lib/wx/api_gen/wx_gen.erl
index cec6ac9ccf..8a00498319 100644
--- a/lib/wx/api_gen/wx_gen.erl
+++ b/lib/wx/api_gen/wx_gen.erl
@@ -701,8 +701,13 @@ parse_type2(["wxe_cb"|R],Info,Opts, T) ->
parse_type2(R,Info,Opts,T#type{name=int,base=wxe_cb});
parse_type2([const|R],Info,Opts,T=#type{mod=Mod}) ->
parse_type2(R,Info,Opts,T#type{mod=[const|Mod]});
-parse_type2(["unsigned"|R],Info,Opts,T=#type{mod=Mod}) ->
- parse_type2(R,Info,Opts,T#type{mod=[unsigned|Mod]});
+parse_type2(["unsigned"|R],Info,Opts,T=#type{mod=Mod}) ->
+ case T#type.base of
+ undefined ->
+ parse_type2(R,Info,Opts,T#type{name=int, base=int, mod=[unsigned|Mod]});
+ _ ->
+ parse_type2(R,Info,Opts,T#type{mod=[unsigned|Mod]})
+ end;
parse_type2(["int"|R],Info,Opts, T) ->
parse_type2(R,Info,Opts,T#type{name=int,base=int});
parse_type2(["wxByte"|R],Info,Opts, T) ->
diff --git a/lib/wx/api_gen/wx_gen_cpp.erl b/lib/wx/api_gen/wx_gen_cpp.erl
index f13d5873a0..c6f2534380 100644
--- a/lib/wx/api_gen/wx_gen_cpp.erl
+++ b/lib/wx/api_gen/wx_gen_cpp.erl
@@ -1165,6 +1165,7 @@ gen_macros() ->
w("#include <wx/fontdlg.h>~n"),
w("#include <wx/progdlg.h>~n"),
w("#include <wx/printdlg.h>~n"),
+ w("#include <wx/display.h>~n"),
w("#include <wx/dcbuffer.h>~n"),
w("#include <wx/dcmirror.h>~n"),
w("#include <wx/glcanvas.h>~n"),
@@ -1176,6 +1177,7 @@ gen_macros() ->
w("#include <wx/sashwin.h>~n"),
w("#include <wx/laywin.h>~n"),
w("#include <wx/graphics.h>~n"),
+ w("#include <wx/dcgraph.h>~n"),
w("#include <wx/aui/aui.h>~n"),
w("#include <wx/datectrl.h>~n"),
w("#include <wx/filepicker.h>~n"),
@@ -1330,8 +1332,10 @@ encode_events(Evs) ->
w(" } else {~n"),
w(" send_res = rt.send();~n"),
w(" if(cb->skip) event->Skip();~n"),
- #class{id=MouseId} = lists:keyfind("wxMouseEvent", #class.name, Evs),
- w(" if(app->recurse_level < 1 && Etype->cID != ~p) {~n", [MouseId]),
+ #class{id=SizeId} = lists:keyfind("wxSizeEvent", #class.name, Evs),
+ #class{id=MoveId} = lists:keyfind("wxMoveEvent", #class.name, Evs),
+ w(" if(app->recurse_level < 1 && (Etype->cID == ~w || Etype->cID == ~w)) {~n",
+ [SizeId, MoveId]),
w(" app->recurse_level++;~n"),
w(" app->dispatch_cmds();~n"),
w(" app->recurse_level--;~n"),
diff --git a/lib/wx/api_gen/wxapi.conf b/lib/wx/api_gen/wxapi.conf
index c1b55b6875..9707fedf67 100644
--- a/lib/wx/api_gen/wxapi.conf
+++ b/lib/wx/api_gen/wxapi.conf
@@ -27,7 +27,7 @@
{not_const, [wxHAS_INT64,wxBYTE_ORDER,wxRETAINED,
wxFONTENCODING_UTF32,wxFONTENCODING_UTF16,
wxDEFAULT_CONTROL_BORDER,wxMOD_CMD,
- wxMAJOR_VERSION, wxMINOR_VERSION,
+ wxMAJOR_VERSION, wxMINOR_VERSION,
wxRELEASE_NUMBER,wxSUBRELEASE_NUMBER,wxBETA_NUMBER,
%%
wxALWAYS_NATIVE_DOUBLE_BUFFER,
@@ -37,16 +37,30 @@
wxCURSOR_DEFAULT,
wxCURSOR_ARROWWAIT,
wxCURSOR_MAX,
- wxLanguage
+ wxLanguage,
+ wxFONTWEIGHT_NORMAL,
+ wxFONTWEIGHT_LIGHT,
+ wxFONTWEIGHT_BOLD,
+ wxFONTWEIGHT_MAX
]}.
-{gvars,
+{gvars,
[
{wxITALIC_FONT, wxFont},
{wxNORMAL_FONT, wxFont},
{wxSMALL_FONT, wxFont},
{wxSWISS_FONT, wxFont},
-
+
+ %% Added (enum) values in 3.1.2
+ {wxFONTWEIGHT_INVALID, {test_if, "wxCHECK_VERSION(3,1,2)"}},
+ {wxFONTWEIGHT_THIN, {test_if, "wxCHECK_VERSION(3,1,2)"}},
+ {wxFONTWEIGHT_EXTRALIGHT, {test_if, "wxCHECK_VERSION(3,1,2)"}},
+ {wxFONTWEIGHT_MEDIUM, {test_if, "wxCHECK_VERSION(3,1,2)"}},
+ {wxFONTWEIGHT_SEMIBOLD, {test_if, "wxCHECK_VERSION(3,1,2)"}},
+ {wxFONTWEIGHT_EXTRABOLD, {test_if, "wxCHECK_VERSION(3,1,2)"}},
+ {wxFONTWEIGHT_HEAVY, {test_if, "wxCHECK_VERSION(3,1,2)"}},
+ {wxFONTWEIGHT_EXTRAHEAVY, {test_if, "wxCHECK_VERSION(3,1,2)"}},
+
{wxBLACK_DASHED_PEN, wxPen},
{wxBLACK_PEN, wxPen},
{wxCYAN_PEN, wxPen},
@@ -2016,3 +2030,17 @@
['GetPosition', 'GetNumberOfFiles',
{'GetFiles', [{return, [{single, {list, 'm_noFiles'}}]}]}
]}.
+
+
+{class, wxDisplay, root, [{ifdef, wxUSE_DISPLAY}],
+ ['wxDisplay', '~wxDisplay',
+ 'IsOk',
+ {'GetClientArea', [{test_if, "wxCHECK_VERSION(2,8,12)"}]},
+ 'GetGeometry', 'GetName', 'IsPrimary',
+ 'GetCount', 'GetFromPoint', 'GetFromWindow',
+ {'GetPPI', [{test_if, "wxCHECK_VERSION(3,1,2)"}]}
+ ]}.
+
+{class, wxGCDC, wxDC, [{ifdef, wxUSE_GRAPHICS_CONTEXT}],
+ ['wxGCDC', '~wxGCDC', 'GetGraphicsContext', 'SetGraphicsContext'
+ ]}.
diff --git a/lib/wx/c_src/Makefile.in b/lib/wx/c_src/Makefile.in
index daa8afce83..8ec64bea7e 100644
--- a/lib/wx/c_src/Makefile.in
+++ b/lib/wx/c_src/Makefile.in
@@ -181,6 +181,7 @@ release_spec: opt
$(INSTALL_DIR) "$(RELSYSDIR)/priv"
$(INSTALL_DATA) ../priv/erlang-logo32.png "$(RELSYSDIR)/priv/"
$(INSTALL_DATA) ../priv/erlang-logo64.png "$(RELSYSDIR)/priv/"
+ $(INSTALL_DATA) ../priv/erlang-logo128.png "$(RELSYSDIR)/priv/"
$(INSTALL_PROGRAM) $(TARGET_DIR)/wxe_driver$(SO_EXT) "$(RELSYSDIR)/priv/"
$(INSTALL_PROGRAM) $(TARGET_DIR)/erl_gl$(SO_EXT) "$(RELSYSDIR)/priv/"
diff --git a/lib/wx/c_src/gen/wxe_derived_dest.h b/lib/wx/c_src/gen/wxe_derived_dest.h
index fc0ae0d9fc..a7114eb188 100644
--- a/lib/wx/c_src/gen/wxe_derived_dest.h
+++ b/lib/wx/c_src/gen/wxe_derived_dest.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2008-2016. All Rights Reserved.
+ * Copyright Ericsson AB 2008-2018. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -799,3 +799,11 @@ class EwxDCOverlay : public wxDCOverlay {
EwxDCOverlay(wxOverlay& overlay,wxWindowDC * dc) : wxDCOverlay(overlay,dc) {};
};
+#if wxUSE_GRAPHICS_CONTEXT
+class EwxGCDC : public wxGCDC {
+ public: ~EwxGCDC() {((WxeApp *)wxTheApp)->clearPtr(this);};
+ EwxGCDC(const wxWindowDC& dc) : wxGCDC(dc) {};
+ EwxGCDC() : wxGCDC() {};
+};
+#endif // wxUSE_GRAPHICS_CONTEXT
+
diff --git a/lib/wx/c_src/gen/wxe_events.cpp b/lib/wx/c_src/gen/wxe_events.cpp
index 01787c8a64..8c3283a670 100644
--- a/lib/wx/c_src/gen/wxe_events.cpp
+++ b/lib/wx/c_src/gen/wxe_events.cpp
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2008-2016. All Rights Reserved.
+ * Copyright Ericsson AB 2008-2019. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -910,7 +910,7 @@ case 238: {// wxDropFilesEvent
} else {
send_res = rt.send();
if(cb->skip) event->Skip();
- if(app->recurse_level < 1 && Etype->cID != 168) {
+ if(app->recurse_level < 1 && (Etype->cID == 171 || Etype->cID == 172)) {
app->recurse_level++;
app->dispatch_cmds();
app->recurse_level--;
diff --git a/lib/wx/c_src/gen/wxe_funcs.cpp b/lib/wx/c_src/gen/wxe_funcs.cpp
index 74961b2e5e..32e4bf855b 100644
--- a/lib/wx/c_src/gen/wxe_funcs.cpp
+++ b/lib/wx/c_src/gen/wxe_funcs.cpp
@@ -32113,6 +32113,120 @@ case wxDropFilesEvent_GetFiles: { // wxDropFilesEvent::GetFiles
rt.add(tmpArrayStr);
break;
}
+#if wxUSE_DISPLAY
+case wxDisplay_new: { // wxDisplay::wxDisplay
+ int n=0;
+ while( * (int*) bp) { switch (* (int*) bp) {
+ case 1: {bp += 4;
+ n = (int)*(unsigned int *) bp; bp += 4;
+ } break;
+ }};
+ wxDisplay * Result = new wxDisplay(n);
+ newPtr((void *) Result, 239, memenv);
+ rt.addRef(getRef((void *)Result,memenv), "wxDisplay");
+ break;
+}
+case wxDisplay_destruct: { // wxDisplay::~wxDisplay
+ wxDisplay *This = (wxDisplay *) getPtr(bp,memenv); bp += 4;
+ if(This) { ((WxeApp *) wxTheApp)->clearPtr((void *) This);
+ delete This;}
+ break;
+}
+case wxDisplay_IsOk: { // wxDisplay::IsOk
+ wxDisplay *This = (wxDisplay *) getPtr(bp,memenv); bp += 4;
+ if(!This) throw wxe_badarg(0);
+ bool Result = This->IsOk();
+ rt.addBool(Result);
+ break;
+}
+#if wxCHECK_VERSION(2,8,12)
+case wxDisplay_GetClientArea: { // wxDisplay::GetClientArea
+ wxDisplay *This = (wxDisplay *) getPtr(bp,memenv); bp += 4;
+ if(!This) throw wxe_badarg(0);
+ wxRect Result = This->GetClientArea();
+ rt.add(Result);
+ break;
+}
+#endif
+case wxDisplay_GetGeometry: { // wxDisplay::GetGeometry
+ wxDisplay *This = (wxDisplay *) getPtr(bp,memenv); bp += 4;
+ if(!This) throw wxe_badarg(0);
+ wxRect Result = This->GetGeometry();
+ rt.add(Result);
+ break;
+}
+case wxDisplay_GetName: { // wxDisplay::GetName
+ wxDisplay *This = (wxDisplay *) getPtr(bp,memenv); bp += 4;
+ if(!This) throw wxe_badarg(0);
+ wxString Result = This->GetName();
+ rt.add(Result);
+ break;
+}
+case wxDisplay_IsPrimary: { // wxDisplay::IsPrimary
+ wxDisplay *This = (wxDisplay *) getPtr(bp,memenv); bp += 4;
+ if(!This) throw wxe_badarg(0);
+ bool Result = This->IsPrimary();
+ rt.addBool(Result);
+ break;
+}
+case wxDisplay_GetCount: { // wxDisplay::GetCount
+ int Result = wxDisplay::GetCount();
+ rt.addUint(Result);
+ break;
+}
+case wxDisplay_GetFromPoint: { // wxDisplay::GetFromPoint
+ int * ptX = (int *) bp; bp += 4;
+ int * ptY = (int *) bp; bp += 4;
+ wxPoint pt = wxPoint(*ptX,*ptY);
+ int Result = wxDisplay::GetFromPoint(pt);
+ rt.addInt(Result);
+ break;
+}
+case wxDisplay_GetFromWindow: { // wxDisplay::GetFromWindow
+ wxWindow *window = (wxWindow *) getPtr(bp,memenv); bp += 4;
+ int Result = wxDisplay::GetFromWindow(window);
+ rt.addInt(Result);
+ break;
+}
+#if wxCHECK_VERSION(3,1,2)
+case wxDisplay_GetPPI: { // wxDisplay::GetPPI
+ wxDisplay *This = (wxDisplay *) getPtr(bp,memenv); bp += 4;
+ if(!This) throw wxe_badarg(0);
+ wxSize Result = This->GetPPI();
+ rt.add(Result);
+ break;
+}
+#endif
+#endif // wxUSE_DISPLAY
+#if wxUSE_GRAPHICS_CONTEXT
+case wxGCDC_new_1: { // wxGCDC::wxGCDC
+ wxWindowDC *dc = (wxWindowDC *) getPtr(bp,memenv); bp += 4;
+ wxGCDC * Result = new EwxGCDC(*dc);
+ newPtr((void *) Result, 8, memenv);
+ rt.addRef(getRef((void *)Result,memenv), "wxGCDC");
+ break;
+}
+case wxGCDC_new_0: { // wxGCDC::wxGCDC
+ wxGCDC * Result = new EwxGCDC();
+ newPtr((void *) Result, 8, memenv);
+ rt.addRef(getRef((void *)Result,memenv), "wxGCDC");
+ break;
+}
+case wxGCDC_GetGraphicsContext: { // wxGCDC::GetGraphicsContext
+ wxGCDC *This = (wxGCDC *) getPtr(bp,memenv); bp += 4;
+ if(!This) throw wxe_badarg(0);
+ wxGraphicsContext * Result = (wxGraphicsContext*)This->GetGraphicsContext();
+ rt.addRef(getRef((void *)Result,memenv,8), "wxGraphicsContext");
+ break;
+}
+case wxGCDC_SetGraphicsContext: { // wxGCDC::SetGraphicsContext
+ wxGCDC *This = (wxGCDC *) getPtr(bp,memenv); bp += 4;
+ wxGraphicsContext *ctx = (wxGraphicsContext *) getPtr(bp,memenv); bp += 4;
+ if(!This) throw wxe_badarg(0);
+ This->SetGraphicsContext(ctx);
+ break;
+}
+#endif // wxUSE_GRAPHICS_CONTEXT
default: {
wxeReturn error = wxeReturn(WXE_DRV_PORT, Ecmd.caller, false); error.addAtom("_wxe_error_");
error.addInt((int) op);
@@ -32174,6 +32288,7 @@ bool WxeApp::delete_object(void *ptr, wxeRefData *refd) {
case 231: delete (EwxLocale *) ptr; return false;
case 236: delete (wxOverlay *) ptr; break;
case 237: delete (EwxDCOverlay *) ptr; return false;
+ case 239: delete (wxDisplay *) ptr; break;
default: delete (wxObject *) ptr; return false;
}
return true;
diff --git a/lib/wx/c_src/gen/wxe_init.cpp b/lib/wx/c_src/gen/wxe_init.cpp
index 6ce33a5449..5a52d69003 100644
--- a/lib/wx/c_src/gen/wxe_init.cpp
+++ b/lib/wx/c_src/gen/wxe_init.cpp
@@ -55,6 +55,14 @@ void WxeApp::init_nonconsts(wxeMemEnv *memenv, ErlDrvTermData caller) {
rt.addTupleCount(2);
rt.addAtom("wxFONTENCODING_UTF32"); rt.addInt(wxFONTENCODING_UTF32);
rt.addTupleCount(2);
+ rt.addAtom("wxFONTWEIGHT_BOLD"); rt.addInt(wxFONTWEIGHT_BOLD);
+ rt.addTupleCount(2);
+ rt.addAtom("wxFONTWEIGHT_LIGHT"); rt.addInt(wxFONTWEIGHT_LIGHT);
+ rt.addTupleCount(2);
+ rt.addAtom("wxFONTWEIGHT_MAX"); rt.addInt(wxFONTWEIGHT_MAX);
+ rt.addTupleCount(2);
+ rt.addAtom("wxFONTWEIGHT_NORMAL"); rt.addInt(wxFONTWEIGHT_NORMAL);
+ rt.addTupleCount(2);
rt.addAtom("wxMOD_CMD"); rt.addInt(wxMOD_CMD);
rt.addTupleCount(2);
rt.addAtom("wxLANGUAGE_ABKHAZIAN"); rt.addInt(wxLANGUAGE_ABKHAZIAN);
@@ -654,6 +662,62 @@ void WxeApp::init_nonconsts(wxeMemEnv *memenv, ErlDrvTermData caller) {
rt.addTupleCount(2);
rt.addAtom("wxCYAN_PEN"); rt.addRef(getRef((void *)wxCYAN_PEN,memenv),"wxPen");
rt.addTupleCount(2);
+#if wxCHECK_VERSION(3,1,2)
+ rt.addAtom("wxFONTWEIGHT_EXTRABOLD"); rt.addInt(wxFONTWEIGHT_EXTRABOLD);
+ rt.addTupleCount(2);
+#else
+ rt.addAtom("wxFONTWEIGHT_EXTRABOLD"); rt.addAtom("undefined");
+ rt.addTupleCount(2);
+#endif
+#if wxCHECK_VERSION(3,1,2)
+ rt.addAtom("wxFONTWEIGHT_EXTRAHEAVY"); rt.addInt(wxFONTWEIGHT_EXTRAHEAVY);
+ rt.addTupleCount(2);
+#else
+ rt.addAtom("wxFONTWEIGHT_EXTRAHEAVY"); rt.addAtom("undefined");
+ rt.addTupleCount(2);
+#endif
+#if wxCHECK_VERSION(3,1,2)
+ rt.addAtom("wxFONTWEIGHT_EXTRALIGHT"); rt.addInt(wxFONTWEIGHT_EXTRALIGHT);
+ rt.addTupleCount(2);
+#else
+ rt.addAtom("wxFONTWEIGHT_EXTRALIGHT"); rt.addAtom("undefined");
+ rt.addTupleCount(2);
+#endif
+#if wxCHECK_VERSION(3,1,2)
+ rt.addAtom("wxFONTWEIGHT_HEAVY"); rt.addInt(wxFONTWEIGHT_HEAVY);
+ rt.addTupleCount(2);
+#else
+ rt.addAtom("wxFONTWEIGHT_HEAVY"); rt.addAtom("undefined");
+ rt.addTupleCount(2);
+#endif
+#if wxCHECK_VERSION(3,1,2)
+ rt.addAtom("wxFONTWEIGHT_INVALID"); rt.addInt(wxFONTWEIGHT_INVALID);
+ rt.addTupleCount(2);
+#else
+ rt.addAtom("wxFONTWEIGHT_INVALID"); rt.addAtom("undefined");
+ rt.addTupleCount(2);
+#endif
+#if wxCHECK_VERSION(3,1,2)
+ rt.addAtom("wxFONTWEIGHT_MEDIUM"); rt.addInt(wxFONTWEIGHT_MEDIUM);
+ rt.addTupleCount(2);
+#else
+ rt.addAtom("wxFONTWEIGHT_MEDIUM"); rt.addAtom("undefined");
+ rt.addTupleCount(2);
+#endif
+#if wxCHECK_VERSION(3,1,2)
+ rt.addAtom("wxFONTWEIGHT_SEMIBOLD"); rt.addInt(wxFONTWEIGHT_SEMIBOLD);
+ rt.addTupleCount(2);
+#else
+ rt.addAtom("wxFONTWEIGHT_SEMIBOLD"); rt.addAtom("undefined");
+ rt.addTupleCount(2);
+#endif
+#if wxCHECK_VERSION(3,1,2)
+ rt.addAtom("wxFONTWEIGHT_THIN"); rt.addInt(wxFONTWEIGHT_THIN);
+ rt.addTupleCount(2);
+#else
+ rt.addAtom("wxFONTWEIGHT_THIN"); rt.addAtom("undefined");
+ rt.addTupleCount(2);
+#endif
rt.addAtom("wxGREEN"); rt.add(*(wxGREEN));
rt.addTupleCount(2);
rt.addAtom("wxGREEN_BRUSH"); rt.addRef(getRef((void *)wxGREEN_BRUSH,memenv),"wxBrush");
@@ -723,7 +787,7 @@ void WxeApp::init_nonconsts(wxeMemEnv *memenv, ErlDrvTermData caller) {
rt.addAtom("wx_GL_COMPAT_PROFILE"); rt.addAtom("undefined");
rt.addTupleCount(2);
#endif
- rt.endList(309);
+ rt.endList(321);
rt.addTupleCount(2);
rt.send();
}
diff --git a/lib/wx/c_src/gen/wxe_macros.h b/lib/wx/c_src/gen/wxe_macros.h
index 4c8e52def2..c23e8a83bd 100644
--- a/lib/wx/c_src/gen/wxe_macros.h
+++ b/lib/wx/c_src/gen/wxe_macros.h
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2008-2017. All Rights Reserved.
+ * Copyright Ericsson AB 2008-2018. All 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 <wx/fontdlg.h>
#include <wx/progdlg.h>
#include <wx/printdlg.h>
+#include <wx/display.h>
#include <wx/dcbuffer.h>
#include <wx/dcmirror.h>
#include <wx/glcanvas.h>
@@ -46,6 +47,7 @@
#include <wx/sashwin.h>
#include <wx/laywin.h>
#include <wx/graphics.h>
+#include <wx/dcgraph.h>
#include <wx/aui/aui.h>
#include <wx/datectrl.h>
#include <wx/filepicker.h>
@@ -3426,5 +3428,21 @@
#define wxDropFilesEvent_GetPosition 3597
#define wxDropFilesEvent_GetNumberOfFiles 3598
#define wxDropFilesEvent_GetFiles 3599
+#define wxDisplay_new 3600
+#define wxDisplay_destruct 3601
+#define wxDisplay_IsOk 3602
+#define wxDisplay_GetClientArea 3603
+#define wxDisplay_GetGeometry 3604
+#define wxDisplay_GetName 3605
+#define wxDisplay_IsPrimary 3606
+#define wxDisplay_GetCount 3607
+#define wxDisplay_GetFromPoint 3608
+#define wxDisplay_GetFromWindow 3609
+#define wxDisplay_GetPPI 3610
+#define wxGCDC_new_1 3611
+#define wxGCDC_new_0 3612
+#define wxGCDC_destruct 3613
+#define wxGCDC_GetGraphicsContext 3614
+#define wxGCDC_SetGraphicsContext 3615
diff --git a/lib/wx/c_src/wxe_impl.cpp b/lib/wx/c_src/wxe_impl.cpp
index bd22502d00..43b5476073 100644
--- a/lib/wx/c_src/wxe_impl.cpp
+++ b/lib/wx/c_src/wxe_impl.cpp
@@ -267,7 +267,7 @@ int WxeApp::dispatch_cmds()
return more;
}
-#define BREAK_BATCH 10000
+#define CHECK_EVENTS 10000
int WxeApp::dispatch(wxeFifo * batch)
{
@@ -278,13 +278,14 @@ int WxeApp::dispatch(wxeFifo * batch)
erl_drv_mutex_lock(wxe_batch_locker_m);
while(true) {
while((event = batch->Get()) != NULL) {
+ wait += 1;
erl_drv_mutex_unlock(wxe_batch_locker_m);
switch(event->op) {
case WXE_BATCH_END:
if(blevel>0) {
blevel--;
if(blevel==0)
- wait += BREAK_BATCH/4;
+ wait += CHECK_EVENTS/4;
}
break;
case WXE_BATCH_BEGIN:
@@ -314,21 +315,18 @@ int WxeApp::dispatch(wxeFifo * batch)
break;
}
event->Delete();
+ if(wait > CHECK_EVENTS)
+ return 1; // Let wx check for events
erl_drv_mutex_lock(wxe_batch_locker_m);
batch->Cleanup();
}
- if(blevel <= 0 || wait >= BREAK_BATCH) {
+ if(blevel <= 0) {
erl_drv_mutex_unlock(wxe_batch_locker_m);
- if(blevel > 0) {
- return 1; // We are still in a batch but we can let wx check for events
- } else {
- return 0;
- }
+ return 0;
}
// sleep until something happens
// fprintf(stderr, "%s:%d sleep %d %d %d\r\n", __FILE__, __LINE__, batch->m_n, blevel, wait);fflush(stderr);
wxe_needs_signal = 1;
- wait += 1;
while(batch->m_n == 0) {
erl_drv_cond_wait(wxe_batch_locker_c, wxe_batch_locker_m);
}
diff --git a/lib/wx/c_src/wxe_ps_init.c b/lib/wx/c_src/wxe_ps_init.c
index 4b3b47a80b..62c7c51c13 100644
--- a/lib/wx/c_src/wxe_ps_init.c
+++ b/lib/wx/c_src/wxe_ps_init.c
@@ -64,6 +64,10 @@ void * wxe_ps_init2() {
size_t app_len = 127;
char app_title_buf[128];
char * app_title;
+ size_t app_icon_len = 1023;
+ char app_icon_buf[1024];
+ char * app_icon;
+
// Setup and enable gui
pool = [[NSAutoreleasePool alloc] init];
@@ -78,9 +82,15 @@ void * wxe_ps_init2() {
if(!GetCurrentProcess(&psn)) {
CPSSetProcessName(&psn, app_title?app_title:"Erlang");
}
- // Load and set icon
+ // Enable setting custom application icon for Mac OS X
+ res = erl_drv_getenv("WX_APP_ICON", app_icon_buf, &app_icon_len);
NSMutableString *file = [[NSMutableString alloc] init];
- [file appendFormat:@"%s/%s", erl_wx_privdir, "erlang-logo64.png"];
+ if (res >= 0) {
+ [file appendFormat:@"%s", app_icon_buf];
+ } else {
+ [file appendFormat:@"%s/%s", erl_wx_privdir, "erlang-logo128.png"];
+ }
+ // Load and set icon
NSImage *icon = [[NSImage alloc] initWithContentsOfFile: file];
[NSApp setApplicationIconImage: icon];
};
diff --git a/lib/wx/doc/overview.edoc b/lib/wx/doc/overview.edoc
index 054016f515..843a9c1320 100644
--- a/lib/wx/doc/overview.edoc
+++ b/lib/wx/doc/overview.edoc
@@ -218,7 +218,7 @@ the <em>fun</em> returns.
The callbacks are always invoked by another process and have
exclusive usage of the GUI when invoked. This means that a callback <em>fun</em>
-can not use the process dictionary and should not make calls to other
+cannot use the process dictionary and should not make calls to other
processes. Calls to another process inside a callback <em>fun</em> may cause a
deadlock if the other process is waiting on completion of his call to
the GUI.
diff --git a/lib/wx/examples/demo/ex_graphicsContext.erl b/lib/wx/examples/demo/ex_graphicsContext.erl
index 1193578037..1e6ffce18d 100644
--- a/lib/wx/examples/demo/ex_graphicsContext.erl
+++ b/lib/wx/examples/demo/ex_graphicsContext.erl
@@ -74,7 +74,7 @@ do_init(Config) ->
pen = Pen, brush = Brush, font = Font}}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% Sync events i.e. from callbacks must return ok, it can not return a new state.
+%% Sync events i.e. from callbacks must return ok, it cannot return a new state.
%% Do the redrawing here.
handle_sync_event(#wx{event = #wxPaint{}},_,
#state{win=Win, pen = Pen, brush = Brush, font = Font}) ->
diff --git a/lib/wx/include/wx.hrl b/lib/wx/include/wx.hrl
index 23f3b95403..2c145595ee 100644
--- a/lib/wx/include/wx.hrl
+++ b/lib/wx/include/wx.hrl
@@ -398,6 +398,14 @@
-define(wxCYAN, wxe_util:get_const(wxCYAN)).
-define(wxCYAN_BRUSH, wxe_util:get_const(wxCYAN_BRUSH)).
-define(wxCYAN_PEN, wxe_util:get_const(wxCYAN_PEN)).
+-define(wxFONTWEIGHT_EXTRABOLD, wxe_util:get_const(wxFONTWEIGHT_EXTRABOLD)).
+-define(wxFONTWEIGHT_EXTRAHEAVY, wxe_util:get_const(wxFONTWEIGHT_EXTRAHEAVY)).
+-define(wxFONTWEIGHT_EXTRALIGHT, wxe_util:get_const(wxFONTWEIGHT_EXTRALIGHT)).
+-define(wxFONTWEIGHT_HEAVY, wxe_util:get_const(wxFONTWEIGHT_HEAVY)).
+-define(wxFONTWEIGHT_INVALID, wxe_util:get_const(wxFONTWEIGHT_INVALID)).
+-define(wxFONTWEIGHT_MEDIUM, wxe_util:get_const(wxFONTWEIGHT_MEDIUM)).
+-define(wxFONTWEIGHT_SEMIBOLD, wxe_util:get_const(wxFONTWEIGHT_SEMIBOLD)).
+-define(wxFONTWEIGHT_THIN, wxe_util:get_const(wxFONTWEIGHT_THIN)).
-define(wxGREEN, wxe_util:get_const(wxGREEN)).
-define(wxGREEN_BRUSH, wxe_util:get_const(wxGREEN_BRUSH)).
-define(wxGREEN_PEN, wxe_util:get_const(wxGREEN_PEN)).
@@ -1685,10 +1693,10 @@
-define(wxFONTSTYLE_SLANT, ?wxSLANT).
-define(wxFONTSTYLE_MAX, (?wxSLANT+1)).
% From "font.h": wxFontWeight
--define(wxFONTWEIGHT_NORMAL, ?wxNORMAL).
--define(wxFONTWEIGHT_LIGHT, ?wxLIGHT).
--define(wxFONTWEIGHT_BOLD, ?wxBOLD).
--define(wxFONTWEIGHT_MAX, (?wxBOLD+1)).
+-define(wxFONTWEIGHT_NORMAL, wxe_util:get_const(wxFONTWEIGHT_NORMAL)).
+-define(wxFONTWEIGHT_LIGHT, wxe_util:get_const(wxFONTWEIGHT_LIGHT)).
+-define(wxFONTWEIGHT_BOLD, wxe_util:get_const(wxFONTWEIGHT_BOLD)).
+-define(wxFONTWEIGHT_MAX, wxe_util:get_const(wxFONTWEIGHT_MAX)).
% From "fontenc.h": wxFontEncoding
-define(wxFONTENCODING_SYSTEM, -1).
-define(wxFONTENCODING_DEFAULT, 0).
diff --git a/lib/wx/src/gen/wxDisplay.erl b/lib/wx/src/gen/wxDisplay.erl
new file mode 100644
index 0000000000..b6a2bf22ac
--- /dev/null
+++ b/lib/wx/src/gen/wxDisplay.erl
@@ -0,0 +1,131 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2018. All 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 file is generated DO NOT EDIT
+
+%% @doc See external documentation: <a href="http://www.wxwidgets.org/manuals/2.8.12/wx_wxdisplay.html">wxDisplay</a>.
+%% @type wxDisplay(). An object reference, The representation is internal
+%% and can be changed without notice. It can't be used for comparsion
+%% stored on disc or distributed for use on other nodes.
+
+-module(wxDisplay).
+-include("wxe.hrl").
+-export([destroy/1,getClientArea/1,getCount/0,getFromPoint/1,getFromWindow/1,
+ getGeometry/1,getName/1,getPPI/1,isOk/1,isPrimary/1,new/0,new/1]).
+
+%% inherited exports
+-export([parent_class/1]).
+
+-export_type([wxDisplay/0]).
+%% @hidden
+parent_class(_Class) -> erlang:error({badtype, ?MODULE}).
+
+-type wxDisplay() :: wx:wx_object().
+%% @equiv new([])
+-spec new() -> wxDisplay().
+
+new() ->
+ new([]).
+
+%% @doc See <a href="http://www.wxwidgets.org/manuals/2.8.12/wx_wxdisplay.html#wxdisplaywxdisplay">external documentation</a>.
+-spec new([Option]) -> wxDisplay() when
+ Option :: {'n', integer()}.
+new(Options)
+ when is_list(Options) ->
+ MOpts = fun({n, N}, Acc) -> [<<1:32/?UI,N:32/?UI>>|Acc];
+ (BadOpt, _) -> erlang:error({badoption, BadOpt}) end,
+ BinOpt = list_to_binary(lists:foldl(MOpts, [<<0:32>>], Options)),
+ wxe_util:construct(?wxDisplay_new,
+ <<BinOpt/binary>>).
+
+%% @doc See <a href="http://www.wxwidgets.org/manuals/2.8.12/wx_wxdisplay.html#wxdisplayisok">external documentation</a>.
+-spec isOk(This) -> boolean() when
+ This::wxDisplay().
+isOk(#wx_ref{type=ThisT,ref=ThisRef}) ->
+ ?CLASS(ThisT,wxDisplay),
+ wxe_util:call(?wxDisplay_IsOk,
+ <<ThisRef:32/?UI>>).
+
+%% @doc See <a href="http://www.wxwidgets.org/manuals/2.8.12/wx_wxdisplay.html#wxdisplaygetclientarea">external documentation</a>.
+-spec getClientArea(This) -> {X::integer(), Y::integer(), W::integer(), H::integer()} when
+ This::wxDisplay().
+getClientArea(#wx_ref{type=ThisT,ref=ThisRef}) ->
+ ?CLASS(ThisT,wxDisplay),
+ wxe_util:call(?wxDisplay_GetClientArea,
+ <<ThisRef:32/?UI>>).
+
+%% @doc See <a href="http://www.wxwidgets.org/manuals/2.8.12/wx_wxdisplay.html#wxdisplaygetgeometry">external documentation</a>.
+-spec getGeometry(This) -> {X::integer(), Y::integer(), W::integer(), H::integer()} when
+ This::wxDisplay().
+getGeometry(#wx_ref{type=ThisT,ref=ThisRef}) ->
+ ?CLASS(ThisT,wxDisplay),
+ wxe_util:call(?wxDisplay_GetGeometry,
+ <<ThisRef:32/?UI>>).
+
+%% @doc See <a href="http://www.wxwidgets.org/manuals/2.8.12/wx_wxdisplay.html#wxdisplaygetname">external documentation</a>.
+-spec getName(This) -> unicode:charlist() when
+ This::wxDisplay().
+getName(#wx_ref{type=ThisT,ref=ThisRef}) ->
+ ?CLASS(ThisT,wxDisplay),
+ wxe_util:call(?wxDisplay_GetName,
+ <<ThisRef:32/?UI>>).
+
+%% @doc See <a href="http://www.wxwidgets.org/manuals/2.8.12/wx_wxdisplay.html#wxdisplayisprimary">external documentation</a>.
+-spec isPrimary(This) -> boolean() when
+ This::wxDisplay().
+isPrimary(#wx_ref{type=ThisT,ref=ThisRef}) ->
+ ?CLASS(ThisT,wxDisplay),
+ wxe_util:call(?wxDisplay_IsPrimary,
+ <<ThisRef:32/?UI>>).
+
+%% @doc See <a href="http://www.wxwidgets.org/manuals/2.8.12/wx_wxdisplay.html#wxdisplaygetcount">external documentation</a>.
+-spec getCount() -> integer().
+getCount() ->
+ wxe_util:call(?wxDisplay_GetCount,
+ <<>>).
+
+%% @doc See <a href="http://www.wxwidgets.org/manuals/2.8.12/wx_wxdisplay.html#wxdisplaygetfrompoint">external documentation</a>.
+-spec getFromPoint(Pt) -> integer() when
+ Pt::{X::integer(), Y::integer()}.
+getFromPoint({PtX,PtY})
+ when is_integer(PtX),is_integer(PtY) ->
+ wxe_util:call(?wxDisplay_GetFromPoint,
+ <<PtX:32/?UI,PtY:32/?UI>>).
+
+%% @doc See <a href="http://www.wxwidgets.org/manuals/2.8.12/wx_wxdisplay.html#wxdisplaygetfromwindow">external documentation</a>.
+-spec getFromWindow(Window) -> integer() when
+ Window::wxWindow:wxWindow().
+getFromWindow(#wx_ref{type=WindowT,ref=WindowRef}) ->
+ ?CLASS(WindowT,wxWindow),
+ wxe_util:call(?wxDisplay_GetFromWindow,
+ <<WindowRef:32/?UI>>).
+
+%% @doc See <a href="http://www.wxwidgets.org/manuals/2.8.12/wx_wxdisplay.html#wxdisplaygetppi">external documentation</a>.
+-spec getPPI(This) -> {W::integer(), H::integer()} when
+ This::wxDisplay().
+getPPI(#wx_ref{type=ThisT,ref=ThisRef}) ->
+ ?CLASS(ThisT,wxDisplay),
+ wxe_util:call(?wxDisplay_GetPPI,
+ <<ThisRef:32/?UI>>).
+
+%% @doc Destroys this object, do not use object again
+-spec destroy(This::wxDisplay()) -> 'ok'.
+destroy(Obj=#wx_ref{type=Type}) ->
+ ?CLASS(Type,wxDisplay),
+ wxe_util:destroy(?wxDisplay_destruct,Obj),
+ ok.
diff --git a/lib/wx/src/gen/wxGCDC.erl b/lib/wx/src/gen/wxGCDC.erl
new file mode 100644
index 0000000000..467013b14e
--- /dev/null
+++ b/lib/wx/src/gen/wxGCDC.erl
@@ -0,0 +1,287 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2018. All 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 file is generated DO NOT EDIT
+
+%% @doc See external documentation: <a href="http://www.wxwidgets.org/manuals/2.8.12/wx_wxgcdc.html">wxGCDC</a>.
+%% <p>This class is derived (and can use functions) from:
+%% <br />{@link wxDC}
+%% </p>
+%% @type wxGCDC(). An object reference, The representation is internal
+%% and can be changed without notice. It can't be used for comparsion
+%% stored on disc or distributed for use on other nodes.
+
+-module(wxGCDC).
+-include("wxe.hrl").
+-export([destroy/1,getGraphicsContext/1,new/0,new/1,setGraphicsContext/2]).
+
+%% inherited exports
+-export([blit/5,blit/6,calcBoundingBox/3,clear/1,computeScaleAndOrigin/1,crossHair/2,
+ destroyClippingRegion/1,deviceToLogicalX/2,deviceToLogicalXRel/2,
+ deviceToLogicalY/2,deviceToLogicalYRel/2,drawArc/4,drawBitmap/3,drawBitmap/4,
+ drawCheckMark/2,drawCircle/3,drawEllipse/2,drawEllipse/3,drawEllipticArc/5,
+ drawIcon/3,drawLabel/3,drawLabel/4,drawLine/3,drawLines/2,drawLines/3,
+ drawPoint/2,drawPolygon/2,drawPolygon/3,drawRectangle/2,drawRectangle/3,
+ drawRotatedText/4,drawRoundedRectangle/3,drawRoundedRectangle/4,
+ drawText/3,endDoc/1,endPage/1,floodFill/3,floodFill/4,getBackground/1,
+ getBackgroundMode/1,getBrush/1,getCharHeight/1,getCharWidth/1,getClippingBox/1,
+ getFont/1,getLayoutDirection/1,getLogicalFunction/1,getMapMode/1,
+ getMultiLineTextExtent/2,getMultiLineTextExtent/3,getPPI/1,getPartialTextExtents/2,
+ getPen/1,getPixel/2,getSize/1,getSizeMM/1,getTextBackground/1,getTextExtent/2,
+ getTextExtent/3,getTextForeground/1,getUserScale/1,gradientFillConcentric/4,
+ gradientFillConcentric/5,gradientFillLinear/4,gradientFillLinear/5,
+ isOk/1,logicalToDeviceX/2,logicalToDeviceXRel/2,logicalToDeviceY/2,
+ logicalToDeviceYRel/2,maxX/1,maxY/1,minX/1,minY/1,parent_class/1,resetBoundingBox/1,
+ setAxisOrientation/3,setBackground/2,setBackgroundMode/2,setBrush/2,
+ setClippingRegion/2,setClippingRegion/3,setDeviceOrigin/3,setFont/2,
+ setLayoutDirection/2,setLogicalFunction/2,setMapMode/2,setPalette/2,
+ setPen/2,setTextBackground/2,setTextForeground/2,setUserScale/3,startDoc/2,
+ startPage/1]).
+
+-export_type([wxGCDC/0]).
+-compile([{nowarn_deprecated_function, {wxDC,computeScaleAndOrigin,1}}]).
+
+%% @hidden
+parent_class(wxDC) -> true;
+parent_class(_Class) -> erlang:error({badtype, ?MODULE}).
+
+-type wxGCDC() :: wx:wx_object().
+%% @doc See <a href="http://www.wxwidgets.org/manuals/2.8.12/wx_wxgcdc.html#wxgcdcwxgcdc">external documentation</a>.
+-spec new() -> wxGCDC().
+new() ->
+ wxe_util:construct(?wxGCDC_new_0,
+ <<>>).
+
+%% @doc See <a href="http://www.wxwidgets.org/manuals/2.8.12/wx_wxgcdc.html#wxgcdcwxgcdc">external documentation</a>.
+-spec new(Dc) -> wxGCDC() when
+ Dc::wxWindowDC:wxWindowDC().
+new(#wx_ref{type=DcT,ref=DcRef}) ->
+ ?CLASS(DcT,wxWindowDC),
+ wxe_util:construct(?wxGCDC_new_1,
+ <<DcRef:32/?UI>>).
+
+%% @doc See <a href="http://www.wxwidgets.org/manuals/2.8.12/wx_wxgcdc.html#wxgcdcgetgraphicscontext">external documentation</a>.
+-spec getGraphicsContext(This) -> wxGraphicsContext:wxGraphicsContext() when
+ This::wxGCDC().
+getGraphicsContext(#wx_ref{type=ThisT,ref=ThisRef}) ->
+ ?CLASS(ThisT,wxGCDC),
+ wxe_util:call(?wxGCDC_GetGraphicsContext,
+ <<ThisRef:32/?UI>>).
+
+%% @doc See <a href="http://www.wxwidgets.org/manuals/2.8.12/wx_wxgcdc.html#wxgcdcsetgraphicscontext">external documentation</a>.
+-spec setGraphicsContext(This, Ctx) -> 'ok' when
+ This::wxGCDC(), Ctx::wxGraphicsContext:wxGraphicsContext().
+setGraphicsContext(#wx_ref{type=ThisT,ref=ThisRef},#wx_ref{type=CtxT,ref=CtxRef}) ->
+ ?CLASS(ThisT,wxGCDC),
+ ?CLASS(CtxT,wxGraphicsContext),
+ wxe_util:cast(?wxGCDC_SetGraphicsContext,
+ <<ThisRef:32/?UI,CtxRef:32/?UI>>).
+
+%% @doc Destroys this object, do not use object again
+-spec destroy(This::wxGCDC()) -> 'ok'.
+destroy(Obj=#wx_ref{type=Type}) ->
+ ?CLASS(Type,wxGCDC),
+ wxe_util:destroy(?DESTROY_OBJECT,Obj),
+ ok.
+ %% From wxDC
+%% @hidden
+startPage(This) -> wxDC:startPage(This).
+%% @hidden
+startDoc(This,Message) -> wxDC:startDoc(This,Message).
+%% @hidden
+setUserScale(This,X,Y) -> wxDC:setUserScale(This,X,Y).
+%% @hidden
+setTextForeground(This,Colour) -> wxDC:setTextForeground(This,Colour).
+%% @hidden
+setTextBackground(This,Colour) -> wxDC:setTextBackground(This,Colour).
+%% @hidden
+setPen(This,Pen) -> wxDC:setPen(This,Pen).
+%% @hidden
+setPalette(This,Palette) -> wxDC:setPalette(This,Palette).
+%% @hidden
+setMapMode(This,Mode) -> wxDC:setMapMode(This,Mode).
+%% @hidden
+setLogicalFunction(This,Function) -> wxDC:setLogicalFunction(This,Function).
+%% @hidden
+setLayoutDirection(This,Dir) -> wxDC:setLayoutDirection(This,Dir).
+%% @hidden
+setFont(This,Font) -> wxDC:setFont(This,Font).
+%% @hidden
+setDeviceOrigin(This,X,Y) -> wxDC:setDeviceOrigin(This,X,Y).
+%% @hidden
+setClippingRegion(This,Pt,Sz) -> wxDC:setClippingRegion(This,Pt,Sz).
+%% @hidden
+setClippingRegion(This,Region) -> wxDC:setClippingRegion(This,Region).
+%% @hidden
+setBrush(This,Brush) -> wxDC:setBrush(This,Brush).
+%% @hidden
+setBackgroundMode(This,Mode) -> wxDC:setBackgroundMode(This,Mode).
+%% @hidden
+setBackground(This,Brush) -> wxDC:setBackground(This,Brush).
+%% @hidden
+setAxisOrientation(This,XLeftRight,YBottomUp) -> wxDC:setAxisOrientation(This,XLeftRight,YBottomUp).
+%% @hidden
+resetBoundingBox(This) -> wxDC:resetBoundingBox(This).
+%% @hidden
+isOk(This) -> wxDC:isOk(This).
+%% @hidden
+minY(This) -> wxDC:minY(This).
+%% @hidden
+minX(This) -> wxDC:minX(This).
+%% @hidden
+maxY(This) -> wxDC:maxY(This).
+%% @hidden
+maxX(This) -> wxDC:maxX(This).
+%% @hidden
+logicalToDeviceYRel(This,Y) -> wxDC:logicalToDeviceYRel(This,Y).
+%% @hidden
+logicalToDeviceY(This,Y) -> wxDC:logicalToDeviceY(This,Y).
+%% @hidden
+logicalToDeviceXRel(This,X) -> wxDC:logicalToDeviceXRel(This,X).
+%% @hidden
+logicalToDeviceX(This,X) -> wxDC:logicalToDeviceX(This,X).
+%% @hidden
+gradientFillLinear(This,Rect,InitialColour,DestColour, Options) -> wxDC:gradientFillLinear(This,Rect,InitialColour,DestColour, Options).
+%% @hidden
+gradientFillLinear(This,Rect,InitialColour,DestColour) -> wxDC:gradientFillLinear(This,Rect,InitialColour,DestColour).
+%% @hidden
+gradientFillConcentric(This,Rect,InitialColour,DestColour,CircleCenter) -> wxDC:gradientFillConcentric(This,Rect,InitialColour,DestColour,CircleCenter).
+%% @hidden
+gradientFillConcentric(This,Rect,InitialColour,DestColour) -> wxDC:gradientFillConcentric(This,Rect,InitialColour,DestColour).
+%% @hidden
+getUserScale(This) -> wxDC:getUserScale(This).
+%% @hidden
+getTextForeground(This) -> wxDC:getTextForeground(This).
+%% @hidden
+getTextExtent(This,String, Options) -> wxDC:getTextExtent(This,String, Options).
+%% @hidden
+getTextExtent(This,String) -> wxDC:getTextExtent(This,String).
+%% @hidden
+getTextBackground(This) -> wxDC:getTextBackground(This).
+%% @hidden
+getSizeMM(This) -> wxDC:getSizeMM(This).
+%% @hidden
+getSize(This) -> wxDC:getSize(This).
+%% @hidden
+getPPI(This) -> wxDC:getPPI(This).
+%% @hidden
+getPixel(This,Pt) -> wxDC:getPixel(This,Pt).
+%% @hidden
+getPen(This) -> wxDC:getPen(This).
+%% @hidden
+getPartialTextExtents(This,Text) -> wxDC:getPartialTextExtents(This,Text).
+%% @hidden
+getMultiLineTextExtent(This,String, Options) -> wxDC:getMultiLineTextExtent(This,String, Options).
+%% @hidden
+getMultiLineTextExtent(This,String) -> wxDC:getMultiLineTextExtent(This,String).
+%% @hidden
+getMapMode(This) -> wxDC:getMapMode(This).
+%% @hidden
+getLogicalFunction(This) -> wxDC:getLogicalFunction(This).
+%% @hidden
+getLayoutDirection(This) -> wxDC:getLayoutDirection(This).
+%% @hidden
+getFont(This) -> wxDC:getFont(This).
+%% @hidden
+getClippingBox(This) -> wxDC:getClippingBox(This).
+%% @hidden
+getCharWidth(This) -> wxDC:getCharWidth(This).
+%% @hidden
+getCharHeight(This) -> wxDC:getCharHeight(This).
+%% @hidden
+getBrush(This) -> wxDC:getBrush(This).
+%% @hidden
+getBackgroundMode(This) -> wxDC:getBackgroundMode(This).
+%% @hidden
+getBackground(This) -> wxDC:getBackground(This).
+%% @hidden
+floodFill(This,Pt,Col, Options) -> wxDC:floodFill(This,Pt,Col, Options).
+%% @hidden
+floodFill(This,Pt,Col) -> wxDC:floodFill(This,Pt,Col).
+%% @hidden
+endPage(This) -> wxDC:endPage(This).
+%% @hidden
+endDoc(This) -> wxDC:endDoc(This).
+%% @hidden
+drawText(This,Text,Pt) -> wxDC:drawText(This,Text,Pt).
+%% @hidden
+drawRoundedRectangle(This,Pt,Sz,Radius) -> wxDC:drawRoundedRectangle(This,Pt,Sz,Radius).
+%% @hidden
+drawRoundedRectangle(This,R,Radius) -> wxDC:drawRoundedRectangle(This,R,Radius).
+%% @hidden
+drawRotatedText(This,Text,Pt,Angle) -> wxDC:drawRotatedText(This,Text,Pt,Angle).
+%% @hidden
+drawRectangle(This,Pt,Sz) -> wxDC:drawRectangle(This,Pt,Sz).
+%% @hidden
+drawRectangle(This,Rect) -> wxDC:drawRectangle(This,Rect).
+%% @hidden
+drawPoint(This,Pt) -> wxDC:drawPoint(This,Pt).
+%% @hidden
+drawPolygon(This,Points, Options) -> wxDC:drawPolygon(This,Points, Options).
+%% @hidden
+drawPolygon(This,Points) -> wxDC:drawPolygon(This,Points).
+%% @hidden
+drawLines(This,Points, Options) -> wxDC:drawLines(This,Points, Options).
+%% @hidden
+drawLines(This,Points) -> wxDC:drawLines(This,Points).
+%% @hidden
+drawLine(This,Pt1,Pt2) -> wxDC:drawLine(This,Pt1,Pt2).
+%% @hidden
+drawLabel(This,Text,Rect, Options) -> wxDC:drawLabel(This,Text,Rect, Options).
+%% @hidden
+drawLabel(This,Text,Rect) -> wxDC:drawLabel(This,Text,Rect).
+%% @hidden
+drawIcon(This,Icon,Pt) -> wxDC:drawIcon(This,Icon,Pt).
+%% @hidden
+drawEllipticArc(This,Pt,Sz,Sa,Ea) -> wxDC:drawEllipticArc(This,Pt,Sz,Sa,Ea).
+%% @hidden
+drawEllipse(This,Pt,Sz) -> wxDC:drawEllipse(This,Pt,Sz).
+%% @hidden
+drawEllipse(This,Rect) -> wxDC:drawEllipse(This,Rect).
+%% @hidden
+drawCircle(This,Pt,Radius) -> wxDC:drawCircle(This,Pt,Radius).
+%% @hidden
+drawCheckMark(This,Rect) -> wxDC:drawCheckMark(This,Rect).
+%% @hidden
+drawBitmap(This,Bmp,Pt, Options) -> wxDC:drawBitmap(This,Bmp,Pt, Options).
+%% @hidden
+drawBitmap(This,Bmp,Pt) -> wxDC:drawBitmap(This,Bmp,Pt).
+%% @hidden
+drawArc(This,Pt1,Pt2,Centre) -> wxDC:drawArc(This,Pt1,Pt2,Centre).
+%% @hidden
+deviceToLogicalYRel(This,Y) -> wxDC:deviceToLogicalYRel(This,Y).
+%% @hidden
+deviceToLogicalY(This,Y) -> wxDC:deviceToLogicalY(This,Y).
+%% @hidden
+deviceToLogicalXRel(This,X) -> wxDC:deviceToLogicalXRel(This,X).
+%% @hidden
+deviceToLogicalX(This,X) -> wxDC:deviceToLogicalX(This,X).
+%% @hidden
+destroyClippingRegion(This) -> wxDC:destroyClippingRegion(This).
+%% @hidden
+crossHair(This,Pt) -> wxDC:crossHair(This,Pt).
+%% @hidden
+computeScaleAndOrigin(This) -> wxDC:computeScaleAndOrigin(This).
+%% @hidden
+clear(This) -> wxDC:clear(This).
+%% @hidden
+calcBoundingBox(This,X,Y) -> wxDC:calcBoundingBox(This,X,Y).
+%% @hidden
+blit(This,DestPt,Sz,Source,SrcPt, Options) -> wxDC:blit(This,DestPt,Sz,Source,SrcPt, Options).
+%% @hidden
+blit(This,DestPt,Sz,Source,SrcPt) -> wxDC:blit(This,DestPt,Sz,Source,SrcPt).
diff --git a/lib/wx/src/gen/wxe_debug.hrl b/lib/wx/src/gen/wxe_debug.hrl
index 533f9f2df0..b64a1b4c61 100644
--- a/lib/wx/src/gen/wxe_debug.hrl
+++ b/lib/wx/src/gen/wxe_debug.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2018. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -3377,6 +3377,22 @@ wxdebug_table() ->
{3597, {wxDropFilesEvent, getPosition, 0}},
{3598, {wxDropFilesEvent, getNumberOfFiles, 0}},
{3599, {wxDropFilesEvent, getFiles, 0}},
+ {3600, {wxDisplay, new, 1}},
+ {3601, {wxDisplay, destruct, 0}},
+ {3602, {wxDisplay, isOk, 0}},
+ {3603, {wxDisplay, getClientArea, 0}},
+ {3604, {wxDisplay, getGeometry, 0}},
+ {3605, {wxDisplay, getName, 0}},
+ {3606, {wxDisplay, isPrimary, 0}},
+ {3607, {wxDisplay, getCount, 0}},
+ {3608, {wxDisplay, getFromPoint, 1}},
+ {3609, {wxDisplay, getFromWindow, 1}},
+ {3610, {wxDisplay, getPPI, 0}},
+ {3611, {wxGCDC, new_1, 1}},
+ {3612, {wxGCDC, new_0, 0}},
+ {3613, {wxGCDC, destruct, 0}},
+ {3614, {wxGCDC, getGraphicsContext, 0}},
+ {3615, {wxGCDC, setGraphicsContext, 1}},
{-1, {mod, func, -1}}
].
diff --git a/lib/wx/src/gen/wxe_funcs.hrl b/lib/wx/src/gen/wxe_funcs.hrl
index 14b5545676..030f7f117d 100644
--- a/lib/wx/src/gen/wxe_funcs.hrl
+++ b/lib/wx/src/gen/wxe_funcs.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2018. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -3374,3 +3374,19 @@
-define(wxDropFilesEvent_GetPosition, 3597).
-define(wxDropFilesEvent_GetNumberOfFiles, 3598).
-define(wxDropFilesEvent_GetFiles, 3599).
+-define(wxDisplay_new, 3600).
+-define(wxDisplay_destruct, 3601).
+-define(wxDisplay_IsOk, 3602).
+-define(wxDisplay_GetClientArea, 3603).
+-define(wxDisplay_GetGeometry, 3604).
+-define(wxDisplay_GetName, 3605).
+-define(wxDisplay_IsPrimary, 3606).
+-define(wxDisplay_GetCount, 3607).
+-define(wxDisplay_GetFromPoint, 3608).
+-define(wxDisplay_GetFromWindow, 3609).
+-define(wxDisplay_GetPPI, 3610).
+-define(wxGCDC_new_1, 3611).
+-define(wxGCDC_new_0, 3612).
+-define(wxGCDC_destruct, 3613).
+-define(wxGCDC_GetGraphicsContext, 3614).
+-define(wxGCDC_SetGraphicsContext, 3615).
diff --git a/lib/wx/test/wx_event_SUITE.erl b/lib/wx/test/wx_event_SUITE.erl
index 7ecd9b7283..1cc194d569 100644
--- a/lib/wx/test/wx_event_SUITE.erl
+++ b/lib/wx/test/wx_event_SUITE.erl
@@ -347,9 +347,9 @@ connect_in_callback(Config) ->
%% such that new events are not fired until the previous
%% callback have returned.
- %% That means that a callback can not wait for other events
+ %% That means that a callback cannot wait for other events
%% in receive since they will not come.
- %% It also means that you can not attach a new callback directly from
+ %% It also means that you cannot attach a new callback directly from
%% the callback since that callback will be removed when the temporary
%% process that executes the outer callback (may) die(s) before the callback
%% is invoked
diff --git a/lib/xmerl/doc/src/notes.xml b/lib/xmerl/doc/src/notes.xml
index f91e5f5047..7f6874e36b 100644
--- a/lib/xmerl/doc/src/notes.xml
+++ b/lib/xmerl/doc/src/notes.xml
@@ -77,6 +77,21 @@
</section>
+<section><title>Xmerl 1.3.16.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>The charset detection parsing crash in some cases when
+ the XML directive is not syntactic correct.</p>
+ <p>
+ Own Id: OTP-15492 Aux Id: ERIERL-283 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Xmerl 1.3.16</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -1427,4 +1442,3 @@
</section>
</section>
</chapter>
-
diff --git a/lib/xmerl/doc/src/xmerl_sax_parser.xml b/lib/xmerl/doc/src/xmerl_sax_parser.xml
index 8ea197e209..2390779028 100644
--- a/lib/xmerl/doc/src/xmerl_sax_parser.xml
+++ b/lib/xmerl/doc/src/xmerl_sax_parser.xml
@@ -31,7 +31,7 @@
<rev></rev>
</header>
- <module>xmerl_sax_parser</module>
+ <module since="">xmerl_sax_parser</module>
<modulesummary>XML SAX parser API</modulesummary>
<description>
@@ -325,7 +325,7 @@
<funcs>
<func>
- <name>file(Filename, Options) -> Result</name>
+ <name since="">file(Filename, Options) -> Result</name>
<fsummary>Parse file containing an XML document.</fsummary>
<type>
<v>Filename = string()</v>
@@ -347,7 +347,7 @@
</func>
<func>
- <name>stream(Xml, Options) -> Result</name>
+ <name since="">stream(Xml, Options) -> Result</name>
<fsummary>Parse a stream containing an XML document.</fsummary>
<type>
<v>Xml = unicode_binary() | latin1_binary() | [unicode_char()]</v>
@@ -381,7 +381,7 @@
<funcs>
<func>
- <name>ContinuationFun(State) -> {NewBytes, NewState}</name>
+ <name since="">ContinuationFun(State) -> {NewBytes, NewState}</name>
<fsummary>Continuation call back function.</fsummary>
<type>
<v>State = NewState = term()</v>
@@ -402,7 +402,7 @@
</func>
<func>
- <name>EventFun(Event, Location, State) -> NewState</name>
+ <name since="">EventFun(Event, Location, State) -> NewState</name>
<fsummary>Event call back function.</fsummary>
<type>
<v>Event = event()</v>
diff --git a/lib/xmerl/src/xmerl_sax_parser_base.erlsrc b/lib/xmerl/src/xmerl_sax_parser_base.erlsrc
index 1dca9608cb..ef753c7148 100644
--- a/lib/xmerl/src/xmerl_sax_parser_base.erlsrc
+++ b/lib/xmerl/src/xmerl_sax_parser_base.erlsrc
@@ -3679,7 +3679,7 @@ create_tempfile(Template) ->
false ->
case os:getenv("TEMP") of
false ->
- throw({error, "Variabel TMP or TEMP doesn't exist"});
+ throw({error, "Variable TMP or TEMP doesn't exist"});
P2 ->
P2
end;
diff --git a/lib/xmerl/test/xmerl_SUITE_data/eventp/CMOM.xml b/lib/xmerl/test/xmerl_SUITE_data/eventp/CMOM.xml
index c2533248d1..0379c18214 100644
--- a/lib/xmerl/test/xmerl_SUITE_data/eventp/CMOM.xml
+++ b/lib/xmerl/test/xmerl_SUITE_data/eventp/CMOM.xml
@@ -276,7 +276,7 @@
</exception>
<exception name="UpgradeNotPossibleException">
- <description>Before an upgrade is started it was found that the upgrade can not take place. A possible reason is that the upgrade package that is running in the node is not in the upgrade window of this upgrade package. </description>
+ <description>Before an upgrade is started it was found that the upgrade cannot take place. A possible reason is that the upgrade package that is running in the node is not in the upgrade window of this upgrade package. </description>
<exceptionParameter name="message">
<dataType>
<string/>
@@ -1703,7 +1703,7 @@ Example:
<exception name="NoSuchAttributeException">
<description>Exception thrown when an MO attribute is requested to be accessed but the access
-method for the is not defined (the attribute can not be accessed)</description>
+method for the is not defined (the attribute cannot be accessed)</description>
</exception>
<exception name="MoCardinalityViolationException">
@@ -3125,7 +3125,7 @@ active = Synchronization is used in system clock generation.
<description>RefState can have the following values:
failed = synchronization reference is not capable to perform its required tasks.
degraded = capability of synchronization reference to perform its required tasks is degraded e.g. because of signal level degradation. This value is only applicable for traffic carrying (ET physical path termination) synchronization references. Note: attribute degradationIsFault controls whether synchronization reference degradation is interpreted as a synchronization reference fault or not.
-lossOfTracking = system clock regulation algorithm on TU board can not follow the 8kHz synchronization reference signal either because of the poor quality of the signal or because of a HW fault at TU board. If all synchronization references repeatedly end up to state lossOfTracking, fault is likely in TU HW.
+lossOfTracking = system clock regulation algorithm on TU board cannot follow the 8kHz synchronization reference signal either because of the poor quality of the signal or because of a HW fault at TU board. If all synchronization references repeatedly end up to state lossOfTracking, fault is likely in TU HW.
ok = synchronization reference is capable of performing its required tasks.
</description>
<enumMember name="failed">
@@ -11270,7 +11270,7 @@ Note! This action requires a transaction.</description>
The changing of the IP address with operation assignIpAddress might cause interruption of the communication if the network management tool is connected via the ethernet link.
-Note: The EthernetLink MO can not be deleted!
+Note: The EthernetLink MO cannot be deleted!
Note: The performance monitoring counters in the EthernetLink MO has a "Wrap-around time" of approximately 2 hours.
</description>
@@ -11532,8 +11532,8 @@ Note! This action requires a transaction.</description>
<description>This MO holds the IP routing table.
The IpRoutingTable MO is automatically created when the Ip MO is created.
-The IpRoutingTable MO can not be created manually.
-The IpRoutingTable MO can not be deleted.
+The IpRoutingTable MO cannot be created manually.
+The IpRoutingTable MO cannot be deleted.
</description>
<systemCreated/>
<attribute name="userLabel">
@@ -12967,7 +12967,7 @@ Note! When using fractional atm, timeslot 1 must be a part of the fraction.
Note! ETM1 does not support EPD and PPD
-Note! ETM1 does not have a proper buffer management. Thus fairness of UBR+ traffic can not be guaranteed and shaping on UBR+ traffic is not possible.
+Note! ETM1 does not have a proper buffer management. Thus fairness of UBR+ traffic cannot be guaranteed and shaping on UBR+ traffic is not possible.
Note! The number of VCC TP+VPC TP with performance monitoring enabled (i.e. PM mode &lt;&gt; off) is restricted to 1 per port.
</description>
@@ -13371,7 +13371,7 @@ Struct element description :
-Date is in string format, max length is 40. Format for date is: weekday month date hour:min:seconds year.
-Status is in string format, max length is 40.
-Note! The identity can not be the same as name. Identity should contain the product identity.
+Note! The identity cannot be the same as name. Identity should contain the product identity.
</description>
@@ -13562,7 +13562,7 @@ Note! For comment and operatorName spaces (' ') are also allowed within the stri
This action does not require a transaction.
-Note! The configurationVersionName and identity can not be the same. The identity should be the product identity.
+Note! The configurationVersionName and identity cannot be the same. The identity should be the product identity.
</description>
<returnType>
<void/>
@@ -17367,7 +17367,7 @@ NOTE: There is a restriction of a maximum of 32 Mtp2Tp's per MP.</description>
<attribute name="mtp2ProfileItuId">
<description>Reference to a Mtp2ProfileItu MO.
-Note: The bitRate can not be changed.</description>
+Note: The bitRate cannot be changed.</description>
<mandatory/>
<noNotification/>
<dataType>
@@ -17566,7 +17566,7 @@ NOTE: There is a restriction of a maximum of 32 Mtp2Tp's per MP.</description>
<attribute name="mtp2ProfileAnsiId">
<description>Reference to a Mtp2ProfileAnsi MO.
-Note: The bitRate can not be changed.</description>
+Note: The bitRate cannot be changed.</description>
<mandatory/>
<noNotification/>
<dataType>
@@ -21777,7 +21777,7 @@ Each E1/DS1/J1 channel can suport up to 2 VP connections.
The ET-MC41 board support one biderectional F4/F5 PM flow per E1 channel.
-Note! The ETMC41 supports IMA. However the E1 ports being part of the same IMA group can not be selected randomly.
+Note! The ETMC41 supports IMA. However the E1 ports being part of the same IMA group cannot be selected randomly.
Note! The number of VCC TP+VPC TP with performance monitoring enabled (i.e. PM mode &lt;&gt; off) is restricted to 1 / port
@@ -21883,7 +21883,7 @@ NOTE: There is a restriction of a maximum of 32 Mtp2Tp's per MP.
<attribute name="mtp2ProfileChinaId">
<description>Reference to a Mtp2ProfileChina MO.
-Note: The bitRate can not be changed.</description>
+Note: The bitRate cannot be changed.</description>
<mandatory/>
<noNotification/>
<dataType>
diff --git a/lib/xmerl/test/xmerl_SUITE_data/eventp/CelloMOM.xml b/lib/xmerl/test/xmerl_SUITE_data/eventp/CelloMOM.xml
index 3b5d8ae2ad..3b9ccac0f4 100644
--- a/lib/xmerl/test/xmerl_SUITE_data/eventp/CelloMOM.xml
+++ b/lib/xmerl/test/xmerl_SUITE_data/eventp/CelloMOM.xml
@@ -276,7 +276,7 @@
</exception>
<exception name="UpgradeNotPossibleException">
- <description>Before an upgrade is started it was found that the upgrade can not take place. A possible reason is that the upgrade package that is running in the node is not in the upgrade window of this upgrade package. </description>
+ <description>Before an upgrade is started it was found that the upgrade cannot take place. A possible reason is that the upgrade package that is running in the node is not in the upgrade window of this upgrade package. </description>
<exceptionParameter name="message">
<dataType>
<string/>
@@ -1703,7 +1703,7 @@ Example:
<exception name="NoSuchAttributeException">
<description>Exception thrown when an MO attribute is requested to be accessed but the access
-method for the is not defined (the attribute can not be accessed)</description>
+method for the is not defined (the attribute cannot be accessed)</description>
</exception>
<exception name="MoCardinalityViolationException">
@@ -3125,7 +3125,7 @@ active = Synchronization is used in system clock generation.
<description>RefState can have the following values:
failed = synchronization reference is not capable to perform its required tasks.
degraded = capability of synchronization reference to perform its required tasks is degraded e.g. because of signal level degradation. This value is only applicable for traffic carrying (ET physical path termination) synchronization references. Note: attribute degradationIsFault controls whether synchronization reference degradation is interpreted as a synchronization reference fault or not.
-lossOfTracking = system clock regulation algorithm on TU board can not follow the 8kHz synchronization reference signal either because of the poor quality of the signal or because of a HW fault at TU board. If all synchronization references repeatedly end up to state lossOfTracking, fault is likely in TU HW.
+lossOfTracking = system clock regulation algorithm on TU board cannot follow the 8kHz synchronization reference signal either because of the poor quality of the signal or because of a HW fault at TU board. If all synchronization references repeatedly end up to state lossOfTracking, fault is likely in TU HW.
ok = synchronization reference is capable of performing its required tasks.
</description>
<enumMember name="failed">
@@ -11270,7 +11270,7 @@ Note! This action requires a transaction.</description>
The changing of the IP address with operation assignIpAddress might cause interruption of the communication if the network management tool is connected via the ethernet link.
-Note: The EthernetLink MO can not be deleted!
+Note: The EthernetLink MO cannot be deleted!
Note: The performance monitoring counters in the EthernetLink MO has a "Wrap-around time" of approximately 2 hours.
</description>
@@ -11532,8 +11532,8 @@ Note! This action requires a transaction.</description>
<description>This MO holds the IP routing table.
The IpRoutingTable MO is automatically created when the Ip MO is created.
-The IpRoutingTable MO can not be created manually.
-The IpRoutingTable MO can not be deleted.
+The IpRoutingTable MO cannot be created manually.
+The IpRoutingTable MO cannot be deleted.
</description>
<systemCreated/>
<attribute name="userLabel">
@@ -12967,7 +12967,7 @@ Note! When using fractional atm, timeslot 1 must be a part of the fraction.
Note! ETM1 does not support EPD and PPD
-Note! ETM1 does not have a proper buffer management. Thus fairness of UBR+ traffic can not be guaranteed and shaping on UBR+ traffic is not possible.
+Note! ETM1 does not have a proper buffer management. Thus fairness of UBR+ traffic cannot be guaranteed and shaping on UBR+ traffic is not possible.
Note! The number of VCC TP+VPC TP with performance monitoring enabled (i.e. PM mode &lt;&gt; off) is restricted to 1 per port.
</description>
@@ -13371,7 +13371,7 @@ Struct element description :
-Date is in string format, max length is 40. Format for date is: weekday month date hour:min:seconds year.
-Status is in string format, max length is 40.
-Note! The identity can not be the same as name. Identity should contain the product identity.
+Note! The identity cannot be the same as name. Identity should contain the product identity.
</description>
@@ -13562,7 +13562,7 @@ Note! For comment and operatorName spaces (' ') are also allowed within the stri
This action does not require a transaction.
-Note! The configurationVersionName and identity can not be the same. The identity should be the product identity.
+Note! The configurationVersionName and identity cannot be the same. The identity should be the product identity.
</description>
<returnType>
<void/>
@@ -17367,7 +17367,7 @@ NOTE: There is a restriction of a maximum of 32 Mtp2Tp's per MP.</description>
<attribute name="mtp2ProfileItuId">
<description>Reference to a Mtp2ProfileItu MO.
-Note: The bitRate can not be changed.</description>
+Note: The bitRate cannot be changed.</description>
<mandatory/>
<noNotification/>
<dataType>
@@ -17566,7 +17566,7 @@ NOTE: There is a restriction of a maximum of 32 Mtp2Tp's per MP.</description>
<attribute name="mtp2ProfileAnsiId">
<description>Reference to a Mtp2ProfileAnsi MO.
-Note: The bitRate can not be changed.</description>
+Note: The bitRate cannot be changed.</description>
<mandatory/>
<noNotification/>
<dataType>
@@ -21777,7 +21777,7 @@ Each E1/DS1/J1 channel can suport up to 2 VP connections.
The ET-MC41 board support one biderectional F4/F5 PM flow per E1 channel.
-Note! The ETMC41 supports IMA. However the E1 ports being part of the same IMA group can not be selected randomly.
+Note! The ETMC41 supports IMA. However the E1 ports being part of the same IMA group cannot be selected randomly.
Note! The number of VCC TP+VPC TP with performance monitoring enabled (i.e. PM mode &lt;&gt; off) is restricted to 1 / port
@@ -21883,7 +21883,7 @@ NOTE: There is a restriction of a maximum of 32 Mtp2Tp's per MP.
<attribute name="mtp2ProfileChinaId">
<description>Reference to a Mtp2ProfileChina MO.
-Note: The bitRate can not be changed.</description>
+Note: The bitRate cannot be changed.</description>
<mandatory/>
<noNotification/>
<dataType>
diff --git a/lib/xmerl/test/xmerl_xsd_lib.erl b/lib/xmerl/test/xmerl_xsd_lib.erl
index 6006cf500f..39300cd6cb 100644
--- a/lib/xmerl/test/xmerl_xsd_lib.erl
+++ b/lib/xmerl/test/xmerl_xsd_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -94,8 +94,9 @@ return_results2({NewFail, NewSuccess, NewMal, NewNotMal}, NumSucc, SkippedN, Tot
_ -> io_lib:format("These ~p skipped tests were malicious, but succeeds now: ~p~n",
[length(NewNotMal), NewNotMal])
end,
- ct:comment(io_lib:format("~p successful tests, ~p skipped tests of totally ~p test cases. ~n" ++
- NFComm ++ NSComm ++ NMComm ++ NNMComm, [NumSucc, SkippedN, TotN])),
+ ct:comment(io_lib:format("~p successful tests, ~p skipped tests of totally ~p test cases. ~n~ts",
+ [NumSucc, SkippedN, TotN,
+ NFComm ++ NSComm ++ NMComm ++ NNMComm])),
[] = NewFail.
%% return_results2(Diff,{STErrs,STOther},{ITErrs,ITOther},TotN) ->
diff --git a/configure.in b/make/configure.in
index 2a42477723..bf3fd0751f 100644
--- a/configure.in
+++ b/make/configure.in
@@ -44,7 +44,7 @@ case "X$ERL_TOP" in
X)
;;
X/*)
- test -f $ERL_TOP/erts/emulator/beam/beam_emu.c || {
+ test -f "$ERL_TOP/erts/emulator/beam/beam_emu.c" || {
AC_MSG_ERROR([Invalid \$ERL_TOP])
}
srcdir="$ERL_TOP";;
@@ -93,6 +93,8 @@ dnl
if test "X$host" != "Xfree_source" -a "X$host" != "Xwin32"; then
AC_CANONICAL_HOST
+else
+ host_os=$host
fi
TARGET=$host
@@ -128,6 +130,8 @@ AC_PROG_CC
AC_PROG_CXX
AC_CHECK_TOOL(LD, [ld])
+LM_WINDOWS_ENVIRONMENT
+
_search_path=/bin:/usr/bin:/usr/local/bin:$PATH
AC_PATH_PROG(ENV, [env], false, $_search_path)
@@ -211,6 +215,9 @@ AC_MSG_CHECKING([OTP version])
AC_MSG_RESULT([$OTP_VSN])
AC_SUBST(OTP_VSN)
+AC_ARG_ENABLE(parallel-configure,
+AS_HELP_STRING([--disable-parallel-configure], [disable parallel execution of configure scripts]))
+
AC_ARG_ENABLE(dirty-schedulers,
AS_HELP_STRING([--enable-dirty-schedulers], [enable dirty scheduler support]))
@@ -416,77 +423,9 @@ if test $CROSS_COMPILING = no; then
esac
fi
-rm -f $ERL_TOP/lib/SKIP-APPLICATIONS
-for app in `cd lib && ls -d *`; do
- var=`eval echo \\$with_$app`
- if test X${var} = Xno; then
- echo "$app" >> $ERL_TOP/lib/SKIP-APPLICATIONS
- fi
-done
-
-export ERL_TOP
-AC_CONFIG_SUBDIRS(lib erts)
+ERL_DED
-AC_CONFIG_FILES([Makefile make/output.mk])
-AC_CONFIG_FILES([make/emd2exml], [chmod +x make/emd2exml])
+AC_CONFIG_FILES([../Makefile output.mk ../make/$host/otp_ded.mk:../make/otp_ded.mk.in])
+AC_CONFIG_FILES([emd2exml], [chmod +x emd2exml])
AC_OUTPUT
-
-pattern="lib/*/SKIP"
-files=`echo $pattern`
-if test "$files" != "$pattern" || test -f $ERL_TOP/lib/SKIP-APPLICATIONS; then
- echo '*********************************************************************'
- echo '********************** APPLICATIONS DISABLED **********************'
- echo '*********************************************************************'
- echo
- if test "$files" != "$pattern"; then
- for skipfile in $files; do
- app=`dirname $skipfile`; app=`basename $app`
- printf "%-15s: " $app; cat $skipfile
- done
- fi
- if test -f $ERL_TOP/lib/SKIP-APPLICATIONS; then
- for skipapp in `cat $ERL_TOP/lib/SKIP-APPLICATIONS`; do
- printf "%-15s: User gave --without-%s option\n" $skipapp $skipapp
- done
- fi
- echo
- echo '*********************************************************************'
-fi
-pattern="lib/*/CONF_INFO"
-files=`echo $pattern`
-if test "$files" != "$pattern" || test -f erts/CONF_INFO; then
- echo '*********************************************************************'
- echo '********************** APPLICATIONS INFORMATION *******************'
- echo '*********************************************************************'
- echo
- test "$files" != "$pattern" || files=""
- test ! -f erts/CONF_INFO || files="$files erts/CONF_INFO"
- for infofile in $files; do
- app=`dirname $infofile`; app=`basename $app`
- printf "%-15s: " $app; cat $infofile
- done
- echo
- echo '*********************************************************************'
-fi
-if test -f "erts/doc/CONF_INFO"; then
- echo '*********************************************************************'
- echo '********************** DOCUMENTATION INFORMATION ******************'
- echo '*********************************************************************'
- echo
- printf "%-15s: \n" documentation;
- havexsltproc="yes"
- for cmd in `cat erts/doc/CONF_INFO`; do
- echo " $cmd is missing."
- if test $cmd = "xsltproc"; then
- havexsltproc="no"
- fi
- done
- if test $havexsltproc = "no"; then
- echo ' The documentation can not be built.'
- else
- echo ' Using fakefop to generate placeholder PDF files.'
- fi
- echo
- echo '*********************************************************************'
-fi
diff --git a/make/lazy_configure.mk b/make/lazy_configure.mk
deleted file mode 100644
index c74f216de0..0000000000
--- a/make/lazy_configure.mk
+++ /dev/null
@@ -1,82 +0,0 @@
-# ``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$
-#
-
-ifndef EXPECTED_AUTOCONF_VERSION
-EXPECTED_AUTOCONF_VERSION=2.59
-endif
-SAVE_ARGS=$(ERL_TOP)/make/save_args
-CONFIG_STATUS=$(CONFIGURE_DIR)/$(TARGET)/config.status
-SAVED_CONFIG_FLAGS_FILE=$(CONFIGURE_DIR)/$(TARGET)/lazy.config.flags
-SAVED_CONFIG_LOG=$(CONFIGURE_DIR)/$(TARGET)/config.log
-CONFIG_CACHE_FILE=$(CONFIGURE_DIR)/$(TARGET)/lazy.config.cache
-ALL_CONFIG_FLAGS=$(CONFIGURE_FLAGS) --no-create --no-recursion --cache-file=$(CONFIG_CACHE_FILE)
-
-lazy_configure: save_config_flags $(CONFIG_STATUS)
- rm -f $(CONFIGURE_DIR)/config.log
- cd $(CONFIGURE_DIR) && $(CONFIG_STATUS)
- cat $(CONFIGURE_DIR)/config.log >> $(SAVED_CONFIG_LOG)
- rm -f $(CONFIGURE_DIR)/config.log
-
-save_config_flags:
- $(SAVE_ARGS) $(SAVED_CONFIG_FLAGS_FILE) --- $(ALL_CONFIG_FLAGS)
-
-$(SAVED_CONFIG_FLAGS_FILE): save_config_flags
-
-$(CONFIGURE_DIR)/configure: $(CONFIGURE_DIR)/configure.in $(EXTRA_CONFIGURE_DEPENDENCIES)
- rm -f $(CONFIG_CACHE_FILE)
- @ exp_ac_vsn=$(EXPECTED_AUTOCONF_VERSION) ; \
- ac_vsn_blob=`autoconf --version` ; \
- ac_vsn=`echo x$$ac_vsn_blob | sed "s|[^0-9]*\([0-9][^ \t\n]*\).*|\1|"` ; \
- case "$$ac_vsn" in \
- $$exp_ac_vsn) \
- ;; \
- *) \
- echo "***************************************************" 1>&2 ; \
- echo "***************************************************" 1>&2 ; \
- echo "*** WARNING: System might fail to configure or" 1>&2 ; \
- echo "*** might be erroneously configured" 1>&2 ; \
- echo "*** since autoconf version $$ac_vsn is used" 1>&2 ; \
- echo "*** instead of version $$exp_ac_vsn!" 1>&2 ; \
- echo "***************************************************" 1>&2 ; \
- echo "***************************************************" 1>&2 ; \
- ;; \
- esac
- cd $(CONFIGURE_DIR) && autoconf -f
-
-$(CONFIGURE_DIR)/config.h.in: $(CONFIGURE_DIR)/configure.in $(CONFIGURE_DIR)/aclocal.m4
- cd $(CONFIGURE_DIR) && autoheader ./configure.in > ./config.h.in
-
-$(CONFIG_STATUS): $(SAVED_CONFIG_FLAGS_FILE) $(CONFIGURE_DIR)/configure $(EXTRA_CONFIG_STATUS_DEPENDENCIES)
- rm -f $(CONFIGURE_DIR)/config.log
- cd $(CONFIGURE_DIR) && CONFIG_STATUS=$(CONFIG_STATUS) ./configure $(ALL_CONFIG_FLAGS)
- rm -f $(SAVED_CONFIG_LOG)
- mv $(CONFIGURE_DIR)/config.log $(SAVED_CONFIG_LOG)
-
-lazy_configure_target_clean:
- rm -f $(CONFIG_STATUS)
- rm -f $(CONFIG_CACHE_FILE)
- rm -f $(SAVED_CONFIG_FLAGS_FILE)
- rm -f $(SAVED_CONFIG_LOG)
-
-lazy_configure_clean: lazy_configure_target_clean
- rm -f $(CONFIGURE_DIR)/configure
- test ! -f $(CONFIGURE_DIR)/acconfig.h || rm -f $(CONFIGURE_DIR)/config.h.in
-
-.PHONY: lazy_configure save_config_flags lazy_configure_clean
-
diff --git a/make/make_emakefile.in b/make/make_emakefile.in
index fbca77887a..e0740d1013 100755
--- a/make/make_emakefile.in
+++ b/make/make_emakefile.in
@@ -1,4 +1,4 @@
-#!@PERL@
+#!/usr/bin/env @PERL@
# -*- cperl -*-
use strict;
diff --git a/make/otp_ded.mk.in b/make/otp_ded.mk.in
index 0b5311d75e..9c8df265de 100644
--- a/make/otp_ded.mk.in
+++ b/make/otp_ded.mk.in
@@ -24,27 +24,26 @@
# explicitly expressed here. Some applications need to
# be able to check this value *before* configure has
# been run and generated otp_ded.mk
-DED_MK_VSN = 1
+DED_MK_VSN = 2
# ----------------------------------------------------
# Variables needed for building Dynamic Erlang Drivers
# ----------------------------------------------------
-DED_CC = @CC@
-DED_GCC = @GCC@
+DED_CC = @DED_CC@
+DED_GCC = @DED_GCC@
DED_LD = @DED_LD@
DED_LDFLAGS = @DED_LDFLAGS@
-DED__NOWARN_NOTHR_CFLAGS = @DED_CFLAGS@
-DED__NOTHR_CFLAGS = @WFLAGS@ @DED_CFLAGS@
-DED__NOWARN_CFLAGS = @DED_EMU_THR_DEFS@ @DED_CFLAGS@
+DED_BASIC_CFLAGS = @DED_CFLAGS@
DED_THR_DEFS = @DED_THR_DEFS@
-DED_EMU_THR_DEFS = @DED_EMU_THR_DEFS@
-DED_WARN_FLAGS = @WFLAGS@
-DED_CFLAGS = @WERRORFLAGS@ @WFLAGS@ @DED_EMU_THR_DEFS@ @DED_CFLAGS@
-DED_STATIC_CFLAGS = @WERRORFLAGS@ @WFLAGS@ @DED_EMU_THR_DEFS@ @DED_STATIC_CFLAGS@
-DED_LIBS = @LIBS@
+DED_WERRORFLAGS = @DED_WERRORFLAGS@
+DED_WARN_FLAGS = @DED_WARN_FLAGS@
+DED_CFLAGS = @DED_CFLAGS@
+DED_LD_FLAG_RUNTIME_LIBRARY_PATH = @DED_LD_FLAG_RUNTIME_LIBRARY_PATH@
+DED_STATIC_CFLAGS = @DED_STATIC_CFLAGS@
+DED_LIBS = @DED_LIBS@
DED_EXT = @DED_EXT@
-ERLANG_OSTYPE = @ERLANG_OSTYPE@
+DED_OSTYPE = @DED_OSTYPE@
PRIVDIR = ../priv
OBJDIR = $(PRIVDIR)/obj/$(TARGET)
LIBDIR = $(PRIVDIR)/lib/$(TARGET)
DED_SYS_INCLUDE = @DED_SYS_INCLUDE@
-DED_INCLUDES = $(DED_SYS_INCLUDE)
+DED_INCLUDES = @DED_INCLUDE@
diff --git a/make/otp_patch_solve_forward_merge_version b/make/otp_patch_solve_forward_merge_version
index 7ed6ff82de..7f8f011eb7 100644
--- a/make/otp_patch_solve_forward_merge_version
+++ b/make/otp_patch_solve_forward_merge_version
@@ -1 +1 @@
-5
+7
diff --git a/make/otp_version_tickets b/make/otp_version_tickets
index 921f8b53fe..b8220e1a87 100644
--- a/make/otp_version_tickets
+++ b/make/otp_version_tickets
@@ -1 +1 @@
-OTP-15554
+DEVELOPMENT
diff --git a/lib/otp_mibs/doc/html/.gitignore b/make/otp_version_tickets_in_merge
index e69de29bb2..e69de29bb2 100644
--- a/lib/otp_mibs/doc/html/.gitignore
+++ b/make/otp_version_tickets_in_merge
diff --git a/otp_build b/otp_build
index 175c5fbcfe..b1c5bcc939 100755
--- a/otp_build
+++ b/otp_build
@@ -21,11 +21,10 @@
# Global configuration variables
#
-# NOTE: lazy_configure depends on '.' always being last directory
if [ -z "$ONLY_ERTS" ]; then
AUTOCONF_SUBDIRS="lib lib/* lib/common_test/test_server"
fi
-AUTOCONF_SUBDIRS="$AUTOCONF_SUBDIRS erts ."
+AUTOCONF_SUBDIRS="$AUTOCONF_SUBDIRS make erts"
# `bootstrap_apps' should include application that are built, or
# partly built in one of the bootstrap phases. Applications that
@@ -212,55 +211,10 @@ set_config_flags ()
NL="\
"
-create_lib_configure_in()
-{
- cd $ERL_TOP
-
- # Multiple versions of autoconf generates code that
- # don't work on all platforms (e.g. SunOS 5.8) if
- # sub directories are soft links. Internally at Ericsson
- # some OTP application directories are soft links.
- # An added "/." solves this problem.
-
- sdirs=
- for lib_app in $bootstrap_apps; do
- case $lib_app in
- lib/*)
- if [ -f "$lib_app/configure.in" ]; then
- app=`echo "$lib_app" | sed "s|lib/\(.*\)|\1|"`
- sdirs="${sdirs}test ! -f $app/configure || AC_CONFIG_SUBDIRS($app/.)${NL}"
- fi;;
- *)
- ;;
- esac
- done
-
- sed_bootstrap="s%@BOOTSTRAP_CONFIGURE_APPS@%$sdirs%g"
-
- sdirs=
- for lib_app in lib/*; do
- is_bapp=false
- for bapp in $bootstrap_apps; do
- test $bapp != $lib_app || { is_bapp=true; break; }
- done
- if [ $is_bapp = false ] && [ -f "$lib_app/configure.in" ]; then
- app=`echo "$lib_app" | sed "s|lib/\(.*\)|\1|"`
- sdirs="${sdirs} test ! -f $app/configure || AC_CONFIG_SUBDIRS($app/.)${NL}"
- fi
- done
-
- sed_non_bootstrap="s%@NON_BOOTSTRAP_CONFIGURE_APPS@%$sdirs%g"
-
- rm -f lib/configure.in
- sed "$sed_bootstrap;$sed_non_bootstrap" > lib/configure.in < lib/configure.in.src || {
- echo "Failed to create lib/configure.in"
- exit 1
- }
-}
distribute_config_helpers ()
{
- aclocal_dirs=". ./lib/erl_interface ./lib/odbc ./lib/wx ./lib/megaco"
+ aclocal_dirs="make ./lib/crypto ./lib/erl_interface ./lib/odbc ./lib/wx ./lib/megaco"
autoconf_aux_dirs="./lib/common_test/priv/auxdir ./lib/erl_interface/src/auxdir ./lib/common_test/test_server ./lib/wx/autoconf"
aclocal_master="./erts/aclocal.m4"
@@ -282,7 +236,6 @@ distribute_config_helpers ()
do_autoconf ()
{
- create_lib_configure_in
distribute_config_helpers
if [ ! -z "$OVERRIDE_CONFIGURE" ]; then
@@ -323,6 +276,15 @@ do_autoconf ()
( cd "$d" && autoheader ) || exit 1
done
+ echo ""
+ echo "=== creating ./configure"
+ otp_version=`cat "$ERL_TOP/OTP_VERSION"`
+ bootstrap_lib_apps=`echo $bootstrap_apps | sed "s|erts||g"`
+ cat "$ERL_TOP/configure.src" \
+ | sed "s|@OTP_VERSION@|$otp_version|g;s|@BOOTSTRAP_LIB_APP_DIRS@|$bootstrap_lib_apps|" \
+ > "$ERL_TOP/configure"
+ chmod +x "$ERL_TOP/configure"
+
restore_vars OVERRIDE_TARGET TARGET
}
@@ -385,8 +347,8 @@ try_cross_configure ()
test "X$build_value" != "X" || build_value="$BUILDSYS"
- build_sys=`$ERL_TOP/erts/autoconf/config.sub "$build_value"` || exit 1
- host_sys=`$ERL_TOP/erts/autoconf/config.sub "$host_value"` || exit 1
+ build_sys=`"$ERL_TOP/erts/autoconf/config.sub" "$build_value"` || exit 1
+ host_sys=`"$ERL_TOP/erts/autoconf/config.sub" "$host_value"` || exit 1
test "$host_sys" = "$build_sys" || cross_configure=yes
@@ -500,7 +462,7 @@ do_configure ()
case $TARGET in
vxworks_*)
( cd erts/autoconf && \
- $ERL_TOP/erts/autoconf/configure.vxworks $TARGET )
+ "$ERL_TOP/erts/autoconf/configure.vxworks" $TARGET )
echo "Configuring for build system too..." >&2
hide_vars OVERRIDE_TARGET TARGET
TARGET=$BUILDSYS
@@ -526,105 +488,6 @@ do_configure ()
fi
}
-do_lazy_configure ()
-{
- setup_make
- if [ "x$OVERRIDE_TARGET" != "x" -a "x$OVERRIDE_TARGET" != "xwin32" ]; then
- echo "Not supported for cross compilation" >&2
- exit 1
- fi
- maybe_copy_static_cache
- CONFIG_FLAGS=
- set_config_flags "$@"
- CONFIGURE_FLAGS="$@"
- [ "$CONFIG_FLAGS" = "" ] || CONFIGURE_FLAGS="$CONFIG_FLAGS $CONFIGURE_FLAGS"
- for c_dir in $AUTOCONF_SUBDIRS; do
- if test -f $ERL_TOP/$c_dir/configure.in; then
- dir=$ERL_TOP/$c_dir
- echo ""
- echo "=== Begin configuring $dir"
- xc_dep= ;
- xcs_dep= ;
- test -d $dir/$TARGET || mkdir $dir/$TARGET
- test -f $dir/aclocal.m4 && xc_dep="$xcs_dep $dir/aclocal.m4"
- test -f $dir/acsite.m4 && xc_dep="$xcs_dep $dir/acsite.m4"
- test x$c_dir = x"erts" && xcs_dep="$xcs_dep $dir/config.h.in"
- $MAKE -f $ERL_TOP/make/lazy_configure.mk \
- MAKE="$MAKE" TARGET=$TARGET \
- ERL_TOP=$ERL_TOP \
- CONFIGURE_FLAGS="$CONFIGURE_FLAGS" \
- CONFIGURE_DIR=$dir \
- EXTRA_CONFIGURE_DEPENDENCIES=$xc_dep \
- EXTRA_CONFIG_STATUS_DEPENDENCIES=$xcs_dep \
- lazy_configure
- echo "=== Done configuring $dir"
- echo ""
- fi
- done
-}
-
-do_lazy_configure_clean ()
-{
- setup_make
- if [ "x$OVERRIDE_TARGET" != "x" -a "x$OVERRIDE_TARGET" != "xwin32" ]; then
- echo "Not supported for cross compilation" >&2
- exit 1
- fi
- for c_dir in $AUTOCONF_SUBDIRS; do
- if test -f $ERL_TOP/$c_dir/configure.in; then
- dir=$ERL_TOP/$c_dir
- echo ""
- echo "=== Begin cleaning configure in $dir"
- xc_dep= ;
- xcs_dep= ;
- test -d $dir/$TARGET || mkdir $dir/$TARGET
- test -f $dir/aclocal.m4 && xc_dep="$xcs_dep $dir/aclocal.m4"
- test -f $dir/acsite.m4 && xc_dep="$xcs_dep $dir/acsite.m4"
- test x$c_dir = x"erts" && xcs_dep="$xcs_dep $dir/config.h.in"
- $MAKE -f $ERL_TOP/make/lazy_configure.mk \
- MAKE="$MAKE" TARGET=$TARGET \
- ERL_TOP=$ERL_TOP \
- CONFIGURE_DIR=$dir \
- lazy_configure_clean
- echo "=== Done cleaning configure in $dir"
- echo ""
- fi
- done
-
-}
-
-do_lazy_configure_target_clean ()
-{
- setup_make
- if [ "x$OVERRIDE_TARGET" != "x" -a "x$OVERRIDE_TARGET" != "xwin32" ]; then
- echo "Not supported for cross compilation" >&2
- exit 1
- fi
- for c_dir in $AUTOCONF_SUBDIRS; do
- if test -f $ERL_TOP/$c_dir/configure.in; then
- dir=$ERL_TOP/$c_dir
- echo ""
- echo "=== Begin target cleaning configure in $dir"
- xc_dep= ;
- xcs_dep= ;
- test -d $dir/$TARGET || mkdir $dir/$TARGET
- test -f $dir/aclocal.m4 && xc_dep="$xcs_dep $dir/aclocal.m4"
- test -f $dir/acsite.m4 && xc_dep="$xcs_dep $dir/acsite.m4"
- test x$c_dir = x"erts" && xcs_dep="$xcs_dep $dir/config.h.in"
- $MAKE -f $ERL_TOP/make/lazy_configure.mk \
- MAKE="$MAKE" TARGET=$TARGET \
- ERL_TOP=$ERL_TOP \
- CONFIGURE_DIR=$dir \
- lazy_configure_target_clean
- echo "=== Done target cleaning configure in $dir"
- echo ""
- fi
- done
-
-}
-
-
-
echo_setenv ()
{
case "$DAILY_BUILD_SCRIPT$SHELL" in
@@ -1075,11 +938,11 @@ do_update_prel_git ()
{
get_do_commit $1
setup_make
- (cd $ERL_TOP/erts/preloaded/src && $MAKE MAKE="$MAKE" BOOTSTRAP_ROOT=$BOOTSTRAP_ROOT TARGET=$TARGET clean)
+ (cd "$ERL_TOP/erts/preloaded/src" && $MAKE MAKE="$MAKE" BOOTSTRAP_ROOT=$BOOTSTRAP_ROOT TARGET=$TARGET clean)
$MAKE MAKE="$MAKE" BOOTSTRAP_ROOT=$BOOTSTRAP_ROOT TARGET=$TARGET preloaded || exit 1
- (cd $ERL_TOP/erts/preloaded/src && $MAKE MAKE="$MAKE" BOOTSTRAP_ROOT=$BOOTSTRAP_ROOT TARGET=$TARGET copy)
+ (cd "$ERL_TOP/erts/preloaded/src" && $MAKE MAKE="$MAKE" BOOTSTRAP_ROOT=$BOOTSTRAP_ROOT TARGET=$TARGET copy)
if [ $do_commit = true ]; then
- git add -A $ERL_TOP/erts/preloaded/ebin/*.beam
+ git add -A "$ERL_TOP/erts/preloaded/ebin/*.beam"
git commit -m 'Update preloaded modules'
echo "Preloaded updated and commited."
else
@@ -1149,13 +1012,13 @@ do_debuginfo_win32 ()
(cd erts/emulator && $MAKE MAKE="$MAKE" TARGET=$TARGET FLAVOR=smp debug &&\
$MAKE MAKE="$MAKE" TARGET=$TARGET FLAVOR=plain debug) || exit 1
if [ -z "$1" ]; then
- RELDIR=$ERL_TOP/release/$TARGET
+ RELDIR="$ERL_TOP/release/$TARGET"
else
RELDIR="$1"
fi
- BINDIR=$ERL_TOP/bin/$TARGET
+ 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.dll beam.debug.smp.dll beam.pdb beam.smp.pdb erl.pdb werl.pdb erlexec.pdb; do
+ for f in beam.debug.smp.dll beam.smp.pdb erl.pdb werl.pdb erlexec.pdb; do
if [ -f $BINDIR/$f ]; then
rm -f $RELDIR/erts-$EVSN/bin/$f
cp $BINDIR/$f $RELDIR/erts-$EVSN/bin/$f
@@ -1166,7 +1029,7 @@ do_debuginfo_win32 ()
do_installer_win32 ()
{
setup_make
- installer_dir=$ERL_TOP/erts/etc/win32/nsis
+ installer_dir="$ERL_TOP/erts/etc/win32/nsis"
(cd $installer_dir; $MAKE MAKE="$MAKE" TARGET=$TARGET TESTROOT=$1 release) || exit 1
}
@@ -1210,7 +1073,7 @@ do_copy_primary_bootstrap ()
cp -f $lib_src/compiler/ebin/*.beam $bootstrap/lib/compiler/ebin
# bootstrap bin
- if [ $bootstrap_src_top != $ERL_TOP ]; then
+ if [ $bootstrap_src_top != "$ERL_TOP" ]; then
test -d $bootstrap/bin || mkdir -p $bootstrap/bin
cp -f $bootstrap_src_top/bin/* $bootstrap/bin
fi
@@ -1219,22 +1082,22 @@ do_copy_primary_bootstrap ()
do_save_bootstrap ()
{
- if [ ! -f $ERL_TOP/prebuilt.files ]; then
+ if [ ! -f "$ERL_TOP/prebuilt.files" ]; then
echo "This is not a pre-built source distribution" 1>&2
exit 1
fi
- if [ -d $ERL_TOP/bootstrap/lib ]; then
+ if [ -d "$ERL_TOP/bootstrap/lib" ]; then
echo "Bootstrap already exist" 1>&2
exit 1
fi
- do_copy_primary_bootstrap $ERL_TOP $ERL_TOP
+ do_copy_primary_bootstrap "$ERL_TOP" "$ERL_TOP"
}
do_remove_prebuilt_files ()
{
do_save_bootstrap
- for file in $ERL_TOP/`cat $ERL_TOP/prebuilt.files` ; do
+ for file in "$ERL_TOP"/`cat "$ERL_TOP/prebuilt.files"` ; do
rm -f $file
done
}
@@ -1243,7 +1106,7 @@ do_remove_prebuilt_files ()
check_erltop
-cd $ERL_TOP
+cd "$ERL_TOP"
determine_version_controller
@@ -1257,7 +1120,7 @@ unset ${erl_otp_flags}
# Target first guess, won't necessarily hold, may be changed for
# certain parameters.
if [ X"$TARGET" = X"" ]; then
- TARGET=`$ERL_TOP/erts/autoconf/config.guess`
+ TARGET=`"$ERL_TOP/erts/autoconf/config.guess"`
fi
BUILDSYS=$TARGET
@@ -1335,27 +1198,11 @@ case "$1" in
do_autoconf;
do_configure "$@";
do_boot;;
- lazy_setup)
- shift;
- if [ $minus_a_flag = true ]; then
- shift
- fi;
- do_lazy_configure "$@";
- do_boot;;
autoconf)
do_autoconf;;
configure)
shift;
do_configure "$@";;
- lazy_configure)
- shift;
- do_lazy_configure "$@";;
- lazy_configure_clean)
- shift;
- do_lazy_configure_clean;;
- lazy_configure_target_clean)
- shift;
- do_lazy_configure_target_clean;;
opt)
do_boot;;
plain|smp)
diff --git a/otp_versions.table b/otp_versions.table
index 377b563eea..aea1e8d5cd 100644
--- a/otp_versions.table
+++ b/otp_versions.table
@@ -1,3 +1,4 @@
+OTP-21.2.6 : erts-10.2.4 stdlib-3.7.1 # asn1-5.0.8 common_test-1.16.1 compiler-7.3.1 crypto-4.4 debugger-4.2.6 dialyzer-3.3.1 diameter-2.1.6 edoc-0.9.4 eldap-1.2.6 erl_docgen-0.8.1 erl_interface-3.10.4 et-1.6.4 eunit-2.3.7 ftp-1.0.1 hipe-3.18.2 inets-7.0.5 jinterface-1.9.1 kernel-6.2 megaco-3.18.4 mnesia-4.15.5 observer-2.8.2 odbc-2.12.2 os_mon-2.4.7 otp_mibs-1.2.1 parsetools-2.1.8 public_key-1.6.4 reltool-0.7.8 runtime_tools-1.13.1 sasl-3.3 snmp-5.2.12 ssh-4.7.3 ssl-9.1.2 syntax_tools-2.1.6 tftp-1.0.1 tools-3.0.2 wx-1.8.6 xmerl-1.3.19 :
OTP-21.2.5 : inets-7.0.5 # asn1-5.0.8 common_test-1.16.1 compiler-7.3.1 crypto-4.4 debugger-4.2.6 dialyzer-3.3.1 diameter-2.1.6 edoc-0.9.4 eldap-1.2.6 erl_docgen-0.8.1 erl_interface-3.10.4 erts-10.2.3 et-1.6.4 eunit-2.3.7 ftp-1.0.1 hipe-3.18.2 jinterface-1.9.1 kernel-6.2 megaco-3.18.4 mnesia-4.15.5 observer-2.8.2 odbc-2.12.2 os_mon-2.4.7 otp_mibs-1.2.1 parsetools-2.1.8 public_key-1.6.4 reltool-0.7.8 runtime_tools-1.13.1 sasl-3.3 snmp-5.2.12 ssh-4.7.3 ssl-9.1.2 stdlib-3.7 syntax_tools-2.1.6 tftp-1.0.1 tools-3.0.2 wx-1.8.6 xmerl-1.3.19 :
OTP-21.2.4 : erts-10.2.3 inets-7.0.4 # asn1-5.0.8 common_test-1.16.1 compiler-7.3.1 crypto-4.4 debugger-4.2.6 dialyzer-3.3.1 diameter-2.1.6 edoc-0.9.4 eldap-1.2.6 erl_docgen-0.8.1 erl_interface-3.10.4 et-1.6.4 eunit-2.3.7 ftp-1.0.1 hipe-3.18.2 jinterface-1.9.1 kernel-6.2 megaco-3.18.4 mnesia-4.15.5 observer-2.8.2 odbc-2.12.2 os_mon-2.4.7 otp_mibs-1.2.1 parsetools-2.1.8 public_key-1.6.4 reltool-0.7.8 runtime_tools-1.13.1 sasl-3.3 snmp-5.2.12 ssh-4.7.3 ssl-9.1.2 stdlib-3.7 syntax_tools-2.1.6 tftp-1.0.1 tools-3.0.2 wx-1.8.6 xmerl-1.3.19 :
OTP-21.2.3 : compiler-7.3.1 erts-10.2.2 ssl-9.1.2 xmerl-1.3.19 # asn1-5.0.8 common_test-1.16.1 crypto-4.4 debugger-4.2.6 dialyzer-3.3.1 diameter-2.1.6 edoc-0.9.4 eldap-1.2.6 erl_docgen-0.8.1 erl_interface-3.10.4 et-1.6.4 eunit-2.3.7 ftp-1.0.1 hipe-3.18.2 inets-7.0.3 jinterface-1.9.1 kernel-6.2 megaco-3.18.4 mnesia-4.15.5 observer-2.8.2 odbc-2.12.2 os_mon-2.4.7 otp_mibs-1.2.1 parsetools-2.1.8 public_key-1.6.4 reltool-0.7.8 runtime_tools-1.13.1 sasl-3.3 snmp-5.2.12 ssh-4.7.3 stdlib-3.7 syntax_tools-2.1.6 tftp-1.0.1 tools-3.0.2 wx-1.8.6 :
@@ -19,6 +20,11 @@ OTP-21.0.3 : erts-10.0.3 # asn1-5.0.6 common_test-1.16 compiler-7.2.2 crypto-4.3
OTP-21.0.2 : compiler-7.2.2 erts-10.0.2 public_key-1.6.1 stdlib-3.5.1 # asn1-5.0.6 common_test-1.16 crypto-4.3 debugger-4.2.5 dialyzer-3.3 diameter-2.1.5 edoc-0.9.3 eldap-1.2.4 erl_docgen-0.8 erl_interface-3.10.3 et-1.6.2 eunit-2.3.6 ftp-1.0 hipe-3.18 inets-7.0 jinterface-1.9 kernel-6.0 megaco-3.18.3 mnesia-4.15.4 observer-2.8 odbc-2.12.1 os_mon-2.4.5 otp_mibs-1.2 parsetools-2.1.7 reltool-0.7.6 runtime_tools-1.13 sasl-3.2 snmp-5.2.11 ssh-4.7 ssl-9.0 syntax_tools-2.1.5 tftp-1.0 tools-3.0 wx-1.8.4 xmerl-1.3.17 :
OTP-21.0.1 : compiler-7.2.1 erts-10.0.1 # asn1-5.0.6 common_test-1.16 crypto-4.3 debugger-4.2.5 dialyzer-3.3 diameter-2.1.5 edoc-0.9.3 eldap-1.2.4 erl_docgen-0.8 erl_interface-3.10.3 et-1.6.2 eunit-2.3.6 ftp-1.0 hipe-3.18 inets-7.0 jinterface-1.9 kernel-6.0 megaco-3.18.3 mnesia-4.15.4 observer-2.8 odbc-2.12.1 os_mon-2.4.5 otp_mibs-1.2 parsetools-2.1.7 public_key-1.6 reltool-0.7.6 runtime_tools-1.13 sasl-3.2 snmp-5.2.11 ssh-4.7 ssl-9.0 stdlib-3.5 syntax_tools-2.1.5 tftp-1.0 tools-3.0 wx-1.8.4 xmerl-1.3.17 :
OTP-21.0 : asn1-5.0.6 common_test-1.16 compiler-7.2 crypto-4.3 debugger-4.2.5 dialyzer-3.3 diameter-2.1.5 edoc-0.9.3 eldap-1.2.4 erl_docgen-0.8 erl_interface-3.10.3 erts-10.0 et-1.6.2 eunit-2.3.6 ftp-1.0 hipe-3.18 inets-7.0 jinterface-1.9 kernel-6.0 mnesia-4.15.4 observer-2.8 os_mon-2.4.5 otp_mibs-1.2 parsetools-2.1.7 public_key-1.6 reltool-0.7.6 runtime_tools-1.13 sasl-3.2 ssh-4.7 ssl-9.0 stdlib-3.5 syntax_tools-2.1.5 tftp-1.0 tools-3.0 wx-1.8.4 xmerl-1.3.17 # megaco-3.18.3 odbc-2.12.1 snmp-5.2.11 :
+OTP-20.3.8.20 : common_test-1.15.4.1 # asn1-5.0.5.2 compiler-7.1.5.2 cosEvent-2.2.2 cosEventDomain-1.2.2 cosFileTransfer-1.2.2 cosNotification-1.2.3 cosProperty-1.2.3 cosTime-1.2.3 cosTransactions-1.3.3 crypto-4.2.2.2 debugger-4.2.4 dialyzer-3.2.4 diameter-2.1.4.1 edoc-0.9.2 eldap-1.2.3.1 erl_docgen-0.7.3 erl_interface-3.10.2.1 erts-9.3.3.9 et-1.6.1 eunit-2.3.5 hipe-3.17.1 ic-4.4.4.2 inets-6.5.2.4 jinterface-1.8.1 kernel-5.4.3.2 megaco-3.18.3 mnesia-4.15.3.2 observer-2.7 odbc-2.12.1 orber-3.8.4 os_mon-2.4.4 otp_mibs-1.1.2 parsetools-2.1.6 public_key-1.5.2 reltool-0.7.5 runtime_tools-1.12.5 sasl-3.1.2 snmp-5.2.11 ssh-4.6.9.3 ssl-8.2.6.4 stdlib-3.4.5.1 syntax_tools-2.1.4.1 tools-2.11.2 wx-1.8.3 xmerl-1.3.16.1 :
+OTP-20.3.8.19 : diameter-2.1.4.1 erts-9.3.3.9 # asn1-5.0.5.2 common_test-1.15.4 compiler-7.1.5.2 cosEvent-2.2.2 cosEventDomain-1.2.2 cosFileTransfer-1.2.2 cosNotification-1.2.3 cosProperty-1.2.3 cosTime-1.2.3 cosTransactions-1.3.3 crypto-4.2.2.2 debugger-4.2.4 dialyzer-3.2.4 edoc-0.9.2 eldap-1.2.3.1 erl_docgen-0.7.3 erl_interface-3.10.2.1 et-1.6.1 eunit-2.3.5 hipe-3.17.1 ic-4.4.4.2 inets-6.5.2.4 jinterface-1.8.1 kernel-5.4.3.2 megaco-3.18.3 mnesia-4.15.3.2 observer-2.7 odbc-2.12.1 orber-3.8.4 os_mon-2.4.4 otp_mibs-1.1.2 parsetools-2.1.6 public_key-1.5.2 reltool-0.7.5 runtime_tools-1.12.5 sasl-3.1.2 snmp-5.2.11 ssh-4.6.9.3 ssl-8.2.6.4 stdlib-3.4.5.1 syntax_tools-2.1.4.1 tools-2.11.2 wx-1.8.3 xmerl-1.3.16.1 :
+OTP-20.3.8.18 : erts-9.3.3.8 # asn1-5.0.5.2 common_test-1.15.4 compiler-7.1.5.2 cosEvent-2.2.2 cosEventDomain-1.2.2 cosFileTransfer-1.2.2 cosNotification-1.2.3 cosProperty-1.2.3 cosTime-1.2.3 cosTransactions-1.3.3 crypto-4.2.2.2 debugger-4.2.4 dialyzer-3.2.4 diameter-2.1.4 edoc-0.9.2 eldap-1.2.3.1 erl_docgen-0.7.3 erl_interface-3.10.2.1 et-1.6.1 eunit-2.3.5 hipe-3.17.1 ic-4.4.4.2 inets-6.5.2.4 jinterface-1.8.1 kernel-5.4.3.2 megaco-3.18.3 mnesia-4.15.3.2 observer-2.7 odbc-2.12.1 orber-3.8.4 os_mon-2.4.4 otp_mibs-1.1.2 parsetools-2.1.6 public_key-1.5.2 reltool-0.7.5 runtime_tools-1.12.5 sasl-3.1.2 snmp-5.2.11 ssh-4.6.9.3 ssl-8.2.6.4 stdlib-3.4.5.1 syntax_tools-2.1.4.1 tools-2.11.2 wx-1.8.3 xmerl-1.3.16.1 :
+OTP-20.3.8.17 : xmerl-1.3.16.1 # asn1-5.0.5.2 common_test-1.15.4 compiler-7.1.5.2 cosEvent-2.2.2 cosEventDomain-1.2.2 cosFileTransfer-1.2.2 cosNotification-1.2.3 cosProperty-1.2.3 cosTime-1.2.3 cosTransactions-1.3.3 crypto-4.2.2.2 debugger-4.2.4 dialyzer-3.2.4 diameter-2.1.4 edoc-0.9.2 eldap-1.2.3.1 erl_docgen-0.7.3 erl_interface-3.10.2.1 erts-9.3.3.7 et-1.6.1 eunit-2.3.5 hipe-3.17.1 ic-4.4.4.2 inets-6.5.2.4 jinterface-1.8.1 kernel-5.4.3.2 megaco-3.18.3 mnesia-4.15.3.2 observer-2.7 odbc-2.12.1 orber-3.8.4 os_mon-2.4.4 otp_mibs-1.1.2 parsetools-2.1.6 public_key-1.5.2 reltool-0.7.5 runtime_tools-1.12.5 sasl-3.1.2 snmp-5.2.11 ssh-4.6.9.3 ssl-8.2.6.4 stdlib-3.4.5.1 syntax_tools-2.1.4.1 tools-2.11.2 wx-1.8.3 :
+OTP-20.3.8.16 : erts-9.3.3.7 ssh-4.6.9.3 # asn1-5.0.5.2 common_test-1.15.4 compiler-7.1.5.2 cosEvent-2.2.2 cosEventDomain-1.2.2 cosFileTransfer-1.2.2 cosNotification-1.2.3 cosProperty-1.2.3 cosTime-1.2.3 cosTransactions-1.3.3 crypto-4.2.2.2 debugger-4.2.4 dialyzer-3.2.4 diameter-2.1.4 edoc-0.9.2 eldap-1.2.3.1 erl_docgen-0.7.3 erl_interface-3.10.2.1 et-1.6.1 eunit-2.3.5 hipe-3.17.1 ic-4.4.4.2 inets-6.5.2.4 jinterface-1.8.1 kernel-5.4.3.2 megaco-3.18.3 mnesia-4.15.3.2 observer-2.7 odbc-2.12.1 orber-3.8.4 os_mon-2.4.4 otp_mibs-1.1.2 parsetools-2.1.6 public_key-1.5.2 reltool-0.7.5 runtime_tools-1.12.5 sasl-3.1.2 snmp-5.2.11 ssl-8.2.6.4 stdlib-3.4.5.1 syntax_tools-2.1.4.1 tools-2.11.2 wx-1.8.3 xmerl-1.3.16 :
OTP-20.3.8.15 : asn1-5.0.5.2 # common_test-1.15.4 compiler-7.1.5.2 cosEvent-2.2.2 cosEventDomain-1.2.2 cosFileTransfer-1.2.2 cosNotification-1.2.3 cosProperty-1.2.3 cosTime-1.2.3 cosTransactions-1.3.3 crypto-4.2.2.2 debugger-4.2.4 dialyzer-3.2.4 diameter-2.1.4 edoc-0.9.2 eldap-1.2.3.1 erl_docgen-0.7.3 erl_interface-3.10.2.1 erts-9.3.3.6 et-1.6.1 eunit-2.3.5 hipe-3.17.1 ic-4.4.4.2 inets-6.5.2.4 jinterface-1.8.1 kernel-5.4.3.2 megaco-3.18.3 mnesia-4.15.3.2 observer-2.7 odbc-2.12.1 orber-3.8.4 os_mon-2.4.4 otp_mibs-1.1.2 parsetools-2.1.6 public_key-1.5.2 reltool-0.7.5 runtime_tools-1.12.5 sasl-3.1.2 snmp-5.2.11 ssh-4.6.9.2 ssl-8.2.6.4 stdlib-3.4.5.1 syntax_tools-2.1.4.1 tools-2.11.2 wx-1.8.3 xmerl-1.3.16 :
OTP-20.3.8.14 : ssh-4.6.9.2 # asn1-5.0.5.1 common_test-1.15.4 compiler-7.1.5.2 cosEvent-2.2.2 cosEventDomain-1.2.2 cosFileTransfer-1.2.2 cosNotification-1.2.3 cosProperty-1.2.3 cosTime-1.2.3 cosTransactions-1.3.3 crypto-4.2.2.2 debugger-4.2.4 dialyzer-3.2.4 diameter-2.1.4 edoc-0.9.2 eldap-1.2.3.1 erl_docgen-0.7.3 erl_interface-3.10.2.1 erts-9.3.3.6 et-1.6.1 eunit-2.3.5 hipe-3.17.1 ic-4.4.4.2 inets-6.5.2.4 jinterface-1.8.1 kernel-5.4.3.2 megaco-3.18.3 mnesia-4.15.3.2 observer-2.7 odbc-2.12.1 orber-3.8.4 os_mon-2.4.4 otp_mibs-1.1.2 parsetools-2.1.6 public_key-1.5.2 reltool-0.7.5 runtime_tools-1.12.5 sasl-3.1.2 snmp-5.2.11 ssl-8.2.6.4 stdlib-3.4.5.1 syntax_tools-2.1.4.1 tools-2.11.2 wx-1.8.3 xmerl-1.3.16 :
OTP-20.3.8.13 : ssl-8.2.6.4 # asn1-5.0.5.1 common_test-1.15.4 compiler-7.1.5.2 cosEvent-2.2.2 cosEventDomain-1.2.2 cosFileTransfer-1.2.2 cosNotification-1.2.3 cosProperty-1.2.3 cosTime-1.2.3 cosTransactions-1.3.3 crypto-4.2.2.2 debugger-4.2.4 dialyzer-3.2.4 diameter-2.1.4 edoc-0.9.2 eldap-1.2.3.1 erl_docgen-0.7.3 erl_interface-3.10.2.1 erts-9.3.3.6 et-1.6.1 eunit-2.3.5 hipe-3.17.1 ic-4.4.4.2 inets-6.5.2.4 jinterface-1.8.1 kernel-5.4.3.2 megaco-3.18.3 mnesia-4.15.3.2 observer-2.7 odbc-2.12.1 orber-3.8.4 os_mon-2.4.4 otp_mibs-1.1.2 parsetools-2.1.6 public_key-1.5.2 reltool-0.7.5 runtime_tools-1.12.5 sasl-3.1.2 snmp-5.2.11 ssh-4.6.9.1 stdlib-3.4.5.1 syntax_tools-2.1.4.1 tools-2.11.2 wx-1.8.3 xmerl-1.3.16 :
@@ -40,6 +46,7 @@ OTP-20.3.6 : crypto-4.2.2 ssh-4.6.9 # asn1-5.0.5 common_test-1.15.4 compiler-7.1
OTP-20.3.5 : erts-9.3.1 ssl-8.2.6 # asn1-5.0.5 common_test-1.15.4 compiler-7.1.5 cosEvent-2.2.2 cosEventDomain-1.2.2 cosFileTransfer-1.2.2 cosNotification-1.2.3 cosProperty-1.2.3 cosTime-1.2.3 cosTransactions-1.3.3 crypto-4.2.1 debugger-4.2.4 dialyzer-3.2.4 diameter-2.1.4 edoc-0.9.2 eldap-1.2.3 erl_docgen-0.7.2 erl_interface-3.10.2 et-1.6.1 eunit-2.3.5 hipe-3.17.1 ic-4.4.4 inets-6.5.1 jinterface-1.8.1 kernel-5.4.3 megaco-3.18.3 mnesia-4.15.3 observer-2.7 odbc-2.12.1 orber-3.8.4 os_mon-2.4.4 otp_mibs-1.1.2 parsetools-2.1.6 public_key-1.5.2 reltool-0.7.5 runtime_tools-1.12.5 sasl-3.1.2 snmp-5.2.10 ssh-4.6.8 stdlib-3.4.5 syntax_tools-2.1.4 tools-2.11.2 wx-1.8.3 xmerl-1.3.16 :
OTP-20.3.4 : erl_interface-3.10.2 ic-4.4.4 inets-6.5.1 ssh-4.6.8 # asn1-5.0.5 common_test-1.15.4 compiler-7.1.5 cosEvent-2.2.2 cosEventDomain-1.2.2 cosFileTransfer-1.2.2 cosNotification-1.2.3 cosProperty-1.2.3 cosTime-1.2.3 cosTransactions-1.3.3 crypto-4.2.1 debugger-4.2.4 dialyzer-3.2.4 diameter-2.1.4 edoc-0.9.2 eldap-1.2.3 erl_docgen-0.7.2 erts-9.3 et-1.6.1 eunit-2.3.5 hipe-3.17.1 jinterface-1.8.1 kernel-5.4.3 megaco-3.18.3 mnesia-4.15.3 observer-2.7 odbc-2.12.1 orber-3.8.4 os_mon-2.4.4 otp_mibs-1.1.2 parsetools-2.1.6 public_key-1.5.2 reltool-0.7.5 runtime_tools-1.12.5 sasl-3.1.2 snmp-5.2.10 ssl-8.2.5 stdlib-3.4.5 syntax_tools-2.1.4 tools-2.11.2 wx-1.8.3 xmerl-1.3.16 :
OTP-20.3.3 : sasl-3.1.2 # asn1-5.0.5 common_test-1.15.4 compiler-7.1.5 cosEvent-2.2.2 cosEventDomain-1.2.2 cosFileTransfer-1.2.2 cosNotification-1.2.3 cosProperty-1.2.3 cosTime-1.2.3 cosTransactions-1.3.3 crypto-4.2.1 debugger-4.2.4 dialyzer-3.2.4 diameter-2.1.4 edoc-0.9.2 eldap-1.2.3 erl_docgen-0.7.2 erl_interface-3.10.1 erts-9.3 et-1.6.1 eunit-2.3.5 hipe-3.17.1 ic-4.4.3 inets-6.5 jinterface-1.8.1 kernel-5.4.3 megaco-3.18.3 mnesia-4.15.3 observer-2.7 odbc-2.12.1 orber-3.8.4 os_mon-2.4.4 otp_mibs-1.1.2 parsetools-2.1.6 public_key-1.5.2 reltool-0.7.5 runtime_tools-1.12.5 snmp-5.2.10 ssh-4.6.7 ssl-8.2.5 stdlib-3.4.5 syntax_tools-2.1.4 tools-2.11.2 wx-1.8.3 xmerl-1.3.16 :
+OTP-20.3.2.1 : common_test-1.15.4.0.1 # asn1-5.0.5 compiler-7.1.5 cosEvent-2.2.2 cosEventDomain-1.2.2 cosFileTransfer-1.2.2 cosNotification-1.2.3 cosProperty-1.2.3 cosTime-1.2.3 cosTransactions-1.3.3 crypto-4.2.1 debugger-4.2.4 dialyzer-3.2.4 diameter-2.1.4 edoc-0.9.2 eldap-1.2.3 erl_docgen-0.7.2 erl_interface-3.10.1 erts-9.3 et-1.6.1 eunit-2.3.5 hipe-3.17.1 ic-4.4.3 inets-6.5 jinterface-1.8.1 kernel-5.4.3 megaco-3.18.3 mnesia-4.15.3 observer-2.7 odbc-2.12.1 orber-3.8.4 os_mon-2.4.4 otp_mibs-1.1.2 parsetools-2.1.6 public_key-1.5.2 reltool-0.7.5 runtime_tools-1.12.5 sasl-3.1.1 snmp-5.2.10 ssh-4.6.7 ssl-8.2.5 stdlib-3.4.5 syntax_tools-2.1.4 tools-2.11.2 wx-1.8.3 xmerl-1.3.16 :
OTP-20.3.2 : ssh-4.6.7 stdlib-3.4.5 # asn1-5.0.5 common_test-1.15.4 compiler-7.1.5 cosEvent-2.2.2 cosEventDomain-1.2.2 cosFileTransfer-1.2.2 cosNotification-1.2.3 cosProperty-1.2.3 cosTime-1.2.3 cosTransactions-1.3.3 crypto-4.2.1 debugger-4.2.4 dialyzer-3.2.4 diameter-2.1.4 edoc-0.9.2 eldap-1.2.3 erl_docgen-0.7.2 erl_interface-3.10.1 erts-9.3 et-1.6.1 eunit-2.3.5 hipe-3.17.1 ic-4.4.3 inets-6.5 jinterface-1.8.1 kernel-5.4.3 megaco-3.18.3 mnesia-4.15.3 observer-2.7 odbc-2.12.1 orber-3.8.4 os_mon-2.4.4 otp_mibs-1.1.2 parsetools-2.1.6 public_key-1.5.2 reltool-0.7.5 runtime_tools-1.12.5 sasl-3.1.1 snmp-5.2.10 ssl-8.2.5 syntax_tools-2.1.4 tools-2.11.2 wx-1.8.3 xmerl-1.3.16 :
OTP-20.3.1 : ssl-8.2.5 # asn1-5.0.5 common_test-1.15.4 compiler-7.1.5 cosEvent-2.2.2 cosEventDomain-1.2.2 cosFileTransfer-1.2.2 cosNotification-1.2.3 cosProperty-1.2.3 cosTime-1.2.3 cosTransactions-1.3.3 crypto-4.2.1 debugger-4.2.4 dialyzer-3.2.4 diameter-2.1.4 edoc-0.9.2 eldap-1.2.3 erl_docgen-0.7.2 erl_interface-3.10.1 erts-9.3 et-1.6.1 eunit-2.3.5 hipe-3.17.1 ic-4.4.3 inets-6.5 jinterface-1.8.1 kernel-5.4.3 megaco-3.18.3 mnesia-4.15.3 observer-2.7 odbc-2.12.1 orber-3.8.4 os_mon-2.4.4 otp_mibs-1.1.2 parsetools-2.1.6 public_key-1.5.2 reltool-0.7.5 runtime_tools-1.12.5 sasl-3.1.1 snmp-5.2.10 ssh-4.6.6 stdlib-3.4.4 syntax_tools-2.1.4 tools-2.11.2 wx-1.8.3 xmerl-1.3.16 :
OTP-20.3 : asn1-5.0.5 common_test-1.15.4 compiler-7.1.5 crypto-4.2.1 dialyzer-3.2.4 diameter-2.1.4 erts-9.3 hipe-3.17.1 inets-6.5 kernel-5.4.3 observer-2.7 runtime_tools-1.12.5 snmp-5.2.10 ssh-4.6.6 ssl-8.2.4 stdlib-3.4.4 tools-2.11.2 # cosEvent-2.2.2 cosEventDomain-1.2.2 cosFileTransfer-1.2.2 cosNotification-1.2.3 cosProperty-1.2.3 cosTime-1.2.3 cosTransactions-1.3.3 debugger-4.2.4 edoc-0.9.2 eldap-1.2.3 erl_docgen-0.7.2 erl_interface-3.10.1 et-1.6.1 eunit-2.3.5 ic-4.4.3 jinterface-1.8.1 megaco-3.18.3 mnesia-4.15.3 odbc-2.12.1 orber-3.8.4 os_mon-2.4.4 otp_mibs-1.1.2 parsetools-2.1.6 public_key-1.5.2 reltool-0.7.5 sasl-3.1.1 syntax_tools-2.1.4 wx-1.8.3 xmerl-1.3.16 :
@@ -47,6 +54,7 @@ OTP-20.2.4 : ssh-4.6.5 # asn1-5.0.4 common_test-1.15.3 compiler-7.1.4 cosEvent-2
OTP-20.2.3 : erts-9.2.1 kernel-5.4.2 runtime_tools-1.12.4 # asn1-5.0.4 common_test-1.15.3 compiler-7.1.4 cosEvent-2.2.2 cosEventDomain-1.2.2 cosFileTransfer-1.2.2 cosNotification-1.2.3 cosProperty-1.2.3 cosTime-1.2.3 cosTransactions-1.3.3 crypto-4.2 debugger-4.2.4 dialyzer-3.2.3 diameter-2.1.3 edoc-0.9.2 eldap-1.2.3 erl_docgen-0.7.2 erl_interface-3.10.1 et-1.6.1 eunit-2.3.5 hipe-3.17 ic-4.4.3 inets-6.4.5 jinterface-1.8.1 megaco-3.18.3 mnesia-4.15.3 observer-2.6 odbc-2.12.1 orber-3.8.4 os_mon-2.4.4 otp_mibs-1.1.2 parsetools-2.1.6 public_key-1.5.2 reltool-0.7.5 sasl-3.1.1 snmp-5.2.9 ssh-4.6.4 ssl-8.2.3 stdlib-3.4.3 syntax_tools-2.1.4 tools-2.11.1 wx-1.8.3 xmerl-1.3.16 :
OTP-20.2.2 : mnesia-4.15.3 # asn1-5.0.4 common_test-1.15.3 compiler-7.1.4 cosEvent-2.2.2 cosEventDomain-1.2.2 cosFileTransfer-1.2.2 cosNotification-1.2.3 cosProperty-1.2.3 cosTime-1.2.3 cosTransactions-1.3.3 crypto-4.2 debugger-4.2.4 dialyzer-3.2.3 diameter-2.1.3 edoc-0.9.2 eldap-1.2.3 erl_docgen-0.7.2 erl_interface-3.10.1 erts-9.2 et-1.6.1 eunit-2.3.5 hipe-3.17 ic-4.4.3 inets-6.4.5 jinterface-1.8.1 kernel-5.4.1 megaco-3.18.3 observer-2.6 odbc-2.12.1 orber-3.8.4 os_mon-2.4.4 otp_mibs-1.1.2 parsetools-2.1.6 public_key-1.5.2 reltool-0.7.5 runtime_tools-1.12.3 sasl-3.1.1 snmp-5.2.9 ssh-4.6.4 ssl-8.2.3 stdlib-3.4.3 syntax_tools-2.1.4 tools-2.11.1 wx-1.8.3 xmerl-1.3.16 :
OTP-20.2.1 : ssh-4.6.4 # asn1-5.0.4 common_test-1.15.3 compiler-7.1.4 cosEvent-2.2.2 cosEventDomain-1.2.2 cosFileTransfer-1.2.2 cosNotification-1.2.3 cosProperty-1.2.3 cosTime-1.2.3 cosTransactions-1.3.3 crypto-4.2 debugger-4.2.4 dialyzer-3.2.3 diameter-2.1.3 edoc-0.9.2 eldap-1.2.3 erl_docgen-0.7.2 erl_interface-3.10.1 erts-9.2 et-1.6.1 eunit-2.3.5 hipe-3.17 ic-4.4.3 inets-6.4.5 jinterface-1.8.1 kernel-5.4.1 megaco-3.18.3 mnesia-4.15.2 observer-2.6 odbc-2.12.1 orber-3.8.4 os_mon-2.4.4 otp_mibs-1.1.2 parsetools-2.1.6 public_key-1.5.2 reltool-0.7.5 runtime_tools-1.12.3 sasl-3.1.1 snmp-5.2.9 ssl-8.2.3 stdlib-3.4.3 syntax_tools-2.1.4 tools-2.11.1 wx-1.8.3 xmerl-1.3.16 :
+OTP-20.2.0.1 : erts-9.2.0.1 # asn1-5.0.4 common_test-1.15.3 compiler-7.1.4 cosEvent-2.2.2 cosEventDomain-1.2.2 cosFileTransfer-1.2.2 cosNotification-1.2.3 cosProperty-1.2.3 cosTime-1.2.3 cosTransactions-1.3.3 crypto-4.2 debugger-4.2.4 dialyzer-3.2.3 diameter-2.1.3 edoc-0.9.2 eldap-1.2.3 erl_docgen-0.7.2 erl_interface-3.10.1 et-1.6.1 eunit-2.3.5 hipe-3.17 ic-4.4.3 inets-6.4.5 jinterface-1.8.1 kernel-5.4.1 megaco-3.18.3 mnesia-4.15.2 observer-2.6 odbc-2.12.1 orber-3.8.4 os_mon-2.4.4 otp_mibs-1.1.2 parsetools-2.1.6 public_key-1.5.2 reltool-0.7.5 runtime_tools-1.12.3 sasl-3.1.1 snmp-5.2.9 ssh-4.6.3 ssl-8.2.3 stdlib-3.4.3 syntax_tools-2.1.4 tools-2.11.1 wx-1.8.3 xmerl-1.3.16 :
OTP-20.2 : asn1-5.0.4 common_test-1.15.3 compiler-7.1.4 cosEvent-2.2.2 cosEventDomain-1.2.2 cosFileTransfer-1.2.2 cosNotification-1.2.3 cosProperty-1.2.3 cosTime-1.2.3 cosTransactions-1.3.3 crypto-4.2 debugger-4.2.4 dialyzer-3.2.3 diameter-2.1.3 edoc-0.9.2 eldap-1.2.3 erl_docgen-0.7.2 erl_interface-3.10.1 erts-9.2 eunit-2.3.5 hipe-3.17 ic-4.4.3 inets-6.4.5 jinterface-1.8.1 kernel-5.4.1 megaco-3.18.3 mnesia-4.15.2 observer-2.6 odbc-2.12.1 orber-3.8.4 os_mon-2.4.4 otp_mibs-1.1.2 parsetools-2.1.6 public_key-1.5.2 runtime_tools-1.12.3 sasl-3.1.1 snmp-5.2.9 ssh-4.6.3 ssl-8.2.3 stdlib-3.4.3 syntax_tools-2.1.4 tools-2.11.1 wx-1.8.3 xmerl-1.3.16 # et-1.6.1 reltool-0.7.5 :
OTP-20.1.7.1 : kernel-5.4.0.1 # asn1-5.0.3 common_test-1.15.2 compiler-7.1.3 cosEvent-2.2.1 cosEventDomain-1.2.1 cosFileTransfer-1.2.1 cosNotification-1.2.2 cosProperty-1.2.2 cosTime-1.2.2 cosTransactions-1.3.2 crypto-4.1 debugger-4.2.3 dialyzer-3.2.2 diameter-2.1.2 edoc-0.9.1 eldap-1.2.2 erl_docgen-0.7.1 erl_interface-3.10 erts-9.1.5 et-1.6.1 eunit-2.3.4 hipe-3.16.1 ic-4.4.2 inets-6.4.4 jinterface-1.8 megaco-3.18.2 mnesia-4.15.1 observer-2.5 odbc-2.12 orber-3.8.3 os_mon-2.4.3 otp_mibs-1.1.1 parsetools-2.1.5 public_key-1.5.1 reltool-0.7.5 runtime_tools-1.12.2 sasl-3.1 snmp-5.2.8 ssh-4.6.2 ssl-8.2.2 stdlib-3.4.2 syntax_tools-2.1.3 tools-2.11 wx-1.8.2 xmerl-1.3.15 :
OTP-20.1.7 : public_key-1.5.1 ssl-8.2.2 # asn1-5.0.3 common_test-1.15.2 compiler-7.1.3 cosEvent-2.2.1 cosEventDomain-1.2.1 cosFileTransfer-1.2.1 cosNotification-1.2.2 cosProperty-1.2.2 cosTime-1.2.2 cosTransactions-1.3.2 crypto-4.1 debugger-4.2.3 dialyzer-3.2.2 diameter-2.1.2 edoc-0.9.1 eldap-1.2.2 erl_docgen-0.7.1 erl_interface-3.10 erts-9.1.5 et-1.6.1 eunit-2.3.4 hipe-3.16.1 ic-4.4.2 inets-6.4.4 jinterface-1.8 kernel-5.4 megaco-3.18.2 mnesia-4.15.1 observer-2.5 odbc-2.12 orber-3.8.3 os_mon-2.4.3 otp_mibs-1.1.1 parsetools-2.1.5 reltool-0.7.5 runtime_tools-1.12.2 sasl-3.1 snmp-5.2.8 ssh-4.6.2 stdlib-3.4.2 syntax_tools-2.1.3 tools-2.11 wx-1.8.2 xmerl-1.3.15 :
@@ -63,6 +71,7 @@ OTP-20.0.3 : asn1-5.0.2 compiler-7.1.1 erts-9.0.3 ssh-4.5.1 # common_test-1.15.1
OTP-20.0.2 : asn1-5.0.1 erts-9.0.2 kernel-5.3.1 # common_test-1.15.1 compiler-7.1 cosEvent-2.2.1 cosEventDomain-1.2.1 cosFileTransfer-1.2.1 cosNotification-1.2.2 cosProperty-1.2.2 cosTime-1.2.2 cosTransactions-1.3.2 crypto-4.0 debugger-4.2.2 dialyzer-3.2 diameter-2.0 edoc-0.9 eldap-1.2.2 erl_docgen-0.7 erl_interface-3.10 et-1.6 eunit-2.3.3 hipe-3.16 ic-4.4.2 inets-6.4 jinterface-1.8 megaco-3.18.2 mnesia-4.15 observer-2.4 odbc-2.12 orber-3.8.3 os_mon-2.4.2 otp_mibs-1.1.1 parsetools-2.1.5 public_key-1.4.1 reltool-0.7.4 runtime_tools-1.12.1 sasl-3.0.4 snmp-5.2.6 ssh-4.5 ssl-8.2 stdlib-3.4.1 syntax_tools-2.1.2 tools-2.10.1 wx-1.8.1 xmerl-1.3.15 :
OTP-20.0.1 : common_test-1.15.1 erts-9.0.1 runtime_tools-1.12.1 stdlib-3.4.1 tools-2.10.1 # asn1-5.0 compiler-7.1 cosEvent-2.2.1 cosEventDomain-1.2.1 cosFileTransfer-1.2.1 cosNotification-1.2.2 cosProperty-1.2.2 cosTime-1.2.2 cosTransactions-1.3.2 crypto-4.0 debugger-4.2.2 dialyzer-3.2 diameter-2.0 edoc-0.9 eldap-1.2.2 erl_docgen-0.7 erl_interface-3.10 et-1.6 eunit-2.3.3 hipe-3.16 ic-4.4.2 inets-6.4 jinterface-1.8 kernel-5.3 megaco-3.18.2 mnesia-4.15 observer-2.4 odbc-2.12 orber-3.8.3 os_mon-2.4.2 otp_mibs-1.1.1 parsetools-2.1.5 public_key-1.4.1 reltool-0.7.4 sasl-3.0.4 snmp-5.2.6 ssh-4.5 ssl-8.2 syntax_tools-2.1.2 wx-1.8.1 xmerl-1.3.15 :
OTP-20.0 : asn1-5.0 common_test-1.15 compiler-7.1 cosProperty-1.2.2 crypto-4.0 debugger-4.2.2 dialyzer-3.2 diameter-2.0 edoc-0.9 erl_docgen-0.7 erl_interface-3.10 erts-9.0 eunit-2.3.3 hipe-3.16 inets-6.4 jinterface-1.8 kernel-5.3 megaco-3.18.2 mnesia-4.15 observer-2.4 orber-3.8.3 parsetools-2.1.5 public_key-1.4.1 reltool-0.7.4 runtime_tools-1.12 sasl-3.0.4 snmp-5.2.6 ssh-4.5 ssl-8.2 stdlib-3.4 syntax_tools-2.1.2 tools-2.10 wx-1.8.1 xmerl-1.3.15 # cosEvent-2.2.1 cosEventDomain-1.2.1 cosFileTransfer-1.2.1 cosNotification-1.2.2 cosTime-1.2.2 cosTransactions-1.3.2 eldap-1.2.2 et-1.6 ic-4.4.2 odbc-2.12 os_mon-2.4.2 otp_mibs-1.1.1 :
+OTP-19.3.6.13 : erts-8.3.5.7 # asn1-4.0.4 common_test-1.14 compiler-7.0.4.1 cosEvent-2.2.1 cosEventDomain-1.2.1 cosFileTransfer-1.2.1 cosNotification-1.2.2 cosProperty-1.2.1 cosTime-1.2.2 cosTransactions-1.3.2 crypto-3.7.4 debugger-4.2.1 dialyzer-3.1.1 diameter-1.12.2 edoc-0.8.1 eldap-1.2.2.1 erl_docgen-0.6.1 erl_interface-3.9.3 et-1.6 eunit-2.3.2 gs-1.6.2 hipe-3.15.4 ic-4.4.2 inets-6.3.9 jinterface-1.7.1 kernel-5.2.0.1 megaco-3.18.1 mnesia-4.14.3.1 observer-2.3.1 odbc-2.12 orber-3.8.2 os_mon-2.4.2 otp_mibs-1.1.1 parsetools-2.1.4 percept-0.9 public_key-1.4 reltool-0.7.3 runtime_tools-1.11.1 sasl-3.0.3 snmp-5.2.5 ssh-4.4.2.4 ssl-8.1.3.1.1 stdlib-3.3 syntax_tools-2.1.1.1 tools-2.9.1 typer-0.9.12 wx-1.8 xmerl-1.3.14 :
OTP-19.3.6.12 : eldap-1.2.2.1 # asn1-4.0.4 common_test-1.14 compiler-7.0.4.1 cosEvent-2.2.1 cosEventDomain-1.2.1 cosFileTransfer-1.2.1 cosNotification-1.2.2 cosProperty-1.2.1 cosTime-1.2.2 cosTransactions-1.3.2 crypto-3.7.4 debugger-4.2.1 dialyzer-3.1.1 diameter-1.12.2 edoc-0.8.1 erl_docgen-0.6.1 erl_interface-3.9.3 erts-8.3.5.6 et-1.6 eunit-2.3.2 gs-1.6.2 hipe-3.15.4 ic-4.4.2 inets-6.3.9 jinterface-1.7.1 kernel-5.2.0.1 megaco-3.18.1 mnesia-4.14.3.1 observer-2.3.1 odbc-2.12 orber-3.8.2 os_mon-2.4.2 otp_mibs-1.1.1 parsetools-2.1.4 percept-0.9 public_key-1.4 reltool-0.7.3 runtime_tools-1.11.1 sasl-3.0.3 snmp-5.2.5 ssh-4.4.2.4 ssl-8.1.3.1.1 stdlib-3.3 syntax_tools-2.1.1.1 tools-2.9.1 typer-0.9.12 wx-1.8 xmerl-1.3.14 :
OTP-19.3.6.11 : erts-8.3.5.6 # asn1-4.0.4 common_test-1.14 compiler-7.0.4.1 cosEvent-2.2.1 cosEventDomain-1.2.1 cosFileTransfer-1.2.1 cosNotification-1.2.2 cosProperty-1.2.1 cosTime-1.2.2 cosTransactions-1.3.2 crypto-3.7.4 debugger-4.2.1 dialyzer-3.1.1 diameter-1.12.2 edoc-0.8.1 eldap-1.2.2 erl_docgen-0.6.1 erl_interface-3.9.3 et-1.6 eunit-2.3.2 gs-1.6.2 hipe-3.15.4 ic-4.4.2 inets-6.3.9 jinterface-1.7.1 kernel-5.2.0.1 megaco-3.18.1 mnesia-4.14.3.1 observer-2.3.1 odbc-2.12 orber-3.8.2 os_mon-2.4.2 otp_mibs-1.1.1 parsetools-2.1.4 percept-0.9 public_key-1.4 reltool-0.7.3 runtime_tools-1.11.1 sasl-3.0.3 snmp-5.2.5 ssh-4.4.2.4 ssl-8.1.3.1.1 stdlib-3.3 syntax_tools-2.1.1.1 tools-2.9.1 typer-0.9.12 wx-1.8 xmerl-1.3.14 :
OTP-19.3.6.10 : erts-8.3.5.5 syntax_tools-2.1.1.1 # asn1-4.0.4 common_test-1.14 compiler-7.0.4.1 cosEvent-2.2.1 cosEventDomain-1.2.1 cosFileTransfer-1.2.1 cosNotification-1.2.2 cosProperty-1.2.1 cosTime-1.2.2 cosTransactions-1.3.2 crypto-3.7.4 debugger-4.2.1 dialyzer-3.1.1 diameter-1.12.2 edoc-0.8.1 eldap-1.2.2 erl_docgen-0.6.1 erl_interface-3.9.3 et-1.6 eunit-2.3.2 gs-1.6.2 hipe-3.15.4 ic-4.4.2 inets-6.3.9 jinterface-1.7.1 kernel-5.2.0.1 megaco-3.18.1 mnesia-4.14.3.1 observer-2.3.1 odbc-2.12 orber-3.8.2 os_mon-2.4.2 otp_mibs-1.1.1 parsetools-2.1.4 percept-0.9 public_key-1.4 reltool-0.7.3 runtime_tools-1.11.1 sasl-3.0.3 snmp-5.2.5 ssh-4.4.2.4 ssl-8.1.3.1.1 stdlib-3.3 tools-2.9.1 typer-0.9.12 wx-1.8 xmerl-1.3.14 :
diff --git a/scripts/diffable b/scripts/diffable
index 08d2d5cb35..d2208d9d89 100755
--- a/scripts/diffable
+++ b/scripts/diffable
@@ -3,56 +3,85 @@
-mode(compile).
main(Args0) ->
- {Args,Opts} = opts(Args0, #{format=>asm,no_compile=>false}),
+ DefOpts = #{format=>asm,no_compile=>false,legacy=>false},
+ {Args,Opts} = opts(Args0, DefOpts),
case Args of
[OutDir] ->
do_compile(OutDir, Opts);
_ ->
- usage(),
- halt(1)
+ usage()
end.
usage() ->
- S = "usage: otp-diffable-asm [OPTION] DIRECTORY\n\n"
- "Options:\n"
- " --asm Output to .S files (default)\n"
- " --dis Output to .dis files\n"
- " --no-compile Disassemble from BEAM files (use with --dis)\n"
- "\n"
- "DESCRIPTION\n"
- "\n"
- "Compile some applications from OTP (more than 700 modules) to either\n"
- ".S files or .dis files. The files are massaged to make them diff-friendly.\n"
- "\n"
- "EXAMPLES\n"
- "\n"
- "This example shows how the effectiveness of a compiler \n"
- "optimization can be verified (alternatively, that pure code\n"
- "refactoring has no effect on the generated code):\n"
- "\n"
- "$ scripts/diffable old\n"
- "# Hack the compiler.\n"
- "$ scripts/diffable new\n"
- "$ diff -u old new\n"
- "\n"
- "This example shows how the effectiveness of loader hacks\n"
- "can be verified:\n"
- "\n"
- "$ scripts/diffable --dis --no-compile old\n"
- "# Hack ops.tab and/or one of the *instr.tab files.\n"
- "$ scripts/diffable --dis --no-compile new\n"
- "$ diff -u old new\n",
- io:put_chars(S).
-
-opts(["--asm"|Args], Opts) ->
- opts(Args, Opts#{format:=asm});
-opts(["--dis"|Args], Opts) ->
- opts(Args, Opts#{format:=dis});
-opts(["--no-compile"|Args], Opts) ->
- opts(Args, Opts#{format:=dis,no_compile:=true});
+ S = ["usage: otp-diffable-asm [OPTION] DIRECTORY\n\n"
+ "Options:\n"
+ " --asm Output to .S files (default)\n"
+ " --legacy-asm Output to legacy .S files\n"
+ " --dis Output to .dis files\n"
+ " --no-compile Disassemble from BEAM files (use with --dis)\n"
+ "\n"
+ "DESCRIPTION\n"
+ "\n"
+ "Compile some applications from OTP (about 1000 modules) to either\n"
+ ".S files or .dis files. The files are massaged to make them diff-friendly.\n"
+ "\n"
+ "The --legacy-asm options forces the output file to be in Latin1 encoding\n"
+ "and adds a latin1 encoding comment to the first line of the file.\n"
+ "\n"
+ "INCLUDING THE ELIXIR STANDARD LIBRARY\n"
+ "\n"
+ "If the Elixir repository is installed alongside the Erlang/OTP repository,\n"
+ "the Elixir standard library will be included in the compilation. For this\n"
+ "to work, the Elixir repository must be installed in: \n",
+ "\n"
+ " ",elixir_root(),"\n"
+ "\n"
+ "Here is how to install Elixir:\n"
+ "\n"
+ " cd ",filename:dirname(elixir_root()),"\n"
+ " git clone [email protected]:elixir-lang/elixir.git\n"
+ " cd elixir\n"
+ " PATH=",code:root_dir(),"/bin:$PATH make clean test\n"
+ "\n"
+ "EXAMPLES\n"
+ "\n"
+ "This example shows how the effectiveness of a compiler \n"
+ "optimization can be verified (alternatively, that pure code\n"
+ "refactoring has no effect on the generated code):\n"
+ "\n"
+ "$ scripts/diffable old\n"
+ "# Hack the compiler.\n"
+ "$ scripts/diffable new\n"
+ "$ diff -u old new\n"
+ "\n"
+ "This example shows how the effectiveness of loader hacks\n"
+ "can be verified:\n"
+ "\n"
+ "$ scripts/diffable --dis --no-compile old\n"
+ "# Hack ops.tab and/or one of the *instr.tab files.\n"
+ "$ scripts/diffable --dis --no-compile new\n"
+ "$ diff -u old new\n"],
+ io:put_chars(S),
+ halt(1).
+
+opts(["--"++Opt|Args], Opts0) ->
+ Opts = opt(Opt, Opts0),
+ opts(Args, Opts);
opts(Args, Opts) ->
{Args,Opts}.
+opt("asm", Opts) ->
+ Opts#{format:=asm};
+opt("dis", Opts) ->
+ Opts#{format:=dis};
+opt("legacy-asm", Opts) ->
+ Opts#{format:=asm,legacy:=true};
+opt("no-compile", Opts) ->
+ Opts#{format:=dis,no_compile:=true};
+opt(Opt, _Opts) ->
+ io:format("Uknown option: --~ts\n\n", [Opt]),
+ usage().
+
do_compile(OutDir, Opts0) ->
Opts1 = Opts0#{outdir=>OutDir},
_ = filelib:ensure_dir(filename:join(OutDir, "dummy")),
@@ -93,17 +122,26 @@ compile_file(CF, File) ->
CF(File)
catch
Class:Error:Stk ->
- io:format("~s: ~p ~p\n~p\n",
- [File,Class,Error,Stk]),
+ if
+ is_list(File) ->
+ io:format("~s: ~p ~p\n~p\n\n",
+ [File,Class,Error,Stk]);
+ true ->
+ io:format("~p: ~p ~p\n~p\n\n",
+ [File,Class,Error,Stk])
+ end,
error
end.
+elixir_root() ->
+ filename:join(filename:dirname(code:root_dir()), "elixir").
+
%%%
%%% Get names of files (either .erl files or BEAM files).
%%%
get_files(Apps, #{format:=dis,no_compile:=true}=Opts) ->
- Files = get_beams(Apps),
+ Files = get_elixir_beams() ++ get_beams(Apps),
{Files,Opts};
get_files(Apps, #{}=Opts) ->
Inc = make_includes(),
@@ -113,20 +151,40 @@ get_files(Apps, #{}=Opts) ->
{d,'COMPILER_VSN',1},
{d,erlang_daemon_port,1337}|Inc],
Files0 = get_src(Apps),
- Files = add_opts(Files0, CompilerOpts),
+ Files1 = add_opts(Files0, CompilerOpts),
+ Files = [{Beam,elixir} || Beam <- get_elixir_beams()] ++ Files1,
{Files,Opts}.
+get_elixir_beams() ->
+ ElixirEbin = filename:join(elixir_root(), "lib/elixir/ebin"),
+ case filelib:is_dir(ElixirEbin) of
+ true ->
+ true = code:add_patha(ElixirEbin),
+ filelib:wildcard(filename:join(ElixirEbin, "*.beam"));
+ false ->
+ []
+ end.
+
add_opts([F|Fs], Opts0) ->
- Opts = case filename:basename(F) of
- "group_history.erl" ->
+ Opts = case vsn_is_harmful(F) of
+ true ->
Opts0 -- [{d,'VSN',1}];
- _ ->
+ false ->
Opts0
end,
[{F,Opts}|add_opts(Fs, Opts0)];
add_opts([], _Opts) ->
[].
+vsn_is_harmful(F) ->
+ case filename:basename(F) of
+ "group_history.erl" ->
+ true;
+ _ ->
+ App = filename:basename(filename:dirname(filename:dirname(F))),
+ App =:= "ssl"
+ end.
+
get_src(["preloaded"|Apps]) ->
WC = filename:join(code:root_dir(), "erts/preloaded/src/*.erl"),
filelib:wildcard(WC) ++ get_src(Apps);
@@ -188,109 +246,48 @@ get_beams([]) -> [].
%%% Generate renumbered .S files.
%%%
-compile_to_asm_fun(#{outdir:=OutDir}) ->
+compile_to_asm_fun(#{outdir:=OutDir}=Opts) ->
fun(File) ->
- compile_to_asm(File, OutDir)
+ Legacy = map_get(legacy, Opts),
+ compile_to_asm(File, OutDir, Legacy)
end.
-compile_to_asm({File,Opts}, OutDir) ->
- case compile:file(File, [to_asm,binary,report_errors|Opts]) of
+compile_to_asm({Beam,elixir}, OutDir, _Legacy) ->
+ Abst = get_abstract_from_beam(Beam),
+ Source = filename:rootname(Beam, ".beam"),
+ Opts = [diffable,{outdir,OutDir},report_errors,{source,Source}],
+ case compile:forms(Abst, Opts) of
+ {ok,_Mod,_Asm} ->
+ ok;
error ->
- error;
- {ok,Mod,Asm0} ->
- {ok,Asm1} = beam_a:module(Asm0, []),
- Asm2 = renumber_asm(Asm1),
- {ok,Asm} = beam_z:module(Asm2, []),
- print_asm(Mod, OutDir, Asm)
+ error
+ end;
+compile_to_asm({File,Opts}, OutDir, Legacy) ->
+ case compile:file(File, [diffable,{outdir,OutDir},report_errors|Opts]) of
+ {ok,_Mod} ->
+ case Legacy of
+ true ->
+ legacy_asm(OutDir, File);
+ false ->
+ ok
+ end;
+ error ->
+ error
end.
-print_asm(Mod, OutDir, Asm) ->
- S = atom_to_list(Mod) ++ ".S",
- Name = filename:join(OutDir, S),
- {ok,Fd} = file:open(Name, [write,raw,delayed_write]),
- ok = beam_listing(Fd, Asm),
- ok = file:close(Fd).
-
-renumber_asm({Mod,Exp,Attr,Fs0,NumLabels}) ->
- EntryLabels = maps:from_list(entry_labels(Fs0)),
- Fs = [fix_func(F, EntryLabels) || F <- Fs0],
- {Mod,Exp,Attr,Fs,NumLabels}.
-
-entry_labels(Fs) ->
- [{Entry,{Name,Arity}} || {function,Name,Arity,Entry,_} <- Fs].
-
-fix_func({function,Name,Arity,Entry0,Is0}, LabelMap0) ->
- Entry = maps:get(Entry0, LabelMap0),
- LabelMap = label_map(Is0, 1, LabelMap0),
- Is = replace(Is0, [], LabelMap),
- {function,Name,Arity,Entry,Is}.
+legacy_asm(OutDir, Source) ->
+ ModName = filename:rootname(filename:basename(Source)),
+ File = filename:join(OutDir, ModName),
+ AsmFile = File ++ ".S",
+ {ok,Asm0} = file:read_file(AsmFile),
+ Asm1 = unicode:characters_to_binary(Asm0, utf8, latin1),
+ Asm = [<<"%% -*- encoding:latin-1 -*-\n">>|Asm1],
+ ok = file:write_file(AsmFile, Asm).
-label_map([{label,Old}|Is], New, Map) ->
- case maps:is_key(Old, Map) of
- false ->
- label_map(Is, New+1, Map#{Old=>New});
- true ->
- label_map(Is, New, Map)
- end;
-label_map([_|Is], New, Map) ->
- label_map(Is, New, Map);
-label_map([], _New, Map) ->
- Map.
-
-replace([{label,Lbl}|Is], Acc, D) ->
- replace(Is, [{label,label(Lbl, D)}|Acc], D);
-replace([{test,Test,{f,Lbl},Ops}|Is], Acc, D) ->
- replace(Is, [{test,Test,{f,label(Lbl, D)},Ops}|Acc], D);
-replace([{test,Test,{f,Lbl},Live,Ops,Dst}|Is], Acc, D) ->
- replace(Is, [{test,Test,{f,label(Lbl, D)},Live,Ops,Dst}|Acc], D);
-replace([{select,I,R,{f,Fail0},Vls0}|Is], Acc, D) ->
- Vls = lists:map(fun ({f,L}) -> {f,label(L, D)};
- (Other) -> Other
- end, Vls0),
- Fail = label(Fail0, D),
- replace(Is, [{select,I,R,{f,Fail},Vls}|Acc], D);
-replace([{'try',R,{f,Lbl}}|Is], Acc, D) ->
- replace(Is, [{'try',R,{f,label(Lbl, D)}}|Acc], D);
-replace([{'catch',R,{f,Lbl}}|Is], Acc, D) ->
- replace(Is, [{'catch',R,{f,label(Lbl, D)}}|Acc], D);
-replace([{jump,{f,Lbl}}|Is], Acc, D) ->
- replace(Is, [{jump,{f,label(Lbl, D)}}|Acc], D);
-replace([{loop_rec,{f,Lbl},R}|Is], Acc, D) ->
- replace(Is, [{loop_rec,{f,label(Lbl, D)},R}|Acc], D);
-replace([{loop_rec_end,{f,Lbl}}|Is], Acc, D) ->
- replace(Is, [{loop_rec_end,{f,label(Lbl, D)}}|Acc], D);
-replace([{wait,{f,Lbl}}|Is], Acc, D) ->
- replace(Is, [{wait,{f,label(Lbl, D)}}|Acc], D);
-replace([{wait_timeout,{f,Lbl},To}|Is], Acc, D) ->
- replace(Is, [{wait_timeout,{f,label(Lbl, D)},To}|Acc], D);
-replace([{bif,Name,{f,Lbl},As,R}|Is], Acc, D) when Lbl =/= 0 ->
- replace(Is, [{bif,Name,{f,label(Lbl, D)},As,R}|Acc], D);
-replace([{gc_bif,Name,{f,Lbl},Live,As,R}|Is], Acc, D) when Lbl =/= 0 ->
- replace(Is, [{gc_bif,Name,{f,label(Lbl, D)},Live,As,R}|Acc], D);
-replace([{call,Ar,{f,Lbl}}|Is], Acc, D) ->
- replace(Is, [{call,Ar,{f,label(Lbl,D)}}|Acc], D);
-replace([{make_fun2,{f,Lbl},U1,U2,U3}|Is], Acc, D) ->
- replace(Is, [{make_fun2,{f,label(Lbl, D)},U1,U2,U3}|Acc], D);
-replace([{bs_init,{f,Lbl},Info,Live,Ss,Dst}|Is], Acc, D) when Lbl =/= 0 ->
- replace(Is, [{bs_init,{f,label(Lbl, D)},Info,Live,Ss,Dst}|Acc], D);
-replace([{bs_put,{f,Lbl},Info,Ss}|Is], Acc, D) when Lbl =/= 0 ->
- replace(Is, [{bs_put,{f,label(Lbl, D)},Info,Ss}|Acc], D);
-replace([{put_map=I,{f,Lbl},Op,Src,Dst,Live,List}|Is], Acc, D)
- when Lbl =/= 0 ->
- replace(Is, [{I,{f,label(Lbl, D)},Op,Src,Dst,Live,List}|Acc], D);
-replace([{get_map_elements=I,{f,Lbl},Src,List}|Is], Acc, D) when Lbl =/= 0 ->
- replace(Is, [{I,{f,label(Lbl, D)},Src,List}|Acc], D);
-replace([{recv_mark=I,{f,Lbl}}|Is], Acc, D) ->
- replace(Is, [{I,{f,label(Lbl, D)}}|Acc], D);
-replace([{recv_set=I,{f,Lbl}}|Is], Acc, D) ->
- replace(Is, [{I,{f,label(Lbl, D)}}|Acc], D);
-replace([I|Is], Acc, D) ->
- replace(Is, [I|Acc], D);
-replace([], Acc, _) ->
- lists:reverse(Acc).
-
-label(Old, D) when is_integer(Old) ->
- maps:get(Old, D).
+get_abstract_from_beam(Beam) ->
+ {ok,{_Mod,[AbstChunk]}} = beam_lib:chunks(Beam, [abstract_code]),
+ {abstract_code,{raw_abstract_v1,Abst}} = AbstChunk,
+ Abst.
%%%
%%% Compile and disassemble the loaded code.
@@ -305,6 +302,15 @@ compile_to_dis_fun(#{outdir:=OutDir,no_compile:=true}) ->
dis_only(File, OutDir)
end.
+compile_to_dis({File,elixir}, OutDir) ->
+ {ok,Beam} = file:read_file(File),
+ Mod0 = filename:rootname(filename:basename(File)),
+ Mod = list_to_atom(Mod0),
+ Dis0 = disasm(Mod, Beam),
+ Dis1 = renumber_disasm(Dis0, Mod, Mod),
+ Dis = format_disasm(Dis1),
+ OutFile = filename:join(OutDir, atom_to_list(Mod)++".dis"),
+ ok = file:write_file(OutFile, Dis);
compile_to_dis({File,Opts}, OutDir) ->
case compile:file(File, [to_asm,binary,report_errors|Opts]) of
error ->
@@ -598,28 +604,3 @@ p_run_loop(Test, List, N, Refs0, Errors0) ->
Refs = Refs0 -- [Ref],
p_run_loop(Test, List, N, Refs, Errors)
end.
-
-%%%
-%%% Borrowed from beam_listing and tweaked.
-%%%
-
-beam_listing(Stream, {Mod,Exp,Attr,Code,NumLabels}) ->
- Head = ["%% -*- encoding:latin-1 -*-\n",
- io_lib:format("{module, ~p}. %% version = ~w\n",
- [Mod, beam_opcodes:format_number()]),
- io_lib:format("\n{exports, ~p}.\n", [Exp]),
- io_lib:format("\n{attributes, ~p}.\n", [Attr]),
- io_lib:format("\n{labels, ~p}.\n", [NumLabels])],
- ok = file:write(Stream, Head),
- lists:foreach(
- fun ({function,Name,Arity,Entry,Asm}) ->
- S = [io_lib:format("\n\n{function, ~w, ~w, ~w}.\n",
- [Name,Arity,Entry])|format_asm(Asm)],
- ok = file:write(Stream, S)
- end, Code).
-
-format_asm([{label,_}=I|Is]) ->
- [io_lib:format(" ~p", [I]),".\n"|format_asm(Is)];
-format_asm([I|Is]) ->
- [io_lib:format(" ~p", [I]),".\n"|format_asm(Is)];
-format_asm([]) -> [].
diff --git a/system/doc/Makefile b/system/doc/Makefile
index 0c4adf6554..8f2faeee04 100644
--- a/system/doc/Makefile
+++ b/system/doc/Makefile
@@ -24,6 +24,7 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
#
SUB_DIRECTORIES = design_principles \
+ general_info \
getting_started \
system_architecture_intro \
embedded \
diff --git a/system/doc/design_principles/applications.xml b/system/doc/design_principles/applications.xml
index 3b7b8fdaee..8f1969ff29 100644
--- a/system/doc/design_principles/applications.xml
+++ b/system/doc/design_principles/applications.xml
@@ -185,7 +185,7 @@ ch_app:stop([])</code>
the directory with the highest version number, if more than one
version of an application is present.</p>
<section>
- <title>Directory Structure guidelines for a Development Environment</title>
+ <title>Directory Structure Guidelines for a Development Environment</title>
<p>Any directory structure for development will suffice as long as the released directory structure
adhere to the <seealso marker="#app_dir_released">description below</seealso>,
but it is encouraged that the same directory structure
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
index 16a901b3a5..07b4284cb8 100644
--- a/system/doc/design_principles/statem.xml
+++ b/system/doc/design_principles/statem.xml
@@ -44,28 +44,40 @@
<title>Event-Driven State Machines</title>
<p>
Established Automata Theory does not deal much with
- how a state transition is triggered,
+ how a <em>state transition</em> is triggered,
but assumes that the output is a function
of the input (and the state) and that they are
some kind of values.
</p>
<p>
For an Event-Driven State Machine, the input is an event
- that triggers a state transition and the output
- is actions executed during the state transition.
+ that triggers a <em>state transition</em> and the output
+ is actions executed during the <em>state transition</em>.
It can analogously to the mathematical model of a
- Finite-State Machine be described as
+ Finite State Machine be described as
a set of relations of the following form:
</p>
<pre>
State(S) x Event(E) -> Actions(A), State(S')</pre>
- <p>These relations are interpreted as follows:
+ <p>
+ These relations are interpreted as follows:
if we are in state <c>S</c> and event <c>E</c> occurs, we
are to perform actions <c>A</c> and make a transition to
state <c>S'</c>. Notice that <c>S'</c> can be equal to <c>S</c>
and that <c>A</c> can be empty.
</p>
<p>
+ In <c>gen_statem</c> we define
+ a <em>state change</em> as a <em>state transition</em>
+ in which the new state <c>S'</c> is different from
+ the current state <c>S</c>, where "different" means
+ Erlang's strict inequality: <c>=/=</c>
+ also know as "does not match".
+ During a <em>state changes</em>,
+ <c>gen_statem</c> does more things
+ than during other <em>state transitions</em>.
+ </p>
+ <p>
As <c>A</c> and <c>S'</c> depend only on
<c>S</c> and <c>E</c>, the kind of state machine described
here is a Mealy machine
@@ -95,8 +107,8 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<list type="bulleted">
<item>
Co-located callback code for each state,
- regardless of
- <seealso marker="#Event Types">Event Type</seealso>
+ for all
+ <seealso marker="#Event Types"><em>Event Types</em></seealso>
(such as <em>call</em>, <em>cast</em> and <em>info</em>)
</item>
<item>
@@ -114,13 +126,13 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</item>
<item>
<seealso marker="#State Enter Calls">
- State Enter Calls
+ <em>State Enter Calls</em>
</seealso>
(callback on state entry co-located with the rest
of each state's callback code)
</item>
<item>
- Easy-to-use timeouts
+ Easy-to-use time-outs
(<seealso marker="#State Time-Outs">State Time-Outs</seealso>,
<seealso marker="#Event Time-Outs">Event Time-Outs</seealso>
and
@@ -152,11 +164,11 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<marker id="Callback Module" />
<title>Callback Module</title>
<p>
- The callback module contains functions that implement
+ The <em>callback module</em> contains functions that implement
the state machine.
When an event occurs,
the <c>gen_statem</c> behaviour engine
- calls a function in the callback module with the event,
+ calls a function in the <em>callback module</em> with the event,
current state and server data.
This function performs the actions for this event,
and returns the new state and server data
@@ -166,7 +178,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
The behaviour engine holds the state machine state,
server data, timer references, a queue of posponed messages
and other metadata. It receives all process messages,
- handles the system messages, and calls the callback module
+ handles the system messages, and calls the <em>callback module</em>
with machine specific events.
</p>
</section>
@@ -177,7 +189,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<marker id="Callback Modes" />
<title>Callback Modes</title>
<p>
- The <c>gen_statem</c> behavior supports two callback modes:
+ The <c>gen_statem</c> behavior supports two <em>callback modes</em>:
</p>
<taglist>
<tag>
@@ -202,31 +214,33 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</item>
</taglist>
<p>
- The callback mode is selected at server start
+ The <em>callback mode</em> is selected at server start
and may be changed with a code upgrade/downgrade.
</p>
<p>
See the section
- <seealso marker="#Event Handler">Event Handler</seealso>
+ <seealso marker="#State Callback"><em>State Callback</em></seealso>
that describes the event handling callback function(s).
</p>
<p>
- The callback mode is selected by implementing
+ The <em>callback mode</em> is selected by implementing
a mandatory callback function
<seealso marker="stdlib:gen_statem#Module:callback_mode/0">
<c>Module:callback_mode()</c>
</seealso>
- that returns one of the callback modes.
+ that returns one of the <em>callback modes</em>.
</p>
<p>
The
<seealso marker="stdlib:gen_statem#Module:callback_mode/0">
<c>Module:callback_mode()</c>
</seealso>
- function may also return a list containing the callback mode
+ function may also return a list containing the <em>callback mode</em>
and the atom <c>state_enter</c> in which case
- <seealso marker="#State Enter Calls">State Enter Calls</seealso>
- are activated for the callback mode.
+ <seealso marker="#State Enter Calls">
+ <em>state enter calls</em>
+ </seealso>
+ are activated for the <em>callback mode</em>.
</p>
<section>
@@ -237,11 +251,11 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
it is the one most like <c>gen_fsm</c>.
But if you do not want the restriction that the state
must be an atom, or if you do not want to write
- one event handler function per state; please read on...
+ one <em>state callback</em> function per state; please read on...
</p>
<p>
The two
- <seealso marker="#Callback Modes">Callback Modes</seealso>
+ <seealso marker="#Callback Modes"><em>callback modes</em></seealso>
give different possibilities and restrictions,
with one common goal:
to handle all possible combinations of events and states.
@@ -257,7 +271,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
With <c>state_functions</c>, you are restricted to use
atom-only states, and the <c>gen_statem</c> engine
branches depending on state name for you.
- This encourages the callback module to co-locate
+ This encourages the <em>callback module</em> to co-locate
the implementation of all event actions particular
to one state in the same place in the code,
hence to focus on one state at the time.
@@ -302,11 +316,12 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<!-- =================================================================== -->
<section>
- <marker id="Event Handler" />
- <title>Event Handler</title>
+ <marker id="State Callback" />
+ <title>State Callback</title>
<p>
- Which callback function that handles an event
- depends on the callback mode:
+ The <em>state callback</em> is the callback function
+ that handles an event in the current state,
+ and which function that is depends on the <em>callback mode</em>:
</p>
<taglist>
<tag><c>state_functions</c></tag>
@@ -329,7 +344,9 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</seealso>
<p>
See section
- <seealso marker="#One Event Handler">One Event Handler</seealso>
+ <seealso marker="#One State Callback">
+ <em>One State Callback</em>
+ </seealso>
for an example.
</p>
</item>
@@ -338,15 +355,17 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
The state is either the name of the function itself or an argument to it.
The other arguments are the <c>EventType</c> described in section
<seealso marker="#Event Types">Event Types</seealso>,
- the event dependent <c>EventContent</c>, and the current server <c>Data</c>.
+ the event dependent <c>EventContent</c>,
+ and the current server <c>Data</c>.
</p>
<p>
- State enter calls are also handled by the event handler and have
- slightly different arguments. See the section
+ <em>State enter calls</em> are also handled by the event handler
+ and have slightly different arguments. See section
<seealso marker="#State Enter Calls">State Enter Calls</seealso>.
</p>
<p>
- The event handler return values are defined in the description of
+ The <em>state callback</em> return values
+ are defined in the description of
<seealso marker="stdlib:gen_statem#Module:StateName/3">
<c>Module:StateName/3</c>
</seealso>
@@ -361,24 +380,29 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<item>
<p>
Set next state and update the server data.
- If the <c>Actions</c> field is used, execute state transition actions.
- An empty <c>Actions</c> list is equivalent to not returning the field.
+ If the <c>Actions</c> field is used,
+ execute <em>transition actions</em>.
+ An empty <c>Actions</c> list is equivalent to
+ not returning the field.
</p>
<p>
See section
- <seealso marker="#State Transition Actions">
- State Transition Actions
+ <seealso marker="#Transition Actions">
+ <em>Transition Actions</em>
</seealso>
for a list of possible
- state transition actions.
+ <em>transition actions</em>.
</p>
<p>
- If <c>NextState =/= State</c> the state machine changes
- to a new state. A
+ If <c>NextState =/= State</c> this is a <em>state change</em>
+ so the extra things <c>gen_statem</c> does are: the event queue
+ is restarted from the oldest
+ <seealso marker="#Postponing Events">postponed event</seealso>,
+ any current
+ <seealso marker="#State Time-Outs">state time-out</seealso>
+ is cancelled, and a
<seealso marker="#State Enter Calls">state enter call</seealso>
- is performed if enabled and all
- <seealso marker="#Postponing Events">postponed events</seealso>
- are retried.
+ is performed, if enabled.
</p>
</item>
<tag>
@@ -388,7 +412,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<item>
<p>
Same as the <c>next_state</c> values with
- <c>NextState =:= State</c>, that is, no state change.
+ <c>NextState =:= State</c>, that is, no <em>state change</em>.
</p>
</item>
<tag>
@@ -414,9 +438,16 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<seealso marker="#State Enter Calls">
State Enter Calls
</seealso>
- are enabled, repeat the state enter call
+ are enabled, repeat the <em>state enter call</em>
as if this state was entered again.
</p>
+ <p>
+ If these return values are used from a
+ <em>state enter call</em> the <c>OldState</c> does not change,
+ but if used from an event handling <em>state callback</em>
+ the new <em>state enter call's</em> <c>OldState</c>
+ will be the current state.
+ </p>
</item>
<tag>
<c>{stop, Reason, NewData}</c><br />
@@ -435,7 +466,10 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<item>
<p>
Same as the <c>stop</c> values, but first execute the given
- state transition actions that may only be reply actions.
+ <seealso marker="#Transition Actions">
+ <em>transition actions</em>
+ </seealso>
+ that may only be reply actions.
</p>
</item>
</taglist>
@@ -449,8 +483,8 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<c>Module:init(Args)</c>
</seealso>
callback function is called before any
- <seealso marker="#Event Handler">Event Handler</seealso>
- is called. This function behaves like an event handler
+ <seealso marker="#State Callback"><em>state callback</em></seealso>
+ is called. This function behaves like an <em>state callback</em>
function, but gets its only argument <c>Args</c> from
the <c>gen_statem</c>
<seealso marker="stdlib:gen_statem#start/3">
@@ -474,8 +508,8 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<!-- =================================================================== -->
<section>
- <marker id="State Transition Actions" />
- <title>State Transition Actions</title>
+ <marker id="Transition Actions" />
+ <title>Transition Actions</title>
<p>
In the first section
<seealso marker="#Event-Driven State Machines">
@@ -483,13 +517,13 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</seealso>
actions were mentioned as a part of
the general state machine model. These general actions
- are implemented with the code that callback module
+ are implemented with the code that <em>callback module</em>
<c>gen_statem</c> executes in an event-handling
callback function before returning
to the <c>gen_statem</c> engine.
</p>
<p>
- There are more specific state-transition actions
+ There are more specific <em>transition actions</em>
that a callback function can command the <c>gen_statem</c>
engine to do after the callback function return.
These are commanded by returning a list of
@@ -500,7 +534,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</seealso>
from the
<seealso marker="stdlib:gen_statem#Module:StateName/3">callback function</seealso>.
- These are the possible state transition actions:
+ These are the possible <em>transition actions</em>:
</p>
<taglist>
<tag>
@@ -512,7 +546,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</tag>
<item>
If set postpone the current event, see section
- <seealso marker="#Postponing Events">Postponing Events</seealso>
+ <seealso marker="#Postponing Events">Postponing Events</seealso>.
</item>
<tag>
<seealso marker="stdlib:gen_statem#type-hibernate">
@@ -523,41 +557,44 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</tag>
<item>
If set hibernate the <c>gen_statem</c>, treated in section
- <seealso marker="#Hibernation">Hibernation</seealso>
+ <seealso marker="#Hibernation">Hibernation</seealso>.
</item>
<tag>
<seealso marker="stdlib:gen_statem#type-state_timeout">
- <c>{state_timeout, Time}</c>
+ <c>{state_timeout, EventContent, Time}</c>
</seealso>
<br />
- <c>{state_timeout, Time, Opts}</c>
+ <c>{state_timeout, EventContent, Time, Opts}</c>
</tag>
<item>
- Start a state time-out, read more in section
- <seealso marker="#State Time-Outs">State Time-Outs</seealso>
+ Start a state time-out, read more in sections
+ <seealso marker="#Time-Outs">Time-Outs</seealso> and
+ <seealso marker="#State Time-Outs">State Time-Outs</seealso>.
</item>
<tag>
<seealso marker="stdlib:gen_statem#type-generic_timeout">
- <c>{{timeout, Name}, Time}</c>
+ <c>{{timeout, Name}, EventContent, Time}</c>
</seealso>
<br />
- <c>{{timeout, Name}, Time, Opts}</c>
+ <c>{{timeout, Name}, EventContent, Time, Opts}</c>
</tag>
<item>
- Start a generic time-out, read more in section
- <seealso marker="#Generic Time-Outs">Generic Time-Outs</seealso>
+ Start a generic time-out, read more in sections
+ <seealso marker="#Time-Outs">Time-Outs</seealso> and
+ <seealso marker="#Generic Time-Outs">Generic Time-Outs</seealso>.
</item>
<tag>
<seealso marker="stdlib:gen_statem#type-event_timeout">
- <c>{timeout, Time}</c>
+ <c>{timeout, EventContent, Time}</c>
</seealso>
<br />
- <c>{timeout, Time, Opts}</c><br />
+ <c>{timeout, EventContent, Time, Opts}</c><br />
<c>Time</c>
</tag>
<item>
- Start an event time-out, see more in section
- <seealso marker="#Event Time-Outs">Event Time-Outs</seealso>
+ Start an event time-out, see more in sections
+ <seealso marker="#Time-Outs">Time-Outs</seealso> and
+ <seealso marker="#Event Time-Outs">Event Time-Outs</seealso>.
</item>
<tag>
<seealso marker="stdlib:gen_statem#type-reply_action">
@@ -566,7 +603,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</tag>
<item>
Reply to a caller, mentioned at the end of section
- <seealso marker="#All State Events">All State Events</seealso>
+ <seealso marker="#All State Events">All State Events</seealso>.
</item>
<tag>
<seealso marker="stdlib:gen_statem#type-action">
@@ -575,7 +612,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</tag>
<item>
Generate the next event to handle, see section
- <seealso marker="#Inserted Events">Inserted Events</seealso>
+ <seealso marker="#Inserted Events">Inserted Events</seealso>.
</item>
</taglist>
<p>
@@ -596,13 +633,13 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<title>Event Types</title>
<p>
Events are categorized in different
- <seealso marker="stdlib:gen_statem#type-event_type">event types</seealso>.
+ <seealso marker="stdlib:gen_statem#type-event_type"><em>event types</em></seealso>.
Events of all types are for a given state
handled in the same callback function, and that function gets
<c>EventType</c> and <c>EventContent</c> as arguments.
</p>
<p>
- The following is a complete list of event types and where
+ The following is a complete list of <em>event types</em> and where
they come from:
</p>
<taglist>
@@ -624,7 +661,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
Generated by
<seealso marker="stdlib:gen_statem#call/2"><c>gen_statem:call</c></seealso>,
where <c>From</c> is the reply address to use
- when replying either through the state transition action
+ when replying either through the <em>transition action</em>
<c>{reply,From,Msg}</c> or by calling
<seealso marker="stdlib:gen_statem#reply/1"><c>gen_statem:reply</c></seealso>.
</item>
@@ -643,11 +680,13 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</seealso>
</tag>
<item>
- Generated by state transition action
- <seealso marker="stdlib:gen_statem#type-state_timeout">
+ Generated by <em>transition action</em>
+ <seealso marker="stdlib:gen_statem#type-timeout_action">
<c>{state_timeout,Time,EventContent}</c>
</seealso>
- state timer timing out.
+ state timer timing out. Read more in sections
+ <seealso marker="#Time-Outs">Time-Outs</seealso> and
+ <seealso marker="#State Time-Outs">State Time-Outs</seealso>.
</item>
<tag>
<seealso marker="stdlib:gen_statem#type-timeout_event_type">
@@ -655,11 +694,13 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</seealso>
</tag>
<item>
- Generated by state transition action
- <seealso marker="stdlib:gen_statem#type-generic_timeout">
+ Generated by <em>transition action</em>
+ <seealso marker="stdlib:gen_statem#type-timeout_action">
<c>{{timeout,Name},Time,EventContent}</c>
</seealso>
- generic timer timing out.
+ generic timer timing out. Read more in sections
+ <seealso marker="#Time-Outs">Time-Outs</seealso> and
+ <seealso marker="#Generic Time-Outs">Generic Time-Outs</seealso>.
</item>
<tag>
<seealso marker="stdlib:gen_statem#type-timeout_event_type">
@@ -667,12 +708,14 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</seealso>
</tag>
<item>
- Generated by state transition action
- <seealso marker="stdlib:gen_statem#type-event_timeout">
+ Generated by <em>transition action</em>
+ <seealso marker="stdlib:gen_statem#type-timeout_action">
<c>{timeout,Time,EventContent}</c>
</seealso>
(or its short form <c>Time</c>)
- event timer timing out.
+ event timer timing out. Read more in sections
+ <seealso marker="#Time-Outs">Time-Outs</seealso> and
+ <seealso marker="#Event Time-Outs">Event Time-Outs</seealso>.
</item>
<tag>
<seealso marker="stdlib:gen_statem#type-event_type">
@@ -680,10 +723,10 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</seealso>
</tag>
<item>
- Generated by state transition
- <seealso marker="stdlib:gen_statem#type-action">action</seealso>
- <c>{next_event,internal,EventContent}</c>.
- All event types above can also be generated using
+ Generated by <em>transition action</em>
+ <seealso marker="stdlib:gen_statem#type-enter_action"><c>{next_event,internal,EventContent}</c></seealso>.
+ All <em>event types</em> above can also be generated using
+ the <c>next_event</c> action:
<c>{next_event,EventType,EventContent}</c>.
</item>
</taglist>
@@ -696,14 +739,14 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<title>State Enter Calls</title>
<p>
The <c>gen_statem</c> behavior can if this is enabled,
- regardless of callback mode,
+ regardless of <em>callback mode</em>,
automatically
<seealso marker="stdlib:gen_statem#type-state_enter">
call the state callback
</seealso>
with special arguments whenever the state changes
so you can write state enter actions
- near the rest of the state transition rules.
+ near the rest of the <em>state transition</em> rules.
It typically looks like this:
</p>
<pre>
@@ -714,36 +757,140 @@ StateName(EventType, EventContent, Data) ->
... code for actions here ...
{next_state, NewStateName, NewData}.</pre>
<p>
- Since the state enter call is not an event there are restrictions
+ Since the <em>state enter call</em> is not an event there are restrictions
on the allowed return value and
<seealso marker="#State Transition Actions">State Transition Actions</seealso>.
You may not change the state,
<seealso marker="#Postponing Events">postpone</seealso>
this non-event, or
- <seealso marker="#Inserted Events">insert events</seealso>.
+ <seealso marker="#Inserted Events">insert any events</seealso>.
</p>
<p>
- The first state that is entered will get a state enter call
+ The first state that is entered
+ will get a <em>state enter call</em>
with <c>OldState</c> equal to the current state.
</p>
<p>
- You may repeat the state enter call using the <c>{repeat_state,...}</c>
+ You may repeat the <em>state enter call</em>
+ using the <c>{repeat_state,...}</c>
return value from the
- <seealso marker="#Event Handler">Event Handler</seealso>.
+ <seealso marker="#State Callback">state callback</seealso>.
In this case <c>OldState</c> will also be equal to the current state.
</p>
<p>
Depending on how your state machine is specified,
- this can be a very useful feature,
- but it forces you to handle the state enter calls in all states.
+ this can be a very useful feature, but it forces you to handle
+ the <em>state enter calls</em> in all states.
See also the
<seealso marker="#State Enter Actions">
State Enter Actions
</seealso>
- chapter.
+ section.
+ </p>
+ </section>
+
+<!-- =================================================================== -->
+
+ <section>
+ <marker id="Time-Outs" />
+ <title>Time-outs</title>
+ <p>
+ Time-outs in <c>gen_statem</c> are started from a
+ <seealso marker="#Transition Actions">
+ <em>transition action</em>
+ </seealso>
+ during a state transition that is when exiting from the
+ <seealso marker="#State Callback"><em>state callback</em></seealso>.
+ </p>
+ <p>
+ There are 3 types of time-outs in <c>gen_statem</c>:
+ </p>
+ <taglist>
+ <tag>
+ <seealso marker="stdlib:gen_statem#type-state_timeout">
+ <c>state_timeout</c>
+ </seealso>
+ </tag>
+ <item>
+ There is one
+ <seealso marker="#State Time-Outs">State Time-Out</seealso>
+ that is automatically cancelled by a <em>state change</em>.
+ </item>
+ <tag>
+ <seealso marker="stdlib:gen_statem#type-generic_timeout">
+ <c>{timeout, Name}</c>
+ </seealso>
+ </tag>
+ <item>
+ There are any number of
+ <seealso marker="#Generic Time-Outs">Generic Time-Outs</seealso>
+ differing by their <c>Name</c>.
+ They have no automatic cancelling.
+ </item>
+ <tag>
+ <seealso marker="stdlib:gen_statem#type-event_timeout">
+ <c>timeout</c>
+ </seealso>
+ </tag>
+ <item>
+ There is one
+ <seealso marker="#Event Time-Outs">Event Time-Out</seealso>
+ that is automatically cancelled by any event.
+ Note that
+ <seealso marker="#Postponing Events">postponed </seealso>
+ and
+ <seealso marker="#Inserted Events">inserted</seealso>
+ events cancel this timeout just as external events.
+ </item>
+ </taglist>
+ <p>
+ When a time-out is started any running time-out with the same tag,
+ <c>state_timeout</c>, <c>{timeout, Name}</c> or <c>timeout</c>,
+ is cancelled, that is the time-out is restarted with the new time.
+ </p>
+ <p>
+ All time-outs has got an <c>EventContent</c> that is part of the
+ <seealso marker="#Transition Actions">
+ <em>transition action</em>
+ </seealso>
+ that starts the time-out.
+ Different <c>EventContent</c>s does not create different time-outs.
+ The <c>EventContent</c> is delivered to the
+ <seealso marker="#State Callback"><em>state callback</em></seealso>
+ when the time-out expires.
</p>
+ <section>
+ <marker id="Cancelling a Time-Out" />
+ <title>Cancelling a Time-Out</title>
+ <p>
+ If a time-out is started with the time <c>infinity</c> it will
+ never time out, in fact it will not even be started, and any
+ running time-out with the same tag will be cancelled.
+ The <c>EventContent</c> will in this case be ignored,
+ so why not set it to <c>undefined</c>.
+ </p>
+ </section>
+ <section>
+ <marker id="Time-Out Zero" />
+ <title>Time-Out Zero</title>
+ <p>
+ If a time-out is started with the time <c>0</c> it will
+ actually not be started. Instead the time-out event will
+ immediately be inserted to be processed after any events
+ already enqueued, and before any not yet received external events.
+ Note that some time-outs are automatically cancelled
+ so if you for example combine
+ <seealso marker="#Postponing Events">postponing</seealso>
+ an event in a <em>state change</em> with starting an
+ <seealso marker="#Event Time-Outs">event time-out</seealso>
+ with time <c>0</c> there will be no timeout event inserted
+ since the event time-out is cancelled by the postponed
+ event that is delivered due to the state change.
+ </p>
+ </section>
</section>
+
<!-- =================================================================== -->
<section>
@@ -765,7 +912,7 @@ StateName(EventType, EventContent, Data) ->
</image>
<p>
This code lock state machine can be implemented using
- <c>gen_statem</c> with the following callback module:
+ <c>gen_statem</c> with the following <em>callback module</em>:
</p>
<code type="erl"><![CDATA[
-module(code_lock).
@@ -868,7 +1015,8 @@ start_link(Code) ->
<item>
<p>
The second argument, <c>?MODULE</c>, is the name of
- the callback module, that is, the module where the callback
+ the <em>callback module</em>, that is,
+ the module where the callback
functions are located, which is this module.
</p>
<p>
@@ -935,7 +1083,7 @@ init(Code) ->
<seealso marker="stdlib:gen_statem#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>
selects the
<seealso marker="#Callback Modes"><c>CallbackMode</c></seealso>
- for the callback module, in this case
+ for the <em>callback module</em>, in this case
<seealso marker="stdlib:gen_statem#type-callback_mode"><c>state_functions</c></seealso>.
That is, each state has got its own handler function:
</p>
@@ -1051,11 +1199,11 @@ open(state_timeout, lock, Data) ->
]]></code>
<p>
The timer for a state time-out is automatically cancelled
- when the state machine changes states. You can restart
- a state time-out by setting it to a new time, which cancels
- the running timer and starts a new. This implies that
- you can cancel a state time-out by restarting it with
- time <c>infinity</c>.
+ when the state machine does a <em>state change</em>.
+ You can restart a state time-out by setting it to a new time,
+ which cancels the running timer and starts a new.
+ This implies that you can cancel a state time-out
+ by restarting it with time <c>infinity</c>.
</p>
</section>
@@ -1137,7 +1285,7 @@ open(...) -> ... ;
care about what it is.
</p>
<p>
- If the common event handler needs to know the current state
+ If the common <em>state callback</em> needs to know the current state
a function <c>handle_common/4</c> can be used instead:
</p>
<code type="erl"><![CDATA[
@@ -1149,12 +1297,12 @@ open(...) -> ... ;
<!-- =================================================================== -->
<section>
- <marker id="One Event Handler" />
- <title>One Event Handler</title>
+ <marker id="One State Callback" />
+ <title>One State Callback</title>
<p>
If
<seealso marker="#Callback Modes">
- Callback Mode
+ <em>callback mode</em>
</seealso>
<c>handle_event_function</c> is used,
all events are handled in
@@ -1289,7 +1437,10 @@ stop() ->
You get either an event or a time-out, but not both.
</p>
<p>
- It is ordered by the state transition action
+ It is ordered by the
+ <seealso marker="#Transition Actions">
+ <em>transition action</em>
+ </seealso>
<c>{timeout,Time,EventContent}</c>, or just an integer <c>Time</c>,
even without the enclosing actions list
(the latter is a form inherited from <c>gen_fsm</c>.
@@ -1315,7 +1466,7 @@ locked(
]]></code>
<p>
Whenever we receive a button event we start an event time-out
- of 30 seconds, and if we get an event type <c>timeout</c>
+ of 30 seconds, and if we get an <em>event type</em> of <c>timeout</c>
we reset the remaining code sequence.
</p>
<p>
@@ -1327,7 +1478,7 @@ locked(
</p>
<p>
Note that an event time-out does not work well with
- when you have for example a status call as in
+ when you have for example a status call as in section
<seealso marker="#All State Events">All State Events</seealso>,
or handle unknown events, since all kinds of events
will cancel the event time-out.
@@ -1383,14 +1534,14 @@ open(cast, {button,_}, Data) ->
]]></code>
<p>
Specific generic time-outs can just as
- <seealso marker="#State Time-Outs">State Time-Outs</seealso>
+ <seealso marker="#State Time-Outs">state time-outs</seealso>
be restarted or cancelled
by setting it to a new time or <c>infinity</c>.
</p>
<p>
- In this particular case we do not need to cancel the timeout
- since the timeout event is the only possible reason to
- change the state from <c>open</c> to <c>locked</c>.
+ In this particular case we do not need to cancel the time-out
+ since the time-out event is the only possible reason to
+ do a <em>state change</em> from <c>open</c> to <c>locked</c>.
</p>
<p>
Instead of bothering with when to cancel a time-out,
@@ -1410,7 +1561,7 @@ open(cast, {button,_}, Data) ->
<seealso marker="erts:erlang#start_timer/4"><c>erlang:start_timer/3,4</c></seealso>.
Most time-out tasks can be performed with the
time-out features in <c>gen_statem</c>,
- but an example of one that can not is if you should need
+ but an example of one that cannot is if you should need
the return value from
<seealso marker="erts:erlang#cancel_timer/2"><c>erlang:cancel_timer(Tref)</c></seealso>, that is; the remaining time of the timer.
</p>
@@ -1442,7 +1593,7 @@ open(cast, {button,_}, Data) ->
]]></code>
<p>
Removing the <c>timer</c> key from the map when we
- change to state <c>locked</c> is not strictly
+ do a <em>state change</em> to <c>locked</c> is not strictly
necessary since we can only get into state <c>open</c>
with an updated <c>timer</c> map value. But it can be nice
to not have outdated values in the state <c>Data</c>!
@@ -1474,13 +1625,13 @@ open(cast, {button,_}, Data) ->
<p>
If you want to ignore a particular event in the current state
and handle it in a future state, you can postpone the event.
- A postponed event is retried after the state has
- changed, that is, <c>OldState =/= NewState</c>.
+ A postponed event is retried after a <em>state change</em>,
+ that is, <c>OldState =/= NewState</c>.
</p>
<p>
- Postponing is ordered by the state transition
- <seealso marker="#State Transition Actions">
- State Transition Action
+ Postponing is ordered by the
+ <seealso marker="#Transition Actions">
+ <em>transition action</em>
</seealso>
<c>postpone</c>.
</p>
@@ -1496,7 +1647,8 @@ open(cast, {button,_}, Data) ->
...
]]></code>
<p>
- Since a postponed event is only retried after a state change,
+ Since a postponed event is only retried
+ after a <em>state change</em>,
you have to think about where to keep a state data item.
You can keep it in the server <c>Data</c>
or in the <c>State</c> itself,
@@ -1505,7 +1657,7 @@ open(cast, {button,_}, Data) ->
(see section
<seealso marker="#Complex State">Complex State</seealso>)
with
- <seealso marker="#Callback Modes">Callback Mode</seealso>
+ <seealso marker="#Callback Modes"><em>callback mode</em></seealso>
<seealso marker="stdlib:gen_statem#type-callback_mode"><c>handle_event_function</c></seealso>.
If a change in the value changes the set of events that is handled,
then the value should be kept in the State.
@@ -1606,17 +1758,17 @@ do_unlock() ->
<seealso marker="stdlib:sys"><c>sys</c></seealso>
compatible behaviors must respond to system messages and therefore
do that in their engine receive loop,
- passing non-system messages to the callback module.
+ passing non-system messages to the <em>callback module</em>.
</p>
<p>
The
- <seealso marker="#State Transition Actions">
- State Transition Action
+ <seealso marker="#Transition Actions">
+ <em>transition action</em>
</seealso>
<c>postpone</c> is designed to model
selective receives. A selective receive implicitly postpones
any not received events, but the <c>postpone</c>
- state transition action explicitly postpones one received event.
+ <em>transition action</em> explicitly postpones one received event.
</p>
<p>
Both mechanisms have the same theoretical
@@ -1638,14 +1790,17 @@ do_unlock() ->
(described in the next section), especially if just
one or a few states has got state enter actions,
this is a perfect use case for the built in
- <seealso marker="#State Enter Calls">State Enter Calls</seealso>.
+ <seealso marker="#State Enter Calls"><em>state enter calls</em></seealso>.
</p>
<p>
You return a list containing <c>state_enter</c> from your
- <seealso marker="stdlib:gen_statem#Module:callback_mode/0"><c>callback_mode/0</c></seealso>
+ <seealso marker="stdlib:gen_statem#Module:callback_mode/0">
+ <c>callback_mode/0</c>
+ </seealso>
function and the <c>gen_statem</c> engine will call your
- state callback once with the arguments
- <c>(enter, OldState, ...)</c> whenever the state changes.
+ <em>state callback</em> once with an event
+ <c>(enter, OldState, ...)</c>
+ whenever it does a <em>state change</em>.
Then you just need to handle these event-like calls in all states.
</p>
<code type="erl"><![CDATA[
@@ -1700,8 +1855,8 @@ open(state_timeout, lock, Data) ->
It can sometimes be beneficial to be able to generate events
to your own state machine.
This can be done with the
- <seealso marker="#State Transition Actions">
- State Transition Action
+ <seealso marker="#Transition Actions">
+ <em>transition action</em>
</seealso>
<c>{next_event,EventType,EventContent}</c>.
</p>
@@ -1731,11 +1886,9 @@ open(state_timeout, lock, Data) ->
</p>
<p>
A variant of this is to use a
- <seealso marker="#Complex State">
- Complex State
- </seealso>
+ <seealso marker="#Complex State">complex state</seealso>
with
- <seealso marker="#One Event Handler">One Event Handler</seealso>.
+ <seealso marker="#One State Callback"><em>one state callback</em></seealso>.
The state is then modeled with for example a tuple
<c>{MainFSMState,SubFSMState}</c>.
</p>
@@ -1795,7 +1948,7 @@ open(internal, {button,_}, Data) ->
<title>Example Revisited</title>
<p>
This section includes the example after most of the mentioned
- modifications and some more using state enter calls,
+ modifications and some more using <em>state enter calls</em>,
which deserves a new state diagram:
</p>
<!-- The image is edited with dia in a .dia file,
@@ -1920,7 +2073,8 @@ terminate(_Reason, State, _Data) ->
This section describes what to change in the example
to use one <c>handle_event/4</c> function.
The previously used approach to first branch depending on event
- does not work that well here because of the state enter calls,
+ does not work that well here
+ because of the <em>state enter calls</em>,
so this example first branches depending on state:
</p>
<code type="erl"><![CDATA[
@@ -2059,7 +2213,7 @@ format_status(Opt, [_PDict,State,Data]) ->
<marker id="Complex State" />
<title>Complex State</title>
<p>
- The callback mode
+ The <em>callback mode</em>
<seealso marker="stdlib:gen_statem#type-callback_mode"><c>handle_event_function</c></seealso>
enables using a non-atom state as described in section
<seealso marker="#Callback Modes">Callback Modes</seealso>,
@@ -2068,7 +2222,7 @@ format_status(Opt, [_PDict,State,Data]) ->
<p>
One reason to use this is when you have a state item
that when changed should cancel the
- <seealso marker="#State Time-Outs">State Time-Out</seealso>,
+ <seealso marker="#State Time-Outs">state time-out</seealso>,
or one that affects the event handling
in combination with postponing events.
We will go for the latter and complicate the previous example
@@ -2104,7 +2258,7 @@ x
so it is not to be recognized as the lock button.
Or we can make the lock button part of the state so
when we then change the lock button in the locked state,
- the change becomes a state change
+ the change becomes a <em>state change</em>
and all postponed events are retried,
therefore the lock is immediately locked!
</p>
@@ -2258,7 +2412,7 @@ handle_event(enter, _OldState, {open,_}, _Data) ->
</p>
<p>
Another not uncommon scenario is to use the
- <seealso marker="#Event Time-Outs">Event Time-Out</seealso>
+ <seealso marker="#Event Time-Outs">event time-out</seealso>
to trigger hibernation after a certain time of inactivity.
There is also a server start option
<seealso marker="stdlib:gen_statem#type-hibernate_after_opt">
diff --git a/system/doc/efficiency_guide/binaryhandling.xml b/system/doc/efficiency_guide/binaryhandling.xml
index b500329ef9..d92da17390 100644
--- a/system/doc/efficiency_guide/binaryhandling.xml
+++ b/system/doc/efficiency_guide/binaryhandling.xml
@@ -384,8 +384,8 @@ export ERL_COMPILER_OPTIONS=bin_opt_info]]></code>
<p>The warnings look as follows:</p>
<code type="erl"><![CDATA[
-./efficiency_guide.erl:60: Warning: NOT OPTIMIZED: sub binary is used or returned
-./efficiency_guide.erl:62: Warning: OPTIMIZED: creation of sub binary delayed]]></code>
+./efficiency_guide.erl:60: Warning: NOT OPTIMIZED: binary is returned from the function
+./efficiency_guide.erl:62: Warning: OPTIMIZED: match context reused]]></code>
<p>To make it clearer exactly what code the warnings refer to, the
warnings in the following examples are inserted as comments
@@ -393,10 +393,10 @@ export ERL_COMPILER_OPTIONS=bin_opt_info]]></code>
<code type="erl"><![CDATA[
after_zero(<<0,T/binary>>) ->
- %% NOT OPTIMIZED: sub binary is used or returned
+ %% BINARY CREATED: binary is returned from the function
T;
after_zero(<<_,T/binary>>) ->
- %% OPTIMIZED: creation of sub binary delayed
+ %% OPTIMIZED: match context reused
after_zero(T);
after_zero(<<>>) ->
<<>>.]]></code>
diff --git a/system/doc/embedded/starting.xml b/system/doc/embedded/starting.xml
index 11bf9b412a..6888f9c959 100644
--- a/system/doc/embedded/starting.xml
+++ b/system/doc/embedded/starting.xml
@@ -231,7 +231,7 @@ exec $BINDIR/erlexec -boot $RELDIR/$VSN/start -config $RELDIR/$VSN/sys $* <
<p>If a diskless and/or read-only client node with the SASL
configuration parameter <c>static_emulator</c> set to <c>true</c>
is about to start the <c>-boot</c> and <c>-config</c> flags must be
- changed. As such a client can not read a new <c>start_erl.data</c>
+ changed. As such a client cannot read a new <c>start_erl.data</c>
file (the file is not possible to change dynamically) the boot and
config files is always fetched from the same place (but with a new
contents if a new release has been installed). The
diff --git a/lib/otp_mibs/mibs/Makefile b/system/doc/general_info/Makefile
index 11d790d014..539075280e 100644
--- a/lib/otp_mibs/mibs/Makefile
+++ b/system/doc/general_info/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1997-2016. All Rights Reserved.
+# Copyright Ericsson AB 1996-2018. All 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,73 +17,83 @@
#
# %CopyrightEnd%
#
-
+#
include $(ERL_TOP)/make/target.mk
include $(ERL_TOP)/make/$(TARGET)/otp.mk
# ----------------------------------------------------
# Application version
+# ----------------------------------------------------
+include $(ERL_TOP)/erts/vsn.mk
+#VSN=$(SYSTEM_VSN)
+
+APPLICATION=otp-system-documentation
+XMLDIR := $(XMLDIR)/general_info
+# ----------------------------------------------------
# Release directory specification
# ----------------------------------------------------
-include ../vsn.mk
-ifdef SASL_VSN
-VSN=$(SASL_VSN)
-RELSYSDIR = $(RELEASE_PATH)/lib/sasl-$(VSN)
-else
-VSN=$(OTP_MIBS_VSN)
-RELSYSDIR = $(RELEASE_PATH)/lib/otp_mibs-$(VSN)
-endif
+RELSYSDIR = "$(RELEASE_PATH)/doc/general_info"
# ----------------------------------------------------
# Target Specs
# ----------------------------------------------------
+XML_PART_FILES = part.xml
+
+include xmlfiles.mk
+
+XML_CHAPTER_FILES=$(GENERAL_INFO_CHAPTER_FILES)
+
+TOPDOCDIR=..
-MIB_FILES= OTP-REG.mib OTP-TC.mib OTP-MIB.mib
-FUNCS_FILES = OTP-MIB.funcs
+BOOK_FILES = book.xml
-BIN_TARGETS= $(MIB_FILES:%.mib=$(SNMP_BIN_TARGET_DIR)/%.bin)
-HRL_TARGETS= $(MIB_FILES:%.mib=$(SNMP_HRL_TARGET_DIR)/%.hrl)
-V1_MIB_FILES= $(MIB_FILES:%.mib=v1/%.mib.v1)
+GIF_FILES =
-TARGET_FILES= $(BIN_TARGETS) $(HRL_TARGETS) $(V1_MIB_FILES)
+XML_FILES = \
+ $(BOOK_FILES) $(XML_CHAPTER_FILES) \
+ $(XML_PART_FILES)
# ----------------------------------------------------
-# FLAGS
+
+HTMLDIR = ../html/general_info
+
+HTML_UG_FILE = $(HTMLDIR)/users_guide.html
+
+# ----------------------------------------------------
+# FLAGS
# ----------------------------------------------------
-SNMP_FLAGS = -I ../priv/mibs
+XML_FLAGS +=
+DVIPS_FLAGS +=
# ----------------------------------------------------
# Targets
# ----------------------------------------------------
-debug opt: $(TARGET_FILES)
+docs: html
-clean:
- rm -f $(TARGET_FILES)
- rm -f core
+local_docs: PDFDIR=../../pdf
-docs:
+html: $(GIF_FILES) $(HTML_UG_FILE)
-# ----------------------------------------------------
-# Special Build Targets
-# ----------------------------------------------------
+debug opt:
-v1/%.mib.v1: %.mib
- $(gen_verbose)$(ERL_TOP)/lib/snmp/bin/snmp-v2tov1 -o $@ $<
+clean clean_docs:
+ rm -f $(XMLDIR)/*.xml
+ rm -f $(HTMLDIR)/*.gif $(HTMLDIR)/*.html
+ rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
+ rm -f errs core *~
# ----------------------------------------------------
# Release Target
# ----------------------------------------------------
include $(ERL_TOP)/make/otp_release_targets.mk
-release_spec: opt
- $(INSTALL_DIR) "$(RELSYSDIR)/mibs"
- $(INSTALL_DIR) "$(RELSYSDIR)/mibs/v1"
- $(INSTALL_DATA) $(MIB_FILES) $(FUNCS_FILES) "$(RELSYSDIR)/mibs"
- $(INSTALL_DATA) $(V1_MIB_FILES) "$(RELSYSDIR)/mibs/v1"
- $(INSTALL_DIR) "$(RELSYSDIR)/include"
- $(INSTALL_DATA) $(HRL_TARGETS) "$(RELSYSDIR)/include"
- $(INSTALL_DIR) "$(RELSYSDIR)/priv/mibs"
- $(INSTALL_DATA) $(BIN_TARGETS) "$(RELSYSDIR)/priv/mibs"
-
-release_docs_spec:
+release_docs_spec: docs
+# $(INSTALL_DIR) "$(RELEASE_PATH)/pdf"
+# $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELEASE_PATH)/pdf"
+ $(INSTALL_DIR) $(RELSYSDIR)
+ $(INSTALL_DATA) $(GIF_FILES) $(HTMLDIR)/*.html \
+ $(RELSYSDIR)
+
+release_spec:
+
diff --git a/lib/otp_mibs/doc/src/book.xml b/system/doc/general_info/book.xml
index 482da46876..ca9b5fae39 100644
--- a/lib/otp_mibs/doc/src/book.xml
+++ b/system/doc/general_info/book.xml
@@ -4,7 +4,7 @@
<book xmlns:xi="http://www.w3.org/2001/XInclude">
<header titlestyle="normal">
<copyright>
- <year>2003</year><year>2016</year>
+ <year>2019</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -22,28 +22,23 @@
</legalnotice>
- <title>OTP_Mibs</title>
- <prepared>Ingela Anderton</prepared>
+ <title>General Information</title>
+ <prepared>OTP Team</prepared>
<docno></docno>
- <date>2003-04-15</date>
- <rev>A</rev>
+ <date>2019-02-23</date>
+ <rev></rev>
+ <file>book.xml</file>
</header>
<insidecover>
</insidecover>
- <pagetext>OTP_Mibs application</pagetext>
+ <pagetext>General Information</pagetext>
<preamble>
<contents level="2"></contents>
</preamble>
<parts lift="no">
<xi:include href="part.xml"/>
</parts>
- <applications>
- <xi:include href="ref_man.xml"/>
- </applications>
- <releasenotes>
- <xi:include href="notes.xml"/>
- </releasenotes>
+ <listofterms></listofterms>
<index></index>
</book>
-
diff --git a/system/doc/general_info/deprecations.xml b/system/doc/general_info/deprecations.xml
new file mode 100644
index 0000000000..ccfd553bad
--- /dev/null
+++ b/system/doc/general_info/deprecations.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>2019</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>Deprecations</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ <file>deprecations.xml</file>
+ </header>
+ <section>
+ <title>Introduction</title>
+ <p>This document aim to list all deprecated functionality in Erlang/OTP. It
+ was introduced as of OTP 22, and have not yet been updated with all old
+ deprecations. Deprecations made in other parts of the documentation are of
+ course still valid. For more information regarding the strategy regarding
+ deprecations see the documentation of
+ <seealso marker="../system_principles/misc#deprecation">Support, Compatibility,
+ Deprecations, and Removal</seealso>.</p>
+ </section>
+ <section>
+ <marker id="OTP-22"/>
+ <title>OTP 22</title>
+ <section>
+ <title>VxWorks Support</title>
+ <p>Some parts of OTP has had limited VxWorks support, such as for
+ example <seealso marker="erl_interface:index"><c>erl_interface</c></seealso>.
+ This support is now deprecated and has also been
+ <seealso marker="scheduled_for_removal#OTP-23">scheduled for removal</seealso>.</p>
+ </section>
+ <section>
+ <title>Legacy parts of erl_interface</title>
+ <p>The old legacy <seealso marker="erl_interface:index"><c>erl_interface</c></seealso>
+ library (functions with prefix <c>erl_</c>) is deprecated as of OTP 22. These
+ parts of <c>erl_interface</c> has been informally deprecated
+ for a very long time. You typically want to replace the usage of
+ the <c>erl_interface</c> library with the use of the <c>ei</c> library
+ which also is part of the <c>erl_interface</c> application. The old legacy
+ <seealso marker="erl_interface:index"><c>erl_interface</c></seealso>
+ library has also been <seealso marker="scheduled_for_removal#OTP-23">scheduled
+ for removal</seealso>.</p>
+ </section>
+ </section>
+ <section>
+ <marker id="OTP-18"/>
+ <title>OTP 18</title>
+ <section>
+ <title>erlang:now()</title>
+ <p>New time functionality and a new time API was introduced. For more information
+ see the <seealso marker="erts:time_correction">Time and Time Correction</seealso>
+ chapter in the ERTS User's guide and specifically the
+ <seealso marker="erts:time_correction#Dos_and_Donts">Dos and Donts</seealso>
+ section on how to replace usage of <c>erlang:now()</c>.</p>
+ </section>
+ </section>
+</chapter>
diff --git a/lib/otp_mibs/doc/src/part.xml b/system/doc/general_info/part.xml
index 0a8ddce268..fead7d58e7 100644
--- a/lib/otp_mibs/doc/src/part.xml
+++ b/system/doc/general_info/part.xml
@@ -4,7 +4,7 @@
<part xmlns:xi="http://www.w3.org/2001/XInclude">
<header>
<copyright>
- <year>2003</year><year>2016</year>
+ <year>2019</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -22,18 +22,13 @@
</legalnotice>
- <title>OTP_Mibs User's Guide</title>
- <prepared>Ingela Anderton</prepared>
+ <title>General Information</title>
+ <prepared>OTP Team</prepared>
<docno></docno>
- <date>2002-09-17</date>
- <rev>A</rev>
+ <date>2019-02-23</date>
+ <rev></rev>
+ <file>part.xml</file>
</header>
- <description>
- <p>The <em>OTP_Mibs</em> application provides an SNMP management
- information base for Erlang nodes.</p>
- </description>
- <xi:include href="introduction.xml"/>
- <xi:include href="mibs.xml"/>
+ <xi:include href="deprecations.xml"/>
+ <xi:include href="scheduled_for_removal.xml"/>
</part>
-
-
diff --git a/system/doc/general_info/scheduled_for_removal.xml b/system/doc/general_info/scheduled_for_removal.xml
new file mode 100644
index 0000000000..a9421df385
--- /dev/null
+++ b/system/doc/general_info/scheduled_for_removal.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>2019</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>Scheduled for Removal</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ <file>scheduled_for_removal.xml</file>
+ </header>
+ <section>
+ <title>Introduction</title>
+ <p>This document list all functionality in Erlang/OTP that currently are
+ scheduled for removal. For more information regarding the strategy regarding
+ removal of functionality see the documentation of
+ <seealso marker="../system_principles/misc#removal">Support, Compatibility,
+ Deprecations, and Removal</seealso>.</p>
+ </section>
+ <section>
+ <marker id="OTP-23"/>
+ <title>OTP 23</title>
+ <section>
+ <title>VxWorks Support</title>
+ <p>Some parts of OTP has had limited VxWorks support, such as for
+ example <seealso marker="erl_interface:index"><c>erl_interface</c></seealso>.
+ This support will be removed as of OTP 23. This limited support
+ was formally deprecated as of OTP 22</p>
+ </section>
+ <section>
+ <title>Legacy parts of erl_interface</title>
+ <p>The old legacy <seealso marker="erl_interface:index"><c>erl_interface</c></seealso>
+ library (functions with prefix <c>erl_</c>) will be removed as of
+ OTP 23. These parts of <c>erl_interface</c> has been informally deprecated
+ for a very long time, and was formally deprecated in OTP 22. You typically
+ want to replace the usage of the <c>erl_interface</c> library with the use
+ of the <c>ei</c> library which also is part of the <c>erl_interface</c>
+ application.</p>
+ </section>
+ </section>
+</chapter>
diff --git a/lib/otp_mibs/Makefile b/system/doc/general_info/xmlfiles.mk
index 64bd683c7a..eab1632a4f 100644
--- a/lib/otp_mibs/Makefile
+++ b/system/doc/general_info/xmlfiles.mk
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2003-2016. All Rights Reserved.
+# Copyright Ericsson AB 2009-2018. All 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,21 +17,6 @@
#
# %CopyrightEnd%
#
-include $(ERL_TOP)/make/target.mk
-include $(ERL_TOP)/make/$(TARGET)/otp.mk
-
-# ----------------------------------------------------
-# Macros
-# ----------------------------------------------------
-
-SUB_DIRECTORIES = src mibs doc/src
-
-include vsn.mk
-VSN = $(OTP_MIBS_VSN)
-
-SPECIAL_TARGETS =
-
-# ----------------------------------------------------
-# Default Subdir Targets
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_subdir.mk
+GENERAL_INFO_CHAPTER_FILES = \
+ deprecations.xml \
+ scheduled_for_removal.xml
diff --git a/lib/otp_mibs/doc/man3/.gitignore b/system/doc/html/general_info/.gitignore
index e69de29bb2..e69de29bb2 100644
--- a/lib/otp_mibs/doc/man3/.gitignore
+++ b/system/doc/html/general_info/.gitignore
diff --git a/system/doc/oam/oam_intro.xml b/system/doc/oam/oam_intro.xml
index ead8c026b9..3d08a5f3b1 100644
--- a/system/doc/oam/oam_intro.xml
+++ b/system/doc/oam/oam_intro.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>1997</year><year>2017</year>
+ <year>1997</year><year>2018</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -178,7 +178,7 @@
<section>
<title>MIB Structure</title>
<p>The top-level OTP MIB is called <c>OTP-REG</c> and it is
- included in the SASL application. All other OTP MIBs
+ included in the SNMP application. All other OTP MIBs
import some objects from this MIB.</p>
<p>Each MIB is contained in one application. The MIB text
@@ -186,67 +186,44 @@
the application directory. The generated <c>.hrl</c> files
with constant declarations are stored under
<c><![CDATA[include/<MIB>.hrl]]></c>, and the compiled MIBs
- are stored under <c><![CDATA[priv/mibs/<MIB>.bin]]></c>.
- For example, the <c>OTP-MIB</c> is included in the
- SASL application:</p>
+ are stored under <c><![CDATA[priv/mibs/<MIB>.bin]]></c>. </p>
- <code type="none">
-sasl-1.3/mibs/OTP-MIB.mib
-include/OTP-MIB.hrl
-priv/mibs/OTP-MIB.bin</code>
-
- <p>An application that needs to import this MIB into another
+ <p>An application that needs to import an MIB into another
MIB is to use the <c>il</c> option to the SNMP MIB compiler:</p>
<code type="none">
-snmp:c("MY-MIB", [{il, ["sasl/priv/mibs"]}]).</code>
+snmp:c("MY-MIB", [{il, ["snmp/priv/mibs"]}]).</code>
- <p>If the application needs to include the generated
+ <p>If the application needs to include a generated
<c>.hrl</c> file, it is to use the <c>-include_lib</c>
directive to the Erlang compiler:</p>
<code type="none">
-module(my_mib).
--include_lib("sasl/include/OTP-MIB.hrl").</code>
+-include_lib("snmp/include/OTP-REG.hrl").</code>
- <p>The following MIBs are defined in the OTP system:</p>
+ <p>Here is a list of some of the MIBs defined in the OTP system:</p>
<list type="bulleted">
- <item><p><c>OTP-REG</c> (in SASL) contains the top-level
+ <item><p><c>OTP-REG</c> (in SNMP) contains the top-level
OTP registration objects, used by all other MIBs.</p></item>
- <item><p><c>OTP-TC</c> (in SASL) contains the general
+ <item><p><c>OTP-TC</c> (in SNMP) contains the general
Textual Conventions, which can be used by any other MIB.</p></item>
- <item><p><c>OTP-MIB</c> (in SASL) contains objects for
- instrumentation of the Erlang nodes, the Erlang machines,
- and the applications in the system.</p></item>
- <item><p><c>OTP-OS-MON-MIB</c> (in <c>oc_mon</c>) contains
- objects for instrumentation of disk, memory, and CPU use
- of the nodes in the system.</p></item>
<item><p><c>OTP-SNMPEA-MIB</c> (in <c>snmp</c>)
contains objects for instrumentation and control of the extensible
SNMP agent itself. The agent also implements the standard SNMPv2-MIB
(or v1 part of MIB-II, if SNMPv1 is used).</p></item>
- <item><p><c>OTP-EVA-MIB</c> (in <c>eva</c>) contains objects
- for instrumentation and control of the events and alarms in
- the system.</p></item>
- <item><p><c>OTP-LOG-MIB</c> (in <c>eva</c>) contains objects
- for instrumentation and control of the logs and FTP transfer of
- logs.</p></item>
- <item><p><c>OTP-EVA-LOG-MIB</c> (in <c>eva</c>) contains objects
- for instrumentation and control of the events and alarm logs
- in the system.</p></item>
- <item><p><c>OTP-SNMPEA-LOG-MIB</c> (in <c>eva</c>) contains
- objects for instrumentation and control of the SNMP audit
- trail log in the system.</p></item>
</list>
<p>The different applications use different strategies for
loading the MIBs into the agent. Some MIB implementations are
code-only, while others need a server. One way, used by the
code-only MIB implementations, is for the user to call a
- function such as <c>otp_mib:load(Agent)</c> to load the MIB,
- and <c>otp_mib:unload(Agent)</c> to unload the MIB. See the
- manual page for each application for a description of how
- to load each MIB.</p>
+ function such as
+ <c>snmpa:unload_mibs(Agent, [Mib])</c>
+ to load the MIB, and
+ <c>snmpa:unload_mibs(Agent, [Mib])</c>
+ to unload the MIB. See the manual page for each application for
+ a description of how to load each MIB.</p>
</section>
</section>
</chapter>
diff --git a/system/doc/reference_manual/expressions.xml b/system/doc/reference_manual/expressions.xml
index 76b3e92937..8c47070890 100644
--- a/system/doc/reference_manual/expressions.xml
+++ b/system/doc/reference_manual/expressions.xml
@@ -578,6 +578,7 @@ number &lt; atom &lt; reference &lt; fun &lt; port &lt; pid &lt; tuple &lt; map
ascending term order and then by values in key order.
In maps key order integers types are considered less than floats types.
</p>
+ <p>Atoms are compared using their string value, codepoint by codepoint.</p>
<p>When comparing an integer to a float, the term with the lesser
precision is converted into the type of the other term, unless the
operator is one of <c>=:=</c> or <c>=/=</c>. A float is more precise than
diff --git a/system/doc/system_principles/misc.xml b/system/doc/system_principles/misc.xml
index dd6c2a1336..ed3675d180 100644
--- a/system/doc/system_principles/misc.xml
+++ b/system/doc/system_principles/misc.xml
@@ -34,6 +34,14 @@
</header>
<section>
+ <title>Introduction</title>
+ <p>This document describes strategy regarding supported Releases,
+ compatibility, deprecations and removal of functionality. This
+ document was introduced in OTP 21. Actions taken regarding these
+ issues before OTP 21 did not adhere this document.</p>
+ </section>
+
+ <section>
<marker id="supported_releases"/>
<title>Supported Releases</title>
<p>
diff --git a/system/doc/top/Makefile b/system/doc/top/Makefile
index 3267bf96df..e3f9c4710a 100644
--- a/system/doc/top/Makefile
+++ b/system/doc/top/Makefile
@@ -39,6 +39,7 @@ INFO_FILES = ../../../README.md ../../COPYRIGHT PR.template
TOPDOCDIR=.
+include ../general_info/xmlfiles.mk
include ../installation_guide/xmlfiles.mk
include ../system_principles/xmlfiles.mk
include ../embedded/xmlfiles.mk
@@ -56,6 +57,7 @@ XML_FILES = \
$(BOOK_FILES)
XML_GUIDE_FILES = \
+ $(GENERAL_INFO_CHAPTER_FILES:%=general_info/%) \
$(INST_GUIDE_CHAPTER_FILES:%=installation_guide/%) \
$(INST_GUIDE_CHAPTER_GEN_FILES:%=installation_guide/%) \
$(SYSTEM_PRINCIPLES_CHAPTER_FILES:%=system_principles/%) \
@@ -79,6 +81,7 @@ XML_GUIDE_FILES = \
XML_GEN_FILES = \
$(XML_GUIDE_FILES:%=$(XMLDIR)/%) \
+ $(XMLDIR)/general_info/part.xml \
$(XMLDIR)/installation_guide/part.xml \
$(XMLDIR)/system_principles/part.xml \
$(XMLDIR)/embedded/part.xml \
@@ -91,7 +94,7 @@ XML_GEN_FILES = \
$(XMLDIR)/oam/part.xml
-XMLLINT_SRCDIRS= $(XMLDIR)/installation_guide:$(XMLDIR)/system_principles:$(XMLDIR)/embedded:$(XMLDIR)/getting_started:$(XMLDIR)/reference_manual:$(XMLDIR)/programming_examples:$(XMLDIR)/efficiency_guide:$(XMLDIR)/tutorial:$(XMLDIR)/design_principles:$(XMLDIR)/oam
+XMLLINT_SRCDIRS= $(XMLDIR)/general_info:$(XMLDIR)/installation_guide:$(XMLDIR)/system_principles:$(XMLDIR)/embedded:$(XMLDIR)/getting_started:$(XMLDIR)/reference_manual:$(XMLDIR)/programming_examples:$(XMLDIR)/efficiency_guide:$(XMLDIR)/tutorial:$(XMLDIR)/design_principles:$(XMLDIR)/oam
HTMLDIR= ../html
PDFREFDIR= pdf
diff --git a/system/doc/top/book.xml b/system/doc/top/book.xml
index 61e75591ef..64c1e4caf6 100644
--- a/system/doc/top/book.xml
+++ b/system/doc/top/book.xml
@@ -36,6 +36,7 @@
<contents level="2"></contents>
</preamble>
<parts lift="no">
+ <xi:include href="../xml/general_info/part.xml"/>
<xi:include href="../xml/installation_guide/part.xml"/>
<xi:include href="../xml/system_principles/part.xml"/>
<xi:include href="../xml/embedded/part.xml"/>
diff --git a/system/doc/top/templates/index.html.src b/system/doc/top/templates/index.html.src
index 747d19cf7e..3b61052b99 100644
--- a/system/doc/top/templates/index.html.src
+++ b/system/doc/top/templates/index.html.src
@@ -41,6 +41,8 @@ limitations under the License.
<ul class="section-links">
<li><a href="applications.html">Applications</a></li>
<li><a href="man_index.html" class="modules">Modules</a></li>
+ <li><a href="general_info/deprecations.html" class="modules">Deprecations</a></li>
+ <li><a href="general_info/scheduled_for_removal.html" class="modules">Scheduled for Removal</a></li>
</ul>
<ul class="expand-collapse-items">
diff --git a/lib/otp_mibs/doc/pdf/.gitignore b/system/doc/xml/general_info/.gitignore
index e69de29bb2..e69de29bb2 100644
--- a/lib/otp_mibs/doc/pdf/.gitignore
+++ b/system/doc/xml/general_info/.gitignore